@dnd-block-tree/vanilla 2.2.1 → 2.4.0

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.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { BaseBlock, BlockRendererProps as BlockRendererProps$1, BlockRenderers as BlockRenderers$1, OrderingStrategy, CanDragFn, CanDropFn, IdGeneratorFn, SensorConfig, BlockTreeCallbacks, ContainerRendererProps as ContainerRendererProps$1, InternalRenderers as InternalRenderers$1, RendererPropsFor as RendererPropsFor$1, BlockTreeInstance, Rect, CollisionCandidate, CoreCollisionDetection } from '@dnd-block-tree/core';
2
- export { AnimationConfig, AutoExpandConfig, BaseBlock, BlockAction, BlockAddEvent, BlockDeleteEvent, BlockIndex, BlockMoveEvent, BlockPosition, BlockStateContextValue, BlockTreeCallbacks, BlockTreeConfig, BlockTreeCustomization, BlockTreeEvents, BlockTreeInstance, BlockTreeOptions, CanDragFn, CanDropFn, CollisionCandidate, CollisionResult, CoreCollisionDetection, DragEndEvent, DragMoveEvent, DragStartEvent, DropZoneConfig, DropZoneType, EventEmitter, ExpandAction, ExpandChangeEvent, HistoryAction, HistoryState, HoverChangeEvent, IdGeneratorFn, MoveOperation, NestedBlock, OrderingStrategy, Rect, SensorConfig, SnapshotRectsRef, TreeValidationResult, blockReducer, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockTree, createStickyCollision, debounce, deleteBlockAndDescendants, expandReducer, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSubtreeDepth, historyReducer, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, validateBlockTree, weightedVerticalCollision } from '@dnd-block-tree/core';
1
+ import { BaseBlock, BlockRendererProps as BlockRendererProps$1, BlockRenderers as BlockRenderers$1, OrderingStrategy, CanDragFn, CanDropFn, IdGeneratorFn, SensorConfig, BlockTreeCallbacks, ContainerRendererProps as ContainerRendererProps$1, InternalRenderers as InternalRenderers$1, RendererPropsFor as RendererPropsFor$1, BlockTreeInstance, Rect, CollisionCandidate, CoreCollisionDetection, MergeBlockVersionsOptions } from '@dnd-block-tree/core';
2
+ export { AnimationConfig, AutoExpandConfig, BaseBlock, BlockAction, BlockAddEvent, BlockDeleteEvent, BlockIndex, BlockMoveEvent, BlockPosition, BlockStateContextValue, BlockTreeCallbacks, BlockTreeConfig, BlockTreeCustomization, BlockTreeEvents, BlockTreeInstance, BlockTreeOptions, CanDragFn, CanDropFn, CollisionCandidate, CollisionResult, CoreCollisionDetection, DragEndEvent, DragMoveEvent, DragStartEvent, DropZoneConfig, DropZoneType, EventEmitter, ExpandAction, ExpandChangeEvent, HistoryAction, HistoryState, HoverChangeEvent, IdGeneratorFn, MergeBlockVersionsOptions, MoveOperation, NestedBlock, OrderingStrategy, Rect, SensorConfig, SnapshotRectsRef, TreeValidationResult, blockReducer, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockTree, createStickyCollision, debounce, deleteBlockAndDescendants, expandReducer, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSubtreeDepth, historyReducer, initFractionalOrder, mergeBlockVersions, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, validateBlockTree, weightedVerticalCollision } from '@dnd-block-tree/core';
3
3
 
4
4
  type BlockRendererProps<T extends BaseBlock = BaseBlock> = BlockRendererProps$1<T, HTMLElement>;
5
5
  type ContainerRendererProps<T extends BaseBlock = BaseBlock> = ContainerRendererProps$1<T, HTMLElement>;
