fluttersdk_wind 1.1.2
fluttersdk_wind: ^1.1.2 copied to clipboard
Tailwind CSS for Flutter. Write className='flex p-4 bg-white dark:bg-gray-800' and Wind renders optimized widget trees with dark mode and responsive breakpoints.
Changelog #
All notable changes to this project will be documented in this file.
This project follows Semantic Versioning 2.0.0.
Unreleased #
1.1.2 - 2026-06-25 #
Fixed #
WPopovernow opens reliably whentriggerBuilderreturns an interactive widget (aWButtonorWAnchorwith its ownonTap), and no longer dismisses itself on the same gesture that opened it. Two defects were in play. First, the trigger was wired through an outerGestureDetector(onTap: toggle); an interactive trigger owns its ownGestureDetector, won the gesture arena, and the outeronTapnever fired, so the popover never opened. The trigger now toggles through aListener(onPointerDown:), whose pointer events bypass the tap arena entirely, so opening works regardless of the trigger's interactivity (enableTriggerOnTapanddisabledsemantics are unchanged). Second, because the trigger and overlay share aTapRegiongroup id (intentionally, so a trigger re-tap does not self-close), the opening gesture's pointer-up reached the freshly mounted overlay'sonTapOutsideand dismissed the popover on the frame it opened. A one-shot, post-frame guard now swallows exactly that first outside-tap; a genuine, later outside tap still closes the popover. (lib/src/widgets/w_popover.dart; covered bytest/widgets/w_popover/gesture_regression_test.dart, the four-behavior regression set parameterized overWButtonandWDivtriggers.) Because the pointerListeneris invisible to assistive technologies, the trigger is wrapped inSemantics(button: true, onTap: ...)so screen readers and keyboard activation still reach the toggle, and the pointer toggle is filtered to the primary button so a secondary (right) click no longer opens the popover.
1.1.1 - 2026-06-23 #
Changed #
WInput: tap-to-focus andonTapdispatch now flow through Flutter's nativeTextSelectionGestureDetectorBuilder(the same gesture pathTextFielduses) instead of a hand-built whole-boxGestureDetector.onTapcontinues to fire when the user taps the field; the change is the gesture mechanism (which also brings drag-select and double-tap word selection), not a newonTapcontract.
Fixed #
WInputnow supports native text selection: mouse drag-select, double-tap word, and long-press all work again. The Material-free rewrite (#106) had dropped the selection gesture layer, so only keyboard select-all (CTRL+A) worked and dragging on web selected nothing. The field now uses the framework's canonical selectable-input recipe (the oneCupertinoTextFielduses):_WInputStateimplementsTextSelectionGestureDetectorBuilderDelegate, the whole decorated box is wrapped by aTextSelectionGestureDetectorBuilder(so a tap anywhere focuses and a drag over the glyphs selects), andEditableText.rendererIgnoresPointeristrueso the gesture layer is the only pointer handler. Selection handles stay Cupertino-style on all platforms (unchanged from 1.1.0), keepingWInputcupertino-only with nopackage:flutter/material.dartimport;cupertinoTextSelectionHandleControlsmixes inTextSelectionHandleControls, so the toolbar still flows through the Material-freecontextMenuBuilderand noOverlay-less long-press throws. Read-only fields stay selectable; disabled fields stay fully inert. (lib/src/widgets/w_input.dart; covered bytest/widgets/w_input/selection_test.dart.)WTextwith no color in its ownclassNamenow inherits an ancestorDefaultTextStylecolor (the CSS text-color cascade) before falling back to the platform-brightness baseline. A parentWDivwith atext-*class publishes its color throughDefaultTextStyle.merge, butWTextpreviously ignored it and forcedColors.white/Colors.blackfrom the OS platform brightness. That made colorless text vanish whenever the app theme disagreed with the OS theme: a secondary/outline button whose text color lives on the container (e.g. a dialog Cancel button) rendered an invisible label in a light app theme on a dark-mode OS. The brightness-aware baseline still applies only when no ancestor supplies a color (bare text with no Material ancestor), preserving the no-yellow-underline guarantee. (lib/src/widgets/w_text.dart; covered byWText baseline rendering > inherits an ancestor color (CSS cascade).)
1.1.0 - 2026-06-17 #
Added #
WindThemeData.aliases(Map<String, String>, empty by default): bare-token recursive className shortcuts expanded centrally inWindParser.parsebefore the 19-parser pipeline runs, so they work in every widget and inWDynamicwithout additional wiring. An alias that shadows a real token wins and emits a debug warning. Expansion is bounded three ways (per-chain cycle guard, depth cap, and a total-output-token budget) so a cyclic or fan-out alias map can never hang the parse. Resolves the#101class of silent unknown-token failures caused by shorthand tokens not being in the default token catalog. (#104)WIcon: inlineforegroundColorprop for runtime-dynamic icon colors. Overrides anytext-*/dark:text-*fromclassNameand stays out of the parser cache key, matchingWText.foregroundColor. (#103)WInput: debugAssertionErrorwhen bothvalueandcontrollerare supplied simultaneously; passing both was always a logic error and previously led to silent precedence behavior (controller wins). The assert surfaces the misuse immediately in debug builds (W2).WInput:readOnly: truenow activates areadonlystate, soreadonly:prefixed classes style a read-only field just likedisabled:does for a disabled one.
Changed #
WInput: selection handles are now Cupertino-style on all platforms (previously Material-style on Android/web). The context menu readsWidgetsLocalizationsso copy/cut/paste labels work under any ancestor, including bareWidgetsApp. This is a visual change on Android and web; behavior is identical. Under a custom root with noOverlayancestor (unusual;MaterialApp/CupertinoApp/WidgetsAppall provide one), typing, cursor movement, and focus still work, but the long-press selection toolbar and handles are suppressed instead of throwing.doc/layout/flexbox.mdand thewind-uiskill: layout-stability guidance added forIntrinsicHeight/IntrinsicWidthin animated subtrees; the safe alternative isStack+Positionedoritems-stretch(W3).WInput:InputType.numbernow restricts input to a signed decimal (digits, one decimal point, optional leading minus) on every platform via a default formatter, so it is numeric on web too, where the keyboard type alone enforces nothing. A caller-suppliedinputFormattersoverrides this default. The number keyboard is nownumberWithOptions(decimal: true, signed: true).
Fixed #
WInputnow renders Material-free (EditableTextcore,BoxDecorationborder/padding) and no longer crashes under a non-Material ancestor such as a bareWidgetsAppor Cupertino app. WrappingWInputin aMaterialAppis no longer required. (#102)WInputemits a single cleantextFieldsemantics node; the previous implementation produced a double-textbox node under the oldMergeSemantics > TextFieldwrapping (W1). Dusk snapshot consumers relying on a single textbox node perWInputcan expect clean output from this release (pairs with dusk D2).WFormInput,WFormSelect,WFormCheckbox, andWFormDatePicker: the default label, hint, and error class names now carrydark:pairs (text-gray-700 dark:text-gray-300,text-gray-500 dark:text-gray-400,text-red-500 dark:text-red-400), so labels and hints stay legible in dark mode instead of rendering dark-on-dark.WInput: a conditionalprefix/suffix(for example a clear button that appears once the field has text) no longer drops focus on the first keystroke, and an appearing suffix no longer grows the field height; the placeholder also shares the input strut so the box height stays constant between empty and filled.WInput:enabled: falseis now fully non-interactive again, the field cannot be tapped, focused, or expose selection handles/toolbar (the Material-free backend would otherwise still react to taps on the text).WInput: a disabled field again reportsisEnabled: falseto assistive technology through itsSemanticsnode. The Material-free rewrite had dropped the flag (theEditableTextnode carries onlyisReadOnly), so a screen reader could not tell a disabled field from a read-only one; the 1.0.0 MaterialTextFieldexposed it and parity is restored.
Quality #
- CI: pushing a version tag (
X.Y.Z) now auto-creates a GitHub Release from the matchingCHANGELOG.mdsection via.github/workflows/publish.yml, alongside the existing pub.dev publish step. (#105)
1.0.0 - 2026-06-09 #
First stable release. wind is utility-first, Tailwind-syntax styling for Flutter; v1.0.0 is a complete rewrite of the 0.0.x line with a fresh API surface, a 19-parser pipeline with cached resolution, dark-mode pairs as a first-class contract, and a contracts-based debug bridge for external tooling. All public APIs follow Semantic Versioning 2.0.0 from this point forward.
Migration from 0.0.x. v1 is not source-compatible with v0. Class names like WText, WDiv, WButton are preserved but constructor signatures, the supported className token set, and theme integration differ throughout. Consumers on 0.0.x rewrite their UI against the v1 API; the full v1 documentation lives at fluttersdk.com/wind.
Added #
- 22 public widgets exported from the single barrel
package:fluttersdk_wind/fluttersdk_wind.dart:- Layout:
WDiv,WSpacer - Structural:
WBreakpoint,WDynamic - Display:
WText,WIcon,WImage,WSvg - Interactive:
WAnchor,WButton - Overlay:
WPopover - Form (raw):
WInput,WSelect<T>,WCheckbox,WDatePicker - Form (FormField wrappers):
WFormInput,WFormSelect<T>,WFormMultiSelect<T>,WFormCheckbox,WFormDatePicker - Utility:
WKeyboardActions,WindAnimationWrapper
- Layout:
- 19 className parsers in a token-routing pipeline: background (color, gradient, image), border + radius, ring, shadow, opacity, padding, margin, sizing, flexbox + grid, position (relative + absolute + insets), order, overflow, aspect-ratio, z-index, text (size / weight / family / tracking / leading / decoration / transform / align / overflow), animation, transition (duration + easing), svg fill/stroke + preserve-colors, debug.
WindThemeDatawith 23 configurable fields:brightness,colors,screens,containers,fontSizes,fontWeights,tracking,leading,borderWidths,borderRadius,fontFamilies,ringWidths,ringOffsets,applyDefaultFontFamily,syncWithSystem,baseSpacingUnit,ringColor,opacities,zIndices,shadows,transitionDurations,transitionCurves,animations.WindThemeControllerexposestoggleTheme(),setTheme(),updateTheme(),resetToSystem(). OptionalonThemeChangedcallback fires only on user-initiated toggles, not system brightness syncs.- State system, three layers. Automatic (
hover:,focus:viaWAnchorpointer + focus listeners), framework-managed (loading:,disabled:,checked:,error:), consumer-passed (states: Set<String>?for any custom string likeselected:). All states funnel into a singleSet<String>on the parser cache key. - Responsive prefixes (
sm:,md:,lg:,xl:,2xl:, customizable viaWindThemeData.screens), dark mode (dark:), and platform prefixes (ios:,android:,macos:,web:,mobile:,windows:,linux:). Stackable freely. - CSS positioning utilities:
relative,absolute,top-*,right-*,bottom-*,left-*,inset-*,inset-x-*,inset-y-*, plus negative variants (-top-*,-inset-*) and arbitrary-px values (top-[24px]). - Child order utilities:
order-0throughorder-12,order-first,order-last,order-none, plus arbitraryorder-[n](including negatives) for reordering flex children without changing source order. - Reverse flex direction:
flex-row-reverseandflex-col-reverseflip the main-axis direction sojustify-startmirrors per CSS semantics. self-*align-self shorthand:self-start,self-end,self-center,self-stretch,self-auto,self-baselineas aliases for thealign-self-*long form, matching Tailwind's canonical class name.- Flex grow / basis tokens:
grow(Tailwind shorthand forflex-grow, i.e.flex: 1),grow-0(no grow), andbasis-1/2/basis-1/3/basis-1/4/basis-full/basis-[Npx]. Basis approximates CSSflex-basis: it sets the child's initial MAIN-axis size (width in a row, height in a column) and ignores grow/shrink interplay. The no-grow / no-shrink resets follow last-class-wins:grow-0cancels an earliergrow/flex-grow/flex-N,shrink-0cancels an earliershrink/flex-shrink, andflex-none(flex: 0 0 auto) cancels both, within the same active class list. - Smart column cross-axis stretch: a
flex flex-colwith no explicititems-*token now stretches eachWDiv,WAnchor(any child), andWButtonchild that does not control its own width to the column width (CSSalign-items: stretchdefault), so a clickable nav row (WAnchor(onTap) > WDiv) fills the column width without an explicititems-stretchorw-full. For aWAnchor: when it wraps aWDiv, the innerWDiv's className decides; when it wraps aWTextor raw widget, it always stretches. Children that self-wrap inExpanded/Flexible(grow,flex-grow,flex-auto,flex-initial,shrink,flex-shrink,flex-N, in any state/breakpoint variant), children with an explicitw-*/min-w-*/max-w-*(includingw-full), absolute children, bareWTextleaves, and raw Flutter widgets are left untouched.shrink-0/flex-nonechildren still stretch on the cross axis (their no-shrink effect is main-axis only, matching CSS). Rows are unaffected. - Inline color props:
WDiv.backgroundColorandWText.foregroundColorfor runtime-dynamic colors. Override anybg-*/text-*from className and stay out of the parser cache key. WBreakpointwidget: declarative per-breakpoint widget tree builder. Reach for it when className prefixes are not enough because the widget structure itself changes between breakpoints.WDynamic: JSON-driven widget tree renderer. 13 Wind types + 16 Flutter core types allowed by default, extensible viabuilders:, restrictable viadenyWidgets:, withcustomIcons:for user-defined icon mappings (24 built-in glyphs). State binding by widgetid, action dispatch viaWActionHandler, max recursion depth 50.- Accessibility / Semantics on 7 interactive widgets (
WAnchor,WButton,WInput,WFormInput,WCheckbox,WSelect,WDatePicker): emitSemanticsnodes with role + label, password fields markobscured. Enables PlaywrightgetByRole/getByLabel/getByTextresolution against the Flutter web build. New optionalsemanticLabelparameters onWInput,WButton, andWAnchor; the button/anchor label gives icon-only controls an accessible name when there is no child text forMergeSemanticsto absorb. Wind.installDebugResolver(): call insidekDebugModeto register aWindDebugResolverImplagainst the newfluttersdk_wind_diagnostics_contractsbridge. Resolves 7 fields per Wind widget element:className,breakpoint,brightness,platform,states, conditionalbgColor, conditionaltextColor. Consumed byfluttersdk_duskfor E2E snapshot capture and by any runtime inspector. Tree-shaken in release builds.wind-uiskill v2.0.2 community pattern:skills/wind-ui/SKILL.mdsection 15 plusskills/wind-ui/references/community.mdadd opt-in star + issue-report CTAs surfaced once per session after a verified Wind task or a genuine wind-side bug. Prose-permission only, never auto-executed,gh auth status-gated. (#89)
Changed #
- BREAKING. Complete API rewrite from 0.0.x. The legacy
lib/src/parsers/(28 modules) andlib/src/components/(8 modules) directories were deleted and reimplemented underlib/src/parser/parsers/andlib/src/widgets/. Class namesWText,WDiv,WButtonare preserved but their constructor signatures, the className token set they accept, and theme integration differ. Not source-compatible with v0. - BREAKING. Flutter SDK minimum raised from
>=3.3.0to>=3.27.0. Dart SDK constraint set to>=3.4.0 <4.0.0. - BREAKING. Parser cache is now always on. The opt-in
trackProvenanceflag onWindParser.parse()and theWindStyle.resolvedViafield are gone; debug-tooling consumers read widget state throughWind.installDebugResolver()and thefluttersdk_wind_diagnostics_contractscontract package instead. - BREAKING. The public
DatePickerModeenum is renamed toWDatePickerMode. The old name collided with Flutter Material's ownDatePickerMode, forcing any consumer importing bothpackage:flutter/material.dartand the wind barrel tohideone symbol.WDatePicker/WFormDatePickermode:now takesWDatePickerMode.
Removed #
- BREAKING.
WindDuskIntegrationclass and thelib/dusk_integration.dartsub-barrel. Replaced byWind.installDebugResolver()from the main barrel. - BREAKING.
fluttersdk_duskas a wind dependency at any level. Consumers needing Dusk for their own E2E add it to their own pubspec. - BREAKING.
google_fontsandplatform_infodependencies. Consumers depending on them transitively must add explicit deps. - BREAKING.
WindParser.parse(trackProvenance:)parameter,WindStyle.resolvedViafield, and theenableProvenance()toggle. The contracts-based diagnostic bridge does not require provenance instrumentation. - BREAKING. 13 internal parser classes (
AspectRatioParser,BackgroundParser,BorderParser,FlexboxGridParser,MarginParser,OpacityParser,OverflowParser,PaddingParser,RingParser,SizingParser,TextParser,TransitionParser,ZIndexParser),WindPlatformService,WindLogger,LogEntry,WDynamicRenderer, andWindDebugResolverImplare no longer exported from the public barrel (package:fluttersdk_wind/fluttersdk_wind.dart). These were always internal implementation details; any consumer referencing them by name must remove those references. The public widget and theme API is unaffected.
Fixed #
WTextbare rendering: aWTextused outside aMaterialApp/Scaffoldnow renders with a brightness-aware baseline color (Colors.whiteon dark platforms,Colors.blackon light, read fromMediaQuery.platformBrightness) instead of Flutter's debug yellow-underline fallback. When noDirectionalityancestor exists,WTextinjects one defaulting toTextDirection.ltr. Explicitly supplied colors (className text-*,foregroundColor,textStyle) still win and are unaffected.- Background image parser (
bg-[/abs/path]): theFileImage(File(...))branch is now guarded bykIsWeb; on web, wheredart:ioFileis unsupported, the image degrades gracefully (skipped) instead of throwing at runtime. Non-web behavior is unchanged.pubspec.yamlnow declares explicit platform support (android,ios,macos,web,linux,windows) so pub.dev platform detection is not narrowed by thedart:ioimport graph. - WASM compatibility: removed
dart:iofrom the library import graph (platform_service.dartnow usesdefaultTargetPlatform; absolute-pathbg-[/...]image resolution moved behind a conditional import). The package is nowis:wasm-ready, raising the pana/pub.dev platform-support score to 20/20 (160/160). (#95) max-w-prose: corrected value from 1040 px (65 × 16, an incorrect approximation) to 512 px, matching the actual parser output. Docs and skill references updated accordingly.WButton/WAnchorsemanticLabel: theSemanticsnode now setsexcludeSemantics: trueand liftsonTap/onLongPressonto itself whensemanticLabelis set, so the label overrides any child text instead of concatenating with it underMergeSemantics, while activation is preserved.Wind.installDebugResolver(): the resolver no longer crashes on a className-less W-widget.WindDebugResolverImpl.resolveguarded its dynamicclassNameread, so a bareWAnchor(orWBreakpoint/WindAnimationWrapper/WKeyboardActions) in the tree no longer throwsNoSuchMethodErrorand abort the entirefluttersdk_dusk/ telescope diagnostic snapshot.WInput:px-*horizontal padding now matches the requested value exactly;OutlineInputBorder.gapPaddingis set to0.0sopx-3produces a 12 px inset instead of 16 px. Multiline geometry unchanged. (#61)WindParser.findAndGroupClasses: duplicate tokens flow through to the parser pipeline so the documented last-class-wins contract holds on repeated overrides liketop-8 top-4 top-8; previously.toSet()dropped the trailing occurrence.WindParser.parse: cache is bypassed in both directions (no read, no write) whenbaseStyleis non-null so per-call styles do not return stale cached entries or poison the cache slot for default-flag callers.example/lib/routes.dart: six widget routes (/widgets/w-input-multiline,/widgets/w-input-search,/widgets/w-popover-alignment,/widgets/w-select_multi,/widgets/w-select_single,/widgets/w-text-transform) renamed to snake_case to match their page-file basenames so live doc iframes atwind.fluttersdk.com/preview/widgets/<key>resolve. Two dead pages (layout/grid_basic,layout/order) without documentation references were removed.BackgroundParser:bg-[#hex]arbitrary-color backgrounds no longer also resolve to a bogusAssetImage("assets/#hex"). The image regex now excludes#-leading bracket values so a hex literal is parsed only as a color, eliminating a stray failed asset fetch on every arbitrary-hex background.flex-none: now means CSSflex: 0 0 auto(no grow AND no shrink). It no longer maps to a shrinkingFlexFit.loose; instead it routes through the same no-shrink path asshrink-0, so aflex-nonechild in ajustify-betweenrow keeps its intrinsic size instead of being forced into aFlexibleshrink allocation.WindStyle.copyWith: a padding-, margin-, or text-only style keepsdecoration == nullinstead of fabricating an emptyBoxDecoration. This stopsWDiv/WTextfrom wrapping a needlessContaineraround non-decorated content.shadow-nonelikewise no longer forces a Container.WDynamicRenderer: malformed JSON degrades gracefully. A non-stringtypeor non-listchildrenis coerced defensively (routed through the whitelist / treated as no children) instead of throwing an implicit-downcastTypeErrorout ofbuild().WindThemeData: implements value-basedoperator ==andhashCode. The equality guards inWindThemeController.setThemeand_WindThemeState.didUpdateWidgetnow compare by value, so a fresh defaultWindThemeData()on a parent rebuild no longer clobbers a priortoggleTheme()choice or triggers spurious full-tree rebuilds.
Quality #
- 1,224 tests across 83 test files; line coverage 90.3% (CI gate enforces
>= 90%via./tool/coverage.sh 90). - New regression coverage in
test/parser/wind_parser_cache_test.dartfor the last-class-wins-on-duplicates andbaseStyle-bypasses-cache contracts. - New regression coverage for the arbitrary-hex background (
background_parser_test.dart),decoration-stays-null contract (wind_style_test.dart), malformed-JSON graceful degradation (w_dynamic_renderer_test.dart),WindThemeDatavalue equality (wind_theme_data_test.dart), and icon-only button Semantics label (w_button_test.dart). tool/coverage.shportable threshold-aware lcov wrapper; GitHub Actions gate fails any PR dropping below 90%.- Surgical
// coverage:ignore-linepragmas only on lines structurally unreachable fromflutter test(kDebugModebranches,dart:ioPlatform.is*branches not matching the CI host). Each pragma carries a one-line WHY comment. - Final 1.0.0 QA gate: added permanent regression suites under
test/pixel/(px-exact geometry + color viagetRect/getSize/renderObject, no golden files),test/interaction/(tap/hover/focus/disabled/responsive/animation/overlay in-process), andtest/performance/(parser cache hit/miss speedup ratio gate, ~26x, plus report-only large-tree pump timing). Validated live via a fresh consumer app driven byfluttersdk_dusk(all routes navigated,wind:enricher 7-field block confirmed, screenshots manually compared). No behavioral regressions found.
Production deps: flutter (SDK), flutter_svg ^2.0.0, fluttersdk_wind_diagnostics_contracts ^1.0.0. Dev deps: flutter_test (SDK), flutter_lints ^5.0.0. Full v1 documentation at fluttersdk.com/wind; LLM-facing skill at skills/wind-ui/ distributed via fluttersdk/ai (npx skills add fluttersdk/ai --skill wind-ui).
Previous versions #
The 1.0.0-alpha.1 through 1.0.0-alpha.10 release notes (Feb 2026 to May 2026) are preserved in git history and on the v0 branch. The 0.0.x line is end-of-life; consumers pin to ^1.0.0 going forward.
