@quillmark/svelte 0.2.0 → 0.3.1

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 (119) hide show
  1. package/NOTICE +6 -6
  2. package/README.md +3 -3
  3. package/THEMING.md +12 -8
  4. package/dist/core/address.d.ts +7 -7
  5. package/dist/core/address.js +6 -6
  6. package/dist/core/bloom.d.ts +3 -32
  7. package/dist/core/bloom.js +19 -51
  8. package/dist/core/codec/anchor.d.ts +1 -1
  9. package/dist/core/codec/anchor.js +1 -1
  10. package/dist/core/codec/atoms.d.ts +10 -0
  11. package/dist/core/codec/atoms.js +78 -0
  12. package/dist/core/codec/clipboard.d.ts +59 -0
  13. package/dist/core/codec/clipboard.js +202 -0
  14. package/dist/core/codec/code.d.ts +1 -1
  15. package/dist/core/codec/code.js +4 -4
  16. package/dist/core/codec/decode.d.ts +1 -1
  17. package/dist/core/codec/decode.js +8 -8
  18. package/dist/core/codec/encode.d.ts +4 -4
  19. package/dist/core/codec/encode.js +10 -10
  20. package/dist/core/codec/field.d.ts +21 -26
  21. package/dist/core/codec/field.js +33 -32
  22. package/dist/core/codec/index.js +5 -8
  23. package/dist/core/codec/inputrules.js +56 -18
  24. package/dist/core/codec/islands.d.ts +11 -7
  25. package/dist/core/codec/islands.js +16 -10
  26. package/dist/core/codec/keymap.d.ts +5 -6
  27. package/dist/core/codec/keymap.js +8 -7
  28. package/dist/core/codec/lists.d.ts +3 -3
  29. package/dist/core/codec/lists.js +32 -13
  30. package/dist/core/codec/marks.d.ts +1 -1
  31. package/dist/core/codec/marks.js +1 -1
  32. package/dist/core/codec/positions.js +1 -1
  33. package/dist/core/codec/prose.css +235 -221
  34. package/dist/core/codec/schema.js +4 -4
  35. package/dist/core/codec/slash.d.ts +39 -41
  36. package/dist/core/codec/slash.js +0 -0
  37. package/dist/core/codec/table-view.d.ts +12 -16
  38. package/dist/core/codec/table-view.js +488 -292
  39. package/dist/core/codec/table.d.ts +42 -15
  40. package/dist/core/codec/table.js +70 -26
  41. package/dist/core/errors.d.ts +5 -5
  42. package/dist/core/index.js +2 -2
  43. package/dist/core/lifecycle.js +1 -1
  44. package/dist/core/rebind.svelte.d.ts +2 -2
  45. package/dist/core/rebind.svelte.js +4 -4
  46. package/dist/core/teardown.d.ts +1 -1
  47. package/dist/core/teardown.js +1 -1
  48. package/dist/core/theme.css +170 -64
  49. package/dist/preset/index.css +1 -1
  50. package/dist/preset/recipes.css +31 -29
  51. package/dist/preset/scale.css +37 -26
  52. package/dist/preview/Preview.svelte +17 -27
  53. package/dist/preview/Preview.svelte.d.ts +4 -5
  54. package/dist/preview/bridge.js +6 -6
  55. package/dist/preview/controller.d.ts +8 -10
  56. package/dist/preview/controller.js +19 -44
  57. package/dist/preview/geometry.d.ts +8 -10
  58. package/dist/preview/geometry.js +8 -10
  59. package/dist/preview/index.js +4 -4
  60. package/dist/preview/paint.d.ts +1 -1
  61. package/dist/preview/paint.js +17 -19
  62. package/dist/visual/ArrayField.svelte +80 -66
  63. package/dist/visual/ArrayField.svelte.d.ts +1 -1
  64. package/dist/visual/BooleanField.svelte +8 -6
  65. package/dist/visual/BooleanField.svelte.d.ts +2 -2
  66. package/dist/visual/Card.svelte +160 -329
  67. package/dist/visual/Card.svelte.d.ts +2 -7
  68. package/dist/visual/CardControls.svelte +1 -1
  69. package/dist/visual/DateField.svelte +19 -19
  70. package/dist/visual/DateField.svelte.d.ts +3 -3
  71. package/dist/visual/DiagnosticList.svelte +1 -1
  72. package/dist/visual/EnumField.svelte +16 -16
  73. package/dist/visual/EnumField.svelte.d.ts +2 -2
  74. package/dist/visual/Field.svelte +128 -115
  75. package/dist/visual/Field.svelte.d.ts +1 -1
  76. package/dist/visual/FieldHint.svelte +24 -42
  77. package/dist/visual/FieldHint.svelte.d.ts +1 -1
  78. package/dist/visual/FieldLabel.svelte +8 -8
  79. package/dist/visual/FormatPopover.svelte +80 -35
  80. package/dist/visual/NumberField.svelte +5 -5
  81. package/dist/visual/NumberField.svelte.d.ts +1 -1
  82. package/dist/visual/ObjectField.svelte +9 -9
  83. package/dist/visual/ObjectField.svelte.d.ts +1 -1
  84. package/dist/visual/ProseArrayElement.svelte +11 -13
  85. package/dist/visual/ProseArrayElement.svelte.d.ts +1 -1
  86. package/dist/visual/ProseField.svelte +53 -87
  87. package/dist/visual/ProseField.svelte.d.ts +13 -19
  88. package/dist/visual/SlashMenu.svelte +42 -16
  89. package/dist/visual/TextField.svelte +5 -5
  90. package/dist/visual/TextField.svelte.d.ts +1 -1
  91. package/dist/visual/TipsCard.svelte +68 -56
  92. package/dist/visual/VisualEditor.svelte +6 -6
  93. package/dist/visual/VisualEditorInner.svelte +203 -290
  94. package/dist/visual/controls.css +74 -30
  95. package/dist/visual/diagnostics.d.ts +5 -5
  96. package/dist/visual/diagnostics.js +3 -3
  97. package/dist/visual/domid.js +3 -3
  98. package/dist/visual/ext.d.ts +1 -1
  99. package/dist/visual/ext.js +1 -1
  100. package/dist/visual/icons/nodes.d.ts +19 -0
  101. package/dist/visual/icons/nodes.js +13 -1
  102. package/dist/visual/index.js +4 -4
  103. package/dist/visual/leaves.d.ts +3 -3
  104. package/dist/visual/leaves.js +1 -1
  105. package/dist/visual/links.d.ts +24 -0
  106. package/dist/visual/links.js +67 -0
  107. package/dist/visual/props.d.ts +8 -8
  108. package/dist/visual/signals.d.ts +6 -6
  109. package/dist/visual/strings.d.ts +14 -19
  110. package/dist/visual/strings.js +5 -7
  111. package/dist/visual/structure.d.ts +24 -17
  112. package/dist/visual/structure.js +8 -8
  113. package/dist/visual/synced.svelte.d.ts +1 -1
  114. package/dist/visual/synced.svelte.js +4 -4
  115. package/dist/visual/tips.d.ts +2 -2
  116. package/dist/visual/tips.js +3 -3
  117. package/package.json +2 -2
  118. package/dist/preview/overlay.d.ts +0 -11
  119. package/dist/preview/overlay.js +0 -97
