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.js CHANGED
@@ -15,7 +15,7 @@ import * as SelectPrimitive from '@radix-ui/react-select';
15
15
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
16
16
  import { DayPicker } from 'react-day-picker';
17
17
  import { format } from 'date-fns';
18
- import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter, closestCorners, DragOverlay, useDroppable } from '@dnd-kit/core';
18
+ import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter, pointerWithin, rectIntersection, closestCorners, DragOverlay, useDroppable } from '@dnd-kit/core';
19
19
  import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, arrayMove, useSortable } from '@dnd-kit/sortable';
20
20
  import { CSS } from '@dnd-kit/utilities';
21
21
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
@@ -9019,7 +9019,18 @@ function KanbanCard({
9019
9019
  transform,
9020
9020
  transition,
9021
9021
  isDragging
9022
- } = useSortable({ id: item.id, data: { columnKey: item.columnKey } });
9022
+ } = useSortable({
9023
+ id: item.id,
9024
+ // sectionKey and laneKey ride along so a drop *onto a card* resolves to
9025
+ // that card's section and lane, not just its column (the section bands
9026
+ // and lane rows cover the gaps between cards, but the cards themselves
9027
+ // are the sortable).
9028
+ data: {
9029
+ columnKey: item.columnKey,
9030
+ sectionKey: item.sectionKey,
9031
+ laneKey: item.laneKey
9032
+ }
9033
+ });
9023
9034
  const priority_var = item.priority ? `--hazo-kanban-priority-${String(item.priority).toLowerCase()}` : null;
9024
9035
  const style = {
9025
9036
  transform: CSS.Transform.toString(transform),
@@ -9079,6 +9090,28 @@ function KanbanCard({
9079
9090
  }
9080
9091
  );
9081
9092
  }
