dnd-block-tree 0.2.0 → 0.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/README.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # dnd-block-tree
2
2
 
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
+ [![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)
10
+
3
11
  A headless React library for building hierarchical drag-and-drop interfaces. Bring your own components, we handle the complexity.
4
12
 
5
13
  ## Features
@@ -7,10 +15,16 @@ A headless React library for building hierarchical drag-and-drop interfaces. Bri
7
15
  - **Stable Drop Zones** - Zones render based on original block positions, not preview state, ensuring consistent drop targets during drag
8
16
  - **Ghost Preview** - Semi-transparent preview shows where blocks will land without affecting zone positions
9
17
  - **Depth-Aware Collision** - Smart algorithm prefers nested zones when cursor is at indented levels, with hysteresis to prevent flickering
10
- - **8px Activation Distance** - Prevents accidental drags. Pointer must move 8px before drag starts, allowing normal clicks
18
+ - **Mobile & Touch Support** - Separate touch/pointer activation constraints prevent interference with scrolling on mobile devices
11
19
  - **Snapshot-Based Computation** - State captured at drag start. All preview computations use snapshot, ensuring consistent behavior
12
20
  - **Debounced Preview** - 150ms debounced virtual state for smooth drag previews without jitter
13
21
  - **Customizable Drag Rules** - `canDrag` and `canDrop` filters for fine-grained control over drag behavior
22
+ - **Max Depth Constraint** - Limit nesting depth via `maxDepth` prop, enforced in both drag validation and programmatic APIs
23
+ - **Keyboard Navigation** - Arrow key traversal, Enter/Space to expand/collapse, Home/End to jump. Opt-in via `keyboardNavigation` prop
24
+ - **Multi-Select Drag** - Cmd/Ctrl+Click and Shift+Click selection with batch drag. Opt-in via `multiSelect` prop
25
+ - **Undo/Redo** - Composable `useBlockHistory` hook with past/future stacks for state history management
26
+ - **Lifecycle Callbacks** - `onBlockAdd`, `onBlockDelete`, `onBlockMove`, `onBeforeMove` middleware, and more
27
+ - **Fractional Indexing** - Opt-in CRDT-compatible ordering via `orderingStrategy: 'fractional'`
14
28
 
15
29
  ## Installation
16
30
 
@@ -69,11 +83,32 @@ function App() {
69
83
  | `showDropPreview` | `boolean` | `true` | Show live preview of block at drop position |
70
84
  | `canDrag` | `(block: T) => boolean` | - | Filter which blocks can be dragged |
71
85
  | `canDrop` | `(block, zone, target) => boolean` | - | Filter valid drop targets |
86
+ | `maxDepth` | `number` | - | Maximum nesting depth (1 = flat, 2 = one level, etc.) |
87
+ | `keyboardNavigation` | `boolean` | `false` | Enable keyboard navigation with arrow keys |
88
+ | `multiSelect` | `boolean` | `false` | Enable multi-select with Cmd/Ctrl+Click and Shift+Click |
89
+ | `selectedIds` | `Set<string>` | - | Externally-controlled selected IDs (for multi-select) |
90
+ | `onSelectionChange` | `(ids: Set<string>) => void` | - | Called when selection changes |
91
+ | `orderingStrategy` | `'integer' \| 'fractional'` | `'integer'` | Sibling ordering strategy |
72
92
  | `className` | `string` | - | Root container class |
73
93
  | `dropZoneClassName` | `string` | - | Drop zone class |
74
94
  | `dropZoneActiveClassName` | `string` | - | Active drop zone class |
75
95
  | `indentClassName` | `string` | - | Nested children indent class |
76
96
 
97
+ ### Callbacks
98
+
99
+ | Callback | Type | Description |
100
+ |----------|------|-------------|
101
+ | `onDragStart` | `(event: DragStartEvent<T>) => boolean \| void` | Called when drag starts. Return `false` to prevent. |
102
+ | `onDragMove` | `(event: DragMoveEvent<T>) => void` | Called during drag movement (debounced) |
103
+ | `onDragEnd` | `(event: DragEndEvent<T>) => void` | Called when drag ends |
104
+ | `onDragCancel` | `(event: DragEndEvent<T>) => void` | Called when drag is cancelled |
105
+ | `onBeforeMove` | `(op: MoveOperation<T>) => MoveOperation<T> \| false \| void` | Middleware to transform or cancel moves |
106
+ | `onBlockMove` | `(event: BlockMoveEvent<T>) => void` | Called after a block is moved |
107
+ | `onBlockAdd` | `(event: BlockAddEvent<T>) => void` | Called after a block is added |
108
+ | `onBlockDelete` | `(event: BlockDeleteEvent<T>) => void` | Called after a block is deleted |
109
+ | `onExpandChange` | `(event: ExpandChangeEvent<T>) => void` | Called when expand/collapse changes |
110
+ | `onHoverChange` | `(event: HoverChangeEvent<T>) => void` | Called when hover zone changes |
111
+
77
112
  ### Types
78
113
 
79
114
  #### BaseBlock
@@ -85,7 +120,7 @@ interface BaseBlock {
85
120
  id: string
86
121
  type: string
87
122
  parentId: string | null
88
- order: number
123
+ order: number | string // number for integer ordering, string for fractional
89
124
  }
90
125
  ```
91
126
 
@@ -115,6 +150,78 @@ interface ContainerRendererProps<T extends BaseBlock> extends BlockRendererProps
115
150
  }
116
151
  ```
117
152
 
153
+ ## Undo/Redo
154
+
155
+ The `useBlockHistory` hook provides undo/redo support as a composable layer on top of `BlockTree`:
156
+
157
+ ```tsx
158
+ import { BlockTree, useBlockHistory } from 'dnd-block-tree'
159
+
160
+ function App() {
161
+ const { blocks, set, undo, redo, canUndo, canRedo } = useBlockHistory<MyBlock>(initialBlocks, {
162
+ maxSteps: 50, // optional, default 50
163
+ })
164
+
165
+ return (
166
+ <>
167
+ <div>
168
+ <button onClick={undo} disabled={!canUndo}>Undo</button>
169
+ <button onClick={redo} disabled={!canRedo}>Redo</button>
170
+ </div>
171
+ <BlockTree blocks={blocks} onChange={set} ... />
172
+ </>
173
+ )
174
+ }
175
+ ```
176
+
177
+ ## Keyboard Navigation
178
+
179
+ Enable accessible tree navigation with the `keyboardNavigation` prop:
180
+
181
+ ```tsx
182
+ <BlockTree keyboardNavigation blocks={blocks} ... />
183
+ ```
184
+
185
+ | Key | Action |
186
+ |-----|--------|
187
+ | Arrow Down | Focus next visible block |
188
+ | Arrow Up | Focus previous visible block |
189
+ | Arrow Right | Expand container, or focus first child |
190
+ | Arrow Left | Collapse container, or focus parent |
191
+ | Enter / Space | Toggle expand/collapse |
192
+ | Home | Focus first block |
193
+ | End | Focus last block |
194
+
195
+ Blocks receive `data-block-id` and `tabIndex` attributes for focus management, and the tree root gets `role="tree"`.
196
+
197
+ ## Multi-Select Drag
198
+
199
+ Enable batch selection and drag with the `multiSelect` prop:
200
+
201
+ ```tsx
202
+ <BlockTree multiSelect blocks={blocks} ... />
203
+ ```
204
+
205
+ - **Cmd/Ctrl+Click** toggles a single block in the selection
206
+ - **Shift+Click** range-selects between the last clicked and current block
207
+ - **Plain click** clears selection and selects only the clicked block
208
+ - Dragging a selected block moves all selected blocks, preserving relative order
209
+ - The drag overlay shows a stacked card effect with a count badge
210
+
211
+ You can also control selection externally via `selectedIds` and `onSelectionChange`.
212
+
213
+ ## Max Depth
214
+
215
+ Limit nesting depth to prevent deeply nested trees:
216
+
217
+ ```tsx
218
+ <BlockTree maxDepth={2} blocks={blocks} ... />
219
+ ```
220
+
221
+ - `maxDepth={1}` - flat list, no nesting allowed
222
+ - `maxDepth={2}` - blocks can nest one level inside containers
223
+ - When a move would exceed the limit, the drop zone is rejected and the move is a no-op
224
+
118
225
  ## Type Safety
119
226
 
120
227
  The library provides automatic type inference for container vs non-container renderers:
@@ -142,10 +249,16 @@ The library exports several utility functions:
142
249
 
143
250
  ```typescript
144
251
  import {
145
- computeNormalizedIndex, // Convert flat array to normalized index
146
- buildOrderedBlocks, // Convert index back to ordered array
147
- reparentBlockIndex, // Move a block to a new position
148
- generateId, // Generate unique block IDs
252
+ computeNormalizedIndex, // Convert flat array to normalized index
253
+ buildOrderedBlocks, // Convert index back to ordered array
254
+ reparentBlockIndex, // Move a block to a new position
255
+ reparentMultipleBlocks, // Move multiple blocks preserving relative order
256
+ getDescendantIds, // Get all descendant IDs of a block
257
+ getBlockDepth, // Compute depth of a block in the tree
258
+ getSubtreeDepth, // Compute max depth of a subtree
259
+ generateId, // Generate unique block IDs
260
+ generateKeyBetween, // Generate a fractional key between two keys
261
+ initFractionalOrder, // Convert integer-ordered blocks to fractional
149
262
  } from 'dnd-block-tree'
150
263
  ```
151
264
 
@@ -153,13 +266,13 @@ import {
153
266
 
154
267
  Check out the [live demo](https://dnd-block-tree.vercel.app) to see the library in action with two example use cases:
155
268
 
156
- - **Productivity** - Sections, tasks, and notes
269
+ - **Productivity** - Sections, tasks, and notes with undo/redo, max depth control, and keyboard navigation
157
270
  - **File System** - Folders and files
158
271
 
159
272
  ## Built With
160
273
 
161
274
  - [dnd-kit](https://dndkit.com/) - Modern drag and drop toolkit for React
162
- - React 18+
275
+ - React 18+ / React 19
163
276
  - TypeScript
164
277
 
165
278
  ## License
package/dist/index.d.mts CHANGED
@@ -11,7 +11,8 @@ interface BaseBlock {
11
11
  id: string;
12
12
  type: string;
13
13
  parentId: string | null;
14
- order: number;
14
+ /** Ordering value. Number for integer ordering (default), string for fractional ordering. */
15
+ order: number | string;
15
16
  }
16
17
  /**
17
18
  * Normalized index structure for efficient tree operations
@@ -106,6 +107,20 @@ interface BlockMoveEvent<T extends BaseBlock = BaseBlock> {
106
107
  to: BlockPosition;
107
108
  /** All blocks after the move */
108
109
  blocks: T[];
110
+ /** IDs of all blocks that were moved (for multi-select) */
111
+ movedIds: string[];
112
+ }
113
+ /**
114
+ * A pending move operation passed through the onBeforeMove middleware pipeline.
115
+ * Return a modified operation to transform the move, or return false to cancel.
116
+ */
117
+ interface MoveOperation<T extends BaseBlock = BaseBlock> {
118
+ /** The block being moved */
119
+ block: T;
120
+ /** Position before the move */
121
+ from: BlockPosition;
122
+ /** Target drop zone ID */
123
+ targetZone: string;
109
124
  }
110
125
  /**
111
126
  * Event fired when expand state changes
@@ -115,6 +130,22 @@ interface ExpandChangeEvent<T extends BaseBlock = BaseBlock> {
115
130
  blockId: string;
116
131
  expanded: boolean;
117
132
  }
133
+ /**
134
+ * Event fired when a block is added
135
+ */
136
+ interface BlockAddEvent<T extends BaseBlock = BaseBlock> {
137
+ block: T;
138
+ parentId: string | null;
139
+ index: number;
140
+ }
141
+ /**
142
+ * Event fired when a block (and optionally its descendants) is deleted
143
+ */
144
+ interface BlockDeleteEvent<T extends BaseBlock = BaseBlock> {
145
+ block: T;
146
+ deletedIds: string[];
147
+ parentId: string | null;
148
+ }
118
149
  /**
119
150
  * Event fired when hover zone changes
120
151
  */
@@ -136,13 +167,33 @@ interface BlockTreeCallbacks<T extends BaseBlock = BaseBlock> {
136
167
  onDragEnd?: (event: DragEndEvent<T>) => void;
137
168
  /** Called when drag is cancelled */
138
169
  onDragCancel?: (event: DragEndEvent<T>) => void;
170
+ /**
171
+ * Called before a block move is committed. Return a modified MoveOperation to
172
+ * transform the move (e.g. change the target zone), or return false to cancel.
173
+ * Return void / undefined to allow the move as-is.
174
+ */
175
+ onBeforeMove?: (operation: MoveOperation<T>) => MoveOperation<T> | false | void;
139
176
  /** Called after a block is moved to a new position */
140
177
  onBlockMove?: (event: BlockMoveEvent<T>) => void;
141
178
  /** Called when expand/collapse state changes */
142
179
  onExpandChange?: (event: ExpandChangeEvent<T>) => void;
143
180
  /** Called when hover zone changes during drag */
144
181
  onHoverChange?: (event: HoverChangeEvent<T>) => void;
182
+ /** Called after a block is added */
183
+ onBlockAdd?: (event: BlockAddEvent<T>) => void;
184
+ /** Called after a block (and its descendants) is deleted */
185
+ onBlockDelete?: (event: BlockDeleteEvent<T>) => void;
145
186
  }
187
+ /**
188
+ * Ordering strategy for block siblings.
189
+ *
190
+ * - `'integer'` (default): siblings are reindexed 0, 1, 2, … after every move.
191
+ * Simple and efficient; not suitable for collaborative/CRDT scenarios.
192
+ * - `'fractional'`: each move only updates the moved block's `order` with a
193
+ * lexicographically sortable string key (fractional index). Siblings are never
194
+ * reindexed, making it conflict-free for concurrent edits.
195
+ */
196
+ type OrderingStrategy = 'integer' | 'fractional';
146
197
  /**
147
198
  * Filter function to determine if a block can be dragged
148
199
  */
@@ -218,6 +269,14 @@ interface BlockTreeCustomization<T extends BaseBlock = BaseBlock> {
218
269
  idGenerator?: IdGeneratorFn;
219
270
  /** Initially expanded block IDs */
220
271
  initialExpanded?: string[] | 'all' | 'none';
272
+ /**
273
+ * Ordering strategy for block siblings.
274
+ * Defaults to `'integer'` (reindex all siblings on every move).
275
+ * Use `'fractional'` for CRDT-compatible collaborative editing.
276
+ */
277
+ orderingStrategy?: OrderingStrategy;
278
+ /** Maximum nesting depth (1 = flat list, 2 = one level of nesting, etc.) */
279
+ maxDepth?: number;
221
280
  }
222
281
  /**
223
282
  * Block action types for the reducer
@@ -312,6 +371,10 @@ interface BlockStateProviderProps<T extends BaseBlock = BaseBlock> {
312
371
  initialBlocks?: T[];
313
372
  containerTypes?: readonly string[];
314
373
  onChange?: (blocks: T[]) => void;
374
+ orderingStrategy?: OrderingStrategy;
375
+ maxDepth?: number;
376
+ onBlockAdd?: (event: BlockAddEvent<T>) => void;
377
+ onBlockDelete?: (event: BlockDeleteEvent<T>) => void;
315
378
  }
316
379
  /**
317
380
  * Tree state provider props
@@ -395,12 +458,20 @@ interface BlockTreeProps<T extends BaseBlock, C extends readonly T['type'][] = r
395
458
  indentClassName?: string;
396
459
  /** Show live preview of drop position during drag (default: true) */
397
460
  showDropPreview?: boolean;
461
+ /** Enable keyboard navigation with arrow keys (default: false) */
462
+ keyboardNavigation?: boolean;
463
+ /** Enable multi-select with Cmd/Ctrl+Click and Shift+Click (default: false) */
464
+ multiSelect?: boolean;
465
+ /** Externally-controlled selected IDs (for multi-select) */
466
+ selectedIds?: Set<string>;
467
+ /** Called when selection changes (for multi-select) */
468
+ onSelectionChange?: (selectedIds: Set<string>) => void;
398
469
  }
399
470
  /**
400
471
  * Main BlockTree component
401
472
  * Provides drag-and-drop functionality for hierarchical block structures
402
473
  */
403
- declare function BlockTree<T extends BaseBlock, C extends readonly T['type'][] = readonly T['type'][]>({ blocks, renderers, containerTypes, onChange, dragOverlay, activationDistance, previewDebounce, className, dropZoneClassName, dropZoneActiveClassName, indentClassName, showDropPreview, onDragStart, onDragMove, onDragEnd, onDragCancel, onBlockMove, onExpandChange, onHoverChange, canDrag, canDrop, collisionDetection, sensors: sensorConfig, initialExpanded, }: BlockTreeProps<T, C>): react_jsx_runtime.JSX.Element;
474
+ declare function BlockTree<T extends BaseBlock, C extends readonly T['type'][] = readonly T['type'][]>({ blocks, renderers, containerTypes, onChange, dragOverlay, activationDistance, previewDebounce, className, dropZoneClassName, dropZoneActiveClassName, indentClassName, showDropPreview, onDragStart, onDragMove, onDragEnd, onDragCancel, onBeforeMove, onBlockMove, onExpandChange, onHoverChange, canDrag, canDrop, collisionDetection, sensors: sensorConfig, initialExpanded, orderingStrategy, maxDepth, keyboardNavigation, multiSelect, selectedIds: externalSelectedIds, onSelectionChange, }: BlockTreeProps<T, C>): react_jsx_runtime.JSX.Element;
404
475
 
405
476
  interface TreeRendererProps<T extends BaseBlock> {
406
477
  blocks: T[];
@@ -425,11 +496,17 @@ interface TreeRendererProps<T extends BaseBlock> {
425
496
  } | null;
426
497
  /** The dragged block for rendering preview ghost */
427
498
  draggedBlock?: T | null;
499
+ /** Currently focused block ID for keyboard navigation */
500
+ focusedId?: string | null;
501
+ /** Currently selected block IDs for multi-select */
502
+ selectedIds?: Set<string>;
503
+ /** Click handler for multi-select */
504
+ onBlockClick?: (blockId: string, event: React.MouseEvent) => void;
428
505
  }
429
506
  /**
430
507
  * Recursive tree renderer with smart drop zones
431
508
  */
432
- declare function TreeRenderer<T extends BaseBlock>({ blocks, blocksByParent, parentId, activeId, expandedMap, renderers, containerTypes, onHover, onToggleExpand, depth, dropZoneClassName, dropZoneActiveClassName, indentClassName, rootClassName, canDrag, previewPosition, draggedBlock, }: TreeRendererProps<T>): react_jsx_runtime.JSX.Element;
509
+ 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, }: TreeRendererProps<T>): react_jsx_runtime.JSX.Element;
433
510
 
434
511
  interface DropZoneProps {
435
512
  id: string;
@@ -450,18 +527,20 @@ declare const DropZone: react.MemoExoticComponent<typeof DropZoneComponent>;
450
527
  interface DragOverlayProps<T extends BaseBlock> {
451
528
  activeBlock: T | null;
452
529
  children?: (block: T) => ReactNode;
530
+ /** Number of selected items being dragged (for multi-select badge) */
531
+ selectedCount?: number;
453
532
  }
454
533
  /**
455
534
  * Default drag overlay component
456
535
  * Shows a preview of the dragged item
457
536
  */
458
- declare function DragOverlay<T extends BaseBlock>({ activeBlock, children, }: DragOverlayProps<T>): react_jsx_runtime.JSX.Element;
537
+ declare function DragOverlay<T extends BaseBlock>({ activeBlock, children, selectedCount, }: DragOverlayProps<T>): react_jsx_runtime.JSX.Element;
459
538
 
460
539
  /**
461
540
  * Create block state context and hooks
462
541
  */
463
542
  declare function createBlockState<T extends BaseBlock>(): {
464
- BlockStateProvider: ({ children, initialBlocks, containerTypes, onChange, }: BlockStateProviderProps<T>) => react_jsx_runtime.JSX.Element;
543
+ BlockStateProvider: ({ children, initialBlocks, containerTypes, onChange, orderingStrategy, maxDepth, onBlockAdd, onBlockDelete, }: BlockStateProviderProps<T>) => react_jsx_runtime.JSX.Element;
465
544
  useBlockState: () => BlockStateContextValue<T>;
466
545
  };
467
546
 
@@ -478,6 +557,37 @@ declare function createTreeState<T extends BaseBlock>(options?: CreateTreeStateO
478
557
  useTreeState: () => TreeStateContextValue<T>;
479
558
  };
480
559
 
560
+ interface UseBlockHistoryOptions {
561
+ /** Maximum number of undo steps to retain (default: 50) */
562
+ maxSteps?: number;
563
+ }
564
+ interface UseBlockHistoryResult<T extends BaseBlock> {
565
+ /** Current blocks state */
566
+ blocks: T[];
567
+ /** Set new blocks state (pushes current to undo stack) */
568
+ set: (blocks: T[]) => void;
569
+ /** Undo the last change */
570
+ undo: () => void;
571
+ /** Redo the last undone change */
572
+ redo: () => void;
573
+ /** Whether undo is available */
574
+ canUndo: boolean;
575
+ /** Whether redo is available */
576
+ canRedo: boolean;
577
+ }
578
+ /**
579
+ * Composable hook for undo/redo support with BlockTree.
580
+ *
581
+ * Usage:
582
+ * ```tsx
583
+ * const { blocks, set, undo, redo, canUndo, canRedo } = useBlockHistory(initialBlocks)
584
+ * <BlockTree blocks={blocks} onChange={set} />
585
+ * <button onClick={undo} disabled={!canUndo}>Undo</button>
586
+ * <button onClick={redo} disabled={!canRedo}>Redo</button>
587
+ * ```
588
+ */
589
+ declare function useBlockHistory<T extends BaseBlock>(initialBlocks: T[], options?: UseBlockHistoryOptions): UseBlockHistoryResult<T>;
590
+
481
591
  /**
482
592
  * Clone a Map
483
593
  */
@@ -487,26 +597,49 @@ declare function cloneMap<K, V>(map: Map<K, V>): Map<K, V>;
487
597
  */
488
598
  declare function cloneParentMap(map: Map<string | null, string[]>): Map<string | null, string[]>;
489
599
  /**
490
- * Compute normalized index from flat block array
600
+ * Compute normalized index from flat block array.
601
+ *
602
+ * With `orderingStrategy: 'fractional'`, siblings are sorted by their `order`
603
+ * field (lexicographic). With `'integer'` (default), the input order is preserved.
491
604
  */
492
- declare function computeNormalizedIndex<T extends BaseBlock>(blocks: T[]): BlockIndex<T>;
605
+ declare function computeNormalizedIndex<T extends BaseBlock>(blocks: T[], orderingStrategy?: OrderingStrategy): BlockIndex<T>;
493
606
  /**
494
- * Build ordered flat array from BlockIndex
607
+ * Build ordered flat array from BlockIndex.
608
+ *
609
+ * With `'integer'` ordering (default), assigns sequential `order: 0, 1, 2, …`.
610
+ * With `'fractional'` ordering, preserves existing `order` values — the moved
611
+ * block already has its new fractional key set by `reparentBlockIndex`.
495
612
  */
496
- declare function buildOrderedBlocks<T extends BaseBlock>(index: BlockIndex<T>, containerTypes?: readonly string[]): T[];
613
+ declare function buildOrderedBlocks<T extends BaseBlock>(index: BlockIndex<T>, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy): T[];
497
614
  /**
498
- * Reparent a block based on drop zone ID
615
+ * Reparent a block based on drop zone ID.
499
616
  *
500
617
  * @param state - Current block index
501
618
  * @param activeId - ID of the dragged block
502
619
  * @param targetZone - Drop zone ID (e.g., "after-uuid", "before-uuid", "into-uuid")
503
620
  * @param containerTypes - Block types that can have children
621
+ * @param orderingStrategy - Whether to assign a fractional key to the moved block
504
622
  */
505
- declare function reparentBlockIndex<T extends BaseBlock>(state: BlockIndex<T>, activeId: UniqueIdentifier, targetZone: string, containerTypes?: readonly string[]): BlockIndex<T>;
623
+ declare function reparentBlockIndex<T extends BaseBlock>(state: BlockIndex<T>, activeId: UniqueIdentifier, targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
624
+ /**
625
+ * Compute the depth of a block by walking its parentId chain.
626
+ * Root-level blocks have depth 1.
627
+ */
628
+ declare function getBlockDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
629
+ /**
630
+ * Compute the maximum depth of a subtree rooted at blockId (inclusive).
631
+ * A leaf block returns 1.
632
+ */
633
+ declare function getSubtreeDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
506
634
  /**
507
635
  * Get all descendant IDs of a block
508
636
  */
509
637
  declare function getDescendantIds<T extends BaseBlock>(state: BlockIndex<T>, parentId: string): Set<string>;
638
+ /**
639
+ * Reparent multiple blocks to a target zone, preserving their relative order.
640
+ * The first block in `blockIds` is treated as the primary (anchor) block.
641
+ */
642
+ declare function reparentMultipleBlocks<T extends BaseBlock>(state: BlockIndex<T>, blockIds: string[], targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
510
643
  /**
511
644
  * Delete a block and all its descendants
512
645
  */
@@ -527,4 +660,54 @@ declare function debounce<Args extends unknown[]>(fn: (...args: Args) => void, d
527
660
  */
528
661
  declare function generateId(): string;
529
662
 
530
- export { type AnimationConfig, type AutoExpandConfig, type BaseBlock, type BlockAction, type BlockIndex, type BlockMoveEvent, type BlockPosition, type BlockRendererProps, type BlockRenderers, type BlockStateContextValue, type BlockStateProviderProps, BlockTree, type BlockTreeCallbacks, type BlockTreeConfig, type BlockTreeCustomization, type BlockTreeProps, 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 RendererPropsFor, type SensorConfig, TreeRenderer, type TreeRendererProps, type TreeStateContextValue, type TreeStateProviderProps, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, generateId, getDescendantIds, getDropZoneType, getSensorConfig, reparentBlockIndex, useConfiguredSensors, weightedVerticalCollision };
663
+ /**
664
+ * Fractional indexing utilities
665
+ *
666
+ * Generates lexicographically sortable string keys that allow insertion between
667
+ * any two existing keys without reindexing siblings. Suitable for CRDT-based
668
+ * collaborative editing.
669
+ *
670
+ * Alphabet: 0-9a-z (base 36). Lexicographic order matches semantic order since
671
+ * '0' < '1' < ... < '9' < 'a' < ... < 'z' in ASCII.
672
+ */
673
+ /**
674
+ * Generate a key that sorts strictly between `lo` and `hi`.
675
+ *
676
+ * @param lo - Lower bound (null means no lower bound)
677
+ * @param hi - Upper bound (null means no upper bound)
678
+ */
679
+ declare function generateKeyBetween(lo: string | null, hi: string | null): string;
680
+ /**
681
+ * Generate `n` evenly distributed keys between `lo` and `hi` using binary
682
+ * subdivision. Keys grow at O(log n) depth, avoiding unbounded length.
683
+ *
684
+ * @param lo - Lower bound (null means no lower bound)
685
+ * @param hi - Upper bound (null means no upper bound)
686
+ * @param n - Number of keys to generate
687
+ */
688
+ declare function generateNKeysBetween(lo: string | null, hi: string | null, n: number): string[];
689
+ /**
690
+ * Generate initial keys for `n` items starting from an empty list.
691
+ */
692
+ declare function generateInitialKeys(n: number): string[];
693
+ /**
694
+ * Compare two fractional keys lexicographically.
695
+ * Returns negative if a < b, positive if a > b, 0 if equal.
696
+ */
697
+ declare function compareFractionalKeys(a: string, b: string): number;
698
+ /**
699
+ * Assign initial fractional `order` keys to a flat array of blocks.
700
+ *
701
+ * Groups siblings by `parentId`, sorts each group by existing `order`, then
702
+ * assigns evenly-distributed fractional keys via binary subdivision.
703
+ *
704
+ * Use this to migrate an integer-ordered (or unkeyed) dataset to fractional
705
+ * ordering before passing blocks to a `BlockTree` with `orderingStrategy="fractional"`.
706
+ */
707
+ declare function initFractionalOrder<T extends {
708
+ id: string;
709
+ parentId: string | null;
710
+ order: number | string;
711
+ }>(blocks: T[]): T[];
712
+
713
+ 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, 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 OrderingStrategy, type RendererPropsFor, type SensorConfig, TreeRenderer, type TreeRendererProps, type TreeStateContextValue, type TreeStateProviderProps, type UseBlockHistoryOptions, type UseBlockHistoryResult, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSensorConfig, getSubtreeDepth, initFractionalOrder, reparentBlockIndex, reparentMultipleBlocks, useBlockHistory, useConfiguredSensors, weightedVerticalCollision };