@quillmark/svelte 0.26.2 → 0.28.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.
- package/NOTICE +4 -4
- package/README.md +26 -8
- package/THEMING.md +40 -4
- package/dist/core/address.d.ts +35 -64
- package/dist/core/address.js +35 -55
- package/dist/core/bloom.d.ts +4 -10
- package/dist/core/bloom.js +14 -27
- package/dist/core/codec/blocks.d.ts +41 -43
- package/dist/core/codec/blocks.js +97 -130
- package/dist/core/codec/code.d.ts +7 -10
- package/dist/core/codec/code.js +37 -46
- package/dist/core/codec/decode.d.ts +19 -23
- package/dist/core/codec/decode.js +76 -111
- package/dist/core/codec/encode.d.ts +11 -15
- package/dist/core/codec/encode.js +60 -67
- package/dist/core/codec/field.d.ts +62 -95
- package/dist/core/codec/field.js +107 -179
- package/dist/core/codec/islands.d.ts +33 -41
- package/dist/core/codec/islands.js +76 -86
- package/dist/core/codec/lists.d.ts +17 -26
- package/dist/core/codec/lists.js +117 -157
- package/dist/core/codec/marks.d.ts +4 -3
- package/dist/core/codec/marks.js +8 -3
- package/dist/core/codec/prose.css +84 -46
- package/dist/core/codec/schema.d.ts +18 -23
- package/dist/core/codec/schema.js +84 -76
- package/dist/core/codec/slash.d.ts +42 -48
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-copy.d.ts +4 -3
- package/dist/core/codec/table-copy.js +25 -7
- package/dist/core/codec/table-view.d.ts +12 -12
- package/dist/core/codec/table-view.js +460 -383
- package/dist/core/codec/table.d.ts +94 -61
- package/dist/core/codec/table.js +331 -83
- package/dist/core/codec/tasks.d.ts +6 -0
- package/dist/core/codec/tasks.js +30 -3
- package/dist/core/errors.d.ts +15 -30
- package/dist/core/errors.js +2 -6
- package/dist/core/index.js +7 -12
- package/dist/core/lifecycle.d.ts +7 -16
- package/dist/core/lifecycle.js +10 -21
- package/dist/core/rebind.svelte.d.ts +10 -20
- package/dist/core/rebind.svelte.js +15 -27
- package/dist/core/rungs.d.ts +14 -11
- package/dist/core/rungs.js +17 -19
- package/dist/core/teardown.d.ts +4 -6
- package/dist/core/teardown.js +7 -10
- package/dist/core/theme.css +74 -54
- package/dist/index.js +3 -8
- package/dist/preset/app.css +71 -0
- package/dist/preset/index.css +8 -7
- package/dist/preset/recipes.css +19 -164
- package/dist/preset/scale.css +28 -18
- package/dist/preset/shell.css +157 -0
- package/dist/preview/Preview.svelte +8 -0
- package/dist/preview/bridge.d.ts +18 -22
- package/dist/preview/bridge.js +29 -58
- package/dist/preview/controller.d.ts +21 -29
- package/dist/preview/controller.js +14 -39
- package/dist/preview/geometry.d.ts +13 -34
- package/dist/preview/geometry.js +13 -34
- package/dist/preview/index.js +4 -16
- package/dist/preview/paint.d.ts +3 -3
- package/dist/preview/paint.js +28 -68
- package/dist/preview/strings.d.ts +1 -1
- package/dist/preview/strings.js +2 -14
- package/dist/preview/viewport.js +20 -15
- package/dist/preview/zoom.d.ts +21 -22
- package/dist/preview/zoom.js +53 -30
- package/dist/visual/ArrayField.svelte +77 -16
- package/dist/visual/BooleanField.svelte +28 -15
- package/dist/visual/Card.svelte +66 -51
- package/dist/visual/CardControls.svelte +5 -11
- package/dist/visual/DateField.svelte +20 -4
- package/dist/visual/EnumField.svelte +3 -0
- package/dist/visual/FieldHint.svelte +2 -1
- package/dist/visual/FormatPopover.svelte +82 -15
- package/dist/visual/FormatPopover.svelte.d.ts +4 -0
- package/dist/visual/MatrixAdd.svelte +30 -12
- package/dist/visual/MatrixField.svelte +24 -16
- package/dist/visual/NumberField.svelte +7 -0
- package/dist/visual/ObjectField.svelte +4 -4
- package/dist/visual/ProseField.svelte +0 -5
- package/dist/visual/TipsCard.svelte +11 -10
- package/dist/visual/VariantField.svelte +3 -1
- package/dist/visual/VisualEditorInner.svelte +19 -3
- package/dist/visual/controls.css +7 -2
- package/dist/visual/fonts.d.ts +18 -0
- package/dist/visual/fonts.js +59 -0
- package/dist/visual/icons/nodes.d.ts +0 -5
- package/dist/visual/icons/nodes.js +0 -4
- package/dist/visual/props.d.ts +14 -1
- package/dist/visual/strings.d.ts +7 -0
- package/dist/visual/strings.js +5 -2
- package/dist/visual/structure.d.ts +43 -87
- package/dist/visual/structure.js +42 -83
- package/package.json +2 -2
|
@@ -14,50 +14,29 @@ export interface PdfPoint {
|
|
|
14
14
|
y: number;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* An address's boxes: `fieldBoxes`'s union, else every `regions()` rect at or under it
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* An address's boxes: `fieldBoxes`'s union, else every `regions()` rect at or under it
|
|
18
|
+
* (PREVIEW.md §"Two responsibilities"). `fieldBoxes` answers `[]` for a scalar reference
|
|
19
|
+
* the plate places without tracking its content and for an array itself; the fallback
|
|
20
|
+
* takes the address and its descendants, matched at a path boundary (`.` or `[`):
|
|
21
|
+
* `main.keywords` matches `main.keywords[0]`, not `main.keywords_note`.
|
|
20
22
|
*
|
|
21
|
-
* `
|
|
22
|
-
* its content and for an array itself; runtime.d.ts says what such a field's box is, a
|
|
23
|
-
* single `regions()` rect. The second rung takes an address and its descendants
|
|
24
|
-
* together, since a container holds both: a plate composing an array's presentation
|
|
25
|
-
* regions the array on the ink it composed — a separator, a heading — where each
|
|
26
|
-
* element regions the ink it carries itself, and the field is placed at all of it. The
|
|
27
|
-
* boundary character (`.` for a nested field, `[` for an array element) keeps the
|
|
28
|
-
* prefix a path boundary: `main.keywords` matches `main.keywords[0]`, not
|
|
29
|
-
* `main.keywords_note`.
|
|
30
|
-
*
|
|
31
|
-
* `regions` is a thunk because the first rung answers without it, and taking it
|
|
32
|
-
* marshals every placement in the compile across the WASM boundary.
|
|
23
|
+
* `regions` is a thunk because taking it marshals every placement across the WASM boundary.
|
|
33
24
|
*/
|
|
34
25
|
export declare function boxesForField(field: string, boxes: readonly FieldRegion[], regions: () => readonly FieldRegion[]): readonly FieldRegion[];
|
|
35
26
|
/**
|
|
36
|
-
* Forward: a PDF-pt rect -> CSS % of the page box
|
|
37
|
-
*
|
|
38
|
-
* scroll's measurement needs no zoom or DPR term of its own (bridge.ts).
|
|
27
|
+
* Forward: a PDF-pt rect -> CSS % of the page box, which tracks the displayed size without a zoom
|
|
28
|
+
* or DPR term.
|
|
39
29
|
*/
|
|
40
30
|
export declare function rectToPercent(rect: PdfRect, pageSize: PageSize): PercentRect;
|
|
41
|
-
/**
|
|
42
|
-
* Position `el` absolutely at `pct` (the % rect this module produces): the one
|
|
43
|
-
* `PercentRect` → `element.style` write, so the positioning convention lives beside
|
|
44
|
-
* the transform that produces it rather than at the caller placing a marker.
|
|
45
|
-
*/
|
|
31
|
+
/** Position `el` absolutely at `pct`. */
|
|
46
32
|
export declare function applyPercentRect(el: HTMLElement, pct: PercentRect): void;
|
|
47
33
|
/**
|
|
48
|
-
* A CSS-px length on a page element of CSS width `cssW` ->
|
|
49
|
-
*
|
|
50
|
-
* by width under a locked aspect ratio, so {@link clickToPdfPt}'s two ratios are one
|
|
51
|
-
* number and a length has one to convert through. What the click bridge reads its
|
|
52
|
-
* pointer slack in points through.
|
|
34
|
+
* A CSS-px length on a page element of CSS width `cssW` -> PDF-pt. Width alone, since `paint.ts`
|
|
35
|
+
* locks the aspect ratio.
|
|
53
36
|
*/
|
|
54
37
|
export declare function pxToPt(px: number, cssW: number, pageSize: PageSize): number;
|
|
55
38
|
/**
|
|
56
|
-
* Inverse: a click at CSS px `(px, py)` within a page element of CSS size
|
|
57
|
-
* `(cssW, cssH)` -> PDF-pt.
|
|
58
|
-
* fraction `rectToPercent` would place a point's box corner at is the same
|
|
59
|
-
* fraction this reads back from `px/cssW`, `py/cssH`; DPR-independent, since
|
|
60
|
-
* the page element's CSS size (not its canvas backing store) is the only input.
|
|
61
|
-
* This is what the click bridge feeds `session.positionAt`/`fieldAt` (bridge.ts).
|
|
39
|
+
* Inverse of {@link rectToPercent}: a click at CSS px `(px, py)` within a page element of CSS size
|
|
40
|
+
* `(cssW, cssH)` -> PDF-pt.
|
|
62
41
|
*/
|
|
63
42
|
export declare function clickToPdfPt(px: number, py: number, cssW: number, cssH: number, pageSize: PageSize): PdfPoint;
|
package/dist/preview/geometry.js
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* An address's boxes: `fieldBoxes`'s union, else every `regions()` rect at or under it
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* An address's boxes: `fieldBoxes`'s union, else every `regions()` rect at or under it
|
|
3
|
+
* (PREVIEW.md §"Two responsibilities"). `fieldBoxes` answers `[]` for a scalar reference
|
|
4
|
+
* the plate places without tracking its content and for an array itself; the fallback
|
|
5
|
+
* takes the address and its descendants, matched at a path boundary (`.` or `[`):
|
|
6
|
+
* `main.keywords` matches `main.keywords[0]`, not `main.keywords_note`.
|
|
5
7
|
*
|
|
6
|
-
* `
|
|
7
|
-
* its content and for an array itself; runtime.d.ts says what such a field's box is, a
|
|
8
|
-
* single `regions()` rect. The second rung takes an address and its descendants
|
|
9
|
-
* together, since a container holds both: a plate composing an array's presentation
|
|
10
|
-
* regions the array on the ink it composed — a separator, a heading — where each
|
|
11
|
-
* element regions the ink it carries itself, and the field is placed at all of it. The
|
|
12
|
-
* boundary character (`.` for a nested field, `[` for an array element) keeps the
|
|
13
|
-
* prefix a path boundary: `main.keywords` matches `main.keywords[0]`, not
|
|
14
|
-
* `main.keywords_note`.
|
|
15
|
-
*
|
|
16
|
-
* `regions` is a thunk because the first rung answers without it, and taking it
|
|
17
|
-
* marshals every placement in the compile across the WASM boundary.
|
|
8
|
+
* `regions` is a thunk because taking it marshals every placement across the WASM boundary.
|
|
18
9
|
*/
|
|
19
10
|
export function boxesForField(field, boxes, regions) {
|
|
20
11
|
if (boxes.length)
|
|
@@ -22,9 +13,8 @@ export function boxesForField(field, boxes, regions) {
|
|
|
22
13
|
return regions().filter((r) => r.field === field || r.field.startsWith(`${field}.`) || r.field.startsWith(`${field}[`));
|
|
23
14
|
}
|
|
24
15
|
/**
|
|
25
|
-
* Forward: a PDF-pt rect -> CSS % of the page box
|
|
26
|
-
*
|
|
27
|
-
* scroll's measurement needs no zoom or DPR term of its own (bridge.ts).
|
|
16
|
+
* Forward: a PDF-pt rect -> CSS % of the page box, which tracks the displayed size without a zoom
|
|
17
|
+
* or DPR term.
|
|
28
18
|
*/
|
|
29
19
|
export function rectToPercent(rect, pageSize) {
|
|
30
20
|
const [x0, y0, x1, y1] = rect;
|
|
@@ -36,11 +26,7 @@ export function rectToPercent(rect, pageSize) {
|
|
|
36
26
|
height: ((y1 - y0) / heightPt) * 100
|
|
37
27
|
};
|
|
38
28
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Position `el` absolutely at `pct` (the % rect this module produces): the one
|
|
41
|
-
* `PercentRect` → `element.style` write, so the positioning convention lives beside
|
|
42
|
-
* the transform that produces it rather than at the caller placing a marker.
|
|
43
|
-
*/
|
|
29
|
+
/** Position `el` absolutely at `pct`. */
|
|
44
30
|
export function applyPercentRect(el, pct) {
|
|
45
31
|
Object.assign(el.style, {
|
|
46
32
|
position: 'absolute',
|
|
@@ -51,22 +37,15 @@ export function applyPercentRect(el, pct) {
|
|
|
51
37
|
});
|
|
52
38
|
}
|
|
53
39
|
/**
|
|
54
|
-
* A CSS-px length on a page element of CSS width `cssW` ->
|
|
55
|
-
*
|
|
56
|
-
* by width under a locked aspect ratio, so {@link clickToPdfPt}'s two ratios are one
|
|
57
|
-
* number and a length has one to convert through. What the click bridge reads its
|
|
58
|
-
* pointer slack in points through.
|
|
40
|
+
* A CSS-px length on a page element of CSS width `cssW` -> PDF-pt. Width alone, since `paint.ts`
|
|
41
|
+
* locks the aspect ratio.
|
|
59
42
|
*/
|
|
60
43
|
export function pxToPt(px, cssW, pageSize) {
|
|
61
44
|
return (px / cssW) * pageSize.widthPt;
|
|
62
45
|
}
|
|
63
46
|
/**
|
|
64
|
-
* Inverse: a click at CSS px `(px, py)` within a page element of CSS size
|
|
65
|
-
* `(cssW, cssH)` -> PDF-pt.
|
|
66
|
-
* fraction `rectToPercent` would place a point's box corner at is the same
|
|
67
|
-
* fraction this reads back from `px/cssW`, `py/cssH`; DPR-independent, since
|
|
68
|
-
* the page element's CSS size (not its canvas backing store) is the only input.
|
|
69
|
-
* This is what the click bridge feeds `session.positionAt`/`fieldAt` (bridge.ts).
|
|
47
|
+
* Inverse of {@link rectToPercent}: a click at CSS px `(px, py)` within a page element of CSS size
|
|
48
|
+
* `(cssW, cssH)` -> PDF-pt.
|
|
70
49
|
*/
|
|
71
50
|
export function clickToPdfPt(px, py, cssW, cssH, pageSize) {
|
|
72
51
|
const { widthPt, heightPt } = pageSize;
|
package/dist/preview/index.js
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
// `@quillmark/svelte/preview`: the live-preview surface.
|
|
2
|
-
//
|
|
3
|
-
// `
|
|
4
|
-
//
|
|
5
|
-
// (the codec, the VisualEditor) so a `@quillmark/preview` promotion stays a
|
|
6
|
-
// re-export, not a refactor: it may reach `/core` (the shared WASM boundary) and
|
|
7
|
-
// nothing else. Enforced by `check:deps`, at the subpath's edge: a relative hop past a
|
|
8
|
-
// module directly in `core/` is what pulls the codec, and all of ProseMirror with it.
|
|
9
|
-
// The theme derivation, which the page slots read through `var()`.
|
|
10
|
-
// Imported at the barrel because a subpath is what a consumer gets: this surface
|
|
11
|
-
// reaches modules inside `core/` and never its entry, which carries no sheet.
|
|
12
|
-
// `check:deps` holds the reach.
|
|
1
|
+
// `@quillmark/svelte/preview`: the live-preview surface. Imports no editor-side code; it may reach
|
|
2
|
+
// `/core` and nothing else (`check:deps`).
|
|
3
|
+
// The theme derivation the page slots read through `var()`; a subpath import reaches `core/`
|
|
4
|
+
// modules, never its entry, which carries no sheet.
|
|
13
5
|
import '../core/theme.css';
|
|
14
6
|
export { createPreview } from './controller.js';
|
|
15
7
|
export { default as Preview } from './Preview.svelte';
|
|
16
|
-
// What the preview says: its message states and the zoom pill's name. Keyed and partial; the
|
|
17
|
-
// English is exported so a consumer composes against it rather than restating it.
|
|
18
|
-
// The merge is the surface's own: a consumer hands `strings` to a prop, never
|
|
19
|
-
// merges one.
|
|
20
8
|
export { DEFAULT_PREVIEW_STRINGS } from './strings.js';
|
package/dist/preview/paint.d.ts
CHANGED
|
@@ -11,9 +11,9 @@ export interface PaintLoop {
|
|
|
11
11
|
/** Reconcile slot count to `pageCount`, re-cache geometry, repaint mounted `dirtyPages`. */
|
|
12
12
|
refresh(dirtyPages: readonly number[], pageCount: number): void;
|
|
13
13
|
/**
|
|
14
|
-
* Draw every page at `scale` CSS px per PDF pt and repaint the mounted ones
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Draw every page at `scale` CSS px per PDF pt and repaint the mounted ones on the next frame,
|
|
15
|
+
* or once `settle` ms pass without another call (a gesture's request, the stretched raster
|
|
16
|
+
* standing in).
|
|
17
17
|
*/
|
|
18
18
|
setScale(scale: number, settle?: number): void;
|
|
19
19
|
destroy(): void;
|
package/dist/preview/paint.js
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
// Vertical breathing room between stacked pages; cosmetic only, and a rung like
|
|
2
|
-
// any other gap: the stack breathes on the same rhythm the cards do.
|
|
3
1
|
const PAGE_GAP = 'var(--_qm-space-4)';
|
|
4
|
-
// The least raster density, in device pixels per CSS pixel
|
|
5
|
-
//
|
|
6
|
-
// is resampled nearly 1:1 and small text smears; a raster at least twice the
|
|
7
|
-
// box is downsampled instead, which keeps body text legible.
|
|
2
|
+
// The least raster density, in device pixels per CSS pixel: glyphs rasterize unhinted, so a 1× or
|
|
3
|
+
// fractional raster smears small text where a 2× one downsamples cleanly.
|
|
8
4
|
const MIN_DENSITY = 2;
|
|
9
5
|
export function createPaintLoop(session, container, margin,
|
|
10
|
-
// Notified when a slot's `session.paint` throws
|
|
11
|
-
//
|
|
12
|
-
// decides how to surface it.
|
|
6
|
+
// Notified when a slot's `session.paint` throws; the throw is swallowed so one bad page never
|
|
7
|
+
// aborts the band sweep.
|
|
13
8
|
onPaintError) {
|
|
14
|
-
// The IntersectionObserver root must be a scrollable ancestor of the slots;
|
|
15
|
-
//
|
|
16
|
-
// a stylesheet rule counts as a choice, not just an inline style).
|
|
9
|
+
// The IntersectionObserver root must be a scrollable ancestor of the slots; a consumer's own
|
|
10
|
+
// choice stands.
|
|
17
11
|
const computed = getComputedStyle(container);
|
|
18
12
|
if (computed.overflowY === 'visible')
|
|
19
13
|
container.style.overflowY = 'auto';
|
|
@@ -42,27 +36,15 @@ onPaintError) {
|
|
|
42
36
|
}, { root: container, threshold: 0 });
|
|
43
37
|
function makeSlot(page) {
|
|
44
38
|
const size = session.pageSize(page);
|
|
45
|
-
// The
|
|
46
|
-
//
|
|
47
|
-
// edge is where two tones meet and no stroke is drawn around it
|
|
48
|
-
// (ARCHITECTURE §Styling). It leaves the no-ink rule untouched, since a
|
|
49
|
-
// boundary that is not painted is not a mark on the page (PREVIEW §"Two
|
|
50
|
-
// responsibilities"). `border-box` costs nothing here and keeps a slot a
|
|
51
|
-
// consumer insets from measuring wider than its paper.
|
|
39
|
+
// The paper edge is where `--_qm-surface` meets the container's `--_qm-surface-sunken`; no
|
|
40
|
+
// stroke is drawn (ARCHITECTURE §Styling).
|
|
52
41
|
const el = document.createElement('div');
|
|
53
|
-
// Not `.qm-page`: that
|
|
54
|
-
//
|
|
55
|
-
// rule's `color-scheme` inside a mounted root, which the derivation leaves to the
|
|
56
|
-
// host (`core/theme.css`).
|
|
42
|
+
// Not `.qm-page`: that is the preset's page baseline, whose `color-scheme` a slot must not
|
|
43
|
+
// take inside a mounted root (`core/theme.css`).
|
|
57
44
|
el.className = 'qm-page-slot';
|
|
58
|
-
// The page's index, in the DOM. `pageByEl` is this module's and a consumer
|
|
59
|
-
// drawing an overlay of its own needs the number: position among the slots is
|
|
60
|
-
// the only handle without it, which is right today and is not a contract.
|
|
61
45
|
el.dataset.page = String(page);
|
|
62
|
-
// `flex: none`
|
|
63
|
-
//
|
|
64
|
-
// narrower than the pane and resolve to zero on one wider, so it overflows to the
|
|
65
|
-
// scrollable side.
|
|
46
|
+
// `flex: none` keeps the flex column from shrinking a page; auto inline margins centre a
|
|
47
|
+
// narrower page and overflow a wider one to the scrollable side.
|
|
66
48
|
Object.assign(el.style, {
|
|
67
49
|
position: 'relative',
|
|
68
50
|
boxSizing: 'border-box',
|
|
@@ -74,7 +56,7 @@ onPaintError) {
|
|
|
74
56
|
sizeSlot(el, size);
|
|
75
57
|
if (page > 0)
|
|
76
58
|
el.style.marginTop = PAGE_GAP;
|
|
77
|
-
// After the last slot
|
|
59
|
+
// After the last slot: the zoom pill's dock is a child too.
|
|
78
60
|
const last = slots.at(-1)?.el;
|
|
79
61
|
if (last)
|
|
80
62
|
last.after(el);
|
|
@@ -92,8 +74,6 @@ onPaintError) {
|
|
|
92
74
|
const layoutScale = scale ?? (slot.el.clientWidth || slot.size.widthPt) / slot.size.widthPt;
|
|
93
75
|
return layoutScale * Math.max(window.devicePixelRatio || 1, MIN_DENSITY);
|
|
94
76
|
}
|
|
95
|
-
// Rasterize `slot` into its canvas (creating the canvas if this is its first
|
|
96
|
-
// paint since mounting).
|
|
97
77
|
function paintSlot(slot) {
|
|
98
78
|
let canvas = canvases.get(slot.page);
|
|
99
79
|
if (!canvas) {
|
|
@@ -107,8 +87,7 @@ onPaintError) {
|
|
|
107
87
|
height: '100%'
|
|
108
88
|
});
|
|
109
89
|
}
|
|
110
|
-
//
|
|
111
|
-
// the page as unmounted and retries instead of keeping a blank page.
|
|
90
|
+
// Bail before registering so `updateBand` retries rather than keeping a blank page.
|
|
112
91
|
const ctx = canvas.getContext('2d');
|
|
113
92
|
if (!ctx)
|
|
114
93
|
return;
|
|
@@ -122,11 +101,8 @@ onPaintError) {
|
|
|
122
101
|
rastered.set(slot.page, at);
|
|
123
102
|
}
|
|
124
103
|
catch (err) {
|
|
125
|
-
//
|
|
126
|
-
// the
|
|
127
|
-
// uncaught throw would strand the rest of the band unpainted. Unmount the
|
|
128
|
-
// half-mounted canvas (so the page is a retryable unmounted slot, not a
|
|
129
|
-
// blank registered one) and surface the failure; the sweep moves on.
|
|
104
|
+
// Unmount the half-mounted canvas so the page stays a retryable unmounted slot, and let
|
|
105
|
+
// the sweep move on.
|
|
130
106
|
unmountPage(slot.page);
|
|
131
107
|
onPaintError?.(slot.page, err);
|
|
132
108
|
}
|
|
@@ -139,10 +115,8 @@ onPaintError) {
|
|
|
139
115
|
canvases.delete(page);
|
|
140
116
|
rastered.delete(page);
|
|
141
117
|
}
|
|
142
|
-
// Mount pages newly inside [min(visible)-margin, max(visible)+margin]
|
|
143
|
-
//
|
|
144
|
-
// an already-mounted page in the band is left alone (paint contract: an idle
|
|
145
|
-
// canvas keeps its pixels for free; repainting it here would be pure waste).
|
|
118
|
+
// Mount pages newly inside [min(visible)-margin, max(visible)+margin] and unmount those outside
|
|
119
|
+
// it; a mounted page in the band keeps its pixels.
|
|
146
120
|
function updateBand() {
|
|
147
121
|
const painted = new Set();
|
|
148
122
|
if (visible.size === 0 || slots.length === 0)
|
|
@@ -162,10 +136,7 @@ onPaintError) {
|
|
|
162
136
|
}
|
|
163
137
|
return painted;
|
|
164
138
|
}
|
|
165
|
-
// Grow
|
|
166
|
-
// reports the new count, and dirtyPages already covers any reflow), then
|
|
167
|
-
// re-cache every page's geometry (cheap report-only reads; simplest way to
|
|
168
|
-
// stay correct across any recompile, not just a count change).
|
|
139
|
+
// Grow or shrink the slots to `pageCount`, then re-cache every page's geometry.
|
|
169
140
|
function reconcile(pageCount) {
|
|
170
141
|
while (slots.length > pageCount) {
|
|
171
142
|
const slot = slots.pop();
|
|
@@ -183,22 +154,15 @@ onPaintError) {
|
|
|
183
154
|
for (let i = 0; i < slots.length; i++) {
|
|
184
155
|
const size = session.pageSize(i);
|
|
185
156
|
slots[i] = { ...slots[i], size };
|
|
186
|
-
//
|
|
187
|
-
// can change a page's dimensions without changing the count, and every
|
|
188
|
-
// %-space click/scroll transform assumes box shape matches PageSize.
|
|
157
|
+
// A recompile can change a page's dimensions without changing the count.
|
|
189
158
|
slots[i].el.style.aspectRatio = `${size.widthPt} / ${size.heightPt}`;
|
|
190
159
|
sizeSlot(slots[i].el, size);
|
|
191
160
|
}
|
|
192
161
|
}
|
|
193
|
-
// Slots are built on the first `refresh`
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
// A canvas stretches with its slot, so a page that stays mounted while the scale
|
|
198
|
-
// or `devicePixelRatio` shifts keeps its place and goes soft. Both repaint mounted
|
|
199
|
-
// pages; canvases are `position:absolute`, so a repaint can't feed back into
|
|
200
|
-
// layout.
|
|
201
|
-
// Repaint mounted pages whose raster scale moved. A no-op for an unmounted page.
|
|
162
|
+
// Slots are built on the first `refresh`: `session.pageSize` throws for a page the count
|
|
163
|
+
// excludes.
|
|
164
|
+
// Repaint mounted pages whose raster scale moved; a canvas stretches with its slot and goes
|
|
165
|
+
// soft otherwise.
|
|
202
166
|
function repaint() {
|
|
203
167
|
for (const page of canvases.keys()) {
|
|
204
168
|
const slot = slots[page];
|
|
@@ -229,11 +193,9 @@ onPaintError) {
|
|
|
229
193
|
repaint();
|
|
230
194
|
});
|
|
231
195
|
}
|
|
232
|
-
// `devicePixelRatio`
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
// is therefore one-shot: re-arm it against the new ratio on each change. Guarded
|
|
236
|
-
// for environments (jsdom) without `matchMedia`.
|
|
196
|
+
// A `devicePixelRatio` change never reaches `setScale`. A `(resolution: …dppx)` query pins the
|
|
197
|
+
// current ratio, so it is re-armed on each change. Guarded for jsdom, which has no
|
|
198
|
+
// `matchMedia`.
|
|
237
199
|
let dprQuery;
|
|
238
200
|
function onDprChange() {
|
|
239
201
|
scheduleRepaint();
|
|
@@ -252,9 +214,7 @@ onPaintError) {
|
|
|
252
214
|
},
|
|
253
215
|
refresh(dirtyPages, pageCount) {
|
|
254
216
|
reconcile(pageCount);
|
|
255
|
-
//
|
|
256
|
-
// band just painted is already post-apply, so a second full
|
|
257
|
-
// rasterization from the dirty loop would add nothing.
|
|
217
|
+
// A page the band just painted is already post-apply.
|
|
258
218
|
const painted = updateBand();
|
|
259
219
|
for (const page of dirtyPages) {
|
|
260
220
|
if (painted.has(page))
|
|
@@ -6,7 +6,7 @@ export interface PreviewStrings {
|
|
|
6
6
|
/** The zoom pill's action, after the percentage it shows. */
|
|
7
7
|
resetZoom: string;
|
|
8
8
|
}
|
|
9
|
-
/** The package's English.
|
|
9
|
+
/** The package's English. */
|
|
10
10
|
export declare const DEFAULT_PREVIEW_STRINGS: PreviewStrings;
|
|
11
11
|
/** A consumer's wording: any subset, the rest the package's. */
|
|
12
12
|
export type PreviewStringsInput = Partial<PreviewStrings>;
|
package/dist/preview/strings.js
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// reader gives the zoom pill.
|
|
4
|
-
//
|
|
5
|
-
// Separate from the visual surface's set rather than pooled with it: `/preview`
|
|
6
|
-
// reaches `/core` and nothing editor-side, which is what lets it promote to its own
|
|
7
|
-
// package, and a shared strings module would be an edge back across that line. The
|
|
8
|
-
// shape is the same, so a consumer wording both writes the same kind of object
|
|
9
|
-
// twice rather than learning two idioms.
|
|
10
|
-
//
|
|
11
|
-
// A plain options field rather than a context channel, unlike the visual surface's:
|
|
12
|
-
// they are read in one module by one function, so there is no tree to make them
|
|
13
|
-
// ambient for.
|
|
14
|
-
/** The package's English. Every key, so the merge below is total. */
|
|
1
|
+
// Separate from the visual surface's strings: `/preview` reaches `/core` and nothing editor-side.
|
|
2
|
+
/** The package's English. */
|
|
15
3
|
export const DEFAULT_PREVIEW_STRINGS = {
|
|
16
4
|
renderFailed: 'Preview failed to render.',
|
|
17
5
|
noPages: 'No pages to preview.',
|
package/dist/preview/viewport.js
CHANGED
|
@@ -42,15 +42,17 @@ export function createViewport(container, slots, setScale, resetLabel) {
|
|
|
42
42
|
// was hidden at stands, so a return to the same box re-rasters nothing.
|
|
43
43
|
return width > 0 && height > 0 ? { width, height } : undefined;
|
|
44
44
|
}
|
|
45
|
-
// What the pages are drawn at, the zoom held against this pane's floor,
|
|
45
|
+
// What the pages are drawn at, the zoom held against this pane's floor, the floor, and
|
|
46
|
+
// the default fit.
|
|
46
47
|
function bounds() {
|
|
47
48
|
const box = pane();
|
|
48
49
|
if (!box || !slots.length)
|
|
49
50
|
return undefined;
|
|
50
51
|
const sizes = slots.map((s) => s.size);
|
|
51
52
|
const floor = floorZoom(sizes, box);
|
|
52
|
-
const
|
|
53
|
-
|
|
53
|
+
const fit = fitZoom(sizes, box);
|
|
54
|
+
const current = zoom === undefined ? fit : clampZoom(zoom, floor);
|
|
55
|
+
return { current, floor, fit };
|
|
54
56
|
}
|
|
55
57
|
function apply(settle) {
|
|
56
58
|
const b = bounds();
|
|
@@ -113,11 +115,11 @@ export function createViewport(container, slots, setScale, resetLabel) {
|
|
|
113
115
|
// A re-fit holds the line at the pane's top edge, so a dragged divider leaves the
|
|
114
116
|
// reader where they were. The desk turns the browser's own scroll anchoring off
|
|
115
117
|
// (`Preview.svelte`), which would correct the same move a second time.
|
|
116
|
-
function refit() {
|
|
118
|
+
function refit(settle) {
|
|
117
119
|
if (!container.scrollTop && !container.scrollLeft)
|
|
118
|
-
return apply();
|
|
120
|
+
return apply(settle);
|
|
119
121
|
const r = container.getBoundingClientRect();
|
|
120
|
-
holding(r.left + r.width / 2, r.top, () => apply());
|
|
122
|
+
holding(r.left + r.width / 2, r.top, () => apply(settle));
|
|
121
123
|
}
|
|
122
124
|
function centre() {
|
|
123
125
|
const r = container.getBoundingClientRect();
|
|
@@ -125,27 +127,29 @@ export function createViewport(container, slots, setScale, resetLabel) {
|
|
|
125
127
|
}
|
|
126
128
|
// The pointer while it is over the pane, else the pane's centre.
|
|
127
129
|
const anchor = () => pointer ?? centre();
|
|
130
|
+
// A zoom landing on the fit is the default again, and follows the pane from there.
|
|
131
|
+
const settled = (next, b) => next === b.fit ? undefined : next;
|
|
128
132
|
function step(next, at = anchor()) {
|
|
129
133
|
const b = bounds();
|
|
130
134
|
if (!b)
|
|
131
135
|
return;
|
|
132
136
|
run = undefined;
|
|
133
|
-
zoomTo(next(b), at.x, at.y);
|
|
137
|
+
zoomTo(settled(next(b), b), at.x, at.y);
|
|
134
138
|
}
|
|
135
139
|
// About the pane's centre, the pointer being on the pill.
|
|
136
140
|
const reset = () => step(() => undefined, centre());
|
|
137
141
|
// The raw zoom a gesture starting now carries from: the run's own while it is still
|
|
138
142
|
// the one on screen.
|
|
139
143
|
function from(b) {
|
|
140
|
-
return run && run.shown === b.current ? run : { raw: b.current, shown: b.current };
|
|
144
|
+
return run && run.shown === b.current ? run : { raw: b.current, shown: b.current, slack: 0 };
|
|
141
145
|
}
|
|
142
146
|
function glideTo(raw, x, y) {
|
|
143
147
|
const b = bounds();
|
|
144
148
|
if (!b)
|
|
145
149
|
return;
|
|
146
150
|
const start = from(b);
|
|
147
|
-
run = glide(start, raw(start), b.floor);
|
|
148
|
-
zoomTo(run.shown, x, y, SETTLE_MS);
|
|
151
|
+
run = glide(start, raw(start), b.floor, b.fit);
|
|
152
|
+
zoomTo(settled(run.shown, b), x, y, SETTLE_MS);
|
|
149
153
|
}
|
|
150
154
|
// Ctrl-wheel is a mouse's zoom, and the trackpad pinch Chromium and Firefox send as
|
|
151
155
|
// `deltaY = -100 ln(scale)`, in deltas smaller than a notch's: those track the fingers.
|
|
@@ -169,7 +173,7 @@ export function createViewport(container, slots, setScale, resetLabel) {
|
|
|
169
173
|
if ((!dir && e.key !== '0') || !bounds())
|
|
170
174
|
return;
|
|
171
175
|
e.preventDefault();
|
|
172
|
-
step((b) => (dir ? stepZoom(b.current, dir, b.floor) : undefined));
|
|
176
|
+
step((b) => (dir ? stepZoom(b.current, dir, b.floor, b.fit) : undefined));
|
|
173
177
|
}
|
|
174
178
|
let pinch;
|
|
175
179
|
// The fingers on this pane, not one resting on the editor beside it.
|
|
@@ -270,13 +274,14 @@ export function createViewport(container, slots, setScale, resetLabel) {
|
|
|
270
274
|
];
|
|
271
275
|
for (const [target, type, fn, opts] of listeners)
|
|
272
276
|
target.addEventListener(type, fn, opts);
|
|
273
|
-
// The pane's box moving: a dragged divider, a resized window, a hidden track shown.
|
|
274
|
-
// Guarded for jsdom, which
|
|
275
|
-
|
|
277
|
+
// The pane's box moving: a dragged divider, a resized window, a hidden track shown. A
|
|
278
|
+
// drag is a gesture, so its pages re-raster once it rests. Guarded for jsdom, which
|
|
279
|
+
// ships no ResizeObserver.
|
|
280
|
+
const resizeObserver = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(() => refit(SETTLE_MS));
|
|
276
281
|
resizeObserver?.observe(container);
|
|
277
282
|
apply();
|
|
278
283
|
return {
|
|
279
|
-
update: refit,
|
|
284
|
+
update: () => refit(),
|
|
280
285
|
setZoom(next) {
|
|
281
286
|
const valid = next !== undefined && Number.isFinite(next) && next > 0 ? next : undefined;
|
|
282
287
|
if (bounds())
|
package/dist/preview/zoom.d.ts
CHANGED
|
@@ -1,41 +1,40 @@
|
|
|
1
1
|
import type { PageSize } from '@quillmark/wasm';
|
|
2
|
-
/** CSS px per PDF pt at 100%: 96 CSS px to the inch over 72 pt
|
|
3
|
-
* size. */
|
|
2
|
+
/** CSS px per PDF pt at 100%: 96 CSS px to the inch over 72 pt. */
|
|
4
3
|
export declare const ACTUAL: number;
|
|
5
4
|
/** The furthest a gesture zooms in. */
|
|
6
5
|
export declare const MAX_ZOOM = 2;
|
|
7
|
-
/** What `Mod +` and `Mod −` step through
|
|
8
|
-
* keys land on, so a step lands somewhere a reader has been before. */
|
|
6
|
+
/** What `Mod +` and `Mod −` step through. */
|
|
9
7
|
export declare const STEPS: readonly [0.67, 0.75, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2];
|
|
10
8
|
/** The pane's content box, in CSS px. */
|
|
11
9
|
export interface Pane {
|
|
12
10
|
width: number;
|
|
13
11
|
height: number;
|
|
14
12
|
}
|
|
15
|
-
/** The default: the widest page fits the pane's width, capped at {@link FIT_CAP}.
|
|
16
|
-
* portrait-only document lands at the pane's width, as a narrow pane always drew it. */
|
|
13
|
+
/** The default: the widest page fits the pane's width, capped at {@link FIT_CAP}. */
|
|
17
14
|
export declare function fitZoom(pages: readonly PageSize[], pane: Pane): number;
|
|
18
|
-
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
/**
|
|
16
|
+
* The furthest a gesture zooms out: every page fits the pane whole with {@link FLOOR_FILL} filled
|
|
17
|
+
* on its limiting axis, and never past the default.
|
|
18
|
+
*/
|
|
21
19
|
export declare function floorZoom(pages: readonly PageSize[], pane: Pane): number;
|
|
22
20
|
export declare function clampZoom(zoom: number, floor: number): number;
|
|
23
|
-
/** The next level in `dir` from `zoom`, skipping levels under `floor`. From between two
|
|
24
|
-
* levels the first press lands on the nearer one in that direction; past the last level
|
|
25
|
-
* above the floor, the floor. Half a percent of slack, so a zoom shown as a level is at
|
|
26
|
-
* it. */
|
|
27
|
-
export declare function stepZoom(zoom: number, dir: 1 | -1, floor: number): number;
|
|
28
21
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
* The next level in `dir` from `zoom`, skipping levels under `floor`; past the last, the floor.
|
|
23
|
+
* Half a percent of slack, so a zoom shown as a level is at it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function stepZoom(zoom: number, dir: 1 | -1, floor: number, fit: number): number;
|
|
26
|
+
/**
|
|
27
|
+
* A continuous gesture's run: the zoom its events asked for (`raw`), what it shows, and while it
|
|
28
|
+
* shows a hold, how far past it the events have carried (`slack`, a log of the ratio).
|
|
33
29
|
*/
|
|
34
30
|
export interface Glide {
|
|
35
31
|
raw: number;
|
|
36
32
|
shown: number;
|
|
33
|
+
slack: number;
|
|
37
34
|
}
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
|
|
35
|
+
/**
|
|
36
|
+
* One event of a continuous gesture, carrying the run to `raw`. It stops at the first of `fit` or
|
|
37
|
+
* 100% it crosses, and leaves a hold once carried {@link DETENT} past. 100% gives way to a `fit`
|
|
38
|
+
* within the detent of it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function glide(run: Glide, raw: number, floor: number, fit: number): Glide;
|