dnd-block-tree 0.5.0 → 1.1.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/README.md CHANGED
@@ -1,20 +1,17 @@
1
1
  # dnd-block-tree
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/dnd-block-tree.svg)](https://www.npmjs.com/package/dnd-block-tree)
4
- [![npm downloads](https://img.shields.io/npm/dm/dnd-block-tree.svg)](https://www.npmjs.com/package/dnd-block-tree)
5
- [![bundle size](https://img.shields.io/bundlephobia/minzip/dnd-block-tree)](https://bundlephobia.com/package/dnd-block-tree)
6
4
  [![CI](https://github.com/thesandybridge/dnd-block-tree/actions/workflows/ci.yml/badge.svg)](https://github.com/thesandybridge/dnd-block-tree/actions/workflows/ci.yml)
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/)
9
- [![demo](https://img.shields.io/badge/demo-live-brightgreen.svg)](https://dnd-block-tree.vercel.app)
5
+ [![demo](https://img.shields.io/badge/demo-live-brightgreen.svg)](https://blocktree.sandybridge.io)
10
6
 
11
7
  A headless React library for building hierarchical drag-and-drop interfaces. Bring your own components, we handle the complexity.
12
8
 
13
9
  ## Features
14
10
 
15
11
  - **Stable Drop Zones** - Zones render based on original block positions, not preview state, ensuring consistent drop targets during drag
16
- - **Ghost Preview** - Semi-transparent preview shows where blocks will land without affecting zone positions
17
- - **Depth-Aware Collision** - Smart algorithm prefers nested zones when cursor is at indented levels, with hysteresis to prevent flickering
12
+ - **Ghost Preview** - In-flow semi-transparent preview shows where blocks will land with accurate layout
13
+ - **Snapshotted Collision** - Zone rects are frozen on drag start and re-measured after each ghost commit, preventing layout-shift feedback loops
14
+ - **Depth-Aware Collision** - Smart algorithm prefers nested zones when cursor is at indented levels, with cross-depth-aware hysteresis
18
15
  - **Mobile & Touch Support** - Separate touch/pointer activation constraints with configurable `longPressDelay` and optional `hapticFeedback`
19
16
  - **Snapshot-Based Computation** - State captured at drag start. All preview computations use snapshot, ensuring consistent behavior
20
17
  - **Debounced Preview** - 150ms debounced virtual state for smooth drag previews without jitter
@@ -516,7 +513,7 @@ Enable accessible tree navigation with the `keyboardNavigation` prop:
516
513
  | Home | Focus first block |
517
514
  | End | Focus last block |
518
515
 
519
- Blocks receive `data-block-id` and `tabIndex` attributes for focus management, and the tree root gets `role="tree"`.
516
+ Blocks receive `data-block-id` and `tabIndex` attributes for focus management, and the tree root gets `role="tree"`. Each block element includes WAI-ARIA TreeView attributes: `aria-level`, `aria-posinset`, `aria-setsize`, `aria-expanded` (containers only), and `aria-selected`.
520
517
 
521
518
  ## Multi-Select Drag
522
519
 
@@ -627,6 +624,31 @@ const collision = createStickyCollision(20)
627
624
 
628
625
  You can also pass any `CollisionDetection` function from `@dnd-kit/core`.
629
626
 
627
+ ### Snapshotted Zone Rects
628
+
629
+ `createStickyCollision` accepts an optional `SnapshotRectsRef` — a ref to a `Map<string, DOMRect>` of frozen zone positions. When provided, collision detection uses these snapshots instead of live DOM measurements, preventing feedback loops caused by the in-flow ghost preview shifting zone positions.
630
+
631
+ ```typescript
632
+ import { createStickyCollision, type SnapshotRectsRef } from 'dnd-block-tree'
633
+
634
+ const snapshotRef: SnapshotRectsRef = { current: null }
635
+ const collision = createStickyCollision(20, snapshotRef)
636
+
637
+ // Snapshot all zone rects after drag starts:
638
+ snapshotRef.current = new Map(
639
+ [...document.querySelectorAll('[data-zone-id]')].map(el => [
640
+ el.getAttribute('data-zone-id')!,
641
+ el.getBoundingClientRect(),
642
+ ])
643
+ )
644
+ ```
645
+
646
+ `BlockTree` handles this lifecycle automatically — zones are snapshotted on drag start and re-measured via `requestAnimationFrame` after each ghost position commit.
647
+
648
+ ### Cross-Depth Hysteresis
649
+
650
+ The sticky collision uses a reduced threshold (25% of normal) when switching between zones at different indentation levels. This makes it easy to drag blocks in and out of containers while still preventing flickering between same-depth adjacent zones.
651
+
630
652
  ## Type Safety
631
653
 
632
654
  The library provides automatic type inference for container vs non-container renderers:
@@ -663,6 +685,7 @@ import {
663
685
  deleteBlockAndDescendants, // Remove a block and all its descendants from index
664
686
  getBlockDepth, // Compute depth of a block (root = 1)
665
687
  getSubtreeDepth, // Max depth of a subtree (leaf = 1)
688
+ validateBlockTree, // Validate tree for cycles, orphans, stale refs
666
689
 
667
690
  // Serialization
668
691
  flatToNested, // Convert flat block array to nested tree
@@ -687,12 +710,22 @@ import {
687
710
  // Collision detection
688
711
  weightedVerticalCollision, // Edge-distance collision, depth-aware
689
712
  closestCenterCollision, // Simple closest-center collision
690
- createStickyCollision, // Hysteresis wrapper to prevent flickering
713
+ createStickyCollision, // Hysteresis wrapper with snapshot support
714
+ type SnapshotRectsRef, // Ref type for frozen zone rects
715
+
716
+ // Internal helpers
717
+ cloneMap, // Clone a Map
718
+ cloneParentMap, // Deep clone a parent->children Map
719
+ debounce, // Debounce with cancel()
691
720
 
692
721
  // Hooks
722
+ createBlockState, // Factory for block state context + provider
723
+ createTreeState, // Factory for tree UI state context + provider
693
724
  useBlockHistory, // Undo/redo state management
694
725
  useLayoutAnimation, // FLIP-based reorder animations
695
726
  useVirtualTree, // Virtual scrolling primitives
727
+ useConfiguredSensors, // Configure dnd-kit sensors
728
+ getSensorConfig, // Get sensor config from SensorConfig
696
729
 
697
730
  // Components
698
731
  BlockTree, // Main drag-and-drop tree component
@@ -705,7 +738,7 @@ import {
705
738
 
706
739
  ## Demo
707
740
 
708
- Check out the [live demo](https://dnd-block-tree.vercel.app) to see the library in action with two example use cases:
741
+ Check out the [live demo](https://blocktree.sandybridge.io) to see the library in action with two example use cases:
709
742
 
710
743
  - **Productivity** - Sections, tasks, and notes with undo/redo, max depth control, and keyboard navigation
711
744
  - **File System** - Folders and files
package/dist/index.d.mts CHANGED
@@ -389,6 +389,81 @@ interface TreeStateProviderProps<T extends BaseBlock = BaseBlock> {
389
389
  blockMap: Map<string, T>;
390
390
  }
391
391
 
392
+ /**
393
+ * Clone a Map
394
+ */
395
+ declare function cloneMap<K, V>(map: Map<K, V>): Map<K, V>;
396
+ /**
397
+ * Clone a parent map with arrays
398
+ */
399
+ declare function cloneParentMap(map: Map<string | null, string[]>): Map<string | null, string[]>;
400
+ /**
401
+ * Compute normalized index from flat block array.
402
+ *
403
+ * With `orderingStrategy: 'fractional'`, siblings are sorted by their `order`
404
+ * field (lexicographic). With `'integer'` (default), the input order is preserved.
405
+ */
406
+ declare function computeNormalizedIndex<T extends BaseBlock>(blocks: T[], orderingStrategy?: OrderingStrategy): BlockIndex<T>;
407
+ /**
408
+ * Build ordered flat array from BlockIndex.
409
+ *
410
+ * With `'integer'` ordering (default), assigns sequential `order: 0, 1, 2, …`.
411
+ * With `'fractional'` ordering, preserves existing `order` values — the moved
412
+ * block already has its new fractional key set by `reparentBlockIndex`.
413
+ */
414
+ declare function buildOrderedBlocks<T extends BaseBlock>(index: BlockIndex<T>, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy): T[];
415
+ /**
416
+ * Reparent a block based on drop zone ID.
417
+ *
418
+ * @param state - Current block index
419
+ * @param activeId - ID of the dragged block
420
+ * @param targetZone - Drop zone ID (e.g., "after-uuid", "before-uuid", "into-uuid")
421
+ * @param containerTypes - Block types that can have children
422
+ * @param orderingStrategy - Whether to assign a fractional key to the moved block
423
+ */
424
+ declare function reparentBlockIndex<T extends BaseBlock>(state: BlockIndex<T>, activeId: UniqueIdentifier, targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
425
+ /**
426
+ * Compute the depth of a block by walking its parentId chain.
427
+ * Root-level blocks have depth 1.
428
+ */
429
+ declare function getBlockDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
430
+ /**
431
+ * Compute the maximum depth of a subtree rooted at blockId (inclusive).
432
+ * A leaf block returns 1.
433
+ */
434
+ declare function getSubtreeDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string, visited?: Set<string>): number;
435
+ /**
436
+ * Get all descendant IDs of a block
437
+ */
438
+ declare function getDescendantIds<T extends BaseBlock>(state: BlockIndex<T>, parentId: string): Set<string>;
439
+ /**
440
+ * Reparent multiple blocks to a target zone, preserving their relative order.
441
+ * The first block in `blockIds` is treated as the primary (anchor) block.
442
+ */
443
+ declare function reparentMultipleBlocks<T extends BaseBlock>(state: BlockIndex<T>, blockIds: string[], targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
444
+ /**
445
+ * Result of validating a block tree
446
+ */
447
+ interface TreeValidationResult {
448
+ valid: boolean;
449
+ issues: string[];
450
+ }
451
+ /**
452
+ * Validate a block tree index for structural integrity.
453
+ * Checks for cycles, orphans (parentId references non-existent block),
454
+ * and stale refs (byParent lists IDs not present in byId).
455
+ *
456
+ * Opt-in utility — not called automatically.
457
+ */
458
+ declare function validateBlockTree<T extends BaseBlock>(index: BlockIndex<T>): TreeValidationResult;
459
+ /**
460
+ * Delete a block and all its descendants
461
+ */
462
+ declare function deleteBlockAndDescendants<T extends BaseBlock>(state: BlockIndex<T>, id: string): BlockIndex<T>;
463
+
464
+ type SnapshotRectsRef = {
465
+ current: Map<UniqueIdentifier, DOMRect> | null;
466
+ };
392
467
  /**
393
468
  * Custom collision detection that scores drop zones by distance to nearest edge.
394
469
  * Uses edge-distance scoring with a bottom bias for more natural drag behavior.
@@ -404,8 +479,11 @@ declare const weightedVerticalCollision: CollisionDetection;
404
479
  * between adjacent drop zones.
405
480
  *
406
481
  * @param threshold - Minimum score improvement required to switch zones (default: 15px)
482
+ * @param snapshotRef - Optional ref to snapshotted zone rects. When populated,
483
+ * collision detection uses these frozen rects instead of live DOM measurements,
484
+ * preventing layout-shift feedback loops from in-flow ghost previews.
407
485
  */
408
- declare function createStickyCollision(threshold?: number): CollisionDetection & {
486
+ declare function createStickyCollision(threshold?: number, snapshotRef?: SnapshotRectsRef): CollisionDetection & {
409
487
  reset: () => void;
410
488
  };
411
489
  /**
@@ -521,7 +599,8 @@ interface TreeRendererProps<T extends BaseBlock> {
521
599
  /**
522
600
  * Recursive tree renderer with smart drop zones
523
601
  */
524
- declare function TreeRenderer<T extends BaseBlock>({ blocks, blocksByParent, parentId, activeId, expandedMap, renderers, containerTypes, onHover, onToggleExpand, depth, dropZoneClassName, dropZoneActiveClassName, indentClassName, rootClassName, canDrag, previewPosition, draggedBlock, focusedId, selectedIds, onBlockClick, animation, virtualVisibleIds, }: TreeRendererProps<T>): react_jsx_runtime.JSX.Element;
602
+ declare function TreeRendererInner<T extends BaseBlock>({ blocks, blocksByParent, parentId, activeId, expandedMap, renderers, containerTypes, onHover, onToggleExpand, depth, dropZoneClassName, dropZoneActiveClassName, indentClassName, rootClassName, canDrag, previewPosition, draggedBlock, focusedId, selectedIds, onBlockClick, animation, virtualVisibleIds, }: TreeRendererProps<T>): react_jsx_runtime.JSX.Element;
603
+ declare const TreeRenderer: typeof TreeRendererInner;
525
604
 
526
605
  interface DropZoneProps {
527
606
  id: string;
@@ -666,63 +745,6 @@ interface UseVirtualTreeResult {
666
745
  */
667
746
  declare function useVirtualTree({ containerRef, itemCount, itemHeight, overscan, }: UseVirtualTreeOptions): UseVirtualTreeResult;
668
747
 
669
- /**
670
- * Clone a Map
671
- */
672
- declare function cloneMap<K, V>(map: Map<K, V>): Map<K, V>;
673
- /**
674
- * Clone a parent map with arrays
675
- */
676
- declare function cloneParentMap(map: Map<string | null, string[]>): Map<string | null, string[]>;
677
- /**
678
- * Compute normalized index from flat block array.
679
- *
680
- * With `orderingStrategy: 'fractional'`, siblings are sorted by their `order`
681
- * field (lexicographic). With `'integer'` (default), the input order is preserved.
682
- */
683
- declare function computeNormalizedIndex<T extends BaseBlock>(blocks: T[], orderingStrategy?: OrderingStrategy): BlockIndex<T>;
684
- /**
685
- * Build ordered flat array from BlockIndex.
686
- *
687
- * With `'integer'` ordering (default), assigns sequential `order: 0, 1, 2, …`.
688
- * With `'fractional'` ordering, preserves existing `order` values — the moved
689
- * block already has its new fractional key set by `reparentBlockIndex`.
690
- */
691
- declare function buildOrderedBlocks<T extends BaseBlock>(index: BlockIndex<T>, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy): T[];
692
- /**
693
- * Reparent a block based on drop zone ID.
694
- *
695
- * @param state - Current block index
696
- * @param activeId - ID of the dragged block
697
- * @param targetZone - Drop zone ID (e.g., "after-uuid", "before-uuid", "into-uuid")
698
- * @param containerTypes - Block types that can have children
699
- * @param orderingStrategy - Whether to assign a fractional key to the moved block
700
- */
701
- declare function reparentBlockIndex<T extends BaseBlock>(state: BlockIndex<T>, activeId: UniqueIdentifier, targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
702
- /**
703
- * Compute the depth of a block by walking its parentId chain.
704
- * Root-level blocks have depth 1.
705
- */
706
- declare function getBlockDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
707
- /**
708
- * Compute the maximum depth of a subtree rooted at blockId (inclusive).
709
- * A leaf block returns 1.
710
- */
711
- declare function getSubtreeDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
712
- /**
713
- * Get all descendant IDs of a block
714
- */
715
- declare function getDescendantIds<T extends BaseBlock>(state: BlockIndex<T>, parentId: string): Set<string>;
716
- /**
717
- * Reparent multiple blocks to a target zone, preserving their relative order.
718
- * The first block in `blockIds` is treated as the primary (anchor) block.
719
- */
720
- declare function reparentMultipleBlocks<T extends BaseBlock>(state: BlockIndex<T>, blockIds: string[], targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
721
- /**
722
- * Delete a block and all its descendants
723
- */
724
- declare function deleteBlockAndDescendants<T extends BaseBlock>(state: BlockIndex<T>, id: string): BlockIndex<T>;
725
-
726
748
  /**
727
749
  * Extract UUID from a zone ID by removing the prefix (before-, after-, into-, end-)
728
750
  */
@@ -811,4 +833,4 @@ declare function initFractionalOrder<T extends {
811
833
  order: number | string;
812
834
  }>(blocks: T[]): T[];
813
835
 
814
- export { type AnimationConfig, type AutoExpandConfig, type BaseBlock, type BlockAction, type BlockAddEvent, type BlockDeleteEvent, type BlockIndex, type BlockMoveEvent, type BlockPosition, type BlockRendererProps, type BlockRenderers, type BlockStateContextValue, type BlockStateProviderProps, BlockTree, type BlockTreeCallbacks, type BlockTreeConfig, type BlockTreeCustomization, type BlockTreeProps, BlockTreeSSR, type BlockTreeSSRProps, type CanDragFn, type CanDropFn, type ContainerRendererProps, type DragEndEvent, type DragMoveEvent, DragOverlay, type DragOverlayProps$1 as DragOverlayProps, type DragStartEvent, DropZone, type DropZoneConfig, type DropZoneProps, type DropZoneType, type ExpandChangeEvent, type HoverChangeEvent, type IdGeneratorFn, type InternalRenderers, type MoveOperation, type NestedBlock, type OrderingStrategy, type RendererPropsFor, type SensorConfig, TreeRenderer, type TreeRendererProps, type TreeStateContextValue, type TreeStateProviderProps, type UseBlockHistoryOptions, type UseBlockHistoryResult, type UseLayoutAnimationOptions, type UseVirtualTreeOptions, type UseVirtualTreeResult, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSensorConfig, getSubtreeDepth, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, triggerHaptic, useBlockHistory, useConfiguredSensors, useLayoutAnimation, useVirtualTree, weightedVerticalCollision };
836
+ export { type AnimationConfig, type AutoExpandConfig, type BaseBlock, type BlockAction, type BlockAddEvent, type BlockDeleteEvent, type BlockIndex, type BlockMoveEvent, type BlockPosition, type BlockRendererProps, type BlockRenderers, type BlockStateContextValue, type BlockStateProviderProps, BlockTree, type BlockTreeCallbacks, type BlockTreeConfig, type BlockTreeCustomization, type BlockTreeProps, BlockTreeSSR, type BlockTreeSSRProps, type CanDragFn, type CanDropFn, type ContainerRendererProps, type DragEndEvent, type DragMoveEvent, DragOverlay, type DragOverlayProps$1 as DragOverlayProps, type DragStartEvent, DropZone, type DropZoneConfig, type DropZoneProps, type DropZoneType, type ExpandChangeEvent, type HoverChangeEvent, type IdGeneratorFn, type InternalRenderers, type MoveOperation, type NestedBlock, type OrderingStrategy, type RendererPropsFor, type SensorConfig, type SnapshotRectsRef, TreeRenderer, type TreeRendererProps, type TreeStateContextValue, type TreeStateProviderProps, type TreeValidationResult, type UseBlockHistoryOptions, type UseBlockHistoryResult, type UseLayoutAnimationOptions, type UseVirtualTreeOptions, type UseVirtualTreeResult, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSensorConfig, getSubtreeDepth, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, triggerHaptic, useBlockHistory, useConfiguredSensors, useLayoutAnimation, useVirtualTree, validateBlockTree, weightedVerticalCollision };
package/dist/index.d.ts CHANGED
@@ -389,6 +389,81 @@ interface TreeStateProviderProps<T extends BaseBlock = BaseBlock> {
389
389
  blockMap: Map<string, T>;
390
390
  }
391
391
 
392
+ /**
393
+ * Clone a Map
394
+ */
395
+ declare function cloneMap<K, V>(map: Map<K, V>): Map<K, V>;
396
+ /**
397
+ * Clone a parent map with arrays
398
+ */
399
+ declare function cloneParentMap(map: Map<string | null, string[]>): Map<string | null, string[]>;
400
+ /**
401
+ * Compute normalized index from flat block array.
402
+ *
403
+ * With `orderingStrategy: 'fractional'`, siblings are sorted by their `order`
404
+ * field (lexicographic). With `'integer'` (default), the input order is preserved.
405
+ */
406
+ declare function computeNormalizedIndex<T extends BaseBlock>(blocks: T[], orderingStrategy?: OrderingStrategy): BlockIndex<T>;
407
+ /**
408
+ * Build ordered flat array from BlockIndex.
409
+ *
410
+ * With `'integer'` ordering (default), assigns sequential `order: 0, 1, 2, …`.
411
+ * With `'fractional'` ordering, preserves existing `order` values — the moved
412
+ * block already has its new fractional key set by `reparentBlockIndex`.
413
+ */
414
+ declare function buildOrderedBlocks<T extends BaseBlock>(index: BlockIndex<T>, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy): T[];
415
+ /**
416
+ * Reparent a block based on drop zone ID.
417
+ *
418
+ * @param state - Current block index
419
+ * @param activeId - ID of the dragged block
420
+ * @param targetZone - Drop zone ID (e.g., "after-uuid", "before-uuid", "into-uuid")
421
+ * @param containerTypes - Block types that can have children
422
+ * @param orderingStrategy - Whether to assign a fractional key to the moved block
423
+ */
424
+ declare function reparentBlockIndex<T extends BaseBlock>(state: BlockIndex<T>, activeId: UniqueIdentifier, targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
425
+ /**
426
+ * Compute the depth of a block by walking its parentId chain.
427
+ * Root-level blocks have depth 1.
428
+ */
429
+ declare function getBlockDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
430
+ /**
431
+ * Compute the maximum depth of a subtree rooted at blockId (inclusive).
432
+ * A leaf block returns 1.
433
+ */
434
+ declare function getSubtreeDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string, visited?: Set<string>): number;
435
+ /**
436
+ * Get all descendant IDs of a block
437
+ */
438
+ declare function getDescendantIds<T extends BaseBlock>(state: BlockIndex<T>, parentId: string): Set<string>;
439
+ /**
440
+ * Reparent multiple blocks to a target zone, preserving their relative order.
441
+ * The first block in `blockIds` is treated as the primary (anchor) block.
442
+ */
443
+ declare function reparentMultipleBlocks<T extends BaseBlock>(state: BlockIndex<T>, blockIds: string[], targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
444
+ /**
445
+ * Result of validating a block tree
446
+ */
447
+ interface TreeValidationResult {
448
+ valid: boolean;
449
+ issues: string[];
450
+ }
451
+ /**
452
+ * Validate a block tree index for structural integrity.
453
+ * Checks for cycles, orphans (parentId references non-existent block),
454
+ * and stale refs (byParent lists IDs not present in byId).
455
+ *
456
+ * Opt-in utility — not called automatically.
457
+ */
458
+ declare function validateBlockTree<T extends BaseBlock>(index: BlockIndex<T>): TreeValidationResult;
459
+ /**
460
+ * Delete a block and all its descendants
461
+ */
462
+ declare function deleteBlockAndDescendants<T extends BaseBlock>(state: BlockIndex<T>, id: string): BlockIndex<T>;
463
+
464
+ type SnapshotRectsRef = {
465
+ current: Map<UniqueIdentifier, DOMRect> | null;
466
+ };
392
467
  /**
393
468
  * Custom collision detection that scores drop zones by distance to nearest edge.
394
469
  * Uses edge-distance scoring with a bottom bias for more natural drag behavior.
@@ -404,8 +479,11 @@ declare const weightedVerticalCollision: CollisionDetection;
404
479
  * between adjacent drop zones.
405
480
  *
406
481
  * @param threshold - Minimum score improvement required to switch zones (default: 15px)
482
+ * @param snapshotRef - Optional ref to snapshotted zone rects. When populated,
483
+ * collision detection uses these frozen rects instead of live DOM measurements,
484
+ * preventing layout-shift feedback loops from in-flow ghost previews.
407
485
  */
408
- declare function createStickyCollision(threshold?: number): CollisionDetection & {
486
+ declare function createStickyCollision(threshold?: number, snapshotRef?: SnapshotRectsRef): CollisionDetection & {
409
487
  reset: () => void;
410
488
  };
411
489
  /**
@@ -521,7 +599,8 @@ interface TreeRendererProps<T extends BaseBlock> {
521
599
  /**
522
600
  * Recursive tree renderer with smart drop zones
523
601
  */
524
- declare function TreeRenderer<T extends BaseBlock>({ blocks, blocksByParent, parentId, activeId, expandedMap, renderers, containerTypes, onHover, onToggleExpand, depth, dropZoneClassName, dropZoneActiveClassName, indentClassName, rootClassName, canDrag, previewPosition, draggedBlock, focusedId, selectedIds, onBlockClick, animation, virtualVisibleIds, }: TreeRendererProps<T>): react_jsx_runtime.JSX.Element;
602
+ declare function TreeRendererInner<T extends BaseBlock>({ blocks, blocksByParent, parentId, activeId, expandedMap, renderers, containerTypes, onHover, onToggleExpand, depth, dropZoneClassName, dropZoneActiveClassName, indentClassName, rootClassName, canDrag, previewPosition, draggedBlock, focusedId, selectedIds, onBlockClick, animation, virtualVisibleIds, }: TreeRendererProps<T>): react_jsx_runtime.JSX.Element;
603
+ declare const TreeRenderer: typeof TreeRendererInner;
525
604
 
526
605
  interface DropZoneProps {
527
606
  id: string;
@@ -666,63 +745,6 @@ interface UseVirtualTreeResult {
666
745
  */
667
746
  declare function useVirtualTree({ containerRef, itemCount, itemHeight, overscan, }: UseVirtualTreeOptions): UseVirtualTreeResult;
668
747
 
669
- /**
670
- * Clone a Map
671
- */
672
- declare function cloneMap<K, V>(map: Map<K, V>): Map<K, V>;
673
- /**
674
- * Clone a parent map with arrays
675
- */
676
- declare function cloneParentMap(map: Map<string | null, string[]>): Map<string | null, string[]>;
677
- /**
678
- * Compute normalized index from flat block array.
679
- *
680
- * With `orderingStrategy: 'fractional'`, siblings are sorted by their `order`
681
- * field (lexicographic). With `'integer'` (default), the input order is preserved.
682
- */
683
- declare function computeNormalizedIndex<T extends BaseBlock>(blocks: T[], orderingStrategy?: OrderingStrategy): BlockIndex<T>;
684
- /**
685
- * Build ordered flat array from BlockIndex.
686
- *
687
- * With `'integer'` ordering (default), assigns sequential `order: 0, 1, 2, …`.
688
- * With `'fractional'` ordering, preserves existing `order` values — the moved
689
- * block already has its new fractional key set by `reparentBlockIndex`.
690
- */
691
- declare function buildOrderedBlocks<T extends BaseBlock>(index: BlockIndex<T>, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy): T[];
692
- /**
693
- * Reparent a block based on drop zone ID.
694
- *
695
- * @param state - Current block index
696
- * @param activeId - ID of the dragged block
697
- * @param targetZone - Drop zone ID (e.g., "after-uuid", "before-uuid", "into-uuid")
698
- * @param containerTypes - Block types that can have children
699
- * @param orderingStrategy - Whether to assign a fractional key to the moved block
700
- */
701
- declare function reparentBlockIndex<T extends BaseBlock>(state: BlockIndex<T>, activeId: UniqueIdentifier, targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
702
- /**
703
- * Compute the depth of a block by walking its parentId chain.
704
- * Root-level blocks have depth 1.
705
- */
706
- declare function getBlockDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
707
- /**
708
- * Compute the maximum depth of a subtree rooted at blockId (inclusive).
709
- * A leaf block returns 1.
710
- */
711
- declare function getSubtreeDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
712
- /**
713
- * Get all descendant IDs of a block
714
- */
715
- declare function getDescendantIds<T extends BaseBlock>(state: BlockIndex<T>, parentId: string): Set<string>;
716
- /**
717
- * Reparent multiple blocks to a target zone, preserving their relative order.
718
- * The first block in `blockIds` is treated as the primary (anchor) block.
719
- */
720
- declare function reparentMultipleBlocks<T extends BaseBlock>(state: BlockIndex<T>, blockIds: string[], targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
721
- /**
722
- * Delete a block and all its descendants
723
- */
724
- declare function deleteBlockAndDescendants<T extends BaseBlock>(state: BlockIndex<T>, id: string): BlockIndex<T>;
725
-
726
748
  /**
727
749
  * Extract UUID from a zone ID by removing the prefix (before-, after-, into-, end-)
728
750
  */
@@ -811,4 +833,4 @@ declare function initFractionalOrder<T extends {
811
833
  order: number | string;
812
834
  }>(blocks: T[]): T[];
813
835
 
814
- export { type AnimationConfig, type AutoExpandConfig, type BaseBlock, type BlockAction, type BlockAddEvent, type BlockDeleteEvent, type BlockIndex, type BlockMoveEvent, type BlockPosition, type BlockRendererProps, type BlockRenderers, type BlockStateContextValue, type BlockStateProviderProps, BlockTree, type BlockTreeCallbacks, type BlockTreeConfig, type BlockTreeCustomization, type BlockTreeProps, BlockTreeSSR, type BlockTreeSSRProps, type CanDragFn, type CanDropFn, type ContainerRendererProps, type DragEndEvent, type DragMoveEvent, DragOverlay, type DragOverlayProps$1 as DragOverlayProps, type DragStartEvent, DropZone, type DropZoneConfig, type DropZoneProps, type DropZoneType, type ExpandChangeEvent, type HoverChangeEvent, type IdGeneratorFn, type InternalRenderers, type MoveOperation, type NestedBlock, type OrderingStrategy, type RendererPropsFor, type SensorConfig, TreeRenderer, type TreeRendererProps, type TreeStateContextValue, type TreeStateProviderProps, type UseBlockHistoryOptions, type UseBlockHistoryResult, type UseLayoutAnimationOptions, type UseVirtualTreeOptions, type UseVirtualTreeResult, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSensorConfig, getSubtreeDepth, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, triggerHaptic, useBlockHistory, useConfiguredSensors, useLayoutAnimation, useVirtualTree, weightedVerticalCollision };
836
+ export { type AnimationConfig, type AutoExpandConfig, type BaseBlock, type BlockAction, type BlockAddEvent, type BlockDeleteEvent, type BlockIndex, type BlockMoveEvent, type BlockPosition, type BlockRendererProps, type BlockRenderers, type BlockStateContextValue, type BlockStateProviderProps, BlockTree, type BlockTreeCallbacks, type BlockTreeConfig, type BlockTreeCustomization, type BlockTreeProps, BlockTreeSSR, type BlockTreeSSRProps, type CanDragFn, type CanDropFn, type ContainerRendererProps, type DragEndEvent, type DragMoveEvent, DragOverlay, type DragOverlayProps$1 as DragOverlayProps, type DragStartEvent, DropZone, type DropZoneConfig, type DropZoneProps, type DropZoneType, type ExpandChangeEvent, type HoverChangeEvent, type IdGeneratorFn, type InternalRenderers, type MoveOperation, type NestedBlock, type OrderingStrategy, type RendererPropsFor, type SensorConfig, type SnapshotRectsRef, TreeRenderer, type TreeRendererProps, type TreeStateContextValue, type TreeStateProviderProps, type TreeValidationResult, type UseBlockHistoryOptions, type UseBlockHistoryResult, type UseLayoutAnimationOptions, type UseVirtualTreeOptions, type UseVirtualTreeResult, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSensorConfig, getSubtreeDepth, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, triggerHaptic, useBlockHistory, useConfiguredSensors, useLayoutAnimation, useVirtualTree, validateBlockTree, weightedVerticalCollision };