9093
+ function tree_droppable_id(prefix, tree_key, lane_key, rest) {
9094
+ return lane_key == null ? `${prefix}:${tree_key}::${rest}` : `${prefix}:${tree_key}::${lane_key}::${rest}`;
9095
+ }
9096
+ function section_droppable_id(column_key, section_key, tree_key, lane_key) {
9097
+ return tree_droppable_id(
9098
+ "section",
9099
+ tree_key,
9100
+ lane_key,
9101
+ `${column_key}::${section_key}`
9102
+ );
9103
+ }
9104
+ function group_items_by_section(items, sections) {
9105
+ const bySection = /* @__PURE__ */ new Map();
9106
+ for (const section of sections) bySection.set(section.key, []);
9107
+ const unmatched = [];
9108
+ for (const item of items) {
9109
+ const bucket = item.sectionKey != null ? bySection.get(item.sectionKey) : void 0;
9110
+ if (bucket) bucket.push(item);
9111
+ else unmatched.push(item);
9112
+ }
9113
+ return { bySection, unmatched };
9114
+ }
9082
9115
  function KanbanColumn({
9083
9116
  column,
9084
9117
  items,
@@ -9086,12 +9119,45 @@ function KanbanColumn({
9086
9119
  emptyColumn,
9087
9120
  cardClassName,
9088
9121
  showEdit,
9089
- onEditRequest
9122
+ onEditRequest,
9123
+ laneKey,
9124
+ treeKey
9090
9125
  }) {
9091
9126
  const { setNodeRef, isOver } = useDroppable({
9092
- id: `column:${column.key}`,
9093
- data: { columnKey: column.key, isColumn: true }
9127
+ id: tree_droppable_id("column", treeKey, laneKey, column.key),
9128
+ data: { columnKey: column.key, laneKey, isColumn: true }
9094
9129
  });
9130
+ const sections = column.sections;
9131
+ const { bySection, unmatched } = React27.useMemo(
9132
+ () => sections ? group_items_by_section(items, sections) : { bySection: /* @__PURE__ */ new Map(), unmatched: items },
9133
+ [items, sections]
9134
+ );
9135
+ const ordered = React27.useMemo(() => {
9136
+ if (!sections) return items;
9137
+ const out = [];
9138
+ for (const section of sections) out.push(...bySection.get(section.key) ?? []);
9139
+ out.push(...unmatched);
9140
+ return out;
9141
+ }, [items, sections, bySection, unmatched]);
9142
+ function render_cards(list) {
9143
+ return list.map((item) => /* @__PURE__ */ jsx(
9144
+ KanbanCard,
9145
+ {
9146
+ item,
9147
+ renderCard,
9148
+ cardClassName,
9149
+ showEdit,
9150
+ onEditRequest
9151
+ },
9152
+ item.id
9153
+ ));
9154
+ }
9155
+ const expanded_sections = (sections ?? []).filter(
9156
+ (s) => !(s.collapseWhenEmpty && (bySection.get(s.key) ?? []).length === 0)
9157
+ );
9158
+ const collapsed_sections = (sections ?? []).filter(
9159
+ (s) => s.collapseWhenEmpty && (bySection.get(s.key) ?? []).length === 0
9160
+ );
9095
9161
  return /* @__PURE__ */ jsxs(
9096
9162
  "div",
9097
9163
  {
@@ -9101,32 +9167,185 @@ function KanbanColumn({
9101
9167
  isOver && "cls_hazo_kanban_column_over bg-accent/30 ring-2 ring-ring/40"
9102
9168
  ),
9103
9169
  children: [
9104
- /* @__PURE__ */ 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: [
9105
- /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_column_title", children: column.title }),
9106
- /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_column_count rounded-full bg-muted px-2 py-0.5 text-foreground", children: items.length })
9107
- ] }),
9170
+ /* @__PURE__ */ jsx(
9171
+ KanbanColumnHead,
9172
+ {
9173
+ columnKey: column.key,
9174
+ laneKey,
9175
+ treeKey,
9176
+ title: column.title,
9177
+ count: items.length,
9178
+ droppable: sections != null
9179
+ }
9180
+ ),
9108
9181
  /* @__PURE__ */ jsx(
9109
9182
  SortableContext,
9110
9183
  {
9111
- items: items.map((i) => i.id),
9184
+ items: ordered.map((i) => i.id),
9112
9185
  strategy: verticalListSortingStrategy,
9113
- children: /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_column_body flex-1 min-h-[40px]", children: items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_column_empty text-xs text-muted-foreground py-2 px-1", children: emptyColumn }) : items.map((item) => /* @__PURE__ */ jsx(
9114
- KanbanCard,
9115
- {
9116
- item,
9117
- renderCard,
9118
- cardClassName,
9119
- showEdit,
9120
- onEditRequest
9121
- },
9122
- item.id
9123
- )) })
9186
+ children: /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_column_body flex-1 min-h-[40px]", children: !sections ? items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_column_empty text-xs text-muted-foreground py-2 px-1", children: emptyColumn }) : render_cards(items) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
9187
+ expanded_sections.map((section) => /* @__PURE__ */ jsx(
9188
+ KanbanSectionBand,
9189
+ {
9190
+ columnKey: column.key,
9191
+ laneKey,
9192
+ treeKey,
9193
+ section,
9194
+ items: bySection.get(section.key) ?? [],
9195
+ emptyColumn,
9196
+ renderCards: render_cards
9197
+ },
9198
+ section.key
9199
+ )),
9200
+ unmatched.length > 0 && /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_section cls_hazo_kanban_section_unmatched", children: render_cards(unmatched) }),
9201
+ collapsed_sections.length > 0 && /* @__PURE__ */ 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__ */ jsx(
9202
+ KanbanSectionStrip,
9203
+ {
9204
+ columnKey: column.key,
9205
+ laneKey,
9206
+ treeKey,
9207
+ section
9208
+ },
9209
+ section.key
9210
+ )) })
9211
+ ] }) })
9124
9212
  }
9125
9213
  )
9126
9214
  ]
9127
9215
  }
9128
9216
  );
9129
9217
  }
