@aionbuilders/nabu 0.1.0-alpha.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 (62) hide show
  1. package/README.md +131 -0
  2. package/dist/behaviors/index.d.ts +1 -0
  3. package/dist/behaviors/index.js +1 -0
  4. package/dist/behaviors/text/RichText.svelte +33 -0
  5. package/dist/behaviors/text/RichText.svelte.d.ts +11 -0
  6. package/dist/behaviors/text/index.d.ts +3 -0
  7. package/dist/behaviors/text/index.js +3 -0
  8. package/dist/behaviors/text/rich-text.extension.d.ts +2 -0
  9. package/dist/behaviors/text/rich-text.extension.js +75 -0
  10. package/dist/behaviors/text/text.behavior.svelte.d.ts +103 -0
  11. package/dist/behaviors/text/text.behavior.svelte.js +346 -0
  12. package/dist/blocks/Block.svelte +18 -0
  13. package/dist/blocks/Block.svelte.d.ts +11 -0
  14. package/dist/blocks/Nabu.svelte +31 -0
  15. package/dist/blocks/Nabu.svelte.d.ts +12 -0
  16. package/dist/blocks/block.svelte.d.ts +143 -0
  17. package/dist/blocks/block.svelte.js +364 -0
  18. package/dist/blocks/container.utils.d.ts +28 -0
  19. package/dist/blocks/container.utils.js +114 -0
  20. package/dist/blocks/heading/Heading.svelte +42 -0
  21. package/dist/blocks/heading/Heading.svelte.d.ts +11 -0
  22. package/dist/blocks/heading/heading.svelte.d.ts +45 -0
  23. package/dist/blocks/heading/heading.svelte.js +94 -0
  24. package/dist/blocks/heading/hooks/onBeforeInput.hook.d.ts +3 -0
  25. package/dist/blocks/heading/hooks/onBeforeInput.hook.js +58 -0
  26. package/dist/blocks/heading/index.d.ts +7 -0
  27. package/dist/blocks/heading/index.js +41 -0
  28. package/dist/blocks/index.d.ts +10 -0
  29. package/dist/blocks/index.js +12 -0
  30. package/dist/blocks/list/List.svelte +25 -0
  31. package/dist/blocks/list/List.svelte.d.ts +11 -0
  32. package/dist/blocks/list/ListItem.svelte +45 -0
  33. package/dist/blocks/list/ListItem.svelte.d.ts +11 -0
  34. package/dist/blocks/list/index.d.ts +10 -0
  35. package/dist/blocks/list/index.js +41 -0
  36. package/dist/blocks/list/list-item.svelte.d.ts +50 -0
  37. package/dist/blocks/list/list-item.svelte.js +213 -0
  38. package/dist/blocks/list/list.behavior.svelte.d.ts +23 -0
  39. package/dist/blocks/list/list.behavior.svelte.js +61 -0
  40. package/dist/blocks/list/list.svelte.d.ts +39 -0
  41. package/dist/blocks/list/list.svelte.js +139 -0
  42. package/dist/blocks/megablock.svelte.d.ts +13 -0
  43. package/dist/blocks/megablock.svelte.js +64 -0
  44. package/dist/blocks/nabu.svelte.d.ts +121 -0
  45. package/dist/blocks/nabu.svelte.js +395 -0
  46. package/dist/blocks/paragraph/Paragraph.svelte +38 -0
  47. package/dist/blocks/paragraph/Paragraph.svelte.d.ts +11 -0
  48. package/dist/blocks/paragraph/index.d.ts +7 -0
  49. package/dist/blocks/paragraph/index.js +44 -0
  50. package/dist/blocks/paragraph/paragraph.svelte.d.ts +41 -0
  51. package/dist/blocks/paragraph/paragraph.svelte.js +86 -0
  52. package/dist/blocks/selection.svelte.d.ts +38 -0
  53. package/dist/blocks/selection.svelte.js +143 -0
  54. package/dist/index.d.ts +4 -0
  55. package/dist/index.js +5 -0
  56. package/dist/utils/extensions.d.ts +69 -0
  57. package/dist/utils/extensions.js +43 -0
  58. package/dist/utils/index.d.ts +2 -0
  59. package/dist/utils/index.js +2 -0
  60. package/dist/utils/selection.svelte.d.ts +219 -0
  61. package/dist/utils/selection.svelte.js +611 -0
  62. package/package.json +74 -0
