dnd-block-tree 0.4.0 → 0.5.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
@@ -15,7 +15,7 @@ A headless React library for building hierarchical drag-and-drop interfaces. Bri
15
15
  - **Stable Drop Zones** - Zones render based on original block positions, not preview state, ensuring consistent drop targets during drag
16
16
  - **Ghost Preview** - Semi-transparent preview shows where blocks will land without affecting zone positions
17
17
  - **Depth-Aware Collision** - Smart algorithm prefers nested zones when cursor is at indented levels, with hysteresis to prevent flickering
18
- - **Mobile & Touch Support** - Separate touch/pointer activation constraints prevent interference with scrolling on mobile devices
18
+ - **Mobile & Touch Support** - Separate touch/pointer activation constraints with configurable `longPressDelay` and optional `hapticFeedback`
19
19
  - **Snapshot-Based Computation** - State captured at drag start. All preview computations use snapshot, ensuring consistent behavior
20
20
  - **Debounced Preview** - 150ms debounced virtual state for smooth drag previews without jitter
21
21
  - **Customizable Drag Rules** - `canDrag` and `canDrop` filters for fine-grained control over drag behavior
@@ -25,6 +25,10 @@ A headless React library for building hierarchical drag-and-drop interfaces. Bri
25
25
  - **Undo/Redo** - Composable `useBlockHistory` hook with past/future stacks for state history management
26
26
  - **Lifecycle Callbacks** - `onBlockAdd`, `onBlockDelete`, `onBlockMove`, `onBeforeMove` middleware, and more
27
27
  - **Fractional Indexing** - Opt-in CRDT-compatible ordering via `orderingStrategy: 'fractional'`
28
+ - **Serialization** - `flatToNested` / `nestedToFlat` converters for flat array ↔ nested tree transforms
29
+ - **SSR Compatible** - `BlockTreeSSR` wrapper for hydration-safe rendering in Next.js and other SSR environments
30
+ - **Animation Support** - CSS expand/collapse transitions via `AnimationConfig` and FLIP reorder animations via `useLayoutAnimation`
31
+ - **Virtual Scrolling** - Windowed rendering for large trees (1000+ blocks) via `virtualize` prop
28
32
 
29
33
  ## Installation
30
34
 
