@quillmark/svelte 0.2.0 → 0.3.1

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 (119) hide show
  1. package/NOTICE +6 -6
  2. package/README.md +3 -3
  3. package/THEMING.md +12 -8
  4. package/dist/core/address.d.ts +7 -7
  5. package/dist/core/address.js +6 -6
  6. package/dist/core/bloom.d.ts +3 -32
  7. package/dist/core/bloom.js +19 -51
  8. package/dist/core/codec/anchor.d.ts +1 -1
  9. package/dist/core/codec/anchor.js +1 -1
  10. package/dist/core/codec/atoms.d.ts +10 -0
  11. package/dist/core/codec/atoms.js +78 -0
  12. package/dist/core/codec/clipboard.d.ts +59 -0
  13. package/dist/core/codec/clipboard.js +202 -0
  14. package/dist/core/codec/code.d.ts +1 -1
  15. package/dist/core/codec/code.js +4 -4
  16. package/dist/core/codec/decode.d.ts +1 -1
  17. package/dist/core/codec/decode.js +8 -8
  18. package/dist/core/codec/encode.d.ts +4 -4
  19. package/dist/core/codec/encode.js +10 -10
  20. package/dist/core/codec/field.d.ts +21 -26
  21. package/dist/core/codec/field.js +33 -32
  22. package/dist/core/codec/index.js +5 -8
  23. package/dist/core/codec/inputrules.js +56 -18
  24. package/dist/core/codec/islands.d.ts +11 -7
  25. package/dist/core/codec/islands.js +16 -10
  26. package/dist/core/codec/keymap.d.ts +5 -6
  27. package/dist/core/codec/keymap.js +8 -7
  28. package/dist/core/codec/lists.d.ts +3 -3
  29. package/dist/core/codec/lists.js +32 -13
  30. package/dist/core/codec/marks.d.ts +1 -1
  31. package/dist/core/codec/marks.js +1 -1
  32. package/dist/core/codec/positions.js +1 -1
  33. package/dist/core/codec/prose.css +235 -221
  34. package/dist/core/codec/schema.js +4 -4
  35. package/dist/core/codec/slash.d.ts +39 -41
  36. package/dist/core/codec/slash.js +0 -0
  37. package/dist/core/codec/table-view.d.ts +12 -16
  38. package/dist/core/codec/table-view.js +488 -292
  39. package/dist/core/codec/table.d.ts +42 -15
  40. package/dist/core/codec/table.js +70 -26
  41. package/dist/core/errors.d.ts +5 -5
  42. package/dist/core/index.js +2 -2
  43. package/dist/core/lifecycle.js +1 -1
  44. package/dist/core/rebind.svelte.d.ts +2 -2
  45. package/dist/core/rebind.svelte.js +4 -4
  46. package/dist/core/teardown.d.ts +1 -1
  47. package/dist/core/teardown.js +1 -1
  48. package/dist/core/theme.css +170 -64
  49. package/dist/preset/index.css +1 -1
  50. package/dist/preset/recipes.css +31 -29
  51. package/dist/preset/scale.css +37 -26
  52. package/dist/preview/Preview.svelte +17 -27
  53. package/dist/preview/Preview.svelte.d.ts +4 -5
  54. package/dist/preview/bridge.js +6 -6
  55. package/dist/preview/controller.d.ts +8 -10
  56. package/dist/preview/controller.js +19 -44
  57. package/dist/preview/geometry.d.ts +8 -10
  58. package/dist/preview/geometry.js +8 -10
  59. package/dist/preview/index.js +4 -4
  60. package/dist/preview/paint.d.ts +1 -1
  61. package/dist/preview/paint.js +17 -19
  62. package/dist/visual/ArrayField.svelte +80 -66
  63. package/dist/visual/ArrayField.svelte.d.ts +1 -1
  64. package/dist/visual/BooleanField.svelte +8 -6
  65. package/dist/visual/BooleanField.svelte.d.ts +2 -2
  66. package/dist/visual/Card.svelte +160 -329
  67. package/dist/visual/Card.svelte.d.ts +2 -7
  68. package/dist/visual/CardControls.svelte +1 -1
  69. package/dist/visual/DateField.svelte +19 -19
  70. package/dist/visual/DateField.svelte.d.ts +3 -3
  71. package/dist/visual/DiagnosticList.svelte +1 -1
  72. package/dist/visual/EnumField.svelte +16 -16
  73. package/dist/visual/EnumField.svelte.d.ts +2 -2
  74. package/dist/visual/Field.svelte +128 -115
  75. package/dist/visual/Field.svelte.d.ts +1 -1
  76. package/dist/visual/FieldHint.svelte +24 -42
  77. package/dist/visual/FieldHint.svelte.d.ts +1 -1
  78. package/dist/visual/FieldLabel.svelte +8 -8
  79. package/dist/visual/FormatPopover.svelte +80 -35
  80. package/dist/visual/NumberField.svelte +5 -5
  81. package/dist/visual/NumberField.svelte.d.ts +1 -1
  82. package/dist/visual/ObjectField.svelte +9 -9
  83. package/dist/visual/ObjectField.svelte.d.ts +1 -1
  84. package/dist/visual/ProseArrayElement.svelte +11 -13
  85. package/dist/visual/ProseArrayElement.svelte.d.ts +1 -1
  86. package/dist/visual/ProseField.svelte +53 -87
  87. package/dist/visual/ProseField.svelte.d.ts +13 -19
  88. package/dist/visual/SlashMenu.svelte +42 -16
  89. package/dist/visual/TextField.svelte +5 -5
  90. package/dist/visual/TextField.svelte.d.ts +1 -1
  91. package/dist/visual/TipsCard.svelte +68 -56
  92. package/dist/visual/VisualEditor.svelte +6 -6
  93. package/dist/visual/VisualEditorInner.svelte +203 -290
  94. package/dist/visual/controls.css +74 -30
  95. package/dist/visual/diagnostics.d.ts +5 -5
  96. package/dist/visual/diagnostics.js +3 -3
  97. package/dist/visual/domid.js +3 -3
  98. package/dist/visual/ext.d.ts +1 -1
  99. package/dist/visual/ext.js +1 -1
  100. package/dist/visual/icons/nodes.d.ts +19 -0
  101. package/dist/visual/icons/nodes.js +13 -1
  102. package/dist/visual/index.js +4 -4
  103. package/dist/visual/leaves.d.ts +3 -3
  104. package/dist/visual/leaves.js +1 -1
  105. package/dist/visual/links.d.ts +24 -0
  106. package/dist/visual/links.js +67 -0
  107. package/dist/visual/props.d.ts +8 -8
  108. package/dist/visual/signals.d.ts +6 -6
  109. package/dist/visual/strings.d.ts +14 -19
  110. package/dist/visual/strings.js +5 -7
  111. package/dist/visual/structure.d.ts +24 -17
  112. package/dist/visual/structure.js +8 -8
  113. package/dist/visual/synced.svelte.d.ts +1 -1
  114. package/dist/visual/synced.svelte.js +4 -4
  115. package/dist/visual/tips.d.ts +2 -2
  116. package/dist/visual/tips.js +3 -3
  117. package/package.json +2 -2
  118. package/dist/preview/overlay.d.ts +0 -11
  119. package/dist/preview/overlay.js +0 -97
