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/dist/index.cjs CHANGED
@@ -9089,7 +9089,18 @@ function KanbanCard({
9089
9089
  transform,
9090
9090
  transition,
9091
9091
  isDragging
9092
- } = sortable.useSortable({ id: item.id, data: { columnKey: item.columnKey } });
9092
+ } = sortable.useSortable({
9093
+ id: item.id,
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
+ }
9103
+ });
9093
9104
  const priority_var = item.priority ? `--hazo-kanban-priority-${String(item.priority).toLowerCase()}` : null;
9094
9105
  const style = {
9095
9106
  transform: utilities.CSS.Transform.toString(transform),
@@ -9149,6 +9160,28 @@ function KanbanCard({
9149
9160
  }
9150
9161
  );
9151
9162
  }
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
+ );
9173
+ }
9174
+ function group_items_by_section(items, sections) {
9175
+ const bySection = /* @__PURE__ */ new Map();
9176
+ for (const section of sections) bySection.set(section.key, []);
9177
+ const unmatched = [];
9178
+ for (const item of items) {
9179
+ const bucket = item.sectionKey != null ? bySection.get(item.sectionKey) : void 0;
9180
+ if (bucket) bucket.push(item);
9181
+ else unmatched.push(item);
9182
+ }
9183
+ return { bySection, unmatched };
9184
+ }
9152
9185
  function KanbanColumn({
9153
9186
  column,
9154
9187
  items,
@@ -9156,12 +9189,45 @@ function KanbanColumn({
9156
9189
  emptyColumn,
9157
9190
  cardClassName,
9158
9191
  showEdit,
9159
- onEditRequest
9192
+ onEditRequest,
9193
+ laneKey,
9194
+ treeKey
9160
9195
  }) {
9161
9196
  const { setNodeRef, isOver } = core$1.useDroppable({
9162
- id: `column:${column.key}`,
9163
- data: { columnKey: column.key, isColumn: true }
9197
+ id: tree_droppable_id("column", treeKey, laneKey, column.key),
9198
+ data: { columnKey: column.key, laneKey, isColumn: true }
9164
9199
  });
9200
+ const sections = column.sections;
9201
+ const { bySection, unmatched } = React27__namespace.useMemo(
9202
+ () => sections ? group_items_by_section(items, sections) : { bySection: /* @__PURE__ */ new Map(), unmatched: items },
9203
+ [items, sections]
9204
+ );
9205
+ const ordered = React27__namespace.useMemo(() => {
9206
+ if (!sections) return items;
9207
+ const out = [];
9208
+ for (const section of sections) out.push(...bySection.get(section.key) ?? []);
9209
+ out.push(...unmatched);
9210
+ return out;
9211
+ }, [items, sections, bySection, unmatched]);
9212
+ function render_cards(list) {
9213
+ return list.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
9214
+ KanbanCard,
9215
+ {
9216
+ item,
9217
+ renderCard,
9218
+ cardClassName,
9219
+ showEdit,
9220
+ onEditRequest
9221
+ },
9222
+ item.id
9223
+ ));
9224
+ }
9225
+ const expanded_sections = (sections ?? []).filter(
9226
+ (s) => !(s.collapseWhenEmpty && (bySection.get(s.key) ?? []).length === 0)
9227
+ );
9228
+ const collapsed_sections = (sections ?? []).filter(
9229
+ (s) => s.collapseWhenEmpty && (bySection.get(s.key) ?? []).length === 0
9230
+ );
9165
9231
  return /* @__PURE__ */ jsxRuntime.jsxs(
9166
9232
  "div",
9167
9233
  {
@@ -9171,32 +9237,185 @@ function KanbanColumn({
9171
9237
  isOver && "cls_hazo_kanban_column_over bg-accent/30 ring-2 ring-ring/40"
9172
9238
  ),
9173
9239
  children: [
9174
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cls_hazo_kanban_column_head flex items-center justify-between mb-2 px-1 text-xs uppercase tracking-wider text-muted-foreground", children: [
9175
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_column_title", children: column.title }),
9176
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_column_count rounded-full bg-muted px-2 py-0.5 text-foreground", children: items.length })
9177
- ] }),
9240
+ /* @__PURE__ */ jsxRuntime.jsx(
9241
+ KanbanColumnHead,
9242
+ {
9243
+ columnKey: column.key,
9244
+ laneKey,
9245
+ treeKey,
9246
+ title: column.title,
9247
+ count: items.length,
9248
+ droppable: sections != null
9249
+ }
9250
+ ),
9178
9251
  /* @__PURE__ */ jsxRuntime.jsx(
9179
9252
  sortable.SortableContext,
9180
9253
  {
9181
- items: items.map((i) => i.id),
9254
+ items: ordered.map((i) => i.id),
9182
9255
  strategy: sortable.verticalListSortingStrategy,
9183
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_column_body flex-1 min-h-[40px]", children: items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_column_empty text-xs text-muted-foreground py-2 px-1", children: emptyColumn }) : items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
9184
- KanbanCard,
9185
- {
9186
- item,
9187
- renderCard,
9188
- cardClassName,
9189
- showEdit,
9190
- onEditRequest
9191
- },
9192
- item.id
9193
- )) })
9256
+ 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: [
9257
+ expanded_sections.map((section) => /* @__PURE__ */ jsxRuntime.jsx(
9258
+ KanbanSectionBand,
9259
+ {
9260
+ columnKey: column.key,
9261
+ laneKey,
9262
+ treeKey,
9263
+ section,
9264
+ items: bySection.get(section.key) ?? [],
9265
+ emptyColumn,
9266
+ renderCards: render_cards
9267
+ },
9268
+ section.key
9269
+ )),
9270
+ unmatched.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_section cls_hazo_kanban_section_unmatched", children: render_cards(unmatched) }),
9271
+ 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(
9272
+ KanbanSectionStrip,
9273
+ {
9274
+ columnKey: column.key,
9275
+ laneKey,
9276
+ treeKey,
9277
+ section
9278
+ },
9279
+ section.key
9280
+ )) })
9281
+ ] }) })
9194
9282
  }