@@ -4,7 +4,7 @@
4
4
  markup: a component that carries a shared class without also carrying its rule
5
5
  ships unstyled, and nothing raises it.
6
6
 
7
- NOT in `core/theme.css`: that file is the DERIVATION (public dials in, the
7
+ Not in `core/theme.css`: that file is the derivation (public dials in, the
8
8
  private `--_qm-*` scale out) and it mints values. This file mints none; every
9
9
  declaration reads a rung, exactly as a component's own block must; `check:style`
10
10
  holds that over `.css` too.
@@ -17,13 +17,13 @@
17
17
 
18
18
  @layer qm.chrome {
19
19
  /* The themed focus ring in place of the raw UA outline, as a
20
- RULE rather than a pair of rungs each control assembles. `--_qm-ring`
21
- fixes the VALUE; which properties draw a ring, and on what state, is the other
20
+ rule rather than a pair of rungs each control assembles. `--_qm-ring`
21
+ fixes the value; which properties draw a ring, and on what state, is the other
22
22
  half of the decision, and `check:style` sees a minted literal but never a
23
23
  divergent rule. A control opts in by carrying the marker class.
24
24
 
25
25
  Two selectors, because focus lands in two places. Usually on the control
26
- itself; but the date field's focus lives on a SEGMENT, so it rings the field
26
+ itself; but the date field's focus lives on a segment, so it rings the field
27
27
  around it: a per-segment ring flickers across the field as the caret walks
28
28
  it. That is the only distinction, and this pair is the whole of it. */
29
29
  .qm-focus-ring:focus-visible,
@@ -32,27 +32,27 @@
32
32
  outline-offset: var(--_qm-ring-offset);
33
33
  }
34
34
 
35
- /* THE TYPE HALF, for both recipe families at once. A UA control inherits neither
35
+ /* The type half, for both recipe families at once. A UA control inherits neither
36
36
  family nor size, so a control or a button that declares none renders in the UA
37
37
  face at the UA size: `--qm-font`, a documented dial, stopping short of the
38
38
  package's own surfaces with nothing raised, since an absence mints no literal
39
- for `check:style`. `font: inherit` first, then the BODY rung off the ramp:
39
+ for `check:style`. `font: inherit` first, then the body rung off the ramp:
40
40
  inheriting the size lands the surface on the host page's body size, a rung the
41
41
  scale never minted and one a consumer moves by restyling their page rather than
42
42
  by turning `--qm-font-size`.
43
43
 
44
- ONE rule over both families rather than the same two declarations in each,
44
+ One rule over both families rather than the same two declarations in each,
45
45
  because the drift a copy invites here is a box and the button beside it reading
46
- DIFFERENT rungs, which no gate can see: `check:style`'s type axis holds that
46
+ different rungs, which no gate can see: `check:style`'s type axis holds that
47
47
  each side reads a rung, never that both read the same one.
48
48
 
49
49
  The list is wider than either recipe's chrome: a prose leaf withholds the box
50
50
  and still reads the ramp, so `.qm-prose` is
51
- here and not below. The BODY leaf steps off this rung onto `--_qm-text-paper`
51
+ here and not below. The body leaf steps off this rung onto `--_qm-text-paper`
52
52
  from its own block (`ProseField`).
53
53
 
54
54
  `font` is a shorthand carrying `line-height`, so each family's leading is
55
- declared in the rules BELOW and would be dropped in silence above them. */
55
+ declared in the rules below and would be dropped in silence above them. */
56
56
  .qm-control-box,
57
57
  .qm-input,
58
58
  .qm-prose,
@@ -64,24 +64,24 @@
64
64
 
65
65
  /* A typed value's box (every scalar control and every prose leaf): the text and
66
66
  numeric inputs, the array's JSON textarea, the enum trigger, the date field, and
67
- the inline richtext leaves. ONE rule, because a
67
+ the inline richtext leaves. One rule, because a
68
68
  leaf that agrees with the input beside it "by construction" has to draw the same
69
69
  declarations, not a copy of them: five hand-kept copies agree until one is
70
70
  edited, and nothing raises it (no literal is minted, so `check:style` stays
71
71
  green while a control renders subtly the wrong height).
72
72
 
73
- LEADING sits on the wide list rather than on the box's own, because a box's
74
- height is an inset rung plus a line box plus two hairlines: a leaf and the
73
+ Leading sits on the wide list rather than on the box's own, because a box's
74
+ height is an inset rung plus a line box and nothing else: a leaf and the
75
75
  input beside it agree on height exactly as long as they agree on leading and on
76
76
  size, which is the whole of what "by construction" buys. The body leaf keeps the
77
77
  leading and steps off the size, which costs it nothing: it draws no box and
78
78
  stands in no row.
79
79
 
80
- COLOUR is the trap one property over: `font` does not carry it either, so an
80
+ Colour is the trap one property over: `font` does not carry it either, so an
81
81
  input or trigger left off this line falls to the UA's own text colour: a second
82
82
  black sitting beside the card's ink rather than the rung it renders on.
83
83
  `caret-color` is pinned to `currentColor` rather than left at its `auto`
84
- initial, which resolves the same way: the prose leaf's caret IS its focus
84
+ initial, which resolves the same way: the prose leaf's caret is its focus
85
85
  indicator, the outline dropped in its favour (`ProseField`), so it tracks
86
86
  this rule's own ink deliberately. */
87
87
  .qm-control-box,
@@ -91,12 +91,15 @@
91
91
  color: var(--_qm-ink);
92
92
  caret-color: currentColor;
93
93
  }
