Skip to content

Added demo desktop application - #478

Open
Hecatron wants to merge 298 commits into
blazorblueprintui:mainfrom
Hecatron-Forks:main
Open

Hecatron wants to merge 298 commits into
blazorblueprintui:mainfrom
Hecatron-Forks:main

Conversation

@Hecatron

@Hecatron Hecatron commented Aug 7, 2026

Copy link
Copy Markdown

Description

Hi,
I'm not sure if you want to include something like this
but I've created a simple demo desktop application based on blazor blueprint
It's using PhotinoX / PhotinoX.Blazor to show it's use as a desktop app

So far I've tried it on Windows and Linux
It's targeting dotnet 10 with it's own solution since they changed the way asset paths are handled a bit between dotnet 8 and 10 I think

mathewtaylor and others added 30 commits April 3, 2026 16:39
Had to add this to my imports to get MainLayout to pickup `BpPortalHost`
Update README with out of box working missing code
…ui#291)

* feat: add BbComboboxGroup for grouped combobox items

Add BbComboboxGroup component that wraps BbCommandGroup to support
labeled, filterable item groups in compositional mode. Groups
automatically hide when search filters out all their items. Label
styling is customizable via LabelClass parameter.

* docs: update changelog for 2026-04-06 and 2026-04-07
…lazorblueprintui#296)

The form field wrapper unconditionally wired a local SearchQueryChanged
handler to the inner BbCombobox, which made BbCombobox's auto-detection
(`SearchQueryChanged.HasDelegate`) flip into external-filtering mode and
install PassthroughFilter — bypassing the internal text filter entirely.
The symptom was most visible inside an EditForm with a validator, where
the extra re-renders also reset BbCommandInput's display value.

Only wire the interception handler (and forward SearchQuery) when the
consumer is actually binding SearchQueryChanged, matching the pattern
used by BbFormFieldMultiSelect.
…96-combobox-search-filter

