@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.
@@ -21104,11 +21104,21 @@ function useDataDnd(args) {
21104
21104
  const eventBus = useEventBus();
21105
21105
  const parentRoot = React80__namespace.default.useContext(RootCtx);
21106
21106
  const isRoot = enabled && parentRoot === null;
21107
- const [localOrder, setLocalOrder] = React80__namespace.default.useState(null);
21108
- const orderedItems = localOrder ?? items;
21109
- React80__namespace.default.useEffect(() => {
21110
- setLocalOrder(null);
21111
- }, [items]);
21107
+ const zoneId = React80__namespace.default.useId();
21108
+ const ownGroup = dragGroup ?? accepts ?? zoneId;
21109
+ const [optimisticOrders, setOptimisticOrders] = React80__namespace.default.useState(() => /* @__PURE__ */ new Map());
21110
+ const optimisticOrdersRef = React80__namespace.default.useRef(optimisticOrders);
21111
+ optimisticOrdersRef.current = optimisticOrders;
21112
+ const clearOptimisticOrder = React80__namespace.default.useCallback((group) => {
21113
+ setOptimisticOrders((prev) => {
21114
+ if (!prev.has(group)) return prev;
21115
+ const next = new Map(prev);
21116
+ next.delete(group);
21117
+ return next;
21118
+ });
21119
+ }, []);
21120
+ const sharedOptimistic = isRoot ? optimisticOrders : parentRoot?.optimisticOrders ?? /* @__PURE__ */ new Map();
21121
+ const orderedItems = sharedOptimistic.get(ownGroup) ?? items;
21112
21122
  const itemIdsSignature = orderedItems.map((it, idx) => {
21113
21123
  const raw = it[dndItemIdField];
21114
21124
  return String(raw ?? `__idx_${idx}`);
@@ -21121,6 +21131,15 @@ function useDataDnd(args) {
21121
21131
  // eslint-disable-next-line react-hooks/exhaustive-deps
21122
21132
  [itemIdsSignature]
21123
21133
  );
21134
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
21135
+ React80__namespace.default.useEffect(() => {
21136
+ const root = isRoot ? null : parentRoot;
21137
+ if (root) {
21138
+ root.clearOptimisticOrder(ownGroup);
21139
+ } else {
21140
+ clearOptimisticOrder(ownGroup);
21141
+ }
21142
+ }, [itemsContentSig, ownGroup]);
21124
21143
  const zonesRef = React80__namespace.default.useRef(/* @__PURE__ */ new Map());
21125
21144
  const registerZone = React80__namespace.default.useCallback((zoneId2, meta2) => {
21126
21145
  zonesRef.current.set(zoneId2, meta2);
@@ -21130,11 +21149,9 @@ function useDataDnd(args) {
21130
21149
  }, []);
21131
21150
  const [activeDrag, setActiveDrag] = React80__namespace.default.useState(null);
21132
21151
  const [overZoneGroup, setOverZoneGroup] = React80__namespace.default.useState(null);
21133
- const zoneId = React80__namespace.default.useId();
21134
- const ownGroup = dragGroup ?? accepts ?? zoneId;
21135
21152
  const meta = React80__namespace.default.useMemo(
21136
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
21137
- [ownGroup, dropEvent, reorderEvent, itemIds]
21153
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
21154
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
21138
21155
  );
21139
21156
  React80__namespace.default.useEffect(() => {
21140
21157
  const target = isRoot ? null : parentRoot;
@@ -21181,7 +21198,7 @@ function useDataDnd(args) {
21181
21198
  },
21182
21199
  []
21183
21200
  );
21184
- const findZoneByGroup = React80__namespace.default.useCallback(
21201
+ React80__namespace.default.useCallback(
21185
21202
  (group) => {
21186
21203
  for (const z of zonesRef.current.values()) {
21187
21204
  if (z.group === group) return z;
@@ -21193,81 +21210,76 @@ function useDataDnd(args) {
21193
21210
  const handleDragEnd = React80__namespace.default.useCallback(
21194
21211
  (event) => {
21195
21212
  const { active, over } = event;
21196
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
21213
+ const activeIdStr = String(active.id);
21197
21214
  dndLog.debug("dragEnd:received", {
21198
21215
  activeId: active.id,
21199
21216
  overId: over?.id,
21200
- overData: over?.data?.current,
21201
- zones: allZones
21217
+ overData: over?.data?.current
21202
21218
  });
21203
- if (!over) {
21204
- dndLog.warn("dragEnd:abort:no-over", { activeId: active.id, reason: "no droppable under pointer at drop time \u2192 item snaps back" });
21219
+ let sourceMeta;
21220
+ let oldIndex = -1;
21221
+ let targetMeta;
21222
+ let newIndex = -1;
21223
+ for (const m of zonesRef.current.values()) {
21224
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21225
+ if (rawIdx >= 0) {
21226
+ sourceMeta = m;
21227
+ oldIndex = rawIdx;
21228
+ }
21229
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21230
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21231
+ if (curIdx >= 0) {
21232
+ targetMeta = m;
21233
+ newIndex = curIdx;
21234
+ }
21235
+ }
21236
+ if (!sourceMeta || !targetMeta) {
21237
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
21205
21238
  return;
21206
21239
  }
21207
- const sourceZone = findZoneByItem(active.id);
21208
- const overData = over.data?.current;
21209
- const targetGroup = overData?.dndGroup;
21210
- dndLog.debug("dragEnd:resolved", { sourceGroup: sourceZone?.group, targetGroup, overDataKeys: overData ? Object.keys(overData) : null });
21211
- if (!sourceZone) {
21212
- dndLog.warn("dragEnd:abort:no-source-zone", { activeId: active.id });
21213
- return;
21214
- }
21215
- if (!targetGroup) {
21216
- dndLog.warn("dragEnd:abort:no-target-group", { overId: over.id, overData });
21217
- return;
21218
- }
21219
- const targetZone = findZoneByGroup(targetGroup);
21220
- if (!targetZone) {
21221
- dndLog.warn("dragEnd:abort:target-zone-not-registered", { targetGroup });
21222
- return;
21223
- }
21224
- if (sourceZone.group !== targetZone.group) {
21225
- if (targetZone.dropEvent) {
21226
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
21227
- const evt = `UI:${targetZone.dropEvent}`;
21240
+ if (sourceMeta.group !== targetMeta.group) {
21241
+ if (targetMeta.dropEvent) {
21242
+ const evt = `UI:${targetMeta.dropEvent}`;
21228
21243
  dndLog.info("dragEnd:cross-container:emit", {
21229
21244
  event: evt,
21230
- id: String(active.id),
21231
- sourceGroup: sourceZone.group,
21232
- targetGroup: targetZone.group,
21233
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21245
+ id: activeIdStr,
21246
+ sourceGroup: sourceMeta.group,
21247
+ targetGroup: targetMeta.group,
21248
+ newIndex
21234
21249
  });
21235
21250
  eventBus.emit(evt, {
21236
- id: String(active.id),
21237
- sourceGroup: sourceZone.group,
21238
- targetGroup: targetZone.group,
21239
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21251
+ id: activeIdStr,
21252
+ sourceGroup: sourceMeta.group,
21253
+ targetGroup: targetMeta.group,
21254
+ newIndex
21240
21255
  });
21241
21256
  } else {
21242
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
21257
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
21243
21258
  }
21244
21259
  return;
21245
21260
  }
21246
- const oldIndex = sourceZone.itemIds.indexOf(active.id);
21247
- const newIndex = sourceZone.itemIds.indexOf(over.id);
21248
- if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return;
21249
- if (sourceZone.group === ownGroup) {
21250
- const reordered = sortable.arrayMove(orderedItems, oldIndex, newIndex);
21251
- setLocalOrder(reordered);
21261
+ if (oldIndex === newIndex) {
21262
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
21263
+ return;
21252
21264
  }
21253
- if (sourceZone.reorderEvent) {
21254
- const evt = `UI:${sourceZone.reorderEvent}`;
21265
+ if (sourceMeta.reorderEvent) {
21266
+ const evt = `UI:${sourceMeta.reorderEvent}`;
21255
21267
  dndLog.info("dragEnd:reorder:emit", {
21256
21268
  event: evt,
21257
- id: String(active.id),
21269
+ id: activeIdStr,
21258
21270
  oldIndex,
21259
21271
  newIndex
21260
21272
  });
21261
21273
  eventBus.emit(evt, {
21262
- id: String(active.id),
21274
+ id: activeIdStr,
21263
21275
  oldIndex,
21264
21276
  newIndex
21265
21277
  });
21266
21278
  } else {
21267
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
21279
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
21268
21280
  }
21269
21281
  },
21270
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
21282
+ [eventBus]
21271
21283
  );
21272
21284
  const sortableData = React80__namespace.default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
21273
21285
  const SortableItem = React80__namespace.default.useCallback(
@@ -21328,30 +21340,20 @@ function useDataDnd(args) {
21328
21340
  React80__namespace.default.useEffect(() => {
21329
21341
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
21330
21342
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
21331
- return /* @__PURE__ */ jsxRuntime.jsxs(
21343
+ return /* @__PURE__ */ jsxRuntime.jsx(
21332
21344
  Box,
21333
21345
  {
21334
21346
  ref: setNodeRef,
21335
21347
  "data-dnd-zone": ownGroup,
21336
21348
  "data-dnd-is-over": isThisZoneOver ? "true" : "false",
21337
- className: isThisZoneOver ? "ring-2 ring-primary ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21338
- children: [
21339
- children,
21340
- showForeignPlaceholder ? /* @__PURE__ */ jsxRuntime.jsx(
21341
- Box,
21342
- {
21343
- "data-dnd-placeholder": true,
21344
- style: { height: activeDrag2.height },
21345
- className: "border-2 border-dashed border-primary/60 bg-primary/5 rounded-md my-1 transition-all"
21346
- }
21347
- ) : null
21348
- ]
21349
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21350
+ children
21349
21351
  }
21350
21352
  );
21351
21353
  };
21352
21354
  const rootContextValue = React80__namespace.default.useMemo(
21353
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
21354
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
21355
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
21356
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
21355
21357
  );
21356
21358
  const handleDragStart = React80__namespace.default.useCallback((event) => {
21357
21359
  const sourceZone = findZoneByItem(event.active.id);
@@ -21373,13 +21375,73 @@ function useDataDnd(args) {
21373
21375
  });
21374
21376
  }, [findZoneByItem, isRoot, zoneId]);
21375
21377
  const handleDragOver = React80__namespace.default.useCallback((event) => {
21376
- const overData = event.over?.data?.current;
21377
- const group = overData?.dndGroup ?? null;
21378
- setOverZoneGroup(group);
21379
- dndLog.debug("dragOver", {
21380
- activeId: event.active.id,
21381
- overId: event.over?.id,
21382
- overGroup: group
21378
+ const { active, over } = event;
21379
+ const overData = over?.data?.current;
21380
+ const overGroup = overData?.dndGroup ?? null;
21381
+ setOverZoneGroup(overGroup);
21382
+ if (!over || !overGroup) return;
21383
+ const activeIdStr = String(active.id);
21384
+ let sourceMeta;
21385
+ let sourceGroup;
21386
+ for (const m of zonesRef.current.values()) {
21387
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21388
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
21389
+ if (found) {
21390
+ sourceMeta = m;
21391
+ sourceGroup = m.group;
21392
+ break;
21393
+ }
21394
+ }
21395
+ if (!sourceMeta || !sourceGroup) {
21396
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
21397
+ return;
21398
+ }
21399
+ let targetMeta;
21400
+ for (const m of zonesRef.current.values()) {
21401
+ if (m.group === overGroup) {
21402
+ targetMeta = m;
21403
+ break;
21404
+ }
21405
+ }
21406
+ if (!targetMeta) {
21407
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
21408
+ return;
21409
+ }
21410
+ if (sourceGroup === overGroup) {
21411
+ setOptimisticOrders((prev) => {
21412
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21413
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
21414
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
21415
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
21416
+ const reordered = sortable.arrayMove([...currentItems], oldIndex, newIndex);
21417
+ const next = new Map(prev);
21418
+ next.set(sourceGroup, reordered);
21419
+ return next;
21420
+ });
21421
+ return;
21422
+ }
21423
+ setOptimisticOrders((prev) => {
21424
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21425
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
21426
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
21427
+ if (!activeItem) return prev;
21428
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
21429
+ return prev;
21430
+ }
21431
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
21432
+ const overIdStr = String(over.id);
21433
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
21434
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
21435
+ const newTarget = [
21436
+ ...currentTarget.slice(0, insertAt),
21437
+ activeItem,
21438
+ ...currentTarget.slice(insertAt)
21439
+ ];
21440
+ const next = new Map(prev);
21441
+ next.set(sourceGroup, newSource);
21442
+ next.set(overGroup, newTarget);
21443
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
21444
+ return next;
21383
21445
  });
21384
21446
  }, []);
21385
21447
  const handleDragCancel = React80__namespace.default.useCallback((event) => {
@@ -37,7 +37,7 @@ import langGo from 'react-syntax-highlighter/dist/esm/languages/prism/go.js';
37
37
  import langGraphql from 'react-syntax-highlighter/dist/esm/languages/prism/graphql.js';
38
38
  import { isCircuitEvent, schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, isEntityCall, isInlineTrait } from '@almadar/core';
39
39
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, pointerWithin, rectIntersection, closestCorners, DndContext, useDroppable } from '@dnd-kit/core';
40
- import { sortableKeyboardCoordinates, arrayMove, useSortable, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
40
+ import { sortableKeyboardCoordinates, useSortable, arrayMove, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
41
41
  import { CSS } from '@dnd-kit/utilities';
42
42
  import { Handle, Position } from '@xyflow/react';
43
43
  import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
@@ -21058,11 +21058,21 @@ function useDataDnd(args) {
21058
21058
  const eventBus = useEventBus();
21059
21059
  const parentRoot = React80__default.useContext(RootCtx);
21060
21060
  const isRoot = enabled && parentRoot === null;
21061
- const [localOrder, setLocalOrder] = React80__default.useState(null);
21062
- const orderedItems = localOrder ?? items;
21063
- React80__default.useEffect(() => {
21064
- setLocalOrder(null);
21065
- }, [items]);
21061
+ const zoneId = React80__default.useId();
21062
+ const ownGroup = dragGroup ?? accepts ?? zoneId;
21063
+ const [optimisticOrders, setOptimisticOrders] = React80__default.useState(() => /* @__PURE__ */ new Map());
21064
+ const optimisticOrdersRef = React80__default.useRef(optimisticOrders);
21065
+ optimisticOrdersRef.current = optimisticOrders;
21066
+ const clearOptimisticOrder = React80__default.useCallback((group) => {
21067
+ setOptimisticOrders((prev) => {
21068
+ if (!prev.has(group)) return prev;
21069
+ const next = new Map(prev);
21070
+ next.delete(group);
21071
+ return next;
21072
+ });
21073
+ }, []);
21074
+ const sharedOptimistic = isRoot ? optimisticOrders : parentRoot?.optimisticOrders ?? /* @__PURE__ */ new Map();
21075
+ const orderedItems = sharedOptimistic.get(ownGroup) ?? items;
21066
21076
  const itemIdsSignature = orderedItems.map((it, idx) => {
21067
21077
  const raw = it[dndItemIdField];
21068
21078
  return String(raw ?? `__idx_${idx}`);
@@ -21075,6 +21085,15 @@ function useDataDnd(args) {
21075
21085
  // eslint-disable-next-line react-hooks/exhaustive-deps
21076
21086
  [itemIdsSignature]
21077
21087
  );
21088
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
21089
+ React80__default.useEffect(() => {
21090
+ const root = isRoot ? null : parentRoot;
21091
+ if (root) {
21092
+ root.clearOptimisticOrder(ownGroup);
21093
+ } else {
21094
+ clearOptimisticOrder(ownGroup);
21095
+ }
21096
+ }, [itemsContentSig, ownGroup]);
21078
21097
  const zonesRef = React80__default.useRef(/* @__PURE__ */ new Map());
21079
21098
  const registerZone = React80__default.useCallback((zoneId2, meta2) => {
21080
21099
  zonesRef.current.set(zoneId2, meta2);
@@ -21084,11 +21103,9 @@ function useDataDnd(args) {
21084
21103
  }, []);
21085
21104
  const [activeDrag, setActiveDrag] = React80__default.useState(null);
21086
21105
  const [overZoneGroup, setOverZoneGroup] = React80__default.useState(null);
21087
- const zoneId = React80__default.useId();
21088
- const ownGroup = dragGroup ?? accepts ?? zoneId;
21089
21106
  const meta = React80__default.useMemo(
21090
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
21091
- [ownGroup, dropEvent, reorderEvent, itemIds]
21107
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
21108
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
21092
21109
  );
21093
21110
  React80__default.useEffect(() => {
21094
21111
  const target = isRoot ? null : parentRoot;
@@ -21135,7 +21152,7 @@ function useDataDnd(args) {
21135
21152
  },
21136
21153
  []
21137
21154
  );
21138
- const findZoneByGroup = React80__default.useCallback(
21155
+ React80__default.useCallback(
21139
21156
  (group) => {
21140
21157
  for (const z of zonesRef.current.values()) {
21141
21158
  if (z.group === group) return z;
@@ -21147,81 +21164,76 @@ function useDataDnd(args) {
21147
21164
  const handleDragEnd = React80__default.useCallback(
21148
21165
  (event) => {
21149
21166
  const { active, over } = event;
21150
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
21167
+ const activeIdStr = String(active.id);
21151
21168
  dndLog.debug("dragEnd:received", {
21152
21169
  activeId: active.id,
21153
21170
  overId: over?.id,
21154
- overData: over?.data?.current,
21155
- zones: allZones
21171
+ overData: over?.data?.current
21156
21172
  });
21157
- if (!over) {
21158
- dndLog.warn("dragEnd:abort:no-over", { activeId: active.id, reason: "no droppable under pointer at drop time \u2192 item snaps back" });
21173
+ let sourceMeta;
21174
+ let oldIndex = -1;
21175
+ let targetMeta;
21176
+ let newIndex = -1;
21177
+ for (const m of zonesRef.current.values()) {
21178
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21179
+ if (rawIdx >= 0) {
21180
+ sourceMeta = m;
21181
+ oldIndex = rawIdx;
21182
+ }
21183
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21184
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21185
+ if (curIdx >= 0) {
21186
+ targetMeta = m;
21187
+ newIndex = curIdx;
21188
+ }
21189
+ }
21190
+ if (!sourceMeta || !targetMeta) {
21191
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
21159
21192
  return;
21160
21193
  }
21161
- const sourceZone = findZoneByItem(active.id);
21162
- const overData = over.data?.current;
21163
- const targetGroup = overData?.dndGroup;
21164
- dndLog.debug("dragEnd:resolved", { sourceGroup: sourceZone?.group, targetGroup, overDataKeys: overData ? Object.keys(overData) : null });
21165
- if (!sourceZone) {
21166
- dndLog.warn("dragEnd:abort:no-source-zone", { activeId: active.id });
21167
- return;
21168
- }
21169
- if (!targetGroup) {
21170
- dndLog.warn("dragEnd:abort:no-target-group", { overId: over.id, overData });
21171
- return;
21172
- }
21173
- const targetZone = findZoneByGroup(targetGroup);
21174
- if (!targetZone) {
21175
- dndLog.warn("dragEnd:abort:target-zone-not-registered", { targetGroup });
21176
- return;
21177
- }
21178
- if (sourceZone.group !== targetZone.group) {
21179
- if (targetZone.dropEvent) {
21180
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
21181
- const evt = `UI:${targetZone.dropEvent}`;
21194
+ if (sourceMeta.group !== targetMeta.group) {
21195
+ if (targetMeta.dropEvent) {
21196
+ const evt = `UI:${targetMeta.dropEvent}`;
21182
21197
  dndLog.info("dragEnd:cross-container:emit", {
21183
21198
  event: evt,
21184
- id: String(active.id),
21185
- sourceGroup: sourceZone.group,
21186
- targetGroup: targetZone.group,
21187
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21199
+ id: activeIdStr,
21200
+ sourceGroup: sourceMeta.group,
21201
+ targetGroup: targetMeta.group,
21202
+ newIndex
21188
21203
  });
21189
21204
  eventBus.emit(evt, {
21190
- id: String(active.id),
21191
- sourceGroup: sourceZone.group,
21192
- targetGroup: targetZone.group,
21193
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21205
+ id: activeIdStr,
21206
+ sourceGroup: sourceMeta.group,
21207
+ targetGroup: targetMeta.group,
21208
+ newIndex
21194
21209
  });
21195
21210
  } else {
21196
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
21211
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
21197
21212
  }
21198
21213
  return;
21199
21214
  }
21200
- const oldIndex = sourceZone.itemIds.indexOf(active.id);
21201
- const newIndex = sourceZone.itemIds.indexOf(over.id);
21202
- if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return;
21203
- if (sourceZone.group === ownGroup) {
21204
- const reordered = arrayMove(orderedItems, oldIndex, newIndex);
21205
- setLocalOrder(reordered);
21215
+ if (oldIndex === newIndex) {
21216
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
21217
+ return;
21206
21218
  }
21207
- if (sourceZone.reorderEvent) {
21208
- const evt = `UI:${sourceZone.reorderEvent}`;
21219
+ if (sourceMeta.reorderEvent) {
21220
+ const evt = `UI:${sourceMeta.reorderEvent}`;
21209
21221
  dndLog.info("dragEnd:reorder:emit", {
21210
21222
  event: evt,
21211
- id: String(active.id),
21223
+ id: activeIdStr,
21212
21224
  oldIndex,
21213
21225
  newIndex
21214
21226
  });
21215
21227
  eventBus.emit(evt, {
21216
- id: String(active.id),
21228
+ id: activeIdStr,
21217
21229
  oldIndex,
21218
21230
  newIndex
21219
21231
  });
21220
21232
  } else {
21221
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
21233
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
21222
21234
  }
21223
21235
  },
21224
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
21236
+ [eventBus]
21225
21237
  );
21226
21238
  const sortableData = React80__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
21227
21239
  const SortableItem = React80__default.useCallback(
@@ -21282,30 +21294,20 @@ function useDataDnd(args) {
21282
21294
  React80__default.useEffect(() => {
21283
21295
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
21284
21296
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
21285
- return /* @__PURE__ */ jsxs(
21297
+ return /* @__PURE__ */ jsx(
21286
21298
  Box,
21287
21299
  {
21288
21300
  ref: setNodeRef,
21289
21301
  "data-dnd-zone": ownGroup,
21290
21302
  "data-dnd-is-over": isThisZoneOver ? "true" : "false",
21291
- className: isThisZoneOver ? "ring-2 ring-primary ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21292
- children: [
21293
- children,
21294
- showForeignPlaceholder ? /* @__PURE__ */ jsx(
21295
- Box,
21296
- {
21297
- "data-dnd-placeholder": true,
21298
- style: { height: activeDrag2.height },
21299
- className: "border-2 border-dashed border-primary/60 bg-primary/5 rounded-md my-1 transition-all"
21300
- }
21301
- ) : null
21302
- ]
21303
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21304
+ children
21303
21305
  }
21304
21306
  );
21305
21307
  };
21306
21308
  const rootContextValue = React80__default.useMemo(
21307
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
21308
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
21309
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
21310
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
21309
21311
  );
21310
21312
  const handleDragStart = React80__default.useCallback((event) => {
21311
21313
  const sourceZone = findZoneByItem(event.active.id);
@@ -21327,13 +21329,73 @@ function useDataDnd(args) {
21327
21329
  });
21328
21330
  }, [findZoneByItem, isRoot, zoneId]);
21329
21331
  const handleDragOver = React80__default.useCallback((event) => {
21330
- const overData = event.over?.data?.current;
21331
- const group = overData?.dndGroup ?? null;
21332
- setOverZoneGroup(group);
21333
- dndLog.debug("dragOver", {
21334
- activeId: event.active.id,
21335
- overId: event.over?.id,
21336
- overGroup: group
21332
+ const { active, over } = event;
21333
+ const overData = over?.data?.current;
21334
+ const overGroup = overData?.dndGroup ?? null;
21335
+ setOverZoneGroup(overGroup);
21336
+ if (!over || !overGroup) return;
21337
+ const activeIdStr = String(active.id);
21338
+ let sourceMeta;
21339
+ let sourceGroup;
21340
+ for (const m of zonesRef.current.values()) {
21341
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21342
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
21343
+ if (found) {
21344
+ sourceMeta = m;
21345
+ sourceGroup = m.group;
21346
+ break;
21347
+ }
21348
+ }
21349
+ if (!sourceMeta || !sourceGroup) {
21350
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
21351
+ return;
21352
+ }
21353
+ let targetMeta;
21354
+ for (const m of zonesRef.current.values()) {
21355
+ if (m.group === overGroup) {
21356
+ targetMeta = m;
21357
+ break;
21358
+ }
21359
+ }
21360
+ if (!targetMeta) {
21361
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
21362
+ return;
21363
+ }
21364
+ if (sourceGroup === overGroup) {
21365
+ setOptimisticOrders((prev) => {
21366
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21367
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
21368
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
21369
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
21370
+ const reordered = arrayMove([...currentItems], oldIndex, newIndex);
21371
+ const next = new Map(prev);
21372
+ next.set(sourceGroup, reordered);
21373
+ return next;
21374
+ });
21375
+ return;
21376
+ }
21377
+ setOptimisticOrders((prev) => {
21378
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21379
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
21380
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
21381
+ if (!activeItem) return prev;
21382
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
21383
+ return prev;
21384
+ }
21385
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
21386
+ const overIdStr = String(over.id);
21387
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
21388
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
21389
+ const newTarget = [
21390
+ ...currentTarget.slice(0, insertAt),
21391
+ activeItem,
21392
+ ...currentTarget.slice(insertAt)
21393
+ ];
21394
+ const next = new Map(prev);
21395
+ next.set(sourceGroup, newSource);
21396
+ next.set(overGroup, newTarget);
21397
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
21398
+ return next;
21337
21399
  });
21338
21400
  }, []);
21339
21401
  const handleDragCancel = React80__default.useCallback((event) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "4.50.14",
3
+ "version": "4.50.15",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [