@quillmark/svelte 0.5.1 → 0.7.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 (66) hide show
  1. package/README.md +6 -0
  2. package/THEMING.md +13 -17
  3. package/dist/core/address.d.ts +17 -3
  4. package/dist/core/address.js +20 -3
  5. package/dist/core/codec/blocks.d.ts +37 -0
  6. package/dist/core/codec/blocks.js +163 -0
  7. package/dist/core/codec/field.js +5 -6
  8. package/dist/core/codec/index.d.ts +1 -1
  9. package/dist/core/codec/index.js +3 -2
  10. package/dist/core/codec/inputrules.js +24 -144
  11. package/dist/core/codec/prose.css +14 -25
  12. package/dist/core/codec/schema.d.ts +8 -0
  13. package/dist/core/codec/schema.js +33 -1
  14. package/dist/core/codec/slash.d.ts +18 -16
  15. package/dist/core/codec/slash.js +0 -0
  16. package/dist/core/codec/table-view.d.ts +4 -0
  17. package/dist/core/codec/table-view.js +137 -158
  18. package/dist/core/codec/table.d.ts +0 -10
  19. package/dist/core/codec/table.js +0 -15
  20. package/dist/core/theme.css +357 -346
  21. package/dist/preset/recipes.css +25 -21
  22. package/dist/preview/Preview.svelte +4 -2
  23. package/dist/preview/bridge.d.ts +23 -0
  24. package/dist/preview/bridge.js +92 -35
  25. package/dist/preview/controller.d.ts +6 -5
  26. package/dist/preview/controller.js +13 -7
  27. package/dist/preview/geometry.d.ts +10 -9
  28. package/dist/preview/geometry.js +11 -13
  29. package/dist/preview/index.js +3 -2
  30. package/dist/preview/paint.js +9 -1
  31. package/dist/visual/ArrayField.svelte +246 -63
  32. package/dist/visual/ArrayField.svelte.d.ts +5 -1
  33. package/dist/visual/Card.svelte +84 -50
  34. package/dist/visual/EnumField.svelte +37 -3
  35. package/dist/visual/Field.svelte +28 -4
  36. package/dist/visual/FieldLabel.svelte +2 -2
  37. package/dist/visual/FormatPopover.svelte +13 -1
  38. package/dist/visual/ObjectField.svelte +128 -35
  39. package/dist/visual/ObjectField.svelte.d.ts +11 -4
  40. package/dist/visual/ProseArrayElement.svelte +22 -2
  41. package/dist/visual/ProseArrayElement.svelte.d.ts +1 -0
  42. package/dist/visual/SlashMenu.svelte +7 -0
  43. package/dist/visual/TipsCard.svelte +33 -13
  44. package/dist/visual/VariantField.svelte +95 -0
  45. package/dist/visual/VariantField.svelte.d.ts +21 -0
  46. package/dist/visual/VisualEditorInner.svelte +44 -21
  47. package/dist/visual/controls.css +282 -283
  48. package/dist/visual/diagnostics.d.ts +13 -11
  49. package/dist/visual/diagnostics.js +11 -9
  50. package/dist/visual/domid.d.ts +13 -0
  51. package/dist/visual/domid.js +16 -0
  52. package/dist/visual/hold.d.ts +17 -0
  53. package/dist/visual/hold.js +39 -0
  54. package/dist/visual/index.js +1 -1
  55. package/dist/visual/leaves.d.ts +9 -7
  56. package/dist/visual/links.d.ts +5 -2
  57. package/dist/visual/links.js +9 -3
  58. package/dist/visual/motion.d.ts +9 -0
  59. package/dist/visual/motion.js +10 -2
  60. package/dist/visual/strings.d.ts +15 -0
  61. package/dist/visual/strings.js +3 -0
  62. package/dist/visual/structure.d.ts +32 -6
  63. package/dist/visual/structure.js +49 -3
  64. package/package.json +2 -2
  65. package/dist/core/codec/clipboard.d.ts +0 -59
  66. package/dist/core/codec/clipboard.js +0 -202
@@ -3,15 +3,23 @@
3
3
  edit / add / remove rebuilds the whole array and hands it to the parent's typed
4
4
  `writer.set(field, wholeArray)` (arrays are not op-addressed). Element control
5
5
  by `items.type`: `richtext` / `plaintext` → a prose element
