@quillmark/svelte 0.26.2 → 0.28.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 +4 -4
- package/README.md +26 -8
- package/THEMING.md +40 -4
- package/dist/core/address.d.ts +35 -64
- package/dist/core/address.js +35 -55
- package/dist/core/bloom.d.ts +4 -10
- package/dist/core/bloom.js +14 -27
- package/dist/core/codec/blocks.d.ts +41 -43
- package/dist/core/codec/blocks.js +97 -130
- package/dist/core/codec/code.d.ts +7 -10
- package/dist/core/codec/code.js +37 -46
- package/dist/core/codec/decode.d.ts +19 -23
- package/dist/core/codec/decode.js +76 -111
- package/dist/core/codec/encode.d.ts +11 -15
- package/dist/core/codec/encode.js +60 -67
- package/dist/core/codec/field.d.ts +62 -95
- package/dist/core/codec/field.js +107 -179
- package/dist/core/codec/islands.d.ts +33 -41
- package/dist/core/codec/islands.js +76 -86
- package/dist/core/codec/lists.d.ts +17 -26
- package/dist/core/codec/lists.js +117 -157
- package/dist/core/codec/marks.d.ts +4 -3
- package/dist/core/codec/marks.js +8 -3
- package/dist/core/codec/prose.css +84 -46
- package/dist/core/codec/schema.d.ts +18 -23
- package/dist/core/codec/schema.js +84 -76
- package/dist/core/codec/slash.d.ts +42 -48
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-copy.d.ts +4 -3
- package/dist/core/codec/table-copy.js +25 -7
- package/dist/core/codec/table-view.d.ts +12 -12
- package/dist/core/codec/table-view.js +460 -383
- package/dist/core/codec/table.d.ts +94 -61
- package/dist/core/codec/table.js +331 -83
- package/dist/core/codec/tasks.d.ts +6 -0
- package/dist/core/codec/tasks.js +30 -3
- package/dist/core/errors.d.ts +15 -30
- package/dist/core/errors.js +2 -6
- package/dist/core/index.js +7 -12
- package/dist/core/lifecycle.d.ts +7 -16
- package/dist/core/lifecycle.js +10 -21
- package/dist/core/rebind.svelte.d.ts +10 -20
- package/dist/core/rebind.svelte.js +15 -27
- package/dist/core/rungs.d.ts +14 -11
- package/dist/core/rungs.js +17 -19
- package/dist/core/teardown.d.ts +4 -6
- package/dist/core/teardown.js +7 -10
- package/dist/core/theme.css +74 -54
- package/dist/index.js +3 -8
- package/dist/preset/app.css +71 -0
- package/dist/preset/index.css +8 -7
- package/dist/preset/recipes.css +19 -164
- package/dist/preset/scale.css +28 -18
- package/dist/preset/shell.css +157 -0
- package/dist/preview/Preview.svelte +8 -0
- package/dist/preview/bridge.d.ts +18 -22
- package/dist/preview/bridge.js +29 -58
- package/dist/preview/controller.d.ts +21 -29
- package/dist/preview/controller.js +14 -39
- package/dist/preview/geometry.d.ts +13 -34
- package/dist/preview/geometry.js +13 -34
- package/dist/preview/index.js +4 -16
- package/dist/preview/paint.d.ts +3 -3
- package/dist/preview/paint.js +28 -68
- package/dist/preview/strings.d.ts +1 -1
- package/dist/preview/strings.js +2 -14
- package/dist/preview/viewport.js +20 -15
- package/dist/preview/zoom.d.ts +21 -22
- package/dist/preview/zoom.js +53 -30
- package/dist/visual/ArrayField.svelte +77 -16
- package/dist/visual/BooleanField.svelte +28 -15
- package/dist/visual/Card.svelte +66 -51
- package/dist/visual/CardControls.svelte +5 -11
- package/dist/visual/DateField.svelte +20 -4
- package/dist/visual/EnumField.svelte +3 -0
- package/dist/visual/FieldHint.svelte +2 -1
- package/dist/visual/FormatPopover.svelte +82 -15
- package/dist/visual/FormatPopover.svelte.d.ts +4 -0
- package/dist/visual/MatrixAdd.svelte +30 -12
- package/dist/visual/MatrixField.svelte +24 -16
- package/dist/visual/NumberField.svelte +7 -0
- package/dist/visual/ObjectField.svelte +4 -4
- package/dist/visual/ProseField.svelte +0 -5
- package/dist/visual/TipsCard.svelte +11 -10
- package/dist/visual/VariantField.svelte +3 -1
- package/dist/visual/VisualEditorInner.svelte +19 -3
- package/dist/visual/controls.css +7 -2
- package/dist/visual/fonts.d.ts +18 -0
- package/dist/visual/fonts.js +59 -0
- package/dist/visual/icons/nodes.d.ts +0 -5
- package/dist/visual/icons/nodes.js +0 -4
- package/dist/visual/props.d.ts +14 -1
- package/dist/visual/strings.d.ts +7 -0
- package/dist/visual/strings.js +5 -2
- package/dist/visual/structure.d.ts +43 -87
- package/dist/visual/structure.js +42 -83
- package/package.json +2 -2
|
@@ -307,12 +307,19 @@
|
|
|
307
307
|
}
|
|
308
308
|
/* The ring rides `.qm-focus-ring-within` (controls.css) rather than the plain
|
|
309
309
|
marker: focus lives on the segment, so it rings the field, not the segment
|
|
310
|
-
the caret happens to be in.
|
|
310
|
+
the caret happens to be in. Tabular figures, so a segment keeps its width as its
|
|
311
|
+
digits change. */
|
|
311
312
|
.qm-date-wrap :global(.qm-date-segment) {
|
|
312
313
|
padding: 0 var(--_qm-space-half);
|
|
314
|
+
font-variant-numeric: tabular-nums;
|
|
313
315
|
border-radius: var(--_qm-radius-inner);
|
|
314
316
|
outline: none;
|
|
315
317
|
}
|
|
318
|
+
/* The first segment's pad sits inside the box's inset rather than after it, so its
|
|
319
|
+
digits start on the column every other box's text starts on. */
|
|
320
|
+
.qm-date-wrap :global(.qm-date-segment:first-child) {
|
|
321
|
+
margin-inline-start: calc(-1 * var(--_qm-space-half));
|
|
322
|
+
}
|
|
316
323
|
.qm-date-wrap :global(.qm-date-segment:focus) {
|
|
317
324
|
background: var(--_qm-surface-hover);
|
|
318
325
|
}
|
|
@@ -329,12 +336,21 @@
|
|
|
329
336
|
.qm-date-wrap :global(.qm-date[data-default] .qm-date-segment) {
|
|
330
337
|
color: var(--_qm-ink-default);
|
|
331
338
|
}
|
|
339
|
+
/* A separator is punctuation between the parts rather than part of the value, so it
|
|
340
|
+
takes the label rung whatever the parts beside it hold: at `ink` it stood darker than
|
|
341
|
+
the hint or the default digits either side of it. */
|
|
342
|
+
.qm-date-wrap :global(.qm-date .qm-date-segment[data-segment='literal']) {
|
|
343
|
+
color: var(--_qm-ink-label);
|
|
344
|
+
}
|
|
332
345
|
/* The toggle's state is its fill and its glyph, never the pointer: a hover fill
|
|
333
346
|
would read as pressed. Off, it rests hidden where a pointer can reveal it, still
|
|
334
347
|
holding its width so the digits never shift. It sits inside the box's line, so
|
|
335
|
-
the box stands as tall as the inputs beside it.
|
|
348
|
+
the box stands as tall as the inputs beside it. Its pad sits inside the box's
|
|
349
|
+
inset, so its label ends on the column the digits start on from the other side,
|
|
350
|
+
and its fill stands about as far off the box's end as off its top and bottom. */
|
|
336
351
|
.qm-date-today {
|
|
337
352
|
margin-inline-start: auto;
|
|
353
|
+
margin-inline-end: calc(-1 * var(--_qm-space-2));
|
|
338
354
|
padding-block: var(--_qm-space-half);
|
|
339
355
|
line-height: 1;
|
|
340
356
|
border-radius: var(--_qm-radius-inner);
|
|
@@ -347,9 +363,9 @@
|
|
|
347
363
|
background: var(--_qm-accent-tint);
|
|
348
364
|
color: var(--_qm-ink);
|
|
349
365
|
}
|
|
366
|
+
/* A default-supplied today keeps the pressed fill: the default ink alone says it is
|
|
367
|
+
shown and not written, as it does for the digits beside it. */
|
|
350
368
|
.qm-date-today[data-default] {
|
|
351
|
-
background: transparent;
|
|
352
|
-
box-shadow: inset 0 0 0 var(--_qm-border-width) var(--_qm-border);
|
|
353
369
|
color: var(--_qm-ink-default);
|
|
354
370
|
}
|
|
355
371
|
@media (hover: hover) {
|
|
@@ -203,8 +203,11 @@
|
|
|
203
203
|
text-overflow: ellipsis;
|
|
204
204
|
white-space: nowrap;
|
|
205
205
|
}
|
|
206
|
+
/* The chevron is chrome, so it takes the label rung the other chrome glyphs take: at
|
|
207
|
+
`ink` beside a written value it read as part of the value. */
|
|
206
208
|
.qm-select-wrap :global(.qm-select-chevron) {
|
|
207
209
|
flex-shrink: 0;
|
|
210
|
+
color: var(--_qm-ink-label);
|
|
208
211
|
width: var(--_qm-glyph-control);
|
|
209
212
|
height: var(--_qm-glyph-control);
|
|
210
213
|
}
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
<script lang="ts">
|
|
32
32
|
import { Popover } from 'bits-ui';
|
|
33
33
|
import Icon from './icons/Icon.svelte';
|
|
34
|
+
import { spacePx } from '../core/rungs.js';
|
|
34
35
|
import './controls.css';
|
|
35
36
|
|
|
36
37
|
interface Props {
|
|
@@ -205,7 +206,7 @@
|
|
|
205
206
|
collisionBoundary={bounds}
|
|
206
207
|
side={beside ? 'right' : 'top'}
|
|
207
208
|
align={beside ? 'center' : 'start'}
|
|
208
|
-
sideOffset={
|
|
209
|
+
sideOffset={(spacePx(triggerEl) ?? 0) + reach}
|
|
209
210
|
trapFocus={false}
|
|
210
211
|
onOpenAutoFocus={(e: Event) => e.preventDefault()}
|
|
211
212
|
onCloseAutoFocus={(e: Event) => e.preventDefault()}
|
|
@@ -10,12 +10,14 @@
|
|
|
10
10
|
the result as it commits a keystroke: a field's lowers it to `markOps` through
|
|
11
11
|
`applyChange` (field.ts), a by-value leaf's hands the re-encoded value to its parent
|
|
12
12
|
(`ProseValue`). This component never touches the content. The keymap mirror
|
|
13
|
-
(Mod-b/i/u) lives in the codec's keymap (field.ts); this is
|
|
14
|
-
affordance for all six marks. Mod-K is this surface's own key, the
|
|
15
|
-
being this surface's: a document `keydown` the leaf's keymap left
|
|
16
|
-
leaf that holds lists, two more buttons number or bullet every
|
|
17
|
-
the marker off where every one carries it: the codec's
|
|
18
|
-
(Mod-Shift-7/8) the leaf's keymap binds.
|
|
13
|
+
(Mod-b/i/u, Mod-Shift-x, Mod-e) lives in the codec's keymap (field.ts); this is
|
|
14
|
+
the pointer affordance for all six marks. Mod-K is this surface's own key, the
|
|
15
|
+
prompt it raises being this surface's: a document `keydown` the leaf's keymap left
|
|
16
|
+
unclaimed. Over a leaf that holds lists, two more buttons number or bullet every
|
|
17
|
+
selected line, or take the marker off where every one carries it: the codec's
|
|
18
|
+
`toggleList`, whose keys (Mod-Shift-7/8) the leaf's keymap binds. Where the host
|
|
19
|
+
handed the editor a font library, or the selection already sets a family, a picker
|
|
20
|
+
sets the selection's family or takes it back to the surrounding face (`fonts.ts`).
|
|
19
21
|
|
|
20
22
|
Selection observation. The view's `dispatchTransaction` is the leaf's
|
|
21
23
|
(read-only), so there is no hook to observe transactions from outside.
|
|
@@ -69,7 +71,7 @@
|
|
|
69
71
|
const t = wording();
|
|
70
72
|
import { toggleMark } from 'prosemirror-commands';
|
|
71
73
|
import { keydownHandler } from 'prosemirror-keymap';
|
|
72
|
-
import {
|
|
74
|
+
import { NodeSelection, type Command } from 'prosemirror-state';
|
|
73
75
|
import type { EditorView } from 'prosemirror-view';
|
|
74
76
|
import type { MarkType } from 'prosemirror-model';
|
|
75
77
|
import { Popover } from 'bits-ui';
|
|
@@ -78,14 +80,19 @@
|
|
|
78
80
|
import { hasMarks, rangeAnchor, toggleList, type RangeAnchor } from '../core/codec/index.js';
|
|
79
81
|
import type { ActiveProse } from './leaves.js';
|
|
80
82
|
import { clearLink, hrefInSelection, normalizeHref, setLink } from './links.js';
|
|
83
|
+
import { familyInSelection, setFamily } from './fonts.js';
|
|
81
84
|
import { spacePx } from '../core/rungs.js';
|
|
82
85
|
import './controls.css';
|
|
83
86
|
|
|
84
87
|
interface Props {
|
|
85
88
|
/** The observation seam VisualEditor exposes over its `leaves` registry. */
|
|
86
89
|
getActiveLeaf: () => ActiveProse | undefined;
|
|
90
|
+
/** The families the host's font library offers, in its order. */
|
|
91
|
+
families?: string[];
|
|
92
|
+
/** The name the picker shows for a family, where it is not the family itself. */
|
|
93
|
+
fontLabels?: Readonly<Record<string, string>>;
|
|
87
94
|
}
|
|
88
|
-
let { getActiveLeaf }: Props = $props();
|
|
95
|
+
let { getActiveLeaf, families = [], fontLabels = {} }: Props = $props();
|
|
89
96
|
|
|
90
97
|
const SETTLE_MS = 450;
|
|
91
98
|
/** The six content formatting marks. `anchor` is withheld: it draws nothing until
|
|
@@ -108,6 +115,23 @@
|
|
|
108
115
|
/** Whether the leaf under the selection holds lists: a body does, a one-line field
|
|
109
116
|
* and a table cell do not. */
|
|
110
117
|
let listable = $state(false);
|
|
118
|
+
/** Whether the leaf under the selection declares `font_family`: a `text` leaf does not. */
|
|
119
|
+
let markable = $state(false);
|
|
120
|
+
/** The family the selection is set in, `''` for none and `null` for several. */
|
|
121
|
+
let family = $state<string | null>('');
|
|
122
|
+
/** Whether the picker draws: the leaf declares the mark, and there is a family to
|
|
123
|
+
* offer or one under the selection to clear, mixed with others or with none. */
|
|
124
|
+
const fontable = $derived(markable && (families.length > 0 || family !== ''));
|
|
125
|
+
/** The picker's options: the library's families, and the selection's own where the
|
|
126
|
+
* library lacks it, so the picker always shows what the document says. */
|
|
127
|
+
const familyOptions = $derived(
|
|
128
|
+
family && !families.includes(family) ? [...families, family] : families
|
|
129
|
+
);
|
|
130
|
+
/** The picker's value over a selection setting several families: no family's. */
|
|
131
|
+
const MIXED = '\u0000';
|
|
132
|
+
/** The view the surface rose over. The picker takes the focus, and a cell view
|
|
133
|
+
* without it is no longer the leaf's `focusedView`. */
|
|
134
|
+
let surfaceView: EditorView | undefined;
|
|
111
135
|
/** A pointer is down outside this surface: the selection is still being made. */
|
|
112
136
|
let pressed = false;
|
|
113
137
|
let linkPromptOpen = $state(false);
|
|
@@ -147,11 +171,12 @@
|
|
|
147
171
|
if (insidePopover) return;
|
|
148
172
|
if (pressed) return;
|
|
149
173
|
const view = activeLeafView();
|
|
150
|
-
// A non-empty
|
|
151
|
-
//
|
|
152
|
-
// Escape out of a table
|
|
174
|
+
// A non-empty selection that is not a node's. Select-all is one: an `AllSelection`,
|
|
175
|
+
// which spans text like any range. A node selection covers a leaf block (an island,
|
|
176
|
+
// a rule), which has nothing to format and is exactly where Escape out of a table
|
|
177
|
+
// cell lands.
|
|
153
178
|
const selection = view?.state.selection;
|
|
154
|
-
const formattable = selection
|
|
179
|
+
const formattable = !!selection && !selection.empty && !(selection instanceof NodeSelection);
|
|
155
180
|
// …in a leaf that has formatting at all. A `text` leaf declares no mark types
|
|
156
181
|
// (`plaintextSchema`, `plainSchema`), so this surface withholds itself over one
|
|
157
182
|
// entirely, the way it never rises over a form control: the value is literal
|
|
@@ -173,6 +198,9 @@
|
|
|
173
198
|
}
|
|
174
199
|
portalTarget = view.dom.closest<HTMLElement>('[data-qm-root]') ?? undefined;
|
|
175
200
|
listable = !!view.state.schema.nodes.list_item;
|
|
201
|
+
markable = !!view.state.schema.marks.font_family;
|
|
202
|
+
family = familyInSelection(view.state);
|
|
203
|
+
surfaceView = view;
|
|
176
204
|
const { from, to } = view.state.selection;
|
|
177
205
|
anchor = rangeAnchor(view, from, to);
|
|
178
206
|
open = true;
|
|
@@ -280,6 +308,14 @@
|
|
|
280
308
|
sync();
|
|
281
309
|
}
|
|
282
310
|
|
|
311
|
+
function pickFamily(name: string): void {
|
|
312
|
+
const view = surfaceView && !surfaceView.isDestroyed ? surfaceView : activeLeafView();
|
|
313
|
+
if (!view) return;
|
|
314
|
+
setFamily(name)(view.state, view.dispatch);
|
|
315
|
+
view.focus();
|
|
316
|
+
sync();
|
|
317
|
+
}
|
|
318
|
+
|
|
283
319
|
function toggleLines(ordered: boolean): void {
|
|
284
320
|
const view = activeLeafView();
|
|
285
321
|
if (!view) return;
|
|
@@ -441,6 +477,24 @@
|
|
|
441
477
|
onclick={() => toggle(m.name)}><Icon name={m.icon} /></button
|
|
442
478
|
>
|
|
443
479
|
{/each}
|
|
480
|
+
{#if fontable}
|
|
481
|
+
<span class="qm-format-divider" aria-hidden="true"></span>
|
|
482
|
+
<select
|
|
483
|
+
class="qm-font-select qm-focus-ring"
|
|
484
|
+
title={t.strings.formatFont}
|
|
485
|
+
aria-label={t.strings.formatFont}
|
|
486
|
+
value={family ?? MIXED}
|
|
487
|
+
onchange={(e) => pickFamily(e.currentTarget.value)}
|
|
488
|
+
>
|
|
489
|
+
{#if family === null}
|
|
490
|
+
<option value={MIXED} disabled hidden>{t.strings.fontMixed}</option>
|
|
491
|
+
{/if}
|
|
492
|
+
<option value="">{t.strings.fontDefault}</option>
|
|
493
|
+
{#each familyOptions as name (name)}
|
|
494
|
+
<option value={name}>{fontLabels[name] ?? name}</option>
|
|
495
|
+
{/each}
|
|
496
|
+
</select>
|
|
497
|
+
{/if}
|
|
444
498
|
{#if listable}
|
|
445
499
|
<span class="qm-format-divider" aria-hidden="true"></span>
|
|
446
500
|
{#each LISTS as l (l.icon)}
|
|
@@ -487,18 +541,31 @@
|
|
|
487
541
|
itself, which a two-state button has no third face for. */
|
|
488
542
|
.qm-format-divider {
|
|
489
543
|
align-self: stretch;
|
|
490
|
-
width:
|
|
544
|
+
width: var(--_qm-border-width);
|
|
491
545
|
margin: var(--_qm-space-half) 0;
|
|
492
|
-
background: var(--_qm-
|
|
546
|
+
background: var(--_qm-border);
|
|
493
547
|
}
|
|
494
548
|
.qm-mark-btn {
|
|
495
549
|
padding: var(--_qm-space) var(--_qm-space-2);
|
|
496
550
|
color: var(--_qm-ink);
|
|
497
551
|
}
|
|
552
|
+
/* The link input's box, a pick rather than a typed value. */
|
|
553
|
+
.qm-font-select {
|
|
554
|
+
font: inherit;
|
|
555
|
+
font-size: var(--_qm-text-body);
|
|
556
|
+
padding: var(--_qm-inset-control);
|
|
557
|
+
border: none;
|
|
558
|
+
border-radius: var(--_qm-radius-inner);
|
|
559
|
+
background: var(--_qm-surface-well);
|
|
560
|
+
color: var(--_qm-ink);
|
|
561
|
+
max-width: 12rem;
|
|
562
|
+
}
|
|
563
|
+
/* Stretched, so the prompt's buttons stand the input's box line and the face is one
|
|
564
|
+
row height rather than a box between two shorter buttons. */
|
|
498
565
|
.qm-link-prompt {
|
|
499
566
|
display: flex;
|
|
500
567
|
gap: var(--_qm-space);
|
|
501
|
-
align-items:
|
|
568
|
+
align-items: stretch;
|
|
502
569
|
}
|
|
503
570
|
/* The box's recipe without `.qm-input`, which adds the `width: 100%` that would
|
|
504
571
|
stretch this to the popover. The fill is the box's own rung and there is no
|
|
@@ -2,6 +2,10 @@ import type { ActiveProse } from './leaves.js';
|
|
|
2
2
|
interface Props {
|
|
3
3
|
/** The observation seam VisualEditor exposes over its `leaves` registry. */
|
|
4
4
|
getActiveLeaf: () => ActiveProse | undefined;
|
|
5
|
+
/** The families the host's font library offers, in its order. */
|
|
6
|
+
families?: string[];
|
|
7
|
+
/** The name the picker shows for a family, where it is not the family itself. */
|
|
8
|
+
fontLabels?: Readonly<Record<string, string>>;
|
|
5
9
|
}
|
|
6
10
|
declare const FormatPopover: import("svelte").Component<Props, {}, "">;
|
|
7
11
|
type FormatPopover = ReturnType<typeof FormatPopover>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
An open matrix's add box (VISUAL_EDITOR §"The matrix"): a plain text input. Enter adds
|
|
3
|
-
what was typed, trimmed, and clears the box; Escape clears it.
|
|
3
|
+
what was typed, trimmed, and clears the box; Escape clears it. It is drawn as the item
|
|
4
|
+
it adds, unwritten: the plus in the tick's column and the words in the title's.
|
|
4
5
|
-->
|
|
5
6
|
<script lang="ts">
|
|
6
7
|
import Icon from './icons/Icon.svelte';
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
</script>
|
|
39
40
|
|
|
40
41
|
<div class="qm-matrix-add">
|
|
41
|
-
<
|
|
42
|
+
<label class="qm-matrix-add-field">
|
|
42
43
|
<Icon name="plus" class="qm-matrix-add-glyph" />
|
|
43
44
|
<input
|
|
44
45
|
bind:this={inputEl}
|
|
@@ -51,41 +52,58 @@
|
|
|
51
52
|
{placeholder}
|
|
52
53
|
{onkeydown}
|
|
53
54
|
/>
|
|
54
|
-
</
|
|
55
|
+
</label>
|
|
55
56
|
</div>
|
|
56
57
|
|
|
57
58
|
<style>
|
|
58
59
|
.qm-matrix-add {
|
|
59
|
-
--_slot: calc(var(--_qm-space-2) * 2 + var(--_qm-glyph-control));
|
|
60
60
|
min-width: 0;
|
|
61
61
|
}
|
|
62
|
+
/* An added item's head (`MatrixField.svelte`): the glyph a tick's width at the
|
|
63
|
+
tick's place, the gap after it the head's, and the input pulled back over that gap
|
|
64
|
+
by its own inset, so its words start where the titles above them do. A `<label>`,
|
|
65
|
+
so a press on the glyph lands in the box. */
|
|
62
66
|
.qm-matrix-add-field {
|
|
63
|
-
position: relative;
|
|
64
67
|
display: flex;
|
|
65
68
|
align-items: center;
|
|
69
|
+
gap: var(--_qm-space-2);
|
|
66
70
|
min-width: 0;
|
|
67
71
|
}
|
|
68
72
|
.qm-matrix-add :global(.qm-matrix-add-glyph) {
|
|
69
|
-
|
|
70
|
-
inset-inline-start: var(--_qm-space-2);
|
|
73
|
+
flex-shrink: 0;
|
|
71
74
|
width: var(--_qm-glyph-control);
|
|
72
75
|
height: var(--_qm-glyph-control);
|
|
73
76
|
color: var(--_qm-ink-label);
|
|
74
|
-
pointer-events: none;
|
|
75
77
|
}
|
|
76
78
|
/* At rest the box is the add chip it stands in for: no well, its words at the label
|
|
77
|
-
ink
|
|
78
|
-
typed words. */
|
|
79
|
+
ink, and the line at the chip's idle rung. The line comes up under the pointer with
|
|
80
|
+
the well, and both stay while the box holds the focus or typed words. */
|
|
81
|
+
.qm-matrix-add-field {
|
|
82
|
+
opacity: var(--_qm-opacity-idle);
|
|
83
|
+
transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
84
|
+
}
|
|
85
|
+
.qm-matrix-add-field:hover,
|
|
86
|
+
.qm-matrix-add-field:focus-within,
|
|
87
|
+
.qm-matrix-add-field:has(.qm-input:not(:placeholder-shown)) {
|
|
88
|
+
opacity: 1;
|
|
89
|
+
}
|
|
79
90
|
.qm-matrix-add-field .qm-input {
|
|
80
91
|
flex: 1;
|
|
81
92
|
min-width: 0;
|
|
82
|
-
|
|
93
|
+
margin-inline-start: calc(-1 * var(--_qm-space-2));
|
|
94
|
+
padding-inline-start: var(--_qm-space-2);
|
|
83
95
|
transition: background-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
84
96
|
}
|
|
85
97
|
.qm-matrix-add-field .qm-input:placeholder-shown:not(:focus) {
|
|
86
98
|
background: transparent;
|
|
87
99
|
}
|
|
88
|
-
.qm-matrix-add-field .qm-input:placeholder-shown:not(:focus)
|
|
100
|
+
.qm-matrix-add-field:hover .qm-input:placeholder-shown:not(:focus) {
|
|
89
101
|
background: var(--_qm-surface-hover);
|
|
90
102
|
}
|
|
103
|
+
/* No hover to come up on: the line stands at the muted rung, as the chip does. */
|
|
104
|
+
@media (hover: none) {
|
|
105
|
+
.qm-matrix-add-field {
|
|
106
|
+
opacity: var(--_qm-opacity-muted);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
91
109
|
</style>
|
|
@@ -283,7 +283,7 @@
|
|
|
283
283
|
{#each members as m (m.id)}
|
|
284
284
|
{@const on = held(m.id)}
|
|
285
285
|
<div
|
|
286
|
-
class={['qm-member qm-wash', { held: on }]}
|
|
286
|
+
class={['qm-member qm-wash', { held: on, open: on && hasColumns }]}
|
|
287
287
|
data-qm-prop={m.id}
|
|
288
288
|
bind:this={memberEls[m.id]}
|
|
289
289
|
>
|
|
@@ -311,7 +311,7 @@
|
|
|
311
311
|
{#each added as item, k (item.id)}
|
|
312
312
|
{@const name = item.title || t.strings.matrixItemTitle(label ?? '', k + 1)}
|
|
313
313
|
<div
|
|
314
|
-
class=
|
|
314
|
+
class={['qm-member qm-wash held added', { open: hasColumns }]}
|
|
315
315
|
data-qm-prop={item.id}
|
|
316
316
|
bind:this={memberEls[item.id]}
|
|
317
317
|
>
|
|
@@ -337,7 +337,7 @@
|
|
|
337
337
|
class="qm-icon-btn qm-focus-ring qm-member-remove"
|
|
338
338
|
title={t.strings.matrixRemove(name)}
|
|
339
339
|
aria-label={t.strings.matrixRemove(name)}
|
|
340
|
-
onclick={() => removeItem(item.id)}><Icon name="
|
|
340
|
+
onclick={() => removeItem(item.id)}><Icon name="minus" /></button
|
|
341
341
|
>
|
|
342
342
|
</div>
|
|
343
343
|
{@render columns(item.id, name, true)}
|
|
@@ -426,6 +426,13 @@
|
|
|
426
426
|
gap: var(--_qm-space);
|
|
427
427
|
border-radius: var(--_qm-radius-inner);
|
|
428
428
|
}
|
|
429
|
+
/* An open array row's guard (`ArrayField`): a member whose columns are unfolded takes
|
|
430
|
+
a rung of air from the members either side of it, so its columns stand nearer its own
|
|
431
|
+
head than the next member's. Abreast, a margin would set the member below the heads
|
|
432
|
+
its row of tracks shares, and each track holds one member's columns alone. */
|
|
433
|
+
.qm-matrix-roster:not(.compact) > .qm-member.open {
|
|
434
|
+
margin-block: var(--_qm-space);
|
|
435
|
+
}
|
|
429
436
|
/* The tick and its title on one line, the line holding the tap floor a tick alone
|
|
430
437
|
would not reach: the title is the target, the `for` carrying its press to the input. */
|
|
431
438
|
.qm-member-head {
|
|
@@ -441,28 +448,25 @@
|
|
|
441
448
|
width: var(--_qm-glyph-control);
|
|
442
449
|
height: var(--_qm-glyph-control);
|
|
443
450
|
}
|
|
444
|
-
/* The box
|
|
445
|
-
|
|
451
|
+
/* The box is the switch's track (`BooleanField.svelte`): a fill and nothing else,
|
|
452
|
+
which held swaps for the accent, so the surface's two marks for one boolean decode
|
|
453
|
+
alike. */
|
|
446
454
|
.qm-tick-input {
|
|
447
455
|
appearance: none;
|
|
448
456
|
margin: 0;
|
|
449
457
|
width: 100%;
|
|
450
458
|
height: 100%;
|
|
451
|
-
border:
|
|
452
|
-
border-radius:
|
|
453
|
-
background: var(--_qm-surface-
|
|
459
|
+
border: none;
|
|
460
|
+
border-radius: var(--_qm-radius-inner);
|
|
461
|
+
background: var(--_qm-surface-band);
|
|
454
462
|
cursor: pointer;
|
|
455
|
-
transition:
|
|
456
|
-
background-color var(--_qm-duration-fast) var(--_qm-ease-reverse),
|
|
457
|
-
border-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
463
|
+
transition: background-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
458
464
|
}
|
|
459
465
|
.qm-tick-input:checked,
|
|
460
466
|
.qm-tick-fixed {
|
|
461
467
|
background: var(--_qm-accent);
|
|
462
|
-
border-color: var(--_qm-accent);
|
|
463
468
|
}
|
|
464
469
|
.qm-tick-fixed {
|
|
465
|
-
box-sizing: border-box;
|
|
466
470
|
cursor: default;
|
|
467
471
|
}
|
|
468
472
|
/* The mark rides over the input and takes no press of its own; it is drawn by the
|
|
@@ -512,8 +516,8 @@
|
|
|
512
516
|
.qm-member-title-input:focus {
|
|
513
517
|
background: var(--_qm-surface-well);
|
|
514
518
|
}
|
|
515
|
-
/* The remove is
|
|
516
|
-
is, and
|
|
519
|
+
/* The remove is an array row's, the minus to the add box's plus: offered by the row
|
|
520
|
+
the pointer or the focus is in, and destructive under the pointer. */
|
|
517
521
|
.qm-member-remove {
|
|
518
522
|
flex-shrink: 0;
|
|
519
523
|
color: var(--_qm-ink-label);
|
|
@@ -531,6 +535,11 @@
|
|
|
531
535
|
background: var(--_qm-danger-tint);
|
|
532
536
|
color: var(--_qm-danger);
|
|
533
537
|
}
|
|
538
|
+
@media (hover: none) {
|
|
539
|
+
.qm-member-remove {
|
|
540
|
+
opacity: var(--_qm-opacity-muted);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
534
543
|
/* The default's set is what prints and nothing written: its titles, its ticks and
|
|
535
544
|
every value its columns draw take the default rung (theme.css), and the first
|
|
536
545
|
gesture, writing it, takes it off. */
|
|
@@ -541,7 +550,6 @@
|
|
|
541
550
|
}
|
|
542
551
|
.qm-matrix-roster[data-default] :is(.qm-tick-input:checked, .qm-tick-fixed) {
|
|
543
552
|
background: var(--_qm-accent-default);
|
|
544
|
-
border-color: var(--_qm-accent-default);
|
|
545
553
|
}
|
|
546
554
|
.qm-matrix-roster[data-default] :global(.qm-toggle[data-state='checked']) {
|
|
547
555
|
background: var(--_qm-accent-default);
|
|
@@ -139,3 +139,10 @@
|
|
|
139
139
|
onkeydown={revert}
|
|
140
140
|
onchange={(e) => commit(e.currentTarget as HTMLInputElement)}
|
|
141
141
|
/>
|
|
142
|
+
|
|
143
|
+
<style>
|
|
144
|
+
/* Tabular figures, so numbers stacked in a column line up digit for digit. */
|
|
145
|
+
.qm-input {
|
|
146
|
+
font-variant-numeric: tabular-nums;
|
|
147
|
+
}
|
|
148
|
+
</style>
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
a subform is collapsed rows again, so an open row under an open row is one figure two
|
|
9
9
|
rungs in.
|
|
10
10
|
|
|
11
|
-
The nesting is a vertical at `--_qm-border`, the ladder's last stroke:
|
|
12
|
-
|
|
13
|
-
further, the properties a rung inside it (ARCHITECTURE §"A plane is a tone").
|
|
11
|
+
The nesting is a vertical at `--_qm-border`, the ladder's last stroke: an open section's
|
|
12
|
+
vertical one `--_qm-nest` in from the card's edge where the field is in one, this one a
|
|
13
|
+
rung further, the properties a rung inside it (ARCHITECTURE §"A plane is a tone").
|
|
14
14
|
|
|
15
15
|
One figure wherever it mounts, and where its stroke lands is the stacker's. A
|
|
16
16
|
variant's cells and a matrix member's columns sit at the depth a field-level subform
|
|
@@ -482,7 +482,7 @@
|
|
|
482
482
|
the card and the section hold too.
|
|
483
483
|
|
|
484
484
|
`--_qm-border`: this is the stroke that structures (ARCHITECTURE §"A plane is a
|
|
485
|
-
tone"), the one the
|
|
485
|
+
tone"), the one the section's vertical reads.
|
|
486
486
|
|
|
487
487
|
`padding-block` is the stroke's end caps, the same rung and the same reason as a
|
|
488
488
|
section's (`Card.svelte`). A cap and not a leading gap: what stands the subform off
|
|
@@ -208,11 +208,6 @@
|
|
|
208
208
|
.qm-prose-block:not(.qm-control-box) {
|
|
209
209
|
font-size: var(--_qm-text-paper);
|
|
210
210
|
}
|
|
211
|
-
/* Paper's margin, which a field has none of. On the editable, so a press in the strip
|
|
212
|
-
lands a caret; carried by the card's inset it would be dead width. */
|
|
213
|
-
.qm-prose-block:not(.qm-control-box) :global(.ProseMirror) {
|
|
214
|
-
padding-inline: var(--_qm-space-2);
|
|
215
|
-
}
|
|
216
211
|
/* The caret is the body's focus indicator: a ring around the one surface in a card
|
|
217
212
|
that is paper reads as form chrome. So the contenteditable's own outline is dropped
|
|
218
213
|
everywhere, and a boxed leaf takes the shared ring on its wrapper instead
|
|
@@ -93,12 +93,12 @@
|
|
|
93
93
|
paints over the rest. The two read as one block, which is what document-level
|
|
94
94
|
guidance is: the main card's, not a card of its own. Still in-flow, no lift.
|
|
95
95
|
|
|
96
|
-
No stroke
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
No stroke, as `main` draws none: the pair's edge is where the two fills meet the
|
|
97
|
+
ground. What shows below the seam is the slip's tint, a step off `main`'s plane, so it
|
|
98
|
+
reads as a slip of the card rather than as a second card.
|
|
99
99
|
|
|
100
|
-
The bottom corners
|
|
101
|
-
|
|
100
|
+
The bottom corners are the card's own, so the pair stands in the stack with the same
|
|
101
|
+
silhouette as every island beside it: the tint is what says slip, once.
|
|
102
102
|
|
|
103
103
|
Down, the insets are the document's own rhythm and they are measured to the ink, not
|
|
104
104
|
to the boxes. The top is a block gap below the seam, what two of the body's paragraphs
|
|
@@ -109,14 +109,15 @@
|
|
|
109
109
|
slack at each edge, and spent inside the gap that is enough to make the tightest space
|
|
110
110
|
in the card measure as the widest.
|
|
111
111
|
|
|
112
|
-
Across, the inset is
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
Across, the inset is the card's inline one and then its text column, so the tip starts
|
|
113
|
+
on the edge the body's lines do and the foot's label ends where a card's delete glyph
|
|
114
|
+
does: the face and the tint already set the tip apart, and an indent would say it a
|
|
115
|
+
third time. */
|
|
115
116
|
.qm-tips {
|
|
116
117
|
margin-top: calc(var(--_qm-radius) * -1);
|
|
117
|
-
border-radius: 0 0 var(--_qm-radius
|
|
118
|
+
border-radius: 0 0 var(--_qm-radius) var(--_qm-radius);
|
|
118
119
|
padding: calc(var(--_qm-space-3) + var(--_qm-radius))
|
|
119
|
-
calc(var(--_qm-
|
|
120
|
+
calc(var(--_qm-space-2) + var(--_qm-column)) var(--_qm-space-3);
|
|
120
121
|
background: var(--_qm-tips-surface);
|
|
121
122
|
display: flex;
|
|
122
123
|
flex-direction: column;
|
|
@@ -149,9 +149,11 @@
|
|
|
149
149
|
</div>
|
|
150
150
|
|
|
151
151
|
<style>
|
|
152
|
+
/* The distance from the discriminant to the cells it unfolds is the one an array row
|
|
153
|
+
and a matrix member keep from what they unfold. */
|
|
152
154
|
.qm-variant {
|
|
153
155
|
display: flex;
|
|
154
156
|
flex-direction: column;
|
|
155
|
-
gap: var(--_qm-space
|
|
157
|
+
gap: var(--_qm-space);
|
|
156
158
|
}
|
|
157
159
|
</style>
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
import { tipsChannel } from './tips.js';
|
|
83
83
|
import { patchEditorExt } from './ext.js';
|
|
84
84
|
import { raiseGuidance } from './guidance.js';
|
|
85
|
+
import { familiesOf, registerFaces } from './fonts.js';
|
|
85
86
|
import Card from './Card.svelte';
|
|
86
87
|
import Icon from './icons/Icon.svelte';
|
|
87
88
|
import TipsCard from './TipsCard.svelte';
|
|
@@ -100,6 +101,8 @@
|
|
|
100
101
|
diagnostics,
|
|
101
102
|
enumOptionAllowed,
|
|
102
103
|
enumDisallowed = 'disable',
|
|
104
|
+
fonts,
|
|
105
|
+
fontLabels,
|
|
103
106
|
strings,
|
|
104
107
|
formatDiagnostic,
|
|
105
108
|
class: className,
|
|
@@ -119,6 +122,11 @@
|
|
|
119
122
|
}
|
|
120
123
|
});
|
|
121
124
|
|
|
125
|
+
// The host's font library: registered with the page while mounted, so a run draws in
|
|
126
|
+
// the face the preview sets it in, and offered family by family to the popover.
|
|
127
|
+
const fontFamilies = $derived(familiesOf(fonts ?? []));
|
|
128
|
+
$effect(() => registerFaces(fonts ?? []));
|
|
129
|
+
|
|
122
130
|
// ── Reactivity + session identity ───────────────────────────────────────────
|
|
123
131
|
let revision = $state(0);
|
|
124
132
|
// This editor's own id, prefixed onto every field's DOM ids (`domid.ts`). The
|
|
@@ -966,7 +974,7 @@
|
|
|
966
974
|
{/each}
|
|
967
975
|
</div>
|
|
968
976
|
|
|
969
|
-
<FormatPopover {getActiveLeaf} />
|
|
977
|
+
<FormatPopover {getActiveLeaf} families={fontFamilies} {fontLabels} />
|
|
970
978
|
|
|
971
979
|
<!-- The add affordance is gated on the schema declaring `card_kinds`, there being
|
|
972
980
|
nothing to seed otherwise. Cards render regardless: a kind with no schema degrades to
|
|
@@ -1057,7 +1065,8 @@
|
|
|
1057
1065
|
than as more card (ARCHITECTURE §Styling). The inset is the widest rung on the
|
|
1058
1066
|
ramp, which is what gives the ground enough of itself to read as ground: at a
|
|
1059
1067
|
narrower one the stack meets whatever frames the surface and the islands are
|
|
1060
|
-
back to being one block clipped at its edges.
|
|
1068
|
+
back to being one block clipped at its edges. That is the page form; a pane's
|
|
1069
|
+
frame is the host's, and it spends less (`.qm-pane` below). */
|
|
1061
1070
|
:global(:where(.qm-editor)) {
|
|
1062
1071
|
box-sizing: border-box;
|
|
1063
1072
|
display: flex;
|
|
@@ -1090,11 +1099,18 @@
|
|
|
1090
1099
|
`.qm-pane` claims (THEMING §"Drop it in"), and it resolves to `auto` on a mounting
|
|
1091
1100
|
site with no height of its own, which is the page form kept. The cards do not
|
|
1092
1101
|
shrink to fit: a flex item's automatic minimum floors each at its own height, so
|
|
1093
|
-
the stack overflows and scrolls rather than squashing.
|
|
1102
|
+
the stack overflows and scrolls rather than squashing.
|
|
1103
|
+
|
|
1104
|
+
A pane is where the host has already framed the surface, so the ground needs less
|
|
1105
|
+
of itself to read as ground: the frame is what the stack would otherwise meet. So
|
|
1106
|
+
the gutter steps in to the seam the add strip holds between cards, the ground
|
|
1107
|
+
being one width all round the stack, and the width it gives back goes to what is
|
|
1108
|
+
written. */
|
|
1094
1109
|
:global(:where(.qm-editor.qm-pane)) {
|
|
1095
1110
|
height: 100%;
|
|
1096
1111
|
overflow: auto;
|
|
1097
1112
|
overscroll-behavior: contain;
|
|
1113
|
+
padding: var(--_qm-space-4);
|
|
1098
1114
|
padding-block-end: var(--_qm-tail);
|
|
1099
1115
|
}
|
|
1100
1116
|
/* The stack's one gapless seam, which `TipsCard` draws and this holds the two
|
package/dist/visual/controls.css
CHANGED
|
@@ -339,9 +339,14 @@
|
|
|
339
339
|
over content the surface did not choose, so there is no plane under it for a
|
|
340
340
|
tone to step off (ARCHITECTURE §Styling). The edge and `--_qm-lift` carry it
|
|
341
341
|
there, and the raised rung joins them in dark, where a tone still has somewhere
|
|
342
|
-
to go.
|
|
342
|
+
to go.
|
|
343
|
+
|
|
344
|
+
The inset is the difference between the two radii, `--_qm-space` between the
|
|
345
|
+
surface's `--_qm-radius` and the item's `--_qm-radius-inner`, so a highlighted
|
|
346
|
+
item's corners run concentric with the surface's. It is the format popover's inset
|
|
347
|
+
too: every floating surface holds its contents one rung in. */
|
|
343
348
|
:where(.qm-menu-surface) {
|
|
344
|
-
padding: var(--_qm-space
|
|
349
|
+
padding: var(--_qm-space);
|
|
345
350
|
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
346
351
|
border-radius: var(--_qm-radius);
|
|
347
352
|
background: var(--_qm-surface-raised);
|