Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.

---

## [Unreleased]

### Fixed

- **`max-h-*` and `max-w-*` now apply to an `h-full` element.** Both were discarded, by the same mechanism in two places: the cap arrived as a `ConstrainedBox` whose additional constraint `BoxConstraints.enforce` clamps into the incoming range, and the incoming range was already tight. `h-full max-h-[120px]` under a `ConstrainedBox(maxHeight: 400)` rendered 400 and now renders 120; `w-1/2 h-full max-w-[100px]` in a 300 pixel parent rendered 150 and now renders 100. A TIGHT parent still wins over `max-h-*`, which is correct rather than the same bug: a tight constraint is the parent stating an exact size.
- **`h-full` no longer throws under an `IntrinsicHeight`.** It resolved through a `LayoutBuilder`, which cannot answer an intrinsic query, so any `IntrinsicHeight` / `IntrinsicWidth` above it asserted `LayoutBuilder does not support returning intrinsic dimensions`. The limitation was documented on five surfaces with an escape hatch ("use explicit `h-*` instead") rather than fixed. `h-full` is now the `WindFullHeightBox` render object, which answers intrinsics by forwarding to its child, so it renders under an `IntrinsicHeight`, in a `Table` cell and in an `items-stretch` grid cell, and matches the tallest sibling rather than reporting the screen height. `grid` is the one remaining `LayoutBuilder` path.

### Changed

- **`h-full` resolves at the render layer instead of through a `LayoutBuilder`.** The question it asks ("is the incoming height bounded") is only answerable during layout, and a `LayoutBuilder` was the widget-layer way to ask it; a `LayoutBuilder` also defers its whole subtree into a second layout pass. A consumer measured 1056 of them in one eight-scroll session against 258 widget builds, one per element carrying the class, re-run every frame. `WindFullHeightBox` reads `constraints` directly and needs neither. Behaviour is otherwise unchanged, pinned by twelve characterisation tests written against the old implementation first.

### Quality

- Twenty tests for `WindFullHeightBox`, none skipped, covering: bounded and unbounded, with and without a width factor, `max-w-*` and `max-h-*`, the outer box's own reported size, in-place updates through all four setters (including a screen-size change, which is what a rotation is), a childless element, and the dry-layout contract agreeing with the size actually laid out. Line coverage 94.5% to 95.2%. Every line of the new file is covered, including both null-child branches: a childless `WDiv` carrying only `h-full` builds no core structure and reaches the box with a null child, so an earlier `coverage:ignore` on those lines rested on a false premise.

## [1.5.1] - 2026-09-07

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion doc/layout/flexbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ WDiv(className: 'flex items-center h-20')

> **Column cross-axis stretch (default AND explicit `items-stretch`).** A `flex flex-col` with no explicit `items-*` token, OR with an explicit `items-stretch`, stretches each `WDiv`, `WAnchor` (any child), and `WButton` child that does not control its own width to the column width, matching CSS `align-items: stretch`. Explicit `items-stretch` therefore equalizes child widths (every eligible child fills the column), closing the asymmetry with `grid ... items-stretch`. For `WAnchor`: when the anchor wraps a `WDiv`, the inner `WDiv`'s className decides (so `WAnchor > WDiv(w-32)` keeps 128 px; a `WAnchor > WDiv` with a self-flex token is excluded just as a direct self-flexing `WDiv` is); when the anchor wraps a `WText` or raw widget, the anchor stretches by policy so its tap surface fills the column. Left untouched: children with an explicit width (`w-*` / `min-w-*` / `max-w-*` / `w-full`, in any state/breakpoint variant), children that self-wrap in `Expanded`/`Flexible` (`grow`, `flex-grow`, `flex-auto`, `flex-initial`, `shrink`, `flex-shrink`, `flex-N`), `basis-*` children, absolute children, bare `WText` leaves, and raw Flutter widgets. `shrink-0` / `flex-none` children still stretch on the cross axis (`flex-shrink` is main-axis only, matching CSS). Add `items-start` / `items-center` / `items-end` to turn stretch off and let children size to content. Rows are never auto-stretched on the cross axis. When the column itself sits in an unbounded-width context (a bare `Row` slot, `UnconstrainedBox`, horizontal scroll), the stretch safely falls back to content-sized children instead of forcing an infinite width.

