@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.
@@ -21104,11 +21104,32 @@ 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 optimisticEntry = sharedOptimistic.get(ownGroup);
21122
+ const orderedItems = optimisticEntry ?? items;
21123
+ if (isZone && enabled) {
21124
+ dndLog.debug("hook:render", {
21125
+ group: ownGroup,
21126
+ isRoot,
21127
+ itemsLen: items.length,
21128
+ optimisticEntryLen: optimisticEntry ? optimisticEntry.length : null,
21129
+ orderedLen: orderedItems.length,
21130
+ sharedKeys: Array.from(sharedOptimistic.keys())
21131
+ });
21132
+ }
21112
21133
  const itemIdsSignature = orderedItems.map((it, idx) => {
21113
21134
  const raw = it[dndItemIdField];
21114
21135
  return String(raw ?? `__idx_${idx}`);
@@ -21121,6 +21142,15 @@ function useDataDnd(args) {
21121
21142
  // eslint-disable-next-line react-hooks/exhaustive-deps
21122
21143
  [itemIdsSignature]
21123
21144
  );
21145
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
21146
+ React80__namespace.default.useEffect(() => {
21147
+ const root = isRoot ? null : parentRoot;
21148
+ if (root) {
21149
+ root.clearOptimisticOrder(ownGroup);
21150
+ } else {
21151
+ clearOptimisticOrder(ownGroup);
21152
+ }
21153
+ }, [itemsContentSig, ownGroup]);
21124
21154
  const zonesRef = React80__namespace.default.useRef(/* @__PURE__ */ new Map());
21125
21155
  const registerZone = React80__namespace.default.useCallback((zoneId2, meta2) => {
21126
21156
  zonesRef.current.set(zoneId2, meta2);
@@ -21130,11 +21160,9 @@ function useDataDnd(args) {
21130
21160
  }, []);
21131
21161
  const [activeDrag, setActiveDrag] = React80__namespace.default.useState(null);
21132
21162
  const [overZoneGroup, setOverZoneGroup] = React80__namespace.default.useState(null);
21133
- const zoneId = React80__namespace.default.useId();
21134
- const ownGroup = dragGroup ?? accepts ?? zoneId;
21135
21163
  const meta = React80__namespace.default.useMemo(
21136
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
21137
- [ownGroup, dropEvent, reorderEvent, itemIds]
21164
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
21165
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
21138
21166
  );
21139
21167
  React80__namespace.default.useEffect(() => {
21140
21168
  const target = isRoot ? null : parentRoot;
@@ -21181,7 +21209,7 @@ function useDataDnd(args) {
21181
21209
  },
21182
21210
  []
21183
21211
  );
21184
- const findZoneByGroup = React80__namespace.default.useCallback(
21212
+ React80__namespace.default.useCallback(
21185
21213
  (group) => {
21186
21214
  for (const z of zonesRef.current.values()) {
21187
21215
  if (z.group === group) return z;
@@ -21193,81 +21221,76 @@ function useDataDnd(args) {
21193
21221
  const handleDragEnd = React80__namespace.default.useCallback(
21194
21222
  (event) => {
21195
21223
  const { active, over } = event;
21196
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
21224
+ const activeIdStr = String(active.id);
21197
21225
  dndLog.debug("dragEnd:received", {
21198
21226
  activeId: active.id,
21199
21227
  overId: over?.id,
21200
- overData: over?.data?.current,
21201
- zones: allZones
21228
+ overData: over?.data?.current
21202
21229
  });
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" });
21205
- return;
21206
- }
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 });
21230
+ let sourceMeta;
21231
+ let oldIndex = -1;
21232
+ let targetMeta;
21233
+ let newIndex = -1;
21234
+ for (const m of zonesRef.current.values()) {
21235
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21236
+ if (rawIdx >= 0) {
21237
+ sourceMeta = m;
21238
+ oldIndex = rawIdx;
21239
+ }
21240
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21241
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21242
+ if (curIdx >= 0) {
21243
+ targetMeta = m;
21244
+ newIndex = curIdx;
21245
+ }
21246
+ }
21247
+ if (!sourceMeta || !targetMeta) {
21248
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
21222
21249
  return;
21223
21250
  }
21224
- if (sourceZone.group !== targetZone.group) {
21225
- if (targetZone.dropEvent) {
21226
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
21227
- const evt = `UI:${targetZone.dropEvent}`;
21251
+ if (sourceMeta.group !== targetMeta.group) {
21252
+ if (targetMeta.dropEvent) {
21253
+ const evt = `UI:${targetMeta.dropEvent}`;
21228
21254
  dndLog.info("dragEnd:cross-container:emit", {
21229
21255
  event: evt,
21230
- id: String(active.id),
21231
- sourceGroup: sourceZone.group,
21232
- targetGroup: targetZone.group,
21233
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21256
+ id: activeIdStr,
21257
+ sourceGroup: sourceMeta.group,
21258
+ targetGroup: targetMeta.group,
21259
+ newIndex
21234
21260
  });
21235
21261
  eventBus.emit(evt, {
21236
- id: String(active.id),
21237
- sourceGroup: sourceZone.group,
21238
- targetGroup: targetZone.group,
21239
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21262
+ id: activeIdStr,
21263
+ sourceGroup: sourceMeta.group,
21264
+ targetGroup: targetMeta.group,
21265
+ newIndex
21240
21266
  });
21241
21267
  } else {
21242
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
21268
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
21243
21269
  }
21244
21270
  return;
21245
21271
  }
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);
21272
+ if (oldIndex === newIndex) {
21273
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
21274
+ return;
21252
21275
  }
21253
- if (sourceZone.reorderEvent) {
21254
- const evt = `UI:${sourceZone.reorderEvent}`;
21276
+ if (sourceMeta.reorderEvent) {
21277
+ const evt = `UI:${sourceMeta.reorderEvent}`;
21255
21278
  dndLog.info("dragEnd:reorder:emit", {
21256
21279
  event: evt,
21257
- id: String(active.id),
21280
+ id: activeIdStr,
21258
21281
  oldIndex,
21259
21282
  newIndex
21260
21283
  });
21261
21284
  eventBus.emit(evt, {
21262
- id: String(active.id),
21285
+ id: activeIdStr,
21263
21286
  oldIndex,
21264
21287
  newIndex
21265
21288
  });
21266
21289
  } else {
21267
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
21290
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
21268
21291
  }
21269
21292
  },
21270
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
21293
+ [eventBus]
21271
21294
  );
21272
21295
  const sortableData = React80__namespace.default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
21273
21296
  const SortableItem = React80__namespace.default.useCallback(
@@ -21328,30 +21351,20 @@ function useDataDnd(args) {
21328
21351
  React80__namespace.default.useEffect(() => {
21329
21352
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
21330
21353
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
21331
- return /* @__PURE__ */ jsxRuntime.jsxs(
21354
+ return /* @__PURE__ */ jsxRuntime.jsx(
21332
21355
  Box,
21333
21356
  {
21334
21357
  ref: setNodeRef,
21335
21358
  "data-dnd-zone": ownGroup,
21336
21359
  "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
- ]
21360
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21361
+ children
21349
21362
  }
21350
21363
  );
21351
21364
  };
21352
21365
  const rootContextValue = React80__namespace.default.useMemo(
21353
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
21354
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
21366
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
21367
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
21355
21368
  );
21356
21369
  const handleDragStart = React80__namespace.default.useCallback((event) => {
21357
21370
  const sourceZone = findZoneByItem(event.active.id);
@@ -21373,13 +21386,73 @@ function useDataDnd(args) {
21373
21386
  });
21374
21387
  }, [findZoneByItem, isRoot, zoneId]);
21375
21388
  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
21389
+ const { active, over } = event;
21390
+ const overData = over?.data?.current;
21391
+ const overGroup = overData?.dndGroup ?? null;
21392
+ setOverZoneGroup(overGroup);
21393
+ if (!over || !overGroup) return;
21394
+ const activeIdStr = String(active.id);
21395
+ let sourceMeta;
21396
+ let sourceGroup;
21397
+ for (const m of zonesRef.current.values()) {
21398
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21399
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
21400
+ if (found) {
21401
+ sourceMeta = m;
21402
+ sourceGroup = m.group;
21403
+ break;
21404
+ }
21405
+ }
21406
+ if (!sourceMeta || !sourceGroup) {
21407
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
21408
+ return;
21409
+ }
21410
+ let targetMeta;
21411
+ for (const m of zonesRef.current.values()) {
21412
+ if (m.group === overGroup) {
21413
+ targetMeta = m;
21414
+ break;
21415
+ }
21416
+ }
21417
+ if (!targetMeta) {
21418
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
21419
+ return;
21420
+ }
21421
+ if (sourceGroup === overGroup) {
21422
+ setOptimisticOrders((prev) => {
21423
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21424
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
21425
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
21426
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
21427
+ const reordered = sortable.arrayMove([...currentItems], oldIndex, newIndex);
21428
+ const next = new Map(prev);
21429
+ next.set(sourceGroup, reordered);
21430
+ return next;
21431
+ });
21432
+ return;
21433
+ }
21434
+ setOptimisticOrders((prev) => {
21435
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21436
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
21437
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
21438
+ if (!activeItem) return prev;
21439
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
21440
+ return prev;
21441
+ }
21442
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
21443
+ const overIdStr = String(over.id);
21444
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
21445
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
21446
+ const newTarget = [
21447
+ ...currentTarget.slice(0, insertAt),
21448
+ activeItem,
21449
+ ...currentTarget.slice(insertAt)
21450
+ ];
21451
+ const next = new Map(prev);
21452
+ next.set(sourceGroup, newSource);
21453
+ next.set(overGroup, newTarget);
21454
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
21455
+ return next;
21383
21456
  });
21384
21457
  }, []);
21385
21458
  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,32 @@ 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 optimisticEntry = sharedOptimistic.get(ownGroup);
21076
+ const orderedItems = optimisticEntry ?? items;
21077
+ if (isZone && enabled) {
21078
+ dndLog.debug("hook:render", {
21079
+ group: ownGroup,
21080
+ isRoot,
21081
+ itemsLen: items.length,
21082
+ optimisticEntryLen: optimisticEntry ? optimisticEntry.length : null,
21083
+ orderedLen: orderedItems.length,
21084
+ sharedKeys: Array.from(sharedOptimistic.keys())
21085
+ });
21086
+ }
21066
21087
  const itemIdsSignature = orderedItems.map((it, idx) => {
21067
21088
  const raw = it[dndItemIdField];
21068
21089
  return String(raw ?? `__idx_${idx}`);
@@ -21075,6 +21096,15 @@ function useDataDnd(args) {
21075
21096
  // eslint-disable-next-line react-hooks/exhaustive-deps
21076
21097
  [itemIdsSignature]
21077
21098
  );
21099
+ const itemsContentSig = items.map((it, idx) => String(it[dndItemIdField] ?? `__${idx}`)).join("|");
21100
+ React80__default.useEffect(() => {
21101
+ const root = isRoot ? null : parentRoot;
21102
+ if (root) {
21103
+ root.clearOptimisticOrder(ownGroup);
21104
+ } else {
21105
+ clearOptimisticOrder(ownGroup);
21106
+ }
21107
+ }, [itemsContentSig, ownGroup]);
21078
21108
  const zonesRef = React80__default.useRef(/* @__PURE__ */ new Map());
21079
21109
  const registerZone = React80__default.useCallback((zoneId2, meta2) => {
21080
21110
  zonesRef.current.set(zoneId2, meta2);
@@ -21084,11 +21114,9 @@ function useDataDnd(args) {
21084
21114
  }, []);
21085
21115
  const [activeDrag, setActiveDrag] = React80__default.useState(null);
21086
21116
  const [overZoneGroup, setOverZoneGroup] = React80__default.useState(null);
21087
- const zoneId = React80__default.useId();
21088
- const ownGroup = dragGroup ?? accepts ?? zoneId;
21089
21117
  const meta = React80__default.useMemo(
21090
- () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds }),
21091
- [ownGroup, dropEvent, reorderEvent, itemIds]
21118
+ () => ({ group: ownGroup, dropEvent, reorderEvent, itemIds, rawItems: items, idField: dndItemIdField }),
21119
+ [ownGroup, dropEvent, reorderEvent, itemIds, items, dndItemIdField]
21092
21120
  );
21093
21121
  React80__default.useEffect(() => {
21094
21122
  const target = isRoot ? null : parentRoot;
@@ -21135,7 +21163,7 @@ function useDataDnd(args) {
21135
21163
  },
21136
21164
  []
21137
21165
  );
21138
- const findZoneByGroup = React80__default.useCallback(
21166
+ React80__default.useCallback(
21139
21167
  (group) => {
21140
21168
  for (const z of zonesRef.current.values()) {
21141
21169
  if (z.group === group) return z;
@@ -21147,81 +21175,76 @@ function useDataDnd(args) {
21147
21175
  const handleDragEnd = React80__default.useCallback(
21148
21176
  (event) => {
21149
21177
  const { active, over } = event;
21150
- const allZones = Array.from(zonesRef.current.entries()).map(([id, m]) => ({ id, group: m.group, items: m.itemIds.length }));
21178
+ const activeIdStr = String(active.id);
21151
21179
  dndLog.debug("dragEnd:received", {
21152
21180
  activeId: active.id,
21153
21181
  overId: over?.id,
21154
- overData: over?.data?.current,
21155
- zones: allZones
21182
+ overData: over?.data?.current
21156
21183
  });
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" });
21159
- return;
21160
- }
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 });
21184
+ let sourceMeta;
21185
+ let oldIndex = -1;
21186
+ let targetMeta;
21187
+ let newIndex = -1;
21188
+ for (const m of zonesRef.current.values()) {
21189
+ const rawIdx = m.rawItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21190
+ if (rawIdx >= 0) {
21191
+ sourceMeta = m;
21192
+ oldIndex = rawIdx;
21193
+ }
21194
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21195
+ const curIdx = currentItems.findIndex((it) => String(it[m.idField]) === activeIdStr);
21196
+ if (curIdx >= 0) {
21197
+ targetMeta = m;
21198
+ newIndex = curIdx;
21199
+ }
21200
+ }
21201
+ if (!sourceMeta || !targetMeta) {
21202
+ dndLog.warn("dragEnd:abort:no-zone-resolved", { activeId: active.id, hasSource: !!sourceMeta, hasTarget: !!targetMeta });
21176
21203
  return;
21177
21204
  }
21178
- if (sourceZone.group !== targetZone.group) {
21179
- if (targetZone.dropEvent) {
21180
- const newIndex2 = targetZone.itemIds.indexOf(over.id);
21181
- const evt = `UI:${targetZone.dropEvent}`;
21205
+ if (sourceMeta.group !== targetMeta.group) {
21206
+ if (targetMeta.dropEvent) {
21207
+ const evt = `UI:${targetMeta.dropEvent}`;
21182
21208
  dndLog.info("dragEnd:cross-container:emit", {
21183
21209
  event: evt,
21184
- id: String(active.id),
21185
- sourceGroup: sourceZone.group,
21186
- targetGroup: targetZone.group,
21187
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21210
+ id: activeIdStr,
21211
+ sourceGroup: sourceMeta.group,
21212
+ targetGroup: targetMeta.group,
21213
+ newIndex
21188
21214
  });
21189
21215
  eventBus.emit(evt, {
21190
- id: String(active.id),
21191
- sourceGroup: sourceZone.group,
21192
- targetGroup: targetZone.group,
21193
- newIndex: newIndex2 === -1 ? targetZone.itemIds.length : newIndex2
21216
+ id: activeIdStr,
21217
+ sourceGroup: sourceMeta.group,
21218
+ targetGroup: targetMeta.group,
21219
+ newIndex
21194
21220
  });
21195
21221
  } else {
21196
- dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetZone.group });
21222
+ dndLog.warn("dragEnd:cross-container:no-dropEvent-on-target", { targetGroup: targetMeta.group });
21197
21223
  }
21198
21224
  return;
21199
21225
  }
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);
21226
+ if (oldIndex === newIndex) {
21227
+ dndLog.debug("dragEnd:reorder:no-op", { sourceGroup: sourceMeta.group, oldIndex });
21228
+ return;
21206
21229
  }
21207
- if (sourceZone.reorderEvent) {
21208
- const evt = `UI:${sourceZone.reorderEvent}`;
21230
+ if (sourceMeta.reorderEvent) {
21231
+ const evt = `UI:${sourceMeta.reorderEvent}`;
21209
21232
  dndLog.info("dragEnd:reorder:emit", {
21210
21233
  event: evt,
21211
- id: String(active.id),
21234
+ id: activeIdStr,
21212
21235
  oldIndex,
21213
21236
  newIndex
21214
21237
  });
21215
21238
  eventBus.emit(evt, {
21216
- id: String(active.id),
21239
+ id: activeIdStr,
21217
21240
  oldIndex,
21218
21241
  newIndex
21219
21242
  });
21220
21243
  } else {
21221
- dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceZone.group });
21244
+ dndLog.debug("dragEnd:reorder:no-reorderEvent", { sourceGroup: sourceMeta.group });
21222
21245
  }
21223
21246
  },
21224
- [orderedItems, ownGroup, findZoneByItem, findZoneByGroup, eventBus]
21247
+ [eventBus]
21225
21248
  );
21226
21249
  const sortableData = React80__default.useMemo(() => ({ dndGroup: ownGroup }), [ownGroup]);
21227
21250
  const SortableItem = React80__default.useCallback(
@@ -21282,30 +21305,20 @@ function useDataDnd(args) {
21282
21305
  React80__default.useEffect(() => {
21283
21306
  dndLog.info("dropzone:isOver:change", { droppableId, group: ownGroup, isOver, isThisZoneOver, showForeignPlaceholder, activeDragSourceGroup: activeDrag2?.sourceGroup ?? null });
21284
21307
  }, [droppableId, isOver, isThisZoneOver, showForeignPlaceholder]);
21285
- return /* @__PURE__ */ jsxs(
21308
+ return /* @__PURE__ */ jsx(
21286
21309
  Box,
21287
21310
  {
21288
21311
  ref: setNodeRef,
21289
21312
  "data-dnd-zone": ownGroup,
21290
21313
  "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
- ]
21314
+ className: isThisZoneOver ? "ring-2 ring-primary/40 ring-offset-2 rounded-lg transition-all min-h-[3rem]" : "min-h-[3rem] rounded-lg transition-all",
21315
+ children
21303
21316
  }
21304
21317
  );
21305
21318
  };
21306
21319
  const rootContextValue = React80__default.useMemo(
21307
- () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup }),
21308
- [registerZone, unregisterZone, activeDrag, overZoneGroup]
21320
+ () => ({ registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder }),
21321
+ [registerZone, unregisterZone, activeDrag, overZoneGroup, optimisticOrders, clearOptimisticOrder]
21309
21322
  );
21310
21323
  const handleDragStart = React80__default.useCallback((event) => {
21311
21324
  const sourceZone = findZoneByItem(event.active.id);
@@ -21327,13 +21340,73 @@ function useDataDnd(args) {
21327
21340
  });
21328
21341
  }, [findZoneByItem, isRoot, zoneId]);
21329
21342
  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
21343
+ const { active, over } = event;
21344
+ const overData = over?.data?.current;
21345
+ const overGroup = overData?.dndGroup ?? null;
21346
+ setOverZoneGroup(overGroup);
21347
+ if (!over || !overGroup) return;
21348
+ const activeIdStr = String(active.id);
21349
+ let sourceMeta;
21350
+ let sourceGroup;
21351
+ for (const m of zonesRef.current.values()) {
21352
+ const currentItems = optimisticOrdersRef.current.get(m.group) ?? m.rawItems;
21353
+ const found = currentItems.find((it) => String(it[m.idField]) === activeIdStr);
21354
+ if (found) {
21355
+ sourceMeta = m;
21356
+ sourceGroup = m.group;
21357
+ break;
21358
+ }
21359
+ }
21360
+ if (!sourceMeta || !sourceGroup) {
21361
+ dndLog.debug("dragOver:no-source-zone", { activeId: active.id });
21362
+ return;
21363
+ }
21364
+ let targetMeta;
21365
+ for (const m of zonesRef.current.values()) {
21366
+ if (m.group === overGroup) {
21367
+ targetMeta = m;
21368
+ break;
21369
+ }
21370
+ }
21371
+ if (!targetMeta) {
21372
+ dndLog.debug("dragOver:no-target-zone", { overGroup });
21373
+ return;
21374
+ }
21375
+ if (sourceGroup === overGroup) {
21376
+ setOptimisticOrders((prev) => {
21377
+ const currentItems = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21378
+ const oldIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === activeIdStr);
21379
+ const newIndex = currentItems.findIndex((it) => String(it[sourceMeta.idField]) === String(over.id));
21380
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) return prev;
21381
+ const reordered = arrayMove([...currentItems], oldIndex, newIndex);
21382
+ const next = new Map(prev);
21383
+ next.set(sourceGroup, reordered);
21384
+ return next;
21385
+ });
21386
+ return;
21387
+ }
21388
+ setOptimisticOrders((prev) => {
21389
+ const currentSource = prev.get(sourceGroup) ?? sourceMeta.rawItems;
21390
+ const currentTarget = prev.get(overGroup) ?? targetMeta.rawItems;
21391
+ const activeItem = currentSource.find((it) => String(it[sourceMeta.idField]) === activeIdStr);
21392
+ if (!activeItem) return prev;
21393
+ if (currentTarget.some((it) => String(it[targetMeta.idField]) === activeIdStr)) {
21394
+ return prev;
21395
+ }
21396
+ const newSource = currentSource.filter((it) => String(it[sourceMeta.idField]) !== activeIdStr);
21397
+ const overIdStr = String(over.id);
21398
+ const overIndex = currentTarget.findIndex((it) => String(it[targetMeta.idField]) === overIdStr);
21399
+ const insertAt = overIndex >= 0 ? overIndex : currentTarget.length;
21400
+ const newTarget = [
21401
+ ...currentTarget.slice(0, insertAt),
21402
+ activeItem,
21403
+ ...currentTarget.slice(insertAt)
21404
+ ];
21405
+ const next = new Map(prev);
21406
+ next.set(sourceGroup, newSource);
21407
+ next.set(overGroup, newTarget);
21408
+ dndLog.debug("dragOver:cross-zone:splice", { sourceGroup, overGroup, sourceLen: newSource.length, targetLen: newTarget.length, insertAt });
21409
+ return next;
21337
21410
  });
21338
21411
  }, []);
21339
21412
  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.16",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [