@quillmark/svelte 0.2.0 → 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 (116) hide show
  1. package/README.md +2 -2
  2. package/THEMING.md +11 -7
  3. package/dist/core/address.d.ts +7 -7
  4. package/dist/core/address.js +6 -6
  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 +10 -10
  19. package/dist/core/codec/field.d.ts +21 -26
  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 +230 -215
  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 +70 -26
  40. package/dist/core/errors.d.ts +5 -5
  41. package/dist/core/index.js +2 -2
  42. package/dist/core/lifecycle.js +1 -1
  43. package/dist/core/rebind.svelte.d.ts +2 -2
  44. package/dist/core/rebind.svelte.js +4 -4
  45. package/dist/core/teardown.d.ts +1 -1
  46. package/dist/core/teardown.js +1 -1
  47. package/dist/core/theme.css +144 -54
  48. package/dist/preset/index.css +1 -1
  49. package/dist/preset/recipes.css +31 -29
  50. package/dist/preset/scale.css +36 -25
  51. package/dist/preview/Preview.svelte +12 -10
  52. package/dist/preview/Preview.svelte.d.ts +3 -3
  53. package/dist/preview/bridge.js +4 -4
  54. package/dist/preview/controller.js +3 -3
  55. package/dist/preview/geometry.d.ts +2 -2
  56. package/dist/preview/geometry.js +2 -2
  57. package/dist/preview/index.js +3 -3
  58. package/dist/preview/overlay.d.ts +1 -1
  59. package/dist/preview/overlay.js +3 -3
  60. package/dist/preview/paint.js +15 -17
  61. package/dist/visual/ArrayField.svelte +80 -66
  62. package/dist/visual/ArrayField.svelte.d.ts +1 -1
  63. package/dist/visual/BooleanField.svelte +8 -6
  64. package/dist/visual/BooleanField.svelte.d.ts +2 -2
  65. package/dist/visual/Card.svelte +151 -325
  66. package/dist/visual/Card.svelte.d.ts +2 -6
  67. package/dist/visual/CardControls.svelte +1 -1
  68. package/dist/visual/DateField.svelte +19 -19
  69. package/dist/visual/DateField.svelte.d.ts +3 -3
  70. package/dist/visual/DiagnosticList.svelte +1 -1
  71. package/dist/visual/EnumField.svelte +16 -16
  72. package/dist/visual/EnumField.svelte.d.ts +2 -2
  73. package/dist/visual/Field.svelte +128 -115
  74. package/dist/visual/Field.svelte.d.ts +1 -1
  75. package/dist/visual/FieldHint.svelte +24 -42
  76. package/dist/visual/FieldHint.svelte.d.ts +1 -1
  77. package/dist/visual/FieldLabel.svelte +8 -8
  78. package/dist/visual/FormatPopover.svelte +80 -35
  79. package/dist/visual/NumberField.svelte +5 -5
  80. package/dist/visual/NumberField.svelte.d.ts +1 -1
  81. package/dist/visual/ObjectField.svelte +9 -9
  82. package/dist/visual/ObjectField.svelte.d.ts +1 -1
  83. package/dist/visual/ProseArrayElement.svelte +11 -13
  84. package/dist/visual/ProseArrayElement.svelte.d.ts +1 -1
  85. package/dist/visual/ProseField.svelte +53 -84
  86. package/dist/visual/ProseField.svelte.d.ts +12 -17
  87. package/dist/visual/SlashMenu.svelte +42 -16
  88. package/dist/visual/TextField.svelte +5 -5
  89. package/dist/visual/TextField.svelte.d.ts +1 -1
  90. package/dist/visual/TipsCard.svelte +43 -28
  91. package/dist/visual/VisualEditor.svelte +6 -6
  92. package/dist/visual/VisualEditorInner.svelte +122 -129
  93. package/dist/visual/controls.css +74 -30
  94. package/dist/visual/diagnostics.d.ts +5 -5
  95. package/dist/visual/diagnostics.js +3 -3
  96. package/dist/visual/domid.js +3 -3
  97. package/dist/visual/ext.d.ts +1 -1
  98. package/dist/visual/ext.js +1 -1
  99. package/dist/visual/icons/nodes.d.ts +3 -0
  100. package/dist/visual/icons/nodes.js +2 -1
  101. package/dist/visual/index.js +4 -4
  102. package/dist/visual/leaves.d.ts +3 -3
  103. package/dist/visual/leaves.js +1 -1
  104. package/dist/visual/links.d.ts +24 -0
  105. package/dist/visual/links.js +67 -0
  106. package/dist/visual/props.d.ts +8 -8
  107. package/dist/visual/signals.d.ts +6 -6
  108. package/dist/visual/strings.d.ts +14 -19
  109. package/dist/visual/strings.js +5 -7
  110. package/dist/visual/structure.d.ts +24 -17
  111. package/dist/visual/structure.js +8 -8
  112. package/dist/visual/synced.svelte.d.ts +1 -1
  113. package/dist/visual/synced.svelte.js +4 -4
  114. package/dist/visual/tips.d.ts +2 -2
  115. package/dist/visual/tips.js +3 -3
  116. package/package.json +1 -1
