@quillmark/svelte 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/README.md +16 -12
  2. package/THEMING.md +48 -25
  3. package/dist/core/address.d.ts +42 -7
  4. package/dist/core/address.js +49 -21
  5. package/dist/core/bloom.d.ts +5 -5
  6. package/dist/core/bloom.js +8 -8
  7. package/dist/core/codec/anchor.d.ts +1 -1
  8. package/dist/core/codec/anchor.js +1 -1
  9. package/dist/core/codec/atoms.d.ts +10 -0
  10. package/dist/core/codec/atoms.js +78 -0
  11. package/dist/core/codec/clipboard.d.ts +59 -0
  12. package/dist/core/codec/clipboard.js +202 -0
  13. package/dist/core/codec/code.d.ts +1 -1
  14. package/dist/core/codec/code.js +4 -4
  15. package/dist/core/codec/decode.d.ts +1 -1
  16. package/dist/core/codec/decode.js +8 -8
  17. package/dist/core/codec/encode.d.ts +4 -4
  18. package/dist/core/codec/encode.js +14 -11
  19. package/dist/core/codec/field.d.ts +22 -27
  20. package/dist/core/codec/field.js +33 -32
  21. package/dist/core/codec/index.js +5 -8
  22. package/dist/core/codec/inputrules.js +56 -18
  23. package/dist/core/codec/islands.d.ts +11 -7
  24. package/dist/core/codec/islands.js +16 -10
  25. package/dist/core/codec/keymap.d.ts +5 -6
  26. package/dist/core/codec/keymap.js +8 -7
  27. package/dist/core/codec/lists.d.ts +3 -3
  28. package/dist/core/codec/lists.js +32 -13
  29. package/dist/core/codec/marks.d.ts +1 -1
  30. package/dist/core/codec/marks.js +1 -1
  31. package/dist/core/codec/positions.js +1 -1
  32. package/dist/core/codec/prose.css +266 -228
  33. package/dist/core/codec/schema.js +4 -4
  34. package/dist/core/codec/slash.d.ts +39 -41
  35. package/dist/core/codec/slash.js +0 -0
  36. package/dist/core/codec/table-view.d.ts +12 -16
  37. package/dist/core/codec/table-view.js +488 -292
  38. package/dist/core/codec/table.d.ts +42 -15
  39. package/dist/core/codec/table.js +73 -27
  40. package/dist/core/errors.d.ts +17 -7
  41. package/dist/core/index.d.ts +1 -1
  42. package/dist/core/index.js +6 -5
  43. package/dist/core/lifecycle.d.ts +21 -5
  44. package/dist/core/lifecycle.js +34 -8
  45. package/dist/core/rebind.svelte.d.ts +18 -4
  46. package/dist/core/rebind.svelte.js +30 -12
  47. package/dist/core/teardown.d.ts +1 -1
  48. package/dist/core/teardown.js +1 -1
  49. package/dist/core/theme.css +173 -58
  50. package/dist/preset/index.css +1 -1
  51. package/dist/preset/recipes.css +79 -30
  52. package/dist/preset/scale.css +40 -28
  53. package/dist/preview/Preview.svelte +22 -19
  54. package/dist/preview/Preview.svelte.d.ts +8 -8
  55. package/dist/preview/bridge.d.ts +6 -4
  56. package/dist/preview/bridge.js +19 -9
  57. package/dist/preview/controller.d.ts +13 -6
  58. package/dist/preview/controller.js +8 -5
  59. package/dist/preview/geometry.d.ts +17 -2
  60. package/dist/preview/geometry.js +21 -1
  61. package/dist/preview/index.d.ts +1 -0
  62. package/dist/preview/index.js +9 -2
  63. package/dist/preview/overlay.d.ts +2 -1
  64. package/dist/preview/overlay.js +26 -11
  65. package/dist/preview/paint.js +19 -17
  66. package/dist/visual/ArrayField.svelte +103 -83
  67. package/dist/visual/ArrayField.svelte.d.ts +5 -4
  68. package/dist/visual/BooleanField.svelte +8 -6
  69. package/dist/visual/BooleanField.svelte.d.ts +2 -2
  70. package/dist/visual/Card.svelte +161 -333
  71. package/dist/visual/Card.svelte.d.ts +5 -4
  72. package/dist/visual/CardControls.svelte +1 -1
  73. package/dist/visual/DateField.svelte +19 -19
  74. package/dist/visual/DateField.svelte.d.ts +3 -3
  75. package/dist/visual/DiagnosticList.svelte +2 -2
  76. package/dist/visual/EnumField.svelte +48 -28
  77. package/dist/visual/EnumField.svelte.d.ts +5 -4
  78. package/dist/visual/Field.svelte +207 -132
  79. package/dist/visual/Field.svelte.d.ts +11 -7
  80. package/dist/visual/FieldHint.svelte +79 -48
  81. package/dist/visual/FieldHint.svelte.d.ts +1 -1
  82. package/dist/visual/FieldLabel.svelte +8 -8
  83. package/dist/visual/FormatPopover.svelte +82 -35
  84. package/dist/visual/NumberField.svelte +5 -5
  85. package/dist/visual/NumberField.svelte.d.ts +1 -1
  86. package/dist/visual/ObjectField.svelte +28 -9
  87. package/dist/visual/ObjectField.svelte.d.ts +4 -2
  88. package/dist/visual/ProseArrayElement.svelte +12 -19
  89. package/dist/visual/ProseArrayElement.svelte.d.ts +1 -2
  90. package/dist/visual/ProseField.svelte +62 -85
  91. package/dist/visual/ProseField.svelte.d.ts +14 -17
  92. package/dist/visual/SlashMenu.svelte +42 -16
  93. package/dist/visual/TextField.svelte +5 -5
  94. package/dist/visual/TextField.svelte.d.ts +1 -1
  95. package/dist/visual/TipsCard.svelte +43 -28
  96. package/dist/visual/VisualEditor.svelte +18 -19
  97. package/dist/visual/VisualEditor.svelte.d.ts +2 -3
  98. package/dist/visual/VisualEditorInner.svelte +251 -184
  99. package/dist/visual/VisualEditorInner.svelte.d.ts +2 -3
  100. package/dist/visual/controls.css +85 -38
  101. package/dist/visual/diagnostics.d.ts +13 -21
  102. package/dist/visual/diagnostics.js +7 -19
  103. package/dist/visual/domid.js +3 -3
  104. package/dist/visual/ext.d.ts +1 -1
  105. package/dist/visual/ext.js +1 -1
  106. package/dist/visual/icons/nodes.d.ts +3 -0
  107. package/dist/visual/icons/nodes.js +2 -1
  108. package/dist/visual/index.d.ts +1 -0
  109. package/dist/visual/index.js +10 -3
  110. package/dist/visual/leaves.d.ts +47 -0
  111. package/dist/visual/leaves.js +31 -0
  112. package/dist/visual/links.d.ts +24 -0
  113. package/dist/visual/links.js +67 -0
  114. package/dist/visual/props.d.ts +33 -12
  115. package/dist/visual/signals.d.ts +6 -6
  116. package/dist/visual/strings.d.ts +14 -19
  117. package/dist/visual/strings.js +5 -7
  118. package/dist/visual/structure.d.ts +24 -17
  119. package/dist/visual/structure.js +8 -8
  120. package/dist/visual/synced.svelte.d.ts +1 -1
  121. package/dist/visual/synced.svelte.js +4 -4
  122. package/dist/visual/tips.d.ts +4 -2
  123. package/dist/visual/tips.js +7 -4
  124. package/package.json +2 -2