94
+ /* The box is A fill and not an edge (ARCHITECTURE §Styling), and the fill is the
95
+ well rung: a field is a hole cut into the card rather than a plane of its own,
96
+ and with no edge to carry it the step is the whole of what says so. */
94
97
  .qm-control-box,
95
98
  .qm-input {
96
99
  padding: var(--_qm-inset-control);
97
- border: var(--_qm-border-width) solid var(--_qm-border);
100
+ border: none;
98
101
  border-radius: var(--_qm-radius-inner);
99
- background: var(--_qm-surface);
102
+ background: var(--_qm-surface-well);
100
103
  }
101
104
  /* What a form control adds over the box: it does not fill its row on its own. */
102
105
  .qm-input {
@@ -121,25 +124,25 @@
121
124
 
122
125
  `--_qm-tap-min` is WCAG 2.5.8's floor, read rather than re-expressed per
123
126
  control: `min-*` is outside every `check:style` axis, so a second expression of
124
- the same threshold has nothing watching it. `box-sizing` is what makes it a BOX
127
+ the same threshold has nothing watching it. `box-sizing` is what makes it a box
125
128
  floor: a `<button>` takes border-box from the UA sheet and a plain element does
126
129
  not, so a control on this recipe that is not a button clears the threshold on its
127
- CONTENT box and stands a padding taller than the button beside it. Declared here,
130
+ content box and stands a padding taller than the button beside it. Declared here,
128
131
  the floor means the same height whatever element carries it; and a caller that
129
132
  stretches one of these to its row gets a width the padding sits inside.
130
133
 
131
134
  Both button families are named here rather than opting in through a marker: a
132
- glyph button and an add affordance are not SOMETIMES flat, so a class they could
135
+ glyph button and an add affordance are not sometimes flat, so a class they could
133
136
  be built without is a class one of them will eventually miss.
134
137
 
135
138
  The type half is the shared rule above, which both families are named in; what
136
- diverges is the LEADING. Where the box's height is a line box, a button's is
139
+ diverges is the leading. Where the box's height is a line box, a button's is
137
140
  `--_qm-tap-min`: a label is one line and a glyph is not text at all, so the line
138
141
  box collapses onto its content (`1`) and the floor decides, rather than two
139
142
  height sources competing. That is the same structural `1` a glyph row takes,
140
143
  which is why it is here and not on one family, and it is what keeps a button off
141
144
  the host page's leading the way the size rung keeps it off the host page's body
142
- size. A caller whose button carries a LABEL restates the rung it wants from its
145
+ size. A caller whose button carries a label restates the rung it wants from its
143
146
  own block: the tips foot takes the label rung, which is that surface's decision,
144
147
  not the family's. */
145
148
  .qm-icon-btn,
@@ -175,7 +178,7 @@
175
178
  }
176
179
 
