@almadar/ui 4.50.14 → 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.
@@ -20090,11 +20090,21 @@ 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 orderedItems = sharedOptimistic.get(ownGroup) ?? items;
20098
20108
  const itemIdsSignature = orderedItems.map((it, idx) => {
20099
20109
  const raw = it[dndItemIdField];
20100
20110
  return String(raw ?? `__idx_${idx}`);
@@ -20107,6 +20117,15 @@ function useDataDnd(args) {
20107
20117
  // eslint-disable-next-line react-hooks/exhaustive-deps
20108
20118
  [itemIdsSignature]
20109
20119
  );
20120
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
20121
+ React75__namespace.default.useEffect(() => {
20122
+ const root = isRoot ? null : parentRoot;
20123
+ if (root) {
20124
+ root.clearOptimisticOrder(ownGroup);
20125
+ } else {
20126
+ clearOptimisticOrder(ownGroup);
20127
+ }
20128
+ }, [itemsContentSig, ownGroup]);
20110
20129
  const zonesRef = React75__namespace.default.useRef(/* @__PURE__ */ new Map());
20111
20130
  const registerZone = React75__namespace.default.useCallback((zoneId2, meta2) => {
20112
20131
  zonesRef.current.set(zoneId2, meta2);
@@ -20116,11 +20135,9 @@ function useDataDnd(args) {
20116
20135
  }, []);
20117
20136
  const [activeDrag, setActiveDrag] = React75__namespace.default.useState(null);
20118
20137
  const [overZoneGroup, setOverZoneGroup] = React75__namespace.default.useState(null);
20119
- const zoneId = React75__namespace.default.useId();
20120
- const ownGroup = dragGroup ?? accepts ?? zoneId;
20121
20138
  const meta = React75__namespace.default.useMemo(
20122
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
20123
- [ownGroup, dropEvent, reorderEvent, itemIds]
20139
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
20140
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
20124
20141
  );
20125
20142
  React75__namespace.default.useEffect(() => {
20126
20143
  const target = isRoot ? null : parentRoot;
@@ -20167,7 +20184,7 @@ function useDataDnd(args) {
20167
20184
  },
20168
20185
  []
20169
20186
  );
20170
- const findZoneByGroup = React75__namespace.default.useCallback(
20187
+ React75__namespace.default.useCallback(
20171
20188
  (group) => {
20172
20189
  for (const z of zonesRef.current.values()) {
20173
20190
  if (z.group === group) return z;
@@ -20179,81 +20196,76 @@ function useDataDnd(args) {
20179
20196
  const handleDragEnd = React75__namespace.default.useCallback(
20180
20197
  (event) => {
20181
20198
  const { active, over } = event;
20182
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
20199
+ const activeIdStr = String(active.id);
20183
20200
  dndLog.debug("dragEnd:received", {
20184
20201
  activeId: active.id,
20185
20202
  overId: over?.id,
20186
- overData: over?.data?.current,
20187
- zones: allZones
20203
+ overData: over?.data?.current
20188
20204
  });
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" });
20205
+ let sourceMeta;
20206
+ let oldIndex = -1;
20207
+ let targetMeta;
20208
+ let newIndex = -1;
20209
+ for (const m of zonesRef.current.values()) {
20210
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
20211
+ if (rawIdx >= 0) {
20212
+ sourceMeta = m;
20213
+ oldIndex = rawIdx;
20214
+ }
20215
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
20216
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
20217
+ if (curIdx >= 0) {
20218
+ targetMeta = m;
20219
+ newIndex = curIdx;
20220
+ }
20221
+ }
20222
+ if (!sourceMeta || !targetMeta) {
20223
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
20191
20224
  return;
20192
20225
  }
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 });
20208
- return;
20209
- }
20210
- if (sourceZone.group !== targetZone.group) {
20211
- if (targetZone.dropEvent) {
20212
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
20213
- const evt = `UI:${targetZone.dropEvent}`;
20226
+ if (sourceMeta.group !== targetMeta.group) {
20227
+ if (targetMeta.dropEvent) {
20228
+ const evt = `UI:${targetMeta.dropEvent}`;
20214
20229
  dndLog.info("dragEnd:cross-container:emit", {
20215
20230
  event: evt,
20216
- id: String(active.id),
20217
- sourceGroup: sourceZone.group,
20218
- targetGroup: targetZone.group,
20219
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
20231
+ id: activeIdStr,
20232
+ sourceGroup: sourceMeta.group,
20233
+ targetGroup: targetMeta.group,
20234
+ newIndex
20220
20235
  });
20221
20236
  eventBus.emit(evt, {
20222
- id: String(active.id),
20223
- sourceGroup: sourceZone.group,
20224
- targetGroup: targetZone.group,
20225
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
20237
+ id: activeIdStr,
20238
+ sourceGroup: sourceMeta.group,
20239
+ targetGroup: targetMeta.group,
20240
+ newIndex
20226
20241
  });
20227
20242
  } else {
20228
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
20243
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
20229
20244
  }
20230
20245
  return;
20231
20246
  }
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);
20247
+ if (oldIndex === newIndex) {
20248
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
20249
+ return;
20238
20250
  }
20239
- if (sourceZone.reorderEvent) {
20240
- const evt = `UI:${sourceZone.reorderEvent}`;
20251
+ if (sourceMeta.reorderEvent) {
20252
+ const evt = `UI:${sourceMeta.reorderEvent}`;
20241
20253
  dndLog.info("dragEnd:reorder:emit", {
20242
20254
  event: evt,
20243
- id: String(active.id),
20255
+ id: activeIdStr,
20244
20256
  oldIndex,
20245
20257
  newIndex
20246
20258
  });
20247
20259
  eventBus.emit(evt, {
20248
- id: String(active.id),
20260
+ id: activeIdStr,
20249
20261
  oldIndex,
20250
20262
  newIndex
20251
20263
  });
20252
20264
  } else {
20253
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
20265
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
20254
20266
  }
20255
20267
  },
20256
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
20268
+ [eventBus]
20257
20269
  );
20258
20270
  const sortableData = React75__namespace.default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
20259
20271
  const SortableItem = React75__namespace.default.useCallback(
@@ -20314,30 +20326,20 @@ function useDataDnd(args) {
20314
20326
  React75__namespace.default.useEffect(() => {
20315
20327
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
20316
20328
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
20317
- return /* @__PURE__ */ jsxRuntime.jsxs(
20329
+ return /* @__PURE__ */ jsxRuntime.jsx(
20318
20330
  exports.Box,
20319
20331
  {
20320
20332
  ref: setNodeRef,
20321
20333
  "data-dnd-zone": ownGroup,
20322
20334
  "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
- ]
20335
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
20336
+ children
20335
20337
  }
20336
20338
  );
20337
20339
  };
20338
20340
  const rootContextValue = React75__namespace.default.useMemo(
20339
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
20340
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
20341
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
20342
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
20341
20343
  );
20342
20344
  const handleDragStart = React75__namespace.default.useCallback((event) => {
20343
20345
  const sourceZone = findZoneByItem(event.active.id);
@@ -20359,13 +20361,73 @@ function useDataDnd(args) {
20359
20361
  });
20360
20362
  }, [findZoneByItem, isRoot, zoneId]);
20361
20363
  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
20364
+ const { active, over } = event;
20365
+ const overData = over?.data?.current;
20366
+ const overGroup = overData?.dndGroup ?? null;
20367
+ setOverZoneGroup(overGroup);
20368
+ if (!over || !overGroup) return;
20369
+ const activeIdStr = String(active.id);
20370
+ let sourceMeta;
20371
+ let sourceGroup;
20372
+ for (const m of zonesRef.current.values()) {
20373
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
20374
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
20375
+ if (found) {
20376
+ sourceMeta = m;
20377
+ sourceGroup = m.group;
20378
+ break;
20379
+ }
20380
+ }
20381
+ if (!sourceMeta || !sourceGroup) {
20382
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
20383
+ return;
20384
+ }
20385
+ let targetMeta;
20386
+ for (const m of zonesRef.current.values()) {
20387
+ if (m.group === overGroup) {
20388
+ targetMeta = m;
20389
+ break;
20390
+ }
20391
+ }
20392
+ if (!targetMeta) {
20393
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
20394
+ return;
20395
+ }
20396
+ if (sourceGroup === overGroup) {
20397
+ setOptimisticOrders((prev) => {
20398
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
20399
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
20400
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
20401
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
20402
+ const reordered = sortable.arrayMove([...currentItems], oldIndex, newIndex);
20403
+ const next = new Map(prev);
20404
+ next.set(sourceGroup, reordered);
20405
+ return next;
20406
+ });
20407
+ return;
20408
+ }
20409
+ setOptimisticOrders((prev) => {
20410
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
20411
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
20412
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
20413
+ if (!activeItem) return prev;
20414
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
20415
+ return prev;
20416
+ }
20417
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
20418
+ const overIdStr = String(over.id);
20419
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
20420
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
20421
+ const newTarget = [
20422
+ ...currentTarget.slice(0, insertAt),
20423
+ activeItem,
20424
+ ...currentTarget.slice(insertAt)
20425
+ ];
20426
+ const next = new Map(prev);
20427
+ next.set(sourceGroup, newSource);
20428
+ next.set(overGroup, newTarget);
20429
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
20430
+ return next;
20369
20431
  });
20370
20432
  }, []);
20371
20433
  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,21 @@ 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 orderedItems = sharedOptimistic.get(ownGroup) ?? items;
20052
20062
  const itemIdsSignature = orderedItems.map((it, idx) => {
20053
20063
  const raw = it[dndItemIdField];
20054
20064
  return String(raw ?? `__idx_${idx}`);
@@ -20061,6 +20071,15 @@ function useDataDnd(args) {
20061
20071
  // eslint-disable-next-line react-hooks/exhaustive-deps
20062
20072
  [itemIdsSignature]
20063
20073
  );
20074
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
20075
+ React75__default.useEffect(() => {
20076
+ const root = isRoot ? null : parentRoot;
20077
+ if (root) {
20078
+ root.clearOptimisticOrder(ownGroup);
20079
+ } else {
20080
+ clearOptimisticOrder(ownGroup);
20081
+ }
20082
+ }, [itemsContentSig, ownGroup]);
20064
20083
  const zonesRef = React75__default.useRef(/* @__PURE__ */ new Map());
20065
20084
  const registerZone = React75__default.useCallback((zoneId2, meta2) => {
20066
20085
  zonesRef.current.set(zoneId2, meta2);
@@ -20070,11 +20089,9 @@ function useDataDnd(args) {
20070
20089
  }, []);
20071
20090
  const [activeDrag, setActiveDrag] = React75__default.useState(null);
20072
20091
  const [overZoneGroup, setOverZoneGroup] = React75__default.useState(null);
20073
- const zoneId = React75__default.useId();
20074
- const ownGroup = dragGroup ?? accepts ?? zoneId;
20075
20092
  const meta = React75__default.useMemo(
20076
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
20077
- [ownGroup, dropEvent, reorderEvent, itemIds]
20093
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
20094
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
20078
20095
  );
20079
20096
  React75__default.useEffect(() => {
20080
20097
  const target = isRoot ? null : parentRoot;
@@ -20121,7 +20138,7 @@ function useDataDnd(args) {
20121
20138
  },
20122
20139
  []
20123
20140
  );
20124
- const findZoneByGroup = React75__default.useCallback(
20141
+ React75__default.useCallback(
20125
20142
  (group) => {
20126
20143
  for (const z of zonesRef.current.values()) {
20127
20144
  if (z.group === group) return z;
@@ -20133,81 +20150,76 @@ function useDataDnd(args) {
20133
20150
  const handleDragEnd = React75__default.useCallback(
20134
20151
  (event) => {
20135
20152
  const { active, over } = event;
20136
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
20153
+ const activeIdStr = String(active.id);
20137
20154
  dndLog.debug("dragEnd:received", {
20138
20155
  activeId: active.id,
20139
20156
  overId: over?.id,
20140
- overData: over?.data?.current,
20141
- zones: allZones
20157
+ overData: over?.data?.current
20142
20158
  });
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" });
20159
+ let sourceMeta;
20160
+ let oldIndex = -1;
20161
+ let targetMeta;
20162
+ let newIndex = -1;
20163
+ for (const m of zonesRef.current.values()) {
20164
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
20165
+ if (rawIdx >= 0) {
20166
+ sourceMeta = m;
20167
+ oldIndex = rawIdx;
20168
+ }
20169
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
20170
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
20171
+ if (curIdx >= 0) {
20172
+ targetMeta = m;
20173
+ newIndex = curIdx;
20174
+ }
20175
+ }
20176
+ if (!sourceMeta || !targetMeta) {
20177
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
20145
20178
  return;
20146
20179
  }
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 });
20162
- return;
20163
- }
20164
- if (sourceZone.group !== targetZone.group) {
20165
- if (targetZone.dropEvent) {
20166
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
20167
- const evt = `UI:${targetZone.dropEvent}`;
20180
+ if (sourceMeta.group !== targetMeta.group) {
20181
+ if (targetMeta.dropEvent) {
20182
+ const evt = `UI:${targetMeta.dropEvent}`;
20168
20183
  dndLog.info("dragEnd:cross-container:emit", {
20169
20184
  event: evt,
20170
- id: String(active.id),
20171
- sourceGroup: sourceZone.group,
20172
- targetGroup: targetZone.group,
20173
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
20185
+ id: activeIdStr,
20186
+ sourceGroup: sourceMeta.group,
20187
+ targetGroup: targetMeta.group,
20188
+ newIndex
20174
20189
  });
20175
20190
  eventBus.emit(evt, {
20176
- id: String(active.id),
20177
- sourceGroup: sourceZone.group,
20178
- targetGroup: targetZone.group,
20179
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
20191
+ id: activeIdStr,
20192
+ sourceGroup: sourceMeta.group,
20193
+ targetGroup: targetMeta.group,
20194
+ newIndex
20180
20195
  });
20181
20196
  } else {
20182
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
20197
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
20183
20198
  }
20184
20199
  return;
20185
20200
  }
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);
20201
+ if (oldIndex === newIndex) {
20202
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
20203
+ return;
20192
20204
  }
20193
- if (sourceZone.reorderEvent) {
20194
- const evt = `UI:${sourceZone.reorderEvent}`;
20205
+ if (sourceMeta.reorderEvent) {
20206
+ const evt = `UI:${sourceMeta.reorderEvent}`;
20195
20207
  dndLog.info("dragEnd:reorder:emit", {
20196
20208
  event: evt,
20197
- id: String(active.id),
20209
+ id: activeIdStr,
20198
20210
  oldIndex,
20199
20211
  newIndex
20200
20212
  });
20201
20213
  eventBus.emit(evt, {
20202
- id: String(active.id),
20214
+ id: activeIdStr,
20203
20215
  oldIndex,
20204
20216
  newIndex
20205
20217
  });
20206
20218
  } else {
20207
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
20219
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
20208
20220
  }
20209
20221
  },
20210
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
20222
+ [eventBus]
20211
20223
  );
20212
20224
  const sortableData = React75__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
20213
20225
  const SortableItem = React75__default.useCallback(
@@ -20268,30 +20280,20 @@ function useDataDnd(args) {
20268
20280
  React75__default.useEffect(() => {
20269
20281
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
20270
20282
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
20271
- return /* @__PURE__ */ jsxs(
20283
+ return /* @__PURE__ */ jsx(
20272
20284
  Box,
20273
20285
  {
20274
20286
  ref: setNodeRef,
20275
20287
  "data-dnd-zone": ownGroup,
20276
20288
  "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
- ]
20289
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
20290
+ children
20289
20291
  }
20290
20292
  );
20291
20293
  };
20292
20294
  const rootContextValue = React75__default.useMemo(
20293
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
20294
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
20295
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
20296
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
20295
20297
  );
20296
20298
  const handleDragStart = React75__default.useCallback((event) => {
20297
20299
  const sourceZone = findZoneByItem(event.active.id);
@@ -20313,13 +20315,73 @@ function useDataDnd(args) {
20313
20315
  });
20314
20316
  }, [findZoneByItem, isRoot, zoneId]);
20315
20317
  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
20318
+ const { active, over } = event;
20319
+ const overData = over?.data?.current;
20320
+ const overGroup = overData?.dndGroup ?? null;
20321
+ setOverZoneGroup(overGroup);
20322
+ if (!over || !overGroup) return;
20323
+ const activeIdStr = String(active.id);
20324
+ let sourceMeta;
20325
+ let sourceGroup;
20326
+ for (const m of zonesRef.current.values()) {
20327
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
20328
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
20329
+ if (found) {
20330
+ sourceMeta = m;
20331
+ sourceGroup = m.group;
20332
+ break;
20333
+ }
20334
+ }
20335
+ if (!sourceMeta || !sourceGroup) {
20336
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
20337
+ return;
20338
+ }
20339
+ let targetMeta;
20340
+ for (const m of zonesRef.current.values()) {
20341
+ if (m.group === overGroup) {
20342
+ targetMeta = m;
20343
+ break;
20344
+ }
20345
+ }
20346
+ if (!targetMeta) {
20347
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
20348
+ return;
20349
+ }
20350
+ if (sourceGroup === overGroup) {
20351
+ setOptimisticOrders((prev) => {
20352
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
20353
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
20354
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
20355
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
20356
+ const reordered = arrayMove([...currentItems], oldIndex, newIndex);
20357
+ const next = new Map(prev);
20358
+ next.set(sourceGroup, reordered);
20359
+ return next;
20360
+ });
20361
+ return;
20362
+ }
20363
+ setOptimisticOrders((prev) => {
20364
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
20365
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
20366
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
20367
+ if (!activeItem) return prev;
20368
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
20369
+ return prev;
20370
+ }
20371
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
20372
+ const overIdStr = String(over.id);
20373
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
20374
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
20375
+ const newTarget = [
20376
+ ...currentTarget.slice(0, insertAt),
20377
+ activeItem,
20378
+ ...currentTarget.slice(insertAt)
20379
+ ];
20380
+ const next = new Map(prev);
20381
+ next.set(sourceGroup, newSource);
20382
+ next.set(overGroup, newTarget);
20383
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
20384
+ return next;
20323
20385
  });
20324
20386
  }, []);
20325
20387
  const handleDragCancel = React75__default.useCallback((event) => {