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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ First stable release. wind is utility-first, Tailwind-syntax styling for Flutter
- **Child order utilities**: `order-0` through `order-12`, `order-first`, `order-last`, `order-none`, plus arbitrary `order-[n]` (including negatives) for reordering flex children without changing source order.
- **Reverse flex direction**: `flex-row-reverse` and `flex-col-reverse` flip the main-axis direction so `justify-start` mirrors per CSS semantics.
- **`self-*` align-self shorthand**: `self-start`, `self-end`, `self-center`, `self-stretch`, `self-auto`, `self-baseline` as aliases for the `align-self-*` long form, matching Tailwind's canonical class name.
- **Flex grow / basis tokens**: `grow` (Tailwind shorthand for `flex-grow`, i.e. `flex: 1`), `grow-0` (no grow), and `basis-1/2` / `basis-1/3` / `basis-1/4` / `basis-full` / `basis-[Npx]`. Basis approximates CSS `flex-basis`: it sets the child's initial MAIN-axis size (width in a row, height in a column) and ignores grow/shrink interplay. The no-grow / no-shrink resets follow last-class-wins: `grow-0` cancels an earlier `grow`/`flex-grow`/`flex-N`, `shrink-0` cancels an earlier `shrink`/`flex-shrink`, and `flex-none` (`flex: 0 0 auto`) cancels both, within the same active class list.
- **Smart column cross-axis stretch**: a `flex flex-col` with no explicit `items-*` token now stretches each `WDiv` child that does not control its own width to the column width (CSS `align-items: stretch` default). Children that self-wrap in `Expanded`/`Flexible` (`grow`, `flex-grow`, `flex-auto`, `flex-initial`, `shrink`, `flex-shrink`, `flex-N`, in any state/breakpoint variant), children with an explicit `w-*` / `min-w-*` / `max-w-*` (including `w-full`), absolute children, and non-`WDiv` children are left untouched. `shrink-0` / `flex-none` children still stretch on the cross axis (their no-shrink effect is main-axis only, matching CSS). Rows are unaffected.
- **Inline color props**: `WDiv.backgroundColor` and `WText.foregroundColor` for runtime-dynamic colors. Override any `bg-*` / `text-*` from className and stay out of the parser cache key.
- **`WBreakpoint` widget**: declarative per-breakpoint widget tree builder. Reach for it when className prefixes are not enough because the widget structure itself changes between breakpoints.
- **`WDynamic`**: JSON-driven widget tree renderer. 13 Wind types + 16 Flutter core types allowed by default, extensible via `builders:`, restrictable via `denyWidgets:`, with `customIcons:` for user-defined icon mappings (24 built-in glyphs). State binding by widget `id`, action dispatch via `WActionHandler`, max recursion depth 50.
Expand All @@ -55,6 +57,7 @@ First stable release. wind is utility-first, Tailwind-syntax styling for Flutter

### Fixed