> **Layout stability: wind's flex is intrinsic-safe.** Flutter's `IntrinsicHeight` and `IntrinsicWidth` perform an intrinsic-dimension pass that reads child sizes mid-layout, and a `LayoutBuilder` on that path asserts `LayoutBuilder does not support returning intrinsic dimensions`. Wind's flex uses NO `LayoutBuilder`: column cross-axis stretch is a real render object (`WindCrossStretch`), and `basis-*` resolves against the flex's own extent via a `WindMainExtentProvider` (see `lib/src/widgets/w_div.dart` and `wind_equal_height_row.dart`). So a `flex flex-col` (with or without `basis-*`) renders correctly inside an `items-stretch` grid cell, under an `IntrinsicHeight`/`IntrinsicWidth`, or in a `Table` cell without asserting. For a connector, rail, or divider that must fill the cross axis to match the tallest sibling, prefer a `Stack` with a `Positioned(top: 0, bottom: 0)` line, or use wind's own `items-stretch` column (also intrinsic-free and animation-safe).
> **Layout stability: wind's flex is intrinsic-safe.** Flutter's `IntrinsicHeight` and `IntrinsicWidth` perform an intrinsic-dimension pass that reads child sizes mid-layout, and a `LayoutBuilder` on that path asserts `LayoutBuilder does not support returning intrinsic dimensions`. Wind's flex uses NO `LayoutBuilder`, and neither does `h-full` since it became the `WindFullHeightBox` render object: column cross-axis stretch is a real render object (`WindCrossStretch`), and `basis-*` resolves against the flex's own extent via a `WindMainExtentProvider` (see `lib/src/widgets/w_div.dart` and `wind_equal_height_row.dart`). So a `flex flex-col` (with or without `basis-*`) renders correctly inside an `items-stretch` grid cell, under an `IntrinsicHeight`/`IntrinsicWidth`, or in a `Table` cell without asserting. For a connector, rail, or divider that must fill the cross axis to match the tallest sibling, prefer a `Stack` with a `Positioned(top: 0, bottom: 0)` line, or use wind's own `items-stretch` column (also intrinsic-free and animation-safe).
>
> ```dart
> // Safe connector pattern: Stack + Positioned, no IntrinsicHeight
Expand Down
2 changes: 1 addition & 1 deletion doc/layout/grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ WDiv(
)
```