6
- ({@link ProseArrayElement}), `object` → a minimal JSON editor, keeping the prior
7
- element value on an entry that does not parse, everything else → a text input. The
6
+ ({@link ProseArrayElement}), `object` → a summary row that opens onto a subform,
7
+ everything else → a text input. The
8
8
  add affordance sits in the label header row (space-between with the field label);
9
9
  {@link Field} skips its own label for array controls and hands this component the
10
10
  label track with it.
11
11
 
12
+ An `object` element collapses: the row is its own summary — a box, titled by the
13
+ element's first `string` cell — and opens onto {@link ObjectField}, one at a time.
14
+ Stacking the subforms instead would nest a field one level past the depth the
15
+ subform's own vertical draws, once per row. Opening is therefore part of a landing rather than something the
16
+ user does first: `focusElement` opens the row it is aimed at before it focuses.
17
+
12
18
  The remove is inside the element: a slab over the end of the element's own box, taking
13
19
  its two end-side corners. So a row's box is the element's box, and an array's rows end
14
- where every other field's control does.
20
+ where every other field's control does. On an object element that box is the summary
21
+ rather than the open row: a destructive control belongs to the line it sits on, not to
22
+ everything that line has unfolded.
15
23
 
16
24
  Keys carry the list without the mouse: Enter inserts a
17
25
  sibling below and takes the caret there, Backspace on an empty element removes it
@@ -34,12 +42,18 @@
34
42
  import { emptyContent } from '../core/codec/index.js';
35
43
  import { createLifespan } from '../core/teardown.js';
36
44
  import { IdSeq, controlKind } from './structure.js';
45
+ import { holdInView } from './hold.js';
37
46
  import Icon from './icons/Icon.svelte';
38
47
  import TextField from './TextField.svelte';
48
+ import ObjectField from './ObjectField.svelte';
39
49
  import ProseArrayElement from './ProseArrayElement.svelte';
40
50
  import FieldLabel from './FieldLabel.svelte';
41
51
  import './controls.css';
42
52
 
53
+ /** The disclosure glyph, at the size the accordion's own chevron takes: one
54
+ * disclosure figure per surface, so it is one glyph at one size. */
55
+ const CHEVRON = 16;
56
+
43
57
  interface Props {
44
58
  value: unknown[] | undefined;
45
59
  items: QuillFieldSchema | undefined;
@@ -55,6 +69,10 @@
55
69
  labelId?: string;
56
70
  /** Where the description parks, for the group's `aria-describedby`. */
57
71
  descriptionId?: string;
72
+ /** The field's control id: the base an `object` element's subform derives its
73
+ * properties' own names from, one `-e-<element id>` segment down, so each open
74
+ * record's cells carry real `<label for>` pairs like every other control. */
75
+ idBase?: string;
58
76
  /** Element `i`'s content, decoded by the boundary through the codec `items`
59
77
  * names (`Field`, `reader.getContentAt`). Asked only for a content-typed
60
78
  * element: on any other the read is not content and throws. `undefined` for a
@@ -71,6 +89,7 @@
71
89
  description,
72
90
  labelId,
73
91
  descriptionId,
92
+ idBase,
74
93
  elementContent,
75
94
  onCommit
76
95
  }: Props = $props();
@@ -115,11 +134,47 @@
115
134
  // it says so once per element per render. Thirteen lines on one memo's first paint,
116
135
  // in the console a consumer is reading to find its own defects. Same shape
117
136
  // {@link Card} keeps its header/panel refs in.
118
- const els: Record<string, { focus: () => void } | undefined> = $state({});
137
+ const els: Record<string, { focus: () => void; setCaret?: (pos: number) => void } | undefined> =
138
+ $state({});
119
139
  let addEl: HTMLButtonElement | undefined = $state();
120
140
  let rootEl: HTMLElement | undefined = $state();
121
141
  let rowsEl: HTMLElement | undefined = $state();
122
142
 
143
+ // ── Object elements: one open at a time ──────────────────────────────────────
144
+ // So an array of ten records is ten lines and one figure, whatever its length.
145
+ let openId = $state<string | undefined>(undefined);
146
+ // The open row's subform, for the landing below. One entry, never a map: only one
147
+ // row is open, so the ref is singular by the same rule the state is.
148
+ let openObjEl = $state<{ focus: () => void } | undefined>();
149
+
150
+ /** The property whose value titles a collapsed row: the first `string` cell in
151
+ * declaration order, which is the order a schema states its own priority in. */
152
+ const titleKey = $derived.by(() => {
153
+ for (const [k, sub] of Object.entries(items?.properties ?? {})) {
154
+ if (controlKind(sub) === 'text') return k;
155
+ }
156
+ return undefined;
157
+ });
158
+ /** A collapsed row's own words: the title cell's committed value, or `undefined`
159
+ * while it has none. */
160
+ function elementTitle(k: number): string | undefined {
161
+ const el = arr[k] as Record<string, unknown> | undefined;
162
+ const v = titleKey ? el?.[titleKey] : undefined;
163
+ return typeof v === 'string' && v.trim() ? v : undefined;
164
+ }
165
+ /** What an untitled row reads as: the name its `aria-label` already spends,
166
+ * `label` + the 1-based index. */
167
+ function untitled(k: number): string {
168
+ return label != null ? t.strings.elementUntitled(label, k + 1) : String(k + 1);
169
+ }
170
+ /** The summary is the anchor: a row closing above it is what would carry it off the fold,
171
+ * and a row's subform hangs under its own summary (`hold.ts`). */
172
+ function toggleRow(id: string, summary: HTMLElement): void {
173
+ holdInView(summary, () => {
174
+ openId = openId === id ? undefined : id;
175
+ });
176
+ }
177
+
123
178
  // The awaited flush below is the only work that outlives a gesture here, so the
124
179
  // span carries no cancellers: it is the liveness `focusAfterFlush` asks for.
125
180
  const span = createLifespan();
@@ -148,6 +203,9 @@
148
203
  const next = arr.slice();
149
204
  next.splice(at, 0, emptyElement());
150
205
  onCommit(next);
206
+ // A row added is a row to fill in, so an object element arrives open: landing on
207
+ // a collapsed empty summary would make adding one a two-press gesture.
208
+ if (control === 'object') openId = id;
151
209
  focusAfterFlush(id);
152
210
  }
