@quillmark/svelte 0.1.1 → 0.3.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/README.md +16 -12
- package/THEMING.md +48 -25
- package/dist/core/address.d.ts +42 -7
- package/dist/core/address.js +49 -21
- package/dist/core/bloom.d.ts +5 -5
- package/dist/core/bloom.js +8 -8
- package/dist/core/codec/anchor.d.ts +1 -1
- package/dist/core/codec/anchor.js +1 -1
- package/dist/core/codec/atoms.d.ts +10 -0
- package/dist/core/codec/atoms.js +78 -0
- package/dist/core/codec/clipboard.d.ts +59 -0
- package/dist/core/codec/clipboard.js +202 -0
- package/dist/core/codec/code.d.ts +1 -1
- package/dist/core/codec/code.js +4 -4
- package/dist/core/codec/decode.d.ts +1 -1
- package/dist/core/codec/decode.js +8 -8
- package/dist/core/codec/encode.d.ts +4 -4
- package/dist/core/codec/encode.js +14 -11
- package/dist/core/codec/field.d.ts +22 -27
- package/dist/core/codec/field.js +33 -32
- package/dist/core/codec/index.js +5 -8
- package/dist/core/codec/inputrules.js +56 -18
- package/dist/core/codec/islands.d.ts +11 -7
- package/dist/core/codec/islands.js +16 -10
- package/dist/core/codec/keymap.d.ts +5 -6
- package/dist/core/codec/keymap.js +8 -7
- package/dist/core/codec/lists.d.ts +3 -3
- package/dist/core/codec/lists.js +32 -13
- package/dist/core/codec/marks.d.ts +1 -1
- package/dist/core/codec/marks.js +1 -1
- package/dist/core/codec/positions.js +1 -1
- package/dist/core/codec/prose.css +266 -228
- package/dist/core/codec/schema.js +4 -4
- package/dist/core/codec/slash.d.ts +39 -41
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.d.ts +12 -16
- package/dist/core/codec/table-view.js +488 -292
- package/dist/core/codec/table.d.ts +42 -15
- package/dist/core/codec/table.js +73 -27
- package/dist/core/errors.d.ts +17 -7
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +6 -5
- package/dist/core/lifecycle.d.ts +21 -5
- package/dist/core/lifecycle.js +34 -8
- package/dist/core/rebind.svelte.d.ts +18 -4
- package/dist/core/rebind.svelte.js +30 -12
- package/dist/core/teardown.d.ts +1 -1
- package/dist/core/teardown.js +1 -1
- package/dist/core/theme.css +173 -58
- package/dist/preset/index.css +1 -1
- package/dist/preset/recipes.css +79 -30
- package/dist/preset/scale.css +40 -28
- package/dist/preview/Preview.svelte +22 -19
- package/dist/preview/Preview.svelte.d.ts +8 -8
- package/dist/preview/bridge.d.ts +6 -4
- package/dist/preview/bridge.js +19 -9
- package/dist/preview/controller.d.ts +13 -6
- package/dist/preview/controller.js +8 -5
- package/dist/preview/geometry.d.ts +17 -2
- package/dist/preview/geometry.js +21 -1
- package/dist/preview/index.d.ts +1 -0
- package/dist/preview/index.js +9 -2
- package/dist/preview/overlay.d.ts +2 -1
- package/dist/preview/overlay.js +26 -11
- package/dist/preview/paint.js +19 -17
- package/dist/visual/ArrayField.svelte +103 -83
- package/dist/visual/ArrayField.svelte.d.ts +5 -4
- package/dist/visual/BooleanField.svelte +8 -6
- package/dist/visual/BooleanField.svelte.d.ts +2 -2
- package/dist/visual/Card.svelte +161 -333
- package/dist/visual/Card.svelte.d.ts +5 -4
- package/dist/visual/CardControls.svelte +1 -1
- package/dist/visual/DateField.svelte +19 -19
- package/dist/visual/DateField.svelte.d.ts +3 -3
- package/dist/visual/DiagnosticList.svelte +2 -2
- package/dist/visual/EnumField.svelte +48 -28
- package/dist/visual/EnumField.svelte.d.ts +5 -4
- package/dist/visual/Field.svelte +207 -132
- package/dist/visual/Field.svelte.d.ts +11 -7
- package/dist/visual/FieldHint.svelte +79 -48
- package/dist/visual/FieldHint.svelte.d.ts +1 -1
- package/dist/visual/FieldLabel.svelte +8 -8
- package/dist/visual/FormatPopover.svelte +82 -35
- package/dist/visual/NumberField.svelte +5 -5
- package/dist/visual/NumberField.svelte.d.ts +1 -1
- package/dist/visual/ObjectField.svelte +28 -9
- package/dist/visual/ObjectField.svelte.d.ts +4 -2
- package/dist/visual/ProseArrayElement.svelte +12 -19
- package/dist/visual/ProseArrayElement.svelte.d.ts +1 -2
- package/dist/visual/ProseField.svelte +62 -85
- package/dist/visual/ProseField.svelte.d.ts +14 -17
- package/dist/visual/SlashMenu.svelte +42 -16
- package/dist/visual/TextField.svelte +5 -5
- package/dist/visual/TextField.svelte.d.ts +1 -1
- package/dist/visual/TipsCard.svelte +43 -28
- package/dist/visual/VisualEditor.svelte +18 -19
- package/dist/visual/VisualEditor.svelte.d.ts +2 -3
- package/dist/visual/VisualEditorInner.svelte +251 -184
- package/dist/visual/VisualEditorInner.svelte.d.ts +2 -3
- package/dist/visual/controls.css +85 -38
- package/dist/visual/diagnostics.d.ts +13 -21
- package/dist/visual/diagnostics.js +7 -19
- package/dist/visual/domid.js +3 -3
- package/dist/visual/ext.d.ts +1 -1
- package/dist/visual/ext.js +1 -1
- package/dist/visual/icons/nodes.d.ts +3 -0
- package/dist/visual/icons/nodes.js +2 -1
- package/dist/visual/index.d.ts +1 -0
- package/dist/visual/index.js +10 -3
- package/dist/visual/leaves.d.ts +47 -0
- package/dist/visual/leaves.js +31 -0
- package/dist/visual/links.d.ts +24 -0
- package/dist/visual/links.js +67 -0
- package/dist/visual/props.d.ts +33 -12
- package/dist/visual/signals.d.ts +6 -6
- package/dist/visual/strings.d.ts +14 -19
- package/dist/visual/strings.js +5 -7
- package/dist/visual/structure.d.ts +24 -17
- package/dist/visual/structure.js +8 -8
- package/dist/visual/synced.svelte.d.ts +1 -1
- package/dist/visual/synced.svelte.js +4 -4
- package/dist/visual/tips.d.ts +4 -2
- package/dist/visual/tips.js +7 -4
- package/package.json +2 -2
package/dist/visual/Card.svelte
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
One card block: the header (composable cards only
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
takes a parent-built LIVE address so a card reorder re-targets its commits
|
|
7
|
-
without a remount.
|
|
2
|
+
One card block: the header (composable cards only), the field list grouped by
|
|
3
|
+
`ui.group`/`ui.compact`, and the body prose leaf. `main` is headerless with no
|
|
4
|
+
controls. Every prose leaf takes a parent-built live address, so a card reorder
|
|
5
|
+
re-targets its commits without a remount.
|
|
8
6
|
-->
|
|
9
7
|
<script lang="ts">
|
|
10
8
|
import { wording } from './strings.js';
|
|
11
9
|
|
|
12
|
-
//
|
|
13
|
-
//
|
|
10
|
+
// Ambient from the editor root, with the package's English off-tree, so this
|
|
11
|
+
// component renders standalone too.
|
|
14
12
|
const t = wording();
|
|
15
13
|
import { untrack } from 'svelte';
|
|
16
14
|
import Icon from './icons/Icon.svelte';
|
|
17
15
|
import type { Document, Quill, Addr, Diagnostic } from '@quillmark/wasm';
|
|
18
16
|
import type { EditorErrorHandler } from '../core/errors.js';
|
|
19
|
-
import type {
|
|
17
|
+
import type { LeafRegistry } from './leaves.js';
|
|
20
18
|
import type { CardModel, FieldModel } from './structure.js';
|
|
21
19
|
import { placeFields, humanize, initialExpandedGroup } from './structure.js';
|
|
22
20
|
import type { FieldDomIds } from './domid.js';
|
|
@@ -44,19 +42,20 @@
|
|
|
44
42
|
interface Props {
|
|
45
43
|
card: CardModel;
|
|
46
44
|
doc: Document;
|
|
47
|
-
/** The schema a prose leaf reads its content through (`ProseField`). */
|
|
48
45
|
quill: Quill;
|
|
49
46
|
index: number;
|
|
50
47
|
isFirst: boolean;
|
|
51
48
|
isLast: boolean;
|
|
52
49
|
kinds: string[];
|
|
53
50
|
ops: CardOps;
|
|
51
|
+
enumDisallowed?: 'hide' | 'disable';
|
|
54
52
|
onFocus?: (addr: Addr) => void;
|
|
55
53
|
onCaretMove?: (addr: Addr, pos: number) => void;
|
|
56
54
|
onChange?: (addr: Addr) => void;
|
|
57
55
|
onError?: EditorErrorHandler;
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
/** Every field in this card registers its landing handle here, which is what the
|
|
57
|
+
* editor's `focusField`/`setCaret` resolve a `DocPath` to. */
|
|
58
|
+
leaves?: LeafRegistry;
|
|
60
59
|
}
|
|
61
60
|
let {
|
|
62
61
|
card,
|
|
@@ -67,12 +66,12 @@
|
|
|
67
66
|
isLast,
|
|
68
67
|
kinds,
|
|
69
68
|
ops,
|
|
69
|
+
enumDisallowed,
|
|
70
70
|
onFocus,
|
|
71
71
|
onCaretMove,
|
|
72
72
|
onChange,
|
|
73
73
|
onError,
|
|
74
|
-
|
|
75
|
-
unregister
|
|
74
|
+
leaves
|
|
76
75
|
}: Props = $props();
|
|
77
76
|
|
|
78
77
|
// Local title reconcile (external change only), like the scalar controls.
|
|
@@ -85,18 +84,15 @@
|
|
|
85
84
|
});
|
|
86
85
|
});
|
|
87
86
|
|
|
88
|
-
//
|
|
89
|
-
// text you replace, and Enter/Escape as commit/revert. Rename stays LIVE on
|
|
90
|
-
// input: `titleAtFocus` is the pre-edit value Escape rolls back to.
|
|
87
|
+
// The pre-edit value Escape rolls back to; the rename itself stays live on input.
|
|
91
88
|
let titleAtFocus = '';
|
|
92
89
|
function onTitleFocus(e: FocusEvent): void {
|
|
93
90
|
titleAtFocus = localTitle;
|
|
94
91
|
(e.currentTarget as HTMLInputElement).select();
|
|
95
92
|
}
|
|
96
|
-
// A
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
// clicking mid-title to place the caret still works.
|
|
93
|
+
// A press on an unfocused title would place a caret on mouseup, collapsing the
|
|
94
|
+
// focus-time select-all. Take focus manually and suppress that caret; a press while
|
|
95
|
+
// already focused stays normal, so clicking mid-title to place the caret still works.
|
|
100
96
|
function onTitleMousedown(e: MouseEvent): void {
|
|
101
97
|
const el = e.currentTarget as HTMLInputElement;
|
|
102
98
|
if (document.activeElement !== el) {
|
|
@@ -104,11 +100,9 @@
|
|
|
104
100
|
el.focus();
|
|
105
101
|
}
|
|
106
102
|
}
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
// from starting a text selection on the wrapper. A press ON the input is left to
|
|
111
|
-
// `onTitleMousedown`, which owns the already-focused caret-placement case.
|
|
103
|
+
// A press on the region's empty width enters the title edit; preventDefault keeps it
|
|
104
|
+
// from starting a text selection on the wrapper. A press on the input itself is left
|
|
105
|
+
// to `onTitleMousedown`, which owns the already-focused caret-placement case.
|
|
112
106
|
function onRenameMousedown(e: MouseEvent): void {
|
|
113
107
|
const input = (e.currentTarget as HTMLElement).querySelector('input');
|
|
114
108
|
if (!input || e.target === input) return;
|
|
@@ -130,36 +124,32 @@
|
|
|
130
124
|
}
|
|
131
125
|
}
|
|
132
126
|
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
/** Not `--_qm-glyph-control`: that rung centres a glyph in a tap target, where this
|
|
128
|
+
* one stands in a run of text at the body rung and is sized to sit level with it. */
|
|
129
|
+
const CHEVRON = 16;
|
|
130
|
+
|
|
135
131
|
const ungrouped = $derived(card.sections.filter((s) => s.group == null));
|
|
136
132
|
const grouped = $derived(card.sections.filter((s) => s.group != null));
|
|
137
133
|
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
// horizontals land on one y and paint as a stroke of twice the width, which is the
|
|
142
|
-
// one thing a figure claiming one width cannot do.
|
|
134
|
+
// A schema that declares no fields still gets a body, and a bracket around nothing is
|
|
135
|
+
// worse than none: with no content between them the two horizontals land on one y and
|
|
136
|
+
// paint as a single stroke of twice the width.
|
|
143
137
|
const hasMeta = $derived(ungrouped.length > 0 || grouped.length > 0);
|
|
144
138
|
|
|
145
|
-
// Ephemeral session state
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
// and
|
|
149
|
-
// a retype is the one reshape, and the open group stays where it still exists.
|
|
139
|
+
// Ephemeral session state, seeded once from the card's shape. Card is keyed by stable
|
|
140
|
+
// id, so this survives the VisualEditor re-derive that reassigns `card` and resets
|
|
141
|
+
// only on a remount. Not reconciled to later section changes: a retype is the one
|
|
142
|
+
// reshape, and the open group stays where it still exists.
|
|
150
143
|
// svelte-ignore state_referenced_locally
|
|
151
144
|
let expanded = $state<string | null>(initialExpandedGroup(card.sections));
|
|
152
|
-
// The accordion's two elements per group, by group id. A closing panel goes
|
|
153
|
-
// `inert` with whatever it held still focused, and `inert` does not say where
|
|
154
|
-
// focus should go: the browser drops it on the body, losing the user's place in
|
|
155
|
-
// the card. So the close hands focus to the header, which is what reopens the
|
|
156
|
-
// section.
|
|
157
145
|
let headers = $state<Record<string, HTMLButtonElement | undefined>>({});
|
|
158
146
|
let panels = $state<Record<string, HTMLElement | undefined>>({});
|
|
159
147
|
/**
|
|
160
|
-
* Move to `next` (`null` = all closed)
|
|
161
|
-
*
|
|
162
|
-
*
|
|
148
|
+
* Move to `next` (`null` = all closed). A closing panel goes `inert` with whatever it
|
|
149
|
+
* held still focused, and `inert` does not say where focus should go — the browser
|
|
150
|
+
* drops it on the body — so the close hands focus to the header, which is what
|
|
151
|
+
* reopens the section. Every write to `expanded` goes through here: a group also
|
|
152
|
+
* closes when another opens.
|
|
163
153
|
*/
|
|
164
154
|
function setExpanded(next: string | null): void {
|
|
165
155
|
const closing = expanded;
|
|
@@ -172,13 +162,10 @@
|
|
|
172
162
|
}
|
|
173
163
|
|
|
174
164
|
/**
|
|
175
|
-
* Open the group holding leaf `key`, if this card has one
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* landing: `inert` clears when the panel renders open, not when `expanded` is
|
|
180
|
-
* assigned. The card keeps owning `expanded`, and a card that does not hold the
|
|
181
|
-
* key does nothing.
|
|
165
|
+
* Open the group holding leaf `key`, if this card has one: a caret placed in a
|
|
166
|
+
* collapsed panel does not land at all. `VisualEditor.setCaret` calls this on every
|
|
167
|
+
* card and then waits a flush before landing, since `inert` clears when the panel
|
|
168
|
+
* renders open, not when `expanded` is assigned.
|
|
182
169
|
*
|
|
183
170
|
* A call, not a prop: a reveal is an event, and modelling it as state needs a
|
|
184
171
|
* fresh-identity wrapper to re-fire and an `untrack` to keep the per-keystroke
|
|
@@ -191,13 +178,10 @@
|
|
|
191
178
|
|
|
192
179
|
let el = $state<HTMLElement | undefined>(undefined);
|
|
193
180
|
/**
|
|
194
|
-
* Bring this card into view after the structure mutation that placed it:
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* The reduced-motion guard is JS rather than the CSS the accordion uses: a
|
|
200
|
-
* scroll's behaviour is an argument, not a transition a media query can cancel.
|
|
181
|
+
* Bring this card into view after the structure mutation that placed it: `center` for
|
|
182
|
+
* an insert, `nearest` for a reorder. The reduced-motion guard is JS rather than the
|
|
183
|
+
* CSS the accordion uses: a scroll's behaviour is an argument, not a transition a
|
|
184
|
+
* media query can cancel.
|
|
201
185
|
*/
|
|
202
186
|
export function scrollIntoViewCard(block: ScrollLogicalPosition): void {
|
|
203
187
|
const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
@@ -205,9 +189,6 @@
|
|
|
205
189
|
}
|
|
206
190
|
</script>
|
|
207
191
|
|
|
208
|
-
<!-- `qm-main` is a structural marker and carries no fill: a tone here would bet on
|
|
209
|
-
what the consumer put behind the column, and structure already says which card
|
|
210
|
-
this is. -->
|
|
211
192
|
<section
|
|
212
193
|
bind:this={el}
|
|
213
194
|
class="qm-card"
|
|
@@ -219,20 +200,9 @@
|
|
|
219
200
|
{:else}
|
|
220
201
|
{#if !card.isMain}
|
|
221
202
|
<header class="qm-card-header">
|
|
222
|
-
<!-- The rename target is the header's whole free width, not the title's text
|
|
223
|
-
box. The autosize keeps that box exactly as wide as its
|
|
224
|
-
text; this wrapper makes the rest of the row enter the edit too. -->
|
|
225
203
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
226
204
|
<div class="qm-card-rename" onmousedown={onRenameMousedown}>
|
|
227
|
-
<!-- Autosize: the sizer span's ::after mirrors the text and dictates the grid
|
|
228
|
-
cell width, so the overlaid input grows with content and reads as text,
|
|
229
|
-
not a persistent box. `data-value` falls back to the
|
|
230
|
-
placeholder so an empty title still reserves its resolved-title width. -->
|
|
231
205
|
<span class="qm-card-title-sizer" data-value={localTitle || card.titlePlaceholder}>
|
|
232
|
-
<!-- `qm-focus-ring` is what the box used to do: it was this control's ONLY
|
|
233
|
-
focus indicator, and the ink step that replaced it cannot be one, since
|
|
234
|
-
hover already takes that step. `:focus-visible`, so a press enters the
|
|
235
|
-
edit unringed while a tab into it draws one. -->
|
|
236
206
|
<input
|
|
237
207
|
class="qm-card-title qm-focus-ring"
|
|
238
208
|
value={localTitle}
|
|
@@ -262,45 +232,26 @@
|
|
|
262
232
|
{/if}
|
|
263
233
|
|
|
264
234
|
<div class="qm-card-body">
|
|
265
|
-
<!--
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
gap instead would stand clear of where that vertical stops, and the corner
|
|
271
|
-
it exists to close would miss. It also survives the shapes the accordion
|
|
272
|
-
does not have: a card with no groups still terminates above its body,
|
|
273
|
-
because the rule belongs to the fields, not to the sections.
|
|
274
|
-
|
|
275
|
-
Rendered only when there ARE fields: the bracket is a statement about a
|
|
276
|
-
block, so with no block there is nothing to say, and an empty wrapper
|
|
277
|
-
between two rules is what collapses them onto each other. -->
|
|
235
|
+
<!-- Every field the card has, ungrouped and grouped alike, inside one element
|
|
236
|
+
whose top and bottom edges are the bracket's two horizontals. Anchored here
|
|
237
|
+
rather than on the header and on the accordion so an open section's vertical
|
|
238
|
+
reaches them, and so a card with no groups still terminates above its body. An
|
|
239
|
+
empty wrapper between the two rules would collapse them onto each other. -->
|
|
278
240
|
{#if hasMeta}
|
|
279
241
|
<div
|
|
280
242
|
class="qm-card-meta"
|
|
281
243
|
class:qm-meta-top={!card.isMain}
|
|
282
244
|
class:qm-meta-bottom={card.hasBody}
|
|
283
245
|
>
|
|
284
|
-
<!-- Ungrouped fields render above the accordion, always visible:
|
|
285
|
-
a label-less section has no header to toggle, and these read as the card's
|
|
286
|
-
primary fields. -->
|
|
287
246
|
{#each ungrouped as section (section.group ?? '_ungrouped')}
|
|
288
247
|
<div class="qm-section">
|
|
289
248
|
{@render sectionFields(section.fields)}
|
|
290
249
|
</div>
|
|
291
250
|
{/each}
|
|
292
251
|
|
|
293
|
-
<!--
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
with no gap of its own: each header's symmetric padding is the whole
|
|
297
|
-
inter-group rhythm. The card body's gap still separates this
|
|
298
|
-
block from the ungrouped fields and the body leaf.
|
|
299
|
-
|
|
300
|
-
Rendered only when a card HAS groups, so an empty accordion is not the
|
|
301
|
-
bracket's last child: the bottom rule's inset belongs to whichever block
|
|
302
|
-
actually abuts it, and a zero-height wrapper standing there would take it
|
|
303
|
-
from the ungrouped fields that do. -->
|
|
252
|
+
<!-- One open at a time. Rendered only when a card has groups, so an empty
|
|
253
|
+
accordion is not the bracket's last child: the bottom rule's inset belongs
|
|
254
|
+
to whichever block abuts it. -->
|
|
304
255
|
{#if grouped.length}
|
|
305
256
|
<div class="qm-groups">
|
|
306
257
|
{#each grouped as section (section.group)}
|
|
@@ -316,13 +267,15 @@
|
|
|
316
267
|
aria-controls={panelId}
|
|
317
268
|
onclick={() => toggleGroup(group)}
|
|
318
269
|
>
|
|
319
|
-
<Icon
|
|
270
|
+
<Icon
|
|
271
|
+
name="chevron-right"
|
|
272
|
+
class="qm-group-chevron"
|
|
273
|
+
size={CHEVRON}
|
|
274
|
+
/>{section.label}
|
|
320
275
|
</button>
|
|
321
|
-
<!--
|
|
322
|
-
unmounted, so without it every field
|
|
276
|
+
<!-- The panel is clipped, not unmounted, so without `inert` every field
|
|
323
277
|
in a hidden section keeps its place in the tab order and in the a11y
|
|
324
|
-
tree, under a header announcing `aria-expanded="false"`.
|
|
325
|
-
untouched, so the 0fr↔1fr slide is the same slide. -->
|
|
278
|
+
tree, under a header announcing `aria-expanded="false"`. -->
|
|
326
279
|
<div
|
|
327
280
|
class="qm-group-panel"
|
|
328
281
|
id={panelId}
|
|
@@ -340,13 +293,9 @@
|
|
|
340
293
|
</div>
|
|
341
294
|
{/if}
|
|
342
295
|
|
|
343
|
-
<!--
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
is still announced. Unframed is the point rather than a saving: the body is
|
|
347
|
-
the ONLY surface in the card without an edge, which is what makes it read
|
|
348
|
-
as paper instead of as one more field, and the bracket's bottom rule
|
|
349
|
-
already does the separating a box was doing. -->
|
|
296
|
+
<!-- Unframed: the body is the only surface at the card's own tone, where every
|
|
297
|
+
other typed value sits in a well cut into it, and that is what makes it read as
|
|
298
|
+
paper instead of as one more field. -->
|
|
350
299
|
{#if card.hasBody}
|
|
351
300
|
<div class="qm-body-leaf">
|
|
352
301
|
<ProseField
|
|
@@ -361,8 +310,7 @@
|
|
|
361
310
|
{onCaretMove}
|
|
362
311
|
{onChange}
|
|
363
312
|
{onError}
|
|
364
|
-
{
|
|
365
|
-
{unregister}
|
|
313
|
+
{leaves}
|
|
366
314
|
/>
|
|
367
315
|
<DiagnosticList diagnostics={ops.diagFor(undefined)} />
|
|
368
316
|
</div>
|
|
@@ -371,10 +319,6 @@
|
|
|
371
319
|
{/if}
|
|
372
320
|
</section>
|
|
373
321
|
|
|
374
|
-
<!-- A section's fields as ONE grid: shared by the ungrouped block and the accordion
|
|
375
|
-
panels so both render a group's fields identically. Rows are the
|
|
376
|
-
grid's business: fields carry a span and auto-place, so nothing here re-derives
|
|
377
|
-
structure on resize and a prose leaf's key never moves. -->
|
|
378
322
|
{#snippet sectionFields(fields: FieldModel[])}
|
|
379
323
|
<div class="qm-fields">
|
|
380
324
|
{#each placeFields(fields) as { field: f, span } (f.name)}
|
|
@@ -385,29 +329,27 @@
|
|
|
385
329
|
provenance={card.provenance[f.name]}
|
|
386
330
|
{doc}
|
|
387
331
|
{quill}
|
|
388
|
-
|
|
332
|
+
addr={ops.makeAddr(f.name)}
|
|
389
333
|
leafKey={ops.leafKey(f.name)}
|
|
390
334
|
domIds={ops.domIds(f.name)}
|
|
391
335
|
onCommitScalar={(v) => ops.commit(f.name, v)}
|
|
392
336
|
optionAllowed={(v) => ops.enumAllowed(f.name, v)}
|
|
337
|
+
{enumDisallowed}
|
|
393
338
|
{onFocus}
|
|
394
339
|
{onCaretMove}
|
|
395
340
|
{onChange}
|
|
396
341
|
{onError}
|
|
397
|
-
{
|
|
398
|
-
{unregister}
|
|
342
|
+
{leaves}
|
|
399
343
|
diagnostics={ops.diagFor(f.name)}
|
|
400
344
|
/>
|
|
401
345
|
{/each}
|
|
402
346
|
</div>
|
|
403
347
|
{/snippet}
|
|
404
348
|
|
|
405
|
-
<!--
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
derive: `setCardKind` swaps the kind and leaves payload and body untouched, so
|
|
410
|
-
no field is dropped; with no kinds to offer, delete is the only exit. -->
|
|
349
|
+
<!-- A card whose `kind` has no schema stays visible and removable, its fields, body and
|
|
350
|
+
`$ext` intact in the Document, so it is never a data trap. `setCardKind` swaps the kind
|
|
351
|
+
and leaves payload and body untouched, so retyping drops no field; with no kinds to
|
|
352
|
+
offer, delete is the only exit. -->
|
|
411
353
|
{#snippet recoveryShell()}
|
|
412
354
|
<header class="qm-card-header">
|
|
413
355
|
<span class="qm-card-title-static">{humanize(card.kind)}</span>
|
|
@@ -446,8 +388,6 @@
|
|
|
446
388
|
</div>
|
|
447
389
|
{/snippet}
|
|
448
390
|
|
|
449
|
-
<!-- The declared card kinds as `<option>`s for the recovery shell's retype,
|
|
450
|
-
the one place a card's kind changes after insert. -->
|
|
451
391
|
{#snippet kindOptions()}
|
|
452
392
|
{#each kinds as k (k)}
|
|
453
393
|
<option value={k}>{humanize(k)}</option>
|
|
@@ -455,16 +395,14 @@
|
|
|
455
395
|
{/snippet}
|
|
456
396
|
|
|
457
397
|
<style>
|
|
398
|
+
/* An island, and the tone is the whole of what makes it one: the card is the base
|
|
399
|
+
plane and the column behind it is the sunken one (ARCHITECTURE §Styling). The inset
|
|
400
|
+
is the same rung as the gap, so what separates the header from the fields is what
|
|
401
|
+
separates the fields from the card's edge. */
|
|
458
402
|
.qm-card {
|
|
459
|
-
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
460
403
|
border-radius: var(--_qm-radius);
|
|
461
|
-
/* Uniform inset on every side: a body-shown and a
|
|
462
|
-
body-hidden card stay symmetric, every left edge on one gutter. The SAME rung
|
|
463
|
-
as the gap between the card's stacked regions below: the card's inset and its
|
|
464
|
-
internal rhythm are one number, so what separates the header from the fields is
|
|
465
|
-
what separates the fields from the card's edge. */
|
|
466
404
|
padding: var(--_qm-space-3);
|
|
467
|
-
background: var(--_qm-surface
|
|
405
|
+
background: var(--_qm-surface);
|
|
468
406
|
display: flex;
|
|
469
407
|
flex-direction: column;
|
|
470
408
|
gap: var(--_qm-space-3);
|
|
@@ -475,46 +413,25 @@
|
|
|
475
413
|
justify-content: space-between;
|
|
476
414
|
gap: var(--_qm-space-2);
|
|
477
415
|
}
|
|
478
|
-
/* The
|
|
479
|
-
card's gutter
|
|
480
|
-
|
|
481
|
-
difference between `--_qm-tap-min` and the glyph — which is a target, not a
|
|
482
|
-
rhythm, and so is exactly what a gutter should not be measured from. Both ends
|
|
483
|
-
therefore hang by the chrome they carry and by nothing else; the title's is now
|
|
484
|
-
zero, and this one is the arithmetic of two rungs rather than a rung of its own,
|
|
485
|
-
because the amount is off the space scale (5px at the shipped dials) and a scale
|
|
486
|
-
that could express it would be a scale with a step nothing else uses.
|
|
487
|
-
|
|
488
|
-
It is the fill, not the box, that overhangs the gutter here: an icon button rests
|
|
489
|
-
unboxed and fills only under the pointer, so the
|
|
490
|
-
thing crossing the line is a transient target wash, not an edge the card claims. */
|
|
416
|
+
/* The overhang is the tap target's own centring given back, so the glyphs sit on the
|
|
417
|
+
card's gutter rather than the targets around them. It is the fill that crosses the
|
|
418
|
+
line, not a box: an icon button rests unboxed and fills only under the pointer. */
|
|
491
419
|
.qm-card-header-right {
|
|
492
420
|
display: flex;
|
|
493
421
|
align-items: center;
|
|
494
422
|
gap: var(--_qm-space-2);
|
|
495
423
|
margin-right: calc(-1 * (var(--_qm-tap-min) - var(--_qm-glyph-control)) / 2);
|
|
496
424
|
}
|
|
497
|
-
/*
|
|
498
|
-
|
|
499
|
-
caret in any leaf, the title, or a chevron itself holds the reveal: hover
|
|
500
|
-
alone would strand the pair on keyboard and touch. This is the card's whole
|
|
501
|
-
active treatment; nothing marks the section itself. */
|
|
425
|
+
/* Focus as well as hover, so a caret in any leaf, the title, or a chevron holds the
|
|
426
|
+
reveal: hover alone would strand the pair on keyboard and touch. */
|
|
502
427
|
.qm-card:hover :global(.qm-card-reorder),
|
|
503
428
|
.qm-card:focus-within :global(.qm-card-reorder) {
|
|
504
429
|
opacity: 1;
|
|
505
430
|
}
|
|
506
|
-
/* The rename hit region
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
than the input's.
|
|
511
|
-
|
|
512
|
-
It hangs by NOTHING, which is the whole of what the title carrying no box buys:
|
|
513
|
-
the first character lands on the card's gutter with the field list, the
|
|
514
|
-
bracket's rules and the body's first character, because nothing stands between
|
|
515
|
-
the region's edge and the glyph. The stretch is also what
|
|
516
|
-
keeps the target legal with the padding gone: WCAG 2.5.8's floor is the ROW's,
|
|
517
|
-
which the controls hold at `--_qm-tap-min`, so the input never carried it. */
|
|
431
|
+
/* The rename hit region is the header's free width at full height, so a press
|
|
432
|
+
anywhere left of the controls enters the title edit. `align-self: stretch` beats the
|
|
433
|
+
header's centring, which is also what keeps the target legal with no padding: WCAG
|
|
434
|
+
2.5.8's floor is the row's, which the controls hold at `--_qm-tap-min`. */
|
|
518
435
|
.qm-card-rename {
|
|
519
436
|
flex: 1;
|
|
520
437
|
min-width: 0;
|
|
@@ -523,25 +440,19 @@
|
|
|
523
440
|
align-items: center;
|
|
524
441
|
cursor: text;
|
|
525
442
|
}
|
|
526
|
-
/* Autosize
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
header's space-between keeps clear of the controls. The input inherits the type
|
|
530
|
-
tokens (`font: inherit`, leading included) so the mirror and the input measure
|
|
531
|
-
alike; which is why the leading rung is declared here and not on the input. */
|
|
443
|
+
/* Autosize: the ::after mirrors the text into the same grid cell as the input, so the
|
|
444
|
+
overlaid input tracks its content width. Both carry the same nothing for a box model
|
|
445
|
+
and inherit the same type tokens, which is what makes the two measure alike. */
|
|
532
446
|
.qm-card-title-sizer {
|
|
533
447
|
display: inline-grid;
|
|
534
448
|
align-items: center;
|
|
535
449
|
min-width: 0;
|
|
536
450
|
max-width: 100%;
|
|
537
451
|
font-size: var(--_qm-text-title);
|
|
538
|
-
font-weight: var(--_qm-weight-
|
|
452
|
+
font-weight: var(--_qm-weight-mid);
|
|
539
453
|
line-height: var(--_qm-leading-tight);
|
|
540
454
|
font-family: inherit;
|
|
541
455
|
}
|
|
542
|
-
/* The mirror has no box model to match, because the input has none: with the
|
|
543
|
-
padding and the hairline gone from both, the two measure alike by carrying the
|
|
544
|
-
same nothing. */
|
|
545
456
|
.qm-card-title-sizer::after {
|
|
546
457
|
content: attr(data-value) ' ';
|
|
547
458
|
grid-area: 1 / 1;
|
|
@@ -549,28 +460,17 @@
|
|
|
549
460
|
white-space: pre;
|
|
550
461
|
min-width: 2ch;
|
|
551
462
|
}
|
|
552
|
-
/* The title draws
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
other control follows, where the box rests drawn and hover changes nothing, and
|
|
557
|
-
it filled to `--_qm-surface` — the page rung,
|
|
558
|
-
BELOW the card it sits on, so the one hover on this surface that receded instead
|
|
559
|
-
of lifting.
|
|
560
|
-
|
|
561
|
-
What replaces it is an ink step, the same cue the group header takes for the same
|
|
562
|
-
reason: a wide borderless target drawing no box has no other way to answer the
|
|
563
|
-
pointer. Rest is `--_qm-ink-label`, which is where the recovery shell's static
|
|
564
|
-
title already sits, so the editable title and its unschemable twin finally agree
|
|
565
|
-
on tone as well as on the three type rungs they share. */
|
|
463
|
+
/* The title draws no box in any state: a card title is already the one line that
|
|
464
|
+
reads as the card's name, and the region's `cursor: text` says the rest. The ink step
|
|
465
|
+
replaces it, the same cue the group header takes — a wide borderless target has no
|
|
466
|
+
other way to answer the pointer. */
|
|
566
467
|
.qm-card-title {
|
|
567
468
|
grid-area: 1 / 1;
|
|
568
469
|
width: 100%;
|
|
569
470
|
min-width: 0;
|
|
570
471
|
font: inherit;
|
|
571
|
-
/* `font: inherit` does not carry colour:
|
|
572
|
-
|
|
573
|
-
header's ink without this line. */
|
|
472
|
+
/* `font: inherit` does not carry colour: a form control defaults to the UA's own
|
|
473
|
+
text colour rather than the header's ink without this line. */
|
|
574
474
|
color: var(--_qm-ink-label);
|
|
575
475
|
border: none;
|
|
576
476
|
padding: 0;
|
|
@@ -581,122 +481,79 @@
|
|
|
581
481
|
.qm-card-title:focus {
|
|
582
482
|
color: var(--_qm-ink);
|
|
583
483
|
}
|
|
584
|
-
/* One rhythm for the card's stacked regions and for the metadata block inside it:
|
|
585
|
-
the gap between a field list and the body is the gap between two field lists. */
|
|
586
484
|
.qm-card-body,
|
|
587
485
|
.qm-card-meta {
|
|
588
486
|
display: flex;
|
|
589
487
|
flex-direction: column;
|
|
590
488
|
gap: var(--_qm-space-3);
|
|
591
489
|
}
|
|
592
|
-
/* The metadata bracket
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
one figure. That sameness is the whole effect; it is why the vertical takes
|
|
597
|
-
`--_qm-border` and not `--_qm-accent`, which would be ornament with the chevron
|
|
598
|
-
and the header's ink step already saying open.
|
|
490
|
+
/* The metadata bracket, in three strokes at one rung: these two horizontals and the
|
|
491
|
+
open section's vertical below. A bracket whose sides disagree on width or tone reads
|
|
492
|
+
as three unrelated lines rather than one figure, which is why the vertical takes
|
|
493
|
+
`--_qm-border` and not `--_qm-accent`.
|
|
599
494
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
sometimes two strokes and sometimes one: an open figure by construction, which
|
|
605
|
-
is what distinguishes it from a second box inside the card. */
|
|
495
|
+
Each condition is what its stroke means: a top rule is the line under a card title,
|
|
496
|
+
so the headerless `main` has none; a bottom rule divides fields from body, so a card
|
|
497
|
+
with no body has nothing to divide. Sometimes two strokes and sometimes one — an open
|
|
498
|
+
figure, which is what distinguishes it from a second box inside the card. */
|
|
606
499
|
.qm-meta-top {
|
|
607
500
|
border-top: var(--_qm-border-width) solid var(--_qm-border);
|
|
608
501
|
}
|
|
609
502
|
.qm-meta-bottom {
|
|
610
503
|
border-bottom: var(--_qm-border-width) solid var(--_qm-border);
|
|
611
504
|
}
|
|
612
|
-
/*
|
|
613
|
-
vertical has to
|
|
614
|
-
is the distance by which it would miss.
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
A block of FIELDS has no padding of its own, so it takes the inset wherever it
|
|
618
|
-
abuts a rule; three places, since three blocks can: an ungrouped block under the
|
|
619
|
-
top rule, an ungrouped block over the bottom rule (only reachable because the
|
|
620
|
-
accordion's `{#if}` keeps an empty wrapper out of last position), and the last
|
|
621
|
-
section's panel when it is open, whose inset is otherwise zero at the bottom and
|
|
622
|
-
would stand its final row of controls directly on the stroke. The panel's padding
|
|
623
|
-
is inside `.qm-group`, so the vertical still runs the full height to the corner.
|
|
624
|
-
Ungrouped fields always render first, so the top selector needs no qualifier. */
|
|
505
|
+
/* The inset sits on the fields rather than on the block that holds them: an open
|
|
506
|
+
section's vertical has to reach the rule to close a corner against it, and padding
|
|
507
|
+
between the two is the distance by which it would miss. Unqualified by open — a shut
|
|
508
|
+
panel clips it. */
|
|
625
509
|
.qm-meta-top > .qm-section {
|
|
626
510
|
padding-top: var(--_qm-space-3);
|
|
627
511
|
}
|
|
628
512
|
.qm-meta-bottom > .qm-section:last-child,
|
|
629
|
-
.qm-meta-bottom .qm-group:last-child
|
|
513
|
+
.qm-meta-bottom .qm-group:last-child .qm-group-panel-inner > .qm-fields {
|
|
630
514
|
padding-bottom: var(--_qm-space-3);
|
|
631
515
|
}
|
|
632
|
-
/*
|
|
633
|
-
|
|
634
|
-
inset makes that narrower than the card.
|
|
635
|
-
|
|
636
|
-
The ACTION COLUMN is minted here and spent one level down, on the field
|
|
637
|
-
(`Field.svelte` holds why it is the field's inset and not this container's). It
|
|
638
|
-
is declared on the query container because both of its consumers hang off one:
|
|
639
|
-
the fields inside, and the capacity ramp below, which has to carry the column in
|
|
640
|
-
its own arithmetic now that the query no longer reads a content box the
|
|
641
|
-
reservation already narrowed. */
|
|
516
|
+
/* Two query containers, one per kind of section, so capacity follows the width the
|
|
517
|
+
fields actually get: a panel's inset makes that narrower than the card. */
|
|
642
518
|
.qm-section,
|
|
643
519
|
.qm-group-panel-inner {
|
|
644
|
-
--action-col: calc(var(--_qm-tap-min) + var(--_qm-space-2));
|
|
645
520
|
container-type: inline-size;
|
|
646
521
|
}
|
|
647
|
-
/*
|
|
648
|
-
|
|
649
|
-
wrong capacity, and no re-packing to restructure the DOM under a prose leaf.
|
|
650
|
-
Fields auto-place, which is what keeps a trailing orphan at its column width
|
|
651
|
-
instead of growing to fill the line.
|
|
652
|
-
|
|
653
|
-
Capacity steps 1 → 2 → 4, skipping 3: each rung is the width at which a track
|
|
654
|
-
still clears the comfortable field minimum (2 needs 32rem, 4 needs 65rem), and
|
|
655
|
-
an even capacity is what lets `lone`'s half land on a track boundary. Each rung
|
|
656
|
-
pays for a track's own action column as well as the gutters between tracks, since
|
|
657
|
-
the reservation is per field now: a rung is `n * (220px + action) + gaps`.
|
|
522
|
+
/* Capacity is the container's, not JavaScript's: nothing measures, so there is no
|
|
523
|
+
observer to loop and no re-packing to restructure the DOM under a prose leaf.
|
|
658
524
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
525
|
+
It steps 1 → 2 → 4, skipping 3: each rung is the width at which a track still clears
|
|
526
|
+
the comfortable field minimum, and an even capacity is what lets `lone`'s half land
|
|
527
|
+
on a track boundary. A field ends on its track's own edge, so a rung is
|
|
528
|
+
`n * 220px + gaps`. */
|
|
663
529
|
.qm-fields {
|
|
664
530
|
--cols: 1;
|
|
665
531
|
--cols-half: 1;
|
|
666
532
|
display: grid;
|
|
667
533
|
grid-template-columns: repeat(var(--cols), 1fr);
|
|
668
534
|
column-gap: var(--_qm-space-2);
|
|
669
|
-
row-gap: var(--_qm-space-
|
|
535
|
+
row-gap: var(--_qm-space-3);
|
|
670
536
|
}
|
|
671
|
-
@container (min-width:
|
|
537
|
+
@container (min-width: 28rem) {
|
|
672
538
|
.qm-fields {
|
|
673
539
|
--cols: 2;
|
|
674
540
|
--cols-half: 1;
|
|
675
541
|
}
|
|
676
542
|
}
|
|
677
|
-
@container (min-width:
|
|
543
|
+
@container (min-width: 57rem) {
|
|
678
544
|
.qm-fields {
|
|
679
545
|
--cols: 4;
|
|
680
546
|
--cols-half: 2;
|
|
681
547
|
}
|
|
682
548
|
}
|
|
683
|
-
/* Group accordion. The header is a toggle
|
|
684
|
-
at the field-label rung; the panel slides via a 0fr↔1fr grid row so the height
|
|
685
|
-
animates without a magic max-height. */
|
|
686
|
-
/* The wrapper carries NO gap: the headers' own padding is the rhythm (see the
|
|
687
|
-
markup note). */
|
|
688
549
|
.qm-groups,
|
|
689
550
|
.qm-group {
|
|
690
551
|
display: flex;
|
|
691
552
|
flex-direction: column;
|
|
692
553
|
}
|
|
693
|
-
/* The bracket's third stroke, on the
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
One rung with the two horizontals, held there by `check:style`'s border-width axis.
|
|
697
|
-
|
|
698
|
-
Drawn transparent when closed rather than absent, so the 1px it occupies is the
|
|
699
|
-
same open or shut and a toggle moves no text. */
|
|
554
|
+
/* The bracket's third stroke, on the section rather than on its panel: spanning the
|
|
555
|
+
header is what gives it a corner to close into at the block's edge. Drawn transparent
|
|
556
|
+
when closed rather than absent, so a toggle moves no text. */
|
|
700
557
|
.qm-group {
|
|
701
558
|
border-left: var(--_qm-border-width) solid transparent;
|
|
702
559
|
transition: border-color var(--_qm-duration-slow) var(--_qm-ease-reverse);
|
|
@@ -704,33 +561,17 @@
|
|
|
704
561
|
.qm-group.qm-open {
|
|
705
562
|
border-left-color: var(--_qm-border);
|
|
706
563
|
}
|
|
707
|
-
/*
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
A button by tag and NEITHER button family by recipe: it reads no
|
|
720
|
-
`--_qm-tap-min` (the padding above already clears the floor) and takes no hover
|
|
721
|
-
fill (an ink step, below); so the type a family would have carried is declared
|
|
722
|
-
here. `font: inherit` because a UA button inherits no face and `--qm-font` would
|
|
723
|
-
stop at this row; then the field-label rung, at the tight leading a single line
|
|
724
|
-
takes. Sentence case at that rung, reused rather than a fifth size minted: a
|
|
725
|
-
section name is structurally a heading, and uppercase costs it twice: the word
|
|
726
|
-
shape a column of them is scanned by, and apparent width, so a long label crowds
|
|
727
|
-
sooner. Size, weight and leading all sit after `font`, which is a shorthand that
|
|
728
|
-
carries `line-height`.
|
|
729
|
-
|
|
730
|
-
On the BUTTON rather than on a span inside it: the row's type is one decision,
|
|
731
|
-
and a wrapper that carries nothing else is indirection between the header and
|
|
732
|
-
its own label. The label is a text run beside the chevron, and flex gives it an
|
|
733
|
-
anonymous item either way. */
|
|
564
|
+
/* Symmetric vertical padding at the tightest rung that still clears WCAG 2.5.8's
|
|
565
|
+
24×24 floor: the header is the whole row, so adjacent labels share one rhythm with no
|
|
566
|
+
dead strip outside the button. Horizontal is one rung left and zero right — left,
|
|
567
|
+
because at zero the chevron stands on the section's vertical with only the icon box's
|
|
568
|
+
own bearing between them, and the glyph's rotation swaps which bearing faces the
|
|
569
|
+
stroke; right, because the row is the target and an inset there is target given back.
|
|
570
|
+
|
|
571
|
+
`font: inherit` because a UA button inherits no face, then the body rung, one step
|
|
572
|
+
over the field labels beneath it: at the label rung the two read as one register and
|
|
573
|
+
the accordion stops looking like structure. Size, weight and leading sit after
|
|
574
|
+
`font`, a shorthand that carries `line-height`. */
|
|
734
575
|
.qm-group-header {
|
|
735
576
|
display: flex;
|
|
736
577
|
align-items: center;
|
|
@@ -740,38 +581,31 @@
|
|
|
740
581
|
background: transparent;
|
|
741
582
|
padding: var(--_qm-space) 0 var(--_qm-space) var(--_qm-space);
|
|
742
583
|
font: inherit;
|
|
743
|
-
font-size: var(--_qm-text-
|
|
744
|
-
font-weight: var(--_qm-weight-
|
|
584
|
+
font-size: var(--_qm-text-body);
|
|
585
|
+
font-weight: var(--_qm-weight-mid);
|
|
745
586
|
line-height: var(--_qm-leading-tight);
|
|
746
587
|
cursor: pointer;
|
|
747
|
-
color: var(--_qm-ink-
|
|
588
|
+
color: var(--_qm-ink-label);
|
|
748
589
|
text-align: left;
|
|
749
590
|
transition: color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
750
591
|
}
|
|
751
592
|
.qm-group-header :global(.qm-group-chevron) {
|
|
752
593
|
flex-shrink: 0;
|
|
594
|
+
display: block;
|
|
595
|
+
transform: rotate(0deg);
|
|
596
|
+
transform-origin: center;
|
|
753
597
|
transition: transform var(--_qm-duration-slow) var(--_qm-ease-reverse);
|
|
754
598
|
}
|
|
755
|
-
/*
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
share a rung and one of them is wrong: an ink step at the slow rung lags the
|
|
761
|
-
pointer, and at the fast one it settles 80ms before the panel it belongs to.
|
|
762
|
-
Hover keeps the property because open has another cue in the
|
|
763
|
-
chevron's rotation and hover has none, a big borderless target drawing no box. */
|
|
764
|
-
.qm-group-header:hover {
|
|
599
|
+
/* Open shares hover's ink step: a big borderless target draws no box, so the ink is
|
|
600
|
+
the only cue an open section keeps once the pointer leaves it. The chevron's
|
|
601
|
+
rotation is the other cue, and stays open-only below. */
|
|
602
|
+
.qm-group-header:hover,
|
|
603
|
+
.qm-group.qm-open .qm-group-header {
|
|
765
604
|
color: var(--_qm-ink);
|
|
766
605
|
}
|
|
767
606
|
.qm-group.qm-open .qm-group-header :global(.qm-group-chevron) {
|
|
768
607
|
transform: rotate(90deg);
|
|
769
608
|
}
|
|
770
|
-
/* Sliding panel: the grid track goes 0fr→1fr; the inner clips at min-height 0.
|
|
771
|
-
Clipping is the whole of what CSS does here: what a closed panel is OUT of is
|
|
772
|
-
`inert`'s to say, on the element. The panel
|
|
773
|
-
draws no rule of its own: the vertical is the section's (`.qm-group`), which is
|
|
774
|
-
what spans the header too. */
|
|
775
609
|
.qm-group-panel {
|
|
776
610
|
display: grid;
|
|
777
611
|
grid-template-rows: 0fr;
|
|
@@ -780,42 +614,35 @@
|
|
|
780
614
|
.qm-group.qm-open .qm-group-panel {
|
|
781
615
|
grid-template-rows: 1fr;
|
|
782
616
|
}
|
|
783
|
-
/*
|
|
784
|
-
|
|
617
|
+
/* Two boxes because the axes clip differently, and the track is the only thing that
|
|
618
|
+
animates: a field sits at its open position from the first frame of the reveal.
|
|
619
|
+
|
|
620
|
+
The clip box takes the inline inset, which costs a shut panel no height and holds the
|
|
621
|
+
queried width still. The block inset goes on the content, where a `0fr` track clips
|
|
622
|
+
it: declared one box out it collapses the content but not itself, and stands under a
|
|
623
|
+
shut header as dead space. Neither is animated, and neither is qualified by open. */
|
|
785
624
|
.qm-group-panel-inner {
|
|
786
|
-
display: flex;
|
|
787
|
-
flex-direction: column;
|
|
788
|
-
gap: var(--_qm-space-2);
|
|
789
625
|
min-height: 0;
|
|
790
626
|
overflow: hidden;
|
|
791
|
-
|
|
792
|
-
the CONTENT, not the padding, and a top inset declared here stands under a
|
|
793
|
-
closed header as dead space the header's symmetric padding cannot absorb. The
|
|
794
|
-
panel animates its width along with them, and the capacity ramp reads that
|
|
795
|
-
width: a closed panel is `inert` and unmeasured either way, so what a rung
|
|
796
|
-
crosses mid-slide is a hidden grid. */
|
|
797
|
-
padding: 0;
|
|
798
|
-
transition: padding var(--_qm-duration-slow) var(--_qm-ease-reverse);
|
|
627
|
+
padding-inline-start: var(--_qm-space-4);
|
|
799
628
|
}
|
|
800
|
-
.qm-group
|
|
801
|
-
padding: var(--_qm-space-2)
|
|
629
|
+
.qm-group-panel-inner > .qm-fields {
|
|
630
|
+
padding-block: var(--_qm-space-2);
|
|
802
631
|
}
|
|
803
632
|
.qm-body-leaf {
|
|
804
633
|
display: flex;
|
|
805
634
|
flex-direction: column;
|
|
806
635
|
gap: var(--_qm-space);
|
|
807
636
|
}
|
|
808
|
-
/*
|
|
809
|
-
|
|
637
|
+
/* The one card that draws an edge at all: an ordinary card is a plane, so a stroke
|
|
638
|
+
around this one is a state rather than heavier chrome. */
|
|
810
639
|
.qm-card.qm-unschemable {
|
|
811
|
-
border
|
|
640
|
+
border: var(--_qm-border-width) dashed var(--_qm-border);
|
|
812
641
|
}
|
|
813
|
-
/* One typographic role with the editable title above
|
|
814
|
-
rungs: including the tight leading a card title takes over the root's reading
|
|
815
|
-
rhythm. */
|
|
642
|
+
/* One typographic role with the editable title above. */
|
|
816
643
|
.qm-card-title-static {
|
|
817
644
|
font-size: var(--_qm-text-title);
|
|
818
|
-
font-weight: var(--_qm-weight-
|
|
645
|
+
font-weight: var(--_qm-weight-mid);
|
|
819
646
|
line-height: var(--_qm-leading-tight);
|
|
820
647
|
color: var(--_qm-ink-label);
|
|
821
648
|
}
|
|
@@ -839,11 +666,12 @@
|
|
|
839
666
|
font-size: var(--_qm-text-label);
|
|
840
667
|
color: var(--_qm-ink-label);
|
|
841
668
|
}
|
|
669
|
+
/* The one native control the surface draws, on the box's own recipe (controls.css). */
|
|
842
670
|
.qm-recovery-retype select {
|
|
843
671
|
font: inherit;
|
|
844
|
-
border:
|
|
672
|
+
border: none;
|
|
845
673
|
border-radius: var(--_qm-radius-inner);
|
|
846
|
-
background: var(--_qm-surface);
|
|
674
|
+
background: var(--_qm-surface-well);
|
|
847
675
|
padding: var(--_qm-space-half) var(--_qm-space-2);
|
|
848
676
|
}
|
|
849
677
|
</style>
|