@@ -4,18 +4,18 @@
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. */
@@ -30,7 +30,7 @@ li.ProseMirror-selectednode::after {
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,22 +103,22 @@ 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.
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.
98
112
 
99
- The withheld properties are DECLARED at their zero, which is what makes the
100
- paragraph above true of a MOUNTED leaf: a chip on these tags is an ordinary page
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
101
115
  rule, so left off, these are the two tags the host draws rather than the surface.
102
116
  The other marks are ink, weight and decoration, which the leaf sets or safely
103
117
  inherits, so the box is this mark's alone and the zeros are not a reset.
104
118
 
105
- BOTH tags, where the face alone needs only one: a `code_block` renders `pre > code`
119
+ Both tags, where the face alone needs only one: a `code_block` renders `pre > code`
106
120
  (`schema.ts`), so every character is inside a `code` element and the face reaches it
107
- there — but the block's BOX is the `pre`, outside that element and styled by the
121
+ there — but the block's box is the `pre`, outside that element and styled by the
108
122
  host on its own. `pre` still takes no `overflow-x`, because there is no overflow:
109
123
  ProseMirror's own stylesheet sets `white-space: pre-wrap` there, so a long line
110
124
  wraps. A scroller means overriding that to `pre` inside an editable `code`
@@ -118,10 +132,10 @@ li.ProseMirror-selectednode::after {
118
132
  }
119
133
 
120
134
  /* A link takes the surface's ink (the UA's blue is the `#8cf` defect again) and a
121
- 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
122
136
  same schema, and a solid rule renders an authored `<u>` and a link identically in a
123
137
  paragraph that can hold both. An underline at all, where the tips card's link takes
124
- 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
125
139
  removing the rule loses an answer rather than ornament. No hue:
126
140
  `--_qm-accent` already means focus, and a body of accent-coloured links collides
127
141
  with the ring and with the correlation wash. */
@@ -144,13 +158,13 @@ li.ProseMirror-selectednode::after {
144
158
  rather than the item: the sibling-gap rule deliberately omits `ul`/`ol`, and
145
159
  padding on the container cannot reintroduce what it withholds.
146
160
 
147
- The MARKERS are the UA's, RECLAIMED rather than inherited. `disc` and `decimal`
161
+ The markers are the UA's, reclaimed rather than inherited. `disc` and `decimal`
148
162
  are right and this restates neither: a host reset (Tailwind's preflight sets
149
163
  `list-style: none` on `ol, ul, menu`) is the common case, and against one an
150
164
  unstated default is no default at all — bullets and numbers vanish from the leaf
151
165
  while the preview beside it still draws them, which is an editor/paper
152
166
  divergence on the plainest formatting there is. `revert` over a named type
153
- because the UA's markers step by DEPTH (disc, circle, square) and the schema
167
+ because the UA's markers step by depth (disc, circle, square) and the schema
154
168
  nests (`lists.ts`: Tab sinks an item); naming `disc` would flatten that ramp to
155
169
  hold the first level. Same reclaim for `strong`/`em`/`u`/`s` above if a reset
156
170
  that takes those ever turns up: the positive half states what it leaves to the
@@ -169,56 +183,77 @@ li.ProseMirror-selectednode::after {
169
183
  minting five more is the drift the scale exists to prevent. */
170
184
  .ProseMirror :is(h1, h2, h3, h4, h5, h6) {
171
185
  font-size: inherit;
172
- font-weight: var(--_qm-weight-label);
186
+ font-weight: var(--_qm-weight-strong);
173
187
  }
174
188
 
175
189
  /* ── The table island ────────────────────────────────────────────────────────
176
190
 
177
- 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
178
192
  states what a block is and lets paper set its size; a table island is edited
179
- through controls that do not print, so it also has a recede ladder, a target
180
- floor and a reveal. Those live here rather than in `visual/controls.css` for the
181
- same reason the NodeView is vanilla: the island is inside the leaf, and the leaf
182
- is the codec's. What it does NOT restate is the surface's dials: every value
183
- below is a `--_qm-*` rung, so the island retints with everything else.
184
-
185
- THE CHROME IS IN NO TRACK. Every control is absolutely positioned inside a real
186
- data cell, hanging into a band the scroller carries as PADDING. So the grid holds
187
- the box a paragraph would, the accessibility tree is the data's shape, and a
188
- handle still lines up with its line by CONSTRUCTION rather than by a measured
189
- overlay: it is inside the cell it names. */
190
-
191
- /* The band, and the whole of what it costs. `padding` opens it; the island's
192
- 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
193
214
  inset from the prose edge instead of a tap target plus one.
194
215
 
195
216
  `margin-top: 0` overrides the sibling-gap rule rather than going negative: the band
196
- ABSORBS the gap. A negative top margin would hang the band over the block above,
197
- and an `opacity: 0` control is still hit-tested, so it would take that block's last
198
- presses. */
199
- .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 {
200
224
  margin-top: 0;
201
225
  margin-left: calc(var(--_qm-space-3) * -1);
202
226
  }
227
+ .ProseMirror > .qm-table-island + * {
228
+ margin-top: 0;
229
+ }
203
230
  .qm-table-scroller {
204
231
  /* The one horizontally scrolling box in a leaf: a table is as wide as its
205
232
  columns, and a wide one must not push the card's own layout. `overflow-y` is
206
233
  `hidden` rather than left alone, because a non-`visible` axis computes the other
207
234
  to `auto` and a table has nothing to scroll vertically. */
208
- position: relative;
209
235
  overflow-x: auto;
210
236
  overflow-y: hidden;
211
- padding: var(--_qm-tap-min) 0 0 var(--_qm-tap-min);
237
+ padding: var(--_qm-tap-min);
212
238
  width: max-content;
213
239
  max-width: 100%;
214
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
+ }
215
250
  .qm-table {
216
251
  border-collapse: separate;
217
252
  border-spacing: 0;
218
253
  border: var(--_qm-border-width) solid var(--_qm-border);
219
254
  border-radius: var(--_qm-radius-inner);
220
255
  }
221
- /* 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
222
257
  header's rule are structure and keep the full hairline; the lines between cells are
223
258
  rhythm and drop to the faint rung, so the grid reads as one object with divisions
224
259
  rather than as a stack of boxes. `separate` with zero spacing rather than
@@ -239,20 +274,21 @@ li.ProseMirror-selectednode::after {
239
274
  .qm-table tbody tr:last-child > .qm-table-cell {
240
275
  border-bottom: none;
241
276
  }
242
- /* 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
243
278
  separate field in the model rather than a flag on a row, so the surface says so.
244
- It takes a tone with it, which is the same elevation move the cards make: a rung,
245
- 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. */
246
282
  .qm-table-header-row .qm-table-cell {
247
- font-weight: var(--_qm-weight-label);
248
- background: var(--_qm-surface-raised);
283
+ font-weight: var(--_qm-weight-strong);
284
+ background: var(--_qm-surface-well);
249
285
  border-bottom: var(--_qm-border-width) solid var(--_qm-border);
250
286
  }
251
287
  /* A cell holds a whole nested view. Its measure floor keeps an empty table from
252
288
  collapsing to a row of hairlines with nothing to aim at, and the caret is the
253
289
  focus indicator here exactly as it is in the leaf: no ring.
254
290
 
255
- 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
256
292
  with the type ramp (`.qm-prose-block` states the same expression for three). An
257
293
  empty row is then the height it will be once typed, so the first keystroke changes
258
294
  no geometry. */
@@ -264,201 +300,180 @@ li.ProseMirror-selectednode::after {
264
300
  outline: none;
265
301
  }
266
302
  /* A selected island takes the node outline every leaf block does
267
- (`.ProseMirror-selectednode`, above), and that is the state the corner and Escape
268
- leave a line FOR: the caret is out, the island is what is selected, and Backspace
269
- deletes it. A caret inside a cell is not that state and does not paint it. */
270
-
271
- /* A SELECTED LINE, washed across its cells: what Backspace deletes and Alt+arrow
272
- moves. The hover rung, because it makes a hover's claim (this line, the one the next
273
- command is about) held rather than passing. */
274
- .qm-table-cell.qm-table-line {
275
- background: var(--_qm-surface-hover);
276
- }
277
-
278
- /* Every control in the band is a flat centred button hung out of flow; what differs
279
- is where it hangs and what it draws. */
280
- :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;
281
331
  display: flex;
282
332
  align-items: center;
283
333
  justify-content: center;
334
+ padding: 0;
284
335
  border: none;
285
336
  background: none;
337
+ color: var(--_qm-ink);
286
338
  cursor: pointer;
287
339
  }
288
- :is(.qm-table-seam, .qm-table-align-item) svg {
289
- width: var(--_qm-glyph-control);
290
- height: var(--_qm-glyph-control);
291
- }
292
-
293
- /* ONE control per line, and it is a bar rather than a glyph: what a handle acts on
294
- is the row or column it sits against, and position says that better than an icon
295
- 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.
296
343
 
297
- The TARGET is the band and the MARK is drawn small inside it, which is the rule
298
- the tips card's dots already state: the handle spans its line on one axis and the
299
- tap minimum on the other, and the bar in the middle of it is 3px. A handle sized to
300
- the bar would be a 3px target on a control a pointer has to find. */
301
- .qm-table-handle {
302
- position: absolute;
303
- padding: var(--_qm-space-half);
304
- /* The pointer owns this gesture, not the page's scroll: a handle drags. */
305
- touch-action: none;
306
- }
307
- .qm-table-handle[data-axis='column'] {
308
- inset-inline: 0;
309
- bottom: 100%;
310
- height: var(--_qm-tap-min);
311
- }
312
- .qm-table-handle[data-axis='row'] {
313
- inset-block: 0;
314
- right: 100%;
315
- width: var(--_qm-tap-min);
316
- }
317
- .qm-table-handle-bar {
318
- 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;
319
352
  border-radius: var(--_qm-radius-pill);
320
353
  background: var(--_qm-border);
321
- }
322
- .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 {
323
370
  width: 100%;
324
- height: 0.1875rem;
371
+ height: var(--_qm-space-3);
325
372
  }
326
- .qm-table-handle[data-axis='row'] .qm-table-handle-bar {
327
- 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);
328
376
  height: 100%;
329
- min-height: 0.75rem;
330
- }
331
- /* A selected handle carries the line's ink, so the bar and the wash say the same
332
- thing at both ends of the gesture. */
333
- .qm-table-handle[aria-pressed='true'] .qm-table-handle-bar {
334
- background: var(--_qm-ink-label);
335
377
  }
336
378
 
337
- /* The corner is the ISLAND's handle: the grid origin, where the two bands converge,
338
- and the one gesture that selects the whole table. Its mark is a square because what
339
- it acts on is the rectangle and not a line of it. */
340
- .qm-table-corner-handle {
341
- right: 100%;
342
- bottom: 100%;
343
- width: var(--_qm-tap-min);
344
- height: var(--_qm-tap-min);
345
- }
346
- .qm-table-corner-mark {
347
- display: block;
348
- width: 0.4375rem;
349
- height: 0.4375rem;
350
- border-radius: var(--_qm-radius-pill);
351
- background: var(--_qm-border);
352
- }
353
-
354
- /* A SEAM: the boundary between two lines, and the whole of how a table grows. One per
355
- boundary including the leading and trailing ones, so an append and an interior
356
- 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.
357
384
 
358
- It sits OVER the handles in the band, and it is narrow, so the two share the band
359
- without a dead zone: the handle owns the line's length and the seam owns its ends. */
360
- .qm-table-seam {
361
- position: absolute;
362
- z-index: 1;
363
- width: var(--_qm-tap-min);
364
- height: var(--_qm-tap-min);
365
- border-radius: var(--_qm-radius-pill);
366
- 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;
367
392
  }
368
- .qm-table-seam[data-axis='column'] {
393
+ .qm-table-grip[data-axis='column'] {
394
+ inset-inline: 0;
369
395
  bottom: 100%;
370
- left: calc(var(--_qm-tap-min) / -2);
371
- }
372
- /* The TRAILING seams sit flush inside their edge rather than centred on it: the last
373
- boundary is the grid's own edge, and a seam centred on it would hang half outside
374
- the scroller, where the padding does not reach and the clip does, making the append
375
- affordance the one control that could not be pressed. */
376
- .qm-table-seam[data-axis='column'][data-side='trail'] {
377
- left: auto;
378
- right: 0;
396
+ height: var(--_qm-tap-min);
379
397
  }
380
- .qm-table-seam[data-axis='row'] {
398
+ .qm-table-grip[data-axis='row'] {
399
+ inset-block: 0;
381
400
  right: 100%;
382
- top: calc(var(--_qm-tap-min) / -2);
401
+ width: var(--_qm-tap-min);
383
402
  }
384
- .qm-table-seam[data-axis='row'][data-side='trail'] {
385
- top: auto;
386
- 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);
387
408
  }
388
409
 
389
- /* The alignment cluster: the one op with no gesture, so the one surface the chrome
390
- still raises. It rides the column's SELECTION rather than a hover, so it costs
391
- nothing at rest, and it sits in the band beside the handle rather than portalled:
392
- four glyphs in a row is not a list to read. */
393
- .qm-table-align {
394
- position: absolute;
395
- z-index: 2;
396
- display: flex;
397
- bottom: 100%;
398
- left: 0;
399
- height: var(--_qm-tap-min);
400
- background: var(--_qm-surface-raised);
401
- border: var(--_qm-border-width) solid var(--_qm-border);
402
- border-radius: var(--_qm-radius-pill);
403
- }
404
- /* The LAST column anchors right instead: a cluster is wider than a narrow column, and
405
- one hung off the right edge would be the only chrome the scroller could clip. */
406
- .qm-table-cell:last-child .qm-table-align {
407
- left: auto;
408
- right: 0;
409
- }
410
- .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%;
411
420
  width: var(--_qm-tap-min);
412
- height: 100%;
413
- padding: 0;
414
- border-radius: var(--_qm-radius-pill);
415
- color: var(--_qm-ink-label);
416
- }
417
- .qm-table-align-item:hover {
418
- background: var(--_qm-surface-hover);
419
- color: var(--_qm-ink);
420
421
  }
421
- .qm-table-align-item[aria-pressed='true'] {
422
- color: var(--_qm-accent);
422
+ .qm-table-add[data-axis='row'] {
423
+ inset-inline: 0;
424
+ top: 100%;
425
+ height: var(--_qm-tap-min);
423
426
  }
424
-
425
- /* Pointer OR focus, the card stack's rule unchanged: opacity rather than absence, so
426
- the controls stay in the tab order, and `:focus-within` on the ISLAND, so a caret
427
- in a cell holds the reveal as a focused control does.
428
-
429
- The reveal is per LINE: the island's hover arms the band, and `qm-hot` (the line the
430
- pointer is actually over) is what reaches the idle rung, so a hover answers about
431
- one line rather than about every one at once. */
432
- .qm-table-handle,
433
- .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) {
434
440
  opacity: 0;
435
441
  transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
436
442
  }
437
- .qm-table-island:is(:hover, :focus-within)
438
- :is(.qm-table-handle.qm-hot, .qm-table-corner-handle, .qm-table-seam) {
439
- 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);
440
448
  }