@@ -54,8 +54,13 @@ interface RenderBlockContext {
54
54
  /** DefaultRenderer options */
55
55
  interface DefaultRendererOptions<T extends BaseBlock = BaseBlock> {
56
56
  container: HTMLElement;
57
+ containerTypes?: readonly string[];
57
58
  renderBlock: (block: T, ctx: RenderBlockContext) => HTMLElement;
58
59
  dropZoneHeight?: number;
60
+ dropZoneClassName?: string;
61
+ dropZoneActiveClassName?: string;
62
+ rootClassName?: string;
63
+ indentClassName?: string;
59
64
  animateExpand?: boolean;
60
65
  }
61
66
  /** Cleanup function */
@@ -142,6 +147,54 @@ interface BlockHistory<T extends BaseBlock> {
142
147
  */
143
148
  declare function createBlockHistory<T extends BaseBlock>(initialBlocks: T[], options?: BlockHistoryOptions): BlockHistory<T>;
144
149
 
150
+ interface DeferredSyncOptions<T extends BaseBlock> {
151
+ /** Called when remote data is applied (only when not busy) */
152
+ onResolve?: (blocks: T[]) => void;
153
+ /** Options passed to mergeBlockVersions when using 'merge' strategy */
154
+ mergeOptions?: MergeBlockVersionsOptions;
155
+ }
156
+ interface DeferredSync<T extends BaseBlock> {
157
+ /** Whether sync is currently deferred */
158
+ isBusy(): boolean;
159
+ /** Apply remote blocks — queues if busy, calls onResolve if idle */
160
+ apply(remoteBlocks: T[]): void;
161
+ /** Enter busy state (call before editing or dragging) */
162
+ enterBusy(): void;
163
+ /**
164
+ * Exit busy state and resolve any queued remote changes.
165
+ * Returns the merged result if a queue existed, null otherwise.
166
+ */
167
+ exitBusy(localBlocks: T[], strategy: 'merge' | 'lww'): T[] | null;
168
+ }
169
+ /**
170
+ * Imperative deferred sync factory for vanilla JS.
171
+ * Queues remote updates during blocking actions and resolves
172
+ * on exit using merge or last-write-wins strategy.
173
+ *
174
+ * @param options - Configuration including onResolve callback and merge options
175
+ * @returns DeferredSync instance with busy/queue/flush methods
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * const sync = createDeferredSync<MyBlock>({
180
+ * onResolve: (blocks) => controller.setBlocks(blocks),
181
+ * })
182
+ *
183
+ * // In your realtime subscription:
184
+ * socket.on('blocks', (blocks) => sync.apply(blocks))
185
+ *
186
+ * // Drag lifecycle:
187
+ * controller.on('drag:statechange', ({ isDragging }) => {
188
+ * if (isDragging) sync.enterBusy()
189
+ * else {
190
+ * const result = sync.exitBusy(controller.getBlocks(), 'lww')
191
+ * if (result) socket.emit('blocks', result)
192
+ * }
193
+ * })
194
+ * ```
195
+ */
196
+ declare function createDeferredSync<T extends BaseBlock>(options?: DeferredSyncOptions<T>): DeferredSync<T>;
197
+
145
198
  interface LayoutAnimationOptions {
146
199
  duration?: number;
147
200
  easing?: string;
@@ -276,6 +329,9 @@ interface TreeRendererOptions<T extends BaseBlock> {
276
329
  renderBlock: (block: T, ctx: RenderBlockContext) => HTMLElement;
277
330
  containerTypes: readonly string[];
278
331
  dropZoneHeight?: number;
332
+ dropZoneClassName?: string;
333
+ rootClassName?: string;
334
+ indentClassName?: string;
279
335
  }
280
336
  /**
281
337
  * Recursive DOM tree builder. Creates the full tree DOM from blocks.
@@ -286,6 +342,7 @@ declare function renderTree<T extends BaseBlock>(blocks: T[], expandedMap: Recor
286
342
  interface DropZoneOptions {
287
343
  id: string;
288
344
  height?: number;
345
+ className?: string;
289
346
  }
290
347
  declare function createDropZoneElement(options: DropZoneOptions): HTMLElement;
291
348
  declare function setDropZoneActive(el: HTMLElement, active: boolean): void;
@@ -311,4 +368,4 @@ declare function createDisposable(): Disposable & {
311
368
  add(fn: () => void): void;
312
369
  };
313
370
 
314
- export { type BlockHistory, type BlockHistoryOptions, type BlockRendererProps, type BlockRenderers, type BlockTreeController, type BlockTreeControllerOptions, type ContainerRendererProps, type ControllerEvents, type DefaultRenderer, type DefaultRendererOptions, type Disposable, DragOverlay, type DragOverlayOptions, type DragState, type InternalRenderers, KeyboardSensor, type KeyboardSensorCallbacks, LayoutAnimation, type LayoutAnimationOptions, PointerSensor, type PointerSensorOptions, type RenderBlockContext, type RendererPropsFor, type Sensor, type SensorCallbacks, TouchSensor, type TouchSensorOptions, type TreeRendererOptions, type Unsubscribe, type VanillaSensorConfig, type VirtualRange, VirtualScroller, type VirtualScrollerOptions, buildCandidates, closestWithData, createBlockHistory, createBlockTreeController, createDefaultRenderer, createDisposable, createDropZoneElement, createElement, createGhostPreview, detectCollision, measureDropZoneRects, pointerToRect, renderTree, setDataAttributes, setDropZoneActive, triggerHaptic };
371
+ export { type BlockHistory, type BlockHistoryOptions, type BlockRendererProps, type BlockRenderers, type BlockTreeController, type BlockTreeControllerOptions, type ContainerRendererProps, type ControllerEvents, type DefaultRenderer, type DefaultRendererOptions, type DeferredSync, type DeferredSyncOptions, type Disposable, DragOverlay, type DragOverlayOptions, type DragState, type InternalRenderers, KeyboardSensor, type KeyboardSensorCallbacks, LayoutAnimation, type LayoutAnimationOptions, PointerSensor, type PointerSensorOptions, type RenderBlockContext, type RendererPropsFor, type Sensor, type SensorCallbacks, TouchSensor, type TouchSensorOptions, type TreeRendererOptions, type Unsubscribe, type VanillaSensorConfig, type VirtualRange, VirtualScroller, type VirtualScrollerOptions, buildCandidates, closestWithData, createBlockHistory, createBlockTreeController, createDefaultRenderer, createDeferredSync, createDisposable, createDropZoneElement, createElement, createGhostPreview, detectCollision, measureDropZoneRects, pointerToRect, renderTree, setDataAttributes, setDropZoneActive, triggerHaptic };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { BaseBlock, BlockRendererProps as BlockRendererProps$1, BlockRenderers as BlockRenderers$1, OrderingStrategy, CanDragFn, CanDropFn, IdGeneratorFn, SensorConfig, BlockTreeCallbacks, ContainerRendererProps as ContainerRendererProps$1, InternalRenderers as InternalRenderers$1, RendererPropsFor as RendererPropsFor$1, BlockTreeInstance, Rect, CollisionCandidate, CoreCollisionDetection } from '@dnd-block-tree/core';
2
- export { AnimationConfig, AutoExpandConfig, BaseBlock, BlockAction, BlockAddEvent, BlockDeleteEvent, BlockIndex, BlockMoveEvent, BlockPosition, BlockStateContextValue, BlockTreeCallbacks, BlockTreeConfig, BlockTreeCustomization, BlockTreeEvents, BlockTreeInstance, BlockTreeOptions, CanDragFn, CanDropFn, CollisionCandidate, CollisionResult, CoreCollisionDetection, DragEndEvent, DragMoveEvent, DragStartEvent, DropZoneConfig, DropZoneType, EventEmitter, ExpandAction, ExpandChangeEvent, HistoryAction, HistoryState, HoverChangeEvent, IdGeneratorFn, MoveOperation, NestedBlock, OrderingStrategy, Rect, SensorConfig, SnapshotRectsRef, TreeValidationResult, blockReducer, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockTree, createStickyCollision, debounce, deleteBlockAndDescendants, expandReducer, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSubtreeDepth, historyReducer, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, validateBlockTree, weightedVerticalCollision } from '@dnd-block-tree/core';
1
+ import { BaseBlock, BlockRendererProps as BlockRendererProps$1, BlockRenderers as BlockRenderers$1, OrderingStrategy, CanDragFn, CanDropFn, IdGeneratorFn, SensorConfig, BlockTreeCallbacks, ContainerRendererProps as ContainerRendererProps$1, InternalRenderers as InternalRenderers$1, RendererPropsFor as RendererPropsFor$1, BlockTreeInstance, Rect, CollisionCandidate, CoreCollisionDetection, MergeBlockVersionsOptions } from '@dnd-block-tree/core';
2
+ export { AnimationConfig, AutoExpandConfig, BaseBlock, BlockAction, BlockAddEvent, BlockDeleteEvent, BlockIndex, BlockMoveEvent, BlockPosition, BlockStateContextValue, BlockTreeCallbacks, BlockTreeConfig, BlockTreeCustomization, BlockTreeEvents, BlockTreeInstance, BlockTreeOptions, CanDragFn, CanDropFn, CollisionCandidate, CollisionResult, CoreCollisionDetection, DragEndEvent, DragMoveEvent, DragStartEvent, DropZoneConfig, DropZoneType, EventEmitter, ExpandAction, ExpandChangeEvent, HistoryAction, HistoryState, HoverChangeEvent, IdGeneratorFn, MergeBlockVersionsOptions, MoveOperation, NestedBlock, OrderingStrategy, Rect, SensorConfig, SnapshotRectsRef, TreeValidationResult, blockReducer, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockTree, createStickyCollision, debounce, deleteBlockAndDescendants, expandReducer, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSubtreeDepth, historyReducer, initFractionalOrder, mergeBlockVersions, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, validateBlockTree, weightedVerticalCollision } from '@dnd-block-tree/core';
3
3
 
4
4
  type BlockRendererProps<T extends BaseBlock = BaseBlock> = BlockRendererProps$1<T, HTMLElement>;
5
5
  type ContainerRendererProps<T extends BaseBlock = BaseBlock> = ContainerRendererProps$1<T, HTMLElement>;
@@ -54,8 +54,13 @@ interface RenderBlockContext {
54
54
  /** DefaultRenderer options */
55
55
  interface DefaultRendererOptions<T extends BaseBlock = BaseBlock> {
56
56
  container: HTMLElement;
57
+ containerTypes?: readonly string[];
57
58
  renderBlock: (block: T, ctx: RenderBlockContext) => HTMLElement;
58
59
  dropZoneHeight?: number;
60
+ dropZoneClassName?: string;
61
+ dropZoneActiveClassName?: string;
62
+ rootClassName?: string;
63
+ indentClassName?: string;
59
64
  animateExpand?: boolean;
60
65
  }
61
66
  /** Cleanup function */
@@ -142,6 +147,54 @@ interface BlockHistory<T extends BaseBlock> {
142
147
  */
143
148
  declare function createBlockHistory<T extends BaseBlock>(initialBlocks: T[], options?: BlockHistoryOptions): BlockHistory<T>;
144
149
 
150
+ interface DeferredSyncOptions<T extends BaseBlock> {
151
+ /** Called when remote data is applied (only when not busy) */
152
+ onResolve?: (blocks: T[]) => void;
153
+ /** Options passed to mergeBlockVersions when using 'merge' strategy */
154
+ mergeOptions?: MergeBlockVersionsOptions;
155
+ }
156
+ interface DeferredSync<T extends BaseBlock> {
157
+ /** Whether sync is currently deferred */
158
+ isBusy(): boolean;
159
+ /** Apply remote blocks — queues if busy, calls onResolve if idle */
160
+ apply(remoteBlocks: T[]): void;
161
+ /** Enter busy state (call before editing or dragging) */
162
+ enterBusy(): void;
163
+ /**
164
+ * Exit busy state and resolve any queued remote changes.
165
+ * Returns the merged result if a queue existed, null otherwise.
166
+ */
167
+ exitBusy(localBlocks: T[], strategy: 'merge' | 'lww'): T[] | null;
168
+ }
169
+ /**
170
+ * Imperative deferred sync factory for vanilla JS.
171
+ * Queues remote updates during blocking actions and resolves
172
+ * on exit using merge or last-write-wins strategy.
173
+ *
174
+ * @param options - Configuration including onResolve callback and merge options
175
+ * @returns DeferredSync instance with busy/queue/flush methods
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * const sync = createDeferredSync<MyBlock>({
180
+ * onResolve: (blocks) => controller.setBlocks(blocks),
181
+ * })
182
+ *
183
+ * // In your realtime subscription:
184
+ * socket.on('blocks', (blocks) => sync.apply(blocks))
185
+ *
186
+ * // Drag lifecycle:
187
+ * controller.on('drag:statechange', ({ isDragging }) => {
188
+ * if (isDragging) sync.enterBusy()
189
+ * else {
190
+ * const result = sync.exitBusy(controller.getBlocks(), 'lww')
191
+ * if (result) socket.emit('blocks', result)
192
+ * }
193
+ * })
194
+ * ```
195
+ */
196
+ declare function createDeferredSync<T extends BaseBlock>(options?: DeferredSyncOptions<T>): DeferredSync<T>;
197
+
145
198
  interface LayoutAnimationOptions {
146
199
  duration?: number;
147
200
  easing?: string;
@@ -276,6 +329,9 @@ interface TreeRendererOptions<T extends BaseBlock> {
276
329
  renderBlock: (block: T, ctx: RenderBlockContext) => HTMLElement;
277
330
  containerTypes: readonly string[];
278
331
  dropZoneHeight?: number;
332
+ dropZoneClassName?: string;
333
+ rootClassName?: string;
334
+ indentClassName?: string;
279
335
  }
280
336
  /**
281
337
  * Recursive DOM tree builder. Creates the full tree DOM from blocks.
@@ -286,6 +342,7 @@ declare function renderTree<T extends BaseBlock>(blocks: T[], expandedMap: Recor
286
342
  interface DropZoneOptions {
287
343
  id: string;
288
344
  height?: number;
345
+ className?: string;
289
346
  }
290
347
  declare function createDropZoneElement(options: DropZoneOptions): HTMLElement;
291
348
  declare function setDropZoneActive(el: HTMLElement, active: boolean): void;
@@ -311,4 +368,4 @@ declare function createDisposable(): Disposable & {
311
368
  add(fn: () => void): void;
312
369
  };
313
370
 
314
- export { type BlockHistory, type BlockHistoryOptions, type BlockRendererProps, type BlockRenderers, type BlockTreeController, type BlockTreeControllerOptions, type ContainerRendererProps, type ControllerEvents, type DefaultRenderer, type DefaultRendererOptions, type Disposable, DragOverlay, type DragOverlayOptions, type DragState, type InternalRenderers, KeyboardSensor, type KeyboardSensorCallbacks, LayoutAnimation, type LayoutAnimationOptions, PointerSensor, type PointerSensorOptions, type RenderBlockContext, type RendererPropsFor, type Sensor, type SensorCallbacks, TouchSensor, type TouchSensorOptions, type TreeRendererOptions, type Unsubscribe, type VanillaSensorConfig, type VirtualRange, VirtualScroller, type VirtualScrollerOptions, buildCandidates, closestWithData, createBlockHistory, createBlockTreeController, createDefaultRenderer, createDisposable, createDropZoneElement, createElement, createGhostPreview, detectCollision, measureDropZoneRects, pointerToRect, renderTree, setDataAttributes, setDropZoneActive, triggerHaptic };
371
+ export { type BlockHistory, type BlockHistoryOptions, type BlockRendererProps, type BlockRenderers, type BlockTreeController, type BlockTreeControllerOptions, type ContainerRendererProps, type ControllerEvents, type DefaultRenderer, type DefaultRendererOptions, type DeferredSync, type DeferredSyncOptions, type Disposable, DragOverlay, type DragOverlayOptions, type DragState, type InternalRenderers, KeyboardSensor, type KeyboardSensorCallbacks, LayoutAnimation, type LayoutAnimationOptions, PointerSensor, type PointerSensorOptions, type RenderBlockContext, type RendererPropsFor, type Sensor, type SensorCallbacks, TouchSensor, type TouchSensorOptions, type TreeRendererOptions, type Unsubscribe, type VanillaSensorConfig, type VirtualRange, VirtualScroller, type VirtualScrollerOptions, buildCandidates, closestWithData, createBlockHistory, createBlockTreeController, createDefaultRenderer, createDeferredSync, createDisposable, createDropZoneElement, createElement, createGhostPreview, detectCollision, measureDropZoneRects, pointerToRect, renderTree, setDataAttributes, setDropZoneActive, triggerHaptic };
package/dist/index.js CHANGED
@@ -55,6 +55,8 @@ var DragOverlay = class {
55
55
  const overlay = document.createElement("div");
56
56
  overlay.setAttribute("data-drag-overlay", "true");
57
57
  overlay.style.position = "fixed";
58
+ overlay.style.left = "0";
59
+ overlay.style.top = "0";
58
60
  overlay.style.zIndex = "9999";
59
61
  overlay.style.pointerEvents = "none";
60
62
  overlay.style.opacity = "0.7";
@@ -380,11 +382,11 @@ function createBlockTreeController(options = {}) {
380
382
  const draggableElements = /* @__PURE__ */ new Map();
381
383
  const dropZoneElements = /* @__PURE__ */ new Map();
382
384
  let snapshotRects = null;
385
+ let dragFromPosition = null;
383
386
  const selectedIds = /* @__PURE__ */ new Set();
384
387
  let lastSelectedId = null;
385
388
  const activeSensors = [];
386
- const overlay = new DragOverlay();
387
- let overlayRenderer = null;
389
+ let overlay = new DragOverlay();
388
390
  let history = null;
389
391
  tree.on("blocks:change", (blocks) => {
390
392
  onChange?.(blocks);
@@ -417,18 +419,26 @@ function createBlockTreeController(options = {}) {
417
419
  const sensorCallbacks = {
418
420
  onDragStart(blockId, x, y) {
419
421
  const draggedIds = selectedIds.has(blockId) && selectedIds.size > 1 ? [...selectedIds] : [blockId];
422
+ const blocks = tree.getBlocks();
423
+ const dragBlock = blocks.find((b) => b.id === blockId);
424
+ if (dragBlock) {
425
+ const siblings = blocks.filter((b) => b.parentId === dragBlock.parentId);
426
+ dragFromPosition = {
427
+ parentId: dragBlock.parentId,
428
+ index: siblings.findIndex((b) => b.id === blockId)
429
+ };
430
+ }
420
431
  const started = tree.startDrag(blockId, draggedIds);
421
- if (!started) return;
432
+ if (!started) {
433
+ dragFromPosition = null;
434
+ return;
435
+ }
422
436
  stickyCollision.reset();
423
437
  snapshotRects = measureDropZoneRects(dropZoneElements);
424
438
  const block = tree.getBlock(blockId);
425
439
  const el = draggableElements.get(blockId);
426
440
  if (block && el) {
427
- if (overlayRenderer) {
428
- overlay.show(block, el, x, y);
429
- } else {
430
- overlay.show(block, el, x, y);
431
- }
441
+ overlay.show(block, el, x, y);
432
442
  }
433
443
  emitter.emit("drag:statechange", getDragState());
434
444
  },
@@ -439,22 +449,35 @@ function createBlockTreeController(options = {}) {
439
449
  if (!detector) return;
440
450
  const targetZone = detectCollision(detector, snapshotRects, x, y);
441
451
  if (targetZone) {
452
+ const prevHover = tree.getHoverZone();
442
453
  tree.updateDrag(targetZone);
454
+ if (tree.getHoverZone() !== prevHover) {
455
+ emitter.emit("drag:statechange", getDragState());
456
+ }
443
457
  }
444
458
  },
445
459
  onDragEnd(_x, _y) {
460
+ const draggedBlockIds = selectedIds.size > 1 ? [...selectedIds] : [];
461
+ const activeDragId = tree.getActiveId();
446
462
  const result = tree.endDrag();
447
463
  overlay.hide();
448
464
  snapshotRects = null;
449
- if (result && callbacks?.onBlockMove) {
450
- callbacks.onBlockMove({
451
- block: tree.getBlock(tree.getBlocks()[0]?.id),
452
- from: { parentId: null, index: 0 },
453
- to: { parentId: null, index: 0 },
454
- blocks: result.blocks,
455
- movedIds: []
456
- });
465
+ if (result && callbacks?.onBlockMove && activeDragId) {
466
+ const movedBlock = result.blocks.find((b) => b.id === activeDragId);
467
+ const movedIds = draggedBlockIds.length > 0 ? draggedBlockIds : activeDragId ? [activeDragId] : [];
468
+ if (movedBlock) {
469
+ const siblings = result.blocks.filter((b) => b.parentId === movedBlock.parentId);
470
+ const toIndex = siblings.findIndex((b) => b.id === activeDragId);
471
+ callbacks.onBlockMove({
472
+ block: movedBlock,
473
+ from: dragFromPosition ?? { parentId: null, index: 0 },
474
+ to: { parentId: movedBlock.parentId, index: toIndex },
475
+ blocks: result.blocks,
476
+ movedIds
477
+ });
478
+ }
457
479
  }
480
+ dragFromPosition = null;
458
481
  emitter.emit("drag:statechange", getDragState());
459
482
  emitter.emit("render", tree.getBlocks(), tree.getExpandedMap());
460
483
  },
@@ -658,7 +681,7 @@ function createBlockTreeController(options = {}) {
658
681
  return emitter.on(event, handler);
659
682
  },
660
683
  setOverlayRenderer(render) {
661
- overlayRenderer = render;
684
+ overlay = new DragOverlay({ renderOverlay: render });
662
685
  },
663
686
  getTree: () => tree,
664
687
  destroy() {
@@ -673,6 +696,33 @@ function createBlockTreeController(options = {}) {
673
696
  };
674
697
  return controller;
675
698
  }
699
+ function createDeferredSync(options) {
700
+ let busy = false;
701
+ let queue = null;
702
+ return {
703
+ isBusy: () => busy,
704
+ apply(remoteBlocks) {
705
+ if (busy) {
706
+ queue = remoteBlocks;
707
+ } else {
708
+ options?.onResolve?.(remoteBlocks);
709
+ }
710
+ },
711
+ enterBusy() {
712
+ busy = true;
713
+ },
714
+ exitBusy(localBlocks, strategy) {
715
+ busy = false;
716
+ const queued = queue;
717
+ queue = null;
718
+ if (!queued) return null;
719
+ if (strategy === "lww") {
720
+ return localBlocks;
721
+ }
722
+ return core.mergeBlockVersions(localBlocks, queued, options?.mergeOptions);
723
+ }
724
+ };
725
+ }
676
726
 
677
727
  // src/layout-animation.ts
678
728
  var LayoutAnimation = class {
@@ -748,14 +798,18 @@ var VirtualScroller = class {
748
798
 
749
799
  // src/renderer/drop-zone.ts
750
800
  function createDropZoneElement(options) {
751
- const { id, height = 4 } = options;
801
+ const { id, height = 4, className } = options;
752
802
  const el = createElement("div");
753
803
  setDataAttributes(el, {
754
804
  "zone-id": id
755
805
  });
756
- el.style.height = `${height}px`;
757
- el.style.minHeight = `${height}px`;
758
- el.style.transition = "background-color 150ms ease";
806
+ if (className) {
807
+ el.className = className;
808
+ } else {
809
+ el.style.height = `${height}px`;
810
+ el.style.minHeight = `${height}px`;
811
+ el.style.transition = "background-color 150ms ease";
812
+ }
759
813
  return el;
760
814
  }
761
815
  function setDropZoneActive(el, active) {
@@ -764,29 +818,32 @@ function setDropZoneActive(el, active) {
764
818
 
765
819
  // src/renderer/tree-renderer.ts
766
820
  function renderTree(blocks, expandedMap, controller, options, parentId = null, depth = 0) {
767
- const { renderBlock, containerTypes, dropZoneHeight } = options;
821
+ const { renderBlock, containerTypes, dropZoneHeight, dropZoneClassName, rootClassName, indentClassName } = options;
768
822
  const container = createElement("div", {
769
823
  role: parentId === null ? "tree" : "group"
770
824
  });
771
825
  if (parentId === null) {
772
826
  container.setAttribute("data-dnd-tree-root", "true");
827
+ if (rootClassName) container.className = rootClassName;
828
+ } else {
829
+ if (indentClassName) container.className = indentClassName;
773
830
  }
774
831
  const children = blocks.filter((b) => b.parentId === parentId);
775
832
  const activeId = controller.getDragState().activeId;
776
833
  const selectedIds = controller.getSelectedIds();
777
834
  const index = controller.getTree().getBlockIndex();
778
835
  if (parentId !== null) {
779
- const startZone = createDropZoneElement({ id: `into-${parentId}`, height: dropZoneHeight });
836
+ const startZone = createDropZoneElement({ id: `into-${parentId}`, height: dropZoneHeight, className: dropZoneClassName });
780
837
  controller.registerDropZone(`into-${parentId}`, startZone);
781
838
  container.appendChild(startZone);
782
839
  } else {
783
- const rootStart = createDropZoneElement({ id: "root-start", height: dropZoneHeight });
840
+ const rootStart = createDropZoneElement({ id: "root-start", height: dropZoneHeight, className: dropZoneClassName });
784
841
  controller.registerDropZone("root-start", rootStart);
785
842
  container.appendChild(rootStart);
786
843
  }
787
844
  for (const block of children) {
788
845
  if (block.id === activeId) continue;
789
- const beforeZone = createDropZoneElement({ id: `before-${block.id}`, height: dropZoneHeight });
846
+ const beforeZone = createDropZoneElement({ id: `before-${block.id}`, height: dropZoneHeight, className: dropZoneClassName });
790
847
  controller.registerDropZone(`before-${block.id}`, beforeZone);
791
848
  container.appendChild(beforeZone);
792
849
  const isContainer = containerTypes.includes(block.type);
@@ -822,11 +879,11 @@ function renderTree(blocks, expandedMap, controller, options, parentId = null, d
822
879
  container.appendChild(blockEl);
823
880
  }
824
881
  if (parentId !== null) {
825
- const endZone = createDropZoneElement({ id: `end-${parentId}`, height: dropZoneHeight });
882
+ const endZone = createDropZoneElement({ id: `end-${parentId}`, height: dropZoneHeight, className: dropZoneClassName });
826
883
  controller.registerDropZone(`end-${parentId}`, endZone);
827
884
  container.appendChild(endZone);
828
885
  } else {
829
- const rootEnd = createDropZoneElement({ id: "root-end", height: dropZoneHeight });
886
+ const rootEnd = createDropZoneElement({ id: "root-end", height: dropZoneHeight, className: dropZoneClassName });
830
887
  controller.registerDropZone("root-end", rootEnd);
831
888
  container.appendChild(rootEnd);
832
889
  }
@@ -835,21 +892,47 @@ function renderTree(blocks, expandedMap, controller, options, parentId = null, d
835
892
 
836
893
  // src/renderer/default-renderer.ts
837
894
  function createDefaultRenderer(controller, options) {
838
- const { container, renderBlock, dropZoneHeight, animateExpand } = options;
839
- const containerTypes = controller.getTree().containerTypes ?? [];
895
+ const { container, containerTypes: explicitContainerTypes, renderBlock, dropZoneHeight, dropZoneClassName, dropZoneActiveClassName } = options;
896
+ const containerTypes = explicitContainerTypes ?? controller.getTree().containerTypes ?? [];
897
+ const activeClasses = dropZoneActiveClassName?.split(/\s+/).filter(Boolean) ?? [];
840
898
  function render(blocks, expandedMap) {
841
- container.innerHTML = "";
899
+ while (container.firstChild) container.removeChild(container.firstChild);
842
900
  const tree = renderTree(blocks, expandedMap, controller, {
843
901
  renderBlock,
844
902
  containerTypes,
845
- dropZoneHeight
903
+ dropZoneHeight,
904
+ dropZoneClassName,
905
+ rootClassName: options.rootClassName,
906
+ indentClassName: options.indentClassName
846
907
  });
847
908
  container.appendChild(tree);
848
909
  }
910
+ let activeZoneEl = null;
911
+ function onDragStateChange(state) {
912
+ if (activeZoneEl) {
913
+ setDropZoneActive(activeZoneEl, false);
914
+ if (activeClasses.length) {
915
+ activeZoneEl.classList.remove(...activeClasses);
916
+ }
917
+ activeZoneEl = null;
918
+ }
919
+ if (state.isDragging && state.hoverZone) {
920
+ const zoneEl = container.querySelector(`[data-zone-id="${state.hoverZone}"]`);
921
+ if (zoneEl) {
922
+ setDropZoneActive(zoneEl, true);
923
+ if (activeClasses.length) {
924
+ zoneEl.classList.add(...activeClasses);
925
+ }
926
+ activeZoneEl = zoneEl;
927
+ }
928
+ }
929
+ }
849
930
  const unsubRender = controller.on("render", render);
931
+ const unsubDrag = controller.on("drag:statechange", onDragStateChange);
850
932
  render(controller.getBlocks(), controller.getExpandedMap());
851
933
  const renderer = () => {
852
934
  unsubRender();
935
+ unsubDrag();
853
936
  };
854
937
  renderer.refresh = () => {
855
938
  render(controller.getBlocks(), controller.getExpandedMap());
@@ -988,6 +1071,10 @@ Object.defineProperty(exports, "initFractionalOrder", {
988
1071
  enumerable: true,
989
1072
  get: function () { return core.initFractionalOrder; }
990
1073
  });
1074
+ Object.defineProperty(exports, "mergeBlockVersions", {
1075
+ enumerable: true,
1076
+ get: function () { return core.mergeBlockVersions; }
1077
+ });
991
1078
  Object.defineProperty(exports, "nestedToFlat", {
992
1079
  enumerable: true,
993
1080
  get: function () { return core.nestedToFlat; }
@@ -1019,6 +1106,7 @@ exports.closestWithData = closestWithData;
1019
1106
  exports.createBlockHistory = createBlockHistory;
1020
1107
  exports.createBlockTreeController = createBlockTreeController;
1021
1108
  exports.createDefaultRenderer = createDefaultRenderer;
1109
+ exports.createDeferredSync = createDeferredSync;
1022
1110
  exports.createDisposable = createDisposable;
1023
1111
  exports.createDropZoneElement = createDropZoneElement;
1024
1112
  exports.createElement = createElement;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { historyReducer, createStickyCollision, createBlockTree, EventEmitter, getBlockDepth } from '@dnd-block-tree/core';
2
- export { EventEmitter, blockReducer, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockTree, createStickyCollision, debounce, deleteBlockAndDescendants, expandReducer, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSubtreeDepth, historyReducer, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, validateBlockTree, weightedVerticalCollision } from '@dnd-block-tree/core';
1
+ import { historyReducer, createStickyCollision, createBlockTree, EventEmitter, mergeBlockVersions, getBlockDepth } from '@dnd-block-tree/core';
2
+ export { EventEmitter, blockReducer, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockTree, createStickyCollision, debounce, deleteBlockAndDescendants, expandReducer, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSubtreeDepth, historyReducer, initFractionalOrder, mergeBlockVersions, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, validateBlockTree, weightedVerticalCollision } from '@dnd-block-tree/core';
3
3
 
4
4
  // src/index.ts
5
5
 
@@ -54,6 +54,8 @@ var DragOverlay = class {
54
54
  const overlay = document.createElement("div");
55
55
  overlay.setAttribute("data-drag-overlay", "true");
56
56
  overlay.style.position = "fixed";
57
+ overlay.style.left = "0";
58
+ overlay.style.top = "0";
57
59
  overlay.style.zIndex = "9999";
58
60
  overlay.style.pointerEvents = "none";
59
61
  overlay.style.opacity = "0.7";
@@ -379,11 +381,11 @@ function createBlockTreeController(options = {}) {
379
381
  const draggableElements = /* @__PURE__ */ new Map();
380
382
  const dropZoneElements = /* @__PURE__ */ new Map();
381
383
  let snapshotRects = null;
384
+ let dragFromPosition = null;
382
385
  const selectedIds = /* @__PURE__ */ new Set();
383
386
  let lastSelectedId = null;
384
387
  const activeSensors = [];
385
- const overlay = new DragOverlay();
386
- let overlayRenderer = null;
388
+ let overlay = new DragOverlay();
387
389
  let history = null;
388
390
  tree.on("blocks:change", (blocks) => {
389
391
  onChange?.(blocks);
@@ -416,18 +418,26 @@ function createBlockTreeController(options = {}) {
416
418
  const sensorCallbacks = {
417
419
  onDragStart(blockId, x, y) {
418
420
  const draggedIds = selectedIds.has(blockId) && selectedIds.size > 1 ? [...selectedIds] : [blockId];
421
+ const blocks = tree.getBlocks();
422
+ const dragBlock = blocks.find((b) => b.id === blockId);
423
+ if (dragBlock) {
424
+ const siblings = blocks.filter((b) => b.parentId === dragBlock.parentId);
425
+ dragFromPosition = {
426
+ parentId: dragBlock.parentId,
427
+ index: siblings.findIndex((b) => b.id === blockId)
428
+ };
429
+ }
419
430
  const started = tree.startDrag(blockId, draggedIds);
420
- if (!started) return;
431
+ if (!started) {
432
+ dragFromPosition = null;
433
+ return;
434
+ }
421
435
  stickyCollision.reset();
422
436
  snapshotRects = measureDropZoneRects(dropZoneElements);
423
437
  const block = tree.getBlock(blockId);
424
438
  const el = draggableElements.get(blockId);
425
439
  if (block && el) {
426
- if (overlayRenderer) {
427
- overlay.show(block, el, x, y);
428
- } else {
429
- overlay.show(block, el, x, y);
430
- }
440
+ overlay.show(block, el, x, y);
431
441
  }
432
442
  emitter.emit("drag:statechange", getDragState());
433
443
  },
@@ -438,22 +448,35 @@ function createBlockTreeController(options = {}) {
438
448
  if (!detector) return;
439
449
  const targetZone = detectCollision(detector, snapshotRects, x, y);
440
450
  if (targetZone) {
451
+ const prevHover = tree.getHoverZone();
441
452
  tree.updateDrag(targetZone);
453
+ if (tree.getHoverZone() !== prevHover) {
454
+ emitter.emit("drag:statechange", getDragState());
455
+ }
442
456
  }
443
457
  },
444
458
  onDragEnd(_x, _y) {
459
+ const draggedBlockIds = selectedIds.size > 1 ? [...selectedIds] : [];
460
+ const activeDragId = tree.getActiveId();
445
461
  const result = tree.endDrag();
446
462
  overlay.hide();
447
463
  snapshotRects = null;
448
- if (result && callbacks?.onBlockMove) {
449
- callbacks.onBlockMove({
450
- block: tree.getBlock(tree.getBlocks()[0]?.id),
451
- from: { parentId: null, index: 0 },
452
- to: { parentId: null, index: 0 },
453
- blocks: result.blocks,
454
- movedIds: []
455
- });
464
+ if (result && callbacks?.onBlockMove && activeDragId) {
465
+ const movedBlock = result.blocks.find((b) => b.id === activeDragId);
466
+ const movedIds = draggedBlockIds.length > 0 ? draggedBlockIds : activeDragId ? [activeDragId] : [];
467
+ if (movedBlock) {
468
+ const siblings = result.blocks.filter((b) => b.parentId === movedBlock.parentId);
469
+ const toIndex = siblings.findIndex((b) => b.id === activeDragId);
470
+ callbacks.onBlockMove({
471
+ block: movedBlock,
472
+ from: dragFromPosition ?? { parentId: null, index: 0 },
473
+ to: { parentId: movedBlock.parentId, index: toIndex },
474
+ blocks: result.blocks,
475
+ movedIds
476
+ });
477
+ }
456
478
  }
479
+ dragFromPosition = null;
457
480
  emitter.emit("drag:statechange", getDragState());
458
481
  emitter.emit("render", tree.getBlocks(), tree.getExpandedMap());
459
482
  },
@@ -657,7 +680,7 @@ function createBlockTreeController(options = {}) {
657
680
  return emitter.on(event, handler);
658
681
  },
659
682
  setOverlayRenderer(render) {
660
- overlayRenderer = render;
683
+ overlay = new DragOverlay({ renderOverlay: render });
661
684
  },
662
685
  getTree: () => tree,
663
686
  destroy() {
@@ -672,6 +695,33 @@ function createBlockTreeController(options = {}) {
672
695
  };
673
696
  return controller;
674
697
  }
698
+ function createDeferredSync(options) {
699
+ let busy = false;
700
+ let queue = null;
701
+ return {
702
+ isBusy: () => busy,
703
+ apply(remoteBlocks) {
704
+ if (busy) {
705
+ queue = remoteBlocks;
706
+ } else {
707
+ options?.onResolve?.(remoteBlocks);
708
+ }
709
+ },
710
+ enterBusy() {
711
+ busy = true;
712
+ },
713
+ exitBusy(localBlocks, strategy) {
714
+ busy = false;
715
+ const queued = queue;
716
+ queue = null;
717
+ if (!queued) return null;
718
+ if (strategy === "lww") {
719
+ return localBlocks;
720
+ }
721
+ return mergeBlockVersions(localBlocks, queued, options?.mergeOptions);
722
+ }
723
+ };
724
+ }
675
725
 
676
726
  // src/layout-animation.ts
677
727
  var LayoutAnimation = class {
@@ -747,14 +797,18 @@ var VirtualScroller = class {
747
797
 
748
798
  // src/renderer/drop-zone.ts
749
799
  function createDropZoneElement(options) {
750
- const { id, height = 4 } = options;
800
+ const { id, height = 4, className } = options;
751
801
  const el = createElement("div");
752
802
  setDataAttributes(el, {
753
803
  "zone-id": id
754
804
  });
755
- el.style.height = `${height}px`;
756
- el.style.minHeight = `${height}px`;
757
- el.style.transition = "background-color 150ms ease";
805
+ if (className) {
806
+ el.className = className;
807
+ } else {
808
+ el.style.height = `${height}px`;
809
+ el.style.minHeight = `${height}px`;
810
+ el.style.transition = "background-color 150ms ease";
811
+ }
758
812
  return el;
759
813
  }
760
814
  function setDropZoneActive(el, active) {
@@ -763,29 +817,32 @@ function setDropZoneActive(el, active) {
763
817
 
764
818
  // src/renderer/tree-renderer.ts
765
819
  function renderTree(blocks, expandedMap, controller, options, parentId = null, depth = 0) {
766
- const { renderBlock, containerTypes, dropZoneHeight } = options;
820
+ const { renderBlock, containerTypes, dropZoneHeight, dropZoneClassName, rootClassName, indentClassName } = options;
767
821
  const container = createElement("div", {
768
822
  role: parentId === null ? "tree" : "group"
769
823
  });
770
824
  if (parentId === null) {
771
825
  container.setAttribute("data-dnd-tree-root", "true");
826
+ if (rootClassName) container.className = rootClassName;
827
+ } else {
828
+ if (indentClassName) container.className = indentClassName;
772
829
  }
773
830
  const children = blocks.filter((b) => b.parentId === parentId);
774
831
  const activeId = controller.getDragState().activeId;
775
832
  const selectedIds = controller.getSelectedIds();
776
833
  const index = controller.getTree().getBlockIndex();
777
834
  if (parentId !== null) {
778
- const startZone = createDropZoneElement({ id: `into-${parentId}`, height: dropZoneHeight });
835
+ const startZone = createDropZoneElement({ id: `into-${parentId}`, height: dropZoneHeight, className: dropZoneClassName });
779
836
  controller.registerDropZone(`into-${parentId}`, startZone);
780
837
  container.appendChild(startZone);
781
838
  } else {
782
- const rootStart = createDropZoneElement({ id: "root-start", height: dropZoneHeight });
839
+ const rootStart = createDropZoneElement({ id: "root-start", height: dropZoneHeight, className: dropZoneClassName });
783
840
  controller.registerDropZone("root-start", rootStart);
784
841
  container.appendChild(rootStart);
785
842
  }
786
843
  for (const block of children) {
787
844
  if (block.id === activeId) continue;
788
- const beforeZone = createDropZoneElement({ id: `before-${block.id}`, height: dropZoneHeight });
845
+ const beforeZone = createDropZoneElement({ id: `before-${block.id}`, height: dropZoneHeight, className: dropZoneClassName });
789
846
  controller.registerDropZone(`before-${block.id}`, beforeZone);
790
847
  container.appendChild(beforeZone);
791
848
  const isContainer = containerTypes.includes(block.type);
@@ -821,11 +878,11 @@ function renderTree(blocks, expandedMap, controller, options, parentId = null, d
821
878
  container.appendChild(blockEl);
822
879
  }
823
880
  if (parentId !== null) {
824
- const endZone = createDropZoneElement({ id: `end-${parentId}`, height: dropZoneHeight });
881
+ const endZone = createDropZoneElement({ id: `end-${parentId}`, height: dropZoneHeight, className: dropZoneClassName });
825
882
  controller.registerDropZone(`end-${parentId}`, endZone);
826
883
  container.appendChild(endZone);
827
884
  } else {
828
- const rootEnd = createDropZoneElement({ id: "root-end", height: dropZoneHeight });
885
+ const rootEnd = createDropZoneElement({ id: "root-end", height: dropZoneHeight, className: dropZoneClassName });
829
886
  controller.registerDropZone("root-end", rootEnd);
830
887
  container.appendChild(rootEnd);
831
888
  }
@@ -834,21 +891,47 @@ function renderTree(blocks, expandedMap, controller, options, parentId = null, d
834
891
 
835
892
  // src/renderer/default-renderer.ts
836
893
  function createDefaultRenderer(controller, options) {
837
- const { container, renderBlock, dropZoneHeight, animateExpand } = options;
838
- const containerTypes = controller.getTree().containerTypes ?? [];
894
+ const { container, containerTypes: explicitContainerTypes, renderBlock, dropZoneHeight, dropZoneClassName, dropZoneActiveClassName } = options;
895
+ const containerTypes = explicitContainerTypes ?? controller.getTree().containerTypes ?? [];
896
+ const activeClasses = dropZoneActiveClassName?.split(/\s+/).filter(Boolean) ?? [];
839
897
  function render(blocks, expandedMap) {
840
- container.innerHTML = "";
898
+ while (container.firstChild) container.removeChild(container.firstChild);
841
899
  const tree = renderTree(blocks, expandedMap, controller, {
842
900
  renderBlock,
843
901
  containerTypes,
844
- dropZoneHeight
902
+ dropZoneHeight,
903
+ dropZoneClassName,
904
+ rootClassName: options.rootClassName,
905
+ indentClassName: options.indentClassName
845
906
  });
846
907
  container.appendChild(tree);
847
908
  }
909
+ let activeZoneEl = null;
910
+ function onDragStateChange(state) {
911
+ if (activeZoneEl) {
912
+ setDropZoneActive(activeZoneEl, false);
913
+ if (activeClasses.length) {
914
+ activeZoneEl.classList.remove(...activeClasses);
915
+ }
916
+ activeZoneEl = null;
917
+ }
918
+ if (state.isDragging && state.hoverZone) {
919
+ const zoneEl = container.querySelector(`[data-zone-id="${state.hoverZone}"]`);
920
+ if (zoneEl) {
921
+ setDropZoneActive(zoneEl, true);
922
+ if (activeClasses.length) {
923
+ zoneEl.classList.add(...activeClasses);
924
+ }
925
+ activeZoneEl = zoneEl;
926
+ }
927
+ }
928
+ }
848
929
  const unsubRender = controller.on("render", render);
930
+ const unsubDrag = controller.on("drag:statechange", onDragStateChange);
849
931
  render(controller.getBlocks(), controller.getExpandedMap());
850
932
  const renderer = () => {
851
933
  unsubRender();
934
+ unsubDrag();
852
935
  };
853
936
  renderer.refresh = () => {
854
937
  render(controller.getBlocks(), controller.getExpandedMap());
@@ -883,4 +966,4 @@ function createDisposable() {
883
966
  };
884
967
  }
885
968
 
886
- export { DragOverlay, KeyboardSensor, LayoutAnimation, PointerSensor, TouchSensor, VirtualScroller, buildCandidates, closestWithData, createBlockHistory, createBlockTreeController, createDefaultRenderer, createDisposable, createDropZoneElement, createElement, createGhostPreview, detectCollision, measureDropZoneRects, pointerToRect, renderTree, setDataAttributes, setDropZoneActive, triggerHaptic };
969
+ export { DragOverlay, KeyboardSensor, LayoutAnimation, PointerSensor, TouchSensor, VirtualScroller, buildCandidates, closestWithData, createBlockHistory, createBlockTreeController, createDefaultRenderer, createDeferredSync, createDisposable, createDropZoneElement, createElement, createGhostPreview, detectCollision, measureDropZoneRects, pointerToRect, renderTree, setDataAttributes, setDropZoneActive, triggerHaptic };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dnd-block-tree/vanilla",
3
- "version": "2.2.1",
3
+ "version": "2.4.0",
4
4
  "description": "Vanilla JS/TS adapter for dnd-block-tree — zero framework dependencies",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -50,5 +50,9 @@
50
50
  "vanilla",
51
51
  "headless",
52
52
  "hierarchical"
53
- ]
53
+ ],
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "provenance": true
57
+ }
54
58
  }