Skip to content

Commit 8f20889

Browse files
committed
v0.4.0
1 parent b567425 commit 8f20889

File tree

2 files changed

+59
-15
lines changed

2 files changed

+59
-15
lines changed

README.md

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Care has been taken to ensure everything "just works" with minimal configuration
2323
- **Smooth animations** - Performant transitions with no jank
2424
- **(Nearly) Headless UI** - Bring your own styles and components*
2525
- **Tag-based constraints** - Control which blocks can be nested where
26+
- **Mobile support** - Supports both mouse and touch events
2627

2728
_*Some minor styling is provided for convenience, but it's easy to override_
2829

@@ -73,12 +74,11 @@ function App() {
7374
<BlockTree {...props}>
7475
{/* Defines how each block in the tree should be rendered */}
7576
{block => (
76-
<div
77-
class="border rounded p-4"
78-
draggable={true}
79-
onDragStart={block.startDrag}
80-
>
81-
<p>{block.data}</p>
77+
<div class="border rounded p-4">
78+
{/* Add data-drag-handle to elements that should initiate drag */}
79+
<div data-drag-handle class="cursor-grab">
80+
<p>{block.data}</p>
81+
</div>
8282
<div class="mt-4">
8383
{block.children}
8484
</div>
@@ -100,14 +100,14 @@ The `BlockTree` is the primary component exposed by this library, and is used as
100100
// The only required prop - the root block of the tree
101101
root={root}
102102
// The currently selected blocks
103-
selection={['key1', 'key2']}
103+
selection={{ blocks: ['key1', 'key2'] }}
104104
// Various event handlers; because this is a controlled component,
105105
// the state of the tree won't update unless these are handled
106106
onSelectionChange={event => {}}
107107
onInsert={event => {}}
108108
onReorder={event => {}}
109109
onRemove={event => {}}
110-
// ...various additional configuration props, documented below
110+
// ...various additional events and configuration props, documented below
111111
>
112112
{/* A function to render each block in the tree */}
113113
{block => <YourBlockComponent {...block} />}
@@ -143,12 +143,12 @@ type Block<K, T> = {
143143
### Root Block
144144
145145
The root block is the top-level container, and is not actually rendered in the UI.
146-
Therefore, it does not need a `data` property, though `children` is required (or else nothing would render).
146+
Therefore, it does not need a `data` property.
147147
148148
```tsx
149149
type RootBlock<K, T> = {
150150
key: K
151-
children: Block<K, T>[]
151+
children?: Block<K, T>[]
152152
tag?: string
153153
accepts?: string[]
154154
spacing?: number
@@ -168,7 +168,7 @@ most if not all of the event handlers too, otherwise the block tree won't be edi
168168
|------|------|---------|-------------|
169169
| `root` | `RootBlock<K, T>` | *required* | The root block of the tree |
170170
| `children` | `Component<BlockProps<K, T>>` | *required* | Render function for blocks |
171-
| `selection` | `Selection<K>` | | Current selection (blocks or insertion point) |
171+
| `selection` | `Selection<K>` | | Current selection |
172172
| `onSelectionChange` | `EventHandler<SelectionEvent<K>>` | | Called when selection changes |
173173
| `onInsert` | `EventHandler<InsertEvent<K, T>>` | | Called when blocks are inserted |
174174
| `onReorder` | `EventHandler<ReorderEvent<K>>` | | Called when blocks are reordered |
@@ -178,12 +178,29 @@ most if not all of the event handlers too, otherwise the block tree won't be edi
178178
| `onPaste` | `EventHandler<PasteEvent<K>>` | | Called when blocks are pasted |
179179
| `defaultSpacing` | `number` | `12` | Default spacing between blocks (px) |
180180
| `transitionDuration` | `number` | `200` | Animation duration (ms) |
181+
| `dragThreshold` | `number` | `10` | Distance cursor must move (px) to start drag |
181182
| `fixedHeightWhileDragging` | `boolean` | `false` | Fix container height during drag operations |
182183
| `multiselect` | `boolean` | `true` | Enable multi-selection |
183184
| `dropzone` | `Component<{}>` | | Custom dropzone component |
184185
| `placeholder` | `Component<{ parent: K }>` | | Custom placeholder component |
185186
| `dragContainer` | `Component<DragContainerProps<K, T>>` | | Custom drag container component |
186187
188+
### Selections
189+
190+
A `Selection` can either be:
191+
- A set of blocks (when `blocks` has a value)
192+
- A place between blocks, like an insertion cursor (when `place` has a value)
193+
- Empty (when neither property has a value)
194+
195+
It is not valid for both properties to have a value at the same time.
196+
197+
```ts
198+
type Selection<K> = {
199+
blocks?: K[]
200+
place?: Place<K>
201+
}
202+
```
203+
187204
### Block Render Props
188205
189206
The `BlockTree` render function receives these props for each block:
@@ -194,13 +211,27 @@ The `BlockTree` render function receives these props for each block:
194211
| `data` | `T` | Your custom data |
195212
| `selected` | `boolean` | Whether block is currently selected |
196213
| `dragging` | `boolean` | Whether block is being dragged |
197-
| `startDrag` | `(ev: MouseEvent) => void` | Call this to initiate drag operation |
198214
| `children` | `JSX.Element` | Rendered child blocks |
199215
200216
It's perfectly fine not to render the `children` in the render function, or only conditionally render it.
201217
This will prevent the user from inserting any new child blocks via drag-and-drop, though any existing children
202218
will remain unless programmatically removed.
203219
220+
### Making blocks draggable
221+
222+
To make an element draggable, add the `data-drag-handle` attribute to it. When a user clicks and drags an element with this attribute, it will initiate a drag operation for the block. The entire block can be made draggable by adding this attribute to the root element.
223+
224+
If there are any elements inside the block that should be able to take focus, like `input` elements, ensure you add an event handler for the `onPointerDown` event that calls `event.stopPropagation`, otherwise focus will be immediately lost and the block itself will become selected.
225+
226+
```tsx
227+
{block => (
228+
<div data-drag-handle>
229+
<p>Some text</p>
230+
<input onPointerDown={ev => ev.stopPropagation()} />
231+
</div>
232+
)}
233+
```
234+
204235
## Events
205236
206237
The `BlockTree` component emits various kinds of events in response to drag-and-drop and other interactions.
@@ -375,6 +406,16 @@ return (
375406
**Note:** `createBlockTree` provides handlers for `onSelectionChange`, `onInsert`, `onReorder`, and `onRemove`.
376407
If you need copy/cut/paste functionality, you'll need to implement those handlers separately.
377408

409+
### Additional Methods
410+
411+
The object returned by `createBlockTree` also includes these additional methods:
412+
413+
- **`toggleBlockSelected(key: K, selected: boolean)`** - Toggle a block's selection state
414+
- **`selectBlock(key: K)`** - Select a specific block
415+
- **`unselectBlock(key: K)`** - Unselect a specific block
416+
- **`updateBlock(key: K, data: T)`** - Update a block's data
417+
- **`setRoot`** - The raw `setStoreFunction`, allowing you to make arbitrary updates to the tree state
418+
378419
See [`createBlockTree.ts`](https://github.com/Rafferty97/solid-nest/blob/main/src/createBlockTree.ts) to get an idea
379420
for how you might implement your own state management system or integrate your existing one with `solid-nest`.
380421

src/createBlockTree.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ export function createBlockTree<K, T>(init: RootBlock<K, T>) {
2626

2727
return {
2828
root,
29+
setRoot,
2930

3031
get selection() {
3132
return selection()
3233
},
34+
setSelection,
3335

3436
onSelectionChange(event: SelectionEvent<K>) {
3537
setSelection(event)
@@ -87,7 +89,7 @@ export function createBlockTree<K, T>(init: RootBlock<K, T>) {
8789
}
8890

8991
/** Utility function to find a block in a tree with a given `key`. */
90-
export function findBlock<K, T>(root: RootBlock<K, T>, key: K): Block<K, T> | RootBlock<K, T> | undefined {
92+
function findBlock<K, T>(root: RootBlock<K, T>, key: K): Block<K, T> | RootBlock<K, T> | undefined {
9193
if (root.key === key) {
9294
return root
9395
}
@@ -102,7 +104,7 @@ export function findBlock<K, T>(root: RootBlock<K, T>, key: K): Block<K, T> | Ro
102104
/**
103105
* Removes blocks with the given set of `keys`,
104106
* optionally accumulating them into the `collect` array. */
105-
export function removeBlocks<K, T>(root: RootBlock<K, T>, keys: K[], collect?: Block<K, T>[]) {
107+
function removeBlocks<K, T>(root: RootBlock<K, T>, keys: K[], collect?: Block<K, T>[]) {
106108
root.children = root.children?.filter(child => {
107109
if (!keys.includes(child.key)) {
108110
removeBlocks(child, keys, collect)
@@ -112,8 +114,9 @@ export function removeBlocks<K, T>(root: RootBlock<K, T>, keys: K[], collect?: B
112114
return false
113115
})
114116
}
117+
115118
/** Inserts `blocks` into the tree at the specified `place`. */
116-
export function insertBlocks<K, T>(root: RootBlock<K, T>, blocks: Block<K, T>[], place: Place<K>) {
119+
function insertBlocks<K, T>(root: RootBlock<K, T>, blocks: Block<K, T>[], place: Place<K>) {
117120
const parent = findBlock(root, place.parent)
118121
if (!parent) return
119122

0 commit comments

Comments
 (0)