@quillmark/svelte 0.11.0 → 0.12.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.
- package/dist/core/address.d.ts +17 -10
- package/dist/core/address.js +26 -12
- package/dist/core/bloom.js +4 -3
- package/dist/core/codec/decode.js +21 -15
- package/dist/core/codec/encode.js +1 -1
- package/dist/core/codec/islands.d.ts +5 -4
- package/dist/core/codec/islands.js +23 -38
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/rungs.d.ts +8 -0
- package/dist/core/rungs.js +24 -0
- package/dist/core/theme.css +10 -0
- package/dist/visual/ArrayField.svelte +596 -167
- package/dist/visual/ArrayField.svelte.d.ts +14 -1
- package/dist/visual/BooleanField.svelte +10 -7
- package/dist/visual/Card.svelte +3 -22
- package/dist/visual/Field.svelte +69 -29
- package/dist/visual/MatrixField.svelte +352 -0
- package/dist/visual/MatrixField.svelte.d.ts +36 -0
- package/dist/visual/ObjectField.svelte +351 -133
- package/dist/visual/ObjectField.svelte.d.ts +17 -2
- package/dist/visual/VariantField.svelte +55 -2
- package/dist/visual/VariantField.svelte.d.ts +10 -1
- package/dist/visual/VisualEditorInner.svelte +53 -49
- package/dist/visual/controls.css +31 -0
- package/dist/visual/diagnostics.d.ts +26 -2
- package/dist/visual/diagnostics.js +52 -5
- package/dist/visual/hold.js +2 -2
- package/dist/visual/icons/nodes.d.ts +3 -0
- package/dist/visual/icons/nodes.js +2 -1
- package/dist/visual/leaves.d.ts +25 -12
- package/dist/visual/motion.d.ts +14 -5
- package/dist/visual/motion.js +13 -11
- package/dist/visual/strings.d.ts +19 -9
- package/dist/visual/strings.js +6 -2
- package/dist/visual/structure.d.ts +94 -9
- package/dist/visual/structure.js +229 -12
- package/package.json +2 -2
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
A `matrix` field → ticks over the roster, columns unfolding under a held member
|
|
3
|
+
(VISUAL_EDITOR §"Structure mirrors the schema", canon `SCHEMAS.md` §Matrix). The
|
|
4
|
+
roster is one flat list, one member to a row, or abreast on the section's track ladder
|
|
5
|
+
under `ui.compact`; each member is a real checkbox bound to its `held` cell with
|
|
6
|
+
its title as the `<label for>`, an unheld member reading at the label ink and a held
|
|
7
|
+
one at the value ink. A held member unfolds its columns beneath its title, drawn by
|
|
8
|
+
{@link ObjectField} over the matrix's `properties` at the member's key, the way a
|
|
9
|
+
variant unfolds its cells; zero columns is a checklist and unfolds nothing. An unheld
|
|
10
|
+
member's retained columns are held but undrawn, the variant rule: in the document and
|
|
11
|
+
not on the page.
|
|
12
|
+
|
|
13
|
+
The control owns its label row, as an array does: the count of held members sits
|
|
14
|
+
where an array's add chip sits, one piece of chrome with one answer, so {@link Field}
|
|
15
|
+
skips its own label for it.
|
|
16
|
+
|
|
17
|
+
Commit is whole: the sparse map, one `writer.set`. A tick writes the member object
|
|
18
|
+
with an explicit `held`, an untick keeps the columns under `held: false`, and a member
|
|
19
|
+
unheld with no columns leaves the map (`memberWrite`). What is blank per column is the
|
|
20
|
+
engine's rule and not restated: a column control that clears drops its key, so the
|
|
21
|
+
editor drops a member it has emptied rather than deciding skippability for itself.
|
|
22
|
+
The read takes both rest forms — a bare tick and `{held?, …columns}`, whose `held` rests
|
|
23
|
+
at `false` where the mapping names none — and the write lands one, on the member edited
|
|
24
|
+
alone; every other member rides through as the document spelled it.
|
|
25
|
+
-->
|
|
26
|
+
<script lang="ts">
|
|
27
|
+
import { tick as flush } from 'svelte';
|
|
28
|
+
import { wording } from './strings.js';
|
|
29
|
+
|
|
30
|
+
// The surface's words, ambient from the editor root; the package's English
|
|
31
|
+
// off-tree, so this component renders standalone too.
|
|
32
|
+
const t = wording();
|
|
33
|
+
import type { Content, PathStep, QuillFieldSchema } from '@quillmark/wasm';
|
|
34
|
+
import {
|
|
35
|
+
MATRIX_HELD,
|
|
36
|
+
commitMember,
|
|
37
|
+
matrixColumns,
|
|
38
|
+
matrixDeclares,
|
|
39
|
+
matrixHeld,
|
|
40
|
+
matrixMembers,
|
|
41
|
+
memberValue,
|
|
42
|
+
memberWrite
|
|
43
|
+
} from './structure.js';
|
|
44
|
+
import { splitDeep, unrouted, type DeepDiagnostic } from './diagnostics.js';
|
|
45
|
+
import type { LandingBox } from './leaves.js';
|
|
46
|
+
import { propertyDomIds } from './domid.js';
|
|
47
|
+
import Icon from './icons/Icon.svelte';
|
|
48
|
+
import FieldLabel from './FieldLabel.svelte';
|
|
49
|
+
import ObjectField from './ObjectField.svelte';
|
|
50
|
+
import DiagnosticList from './DiagnosticList.svelte';
|
|
51
|
+
import './controls.css';
|
|
52
|
+
|
|
53
|
+
interface Props {
|
|
54
|
+
/** The stored sparse map, or undefined while the field is unset. */
|
|
55
|
+
value: Record<string, unknown> | undefined;
|
|
56
|
+
/** The field's own schema: `members` is the roster, `properties` the columns. */
|
|
57
|
+
schema: QuillFieldSchema;
|
|
58
|
+
/** Field label, the naming prefix a member's columns compose with. */
|
|
59
|
+
label?: string;
|
|
60
|
+
/** Schema `description`: the label's help affordance. */
|
|
61
|
+
description?: string;
|
|
62
|
+
/** The label's own DOM id: the group's name, each tick keeping its own `<label>`. */
|
|
63
|
+
labelId?: string;
|
|
64
|
+
/** Where the description parks, for the group's `aria-describedby`. */
|
|
65
|
+
descriptionId?: string;
|
|
66
|
+
/** The field's control id: the base a member's tick and columns derive their own
|
|
67
|
+
* names from, one `-m-<member id>` segment down. */
|
|
68
|
+
idBase?: string;
|
|
69
|
+
/** The boundary's nested content read, rooted at this field: a member's content
|
|
70
|
+
* column reads at `[id, column]` (`reader.getContentAt`). */
|
|
71
|
+
contentAt: (path: PathStep[]) => Content | undefined;
|
|
72
|
+
onCommit: (v: Record<string, unknown> | undefined) => void;
|
|
73
|
+
/** The diagnostics routed into this field, each with its steps still to walk: one
|
|
74
|
+
* naming a member draws under that member, one naming nothing on the roster at the
|
|
75
|
+
* field's foot. */
|
|
76
|
+
diagnostics?: readonly DeepDiagnostic[];
|
|
77
|
+
}
|
|
78
|
+
let {
|
|
79
|
+
value,
|
|
80
|
+
schema,
|
|
81
|
+
label,
|
|
82
|
+
description,
|
|
83
|
+
labelId,
|
|
84
|
+
descriptionId,
|
|
85
|
+
idBase,
|
|
86
|
+
contentAt,
|
|
87
|
+
onCommit,
|
|
88
|
+
diagnostics
|
|
89
|
+
}: Props = $props();
|
|
90
|
+
|
|
91
|
+
const members = $derived(matrixMembers(schema.members));
|
|
92
|
+
const compact = $derived(!!schema.ui?.compact);
|
|
93
|
+
const hasColumns = $derived(Object.keys(schema.properties ?? {}).length > 0);
|
|
94
|
+
const held = (id: string): boolean => matrixHeld(memberValue(value, id));
|
|
95
|
+
const heldCount = $derived(members.filter((m) => held(m.id)).length);
|
|
96
|
+
const routed = $derived(splitDeep(diagnostics));
|
|
97
|
+
const foot = $derived(
|
|
98
|
+
unrouted(routed, (step) => typeof step === 'string' && matrixDeclares(schema, step))
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// A member with no field id space around it still needs an id: the tick is what its
|
|
102
|
+
// title's `for` names. Stable across SSR and hydration, which a counter is not.
|
|
103
|
+
const uid = $props.id();
|
|
104
|
+
const memberBase = (id: string): string =>
|
|
105
|
+
idBase != null ? `${idBase}-m-${id}` : `qm-${uid}-${id}`;
|
|
106
|
+
/** The tick is the member's `held` cell, so its id is the one that cell's label
|
|
107
|
+
* would take: the column subform below it derives its own names from the same base. */
|
|
108
|
+
const tickId = (id: string): string => propertyDomIds(memberBase(id), MATRIX_HELD).control;
|
|
109
|
+
const titleId = (id: string): string => propertyDomIds(memberBase(id), MATRIX_HELD).label;
|
|
110
|
+
|
|
111
|
+
// The member boxes, the ticks and the column subforms, keyed by member id — the
|
|
112
|
+
// roster's own key, stable for the schema's life. `$state` for the binding's sake
|
|
113
|
+
// (`ArrayField` keeps its element refs the same way).
|
|
114
|
+
type Subform = {
|
|
115
|
+
focus: () => void;
|
|
116
|
+
focusPath: (steps: readonly PathStep[], pos?: number) => LandingBox;
|
|
117
|
+
};
|
|
118
|
+
const memberEls: Record<string, HTMLElement | undefined> = $state({});
|
|
119
|
+
const tickEls: Record<string, HTMLInputElement | undefined> = $state({});
|
|
120
|
+
const colEls: Record<string, Subform | undefined> = $state({});
|
|
121
|
+
let rosterEl = $state<HTMLElement | undefined>();
|
|
122
|
+
/** A ref by member id, own keys only: an id can be spelled `constructor`. */
|
|
123
|
+
const ref = <T,>(map: Record<string, T | undefined>, id: string): T | undefined =>
|
|
124
|
+
Object.hasOwn(map, id) ? map[id] : undefined;
|
|
125
|
+
|
|
126
|
+
/** A native checkbox carries its own state, so a commit the document declines leaves
|
|
127
|
+
* the face ticked over a map that says otherwise, `checked={held(id)}` having nothing
|
|
128
|
+
* new to write. The reassert after the flush is what the styled controls get from
|
|
129
|
+
* their synced local (`synced.svelte.ts`): the document is what the face reads. */
|
|
130
|
+
async function tick(id: string, on: boolean, el: HTMLInputElement): Promise<void> {
|
|
131
|
+
onCommit(commitMember(value, id, memberWrite(on, matrixColumns(memberValue(value, id)))));
|
|
132
|
+
await flush();
|
|
133
|
+
if (el.isConnected) el.checked = held(id);
|
|
134
|
+
}
|
|
135
|
+
/** A column edit lands on a held member: the subform draws under a tick alone. */
|
|
136
|
+
function commitColumns(id: string, columns: Record<string, unknown>): void {
|
|
137
|
+
onCommit(commitMember(value, id, memberWrite(true, columns)));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Take the caret: the first member's tick. Reached by a label click and by the
|
|
141
|
+
* editor's landing verbs, which ask one function so they cannot disagree. */
|
|
142
|
+
export function focus(): void {
|
|
143
|
+
const first = members[0];
|
|
144
|
+
if (first) ref(tickEls, first.id)?.focus();
|
|
145
|
+
}
|
|
146
|
+
/** The box an arrival wash blooms in: the roster, not the label row above it, this
|
|
147
|
+
* component owning the field's label as an array does. */
|
|
148
|
+
export function washBox(): HTMLElement | undefined {
|
|
149
|
+
return rosterEl;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Land at `steps`: a member, or a column under it ({@link FieldControl.focusPath}).
|
|
153
|
+
* A member address and its `held` cell focus the tick; a column address focuses
|
|
154
|
+
* that column where the member is held and its columns are drawn, and the tick
|
|
155
|
+
* where they are not — the address names the column, and what is drawn for it is
|
|
156
|
+
* the tick that would open it. One rule keyed on the address, never on the
|
|
157
|
+
* document, so one address lands in one place. A member off the roster falls back
|
|
158
|
+
* to {@link focus}. The innermost row the walk opened comes back, else the member's
|
|
159
|
+
* box: the member is the row the address named.
|
|
160
|
+
*/
|
|
161
|
+
export function focusPath(steps: readonly PathStep[], pos?: number): LandingBox {
|
|
162
|
+
const [id, ...rest] = steps;
|
|
163
|
+
if (typeof id !== 'string' || !matrixDeclares(schema, id)) {
|
|
164
|
+
focus();
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
const column = rest[0];
|
|
168
|
+
const cells = ref(colEls, id);
|
|
169
|
+
const member = ref(memberEls, id);
|
|
170
|
+
if (column !== undefined && column !== MATRIX_HELD && cells) {
|
|
171
|
+
const inner = cells.focusPath(rest, pos);
|
|
172
|
+
return inner instanceof Promise ? inner.then((box) => box ?? member) : (inner ?? member);
|
|
173
|
+
}
|
|
174
|
+
ref(tickEls, id)?.focus();
|
|
175
|
+
return member;
|
|
176
|
+
}
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
<div
|
|
180
|
+
class="qm-matrix"
|
|
181
|
+
role="group"
|
|
182
|
+
aria-labelledby={label != null ? labelId : undefined}
|
|
183
|
+
aria-describedby={description ? descriptionId : undefined}
|
|
184
|
+
>
|
|
185
|
+
<div class="qm-matrix-header">
|
|
186
|
+
{#if label != null}
|
|
187
|
+
<FieldLabel {label} id={labelId} {descriptionId} onActivate={focus} {description} />
|
|
188
|
+
{:else}
|
|
189
|
+
<span></span>
|
|
190
|
+
{/if}
|
|
191
|
+
<span class="qm-matrix-count">{t.strings.matrixHeld(heldCount, members.length)}</span>
|
|
192
|
+
</div>
|
|
193
|
+
<div class="qm-matrix-roster" class:qm-tracks={compact} class:compact bind:this={rosterEl}>
|
|
194
|
+
{#each members as m (m.id)}
|
|
195
|
+
{@const on = held(m.id)}
|
|
196
|
+
{@const deep = routed.below.get(m.id)}
|
|
197
|
+
<div class="qm-member" class:held={on} bind:this={memberEls[m.id]}>
|
|
198
|
+
<div class="qm-member-head">
|
|
199
|
+
<!-- A real checkbox with its face drawn here: the UA's face is shadow DOM no
|
|
200
|
+
dial reaches, so the input is `appearance: none` and the box and the
|
|
201
|
+
mark beside it read the rungs. The mark is the surface's one check
|
|
202
|
+
glyph, shown by the input's own state. -->
|
|
203
|
+
<span class="qm-tick">
|
|
204
|
+
<input
|
|
205
|
+
type="checkbox"
|
|
206
|
+
class="qm-tick-input qm-focus-ring"
|
|
207
|
+
id={tickId(m.id)}
|
|
208
|
+
checked={on}
|
|
209
|
+
bind:this={tickEls[m.id]}
|
|
210
|
+
onchange={(e) => tick(m.id, e.currentTarget.checked, e.currentTarget)}
|
|
211
|
+
/>
|
|
212
|
+
<Icon name="check" class="qm-tick-mark" />
|
|
213
|
+
</span>
|
|
214
|
+
<label class="qm-member-title" id={titleId(m.id)} for={tickId(m.id)}>{m.title}</label>
|
|
215
|
+
</div>
|
|
216
|
+
{#if on && hasColumns}
|
|
217
|
+
<!-- The columns, one rung in, the way a variant's cells unfold under the
|
|
218
|
+
discriminant. Keyed by member id above, so a tick elsewhere leaves this
|
|
219
|
+
subform mounted and the caret in it. -->
|
|
220
|
+
<ObjectField
|
|
221
|
+
bind:this={colEls[m.id]}
|
|
222
|
+
value={matrixColumns(memberValue(value, m.id))}
|
|
223
|
+
properties={schema.properties}
|
|
224
|
+
label={`${label ?? ''} ${m.title}`.trim()}
|
|
225
|
+
idBase={memberBase(m.id)}
|
|
226
|
+
labelledBy={titleId(m.id)}
|
|
227
|
+
contentAt={(path) => contentAt([m.id, ...path])}
|
|
228
|
+
onCommit={(columns) => commitColumns(m.id, columns)}
|
|
229
|
+
diagnostics={deep}
|
|
230
|
+
/>
|
|
231
|
+
{:else}
|
|
232
|
+
<DiagnosticList diagnostics={deep?.map((d) => d.diagnostic)} />
|
|
233
|
+
{/if}
|
|
234
|
+
</div>
|
|
235
|
+
{/each}
|
|
236
|
+
</div>
|
|
237
|
+
<DiagnosticList diagnostics={foot} />
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<style>
|
|
241
|
+
.qm-matrix {
|
|
242
|
+
display: flex;
|
|
243
|
+
flex-direction: column;
|
|
244
|
+
gap: var(--_qm-space);
|
|
245
|
+
}
|
|
246
|
+
/* The label row, the array's own shape: this component owns the label track
|
|
247
|
+
(`Field.svelte`), and the count stands where the add chip would. */
|
|
248
|
+
.qm-matrix-header {
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
justify-content: space-between;
|
|
252
|
+
gap: var(--_qm-space-2);
|
|
253
|
+
}
|
|
254
|
+
/* The count reads at the label rung: a fact about the field, never a value. */
|
|
255
|
+
.qm-matrix-count {
|
|
256
|
+
font-size: var(--_qm-text-label);
|
|
257
|
+
color: var(--_qm-ink-label);
|
|
258
|
+
font-variant-numeric: tabular-nums;
|
|
259
|
+
}
|
|
260
|
+
/* The roster, one member to a row in declaration order. Positioned for the wash's
|
|
261
|
+
inset child (`washBox`), and rounded to the rung a row's box draws, as an array's
|
|
262
|
+
rows are. */
|
|
263
|
+
.qm-matrix-roster {
|
|
264
|
+
position: relative;
|
|
265
|
+
display: flex;
|
|
266
|
+
flex-direction: column;
|
|
267
|
+
gap: var(--_qm-space);
|
|
268
|
+
min-width: 0;
|
|
269
|
+
border-radius: var(--_qm-radius-inner);
|
|
270
|
+
}
|
|
271
|
+
/* Compact, the members stand abreast in reading order at the count the ladder gives
|
|
272
|
+
the width (`.qm-tracks`, controls.css), a held member's columns unfolding inside its
|
|
273
|
+
own track. */
|
|
274
|
+
.qm-matrix-roster.compact {
|
|
275
|
+
display: grid;
|
|
276
|
+
grid-template-columns: repeat(var(--cols), 1fr);
|
|
277
|
+
align-items: start;
|
|
278
|
+
column-gap: var(--_qm-space-2);
|
|
279
|
+
}
|
|
280
|
+
/* A member is a row: positioned for the wash a landing on it blooms, rounded to the
|
|
281
|
+
rung the rows around it draw. */
|
|
282
|
+
.qm-member {
|
|
283
|
+
position: relative;
|
|
284
|
+
display: flex;
|
|
285
|
+
flex-direction: column;
|
|
286
|
+
gap: var(--_qm-space);
|
|
287
|
+
border-radius: var(--_qm-radius-inner);
|
|
288
|
+
}
|
|
289
|
+
/* The tick and its title on one line, the line holding the tap floor a tick alone
|
|
290
|
+
would not reach: the title is the target, the `for` carrying its press to the input. */
|
|
291
|
+
.qm-member-head {
|
|
292
|
+
display: flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
gap: var(--_qm-space-2);
|
|
295
|
+
min-height: var(--_qm-tap-min);
|
|
296
|
+
}
|
|
297
|
+
.qm-tick {
|
|
298
|
+
position: relative;
|
|
299
|
+
display: inline-flex;
|
|
300
|
+
flex-shrink: 0;
|
|
301
|
+
width: var(--_qm-glyph-control);
|
|
302
|
+
height: var(--_qm-glyph-control);
|
|
303
|
+
}
|
|
304
|
+
/* The box: a well, edged, since a mark this small has no plane under it to step off
|
|
305
|
+
by tone alone; held, it takes the accent the switch's track takes when checked. */
|
|
306
|
+
.qm-tick-input {
|
|
307
|
+
appearance: none;
|
|
308
|
+
margin: 0;
|
|
309
|
+
width: 100%;
|
|
310
|
+
height: 100%;
|
|
311
|
+
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
312
|
+
border-radius: calc(var(--_qm-radius-inner) / 2);
|
|
313
|
+
background: var(--_qm-surface-well);
|
|
314
|
+
cursor: pointer;
|
|
315
|
+
transition:
|
|
316
|
+
background-color var(--_qm-duration-fast) var(--_qm-ease-reverse),
|
|
317
|
+
border-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
318
|
+
}
|
|
319
|
+
.qm-tick-input:checked {
|
|
320
|
+
background: var(--_qm-accent);
|
|
321
|
+
border-color: var(--_qm-accent);
|
|
322
|
+
}
|
|
323
|
+
/* The mark rides over the input and takes no press of its own; it is drawn by the
|
|
324
|
+
input's state, in the ink the surface reads over the accent. */
|
|
325
|
+
.qm-tick :global(.qm-tick-mark) {
|
|
326
|
+
position: absolute;
|
|
327
|
+
inset: 0;
|
|
328
|
+
width: 100%;
|
|
329
|
+
height: 100%;
|
|
330
|
+
pointer-events: none;
|
|
331
|
+
color: var(--_qm-surface);
|
|
332
|
+
opacity: 0;
|
|
333
|
+
transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
334
|
+
}
|
|
335
|
+
.qm-tick:has(:checked) :global(.qm-tick-mark) {
|
|
336
|
+
opacity: 1;
|
|
337
|
+
}
|
|
338
|
+
/* Unheld reads at the label ink, held at the value ink: the title is what the
|
|
339
|
+
document says once it is ticked, and until then it is the vocabulary. */
|
|
340
|
+
.qm-member-title {
|
|
341
|
+
font-size: var(--_qm-text-body);
|
|
342
|
+
line-height: var(--_qm-leading-tight);
|
|
343
|
+
color: var(--_qm-ink-label);
|
|
344
|
+
cursor: pointer;
|
|
345
|
+
min-width: 0;
|
|
346
|
+
transition: color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
347
|
+
}
|
|
348
|
+
.qm-member.held .qm-member-title,
|
|
349
|
+
.qm-member-title:hover {
|
|
350
|
+
color: var(--_qm-ink);
|
|
351
|
+
}
|
|
352
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Content, PathStep, QuillFieldSchema } from '@quillmark/wasm';
|
|
2
|
+
import { type DeepDiagnostic } from './diagnostics.js';
|
|
3
|
+
import type { LandingBox } from './leaves.js';
|
|
4
|
+
import './controls.css';
|
|
5
|
+
interface Props {
|
|
6
|
+
/** The stored sparse map, or undefined while the field is unset. */
|
|
7
|
+
value: Record<string, unknown> | undefined;
|
|
8
|
+
/** The field's own schema: `members` is the roster, `properties` the columns. */
|
|
9
|
+
schema: QuillFieldSchema;
|
|
10
|
+
/** Field label, the naming prefix a member's columns compose with. */
|
|
11
|
+
label?: string;
|
|
12
|
+
/** Schema `description`: the label's help affordance. */
|
|
13
|
+
description?: string;
|
|
14
|
+
/** The label's own DOM id: the group's name, each tick keeping its own `<label>`. */
|
|
15
|
+
labelId?: string;
|
|
16
|
+
/** Where the description parks, for the group's `aria-describedby`. */
|
|
17
|
+
descriptionId?: string;
|
|
18
|
+
/** The field's control id: the base a member's tick and columns derive their own
|
|
19
|
+
* names from, one `-m-<member id>` segment down. */
|
|
20
|
+
idBase?: string;
|
|
21
|
+
/** The boundary's nested content read, rooted at this field: a member's content
|
|
22
|
+
* column reads at `[id, column]` (`reader.getContentAt`). */
|
|
23
|
+
contentAt: (path: PathStep[]) => Content | undefined;
|
|
24
|
+
onCommit: (v: Record<string, unknown> | undefined) => void;
|
|
25
|
+
/** The diagnostics routed into this field, each with its steps still to walk: one
|
|
26
|
+
* naming a member draws under that member, one naming nothing on the roster at the
|
|
27
|
+
* field's foot. */
|
|
28
|
+
diagnostics?: readonly DeepDiagnostic[];
|
|
29
|
+
}
|
|
30
|
+
declare const MatrixField: import("svelte").Component<Props, {
|
|
31
|
+
focus: () => void;
|
|
32
|
+
washBox: () => HTMLElement | undefined;
|
|
33
|
+
focusPath: (steps: readonly PathStep[], pos?: number) => LandingBox;
|
|
34
|
+
}, "">;
|
|
35
|
+
type MatrixField = ReturnType<typeof MatrixField>;
|
|
36
|
+
export default MatrixField;
|