177
180
  /* An add affordance: the between-card trigger and the array foot's. A pill rather
178
- than the inner radius, because it FILLS on hover instead of tinting ink: both rest
181
+ than the inner radius, because it fills on hover instead of tinting ink: both rest
179
182
  unfilled on `--_qm-opacity-idle`, so a hover shifting ink alone would have little
180
183
  to shift. The recede ladder itself stays with each caller: it is driven by their
181
184
  own hover/focus state. */
@@ -190,36 +193,77 @@
190
193
  color: var(--_qm-ink);
191
194
  }
192
195
 
196
+ /* The press floor, for a trigger standing in a line of text or a band of gutter
197
+ rather than in a row of its own, whose drawn box is therefore smaller than the target
198
+ it owes. A target is a region of the screen and a box is paint; separating them is
199
+ what lets both be right at once. The target is the box or the floor, whichever is
200
+ larger, which is what `width: 100%` under a `min-width` says. Unpainted, so it
201
+ enlarges the press and not the mark, and out of the accessibility tree by
202
+ construction: a pseudo-element has no node.
203
+
204
+ The family's floor comes off in the same rule, carrying this class being what giving
205
+ it up means. It sits after both families for that: same specificity, so source order
206
+ decides. */
207
+ .qm-tap-floor {
208
+ position: relative;
209
+ min-width: 0;
210
+ min-height: 0;
211
+ }
212
+ .qm-tap-floor::after {
213
+ content: '';
214
+ position: absolute;
215
+ top: 50%;
216
+ left: 50%;
217
+ width: 100%;
218
+ min-width: var(--_qm-tap-min);
219
+ height: var(--_qm-tap-min);
220
+ transform: translate(-50%, -50%);
221
+ }
222
+
193
223
  /* A floating list of choices: the enum's listbox and the card stack's kind menu.
194
- Both portal into the nearest `[data-qm-root]`, and both are PICKED FROM, so the
224
+ Both portal into the nearest `[data-qm-root]`, and both are picked from, so the
195
225
  surface (its edge, its inset, its ink) and the item (its pad, its radius, the
196
226
  rung a highlight fills with) are one recipe rather than two that agree until one
197
227
  is edited. What a caller adds over it is its own: the enum sizes itself off its
198
228
  trigger and weights the stored value.
199
229
 
200
- `[data-highlighted]` is bits-ui's mark and it is the ONLY hover lane: an item
230
+ `[data-highlighted]` is bits-ui's mark and it is the only hover lane: an item
201
231
  under the pointer and an item under the keyboard cursor are one state to a menu,
202
232
  so the primitive marks both and a `:hover` rule beside it would paint a second
203
- highlight the keyboard cannot reach. */
233
+ highlight the keyboard cannot reach. It fills with the accent held rather than
234
+ the hover rung: the mark is a cursor a keyboard drives, so it has to read at a
235
+ glance from across the list, where a hover only has to confirm the pointer
236
+ already under it.
237
+
238
+ The resting fill is the recipe's too: an item drawn on a `<button>` carries a UA
239
+ fill to take back, and a component's scoped block is unlayered, so a caller taking
240
+ it back outranks this layer and erases the highlight with it. Transparent here
241
+ leaves the caller nothing to declare.
242
+
243
+ A floating surface is the one place a hairline is still load-bearing: it hangs
244
+ over content the surface did not choose, so there is no plane under it for a
245
+ tone to step off (ARCHITECTURE §Styling). It takes the raised rung as well, which
246
+ is a real step in dark and nothing in light, where the edge is the whole of it. */
204
247
  .qm-menu-surface {
205
248
  padding: var(--_qm-space-half);
206
249
  border: var(--_qm-border-width) solid var(--_qm-border);
207
250
  border-radius: var(--_qm-radius);
208
- background: var(--_qm-surface);
251
+ background: var(--_qm-surface-raised);
209
252
  font-size: var(--_qm-text-body);
210
253
  color: var(--_qm-ink);
211
254
  }
212
255
  .qm-menu-item {
213
256
  padding: var(--_qm-space) var(--_qm-space-2);
214
257
  border-radius: var(--_qm-radius-inner);
258
+ background: transparent;
215
259
  cursor: pointer;
216
260
  user-select: none;
217
261
  }
218
262
  .qm-menu-item[data-highlighted] {
219
- background: var(--_qm-surface-hover);
263
+ background: var(--_qm-accent-tint);
220
264
  }
221
265
 
222
- /* A floating surface that is READ rather than picked from: the selection popover
266
+ /* A floating surface that is read rather than picked from: the selection popover
223
267
  and the field label's guidance. The theme surface mixed toward transparent
224
268
  behind a backdrop blur, so the page reads faintly through it and the surface
225
269
  rung stays the mix base, which is what keeps `--qm-bg` tinting it. The lists
@@ -227,7 +271,7 @@
227
271
  row of glyphs does.
228
272
 
229
273
  The raise and the dismissal are the recipe's too, one existence↔absence trip
230
- taken twice, which is why the class goes on the PRESENCE
274
+ taken twice, which is why the class goes on the presence
231
275
  node (bits-ui's own content element, reached through its `child` snippet)
232
276
  rather than on a pill inside it: the node's animations are what the primitive
233
277
  waits on before unmounting, and an animation one level in is a fade it never
@@ -1,9 +1,9 @@
1
1
  import type { Diagnostic } from '@quillmark/wasm';
2
- /** A field's routing address, and `/core`'s `Addr` structurally: an `Addr` IS a
2
+ /** A field's routing address, and `/core`'s `Addr` structurally: an `Addr` is a
3
3
  * positional `FieldKey`, so `addrForFieldPath` is the path→key walk and routing
4
4
  * carries no second copy of the grammar. `card` is `undefined` for the main card; a
5
5
  * composable card slot is a stable session id (the editor's own bookkeeping) once
6
- * resolved, or an ABSOLUTE document-array index straight off a parsed `DocPath`
6
+ * resolved, or an absolute document-array index straight off a parsed `DocPath`
7
7
  * (resolve via {@link resolveCardKey} before merging with id-keyed sources). `field`
8
8
  * `undefined` addresses the body (the field-less leaf). */
