Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.

## [Unreleased]

## [1.2.0] - 2026-07-08

### Changed

- Wind's internal `flex` layout is now fully intrinsic-safe: the column cross-axis stretch and the `basis-*` resolution no longer use a `LayoutBuilder`. Column stretch is a real render object (`WindCrossStretch`) and fractional `basis-*` resolves against the flex's own extent via `WindMainExtentProvider`/`WindFractionBasis`. A `flex flex-col` (with or without `basis-*`) now renders inside an `items-stretch` grid cell, under a Flutter `IntrinsicHeight`/`IntrinsicWidth`, or in a `Table` cell without the `LayoutBuilder does not support returning intrinsic dimensions` assert (the web `_owner != null` cascade that produced). (`lib/src/widgets/w_div.dart`, `lib/src/widgets/wind_equal_height_row.dart`) (WIND-4)
Expand All @@ -26,18 +28,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<String, String>` (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<T>`: 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<String, String>` (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<T>`: 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)

Expand All @@ -46,6 +48,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
Expand Down Expand Up @@ -177,7 +184,8 @@ Production deps: `flutter` (SDK), `flutter_svg ^2.0.0`, `fluttersdk_wind_diagnos

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.

[Unreleased]: https://github.com/fluttersdk/wind/compare/1.1.2...HEAD
[Unreleased]: https://github.com/fluttersdk/wind/compare/1.2.0...HEAD
[1.2.0]: https://github.com/fluttersdk/wind/releases/tag/1.2.0
[1.1.2]: https://github.com/fluttersdk/wind/releases/tag/1.1.2
[1.1.1]: https://github.com/fluttersdk/wind/releases/tag/1.1.1
[1.1.0]: https://github.com/fluttersdk/wind/releases/tag/1.1.0
Expand Down
2 changes: 1 addition & 1 deletion dartdoc_options.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
dartdoc:
linkTo:
url: 'https://github.com/fluttersdk/wind/blob/1.1.2/%f%#L%l%'
url: 'https://github.com/fluttersdk/wind/blob/1.2.0/%f%#L%l%'
17 changes: 17 additions & 0 deletions doc/core-concepts/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<a name="introduction"></a>
Expand Down Expand Up @@ -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.

<a name="unknown-classname-warnings"></a>
## 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`.

<a name="external-tooling-integration"></a>
## External Tooling Integration

Expand Down
2 changes: 1 addition & 1 deletion doc/core-concepts/dynamic-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<a name="error-handling"></a>
## Error Handling
Expand Down
4 changes: 2 additions & 2 deletions doc/core-concepts/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 Windfrom 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.

<a name="the-windtheme-widget"></a>
## The WindTheme Widget
Expand Down Expand Up @@ -54,7 +54,7 @@ void main() {
<a name="theme-change-callbacks"></a>
## 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 changesit 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(
Expand Down
2 changes: 1 addition & 1 deletion doc/core-concepts/utility-first.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` objectan 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]
Expand Down
4 changes: 2 additions & 2 deletions doc/interactivity/cursor.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,5 @@ WDiv(className: 'cursor-pointer bg-white dark:bg-gray-800')
<a name="related-documentation"></a>
## 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.
Loading