Controls Releases
Release notes for the MAUI packages (Shiny.Maui.Controls, Shiny.Maui.Controls.Markdown, Shiny.Maui.Controls.MermaidDiagrams) and the Blazor packages (Shiny.Blazor.Controls, Shiny.Blazor.Controls.Markdown, Shiny.Blazor.Controls.MermaidDiagrams).
1.0 - TBD
Section titled “1.0 - TBD”Shiny.Blazor.Controls, with Markdown and Mermaid Diagrams in their own Shiny.Blazor.Controls.Markdown and Shiny.Blazor.Controls.MermaidDiagrams packages. Drop them into any .razor page — no DI registration required, just @using directivesOverlayHost, pan gestures, locked mode, fit-content auto-sizing, and keyboard handling. Includes ShinyContentPage convenience base class. Replaces the previous SheetView on MAUI (MAUI). Blazor retains SheetView as its equivalent componentHideCharacter, configurable length, cell sizing, colors, and keyboard type (MAUI + Blazor)IsOpen (MAUI + Blazor)FontSizePicker and FontSizePickerButton for size selection. Integrated into the ImageEditor toolbar (MAUI)Address results with coordinates, country code filtering, and pluggable IAddressSearchProvider for custom geocoding services (MAUI + Blazor)ColorPicker or a ColorPickerButton that opens a popup dialog with auto-contrast text (MAUI + Blazor)ShinyDurationPicker) that opens a FloatingPanel with hour/minute selection, “hr”/“min” labels, min/max constraints, and configurable minute intervals. The DurationPickerCell TableView cell uses the same FloatingPanel-based picker internally. Requires ShinyContentPage (MAUI)UIVisualEffectView with theme-aware UIBlurEffect for true real-time backdrop blur. Android 12+ captures the background behind the view and applies RenderEffect blur for per-view frosted glass. Blazor uses CSS backdrop-filter: blur(). Configurable blur radius, tint color/opacity, and corner radius. Falls back to semi-transparent tint on older Android (MAUI + Blazor)DataTemplate and DataTemplateSelector properties (MessageTemplate, MessageTemplateSelector) for controlling how each chat message bubble’s content is rendered. Enables action buttons, cards, rich content, or any custom layout per message type while ChatView still manages bubble chrome (avatar, name, timestamp, colors, alignment) (MAUI)Use24Hour property switches between 24-hour and AM/PM format; MinuteInterval constrains minute selection (iOS enforces natively via UIDatePicker.MinuteInterval, other platforms snap to nearest interval on selection) (MAUI)IToaster.ShowAsync(text, cfg => {...}) (MAUI) or IToastService.ShowAsync(...) (Blazor). Supports auto-dismiss with configurable duration, manual dismiss via IDisposable, pill or fill-horizontal display modes, top/bottom positioning, queue or stack mode for multiple toasts, indeterminate spinner, countdown progress bar, icon, tap command/callback, full styling (colors, border, corner radius), feedback, screen reader announce, and iOS safe area awareness. No XAML or OverlayHost required — the overlay auto-attaches on first use (MAUI + Blazor)ClearButtonTool (auto-shows/hides) and speech-to-text tools in the SpeechAddins packages (MAUI uses ISpeechToTextService, Blazor uses Web Speech API). Tools are TextEntryTool subclasses on both hosts with lifecycle hooks for parent text access (MAUI + Blazor)Mask property enables declarative input formatting with # as digit placeholder and auto-inserted literal characters. Supports phone numbers (###) ###-####, credit cards #### #### #### ####, dates ##/##/####, SSN, ZIP codes, and custom patterns. Text always contains raw digits for clean binding/validation; FormattedText provides the display value. Keyboard auto-sets to Numeric, cursor positioning handled on both platforms (MAUI + Blazor)TextEntryTool subclass that increments or decrements the numeric value in a TextEntry by a configurable Step amount on each tap. Auto-displays +N or -N as button text when Text is not explicitly set. Place in LeftTools (decrement) and RightTools (increment) for a numeric stepper pattern (MAUI + Blazor)ChatMessage.IsSent property dims the bubble (50% opacity) for user messages until set to true, providing visual feedback for pending server confirmation (MAUI + Blazor)ChatMessage.Identifier optional string property for attaching user-defined context (e.g., server message ID) to a message after it is sent (MAUI + Blazor)ChatMessage.Acknowledgements list of Acknowledgement objects (Glyph, UserId, Timestamp) rendered as grouped reaction badges below the chat bubble. Badges show the glyph emoji with a count when more than one user reacted with the same glyph (MAUI + Blazor)Value/Minimum/Maximum, indeterminate mode with sliding animation, text overlay with configurable format, and pulse triggers on value change or timed interval. Pulse has configurable PulseLength (sheen width as fraction of fill) and PulseSpeed (ms for one sweep). Gradient mode uses GradientStartColor/GradientEndColor for a linear gradient fill (MAUI + Blazor)Overlay supports any custom content via DataTemplate (MAUI) or RenderFragment (Blazor). The LoadingOverlay subclass provides a built-in loading template with either an indeterminate spinner (ActivityIndicator) or a determinate progress bar (using the ProgressBar control). Bindable IsShown property for show/hide with smooth animation, plus Message text support (MAUI + Blazor)SnapCount property (int, default 1) controls snap behavior. Set to 0 for Netflix-style free scrolling with no snapping. Works across all platforms: Android attaches/detaches LinearSnapHelper, iOS adjusts deceleration rate and targeting, Windows toggles SnapPointsType, Blazor uses CSS scroll-snap-type (MAUI + Blazor)IFeedbackService. The default HapticFeedbackService provides tactile click/long-press feedback. Replace it with SetCustomFeedback<T>() in UseShinyControls() to implement text-to-speech, sound effects, analytics, or any custom response. ChatView passes message text as details, enabling TTS for incoming messages. Every control’s UseFeedback property (default true) gates whether feedback firesShiny.Maui.Controls.Barcodes and Shiny.Blazor.Controls.Barcodes) — Pure-managed 1D and 2D barcode renderer powered by ZXing.Net, with a custom PNG encoder built on zlib + CRC32 + Adler32 (no SkiaSharp, no System.Drawing — AOT-safe on every TFM, ships clean on iOS, Android, Mac Catalyst, Windows, and Blazor WebAssembly). BarcodeView (a ContentView on MAUI, a Razor component on Blazor) renders any of 13 symbologies — QRCode, Aztec, DataMatrix, Pdf417, Code128, Code39, Code93, Codabar, Ean8, Ean13, UpcA, UpcE, Itf — with bindable Value, Format, PixelWidth, PixelHeight, MarginPixels, ForegroundColor, and BarcodeBackgroundColor. QRCodeView is a BarcodeView subclass that locks Format to QRCode, exposes a single square Size property, and adds ErrorCorrection (Low / Medium / Quartile / High — higher tolerates more damage at the cost of capacity). On Blazor, output defaults to inline SVG with shape-rendering="crispEdges" and a single horizontal-run <path> so it scales infinitely without aliasing and stays tiny in DOM size; switching ImageFormat to Png renders an <img> with a data: URI. Optional CssWidth / CssHeight decouple the encoder resolution from the host element’s CSS size. For headless rendering — PDF exports, email attachments, label printing — the static BarcodeRenderer exposes RenderPng(string, BarcodeFormat, BarcodeRenderOptions?), RenderSvg(...), and RenderDataUri(...). Invalid 1D payloads (e.g. EAN-13 with non-numeric content) silently clear the image instead of throwing. XAML namespace xmlns:bc="http://shiny.net/maui/barcodes" (MAUI + Blazor)RootLoader / ChildrenLoader, configurable expand/collapse/retry icons (with sensible ▼ / ▶ / ↻ glyph fallbacks), single or multi-select via SelectionMode, per-item CanExpand / CanSelect predicates, retry-on-load-failure, optional vertical guide lines between parent and children, and drag/drop reorder with event-only mutation. RootLoader, ChildrenSelector, ChildrenLoader, and HasChildrenSelector are independent so the same control handles fully in-memory trees, half-lazy trees, and fully remote trees. Events + matching *Command bindables: ItemSelected, ItemExpanded, ItemCollapsed, LoadFailed, ItemDropped. Drag/drop reports Above / Below / Into drop positions with visual drop indicators — Blazor uses native HTML5 drag events via JS interop (required for Safari/Firefox), while MAUI uses platform drag gestures with a pan-gesture fallback on Mac Catalyst, AppKit, and GTK4. Public methods: ExpandAll, ExpandAllAsync, CollapseAll, Expand(item), Collapse(item), Refresh(item), ReloadAsync (preserves expansion/selection on Blazor), FindNode(item). Blazor adds keyboard navigation on top of the same shape (MAUI + Blazor)TopLeft, TopRight, BottomLeft, BottomRight) with per-corner OffsetX / OffsetY nudge. Bind your unread / cart / pending count to Text — an empty string auto-hides the badge so you don’t write show/hide logic. MaxCount collapses overflow to "99+" (or any chosen ceiling). IsDot switches to a simple notification dot mode with configurable DotSize for “has new” indicators. Full styling: BadgeColor, BadgeTextColor, BadgeBorderColor (creates a clean ring around the badge), BadgeBorderThickness, CornerRadius (default fully-rounded pill), BadgePadding, FontSize, and FontAttributes / FontWeight. Optional show/hide scale + fade animation (IsAnimated) and a continuous attention-grabbing pulse (IsPulsing). Blazor honors prefers-reduced-motion and disables both animations when set (MAUI + Blazor)CollectionView and drives the hero from CollectionView.Scrolled (no platform handlers), so multi-column GridItemsLayout, selection, item-selected commands, empty views, and Scrolled event arguments (ParallaxScrollEventArgs with verticalOffset, headerTranslation, headerVisibleHeight) all pass through. Blazor uses a small JS scroll listener that mutates transform / opacity directly via requestAnimationFrame, so the parallax runs at native scroll framerate without re-rendering Razor components. Configurable HeaderHeight, MinHeaderHeight, ParallaxFactor (0 = pinned, 1 = scrolls with content), CollapseToSticky (clamp to min after scrolling past), and FadeHeaderOnScroll. Drive sticky titles, fading nav chrome, or scroll-linked animations from the Scrolled event (MAUI + Blazor)Desktop add-on (Shiny.Maui.Controls.Desktop) — A single MAUI-desktop-only package that bundles three features sharing the same TFM matrix (net10.0;-windows;-maccatalyst;-macos + Linux GTK4): a system tray / status-bar icon, Visual-Studio-style window docking, and a touch / kiosk on-screen keyboard. Blazor gets the docking + on-screen keyboard via the companion Shiny.Blazor.Controls.Kiosk package.
Tray Icon (Shiny.Maui.Controls.Desktop.TrayIcon namespace) — Cross-platform system tray / status-bar / menu-bar icon. Supports Windows (Shell_NotifyIcon via Win32 P/Invoke with a message-only window for click routing), macOS AppKit (native NSStatusItem via the net10.0-macos bindings), MacCatalyst (bridges to AppKit at runtime via the Objective-C runtime with [UnmanagedCallersOnly] trampolines for menu callbacks), and Linux (libayatana-appindicator3 + GTK 3 — depends on libayatana-appindicator3-1 and libgtk-3-0 system packages). API: ITrayIconFactory resolved from DI, ITrayIcon with SetIcon(Func<Stream>), Tooltip, Title, Badge, IsVisible, IsTemplateImage (macOS dark/light auto-tint), SetMenu(TrayMenu), ShowMenu(), ShowNotification(title, message), StartAnimation(frames, interval) / StopAnimation() / IsAnimating, and PrimaryClick/SecondaryClick/DoubleClick events. Menus are built fluently with TrayMenu.Build(b => b.Item(...).Check(...).Separator().Submenu(...)); TrayMenuItem supports per-item Icon (Func<Stream> rendered next to the label on all four platforms) and Accelerator strings parsed by TrayAccelerator and dispatched via the OS — RegisterHotKey on Windows (process-global), native KeyEquivalent + modifier mask on macOS / Catalyst (app-foreground), and gtk_widget_add_accelerator on a GtkAccelGroup on Linux (menu-focused, best-effort). Mutating any item rebuilds the native menu automatically. Badge renders as a composited red pill on the Windows HICON (System.Drawing.Common, Windows TFM only) and beside the icon on macOS / Linux. ShowNotification routes to Shell_NotifyIcon NIF_INFO on Windows, NSUserNotificationCenter on macOS / Catalyst, and libnotify on Linux (no-op when libnotify is missing). Windows auto-wraps PNG bytes in an ICO container so the same PNG asset works on every platform. Register with .UseTrayIcon() in MauiProgram.cs. (Migrating from the previous standalone Shiny.Maui.Controls.TrayIcon package: swap your <PackageReference> and change using Shiny.Maui.Controls.TrayIcon; to using Shiny.Maui.Controls.Desktop.TrayIcon; — the UseTrayIcon() extension method in the Shiny namespace is unchanged.)
On-Screen Keyboard (Shiny.Maui.Controls.Desktop.OnScreenKeyboard namespace; companion Shiny.Blazor.Controls.Kiosk.OnScreenKeyboard for Blazor) — Touch / kiosk on-screen keyboard for desktop apps and Blazor. US-QWERTY with three layers (lowercase / Shift / 123-symbols), bottom-docked, auto-shows when an Entry / Editor (MAUI) or <input> / <textarea> (Blazor) gains focus. Critically does NOT steal focus when keys are tapped — every key uses pointerdown + preventDefault() (Blazor) or Focusable = false + intercepted PointerPressed (MAUI). Press-and-hold autorepeat (400ms delay, 50ms interval, both configurable). Dispatches into the focused control via managed Text mutation at CursorPosition (MAUI) or document.execCommand('insertText', char) after focus restore (Blazor) — no native synthetic key events, so no macOS Accessibility entitlement required and Mac App Store distribution is fine. Full AutomationPeer (Windows / MAUI / GTK) and ARIA role="button" + aria-keyshortcuts (Blazor) tree so switch-input users can step through keys. Theme tokens (OnScreenKeyboardKeyBrush / --shiny-osk-key-bg etc.) mirror docking’s pattern. Public surface: IOnScreenKeyboard (MAUI) / IOnScreenKeyboardService (Blazor) with Show() / Hide() / Toggle() / IsVisible / VisibilityChanged, and an OnScreenKeyboardOptions for auto-show-on-focus, push-content vs overlay, height, theme, and autorepeat timing. Register with .UseOnScreenKeyboard(opts => ...) on MAUI or services.AddShinyOnScreenKeyboard(opts => ...) on Blazor. Limitations: MAUI inputs / DOM inputs only (no system-wide injection), no Shadow DOM, no IME / dead-key composition, US-QWERTY layout only.
Docking (Shiny.Maui.Controls.Desktop.Docking namespace; companion Shiny.Blazor.Controls.Kiosk for Blazor (kiosk-shaped add-on, namespace Shiny.Blazor.Controls.Kiosk.Docking, also home to the on-screen keyboard)) — Visual-Studio-style window docking for desktop apps. Dockable tool windows, tabbed groups, draggable splitters, auto-hide rails, and tear-off floating windows. DockHostView (MAUI) / <DockHost /> (Blazor) attaches inside any existing page — not a ContentPage subclass, so consumers keep their Shell architecture. Public surface: layout schema POCOs (DockRoot, DockWindowState, DockSplit, DockGroup, DockEmpty, DockTab) with [JsonPolymorphic] + a source-generated JsonSerializerContext for AOT-safe JSON round-trip via DockSerialization.Serialize/Deserialize; contracts IDockHost (per-window controller with LoadAsync / Snapshot / ShowPanelAsync / HidePanelAsync / ActivatePanelAsync / ResetLayoutAsync / SetRailCollapsedAsync / IsLocked / Events — implemented directly by DockHostView and <DockHost>), IDockableContent (optional interface on panel views for per-instance Title / Icon, CanClose / CanFloat, activation callbacks, and pointer-down claim for embedded editors), IDockableContentFactory (async Task<View> CreateAsync(string instanceId, ...) for MAUI / Task<RenderFragment> for Blazor, plus DisplayName / Icon — registered via .AddDockPanel<TView>("panel-id", displayName: …, icon: …)), IDockLayoutStore (bring-your-own — no default ships; attach via the host’s LayoutStore property for auto-load at startup and debounced auto-save on every layout change), IDockLayoutMigrator (forward-only schema migrations), IDockEvents (LayoutChanged / PanelActivated / DragStarted / DragCompleted / DragCancelled), and IDockCommandScope (scopes Ctrl+W close-tab, Ctrl+Tab MRU, Ctrl+Alt+PgUp/Dn group nav to the dock surface). Fully interactive on both hosts: drag tabs to merge (drop center), split (drop edge), reorder (drop in the tab strip), or tear off floating windows (drop outside the host — movable, resizable, re-dockable, with persisted bounds); drag splitters with persisted clamped ratios; collapse individual panels to slim edge bars that restore on click (whole rails via SetRailCollapsedAsync); lock the layout read-only with IsLocked. Schema versioning (SchemaVersion + MinReadableVersion) is wired in from day one, and unknown PanelTypeIds land in a missing-panels tray rather than silently dropping. Register with .UseShinyDocking() + .AddDockPanel<TView>("id") on MAUI, services.AddShinyDocking().AddDockPanel<TComponent>("id") on Blazor; host controls accept InitialLayout, LayoutStore, and IsLocked. Cross-window drag uses mouse-down implicit capture — no global mouse hooks, no macOS Accessibility entitlement required.
ShinyToolbar docks to the Top or Bottom of its scroll container as an action bar with a Title, icon Items (links or buttons with Badge/IconColor), and StartContent/ChildContent/EndContent slots; position: sticky reserves its height while page content scrolls under it. ShinyTabBar is a mobile-style bottom tab bar (position: fixed) with two-way @bind-SelectedKey, optional filled ActiveIcon for the selected state, and Badges ("" renders a dot). Both support a Frosted glass toggle via CSS backdrop-filter. On MAUI, use Shell tab bars / ToolbarItems or FloatingPanel instead (Blazor)Items that don’t fit the available width automatically collapse into a hamburger dropdown behind an overflow (“more”) button, popping back out as the bar widens (OverflowEnabled, default true). A bundled toolbar.js ResizeObserver measures each item’s intrinsic width and reports how many fit (reserving room for the overflow button at the boundary); menu entries mirror each item’s icon, Text, and Badge, raise the same ItemClicked, and close on outside click or selection. Customize via OverflowIcon, OverflowText, OverflowAriaLabel, MenuBackgroundColor, and MenuTextColor. Overflow is auto-disabled when EndContent is supplied (Blazor)Shiny.Maui.Controls.Camera / Shiny.Blazor.Controls.Camera) — Cross-platform camera preview on iOS, Android, Windows, macOS AppKit, and Blazor WASM with zoom, torch, lens/device selection, photo + video capture, and live color filters (Mono/Noir/Sepia/Vivid/Cool/Warm/Fade/Chrome/Instant/Tonal). Filters apply to the live preview and captured photos (Apple + Android; recorded video records the raw feed; Windows has no live filter). A pluggable IFrameAnalyzer pipeline streams frames off the UI thread with per-analyzer drop-on-busy back-pressure; each analyzer raises its own strongly-typed event (or bindable Command), can be declared right in XAML, and draws styled OverlayBoxes via the built-in CameraOverlayView. Analyzers can be added/removed live, or toggled on/off in place via FrameAnalyzer.IsEnabled (bindings + state preserved), with ShowBoundingBox to suppress just the boxes. Modular add-ons Shiny.Maui.Controls.Camera.Barcode/.Face/.Motion/.Ocr/.Documents scan barcodes, detect faces and motion, run OCR, and extract structured documents (Invoice with order lines, Receipt with line items + per-tax breakdown + totals, AAMVA DriversLicense, HealthCard, CreditCard via IIN+Luhn, Passport via MRZ) — each a strong record with nullable fields + a typed event. Register with .UseShinyCamera() (MAUI + Blazor)MotionEventArgs now carries Regions (one normalized RectF per moving area) alongside Region (their union) and Intensity; tune granularity with GridColumns / CellThreshold (MAUI)SelectedItem set from code or a binding now reflects in the cell’s value label and the underlying picker selection (previously only the user tapping the picker updated the display), and it re-syncs when ItemsSource is populated asynchronously (MAUI)IsContentScrollEnabled — New property (default true) that wraps panel content in a ScrollView. Set it false when the content already scrolls itself (a TableView/CollectionView) — nesting scroll-views collapses the inner one to near-zero height, leaving its rows blank (MAUI)PreviewView renders in Compatible (TextureView) mode so the RenderEffect colour matrix actually affects the camera pixels — previously it defaulted to a SurfaceView, whose separate compositor surface ignored the effect, so filters did nothing to the preview (live preview filtering requires API 31+; captured photos are filtered on all versions). On iOS/Mac Catalyst applying a filter no longer blanks/“stops” the preview: the handler previously hid the AVCaptureVideoPreviewLayer, but that layer is the view’s backing layer, so hiding it also hid the filtered-frame overlay subview — the opaque overlay now simply covers the live preview instead (MAUI)ExportWidth×ExportHeight, preserving the whole signature and its aspect ratio (MAUI)CameraId parameter and GetAvailableCamerasAsync() method (returning CameraDevice Name) let you enumerate and pin a specific video input device — useful on machines with several webcams. Device labels populate once permission is granted (Blazor)Facing, CameraId, EnableBarcode, or ShowOverlay while running now re-acquires the stream automatically (matching MAUI’s live toggling), and CapturePhotoAsync() bakes the current Filter into the captured still so it matches the preview (Blazor)HealthCardAnalyzer now detects the issuing Canadian province from on-card text and applies that province’s member-number format — Quebec/RAMQ (4 letters + 8 digits), Ontario/OHIP (10 digits + 2-letter version code), BC Personal Health Number, Alberta/AHCIP, and the rest — surfacing a new HealthCard.Province plus a Plan field (unknown layouts fall back to the generic digit-run heuristic). DriversLicenseAnalyzer adds DriversLicense.Jurisdiction (the AAMVA DAJ province/state) and now infers Canadian CCYYMMDD date order from the province code when the country element is absent, and accepts the legacy AAMVA file header in addition to ANSI. Note: Ontario & Quebec driver’s licences carry no PDF417 barcode and still cannot be scanned via AAMVA (MAUI)Shiny.Maui.Controls.Camera.Documents that extracts a point-of-sale receipt into a strongly-typed Receipt record: merchant name + phone, receipt/invoice number, date and time, purchased line items (Receipt.Lines), a per-tax breakdown (Receipt.Taxes, each with an optional rate), plus Subtotal, total Tax, Tip, Discount, Total, and best-effort Currency / PaymentMethod / CardLast4. OCR + best-effort rules; supply a custom IDocumentParser<Receipt> for production accuracy. The sample’s Camera page now exercises every document analyzer (Invoice, Receipt, Health Card, Driver’s License, Credit Card, Passport) (MAUI)DocumentDetected once with the richest combined record, instead of firing on every frame with a stream of partial/incomplete objects as the document slides into focus. Tune with AccumulationFrames (default 5; set 1 for the old per-frame behavior) and ResetAfterEmptyFrames (re-arm once the document leaves view); the analyzer also fires early when the parse is complete. Custom parsers opt in via two new (defaulted, non-breaking) IDocumentParser<T> methods, Merge and IsComplete (MAUI)MotionChanged is now debounced so it isn’t twitchy: motion is reported started only after EnterFrames consecutive frames above threshold (default 3) and stopped only after ExitFrames below it (default 5). The overlay boxes remain un-debounced so they stay responsive (MAUI)FrameAnalyzer.CaptureOnDetection / StopOnDetection; the result (analyzer, detection payload, and photo) is delivered on the new CameraView.DetectionCaptured event. A latch fires the trigger once (re-armed by StartAsync). For manual control, the new CameraView.CaptureAndStopAsync() grabs a full-res still and stops the session atomically (MAUI)AutoFocusRangeRestriction so the lens can reach macro range, centers the focus point of interest, enables smooth autofocus, and pairs continuous auto-exposure — applied on session start and on every camera switch (MAUI)Carousel<TItem> built on a transform-based drag engine: click-and-drag on desktop and flick-with-momentum on touch, replacing native scroll-snap. Adds Align (Start/Center/End), SlidesPerView, SlidesToScroll, VariableWidths, vertical orientation (ViewportHeight), Rtl, seamless Loop (per-slide repetition, works with variable widths), DragFree momentum, scroll-linked Effect (Scale/Opacity/Parallax/Fade) that animates live while dragging, discrete AutoPlay with a scroll-position progress bar plus continuous AutoScroll marquee, and chrome for a counter, dot indicators (one per snap), and a thumbnail strip. Methods NextAsync/PreviousAsync/GoToAsync/GoToSlideAsync, two-way CurrentPosition, keyboard navigation, and lazy item loading. Use CarouselGallery for MAUI/Blazor parity; use Carousel for the richer Blazor-only drag experience (Blazor)