hazo_ui 6.4.0 → 6.7.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/dist/index.cjs CHANGED
@@ -9091,10 +9091,15 @@ function KanbanCard({
9091
9091
  isDragging
9092
9092
  } = sortable.useSortable({
9093
9093
  id: item.id,
9094
- // sectionKey rides along so a drop *onto a card* resolves to that card's
9095
- // section, not just its column (kanban_column.tsx's section bands cover
9096
- // the gaps between cards, but the cards themselves are the sortable).
9097
- data: { columnKey: item.columnKey, sectionKey: item.sectionKey }
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 = {
@@ -9103,6 +9108,12 @@ function KanbanCard({
9103
9108
  backgroundColor: "hsl(var(--hazo-kanban-card-bg))",
9104
9109
  border: `1px solid hsl(var(--hazo-kanban-card-border))`,
9105
9110
  borderLeft: priority_var ? `4px solid hsl(var(${priority_var}))` : `1px solid hsl(var(--hazo-kanban-card-border))`,
9111
+ // Opt-in lift. A card whose background equals the column's (the case for
9112
+ // every palette where --card and --background resolve to the same value)
9113
+ // is separated by nothing but a 1px border, which reads as flat at a
9114
+ // glance. The fallback is `none`, so a consumer that never sets the token
9115
+ // renders exactly as before.
9116
+ boxShadow: "var(--hazo-kanban-card-shadow, none)",
9106
9117
  opacity: isDragging && !dragging ? 0.4 : 1,
9107
9118
  cursor: "grab"
9108
9119
  };
@@ -9111,6 +9122,9 @@ function KanbanCard({
9111
9122
  {
9112
9123
  ref: setNodeRef,
9113
9124
  style,
9125
+ "data-column-key": item.columnKey,
9126
+ "data-lane-key": item.laneKey,
9127
+ "data-section-key": item.sectionKey,
9114
9128
  className: cn(
9115
9129
  "cls_hazo_kanban_card group relative rounded-md p-3 mb-2 select-none",
9116
9130
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
@@ -9155,8 +9169,16 @@ function KanbanCard({
9155
9169
  }
9156
9170
  );
9157
9171
  }
9158
- function section_droppable_id(column_key, section_key) {
9159
- return `section:${column_key}::${section_key}`;
9172
+ function tree_droppable_id(prefix, tree_key, lane_key, rest) {
9173
+ return lane_key == null ? `${prefix}:${tree_key}::${rest}` : `${prefix}:${tree_key}::${lane_key}::${rest}`;
9174
+ }
9175
+ function section_droppable_id(column_key, section_key, tree_key, lane_key) {
9176
+ return tree_droppable_id(
9177
+ "section",
9178
+ tree_key,
9179
+ lane_key,
9180
+ `${column_key}::${section_key}`
9181
+ );
9160
9182
  }
9161
9183
  function group_items_by_section(items, sections) {
9162
9184
  const bySection = /* @__PURE__ */ new Map();
@@ -9174,13 +9196,16 @@ function KanbanColumn({
9174
9196
  items,
9175
9197
  renderCard,
9176
9198
  emptyColumn,
9199
+ footer,
9177
9200
  cardClassName,
9178
9201
  showEdit,
9179
- onEditRequest
9202
+ onEditRequest,
9203
+ laneKey,
9204
+ treeKey
9180
9205
  }) {
9181
9206
  const { setNodeRef, isOver } = core$1.useDroppable({
9182
- id: `column:${column.key}`,
9183
- data: { columnKey: column.key, isColumn: true }
9207
+ id: tree_droppable_id("column", treeKey, laneKey, column.key),
9208
+ data: { columnKey: column.key, laneKey, isColumn: true }
9184
9209
  });
9185
9210
  const sections = column.sections;
9186
9211
  const { bySection, unmatched } = React27__namespace.useMemo(
@@ -9217,6 +9242,8 @@ function KanbanColumn({
9217
9242
  "div",
9218
9243
  {
9219
9244
  ref: setNodeRef,
9245
+ "data-column-key": column.key,
9246
+ "data-lane-key": laneKey,
9220
9247
  className: cn(
9221
9248
  "cls_hazo_kanban_column flex flex-col rounded-md p-2",
9222
9249
  isOver && "cls_hazo_kanban_column_over bg-accent/30 ring-2 ring-ring/40"
@@ -9226,6 +9253,8 @@ function KanbanColumn({
9226
9253
  KanbanColumnHead,
9227
9254
  {
9228
9255
  columnKey: column.key,
9256
+ laneKey,
9257
+ treeKey,
9229
9258
  title: column.title,
9230
9259
  count: items.length,
9231
9260
  droppable: sections != null
@@ -9236,49 +9265,66 @@ function KanbanColumn({
9236
9265
  {
9237
9266
  items: ordered.map((i) => i.id),
9238
9267
  strategy: sortable.verticalListSortingStrategy,
9239
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_column_body flex-1 min-h-[40px]", children: !sections ? items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_column_empty text-xs text-muted-foreground py-2 px-1", children: emptyColumn }) : render_cards(items) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9240
- expanded_sections.map((section) => /* @__PURE__ */ jsxRuntime.jsx(
9241
- KanbanSectionBand,
9242
- {
9243
- columnKey: column.key,
9244
- section,
9245
- items: bySection.get(section.key) ?? [],
9246
- emptyColumn,
9247
- renderCards: render_cards
9248
- },
9249
- section.key
9250
- )),
9251
- unmatched.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_section cls_hazo_kanban_section_unmatched", children: render_cards(unmatched) }),
9252
- collapsed_sections.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_section_strips mt-2 flex flex-wrap gap-1 border-t border-dashed border-border pt-2", children: collapsed_sections.map((section) => /* @__PURE__ */ jsxRuntime.jsx(
9253
- KanbanSectionStrip,
9254
- {
9255
- columnKey: column.key,
9256
- section
9257
- },
9258
- section.key
9259
- )) })
9260
- ] }) })
9268
+ children: /* @__PURE__ */ jsxRuntime.jsx(
9269
+ "div",
9270
+ {
9271
+ "data-column-key": column.key,
9272
+ "data-lane-key": laneKey,
9273
+ className: "cls_hazo_kanban_column_body flex-1 min-h-[40px]",
9274
+ children: !sections ? items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_column_empty text-xs text-muted-foreground py-2 px-1", children: emptyColumn }) : render_cards(items) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9275
+ expanded_sections.map((section) => /* @__PURE__ */ jsxRuntime.jsx(
9276
+ KanbanSectionBand,
9277
+ {
9278
+ columnKey: column.key,
9279
+ laneKey,
9280
+ treeKey,
9281
+ section,
9282
+ items: bySection.get(section.key) ?? [],
9283
+ emptyColumn,
9284
+ renderCards: render_cards
9285
+ },
9286
+ section.key
9287
+ )),
9288
+ unmatched.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_section cls_hazo_kanban_section_unmatched", children: render_cards(unmatched) }),
9289
+ collapsed_sections.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_section_strips mt-2 flex flex-wrap gap-1 border-t border-dashed border-border pt-2", children: collapsed_sections.map((section) => /* @__PURE__ */ jsxRuntime.jsx(
9290
+ KanbanSectionStrip,
9291
+ {
9292
+ columnKey: column.key,
9293
+ laneKey,
9294
+ treeKey,
9295
+ section
9296
+ },
9297
+ section.key
9298
+ )) })
9299
+ ] })
9300
+ }
9301
+ )
9261
9302
  }
9262
- )
9303
+ ),
9304
+ footer ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_column_footer mt-1", children: footer }) : null
9263
9305
  ]
