@quillmark/svelte 0.5.1 → 0.7.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.
Files changed (66) hide show
  1. package/README.md +6 -0
  2. package/THEMING.md +13 -17
  3. package/dist/core/address.d.ts +17 -3
  4. package/dist/core/address.js +20 -3
  5. package/dist/core/codec/blocks.d.ts +37 -0
  6. package/dist/core/codec/blocks.js +163 -0
  7. package/dist/core/codec/field.js +5 -6
  8. package/dist/core/codec/index.d.ts +1 -1
  9. package/dist/core/codec/index.js +3 -2
  10. package/dist/core/codec/inputrules.js +24 -144
  11. package/dist/core/codec/prose.css +14 -25
  12. package/dist/core/codec/schema.d.ts +8 -0
  13. package/dist/core/codec/schema.js +33 -1
  14. package/dist/core/codec/slash.d.ts +18 -16
  15. package/dist/core/codec/slash.js +0 -0
  16. package/dist/core/codec/table-view.d.ts +4 -0
  17. package/dist/core/codec/table-view.js +137 -158
  18. package/dist/core/codec/table.d.ts +0 -10
  19. package/dist/core/codec/table.js +0 -15
  20. package/dist/core/theme.css +357 -346
  21. package/dist/preset/recipes.css +25 -21
  22. package/dist/preview/Preview.svelte +4 -2
  23. package/dist/preview/bridge.d.ts +23 -0
  24. package/dist/preview/bridge.js +92 -35
  25. package/dist/preview/controller.d.ts +6 -5
  26. package/dist/preview/controller.js +13 -7
  27. package/dist/preview/geometry.d.ts +10 -9
  28. package/dist/preview/geometry.js +11 -13
  29. package/dist/preview/index.js +3 -2
  30. package/dist/preview/paint.js +9 -1
  31. package/dist/visual/ArrayField.svelte +246 -63
  32. package/dist/visual/ArrayField.svelte.d.ts +5 -1
  33. package/dist/visual/Card.svelte +84 -50
  34. package/dist/visual/EnumField.svelte +37 -3
  35. package/dist/visual/Field.svelte +28 -4
  36. package/dist/visual/FieldLabel.svelte +2 -2
  37. package/dist/visual/FormatPopover.svelte +13 -1
  38. package/dist/visual/ObjectField.svelte +128 -35
  39. package/dist/visual/ObjectField.svelte.d.ts +11 -4
  40. package/dist/visual/ProseArrayElement.svelte +22 -2
  41. package/dist/visual/ProseArrayElement.svelte.d.ts +1 -0
  42. package/dist/visual/SlashMenu.svelte +7 -0
  43. package/dist/visual/TipsCard.svelte +33 -13
  44. package/dist/visual/VariantField.svelte +95 -0
  45. package/dist/visual/VariantField.svelte.d.ts +21 -0
  46. package/dist/visual/VisualEditorInner.svelte +44 -21
  47. package/dist/visual/controls.css +282 -283
  48. package/dist/visual/diagnostics.d.ts +13 -11
  49. package/dist/visual/diagnostics.js +11 -9
  50. package/dist/visual/domid.d.ts +13 -0
  51. package/dist/visual/domid.js +16 -0
  52. package/dist/visual/hold.d.ts +17 -0
  53. package/dist/visual/hold.js +39 -0
  54. package/dist/visual/index.js +1 -1
  55. package/dist/visual/leaves.d.ts +9 -7
  56. package/dist/visual/links.d.ts +5 -2
  57. package/dist/visual/links.js +9 -3
  58. package/dist/visual/motion.d.ts +9 -0
  59. package/dist/visual/motion.js +10 -2
  60. package/dist/visual/strings.d.ts +15 -0
  61. package/dist/visual/strings.js +3 -0
  62. package/dist/visual/structure.d.ts +32 -6
  63. package/dist/visual/structure.js +49 -3
  64. package/package.json +2 -2
  65. package/dist/core/codec/clipboard.d.ts +0 -59
  66. package/dist/core/codec/clipboard.js +0 -202
