dnd-block-tree 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -7
- package/dist/index.d.mts +75 -59
- package/dist/index.d.ts +75 -59
- package/dist/index.js +92 -27
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +93 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
# dnd-block-tree
|
|
2
2
|
|
|
3
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
4
|
[](https://github.com/thesandybridge/dnd-block-tree/actions/workflows/ci.yml)
|
|
7
|
-
[](https://www.typescriptlang.org/)
|
|
9
|
-
[](https://dnd-block-tree.vercel.app)
|
|
5
|
+
[](https://blocktree.sandybridge.io)
|
|
10
6
|
|
|
11
7
|
A headless React library for building hierarchical drag-and-drop interfaces. Bring your own components, we handle the complexity.
|
|
12
8
|
|
|
@@ -517,7 +513,7 @@ Enable accessible tree navigation with the `keyboardNavigation` prop:
|
|
|
517
513
|
| Home | Focus first block |
|
|
518
514
|
| End | Focus last block |
|
|
519
515
|
|
|
520
|
-
Blocks receive `data-block-id` and `tabIndex` attributes for focus management, and the tree root gets `role="tree"`.
|
|
516
|
+
Blocks receive `data-block-id` and `tabIndex` attributes for focus management, and the tree root gets `role="tree"`. Each block element includes WAI-ARIA TreeView attributes: `aria-level`, `aria-posinset`, `aria-setsize`, `aria-expanded` (containers only), and `aria-selected`.
|
|
521
517
|
|
|
522
518
|
## Multi-Select Drag
|
|
523
519
|
|
|
@@ -689,6 +685,7 @@ import {
|
|
|
689
685
|
deleteBlockAndDescendants, // Remove a block and all its descendants from index
|
|
690
686
|
getBlockDepth, // Compute depth of a block (root = 1)
|
|
691
687
|
getSubtreeDepth, // Max depth of a subtree (leaf = 1)
|
|
688
|
+
validateBlockTree, // Validate tree for cycles, orphans, stale refs
|
|
692
689
|
|
|
693
690
|
// Serialization
|
|
694
691
|
flatToNested, // Convert flat block array to nested tree
|
|
@@ -716,10 +713,19 @@ import {
|
|
|
716
713
|
createStickyCollision, // Hysteresis wrapper with snapshot support
|
|
717
714
|
type SnapshotRectsRef, // Ref type for frozen zone rects
|
|
718
715
|
|
|
716
|
+
// Internal helpers
|
|
717
|
+
cloneMap, // Clone a Map
|
|
718
|
+
cloneParentMap, // Deep clone a parent->children Map
|
|
719
|
+
debounce, // Debounce with cancel()
|
|
720
|
+
|
|
719
721
|
// Hooks
|
|
722
|
+
createBlockState, // Factory for block state context + provider
|
|
723
|
+
createTreeState, // Factory for tree UI state context + provider
|
|
720
724
|
useBlockHistory, // Undo/redo state management
|
|
721
725
|
useLayoutAnimation, // FLIP-based reorder animations
|
|
722
726
|
useVirtualTree, // Virtual scrolling primitives
|
|
727
|
+
useConfiguredSensors, // Configure dnd-kit sensors
|
|
728
|
+
getSensorConfig, // Get sensor config from SensorConfig
|
|
723
729
|
|
|
724
730
|
// Components
|
|
725
731
|
BlockTree, // Main drag-and-drop tree component
|
|
@@ -732,7 +738,7 @@ import {
|
|
|
732
738
|
|
|
733
739
|
## Demo
|
|
734
740
|
|
|
735
|
-
Check out the [live demo](https://
|
|
741
|
+
Check out the [live demo](https://blocktree.sandybridge.io) to see the library in action with two example use cases:
|
|
736
742
|
|
|
737
743
|
- **Productivity** - Sections, tasks, and notes with undo/redo, max depth control, and keyboard navigation
|
|
738
744
|
- **File System** - Folders and files
|
package/dist/index.d.mts
CHANGED
|
@@ -389,6 +389,78 @@ interface TreeStateProviderProps<T extends BaseBlock = BaseBlock> {
|
|
|
389
389
|
blockMap: Map<string, T>;
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Clone a Map
|
|
394
|
+
*/
|
|
395
|
+
declare function cloneMap<K, V>(map: Map<K, V>): Map<K, V>;
|
|
396
|
+
/**
|
|
397
|
+
* Clone a parent map with arrays
|
|
398
|
+
*/
|
|
399
|
+
declare function cloneParentMap(map: Map<string | null, string[]>): Map<string | null, string[]>;
|
|
400
|
+
/**
|
|
401
|
+
* Compute normalized index from flat block array.
|
|
402
|
+
*
|
|
403
|
+
* With `orderingStrategy: 'fractional'`, siblings are sorted by their `order`
|
|
404
|
+
* field (lexicographic). With `'integer'` (default), the input order is preserved.
|
|
405
|
+
*/
|
|
406
|
+
declare function computeNormalizedIndex<T extends BaseBlock>(blocks: T[], orderingStrategy?: OrderingStrategy): BlockIndex<T>;
|
|
407
|
+
/**
|
|
408
|
+
* Build ordered flat array from BlockIndex.
|
|
409
|
+
*
|
|
410
|
+
* With `'integer'` ordering (default), assigns sequential `order: 0, 1, 2, …`.
|
|
411
|
+
* With `'fractional'` ordering, preserves existing `order` values — the moved
|
|
412
|
+
* block already has its new fractional key set by `reparentBlockIndex`.
|
|
413
|
+
*/
|
|
414
|
+
declare function buildOrderedBlocks<T extends BaseBlock>(index: BlockIndex<T>, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy): T[];
|
|
415
|
+
/**
|
|
416
|
+
* Reparent a block based on drop zone ID.
|
|
417
|
+
*
|
|
418
|
+
* @param state - Current block index
|
|
419
|
+
* @param activeId - ID of the dragged block
|
|
420
|
+
* @param targetZone - Drop zone ID (e.g., "after-uuid", "before-uuid", "into-uuid")
|
|
421
|
+
* @param containerTypes - Block types that can have children
|
|
422
|
+
* @param orderingStrategy - Whether to assign a fractional key to the moved block
|
|
423
|
+
*/
|
|
424
|
+
declare function reparentBlockIndex<T extends BaseBlock>(state: BlockIndex<T>, activeId: UniqueIdentifier, targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
|
|
425
|
+
/**
|
|
426
|
+
* Compute the depth of a block by walking its parentId chain.
|
|
427
|
+
* Root-level blocks have depth 1.
|
|
428
|
+
*/
|
|
429
|
+
declare function getBlockDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
|
|
430
|
+
/**
|
|
431
|
+
* Compute the maximum depth of a subtree rooted at blockId (inclusive).
|
|
432
|
+
* A leaf block returns 1.
|
|
433
|
+
*/
|
|
434
|
+
declare function getSubtreeDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string, visited?: Set<string>): number;
|
|
435
|
+
/**
|
|
436
|
+
* Get all descendant IDs of a block
|
|
437
|
+
*/
|
|
438
|
+
declare function getDescendantIds<T extends BaseBlock>(state: BlockIndex<T>, parentId: string): Set<string>;
|
|
439
|
+
/**
|
|
440
|
+
* Reparent multiple blocks to a target zone, preserving their relative order.
|
|
441
|
+
* The first block in `blockIds` is treated as the primary (anchor) block.
|
|
442
|
+
*/
|
|
443
|
+
declare function reparentMultipleBlocks<T extends BaseBlock>(state: BlockIndex<T>, blockIds: string[], targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
|
|
444
|
+
/**
|
|
445
|
+
* Result of validating a block tree
|
|
446
|
+
*/
|
|
447
|
+
interface TreeValidationResult {
|
|
448
|
+
valid: boolean;
|
|
449
|
+
issues: string[];
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Validate a block tree index for structural integrity.
|
|
453
|
+
* Checks for cycles, orphans (parentId references non-existent block),
|
|
454
|
+
* and stale refs (byParent lists IDs not present in byId).
|
|
455
|
+
*
|
|
456
|
+
* Opt-in utility — not called automatically.
|
|
457
|
+
*/
|
|
458
|
+
declare function validateBlockTree<T extends BaseBlock>(index: BlockIndex<T>): TreeValidationResult;
|
|
459
|
+
/**
|
|
460
|
+
* Delete a block and all its descendants
|
|
461
|
+
*/
|
|
462
|
+
declare function deleteBlockAndDescendants<T extends BaseBlock>(state: BlockIndex<T>, id: string): BlockIndex<T>;
|
|
463
|
+
|
|
392
464
|
type SnapshotRectsRef = {
|
|
393
465
|
current: Map<UniqueIdentifier, DOMRect> | null;
|
|
394
466
|
};
|
|
@@ -527,7 +599,8 @@ interface TreeRendererProps<T extends BaseBlock> {
|
|
|
527
599
|
/**
|
|
528
600
|
* Recursive tree renderer with smart drop zones
|
|
529
601
|
*/
|
|
530
|
-
declare function
|
|
602
|
+
declare function TreeRendererInner<T extends BaseBlock>({ blocks, blocksByParent, parentId, activeId, expandedMap, renderers, containerTypes, onHover, onToggleExpand, depth, dropZoneClassName, dropZoneActiveClassName, indentClassName, rootClassName, canDrag, previewPosition, draggedBlock, focusedId, selectedIds, onBlockClick, animation, virtualVisibleIds, }: TreeRendererProps<T>): react_jsx_runtime.JSX.Element;
|
|
603
|
+
declare const TreeRenderer: typeof TreeRendererInner;
|
|
531
604
|
|
|
532
605
|
interface DropZoneProps {
|
|
533
606
|
id: string;
|
|
@@ -672,63 +745,6 @@ interface UseVirtualTreeResult {
|
|
|
672
745
|
*/
|
|
673
746
|
declare function useVirtualTree({ containerRef, itemCount, itemHeight, overscan, }: UseVirtualTreeOptions): UseVirtualTreeResult;
|
|
674
747
|
|
|
675
|
-
/**
|
|
676
|
-
* Clone a Map
|
|
677
|
-
*/
|
|
678
|
-
declare function cloneMap<K, V>(map: Map<K, V>): Map<K, V>;
|
|
679
|
-
/**
|
|
680
|
-
* Clone a parent map with arrays
|
|
681
|
-
*/
|
|
682
|
-
declare function cloneParentMap(map: Map<string | null, string[]>): Map<string | null, string[]>;
|
|
683
|
-
/**
|
|
684
|
-
* Compute normalized index from flat block array.
|
|
685
|
-
*
|
|
686
|
-
* With `orderingStrategy: 'fractional'`, siblings are sorted by their `order`
|
|
687
|
-
* field (lexicographic). With `'integer'` (default), the input order is preserved.
|
|
688
|
-
*/
|
|
689
|
-
declare function computeNormalizedIndex<T extends BaseBlock>(blocks: T[], orderingStrategy?: OrderingStrategy): BlockIndex<T>;
|
|
690
|
-
/**
|
|
691
|
-
* Build ordered flat array from BlockIndex.
|
|
692
|
-
*
|
|
693
|
-
* With `'integer'` ordering (default), assigns sequential `order: 0, 1, 2, …`.
|
|
694
|
-
* With `'fractional'` ordering, preserves existing `order` values — the moved
|
|
695
|
-
* block already has its new fractional key set by `reparentBlockIndex`.
|
|
696
|
-
*/
|
|
697
|
-
declare function buildOrderedBlocks<T extends BaseBlock>(index: BlockIndex<T>, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy): T[];
|
|
698
|
-
/**
|
|
699
|
-
* Reparent a block based on drop zone ID.
|
|
700
|
-
*
|
|
701
|
-
* @param state - Current block index
|
|
702
|
-
* @param activeId - ID of the dragged block
|
|
703
|
-
* @param targetZone - Drop zone ID (e.g., "after-uuid", "before-uuid", "into-uuid")
|
|
704
|
-
* @param containerTypes - Block types that can have children
|
|
705
|
-
* @param orderingStrategy - Whether to assign a fractional key to the moved block
|
|
706
|
-
*/
|
|
707
|
-
declare function reparentBlockIndex<T extends BaseBlock>(state: BlockIndex<T>, activeId: UniqueIdentifier, targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
|
|
708
|
-
/**
|
|
709
|
-
* Compute the depth of a block by walking its parentId chain.
|
|
710
|
-
* Root-level blocks have depth 1.
|
|
711
|
-
*/
|
|
712
|
-
declare function getBlockDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
|
|
713
|
-
/**
|
|
714
|
-
* Compute the maximum depth of a subtree rooted at blockId (inclusive).
|
|
715
|
-
* A leaf block returns 1.
|
|
716
|
-
*/
|
|
717
|
-
declare function getSubtreeDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
|
|
718
|
-
/**
|
|
719
|
-
* Get all descendant IDs of a block
|
|
720
|
-
*/
|
|
721
|
-
declare function getDescendantIds<T extends BaseBlock>(state: BlockIndex<T>, parentId: string): Set<string>;
|
|
722
|
-
/**
|
|
723
|
-
* Reparent multiple blocks to a target zone, preserving their relative order.
|
|
724
|
-
* The first block in `blockIds` is treated as the primary (anchor) block.
|
|
725
|
-
*/
|
|
726
|
-
declare function reparentMultipleBlocks<T extends BaseBlock>(state: BlockIndex<T>, blockIds: string[], targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
|
|
727
|
-
/**
|
|
728
|
-
* Delete a block and all its descendants
|
|
729
|
-
*/
|
|
730
|
-
declare function deleteBlockAndDescendants<T extends BaseBlock>(state: BlockIndex<T>, id: string): BlockIndex<T>;
|
|
731
|
-
|
|
732
748
|
/**
|
|
733
749
|
* Extract UUID from a zone ID by removing the prefix (before-, after-, into-, end-)
|
|
734
750
|
*/
|
|
@@ -817,4 +833,4 @@ declare function initFractionalOrder<T extends {
|
|
|
817
833
|
order: number | string;
|
|
818
834
|
}>(blocks: T[]): T[];
|
|
819
835
|
|
|
820
|
-
export { type AnimationConfig, type AutoExpandConfig, type BaseBlock, type BlockAction, type BlockAddEvent, type BlockDeleteEvent, type BlockIndex, type BlockMoveEvent, type BlockPosition, type BlockRendererProps, type BlockRenderers, type BlockStateContextValue, type BlockStateProviderProps, BlockTree, type BlockTreeCallbacks, type BlockTreeConfig, type BlockTreeCustomization, type BlockTreeProps, BlockTreeSSR, type BlockTreeSSRProps, type CanDragFn, type CanDropFn, type ContainerRendererProps, type DragEndEvent, type DragMoveEvent, DragOverlay, type DragOverlayProps$1 as DragOverlayProps, type DragStartEvent, DropZone, type DropZoneConfig, type DropZoneProps, type DropZoneType, type ExpandChangeEvent, type HoverChangeEvent, type IdGeneratorFn, type InternalRenderers, type MoveOperation, type NestedBlock, type OrderingStrategy, type RendererPropsFor, type SensorConfig, type SnapshotRectsRef, TreeRenderer, type TreeRendererProps, type TreeStateContextValue, type TreeStateProviderProps, type UseBlockHistoryOptions, type UseBlockHistoryResult, type UseLayoutAnimationOptions, type UseVirtualTreeOptions, type UseVirtualTreeResult, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSensorConfig, getSubtreeDepth, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, triggerHaptic, useBlockHistory, useConfiguredSensors, useLayoutAnimation, useVirtualTree, weightedVerticalCollision };
|
|
836
|
+
export { type AnimationConfig, type AutoExpandConfig, type BaseBlock, type BlockAction, type BlockAddEvent, type BlockDeleteEvent, type BlockIndex, type BlockMoveEvent, type BlockPosition, type BlockRendererProps, type BlockRenderers, type BlockStateContextValue, type BlockStateProviderProps, BlockTree, type BlockTreeCallbacks, type BlockTreeConfig, type BlockTreeCustomization, type BlockTreeProps, BlockTreeSSR, type BlockTreeSSRProps, type CanDragFn, type CanDropFn, type ContainerRendererProps, type DragEndEvent, type DragMoveEvent, DragOverlay, type DragOverlayProps$1 as DragOverlayProps, type DragStartEvent, DropZone, type DropZoneConfig, type DropZoneProps, type DropZoneType, type ExpandChangeEvent, type HoverChangeEvent, type IdGeneratorFn, type InternalRenderers, type MoveOperation, type NestedBlock, type OrderingStrategy, type RendererPropsFor, type SensorConfig, type SnapshotRectsRef, TreeRenderer, type TreeRendererProps, type TreeStateContextValue, type TreeStateProviderProps, type TreeValidationResult, type UseBlockHistoryOptions, type UseBlockHistoryResult, type UseLayoutAnimationOptions, type UseVirtualTreeOptions, type UseVirtualTreeResult, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSensorConfig, getSubtreeDepth, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, triggerHaptic, useBlockHistory, useConfiguredSensors, useLayoutAnimation, useVirtualTree, validateBlockTree, weightedVerticalCollision };
|
package/dist/index.d.ts
CHANGED
|
@@ -389,6 +389,78 @@ interface TreeStateProviderProps<T extends BaseBlock = BaseBlock> {
|
|
|
389
389
|
blockMap: Map<string, T>;
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Clone a Map
|
|
394
|
+
*/
|
|
395
|
+
declare function cloneMap<K, V>(map: Map<K, V>): Map<K, V>;
|
|
396
|
+
/**
|
|
397
|
+
* Clone a parent map with arrays
|
|
398
|
+
*/
|
|
399
|
+
declare function cloneParentMap(map: Map<string | null, string[]>): Map<string | null, string[]>;
|
|
400
|
+
/**
|
|
401
|
+
* Compute normalized index from flat block array.
|
|
402
|
+
*
|
|
403
|
+
* With `orderingStrategy: 'fractional'`, siblings are sorted by their `order`
|
|
404
|
+
* field (lexicographic). With `'integer'` (default), the input order is preserved.
|
|
405
|
+
*/
|
|
406
|
+
declare function computeNormalizedIndex<T extends BaseBlock>(blocks: T[], orderingStrategy?: OrderingStrategy): BlockIndex<T>;
|
|
407
|
+
/**
|
|
408
|
+
* Build ordered flat array from BlockIndex.
|
|
409
|
+
*
|
|
410
|
+
* With `'integer'` ordering (default), assigns sequential `order: 0, 1, 2, …`.
|
|
411
|
+
* With `'fractional'` ordering, preserves existing `order` values — the moved
|
|
412
|
+
* block already has its new fractional key set by `reparentBlockIndex`.
|
|
413
|
+
*/
|
|
414
|
+
declare function buildOrderedBlocks<T extends BaseBlock>(index: BlockIndex<T>, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy): T[];
|
|
415
|
+
/**
|
|
416
|
+
* Reparent a block based on drop zone ID.
|
|
417
|
+
*
|
|
418
|
+
* @param state - Current block index
|
|
419
|
+
* @param activeId - ID of the dragged block
|
|
420
|
+
* @param targetZone - Drop zone ID (e.g., "after-uuid", "before-uuid", "into-uuid")
|
|
421
|
+
* @param containerTypes - Block types that can have children
|
|
422
|
+
* @param orderingStrategy - Whether to assign a fractional key to the moved block
|
|
423
|
+
*/
|
|
424
|
+
declare function reparentBlockIndex<T extends BaseBlock>(state: BlockIndex<T>, activeId: UniqueIdentifier, targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
|
|
425
|
+
/**
|
|
426
|
+
* Compute the depth of a block by walking its parentId chain.
|
|
427
|
+
* Root-level blocks have depth 1.
|
|
428
|
+
*/
|
|
429
|
+
declare function getBlockDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
|
|
430
|
+
/**
|
|
431
|
+
* Compute the maximum depth of a subtree rooted at blockId (inclusive).
|
|
432
|
+
* A leaf block returns 1.
|
|
433
|
+
*/
|
|
434
|
+
declare function getSubtreeDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string, visited?: Set<string>): number;
|
|
435
|
+
/**
|
|
436
|
+
* Get all descendant IDs of a block
|
|
437
|
+
*/
|
|
438
|
+
declare function getDescendantIds<T extends BaseBlock>(state: BlockIndex<T>, parentId: string): Set<string>;
|
|
439
|
+
/**
|
|
440
|
+
* Reparent multiple blocks to a target zone, preserving their relative order.
|
|
441
|
+
* The first block in `blockIds` is treated as the primary (anchor) block.
|
|
442
|
+
*/
|
|
443
|
+
declare function reparentMultipleBlocks<T extends BaseBlock>(state: BlockIndex<T>, blockIds: string[], targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
|
|
444
|
+
/**
|
|
445
|
+
* Result of validating a block tree
|
|
446
|
+
*/
|
|
447
|
+
interface TreeValidationResult {
|
|
448
|
+
valid: boolean;
|
|
449
|
+
issues: string[];
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Validate a block tree index for structural integrity.
|
|
453
|
+
* Checks for cycles, orphans (parentId references non-existent block),
|
|
454
|
+
* and stale refs (byParent lists IDs not present in byId).
|
|
455
|
+
*
|
|
456
|
+
* Opt-in utility — not called automatically.
|
|
457
|
+
*/
|
|
458
|
+
declare function validateBlockTree<T extends BaseBlock>(index: BlockIndex<T>): TreeValidationResult;
|
|
459
|
+
/**
|
|
460
|
+
* Delete a block and all its descendants
|
|
461
|
+
*/
|
|
462
|
+
declare function deleteBlockAndDescendants<T extends BaseBlock>(state: BlockIndex<T>, id: string): BlockIndex<T>;
|
|
463
|
+
|
|
392
464
|
type SnapshotRectsRef = {
|
|
393
465
|
current: Map<UniqueIdentifier, DOMRect> | null;
|
|
394
466
|
};
|
|
@@ -527,7 +599,8 @@ interface TreeRendererProps<T extends BaseBlock> {
|
|
|
527
599
|
/**
|
|
528
600
|
* Recursive tree renderer with smart drop zones
|
|
529
601
|
*/
|
|
530
|
-
declare function
|
|
602
|
+
declare function TreeRendererInner<T extends BaseBlock>({ blocks, blocksByParent, parentId, activeId, expandedMap, renderers, containerTypes, onHover, onToggleExpand, depth, dropZoneClassName, dropZoneActiveClassName, indentClassName, rootClassName, canDrag, previewPosition, draggedBlock, focusedId, selectedIds, onBlockClick, animation, virtualVisibleIds, }: TreeRendererProps<T>): react_jsx_runtime.JSX.Element;
|
|
603
|
+
declare const TreeRenderer: typeof TreeRendererInner;
|
|
531
604
|
|
|
532
605
|
interface DropZoneProps {
|
|
533
606
|
id: string;
|
|
@@ -672,63 +745,6 @@ interface UseVirtualTreeResult {
|
|
|
672
745
|
*/
|
|
673
746
|
declare function useVirtualTree({ containerRef, itemCount, itemHeight, overscan, }: UseVirtualTreeOptions): UseVirtualTreeResult;
|
|
674
747
|
|
|
675
|
-
/**
|
|
676
|
-
* Clone a Map
|
|
677
|
-
*/
|
|
678
|
-
declare function cloneMap<K, V>(map: Map<K, V>): Map<K, V>;
|
|
679
|
-
/**
|
|
680
|
-
* Clone a parent map with arrays
|
|
681
|
-
*/
|
|
682
|
-
declare function cloneParentMap(map: Map<string | null, string[]>): Map<string | null, string[]>;
|
|
683
|
-
/**
|
|
684
|
-
* Compute normalized index from flat block array.
|
|
685
|
-
*
|
|
686
|
-
* With `orderingStrategy: 'fractional'`, siblings are sorted by their `order`
|
|
687
|
-
* field (lexicographic). With `'integer'` (default), the input order is preserved.
|
|
688
|
-
*/
|
|
689
|
-
declare function computeNormalizedIndex<T extends BaseBlock>(blocks: T[], orderingStrategy?: OrderingStrategy): BlockIndex<T>;
|
|
690
|
-
/**
|
|
691
|
-
* Build ordered flat array from BlockIndex.
|
|
692
|
-
*
|
|
693
|
-
* With `'integer'` ordering (default), assigns sequential `order: 0, 1, 2, …`.
|
|
694
|
-
* With `'fractional'` ordering, preserves existing `order` values — the moved
|
|
695
|
-
* block already has its new fractional key set by `reparentBlockIndex`.
|
|
696
|
-
*/
|
|
697
|
-
declare function buildOrderedBlocks<T extends BaseBlock>(index: BlockIndex<T>, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy): T[];
|
|
698
|
-
/**
|
|
699
|
-
* Reparent a block based on drop zone ID.
|
|
700
|
-
*
|
|
701
|
-
* @param state - Current block index
|
|
702
|
-
* @param activeId - ID of the dragged block
|
|
703
|
-
* @param targetZone - Drop zone ID (e.g., "after-uuid", "before-uuid", "into-uuid")
|
|
704
|
-
* @param containerTypes - Block types that can have children
|
|
705
|
-
* @param orderingStrategy - Whether to assign a fractional key to the moved block
|
|
706
|
-
*/
|
|
707
|
-
declare function reparentBlockIndex<T extends BaseBlock>(state: BlockIndex<T>, activeId: UniqueIdentifier, targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
|
|
708
|
-
/**
|
|
709
|
-
* Compute the depth of a block by walking its parentId chain.
|
|
710
|
-
* Root-level blocks have depth 1.
|
|
711
|
-
*/
|
|
712
|
-
declare function getBlockDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
|
|
713
|
-
/**
|
|
714
|
-
* Compute the maximum depth of a subtree rooted at blockId (inclusive).
|
|
715
|
-
* A leaf block returns 1.
|
|
716
|
-
*/
|
|
717
|
-
declare function getSubtreeDepth<T extends BaseBlock>(index: BlockIndex<T>, blockId: string): number;
|
|
718
|
-
/**
|
|
719
|
-
* Get all descendant IDs of a block
|
|
720
|
-
*/
|
|
721
|
-
declare function getDescendantIds<T extends BaseBlock>(state: BlockIndex<T>, parentId: string): Set<string>;
|
|
722
|
-
/**
|
|
723
|
-
* Reparent multiple blocks to a target zone, preserving their relative order.
|
|
724
|
-
* The first block in `blockIds` is treated as the primary (anchor) block.
|
|
725
|
-
*/
|
|
726
|
-
declare function reparentMultipleBlocks<T extends BaseBlock>(state: BlockIndex<T>, blockIds: string[], targetZone: string, containerTypes?: readonly string[], orderingStrategy?: OrderingStrategy, maxDepth?: number): BlockIndex<T>;
|
|
727
|
-
/**
|
|
728
|
-
* Delete a block and all its descendants
|
|
729
|
-
*/
|
|
730
|
-
declare function deleteBlockAndDescendants<T extends BaseBlock>(state: BlockIndex<T>, id: string): BlockIndex<T>;
|
|
731
|
-
|
|
732
748
|
/**
|
|
733
749
|
* Extract UUID from a zone ID by removing the prefix (before-, after-, into-, end-)
|
|
734
750
|
*/
|
|
@@ -817,4 +833,4 @@ declare function initFractionalOrder<T extends {
|
|
|
817
833
|
order: number | string;
|
|
818
834
|
}>(blocks: T[]): T[];
|
|
819
835
|
|
|
820
|
-
export { type AnimationConfig, type AutoExpandConfig, type BaseBlock, type BlockAction, type BlockAddEvent, type BlockDeleteEvent, type BlockIndex, type BlockMoveEvent, type BlockPosition, type BlockRendererProps, type BlockRenderers, type BlockStateContextValue, type BlockStateProviderProps, BlockTree, type BlockTreeCallbacks, type BlockTreeConfig, type BlockTreeCustomization, type BlockTreeProps, BlockTreeSSR, type BlockTreeSSRProps, type CanDragFn, type CanDropFn, type ContainerRendererProps, type DragEndEvent, type DragMoveEvent, DragOverlay, type DragOverlayProps$1 as DragOverlayProps, type DragStartEvent, DropZone, type DropZoneConfig, type DropZoneProps, type DropZoneType, type ExpandChangeEvent, type HoverChangeEvent, type IdGeneratorFn, type InternalRenderers, type MoveOperation, type NestedBlock, type OrderingStrategy, type RendererPropsFor, type SensorConfig, type SnapshotRectsRef, TreeRenderer, type TreeRendererProps, type TreeStateContextValue, type TreeStateProviderProps, type UseBlockHistoryOptions, type UseBlockHistoryResult, type UseLayoutAnimationOptions, type UseVirtualTreeOptions, type UseVirtualTreeResult, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSensorConfig, getSubtreeDepth, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, triggerHaptic, useBlockHistory, useConfiguredSensors, useLayoutAnimation, useVirtualTree, weightedVerticalCollision };
|
|
836
|
+
export { type AnimationConfig, type AutoExpandConfig, type BaseBlock, type BlockAction, type BlockAddEvent, type BlockDeleteEvent, type BlockIndex, type BlockMoveEvent, type BlockPosition, type BlockRendererProps, type BlockRenderers, type BlockStateContextValue, type BlockStateProviderProps, BlockTree, type BlockTreeCallbacks, type BlockTreeConfig, type BlockTreeCustomization, type BlockTreeProps, BlockTreeSSR, type BlockTreeSSRProps, type CanDragFn, type CanDropFn, type ContainerRendererProps, type DragEndEvent, type DragMoveEvent, DragOverlay, type DragOverlayProps$1 as DragOverlayProps, type DragStartEvent, DropZone, type DropZoneConfig, type DropZoneProps, type DropZoneType, type ExpandChangeEvent, type HoverChangeEvent, type IdGeneratorFn, type InternalRenderers, type MoveOperation, type NestedBlock, type OrderingStrategy, type RendererPropsFor, type SensorConfig, type SnapshotRectsRef, TreeRenderer, type TreeRendererProps, type TreeStateContextValue, type TreeStateProviderProps, type TreeValidationResult, type UseBlockHistoryOptions, type UseBlockHistoryResult, type UseLayoutAnimationOptions, type UseVirtualTreeOptions, type UseVirtualTreeResult, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, compareFractionalKeys, computeNormalizedIndex, createBlockState, createStickyCollision, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, flatToNested, generateId, generateInitialKeys, generateKeyBetween, generateNKeysBetween, getBlockDepth, getDescendantIds, getDropZoneType, getSensorConfig, getSubtreeDepth, initFractionalOrder, nestedToFlat, reparentBlockIndex, reparentMultipleBlocks, triggerHaptic, useBlockHistory, useConfiguredSensors, useLayoutAnimation, useVirtualTree, validateBlockTree, weightedVerticalCollision };
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,12 @@ function extractBlockId(zoneId) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// src/core/collision.ts
|
|
18
|
+
function collisionValue(d) {
|
|
19
|
+
return d.data.value;
|
|
20
|
+
}
|
|
21
|
+
function collisionLeft(d) {
|
|
22
|
+
return d.data.left;
|
|
23
|
+
}
|
|
18
24
|
function computeCollisionScores(droppableContainers, collisionRect, snapshotRects) {
|
|
19
25
|
const pointerX = collisionRect.left + collisionRect.width / 2;
|
|
20
26
|
const pointerY = collisionRect.top + collisionRect.height / 2;
|
|
@@ -43,11 +49,7 @@ function computeCollisionScores(droppableContainers, collisionRect, snapshotRect
|
|
|
43
49
|
}
|
|
44
50
|
};
|
|
45
51
|
}).filter((c) => c !== null);
|
|
46
|
-
candidates.sort((a, b) =>
|
|
47
|
-
const aValue = a.data.value;
|
|
48
|
-
const bValue = b.data.value;
|
|
49
|
-
return aValue - bValue;
|
|
50
|
-
});
|
|
52
|
+
candidates.sort((a, b) => collisionValue(a) - collisionValue(b));
|
|
51
53
|
return candidates;
|
|
52
54
|
}
|
|
53
55
|
var weightedVerticalCollision = ({
|
|
@@ -68,13 +70,13 @@ function createStickyCollision(threshold = 15, snapshotRef) {
|
|
|
68
70
|
const candidates = computeCollisionScores(droppableContainers, collisionRect, snapshotRef?.current);
|
|
69
71
|
if (candidates.length === 0) return [];
|
|
70
72
|
const bestCandidate = candidates[0];
|
|
71
|
-
const bestScore = bestCandidate
|
|
73
|
+
const bestScore = collisionValue(bestCandidate);
|
|
72
74
|
if (currentZoneId !== null) {
|
|
73
75
|
const currentCandidate = candidates.find((c) => c.id === currentZoneId);
|
|
74
76
|
if (currentCandidate) {
|
|
75
|
-
const currentScore = currentCandidate
|
|
76
|
-
const currentLeft = currentCandidate
|
|
77
|
-
const bestLeft = bestCandidate
|
|
77
|
+
const currentScore = collisionValue(currentCandidate);
|
|
78
|
+
const currentLeft = collisionLeft(currentCandidate);
|
|
79
|
+
const bestLeft = collisionLeft(bestCandidate);
|
|
78
80
|
const crossDepth = Math.abs(currentLeft - bestLeft) > 20;
|
|
79
81
|
const effectiveThreshold = crossDepth ? threshold * 0.25 : threshold;
|
|
80
82
|
if (currentScore - bestScore < effectiveThreshold) {
|
|
@@ -113,11 +115,7 @@ var closestCenterCollision = ({
|
|
|
113
115
|
}
|
|
114
116
|
};
|
|
115
117
|
}).filter((c) => c !== null);
|
|
116
|
-
candidates.sort((a, b) =>
|
|
117
|
-
const aValue = a.data.value;
|
|
118
|
-
const bValue = b.data.value;
|
|
119
|
-
return aValue - bValue;
|
|
120
|
-
});
|
|
118
|
+
candidates.sort((a, b) => collisionValue(a) - collisionValue(b));
|
|
121
119
|
return candidates.slice(0, 1);
|
|
122
120
|
};
|
|
123
121
|
var DEFAULT_ACTIVATION_DISTANCE = 8;
|
|
@@ -248,7 +246,12 @@ function DraggableBlock({
|
|
|
248
246
|
disabled,
|
|
249
247
|
focusedId,
|
|
250
248
|
isSelected,
|
|
251
|
-
onBlockClick
|
|
249
|
+
onBlockClick,
|
|
250
|
+
isContainer,
|
|
251
|
+
isExpanded,
|
|
252
|
+
depth,
|
|
253
|
+
posInSet,
|
|
254
|
+
setSize
|
|
252
255
|
}) {
|
|
253
256
|
const { attributes, listeners, setNodeRef, isDragging } = core.useDraggable({
|
|
254
257
|
id: block.id,
|
|
@@ -267,11 +270,16 @@ function DraggableBlock({
|
|
|
267
270
|
"data-selected": isSelected || void 0,
|
|
268
271
|
style: { touchAction: "none", minWidth: 0, outline: "none" },
|
|
269
272
|
role: "treeitem",
|
|
273
|
+
"aria-level": depth + 1,
|
|
274
|
+
"aria-posinset": posInSet,
|
|
275
|
+
"aria-setsize": setSize,
|
|
276
|
+
"aria-expanded": isContainer ? isExpanded : void 0,
|
|
277
|
+
"aria-selected": isSelected ?? void 0,
|
|
270
278
|
children: children({ isDragging })
|
|
271
279
|
}
|
|
272
280
|
);
|
|
273
281
|
}
|
|
274
|
-
function
|
|
282
|
+
function TreeRendererInner({
|
|
275
283
|
blocks,
|
|
276
284
|
blocksByParent,
|
|
277
285
|
parentId,
|
|
@@ -341,6 +349,11 @@ function TreeRenderer({
|
|
|
341
349
|
focusedId,
|
|
342
350
|
isSelected: selectedIds?.has(block.id),
|
|
343
351
|
onBlockClick,
|
|
352
|
+
isContainer,
|
|
353
|
+
isExpanded,
|
|
354
|
+
depth,
|
|
355
|
+
posInSet: originalIndex + 1,
|
|
356
|
+
setSize: items.length,
|
|
344
357
|
children: ({ isDragging }) => {
|
|
345
358
|
if (isContainer) {
|
|
346
359
|
const expandStyle = animation?.expandDuration ? {
|
|
@@ -425,6 +438,7 @@ function TreeRenderer({
|
|
|
425
438
|
)
|
|
426
439
|
] });
|
|
427
440
|
}
|
|
441
|
+
var TreeRenderer = react.memo(TreeRendererInner);
|
|
428
442
|
function DragOverlay({
|
|
429
443
|
activeBlock,
|
|
430
444
|
children,
|
|
@@ -657,6 +671,9 @@ function reparentBlockIndex(state, activeId, targetZone, containerTypes = [], or
|
|
|
657
671
|
if (parentDepth + subtreeDepth > maxDepth) return state;
|
|
658
672
|
}
|
|
659
673
|
if (dragged.id === zoneTargetId) return state;
|
|
674
|
+
if (newParentId !== null && getDescendantIds(state, dragged.id).has(newParentId)) {
|
|
675
|
+
return state;
|
|
676
|
+
}
|
|
660
677
|
const oldList = byParent.get(oldParentId) ?? [];
|
|
661
678
|
const currentIndexInOldParent = oldList.indexOf(dragged.id);
|
|
662
679
|
const preNewList = byParent.get(newParentId) ?? [];
|
|
@@ -709,19 +726,24 @@ function reparentBlockIndex(state, activeId, targetZone, containerTypes = [], or
|
|
|
709
726
|
function getBlockDepth(index, blockId) {
|
|
710
727
|
let depth = 0;
|
|
711
728
|
let current = blockId;
|
|
729
|
+
const visited = /* @__PURE__ */ new Set();
|
|
712
730
|
while (current !== null) {
|
|
731
|
+
if (visited.has(current)) break;
|
|
732
|
+
visited.add(current);
|
|
713
733
|
depth++;
|
|
714
734
|
const block = index.byId.get(current);
|
|
715
735
|
current = block?.parentId ?? null;
|
|
716
736
|
}
|
|
717
737
|
return depth;
|
|
718
738
|
}
|
|
719
|
-
function getSubtreeDepth(index, blockId) {
|
|
739
|
+
function getSubtreeDepth(index, blockId, visited = /* @__PURE__ */ new Set()) {
|
|
740
|
+
if (visited.has(blockId)) return 0;
|
|
741
|
+
visited.add(blockId);
|
|
720
742
|
const children = index.byParent.get(blockId) ?? [];
|
|
721
743
|
if (children.length === 0) return 1;
|
|
722
744
|
let max = 0;
|
|
723
745
|
for (const childId of children) {
|
|
724
|
-
max = Math.max(max, getSubtreeDepth(index, childId));
|
|
746
|
+
max = Math.max(max, getSubtreeDepth(index, childId, visited));
|
|
725
747
|
}
|
|
726
748
|
return 1 + max;
|
|
727
749
|
}
|
|
@@ -748,6 +770,40 @@ function reparentMultipleBlocks(state, blockIds, targetZone, containerTypes = []
|
|
|
748
770
|
}
|
|
749
771
|
return result;
|
|
750
772
|
}
|
|
773
|
+
function validateBlockTree(index) {
|
|
774
|
+
const issues = [];
|
|
775
|
+
for (const [id] of index.byId) {
|
|
776
|
+
const visited = /* @__PURE__ */ new Set();
|
|
777
|
+
let current = id;
|
|
778
|
+
while (current !== null) {
|
|
779
|
+
if (visited.has(current)) {
|
|
780
|
+
issues.push(`Cycle detected: block "${id}" has a circular parentId chain`);
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
visited.add(current);
|
|
784
|
+
const block = index.byId.get(current);
|
|
785
|
+
current = block?.parentId ?? null;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
for (const [id, block] of index.byId) {
|
|
789
|
+
if (block.parentId !== null && !index.byId.has(block.parentId)) {
|
|
790
|
+
issues.push(`Orphan: block "${id}" references non-existent parent "${block.parentId}"`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
for (const [parentId, childIds] of index.byParent) {
|
|
794
|
+
for (const childId of childIds) {
|
|
795
|
+
if (!index.byId.has(childId)) {
|
|
796
|
+
issues.push(`Stale ref: byParent key "${parentId}" lists non-existent block "${childId}"`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (issues.length > 0) {
|
|
801
|
+
for (const issue of issues) {
|
|
802
|
+
console.warn(`[dnd-block-tree] ${issue}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return { valid: issues.length === 0, issues };
|
|
806
|
+
}
|
|
751
807
|
function deleteBlockAndDescendants(state, id) {
|
|
752
808
|
const byId = cloneMap(state.byId);
|
|
753
809
|
const byParent = cloneParentMap(state.byParent);
|
|
@@ -770,7 +826,12 @@ function getBlockPosition(blocks, blockId) {
|
|
|
770
826
|
}
|
|
771
827
|
function computeInitialExpanded(blocks, containerTypes, initialExpanded) {
|
|
772
828
|
if (initialExpanded === "none") {
|
|
773
|
-
|
|
829
|
+
const expandedMap2 = {};
|
|
830
|
+
const containers2 = blocks.filter((b) => containerTypes.includes(b.type));
|
|
831
|
+
for (const container of containers2) {
|
|
832
|
+
expandedMap2[container.id] = false;
|
|
833
|
+
}
|
|
834
|
+
return expandedMap2;
|
|
774
835
|
}
|
|
775
836
|
const expandedMap = {};
|
|
776
837
|
const containers = blocks.filter((b) => containerTypes.includes(b.type));
|
|
@@ -887,14 +948,17 @@ function BlockTree({
|
|
|
887
948
|
onDragMove?.(event);
|
|
888
949
|
}, 50)
|
|
889
950
|
).current;
|
|
890
|
-
const originalIndex =
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
)
|
|
897
|
-
|
|
951
|
+
const originalIndex = react.useMemo(
|
|
952
|
+
() => computeNormalizedIndex(blocks, orderingStrategy),
|
|
953
|
+
[blocks, orderingStrategy]
|
|
954
|
+
);
|
|
955
|
+
const blocksByParent = react.useMemo(() => {
|
|
956
|
+
const map = /* @__PURE__ */ new Map();
|
|
957
|
+
for (const [parentId, ids] of originalIndex.byParent.entries()) {
|
|
958
|
+
map.set(parentId, ids.map((id) => originalIndex.byId.get(id)).filter(Boolean));
|
|
959
|
+
}
|
|
960
|
+
return map;
|
|
961
|
+
}, [originalIndex]);
|
|
898
962
|
const focusedIdRef = react.useRef(null);
|
|
899
963
|
const rootRef = react.useRef(null);
|
|
900
964
|
const lastClickedIdRef = react.useRef(null);
|
|
@@ -1956,6 +2020,7 @@ exports.useBlockHistory = useBlockHistory;
|
|
|
1956
2020
|
exports.useConfiguredSensors = useConfiguredSensors;
|
|
1957
2021
|
exports.useLayoutAnimation = useLayoutAnimation;
|
|
1958
2022
|
exports.useVirtualTree = useVirtualTree;
|
|
2023
|
+
exports.validateBlockTree = validateBlockTree;
|
|
1959
2024
|
exports.weightedVerticalCollision = weightedVerticalCollision;
|
|
1960
2025
|
//# sourceMappingURL=index.js.map
|
|
1961
2026
|
//# sourceMappingURL=index.js.map
|