@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.
@@ -20090,11 +20090,32 @@ function useDataDnd(args) {
20090
20090
  const eventBus = useEventBus();
20091
20091
  const parentRoot = React75__namespace.default.useContext(RootCtx);
20092
20092
  const isRoot = enabled && parentRoot === null;
20093
- const [localOrder, setLocalOrder] = React75__namespace.default.useState(null);
20094
- const orderedItems = localOrder ?? items;
20095
- React75__namespace.default.useEffect(() => {
20096
- setLocalOrder(null);
20097
- }, [items]);
20093
+ const zoneId = React75__namespace.default.useId();
20094
+ const ownGroup = dragGroup ?? accepts ?? zoneId;
20095
+ const [optimisticOrders, setOptimisticOrders] = React75__namespace.default.useState(() => /* @__PURE__ */ new Map());
20096
+ const optimisticOrdersRef = React75__namespace.default.useRef(optimisticOrders);
20097
+ optimisticOrdersRef.current = optimisticOrders;
20098
+ const clearOptimisticOrder = React75__namespace.default.useCallback((group) => {
20099
+ setOptimisticOrders((prev) => {
20100
+ if (!prev.has(group)) return prev;
20101
+ const next = new Map(prev);
20102
+ next.delete(group);
20103
+ return next;
20104
+ });
20105
+ }, []);
20106
+ const sharedOptimistic = isRoot ? optimisticOrders : parentRoot?.optimisticOrders ?? /* @__PURE__ */ new Map();
20107
+ const optimisticEntry = sharedOptimistic.get(ownGroup);
20108
+ const orderedItems = optimisticEntry ?? items;
20109
+ if (isZone && enabled) {
20110
+ dndLog.debug("hook:render", {
20111
+ group: ownGroup,
20112
+ isRoot,
20113
+ itemsLen: items.length,
20114
+ optimisticEntryLen: optimisticEntry ? optimisticEntry.length : null,
20115
+ orderedLen: orderedItems.length,
20116
+ sharedKeys: Array.from(sharedOptimistic.keys())
20117
+ });
20118
+ }
20098
20119
  const itemIdsSignature = orderedItems.map((it, idx) => {
20099
20120
  const raw = it[dndItemIdField];
20100
20121
  return String(raw ?? `__idx_${idx}`);
@@ -20107,6 +20128,15 @@ function useDataDnd(args) {
20107
20128
  // eslint-disable-next-line react-hooks/exhaustive-deps
20108
20129
  [itemIdsSignature]
20109
20130
  );
20131
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
20132
+ React75__namespace.default.useEffect(() => {
20133
+ const root = isRoot ? null : parentRoot;
20134
+ if (root) {
20135
+ root.clearOptimisticOrder(ownGroup);
20136
+ } else {
20137
+ clearOptimisticOrder(ownGroup);
20138
+ }
20139
+ }, [itemsContentSig, ownGroup]);
20110
20140
  const zonesRef = React75__namespace.default.useRef(/* @__PURE__ */ new Map());
20111
20141
  const registerZone = React75__namespace.default.useCallback((zoneId2, meta2) => {
20112
20142
  zonesRef.current.set(zoneId2, meta2);
@@ -20116,11 +20146,9 @@ function useDataDnd(args) {
20116
20146
  }, []);
20117
20147
  const [activeDrag, setActiveDrag] = React75__namespace.default.useState(null);
20118
20148
  const [overZoneGroup, setOverZoneGroup] = React75__namespace.default.useState(null);
20119
- const zoneId = React75__namespace.default.useId();
20120
- const ownGroup = dragGroup ?? accepts ?? zoneId;
20121
20149
  const meta = React75__namespace.default.useMemo(
20122
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
20123
- [ownGroup, dropEvent, reorderEvent, itemIds]
20150
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
20151
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
20124
20152
  );
20125
20153
  React75__namespace.default.useEffect(() => {
20126
20154
  const target = isRoot ? null : parentRoot;
@@ -20167,7 +20195,7 @@ function useDataDnd(args) {
20167
20195
  },
20168
20196
  []
20169
20197
  );
20170
- const findZoneByGroup = React75__namespace.default.useCallback(
20198
+ React75__namespace.default.useCallback(
20171
20199
  (group) => {
20172
20200
  for (const z of zonesRef.current.values()) {
20173
20201
  if (z.group === group) return z;
@@ -20179,81 +20207,76 @@ function useDataDnd(args) {
20179
20207
  const handleDragEnd = React75__namespace.default.useCallback(
20180
20208
  (event) => {
20181
20209
  const { active, over } = event;
20182
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
20210
+ const activeIdStr = String(active.id);
20183
20211
  dndLog.debug("dragEnd:received", {
20184
20212
  activeId: active.id,
20185
20213
  overId: over?.id,
20186
- overData: over?.data?.current,
20187
- zones: allZones
20214
+ overData: over?.data?.current
20188
20215
  });
20189
- if (!over) {
20190
- dndLog.warn("dragEnd:abort:no-over", { activeId: active.id, reason: "no droppable under pointer at drop time \u2192 item snaps back" });
20191
- return;
20192
- }
20193
- const sourceZone = findZoneByItem(active.id);
20194
- const overData = over.data?.current;
20195
- const targetGroup = overData?.dndGroup;
20196
- dndLog.debug("dragEnd:resolved", { sourceGroup: sourceZone?.group, targetGroup, overDataKeys: overData ? Object.keys(overData) : null });
20197
- if (!sourceZone) {
20198
- dndLog.warn("dragEnd:abort:no-source-zone", { activeId: active.id });
20199
- return;
20200
- }
20201
- if (!targetGroup) {
20202
- dndLog.warn("dragEnd:abort:no-target-group", { overId: over.id, overData });
20203
- return;
20204
- }
20205
- const targetZone = findZoneByGroup(targetGroup);
20206
- if (!targetZone) {
20207
- dndLog.warn("dragEnd:abort:target-zone-not-registered", { targetGroup });
20216
+ let sourceMeta;
20217
+ let oldIndex = -1;
20218
+ let targetMeta;
20219
+ let newIndex = -1;
20220
+ for (const m of zonesRef.current.values()) {
20221
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
20222
+ if (rawIdx >= 0) {
20223
+ sourceMeta = m;
20224
+ oldIndex = rawIdx;
20225
+ }
20226
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
20227
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
20228
+ if (curIdx >= 0) {
20229
+ targetMeta = m;
20230
+ newIndex = curIdx;
20231
+ }
20232
+ }
20233
+ if (!sourceMeta || !targetMeta) {
20234
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
20208
20235
  return;
20209
20236
  }
20210
- if (sourceZone.group !== targetZone.group) {
20211
- if (targetZone.dropEvent) {
20212
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
20213
- const evt = `UI:${targetZone.dropEvent}`;
20237
+ if (sourceMeta.group !== targetMeta.group) {
20238
+ if (targetMeta.dropEvent) {
20239
+ const evt = `UI:${targetMeta.dropEvent}`;
20214
20240
  dndLog.info("dragEnd:cross-container:emit", {
20215
20241
  event: evt,
20216
- id: String(active.id),
20217
- sourceGroup: sourceZone.group,
20218
- targetGroup: targetZone.group,
20219
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
20242
+ id: activeIdStr,
20243
+ sourceGroup: sourceMeta.group,
20244
+ targetGroup: targetMeta.group,
20245
+ newIndex
20220
20246
  });
20221
20247
  eventBus.emit(evt, {
20222
- id: String(active.id),
20223
- sourceGroup: sourceZone.group,
20224
- targetGroup: targetZone.group,
20225
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
20248
+ id: activeIdStr,
20249
+ sourceGroup: sourceMeta.group,
20250
+ targetGroup: targetMeta.group,
20251
+ newIndex
20226
20252
  });
20227
20253
  } else {
20228
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
20254
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
20229
20255
  }
20230
20256
  return;
20231
20257
  }
20232
- const oldIndex = sourceZone.itemIds.indexOf(active.id);
20233
- const newIndex = sourceZone.itemIds.indexOf(over.id);
20234
- if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return;
20235
- if (sourceZone.group === ownGroup) {
20236
- const reordered = sortable.arrayMove(orderedItems, oldIndex, newIndex);
20237
- setLocalOrder(reordered);
20258
+ if (oldIndex === newIndex) {
20259
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
20260
+ return;
20238
20261
  }
20239
- if (sourceZone.reorderEvent) {
20240
- const evt = `UI:${sourceZone.reorderEvent}`;
20262
+ if (sourceMeta.reorderEvent) {
20263
+ const evt = `UI:${sourceMeta.reorderEvent}`;
20241
20264
  dndLog.info("dragEnd:reorder:emit", {
20242
20265
  event: evt,
20243
- id: String(active.id),
20266
+ id: activeIdStr,
20244
20267
  oldIndex,
20245
20268
  newIndex
20246
20269
  });
20247
20270
  eventBus.emit(evt, {
20248
- id: String(active.id),
20271
+ id: activeIdStr,
20249
20272
  oldIndex,
20250
20273
  newIndex
20251
20274
  });
20252
20275
  } else {
20253
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
20276
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
20254
20277
  }
20255
20278
  },
20256
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
20279
+ [eventBus]
20257
20280
  );
20258
20281
  const sortableData = React75__namespace.default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
20259
20282
  const SortableItem = React75__namespace.default.useCallback(
@@ -20314,30 +20337,20 @@ function useDataDnd(args) {
20314
20337
  React75__namespace.default.useEffect(() => {
20315
20338
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
20316
20339
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
20317
- return /* @__PURE__ */ jsxRuntime.jsxs(
20340
+ return /* @__PURE__ */ jsxRuntime.jsx(
20318
20341
  exports.Box,
20319
20342
  {
20320
20343
  ref: setNodeRef,
20321
20344
  "data-dnd-zone": ownGroup,
20322
20345
  "data-dnd-is-over": isThisZoneOver ? "true" : "false",
20323
- className: isThisZoneOver ? "ring-2 ring-primary ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
20324
- children: [
20325
- children,
20326
- showForeignPlaceholder ? /* @__PURE__ */ jsxRuntime.jsx(
20327
- exports.Box,
20328
- {
20329
- "data-dnd-placeholder": true,
20330
- style: { height: activeDrag2.height },
20331
- className: "border-2 border-dashed border-primary/60 bg-primary/5 rounded-md my-1 transition-all"
20332
- }
20333
- ) : null
20334
- ]
20346
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
20347
+ children
20335
20348
  }
20336
20349
  );
20337
20350
  };
20338
20351
  const rootContextValue = React75__namespace.default.useMemo(
20339
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
20340
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
20352
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
20353
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
20341
20354
  );
20342
20355
  const handleDragStart = React75__namespace.default.useCallback((event) => {
20343
20356
  const sourceZone = findZoneByItem(event.active.id);
@@ -20359,13 +20372,73 @@ function useDataDnd(args) {
20359
20372
  });
20360
20373
  }, [findZoneByItem, isRoot, zoneId]);
20361
20374
  const handleDragOver = React75__namespace.default.useCallback((event) => {
20362
- const overData = event.over?.data?.current;
20363
- const group = overData?.dndGroup ?? null;
20364
- setOverZoneGroup(group);
20365
- dndLog.debug("dragOver", {
20366
- activeId: event.active.id,
20367
- overId: event.over?.id,
20368
- overGroup: group
20375
+ const { active, over } = event;
20376
+ const overData = over?.data?.current;
20377
+ const overGroup = overData?.dndGroup ?? null;
20378
+ setOverZoneGroup(overGroup);
20379
+ if (!over || !overGroup) return;
20380
+ const activeIdStr = String(active.id);
20381
+ let sourceMeta;
20382
+ let sourceGroup;
20383
+ for (const m of zonesRef.current.values()) {
20384
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
20385
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
20386
+ if (found) {
20387
+ sourceMeta = m;
20388
+ sourceGroup = m.group;
20389
+ break;
20390
+ }
20391
+ }
20392
+ if (!sourceMeta || !sourceGroup) {
20393
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
20394
+ return;
20395
+ }
20396
+ let targetMeta;
20397
+ for (const m of zonesRef.current.values()) {
20398
+ if (m.group === overGroup) {
20399
+ targetMeta = m;
20400
+ break;
20401
+ }
20402
+ }
20403
+ if (!targetMeta) {
20404
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
20405
+ return;
20406
+ }
20407
+ if (sourceGroup === overGroup) {
20408
+ setOptimisticOrders((prev) => {
20409
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
20410
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
20411
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
20412
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
20413
+ const reordered = sortable.arrayMove([...currentItems], oldIndex, newIndex);
20414
+ const next = new Map(prev);
20415
+ next.set(sourceGroup, reordered);
20416
+ return next;
20417
+ });
20418
+ return;
20419
+ }
20420
+ setOptimisticOrders((prev) => {
20421
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
20422
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
20423
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
20424
+ if (!activeItem) return prev;
20425
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
20426
+ return prev;
20427
+ }
20428
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
20429
+ const overIdStr = String(over.id);
20430
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
20431
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
20432
+ const newTarget = [
20433
+ ...currentTarget.slice(0, insertAt),
20434
+ activeItem,
20435
+ ...currentTarget.slice(insertAt)
20436
+ ];
20437
+ const next = new Map(prev);
20438
+ next.set(sourceGroup, newSource);
20439
+ next.set(overGroup, newTarget);
20440
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
20441
+ return next;
20369
20442
  });
20370
20443
  }, []);
20371
20444
  const handleDragCancel = React75__namespace.default.useCallback((event) => {
@@ -36,7 +36,7 @@ import langGo from 'react-syntax-highlighter/dist/esm/languages/prism/go.js';
36
36
  import langGraphql from 'react-syntax-highlighter/dist/esm/languages/prism/graphql.js';
37
37
  import { isInlineTrait } from '@almadar/core';
38
38
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, pointerWithin, rectIntersection, closestCorners, DndContext, useDroppable } from '@dnd-kit/core';
39
- import { sortableKeyboardCoordinates, arrayMove, useSortable, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
39
+ import { sortableKeyboardCoordinates, useSortable, arrayMove, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
40
40
  import { CSS } from '@dnd-kit/utilities';
41
41
  import { Handle, Position } from '@xyflow/react';
42
42
  import { useUISlots } from '@almadar/ui/context';
@@ -20044,11 +20044,32 @@ function useDataDnd(args) {
20044
20044
  const eventBus = useEventBus();
20045
20045
  const parentRoot = React75__default.useContext(RootCtx);
20046
20046
  const isRoot = enabled && parentRoot === null;
20047
- const [localOrder, setLocalOrder] = React75__default.useState(null);
20048
- const orderedItems = localOrder ?? items;
20049
- React75__default.useEffect(() => {
20050
- setLocalOrder(null);
20051
- }, [items]);
20047
+ const zoneId = React75__default.useId();
20048
+ const ownGroup = dragGroup ?? accepts ?? zoneId;
20049
+ const [optimisticOrders, setOptimisticOrders] = React75__default.useState(() => /* @__PURE__ */ new Map());
20050
+ const optimisticOrdersRef = React75__default.useRef(optimisticOrders);
20051
+ optimisticOrdersRef.current = optimisticOrders;
20052
+ const clearOptimisticOrder = React75__default.useCallback((group) => {
20053
+ setOptimisticOrders((prev) => {
20054
+ if (!prev.has(group)) return prev;
20055
+ const next = new Map(prev);
20056
+ next.delete(group);
20057
+ return next;
20058
+ });
20059
+ }, []);
20060
+ const sharedOptimistic = isRoot ? optimisticOrders : parentRoot?.optimisticOrders ?? /* @__PURE__ */ new Map();
20061
+ const optimisticEntry = sharedOptimistic.get(ownGroup);
20062
+ const orderedItems = optimisticEntry ?? items;
20063
+ if (isZone && enabled) {
20064
+ dndLog.debug("hook:render", {
20065
+ group: ownGroup,
20066
+ isRoot,
20067
+ itemsLen: items.length,
20068
+ optimisticEntryLen: optimisticEntry ? optimisticEntry.length : null,
20069
+ orderedLen: orderedItems.length,
20070
+ sharedKeys: Array.from(sharedOptimistic.keys())
20071
+ });
20072
+ }
20052
20073
  const itemIdsSignature = orderedItems.map((it, idx) => {
20053
20074
  const raw = it[dndItemIdField];
20054
20075
  return String(raw ?? `__idx_${idx}`);
@@ -20061,6 +20082,15 @@ function useDataDnd(args) {
20061
20082
  // eslint-disable-next-line react-hooks/exhaustive-deps
20062
20083
  [itemIdsSignature]
20063
20084
  );
20085
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
20086
+ React75__default.useEffect(() => {
20087
+ const root = isRoot ? null : parentRoot;
20088
+ if (root) {
20089
+ root.clearOptimisticOrder(ownGroup);
20090
+ } else {
20091
+ clearOptimisticOrder(ownGroup);
20092
+ }
20093
+ }, [itemsContentSig, ownGroup]);
20064
20094
  const zonesRef = React75__default.useRef(/* @__PURE__ */ new Map());
20065
20095
  const registerZone = React75__default.useCallback((zoneId2, meta2) => {
20066
20096
  zonesRef.current.set(zoneId2, meta2);
@@ -20070,11 +20100,9 @@ function useDataDnd(args) {
20070
20100
  }, []);
20071
20101
  const [activeDrag, setActiveDrag] = React75__default.useState(null);
20072
20102
  const [overZoneGroup, setOverZoneGroup] = React75__default.useState(null);
20073
- const zoneId = React75__default.useId();
20074
- const ownGroup = dragGroup ?? accepts ?? zoneId;
20075
20103
  const meta = React75__default.useMemo(
20076
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
20077
- [ownGroup, dropEvent, reorderEvent, itemIds]
20104
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
20105
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
20078
20106
  );
20079
20107
  React75__default.useEffect(() => {
20080
20108
  const target = isRoot ? null : parentRoot;
@@ -20121,7 +20149,7 @@ function useDataDnd(args) {
20121
20149
  },
20122
20150
  []
20123
20151
  );
20124
- const findZoneByGroup = React75__default.useCallback(
20152
+ React75__default.useCallback(
20125
20153
  (group) => {
20126
20154
  for (const z of zonesRef.current.values()) {
20127
20155
  if (z.group === group) return z;
@@ -20133,81 +20161,76 @@ function useDataDnd(args) {
20133
20161
  const handleDragEnd = React75__default.useCallback(
20134
20162
  (event) => {
20135
20163
  const { active, over } = event;
20136
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
20164
+ const activeIdStr = String(active.id);
20137
20165
  dndLog.debug("dragEnd:received", {
20138
20166
  activeId: active.id,
20139
20167
  overId: over?.id,
20140
- overData: over?.data?.current,
20141
- zones: allZones
20168
+ overData: over?.data?.current
20142
20169
  });
20143
- if (!over) {
20144
- dndLog.warn("dragEnd:abort:no-over", { activeId: active.id, reason: "no droppable under pointer at drop time \u2192 item snaps back" });
20145
- return;
20146
- }
20147
- const sourceZone = findZoneByItem(active.id);
20148
- const overData = over.data?.current;
20149
- const targetGroup = overData?.dndGroup;
20150
- dndLog.debug("dragEnd:resolved", { sourceGroup: sourceZone?.group, targetGroup, overDataKeys: overData ? Object.keys(overData) : null });
20151
- if (!sourceZone) {
20152
- dndLog.warn("dragEnd:abort:no-source-zone", { activeId: active.id });
20153
- return;
20154
- }
20155
- if (!targetGroup) {
20156
- dndLog.warn("dragEnd:abort:no-target-group", { overId: over.id, overData });
20157
- return;
20158
- }
20159
- const targetZone = findZoneByGroup(targetGroup);
20160
- if (!targetZone) {
20161
- dndLog.warn("dragEnd:abort:target-zone-not-registered", { targetGroup });
20170
+ let sourceMeta;
20171
+ let oldIndex = -1;
20172
+ let targetMeta;
20173
+ let newIndex = -1;
20174
+ for (const m of zonesRef.current.values()) {
20175
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
20176
+ if (rawIdx >= 0) {
20177
+ sourceMeta = m;
20178
+ oldIndex = rawIdx;
20179
+ }
20180
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
20181
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
20182
+ if (curIdx >= 0) {
20183
+ targetMeta = m;
20184
+ newIndex = curIdx;
20185
+ }
20186
+ }
20187
+ if (!sourceMeta || !targetMeta) {
20188
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
20162
20189
  return;
20163
20190
  }
20164
- if (sourceZone.group !== targetZone.group) {
20165
- if (targetZone.dropEvent) {
20166
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
20167
- const evt = `UI:${targetZone.dropEvent}`;
20191
+ if (sourceMeta.group !== targetMeta.group) {
20192
+ if (targetMeta.dropEvent) {
20193
+ const evt = `UI:${targetMeta.dropEvent}`;
20168
20194
  dndLog.info("dragEnd:cross-container:emit", {
20169
20195
  event: evt,
20170
- id: String(active.id),
20171
- sourceGroup: sourceZone.group,
20172
- targetGroup: targetZone.group,
20173
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
20196
+ id: activeIdStr,
20197
+ sourceGroup: sourceMeta.group,
20198
+ targetGroup: targetMeta.group,
20199
+ newIndex
20174
20200
  });
20175
20201
  eventBus.emit(evt, {
20176
- id: String(active.id),
20177
- sourceGroup: sourceZone.group,
20178
- targetGroup: targetZone.group,
20179
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
20202
+ id: activeIdStr,
20203
+ sourceGroup: sourceMeta.group,
20204
+ targetGroup: targetMeta.group,
20205
+ newIndex
20180
20206
  });
20181
20207
  } else {
20182
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
20208
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
20183
20209
  }
20184
20210
  return;
20185
20211
  }
20186
- const oldIndex = sourceZone.itemIds.indexOf(active.id);
20187
- const newIndex = sourceZone.itemIds.indexOf(over.id);
20188
- if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return;
20189
- if (sourceZone.group === ownGroup) {
20190
- const reordered = arrayMove(orderedItems, oldIndex, newIndex);
20191
- setLocalOrder(reordered);
20212
+ if (oldIndex === newIndex) {
20213
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
20214
+ return;
20192
20215
  }
20193
- if (sourceZone.reorderEvent) {
20194
- const evt = `UI:${sourceZone.reorderEvent}`;
20216
+ if (sourceMeta.reorderEvent) {
20217
+ const evt = `UI:${sourceMeta.reorderEvent}`;
20195
20218
  dndLog.info("dragEnd:reorder:emit", {
20196
20219
  event: evt,
20197
- id: String(active.id),
20220
+ id: activeIdStr,
20198
20221
  oldIndex,
20199
20222
  newIndex
20200
20223
  });
20201
20224
  eventBus.emit(evt, {
20202
- id: String(active.id),
20225
+ id: activeIdStr,
20203
20226
  oldIndex,
20204
20227
  newIndex
20205
20228
  });
20206
20229
  } else {
20207
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
20230
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
20208
20231
  }
20209
20232
  },
20210
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
20233
+ [eventBus]
20211
20234
  );
20212
20235
  const sortableData = React75__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
20213
20236
  const SortableItem = React75__default.useCallback(
@@ -20268,30 +20291,20 @@ function useDataDnd(args) {
20268
20291
  React75__default.useEffect(() => {
20269
20292
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
20270
20293
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
20271
- return /* @__PURE__ */ jsxs(
20294
+ return /* @__PURE__ */ jsx(
20272
20295
  Box,
20273
20296
  {
20274
20297
  ref: setNodeRef,
20275
20298
  "data-dnd-zone": ownGroup,
20276
20299
  "data-dnd-is-over": isThisZoneOver ? "true" : "false",
20277
- className: isThisZoneOver ? "ring-2 ring-primary ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
20278
- children: [
20279
- children,
20280
- showForeignPlaceholder ? /* @__PURE__ */ jsx(
20281
- Box,
20282
- {
20283
- "data-dnd-placeholder": true,
20284
- style: { height: activeDrag2.height },
20285
- className: "border-2 border-dashed border-primary/60 bg-primary/5 rounded-md my-1 transition-all"
20286
- }
20287
- ) : null
20288
- ]
20300
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
20301
+ children
20289
20302
  }
20290
20303
  );
20291
20304
  };
20292
20305
  const rootContextValue = React75__default.useMemo(
20293
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
20294
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
20306
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
20307
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
20295
20308
  );
20296
20309
  const handleDragStart = React75__default.useCallback((event) => {
20297
20310
  const sourceZone = findZoneByItem(event.active.id);
@@ -20313,13 +20326,73 @@ function useDataDnd(args) {
20313
20326
  });
20314
20327
  }, [findZoneByItem, isRoot, zoneId]);
20315
20328
  const handleDragOver = React75__default.useCallback((event) => {
20316
- const overData = event.over?.data?.current;
20317
- const group = overData?.dndGroup ?? null;
20318
- setOverZoneGroup(group);
20319
- dndLog.debug("dragOver", {
20320
- activeId: event.active.id,
20321
- overId: event.over?.id,
20322
- overGroup: group
20329
+ const { active, over } = event;
20330
+ const overData = over?.data?.current;
20331
+ const overGroup = overData?.dndGroup ?? null;
20332
+ setOverZoneGroup(overGroup);
20333
+ if (!over || !overGroup) return;
20334
+ const activeIdStr = String(active.id);
20335
+ let sourceMeta;
20336
+ let sourceGroup;
20337
+ for (const m of zonesRef.current.values()) {
20338
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
20339
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
20340
+ if (found) {
20341
+ sourceMeta = m;
20342
+ sourceGroup = m.group;
20343
+ break;
20344
+ }
20345
+ }
20346
+ if (!sourceMeta || !sourceGroup) {
20347
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
20348
+ return;
20349
+ }
20350
+ let targetMeta;
20351
+ for (const m of zonesRef.current.values()) {
20352
+ if (m.group === overGroup) {
20353
+ targetMeta = m;
20354
+ break;
20355
+ }
20356
+ }
20357
+ if (!targetMeta) {
20358
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
20359
+ return;
20360
+ }
20361
+ if (sourceGroup === overGroup) {
20362
+ setOptimisticOrders((prev) => {
20363
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
20364
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
20365
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
20366
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
20367
+ const reordered = arrayMove([...currentItems], oldIndex, newIndex);
20368
+ const next = new Map(prev);
20369
+ next.set(sourceGroup, reordered);
20370
+ return next;
20371
+ });
20372
+ return;
20373
+ }
20374
+ setOptimisticOrders((prev) => {
20375
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
20376
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
20377
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
20378
+ if (!activeItem) return prev;
20379
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
20380
+ return prev;
20381
+ }
20382
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
20383
+ const overIdStr = String(over.id);
20384
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
20385
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
20386
+ const newTarget = [
20387
+ ...currentTarget.slice(0, insertAt),
20388
+ activeItem,
20389
+ ...currentTarget.slice(insertAt)
20390
+ ];
20391
+ const next = new Map(prev);
20392
+ next.set(sourceGroup, newSource);
20393
+ next.set(overGroup, newTarget);
20394
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
20395
+ return next;
20323
20396
  });
20324
20397
  }, []);
20325
20398
  const handleDragCancel = React75__default.useCallback((event) => {