@quillmark/svelte 0.1.1 → 0.3.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 (124) hide show
  1. package/README.md +16 -12
  2. package/THEMING.md +48 -25
  3. package/dist/core/address.d.ts +42 -7
  4. package/dist/core/address.js +49 -21
  5. package/dist/core/bloom.d.ts +5 -5
  6. package/dist/core/bloom.js +8 -8
  7. package/dist/core/codec/anchor.d.ts +1 -1
  8. package/dist/core/codec/anchor.js +1 -1
  9. package/dist/core/codec/atoms.d.ts +10 -0
  10. package/dist/core/codec/atoms.js +78 -0
  11. package/dist/core/codec/clipboard.d.ts +59 -0
  12. package/dist/core/codec/clipboard.js +202 -0
  13. package/dist/core/codec/code.d.ts +1 -1
  14. package/dist/core/codec/code.js +4 -4
  15. package/dist/core/codec/decode.d.ts +1 -1
  16. package/dist/core/codec/decode.js +8 -8
  17. package/dist/core/codec/encode.d.ts +4 -4
  18. package/dist/core/codec/encode.js +14 -11
  19. package/dist/core/codec/field.d.ts +22 -27
  20. package/dist/core/codec/field.js +33 -32
  21. package/dist/core/codec/index.js +5 -8
  22. package/dist/core/codec/inputrules.js +56 -18
  23. package/dist/core/codec/islands.d.ts +11 -7
  24. package/dist/core/codec/islands.js +16 -10
  25. package/dist/core/codec/keymap.d.ts +5 -6
  26. package/dist/core/codec/keymap.js +8 -7
  27. package/dist/core/codec/lists.d.ts +3 -3
  28. package/dist/core/codec/lists.js +32 -13
  29. package/dist/core/codec/marks.d.ts +1 -1
  30. package/dist/core/codec/marks.js +1 -1
  31. package/dist/core/codec/positions.js +1 -1
  32. package/dist/core/codec/prose.css +266 -228
  33. package/dist/core/codec/schema.js +4 -4
  34. package/dist/core/codec/slash.d.ts +39 -41
  35. package/dist/core/codec/slash.js +0 -0
  36. package/dist/core/codec/table-view.d.ts +12 -16
  37. package/dist/core/codec/table-view.js +488 -292
  38. package/dist/core/codec/table.d.ts +42 -15
  39. package/dist/core/codec/table.js +73 -27
  40. package/dist/core/errors.d.ts +17 -7
  41. package/dist/core/index.d.ts +1 -1
  42. package/dist/core/index.js +6 -5
  43. package/dist/core/lifecycle.d.ts +21 -5
  44. package/dist/core/lifecycle.js +34 -8
  45. package/dist/core/rebind.svelte.d.ts +18 -4
  46. package/dist/core/rebind.svelte.js +30 -12
  47. package/dist/core/teardown.d.ts +1 -1
  48. package/dist/core/teardown.js +1 -1
  49. package/dist/core/theme.css +173 -58
  50. package/dist/preset/index.css +1 -1
  51. package/dist/preset/recipes.css +79 -30
  52. package/dist/preset/scale.css +40 -28
  53. package/dist/preview/Preview.svelte +22 -19
  54. package/dist/preview/Preview.svelte.d.ts +8 -8
  55. package/dist/preview/bridge.d.ts +6 -4
  56. package/dist/preview/bridge.js +19 -9
  57. package/dist/preview/controller.d.ts +13 -6
  58. package/dist/preview/controller.js +8 -5
  59. package/dist/preview/geometry.d.ts +17 -2
  60. package/dist/preview/geometry.js +21 -1
  61. package/dist/preview/index.d.ts +1 -0
  62. package/dist/preview/index.js +9 -2
  63. package/dist/preview/overlay.d.ts +2 -1
  64. package/dist/preview/overlay.js +26 -11
  65. package/dist/preview/paint.js +19 -17
  66. package/dist/visual/ArrayField.svelte +103 -83
  67. package/dist/visual/ArrayField.svelte.d.ts +5 -4
  68. package/dist/visual/BooleanField.svelte +8 -6
  69. package/dist/visual/BooleanField.svelte.d.ts +2 -2
  70. package/dist/visual/Card.svelte +161 -333
  71. package/dist/visual/Card.svelte.d.ts +5 -4
  72. package/dist/visual/CardControls.svelte +1 -1
  73. package/dist/visual/DateField.svelte +19 -19
  74. package/dist/visual/DateField.svelte.d.ts +3 -3
  75. package/dist/visual/DiagnosticList.svelte +2 -2
  76. package/dist/visual/EnumField.svelte +48 -28
  77. package/dist/visual/EnumField.svelte.d.ts +5 -4
  78. package/dist/visual/Field.svelte +207 -132
  79. package/dist/visual/Field.svelte.d.ts +11 -7
  80. package/dist/visual/FieldHint.svelte +79 -48
  81. package/dist/visual/FieldHint.svelte.d.ts +1 -1
  82. package/dist/visual/FieldLabel.svelte +8 -8
  83. package/dist/visual/FormatPopover.svelte +82 -35
  84. package/dist/visual/NumberField.svelte +5 -5
  85. package/dist/visual/NumberField.svelte.d.ts +1 -1
  86. package/dist/visual/ObjectField.svelte +28 -9
  87. package/dist/visual/ObjectField.svelte.d.ts +4 -2
  88. package/dist/visual/ProseArrayElement.svelte +12 -19
  89. package/dist/visual/ProseArrayElement.svelte.d.ts +1 -2
  90. package/dist/visual/ProseField.svelte +62 -85
  91. package/dist/visual/ProseField.svelte.d.ts +14 -17
  92. package/dist/visual/SlashMenu.svelte +42 -16
  93. package/dist/visual/TextField.svelte +5 -5
  94. package/dist/visual/TextField.svelte.d.ts +1 -1
  95. package/dist/visual/TipsCard.svelte +43 -28
  96. package/dist/visual/VisualEditor.svelte +18 -19
  97. package/dist/visual/VisualEditor.svelte.d.ts +2 -3
  98. package/dist/visual/VisualEditorInner.svelte +251 -184
  99. package/dist/visual/VisualEditorInner.svelte.d.ts +2 -3
  100. package/dist/visual/controls.css +85 -38
  101. package/dist/visual/diagnostics.d.ts +13 -21
  102. package/dist/visual/diagnostics.js +7 -19
  103. package/dist/visual/domid.js +3 -3
  104. package/dist/visual/ext.d.ts +1 -1
  105. package/dist/visual/ext.js +1 -1
  106. package/dist/visual/icons/nodes.d.ts +3 -0
  107. package/dist/visual/icons/nodes.js +2 -1
  108. package/dist/visual/index.d.ts +1 -0
  109. package/dist/visual/index.js +10 -3
  110. package/dist/visual/leaves.d.ts +47 -0
  111. package/dist/visual/leaves.js +31 -0
  112. package/dist/visual/links.d.ts +24 -0
  113. package/dist/visual/links.js +67 -0
  114. package/dist/visual/props.d.ts +33 -12
  115. package/dist/visual/signals.d.ts +6 -6
  116. package/dist/visual/strings.d.ts +14 -19
  117. package/dist/visual/strings.js +5 -7
  118. package/dist/visual/structure.d.ts +24 -17
  119. package/dist/visual/structure.js +8 -8
  120. package/dist/visual/synced.svelte.d.ts +1 -1
  121. package/dist/visual/synced.svelte.js +4 -4
  122. package/dist/visual/tips.d.ts +4 -2
  123. package/dist/visual/tips.js +7 -4
  124. package/package.json +2 -2