441
- .qm-table-handle:hover,
442
- .qm-table-handle:focus-visible,
443
- .qm-table-handle[aria-pressed='true'],
444
- .qm-table-seam:hover,
445
- .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) {
446
451
  opacity: 1;
447
452
  }
448
453
 
449
- /* The ring, on the chrome and nowhere else in this file. A prose leaf's focus
450
- indicator is its CARET and a cell's is too, so the outline is dropped there; a
451
- handle is a button, and a button that takes focus to hold a selection has to show
452
- it. Declared here rather than reached for as `.qm-focus-ring`: that recipe is the
453
- visual tier's stylesheet, and the island is inside the leaf, which is the codec's. */
454
- .qm-table-handle:focus-visible,
455
- .qm-table-seam:focus-visible,
456
- .qm-table-align-item:focus-visible {
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 {
457
469
  outline: var(--_qm-ring);
458
470
  outline-offset: var(--_qm-ring-offset);
459
471
  }
472
+ .qm-table-grip[aria-pressed='true']:focus-visible {
473
+ outline: none;
474
+ }
460
475
 
461
- /* 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:
462
477
  elevation is a surface rung here, which says the same thing at both poles. The drop
463
478
  rule is the accent, the surface's one saturated ink, spent where a change is about
464
479
  to land. */