@embedpdf/plugin-stage 3.0.0-next.5 → 3.0.0-next.7
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/dist/index.cjs +566 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +185 -44
- package/dist/index.d.ts +185 -44
- package/dist/index.js +562 -85
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CapabilityToken, PageLayout, PageObjectNumber, PdfDestination } from "@embedpdf/core";
|
|
2
2
|
import { PageRotation, PageTransform, Rect } from "@embedpdf/core-geometry";
|
|
3
3
|
import { Align, AlignValue, Alignment, Alignment as Alignment$1, AlignmentValue, Anchor, Camera, Direction, Direction as Direction$1, PageBox, PageFrame, PageFrame as PageFrame$1, Point, ScrollMetrics, ScrollMetrics as ScrollMetrics$1, Size, SizingMode, SizingMode as SizingMode$1, SpreadMode, SpreadMode as SpreadMode$1, ZoomMode, ZoomModeValue, ZoomModeValue as ZoomModeValue$1, ZoomSpec, ZoomSpec as ZoomSpec$1 } from "@embedpdf/core-stage";
|
|
4
|
+
import { InteractionCapability, InteractionHandler } from "@embedpdf/plugin-interaction";
|
|
4
5
|
//#region src/types.d.ts
|
|
5
6
|
type LayoutKind = 'vertical' | 'horizontal' | 'grid';
|
|
6
7
|
/** Navigation (goToPage) either tweens the camera or jumps instantly. */
|
|
@@ -30,6 +31,53 @@ type GridColumns = 'square' | 'auto' | number;
|
|
|
30
31
|
type Gap = number | {
|
|
31
32
|
px: number;
|
|
32
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* The one environmental fact a headless stage has: the box it was told about
|
|
36
|
+
* (`setViewport`). Responsive rules can query nothing else — no user agent, no
|
|
37
|
+
* pointer type (modality is per-event, on `PointerSample`), no window. Space,
|
|
38
|
+
* not device: a narrow pane on a desktop is compact too, and each stage
|
|
39
|
+
* instance resolves against ITS OWN box.
|
|
40
|
+
*/
|
|
41
|
+
interface StageBox {
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
/** Of the CONTAINER, not the device. A square box is 'portrait' (the CSS rule). */
|
|
45
|
+
orientation: 'portrait' | 'landscape';
|
|
46
|
+
}
|
|
47
|
+
/** Declarative box query — all bounds inclusive, all fields optional (AND-ed). */
|
|
48
|
+
interface BoxQuery {
|
|
49
|
+
minWidth?: number;
|
|
50
|
+
maxWidth?: number;
|
|
51
|
+
minHeight?: number;
|
|
52
|
+
maxHeight?: number;
|
|
53
|
+
orientation?: 'portrait' | 'landscape';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* One `@container` block for the settings bag: when the box matches, assert
|
|
57
|
+
* this settings patch. Rules evaluate in source order and ALL matching rules
|
|
58
|
+
* apply, later winning per key (each key replaced whole — no deep merges).
|
|
59
|
+
* Effective settings = base (config + runtime setters) ⊕ matching patches.
|
|
60
|
+
*
|
|
61
|
+
* Semantics, stated honestly:
|
|
62
|
+
* • Runtime setters write the BASE; a matching rule wins over it. Apps
|
|
63
|
+
* needing situational absolute control edit the rules (`setResponsive`).
|
|
64
|
+
* • Rules assert at TRANSITIONS (box/base/rules changes), not continuously —
|
|
65
|
+
* between crossings, interaction owns the state. A rule containing `zoom`
|
|
66
|
+
* re-fits when the box crosses it (the rotate-an-iPad behavior) and then
|
|
67
|
+
* leaves the pinch alone.
|
|
68
|
+
*
|
|
69
|
+
* A rule with a `name` is a queryable fact (`matches(name)`), reactive in
|
|
70
|
+
* every framework; a named rule with NO settings is a pure shared breakpoint
|
|
71
|
+
* the app chrome can key its own presentation off — one definition serving
|
|
72
|
+
* both the layout math and the UI.
|
|
73
|
+
*/
|
|
74
|
+
interface ResponsiveRule {
|
|
75
|
+
name?: string;
|
|
76
|
+
/** Declarative box query, or a predicate for anything the box can answer. */
|
|
77
|
+
when: BoxQuery | ((box: StageBox) => boolean);
|
|
78
|
+
/** The settings this situation asserts. Omit for a pure named query. */
|
|
79
|
+
settings?: Partial<StageSettings>;
|
|
80
|
+
}
|
|
33
81
|
/**
|
|
34
82
|
* One axis of the ARRIVAL policy — stage-core's AlignValue ('start' |
|
|
35
83
|
* 'center' | 'end' | viewport fraction 0–1) plus one navigation-only word:
|
|
@@ -186,6 +234,22 @@ interface VisiblePage extends PageBox {
|
|
|
186
234
|
}
|
|
187
235
|
interface StageState extends StageSettings {
|
|
188
236
|
camera: Camera;
|
|
237
|
+
/**
|
|
238
|
+
* Names of the responsive rules currently matching the box, in source order.
|
|
239
|
+
* State (not derived) so `matches()` is reactive through the ordinary
|
|
240
|
+
* selector machinery in every framework.
|
|
241
|
+
*/
|
|
242
|
+
activeRules: readonly string[];
|
|
243
|
+
/**
|
|
244
|
+
* False while the ZOOM is in motion, true once it has rested (~150ms of
|
|
245
|
+
* frames without a zoom write). Device-snapping of page origins is gated
|
|
246
|
+
* on this: a continuous zoom and a snapped origin cannot coexist without
|
|
247
|
+
* the anchor point jittering ±0.5 device px per step (the rounding lands
|
|
248
|
+
* differently every frame), so pages place fractionally while zooming and
|
|
249
|
+
* snap once at rest — when crispness actually matters. Pans always snap
|
|
250
|
+
* (a pure translation snap has no anchor error). Transient like `camera`.
|
|
251
|
+
*/
|
|
252
|
+
cameraResting: boolean;
|
|
189
253
|
vp: Size;
|
|
190
254
|
/**
|
|
191
255
|
* Device pixels per view pixel (web: `window.devicePixelRatio`). Reported by
|
|
@@ -204,6 +268,9 @@ interface StageState extends StageSettings {
|
|
|
204
268
|
type StageAction = {
|
|
205
269
|
type: 'CAMERA';
|
|
206
270
|
camera: Camera;
|
|
271
|
+
} | {
|
|
272
|
+
type: 'CAMERA_REST';
|
|
273
|
+
resting: boolean;
|
|
207
274
|
} | {
|
|
208
275
|
type: 'VP';
|
|
209
276
|
vp: Size;
|
|
@@ -216,6 +283,9 @@ type StageAction = {
|
|
|
216
283
|
} | {
|
|
217
284
|
type: 'PATCH';
|
|
218
285
|
patch: Partial<StageSettings>;
|
|
286
|
+
} | {
|
|
287
|
+
type: 'RESPONSIVE';
|
|
288
|
+
active: readonly string[];
|
|
219
289
|
};
|
|
220
290
|
/**
|
|
221
291
|
* A page-relative view memento: "what I'm looking at and how zoomed". The durable
|
|
@@ -424,6 +494,45 @@ interface StageCapability {
|
|
|
424
494
|
/** `Element.scrollBy`: relative offsets — sugar over `scrollTo`. */
|
|
425
495
|
scrollBy(opts: StageScrollToOptions): void;
|
|
426
496
|
zoomAround(screenPt: Point, factor: number): void;
|
|
497
|
+
/**
|
|
498
|
+
* Bracket a continuous direct-manipulation gesture (touch pan / pinch).
|
|
499
|
+
* While a gesture is open the camera writes stay cheap and visually calm:
|
|
500
|
+
* `zoomAround` defers its zoom-intent PATCH (one per gesture instead of one
|
|
501
|
+
* per event), and the camera-rest detector holds un-rested — device
|
|
502
|
+
* snapping and settle-gated rendering wait for the END of the gesture, not
|
|
503
|
+
* for a 150 ms hesitation inside it. Re-entrant (nesting counts). Opening a
|
|
504
|
+
* gesture cancels any running tween or fling — that is how a finger
|
|
505
|
+
* "catches" a moving page.
|
|
506
|
+
*
|
|
507
|
+
* `elastic` opts the gesture into RUBBER-BAND overscroll: pans past the
|
|
508
|
+
* clamp stretch on the iOS resistance curve instead of stopping dead, and
|
|
509
|
+
* ending the gesture (or a fling reaching an edge) springs the camera home
|
|
510
|
+
* on a critically-damped curve. The clamp itself stays the untouched law of
|
|
511
|
+
* REST — elasticity is a transient the gesture is allowed to hold, never a
|
|
512
|
+
* state the camera can settle in. Touch contacts pass it; mouse drags and
|
|
513
|
+
* wheel pans stay rigid (the desktop convention). Default false.
|
|
514
|
+
*/
|
|
515
|
+
beginGesture(options?: {
|
|
516
|
+
elastic?: boolean;
|
|
517
|
+
}): void;
|
|
518
|
+
endGesture(): void;
|
|
519
|
+
/**
|
|
520
|
+
* Momentum scroll: keep panning from a release velocity (screen px/s, the
|
|
521
|
+
* same sign convention as `panBy` deltas), decelerating on UIScrollView's
|
|
522
|
+
* curve. Every other camera verb — including `beginGesture` from the next
|
|
523
|
+
* touch — cancels it. No-op without host frames (SSR/tests without a
|
|
524
|
+
* scheduler jump nowhere: momentum is presentation, not state).
|
|
525
|
+
*/
|
|
526
|
+
fling(velocityX: number, velocityY: number): void;
|
|
527
|
+
/** True while the camera is animating (navigation tween or fling) — the
|
|
528
|
+
* input layer reads it to tell a "catch" from a tap. */
|
|
529
|
+
cameraInMotion(): boolean;
|
|
530
|
+
/**
|
|
531
|
+
* The touch double-tap toggle: zoomed out → animate to ~2.5× the automatic
|
|
532
|
+
* fit around `screenPt`; already zoomed past that → animate back to the fit
|
|
533
|
+
* level, same focal point. Lands as a fixed zoom level either way.
|
|
534
|
+
*/
|
|
535
|
+
doubleTapZoom(screenPt: Point): void;
|
|
427
536
|
zoomIn(): void;
|
|
428
537
|
zoomOut(): void;
|
|
429
538
|
zoomTo(spec: ZoomSpec$1): void;
|
|
@@ -459,9 +568,26 @@ interface StageCapability {
|
|
|
459
568
|
next(opts?: GoToOptions): void;
|
|
460
569
|
/** Step backward by the navigation unit. */
|
|
461
570
|
prev(opts?: GoToOptions): void;
|
|
571
|
+
/**
|
|
572
|
+
* This lens's identity — the stage plugin id it was registered under
|
|
573
|
+
* ('stage' for the main lens; a custom id per additional lens). The surface
|
|
574
|
+
* binding stamps it on every pointer sample (`PointerSample.source`) and
|
|
575
|
+
* lens-scoped interaction handlers register under it, so two stages on one
|
|
576
|
+
* document can never capture each other's input.
|
|
577
|
+
*/
|
|
578
|
+
lensId(): string;
|
|
462
579
|
/** Set any subset of settings at once — ONE anchor-preserving update. The way to
|
|
463
|
-
* apply a customer preset: `update(myPreset)`.
|
|
580
|
+
* apply a customer preset: `update(myPreset)`. Writes the responsive BASE:
|
|
581
|
+
* a matching rule's key wins until its rule stops matching. */
|
|
464
582
|
update(patch: Partial<StageSettings>): void;
|
|
583
|
+
/** Replace the responsive rules (see {@link ResponsiveRule}); re-resolves
|
|
584
|
+
* immediately with the usual anchor-preserving reactions. */
|
|
585
|
+
setResponsive(rules: readonly ResponsiveRule[]): void;
|
|
586
|
+
/** Is the named responsive rule currently matching? Reactive: recomputed
|
|
587
|
+
* whenever the box crosses a rule boundary. */
|
|
588
|
+
matches(name: string): boolean;
|
|
589
|
+
/** Names of all currently-matching rules, in source order. */
|
|
590
|
+
activeRules(): readonly string[];
|
|
465
591
|
setFlow(flow: FlowMode): void;
|
|
466
592
|
setLayout(layout: LayoutKind): void;
|
|
467
593
|
setSpread(spread: SpreadMode$1): void;
|
|
@@ -495,6 +621,12 @@ interface StageCapability {
|
|
|
495
621
|
interface StageConfig extends Partial<StageSettings> {
|
|
496
622
|
/** Override the host timing seam (tests/SSR). Defaults to browser rAF. */
|
|
497
623
|
scheduler?: Scheduler;
|
|
624
|
+
/**
|
|
625
|
+
* Container queries for the settings bag (see {@link ResponsiveRule}).
|
|
626
|
+
* Defaults to `DEFAULT_RESPONSIVE` (compact containers get the thin phone
|
|
627
|
+
* gutter); pass `[]` to opt out entirely.
|
|
628
|
+
*/
|
|
629
|
+
responsive?: readonly ResponsiveRule[];
|
|
498
630
|
}
|
|
499
631
|
declare const StageToken: import("@embedpdf/core").CapabilityToken<StageCapability>;
|
|
500
632
|
//#endregion
|
|
@@ -518,20 +650,6 @@ declare const StageToken: import("@embedpdf/core").CapabilityToken<StageCapabili
|
|
|
518
650
|
interface StagePluginOptions extends StageConfig {
|
|
519
651
|
id?: string;
|
|
520
652
|
token?: CapabilityToken<StageCapability>;
|
|
521
|
-
/**
|
|
522
|
-
* Opt this lens into the interaction hub: register a tool-gated `scroll`
|
|
523
|
-
* handler (so dragging pans only in `pan` mode). Pair with `<Stage interaction>`
|
|
524
|
-
* on the React side, which forwards pointer events to the hub. Default false —
|
|
525
|
-
* secondary lenses (thumbnails) stay click-to-navigate.
|
|
526
|
-
*/
|
|
527
|
-
interaction?: boolean;
|
|
528
|
-
/**
|
|
529
|
-
* When {@link interaction} is on, let drags over page GAPS pan regardless of the
|
|
530
|
-
* active tool (and show a grab cursor there) — the gutter always pans, matching
|
|
531
|
-
* v2 and the intuition that there's nothing to draw/select outside a page.
|
|
532
|
-
* On-page behaviour is untouched. Default true; ignored without `interaction`.
|
|
533
|
-
*/
|
|
534
|
-
panFallback?: boolean;
|
|
535
653
|
}
|
|
536
654
|
/**
|
|
537
655
|
* Wires the parts into a kernel plugin. This file is the "manifest": it says what
|
|
@@ -554,37 +672,28 @@ interface DestinationReveal {
|
|
|
554
672
|
*/
|
|
555
673
|
declare function destinationToReveal(dest: PdfDestination, layout: PageLayout, bbox?: Rect): DestinationReveal;
|
|
556
674
|
//#endregion
|
|
557
|
-
//#region src/
|
|
675
|
+
//#region src/scroll-handler.d.ts
|
|
676
|
+
interface ScrollHandlerOptions {
|
|
677
|
+
/**
|
|
678
|
+
* Let a drag over a page GAP pan regardless of the active tool — and show a grab
|
|
679
|
+
* cursor there — so the gutter always pans (v2 parity, and the natural feel:
|
|
680
|
+
* outside a page there's nothing to draw/select on). On-page behaviour is
|
|
681
|
+
* untouched; the tool's own gesture still owns page space. Default true.
|
|
682
|
+
*/
|
|
683
|
+
panFallback?: boolean;
|
|
684
|
+
}
|
|
558
685
|
/**
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
* • trackpad PINCH — browsers (Chrome/Edge/Firefox) synthesize ctrl+wheel
|
|
566
|
-
* px-deltas scaled to the de-facto convention that `exp(-Δ/100)` tracks
|
|
567
|
-
* the physical finger scale 1:1. Anything weaker feels dead (the content
|
|
568
|
-
* must track the fingers — the Figma/maps feel).
|
|
569
|
-
* • mouse NOTCHES — coarse steps: ±100/±120 px in Chrome, lines/pages mode
|
|
570
|
-
* in Firefox. The pinch mapping would triple per click; a notch instead
|
|
571
|
-
* steps by 1.2, the same ratio as the zoom buttons.
|
|
572
|
-
* • cmd + continuous SCROLL — real scroll px, big and streaming (with
|
|
573
|
-
* momentum): a gentle scrub coefficient. A cmd+mouse notch (±120 px)
|
|
574
|
-
* lands within 0.3% of the 1.2 button step, so it needs no special case.
|
|
686
|
+
* Pan-the-camera as an interaction handler. Live under the built-in `pan` tool
|
|
687
|
+
* (the `scroll` tag) and — with `panFallback` — under every tool, where it then
|
|
688
|
+
* captures ONLY page-gap drags (on-page downs belong to the active tool, which
|
|
689
|
+
* claims them first at higher priority). It uses the sample's viewport delta, so
|
|
690
|
+
* it pans over the whole viewport (pages and gaps).
|
|
575
691
|
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
692
|
+
* Cursor feel: a closed hand (`grabbing`) while dragging; an open hand (`grab`)
|
|
693
|
+
* hovering a gap under a non-pan tool. The pan tool's own resting `grab` comes
|
|
694
|
+
* from its `Tool.cursor`, so hovering it needs no claim here.
|
|
578
695
|
*/
|
|
579
|
-
|
|
580
|
-
deltaY: number;
|
|
581
|
-
/** WheelEvent.deltaMode: 0 = pixels, 1 = lines, 2 = pages. */
|
|
582
|
-
deltaMode: number;
|
|
583
|
-
ctrlKey: boolean;
|
|
584
|
-
metaKey: boolean;
|
|
585
|
-
}
|
|
586
|
-
/** Zoom factor for a ctrl/cmd wheel event (the caller decides zoom vs pan). */
|
|
587
|
-
declare function wheelZoomFactor(e: WheelSample): number;
|
|
696
|
+
declare function createScrollHandler(stage: StageCapability, interaction: InteractionCapability, options?: ScrollHandlerOptions): InteractionHandler;
|
|
588
697
|
//#endregion
|
|
589
698
|
//#region src/settings.d.ts
|
|
590
699
|
/**
|
|
@@ -595,9 +704,41 @@ declare function wheelZoomFactor(e: WheelSample): number;
|
|
|
595
704
|
* `update()`, so that taxonomy stays a customer concern.
|
|
596
705
|
*/
|
|
597
706
|
declare const DEFAULT_SETTINGS: StageSettings;
|
|
707
|
+
/**
|
|
708
|
+
* Out-of-the-box responsive rules — the platform feel with zero configuration:
|
|
709
|
+
* a compact container gets the thin gutter phones use (space, not device — an
|
|
710
|
+
* embedded 500px pane on a desktop is compact too). Override the whole list
|
|
711
|
+
* with `stagePlugin({ responsive: [...] })`; `responsive: []` opts out. The
|
|
712
|
+
* 'compact' name is queryable app-side via `stage.matches('compact')`.
|
|
713
|
+
*/
|
|
714
|
+
declare const DEFAULT_RESPONSIVE: readonly ResponsiveRule[];
|
|
598
715
|
/** Field-by-field settings equality, derived from the registry — a new setting is
|
|
599
716
|
* covered automatically. (The React `useStageSettings` selector equality.) */
|
|
600
717
|
declare const settingsEqual: (a: StageSettings, b: StageSettings) => boolean;
|
|
601
718
|
//#endregion
|
|
602
|
-
|
|
719
|
+
//#region src/responsive.d.ts
|
|
720
|
+
/**
|
|
721
|
+
* Container queries for the settings bag — the pure half. The capability owns
|
|
722
|
+
* the driver (when to re-resolve, how to react); the LAWS live here: what a
|
|
723
|
+
* box is, when a query matches, and what the effective settings are. No state,
|
|
724
|
+
* no dispatch — property-testable in isolation (the `wheel.ts` convention).
|
|
725
|
+
*
|
|
726
|
+
* The query vocabulary is deliberately just the box: a headless, DOM-free
|
|
727
|
+
* stage knows exactly one environmental fact — the viewport it was told.
|
|
728
|
+
* Anything else (pointer coarseness, platform, app state) is either per-event
|
|
729
|
+
* (`PointerSample.pointerType`) or the app's own business via `update()`.
|
|
730
|
+
*/
|
|
731
|
+
/** The reported viewport as a queryable box. A square box is 'portrait' (CSS). */
|
|
732
|
+
declare const boxOf: (vp: Size) => StageBox;
|
|
733
|
+
/** All bounds inclusive, all fields optional, conditions AND-ed (CSS ranges). */
|
|
734
|
+
declare const matchesQuery: (q: BoxQuery, box: StageBox) => boolean;
|
|
735
|
+
interface ResolvedResponsive {
|
|
736
|
+
/** base ⊕ every matching rule's patch, in source order (later wins per key). */
|
|
737
|
+
effective: StageSettings;
|
|
738
|
+
/** Names of the matching rules, in source order. */
|
|
739
|
+
active: string[];
|
|
740
|
+
}
|
|
741
|
+
declare const resolveResponsive: (base: StageSettings, rules: readonly ResponsiveRule[], box: StageBox) => ResolvedResponsive;
|
|
742
|
+
//#endregion
|
|
743
|
+
export { type Align, type Alignment, type BoxQuery, DEFAULT_RESPONSIVE, DEFAULT_SETTINGS, type DestinationReveal, type Direction, type FlowMode, type Gap, type GoToOptions, type GridColumns, type LayoutKind, type PageFrame, type ResponsiveRule, type RevealAnchor, type RevealAnchorValue, type RevealOptions, type RevealZoom, type Scheduler, type ScrollBehaviorKind, type ScrollHandlerOptions, type ScrollMetrics, type SizingMode, type SpreadMode, type StageAction, type StageBox, type StageCapability, type StageConfig, type StagePluginOptions, type StageScrollToOptions, type StageSettings, type StageState, StageToken, type StageViewState, type Viewpoint, type VisiblePage, ZoomMode, type ZoomModeValue, type ZoomSpec, boxOf, createScrollHandler, destinationToReveal, matchesQuery, resolveResponsive, settingsEqual, stagePlugin };
|
|
603
744
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CapabilityToken, PageLayout, PageObjectNumber, PdfDestination } from "@embedpdf/core";
|
|
2
2
|
import { Align, AlignValue, Alignment, Alignment as Alignment$1, AlignmentValue, Anchor, Camera, Direction, Direction as Direction$1, PageBox, PageFrame, PageFrame as PageFrame$1, Point, ScrollMetrics, ScrollMetrics as ScrollMetrics$1, Size, SizingMode, SizingMode as SizingMode$1, SpreadMode, SpreadMode as SpreadMode$1, ZoomMode, ZoomModeValue, ZoomModeValue as ZoomModeValue$1, ZoomSpec, ZoomSpec as ZoomSpec$1 } from "@embedpdf/core-stage";
|
|
3
3
|
import { PageRotation, PageTransform, Rect } from "@embedpdf/core-geometry";
|
|
4
|
+
import { InteractionCapability, InteractionHandler } from "@embedpdf/plugin-interaction";
|
|
4
5
|
//#region src/types.d.ts
|
|
5
6
|
type LayoutKind = 'vertical' | 'horizontal' | 'grid';
|
|
6
7
|
/** Navigation (goToPage) either tweens the camera or jumps instantly. */
|
|
@@ -30,6 +31,53 @@ type GridColumns = 'square' | 'auto' | number;
|
|
|
30
31
|
type Gap = number | {
|
|
31
32
|
px: number;
|
|
32
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* The one environmental fact a headless stage has: the box it was told about
|
|
36
|
+
* (`setViewport`). Responsive rules can query nothing else — no user agent, no
|
|
37
|
+
* pointer type (modality is per-event, on `PointerSample`), no window. Space,
|
|
38
|
+
* not device: a narrow pane on a desktop is compact too, and each stage
|
|
39
|
+
* instance resolves against ITS OWN box.
|
|
40
|
+
*/
|
|
41
|
+
interface StageBox {
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
/** Of the CONTAINER, not the device. A square box is 'portrait' (the CSS rule). */
|
|
45
|
+
orientation: 'portrait' | 'landscape';
|
|
46
|
+
}
|
|
47
|
+
/** Declarative box query — all bounds inclusive, all fields optional (AND-ed). */
|
|
48
|
+
interface BoxQuery {
|
|
49
|
+
minWidth?: number;
|
|
50
|
+
maxWidth?: number;
|
|
51
|
+
minHeight?: number;
|
|
52
|
+
maxHeight?: number;
|
|
53
|
+
orientation?: 'portrait' | 'landscape';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* One `@container` block for the settings bag: when the box matches, assert
|
|
57
|
+
* this settings patch. Rules evaluate in source order and ALL matching rules
|
|
58
|
+
* apply, later winning per key (each key replaced whole — no deep merges).
|
|
59
|
+
* Effective settings = base (config + runtime setters) ⊕ matching patches.
|
|
60
|
+
*
|
|
61
|
+
* Semantics, stated honestly:
|
|
62
|
+
* • Runtime setters write the BASE; a matching rule wins over it. Apps
|
|
63
|
+
* needing situational absolute control edit the rules (`setResponsive`).
|
|
64
|
+
* • Rules assert at TRANSITIONS (box/base/rules changes), not continuously —
|
|
65
|
+
* between crossings, interaction owns the state. A rule containing `zoom`
|
|
66
|
+
* re-fits when the box crosses it (the rotate-an-iPad behavior) and then
|
|
67
|
+
* leaves the pinch alone.
|
|
68
|
+
*
|
|
69
|
+
* A rule with a `name` is a queryable fact (`matches(name)`), reactive in
|
|
70
|
+
* every framework; a named rule with NO settings is a pure shared breakpoint
|
|
71
|
+
* the app chrome can key its own presentation off — one definition serving
|
|
72
|
+
* both the layout math and the UI.
|
|
73
|
+
*/
|
|
74
|
+
interface ResponsiveRule {
|
|
75
|
+
name?: string;
|
|
76
|
+
/** Declarative box query, or a predicate for anything the box can answer. */
|
|
77
|
+
when: BoxQuery | ((box: StageBox) => boolean);
|
|
78
|
+
/** The settings this situation asserts. Omit for a pure named query. */
|
|
79
|
+
settings?: Partial<StageSettings>;
|
|
80
|
+
}
|
|
33
81
|
/**
|
|
34
82
|
* One axis of the ARRIVAL policy — stage-core's AlignValue ('start' |
|
|
35
83
|
* 'center' | 'end' | viewport fraction 0–1) plus one navigation-only word:
|
|
@@ -186,6 +234,22 @@ interface VisiblePage extends PageBox {
|
|
|
186
234
|
}
|
|
187
235
|
interface StageState extends StageSettings {
|
|
188
236
|
camera: Camera;
|
|
237
|
+
/**
|
|
238
|
+
* Names of the responsive rules currently matching the box, in source order.
|
|
239
|
+
* State (not derived) so `matches()` is reactive through the ordinary
|
|
240
|
+
* selector machinery in every framework.
|
|
241
|
+
*/
|
|
242
|
+
activeRules: readonly string[];
|
|
243
|
+
/**
|
|
244
|
+
* False while the ZOOM is in motion, true once it has rested (~150ms of
|
|
245
|
+
* frames without a zoom write). Device-snapping of page origins is gated
|
|
246
|
+
* on this: a continuous zoom and a snapped origin cannot coexist without
|
|
247
|
+
* the anchor point jittering ±0.5 device px per step (the rounding lands
|
|
248
|
+
* differently every frame), so pages place fractionally while zooming and
|
|
249
|
+
* snap once at rest — when crispness actually matters. Pans always snap
|
|
250
|
+
* (a pure translation snap has no anchor error). Transient like `camera`.
|
|
251
|
+
*/
|
|
252
|
+
cameraResting: boolean;
|
|
189
253
|
vp: Size;
|
|
190
254
|
/**
|
|
191
255
|
* Device pixels per view pixel (web: `window.devicePixelRatio`). Reported by
|
|
@@ -204,6 +268,9 @@ interface StageState extends StageSettings {
|
|
|
204
268
|
type StageAction = {
|
|
205
269
|
type: 'CAMERA';
|
|
206
270
|
camera: Camera;
|
|
271
|
+
} | {
|
|
272
|
+
type: 'CAMERA_REST';
|
|
273
|
+
resting: boolean;
|
|
207
274
|
} | {
|
|
208
275
|
type: 'VP';
|
|
209
276
|
vp: Size;
|
|
@@ -216,6 +283,9 @@ type StageAction = {
|
|
|
216
283
|
} | {
|
|
217
284
|
type: 'PATCH';
|
|
218
285
|
patch: Partial<StageSettings>;
|
|
286
|
+
} | {
|
|
287
|
+
type: 'RESPONSIVE';
|
|
288
|
+
active: readonly string[];
|
|
219
289
|
};
|
|
220
290
|
/**
|
|
221
291
|
* A page-relative view memento: "what I'm looking at and how zoomed". The durable
|
|
@@ -424,6 +494,45 @@ interface StageCapability {
|
|
|
424
494
|
/** `Element.scrollBy`: relative offsets — sugar over `scrollTo`. */
|
|
425
495
|
scrollBy(opts: StageScrollToOptions): void;
|
|
426
496
|
zoomAround(screenPt: Point, factor: number): void;
|
|
497
|
+
/**
|
|
498
|
+
* Bracket a continuous direct-manipulation gesture (touch pan / pinch).
|
|
499
|
+
* While a gesture is open the camera writes stay cheap and visually calm:
|
|
500
|
+
* `zoomAround` defers its zoom-intent PATCH (one per gesture instead of one
|
|
501
|
+
* per event), and the camera-rest detector holds un-rested — device
|
|
502
|
+
* snapping and settle-gated rendering wait for the END of the gesture, not
|
|
503
|
+
* for a 150 ms hesitation inside it. Re-entrant (nesting counts). Opening a
|
|
504
|
+
* gesture cancels any running tween or fling — that is how a finger
|
|
505
|
+
* "catches" a moving page.
|
|
506
|
+
*
|
|
507
|
+
* `elastic` opts the gesture into RUBBER-BAND overscroll: pans past the
|
|
508
|
+
* clamp stretch on the iOS resistance curve instead of stopping dead, and
|
|
509
|
+
* ending the gesture (or a fling reaching an edge) springs the camera home
|
|
510
|
+
* on a critically-damped curve. The clamp itself stays the untouched law of
|
|
511
|
+
* REST — elasticity is a transient the gesture is allowed to hold, never a
|
|
512
|
+
* state the camera can settle in. Touch contacts pass it; mouse drags and
|
|
513
|
+
* wheel pans stay rigid (the desktop convention). Default false.
|
|
514
|
+
*/
|
|
515
|
+
beginGesture(options?: {
|
|
516
|
+
elastic?: boolean;
|
|
517
|
+
}): void;
|
|
518
|
+
endGesture(): void;
|
|
519
|
+
/**
|
|
520
|
+
* Momentum scroll: keep panning from a release velocity (screen px/s, the
|
|
521
|
+
* same sign convention as `panBy` deltas), decelerating on UIScrollView's
|
|
522
|
+
* curve. Every other camera verb — including `beginGesture` from the next
|
|
523
|
+
* touch — cancels it. No-op without host frames (SSR/tests without a
|
|
524
|
+
* scheduler jump nowhere: momentum is presentation, not state).
|
|
525
|
+
*/
|
|
526
|
+
fling(velocityX: number, velocityY: number): void;
|
|
527
|
+
/** True while the camera is animating (navigation tween or fling) — the
|
|
528
|
+
* input layer reads it to tell a "catch" from a tap. */
|
|
529
|
+
cameraInMotion(): boolean;
|
|
530
|
+
/**
|
|
531
|
+
* The touch double-tap toggle: zoomed out → animate to ~2.5× the automatic
|
|
532
|
+
* fit around `screenPt`; already zoomed past that → animate back to the fit
|
|
533
|
+
* level, same focal point. Lands as a fixed zoom level either way.
|
|
534
|
+
*/
|
|
535
|
+
doubleTapZoom(screenPt: Point): void;
|
|
427
536
|
zoomIn(): void;
|
|
428
537
|
zoomOut(): void;
|
|
429
538
|
zoomTo(spec: ZoomSpec$1): void;
|
|
@@ -459,9 +568,26 @@ interface StageCapability {
|
|
|
459
568
|
next(opts?: GoToOptions): void;
|
|
460
569
|
/** Step backward by the navigation unit. */
|
|
461
570
|
prev(opts?: GoToOptions): void;
|
|
571
|
+
/**
|
|
572
|
+
* This lens's identity — the stage plugin id it was registered under
|
|
573
|
+
* ('stage' for the main lens; a custom id per additional lens). The surface
|
|
574
|
+
* binding stamps it on every pointer sample (`PointerSample.source`) and
|
|
575
|
+
* lens-scoped interaction handlers register under it, so two stages on one
|
|
576
|
+
* document can never capture each other's input.
|
|
577
|
+
*/
|
|
578
|
+
lensId(): string;
|
|
462
579
|
/** Set any subset of settings at once — ONE anchor-preserving update. The way to
|
|
463
|
-
* apply a customer preset: `update(myPreset)`.
|
|
580
|
+
* apply a customer preset: `update(myPreset)`. Writes the responsive BASE:
|
|
581
|
+
* a matching rule's key wins until its rule stops matching. */
|
|
464
582
|
update(patch: Partial<StageSettings>): void;
|
|
583
|
+
/** Replace the responsive rules (see {@link ResponsiveRule}); re-resolves
|
|
584
|
+
* immediately with the usual anchor-preserving reactions. */
|
|
585
|
+
setResponsive(rules: readonly ResponsiveRule[]): void;
|
|
586
|
+
/** Is the named responsive rule currently matching? Reactive: recomputed
|
|
587
|
+
* whenever the box crosses a rule boundary. */
|
|
588
|
+
matches(name: string): boolean;
|
|
589
|
+
/** Names of all currently-matching rules, in source order. */
|
|
590
|
+
activeRules(): readonly string[];
|
|
465
591
|
setFlow(flow: FlowMode): void;
|
|
466
592
|
setLayout(layout: LayoutKind): void;
|
|
467
593
|
setSpread(spread: SpreadMode$1): void;
|
|
@@ -495,6 +621,12 @@ interface StageCapability {
|
|
|
495
621
|
interface StageConfig extends Partial<StageSettings> {
|
|
496
622
|
/** Override the host timing seam (tests/SSR). Defaults to browser rAF. */
|
|
497
623
|
scheduler?: Scheduler;
|
|
624
|
+
/**
|
|
625
|
+
* Container queries for the settings bag (see {@link ResponsiveRule}).
|
|
626
|
+
* Defaults to `DEFAULT_RESPONSIVE` (compact containers get the thin phone
|
|
627
|
+
* gutter); pass `[]` to opt out entirely.
|
|
628
|
+
*/
|
|
629
|
+
responsive?: readonly ResponsiveRule[];
|
|
498
630
|
}
|
|
499
631
|
declare const StageToken: import("@embedpdf/core").CapabilityToken<StageCapability>;
|
|
500
632
|
//#endregion
|
|
@@ -518,20 +650,6 @@ declare const StageToken: import("@embedpdf/core").CapabilityToken<StageCapabili
|
|
|
518
650
|
interface StagePluginOptions extends StageConfig {
|
|
519
651
|
id?: string;
|
|
520
652
|
token?: CapabilityToken<StageCapability>;
|
|
521
|
-
/**
|
|
522
|
-
* Opt this lens into the interaction hub: register a tool-gated `scroll`
|
|
523
|
-
* handler (so dragging pans only in `pan` mode). Pair with `<Stage interaction>`
|
|
524
|
-
* on the React side, which forwards pointer events to the hub. Default false —
|
|
525
|
-
* secondary lenses (thumbnails) stay click-to-navigate.
|
|
526
|
-
*/
|
|
527
|
-
interaction?: boolean;
|
|
528
|
-
/**
|
|
529
|
-
* When {@link interaction} is on, let drags over page GAPS pan regardless of the
|
|
530
|
-
* active tool (and show a grab cursor there) — the gutter always pans, matching
|
|
531
|
-
* v2 and the intuition that there's nothing to draw/select outside a page.
|
|
532
|
-
* On-page behaviour is untouched. Default true; ignored without `interaction`.
|
|
533
|
-
*/
|
|
534
|
-
panFallback?: boolean;
|
|
535
653
|
}
|
|
536
654
|
/**
|
|
537
655
|
* Wires the parts into a kernel plugin. This file is the "manifest": it says what
|
|
@@ -554,37 +672,28 @@ interface DestinationReveal {
|
|
|
554
672
|
*/
|
|
555
673
|
declare function destinationToReveal(dest: PdfDestination, layout: PageLayout, bbox?: Rect): DestinationReveal;
|
|
556
674
|
//#endregion
|
|
557
|
-
//#region src/
|
|
675
|
+
//#region src/scroll-handler.d.ts
|
|
676
|
+
interface ScrollHandlerOptions {
|
|
677
|
+
/**
|
|
678
|
+
* Let a drag over a page GAP pan regardless of the active tool — and show a grab
|
|
679
|
+
* cursor there — so the gutter always pans (v2 parity, and the natural feel:
|
|
680
|
+
* outside a page there's nothing to draw/select on). On-page behaviour is
|
|
681
|
+
* untouched; the tool's own gesture still owns page space. Default true.
|
|
682
|
+
*/
|
|
683
|
+
panFallback?: boolean;
|
|
684
|
+
}
|
|
558
685
|
/**
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
* • trackpad PINCH — browsers (Chrome/Edge/Firefox) synthesize ctrl+wheel
|
|
566
|
-
* px-deltas scaled to the de-facto convention that `exp(-Δ/100)` tracks
|
|
567
|
-
* the physical finger scale 1:1. Anything weaker feels dead (the content
|
|
568
|
-
* must track the fingers — the Figma/maps feel).
|
|
569
|
-
* • mouse NOTCHES — coarse steps: ±100/±120 px in Chrome, lines/pages mode
|
|
570
|
-
* in Firefox. The pinch mapping would triple per click; a notch instead
|
|
571
|
-
* steps by 1.2, the same ratio as the zoom buttons.
|
|
572
|
-
* • cmd + continuous SCROLL — real scroll px, big and streaming (with
|
|
573
|
-
* momentum): a gentle scrub coefficient. A cmd+mouse notch (±120 px)
|
|
574
|
-
* lands within 0.3% of the 1.2 button step, so it needs no special case.
|
|
686
|
+
* Pan-the-camera as an interaction handler. Live under the built-in `pan` tool
|
|
687
|
+
* (the `scroll` tag) and — with `panFallback` — under every tool, where it then
|
|
688
|
+
* captures ONLY page-gap drags (on-page downs belong to the active tool, which
|
|
689
|
+
* claims them first at higher priority). It uses the sample's viewport delta, so
|
|
690
|
+
* it pans over the whole viewport (pages and gaps).
|
|
575
691
|
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
692
|
+
* Cursor feel: a closed hand (`grabbing`) while dragging; an open hand (`grab`)
|
|
693
|
+
* hovering a gap under a non-pan tool. The pan tool's own resting `grab` comes
|
|
694
|
+
* from its `Tool.cursor`, so hovering it needs no claim here.
|
|
578
695
|
*/
|
|
579
|
-
|
|
580
|
-
deltaY: number;
|
|
581
|
-
/** WheelEvent.deltaMode: 0 = pixels, 1 = lines, 2 = pages. */
|
|
582
|
-
deltaMode: number;
|
|
583
|
-
ctrlKey: boolean;
|
|
584
|
-
metaKey: boolean;
|
|
585
|
-
}
|
|
586
|
-
/** Zoom factor for a ctrl/cmd wheel event (the caller decides zoom vs pan). */
|
|
587
|
-
declare function wheelZoomFactor(e: WheelSample): number;
|
|
696
|
+
declare function createScrollHandler(stage: StageCapability, interaction: InteractionCapability, options?: ScrollHandlerOptions): InteractionHandler;
|
|
588
697
|
//#endregion
|
|
589
698
|
//#region src/settings.d.ts
|
|
590
699
|
/**
|
|
@@ -595,9 +704,41 @@ declare function wheelZoomFactor(e: WheelSample): number;
|
|
|
595
704
|
* `update()`, so that taxonomy stays a customer concern.
|
|
596
705
|
*/
|
|
597
706
|
declare const DEFAULT_SETTINGS: StageSettings;
|
|
707
|
+
/**
|
|
708
|
+
* Out-of-the-box responsive rules — the platform feel with zero configuration:
|
|
709
|
+
* a compact container gets the thin gutter phones use (space, not device — an
|
|
710
|
+
* embedded 500px pane on a desktop is compact too). Override the whole list
|
|
711
|
+
* with `stagePlugin({ responsive: [...] })`; `responsive: []` opts out. The
|
|
712
|
+
* 'compact' name is queryable app-side via `stage.matches('compact')`.
|
|
713
|
+
*/
|
|
714
|
+
declare const DEFAULT_RESPONSIVE: readonly ResponsiveRule[];
|
|
598
715
|
/** Field-by-field settings equality, derived from the registry — a new setting is
|
|
599
716
|
* covered automatically. (The React `useStageSettings` selector equality.) */
|
|
600
717
|
declare const settingsEqual: (a: StageSettings, b: StageSettings) => boolean;
|
|
601
718
|
//#endregion
|
|
602
|
-
|
|
719
|
+
//#region src/responsive.d.ts
|
|
720
|
+
/**
|
|
721
|
+
* Container queries for the settings bag — the pure half. The capability owns
|
|
722
|
+
* the driver (when to re-resolve, how to react); the LAWS live here: what a
|
|
723
|
+
* box is, when a query matches, and what the effective settings are. No state,
|
|
724
|
+
* no dispatch — property-testable in isolation (the `wheel.ts` convention).
|
|
725
|
+
*
|
|
726
|
+
* The query vocabulary is deliberately just the box: a headless, DOM-free
|
|
727
|
+
* stage knows exactly one environmental fact — the viewport it was told.
|
|
728
|
+
* Anything else (pointer coarseness, platform, app state) is either per-event
|
|
729
|
+
* (`PointerSample.pointerType`) or the app's own business via `update()`.
|
|
730
|
+
*/
|
|
731
|
+
/** The reported viewport as a queryable box. A square box is 'portrait' (CSS). */
|
|
732
|
+
declare const boxOf: (vp: Size) => StageBox;
|
|
733
|
+
/** All bounds inclusive, all fields optional, conditions AND-ed (CSS ranges). */
|
|
734
|
+
declare const matchesQuery: (q: BoxQuery, box: StageBox) => boolean;
|
|
735
|
+
interface ResolvedResponsive {
|
|
736
|
+
/** base ⊕ every matching rule's patch, in source order (later wins per key). */
|
|
737
|
+
effective: StageSettings;
|
|
738
|
+
/** Names of the matching rules, in source order. */
|
|
739
|
+
active: string[];
|
|
740
|
+
}
|
|
741
|
+
declare const resolveResponsive: (base: StageSettings, rules: readonly ResponsiveRule[], box: StageBox) => ResolvedResponsive;
|
|
742
|
+
//#endregion
|
|
743
|
+
export { type Align, type Alignment, type BoxQuery, DEFAULT_RESPONSIVE, DEFAULT_SETTINGS, type DestinationReveal, type Direction, type FlowMode, type Gap, type GoToOptions, type GridColumns, type LayoutKind, type PageFrame, type ResponsiveRule, type RevealAnchor, type RevealAnchorValue, type RevealOptions, type RevealZoom, type Scheduler, type ScrollBehaviorKind, type ScrollHandlerOptions, type ScrollMetrics, type SizingMode, type SpreadMode, type StageAction, type StageBox, type StageCapability, type StageConfig, type StagePluginOptions, type StageScrollToOptions, type StageSettings, type StageState, StageToken, type StageViewState, type Viewpoint, type VisiblePage, ZoomMode, type ZoomModeValue, type ZoomSpec, boxOf, createScrollHandler, destinationToReveal, matchesQuery, resolveResponsive, settingsEqual, stagePlugin };
|
|
603
744
|
//# sourceMappingURL=index.d.ts.map
|