@quillmark/svelte 0.26.2 → 0.28.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 (97) hide show
  1. package/NOTICE +4 -4
  2. package/README.md +26 -8
  3. package/THEMING.md +40 -4
  4. package/dist/core/address.d.ts +35 -64
  5. package/dist/core/address.js +35 -55
  6. package/dist/core/bloom.d.ts +4 -10
  7. package/dist/core/bloom.js +14 -27
  8. package/dist/core/codec/blocks.d.ts +41 -43
  9. package/dist/core/codec/blocks.js +97 -130
  10. package/dist/core/codec/code.d.ts +7 -10
  11. package/dist/core/codec/code.js +37 -46
  12. package/dist/core/codec/decode.d.ts +19 -23
  13. package/dist/core/codec/decode.js +76 -111
  14. package/dist/core/codec/encode.d.ts +11 -15
  15. package/dist/core/codec/encode.js +60 -67
  16. package/dist/core/codec/field.d.ts +62 -95
  17. package/dist/core/codec/field.js +107 -179
  18. package/dist/core/codec/islands.d.ts +33 -41
  19. package/dist/core/codec/islands.js +76 -86
  20. package/dist/core/codec/lists.d.ts +17 -26
  21. package/dist/core/codec/lists.js +117 -157
  22. package/dist/core/codec/marks.d.ts +4 -3
  23. package/dist/core/codec/marks.js +8 -3
  24. package/dist/core/codec/prose.css +84 -46
  25. package/dist/core/codec/schema.d.ts +18 -23
  26. package/dist/core/codec/schema.js +84 -76
  27. package/dist/core/codec/slash.d.ts +42 -48
  28. package/dist/core/codec/slash.js +0 -0
  29. package/dist/core/codec/table-copy.d.ts +4 -3
  30. package/dist/core/codec/table-copy.js +25 -7
  31. package/dist/core/codec/table-view.d.ts +12 -12
  32. package/dist/core/codec/table-view.js +460 -383
  33. package/dist/core/codec/table.d.ts +94 -61
  34. package/dist/core/codec/table.js +331 -83
  35. package/dist/core/codec/tasks.d.ts +6 -0
  36. package/dist/core/codec/tasks.js +30 -3
  37. package/dist/core/errors.d.ts +15 -30
  38. package/dist/core/errors.js +2 -6
  39. package/dist/core/index.js +7 -12
  40. package/dist/core/lifecycle.d.ts +7 -16
  41. package/dist/core/lifecycle.js +10 -21
  42. package/dist/core/rebind.svelte.d.ts +10 -20
  43. package/dist/core/rebind.svelte.js +15 -27
  44. package/dist/core/rungs.d.ts +14 -11
  45. package/dist/core/rungs.js +17 -19
  46. package/dist/core/teardown.d.ts +4 -6
  47. package/dist/core/teardown.js +7 -10
  48. package/dist/core/theme.css +74 -54
  49. package/dist/index.js +3 -8
  50. package/dist/preset/app.css +71 -0
  51. package/dist/preset/index.css +8 -7
  52. package/dist/preset/recipes.css +19 -164
  53. package/dist/preset/scale.css +28 -18
  54. package/dist/preset/shell.css +157 -0
  55. package/dist/preview/Preview.svelte +8 -0
  56. package/dist/preview/bridge.d.ts +18 -22
  57. package/dist/preview/bridge.js +29 -58
  58. package/dist/preview/controller.d.ts +21 -29
  59. package/dist/preview/controller.js +14 -39
  60. package/dist/preview/geometry.d.ts +13 -34
  61. package/dist/preview/geometry.js +13 -34
  62. package/dist/preview/index.js +4 -16
  63. package/dist/preview/paint.d.ts +3 -3
  64. package/dist/preview/paint.js +28 -68
  65. package/dist/preview/strings.d.ts +1 -1
  66. package/dist/preview/strings.js +2 -14
  67. package/dist/preview/viewport.js +20 -15
  68. package/dist/preview/zoom.d.ts +21 -22
  69. package/dist/preview/zoom.js +53 -30
  70. package/dist/visual/ArrayField.svelte +77 -16
  71. package/dist/visual/BooleanField.svelte +28 -15
  72. package/dist/visual/Card.svelte +66 -51
  73. package/dist/visual/CardControls.svelte +5 -11
  74. package/dist/visual/DateField.svelte +20 -4
  75. package/dist/visual/EnumField.svelte +3 -0
  76. package/dist/visual/FieldHint.svelte +2 -1
  77. package/dist/visual/FormatPopover.svelte +82 -15
  78. package/dist/visual/FormatPopover.svelte.d.ts +4 -0
  79. package/dist/visual/MatrixAdd.svelte +30 -12
  80. package/dist/visual/MatrixField.svelte +24 -16
  81. package/dist/visual/NumberField.svelte +7 -0
  82. package/dist/visual/ObjectField.svelte +4 -4
  83. package/dist/visual/ProseField.svelte +0 -5
  84. package/dist/visual/TipsCard.svelte +11 -10
  85. package/dist/visual/VariantField.svelte +3 -1
  86. package/dist/visual/VisualEditorInner.svelte +19 -3
  87. package/dist/visual/controls.css +7 -2
  88. package/dist/visual/fonts.d.ts +18 -0
  89. package/dist/visual/fonts.js +59 -0
  90. package/dist/visual/icons/nodes.d.ts +0 -5
  91. package/dist/visual/icons/nodes.js +0 -4
  92. package/dist/visual/props.d.ts +14 -1
  93. package/dist/visual/strings.d.ts +7 -0
  94. package/dist/visual/strings.js +5 -2
  95. package/dist/visual/structure.d.ts +43 -87
  96. package/dist/visual/structure.js +42 -83
  97. package/package.json +2 -2