- `WText` bare rendering: a `WText` used outside a `MaterialApp` / `Scaffold` now renders with a brightness-aware baseline color (`Colors.white` on dark platforms, `Colors.black` on light, read from `MediaQuery.platformBrightness`) instead of Flutter's debug yellow-underline fallback. When no `Directionality` ancestor exists, `WText` injects one defaulting to `TextDirection.ltr`. Explicitly supplied colors (`className text-*`, `foregroundColor`, `textStyle`) still win and are unaffected.
- Background image parser (`bg-[/abs/path]`): the `FileImage(File(...))` branch is now guarded by `kIsWeb`; on web, where `dart:io` `File` is unsupported, the image degrades gracefully (skipped) instead of throwing at runtime. Non-web behavior is unchanged. `pubspec.yaml` now declares explicit platform support (`android`, `ios`, `macos`, `web`, `linux`, `windows`) so pub.dev platform detection is not narrowed by the `dart:io` import graph.
- WASM compatibility: removed `dart:io` from the library import graph (`platform_service.dart` now uses `defaultTargetPlatform`; absolute-path `bg-[/...]` image resolution moved behind a conditional import). The package is now `is:wasm-ready`, raising the pana/pub.dev platform-support score to 20/20 (160/160). (#95)
- `max-w-prose`: corrected value from 1040 px (65 × 16, an incorrect approximation) to 512 px, matching the actual parser output. Docs and skill references updated accordingly.
Expand All @@ -65,6 +68,7 @@ First stable release. wind is utility-first, Tailwind-syntax styling for Flutter
- `WindParser.parse`: cache is bypassed in both directions (no read, no write) when `baseStyle` is non-null so per-call styles do not return stale cached entries or poison the cache slot for default-flag callers.
- `example/lib/routes.dart`: six widget routes (`/widgets/w-input-multiline`, `/widgets/w-input-search`, `/widgets/w-popover-alignment`, `/widgets/w-select_multi`, `/widgets/w-select_single`, `/widgets/w-text-transform`) renamed to snake_case to match their page-file basenames so live doc iframes at `wind.fluttersdk.com/preview/widgets/<key>` resolve. Two dead pages (`layout/grid_basic`, `layout/order`) without documentation references were removed.
- `BackgroundParser`: `bg-[#hex]` arbitrary-color backgrounds no longer also resolve to a bogus `AssetImage("assets/#hex")`. The image regex now excludes `#`-leading bracket values so a hex literal is parsed only as a color, eliminating a stray failed asset fetch on every arbitrary-hex background.
- `flex-none`: now means CSS `flex: 0 0 auto` (no grow AND no shrink). It no longer maps to a shrinking `FlexFit.loose`; instead it routes through the same no-shrink path as `shrink-0`, so a `flex-none` child in a `justify-between` row keeps its intrinsic size instead of being forced into a `Flexible` shrink allocation.
- `WindStyle.copyWith`: a padding-, margin-, or text-only style keeps `decoration == null` instead of fabricating an empty `BoxDecoration`. This stops `WDiv`/`WText` from wrapping a needless `Container` around non-decorated content. `shadow-none` likewise no longer forces a Container.
- `WDynamicRenderer`: malformed JSON degrades gracefully. A non-string `type` or non-list `children` is coerced defensively (routed through the whitelist / treated as no children) instead of throwing an implicit-downcast `TypeError` out of `build()`.
- `WindThemeData`: implements value-based `operator ==` and `hashCode`. The equality guards in `WindThemeController.setTheme` and `_WindThemeState.didUpdateWidget` now compare by value, so a fresh default `WindThemeData()` on a parent rebuild no longer clobbers a prior `toggleTheme()` choice or triggers spurious full-tree rebuilds.
Expand Down
9 changes: 7 additions & 2 deletions doc/layout/flexbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ Controls how children are distributed along the **cross axis** (Vertical for `ro
WDiv(className: 'flex items-center h-20')
```

> **Column default — smart cross-axis stretch.** A `flex flex-col` with no explicit `items-*` token stretches each `WDiv` child that does not control its own width to the column width, matching CSS `align-items: stretch`. 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`), absolute children, and non-`WDiv` children. `shrink-0` / `flex-none` children still stretch on the cross axis (`flex-shrink` is main-axis only, matching CSS). Add any `items-*` token (e.g. `items-start`) to turn this 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.

<a name="align-content"></a>
## Align Content

Expand Down Expand Up @@ -257,11 +259,14 @@ Control how individual children resize to fill available space.
| Class | Description |
|:------|:------------|
| `flex-1` | Allow child to grow and fill available space (`Expanded`). |
| `flex-grow` | Alias for `flex-1`. |
| `flex-grow` / `grow` | Alias for `flex-1` (Tailwind v3 and v4 names). |
| `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. |
| `flex-none` | Do not grow or shrink. |
| `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]`). |

<x-preview path="layout/flex_grow" size="md" source="example/lib/pages/layout/flex_grow.dart"></x-preview>

Expand Down
182 changes: 180 additions & 2 deletions example/lib/pages/layout/flex_grow.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,103 @@ class FlexGrowExamplePage extends StatelessWidget {
],
),
),
ExampleSection(
title: 'grow vs grow-0',
description:
'grow is an alias for flex-1 (Expanded). grow-0 keeps the item at its natural size and refuses to expand.',
child: WDiv(
className: '''
flex gap-2 p-2 rounded-lg
bg-white dark:bg-slate-800
''',
children: const [
_FactorBox(
label: 'grow-0\n(stays fixed)',
factorClass: 'grow-0 w-20',
color: 'bg-slate-400 dark:bg-slate-600',
),
_FactorBox(
label: 'grow\n(fills rest)',
factorClass: 'grow',
color: 'bg-green-500 dark:bg-green-600',
),
],
),
),
ExampleSection(
title: 'basis-* (flex-basis)',
description:
'basis-* sets the initial main-axis size before grow/shrink apply. basis-1/2 takes half, basis-1/3 takes a third, basis-full spans the whole axis.',
child: WDiv(
className: 'flex flex-col gap-2',
children: const [
_BasisRow(
left: 'basis-1/3',
leftClass: 'basis-1/3',
leftColor: 'bg-violet-400 dark:bg-violet-600',
right: 'basis-2/3 (remainder)',
rightClass: 'grow',
rightColor: 'bg-violet-200 dark:bg-violet-800',
),
_BasisRow(
left: 'basis-1/2',
leftClass: 'basis-1/2',
leftColor: 'bg-indigo-400 dark:bg-indigo-600',
right: 'basis-1/2',
rightClass: 'basis-1/2',
rightColor: 'bg-indigo-200 dark:bg-indigo-800',
),
_BasisRow(
left: 'basis-[80px]',
leftClass: 'basis-[80px]',
leftColor: 'bg-sky-400 dark:bg-sky-600',
right: 'grow (fills rest)',
rightClass: 'grow',
rightColor: 'bg-sky-200 dark:bg-sky-800',
),
],
),
),
ExampleSection(
title: 'Smart Column Stretch',
description:
'A flex flex-col stretches each WDiv child to the column width by default (no w-full needed). Add items-start to shrink children to content size.',
child: WDiv(
className: 'flex flex-col gap-4',
children: const [
_StretchDemo(
label: 'Default: children fill column width',
containerClass: 'flex flex-col gap-2',
showItemsStart: false,
),
_StretchDemo(
label: 'items-start: children size to content',
containerClass: 'flex flex-col gap-2 items-start',
showItemsStart: true,
),
],
),
),
ExampleSection(
title: 'Quick Reference',
description: 'Five tokens cover the bulk of flex sizing scenarios.',
description: 'Eight tokens cover the bulk of flex sizing scenarios.',
child: WDiv(
className: 'flex flex-col gap-1',
children: const [
_RefRow(cls: 'flex-1', desc: 'Expanded() — grow to fill'),
_RefRow(
cls: 'flex-{n}',
desc: 'Specific flex factor (n is any integer)'),
_RefRow(cls: 'flex-grow', desc: 'Alias of flex-1'),
_RefRow(cls: 'flex-grow / grow', desc: 'Alias of flex-1'),
_RefRow(cls: 'grow-0', desc: 'Keep intrinsic size, no growing'),
_RefRow(cls: 'shrink', desc: 'Allow shrinking (FlexFit.loose)'),
_RefRow(
cls: 'shrink-0', desc: 'Preserve intrinsic size (no wrap)'),
_RefRow(cls: 'flex-none', desc: 'Do not grow or shrink'),
_RefRow(
cls: 'basis-1/2 / basis-1/3 / basis-full',
desc: 'Fractional flex-basis'),
_RefRow(cls: 'basis-[Npx]', desc: 'Fixed flex-basis in pixels'),
],
),
),
Expand Down Expand Up @@ -164,3 +246,99 @@ class _RefRow extends StatelessWidget {
);
}
}