9264
9306
  }
9265
9307
  );
9266
9308
  }
9267
9309
  function KanbanColumnHead({
9268
9310
  columnKey,
9311
+ laneKey,
9312
+ treeKey,
9269
9313
  title,
9270
9314
  count,
9271
9315
  droppable
9272
9316
  }) {
9273
9317
  const { setNodeRef, isOver } = core$1.useDroppable({
9274
- id: `columnhead:${columnKey}`,
9275
- data: { columnKey, isColumn: true },
9318
+ id: tree_droppable_id("columnhead", treeKey, laneKey, columnKey),
9319
+ data: { columnKey, laneKey, isColumn: true },
9276
9320
  disabled: !droppable
9277
9321
  });
9278
9322
  return /* @__PURE__ */ jsxRuntime.jsxs(
9279
9323
  "div",
9280
9324
  {
9281
9325
  ref: droppable ? setNodeRef : void 0,
9326
+ "data-column-key": columnKey,
9327
+ "data-lane-key": laneKey,
9282
9328
  className: cn(
9283
9329
  "cls_hazo_kanban_column_head mb-2 flex items-center justify-between rounded-sm px-1 text-xs uppercase tracking-wider text-muted-foreground",
9284
9330
  droppable && "py-1",
@@ -9293,14 +9339,16 @@ function KanbanColumnHead({
9293
9339
  }
9294
9340
  function KanbanSectionBand({
9295
9341
  columnKey,
9342
+ laneKey,
9343
+ treeKey,
9296
9344
  section,
9297
9345
  items,
9298
9346
  emptyColumn,
9299
9347
  renderCards
9300
9348
  }) {
9301
9349
  const { setNodeRef, isOver } = core$1.useDroppable({
9302
- id: section_droppable_id(columnKey, section.key),
9303
- data: { columnKey, sectionKey: section.key, isSection: true }
9350
+ id: section_droppable_id(columnKey, section.key, treeKey, laneKey),
9351
+ data: { columnKey, laneKey, sectionKey: section.key, isSection: true }
9304
9352
  });
9305
9353
  return /* @__PURE__ */ jsxRuntime.jsxs(
9306
9354
  "div",
@@ -9323,11 +9371,13 @@ function KanbanSectionBand({
9323
9371
  }
9324
9372
  function KanbanSectionStrip({
9325
9373
  columnKey,
9374
+ laneKey,
9375
+ treeKey,
9326
9376
  section
9327
9377
  }) {
9328
9378
  const { setNodeRef, isOver } = core$1.useDroppable({
9329
- id: section_droppable_id(columnKey, section.key),
9330
- data: { columnKey, sectionKey: section.key, isSection: true }
9379
+ id: section_droppable_id(columnKey, section.key, treeKey, laneKey),
9380
+ data: { columnKey, laneKey, sectionKey: section.key, isSection: true }
9331
9381
  });
9332
9382
  return /* @__PURE__ */ jsxRuntime.jsx(
9333
9383
  "div",
@@ -9341,6 +9391,58 @@ function KanbanSectionStrip({
9341
9391
  }
9342
9392
  );
9343
9393
  }
9394
+ function lane_droppable_id(lane_key, tree_key) {
9395
+ return `lane:${tree_key}::${lane_key}`;
9396
+ }
9397
+ function KanbanLaneHead({
9398
+ lane,
9399
+ count,
9400
+ treeKey
9401
+ }) {
9402
+ const { setNodeRef, isOver } = core$1.useDroppable({
9403
+ id: lane_droppable_id(lane.key, treeKey),
9404
+ data: { laneKey: lane.key, isLane: true }
9405
+ });
9406
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9407
+ "div",
9408
+ {
9409
+ ref: setNodeRef,
9410
+ className: cn(
9411
+ "cls_hazo_kanban_lane_head mb-2 flex items-center gap-3 rounded-sm px-1 py-1",
9412
+ isOver && "cls_hazo_kanban_lane_head_over bg-accent/30 ring-1 ring-ring/40"
9413
+ ),
9414
+ children: [
9415
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_title text-xs font-semibold uppercase tracking-wider text-foreground", children: lane.title }),
9416
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_rule h-px flex-1 bg-border" }),
9417
+ lane.actions ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_actions flex items-center gap-1", children: lane.actions }) : null,
9418
+ /* @__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 })
9419
+ ]
9420
+ }
9421
+ );
9422
+ }
9423
+ function KanbanLaneStrip({
9424
+ lane,
9425
+ treeKey
9426
+ }) {
9427
+ const { setNodeRef, isOver } = core$1.useDroppable({
9428
+ id: lane_droppable_id(lane.key, treeKey),
9429
+ data: { laneKey: lane.key, isLane: true }
9430
+ });
9431
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9432
+ "div",
9433
+ {
9434
+ ref: setNodeRef,
9435
+ className: cn(
9436
+ "cls_hazo_kanban_lane_strip mb-2 flex items-center gap-3 rounded-md border border-dashed border-border px-2 py-1 text-[11px] uppercase tracking-wider text-muted-foreground",
9437
+ isOver && "cls_hazo_kanban_lane_strip_over border-solid bg-accent/30 text-foreground ring-1 ring-ring/30"
9438
+ ),
9439
+ children: [
9440
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_title", children: lane.title }),
9441
+ lane.actions ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_lane_actions ml-auto flex items-center gap-1", children: lane.actions }) : null
9442
+ ]
9443
+ }
9444
+ );
9445
+ }
9344
9446
  var DEFAULT_PRIORITIES = ["P0", "P1", "P2", "P3"];
9345
9447
  function humanize(key) {
9346
9448
  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 +9849,10 @@ function applyKanbanFilter(items, filter) {
9747
9849
  return true;
9748
9850
  });
9749
9851
  }
9852
+ var UNMATCHED_LANE_KEY = "__hazo_lane_unmatched__";
9853
+ function bucket_key(lane_key, column_key) {
9854
+ return lane_key == null ? column_key : `${lane_key}\0${column_key}`;
9855
+ }
9750
9856
  function default_announcements(itemLabel) {
9751
9857
  return {
9752
9858
  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 +9861,50 @@ function default_announcements(itemLabel) {
9755
9861
  onDragCancel: ({ item, fromColumn }) => `Movement cancelled. ${itemLabel(item)} returned to ${fromColumn}.`
9756
9862
  };
9757
9863
  }
9758
- function group_items_by_column(items, overlay, columns) {
9864
+ function group_items_by_column(items, overlay, columns, lanes) {
9759
9865
  const out = /* @__PURE__ */ new Map();
9760
- for (const col of columns) out.set(col.key, []);
9866
+ const lane_keys = lanes ? [...lanes.map((l) => l.key), UNMATCHED_LANE_KEY] : [void 0];
9867
+ for (const lane_key of lane_keys)
9868
+ for (const col of columns) out.set(bucket_key(lane_key, col.key), []);
9761
9869
  const overlay_inserts = /* @__PURE__ */ new Map();
9762
- for (const col of columns) overlay_inserts.set(col.key, []);
9870
+ for (const key of out.keys()) overlay_inserts.set(key, []);
9871
+ const declared = lanes ? new Set(lanes.map((l) => l.key)) : null;
9872
+ function resolve_lane(lane_key) {
9873
+ if (!declared) return void 0;
9874
+ return lane_key != null && declared.has(lane_key) ? lane_key : UNMATCHED_LANE_KEY;
9875
+ }
9763
9876
  for (const item of items) {
9764
9877
  const ov = overlay.get(item.id);
9765
9878
  if (ov) {
9766
- const bucket = overlay_inserts.get(ov.columnKey);
9879
+ const bucket = overlay_inserts.get(
9880
+ bucket_key(resolve_lane(ov.laneKey), ov.columnKey)
9881
+ );
9767
9882
  if (bucket) {
9768
- const placed = ov.sectionKey === item.sectionKey ? item : { ...item, sectionKey: ov.sectionKey };
9883
+ const placed = ov.sectionKey === item.sectionKey && ov.laneKey === item.laneKey ? item : { ...item, sectionKey: ov.sectionKey, laneKey: ov.laneKey };
9769
9884
  bucket.push({ item: placed, index: ov.index });
9770
9885
  }
9771
9886
  } else {
9772
- const bucket = out.get(item.columnKey);
9887
+ const bucket = out.get(
9888
+ bucket_key(resolve_lane(item.laneKey), item.columnKey)
9889
+ );
9773
9890
  if (bucket) bucket.push(item);
9774
9891
  }
9775
9892
  }
9776
- for (const col of columns) {
9777
- const inserts = overlay_inserts.get(col.key) ?? [];
9893
+ for (const [key, inserts] of overlay_inserts) {
9894
+ if (inserts.length === 0) continue;
9778
9895
  inserts.sort((a, b) => a.index - b.index);
9779
- const arr = out.get(col.key) ?? [];
9896
+ const arr = out.get(key) ?? [];
9780
9897
  for (const { item, index } of inserts) {
9781
9898
  const at = Math.max(0, Math.min(index, arr.length));
9782
9899
  arr.splice(at, 0, item);
9783
9900
  }
9784
- out.set(col.key, arr);
9901
+ out.set(key, arr);
9785
9902
  }
9786
9903
  return out;
9787
9904
  }
9788
9905
  function HazoUiKanban({
9789
9906
  columns,
9907
+ lanes,
9790
9908
  items,
9791
9909
  renderCard,
9792
9910
  onMove,
@@ -9794,6 +9912,7 @@ function HazoUiKanban({
9794
9912
  mobileBreakpoint = 640,
9795
9913
  announcements,
9796
9914
  emptyColumn = "\u2014",
9915
+ renderColumnFooter,
9797
9916
  className,
9798
9917
  cardClassName,
9799
9918
  itemLabel,
@@ -9834,7 +9953,7 @@ function HazoUiKanban({
9834
9953
  const next = new Map(prev);
9835
9954
  for (const [item_id, pos] of prev.entries()) {
9836
9955
  const item = items.find((i) => i.id === item_id);
9837
- if (item && item.columnKey === pos.columnKey && item.sectionKey === pos.sectionKey) {
9956
+ if (item && item.columnKey === pos.columnKey && item.sectionKey === pos.sectionKey && item.laneKey === pos.laneKey) {
9838
9957
  next.delete(item_id);
9839
9958
  changed = true;
9840
9959
  }
@@ -9843,8 +9962,8 @@ function HazoUiKanban({
9843
9962
  });
9844
9963
  }, [items, overlay]);
9845
9964
  const grouped = React27__namespace.useMemo(
9846
- () => group_items_by_column(items, overlay, columns),
9847
- [items, overlay, columns]
9965
+ () => group_items_by_column(items, overlay, columns, lanes),
9966
+ [items, overlay, columns, lanes]
9848
9967
  );
9849
9968
  const find_item = React27__namespace.useCallback(
9850
9969
  (id) => items.find((i) => i.id === id),
@@ -9854,6 +9973,13 @@ function HazoUiKanban({
9854
9973
  core$1.useSensor(core$1.PointerSensor, { activationConstraint: { distance: 5 } }),
9855
9974
  core$1.useSensor(core$1.KeyboardSensor, { coordinateGetter: sortable.sortableKeyboardCoordinates })
9856
9975
  );
9976
+ const collision_detection = React27__namespace.useCallback((args) => {
9977
+ const under_pointer = core$1.pointerWithin(args);
9978
+ if (under_pointer.length > 0) return under_pointer;
9979
+ const overlapping = core$1.rectIntersection(args);
9980
+ if (overlapping.length > 0) return overlapping;
9981
+ return core$1.closestCorners(args);
9982
+ }, []);
9857
9983
  const merged_announcements = React27__namespace.useMemo(() => {
9858
9984
  const defaults = default_announcements(label_fn);
9859
9985
  return {
@@ -9882,7 +10008,7 @@ function HazoUiKanban({
9882
10008
  const id = String(active.id);
9883
10009
  const item = find_item(id);
9884
10010
  if (!item) return "";
9885
- const over_col_key = over ? over.data?.current?.columnKey ?? String(over.id).replace(/^column:/, "") : null;
10011
+ const over_col_key = over ? over.data?.current?.columnKey ?? item.columnKey : null;
9886
10012
  const col = over_col_key ? columns.find((c) => c.key === over_col_key) : null;
9887
10013
  return merged_announcements.onDragOver({
9888
10014
  item,
@@ -9897,10 +10023,13 @@ function HazoUiKanban({
9897
10023
  const item = find_item(id);
9898
10024
  if (!item) return "";
9899
10025
  const from_col = columns.find((c) => c.key === item.columnKey);
9900
- const over_col_key = over ? over.data?.current?.columnKey ?? String(over.id).replace(/^column:/, "") : null;
10026
+ const over_col_key = over?.data?.current?.columnKey ?? item.columnKey;
10027
+ const over_lane_key = lanes ? over?.data?.current?.laneKey ?? item.laneKey : void 0;
9901
10028
  const to_col = over_col_key ? columns.find((c) => c.key === over_col_key) ?? from_col : from_col;
9902
- const target = over_col_key ? grouped.get(over_col_key) ?? [] : grouped.get(item.columnKey) ?? [];
9903
- const on_shell = !over || String(over.id).startsWith("column:") || String(over.id).startsWith("columnhead:") || String(over.id).startsWith("section:");
10029
+ const target = grouped.get(
10030
+ bucket_key(over_lane_key, over_col_key ?? item.columnKey)
10031
+ ) ?? [];
10032
+ 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
10033
  const new_index = on_shell ? target.length : target.findIndex((it) => it.id === String(over.id));
9905
10034
  return merged_announcements.onDragEnd({
9906
10035
  item,
@@ -9920,7 +10049,7 @@ function HazoUiKanban({
9920
10049
  });
9921
10050
  }
9922
10051
  }),
9923
- [find_item, columns, grouped, merged_announcements]
10052
+ [find_item, columns, lanes, grouped, merged_announcements]
9924
10053
  );
9925
10054
  const handle_drag_start = React27__namespace.useCallback((event) => {
9926
10055
  set_active_id(String(event.active.id));
@@ -9947,31 +10076,35 @@ function HazoUiKanban({
9947
10076
  if (!event.over) return;
9948
10077
  const over_id = String(event.over.id);
9949
10078
  const over_data = event.over.data?.current;
9950
- const dest_column_key = over_data?.columnKey ?? over_id.replace(/^column:/, "");
10079
+ const dest_column_key = over_data?.columnKey ?? item.columnKey;
9951
10080
  const dest_column = columns.find((c) => c.key === dest_column_key);
9952
10081
  if (!dest_column) return;
10082
+ const dest_lane_key = lanes ? over_data?.laneKey ?? item.laneKey : void 0;
10083
+ const dest_lane = lanes?.find((l) => l.key === dest_lane_key);
10084
+ if (lanes && dest_lane?.accepts && !dest_lane.accepts(item)) return;
9953
10085
  const dest_section_key = dest_column.sections ? over_data?.sectionKey ?? item.sectionKey : void 0;
9954
10086
  const dest_section = dest_column.sections?.find(
9955
10087
  (s) => s.key === dest_section_key
9956
10088
  );
9957
10089
  if (dest_column.accepts && !dest_column.accepts(item)) return;
9958
10090
  if (dest_section?.accepts && !dest_section.accepts(item)) return;
9959
- const dest_items_pre_overlay = grouped.get(dest_column_key) ?? [];
10091
+ const dest_items_pre_overlay = grouped.get(bucket_key(dest_lane_key, dest_column_key)) ?? [];
9960
10092
  let new_index;
9961
- if (over_data?.isColumn || over_data?.isSection) {
10093
+ if (over_data?.isColumn || over_data?.isSection || over_data?.isLane) {
9962
10094
  new_index = dest_items_pre_overlay.length;
9963
10095
  } else {
9964
10096
  const idx = dest_items_pre_overlay.findIndex((i) => i.id === over_id);
9965
10097
  new_index = idx === -1 ? dest_items_pre_overlay.length : idx;
9966
10098
  }
9967
10099
  const handle = make_revert_handle(active_id_str);
9968
- if (item.columnKey === dest_column_key && item.sectionKey === dest_section_key) {
10100
+ if (item.columnKey === dest_column_key && item.sectionKey === dest_section_key && item.laneKey === dest_lane_key) {
9969
10101
  if (active_id_str === over_id) return;
9970
10102
  set_overlay((prev) => {
9971
10103
  const next = new Map(prev);
9972
10104
  next.set(active_id_str, {
9973
10105
  columnKey: dest_column_key,
9974
10106
  sectionKey: dest_section_key,
10107
+ laneKey: dest_lane_key,
9975
10108
  index: new_index
9976
10109
  });
9977
10110
  return next;
@@ -9981,6 +10114,7 @@ function HazoUiKanban({
9981
10114
  item,
9982
10115
  columnKey: dest_column_key,
9983
10116
  sectionKey: dest_section_key,
10117
+ laneKey: dest_lane_key,
9984
10118
  newIndex: new_index,
9985
10119
  revert: handle.revert
9986
10120
  };
@@ -9992,6 +10126,7 @@ function HazoUiKanban({
9992
10126
  next.set(active_id_str, {
9993
10127
  columnKey: dest_column_key,
9994
10128
  sectionKey: dest_section_key,
10129
+ laneKey: dest_lane_key,
9995
10130
  index: new_index
9996
10131
  });
9997
10132
  return next;
@@ -10003,12 +10138,14 @@ function HazoUiKanban({
10003
10138
  toColumn: dest_column_key,
10004
10139
  fromSection: item.sectionKey,
10005
10140
  toSection: dest_section_key,
10141
+ fromLane: item.laneKey,
10142
+ toLane: dest_lane_key,
10006
10143
  newIndex: new_index,
10007
10144
  revert: handle.revert
10008
10145
  };
10009
10146
  onMove?.(move_event);
10010
10147
  },
10011
- [find_item, columns, grouped, onMove, onReorder, make_revert_handle]
10148
+ [find_item, columns, lanes, grouped, onMove, onReorder, make_revert_handle]
10012
10149
  );
10013
10150
  const handle_drag_cancel = React27__namespace.useCallback(() => {
10014
10151
  set_active_id(null);
@@ -10019,13 +10156,57 @@ function HazoUiKanban({
10019
10156
  `(max-width: ${Math.max(0, mobileBreakpoint - 1)}px)`
10020
10157
  );
10021
10158
  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";
10159
+ const desktop_classes = is_custom_breakpoint ? is_mobile_via_query ? "hidden" : lanes ? "block" : "grid" : lanes ? "hidden sm:block" : "hidden sm:grid";
10160
+ const grid_style = {
10161
+ gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`
10162
+ };
10163
+ function lane_total(lane_key) {
10164
+ let total = 0;
10165
+ for (const col of columns)
10166
+ total += (grouped.get(bucket_key(lane_key, col.key)) ?? []).length;
10167
+ return total;
10168
+ }
10169
+ function render_column(col, tree_key, lane_key, with_footer = true) {
10170
+ return /* @__PURE__ */ jsxRuntime.jsx(
10171
+ KanbanColumn,
10172
+ {
10173
+ column: col,
10174
+ laneKey: lane_key,
10175
+ treeKey: tree_key,
10176
+ items: grouped.get(bucket_key(lane_key, col.key)) ?? [],
10177
+ renderCard,
10178
+ emptyColumn,
10179
+ footer: with_footer ? renderColumnFooter?.({ columnKey: col.key, laneKey: lane_key }) ?? null : null,
10180
+ cardClassName,
10181
+ showEdit: show_edit,
10182
+ onEditRequest: handle_edit_request
10183
+ },
10184
+ col.key
10185
+ );
10186
+ }
10187
+ function render_lane_row(lane, tree_key, only_column) {
10188
+ const count = only_column ? (grouped.get(bucket_key(lane.key, only_column.key)) ?? []).length : lane_total(lane.key);
10189
+ if (lane.collapseWhenEmpty && count === 0) {
10190
+ return /* @__PURE__ */ jsxRuntime.jsx(KanbanLaneStrip, { lane, treeKey: tree_key }, lane.key);
10191
+ }
10192
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cls_hazo_kanban_lane", children: [
10193
+ /* @__PURE__ */ jsxRuntime.jsx(KanbanLaneHead, { lane, count, treeKey: tree_key }),
10194
+ 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)) })
10195
+ ] }, lane.key);
10196
+ }
10197
+ function render_unmatched_lane(tree_key, only_column) {
10198
+ const count = only_column ? (grouped.get(bucket_key(UNMATCHED_LANE_KEY, only_column.key)) ?? []).length : lane_total(UNMATCHED_LANE_KEY);
10199
+ if (count === 0) return null;
10200
+ 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, false) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_lane_grid grid gap-3", style: grid_style, children: columns.map(
10201
+ (col) => render_column(col, tree_key, UNMATCHED_LANE_KEY, false)
10202
+ ) }) });
10203
+ }
10023
10204
  return /* @__PURE__ */ jsxRuntime.jsxs(
10024
10205
  core$1.DndContext,
10025
10206
  {
10026
10207
  id: dnd_context_id,
10027
10208
  sensors,
10028
- collisionDetection: core$1.closestCorners,
10209
+ collisionDetection: collision_detection,
10029
10210
  onDragStart: handle_drag_start,
10030
10211
  onDragEnd: handle_drag_end,
10031
10212
  onDragCancel: handle_drag_cancel,
@@ -10034,43 +10215,33 @@ function HazoUiKanban({
10034
10215
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("cls_hazo_kanban w-full", className), children: [
10035
10216
  /* @__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
10217
  /* @__PURE__ */ jsxRuntime.jsx(TabsList, { className: "grid w-full", style: { gridTemplateColumns: `repeat(${columns.length}, 1fr)` }, children: columns.map((col) => {
10037
- const count = (grouped.get(col.key) ?? []).length;
10218
+ const count = lanes ? lanes.reduce(
10219
+ (n, lane) => n + (grouped.get(bucket_key(lane.key, col.key)) ?? []).length,
10220
+ (grouped.get(bucket_key(UNMATCHED_LANE_KEY, col.key)) ?? []).length
10221
+ ) : (grouped.get(col.key) ?? []).length;
10038
10222
  return /* @__PURE__ */ jsxRuntime.jsxs(TabsTrigger, { value: col.key, className: "flex flex-col gap-0.5", children: [
10039
10223
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs", children: col.title }),
10040
10224
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: count })
10041
10225
  ] }, col.key);
10042
10226
  }) }),
10043
- columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(TabsContent, { value: col.key, className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsx(
10044
- KanbanColumn,
10045
- {
10046
- column: col,
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))
10227
+ 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: [
10228
+ lanes.map((lane) => render_lane_row(lane, "m", col)),
10229
+ render_unmatched_lane("m", col)
10230
+ ] }) : render_column(col, "m") }, col.key))
10055
10231
  ] }) }),
10056
10232
  /* @__PURE__ */ jsxRuntime.jsx(
10057
10233
  "div",
10058
10234
  {
10059
- className: cn("cls_hazo_kanban_desktop gap-3", desktop_classes),
10060
- style: { gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))` },
10061
- children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
10062
- KanbanColumn,
10063
- {
10064
- column: col,
10065
- items: grouped.get(col.key) ?? [],
10066
- renderCard,
10067
- emptyColumn,
10068
- cardClassName,
10069
- showEdit: show_edit,
10070
- onEditRequest: handle_edit_request
10071
- },
10072
- col.key
10073
- ))
10235
+ className: cn(
10236
+ "cls_hazo_kanban_desktop",
10237
+ lanes ? "space-y-4" : "gap-3",
10238
+ desktop_classes
10239
+ ),
10240
+ style: lanes ? void 0 : grid_style,
10241
+ children: lanes ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10242
+ lanes.map((lane) => render_lane_row(lane, "d")),
10243
+ render_unmatched_lane("d")
10244
+ ] }) : columns.map((col) => render_column(col, "d"))
10074
10245
  }
10075
10246
  ),
10076
10247
  /* @__PURE__ */ jsxRuntime.jsx(core$1.DragOverlay, { children: active_item ? /* @__PURE__ */ jsxRuntime.jsx(