@embedpdf/web 3.0.0-next.0 → 3.0.0-next.10

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.d.ts CHANGED
@@ -16,6 +16,163 @@ declare const pickFile: (options?: PickFileOptions) => Promise<File | null>;
16
16
  */
17
17
  declare function saveFile(bytes: Uint8Array | Blob, name: string, mimeType?: string): void;
18
18
  //#endregion
19
+ //#region src/clipboard.d.ts
20
+ /**
21
+ * Clipboard wiring for text selection — the environmental half of copy.
22
+ *
23
+ * The selection plugin is deliberately DOM-free: its public capability ends
24
+ * at `readText()` (data). Everything that touches `navigator.clipboard` or
25
+ * the native `copy` event lives HERE, structurally typed so this module
26
+ * stays a pure DOM utility with no EmbedPDF dependencies (the selection
27
+ * capability satisfies {@link ClipboardSelectionSource} as-is).
28
+ */
29
+ /** The slice of the selection capability clipboard wiring needs. */
30
+ interface ClipboardSelectionSource {
31
+ hasSelection(): boolean;
32
+ canCopy(): boolean;
33
+ readText(): Promise<string>;
34
+ onChange(cb: () => void): () => void;
35
+ onCommit(cb: () => void): () => void;
36
+ }
37
+ interface SelectionClipboardOptions {
38
+ /**
39
+ * Prefetch the selected text when the selection settles (default true).
40
+ * This is what makes the NATIVE copy path work: a `copy` event handler
41
+ * must call `clipboardData.setData` synchronously — awaiting a page-text
42
+ * read inside it is too late — and it also keeps the async path instant.
43
+ * The fetch is one versioned, cached, permission-gated read per page;
44
+ * pass false to make copying strictly on-demand.
45
+ */
46
+ prefetch?: boolean;
47
+ /** Event target to listen on. Defaults to `document`. */
48
+ target?: Document;
49
+ }
50
+ /**
51
+ * Wire clipboard copy for a selection. Two delivery paths, both fed by the
52
+ * prefetched text:
53
+ *
54
+ * - the native `copy` event (menu Edit→Copy, or ctrl/cmd+C while the page
55
+ * has a DOM selection or focused editable — e.g. the viewer shell's
56
+ * focus sink): answered SYNCHRONOUSLY from the cache;
57
+ * - ctrl/cmd+C with no DOM selection (canvas-rendered viewers usually
58
+ * have none, and browsers don't dispatch `copy` then): a keydown
59
+ * fallback writes via the async Clipboard API inside the keystroke's
60
+ * user-activation window.
61
+ *
62
+ * Returns the unwire function. Mount once per document view (not per page).
63
+ */
64
+ declare function wireSelectionClipboard(selection: ClipboardSelectionSource, options?: SelectionClipboardOptions): () => void;
65
+ /**
66
+ * Toolbar/menu path: read the selected text and write it to the clipboard
67
+ * via the async Clipboard API. Call from a user-gesture handler (browsers
68
+ * require a secure context + user activation for clipboard writes). Resolves
69
+ * with what was copied ('' when nothing was — nothing is written then, so an
70
+ * empty selection never clobbers the user's clipboard).
71
+ */
72
+ declare function copySelection(selection: ClipboardSelectionSource): Promise<string>;
73
+ //#endregion
74
+ //#region src/anchored-position.d.ts
75
+ /**
76
+ * Anchored-overlay geometry for UI floating over page content (selection
77
+ * menus, draft menus, popovers): the projector SNAPSHOT contract, the
78
+ * anchor shape, and the placement math. Pure values and pure functions over
79
+ * structural rect/point shapes — no EmbedPDF types, no framework, no
80
+ * reactive lifecycle — so every framework adapter (React, Vue, Svelte,
81
+ * Angular) shares ONE implementation and binds it with its NATIVE
82
+ * reactivity (context revision, signals, computed, $derived).
83
+ *
84
+ * The boundary laws:
85
+ * - Placement policy lives HERE, above the projector seam: a projector
86
+ * only knows geometry — never what is being positioned nor where it
87
+ * prefers to sit.
88
+ * - A projector describes the CURRENT projection; it never implements a
89
+ * framework-style reactive lifecycle. State-driven changes (the Stage
90
+ * camera) must reach consumers through the framework's own render
91
+ * cycle so surface and overlay commit TOGETHER; only genuinely
92
+ * browser-driven changes (a PageView moving because the document
93
+ * scrolled) use an external listener ({@link observeClientGeometry}).
94
+ */
95
+ interface AnchoredRect {
96
+ x: number;
97
+ y: number;
98
+ width: number;
99
+ height: number;
100
+ }
101
+ interface AnchoredPoint {
102
+ x: number;
103
+ y: number;
104
+ }
105
+ type AnchoredPlacement = 'top' | 'right' | 'bottom' | 'left';
106
+ /** CSS-ready output: absolute/fixed `left`/`top` plus a centering transform. */
107
+ interface AnchoredPosition {
108
+ left: number;
109
+ top: number;
110
+ transform: string;
111
+ }
112
+ /**
113
+ * Place an upright element around `box` (screen px). `avoid` is a screen
114
+ * point the element must clear (e.g. the rotate knob): the element extends
115
+ * ONLY the edge it sits on, and ONLY when the point protrudes past that
116
+ * edge — so it clears the obstacle without ever shifting off-centre on the
117
+ * other axis. When the point is on another side (e.g. a 90° shape, knob at
118
+ * mid-height for a `top` placement) the edge is untouched and the element
119
+ * stays centred on `box`.
120
+ */
121
+ /** What anchored UI attaches to: a content-space rect on a page, plus
122
+ * optional content-space points the UI must clear (e.g. a rotate knob).
123
+ * Structural — plugin anchor reads satisfy it without importing this
124
+ * package. */
125
+ interface AnchorTarget {
126
+ pon: number;
127
+ bounds: AnchoredRect;
128
+ avoid?: AnchoredPoint[];
129
+ }
130
+ /**
131
+ * A page surface's PROJECTION SNAPSHOT: how a content-space rect on a page
132
+ * becomes screen coordinates, right now. Provided by `<Stage>`
133
+ * (camera-driven, pure state, no DOM reads) and `<PageView>` (DOM-measured).
134
+ * Deliberately NO subscribe here — when projection changes is a framework
135
+ * binding concern (see the module doc), not part of the snapshot.
136
+ */
137
+ interface ViewProjector {
138
+ /**
139
+ * Which coordinate space `toScreen` speaks, and therefore how anchored
140
+ * content mounts:
141
+ * - 'overlay': coords are relative to the surface's own positioned
142
+ * container → rendered in place, position:absolute. (Stage)
143
+ * - 'client': coords are viewport px → portalled to the document body,
144
+ * position:fixed, so no ancestor overflow can clip. (PageView)
145
+ */
146
+ space: 'overlay' | 'client';
147
+ /** Content-space rect on a page → coords in `space`. Null: not projectable
148
+ * right now (page not shown / not measurable yet). */
149
+ toScreen(pon: number, rect: AnchoredRect): AnchoredRect | null;
150
+ toScreenPoint(pon: number, at: AnchoredPoint): AnchoredPoint | null;
151
+ /** The page's live view facts (for anchor reads that need them — e.g. the
152
+ * screen-constant rotate-knob stalk), or null when the page isn't shown. */
153
+ viewEnv(pon: number): {
154
+ scale: number;
155
+ rotation: 0 | 90 | 180 | 270;
156
+ zoom: number;
157
+ } | null;
158
+ }
159
+ /**
160
+ * THE anchored projection: anchor → screen position, in one pure call —
161
+ * projection, avoid-point transform, and the shared placement policy.
162
+ * Frameworks recompute this inside their native reactive primitive
163
+ * (a React render, a Vue `computed`, an Angular signal, a Svelte
164
+ * `$derived`); nothing here schedules anything.
165
+ */
166
+ declare function projectAnchoredTarget(projector: ViewProjector, anchor: AnchorTarget, placement: AnchoredPlacement, gap: number): AnchoredPosition | null;
167
+ /**
168
+ * The one genuinely BROWSER-driven invalidation: an element in normal
169
+ * document flow moves when the document scrolls or the window resizes —
170
+ * no state change announces it. `<PageView>`'s binding registers this;
171
+ * the Stage never does (its camera is state, delivered through render).
172
+ */
173
+ declare function observeClientGeometry(callback: () => void): () => void;
174
+ declare function positionAnchoredRect(box: AnchoredRect, placement: AnchoredPlacement, gap: number, avoid?: AnchoredPoint | null): AnchoredPosition;
175
+ //#endregion
19
176
  //#region src/cursor.d.ts