9218
+ function KanbanColumnHead({
9219
+ columnKey,
9220
+ laneKey,
9221
+ treeKey,
9222
+ title,
9223
+ count,
9224
+ droppable
9225
+ }) {
9226
+ const { setNodeRef, isOver } = useDroppable({
9227
+ id: tree_droppable_id("columnhead", treeKey, laneKey, columnKey),
9228
+ data: { columnKey, laneKey, isColumn: true },
9229
+ disabled: !droppable
9230
+ });
9231
+ return /* @__PURE__ */ jsxs(
9232
+ "div",
9233
+ {
9234
+ ref: droppable ? setNodeRef : void 0,
9235
+ className: cn(
9236
+ "cls_hazo_kanban_column_head mb-2 flex items-center justify-between rounded-sm px-1 text-xs uppercase tracking-wider text-muted-foreground",
9237
+ droppable && "py-1",
9238
+ isOver && "cls_hazo_kanban_column_head_over bg-accent/30 text-foreground ring-1 ring-ring/40"
9239
+ ),
9240
+ children: [
9241
+ /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_column_title", children: title }),
9242
+ /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_column_count rounded-full bg-muted px-2 py-0.5 text-foreground", children: count })
9243
+ ]
9244
+ }
9245
+ );
9246
+ }
9247
+ function KanbanSectionBand({
9248
+ columnKey,
9249
+ laneKey,
9250
+ treeKey,
9251
+ section,
9252
+ items,
9253
+ emptyColumn,
9254
+ renderCards
9255
+ }) {
9256
+ const { setNodeRef, isOver } = useDroppable({
9257
+ id: section_droppable_id(columnKey, section.key, treeKey, laneKey),
9258
+ data: { columnKey, laneKey, sectionKey: section.key, isSection: true }
9259
+ });
9260
+ return /* @__PURE__ */ jsxs(
9261
+ "div",
9262
+ {
9263
+ ref: setNodeRef,
9264
+ className: cn(
9265
+ "cls_hazo_kanban_section rounded-sm",
9266
+ isOver && "cls_hazo_kanban_section_over bg-accent/20 ring-1 ring-ring/30"
9267
+ ),
9268
+ children: [
9269
+ /* @__PURE__ */ jsxs("div", { className: "cls_hazo_kanban_section_head mt-2 mb-1 flex items-center gap-2 px-1 first:mt-0", children: [
9270
+ /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_section_title text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: section.title }),
9271
+ /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_section_rule h-px flex-1 bg-border" }),
9272
+ /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_section_count text-[11px] tabular-nums text-muted-foreground", children: items.length })
9273
+ ] }),
9274
+ /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_section_body min-h-[32px]", children: items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cls_hazo_kanban_section_empty px-1 py-1 text-xs text-muted-foreground", children: emptyColumn }) : renderCards(items) })
9275
+ ]
9276
+ }
9277
+ );
9278
+ }
9279
+ function KanbanSectionStrip({
9280
+ columnKey,
9281
+ laneKey,
9282
+ treeKey,
9283
+ section
9284
+ }) {
9285
+ const { setNodeRef, isOver } = useDroppable({
9286
+ id: section_droppable_id(columnKey, section.key, treeKey, laneKey),
9287
+ data: { columnKey, laneKey, sectionKey: section.key, isSection: true }
9288
+ });
9289
+ return /* @__PURE__ */ jsx(
9290
+ "div",
9291
+ {
9292
+ ref: setNodeRef,
9293
+ className: cn(
9294
+ "cls_hazo_kanban_section_strip rounded-full border border-dashed border-border px-2 py-0.5 text-[11px] text-muted-foreground",
9295
+ isOver && "cls_hazo_kanban_section_strip_over border-solid bg-accent/30 text-foreground ring-1 ring-ring/30"
9296
+ ),
9297
+ children: section.title
9298
+ }
9299
+ );
9300
+ }
9301
+ function lane_droppable_id(lane_key, tree_key) {
9302
+ return `lane:${tree_key}::${lane_key}`;
9303
+ }
9304
+ function KanbanLaneHead({
9305
+ lane,
9306
+ count,
9307
+ treeKey
9308
+ }) {
9309
+ const { setNodeRef, isOver } = useDroppable({
9310
+ id: lane_droppable_id(lane.key, treeKey),
9311
+ data: { laneKey: lane.key, isLane: true }
9312
+ });
9313
+ return /* @__PURE__ */ jsxs(
9314
+ "div",
9315
+ {
9316
+ ref: setNodeRef,
9317
+ className: cn(
9318
+ "cls_hazo_kanban_lane_head mb-2 flex items-center gap-3 rounded-sm px-1 py-1",
9319
+ isOver && "cls_hazo_kanban_lane_head_over bg-accent/30 ring-1 ring-ring/40"
9320
+ ),
9321
+ children: [
9322
+ /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_lane_title text-xs font-semibold uppercase tracking-wider text-foreground", children: lane.title }),
9323
+ /* @__PURE__ */ jsx("span", { className: "cls_hazo_kanban_lane_rule h-px flex-1 bg-border" }),
9324
+ /* @__PURE__ */ 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 })
9325
+ ]
9326
+ }
9327
+ );
9328
+ }
9329
+ function KanbanLaneStrip({
9330
+ lane,
9331
+ treeKey
9332
+ }) {
9333
+ const { setNodeRef, isOver } = useDroppable({
9334
+ id: lane_droppable_id(lane.key, treeKey),
9335
+ data: { laneKey: lane.key, isLane: true }
9336
+ });
9337
+ return /* @__PURE__ */ jsx(
9338
+ "div",
9339
+ {
9340
+ ref: setNodeRef,
9341
+ className: cn(
9342
+ "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",
9343
+ isOver && "cls_hazo_kanban_lane_strip_over border-solid bg-accent/30 text-foreground ring-1 ring-ring/30"
9344
+ ),
9345
+ children: lane.title
9346
+ }
9347
+ );
9348
+ }
9130
9349
  var DEFAULT_PRIORITIES = ["P0", "P1", "P2", "P3"];
