@quillmark/svelte 0.3.2 → 0.4.1
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/README.md +1 -1
- package/THEMING.md +10 -6
- package/dist/core/address.d.ts +5 -11
- package/dist/core/address.js +7 -18
- package/dist/core/codec/decode.d.ts +2 -7
- package/dist/core/codec/decode.js +9 -5
- package/dist/core/codec/field.d.ts +6 -7
- package/dist/core/codec/field.js +21 -21
- package/dist/core/codec/index.d.ts +1 -1
- package/dist/core/codec/index.js +3 -2
- package/dist/core/codec/inputrules.js +17 -19
- package/dist/core/codec/prose.css +8 -7
- package/dist/core/codec/schema.d.ts +15 -6
- package/dist/core/codec/schema.js +30 -17
- package/dist/core/theme.css +52 -40
- package/dist/preset/recipes.css +3 -3
- package/dist/preset/scale.css +21 -19
- package/dist/preview/geometry.d.ts +4 -3
- package/dist/preview/geometry.js +7 -4
- package/dist/visual/ArrayField.svelte +11 -6
- package/dist/visual/Card.svelte +5 -1
- package/dist/visual/DateField.svelte +1 -1
- package/dist/visual/EnumField.svelte +4 -4
- package/dist/visual/Field.svelte +2 -2
- package/dist/visual/FieldHint.svelte +16 -2
- package/dist/visual/FormatPopover.svelte +12 -1
- package/dist/visual/ObjectField.svelte +3 -3
- package/dist/visual/ProseArrayElement.svelte +5 -6
- package/dist/visual/ProseArrayElement.svelte.d.ts +0 -1
- package/dist/visual/ProseField.svelte +5 -3
- package/dist/visual/VisualEditorInner.svelte +5 -6
- package/dist/visual/controls.css +11 -7
- package/dist/visual/structure.d.ts +0 -2
- package/dist/visual/structure.js +1 -7
- package/package.json +2 -2
package/dist/core/theme.css
CHANGED
|
@@ -89,8 +89,16 @@
|
|
|
89
89
|
/* Neutral: the surface spends no hue on a state it can draw in grey. The pair
|
|
90
90
|
clears WCAG 2.4.11's 3:1 against the card at both poles, at 4.8:1 and 5.8:1. */
|
|
91
91
|
--_qm-accent: var(--qm-accent, light-dark(#71717a, #a1a1aa));
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
/* The two hues the surface does spend, and it spends them on a state. Each pole
|
|
93
|
+
is its own figure rather than one ink shown twice: a hue calibrated against
|
|
94
|
+
white reads as a stain on a dark card. All four clear WCAG 1.4.3's 4.5:1
|
|
95
|
+
against the card, which is the bar the label rung asks for and the one the
|
|
96
|
+
glyphs drawn in these hues clear on the way past 1.4.11's 3:1 — 5.8:1 and
|
|
97
|
+
5.2:1 light, 7.4:1 and 7.7:1 dark. The figures are the host scale's
|
|
98
|
+
(`preset/scale.css`), so a page's status line and a card's diagnostic carry
|
|
99
|
+
one colour meaning error and one meaning warning. */
|
|
100
|
+
--_qm-danger: var(--qm-danger, light-dark(#c5221f, #ff9c94));
|
|
101
|
+
--_qm-warning: var(--qm-warning, light-dark(#b25000, #e0b44c));
|
|
94
102
|
--_qm-font: var(--qm-font, ui-sans-serif, system-ui, sans-serif);
|
|
95
103
|
--_qm-font-mono: var(--qm-font-mono, ui-monospace, monospace);
|
|
96
104
|
|
|
@@ -118,46 +126,45 @@
|
|
|
118
126
|
a channel of each other, which is where a card with little light left to
|
|
119
127
|
give up leaves them.
|
|
120
128
|
|
|
121
|
-
|
|
122
|
-
surface has to spend more of what little it has
|
|
123
|
-
|
|
124
|
-
the
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
How far each sinks is a function of the card's own L, not of the scheme the
|
|
130
|
+
host declared: a dark surface has to spend more of what little it has
|
|
131
|
+
before the card above it reads as lifted, so the two calibrated distances
|
|
132
|
+
— 0.0214 off white, 0.0553 off the dark default — interpolate linearly
|
|
133
|
+
between. A card pinned at neither pole takes a proportionate step whatever
|
|
134
|
+
`color-scheme` says, which is the half of the derivation that reads a
|
|
135
|
+
colour rather than a declaration.
|
|
127
136
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
neutral, and a ground is a fixed distance
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
`raised` is a no-op at the light pole, where the card is already white
|
|
137
|
-
and there is nowhere further to go, so a floating surface separates on
|
|
138
|
-
its hairline in light and on the step in dark. */
|
|
137
|
+
Both sink by subtraction where every other rung mixes: a mix toward black
|
|
138
|
+
is a ratio, so it scales what it takes and pulls the result toward
|
|
139
|
+
neutral, and a ground is a fixed distance. `a b` pass through, so the
|
|
140
|
+
plane keeps the pole's hue and a turned `--qm-bg` tints it. The defaults
|
|
141
|
+
land within a channel of `#f7f8f9` and `#1a1a1b`. Below L ≈ 0.065 the
|
|
142
|
+
ground clamps to black, which is where a near-black card leaves no room
|
|
143
|
+
for a ground of its own. */
|
|
139
144
|
--_qm-surface: var(--_qm-bg);
|
|
140
|
-
--_qm-surface-sunken:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
color-mix(in oklab, var(--_qm-bg), #000 20%)
|
|
147
|
-
);
|
|
145
|
+
--_qm-surface-sunken: oklab(from var(--_qm-bg) calc(l - (0.0214 + 0.0467 * (1 - l))) a b);
|
|
146
|
+
--_qm-surface-well: oklab(from var(--_qm-bg) calc(l - (0.04 + 0.0202 * (1 - l))) a b);
|
|
147
|
+
/* `raised` is the same ramp spelled as a mix, its step 0.08 × (1 − l): the light
|
|
148
|
+
arm is zero, a white card having nowhere further to go, so a floating surface
|
|
149
|
+
separates on its hairline and `--_qm-lift` there and the step joins them in
|
|
150
|
+
dark. */
|
|
148
151
|
--_qm-surface-raised: color-mix(in oklab, var(--_qm-bg), #fff 8%);
|
|
149
152
|
--_qm-surface-hover: color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 8%);
|
|
150
153
|
--_qm-border: color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 17%);
|
|
151
154
|
/* A rule that separates without structuring: the lines between a table's cells,
|
|
152
|
-
where the frame around them is the structure. Half the border
|
|
153
|
-
than a
|
|
154
|
-
surface behind it, not a fill
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
where the frame around them is the structure. Half the border, and read off
|
|
156
|
+
it rather than restated as a percentage of its own, so the two hold their
|
|
157
|
+
relationship at any mix: a stroke against the surface behind it, not a fill
|
|
158
|
+
stacked on one. */
|
|
159
|
+
--_qm-border-faint: color-mix(in oklab, var(--_qm-bg), var(--_qm-border) 50%);
|
|
160
|
+
/* Two rungs, and there is no third: a step below `label` lands under 3:1 on the
|
|
161
|
+
well a field draws it against, and what would take it — a placeholder, an
|
|
162
|
+
unset value, the resolved `default:` — is content a reader has to read
|
|
163
|
+
(WCAG 1.4.3), not decoration. So tone carries hierarchy alone, and a leaf
|
|
164
|
+
saying shown-never-written says it against the `ink` a written value takes,
|
|
165
|
+
in italic where it has no written neighbour to be read against. */
|
|
158
166
|
--_qm-ink: var(--_qm-fg);
|
|
159
167
|
--_qm-ink-label: color-mix(in oklab, var(--_qm-fg), var(--_qm-bg) 35%);
|
|
160
|
-
--_qm-ink-ghost: color-mix(in oklab, var(--_qm-fg), var(--_qm-bg) 60%);
|
|
161
168
|
/* The arrival wash (`core/bloom.ts`). Its alpha is a peak, not a tint: the
|
|
162
169
|
wash animates from and back to zero, never held, so it reads over the leaf it
|
|
163
170
|
lands on while the leaf rests clean. */
|
|
@@ -208,16 +215,21 @@
|
|
|
208
215
|
--_qm-tips-font: 'Courier Prime', 'Courier New', Courier, ui-monospace, monospace;
|
|
209
216
|
|
|
210
217
|
/* ── Ratios and effects ──────────────────────────────────────────────────
|
|
211
|
-
No palette fixes these, so they are their own rungs.
|
|
212
|
-
rung: elevation is a tone rung and a hairline, which say the same thing at
|
|
213
|
-
both poles, where a shadow's offset states a light source the poles do not
|
|
214
|
-
carry. `check:style` forbids the property outright,
|
|
215
|
-
here as well as over the surfaces, so the absence is gated rather than
|
|
216
|
-
remembered. */
|
|
218
|
+
No palette fixes these, so they are their own rungs. */
|
|
217
219
|
--_qm-blur: 8px;
|
|
218
220
|
/* The popover's translucency: the raised surface mixed toward transparent, so
|
|
219
221
|
the page reads faintly through the pill. */
|
|
220
222
|
--_qm-surface-popover: color-mix(in oklab, var(--_qm-surface-raised) 82%, transparent);
|
|
223
|
+
/* What a surface with no plane under it lifts on. A tone rung cannot carry that at
|
|
224
|
+
both poles: `raised` is a no-op against a white card (§Colour), so in light the
|
|
225
|
+
hairline is otherwise the whole separation over the body prose these float on.
|
|
226
|
+
Offset-free is the whole of it: an offset states where the light is and no pole
|
|
227
|
+
carries that, where a halo states occlusion. Toward black at both poles for the
|
|
228
|
+
same reason — a crevice is darker than what surrounds it in a dark room too — so
|
|
229
|
+
the cue means one thing whichever way the scale runs. Spent only on what portals;
|
|
230
|
+
`check:style` holds the offsets at zero here and the rung as the only shadow
|
|
231
|
+
anywhere. */
|
|
232
|
+
--_qm-lift: 0 0 12px color-mix(in oklab, #000 16%, transparent);
|
|
221
233
|
/* The recede ladder: a hover-revealed or disabled affordance at rest, and a
|
|
222
234
|
muted one. Opacity recedes toward whatever is behind, so these invert
|
|
223
235
|
correctly given the element's own colour is a rung. */
|
package/dist/preset/recipes.css
CHANGED
|
@@ -183,7 +183,7 @@ html:has(.qm-workspace) {
|
|
|
183
183
|
/* ── Type ──────────────────────────────────────────────────────────────────── */
|
|
184
184
|
|
|
185
185
|
/* A run of chrome type: a section's label, a panel's caption, the name over a readout.
|
|
186
|
-
Uppercase and tracked, which is what keeps a run this small legible at the
|
|
186
|
+
Uppercase and tracked, which is what keeps a run this small legible at the meta
|
|
187
187
|
rung. */
|
|
188
188
|
.qm-label {
|
|
189
189
|
font-family: var(--qmh-font-mono);
|
|
@@ -191,7 +191,7 @@ html:has(.qm-workspace) {
|
|
|
191
191
|
line-height: var(--qmh-leading-tight);
|
|
192
192
|
letter-spacing: var(--qmh-track-label);
|
|
193
193
|
text-transform: uppercase;
|
|
194
|
-
color: var(--qmh-
|
|
194
|
+
color: var(--qmh-ink-meta);
|
|
195
195
|
margin: 0;
|
|
196
196
|
}
|
|
197
197
|
|
|
@@ -232,7 +232,7 @@ pre.qm-readout {
|
|
|
232
232
|
line-height: var(--qmh-leading-tight);
|
|
233
233
|
letter-spacing: var(--qmh-track-label);
|
|
234
234
|
text-transform: uppercase;
|
|
235
|
-
color: var(--qmh-
|
|
235
|
+
color: var(--qmh-ink-meta);
|
|
236
236
|
margin: 0;
|
|
237
237
|
}
|
|
238
238
|
|
package/dist/preset/scale.css
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/* The host'S derivation: the same public dials in, a closed `--qmh-*` scale for the
|
|
2
2
|
page around the surfaces out. `core/theme.css` is the same shape one level down
|
|
3
3
|
(dials in, `--_qm-*` out), and the two are calibrated against each other: the
|
|
4
|
-
figures here are the figures there (the ground step's
|
|
5
|
-
border, 45%
|
|
6
|
-
both, so a host plate and a card on it land on the same
|
|
7
|
-
host page meets an editor's own column exactly. The well
|
|
8
|
-
host's own controls are boxed and carry a
|
|
9
|
-
step an edgeless field does.
|
|
4
|
+
figures here are the figures there (the ground step's ramp, 8% hover, 17%
|
|
5
|
+
border, 45% ink, the ring's width, the status hues at both poles) and the
|
|
6
|
+
dial means the same plane in both, so a host plate and a card on it land on the same
|
|
7
|
+
tone at both poles and a host page meets an editor's own column exactly. The well
|
|
8
|
+
under the ground has no counterpart here: a host's own controls are boxed and carry a
|
|
9
|
+
hairline, so nothing on a page needs the step an edgeless field does.
|
|
10
10
|
|
|
11
11
|
Two scales rather than one because they answer to different roots. `--_qm-*` is
|
|
12
12
|
declared on each mounted root, out of reach from an ancestor, so a surface holds its
|
|
@@ -26,30 +26,32 @@
|
|
|
26
26
|
/* ── Colour ──────────────────────────────────────────────────────────────── */
|
|
27
27
|
/* The dial is the plate, exactly as it is the card one level down: a plate beside
|
|
28
28
|
a mounted editor is the same plane as a card in it, so both read the pole and
|
|
29
|
-
the page sinks from it by the same
|
|
29
|
+
the page sinks from it by the same ramp. That is what puts a host page and
|
|
30
30
|
an editor's own column on one tone rather than a plane apart. Planes step by
|
|
31
31
|
light and everything else by ink, which is the package's own split
|
|
32
|
-
(ARCHITECTURE §Styling); the figures are its figures.
|
|
32
|
+
(ARCHITECTURE §Styling); the figures are its figures. `Canvas` flips with the
|
|
33
|
+
declared scheme on its own, so the ramp reads whichever plane it resolved to
|
|
34
|
+
rather than trusting an arm to match. */
|
|
33
35
|
--qmh-surface: var(--qm-bg, Canvas);
|
|
34
|
-
--qmh-page:
|
|
35
|
-
oklab(from var(--qmh-surface) calc(l - 0.0214) calc(a - 0.00065) calc(b - 0.00158)),
|
|
36
|
-
oklab(from var(--qmh-surface) calc(l - 0.0553) calc(a - 0.00048) calc(b + 0.00165))
|
|
37
|
-
);
|
|
36
|
+
--qmh-page: oklab(from var(--qmh-surface) calc(l - (0.0214 + 0.0467 * (1 - l))) a b);
|
|
38
37
|
--qmh-ink: var(--qm-fg, CanvasText);
|
|
38
|
+
/* One step off the ink and no second: a page's chrome type is small, tracked and
|
|
39
|
+
uppercase, so a rung below this reads under 4.5:1 against the plate and the
|
|
40
|
+
label and status lines that would take it are text (WCAG 1.4.3). */
|
|
39
41
|
--qmh-ink-meta: color-mix(in oklab, var(--qmh-ink), var(--qmh-surface) 45%);
|
|
40
|
-
--qmh-ghost: color-mix(in oklab, var(--qmh-ink), var(--qmh-surface) 60%);
|
|
41
42
|
--qmh-surface-hover: color-mix(in oklab, var(--qmh-surface), var(--qmh-ink) 8%);
|
|
42
43
|
--qmh-border: color-mix(in oklab, var(--qmh-surface), var(--qmh-ink) 17%);
|
|
43
44
|
/* The edge that says "engaged" where the hairline says "edge": a pressed switch,
|
|
44
45
|
the underline under a link. */
|
|
45
46
|
--qmh-border-strong: color-mix(in oklab, var(--qmh-surface), var(--qmh-ink) 40%);
|
|
46
47
|
|
|
47
|
-
/* The two hues a host spends, and it spends them on a message.
|
|
48
|
-
so a page carries one colour meaning error and one meaning warning
|
|
49
|
-
second pair beside the surfaces'.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
--qmh-
|
|
48
|
+
/* The two hues a host spends, and it spends them on a message. The figures are the
|
|
49
|
+
surfaces' own, so a page carries one colour meaning error and one meaning warning
|
|
50
|
+
rather than a second pair beside the surfaces'. Each pole is its own figure: a hue
|
|
51
|
+
calibrated against white reads as a stain on a dark page. The dial sits in front
|
|
52
|
+
of the pair, as every pole's does, so a host that turns one owns both schemes. */
|
|
53
|
+
--qmh-alert: var(--qm-danger, light-dark(#c5221f, #ff9c94));
|
|
54
|
+
--qmh-warn: var(--qm-warning, light-dark(#b25000, #e0b44c));
|
|
53
55
|
|
|
54
56
|
/* ── Rhythm ──────────────────────────────────────────────────────────────── */
|
|
55
57
|
/* One base and its multiples, the same four steps the surfaces derive. A host
|
|
@@ -22,9 +22,10 @@ export interface PdfPoint {
|
|
|
22
22
|
* its content and for a `richtext[]` element; runtime.d.ts says what such a field's
|
|
23
23
|
* box is, a single `regions()` rect. The third rung is that sentence one granularity
|
|
24
24
|
* down — an array's ink is its elements' — and is a fallback, not a union, so an
|
|
25
|
-
* address with rects of its own never also draws its children's. The
|
|
26
|
-
*
|
|
27
|
-
* `main.
|
|
25
|
+
* address with rects of its own never also draws its children's. The boundary
|
|
26
|
+
* character (`.` for a nested field, `[` for an array element) keeps the prefix a
|
|
27
|
+
* path boundary: `main.keywords` matches `main.keywords[0]`, not
|
|
28
|
+
* `main.keywords_note`.
|
|
28
29
|
*/
|
|
29
30
|
export declare function boxesForField(field: string, boxes: readonly FieldRegion[], regions: readonly FieldRegion[]): readonly FieldRegion[];
|
|
30
31
|
/**
|
package/dist/preview/geometry.js
CHANGED
|
@@ -7,15 +7,18 @@
|
|
|
7
7
|
* its content and for a `richtext[]` element; runtime.d.ts says what such a field's
|
|
8
8
|
* box is, a single `regions()` rect. The third rung is that sentence one granularity
|
|
9
9
|
* down — an array's ink is its elements' — and is a fallback, not a union, so an
|
|
10
|
-
* address with rects of its own never also draws its children's. The
|
|
11
|
-
*
|
|
12
|
-
* `main.
|
|
10
|
+
* address with rects of its own never also draws its children's. The boundary
|
|
11
|
+
* character (`.` for a nested field, `[` for an array element) keeps the prefix a
|
|
12
|
+
* path boundary: `main.keywords` matches `main.keywords[0]`, not
|
|
13
|
+
* `main.keywords_note`.
|
|
13
14
|
*/
|
|
14
15
|
export function boxesForField(field, boxes, regions) {
|
|
15
16
|
if (boxes.length)
|
|
16
17
|
return boxes;
|
|
17
18
|
const own = regions.filter((r) => r.field === field);
|
|
18
|
-
|
|
19
|
+
if (own.length)
|
|
20
|
+
return own;
|
|
21
|
+
return regions.filter((r) => r.field.startsWith(`${field}.`) || r.field.startsWith(`${field}[`));
|
|
19
22
|
}
|
|
20
23
|
/**
|
|
21
24
|
* Forward: a PDF-pt rect -> CSS % of the page box. Percent positioning tracks
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
An `array` field → an add/remove repeater. Elements commit by value: every
|
|
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
|
-
by `items.type`: `string` → text input, `richtext` → a prose element
|
|
5
|
+
by `items.type`: `string` / `plaintext` → text input, `richtext` → a prose element
|
|
6
6
|
({@link ProseArrayElement}), `object` → a minimal JSON editor, keeping the prior
|
|
7
7
|
element value on an entry that does not parse. The add affordance sits in the
|
|
8
8
|
label header row (space-between with the field label); {@link Field} skips its
|
|
@@ -59,10 +59,16 @@
|
|
|
59
59
|
let { value, items, label, required, description, labelId, descriptionId, onCommit }: Props =
|
|
60
60
|
$props();
|
|
61
61
|
|
|
62
|
-
// The element control is the item schema's own
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
// The element control is the item schema's own, with one departure: a `plaintext`
|
|
63
|
+
// element is a text input where the scalar field of that type is a prose leaf. A
|
|
64
|
+
// `plaintext` value rests as its literal string (canon SCHEMAS.md §"Content fields
|
|
65
|
+
// rest per codec"), and the read that decodes a resting value to `Content` addresses
|
|
66
|
+
// a field, never an element (`getContent`; borb-sh/quillmark#1243), so a string is
|
|
67
|
+
// what the row is handed and a string is what it commits. The prose element stays
|
|
68
|
+
// the `richtext` arm, whose elements rest as `Content` and have one.
|
|
69
|
+
//
|
|
70
|
+
// An array declaring no `items` has text elements.
|
|
71
|
+
const control = $derived(items && items.type !== 'plaintext' ? controlKind(items) : 'text');
|
|
66
72
|
const arr = $derived((value ?? []) as unknown[]);
|
|
67
73
|
|
|
68
74
|
// Parallel stable ids, one per element, kept in lockstep with the data below.
|
|
@@ -243,7 +249,6 @@
|
|
|
243
249
|
<ProseArrayElement
|
|
244
250
|
bind:this={els[id]}
|
|
245
251
|
value={(arr[k] ?? emptyElement()) as Content}
|
|
246
|
-
{plaintext}
|
|
247
252
|
label={label != null ? `${label} ${k + 1}` : undefined}
|
|
248
253
|
onChange={(rt) => commitElement(k, rt)}
|
|
249
254
|
onKey={(e) => onElementKey(e, k)}
|
package/dist/visual/Card.svelte
CHANGED
|
@@ -185,9 +185,13 @@
|
|
|
185
185
|
}
|
|
186
186
|
</script>
|
|
187
187
|
|
|
188
|
+
<!-- `data-qm-card` names the island for a floating surface that has to stay inside one
|
|
189
|
+
({@link FieldHint}). A surface portals to `[data-qm-root]`, so the card is not its
|
|
190
|
+
ancestor and nothing else marks the box its guidance belongs in. -->
|
|
188
191
|
<section
|
|
189
192
|
bind:this={el}
|
|
190
193
|
class="qm-card"
|
|
194
|
+
data-qm-card
|
|
191
195
|
class:qm-main={card.isMain}
|
|
192
196
|
class:qm-unschemable={card.unschemable}
|
|
193
197
|
>
|
|
@@ -662,7 +666,7 @@
|
|
|
662
666
|
color: var(--_qm-ink-label);
|
|
663
667
|
}
|
|
664
668
|
.qm-recovery-muted {
|
|
665
|
-
color: var(--_qm-ink-
|
|
669
|
+
color: var(--_qm-ink-label);
|
|
666
670
|
}
|
|
667
671
|
.qm-recovery-retype {
|
|
668
672
|
display: inline-flex;
|
|
@@ -198,6 +198,6 @@
|
|
|
198
198
|
never written, either way. The marker is the component's own (see the snippet):
|
|
199
199
|
the date primitive emits no placeholder attribute of its own. */
|
|
200
200
|
.qm-date-wrap :global(.qm-date-segment[data-ghosted]) {
|
|
201
|
-
color: var(--_qm-ink-
|
|
201
|
+
color: var(--_qm-ink-label);
|
|
202
202
|
}
|
|
203
203
|
</style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
A `
|
|
3
|
-
|
|
2
|
+
A `type: enum` field → a styled listbox over its `values`, on bits-ui. When
|
|
3
|
+
nothing is authored the list shows a distinct unset
|
|
4
4
|
sentinel that ghosts the `default:` (muted, shown-never-written), distinguishable
|
|
5
5
|
from an authored pick and re-selectable; so re-picking the default fires a
|
|
6
6
|
change. The sentinel commits nothing; any real pick: including the
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
/* Shown-never-written: the closed control reads muted while unset, matching the
|
|
165
165
|
ghosted placeholder the text/number controls show. */
|
|
166
166
|
.qm-select-wrap :global(.qm-select[data-ghosted]) {
|
|
167
|
-
color: var(--_qm-ink-
|
|
167
|
+
color: var(--_qm-ink-label);
|
|
168
168
|
}
|
|
169
169
|
/* The open list is `.qm-menu-surface` and its rows `.qm-menu-item` (controls.css):
|
|
170
170
|
the lift, the inset and the highlight are the shared menu recipe. What a listbox
|
|
@@ -184,6 +184,6 @@
|
|
|
184
184
|
cursor: default;
|
|
185
185
|
}
|
|
186
186
|
.qm-select-ghost {
|
|
187
|
-
color: var(--_qm-ink-
|
|
187
|
+
color: var(--_qm-ink-label);
|
|
188
188
|
}
|
|
189
189
|
</style>
|
package/dist/visual/Field.svelte
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import type { EditorErrorHandler } from '../core/errors.js';
|
|
19
19
|
import type { LeafRegistry } from './leaves.js';
|
|
20
20
|
import type { FieldModel, FieldSpan } from './structure.js';
|
|
21
|
-
import {
|
|
21
|
+
import { ghostDefault, stringifyGhost } from './structure.js';
|
|
22
22
|
import type { FieldDomIds } from './domid.js';
|
|
23
23
|
import ProseField from './ProseField.svelte';
|
|
24
24
|
import TextField from './TextField.svelte';
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
{:else if field.control === 'enum'}
|
|
208
208
|
<EnumField
|
|
209
209
|
value={value as string | undefined}
|
|
210
|
-
values={
|
|
210
|
+
values={field.schema.values ?? []}
|
|
211
211
|
fallback={ghost as string | undefined}
|
|
212
212
|
id={domIds.control}
|
|
213
213
|
{describedBy}
|
|
@@ -46,6 +46,13 @@
|
|
|
46
46
|
* along with the editor's subtree. `undefined` falls back to bits-ui's default. */
|
|
47
47
|
const portalTarget = $derived(triggerEl?.closest<HTMLElement>('[data-qm-root]') ?? undefined);
|
|
48
48
|
|
|
49
|
+
/** What the surface stays inside: the field's own card and the root it portals into.
|
|
50
|
+
* floating-ui intersects the two, so a flip answers to whichever is tighter. Empty
|
|
51
|
+
* falls back to the primitive's default, the surface's own clipping ancestors. */
|
|
52
|
+
const bounds = $derived(
|
|
53
|
+
[triggerEl?.closest<HTMLElement>('[data-qm-card]'), portalTarget].filter((el) => el != null)
|
|
54
|
+
);
|
|
55
|
+
|
|
49
56
|
/** Placement, re-measured on each opening: the surface rides the label rung beside
|
|
50
57
|
* the glyph, or stands over the field. */
|
|
51
58
|
let beside = $state(false);
|
|
@@ -132,11 +139,18 @@
|
|
|
132
139
|
neighbour's control instead, which reaching for it dismisses. -->
|
|
133
140
|
<!-- `align="center"` for the rung, `"start"` over the field: centred, a surface taller
|
|
134
141
|
than the glyph's line box would hang half its height onto the field's own control. -->
|
|
142
|
+
<!-- Both are preferences, and the card is what refuses them: its first field has no room
|
|
143
|
+
above its label inside one, so standing over that field puts the surface on the ground
|
|
144
|
+
above the card, reading as the neighbour's rather than as this field's. Refused, the
|
|
145
|
+
placement reverses and the surface goes under the field, over the control it describes:
|
|
146
|
+
the overlap the fork above spends a probe to avoid, and the only one left where there is
|
|
147
|
+
nothing above to stand over. -->
|
|
135
148
|
|
|
136
149
|
<Popover.Root bind:open>
|
|
137
150
|
<Popover.Portal to={portalTarget}>
|
|
138
151
|
<Popover.Content
|
|
139
152
|
customAnchor={triggerEl ?? null}
|
|
153
|
+
collisionBoundary={bounds}
|
|
140
154
|
side={beside ? 'right' : 'top'}
|
|
141
155
|
align={beside ? 'center' : 'start'}
|
|
142
156
|
sideOffset={6}
|
|
@@ -186,8 +200,8 @@
|
|
|
186
200
|
It takes no fill either: the ink step below says hover, and the surface itself
|
|
187
201
|
opens on it. */
|
|
188
202
|
.qm-field-hint {
|
|
189
|
-
/* The marker recedes to the label
|
|
190
|
-
color: var(--_qm-ink-
|
|
203
|
+
/* The marker recedes to the label rung; the surface carries the text. */
|
|
204
|
+
color: var(--_qm-ink-label);
|
|
191
205
|
/* Not the family's `pointer`: the glyph raises guidance, it does not act. */
|
|
192
206
|
cursor: help;
|
|
193
207
|
padding: 0;
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
import Icon from './icons/Icon.svelte';
|
|
66
66
|
import type { IconName } from './icons/nodes.js';
|
|
67
67
|
import {
|
|
68
|
+
hasMarks,
|
|
68
69
|
rangeAnchor,
|
|
69
70
|
type FieldController,
|
|
70
71
|
type LeafViews,
|
|
@@ -140,7 +141,17 @@
|
|
|
140
141
|
// Escape out of a table cell lands.
|
|
141
142
|
const selection = view?.state.selection;
|
|
142
143
|
const formattable = selection instanceof TextSelection && !selection.empty;
|
|
143
|
-
|
|
144
|
+
// …in a leaf that has formatting at all. A `plaintext` leaf declares no mark
|
|
145
|
+
// types (`plaintextSchema`), so this surface withholds itself over one entirely,
|
|
146
|
+
// the way it never rises over the text input a `string` field draws: the value is
|
|
147
|
+
// literal text, and a mark on it is a value the boundary refuses to coerce back.
|
|
148
|
+
if (
|
|
149
|
+
!view ||
|
|
150
|
+
!view.hasFocus() ||
|
|
151
|
+
!formattable ||
|
|
152
|
+
!hasMarks(view.state.schema) ||
|
|
153
|
+
!hasText(view)
|
|
154
|
+
) {
|
|
144
155
|
open = false;
|
|
145
156
|
linkPromptOpen = false;
|
|
146
157
|
return;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
-->
|
|
12
12
|
<script lang="ts">
|
|
13
13
|
import type { QuillFieldSchema } from '@quillmark/wasm';
|
|
14
|
-
import { controlKind,
|
|
14
|
+
import { controlKind, humanize } from './structure.js';
|
|
15
15
|
import TextField from './TextField.svelte';
|
|
16
16
|
import EnumField from './EnumField.svelte';
|
|
17
17
|
import NumberField from './NumberField.svelte';
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
<EnumField
|
|
80
80
|
label={propLabel}
|
|
81
81
|
value={obj[key] as string | undefined}
|
|
82
|
-
values={
|
|
82
|
+
values={sub.values ?? []}
|
|
83
83
|
fallback={sub.default as string | undefined}
|
|
84
84
|
onCommit={(v) => commitProp(key, v)}
|
|
85
85
|
/>
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
}
|
|
145
145
|
.qm-unsupported {
|
|
146
146
|
font-size: var(--_qm-text-body);
|
|
147
|
-
color: var(--_qm-ink-
|
|
147
|
+
color: var(--_qm-ink-label);
|
|
148
148
|
font-style: italic;
|
|
149
149
|
}
|
|
150
150
|
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
One element of an array-of-`richtext` field (fixture `
|
|
2
|
+
One element of an array-of-`richtext` field (fixture `keywords`). Array
|
|
3
3
|
elements are not `applyChange`-addressable: `Addr.field` is a flat name, so
|
|
4
|
-
`
|
|
4
|
+
`keywords[0]` has no op address. So
|
|
5
5
|
this is not a `createField` leaf: it mounts a minimal PM view over the codec's
|
|
6
6
|
decode/encode + inline schema, and on every edit hands the re-encoded
|
|
7
7
|
`Content` up to the parent {@link ArrayField}, which commits the whole array
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
|
|
20
20
|
interface Props {
|
|
21
21
|
value: Content;
|
|
22
|
-
plaintext?: boolean;
|
|
23
22
|
/** Accessible name for the editable region (the array has no per-element label). */
|
|
24
23
|
label?: string;
|
|
25
24
|
onChange: (rt: Content) => void;
|
|
@@ -28,7 +27,7 @@
|
|
|
28
27
|
* keymap, so the state it reads is the one this keystroke has yet to change. */
|
|
29
28
|
onKey?: (e: KeyboardEvent) => void;
|
|
30
29
|
}
|
|
31
|
-
let { value,
|
|
30
|
+
let { value, label, onChange, onKey }: Props = $props();
|
|
32
31
|
|
|
33
32
|
let containerEl: HTMLDivElement | undefined = $state();
|
|
34
33
|
let view: EditorView | undefined;
|
|
@@ -45,10 +44,10 @@
|
|
|
45
44
|
// plugin stack a `createField` leaf mounts (shared `proseLeafPlugins`), minus
|
|
46
45
|
// the anchor-position plugin (element anchors are dropped on the array's value
|
|
47
46
|
// write, per the header). Its own `dispatchTransaction` hands `Content` up.
|
|
48
|
-
const pmDoc = decode(value, inlineSchema
|
|
47
|
+
const pmDoc = decode(value, inlineSchema);
|
|
49
48
|
const state = EditorState.create({
|
|
50
49
|
doc: pmDoc,
|
|
51
|
-
plugins: proseLeafPlugins(inlineSchema, { inline: true
|
|
50
|
+
plugins: proseLeafPlugins(inlineSchema, { inline: true })
|
|
52
51
|
});
|
|
53
52
|
const mounted = new EditorView(containerEl, {
|
|
54
53
|
state,
|
|
@@ -2,7 +2,6 @@ import './controls.css';
|
|
|
2
2
|
import type { Content } from '@quillmark/wasm';
|
|
3
3
|
interface Props {
|
|
4
4
|
value: Content;
|
|
5
|
-
plaintext?: boolean;
|
|
6
5
|
/** Accessible name for the editable region (the array has no per-element label). */
|
|
7
6
|
label?: string;
|
|
8
7
|
onChange: (rt: Content) => void;
|
|
@@ -179,11 +179,13 @@
|
|
|
179
179
|
.qm-prose :global(.ProseMirror) {
|
|
180
180
|
outline: none;
|
|
181
181
|
}
|
|
182
|
-
/* Empty-leaf ghost, at the scalar
|
|
183
|
-
stays out of the document; `float`/`height: 0` keep it from
|
|
182
|
+
/* Empty-leaf ghost, at the rung every scalar's placeholder takes: a node decoration's
|
|
183
|
+
data attr, so it stays out of the document; `float`/`height: 0` keep it from
|
|
184
|
+
displacing the caret. The italic is what a leaf with no written neighbour has
|
|
185
|
+
instead of the step to `ink`. */
|
|
184
186
|
.qm-prose :global(.ProseMirror .qm-prose-placeholder::before) {
|
|
185
187
|
content: attr(data-placeholder);
|
|
186
|
-
color: var(--_qm-ink-
|
|
188
|
+
color: var(--_qm-ink-label);
|
|
187
189
|
font-style: italic;
|
|
188
190
|
float: left;
|
|
189
191
|
height: 0;
|
|
@@ -736,12 +736,11 @@
|
|
|
736
736
|
* diagnostics take.
|
|
737
737
|
*
|
|
738
738
|
* Two rungs, because the boundary mints addresses at a finer granularity than
|
|
739
|
-
* `Addr` can name: a `richtext[]` element surfaces as `main.
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
* `pos` is an offset into the element's own content.
|
|
739
|
+
* `Addr` can name: a `richtext[]` element surfaces as `main.keywords[0]`. The
|
|
740
|
+
* second rung reads that trailing index segment under a field the schema declares
|
|
741
|
+
* an array — which is why the ladder is the editor's (VISUAL_EDITOR.md §Surface):
|
|
742
|
+
* the preview carries no schema, and truncating the address there is worse than
|
|
743
|
+
* guessing, since `pos` is an offset into the element's own content.
|
|
745
744
|
*/
|
|
746
745
|
function leafTargetFor(field: DocPath): LeafTarget | undefined {
|
|
747
746
|
const direct = addrForFieldPath(field);
|
package/dist/visual/controls.css
CHANGED
|
@@ -106,12 +106,13 @@
|
|
|
106
106
|
width: 100%;
|
|
107
107
|
box-sizing: border-box;
|
|
108
108
|
}
|
|
109
|
-
/* The native placeholder is the
|
|
110
|
-
number control's placeholder is the resolved `default:` (Field's `defaultStr`)
|
|
111
|
-
and reads
|
|
112
|
-
|
|
109
|
+
/* The native placeholder is the label rung, not the UA's own grey: a text or
|
|
110
|
+
number control's placeholder is the resolved `default:` (Field's `defaultStr`),
|
|
111
|
+
which is content and reads at a content's contrast. What says shown-never-written
|
|
112
|
+
is the step from the `ink` a written value takes, the same step the prose leaf's
|
|
113
|
+
empty state reads (`ProseField`). */
|
|
113
114
|
.qm-input::placeholder {
|
|
114
|
-
color: var(--_qm-ink-
|
|
115
|
+
color: var(--_qm-ink-label);
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
/* The second recipe, and it is not the box above: a field is a box because you
|
|
@@ -242,13 +243,15 @@
|
|
|
242
243
|
|
|
243
244
|
A floating surface is the one place a hairline is still load-bearing: it hangs
|
|
244
245
|
over content the surface did not choose, so there is no plane under it for a
|
|
245
|
-
tone to step off (ARCHITECTURE §Styling).
|
|
246
|
-
|
|
246
|
+
tone to step off (ARCHITECTURE §Styling). The edge and `--_qm-lift` carry it
|
|
247
|
+
there, and the raised rung joins them in dark, where a tone still has somewhere
|
|
248
|
+
to go. */
|
|
247
249
|
.qm-menu-surface {
|
|
248
250
|
padding: var(--_qm-space-half);
|
|
249
251
|
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
250
252
|
border-radius: var(--_qm-radius);
|
|
251
253
|
background: var(--_qm-surface-raised);
|
|
254
|
+
box-shadow: var(--_qm-lift);
|
|
252
255
|
font-size: var(--_qm-text-body);
|
|
253
256
|
color: var(--_qm-ink);
|
|
254
257
|
}
|
|
@@ -291,6 +294,7 @@
|
|
|
291
294
|
-webkit-backdrop-filter: blur(var(--_qm-blur));
|
|
292
295
|
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
293
296
|
border-radius: var(--_qm-radius);
|
|
297
|
+
box-shadow: var(--_qm-lift);
|
|
294
298
|
animation: qm-pop-in var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
295
299
|
}
|
|
296
300
|
.qm-popover-surface[data-ending-style] {
|
|
@@ -150,8 +150,6 @@ export declare function resolveBodyGhost(resolvedDefault: string | undefined, cu
|
|
|
150
150
|
* An array's element control is this over `items`: a missing `items` is a text
|
|
151
151
|
* element. */
|
|
152
152
|
export declare function controlKind(f: QuillFieldSchema): ControlKind;
|
|
153
|
-
/** The closed value domain of an enum/enum-string field, or undefined. */
|
|
154
|
-
export declare function enumValues(f: QuillFieldSchema): string[] | undefined;
|
|
155
153
|
/** `foo_bar` → `Foo bar`: the label fallback when a field declares no `ui.title`. */
|
|
156
154
|
export declare function humanize(name: string): string;
|
|
157
155
|
/** Project a card schema's `fields` map (declaration = key order) into models. */
|