9195
9283
  )
9196
9284
  ]
9197
9285
  }
9198
9286
  );
9199
9287
  }
9288
+ function KanbanColumnHead({
9289
+ columnKey,
9290
+ laneKey,
9291
+ treeKey,
9292
+ title,
9293
+ count,
9294
+ droppable
9295
+ }) {
9296
+ const { setNodeRef, isOver } = core$1.useDroppable({
9297
+ id: tree_droppable_id("columnhead", treeKey, laneKey, columnKey),
9298
+ data: { columnKey, laneKey, isColumn: true },
9299
+ disabled: !droppable
9300
+ });
9301
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9302
+ "div",
9303
+ {
9304
+ ref: droppable ? setNodeRef : void 0,
9305
+ className: cn(
9306
+ "cls_hazo_kanban_column_head mb-2 flex items-center justify-between rounded-sm px-1 text-xs uppercase tracking-wider text-muted-foreground",
9307
+ droppable && "py-1",
9308
+ isOver && "cls_hazo_kanban_column_head_over bg-accent/30 text-foreground ring-1 ring-ring/40"
9309
+ ),
9310
+ children: [
9311
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_column_title", children: title }),
9312
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_column_count rounded-full bg-muted px-2 py-0.5 text-foreground", children: count })
9313
+ ]
9314
+ }
9315
+ );
9316
+ }
9317
+ function KanbanSectionBand({
9318
+ columnKey,
9319
+ laneKey,
9320
+ treeKey,
9321
+ section,
9322
+ items,
9323
+ emptyColumn,
9324
+ renderCards
9325
+ }) {
9326
+ const { setNodeRef, isOver } = core$1.useDroppable({
9327
+ id: section_droppable_id(columnKey, section.key, treeKey, laneKey),
9328
+ data: { columnKey, laneKey, sectionKey: section.key, isSection: true }
9329
+ });
9330
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9331
+ "div",
9332
+ {
9333
+ ref: setNodeRef,
9334
+ className: cn(
9335
+ "cls_hazo_kanban_section rounded-sm",
9336
+ isOver && "cls_hazo_kanban_section_over bg-accent/20 ring-1 ring-ring/30"
9337
+ ),
9338
+ children: [
9339
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cls_hazo_kanban_section_head mt-2 mb-1 flex items-center gap-2 px-1 first:mt-0", children: [
9340
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_section_title text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: section.title }),
9341
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_section_rule h-px flex-1 bg-border" }),
9342
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cls_hazo_kanban_section_count text-[11px] tabular-nums text-muted-foreground", children: items.length })
9343
+ ] }),
9344
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_section_body min-h-[32px]", children: items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cls_hazo_kanban_section_empty px-1 py-1 text-xs text-muted-foreground", children: emptyColumn }) : renderCards(items) })
9345
+ ]
9346
+ }
9347
+ );
9348
+ }
9349
+ function KanbanSectionStrip({
9350
+ columnKey,
9351
+ laneKey,
9352
+ treeKey,
9353
+ section
9354
+ }) {
9355
+ const { setNodeRef, isOver } = core$1.useDroppable({
9356
+ id: section_droppable_id(columnKey, section.key, treeKey, laneKey),
9357
+ data: { columnKey, laneKey, sectionKey: section.key, isSection: true }
9358
+ });
9359
+ return /* @__PURE__ */ jsxRuntime.jsx(
9360
+ "div",
9361
+ {
9362
+ ref: setNodeRef,
9363
+ className: cn(
9364
+ "cls_hazo_kanban_section_strip rounded-full border border-dashed border-border px-2 py-0.5 text-[11px] text-muted-foreground",
9365
+ isOver && "cls_hazo_kanban_section_strip_over border-solid bg-accent/30 text-foreground ring-1 ring-ring/30"
9366
+ ),
9367
+ children: section.title
9368
+ }
9369
+ );
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
+ }
9200
9419
  var DEFAULT_PRIORITIES = ["P0", "P1", "P2", "P3"];
