@almadar/ui 4.50.13 → 4.50.15

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.
@@ -21335,11 +21335,21 @@ function useDataDnd(args) {
21335
21335
  const eventBus = useEventBus();
21336
21336
  const parentRoot = React81__namespace.default.useContext(RootCtx);
21337
21337
  const isRoot = enabled && parentRoot === null;
21338
- const [localOrder, setLocalOrder] = React81__namespace.default.useState(null);
21339
- const orderedItems = localOrder ?? items;
21340
- React81__namespace.default.useEffect(() => {
21341
- setLocalOrder(null);
21342
- }, [items]);
21338
+ const zoneId = React81__namespace.default.useId();
21339
+ const ownGroup = dragGroup ?? accepts ?? zoneId;
21340
+ const [optimisticOrders, setOptimisticOrders] = React81__namespace.default.useState(() => /* @__PURE__ */ new Map());
21341
+ const optimisticOrdersRef = React81__namespace.default.useRef(optimisticOrders);
21342
+ optimisticOrdersRef.current = optimisticOrders;
21343
+ const clearOptimisticOrder = React81__namespace.default.useCallback((group) => {
21344
+ setOptimisticOrders((prev) => {
21345
+ if (!prev.has(group)) return prev;
21346
+ const next = new Map(prev);
21347
+ next.delete(group);
21348
+ return next;
21349
+ });
21350
+ }, []);
21351
+ const sharedOptimistic = isRoot ? optimisticOrders : parentRoot?.optimisticOrders ?? /* @__PURE__ */ new Map();
21352
+ const orderedItems = sharedOptimistic.get(ownGroup) ?? items;
21343
21353
  const itemIdsSignature = orderedItems.map((it, idx) => {
21344
21354
  const raw = it[dndItemIdField];
21345
21355
  return String(raw ?? `__idx_${idx}`);
@@ -21352,6 +21362,15 @@ function useDataDnd(args) {
21352
21362
  // eslint-disable-next-line react-hooks/exhaustive-deps
21353
21363
  [itemIdsSignature]
21354
21364
  );
21365
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
21366
+ React81__namespace.default.useEffect(() => {
21367
+ const root = isRoot ? null : parentRoot;
21368
+ if (root) {
21369
+ root.clearOptimisticOrder(ownGroup);
21370
+ } else {
21371
+ clearOptimisticOrder(ownGroup);
21372
+ }
21373
+ }, [itemsContentSig, ownGroup]);
21355
21374
  const zonesRef = React81__namespace.default.useRef(/* @__PURE__ */ new Map());
21356
21375
  const registerZone = React81__namespace.default.useCallback((zoneId2, meta2) => {
21357
21376
  zonesRef.current.set(zoneId2, meta2);
@@ -21361,11 +21380,9 @@ function useDataDnd(args) {
21361
21380
  }, []);
21362
21381
  const [activeDrag, setActiveDrag] = React81__namespace.default.useState(null);
21363
21382
  const [overZoneGroup, setOverZoneGroup] = React81__namespace.default.useState(null);
21364
- const zoneId = React81__namespace.default.useId();
21365
- const ownGroup = dragGroup ?? accepts ?? zoneId;
21366
21383
  const meta = React81__namespace.default.useMemo(
21367
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
21368
- [ownGroup, dropEvent, reorderEvent, itemIds]
21384
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
21385
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
21369
21386
  );
21370
21387
  React81__namespace.default.useEffect(() => {
21371
21388
  const target = isRoot ? null : parentRoot;
@@ -21412,7 +21429,7 @@ function useDataDnd(args) {
21412
21429
  },
21413
21430
  []
21414
21431
  );
21415
- const findZoneByGroup = React81__namespace.default.useCallback(
21432
+ React81__namespace.default.useCallback(
21416
21433
  (group) => {
21417
21434
  for (const z of zonesRef.current.values()) {
21418
21435
  if (z.group === group) return z;
@@ -21424,81 +21441,76 @@ function useDataDnd(args) {
21424
21441
  const handleDragEnd = React81__namespace.default.useCallback(
21425
21442
  (event) => {
21426
21443
  const { active, over } = event;
21427
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
21444
+ const activeIdStr = String(active.id);
21428
21445
  dndLog.debug("dragEnd:received", {
21429
21446
  activeId: active.id,
21430
21447
  overId: over?.id,
21431
- overData: over?.data?.current,
21432
- zones: allZones
21448
+ overData: over?.data?.current
21433
21449
  });
21434
- if (!over) {
21435
- dndLog.warn("dragEnd:abort:no-over", { activeId: active.id, reason: "no droppable under pointer at drop time \u2192 item snaps back" });
21450
+ let sourceMeta;
21451
+ let oldIndex = -1;
21452
+ let targetMeta;
21453
+ let newIndex = -1;
21454
+ for (const m of zonesRef.current.values()) {
21455
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21456
+ if (rawIdx >= 0) {
21457
+ sourceMeta = m;
21458
+ oldIndex = rawIdx;
21459
+ }
21460
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21461
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21462
+ if (curIdx >= 0) {
21463
+ targetMeta = m;
21464
+ newIndex = curIdx;
21465
+ }
21466
+ }
21467
+ if (!sourceMeta || !targetMeta) {
21468
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
21436
21469
  return;
21437
21470
  }
21438
- const sourceZone = findZoneByItem(active.id);
21439
- const overData = over.data?.current;
21440
- const targetGroup = overData?.dndGroup;
21441
- dndLog.debug("dragEnd:resolved", { sourceGroup: sourceZone?.group, targetGroup, overDataKeys: overData ? Object.keys(overData) : null });
21442
- if (!sourceZone) {
21443
- dndLog.warn("dragEnd:abort:no-source-zone", { activeId: active.id });
21444
- return;
21445
- }
21446
- if (!targetGroup) {
21447
- dndLog.warn("dragEnd:abort:no-target-group", { overId: over.id, overData });
21448
- return;
21449
- }
21450
- const targetZone = findZoneByGroup(targetGroup);
21451
- if (!targetZone) {
21452
- dndLog.warn("dragEnd:abort:target-zone-not-registered", { targetGroup });
21453
- return;
21454
- }
21455
- if (sourceZone.group !== targetZone.group) {
21456
- if (targetZone.dropEvent) {
21457
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
21458
- const evt = `UI:${targetZone.dropEvent}`;
21471
+ if (sourceMeta.group !== targetMeta.group) {
21472
+ if (targetMeta.dropEvent) {
21473
+ const evt = `UI:${targetMeta.dropEvent}`;
21459
21474
  dndLog.info("dragEnd:cross-container:emit", {
21460
21475
  event: evt,
21461
- id: String(active.id),
21462
- sourceGroup: sourceZone.group,
21463
- targetGroup: targetZone.group,
21464
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21476
+ id: activeIdStr,
21477
+ sourceGroup: sourceMeta.group,
21478
+ targetGroup: targetMeta.group,
21479
+ newIndex
21465
21480
  });
21466
21481
  eventBus.emit(evt, {
21467
- id: String(active.id),
21468
- sourceGroup: sourceZone.group,
21469
- targetGroup: targetZone.group,
21470
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21482
+ id: activeIdStr,
21483
+ sourceGroup: sourceMeta.group,
21484
+ targetGroup: targetMeta.group,
21485
+ newIndex
21471
21486
  });
21472
21487
  } else {
21473
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
21488
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
21474
21489
  }
21475
21490
  return;
21476
21491
  }
21477
- const oldIndex = sourceZone.itemIds.indexOf(active.id);
21478
- const newIndex = sourceZone.itemIds.indexOf(over.id);
21479
- if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return;
21480
- if (sourceZone.group === ownGroup) {
21481
- const reordered = sortable.arrayMove(orderedItems, oldIndex, newIndex);
21482
- setLocalOrder(reordered);
21492
+ if (oldIndex === newIndex) {
21493
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
21494
+ return;
21483
21495
  }
21484
- if (sourceZone.reorderEvent) {
21485
- const evt = `UI:${sourceZone.reorderEvent}`;
21496
+ if (sourceMeta.reorderEvent) {
21497
+ const evt = `UI:${sourceMeta.reorderEvent}`;
21486
21498
  dndLog.info("dragEnd:reorder:emit", {
21487
21499
  event: evt,
21488
- id: String(active.id),
21500
+ id: activeIdStr,
21489
21501
  oldIndex,
21490
21502
  newIndex
21491
21503
  });
21492
21504
  eventBus.emit(evt, {
21493
- id: String(active.id),
21505
+ id: activeIdStr,
21494
21506
  oldIndex,
21495
21507
  newIndex
21496
21508
  });
21497
21509
  } else {
21498
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
21510
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
21499
21511
  }
21500
21512
  },
21501
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
21513
+ [eventBus]
21502
21514
  );
21503
21515
  const sortableData = React81__namespace.default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
21504
21516
  const SortableItem = React81__namespace.default.useCallback(
@@ -21559,30 +21571,20 @@ function useDataDnd(args) {
21559
21571
  React81__namespace.default.useEffect(() => {
21560
21572
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
21561
21573
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
21562
- return /* @__PURE__ */ jsxRuntime.jsxs(
21574
+ return /* @__PURE__ */ jsxRuntime.jsx(
21563
21575
  Box,
21564
21576
  {
21565
21577
  ref: setNodeRef,
21566
21578
  "data-dnd-zone": ownGroup,
21567
21579
  "data-dnd-is-over": isThisZoneOver ? "true" : "false",
21568
- className: isThisZoneOver ? "ring-2 ring-primary ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21569
- children: [
21570
- children,
21571
- showForeignPlaceholder ? /* @__PURE__ */ jsxRuntime.jsx(
21572
- Box,
21573
- {
21574
- "data-dnd-placeholder": true,
21575
- style: { height: activeDrag2.height },
21576
- className: "border-2 border-dashed border-primary/60 bg-primary/5 rounded-md my-1 transition-all"
21577
- }
21578
- ) : null
21579
- ]
21580
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21581
+ children
21580
21582
  }
21581
21583
  );
21582
21584
  };
21583
21585
  const rootContextValue = React81__namespace.default.useMemo(
21584
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
21585
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
21586
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
21587
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
21586
21588
  );
21587
21589
  const handleDragStart = React81__namespace.default.useCallback((event) => {
21588
21590
  const sourceZone = findZoneByItem(event.active.id);
@@ -21604,13 +21606,73 @@ function useDataDnd(args) {
21604
21606
  });
21605
21607
  }, [findZoneByItem, isRoot, zoneId]);
21606
21608
  const handleDragOver = React81__namespace.default.useCallback((event) => {
21607
- const overData = event.over?.data?.current;
21608
- const group = overData?.dndGroup ?? null;
21609
- setOverZoneGroup(group);
21610
- dndLog.debug("dragOver", {
21611
- activeId: event.active.id,
21612
- overId: event.over?.id,
21613
- overGroup: group
21609
+ const { active, over } = event;
21610
+ const overData = over?.data?.current;
21611
+ const overGroup = overData?.dndGroup ?? null;
21612
+ setOverZoneGroup(overGroup);
21613
+ if (!over || !overGroup) return;
21614
+ const activeIdStr = String(active.id);
21615
+ let sourceMeta;
21616
+ let sourceGroup;
21617
+ for (const m of zonesRef.current.values()) {
21618
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21619
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
21620
+ if (found) {
21621
+ sourceMeta = m;
21622
+ sourceGroup = m.group;
21623
+ break;
21624
+ }
21625
+ }
21626
+ if (!sourceMeta || !sourceGroup) {
21627
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
21628
+ return;
21629
+ }
21630
+ let targetMeta;
21631
+ for (const m of zonesRef.current.values()) {
21632
+ if (m.group === overGroup) {
21633
+ targetMeta = m;
21634
+ break;
21635
+ }
21636
+ }
21637
+ if (!targetMeta) {
21638
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
21639
+ return;
21640
+ }
21641
+ if (sourceGroup === overGroup) {
21642
+ setOptimisticOrders((prev) => {
21643
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21644
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
21645
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
21646
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
21647
+ const reordered = sortable.arrayMove([...currentItems], oldIndex, newIndex);
21648
+ const next = new Map(prev);
21649
+ next.set(sourceGroup, reordered);
21650
+ return next;
21651
+ });
21652
+ return;
21653
+ }
21654
+ setOptimisticOrders((prev) => {
21655
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21656
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
21657
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
21658
+ if (!activeItem) return prev;
21659
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
21660
+ return prev;
21661
+ }
21662
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
21663
+ const overIdStr = String(over.id);
21664
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
21665
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
21666
+ const newTarget = [
21667
+ ...currentTarget.slice(0, insertAt),
21668
+ activeItem,
21669
+ ...currentTarget.slice(insertAt)
21670
+ ];
21671
+ const next = new Map(prev);
21672
+ next.set(sourceGroup, newSource);
21673
+ next.set(overGroup, newTarget);
21674
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
21675
+ return next;
21614
21676
  });
21615
21677
  }, []);
21616
21678
  const handleDragCancel = React81__namespace.default.useCallback((event) => {
@@ -37,7 +37,7 @@ import langGo from 'react-syntax-highlighter/dist/esm/languages/prism/go.js';
37
37
  import langGraphql from 'react-syntax-highlighter/dist/esm/languages/prism/graphql.js';
38
38
  import { isInlineTrait } from '@almadar/core';
39
39
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, pointerWithin, rectIntersection, closestCorners, DndContext, useDroppable } from '@dnd-kit/core';
40
- import { sortableKeyboardCoordinates, arrayMove, useSortable, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
40
+ import { sortableKeyboardCoordinates, useSortable, arrayMove, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
41
41
  import { CSS } from '@dnd-kit/utilities';
42
42
  import { Handle, Position } from '@xyflow/react';
43
43
  import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
@@ -21289,11 +21289,21 @@ function useDataDnd(args) {
21289
21289
  const eventBus = useEventBus();
21290
21290
  const parentRoot = React81__default.useContext(RootCtx);
21291
21291
  const isRoot = enabled && parentRoot === null;
21292
- const [localOrder, setLocalOrder] = React81__default.useState(null);
21293
- const orderedItems = localOrder ?? items;
21294
- React81__default.useEffect(() => {
21295
- setLocalOrder(null);
21296
- }, [items]);
21292
+ const zoneId = React81__default.useId();
21293
+ const ownGroup = dragGroup ?? accepts ?? zoneId;
21294
+ const [optimisticOrders, setOptimisticOrders] = React81__default.useState(() => /* @__PURE__ */ new Map());
21295
+ const optimisticOrdersRef = React81__default.useRef(optimisticOrders);
21296
+ optimisticOrdersRef.current = optimisticOrders;
21297
+ const clearOptimisticOrder = React81__default.useCallback((group) => {
21298
+ setOptimisticOrders((prev) => {
21299
+ if (!prev.has(group)) return prev;
21300
+ const next = new Map(prev);
21301
+ next.delete(group);
21302
+ return next;
21303
+ });
21304
+ }, []);
21305
+ const sharedOptimistic = isRoot ? optimisticOrders : parentRoot?.optimisticOrders ?? /* @__PURE__ */ new Map();
21306
+ const orderedItems = sharedOptimistic.get(ownGroup) ?? items;
21297
21307
  const itemIdsSignature = orderedItems.map((it, idx) => {
21298
21308
  const raw = it[dndItemIdField];
21299
21309
  return String(raw ?? `__idx_${idx}`);
@@ -21306,6 +21316,15 @@ function useDataDnd(args) {
21306
21316
  // eslint-disable-next-line react-hooks/exhaustive-deps
21307
21317
  [itemIdsSignature]
21308
21318
  );
21319
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
21320
+ React81__default.useEffect(() => {
21321
+ const root = isRoot ? null : parentRoot;
21322
+ if (root) {
21323
+ root.clearOptimisticOrder(ownGroup);
21324
+ } else {
21325
+ clearOptimisticOrder(ownGroup);
21326
+ }
21327
+ }, [itemsContentSig, ownGroup]);
21309
21328
  const zonesRef = React81__default.useRef(/* @__PURE__ */ new Map());
21310
21329
  const registerZone = React81__default.useCallback((zoneId2, meta2) => {
21311
21330
  zonesRef.current.set(zoneId2, meta2);
@@ -21315,11 +21334,9 @@ function useDataDnd(args) {
21315
21334
  }, []);
21316
21335
  const [activeDrag, setActiveDrag] = React81__default.useState(null);
21317
21336
  const [overZoneGroup, setOverZoneGroup] = React81__default.useState(null);
21318
- const zoneId = React81__default.useId();
21319
- const ownGroup = dragGroup ?? accepts ?? zoneId;
21320
21337
  const meta = React81__default.useMemo(
21321
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
21322
- [ownGroup, dropEvent, reorderEvent, itemIds]
21338
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
21339
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
21323
21340
  );
21324
21341
  React81__default.useEffect(() => {
21325
21342
  const target = isRoot ? null : parentRoot;
@@ -21366,7 +21383,7 @@ function useDataDnd(args) {
21366
21383
  },
21367
21384
  []
21368
21385
  );
21369
- const findZoneByGroup = React81__default.useCallback(
21386
+ React81__default.useCallback(
21370
21387
  (group) => {
21371
21388
  for (const z of zonesRef.current.values()) {
21372
21389
  if (z.group === group) return z;
@@ -21378,81 +21395,76 @@ function useDataDnd(args) {
21378
21395
  const handleDragEnd = React81__default.useCallback(
21379
21396
  (event) => {
21380
21397
  const { active, over } = event;
21381
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
21398
+ const activeIdStr = String(active.id);
21382
21399
  dndLog.debug("dragEnd:received", {
21383
21400
  activeId: active.id,
21384
21401
  overId: over?.id,
21385
- overData: over?.data?.current,
21386
- zones: allZones
21402
+ overData: over?.data?.current
21387
21403
  });
21388
- if (!over) {
21389
- dndLog.warn("dragEnd:abort:no-over", { activeId: active.id, reason: "no droppable under pointer at drop time \u2192 item snaps back" });
21404
+ let sourceMeta;
21405
+ let oldIndex = -1;
21406
+ let targetMeta;
21407
+ let newIndex = -1;
21408
+ for (const m of zonesRef.current.values()) {
21409
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21410
+ if (rawIdx >= 0) {
21411
+ sourceMeta = m;
21412
+ oldIndex = rawIdx;
21413
+ }
21414
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21415
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21416
+ if (curIdx >= 0) {
21417
+ targetMeta = m;
21418
+ newIndex = curIdx;
21419
+ }
21420
+ }
21421
+ if (!sourceMeta || !targetMeta) {
21422
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
21390
21423
  return;
21391
21424
  }
21392
- const sourceZone = findZoneByItem(active.id);
21393
- const overData = over.data?.current;
21394
- const targetGroup = overData?.dndGroup;
21395
- dndLog.debug("dragEnd:resolved", { sourceGroup: sourceZone?.group, targetGroup, overDataKeys: overData ? Object.keys(overData) : null });
21396
- if (!sourceZone) {
21397
- dndLog.warn("dragEnd:abort:no-source-zone", { activeId: active.id });
21398
- return;
21399
- }
21400
- if (!targetGroup) {
21401
- dndLog.warn("dragEnd:abort:no-target-group", { overId: over.id, overData });
21402
- return;
21403
- }
21404
- const targetZone = findZoneByGroup(targetGroup);
21405
- if (!targetZone) {
21406
- dndLog.warn("dragEnd:abort:target-zone-not-registered", { targetGroup });
21407
- return;
21408
- }
21409
- if (sourceZone.group !== targetZone.group) {
21410
- if (targetZone.dropEvent) {
21411
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
21412
- const evt = `UI:${targetZone.dropEvent}`;
21425
+ if (sourceMeta.group !== targetMeta.group) {
21426
+ if (targetMeta.dropEvent) {
21427
+ const evt = `UI:${targetMeta.dropEvent}`;
21413
21428
  dndLog.info("dragEnd:cross-container:emit", {
21414
21429
  event: evt,
21415
- id: String(active.id),
21416
- sourceGroup: sourceZone.group,
21417
- targetGroup: targetZone.group,
21418
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21430
+ id: activeIdStr,
21431
+ sourceGroup: sourceMeta.group,
21432
+ targetGroup: targetMeta.group,
21433
+ newIndex
21419
21434
  });
21420
21435
  eventBus.emit(evt, {
21421
- id: String(active.id),
21422
- sourceGroup: sourceZone.group,
21423
- targetGroup: targetZone.group,
21424
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21436
+ id: activeIdStr,
21437
+ sourceGroup: sourceMeta.group,
21438
+ targetGroup: targetMeta.group,
21439
+ newIndex
21425
21440
  });
21426
21441
  } else {
21427
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
21442
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
21428
21443
  }
21429
21444
  return;
21430
21445
  }
21431
- const oldIndex = sourceZone.itemIds.indexOf(active.id);
21432
- const newIndex = sourceZone.itemIds.indexOf(over.id);
21433
- if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return;
21434
- if (sourceZone.group === ownGroup) {
21435
- const reordered = arrayMove(orderedItems, oldIndex, newIndex);
21436
- setLocalOrder(reordered);
21446
+ if (oldIndex === newIndex) {
21447
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
21448
+ return;
21437
21449
  }
21438
- if (sourceZone.reorderEvent) {
21439
- const evt = `UI:${sourceZone.reorderEvent}`;
21450
+ if (sourceMeta.reorderEvent) {
21451
+ const evt = `UI:${sourceMeta.reorderEvent}`;
21440
21452
  dndLog.info("dragEnd:reorder:emit", {
21441
21453
  event: evt,
21442
- id: String(active.id),
21454
+ id: activeIdStr,
21443
21455
  oldIndex,
21444
21456
  newIndex
21445
21457
  });
21446
21458
  eventBus.emit(evt, {
21447
- id: String(active.id),
21459
+ id: activeIdStr,
21448
21460
  oldIndex,
21449
21461
  newIndex
21450
21462
  });
21451
21463
  } else {
21452
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
21464
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
21453
21465
  }
21454
21466
  },
21455
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
21467
+ [eventBus]
21456
21468
  );
21457
21469
  const sortableData = React81__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
21458
21470
  const SortableItem = React81__default.useCallback(
@@ -21513,30 +21525,20 @@ function useDataDnd(args) {
21513
21525
  React81__default.useEffect(() => {
21514
21526
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
21515
21527
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
21516
- return /* @__PURE__ */ jsxs(
21528
+ return /* @__PURE__ */ jsx(
21517
21529
  Box,
21518
21530
  {
21519
21531
  ref: setNodeRef,
21520
21532
  "data-dnd-zone": ownGroup,
21521
21533
  "data-dnd-is-over": isThisZoneOver ? "true" : "false",
21522
- className: isThisZoneOver ? "ring-2 ring-primary ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21523
- children: [
21524
- children,
21525
- showForeignPlaceholder ? /* @__PURE__ */ jsx(
21526
- Box,
21527
- {
21528
- "data-dnd-placeholder": true,
21529
- style: { height: activeDrag2.height },
21530
- className: "border-2 border-dashed border-primary/60 bg-primary/5 rounded-md my-1 transition-all"
21531
- }
21532
- ) : null
21533
- ]
21534
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21535
+ children
21534
21536
  }
21535
21537
  );
21536
21538
  };
21537
21539
  const rootContextValue = React81__default.useMemo(
21538
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
21539
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
21540
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
21541
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
21540
21542
  );
21541
21543
  const handleDragStart = React81__default.useCallback((event) => {
21542
21544
  const sourceZone = findZoneByItem(event.active.id);
@@ -21558,13 +21560,73 @@ function useDataDnd(args) {
21558
21560
  });
21559
21561
  }, [findZoneByItem, isRoot, zoneId]);
21560
21562
  const handleDragOver = React81__default.useCallback((event) => {
21561
- const overData = event.over?.data?.current;
21562
- const group = overData?.dndGroup ?? null;
21563
- setOverZoneGroup(group);
21564
- dndLog.debug("dragOver", {
21565
- activeId: event.active.id,
21566
- overId: event.over?.id,
21567
- overGroup: group
21563
+ const { active, over } = event;
21564
+ const overData = over?.data?.current;
21565
+ const overGroup = overData?.dndGroup ?? null;
21566
+ setOverZoneGroup(overGroup);
21567
+ if (!over || !overGroup) return;
21568
+ const activeIdStr = String(active.id);
21569
+ let sourceMeta;
21570
+ let sourceGroup;
21571
+ for (const m of zonesRef.current.values()) {
21572
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21573
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
21574
+ if (found) {
21575
+ sourceMeta = m;
21576
+ sourceGroup = m.group;
21577
+ break;
21578
+ }
21579
+ }
21580
+ if (!sourceMeta || !sourceGroup) {
21581
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
21582
+ return;
21583
+ }
21584
+ let targetMeta;
21585
+ for (const m of zonesRef.current.values()) {
21586
+ if (m.group === overGroup) {
21587
+ targetMeta = m;
21588
+ break;
21589
+ }
21590
+ }
21591
+ if (!targetMeta) {
21592
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
21593
+ return;
21594
+ }
21595
+ if (sourceGroup === overGroup) {
21596
+ setOptimisticOrders((prev) => {
21597
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21598
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
21599
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
21600
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
21601
+ const reordered = arrayMove([...currentItems], oldIndex, newIndex);
21602
+ const next = new Map(prev);
21603
+ next.set(sourceGroup, reordered);
21604
+ return next;
21605
+ });
21606
+ return;
21607
+ }
21608
+ setOptimisticOrders((prev) => {
21609
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21610
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
21611
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
21612
+ if (!activeItem) return prev;
21613
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
21614
+ return prev;
21615
+ }
21616
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
21617
+ const overIdStr = String(over.id);
21618
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
21619
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
21620
+ const newTarget = [
21621
+ ...currentTarget.slice(0, insertAt),
21622
+ activeItem,
21623
+ ...currentTarget.slice(insertAt)
21624
+ ];
21625
+ const next = new Map(prev);
21626
+ next.set(sourceGroup, newSource);
21627
+ next.set(overGroup, newTarget);
21628
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
21629
+ return next;
21568
21630
  });
21569
21631
  }, []);
21570
21632
  const handleDragCancel = React81__default.useCallback((event) => {