9
9
  export interface FieldKey {
@@ -13,8 +13,8 @@ export interface FieldKey {
13
13
  /** Canonical string key for a `FieldKey`: the routing `Map`'s key space. */
14
14
  export declare function fieldKeyToString(k: FieldKey): string;
15
15
  /**
16
- * Resolve a positional `FieldKey` (an ABSOLUTE document-array index, straight off
17
- * `addrForFieldPath`) to the editor's stable-id keying using the LIVE `cardIds`
16
+ * Resolve a positional `FieldKey` (an absolute document-array index, straight off
17
+ * `addrForFieldPath`) to the editor's stable-id keying using the live `cardIds`
18
18
  * array: re-resolved fresh on every call (never cached), the same "resolve only
19
19
  * at the point of use" discipline `cardIndexOf` applies to writes (VISUAL_EDITOR
20
20
  * §"The address is the spine"). A key already id-keyed (`card` a string) or main
@@ -29,7 +29,7 @@ export interface RoutedDiagnostic {
29
29
  }
30
30
  /**
31
31
  * Route a path-keyed producer's raw `Diagnostic[]` (validate / external) to the
32
- * editor's stable-id keying: the ONE door producers #1/#3 take, `addrForFieldPath`
32
+ * editor's stable-id keying: the one door producers #1/#3 take, `addrForFieldPath`
33
33
  * and `resolveCardKey` in a single pass. An entry drops rather than mis-routes when
34
34
  * it carries no `path`, when the path names no single commit address (a nested /
35
35
  * array-element one), or when its absolute card index is out of the live `cardIds`.
@@ -4,8 +4,8 @@ export function fieldKeyToString(k) {
4
4
  return `${k.card ?? 'main'}:${k.field ?? '$body'}`;
5
5
  }
6
6
  /**
7
- * Resolve a positional `FieldKey` (an ABSOLUTE document-array index, straight off
8
- * `addrForFieldPath`) to the editor's stable-id keying using the LIVE `cardIds`
7
+ * Resolve a positional `FieldKey` (an absolute document-array index, straight off
8
+ * `addrForFieldPath`) to the editor's stable-id keying using the live `cardIds`
9
9
  * array: re-resolved fresh on every call (never cached), the same "resolve only
10
10
  * at the point of use" discipline `cardIndexOf` applies to writes (VISUAL_EDITOR
11
11
  * §"The address is the spine"). A key already id-keyed (`card` a string) or main
@@ -20,7 +20,7 @@ export function resolveCardKey(key, cardIds) {
20
20
  }
21
21
  /**
22
22
  * Route a path-keyed producer's raw `Diagnostic[]` (validate / external) to the
23
- * editor's stable-id keying: the ONE door producers #1/#3 take, `addrForFieldPath`
23
+ * editor's stable-id keying: the one door producers #1/#3 take, `addrForFieldPath`
24
24
  * and `resolveCardKey` in a single pass. An entry drops rather than mis-routes when
25
25
  * it carries no `path`, when the path names no single commit address (a nested /
26
26
  * array-element one), or when its absolute card index is out of the live `cardIds`.
@@ -1,17 +1,17 @@
1
1
  // A field's DOM ids. The DOM needs three names per field (the control a `<label
2
2
  // for>` points at, the label an `aria-labelledby` control points back to, and the
3
3
  // parked description an `aria-describedby` reads) and they are derived from the
4
- // leaf key so the DOM shares the ONE per-field identity space the registry and
4
+ // leaf key so the DOM shares the one per-field identity space the registry and
5
5
  // diagnostics already collapse to (`fieldKeyToString`), rather than minting a
6
6
  // second.
7
7
  //
8
8
  // `uid` is the editor instance's own (`$props.id()`, stable across SSR and
9
- // hydration). The leaf-key space is unique per EDITOR, not per document: two
9
+ // hydration). The leaf-key space is unique per editor, not per document: two
10
10
  // `VisualEditor`s on a page both hold `main:subject`, and a duplicate `id` makes
11
11
  // `for` resolve to whichever mounted first.
12
12
  /**
13
13
  * `id` syntax admits far less than a leaf key spends (`main:subject`, `$body`).
14
- * The escape is INJECTIVE (`-` introduces it, so `-` escapes itself), which a
14
+ * The escape is injective (`-` introduces it, so `-` escapes itself), which a
15
15
  * plain replace-with-dash is not: `a:b` and `a-b` would collapse onto one id.
16
16
  */
17
17
  function escapeId(key) {
@@ -1,7 +1,7 @@
1
1
  import type { Document, CardAddr } from '@quillmark/wasm';
2
2
  /**
3
3
  * Merge `patch` into `$ext.editor` on the card `addr` targets (absent `card` =
4
- * main). A key whose patch value is `undefined` is DROPPED; every other key in the
4
+ * main). A key whose patch value is `undefined` is dropped; every other key in the
5
5
  * namespace is carried through untouched.
6
6
  *
7
7
  * **The namespace is the write unit.** `storeExtNamespace` replaces the namespace
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Merge `patch` into `$ext.editor` on the card `addr` targets (absent `card` =
3
- * main). A key whose patch value is `undefined` is DROPPED; every other key in the
3
+ * main). A key whose patch value is `undefined` is dropped; every other key in the
4
4
  * namespace is carried through untouched.
5
5
  *
6
6
  * **The namespace is the write unit.** `storeExtNamespace` replaces the namespace
@@ -4,6 +4,11 @@ export declare const ICONS: {
4
4
  readonly 'chevron-right': readonly [readonly ["path", {
5
5
  readonly d: "m9 18 6-6-6-6";
6
6
  }]];
7
+ readonly 'arrow-right': readonly [readonly ["path", {
8
+ readonly d: "M5 12h14";
9
+ }], readonly ["path", {
10
+ readonly d: "m12 5 7 7-7 7";
11
+ }]];
7
12
  readonly 'chevron-down': readonly [readonly ["path", {
8
13
  readonly d: "m6 9 6 6 6-6";
9
14
  }]];
@@ -97,6 +102,20 @@ export declare const ICONS: {
97
102
  }], readonly ["path", {
98
103
  readonly d: "m6 6 12 12";
99
104
  }]];
105
+ readonly 'trash-2': readonly [readonly ["path", {
106
+ readonly d: "M10 11v6";
107
+ }], readonly ["path", {
108
+ readonly d: "M14 11v6";
109
+ }], readonly ["path", {
110
+ readonly d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6";
111
+ }], readonly ["path", {
112
+ readonly d: "M3 6h18";
113
+ }], readonly ["path", {
114
+ readonly d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2";
115
+ }]];
116
+ readonly minus: readonly [readonly ["path", {
117
+ readonly d: "M5 12h14";
118
+ }]];
100
119
  };
101
120
  /** The closed set. A name that is not here is a compile error at the call site,
102
121
  * which is what a string key buys over thirteen imported components. */
@@ -8,6 +8,10 @@
8
8
  // ones that release carries (ISC, and MIT for the Feather-derived ten).
9
9
  export const ICONS = {
10
10
  'chevron-right': [['path', { d: 'm9 18 6-6-6-6' }]],
11
+ 'arrow-right': [
12
+ ['path', { d: 'M5 12h14' }],
13
+ ['path', { d: 'm12 5 7 7-7 7' }]
14
+ ],
11
15
  'chevron-down': [['path', { d: 'm6 9 6 6 6-6' }]],
12
16
  'chevron-up': [['path', { d: 'm18 15-6-6-6 6' }]],
13
17
  bold: [['path', { d: 'M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8' }]],
@@ -51,5 +55,13 @@ export const ICONS = {
51
55
  x: [
52
56
  ['path', { d: 'M18 6 6 18' }],
53
57
  ['path', { d: 'm6 6 12 12' }]
54
- ]
58
+ ],
59
+ 'trash-2': [
60
+ ['path', { d: 'M10 11v6' }],
61
+ ['path', { d: 'M14 11v6' }],
62
+ ['path', { d: 'M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6' }],
63
+ ['path', { d: 'M3 6h18' }],
64
+ ['path', { d: 'M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2' }]
65
+ ],
66
+ minus: [['path', { d: 'M5 12h14' }]]
55
67
  };
@@ -3,13 +3,13 @@
3
3
  // The federated VisualEditor: a thin Svelte composition over many small editors.
4
4
  // Each content leaf is the codec's `createField` prose leaf; scalar fields are
5
5
  // form controls; structure/cards are the editor's own. Depends on `/core` and the
6
- // codec. RELATIVE imports (not `$lib`): svelte-package ships this as-is.
6
+ // codec. Relative imports (not `$lib`): svelte-package ships this as-is.
7
7
  //
8
8
  // The barrel carries what a consumer story wants, and nothing else: a symbol
9
9
  // earns its line when a caller outside the package needs it, and grows one when
10
10
  // that caller arrives, not before.
11
11
  // The theme derivation, which every control on this surface reads through `var()`.
12
- // Imported at the BARREL because a subpath is what a consumer gets: this surface
12
+ // Imported at the barrel because a subpath is what a consumer gets: this surface
13
13
  // reaches modules inside `core/` and never its entry, so a sheet hanging off that
14
14
  // entry arrives only for a consumer importing `/core` for some other reason. The
15
15
  // sheet rather than the entry, because the barrel needs the derivation and not
@@ -19,10 +19,10 @@ export { default as VisualEditor } from './VisualEditor.svelte';
19
19
  // The prose leaf: the vanilla mount API a non-Svelte consumer (or a custom
20
20
  // surface) builds on.
21
21
  export { createField } from '../core/codec/index.js';
22
- // What the surface SAYS, and how it words a diagnostic. `strings` is keyed and
22
+ // What the surface says, and how it words a diagnostic. `strings` is keyed and
23
23
  // partial (unset keys take the package's English) and `DEFAULT_VISUAL_STRINGS` is
24
24
  // that English, exported so a consumer can compose against it rather than restate
25
- // it. The empty body's per-kind hook is the `bodyPlaceholder` key inside it.
25
+ // it. The empty body's per-card hook is the `bodyPlaceholder` key inside it.
26
26
  export { DEFAULT_VISUAL_STRINGS } from './strings.js';
27
27
  // The `$ext.editor` write unit. A consumer seeding editor-side chrome
28
28
  // state (a card title, the tips channel) goes through this rather than
@@ -10,11 +10,11 @@ import type { FieldController } from '../core/codec/index.js';
10
10
  export interface FieldControl {
11
11
  focus(): void;
12
12
  /**
13
- * Focus element `index` of a control that HAS elements, resolving the index to the
13
+ * Focus element `index` of a control that has elements, resolving the index to the
14
14
  * element's session id at the call; {@link focus}'s own answer for an index past
15
15
  * the live list. Absent on every control without elements.
16
16
  *
17
- * The granularity rides the CALL because the registry stays parent-keyed: a
17
+ * The granularity rides the call because the registry stays parent-keyed: a
18
18
  * per-element key is positional, in a registry whose doctrine is dodging
19
19
  * positional churn.
20
20
  */
@@ -24,7 +24,7 @@ export interface FieldControl {
24
24
  /**
25
25
  * The registry the editor holds and the mounted tree writes into.
26
26
  *
27
- * A LANE IS DROPPED BY ITS OWN OWNER, which is what the symmetric pairs are for: a
27
+ * A lane is dropped by its own owner, which is what the symmetric pairs are for: a
28
28
  * card retyped to a kind that declares the same field name at another type swaps the
29
29
  * control under one leaf key without remounting `Field`, so the leaving control and
30
30
  * the arriving prose leaf both touch that key with no order between them to rely on.
@@ -14,7 +14,7 @@ export function createLeafRegistry() {
14
14
  unregisterControl(key) {
15
15
  controls.delete(key);
16
16
  },
17
- // The prose map is read SECOND: a controller is a control, so a leaf answers a
17
+ // The prose map is read second: a controller is a control, so a leaf answers a
18
18
  // landing without a second entry, and a key the two lanes overlap on during a
19
19
  // retype resolves to the form control the tree is currently rendering.
20
20
  control(key) {
@@ -0,0 +1,24 @@
1
+ import type { Command, EditorState } from 'prosemirror-state';
2
+ /**
3
+ * The href a typed value stands for. A value with no scheme names a host, and
4
+ * stored verbatim it is a relative href resolving against whatever page the editor
5
+ * is embedded in, so it takes `https://`. A bare address takes `mailto:` instead,
6
+ * the host prefix reading `jane@example.com` as `example.com` with `jane` for
7
+ * userinfo — a wrong destination this normalization would itself have minted.
8
+ *
9
+ * A rooted value is left alone, being the spelling that asks for the embedding
10
+ * page, and so is anything already carrying a scheme. `''` for a blank value, which
11
+ * is nothing to apply.
12
+ */
13
+ export declare function normalizeHref(raw: string): string;
14
+ /**
15
+ * The href under the selection: the first link mark it touches, `''` where it
16
+ * touches none. A selection may span several links; the prompt holds one value, so
17
+ * the first is both what it seeds with and what an apply writes over the whole
18
+ * range.
19
+ */
20
+ export declare function hrefInSelection(state: EditorState): string;
21
+ /** Write `href` over the selection, replacing whatever link it already carries. */
22
+ export declare function setLink(href: string): Command;
23
+ /** Drop every link the selection carries. */
24
+ export declare const clearLink: Command;
@@ -0,0 +1,67 @@
1
+ /** A scheme: a letter, then letters, digits, `+`, `-` or `.`, then `:` (RFC 3986). */
2
+ const SCHEME = /^[a-z][a-z0-9+.-]*:/i;
3
+ /** Rooted at the page rather than at a host: an absolute path, a query, a fragment,
4
+ * or a protocol-relative host. Every one of them means relative on purpose. */
5
+ const ROOTED = /^[/#?]/;
6
+ /** A bare mail address: one `@`, no scheme and no path. */
7
+ const ADDRESS = /^[^\s/@]+@[^\s/@]+$/;
8
+ /**
9
+ * The href a typed value stands for. A value with no scheme names a host, and
10
+ * stored verbatim it is a relative href resolving against whatever page the editor
11
+ * is embedded in, so it takes `https://`. A bare address takes `mailto:` instead,
12
+ * the host prefix reading `jane@example.com` as `example.com` with `jane` for
13
+ * userinfo — a wrong destination this normalization would itself have minted.
14
+ *
15
+ * A rooted value is left alone, being the spelling that asks for the embedding
16
+ * page, and so is anything already carrying a scheme. `''` for a blank value, which
17
+ * is nothing to apply.
18
+ */
19
+ export function normalizeHref(raw) {
20
+ const value = raw.trim();
21
+ if (!value || SCHEME.test(value) || ROOTED.test(value))
22
+ return value;
23
+ return ADDRESS.test(value) ? `mailto:${value}` : `https://${value}`;
24
+ }
25
+ /**
26
+ * The href under the selection: the first link mark it touches, `''` where it
27
+ * touches none. A selection may span several links; the prompt holds one value, so
28
+ * the first is both what it seeds with and what an apply writes over the whole
29
+ * range.
30
+ */
31
+ export function hrefInSelection(state) {
32
+ const type = state.schema.marks.link;
33
+ if (!type)
34
+ return '';
35
+ const { from, to } = state.selection;
36
+ let href = '';
37
+ state.doc.nodesBetween(from, to, (node) => {
38
+ if (href)
39
+ return false;
40
+ const mark = type.isInSet(node.marks);
41
+ if (mark)
42
+ href = String(mark.attrs.href ?? '');
43
+ });
44
+ return href;
45
+ }
46
+ /** Write `href` over the selection, replacing whatever link it already carries. */
47
+ export function setLink(href) {
48
+ return (state, dispatch) => {
49
+ const type = state.schema.marks.link;
50
+ const { from, to, empty } = state.selection;
51
+ if (!type || empty || !href)
52
+ return false;
53
+ if (dispatch)
54
+ dispatch(state.tr.removeMark(from, to, type).addMark(from, to, type.create({ href })));
55
+ return true;
56
+ };
57
+ }
58
+ /** Drop every link the selection carries. */
59
+ export const clearLink = (state, dispatch) => {
60
+ const type = state.schema.marks.link;
61
+ const { from, to, empty } = state.selection;
62
+ if (!type || empty || !state.doc.rangeHasMark(from, to, type))
63
+ return false;
64
+ if (dispatch)
65
+ dispatch(state.tr.removeMark(from, to, type));
66
+ return true;
67
+ };
@@ -6,7 +6,7 @@ import type { FormatDiagnostic, VisualStringsInput } from './strings.js';
6
6
  export interface VisualEditorProps {
7
7
  doc: Document;
8
8
  /**
9
- * The schema this document is edited against. BORROWED for the surface's lifetime,
9
+ * The schema this document is edited against. Borrowed for the surface's lifetime,
10
10
  * whoever handed it over: a quill from `@quillmark/quiver`'s `getQuill` is the
11
11
  * quiver's, shared with every other caller for that ref, and freeing it hands the
12
12
  * next caller a freed handle. A host wanting one of its own mints it from
@@ -26,16 +26,16 @@ export interface VisualEditorProps {
26
26
  * keystroke path (the editor mints the path off its derived card tree, which
27
27
  * already holds every kind).
28
28
  *
29
- * A SELECTION signal, not a change signal: an arrow key fires it and commits
29
+ * A selection signal, not a change signal: an arrow key fires it and commits
30
30
  * nothing. One keystroke fires `onChange` first and this second, so the edit
31
31
  * has landed by the time the caret names where it is.
32
32
  *
33
- * A PLACE, so consecutive duplicates do not arrive: landing the caret where it
33
+ * A place, so consecutive duplicates do not arrive: landing the caret where it
34
34
  * already sits reports nothing, and a place left and returned to reports twice.
35
35
  */
36
36
  onCaretMove?: (at: Place) => void;
37
37
  /**
38
- * EVERY edit that lands on the document: a prose commit, a scalar/array/object
38
+ * Every edit that lands on the document: a prose commit, a scalar/array/object
39
39
  * write, a card operation. The signal a host recompiles off; `source` is what
40
40
  * moved.
41
41
  *
@@ -47,7 +47,7 @@ export interface VisualEditorProps {
47
47
  */
48
48
  onChange?: (change: EditorChange) => void;
49
49
  /**
50
- * Failures the editor RECOVERED from: a commit the boundary refused (also
50
+ * Failures the editor recovered from: a commit the boundary refused (also
51
51
  * pinned as a diagnostic on its field), a card operation that threw, a
52
52
  * `validate`/`resolve` that threw, a prose commit that fell back. None of them
53
53
  * stop editing; without this hook each is a `console.error` an app cannot
@@ -71,15 +71,15 @@ export interface VisualEditorProps {
71
71
  /**
72
72
  * How an `enumOptionAllowed: false` option draws: `'disable'` (the default) greys
73
73
  * it in place, `'hide'` leaves it out of the list. One policy for the surface, not
74
- * a per-option verdict, and the SELECTED value is drawn under either (disabled
74
+ * a per-option verdict, and the selected value is drawn under either (disabled
75
75
  * when refused), so the control always shows what the document says
76
76
  * (VISUAL_EDITOR §"Enum policy").
77
77
  */
78
78
  enumDisallowed?: 'hide' | 'disable';
79
79
  /**
80
80
  * Every word the surface says, keyed and partial: unset keys take the package's
81
- * English. Several are ACCESSIBLE NAMES rather than decoration, so this is what
82
- * a product shipping in another language replaces. The empty body's per-kind
81
+ * English. Several are accessible names rather than decoration, so this is what
82
+ * a product shipping in another language replaces. The empty body's per-card
83
83
  * wording is the `bodyPlaceholder` key here rather than a prop of its own.
84
84
  */
85
85
  strings?: VisualStringsInput;