fluent_2 0.0.5
fluent_2: ^0.0.5 copied to clipboard
Microsoft Fluent 2 components for Flutter web and desktop: 90+ widgets, 20 chart types, tokens, theming, a11y and 135 locales. No Material.
0.0.5 #
Fixed #
- Selected text stayed highlighted after clicking away from a field. Flutter
hides a selection in exactly one way — by handing
EditableTexta nullselectionColor. Blur does none of the work itself:_handleFocusChangedleavescontroller.selectionalone andRenderEditable's highlight painter draws from (range, colour) with no focus term, so a control that keeps passing a colour keeps painting the selection after the pointer has gone elsewhere. Focus was dropping correctly all along; only the paint outlived it.FluentInput(and soFluentDatePicker,FluentTimePickerandFluentTagPicker, which sharebuildFluentInput),FluentTextareaandFluentSearchBoxnow gate the colour on real focus, the wayTextFieldandCupertinoTextFieldboth do andFluentSpinButtonalready did.FluentSearchBoxhad a guard onenabled && !readOnly, which is true for an ordinary field's whole lifetime — it read as a gate in a diff and did nothing at runtime; both terms are now applied. - Pressing a field's own chrome dropped its focus. The padding, the border
and the
contentBefore/contentAfterslots are built around theEditableText, so they fall outside theTextFieldTapRegionit installs for itself and a pointer-down there ran_EditableTextTapOutsideAction. The selection gesture detector handed focus back on pointer-up, so it read as a flicker rather than a fault — but a focus-reactive slot unmounts under the cursor between press and release, and a spin button's steppers are outside the field too, so every increment blurred it.buildFluentInput,FluentTextarea,FluentSearchBoxandFluentSpinButtonnow each wrap their faceplate, asTextFieldwraps its decoration. All use the default group id, so the pickers wrapping the result again for their popups stays a no-op.
Changed #
- Blurring a Fluent text control now collapses its selection, rather than
only hiding the highlight. Material and Cupertino keep the range and restore
it on refocus; these controls deliberately do not, so returning to a field
lands a caret where the selection ended. The value is never touched — only
controller.selection— but an application that reads a selection back from its own controller after the field has lost focus will now find it collapsed. collapseFluentSelectionOnBluris new. An internal helper inlib/src/internal/text_selection_dismiss.dart, called from the focus listener each control already owned. It writes as little as it can: nothing while focused, nothing for an invalid or already-collapsed range, and it clamps the caret to the current text —TextEditingController.selection's setter throws rather than asserts pasttext.length, so that guard holds in release too.FluentDatePickerandFluentTimePickerkey it to focus leaving the whole control, not just the field, so an open calendar or listbox keeps its range.
0.0.4 #
Fixed #
- A popup opened from inside a
FluentPopovercollapsed the popover on the first click. Every popup light-dismisses through aTapRegiongroup; the seven non-popover popups hardcodedgroupId: thisand inflated their surface into a siblingOverlayEntry, so a pointer-down on the popup carried none of the popover's regions and itsonTapOutsidefired on the down, tearing down the subtree the popup lived in. This broke five controls in popover-hosted panels: the column and operator dropdowns, the value tag picker, and the date and time pickers. Popups now adopt the enclosing chain's group around their own viaadoptFluentTapGroup— a click on the popup is inside both, a click on the host's surface closes just the popup, and a click outside closes both. Placement and caching are documented at each call site; the wrapper must sit inside theCompositedTransformFollowerand the group must be read at the trigger's context, both of which fail silently if wrong.
Changed #
adoptFluentTapGroupis new. An internal helper inlib/src/internal/tap_group.dartthat wraps a popup surface in a secondTapRegioncarrying the enclosing chain's id. Returns the child untouched when there is no enclosing group.
0.0.3 #
Changed #
- BREAKING:
FluentRadioGroupScopeno longer carriesvalueoronChanged. Selection now travels through the framework'sRadioGroup<T>/RadioGroupRegistryinstead, so the scope keeps onlydisabledandlabelPosition. Code constructing or reading the scope directly breaks at compile time; code usingFluentRadioGroupandFluentRadiois unaffected. FluentRadio<T>is now aStatefulWidget. Its constructor is unchanged, so ordinary callers are unaffected — only subclasses andis StatelessWidgetchecks break.- Two radios sharing one value in a group now assert. The framework's
RadioGroupPolicyrejects it ("can't be used for a radio group that allows multiple selection") where it previously just painted two checked dots. That is a caller bug the framework is now diagnosing; suppressing it would mean not registering with the group at all. - A determinate
FluentProgressBarannounces50, not50%. The role requires a parseable number: at the declared floor of Flutter 3.41 the framework's owndouble.parsethrows aFlutterErroron a trailing%(3.47 later relaxed this). TheprogressBarrole plus the newminValue/maxValueis what makes a screen reader say "percent"; upstream Flutter's ownProgressIndicatoremits a bare number for the same reason. FluentPopoverArrowPaintertakes atextDirection. The painter drew a fixed physical apex whilebuildFluentPopoverlays its arrow out with a direction-awareRow, so in an RTL subtree the arrow appeared on the wrong edge, pointing into its own surface. PassDirectionality.of(context). Optional, defaulting toTextDirection.ltr— which is precisely the behaviour the painter had before the field existed, so this is not a breaking change and callers that omit it are unaffected.
Fixed #
- Every popup swallowed the click, hover and scroll behind it. All seven —
FluentPopover,FluentMenu,FluentDropdown,FluentTagPicker,FluentTimePicker,FluentDatePickerandFluentBreadcrumb's overflow — painted an opaque full-screenPositioned.fillbarrier to catch outside taps. It caught everything else too: a button behind an open popup needed two clicks, hover never reached it, and the page could not be scrolled. Dismissal is now aTapRegiongroup, matching upstream's document-leveluseOnClickOutside— the click dismisses and lands. Two consequences worth knowing: aTapRegionSurfaceancestor is required, whichWidgetsApp(and soFluentApp) provides but a bareOverlaydoes not; and on touch or trackpad a drag-scroll now dismisses, becauseRenderTapRegionSurfacedoes not take part in gesture disambiguation. Mouse wheel is unaffected. - A popover opened from inside another popover collapsed the chain. The
inner surface lives in its own
OverlayEntry, so it fell outside the outer popover's tap group and merely opening it read as an outside tap. Nested popups now share one group viaFluentTapGroup. - Closing a popover no longer steals focus from the control an outside tap
just landed on — it restores to the trigger only when the popover itself held
focus, the rule
FluentDatePickeralready documented. - A
FluentSwitchinside aFluentFieldrendered a stretched track. With no label the switch's content was a bareSizedBox.fromSize, andFluentFieldlays its children out withCrossAxisAlignment.stretch(deliberately, matching upstream'sdisplay: gridroot) — so a tight width constraint forced the 40x20 track to the full field width, measured at 584px on the Field page's component example. Upstream's switch root isdisplay: inline-flexand never stretches, so the switch now always wraps in amainAxisSize: minrow, exactly asFluentCheckboxalready did. FluentSwatchPickerwas N tab stops with dead arrow keys, while telling assistive technology it was a mutually-exclusive group. It now has a roving tabindex: one tab stop, arrows move within it. The arrow model follows upstream'suseArrowNavigationGroup— a row wraps on all four arrows; a grid steps Left/Right across row boundaries and wraps, moves Up/Down by row without wrapping, and Home/End reach the ends of the current row.- An open popup no longer keeps a stale height when the page scrolls under
it.
FluentDropdown,FluentTagPicker,FluentTimePicker,FluentDatePicker,FluentInfoButton,FluentBreadcrumbandFluentMenuall cap their surface at the room left beside the anchor, and nothing re-measured it once open: the surface correctly followed its trigger up the viewport while keeping the height it was given near the bottom. They now re-measure on scroll, matching upstream's reposition-rather-than-close behaviour. Gated onScrollPosition.isScrollingNotifier, so it re-measures at scroll start and stop rather than every frame. FluentRadioGroupwas N tab stops, not one. A radio group is a single composite control: Tab enters it once and arrows move within it. It now adopts the framework'sRadioGroup/RadioClient, which also fixes a group whose selected radio is disabled becoming entirely unreachable by Tab.FluentTabListandFluentListwere N tab stops each, contradicting theSemanticsRole.tab/tabBarthey already declared. Both now use the roving tabindexFluentToolbaralready implemented.- Semantics roles.
FluentDataGridnow exposestable/row/cell/columnHeader;FluentSpinnerloadingSpinner;FluentPresenceBadgeandFluentStatusIndicatorstatus;FluentProgressBarprogressBarwith a range.FluentNav,FluentNavDrawerandFluentBreadcrumbtake thenavigationlandmark role only when given a non-emptysemanticLabel— two unnamed landmarks on one page is a framework assertion, and an unnamed nav beside an unnamed breadcrumb is the ordinary case. - Text controls had no context menu at all.
FluentTextSelectionControlsextendedTextSelectionControlsrather than mixing inTextSelectionHandleControls, soTextSelectionOverlay.showToolbartook the legacybuildToolbarpath — which returned an empty widget — and never consultedEditableText.contextMenuBuilder. Right-click and long-press produced nothing onFluentInput,FluentTextarea,FluentSearchBoxandFluentSpinButton. - A subtree
FluentThemeOverrideno longer leaks out of the text context menu.ContextMenuController.showcaptures inherited themes from inside its ownOverlayEntry.builder, wherefrom:andto:are both already below theNavigator, so the capture came back empty;fluentTextContextMenuBuildernow captures at the call site. FluentNavandFluentToolbarcould become permanently unreachable by Tab. Disabling the row or item holding the roving tab stop left the control with zero tab stops, and no registration event fired to re-park it. The toolbar additionally had to clear the derivedskipTraversalits item latched onto its own node while its gate was shut.FluentDataGridpainted no focus ring on the first cell reached by Tab — the rebuild was gated on the cell having moved, and the cell you enter is always the one already holding the roving index.- Popups no longer open off-screen.
FluentTagPicker,FluentTimePicker,FluentBreadcrumb's overflow,FluentInfoButtonandFluentMenunow clamp to the room actually left beside their anchor and flip to the other side rather than collapsing. A bottom-edgeFluentMenutrigger previously rendered a surface of height zero — focus moved into it, Escape bound to it, and nothing painted. Submenus measured their anchor row's position inside the parent surface and read it as a screen coordinate, overhanging the viewport by exactly the parent's owny. - Open popups no longer go stale.
FluentDropdown,FluentTagPickerandFluentTimePickerrebuild their overlay when a dependency moves, so resizing the window re-measures the maximum height and a theme swap repaints the popup. - RTL.
FluentPopoverimplemented its documented reading-order positioning physically;FluentTooltippicked the wrong side and mirrored its arrow wrongly;FluentCarousel's arrow keys ran opposite to its visible motion and its buttons were pinned to physical edges;FluentRatingpainted, hit-tested and arrow-keyed left-to-right only.Directionalityalso does not cross anOverlayEntryboundary and is now carried explicitly into the popover, dialog, toast and menu overlays. - Leaks and lifecycle. Four
OverlayEntrys were removed without being disposed (FluentDatePicker,FluentTimePicker);FluentDatePickerdisposed itsFocusScopenode before removing the entry that builds under it;FluentTreenever pruned focus nodes for removed items;FluentTabListnever disposed its flipCurvedAnimation;FluentSpinButtonleft a focus listener on its internal node. FluentMenuno longer closes an open submenu on an unrelated rebuild. It compared itsitemslist by identity, which is false for any caller passing a fresh list literal.- A right-click no longer paints the pressed token on every control built on
FluentInteractive. - An overlay
FluentDrawernow hides the page behind it from assistive technology, andFluentBreadcrumb's invisible dismiss scrim no longer appears in the semantics tree as an unlabelled full-screen button. FluentTooltipno longer announces its content twice when asemanticLabelis set.
Publishing #
- Require
fluent_2_core0.0.3.
0.0.2 #
Added #
- ColorPicker.
FluentColorPicker,FluentColorArea,FluentColorSliderandFluentAlphaSlider, ported from@fluentui/react-color-picker-preview, withFluentColorPickerStyle,FluentColorAreaStyleandFluentColorSliderStyle, the three matching*Themewidgets and the usualresolve*State/resolve*Style/build*trio per component. The colour is Flutter's ownHSVColor— no new colour type reaches the API — and a picker publishes it to its children throughFluentColorPickerScope, so all four controls always agree. Controlled only: there is nodefaultColor, because upstream's uncontrolled mode gives each child its own copy of the value and they drift apart on the first drag. A new showroom page covers all eight upstream stories.
Fixed #
FluentSliderno longer loses a touch drag to a scrolling ancestor. It used aGestureDetectorwithonTapDown+onHorizontalDrag*, which does not claim the pointer until the drag slop is exceeded — long enough for a horizontally scrolling parent to win the arena. It now claims at pointer-down through the sameEagerGestureRecognizerpath the colour controls use. Mouse input was never affected, because Flutter leaves mouse out ofScrollBehavior.dragDevices, which is why no test caught it.
Breaking #
- Removed
FluentSpinButtonUnderlinePainter. The spin button's two bottom rules now come from the sharedFluentInputUnderline/FluentInputFocusUnderlinepair, like every other input. The painter drew both withcanvas.drawRect, so they were square and overhung the field's rounded corners; upstream'suseSpinButtonStylesrounds them viaheight: max(2px, borderRadiusMedium)plusclipPath: inset(calc(100% - 2px) 0 0 0).FluentSpinButtonChevronPainteris unaffected. Anyone who subclassed or instantiated the removed painter should use the two widgets instead.
Publishing #
- Use the Fluent 2 project logo as the first pub.dev screenshot and thumbnail.
- Require
fluent_2_coreandfluent_2_fonts_web0.0.2.
0.0.1 #
- Initial release.