20
177
  /**
21
178
  * SVG → CSS cursor string. The armed-tool indicator is the CURSOR itself (the
@@ -59,5 +216,346 @@ declare function svgCursor({ svg, hotspot, fallback }: SvgCursorOptions): string
59
216
  */
60
217
  declare function sanitizeExternalUri(uri: string, allowedSchemes?: Iterable<string>): string | null;
61
218
  //#endregion
62
- export { type PickFileOptions, type SvgCursorOptions, pickFile, pickImageFile, sanitizeExternalUri, saveFile, svgCursor };
219
+ //#region src/painted-image.d.ts
220
+ /** A browser image source whose object URL has an explicit lifetime. */
221
+ interface ObjectUrlImageSource {
222
+ objectUrl(signal?: AbortSignal): Promise<{
223
+ url: string;
224
+ revoke(): void;
225
+ }>;
226
+ }
227
+ interface PaintedImageCallbacks {
228
+ onPainted(): void;
229
+ onUnpainted(): void;
230
+ }
231
+ /**
232
+ * Bind a raster source to an <img> without ever painting its incomplete-image
233
+ * placeholder. The source becomes visible on load; retained coverage is
234
+ * released only after the browser has had one full frame to present it.
235
+ *
236
+ * Call this from the framework's pre-paint mount hook and invoke the returned
237
+ * cleanup on unmount. Keeping this DOM lifecycle here lets every framework
238
+ * adapter remain a ref plus one bind/unbind hook.
239
+ */
240
+ declare function bindPaintedImage(image: HTMLImageElement, source: ObjectUrlImageSource, callbacks: PaintedImageCallbacks): () => void;
241
+ //#endregion
242
+ //#region src/feedback.d.ts
243
+ /**
244
+ * Platform-feedback providers — the browser implementations of the interaction
245
+ * hub's haptic port (structurally `PlatformFeedback` from
246
+ * `@embedpdf/plugin-interaction`; declared structurally here so this package
247
+ * stays plugin-free, per the layering law).
248
+ *
249
+ * Honesty about reach: `navigator.vibrate` works on Android browsers and is
250
+ * NOT implemented in iOS Safari — there the default provider is a silent
251
+ * no-op until Apple ships a haptics API. (The community switch-control hacks
252
+ * were evaluated and deliberately NOT shipped: Apple patched programmatic
253
+ * triggering in iOS 26.5, leaving only tap-on-control tricks that don't fit a
254
+ * viewer's gesture moments.) The real iPhone haptic today comes through a
255
+ * native shell: a WKWebView embedder installs a script message handler and
256
+ * passes {@link wkFeedback} (or their own provider) instead.
257
+ */
258
+ /** Structural twin of plugin-interaction's `PlatformFeedback`. */
259
+ interface WebPlatformFeedback {
260
+ selection(): void;
261
+ impact(weight?: 'light' | 'medium' | 'heavy'): void;
262
+ notify(kind: 'success' | 'warning' | 'error'): void;
263
+ }
264
+ /**
265
+ * The default web provider: the Vibration API, mapped onto the three haptic
266
+ * families (selection = the shortest tick; impact scales with weight; notify
267
+ * uses patterns). A no-op wherever vibration is unsupported — notably iOS
268
+ * Safari — so it is always safe to install.
269
+ */
270
+ declare const vibrationFeedback: WebPlatformFeedback;
271
+ /**
272
+ * A provider for native iOS shells: forwards each call to a WKWebView script
273
+ * message handler the HOST APP installed under `handlerName` — an explicit
274
+ * contract between the embedder and their own app, never a global this library
275
+ * sniffs on its own. The message shape is
276
+ * `{ family: 'selection' } | { family: 'impact', weight } | { family: 'notify', kind }`;
277
+ * the native side maps families onto the matching `UIFeedbackGenerator`.
278
+ * Calls fall back to `fallback` (default: {@link vibrationFeedback}) when the
279
+ * handler is absent, so one provider serves a hybrid web/app deployment.
280
+ */
281
+ declare const wkFeedback: (handlerName: string, fallback?: WebPlatformFeedback) => WebPlatformFeedback;
282
+ //#endregion
283
+ //#region src/stage-gestures.d.ts
284
+ /**
285
+ * Stage gesture controller — the ONE DOM input binding for a Stage surface,
286
+ * shared by every framework adapter (React, Angular, …) so the feel can never
287
+ * drift between them.
288
+ *
289
+ * The premise (see the stage plugin's camera doctrine): desktop inputs arrive
290
+ * with physics already applied by the OS — momentum lives in the wheel stream,
291
+ * a trackpad pinch is one pre-arbitrated gesture stream. Touch arrives RAW:
292
+ * with `touch-action: none` the platform scroller is out of the loop, so the
293
+ * ballistics (velocity → fling) and the arbitration (is this contact a scroll,
294
+ * a pinch, a tap, or a tool gesture?) must be synthesized here.
295
+ *
296
+ * Arbitration is MODALITY-AWARE:
297
+ * - touch — navigation-first: one finger pans (whatever tool is armed), two
298
+ * fingers pinch-zoom around their centroid, release velocity flings, a tap
299
+ * forwards as a click, a double-tap zoom-toggles, a long-press hands the
300
+ * gesture to the interaction hub (text selection), and a second finger
301
+ * landing mid-tool-gesture CANCELS it into a pinch (the Notes/Procreate
302
+ * convention).
303
+ * - mouse/pen — tool-first, exactly the pre-existing behavior: with a hub,
304
+ * every down/move/up forwards (pan is the pan tool's job); without one,
305
+ * dragging pans. Wheel and Safari-trackpad gesture events are unchanged.
306
+ *
307
+ * Camera writes are rAF-COALESCED: pointer events only update gesture state;
308
+ * one animation-frame tick applies at most one pan and one zoom per frame,
309
+ * inside the host's begin/endGesture transaction. Events may arrive at 120 Hz;
310
+ * the camera moves at display rate.
311
+ *
312
+ * Dependency note: this module speaks to the stage through the STRUCTURAL
313
+ * {@link StageGestureHost} interface (satisfied by `StageCapability`) and to
314
+ * the interaction hub through {@link StageGestureSink} (a closure the adapter
315
+ * builds) — @embedpdf/web stays free of plugin imports, per the layering law.
316
+ */
317
+ type StagePointerKind = 'mouse' | 'pen' | 'touch';
318
+ /** What the controller needs from the camera — `StageCapability` satisfies it. */
319
+ interface StageGestureHost {
320
+ panBy(dxScreen: number, dyScreen: number): void;
321
+ zoomAround(screenPt: {
322
+ x: number;
323
+ y: number;
324
+ }, factor: number): void;
325
+ beginGesture(options?: {
326
+ elastic?: boolean;
327
+ }): void;
328
+ endGesture(): void;
329
+ /** Momentum pan from a release velocity in screen px/s. */
330
+ fling(velocityX: number, velocityY: number): void;
331
+ /** True while a tween/fling runs — a touch-down then is a "catch", not a tap. */
332
+ cameraInMotion(): boolean;
333
+ doubleTapZoom(screenPt: {
334
+ x: number;
335
+ y: number;
336
+ }): void;
337
+ }
338
+ /**
339
+ * Where non-navigation gestures go — the adapter's bridge to the interaction
340
+ * hub. Every callback receives the ORIGINAL PointerEvent so the adapter can
341
+ * resolve pages/points exactly as it always has. Omit the sink entirely for a
342
+ * hub-less (built-in pan) stage.
343
+ */
344
+ interface StageGestureSink {
345
+ down(e: PointerEvent, clickCount: number): void;
346
+ move(e: PointerEvent): void;
347
+ up(e: PointerEvent): void;
348
+ /** The gesture was taken over by navigation (second finger → pinch) or
349
+ * cancelled by the system — abort, don't commit. */
350
+ cancel(e: PointerEvent): void;
351
+ /** Pointer travel with no gesture in flight — cursor feedback. */
352
+ hover(e: PointerEvent): void;
353
+ /** A touch press held still: hand the gesture to the hub (the adapter
354
+ * typically forwards it as a word-select down). Subsequent move/up arrive
355
+ * via {@link move}/{@link up}. */
356
+ longPress(e: PointerEvent): void;
357
+ /**
358
+ * Touch-consent pre-flight, asked at touch-down BEFORE the contact is
359
+ * classified: does a tool have standing to own this contact? True when the
360
+ * armed tool takes fingers wholesale (a drawing tool) or something under
361
+ * the point claims its drags (a selected annotation's body or handles).
362
+ * True routes the whole contact to {@link down}/{@link move}/{@link up} —
363
+ * where a second finger still cancels it into a pinch. Must be a pure
364
+ * read. Absent = never; the contact navigates.
365
+ */
366
+ claimsPoint?(e: PointerEvent): boolean;
367
+ }
368
+ /** The wheel fields the zoom classifier reads (see `./wheel`'s `WheelSample`). */
369
+ interface StageWheelSample {
370
+ deltaY: number;
371
+ deltaMode: number;
372
+ ctrlKey: boolean;
373
+ metaKey: boolean;
374
+ }
375
+ interface StageGestureOptions {
376
+ /** Ambient zoom (wheel-zoom, pinch-zoom, double-tap zoom). Off: zoom wheels
377
+ * fall through to pan and pinches pan without zooming — but are still
378
+ * swallowed, never page-zooming the browser. Default true. */
379
+ zoomGestures?: boolean;
380
+ /** The wheel → zoom-factor classifier. Defaults to this package's
381
+ * `wheelZoomFactor` (browser wheel classification lives HERE, with the rest
382
+ * of the browser input handling); inject to override or to fake in tests. */
383
+ wheelZoomFactor?: (sample: StageWheelSample) => number;
384
+ /** Tool routing for non-navigation gestures; omit for built-in-pan stages. */
385
+ sink?: StageGestureSink | null;
386
+ /** Touch press duration that becomes a long-press (ms). Default 450. */
387
+ longPressMs?: number;
388
+ /** Finger travel below which a touch stays a tap/press (px). Default 10. */
389
+ tapSlopPx?: number;
390
+ /** Max gap between taps for a double-tap (ms). Default 300. */
391
+ doubleTapMs?: number;
392
+ /** Release speed below which no fling starts (px/s). Default 50. */
393
+ flingMinVelocity?: number;
394
+ }
395
+ /**
396
+ * Release velocity from a trail of pointer samples: the mean velocity over the
397
+ * trailing `windowMs` (first-to-last inside the window). Null when the trail is
398
+ * too thin or too stale to trust — the standard "held still, then let go"
399
+ * case, which must NOT fling. Pure; exported for tests.
400
+ */
401
+ declare function computeReleaseVelocity(samples: ReadonlyArray<{
402
+ t: number;
403
+ x: number;
404
+ y: number;
405
+ }>, now: number, windowMs?: number): {
406
+ vx: number;
407
+ vy: number;
408
+ } | null;
409
+ /** Attach the gesture controller to a Stage container. Returns the detach fn. */
410
+ declare function createStageGestureController(el: HTMLElement, host: StageGestureHost, options: StageGestureOptions): () => void;
411
+ //#endregion
412
+ //#region src/selection-handles.d.ts
413
+ /**
414
+ * Selection-handle DOM binding — the listener mechanics of dragging one
415
+ * handle element, shared by every framework adapter (the policy — geometry
416
+ * and the drag session — lives in `@embedpdf/plugin-selection`'s pure
417
+ * `handles` module; this file never imports it, per the layering law:
418
+ * structural interfaces only).
419
+ *
420
+ * The load-bearing subtlety this module owns: the pointer-DOWN shield must be
421
+ * a NATIVE listener. The stage's gesture controller listens natively on the
422
+ * container, so a framework-synthetic stopPropagation (React runs its
423
+ * handlers at the root, AFTER the container's native ones) would be too late
424
+ * — the stage would already be panning underneath the handle drag. Once the
425
+ * down is captured here, movement tracks by CLIENT DELTAS from the handle's
426
+ * own base point: no DOM geometry reads, and the client↔overlay conversion
427
+ * cancels out.
428
+ */
429
+ interface SurfacePoint$1 {
430
+ x: number;
431
+ y: number;
432
+ }
433
+ /** The armed drag the adapter hands back — plugin-selection's
434
+ * `SelectionHandleDragSession` satisfies it. */
435
+ interface SelectionHandleSession {
436
+ move(overlay: SurfacePoint$1): void;
437
+ end(): void;
438
+ }
439
+ interface AttachSelectionHandleOptions {
440
+ /**
441
+ * Called at pointer-down to arm a drag: return the drag session plus the
442
+ * handle's current BASE point in overlay space (the bar's midpoint — the
443
+ * point the user grabbed). Return null to decline (endpoint vanished, page
444
+ * not laid out) — the press then does nothing and nothing is captured.
445
+ */
446
+ arm(): {
447
+ base: SurfacePoint$1;
448
+ session: SelectionHandleSession;
449
+ } | null;
450
+ }
451
+ /** Bind one handle element. Returns the detach fn. */
452
+ declare function attachSelectionHandle(el: HTMLElement, options: AttachSelectionHandleOptions): () => void;
453
+ //#endregion
454
+ //#region src/stage-surface.d.ts
455
+ interface SurfacePoint {
456
+ x: number;
457
+ y: number;
458
+ }
459
+ /** What the binding needs from the stage — `StageCapability` satisfies it. */
460
+ interface StageSurfaceHost extends StageGestureHost {
461
+ setViewport(size: {
462
+ width: number;
463
+ height: number;
464
+ }): void;
465
+ setDevicePixelRatio(ratio: number): void;
466
+ /** Viewport point → the page under it (with per-page display context), or null over a gap. */
467
+ pageAt(screen: SurfacePoint): {
468
+ pon: number;
469
+ point: SurfacePoint;
470
+ scale?: number;
471
+ rotation?: 0 | 90 | 180 | 270;
472
+ zoom?: number;
473
+ } | null;
474
+ /** Viewport point → a SPECIFIC page's content space, unclamped (frame-stable projection). */
475
+ pointOnPage(pon: number, screen: SurfacePoint): SurfacePoint | null;
476
+ }
477
+ /**
478
+ * The page-resolved sample the binding hands the hub — structurally a
479
+ * `PointerSample` (plugin-interaction). `source` carries the lens identity so
480
+ * the hub can route lens-scoped handlers; with several stages on one document,
481
+ * a drag on one lens must never be captured by another lens's handler.
482
+ */
483
+ interface StageSurfaceSample {
484
+ phase: 'down' | 'move' | 'up' | 'cancel';
485
+ viewport: SurfacePoint;
486
+ page?: {
487
+ pon: number;
488
+ point: SurfacePoint;
489
+ scale?: number;
490
+ rotation?: 0 | 90 | 180 | 270;
491
+ zoom?: number;
492
+ };
493
+ project: (pon: number) => SurfacePoint | null;
494
+ modifiers: {
495
+ shift: boolean;
496
+ alt: boolean;
497
+ ctrl: boolean;
498
+ meta: boolean;
499
+ };
500
+ clickCount: number;
501
+ pointerType: 'mouse' | 'pen' | 'touch';
502
+ gesture?: 'long-press';
503
+ source?: string;
504
+ }
505
+ /** What the binding needs from the interaction hub — `InteractionCapability` satisfies it. */
506
+ interface StageSurfaceHub {
507
+ dispatch(sample: StageSurfaceSample): void;
508
+ activeTool(): {
509
+ touchDirect?: boolean;
510
+ };
511
+ wouldClaimTouch(sample: StageSurfaceSample): boolean;
512
+ }
513
+ interface StageSurfaceOptions {
514
+ /** Route pointer input to the interaction hub (page-resolved samples) instead
515
+ * of built-in drag-to-pan. Omit/null for a hub-less stage. */
516
+ hub?: StageSurfaceHub | null;
517
+ /** The lens identity stamped on every sample (`StageCapability.lensId()`),
518
+ * so lens-scoped handlers only see their own stage's input. */
519
+ source?: string;
520
+ /** Ambient zoom gestures (see {@link StageGestureOptions}). Default true. */
521
+ zoomGestures?: boolean;
522
+ /** Override the wheel → zoom-factor classifier (defaults to `./wheel`). */
523
+ wheelZoomFactor?: (sample: StageWheelSample) => number;
524
+ }
525
+ /** Attach the full surface binding. Returns the detach fn. */
526
+ declare function createStageSurface(el: HTMLElement, stage: StageSurfaceHost, options?: StageSurfaceOptions): () => void;
527
+ //#endregion
528
+ //#region src/wheel.d.ts
529
+ /**
530
+ * Wheel-input classification for ambient zoom — pure, so every framework
531
+ * adapter shares one feel and the mapping is unit-testable.
532
+ *
533
+ * A ctrl/cmd wheel event is one of three physically different inputs that
534
+ * happen to share an event type, each with its own unit system:
535
+ *
536
+ * • trackpad PINCH — browsers (Chrome/Edge/Firefox) synthesize ctrl+wheel
537
+ * px-deltas scaled to the de-facto convention that `exp(-Δ/100)` tracks
538
+ * the physical finger scale 1:1. Anything weaker feels dead (the content
539
+ * must track the fingers — the Figma/maps feel).
540
+ * • mouse NOTCHES — coarse steps: ±100/±120 px in Chrome, lines/pages mode
541
+ * in Firefox. The pinch mapping would triple per click; a notch instead
542
+ * steps by 1.2, the same ratio as the zoom buttons.
543
+ * • cmd + continuous SCROLL — real scroll px, big and streaming (with
544
+ * momentum): a gentle scrub coefficient. A cmd+mouse notch (±120 px)
545
+ * lands within 0.3% of the 1.2 button step, so it needs no special case.
546
+ *
547
+ * (Safari never synthesizes ctrl+wheel for pinch — it fires proprietary
548
+ * gesture events with an absolute scale; that wiring lives in the adapters.)
549
+ */
550
+ interface WheelSample {
551
+ deltaY: number;
552
+ /** WheelEvent.deltaMode: 0 = pixels, 1 = lines, 2 = pages. */
553
+ deltaMode: number;
554
+ ctrlKey: boolean;
555
+ metaKey: boolean;
556
+ }
557
+ /** Zoom factor for a ctrl/cmd wheel event (the caller decides zoom vs pan). */
558
+ declare function wheelZoomFactor(e: WheelSample): number;
559
+ //#endregion
560
+ export { type AnchorTarget, type AnchoredPlacement, type AnchoredPoint, type AnchoredPosition, type AnchoredRect, type AttachSelectionHandleOptions, type ClipboardSelectionSource, type ObjectUrlImageSource, type PaintedImageCallbacks, type PickFileOptions, type SelectionClipboardOptions, type SelectionHandleSession, type StageGestureHost, type StageGestureOptions, type StageGestureSink, type StagePointerKind, type StageSurfaceHost, type StageSurfaceHub, type StageSurfaceOptions, type StageSurfaceSample, type StageWheelSample, type SvgCursorOptions, type ViewProjector, type WebPlatformFeedback, type WheelSample, attachSelectionHandle, bindPaintedImage, computeReleaseVelocity, copySelection, createStageGestureController, createStageSurface, observeClientGeometry, pickFile, pickImageFile, positionAnchoredRect, projectAnchoredTarget, sanitizeExternalUri, saveFile, svgCursor, vibrationFeedback, wheelZoomFactor, wireSelectionClipboard, wkFeedback };
63
561
  //# sourceMappingURL=index.d.ts.map