@@ -83,12 +87,17 @@ function App() {
83
87
  | `showDropPreview` | `boolean` | `true` | Show live preview of block at drop position |
84
88
  | `canDrag` | `(block: T) => boolean` | - | Filter which blocks can be dragged |
85
89
  | `canDrop` | `(block, zone, target) => boolean` | - | Filter valid drop targets |
90
+ | `collisionDetection` | `CollisionDetection` | sticky | Custom collision detection algorithm (from `@dnd-kit/core`) |
91
+ | `sensors` | `SensorConfig` | - | Sensor configuration (see [Sensor Config](#sensor-config)) |
92
+ | `animation` | `AnimationConfig` | - | Animation configuration (see [Animation](#animation--transitions)) |
86
93
  | `maxDepth` | `number` | - | Maximum nesting depth (1 = flat, 2 = one level, etc.) |
87
94
  | `keyboardNavigation` | `boolean` | `false` | Enable keyboard navigation with arrow keys |
88
95
  | `multiSelect` | `boolean` | `false` | Enable multi-select with Cmd/Ctrl+Click and Shift+Click |
89
96
  | `selectedIds` | `Set<string>` | - | Externally-controlled selected IDs (for multi-select) |
90
97
  | `onSelectionChange` | `(ids: Set<string>) => void` | - | Called when selection changes |
91
98
  | `orderingStrategy` | `'integer' \| 'fractional'` | `'integer'` | Sibling ordering strategy |
99
+ | `initialExpanded` | `string[] \| 'all' \| 'none'` | `'all'` | Initially expanded container IDs |
100
+ | `virtualize` | `{ itemHeight: number; overscan?: number }` | - | Enable virtual scrolling (see [Virtual Scrolling](#virtual-scrolling)) |
92
101
  | `className` | `string` | - | Root container class |
93
102
  | `dropZoneClassName` | `string` | - | Drop zone class |
94
103
  | `dropZoneActiveClassName` | `string` | - | Active drop zone class |
@@ -109,6 +118,145 @@ function App() {
109
118
  | `onExpandChange` | `(event: ExpandChangeEvent<T>) => void` | Called when expand/collapse changes |
110
119
  | `onHoverChange` | `(event: HoverChangeEvent<T>) => void` | Called when hover zone changes |
111
120
 
121
+ ### Sensor Config
122
+
123
+ ```typescript
124
+ interface SensorConfig {
125
+ /** Distance in pixels before drag starts (default: 8) */
126
+ activationDistance?: number
127
+ /** Delay in ms before drag starts (overrides distance) */
128
+ activationDelay?: number
129
+ /** Tolerance in px for delay-based activation (default: 5) */
130
+ tolerance?: number
131
+ /** Override the default long-press delay for touch sensors (default: 200ms) */
132
+ longPressDelay?: number
133
+ /** Trigger haptic feedback (vibration) on drag start for touch devices */
134
+ hapticFeedback?: boolean
135
+ }
136
+ ```
137
+
138
+ ```tsx
139
+ <BlockTree
140
+ sensors={{
141
+ longPressDelay: 300, // 300ms long-press for touch
142
+ hapticFeedback: true, // Vibrate on drag start
143
+ }}
144
+ ...
145
+ />
146
+ ```
147
+
148
+ ### Animation Config
149
+
150
+ ```typescript
151
+ interface AnimationConfig {
152
+ /** Duration for expand/collapse animations in ms */
153
+ expandDuration?: number
154
+ /** Duration for drag overlay animation in ms */
155
+ dragOverlayDuration?: number
156
+ /** Easing function (CSS timing function, default: 'ease') */
157
+ easing?: string
158
+ }
159
+ ```
160
+
161
+ ### Event Types
162
+
163
+ All event types are exported and generic over your block type `T`.
164
+
165
+ #### DragStartEvent
166
+
167
+ ```typescript
168
+ interface DragStartEvent<T> {
169
+ block: T
170
+ blockId: string
171
+ }
172
+ ```
173
+
174
+ #### DragMoveEvent
175
+
176
+ ```typescript
177
+ interface DragMoveEvent<T> {
178
+ block: T
179
+ blockId: string
180
+ overZone: string | null // Current hover zone ID
181
+ coordinates: { x: number; y: number }
182
+ }
183
+ ```
184
+
185
+ #### DragEndEvent
186
+
187
+ ```typescript
188
+ interface DragEndEvent<T> {
189
+ block: T
190
+ blockId: string
191
+ targetZone: string | null // Zone where block was dropped
192
+ cancelled: boolean
193
+ }
194
+ ```
195
+
196
+ #### BlockMoveEvent
197
+
198
+ ```typescript
199
+ interface BlockMoveEvent<T> {
200
+ block: T
201
+ from: BlockPosition // { parentId, index }
202
+ to: BlockPosition
203
+ blocks: T[] // Full block array after the move
204
+ movedIds: string[] // All moved block IDs (for multi-select)
205
+ }
206
+ ```
207
+
208
+ #### MoveOperation
209
+
210
+ Passed to the `onBeforeMove` middleware:
211
+
212
+ ```typescript
213
+ interface MoveOperation<T> {
214
+ block: T
215
+ from: BlockPosition
216
+ targetZone: string // Drop zone ID (e.g. "after-uuid", "into-uuid")
217
+ }
218
+ ```
219
+
220
+ #### BlockAddEvent
221
+
222
+ ```typescript
223
+ interface BlockAddEvent<T> {
224
+ block: T
225
+ parentId: string | null
226
+ index: number
227
+ }
228
+ ```
229
+
230
+ #### BlockDeleteEvent
231
+
232
+ ```typescript
233
+ interface BlockDeleteEvent<T> {
234
+ block: T
235
+ deletedIds: string[] // Block + all descendant IDs
236
+ parentId: string | null
237
+ }
238
+ ```
239
+
240
+ #### ExpandChangeEvent
241
+
242
+ ```typescript
243
+ interface ExpandChangeEvent<T> {
244
+ block: T
245
+ blockId: string
246
+ expanded: boolean
247
+ }
248
+ ```
249
+
250
+ #### HoverChangeEvent
251
+
252
+ ```typescript
253
+ interface HoverChangeEvent<T> {
254
+ zoneId: string | null
255
+ zoneType: 'before' | 'after' | 'into' | null
256
+ targetBlock: T | null
257
+ }
258
+ ```
259
+
112
260
  ### Types
113
261
 
114
262
  #### BaseBlock
@@ -150,6 +298,182 @@ interface ContainerRendererProps<T extends BaseBlock> extends BlockRendererProps
150
298
  }
151
299
  ```
152
300
 
301
+ #### NestedBlock
302
+
303
+ Tree representation used by serialization helpers:
304
+
305
+ ```typescript
306
+ type NestedBlock<T extends BaseBlock> = Omit<T, 'parentId' | 'order'> & {
307
+ children: NestedBlock<T>[]
308
+ }
309
+ ```
310
+
311
+ ## Serialization Helpers
312
+
313
+ Convert between flat block arrays and nested tree structures:
314
+
315
+ ```tsx
316
+ import { flatToNested, nestedToFlat, type NestedBlock } from 'dnd-block-tree'
317
+
318
+ // Flat array -> nested tree (e.g. for JSON export)
319
+ const nested: NestedBlock<MyBlock>[] = flatToNested(blocks)
320
+
321
+ // Nested tree -> flat array (e.g. for JSON import)
322
+ const flat: MyBlock[] = nestedToFlat(nested)
323
+ ```
324
+
325
+ `flatToNested` groups by `parentId`, sorts siblings by `order`, and recursively builds `children` arrays. `parentId` and `order` are omitted from the output since they're structural.
326
+
327
+ `nestedToFlat` performs a DFS walk, assigning `parentId` and integer `order` on the way down.
328
+
329
+ ## SSR Compatibility
330
+
331
+ For Next.js App Router or other SSR environments, use `BlockTreeSSR` to avoid hydration mismatches:
332
+
333
+ ```tsx
334
+ import { BlockTreeSSR } from 'dnd-block-tree'
335
+
336
+ function Page() {
337
+ return (
338
+ <BlockTreeSSR
339
+ blocks={blocks}
340
+ renderers={renderers}
341
+ containerTypes={CONTAINER_TYPES}
342
+ onChange={setBlocks}
343
+ fallback={<div>Loading tree...</div>}
344
+ />
345
+ )
346
+ }
347
+ ```
348
+
349
+ `BlockTreeSSR` renders the `fallback` (default: `null`) on the server, then mounts the full `BlockTree` after hydration via `useEffect`. All `BlockTree` props are passed through.
350
+
351
+ ```typescript
352
+ interface BlockTreeSSRProps<T, C> extends BlockTreeProps<T, C> {
353
+ fallback?: ReactNode
354
+ }
355
+ ```
356
+
357
+ ## Animation & Transitions
358
+
359
+ ### Expand/Collapse Transitions
360
+
361
+ Pass an `AnimationConfig` to enable CSS transitions on container expand/collapse:
362
+
363
+ ```tsx
364
+ <BlockTree
365
+ animation={{
366
+ expandDuration: 200, // ms
367
+ easing: 'ease-in-out', // CSS timing function
368
+ }}
369
+ ...
370
+ />
371
+ ```
372
+
373
+ ### FLIP Reorder Animation
374
+
375
+ The `useLayoutAnimation` hook provides FLIP-based (First-Last-Invert-Play) animations for block reorder transitions. It's a standalone composable — not built into BlockTree:
376
+
377
+ ```tsx
378
+ import { useLayoutAnimation } from 'dnd-block-tree'
379
+
380
+ function MyTree() {
381
+ const containerRef = useRef<HTMLDivElement>(null)
382
+
383
+ useLayoutAnimation(containerRef, {
384
+ duration: 200, // Transition duration in ms (default: 200)
385
+ easing: 'ease', // CSS easing (default: 'ease')
386
+ selector: '[data-block-id]', // Selector for animated children (default)
387
+ })
388
+
389
+ return (
390
+ <div ref={containerRef}>
391
+ <BlockTree ... />
392
+ </div>
393
+ )
394
+ }
395
+ ```
396
+
397
+ ```typescript
398
+ interface UseLayoutAnimationOptions {
399
+ duration?: number // default: 200
400
+ easing?: string // default: 'ease'
401
+ selector?: string // default: '[data-block-id]'
402
+ }
403
+ ```
404
+
405
+ ## Virtual Scrolling
406
+
407
+ Enable windowed rendering for large trees (1000+ blocks) with the `virtualize` prop:
408
+
409
+ ```tsx
410
+ <BlockTree
411
+ virtualize={{
412
+ itemHeight: 40, // Fixed height of each item in pixels
413
+ overscan: 5, // Extra items rendered outside viewport (default: 5)
414
+ }}
415
+ blocks={blocks}
416
+ ...
417
+ />
418
+ ```
419
+
420
+ When enabled, only visible blocks (plus overscan) are rendered. The tree is wrapped in a scrollable container with a spacer div maintaining correct total height.
421
+
422
+ **Limitations:** Fixed item height only. Variable height items are not supported.
423
+
424
+ ### useVirtualTree Hook
425
+
426
+ For custom virtual scrolling implementations outside of BlockTree:
427
+
428
+ ```tsx
429
+ import { useVirtualTree } from 'dnd-block-tree'
430
+
431
+ const containerRef = useRef<HTMLDivElement>(null)
432
+ const { visibleRange, totalHeight, offsetY } = useVirtualTree({
433
+ containerRef,
434
+ itemCount: 1000,
435
+ itemHeight: 40,
436
+ overscan: 5,
437
+ })
438
+ ```
439
+
440
+ ```typescript
441
+ interface UseVirtualTreeOptions {
442
+ containerRef: React.RefObject<HTMLElement | null>
443
+ itemCount: number
444
+ itemHeight: number
445
+ overscan?: number // default: 5
446
+ }
447
+
448
+ interface UseVirtualTreeResult {
449
+ visibleRange: { start: number; end: number }
450
+ totalHeight: number
451
+ offsetY: number
452
+ }
453
+ ```
454
+
455
+ ## Touch & Mobile
456
+
457
+ Touch support is built-in with sensible defaults (200ms long-press, 5px tolerance). For fine-tuning:
458
+
459
+ ```tsx
460
+ <BlockTree
461
+ sensors={{
462
+ longPressDelay: 300, // Override default 200ms touch activation
463
+ hapticFeedback: true, // Vibrate on drag start (uses navigator.vibrate)
464
+ }}
465
+ ...
466
+ />
467
+ ```
468
+
469
+ The `triggerHaptic` utility is also exported for use in custom components:
470
+
471
+ ```tsx
472
+ import { triggerHaptic } from 'dnd-block-tree'
473
+
474
+ triggerHaptic(10) // Vibrate for 10ms (default)
475
+ ```
476
+
153
477
  ## Undo/Redo
154
478
 
155
479
  The `useBlockHistory` hook provides undo/redo support as a composable layer on top of `BlockTree`:
@@ -222,6 +546,87 @@ Limit nesting depth to prevent deeply nested trees:
222
546
  - `maxDepth={2}` - blocks can nest one level inside containers
223
547
  - When a move would exceed the limit, the drop zone is rejected and the move is a no-op
224
548
 
549
+ ## Move Middleware (`onBeforeMove`)
550
+
551
+ The `onBeforeMove` callback intercepts moves before they are committed. You can use it to validate, transform, or cancel moves:
552
+
553
+ ```tsx
554
+ <BlockTree
555
+ blocks={blocks}
556
+ onChange={setBlocks}
557
+ onBeforeMove={(operation) => {
558
+ // Cancel: return false to prevent the move
559
+ if (operation.block.type === 'locked') {
560
+ return false
561
+ }
562
+
563
+ // Transform: change the target zone
564
+ if (operation.targetZone.startsWith('into-') && someCondition) {
565
+ return { ...operation, targetZone: `after-${extractBlockId(operation.targetZone)}` }
566
+ }
567
+
568
+ // Allow: return void/undefined to proceed as-is
569
+ }}
570
+ />
571
+ ```
572
+
573
+ The middleware receives a `MoveOperation` with the block, its original position (`from`), and the target drop zone. Returning:
574
+ - `false` cancels the move entirely
575
+ - A modified `MoveOperation` transforms the move (e.g. redirect to a different zone)
576
+ - `void` / `undefined` allows the move as-is
577
+
578
+ ## Fractional Indexing
579
+
580
+ By default, siblings are reindexed `0, 1, 2, ...` on every move. For collaborative or CRDT-compatible scenarios, use fractional indexing:
581
+
582
+ ```tsx
583
+ import { BlockTree, initFractionalOrder } from 'dnd-block-tree'
584
+
585
+ // Convert existing blocks to fractional ordering
586
+ const [blocks, setBlocks] = useState(() => initFractionalOrder(initialBlocks))
587
+
588
+ <BlockTree
589
+ blocks={blocks}
590
+ onChange={setBlocks}
591
+ orderingStrategy="fractional"
592
+ />
593
+ ```
594
+
595
+ With fractional ordering, only the moved block receives a new `order` value (a lexicographically sortable string key). Siblings are never reindexed, making it safe for concurrent edits.
596
+
597
+ Related utilities:
598
+
599
+ ```typescript
600
+ import {
601
+ generateKeyBetween, // Generate a key between two existing keys
602
+ generateNKeysBetween, // Generate N keys between two existing keys
603
+ generateInitialKeys, // Generate N evenly-spaced initial keys
604
+ initFractionalOrder, // Convert integer-ordered blocks to fractional
605
+ compareFractionalKeys, // Compare two fractional keys
606
+ } from 'dnd-block-tree'
607
+ ```
608
+
609
+ ## Collision Detection
610
+
611
+ The library ships with three collision detection strategies:
612
+
613
+ ```typescript
614
+ import {
615
+ weightedVerticalCollision, // Default: edge-distance based, depth-aware
616
+ closestCenterCollision, // Simple closest-center algorithm
617
+ createStickyCollision, // Wraps any strategy with hysteresis to prevent flickering
618
+ } from 'dnd-block-tree'
619
+
620
+ // Use a custom collision strategy
621
+ <BlockTree collisionDetection={closestCenterCollision} ... />
622
+
623
+ // Or use the sticky wrapper with a custom threshold (px)
624
+ const collision = createStickyCollision(20)
625
+ <BlockTree collisionDetection={collision} ... />
626
+ ```
627
+
628
+ You can also pass any `CollisionDetection` function from `@dnd-kit/core`.
629
+
225
630
  ## Type Safety
226
631
 
227
632
  The library provides automatic type inference for container vs non-container renderers:
@@ -245,20 +650,56 @@ const renderers: BlockRenderers<MyBlock, typeof CONTAINER_TYPES> = {
245
650
 
246
651
  ## Utilities
247
652
 
248
- The library exports several utility functions:
653
+ The library exports utility functions for tree manipulation, ID generation, and zone parsing:
249
654
 
250
655
  ```typescript
251
656
  import {
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
657
+ // Tree operations
658
+ computeNormalizedIndex, // Convert flat array to { byId, byParent } index
659
+ buildOrderedBlocks, // Convert index back to ordered flat array
660
+ reparentBlockIndex, // Move a single block to a new position
661
+ reparentMultipleBlocks, // Move multiple blocks preserving relative order
662
+ getDescendantIds, // Get all descendant IDs of a block (Set)
663
+ deleteBlockAndDescendants, // Remove a block and all its descendants from index
664
+ getBlockDepth, // Compute depth of a block (root = 1)
665
+ getSubtreeDepth, // Max depth of a subtree (leaf = 1)
666
+
667
+ // Serialization
668
+ flatToNested, // Convert flat block array to nested tree
669
+ nestedToFlat, // Convert nested tree to flat block array
670
+
671
+ // ID / zone helpers
672
+ generateId, // Generate unique block IDs
673
+ extractUUID, // Extract block ID from zone ID string
674
+ getDropZoneType, // Parse zone type: 'before' | 'after' | 'into'
675
+ extractBlockId, // Extract block ID from zone ID (alias)
676
+
677
+ // Touch
678
+ triggerHaptic, // Trigger haptic feedback (navigator.vibrate)
679
+
680
+ // Fractional indexing
681
+ generateKeyBetween, // Generate a fractional key between two keys
682
+ generateNKeysBetween, // Generate N keys between two existing keys
683
+ generateInitialKeys, // Generate N evenly-spaced initial keys
684
+ initFractionalOrder, // Convert integer-ordered blocks to fractional
685
+ compareFractionalKeys, // Compare two fractional keys
686
+
687
+ // Collision detection
688
+ weightedVerticalCollision, // Edge-distance collision, depth-aware
689
+ closestCenterCollision, // Simple closest-center collision
690
+ createStickyCollision, // Hysteresis wrapper to prevent flickering
691
+
692
+ // Hooks
693
+ useBlockHistory, // Undo/redo state management
694
+ useLayoutAnimation, // FLIP-based reorder animations
695
+ useVirtualTree, // Virtual scrolling primitives
696
+
697
+ // Components
698
+ BlockTree, // Main drag-and-drop tree component
699
+ BlockTreeSSR, // SSR-safe wrapper
700
+ TreeRenderer, // Recursive tree renderer
701
+ DropZone, // Individual drop zone
702
+ DragOverlay, // Drag overlay wrapper
262
703
  } from 'dnd-block-tree'
263
704
  ```
264
705
 
package/dist/index.d.mts CHANGED
@@ -213,6 +213,10 @@ interface SensorConfig {
213
213
  activationDistance?: number;
214
214
  activationDelay?: number;
215
215
  tolerance?: number;
216
+ /** Override the default long-press delay (200ms) for touch sensors */
217
+ longPressDelay?: number;
218
+ /** Trigger haptic feedback (vibration) on drag start for touch devices */
219
+ hapticFeedback?: boolean;
216
220
  }
217
221
  /**
218
222
  * Drop zone configuration
@@ -466,12 +470,19 @@ interface BlockTreeProps<T extends BaseBlock, C extends readonly T['type'][] = r
466
470
  selectedIds?: Set<string>;
467
471
  /** Called when selection changes (for multi-select) */
468
472
  onSelectionChange?: (selectedIds: Set<string>) => void;
473
+ /** Enable virtual scrolling for large trees (fixed item height only) */
474
+ virtualize?: {
475
+ /** Fixed height of each item in pixels */
476
+ itemHeight: number;
477
+ /** Number of extra items to render outside the visible range (default: 5) */
478
+ overscan?: number;
479
+ };
469
480
  }
470
481
  /**
471
482
  * Main BlockTree component
472
483
  * Provides drag-and-drop functionality for hierarchical block structures
473
484
  */
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;
485
+ 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, animation, initialExpanded, orderingStrategy, maxDepth, keyboardNavigation, multiSelect, selectedIds: externalSelectedIds, onSelectionChange, virtualize, }: BlockTreeProps<T, C>): react_jsx_runtime.JSX.Element;
475
486
 
476
487
  interface TreeRendererProps<T extends BaseBlock> {
477
488
  blocks: T[];
@@ -502,11 +513,15 @@ interface TreeRendererProps<T extends BaseBlock> {
502
513
  selectedIds?: Set<string>;
503
514
  /** Click handler for multi-select */
504
515
  onBlockClick?: (blockId: string, event: React.MouseEvent) => void;
516
+ /** Animation configuration */
517
+ animation?: AnimationConfig;
518
+ /** When virtual scrolling is active, only render blocks in this set */
519
+ virtualVisibleIds?: Set<string> | null;
505
520
  }
506
521
  /**
507
522
  * Recursive tree renderer with smart drop zones
508
523
  */
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;
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;
510
525
 
511
526
  interface DropZoneProps {
512
527
  id: string;
@@ -536,6 +551,17 @@ interface DragOverlayProps<T extends BaseBlock> {
536
551
  */
537
552
  declare function DragOverlay<T extends BaseBlock>({ activeBlock, children, selectedCount, }: DragOverlayProps<T>): react_jsx_runtime.JSX.Element;
538
553
 
554
+ interface BlockTreeSSRProps<T extends BaseBlock, C extends readonly T['type'][] = readonly T['type'][]> extends BlockTreeProps<T, C> {
555
+ /** Content to render before hydration completes (default: null) */
556
+ fallback?: ReactNode;
557
+ }
558
+ /**
559
+ * Hydration-safe wrapper for BlockTree in SSR environments.
560
+ * Renders the fallback (or nothing) on the server and during initial hydration,
561
+ * then mounts the full BlockTree after the client has hydrated.
562
+ */
563
+ declare function BlockTreeSSR<T extends BaseBlock, C extends readonly T['type'][] = readonly T['type'][]>({ fallback, ...props }: BlockTreeSSRProps<T, C>): react_jsx_runtime.JSX.Element;
564
+
539
565
  /**
540
566
  * Create block state context and hooks
541
567
  */
@@ -588,6 +614,58 @@ interface UseBlockHistoryResult<T extends BaseBlock> {
588
614
  */
589
615
  declare function useBlockHistory<T extends BaseBlock>(initialBlocks: T[], options?: UseBlockHistoryOptions): UseBlockHistoryResult<T>;
590
616
 
617
+ interface UseLayoutAnimationOptions {
618
+ /** Duration of the transition in ms (default: 200) */
619
+ duration?: number;
620
+ /** CSS easing function (default: 'ease') */
621
+ easing?: string;
622
+ /** CSS selector for animated children (default: '[data-block-id]') */
623
+ selector?: string;
624
+ }
625
+ /**
626
+ * FLIP-based layout animation hook for reorder transitions.
627
+ *
628
+ * Captures block positions before render (layout effect cleanup),
629
+ * computes the delta after render, and applies a CSS transform transition.
630
+ *
631
+ * Usage:
632
+ * ```tsx
633
+ * const containerRef = useRef<HTMLDivElement>(null)
634
+ * useLayoutAnimation(containerRef, { duration: 200 })
635
+ * return <div ref={containerRef}>...</div>
636
+ * ```
637
+ */
638
+ declare function useLayoutAnimation(containerRef: React.RefObject<HTMLElement | null>, options?: UseLayoutAnimationOptions): void;
639
+
640
+ interface UseVirtualTreeOptions {
641
+ /** Ref to the scrollable container element */
642
+ containerRef: React.RefObject<HTMLElement | null>;
643
+ /** Total number of items in the tree */
644
+ itemCount: number;
645
+ /** Fixed height of each item in pixels */
646
+ itemHeight: number;
647
+ /** Number of extra items to render outside the visible range (default: 5) */
648
+ overscan?: number;
649
+ }
650
+ interface UseVirtualTreeResult {
651
+ /** Start and end indices of the visible range (inclusive) */
652
+ visibleRange: {
653
+ start: number;
654
+ end: number;
655
+ };
656
+ /** Total height of all items for the spacer div */
657
+ totalHeight: number;
658
+ /** Offset from top for the first rendered item */
659
+ offsetY: number;
660
+ }
661
+ /**
662
+ * Lightweight fixed-height virtual scrolling hook for tree lists.
663
+ *
664
+ * Tracks scroll position on the container and computes which items
665
+ * should be rendered based on the viewport and overscan.
666
+ */
667
+ declare function useVirtualTree({ containerRef, itemCount, itemHeight, overscan, }: UseVirtualTreeOptions): UseVirtualTreeResult;
668
+
591
669
  /**
592
670
  * Clone a Map
593
671
  */
@@ -659,6 +737,29 @@ declare function debounce<Args extends unknown[]>(fn: (...args: Args) => void, d
659
737
  * Generate a unique ID (simple implementation)
660
738
  */
661
739
  declare function generateId(): string;
740
+ /**
741
+ * Trigger haptic feedback (vibration) on supported devices.
742
+ * Safe to call in any environment — no-ops when `navigator.vibrate` is unavailable.
743
+ */
744
+ declare function triggerHaptic(durationMs?: number): void;
745
+
746
+ /**
747
+ * A nested/tree representation of a block.
748
+ * Omits `parentId` and `order` since they are reconstructed during flattening.
749
+ */
750
+ type NestedBlock<T extends BaseBlock> = Omit<T, 'parentId' | 'order'> & {
751
+ children: NestedBlock<T>[];
752
+ };
753
+ /**
754
+ * Convert a flat block array into a nested tree structure.
755
+ * Groups blocks by parentId, sorts siblings by order, and recursively builds children arrays.
756
+ */
757
+ declare function flatToNested<T extends BaseBlock>(blocks: T[]): NestedBlock<T>[];
758
+ /**
759
+ * Convert a nested tree structure back to a flat block array.
760
+ * DFS walk assigning parentId and integer order on the way down.
761
+ */
762
+ declare function nestedToFlat<T extends BaseBlock>(nested: NestedBlock<T>[]): T[];
662
763
 
663
764
  /**
664
765
  * Fractional indexing utilities
@@ -710,4 +811,4 @@ declare function initFractionalOrder<T extends {
710
811
  order: number | string;
711
812
  }>(blocks: T[]): T[];
712
813
 
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 };
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 };