@quillmark/svelte 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NOTICE CHANGED
@@ -6,9 +6,9 @@ Copyright 2026 Nibs
6
6
  This package contains SVG glyph geometry derived from Lucide, carried in source
7
7
  rather than installed.
8
8
 
9
- `visual/icons/nodes.ts` holds thirteen verbatim from @lucide/svelte 1.28.0:
10
- chevron-right, chevron-down, chevron-up, bold, italic, underline, strikethrough,
11
- code, link, hash, info, plus and x.
9
+ `visual/icons/nodes.ts` holds sixteen verbatim from @lucide/svelte 1.28.0:
10
+ chevron-right, arrow-right, chevron-down, chevron-up, bold, italic, underline,
11
+ strikethrough, code, link, hash, info, plus, x, trash-2 and minus.
12
12
 
13
13
  `core/codec/table-view.ts` holds a plus and four text-alignment glyphs derived
14
14
  from an earlier release of the same project.
@@ -31,9 +31,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
31
31
 
32
32
  --------------------------------------------------------------------------------
33
33
 
34
- Ten of those glyphs — chevron-down, chevron-right, chevron-up, code, hash, info,
35
- italic, link, plus and x — are derived from the Feather project and carry its
36
- license as well.
34
+ Thirteen of those glyphs — arrow-right, chevron-down, chevron-right, chevron-up,
35
+ code, hash, info, italic, link, minus, plus, trash-2 and x — are drawn exactly as
36
+ the Feather project draws them and carry its license as well.
37
37
 
38
38
  The MIT License (MIT)
39
39
 
package/README.md CHANGED
@@ -83,7 +83,7 @@ preview.refresh(session.update(doc)); // repaint dirtyPages ∩ visible
83
83
 
84
84
  In Svelte, `<Preview {session} onPick={…} />` exposes the same verbs (`refresh`, `scrollToField`, `focusPosition`, `setZoom`) via `bind:this`.
85
85
 
86
- `<Preview>` binds **once**, at mount: swapping `session`, `margin`, `overlays`, `onPick`, `onError` or `strings` in place changes nothing on screen and reports `rebind-ignored` through `onError` at `dev` severity, naming the prop. Swap by remounting (`{#key session}`); drive in-place edits through `refresh(change)`. `class` and `style` are the exceptions, landing on the root element and staying live.
86
+ `<Preview>` binds **once**, at mount: swapping `session`, `margin`, `onPick`, `onError` or `strings` in place changes nothing on screen and reports `rebind-ignored` through `onError` at `dev` severity, naming the prop. Swap by remounting (`{#key session}`); drive in-place edits through `refresh(change)`. `class` and `style` are the exceptions, landing on the root element and staying live.
87
87
 
88
88
  ## Visual editor
89
89
 
package/THEMING.md CHANGED
@@ -53,7 +53,7 @@ The poles take a colour, not `transparent` or `inherit`: the raised surfaces, th
53
53
  | ---------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
54
54
  | `--qm-bg` | `#fff` / `#272729` | Base surface: a card, and the preview's sheet. Fields, the popover and the column behind the cards each step off it. |
55
55
  | `--qm-fg` | `#1c1c1c` / `#d7dadc` | Base ink. Body text, labels, and borders step off it. |
56
- | `--qm-accent` | `#2563eb` | Focus rings, active marks, the preview's active field box. |
56
+ | `--qm-accent` | `#71717a` / `#a1a1aa` | Focus rings, active marks, the preview's active field box. The surface spends no hue of its own, so this is the one it wears. |
57
57
  | `--qm-danger` | `#c5221f` | Error diagnostics, the required marker, the delete glyph. |
58
58
  | `--qm-warning` | `#b25000` | Warning diagnostics. |
59
59
  | `--qm-font` | `ui-sans-serif, system-ui, sans-serif` | The editor surface's font family. Controls and buttons take it too, in place of the UA face. |