fix: BbFormFieldCombobox search filter bypassed when wrapper is used (blazorblueprintui#296)
…orblueprintui#293)

Drops the redundant outer wrapper div and passes class +
AdditionalAttributes straight to the Tabs primitive, matching the
Accordion/Dialog wrapping pattern. Previously attributes like id and
data-* set on BbTabs never reached the primitive's root element.
…93-bbtabs-additional-attributes

fix: BbTabs forwards AdditionalAttributes to primitive root (blazorblueprintui#293)
fix: update source property from Avatar component
…eprintui#311)

Adds flex-wrap to the filter condition row and to the inner range/InLast
sub-rows so the fixed-width selects/inputs wrap onto multiple lines
instead of overflowing the parent card on mobile widths.
…eprintui#309)

The toolbar's Filter popover shipped a "to be implemented in Phase 2"
placeholder UI that consumers had no way to disable. Removed the popover
and the unused DataTable.Filter / DataTable.FilterColumns localization
keys. Updated XML docs to describe Filterable as scoping the global
search and to point users at BbDataGrid for per-column filter UIs.
Wraps the Tailwind utility output and authored component styles inside a
dedicated `bb` cascade layer declared as the strongest layer:
@layer properties, theme, base, components, utilities, bb. Component
utilities like .md:flex on the sidebar now win the cascade regardless of
which <link> tag a consumer's app.css loads in relative to ours, fixing
the class of bug seen in blazorblueprintui#308 once and for all.

@media (prefers-reduced-motion) and @Keyframes stay at top level so they
remain authoritative. tw-animate.css is imported unlayered because it
uses @Utility (a Tailwind v4 source directive that cannot be nested
inside a layer() import); its animation utilities land in the standard
utilities layer.

Consumer override path: !important (which inverts layer priority) or
authored unlayered CSS.
…ss-cascade-and-datatable-cleanup

fix: scope component CSS in @layer bb + DataTable / FilterBuilder polish
release: Primitives v3.10.0 + Components v3.10.0
- Pin SDK to 8.0.421 via global.json so newer SDKs (9/10) don't break the Razor source generator on DashboardGridDemo
- Mark auto-generated icon data files (Feather/Heroicons/Lucide) with // <auto-generated/> + #nullable enable so analyzers (CA1859) skip them
- Fix CA1861 in DataGridHierarchyDemo by lifting inline string[] to a static readonly field
- gitignore tailwindcss-macos binary alongside the existing Windows/Linux entries
v3.10.0 wrapped Tailwind's entire output in @layer bb to make BB component
utilities win the cascade against consumer Tailwind output (fixing blazorblueprintui#308).
The side effect was that consumer-applied utilities like grid-cols-* and
col-span-* were also being defeated by BB's emitted utilities, squishing
grid layouts on consumer sites and on our own docs.

Drop `layer(bb)` from the Tailwind import so BB's bulk output now lives
in Tailwind's native layers and ties with the consumer's at parity --
specificity and DOM order decide as normal. Keep @layer bb as the
priority layer for BB's hand-authored attribute-selector component rules,
which is what it should have been from the start.

To preserve the blazorblueprintui#308 sidebar fix without relying on layer-wide priority,
add a higher-specificity authored rule inside @layer bb:

  aside[data-variant][data-side] { display: none; }
  @media (min-width: 768px) {
    aside[data-variant][data-side] { display: flex; }
  }

Specificity (0,2,1) beats class-based .md\:flex (0,1,0) regardless of
which layer it lands in, so the sidebar's mobile/desktop split is now
authoritative even if a consumer's app.css emits a competing .hidden or
.md:flex rule.

Verified against Kevin Mallinson's blazorblueprintui#308 repro repo with project refs to
the patched library, and against the squished-grid scenario on the local
demo site. Likely also resolves blazorblueprintui#314 (same root cause).
_Imports.razor compiles into a public ComponentBase-derived class as a
build-time Razor convention; it is not a real API surface. Whether it
gets included in the assembly's exported types was effectively a coin
flip across .NET SDK versions, which broke the snapshot tests when
building on macOS with .NET 8 SDK 8.0.421 even though no real public API
had changed.

Filter underscore-prefixed type names out of the component scan so the
snapshot reflects designed APIs only.
…ueprintui#316)

BbToastProvider always renders its toast container div, even with zero
toasts. The container is position:fixed, pinned bottom-0 right-0, up to
420px wide, with z-[100] and p-4 padding — so the empty box sat over the
bottom-right corner of every page and swallowed all clicks there
(reported eating the bottom half of the "Send" button in the chat demo).

Add pointer-events-none to the container so clicks pass through it.
BbToast already carries pointer-events-auto on its own root, so toasts
themselves stay fully interactive — the container half of that pattern
was simply missing. Fixes both the main stack and per-toast position
containers, which share GetContainerCssClass.
mathewtaylor and others added 28 commits August 4, 2026 14:49
…52-copytext-tooltip-clipping

fix(copytext): render the tooltip through the portal so containers can't clip it
The text-input family set focus-visible:outline-none (or focus:outline-none)
and drew nothing in its place — the browser's native ring was removed and
never replaced, so a focused input looked identical to an unfocused one.
A WCAG 2.4.7 failure, and per discussion blazorblueprintui#355 the reason three people
picked a different library.

An omission, not a decision: BbButton, BbCheckbox, BbSwitch, BbTabs,
BbToggle, BbSlider, BbCalendar, BbRadioGroupItem and BbPaginationLink had
all along used the ring-2/ring-ring/ring-offset-2 pattern. It now applies
to the input family too.

BbTextarea was a third case — it indicated focus by tinting the border
via focus-visible:border-ring — and now matches. BbDrawerItem,
BbDatePickerInput and BbResponsiveNavTrigger had the ring but not the
offset.

Left deliberately alone: sidebar components ring in ring-sidebar-ring,
their own theme token, and skip the offset so it doesn't bleed into
adjacent rows; event-calendar chips skip it for density; BbBubbleContent
keeps its softer inline treatment; overlay containers and roving-focus
menu items keep outline-none, being focused programmatically or indicating
state through data-[highlighted].

Verified in a browser: computed box-shadow goes from none to a 2px offset
plus 2px ring in the theme colour, on both BbInput and BbTextarea.
The library's form rows leave a 3px gap between a label and its control,
while ring-offset-2 extends 4px — so the offset ring overlapped the label
by a pixel on every labelled field. Measured across 11 labelled textareas:
all 11 overlapping before, none after.

Drops ring-offset-2 from the input family only. Buttons, checkboxes,
switches, tabs, toggles, sliders and the calendar keep theirs — they are
usually standalone with margin around them, whereas inputs sit in labelled
rows with tight vertical rhythm. shadcn/ui splits it the same way.

Still a clear WCAG 2.4.7 pass: a 2px ring in the theme colour against the
control's border.
…55-input-focus-styling

fix(a11y): give inputs a visible focus indicator
…d-grid-disposed-ref-callbacks

fix(DashboardGrid): stop stale JS->.NET callbacks to disposed grids
…dicators

Both bugs this session were omissions in markup, not logic errors, so unit
tests of the behaviour would have passed while the defects remained. These
scan component source as text instead.

CultureInvariantStyleTests — flags a numeric value interpolated into an
inline style without InvariantCulture. Razor renders a bare @value with
CurrentCulture, so a comma-decimal locale produces invalid CSS that the
browser discards silently. That shipped twice, in BbColorPicker (blazorblueprintui#436) and
BbRangeSlider (blazorblueprintui#443) a fortnight apart; the second was only found by
sweeping the tree by hand.

FocusIndicatorTests — flags a component that removes the focus outline
without drawing a replacement, a WCAG 2.4.7 failure that the whole input
family carried until blazorblueprintui#457. Accepts any recognised affordance, not just a
ring: menu items indicate focus with a background change, which is correct
for a roving-focus list. Overlay panels are exempt by suffix since they are
focused programmatically. A short allowlist covers the rest, each entry
carrying a reason, and a second test fails if an entry stops being needed —
a stale exemption hides a regression.

Verified by mutation: reintroducing the RangeSlider interpolation and
removing BbInput's ring each turn the relevant test red.

No new dependencies; these read the working tree, so SourceTree locates the
repo root by walking up from the test assembly.
…convention-guards

test: add convention guards for culture-invariant styles and focus indicators
BbScatter, BbLine and BbArea emitted a flat list of Y values, which
leaves ECharts to derive each point's X from its index. That is correct
for a category axis and wrong for a value axis, which ignores the data
handed to it — so X values passed to BbXAxis via DataKey were dropped
and points were plotted against ordinal position regardless.

The two halves hid each other: the axis looked configured, the labels
came from the right property, and the result was a Y-over-position plot
wearing X's labels. No arrangement of the existing parameters produced a
genuine X:Y plot, including the one advertised in BbScatter's own XML
documentation.

Add XDataKey to the three cartesian series, emitting explicit [x, y]
pairs through a shared SeriesBase.GetPointData helper. Unset, behaviour
is unchanged — the flat list is still emitted, so categorical charts and
the composite bar-plus-scatter overlay are untouched.

Rows missing either coordinate yield a null entry rather than a partial
pair, which ECharts would render against a coerced zero. The list stays
parallel to the source data rather than compacted, because SymbolSizeKey
zips against it by index; compacting would misassign every bubble size
after the first gap.

BbBar is deliberately excluded: its HasNegativeValues/BuildPerItemData
path inspects each datum as a scalar and would misread a pair.
BbCandlestick and BbHeatmap already build their own pair data.

Also add BbXAxis.Scale, mirroring the existing BbYAxis.Scale. A value
axis includes zero by default, so the first genuine numeric X axis put
heights of 160-190 in the last sixth of the plot. Opt-in on both axes,
since suppressing zero exaggerates small differences.

Refs blazorblueprintui#439
…39-scatter-xy-datakey

fix(charts): plot scatter, line and area against a real X value
….982

The 9.1 beta pin was deliberate but temporary. The reason for being on
9.1 is unchanged: the 9.0.x stable line hard-pins AngleSharp to exactly
[0.17.1], which carries GHSA-pgww-w46g-26qg, and an exact pin cannot be
lifted from here without NU1608 — a hard error under TreatWarningsAsErrors.

What changed is that 9.1 now has a stable release, so that fix no longer
costs a prerelease. HtmlSanitizer and AngleSharp.Css were both prerelease
transitive dependencies of a published package, which resolves fine for
consumers but can trip supply-chain policies that ban prereleases.

Resolved graph is now HtmlSanitizer 9.1.982, AngleSharp 1.7.0 and
AngleSharp.Css 1.0.1 — no prerelease, no vulnerable package.

Sanitiser output compared across the same 41 inputs used to verify the
original move (XSS vectors, Quill markup, Markdig output, malformed and
non-ASCII HTML): byte-identical between 9.1.949-beta and 9.1.982, with no
executable vector surviving either. The library only touches
new HtmlSanitizer() and Sanitize(string), both unchanged.

Closes blazorblueprintui#426
…/426-htmlsanitizer-stable

chore(deps): move HtmlSanitizer off the prerelease line to stable 9.1.982
…lture

Focusing a BbCurrencyInput multiplied its value by 100, compounding on
every visit: 999,99 came back as 99.999,00.

Three steps read three different cultures for the same value:

  FormatCurrency / TryParseValue  -> the currency's culture (EUR = de-DE,
                                     comma decimal, dot group separator)
  JsOnFocus                       -> invariant, so the field showed 999.99
  GetJsConfig                     -> the ambient culture's separator

On Blazor Server the ambient culture is the server's, not the user's, so
the sanitiser was told "dot" for every visitor regardless of locale and
left the dot in place. On blur TryParseValue read 999.99 back through
de-DE, stripped the dot as a group separator, and parsed 99999.

Each step was individually defensible; they just disagreed about what a
dot meant. Resolve all three through the currency's culture so the string
written on focus is the string the parser expects on blur.

Not locale-specific to the reporter: any currency whose culture disagrees
with the server's on the decimal separator was affected.

Verified across USD, EUR, JPY (zero decimal places) and a currency using
the Arabic decimal separator, each stable across repeated focus/blur.

Refs blazorblueprintui#438
Value had to be a string already in hand, so text only known at copy
time — derived from state that moves, or expensive enough that computing
it every render is wasted — had to be recomputed on each render and held
in a field.

ValueFunc is a Func<string?> evaluated when the user copies. Naming
follows the convention already used for this shape (CellClassFunc,
DayClassFunc).

Value still wins when set. The precedence test is emptiness rather than
null: Value is a string, and a bound-but-unset one reaches the empty
string naturally, which a literal first-non-null reading would let win
and silently copy nothing.

Value loses [EditorRequired] — supplying only a func is now a complete
configuration, and the analyzer would otherwise nag everyone who does.

Resolved once per click rather than per render, so an expensive func is
not called speculatively and OnCopied cannot report a different string
from the one placed on the clipboard.

The async counterpart is deliberately excluded: awaiting a consumer task
before writing spends the transient user activation clipboard writes
require. Tracked in blazorblueprintui#466.

Refs blazorblueprintui#453
…38-currencyinput-culture-split

fix(currency-input): resolve focus, sanitise and parse through one culture
…453-copytext-valuefunc

feat(copy-text): add ValueFunc for copy-time value resolution
…nstant

ButtonClass was static, so it could not observe anything about the
instance — including Class, which is the only way to size this component
since it has no size parameter.

Each button was h-5, and two of them summed to exactly the input's
default h-10. The pieces lined up by coincidence rather than by
construction, so the mismatch only surfaced once a consumer changed the
height: Class="h-8" shrank the field to 32px through TailwindMerge while
the stepper stayed at 40px, overhanging it at both ends with the rounded
corners no longer meeting the input's border.

Buttons now take half the stepper column via flex-1, and the row is
items-stretch rather than items-center, so the column sizes itself from
the field whatever sets that height. min-h-0 lets them shrink past the
chevron's intrinsic height.

BbCurrencyInput shares the markup shape but renders a currency symbol
rather than stepper buttons, so it is unaffected.

Refs blazorblueprintui#465
…65-numericinput-stepper-height

fix(numeric-input): size the stepper buttons from the field, not a constant
The ValueFunc demo added in blazorblueprintui#469 used a ButtonSize value that does not
exist, breaking the build of every demo host.

It got through because the demo build output was piped through tail -2,
which shows MSBuild's elapsed-time line on failure as well as success,
and the server was then started with --no-build — so the browser check
ran against the previous build rather than this markup.

Verified this time by exit code across all three demo hosts (Server, Wasm
and Auto) rather than by tail, and by exercising the demo in a browser:
the button renders at 36px, cycling the environment updates the copied
value, and copying reaches the "Copied!" state.
The ValueFunc demo added in blazorblueprintui#469 used a ButtonSize value that does not
exist, breaking the build of every demo host.

It got through because the demo build output was piped through tail -2,
which shows MSBuild's elapsed-time line on failure as well as success,
and the server was then started with --no-build — so the browser check
ran against the previous build rather than this markup.

Verified this time by exit code across all three demo hosts (Server, Wasm
and Auto) rather than by tail, and by exercising the demo in a browser:
the button renders at 36px, cycling the environment updates the copied
value, and copying reaches the "Copied!" state.
BbCopyText tooltips stayed on screen once shown and accumulated one per
hover; a later scroll then scattered them over unrelated text, because
each sits at the coordinates it was given.

The failure is at the boundary between Blazor's rendering and JS's.
SetupPositioningAsync shows content by calling applyPosition(makeVisible:
true), which writes visibility, opacity and pointer-events onto the
element with !important — the style attribute belongs to JS from then on.
Blazor diffs against what it last rendered, not against the DOM, and its
markup for a closed portal is byte-identical to the markup it rendered
before the portal opened. So on close it correctly concludes nothing
changed, emits no style update, and the element keeps the visible values
JS left behind.

Nothing in the close path was broken: isHovered reached false, the
component re-rendered, data-state flipped to "closed", the auto-update
subscription was disposed, and a MutationObserver recorded no further
writes. The style simply had no owner willing to reset it.

Add IPositioningService.HidePositionAsync, mirroring GetInitialStyle()'s
hidden state, and call it from HideAsync — the single teardown both the
ForceMount and standard lifecycles route through. Hiding is now symmetric
with showing.

Components whose markup changes elsewhere while open — BbTooltip among
them — were never affected: any other difference gives Blazor a reason to
re-emit the attribute. That is why this looked component-specific rather
than structural.
…ement

Opening a popover and pressing Escape left it open, and the trigger could
not be clicked to recover either: BbPopoverTrigger sets pointer-events:
none on itself while open to guard against double-toggling. Clicking
elsewhere was the only way out, and from the keyboard there was none.

CloseOnEscape has always defaulted to true and its handler was correct —
it just never ran. It is bound with @onkeydown on the content element,
which only sees the key once focus is inside the popover, and opening one
does not move focus. Focus stays on the trigger, which sits in a
different part of the DOM now that content renders through the portal, so
the keydown bubbled nowhere near the handler.

Watch for Escape at the document while open, using the onEscapeKey helper
already present in click-outside.js and until now unused. Registered
alongside the click-outside listener and torn down with it, taking
ownership of the field before the first await so a close racing a
teardown cannot double-dispose (blazorblueprintui#441).

The element-level handler stays. The document listener fires on bubble,
so where focus genuinely is inside the popover the original handler runs
first and this one finds the popover already closed.

BbSelect was never affected — it routes Escape through its
keyboard-navigation module, which is document-level for the same reason.
…opytext-demo-buttonsize

fix(demo): correct ButtonSize.Sm to ButtonSize.Small in CopyText demo
…opytext-tooltip-stuck

fix(overlays): stuck CopyText tooltips and a popover that Escape could not close
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants