@almadar/ui 4.50.14 → 4.50.16

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,32 @@ 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 optimisticEntry = sharedOptimistic.get(ownGroup);
21353
+ const orderedItems = optimisticEntry ?? items;
21354
+ if (isZone && enabled) {
21355
+ dndLog.debug("hook:render", {
21356
+ group: ownGroup,
21357
+ isRoot,
21358
+ itemsLen: items.length,
21359
+ optimisticEntryLen: optimisticEntry ? optimisticEntry.length : null,
21360
+ orderedLen: orderedItems.length,
21361
+ sharedKeys: Array.from(sharedOptimistic.keys())
21362
+ });
21363
+ }
21343
21364
  const itemIdsSignature = orderedItems.map((it, idx) => {
21344
21365
  const raw = it[dndItemIdField];
21345
21366
  return String(raw ?? `__idx_${idx}`);
@@ -21352,6 +21373,15 @@ function useDataDnd(args) {
21352
21373
  // eslint-disable-next-line react-hooks/exhaustive-deps
21353
21374
  [itemIdsSignature]
21354
21375
  );
21376
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
21377
+ React81__namespace.default.useEffect(() => {
21378
+ const root = isRoot ? null : parentRoot;
21379
+ if (root) {
21380
+ root.clearOptimisticOrder(ownGroup);
21381
+ } else {
21382
+ clearOptimisticOrder(ownGroup);
21383
+ }
21384
+ }, [itemsContentSig, ownGroup]);
21355
21385
  const zonesRef = React81__namespace.default.useRef(/* @__PURE__ */ new Map());
21356
21386
  const registerZone = React81__namespace.default.useCallback((zoneId2, meta2) => {
21357
21387
  zonesRef.current.set(zoneId2, meta2);
@@ -21361,11 +21391,9 @@ function useDataDnd(args) {
21361
21391
  }, []);
21362
21392
  const [activeDrag, setActiveDrag] = React81__namespace.default.useState(null);
21363
21393
  const [overZoneGroup, setOverZoneGroup] = React81__namespace.default.useState(null);
21364
- const zoneId = React81__namespace.default.useId();
21365
- const ownGroup = dragGroup ?? accepts ?? zoneId;
21366
21394
  const meta = React81__namespace.default.useMemo(
21367
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
21368
- [ownGroup, dropEvent, reorderEvent, itemIds]
21395
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
21396
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
21369
21397
  );
21370
21398
  React81__namespace.default.useEffect(() => {
21371
21399
  const target = isRoot ? null : parentRoot;
@@ -21412,7 +21440,7 @@ function useDataDnd(args) {
21412
21440
  },
21413
21441
  []
21414
21442
  );
21415
- const findZoneByGroup = React81__namespace.default.useCallback(
21443
+ React81__namespace.default.useCallback(
21416
21444
  (group) => {
21417
21445
  for (const z of zonesRef.current.values()) {
21418
21446
  if (z.group === group) return z;
@@ -21424,81 +21452,76 @@ function useDataDnd(args) {
21424
21452
  const handleDragEnd = React81__namespace.default.useCallback(
21425
21453
  (event) => {
21426
21454
  const { active, over } = event;
21427
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
21455
+ const activeIdStr = String(active.id);
21428
21456
  dndLog.debug("dragEnd:received", {
21429
21457
  activeId: active.id,
21430
21458
  overId: over?.id,
21431
- overData: over?.data?.current,
21432
- zones: allZones
21459
+ overData: over?.data?.current
21433
21460
  });
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" });
21436
- return;
21437
- }
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 });
21461
+ let sourceMeta;
21462
+ let oldIndex = -1;
21463
+ let targetMeta;
21464
+ let newIndex = -1;
21465
+ for (const m of zonesRef.current.values()) {
21466
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21467
+ if (rawIdx >= 0) {
21468
+ sourceMeta = m;
21469
+ oldIndex = rawIdx;
21470
+ }
21471
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21472
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21473
+ if (curIdx >= 0) {
21474
+ targetMeta = m;
21475
+ newIndex = curIdx;
21476
+ }
21477
+ }
21478
+ if (!sourceMeta || !targetMeta) {
21479
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
21453
21480
  return;
21454
21481
  }
21455
- if (sourceZone.group !== targetZone.group) {
21456
- if (targetZone.dropEvent) {
21457
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
21458
- const evt = `UI:${targetZone.dropEvent}`;
21482
+ if (sourceMeta.group !== targetMeta.group) {
21483
+ if (targetMeta.dropEvent) {
21484
+ const evt = `UI:${targetMeta.dropEvent}`;
21459
21485
  dndLog.info("dragEnd:cross-container:emit", {
21460
21486
  event: evt,
21461
- id: String(active.id),
21462
- sourceGroup: sourceZone.group,
21463
- targetGroup: targetZone.group,
21464
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21487
+ id: activeIdStr,
21488
+ sourceGroup: sourceMeta.group,
21489
+ targetGroup: targetMeta.group,
21490
+ newIndex
21465
21491
  });
21466
21492
  eventBus.emit(evt, {
21467
- id: String(active.id),
21468
- sourceGroup: sourceZone.group,
21469
- targetGroup: targetZone.group,
21470
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21493
+ id: activeIdStr,
21494
+ sourceGroup: sourceMeta.group,
21495
+ targetGroup: targetMeta.group,
21496
+ newIndex
21471
21497
  });
21472
21498
  } else {
21473
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
21499
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
21474
21500
  }
21475
21501
  return;
21476
21502
  }
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);
21503
+ if (oldIndex === newIndex) {
21504
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
21505
+ return;
21483
21506
  }
21484
- if (sourceZone.reorderEvent) {
21485
- const evt = `UI:${sourceZone.reorderEvent}`;
21507
+ if (sourceMeta.reorderEvent) {
21508
+ const evt = `UI:${sourceMeta.reorderEvent}`;
21486
21509
  dndLog.info("dragEnd:reorder:emit", {
21487
21510
  event: evt,
21488
- id: String(active.id),
21511
+ id: activeIdStr,
21489
21512
  oldIndex,
21490
21513
  newIndex
21491
21514
  });
21492
21515
  eventBus.emit(evt, {
21493
- id: String(active.id),
21516
+ id: activeIdStr,
21494
21517
  oldIndex,
21495
21518
  newIndex
21496
21519
  });
21497
21520
  } else {
21498
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
21521
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
21499
21522
  }
21500
21523
  },
21501
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
21524
+ [eventBus]
21502
21525
  );
21503
21526
  const sortableData = React81__namespace.default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
21504
21527
  const SortableItem = React81__namespace.default.useCallback(
@@ -21559,30 +21582,20 @@ function useDataDnd(args) {
21559
21582
  React81__namespace.default.useEffect(() => {
21560
21583
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
21561
21584
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
21562
- return /* @__PURE__ */ jsxRuntime.jsxs(
21585
+ return /* @__PURE__ */ jsxRuntime.jsx(
21563
21586
  Box,
21564
21587
  {
21565
21588
  ref: setNodeRef,
21566
21589
  "data-dnd-zone": ownGroup,
21567
21590
  "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
- ]
21591
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21592
+ children
21580
21593
  }
21581
21594
  );
21582
21595
  };
21583
21596
  const rootContextValue = React81__namespace.default.useMemo(
21584
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
21585
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
21597
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
21598
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
21586
21599
  );
21587
21600
  const handleDragStart = React81__namespace.default.useCallback((event) => {
21588
21601
  const sourceZone = findZoneByItem(event.active.id);
@@ -21604,13 +21617,73 @@ function useDataDnd(args) {
21604
21617
  });
21605
21618
  }, [findZoneByItem, isRoot, zoneId]);
21606
21619
  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
21620
+ const { active, over } = event;
21621
+ const overData = over?.data?.current;
21622
+ const overGroup = overData?.dndGroup ?? null;
21623
+ setOverZoneGroup(overGroup);
21624
+ if (!over || !overGroup) return;
21625
+ const activeIdStr = String(active.id);
21626
+ let sourceMeta;
21627
+ let sourceGroup;
21628
+ for (const m of zonesRef.current.values()) {
21629
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21630
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
21631
+ if (found) {
21632
+ sourceMeta = m;
21633
+ sourceGroup = m.group;
21634
+ break;
21635
+ }
21636
+ }
21637
+ if (!sourceMeta || !sourceGroup) {
21638
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
21639
+ return;
21640
+ }
21641
+ let targetMeta;
21642
+ for (const m of zonesRef.current.values()) {
21643
+ if (m.group === overGroup) {
21644
+ targetMeta = m;
21645
+ break;
21646
+ }
21647
+ }
21648
+ if (!targetMeta) {
21649
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
21650
+ return;
21651
+ }
21652
+ if (sourceGroup === overGroup) {
21653
+ setOptimisticOrders((prev) => {
21654
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21655
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
21656
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
21657
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
21658
+ const reordered = sortable.arrayMove([...currentItems], oldIndex, newIndex);
21659
+ const next = new Map(prev);
21660
+ next.set(sourceGroup, reordered);
21661
+ return next;
21662
+ });
21663
+ return;
21664
+ }
21665
+ setOptimisticOrders((prev) => {
21666
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21667
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
21668
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
21669
+ if (!activeItem) return prev;
21670
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
21671
+ return prev;
21672
+ }
21673
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
21674
+ const overIdStr = String(over.id);
21675
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
21676
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
21677
+ const newTarget = [
21678
+ ...currentTarget.slice(0, insertAt),
21679
+ activeItem,
21680
+ ...currentTarget.slice(insertAt)
21681
+ ];
21682
+ const next = new Map(prev);
21683
+ next.set(sourceGroup, newSource);
21684
+ next.set(overGroup, newTarget);
21685
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
21686
+ return next;
21614
21687
  });
21615
21688
  }, []);
21616
21689
  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,32 @@ 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 optimisticEntry = sharedOptimistic.get(ownGroup);
21307
+ const orderedItems = optimisticEntry ?? items;
21308
+ if (isZone && enabled) {
21309
+ dndLog.debug("hook:render", {
21310
+ group: ownGroup,
21311
+ isRoot,
21312
+ itemsLen: items.length,
21313
+ optimisticEntryLen: optimisticEntry ? optimisticEntry.length : null,
21314
+ orderedLen: orderedItems.length,
21315
+ sharedKeys: Array.from(sharedOptimistic.keys())
21316
+ });
21317
+ }
21297
21318
  const itemIdsSignature = orderedItems.map((it, idx) => {
21298
21319
  const raw = it[dndItemIdField];
21299
21320
  return String(raw ?? `__idx_${idx}`);
@@ -21306,6 +21327,15 @@ function useDataDnd(args) {
21306
21327
  // eslint-disable-next-line react-hooks/exhaustive-deps
21307
21328
  [itemIdsSignature]
21308
21329
  );
21330
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
21331
+ React81__default.useEffect(() => {
21332
+ const root = isRoot ? null : parentRoot;
21333
+ if (root) {
21334
+ root.clearOptimisticOrder(ownGroup);
21335
+ } else {
21336
+ clearOptimisticOrder(ownGroup);
21337
+ }
21338
+ }, [itemsContentSig, ownGroup]);
21309
21339
  const zonesRef = React81__default.useRef(/* @__PURE__ */ new Map());
21310
21340
  const registerZone = React81__default.useCallback((zoneId2, meta2) => {
21311
21341
  zonesRef.current.set(zoneId2, meta2);
@@ -21315,11 +21345,9 @@ function useDataDnd(args) {
21315
21345
  }, []);
21316
21346
  const [activeDrag, setActiveDrag] = React81__default.useState(null);
21317
21347
  const [overZoneGroup, setOverZoneGroup] = React81__default.useState(null);
21318
- const zoneId = React81__default.useId();
21319
- const ownGroup = dragGroup ?? accepts ?? zoneId;
21320
21348
  const meta = React81__default.useMemo(
21321
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
21322
- [ownGroup, dropEvent, reorderEvent, itemIds]
21349
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
21350
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
21323
21351
  );
21324
21352
  React81__default.useEffect(() => {
21325
21353
  const target = isRoot ? null : parentRoot;
@@ -21366,7 +21394,7 @@ function useDataDnd(args) {
21366
21394
  },
21367
21395
  []
21368
21396
  );
21369
- const findZoneByGroup = React81__default.useCallback(
21397
+ React81__default.useCallback(
21370
21398
  (group) => {
21371
21399
  for (const z of zonesRef.current.values()) {
21372
21400
  if (z.group === group) return z;
@@ -21378,81 +21406,76 @@ function useDataDnd(args) {
21378
21406
  const handleDragEnd = React81__default.useCallback(
21379
21407
  (event) => {
21380
21408
  const { active, over } = event;
21381
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
21409
+ const activeIdStr = String(active.id);
21382
21410
  dndLog.debug("dragEnd:received", {
21383
21411
  activeId: active.id,
21384
21412
  overId: over?.id,
21385
- overData: over?.data?.current,
21386
- zones: allZones
21413
+ overData: over?.data?.current
21387
21414
  });
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" });
21390
- return;
21391
- }
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 });
21415
+ let sourceMeta;
21416
+ let oldIndex = -1;
21417
+ let targetMeta;
21418
+ let newIndex = -1;
21419
+ for (const m of zonesRef.current.values()) {
21420
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21421
+ if (rawIdx >= 0) {
21422
+ sourceMeta = m;
21423
+ oldIndex = rawIdx;
21424
+ }
21425
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21426
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21427
+ if (curIdx >= 0) {
21428
+ targetMeta = m;
21429
+ newIndex = curIdx;
21430
+ }
21431
+ }
21432
+ if (!sourceMeta || !targetMeta) {
21433
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
21407
21434
  return;
21408
21435
  }
21409
- if (sourceZone.group !== targetZone.group) {
21410
- if (targetZone.dropEvent) {
21411
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
21412
- const evt = `UI:${targetZone.dropEvent}`;
21436
+ if (sourceMeta.group !== targetMeta.group) {
21437
+ if (targetMeta.dropEvent) {
21438
+ const evt = `UI:${targetMeta.dropEvent}`;
21413
21439
  dndLog.info("dragEnd:cross-container:emit", {
21414
21440
  event: evt,
21415
- id: String(active.id),
21416
- sourceGroup: sourceZone.group,
21417
- targetGroup: targetZone.group,
21418
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21441
+ id: activeIdStr,
21442
+ sourceGroup: sourceMeta.group,
21443
+ targetGroup: targetMeta.group,
21444
+ newIndex
21419
21445
  });
21420
21446
  eventBus.emit(evt, {
21421
- id: String(active.id),
21422
- sourceGroup: sourceZone.group,
21423
- targetGroup: targetZone.group,
21424
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21447
+ id: activeIdStr,
21448
+ sourceGroup: sourceMeta.group,
21449
+ targetGroup: targetMeta.group,
21450
+ newIndex
21425
21451
  });
21426
21452
  } else {
21427
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
21453
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
21428
21454
  }
21429
21455
  return;
21430
21456
  }
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);
21457
+ if (oldIndex === newIndex) {
21458
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
21459
+ return;
21437
21460
  }
21438
- if (sourceZone.reorderEvent) {
21439
- const evt = `UI:${sourceZone.reorderEvent}`;
21461
+ if (sourceMeta.reorderEvent) {
21462
+ const evt = `UI:${sourceMeta.reorderEvent}`;
21440
21463
  dndLog.info("dragEnd:reorder:emit", {
21441
21464
  event: evt,
21442
- id: String(active.id),
21465
+ id: activeIdStr,
21443
21466
  oldIndex,
21444
21467
  newIndex
21445
21468
  });
21446
21469
  eventBus.emit(evt, {
21447
- id: String(active.id),
21470
+ id: activeIdStr,
21448
21471
  oldIndex,
21449
21472
  newIndex
21450
21473
  });
21451
21474
  } else {
21452
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
21475
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
21453
21476
  }
21454
21477
  },
21455
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
21478
+ [eventBus]
21456
21479
  );
21457
21480
  const sortableData = React81__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
21458
21481
  const SortableItem = React81__default.useCallback(
@@ -21513,30 +21536,20 @@ function useDataDnd(args) {
21513
21536
  React81__default.useEffect(() => {
21514
21537
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
21515
21538
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
21516
- return /* @__PURE__ */ jsxs(
21539
+ return /* @__PURE__ */ jsx(
21517
21540
  Box,
21518
21541
  {
21519
21542
  ref: setNodeRef,
21520
21543
  "data-dnd-zone": ownGroup,
21521
21544
  "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
- ]
21545
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21546
+ children
21534
21547
  }
21535
21548
  );
21536
21549
  };
21537
21550
  const rootContextValue = React81__default.useMemo(
21538
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
21539
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
21551
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
21552
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
21540
21553
  );
21541
21554
  const handleDragStart = React81__default.useCallback((event) => {
21542
21555
  const sourceZone = findZoneByItem(event.active.id);
@@ -21558,13 +21571,73 @@ function useDataDnd(args) {
21558
21571
  });
21559
21572
  }, [findZoneByItem, isRoot, zoneId]);
21560
21573
  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
21574
+ const { active, over } = event;
21575
+ const overData = over?.data?.current;
21576
+ const overGroup = overData?.dndGroup ?? null;
21577
+ setOverZoneGroup(overGroup);
21578
+ if (!over || !overGroup) return;
21579
+ const activeIdStr = String(active.id);
21580
+ let sourceMeta;
21581
+ let sourceGroup;
21582
+ for (const m of zonesRef.current.values()) {
21583
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21584
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
21585
+ if (found) {
21586
+ sourceMeta = m;
21587
+ sourceGroup = m.group;
21588
+ break;
21589
+ }
21590
+ }
21591
+ if (!sourceMeta || !sourceGroup) {
21592
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
21593
+ return;
21594
+ }
21595
+ let targetMeta;
21596
+ for (const m of zonesRef.current.values()) {
21597
+ if (m.group === overGroup) {
21598
+ targetMeta = m;
21599
+ break;
21600
+ }
21601
+ }
21602
+ if (!targetMeta) {
21603
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
21604
+ return;
21605
+ }
21606
+ if (sourceGroup === overGroup) {
21607
+ setOptimisticOrders((prev) => {
21608
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21609
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
21610
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
21611
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
21612
+ const reordered = arrayMove([...currentItems], oldIndex, newIndex);
21613
+ const next = new Map(prev);
21614
+ next.set(sourceGroup, reordered);
21615
+ return next;
21616
+ });
21617
+ return;
21618
+ }
21619
+ setOptimisticOrders((prev) => {
21620
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21621
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
21622
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
21623
+ if (!activeItem) return prev;
21624
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
21625
+ return prev;
21626
+ }
21627
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
21628
+ const overIdStr = String(over.id);
21629
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
21630
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
21631
+ const newTarget = [
21632
+ ...currentTarget.slice(0, insertAt),
21633
+ activeItem,
21634
+ ...currentTarget.slice(insertAt)
21635
+ ];
21636
+ const next = new Map(prev);
21637
+ next.set(sourceGroup, newSource);
21638
+ next.set(overGroup, newTarget);
21639
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
21640
+ return next;
21568
21641
  });
21569
21642
  }, []);
21570
21643
  const handleDragCancel = React81__default.useCallback((event) => {