@@ -1,37 +1,8 @@
1
- /** The resting state a bloom animates from and back to: the wash, held at zero. */
2
- export declare function primeWash(el: HTMLElement): void;
3
- /** What one bloom runs: resolved from the scale, and from the viewer's motion
4
- * preference. */
5
- export interface BloomTiming {
6
- duration: number;
7
- frames: Keyframe[];
8
- }
9
- /**
10
- * Resolve the timing off `el`, so the dwell is single-sourced in CSS.
11
- *
12
- * One property, both motion preferences: `--_qm-bloom-dwell` already shortens under
13
- * `prefers-reduced-motion` in the derivation, so what is left to `matchMedia` here is
14
- * the shape: a hold and a cut carry no ramps, which is a frame list rather than a
15
- * number and the one half CSS cannot hand over.
16
- *
17
- * Read once per batch, not per element: `getComputedStyle` forces a style recalc and
18
- * `animate` re-dirties style, so a read inside a loop flushes once per element (~0.15ms
19
- * each). The rungs inherit, so every element in a batch resolves the same numbers.
20
- */
21
- export declare function bloomTiming(el: HTMLElement): BloomTiming;
22
- /**
23
- * Bloom the wash on `el` itself: the caller owns the element and has primed it
24
- * ({@link primeWash}). Returns the running animation, or `undefined` when there is
25
- * nothing left to run: `elapsed` past the decay, or an environment without WAAPI.
26
- * Pass `timing` to share one resolve across a batch.
27
- */
28
- export declare function bloom(el: HTMLElement, elapsed?: number, timing?: BloomTiming): Animation | undefined;
29
1
  /**
30
2
  * Bloom over `host`'s content: an inset child, so the wash neither tints the host's
31
3
  * own background nor fades the text under it. `host` must be positioned.
32
4
  *
33
5
  * One wash node per host, reused while it lives: two quick landings on the same leaf
34
- * must not stack two alphas over it. No `elapsed` seam: the surfaces that use this
35
- * (editor leaves) are not rebuilt under the animation the way the preview's boxes are.
6
+ * must not stack two alphas over it.
36
7
  */
37
8
  export declare function bloomInside(host: HTMLElement): void;
@@ -1,21 +1,11 @@
1
- // The correlation bloom: a soft accent wash that rises over a target and decays to
2
- // nothing. The editor↔preview address is an event, not a state: the preview claims
3
- // to be the rendered output, so it carries no resting ink, and a highlight that
4
- // outlives the hop that caused it is ink the document did not ask for. Both
5
- // directions land here: the preview's field boxes bloom themselves (they are already
6
- // empty rects over the page), an editor leaf blooms a transient child over its
7
- // surface.
1
+ // The arrival wash: a soft accent bloom that rises over a leaf the editor just
2
+ // landed on and decays to nothing. A landing is an event, not a state, so a
3
+ // highlight that outlives the hop that caused it is ink nothing asked for.
8
4
  //
9
5
  // Opacity is the animated property, never the colour. The wash's alpha is a peak
10
6
  // fixed by `--_qm-accent-wash`, so interpolating 0→1→0 keeps the scale in CSS where
11
7
  // `color-mix` resolves, instead of in keyframe values a script would have to
12
8
  // pre-resolve and re-resolve on a theme change.
13
- //
14
- // `elapsed` is what lets a bloom survive the element that carries it. `overlay.ts`
15
- // rebuilds every box on refresh and the playground recompiles 120ms after each
16
- // keystroke burst, so a bloom re-STARTED per rebuild would re-bloom continuously
17
- // while the user writes; a rebuilt node resumes at the offset the old one reached.
18
- /** Marks a transient wash node: the wash is decoration, never a hit target. */
19
9
  const BLOOM_CLASS = 'qm-bloom';
20
10
  const WASH = 'var(--_qm-accent-wash)';
21
11
  /** The dwell used when the derivation is out of reach (an unstyled root, or jsdom,
@@ -33,53 +23,30 @@ const FRAMES = [
33
23
  { opacity: 0, offset: 1 }
34
24
  ];
35
25
  const FRAMES_REDUCED = [{ opacity: 1 }, { opacity: 1 }];
36
- /** The resting state a bloom animates from and back to: the wash, held at zero. */
37
- export function primeWash(el) {
38
- Object.assign(el.style, { background: WASH, opacity: '0' });
39
- }
40
26
  /**
41
- * Resolve the timing off `el`, so the dwell is single-sourced in CSS.
27
+ * Run one wash on `el`, resolving the dwell off it so the number is single-sourced
28
+ * in CSS.
42
29
  *
43
30
  * One property, both motion preferences: `--_qm-bloom-dwell` already shortens under
44
31
  * `prefers-reduced-motion` in the derivation, so what is left to `matchMedia` here is
45
32
  * the shape: a hold and a cut carry no ramps, which is a frame list rather than a
46
33
  * number and the one half CSS cannot hand over.
47
- *
48
- * Read once per batch, not per element: `getComputedStyle` forces a style recalc and
49
- * `animate` re-dirties style, so a read inside a loop flushes once per element (~0.15ms
50
- * each). The rungs inherit, so every element in a batch resolves the same numbers.
51
34
  */
