hazo_ui 6.4.0 → 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 +43 -0
- package/README.md +42 -0
- package/dist/index.cjs +210 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -2
- package/dist/index.d.ts +42 -2
- package/dist/index.js +211 -69
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGE_LOG.md
CHANGED
|
@@ -5,6 +5,49 @@ 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
|
+
|
|
8
51
|
## v6.4.0 (2026-08-01)
|
|
9
52
|
|
|
10
53
|
### Added — in-column sections for `HazoUiKanban`
|
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 |
|
package/dist/index.cjs
CHANGED
|
@@ -9091,10 +9091,15 @@ function KanbanCard({
|
|
|
9091
9091
|
isDragging
|
|
9092
9092
|
} = sortable.useSortable({
|
|
9093
9093
|
id: item.id,
|
|
9094
|
-
// sectionKey
|
|
9095
|
-
// section, not just its column (
|
|
9096
|
-
// the gaps between cards, but the cards themselves
|
|
9097
|
-
|
|
9094
|
+
// sectionKey and laneKey ride along so a drop *onto a card* resolves to
|
|
9095
|
+
// that card's section and lane, not just its column (the section bands
|
|
9096
|
+
// and lane rows cover the gaps between cards, but the cards themselves
|
|
9097
|
+
// are the sortable).
|
|
9098
|
+
data: {
|
|
9099
|
+
columnKey: item.columnKey,
|
|
9100
|
+
sectionKey: item.sectionKey,
|
|
9101
|
+
laneKey: item.laneKey
|
|
9102
|
+
}
|
|
9098
9103
|
});
|
|
9099
9104
|
const priority_var = item.priority ? `--hazo-kanban-priority-${String(item.priority).toLowerCase()}` : null;
|
|
9100
9105
|
const style = {
|
|
@@ -9155,8 +9160,16 @@ function KanbanCard({
|
|
|
9155
9160
|
}
|
|
9156
9161
|
);
|
|
9157
9162
|
}
|
|
9158
|
-
function
|
|
9159
|
-
return `
|
|
9163
|
+
function tree_droppable_id(prefix, tree_key, lane_key, rest) {
|
|
9164
|
+
return lane_key == null ? `${prefix}:${tree_key}::${rest}` : `${prefix}:${tree_key}::${lane_key}::${rest}`;
|
|
9165
|
+
}
|
|
9166
|
+
function section_droppable_id(column_key, section_key, tree_key, lane_key) {
|
|
9167
|
+
return tree_droppable_id(
|
|
9168
|
+
"section",
|
|
9169
|
+
tree_key,
|
|
9170
|
+
lane_key,
|
|
9171
|
+
`${column_key}::${section_key}`
|
|
9172
|
+
);
|
|
9160
9173
|
}
|
|
9161
9174
|
function group_items_by_section(items, sections) {
|
|
9162
9175
|
const bySection = /* @__PURE__ */ new Map();
|
|
@@ -9176,11 +9189,13 @@ function KanbanColumn({
|
|
|
9176
9189
|
emptyColumn,
|
|
9177
9190
|
cardClassName,
|
|
9178
9191
|
showEdit,
|
|
9179
|
-
onEditRequest
|
|
9192
|
+
onEditRequest,
|
|
9193
|
+
laneKey,
|
|
9194
|
+
treeKey
|
|
9180
9195
|
}) {
|
|
9181
9196
|
const { setNodeRef, isOver } = core$1.useDroppable({
|
|
9182
|
-
id:
|
|
9183
|
-
data: { columnKey: column.key, isColumn: true }
|
|
9197
|
+
id: tree_droppable_id("column", treeKey, laneKey, column.key),
|
|
9198
|
+
data: { columnKey: column.key, laneKey, isColumn: true }
|
|
9184
9199
|
});
|
|
9185
9200
|
const sections = column.sections;
|
|
9186
9201
|
const { bySection, unmatched } = React27__namespace.useMemo(
|
|
@@ -9226,6 +9241,8 @@ function KanbanColumn({
|
|
|
9226
9241
|
KanbanColumnHead,
|
|
9227
9242
|
{
|
|
9228
9243
|
columnKey: column.key,
|
|
9244
|
+
laneKey,
|
|
9245
|
+
treeKey,
|
|
9229
9246
|
title: column.title,
|
|
9230
9247
|
count: items.length,
|
|
9231
9248
|
droppable: sections != null
|
|
@@ -9241,6 +9258,8 @@ function KanbanColumn({
|
|
|
9241
9258
|
KanbanSectionBand,
|
|
9242
9259
|
{
|
|
9243
9260
|
columnKey: column.key,
|
|
9261
|
+
laneKey,
|
|
9262
|
+
treeKey,
|
|
9244
9263
|
section,
|
|
9245
9264
|
items: bySection.get(section.key) ?? [],
|
|
9246
9265
|
emptyColumn,
|
|
@@ -9253,6 +9272,8 @@ function KanbanColumn({
|
|
|
9253
9272
|
KanbanSectionStrip,
|
|
9254
9273
|
{
|
|
9255
9274
|
columnKey: column.key,
|
|
9275
|
+
laneKey,
|
|
9276
|
+
treeKey,
|
|
9256
9277
|
section
|
|
9257
9278
|
},
|
|
9258
9279
|
section.key
|
|
@@ -9266,13 +9287,15 @@ function KanbanColumn({
|
|
|
9266
9287
|
}
|
|
9267
9288
|
function KanbanColumnHead({
|
|
9268
9289
|
columnKey,
|
|
9290
|
+
laneKey,
|
|
9291
|
+
treeKey,
|
|
9269
9292
|
title,
|
|
9270
9293
|
count,
|
|
9271
9294
|
droppable
|
|
9272
9295
|
}) {
|
|
9273
9296
|
const { setNodeRef, isOver } = core$1.useDroppable({
|
|
9274
|
-
id:
|
|
9275
|
-
data: { columnKey, isColumn: true },
|
|
9297
|
+
id: tree_droppable_id("columnhead", treeKey, laneKey, columnKey),
|
|
9298
|
+
data: { columnKey, laneKey, isColumn: true },
|
|
9276
9299
|
disabled: !droppable
|
|
9277
9300
|
});
|
|
9278
9301
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -9293,14 +9316,16 @@ function KanbanColumnHead({
|
|
|
9293
9316
|
}
|
|
9294
9317
|
function KanbanSectionBand({
|
|
9295
9318
|
columnKey,
|
|
9319
|
+
laneKey,
|
|
9320
|
+
treeKey,
|
|
9296
9321
|
section,
|
|
9297
9322
|
items,
|
|
9298
9323
|
emptyColumn,
|
|
9299
9324
|
renderCards
|
|
9300
9325
|
}) {
|
|
9301
9326
|
const { setNodeRef, isOver } = core$1.useDroppable({
|
|
9302
|
-
id: section_droppable_id(columnKey, section.key),
|
|
9303
|
-
data: { columnKey, sectionKey: section.key, isSection: true }
|
|
9327
|
+
id: section_droppable_id(columnKey, section.key, treeKey, laneKey),
|
|
9328
|
+
data: { columnKey, laneKey, sectionKey: section.key, isSection: true }
|
|
9304
9329
|
});
|
|
9305
9330
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9306
9331
|
"div",
|
|
@@ -9323,11 +9348,13 @@ function KanbanSectionBand({
|
|
|
9323
9348
|
}
|
|
9324
9349
|
function KanbanSectionStrip({
|
|
9325
9350
|
columnKey,
|
|
9351
|
+
laneKey,
|
|
9352
|
+
treeKey,
|
|
9326
9353
|
section
|
|
9327
9354
|
}) {
|
|
9328
9355
|
const { setNodeRef, isOver } = core$1.useDroppable({
|
|
9329
|
-
id: section_droppable_id(columnKey, section.key),
|
|
9330
|
-
data: { columnKey, sectionKey: section.key, isSection: true }
|
|
9356
|
+
id: section_droppable_id(columnKey, section.key, treeKey, laneKey),
|
|
9357
|
+
data: { columnKey, laneKey, sectionKey: section.key, isSection: true }
|
|
9331
9358
|
});
|
|
9332
9359
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
9333
9360
|
"div",
|
|
@@ -9341,6 +9368,54 @@ function KanbanSectionStrip({
|
|
|
9341
9368
|
}
|
|
9342
9369
|
);
|
|
9343
9370
|
}
|
|
9371
|
+
function lane_droppable_id(lane_key, tree_key) {
|
|
9372
|
+
return `lane:${tree_key}::${lane_key}`;
|
|
9373
|
+
}
|
|
9374
|
+
function KanbanLaneHead({
|
|
9375
|
+
lane,
|
|
9376
|
+
count,
|
|
9377
|
+
treeKey
|
|
9378
|
+
}) {
|
|
9379
|
+
const { setNodeRef, isOver } = core$1.useDroppable({
|
|
9380
|
+
id: lane_droppable_id(lane.key, treeKey),
|
|
9381
|
+
data: { laneKey: lane.key, isLane: true }
|
|
9382
|
+
});
|
|
9383
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9384
|
+
"div",
|
|
9385
|
+
{
|
|
9386
|
+
ref: setNodeRef,
|
|
9387
|
+
className: cn(
|
|
9388
|
+
"cls_hazo_kanban_lane_head mb-2 flex items-center gap-3 rounded-sm px-1 py-1",
|
|
9389
|
+
isOver && "cls_hazo_kanban_lane_head_over bg-accent/30 ring-1 ring-ring/40"
|
|
9390
|
+
),
|
|
9391
|
+
children: [
|
|
9392
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_title text-xs font-semibold uppercase tracking-wider text-foreground", children: lane.title }),
|
|
9393
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_rule h-px flex-1 bg-border" }),
|
|
9394
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_count rounded-full bg-muted px-2 py-0.5 text-[11px] tabular-nums text-foreground", children: count })
|
|
9395
|
+
]
|
|
9396
|
+
}
|
|
9397
|
+
);
|
|
9398
|
+
}
|
|
9399
|
+
function KanbanLaneStrip({
|
|
9400
|
+
lane,
|
|
9401
|
+
treeKey
|
|
9402
|
+
}) {
|
|
9403
|
+
const { setNodeRef, isOver } = core$1.useDroppable({
|
|
9404
|
+
id: lane_droppable_id(lane.key, treeKey),
|
|
9405
|
+
data: { laneKey: lane.key, isLane: true }
|
|
9406
|
+
});
|
|
9407
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
9408
|
+
"div",
|
|
9409
|
+
{
|
|
9410
|
+
ref: setNodeRef,
|
|
9411
|
+
className: cn(
|
|
9412
|
+
"cls_hazo_kanban_lane_strip mb-2 rounded-md border border-dashed border-border px-2 py-1 text-[11px] uppercase tracking-wider text-muted-foreground",
|
|
9413
|
+
isOver && "cls_hazo_kanban_lane_strip_over border-solid bg-accent/30 text-foreground ring-1 ring-ring/30"
|
|
9414
|
+
),
|
|
9415
|
+
children: lane.title
|
|
9416
|
+
}
|
|
9417
|
+
);
|
|
9418
|
+
}
|
|
9344
9419
|
var DEFAULT_PRIORITIES = ["P0", "P1", "P2", "P3"];
|
|
9345
9420
|
function humanize(key) {
|
|
9346
9421
|
return key.replace(/[_\-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\s+/g, " ").trim().split(" ").map((w) => w ? w[0].toUpperCase() + w.slice(1) : w).join(" ");
|
|
@@ -9747,6 +9822,10 @@ function applyKanbanFilter(items, filter) {
|
|
|
9747
9822
|
return true;
|
|
9748
9823
|
});
|
|
9749
9824
|
}
|
|
9825
|
+
var UNMATCHED_LANE_KEY = "__hazo_lane_unmatched__";
|
|
9826
|
+
function bucket_key(lane_key, column_key) {
|
|
9827
|
+
return lane_key == null ? column_key : `${lane_key}\0${column_key}`;
|
|
9828
|
+
}
|
|
9750
9829
|
function default_announcements(itemLabel) {
|
|
9751
9830
|
return {
|
|
9752
9831
|
onDragStart: ({ item, fromColumn }) => `Picked up ${itemLabel(item)}. Currently in ${fromColumn}. Use arrow keys to choose a different column. Press space to drop, escape to cancel.`,
|
|
@@ -9755,38 +9834,50 @@ function default_announcements(itemLabel) {
|
|
|
9755
9834
|
onDragCancel: ({ item, fromColumn }) => `Movement cancelled. ${itemLabel(item)} returned to ${fromColumn}.`
|
|
9756
9835
|
};
|
|
9757
9836
|
}
|
|
9758
|
-
function group_items_by_column(items, overlay, columns) {
|
|
9837
|
+
function group_items_by_column(items, overlay, columns, lanes) {
|
|
9759
9838
|
const out = /* @__PURE__ */ new Map();
|
|
9760
|
-
|
|
9839
|
+
const lane_keys = lanes ? [...lanes.map((l) => l.key), UNMATCHED_LANE_KEY] : [void 0];
|
|
9840
|
+
for (const lane_key of lane_keys)
|
|
9841
|
+
for (const col of columns) out.set(bucket_key(lane_key, col.key), []);
|
|
9761
9842
|
const overlay_inserts = /* @__PURE__ */ new Map();
|
|
9762
|
-
for (const
|
|
9843
|
+
for (const key of out.keys()) overlay_inserts.set(key, []);
|
|
9844
|
+
const declared = lanes ? new Set(lanes.map((l) => l.key)) : null;
|
|
9845
|
+
function resolve_lane(lane_key) {
|
|
9846
|
+
if (!declared) return void 0;
|
|
9847
|
+
return lane_key != null && declared.has(lane_key) ? lane_key : UNMATCHED_LANE_KEY;
|
|
9848
|
+
}
|
|
9763
9849
|
for (const item of items) {
|
|
9764
9850
|
const ov = overlay.get(item.id);
|
|
9765
9851
|
if (ov) {
|
|
9766
|
-
const bucket = overlay_inserts.get(
|
|
9852
|
+
const bucket = overlay_inserts.get(
|
|
9853
|
+
bucket_key(resolve_lane(ov.laneKey), ov.columnKey)
|
|
9854
|
+
);
|
|
9767
9855
|
if (bucket) {
|
|
9768
|
-
const placed = ov.sectionKey === item.sectionKey ? item : { ...item, sectionKey: ov.sectionKey };
|
|
9856
|
+
const placed = ov.sectionKey === item.sectionKey && ov.laneKey === item.laneKey ? item : { ...item, sectionKey: ov.sectionKey, laneKey: ov.laneKey };
|
|
9769
9857
|
bucket.push({ item: placed, index: ov.index });
|
|
9770
9858
|
}
|
|
9771
9859
|
} else {
|
|
9772
|
-
const bucket = out.get(
|
|
9860
|
+
const bucket = out.get(
|
|
9861
|
+
bucket_key(resolve_lane(item.laneKey), item.columnKey)
|
|
9862
|
+
);
|
|
9773
9863
|
if (bucket) bucket.push(item);
|
|
9774
9864
|
}
|
|
9775
9865
|
}
|
|
9776
|
-
for (const
|
|
9777
|
-
|
|
9866
|
+
for (const [key, inserts] of overlay_inserts) {
|
|
9867
|
+
if (inserts.length === 0) continue;
|
|
9778
9868
|
inserts.sort((a, b) => a.index - b.index);
|
|
9779
|
-
const arr = out.get(
|
|
9869
|
+
const arr = out.get(key) ?? [];
|
|
9780
9870
|
for (const { item, index } of inserts) {
|
|
9781
9871
|
const at = Math.max(0, Math.min(index, arr.length));
|
|
9782
9872
|
arr.splice(at, 0, item);
|
|
9783
9873
|
}
|
|
9784
|
-
out.set(
|
|
9874
|
+
out.set(key, arr);
|
|
9785
9875
|
}
|
|
9786
9876
|
return out;
|
|
9787
9877
|
}
|
|
9788
9878
|
function HazoUiKanban({
|
|
9789
9879
|
columns,
|
|
9880
|
+
lanes,
|
|
9790
9881
|
items,
|
|
9791
9882
|
renderCard,
|
|
9792
9883
|
onMove,
|
|
@@ -9834,7 +9925,7 @@ function HazoUiKanban({
|
|
|
9834
9925
|
const next = new Map(prev);
|
|
9835
9926
|
for (const [item_id, pos] of prev.entries()) {
|
|
9836
9927
|
const item = items.find((i) => i.id === item_id);
|
|
9837
|
-
if (item && item.columnKey === pos.columnKey && item.sectionKey === pos.sectionKey) {
|
|
9928
|
+
if (item && item.columnKey === pos.columnKey && item.sectionKey === pos.sectionKey && item.laneKey === pos.laneKey) {
|
|
9838
9929
|
next.delete(item_id);
|
|
9839
9930
|
changed = true;
|
|
9840
9931
|
}
|
|
@@ -9843,8 +9934,8 @@ function HazoUiKanban({
|
|
|
9843
9934
|
});
|
|
9844
9935
|
}, [items, overlay]);
|
|
9845
9936
|
const grouped = React27__namespace.useMemo(
|
|
9846
|
-
() => group_items_by_column(items, overlay, columns),
|
|
9847
|
-
[items, overlay, columns]
|
|
9937
|
+
() => group_items_by_column(items, overlay, columns, lanes),
|
|
9938
|
+
[items, overlay, columns, lanes]
|
|
9848
9939
|
);
|
|
9849
9940
|
const find_item = React27__namespace.useCallback(
|
|
9850
9941
|
(id) => items.find((i) => i.id === id),
|
|
@@ -9854,6 +9945,13 @@ function HazoUiKanban({
|
|
|
9854
9945
|
core$1.useSensor(core$1.PointerSensor, { activationConstraint: { distance: 5 } }),
|
|
9855
9946
|
core$1.useSensor(core$1.KeyboardSensor, { coordinateGetter: sortable.sortableKeyboardCoordinates })
|
|
9856
9947
|
);
|
|
9948
|
+
const collision_detection = React27__namespace.useCallback((args) => {
|
|
9949
|
+
const under_pointer = core$1.pointerWithin(args);
|
|
9950
|
+
if (under_pointer.length > 0) return under_pointer;
|
|
9951
|
+
const overlapping = core$1.rectIntersection(args);
|
|
9952
|
+
if (overlapping.length > 0) return overlapping;
|
|
9953
|
+
return core$1.closestCorners(args);
|
|
9954
|
+
}, []);
|
|
9857
9955
|
const merged_announcements = React27__namespace.useMemo(() => {
|
|
9858
9956
|
const defaults = default_announcements(label_fn);
|
|
9859
9957
|
return {
|
|
@@ -9882,7 +9980,7 @@ function HazoUiKanban({
|
|
|
9882
9980
|
const id = String(active.id);
|
|
9883
9981
|
const item = find_item(id);
|
|
9884
9982
|
if (!item) return "";
|
|
9885
|
-
const over_col_key = over ? over.data?.current?.columnKey ??
|
|
9983
|
+
const over_col_key = over ? over.data?.current?.columnKey ?? item.columnKey : null;
|
|
9886
9984
|
const col = over_col_key ? columns.find((c) => c.key === over_col_key) : null;
|
|
9887
9985
|
return merged_announcements.onDragOver({
|
|
9888
9986
|
item,
|
|
@@ -9897,10 +9995,13 @@ function HazoUiKanban({
|
|
|
9897
9995
|
const item = find_item(id);
|
|
9898
9996
|
if (!item) return "";
|
|
9899
9997
|
const from_col = columns.find((c) => c.key === item.columnKey);
|
|
9900
|
-
const over_col_key = over
|
|
9998
|
+
const over_col_key = over?.data?.current?.columnKey ?? item.columnKey;
|
|
9999
|
+
const over_lane_key = lanes ? over?.data?.current?.laneKey ?? item.laneKey : void 0;
|
|
9901
10000
|
const to_col = over_col_key ? columns.find((c) => c.key === over_col_key) ?? from_col : from_col;
|
|
9902
|
-
const target =
|
|
9903
|
-
|
|
10001
|
+
const target = grouped.get(
|
|
10002
|
+
bucket_key(over_lane_key, over_col_key ?? item.columnKey)
|
|
10003
|
+
) ?? [];
|
|
10004
|
+
const on_shell = !over || String(over.id).startsWith("column:") || String(over.id).startsWith("columnhead:") || String(over.id).startsWith("section:") || String(over.id).startsWith("lane:");
|
|
9904
10005
|
const new_index = on_shell ? target.length : target.findIndex((it) => it.id === String(over.id));
|
|
9905
10006
|
return merged_announcements.onDragEnd({
|
|
9906
10007
|
item,
|
|
@@ -9920,7 +10021,7 @@ function HazoUiKanban({
|
|
|
9920
10021
|
});
|
|
9921
10022
|
}
|
|
9922
10023
|
}),
|
|
9923
|
-
[find_item, columns, grouped, merged_announcements]
|
|
10024
|
+
[find_item, columns, lanes, grouped, merged_announcements]
|
|
9924
10025
|
);
|
|
9925
10026
|
const handle_drag_start = React27__namespace.useCallback((event) => {
|
|
9926
10027
|
set_active_id(String(event.active.id));
|
|
@@ -9947,31 +10048,35 @@ function HazoUiKanban({
|
|
|
9947
10048
|
if (!event.over) return;
|
|
9948
10049
|
const over_id = String(event.over.id);
|
|
9949
10050
|
const over_data = event.over.data?.current;
|
|
9950
|
-
const dest_column_key = over_data?.columnKey ??
|
|
10051
|
+
const dest_column_key = over_data?.columnKey ?? item.columnKey;
|
|
9951
10052
|
const dest_column = columns.find((c) => c.key === dest_column_key);
|
|
9952
10053
|
if (!dest_column) return;
|
|
10054
|
+
const dest_lane_key = lanes ? over_data?.laneKey ?? item.laneKey : void 0;
|
|
10055
|
+
const dest_lane = lanes?.find((l) => l.key === dest_lane_key);
|
|
10056
|
+
if (lanes && dest_lane?.accepts && !dest_lane.accepts(item)) return;
|
|
9953
10057
|
const dest_section_key = dest_column.sections ? over_data?.sectionKey ?? item.sectionKey : void 0;
|
|
9954
10058
|
const dest_section = dest_column.sections?.find(
|
|
9955
10059
|
(s) => s.key === dest_section_key
|
|
9956
10060
|
);
|
|
9957
10061
|
if (dest_column.accepts && !dest_column.accepts(item)) return;
|
|
9958
10062
|
if (dest_section?.accepts && !dest_section.accepts(item)) return;
|
|
9959
|
-
const dest_items_pre_overlay = grouped.get(dest_column_key) ?? [];
|
|
10063
|
+
const dest_items_pre_overlay = grouped.get(bucket_key(dest_lane_key, dest_column_key)) ?? [];
|
|
9960
10064
|
let new_index;
|
|
9961
|
-
if (over_data?.isColumn || over_data?.isSection) {
|
|
10065
|
+
if (over_data?.isColumn || over_data?.isSection || over_data?.isLane) {
|
|
9962
10066
|
new_index = dest_items_pre_overlay.length;
|
|
9963
10067
|
} else {
|
|
9964
10068
|
const idx = dest_items_pre_overlay.findIndex((i) => i.id === over_id);
|
|
9965
10069
|
new_index = idx === -1 ? dest_items_pre_overlay.length : idx;
|
|
9966
10070
|
}
|
|
9967
10071
|
const handle = make_revert_handle(active_id_str);
|
|
9968
|
-
if (item.columnKey === dest_column_key && item.sectionKey === dest_section_key) {
|
|
10072
|
+
if (item.columnKey === dest_column_key && item.sectionKey === dest_section_key && item.laneKey === dest_lane_key) {
|
|
9969
10073
|
if (active_id_str === over_id) return;
|
|
9970
10074
|
set_overlay((prev) => {
|
|
9971
10075
|
const next = new Map(prev);
|
|
9972
10076
|
next.set(active_id_str, {
|
|
9973
10077
|
columnKey: dest_column_key,
|
|
9974
10078
|
sectionKey: dest_section_key,
|
|
10079
|
+
laneKey: dest_lane_key,
|
|
9975
10080
|
index: new_index
|
|
9976
10081
|
});
|
|
9977
10082
|
return next;
|
|
@@ -9981,6 +10086,7 @@ function HazoUiKanban({
|
|
|
9981
10086
|
item,
|
|
9982
10087
|
columnKey: dest_column_key,
|
|
9983
10088
|
sectionKey: dest_section_key,
|
|
10089
|
+
laneKey: dest_lane_key,
|
|
9984
10090
|
newIndex: new_index,
|
|
9985
10091
|
revert: handle.revert
|
|
9986
10092
|
};
|
|
@@ -9992,6 +10098,7 @@ function HazoUiKanban({
|
|
|
9992
10098
|
next.set(active_id_str, {
|
|
9993
10099
|
columnKey: dest_column_key,
|
|
9994
10100
|
sectionKey: dest_section_key,
|
|
10101
|
+
laneKey: dest_lane_key,
|
|
9995
10102
|
index: new_index
|
|
9996
10103
|
});
|
|
9997
10104
|
return next;
|
|
@@ -10003,12 +10110,14 @@ function HazoUiKanban({
|
|
|
10003
10110
|
toColumn: dest_column_key,
|
|
10004
10111
|
fromSection: item.sectionKey,
|
|
10005
10112
|
toSection: dest_section_key,
|
|
10113
|
+
fromLane: item.laneKey,
|
|
10114
|
+
toLane: dest_lane_key,
|
|
10006
10115
|
newIndex: new_index,
|
|
10007
10116
|
revert: handle.revert
|
|
10008
10117
|
};
|
|
10009
10118
|
onMove?.(move_event);
|
|
10010
10119
|
},
|
|
10011
|
-
[find_item, columns, grouped, onMove, onReorder, make_revert_handle]
|
|
10120
|
+
[find_item, columns, lanes, grouped, onMove, onReorder, make_revert_handle]
|
|
10012
10121
|
);
|
|
10013
10122
|
const handle_drag_cancel = React27__namespace.useCallback(() => {
|
|
10014
10123
|
set_active_id(null);
|
|
@@ -10019,13 +10128,56 @@ function HazoUiKanban({
|
|
|
10019
10128
|
`(max-width: ${Math.max(0, mobileBreakpoint - 1)}px)`
|
|
10020
10129
|
);
|
|
10021
10130
|
const mobile_classes = is_custom_breakpoint ? is_mobile_via_query ? "block" : "hidden" : "sm:hidden";
|
|
10022
|
-
const desktop_classes = is_custom_breakpoint ? is_mobile_via_query ? "hidden" : "grid" : "hidden sm:grid";
|
|
10131
|
+
const desktop_classes = is_custom_breakpoint ? is_mobile_via_query ? "hidden" : lanes ? "block" : "grid" : lanes ? "hidden sm:block" : "hidden sm:grid";
|
|
10132
|
+
const grid_style = {
|
|
10133
|
+
gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`
|
|
10134
|
+
};
|
|
10135
|
+
function lane_total(lane_key) {
|
|
10136
|
+
let total = 0;
|
|
10137
|
+
for (const col of columns)
|
|
10138
|
+
total += (grouped.get(bucket_key(lane_key, col.key)) ?? []).length;
|
|
10139
|
+
return total;
|
|
10140
|
+
}
|
|
10141
|
+
function render_column(col, tree_key, lane_key) {
|
|
10142
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
10143
|
+
KanbanColumn,
|
|
10144
|
+
{
|
|
10145
|
+
column: col,
|
|
10146
|
+
laneKey: lane_key,
|
|
10147
|
+
treeKey: tree_key,
|
|
10148
|
+
items: grouped.get(bucket_key(lane_key, col.key)) ?? [],
|
|
10149
|
+
renderCard,
|
|
10150
|
+
emptyColumn,
|
|
10151
|
+
cardClassName,
|
|
10152
|
+
showEdit: show_edit,
|
|
10153
|
+
onEditRequest: handle_edit_request
|
|
10154
|
+
},
|
|
10155
|
+
col.key
|
|
10156
|
+
);
|
|
10157
|
+
}
|
|
10158
|
+
function render_lane_row(lane, tree_key, only_column) {
|
|
10159
|
+
const count = only_column ? (grouped.get(bucket_key(lane.key, only_column.key)) ?? []).length : lane_total(lane.key);
|
|
10160
|
+
if (lane.collapseWhenEmpty && count === 0) {
|
|
10161
|
+
return /* @__PURE__ */ jsxRuntime.jsx(KanbanLaneStrip, { lane, treeKey: tree_key }, lane.key);
|
|
10162
|
+
}
|
|
10163
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cls_hazo_kanban_lane", children: [
|
|
10164
|
+
/* @__PURE__ */ jsxRuntime.jsx(KanbanLaneHead, { lane, count, treeKey: tree_key }),
|
|
10165
|
+
only_column ? render_column(only_column, tree_key, lane.key) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_lane_grid grid gap-3", style: grid_style, children: columns.map((col) => render_column(col, tree_key, lane.key)) })
|
|
10166
|
+
] }, lane.key);
|
|
10167
|
+
}
|
|
10168
|
+
function render_unmatched_lane(tree_key, only_column) {
|
|
10169
|
+
const count = only_column ? (grouped.get(bucket_key(UNMATCHED_LANE_KEY, only_column.key)) ?? []).length : lane_total(UNMATCHED_LANE_KEY);
|
|
10170
|
+
if (count === 0) return null;
|
|
10171
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_lane cls_hazo_kanban_lane_unmatched", children: only_column ? render_column(only_column, tree_key, UNMATCHED_LANE_KEY) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_lane_grid grid gap-3", style: grid_style, children: columns.map(
|
|
10172
|
+
(col) => render_column(col, tree_key, UNMATCHED_LANE_KEY)
|
|
10173
|
+
) }) });
|
|
10174
|
+
}
|
|
10023
10175
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10024
10176
|
core$1.DndContext,
|
|
10025
10177
|
{
|
|
10026
10178
|
id: dnd_context_id,
|
|
10027
10179
|
sensors,
|
|
10028
|
-
collisionDetection:
|
|
10180
|
+
collisionDetection: collision_detection,
|
|
10029
10181
|
onDragStart: handle_drag_start,
|
|
10030
10182
|
onDragEnd: handle_drag_end,
|
|
10031
10183
|
onDragCancel: handle_drag_cancel,
|
|
@@ -10034,43 +10186,33 @@ function HazoUiKanban({
|
|
|
10034
10186
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("cls_hazo_kanban w-full", className), children: [
|
|
10035
10187
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("cls_hazo_kanban_mobile", mobile_classes), children: /* @__PURE__ */ jsxRuntime.jsxs(Tabs, { value: active_tab, onValueChange: set_active_tab, children: [
|
|
10036
10188
|
/* @__PURE__ */ jsxRuntime.jsx(TabsList, { className: "grid w-full", style: { gridTemplateColumns: `repeat(${columns.length}, 1fr)` }, children: columns.map((col) => {
|
|
10037
|
-
const count =
|
|
10189
|
+
const count = lanes ? lanes.reduce(
|
|
10190
|
+
(n, lane) => n + (grouped.get(bucket_key(lane.key, col.key)) ?? []).length,
|
|
10191
|
+
(grouped.get(bucket_key(UNMATCHED_LANE_KEY, col.key)) ?? []).length
|
|
10192
|
+
) : (grouped.get(col.key) ?? []).length;
|
|
10038
10193
|
return /* @__PURE__ */ jsxRuntime.jsxs(TabsTrigger, { value: col.key, className: "flex flex-col gap-0.5", children: [
|
|
10039
10194
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs", children: col.title }),
|
|
10040
10195
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: count })
|
|
10041
10196
|
] }, col.key);
|
|
10042
10197
|
}) }),
|
|
10043
|
-
columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(TabsContent, { value: col.key, className: "mt-3", children: /* @__PURE__ */ jsxRuntime.
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
items: grouped.get(col.key) ?? [],
|
|
10048
|
-
renderCard,
|
|
10049
|
-
emptyColumn,
|
|
10050
|
-
cardClassName,
|
|
10051
|
-
showEdit: show_edit,
|
|
10052
|
-
onEditRequest: handle_edit_request
|
|
10053
|
-
}
|
|
10054
|
-
) }, col.key))
|
|
10198
|
+
columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(TabsContent, { value: col.key, className: "mt-3", children: lanes ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cls_hazo_kanban_lanes flex flex-col gap-4", children: [
|
|
10199
|
+
lanes.map((lane) => render_lane_row(lane, "m", col)),
|
|
10200
|
+
render_unmatched_lane("m", col)
|
|
10201
|
+
] }) : render_column(col, "m") }, col.key))
|
|
10055
10202
|
] }) }),
|
|
10056
10203
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10057
10204
|
"div",
|
|
10058
10205
|
{
|
|
10059
|
-
className: cn(
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
showEdit: show_edit,
|
|
10070
|
-
onEditRequest: handle_edit_request
|
|
10071
|
-
},
|
|
10072
|
-
col.key
|
|
10073
|
-
))
|
|
10206
|
+
className: cn(
|
|
10207
|
+
"cls_hazo_kanban_desktop",
|
|
10208
|
+
lanes ? "space-y-4" : "gap-3",
|
|
10209
|
+
desktop_classes
|
|
10210
|
+
),
|
|
10211
|
+
style: lanes ? void 0 : grid_style,
|
|
10212
|
+
children: lanes ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10213
|
+
lanes.map((lane) => render_lane_row(lane, "d")),
|
|
10214
|
+
render_unmatched_lane("d")
|
|
10215
|
+
] }) : columns.map((col) => render_column(col, "d"))
|
|
10074
10216
|
}
|
|
10075
10217
|
),
|
|
10076
10218
|
/* @__PURE__ */ jsxRuntime.jsx(core$1.DragOverlay, { children: active_item ? /* @__PURE__ */ jsxRuntime.jsx(
|