9131
9350
  function humanize(key) {
9132
9351
  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(" ");
@@ -9533,6 +9752,10 @@ function applyKanbanFilter(items, filter) {
9533
9752
  return true;
9534
9753
  });
9535
9754
  }
9755
+ var UNMATCHED_LANE_KEY = "__hazo_lane_unmatched__";
9756
+ function bucket_key(lane_key, column_key) {
9757
+ return lane_key == null ? column_key : `${lane_key}\0${column_key}`;
9758
+ }
9536
9759
  function default_announcements(itemLabel) {
9537
9760
  return {
9538
9761
  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.`,
@@ -9541,37 +9764,50 @@ function default_announcements(itemLabel) {
9541
9764
  onDragCancel: ({ item, fromColumn }) => `Movement cancelled. ${itemLabel(item)} returned to ${fromColumn}.`
9542
9765
  };
9543
9766
  }
9544
- function group_items_by_column(items, overlay, columns) {
9767
+ function group_items_by_column(items, overlay, columns, lanes) {
9545
9768
  const out = /* @__PURE__ */ new Map();
9546
- for (const col of columns) out.set(col.key, []);
9769
+ const lane_keys = lanes ? [...lanes.map((l) => l.key), UNMATCHED_LANE_KEY] : [void 0];
9770
+ for (const lane_key of lane_keys)
9771
+ for (const col of columns) out.set(bucket_key(lane_key, col.key), []);
9547
9772
  const overlay_inserts = /* @__PURE__ */ new Map();
9548
- for (const col of columns) overlay_inserts.set(col.key, []);
9773
+ for (const key of out.keys()) overlay_inserts.set(key, []);
9774
+ const declared = lanes ? new Set(lanes.map((l) => l.key)) : null;
9775
+ function resolve_lane(lane_key) {
9776
+ if (!declared) return void 0;
9777
+ return lane_key != null && declared.has(lane_key) ? lane_key : UNMATCHED_LANE_KEY;
9778
+ }
9549
9779
  for (const item of items) {
9550
9780
  const ov = overlay.get(item.id);
9551
9781
  if (ov) {
9552
- const bucket = overlay_inserts.get(ov.columnKey);
9782
+ const bucket = overlay_inserts.get(
9783
+ bucket_key(resolve_lane(ov.laneKey), ov.columnKey)
9784
+ );
9553
9785
  if (bucket) {
9554
- bucket.push({ item, index: ov.index });
9786
+ const placed = ov.sectionKey === item.sectionKey && ov.laneKey === item.laneKey ? item : { ...item, sectionKey: ov.sectionKey, laneKey: ov.laneKey };
9787
+ bucket.push({ item: placed, index: ov.index });
9555
9788
  }
9556
9789
  } else {
9557
- const bucket = out.get(item.columnKey);
9790
+ const bucket = out.get(
9791
+ bucket_key(resolve_lane(item.laneKey), item.columnKey)
9792
+ );
9558
9793
  if (bucket) bucket.push(item);
9559
9794
  }
9560
9795
  }
9561
- for (const col of columns) {
9562
- const inserts = overlay_inserts.get(col.key) ?? [];
9796
+ for (const [key, inserts] of overlay_inserts) {
9797
+ if (inserts.length === 0) continue;
9563
9798
  inserts.sort((a, b) => a.index - b.index);
9564
- const arr = out.get(col.key) ?? [];
9799
+ const arr = out.get(key) ?? [];
9565
9800
  for (const { item, index } of inserts) {
9566
9801
  const at = Math.max(0, Math.min(index, arr.length));
9567
9802
  arr.splice(at, 0, item);
9568
9803
  }
9569
- out.set(col.key, arr);
9804
+ out.set(key, arr);
9570
9805
  }
9571
9806
  return out;
9572
9807
  }
9573
9808
  function HazoUiKanban({
9574
9809
  columns,
9810
+ lanes,
9575
9811
  items,
9576
9812
  renderCard,
9577
9813
  onMove,
@@ -9619,7 +9855,7 @@ function HazoUiKanban({
9619
9855
  const next = new Map(prev);
9620
9856
  for (const [item_id, pos] of prev.entries()) {
9621
9857
  const item = items.find((i) => i.id === item_id);
9622
- if (item && item.columnKey === pos.columnKey) {
9858
+ if (item && item.columnKey === pos.columnKey && item.sectionKey === pos.sectionKey && item.laneKey === pos.laneKey) {
9623
9859
  next.delete(item_id);
9624
9860
  changed = true;
9625
9861
  }
@@ -9628,8 +9864,8 @@ function HazoUiKanban({
9628
9864
  });
9629
9865
  }, [items, overlay]);
9630
9866
  const grouped = React27.useMemo(
9631
- () => group_items_by_column(items, overlay, columns),
9632
- [items, overlay, columns]
9867
+ () => group_items_by_column(items, overlay, columns, lanes),
9868
+ [items, overlay, columns, lanes]
9633
9869
  );
9634
9870
  const find_item = React27.useCallback(
9635
9871
  (id) => items.find((i) => i.id === id),
@@ -9639,6 +9875,13 @@ function HazoUiKanban({
9639
9875
  useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
9640
9876
  useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
9641
9877
  );
9878
+ const collision_detection = React27.useCallback((args) => {
9879
+ const under_pointer = pointerWithin(args);
9880
+ if (under_pointer.length > 0) return under_pointer;
9881
+ const overlapping = rectIntersection(args);
9882
+ if (overlapping.length > 0) return overlapping;
9883
+ return closestCorners(args);
9884
+ }, []);
9642
9885
  const merged_announcements = React27.useMemo(() => {
9643
9886
  const defaults = default_announcements(label_fn);
9644
9887
  return {
@@ -9667,7 +9910,7 @@ function HazoUiKanban({
9667
9910
  const id = String(active.id);
9668
9911
  const item = find_item(id);
9669
9912
  if (!item) return "";
9670
- const over_col_key = over ? over.data?.current?.columnKey ?? String(over.id).replace(/^column:/, "") : null;
9913
+ const over_col_key = over ? over.data?.current?.columnKey ?? item.columnKey : null;
9671
9914
  const col = over_col_key ? columns.find((c) => c.key === over_col_key) : null;
9672
9915
  return merged_announcements.onDragOver({
9673
9916
  item,
@@ -9682,10 +9925,14 @@ function HazoUiKanban({
9682
9925
  const item = find_item(id);
9683
9926
  if (!item) return "";
9684
9927
  const from_col = columns.find((c) => c.key === item.columnKey);
9685
- const over_col_key = over ? over.data?.current?.columnKey ?? String(over.id).replace(/^column:/, "") : null;
9928
+ const over_col_key = over?.data?.current?.columnKey ?? item.columnKey;
9929
+ const over_lane_key = lanes ? over?.data?.current?.laneKey ?? item.laneKey : void 0;
9686
9930
  const to_col = over_col_key ? columns.find((c) => c.key === over_col_key) ?? from_col : from_col;
9687
- const target = over_col_key ? grouped.get(over_col_key) ?? [] : grouped.get(item.columnKey) ?? [];
9688
- const new_index = over && String(over.id) !== `column:${over_col_key}` ? target.findIndex((it) => it.id === String(over.id)) : target.length;
9931
+ const target = grouped.get(
9932
+ bucket_key(over_lane_key, over_col_key ?? item.columnKey)
9933
+ ) ?? [];
9934
+ const on_shell = !over || String(over.id).startsWith("column:") || String(over.id).startsWith("columnhead:") || String(over.id).startsWith("section:") || String(over.id).startsWith("lane:");
9935
+ const new_index = on_shell ? target.length : target.findIndex((it) => it.id === String(over.id));
9689
9936
  return merged_announcements.onDragEnd({
9690
9937
  item,
9691
9938
  fromColumn: from_col?.title ?? item.columnKey,
@@ -9704,7 +9951,7 @@ function HazoUiKanban({
9704
9951
  });
9705
9952
  }
9706
9953
  }),
9707
- [find_item, columns, grouped, merged_announcements]
9954
+ [find_item, columns, lanes, grouped, merged_announcements]
9708
9955
  );
9709
9956
  const handle_drag_start = React27.useCallback((event) => {
9710
9957
  set_active_id(String(event.active.id));
@@ -9731,25 +9978,35 @@ function HazoUiKanban({
9731
9978
  if (!event.over) return;
9732
9979
  const over_id = String(event.over.id);
9733
9980
  const over_data = event.over.data?.current;
9734
- const dest_column_key = over_data?.columnKey ?? over_id.replace(/^column:/, "");
9981
+ const dest_column_key = over_data?.columnKey ?? item.columnKey;
9735
9982
  const dest_column = columns.find((c) => c.key === dest_column_key);
9736
9983
  if (!dest_column) return;
9984
+ const dest_lane_key = lanes ? over_data?.laneKey ?? item.laneKey : void 0;
9985
+ const dest_lane = lanes?.find((l) => l.key === dest_lane_key);
9986
+ if (lanes && dest_lane?.accepts && !dest_lane.accepts(item)) return;
9987
+ const dest_section_key = dest_column.sections ? over_data?.sectionKey ?? item.sectionKey : void 0;
9988
+ const dest_section = dest_column.sections?.find(
9989
+ (s) => s.key === dest_section_key
9990
+ );
9737
9991
  if (dest_column.accepts && !dest_column.accepts(item)) return;
9738
- const dest_items_pre_overlay = grouped.get(dest_column_key) ?? [];
9992
+ if (dest_section?.accepts && !dest_section.accepts(item)) return;
9993
+ const dest_items_pre_overlay = grouped.get(bucket_key(dest_lane_key, dest_column_key)) ?? [];
9739
9994
  let new_index;
9740
- if (over_data?.isColumn) {
9995
+ if (over_data?.isColumn || over_data?.isSection || over_data?.isLane) {
9741
9996
  new_index = dest_items_pre_overlay.length;
9742
9997
  } else {
9743
9998
  const idx = dest_items_pre_overlay.findIndex((i) => i.id === over_id);
9744
9999
  new_index = idx === -1 ? dest_items_pre_overlay.length : idx;
9745
10000
  }
9746
10001
  const handle = make_revert_handle(active_id_str);
9747
- if (item.columnKey === dest_column_key) {
10002
+ if (item.columnKey === dest_column_key && item.sectionKey === dest_section_key && item.laneKey === dest_lane_key) {
9748
10003
  if (active_id_str === over_id) return;
9749
10004
  set_overlay((prev) => {
9750
10005
  const next = new Map(prev);
9751
10006
  next.set(active_id_str, {
9752
10007
  columnKey: dest_column_key,
10008
+ sectionKey: dest_section_key,
10009
+ laneKey: dest_lane_key,
9753
10010
  index: new_index
9754
10011
  });
9755
10012
  return next;
@@ -9758,6 +10015,8 @@ function HazoUiKanban({
9758
10015
  itemId: active_id_str,
9759
10016
  item,
9760
10017
  columnKey: dest_column_key,
10018
+ sectionKey: dest_section_key,
10019
+ laneKey: dest_lane_key,
9761
10020
  newIndex: new_index,
9762
10021
  revert: handle.revert
9763
10022
  };
@@ -9768,6 +10027,8 @@ function HazoUiKanban({
9768
10027
  const next = new Map(prev);
9769
10028
  next.set(active_id_str, {
9770
10029
  columnKey: dest_column_key,
10030
+ sectionKey: dest_section_key,
10031
+ laneKey: dest_lane_key,
9771
10032
  index: new_index
9772
10033
  });
9773
10034
  return next;
@@ -9777,12 +10038,16 @@ function HazoUiKanban({
9777
10038
  item,
9778
10039
  fromColumn: item.columnKey,
9779
10040
  toColumn: dest_column_key,
10041
+ fromSection: item.sectionKey,
10042
+ toSection: dest_section_key,
10043
+ fromLane: item.laneKey,
10044
+ toLane: dest_lane_key,
9780
10045
  newIndex: new_index,
9781
10046
  revert: handle.revert
9782
10047
  };
9783
10048
  onMove?.(move_event);
9784
10049
  },
9785
- [find_item, columns, grouped, onMove, onReorder, make_revert_handle]
10050
+ [find_item, columns, lanes, grouped, onMove, onReorder, make_revert_handle]
9786
10051
  );
9787
10052
  const handle_drag_cancel = React27.useCallback(() => {
9788
10053
  set_active_id(null);
@@ -9793,13 +10058,56 @@ function HazoUiKanban({
9793
10058
  `(max-width: ${Math.max(0, mobileBreakpoint - 1)}px)`
9794
10059
  );
9795
10060
  const mobile_classes = is_custom_breakpoint ? is_mobile_via_query ? "block" : "hidden" : "sm:hidden";
9796
- const desktop_classes = is_custom_breakpoint ? is_mobile_via_query ? "hidden" : "grid" : "hidden sm:grid";
10061
+ const desktop_classes = is_custom_breakpoint ? is_mobile_via_query ? "hidden" : lanes ? "block" : "grid" : lanes ? "hidden sm:block" : "hidden sm:grid";
10062
+ const grid_style = {
10063
+ gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`
10064
+ };
10065
+ function lane_total(lane_key) {
10066
+ let total = 0;
10067
+ for (const col of columns)
10068
+ total += (grouped.get(bucket_key(lane_key, col.key)) ?? []).length;
10069
+ return total;
10070
+ }
10071
+ function render_column(col, tree_key, lane_key) {
10072
+ return /* @__PURE__ */ jsx(
10073
+ KanbanColumn,
10074
+ {
10075
+ column: col,
10076
+ laneKey: lane_key,
10077
+ treeKey: tree_key,
10078
+ items: grouped.get(bucket_key(lane_key, col.key)) ?? [],
10079
+ renderCard,
10080
+ emptyColumn,
10081
+ cardClassName,
10082
+ showEdit: show_edit,
10083
+ onEditRequest: handle_edit_request
10084
+ },
10085
+ col.key
10086
+ );
10087
+ }
10088
+ function render_lane_row(lane, tree_key, only_column) {
10089
+ const count = only_column ? (grouped.get(bucket_key(lane.key, only_column.key)) ?? []).length : lane_total(lane.key);
10090
+ if (lane.collapseWhenEmpty && count === 0) {
10091
+ return /* @__PURE__ */ jsx(KanbanLaneStrip, { lane, treeKey: tree_key }, lane.key);
10092
+ }
10093
+ return /* @__PURE__ */ jsxs("div", { className: "cls_hazo_kanban_lane", children: [
10094
+ /* @__PURE__ */ jsx(KanbanLaneHead, { lane, count, treeKey: tree_key }),
10095
+ only_column ? render_column(only_column, tree_key, lane.key) : /* @__PURE__ */ 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)) })
10096
+ ] }, lane.key);
10097
+ }
10098
+ function render_unmatched_lane(tree_key, only_column) {
10099
+ const count = only_column ? (grouped.get(bucket_key(UNMATCHED_LANE_KEY, only_column.key)) ?? []).length : lane_total(UNMATCHED_LANE_KEY);
10100
+ if (count === 0) return null;
10101
+ return /* @__PURE__ */ 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__ */ jsx("div", { className: "cls_hazo_kanban_lane_grid grid gap-3", style: grid_style, children: columns.map(
10102
+ (col) => render_column(col, tree_key, UNMATCHED_LANE_KEY)
10103
+ ) }) });
10104
+ }
9797
10105
  return /* @__PURE__ */ jsxs(
9798
10106
  DndContext,
9799
10107
  {
9800
10108
  id: dnd_context_id,
9801
10109
  sensors,
9802
- collisionDetection: closestCorners,
10110
+ collisionDetection: collision_detection,
9803
10111
  onDragStart: handle_drag_start,
9804
10112
  onDragEnd: handle_drag_end,
9805
10113
  onDragCancel: handle_drag_cancel,
@@ -9808,43 +10116,33 @@ function HazoUiKanban({
9808
10116
  /* @__PURE__ */ jsxs("div", { className: cn("cls_hazo_kanban w-full", className), children: [
9809
10117
  /* @__PURE__ */ jsx("div", { className: cn("cls_hazo_kanban_mobile", mobile_classes), children: /* @__PURE__ */ jsxs(Tabs, { value: active_tab, onValueChange: set_active_tab, children: [
9810
10118
  /* @__PURE__ */ jsx(TabsList, { className: "grid w-full", style: { gridTemplateColumns: `repeat(${columns.length}, 1fr)` }, children: columns.map((col) => {
9811
- const count = (grouped.get(col.key) ?? []).length;
10119
+ const count = lanes ? lanes.reduce(
10120
+ (n, lane) => n + (grouped.get(bucket_key(lane.key, col.key)) ?? []).length,
10121
+ (grouped.get(bucket_key(UNMATCHED_LANE_KEY, col.key)) ?? []).length
10122
+ ) : (grouped.get(col.key) ?? []).length;
9812
10123
  return /* @__PURE__ */ jsxs(TabsTrigger, { value: col.key, className: "flex flex-col gap-0.5", children: [
9813
10124
  /* @__PURE__ */ jsx("span", { className: "text-xs", children: col.title }),
9814
10125
  /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: count })
9815
10126
  ] }, col.key);
9816
10127
  }) }),
9817
- columns.map((col) => /* @__PURE__ */ jsx(TabsContent, { value: col.key, className: "mt-3", children: /* @__PURE__ */ jsx(
9818
- KanbanColumn,
9819
- {
9820
- column: col,
9821
- items: grouped.get(col.key) ?? [],
9822
- renderCard,
9823
- emptyColumn,
9824
- cardClassName,
9825
- showEdit: show_edit,
9826
- onEditRequest: handle_edit_request
9827
- }
9828
- ) }, col.key))
10128
+ columns.map((col) => /* @__PURE__ */ jsx(TabsContent, { value: col.key, className: "mt-3", children: lanes ? /* @__PURE__ */ jsxs("div", { className: "cls_hazo_kanban_lanes flex flex-col gap-4", children: [
10129
+ lanes.map((lane) => render_lane_row(lane, "m", col)),
10130
+ render_unmatched_lane("m", col)
10131
+ ] }) : render_column(col, "m") }, col.key))
9829
10132
  ] }) }),
9830
10133
  /* @__PURE__ */ jsx(
9831
10134
  "div",
9832
10135
  {
9833
- className: cn("cls_hazo_kanban_desktop gap-3", desktop_classes),
9834
- style: { gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))` },
9835
- children: columns.map((col) => /* @__PURE__ */ jsx(
9836
- KanbanColumn,
9837
- {
9838
- column: col,
9839
- items: grouped.get(col.key) ?? [],
9840
- renderCard,
9841
- emptyColumn,
9842
- cardClassName,
9843
- showEdit: show_edit,
9844
- onEditRequest: handle_edit_request
9845
- },
9846
- col.key
9847
- ))
10136
+ className: cn(
10137
+ "cls_hazo_kanban_desktop",
10138
+ lanes ? "space-y-4" : "gap-3",
10139
+ desktop_classes
10140
+ ),
10141
+ style: lanes ? void 0 : grid_style,
10142
+ children: lanes ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
10143
+ lanes.map((lane) => render_lane_row(lane, "d")),
10144
+ render_unmatched_lane("d")
10145
+ ] }) : columns.map((col) => render_column(col, "d"))
9848
10146
  }
9849
10147
  ),
9850
10148
  /* @__PURE__ */ jsx(DragOverlay, { children: active_item ? /* @__PURE__ */ jsx(