@@ -1,12 +1,15 @@
1
- /* THE HOST'S DERIVATION: the same public dials in, a closed `--qmh-*` scale for the
2
- page AROUND the surfaces out. `core/theme.css` is the same shape one level down
3
- (dials in, `--_qm-*` out), and the two are calibrated against each other: the mixes
4
- here are the percentages there (4pp between surface steps, 17% border, 45% and 60%
5
- ink), so a host plate and a card on it agree about which way "raised" goes and land
6
- on the same tone at both poles.
1
+ /* The host'S derivation: the same public dials in, a closed `--qmh-*` scale for the
2
+ page around the surfaces out. `core/theme.css` is the same shape one level down
3
+ (dials in, `--_qm-*` out), and the two are calibrated against each other: the
4
+ figures here are the figures there (the ground step's two arms, 8% hover, 17%
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
+ host's own controls are boxed and carry a hairline, so nothing on a page needs the
9
+ step an edgeless field does.
7
10
 
8
- TWO SCALES rather than one because they answer to different roots. `--_qm-*` is
9
- declared ON each mounted root, out of reach from an ancestor, so a surface holds its
11
+ Two scales rather than one because they answer to different roots. `--_qm-*` is
12
+ declared on each mounted root, out of reach from an ancestor, so a surface holds its
10
13
  scale wherever it is dropped. A host scale has the opposite job: it is the document,
11
14
  so it is declared at `:root` and everything on the page reads it. A single namespace
12
15
  could not be both.
@@ -14,26 +17,34 @@
14
17
  Literals live here and nowhere else in the preset: `check:style` runs its axes over
15
18
  `recipes.css` beside it, with this file exempt as the place the defaults are minted.
16
19
 
17
- The poles fall back to the SYSTEM colours, so a host that turns no dial still lands
18
- where its own unstyled text does. The dials sit in front, so `--qm-bg`/`--qm-fg` on
19
- an ancestor retunes page and surface together. */
20
+ The poles fall back to the system colours, so a host that turns no dial still lands
21
+ where its own unstyled text does — `Canvas` being the plane the UA would have
22
+ painted a page with, which is what a plate should be. The dials sit in front, so
23
+ `--qm-bg`/`--qm-fg` on an ancestor retunes page and surface together. */
20
24
 