@@ -9,315 +9,314 @@
9
9
  declaration reads a rung, exactly as a component's own block must; `check:style`
10
10
  holds that over `.css` too.
11
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. */
12
+ Every selector here is `:where()`-wrapped, so a recipe computes at specificity zero.
13
+ That is the point twice over: a consumer's stylesheet beats the chrome without
14
+ `!important`, and a component's own scoped block overrides its shared base —
15
+ `.qm-card-delete` recolours a `.qm-icon-btn`, `.qm-json` adds the monospace face over
16
+ `.qm-input` — with no specificity fight. Unlayered with it, so a host's own layered
17
+ reset never reaches these rules whatever order the sheets load in (`core/theme.css`). */
17
18
 
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`
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.
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`
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
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);
32
- outline-offset: var(--_qm-ring-offset);
33
- }
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
+ :where(.qm-focus-ring:focus-visible),
30
+ :where(.qm-focus-ring-within:focus-within) {
31
+ outline: var(--_qm-ring);
32
+ outline-offset: var(--_qm-ring-offset);
33
+ }
34
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`.
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
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.
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
48
 
49
- The list is wider than either recipe's chrome: a prose leaf withholds the box
50
- and still reads the ramp, so `.qm-prose` is
51
- here and not below. The body leaf steps off this rung onto `--_qm-text-paper`
52
- from its own block (`ProseField`).
49
+ The list is wider than either recipe's chrome: a prose leaf withholds the box
50
+ and still reads the ramp, so `.qm-prose` is
51
+ here and not below. The body leaf steps off this rung onto `--_qm-text-paper`
52
+ from its own block (`ProseField`).
53
53
 
54
- `font` is a shorthand carrying `line-height`, so each family's leading is
55
- declared in the rules below and would be dropped in silence above them. */
56
- .qm-control-box,
57
- .qm-input,
58
- .qm-prose,
59
- .qm-icon-btn,
60
- .qm-add-affordance {
61
- font: inherit;
62
- font-size: var(--_qm-text-body);
63
- }
54
+ `font` is a shorthand carrying `line-height`, so each family's leading is
55
+ declared in the rules below and would be dropped in silence above them. */
56
+ :where(.qm-control-box),
57
+ :where(.qm-input),
58
+ :where(.qm-prose),
59
+ :where(.qm-icon-btn),
60
+ :where(.qm-add-affordance) {
61
+ font: inherit;
62
+ font-size: var(--_qm-text-body);
63
+ }
64
64
 
65
- /* A typed value's box (every scalar control and every prose leaf): the text and
66
- numeric inputs, the array's JSON textarea, the enum trigger, the date field, and
67
- the inline richtext leaves. One rule, because a
68
- leaf that agrees with the input beside it "by construction" has to draw the same
69
- declarations, not a copy of them: five hand-kept copies agree until one is
70
- edited, and nothing raises it (no literal is minted, so `check:style` stays
71
- green while a control renders subtly the wrong height).
65
+ /* A typed value's box (every scalar control and every prose leaf): the text and
66
+ numeric inputs, the array's JSON textarea, the enum trigger, the date field, and
67
+ the inline richtext leaves. One rule, because a
68
+ leaf that agrees with the input beside it "by construction" has to draw the same
69
+ declarations, not a copy of them: five hand-kept copies agree until one is
70
+ edited, and nothing raises it (no literal is minted, so `check:style` stays
71
+ green while a control renders subtly the wrong height).
72
72
 
73
- Leading sits on the wide list rather than on the box's own, because a box's
74
- height is an inset rung plus a line box and nothing else: a leaf and the
75
- input beside it agree on height exactly as long as they agree on leading and on
76
- size, which is the whole of what "by construction" buys. The body leaf keeps the
77
- leading and steps off the size, which costs it nothing: it draws no box and
78
- stands in no row.
73
+ Leading sits on the wide list rather than on the box's own, because a box's
74
+ height is an inset rung plus a line box and nothing else: a leaf and the
75
+ input beside it agree on height exactly as long as they agree on leading and on
76
+ size, which is the whole of what "by construction" buys. The body leaf keeps the
77
+ leading and steps off the size, which costs it nothing: it draws no box and
78
+ stands in no row.
79
79
 
80
- Colour is the trap one property over: `font` does not carry it either, so an
81
- input or trigger left off this line falls to the UA's own text colour: a second
82
- black sitting beside the card's ink rather than the rung it renders on.
83
- `caret-color` is pinned to `currentColor` rather than left at its `auto`
84
- initial, which resolves the same way: the prose leaf's caret is its focus
85
- indicator, the outline dropped in its favour (`ProseField`), so it tracks
86
- this rule's own ink deliberately. */
87
- .qm-control-box,
88
- .qm-input,
89
- .qm-prose {
90
- line-height: var(--_qm-leading-body);
91
- color: var(--_qm-ink);
92
- caret-color: currentColor;
93
- }
94
- /* The box is A fill and not an edge (ARCHITECTURE §Styling), and the fill is the
95
- well rung: a field is a hole cut into the card rather than a plane of its own,
96
- and with no edge to carry it the step is the whole of what says so. */
97
- .qm-control-box,
98
- .qm-input {
99
- padding: var(--_qm-inset-control);
100
- border: none;
101
- border-radius: var(--_qm-radius-inner);
102
- background: var(--_qm-surface-well);
103
- }
104
- /* What a form control adds over the box: it does not fill its row on its own. */
105
- .qm-input {
106
- width: 100%;
107
- box-sizing: border-box;
108
- }
109
- /* The native placeholder is the label rung, not the UA's own grey: a text or
110
- number control's placeholder is the resolved `default:` (Field's `defaultStr`),
111
- which is content and reads at a content's contrast. What says shown-never-written
112
- is the step from the `ink` a written value takes, the same step the prose leaf's
113
- empty state reads (`ProseField`). */
114
- .qm-input::placeholder {
115
- color: var(--_qm-ink-label);
116
- }
80
+ Colour is the trap one property over: `font` does not carry it either, so an
81
+ input or trigger left off this line falls to the UA's own text colour: a second
82
+ black sitting beside the card's ink rather than the rung it renders on.
83
+ `caret-color` is pinned to `currentColor` rather than left at its `auto`
84
+ initial, which resolves the same way: the prose leaf's caret is its focus
85
+ indicator, the outline dropped in its favour (`ProseField`), so it tracks
86
+ this rule's own ink deliberately. */
87
+ :where(.qm-control-box),
88
+ :where(.qm-input),
89
+ :where(.qm-prose) {
90
+ line-height: var(--_qm-leading-body);
91
+ color: var(--_qm-ink);
92
+ caret-color: currentColor;
93
+ }
94
+ /* The box is A fill and not an edge (ARCHITECTURE §Styling), and the fill is the
95
+ well rung: a field is a hole cut into the card rather than a plane of its own,
96
+ and with no edge to carry it the step is the whole of what says so. */
97
+ :where(.qm-control-box),
98
+ :where(.qm-input) {
99
+ padding: var(--_qm-inset-control);
100
+ border: none;
101
+ border-radius: var(--_qm-radius-inner);
102
+ background: var(--_qm-surface-well);
103
+ }
104
+ /* What a form control adds over the box: it does not fill its row on its own. */
105
+ :where(.qm-input) {
106
+ width: 100%;
107
+ box-sizing: border-box;
108
+ }
109
+ /* The native placeholder is the label rung, not the UA's own grey: a text or
110
+ number control's placeholder is the resolved `default:` (Field's `defaultStr`),
111
+ which is content and reads at a content's contrast. What says shown-never-written
112
+ is the step from the `ink` a written value takes, the same step the prose leaf's
113
+ empty state reads (`ProseField`). */
114
+ :where(.qm-input)::placeholder {
115
+ color: var(--_qm-ink-label);
116
+ }
117
117
 
118
- /* The second recipe, and it is not the box above: a field is a box because you
119
- type into it, a button is not a box because you press it. No resting border and
120
- no resting fill; the
121
- surface arrives on hover, off the rung the enum listbox already highlights with.
122
- Deliberately no `color`: the callers disagree on purpose (the card delete is
123
- `--_qm-danger`, the array remove inherits its row's ink), and a base colour here
124
- would flatten that distinction rather than share a recipe.
118
+ /* The second recipe, and it is not the box above: a field is a box because you
119
+ type into it, a button is not a box because you press it. No resting border and
120
+ no resting fill; the
121
+ surface arrives on hover, off the rung the enum listbox already highlights with.
122
+ Deliberately no `color`: the callers disagree on purpose (the card delete is
123
+ `--_qm-danger`, the array remove inherits its row's ink), and a base colour here
124
+ would flatten that distinction rather than share a recipe.
125
125
 
126
- `--_qm-tap-min` is WCAG 2.5.8's floor, read rather than re-expressed per
127
- control: `min-*` is outside every `check:style` axis, so a second expression of
128
- the same threshold has nothing watching it. `box-sizing` is what makes it a box
129
- floor: a `<button>` takes border-box from the UA sheet and a plain element does
130
- not, so a control on this recipe that is not a button clears the threshold on its
131
- content box and stands a padding taller than the button beside it. Declared here,
132
- the floor means the same height whatever element carries it; and a caller that
133
- stretches one of these to its row gets a width the padding sits inside.
126
+ `--_qm-tap-min` is WCAG 2.5.8's floor, read rather than re-expressed per
127
+ control: `min-*` is outside every `check:style` axis, so a second expression of
128
+ the same threshold has nothing watching it. `box-sizing` is what makes it a box
129
+ floor: a `<button>` takes border-box from the UA sheet and a plain element does
130
+ not, so a control on this recipe that is not a button clears the threshold on its
131
+ content box and stands a padding taller than the button beside it. Declared here,
132
+ the floor means the same height whatever element carries it; and a caller that
133
+ stretches one of these to its row gets a width the padding sits inside.
134
134
 
135
- Both button families are named here rather than opting in through a marker: a
136
- glyph button and an add affordance are not sometimes flat, so a class they could
137
- be built without is a class one of them will eventually miss.
135
+ Both button families are named here rather than opting in through a marker: a
136
+ glyph button and an add affordance are not sometimes flat, so a class they could
137
+ be built without is a class one of them will eventually miss.
138
138
 
139
- The type half is the shared rule above, which both families are named in; what
140
- diverges is the leading. Where the box's height is a line box, a button's is
141
- `--_qm-tap-min`: a label is one line and a glyph is not text at all, so the line
142
- box collapses onto its content (`1`) and the floor decides, rather than two
143
- height sources competing. That is the same structural `1` a glyph row takes,
144
- which is why it is here and not on one family, and it is what keeps a button off
145
- the host page's leading the way the size rung keeps it off the host page's body
146
- size. A caller whose button carries a label restates the rung it wants from its
147
- own block: the tips foot takes the label rung, which is that surface's decision,
148
- not the family's. */
149
- .qm-icon-btn,
150
- .qm-add-affordance {
151
- line-height: 1;
152
- box-sizing: border-box;
153
- min-width: var(--_qm-tap-min);
154
- min-height: var(--_qm-tap-min);
155
- border: none;
156
- background: transparent;
157
- border-radius: var(--_qm-radius-inner);
158
- cursor: pointer;
159
- transition: background-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
160
- }
161
- .qm-icon-btn:hover:not(:disabled),
162
- .qm-add-affordance:hover:not(:disabled) {
163
- background: var(--_qm-surface-hover);
164
- }
165
- .qm-icon-btn:disabled,
166
- .qm-add-affordance:disabled {
167
- opacity: var(--_qm-opacity-idle);
168
- cursor: default;
169
- }
139
+ The type half is the shared rule above, which both families are named in; what
140
+ diverges is the leading. Where the box's height is a line box, a button's is
141
+ `--_qm-tap-min`: a label is one line and a glyph is not text at all, so the line
142
+ box collapses onto its content (`1`) and the floor decides, rather than two
143
+ height sources competing. That is the same structural `1` a glyph row takes,
144
+ which is why it is here and not on one family, and it is what keeps a button off
145
+ the host page's leading the way the size rung keeps it off the host page's body
146
+ size. A caller whose button carries a label restates the rung it wants from its
147
+ own block: the tips foot takes the label rung, which is that surface's decision,
148
+ not the family's. */
149
+ :where(.qm-icon-btn),
150
+ :where(.qm-add-affordance) {
151
+ line-height: 1;
152
+ box-sizing: border-box;
153
+ min-width: var(--_qm-tap-min);
154
+ min-height: var(--_qm-tap-min);
155
+ border: none;
156
+ background: transparent;
157
+ border-radius: var(--_qm-radius-inner);
158
+ cursor: pointer;
159
+ transition: background-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
160
+ }
161
+ :where(.qm-icon-btn:hover:not(:disabled)),
162
+ :where(.qm-add-affordance:hover:not(:disabled)) {
163
+ background: var(--_qm-surface-hover);
164
+ }
165
+ :where(.qm-icon-btn:disabled),
166
+ :where(.qm-add-affordance:disabled) {
167
+ opacity: var(--_qm-opacity-idle);
168
+ cursor: default;
169
+ }
170
170
 
171
- /* A glyph button: card reorder/delete, array remove, the field label's guidance
172
- marker, the tips card's foot. */
173
- .qm-icon-btn {
174
- display: inline-flex;
175
- align-items: center;
176
- justify-content: center;
177
- gap: var(--_qm-space-half);
178
- padding: var(--_qm-space-half) var(--_qm-space);
179
- }
171
+ /* A glyph button: card reorder/delete, array remove, the field label's guidance
172
+ marker, the tips card's foot. */
173
+ :where(.qm-icon-btn) {
174
+ display: inline-flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ gap: var(--_qm-space-half);
178
+ padding: var(--_qm-space-half) var(--_qm-space);
179
+ }
180
180
 
181
- /* An add affordance: the between-card trigger and the array foot's. A pill rather
182
- than the inner radius, because it fills on hover instead of tinting ink: both rest
183
- unfilled on `--_qm-opacity-idle`, so a hover shifting ink alone would have little
184
- to shift. The recede ladder itself stays with each caller: it is driven by their
185
- own hover/focus state. */
186
- .qm-add-affordance {
187
- border-radius: var(--_qm-radius-pill);
188
- color: var(--_qm-ink-label);
189
- transition:
190
- opacity var(--_qm-duration-fast) var(--_qm-ease-reverse),
191
- background-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
192
- }
193
- .qm-add-affordance:hover {
194
- color: var(--_qm-ink);
195
- }
181
+ /* An add affordance: the between-card trigger and the array foot's. A pill rather
182
+ than the inner radius, because it fills on hover instead of tinting ink: both rest
183
+ unfilled on `--_qm-opacity-idle`, so a hover shifting ink alone would have little
184
+ to shift. The recede ladder itself stays with each caller: it is driven by their
185
+ own hover/focus state. */
186
+ :where(.qm-add-affordance) {
187
+ border-radius: var(--_qm-radius-pill);
188
+ color: var(--_qm-ink-label);
189
+ transition:
190
+ opacity var(--_qm-duration-fast) var(--_qm-ease-reverse),
191
+ background-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
192
+ }
193
+ :where(.qm-add-affordance:hover) {
194
+ color: var(--_qm-ink);
195
+ }
196
196
 
197
- /* The press floor, for a trigger standing in a line of text or a band of gutter
198
- rather than in a row of its own, whose drawn box is therefore smaller than the target
199
- it owes. A target is a region of the screen and a box is paint; separating them is
200
- what lets both be right at once. The target is the box or the floor, whichever is
201
- larger, which is what `width: 100%` under a `min-width` says. Unpainted, so it
202
- enlarges the press and not the mark, and out of the accessibility tree by
203
- construction: a pseudo-element has no node.
197
+ /* The press floor, for a trigger standing in a line of text or a band of gutter
198
+ rather than in a row of its own, whose drawn box is therefore smaller than the target
199
+ it owes. A target is a region of the screen and a box is paint; separating them is
200
+ what lets both be right at once. The target is the box or the floor, whichever is
201
+ larger, which is what `width: 100%` under a `min-width` says. Unpainted, so it
202
+ enlarges the press and not the mark, and out of the accessibility tree by
203
+ construction: a pseudo-element has no node.
204
204
 
205
- The family's floor comes off in the same rule, carrying this class being what giving
206
- it up means. It sits after both families for that: same specificity, so source order
207
- decides. */
208
- .qm-tap-floor {
209
- position: relative;
210
- min-width: 0;
211
- min-height: 0;
212
- }
213
- .qm-tap-floor::after {
214
- content: '';
215
- position: absolute;
216
- top: 50%;
217
- left: 50%;
218
- width: 100%;
219
- min-width: var(--_qm-tap-min);
220
- height: var(--_qm-tap-min);
221
- transform: translate(-50%, -50%);
222
- }
205
+ The family's floor comes off in the same rule, carrying this class being what giving
206
+ it up means. It sits after both families for that: same specificity, so source order
207
+ decides. */
208
+ :where(.qm-tap-floor) {
209
+ position: relative;
210
+ min-width: 0;
211
+ min-height: 0;
212
+ }
213
+ :where(.qm-tap-floor)::after {
214
+ content: '';
215
+ position: absolute;
216
+ top: 50%;
217
+ left: 50%;
218
+ width: 100%;
219
+ min-width: var(--_qm-tap-min);
220
+ height: var(--_qm-tap-min);
221
+ transform: translate(-50%, -50%);
222
+ }
223
223
 
224
- /* A floating list of choices: the enum's listbox and the card stack's kind menu.
225
- Both portal into the nearest `[data-qm-root]`, and both are picked from, so the
226
- surface (its edge, its inset, its ink) and the item (its pad, its radius, the
227
- rung a highlight fills with) are one recipe rather than two that agree until one
228
- is edited. What a caller adds over it is its own: the enum sizes itself off its
229
- trigger and weights the stored value.
224
+ /* A floating list of choices: the enum's listbox and the card stack's kind menu.
225
+ Both portal into the nearest `[data-qm-root]`, and both are picked from, so the
226
+ surface (its edge, its inset, its ink) and the item (its pad, its radius, the
227
+ rung a highlight fills with) are one recipe rather than two that agree until one
228
+ is edited. What a caller adds over it is its own: the enum sizes itself off its
229
+ trigger and weights the stored value.
230
230
 
231
- `[data-highlighted]` is bits-ui's mark and it is the only hover lane: an item
232
- under the pointer and an item under the keyboard cursor are one state to a menu,
233
- so the primitive marks both and a `:hover` rule beside it would paint a second
234
- highlight the keyboard cannot reach. It fills with the accent held rather than
235
- the hover rung: the mark is a cursor a keyboard drives, so it has to read at a
236
- glance from across the list, where a hover only has to confirm the pointer
237
- already under it.
231
+ `[data-highlighted]` is bits-ui's mark and it is the only hover lane: an item
232
+ under the pointer and an item under the keyboard cursor are one state to a menu,
233
+ so the primitive marks both and a `:hover` rule beside it would paint a second
234
+ highlight the keyboard cannot reach. It fills with the accent held rather than
235
+ the hover rung: the mark is a cursor a keyboard drives, so it has to read at a
236
+ glance from across the list, where a hover only has to confirm the pointer
237
+ already under it.
238
238
 
239
- The resting fill is the recipe's too: an item drawn on a `<button>` carries a UA
240
- fill to take back, and a component's scoped block is unlayered, so a caller taking
241
- it back outranks this layer and erases the highlight with it. Transparent here
242
- leaves the caller nothing to declare.
239
+ The resting fill is the recipe's too: an item drawn on a `<button>` carries a UA
240
+ fill to take back, and a scoped block outranks this file, so a caller taking it
241
+ back erases the highlight with it. Transparent here leaves the caller nothing to
242
+ declare.
243
243
 
244
- A floating surface is the one place a hairline is still load-bearing: it hangs
245
- over content the surface did not choose, so there is no plane under it for a
246
- tone to step off (ARCHITECTURE §Styling). The edge and `--_qm-lift` carry it
247
- there, and the raised rung joins them in dark, where a tone still has somewhere
248
- to go. */
249
- .qm-menu-surface {
250
- padding: var(--_qm-space-half);
251
- border: var(--_qm-border-width) solid var(--_qm-border);
252
- border-radius: var(--_qm-radius);
253
- background: var(--_qm-surface-raised);
254
- box-shadow: var(--_qm-lift);
255
- font-size: var(--_qm-text-body);
256
- color: var(--_qm-ink);
257
- }
258
- .qm-menu-item {
259
- padding: var(--_qm-space) var(--_qm-space-2);
260
- border-radius: var(--_qm-radius-inner);
261
- background: transparent;
262
- cursor: pointer;
263
- user-select: none;
264
- }
265
- .qm-menu-item[data-highlighted] {
266
- background: var(--_qm-accent-tint);
267
- }
244
+ A floating surface is the one place a hairline is still load-bearing: it hangs
245
+ over content the surface did not choose, so there is no plane under it for a
246
+ tone to step off (ARCHITECTURE §Styling). The edge and `--_qm-lift` carry it
247
+ there, and the raised rung joins them in dark, where a tone still has somewhere
248
+ to go. */
249
+ :where(.qm-menu-surface) {
250
+ padding: var(--_qm-space-half);
251
+ border: var(--_qm-border-width) solid var(--_qm-border);
252
+ border-radius: var(--_qm-radius);
253
+ background: var(--_qm-surface-raised);
254
+ box-shadow: var(--_qm-lift);
255
+ font-size: var(--_qm-text-body);
256
+ color: var(--_qm-ink);
257
+ }
258
+ :where(.qm-menu-item) {
259
+ padding: var(--_qm-space) var(--_qm-space-2);
260
+ border-radius: var(--_qm-radius-inner);
261
+ background: transparent;
262
+ cursor: pointer;
263
+ user-select: none;
264
+ }
265
+ :where(.qm-menu-item[data-highlighted]) {
266
+ background: var(--_qm-accent-tint);
267
+ }
268
268
 
269
- /* A floating surface that is read rather than picked from: the selection popover
270
- and the field label's guidance. The theme surface mixed toward transparent
271
- behind a backdrop blur, so the page reads faintly through it and the surface
272
- rung stays the mix base, which is what keeps `--qm-bg` tinting it. The lists
273
- above stay opaque instead: a row of choices reads through a blur worse than a
274
- row of glyphs does.
269
+ /* A floating surface that is read rather than picked from: the selection popover
270
+ and the field label's guidance. The theme surface mixed toward transparent
271
+ behind a backdrop blur, so the page reads faintly through it and the surface
272
+ rung stays the mix base, which is what keeps `--qm-bg` tinting it. The lists
273
+ above stay opaque instead: a row of choices reads through a blur worse than a
274
+ row of glyphs does.
275
275
 
276
- The raise and the dismissal are the recipe's too, one existence↔absence trip
277
- taken twice, which is why the class goes on the presence
278
- node (bits-ui's own content element, reached through its `child` snippet)
279
- rather than on a pill inside it: the node's animations are what the primitive
280
- waits on before unmounting, and an animation one level in is a fade it never
281
- sees. Both frames are the inner box's own; floating-ui's outer wrapper, whose
282
- transform is doing the positioning, is untouched. What a caller adds over this
283
- is its own inset, its measure, and its type.
276
+ The raise and the dismissal are the recipe's too, one existence↔absence trip
277
+ taken twice, which is why the class goes on the presence
278
+ node (bits-ui's own content element, reached through its `child` snippet)
279
+ rather than on a pill inside it: the node's animations are what the primitive
280
+ waits on before unmounting, and an animation one level in is a fade it never
281
+ sees. Both frames are the inner box's own; floating-ui's outer wrapper, whose
282
+ transform is doing the positioning, is untouched. What a caller adds over this
283
+ is its own inset, its measure, and its type.
284
284
 
285
- `data-ending-style` is the primitive's mark for the leaving half. The ink is
286
- not all that leaves with it: a surface fading out is not a thing to click or to
287
- read. That half is the caller's `inert` rather than a rule here, because the
288
- primitive styles the same node inline with `pointer-events: auto`, which no
289
- layered rule outranks; the attribute settles it without a cascade fight and
290
- takes the leaving surface out of the tab order and the a11y tree with it. */
291
- .qm-popover-surface {
292
- background: var(--_qm-surface-popover);
293
- backdrop-filter: blur(var(--_qm-blur));
294
- -webkit-backdrop-filter: blur(var(--_qm-blur));
295
- border: var(--_qm-border-width) solid var(--_qm-border);
296
- border-radius: var(--_qm-radius);
297
- box-shadow: var(--_qm-lift);
298
- animation: qm-pop-in var(--_qm-duration-fast) var(--_qm-ease-reverse);
285
+ `data-ending-style` is the primitive's mark for the leaving half. The ink is
286
+ not all that leaves with it: a surface fading out is not a thing to click or to
287
+ read. That half is the caller's `inert` rather than a rule here, because the
288
+ primitive styles the same node inline with `pointer-events: auto`, which no author
289
+ rule outranks; the attribute settles it without a cascade fight and
290
+ takes the leaving surface out of the tab order and the a11y tree with it. */
291
+ :where(.qm-popover-surface) {
292
+ background: var(--_qm-surface-popover);
293
+ backdrop-filter: blur(var(--_qm-blur));
294
+ -webkit-backdrop-filter: blur(var(--_qm-blur));
295
+ border: var(--_qm-border-width) solid var(--_qm-border);
296
+ border-radius: var(--_qm-radius);
297
+ box-shadow: var(--_qm-lift);
298
+ animation: qm-pop-in var(--_qm-duration-fast) var(--_qm-ease-reverse);
299
+ }
300
+ :where(.qm-popover-surface[data-ending-style]) {
301
+ animation: qm-pop-out var(--_qm-duration-fast) var(--_qm-ease-reverse);
302
+ }
303
+ @keyframes qm-pop-in {
304
+ from {
305
+ opacity: 0;
306
+ transform: scale(0.94);
299
307
  }
300
- .qm-popover-surface[data-ending-style] {
301
- animation: qm-pop-out var(--_qm-duration-fast) var(--_qm-ease-reverse);
308
+ to {
309
+ opacity: 1;
310
+ transform: none;
302
311
  }
303
- @keyframes qm-pop-in {
304
- from {
305
- opacity: 0;
306
- transform: scale(0.94);
307
- }
308
- to {
309
- opacity: 1;
310
- transform: none;
311
- }
312
+ }
313
+ @keyframes qm-pop-out {
314
+ from {
315
+ opacity: 1;
316
+ transform: none;
312
317
  }
313
- @keyframes qm-pop-out {
314
- from {
315
- opacity: 1;
316
- transform: none;
317
- }
318
- to {
319
- opacity: 0;
320
- transform: scale(0.94);
321
- }
318
+ to {
319
+ opacity: 0;
320
+ transform: scale(0.94);
322
321
  }
323
322
  }