@@ -4,33 +4,33 @@
4
4
  hidden-selection and separator-image guards. It is imported whole; the rules are
5
5
  interdependent and which ones a given schema needs is not visible from here.
6
6
 
7
- What it does NOT carry is a block reset, so without the rules below every leaf
7
+ What it does not carry is a block reset, so without the rules below every leaf
8
8
  inherits the UA `p { margin: 1em 0 }` (32px of margin in a box drawn to hold one
9
9
  line), which is enough on its own to make an inline richtext field twice the height
10
10
  of the input beside it. The reset lives here rather than per component because every
11
11
  prose leaf in the package mounts through the codec (a field, the body, an array
12
12
  element) and picks it up without restating it, and a leaf that carries the
13
13
  control recipe (`visual/ProseField.svelte`) can then match `.qm-input` by
14
- construction instead of by a tuned `min-height`. The source view is NOT one of
14
+ construction instead of by a tuned `min-height`. The source view is not one of
15
15
  them: it is a `<pre>` holding `textContent` (`source/view.ts`), outside
16
16
  `.ProseMirror` and so outside every rule in this file.
17
17
 
18
- It also carries hard-coded VALUES that answer to no rung: a `#8cf` node-selection
18
+ It also carries hard-coded values that answer to no rung: a `#8cf` node-selection
19
19
  outline, twice, and the `-32px` placing a selected list item's. Retinted and
20
20
  re-measured here rather than in `theme.css` because a layered rule loses to that
21
21
  unlayered stylesheet: equal specificity and a later import is what wins. */
22
22
  .ProseMirror-selectednode {
23
- outline: var(--_qm-ring-width) solid var(--_qm-accent);
23
+ outline: var(--_qm-ring);
24
24
  }
25
25
  /* Upstream's `-32px` is measured against the UA's 40px list indent, which the list
26
26
  rule below replaces with a rung. The offset moves with it, or a selected item's
27
27
  outline hangs into the margin by the difference. */
28
28
  li.ProseMirror-selectednode::after {
29
- border: var(--_qm-ring-width) solid var(--_qm-accent);
29
+ border: var(--_qm-ring);
30
30
  left: calc(var(--_qm-space-4) * -1);
31
31
  }
32
32
 
33
- /* The GAP CURSOR (`prosemirror-gapcursor`, imported beside this file): a caret
33
+ /* The gap cursor (`prosemirror-gapcursor`, imported beside this file): a caret
34
34
  between two blocks, where the document holds no text position for an ordinary one.
35
35
  It is what a press beside an island lands, so it is drawn in the surface's ink
36
36
  rather than the upstream sheet's `black`, which is the `#8cf` defect again. */
@@ -47,14 +47,28 @@ li.ProseMirror-selectednode::after {
47
47
  }
48
48
 
49
49
  /* One rhythm between sibling blocks, in place of the UA's collapsed pair. Scoped by
50
- PARENT rather than by tag, which is what keeps it true as the schema grows: the
51
- four containers below are every node whose content is `block+` (`schema.ts`), so
50
+ parent rather than by tag, which is what keeps it true as the schema grows: the
51
+ containers below are every node whose content is `block+` (`schema.ts`), so
52
52
  their children are blocks by definition, whatever kinds exist. `ul`/`ol` are
53
53
  deliberately absent: a run of list items is tight, and the gap before a list is
54
- the list's, not each item's. */
54
+ the list's, not each item's.
55
+
56
+ An item is the exception to its own container. A list nested under an item is
57
+ part of the run that omission already keeps tight — `a. …` under `1. …` is the
58
+ next line of one list, not a new block after a paragraph — so an item's children
59
+ take the gap only up to the point where one of them is a list. Written as its own
60
+ arm rather than as a `:not()` over the shared one, because the exception is the
61
+ item's: a list after a paragraph inside a blockquote is an ordinary sibling pair
62
+ and keeps the gap.
63
+
64
+ A line box is ~22px at the paper size and the body leading, so the rung is a
65
+ little over half a line: enough that a break reads as a break, short of the full
66
+ blank line that doubles the leading and makes a short document read as a list of
67
+ fragments. */
55
68
  .ProseMirror > * + *,
