From 7410aabafe0926ad51a40ddad2f97350822e89a6 Mon Sep 17 00:00:00 2001 From: Anilcan Cakir Date: Wed, 8 Jul 2026 00:27:59 +0300 Subject: [PATCH 1/3] docs: sync references and sweep em/en-dashes for the 1.2.0 surface Bring every documentation surface in line with the post-1.1.2 changes and the project style rule: - llms.txt: 22 -> 27 widgets, add WBadge/WCard/WSwitch/WRadio/WTabs, the cursor-* / size-* families, and WindRecipe/WindSlotRecipe; correct the unknown-token behavior (debug hint, not silent). - skills/wind-ui: add a Composed-widgets section to references/widgets.md, fix the parser count (19 -> 20) and the cursor-* / flex-wrap support state in tokens.md and community.md. - doc/core-concepts/debugging.md: document the unknown-className debug hint. - CHANGELOG: attribute the #120 design-system widgets and record the sync under Quality. - Remove every em-dash and en-dash from doc/, skills/, lib/ comments, and example/ (empty-value UI glyphs preserved); the WindDynamic unknown-action debug message now uses the comma form. --- CHANGELOG.md | 17 ++- doc/core-concepts/debugging.md | 17 +++ doc/core-concepts/dynamic-rendering.md | 2 +- doc/core-concepts/theming.md | 4 +- doc/core-concepts/utility-first.md | 2 +- doc/interactivity/cursor.md | 4 +- doc/layout/flexbox.md | 10 +- doc/layout/positioning.md | 18 +-- doc/widgets/w-breakpoint.md | 4 +- doc/widgets/w-date-picker.md | 8 +- doc/widgets/w-form-date-picker.md | 4 +- doc/widgets/w-svg.md | 6 +- doc/widgets/w-switch.md | 2 +- example/lib/pages/buttons/button_basic.dart | 2 +- example/lib/pages/effects/opacity.dart | 2 +- example/lib/pages/forms/input_basic.dart | 8 +- example/lib/pages/forms/select_basic.dart | 2 +- .../pages/interactivity/animation_basic.dart | 2 +- .../lib/pages/interactivity/cursor_basic.dart | 2 +- .../pages/interactivity/transition_basic.dart | 2 +- example/lib/pages/layout/display.dart | 4 +- example/lib/pages/layout/flex_grow.dart | 2 +- example/lib/pages/layout/grid_cols.dart | 2 +- example/lib/pages/layout/sizing_height.dart | 2 +- example/lib/pages/popover/popover_basic.dart | 2 +- example/lib/pages/typography/font_size.dart | 8 +- example/lib/pages/typography/line_height.dart | 2 +- .../typography/text_overflow_truncate.dart | 2 +- .../pages/utilities/color_helpers_basic.dart | 8 +- .../utilities/context_extensions_basic.dart | 6 +- .../utilities/spacing_helpers_basic.dart | 2 +- .../pages/utilities/style_parser_basic.dart | 2 +- .../utilities/typography_helpers_basic.dart | 4 +- example/lib/pages/widgets/w_anchor_flex.dart | 2 +- example/lib/pages/widgets/w_div_basic.dart | 2 +- .../pages/widgets/w_form_input_layout.dart | 2 +- example/lib/pages/widgets/w_spacer_basic.dart | 2 +- .../pages/widgets/w_spacer_responsive.dart | 4 +- example/lib/pages/widgets/w_text.dart | 2 +- lib/src/dynamic/w_action_handler.dart | 4 +- lib/src/parser/parsers/order_parser.dart | 14 +-- lib/src/parser/parsers/position_parser.dart | 4 +- lib/src/parser/parsers/svg_parser.dart | 4 +- lib/src/parser/wind_style.dart | 2 +- lib/src/theme/wind_theme.dart | 2 +- lib/src/widgets/w_anchor.dart | 2 +- lib/src/widgets/w_breakpoint.dart | 8 +- lib/src/widgets/w_button.dart | 6 +- lib/src/widgets/w_card.dart | 8 +- lib/src/widgets/w_div.dart | 20 +-- lib/src/widgets/w_keyboard_actions.dart | 10 +- lib/src/widgets/w_svg.dart | 2 +- lib/src/widgets/w_tabs.dart | 6 +- lib/src/widgets/w_text.dart | 2 +- llms.txt | 15 ++- skills/wind-ui/SKILL.md | 4 +- skills/wind-ui/references/community.md | 2 +- skills/wind-ui/references/debug.md | 8 +- skills/wind-ui/references/dynamic.md | 18 +-- skills/wind-ui/references/forms.md | 4 +- skills/wind-ui/references/layouts.md | 12 +- .../wind-ui/references/tailwind-divergence.md | 50 ++++---- skills/wind-ui/references/theme.md | 4 +- skills/wind-ui/references/tokens.md | 39 +++--- skills/wind-ui/references/widgets.md | 115 ++++++++++++++++-- 65 files changed, 332 insertions(+), 211 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecd93825..cec9b0ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,18 +26,18 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0. - `cursor-*` utilities: a new `CursorParser` maps Tailwind cursor names (`cursor-pointer`, `cursor-not-allowed`, `cursor-text`, `cursor-grab`/`cursor-grabbing`, `cursor-zoom-in`/`cursor-zoom-out`, the full resize set, etc.) to the matching `SystemMouseCursors`. `WDiv` applies the resolved cursor through a `MouseRegion`, so a plain container can feel clickable on web/desktop without `WAnchor` or a manual `MouseRegion`; on a `hover:`/`focus:`/`active:` `WDiv` the cursor `MouseRegion` sits below the auto-wrapped `WAnchor` and wins. Inert on touch platforms; last token wins; unknown names no-op. (`lib/src/parser/parsers/cursor_parser.dart`, `lib/src/parser/wind_style.dart`, `lib/src/widgets/w_div.dart`) (#125) - `size-*` sizing utility (Tailwind v3.4+ shorthand): `size-2`, `size-full`, `size-1/2`, `size-[20px]`, `size-[50%]`, `size-screen` set BOTH width and height in one token. Fixes childless `WDiv` sizing: a `WDiv(className: 'size-2 rounded-full bg-...')` status dot now renders its box instead of collapsing (the previous gap was that `size-*` was unrecognized, not that `w-*`/`h-*` were ignored, which already worked childless). A later `w-*`/`h-*` overrides the matching axis. (`lib/src/parser/parsers/sizing_parser.dart`) (#123) - `grid` equal-height rows via `items-stretch`. By default a Wind `grid` renders as a `Wrap` and sizes each cell to its own content, leaving a row ragged when one card is taller. Adding `items-stretch` (CSS Grid's default `align-items: stretch`) now builds the grid as a column of `IntrinsicHeight` rows so every cell in a row matches the tallest, and cells divide the row width evenly. Cells should size from their own content; `h-full` on a stretched cell is unsupported (it inserts a `LayoutBuilder` that asserts under `IntrinsicHeight`, see the intrinsic-sizing limitation docs). (`lib/src/widgets/w_div.dart`) (#126) -- `WindRecipe` and `WindSlotRecipe` (+ `WindCompoundVariant`, `WindSlotCompoundVariant`): a `tv()` (tailwind-variants) equivalent. `WindRecipe` resolves a single className from `base` + variant axes + compoundVariants + a caller `className`, in strict emission order (`base ++ variant ++ compound ++ caller`), with no dedupe/sort/twMerge. `WindSlotRecipe` extends the same model to multi-slot components and returns a `Map` (slot -> className). (`lib/src/recipe/wind_recipe.dart`) -- `WBadge`: inline status/label pill. Composes a rounded-full `WDiv` around a `WText(text-xs)`; all visual tone is className-driven (`bg-*`, `text-*`, `dark:` pairs). (`lib/src/widgets/w_badge.dart`) -- `WCard`: surface container with optional `header` and `footer` slots. Delegates to `WDiv` (flex-col); all styling is className-driven. (`lib/src/widgets/w_card.dart`) -- `WSwitch`: controlled toggle switch. Pushes the `checked:` state prefix when `value` is `true`. The thumb is a flex child of the track, so the caller positions it with `justify-start` -> `checked:justify-end` on the track `className` (the track stays a flex Row; `WSwitch` does not inject `relative`, which would force a single-child Stack and defeat `justify-*`). `thumbClassName` supplies thumb shape/color only; `translate-x-*` is intentionally unsupported (Wind has no transform parser). Wraps `WAnchor` for hover/focus/disabled state propagation. (`lib/src/widgets/w_switch.dart`) -- `WRadio`: controlled radio button. Drives the `selected:` state prefix when `value == groupValue`. Group behavior (mutual exclusivity) is the caller's responsibility. (`lib/src/widgets/w_radio.dart`) +- `WindRecipe` and `WindSlotRecipe` (+ `WindCompoundVariant`, `WindSlotCompoundVariant`): a `tv()` (tailwind-variants) equivalent. `WindRecipe` resolves a single className from `base` + variant axes + compoundVariants + a caller `className`, in strict emission order (`base ++ variant ++ compound ++ caller`), with no dedupe/sort/twMerge. `WindSlotRecipe` extends the same model to multi-slot components and returns a `Map` (slot -> className). (`lib/src/recipe/wind_recipe.dart`) (#120) +- `WBadge`: inline status/label pill. Composes a rounded-full `WDiv` around a `WText(text-xs)`; all visual tone is className-driven (`bg-*`, `text-*`, `dark:` pairs). (`lib/src/widgets/w_badge.dart`) (#120) +- `WCard`: surface container with optional `header` and `footer` slots. Delegates to `WDiv` (flex-col); all styling is className-driven. (`lib/src/widgets/w_card.dart`) (#120) +- `WSwitch`: controlled toggle switch. Pushes the `checked:` state prefix when `value` is `true`. The thumb is a flex child of the track, so the caller positions it with `justify-start` -> `checked:justify-end` on the track `className` (the track stays a flex Row; `WSwitch` does not inject `relative`, which would force a single-child Stack and defeat `justify-*`). `thumbClassName` supplies thumb shape/color only; `translate-x-*` is intentionally unsupported (Wind has no transform parser). Wraps `WAnchor` for hover/focus/disabled state propagation. (`lib/src/widgets/w_switch.dart`) (#120) +- `WRadio`: controlled radio button. Drives the `selected:` state prefix when `value == groupValue`. Group behavior (mutual exclusivity) is the caller's responsibility. (`lib/src/widgets/w_radio.dart`) (#120) - `WTabs`: controlled tabs widget. The `selected:` state prefix activates on the active tab; `listClassName`, `tabClassName`, `selectedTabClassName`, and `panelClassName` cover every structural region. `panelBuilder` receives the selected index each rebuild. The tab list defaults to full container width (`fullWidthList: true`) so a `border-b` underline spans the container rather than only the tabs; set `fullWidthList: false` for a content-width / pill tab strip. (#128) (`lib/src/widgets/w_tabs.dart`) ### Fixed - `WPopover` overlay no longer overflows the right edge of the viewport when `autoFlip` is on. A wide panel next to a near-centered trigger on a narrow screen flipped to the opposite side but still spilled off-screen; a `computeHorizontalClamp` helper now folds a corrective `dx` into the follower offset so the panel is pulled fully inside the viewport (seated `8px` from the edge, or flush when it is too wide to honor both margins). The clamp engages only on genuine off-screen overflow, so an edge-anchored popover already fully visible is left untouched. (`lib/src/widgets/w_popover.dart`) -- `grid ... items-stretch` no longer emits a residual `RenderFlex overflowed by ~2px on the bottom` when it stretches an unequal cell (follow-up to #139). `WindEqualHeightRow` previously re-laid each cell to a TIGHT height equal to the loose-measured row max; a cell whose `flex flex-col` content needs a hair more under the tight re-lay (sub-pixel text/flex rounding on real rendering, e.g. CanvasKit) overflowed by a couple of pixels. It now stretches with a MIN height instead (never a tight squeeze), so a cell is never forced below its own content and the row takes the tallest resulting height — overflow-free by construction. (`lib/src/widgets/wind_equal_height_row.dart`) (#141) +- `grid ... items-stretch` no longer emits a residual `RenderFlex overflowed by ~2px on the bottom` when it stretches an unequal cell (follow-up to #139). `WindEqualHeightRow` previously re-laid each cell to a TIGHT height equal to the loose-measured row max; a cell whose `flex flex-col` content needs a hair more under the tight re-lay (sub-pixel text/flex rounding on real rendering, e.g. CanvasKit) overflowed by a couple of pixels. It now stretches with a MIN height instead (never a tight squeeze), so a cell is never forced below its own content and the row takes the tallest resulting height, overflow-free by construction. (`lib/src/widgets/wind_equal_height_row.dart`) (#141) - `grid ... items-stretch` no longer asserts `RenderBox was not laid out` (rooted at `RenderIntrinsicHeight` / `LayoutBuilder does not support returning intrinsic dimensions`) when it stretches an unequal `flex flex-col` cell. The equal-height mechanism from #126 used `IntrinsicHeight`, which queries child intrinsics; a `flex flex-col` cell (or one using `h-full` / `basis-*`) carries a `LayoutBuilder` that cannot answer intrinsic queries, so stretching a shorter cell crashed the exact case the feature exists for. Replaced it with `WindEqualHeightRow`, a `RenderObject` that measures each cell with a real loose-height layout and re-lays it tight to the row max, so LayoutBuilder-bearing cells stretch correctly. As a bonus, `h-full` on a stretched cell now resolves too. (`lib/src/widgets/wind_equal_height_row.dart`, `lib/src/widgets/w_div.dart`) (#139) @@ -46,6 +46,11 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0. - `w-full` on a direct child of a `flex flex-row` no longer aborts layout with "RenderBox was not laid out". A Row hands non-flex children an unbounded main-axis constraint, so a `w-full` child's `SizedBox(width: infinity)` could not resolve. Wind now treats a bare `w-full` Row child as `flex-1`: the Row wraps it in `Expanded` (and defaults to `MainAxisSize.max`), giving the child a bounded width share inside which its infinite-width box resolves, so it fills the row. `flex-1` remains the idiomatic choice; a prefixed `md:w-full` is intentionally not auto-expanded (use `md:flex-1`). Nested (non-direct) `w-full` children are unaffected. (`lib/src/widgets/w_div.dart`) (#122) - `WSelect`: the dropdown no longer dismisses itself on the same click that opens it on web (verified with real browser mouse events via CDP). Two independent self-close paths fired on the opening click: (1) the overlay's `TapRegion.onTapOutside` received the opening tap's own pointer-up because `OverlayPortal` mounts synchronously on the frame it opens, and (2) `_onFocusChange` closed the menu when the trigger lost focus, which web does transiently (~150ms) right after the opening click. Fixes: the overlay mount is now deferred one frame (`addPostFrameCallback` -> `OverlayPortal.show()`) so the opening pointer-up is fully dispatched before the overlay's `TapRegion` exists; and the fragile focus-loss auto-close was removed (the tap-only trigger cannot be opened by keyboard, so it added no real dismissal path while firing spuriously on web). Dismissal is now driven solely by an outside tap (`onTapOutside`) and option selection; the trigger and open menu still share a `TapRegion` group id so a trigger re-tap toggles closed instead of self-closing. Custom `triggerBuilder` / `multiTriggerBuilder` triggers are wrapped in that same shared group too, so a custom trigger no longer reads as an outside tap and self-closes the menu. (`lib/src/widgets/w_select.dart`) +### Quality + +- Documentation, the `wind-ui` skill references, and `llms.txt` synced to the 1.2.0 surface: the five new widgets gained sections in `skills/wind-ui/references/widgets.md`, the unknown-className debug hint is now documented in `doc/core-concepts/debugging.md`, the parser count (20) and the `cursor-*` / `flex-wrap` support state were corrected across the skill, and `llms.txt` now lists every new widget, the `cursor-*` / `size-*` families, and `WindRecipe`. +- Removed every em-dash and en-dash from `doc/`, `skills/`, `lib/` comments, and the `example/` gallery to honor the project style rule; the `WindDynamic` unknown-action debug message now reads `..., ignored.` (comma form) to match. + ## [1.1.2] - 2026-06-25 ### Fixed diff --git a/doc/core-concepts/debugging.md b/doc/core-concepts/debugging.md index 38b6aa70..91fef860 100644 --- a/doc/core-concepts/debugging.md +++ b/doc/core-concepts/debugging.md @@ -9,6 +9,7 @@ - [How WindLogger Works](#how-windlogger-works) - [Quick Reference](#quick-reference) - [Common Scenarios](#common-scenarios) +- [Unknown className Warnings](#unknown-classname-warnings) - [External Tooling Integration](#external-tooling-integration) @@ -122,6 +123,22 @@ Let's look at common debugging workflows using the `debug` class: - **Verifying State Variants**: Hover over or focus a widget with state-based classes (like `hover:bg-blue-600`). The log will update to show the merged style. - **Analyzing Layout Layers**: Use the `Composition Tree` to determine which utility class is responsible for a specific Flutter wrapper, helping identify unexpected padding or alignment issues. + +## Unknown className Warnings + +Wind drops any token no parser recognizes: an unknown token never throws and never renders. That keeps a typo from crashing the UI, but a silent drop also hides the typo. To surface it, the parser prints a one-time hint in `kDebugMode` whenever it meets a className it cannot resolve: + +```text +Wind: unknown className 'flex-col-reverse' was ignored. +``` + +The hint fires once per unique token per session (deduped like the alias shadow and cycle warnings), so a token repeated across many widgets logs a single line. Release builds print nothing: the check sits behind `kDebugMode` and is tree-shaken out. + +> [!NOTE] +> The warning targets genuine typos. Valid Wind tokens that a widget consumes outside the parser map are exempt and never warn: the `object-*` fit family (read by `WImage`), the `inline-flex` / `inline-block` / `inline` display keywords (emitted by `WBadge`, inert in Flutter), and deliberately inert compatibility tokens (`transition` / `transition-colors`, `antialiased`, `sr-only`, and the `*-nums` font-variant family such as `tabular-nums`). + +If a token you expect to work triggers the warning, check the [Token catalog](https://fluttersdk.com/wind/layout/index.md) for the canonical spelling. A common case is a CSS name that Wind aliases: `flex-wrap` prints the hint and points you at the canonical `wrap`. + ## External Tooling Integration diff --git a/doc/core-concepts/dynamic-rendering.md b/doc/core-concepts/dynamic-rendering.md index cbf26a60..9944f525 100644 --- a/doc/core-concepts/dynamic-rendering.md +++ b/doc/core-concepts/dynamic-rendering.md @@ -411,7 +411,7 @@ WDynamic( ) ``` -This is a lightweight alternative to custom builders when you only need to extend the icon library. See [WDynamic — Custom Icons](../widgets/w-dynamic.md#custom-icons) for complete configuration details. +This is a lightweight alternative to custom builders when you only need to extend the icon library. See [WDynamic: Custom Icons](../widgets/w-dynamic.md#custom-icons) for complete configuration details. ## Error Handling diff --git a/doc/core-concepts/theming.md b/doc/core-concepts/theming.md index a9dab001..6ede5408 100644 --- a/doc/core-concepts/theming.md +++ b/doc/core-concepts/theming.md @@ -19,7 +19,7 @@ Wind is designed to be fully customizable from the ground up. If you've ever used Tailwind CSS, you'll feel right at home with how we handle "design tokens." Instead of hunting through nested widget properties to change a color or a font size, you define your design system once in your theme configuration. -Everything in Wind—from the colors in `bg-blue-500` to the spacing in `p-4`—is driven by `WindThemeData`. This ensures that your UI remains consistent across the entire application while giving you the freedom to break away from defaults whenever you need to. +Everything in Wind (from the colors in `bg-blue-500` to the spacing in `p-4`) is driven by `WindThemeData`. This ensures that your UI remains consistent across the entire application while giving you the freedom to break away from defaults whenever you need to. ## The WindTheme Widget @@ -54,7 +54,7 @@ void main() { ## Theme Change Callbacks -When a user manually toggles the theme via `toggleTheme()`, you may want to persist their preference. The `onThemeChanged` callback fires only on user-initiated theme changes—it does **not** fire when the system brightness changes automatically. +When a user manually toggles the theme via `toggleTheme()`, you may want to persist their preference. The `onThemeChanged` callback fires only on user-initiated theme changes; it does **not** fire when the system brightness changes automatically. ```dart WindTheme( diff --git a/doc/core-concepts/utility-first.md b/doc/core-concepts/utility-first.md index 752366d1..f6a3d8f7 100644 --- a/doc/core-concepts/utility-first.md +++ b/doc/core-concepts/utility-first.md @@ -94,7 +94,7 @@ Wind uses a high-performance pipeline to transform strings into native Flutter s 1. **Context Initialization**: `WindContext` captures the current environment, including screen size (breakpoints), brightness, and theme scales. 2. **Parsing**: The `WindParser` tokenizes the `className` string and delegates to specialized parsers for colors, spacing, borders, and more. -3. **Style Composition**: Parsers generate a `WindStyle` object—an immutable, typed representation of the requested styles. +3. **Style Composition**: Parsers generate a `WindStyle` object, an immutable, typed representation of the requested styles. 4. **Widget Application**: `W-prefixed` widgets consume this `WindStyle` to dynamically build the optimal Flutter widget hierarchy (e.g., injecting `Padding` or `DecoratedBox` only when needed). > [!NOTE] diff --git a/doc/interactivity/cursor.md b/doc/interactivity/cursor.md index a762f64e..7190cb46 100644 --- a/doc/interactivity/cursor.md +++ b/doc/interactivity/cursor.md @@ -88,5 +88,5 @@ WDiv(className: 'cursor-pointer bg-white dark:bg-gray-800') ## Related Documentation -- [WAnchor](../widgets/w-anchor.md) — exposes a `mouseCursor` constructor prop for fully imperative control. -- [Transitions](./transition.md) — smooth state changes on hover. +- [WAnchor](../widgets/w-anchor.md): exposes a `mouseCursor` constructor prop for fully imperative control. +- [Transitions](./transition.md): smooth state changes on hover. diff --git a/doc/layout/flexbox.md b/doc/layout/flexbox.md index 17c588dd..88faa0d0 100644 --- a/doc/layout/flexbox.md +++ b/doc/layout/flexbox.md @@ -67,7 +67,7 @@ WDiv( | `items-{alignment}` | `align-items: ...` | `CrossAxisAlignment` | | `gap-{n}` | `gap: {n}` | `SizedBox` (spacer) | | `flex-1` | `flex: 1` | `Expanded()` | -| `shrink-0` | `flex-shrink: 0` | No wrapper — preserves intrinsic size | +| `shrink-0` | `flex-shrink: 0` | No wrapper: preserves intrinsic size | ## Flex Direction @@ -132,7 +132,7 @@ WDiv( // Visual order: B, C, A ``` -Responsive reordering is a common use case — swap the layout per breakpoint without duplicating children: +Responsive reordering is a common use case: swap the layout per breakpoint without duplicating children: ```dart WDiv( @@ -281,10 +281,10 @@ Control how individual children resize to fill available space. |:------|:------------| | `flex-1` | Allow child to grow and fill available space (`Expanded`). | | `flex-grow` / `grow` | Alias for `flex-1` (Tailwind v3 and v4 names). | -| `grow-0` | Do not grow — child keeps its intrinsic main-axis size. | +| `grow-0` | Do not grow: child keeps its intrinsic main-axis size. | | `flex-{n}` | Specific flex factor (e.g., `flex-2`). | | `shrink` | Allow child to shrink if needed (`FlexFit.loose`). | -| `shrink-0` | Preserve intrinsic size — no Flexible wrapper, child keeps its natural dimensions. | +| `shrink-0` | Preserve intrinsic size: no Flexible wrapper, child keeps its natural dimensions. | | `flex-none` | CSS `flex: 0 0 auto`: do not grow AND do not shrink. Keeps intrinsic size (same no-shrink path as `shrink-0`). | | `basis-1/2` / `basis-1/3` / `basis-1/4` / `basis-full` | Fractional flex-basis: initial main-axis size (width in a row, height in a column). Approximates CSS `flex-basis`; ignores grow/shrink interplay. | | `basis-[Npx]` | Fixed flex-basis: a fixed main-axis size in logical pixels (e.g., `basis-[120px]`). | @@ -371,7 +371,7 @@ WindThemeData( ## Flex Children Inside a Scrollable Axis -When a flex container's own main axis is scrollable (`flex-row` with `overflow-x-auto|scroll`, or `flex-col` with `overflow-y-auto|scroll`), Flutter's `Expanded`/`Flexible` cannot resolve — the incoming constraint is unbounded. Wind detects this at resolution time and skips the `Expanded`/`Flexible` wrap for direct flex children in that pass, so `flex-N` becomes a no-op while the axis is scrollable. +When a flex container's own main axis is scrollable (`flex-row` with `overflow-x-auto|scroll`, or `flex-col` with `overflow-y-auto|scroll`), Flutter's `Expanded`/`Flexible` cannot resolve: the incoming constraint is unbounded. Wind detects this at resolution time and skips the `Expanded`/`Flexible` wrap for direct flex children in that pass, so `flex-N` becomes a no-op while the axis is scrollable. This enables the common responsive pattern of "scroll on narrow, distribute on wide": diff --git a/doc/layout/positioning.md b/doc/layout/positioning.md index 9949d833..21240c90 100644 --- a/doc/layout/positioning.md +++ b/doc/layout/positioning.md @@ -15,7 +15,7 @@ Utilities for controlling how elements are positioned in the layout using `relat ```dart -// Badge overlay — red dot on an avatar +// Badge overlay: red dot on an avatar WDiv( className: 'relative', children: [ @@ -97,7 +97,7 @@ Apply offsets to multiple sides at once with `inset-*` shorthand classes. | `inset-0` | top, right, bottom, left | Full stretch (fills parent) | ```dart -// Full overlay — covers the entire relative parent +// Full overlay: covers the entire relative parent WDiv( className: 'relative w-full h-48', children: [ @@ -115,7 +115,7 @@ WDiv( ``` ```dart -// Horizontal inset — leaves top and bottom unconstrained +// Horizontal inset: leaves top and bottom unconstrained WDiv( className: 'absolute inset-x-4 bottom-4 bg-white rounded p-3', child: WText('Bottom bar'), @@ -136,7 +136,7 @@ Prefix any offset class with `-` to pull an element outside its parent's boundar | `-inset-{n}` | negative all sides | Expands element beyond all four parent edges | ```dart -// Notification badge — overlaps the top-right corner of an icon button +// Notification badge: overlaps the top-right corner of an icon button WDiv( className: 'relative', children: [ @@ -152,13 +152,13 @@ WDiv( ## Arbitrary Values -Use bracket notation when you need an exact pixel value not in the theme scale. Only `px` values are supported — percentage (`%`) offsets are not supported because Flutter's `Positioned` widget uses logical pixels, not relative units. +Use bracket notation when you need an exact pixel value not in the theme scale. Only `px` values are supported; percentage (`%`) offsets are not supported because Flutter's `Positioned` widget uses logical pixels, not relative units. ```dart // Exact pixel value WDiv(className: 'absolute top-[24px] left-[12px]') -// Mixed — precise multi-side offset +// Mixed: precise multi-side offset WDiv(className: 'absolute top-[12px] right-[8px] bottom-[12px] left-[8px]') ``` @@ -223,7 +223,7 @@ WDiv( ## Combining with Flex -`relative` and `absolute` compose naturally with flex layouts. The `relative` container itself can be a flex row or column — the `Stack` wraps around the flex widget, and `absolute` children are layered on top. +`relative` and `absolute` compose naturally with flex layouts. The `relative` container itself can be a flex row or column; the `Stack` wraps around the flex widget, and `absolute` children are layered on top. ```dart // Navigation bar with an absolute badge on the icon @@ -254,8 +254,8 @@ The following position types are tracked but not yet implemented in v1: | Class | Status | |:------|:-------| -| `fixed` | Deferred — maps to Flutter's `Overlay`/`Stack` at the root level | -| `sticky` | Deferred — requires custom `SliverPersistentHeader` integration | +| `fixed` | Deferred: maps to Flutter's `Overlay`/`Stack` at the root level | +| `sticky` | Deferred: requires custom `SliverPersistentHeader` integration | Until these land, use `Overlay` directly or Flutter's `Stack` at the `Scaffold` body level for fixed positioning. diff --git a/doc/widgets/w-breakpoint.md b/doc/widgets/w-breakpoint.md index 42aee98e..6d7d3ce8 100644 --- a/doc/widgets/w-breakpoint.md +++ b/doc/widgets/w-breakpoint.md @@ -1,6 +1,6 @@ # WBreakpoint -Declarative breakpoint-keyed builder for rendering different widget trees per responsive breakpoint. An **escape hatch** for cases where the widget structure genuinely differs between breakpoints — reach for className prefixes first. +Declarative breakpoint-keyed builder for rendering different widget trees per responsive breakpoint. An **escape hatch** for cases where the widget structure genuinely differs between breakpoints; reach for className prefixes first. - [Basic Usage](#basic-usage) - [When to Use](#when-to-use) @@ -52,7 +52,7 @@ Prefer className-first patterns. `WBreakpoint` is a last resort: | Render different widget **types** | `WBreakpoint` | | Render different child **counts** | `WBreakpoint` | -If your two branches differ only in className, use prefixes instead — you'll get less code and a cleaner diff. +If your two branches differ only in className, use prefixes instead: you'll get less code and a cleaner diff. ## Constructor diff --git a/doc/widgets/w-date-picker.md b/doc/widgets/w-date-picker.md index 8864a340..2dded9ce 100644 --- a/doc/widgets/w-date-picker.md +++ b/doc/widgets/w-date-picker.md @@ -148,9 +148,9 @@ WDatePicker( How range selection works: -1. **First click** — Sets the range start. The `onRangeChanged` callback fires with a `DateRange` where `end` is `null`. -2. **Hover** — As the user moves the mouse, dates between start and the hovered date are highlighted with a blue tint. -3. **Second click** — Completes the range. If the second date is before the first, they're automatically swapped. The popover closes. +1. **First click**: Sets the range start. The `onRangeChanged` callback fires with a `DateRange` where `end` is `null`. +2. **Hover**: As the user moves the mouse, dates between start and the hovered date are highlighted with a blue tint. +3. **Second click**: Completes the range. If the second date is before the first, they're automatically swapped. The popover closes. The trigger display text updates throughout: `"Jan 15, 2025 - ..."` while in progress, then `"Jan 15, 2025 - Jan 20, 2025"` when complete. @@ -205,7 +205,7 @@ WDatePicker( ### Range Mode -`onRangeChanged` fires twice during a range selection — once on the first click (start only) and once on the second click (complete range): +`onRangeChanged` fires twice during a range selection: once on the first click (start only) and once on the second click (complete range): ```dart WDatePicker( diff --git a/doc/widgets/w-form-date-picker.md b/doc/widgets/w-form-date-picker.md index 1149f9ce..27e87722 100644 --- a/doc/widgets/w-form-date-picker.md +++ b/doc/widgets/w-form-date-picker.md @@ -197,11 +197,11 @@ WFormDatePicker( ``` > [!NOTE] -> The `validator` receives the range's start `DateTime` — not a `DateRange` object. For more complex range validation (e.g., minimum stay duration), use the `onRangeChanged` callback to manage validation externally. +> The `validator` receives the range's start `DateTime`, not a `DateRange` object. For more complex range validation (e.g., minimum stay duration), use the `onRangeChanged` callback to manage validation externally. ## Event Handling -`onChanged` and `onRangeChanged` fire alongside the form state updates. The `FormFieldState` is updated automatically — you don't need to call `didChange` yourself. +`onChanged` and `onRangeChanged` fire alongside the form state updates. The `FormFieldState` is updated automatically; you don't need to call `didChange` yourself. ```dart WFormDatePicker( diff --git a/doc/widgets/w-svg.md b/doc/widgets/w-svg.md index 5403b344..390c0c32 100644 --- a/doc/widgets/w-svg.md +++ b/doc/widgets/w-svg.md @@ -152,16 +152,16 @@ WSvg( ### Preserving Original Colors -By default, `WSvg` applies a `ColorFilter` based on the color priority chain above. For SVGs that contain multiple colors you want to keep intact—like QR codes, branded logos, or multi-color illustrations—use the `preserve-colors` utility class. +By default, `WSvg` applies a `ColorFilter` based on the color priority chain above. For SVGs that contain multiple colors you want to keep intact (like QR codes, branded logos, or multi-color illustrations), use the `preserve-colors` utility class. ```dart -// Multi-color logo — keep original colors +// Multi-color logo: keep original colors WSvg( src: 'assets/logo-colored.svg', className: 'w-32 h-32 preserve-colors', ) -// QR code — must not be tinted +// QR code: must not be tinted WSvg( src: 'assets/qr-code.svg', className: 'w-48 h-48 preserve-colors', diff --git a/doc/widgets/w-switch.md b/doc/widgets/w-switch.md index b3d2101d..de5af207 100644 --- a/doc/widgets/w-switch.md +++ b/doc/widgets/w-switch.md @@ -39,7 +39,7 @@ WSwitch( | `value` | `bool` | **Required** | Whether the switch is on. Drives the `checked:` state prefix. | | `onChanged` | `ValueChanged?` | `null` | Called with the new value when the switch is tapped. Omit or pass `null` to make the switch non-interactive. | | `className` | `String?` | `null` | Utility classes for the track (the outer pill). Supports `checked:`, `disabled:`, `hover:`, and `focus:` prefixes. | -| `thumbClassName` | `String?` | `null` | Utility classes for the thumb (the circular indicator) — shape and color only. The thumb is a flex child of the track; position it with `justify-start` / `checked:justify-end` on the track `className`, not with `translate-x-*` (Wind has no transform parser, so translate is a no-op). | +| `thumbClassName` | `String?` | `null` | Utility classes for the thumb (the circular indicator), shape and color only. The thumb is a flex child of the track; position it with `justify-start` / `checked:justify-end` on the track `className`, not with `translate-x-*` (Wind has no transform parser, so translate is a no-op). | | `disabled` | `bool` | `false` | Blocks tap and activates the `disabled:` prefix. | | `states` | `Set?` | `null` | Extra custom states merged with built-in `checked` and `disabled`. | | `semanticLabel` | `String?` | `null` | Accessible label for the switch Semantics node. Set this when the switch has no adjacent visible text label. | diff --git a/example/lib/pages/buttons/button_basic.dart b/example/lib/pages/buttons/button_basic.dart index 5ece06ff..870d7fc7 100644 --- a/example/lib/pages/buttons/button_basic.dart +++ b/example/lib/pages/buttons/button_basic.dart @@ -48,7 +48,7 @@ class _ButtonBasicExamplePageState extends State { ExampleSection( title: 'Button Variants', description: - 'Same WButton, different className. Primary, secondary, outline, danger — all utility-driven.', + 'Same WButton, different className. Primary, secondary, outline, danger: all utility-driven.', child: WDiv( className: 'wrap gap-3', children: [ diff --git a/example/lib/pages/effects/opacity.dart b/example/lib/pages/effects/opacity.dart index 85b9fc68..780c16ac 100644 --- a/example/lib/pages/effects/opacity.dart +++ b/example/lib/pages/effects/opacity.dart @@ -134,7 +134,7 @@ class _CompareCard extends StatelessWidget { WDiv( className: '$cls rounded-lg', child: WText( - 'Sample Text — $note', + 'Sample Text: $note', className: 'font-medium', ), ), diff --git a/example/lib/pages/forms/input_basic.dart b/example/lib/pages/forms/input_basic.dart index 6d436841..ebcc3a47 100644 --- a/example/lib/pages/forms/input_basic.dart +++ b/example/lib/pages/forms/input_basic.dart @@ -66,22 +66,22 @@ class _InputBasicExamplePageState extends State { children: [ WInput( type: InputType.text, - placeholder: 'text — default', + placeholder: 'text: default', className: _inputCls, ), WInput( type: InputType.email, - placeholder: 'email — email keyboard', + placeholder: 'email: email keyboard', className: _inputCls, ), WInput( type: InputType.password, - placeholder: 'password — obscured', + placeholder: 'password: obscured', className: _inputCls, ), WInput( type: InputType.number, - placeholder: 'number — numeric keyboard', + placeholder: 'number: numeric keyboard', className: _inputCls, ), ], diff --git a/example/lib/pages/forms/select_basic.dart b/example/lib/pages/forms/select_basic.dart index 4ac3e87a..f9a53c33 100644 --- a/example/lib/pages/forms/select_basic.dart +++ b/example/lib/pages/forms/select_basic.dart @@ -32,7 +32,7 @@ class _SelectBasicExamplePageState extends State { return ExampleScaffold( title: 'WSelect', description: - 'Utility-first dropdown built on WPopover. Single + multi + searchable + async load + tagging — all from one widget.', + 'Utility-first dropdown built on WPopover. Single + multi + searchable + async load + tagging: all from one widget.', gradient: 'from-indigo-500 to-blue-600', children: [ ExampleSection( diff --git a/example/lib/pages/interactivity/animation_basic.dart b/example/lib/pages/interactivity/animation_basic.dart index 44f2d0f2..51901101 100644 --- a/example/lib/pages/interactivity/animation_basic.dart +++ b/example/lib/pages/interactivity/animation_basic.dart @@ -69,7 +69,7 @@ class InteractivityAnimationExamplePage extends StatelessWidget { ExampleSection( title: 'Ping', description: - 'Element scales outward and fades — radar pulse / notification dot.', + 'Element scales outward and fades (radar pulse / notification dot).', child: WDiv( className: 'wrap gap-8 items-center py-6', children: const [ diff --git a/example/lib/pages/interactivity/cursor_basic.dart b/example/lib/pages/interactivity/cursor_basic.dart index 8e69dca1..edcc6991 100644 --- a/example/lib/pages/interactivity/cursor_basic.dart +++ b/example/lib/pages/interactivity/cursor_basic.dart @@ -29,7 +29,7 @@ class CursorBasicExamplePage extends StatelessWidget { dark:bg-blue-600 dark:hover:bg-blue-700 ''', child: const WText( - 'Hover me — cursor-pointer', + 'Hover me (cursor-pointer)', className: 'text-white font-medium', ), ), diff --git a/example/lib/pages/interactivity/transition_basic.dart b/example/lib/pages/interactivity/transition_basic.dart index e79a486f..e0707dad 100644 --- a/example/lib/pages/interactivity/transition_basic.dart +++ b/example/lib/pages/interactivity/transition_basic.dart @@ -25,7 +25,7 @@ class TransitionBasicExamplePage extends StatelessWidget { px-6 py-4 rounded-lg self-start ''', child: const WText( - 'Hover me — 300ms ease-in-out', + 'Hover me: 300ms ease-in-out', className: 'text-white font-medium', ), ), diff --git a/example/lib/pages/layout/display.dart b/example/lib/pages/layout/display.dart index c012864e..957a8183 100644 --- a/example/lib/pages/layout/display.dart +++ b/example/lib/pages/layout/display.dart @@ -135,9 +135,9 @@ class DisplayExamplePage extends StatelessWidget { _RefRow(cls: 'block', body: 'Default. Container or Column.'), _RefRow(cls: 'flex', body: 'Row/Column with flex semantics.'), _RefRow(cls: 'grid', body: 'GridView with grid-cols-{n}.'), - _RefRow(cls: 'wrap', body: 'Wrap widget — multi-line flow.'), + _RefRow(cls: 'wrap', body: 'Wrap widget: multi-line flow.'), _RefRow( - cls: 'hidden', body: 'SizedBox.shrink — removes from tree.'), + cls: 'hidden', body: 'SizedBox.shrink: removes from tree.'), ], ), ), diff --git a/example/lib/pages/layout/flex_grow.dart b/example/lib/pages/layout/flex_grow.dart index 3a16b87f..543184f8 100644 --- a/example/lib/pages/layout/flex_grow.dart +++ b/example/lib/pages/layout/flex_grow.dart @@ -152,7 +152,7 @@ class FlexGrowExamplePage extends StatelessWidget { child: WDiv( className: 'flex flex-col gap-1', children: const [ - _RefRow(cls: 'flex-1', desc: 'Expanded() — grow to fill'), + _RefRow(cls: 'flex-1', desc: 'Expanded(): grow to fill'), _RefRow( cls: 'flex-{n}', desc: 'Specific flex factor (n is any integer)'), diff --git a/example/lib/pages/layout/grid_cols.dart b/example/lib/pages/layout/grid_cols.dart index 0213863a..0624d9ff 100644 --- a/example/lib/pages/layout/grid_cols.dart +++ b/example/lib/pages/layout/grid_cols.dart @@ -51,7 +51,7 @@ class GridColsExamplePage extends StatelessWidget { ExampleSection( title: 'Quick Reference', description: - 'Default scale covers 1–12 columns. Any other integer also resolves correctly.', + 'Default scale covers 1 to 12 columns. Any other integer also resolves correctly.', child: WDiv( className: 'flex flex-col gap-1', children: const [ diff --git a/example/lib/pages/layout/sizing_height.dart b/example/lib/pages/layout/sizing_height.dart index 6a85e296..b0959bb3 100644 --- a/example/lib/pages/layout/sizing_height.dart +++ b/example/lib/pages/layout/sizing_height.dart @@ -52,7 +52,7 @@ class SizingHeightExamplePage extends StatelessWidget { bg-yellow-100 dark:bg-yellow-900/30 ''', child: const WText( - 'min-h-32 — at least 128px tall', + 'min-h-32: at least 128px tall', className: 'text-sm text-slate-700 dark:text-slate-200', ), ), diff --git a/example/lib/pages/popover/popover_basic.dart b/example/lib/pages/popover/popover_basic.dart index cc10dd55..64972afa 100644 --- a/example/lib/pages/popover/popover_basic.dart +++ b/example/lib/pages/popover/popover_basic.dart @@ -97,7 +97,7 @@ class PopoverBasicExamplePage extends StatelessWidget { ExampleSection( title: 'Trigger State', description: - 'triggerBuilder receives isOpen + isHovering — style the trigger reactively.', + 'triggerBuilder receives isOpen + isHovering, style the trigger reactively.', child: WPopover( className: ''' w-64 rounded-lg p-3 diff --git a/example/lib/pages/typography/font_size.dart b/example/lib/pages/typography/font_size.dart index b2a1b013..d173915f 100644 --- a/example/lib/pages/typography/font_size.dart +++ b/example/lib/pages/typography/font_size.dart @@ -42,15 +42,15 @@ class FontSizeExamplePage extends StatelessWidget { className: 'flex flex-col gap-3', children: const [ WText( - 'text-lg/loose — generous breathing room', + 'text-lg/loose: generous breathing room', className: 'text-lg/loose text-slate-900 dark:text-white', ), WText( - 'text-base/7 — fixed 28px line height', + 'text-base/7: fixed 28px line height', className: 'text-base/7 text-slate-900 dark:text-white', ), WText( - 'text-xl/[32px] — arbitrary', + 'text-xl/[32px]: arbitrary', className: 'text-xl/[32px] text-slate-900 dark:text-white', ), ], @@ -69,7 +69,7 @@ class FontSizeExamplePage extends StatelessWidget { ExampleSection( title: 'Arbitrary Sizes', description: - 'Brackets accept exact px or rem. Use sparingly — prefer the scale for consistency.', + 'Brackets accept exact px or rem. Use sparingly; prefer the scale for consistency.', child: WDiv( className: 'flex flex-col gap-2', children: const [ diff --git a/example/lib/pages/typography/line_height.dart b/example/lib/pages/typography/line_height.dart index b3117eff..7c2bded1 100644 --- a/example/lib/pages/typography/line_height.dart +++ b/example/lib/pages/typography/line_height.dart @@ -64,7 +64,7 @@ class LineHeightExamplePage extends StatelessWidget { description: 'Brackets accept fixed px, rem, or unitless multipliers.', child: const WText( - 'Custom 1.8 multiplier — the lines have a precise 1.8× breathing distance from one another for editorial layouts.', + 'Custom 1.8 multiplier: the lines have a precise 1.8× breathing distance from one another for editorial layouts.', className: 'leading-[1.8] text-base text-slate-900 dark:text-white', ), ), diff --git a/example/lib/pages/typography/text_overflow_truncate.dart b/example/lib/pages/typography/text_overflow_truncate.dart index d090dde7..a0828642 100644 --- a/example/lib/pages/typography/text_overflow_truncate.dart +++ b/example/lib/pages/typography/text_overflow_truncate.dart @@ -42,7 +42,7 @@ class TextOverflowTruncateExamplePage extends StatelessWidget { WDiv( className: 'flex-1 min-w-0', child: const WText( - 'Jane Doe — Senior Engineer at a long-named company that has many people', + 'Jane Doe, Senior Engineer at a long-named company that has many people', className: 'truncate text-slate-900 dark:text-white', ), ), diff --git a/example/lib/pages/utilities/color_helpers_basic.dart b/example/lib/pages/utilities/color_helpers_basic.dart index 4fe79abf..e3f8cf04 100644 --- a/example/lib/pages/utilities/color_helpers_basic.dart +++ b/example/lib/pages/utilities/color_helpers_basic.dart @@ -15,7 +15,7 @@ class ColorHelpersBasicExamplePage extends StatelessWidget { gradient: 'from-pink-500 to-rose-600', children: [ ExampleSection( - title: 'wColor() — Resolve Theme Color', + title: 'wColor(): Resolve Theme Color', description: 'Pass a color name and shade. Wind returns the resolved Color from the active theme.', child: WDiv( @@ -31,7 +31,7 @@ class ColorHelpersBasicExamplePage extends StatelessWidget { ), ), ExampleSection( - title: 'hexToColor() — Hex String to Color', + title: 'hexToColor(): Hex String to Color', description: 'Accepts 3/4/6/8-digit hex with optional # prefix.', child: WDiv( className: 'wrap gap-3', @@ -44,7 +44,7 @@ class ColorHelpersBasicExamplePage extends StatelessWidget { ), ), ExampleSection( - title: 'applyOpacity() — Adjust Alpha', + title: 'applyOpacity(): Adjust Alpha', description: 'Same base color, descending alpha via applyOpacity(color, N).', child: WDiv( @@ -59,7 +59,7 @@ class ColorHelpersBasicExamplePage extends StatelessWidget { ), ), ExampleSection( - title: 'context.wColorExt — Extension Shortcut', + title: 'context.wColorExt: Extension Shortcut', description: 'Same as wColor(context, ...). Cleaner inside build methods.', child: _CodeBlock( diff --git a/example/lib/pages/utilities/context_extensions_basic.dart b/example/lib/pages/utilities/context_extensions_basic.dart index 0149503b..599f03a3 100644 --- a/example/lib/pages/utilities/context_extensions_basic.dart +++ b/example/lib/pages/utilities/context_extensions_basic.dart @@ -44,7 +44,7 @@ class ContextExtensionsBasicExamplePage extends StatelessWidget { ), ), ExampleSection( - title: 'context.windTheme — Toggle from Anywhere', + title: 'context.windTheme: Toggle from Anywhere', description: 'The header toggle uses this exact call. Available globally without provider drilling.', child: _CodeBlock( @@ -53,7 +53,7 @@ class ContextExtensionsBasicExamplePage extends StatelessWidget { ), ), ExampleSection( - title: 'context.wColorExt — Color Resolver', + title: 'context.wColorExt: Color Resolver', description: 'Resolves a theme color in one call. The block below shows three live resolutions.', child: WDiv( @@ -66,7 +66,7 @@ class ContextExtensionsBasicExamplePage extends StatelessWidget { ), ), ExampleSection( - title: 'context.wStyleExt — Parse className', + title: 'context.wStyleExt: Parse className', description: 'Parse any utility string into a WindStyle to apply on plain Flutter widgets.', child: _CodeBlock( diff --git a/example/lib/pages/utilities/spacing_helpers_basic.dart b/example/lib/pages/utilities/spacing_helpers_basic.dart index 55812715..5007a420 100644 --- a/example/lib/pages/utilities/spacing_helpers_basic.dart +++ b/example/lib/pages/utilities/spacing_helpers_basic.dart @@ -37,7 +37,7 @@ class SpacingHelpersBasicExamplePage extends StatelessWidget { ExampleSection( title: 'Fractional Multipliers', description: - 'wSpacing also accepts decimals — useful for tight spacings that the className scale does not include.', + 'wSpacing also accepts decimals, useful for tight spacings that the className scale does not include.', child: WDiv( className: 'flex flex-col gap-2', children: const [ diff --git a/example/lib/pages/utilities/style_parser_basic.dart b/example/lib/pages/utilities/style_parser_basic.dart index 5b6d8f63..5796ffd3 100644 --- a/example/lib/pages/utilities/style_parser_basic.dart +++ b/example/lib/pages/utilities/style_parser_basic.dart @@ -97,7 +97,7 @@ class _StyleParserBasicExamplePageState ExampleSection( title: 'Apply to Plain Flutter Widgets', description: - 'Use wStyle to bring Wind into Container, Text, AppBar, ListTile — anything outside the W-prefixed family.', + 'Use wStyle to bring Wind into Container, Text, AppBar, ListTile: anything outside the W-prefixed family.', child: _CodeBlock( code: 'final style = wStyle(context, "p-4 bg-red-500 rounded");\n\n' 'return Container(\n' diff --git a/example/lib/pages/utilities/typography_helpers_basic.dart b/example/lib/pages/utilities/typography_helpers_basic.dart index 87ef91e5..179eddac 100644 --- a/example/lib/pages/utilities/typography_helpers_basic.dart +++ b/example/lib/pages/utilities/typography_helpers_basic.dart @@ -15,7 +15,7 @@ class TypographyHelpersBasicExamplePage extends StatelessWidget { gradient: 'from-amber-500 to-orange-500', children: [ ExampleSection( - title: 'wFontSize() — Resolve Pixel Size', + title: 'wFontSize(): Resolve Pixel Size', description: 'Every named size from xs to 6xl maps to a double pixel value.', child: WDiv( @@ -32,7 +32,7 @@ class TypographyHelpersBasicExamplePage extends StatelessWidget { ), ), ExampleSection( - title: 'wFontWeight() — Resolve FontWeight', + title: 'wFontWeight(): Resolve FontWeight', description: 'Named weights map to Flutter FontWeight constants.', child: WDiv( className: 'flex flex-col gap-2', diff --git a/example/lib/pages/widgets/w_anchor_flex.dart b/example/lib/pages/widgets/w_anchor_flex.dart index 9ea8f8b7..f0d98de2 100644 --- a/example/lib/pages/widgets/w_anchor_flex.dart +++ b/example/lib/pages/widgets/w_anchor_flex.dart @@ -50,7 +50,7 @@ class WAnchorFlexExamplePage extends StatelessWidget { ExampleSection( title: 'Card Lift Effect', description: - 'Hover raises shadow + tints background — works for entire interactive cards.', + 'Hover raises shadow + tints background, works for entire interactive cards.', child: WAnchor( onTap: () {}, child: WDiv( diff --git a/example/lib/pages/widgets/w_div_basic.dart b/example/lib/pages/widgets/w_div_basic.dart index 6ee576b4..090f9929 100644 --- a/example/lib/pages/widgets/w_div_basic.dart +++ b/example/lib/pages/widgets/w_div_basic.dart @@ -60,7 +60,7 @@ class WDivBasicExamplePage extends StatelessWidget { ExampleSection( title: 'Layout Modes', description: - 'block, flex, grid, wrap, hidden — each maps to a different Flutter widget under the hood.', + 'block, flex, grid, wrap, hidden: each maps to a different Flutter widget under the hood.', child: WDiv( className: 'flex flex-col gap-3', children: [ diff --git a/example/lib/pages/widgets/w_form_input_layout.dart b/example/lib/pages/widgets/w_form_input_layout.dart index 92562e5a..bf55a70f 100644 --- a/example/lib/pages/widgets/w_form_input_layout.dart +++ b/example/lib/pages/widgets/w_form_input_layout.dart @@ -53,7 +53,7 @@ class WFormInputLayoutExamplePage extends StatelessWidget { ExampleSection( title: 'Hide Error String', description: - 'showError: false keeps the error: prefix active on the input but suppresses the message text — useful when you render your own.', + 'showError: false keeps the error: prefix active on the input but suppresses the message text; useful when you render your own.', child: WFormInput( label: 'Email', placeholder: 'you@example.com', diff --git a/example/lib/pages/widgets/w_spacer_basic.dart b/example/lib/pages/widgets/w_spacer_basic.dart index 13433ee8..01f6f393 100644 --- a/example/lib/pages/widgets/w_spacer_basic.dart +++ b/example/lib/pages/widgets/w_spacer_basic.dart @@ -78,7 +78,7 @@ class WSpacerBasicExamplePage extends StatelessWidget { children: const [ _Box(label: 'Above'), WSpacer(className: 'h-[13px]'), - _Box(label: 'Below — 13px gap above'), + _Box(label: 'Below (13px gap above)'), ], ), ), diff --git a/example/lib/pages/widgets/w_spacer_responsive.dart b/example/lib/pages/widgets/w_spacer_responsive.dart index cdfc9fea..e317cb36 100644 --- a/example/lib/pages/widgets/w_spacer_responsive.dart +++ b/example/lib/pages/widgets/w_spacer_responsive.dart @@ -17,7 +17,7 @@ class WSpacerResponsiveExamplePage extends StatelessWidget { ExampleSection( title: 'Basic Usage', description: - 'h-4 md:h-8 — 16px gap on mobile, 32px on md+. Resize the window to see the spacing change.', + 'h-4 md:h-8: 16px gap on mobile, 32px on md+. Resize the window to see the spacing change.', child: WDiv( className: 'flex flex-col items-stretch', children: const [ @@ -36,7 +36,7 @@ class WSpacerResponsiveExamplePage extends StatelessWidget { children: const [ _Card(label: 'First'), WSpacer(className: 'h-2 sm:h-4 lg:h-8 xl:h-12'), - _Card(label: 'Second — cascading gap'), + _Card(label: 'Second (cascading gap)'), ], ), ), diff --git a/example/lib/pages/widgets/w_text.dart b/example/lib/pages/widgets/w_text.dart index e7756bf0..01aa83d8 100644 --- a/example/lib/pages/widgets/w_text.dart +++ b/example/lib/pages/widgets/w_text.dart @@ -83,7 +83,7 @@ class WTextExamplePage extends StatelessWidget { ExampleSection( title: 'Layout Compose', description: - 'WText accepts padding, background, and borders — Wind auto-wraps in Container.', + 'WText accepts padding, background, and borders; Wind auto-wraps in Container.', child: WText( 'Alert: build failed at 14:32. Please retry.', className: ''' diff --git a/lib/src/dynamic/w_action_handler.dart b/lib/src/dynamic/w_action_handler.dart index fcc7bd51..5ba01fc8 100644 --- a/lib/src/dynamic/w_action_handler.dart +++ b/lib/src/dynamic/w_action_handler.dart @@ -85,7 +85,7 @@ import 'w_dynamic_state.dart'; /// ## Error Handling /// /// **Unknown action**: If an action name is not in the registered actions map, the call -/// is silently ignored and a debug message is printed: `"Unknown action {name} — ignored."` +/// is silently ignored and a debug message is printed: `"Unknown action {name}, ignored."` /// /// **Handler exception**: If a handler throws an exception, it is caught and logged: /// `"Action {name} error: {exception}"`. The error does not propagate to the widget tree. @@ -147,7 +147,7 @@ class WActionHandler { FutureOr dispatch(String actionName, Map args) { final handler = _actions[actionName]; if (handler == null) { - debugPrint('WindDynamic: Unknown action "$actionName" — ignored.'); + debugPrint('WindDynamic: Unknown action "$actionName", ignored.'); return null; } diff --git a/lib/src/parser/parsers/order_parser.dart b/lib/src/parser/parsers/order_parser.dart index 111f45d0..211735aa 100644 --- a/lib/src/parser/parsers/order_parser.dart +++ b/lib/src/parser/parsers/order_parser.dart @@ -10,11 +10,11 @@ import 'wind_parser_interface.dart'; /// stable-sorts them before layout. /// /// ### Supported Utility Classes: -/// - `order-0` .. `order-12` — explicit index -/// - `order-first` — sentinel `-9999` -/// - `order-last` — sentinel `9999` -/// - `order-none` — reset to `0` -/// - `order-[N]` — arbitrary integer value (supports negative) +/// - `order-0` .. `order-12`: explicit index +/// - `order-first`: sentinel `-9999` +/// - `order-last`: sentinel `9999` +/// - `order-none`: reset to `0` +/// - `order-[N]`: arbitrary integer value (supports negative) /// /// Prefixes such as `sm:`, `dark:`, `hover:` are stripped by the orchestrator /// before classes reach this parser; the full responsive + state stack works @@ -33,7 +33,7 @@ class OrderParser implements WindParserInterface { @override bool canParse(String className) { - // Fast path — `canParse` must stay O(1). We match the literal prefix and + // Fast path: `canParse` must stay O(1). We match the literal prefix and // let `parse` do the full regex validation. return className.startsWith('order-'); } @@ -48,7 +48,7 @@ class OrderParser implements WindParserInterface { int? order; - // Reverse iteration — last class wins, consistent with other parsers. + // Reverse iteration: last class wins, consistent with other parsers. for (var i = classes.length - 1; i >= 0; i--) { final match = _orderRegex.firstMatch(classes[i]); if (match == null) continue; diff --git a/lib/src/parser/parsers/position_parser.dart b/lib/src/parser/parsers/position_parser.dart index ec0a692a..5b3f7cd3 100644 --- a/lib/src/parser/parsers/position_parser.dart +++ b/lib/src/parser/parsers/position_parser.dart @@ -36,7 +36,7 @@ class PositionParser implements WindParserInterface { /// Regex for arbitrary offset classes /// e.g., top-[24px], left-[12px], -inset-[10px] - /// Note: percentage values (e.g., left-[50%]) are intentionally unsupported — + /// Note: percentage values (e.g., left-[50%]) are intentionally unsupported: /// Flutter's Positioned uses logical pixels, not percentages. static final _arbitraryOffsetRegex = RegExp( r'^-?(?top|bottom|left|right|inset-x|inset-y|inset)-\[(?[0-9.]+(?:px)?)\]$', @@ -72,7 +72,7 @@ class PositionParser implements WindParserInterface { } else if (className == 'absolute') { positionType = WindPositionType.absolute; } - // fixed/sticky are claimed but ignored — no style change + // fixed/sticky are claimed but ignored, no style change } continue; } diff --git a/lib/src/parser/parsers/svg_parser.dart b/lib/src/parser/parsers/svg_parser.dart index c540c56d..ded6d2f1 100644 --- a/lib/src/parser/parsers/svg_parser.dart +++ b/lib/src/parser/parsers/svg_parser.dart @@ -13,7 +13,7 @@ import '../../theme/wind_theme_data.dart'; /// ### Supported Utility Classes: /// - **Fill:** `fill-red-500`, `fill-none`, `fill-current` /// - **Stroke:** `stroke-blue-500`, `stroke-none`, `stroke-[2px]` (future) -/// - **Preserve:** `preserve-colors` — disables any [ColorFilter] on the SVG, +/// - **Preserve:** `preserve-colors`: disables any [ColorFilter] on the SVG, /// rendering embedded colours unchanged (ideal for QR codes and logos). /// /// Returns a [WindStyle] with `fillColor`, `strokeColor`, and `preserveColors`. @@ -43,7 +43,7 @@ class SvgParser implements WindParserInterface { final theme = context.theme; for (final className in classes) { - // Handle preserve-colors — disables ColorFilter entirely for multi-colour SVGs. + // Handle preserve-colors: disables ColorFilter entirely for multi-colour SVGs. if (className == 'preserve-colors') { preserveColors = true; } diff --git a/lib/src/parser/wind_style.dart b/lib/src/parser/wind_style.dart index 844cb3ae..1a899068 100644 --- a/lib/src/parser/wind_style.dart +++ b/lib/src/parser/wind_style.dart @@ -227,7 +227,7 @@ class WindStyle { /// SVG stroke color e.g., stroke-blue-500, stroke-current final Color? strokeColor; - /// When true, no [ColorFilter] is applied to the SVG — the widget renders + /// When true, no [ColorFilter] is applied to the SVG; the widget renders /// with its own embedded colors unchanged. Use `preserve-colors` in className. /// Ideal for QR codes, logos, and multi-colour illustrations. final bool preserveColors; diff --git a/lib/src/theme/wind_theme.dart b/lib/src/theme/wind_theme.dart index f9ab8fb3..cc45a3ad 100644 --- a/lib/src/theme/wind_theme.dart +++ b/lib/src/theme/wind_theme.dart @@ -260,7 +260,7 @@ class _WindThemeState extends State with WidgetsBindingObserver { /// When true, [onThemeChanged] will NOT fire. bool _isSystemChange = false; - /// Previous brightness — used to detect actual changes. + /// Previous brightness, used to detect actual changes. Brightness? _previousBrightness; @override diff --git a/lib/src/widgets/w_anchor.dart b/lib/src/widgets/w_anchor.dart index 4b77b7a5..a53cb2c3 100644 --- a/lib/src/widgets/w_anchor.dart +++ b/lib/src/widgets/w_anchor.dart @@ -188,7 +188,7 @@ class _WAnchorState extends State { widget.onLongPress != null || widget.onDoubleTap != null; - // Focus is always present — needed for focus: class prefix to work + // Focus is always present, needed for focus: class prefix to work Widget innerChild = Focus( focusNode: _focusNode, canRequestFocus: !widget.isDisabled, diff --git a/lib/src/widgets/w_breakpoint.dart b/lib/src/widgets/w_breakpoint.dart index 47e78cb3..bae59448 100644 --- a/lib/src/widgets/w_breakpoint.dart +++ b/lib/src/widgets/w_breakpoint.dart @@ -10,9 +10,9 @@ import '../parser/wind_context.dart'; /// /// This is an **escape hatch**. Prefer className-first solutions when the /// difference is purely stylistic: -/// - `sm:flex-row`, `md:gap-8` — swap styles per breakpoint -/// - `hidden sm:block` / `block sm:hidden` — swap visibility per breakpoint -/// - `order-2 md:order-1` — reorder flex children per breakpoint +/// - `sm:flex-row`, `md:gap-8`: swap styles per breakpoint +/// - `hidden sm:block` / `block sm:hidden`: swap visibility per breakpoint +/// - `order-2 md:order-1`: reorder flex children per breakpoint /// /// Reach for `WBreakpoint` only when the widget **tree structure** genuinely /// differs per breakpoint (different widget types, different child counts, @@ -23,7 +23,7 @@ import '../parser/wind_context.dart'; /// 1. Reads active breakpoint via [WindContext.build]. /// 2. Walks the breakpoint chain in descending order by min width, restricted /// to breakpoints whose min width ≤ the active breakpoint's min width. -/// 3. Returns the first builder found — i.e. the builder for the highest +/// 3. Returns the first builder found: the builder for the highest /// defined breakpoint that is still ≤ the active breakpoint. /// 4. Falls back to [base] if no matching builder exists. /// diff --git a/lib/src/widgets/w_button.dart b/lib/src/widgets/w_button.dart index 797239f7..2db6c7ba 100644 --- a/lib/src/widgets/w_button.dart +++ b/lib/src/widgets/w_button.dart @@ -213,7 +213,7 @@ class WButton extends StatelessWidget { // Determine spinner color: // 1. Explicit loadingColor takes priority. // 2. Text color from className (styles.color). - // 3. Contrast color based on background — prevents invisible spinners + // 3. Contrast color based on background: prevents invisible spinners // (e.g., white spinner on white button). // 4. Final fallback: white. final Color spinnerColor = @@ -244,7 +244,7 @@ class WButton extends StatelessWidget { /// Returns a contrasting spinner color based on background luminance. /// /// If the background is light (luminance > 0.5), returns a dark color. - /// Otherwise returns white — matching the common pattern of light text + /// Otherwise returns white, matching the common pattern of light text /// on dark buttons. Color _contrastColor(WindStyle styles) { final Color? bgColor = styles.decoration?.color; @@ -252,7 +252,7 @@ class WButton extends StatelessWidget { // Use W3C relative luminance to determine if background is light. return bgColor.computeLuminance() > 0.5 - ? const Color(0xFF1E293B) // slate-800 — readable on light bg + ? const Color(0xFF1E293B) // slate-800, readable on light bg : Colors.white; } } diff --git a/lib/src/widgets/w_card.dart b/lib/src/widgets/w_card.dart index d7495a1f..87604e05 100644 --- a/lib/src/widgets/w_card.dart +++ b/lib/src/widgets/w_card.dart @@ -6,16 +6,16 @@ import 'w_div.dart'; /// /// `WCard` composes a `WDiv` (flex-col) with an optional `header` slot, /// a required `child` body, and an optional `footer` slot. All styling is -/// className-driven — no colors or variants are baked in; tone and appearance +/// className-driven: no colors or variants are baked in; tone and appearance /// are the caller's responsibility via `className` or a `WindRecipe` in the /// consuming component layer. /// /// ### Slots /// -/// - **header** — optional widget rendered above the body (e.g. a title row, +/// - **header**: optional widget rendered above the body (e.g. a title row, /// an image, or a section label). -/// - **child** — required body content. -/// - **footer** — optional widget rendered below the body (e.g. action buttons +/// - **child**: required body content. +/// - **footer**: optional widget rendered below the body (e.g. action buttons /// or metadata). /// /// ### Example Usage: diff --git a/lib/src/widgets/w_div.dart b/lib/src/widgets/w_div.dart index 5442d893..b6ebed7d 100644 --- a/lib/src/widgets/w_div.dart +++ b/lib/src/widgets/w_div.dart @@ -499,7 +499,7 @@ class WDiv extends StatelessWidget { isColumn: isColumn, ); - // Resolve child ordering (order-*) before gap injection — sort stably by + // Resolve child ordering (order-*) before gap injection: sort stably by // resolved order (children without `order-*` default to 0). `flex-*-reverse` // is applied via Row.textDirection / Column.verticalDirection below so the // main-axis semantics (justify-start etc.) mirror correctly, matching CSS. @@ -661,7 +661,7 @@ class WDiv extends StatelessWidget { } else { // For Row with space distribution OR overflow-hidden, wrap children with Flexible // This mimics CSS flex-shrink: 1 default behavior. Inside a horizontally - // scrollable row, `Flexible` is also invalid — skip it too. + // scrollable row, `Flexible` is also invalid, skip it too. final needsFlexible = (needsSpaceDistribution || hasOverflowClip) && !isMainAxisScrollable; // A Row hands non-flex children an UNBOUNDED main-axis constraint, so a @@ -698,14 +698,14 @@ class WDiv extends StatelessWidget { } // Don't wrap children that self-wrap in Expanded/Flexible // (flex-N, grow, flex-grow, flex-auto, flex-initial, shrink, - // flex-shrink) — wrapping them again asserts ParentDataWidget. + // flex-shrink): wrapping them again asserts ParentDataWidget. if (child is WDiv && _selfWrapsInFlex(child.className)) { return child; } if (child is WText && _selfWrapsInFlex(child.className)) { return child; } - // Skip shrink-0 children (should not shrink — keep intrinsic size) + // Skip shrink-0 children (should not shrink, keep intrinsic size) if (child is WDiv && _hasShrinkZero(child.className)) { return child; } @@ -1535,9 +1535,9 @@ class WDiv extends StatelessWidget { // _debugRelayoutBoundaryAlreadyMarkedNeedsLayout() is not true // // Strategy: - // - w-full: SizedBox(width: infinity) — no LayoutBuilder needed - // - w-full + max-w-*: ConstrainedBox + SizedBox — no LayoutBuilder needed - // - w-1/2, w-1/3 etc: FractionallySizedBox — no LayoutBuilder needed + // - w-full: SizedBox(width: infinity), no LayoutBuilder needed + // - w-full + max-w-*: ConstrainedBox + SizedBox, no LayoutBuilder needed + // - w-1/2, w-1/3 etc: FractionallySizedBox, no LayoutBuilder needed // - h-full: LayoutBuilder only when vertical axis is unbounded if (styles.widthFactor != null || styles.heightFactor != null) { final innerChild = widgetToBuild; @@ -1566,7 +1566,7 @@ class WDiv extends StatelessWidget { 'instead of `h-full` inside a vertical scroll.', ); - // Fast path: width-only sizing (no heightFactor) — avoids LayoutBuilder + // Fast path: width-only sizing (no heightFactor), avoids LayoutBuilder // Covers: w-full, w-full max-w-*, w-1/2, w-1/3, etc. if (styles.heightFactor == null) { if (isFullWidth && scrollScope?.horizontalPort != null) { @@ -1763,14 +1763,14 @@ class WDiv extends StatelessWidget { ); } - // Absolute-positioned elements are out of normal flow — + // Absolute-positioned elements are out of normal flow: // skip Expanded/Flexible wrapping (parent handles Positioned). if (styles.positionType == WindPositionType.absolute) { return widgetToBuild ?? const SizedBox.shrink(); } // Apply Flex/Expanded (flex-*). Skipped when an ancestor signals that - // the flex main axis is scrollable — `Expanded`/`Flexible` inside an + // the flex main axis is scrollable: `Expanded`/`Flexible` inside an // unbounded-width/height flex would assert. if (!skipFlexWrap) { if (styles.flex != null) { diff --git a/lib/src/widgets/w_keyboard_actions.dart b/lib/src/widgets/w_keyboard_actions.dart index 323e380e..6c999e79 100644 --- a/lib/src/widgets/w_keyboard_actions.dart +++ b/lib/src/widgets/w_keyboard_actions.dart @@ -133,7 +133,7 @@ class _WKeyboardActionsState extends State { OverlayEntry? _overlayEntry; int? _currentIndex; - /// Parsed platform gate — derived from widget.platform once in initState. + /// Parsed platform gate, derived from widget.platform once in initState. late WKeyboardPlatform _platform; @override @@ -236,16 +236,16 @@ class _WKeyboardActionsState extends State { /// itself as long as the overlay remains active. /// /// Transient callbacks fire BEFORE the build phase, so any [setState] from - /// [_removeOverlay] takes effect within the same rendered frame — no extra + /// [_removeOverlay] takes effect within the same rendered frame; no extra /// pump needed. void _schedulePlatformGuard() { SchedulerBinding.instance.scheduleFrameCallback((_) { if (!mounted || _overlayEntry == null) return; if (!_platformMatches()) { - // 2. Platform no longer matches — remove the toolbar in this frame. + // 2. Platform no longer matches, remove the toolbar in this frame. _removeOverlay(); } else { - // 3. Platform still valid — reschedule for the next frame. + // 3. Platform still valid, reschedule for the next frame. _schedulePlatformGuard(); } }); @@ -257,7 +257,7 @@ class _WKeyboardActionsState extends State { /// /// The parameter shadows the State's `context` getter; use `this.context` to /// reach the State's own context when needed (e.g. for `WindParser`, which - /// must walk up to `WindTheme` — see `_resolveToolbarColor`). + /// must walk up to `WindTheme`, see `_resolveToolbarColor`). Widget _buildToolbar(BuildContext context) { final index = _currentIndex; if (index == null) return const SizedBox.shrink(); diff --git a/lib/src/widgets/w_svg.dart b/lib/src/widgets/w_svg.dart index bedeacc4..125fee49 100644 --- a/lib/src/widgets/w_svg.dart +++ b/lib/src/widgets/w_svg.dart @@ -102,7 +102,7 @@ class WSvg extends StatelessWidget { styles.color ?? inheritedColor; - // Build the color filter — skipped entirely when `preserve-colors` is set, + // Build the color filter: skipped entirely when `preserve-colors` is set, // so multi-colour SVGs (QR codes, logos) render with their own embedded colours. ColorFilter? colorFilter; if (!styles.preserveColors && color != null) { diff --git a/lib/src/widgets/w_tabs.dart b/lib/src/widgets/w_tabs.dart index 27c65d76..8221c801 100644 --- a/lib/src/widgets/w_tabs.dart +++ b/lib/src/widgets/w_tabs.dart @@ -19,7 +19,7 @@ import 'w_text.dart'; /// Use [onChanged] to react to tab taps. /// /// ### Supported Features: -/// - **Controlled:** [selectedIndex] + [onChanged] — no internal state. +/// - **Controlled:** [selectedIndex] + [onChanged], no internal state. /// - **Selected state:** `selected:` className prefix activates on the active tab. /// - **Slot classNames:** [listClassName], [tabClassName], [selectedTabClassName], /// [panelClassName] for each structural region. @@ -71,7 +71,7 @@ class WTabs extends StatelessWidget { /// Utility classes applied to every tab's inner [WDiv]. /// - /// Supports `selected:` prefix tokens — they activate only on the selected tab. + /// Supports `selected:` prefix tokens, they activate only on the selected tab. /// /// Example: `'px-4 py-2 text-sm text-gray-600 selected:text-blue-600'` final String? tabClassName; @@ -125,7 +125,7 @@ class WTabs extends StatelessWidget { @override Widget build(BuildContext context) { - // 1. Parse panel className for debug — delegates the tab-level parse to each tab builder. + // 1. Parse panel className for debug: delegates the tab-level parse to each tab builder. final WindStyle panelStyles = panelClassName != null ? WindParser.parse(panelClassName!, context) : const WindStyle(); diff --git a/lib/src/widgets/w_text.dart b/lib/src/widgets/w_text.dart index a69ad211..c6e8eff2 100644 --- a/lib/src/widgets/w_text.dart +++ b/lib/src/widgets/w_text.dart @@ -305,7 +305,7 @@ class WText extends StatelessWidget { widgetToBuild = Align(alignment: styles.alignment!, child: widgetToBuild); } - // Step E: Flex (Expanded/Flexible) — skipped inside a scrollable main axis. + // Step E: Flex (Expanded/Flexible), skipped inside a scrollable main axis. if (!skipFlexWrap) { if (styles.flex != null) { logger.wrapWith("Expanded", "flex: ${styles.flex}"); diff --git a/llms.txt b/llms.txt index a9aec795..dddd52dc 100644 --- a/llms.txt +++ b/llms.txt @@ -1,6 +1,6 @@ # fluttersdk_wind -> Tailwind CSS for Flutter. A utility-first styling framework that maps `className` strings to optimized Flutter widget trees, with 24-field `WindThemeData` theming, responsive prefixes (`sm:` / `md:` / `lg:` / `xl:` / `2xl:`), `dark:`, state prefixes (`hover:` / `focus:` / `disabled:` / `loading:` / `selected:`), platform prefixes (`ios:` / `android:` / `web:` / `mobile:`), 22 W-prefix widgets, server-driven UI via `WDynamic`, and three AI integration layers (hosted MCP server, Claude Code skill, and a skill distributed to 8+ AI clients via fluttersdk/ai). Open Source. Tailwind syntax. Flutter native. Version 1.1.2 stable. +> Tailwind CSS for Flutter. A utility-first styling framework that maps `className` strings to optimized Flutter widget trees, with 24-field `WindThemeData` theming, responsive prefixes (`sm:` / `md:` / `lg:` / `xl:` / `2xl:`), `dark:`, state prefixes (`hover:` / `focus:` / `disabled:` / `loading:` / `selected:`), platform prefixes (`ios:` / `android:` / `web:` / `mobile:`), 27 W-prefix widgets, `WindRecipe` / `WindSlotRecipe` variant composition, server-driven UI via `WDynamic`, and three AI integration layers (hosted MCP server, Claude Code skill, and a skill distributed to 8+ AI clients via fluttersdk/ai). Open Source. Tailwind syntax. Flutter native. Stable 1.x line. **Stack**: Flutter >=3.27.0 · Dart >=3.4.0 · Runtime deps: `flutter_svg`, `fluttersdk_wind_diagnostics_contracts`. No `mockito`, no state management library, no router. @@ -31,7 +31,7 @@ WDiv( if (kDebugMode) Wind.installDebugResolver(); ``` -**Prefix stack** in className: `::::`. Last class wins; unknown tokens fail silently. Every color token (`bg-` / `text-` / `border-` / `fill-` / `ring-`) carries a `dark:` pair in the same className. +**Prefix stack** in className: `::::`. Last class wins; unknown tokens are dropped and a one-time `kDebugMode` hint names each (release builds stay silent). Every color token (`bg-` / `text-` / `border-` / `fill-` / `ring-` / `shadow-`) carries a `dark:` pair in the same className. **Parser cache key**: `className + activeBreakpoint + brightness + platform + sorted(activeStates)`. Stable across rebuilds; invalidate via `WindParser.clearCache()` in test `setUp` only, never in production. @@ -43,12 +43,15 @@ if (kDebugMode) Wind.installDebugResolver(); - [WText](https://fluttersdk.com/wind/widgets/w-text.md): Text widget with `className`-driven font, color, alignment, decoration. - [WSpacer](https://fluttersdk.com/wind/widgets/w-spacer.md): Flex-aware spacer that obeys responsive prefixes. - [WBreakpoint](https://fluttersdk.com/wind/widgets/w-breakpoint.md): Declarative per-breakpoint widget tree switcher; escape hatch when prefixes are not enough. +- [WCard](https://fluttersdk.com/wind/widgets/w-card.md): Surface container with optional `header` and `footer` slots; all styling `className`-driven. +- [WBadge](https://fluttersdk.com/wind/widgets/w-badge.md): Inline status/label pill; composes a rounded-full `WDiv` around a `WText`. ### Interactive - [WButton](https://fluttersdk.com/wind/widgets/w-button.md): Button with `hover:` / `focus:` / `active:` / `loading:` / `disabled:` state prefixes. - [WAnchor](https://fluttersdk.com/wind/widgets/w-anchor.md): Inline interactive wrapper; auto-wraps `WDiv` / `WButton` when hover/focus prefixes are detected. - [WPopover](https://fluttersdk.com/wind/widgets/w-popover.md): Overlay container with alignment + trigger / content builders. +- [WTabs](https://fluttersdk.com/wind/widgets/w-tabs.md): Controlled tabs; `selected:` state prefix marks the active tab, `panelBuilder` renders the active panel. - [WKeyboardActions](https://fluttersdk.com/wind/widgets/w-keyboard-actions.md): Above-keyboard toolbar with Done button and field navigation for iOS numeric keyboards; platform-targeted via `platform: 'ios'`. - [WindAnimationWrapper](https://fluttersdk.com/wind/widgets/wind-animation-wrapper.md): Low-level animation engine; wraps a child in spin, ping, pulse, or bounce with configurable `duration` and `curve`. Used internally by `animate-*` className tokens. @@ -57,6 +60,8 @@ if (kDebugMode) Wind.installDebugResolver(); - [WInput](https://fluttersdk.com/wind/widgets/w-input.md): TextField with `className` styling + `InputType` (text, email, number, password, multiline, search). - [WSelect](https://fluttersdk.com/wind/widgets/w-select.md): Dropdown with optional search, multi-select, pagination. - [WCheckbox](https://fluttersdk.com/wind/widgets/w-checkbox.md): Checkbox or switch with `className`. +- [WSwitch](https://fluttersdk.com/wind/widgets/w-switch.md): Controlled toggle switch; pushes the `checked:` state prefix, thumb positioned via `justify-*` on the track. +- [WRadio](https://fluttersdk.com/wind/widgets/w-radio.md): Controlled radio button; drives the `selected:` state prefix when `value == groupValue`. - [WDatePicker](https://fluttersdk.com/wind/widgets/w-date-picker.md): Single-date and date-range picker. - [WFormInput](https://fluttersdk.com/wind/widgets/w-form-input.md): Form-bound `WInput` wrapper with validator. - [WFormSelect](https://fluttersdk.com/wind/widgets/w-form-select.md): Form-bound `WSelect` wrapper with validator. @@ -84,12 +89,13 @@ if (kDebugMode) Wind.installDebugResolver(); - [Responsive design](https://fluttersdk.com/wind/core-concepts/responsive-design.md): breakpoint prefixes + `WBreakpoint` escape hatch. - [State management](https://fluttersdk.com/wind/core-concepts/state-management.md): `hover:`, `focus:`, `disabled:`, `loading:`, `selected:`, custom states. - [Dynamic rendering](https://fluttersdk.com/wind/core-concepts/dynamic-rendering.md): `WDynamic` JSON schema + security model. -- [Debugging](https://fluttersdk.com/wind/core-concepts/debugging.md): `WindLogger`, `Wind.installDebugResolver()`, cache inspection. +- [Debugging](https://fluttersdk.com/wind/core-concepts/debugging.md): `WindLogger`, `Wind.installDebugResolver()`, cache inspection, unknown-className debug hint. +- [Recipes](https://fluttersdk.com/wind/styling/wind-recipe.md): `WindRecipe` / `WindSlotRecipe` variant composition (base + axes + compoundVariants + caller className) in strict emission order, no dedupe / sort / twMerge. ## Utility families - [Layout](https://fluttersdk.com/wind/layout/index.md): flex, grid, position, overflow, aspect-ratio, z-index, order. -- [Sizing](https://fluttersdk.com/wind/layout/sizing.md): `w-*`, `h-*`, `min-*`, `max-*`, arbitrary `w-[200px]`. +- [Sizing](https://fluttersdk.com/wind/layout/sizing.md): `w-*`, `h-*`, `size-*` (width + height shorthand), `min-*`, `max-*`, arbitrary `w-[200px]`. - [Spacing](https://fluttersdk.com/wind/layout/spacing.md): padding (`p-*`, `px-*`, `pt-*`), margin (`m-*`, `-mt-*`), gap (`gap-*`, `space-x-*`). - [Typography](https://fluttersdk.com/wind/typography/index.md): `text-*` (size + color + alignment overload), `font-*`, `leading-*`, `tracking-*`, `truncate`. - [Backgrounds](https://fluttersdk.com/wind/styling/background-color.md): `bg-*`, gradients, images. @@ -97,6 +103,7 @@ if (kDebugMode) Wind.installDebugResolver(); - [Shadows](https://fluttersdk.com/wind/styling/shadow.md): `shadow`, `shadow-md`, colored shadows (`shadow-blue-500/20`). - [Opacity](https://fluttersdk.com/wind/styling/opacity.md): `opacity-*` + opacity modifiers (`bg-red-500/50`). - [Transitions & animations](https://fluttersdk.com/wind/interactivity/index.md): `duration-*`, `ease-*`, `animate-spin` / `pulse` / `bounce` / `ping`. +- [Cursor](https://fluttersdk.com/wind/interactivity/cursor.md): `cursor-pointer`, `cursor-not-allowed`, `cursor-text`, grab / zoom / resize families mapped to `SystemMouseCursors` via `MouseRegion`. ## Utility helpers (Dart APIs) diff --git a/skills/wind-ui/SKILL.md b/skills/wind-ui/SKILL.md index ba1f7c63..59744f4f 100644 --- a/skills/wind-ui/SKILL.md +++ b/skills/wind-ui/SKILL.md @@ -219,7 +219,7 @@ Wind hides most boilerplate but never changes Flutter's "constraints down, sizes | Rule | Wrong | Right | |---|---|---| -| **Row children: prefer `flex-1`** (a bare `w-full` now also works, treated as `flex-1`) | — | `WDiv(className: 'flex flex-row', children: [WDiv(className: 'flex-1', ...)])` | +| **Row children: prefer `flex-1`** (a bare `w-full` now also works, treated as `flex-1`) | n/a | `WDiv(className: 'flex flex-row', children: [WDiv(className: 'flex-1', ...)])` | | **Scrollable children use `flex-1`, not `h-full`** | `WDiv(className: 'flex flex-col', children: [WDiv(className: 'overflow-y-auto h-full', ...)])` → unbounded height | `WDiv(className: 'flex flex-col h-full', children: [WDiv(className: 'flex-1 overflow-y-auto', scrollPrimary: true, ...)])` | | **`absolute` requires `relative` parent** | `WDiv(className: 'flex', children: [..., WDiv(className: 'absolute top-0 right-0')])` does not position correctly | `WDiv(className: 'relative flex', children: [..., WDiv(className: 'absolute top-0 right-0')])` | | **`truncate` requires bounded width** | `WText('long...', className: 'truncate')` inside a Row | wrap in `WDiv(className: 'flex-1', child: WText(..., className: 'truncate'))` | @@ -391,7 +391,7 @@ Compact catalog of consistent footguns. Each entry: what's wrong, why, the corre | `WIcon(Icons.settings)` | Filled Material icon (off-brand for Wind) | `WIcon(Icons.settings_outlined)` | | `className: 'flex-wrap'` | Works (aliased to `wrap`), but `wrap` is the canonical token and avoids the debug hint | `'wrap gap-2'` | | `className: 'text-7xl'` | Font scale stops at 6xl; silent no-op | `'text-6xl'` (cap) or use `font-size` via custom theme | -| `className: 'w-full'` inside a Row (works, but `flex-1` is clearer) | — | `'flex-1'` | +| `className: 'w-full'` inside a Row (works, but `flex-1` is clearer) | No failure, just intent clarity | `'flex-1'` | | `className: 'h-full'` inside a scrollable | Vertical viewport unbounded | `'flex-1 overflow-y-auto'` + `scrollPrimary: true` on the constructor | | `className: 'overflow-y-auto'` without `scrollPrimary: true` | iOS tap-to-top broken | add the constructor prop | | `className: 'bg-white'` alone | Missing dark pair = bug | `'bg-white dark:bg-gray-800'` | diff --git a/skills/wind-ui/references/community.md b/skills/wind-ui/references/community.md index 33c5ce67..af9d11ad 100644 --- a/skills/wind-ui/references/community.md +++ b/skills/wind-ui/references/community.md @@ -37,7 +37,7 @@ A genuine wind-side bug per SKILL.md §15. If the symptom matches any of the doc Not-bug-worthy (documented behavior, recover per the cited section): -- Core Law 6 silent-no-op: any unknown token, `text-7xl` / `text-8xl` / `text-9xl`, `flex-wrap`, `ps-*` / `pe-*` / `ms-*` / `me-*`, `-m-N`, `w-auto` / `h-auto`, bare `transition` / `transition-all` / `transition-colors`, `border-dashed` / `border-dotted`, `fixed` / `sticky`, `divide-*`, `cursor-*`, `filter`, `backdrop-blur`, `group-*`, `peer-*`, `@apply`, `!important`. See SKILL.md §4, §5, and `references/tailwind-divergence.md`. +- Core Law 6 silent-no-op: any unknown token, `text-7xl` / `text-8xl` / `text-9xl`, `ps-*` / `pe-*` / `ms-*` / `me-*`, `-m-N`, `w-auto` / `h-auto`, bare `transition` / `transition-all` / `transition-colors`, `border-dashed` / `border-dotted`, `fixed` / `sticky`, `divide-*`, `filter`, `backdrop-blur`, `group-*`, `peer-*`, `@apply`, `!important`. (`flex-wrap` is aliased to `wrap`, and `cursor-*` is a supported utility, so neither is a silent no-op.) See SKILL.md §4, §5, and `references/tailwind-divergence.md`. - `active:` prefix has no effect (Core Law 10: reserved but not wired). Recover by tracking press in consumer state and passing `states: {'pressed'}`. - RenderFlex overflow from `w-full` inside a Row, "Vertical viewport was given unbounded height" from `h-full` inside a scrollable parent, or `absolute` not positioning without a `relative` ancestor (§6 layout rules: Flutter constraint reality, not Wind bugs). - Missing `dark:` peer producing light-only colors in dark mode (Core Law 2: consumer-code bug). diff --git a/skills/wind-ui/references/debug.md b/skills/wind-ui/references/debug.md index 3d01f6d5..74f195b2 100644 --- a/skills/wind-ui/references/debug.md +++ b/skills/wind-ui/references/debug.md @@ -1,4 +1,4 @@ -# Wind 1.0 — Debug bridge, parser cache, logger +# Wind 1.1: Debug bridge, parser cache, logger Wiring `Wind.installDebugResolver()` for E2E tooling (Dusk, Telescope, Playwright), understanding the parser cache (and why tests need `WindParser.clearCache()`), and reading `WindLogger` output for performance / composition debugging. @@ -136,9 +136,9 @@ Output (sample): ``` What it shows: -1. **Composition tree** — pseudo-Dart pseudo-code of the widget hierarchy Wind constructs (Container → Padding → Column / Row / etc.). -2. **Final WindStyle** — the immutable value object after all parsers have run. -3. **Build time** — microseconds from className-receive to widget-return. +1. **Composition tree**: pseudo-Dart pseudo-code of the widget hierarchy Wind constructs (Container → Padding → Column / Row / etc.). +2. **Final WindStyle**: the immutable value object after all parsers have run. +3. **Build time**: microseconds from className-receive to widget-return. The `debug` token does NOT draw visual borders or wireframes. For visual debugging, reach for Flutter's `debugPaintSizeEnabled = true` (set in `main()` inside `kDebugMode`). diff --git a/skills/wind-ui/references/dynamic.md b/skills/wind-ui/references/dynamic.md index 97aad379..1e8319a4 100644 --- a/skills/wind-ui/references/dynamic.md +++ b/skills/wind-ui/references/dynamic.md @@ -1,4 +1,4 @@ -# Wind 1.0 — WDynamic (server-driven UI) +# Wind 1.1: WDynamic (server-driven UI) JSON node tree → Wind widget tree. Reach for this file when rendering UI from a CMS, A/B framework, remote-config service, or any source where the widget shape is decided at runtime. @@ -37,9 +37,9 @@ Every node is a `Map` with one required field and two optional } ``` -- `type` (required, String) — the widget type name. Must match an allowed type (§2) or a registered custom builder (§7), or the node renders an error box. -- `props` (optional, `Map`) — widget constructor arguments. Keys are widget-prop names exactly (no auto-rename; see §4). -- `children` (optional, `List`) — nested nodes. Recursively processed depth-first. +- `type` (required, String): the widget type name. Must match an allowed type (§2) or a registered custom builder (§7), or the node renders an error box. +- `props` (optional, `Map`): widget constructor arguments. Keys are widget-prop names exactly (no auto-rename; see §4). +- `children` (optional, `List`): nested nodes. Recursively processed depth-first. All `Map` values are deep-converted to `Map` internally; mixed `Map` payloads from JSON parsers are normalized automatically. @@ -179,11 +179,11 @@ WDynamic( ``` Handler signature is auto-detected in order: -1. `Function(Map, WDynamicState)` — preferred when handler needs to read or mutate state -2. `Function(Map)` — pure action with no state access -3. Fallback `Function.apply(handler, [args, state])` — for handlers with unusual signatures +1. `Function(Map, WDynamicState)`: preferred when handler needs to read or mutate state +2. `Function(Map)`: pure action with no state access +3. Fallback `Function.apply(handler, [args, state])`: for handlers with unusual signatures -Unknown action names log `WindDynamic: Unknown action "" — ignored.` via `debugPrint` and return null. Exceptions inside handlers are caught and logged via `debugPrint('WindDynamic: Action "" error: ')`. Neither crashes the build. +Unknown action names log `WindDynamic: Unknown action "", ignored.` via `debugPrint` and return null. Exceptions inside handlers are caught and logged via `debugPrint('WindDynamic: Action "" error: ')`. Neither crashes the build. Value actions (used by WInput, WCheckbox, WSelect, WDatePicker): - The widget's `onChange` callback is wired through `parseValueAction`. @@ -277,7 +277,7 @@ The `customIcons:` map merges with the built-in defaults; user keys override. Threat model considerations: - **JSON source matters.** A controlled backend rendering a known schema is safe. Untrusted client input is not; even with whitelist enforcement, props can hold dangerous data (URLs that load attacker-controlled assets via `WImage(src:)`, action names that overlap with privileged handlers). - **`builders:` is the escape hatch.** If you don't add custom builders, the surface is bounded by the 29-widget whitelist + the action handlers you registered. Action handlers run consumer Dart code; they are the trust boundary. -- **Action argument coercion is shallow.** `args['_value'] as String?` will succeed if the JSON sends `"_value": "