@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
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import type { Content, PathStep, QuillFieldSchema } from '@quillmark/wasm';
|
|
2
|
+
import { type ArrayLayout } from './structure.js';
|
|
3
|
+
import { type DeepDiagnostic } from './diagnostics.js';
|
|
4
|
+
import type { LandingBox } from './leaves.js';
|
|
2
5
|
import './controls.css';
|
|
3
6
|
interface Props {
|
|
4
7
|
value: unknown[] | undefined;
|
|
5
8
|
items: QuillFieldSchema | undefined;
|
|
9
|
+
/** How the elements draw: the record list, or the table an `array<object>` of
|
|
10
|
+
* short cells asks for (`arrayLayout`). */
|
|
11
|
+
layout?: ArrayLayout;
|
|
12
|
+
/** The schema's `max:`, past which the add chip disables and Enter stops
|
|
13
|
+
* inserting; `undefined` is no cap. */
|
|
14
|
+
max?: number;
|
|
6
15
|
/** Accessible-name prefix for the element controls (`label` + 1-based index). */
|
|
7
16
|
label?: string;
|
|
8
17
|
/** No-default field → a persistent required `*` on the label. */
|
|
@@ -28,11 +37,15 @@ interface Props {
|
|
|
28
37
|
* control has spliced in and whose commit has yet to land, or was refused. */
|
|
29
38
|
contentAt: (path: PathStep[]) => Content | undefined;
|
|
30
39
|
onCommit: (arr: unknown[]) => void;
|
|
40
|
+
/** The diagnostics routed into this array, each with its steps still to walk: one
|
|
41
|
+
* naming a row draws under that row — under its head while it is collapsed, under
|
|
42
|
+
* the cell once it is open — and one naming no row draws at the list's foot. */
|
|
43
|
+
diagnostics?: readonly DeepDiagnostic[];
|
|
31
44
|
}
|
|
32
45
|
declare const ArrayField: import("svelte").Component<Props, {
|
|
33
46
|
focus: () => void;
|
|
34
47
|
washBox: () => HTMLElement | undefined;
|
|
35
|
-
|
|
48
|
+
focusPath: (steps: readonly PathStep[], pos?: number) => LandingBox;
|
|
36
49
|
}, "">;
|
|
37
50
|
type ArrayField = ReturnType<typeof ArrayField>;
|
|
38
51
|
export default ArrayField;
|
|
@@ -36,9 +36,12 @@
|
|
|
36
36
|
const local = syncedLocal(() => value ?? fallback ?? false);
|
|
37
37
|
</script>
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
<!-- Not `.qm-switch`: that name is the preset's pane band, whose narrow-viewport rule
|
|
40
|
+
stands every child of it at `--qmh-tap`, so a track wearing it grows a thumb three times
|
|
41
|
+
its own height over the labels around it (`preset/recipes.css`, held by `check:style`). -->
|
|
42
|
+
<span class="qm-toggle-wrap">
|
|
40
43
|
<Switch.Root
|
|
41
|
-
class="qm-
|
|
44
|
+
class="qm-toggle qm-focus-ring qm-tap-floor"
|
|
42
45
|
checked={local.value}
|
|
43
46
|
{id}
|
|
44
47
|
aria-label={id ? undefined : label}
|
|
@@ -48,7 +51,7 @@
|
|
|
48
51
|
onCommit(v);
|
|
49
52
|
}}
|
|
50
53
|
>
|
|
51
|
-
<Switch.Thumb class="qm-
|
|
54
|
+
<Switch.Thumb class="qm-toggle-thumb" />
|
|
52
55
|
</Switch.Root>
|
|
53
56
|
</span>
|
|
54
57
|
|
|
@@ -60,7 +63,7 @@
|
|
|
60
63
|
2.5.8's floor is half again that. The `<label for>` beside it forwards a press but
|
|
61
64
|
stands at the label rung, so it is no taller: without the floor no region on the
|
|
62
65
|
surface reaches this control at the size every other control on it holds. */
|
|
63
|
-
.qm-
|
|
66
|
+
.qm-toggle-wrap :global(.qm-toggle) {
|
|
64
67
|
display: inline-flex;
|
|
65
68
|
align-items: center;
|
|
66
69
|
width: 1.75rem;
|
|
@@ -75,21 +78,21 @@
|
|
|
75
78
|
cursor: pointer;
|
|
76
79
|
transition: background var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
77
80
|
}
|
|
78
|
-
.qm-
|
|
81
|
+
.qm-toggle-wrap :global(.qm-toggle[data-state='checked']) {
|
|
79
82
|
background: var(--_qm-accent);
|
|
80
83
|
}
|
|
81
84
|
/* The focus ring rides `.qm-focus-ring` on the switch (controls.css). */
|
|
82
85
|
/* The thumb reads on tone alone: the base surface over the track's `hover` rung
|
|
83
86
|
unchecked, and over `--_qm-accent` checked. No edge of its own: a hairline around
|
|
84
87
|
a 12px pill is a second box inside the track's. */
|
|
85
|
-
.qm-
|
|
88
|
+
.qm-toggle-wrap :global(.qm-toggle-thumb) {
|
|
86
89
|
width: 0.75rem;
|
|
87
90
|
height: 0.75rem;
|
|
88
91
|
border-radius: var(--_qm-radius-pill);
|
|
89
92
|
background: var(--_qm-surface);
|
|
90
93
|
transition: translate var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
91
94
|
}
|
|
92
|
-
.qm-
|
|
95
|
+
.qm-toggle-wrap :global(.qm-toggle-thumb[data-state='checked']) {
|
|
93
96
|
translate: 0.75rem;
|
|
94
97
|
}
|
|
95
98
|
</style>
|
package/dist/visual/Card.svelte
CHANGED
|
@@ -332,7 +332,7 @@
|
|
|
332
332
|
</section>
|
|
333
333
|
|
|
334
334
|
{#snippet sectionFields(fields: FieldModel[])}
|
|
335
|
-
<div class="qm-fields">
|
|
335
|
+
<div class="qm-fields qm-tracks">
|
|
336
336
|
{#each placeFields(fields) as { field: f, span } (f.name)}
|
|
337
337
|
<Field
|
|
338
338
|
field={f}
|
|
@@ -584,33 +584,14 @@
|
|
|
584
584
|
.qm-group-panel-inner {
|
|
585
585
|
container-type: inline-size;
|
|
586
586
|
}
|
|
587
|
-
/*
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
It steps 1 → 2 → 4, skipping 3: each rung is the width at which a track still clears
|
|
591
|
-
the comfortable field minimum, and an even capacity is what lets the half a subform
|
|
592
|
-
of one takes (`--cols-half`, `ObjectField`) land on a track boundary. A field ends on
|
|
593
|
-
its track's own edge, so a rung is `n * 220px + gaps`. */
|
|
587
|
+
/* The count is the ladder's (`.qm-tracks`, controls.css), stepped by the container
|
|
588
|
+
above; what this block owns is the grid the count fills. */
|
|
594
589
|
.qm-fields {
|
|
595
|
-
--cols: 1;
|
|
596
|
-
--cols-half: 1;
|
|
597
590
|
display: grid;
|
|
598
591
|
grid-template-columns: repeat(var(--cols), 1fr);
|
|
599
592
|
column-gap: var(--_qm-space-2);
|
|
600
593
|
row-gap: var(--_qm-space-3);
|
|
601
594
|
}
|
|
602
|
-
@container (min-width: 28rem) {
|
|
603
|
-
.qm-fields {
|
|
604
|
-
--cols: 2;
|
|
605
|
-
--cols-half: 1;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
@container (min-width: 57rem) {
|
|
609
|
-
.qm-fields {
|
|
610
|
-
--cols: 4;
|
|
611
|
-
--cols-half: 2;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
595
|
/* The nesting vertical, one `--_qm-nest` in from the card's edge with the panel's
|
|
615
596
|
fields the same rung off it (ARCHITECTURE §"A plane is a tone"). `--_qm-border` and
|
|
616
597
|
not `--_qm-accent`: a card's edge and the verticals inside it are one stroke at three
|
package/dist/visual/Field.svelte
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
Type dispatch (VISUAL_EDITOR §"Structure mirrors the schema"). Given one projected
|
|
3
3
|
{@link FieldModel} and its live value, render the label + the control the type
|
|
4
|
-
maps to. Array controls own their label (paired with the add affordance in
|
|
5
|
-
{@link ArrayField}
|
|
4
|
+
maps to. Array and matrix controls own their label (paired with the add affordance in
|
|
5
|
+
{@link ArrayField}, with the held count in {@link MatrixField}); other types render
|
|
6
|
+
the label here. A container control also takes the walk a nested address makes into
|
|
7
|
+
it ({@link FieldControl.focusPath}) and the diagnostics anchored inside it. Prose leaves take a
|
|
6
8
|
parent-built live `addr` (its `card` a getter over the stable-id→index map) so
|
|
7
9
|
a reorder re-targets without a remount; scalars, arrays, and objects commit
|
|
8
10
|
their value up through `onCommitScalar`, which the parent lowers to the typed
|
|
@@ -24,9 +26,16 @@
|
|
|
24
26
|
ResolvedField
|
|
25
27
|
} from '@quillmark/wasm';
|
|
26
28
|
import type { EditorErrorHandler } from '../core/errors.js';
|
|
27
|
-
import type { LeafRegistry } from './leaves.js';
|
|
29
|
+
import type { LandingBox, LeafRegistry } from './leaves.js';
|
|
28
30
|
import type { FieldModel, FieldSpan } from './structure.js';
|
|
29
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
VARIANT_DISCRIMINANT,
|
|
33
|
+
arrayLayout,
|
|
34
|
+
ghostDefault,
|
|
35
|
+
isContainer,
|
|
36
|
+
stringifyGhost
|
|
37
|
+
} from './structure.js';
|
|
38
|
+
import { deepen } from './diagnostics.js';
|
|
30
39
|
import type { FieldDomIds } from './domid.js';
|
|
31
40
|
import ProseField from './ProseField.svelte';
|
|
32
41
|
import TextField from './TextField.svelte';
|
|
@@ -37,6 +46,7 @@
|
|
|
37
46
|
import ArrayField from './ArrayField.svelte';
|
|
38
47
|
import ObjectField from './ObjectField.svelte';
|
|
39
48
|
import VariantField from './VariantField.svelte';
|
|
49
|
+
import MatrixField from './MatrixField.svelte';
|
|
40
50
|
import DiagnosticList from './DiagnosticList.svelte';
|
|
41
51
|
import FieldLabel from './FieldLabel.svelte';
|
|
42
52
|
|
|
@@ -123,6 +133,13 @@
|
|
|
123
133
|
// reference must vanish with it: `aria-describedby` pointing at nothing describes
|
|
124
134
|
// nothing, and silently.
|
|
125
135
|
const describedBy = $derived(field.description ? domIds.description : undefined);
|
|
136
|
+
// Two controls own their label row, the add chip and the held count sharing it.
|
|
137
|
+
const ownsLabel = $derived(field.control === 'array' || field.control === 'matrix');
|
|
138
|
+
|
|
139
|
+
// A container walks its diagnostics down to the cell each names and draws the rest
|
|
140
|
+
// at its own foot (`diagnostics.ts`); every other control draws them here, under
|
|
141
|
+
// itself, there being nothing inside it for a path to name.
|
|
142
|
+
const deep = $derived(deepen(diagnostics));
|
|
126
143
|
|
|
127
144
|
/** The boundary's nested content read with this field's address already bound: an
|
|
128
145
|
* array's elements, an object's properties, a variant's cells, and a subform's cells
|
|
@@ -145,28 +162,29 @@
|
|
|
145
162
|
// restores a selection, a date field lands on its first segment, an array lands on
|
|
146
163
|
// its first element or on the add affordance that is all an empty one has, an
|
|
147
164
|
// object on its first property.
|
|
165
|
+
/** What a container control exports: its focus, and the walk into it. */
|
|
166
|
+
type Inside = {
|
|
167
|
+
focus: () => void;
|
|
168
|
+
focusPath: (steps: readonly PathStep[], pos?: number) => LandingBox;
|
|
169
|
+
washBox?: () => HTMLElement | undefined;
|
|
170
|
+
};
|
|
148
171
|
let proseEl = $state<{ focus: () => void } | undefined>();
|
|
149
172
|
let dateEl = $state<{ focus: () => void } | undefined>();
|
|
150
|
-
let arrayEl = $state<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
| undefined
|
|
157
|
-
>();
|
|
158
|
-
let objectEl = $state<{ focus: () => void } | undefined>();
|
|
173
|
+
let arrayEl = $state<Inside | undefined>();
|
|
174
|
+
let objectEl = $state<Inside | undefined>();
|
|
175
|
+
let variantEl = $state<Inside | undefined>();
|
|
176
|
+
let matrixEl = $state<Inside | undefined>();
|
|
177
|
+
const inside = (): Inside | undefined => arrayEl ?? objectEl ?? variantEl ?? matrixEl;
|
|
159
178
|
function focusControl(): void {
|
|
160
|
-
const owner = proseEl ?? dateEl ??
|
|
179
|
+
const owner = proseEl ?? dateEl ?? inside();
|
|
161
180
|
if (owner) return owner.focus();
|
|
162
181
|
document.getElementById(domIds.control)?.focus();
|
|
163
182
|
}
|
|
164
|
-
/** The
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return arrayEl?.focusElement(k, pos);
|
|
183
|
+
/** The landing at depth, for the addresses the boundary mints under a container
|
|
184
|
+
* (`leaves.ts`); read at the call, so it tracks the mounted control. The innermost
|
|
185
|
+
* row it settled in comes back with it, the wash being the landing's own granularity. */
|
|
186
|
+
function focusPath(steps: readonly PathStep[], pos?: number): LandingBox {
|
|
187
|
+
return inside()?.focusPath(steps, pos);
|
|
170
188
|
}
|
|
171
189
|
// Only where `for` cannot reach; the labelable four are the browser's own, and a
|
|
172
190
|
// second handler over them would be a focus the label already placed.
|
|
@@ -176,17 +194,17 @@
|
|
|
176
194
|
* This field's landing handle. The wrapper is the bloom host rather than the
|
|
177
195
|
* control: `bloomInside` appends an inset child and an `<input>` holds none. The
|
|
178
196
|
* label is outside it, and stays out of the wash: an arrival marks where the caret
|
|
179
|
-
* landed, which is the control.
|
|
180
|
-
*
|
|
181
|
-
* `
|
|
197
|
+
* landed, which is the control. A control that owns its label (`ownsLabel`: the array
|
|
198
|
+
* and the matrix) holds that row inside itself, so it names its own box
|
|
199
|
+
* (`washBox`) and the bloom reads that in the wrapper's place.
|
|
182
200
|
*
|
|
183
201
|
* A prose leaf is absent here — it registers its own controller from inside
|
|
184
202
|
* `ProseField`, carrying the codec seam this handle has no half of — and reactive
|
|
185
203
|
* rather than mount-once, because a retype can swap the control under a leaf key
|
|
186
204
|
* that does not remount.
|
|
187
205
|
*
|
|
188
|
-
*
|
|
189
|
-
* an address to name. `el` is the field-wide box, which
|
|
206
|
+
* A container carries the walk into it as well; no other control has an inside for
|
|
207
|
+
* an address to name. `el` is the field-wide box, which a landing at depth does not
|
|
190
208
|
* wash: that lane hands back its own row (`leaves.ts`).
|
|
191
209
|
*/
|
|
192
210
|
let controlEl = $state<HTMLElement | undefined>();
|
|
@@ -197,9 +215,9 @@
|
|
|
197
215
|
const wrapper = controlEl;
|
|
198
216
|
registry.registerControl(key, {
|
|
199
217
|
focus: focusControl,
|
|
200
|
-
|
|
218
|
+
focusPath: isContainer(field.control) ? focusPath : undefined,
|
|
201
219
|
get el() {
|
|
202
|
-
return
|
|
220
|
+
return inside()?.washBox?.() ?? wrapper;
|
|
203
221
|
}
|
|
204
222
|
});
|
|
205
223
|
return () => registry.unregisterControl(key);
|
|
@@ -217,7 +235,7 @@
|
|
|
217
235
|
</script>
|
|
218
236
|
|
|
219
237
|
<div class="qm-field" class:cell={span === 'cell'}>
|
|
220
|
-
{#if
|
|
238
|
+
{#if !ownsLabel}
|
|
221
239
|
<FieldLabel
|
|
222
240
|
label={field.label}
|
|
223
241
|
controlId={labelable ? domIds.control : undefined}
|
|
@@ -265,6 +283,7 @@
|
|
|
265
283
|
/>
|
|
266
284
|
{:else if field.control === 'variant'}
|
|
267
285
|
<VariantField
|
|
286
|
+
bind:this={variantEl}
|
|
268
287
|
value={value as Record<string, unknown> | undefined}
|
|
269
288
|
schema={field.schema}
|
|
270
289
|
{ghostMember}
|
|
@@ -276,6 +295,7 @@
|
|
|
276
295
|
onCommit={onCommitScalar}
|
|
277
296
|
{optionAllowed}
|
|
278
297
|
{enumDisallowed}
|
|
298
|
+
diagnostics={deep}
|
|
279
299
|
/>
|
|
280
300
|
{:else if field.control === 'number'}
|
|
281
301
|
<NumberField
|
|
@@ -308,6 +328,8 @@
|
|
|
308
328
|
bind:this={arrayEl}
|
|
309
329
|
value={value as unknown[] | undefined}
|
|
310
330
|
items={field.schema.items}
|
|
331
|
+
layout={arrayLayout(field.schema)}
|
|
332
|
+
max={field.schema.max}
|
|
311
333
|
label={field.label}
|
|
312
334
|
required={field.required}
|
|
313
335
|
description={field.description}
|
|
@@ -316,6 +338,7 @@
|
|
|
316
338
|
idBase={domIds.control}
|
|
317
339
|
{contentAt}
|
|
318
340
|
onCommit={onCommitScalar}
|
|
341
|
+
diagnostics={deep}
|
|
319
342
|
/>
|
|
320
343
|
{:else if field.control === 'object'}
|
|
321
344
|
<ObjectField
|
|
@@ -328,6 +351,21 @@
|
|
|
328
351
|
{describedBy}
|
|
329
352
|
{contentAt}
|
|
330
353
|
onCommit={onCommitScalar}
|
|
354
|
+
diagnostics={deep}
|
|
355
|
+
/>
|
|
356
|
+
{:else if field.control === 'matrix'}
|
|
357
|
+
<MatrixField
|
|
358
|
+
bind:this={matrixEl}
|
|
359
|
+
value={value as Record<string, unknown> | undefined}
|
|
360
|
+
schema={field.schema}
|
|
361
|
+
label={field.label}
|
|
362
|
+
description={field.description}
|
|
363
|
+
labelId={domIds.label}
|
|
364
|
+
descriptionId={domIds.description}
|
|
365
|
+
idBase={domIds.control}
|
|
366
|
+
{contentAt}
|
|
367
|
+
onCommit={onCommitScalar}
|
|
368
|
+
diagnostics={deep}
|
|
331
369
|
/>
|
|
332
370
|
{:else}
|
|
333
371
|
<TextField
|
|
@@ -340,7 +378,9 @@
|
|
|
340
378
|
{/if}
|
|
341
379
|
</div>
|
|
342
380
|
|
|
343
|
-
|
|
381
|
+
{#if !isContainer(field.control)}
|
|
382
|
+
<DiagnosticList {diagnostics} />
|
|
383
|
+
{/if}
|
|
344
384
|
</div>
|
|
345
385
|
</div>
|
|
346
386
|
|