@dnd-block-tree/svelte 2.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.
Files changed (50) hide show
  1. package/README.md +62 -0
  2. package/dist/bridge.d.ts +13 -0
  3. package/dist/bridge.d.ts.map +1 -0
  4. package/dist/bridge.js +55 -0
  5. package/dist/components/BlockTree.svelte +368 -0
  6. package/dist/components/BlockTree.svelte.d.ts +32 -0
  7. package/dist/components/BlockTree.svelte.d.ts.map +1 -0
  8. package/dist/components/BlockTreeDevTools.svelte +54 -0
  9. package/dist/components/BlockTreeDevTools.svelte.d.ts +12 -0
  10. package/dist/components/BlockTreeDevTools.svelte.d.ts.map +1 -0
  11. package/dist/components/BlockTreeSSR.svelte +22 -0
  12. package/dist/components/BlockTreeSSR.svelte.d.ts +9 -0
  13. package/dist/components/BlockTreeSSR.svelte.d.ts.map +1 -0
  14. package/dist/components/DragOverlay.svelte +48 -0
  15. package/dist/components/DragOverlay.svelte.d.ts +11 -0
  16. package/dist/components/DragOverlay.svelte.d.ts.map +1 -0
  17. package/dist/components/DraggableBlock.svelte +43 -0
  18. package/dist/components/DraggableBlock.svelte.d.ts +17 -0
  19. package/dist/components/DraggableBlock.svelte.d.ts.map +1 -0
  20. package/dist/components/DropZone.svelte +50 -0
  21. package/dist/components/DropZone.svelte.d.ts +13 -0
  22. package/dist/components/DropZone.svelte.d.ts.map +1 -0
  23. package/dist/components/GhostPreview.svelte +13 -0
  24. package/dist/components/GhostPreview.svelte.d.ts +8 -0
  25. package/dist/components/GhostPreview.svelte.d.ts.map +1 -0
  26. package/dist/components/TreeRenderer.svelte +197 -0
  27. package/dist/components/TreeRenderer.svelte.d.ts +35 -0
  28. package/dist/components/TreeRenderer.svelte.d.ts.map +1 -0
  29. package/dist/index.d.ts +22 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +20 -0
  32. package/dist/state/block-history.svelte.d.ts +18 -0
  33. package/dist/state/block-history.svelte.d.ts.map +1 -0
  34. package/dist/state/block-history.svelte.js +30 -0
  35. package/dist/state/block-state.svelte.d.ts +27 -0
  36. package/dist/state/block-state.svelte.d.ts.map +1 -0
  37. package/dist/state/block-state.svelte.js +91 -0
  38. package/dist/state/tree-state.svelte.d.ts +39 -0
  39. package/dist/state/tree-state.svelte.d.ts.map +1 -0
  40. package/dist/state/tree-state.svelte.js +118 -0
  41. package/dist/types.d.ts +46 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +1 -0
  44. package/dist/utils/haptic.d.ts +6 -0
  45. package/dist/utils/haptic.d.ts.map +1 -0
  46. package/dist/utils/haptic.js +9 -0
  47. package/dist/utils/sensors.d.ts +11 -0
  48. package/dist/utils/sensors.d.ts.map +1 -0
  49. package/dist/utils/sensors.js +10 -0
  50. package/package.json +56 -0
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # @dnd-block-tree/svelte
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@dnd-block-tree/svelte.svg)](https://www.npmjs.com/package/@dnd-block-tree/svelte)
4
+
5
+ Svelte 5 adapter for [dnd-block-tree](https://github.com/thesandybridge/dnd-block-tree) — components, state factories, and [@dnd-kit/svelte](https://dndkit.com/) integration for building hierarchical drag-and-drop interfaces.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @dnd-block-tree/svelte @dnd-kit/svelte @dnd-kit/dom svelte
11
+ ```
12
+
13
+ Requires **Svelte 5.29+**, **@dnd-kit/svelte 0.3+**, and **@dnd-kit/dom 0.3+**.
14
+
15
+ ## What's Included
16
+
17
+ - **Components** — `BlockTree`, `BlockTreeSSR`, `TreeRenderer`, `DropZone`, `DraggableBlock`, `DragOverlay`, `GhostPreview`, `BlockTreeDevTools`
18
+ - **State Factories** — `createBlockState`, `createTreeState`, `createBlockHistory` (runes-based)
19
+ - **Collision Bridge** — `adaptCollisionDetection` to bridge core's `CoreCollisionDetection` to @dnd-kit/svelte
20
+ - **Re-exports** — all types and utilities from `@dnd-block-tree/core`
21
+
22
+ ## Quick Example
23
+
24
+ ```svelte
25
+ <script lang="ts">
26
+ import { BlockTree, type BaseBlock } from '@dnd-block-tree/svelte'
27
+
28
+ interface MyBlock extends BaseBlock {
29
+ type: 'section' | 'task'
30
+ title: string
31
+ }
32
+
33
+ let blocks = $state<MyBlock[]>([
34
+ { id: '1', type: 'section', title: 'Tasks', parentId: null, order: 0 },
35
+ { id: '2', type: 'task', title: 'Do something', parentId: '1', order: 0 },
36
+ ])
37
+ </script>
38
+
39
+ <BlockTree
40
+ {blocks}
41
+ containerTypes={['section']}
42
+ onChange={(b) => blocks = b}
43
+ >
44
+ {#snippet renderBlock({ block, children, isExpanded, onToggleExpand })}
45
+ <div>
46
+ {#if onToggleExpand}
47
+ <button onclick={onToggleExpand}>{isExpanded ? '▼' : '▶'}</button>
48
+ {/if}
49
+ {block.title}
50
+ {#if children}{@render children()}{/if}
51
+ </div>
52
+ {/snippet}
53
+ </BlockTree>
54
+ ```
55
+
56
+ ## Documentation
57
+
58
+ Full docs at **[blocktree.sandybridge.io](https://blocktree.sandybridge.io/docs)**.
59
+
60
+ ## License
61
+
62
+ MIT
@@ -0,0 +1,13 @@
1
+ import type { CoreCollisionDetection } from '@dnd-block-tree/core';
2
+ /**
3
+ * Adapt a framework-agnostic CoreCollisionDetection into a @dnd-kit/dom CollisionDetection.
4
+ *
5
+ * @dnd-kit/dom collision detection receives a DragOperation and returns collisions.
6
+ * We bridge by extracting droppable rects and the pointer rect, running the core
7
+ * detector, and mapping results back.
8
+ */
9
+ export declare function adaptCollisionDetection(coreDetector: CoreCollisionDetection): (args: {
10
+ droppables: any[];
11
+ dragOperation: any;
12
+ }) => any[];
13
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EAIvB,MAAM,sBAAsB,CAAA;AAE7B;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,sBAAsB,GACnC,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,GAAG,EAAE,CAAC;IAAC,aAAa,EAAE,GAAG,CAAA;CAAE,KAAK,GAAG,EAAE,CAgD5D"}
package/dist/bridge.js ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Adapt a framework-agnostic CoreCollisionDetection into a @dnd-kit/dom CollisionDetection.
3
+ *
4
+ * @dnd-kit/dom collision detection receives a DragOperation and returns collisions.
5
+ * We bridge by extracting droppable rects and the pointer rect, running the core
6
+ * detector, and mapping results back.
7
+ */
8
+ export function adaptCollisionDetection(coreDetector) {
9
+ return ({ droppables, dragOperation }) => {
10
+ const pointerPosition = dragOperation?.position?.current;
11
+ if (!pointerPosition)
12
+ return [];
13
+ // Build collision candidates from droppable elements
14
+ const candidates = [];
15
+ for (const droppable of droppables) {
16
+ const el = droppable.element;
17
+ if (!el)
18
+ continue;
19
+ const domRect = el.getBoundingClientRect();
20
+ candidates.push({
21
+ id: String(droppable.id),
22
+ rect: {
23
+ top: domRect.top,
24
+ left: domRect.left,
25
+ width: domRect.width,
26
+ height: domRect.height,
27
+ right: domRect.right,
28
+ bottom: domRect.bottom,
29
+ },
30
+ });
31
+ }
32
+ // Build pointer rect
33
+ const pointerRect = {
34
+ top: pointerPosition.y,
35
+ left: pointerPosition.x,
36
+ width: 1,
37
+ height: 1,
38
+ right: pointerPosition.x + 1,
39
+ bottom: pointerPosition.y + 1,
40
+ };
41
+ // Call core detector
42
+ const results = coreDetector(candidates, pointerRect);
43
+ // Map results back to droppable references
44
+ return results.map(result => {
45
+ const droppable = droppables.find((d) => String(d.id) === result.id);
46
+ if (!droppable)
47
+ return null;
48
+ return {
49
+ id: result.id,
50
+ value: result.value,
51
+ droppable,
52
+ };
53
+ }).filter(Boolean);
54
+ };
55
+ }
@@ -0,0 +1,368 @@
1
+ <script lang="ts">
2
+ import { DragDropProvider } from '@dnd-kit/svelte'
3
+ import type {
4
+ BaseBlock,
5
+ BlockTreeCallbacks,
6
+ BlockPosition,
7
+ DragStartEvent,
8
+ DragMoveEvent,
9
+ DragEndEvent,
10
+ BlockMoveEvent,
11
+ MoveOperation,
12
+ ExpandChangeEvent,
13
+ HoverChangeEvent,
14
+ DropZoneType,
15
+ Rect,
16
+ SnapshotRectsRef,
17
+ } from '@dnd-block-tree/core'
18
+ import {
19
+ getDropZoneType,
20
+ extractBlockId,
21
+ createStickyCollision,
22
+ computeNormalizedIndex,
23
+ reparentBlockIndex,
24
+ reparentMultipleBlocks,
25
+ buildOrderedBlocks,
26
+ debounce,
27
+ } from '@dnd-block-tree/core'
28
+ import type { BlockTreeCustomization } from '../types'
29
+ import { adaptCollisionDetection } from '../bridge'
30
+ import { triggerHaptic } from '../utils/haptic'
31
+ import TreeRenderer from './TreeRenderer.svelte'
32
+ import DragOverlay from './DragOverlay.svelte'
33
+ import type { Snippet } from 'svelte'
34
+
35
+ interface Props extends BlockTreeCallbacks<BaseBlock>, BlockTreeCustomization<BaseBlock> {
36
+ blocks: BaseBlock[]
37
+ containerTypes?: readonly string[]
38
+ onChange?: (blocks: BaseBlock[]) => void
39
+ renderBlock: Snippet<[{
40
+ block: BaseBlock
41
+ isDragging: boolean
42
+ depth: number
43
+ isExpanded: boolean
44
+ onToggleExpand: (() => void) | null
45
+ children: Snippet | null
46
+ }]>
47
+ dragOverlay?: Snippet<[BaseBlock]>
48
+ activationDistance?: number
49
+ previewDebounce?: number
50
+ showDropPreview?: boolean
51
+ multiSelect?: boolean
52
+ selectedIds?: Set<string>
53
+ onSelectionChange?: (selectedIds: Set<string>) => void
54
+ dropZoneClass?: string
55
+ dropZoneActiveClass?: string
56
+ class?: string
57
+ }
58
+
59
+ let {
60
+ blocks,
61
+ containerTypes = [],
62
+ onChange,
63
+ renderBlock,
64
+ dragOverlay,
65
+ activationDistance = 8,
66
+ previewDebounce = 150,
67
+ showDropPreview = true,
68
+ class: className = '',
69
+ dropZoneClass = '',
70
+ dropZoneActiveClass = '',
71
+ // Callbacks
72
+ onDragStart,
73
+ onDragMove,
74
+ onDragEnd,
75
+ onDragCancel,
76
+ onBeforeMove,
77
+ onBlockMove,
78
+ onExpandChange,
79
+ onHoverChange,
80
+ // Customization
81
+ canDrag,
82
+ canDrop,
83
+ collisionDetection: userCollisionDetection,
84
+ sensors: sensorConfig,
85
+ animation,
86
+ initialExpanded,
87
+ orderingStrategy = 'integer',
88
+ maxDepth,
89
+ multiSelect = false,
90
+ selectedIds: externalSelectedIds,
91
+ onSelectionChange,
92
+ }: Props = $props()
93
+
94
+ // Sticky collision detection bridged from core
95
+ const coreStickyDetector = createStickyCollision(20)
96
+ const adaptedCollision = adaptCollisionDetection(
97
+ userCollisionDetection ?? coreStickyDetector
98
+ )
99
+
100
+ // Internal state
101
+ let activeId = $state<string | null>(null)
102
+ let hoverZone = $state<string | null>(null)
103
+ let expandedMap = $state<Record<string, boolean>>(
104
+ computeInitialExpanded(blocks, containerTypes, initialExpanded)
105
+ )
106
+ let virtualState = $state<ReturnType<typeof computeNormalizedIndex> | null>(null)
107
+ let isDragging = $state(false)
108
+
109
+ // Non-reactive refs
110
+ let initialBlocksRef: BaseBlock[] = []
111
+ let cachedReorderRef: { targetId: string; reorderedBlocks: BaseBlock[] } | null = null
112
+ let fromPositionRef: BlockPosition | null = null
113
+ let draggedIdsRef: string[] = []
114
+
115
+ // Selection
116
+ let internalSelectedIds = $state(new Set<string>())
117
+ const selectedIds = $derived(externalSelectedIds ?? internalSelectedIds)
118
+
119
+ function setSelectedIds(ids: Set<string>) {
120
+ if (onSelectionChange) {
121
+ onSelectionChange(ids)
122
+ } else {
123
+ internalSelectedIds = ids
124
+ }
125
+ }
126
+
127
+ // Computed
128
+ const originalIndex = $derived(computeNormalizedIndex(blocks, orderingStrategy))
129
+
130
+ const blocksByParent = $derived.by(() => {
131
+ const effectiveIdx = virtualState ?? originalIndex
132
+ const map = new Map<string | null, BaseBlock[]>()
133
+ for (const [parentId, ids] of effectiveIdx.byParent.entries()) {
134
+ map.set(parentId, ids.map(id => effectiveIdx.byId.get(id)!).filter(Boolean))
135
+ }
136
+ return map
137
+ })
138
+
139
+ const activeBlock = $derived(
140
+ activeId ? originalIndex.byId.get(activeId) ?? null : null
141
+ )
142
+
143
+ const previewPosition = $derived.by(() => {
144
+ if (!showDropPreview || !virtualState || !activeId) return null
145
+ const block = virtualState.byId.get(activeId)
146
+ if (!block) return null
147
+ const parentId = block.parentId ?? null
148
+ const siblings = virtualState.byParent.get(parentId) ?? []
149
+ const index = siblings.indexOf(activeId)
150
+ return { parentId, index }
151
+ })
152
+
153
+ const debouncedSetVirtual = debounce((newBlocks: BaseBlock[] | null) => {
154
+ if (newBlocks) {
155
+ virtualState = computeNormalizedIndex(newBlocks)
156
+ } else {
157
+ virtualState = null
158
+ }
159
+ }, previewDebounce)
160
+
161
+ // Event handlers
162
+ function handleDragStart(event: any) {
163
+ const id = String(event.operation?.source?.id ?? event.active?.id)
164
+ const block = blocks.find(b => b.id === id)
165
+ if (!block) return
166
+ if (canDrag && !canDrag(block)) return
167
+
168
+ const dragEvent: DragStartEvent<BaseBlock> = { block, blockId: id }
169
+ const result = onDragStart?.(dragEvent)
170
+ if (result === false) return
171
+
172
+ coreStickyDetector.reset()
173
+ fromPositionRef = getBlockPosition(blocks, id)
174
+
175
+ if (multiSelect && selectedIds.has(id)) {
176
+ draggedIdsRef = blocks.filter(b => selectedIds.has(b.id)).map(b => b.id)
177
+ } else {
178
+ draggedIdsRef = [id]
179
+ }
180
+
181
+ if (sensorConfig?.hapticFeedback) triggerHaptic()
182
+
183
+ activeId = id
184
+ isDragging = true
185
+ initialBlocksRef = [...blocks]
186
+ cachedReorderRef = null
187
+ }
188
+
189
+ function handleDragOver(event: any) {
190
+ const targetId = String(event.operation?.target?.id ?? event.over?.id ?? '')
191
+ if (!targetId || !activeId) return
192
+ processHover(targetId)
193
+ }
194
+
195
+ function processHover(targetZone: string) {
196
+ const block = blocks.find(b => b.id === activeId)
197
+ const targetBlockId = extractBlockId(targetZone)
198
+ const targetBlock = blocks.find(b => b.id === targetBlockId) ?? null
199
+
200
+ if (canDrop && block && !canDrop(block, targetZone, targetBlock)) return
201
+
202
+ if (hoverZone !== targetZone) {
203
+ const zoneType: DropZoneType = getDropZoneType(targetZone)
204
+ onHoverChange?.({ zoneId: targetZone, zoneType, targetBlock })
205
+ }
206
+
207
+ hoverZone = targetZone
208
+
209
+ const baseIndex = computeNormalizedIndex(initialBlocksRef, orderingStrategy)
210
+ const ids = draggedIdsRef
211
+ const updatedIndex = ids.length > 1
212
+ ? reparentMultipleBlocks(baseIndex, ids, targetZone, containerTypes, orderingStrategy, maxDepth)
213
+ : reparentBlockIndex(baseIndex, activeId!, targetZone, containerTypes, orderingStrategy, maxDepth)
214
+ const orderedBlocks = buildOrderedBlocks(updatedIndex, containerTypes, orderingStrategy)
215
+
216
+ cachedReorderRef = { targetId: targetZone, reorderedBlocks: orderedBlocks }
217
+ if (showDropPreview) debouncedSetVirtual(orderedBlocks)
218
+ }
219
+
220
+ function handleDragEnd(event: any) {
221
+ debouncedSetVirtual.cancel()
222
+
223
+ let cached = cachedReorderRef
224
+ const dragId = activeId
225
+ const block = dragId ? blocks.find(b => b.id === dragId) : null
226
+ const cancelled = event?.canceled ?? false
227
+
228
+ if (cancelled) {
229
+ if (block && dragId) {
230
+ const cancelEvent: DragEndEvent<BaseBlock> = { block, blockId: dragId, targetZone: null, cancelled: true }
231
+ onDragCancel?.(cancelEvent)
232
+ onDragEnd?.(cancelEvent)
233
+ }
234
+ resetDragState()
235
+ return
236
+ }
237
+
238
+ // onBeforeMove middleware
239
+ if (cached && block && fromPositionRef && onBeforeMove) {
240
+ const operation: MoveOperation<BaseBlock> = {
241
+ block,
242
+ from: fromPositionRef,
243
+ targetZone: cached.targetId,
244
+ }
245
+ const result = onBeforeMove(operation)
246
+ if (result === false) {
247
+ resetDragState()
248
+ return
249
+ }
250
+ if (result && result.targetZone !== cached.targetId) {
251
+ const baseIndex = computeNormalizedIndex(initialBlocksRef, orderingStrategy)
252
+ const ids = draggedIdsRef
253
+ const updatedIndex = ids.length > 1
254
+ ? reparentMultipleBlocks(baseIndex, ids, result.targetZone, containerTypes, orderingStrategy, maxDepth)
255
+ : reparentBlockIndex(baseIndex, dragId!, result.targetZone, containerTypes, orderingStrategy, maxDepth)
256
+ cached = { targetId: result.targetZone, reorderedBlocks: buildOrderedBlocks(updatedIndex, containerTypes, orderingStrategy) }
257
+ }
258
+ }
259
+
260
+ if (block && dragId) {
261
+ onDragEnd?.({ block, blockId: dragId, targetZone: cached?.targetId ?? null, cancelled: false })
262
+ }
263
+
264
+ if (cached && block && fromPositionRef) {
265
+ const toPosition = getBlockPosition(cached.reorderedBlocks, block.id)
266
+ onBlockMove?.({ block, from: fromPositionRef, to: toPosition, blocks: cached.reorderedBlocks, movedIds: [...draggedIdsRef] })
267
+ }
268
+
269
+ if (cached && onChange) {
270
+ onChange(cached.reorderedBlocks)
271
+ }
272
+
273
+ resetDragState()
274
+ }
275
+
276
+ function resetDragState() {
277
+ activeId = null
278
+ hoverZone = null
279
+ virtualState = null
280
+ isDragging = false
281
+ cachedReorderRef = null
282
+ initialBlocksRef = []
283
+ fromPositionRef = null
284
+ draggedIdsRef = []
285
+ }
286
+
287
+ function handleToggleExpand(id: string) {
288
+ const newExpanded = expandedMap[id] === false
289
+ expandedMap = { ...expandedMap, [id]: newExpanded }
290
+
291
+ const block = blocks.find(b => b.id === id)
292
+ if (block && onExpandChange) {
293
+ onExpandChange({ block, blockId: id, expanded: newExpanded })
294
+ }
295
+ }
296
+
297
+ function handleHover(zoneId: string, _parentId: string | null) {
298
+ if (!activeId) return
299
+ processHover(zoneId)
300
+ }
301
+
302
+ function getBlockPosition(blocks: BaseBlock[], blockId: string): BlockPosition {
303
+ const block = blocks.find(b => b.id === blockId)
304
+ if (!block) return { parentId: null, index: 0 }
305
+ const siblings = blocks.filter(b => b.parentId === block.parentId)
306
+ const index = siblings.findIndex(b => b.id === blockId)
307
+ return { parentId: block.parentId, index }
308
+ }
309
+
310
+ function computeInitialExpanded(
311
+ blocks: BaseBlock[],
312
+ containerTypes: readonly string[],
313
+ initialExpanded: string[] | 'all' | 'none' | undefined
314
+ ): Record<string, boolean> {
315
+ if (initialExpanded === 'none') {
316
+ const map: Record<string, boolean> = {}
317
+ for (const b of blocks) {
318
+ if (containerTypes.includes(b.type)) map[b.id] = false
319
+ }
320
+ return map
321
+ }
322
+ const map: Record<string, boolean> = {}
323
+ if (initialExpanded === 'all' || initialExpanded === undefined) {
324
+ for (const b of blocks) {
325
+ if (containerTypes.includes(b.type)) map[b.id] = true
326
+ }
327
+ } else if (Array.isArray(initialExpanded)) {
328
+ for (const id of initialExpanded) {
329
+ map[id] = true
330
+ }
331
+ }
332
+ return map
333
+ }
334
+ </script>
335
+
336
+ <DragDropProvider
337
+ onDragStart={handleDragStart}
338
+ onDragOver={handleDragOver}
339
+ onDragEnd={handleDragEnd}
340
+ >
341
+ <div class={className} style:min-width="0">
342
+ <TreeRenderer
343
+ {blocks}
344
+ {blocksByParent}
345
+ parentId={null}
346
+ {activeId}
347
+ {expandedMap}
348
+ {containerTypes}
349
+ onHover={handleHover}
350
+ onToggleExpand={handleToggleExpand}
351
+ {renderBlock}
352
+ {dropZoneClass}
353
+ {dropZoneActiveClass}
354
+ {canDrag}
355
+ {previewPosition}
356
+ draggedBlock={activeBlock}
357
+ {selectedIds}
358
+ {animation}
359
+ />
360
+ </div>
361
+ <DragOverlay {activeBlock} selectedCount={multiSelect ? selectedIds.size : 0}>
362
+ {#snippet children(block)}
363
+ {#if dragOverlay}
364
+ {@render dragOverlay(block)}
365
+ {/if}
366
+ {/snippet}
367
+ </DragOverlay>
368
+ </DragDropProvider>
@@ -0,0 +1,32 @@
1
+ import type { BaseBlock, BlockTreeCallbacks } from '@dnd-block-tree/core';
2
+ import type { BlockTreeCustomization } from '../types';
3
+ import type { Snippet } from 'svelte';
4
+ interface Props extends BlockTreeCallbacks<BaseBlock>, BlockTreeCustomization<BaseBlock> {
5
+ blocks: BaseBlock[];
6
+ containerTypes?: readonly string[];
7
+ onChange?: (blocks: BaseBlock[]) => void;
8
+ renderBlock: Snippet<[
9
+ {
10
+ block: BaseBlock;
11
+ isDragging: boolean;
12
+ depth: number;
13
+ isExpanded: boolean;
14
+ onToggleExpand: (() => void) | null;
15
+ children: Snippet | null;
16
+ }
17
+ ]>;
18
+ dragOverlay?: Snippet<[BaseBlock]>;
19
+ activationDistance?: number;
20
+ previewDebounce?: number;
21
+ showDropPreview?: boolean;
22
+ multiSelect?: boolean;
23
+ selectedIds?: Set<string>;
24
+ onSelectionChange?: (selectedIds: Set<string>) => void;
25
+ dropZoneClass?: string;
26
+ dropZoneActiveClass?: string;
27
+ class?: string;
28
+ }
29
+ declare const BlockTree: import("svelte").Component<Props, {}, "">;
30
+ type BlockTree = ReturnType<typeof BlockTree>;
31
+ export default BlockTree;
32
+ //# sourceMappingURL=BlockTree.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockTree.svelte.d.ts","sourceRoot":"","sources":["../../src/components/BlockTree.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACR,SAAS,EACT,kBAAkB,EAYnB,MAAM,sBAAsB,CAAA;AAW/B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAKtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGpC,UAAU,KAAM,SAAQ,kBAAkB,CAAC,SAAS,CAAC,EAAE,sBAAsB,CAAC,SAAS,CAAC;IACtF,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IACxC,WAAW,EAAE,OAAO,CAAC;QAAC;YACpB,KAAK,EAAE,SAAS,CAAA;YAChB,UAAU,EAAE,OAAO,CAAA;YACnB,KAAK,EAAE,MAAM,CAAA;YACb,UAAU,EAAE,OAAO,CAAA;YACnB,cAAc,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;YACnC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAA;SACzB;KAAC,CAAC,CAAA;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,CAAA;IACtD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAiTH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import type { BaseBlock } from '@dnd-block-tree/core'
3
+
4
+ interface Props {
5
+ blocks: BaseBlock[]
6
+ expandedMap: Record<string, boolean>
7
+ activeId?: string | null
8
+ hoverZone?: string | null
9
+ open?: boolean
10
+ }
11
+
12
+ let {
13
+ blocks,
14
+ expandedMap,
15
+ activeId = null,
16
+ hoverZone = null,
17
+ open = false,
18
+ }: Props = $props()
19
+
20
+ let isOpen = $state(open)
21
+ </script>
22
+
23
+ {#if isOpen}
24
+ <div
25
+ style="position: fixed; bottom: 10px; right: 10px; width: 360px; max-height: 400px; overflow: auto; background: #1f2937; color: #e5e7eb; font-family: monospace; font-size: 12px; padding: 12px; border-radius: 8px; z-index: 9999; box-shadow: 0 4px 16px rgba(0,0,0,0.3);"
26
+ >
27
+ <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
28
+ <strong>BlockTree DevTools</strong>
29
+ <button onclick={() => isOpen = false} style="background: none; border: none; color: #9ca3af; cursor: pointer;">x</button>
30
+ </div>
31
+ <div style="margin-bottom: 8px;">
32
+ <span style="color: #9ca3af;">Blocks:</span> {blocks.length}
33
+ {#if activeId}
34
+ <span style="color: #fbbf24; margin-left: 8px;">Dragging: {activeId.slice(0, 8)}</span>
35
+ {/if}
36
+ {#if hoverZone}
37
+ <span style="color: #34d399; margin-left: 8px;">Hover: {hoverZone}</span>
38
+ {/if}
39
+ </div>
40
+ <pre style="margin: 0; white-space: pre-wrap; font-size: 11px;">{JSON.stringify(blocks.map(b => ({
41
+ id: b.id.slice(0, 8),
42
+ type: b.type,
43
+ parentId: b.parentId?.slice(0, 8) ?? null,
44
+ order: b.order,
45
+ })), null, 2)}</pre>
46
+ </div>
47
+ {:else}
48
+ <button
49
+ onclick={() => isOpen = true}
50
+ style="position: fixed; bottom: 10px; right: 10px; background: #1f2937; color: #e5e7eb; border: none; border-radius: 8px; padding: 8px 12px; font-size: 12px; cursor: pointer; z-index: 9999; box-shadow: 0 2px 8px rgba(0,0,0,0.2);"
51
+ >
52
+ DevTools
53
+ </button>
54
+ {/if}
@@ -0,0 +1,12 @@
1
+ import type { BaseBlock } from '@dnd-block-tree/core';
2
+ interface Props {
3
+ blocks: BaseBlock[];
4
+ expandedMap: Record<string, boolean>;
5
+ activeId?: string | null;
6
+ hoverZone?: string | null;
7
+ open?: boolean;
8
+ }
9
+ declare const BlockTreeDevTools: import("svelte").Component<Props, {}, "">;
10
+ type BlockTreeDevTools = ReturnType<typeof BlockTreeDevTools>;
11
+ export default BlockTreeDevTools;
12
+ //# sourceMappingURL=BlockTreeDevTools.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockTreeDevTools.svelte.d.ts","sourceRoot":"","sources":["../../src/components/BlockTreeDevTools.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAGpD,UAAU,KAAK;IACb,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AA8CH,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte'
3
+ import type { Snippet } from 'svelte'
4
+
5
+ interface Props {
6
+ children: Snippet
7
+ fallback?: Snippet
8
+ }
9
+
10
+ let { children, fallback }: Props = $props()
11
+ let mounted = $state(false)
12
+
13
+ onMount(() => {
14
+ mounted = true
15
+ })
16
+ </script>
17
+
18
+ {#if mounted}
19
+ {@render children()}
20
+ {:else if fallback}
21
+ {@render fallback()}
22
+ {/if}
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ children: Snippet;
4
+ fallback?: Snippet;
5
+ }
6
+ declare const BlockTreeSSR: import("svelte").Component<Props, {}, "">;
7
+ type BlockTreeSSR = ReturnType<typeof BlockTreeSSR>;
8
+ export default BlockTreeSSR;
9
+ //# sourceMappingURL=BlockTreeSSR.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockTreeSSR.svelte.d.ts","sourceRoot":"","sources":["../../src/components/BlockTreeSSR.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGpC,UAAU,KAAK;IACb,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAsBH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}