@quillmark/svelte 0.20.1 → 0.22.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/NOTICE +6 -7
- package/README.md +1 -1
- package/THEMING.md +1 -1
- package/dist/core/address.d.ts +6 -1
- package/dist/core/address.js +12 -3
- package/dist/core/bloom.d.ts +9 -4
- package/dist/core/bloom.js +18 -49
- package/dist/core/codec/atoms.d.ts +1 -1
- package/dist/core/codec/atoms.js +26 -11
- package/dist/core/codec/blocks.d.ts +5 -5
- package/dist/core/codec/blocks.js +27 -8
- package/dist/core/codec/code.d.ts +4 -4
- package/dist/core/codec/code.js +52 -22
- package/dist/core/codec/decode.d.ts +5 -5
- package/dist/core/codec/decode.js +18 -12
- package/dist/core/codec/encode.js +6 -1
- package/dist/core/codec/field.d.ts +22 -15
- package/dist/core/codec/field.js +66 -26
- package/dist/core/codec/index.d.ts +1 -1
- package/dist/core/codec/index.js +2 -2
- package/dist/core/codec/inputrules.d.ts +4 -0
- package/dist/core/codec/inputrules.js +44 -6
- package/dist/core/codec/link-paste.d.ts +11 -0
- package/dist/core/codec/link-paste.js +32 -0
- package/dist/core/codec/lists.d.ts +5 -4
- package/dist/core/codec/lists.js +87 -31
- package/dist/core/codec/nesting.d.ts +20 -0
- package/dist/core/codec/nesting.js +62 -0
- package/dist/core/codec/prose.css +145 -63
- package/dist/core/codec/schema.d.ts +2 -2
- package/dist/core/codec/schema.js +71 -18
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-copy.d.ts +17 -0
- package/dist/core/codec/table-copy.js +84 -0
- package/dist/core/codec/table-view.d.ts +6 -7
- package/dist/core/codec/table-view.js +281 -201
- package/dist/core/codec/table.d.ts +2 -9
- package/dist/core/codec/table.js +10 -19
- package/dist/core/codec/tasks.d.ts +19 -0
- package/dist/core/codec/tasks.js +140 -0
- package/dist/core/errors.d.ts +2 -2
- package/dist/core/rungs.d.ts +6 -0
- package/dist/core/rungs.js +11 -0
- package/dist/core/theme.css +24 -12
- package/dist/visual/ArrayField.svelte +236 -78
- package/dist/visual/ArrayField.svelte.d.ts +0 -2
- package/dist/visual/BooleanField.svelte +12 -1
- package/dist/visual/Card.svelte +41 -23
- package/dist/visual/Card.svelte.d.ts +3 -2
- package/dist/visual/CardControls.svelte +15 -15
- package/dist/visual/DateField.svelte +93 -64
- package/dist/visual/EnumField.svelte +9 -4
- package/dist/visual/Field.svelte +20 -16
- package/dist/visual/Field.svelte.d.ts +4 -2
- package/dist/visual/FieldHint.svelte +22 -5
- package/dist/visual/FieldLabel.svelte +5 -33
- package/dist/visual/FieldLabel.svelte.d.ts +0 -2
- package/dist/visual/FormatPopover.svelte +78 -81
- package/dist/visual/MatrixField.svelte +28 -19
- package/dist/visual/NumberField.svelte +32 -3
- package/dist/visual/ObjectField.svelte +40 -24
- package/dist/visual/ObjectField.svelte.d.ts +6 -0
- package/dist/visual/ProseField.svelte +10 -13
- package/dist/visual/ProseField.svelte.d.ts +5 -2
- package/dist/visual/ProseValue.svelte +25 -19
- package/dist/visual/ProseValue.svelte.d.ts +4 -4
- package/dist/visual/TextField.svelte +3 -2
- package/dist/visual/TipsCard.svelte +7 -7
- package/dist/visual/VariantField.svelte +1 -1
- package/dist/visual/VariantField.svelte.d.ts +1 -1
- package/dist/visual/VisualEditorInner.svelte +121 -39
- package/dist/visual/controls.css +21 -2
- package/dist/visual/icons/Icon.svelte +3 -2
- package/dist/visual/icons/Icon.svelte.d.ts +3 -2
- package/dist/visual/icons/nodes.d.ts +25 -21
- package/dist/visual/icons/nodes.js +9 -7
- package/dist/visual/leaves.d.ts +12 -9
- package/dist/visual/leaves.js +19 -0
- package/dist/visual/motion.d.ts +25 -3
- package/dist/visual/motion.js +58 -9
- package/dist/visual/strings.d.ts +15 -9
- package/dist/visual/strings.js +19 -12
- package/dist/visual/structure.d.ts +21 -31
- package/dist/visual/structure.js +54 -51
- package/package.json +3 -3
|
@@ -41,18 +41,21 @@
|
|
|
41
41
|
baseType,
|
|
42
42
|
controlKind,
|
|
43
43
|
declaredContent,
|
|
44
|
+
fieldModels,
|
|
44
45
|
flatObject,
|
|
45
46
|
humanize,
|
|
46
47
|
isContainer,
|
|
48
|
+
literalText,
|
|
47
49
|
mountKey,
|
|
48
|
-
obliged,
|
|
49
50
|
optionalCell,
|
|
51
|
+
placeFields,
|
|
50
52
|
printedText,
|
|
51
53
|
renderedBoolean,
|
|
52
54
|
shortCell
|
|
53
55
|
} from './structure.js';
|
|
54
56
|
import { splitDeep, unrouted, type DeepDiagnostic } from './diagnostics.js';
|
|
55
57
|
import type { LandingBox } from './leaves.js';
|
|
58
|
+
import { isEmptyContent } from '../core/codec/index.js';
|
|
56
59
|
import { wording } from './strings.js';
|
|
57
60
|
import { propertyDomIds } from './domid.js';
|
|
58
61
|
import FieldLabel from './FieldLabel.svelte';
|
|
@@ -102,6 +105,12 @@
|
|
|
102
105
|
/** `ui.layout: flat`: no vertical and no rung, the properties at the depth of the
|
|
103
106
|
* list the subform stands in. */
|
|
104
107
|
flat?: boolean;
|
|
108
|
+
/** A card-level object, or one nested in it: its properties are placed as a
|
|
109
|
+
* card's fields are (`placeFields`), so moving a field into a group leaves its row
|
|
110
|
+
* as it was. Unset,
|
|
111
|
+
* every short cell packs: an array's record, a variant's world and a matrix
|
|
112
|
+
* member's columns are one record's cells, not a card's fields. */
|
|
113
|
+
placed?: boolean;
|
|
105
114
|
/** Raw keydown off a text or prose cell, for a row whose own keys run through its
|
|
106
115
|
* cells: the table's Enter and Backspace ({@link ArrayField}). */
|
|
107
116
|
onCellKey?: (e: KeyboardEvent, key: string) => void;
|
|
@@ -121,6 +130,7 @@
|
|
|
121
130
|
onCommit,
|
|
122
131
|
bare = false,
|
|
123
132
|
flat = false,
|
|
133
|
+
placed = false,
|
|
124
134
|
onCellKey,
|
|
125
135
|
diagnostics
|
|
126
136
|
}: Props = $props();
|
|
@@ -129,15 +139,16 @@
|
|
|
129
139
|
const entries = $derived(Object.entries(properties ?? {}));
|
|
130
140
|
const obj = $derived((value ?? {}) as Record<string, unknown>);
|
|
131
141
|
const has = (key: string): boolean => Object.hasOwn(properties ?? {}, key);
|
|
142
|
+
const spans = $derived(
|
|
143
|
+
placed
|
|
144
|
+
? new Map(
|
|
145
|
+
placeFields(fieldModels({ fields: properties ?? {} })).map((p) => [p.field.name, p.span])
|
|
146
|
+
)
|
|
147
|
+
: undefined
|
|
148
|
+
);
|
|
132
149
|
const routed = $derived(splitDeep(diagnostics));
|
|
133
150
|
const foot = $derived(unrouted(routed, (step) => typeof step === 'string' && has(step)));
|
|
134
151
|
|
|
135
|
-
/** Obligation, read off the cell exactly as `fieldModels` reads it off a field
|
|
136
|
-
* ({@link obliged}). `validate` anchors it per leaf, so a property with no
|
|
137
|
-
* `default:` is obliged in its own right and the container around it holds none
|
|
138
|
-
* (VISUAL_EDITOR §"Enum variants"). */
|
|
139
|
-
const required = obliged;
|
|
140
|
-
|
|
141
152
|
const title = (key: string, sub: QuillFieldSchema): string => sub.title ?? humanize(key);
|
|
142
153
|
/** What an unset optional cell ghosts, which is what it prints: `none`. An answered
|
|
143
154
|
* cell ghosts nothing, an empty answer printing empty. */
|
|
@@ -151,8 +162,7 @@
|
|
|
151
162
|
/** The `aria-label` fallback, for a subform mounted without a field's id space:
|
|
152
163
|
* the field's name and the property's, since nothing else names the control. */
|
|
153
164
|
const fallbackName = (key: string, sub: QuillFieldSchema): string =>
|
|
154
|
-
`${label != null ? `${label} ` : ''}${title(key, sub)}
|
|
155
|
-
(required(sub) ? ` ${t.strings.fieldRequired}` : '');
|
|
165
|
+
`${label != null ? `${label} ` : ''}${title(key, sub)}`;
|
|
156
166
|
|
|
157
167
|
/** The handles of the cells the DOM cannot answer for, keyed by property name —
|
|
158
168
|
* which is stable, a subform drawing its properties by declaration and never by
|
|
@@ -285,10 +295,17 @@
|
|
|
285
295
|
kind === 'boolean'}
|
|
286
296
|
<!-- A container takes the full span at every depth: its rows are the document's
|
|
287
297
|
to count, and a track beside it would stand in a column of whitespace
|
|
288
|
-
(`packable`). A block prose cell spans it too, and keeps its label row
|
|
298
|
+
(`packable`). A block prose cell spans it too, and keeps its label row, as
|
|
299
|
+
does a placed cell `placeFields` gives the whole row. -->
|
|
289
300
|
{@const block = kind === 'prose' && !shortCell(sub)}
|
|
290
301
|
<div
|
|
291
|
-
class={[
|
|
302
|
+
class={[
|
|
303
|
+
'qm-object-prop',
|
|
304
|
+
{
|
|
305
|
+
'qm-prop-full': isContainer(kind),
|
|
306
|
+
'qm-prop-wide': block || (!isContainer(kind) && spans?.get(key) === 'full')
|
|
307
|
+
}
|
|
308
|
+
]}
|
|
292
309
|
data-qm-prop={key}
|
|
293
310
|
>
|
|
294
311
|
{#if ids && kind !== 'array' && kind !== 'matrix'}
|
|
@@ -298,7 +315,6 @@
|
|
|
298
315
|
id={ids.label}
|
|
299
316
|
descriptionId={describes}
|
|
300
317
|
onActivate={labelable ? undefined : () => focusProp(key)}
|
|
301
|
-
required={required(sub)}
|
|
302
318
|
description={sub.description}
|
|
303
319
|
/>
|
|
304
320
|
{/if}
|
|
@@ -364,13 +380,14 @@
|
|
|
364
380
|
content={() => contentAt([key])}
|
|
365
381
|
value={obj[key]}
|
|
366
382
|
fallback={contentDefault(sub)}
|
|
367
|
-
plaintext={
|
|
383
|
+
plaintext={literalText(sub)}
|
|
368
384
|
placeholder={noneOf(key, sub)}
|
|
369
385
|
{block}
|
|
370
386
|
label={named}
|
|
371
387
|
labelledBy={ids?.label}
|
|
372
388
|
describedBy={describes}
|
|
373
|
-
onChange={(rt) =>
|
|
389
|
+
onChange={(rt) =>
|
|
390
|
+
commitProp(key, optionalCell(sub) && isEmptyContent(rt) ? undefined : rt)}
|
|
374
391
|
onKey={onCellKey ? (e) => onCellKey(e, key) : undefined}
|
|
375
392
|
/>
|
|
376
393
|
{:else if kind === 'array'}
|
|
@@ -386,7 +403,6 @@
|
|
|
386
403
|
layout={arrayLayout(sub)}
|
|
387
404
|
max={sub.max}
|
|
388
405
|
label={title(key, sub)}
|
|
389
|
-
required={required(sub)}
|
|
390
406
|
description={sub.description}
|
|
391
407
|
labelId={ids?.label}
|
|
392
408
|
descriptionId={describes}
|
|
@@ -401,6 +417,7 @@
|
|
|
401
417
|
value={obj[key] as Record<string, unknown> | undefined}
|
|
402
418
|
properties={sub.properties}
|
|
403
419
|
flat={flatObject(sub)}
|
|
420
|
+
{placed}
|
|
404
421
|
label={ids ? undefined : fallbackName(key, sub)}
|
|
405
422
|
idBase={ids?.control}
|
|
406
423
|
labelledBy={ids?.label}
|
|
@@ -457,10 +474,8 @@
|
|
|
457
474
|
/* The vertical and the rung between it and the properties, `--_qm-nest` being the step
|
|
458
475
|
the card and the section hold too.
|
|
459
476
|
|
|
460
|
-
`--_qm-border
|
|
461
|
-
|
|
462
|
-
vertical read. `faint` separates without structuring — a table's interior lines
|
|
463
|
-
under a frame that is doing the structuring — which is the other job.
|
|
477
|
+
`--_qm-border`: this is the stroke that structures (ARCHITECTURE §"A plane is a
|
|
478
|
+
tone"), the one the card's edge and the section's vertical read.
|
|
464
479
|
|
|
465
480
|
`padding-block` is the stroke's end caps, the same rung and the same reason as a
|
|
466
481
|
section's (`Card.svelte`). A cap and not a leading gap: what stands the subform off
|
|
@@ -516,15 +531,16 @@
|
|
|
516
531
|
align-items: start;
|
|
517
532
|
min-width: 0;
|
|
518
533
|
}
|
|
519
|
-
/* A
|
|
520
|
-
truncated, and there are no sibling properties for it to line up with. A
|
|
521
|
-
|
|
522
|
-
|
|
534
|
+
/* A record of one cell takes half the capacity: at full capacity a single track reads
|
|
535
|
+
as truncated, and there are no sibling properties for it to line up with. A placed
|
|
536
|
+
property's span is `placeFields`', which strands a lone one to the whole row as it
|
|
537
|
+
does a card's field. */
|
|
523
538
|
.qm-object-prop:only-child {
|
|
524
539
|
grid-column: span var(--cols-half);
|
|
525
540
|
}
|
|
526
541
|
/* A block prose property holds paragraphs, so a track beside it stands in whitespace
|
|
527
|
-
once it grows (`packable`); its label still rides the row above it.
|
|
542
|
+
once it grows (`packable`); its label still rides the row above it. A placed
|
|
543
|
+
property `placeFields` gives the whole row spans it the same way. */
|
|
528
544
|
.qm-object-prop.qm-prop-wide,
|
|
529
545
|
.qm-object-prop.qm-prop-wide:only-child {
|
|
530
546
|
grid-column: 1 / -1;
|
|
@@ -35,6 +35,12 @@ interface Props {
|
|
|
35
35
|
/** `ui.layout: flat`: no vertical and no rung, the properties at the depth of the
|
|
36
36
|
* list the subform stands in. */
|
|
37
37
|
flat?: boolean;
|
|
38
|
+
/** A card-level object, or one nested in it: its properties are placed as a
|
|
39
|
+
* card's fields are (`placeFields`), so moving a field into a group leaves its row
|
|
40
|
+
* as it was. Unset,
|
|
41
|
+
* every short cell packs: an array's record, a variant's world and a matrix
|
|
42
|
+
* member's columns are one record's cells, not a card's fields. */
|
|
43
|
+
placed?: boolean;
|
|
38
44
|
/** Raw keydown off a text or prose cell, for a row whose own keys run through its
|
|
39
45
|
* cells: the table's Enter and Backspace ({@link ArrayField}). */
|
|
40
46
|
onCellKey?: (e: KeyboardEvent, key: string) => void;
|
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
plaintext?: boolean;
|
|
25
25
|
/**
|
|
26
26
|
* Draw no box: the card body's leaf, and nothing else. Not `block`, which is a
|
|
27
|
-
* different predicate: a `richtext` field
|
|
28
|
-
* still a control in a row of controls, so only the caller knows what
|
|
27
|
+
* different predicate: a `richtext` field declaring `multiline: true` is block
|
|
28
|
+
* prose and still a control in a row of controls, so only the caller knows what
|
|
29
|
+
* is paper.
|
|
29
30
|
*/
|
|
30
31
|
unframed?: boolean;
|
|
31
32
|
/** Accessible name for a leaf nothing else names: an array element, or the
|
|
@@ -45,6 +46,8 @@
|
|
|
45
46
|
placeholder?: string;
|
|
46
47
|
/** The placeholder goes at the leaf's first edit (`createField`). */
|
|
47
48
|
placeholderUntilEdit?: boolean;
|
|
49
|
+
/** An emptied leaf removes the field (`createField`). */
|
|
50
|
+
emptyUnsets?: boolean;
|
|
48
51
|
/** Registry identity, stamped on the DOM node so a remount is visible as one. */
|
|
49
52
|
leafKey: string;
|
|
50
53
|
onFocus?: (addr: Addr) => void;
|
|
@@ -73,6 +76,7 @@
|
|
|
73
76
|
fallback,
|
|
74
77
|
placeholder,
|
|
75
78
|
placeholderUntilEdit,
|
|
79
|
+
emptyUnsets,
|
|
76
80
|
leafKey,
|
|
77
81
|
onFocus,
|
|
78
82
|
onCaretMove,
|
|
@@ -122,6 +126,7 @@
|
|
|
122
126
|
fallback,
|
|
123
127
|
placeholder,
|
|
124
128
|
placeholderUntilEdit,
|
|
129
|
+
emptyUnsets,
|
|
125
130
|
tableStrings: () => t.strings,
|
|
126
131
|
slashStrings: () => t.strings,
|
|
127
132
|
onSlash: (next) => {
|
|
@@ -166,7 +171,7 @@
|
|
|
166
171
|
<div
|
|
167
172
|
{@attach mountField}
|
|
168
173
|
class={[
|
|
169
|
-
'qm-prose',
|
|
174
|
+
'qm-prose qm-wash',
|
|
170
175
|
{ 'qm-control-box': !unframed, 'qm-focus-ring-within': !unframed, 'qm-prose-block': block }
|
|
171
176
|
]}
|
|
172
177
|
data-leaf-key={leafKey}
|
|
@@ -178,14 +183,6 @@
|
|
|
178
183
|
<SlashMenu menu={slash} leaf={() => controller} label={t.strings.slashLabel} />
|
|
179
184
|
|
|
180
185
|
<style>
|
|
181
|
-
/* The box is `.qm-control-box` (controls.css), the same rule the input beside it
|
|
182
|
-
draws, so the two agree on height by construction. No `min-height` with it:
|
|
183
|
-
`core/codec/prose.css` resets the paragraph box, so one line of prose in this box
|
|
184
|
-
measures one line of text in that one. */
|
|
185
|
-
.qm-prose {
|
|
186
|
-
/* Containing block for the arrival wash's inset child (`core/bloom.ts`). */
|
|
187
|
-
position: relative;
|
|
188
|
-
}
|
|
189
186
|
/* A leaf the inline schema does not constrain grows, and opens at one line box: the
|
|
190
187
|
empty leaf is the height its first line will take, so the first keystroke displaces
|
|
191
188
|
nothing below it. `1em`, not a size rung, which is the rule below's to name.
|
|
@@ -196,8 +193,8 @@
|
|
|
196
193
|
.qm-prose-block :global(.ProseMirror) {
|
|
197
194
|
min-height: calc(1em * var(--_qm-leading-body));
|
|
198
195
|
}
|
|
199
|
-
/* Paper, and the withheld box is what says so. Not `block` alone: `
|
|
200
|
-
quill's to declare, so a schema
|
|
196
|
+
/* Paper, and the withheld box is what says so. Not `block` alone: `multiline` is the
|
|
197
|
+
quill's to declare, so a schema spelling it on a richtext field puts a block leaf in
|
|
201
198
|
a field row, where this rung would break the height agreement above. */
|
|
202
199
|
.qm-prose-block:not(.qm-control-box) {
|
|
203
200
|
font-size: var(--_qm-text-paper);
|
|
@@ -11,8 +11,9 @@ interface Props {
|
|
|
11
11
|
plaintext?: boolean;
|
|
12
12
|
/**
|
|
13
13
|
* Draw no box: the card body's leaf, and nothing else. Not `block`, which is a
|
|
14
|
-
* different predicate: a `richtext` field
|
|
15
|
-
* still a control in a row of controls, so only the caller knows what
|
|
14
|
+
* different predicate: a `richtext` field declaring `multiline: true` is block
|
|
15
|
+
* prose and still a control in a row of controls, so only the caller knows what
|
|
16
|
+
* is paper.
|
|
16
17
|
*/
|
|
17
18
|
unframed?: boolean;
|
|
18
19
|
/** Accessible name for a leaf nothing else names: an array element, or the
|
|
@@ -32,6 +33,8 @@ interface Props {
|
|
|
32
33
|
placeholder?: string;
|
|
33
34
|
/** The placeholder goes at the leaf's first edit (`createField`). */
|
|
34
35
|
placeholderUntilEdit?: boolean;
|
|
36
|
+
/** An emptied leaf removes the field (`createField`). */
|
|
37
|
+
emptyUnsets?: boolean;
|
|
35
38
|
/** Registry identity, stamped on the DOM node so a remount is visible as one. */
|
|
36
39
|
leafKey: string;
|
|
37
40
|
onFocus?: (addr: Addr) => void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
A prose leaf the parent commits by value: an element of a `richtext` (fixture
|
|
3
|
-
`keywords`) or `
|
|
3
|
+
`keywords`) or `text` (`authors`, `errata`) array, and a content cell of a subform — an
|
|
4
4
|
`object`'s property, a variant's cell ({@link ObjectField}). None of them is
|
|
5
5
|
`applyChange`-addressable: `Addr.field` is a flat name, so `keywords[0]` and
|
|
6
6
|
`contact.note` have no op address. So this is not a `createField` leaf: it mounts a
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
The content is read, not passed: `reader.getContentAt(addr, path)` decodes through
|
|
21
21
|
the codec the leaf's own declared type names, so what it rests as — the content
|
|
22
|
-
object, a `
|
|
22
|
+
object, a `text` literal, the authored string a transport door left — stops
|
|
23
23
|
being the row's or the cell's business. It is read at mount and again wherever the
|
|
24
24
|
parent's re-derive hands down a `value` that moved, and a read differing from the
|
|
25
25
|
content the leaf last committed or took replaces the view, the gate `createField`
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
it, so the echo replaces nothing and the caret and the history stay.
|
|
29
29
|
|
|
30
30
|
The schema is the declared type's where the parent has room for it: a subform cell
|
|
31
|
-
declaring
|
|
32
|
-
block schema and a `
|
|
31
|
+
declaring `multiline: true` takes the full row ({@link ObjectField}), a `richtext` one
|
|
32
|
+
on the block schema and a `text` one on the plain schema, paragraphs and hard breaks.
|
|
33
33
|
An array element is one textblock whatever it declares, since Enter there is the
|
|
34
34
|
repeater's. There is no slash menu at either width, and no island view.
|
|
35
35
|
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
/** The default an unset leaf holds, where it prints: drawn at the default rung,
|
|
81
81
|
* and handed up whole with the first edit in it, as `createField` takes one. */
|
|
82
82
|
fallback?: Content;
|
|
83
|
-
/** The mark-free schema (a `
|
|
84
|
-
*
|
|
83
|
+
/** The mark-free schema (a `text` leaf): literal text, no formatting, exactly
|
|
84
|
+
* as the field of that type mounts. */
|
|
85
85
|
plaintext?: boolean;
|
|
86
86
|
/** A multi-block schema, for a cell the parent draws at full width: the block
|
|
87
87
|
* schema, or the plain one with `plaintext`. Absent, the leaf is one textblock. */
|
|
@@ -96,8 +96,8 @@
|
|
|
96
96
|
labelledBy?: string;
|
|
97
97
|
/** The parked `description` (FieldLabel) → `aria-describedby`. */
|
|
98
98
|
describedBy?: string;
|
|
99
|
-
/** The empty leaf's ghost, a subform cell's
|
|
100
|
-
*
|
|
99
|
+
/** The empty leaf's ghost, a subform cell's: the `none` an optional cell prints
|
|
100
|
+
* unset. Read per decoration pass. */
|
|
101
101
|
placeholder?: string;
|
|
102
102
|
onChange: (rt: Content) => void;
|
|
103
103
|
/** Raw keydown, for a container whose own keys run through this leaf: the
|
|
@@ -130,9 +130,8 @@
|
|
|
130
130
|
/** Take the caret: what a parent placing focus on this leaf calls. The view's
|
|
131
131
|
* focus, not the element's: a PM view restores its selection, where a bare DOM
|
|
132
132
|
* focus on a contenteditable leaves the caret unplaced. And the reveal beside it,
|
|
133
|
-
* since PM's focus prevents the scroll a
|
|
134
|
-
*
|
|
135
|
-
* landing off screen. */
|
|
133
|
+
* since PM's focus prevents the scroll a form control takes by default — an array
|
|
134
|
+
* whose two item types revealed differently is one row of it landing off screen. */
|
|
136
135
|
export function focus(): void {
|
|
137
136
|
if (!view) return;
|
|
138
137
|
view.focus();
|
|
@@ -157,11 +156,18 @@
|
|
|
157
156
|
view.dispatch(view.state.tr.setSelection(sel).scrollIntoView());
|
|
158
157
|
}
|
|
159
158
|
|
|
159
|
+
$effect(() => {
|
|
160
|
+
void [label, labelledBy, describedBy, placeholder];
|
|
161
|
+
view?.setProps({});
|
|
162
|
+
});
|
|
163
|
+
|
|
160
164
|
function mountView(mount: HTMLDivElement): () => void {
|
|
161
165
|
return untrack(() => {
|
|
162
166
|
const inline = !block;
|
|
163
167
|
const leaf = { inline, plaintext, islands: false };
|
|
164
|
-
|
|
168
|
+
// Read per attribute pass, not captured: an element's name carries its index,
|
|
169
|
+
// which a remove above it moves.
|
|
170
|
+
const names = () => ({ label, labelledBy, describedBy });
|
|
165
171
|
const shown = (stored: Content | undefined): Content => stored ?? fallback ?? emptyContent();
|
|
166
172
|
const stored = content();
|
|
167
173
|
let seen = value;
|
|
@@ -171,8 +177,8 @@
|
|
|
171
177
|
// Which of `aria-label` / `aria-labelledby` wins is the codec's one answer
|
|
172
178
|
// (`proseAttributes`), so a cell carrying a label element and a row carrying
|
|
173
179
|
// none cannot name their regions by different rules.
|
|
174
|
-
const plainNamed = proseAttributes(names) ?? {};
|
|
175
|
-
const heldNamed = heldAttributes(names, heldId);
|
|
180
|
+
const plainNamed = () => proseAttributes(names()) ?? {};
|
|
181
|
+
const heldNamed = () => heldAttributes(names(), heldId);
|
|
176
182
|
const build = (rt: Content): EditorState => {
|
|
177
183
|
// The same keymap and plugin stack a `createField` leaf mounts (shared
|
|
178
184
|
// `proseLeafPlugins`), minus the anchor-position plugin: anchors are dropped on
|
|
@@ -184,8 +190,7 @@
|
|
|
184
190
|
? []
|
|
185
191
|
: proseLeafPlugins(schema, {
|
|
186
192
|
inline,
|
|
187
|
-
placeholder: () => placeholder
|
|
188
|
-
placeholderUntilEdit: true
|
|
193
|
+
placeholder: () => placeholder
|
|
189
194
|
})
|
|
190
195
|
});
|
|
191
196
|
};
|
|
@@ -195,11 +200,12 @@
|
|
|
195
200
|
state: build(reconciler.last),
|
|
196
201
|
editable: () => !held,
|
|
197
202
|
attributes: () => {
|
|
198
|
-
const named = held ? heldNamed : plainNamed;
|
|
203
|
+
const named = held ? heldNamed() : plainNamed();
|
|
199
204
|
return defaulted ? { ...named, 'data-default': '' } : named;
|
|
200
205
|
},
|
|
201
206
|
dispatchTransaction(tr) {
|
|
202
207
|
const next = mounted.state.apply(tr);
|
|
208
|
+
if (next === mounted.state) return;
|
|
203
209
|
const edit = tr.docChanged && !held;
|
|
204
210
|
if (edit) defaulted = false;
|
|
205
211
|
mounted.updateState(next);
|
|
@@ -265,8 +271,8 @@
|
|
|
265
271
|
</script>
|
|
266
272
|
|
|
267
273
|
<!-- `.qm-control-box` (controls.css) is the whole box, so an array of `richtext` and
|
|
268
|
-
an array of `
|
|
269
|
-
like the
|
|
274
|
+
an array of `text` render rows of equal height, and a subform's prose cell measures
|
|
275
|
+
like the form control beside it. No floor: the reset in `core/codec/prose.css` makes one
|
|
270
276
|
line of prose measure one line. Width is the row's or the cell's to give: the leaf
|
|
271
277
|
fills the track it is placed in. -->
|
|
272
278
|
<div class="qm-prose-value qm-control-box qm-focus-ring-within">
|
|
@@ -12,8 +12,8 @@ interface Props {
|
|
|
12
12
|
/** The default an unset leaf holds, where it prints: drawn at the default rung,
|
|
13
13
|
* and handed up whole with the first edit in it, as `createField` takes one. */
|
|
14
14
|
fallback?: Content;
|
|
15
|
-
/** The mark-free schema (a `
|
|
16
|
-
*
|
|
15
|
+
/** The mark-free schema (a `text` leaf): literal text, no formatting, exactly
|
|
16
|
+
* as the field of that type mounts. */
|
|
17
17
|
plaintext?: boolean;
|
|
18
18
|
/** A multi-block schema, for a cell the parent draws at full width: the block
|
|
19
19
|
* schema, or the plain one with `plaintext`. Absent, the leaf is one textblock. */
|
|
@@ -28,8 +28,8 @@ interface Props {
|
|
|
28
28
|
labelledBy?: string;
|
|
29
29
|
/** The parked `description` (FieldLabel) → `aria-describedby`. */
|
|
30
30
|
describedBy?: string;
|
|
31
|
-
/** The empty leaf's ghost, a subform cell's
|
|
32
|
-
*
|
|
31
|
+
/** The empty leaf's ghost, a subform cell's: the `none` an optional cell prints
|
|
32
|
+
* unset. Read per decoration pass. */
|
|
33
33
|
placeholder?: string;
|
|
34
34
|
onChange: (rt: Content) => void;
|
|
35
35
|
/** Raw keydown, for a container whose own keys run through this leaf: the
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
A
|
|
3
|
-
typed `writer.set`, so the
|
|
2
|
+
A text input: the control a field of a type this build does not know falls to.
|
|
3
|
+
Commits every edit live (on input) via the parent's typed `writer.set`, so the
|
|
4
|
+
preview tracks typing.
|
|
4
5
|
|
|
5
6
|
An unset field whose `default:` prints holds that default as its text, at the
|
|
6
7
|
default rung (theme.css): the value it prints, drawn as one and a step off a written
|
|
@@ -31,9 +31,6 @@
|
|
|
31
31
|
}
|
|
32
32
|
let { tips, onDismiss, onError }: Props = $props();
|
|
33
33
|
|
|
34
|
-
/** Control-glyph size: the shared rule, as CardControls. */
|
|
35
|
-
const GLYPH = 14;
|
|
36
|
-
|
|
37
34
|
let cursor = $state(0);
|
|
38
35
|
// Clamped: the channel is consumer-authored and can be re-seeded shorter while
|
|
39
36
|
// the card is open, which would otherwise index past the end.
|
|
@@ -72,7 +69,7 @@
|
|
|
72
69
|
{#each tips as _, i (i)}
|
|
73
70
|
<button
|
|
74
71
|
type="button"
|
|
75
|
-
class="qm-tips-dot"
|
|
72
|
+
class="qm-tips-dot qm-focus-ring"
|
|
76
73
|
aria-label={t.strings.tipsDot(i + 1, tips.length)}
|
|
77
74
|
aria-current={i === index}
|
|
78
75
|
onclick={() => (cursor = i)}
|
|
@@ -80,11 +77,11 @@
|
|
|
80
77
|
{/each}
|
|
81
78
|
</div>
|
|
82
79
|
{/if}
|
|
83
|
-
<button type="button" class="qm-icon-btn qm-tips-action" onclick={advance}>
|
|
80
|
+
<button type="button" class="qm-icon-btn qm-tips-action qm-focus-ring" onclick={advance}>
|
|
84
81
|
{#if isLast}
|
|
85
|
-
{t.strings.tipsDismiss} <Icon name="trash-2"
|
|
82
|
+
{t.strings.tipsDismiss} <Icon name="trash-2" />
|
|
86
83
|
{:else}
|
|
87
|
-
{t.strings.tipsNext} <Icon name="arrow-right"
|
|
84
|
+
{t.strings.tipsNext} <Icon name="arrow-right" />
|
|
88
85
|
{/if}
|
|
89
86
|
</button>
|
|
90
87
|
</div>
|
|
@@ -196,6 +193,9 @@
|
|
|
196
193
|
.qm-tips-dot:hover {
|
|
197
194
|
opacity: var(--_qm-opacity-muted);
|
|
198
195
|
}
|
|
196
|
+
.qm-tips-dot:focus-visible {
|
|
197
|
+
opacity: 1;
|
|
198
|
+
}
|
|
199
199
|
.qm-tips-dot[aria-current='true'] {
|
|
200
200
|
opacity: 1;
|
|
201
201
|
}
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
describedBy?: string;
|
|
43
43
|
/** The boundary's nested content read for this field, which the cells take
|
|
44
44
|
* unprefixed: a variant's cell is one key in from the container it rests as, so
|
|
45
|
-
* a `
|
|
45
|
+
* a `text` cell's path is its own name ({@link ObjectField}). */
|
|
46
46
|
contentAt: (path: PathStep[]) => Content | undefined;
|
|
47
47
|
onCommit: (v: Record<string, unknown> | undefined) => void;
|
|
48
48
|
optionAllowed?: (value: string) => boolean;
|
|
@@ -16,7 +16,7 @@ interface Props {
|
|
|
16
16
|
describedBy?: string;
|
|
17
17
|
/** The boundary's nested content read for this field, which the cells take
|
|
18
18
|
* unprefixed: a variant's cell is one key in from the container it rests as, so
|
|
19
|
-
* a `
|
|
19
|
+
* a `text` cell's path is its own name ({@link ObjectField}). */
|
|
20
20
|
contentAt: (path: PathStep[]) => Content | undefined;
|
|
21
21
|
onCommit: (v: Record<string, unknown> | undefined) => void;
|
|
22
22
|
optionAllowed?: (value: string) => boolean;
|