56
- .ProseMirror :is(blockquote, li, [data-qm-unknown-container]) > * + * {
57
- margin-top: var(--_qm-space-4);
69
+ .ProseMirror :is(blockquote, [data-qm-unknown-container]) > * + *,
70
+ .ProseMirror li > * + :not(ul, ol) {
71
+ margin-top: var(--_qm-space-3);
58
72
  }
59
73
 
60
74
  /* A blockquote's UA indent was its only mark, and the reset above takes it. The rule
@@ -70,18 +84,18 @@ li.ProseMirror-selectednode::after {
70
84
  emits, minus the four the UA already renders correctly (`strong`, `em`, `u`, `s`)
71
85
  and the blockquote above.
72
86
 
73
- Each rule answers to the REFERENCE QUILL rather than to a browser default or to a
87
+ Each rule answers to the reference quill rather than to a browser default or to a
74
88
  web editor's chrome, because the leaf sits beside a live preview of the same
75
89
  content. `usaf_memo` sets `raw` in a monospace face and adds nothing; it renders a
76
- heading as a bold RUN-IN prepended to the following paragraph, with the level
77
- never read; it gives a link no decoration at all. So a leaf shows that a span IS
78
- code and a line IS a heading: never a prediction of the size paper will set it
90
+ heading as a bold run-in prepended to the following paragraph, with the level
91
+ never read; it gives a link no decoration at all. So a leaf shows that a span is
92
+ code and a line is a heading: never a prediction of the size paper will set it
79
93
  at, which the package cannot know for a quill it has not seen.
80
94
 
81
- Scoping is by TAG, and the schema does the rest: `h1`–`h6`, `hr` and the lists
95
+ Scoping is by tag, and the schema does the rest: `h1`–`h6`, `hr` and the lists
82
96
  cannot exist under the inline schema, so no rule for them can land on a `subject`
83
- field however that field is mounted. The two MARKS are the exception: `code` and
84
- `link` are in both schemas, and a `plaintext` leaf strips them at DECODE while
97
+ field however that field is mounted. The two marks are the exception: `code` and
98
+ `link` are in both schemas, and a `plaintext` leaf strips them at decode while
85
99
  mounting a schema that still carries them, so a paste can put either in one. The
86
100
  rules stand there too, and correctly: they say the leaf holds code, or a link,
87
101
  which is exactly what it then holds.
@@ -89,28 +103,39 @@ li.ProseMirror-selectednode::after {
89
103
  `visual/TipsCard.svelte` restates the two mark rules by hand, over the same
90
104
  `toDOM` output injected outside any view. */
91
105
 
92
- /* Code is a FACE: no chip, no fill, no inset, no size step. The blockquote above
106
+ /* Code is a face: no chip, no fill, no inset, no size step. The blockquote above
93
107
  already argues the general form: a span reads as set apart without a fill or a
94
108
  second box, on the one surface in the card with no edge of its own. A chip has
95
- nowhere to come from in any case: the card is
96
- `--_qm-surface-raised` and the only rung over it is `--_qm-surface-hover`, the
97
- hover lane, so a chip at rest would read as a hovered button.
98
-
99
- ONE selector for the mark and the block: a `code_block` renders `pre > code`
100
- (`schema.ts`), so every character of it is inside a `code` element and the `pre`
101
- needs no face of its own. `pre` takes no `overflow-x` either, because there is no
102
- overflow: ProseMirror's own stylesheet sets `white-space: pre-wrap` there, so a
103
- long line wraps. A scroller means overriding that to `pre` inside an editable
104
- `code` textblock, which is an editing change, not a display rule. */
105
- .ProseMirror code {
109
+ nowhere to come from in any case: the body sits at `--_qm-surface` and the rungs
110
+ either side of it are the field well and the hover lane, so a chip at rest would
111
+ read as a hole in the paper or as a hovered button.
112
+
113
+ The withheld properties are declared at their zero, which is what makes the
114
+ paragraph above true of a mounted leaf: a chip on these tags is an ordinary page
115
+ rule, so left off, these are the two tags the host draws rather than the surface.
116
+ The other marks are ink, weight and decoration, which the leaf sets or safely
117
+ inherits, so the box is this mark's alone and the zeros are not a reset.
118
+
119
+ Both tags, where the face alone needs only one: a `code_block` renders `pre > code`
120
+ (`schema.ts`), so every character is inside a `code` element and the face reaches it
121
+ there — but the block's box is the `pre`, outside that element and styled by the
122
+ host on its own. `pre` still takes no `overflow-x`, because there is no overflow:
123
+ ProseMirror's own stylesheet sets `white-space: pre-wrap` there, so a long line
124
+ wraps. A scroller means overriding that to `pre` inside an editable `code`
125
+ textblock, which is an editing change, not a display rule. */
126
+ .ProseMirror :is(code, pre) {
106
127
  font-family: var(--_qm-font-mono);
128
+ padding: 0;
129
+ border-radius: 0;
130
+ background: none;
131
+ font-size: inherit;
107
132
  }
108
133
 
109
134
  /* A link takes the surface's ink (the UA's blue is the `#8cf` defect again) and a
110
- DOTTED underline. Dotted rather than solid because `underline` is a mark in the
135
+ dotted underline. Dotted rather than solid because `underline` is a mark in the
111
136
  same schema, and a solid rule renders an authored `<u>` and a link identically in a
112
137
  paragraph that can hold both. An underline at all, where the tips card's link takes
113
- none, because this leaf is EDITABLE: the `href` is otherwise imperceptible, so
138
+ none, because this leaf is editable: the `href` is otherwise imperceptible, so
114
139
  removing the rule loses an answer rather than ornament. No hue:
115
140
  `--_qm-accent` already means focus, and a body of accent-coloured links collides
116
141
  with the ring and with the correlation wash. */
@@ -128,74 +153,107 @@ li.ProseMirror-selectednode::after {
128
153
  }
129
154
 
130
155
  /* The UA's 40px list indent is the one box the reset above does not take (it is
131
- padding, not margin), and it is a length nothing chose. A rung replaces it. The
132
- MARKERS stay the UA's: `disc` and `decimal` are already right, and restating them
133
- buys nothing. Per-item spacing is untouched, which is the point of putting this on
134
- the list rather than the item: the sibling-gap rule deliberately omits `ul`/`ol`,
135
- and padding on the container cannot reintroduce what it withholds. */
156
+ padding, not margin), and it is a length nothing chose. A rung replaces it.
157
+ Per-item spacing is untouched, which is the point of putting this on the list
158
+ rather than the item: the sibling-gap rule deliberately omits `ul`/`ol`, and
159
+ padding on the container cannot reintroduce what it withholds.
160
+
161
+ The markers are the UA's, reclaimed rather than inherited. `disc` and `decimal`
162
+ are right and this restates neither: a host reset (Tailwind's preflight sets
163
+ `list-style: none` on `ol, ul, menu`) is the common case, and against one an
164
+ unstated default is no default at all — bullets and numbers vanish from the leaf
165
+ while the preview beside it still draws them, which is an editor/paper
166
+ divergence on the plainest formatting there is. `revert` over a named type
167
+ because the UA's markers step by depth (disc, circle, square) and the schema
168
+ nests (`lists.ts`: Tab sinks an item); naming `disc` would flatten that ramp to
169
+ hold the first level. Same reclaim for `strong`/`em`/`u`/`s` above if a reset
170
+ that takes those ever turns up: the positive half states what it leaves to the
171
+ UA, and what a reset takes is what has to be asked back. */
136
172
  .ProseMirror :is(ul, ol) {
137
173
  padding-left: var(--_qm-space-4);
174
+ list-style: revert;
138
175
  }
139
176
 
140
177
  /* Six levels, one weight, no size step. The quill reads no level at all, so a size
141
178
  ramp here would be the editor predicting a hierarchy the paper does not set; the
142
179
  level stays in the model, which is where the codec round-trips it. It is also all
143
- the ramp could carry: `--_qm-text-title` is one 1.125 step over the body, the
144
- body sits on `--_qm-text-body` by decision, and leading alone is what separates
145
- paper from chrome. Six levels do not fit in one step, and
180
+ the ramp could carry: this leaf renders at `--_qm-text-paper` and the next rung up
181
+ is `--_qm-text-title`, a chrome rung a heading inside a document has no business
182
+ reading. Six levels do not fit in the gap, and
146
183
  minting five more is the drift the scale exists to prevent. */
147
184
  .ProseMirror :is(h1, h2, h3, h4, h5, h6) {
148
185
  font-size: inherit;
149
- font-weight: var(--_qm-weight-label);
186
+ font-weight: var(--_qm-weight-strong);
150
187
  }
151
188
 
152
189
  /* ── The table island ────────────────────────────────────────────────────────
153
190
 
154
- The one construct in this file with CHROME in it. The rest of the stylesheet
191
+ The one construct in this file with chrome in it. The rest of the stylesheet
155
192
  states what a block is and lets paper set its size; a table island is edited
156
- through controls that do not print, so it also has a recede ladder, a target
157
- floor and a reveal. Those live here rather than in `visual/controls.css` for the
158
- same reason the NodeView is vanilla: the island is inside the leaf, and the leaf
159
- is the codec's. What it does NOT restate is the surface's dials: every value
160
- below is a `--_qm-*` rung, so the island retints with everything else.
161
-
162
- THE CHROME IS IN NO TRACK. Every control is absolutely positioned inside a real
163
- data cell, hanging into a band the scroller carries as PADDING. So the grid holds
164
- the box a paragraph would, the accessibility tree is the data's shape, and a
165
- handle still lines up with its line by CONSTRUCTION rather than by a measured
166
- overlay: it is inside the cell it names. */
167
-
168
- /* The band, and the whole of what it costs. `padding` opens it; the island's
169
- negative `margin` spends the card's own inset buying it back, so the grid sits one
193
+ through controls that do not print, so it also has a target floor and a reveal.
194
+ Those live here rather than in `visual/controls.css` for the same reason the
195
+ NodeView is vanilla: the island is inside the leaf, and the leaf is the codec's.
196
+ What it does not restate is the surface's dials: every value below is a `--_qm-*`
197
+ rung, so the island retints with everything else.
198
+
199
+ The band is in no track. Every control in it is absolutely positioned inside a real
200
+ data cell or against the grid's frame, hanging into a band the scroller carries as
201
+ padding. So the grid holds the box a paragraph would, the accessibility tree is the
202
+ data's shape, and a grip still lines up with its line by construction rather than by
203
+ a measured overlay: it is inside the cell it names.
204
+
205
+ Each control comes up alone. A grip or a bar is drawn while the pointer is on that
206
+ control, while it holds the focus, or while it is the one the gesture is about — a
207
+ selected line, a lifted one. The target is there the whole time either way, an
208
+ invisible button being hit-tested like any other, so what the reveal decides is what
209
+ the table looks like under a passing pointer, not what it answers to. */
210
+
211
+ /* The band, and the whole of what it costs. `padding` opens it on all four sides,
212
+ because each axis begins with a grip and ends with an add bar; the island's negative
213
+ `margin` spends the card's own inset buying the left one back, so the grid sits one
170
214
  inset from the prose edge instead of a tap target plus one.
171
215
 
172
216
  `margin-top: 0` overrides the sibling-gap rule rather than going negative: the band
173
- ABSORBS the gap. A negative top margin would hang the band over the block above,
174
- and an `opacity: 0` control is still hit-tested, so it would take that block's last
175
- presses. */
176
- .qm-table-island {
217
+ absorbs the gap, at both ends. A negative margin would hang the band over the
218
+ neighbouring block, and an `opacity: 0` control is still hit-tested, so it would take
219
+ that block's presses. The `.ProseMirror` qualifier is what makes the override hold
220
+ wherever the island lands: two of the gap rule's arms name a container, so a bare
221
+ class loses to them for an island inside a list item or a quote, both of which the
222
+ schema admits. */
223
+ .ProseMirror .qm-table-island {
177
224
  margin-top: 0;
178
225
  margin-left: calc(var(--_qm-space-3) * -1);
179
226
  }
227
+ .ProseMirror > .qm-table-island + * {
228
+ margin-top: 0;
229
+ }
180
230
  .qm-table-scroller {
181
231
  /* The one horizontally scrolling box in a leaf: a table is as wide as its
182
232
  columns, and a wide one must not push the card's own layout. `overflow-y` is
183
233
  `hidden` rather than left alone, because a non-`visible` axis computes the other
184
234
  to `auto` and a table has nothing to scroll vertically. */
185
- position: relative;
186
235
  overflow-x: auto;
187
236
  overflow-y: hidden;
188
- padding: var(--_qm-tap-min) 0 0 var(--_qm-tap-min);
237
+ padding: var(--_qm-tap-min);
189
238
  width: max-content;
190
239
  max-width: 100%;
191
240
  }
241
+ /* The containing block for everything out of flow, and the reason it exists rather
242
+ than the scroller serving: an absolute inside a scroll container is placed against a
243
+ padding box the scroll then slides out from under it, so a cap pinned to the grid's
244
+ far end would drift into the middle of a scrolled table. The frame is the grid's own
245
+ box and travels with it. */
246
+ .qm-table-frame {
247
+ position: relative;
248
+ width: max-content;
249
+ }
192
250
  .qm-table {
193
251
  border-collapse: separate;
194
252
  border-spacing: 0;
195
253
  border: var(--_qm-border-width) solid var(--_qm-border);
196
254
  border-radius: var(--_qm-radius-inner);
197
255
  }
198
- /* Two tiers of rule, because a table draws two different things. The FRAME and the
256
+ /* Two tiers of rule, because a table draws two different things. The frame and the
199
257
  header's rule are structure and keep the full hairline; the lines between cells are
200
258
  rhythm and drop to the faint rung, so the grid reads as one object with divisions
201
259
  rather than as a stack of boxes. `separate` with zero spacing rather than
@@ -216,20 +274,21 @@ li.ProseMirror-selectednode::after {
216
274
  .qm-table tbody tr:last-child > .qm-table-cell {
217
275
  border-bottom: none;
218
276
  }
219
- /* The header row is the ONE weight distinction the island draws: `header` is a
277
+ /* The header row is the one weight distinction the island draws: `header` is a
220
278
  separate field in the model rather than a flag on a row, so the surface says so.
221
- It takes a tone with it, which is the same elevation move the cards make: a rung,
222
- never a shadow. The rest of a table's type is paper's. */
279
+ It takes a tone with it, which is the same move a field makes: the well rung, a
280
+ band cut into the paper rather than a layer stacked on it, and a rung either way
281
+ rather than a shadow. The rest of a table's type is paper's. */
223
282
  .qm-table-header-row .qm-table-cell {
224
- font-weight: var(--_qm-weight-label);
225
- background: var(--_qm-surface-raised);
283
+ font-weight: var(--_qm-weight-strong);
284
+ background: var(--_qm-surface-well);
226
285
  border-bottom: var(--_qm-border-width) solid var(--_qm-border);
227
286
  }
228
287
  /* A cell holds a whole nested view. Its measure floor keeps an empty table from
229
288
  collapsing to a row of hairlines with nothing to aim at, and the caret is the
230
289
  focus indicator here exactly as it is in the leaf: no ring.
231
290
 
232
- The HEIGHT floor is one line box, named as size times leading so both factors move
291
+ The height floor is one line box, named as size times leading so both factors move
233
292
  with the type ramp (`.qm-prose-block` states the same expression for three). An
234
293
  empty row is then the height it will be once typed, so the first keystroke changes
235
294
  no geometry. */
@@ -241,201 +300,180 @@ li.ProseMirror-selectednode::after {
241
300
  outline: none;
242
301
  }
243
302
  /* A selected island takes the node outline every leaf block does
244
- (`.ProseMirror-selectednode`, above), and that is the state the corner and Escape
245
- leave a line FOR: the caret is out, the island is what is selected, and Backspace
246
- deletes it. A caret inside a cell is not that state and does not paint it. */
247
-
248
- /* A SELECTED LINE, washed across its cells: what Backspace deletes and Alt+arrow
249
- moves. The hover rung, because it makes a hover's claim (this line, the one the next
250
- command is about) held rather than passing. */
251
- .qm-table-cell.qm-table-line {
252
- background: var(--_qm-surface-hover);
253
- }
254
-
255
- /* Every control in the band is a flat centred button hung out of flow; what differs
256
- is where it hangs and what it draws. */
257
- :is(.qm-table-handle, .qm-table-seam, .qm-table-align-item) {
303
+ (`.ProseMirror-selectednode`, above), and that is the state Escape leaves a cell or a
304
+ line for: the caret is out, the island is what is selected, and Backspace deletes it.
305
+ A caret inside a cell is not that state and does not paint it. */
306
+
307
+ /* The selection, washed across the cells it covers: what Backspace acts on, and the
308
+ only thing on the island that says what the next command is about. The accent held,
309
+ not the hover rung: a hover is what the pointer is passing over and a selection is
310
+ what the next command will act on, and two states one tone apart on the same surface
311
+ ladder read as the same state at a glance. Translucent, so a selected header cell
312
+ keeps its raised tone under the tint instead of trading it away.
313
+
314
+ One attribute for both gestures, because there is one selection: a grip draws the
315
+ rectangle covering its line and a sweep draws the block the pointer crossed, and the
316
+ wash cannot tell them apart because nothing downstream needs to. */
317
+ .qm-table-cell[data-selected] {
318
+ background: var(--_qm-accent-tint);
319
+ }
320
+
321
+ /* ── The band ────────────────────────────────────────────────────────────────
322
+ Two control families, one geometry: absolutely positioned out of the grid, a
323
+ `--_qm-tap-min` target whatever is drawn inside it, and no box of their own at rest.
324
+ What differs is which edge each hangs off and what it draws. The grips take the
325
+ leading edges, where a line begins; the add bars take the trailing ones, past where
326
+ the axis ends. Position is the whole of what says which is which: the grip's marks are
327
+ the only glyph on the island, and they say draggable rather than which control this
328
+ is. */
329
+ :is(.qm-table-grip, .qm-table-add) {
330
+ position: absolute;
258
331
  display: flex;
259
332
  align-items: center;
260
333
  justify-content: center;
334
+ padding: 0;
261
335
  border: none;
262
336
  background: none;
337
+ color: var(--_qm-ink);
263
338
  cursor: pointer;
264
339
  }
265
- :is(.qm-table-seam, .qm-table-align-item) svg {
266
- width: var(--_qm-glyph-control);
267
- height: var(--_qm-glyph-control);
268
- }
269
-
270
- /* ONE control per line, and it is a bar rather than a glyph: what a handle acts on
271
- is the row or column it sits against, and position says that better than an icon
272
- would.
340
+ /* The bar every one of them draws: a pill the length of what it acts on, a rhythm rung
341
+ thick where the target is the tap floor. Stated once, so a grip and an add bar cannot
342
+ drift into two shapes.
273
343
 
274
- The TARGET is the band and the MARK is drawn small inside it, which is the rule
275
- the tips card's dots already state: the handle spans its line on one axis and the
276
- tap minimum on the other, and the bar in the middle of it is 3px. A handle sized to
277
- the bar would be a 3px target on a control a pointer has to find. */
278
- .qm-table-handle {
279
- position: absolute;
280
- padding: var(--_qm-space-half);
281
- /* The pointer owns this gesture, not the page's scroll: a handle drags. */
282
- touch-action: none;
283
- }
284
- .qm-table-handle[data-axis='column'] {
285
- inset-inline: 0;
286
- bottom: 100%;
287
- height: var(--_qm-tap-min);
288
- }
289
- .qm-table-handle[data-axis='row'] {
290
- inset-block: 0;
291
- right: 100%;
292
- width: var(--_qm-tap-min);
293
- }
294
- .qm-table-handle-bar {
295
- display: block;
344
+ One tone, not a hover ladder: the control is drawn only while it is the one the
345
+ pointer is on or the one a gesture is about, so a resting tone under that would be a
346
+ rung nothing ever reaches. The selected grip's tint below is the one exception, and it
347
+ says which line the next command acts on rather than where the pointer is. */
348
+ :is(.qm-table-grip-bar, .qm-table-add-bar) {
349
+ display: flex;
350
+ align-items: center;
351
+ justify-content: center;
296
352
  border-radius: var(--_qm-radius-pill);
297
353
  background: var(--_qm-border);
298
- }
299
- .qm-table-handle[data-axis='column'] .qm-table-handle-bar {
354
+ transition:
355
+ background-color var(--_qm-duration-fast) var(--_qm-ease-reverse),
356
+ color var(--_qm-duration-fast) var(--_qm-ease-reverse);
357
+ }
358
+ /* The glyph is square and the bar is not: the marks sit in the bar's thickness and the
359
+ bar's length is its line's, so the frame is sized off the one axis both share. */
360
+ .qm-table-grip-bar svg {
361
+ width: var(--_qm-space-3);
362
+ height: var(--_qm-space-3);
363
+ }
364
+ /* A grip runs along the line it names; an add bar runs across the axis it appends to.
365
+ So the two families map one `data-axis` onto opposite orientations, and orientation
366
+ is the one thing the rule above cannot share with them: a column's grip is as wide as
367
+ its column, and the bar that adds a column is as tall as the table. */
368
+ .qm-table-grip[data-axis='column'] .qm-table-grip-bar,
369
+ .qm-table-add[data-axis='row'] .qm-table-add-bar {
300
370
  width: 100%;
301
- height: 0.1875rem;
371
+ height: var(--_qm-space-3);
302
372
  }
303
- .qm-table-handle[data-axis='row'] .qm-table-handle-bar {
304
- width: 0.1875rem;
373
+ .qm-table-grip[data-axis='row'] .qm-table-grip-bar,
374
+ .qm-table-add[data-axis='column'] .qm-table-add-bar {
375
+ width: var(--_qm-space-3);
305
376
  height: 100%;
306
- min-height: 0.75rem;
307
- }
308
- /* A selected handle carries the line's ink, so the bar and the wash say the same
309
- thing at both ends of the gesture. */
310
- .qm-table-handle[aria-pressed='true'] .qm-table-handle-bar {
311
- background: var(--_qm-ink-label);
312
- }
313
-
314
- /* The corner is the ISLAND's handle: the grid origin, where the two bands converge,
315
- and the one gesture that selects the whole table. Its mark is a square because what
316
- it acts on is the rectangle and not a line of it. */
317
- .qm-table-corner-handle {
318
- right: 100%;
319
- bottom: 100%;
320
- width: var(--_qm-tap-min);
321
- height: var(--_qm-tap-min);
322
- }
323
- .qm-table-corner-mark {
324
- display: block;
325
- width: 0.4375rem;
326
- height: 0.4375rem;
327
- border-radius: var(--_qm-radius-pill);
328
- background: var(--_qm-border);
329
377
  }
330
378
 
331
- /* A SEAM: the boundary between two lines, and the whole of how a table grows. One per
332
- boundary including the leading and trailing ones, so an append and an interior
333
- insert are one gesture at different places.
379
+ /* One grip per line, and it is a bar rather than a glyph: what a grip acts on is the
380
+ row or column it sits against, and spanning that line says so better than an icon
381
+ could. The bar carries the grip's own marks — two rows of three, the shape a thing
382
+ you can pick up has everywhere else — so the control reads as draggable before it is
383
+ dragged.
334
384
 
335
- It sits OVER the handles in the band, and it is narrow, so the two share the band
336
- without a dead zone: the handle owns the line's length and the seam owns its ends. */
337
- .qm-table-seam {
338
- position: absolute;
339
- z-index: 1;
340
- width: var(--_qm-tap-min);
341
- height: var(--_qm-tap-min);
342
- border-radius: var(--_qm-radius-pill);
343
- color: var(--_qm-accent);
385
+ The target is the band and the bar is drawn inside it: a grip spans its line on one
386
+ axis and the tap floor on the other, and the bar is drawn thinner inside that. A grip
387
+ sized to its own bar would be a target the thickness of the mark on it, on a control
388
+ the pointer has to find. */
389
+ .qm-table-grip {
390
+ /* The pointer owns this gesture, not the page's scroll: a grip drags. */
391
+ touch-action: none;
344
392
  }
345
- .qm-table-seam[data-axis='column'] {
393
+ .qm-table-grip[data-axis='column'] {
394
+ inset-inline: 0;
346
395
  bottom: 100%;
347
- left: calc(var(--_qm-tap-min) / -2);
348
- }
349
- /* The TRAILING seams sit flush inside their edge rather than centred on it: the last
350
- boundary is the grid's own edge, and a seam centred on it would hang half outside
351
- the scroller, where the padding does not reach and the clip does, making the append
352
- affordance the one control that could not be pressed. */
353
- .qm-table-seam[data-axis='column'][data-side='trail'] {
354
- left: auto;
355
- right: 0;
396
+ height: var(--_qm-tap-min);
356
397
  }
357
- .qm-table-seam[data-axis='row'] {
398
+ .qm-table-grip[data-axis='row'] {
399
+ inset-block: 0;
358
400
  right: 100%;
359
- top: calc(var(--_qm-tap-min) / -2);
401
+ width: var(--_qm-tap-min);
360
402
  }
361
- .qm-table-seam[data-axis='row'][data-side='trail'] {
362
- top: auto;
363
- bottom: 0;
403
+ /* A selected grip takes the line's own tint and the accent for its marks, so the bar
404
+ and the wash say the same thing at both ends of the gesture. */
405
+ .qm-table-grip[aria-pressed='true'] .qm-table-grip-bar {
406
+ background: var(--_qm-accent-tint);
407
+ color: var(--_qm-accent);
364
408
  }
365
409
 
366
- /* The alignment cluster: the one op with no gesture, so the one surface the chrome
367
- still raises. It rides the column's SELECTION rather than a hover, so it costs
368
- nothing at rest, and it sits in the band beside the handle rather than portalled:
369
- four glyphs in a row is not a list to read. */
370
- .qm-table-align {
371
- position: absolute;
372
- z-index: 2;
373
- display: flex;
374
- bottom: 100%;
375
- left: 0;
376
- height: var(--_qm-tap-min);
377
- background: var(--_qm-surface-raised);
378
- border: var(--_qm-border-width) solid var(--_qm-border);
379
- border-radius: var(--_qm-radius-pill);
380
- }
381
- /* The LAST column anchors right instead: a cluster is wider than a narrow column, and
382
- one hung off the right edge would be the only chrome the scroller could clip. */
383
- .qm-table-cell:last-child .qm-table-align {
384
- left: auto;
385
- right: 0;
386
- }
387
- .qm-table-align-item {
410
+ /* The add bars: one along each trailing edge, spanning it. A bar rather than a button
411
+ at the end of the band, because what it appends to is the axis and not a line — there
412
+ is no line out past the last one to sit against, and the whole edge is the easiest
413
+ target on the island to hit. They are the grips' mirror in every way but the edge and
414
+ the marks, which is what makes a bar out there read as one more of these rather than
415
+ as a control of its own: an empty bar past the last line is the table's own shape,
416
+ offered one line further on. */
417
+ .qm-table-add[data-axis='column'] {
418
+ inset-block: 0;
419
+ left: 100%;
388
420
  width: var(--_qm-tap-min);
389
- height: 100%;
390
- padding: 0;
391
- border-radius: var(--_qm-radius-pill);
392
- color: var(--_qm-ink-label);
393
421
  }
394
- .qm-table-align-item:hover {
395
- background: var(--_qm-surface-hover);
396
- color: var(--_qm-ink);
397
- }
398
- .qm-table-align-item[aria-pressed='true'] {
399
- color: var(--_qm-accent);
422
+ .qm-table-add[data-axis='row'] {
423
+ inset-inline: 0;
424
+ top: 100%;
425
+ height: var(--_qm-tap-min);
400
426
  }
401
-
402
- /* Pointer OR focus, the card stack's rule unchanged: opacity rather than absence, so
403
- the controls stay in the tab order, and `:focus-within` on the ISLAND, so a caret
404
- in a cell holds the reveal as a focused control does.
405
-
406
- The reveal is per LINE: the island's hover arms the band, and `qm-hot` (the line the
407
- pointer is actually over) is what reaches the idle rung, so a hover answers about
408
- one line rather than about every one at once. */
409
- .qm-table-handle,
410
- .qm-table-seam {
427
+ /* The reveal, in two rungs, one per question (CODEC §"The table island"). The band comes
428
+ up muted while the pointer is anywhere on the island, answering whether there is one at
429
+ all; a control raises to full while the pointer is on it, while it holds the focus, or
430
+ while it is what a gesture is about — the selected line's grip, and the lifted one,
431
+ which is drawn through a drag the pointer has travelled off — answering which line.
432
+
433
+ Opacity rather than absence, so every control keeps its box and is hit-tested wherever
434
+ it sits. A grip's reach is the pointer's — no Tab arrives at one, the cell traversal
435
+ claiming the key first (`table-view.ts` §`step`) — so it is out of the tab order, and
436
+ the focus rung there draws the focus the view takes itself. The two bars are in it, the
437
+ Tab that declines off the last cell landing on them, and the same rung is what draws
438
+ one a keyboard has reached. */
439
+ :is(.qm-table-grip, .qm-table-add) {
411
440
  opacity: 0;
412
441
  transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
413
442
  }
414
- .qm-table-island:is(:hover, :focus-within)
415
- :is(.qm-table-handle.qm-hot, .qm-table-corner-handle, .qm-table-seam) {
416
- opacity: var(--_qm-opacity-idle);
443
+ /* `:where` on the island, so the first rung carries the specificity of the control alone
444
+ and the second one outranks it. Unwrapped, the descendant selector is the heavier of
445
+ the two and a hovered grip stays muted under the pointer that is on it. */
446
+ :where(.qm-table-island:hover) :is(.qm-table-grip, .qm-table-add) {
447
+ opacity: var(--_qm-opacity-muted);
417
448
  }
418
- .qm-table-handle:hover,
419
- .qm-table-handle:focus-visible,
420
- .qm-table-handle[aria-pressed='true'],
421
- .qm-table-seam:hover,
422
- .qm-table-seam:focus-visible {
449
+ :is(.qm-table-grip, .qm-table-add):is(:hover, :focus-visible),
450
+ .qm-table-grip:is([aria-pressed='true'], .qm-table-lifted) {
423
451
  opacity: 1;
424
452
  }
425
453
 
426
- /* The ring, on the chrome and nowhere else in this file. A prose leaf's focus
427
- indicator is its CARET and a cell's is too, so the outline is dropped there; a
428
- handle is a button, and a button that takes focus to hold a selection has to show
429
- it. Declared here rather than reached for as `.qm-focus-ring`: that recipe is the
430
- visual tier's stylesheet, and the island is inside the leaf, which is the codec's. */
431
- .qm-table-handle:focus-visible,
432
- .qm-table-seam:focus-visible,
433
- .qm-table-align-item:focus-visible {
434
- outline: var(--_qm-ring-focus);
454
+ /* The ring, on the chrome and nowhere else in this file. A prose leaf's focus indicator
455
+ is its caret and a cell's is too, so the outline is dropped there; a control that comes
456
+ up out of nothing has the reveal, and a ring under a keyboard is what says the focus is
457
+ there rather than on the paper behind it. Declared here rather than reached for as
458
+ `.qm-focus-ring`: that recipe is the visual tier's stylesheet, and the island is inside
459
+ the leaf, which is the codec's.
460
+
461
+ A selected grip takes none. Focus and selection arrive together there — the grip takes
462
+ the focus because the selection's keys bind on it — and the tint plus the wash already
463
+ say where the next command lands. A ring on top is a second box around the loudest
464
+ thing on the island, drawn on the press that made the selection: a pointer gesture
465
+ answering with a keyboard's indicator. Stated as `none` rather than left unset, because
466
+ what fills the gap is the browser's own ring, which is the same box in a colour the
467
+ surface does not own. */
468
+ :is(.qm-table-grip, .qm-table-add):focus-visible {
469
+ outline: var(--_qm-ring);
435
470
  outline-offset: var(--_qm-ring-offset);
436
471
  }
472
+ .qm-table-grip[aria-pressed='true']:focus-visible {
473
+ outline: none;
474
+ }
437
475
 
438
- /* A DRAG in flight. The lifted line reads as picked up by TONE, never a shadow:
476
+ /* A drag in flight. The lifted line reads as picked up by tone, never a shadow:
439
477
  elevation is a surface rung here, which says the same thing at both poles. The drop
440
478
  rule is the accent, the surface's one saturated ink, spent where a change is about
441
479
  to land. */