9201
9420
  function humanize(key) {
9202
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(" ");
@@ -9603,6 +9822,10 @@ function applyKanbanFilter(items, filter) {
9603
9822
  return true;
9604
9823
  });
9605
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
+ }
9606
9829
  function default_announcements(itemLabel) {
9607
9830
  return {
9608
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.`,
@@ -9611,37 +9834,50 @@ function default_announcements(itemLabel) {
9611
9834
  onDragCancel: ({ item, fromColumn }) => `Movement cancelled. ${itemLabel(item)} returned to ${fromColumn}.`
9612
9835
  };
9613
9836
  }
9614
- function group_items_by_column(items, overlay, columns) {
9837
+ function group_items_by_column(items, overlay, columns, lanes) {
9615
9838
  const out = /* @__PURE__ */ new Map();
9616
- for (const col of columns) out.set(col.key, []);
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), []);
9617
9842
  const overlay_inserts = /* @__PURE__ */ new Map();
9618
- for (const col of columns) overlay_inserts.set(col.key, []);
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
+ }
9619
9849
  for (const item of items) {
9620
9850
  const ov = overlay.get(item.id);
9621
9851
  if (ov) {
9622
- const bucket = overlay_inserts.get(ov.columnKey);
9852
+ const bucket = overlay_inserts.get(
9853
+ bucket_key(resolve_lane(ov.laneKey), ov.columnKey)
9854
+ );
9623
9855
  if (bucket) {
9624
- bucket.push({ item, index: ov.index });
9856
+ const placed = ov.sectionKey === item.sectionKey && ov.laneKey === item.laneKey ? item : { ...item, sectionKey: ov.sectionKey, laneKey: ov.laneKey };
9857
+ bucket.push({ item: placed, index: ov.index });
9625
9858
  }
9626
9859
  } else {
9627
- const bucket = out.get(item.columnKey);
9860
+ const bucket = out.get(
9861
+ bucket_key(resolve_lane(item.laneKey), item.columnKey)
9862
+ );
9628
9863
  if (bucket) bucket.push(item);
9629
9864
  }
9630
9865
  }
9631
- for (const col of columns) {
9632
- const inserts = overlay_inserts.get(col.key) ?? [];
9866
+ for (const [key, inserts] of overlay_inserts) {
9867
+ if (inserts.length === 0) continue;
9633
9868
  inserts.sort((a, b) => a.index - b.index);
9634
- const arr = out.get(col.key) ?? [];
9869
+ const arr = out.get(key) ?? [];
9635
9870
  for (const { item, index } of inserts) {
9636
9871
  const at = Math.max(0, Math.min(index, arr.length));
9637
9872
  arr.splice(at, 0, item);
9638
9873
  }
9639
- out.set(col.key, arr);
9874
+ out.set(key, arr);
9640
9875
  }
9641
9876
  return out;
9642
9877
  }
9643
9878
  function HazoUiKanban({
9644
9879
  columns,
9880
+ lanes,
9645
9881
  items,
9646
9882
  renderCard,
9647
9883
  onMove,
@@ -9689,7 +9925,7 @@ function HazoUiKanban({
9689
9925
  const next = new Map(prev);
9690
9926
  for (const [item_id, pos] of prev.entries()) {
9691
9927
  const item = items.find((i) => i.id === item_id);
9692
- if (item && item.columnKey === pos.columnKey) {
9928
+ if (item && item.columnKey === pos.columnKey && item.sectionKey === pos.sectionKey && item.laneKey === pos.laneKey) {
9693
9929
  next.delete(item_id);
9694
9930
  changed = true;
9695
9931
  }
@@ -9698,8 +9934,8 @@ function HazoUiKanban({
9698
9934
  });
9699
9935
  }, [items, overlay]);
9700
9936
  const grouped = React27__namespace.useMemo(
9701
- () => group_items_by_column(items, overlay, columns),
9702
- [items, overlay, columns]
9937
+ () => group_items_by_column(items, overlay, columns, lanes),
9938
+ [items, overlay, columns, lanes]
9703
9939
  );
9704
9940
  const find_item = React27__namespace.useCallback(
9705
9941
  (id) => items.find((i) => i.id === id),
@@ -9709,6 +9945,13 @@ function HazoUiKanban({
9709
9945
  core$1.useSensor(core$1.PointerSensor, { activationConstraint: { distance: 5 } }),
9710
9946
  core$1.useSensor(core$1.KeyboardSensor, { coordinateGetter: sortable.sortableKeyboardCoordinates })
9711
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
+ }, []);
9712
9955
  const merged_announcements = React27__namespace.useMemo(() => {
9713
9956
  const defaults = default_announcements(label_fn);
9714
9957
  return {
@@ -9737,7 +9980,7 @@ function HazoUiKanban({
9737
9980
  const id = String(active.id);
9738
9981
  const item = find_item(id);
9739
9982
  if (!item) return "";
9740
- const over_col_key = over ? over.data?.current?.columnKey ?? String(over.id).replace(/^column:/, "") : null;
9983
+ const over_col_key = over ? over.data?.current?.columnKey ?? item.columnKey : null;
9741
9984
  const col = over_col_key ? columns.find((c) => c.key === over_col_key) : null;
9742
9985
  return merged_announcements.onDragOver({
9743
9986
  item,
@@ -9752,10 +9995,14 @@ function HazoUiKanban({
9752
9995
  const item = find_item(id);
9753
9996
  if (!item) return "";
9754
9997
  const from_col = columns.find((c) => c.key === item.columnKey);
9755
- const over_col_key = over ? over.data?.current?.columnKey ?? String(over.id).replace(/^column:/, "") : null;
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;
9756
10000
  const to_col = over_col_key ? columns.find((c) => c.key === over_col_key) ?? from_col : from_col;
9757
- const target = over_col_key ? grouped.get(over_col_key) ?? [] : grouped.get(item.columnKey) ?? [];
9758
- const new_index = over && String(over.id) !== `column:${over_col_key}` ? target.findIndex((it) => it.id === String(over.id)) : target.length;
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:");
10005
+ const new_index = on_shell ? target.length : target.findIndex((it) => it.id === String(over.id));
9759
10006
  return merged_announcements.onDragEnd({
9760
10007
  item,
9761
10008
  fromColumn: from_col?.title ?? item.columnKey,
@@ -9774,7 +10021,7 @@ function HazoUiKanban({
9774
10021
  });
9775
10022
  }
9776
10023
  }),
9777
- [find_item, columns, grouped, merged_announcements]
10024
+ [find_item, columns, lanes, grouped, merged_announcements]
9778
10025
  );
9779
10026
  const handle_drag_start = React27__namespace.useCallback((event) => {
9780
10027
  set_active_id(String(event.active.id));
@@ -9801,25 +10048,35 @@ function HazoUiKanban({
9801
10048
  if (!event.over) return;
9802
10049
  const over_id = String(event.over.id);
9803
10050
  const over_data = event.over.data?.current;
9804
- const dest_column_key = over_data?.columnKey ?? over_id.replace(/^column:/, "");
10051
+ const dest_column_key = over_data?.columnKey ?? item.columnKey;
9805
10052
  const dest_column = columns.find((c) => c.key === dest_column_key);
9806
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;
10057
+ const dest_section_key = dest_column.sections ? over_data?.sectionKey ?? item.sectionKey : void 0;
10058
+ const dest_section = dest_column.sections?.find(
10059
+ (s) => s.key === dest_section_key
10060
+ );
9807
10061
  if (dest_column.accepts && !dest_column.accepts(item)) return;
9808
- const dest_items_pre_overlay = grouped.get(dest_column_key) ?? [];
10062
+ if (dest_section?.accepts && !dest_section.accepts(item)) return;
10063
+ const dest_items_pre_overlay = grouped.get(bucket_key(dest_lane_key, dest_column_key)) ?? [];
9809
10064
  let new_index;
9810
- if (over_data?.isColumn) {
10065
+ if (over_data?.isColumn || over_data?.isSection || over_data?.isLane) {
9811
10066
  new_index = dest_items_pre_overlay.length;
9812
10067
  } else {
9813
10068
  const idx = dest_items_pre_overlay.findIndex((i) => i.id === over_id);
9814
10069
  new_index = idx === -1 ? dest_items_pre_overlay.length : idx;
9815
10070
  }
9816
10071
  const handle = make_revert_handle(active_id_str);
9817
- if (item.columnKey === dest_column_key) {
10072
+ if (item.columnKey === dest_column_key && item.sectionKey === dest_section_key && item.laneKey === dest_lane_key) {
9818
10073
  if (active_id_str === over_id) return;
9819
10074
  set_overlay((prev) => {
9820
10075
  const next = new Map(prev);
9821
10076
  next.set(active_id_str, {
9822
10077
  columnKey: dest_column_key,
10078
+ sectionKey: dest_section_key,
10079
+ laneKey: dest_lane_key,
9823
10080
  index: new_index
9824
10081
  });
9825
10082
  return next;
@@ -9828,6 +10085,8 @@ function HazoUiKanban({
9828
10085
  itemId: active_id_str,
9829
10086
  item,
9830
10087
  columnKey: dest_column_key,
10088
+ sectionKey: dest_section_key,
10089
+ laneKey: dest_lane_key,
9831
10090
  newIndex: new_index,
9832
10091
  revert: handle.revert
9833
10092
  };
@@ -9838,6 +10097,8 @@ function HazoUiKanban({
9838
10097
  const next = new Map(prev);
9839
10098
  next.set(active_id_str, {
9840
10099
  columnKey: dest_column_key,
10100
+ sectionKey: dest_section_key,
10101
+ laneKey: dest_lane_key,
9841
10102
  index: new_index
9842
10103
  });
9843
10104
  return next;
@@ -9847,12 +10108,16 @@ function HazoUiKanban({
9847
10108
  item,
9848
10109
  fromColumn: item.columnKey,
9849
10110
  toColumn: dest_column_key,
10111
+ fromSection: item.sectionKey,
10112
+ toSection: dest_section_key,
10113
+ fromLane: item.laneKey,
10114
+ toLane: dest_lane_key,
9850
10115
  newIndex: new_index,
9851
10116
  revert: handle.revert
9852
10117
  };
9853
10118
  onMove?.(move_event);
9854
10119
  },
9855
- [find_item, columns, grouped, onMove, onReorder, make_revert_handle]
10120
+ [find_item, columns, lanes, grouped, onMove, onReorder, make_revert_handle]
9856
10121
  );
9857
10122
  const handle_drag_cancel = React27__namespace.useCallback(() => {
9858
10123
  set_active_id(null);
@@ -9863,13 +10128,56 @@ function HazoUiKanban({
9863
10128
  `(max-width: ${Math.max(0, mobileBreakpoint - 1)}px)`
9864
10129
  );
9865
10130
  const mobile_classes = is_custom_breakpoint ? is_mobile_via_query ? "block" : "hidden" : "sm:hidden";
9866
- 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
+ }
9867
10175
  return /* @__PURE__ */ jsxRuntime.jsxs(
9868
10176
  core$1.DndContext,
9869
10177
  {
9870
10178
  id: dnd_context_id,
9871
10179
  sensors,
9872
- collisionDetection: core$1.closestCorners,
10180
+ collisionDetection: collision_detection,
9873
10181
  onDragStart: handle_drag_start,
9874
10182
  onDragEnd: handle_drag_end,
9875
10183
  onDragCancel: handle_drag_cancel,
@@ -9878,43 +10186,33 @@ function HazoUiKanban({
9878
10186
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("cls_hazo_kanban w-full", className), children: [
9879
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: [
9880
10188
  /* @__PURE__ */ jsxRuntime.jsx(TabsList, { className: "grid w-full", style: { gridTemplateColumns: `repeat(${columns.length}, 1fr)` }, children: columns.map((col) => {
9881
- const count = (grouped.get(col.key) ?? []).length;
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;
9882
10193
  return /* @__PURE__ */ jsxRuntime.jsxs(TabsTrigger, { value: col.key, className: "flex flex-col gap-0.5", children: [
9883
10194
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs", children: col.title }),
9884
10195
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: count })
9885
10196
  ] }, col.key);
9886
10197
  }) }),
9887
- columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(TabsContent, { value: col.key, className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsx(
9888
- KanbanColumn,
9889
- {
9890
- column: col,
9891
- items: grouped.get(col.key) ?? [],
9892
- renderCard,
9893
- emptyColumn,
9894
- cardClassName,
9895
- showEdit: show_edit,
9896
- onEditRequest: handle_edit_request
9897
- }
9898
- ) }, 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))
9899
10202
  ] }) }),
9900
10203
  /* @__PURE__ */ jsxRuntime.jsx(
9901
10204
  "div",
9902
10205
  {
9903
- className: cn("cls_hazo_kanban_desktop gap-3", desktop_classes),
9904
- style: { gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))` },
9905
- children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
9906
- KanbanColumn,
9907
- {
9908
- column: col,
9909
- items: grouped.get(col.key) ?? [],
9910
- renderCard,
9911
- emptyColumn,
9912
- cardClassName,
9913
- showEdit: show_edit,
9914
- onEditRequest: handle_edit_request
9915
- },
9916
- col.key
9917
- ))
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"))
9918
10216
  }
9919
10217
  ),
9920
10218
  /* @__PURE__ */ jsxRuntime.jsx(core$1.DragOverlay, { children: active_item ? /* @__PURE__ */ jsxRuntime.jsx(