The equal-height rows are laid out for real (each cell is measured with a loose height, then re-laid to at least the row's tallest via a **min** height, never a tight squeeze), NOT via `IntrinsicHeight`, so cells whose content is itself a `flex flex-col`, or that use `h-full` / `basis-*` (which carry a `LayoutBuilder`), stretch correctly instead of asserting `LayoutBuilder does not support returning intrinsic dimensions`. Because a cell is never forced below its own content height, a stretched cell also produces no residual `RenderFlex overflowed` warning (#141).
The equal-height rows are laid out for real (each cell is measured with a loose height, then re-laid to at least the row's tallest via a **min** height, never a tight squeeze), NOT via `IntrinsicHeight`, so cells whose content is itself a `flex flex-col`, or that use `h-full` / `basis-*` (neither of which carries a `LayoutBuilder` any more), stretch correctly instead of asserting `LayoutBuilder does not support returning intrinsic dimensions`. Because a cell is never forced below its own content height, a stretched cell also produces no residual `RenderFlex overflowed` warning (#141).
Comment thread
coderabbitai[bot] marked this conversation as resolved.

<a name="responsive"></a>
## Responsive
Expand Down
26 changes: 12 additions & 14 deletions doc/layout/sizing.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,30 +177,28 @@ Wrapping Wind content in `IntrinsicHeight` or `IntrinsicWidth` (or placing it in
LayoutBuilder does not support returning intrinsic dimensions.
```

**Why.** To resolve some sizes against the parent's real constraints, Wind introduces a `LayoutBuilder` (see `lib/src/widgets/w_div.dart`): `h-full` adds one around the cell only when the incoming height is unbounded, and a flex `basis-*` adds a single one around the surrounding flex when any direct child uses `basis-*`. `LayoutBuilder` runs during the layout phase, not the intrinsic-sizing phase, so an intrinsic-dimension query that passes through one of those `LayoutBuilder` paths asserts. This is a fundamental Flutter constraint (`LayoutBuilder` genuinely cannot answer intrinsics), not a Wind bug. Wind content that hits none of Wind's `LayoutBuilder` paths (`h-full` in an unbounded height, `basis-*`, or the column cross-axis stretch above) carries no `LayoutBuilder` and is safe to wrap.
**Why.** To resolve a size against the parent's real constraints, Wind used to introduce a `LayoutBuilder`, and a `LayoutBuilder` runs during the layout phase rather than the intrinsic-sizing phase, so any intrinsic query passing through one asserts. That is a Flutter constraint, not a Wind bug.

**What triggers it.** A `WDiv` (or any W-widget) whose `className` resolves `h-full` or a flex `basis-*`, anywhere inside the subtree you wrap in `IntrinsicHeight` / `IntrinsicWidth`. A `Row` of cards that you try to equalize with `IntrinsicHeight` is the common case.
**What still triggers it: `grid` only.** `grid-cols-*` composes a `Wrap` inside a `LayoutBuilder` (it needs the available width to compute a column width), so a `grid` anywhere inside the subtree you wrap in `IntrinsicHeight` / `IntrinsicWidth` still asserts.

**Escape hatches.**
**What no longer does.** `h-full` and flex `basis-*` are both intrinsic-safe now. `h-full` resolves through the `WindFullHeightBox` render object, `basis-*` through `WindMainExtentProvider`, and the column cross-axis stretch through `WindCrossStretch`; a render object answers intrinsic queries, so all three render under an `IntrinsicHeight`, in a `Table` cell, or in an `items-stretch` grid cell without throwing. If you carry an escape hatch for `h-full` from an earlier version, you can drop it.

- Prefer explicit sizing: give the cells a fixed `h-*` (or `size-*`) instead of `h-full` + `IntrinsicHeight`.
- Do not wrap Wind content that uses `h-full` / `basis-*` in `IntrinsicHeight` / `IntrinsicWidth`.
**Escape hatches, for the `grid` case that remains.**

- Prefer explicit sizing: give the cells a fixed `h-*` (or `size-*`) instead of a `grid` + `IntrinsicHeight`.
- For an equal-height row, use a `Stack` with a `Positioned(top: 0, bottom: 0)` element for the part that must fill, or reserve equal content so natural heights already match.
- Wind's own column cross-axis stretch (`items-stretch`, the `flex flex-col` default) equalizes width WITHOUT you wrapping it in `IntrinsicHeight`. It uses a `LayoutBuilder` + `SizedBox(width: double.infinity)` internally (gated on a bounded width), so treat it as a REPLACEMENT for `IntrinsicHeight`, not something to nest inside one.
- Wind's own `items-stretch` grid equalizes row heights with real layout rather than `IntrinsicHeight`, so reach for it INSTEAD of wrapping.

```dart
// Throws if a card resolves h-full / basis-* internally:
// Throws: the wrapped subtree contains a grid.
IntrinsicHeight(
child: WDiv(className: 'flex flex-row', children: cards),
child: WDiv(className: 'grid grid-cols-2 gap-4', children: cards),
)

// Safe: explicit height on each cell, no IntrinsicHeight needed.
// Safe: items-stretch equalizes the row heights with real layout.
WDiv(
className: 'flex flex-row gap-4',
children: [
WDiv(className: 'h-40 ...', child: card1),
WDiv(className: 'h-40 ...', child: card2),
],
className: 'grid grid-cols-2 gap-4 items-stretch',
children: cards,
)
```

Expand Down
Loading
Loading