153
211
  function add(): void {
@@ -158,6 +216,9 @@
158
216
  const next = ids.filter((_, i) => i !== k);
159
217
  ids = next;
160
218
  delete els[dropped];
219
+ // The open row can be the one removed; `openId` is cleared with it rather than
220
+ // left naming an element that has gone.
221
+ if (openId === dropped) openId = undefined;
161
222
  onCommit(arr.filter((_, i) => i !== k));
162
223
  // Focus lands on the element before the removed one, or on the one that slid
163
224
  // into its place; on the add affordance once the list is empty, which is then
@@ -171,11 +232,20 @@
171
232
  * ask one function so they cannot disagree (`Field`, `leaves.ts`). */
172
233
  export function focus(): void {
173
234
  if (ids.length === 0) return void addEl?.focus();
174
- const first = els[ids[0]];
175
- if (first) return first.focus();
176
- // The JSON element registers no controller: it is a plain textarea, with
177
- // nothing about focusing it that the DOM does not already know.
178
- rootEl?.querySelector<HTMLTextAreaElement>('.qm-array-row textarea')?.focus();
235
+ if (control === 'object') return focusObjectRow(ids[0]);
236
+ els[ids[0]]?.focus();
237
+ }
238
+ /** An object row's landing: inside the subform when that row is the open one, on
239
+ * the row's own summary otherwise — a collapsed row's control is its summary. */
240
+ function focusObjectRow(id: string): void {
241
+ if (id === openId && openObjEl) return openObjEl.focus();
242
+ // Matched on the dataset: spelling an id into a selector needs `CSS.escape`,
243
+ // which the test DOM does not carry.
244
+ const rows = rowsEl?.querySelectorAll<HTMLElement>('[data-qm-el]') ?? [];
245
+ for (const row of rows) {
246
+ if (row.dataset.qmEl === id)
247
+ return row.querySelector<HTMLElement>('.qm-element-summary')?.focus();
248
+ }
179
249
  }
180
250
  /** The box an arrival wash blooms in (`leaves.ts`, `core/bloom.ts`): the elements,
181
251
  * not the header above them. This component owns the field's label, so the wrapper
@@ -185,15 +255,30 @@
185
255
  export function washBox(): HTMLElement | undefined {
186
256
  return rowsEl;
187
257
  }
188
- /** Take the caret to element `k`: what a landing on an element address resolves to
189
- * (`leaves.ts`). The index resolves to the element's session id here, at the call,
190
- * never carried as one — an index is stale the moment anything above it splices.
191
- * Past the live list it falls back to {@link focus}: the field is right and the row
192
- * is gone, which is a landing off a compile the document has moved past. */
193
- export function focusElement(k: number): void {
258
+ /** Take the caret to element `k`, at USV `pos` where the row's control can take one:
259
+ * what a landing on an element address resolves to (`leaves.ts`). The index resolves
260
+ * to the element's session id here, at the call, never carried as one — an index is
261
+ * stale the moment anything above it splices. Past the live list it falls back to
262
+ * {@link focus}: the field is right and the row is gone, which is a landing off a
263
+ * compile the document has moved past.
264
+ *
265
+ * An absent `pos` is the placement rung, exactly as on `Landing`. A row that takes
266
+ * no offset gets the bare focus: the JSON element, whose textarea has no coordinate
267
+ * to spend one in, and a `string` element, which the compile never addresses. */
268
+ export function focusElement(k: number, pos?: number): void {
269
+ // A collapsed row holds no control for a caret to land in, so opening it is part
270
+ // of the landing rather than something the user does first: a preview click that
271
+ // resolves into an element has to arrive somewhere the caret can sit.
272
+ if (control === 'object') {
273
+ const id = ids[k];
274
+ if (id === undefined) return focus();
275
+ openId = id;
276
+ return void focusAfterFlush(id);
277
+ }
194
278
  const el = els[ids[k]];
195
- if (el) el.focus();
196
- else focus();
279
+ if (!el) return focus();
280
+ if (pos != null && el.setCaret) return el.setCaret(pos);
281
+ el.focus();
197
282
  }
198
283
  /** Focus element `id` after the flush, never in the same tick: a mutation commits
199
284
  * the array by value, so the parent re-derives and the row does not exist until
@@ -203,8 +288,9 @@
203
288
  * unmounts this component inside the window (core/teardown.ts). */
204
289
  async function focusAfterFlush(id: string | undefined): Promise<void> {
205
290
  if (!(await span.resumes(tick()))) return;
206
- if (id === undefined) addEl?.focus();
207
- else els[id]?.focus();
291
+ if (id === undefined) return void addEl?.focus();
292
+ if (control === 'object') return focusObjectRow(id);
293
+ els[id]?.focus();
208
294
  }
209
295
  /** Whether element `k` reads empty to the user. A text element's committed value
210
296
  * lags the input: a cleared field commits at `change`, not per keystroke
@@ -244,6 +330,7 @@
244
330
  <div
245
331
  bind:this={rootEl}
246
332
  class="qm-array"
333
+ class:empty={ids.length === 0}
247
334
  role="group"
248
335
  aria-labelledby={label != null ? labelId : undefined}
249
336
  aria-describedby={description ? descriptionId : undefined}
@@ -270,45 +357,77 @@
270
357
  </div>
271
358
  <div class="qm-array-rows" class:empty={ids.length === 0} bind:this={rowsEl}>
272
359
  {#each ids as id, k (id)}
273
- <div class="qm-array-row">
274
- {#if control === 'prose'}
275
- <ProseArrayElement
276
- bind:this={els[id]}
277
- content={() => elementContent(k) ?? emptyContent()}
278
- plaintext={items?.type === 'plaintext'}
279
- label={label != null ? `${label} ${k + 1}` : undefined}
280
- onChange={(rt) => commitElement(k, rt)}
281
- onKey={(e) => onElementKey(e, k)}
282
- />
283
- {:else if control === 'object'}
284
- <textarea
285
- class="qm-input qm-json qm-focus-ring"
286
- aria-label={label != null ? `${label} ${k + 1}` : undefined}
287
- value={JSON.stringify(arr[k] ?? {})}
288
- onchange={(e) => {
289
- try {
290
- commitElement(k, JSON.parse((e.currentTarget as HTMLTextAreaElement).value));
291
- } catch {
292
- /* keep prior value on invalid JSON */
293
- }
294
- }}
295
- ></textarea>
296
- {:else}
297
- <TextField
298
- bind:this={els[id]}
299
- value={String(arr[k] ?? '')}
300
- label={label != null ? `${label} ${k + 1}` : undefined}
301
- onCommit={(v) => commitElement(k, v)}
302
- onKey={(e) => onElementKey(e, k)}
303
- />
304
- {/if}
305
- <button
306
- type="button"
307
- class="qm-icon-btn qm-remove qm-focus-ring"
308
- title={t.strings.arrayRemove}
309
- onclick={() => remove(k)}><Icon name="minus" /></button
310
- >
311
- </div>
360
+ {#if control === 'object'}
361
+ {@const open = openId === id}
362
+ {@const shown = elementTitle(k)}
363
+ <div class="qm-array-row qm-element" class:open data-qm-el={id}>
364
+ <!-- The head is the row in collapsed form, and it is a box: the element IS
365
+ a value, the way the enum trigger is, and the remove slab's grammar
366
+ (the box's two end-side corners) needs corners to take. So a list of
367
+ records measures like a list of inputs whatever the element type. -->
368
+ <div class="qm-element-head">
369
+ <button
370
+ type="button"
371
+ class="qm-control-box qm-focus-ring qm-element-summary"
372
+ aria-expanded={open}
373
+ onclick={(e) => toggleRow(id, e.currentTarget)}
374
+ >
375
+ <!-- Leading, and it rotates: trailing is the figure for pushing a new
376
+ screen, where this unfolds in place. Same glyph, same rotation and
377
+ same rung as the accordion's, so the surface has one disclosure. -->
378
+ <Icon name="chevron-right" class="qm-el-chevron" size={CHEVRON} />
379
+ {#if shown}
380
+ <span class="qm-element-title">{shown}</span>
381
+ {:else}
382
+ <span class="qm-element-title untitled">{untitled(k)}</span>
383
+ {/if}
384
+ </button>
385
+ <button
386
+ type="button"
387
+ class="qm-icon-btn qm-remove qm-focus-ring"
388
+ title={t.strings.arrayRemove}
389
+ onclick={() => remove(k)}><Icon name="minus" /></button
390
+ >
391
+ </div>
392
+ {#if open}
393
+ <ObjectField
394
+ bind:this={openObjEl}
395
+ value={(arr[k] ?? {}) as Record<string, unknown>}
396
+ properties={items?.properties}
397
+ label={label != null ? `${label} ${k + 1}` : undefined}
398
+ idBase={idBase != null ? `${idBase}-e-${id}` : undefined}
399
+ onCommit={(obj) => commitElement(k, obj)}
400
+ />
401
+ {/if}
402
+ </div>
403
+ {:else}
404
+ <div class="qm-array-row">
405
+ {#if control === 'prose'}
406
+ <ProseArrayElement
407
+ bind:this={els[id]}
408
+ content={() => elementContent(k) ?? emptyContent()}
409
+ plaintext={items?.type === 'plaintext'}
410
+ label={label != null ? `${label} ${k + 1}` : undefined}
411
+ onChange={(rt) => commitElement(k, rt)}
412
+ onKey={(e) => onElementKey(e, k)}
413
+ />
414
+ {:else}
415
+ <TextField
416
+ bind:this={els[id]}
417
+ value={String(arr[k] ?? '')}
418
+ label={label != null ? `${label} ${k + 1}` : undefined}
419
+ onCommit={(v) => commitElement(k, v)}
420
+ onKey={(e) => onElementKey(e, k)}
421
+ />
422
+ {/if}
423
+ <button
424
+ type="button"
425
+ class="qm-icon-btn qm-remove qm-focus-ring"
426
+ title={t.strings.arrayRemove}
427
+ onclick={() => remove(k)}><Icon name="minus" /></button
428
+ >
429
+ </div>
430
+ {/if}
312
431
  {/each}
313
432
  </div>
314
433
  </div>
@@ -399,11 +518,68 @@
399
518
  background: var(--_qm-danger-tint);
400
519
  color: var(--_qm-danger);
401
520
  }
402
- /* The JSON element is a `.qm-input` (controls.css): box, focus ring, and all;
403
- what a textarea adds over an input is the face and a floor on its height. */
404
- .qm-json {
405
- font-family: var(--_qm-font-mono);
406
- min-height: 2.5rem;
521
+ /* ── An object element ──────────────────────────────────────────────────────
522
+ The row is a summary and the subform hangs under it. `row-gap` rather than a
523
+ margin on the subform, because the distance between a control and what it has
524
+ unfolded belongs to the thing stacking them: the variant field spends its own `gap` on
525
+ exactly this, and what `ObjectField` adds is the cap on its own stroke, equal at both
526
+ of its ends. */
527
+ .qm-element {
528
+ row-gap: var(--_qm-space-2);
529
+ }
530
+ /* The slab measures the head, not the row: an open element is the head plus
531
+ everything it unfolded, and a destructive control belongs to the line it sits on
532
+ rather than to all of that. Positioned, so `.qm-remove` anchors here. */
533
+ .qm-element-head {
534
+ position: relative;
535
+ display: grid;
536
+ grid-template-columns: minmax(0, 1fr);
537
+ }
538
+ /* A box, which the button family otherwise is not: the row IS the element, a value
539
+ in collapsed form the way the enum trigger is one, and the slab's grammar — the
540
+ box's two end-side corners — needs a box with corners to take. It carries
541
+ `.qm-control-box`, so the fill, the radius, the inset and the type are the recipe's
542
+ (controls.css) and a list of records measures like the list of inputs beside it.
543
+ The end inset the slab stands in arrives from the row's own rule above. */
544
+ .qm-element-summary {
545
+ display: flex;
546
+ align-items: center;
547
+ gap: var(--_qm-space);
548
+ width: 100%;
549
+ box-sizing: border-box;
550
+ text-align: start;
551
+ cursor: pointer;
552
+ }
553
+ /* No hover fill: a well does not fill under the pointer anywhere on this surface.
554
+ The chevron's ink is the cue, which is the accordion header's own ladder. */
555
+ .qm-element-summary :global(.qm-el-chevron) {
556
+ flex-shrink: 0;
557
+ display: block;
558
+ color: var(--_qm-ink-label);
559
+ transform: rotate(0deg);
560
+ transform-origin: center;
561
+ transition:
562
+ transform var(--_qm-duration-slow) var(--_qm-ease-reverse),
563
+ color var(--_qm-duration-fast) var(--_qm-ease-reverse);
564
+ }
565
+ .qm-element-summary:hover :global(.qm-el-chevron),
566
+ .qm-element.open :global(.qm-el-chevron) {
567
+ color: var(--_qm-ink);
568
+ }
569
+ .qm-element.open :global(.qm-el-chevron) {
570
+ transform: rotate(90deg);
571
+ }
572
+ /* The title is the element's own value, so it reads at the ink a written value
573
+ takes, on one line however long the cell runs. An untitled row has nothing written
574
+ in it yet and says so the way every other empty rung does. */
575
+ .qm-element-title {
576
+ overflow: hidden;
577
+ text-overflow: ellipsis;
578
+ white-space: nowrap;
579
+ }
580
+ .qm-element-title.untitled {
581
+ color: var(--_qm-ink-label);
582
+ font-style: italic;
407
583
  }
408
584
  /* It rests dim and comes up on hover of the field rather than of itself: one trigger
409
585
  at the head of a row of controls is found by looking at the array, not by grazing its
@@ -424,6 +600,13 @@
424
600
  .qm-add-el:focus-visible {
425
601
  opacity: 1;
426
602
  }
603
+ /* Empty, the rows box is `display: none` and the field is a label and this trigger:
604
+ nothing left for it to compete with, and nothing else in the field to act on. So
605
+ it rests full-strength there and takes the recede back once it heads a list. A
606
+ receded trigger beside no content reads as a caption rather than as a way in. */
607
+ .qm-array.empty .qm-add-el {
608
+ opacity: 1;
609
+ }
427
610
  @media (hover: none) {
428
611
  .qm-add-el,
429
612
  .qm-remove {
@@ -15,6 +15,10 @@ interface Props {
15
15
  labelId?: string;
16
16
  /** Where the description parks, for the group's `aria-describedby`. */
17
17
  descriptionId?: string;
18
+ /** The field's control id: the base an `object` element's subform derives its
19
+ * properties' own names from, one `-e-<element id>` segment down, so each open
20
+ * record's cells carry real `<label for>` pairs like every other control. */
21
+ idBase?: string;
18
22
  /** Element `i`'s content, decoded by the boundary through the codec `items`
19
23
  * names (`Field`, `reader.getContentAt`). Asked only for a content-typed
20
24
  * element: on any other the read is not content and throws. `undefined` for a
@@ -26,7 +30,7 @@ interface Props {
26
30
  declare const ArrayField: import("svelte").Component<Props, {
27
31
  focus: () => void;
28
32
  washBox: () => HTMLElement | undefined;
29
- focusElement: (k: number) => void;
33
+ focusElement: (k: number, pos?: number) => void;
30
34
  }, "">;
31
35
  type ArrayField = ReturnType<typeof ArrayField>;
32
36
  export default ArrayField;