class _BasisRow extends StatelessWidget {
final String left;
final String leftClass;
final String leftColor;
final String right;
final String rightClass;
final String rightColor;

const _BasisRow({
required this.left,
required this.leftClass,
required this.leftColor,
required this.right,
required this.rightClass,
required this.rightColor,
});

@override
Widget build(BuildContext context) {
return WDiv(
className: 'flex gap-1',
children: [
WDiv(
className:
'$leftClass $leftColor h-14 rounded flex items-center justify-center',
child: WText(
left,
className: 'font-mono text-xs font-bold text-white text-center',
),
),
WDiv(
className:
'$rightClass $rightColor h-14 rounded flex items-center justify-center',
child: WText(
right,
className: 'font-mono text-xs font-bold text-white/80 text-center',
),
),
],
);
}
}

class _StretchDemo extends StatelessWidget {
final String label;
final String containerClass;
final bool showItemsStart;

const _StretchDemo({
required this.label,
required this.containerClass,
required this.showItemsStart,
});

@override
Widget build(BuildContext context) {
return WDiv(
className: 'flex flex-col gap-2',
children: [
WText(
label,
className: 'text-xs font-mono text-slate-500 dark:text-slate-400',
),
WDiv(
className: '''
$containerClass p-2 rounded-lg
bg-slate-100 dark:bg-slate-700
''',
children: [
WDiv(
className: '''
px-3 py-2 rounded
bg-orange-400 dark:bg-orange-600
''',
child: WText(
'Nav bar',
className: 'text-white font-semibold text-sm',
),
),
WDiv(
className: '''
px-3 py-2 rounded
bg-orange-300 dark:bg-orange-500
''',
child: WText(
'Content card',
className: 'text-white text-sm',
),
),
],
),
],
);
}
}
Loading