@@ -2,9 +2,11 @@ import { Selection, TextSelection } from 'prosemirror-state';
2
2
  import { newlineInCode } from 'prosemirror-commands';
3
3
  /** One indent level. */
4
4
  const UNIT = ' ';
5
- /** The enclosing code block's content start and text, or `null` when the selection
6
- * is not wholly inside one: a selection that escapes the block is a document-level
7
- * range, where the structural links keep their meaning. */
5
+ /**
6
+ * The enclosing code block's content start and text, or `null` when the selection is not wholly
7
+ * inside one: a selection that escapes is a document-level range, where the structural links keep
8
+ * their meaning.
9
+ */
8
10
  function codeBlockAt(state) {
9
11
  const { $from, $to } = state.selection;
10
12
  if (!$from.parent.type.spec.code)
@@ -14,11 +16,8 @@ function codeBlockAt(state) {
14
16
  return { start: $from.start(), text: $from.parent.textContent };
15
17
  }
16
18
  /**
17
- * Start offsets of the lines the range `[f, t)` covers, in document order.
18
- *
19
- * A line whose start is `t` is excluded on a non-empty range: a selection ending at
20
- * a line start does not reach into that line, the editor convention (else selecting
21
- * whole lines by dragging to the next line's start indents one line too many).
19
+ * Start offsets of the lines the range `[f, t)` covers. A line whose start is `t` is excluded on a
20
+ * non-empty range, so dragging to the next line's start does not indent one line too many.
22
21
  */
23
22
  function coveredLineStarts(text, f, t) {
24
23
  const last = t > f ? t - 1 : t;
@@ -31,9 +30,10 @@ function coveredLineStarts(text, f, t) {
31
30
  }
32
31
  return starts;
33
32
  }
34
- /** Width of the indent one outdent removes at line offset `s`: a leading tab, else
35
- * up to one unit of leading spaces (both forms, whatever Tab inserts). `0` when the
36
- * line carries no indent. */
33
+ /**
34
+ * Width of the indent one outdent removes at line offset `s`: a leading tab, else up to one unit of
35
+ * leading spaces; `0` if none.
36
+ */
37
37
  function outdentWidth(text, s) {
38
38
  if (text[s] === '\t')
39
39
  return 1;
@@ -43,9 +43,8 @@ function outdentWidth(text, s) {
43
43
  return n;
44
44
  }
45
45
  /**
46
- * Tab in a code block: insert one indent unit, or indent every covered line when the
47
- * selection spans a newline. A single-line selection is replaced by the unit: Tab
48
- * types, the way any other key does.
46
+ * Tab in a code block: insert one indent unit, or indent every covered line when the selection
47
+ * spans a newline. A single-line selection is replaced by the unit, as any key types.
49
48
  */
50
49
  const indentInCode = (state, dispatch) => {
51
50
  const block = codeBlockAt(state);
@@ -73,11 +72,9 @@ const indentInCode = (state, dispatch) => {
73
72
  return true;
74
73
  };
75
74
  /**
76
- * Shift-Tab in a code block: remove one indent level from every covered line.
77
- *
78
- * Declines when no covered line carries an indent, so the key falls through to the
79
- * list link: inside a `list_item > code_block` it lifts the item out
80
- * (`tests/codec/code-keys.test.ts`), and outside a list the body holds it (`keymap.ts`).
75
+ * Shift-Tab in a code block: remove one indent level from every covered line. Declines when none
76
+ * carries an indent, so the key falls through to the list link (lifting a `list_item > code_block`
77
+ * out of the item; `tests/codec/code-keys.test.ts`) or the body (`keymap.ts`).
81
78
  */
82
79
  const outdentInCode = (state, dispatch) => {
83
80
  const block = codeBlockAt(state);
@@ -99,10 +96,9 @@ const outdentInCode = (state, dispatch) => {
99
96
  return true;
100
97
  };
101
98
  /**
102
- * The textblock the caret's own block stands directly against on `side`: its sibling
103
- * under one parent, which is the only neighbour a press joins the text of. `null` at a
104
- * container's edge, where the base keymap moves the whole block in or out rather than
105
- * joining anything, and so changes nothing here.
99
+ * The textblock the caret's block stands directly against on `side`: its sibling under one parent,
100
+ * the only neighbour a press joins text with. `null` at a container's edge, where the base keymap
101
+ * moves the whole block instead.
106
102
  */
107
103
  function blockAgainst(state, side) {
108
104
  const { $from, empty } = state.selection;
@@ -117,18 +113,15 @@ function blockAgainst(state, side) {
117
113
  return { node, pos: side < 0 ? $edge.pos - node.nodeSize : $edge.pos };
118
114
  }
119
115
  /**
120
- * Backspace or Delete at a fence's edge, from either side, edits only the block the caret
121
- * is in.
116
+ * Backspace or Delete at a fence's edge, from either side, edits only the block the caret is in.
122
117
  *
123
- * Inside the fence, a press at its head or its end retypes it to a paragraph and joins
124
- * nothing: `toCodeBlock`'s inverse in its place (`blocks.ts`), taking no text with it,
125
- * since `linebreakReplacement` carries the fence's newlines across as breaks
126
- * (`schema.ts`). The press after that is an ordinary join between two prose blocks.
118
+ * Inside the fence, a press at its head or end retypes it to a paragraph and joins nothing
119
+ * (`toCodeBlock`'s inverse, `blocks.ts`); `linebreakReplacement` carries the newlines across as
120
+ * breaks (`schema.ts`). The press after is an ordinary prose join.
127
121
  *
128
- * From the prose block against it, the caret steps into the fence, to the end it faces,
129
- * and the fence is left as it is: a join would retype one side's whole text as the
130
- * other's kind, and a retype from outside edits a block the caret is not in. An empty
131
- * line under a fence goes with the step, its Backspace having nothing of its own to edit.
122
+ * From the prose block against it, the caret steps into the fence at the end it faces and the fence
123
+ * is left as it is: a join would retype one side's text as the other's kind. An empty line under a
124
+ * fence goes with the step.
132
125
  *
133
126
  * Declines where both sides are code and where neither is: those joins retype nothing.
134
127
  */
@@ -155,9 +148,10 @@ function atProseEdge(side) {
155
148
  return true;
156
149
  };
157
150
  }
158
- /** ↓ on the last line of a fence nothing follows → a paragraph after it, the caret
159
- * there: no block stands below to arrow to, and a gap cursor will not sit beside a
160
- * fence. */
151
+ /**
152
+ * ↓ on the last line of a fence nothing follows → a paragraph after it, the caret there: a gap
153
+ * cursor will not sit beside a fence.
154
+ */
161
155
  const belowFinalFence = (state, dispatch) => {
162
156
  const paragraph = state.schema.nodes.paragraph;
163
157
  const { $from, empty } = state.selection;
@@ -177,17 +171,14 @@ const belowFinalFence = (state, dispatch) => {
177
171
  return true;
178
172
  };
179
173
  /**
180
- * The `code_block` link of the body's key chains: `{}` for the one-line and plain
181
- * schemas, which declare no code node.
174
+ * The `code_block` link of the body's key chains: `{}` for the one-line and plain schemas, which
175
+ * declare no code node.
182
176
  *
183
- * `Enter` is upstream's `newlineInCode`, and it is load-bearing rather than
184
- * cosmetic: inside a `list_item > code_block` the list link's `splitListItem`
185
- * otherwise splits the item in two, each half holding a code block. `Shift-Enter` is
186
- * the same command because it is the same edit: the line break the key means
187
- * everywhere else (`breaks.ts`) is, inside a fence, the newline Enter already takes.
188
- * Every command here declines away from a code block — inside one for the keys about
189
- * its text and its last line, against one for the two about its edge — so the list
190
- * links keep the keys elsewhere.
177
+ * `Enter` is upstream's `newlineInCode`, load-bearing: inside a `list_item > code_block` the list
178
+ * link's `splitListItem` would otherwise split the item in two. `Shift-Enter` is the same command:
179
+ * the line break the key means elsewhere (`breaks.ts`) is, inside a fence, the newline Enter takes.
180
+ * Every command here declines away from a code block (inside it for the text and last-line keys,
181
+ * against it for the two edge keys), leaving the list links the keys elsewhere.
191
182
  */
192
183
  export function codeKeymap(schema) {
193
184
  if (!schema.nodes.code_block)
@@ -5,35 +5,31 @@ export declare function codePoints(s: string): string[];
5
5
  /** USV length of `s` (code points, not UTF-16 units). */
6
6
  export declare function usvLength(s: string): number;
7
7
  /**
8
- * A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM`.
9
- * The rendering half of the codec's job with no editing attached (no PM view, no
10
- * plugins, no `contenteditable`) for chrome that must show content in the same
11
- * mark vocabulary a leaf edits it in (the tips card). A second renderer over the
12
- * same content would drift from `decode`; this cannot.
13
- *
14
- * `DOMSerializer.fromSchema` memoizes on the schema, so repeat calls build no
15
- * serializer.
8
+ * A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM`, for chrome that
9
+ * must show content in the mark vocabulary a leaf edits it in (the tips card) without a second
10
+ * renderer to drift from `decode`. `DOMSerializer.fromSchema` memoizes on the schema.
16
11
  */
17
12
  export declare function renderContent(rt: Content, schema: Schema): Node;
18
13
  /** Decode a `Content` to a PM document under `schema`. */
19
14
  export declare function decode(rt: Content, schema: Schema): PMNode;
20
- /** Whether `rt` decodes under an inline schema with nothing but whitespace dropped:
21
- * upstream's one-line rule, or one plain line and the empty one a trailing `\n`
22
- * opens. That newline is the one a YAML `|` scalar keeps in a `text` value, which
23
- * upstream ignores and the inline decode drops. Marks are the schema's own business
24
- * (`plaintextSchema` declares none). */
15
+ /**
16
+ * Whether `rt` decodes under an inline schema with nothing but whitespace dropped: upstream's
17
+ * one-line rule, or one plain line and the empty one a trailing `\n` opens (the one a YAML `|`
18
+ * scalar keeps in a `text` value). Marks are the schema's business.
19
+ */
25
20
  export declare function fitsInline(rt: Content): boolean;
26
- /** Whether `rt` decodes under `plainSchema` losing nothing but its marks, which the
27
- * next commit heals (CODEC §Inline mode): upstream's `isPlain`, asked of the content
28
- * with them set aside. */
21
+ /**
22
+ * Whether `rt` decodes under `plainSchema` losing nothing but its marks, which the next commit
23
+ * heals (CODEC §Inline mode): upstream's `isPlain` with marks set aside.
24
+ */
29
25
  export declare function fitsPlain(rt: Content): boolean;
30
- /** Whether a leaf so declared edits `rt` losing nothing: {@link fitsInline} narrowed
31
- * and {@link fitsPlain} plain, past which the decode drops what the first commit would
32
- * store; on the block schema anything but a construct read and not edited — an element,
33
- * a definition list ({@link editable}), a footnote or an equation (`isEditedIsland`) —
34
- * and an island where the leaf mounts no island view (`islands`), which draws as its
35
- * placeholder tag, an atom a keystroke deletes unseen. What a leaf held over the rest
36
- * reads. */
26
+ /**
27
+ * Whether a leaf so declared edits `rt` losing nothing: {@link fitsInline} narrowed and {@link
28
+ * fitsPlain} plain, past which the decode drops what the first commit would store. On the block
29
+ * schema, anything but a construct read and not edited (`isEditedIsland`) and an island where the
30
+ * leaf mounts no island view (`islands`), which draws as a placeholder atom a keystroke deletes
31
+ * unseen.
32
+ */
37
33
  export declare function fitsLeaf(rt: Content, leaf: {
38
34
  inline: boolean;
39
35
  plaintext: boolean;
@@ -1,12 +1,9 @@
1
- // Decode: content → PM, a pure function (CODEC §Decode). Fold the flat lines into
2
- // the tree: group a `continues` run into one block (para hard breaks → `hard_break`
3
- // nodes; a code fence's lines → one `code_block`), nest by shared `containers`
4
- // prefix (`list_item`/`quote` → lists/blockquote), select the block node by
5
- // `kind`, apply marks over their `[start,end)` USV ranges (PM splits inline nodes
6
- // at mark boundaries), and lower island slots to leaf nodes (block where a block
7
- // island's slot stands alone on its line, inline otherwise). Anchors are not
8
- // applied here; they are decorations (field.ts). Positions throughout are USV; `Array.from` iterates by
9
- // code point so an astral char is one unit, never a surrogate half.
1
+ // Decode: content → PM, a pure function (CODEC §Decode). Fold the flat lines into the tree: group a
2
+ // `continues` run into one block (para hard breaks → `hard_break`; a fence's lines → one
3
+ // `code_block`), nest by shared `containers` prefix, select the block node by `kind`, apply marks
4
+ // over their `[start,end)` USV ranges, and lower island slots to leaf nodes. Anchors are
5
+ // decorations (field.ts). Positions are USV; `Array.from` iterates by code point so an astral char
6
+ // is one unit.
10
7
  import { DOMSerializer } from 'prosemirror-model';
11
8
  import { ISLAND_SLOT, isBlockIsland, isEditedIsland } from './islands.js';
12
9
  import { descriptorOf, markKey, pmMarkFromContent } from './marks.js';
@@ -24,14 +21,9 @@ export function usvLength(s) {
24
21
  return n;
25
22
  }
26
23
  /**
27
- * A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM`.
28
- * The rendering half of the codec's job with no editing attached (no PM view, no
29
- * plugins, no `contenteditable`) for chrome that must show content in the same
30
- * mark vocabulary a leaf edits it in (the tips card). A second renderer over the
31
- * same content would drift from `decode`; this cannot.
32
- *
33
- * `DOMSerializer.fromSchema` memoizes on the schema, so repeat calls build no
34
- * serializer.
24
+ * A `Content` as read-only DOM: decode under `schema`, then the nodes' own `toDOM`, for chrome that
25
+ * must show content in the mark vocabulary a leaf edits it in (the tips card) without a second
26
+ * renderer to drift from `decode`. `DOMSerializer.fromSchema` memoizes on the schema.
35
27
  */
36
28
  export function renderContent(rt, schema) {
37
29
  return DOMSerializer.fromSchema(schema).serializeFragment(decode(rt, schema).content);
@@ -46,14 +38,12 @@ export function decode(rt, schema) {
46
38
  starts.push(acc);
47
39
  acc += usvLength(lineTexts[i]) + 1; // +1 for the `\n` boundary
48
40
  }
49
- // A mark-free schema (`plaintextSchema`) has nothing to apply them with, and a
50
- // `text` field that acquired one anyway — an older build's popover, a hand-written
51
- // document — is exactly the content that must open and then heal on the next
52
- // commit, not throw here.
41
+ // A mark-free schema has nothing to apply marks with, and a `text` field that acquired one
42
+ // anyway (a hand-written document) must open and heal on the next commit, not throw.
53
43
  const marks = hasMarks(schema) ? rt.marks.filter((m) => m.type !== 'anchor') : [];
54
44
  const cursor = { i: 0, rt };
55
45
  if (isInlineSchema(schema)) {
56
- return decodeInline(rt, schema, lineTexts, starts, marks, cursor);
46
+ return decodeInline(schema, lineTexts, starts, marks, cursor);
57
47
  }
58
48
  if (!isBlockSchema(schema)) {
59
49
  return decodePlain(rt, schema, lineTexts, starts, marks, cursor);
@@ -73,11 +63,11 @@ export function decode(rt, schema) {
73
63
  const blocks = groupBlocks(schema, leaves, 0, marks, cursor);
74
64
  return schema.nodes.doc.create(null, blocks.length ? blocks : schema.nodes.paragraph.create());
75
65
  }
76
- /** Whether `rt` decodes under an inline schema with nothing but whitespace dropped:
77
- * upstream's one-line rule, or one plain line and the empty one a trailing `\n`
78
- * opens. That newline is the one a YAML `|` scalar keeps in a `text` value, which
79
- * upstream ignores and the inline decode drops. Marks are the schema's own business
80
- * (`plaintextSchema` declares none). */
66
+ /**
67
+ * Whether `rt` decodes under an inline schema with nothing but whitespace dropped: upstream's
68
+ * one-line rule, or one plain line and the empty one a trailing `\n` opens (the one a YAML `|`
69
+ * scalar keeps in a `text` value). Marks are the schema's business.
70
+ */
81
71
  export function fitsInline(rt) {
82
72
  if (core().isInline(rt))
83
73
  return true;
@@ -88,19 +78,20 @@ export function fitsInline(rt) {
88
78
  !rt.lines[1].continues &&
89
79
  rt.text.endsWith('\n'));
90
80
  }
91
- /** Whether `rt` decodes under `plainSchema` losing nothing but its marks, which the
92
- * next commit heals (CODEC §Inline mode): upstream's `isPlain`, asked of the content
93
- * with them set aside. */
81
+ /**
82
+ * Whether `rt` decodes under `plainSchema` losing nothing but its marks, which the next commit
83
+ * heals (CODEC §Inline mode): upstream's `isPlain` with marks set aside.
84
+ */
94
85
  export function fitsPlain(rt) {
95
86
  return core().isPlain({ ...rt, marks: [] });
96
87
  }
97
- /** Whether a leaf so declared edits `rt` losing nothing: {@link fitsInline} narrowed
98
- * and {@link fitsPlain} plain, past which the decode drops what the first commit would
99
- * store; on the block schema anything but a construct read and not edited — an element,
100
- * a definition list ({@link editable}), a footnote or an equation (`isEditedIsland`) —
101
- * and an island where the leaf mounts no island view (`islands`), which draws as its
102
- * placeholder tag, an atom a keystroke deletes unseen. What a leaf held over the rest
103
- * reads. */
88
+ /**
89
+ * Whether a leaf so declared edits `rt` losing nothing: {@link fitsInline} narrowed and {@link
90
+ * fitsPlain} plain, past which the decode drops what the first commit would store. On the block
91
+ * schema, anything but a construct read and not edited (`isEditedIsland`) and an island where the
92
+ * leaf mounts no island view (`islands`), which draws as a placeholder atom a keystroke deletes
93
+ * unseen.
94
+ */
104
95
  export function fitsLeaf(rt, leaf) {
105
96
  if (leaf.inline)
106
97
  return fitsInline(rt);
@@ -108,22 +99,13 @@ export function fitsLeaf(rt, leaf) {
108
99
  return fitsPlain(rt);
109
100
  if (!leaf.islands && rt.islands.length)
110
101
  return false;
111
- return rt.islands.every((isl) => isEditedIsland(isl.type)) && rt.lines.every(editable);
112
- }
113
- /** Whether the block schema edits `line`'s kind and every container on it: a term draws
114
- * as a paragraph and an element and a description as the blocks they wrap, so the first
115
- * commit would drop each. */
116
- function editable(line) {
117
- if (line.kind === 'term')
118
- return false;
119
- return line.containers.every((c) => c.container === 'quote' || c.container === 'list_item');
102
+ return rt.islands.every((isl) => isEditedIsland(isl.type));
120
103
  }
121
104
  /** Inline decode: one paragraph, containers and islands stripped. */
122
- function decodeInline(rt, schema, lineTexts, starts, marks, cursor) {
123
- // An inline field is single-line; join any stray lines with a space (no
124
- // hard_break node exists in this schema). The empty line a trailing `\n` opens is
125
- // dropped rather than joined, so a first edit stores no space nobody typed. Islands
126
- // are not representable inline: the slot char is dropped and its entry skipped.
105
+ function decodeInline(schema, lineTexts, starts, marks, cursor) {
106
+ // An inline field is single-line: stray lines join with a space. The empty line a trailing `\n`
107
+ // opens is dropped, so a first edit stores no space nobody typed. Islands are not representable
108
+ // inline: the slot char is dropped and its entry skipped.
127
109
  const inline = [];
128
110
  const last = lineTexts.length - 1;
129
111
  for (let i = 0; i < lineTexts.length; i++) {
@@ -136,9 +118,11 @@ function decodeInline(rt, schema, lineTexts, starts, marks, cursor) {
136
118
  const para = schema.nodes.paragraph.create(null, inline);
137
119
  return schema.nodes.doc.create(null, para);
138
120
  }
139
- /** Plain decode: a `continues` line is a `hard_break` in the paragraph above and every
140
- * other line opens one. A line's kind and containers and every island have no node
141
- * here and are dropped, so a content {@link fitsPlain} refuses opens flattened. */
121
+ /**
122
+ * Plain decode: a `continues` line is a `hard_break` in the paragraph above and every other line
123
+ * opens one. Line kinds, containers and islands are dropped, so content {@link fitsPlain} refuses
124
+ * opens flattened.
125
+ */
142
126
  function decodePlain(rt, schema, lineTexts, starts, marks, cursor) {
143
127
  const paras = [];
144
128
  for (let i = 0; i < rt.lines.length; i++) {
@@ -198,25 +182,9 @@ function groupBlocks(schema, leaves, depth, marks, cursor) {
198
182
  i = j;
199
183
  continue;
200
184
  }
201
- if (here.container === 'element' || here.container === 'description') {
202
- // No node holds an element or a description, and a leaf holding one is held
203
- // (`fitsLeaf`), so what it wraps draws in place, read-only.
204
- const key = containerKey(here);
205
- let j = i + 1;
206
- while (j < leaves.length) {
207
- const c = atDepth(leaves[j], depth);
208
- if (!c || containerKey(c) !== key)
209
- break;
210
- j++;
211
- }
212
- out.push(...groupBlocks(schema, leaves.slice(i, j), depth + 1, marks, cursor));
213
- i = j;
214
- continue;
215
- }
216
- // The quote, and the whole of what is left: `satisfies` is the exhaustiveness
217
- // check, so a container added upstream is a compile error here rather than a
218
- // blockquote it is not. A wrapper over the run of leaves carrying the identical
219
- // container here, identity by `containerKey`.
185
+ // The quote, and the whole of what is left: `satisfies` makes a container added upstream a
186
+ // compile error. A wrapper over the run of leaves carrying the identical container
187
+ // (`containerKey`).
220
188
  here.container;
221
189
  const key = containerKey(here);
222
190
  let j = i + 1;
@@ -235,8 +203,10 @@ function groupBlocks(schema, leaves, depth, marks, cursor) {
235
203
  function atDepth(leaf, depth) {
236
204
  return leaf.containers[depth];
237
205
  }
238
- /** The `ordinal` of a leaf's `list_item` at `depth`: only called inside a run the
239
- * discriminant has already established, so a miss is unreachable. */
206
+ /**
207
+ * The `ordinal` of a leaf's `list_item` at `depth`, only called inside a run the discriminant
208
+ * established.
209
+ */
240
210
  function ordinalAt(leaf, depth) {
241
211
  const c = atDepth(leaf, depth);
242
212
  return c?.container === 'list_item' ? c.attrs.ordinal : -1;
@@ -247,22 +217,23 @@ function checkedAt(leaf, depth) {
247
217
  const c = atDepth(leaf, depth);
248
218
  return c?.container === 'list_item' ? (c.attrs.checked ?? null) : null;
249
219
  }
250
- /** A container's `instance`, absent meaning the zero a canonical read omits. It is what
251
- * tells one container from an adjacent sibling of identical shape, which contiguity
252
- * alone reads as one, so absent and `0` have to answer alike. */
220
+ /**
221
+ * A container's `instance`, absent meaning the zero a canonical read omits. It tells one container
222
+ * from an adjacent sibling of identical shape, so absent and `0` answer alike.
223
+ */
253
224
  function instanceOf(c) {
254
225
  return c.instance ?? 0;
255
226
  }
256
- /** Identity of a container for run gathering: its name, its `instance` and an element's
257
- * attributes, NUL-joined as `markKey` joins a mark's. `list_item` carries its own run
258
- * rule, so the rest are what key here. */
227
+ /** Identity of a container for run gathering: its name and its `instance`, NUL-joined
228
+ * as `markKey` joins a mark's. `list_item` carries its own run rule, so the rest are what
229
+ * key here. */
259
230
  function containerKey(c) {
260
- const attrs = c.container === 'element' ? JSON.stringify(c.attrs) : '';
261
- return `${c.container}\u0000${instanceOf(c)}\u0000${attrs}`;
231
+ return `${c.container}\u0000${instanceOf(c)}`;
262
232
  }
263
- /** A single leaf block node from its segments. Exhaustive over the closed line
264
- * vocabulary, so a kind added upstream is a compile error rather than a silent
265
- * flattening to `para` that the next commit would store. */
233
+ /**
234
+ * A single leaf block node from its segments, exhaustive over the closed line vocabulary so a kind
235
+ * added upstream is a compile error.
236
+ */
266
237
  function makeLeaf(schema, leaf, marks, cursor) {
267
238
  const line = leaf.line;
268
239
  switch (line.kind) {
@@ -275,15 +246,12 @@ function makeLeaf(schema, leaf, marks, cursor) {
275
246
  return schema.nodes.code_block.create({ lang: line.attrs?.lang ?? null }, content);
276
247
  }
277
248
  case 'para':
278
- case 'term':
279
249
  case 'heading': {
280
250
  if (line.kind !== 'heading' && blockIslandLine(leaf, cursor))
281
251
  return schema.nodes.island_block.create(islandAttrs(cursor));
282
- // Inline content, the segment boundary a `hard_break` in a paragraph and a space
283
- // in a heading, which takes none (`schema.ts` §`takesLineBreak`). A stored heading
284
- // carries no continuation — the store clears one after a block of a single line —
285
- // so the space is what keeps a hand-built content decodable. A term draws as a
286
- // paragraph, read-only (`fitsLeaf`).
252
+ // Inline content: the segment boundary is a `hard_break` in a paragraph and a space in
253
+ // a heading, which takes none (`schema.ts` §`takesLineBreak`). A stored heading carries
254
+ // no continuation, so the space keeps hand-built content decodable.
287
255
  const type = line.kind === 'heading' ? schema.nodes.heading : schema.nodes.paragraph;
288
256
  const inline = [];
289
257
  leaf.segments.forEach((seg, idx) => {
@@ -301,29 +269,29 @@ function makeLeaf(schema, leaf, marks, cursor) {
301
269
  }
302
270
  }
303
271
  }
304
- /** Whether a `para` leaf is one block island: a single line holding its slot alone,
305
- * backed by an island whose markup is a block. The island is the one fact; the line
306
- * carries no kind of its own for it. */
272
+ /**
273
+ * Whether a `para` leaf is one block island: a single line holding its slot alone, backed by an
274
+ * island whose markup is a block.
275
+ */
307
276
  function blockIslandLine(leaf, cursor) {
308
277
  if (leaf.segments.length !== 1 || leaf.segments[0].text !== ISLAND_SLOT)
309
278
  return false;
310
279
  const isl = cursor.rt.islands[cursor.i];
311
280
  return isl != null && isBlockIsland(isl.type);
312
281
  }
313
- /** Consume the next island entry as PM node attrs (text-order matched to slots): the
314
- * whole entry, which an island edit has to write back (`islands.ts`). `null` where a
315
- * slot has no entry behind it, which is a malformed content: the slot draws nothing
316
- * and its line an empty paragraph, so the decode stays total without minting a node
317
- * whose `type` the vocabulary would refuse on the way back. */
282
+ /**
283
+ * Consume the next island entry as PM node attrs (text-order matched to slots): the whole entry,
284
+ * which an island edit writes back (`islands.ts`). `null` where a slot has no entry (malformed
285
+ * content): the slot draws nothing, keeping the decode total.
286
+ */
318
287
  function islandAttrs(cursor) {
319
288
  const isl = cursor.rt.islands[cursor.i++];
320
289
  return isl ? { id: isl.id, islandType: isl.type, props: isl.props } : null;
321
290
  }
322
291
  /**
323
- * Inline nodes for one segment: split text into maximal runs of a constant mark
324
- * set, lower each `U+FFFC` to an inline island node. `stripIslands` drops slots
325
- * (inline-field mode). Offsets are USV; `pos = segStartUSV + k` indexes into the
326
- * global mark ranges.
292
+ * Inline nodes for one segment: split text into maximal runs of a constant mark set, lower each
293
+ * `U+FFFC` to an inline island node. `stripIslands` drops slots. `pos = segStartUSV + k` indexes
294
+ * into the global mark ranges.
327
295
  */
328
296
  function buildInline(schema, segText, segStartUSV, marks, cursor, stripIslands) {
329
297
  const cps = codePoints(segText);
@@ -336,9 +304,8 @@ function buildInline(schema, segText, segStartUSV, marks, cursor, stripIslands)
336
304
  out.push(schema.text(runText, runMarks));
337
305
  runText = '';
338
306
  };
339
- // A mark set changes only where a mark starts or ends. `marks` is the field's whole
340
- // list, so resolving one per code point costs the text against every mark in the
341
- // document.
307
+ // A mark set changes only where a mark starts or ends; `marks` is the whole list, so resolving
308
+ // per code point would cost the text against every mark.
342
309
  const segEndUSV = segStartUSV + cps.length;
343
310
  const spanning = marks.filter((m) => m.start < segEndUSV && m.end > segStartUSV);
344
311
  const edges = new Set();
@@ -384,10 +351,8 @@ function buildInline(schema, segText, segStartUSV, marks, cursor, stripIslands)
384
351
  function markSetKey(active) {
385
352
  if (!active.length)
386
353
  return '';
387
- // Per-mark keys via the shared descriptor + NUL-delimited `markKey`: the same
388
- // pair the mark diff groups families by, so a run boundary and a mark family are
389
- // one rule. The set is JSON-joined so no url/attrs content collides with a
390
- // delimiter: a `link:a|strong` url stays distinct from `{link:a} + {strong}`.
354
+ // Per-mark keys via the shared descriptor + NUL-delimited `markKey`, the pair the mark diff
355
+ // groups by. JSON-joined so a `link:a|strong` url stays distinct from `{link:a} + {strong}`.
391
356
  return JSON.stringify(active.map((m) => markKey(descriptorOf(m))).sort());
392
357
  }
393
358
  /** The PM mark array for an active content mark set (anchors already excluded). */
@@ -25,13 +25,14 @@ export interface Scan {
25
25
  usvEnd: number;
26
26
  }
27
27
  /**
28
- * Walk a PM document once, producing the content projection and the position-map
29
- * runs (they share the traversal so they can never disagree). This is the single
30
- * source both `pmToContent` and the position map draw from.
28
+ * Walk a PM document once, producing the content projection and the position-map runs; sharing the
29
+ * traversal means they cannot disagree.
31
30
  */
32
31
  export declare function scanDoc(doc: PMNode): Scan;
33
- /** The content projection a scan carries (drops the position runs): what a caller
34
- * holding a `Scan` reads instead of walking the document a second time. */
32
+ /**
33
+ * The content projection a scan carries, for a caller holding a `Scan` that would otherwise walk
34
+ * again.
35
+ */
35
36
  export declare function scanContent(s: Scan): Content;
36
37
  /** `pmToContent`: the pure inverse of decode (drops the position runs). */
37
38
  export declare function pmToContent(doc: PMNode): Content;
@@ -44,16 +45,11 @@ interface LowerOpts {
44
45
  }[];
45
46
  }
46
47
  /**
47
- * One old→new content edit with the text splice it implies. Both sides are content,
48
- * never a PM doc: the projection of the new doc (`pmToContent`) is the caller's to
49
- * hold, because a doc-taking overload re-walks the whole tree, once per keystroke,
50
- * for a value the caller has in hand.
51
- *
52
- * The splice rides along because `diffText` allocates a code-point array per side
53
- * and the commit path holds the edit across the lowering. `contentEdit` being its
54
- * only constructor is what keeps a `delta` from disagreeing with the contents it
55
- * was diffed from; `lower` is where a slot inside that splice moves to the island
56
- * channel, so this `delta` is the raw splice, not always the one committed.
48
+ * One old→new content edit with the text splice it implies. Both sides are content, never a PM doc:
49
+ * a doc-taking overload would re-walk the tree per keystroke for a projection the caller holds. The
50
+ * splice rides along because `diffText` allocates a code-point array per side. `contentEdit` is its
51
+ * only constructor, which keeps a `delta` agreeing with the contents it was diffed from; `lower`
52
+ * moves a slot inside the splice to the island channel, so this `delta` is the raw splice.
57
53
  */
58
54
  export interface ContentEdit {
59
55
  readonly oldRt: Content;