@@ -3,20 +3,20 @@
3
3
  {@link FieldModel} and its live value, render the label + the control the type
4
4
  maps to. Array controls own their label (paired with the add affordance in
5
5
  {@link ArrayField}); other types render the label here. Prose leaves take a
6
- parent-built LIVE `addr` (its `card` a getter over the stable-id→index map) so
6
+ parent-built live `addr` (its `card` a getter over the stable-id→index map) so
7
7
  a reorder re-targets without a remount; scalars, arrays, and objects commit
8
- their value UP through `onCommitScalar`, which the parent lowers to the typed
8
+ their value up through `onCommitScalar`, which the parent lowers to the typed
9
9
  writer.
10
10
 
11
11
  `diagnostics` is the routed `Diagnostic[]` for this field (VisualEditor's
12
12
  `diagByKey`, merging `quill.validate`, local commit errors, and the external
13
13
  `diagnostics` prop (VISUAL_EDITOR §Diagnostics)) rendered via the shared
14
- `DiagnosticList`, severity-styled, NON-GATING.
14
+ `DiagnosticList`, severity-styled, non-gating.
15
15
  -->
16
16
  <script lang="ts">
17
17
  import type { Document, Quill, Addr, Diagnostic, ResolvedField } from '@quillmark/wasm';
