dnd-block-tree 0.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.
@@ -0,0 +1,366 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as _dnd_kit_core from '@dnd-kit/core';
4
+ import { UniqueIdentifier, CollisionDetection } from '@dnd-kit/core';
5
+ import * as react_jsx_runtime from 'react/jsx-runtime';
6
+
7
+ /**
8
+ * Base block interface - extend this for your custom block types
9
+ */
10
+ interface BaseBlock {
11
+ id: string;
12
+ type: string;
13
+ parentId: string | null;
14
+ order: number;
15
+ }
16
+ /**
17
+ * Normalized index structure for efficient tree operations
18
+ */
19
+ interface BlockIndex<T extends BaseBlock = BaseBlock> {
20
+ byId: Map<string, T>;
21
+ byParent: Map<string | null, string[]>;
22
+ }
23
+ /**
24
+ * Props passed to non-container block renderers
25
+ */
26
+ interface BlockRendererProps<T extends BaseBlock = BaseBlock> {
27
+ block: T;
28
+ children?: ReactNode;
29
+ isDragging?: boolean;
30
+ isOver?: boolean;
31
+ depth: number;
32
+ }
33
+ /**
34
+ * Props passed to container block renderers (blocks that can have children)
35
+ */
36
+ interface ContainerRendererProps<T extends BaseBlock = BaseBlock> extends BlockRendererProps<T> {
37
+ children: ReactNode;
38
+ isExpanded: boolean;
39
+ onToggleExpand: () => void;
40
+ }
41
+ /**
42
+ * Get the appropriate props type for a renderer based on whether it's a container type
43
+ *
44
+ * @example
45
+ * type SectionProps = RendererPropsFor<MyBlock, 'section', typeof CONTAINER_TYPES>
46
+ * // If CONTAINER_TYPES includes 'section', this is ContainerRendererProps
47
+ * // Otherwise, it's BlockRendererProps
48
+ */
49
+ type RendererPropsFor<T extends BaseBlock, K extends T['type'], C extends readonly string[]> = K extends C[number] ? ContainerRendererProps<T & {
50
+ type: K;
51
+ }> : BlockRendererProps<T & {
52
+ type: K;
53
+ }>;
54
+ /**
55
+ * Map of block types to their renderers with automatic container detection
56
+ *
57
+ * @example
58
+ * const CONTAINER_TYPES = ['section'] as const
59
+ *
60
+ * const renderers: BlockRenderers<MyBlock, typeof CONTAINER_TYPES> = {
61
+ * section: (props) => <Section {...props} />, // props includes isExpanded, onToggleExpand
62
+ * task: (props) => <Task {...props} />, // props is BlockRendererProps
63
+ * }
64
+ */
65
+ type BlockRenderers<T extends BaseBlock = BaseBlock, C extends readonly string[] = readonly string[]> = {
66
+ [K in T['type']]: (props: RendererPropsFor<T, K, C>) => ReactNode;
67
+ };
68
+ /**
69
+ * Internal renderer type used by TreeRenderer (less strict for flexibility)
70
+ */
71
+ type InternalRenderers<T extends BaseBlock = BaseBlock> = {
72
+ [K in T['type']]: (props: BlockRendererProps<T> | ContainerRendererProps<T>) => ReactNode;
73
+ };
74
+ /**
75
+ * Block action types for the reducer
76
+ */
77
+ type BlockAction<T extends BaseBlock> = {
78
+ type: 'ADD_ITEM';
79
+ payload: T;
80
+ } | {
81
+ type: 'DELETE_ITEM';
82
+ payload: {
83
+ id: string;
84
+ };
85
+ } | {
86
+ type: 'SET_ALL';
87
+ payload: T[];
88
+ } | {
89
+ type: 'MOVE_ITEM';
90
+ payload: {
91
+ activeId: UniqueIdentifier;
92
+ targetZone: string;
93
+ };
94
+ } | {
95
+ type: 'INSERT_ITEM';
96
+ payload: {
97
+ item: T;
98
+ parentId: string | null;
99
+ index: number;
100
+ };
101
+ };
102
+ /**
103
+ * Drag overlay renderer
104
+ */
105
+ interface DragOverlayProps$1<T extends BaseBlock = BaseBlock> {
106
+ block: T;
107
+ }
108
+ /**
109
+ * BlockTree configuration options
110
+ */
111
+ interface BlockTreeConfig {
112
+ activationDistance?: number;
113
+ previewDebounce?: number;
114
+ dropZoneHeight?: number;
115
+ }
116
+ /**
117
+ * Block state context value
118
+ */
119
+ interface BlockStateContextValue<T extends BaseBlock = BaseBlock> {
120
+ blocks: T[];
121
+ blockMap: Map<string, T>;
122
+ childrenMap: Map<string | null, T[]>;
123
+ indexMap: Map<string, number>;
124
+ normalizedIndex: BlockIndex<T>;
125
+ createItem: (type: T['type'], parentId?: string | null) => T;
126
+ insertItem: (type: T['type'], referenceId: string, position: 'before' | 'after') => T;
127
+ deleteItem: (id: string) => void;
128
+ moveItem: (activeId: UniqueIdentifier, targetZone: string) => void;
129
+ setAll: (blocks: T[]) => void;
130
+ }
131
+ /**
132
+ * Tree state context value (UI state)
133
+ */
134
+ interface TreeStateContextValue<T extends BaseBlock = BaseBlock> {
135
+ activeId: string | null;
136
+ activeBlock: T | null;
137
+ hoverZone: string | null;
138
+ expandedMap: Record<string, boolean>;
139
+ effectiveBlocks: T[];
140
+ blocksByParent: Map<string | null, T[]>;
141
+ setActiveId: (id: string | null) => void;
142
+ setHoverZone: (zone: string | null) => void;
143
+ toggleExpand: (id: string) => void;
144
+ setExpandAll: (expanded: boolean) => void;
145
+ handleHover: (zoneId: string, parentId: string | null) => void;
146
+ }
147
+ /**
148
+ * Drop zone types
149
+ */
150
+ type DropZoneType = 'before' | 'after' | 'into';
151
+ /**
152
+ * Extract zone type from zone ID
153
+ */
154
+ declare function getDropZoneType(zoneId: string): DropZoneType;
155
+ /**
156
+ * Extract block ID from zone ID
157
+ */
158
+ declare function extractBlockId(zoneId: string): string;
159
+ /**
160
+ * Block state provider props
161
+ */
162
+ interface BlockStateProviderProps<T extends BaseBlock = BaseBlock> {
163
+ children: ReactNode;
164
+ initialBlocks?: T[];
165
+ containerTypes?: readonly string[];
166
+ onChange?: (blocks: T[]) => void;
167
+ }
168
+ /**
169
+ * Tree state provider props
170
+ */
171
+ interface TreeStateProviderProps<T extends BaseBlock = BaseBlock> {
172
+ children: ReactNode;
173
+ blocks: T[];
174
+ blockMap: Map<string, T>;
175
+ }
176
+
177
+ /**
178
+ * Custom collision detection that scores drop zones by distance to nearest edge.
179
+ * Uses edge-distance scoring with a bottom bias for more natural drag behavior.
180
+ *
181
+ * Key features:
182
+ * - Scores by distance to nearest edge (top or bottom) of droppable
183
+ * - Applies -5px bias to elements below pointer midpoint (prefers dropping below)
184
+ * - Returns single winner (lowest score)
185
+ */
186
+ declare const weightedVerticalCollision: CollisionDetection;
187
+ /**
188
+ * Simple closest center collision (fallback)
189
+ */
190
+ declare const closestCenterCollision: CollisionDetection;
191
+
192
+ interface SensorConfig {
193
+ activationDistance?: number;
194
+ }
195
+ /**
196
+ * Create configured sensors with activation distance constraint.
197
+ * The activation distance prevents accidental drags while still allowing clicks.
198
+ *
199
+ * @param config - Sensor configuration
200
+ * @returns Configured sensors for DndContext
201
+ */
202
+ declare function useConfiguredSensors(config?: SensorConfig): _dnd_kit_core.SensorDescriptor<_dnd_kit_core.SensorOptions>[];
203
+ /**
204
+ * Get sensor configuration for manual setup
205
+ */
206
+ declare function getSensorConfig(activationDistance?: number): {
207
+ pointer: {
208
+ activationConstraint: {
209
+ distance: number;
210
+ };
211
+ };
212
+ touch: {
213
+ activationConstraint: {
214
+ distance: number;
215
+ };
216
+ };
217
+ };
218
+
219
+ interface BlockTreeProps<T extends BaseBlock, C extends readonly T['type'][] = readonly T['type'][]> {
220
+ /** Current blocks array */
221
+ blocks: T[];
222
+ /** Block renderers for each type */
223
+ renderers: BlockRenderers<T, C>;
224
+ /** Block types that can have children */
225
+ containerTypes?: C;
226
+ /** Called when blocks are reordered */
227
+ onChange?: (blocks: T[]) => void;
228
+ /** Custom drag overlay renderer */
229
+ dragOverlay?: (block: T) => ReactNode;
230
+ /** Activation distance in pixels (default: 8) */
231
+ activationDistance?: number;
232
+ /** Preview debounce in ms (default: 150) */
233
+ previewDebounce?: number;
234
+ /** Root container className */
235
+ className?: string;
236
+ /** Drop zone className */
237
+ dropZoneClassName?: string;
238
+ /** Active drop zone className */
239
+ dropZoneActiveClassName?: string;
240
+ /** Indent className for nested items */
241
+ indentClassName?: string;
242
+ }
243
+ /**
244
+ * Main BlockTree component
245
+ * Provides drag-and-drop functionality for hierarchical block structures
246
+ */
247
+ 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, }: BlockTreeProps<T, C>): react_jsx_runtime.JSX.Element;
248
+
249
+ interface TreeRendererProps<T extends BaseBlock> {
250
+ blocks: T[];
251
+ blocksByParent: Map<string | null, T[]>;
252
+ parentId: string | null;
253
+ activeId: string | null;
254
+ expandedMap: Record<string, boolean>;
255
+ renderers: InternalRenderers<T>;
256
+ containerTypes: readonly string[];
257
+ onHover: (zoneId: string, parentId: string | null) => void;
258
+ onToggleExpand: (id: string) => void;
259
+ depth?: number;
260
+ dropZoneClassName?: string;
261
+ dropZoneActiveClassName?: string;
262
+ indentClassName?: string;
263
+ rootClassName?: string;
264
+ }
265
+ /**
266
+ * Recursive tree renderer with smart drop zones
267
+ */
268
+ declare function TreeRenderer<T extends BaseBlock>({ blocks, blocksByParent, parentId, activeId, expandedMap, renderers, containerTypes, onHover, onToggleExpand, depth, dropZoneClassName, dropZoneActiveClassName, indentClassName, rootClassName, }: TreeRendererProps<T>): react_jsx_runtime.JSX.Element;
269
+
270
+ interface DropZoneProps {
271
+ id: string;
272
+ parentId: string | null;
273
+ onHover: (zoneId: string, parentId: string | null) => void;
274
+ activeId: string | null;
275
+ className?: string;
276
+ activeClassName?: string;
277
+ height?: number;
278
+ }
279
+ /**
280
+ * Drop zone indicator component
281
+ * Shows where blocks can be dropped
282
+ */
283
+ declare function DropZoneComponent({ id, parentId, onHover, activeId, className, activeClassName, height, }: DropZoneProps): react_jsx_runtime.JSX.Element | null;
284
+ declare const DropZone: react.MemoExoticComponent<typeof DropZoneComponent>;
285
+
286
+ interface DragOverlayProps<T extends BaseBlock> {
287
+ activeBlock: T | null;
288
+ children?: (block: T) => ReactNode;
289
+ }
290
+ /**
291
+ * Default drag overlay component
292
+ * Shows a preview of the dragged item
293
+ */
294
+ declare function DragOverlay<T extends BaseBlock>({ activeBlock, children, }: DragOverlayProps<T>): react_jsx_runtime.JSX.Element;
295
+
296
+ /**
297
+ * Create block state context and hooks
298
+ */
299
+ declare function createBlockState<T extends BaseBlock>(): {
300
+ BlockStateProvider: ({ children, initialBlocks, containerTypes, onChange, }: BlockStateProviderProps<T>) => react_jsx_runtime.JSX.Element;
301
+ useBlockState: () => BlockStateContextValue<T>;
302
+ };
303
+
304
+ interface CreateTreeStateOptions<T extends BaseBlock> {
305
+ previewDebounce?: number;
306
+ containerTypes?: string[];
307
+ }
308
+ /**
309
+ * Create tree state context and hooks
310
+ * Handles UI state: active drag, hover zone, expand/collapse, virtual preview
311
+ */
312
+ declare function createTreeState<T extends BaseBlock>(options?: CreateTreeStateOptions<T>): {
313
+ TreeStateProvider: ({ children, blocks, blockMap }: TreeStateProviderProps<T>) => react_jsx_runtime.JSX.Element;
314
+ useTreeState: () => TreeStateContextValue<T>;
315
+ };
316
+
317
+ /**
318
+ * Clone a Map
319
+ */
320
+ declare function cloneMap<K, V>(map: Map<K, V>): Map<K, V>;
321
+ /**
322
+ * Clone a parent map with arrays
323
+ */
324
+ declare function cloneParentMap(map: Map<string | null, string[]>): Map<string | null, string[]>;
325
+ /**
326
+ * Compute normalized index from flat block array
327
+ */
328
+ declare function computeNormalizedIndex<T extends BaseBlock>(blocks: T[]): BlockIndex<T>;
329
+ /**
330
+ * Build ordered flat array from BlockIndex
331
+ */
332
+ declare function buildOrderedBlocks<T extends BaseBlock>(index: BlockIndex<T>, containerTypes?: readonly string[]): T[];
333
+ /**
334
+ * Reparent a block based on drop zone ID
335
+ *
336
+ * @param state - Current block index
337
+ * @param activeId - ID of the dragged block
338
+ * @param targetZone - Drop zone ID (e.g., "after-uuid", "before-uuid", "into-uuid")
339
+ * @param containerTypes - Block types that can have children
340
+ */
341
+ declare function reparentBlockIndex<T extends BaseBlock>(state: BlockIndex<T>, activeId: UniqueIdentifier, targetZone: string, containerTypes?: readonly string[]): BlockIndex<T>;
342
+ /**
343
+ * Get all descendant IDs of a block
344
+ */
345
+ declare function getDescendantIds<T extends BaseBlock>(state: BlockIndex<T>, parentId: string): Set<string>;
346
+ /**
347
+ * Delete a block and all its descendants
348
+ */
349
+ declare function deleteBlockAndDescendants<T extends BaseBlock>(state: BlockIndex<T>, id: string): BlockIndex<T>;
350
+
351
+ /**
352
+ * Extract UUID from a zone ID by removing the prefix (before-, after-, into-)
353
+ */
354
+ declare function extractUUID(id: string, pattern?: string): string;
355
+ /**
356
+ * Create a debounced function
357
+ */
358
+ declare function debounce<Args extends unknown[]>(fn: (...args: Args) => void, delay: number): ((...args: Args) => void) & {
359
+ cancel: () => void;
360
+ };
361
+ /**
362
+ * Generate a unique ID (simple implementation)
363
+ */
364
+ declare function generateId(): string;
365
+
366
+ export { type BaseBlock, type BlockAction, type BlockIndex, type BlockRendererProps, type BlockRenderers, type BlockStateContextValue, type BlockStateProviderProps, BlockTree, type BlockTreeConfig, type BlockTreeProps, type ContainerRendererProps, DragOverlay, type DragOverlayProps$1 as DragOverlayProps, DropZone, type DropZoneProps, type DropZoneType, type RendererPropsFor, TreeRenderer, type TreeRendererProps, type TreeStateContextValue, type TreeStateProviderProps, buildOrderedBlocks, cloneMap, cloneParentMap, closestCenterCollision, computeNormalizedIndex, createBlockState, createTreeState, debounce, deleteBlockAndDescendants, extractBlockId, extractUUID, generateId, getDescendantIds, getDropZoneType, getSensorConfig, reparentBlockIndex, useConfiguredSensors, weightedVerticalCollision };