@@ -0,0 +1,364 @@
1
+ /**
2
+ * @import { Nabu, NabuNode } from "./nabu.svelte";
3
+ * @import { MegaBlock } from "./megablock.svelte";
4
+ */
5
+
6
+ import { tick } from "svelte";
7
+ import { SvelteMap, SvelteSet } from "svelte/reactivity";
8
+
9
+
10
+ export class Block {
11
+ /** @param {Nabu} nabu @param {NabuNode} node */
12
+ constructor(nabu, node) {
13
+ this.nabu = nabu;
14
+ this.node = node;
15
+ const metadata = node.data;
16
+ this.id = node.id.toString()
17
+ this.type = metadata.get("type") || "block";
18
+ this.nabu.blocks.set(this.id, this);
19
+ const blocksOfType = this.nabu.blocksByType.get(this.type) || new SvelteSet();
20
+ blocksOfType.add(this);
21
+ this.nabu.blocksByType.set(this.type, blocksOfType);
22
+
23
+
24
+
25
+ const parent = node.parent();
26
+ if (parent) {
27
+ this.parent = this.nabu.blocks.get(parent.id.toString()) || null;
28
+ }
29
+ }
30
+
31
+ serializers = new SvelteMap();
32
+ behaviors = new SvelteMap();
33
+ selected = $state(false);
34
+ isSelectionStart = $state(false);
35
+ isSelectionEnd = $state(false);
36
+ isIntermediate = $derived(this.selected && !this.isSelectionStart && !this.isSelectionEnd);
37
+
38
+ clearSelection() {
39
+ this.selected = false;
40
+ this.isSelectionStart = false;
41
+ this.isSelectionEnd = false;
42
+ }
43
+
44
+ /** @type {{from: number, to: number, direction: "forward" | "backward" | "none"} | null} */
45
+ selection = $derived(null);
46
+
47
+ /** @type {MegaBlock | null} */
48
+ parent = $state(null);
49
+ index = $state(0);
50
+ /** @type {Block?} */
51
+ previous = $derived(this.index > 0 && this.parent ? this.parent.children[this.index - 1] : null);
52
+ /** @type {Block?} */
53
+ next = $derived(this.parent && this.index < this.parent.children.length - 1 ? this.parent.children[this.index + 1] : null);
54
+
55
+ parents = $derived.by(() => {
56
+ const parents = [];
57
+ let current = this.parent;
58
+ while (current) {
59
+ parents.push(current);
60
+ current = current.parent;
61
+ }
62
+ return parents;
63
+ });
64
+
65
+ component = $derived(this.nabu.components.get(this.type) || null);
66
+
67
+ /** @type {HTMLElement | null} */
68
+ element = $state(null);
69
+
70
+ /** @param {(block: Block) => boolean} predicate @returns {Block | null} */
71
+ findForward(predicate) {
72
+ // 1. Chercher dans les enfants (descendre)
73
+ // @ts-ignore
74
+ if (this.children?.length) {
75
+ // @ts-ignore
76
+ for (const child of this.children) {
77
+ if (predicate(child)) return child;
78
+ const found = child.findForward(predicate);
79
+ if (found) return found;
80
+ }
81
+ }
82
+
83
+ // 2. Chercher dans les frères suivants et remonter
84
+ let current = this;
85
+ while (current) {
86
+ const parent = current.parent || current.nabu;
87
+ const i = parent.children.indexOf(current);
88
+ if (i !== -1) {
89
+ for (let j = i + 1; j < parent.children.length; j++) {
90
+ const sibling = parent.children[j];
91
+ if (predicate(sibling)) return sibling;
92
+ const found = sibling.findForward(predicate);
93
+ if (found) return found;
94
+ }
95
+ }
96
+ current = current.parent;
97
+ }
98
+ return null;
99
+ }
100
+
101
+ /** @param {(block: Block) => boolean} predicate @returns {Block | null} */
102
+ findBackward(predicate) {
103
+ let current = this;
104
+ while (current) {
105
+ const parent = current.parent || current.nabu;
106
+ const i = parent.children.indexOf(current);
107
+
108
+ if (i !== -1) {
109
+ // On parcourt les frères précédents de bas en haut
110
+ for (let j = i - 1; j >= 0; j--) {
111
+ const sibling = parent.children[j];
112
+
113
+ // Si le frère a des enfants, le "précédent" est le DERNIER de ses descendants
114
+ // @ts-ignore
115
+ if (sibling.children?.length) {
116
+ // @ts-ignore
117
+ const lastDescendant = sibling.findLastDescendant(predicate);
118
+ if (lastDescendant) return lastDescendant;
119
+ }
120
+
121
+ if (predicate(sibling)) return sibling;
122
+ }
123
+ }
124
+
125
+ // Si aucun frère précédent ne match, on teste le parent lui-même
126
+ if (current.parent) {
127
+ if (predicate(current.parent)) return current.parent;
128
+ current = current.parent;
129
+ } else {
130
+ break;
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+
136
+ /**
137
+ * Returns real, uncommitted siblings, which is useful for extensions that want to check the document structure before the transaction is committed.
138
+ */
139
+ getAdjacentSiblings() {
140
+ const parent = this.node.parent();
141
+ const siblings = parent ? parent.children() : this.nabu.tree.roots();
142
+ if (!siblings) return { previous: null, next: null };
143
+
144
+ const index = this.node.index();
145
+ if (index === null || index === undefined) return { previous: null, next: null };
146
+
147
+ const previousNode = index > 0 ? siblings[index - 1] : null;
148
+ const nextNode = index < siblings.length - 1 ? siblings[index + 1] : null;
149
+ const previous = previousNode ? this.nabu.blocks.get(previousNode.id.toString()) : null;
150
+ const next = nextNode ? this.nabu.blocks.get(nextNode.id.toString()) : null;
151
+
152
+ return {
153
+ previous,
154
+ next,
155
+ previousNode,
156
+ nextNode
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Helper pour trouver le dernier descendant profond qui matche
162
+ * @param {(block: Block) => boolean} predicate
163
+ * @returns {Block | null}
164
+ */
165
+ findLastDescendant(predicate) {
166
+ // @ts-ignore
167
+ const children = this.children || [];
168
+ for (let i = children.length - 1; i >= 0; i--) {
169
+ const child = children[i];
170
+ const found = child.findLastDescendant(predicate);
171
+ if (found) return found;
172
+ if (predicate(child)) return child;
173
+ }
174
+ return null;
175
+ }
176
+
177
+ destroy() {
178
+ this.nabu.delete(this);
179
+ }
180
+
181
+ /**
182
+ * Transforme ce bloc en un autre type de bloc.
183
+ * @param {string} newType
184
+ * @param {Object} [props={}]
185
+ */
186
+ transformTo(newType, props = {}) {
187
+ const data = this.node.data;
188
+ data.set("type", newType);
189
+ for (const [key, value] of Object.entries(props)) {
190
+ data.set(key, value);
191
+ }
192
+ this.commit();
193
+ }
194
+
195
+ /** @param {number} index @param {string} text */
196
+ insert(index, text) {
197
+ console.warn("Not implemented: insert text", text, "at index", index, "in block", this.id);
198
+ }
199
+
200
+ /** @param {{from?: number, to?: number, index?: number, length?: number}} [deletion] */
201
+ delete(deletion) {
202
+ console.warn("Not implemented: delete block", this.id, "with deletion range", deletion);
203
+ }
204
+
205
+ /** @param {Block} block @returns {any} */
206
+ absorbs(block) {
207
+ console.warn("Not implemented: check if block", this.id, "absorbs block", block.id);
208
+ return false;
209
+ }
210
+
211
+
212
+ /** @param {Block} block @returns {any} */
213
+ mergeWith(block) {
214
+ const success = this.absorbs(block);
215
+ if (success) {
216
+ block.destroy();
217
+ return true;
218
+ }
219
+ return false;
220
+ }
221
+
222
+ /**
223
+ * Consumes another block, handling children relocation intelligently.
224
+ * @param {Block} otherBlock - The block to consume or be consumed by
225
+ * @param {'into' | 'from'} direction - 'into' = this merges into other, 'from' = other merges into this
226
+ * @returns {Block} The surviving block
227
+ */
228
+ consume(otherBlock, direction = 'from') {
229
+ const [survivor, victim] = direction === 'into'
230
+ ? [otherBlock, this]
231
+ : [this, otherBlock];
232
+
233
+ const absorbed = survivor.absorbs(victim);
234
+
235
+ // Handle children relocation if victim has any
236
+ // @ts-ignore - MegaBlock has children
237
+ if (victim.children?.length) {
238
+ if (absorbed && survivor.adoptChildren) {
239
+ // MegaBlock survivor adopts children
240
+ // @ts-ignore
241
+ survivor.adoptChildren(victim.children);
242
+ } else {
243
+ // Non-MegaBlock survivor: promote children as siblings
244
+ // @ts-ignore
245
+ victim.children.forEach(child => {
246
+ child.node.moveAfter(survivor.node);
247
+ });
248
+ }
249
+ }
250
+
251
+ // Handle the victim block itself if not absorbed
252
+ if (!absorbed) {
253
+ victim.node.moveAfter(survivor.node);
254
+ }
255
+
256
+ return survivor;
257
+ }
258
+
259
+ /** @param {Block[]} children @param {number | null} [index] */
260
+ adoptChildren(children, index = null) {
261
+ console.warn("Not implemented: adopt children", children.map(c => c.id), "into block", this.id, "at index", index);
262
+ }
263
+
264
+ /** @param {{from?: number, to?: number, index?: number, length?: number, offset?: number}} options @returns {{block: Block} | null} */
265
+ split(options) {
266
+ console.warn("Not implemented: split block", this.id, "with options", options);
267
+ return null;
268
+ }
269
+
270
+ /** @param {{start?: number, end?: number, offset?: number} | null } options @param {boolean} [passive=false] */
271
+ focus(options = {}, passive = false) {
272
+ let start = options?.start ?? options?.offset ?? this.selection?.from ?? 0;
273
+ let end = options?.end ?? options?.offset ?? this.selection?.to ?? 0;
274
+ const startPoint = this.getDOMPoint(start);
275
+ const endPoint = this.getDOMPoint(end);
276
+ if (!passive && startPoint && endPoint) {
277
+ tick().then(() => {
278
+ this.nabu.selection.setBaseAndExtent(startPoint.node, startPoint.offset || 0, endPoint.node || null, endPoint.offset || 0)
279
+ })
280
+ }
281
+
282
+ return {start: startPoint, end: endPoint, options: {startOffset: start, endOffset: end}};
283
+ }
284
+
285
+ /**
286
+ * @param {number} offset
287
+ * @returns {{node: Node, offset: number} | null}
288
+ */
289
+ getDOMPoint(offset) {
290
+ console.warn("getDOMPoint not implemented for block", this.type, this.id);
291
+ return null;
292
+ }
293
+
294
+ // -- Event Handling --
295
+
296
+ /** @param {string} eventName @param {Event} event @param {Object} [data={}] */
297
+ ascend(eventName, event, data = {}) {
298
+ //@ts-ignore
299
+ if (this.parent && typeof this.parent[eventName] === "function") {
300
+ //@ts-ignore
301
+ return this.parent[eventName](event, { ...data, from: this });
302
+ }
303
+
304
+ // 2. On essaye les hooks d'extensions enregistrés dans Nabu
305
+ const hooks = this.nabu.hooks.get(eventName);
306
+ if (hooks) {
307
+ for (const hook of hooks) {
308
+
309
+ // Si un hook retourne 'true', on considère l'événement géré
310
+ const result = hook(this.nabu, this, event, data);
311
+ if (result) return result;
312
+ }
313
+ }
314
+
315
+ return false;
316
+ }
317
+
318
+ /** @param {InputEvent} event @returns {any} */
319
+ beforeinput(event) {
320
+ // On peut intercepter les événements d'input ici pour faire des choses comme :
321
+ }
322
+
323
+ /** @param {KeyboardEvent} event @returns {any} */
324
+ keydown(event) {
325
+ // Intercepter les touches spéciales (Tab, Enter, flèches)
326
+ }
327
+
328
+ // -- UTILS --
329
+
330
+ commit() {
331
+ this.nabu.commit();
332
+ }
333
+
334
+
335
+ /** @param {string} format */
336
+ serialize(format) {
337
+ const serializer = this.serializers.get(format);
338
+ if (serializer) {
339
+ return serializer(this);
340
+ } else {
341
+ console.warn(`No serializer found for format "${format}" on block type "${this.type}"`);
342
+ return null;
343
+ }
344
+ }
345
+
346
+
347
+ /** @param {Nabu} nabu @param {NabuNode} node */
348
+ static load(nabu, node) {
349
+ const metadata = node.data;
350
+ const type = metadata.get("type") || "block";
351
+ const BlockClass = nabu.registry.get(type) || Block;
352
+ const block = new BlockClass(nabu, node);
353
+ return block;
354
+ }
355
+
356
+ /** @param {Nabu} nabu @param {string} type @param {Object} [props={}] @param {string|null} [parentId=null] @param {number|null} [index=null] */
357
+ static create(nabu, type, props = {}, parentId = null, index = null) {
358
+ const node = nabu.tree.createNode(parentId || undefined, index || undefined);
359
+ node.data.set("type", type);
360
+ const block = Block.load(nabu, node);
361
+ return block;
362
+ }
363
+
364
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @import { Block } from './block.svelte';
3
+ * @import { Nabu } from './nabu.svelte';
4
+ */
5
+ /**
6
+ * Shared beforeinput logic for block containers (Nabu root and MegaBlock).
7
+ *
8
+ * Uses a "spine" approach: for each selection boundary, we walk up the parent
9
+ * chain until we hit the container, collecting intermediate blocks to clean up
10
+ * along the way. This correctly handles arbitrarily nested structures.
11
+ *
12
+ * Spine termination differs between the two callers:
13
+ * - MegaBlock: the container IS pushed into the spine (loop exits on === container),
14
+ * so the direct child of container is at spine.at(-2).
15
+ * - Nabu: root blocks have parent = null, so the loop breaks before Nabu is ever
16
+ * pushed, and the direct child of container is at spine.at(-1).
17
+ * Both cases are unified by the directChild() helper below.
18
+ *
19
+ * @param {{ children: Block[], commit: () => void }} container
20
+ * @param {Nabu} nabu
21
+ * @param {InputEvent} event
22
+ */
23
+ export function handleContainerBeforeInput(container: {
24
+ children: Block[];
25
+ commit: () => void;
26
+ }, nabu: Nabu, event: InputEvent): any;
27
+ import type { Block } from './block.svelte';
28
+ import type { Nabu } from './nabu.svelte';
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @import { Block } from './block.svelte';
3
+ * @import { Nabu } from './nabu.svelte';
4
+ */
5
+
6
+ /**
7
+ * Shared beforeinput logic for block containers (Nabu root and MegaBlock).
8
+ *
9
+ * Uses a "spine" approach: for each selection boundary, we walk up the parent
10
+ * chain until we hit the container, collecting intermediate blocks to clean up
11
+ * along the way. This correctly handles arbitrarily nested structures.
12
+ *
13
+ * Spine termination differs between the two callers:
14
+ * - MegaBlock: the container IS pushed into the spine (loop exits on === container),
15
+ * so the direct child of container is at spine.at(-2).
16
+ * - Nabu: root blocks have parent = null, so the loop breaks before Nabu is ever
17
+ * pushed, and the direct child of container is at spine.at(-1).
18
+ * Both cases are unified by the directChild() helper below.
19
+ *
20
+ * @param {{ children: Block[], commit: () => void }} container
21
+ * @param {Nabu} nabu
22
+ * @param {InputEvent} event
23
+ */
24
+ export function handleContainerBeforeInput(container, nabu, event) {
25
+ const startBlock = /** @type {Block} */ (nabu.selection.startBlock);
26
+ const endBlock = /** @type {Block} */ (nabu.selection.endBlock);
27
+ if (!startBlock || !endBlock) return false;
28
+
29
+ const focusData = startBlock.focus(undefined, true);
30
+ const inputType = event.inputType;
31
+
32
+ // Build start spine: walk from startBlock up to (but not past) container,
33
+ // pruning next-siblings of each intermediate node along the way.
34
+ const startSpine = [startBlock];
35
+ while (startSpine.at(-1) && startSpine.at(-1) !== container) {
36
+ const current = startSpine.at(-1);
37
+ const parent = current?.parent;
38
+ if (!current || !parent) break;
39
+ startSpine.push(parent);
40
+ if (parent !== container) {
41
+ const nextSiblings = parent.children.slice(current.index + 1);
42
+ if (nextSiblings.length) nextSiblings.forEach(block => block.destroy());
43
+ }
44
+ }
45
+
46
+ // Build end spine: walk from endBlock up to (but not past) container,
47
+ // pruning prev-siblings of each intermediate node along the way.
48
+ const endSpine = [endBlock];
49
+ while (endSpine.at(-1) && endSpine.at(-1) !== container) {
50
+ const current = endSpine.at(-1);
51
+ const parent = current?.parent;
52
+ if (!current || !parent) break;
53
+ endSpine.push(parent);
54
+ if (parent !== container) {
55
+ const previousSiblings = parent.children.slice(0, current.index);
56
+ if (previousSiblings.length) previousSiblings.forEach(block => block.destroy());
57
+ }
58
+ }
59
+
60
+ // Resolve the direct children of container from each spine.
61
+ const directChild = (spine) => spine.at(-1) === container ? spine.at(-2) : spine.at(-1);
62
+ const startOfStartSpine = directChild(startSpine);
63
+ const startOfEndSpine = directChild(endSpine);
64
+ if (!startOfStartSpine || !startOfEndSpine) return false;
65
+
66
+ // Delete intermediate direct children of container between the two spines.
67
+ const intermediates = container.children.slice(startOfStartSpine.index + 1, startOfEndSpine.index);
68
+ intermediates.forEach(block => block.destroy());
69
+
70
+ // Single-block selection: delegate directly to that block.
71
+ if (startBlock === endBlock) {
72
+ return startBlock.beforeinput(event);
73
+ }
74
+
75
+ // Delete the selected portions of both boundary blocks.
76
+ startBlock.delete();
77
+ endBlock.delete();
78
+
79
+ let focusBlock = startBlock;
80
+
81
+ if (inputType === 'deleteContentBackward' || inputType === 'deleteContentForward') {
82
+ // Text deletion already handled above.
83
+ } else if (inputType === 'insertParagraph') {
84
+ const { block: newBlock } = startBlock.split({ offset: focusData.options.startOffset }) || {};
85
+ if (newBlock) focusBlock = newBlock;
86
+ } else if (inputType === 'insertText' || inputType === 'insertLineBreak') {
87
+ const text = event.data || (inputType === 'insertLineBreak' ? '\n' : '');
88
+ startBlock.insert(focusData.options.startOffset, text);
89
+ } else {
90
+ console.warn('Unhandled input type in container:', inputType);
91
+ return false;
92
+ }
93
+
94
+ // Merge the end block into focusBlock and relocate orphaned siblings.
95
+ focusBlock.consume(endBlock);
96
+
97
+ endSpine.forEach(block => {
98
+ if (block === container) return;
99
+ const brotherhood = block.parent?.children || [];
100
+ const index = brotherhood.indexOf(block);
101
+ if (index !== -1) {
102
+ const nextSiblings = brotherhood.slice(index + 1);
103
+ if (nextSiblings.length) nextSiblings.forEach(sibling => sibling.node.moveAfter(focusBlock.node));
104
+ }
105
+ });
106
+
107
+ startOfEndSpine?.destroy();
108
+
109
+ container.commit();
110
+ startBlock.focus({
111
+ offset: focusData.options.startOffset + (inputType === 'insertText' ? (event.data?.length || 0) : 0)
112
+ });
113
+ return true;
114
+ }
@@ -0,0 +1,42 @@
1
+ <script>
2
+ import RichText from '../../behaviors/text/RichText.svelte';
3
+ /** @type {{block: import('./heading.svelte.js').Heading}}*/
4
+ let {block} = $props();
5
+ </script>
6
+
7
+ <svelte:element
8
+ this={"h" + block.level}
9
+ bind:this={block.element}
10
+ data-block-id={block.id}
11
+ data-block-type="heading"
12
+ class="nabu-heading"
13
+ class:selected={block.selected}
14
+ class:first={block.isSelectionStart}
15
+ class:last={block.isSelectionEnd}
16
+ ><RichText delta={block.delta} /></svelte:element>
17
+
18
+ <style>
19
+ .nabu-heading {
20
+ margin-top: 1.5rem;
21
+ margin-bottom: 0.5rem;
22
+ white-space: pre-wrap;
23
+ outline: none;
24
+
25
+ &.selected {
26
+ background-color: rgba(59, 130, 246, 0.25);
27
+ }
28
+ &.first {
29
+ border-top: 1px solid rgba(59, 130, 246, 0.5);
30
+ }
31
+ &.last {
32
+ border-bottom: 1px solid rgba(59, 130, 246, 0.5);
33
+ }
34
+ }
35
+
36
+ h1 { font-size: 2.25rem; font-weight: 800; }
37
+ h2 { font-size: 1.875rem; font-weight: 700; }
38
+ h3 { font-size: 1.5rem; font-weight: 700; }
39
+ h4 { font-size: 1.25rem; font-weight: 600; }
40
+ h5 { font-size: 1.125rem; font-weight: 600; }
41
+ h6 { font-size: 1rem; font-weight: 600; }
42
+ </style>
@@ -0,0 +1,11 @@
1
+ export default Heading;
2
+ type Heading = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ declare const Heading: import("svelte").Component<{
7
+ block: import("./heading.svelte.js").Heading;
8
+ }, {}, "">;
9
+ type $$ComponentProps = {
10
+ block: import("./heading.svelte.js").Heading;
11
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @import { Nabu, NabuNode } from "../nabu.svelte";
3
+ * @import { TextNode } from "../../behaviors/text";
4
+ */
5
+ /**
6
+ * @typedef {NabuNode<{type: "heading", text: LoroText, level: number}>} HeadingNode
7
+ */
8
+ export class Heading extends Block {
9
+ /** @param {Nabu} nabu @param {string} type @param {Object} [props={}] @param {string|null} [parentId=null] @param {number|null} [index=null] */
10
+ static create(nabu: Nabu, type: string, props?: Object, parentId?: string | null, index?: number | null): Heading;
11
+ /** @param {Nabu} nabu @param {HeadingNode} node */
12
+ constructor(nabu: Nabu, node: HeadingNode);
13
+ container: LoroText;
14
+ /** @type {TextBehavior} */
15
+ behavior: TextBehavior;
16
+ /** @type {number} */
17
+ level: number;
18
+ component: import("svelte").Component<any, any, string> | import("svelte").Component<{
19
+ block: import("./heading.svelte").Heading;
20
+ }, {}, "">;
21
+ get text(): string;
22
+ get delta(): import("loro-crdt").Delta<string>[];
23
+ selection: {
24
+ from: number;
25
+ to: number;
26
+ isCollapsed: boolean;
27
+ direction: "forward" | "backward" | "none";
28
+ } | null;
29
+ /** @param {Block} block */
30
+ absorbs(block: Block): boolean;
31
+ /** @param {import('loro-crdt').Delta<string>[]} data */
32
+ applyDelta(data?: import("loro-crdt").Delta<string>[]): void;
33
+ /** @param {Parameters<Block["split"]>[0]} [options] @returns {ReturnType<Block["split"]>} */
34
+ split(options?: Parameters<Block["split"]>[0]): ReturnType<Block["split"]>;
35
+ }
36
+ export type HeadingNode = NabuNode<{
37
+ type: "heading";
38
+ text: LoroText;
39
+ level: number;
40
+ }>;
41
+ import { Block } from "../block.svelte";
42
+ import { LoroText } from "loro-crdt";
43
+ import { TextBehavior } from "../../behaviors/text";
44
+ import type { Nabu } from "../nabu.svelte";
45
+ import type { NabuNode } from "../nabu.svelte";