18
18
  import type { EditorErrorHandler } from '../core/errors.js';
19
- import type { FieldController } from '../core/codec/index.js';
19
+ import type { LeafRegistry } from './leaves.js';
20
20
  import type { FieldModel, FieldSpan } from './structure.js';
21
21
  import { enumValues, ghostDefault, stringifyGhost } from './structure.js';
22
22
  import type { FieldDomIds } from './domid.js';
@@ -42,22 +42,26 @@
42
42
  doc: Document;
43
43
  /** The schema a prose leaf reads its content through (`ProseField`). */
44
44
  quill: Quill;
45
- /** LIVE prose address (getter-`card`); used only when control === 'prose'. */
46
- proseAddr: Addr;
45
+ /** This field's live address (getter-`card`, so a card reorder re-targets in
46
+ * place): the prose leaf commits to it, and a focus reports it. */
47
+ addr: Addr;
47
48
  leafKey: string;
48
49
  /** This field's three DOM names, derived from `leafKey` (see `domid.ts`): how
49
50
  * the label and the control find each other. */
50
51
  domIds: FieldDomIds;
51
52
  onCommitScalar: (value: unknown) => void;
52
- /** Enum-option policy: `false` disables that option. Only the enum
53
- * control reads it; other controls ignore it. */
53
+ /** Enum-option policy: `false` marks that option unavailable. Only the enum
54
+ * control reads this pair. */
54
55
  optionAllowed?: (value: string) => boolean;
56
+ /** How a refused option draws: greyed (default) or left out. */
57
+ enumDisallowed?: 'hide' | 'disable';
55
58
  onFocus?: (addr: Addr) => void;
56
59
  onCaretMove?: (addr: Addr, pos: number) => void;
57
60
  onChange?: (addr: Addr) => void;
58
61
  onError?: EditorErrorHandler;
59
- register?: (key: string, controller: FieldController) => void;
60
- unregister?: (key: string) => void;
62
+ /** The editor's leaf registry (`leaves.ts`): a form control registers its
63
+ * landing handle here, a prose leaf its controller from inside `ProseField`. */
64
+ leaves?: LeafRegistry;
61
65
  diagnostics?: Diagnostic[];
62
66
  }
63
67
  let {
@@ -67,17 +71,17 @@
67
71
  provenance,
68
72
  doc,
69
73
  quill,
70
- proseAddr,
74
+ addr,
71
75
  leafKey,
72
76
  domIds,
73
77
  onCommitScalar,
74
78
  optionAllowed,
79
+ enumDisallowed,
75
80
  onFocus,
76
81
  onCaretMove,
77
82
  onChange,
78
83
  onError,
79
- register,
80
- unregister,
84
+ leaves,
81
85
  diagnostics
82
86
  }: Props = $props();
83
87
 
@@ -88,7 +92,7 @@
88
92
  const ghost = $derived(ghostDefault(provenance));
89
93
  const defaultStr = $derived(stringifyGhost(ghost));
90
94
 
91
- // `for` reaches a LABELABLE control and the browser does the rest. The other four
95
+ // `for` reaches a labelable control and the browser does the rest. The other four
92
96
  // are not labelable (the prose leaf's `contenteditable`, the date field's segment
93
97
  // container, the object subform, an array's N inputs) so `for` there would be
94
98
  // both inert and invalid markup; they take `aria-labelledby` and a click handoff.
@@ -103,18 +107,66 @@
103
107
  // nothing, and silently.
104
108
  const describedBy = $derived(field.description ? domIds.description : undefined);
105
109
 
