fluent_2 0.0.7
fluent_2: ^0.0.7 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.7 #
Added #
- Declarative routes to the two charts no schema could reach (extension,
not upstream:
PlotlySchemaConverter.tshas no arm for either). A Plotly trace whose free-formmetacarries{"fluentChart": "sparkline"}(on ascatter/scattergl) rendersFluentSparkline, and{"fluentChart": "horizontalBarChart"}(onbarwithorientation: "h") renders the axis-free part-to-wholeFluentHorizontalBarChart— one row pery, one segment per trace, optionalmeta.totals/meta.variant. Without the marker every trace routes exactly as upstream does; Plotly itself never interpretsmeta, so the figure stays valid elsewhere.FluentPlotlyChartKindgainssparklineandhorizontalBarChart.
Fixed #
-
FluentGaugeChartunder an unbounded height — a scroll view, orFluentDeclarativeChart's own column for anindicatortrace — threw a flex error thaterrorBuildernever saw. The arc box now takes the height the layout was computed at (height, else the intrinsic one), as upstream sizes the svg from theheightprop; a bounded height still expands as before. -
Plotly labels drew their HTML entities —
R&D,TK'ninanda<brendered asR&D,TK'nin,a<bin everyFluentDeclarativeChartchart but annotations.sanitizePlotlyJsonencodes every string as upstream does for the DOM, which decodes on the way in; Flutter'sTextdoes not, and only annotations decoded. The figure is now decoded once after routing, before any transformer (decodePlotlyJsonStrings), and annotations no longer decode a second time, so a typed&stays&. -
FluentHorizontalBarChartwith a series repeated across rows threw a duplicate-key error: upstream builds a legend per point and React only warns. One legend per title now, first-seen.
0.0.6 #
Added #
-
FluentBoxDecorationpaintsboxShadowthe way CSS does — outside the box only. ABoxDecorationshadow is a blurred copy of the box drawn under it, which a transparent box shows as a grey wash (shadow16darkens the inside to about 76%). Upstream shadows unfilled elements routinely, so this is the decoration for them; the blur also uses CSS's σ = blur / 2. Everything else paints asBoxDecoration. The CarouselNav and Carousel demos use it, which is what stopped the CarouselNav card rendering solid grey. -
Charts:
calloutPropsPerDataPointonFluentHorizontalBarChartandFluentDonutChart, spread over the built-in reading as ChartPopover does, andlegends/enabledWrapLinesonFluentHorizontalBarChart(upstream'slegendProps), whose wrapped rows render each legend'sannotationBuilder.FluentChartTooltipBox, the box a cut-short axis label shows its whole text in on hover or a touch tap.FluentChartPopover.anchorRect,FluentChartPopoverData.isCartesianandcontentMaxWidth,FluentChartHitRegion.popoverAnchor,FluentCartesianChartProps.popoverFollowsPointerandFluentYValueHover.shouldDrawBorderBottom.FluentChartHitRegion.hitTest(a mark hovers and clicks as its painted shape, a circle as a circle),focusableandfollowsPointer, andFluentCartesianSeriesDelegate.hoveredRegionAt, which names the hovered region on the same pointer event the chart hears.FluentCartesianSeriesDelegate.activationAt, consulted on a press that hits no region (a line chart uses it to click its stroke), andFluentCartesianChart.onFocusedRegionChange, fired as the arrow keys move the roving focus index and with null on blur.FluentChartPopoverData.culture(upstream'sChartPopoverProps.culture), in which a multi-value popover formats its rows.FluentDonutChart.popoverBuilder, which replaces the popover body for the hovered or focused datum (upstream'sonRenderCalloutPerDataPoint); a null return keeps the default body.FluentLineChart.eventAnnotationStrokeColor,eventAnnotationLabelColorandeventAnnotationLabelWidth(upstream'seventAnnotationProps.labelWidth, default 105), forwarded to the event annotation layer.FluentCartesianChartProps.copyWith(useUTC:).FluentChartImageExportertakesclipHeight,continuationKeyandcontinuationX, which the declarative chart's export uses to draw every legend and a table's whole grid.
-
Components:
FluentSplitButton.menuExpandedand an optionalchild(an icon-only primary half);FluentMenuTriggerScope, through whichFluentMenuscopes its open state to its trigger so the split button's chevron half takes its Selected tokens and announces expanded.FluentFocusRing's inset mode, andFluentButtonStyle.focusRingInsets,focusRingInnerColorandshadow(upstream's ring sits inside the button; primary adds a white inner ring andshadow2).FluentSpinnerPainter.textDirection(the arc mirrors in RTL),FluentSpinnerMotion.reducedRotation,FluentSpinnerPose.reducedandtailFades.FluentInputStyle.focusUnderlineWidth, the focus bar's thickness.FluentDatePickerValidationResult.message, theerrorStrings(or ambient locale) text for the error, for aFluentFieldto show.FluentCalendarPanel.slideProgressandslideForwards.FluentButton.activeIcon(upstream'sbundleIconfilled glyph, shown while a subtle or transparent button is hovered or pressed; also onFluentCompoundButton) andFluentButton.menuIcon(MenuButton's chevron slot: 12, or 16 at large, 4 after the label, in the label colour).FluentButtonStyle.iconColor,menuIconSizeandanimationDuration.FluentLinkUnderline, which paints a link's underline as a crisp 1px line under every line of the label.FluentCarouselNavAppearance,FluentCarousel.navAppearanceandFluentCarouselStep.appearance(upstream's brand CarouselNav).FluentTreeItem.aside, the always-visible trailing slot.FluentInteractive.disabledMouseCursorandpressedRequiresHover(upstream's:hover:active).FluentInteractionTag.activeIcon, the filled glyph an outline tag shows while hovered or pressed, andFluentTagStyle.iconColor.
Changed #
-
Requires
fluent_2_core0.0.4, which keepsFluentApp's tree when it rebuilds after the fonts load and corrects eight palette tokens to Fluent UI React v9 (lavender avatars no longer render navy). -
Chart prop spellings fixed; the old names are deprecated, not removed.
showXAxisLablesTooltip,wrapXAxisLables,rotateXAxisLables,showYAxisLablesandshowYAxisLablesTooltiponFluentCartesianChartProps(andresolveShellXAxisTickPadding's parameter) are now*Labels, andxAxistickSizeonFluentCartesianChartPropsandFluentXAxisParamsisxAxisTickSize. Each old name stays as a@Deprecatedconstructor argument, getter andcopyWithargument that forwards to the new one, so 0.0.5 code compiles. -
Deprecated:
FluentChartLegendRow.listLength(now optional; Flutter has noaria-setsizesemantics to carry it) andFluentCartesianChartProps.enableFirstRenderOptimization(the port always lays out with real constraints, so there is no first frame to skip). Both were already ignored. -
tickPaddingis honoured. A caller'sFluentCartesianChartProps .tickPaddingused to be read only as a truth test (25 painted a 5 gap, 0 gave 10), copying a JavaScript precedence slip; it is nowtickPadding ?? (tooltip ? 5 : 10). -
Button geometry follows upstream in Chrome (every
FluentButton): a 1px border that takes layout space on every appearance, labelled buttons floored at 64/96/96 wide, icon-only buttons a 24/32/40 square with 1/5/7 padding, the focus ring inside the button, and a menu button's label and chevron centred as one group. The legend's "+N more" button grows to the floor as well. -
FluentSplitButtonmatches upstream: icon-only chevron half (large is 31 wide, the chevron 1px low as upstream's svg sits), the divider's tokens per appearance and state, the open-menu Selected state, a whole-pixel seam, and the edges drawn with CSS border geometry — a circular split button is now a stadium, not two ellipses, and the outline no longer notches at the seam. -
FluentSpinnermoves like react-spinner 9.4+ (two conic wedges behind a mask) instead of the old stroke-dasharray keyframes: the arc grows from 30° to 255° and back with no snap at the cycle boundary, has flat ends, and mirrors in RTL. Under reduced motion the ring keeps turning over 1.8s with a fixed, fading comet tail, as upstream does, instead of freezing. The ring repaints on its ownRepaintBoundary. -
FluentGanttChart's date axis honoursuseUtc(it built a local time scale while the popover read UTC);props.useUTC ?? useUtcnow feeds both, and onlytrueor'utc'enable UTC. -
FluentDataGridheaders and rows follow upstream: a sortable header is one interactive surface with the subtle hover and press fill, a bare 12px arrow shown only while it holds the sort (an unsorted column draws none, keeping an empty slot), and regular-weight labels; a pressed row fillssubtleBackgroundPressed; a cell's focus ring shows for keyboard focus only. -
Nav rows follow the storybook: labels stay
neutralForeground2in every state, a held plain row keeps the hover fill, and split-row secondary actions fade in over the row end on hover, press or focus, so the label keeps its width. -
Horizontal bar rows fit their gaps. A row of n bars used to run (n − 1) × 3px past its edge (past the start in RTL); bars now shrink into the room the gaps leave, and a zero-width bar takes no gap (
FluentHorizontalBarRowLayout.compute, newabsoluteLabelIndex). -
BREAKING (custom styles):
FluentInputStyle.borderWidth,bottomBorderColorandbottomBorderWidthnow describe a real CSS-style border. The border takes space and the content row sits inside it, on top ofpadding— the bottom side used to be a rule overlaid on the box's bottom edge that cost no layout. A nullborderColormeans no border and no inset whateverborderWidthsays; a nullbottomBorderColormeans the bottom followsborderColor. A custom style that set a wide bottom rule, or relied on the border not moving the text, will lay out differently. The same holds for the bottom-side fields ofFluentSearchBoxStyle,FluentTextareaStyle,FluentSpinButtonStyle,FluentDropdownStyle,FluentTagPickerStyle,FluentDatePickerStyleandFluentTimePickerStyle. -
BREAKING (sibling APIs), each to match upstream:
FluentSpinButtonBaseState.inertis gone — read-only no longer styles the surface, only the steppers go inert;FluentSpinButtonChevronPainterno longer takesglyphSizeorstrokeWidth: it fills upstream's 14px svg path;FluentTextarea.maxLines: nullholds the field atminLinesrows and scrolls, as upstream'srowsdoes; passmaxLinesto let it grow;FluentTimePickerStyle.trailingGapis now the space between the text field and the trailing glyph, andtrailingPaddingthe inset around the text field;FluentDropdownandFluentTimePickertake upstream's 250px minimum width;FluentTagPickerdraws upstream's expand chevron by default (expandIcon,fluentTagPickerChevron), is 34/42/46 tall, and has 32px option rows and upstream-sized tags;FluentTagDismissPainter.inkRatiois gone: the dismiss glyph fills upstream'sDismissRegularpath instead of a scaled cross;FluentSpinButtonStepperis now aStatefulWidget(it repeats while held).
-
FluentTag.mediais new — upstream'smediaslot, for an avatar, 1px inside the border;iconstays at the content inset. AFluentTagPickerchip puts its avatar there (newFluentTagPickerOption.tagMedia, which falls back tomedia). -
FluentInputBorderPainteris new and is what paints the border. Where the bottom colour differs from the sides, the two meet along each bottom corner's diagonal the way a browser joins adjacent border colours, instead of the bottom colour stopping a pixel up the arc. Public with its fields, so tests read the resolved tones off it, as withFluentRadioIndicatorPainter. -
The focus bar eases on CSS
easeand reverses the way CSS does. Upstream writes the curve tokens intotransitionDelay— a typo fortransitionTimingFunction— so every browser drops them and runsease, which the port had been reading as intent and replacing withcurveDecelerateMid/curveAccelerateMid(45% of the bar drawn 20ms in, against upstream's 9.5%).fluentInputFocusUnderlineEnter/Exitare nowFluentCssCubic.ease— new, acubic-bezier()solved to 1e-7 as Chromium solves it, where Flutter'sCubicstops at 1e-3 and drew the bar's ends up to 0.4px off Chrome's — and a focus change mid-flight starts a fresheasefrom the current scale over the direction's duration times the distance left, instead of retracing the old curve. Every field sharing the bar picks this up:FluentInput,FluentTextarea,FluentSearchBox,FluentDropdown,FluentTagPicker,FluentSpinButton,FluentDatePickerandFluentTimePicker, whose alias constants follow. -
FluentInputmatches upstream as rendered in Chrome rather than the Figma set, and so doesFluentDatePicker, which is.fui-Inputupstream and derives its faceplate from it:- a focused outline field keeps
neutralStroke1Pressed/neutralStrokeAccessiblePressedeven while hovered; - a read-only
FluentInputis styled exactly like rest — it was on the disabled ramp; - invalid is
colorPaletteRedBorder2(#d13438light), notstatusDangerBorder2; high contrast keeps the status token; - underline has square corners, on the root and the focus bar;
- the bottom border stays 1px while pressed — it was 2px;
- text and slots sit inside the border: 1px further in on outline and filled, and 0.5px higher on underline, whose only border is the bottom one;
- large's text inset is 18 (12 +
SNudge), not Figma's 14; - the caret is 1px, a browser's width, not
EditableText's 2 (also inFluentTagPicker, which composesFluentInput).
- a focused outline field keeps
-
FluentSearchBox,FluentTextarea,FluentSpinButton,FluentDropdown,FluentTagPickerandFluentTimePickermatch upstream as rendered in Chrome, each measured across its appearances, sizes and states at a pixel ratio of 4:- read-only is styled like rest on all of them — it was on the disabled ramp;
- invalid is
colorPaletteRedBorder2, with newerrorparameters onFluentSearchBox,FluentDropdownandFluentTagPicker; - the bottom border is 1px in every state, joins the sides on the corner diagonal and, like the rest of the border, sits outside the content;
- the caret is 1px, and a disabled field shows
not-allowed; - focus follows each component's own cascade. SearchBox and SpinButton write
:active,:focus-withinas one rule, so a focused field keeps the Pressed stop through a hover, as Input does. Textarea, Dropdown, TagPicker and TimePicker write:focus-withinalone, which Griffel sorts before:hover, so hover wins there; a focused Textarea's bottom border iscompoundBrandStroke; - Textarea is 44/56/68 tall (new
FluentTextareaStyle.minimumSize); SpinButton's border is drawn over its steppers, whose fills follow the appearance; TimePicker follows Combobox's padding and ramp rather than Input's, and gained hover and press states; Dropdown's fill no longer ramps on hover and press — only Outline's border does, as upstream.
-
FluentDatePickerandFluentTimePickerpass their realreadOnlyto the style resolver. They used to hide it so a default, read-only picker did not render greyed out; with read-only unstyled there is nothing left to hide. -
Every text field focuses on mouse-down, as Chrome does, so the focus bar grows under a held press. Flutter's text gestures focus on tap-down, which a middle press never reaches and which waits for the gesture arena whenever it is contested: the bar started a whole click late.
FluentInput(and soFluentTagPicker),FluentTextarea,FluentSearchBox,FluentSpinButton,FluentDatePicker,FluentTimePickerandFluentDropdownnow focus on a mouse press of any button, with the caret where it landed, through the field's own selection path so desktop's select-all-on-focus stays out. SearchBox focuses only from its<input>box, not the icon or padding, and a right press that moves focus there is not:active(Chrome). -
Behaviour now follows upstream in Chrome:
- SpinButton steppers step on press and repeat while held (305 / 541 /
725ms…, within a frame of upstream); a stepper whose value equals its
bound goes inert — greyed glyph, no fill,
not-allowed— and a press there takes focus away; - TagPicker keeps its field (and focus) when the first tag appears, so typing continues after a pick; a printable key reopens the list and highlights the first option starting with the text, which Enter adds; the list refreshes while typing; a click anywhere on a chip removes it; the secondary-action aside stretches to the full height;
- DatePicker gained hover and press, and shows the hover ramp rather than the focused one while the calendar holds focus;
- a right press shows
:activeon Input, DatePicker, Textarea, SearchBox and SpinButton's text, but not on a live stepper or the Combobox family; - Textarea shows touch selection handles for touch only;
- a disabled field shows
not-allowedover its text, and a disabled Dropdown over its whole trigger.
- SpinButton steppers step on press and repeat while held (305 / 541 /
725ms…, within a frame of upstream); a stepper whose value equals its
bound goes inert — greyed glyph, no fill,
-
A fixed parent height stretches every text field, with the focus bar on its bottom edge, as CSS
heightdoes — it used to draw the box at its natural height and the bar below it. -
BREAKING (behaviour), to match upstream's Combobox family:
FluentTagPickerRemoveLastIntent(Backspace in an empty field) focuses the last chip instead of removing it; Delete, Backspace, Enter or Space (on release) then removes the focused chip;FluentTagPickerStyle.fieldWidthis a minimum (24) rather than a fixed 96: the field takes the rest of the chips' last line and wraps below it or when its text no longer fits;contentPaddingchanged meaning, andtagRunSpacing,tagPaddingandfieldSpacingare new;- a non-freeform
FluentTimePickertakes a caret and typing (type-ahead; its text reverts on close or picks an exact match), andresolveFluentTimePickerState(readOnly:)defaults to false;hourCycleis nullable; the default parser accepts upstream's formats only (8is no longer a time); the 416pxsurfaceMaxHeightdefault is gone — the list takes the room below the field; FluentInteractivereportspressedfor middle and right presses (newpressedOnSecondary, which the Dropdown trigger turns off) and tracks hover while disabled.
-
Pickers behave as upstream's in Chrome (each measured there first):
- TimePicker: freeform typing rings the matching option; typed text is kept after Tab, blur and Escape instead of being reformatted; Enter with a typed match only opens, with none it commits and opens on the committed time; Space picks on an open list not being typed into; a chevron press toggles on mousedown for any button and leaves the caret; a click that drifts still counts; the list keeps its scroll across a reopen;
- TagPicker: typed text is cleared on blur or close; chips are focusable, and a press on one while open removes it and moves focus to the next; a click on the field toggles the list (primary button only), and a press on the chevron, padding or aside toggles on mousedown; Up opens on the first row, disabled rows are visited, and a pick leaves a caller's controller alone;
- Dropdown: PageUp/PageDown move ten rows, Alt+Up selects and closes, Alt+Down opens or moves on, keypad Enter commits, and every key but Alt+Up reads the same under Shift, Ctrl or Meta; disabled rows are reachable; Escape on a shut list reaches a dialog around it;
- DatePicker: a click while the calendar is open leaves it open unless text input is on; with text input the popup keeps focus under a held press and the closing click puts the caret where it landed; a press dragged off the field opens nothing;
- lists scroll their active row just into view, 2px clear, instead of centring it.
-
FluentFieldtakes upstream's validation colours and glyphs: the message and glyph arecolorPalette*Foreground1per state (error#bc2f32), and a default glyph is drawn per state (newFluentFieldValidationGlyph) unless one is passed.FluentLabelwraps a long label with the required asterisk inline after the last word. -
FluentTooltip.positionis now a preference. A tooltip with no room on that side opens on the opposite one; see the Positioning section ofFluentTooltip.buildFluentTooltipis unchanged and still stacks the arrow on exactly the side its state names. -
BREAKING (charts):
FluentChartPopoverLayoutDelegateis replaced byFluentChartPopoverLayout(andRenderFluentChartPopoverLayout), which places the surface as upstream's ChartPopover does: above and centred, flipped, shifted into the chart root and height-capped with its body clipped.FluentVerticalBarChartDelegate.barDomainis nowbarDomainFor(FluentCartesianChildContext), andFluentScatterChartDelegate.popoverForreturnsFluentChartPopoverData?(null where no point at that x shows a callout).- The HorizontalBar, Donut, Funnel and Polar popovers float in the app's
Overlay, as upstream's overhang their chart, so hovering one needs anOverlayancestor (FluentAppandWidgetsAppprovide it). FluentChartHitRegion.popoverDatais nullable: null opens no callout and closes an open one, as a mark another legend dims does upstream. A custom delegate that readsregion.popoverDataneeds a null check.
-
BREAKING (components), each to match the storybook:
FluentDialog.showCloseButtonis nullable; null draws the header close button on a non-modal dialog only, and the close is a bare 20px glyph;- every popover surface lays out its 1px border (2px larger, content 1px
further in), flips and shifts to stay in the viewport, and
FluentPopoverStyle.arrowInsetdefaults to 8; - tree item actions show only while the row is hovered, pressed or focused, over a 32px row; list items paint no fill; carousel previous, next and autoplay are 32x32;
- compound button padding, type and icon gap follow upstream (60/72/80 high with an icon).
-
Charts match the storybook's hover (each measured against the live storybook): line, area, scatter, vertical, stacked, grouped and horizontal bar charts, donut, gauge, funnel, heat map, gantt, polar, sankey and sparkline open, place, follow and close their callouts as upstream does, and grow markers and draw hover rules where upstream does.
-
Components match the storybook's hover and press: buttons tween border and label colour, subtle icons turn brand on their own, checked toggles draw upstream's border and icon, radio labels ramp, nav, tree, data grid, list item, breadcrumb and carousel dots follow React's ramps where Figma disagreed, and swatch rings keep their white hairline.
-
Disabled controls show
not-allowed, and a press dragged off falls back to rest, where upstream writes them:FluentInteractiveshowsnot-allowedover a disabled surface by default, as Button, Link, menu rows, Tab, Tag (the whole tag), InteractionTag, ColorSwatch, AccordionHeader, Card and Breadcrumb do; Checkbox, Radio, Switch, Slider, ListItem, listbox options, Calendar, the colour pickers, InfoButton, Nav and Tree keep the arrow;- Button, CompoundButton, SplitButton, Switch, Radio, ColorSwatch, InfoButton, the Calendar's navigation, caption, today and month/year cells, menu rows and breadcrumbs are pressed only while the mouse is over them, so a press dragged off shows rest and presses again on the way back; a touch press holds.
-
Charts answer touch and legends as upstream does: a touch tap hovers what it lands on (markers, hover rules and VerticalBarChart's line dots included) and a tap outside the chart ends it; a mark another legend dims opens no callout and takes no tab stop but still clicks; a LineChart segment opens its start point's callout on the move that reaches it; a following callout moves only once the pointer has moved more than 1px.
-
More components follow React over Figma:
- the Tag dismiss glyph (every appearance) and brand InteractionTags ramp
through
colorCompoundBrandForeground1Hover/Pressed; FluentSwatchPickerpads by 0, so its swatches sit flush with the picker (they were 10px in);- a vertical
FluentFieldpads its label 2px above and below (1px at large), so the control starts 26px below the field's top, not 22; FluentCalendarpages with upstream's arrow glyphs instead of chevrons;- the hamburger stays transparent and only its glyph ramps, a disabled unselected subtle-circular tab is unfilled, and an autoplaying carousel's toggle rests checked while it plays.
- the Tag dismiss glyph (every appearance) and brand InteractionTags ramp
through
Fixed #
-
Turning reduced motion off again left the focus bar snapping. The bar zeroed its durations when
MediaQuery.disableAnimationscame on and never restored them; they are now set on every dependency change. -
Pressing another field while a
FluentDropdownheld focus focused nothing until release. The Dropdown's outside-tap blur ran on the same pointer-down and parked focus on the route's scope, cancelling the pressed field's own request. It now blurs only if focus is still its own once that event has settled, as a click on the page body does. -
A field removed mid-press threw on the release.
FluentInteractive,FluentInput,FluentTextarea,FluentSearchBox,FluentSpinButton,FluentDatePicker,FluentTimePickerandFluentTagPickerignore a press that ends after they are gone. -
Hover and press were lost when a field was re-enabled under the mouse.
FluentDatePicker,FluentTimePicker,FluentTagPicker,FluentTextareaandFluentSearchBoxtrack them while disabled and filter them in the build, as Chrome keeps a disabled root's:hoverand:active. -
A
FluentDropdownwhose options shrank under the open list threw on Enter; the active row now falls back to the first option. -
A
FluentTooltipnear the edge of the window was drawn outside it. The surface always sat on the sidepositionnamed (aboveby default) and was centred on its trigger with no bounds check, so a tooltip on a bar at the top of the window landed above the window entirely, and one on a bar's end button ran off the side. Upstream's tooltip never pins its placement, sousePositioningapplies floating-ui'sflipandshiftto it: the surface now flips to the opposite side when the preferred one cannot hold it and the opposite one has more room (before/afterin reading order), and slides along the trigger's edge to stay inside theOverlay. The arrow is drawn for the side the surface landed on and stays centred on the trigger, 8px clear of the surface's corners, the way floating-ui'sarrowmiddleware places it. With room on every side nothing moves. It is settled in one layout pass — no frame shows the surface on the wrong side, and nothing is scheduled while it is open — and the surface still follows a trigger that moves. -
Documented chart and input options that did nothing now work:
cultureonFluentAreaChart,FluentVerticalBarChart,FluentHorizontalBarChartWithAxisandFluentScatterChartlocalizes their readings and tick labels, and the stacked popover of Line, grouped and stacked bar charts formats in it (12.345,6under de-DE);- per-point
onClick/onDataPointClickhandlers run on HeatMap, VerticalBar, HorizontalBarWithAxis, Gantt, GroupedVerticalBar, Area and Scatter (only Line and stacked bar ran them), and each bar of a coalesced grouped-bar stop keeps its own click; FluentLineChartSeries.onLineClickruns when the stroke between markers is clicked (markers still win);FluentEventAnnotation.cardBuilder: clicking a label with cards opens them in a popover below it, in date order, closed by Escape or an outside click;accentWidthonFluentDatePickerStyleandFluentTimePickerStylesets the focus bar's thickness;FluentDatePicker.errorStringsreaches the validation result'smessage.
-
Chart export is sharp and complete. The declarative chart's Download captured at pixel ratio 1 and stretched it 5× nearest-neighbour, exported only the table rows in view and the "+N more" button instead of the legends. It now captures at the export scale (fitted inside the browser's 8192px / 4096² limits), draws every legend as vectors and exports a table's whole grid.
-
A submenu's entrance played twice when its row was clicked within the hover delay, or clicked again while open. Opening cancels the pending hover timer, an already-open submenu only closes what is below it and takes focus, and a stale timer no longer reopens a submenu after Escape or after the pointer left for an ancestor row.
-
A split
FluentCalendarreplayed the wrong grid's slide. Each panel now slides on its own when its page changes, and paging the day grid past December moves the month picker to the new year. -
The scatter chart grew marker
0_0whenever it had focus; it now grows the marker the arrow keys moved to, and nothing on a bare Tab. -
Charts:
- an unknown
culture(such as'rs-ss') falls back to the default locale instead of throwing during build; - a gauge segment hovers on its painted path (not its bounding box), strokes its outline on keyboard focus only, and its callout targets the segment, the rotated needle or the value text; the title sits on upstream's baseline and the value is measured in the font it paints in;
- the donut popover anchors on its arc, stays open until the pointer leaves the chart, and reads in the chart's culture; the polar popover clears its marker; the funnel and polar callouts follow their mark on scroll; the sankey callout stays where the pointer came in and closes only when the pointer leaves the chart;
- an open HorizontalBarChart popover hides when
hideTooltipturns on; - the scatter chart builds its callout index once (a 2000-point chart laid out in ten seconds before its first hover) and ends its rule where upstream does; the area chart's hover listens from the left edge in RTL;
- an axis-label tooltip near the edge keeps its words whole instead of breaking them mid-word;
- a dashed Vega rule keeps its label border solid; an empty legend adds no margin to a barred popover row;
- the donut and gauge measure only the arc a popover targets (a 30-slice hover sweep went from 3.6s to 1.2s in debug);
- a log y axis draws its default log ticks; mixed number and
Datex values compare by value, and aDateextent on a numeric axis no longer throws; - annotation boxes lay out and paint as the CSS container (max-width, border, outer shadows, dashed and dotted borders);
- legend swatches and popover row markers snap to device pixels, swatches rotate about their centre, line swatches in a bar chart are 14x6, wrapped legend rows start at the leading edge, and the overflow chevron sits in the menu icon slot;
- VerticalBar and VerticalStackedBar place date bars on upstream's un-niced time scale and stand on the height above the label reserve; the HorizontalBarChart benchmark triangle sits where upstream draws it;
- the funnel legend sits right under its
height-tall plot and each stage paints on its own, so the seam between categories shows; the heat map callout body is capped at upstream's 238px, and heat map and gantt readings take title2 as non-cartesian callouts; - a secondary y axis fills areas to zero; line segments honour
strokeDashoffset; colour fill bars top out at the data maximum; the sparkline stroke is clipped to its plot; a sankey node name collapses its white space; - a stack callout with two or more line rows no longer throws 'Duplicate keys found';
- a numeric
yAxisCalloutDatareads as a formatted number ('12345'is12,345) and an empty one falls back to the y value; a secondary y axis no series is on spans 0 to itsyMaxValueinstead of asserting on NaN; - a sankey node name cut short shows whole in the tooltip box beside the
pointer; a dimmed line series draws no white halo across the highlighted
one; HorizontalBarChartWithAxis dims its bars on legend hover and prints
readings as JS does (
5000, not5000.0); - hovering a large chart costs less: VerticalBarChart's hover is linear in the bar count, stacked bars read their hover targets back instead of re-solving them, and a move no longer rebuilds number formats or re-measures every tick label.
- an unknown
-
Components: the toast clips only while its height animates; a popover's surface is capped only in width, so a tall one no longer squeezes its content; the teaching popover dismiss is upstream's bare 12px glyph; a labelled list item announces its label once;
FluentSearchBoxkeeps its 468px cap inside a stretching parent such asFluentField; a hovered carousel step mark stays visible in high contrast and snaps its colours asCarouselNavButtondoes; a disabled tag showsnot-allowedacross the whole tag and its resting icon keeps the label colour;FluentSpinButtonshows no selection handles for a mouse.
Showroom (example app) #
- Storybook's canvas and docs toolbars as upstream renders them — grid,
backgrounds (
PreviewBand), per-element outlines (StoryOutlines), the vision simulator filters and width — and toggling any of them keeps the story's state; an RTL story sits at the canvas's start edge. - Stories wired to the new APIs: chart callouts, legends, locale, event
annotation colours and label width, the donut popover override, the
button stories' bundled, menu and checked icons, the tag stories' filled
glyphs, brand carousel nav, TeachingPopover's first-run carousel, and the
date picker's error messages from
result.message. - Chart export now downloads the image; inert knobs were dropped (gradient switches, dialog motion, the date range type, the disabled bottom drawer buttons) and switch labels no longer change with their value.
- Fixes to upstream's story details: colourful avatars use upstream's name hash, the tree actions are small, empty spin buttons start at 0, the title-custom-action dialog has the subtle close, the HBC annotation toggle shows the pointer, the card text is readable in dark theme, and copy typos are corrected.
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.flutter-io.cn screenshot and thumbnail.
- Require
fluent_2_coreandfluent_2_fonts_web0.0.2.
0.0.1 #
- Initial release.
