@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.
@@ -24923,11 +24923,32 @@ function useDataDnd(args) {
24923
24923
  const eventBus = useEventBus();
24924
24924
  const parentRoot = React93__namespace.default.useContext(RootCtx);
24925
24925
  const isRoot = enabled && parentRoot === null;
24926
- const [localOrder, setLocalOrder] = React93__namespace.default.useState(null);
24927
- const orderedItems = localOrder ?? items;
24928
- React93__namespace.default.useEffect(() => {
24929
- setLocalOrder(null);
24930
- }, [items]);
24926
+ const zoneId = React93__namespace.default.useId();
24927
+ const ownGroup = dragGroup ?? accepts ?? zoneId;
24928
+ const [optimisticOrders, setOptimisticOrders] = React93__namespace.default.useState(() => /* @__PURE__ */ new Map());
24929
+ const optimisticOrdersRef = React93__namespace.default.useRef(optimisticOrders);
24930
+ optimisticOrdersRef.current = optimisticOrders;
24931
+ const clearOptimisticOrder = React93__namespace.default.useCallback((group) => {
24932
+ setOptimisticOrders((prev) => {
24933
+ if (!prev.has(group)) return prev;
24934
+ const next = new Map(prev);
24935
+ next.delete(group);
24936
+ return next;
24937
+ });
24938
+ }, []);
24939
+ const sharedOptimistic = isRoot ? optimisticOrders : parentRoot?.optimisticOrders ?? /* @__PURE__ */ new Map();
24940
+ const optimisticEntry = sharedOptimistic.get(ownGroup);
24941
+ const orderedItems = optimisticEntry ?? items;
24942
+ if (isZone && enabled) {
24943
+ dndLog.debug("hook:render", {
24944
+ group: ownGroup,
24945
+ isRoot,
24946
+ itemsLen: items.length,
24947
+ optimisticEntryLen: optimisticEntry ? optimisticEntry.length : null,
24948
+ orderedLen: orderedItems.length,
24949
+ sharedKeys: Array.from(sharedOptimistic.keys())
24950
+ });
24951
+ }
24931
24952
  const itemIdsSignature = orderedItems.map((it, idx) => {
24932
24953
  const raw = it[dndItemIdField];
24933
24954
  return String(raw ?? `__idx_${idx}`);
@@ -24940,6 +24961,15 @@ function useDataDnd(args) {
24940
24961
  // eslint-disable-next-line react-hooks/exhaustive-deps
24941
24962
  [itemIdsSignature]
24942
24963
  );
24964
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
24965
+ React93__namespace.default.useEffect(() => {
24966
+ const root = isRoot ? null : parentRoot;
24967
+ if (root) {
24968
+ root.clearOptimisticOrder(ownGroup);
24969
+ } else {
24970
+ clearOptimisticOrder(ownGroup);
24971
+ }
24972
+ }, [itemsContentSig, ownGroup]);
24943
24973
  const zonesRef = React93__namespace.default.useRef(/* @__PURE__ */ new Map());
24944
24974
  const registerZone = React93__namespace.default.useCallback((zoneId2, meta2) => {
24945
24975
  zonesRef.current.set(zoneId2, meta2);
@@ -24949,11 +24979,9 @@ function useDataDnd(args) {
24949
24979
  }, []);
24950
24980
  const [activeDrag, setActiveDrag] = React93__namespace.default.useState(null);
24951
24981
  const [overZoneGroup, setOverZoneGroup] = React93__namespace.default.useState(null);
24952
- const zoneId = React93__namespace.default.useId();
24953
- const ownGroup = dragGroup ?? accepts ?? zoneId;
24954
24982
  const meta = React93__namespace.default.useMemo(
24955
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
24956
- [ownGroup, dropEvent, reorderEvent, itemIds]
24983
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
24984
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
24957
24985
  );
24958
24986
  React93__namespace.default.useEffect(() => {
24959
24987
  const target = isRoot ? null : parentRoot;
@@ -25000,7 +25028,7 @@ function useDataDnd(args) {
25000
25028
  },
25001
25029
  []
25002
25030
  );
25003
- const findZoneByGroup = React93__namespace.default.useCallback(
25031
+ React93__namespace.default.useCallback(
25004
25032
  (group) => {
25005
25033
  for (const z of zonesRef.current.values()) {
25006
25034
  if (z.group === group) return z;
@@ -25012,81 +25040,76 @@ function useDataDnd(args) {
25012
25040
  const handleDragEnd = React93__namespace.default.useCallback(
25013
25041
  (event) => {
25014
25042
  const { active, over } = event;
25015
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
25043
+ const activeIdStr = String(active.id);
25016
25044
  dndLog.debug("dragEnd:received", {
25017
25045
  activeId: active.id,
25018
25046
  overId: over?.id,
25019
- overData: over?.data?.current,
25020
- zones: allZones
25047
+ overData: over?.data?.current
25021
25048
  });
25022
- if (!over) {
25023
- dndLog.warn("dragEnd:abort:no-over", { activeId: active.id, reason: "no droppable under pointer at drop time \u2192 item snaps back" });
25024
- return;
25025
- }
25026
- const sourceZone = findZoneByItem(active.id);
25027
- const overData = over.data?.current;
25028
- const targetGroup = overData?.dndGroup;
25029
- dndLog.debug("dragEnd:resolved", { sourceGroup: sourceZone?.group, targetGroup, overDataKeys: overData ? Object.keys(overData) : null });
25030
- if (!sourceZone) {
25031
- dndLog.warn("dragEnd:abort:no-source-zone", { activeId: active.id });
25032
- return;
25033
- }
25034
- if (!targetGroup) {
25035
- dndLog.warn("dragEnd:abort:no-target-group", { overId: over.id, overData });
25036
- return;
25037
- }
25038
- const targetZone = findZoneByGroup(targetGroup);
25039
- if (!targetZone) {
25040
- dndLog.warn("dragEnd:abort:target-zone-not-registered", { targetGroup });
25049
+ let sourceMeta;
25050
+ let oldIndex = -1;
25051
+ let targetMeta;
25052
+ let newIndex = -1;
25053
+ for (const m of zonesRef.current.values()) {
25054
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
25055
+ if (rawIdx >= 0) {
25056
+ sourceMeta = m;
25057
+ oldIndex = rawIdx;
25058
+ }
25059
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
25060
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
25061
+ if (curIdx >= 0) {
25062
+ targetMeta = m;
25063
+ newIndex = curIdx;
25064
+ }
25065
+ }
25066
+ if (!sourceMeta || !targetMeta) {
25067
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
25041
25068
  return;
25042
25069
  }
25043
- if (sourceZone.group !== targetZone.group) {
25044
- if (targetZone.dropEvent) {
25045
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
25046
- const evt = `UI:${targetZone.dropEvent}`;
25070
+ if (sourceMeta.group !== targetMeta.group) {
25071
+ if (targetMeta.dropEvent) {
25072
+ const evt = `UI:${targetMeta.dropEvent}`;
25047
25073
  dndLog.info("dragEnd:cross-container:emit", {
25048
25074
  event: evt,
25049
- id: String(active.id),
25050
- sourceGroup: sourceZone.group,
25051
- targetGroup: targetZone.group,
25052
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
25075
+ id: activeIdStr,
25076
+ sourceGroup: sourceMeta.group,
25077
+ targetGroup: targetMeta.group,
25078
+ newIndex
25053
25079
  });
25054
25080
  eventBus.emit(evt, {
25055
- id: String(active.id),
25056
- sourceGroup: sourceZone.group,
25057
- targetGroup: targetZone.group,
25058
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
25081
+ id: activeIdStr,
25082
+ sourceGroup: sourceMeta.group,
25083
+ targetGroup: targetMeta.group,
25084
+ newIndex
25059
25085
  });
25060
25086
  } else {
25061
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
25087
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
25062
25088
  }
25063
25089
  return;
25064
25090
  }
25065
- const oldIndex = sourceZone.itemIds.indexOf(active.id);
25066
- const newIndex = sourceZone.itemIds.indexOf(over.id);
25067
- if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return;
25068
- if (sourceZone.group === ownGroup) {
25069
- const reordered = sortable.arrayMove(orderedItems, oldIndex, newIndex);
25070
- setLocalOrder(reordered);
25091
+ if (oldIndex === newIndex) {
25092
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
25093
+ return;
25071
25094
  }
25072
- if (sourceZone.reorderEvent) {
25073
- const evt = `UI:${sourceZone.reorderEvent}`;
25095
+ if (sourceMeta.reorderEvent) {
25096
+ const evt = `UI:${sourceMeta.reorderEvent}`;
25074
25097
  dndLog.info("dragEnd:reorder:emit", {
25075
25098
  event: evt,
25076
- id: String(active.id),
25099
+ id: activeIdStr,
25077
25100
  oldIndex,
25078
25101
  newIndex
25079
25102
  });
25080
25103
  eventBus.emit(evt, {
25081
- id: String(active.id),
25104
+ id: activeIdStr,
25082
25105
  oldIndex,
25083
25106
  newIndex
25084
25107
  });
25085
25108
  } else {
25086
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
25109
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
25087
25110
  }
25088
25111
  },
25089
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
25112
+ [eventBus]
25090
25113
  );
25091
25114
  const sortableData = React93__namespace.default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
25092
25115
  const SortableItem = React93__namespace.default.useCallback(
@@ -25147,30 +25170,20 @@ function useDataDnd(args) {
25147
25170
  React93__namespace.default.useEffect(() => {
25148
25171
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
25149
25172
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
25150
- return /* @__PURE__ */ jsxRuntime.jsxs(
25173
+ return /* @__PURE__ */ jsxRuntime.jsx(
25151
25174
  Box,
25152
25175
  {
25153
25176
  ref: setNodeRef,
25154
25177
  "data-dnd-zone": ownGroup,
25155
25178
  "data-dnd-is-over": isThisZoneOver ? "true" : "false",
25156
- className: isThisZoneOver ? "ring-2 ring-primary ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
25157
- children: [
25158
- children,
25159
- showForeignPlaceholder ? /* @__PURE__ */ jsxRuntime.jsx(
25160
- Box,
25161
- {
25162
- "data-dnd-placeholder": true,
25163
- style: { height: activeDrag2.height },
25164
- className: "border-2 border-dashed border-primary/60 bg-primary/5 rounded-md my-1 transition-all"
25165
- }
25166
- ) : null
25167
- ]
25179
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
25180
+ children
25168
25181
  }
25169
25182
  );
25170
25183
  };
25171
25184
  const rootContextValue = React93__namespace.default.useMemo(
25172
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
25173
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
25185
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
25186
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
25174
25187
  );
25175
25188
  const handleDragStart = React93__namespace.default.useCallback((event) => {
25176
25189
  const sourceZone = findZoneByItem(event.active.id);
@@ -25192,13 +25205,73 @@ function useDataDnd(args) {
25192
25205
  });
25193
25206
  }, [findZoneByItem, isRoot, zoneId]);
25194
25207
  const handleDragOver = React93__namespace.default.useCallback((event) => {
25195
- const overData = event.over?.data?.current;
25196
- const group = overData?.dndGroup ?? null;
25197
- setOverZoneGroup(group);
25198
- dndLog.debug("dragOver", {
25199
- activeId: event.active.id,
25200
- overId: event.over?.id,
25201
- overGroup: group
25208
+ const { active, over } = event;
25209
+ const overData = over?.data?.current;
25210
+ const overGroup = overData?.dndGroup ?? null;
25211
+ setOverZoneGroup(overGroup);
25212
+ if (!over || !overGroup) return;
25213
+ const activeIdStr = String(active.id);
25214
+ let sourceMeta;
25215
+ let sourceGroup;
25216
+ for (const m of zonesRef.current.values()) {
25217
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
25218
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
25219
+ if (found) {
25220
+ sourceMeta = m;
25221
+ sourceGroup = m.group;
25222
+ break;
25223
+ }
25224
+ }
25225
+ if (!sourceMeta || !sourceGroup) {
25226
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
25227
+ return;
25228
+ }
25229
+ let targetMeta;
25230
+ for (const m of zonesRef.current.values()) {
25231
+ if (m.group === overGroup) {
25232
+ targetMeta = m;
25233
+ break;
25234
+ }
25235
+ }
25236
+ if (!targetMeta) {
25237
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
25238
+ return;
25239
+ }
25240
+ if (sourceGroup === overGroup) {
25241
+ setOptimisticOrders((prev) => {
25242
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
25243
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
25244
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
25245
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
25246
+ const reordered = sortable.arrayMove([...currentItems], oldIndex, newIndex);
25247
+ const next = new Map(prev);
25248
+ next.set(sourceGroup, reordered);
25249
+ return next;
25250
+ });
25251
+ return;
25252
+ }
25253
+ setOptimisticOrders((prev) => {
25254
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
25255
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
25256
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
25257
+ if (!activeItem) return prev;
25258
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
25259
+ return prev;
25260
+ }
25261
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
25262
+ const overIdStr = String(over.id);
25263
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
25264
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
25265
+ const newTarget = [
25266
+ ...currentTarget.slice(0, insertAt),
25267
+ activeItem,
25268
+ ...currentTarget.slice(insertAt)
25269
+ ];
25270
+ const next = new Map(prev);
25271
+ next.set(sourceGroup, newSource);
25272
+ next.set(overGroup, newTarget);
25273
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
25274
+ return next;
25202
25275
  });
25203
25276
  }, []);
25204
25277
  const handleDragCancel = React93__namespace.default.useCallback((event) => {
package/dist/avl/index.js CHANGED
@@ -35,7 +35,7 @@ import langGo from 'react-syntax-highlighter/dist/esm/languages/prism/go.js';
35
35
  import langGraphql from 'react-syntax-highlighter/dist/esm/languages/prism/graphql.js';
36
36
  import { FieldTypeSchema, isInlineTrait, isEntityCall, schemaToIR, getPage, isCircuitEvent } from '@almadar/core';
37
37
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, pointerWithin, rectIntersection, closestCorners, DndContext, useDroppable } from '@dnd-kit/core';
38
- import { sortableKeyboardCoordinates, arrayMove, useSortable, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
38
+ import { sortableKeyboardCoordinates, useSortable, arrayMove, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
39
39
  import { CSS } from '@dnd-kit/utilities';
40
40
  import { useThree, useFrame, Canvas } from '@react-three/fiber';
41
41
  import * as THREE6 from 'three';
@@ -24877,11 +24877,32 @@ function useDataDnd(args) {
24877
24877
  const eventBus = useEventBus();
24878
24878
  const parentRoot = React93__default.useContext(RootCtx);
24879
24879
  const isRoot = enabled && parentRoot === null;
24880
- const [localOrder, setLocalOrder] = React93__default.useState(null);
24881
- const orderedItems = localOrder ?? items;
24882
- React93__default.useEffect(() => {
24883
- setLocalOrder(null);
24884
- }, [items]);
24880
+ const zoneId = React93__default.useId();
24881
+ const ownGroup = dragGroup ?? accepts ?? zoneId;
24882
+ const [optimisticOrders, setOptimisticOrders] = React93__default.useState(() => /* @__PURE__ */ new Map());
24883
+ const optimisticOrdersRef = React93__default.useRef(optimisticOrders);
24884
+ optimisticOrdersRef.current = optimisticOrders;
24885
+ const clearOptimisticOrder = React93__default.useCallback((group) => {
24886
+ setOptimisticOrders((prev) => {
24887
+ if (!prev.has(group)) return prev;
24888
+ const next = new Map(prev);
24889
+ next.delete(group);
24890
+ return next;
24891
+ });
24892
+ }, []);
24893
+ const sharedOptimistic = isRoot ? optimisticOrders : parentRoot?.optimisticOrders ?? /* @__PURE__ */ new Map();
24894
+ const optimisticEntry = sharedOptimistic.get(ownGroup);
24895
+ const orderedItems = optimisticEntry ?? items;
24896
+ if (isZone && enabled) {
24897
+ dndLog.debug("hook:render", {
24898
+ group: ownGroup,
24899
+ isRoot,
24900
+ itemsLen: items.length,
24901
+ optimisticEntryLen: optimisticEntry ? optimisticEntry.length : null,
24902
+ orderedLen: orderedItems.length,
24903
+ sharedKeys: Array.from(sharedOptimistic.keys())
24904
+ });
24905
+ }
24885
24906
  const itemIdsSignature = orderedItems.map((it, idx) => {
24886
24907
  const raw = it[dndItemIdField];
24887
24908
  return String(raw ?? `__idx_${idx}`);
@@ -24894,6 +24915,15 @@ function useDataDnd(args) {
24894
24915
  // eslint-disable-next-line react-hooks/exhaustive-deps
24895
24916
  [itemIdsSignature]
24896
24917
  );
24918
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
24919
+ React93__default.useEffect(() => {
24920
+ const root = isRoot ? null : parentRoot;
24921
+ if (root) {
24922
+ root.clearOptimisticOrder(ownGroup);
24923
+ } else {
24924
+ clearOptimisticOrder(ownGroup);
24925
+ }
24926
+ }, [itemsContentSig, ownGroup]);
24897
24927
  const zonesRef = React93__default.useRef(/* @__PURE__ */ new Map());
24898
24928
  const registerZone = React93__default.useCallback((zoneId2, meta2) => {
24899
24929
  zonesRef.current.set(zoneId2, meta2);
@@ -24903,11 +24933,9 @@ function useDataDnd(args) {
24903
24933
  }, []);
24904
24934
  const [activeDrag, setActiveDrag] = React93__default.useState(null);
24905
24935
  const [overZoneGroup, setOverZoneGroup] = React93__default.useState(null);
24906
- const zoneId = React93__default.useId();
24907
- const ownGroup = dragGroup ?? accepts ?? zoneId;
24908
24936
  const meta = React93__default.useMemo(
24909
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
24910
- [ownGroup, dropEvent, reorderEvent, itemIds]
24937
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
24938
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
24911
24939
  );
24912
24940
  React93__default.useEffect(() => {
24913
24941
  const target = isRoot ? null : parentRoot;
@@ -24954,7 +24982,7 @@ function useDataDnd(args) {
24954
24982
  },
24955
24983
  []
24956
24984
  );
24957
- const findZoneByGroup = React93__default.useCallback(
24985
+ React93__default.useCallback(
24958
24986
  (group) => {
24959
24987
  for (const z of zonesRef.current.values()) {
24960
24988
  if (z.group === group) return z;
@@ -24966,81 +24994,76 @@ function useDataDnd(args) {
24966
24994
  const handleDragEnd = React93__default.useCallback(
24967
24995
  (event) => {
24968
24996
  const { active, over } = event;
24969
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
24997
+ const activeIdStr = String(active.id);
24970
24998
  dndLog.debug("dragEnd:received", {
24971
24999
  activeId: active.id,
24972
25000
  overId: over?.id,
24973
- overData: over?.data?.current,
24974
- zones: allZones
25001
+ overData: over?.data?.current
24975
25002
  });
24976
- if (!over) {
24977
- dndLog.warn("dragEnd:abort:no-over", { activeId: active.id, reason: "no droppable under pointer at drop time \u2192 item snaps back" });
24978
- return;
24979
- }
24980
- const sourceZone = findZoneByItem(active.id);
24981
- const overData = over.data?.current;
24982
- const targetGroup = overData?.dndGroup;
24983
- dndLog.debug("dragEnd:resolved", { sourceGroup: sourceZone?.group, targetGroup, overDataKeys: overData ? Object.keys(overData) : null });
24984
- if (!sourceZone) {
24985
- dndLog.warn("dragEnd:abort:no-source-zone", { activeId: active.id });
24986
- return;
24987
- }
24988
- if (!targetGroup) {
24989
- dndLog.warn("dragEnd:abort:no-target-group", { overId: over.id, overData });
24990
- return;
24991
- }
24992
- const targetZone = findZoneByGroup(targetGroup);
24993
- if (!targetZone) {
24994
- dndLog.warn("dragEnd:abort:target-zone-not-registered", { targetGroup });
25003
+ let sourceMeta;
25004
+ let oldIndex = -1;
25005
+ let targetMeta;
25006
+ let newIndex = -1;
25007
+ for (const m of zonesRef.current.values()) {
25008
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
25009
+ if (rawIdx >= 0) {
25010
+ sourceMeta = m;
25011
+ oldIndex = rawIdx;
25012
+ }
25013
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
25014
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
25015
+ if (curIdx >= 0) {
25016
+ targetMeta = m;
25017
+ newIndex = curIdx;
25018
+ }
25019
+ }
25020
+ if (!sourceMeta || !targetMeta) {
25021
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
24995
25022
  return;
24996
25023
  }
24997
- if (sourceZone.group !== targetZone.group) {
24998
- if (targetZone.dropEvent) {
24999
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
25000
- const evt = `UI:${targetZone.dropEvent}`;
25024
+ if (sourceMeta.group !== targetMeta.group) {
25025
+ if (targetMeta.dropEvent) {
25026
+ const evt = `UI:${targetMeta.dropEvent}`;
25001
25027
  dndLog.info("dragEnd:cross-container:emit", {
25002
25028
  event: evt,
25003
- id: String(active.id),
25004
- sourceGroup: sourceZone.group,
25005
- targetGroup: targetZone.group,
25006
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
25029
+ id: activeIdStr,
25030
+ sourceGroup: sourceMeta.group,
25031
+ targetGroup: targetMeta.group,
25032
+ newIndex
25007
25033
  });
25008
25034
  eventBus.emit(evt, {
25009
- id: String(active.id),
25010
- sourceGroup: sourceZone.group,
25011
- targetGroup: targetZone.group,
25012
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
25035
+ id: activeIdStr,
25036
+ sourceGroup: sourceMeta.group,
25037
+ targetGroup: targetMeta.group,
25038
+ newIndex
25013
25039
  });
25014
25040
  } else {
25015
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
25041
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
25016
25042
  }
25017
25043
  return;
25018
25044
  }
25019
- const oldIndex = sourceZone.itemIds.indexOf(active.id);
25020
- const newIndex = sourceZone.itemIds.indexOf(over.id);
25021
- if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return;
25022
- if (sourceZone.group === ownGroup) {
25023
- const reordered = arrayMove(orderedItems, oldIndex, newIndex);
25024
- setLocalOrder(reordered);
25045
+ if (oldIndex === newIndex) {
25046
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
25047
+ return;
25025
25048
  }
25026
- if (sourceZone.reorderEvent) {
25027
- const evt = `UI:${sourceZone.reorderEvent}`;
25049
+ if (sourceMeta.reorderEvent) {
25050
+ const evt = `UI:${sourceMeta.reorderEvent}`;
25028
25051
  dndLog.info("dragEnd:reorder:emit", {
25029
25052
  event: evt,
25030
- id: String(active.id),
25053
+ id: activeIdStr,
25031
25054
  oldIndex,
25032
25055
  newIndex
25033
25056
  });
25034
25057
  eventBus.emit(evt, {
25035
- id: String(active.id),
25058
+ id: activeIdStr,
25036
25059
  oldIndex,
25037
25060
  newIndex
25038
25061
  });
25039
25062
  } else {
25040
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
25063
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
25041
25064
  }
25042
25065
  },
25043
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
25066
+ [eventBus]
25044
25067
  );
25045
25068
  const sortableData = React93__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
25046
25069
  const SortableItem = React93__default.useCallback(
@@ -25101,30 +25124,20 @@ function useDataDnd(args) {
25101
25124
  React93__default.useEffect(() => {
25102
25125
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
25103
25126
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
25104
- return /* @__PURE__ */ jsxs(
25127
+ return /* @__PURE__ */ jsx(
25105
25128
  Box,
25106
25129
  {
25107
25130
  ref: setNodeRef,
25108
25131
  "data-dnd-zone": ownGroup,
25109
25132
  "data-dnd-is-over": isThisZoneOver ? "true" : "false",
25110
- className: isThisZoneOver ? "ring-2 ring-primary ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
25111
- children: [
25112
- children,
25113
- showForeignPlaceholder ? /* @__PURE__ */ jsx(
25114
- Box,
25115
- {
25116
- "data-dnd-placeholder": true,
25117
- style: { height: activeDrag2.height },
25118
- className: "border-2 border-dashed border-primary/60 bg-primary/5 rounded-md my-1 transition-all"
25119
- }
25120
- ) : null
25121
- ]
25133
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
25134
+ children
25122
25135
  }
25123
25136
  );
25124
25137
  };
25125
25138
  const rootContextValue = React93__default.useMemo(
25126
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
25127
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
25139
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
25140
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
25128
25141
  );
25129
25142
  const handleDragStart = React93__default.useCallback((event) => {
25130
25143
  const sourceZone = findZoneByItem(event.active.id);
@@ -25146,13 +25159,73 @@ function useDataDnd(args) {
25146
25159
  });
25147
25160
  }, [findZoneByItem, isRoot, zoneId]);
25148
25161
  const handleDragOver = React93__default.useCallback((event) => {
25149
- const overData = event.over?.data?.current;
25150
- const group = overData?.dndGroup ?? null;
25151
- setOverZoneGroup(group);
25152
- dndLog.debug("dragOver", {
25153
- activeId: event.active.id,
25154
- overId: event.over?.id,
25155
- overGroup: group
25162
+ const { active, over } = event;
25163
+ const overData = over?.data?.current;
25164
+ const overGroup = overData?.dndGroup ?? null;
25165
+ setOverZoneGroup(overGroup);
25166
+ if (!over || !overGroup) return;
25167
+ const activeIdStr = String(active.id);
25168
+ let sourceMeta;
25169
+ let sourceGroup;
25170
+ for (const m of zonesRef.current.values()) {
25171
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
25172
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
25173
+ if (found) {
25174
+ sourceMeta = m;
25175
+ sourceGroup = m.group;
25176
+ break;
25177
+ }
25178
+ }
25179
+ if (!sourceMeta || !sourceGroup) {
25180
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
25181
+ return;
25182
+ }
25183
+ let targetMeta;
25184
+ for (const m of zonesRef.current.values()) {
25185
+ if (m.group === overGroup) {
25186
+ targetMeta = m;
25187
+ break;
25188
+ }
25189
+ }
25190
+ if (!targetMeta) {
25191
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
25192
+ return;
25193
+ }
25194
+ if (sourceGroup === overGroup) {
25195
+ setOptimisticOrders((prev) => {
25196
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
25197
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
25198
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
25199
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
25200
+ const reordered = arrayMove([...currentItems], oldIndex, newIndex);
25201
+ const next = new Map(prev);
25202
+ next.set(sourceGroup, reordered);
25203
+ return next;
25204
+ });
25205
+ return;
25206
+ }
25207
+ setOptimisticOrders((prev) => {
25208
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
25209
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
25210
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
25211
+ if (!activeItem) return prev;
25212
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
25213
+ return prev;
25214
+ }
25215
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
25216
+ const overIdStr = String(over.id);
25217
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
25218
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
25219
+ const newTarget = [
25220
+ ...currentTarget.slice(0, insertAt),
25221
+ activeItem,
25222
+ ...currentTarget.slice(insertAt)
25223
+ ];
25224
+ const next = new Map(prev);
25225
+ next.set(sourceGroup, newSource);
25226
+ next.set(overGroup, newTarget);
25227
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
25228
+ return next;
25156
25229
  });
25157
25230
  }, []);
25158
25231
  const handleDragCancel = React93__default.useCallback((event) => {