106
- // Focus handoff for the two controls a label click cannot reach natively. Both
107
- // expose their own `focus()` because "focusing" differs: a PM view restores a
108
- // selection, a date field lands on its first segment.
110
+ // ── Focus: one answer, two callers (`leaves.ts`) ─────────────────────────────
111
+ // A label click and the editor's `focusField`/`setCaret` ask the same question, so
112
+ // they read the same function and cannot land in different places. The four
113
+ // labelable controls are reached through the DOM id `for` already points at; the
114
+ // rest own what focusing means and expose `focus()`, because it differs: a PM view
115
+ // restores a selection, a date field lands on its first segment, an array lands on
116
+ // its first element or on the add affordance that is all an empty one has, an
117
+ // object on its first property.
109
118
  let proseEl = $state<{ focus: () => void } | undefined>();
110
119
  let dateEl = $state<{ focus: () => void } | undefined>();
111
- const onActivate = $derived(
112
- field.control === 'prose'
113
- ? () => proseEl?.focus()
114
- : field.control === 'date'
115
- ? () => dateEl?.focus()
116
- : undefined
117
- );
120
+ let arrayEl = $state<{ focus: () => void; focusElement: (k: number) => void } | undefined>();
121
+ let objectEl = $state<{ focus: () => void } | undefined>();
122
+ function focusControl(): void {
123
+ const owner = proseEl ?? dateEl ?? arrayEl ?? objectEl;
124
+ if (owner) return owner.focus();
125
+ document.getElementById(domIds.control)?.focus();
126
+ }
127
+ /** The array's per-ELEMENT landing, for the addresses the preview mints under an
128
+ * array field (`leaves.ts`); read at the call, so it tracks the mounted repeater. */
129
+ function focusElement(k: number): void {
130
+ arrayEl?.focusElement(k);
131
+ }
132
+ // Only where `for` cannot reach; the labelable four are the browser's own, and a
133
+ // second handler over them would be a focus the label already placed.
134
+ const onActivate = $derived(labelable ? undefined : focusControl);
135
+
136
+ /**
137
+ * This field's landing handle. The wrapper is the bloom host rather than the
138
+ * control: `bloomInside` appends an inset child and an `<input>` holds none.
139
+ *
140
+ * A prose leaf is absent here — it registers its own controller from inside
141
+ * `ProseField`, carrying the codec seam this handle has no half of — and reactive
142
+ * rather than mount-once, because a retype can swap the control under a leaf key
143
+ * that does not remount.
144
+ *
145
+ * An array carries the per-element lane as well; no other control has elements for
146
+ * an address to name.
147
+ */
148
+ let controlEl = $state<HTMLElement | undefined>();
149
+ $effect(() => {
150
+ if (field.control === 'prose' || !controlEl || !leaves) return;
151
+ const key = leafKey;
152
+ const registry = leaves;
153
+ registry.registerControl(key, {
154
+ focus: focusControl,
155
+ focusElement: field.control === 'array' ? focusElement : undefined,
156
+ el: controlEl
157
+ });
158
+ return () => registry.unregisterControl(key);
159
+ });
160
+
161
+ /**
162
+ * A form control has no controller to report its focus through, so the wrapper
163
+ * reports: `focusin` bubbles, so one handler covers a plain input, an array's N
164
+ * elements and an object's properties alike, and the active leaf names a scalar
165
+ * field the way it names a prose one. A prose leaf reports through its own
166
+ * controller — the source that drives its caret signals too — and is excluded here
167
+ * rather than counted twice.
168
+ */
169
+ const reportFocus = $derived(field.control === 'prose' ? undefined : () => onFocus?.(addr));
118
170
  </script>
119
171
 
120
172
  <div class="qm-field" class:cell={span === 'cell'} class:lone={span === 'lone'}>
@@ -129,139 +181,162 @@
129
181
  description={field.description}
130
182
  />
131
183
  {/if}
