@quillmark/svelte 0.0.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/LICENSE +201 -0
- package/NOTICE +58 -0
- package/README.md +241 -0
- package/THEMING.md +153 -0
- package/dist/core/address.d.ts +56 -0
- package/dist/core/address.js +95 -0
- package/dist/core/bloom.d.ts +37 -0
- package/dist/core/bloom.js +116 -0
- package/dist/core/codec/anchor.d.ts +19 -0
- package/dist/core/codec/anchor.js +36 -0
- package/dist/core/codec/code.d.ts +12 -0
- package/dist/core/codec/code.js +118 -0
- package/dist/core/codec/decode.d.ts +24 -0
- package/dist/core/codec/decode.js +270 -0
- package/dist/core/codec/encode.d.ts +71 -0
- package/dist/core/codec/encode.js +480 -0
- package/dist/core/codec/field.d.ts +171 -0
- package/dist/core/codec/field.js +493 -0
- package/dist/core/codec/index.d.ts +22 -0
- package/dist/core/codec/index.js +43 -0
- package/dist/core/codec/inputrules.d.ts +7 -0
- package/dist/core/codec/inputrules.js +168 -0
- package/dist/core/codec/islands.d.ts +36 -0
- package/dist/core/codec/islands.js +82 -0
- package/dist/core/codec/keymap.d.ts +13 -0
- package/dist/core/codec/keymap.js +29 -0
- package/dist/core/codec/lists.d.ts +22 -0
- package/dist/core/codec/lists.js +104 -0
- package/dist/core/codec/marks.d.ts +37 -0
- package/dist/core/codec/marks.js +74 -0
- package/dist/core/codec/positions.d.ts +17 -0
- package/dist/core/codec/positions.js +108 -0
- package/dist/core/codec/prose.css +458 -0
- package/dist/core/codec/reconcile.d.ts +26 -0
- package/dist/core/codec/reconcile.js +64 -0
- package/dist/core/codec/schema.d.ts +13 -0
- package/dist/core/codec/schema.js +155 -0
- package/dist/core/codec/slash.d.ts +79 -0
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.d.ts +50 -0
- package/dist/core/codec/table-view.js +928 -0
- package/dist/core/codec/table.d.ts +85 -0
- package/dist/core/codec/table.js +224 -0
- package/dist/core/errors.d.ts +73 -0
- package/dist/core/errors.js +24 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +19 -0
- package/dist/core/lifecycle.d.ts +7 -0
- package/dist/core/lifecycle.js +21 -0
- package/dist/core/rebind.svelte.d.ts +18 -0
- package/dist/core/rebind.svelte.js +44 -0
- package/dist/core/teardown.d.ts +20 -0
- package/dist/core/teardown.js +37 -0
- package/dist/core/theme.css +276 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -0
- package/dist/preset/index.css +10 -0
- package/dist/preset/recipes.css +188 -0
- package/dist/preset/scale.css +97 -0
- package/dist/preview/Preview.svelte +118 -0
- package/dist/preview/Preview.svelte.d.ts +39 -0
- package/dist/preview/bridge.d.ts +13 -0
- package/dist/preview/bridge.js +88 -0
- package/dist/preview/controller.d.ts +40 -0
- package/dist/preview/controller.js +141 -0
- package/dist/preview/geometry.d.ts +37 -0
- package/dist/preview/geometry.js +45 -0
- package/dist/preview/index.d.ts +5 -0
- package/dist/preview/index.js +14 -0
- package/dist/preview/overlay.d.ts +10 -0
- package/dist/preview/overlay.js +82 -0
- package/dist/preview/paint.d.ts +17 -0
- package/dist/preview/paint.js +262 -0
- package/dist/preview/strings.d.ts +14 -0
- package/dist/preview/strings.js +22 -0
- package/dist/visual/ArrayField.svelte +362 -0
- package/dist/visual/ArrayField.svelte.d.ts +24 -0
- package/dist/visual/BooleanField.svelte +88 -0
- package/dist/visual/BooleanField.svelte.d.ts +20 -0
- package/dist/visual/Card.svelte +849 -0
- package/dist/visual/Card.svelte.d.ts +43 -0
- package/dist/visual/CardControls.svelte +92 -0
- package/dist/visual/CardControls.svelte.d.ts +11 -0
- package/dist/visual/DateField.svelte +203 -0
- package/dist/visual/DateField.svelte.d.ts +28 -0
- package/dist/visual/DiagnosticList.svelte +50 -0
- package/dist/visual/DiagnosticList.svelte.d.ts +7 -0
- package/dist/visual/EnumField.svelte +169 -0
- package/dist/visual/EnumField.svelte.d.ts +25 -0
- package/dist/visual/Field.svelte +270 -0
- package/dist/visual/Field.svelte.d.ts +37 -0
- package/dist/visual/FieldHint.svelte +185 -0
- package/dist/visual/FieldHint.svelte.d.ts +13 -0
- package/dist/visual/FieldLabel.svelte +141 -0
- package/dist/visual/FieldLabel.svelte.d.ts +19 -0
- package/dist/visual/FormatPopover.svelte +396 -0
- package/dist/visual/FormatPopover.svelte.d.ts +9 -0
- package/dist/visual/NumberField.svelte +72 -0
- package/dist/visual/NumberField.svelte.d.ts +19 -0
- package/dist/visual/ObjectField.svelte +131 -0
- package/dist/visual/ObjectField.svelte.d.ts +17 -0
- package/dist/visual/ProseArrayElement.svelte +98 -0
- package/dist/visual/ProseArrayElement.svelte.d.ts +19 -0
- package/dist/visual/ProseField.svelte +213 -0
- package/dist/visual/ProseField.svelte.d.ts +50 -0
- package/dist/visual/SlashMenu.svelte +126 -0
- package/dist/visual/SlashMenu.svelte.d.ts +14 -0
- package/dist/visual/TextField.svelte +63 -0
- package/dist/visual/TextField.svelte.d.ts +23 -0
- package/dist/visual/TipsCard.svelte +191 -0
- package/dist/visual/TipsCard.svelte.d.ts +13 -0
- package/dist/visual/VisualEditor.svelte +96 -0
- package/dist/visual/VisualEditor.svelte.d.ts +14 -0
- package/dist/visual/VisualEditorInner.svelte +1032 -0
- package/dist/visual/VisualEditorInner.svelte.d.ts +17 -0
- package/dist/visual/controls.css +272 -0
- package/dist/visual/diagnostics.d.ts +52 -0
- package/dist/visual/diagnostics.js +83 -0
- package/dist/visual/domid.d.ts +21 -0
- package/dist/visual/domid.js +36 -0
- package/dist/visual/ext.d.ts +19 -0
- package/dist/visual/ext.js +29 -0
- package/dist/visual/icons/Icon.svelte +39 -0
- package/dist/visual/icons/Icon.svelte.d.ts +12 -0
- package/dist/visual/icons/nodes.d.ts +103 -0
- package/dist/visual/icons/nodes.js +55 -0
- package/dist/visual/index.d.ts +9 -0
- package/dist/visual/index.js +24 -0
- package/dist/visual/motion.d.ts +18 -0
- package/dist/visual/motion.js +53 -0
- package/dist/visual/props.d.ts +77 -0
- package/dist/visual/props.js +1 -0
- package/dist/visual/signals.d.ts +59 -0
- package/dist/visual/signals.js +1 -0
- package/dist/visual/strings.d.ts +123 -0
- package/dist/visual/strings.js +80 -0
- package/dist/visual/structure.d.ts +224 -0
- package/dist/visual/structure.js +270 -0
- package/dist/visual/synced.svelte.d.ts +15 -0
- package/dist/visual/synced.svelte.js +34 -0
- package/dist/visual/tips.d.ts +24 -0
- package/dist/visual/tips.js +50 -0
- package/package.json +102 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type DocPath } from '../core/address.js';
|
|
2
|
+
import type { ContentHit } from '@quillmark/wasm';
|
|
3
|
+
import type { VisualEditorProps } from './props.js';
|
|
4
|
+
import type { CardId } from './signals.js';
|
|
5
|
+
import type { FieldController } from '../core/codec/index.js';
|
|
6
|
+
import './controls.css';
|
|
7
|
+
declare const VisualEditorInner: import("svelte").Component<VisualEditorProps, {
|
|
8
|
+
setCaret: (hit: ContentHit) => Promise<void>;
|
|
9
|
+
getActiveLeaf: () => FieldController | undefined;
|
|
10
|
+
focusField: (field: DocPath) => Promise<void>;
|
|
11
|
+
insertCard: (kind: string, at?: number) => CardId | undefined;
|
|
12
|
+
removeCard: (cardId: CardId) => void;
|
|
13
|
+
moveCard: (cardId: CardId, dir: -1 | 1) => void;
|
|
14
|
+
setKind: (cardId: CardId, kind: string) => void;
|
|
15
|
+
}, "">;
|
|
16
|
+
type VisualEditorInner = ReturnType<typeof VisualEditorInner>;
|
|
17
|
+
export default VisualEditorInner;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/* The control recipes more than one component draws: the focus ring, the box a
|
|
2
|
+
typed value sits in, and the square icon button. They live here rather than per
|
|
3
|
+
component because Svelte scopes a `<style>` block to the component that owns the
|
|
4
|
+
markup: a component that carries a shared class without also carrying its rule
|
|
5
|
+
ships unstyled, and nothing raises it.
|
|
6
|
+
|
|
7
|
+
NOT in `core/theme.css`: that file is the DERIVATION (public dials in, the
|
|
8
|
+
private `--_qm-*` scale out) and it mints values. This file mints none; every
|
|
9
|
+
declaration reads a rung, exactly as a component's own block must; `check:style`
|
|
10
|
+
holds that over `.css` too.
|
|
11
|
+
|
|
12
|
+
`@layer qm.chrome` is the second layer `theme.css` declares. Layered rules lose
|
|
13
|
+
to unlayered ones, which is the point twice over: a consumer's stylesheet beats
|
|
14
|
+
the chrome without `!important`, and a component's own scoped block still
|
|
15
|
+
overrides its shared base: `.qm-card-delete` recolours a `.qm-icon-btn`, and
|
|
16
|
+
`.qm-json` adds the monospace face over `.qm-input`, with no specificity fight. */
|
|
17
|
+
|
|
18
|
+
@layer qm.chrome {
|
|
19
|
+
/* The themed focus ring in place of the raw UA outline, as a
|
|
20
|
+
RULE rather than a pair of rungs each control assembles. `--_qm-ring-focus`
|
|
21
|
+
fixes the VALUE; which properties draw a ring, and on what state, is the other
|
|
22
|
+
half of the decision, and `check:style` sees a minted literal but never a
|
|
23
|
+
divergent rule. A control opts in by carrying the marker class.
|
|
24
|
+
|
|
25
|
+
Two selectors, because focus lands in two places. Usually on the control
|
|
26
|
+
itself; but the date field's focus lives on a SEGMENT, so it rings the field
|
|
27
|
+
around it: a per-segment ring flickers across the field as the caret walks
|
|
28
|
+
it. That is the only distinction, and this pair is the whole of it. */
|
|
29
|
+
.qm-focus-ring:focus-visible,
|
|
30
|
+
.qm-focus-ring-within:focus-within {
|
|
31
|
+
outline: var(--_qm-ring-focus);
|
|
32
|
+
outline-offset: var(--_qm-ring-offset);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* THE TYPE HALF, for both recipe families at once. A UA control inherits neither
|
|
36
|
+
family nor size, so a control or a button that declares none renders in the UA
|
|
37
|
+
face at the UA size: `--qm-font`, a documented dial, stopping short of the
|
|
38
|
+
package's own surfaces with nothing raised, since an absence mints no literal
|
|
39
|
+
for `check:style`. `font: inherit` first, then the BODY rung off the ramp:
|
|
40
|
+
inheriting the size lands the surface on the host page's body size, a rung the
|
|
41
|
+
scale never minted and one a consumer moves by restyling their page rather than
|
|
42
|
+
by turning `--qm-font-size`.
|
|
43
|
+
|
|
44
|
+
ONE rule over both families rather than the same two declarations in each,
|
|
45
|
+
because the drift a copy invites here is a box and the button beside it reading
|
|
46
|
+
DIFFERENT rungs, which no gate can see: `check:style`'s type axis holds that
|
|
47
|
+
each side reads a rung, never that both read the same one.
|
|
48
|
+
|
|
49
|
+
The list is wider than either recipe's chrome: the body leaf withholds the box
|
|
50
|
+
and still reads the ramp, so `.qm-prose` is
|
|
51
|
+
here and not below.
|
|
52
|
+
|
|
53
|
+
`font` is a shorthand carrying `line-height`, so each family's leading is
|
|
54
|
+
declared in the rules BELOW and would be dropped in silence above them. */
|
|
55
|
+
.qm-control-box,
|
|
56
|
+
.qm-input,
|
|
57
|
+
.qm-prose,
|
|
58
|
+
.qm-icon-btn,
|
|
59
|
+
.qm-add-affordance {
|
|
60
|
+
font: inherit;
|
|
61
|
+
font-size: var(--_qm-text-body);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* A typed value's box (every scalar control and every prose leaf): the text and
|
|
65
|
+
numeric inputs, the array's JSON textarea, the enum trigger, the date field, and
|
|
66
|
+
the inline richtext leaves. ONE rule, because a
|
|
67
|
+
leaf that agrees with the input beside it "by construction" has to draw the same
|
|
68
|
+
declarations, not a copy of them: five hand-kept copies agree until one is
|
|
69
|
+
edited, and nothing raises it (no literal is minted, so `check:style` stays
|
|
70
|
+
green while a control renders subtly the wrong height).
|
|
71
|
+
|
|
72
|
+
LEADING sits on the wide list rather than on the box's own, because a box's
|
|
73
|
+
height is a padding rung plus a line box plus two hairlines: a leaf and the
|
|
74
|
+
input beside it agree on height exactly as long as they agree on leading, which
|
|
75
|
+
is the whole of what "by construction" buys.
|
|
76
|
+
|
|
77
|
+
COLOUR is the trap one property over: `font` does not carry it either, so an
|
|
78
|
+
input or trigger left off this line falls to the UA's own text colour: a second
|
|
79
|
+
black sitting beside the card's ink rather than the rung it renders on.
|
|
80
|
+
`caret-color` is pinned to `currentColor` rather than left at its `auto`
|
|
81
|
+
initial, which resolves the same way: the prose leaf's caret IS its focus
|
|
82
|
+
indicator, the outline dropped in its favour (`ProseField`), so it tracks
|
|
83
|
+
this rule's own ink deliberately. */
|
|
84
|
+
.qm-control-box,
|
|
85
|
+
.qm-input,
|
|
86
|
+
.qm-prose {
|
|
87
|
+
line-height: var(--_qm-leading-body);
|
|
88
|
+
color: var(--_qm-ink);
|
|
89
|
+
caret-color: currentColor;
|
|
90
|
+
}
|
|
91
|
+
.qm-control-box,
|
|
92
|
+
.qm-input {
|
|
93
|
+
padding: var(--_qm-space) var(--_qm-space-2);
|
|
94
|
+
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
95
|
+
border-radius: var(--_qm-radius-inner);
|
|
96
|
+
background: var(--_qm-surface);
|
|
97
|
+
}
|
|
98
|
+
/* What a form control adds over the box: it does not fill its row on its own. */
|
|
99
|
+
.qm-input {
|
|
100
|
+
width: 100%;
|
|
101
|
+
box-sizing: border-box;
|
|
102
|
+
}
|
|
103
|
+
/* The native placeholder is the ghost rung, not the UA's own grey: a text or
|
|
104
|
+
number control's placeholder is the resolved `default:` (Field's `defaultStr`)
|
|
105
|
+
and reads as shown-never-written the same way the prose leaf's empty-state
|
|
106
|
+
ghost does (`ProseField`). */
|
|
107
|
+
.qm-input::placeholder {
|
|
108
|
+
color: var(--_qm-ink-ghost);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* The second recipe, and it is not the box above: a field is a box because you
|
|
112
|
+
type into it, a button is not a box because you press it. No resting border and
|
|
113
|
+
no resting fill; the
|
|
114
|
+
surface arrives on hover, off the rung the enum listbox already highlights with.
|
|
115
|
+
Deliberately no `color`: the callers disagree on purpose (the card delete is
|
|
116
|
+
`--_qm-danger`, the array remove inherits its row's ink), and a base colour here
|
|
117
|
+
would flatten that distinction rather than share a recipe.
|
|
118
|
+
|
|
119
|
+
`--_qm-tap-min` is WCAG 2.5.8's floor, read rather than re-expressed per
|
|
120
|
+
control: `min-*` is outside every `check:style` axis, so a second expression of
|
|
121
|
+
the same threshold has nothing watching it. `box-sizing` is what makes it a BOX
|
|
122
|
+
floor: a `<button>` takes border-box from the UA sheet and a plain element does
|
|
123
|
+
not, so a control on this recipe that is not a button clears the threshold on its
|
|
124
|
+
CONTENT box and stands a padding taller than the button beside it. Declared here,
|
|
125
|
+
the floor means the same height whatever element carries it; and a caller that
|
|
126
|
+
stretches one of these to its row gets a width the padding sits inside.
|
|
127
|
+
|
|
128
|
+
Both button families are named here rather than opting in through a marker: a
|
|
129
|
+
glyph button and an add affordance are not SOMETIMES flat, so a class they could
|
|
130
|
+
be built without is a class one of them will eventually miss.
|
|
131
|
+
|
|
132
|
+
The type half is the shared rule above, which both families are named in; what
|
|
133
|
+
diverges is the LEADING. Where the box's height is a line box, a button's is
|
|
134
|
+
`--_qm-tap-min`: a label is one line and a glyph is not text at all, so the line
|
|
135
|
+
box collapses onto its content (`1`) and the floor decides, rather than two
|
|
136
|
+
height sources competing. That is the same structural `1` a glyph row takes,
|
|
137
|
+
which is why it is here and not on one family, and it is what keeps a button off
|
|
138
|
+
the host page's leading the way the size rung keeps it off the host page's body
|
|
139
|
+
size. A caller whose button carries a LABEL restates the rung it wants from its
|
|
140
|
+
own block: the tips foot takes the meta rung, which is that surface's decision,
|
|
141
|
+
not the family's. */
|
|
142
|
+
.qm-icon-btn,
|
|
143
|
+
.qm-add-affordance {
|
|
144
|
+
line-height: 1;
|
|
145
|
+
box-sizing: border-box;
|
|
146
|
+
min-width: var(--_qm-tap-min);
|
|
147
|
+
min-height: var(--_qm-tap-min);
|
|
148
|
+
border: none;
|
|
149
|
+
background: transparent;
|
|
150
|
+
border-radius: var(--_qm-radius-inner);
|
|
151
|
+
cursor: pointer;
|
|
152
|
+
transition: background-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
153
|
+
}
|
|
154
|
+
.qm-icon-btn:hover:not(:disabled),
|
|
155
|
+
.qm-add-affordance:hover:not(:disabled) {
|
|
156
|
+
background: var(--_qm-surface-hover);
|
|
157
|
+
}
|
|
158
|
+
.qm-icon-btn:disabled,
|
|
159
|
+
.qm-add-affordance:disabled {
|
|
160
|
+
opacity: var(--_qm-opacity-idle);
|
|
161
|
+
cursor: default;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* A glyph button: card reorder/delete, array remove, the field label's guidance
|
|
165
|
+
marker, the tips card's foot. */
|
|
166
|
+
.qm-icon-btn {
|
|
167
|
+
display: inline-flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
justify-content: center;
|
|
170
|
+
gap: var(--_qm-space-half);
|
|
171
|
+
padding: var(--_qm-space-half) var(--_qm-space);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* An add affordance: the between-card trigger and the array foot's. A pill rather
|
|
175
|
+
than the inner radius, because it FILLS on hover instead of tinting ink: both rest
|
|
176
|
+
unfilled on `--_qm-opacity-idle`, so a hover shifting ink alone would have little
|
|
177
|
+
to shift. The recede ladder itself stays with each caller: it is driven by their
|
|
178
|
+
own hover/focus state. */
|
|
179
|
+
.qm-add-affordance {
|
|
180
|
+
border-radius: var(--_qm-radius-pill);
|
|
181
|
+
color: var(--_qm-ink-label);
|
|
182
|
+
transition:
|
|
183
|
+
opacity var(--_qm-duration-fast) var(--_qm-ease-reverse),
|
|
184
|
+
background-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
185
|
+
}
|
|
186
|
+
.qm-add-affordance:hover {
|
|
187
|
+
color: var(--_qm-ink);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* A floating list of choices: the enum's listbox and the card stack's kind menu.
|
|
191
|
+
Both portal into the nearest `[data-qm-root]`, and both are PICKED FROM, so the
|
|
192
|
+
surface (its edge, its inset, its ink) and the item (its pad, its radius, the
|
|
193
|
+
rung a highlight fills with) are one recipe rather than two that agree until one
|
|
194
|
+
is edited. What a caller adds over it is its own: the enum sizes itself off its
|
|
195
|
+
trigger and weights the stored value.
|
|
196
|
+
|
|
197
|
+
`[data-highlighted]` is bits-ui's mark and it is the ONLY hover lane: an item
|
|
198
|
+
under the pointer and an item under the keyboard cursor are one state to a menu,
|
|
199
|
+
so the primitive marks both and a `:hover` rule beside it would paint a second
|
|
200
|
+
highlight the keyboard cannot reach. */
|
|
201
|
+
.qm-menu-surface {
|
|
202
|
+
padding: var(--_qm-space-half);
|
|
203
|
+
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
204
|
+
border-radius: var(--_qm-radius);
|
|
205
|
+
background: var(--_qm-surface);
|
|
206
|
+
font-size: var(--_qm-text-body);
|
|
207
|
+
color: var(--_qm-ink);
|
|
208
|
+
}
|
|
209
|
+
.qm-menu-item {
|
|
210
|
+
padding: var(--_qm-space) var(--_qm-space-2);
|
|
211
|
+
border-radius: var(--_qm-radius-inner);
|
|
212
|
+
cursor: pointer;
|
|
213
|
+
user-select: none;
|
|
214
|
+
}
|
|
215
|
+
.qm-menu-item[data-highlighted] {
|
|
216
|
+
background: var(--_qm-surface-hover);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* A floating surface that is READ rather than picked from: the selection popover
|
|
220
|
+
and the field label's guidance. The theme surface mixed toward transparent
|
|
221
|
+
behind a backdrop blur, so the page reads faintly through it and the surface
|
|
222
|
+
rung stays the mix base, which is what keeps `--qm-bg` tinting it. The lists
|
|
223
|
+
above stay opaque instead: a row of choices reads through a blur worse than a
|
|
224
|
+
row of glyphs does.
|
|
225
|
+
|
|
226
|
+
The raise and the dismissal are the recipe's too, one existence↔absence trip
|
|
227
|
+
taken twice, which is why the class goes on the PRESENCE
|
|
228
|
+
node (bits-ui's own content element, reached through its `child` snippet)
|
|
229
|
+
rather than on a pill inside it: the node's animations are what the primitive
|
|
230
|
+
waits on before unmounting, and an animation one level in is a fade it never
|
|
231
|
+
sees. Both frames are the inner box's own; floating-ui's outer wrapper, whose
|
|
232
|
+
transform is doing the positioning, is untouched. What a caller adds over this
|
|
233
|
+
is its own inset, its measure, and its type.
|
|
234
|
+
|
|
235
|
+
`data-ending-style` is the primitive's mark for the leaving half. The ink is
|
|
236
|
+
not all that leaves with it: a surface fading out is not a thing to click or to
|
|
237
|
+
read. That half is the caller's `inert` rather than a rule here, because the
|
|
238
|
+
primitive styles the same node inline with `pointer-events: auto`, which no
|
|
239
|
+
layered rule outranks; the attribute settles it without a cascade fight and
|
|
240
|
+
takes the leaving surface out of the tab order and the a11y tree with it. */
|
|
241
|
+
.qm-popover-surface {
|
|
242
|
+
background: var(--_qm-surface-popover);
|
|
243
|
+
backdrop-filter: blur(var(--_qm-blur));
|
|
244
|
+
-webkit-backdrop-filter: blur(var(--_qm-blur));
|
|
245
|
+
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
246
|
+
border-radius: var(--_qm-radius);
|
|
247
|
+
animation: qm-pop-in var(--_qm-duration-fast) var(--_qm-ease-arrive);
|
|
248
|
+
}
|
|
249
|
+
.qm-popover-surface[data-ending-style] {
|
|
250
|
+
animation: qm-pop-out var(--_qm-duration-fast) var(--_qm-ease-depart);
|
|
251
|
+
}
|
|
252
|
+
@keyframes qm-pop-in {
|
|
253
|
+
from {
|
|
254
|
+
opacity: 0;
|
|
255
|
+
transform: scale(0.94);
|
|
256
|
+
}
|
|
257
|
+
to {
|
|
258
|
+
opacity: 1;
|
|
259
|
+
transform: none;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
@keyframes qm-pop-out {
|
|
263
|
+
from {
|
|
264
|
+
opacity: 1;
|
|
265
|
+
transform: none;
|
|
266
|
+
}
|
|
267
|
+
to {
|
|
268
|
+
opacity: 0;
|
|
269
|
+
transform: scale(0.94);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Diagnostic } from '@quillmark/wasm';
|
|
2
|
+
/** A field's routing address. `card` is `undefined` for the main card; a
|
|
3
|
+
* composable card slot is a stable session id (the editor's own bookkeeping)
|
|
4
|
+
* once resolved, or an ABSOLUTE document-array index straight off a parsed
|
|
5
|
+
* `DocPath` (resolve via {@link resolveCardKey} before merging with id-keyed
|
|
6
|
+
* sources). `field` `undefined` addresses the body (the field-less leaf). */
|
|
7
|
+
export interface FieldKey {
|
|
8
|
+
card?: string | number;
|
|
9
|
+
field?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Canonical string key for a `FieldKey`: the routing `Map`'s key space. */
|
|
12
|
+
export declare function fieldKeyToString(k: FieldKey): string;
|
|
13
|
+
/**
|
|
14
|
+
* Route a canonical `DocPath` (a `Diagnostic.path`, or a `ContentHit` /
|
|
15
|
+
* `FieldRegion` field address; one grammar) to a `FieldKey`, `card` the ABSOLUTE
|
|
16
|
+
* document-array index. `/core`'s `addrForFieldPath` IS this walk — an `Addr` is a
|
|
17
|
+
* positional `FieldKey` — so routing reads the one public inverse rather than a
|
|
18
|
+
* second copy of the grammar that drifts from it. `undefined` for a path naming no
|
|
19
|
+
* single commit address (a nested / array-element path, a field-rooted one, a
|
|
20
|
+
* malformed one).
|
|
21
|
+
*/
|
|
22
|
+
export declare function parsePath(path: string): FieldKey | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a positional `FieldKey` (an ABSOLUTE document-array index, straight off
|
|
25
|
+
* {@link parsePath}) to the editor's stable-id keying using the LIVE `cardIds`
|
|
26
|
+
* array: re-resolved fresh on every call (never cached), the same "resolve only
|
|
27
|
+
* at the point of use" discipline `cardIndexOf` applies to writes (VISUAL_EDITOR
|
|
28
|
+
* §"The address is the spine"). A key already id-keyed (`card` a string) or main
|
|
29
|
+
* (`card` undefined) passes through unchanged. `undefined` when the index is out
|
|
30
|
+
* of the current card array: the diagnostic is dropped rather than mis-routed.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveCardKey(key: FieldKey, cardIds: readonly string[]): FieldKey | undefined;
|
|
33
|
+
/** One diagnostic paired with the `FieldKey` it targets. */
|
|
34
|
+
export interface RoutedDiagnostic {
|
|
35
|
+
key: FieldKey;
|
|
36
|
+
diagnostic: Diagnostic;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Route a path-keyed producer's raw `Diagnostic[]` (validate / external) to the
|
|
40
|
+
* editor's stable-id keying: the ONE door producers #1/#3 take, `parsePath` and
|
|
41
|
+
* `resolveCardKey` in a single pass. An entry drops rather than mis-routes when it
|
|
42
|
+
* carries no `path`, when `parsePath` cannot place it (a nested / array-element
|
|
43
|
+
* address), or when its absolute card index is out of the live `cardIds`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function routeAndResolve(diagnostics: Diagnostic[] | undefined, cardIds: readonly string[]): RoutedDiagnostic[];
|
|
46
|
+
/**
|
|
47
|
+
* Merge N routed groups into `Map<fieldKeyToString(key), Diagnostic[]>`.
|
|
48
|
+
* Dedupes an identical `(severity, message)` pair landing on the same key from
|
|
49
|
+
* more than one group (e.g. the same warning present in both `validate()` and
|
|
50
|
+
* an external feed); within a key, errors sort before warnings.
|
|
51
|
+
*/
|
|
52
|
+
export declare function mergeDiagnostics(...groups: RoutedDiagnostic[][]): Map<string, Diagnostic[]>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { addrForFieldPath } from '../core/address.js';
|
|
2
|
+
/** Canonical string key for a `FieldKey`: the routing `Map`'s key space. */
|
|
3
|
+
export function fieldKeyToString(k) {
|
|
4
|
+
return `${k.card ?? 'main'}:${k.field ?? '$body'}`;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Route a canonical `DocPath` (a `Diagnostic.path`, or a `ContentHit` /
|
|
8
|
+
* `FieldRegion` field address; one grammar) to a `FieldKey`, `card` the ABSOLUTE
|
|
9
|
+
* document-array index. `/core`'s `addrForFieldPath` IS this walk — an `Addr` is a
|
|
10
|
+
* positional `FieldKey` — so routing reads the one public inverse rather than a
|
|
11
|
+
* second copy of the grammar that drifts from it. `undefined` for a path naming no
|
|
12
|
+
* single commit address (a nested / array-element path, a field-rooted one, a
|
|
13
|
+
* malformed one).
|
|
14
|
+
*/
|
|
15
|
+
export function parsePath(path) {
|
|
16
|
+
return addrForFieldPath(path);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a positional `FieldKey` (an ABSOLUTE document-array index, straight off
|
|
20
|
+
* {@link parsePath}) to the editor's stable-id keying using the LIVE `cardIds`
|
|
21
|
+
* array: re-resolved fresh on every call (never cached), the same "resolve only
|
|
22
|
+
* at the point of use" discipline `cardIndexOf` applies to writes (VISUAL_EDITOR
|
|
23
|
+
* §"The address is the spine"). A key already id-keyed (`card` a string) or main
|
|
24
|
+
* (`card` undefined) passes through unchanged. `undefined` when the index is out
|
|
25
|
+
* of the current card array: the diagnostic is dropped rather than mis-routed.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveCardKey(key, cardIds) {
|
|
28
|
+
if (typeof key.card !== 'number')
|
|
29
|
+
return key;
|
|
30
|
+
const id = key.card >= 0 && key.card < cardIds.length ? cardIds[key.card] : undefined;
|
|
31
|
+
return id != null ? { card: id, field: key.field } : undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Route a path-keyed producer's raw `Diagnostic[]` (validate / external) to the
|
|
35
|
+
* editor's stable-id keying: the ONE door producers #1/#3 take, `parsePath` and
|
|
36
|
+
* `resolveCardKey` in a single pass. An entry drops rather than mis-routes when it
|
|
37
|
+
* carries no `path`, when `parsePath` cannot place it (a nested / array-element
|
|
38
|
+
* address), or when its absolute card index is out of the live `cardIds`.
|
|
39
|
+
*/
|
|
40
|
+
export function routeAndResolve(diagnostics, cardIds) {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const d of diagnostics ?? []) {
|
|
43
|
+
if (!d.path)
|
|
44
|
+
continue;
|
|
45
|
+
const key = parsePath(d.path);
|
|
46
|
+
const resolved = key && resolveCardKey(key, cardIds);
|
|
47
|
+
if (resolved)
|
|
48
|
+
out.push({ key: resolved, diagnostic: d });
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
/** Severity ordering: errors sort before warnings within a field's list (never dropped, just ranked: VISUAL_EDITOR §Diagnostics "nothing gates" extends to nothing hides). */
|
|
53
|
+
const SEVERITY_RANK = { error: 0, warning: 1 };
|
|
54
|
+
/**
|
|
55
|
+
* Merge N routed groups into `Map<fieldKeyToString(key), Diagnostic[]>`.
|
|
56
|
+
* Dedupes an identical `(severity, message)` pair landing on the same key from
|
|
57
|
+
* more than one group (e.g. the same warning present in both `validate()` and
|
|
58
|
+
* an external feed); within a key, errors sort before warnings.
|
|
59
|
+
*/
|
|
60
|
+
export function mergeDiagnostics(...groups) {
|
|
61
|
+
const byKey = new Map();
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
for (const group of groups) {
|
|
64
|
+
for (const { key, diagnostic } of group) {
|
|
65
|
+
const k = fieldKeyToString(key);
|
|
66
|
+
// Collision-proof key: JSON-encode the triple so arbitrary `message` text
|
|
67
|
+
// can never merge two distinct triples into one (a real diagnostic dropped).
|
|
68
|
+
const dedupeKey = JSON.stringify([k, diagnostic.severity, diagnostic.message]);
|
|
69
|
+
if (seen.has(dedupeKey))
|
|
70
|
+
continue;
|
|
71
|
+
seen.add(dedupeKey);
|
|
72
|
+
const arr = byKey.get(k);
|
|
73
|
+
if (arr)
|
|
74
|
+
arr.push(diagnostic);
|
|
75
|
+
else
|
|
76
|
+
byKey.set(k, [diagnostic]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const arr of byKey.values()) {
|
|
80
|
+
arr.sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]);
|
|
81
|
+
}
|
|
82
|
+
return byKey;
|
|
83
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** The three names one field spends in the DOM. */
|
|
2
|
+
export interface FieldDomIds {
|
|
3
|
+
/** The control's own id: a `<label for>` target where the control is labelable. */
|
|
4
|
+
control: string;
|
|
5
|
+
/** The label's own id: the `aria-labelledby` target where `for` cannot reach. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Where the `description` text is parked for `aria-describedby`. */
|
|
8
|
+
description: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function fieldDomIds(uid: string, leafKey: string): FieldDomIds;
|
|
11
|
+
/**
|
|
12
|
+
* A group panel's own id: what its header's `aria-controls` names, and the only
|
|
13
|
+
* name in the DOM that is a card's rather than a field's. `card` follows the leaf
|
|
14
|
+
* key's convention (`undefined` is the main card), so a panel and the fields
|
|
15
|
+
* inside it carry the same card token.
|
|
16
|
+
*
|
|
17
|
+
* The `-g-` infix cannot land on a field id: a field's is the escaped
|
|
18
|
+
* `card:field`, whose separator escapes to `-3a-`, and the two differ at exactly
|
|
19
|
+
* that position for every card token the id space mints.
|
|
20
|
+
*/
|
|
21
|
+
export declare function groupPanelId(uid: string, card: string | undefined, group: string): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// A field's DOM ids. The DOM needs three names per field (the control a `<label
|
|
2
|
+
// for>` points at, the label an `aria-labelledby` control points back to, and the
|
|
3
|
+
// parked description an `aria-describedby` reads) and they are derived from the
|
|
4
|
+
// leaf key so the DOM shares the ONE per-field identity space the registry and
|
|
5
|
+
// diagnostics already collapse to (`fieldKeyToString`), rather than minting a
|
|
6
|
+
// second.
|
|
7
|
+
//
|
|
8
|
+
// `uid` is the editor instance's own (`$props.id()`, stable across SSR and
|
|
9
|
+
// hydration). The leaf-key space is unique per EDITOR, not per document: two
|
|
10
|
+
// `VisualEditor`s on a page both hold `main:subject`, and a duplicate `id` makes
|
|
11
|
+
// `for` resolve to whichever mounted first.
|
|
12
|
+
/**
|
|
13
|
+
* `id` syntax admits far less than a leaf key spends (`main:subject`, `$body`).
|
|
14
|
+
* The escape is INJECTIVE (`-` introduces it, so `-` escapes itself), which a
|
|
15
|
+
* plain replace-with-dash is not: `a:b` and `a-b` would collapse onto one id.
|
|
16
|
+
*/
|
|
17
|
+
function escapeId(key) {
|
|
18
|
+
return key.replace(/[^A-Za-z0-9_]/g, (c) => `-${c.codePointAt(0).toString(16)}-`);
|
|
19
|
+
}
|
|
20
|
+
export function fieldDomIds(uid, leafKey) {
|
|
21
|
+
const base = `qm-${uid}-${escapeId(leafKey)}`;
|
|
22
|
+
return { control: base, label: `${base}-label`, description: `${base}-desc` };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A group panel's own id: what its header's `aria-controls` names, and the only
|
|
26
|
+
* name in the DOM that is a card's rather than a field's. `card` follows the leaf
|
|
27
|
+
* key's convention (`undefined` is the main card), so a panel and the fields
|
|
28
|
+
* inside it carry the same card token.
|
|
29
|
+
*
|
|
30
|
+
* The `-g-` infix cannot land on a field id: a field's is the escaped
|
|
31
|
+
* `card:field`, whose separator escapes to `-3a-`, and the two differ at exactly
|
|
32
|
+
* that position for every card token the id space mints.
|
|
33
|
+
*/
|
|
34
|
+
export function groupPanelId(uid, card, group) {
|
|
35
|
+
return `qm-${uid}-${escapeId(card ?? 'main')}-g-${escapeId(group)}`;
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Document, CardAddr } from '@quillmark/wasm';
|
|
2
|
+
/**
|
|
3
|
+
* Merge `patch` into `$ext.editor` on the card `addr` targets (absent `card` =
|
|
4
|
+
* main). A key whose patch value is `undefined` is DROPPED; every other key in the
|
|
5
|
+
* namespace is carried through untouched.
|
|
6
|
+
*
|
|
7
|
+
* **The namespace is the write unit.** `storeExtNamespace` replaces the namespace
|
|
8
|
+
* it targets (preserving sibling namespaces, but not sibling keys) so a writer
|
|
9
|
+
* that stores only its own key silently destroys the others, and
|
|
10
|
+
* `removeExtNamespace` destroys all of them. `tips` and `title` are siblings here,
|
|
11
|
+
* so a namespace-replacing dismissal would wipe every renamed card's title. Routing
|
|
12
|
+
* every writer through this function is what makes that unexpressible rather than
|
|
13
|
+
* merely documented: key N+1 inherits the merge instead of re-deriving it.
|
|
14
|
+
*
|
|
15
|
+
* The drop is an explicit `delete`, not a stored `undefined`: whether a JS
|
|
16
|
+
* `undefined` survives the wasm-bindgen crossing is not a property worth depending
|
|
17
|
+
* on.
|
|
18
|
+
*/
|
|
19
|
+
export declare function patchEditorExt(doc: Document, addr: CardAddr, patch: Record<string, unknown>): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge `patch` into `$ext.editor` on the card `addr` targets (absent `card` =
|
|
3
|
+
* main). A key whose patch value is `undefined` is DROPPED; every other key in the
|
|
4
|
+
* namespace is carried through untouched.
|
|
5
|
+
*
|
|
6
|
+
* **The namespace is the write unit.** `storeExtNamespace` replaces the namespace
|
|
7
|
+
* it targets (preserving sibling namespaces, but not sibling keys) so a writer
|
|
8
|
+
* that stores only its own key silently destroys the others, and
|
|
9
|
+
* `removeExtNamespace` destroys all of them. `tips` and `title` are siblings here,
|
|
10
|
+
* so a namespace-replacing dismissal would wipe every renamed card's title. Routing
|
|
11
|
+
* every writer through this function is what makes that unexpressible rather than
|
|
12
|
+
* merely documented: key N+1 inherits the merge instead of re-deriving it.
|
|
13
|
+
*
|
|
14
|
+
* The drop is an explicit `delete`, not a stored `undefined`: whether a JS
|
|
15
|
+
* `undefined` survives the wasm-bindgen crossing is not a property worth depending
|
|
16
|
+
* on.
|
|
17
|
+
*/
|
|
18
|
+
export function patchEditorExt(doc, addr, patch) {
|
|
19
|
+
// `getExtNamespace` reads just this namespace rather than serializing the whole
|
|
20
|
+
// card to fish out one `$ext` slot.
|
|
21
|
+
const next = { ...(doc.getExtNamespace(addr, 'editor') ?? {}) };
|
|
22
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
delete next[key];
|
|
25
|
+
else
|
|
26
|
+
next[key] = value;
|
|
27
|
+
}
|
|
28
|
+
doc.storeExtNamespace(addr, 'editor', next);
|
|
29
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The one glyph renderer: a name off the closed set in `nodes.ts`, drawn against
|
|
3
|
+
// the shared frame. Stroke, fill and cap are the frame rather than props — a glyph
|
|
4
|
+
// that strokes differently from its neighbours is a decision no call site has, and
|
|
5
|
+
// `currentColor` is what puts the hue on the button's `color`, where the surface's
|
|
6
|
+
// hover and open transitions already reach it.
|
|
7
|
+
import { ICONS, type IconName } from './nodes.js';
|
|
8
|
+
import type { SVGAttributes } from 'svelte/elements';
|
|
9
|
+
|
|
10
|
+
interface Props extends Omit<SVGAttributes<SVGSVGElement>, 'name'> {
|
|
11
|
+
name: IconName;
|
|
12
|
+
/** The frame's edge, in px. The card controls pass none and take the default,
|
|
13
|
+
* then size the glyph from `--_qm-glyph-control` in CSS, where the header's
|
|
14
|
+
* right hang is measured against the same number. */
|
|
15
|
+
size?: number;
|
|
16
|
+
}
|
|
17
|
+
let { name, size = 24, ...rest }: Props = $props();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<!-- `aria-hidden` ahead of the spread, so it is the default rather than the rule: every
|
|
21
|
+
glyph in this package sits inside a button that carries the name, and a call site
|
|
22
|
+
that has one to say overrides it. -->
|
|
23
|
+
<svg
|
|
24
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
25
|
+
width={size}
|
|
26
|
+
height={size}
|
|
27
|
+
viewBox="0 0 24 24"
|
|
28
|
+
fill="none"
|
|
29
|
+
stroke="currentColor"
|
|
30
|
+
stroke-width="2"
|
|
31
|
+
stroke-linecap="round"
|
|
32
|
+
stroke-linejoin="round"
|
|
33
|
+
aria-hidden="true"
|
|
34
|
+
{...rest}
|
|
35
|
+
>
|
|
36
|
+
{#each ICONS[name] as [tag, attrs]}
|
|
37
|
+
<svelte:element this={tag} {...attrs} />
|
|
38
|
+
{/each}
|
|
39
|
+
</svg>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type IconName } from './nodes.js';
|
|
2
|
+
import type { SVGAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends Omit<SVGAttributes<SVGSVGElement>, 'name'> {
|
|
4
|
+
name: IconName;
|
|
5
|
+
/** The frame's edge, in px. The card controls pass none and take the default,
|
|
6
|
+
* then size the glyph from `--_qm-glyph-control` in CSS, where the header's
|
|
7
|
+
* right hang is measured against the same number. */
|
|
8
|
+
size?: number;
|
|
9
|
+
}
|
|
10
|
+
declare const Icon: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type Icon = ReturnType<typeof Icon>;
|
|
12
|
+
export default Icon;
|