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 +121 -8
- package/dist/index.d.mts +195 -12
- package/dist/index.d.ts +195 -12
- package/dist/index.js +652 -119
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +644 -120
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# dnd-block-tree
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/dnd-block-tree)
|
|
4
|
+
[](https://www.npmjs.com/package/dnd-block-tree)
|
|
5
|
+
[](https://bundlephobia.com/package/dnd-block-tree)
|
|
6
|
+
[](https://github.com/thesandybridge/dnd-block-tree/actions/workflows/ci.yml)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](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
|
-
- **
|
|
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,
|
|
146
|
-
buildOrderedBlocks,
|
|
147
|
-
reparentBlockIndex,
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|