@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.
@@ -24923,11 +24923,21 @@ 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 orderedItems = sharedOptimistic.get(ownGroup) ?? items;
24931
24941
  const itemIdsSignature = orderedItems.map((it, idx) => {
24932
24942
  const raw = it[dndItemIdField];
24933
24943
  return String(raw ?? `__idx_${idx}`);
@@ -24940,6 +24950,15 @@ function useDataDnd(args) {
24940
24950
  // eslint-disable-next-line react-hooks/exhaustive-deps
24941
24951
  [itemIdsSignature]
24942
24952
  );
24953
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
24954
+ React93__namespace.default.useEffect(() => {
24955
+ const root = isRoot ? null : parentRoot;
24956
+ if (root) {
24957
+ root.clearOptimisticOrder(ownGroup);
24958
+ } else {
24959
+ clearOptimisticOrder(ownGroup);
24960
+ }
24961
+ }, [itemsContentSig, ownGroup]);
24943
24962
  const zonesRef = React93__namespace.default.useRef(/* @__PURE__ */ new Map());
24944
24963
  const registerZone = React93__namespace.default.useCallback((zoneId2, meta2) => {
24945
24964
  zonesRef.current.set(zoneId2, meta2);
@@ -24949,11 +24968,9 @@ function useDataDnd(args) {
24949
24968
  }, []);
24950
24969
  const [activeDrag, setActiveDrag] = React93__namespace.default.useState(null);
24951
24970
  const [overZoneGroup, setOverZoneGroup] = React93__namespace.default.useState(null);
24952
- const zoneId = React93__namespace.default.useId();
24953
- const ownGroup = dragGroup ?? accepts ?? zoneId;
24954
24971
  const meta = React93__namespace.default.useMemo(
24955
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
24956
- [ownGroup, dropEvent, reorderEvent, itemIds]
24972
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
24973
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
24957
24974
  );
24958
24975
  React93__namespace.default.useEffect(() => {
24959
24976
  const target = isRoot ? null : parentRoot;
@@ -25000,7 +25017,7 @@ function useDataDnd(args) {
25000
25017
  },
25001
25018
  []
25002
25019
  );
25003
- const findZoneByGroup = React93__namespace.default.useCallback(
25020
+ React93__namespace.default.useCallback(
25004
25021
  (group) => {
25005
25022
  for (const z of zonesRef.current.values()) {
25006
25023
  if (z.group === group) return z;
@@ -25012,81 +25029,76 @@ function useDataDnd(args) {
25012
25029
  const handleDragEnd = React93__namespace.default.useCallback(
25013
25030
  (event) => {
25014
25031
  const { active, over } = event;
25015
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
25032
+ const activeIdStr = String(active.id);
25016
25033
  dndLog.debug("dragEnd:received", {
25017
25034
  activeId: active.id,
25018
25035
  overId: over?.id,
25019
- overData: over?.data?.current,
25020
- zones: allZones
25036
+ overData: over?.data?.current
25021
25037
  });
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 });
25038
+ let sourceMeta;
25039
+ let oldIndex = -1;
25040
+ let targetMeta;
25041
+ let newIndex = -1;
25042
+ for (const m of zonesRef.current.values()) {
25043
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
25044
+ if (rawIdx >= 0) {
25045
+ sourceMeta = m;
25046
+ oldIndex = rawIdx;
25047
+ }
25048
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
25049
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
25050
+ if (curIdx >= 0) {
25051
+ targetMeta = m;
25052
+ newIndex = curIdx;
25053
+ }
25054
+ }
25055
+ if (!sourceMeta || !targetMeta) {
25056
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
25036
25057
  return;
25037
25058
  }
25038
- const targetZone = findZoneByGroup(targetGroup);
25039
- if (!targetZone) {
25040
- dndLog.warn("dragEnd:abort:target-zone-not-registered", { targetGroup });
25041
- return;
25042
- }
25043
- if (sourceZone.group !== targetZone.group) {
25044
- if (targetZone.dropEvent) {
25045
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
25046
- const evt = `UI:${targetZone.dropEvent}`;
25059
+ if (sourceMeta.group !== targetMeta.group) {
25060
+ if (targetMeta.dropEvent) {
25061
+ const evt = `UI:${targetMeta.dropEvent}`;
25047
25062
  dndLog.info("dragEnd:cross-container:emit", {
25048
25063
  event: evt,
25049
- id: String(active.id),
25050
- sourceGroup: sourceZone.group,
25051
- targetGroup: targetZone.group,
25052
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
25064
+ id: activeIdStr,
25065
+ sourceGroup: sourceMeta.group,
25066
+ targetGroup: targetMeta.group,
25067
+ newIndex
25053
25068
  });
25054
25069
  eventBus.emit(evt, {
25055
- id: String(active.id),
25056
- sourceGroup: sourceZone.group,
25057
- targetGroup: targetZone.group,
25058
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
25070
+ id: activeIdStr,
25071
+ sourceGroup: sourceMeta.group,
25072
+ targetGroup: targetMeta.group,
25073
+ newIndex
25059
25074
  });
25060
25075
  } else {
25061
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
25076
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
25062
25077
  }
25063
25078
  return;
25064
25079
  }
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);
25080
+ if (oldIndex === newIndex) {
25081
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
25082
+ return;
25071
25083
  }
25072
- if (sourceZone.reorderEvent) {
25073
- const evt = `UI:${sourceZone.reorderEvent}`;
25084
+ if (sourceMeta.reorderEvent) {
25085
+ const evt = `UI:${sourceMeta.reorderEvent}`;
25074
25086
  dndLog.info("dragEnd:reorder:emit", {
25075
25087
  event: evt,
25076
- id: String(active.id),
25088
+ id: activeIdStr,
25077
25089
  oldIndex,
25078
25090
  newIndex
25079
25091
  });
25080
25092
  eventBus.emit(evt, {
25081
- id: String(active.id),
25093
+ id: activeIdStr,
25082
25094
  oldIndex,
25083
25095
  newIndex
25084
25096
  });
25085
25097
  } else {
25086
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
25098
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
25087
25099
  }
25088
25100
  },
25089
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
25101
+ [eventBus]
25090
25102
  );
25091
25103
  const sortableData = React93__namespace.default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
25092
25104
  const SortableItem = React93__namespace.default.useCallback(
@@ -25147,30 +25159,20 @@ function useDataDnd(args) {
25147
25159
  React93__namespace.default.useEffect(() => {
25148
25160
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
25149
25161
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
25150
- return /* @__PURE__ */ jsxRuntime.jsxs(
25162
+ return /* @__PURE__ */ jsxRuntime.jsx(
25151
25163
  Box,
25152
25164
  {
25153
25165
  ref: setNodeRef,
25154
25166
  "data-dnd-zone": ownGroup,
25155
25167
  "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
- ]
25168
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
25169
+ children
25168
25170
  }
25169
25171
  );
25170
25172
  };
25171
25173
  const rootContextValue = React93__namespace.default.useMemo(
25172
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
25173
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
25174
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
25175
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
25174
25176
  );
25175
25177
  const handleDragStart = React93__namespace.default.useCallback((event) => {
25176
25178
  const sourceZone = findZoneByItem(event.active.id);
@@ -25192,13 +25194,73 @@ function useDataDnd(args) {
25192
25194
  });
25193
25195
  }, [findZoneByItem, isRoot, zoneId]);
25194
25196
  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
25197
+ const { active, over } = event;
25198
+ const overData = over?.data?.current;
25199
+ const overGroup = overData?.dndGroup ?? null;
25200
+ setOverZoneGroup(overGroup);
25201
+ if (!over || !overGroup) return;
25202
+ const activeIdStr = String(active.id);
25203
+ let sourceMeta;
25204
+ let sourceGroup;
25205
+ for (const m of zonesRef.current.values()) {
25206
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
25207
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
25208
+ if (found) {
25209
+ sourceMeta = m;
25210
+ sourceGroup = m.group;
25211
+ break;
25212
+ }
25213
+ }
25214
+ if (!sourceMeta || !sourceGroup) {
25215
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
25216
+ return;
25217
+ }
25218
+ let targetMeta;
25219
+ for (const m of zonesRef.current.values()) {
25220
+ if (m.group === overGroup) {
25221
+ targetMeta = m;
25222
+ break;
25223
+ }
25224
+ }
25225
+ if (!targetMeta) {
25226
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
25227
+ return;
25228
+ }
25229
+ if (sourceGroup === overGroup) {
25230
+ setOptimisticOrders((prev) => {
25231
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
25232
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
25233
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
25234
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
25235
+ const reordered = sortable.arrayMove([...currentItems], oldIndex, newIndex);
25236
+ const next = new Map(prev);
25237
+ next.set(sourceGroup, reordered);
25238
+ return next;
25239
+ });
25240
+ return;
25241
+ }
25242
+ setOptimisticOrders((prev) => {
25243
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
25244
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
25245
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
25246
+ if (!activeItem) return prev;
25247
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
25248
+ return prev;
25249
+ }
25250
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
25251
+ const overIdStr = String(over.id);
25252
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
25253
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
25254
+ const newTarget = [
25255
+ ...currentTarget.slice(0, insertAt),
25256
+ activeItem,
25257
+ ...currentTarget.slice(insertAt)
25258
+ ];
25259
+ const next = new Map(prev);
25260
+ next.set(sourceGroup, newSource);
25261
+ next.set(overGroup, newTarget);
25262
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
25263
+ return next;
25202
25264
  });
25203
25265
  }, []);
25204
25266
  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,21 @@ 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 orderedItems = sharedOptimistic.get(ownGroup) ?? items;
24885
24895
  const itemIdsSignature = orderedItems.map((it, idx) => {
24886
24896
  const raw = it[dndItemIdField];
24887
24897
  return String(raw ?? `__idx_${idx}`);
@@ -24894,6 +24904,15 @@ function useDataDnd(args) {
24894
24904
  // eslint-disable-next-line react-hooks/exhaustive-deps
24895
24905
  [itemIdsSignature]
24896
24906
  );
24907
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
24908
+ React93__default.useEffect(() => {
24909
+ const root = isRoot ? null : parentRoot;
24910
+ if (root) {
24911
+ root.clearOptimisticOrder(ownGroup);
24912
+ } else {
24913
+ clearOptimisticOrder(ownGroup);
24914
+ }
24915
+ }, [itemsContentSig, ownGroup]);
24897
24916
  const zonesRef = React93__default.useRef(/* @__PURE__ */ new Map());
24898
24917
  const registerZone = React93__default.useCallback((zoneId2, meta2) => {
24899
24918
  zonesRef.current.set(zoneId2, meta2);
@@ -24903,11 +24922,9 @@ function useDataDnd(args) {
24903
24922
  }, []);
24904
24923
  const [activeDrag, setActiveDrag] = React93__default.useState(null);
24905
24924
  const [overZoneGroup, setOverZoneGroup] = React93__default.useState(null);
24906
- const zoneId = React93__default.useId();
24907
- const ownGroup = dragGroup ?? accepts ?? zoneId;
24908
24925
  const meta = React93__default.useMemo(
24909
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
24910
- [ownGroup, dropEvent, reorderEvent, itemIds]
24926
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
24927
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
24911
24928
  );
24912
24929
  React93__default.useEffect(() => {
24913
24930
  const target = isRoot ? null : parentRoot;
@@ -24954,7 +24971,7 @@ function useDataDnd(args) {
24954
24971
  },
24955
24972
  []
24956
24973
  );
24957
- const findZoneByGroup = React93__default.useCallback(
24974
+ React93__default.useCallback(
24958
24975
  (group) => {
24959
24976
  for (const z of zonesRef.current.values()) {
24960
24977
  if (z.group === group) return z;
@@ -24966,81 +24983,76 @@ function useDataDnd(args) {
24966
24983
  const handleDragEnd = React93__default.useCallback(
24967
24984
  (event) => {
24968
24985
  const { active, over } = event;
24969
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
24986
+ const activeIdStr = String(active.id);
24970
24987
  dndLog.debug("dragEnd:received", {
24971
24988
  activeId: active.id,
24972
24989
  overId: over?.id,
24973
- overData: over?.data?.current,
24974
- zones: allZones
24990
+ overData: over?.data?.current
24975
24991
  });
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 });
24992
+ let sourceMeta;
24993
+ let oldIndex = -1;
24994
+ let targetMeta;
24995
+ let newIndex = -1;
24996
+ for (const m of zonesRef.current.values()) {
24997
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
24998
+ if (rawIdx >= 0) {
24999
+ sourceMeta = m;
25000
+ oldIndex = rawIdx;
25001
+ }
25002
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
25003
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
25004
+ if (curIdx >= 0) {
25005
+ targetMeta = m;
25006
+ newIndex = curIdx;
25007
+ }
25008
+ }
25009
+ if (!sourceMeta || !targetMeta) {
25010
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
24990
25011
  return;
24991
25012
  }
24992
- const targetZone = findZoneByGroup(targetGroup);
24993
- if (!targetZone) {
24994
- dndLog.warn("dragEnd:abort:target-zone-not-registered", { targetGroup });
24995
- return;
24996
- }
24997
- if (sourceZone.group !== targetZone.group) {
24998
- if (targetZone.dropEvent) {
24999
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
25000
- const evt = `UI:${targetZone.dropEvent}`;
25013
+ if (sourceMeta.group !== targetMeta.group) {
25014
+ if (targetMeta.dropEvent) {
25015
+ const evt = `UI:${targetMeta.dropEvent}`;
25001
25016
  dndLog.info("dragEnd:cross-container:emit", {
25002
25017
  event: evt,
25003
- id: String(active.id),
25004
- sourceGroup: sourceZone.group,
25005
- targetGroup: targetZone.group,
25006
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
25018
+ id: activeIdStr,
25019
+ sourceGroup: sourceMeta.group,
25020
+ targetGroup: targetMeta.group,
25021
+ newIndex
25007
25022
  });
25008
25023
  eventBus.emit(evt, {
25009
- id: String(active.id),
25010
- sourceGroup: sourceZone.group,
25011
- targetGroup: targetZone.group,
25012
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
25024
+ id: activeIdStr,
25025
+ sourceGroup: sourceMeta.group,
25026
+ targetGroup: targetMeta.group,
25027
+ newIndex
25013
25028
  });
25014
25029
  } else {
25015
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
25030
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
25016
25031
  }
25017
25032
  return;
25018
25033
  }
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);
25034
+ if (oldIndex === newIndex) {
25035
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
25036
+ return;
25025
25037
  }
25026
- if (sourceZone.reorderEvent) {
25027
- const evt = `UI:${sourceZone.reorderEvent}`;
25038
+ if (sourceMeta.reorderEvent) {
25039
+ const evt = `UI:${sourceMeta.reorderEvent}`;
25028
25040
  dndLog.info("dragEnd:reorder:emit", {
25029
25041
  event: evt,
25030
- id: String(active.id),
25042
+ id: activeIdStr,
25031
25043
  oldIndex,
25032
25044
  newIndex
25033
25045
  });
25034
25046
  eventBus.emit(evt, {
25035
- id: String(active.id),
25047
+ id: activeIdStr,
25036
25048
  oldIndex,
25037
25049
  newIndex
25038
25050
  });
25039
25051
  } else {
25040
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
25052
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
25041
25053
  }
25042
25054
  },
25043
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
25055
+ [eventBus]
25044
25056
  );
25045
25057
  const sortableData = React93__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
25046
25058
  const SortableItem = React93__default.useCallback(
@@ -25101,30 +25113,20 @@ function useDataDnd(args) {
25101
25113
  React93__default.useEffect(() => {
25102
25114
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
25103
25115
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
25104
- return /* @__PURE__ */ jsxs(
25116
+ return /* @__PURE__ */ jsx(
25105
25117
  Box,
25106
25118
  {
25107
25119
  ref: setNodeRef,
25108
25120
  "data-dnd-zone": ownGroup,
25109
25121
  "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
- ]
25122
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
25123
+ children
25122
25124
  }
25123
25125
  );
25124
25126
  };
25125
25127
  const rootContextValue = React93__default.useMemo(
25126
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
25127
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
25128
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
25129
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
25128
25130
  );
25129
25131
  const handleDragStart = React93__default.useCallback((event) => {
25130
25132
  const sourceZone = findZoneByItem(event.active.id);
@@ -25146,13 +25148,73 @@ function useDataDnd(args) {
25146
25148
  });
25147
25149
  }, [findZoneByItem, isRoot, zoneId]);
25148
25150
  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
25151
+ const { active, over } = event;
25152
+ const overData = over?.data?.current;
25153
+ const overGroup = overData?.dndGroup ?? null;
25154
+ setOverZoneGroup(overGroup);
25155
+ if (!over || !overGroup) return;
25156
+ const activeIdStr = String(active.id);
25157
+ let sourceMeta;
25158
+ let sourceGroup;
25159
+ for (const m of zonesRef.current.values()) {
25160
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
25161
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
25162
+ if (found) {
25163
+ sourceMeta = m;
25164
+ sourceGroup = m.group;
25165
+ break;
25166
+ }
25167
+ }
25168
+ if (!sourceMeta || !sourceGroup) {
25169
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
25170
+ return;
25171
+ }
25172
+ let targetMeta;
25173
+ for (const m of zonesRef.current.values()) {
25174
+ if (m.group === overGroup) {
25175
+ targetMeta = m;
25176
+ break;
25177
+ }
25178
+ }
25179
+ if (!targetMeta) {
25180
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
25181
+ return;
25182
+ }
25183
+ if (sourceGroup === overGroup) {
25184
+ setOptimisticOrders((prev) => {
25185
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
25186
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
25187
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
25188
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
25189
+ const reordered = arrayMove([...currentItems], oldIndex, newIndex);
25190
+ const next = new Map(prev);
25191
+ next.set(sourceGroup, reordered);
25192
+ return next;
25193
+ });
25194
+ return;
25195
+ }
25196
+ setOptimisticOrders((prev) => {
25197
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
25198
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
25199
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
25200
+ if (!activeItem) return prev;
25201
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
25202
+ return prev;
25203
+ }
25204
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
25205
+ const overIdStr = String(over.id);
25206
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
25207
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
25208
+ const newTarget = [
25209
+ ...currentTarget.slice(0, insertAt),
25210
+ activeItem,
25211
+ ...currentTarget.slice(insertAt)
25212
+ ];
25213
+ const next = new Map(prev);
25214
+ next.set(sourceGroup, newSource);
25215
+ next.set(overGroup, newTarget);
25216
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
25217
+ return next;
25156
25218
  });
25157
25219
  }, []);
25158
25220
  const handleDragCancel = React93__default.useCallback((event) => {