hazo_ui 6.3.1 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGE_LOG.md CHANGED
@@ -5,6 +5,91 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## v6.5.0 (2026-08-02)
9
+
10
+ ### Added — swimlanes for `HazoUiKanban`
11
+
12
+ A board can now declare `lanes`, rendering one full-width row per lane —
13
+ a wide lane heading, then that lane's own copy of the whole column grid. Where
14
+ `sections` (v6.4.0) group *within* a column, lanes group *across* all of them,
15
+ so the second axis can be the row axis instead of the inner one.
16
+
17
+ - `HazoUiKanbanProps.lanes?: KanbanLane[]` — `{ key, title, accepts?,
18
+ collapseWhenEmpty? }`. Cards are placed by the new `KanbanItem.laneKey`.
19
+ Omit `lanes` and nothing changes: existing boards render exactly as before.
20
+ - `KanbanMoveEvent` gained `fromLane`/`toLane`; `KanbanReorderEvent` gained
21
+ `laneKey`. Move-vs-reorder is now decided on all three axes — same column,
22
+ section *and* lane is a reorder; changing any one is a move.
23
+ - **A lane heading is the lane-only drop target.** It carries a lane but no
24
+ column, read as "change the lane, keep the card's column" — the mirror of a
25
+ sectioned column's head ("change the column, keep the section").
26
+ - `collapseWhenEmpty` renders a lane with no cards as a thin full-width dashed
27
+ strip, which is what makes "declare every lane" affordable for a long
28
+ vocabulary. A card whose `laneKey` matches no declared lane renders in an
29
+ unlabelled trailing lane rather than disappearing.
30
+ - The optimistic overlay tracks the destination lane too, so a cross-lane drop
31
+ moves immediately instead of snapping back until the consumer's async commit
32
+ lands.
33
+
34
+ ### Fixed — drops landed in the wrong column on tall or scrolled boards
35
+
36
+ Two long-standing defects in drop resolution, both of which lanes made
37
+ impossible to miss:
38
+
39
+ - **Collision detection is now pointer-first** (`pointerWithin`, falling back
40
+ to `rectIntersection`, then `closestCorners`). `closestCorners` alone
41
+ averages the distance to all four corners, so a column stretched tall by a
42
+ neighbour's card stack scored *worse* than a short, far-away column — a drop
43
+ well inside the right column could resolve to a different one entirely.
44
+ Keyboard drags have no pointer and take the same fallbacks as before.
45
+ - **Droppable ids are scoped per layout tree.** The mobile tab strip and the
46
+ desktop grid both always render (CSS hides one), so every column previously
47
+ registered twice under one id. dnd-kit keys its registry by id, so one node
48
+ won and collisions were measured against the CSS-hidden copy's 0×0 rect.
49
+ Ids now carry the tree ("m"/"d") and, on a laned board, the lane.
50
+
51
+ ## v6.4.0 (2026-08-01)
52
+
53
+ ### Added — in-column sections for `HazoUiKanban`
54
+
55
+ A kanban column can now declare `sections`, splitting its body into
56
+ separator-headed bands so a board can group on two axes at once (e.g. status
57
+ across, tag down) without a mode toggle between them.
58
+
59
+ - `KanbanColumn.sections?: KanbanSection[]` — `{ key, title, accepts?,
60
+ collapseWhenEmpty? }`. Cards are placed by the new
61
+ `KanbanItem.sectionKey`. Omit `sections` and nothing changes: existing
62
+ boards render exactly as before.
63
+ - Each band is its own droppable, so a single drop expresses both column and
64
+ section. `KanbanMoveEvent` gained `fromSection`/`toSection`, and
65
+ `KanbanReorderEvent` gained `sectionKey`.
66
+ - Move-vs-reorder is now decided on both axes — same column *and* same
67
+ section is a reorder; changing either is a move. Unsectioned boards have
68
+ `undefined` on both sides, so this collapses to the previous behaviour.
69
+ - On a sectioned column the **header becomes a droppable** carrying the
70
+ column but no section — the status-only drop target ("move column, keep
71
+ section"). Required, not cosmetic: section bands cover the column body and
72
+ win every `closestCorners` contest, so without it a sectioned column has no
73
+ reachable spot that isn't a band. Unsectioned columns keep an inert header.
74
+ - `collapseWhenEmpty` renders a section with no cards as a thin dashed drop
75
+ strip at the foot of the column. This is what makes "declare every section
76
+ in every column" practical for a long vocabulary: every section stays a
77
+ drop target without filling the column with empty headers.
78
+ - The optimistic overlay tracks the destination section too, so a
79
+ same-column cross-section drop moves immediately rather than snapping back
80
+ until the consumer's async commit lands.
81
+ - Dropping on a column's own padding leaves the card's section alone; a card
82
+ whose `sectionKey` matches no declared section renders in an unlabelled
83
+ trailing group rather than disappearing.
84
+
85
+ New styling hooks (no new CSS variables): `cls_hazo_kanban_section`,
86
+ `_section_head`, `_section_title`, `_section_rule`, `_section_count`,
87
+ `_section_body`, `_section_empty`, `_section_over`, `_section_strips`,
88
+ `cls_hazo_kanban_column_head_over`,
89
+ `_section_strip`, `_section_strip_over`, `_section_unmatched`.
90
+
91
+ Demo: test-app `/kanban` §10.
92
+
8
93
  ## v6.3.1 (2026-07-29)
9
94
 
10
95
  ### Fixed — test-app builds again
package/README.md CHANGED
@@ -3677,6 +3677,48 @@ function Actions({ initial }) {
3677
3677
  event carries `revert()` — call it when your API request fails and the
3678
3678
  overlay snaps back to whatever `items` says.
3679
3679
 
3680
+ ### Grouping: sections (v6.4.0) and swimlanes (v6.5.0)
3681
+
3682
+ A board can group on a second axis, either *inside* each column or *across*
3683
+ all of them. Both are opt-in; omit them and the board is a plain column grid.
3684
+
3685
+ ```tsx
3686
+ // Sections: separator-headed bands inside each column, keyed by item.sectionKey
3687
+ <HazoUiKanban
3688
+ columns={COLUMNS.map(col => ({
3689
+ ...col,
3690
+ sections: TAGS.map(t => ({ key: t, title: t, collapseWhenEmpty: true })),
3691
+ }))}
3692
+ items={items.map(it => ({ ...it, sectionKey: it.tag }))}
3693
+ onMove={(e) => save(e.itemId, { column: e.toColumn, tag: e.toSection })}
3694
+ renderCard={renderCard}
3695
+ />
3696
+
3697
+ // Swimlanes: one full-width row per lane, each with its own column grid,
3698
+ // keyed by item.laneKey
3699
+ <HazoUiKanban
3700
+ columns={COLUMNS}
3701
+ lanes={TAGS.map(t => ({ key: t, title: t, collapseWhenEmpty: true }))}
3702
+ items={items.map(it => ({ ...it, laneKey: it.tag }))}
3703
+ onMove={(e) => save(e.itemId, { column: e.toColumn, tag: e.toLane })}
3704
+ renderCard={renderCard}
3705
+ />
3706
+ ```
3707
+
3708
+ | | Sections | Lanes |
3709
+ |---|---|---|
3710
+ | Declared on | `KanbanColumn.sections` | `HazoUiKanbanProps.lanes` |
3711
+ | Card placed by | `KanbanItem.sectionKey` | `KanbanItem.laneKey` |
3712
+ | Renders as | bands stacked inside one column | full-width rows, each with the whole column grid |
3713
+ | Move event carries | `fromSection`/`toSection` | `fromLane`/`toLane` |
3714
+ | "Keep the other axis" drop target | the column head | the lane heading |
3715
+ | `collapseWhenEmpty` | thin drop strip at the column's foot | full-width drop strip |
3716
+
3717
+ A drop expresses both axes at once, so one gesture can change status and tag
3718
+ together. `accepts(item)` on a column, section or lane rejects a drop into it.
3719
+ Cards whose key matches nothing declared render in an unlabelled trailing
3720
+ group (sections) or lane row (lanes) rather than disappearing.
3721
+
3680
3722
  ### Theming (CSS custom properties)
3681
3723
 
3682
3724
  | Variable | Default | Purpose |