132
- <div class="qm-field-control">
133
- {#if field.control === 'prose'}
134
- <ProseField
135
- {quill}
136
- bind:this={proseEl}
137
- {doc}
138
- addr={proseAddr}
139
- inline={field.inline}
140
- plaintext={field.plaintext}
141
- labelledBy={domIds.label}
142
- {describedBy}
143
- {leafKey}
144
- {onFocus}
145
- {onCaretMove}
146
- {onChange}
147
- {onError}
148
- {register}
149
- {unregister}
150
- />
151
- {:else if field.control === 'enum'}
152
- <EnumField
153
- value={value as string | undefined}
154
- values={enumValues(field.schema) ?? []}
155
- fallback={ghost as string | undefined}
156
- id={domIds.control}
157
- {describedBy}
158
- onCommit={onCommitScalar}
159
- {optionAllowed}
160
- />
161
- {:else if field.control === 'number'}
162
- <NumberField
163
- value={value as number | undefined}
164
- integer={field.schema.type === 'integer'}
165
- fallback={ghost as number | undefined}
166
- id={domIds.control}
167
- {describedBy}
168
- onCommit={onCommitScalar}
169
- />
170
- {:else if field.control === 'boolean'}
171
- <BooleanField
172
- value={value as boolean | undefined}
173
- fallback={ghost as boolean | undefined}
174
- id={domIds.control}
175
- {describedBy}
176
- onCommit={onCommitScalar}
177
- />
178
- {:else if field.control === 'date'}
179
- <DateField
180
- bind:this={dateEl}
181
- value={value as string | undefined}
182
- fallback={defaultStr}
183
- labelledBy={domIds.label}
184
- {describedBy}
185
- onCommit={onCommitScalar}
186
- />
187
- {:else if field.control === 'array'}
188
- <ArrayField
189
- value={value as unknown[] | undefined}
190
- items={field.schema.items}
191
- label={field.label}
192
- required={field.required}
193
- description={field.description}
194
- labelId={domIds.label}
195
- descriptionId={domIds.description}
196
- onCommit={onCommitScalar}
197
- onFocusEl={() => onFocus?.(proseAddr)}
198
- />
199
- {:else if field.control === 'object'}
200
- <ObjectField
201
- value={value as Record<string, unknown> | undefined}
202
- properties={field.schema.properties}
203
- label={field.label}
204
- labelledBy={domIds.label}
205
- {describedBy}
206
- onCommit={onCommitScalar}
207
- />
208
- {:else}
209
- <TextField
210
- value={value as string | undefined}
211
- placeholder={defaultStr}
212
- id={domIds.control}
213
- {describedBy}
214
- onCommit={onCommitScalar}
215
- />
216
- {/if}
217
- </div>
184
+ <!-- The control and what hangs under it, in one grid cell: the diagnostics stack
185
+ inside the control's track rather than claiming a track of their own (the subgrid
186
+ rule below holds why). One markup for both spans; a field owning its row nests a
187
+ level and measures the same. -->
188
+ <div class="qm-field-stack">
189
+ <div class="qm-field-control" bind:this={controlEl} onfocusin={reportFocus}>
190
+ {#if field.control === 'prose'}
191
+ <ProseField
192
+ {quill}
193
+ bind:this={proseEl}
194
+ {doc}
195
+ {addr}
196
+ inline={field.inline}
197
+ plaintext={field.plaintext}
198
+ labelledBy={domIds.label}
199
+ {describedBy}
200
+ {leafKey}
201
+ {onFocus}
202
+ {onCaretMove}
203
+ {onChange}
204
+ {onError}
205
+ {leaves}
206
+ />
207
+ {:else if field.control === 'enum'}
208
+ <EnumField
209
+ value={value as string | undefined}
210
+ values={enumValues(field.schema) ?? []}
211
+ fallback={ghost as string | undefined}
212
+ id={domIds.control}
213
+ {describedBy}
214
+ onCommit={onCommitScalar}
215
+ {optionAllowed}
216
+ {enumDisallowed}
217
+ />
218
+ {:else if field.control === 'number'}
219
+ <NumberField
220
+ value={value as number | undefined}
221
+ integer={field.schema.type === 'integer'}
222
+ fallback={ghost as number | undefined}
223
+ id={domIds.control}
224
+ {describedBy}
225
+ onCommit={onCommitScalar}
226
+ />
227
+ {:else if field.control === 'boolean'}
228
+ <BooleanField
229
+ value={value as boolean | undefined}
230
+ fallback={ghost as boolean | undefined}
231
+ id={domIds.control}
232
+ {describedBy}
233
+ onCommit={onCommitScalar}
234
+ />
235
+ {:else if field.control === 'date'}
236
+ <DateField
237
+ bind:this={dateEl}
238
+ value={value as string | undefined}
239
+ fallback={defaultStr}
240
+ labelledBy={domIds.label}
241
+ {describedBy}
242
+ onCommit={onCommitScalar}
243
+ />
244
+ {:else if field.control === 'array'}
245
+ <ArrayField
246
+ bind:this={arrayEl}
247
+ value={value as unknown[] | undefined}
248
+ items={field.schema.items}
249
+ label={field.label}
250
+ required={field.required}
251
+ description={field.description}
252
+ labelId={domIds.label}
253
+ descriptionId={domIds.description}
254
+ onCommit={onCommitScalar}
255
+ />
256
+ {:else if field.control === 'object'}
257
+ <ObjectField
258
+ bind:this={objectEl}
259
+ value={value as Record<string, unknown> | undefined}
260
+ properties={field.schema.properties}
261
+ label={field.label}
262
+ labelledBy={domIds.label}
263
+ {describedBy}
264
+ onCommit={onCommitScalar}
265
+ />
266
+ {:else}
267
+ <TextField
268
+ value={value as string | undefined}
269
+ placeholder={defaultStr}
270
+ id={domIds.control}
271
+ {describedBy}
272
+ onCommit={onCommitScalar}
273
+ />
274
+ {/if}
275
+ </div>
218
276
 
219
- <DiagnosticList {diagnostics} />
277
+ <DiagnosticList {diagnostics} />
278
+ </div>
220
279
  </div>
221
280
 
222
281
  <style>
223
282
  /* The `full` span, and the base every field starts from: its own row, a plain
224
283
  stack. Nothing shares the row, so there are no internals to align against.
225
284
 
226
- THE ACTION COLUMN IS THE FIELD'S INSET, not the section's:
227
- a row action's tap target plus the grid's own column gutter, held clear at the
228
- end of whatever the field spans. On the SECTION it reserves one trailing column
229
- for the whole grid, which reaches only a field that stops short of it. The array
230
- carries the only row action there is, and it always owns its row (structure.ts
231
- `packable`), so it spans that reservation with every other column: the one field
232
- a section could reserve for is the one it would miss. Per field the reservation
233
- travels with the thing that uses it: one right edge per track, every control in a
234
- row ending on it, and an array reaching back across its own to put its remove
235
- past it (ArrayField). Every field pays the width, including one with no action in
236
- it; a right edge that moved with a field's contents is the raggedness this
237
- removes. */
285
+ No inset at the end: the right edge is the track's and every control in a row ends
286
+ on it, a row action sitting inside the element that carries it (ArrayField) rather
287
+ than in a column every field would hold clear. */
238
288
  .qm-field {
239
289
  display: flex;
240
290
  flex-direction: column;
241
291
  gap: var(--_qm-space);
242
292
  grid-column: 1 / -1;
243
293
  min-width: 0;
244
- padding-right: var(--action-col);
245
294
  }
246
295
  /* A row-sharing field subgrids onto the section's row tracks instead of sizing its
247
- own: three tracks (label, control, diagnostics) taken from the parent, so every
248
- control in a visual row starts at the same y however tall a neighbour's label
249
- wrapped, and one field's diagnostic lifts none of the others out of line. Source
250
- order IS track order; `align-items: start` keeps a short control from stretching
251
- to a taller sibling's track. `row-gap` overrides the section's inter-row gutter
252
- for the tracks this field spans: inside a field the rhythm is tighter than
253
- between rows. */
296
+ own: two tracks, the label and the control's stack, taken from the parent, so
297
+ every control in a visual row starts at the same y however tall a neighbour's
298
+ label wrapped. Source order is track order; `align-items: start` keeps a short
299
+ control from stretching to a taller sibling's track. `row-gap` overrides the
300
+ section's inter-row gutter for the tracks this field spans: inside a field the
301
+ rhythm is tighter than between rows.
302
+
303
+ Two tracks, and not a third for the diagnostics: a track is permanent where a
304
+ diagnostic is occasional, and an empty track still costs the gutter above it, so
305
+ a third stands a rung of dead space under every field on the path where nothing
306
+ is wrong. That rung is the one place a field's box outruns its ink. It is what
307
+ makes a run of row-sharing fields read looser than the arrays and prose leaves
308
+ beside them, whose boxes end where their ink does. What a third track buys is the
309
+ diagnostics of one row starting at one y; the alignment a row is read by is the
310
+ controls', and the label track holds that. A diagnostic grows its row instead. */
254
311
  .qm-field.cell,
255
312
  .qm-field.lone {
256
313
  display: grid;
257
- grid-row: span 3;
314
+ grid-row: span 2;
258
315
  grid-template-rows: subgrid;
259
316
  row-gap: var(--_qm-space);
260
317
  align-items: start;
261
318
  }
319
+ /* The stack the control's track holds: the control, and the diagnostics when
320
+ there are any. Its gap is the field's own, so a diagnostic sits the same rung
321
+ under its control that the control sits under its label. */
322
+ .qm-field-stack {
323
+ display: flex;
324
+ flex-direction: column;
325
+ gap: var(--_qm-space);
326
+ min-width: 0;
327
+ }
262
328
  .qm-field.cell {
263
329
  grid-column: span 1;
264
330
  }
331
+ /* Positioned for the arrival wash a landing inserts (`core/bloom.ts`), the way
332
+ `.qm-prose` is: an inset child over the control, since an `<input>` takes none.
333
+ The radius is the control's own, so the wash's corners are the box's rather than
334
+ square over a rounded one; on an array or a subform it bounds a group, where
335
+ there is no single box for it to disagree with. */
336
+ .qm-field-control {
337
+ position: relative;
338
+ border-radius: var(--_qm-radius-inner);
339
+ }
265
340
  /* A run of one takes half the capacity from column 1: `--cols-half` is the section's
266
341
  capacity halved, so the edge lands on a track boundary at every capacity. */
267
342
  .qm-field.lone {
@@ -1,6 +1,6 @@
1
1
  import type { Document, Quill, Addr, Diagnostic, ResolvedField } from '@quillmark/wasm';
2
2
  import type { EditorErrorHandler } from '../core/errors.js';
3
- import type { FieldController } from '../core/codec/index.js';
3
+ import type { LeafRegistry } from './leaves.js';
4
4
  import type { FieldModel, FieldSpan } from './structure.js';
5
5
  import type { FieldDomIds } from './domid.js';
6
6
  interface Props {
@@ -14,22 +14,26 @@ interface Props {
14
14
  doc: Document;
15
15
  /** The schema a prose leaf reads its content through (`ProseField`). */
16
16
  quill: Quill;
17
- /** LIVE prose address (getter-`card`); used only when control === 'prose'. */
18
- proseAddr: Addr;
17
+ /** This field's live address (getter-`card`, so a card reorder re-targets in
18
+ * place): the prose leaf commits to it, and a focus reports it. */
19
+ addr: Addr;
19
20
  leafKey: string;
20
21
  /** This field's three DOM names, derived from `leafKey` (see `domid.ts`): how
21
22
  * the label and the control find each other. */
22
23
  domIds: FieldDomIds;
23
24
  onCommitScalar: (value: unknown) => void;
24
- /** Enum-option policy: `false` disables that option. Only the enum
25
- * control reads it; other controls ignore it. */
25
+ /** Enum-option policy: `false` marks that option unavailable. Only the enum
26
+ * control reads this pair. */
26
27
  optionAllowed?: (value: string) => boolean;
28
+ /** How a refused option draws: greyed (default) or left out. */
29
+ enumDisallowed?: 'hide' | 'disable';
27
30
  onFocus?: (addr: Addr) => void;
28
31
  onCaretMove?: (addr: Addr, pos: number) => void;
29
32
  onChange?: (addr: Addr) => void;
30
33
  onError?: EditorErrorHandler;
31
- register?: (key: string, controller: FieldController) => void;
32
- unregister?: (key: string) => void;
34
+ /** The editor's leaf registry (`leaves.ts`): a form control registers its
35
+ * landing handle here, a prose leaf its controller from inside `ProseField`. */
36
+ leaves?: LeafRegistry;
33
37
  diagnostics?: Diagnostic[];
34
38
  }
35
39
  declare const Field: import("svelte").Component<Props, {}, "">;