52
- export function bloomTiming(el) {
35
+ function bloom(el) {
36
+ if (typeof el.animate !== 'function')
37
+ return undefined;
53
38
  const reduced = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
54
39
  const raw = getComputedStyle(el).getPropertyValue('--_qm-bloom-dwell').trim();
55
40
  const n = Number.parseFloat(raw);
56
41
  const duration = !Number.isFinite(n) ? FALLBACK_MS : raw.endsWith('ms') ? n : n * 1000;
57
- return { duration, frames: reduced ? FRAMES_REDUCED : FRAMES };
58
- }
59
- /**
60
- * Bloom the wash on `el` itself: the caller owns the element and has primed it
61
- * ({@link primeWash}). Returns the running animation, or `undefined` when there is
62
- * nothing left to run: `elapsed` past the decay, or an environment without WAAPI.
63
- * Pass `timing` to share one resolve across a batch.
64
- */
65
- export function bloom(el, elapsed = 0, timing) {
66
- if (typeof el.animate !== 'function')
67
- return undefined;
68
- const { duration, frames } = timing ?? bloomTiming(el);
69
- if (elapsed >= duration)
70
- return undefined;
71
- const anim = el.animate(frames, { duration });
72
- if (elapsed > 0)
73
- anim.currentTime = elapsed;
74
- return anim;
42
+ return el.animate(reduced ? FRAMES_REDUCED : FRAMES, { duration });
75
43
  }
76
44
  /**
77
45
  * Bloom over `host`'s content: an inset child, so the wash neither tints the host's
78
46
  * own background nor fades the text under it. `host` must be positioned.
79
47
  *
80
48
  * One wash node per host, reused while it lives: two quick landings on the same leaf
81
- * must not stack two alphas over it. No `elapsed` seam: the surfaces that use this
82
- * (editor leaves) are not rebuilt under the animation the way the preview's boxes are.
49
+ * must not stack two alphas over it.
83
50
  */
84
51
  export function bloomInside(host) {
85
52
  let el = host.querySelector(`:scope > .${BLOOM_CLASS}`);
@@ -99,9 +66,10 @@ export function bloomInside(host) {
99
66
  position: 'absolute',
100
67
  inset: '0',
101
68
  pointerEvents: 'none',
102
- borderRadius: 'inherit'
69
+ borderRadius: 'inherit',
70
+ background: WASH,
71
+ opacity: '0'
103
72
  });
104
- primeWash(el);
105
73
  host.appendChild(el);
106
74
  }
107
75
  const node = el;
