G9MAUIControls 1.0.14
dotnet add package G9MAUIControls --version 1.0.14
NuGet\Install-Package G9MAUIControls -Version 1.0.14
<PackageReference Include="G9MAUIControls" Version="1.0.14" />
<PackageVersion Include="G9MAUIControls" Version="1.0.14" />
<PackageReference Include="G9MAUIControls" />
paket add G9MAUIControls --version 1.0.14
#r "nuget: G9MAUIControls, 1.0.14"
#:package G9MAUIControls@1.0.14
#addin nuget:?package=G9MAUIControls&version=1.0.14
#tool nuget:?package=G9MAUIControls&version=1.0.14
G9MAUIControls
A dependency-light .NET MAUI control suite, built entirely on public MAUI primitives.
Twenty-five input and feedback controls sharing one outlined-field architecture, a bottom sheet with
real per-platform edge-handoff gestures, a popup system, toasts, an animated tab bar, an edge drawer,
and a ~110-token light/dark theme engine. Android, iOS, Mac Catalyst and Windows from one codebase,
with no #if in the control layer.
dotnet add package G9MAUIControls
What makes it different
Bring your own icon font — or none at all. Every icon slot takes a G9IconSource, which any
icon-font enum converts to implicitly (font family = the enum's type name, glyph = its
[Description], the convention icon-font generators already emit). The suite's own chrome glyphs are
vector paths, not a bundled font, so it looks complete out of the box with zero icon
configuration and zero chance of a tofu box.
No shadows, anywhere — and that is a measured decision, not a style. On Android a MAUI Shadow
on a view whose platform background isn't a shadow-capable BorderDrawable falls back to
drawShadowViaDispatchDraw: an ARGB_8888 bitmap the size of the view, the whole subtree rasterized
into it, then software-blurred through a BlurMaskFilter on the UI thread, every draw pass. That
produced a hard ANR on real hardware. Elevation here is Stroke + surface-tone steps instead. Full
post-mortem in Controls/G9Controls.md §0.
Trim and AOT safe, and verified. No Reflection.Emit, no private-field reads, no runtime code
generation. IsTrimmable and IsAotCompatible are declared, and a consuming app published with
-p:AndroidLinkMode=Full -p:PublishTrimmed=true, with this assembly rooted, completes with zero IL
warnings. That check found four real defects the first time it ran, all fixed — a library build cannot see
its dependencies' implementations, so it is the only check that counts.
iOS NativeAOT is not yet verified; the claim above is trimming on Android.
RTL is real, not a FlowDirection you set and hope. Outlined fields physically swap their icon
columns; every drawable that mirrors does so itself with its canvas pinned to LTR (a canvas either
mirrors itself or the drawable mirrors — never both, or ticks come out backwards).
Three runtime dependencies, and each one earns its place: CommunityToolkit.Mvvm
(ObservableObject for the palette, IRelayCommand for the safe buttons), Microsoft.Maui.Controls,
and SkiaSharp.Views.Maui.Controls — used only for the tab bar's concave FAB-notch silhouette,
which needs a genuinely blurred path on the render thread.
Quick start
// MauiProgram.cs
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>()
.UseG9SheetView(); // registers the bottom-sheet handler
// Optional: point the culture facade at your app's language state.
G9Culture.Configure(
currentCulture: () => LocalizationManager.CurrentCulture,
isRtl: () => LocalizationManager.CurrentCulture.TextInfo.IsRightToLeft);
G9Culture.LtrFontFamily = "OpenSansRegular";
G9Culture.RtlFontFamily = "Yekan";
// Optional: translate the ~50 strings the controls display.
G9Strings.UseResources(AppDictionary.ResourceManager, keyPrefix: "G9");
// Optional: use your own icon font everywhere.
G9IconFonts.Register<MyIcons>(isDefault: true);
G9Glyphs.Chevron = MyIcons.ExpandMore;
return builder.Build();
}
Then derive pages from G9PageBase (it applies the control template that hosts the overlay layers)
and use the controls:
<g9:G9TextEntry Label="Device name" LeadingIcon="Search" />
<g9:G9ComboBox Label="Relay channel" ItemsSource="{Binding Channels}" />
<g9:G9Switch IsOn="{Binding IsEnabled}" />
<g9:G9Button Text="Connect" Variant="Primary" LeadingIcon="{x:Static my:MyIcons.Bluetooth}" />
await G9ToastHelper.ShowToastAsync("Saved", G9ToastType.Success);
var ok = await G9PopupHelper.ShowConfirmAsync("Turn off all relays?", type: G9PopupType.Warning);
G9BottomSheetHelper.ShowG9BottomSheet(content, G9BottomSheetOptions.FitToContentOptions());
// Two detents: opens at a peek, drags open to the CONTENT's own height (capped, then scrolls).
G9BottomSheetHelper.ShowG9BottomSheet(content, G9BottomSheetOptions.DefaultOptions() with
{
CurrentState = G9BottomSheetState.Peek,
States = [G9BottomSheetState.Peek, G9BottomSheetState.Medium],
PeekHeight = 260,
CollapsedHeight = 260,
ExpandedFitsContent = true // ScrollingExpandsSheet is already on by default
});
What's in it
| Area | Contents |
|---|---|
| Inputs | G9TextEntry G9Editor G9SearchEntry G9PinEntry G9Picker G9ComboBox G9DateTimePicker G9TimeSpanPicker G9RangeSlider G9Switch G9ChipGroup |
| Actions | G9Button G9IconButton G9SafeButton G9SafeIconButton G9PlusButton G9NavCard G9SwipeView |
| Structure | G9TabView G9Expander G9CascadePanel G9Separator G9TitleWithLine |
| Feedback | G9ProgressBar G9Shimmer G9ActivityIndicator |
| Overlays | G9BottomSheetHelper (+ stacking, morph, fit-to-content, content-sized detents, list picker) · G9PopupHelper (types, input forms, confirm, non-modal draggable) · G9ToastHelper (typed toasts, loaders, progress) |
| Navigation | G9TabBar (animated bottom bar + FAB notch) · G9EdgePanel (edge drawer) |
| Theming | G9Theme G9Palette (~110 tokens) G9LayoutMetrics · {theme:G9Color …} markup extension |
| Hosting | G9PageBase G9ContentViewBase G9PageTemplate (the six-layer overlay z-stack) |
Every control folder ships its own .md guide next to the .cs. Start with
Controls/G9Controls.md — the architecture guide, including the platform crash-and-pitfall catalog
(§15) that documents every WinUI stowed exception, Android gesture-interception and iOS focus quirk
this suite has already paid for.
Optional integration hooks
Nothing below is required; each one lights up a feature that a library genuinely cannot do alone.
| Hook | Enables |
|---|---|
G9Culture.Configure / NotifyChanged |
live language + RTL switching, correct typeface per script |
G9Strings.UseResources / UseProvider |
translating the controls' own strings |
G9IconFonts.Register<T> / G9Glyphs.* |
your icon font throughout the suite |
G9ImageFactory.Factory |
routing bitmap icons through a caching image control |
G9Speech.Provider |
the search entry's microphone |
G9AndroidHost.* |
tap-outside-to-dismiss-keyboard, safe-area re-measure on rotation |
G9Preferences.Store |
redirecting persisted theme + learned sheet heights to your own store |
G9SafeCommand.DiagnosticsHandler |
a "More details" button on error popups |
G9SafeCommand.DiagnosticsAvailable |
gating that button on a runtime setting (a developer mode) |
G9BottomSheetHeightSeeds.Seed |
first-open heights for fit-to-content sheets |
Building a control of your own beside the suite? These are public for exactly that:
G9Metrics, G9Colors, G9Visuals and G9TabBarMetrics for its measurements;
G9ColorExtension.ResolveColor + G9PaletteSubscriptions to paint from the palette and repaint on a
theme flip; and G9OverlayHosts.TryGetCurrent(out var host) for the current page's layers —
host.OverlayLayer (sheet level), host.ToastLayer (above sheets and popups), host.DevLayer
(topmost), and host.Page for its safe-area insets.
License
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-android36.0 is compatible. net10.0-ios26.0 is compatible. net10.0-maccatalyst26.0 is compatible. net10.0-windows10.0.19041 is compatible. |
-
net10.0-android36.0
- CommunityToolkit.Mvvm (>= 8.4.2)
- Microsoft.Maui.Controls (>= 10.0.90)
- SkiaSharp.Views.Maui.Controls (>= 4.151.0)
-
net10.0-ios26.0
- CommunityToolkit.Mvvm (>= 8.4.2)
- Microsoft.Maui.Controls (>= 10.0.90)
- SkiaSharp.Views.Maui.Controls (>= 4.151.0)
-
net10.0-maccatalyst26.0
- CommunityToolkit.Mvvm (>= 8.4.2)
- Microsoft.Maui.Controls (>= 10.0.90)
- SkiaSharp.Views.Maui.Controls (>= 4.151.0)
-
net10.0-windows10.0.19041
- CommunityToolkit.Mvvm (>= 8.4.2)
- Microsoft.Maui.Controls (>= 10.0.90)
- SkiaSharp.Views.Maui.Controls (>= 4.151.0)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on G9MAUIControls:
| Package | Downloads |
|---|---|
|
G9MAUIControls.ProgressOverlay
Adds a bottom-anchored staged-progress overlay to G9MAUIControls: one Running / Canceling / Success / Error state machine plus a draggable minimized bubble, a 44dp cancel target that never routes a cancellation through an error terminal, and a retry affordance on failure. Mounts above the toast stack via the core's IG9BottomAnchoredOverlay seam. No dependencies beyond the core. |
|
|
G9MAUIControls.IntroCarousel
Adds G9IntroCarousel to G9MAUIControls: a swipeable onboarding / intro carousel whose slides can be images or video, with page indicators, a skip affordance, and correct RTL paging. Ships separately because video playback requires CommunityToolkit.Maui.MediaElement and a platform media stack, which the core deliberately does not depend on. |
|
|
G9MAUIControls.Barcode
Adds G9BarcodeTextEntry to G9MAUIControls: an outlined text field with an integrated camera scan affordance, busy/accepted/error trailing states, optional format validation, and a multi-scan session mode. Ships separately because camera scanning requires a native camera stack and a camera permission, which the core deliberately does not depend on. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.14 | 98 | 9/12/2026 |
| 1.0.13 | 182 | 9/8/2026 |
| 1.0.12 | 153 | 9/8/2026 |
| 1.0.10 | 170 | 9/7/2026 |
| 1.0.9 | 157 | 9/4/2026 |
| 1.0.8 | 149 | 9/4/2026 |
| 1.0.7 | 148 | 9/2/2026 |
| 1.0.6 | 151 | 9/1/2026 |
| 1.0.5 | 204 | 8/19/2026 |
| 1.0.4 | 196 | 8/18/2026 |
| 1.0.3 | 179 | 8/18/2026 |
| 1.0.2 | 189 | 8/17/2026 |
| 1.0.1 | 195 | 8/15/2026 |
| 1.0.0 | 204 | 8/14/2026 |
1.0.14
- A default field icon comes back after a subclass affordance has replaced it.
G9OutlinedFieldBase caches the default leading/trailing G9IconView so a signature flip does not
detach it and re-trigger the embedded font's rasterisation. A subclass affordance — G9ComboBox's
clear "x", G9Editor's voice mic — is attached through SetIconHostContent, which removes every
non-ripple child of the host, INCLUDING that cached view. The cached field still pointed at the
detached instance, so on the way back ShowDefaultTrailingIcon saw non-null, took its
"already built, just recolour" branch, and never re-added it to the host; the visibility loop
below could not find it either. The slot was left holding nothing but the ripple layer.
Visible as G9ComboBox losing its search glyph permanently once an item had been picked and then
cleared: magnifier -> x -> NOTHING. A field that had never been touched still looked correct,
which is what made it read as a combobox bug rather than a caching one. Both
ShowDefaultTrailingIcon and ShowDefaultLeadingIcon now re-attach the cached view when it is no
longer a child of its host. Any control that mixes a default icon with a subclass-supplied one
is affected.
- Build dependency: Microsoft.SourceLink.GitHub 8.0.0 -> 10.0.401.
CVE-2026-62900 (GHSA-23fw-v26w-5fgq, published 2026-09-08) affects Microsoft.Build.Tasks.Git
8.0.0, which SourceLink pulls in, and 8.0.0 has no patched release. 10.0.401 is stable, outside
every affected range, and in the same band as the pinned SDK. Build-time only (PrivateAssets),
so nothing about the shipped assemblies changes.
1.0.13
- A control that offers a G9Glyphs slot now reads that slot EVERYWHERE, including its own XAML.
G9Glyphs.X is a settable slot so a consumer can point the suite at its own icon font. But
Icon="Refresh" written in a library's own XAML is resolved by G9IconSourceTypeConverter to the
built-in vector G9Glyph and never consults the slot — so a consumer's override reached some of a
control's icons and not others. On the progress overlay's failure banner that was visible: the
leading icon came from G9Glyphs.Refresh (the host's own font) while the retry button beside it
carried the library's built-in drawing, in a different style and turning the opposite way. One
banner, two refresh icons, disagreeing. G9ProgressOverlayView now assigns
TerminalRetryIcon.Icon = G9Glyphs.Refresh in code — it has to be code rather than XAML, because
the slots are configured during consumer startup, after InitializeComponent has run.
- G9Glyph.Refresh is drawn correctly.
Its arrow head hung off the START of the arc: the apex sat 3.3 units clear of the stroke and the
two legs opened AWAY from the direction of travel, so at 18 dp with a round-capped stroke it
rendered as a broken flag beside the ring rather than an arrow on it. The head is now a chevron
at the END of the sweep, tangent-aligned, so the arrow continues the stroke. Only the glyph's
geometry changed; its size, weight and colour behaviour are untouched.
1.0.12
- Cleaning up the codebase, removing unused files and improving maintainability.
1.0.11
- G9Editor.MaxEditorHeight: a text area that grows as you type, up to a ceiling, and then
scrolls its own content.
AutoSize=TextChanges grows the box with the text and never stops, which is right in a scrolling
page and wrong in a form with anything BELOW the field: a long description pushed a bottom
sheet's footer buttons off the screen, leaving the user typing into a control whose Save button
they could no longer reach. Setting MaxEditorHeight constrains the MEASURE, which is what makes
the platform text view scroll - it is already scrollable, it simply never had a reason to be.
No ScrollView is added.
Both the inner editor and the outlined box are capped, the box by the ceiling plus its inner
padding, or the outline keeps growing around a field that has already stopped. Default 0 means
no ceiling, so existing editors are unchanged. Ignored when AutoSize is Disabled, which already
pins the height.
1.0.10
- Voice dictation is now a suite capability, not a search-box feature.
The session engine moved out of G9SearchEntry into G9VoiceDictation (Localization/), a small
control-agnostic class driven through three delegates. G9TextEntry declares the public surface —
VoiceEnabled, VoiceCulture, IsListening, Start/Stop/ToggleVoiceAsync and the three voice events —
and G9Editor gained the same. G9SearchEntry keeps only its default (VoiceEnabled = true) and
inherits the rest, so nothing a consumer wrote against it changes. Why a class and not a shared
base: G9Editor is a SIBLING of G9TextEntry under G9OutlinedFieldBase, whose job is outline and
notch geometry — see ADR-0020.
Two fixes came with it. The microphone no longer disappears mid-session: its visibility was
keyed on the field being EMPTY, and a live transcript writes into Text, so the first
recognized word removed the only control that could stop the session (LES-0044). And a missing
G9Speech.Provider now really does hide the affordance, which the interface documentation had
promised since it was written and no code checked.
On G9Editor the microphone is deliberately NOT value-gated (no clear button competes for the slot,
and a long description is what people dictate) and it is pinned to the bottom of the box rather
than centred.
- G9PopupInputField.Text no longer forces LeftToRight.
It defaulted to LTR because its sibling factories do, where the pin is correct for a real reason:
a phone number, an email and a password are written left-to-right in every language. Prose is
not. In an RTL app the symptom was a "Title" box whose caret sat on the left and whose text ran
away from its own label. Phone / Email / Password keep the pin. Text and TextArea also gained an
`enableVoice:` parameter.
1.0.9
- G9Diagnostics gets its first call sites: three periods the suite already knew about, now
reported as busy activities.
1.0.8 added the seam and only G9SafeCommand raised it, so a host could see commands and nothing
else. Three `using` scopes now cover the windows where the UI looks ready and is not:
G9PageBase reports `page:` + the page type name for as long as the page-loading overlay is on screen,
DeferredContentView reports `deferred:` + the view name across build AND reveal, and
G9BottomSheetHelper.ShowFullScreenAsync reports `sheet:open` while the open animation runs and a
factory sheet builds its body.
Why the library and not the consumer: these three windows are the library's own — it owns the
overlay, the deferred build and the sheet animation, and it is the only code that knows when each
one ends. A host can observe that a command started; it cannot observe that a sheet has finished
opening without reaching into internals that are deliberately private.
Two details are load-bearing. The page token is released BEFORE the exit animation, not after:
the page is usable the moment dismissal begins, and holding it through a 200 ms fade would make
every page transition look like work still in flight. And the sheet scope is released in a
continuation rather than a `finally`, because the work it covers is asynchronous — a `using`
around the synchronous call would release before the sheet had opened, which is worse than not
reporting at all.
Unchanged from 1.0.8 and still true: nothing subscribes by default, both hooks are null until a
host assigns them, every invocation is guarded, and the library still decides nothing about what
busy means. No API, signature or rendering changes.
1.0.8
- G9Diagnostics: one observation seam, so a host can tell when its app is busy.
New static hooks in Helpers/G9Diagnostics.cs — OperationStarted, OperationCompleted and
ActivityChanged, plus an Activity(name) scope helper. G9SafeCommand raises the first two from the
trace markers it already emitted.
Why it belongs here rather than in the consumer: a host that wants to wait for the app to settle
instead of sleeping, or to record what a user actually did as semantic actions rather than raw
touches, has to observe the choke points — and in an app built on this suite those choke points
are G9SafeCommand, G9TabBar, DeferredContentView and G9PageBase, which live in the library. The
first consumer has 462 G9SafeCommand call sites. Wrapping them host-side would be a week of work
that decays the moment somebody writes the 463rd; one hook where they already funnel covers every
existing site and every future one.
This is NOT a testing feature and must not become one. The library raises two events and decides
nothing: it never references a test framework, never decides that a build is a test build, and
never collects anything on its own. What counts as busy, what is worth recording, and whether any
of it happens at all are the host's decisions, in the host's code and build configuration. The
moment this file grows a policy, every consumer inherits somebody else's opinions about testing.
Three details are load-bearing. Both hooks are plain static delegate fields that are null until
somebody assigns them, so a build that never subscribes pays one predictable branch per operation
— which is what makes it acceptable to ship in every configuration rather than behind a
conditional. Every invocation is guarded, because an observer that throws must not be able to
break the operation it was observing; a diagnostics hook taking down a user's save is the failure
that would end the feature. And OperationCompleted is raised from G9SafeCommand's finally, so a
host counting in-flight work can never leak a count when an operation throws and then wait
forever for an app that is already idle. Anything added to these hooks must preserve that pairing.
No behaviour change for existing consumers: nothing subscribes by default, and no existing API,
signature or rendering is touched.
1.0.7
- Selection lists and picker triggers: honour a per-item icon colour, and stop the value text
drifting away from its own icon.
G9SelectionItem.IconTintColor was ignored by the picker/combobox LIST. The trigger honoured it and
always had, so an item that carries its own colour — a soil type, a health state, an option a back
office coloured on purpose — rendered grey in the list and then snapped to its real colour the
moment it was chosen. The tint now wins in BOTH states, because an item that carries a colour
carries it as its MEANING, not as decoration.
⛔ That took TWO edits, and the first one alone was invisible. Rows are BUILT by
G9SelectionSheet.CreateRow and then RE-STYLED in place by UpdateRowVisuals on every selection
change (which exists to avoid a rebuild blink), and the second carried its own copy of the rule —
writing `selected ? Primary : TextSecondary` straight over the colour the first had just set.
Both now call one ResolveRowIconColor. A rendered value with two writers has no owner; see
LES-0043 for the diagnostic lesson, which is the part worth reading.
G9ComboBox and G9Picker triggers: the value label no longer picks its alignment with a direction
ternary, and no longer stretches. It carried
`HorizontalTextAlignment = IsRtl ? End : Start` while ALSO being given the culture's
FlowDirection — so under RTL it asked for the physical LEFT edge of a box that had just been told
to read right-to-left. Combined with a Fill/Grow label claiming all the leftover width, the
selected value ended up on the opposite side of the field from its own leading icon. Alignment is
Start in both controls now (direction-relative, so the flow mirrors it), and the label is sized to
its text so the glyph and its label stay one block on the reading edge. Same defect class as the
G9CultureDateTimeLabel fix in 1.0.2; see LES-0042.
No API change — all of it is rendering behaviour inside the shared selection row and the two
triggers. A consumer that had compensated for the old alignment with a PHYSICAL alignment should
re-check those call sites, exactly as in 1.0.2.
1.0.6
- G9SheetView / G9BottomSheetHelper: a multi-detent sheet now behaves like a platform sheet.
Three defects, one shape. A sheet opened with States = [Peek, Medium] could be dragged past
its own largest detent all the way to the top of the window, was then snapped back down to
whatever ratio the caller had guessed for Medium (leaving a band of empty sheet under the
content), and a downward drag from Medium raised a CLOSE request instead of stepping back to
Peek. All three came from the same place: the control derived its drag limits from the state
it happened to be IN, and AllowedState could not express "there is a peek step below this".
* The drag is clamped to the largest ALLOWED detent (and, for a non-cancelable sheet, to the
smallest). Below the smallest detent of a cancelable sheet the body now slides off instead
of shrinking, so a dismiss drag no longer re-lays the content out per frame.
* G9BottomSheetOptions.ExpandedFitsContent (new, default off) sizes the largest detent to the
MEASURED content, capped by MaxFitToContentHeightRatio — Material's fitToContents applied
to the top detent. Content taller than the cap stops at the cap and scrolls inside a
viewport the helper hosts for it; content shorter than PeekHeight also lowers the peek, so
neither step can show dead space.
* G9BottomSheetOptions.ScrollingExpandsSheet (new, default ON) makes a drag on a scrollable
body expand the sheet before the content scrolls — UIKit's
prefersScrollingExpandsWhenScrolledToEdge, whose default is likewise on, and Material's
nested-scroll contract. It is a NO-OP for every single-detent sheet by construction (they
are always at their maximum detent), so full-screen, single-state and fit-to-content sheets
scroll exactly as before. Turn it off for a multi-detent sheet whose body must scroll at
every step.
Also: the helper now applies IsCancelable / DragCloseThreshold to the control, which it had
always been documented to do; and the iOS / Windows handlers skip horizontal-only scrollers
when resolving the scroller under the finger, so a side-scrolling row inside a body can no
longer swallow that body's vertical scrolling.
CONSUMER ACTION: none for a single-detent sheet. A multi-detent sheet gains the clamp and the
scroll-expands behaviour; both are what the platforms do, and ScrollingExpandsSheet = false
restores the previous gesture priority if a call site depended on it.
1.0.5
- G9TabBar: new FabTapped event, raised BEFORE the control acts on a centre-FAB tap. Setting
G9TabBarFabTappedEventArgs.Handled suppresses the built-in sub-menu fan-out (and the implicit
move of SelectedIndex to the FAB slot) so a host can give the '+' its own meaning without the
fan-out flashing open and shut. Additive: unhandled taps behave exactly as in 1.0.4.
1.0.4 — Icons: restore the affordances the extraction had quietly replaced, and make each one
themable.
Extracting these controls out of their host app swapped every host icon reference for the
package's own built-in vector set. Where an icon had no built-in equivalent, the nearest
available glyph was substituted — and three of those substitutions changed what the control
MEANT rather than merely how it looked:
* G9BarcodeTextEntry showed the SEARCH magnifier at rest. The field opens a camera scanner;
promising "search" for that is a different action, not a different drawing.
* G9IntroCarousel's language switcher showed the INFO mark, which reads as help, not as a
choice of language.
* G9ProgressOverlayHelper's cancelled-sync toast showed the INFO mark instead of an
offline/no-connection cloud.
Two more collapsed distinct affordances onto one slot: the date picker sheet's "jump to today"
and the time-span picker's "date range" both resolved to the generic Calendar glyph.
This release adds the built-in glyphs ScanCode, Language and CloudOff (drawn in the same 24x24
stroke style as the rest), exposes G9Glyphs.ScanCode / Language / CloudOff / CalendarToday /
DateRange so a host can substitute its own icon font per affordance, and points the affected
controls at those slots. G9IntroCarousel and G9ProgressOverlayHelper had hard-coded G9Glyph
values, which no host could override at all; they now go through G9Glyphs like everything else.
No API removed and no behaviour changed beyond the artwork. A host that sets nothing gets the
new built-in drawings; a host that wants the pre-extraction icons assigns its own to the five
new slots.
1.0.2 — G9CultureDateTimeLabel no longer pins its own FlowDirection.
The label used to set FlowDirection = LeftToRight for every absolute (Date / Time / DateTime)
value, to keep a numeric date reading left-to-right inside a right-to-left screen. That is a
paragraph-direction switch, so it also pinned the label's ALIGNMENT: HorizontalTextAlignment and
HorizontalOptions resolve Start/End against the view's own effective flow direction, so a consumer
asking for "Start" got the physical LEFT edge under an RTL culture too. A date could therefore not
be aligned with the plain Label beside it in both languages — whichever alignment the consumer
wrote, one language was wrong. The order is now kept by wrapping the formatted value in a Unicode
LTR embedding (U+202A/U+202C), so the label is an ordinary Label for layout and its logical
alignment mirrors with the rest of the screen. Relative mode is unchanged (its phrase is localized
words and must read in the culture's direction), and nothing is wrapped under an LTR culture.
CONSUMER ACTION: a consumer that compensated by writing a PHYSICAL alignment (e.g.
HorizontalTextAlignment="End" purely to reach the right edge under Persian) now gets the trailing
edge in both languages, which is probably not what that call site meant. Express the intent
logically — Start to align with the label above it, End to sit against the trailing edge — and it
is correct in both directions.
1.0.1 — packaging fix, no API or behaviour change.
G9MAUIControls.Persistence.Sqlite 1.0.0 shipped a Windows resource index (.pri) that named
`G9MAUIControls.Persistence.Sqlite\icon.png`, a path the package does not contain — the icon is
packed at the package root, as PackageIcon requires. Every consumer building a net10.0-windows*
target failed with MSB3030 ("could not copy ... icon.png ... because it was not found"). The
packaging icon is now kept out of the SDK's default item globs family-wide, so it can no longer be
indexed as a WinUI resource. Only that one package was affected, because it is the only
Essentials-only project — the Controls-based four had the image swept back out of by
the MAUI targets. Consumers on 1.0.0 that added a build-side workaround can remove it after
upgrading.
1.0.0 — first stable release. The controls themselves are not new: they have been carrying a
production application for a long time, and this version is the point at which the extracted
PACKAGES have been driven by that same application end to end rather than merely compiling.
That pass found 21 defects in the library, all fixed here. The most significant were directional:
G9IconView now pins both of its children to LeftToRight, because an RTL parent made the platform
mirror the glyph canvas ON TOP of a control's already-correct directional choice — a double flip
that reversed every chevron and arrow in the suite and silently mirrored the non-directional glyphs
too. G9Glyph gains ArrowBack / ArrowForward (shaft + head) with overridable G9Glyphs slots, and the
bottom-sheet header uses them: a bare chevron in a header reads as an expander, not as "go back".
Verified on Android. iOS, Mac Catalyst and Windows build and pack but have not been rendered — see
AiGuides/09-Progress.md, which tracks that gap honestly.