21
25
  :root {
22
26
  /* ── Colour ──────────────────────────────────────────────────────────────── */
23
- --qmh-page: var(--qm-bg, Canvas);
27
+ /* The dial is the plate, exactly as it is the card one level down: a plate beside
28
+ a mounted editor is the same plane as a card in it, so both read the pole and
29
+ the page sinks from it by the same two arms. That is what puts a host page and
30
+ an editor's own column on one tone rather than a plane apart. Planes step by
31
+ light and everything else by ink, which is the package's own split
32
+ (ARCHITECTURE §Styling); the figures are its figures. */
33
+ --qmh-surface: var(--qm-bg, Canvas);
34
+ --qmh-page: light-dark(
35
+ oklab(from var(--qmh-surface) calc(l - 0.0214) calc(a - 0.00065) calc(b - 0.00158)),
36
+ oklab(from var(--qmh-surface) calc(l - 0.0553) calc(a - 0.00048) calc(b + 0.00165))
37
+ );
24
38
  --qmh-ink: var(--qm-fg, CanvasText);
25
- --qmh-ink-meta: color-mix(in oklab, var(--qmh-ink), var(--qmh-page) 45%);
26
- --qmh-ghost: color-mix(in oklab, var(--qmh-ink), var(--qmh-page) 60%);
27
- /* Two planes behind content, 4pp apart exactly as the surfaces step, so a host
28
- plate and a card on it agree about which way "raised" goes in both schemes. */
29
- --qmh-surface: color-mix(in oklab, var(--qmh-page), var(--qmh-ink) 4%);
30
- --qmh-surface-hover: color-mix(in oklab, var(--qmh-page), var(--qmh-ink) 8%);
31
- --qmh-border: color-mix(in oklab, var(--qmh-page), var(--qmh-ink) 17%);
39
+ --qmh-ink-meta: color-mix(in oklab, var(--qmh-ink), var(--qmh-surface) 45%);
40
+ --qmh-ghost: color-mix(in oklab, var(--qmh-ink), var(--qmh-surface) 60%);
41
+ --qmh-surface-hover: color-mix(in oklab, var(--qmh-surface), var(--qmh-ink) 8%);
42
+ --qmh-border: color-mix(in oklab, var(--qmh-surface), var(--qmh-ink) 17%);
32
43
  /* The edge that says "engaged" where the hairline says "edge": a pressed switch,
33
44
  the underline under a link. */
34
- --qmh-border-strong: color-mix(in oklab, var(--qmh-page), var(--qmh-ink) 40%);
45
+ --qmh-border-strong: color-mix(in oklab, var(--qmh-surface), var(--qmh-ink) 40%);
35
46
 
36
- /* The two hues a host spends, and it spends them on a message. Both read the DIAL,
47
+ /* The two hues a host spends, and it spends them on a message. Both read the dial,
37
48
  so a page carries one colour meaning error and one meaning warning rather than a
38
49
  second pair beside the surfaces'. The dark half is a lift the dials do not carry:
39
50
  a hue calibrated against white reads as a stain on a dark page. */
@@ -42,7 +53,7 @@
42
53
 
43
54
  /* ── Rhythm ──────────────────────────────────────────────────────────────── */
44
55
  /* One base and its multiples, the same four steps the surfaces derive. A host
45
- separating SECTIONS down a page wants steps above these; it derives them from
56
+ separating sections down a page wants steps above these; it derives them from
46
57
  this base rather than minting them, which is what keeps its ramp one ramp. */
47
58
  --qmh-space: var(--qm-space, 0.25rem);
48
59
  --qmh-space-half: calc(var(--qmh-space) / 2);
@@ -50,7 +61,7 @@
50
61
  --qmh-space-3: calc(var(--qmh-space) * 3);
51
62
  --qmh-space-4: calc(var(--qmh-space) * 4);
52
63
  --qmh-border-width: 1px;
53
- --qmh-ring-width: 2px;
64
+ --qmh-ring-width: 1.5px;
54
65
  --qmh-radius: var(--qm-radius, 8px);
55
66
  --qmh-radius-inner: calc(var(--qmh-radius) / 2);
56
67
 
@@ -58,7 +69,7 @@
58
69
  so it does not derive from the spacing dial. */
59
70
  --qmh-measure: 34rem;
60
71
 
61
- /* The floor under a control a THUMB lands on. Hand geometry rather than rhythm, which
72
+ /* The floor under a control a thumb lands on. Hand geometry rather than rhythm, which
62
73
  is why it does not derive from the spacing dial either: 44px is where both platform
63
74
  guidelines and WCAG put a touch target, and a control padded to a pointer's size is
64
75
  under it whatever rung the padding reads. */
@@ -73,7 +84,7 @@
73
84
  --qmh-font: var(--qm-font, ui-sans-serif, system-ui, sans-serif);
74
85
  --qmh-font-mono: var(--qm-font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
75
86
 
76
- /* The page's own ramp, and it does NOT read `--qm-font-size`: that dial anchors the
87
+ /* The page's own ramp, and it does not read `--qm-font-size`: that dial anchors the
77
88
  surfaces' ramp, which is chrome inside a card, and a page's body text is a
78
89
  different job at a different size. A host that wants them locked sets both. */
79
90
  --qmh-text: 1rem;
@@ -94,7 +105,7 @@
94
105
  --qmh-ease-reverse: cubic-bezier(0.4, 0, 0.6, 1);
95
106
  }
96
107
 
97
- /* Collapse the SCALE, not each transition: every animated recipe reads the rung, so one
108
+ /* Collapse the scale, not each transition: every animated recipe reads the rung, so one
98
109
  override reaches all of them with no `*` rule touching mounted package elements. */
99
110
  @media (prefers-reduced-motion: reduce) {
100
111
  :root {
@@ -1,10 +1,10 @@
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
- (e.g. after `session.apply` elsewhere); pure passthrough, no added logic.
7
+ (e.g. after `session.apply` elsewhere).
8
8
  -->
9
9
  <script lang="ts">
10
10
  import { onMount } from 'svelte';
@@ -16,10 +16,10 @@
16
16
  import type { EditorErrorHandler } from '../core/errors.js';
17
17
 
18
18
  /**
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
22
- * the session by REMOUNTING (`{#key session}`, as the playground does); drive
19
+ * Remount contract. `createPreview` binds once in `onMount`; a later change to any
20
+ * prop it closed over (`session`, `margin`, `onPick`, `onError`, `strings`) is not
21
+ * observed, and each reports `rebind-ignored` when swapped. Swap
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
  *
25
25
  * `onError` is itself once-bound, so a swapped handler means the report of its own
@@ -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
@@ -97,15 +86,16 @@
97
86
  <div bind:this={containerEl} class="qm-preview {className ?? ''}" {style} data-qm-root></div>
98
87
 
99
88
  <style>
100
- /* 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).
89
+ /* A detached root: the preview is not a descendant of the editor, so it carries
90
+ `data-qm-root` for the page rungs paint.ts reads (core/theme.css).
103
91
 
104
- The DESK as well as the paper: a sheet is at `--_qm-surface` (paint.ts), so the
105
- tone behind it is one rung raised and the gutter is the margin the sheet floats
106
- in. Both are the surface's own, so a bare `<div>` is a mounting site. The
92
+ The desk as well as the paper: a sheet is at `--_qm-surface` (paint.ts), so the
93
+ tone behind it is the sunken rung and the gutter is the margin the sheet floats
94
+ in. Paper is the brighter plane at both poles, exactly as a card is over the
95
+ editor's own column, so the two panes step the same way. Both are
96
+ the surface's own, so a bare `<div>` is a mounting site. The
107
97
  padding is safe against the paint loop: a slot is a `width: 100%` child and
108
- measures the CONTENT box, and the container's own `clientWidth` is read only as
98
+ measures the content box, and the container's own `clientWidth` is read only as
109
99
  a change detector, which a constant inset does not disturb. */
110
100
  .qm-preview {
111
101
  box-sizing: border-box;
@@ -114,6 +104,6 @@
114
104
  height: 100%;
115
105
  overflow-y: auto;
116
106
  padding: var(--_qm-space-4);
117
- background: var(--_qm-surface-raised);
107
+ background: var(--_qm-surface-sunken);
118
108
  }
119
109
  </style>
@@ -3,10 +3,10 @@ import type { LiveSession, ChangeSet } from '@quillmark/wasm';
3
3
  import type { DocPath, Landing, Place } from '../core/address.js';
4
4
  import type { EditorErrorHandler } from '../core/errors.js';
5
5
  /**
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
9
- * the session by REMOUNTING (`{#key session}`, as the playground does); drive
6
+ * Remount contract. `createPreview` binds once in `onMount`; a later change to any
7
+ * prop it closed over (`session`, `margin`, `onPick`, `onError`, `strings`) is not
8
+ * observed, and each reports `rebind-ignored` when swapped. Swap
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
  *
12
12
  * `onError` is itself once-bound, so a swapped handler means the report of its own
@@ -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;
@@ -1,10 +1,10 @@
1
1
  import { boxesForField, rectToPercent, clickToPdfPt, applyPercentRect } from './geometry.js';
2
2
  export function createBridge(session, container, slots, onPick) {
3
3
  const unlisten = [];
4
- // TWO RUNGS: `positionAt` over span-tracked content, `fieldAt` over every placement
4
+ // Two rungs: `positionAt` over span-tracked content, `fieldAt` over every placement
5
5
  // the compile tracks, a strict superset of it (PREVIEW.md §"Click bridge" carries
6
6
  // the measurement). The second fires where a field is placed without its content
7
- // being tracked, and the pick carries NO `pos` there: a fabricated `0` would be an
7
+ // being tracked, and the pick carries no `pos` there: a fabricated `0` would be an
8
8
  // invented offset wearing a real one's type. No third rung hit-tests `regions()`,
9
9
  // whose rects bound ink the field does not fill.
10
10
  if (onPick) {
@@ -53,7 +53,7 @@ export function createBridge(session, container, slots, onPick) {
53
53
  else if (target.right > port.right)
54
54
  container.scrollLeft += target.right - port.right;
55
55
  }
56
- // Clear of the fold is the target's OWN height of clearance at each edge, not bare
56
+ // Clear of the fold is the target's own height of clearance at each edge, not bare
57
57
  // intersection: a caret rect flush against one is visible and unusable, and the
58
58
  // next line typed lands past it. Derived from the target rather than a margin
59
59
  // dial, so it scales with zoom exactly as the caret does.
@@ -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
  },
@@ -81,7 +81,7 @@ export function createBridge(session, container, slots, onPick) {
81
81
  const slot = slots[region.page];
82
82
  if (!slot)
83
83
  return;
84
- // The CONTINUOUS hop: one call per keystroke and per arrow key, so it moves
84
+ // The continuous hop: one call per keystroke and per arrow key, so it moves
85
85
  // the pane only when the caret has left the fold. Centring unconditionally
86
86
  // takes the scrollport back from the user on every one of them, including
87
87
  // the ones that changed nothing about where the caret already was.
@@ -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
@@ -1,12 +1,10 @@
1
1
  import { reportError, errorMessage } from '../core/errors.js';
2
2
  import { createPaintLoop } from './paint.js';
3
- import { createOverlay } from './overlay.js';
4
3
  import { createBridge } from './bridge.js';
5
4
  import { mergePreviewStrings } from './strings.js';
6
5
  const CONTAINER_CLASS = 'qm-preview';
7
- // The message states share one element; each carries `MESSAGE_CLASS` plus a
8
- // state class so a consumer (and the tests) can target them. `EMPTY_CLASS` is
9
- // the zero-page hook consumers and tests target.
6
+ // The message states share one element; each carries `MESSAGE_CLASS` plus a state
7
+ // class, the hooks a consumer and the tests target.
10
8
  const MESSAGE_CLASS = 'qm-preview-message';
11
9
  const EMPTY_CLASS = 'qm-preview-empty';
12
10
  const UNSUPPORTED_CLASS = 'qm-preview-unsupported';
@@ -14,13 +12,10 @@ const ERROR_CLASS = 'qm-preview-error';
14
12
  export function createPreview(session, opts) {
15
13
  const container = opts.container;
16
14
  const margin = opts.margin ?? 1;
17
- const overlaysEnabled = opts.overlays ?? true;
18
15
  const t = mergePreviewStrings(opts.strings);
19
16
  container.classList.add(CONTAINER_CLASS);
20
- // The full-container message slot, shared by every non-paint state: the empty
21
- // seed / drop-to-zero, a compile that cannot paint (`supportsCanvas` false),
22
- // and a paint that threw. One element, restamped; these states are mutually
23
- // exclusive, and the zero-page case keeps its `qm-preview-empty` hook.
17
+ // One element, restamped: the non-paint states (empty, unsupported, a paint that
18
+ // threw) are mutually exclusive.
24
19
  let message;
25
20
  function showMessage(text, state) {
26
21
  if (!message) {
@@ -50,61 +45,46 @@ export function createPreview(session, opts) {
50
45
  });
51
46
  showMessage(t.renderFailed, ERROR_CLASS);
52
47
  });
53
- // overlay/bridge query geometry at build (`session.regions()`), so they are
54
- // held until slots exist; (re)built by `render` when a compile is paintable.
55
- let overlay;
48
+ // The bridge lives and dies with the slot set, so it is held until `render` finds
49
+ // a compile paintable. Rebuilt rather than patched whenever the count changes: the
50
+ // slots array is a stable identity but its members are swapped on reconcile, and
51
+ // the listeners are per slot.
56
52
  let bridge;
57
- // overlay/bridge live and die with the slot set. Rebuilt (not patched) whenever
58
- // the count changes: the slots array is a stable identity but its members are
59
- // swapped on reconcile, and both attach DOM/listeners per slot.
60
53
  function attach() {
61
- overlay = overlaysEnabled ? createOverlay(session, paintLoop.slots) : undefined;
62
54
  bridge = createBridge(session, container, paintLoop.slots, opts.onPick);
63
55
  }
64
56
  function detach() {
65
- overlay?.destroy();
66
57
  bridge?.destroy();
67
- overlay = undefined;
68
58
  bridge = undefined;
69
59
  }
70
- // Reflect one compile's paintability into the DOM: page slots + overlay/bridge
71
- // when there is something to paint, the shared message otherwise. Called at
72
- // construction and after every `apply`, so `supportsCanvas` is re-read per
73
- // compile (runtime.d.ts: re-check after `open`) and a 0-page or non-canvas
74
- // compile that later gains paintable pages recovers; the check spans the
75
- // paint capability generally, not just the page count.
60
+ // Called at construction and after every `apply`, so `supportsCanvas` is re-read
61
+ // per compile (runtime.d.ts: re-check after `open`) and a 0-page or non-canvas
62
+ // compile that later gains paintable pages recovers; the check spans the paint
63
+ // capability generally, not just the page count.
76
64
  function render(pageCount, dirtyPages) {
77
65
  if (!session.supportsCanvas || pageCount === 0) {
78
- // Nothing paintable: collapse slots, drop geometry attachments, say why.
79
- // A 0-page compile is a recoverable empty; a compile with pages the
80
- // boundary cannot raster is a genuine unsupported.
66
+ // A 0-page compile is a recoverable empty; pages the boundary cannot
67
+ // raster are a genuine unsupported.
81
68
  paintLoop.refresh([], 0);
82
69
  detach();
83
70
  showMessage(pageCount === 0 ? t.noPages : t.unsupported, pageCount === 0 ? EMPTY_CLASS : UNSUPPORTED_CLASS);
84
71
  return;
85
72
  }
86
- // Read the count against the current slots BEFORE reconcile moves it, and
87
- // clear any prior message BEFORE painting so a paint that throws mid-refresh
73
+ // Read the count against the current slots before reconcile moves it, and
74
+ // clear any prior message before painting so a paint that throws mid-refresh
88
75
  // leaves its error surfaced rather than have `hideMessage` clobber it.
89
76
  const countChanged = pageCount !== paintLoop.slots.length;
90
77
  hideMessage();
91
78
  paintLoop.refresh(dirtyPages, pageCount);
79
+ // The slot set moved under the bridge's feet, or we are leaving a message state.
92
80
  if (countChanged || !bridge) {
93
- // The slot set moved under overlay/bridge's feet (pages added/removed,
94
- // or we are leaving a message state): rebuild both against the
95
- // reconciled slots rather than patch a stale snapshot in place.
96
81
  detach();
97
82
  attach();
98
83
  }
99
- else {
100
- // Box positions can still shift within the same page set (text reflow),
101
- // so geometry is always re-read, not just dirty pages.
102
- overlay?.refresh();
103
- }
104
84
  }
105
85
  render(session.pageCount, []);
106
86
  // The last place the editor put the caret, re-located after every recompile.
107
- // `session.locate` answers against the LAST COMPILED layout and a consumer
87
+ // `session.locate` answers against the last compiled layout and a consumer
108
88
  // debounces `update`, so a caret typed past that layout's content is off-content
109
89
  // for the whole burst: `focusPosition` no-ops and the pane sits still, and a
110
90
  // caret event is otherwise the only thing that asks again. The re-locate is the
@@ -120,16 +100,11 @@ export function createPreview(session, opts) {
120
100
  bridge?.focusPosition(followed.field, followed.pos);
121
101
  },
122
102
  scrollToField(field) {
123
- const found = bridge?.scrollToField(field) ?? false;
124
- // Marked either way: a compile that gains the address later blooms it on the
125
- // rebuild, since the flash carries its own start time (`core/bloom.ts`).
126
- overlay?.flashField(field);
127
- return found;
103
+ return bridge?.scrollToField(field) ?? false;
128
104
  },
129
105
  focusPosition(at) {
130
106
  followed = at;
131
107
  bridge?.focusPosition(at.field, at.pos);
132
- overlay?.flashField(at.field);
133
108
  },
134
109
  setZoom(scale) {
135
110
  paintLoop.setDensityZoom(scale);
@@ -15,9 +15,8 @@ export interface PdfPoint {
15
15
  }
16
16
  /**
17
17
  * An address's boxes: `fieldBoxes`'s union, else its own `regions()` rects, else the
18
- * rects of everything UNDER it. The one rule the overlay and the scroll both read, so
19
- * an address `regions()` names never draws nothing (PREVIEW.md §"Three
20
- * responsibilities").
18
+ * rects of everything under it. What the scroll reads, so an address `regions()`
19
+ * names is never one the preview cannot place (PREVIEW.md §"Two responsibilities").
21
20
  *
22
21
  * `fieldBoxes` answers `[]` for a scalar reference the plate places without tracking
23
22
  * its content and for a `richtext[]` element; runtime.d.ts says what such a field's
@@ -30,20 +29,19 @@ export interface PdfPoint {
30
29
  export declare function boxesForField(field: string, boxes: readonly FieldRegion[], regions: readonly FieldRegion[]): readonly FieldRegion[];
31
30
  /**
32
31
  * Forward: a PDF-pt rect -> CSS % of the page box. Percent positioning tracks
33
- * the page's displayed size across DPR and container resize for free; this is
34
- * what the overlay draws (overlay.ts).
32
+ * the page's displayed size across DPR and container resize for free, so the
33
+ * scroll's measurement needs no zoom or DPR term of its own (bridge.ts).
35
34
  */
36
35
  export declare function rectToPercent(rect: PdfRect, pageSize: PageSize): PercentRect;
37
36
  /**
38
- * Position `el` absolutely at `pct` (the % rect this module produces); the one
39
- * `PercentRect` → `element.style` write, shared by the overlay's field boxes and
40
- * the bridge's scroll marker so the positioning convention lives in one place.
41
- * The caller sets any box chrome (border, background) on top.
37
+ * Position `el` absolutely at `pct` (the % rect this module produces): the one
38
+ * `PercentRect` → `element.style` write, so the positioning convention lives beside
39
+ * the transform that produces it rather than at the caller placing a marker.
42
40
  */
43
41
  export declare function applyPercentRect(el: HTMLElement, pct: PercentRect): void;
44
42
  /**
45
43
  * Inverse: a click at CSS px `(px, py)` within a page element of CSS size
46
- * `(cssW, cssH)` -> PDF-pt. The EXACT inverse of {@link rectToPercent}: the
44
+ * `(cssW, cssH)` -> PDF-pt. The exact inverse of {@link rectToPercent}: the
47
45
  * fraction `rectToPercent` would place a point's box corner at is the same
48
46
  * fraction this reads back from `px/cssW`, `py/cssH`; DPR-independent, since
49
47
  * the page element's CSS size (not its canvas backing store) is the only input.
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * An address's boxes: `fieldBoxes`'s union, else its own `regions()` rects, else the
3
- * rects of everything UNDER it. The one rule the overlay and the scroll both read, so
4
- * an address `regions()` names never draws nothing (PREVIEW.md §"Three
5
- * responsibilities").
3
+ * rects of everything under it. What the scroll reads, so an address `regions()`
4
+ * names is never one the preview cannot place (PREVIEW.md §"Two responsibilities").
6
5
  *
7
6
  * `fieldBoxes` answers `[]` for a scalar reference the plate places without tracking
8
7
  * its content and for a `richtext[]` element; runtime.d.ts says what such a field's
@@ -20,8 +19,8 @@ export function boxesForField(field, boxes, regions) {
20
19
  }
21
20
  /**
22
21
  * Forward: a PDF-pt rect -> CSS % of the page box. Percent positioning tracks
23
- * the page's displayed size across DPR and container resize for free; this is
24
- * what the overlay draws (overlay.ts).
22
+ * the page's displayed size across DPR and container resize for free, so the
23
+ * scroll's measurement needs no zoom or DPR term of its own (bridge.ts).
25
24
  */
26
25
  export function rectToPercent(rect, pageSize) {
27
26
  const [x0, y0, x1, y1] = rect;
@@ -34,10 +33,9 @@ export function rectToPercent(rect, pageSize) {
34
33
  };
35
34
  }
36
35
  /**
37
- * Position `el` absolutely at `pct` (the % rect this module produces); the one
38
- * `PercentRect` → `element.style` write, shared by the overlay's field boxes and
39
- * the bridge's scroll marker so the positioning convention lives in one place.
40
- * The caller sets any box chrome (border, background) on top.
36
+ * Position `el` absolutely at `pct` (the % rect this module produces): the one
37
+ * `PercentRect` → `element.style` write, so the positioning convention lives beside
38
+ * the transform that produces it rather than at the caller placing a marker.
41
39
  */
42
40
  export function applyPercentRect(el, pct) {
43
41
  Object.assign(el.style, {
@@ -50,7 +48,7 @@ export function applyPercentRect(el, pct) {
50
48
  }
51
49
  /**
52
50
  * Inverse: a click at CSS px `(px, py)` within a page element of CSS size
53
- * `(cssW, cssH)` -> PDF-pt. The EXACT inverse of {@link rectToPercent}: the
51
+ * `(cssW, cssH)` -> PDF-pt. The exact inverse of {@link rectToPercent}: the
54
52
  * fraction `rectToPercent` would place a point's box corner at is the same
55
53
  * fraction this reads back from `px/cssW`, `py/cssH`; DPR-independent, since
56
54
  * the page element's CSS size (not its canvas backing store) is the only input.
@@ -1,12 +1,12 @@
1
1
  // `@quillmark/svelte/preview`: the live-preview surface.
2
2
  //
3
3
  // `createPreview(session)` (vanilla-TS core) + a thin `<Preview>` Svelte wrapper
4
- // land here. RESERVED-PACKAGE INVARIANT: this subpath imports no editor-side code
4
+ // land here. Reserved-package invariant: this subpath imports no editor-side code
5
5
  // (the codec, the VisualEditor) so a `@quillmark/preview` promotion stays a
6
6
  // re-export, not a refactor: it may reach `/core` (the shared WASM boundary) and
7
7
  // nothing else. Enforced by `check:deps`, which walks the subpath's import graph.
8
- // The theme derivation, which the page slots and the overlay read through `var()`.
9
- // Imported at the BARREL because a subpath is what a consumer gets: this surface
8
+ // The theme derivation, which the page slots read through `var()`.
9
+ // Imported at the barrel because a subpath is what a consumer gets: this surface
10
10
  // reaches modules inside `core/` and never its entry, so a sheet hanging off that
11
11
  // entry arrives only for a consumer importing `/core` for some other reason. The
12
12
  // sheet rather than the entry, because the barrel needs the derivation and not
@@ -14,7 +14,7 @@
14
14
  import '../core/theme.css';
15
15
  export { createPreview } from './controller.js';
16
16
  export { default as Preview } from './Preview.svelte';
17
- // What the preview SAYS when there is nothing to paint. Keyed and partial; the
17
+ // What the preview says when there is nothing to paint. Keyed and partial; the
18
18
  // English is exported so a consumer composes against it rather than restating it.
19
19
  // The merge is the surface's own: a consumer hands `strings` to a prop, never
20
20
  // merges one.
@@ -1,5 +1,5 @@
1
1
  import type { LiveSession, PageSize } from '@quillmark/wasm';
2
- /** One page's DOM slot: the box overlay.ts/bridge.ts position against, plus its cached geometry. */
2
+ /** One page's DOM slot: the box bridge.ts measures against, plus its cached geometry. */
3
3
  export interface PageSlot {
4
4
  readonly page: number;
5
5
  readonly size: PageSize;