@@ -8,11 +8,11 @@ export interface RangeAnchor {
8
8
  /**
9
9
  * A virtual anchor over one PM range, measured when asked rather than when made.
10
10
  *
11
- * The measure is total. `coordsAtPos` reads layout, and layout can be gone: a
12
- * position the document has since invalidated, a leaf inside a collapsed section, a
13
- * view torn down between a scroll firing and the measure running. `autoUpdate` calls
14
- * this at moments none of its callers choose, so an unmeasurable anchor returns the
15
- * last rect that measured rather than throwing out of floating-ui's positioning pass.
11
+ * The measure is total. It reads layout, and layout can be gone: a position the
12
+ * document has since invalidated, a leaf inside a collapsed section, a view torn down
13
+ * between a scroll firing and the measure running. `autoUpdate` calls this at moments
14
+ * none of its callers choose, so an unmeasurable anchor returns the last rect that
15
+ * measured rather than throwing out of floating-ui's positioning pass.
16
16
  *
17
17
  * `from === to` is a caret, which is what the slash menu hangs off.
18
18
  */
@@ -1,11 +1,42 @@
1
+ function union(a, b) {
2
+ return {
3
+ left: Math.min(a.left, b.left),
4
+ top: Math.min(a.top, b.top),
5
+ right: Math.max(a.right, b.right),
6
+ bottom: Math.max(a.bottom, b.bottom)
7
+ };
8
+ }
9
+ function caretBox(view, pos) {
10
+ const { left, top, right, bottom } = view.coordsAtPos(pos);
11
+ return { left, top, right, bottom };
12
+ }
13
+ /**
14
+ * The box the highlight paints, and not the two ends' caret boxes: a selection that
15
+ * wraps has its head and its anchor on opposite sides of the block, so their union
16
+ * names a rectangle no text is under.
17
+ *
18
+ * A range measuring to nothing falls back to that union. A leaf the view draws no text
19
+ * for has no painted box, and an approximate anchor beats one of zero.
20
+ */
21
+ function selectionBox(view, from, to) {
22
+ const range = document.createRange();
23
+ const start = view.domAtPos(from);
24
+ const end = view.domAtPos(to);
25
+ range.setStart(start.node, start.offset);
26
+ range.setEnd(end.node, end.offset);
27
+ const { left, top, right, bottom, width, height } = range.getBoundingClientRect();
28
+ if (width || height)
29
+ return { left, top, right, bottom };
30
+ return union(caretBox(view, from), caretBox(view, to));
31
+ }
1
32
  /**
2
33
  * A virtual anchor over one PM range, measured when asked rather than when made.
3
34
  *
4
- * The measure is total. `coordsAtPos` reads layout, and layout can be gone: a
5
- * position the document has since invalidated, a leaf inside a collapsed section, a
6
- * view torn down between a scroll firing and the measure running. `autoUpdate` calls
7
- * this at moments none of its callers choose, so an unmeasurable anchor returns the
8
- * last rect that measured rather than throwing out of floating-ui's positioning pass.
35
+ * The measure is total. It reads layout, and layout can be gone: a position the
36
+ * document has since invalidated, a leaf inside a collapsed section, a view torn down
37
+ * between a scroll firing and the measure running. `autoUpdate` calls this at moments
38
+ * none of its callers choose, so an unmeasurable anchor returns the last rect that
39
+ * measured rather than throwing out of floating-ui's positioning pass.
9
40
  *
10
41
  * `from === to` is a caret, which is what the slash menu hangs off.
11
42
  */
@@ -18,14 +49,7 @@ export function rangeAnchor(view, from, to) {
18
49
  contextElement: view.dom,
19
50
  getBoundingClientRect: () => {
20
51
  try {
21
- const a = view.coordsAtPos(from);
22
- const b = view.coordsAtPos(to);
23
- last = {
24
- left: Math.min(a.left, b.left),
25
- top: Math.min(a.top, b.top),
26
- right: Math.max(a.right, b.right),
27
- bottom: Math.max(a.bottom, b.bottom)
28
- };
52
+ last = from === to ? caretBox(view, from) : selectionBox(view, from, to);
29
53
  }
30
54
  catch {
31
55
  // unmeasurable: keep the last good rect
@@ -136,7 +136,7 @@ li.ProseMirror-selectednode::after {
136
136
  same schema, and a solid rule renders an authored `<u>` and a link identically in a
137
137
  paragraph that can hold both. An underline at all, where the tips card's link takes
138
138
  none, because this leaf is editable: the `href` is otherwise imperceptible, so
139
- removing the rule loses an answer rather than ornament. No hue:
139
+ removing the rule loses an answer rather than ornament. No tone of its own:
140
140
  `--_qm-accent` already means focus, and a body of accent-coloured links collides
141
141
  with the ring and with the correlation wash. */
142
142
  .ProseMirror a {
@@ -289,9 +289,9 @@ li.ProseMirror-selectednode::after {
289
289
  focus indicator here exactly as it is in the leaf: no ring.
290
290
 
291
291
  The height floor is one line box, named as size times leading so both factors move
292
- with the type ramp (`.qm-prose-block` states the same expression for three). An
293
- empty row is then the height it will be once typed, so the first keystroke changes
294
- no geometry. */
292
+ with the type ramp (`.qm-prose-block` states the same floor against `1em`, whichever
293
+ rung that leaf sets). An empty row is then the height it will be once typed,
294
+ so the first keystroke changes no geometry. */
295
295
  .qm-table-cell-host {
296
296
  min-width: 4rem;
297
297
  min-height: calc(var(--_qm-text-body) * var(--_qm-leading-body));
@@ -475,8 +475,7 @@ li.ProseMirror-selectednode::after {
475
475
 
476
476
  /* A drag in flight. The lifted line reads as picked up by tone, never a shadow:
477
477
  elevation is a surface rung here, which says the same thing at both poles. The drop
478
- rule is the accent, the surface's one saturated ink, spent where a change is about
479
- to land. */
478
+ rule is the accent, the ink the surface keeps for a state rather than for text. */
480
479
  .qm-table-dragging .qm-table-cell.qm-table-lifted {
481
480
  background: var(--_qm-surface-raised);
482
481
  opacity: var(--_qm-opacity-muted);
@@ -86,7 +86,9 @@
86
86
  off it, and the column is a step down. */
87
87
  --_qm-bg: var(--qm-bg, light-dark(#fff, #272729));
88
88
  --_qm-fg: var(--qm-fg, light-dark(#1c1c1c, #d7dadc));
89
- --_qm-accent: var(--qm-accent, #2563eb);
89
+ /* Neutral: the surface spends no hue on a state it can draw in grey. The pair
90
+ clears WCAG 2.4.11's 3:1 against the card at both poles, at 4.8:1 and 5.8:1. */
91
+ --_qm-accent: var(--qm-accent, light-dark(#71717a, #a1a1aa));
90
92
  --_qm-danger: var(--qm-danger, #c5221f);
91
93
  --_qm-warning: var(--qm-warning, #b25000);
92
94
  --_qm-font: var(--qm-font, ui-sans-serif, system-ui, sans-serif);
@@ -156,9 +158,9 @@
156
158
  --_qm-ink: var(--_qm-fg);
157
159
  --_qm-ink-label: color-mix(in oklab, var(--_qm-fg), var(--_qm-bg) 35%);
158
160
  --_qm-ink-ghost: color-mix(in oklab, var(--_qm-fg), var(--_qm-bg) 60%);
159
- /* The correlation wash (`core/bloom.ts`). Its alpha is a peak, not a tint: the
160
- wash animates from and back to zero, never held, so it reads at pane scale
161
- over the preview's paper while the paper rests clean. */
161
+ /* The arrival wash (`core/bloom.ts`). Its alpha is a peak, not a tint: the
162
+ wash animates from and back to zero, never held, so it reads over the leaf it
163
+ lands on while the leaf rests clean. */
162
164
  --_qm-accent-wash: color-mix(in oklab, var(--_qm-accent) 18%, transparent);
163
165
  /* The accent held: what a selected line of a table is tinted with, and what its
164
166
  grip is filled with, so the two ends of one gesture are one colour. A second
@@ -180,16 +182,30 @@
180
182
  /* The tips card'S hue: the surface's one exit from the greyscale that is not
181
183
  a status, and what the amber and the typewriter face say together is
182
184
  draft (VISUAL_EDITOR §"Card operations"). A literal rather than a dial
183
- because there is nothing left to turn: the three rungs under it mix over
185
+ because there is nothing left to turn: the rungs under it mix over
184
186
  the surface and the ink a consumer already tuned, so the card follows a
185
- brand untold. The mixes are faint on purpose: a warmth, not a badge. */
187
+ brand untold. The mixes are faint on purpose: a warmth, not a badge.
188
+
189
+ The one group mixed in sRGB. oklab holds perceived lightness across a mix and
190
+ spends chroma to do it, which is right where both endpoints are grey and wrong
191
+ for a hue: at either pole it lands the ink a channel short of warm. */
186
192
  --_qm-tips: #e88c0a;
187
- --_qm-tips-surface: color-mix(in oklab, var(--_qm-surface), var(--_qm-tips) 4%);
188
- --_qm-tips-ink: color-mix(in oklab, var(--_qm-ink), var(--_qm-tips) 24%);
189
- /* The chip behind a token in a tip. The card's own face is already
190
- monospace, so a `code` run needs a fill to read as literal text rather
191
- than as the sentence around it. */
192
- --_qm-tips-chip: color-mix(in oklab, var(--_qm-tips) 12%, transparent);
193
+ --_qm-tips-surface: color-mix(in srgb, var(--_qm-surface), var(--_qm-tips) 3%);
194
+ --_qm-tips-ink: color-mix(in srgb, var(--_qm-ink), var(--_qm-tips) 24%);
195
+ /* The ink and the amber stepped up, and the pair is spent on the two runs that
196
+ are not the sentence: a token in a tip, and the foot's control under the
197
+ pointer. One rung each rather than one per run, a chip and a hover four
198
+ percent apart being a number with no second decision under it. */
199
+ --_qm-tips-ink-warm: color-mix(in srgb, var(--_qm-ink), var(--_qm-tips) 45%);
200
+ --_qm-tips-fill: color-mix(in srgb, var(--_qm-tips) 12%, transparent);
201
+ /* The typewriter, the half of the slip the amber cannot carry: a modern mono
202
+ reads as code where a Courier reads as typed. Its own rung rather than
203
+ `--_qm-font-mono`, the chrome's readout face, which a consumer turns for
204
+ tabular figures and not to dress this card. Courier Prime leads and is nobody's
205
+ system font, so a host that loads it lands on it and every other host falls to
206
+ the Courier the platform already carries; the package fetches nothing, a preset
207
+ costing a network request being one a consumer cannot decline. */
208
+ --_qm-tips-font: 'Courier Prime', 'Courier New', Courier, ui-monospace, monospace;
193
209
 
194
210
  /* ── Ratios and effects ──────────────────────────────────────────────────
195
211
  No palette fixes these, so they are their own rungs. There is no shadow
@@ -213,15 +229,18 @@
213
229
  ring, and on what state, is the other half: a rule, `.qm-focus-ring` in
214
230
  `visual/controls.css`, since a rung each control reassembles is a scale
215
231
  only up to the point where it is assembled. One width: focus is the only
216
- state that draws a ring anywhere, so there is no held-back tier under it. */
217
- --_qm-ring-width: 2px;
218
- /* Every ring the surface draws, whatever the state under it: focus and selection
219
- alike, which is why the name carries neither. Assembled here because the parts
220
- spelled out at a use site pass every axis — each reads a rung — while drawing a
221
- ring nothing else agrees with. The width stays separately reachable: a drop
222
- indicator is the ring's thickness without being a ring. */
232
+ state that draws a ring anywhere, so there is no held-back tier under it.
233
+ Sub-pixel, because at two it reads as a second border on a card whose every
234
+ other edge is a hairline. */
235
+ --_qm-ring-width: 1.5px;
236
+ /* Every ring the surface draws, focus and selection alike, which is why the name
237
+ carries neither. Assembled here because the parts spelled out at a use site pass
238
+ every axis — each reads a rung — while drawing a ring nothing else agrees with. */
223
239
  --_qm-ring: var(--_qm-ring-width) solid var(--_qm-accent);
224
240
  --_qm-ring-offset: 1px;
241
+ /* What a clip box carries past its content for a focused control inside it to
242
+ keep the whole mark. */
243
+ --_qm-ring-reach: calc(var(--_qm-ring-width) + var(--_qm-ring-offset));
225
244
 
226
245
  /* ── Motion ──────────────────────────────────────────────────────────────
227
246
  Two rungs, by what the trip disturbs: `fast` stays inside the element's own
@@ -265,6 +284,7 @@
265
284
  --_qm-space-2: calc(var(--_qm-space) * 2);
266
285
  --_qm-space-3: calc(var(--_qm-space) * 3);
267
286
  --_qm-space-4: calc(var(--_qm-space) * 4);
287
+ --_qm-space-5: calc(var(--_qm-space) * 5);
268
288
  /* A rung because the two axes are two decisions and the ladder alone cannot hold
269
289
  them together: `check:style`'s rhythm axis sees each box read a rung, never that
270
290
  they read the same one, which is the drift `visual/controls.css` already names
@@ -2,9 +2,9 @@
2
2
  page around the surfaces out. `core/theme.css` is the same shape one level down
3
3
  (dials in, `--_qm-*` out), and the two are calibrated against each other: the
4
4
  figures here are the figures there (the ground step's two arms, 8% hover, 17%
5
- border, 45% and 60% ink) and the dial means the same plane in both, so a host plate
6
- and a card on it land on the same tone at both poles and a host page meets an
7
- editor's own column exactly. The well under the ground has no counterpart here: a
5
+ border, 45% and 60% ink, the ring's width) and the dial means the same plane in
6
+ both, so a host plate and a card on it land on the same tone at both poles and a
7
+ host page meets an editor's own column exactly. The well under the ground has no counterpart here: a
8
8
  host's own controls are boxed and carry a hairline, so nothing on a page needs the
9
9
  step an edgeless field does.
10
10
 
@@ -61,7 +61,7 @@
61
61
  --qmh-space-3: calc(var(--qmh-space) * 3);
62
62
  --qmh-space-4: calc(var(--qmh-space) * 4);
63
63
  --qmh-border-width: 1px;
64
- --qmh-ring-width: 2px;
64
+ --qmh-ring-width: 1.5px;
65
65
  --qmh-radius: var(--qm-radius, 8px);
66
66
  --qmh-radius-inner: calc(var(--qmh-radius) / 2);
67
67
 
@@ -1,7 +1,7 @@
1
1
  <!--
2
2
  `@quillmark/svelte/preview`'s Svelte wrapper: mounts `createPreview` over a
3
3
  container div on mount and tears it down on unmount. No logic beyond wiring;
4
- paint.ts/overlay.ts/bridge.ts/controller.ts own the behavior. Exposes the
4
+ paint.ts/bridge.ts/controller.ts own the behavior. Exposes the
5
5
  `PreviewController` verbs as instance methods (`bind:this`) for a consumer
6
6
  that drives `refresh`/`scrollToField`/`focusPosition`/`setZoom` imperatively
7
7
  (e.g. after `session.apply` elsewhere).
@@ -17,8 +17,8 @@
17
17
 
18
18
  /**
19
19
  * Remount contract. `createPreview` binds once in `onMount`; a later change to any
20
- * prop it closed over (`session`, `margin`, `overlays`, `onPick`, `onError`,
21
- * `strings`) is not observed, and each reports `rebind-ignored` when swapped. Swap
20
+ * prop it closed over (`session`, `margin`, `onPick`, `onError`, `strings`) is not
21
+ * observed, and each reports `rebind-ignored` when swapped. Swap
22
22
  * the session by remounting (`{#key session}`, as the playground does); drive
23
23
  * in-place edits through the `refresh(change)` method, not a prop change.
24
24
  *
@@ -35,7 +35,6 @@
35
35
  * not this attribute. */
36
36
  style?: string;
37
37
  margin?: number;
38
- overlays?: boolean;
39
38
  onPick?: (at: Landing) => void;
40
39
  /** A page paint the backend refused; the error message state shows either way. */
41
40
  onError?: EditorErrorHandler;
@@ -43,22 +42,13 @@
43
42
  strings?: PreviewStringsInput;
44
43
  }
45
44
 
46
- let {
47
- session,
48
- margin,
49
- overlays,
50
- onPick,
51
- onError,
52
- strings,
53
- class: className,
54
- style
55
- }: Props = $props();
45
+ let { session, margin, onPick, onError, strings, class: className, style }: Props = $props();
56
46
 
57
47
  let containerEl: HTMLDivElement | undefined = $state();
58
48
  let controller: PreviewController | undefined;
59
49
 
60
50
  guardRebind(
61
- () => ({ session, margin, overlays, onPick, onError, strings }),
51
+ () => ({ session, margin, onPick, onError, strings }),
62
52
  'Remount the preview ({#key session}) to rebind.'
63
53
  );
64
54
 
@@ -67,7 +57,6 @@
67
57
  controller = createPreview(session, {
68
58
  container: containerEl,
69
59
  margin,
70
- overlays,
71
60
  onPick,
72
61
  onError,
73
62
  strings
@@ -98,8 +87,7 @@
98
87
 
99
88
  <style>
100
89
  /* A detached root: the preview is not a descendant of the editor, so it carries
101
- `data-qm-root` for the page/overlay rungs paint.ts and overlay.ts read
102
- (core/theme.css).
90
+ `data-qm-root` for the page rungs paint.ts reads (core/theme.css).
103
91
 
104
92
  The desk as well as the paper: a sheet is at `--_qm-surface` (paint.ts), so the
105
93
  tone behind it is the sunken rung and the gutter is the margin the sheet floats
@@ -4,8 +4,8 @@ import type { DocPath, Landing, Place } from '../core/address.js';
4
4
  import type { EditorErrorHandler } from '../core/errors.js';
5
5
  /**
6
6
  * Remount contract. `createPreview` binds once in `onMount`; a later change to any
7
- * prop it closed over (`session`, `margin`, `overlays`, `onPick`, `onError`,
8
- * `strings`) is not observed, and each reports `rebind-ignored` when swapped. Swap
7
+ * prop it closed over (`session`, `margin`, `onPick`, `onError`, `strings`) is not
8
+ * observed, and each reports `rebind-ignored` when swapped. Swap
9
9
  * the session by remounting (`{#key session}`, as the playground does); drive
10
10
  * in-place edits through the `refresh(change)` method, not a prop change.
11
11
  *
@@ -22,7 +22,6 @@ interface Props {
22
22
  * not this attribute. */
23
23
  style?: string;
24
24
  margin?: number;
25
- overlays?: boolean;
26
25
  onPick?: (at: Landing) => void;
27
26
  /** A page paint the backend refused; the error message state shows either way. */
28
27
  onError?: EditorErrorHandler;
@@ -69,8 +69,8 @@ export function createBridge(session, container, slots, onPick) {
69
69
  const slot = slots[box.page];
70
70
  if (!slot)
71
71
  return false;
72
- // A discrete act ("show me this field"), so it centres every time: the
73
- // asymmetry the bloom draws between a continuous signal and a one-shot one.
72
+ // A discrete act ("show me this field"), so it centres every time, unlike
73
+ // the continuous hop below.
74
74
  centre(measure(slot, rectToPercent(box.rect, slot.size)));
75
75
  return true;
76
76
  },
@@ -7,8 +7,6 @@ export interface PreviewOptions {
7
7
  container: HTMLElement;
8
8
  /** Pages kept painted beyond the visible band. Default 1. */
9
9
  margin?: number;
10
- /** Draw field-box overlays. Default true. */
11
- overlays?: boolean;
12
10
  /** A click resolved to an address ({@link Landing}): a caret where the compile
13
11
  * tracks the content under the point, the field alone where it tracks only the
14
12
  * placement. The hook does not fire where the compile tracks neither. */
@@ -21,19 +19,19 @@ export interface PreviewOptions {
21
19
  strings?: PreviewStringsInput;
22
20
  }
23
21
  export interface PreviewController {
24
- /** Repaint `dirtyPages ∩ visible` and re-read geometry; the only apply-driven hop. */
22
+ /** Repaint `dirtyPages ∩ visible` and re-locate the followed caret; the only
23
+ * apply-driven hop. */
25
24
  refresh(change: ChangeSet): void;
26
25
  /**
27
- * Scroll `field`'s first box into view and bloom it. `false` when this compile
28
- * places nothing at that address — an answer, not a failure: the plate places
29
- * plenty it does not track, and the preview carries no schema to tell that from a
30
- * field the host misnamed. The editor's `focusField` is what distinguishes them.
26
+ * Scroll `field`'s first box into view. `false` when this compile places nothing
27
+ * at that address — an answer, not a failure: the plate places plenty it does not
28
+ * track, and the preview carries no schema to tell that from a field the host
29
+ * misnamed. The editor's `focusField` is what distinguishes them.
31
30
  */
32
31
  scrollToField(field: DocPath): boolean;
33
32
  /**
34
- * Bring the caret at `at` into view and bloom its field on arrival. Both halves
35
- * are change-guarded: the pane moves only when the caret has left the fold, the
36
- * bloom fires only on a change of address.
33
+ * Bring the caret at `at` into view: the pane moves only when the caret has left
34
+ * the fold.
37
35
  *
38
36
  * Takes the editor's own `onCaretMove` payload, so the editor→preview half of
39
37
  * the bridge is `onCaretMove={preview.focusPosition}` and translates nothing. A