insomni 0.2.0-alpha.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/LICENSE.md +674 -0
- package/README.md +234 -0
- package/dist/advanced.d.mts +76 -0
- package/dist/advanced.mjs +81 -0
- package/dist/assemble-BT3CXbSx.mjs +1574 -0
- package/dist/camera-view-DHmMiKvP.d.mts +326 -0
- package/dist/frame-mHNdKRpF.mjs +135 -0
- package/dist/index-CmMZCMJT.d.mts +39 -0
- package/dist/index-DkJfpntS.d.mts +2417 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +6612 -0
- package/dist/internal.d.mts +892 -0
- package/dist/internal.mjs +566 -0
- package/dist/logger-DSyBF3Y_.mjs +15 -0
- package/dist/particles.d.mts +816 -0
- package/dist/particles.mjs +4804 -0
- package/dist/pipeline-BWCAZTKx.mjs +470 -0
- package/dist/pipeline-DE3a1Pnk.d.mts +115 -0
- package/dist/reactivity-B7I0pvzm.mjs +191 -0
- package/dist/reactivity.d.mts +2 -0
- package/dist/reactivity.mjs +2 -0
- package/dist/renderer-DzZqd1bY.d.mts +4566 -0
- package/dist/root-CHradZKM.mjs +30 -0
- package/dist/shape-DfZP9Jdk.mjs +349 -0
- package/dist/space-CeDnj6eu.mjs +11240 -0
- package/dist/spatial-Bd3Ay8I2.d.mts +85 -0
- package/dist/spatial-hash-C1crBjTo.mjs +77 -0
- package/dist/spatial.d.mts +2 -0
- package/dist/spatial.mjs +121 -0
- package/dist/text-font-D7GGDtTK.d.mts +185 -0
- package/dist/text-ttf.d.mts +91 -0
- package/dist/text-ttf.mjs +298 -0
- package/dist/texture-dABoqFoP.mjs +131 -0
- package/dist/viewport.d.mts +2 -0
- package/dist/viewport.mjs +274 -0
- package/package.json +69 -0
|
@@ -0,0 +1,2417 @@
|
|
|
1
|
+
import { At as TextStyleTable, Dt as TextOutline, Et as TextGradient, Ot as TextShadow, _r as Color, gn as Logger, r as Renderer2D, vt as Layer, yt as LayerOptions, z as AABB } from "./renderer-DzZqd1bY.mjs";
|
|
2
|
+
import { N as FrameRect, R as Vec2, S as Mat3, f as CameraViewport, g as Viewport, i as ResolvedCameraState, n as CameraState, r as FitCameraToBoundsOptions, t as Bounds2D } from "./camera-view-DHmMiKvP.mjs";
|
|
3
|
+
import { a as TextMetrics, g as GPUOwner, i as TextFont, n as GlyphAtlasOptions, o as MsdfGlyph, p as Texture, r as MeasureTextOptions, t as GlyphAtlas } from "./text-font-D7GGDtTK.mjs";
|
|
4
|
+
import { a as ReadableSignal } from "./index-CmMZCMJT.mjs";
|
|
5
|
+
import { TgpuBindGroup, TgpuRoot } from "typegpu";
|
|
6
|
+
|
|
7
|
+
//#region src/core/device.d.ts
|
|
8
|
+
interface GPUHandle {
|
|
9
|
+
adapter: GPUAdapter;
|
|
10
|
+
device: GPUDevice;
|
|
11
|
+
root: TgpuRoot;
|
|
12
|
+
/** Features the adapter granted. Subset of the caller's `requiredFeatures`. */
|
|
13
|
+
features: ReadonlySet<GPUFeatureName>;
|
|
14
|
+
destroy(): void;
|
|
15
|
+
}
|
|
16
|
+
interface InitGPUOptions extends GPURequestAdapterOptions {
|
|
17
|
+
maxAdapterAttempts?: number;
|
|
18
|
+
retryDelayMs?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Features to enable if the adapter exposes them. Unsupported entries are
|
|
21
|
+
* silently dropped — no rejection, no warning. Use `handle.features.has(...)`
|
|
22
|
+
* on the returned handle to branch at runtime.
|
|
23
|
+
*/
|
|
24
|
+
requiredFeatures?: readonly GPUFeatureName[];
|
|
25
|
+
/**
|
|
26
|
+
* Limits to request on the device. Each entry is clamped to the adapter's
|
|
27
|
+
* supported maximum — unsupported keys or over-max values are silently
|
|
28
|
+
* lowered rather than rejected. Pass `"adapter-max"` as the value to opt
|
|
29
|
+
* into whatever ceiling the adapter exposes for that limit.
|
|
30
|
+
*/
|
|
31
|
+
requiredLimits?: Record<string, number | "adapter-max">;
|
|
32
|
+
/**
|
|
33
|
+
* Diagnostic logger for library output (device-loss / GPU validation errors,
|
|
34
|
+
* warnings). Installed process-wide via {@link setLogger} so the diagnostic
|
|
35
|
+
* sites in unrelated modules pick it up too. Defaults to `console`.
|
|
36
|
+
*/
|
|
37
|
+
logger?: Logger;
|
|
38
|
+
/**
|
|
39
|
+
* Invoked when the device is lost for a reason OTHER than an intentional
|
|
40
|
+
* {@link GPUHandle.destroy} (`info.reason !== "destroyed"`) — e.g. a GPU
|
|
41
|
+
* reset, driver crash, or TDR. This is a NOTIFY-ONLY seam for alpha: insomni
|
|
42
|
+
* does NOT auto-recreate the device or rebuild GPU resources. Callers wanting
|
|
43
|
+
* recovery should tear down their renderer and re-run `initGPU` from this
|
|
44
|
+
* hook. (Auto-reinit is deferred — it requires re-uploading every retained
|
|
45
|
+
* buffer and re-binding pipelines, which the renderer does not yet support.)
|
|
46
|
+
*/
|
|
47
|
+
onDeviceLost?: (info: GPUDeviceLostInfo) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Invoked for each uncaptured GPU error (`device.onuncapturederror`) in
|
|
50
|
+
* addition to logging it via {@link logger}. Use to surface GPU validation /
|
|
51
|
+
* out-of-memory errors to the host app. Cheap: a single event listener, no
|
|
52
|
+
* per-frame error-scope wrapping.
|
|
53
|
+
*/
|
|
54
|
+
onUncapturedError?: (error: GPUError) => void;
|
|
55
|
+
}
|
|
56
|
+
declare function initGPU(options?: InitGPUOptions): Promise<GPUHandle>;
|
|
57
|
+
declare function getLastGPUHandle(): GPUHandle | null;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/svg-renderer.d.ts
|
|
60
|
+
interface SVGRendererOptions {
|
|
61
|
+
width?: number;
|
|
62
|
+
height?: number;
|
|
63
|
+
camera?: CameraState;
|
|
64
|
+
/**
|
|
65
|
+
* Device-pixel ratio used to scale `space: "ui"` layers into the SVG viewBox.
|
|
66
|
+
* Default: 1.
|
|
67
|
+
*/
|
|
68
|
+
dpr?: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* CPU-only SVG export backend for the v3 renderer. Consumes the same
|
|
72
|
+
* `readonly Layer[]` as `Renderer2D.render()` and produces an SVG string.
|
|
73
|
+
*
|
|
74
|
+
* Supported: rect / circle / ellipse (`<rect>` / `<circle>` / `<ellipse>`),
|
|
75
|
+
* segment (`<line>`), curve (`<path>` cubic), arc (`<path>` arc), triangle /
|
|
76
|
+
* polygon (`<polygon>`), and text (`<text>`, recovered from the glyph atlas).
|
|
77
|
+
* Honors per-layer `space` (world / ui / device), the camera, per-shape group
|
|
78
|
+
* transforms (folded — see note), and per-layer clip rects.
|
|
79
|
+
*
|
|
80
|
+
* NOT supported (warn-and-skip): sprites / textures (`TYPE_SPRITE` is a
|
|
81
|
+
* Phase-4 concern — see `kinds/kind.ts`), BufferLayer compute output (no CPU
|
|
82
|
+
* introspection of GPU buffers).
|
|
83
|
+
*/
|
|
84
|
+
declare class SVGRenderer {
|
|
85
|
+
private _width;
|
|
86
|
+
private _height;
|
|
87
|
+
private _dpr;
|
|
88
|
+
private _camera;
|
|
89
|
+
private _background;
|
|
90
|
+
private _lastSvg;
|
|
91
|
+
private readonly warned;
|
|
92
|
+
constructor(options?: SVGRendererOptions);
|
|
93
|
+
get width(): number;
|
|
94
|
+
get height(): number;
|
|
95
|
+
get dpr(): number;
|
|
96
|
+
setDpr(dpr: number): void;
|
|
97
|
+
resize(width: number, height: number): void;
|
|
98
|
+
setBackground(color: Color): void;
|
|
99
|
+
setCamera(state: CameraState): void;
|
|
100
|
+
getCamera(): ResolvedCameraState;
|
|
101
|
+
fitCameraToBounds(bounds: Bounds2D, options?: FitCameraToBoundsOptions): void;
|
|
102
|
+
getViewMatrix(): Mat3;
|
|
103
|
+
screenToWorld(sx: number, sy: number): Vec2;
|
|
104
|
+
worldToScreen(wx: number, wy: number): Vec2;
|
|
105
|
+
/** SVG has no GPU compute phase. */
|
|
106
|
+
compute(_dispatch: (pass: GPUComputePassEncoder) => void): void;
|
|
107
|
+
/**
|
|
108
|
+
* Render `layers` to an SVG document and return it as a string. Layers are
|
|
109
|
+
* composited in array order. The returned string is also cached and exposed
|
|
110
|
+
* via `svg()` / `element()`.
|
|
111
|
+
*/
|
|
112
|
+
render(layers: readonly Layer[]): string;
|
|
113
|
+
/** The SVG produced by the most recent `render()` call (empty before the first). */
|
|
114
|
+
svg(): string;
|
|
115
|
+
/**
|
|
116
|
+
* Parse the most recent `render()` output into an `SVGSVGElement`. Requires a
|
|
117
|
+
* DOM (`DOMParser`); throws in a pure-Node environment. Use `svg()` for the
|
|
118
|
+
* string form in Node.
|
|
119
|
+
*/
|
|
120
|
+
element(): SVGSVGElement;
|
|
121
|
+
destroy(): void;
|
|
122
|
+
/** Build the `<g>` body for one layer (base VP + shape commands + glyphs). */
|
|
123
|
+
private renderLayerBody;
|
|
124
|
+
private emitCommand;
|
|
125
|
+
private emitShapes;
|
|
126
|
+
private rectEl;
|
|
127
|
+
private emitSegments;
|
|
128
|
+
private emitCurves;
|
|
129
|
+
private emitArcs;
|
|
130
|
+
private emitTriangles;
|
|
131
|
+
/**
|
|
132
|
+
* Emit `<text>` elements recovered from glyph instances. The GlyphPack does
|
|
133
|
+
* NOT retain the original strings, so we reconstruct text by walking the
|
|
134
|
+
* glyph instances in pack order, grouping consecutive glyphs that share a
|
|
135
|
+
* baseline (same packed `y` and color) into one run, and mapping each glyph's
|
|
136
|
+
* `atlasGlyphId` (lane0) back to its codepoint via the atlas. The run's left
|
|
137
|
+
* edge (min x) and the first glyph's top set the `<text>` origin.
|
|
138
|
+
*
|
|
139
|
+
* `anchored` glyphs hold a world anchor in geom0.xy (the live GPU path
|
|
140
|
+
* projects them per-frame); here we leave them in the layer's base VP like
|
|
141
|
+
* any other glyph, which is correct for `world` layers (the base VP already
|
|
142
|
+
* carries the camera projection).
|
|
143
|
+
*/
|
|
144
|
+
private emitGlyphs;
|
|
145
|
+
private wrapWithClip;
|
|
146
|
+
private warnOnce;
|
|
147
|
+
}
|
|
148
|
+
declare function createSVGRenderer(options?: SVGRendererOptions): SVGRenderer;
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/layers/mirror-layer.d.ts
|
|
151
|
+
/**
|
|
152
|
+
* LOD options for a simplified mirror. Skipping categories cuts CPU→GPU
|
|
153
|
+
* upload and draw-call overhead for overview / minimap renders where the
|
|
154
|
+
* primary's fine details are unreadable or invisible at the reduced scale.
|
|
155
|
+
*
|
|
156
|
+
* A bare `true` enables a sensible default preset:
|
|
157
|
+
* `{ skipTriangles: true, skipSprites: true }`.
|
|
158
|
+
*/
|
|
159
|
+
interface MirrorSimplify {
|
|
160
|
+
/**
|
|
161
|
+
* Omit triangle draws — polygon fills, polyline tessellations, and filled
|
|
162
|
+
* line strokes. Shape rects/circles/ellipses are unaffected. Default: false.
|
|
163
|
+
*/
|
|
164
|
+
skipTriangles?: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Omit sprite commands (raster images / MSDF text glyphs). Default: false.
|
|
167
|
+
*/
|
|
168
|
+
skipSprites?: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Strip stroke color and stroke-width from SDF shape records
|
|
171
|
+
* (rect, circle, ellipse). Shapes render fill-only. Outline-only shapes
|
|
172
|
+
* (fill alpha 0, stroke alpha > 0) are preserved and their stroke width is
|
|
173
|
+
* multiplied by {@link outlineBoost} so they stay readable at minimap scale.
|
|
174
|
+
* Default: false.
|
|
175
|
+
*/
|
|
176
|
+
stripStrokes?: boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Multiplier applied to the stroke width of preserved outline-only shapes
|
|
179
|
+
* when {@link stripStrokes} is on. Default: 1.
|
|
180
|
+
*/
|
|
181
|
+
outlineBoost?: number;
|
|
182
|
+
}
|
|
183
|
+
interface MirrorLayerOptions {
|
|
184
|
+
/**
|
|
185
|
+
* CSS-pixel scissor / crop rect. Passed as `clip` to the Layer super
|
|
186
|
+
* constructor. Defaults to `undefined` (full viewport).
|
|
187
|
+
*/
|
|
188
|
+
clip?: LayerOptions["clip"];
|
|
189
|
+
/**
|
|
190
|
+
* Enable LOD simplification. When set, the mirror owns a derived pack built
|
|
191
|
+
* by filtering the primary's draw commands. Call {@link MirrorLayer.refresh}
|
|
192
|
+
* after mutating the primary to rebuild. Without this option (default), the
|
|
193
|
+
* mirror shares the primary's pack by reference and auto-tracks primary pushes.
|
|
194
|
+
*/
|
|
195
|
+
simplify?: MirrorSimplify | true;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* A {@link Layer} that re-renders another layer's packed shapes through a
|
|
199
|
+
* different viewport / clip rect. By default shares the primary's `_pack`
|
|
200
|
+
* by reference, so any push to the primary appears in the mirror on the next
|
|
201
|
+
* frame at zero CPU cost.
|
|
202
|
+
*
|
|
203
|
+
* Pass `simplify` to enable LOD: the mirror then owns a derived pack built by
|
|
204
|
+
* filtering the primary's draw commands by kind. Call
|
|
205
|
+
* {@link MirrorLayer.refresh} after mutating the primary to rebuild.
|
|
206
|
+
*
|
|
207
|
+
* Push and clear methods throw — mutate the primary instead.
|
|
208
|
+
*/
|
|
209
|
+
declare class MirrorLayer extends Layer {
|
|
210
|
+
/** Primary layer this mirror tracks. */
|
|
211
|
+
readonly primary: Layer;
|
|
212
|
+
private readonly _simplify;
|
|
213
|
+
private _lastVersion;
|
|
214
|
+
constructor(primary: Layer, options?: MirrorLayerOptions);
|
|
215
|
+
/**
|
|
216
|
+
* In simplify mode: rebuild the derived pack from the primary if the
|
|
217
|
+
* primary's pack version has advanced since the last refresh. No-op
|
|
218
|
+
* otherwise. Calling this on a non-simplify mirror is always a no-op.
|
|
219
|
+
*/
|
|
220
|
+
refresh(): this;
|
|
221
|
+
clear(): this;
|
|
222
|
+
pushRect(): this;
|
|
223
|
+
pushCircle(): this;
|
|
224
|
+
pushEllipse(): this;
|
|
225
|
+
pushSegment(): this;
|
|
226
|
+
pushLine(): this;
|
|
227
|
+
pushCurve(): this;
|
|
228
|
+
pushArc(): this;
|
|
229
|
+
pushTriangle(): this;
|
|
230
|
+
pushPolygon(): this;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Construct a {@link MirrorLayer} that renders `primary`'s shapes through a
|
|
234
|
+
* different viewport. See {@link MirrorLayer} for constraints.
|
|
235
|
+
*/
|
|
236
|
+
declare function mirrorLayer(primary: Layer, options?: MirrorLayerOptions): MirrorLayer;
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/interaction/constants.d.ts
|
|
239
|
+
/**
|
|
240
|
+
* Movement in CSS pixels a pointer may travel between press and release
|
|
241
|
+
* while still firing onPress (above this → drag).
|
|
242
|
+
*/
|
|
243
|
+
declare const PRESS_MOVEMENT_THRESHOLD_PX = 4;
|
|
244
|
+
/** Default zIndex for user-registered nodes. */
|
|
245
|
+
declare const USER_DEFAULT_Z = 0;
|
|
246
|
+
/** Viewport-scoped interactions (data viewports) register below user nodes. */
|
|
247
|
+
declare const VIEWPORT_Z = -100;
|
|
248
|
+
/** Camera viewports register below data viewports. */
|
|
249
|
+
declare const CAMERA_Z = -200;
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/interaction/node.d.ts
|
|
252
|
+
interface Mods {
|
|
253
|
+
shift: boolean;
|
|
254
|
+
ctrl: boolean;
|
|
255
|
+
meta: boolean;
|
|
256
|
+
alt: boolean;
|
|
257
|
+
}
|
|
258
|
+
interface PointerInfo {
|
|
259
|
+
pointerId: number;
|
|
260
|
+
type: "mouse" | "touch" | "pen";
|
|
261
|
+
/** Element-local CSS pixels. */
|
|
262
|
+
x: number;
|
|
263
|
+
y: number;
|
|
264
|
+
/**
|
|
265
|
+
* Node-space coords.
|
|
266
|
+
* - `space: 'ui'` → same as `x` / `y` (element-local CSS px).
|
|
267
|
+
* - `space: 'world'` on a camera viewport → world units (post camera transform).
|
|
268
|
+
* - `space: 'world'` on a data viewport → CSS px local to `viewport.absoluteFrame`
|
|
269
|
+
* (no axis-domain transform; use `viewport.screenToData` for that).
|
|
270
|
+
*/
|
|
271
|
+
localX: number;
|
|
272
|
+
localY: number;
|
|
273
|
+
buttons: number;
|
|
274
|
+
mods: Mods;
|
|
275
|
+
stopPropagation(): void;
|
|
276
|
+
}
|
|
277
|
+
interface ScrollInfo {
|
|
278
|
+
pointerId: number;
|
|
279
|
+
/** Element-local CSS pixels. */
|
|
280
|
+
x: number;
|
|
281
|
+
y: number;
|
|
282
|
+
/** Wheel deltas in CSS pixels. */
|
|
283
|
+
dx: number;
|
|
284
|
+
dy: number;
|
|
285
|
+
mods: Mods;
|
|
286
|
+
stopPropagation(): void;
|
|
287
|
+
}
|
|
288
|
+
interface PinchInfo {
|
|
289
|
+
phase: "start" | "move" | "end";
|
|
290
|
+
/** Cumulative scale relative to start (1 at start and end). */
|
|
291
|
+
scaleFactor: number;
|
|
292
|
+
/** Incremental scale since the previous move (1 at start). */
|
|
293
|
+
deltaScale: number;
|
|
294
|
+
/** Midpoint of the two pointers, element-local CSS px. */
|
|
295
|
+
focalX: number;
|
|
296
|
+
focalY: number;
|
|
297
|
+
/** Focal-point delta since the previous move (0 at start), CSS px. */
|
|
298
|
+
deltaX: number;
|
|
299
|
+
deltaY: number;
|
|
300
|
+
mods: Mods;
|
|
301
|
+
stopPropagation(): void;
|
|
302
|
+
}
|
|
303
|
+
interface DragPointerInfo extends PointerInfo {
|
|
304
|
+
/** Delta from the previous pointermove, CSS pixels. */
|
|
305
|
+
dx: number;
|
|
306
|
+
dy: number;
|
|
307
|
+
}
|
|
308
|
+
interface InteractionNodeSpec {
|
|
309
|
+
/** Higher wins hit-test. Ties break by insertion order (later = on top). */
|
|
310
|
+
zIndex?: number;
|
|
311
|
+
/** `'world'` = transformed by viewport's camera; `'ui'` = CSS-pixel local. Default `'ui'`. */
|
|
312
|
+
space?: "world" | "ui";
|
|
313
|
+
/** Required when `space === 'world'`. Optional frame for `'ui'`. */
|
|
314
|
+
viewport?: Viewport;
|
|
315
|
+
/** Cheap bounds test. Coordinates are element-local CSS px. */
|
|
316
|
+
bounds?: () => FrameRect;
|
|
317
|
+
/** Fine-grained test in node-space coords. Called only if `bounds` passed. */
|
|
318
|
+
contains?: (x: number, y: number) => boolean;
|
|
319
|
+
/**
|
|
320
|
+
* If true, this node is skipped during hit-testing entirely (events fall
|
|
321
|
+
* through to nodes below). v1 treats passthrough as "decorative / never
|
|
322
|
+
* claims." True fan-out (claim AND continue) is v2.
|
|
323
|
+
*/
|
|
324
|
+
passthrough?: boolean;
|
|
325
|
+
/** Cursor applied while hovering this node. */
|
|
326
|
+
cursor?: string;
|
|
327
|
+
/** Cursor applied while actively dragging this node. Defaults to `cursor`. */
|
|
328
|
+
dragCursor?: string;
|
|
329
|
+
/** Whether the node responds to events. Default: true. */
|
|
330
|
+
enabled?: boolean;
|
|
331
|
+
/**
|
|
332
|
+
* Extra hit radius (CSS px) added to bounds/contains checks for touch
|
|
333
|
+
* pointers only. Defaults to 0. Recommended: 10–20 for small marks.
|
|
334
|
+
* Mouse and pen hit-testing is never affected.
|
|
335
|
+
*/
|
|
336
|
+
touchHitSlop?: number;
|
|
337
|
+
onHoverEnter?(evt: PointerInfo): void;
|
|
338
|
+
onHoverMove?(evt: PointerInfo): void;
|
|
339
|
+
onHoverLeave?(evt: PointerInfo): void;
|
|
340
|
+
onPress?(evt: PointerInfo): void;
|
|
341
|
+
onDragStart?(evt: PointerInfo): void;
|
|
342
|
+
onDragMove?(evt: DragPointerInfo): void;
|
|
343
|
+
onDragEnd?(evt: PointerInfo): void;
|
|
344
|
+
/** Wheel only. Pinch is delivered via `onPinch`. */
|
|
345
|
+
onScroll?(evt: ScrollInfo): void;
|
|
346
|
+
onPinch?(evt: PinchInfo): void;
|
|
347
|
+
}
|
|
348
|
+
interface InteractionNode {
|
|
349
|
+
readonly id: symbol;
|
|
350
|
+
/** Patch the spec in place (e.g., toggle enabled, swap bounds). */
|
|
351
|
+
update(patch: Partial<InteractionNodeSpec>): void;
|
|
352
|
+
/** Unregister. Fires pending onDragEnd / onHoverLeave / onPinch end as needed. */
|
|
353
|
+
destroy(): void;
|
|
354
|
+
}
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/interaction/point-cloud.d.ts
|
|
357
|
+
interface PointCloudSpec {
|
|
358
|
+
/** `[x0, y0, x1, y1, ...]` in the declared `space`. */
|
|
359
|
+
points: ReadonlyArray<number> | Float32Array;
|
|
360
|
+
/**
|
|
361
|
+
* Coordinate space the points live in.
|
|
362
|
+
* - `'ui'` → element-local CSS px.
|
|
363
|
+
* - `'world'` → world units (requires a camera viewport).
|
|
364
|
+
* Defaults to `'ui'`.
|
|
365
|
+
*/
|
|
366
|
+
space?: "world" | "ui";
|
|
367
|
+
/** Required when `space: 'world'` and the viewport is a camera viewport. */
|
|
368
|
+
viewport?: Viewport;
|
|
369
|
+
/**
|
|
370
|
+
* Nearest-within selection radius, CSS pixels. When set, the nearest point
|
|
371
|
+
* within this radius of the pointer is hovered.
|
|
372
|
+
*/
|
|
373
|
+
nearestWithin?: number;
|
|
374
|
+
/**
|
|
375
|
+
* Exact hit radius per point (CSS pixels) when `nearestWithin` is not set.
|
|
376
|
+
* First point found within radius wins.
|
|
377
|
+
*/
|
|
378
|
+
hitRadius?: number;
|
|
379
|
+
/**
|
|
380
|
+
* Distance metric for nearest-point queries.
|
|
381
|
+
* - `"xy"` (default) — Euclidean distance, picks point closest in 2D.
|
|
382
|
+
* - `"x"` — only x-axis distance counts; the off-axis y is ignored. Useful
|
|
383
|
+
* for "nearest x" hover modes on lines/areas where the cursor's vertical
|
|
384
|
+
* position shouldn't influence which vertex is selected.
|
|
385
|
+
* - `"y"` — symmetric, x ignored.
|
|
386
|
+
*/
|
|
387
|
+
pickAxis?: "x" | "y" | "xy";
|
|
388
|
+
/** Higher wins hit-test. */
|
|
389
|
+
zIndex?: number;
|
|
390
|
+
/** Disable without tearing down state. */
|
|
391
|
+
enabled?: boolean;
|
|
392
|
+
onHoverEnter?(index: number, evt: PointerInfo): void;
|
|
393
|
+
onHoverLeave?(index: number, evt: PointerInfo): void;
|
|
394
|
+
onPress?(index: number, evt: PointerInfo): void;
|
|
395
|
+
}
|
|
396
|
+
interface PointCloudNode extends InteractionNode {
|
|
397
|
+
/** Replace the point array. Triggers a grid rebuild. */
|
|
398
|
+
setPoints(points: ReadonlyArray<number> | Float32Array): void;
|
|
399
|
+
/** Enable / disable without tearing down the grid. */
|
|
400
|
+
setEnabled(enabled: boolean): void;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Registers a point cloud as a single InteractionNode. The manager delegates
|
|
404
|
+
* hit-testing through a uniform spatial grid keyed on the declared `space`.
|
|
405
|
+
*
|
|
406
|
+
* World-space point clouds on camera viewports:
|
|
407
|
+
* - Points are stored in world units.
|
|
408
|
+
* - The pointer is transformed into world coords at query time (no rebuild
|
|
409
|
+
* on camera change).
|
|
410
|
+
* - The CSS-px `nearestWithin` radius is converted to world units via
|
|
411
|
+
* `radius / camera.zoom`.
|
|
412
|
+
*/
|
|
413
|
+
declare function registerPointCloud(manager: InteractionManager, element: HTMLElement, spec: PointCloudSpec): PointCloudNode;
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region src/interaction/manager.d.ts
|
|
416
|
+
interface BackgroundTapInfo {
|
|
417
|
+
/** Element-local CSS pixel coordinates at pointerup. */
|
|
418
|
+
x: number;
|
|
419
|
+
y: number;
|
|
420
|
+
pointerId: number;
|
|
421
|
+
type: "mouse" | "touch" | "pen";
|
|
422
|
+
mods: Mods;
|
|
423
|
+
}
|
|
424
|
+
interface GestureEventInfo {
|
|
425
|
+
/** Element-local CSS pixel coordinates. */
|
|
426
|
+
x: number;
|
|
427
|
+
y: number;
|
|
428
|
+
source: "mouse" | "touch" | "pen" | "keyboard";
|
|
429
|
+
mods: Mods;
|
|
430
|
+
originalEvent: Event | null;
|
|
431
|
+
}
|
|
432
|
+
interface ContextMenuOpts {
|
|
433
|
+
/** Touch hold duration before contextMenuRequest fires. Default 500ms. */
|
|
434
|
+
holdMs?: number;
|
|
435
|
+
/** Max pointer movement during hold (CSS px) before cancellation. Default 5. */
|
|
436
|
+
slopPx?: number;
|
|
437
|
+
}
|
|
438
|
+
interface DoubleTapOpts {
|
|
439
|
+
/** Max interval between the two taps. Default 300ms. */
|
|
440
|
+
withinMs?: number;
|
|
441
|
+
/** Max distance between the two taps (CSS px). Default 5. */
|
|
442
|
+
slopPx?: number;
|
|
443
|
+
}
|
|
444
|
+
interface InteractionManager {
|
|
445
|
+
/** Element whose listeners and touch-behavior this manager owns. */
|
|
446
|
+
readonly element: HTMLElement;
|
|
447
|
+
add(spec: InteractionNodeSpec): InteractionNode;
|
|
448
|
+
addPointCloud(spec: PointCloudSpec): PointCloudNode;
|
|
449
|
+
/**
|
|
450
|
+
* Subscribe to background taps — press-without-drag releases where no
|
|
451
|
+
* press-capable node claimed the press. Fires for genuine clicks on empty
|
|
452
|
+
* space (the common "click to deselect" pattern).
|
|
453
|
+
*
|
|
454
|
+
* Returns an unsubscribe function. The threshold for "not a drag" is the
|
|
455
|
+
* same as elsewhere ({@link PRESS_MOVEMENT_THRESHOLD_PX}).
|
|
456
|
+
*/
|
|
457
|
+
onBackgroundTap(handler: (info: BackgroundTapInfo) => void): () => void;
|
|
458
|
+
/**
|
|
459
|
+
* Subscribe to context-menu requests — fires for native `contextmenu`
|
|
460
|
+
* events (right-click / Ctrl-click), touch long-press (≥holdMs hold,
|
|
461
|
+
* ≤slopPx movement), and keyboard synthesis (Shift+F10 or the dedicated
|
|
462
|
+
* ContextMenu key). The manager calls `preventDefault()` on the native
|
|
463
|
+
* `contextmenu` and on keyboard events while at least one listener is
|
|
464
|
+
* registered. The most recently passed `opts` win — multiple listeners
|
|
465
|
+
* with conflicting timings is not a supported configuration.
|
|
466
|
+
*/
|
|
467
|
+
onContextMenuRequest(handler: (info: GestureEventInfo) => void, opts?: ContextMenuOpts): () => void;
|
|
468
|
+
/**
|
|
469
|
+
* Subscribe to double-tap / double-click. Fires on two `pointerup` events
|
|
470
|
+
* within `withinMs` and `slopPx`, where neither leg promoted to a drag.
|
|
471
|
+
* Does NOT suppress the underlying single-tap callbacks — consumers that
|
|
472
|
+
* need true "either-or" semantics should debounce in user code.
|
|
473
|
+
*/
|
|
474
|
+
onDoubleTap(handler: (info: GestureEventInfo) => void, opts?: DoubleTapOpts): () => void;
|
|
475
|
+
/**
|
|
476
|
+
* Subscribe to any user-callback invocation by the manager (press, drag,
|
|
477
|
+
* hover, scroll, pinch, background tap). Pair with an `Invalidator` to
|
|
478
|
+
* drive on-demand rendering without reacting to pointer motion over inert
|
|
479
|
+
* areas. Fires synchronously each time a user callback runs; multiple
|
|
480
|
+
* invocations per DOM event are normal and idempotent for typical dirty-flag
|
|
481
|
+
* listeners.
|
|
482
|
+
*/
|
|
483
|
+
onChange(cb: () => void): () => void;
|
|
484
|
+
/** Check if a point at local CSS-px coordinates hits any active node. */
|
|
485
|
+
hitTest(cssX: number, cssY: number): boolean;
|
|
486
|
+
/** Drop all nodes and listeners. The element loses its manager binding. */
|
|
487
|
+
destroy(): void;
|
|
488
|
+
}
|
|
489
|
+
declare function createInteractionManager(element: HTMLElement): InteractionManager;
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region src/interaction/region-cloud.d.ts
|
|
492
|
+
interface RegionCloudSpec {
|
|
493
|
+
/**
|
|
494
|
+
* Per-slot rects in element-local CSS pixels.
|
|
495
|
+
* Layout: `[x0, y0, w0, h0, x1, y1, w1, h1, ...]`.
|
|
496
|
+
*/
|
|
497
|
+
rects: ReadonlyArray<number> | Float32Array;
|
|
498
|
+
/** Higher wins hit-test. */
|
|
499
|
+
zIndex?: number;
|
|
500
|
+
/** Disable without tearing down state. */
|
|
501
|
+
enabled?: boolean;
|
|
502
|
+
onHoverEnter?(index: number, evt: PointerInfo): void;
|
|
503
|
+
onHoverLeave?(index: number, evt: PointerInfo): void;
|
|
504
|
+
onPress?(index: number, evt: PointerInfo): void;
|
|
505
|
+
}
|
|
506
|
+
interface RegionCloudNode extends InteractionNode {
|
|
507
|
+
/** Replace the rect array. Triggers a grid rebuild. */
|
|
508
|
+
setRects(rects: ReadonlyArray<number> | Float32Array): void;
|
|
509
|
+
setEnabled(enabled: boolean): void;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Registers a rect cloud as a single InteractionNode. The manager delegates
|
|
513
|
+
* hit-testing through a uniform spatial grid in element-local CSS pixels.
|
|
514
|
+
*/
|
|
515
|
+
declare function registerRegionCloud(manager: InteractionManager, element: HTMLElement, spec: RegionCloudSpec): RegionCloudNode;
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/interaction/hit-fanout.d.ts
|
|
518
|
+
/**
|
|
519
|
+
* Opaque slot descriptor. Either `positions` (point cloud) or `rects` (region
|
|
520
|
+
* cloud) must be set — slots flip between the two by changing which is
|
|
521
|
+
* populated. `id` is for slot identity across syncs (stable id → fast path;
|
|
522
|
+
* different id → rebuild) and for state survival across in-place updates.
|
|
523
|
+
*/
|
|
524
|
+
interface HitSlot {
|
|
525
|
+
/** Stable identity. Used for fast-path matching across sync calls. */
|
|
526
|
+
id: string | number;
|
|
527
|
+
/** Flat `[x0,y0,x1,y1,...]` in element-local CSS px. Required for point slots. */
|
|
528
|
+
positions?: Float32Array;
|
|
529
|
+
/** Flat `[x,y,w,h, x,y,w,h, ...]` in element-local CSS px. Required for region slots. */
|
|
530
|
+
rects?: Float32Array;
|
|
531
|
+
/** Hit radius in element-local CSS px. Ignored for region slots. */
|
|
532
|
+
pickRadius: number;
|
|
533
|
+
/** Axis to restrict distance computation to. Default `"xy"` (Euclidean). */
|
|
534
|
+
pickAxis?: "x" | "y" | "xy";
|
|
535
|
+
}
|
|
536
|
+
interface HitFanOutEventContext {
|
|
537
|
+
/** Position of the dispatched hit, derived from the slot's geometry. */
|
|
538
|
+
position: {
|
|
539
|
+
x: number;
|
|
540
|
+
y: number;
|
|
541
|
+
};
|
|
542
|
+
/** Index of the slot the hit belongs to (in current sync's order). */
|
|
543
|
+
slotIdx: number;
|
|
544
|
+
/** Snapshot of the slot at dispatch time (the live ref the fan-out holds). */
|
|
545
|
+
slot: HitSlot;
|
|
546
|
+
/** Index into the slot's positions / rects array. */
|
|
547
|
+
hitIndex: number;
|
|
548
|
+
/** Raw pointer event that produced the hit. */
|
|
549
|
+
pointer: PointerInfo;
|
|
550
|
+
}
|
|
551
|
+
interface HitFanOutSubscriber {
|
|
552
|
+
/** Stable key for debugging — shown in dev-tools / error traces. */
|
|
553
|
+
key: string;
|
|
554
|
+
onHoverEnter?(ctx: HitFanOutEventContext): void;
|
|
555
|
+
onHoverLeave?(ctx: HitFanOutEventContext): void;
|
|
556
|
+
onPress?(ctx: HitFanOutEventContext): void;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Live snapshot of the dispatched hit. `slot` is read fresh on every `state()`
|
|
560
|
+
* call so consumers see in-place updates without re-subscribing — that's the
|
|
561
|
+
* whole reason this type exists separate from event ctxs.
|
|
562
|
+
*/
|
|
563
|
+
interface HitFanOutActive {
|
|
564
|
+
slotIdx: number;
|
|
565
|
+
slot: HitSlot;
|
|
566
|
+
hitIndex: number;
|
|
567
|
+
position: {
|
|
568
|
+
x: number;
|
|
569
|
+
y: number;
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
interface HitFanOutState {
|
|
573
|
+
active: HitFanOutActive | null;
|
|
574
|
+
}
|
|
575
|
+
interface PickAtOptions {
|
|
576
|
+
/**
|
|
577
|
+
* Slot kinds participating.
|
|
578
|
+
* - `"point"`: point slots only (pickRadius / pickAxis).
|
|
579
|
+
* - `"region"`: region slots only (rect containment).
|
|
580
|
+
* - `"any"`: both, region containment winning ties.
|
|
581
|
+
* Default `"any"`.
|
|
582
|
+
*/
|
|
583
|
+
mode?: "point" | "region" | "any";
|
|
584
|
+
}
|
|
585
|
+
interface PickAtResult {
|
|
586
|
+
slotIdx: number;
|
|
587
|
+
slot: HitSlot;
|
|
588
|
+
hitIndex: number;
|
|
589
|
+
position: {
|
|
590
|
+
x: number;
|
|
591
|
+
y: number;
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
interface HitFanOutSpec {
|
|
595
|
+
manager: InteractionManager;
|
|
596
|
+
element: HTMLElement;
|
|
597
|
+
/**
|
|
598
|
+
* Base zIndex for slot hit-clouds. Each slot's cloud sits at
|
|
599
|
+
* `baseZIndex + slotIdx` so overlapping slots resolve in stable order.
|
|
600
|
+
* Defaults to `0` — pick a value above whatever else lives at that z.
|
|
601
|
+
*/
|
|
602
|
+
baseZIndex?: number;
|
|
603
|
+
}
|
|
604
|
+
interface HitFanOut {
|
|
605
|
+
/** Replace the active slot set (typically called once per pipeline run). */
|
|
606
|
+
sync(slots: readonly HitSlot[]): void;
|
|
607
|
+
/** Register an event subscriber. Returns an unsubscribe fn. */
|
|
608
|
+
subscribe(sub: HitFanOutSubscriber): () => void;
|
|
609
|
+
/** Live view of the currently dispatched hit. */
|
|
610
|
+
state(): HitFanOutState;
|
|
611
|
+
/** Subscribe to `state()` transitions (enter / leave / sync-induced). */
|
|
612
|
+
subscribeState(fn: (state: HitFanOutState) => void): () => void;
|
|
613
|
+
/** One-shot lookup at element-local CSS coordinates. Highest-z slot wins. */
|
|
614
|
+
pickAt(x: number, y: number, opts?: PickAtOptions): PickAtResult | null;
|
|
615
|
+
dispose(): void;
|
|
616
|
+
}
|
|
617
|
+
declare function createHitFanOut(spec: HitFanOutSpec): HitFanOut;
|
|
618
|
+
//#endregion
|
|
619
|
+
//#region src/interaction/selection.d.ts
|
|
620
|
+
/**
|
|
621
|
+
* Keyboard modifier policy for pointer-driven selection. Matches the common
|
|
622
|
+
* editor convention:
|
|
623
|
+
*
|
|
624
|
+
* - plain click → replace selection with `{ id }`
|
|
625
|
+
* - shift-click → toggle membership of `id`
|
|
626
|
+
* - meta/ctrl → remove `id`
|
|
627
|
+
*/
|
|
628
|
+
type SelectionMods = Pick<Mods, "shift" | "meta" | "ctrl">;
|
|
629
|
+
interface Selection<T> {
|
|
630
|
+
readonly size: number;
|
|
631
|
+
has(id: T): boolean;
|
|
632
|
+
values(): IterableIterator<T>;
|
|
633
|
+
/** Snapshot as an array. Useful when callers need a stable iteration order. */
|
|
634
|
+
toArray(): T[];
|
|
635
|
+
/** Single-selected id, if exactly one is selected; otherwise `undefined`. */
|
|
636
|
+
single(): T | undefined;
|
|
637
|
+
set(ids: Iterable<T>): void;
|
|
638
|
+
add(id: T): void;
|
|
639
|
+
delete(id: T): boolean;
|
|
640
|
+
clear(): void;
|
|
641
|
+
/**
|
|
642
|
+
* Apply a pointer gesture: plain → replace, shift → toggle, meta/ctrl → remove.
|
|
643
|
+
* Emits a change event only when the set actually changed.
|
|
644
|
+
*/
|
|
645
|
+
pressFromEvent(id: T, mods: SelectionMods): void;
|
|
646
|
+
/** Subscribe to change events. Returns an unsubscribe function. */
|
|
647
|
+
onChange(handler: () => void): () => void;
|
|
648
|
+
}
|
|
649
|
+
declare function createSelection<T>(initial?: Iterable<T>): Selection<T>;
|
|
650
|
+
//#endregion
|
|
651
|
+
//#region src/interaction/hover.d.ts
|
|
652
|
+
/**
|
|
653
|
+
* Tiny helper that collapses the `onHoverEnter / onHoverLeave` bookkeeping
|
|
654
|
+
* every interactive node repeats. One tracker per logical "group" of nodes
|
|
655
|
+
* (e.g. all shape nodes, all toolbar buttons). Each node registers an id —
|
|
656
|
+
* enter sets the current id, leave clears it only if still matching.
|
|
657
|
+
*/
|
|
658
|
+
interface HoverTracker<Id> {
|
|
659
|
+
/** Currently hovered id, or `null` if none. */
|
|
660
|
+
current(): Id | null;
|
|
661
|
+
/** True when the tracked current id equals `id`. */
|
|
662
|
+
isHovered(id: Id): boolean;
|
|
663
|
+
/**
|
|
664
|
+
* Returns `{ onHoverEnter, onHoverLeave }` handlers to spread into a
|
|
665
|
+
* {@link InteractionNodeSpec}. Works without stepping on any handlers the
|
|
666
|
+
* caller already defines; compose with your own if you need both.
|
|
667
|
+
*/
|
|
668
|
+
bind(id: Id): Pick<InteractionNodeSpec, "onHoverEnter" | "onHoverLeave">;
|
|
669
|
+
/**
|
|
670
|
+
* Directly set (or clear) the current id. Useful when the hover signal
|
|
671
|
+
* comes from somewhere other than an {@link InteractionNode} — e.g. the
|
|
672
|
+
* index emitted by a {@link PointCloudNode}'s `onHoverEnter`.
|
|
673
|
+
*/
|
|
674
|
+
set(id: Id | null): void;
|
|
675
|
+
/** Force the tracker to a specific state (e.g. on a global reset). */
|
|
676
|
+
reset(): void;
|
|
677
|
+
}
|
|
678
|
+
declare function trackHover<Id>(): HoverTracker<Id>;
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region src/scheduler.d.ts
|
|
681
|
+
interface Invalidator {
|
|
682
|
+
/** True if anything has invalidated since the last `clear()`. */
|
|
683
|
+
readonly dirty: boolean;
|
|
684
|
+
/** Mark dirty. Safe to call from event handlers. */
|
|
685
|
+
invalidate(): void;
|
|
686
|
+
/** Subscribe to invalidation edges. Useful for waking an on-demand RAF loop. */
|
|
687
|
+
onInvalidate(cb: () => void): () => void;
|
|
688
|
+
/** Clear the dirty flag. Call after a successful render. */
|
|
689
|
+
clear(): void;
|
|
690
|
+
/**
|
|
691
|
+
* Auto-invalidate whenever `source.onChange` fires. Returns an unsubscribe.
|
|
692
|
+
* The subscription is also released on `dispose()`.
|
|
693
|
+
*/
|
|
694
|
+
track<T extends {
|
|
695
|
+
onChange(cb: () => void): () => void;
|
|
696
|
+
}>(source: T): () => void;
|
|
697
|
+
/**
|
|
698
|
+
* Auto-invalidate on the given DOM events. Defaults to pointer, wheel, and
|
|
699
|
+
* keyboard edges (no `pointermove` — hover-sensitive demos opt in).
|
|
700
|
+
*/
|
|
701
|
+
trackElement(el: EventTarget, events?: readonly string[]): () => void;
|
|
702
|
+
/** Release every tracked subscription. */
|
|
703
|
+
dispose(): void;
|
|
704
|
+
}
|
|
705
|
+
declare function createInvalidator(initialDirty?: boolean): Invalidator;
|
|
706
|
+
interface AnimatedValue {
|
|
707
|
+
/** Current eased value. */
|
|
708
|
+
readonly value: number;
|
|
709
|
+
/** Target the value is easing toward. */
|
|
710
|
+
readonly target: number;
|
|
711
|
+
/** True while `value` has not yet reached `target` (within `epsilon`). */
|
|
712
|
+
readonly active: boolean;
|
|
713
|
+
/**
|
|
714
|
+
* Update the target. Invalidates the paired scheduler when the target
|
|
715
|
+
* actually differs from the current value. Pass `immediate=true` to snap.
|
|
716
|
+
*/
|
|
717
|
+
setTarget(value: number, immediate?: boolean): void;
|
|
718
|
+
/** Advance by `dt` seconds, easing `value` toward `target`. */
|
|
719
|
+
step(dt: number): void;
|
|
720
|
+
}
|
|
721
|
+
interface AnimateTowardOptions {
|
|
722
|
+
/** Starting value. Defaults to 0. */
|
|
723
|
+
initial?: number;
|
|
724
|
+
/**
|
|
725
|
+
* Easing rate. Higher = snappier. The per-frame interpolant is
|
|
726
|
+
* `1 - exp(-speed * dt)`, so behaviour is frame-rate independent.
|
|
727
|
+
*/
|
|
728
|
+
speed?: number;
|
|
729
|
+
/** Distance from target below which `active` flips to false. */
|
|
730
|
+
epsilon?: number;
|
|
731
|
+
/** Optional invalidator that is flagged whenever the target moves. */
|
|
732
|
+
invalidator?: Invalidator;
|
|
733
|
+
}
|
|
734
|
+
declare function animateToward(opts?: AnimateTowardOptions): AnimatedValue;
|
|
735
|
+
//#endregion
|
|
736
|
+
//#region src/interaction/crosshair.d.ts
|
|
737
|
+
interface CrosshairBounds {
|
|
738
|
+
x: number;
|
|
739
|
+
y: number;
|
|
740
|
+
width: number;
|
|
741
|
+
height: number;
|
|
742
|
+
}
|
|
743
|
+
type CrosshairAxis = "x" | "y" | "xy";
|
|
744
|
+
interface CrosshairStyle {
|
|
745
|
+
color?: Color;
|
|
746
|
+
width?: number;
|
|
747
|
+
/** 0..1 — additional alpha multiplier applied to color.a. Default 1. */
|
|
748
|
+
opacity?: number;
|
|
749
|
+
}
|
|
750
|
+
interface CrosshairOptions {
|
|
751
|
+
/** Which guide line(s) to draw. Default "x" (vertical line tracking cursor x). */
|
|
752
|
+
axis?: CrosshairAxis;
|
|
753
|
+
/** Region in layer coordinates within which the lines are drawn. */
|
|
754
|
+
bounds: () => CrosshairBounds;
|
|
755
|
+
style?: CrosshairStyle;
|
|
756
|
+
/**
|
|
757
|
+
* Optional snap transform applied before drawing. Returns the snapped point
|
|
758
|
+
* (e.g., to the nearest data x), or `null` to suppress drawing for that
|
|
759
|
+
* frame. The unsnapped point is still exposed via `rawPosition`.
|
|
760
|
+
*/
|
|
761
|
+
snap?: (p: Vec2) => Vec2 | null;
|
|
762
|
+
invalidator?: Invalidator;
|
|
763
|
+
}
|
|
764
|
+
interface Crosshair {
|
|
765
|
+
/** Snapped (or raw, when no snap) position currently displayed. Null when hidden. */
|
|
766
|
+
readonly position: Vec2 | null;
|
|
767
|
+
/** Raw position last passed to `setPosition`, before snap. Null when cleared. */
|
|
768
|
+
readonly rawPosition: Vec2 | null;
|
|
769
|
+
/** Set the cursor position. Pass `null` to hide the crosshair. */
|
|
770
|
+
setPosition(p: Vec2 | null): void;
|
|
771
|
+
draw(layer: Layer): void;
|
|
772
|
+
dispose(): void;
|
|
773
|
+
}
|
|
774
|
+
declare function createCrosshair(opts: CrosshairOptions): Crosshair;
|
|
775
|
+
//#endregion
|
|
776
|
+
//#region src/interaction/brush.d.ts
|
|
777
|
+
interface BrushRect {
|
|
778
|
+
x: number;
|
|
779
|
+
y: number;
|
|
780
|
+
width: number;
|
|
781
|
+
height: number;
|
|
782
|
+
}
|
|
783
|
+
type BrushAxis = "x" | "y" | "xy";
|
|
784
|
+
/**
|
|
785
|
+
* Named edge or corner on a brush rect — used by `beginResize()` to identify
|
|
786
|
+
* which side(s) of the rect follow the cursor during a resize gesture. The
|
|
787
|
+
* single-letter names are the sides ("n", "s", "e", "w"); two-letter names
|
|
788
|
+
* are the corners ("ne", "nw", "se", "sw").
|
|
789
|
+
*/
|
|
790
|
+
type BrushEdge = "n" | "s" | "e" | "w" | "ne" | "nw" | "se" | "sw";
|
|
791
|
+
interface BrushStyle {
|
|
792
|
+
fill?: Color;
|
|
793
|
+
stroke?: Color;
|
|
794
|
+
strokeWidth?: number;
|
|
795
|
+
}
|
|
796
|
+
interface BrushOptions {
|
|
797
|
+
/** Which axes the brush spans. Default `"xy"`. */
|
|
798
|
+
axis?: BrushAxis;
|
|
799
|
+
/** Region within which the brush is allowed; the rect is clamped to this. */
|
|
800
|
+
bounds: () => BrushRect;
|
|
801
|
+
style?: BrushStyle;
|
|
802
|
+
/** Fired when a brush gesture begins (after first update). */
|
|
803
|
+
onStart?(rect: BrushRect): void;
|
|
804
|
+
/** Fired on every position update. Receives the current normalized rect. */
|
|
805
|
+
onChange?(rect: BrushRect): void;
|
|
806
|
+
/**
|
|
807
|
+
* Fired on `end()` — after a successful drag. The committed rect (or null
|
|
808
|
+
* when the brush collapsed to zero area).
|
|
809
|
+
*/
|
|
810
|
+
onEnd?(rect: BrushRect | null): void;
|
|
811
|
+
/**
|
|
812
|
+
* Optional snap transform applied after axis-lock + bounds-clamp. Returns
|
|
813
|
+
* the snapped rect (e.g. with edges snapped to nearest data values). Runs
|
|
814
|
+
* for every gesture update — including `begin`, `update`, `setRect`, and
|
|
815
|
+
* the initial state of `beginResize` / `beginTranslate` — so the live rect
|
|
816
|
+
* always reflects snapped positions.
|
|
817
|
+
*/
|
|
818
|
+
snap?: (rect: BrushRect) => BrushRect;
|
|
819
|
+
invalidator?: Invalidator;
|
|
820
|
+
}
|
|
821
|
+
interface Brush {
|
|
822
|
+
/** Current normalized rect (or `null` when idle). */
|
|
823
|
+
readonly rect: BrushRect | null;
|
|
824
|
+
/** True between begin()/beginResize() and end()/cancel(). */
|
|
825
|
+
readonly active: boolean;
|
|
826
|
+
/** Start a brush at the given element-local coords. */
|
|
827
|
+
begin(x: number, y: number): void;
|
|
828
|
+
/**
|
|
829
|
+
* Begin a resize gesture on the existing rect. The opposite edge/corner is
|
|
830
|
+
* pinned; subsequent `update(x, y)` calls move the named edge/corner. No-op
|
|
831
|
+
* when no rect exists. Fires `onChange` (and eventually `onEnd`) like a
|
|
832
|
+
* regular brush gesture so callers see the resized rect through the same
|
|
833
|
+
* pipeline.
|
|
834
|
+
*/
|
|
835
|
+
beginResize(edge: BrushEdge, x: number, y: number): void;
|
|
836
|
+
/**
|
|
837
|
+
* Begin a translate gesture on the existing rect. The rect's size is held
|
|
838
|
+
* constant; subsequent `update(x, y)` calls move the rect by the cursor
|
|
839
|
+
* delta from `(x, y)`, clamped so the rect stays within bounds. No-op when
|
|
840
|
+
* no rect exists. Off-axis movement is ignored when `axis` is `"x"` / `"y"`
|
|
841
|
+
* (the rect already spans bounds on the locked axis). Fires `onChange` /
|
|
842
|
+
* `onEnd` like other gestures.
|
|
843
|
+
*/
|
|
844
|
+
beginTranslate(x: number, y: number): void;
|
|
845
|
+
/** Update the trailing corner of an active brush. No-op when idle. */
|
|
846
|
+
update(x: number, y: number): void;
|
|
847
|
+
/** Commit the active brush. Fires `onEnd` then settles into a static rect. */
|
|
848
|
+
end(): void;
|
|
849
|
+
/** Cancel an active brush and clear the rect. */
|
|
850
|
+
cancel(): void;
|
|
851
|
+
/** Imperatively set the rect (or `null` to clear). Skips begin/end callbacks. */
|
|
852
|
+
setRect(rect: BrushRect | null): void;
|
|
853
|
+
/** Push the rect into the layer. No-op when no rect is set. */
|
|
854
|
+
draw(layer: Layer): void;
|
|
855
|
+
dispose(): void;
|
|
856
|
+
}
|
|
857
|
+
declare function createBrush(opts: BrushOptions): Brush;
|
|
858
|
+
//#endregion
|
|
859
|
+
//#region src/interaction/tooltip.d.ts
|
|
860
|
+
interface TooltipAnchor {
|
|
861
|
+
/** Element-space x in CSS pixels. */
|
|
862
|
+
x: number;
|
|
863
|
+
/** Element-space y in CSS pixels (Y-down to match pointer events). */
|
|
864
|
+
y: number;
|
|
865
|
+
}
|
|
866
|
+
interface TooltipRow {
|
|
867
|
+
label?: string;
|
|
868
|
+
value: string;
|
|
869
|
+
swatch?: Color;
|
|
870
|
+
/** Override row text color. */
|
|
871
|
+
color?: Color;
|
|
872
|
+
/** Override row font size. */
|
|
873
|
+
fontSize?: number;
|
|
874
|
+
}
|
|
875
|
+
interface TooltipContent {
|
|
876
|
+
title?: string;
|
|
877
|
+
rows: readonly TooltipRow[];
|
|
878
|
+
}
|
|
879
|
+
type TooltipPlacement = "top" | "bottom" | "left" | "right" | "auto";
|
|
880
|
+
interface TooltipBounds {
|
|
881
|
+
x: number;
|
|
882
|
+
y: number;
|
|
883
|
+
width: number;
|
|
884
|
+
height: number;
|
|
885
|
+
}
|
|
886
|
+
interface TooltipMeasure {
|
|
887
|
+
(text: string, fontSize: number): {
|
|
888
|
+
width: number;
|
|
889
|
+
height: number;
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
interface TooltipStyle {
|
|
893
|
+
background?: Color;
|
|
894
|
+
border?: {
|
|
895
|
+
color: Color;
|
|
896
|
+
width: number;
|
|
897
|
+
};
|
|
898
|
+
cornerRadius?: number;
|
|
899
|
+
padding?: number;
|
|
900
|
+
rowGap?: number;
|
|
901
|
+
columnGap?: number;
|
|
902
|
+
swatchSize?: number;
|
|
903
|
+
fontSize?: number;
|
|
904
|
+
titleFontSize?: number;
|
|
905
|
+
textColor?: Color;
|
|
906
|
+
titleColor?: Color;
|
|
907
|
+
/** Gap between anchor and tooltip box in placement direction. Default 8. */
|
|
908
|
+
offset?: number;
|
|
909
|
+
}
|
|
910
|
+
interface TooltipOptions {
|
|
911
|
+
/** Default placement. "auto" tries top → bottom → right → left. Default "top". */
|
|
912
|
+
placement?: TooltipPlacement;
|
|
913
|
+
/** Bounds region for clamp/flip. Default: no clamp. */
|
|
914
|
+
bounds?: () => TooltipBounds;
|
|
915
|
+
/** Required: returns text size. Adapter over GlyphAtlas.measureText. */
|
|
916
|
+
measure: TooltipMeasure;
|
|
917
|
+
style?: TooltipStyle;
|
|
918
|
+
/** Hover-intent delay before showing (ms). Default 80. */
|
|
919
|
+
showDelay?: number;
|
|
920
|
+
/** Linger before hiding (ms). Default 100. */
|
|
921
|
+
hideDelay?: number;
|
|
922
|
+
/** Fade duration (ms). 0 disables fade. Default 120. */
|
|
923
|
+
fadeMs?: number;
|
|
924
|
+
/** Optional Invalidator: woken on visibility change and during fade/delay. */
|
|
925
|
+
invalidator?: Invalidator;
|
|
926
|
+
}
|
|
927
|
+
interface Tooltip {
|
|
928
|
+
/** Show with content at anchor. Subsequent calls update content+anchor. */
|
|
929
|
+
show(anchor: TooltipAnchor, content: TooltipContent): void;
|
|
930
|
+
/** Update only the anchor (cheaper than show when content is unchanged). */
|
|
931
|
+
move(anchor: TooltipAnchor): void;
|
|
932
|
+
/** Hide. Linger applies before fade-out. */
|
|
933
|
+
hide(): void;
|
|
934
|
+
/** Advance opacity + delays by dt seconds. */
|
|
935
|
+
step(dt: number): void;
|
|
936
|
+
/** Push tooltip into a UI/screen-space layer. No-op when fully hidden. */
|
|
937
|
+
draw(layer: Layer): void;
|
|
938
|
+
/** Logical visibility — what the caller most recently asked for. */
|
|
939
|
+
readonly visible: boolean;
|
|
940
|
+
/** Current animated opacity, 0..1. */
|
|
941
|
+
readonly opacity: number;
|
|
942
|
+
dispose(): void;
|
|
943
|
+
}
|
|
944
|
+
interface ResolvedStyle$1 {
|
|
945
|
+
background: Color;
|
|
946
|
+
border: {
|
|
947
|
+
color: Color;
|
|
948
|
+
width: number;
|
|
949
|
+
} | null;
|
|
950
|
+
cornerRadius: number;
|
|
951
|
+
padding: number;
|
|
952
|
+
rowGap: number;
|
|
953
|
+
columnGap: number;
|
|
954
|
+
swatchSize: number;
|
|
955
|
+
fontSize: number;
|
|
956
|
+
titleFontSize: number;
|
|
957
|
+
textColor: Color;
|
|
958
|
+
titleColor: Color;
|
|
959
|
+
offset: number;
|
|
960
|
+
}
|
|
961
|
+
interface TooltipRowLayout {
|
|
962
|
+
/** y of row top, relative to box's inner top-left. */
|
|
963
|
+
y: number;
|
|
964
|
+
/** Row height. */
|
|
965
|
+
height: number;
|
|
966
|
+
swatch: {
|
|
967
|
+
x: number;
|
|
968
|
+
y: number;
|
|
969
|
+
size: number;
|
|
970
|
+
} | null;
|
|
971
|
+
label: {
|
|
972
|
+
x: number;
|
|
973
|
+
y: number;
|
|
974
|
+
text: string;
|
|
975
|
+
fontSize: number;
|
|
976
|
+
} | null;
|
|
977
|
+
/** Right-aligned to inner width when label present, else left at column start. */
|
|
978
|
+
value: {
|
|
979
|
+
x: number;
|
|
980
|
+
y: number;
|
|
981
|
+
text: string;
|
|
982
|
+
fontSize: number;
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
interface TooltipLayout {
|
|
986
|
+
box: {
|
|
987
|
+
x: number;
|
|
988
|
+
y: number;
|
|
989
|
+
width: number;
|
|
990
|
+
height: number;
|
|
991
|
+
};
|
|
992
|
+
innerOrigin: {
|
|
993
|
+
x: number;
|
|
994
|
+
y: number;
|
|
995
|
+
};
|
|
996
|
+
title: {
|
|
997
|
+
x: number;
|
|
998
|
+
y: number;
|
|
999
|
+
text: string;
|
|
1000
|
+
fontSize: number;
|
|
1001
|
+
} | null;
|
|
1002
|
+
rows: TooltipRowLayout[];
|
|
1003
|
+
placement: Exclude<TooltipPlacement, "auto">;
|
|
1004
|
+
}
|
|
1005
|
+
declare function layoutTooltip(anchor: TooltipAnchor, content: TooltipContent, measure: TooltipMeasure, style: ResolvedStyle$1, placement: TooltipPlacement, bounds: TooltipBounds | null): TooltipLayout;
|
|
1006
|
+
declare function createTooltip(opts: TooltipOptions): Tooltip;
|
|
1007
|
+
//#endregion
|
|
1008
|
+
//#region src/interaction/menu.d.ts
|
|
1009
|
+
interface MenuAnchor {
|
|
1010
|
+
/** Element-space x in CSS pixels. */
|
|
1011
|
+
x: number;
|
|
1012
|
+
/** Element-space y in CSS pixels (Y-down to match pointer events). */
|
|
1013
|
+
y: number;
|
|
1014
|
+
}
|
|
1015
|
+
interface MenuItem {
|
|
1016
|
+
/** Stable identifier passed to `onAction`. Ignored for separators. */
|
|
1017
|
+
id: string;
|
|
1018
|
+
/** Display label. Ignored for separators. */
|
|
1019
|
+
label: string;
|
|
1020
|
+
/** Renders as a thin separator line; non-interactive. */
|
|
1021
|
+
separator?: boolean;
|
|
1022
|
+
/** Greyed out; hover/click ignored. */
|
|
1023
|
+
disabled?: boolean;
|
|
1024
|
+
/** Tinted with `dangerColor` (e.g. destructive actions). */
|
|
1025
|
+
danger?: boolean;
|
|
1026
|
+
/** Optional swatch rendered at the start of the row. */
|
|
1027
|
+
swatch?: Color;
|
|
1028
|
+
/** Optional right-aligned shortcut hint (e.g. "⌘D"). */
|
|
1029
|
+
kbd?: string;
|
|
1030
|
+
}
|
|
1031
|
+
interface MenuContent {
|
|
1032
|
+
items: readonly MenuItem[];
|
|
1033
|
+
}
|
|
1034
|
+
type MenuPlacement = "bottom-right" | "bottom-left" | "top-right" | "top-left" | "auto";
|
|
1035
|
+
interface MenuBounds {
|
|
1036
|
+
x: number;
|
|
1037
|
+
y: number;
|
|
1038
|
+
width: number;
|
|
1039
|
+
height: number;
|
|
1040
|
+
}
|
|
1041
|
+
interface MenuMeasure {
|
|
1042
|
+
(text: string, fontSize: number): {
|
|
1043
|
+
width: number;
|
|
1044
|
+
height: number;
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
interface MenuStyle {
|
|
1048
|
+
background?: Color;
|
|
1049
|
+
hoverBackground?: Color;
|
|
1050
|
+
border?: {
|
|
1051
|
+
color: Color;
|
|
1052
|
+
width: number;
|
|
1053
|
+
};
|
|
1054
|
+
cornerRadius?: number;
|
|
1055
|
+
paddingX?: number;
|
|
1056
|
+
paddingY?: number;
|
|
1057
|
+
itemHeight?: number;
|
|
1058
|
+
itemPaddingX?: number;
|
|
1059
|
+
fontSize?: number;
|
|
1060
|
+
textColor?: Color;
|
|
1061
|
+
disabledColor?: Color;
|
|
1062
|
+
dangerColor?: Color;
|
|
1063
|
+
separatorColor?: Color;
|
|
1064
|
+
separatorHeight?: number;
|
|
1065
|
+
swatchSize?: number;
|
|
1066
|
+
swatchGap?: number;
|
|
1067
|
+
kbdColor?: Color;
|
|
1068
|
+
/** Min width for the box. Default 160. */
|
|
1069
|
+
minWidth?: number;
|
|
1070
|
+
/** Gap between anchor point and box. Default 4. */
|
|
1071
|
+
offset?: number;
|
|
1072
|
+
}
|
|
1073
|
+
interface MenuOptions {
|
|
1074
|
+
/** Default placement. "auto" tries bottom-right → bottom-left → top-right → top-left. Default "bottom-right". */
|
|
1075
|
+
placement?: MenuPlacement;
|
|
1076
|
+
/** Bounds region for clamp/flip. Default: no clamp. */
|
|
1077
|
+
bounds?: () => MenuBounds;
|
|
1078
|
+
/** Required: returns text size. Adapter over GlyphAtlas.measureText. */
|
|
1079
|
+
measure: MenuMeasure;
|
|
1080
|
+
style?: MenuStyle;
|
|
1081
|
+
/** Fade duration (ms). 0 disables fade. Default 80. */
|
|
1082
|
+
fadeMs?: number;
|
|
1083
|
+
/** Optional Invalidator: woken on visibility change and during fade. */
|
|
1084
|
+
invalidator?: Invalidator;
|
|
1085
|
+
}
|
|
1086
|
+
interface Menu {
|
|
1087
|
+
/** Show with content at anchor. Subsequent calls update content+anchor. */
|
|
1088
|
+
show(anchor: MenuAnchor, content: MenuContent): void;
|
|
1089
|
+
/** Hide. Triggers fade-out. */
|
|
1090
|
+
hide(): void;
|
|
1091
|
+
/** Advance opacity by dt seconds. */
|
|
1092
|
+
step(dt: number): void;
|
|
1093
|
+
/** Push menu into a UI/screen-space layer. No-op when fully hidden. */
|
|
1094
|
+
draw(layer: Layer): void;
|
|
1095
|
+
/**
|
|
1096
|
+
* Update which item is hovered. Pass element-local CSS px; the menu resolves
|
|
1097
|
+
* to an item index internally. Pass `null` when the pointer leaves the menu
|
|
1098
|
+
* box entirely so the highlight clears.
|
|
1099
|
+
*/
|
|
1100
|
+
setHoverPosition(p: {
|
|
1101
|
+
x: number;
|
|
1102
|
+
y: number;
|
|
1103
|
+
} | null): void;
|
|
1104
|
+
/**
|
|
1105
|
+
* One-shot item lookup at element-local CSS px. Returns the item id (or null
|
|
1106
|
+
* if the point is outside the box, on a separator, or on a disabled item).
|
|
1107
|
+
*/
|
|
1108
|
+
pickItemAt(x: number, y: number): string | null;
|
|
1109
|
+
/**
|
|
1110
|
+
* Element-local CSS rect occupied by the menu box, or null when hidden.
|
|
1111
|
+
* Useful for binding an InteractionNode bounds to "the menu".
|
|
1112
|
+
*/
|
|
1113
|
+
getBounds(): {
|
|
1114
|
+
x: number;
|
|
1115
|
+
y: number;
|
|
1116
|
+
width: number;
|
|
1117
|
+
height: number;
|
|
1118
|
+
} | null;
|
|
1119
|
+
/** Logical visibility — what the caller most recently asked for. */
|
|
1120
|
+
readonly visible: boolean;
|
|
1121
|
+
/** Current animated opacity, 0..1. */
|
|
1122
|
+
readonly opacity: number;
|
|
1123
|
+
dispose(): void;
|
|
1124
|
+
}
|
|
1125
|
+
interface ResolvedStyle {
|
|
1126
|
+
background: Color;
|
|
1127
|
+
hoverBackground: Color;
|
|
1128
|
+
border: {
|
|
1129
|
+
color: Color;
|
|
1130
|
+
width: number;
|
|
1131
|
+
} | null;
|
|
1132
|
+
cornerRadius: number;
|
|
1133
|
+
paddingX: number;
|
|
1134
|
+
paddingY: number;
|
|
1135
|
+
itemHeight: number;
|
|
1136
|
+
itemPaddingX: number;
|
|
1137
|
+
fontSize: number;
|
|
1138
|
+
textColor: Color;
|
|
1139
|
+
disabledColor: Color;
|
|
1140
|
+
dangerColor: Color;
|
|
1141
|
+
separatorColor: Color;
|
|
1142
|
+
separatorHeight: number;
|
|
1143
|
+
swatchSize: number;
|
|
1144
|
+
swatchGap: number;
|
|
1145
|
+
kbdColor: Color;
|
|
1146
|
+
minWidth: number;
|
|
1147
|
+
offset: number;
|
|
1148
|
+
}
|
|
1149
|
+
interface MenuItemLayout {
|
|
1150
|
+
/** Index back into `content.items`. */
|
|
1151
|
+
itemIndex: number;
|
|
1152
|
+
/** Top-left of this row's hit region (full box width). */
|
|
1153
|
+
y: number;
|
|
1154
|
+
height: number;
|
|
1155
|
+
/** True if this row is a separator (non-interactive). */
|
|
1156
|
+
separator: boolean;
|
|
1157
|
+
/** True if this row is disabled. */
|
|
1158
|
+
disabled: boolean;
|
|
1159
|
+
swatch: {
|
|
1160
|
+
x: number;
|
|
1161
|
+
y: number;
|
|
1162
|
+
size: number;
|
|
1163
|
+
color: Color;
|
|
1164
|
+
} | null;
|
|
1165
|
+
label: {
|
|
1166
|
+
x: number;
|
|
1167
|
+
y: number;
|
|
1168
|
+
text: string;
|
|
1169
|
+
fontSize: number;
|
|
1170
|
+
} | null;
|
|
1171
|
+
kbd: {
|
|
1172
|
+
x: number;
|
|
1173
|
+
y: number;
|
|
1174
|
+
text: string;
|
|
1175
|
+
fontSize: number;
|
|
1176
|
+
} | null;
|
|
1177
|
+
}
|
|
1178
|
+
interface MenuLayout {
|
|
1179
|
+
box: {
|
|
1180
|
+
x: number;
|
|
1181
|
+
y: number;
|
|
1182
|
+
width: number;
|
|
1183
|
+
height: number;
|
|
1184
|
+
};
|
|
1185
|
+
items: MenuItemLayout[];
|
|
1186
|
+
placement: Exclude<MenuPlacement, "auto">;
|
|
1187
|
+
}
|
|
1188
|
+
declare function layoutMenu(anchor: MenuAnchor, content: MenuContent, measure: MenuMeasure, style: ResolvedStyle, placement: MenuPlacement, bounds: MenuBounds | null): MenuLayout;
|
|
1189
|
+
declare function createMenu(opts: MenuOptions): Menu;
|
|
1190
|
+
//#endregion
|
|
1191
|
+
//#region src/interaction/pointer-env.d.ts
|
|
1192
|
+
/** Returns true when the primary input is a coarse pointer (touch/stylus). */
|
|
1193
|
+
declare function isCoarsePointer(): boolean;
|
|
1194
|
+
/** Subscribe to coarse-pointer environment changes (e.g. keyboard attach). */
|
|
1195
|
+
declare function watchCoarsePointer(cb: (coarse: boolean) => void): () => void;
|
|
1196
|
+
//#endregion
|
|
1197
|
+
//#region src/layers/navigator.d.ts
|
|
1198
|
+
/**
|
|
1199
|
+
* Project a world coordinate to CSS pixels (canvas-relative) in the overview
|
|
1200
|
+
* frame. Passed to the `render` callback so it can emit a fixed-weight,
|
|
1201
|
+
* screen-space schematic instead of world geometry that would shrink with the
|
|
1202
|
+
* overview camera. See {@link NavigatorOptions.render}.
|
|
1203
|
+
*/
|
|
1204
|
+
type NavigatorProject = (worldX: number, worldY: number) => {
|
|
1205
|
+
x: number;
|
|
1206
|
+
y: number;
|
|
1207
|
+
};
|
|
1208
|
+
interface NavigatorIndicatorOptions {
|
|
1209
|
+
/** Indicator fill. Default: rgba(1, 1, 1, 0.08). */
|
|
1210
|
+
fill?: Color;
|
|
1211
|
+
/** Indicator stroke. Default: rgba(1, 1, 1, 0.85). */
|
|
1212
|
+
stroke?: Color;
|
|
1213
|
+
/** Stroke width in CSS pixels. Default: 1. */
|
|
1214
|
+
strokeWidth?: number;
|
|
1215
|
+
/** Corner radius in CSS pixels. Default: 0. */
|
|
1216
|
+
cornerRadius?: number;
|
|
1217
|
+
}
|
|
1218
|
+
interface NavigatorInteractionOptions {
|
|
1219
|
+
manager: InteractionManager;
|
|
1220
|
+
/** Drag inside the overview to pan the source. Default: true. */
|
|
1221
|
+
drag?: boolean;
|
|
1222
|
+
/** Press outside the indicator to recenter the source. Default: `"center"`. */
|
|
1223
|
+
click?: "center" | false;
|
|
1224
|
+
/**
|
|
1225
|
+
* Override the interaction-node bounds (CSS pixels, element-local). Default:
|
|
1226
|
+
* the overview's `absoluteFrame` (CSS px — the camera baseline is CSS px).
|
|
1227
|
+
*/
|
|
1228
|
+
bounds?: () => FrameRect;
|
|
1229
|
+
/** Interaction node z-index. Default: 50. */
|
|
1230
|
+
zIndex?: number;
|
|
1231
|
+
/**
|
|
1232
|
+
* Called with the desired camera state on drag/click instead of writing
|
|
1233
|
+
* directly to `source`. Use this when the source is driven by a smoothed
|
|
1234
|
+
* binding whose target would otherwise overwrite direct `source.setCamera`
|
|
1235
|
+
* writes next frame.
|
|
1236
|
+
*/
|
|
1237
|
+
onMove?: (camera: {
|
|
1238
|
+
x?: number;
|
|
1239
|
+
y?: number;
|
|
1240
|
+
}) => void;
|
|
1241
|
+
}
|
|
1242
|
+
interface NavigatorOptions {
|
|
1243
|
+
/** Camera viewport the user navigates in the main view. */
|
|
1244
|
+
source: CameraViewport;
|
|
1245
|
+
/** Camera viewport that hosts the navigator (position via its frame). */
|
|
1246
|
+
overview: CameraViewport;
|
|
1247
|
+
/** Layers to mirror through `overview` via {@link mirrorLayer}. */
|
|
1248
|
+
content?: Layer | readonly Layer[];
|
|
1249
|
+
/**
|
|
1250
|
+
* Callback path: populates a navigator-owned content layer each refresh. The
|
|
1251
|
+
* target is a UI (CSS-pixel) layer; use the supplied `project` to map world
|
|
1252
|
+
* coordinates into the overview frame and emit fixed-size primitives so the
|
|
1253
|
+
* minimap keeps constant visual weight regardless of the source camera's
|
|
1254
|
+
* zoom/pan. Re-run only on content change (and overview resize) — a source
|
|
1255
|
+
* pan/zoom moves only the indicator, never this layer.
|
|
1256
|
+
*/
|
|
1257
|
+
render?: (target: Layer, project: NavigatorProject) => void;
|
|
1258
|
+
indicator?: NavigatorIndicatorOptions;
|
|
1259
|
+
interaction?: NavigatorInteractionOptions;
|
|
1260
|
+
/**
|
|
1261
|
+
* Simplify the mirrored content to reduce CPU→GPU upload and draw-call cost
|
|
1262
|
+
* at overview scale. `true` enables the default preset; pass an object to
|
|
1263
|
+
* customize. Rebuild is triggered on {@link Navigator.refresh}.
|
|
1264
|
+
*/
|
|
1265
|
+
contentSimplify?: MirrorSimplify | true;
|
|
1266
|
+
/**
|
|
1267
|
+
* Retain the overview content in a STABLE GPU buffer via the renderer
|
|
1268
|
+
* (T-CULLNAV) so a static overview is not re-uploaded every frame — the v3
|
|
1269
|
+
* equivalent of v1's navigator RTT cache. Requires the `render`-callback
|
|
1270
|
+
* content path (the navigator owns that layer); has no effect on the
|
|
1271
|
+
* `content`-mirror path (mirrors are themselves a cheap re-emit). Set
|
|
1272
|
+
* `spatialIndex: true` to build a cell-binned grid for an overview large
|
|
1273
|
+
* enough to benefit from range-query culling. Re-retained on each
|
|
1274
|
+
* {@link Navigator.refresh} so content changes re-upload exactly once.
|
|
1275
|
+
*/
|
|
1276
|
+
cache?: NavigatorCacheOptions;
|
|
1277
|
+
}
|
|
1278
|
+
/** Renderer surface the navigator needs to retain its overview content. */
|
|
1279
|
+
interface NavigatorRetainHost {
|
|
1280
|
+
retainLayer(layer: Layer, key: string, opts?: {
|
|
1281
|
+
spatialIndex?: boolean;
|
|
1282
|
+
}): void;
|
|
1283
|
+
unretainLayer(layer: Layer): void;
|
|
1284
|
+
}
|
|
1285
|
+
interface NavigatorCacheOptions {
|
|
1286
|
+
/** The renderer (or any host exposing `retainLayer`/`unretainLayer`). */
|
|
1287
|
+
renderer: NavigatorRetainHost;
|
|
1288
|
+
/** Build a spatial-index grid over the retained overview. Default `false`. */
|
|
1289
|
+
spatialIndex?: boolean;
|
|
1290
|
+
}
|
|
1291
|
+
interface Navigator {
|
|
1292
|
+
/** Layers to add to your `renderer.render([...])` call, in z-order. */
|
|
1293
|
+
readonly layers: readonly Layer[];
|
|
1294
|
+
/** True while the navigator is actively dragging its indicator. */
|
|
1295
|
+
readonly interacting: boolean;
|
|
1296
|
+
/**
|
|
1297
|
+
* Re-derive content (when using the `render` callback) and the indicator.
|
|
1298
|
+
* Auto-runs on relevant viewport changes; call manually when the data the
|
|
1299
|
+
* `render` callback iterates has changed.
|
|
1300
|
+
*/
|
|
1301
|
+
refresh(): void;
|
|
1302
|
+
/** Tear down listeners and the interaction node. Mirrors stay valid. */
|
|
1303
|
+
dispose(): void;
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Build a Navigator: a secondary view of the same content with a rect that
|
|
1307
|
+
* tracks the source camera's visible region. Drag inside the overview pans
|
|
1308
|
+
* the source; pressing outside the indicator centers it.
|
|
1309
|
+
*
|
|
1310
|
+
* - `content`: mirrors layers via {@link mirrorLayer}, no re-emit cost.
|
|
1311
|
+
* - `render`: callback populates a navigator-owned layer for dynamic content;
|
|
1312
|
+
* called on viewport changes and via {@link Navigator.refresh}.
|
|
1313
|
+
*/
|
|
1314
|
+
declare function createNavigator(opts: NavigatorOptions): Navigator;
|
|
1315
|
+
//#endregion
|
|
1316
|
+
//#region src/layers/lod-layer.d.ts
|
|
1317
|
+
interface LodLevel {
|
|
1318
|
+
/**
|
|
1319
|
+
* Minimum camera zoom (inclusive) at which this level is active. Use `0`
|
|
1320
|
+
* to open-end the bottom.
|
|
1321
|
+
*/
|
|
1322
|
+
minZoom: number;
|
|
1323
|
+
/**
|
|
1324
|
+
* Maximum camera zoom (exclusive) at which this level is active. Use
|
|
1325
|
+
* `Infinity` to open-end the top.
|
|
1326
|
+
*/
|
|
1327
|
+
maxZoom: number;
|
|
1328
|
+
/**
|
|
1329
|
+
* Layers rendered when this band is active. Multiple allowed — useful when
|
|
1330
|
+
* a level mixes a world-space shape layer and a UI-space label layer.
|
|
1331
|
+
*/
|
|
1332
|
+
layers: readonly Layer[];
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Picks one of several pre-built layer sets based on the camera zoom. Lets
|
|
1336
|
+
* consumers swap between coarse/medium/fine representations without rebuilding
|
|
1337
|
+
* per frame.
|
|
1338
|
+
*
|
|
1339
|
+
* Levels are evaluated in the order given; the first whose `[minZoom, maxZoom)`
|
|
1340
|
+
* contains `zoom` is returned. If no band matches, `resolve()` returns `[]`.
|
|
1341
|
+
*
|
|
1342
|
+
* Usage in a frame loop:
|
|
1343
|
+
* ```ts
|
|
1344
|
+
* const layers = lod.resolve(camera.zoom);
|
|
1345
|
+
* renderer.render(layers);
|
|
1346
|
+
* ```
|
|
1347
|
+
*/
|
|
1348
|
+
declare class LodLayer {
|
|
1349
|
+
private readonly levels;
|
|
1350
|
+
constructor(levels: readonly LodLevel[]);
|
|
1351
|
+
/**
|
|
1352
|
+
* The first band whose `[minZoom, maxZoom)` contains `zoom`, or `null` if
|
|
1353
|
+
* none matches. Exposed for tests and debugging.
|
|
1354
|
+
*/
|
|
1355
|
+
selectLevel(zoom: number): LodLevel | null;
|
|
1356
|
+
/**
|
|
1357
|
+
* Layers active at `zoom`. Returns `[]` when no band matches.
|
|
1358
|
+
*/
|
|
1359
|
+
resolve(zoom: number): readonly Layer[];
|
|
1360
|
+
}
|
|
1361
|
+
/** Construct a {@link LodLayer} from a list of zoom bands. */
|
|
1362
|
+
declare function createLodLayer(levels: readonly LodLevel[]): LodLayer;
|
|
1363
|
+
//#endregion
|
|
1364
|
+
//#region src/layers/label-culler.d.ts
|
|
1365
|
+
interface LabelCullerOptions {
|
|
1366
|
+
/**
|
|
1367
|
+
* Grid cell size in the same coordinate unit as the AABBs passed to
|
|
1368
|
+
* {@link LabelCuller.place}. Pick a value close to the typical label size —
|
|
1369
|
+
* too small and buckets fragment; too large and each bucket scans more
|
|
1370
|
+
* candidates. Default: 64.
|
|
1371
|
+
*/
|
|
1372
|
+
cellSize?: number;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Greedy 2D overlap culler backed by a uniform hash grid.
|
|
1376
|
+
*
|
|
1377
|
+
* Feed AABBs in priority order (most-important first). Each call to
|
|
1378
|
+
* {@link place} returns `true` when the AABB does not overlap any previously
|
|
1379
|
+
* placed AABB, and adds it to the grid so later calls see it. Use to thin a
|
|
1380
|
+
* large set of labels down to a screen-filling, non-overlapping subset in
|
|
1381
|
+
* O(N) expected time.
|
|
1382
|
+
*/
|
|
1383
|
+
declare class LabelCuller {
|
|
1384
|
+
private readonly cellSize;
|
|
1385
|
+
private readonly buckets;
|
|
1386
|
+
constructor(options?: LabelCullerOptions);
|
|
1387
|
+
/** Drop all placed AABBs. Call between frames. */
|
|
1388
|
+
reset(): void;
|
|
1389
|
+
/** Number of AABBs currently placed. Diagnostic. */
|
|
1390
|
+
get size(): number;
|
|
1391
|
+
/**
|
|
1392
|
+
* Try to place `aabb`. Returns `true` if it does not overlap any previously
|
|
1393
|
+
* placed AABB (and records it for future queries). Returns `false` if it
|
|
1394
|
+
* overlaps any placed AABB (in which case the grid is unchanged).
|
|
1395
|
+
*/
|
|
1396
|
+
place(aabb: AABB): boolean;
|
|
1397
|
+
/**
|
|
1398
|
+
* Convenience for axis-aligned rectangles. `x, y` is the top-left corner.
|
|
1399
|
+
*/
|
|
1400
|
+
placeRect(x: number, y: number, width: number, height: number): boolean;
|
|
1401
|
+
}
|
|
1402
|
+
//#endregion
|
|
1403
|
+
//#region src/shared/viewport-aabb.d.ts
|
|
1404
|
+
/**
|
|
1405
|
+
* World-space AABB of the given camera viewport — the rectangle of world
|
|
1406
|
+
* coordinates currently visible — inflated by {@link CULL_AA_EPSILON}
|
|
1407
|
+
* CSS pixels (converted to world units via the current zoom) to absorb SDF
|
|
1408
|
+
* AA falloff. Shape AABBs are kept tight; this is the single place the pad
|
|
1409
|
+
* lives so it doesn't dominate world bounds on tiny-world content.
|
|
1410
|
+
*
|
|
1411
|
+
* Rotation is ignored (same caveat as `worldCullBounds`): under a rotated
|
|
1412
|
+
* camera the returned AABB is a conservative over-approximation of what is
|
|
1413
|
+
* on screen.
|
|
1414
|
+
*
|
|
1415
|
+
* Pass `rect` to query a sub-rect of the viewport (in the same CSS-pixel
|
|
1416
|
+
* space as `viewport.absoluteFrame`); omit it to query the full viewport.
|
|
1417
|
+
*/
|
|
1418
|
+
declare function viewportAabb(viewport: CameraViewport, rect?: FrameRect): AABB;
|
|
1419
|
+
//#endregion
|
|
1420
|
+
//#region src/damage/region-from-aabb.d.ts
|
|
1421
|
+
/** Default padding (CSS px) added on every side — covers SDF anti-alias + a thin halo. */
|
|
1422
|
+
declare const DEFAULT_REGION_PAD = 2;
|
|
1423
|
+
/**
|
|
1424
|
+
* Project one world-space AABB (`minX,minY,maxX,maxY`) through `viewProjection`
|
|
1425
|
+
* (layer-space → NDC) into a padded CSS-px {@link Bounds2D}. All FOUR corners are
|
|
1426
|
+
* transformed and re-bounded, so the result is a correct screen-space AABB even
|
|
1427
|
+
* when the camera is rotated (a rotated world rect's screen AABB is larger than
|
|
1428
|
+
* its two-corner projection). `pad` is added on every side.
|
|
1429
|
+
*/
|
|
1430
|
+
declare function projectAabbToBounds(minX: number, minY: number, maxX: number, maxY: number, viewProjection: Mat3, cssW: number, cssH: number, pad?: number): Bounds2D;
|
|
1431
|
+
/**
|
|
1432
|
+
* Union the projected CSS-px regions of the instances at `indices` into a SINGLE
|
|
1433
|
+
* padded {@link Bounds2D} — the focused-point halo case (one index → one padded
|
|
1434
|
+
* rect) and multi-instance unions (a whole highlighted series → one bounding
|
|
1435
|
+
* rect) both come out as one region.
|
|
1436
|
+
*
|
|
1437
|
+
* `aabbs` is the layer's flat per-instance world-AABB array (`UberPack.aabbs`):
|
|
1438
|
+
* instance `i` reads `aabbs[i*4 .. i*4+3]` as `minX,minY,maxX,maxY`. Indices out
|
|
1439
|
+
* of range (or a degenerate zero AABB, the cull sentinel for raw-appended spans)
|
|
1440
|
+
* are skipped. Returns `null` when no in-range index contributes — the caller
|
|
1441
|
+
* then has nothing to repaint.
|
|
1442
|
+
*
|
|
1443
|
+
* To get DISJOINT regions (several far-apart clusters) call this once per
|
|
1444
|
+
* cluster; a single call always returns the one enclosing rect.
|
|
1445
|
+
*/
|
|
1446
|
+
declare function unionRegionFromAabbs(aabbs: Float32Array, indices: Iterable<number>, viewProjection: Mat3, cssW: number, cssH: number, pad?: number): Bounds2D | null;
|
|
1447
|
+
//#endregion
|
|
1448
|
+
//#region src/text/raster-font.d.ts
|
|
1449
|
+
/** Per-glyph metrics in em units (1 em = `samplePx` Canvas pixels at measure time). */
|
|
1450
|
+
interface RasterGlyphMeasure {
|
|
1451
|
+
advance: number;
|
|
1452
|
+
/** Tight ink bbox in em units, Y-up. Zero box for whitespace / no-ink glyphs. */
|
|
1453
|
+
bbox: {
|
|
1454
|
+
minX: number;
|
|
1455
|
+
minY: number;
|
|
1456
|
+
maxX: number;
|
|
1457
|
+
maxY: number;
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
interface RasterFontOptions {
|
|
1461
|
+
/**
|
|
1462
|
+
* Em-style label for the public `family` field. Defaults to the CSS family.
|
|
1463
|
+
* Useful when callers want a stable label even if the resolved font changes.
|
|
1464
|
+
*/
|
|
1465
|
+
family?: string;
|
|
1466
|
+
/**
|
|
1467
|
+
* CSS pixel size used as the "1 em" reference when measuring glyph
|
|
1468
|
+
* metrics. Larger = more precise sub-pixel metrics. Default 64.
|
|
1469
|
+
*/
|
|
1470
|
+
samplePx?: number;
|
|
1471
|
+
/**
|
|
1472
|
+
* Optional CSS font weight passed to `ctx.font`. Default "normal".
|
|
1473
|
+
*/
|
|
1474
|
+
weight?: string | number;
|
|
1475
|
+
/**
|
|
1476
|
+
* Optional CSS font style passed to `ctx.font`. Default "normal".
|
|
1477
|
+
*/
|
|
1478
|
+
style?: string;
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* System-font glyph source. `family` is a CSS font-family string —
|
|
1482
|
+
* `"sans-serif"`, `"system-ui"`, `"monospace"`, or any specific family the
|
|
1483
|
+
* browser can resolve.
|
|
1484
|
+
*
|
|
1485
|
+
* Glyphs are measured lazily; the per-glyph SDF is rasterized only when the
|
|
1486
|
+
* atlas asks for it.
|
|
1487
|
+
*/
|
|
1488
|
+
declare class RasterFont implements TextFont {
|
|
1489
|
+
readonly family: string;
|
|
1490
|
+
readonly cssFamily: string;
|
|
1491
|
+
/** Em is unitless for raster fonts — already normalized to 1. */
|
|
1492
|
+
readonly unitsPerEm = 1;
|
|
1493
|
+
readonly ascender: number;
|
|
1494
|
+
readonly descender: number;
|
|
1495
|
+
readonly lineGap: number;
|
|
1496
|
+
readonly samplePx: number;
|
|
1497
|
+
readonly weight: string;
|
|
1498
|
+
readonly style: string;
|
|
1499
|
+
private readonly measureCanvas;
|
|
1500
|
+
private readonly measureCtx;
|
|
1501
|
+
private readonly measureCache;
|
|
1502
|
+
constructor(cssFamily: string, options?: RasterFontOptions);
|
|
1503
|
+
/** Always 0 — Canvas exposes no kerning data. */
|
|
1504
|
+
getKerning(_left: number, _right: number): number;
|
|
1505
|
+
/**
|
|
1506
|
+
* Em-relative tight ink bbox + advance for a codepoint. Cached on first
|
|
1507
|
+
* call. Returns null only for control codepoints we explicitly skip.
|
|
1508
|
+
*/
|
|
1509
|
+
measureGlyph(codepoint: number): RasterGlyphMeasure | null;
|
|
1510
|
+
/**
|
|
1511
|
+
* Render a single glyph into a fresh `Uint8Array` of RGBA SDF texels of
|
|
1512
|
+
* the requested atlas region size. Single-channel SDF is splat into R/G/B/A.
|
|
1513
|
+
*
|
|
1514
|
+
* `bbox` is the unpadded em-space ink bbox; `emPad` is the SDF half-range
|
|
1515
|
+
* in em units. The glyph is drawn so em-space (minX − emPad, maxY + emPad)
|
|
1516
|
+
* lands at canvas (0, 0).
|
|
1517
|
+
*/
|
|
1518
|
+
rasterizeGlyphRegion(codepoint: number, bbox: {
|
|
1519
|
+
minX: number;
|
|
1520
|
+
minY: number;
|
|
1521
|
+
maxX: number;
|
|
1522
|
+
maxY: number;
|
|
1523
|
+
}, emPad: number, width: number, height: number, atlasFontSize: number, pxRange: number): Uint8Array;
|
|
1524
|
+
private cssFont;
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Load a system font. Resolves immediately — Canvas font metrics are
|
|
1528
|
+
* available synchronously once the font is set on the context. Async
|
|
1529
|
+
* signature parity with `loadFont(url)` makes the two paths interchangeable
|
|
1530
|
+
* in callers.
|
|
1531
|
+
*
|
|
1532
|
+
* `family` is a CSS font-family value: `"sans-serif"`, `"system-ui"`,
|
|
1533
|
+
* `"monospace"`, or any specific family the browser can resolve.
|
|
1534
|
+
*/
|
|
1535
|
+
declare function loadSystemFont(family: string, options?: RasterFontOptions): Promise<RasterFont>;
|
|
1536
|
+
//#endregion
|
|
1537
|
+
//#region src/text/sdf-glyph-atlas.d.ts
|
|
1538
|
+
declare class SdfGlyphAtlas implements GlyphAtlas {
|
|
1539
|
+
readonly font: RasterFont;
|
|
1540
|
+
readonly atlasFontSize: number;
|
|
1541
|
+
readonly pxRange: number;
|
|
1542
|
+
private readonly _atlasSize;
|
|
1543
|
+
private readonly device;
|
|
1544
|
+
private readonly _texture;
|
|
1545
|
+
private readonly cache;
|
|
1546
|
+
/** Glyphs indexed by `atlasGlyphId`. Append-only; mirrors `MsdfGlyphAtlas`. */
|
|
1547
|
+
private readonly _glyphList;
|
|
1548
|
+
private _version;
|
|
1549
|
+
private measureCache;
|
|
1550
|
+
private shelfX;
|
|
1551
|
+
private shelfY;
|
|
1552
|
+
private shelfRowH;
|
|
1553
|
+
private _overflowWarned;
|
|
1554
|
+
private _destroyed;
|
|
1555
|
+
constructor(owner: GPUOwner, font: RasterFont, options?: GlyphAtlasOptions);
|
|
1556
|
+
get texture(): Texture;
|
|
1557
|
+
get atlasSize(): number;
|
|
1558
|
+
get glyphCount(): number;
|
|
1559
|
+
get version(): number;
|
|
1560
|
+
getGlyphById(id: number): MsdfGlyph | undefined;
|
|
1561
|
+
get destroyed(): boolean;
|
|
1562
|
+
get fillPercent(): number;
|
|
1563
|
+
getGlyph(codepoint: number): MsdfGlyph | null;
|
|
1564
|
+
measureText(text: string, options: MeasureTextOptions): TextMetrics;
|
|
1565
|
+
preload(text: string): void;
|
|
1566
|
+
destroy(): void;
|
|
1567
|
+
}
|
|
1568
|
+
//#endregion
|
|
1569
|
+
//#region src/text/shape.d.ts
|
|
1570
|
+
interface ShapeOptions {
|
|
1571
|
+
/** World units per em. */
|
|
1572
|
+
fontSize: number;
|
|
1573
|
+
align?: "left" | "center" | "right";
|
|
1574
|
+
/** Hard wrap width in world units. */
|
|
1575
|
+
maxWidth?: number;
|
|
1576
|
+
/** Line spacing in world units. Default: fontSize * 1.2. */
|
|
1577
|
+
lineHeight?: number;
|
|
1578
|
+
/**
|
|
1579
|
+
* Skip kerning, multi-line splits, and word wrap. Single-line, ASCII-fast.
|
|
1580
|
+
* Use when callers don't need exact kerned widths or `\n` handling.
|
|
1581
|
+
*/
|
|
1582
|
+
simple?: boolean;
|
|
1583
|
+
}
|
|
1584
|
+
interface ShapedGlyph {
|
|
1585
|
+
glyph: MsdfGlyph;
|
|
1586
|
+
/** World-space top-left of the glyph quad (Y-up). */
|
|
1587
|
+
worldX: number;
|
|
1588
|
+
worldY: number;
|
|
1589
|
+
/** World-space size of the glyph quad. */
|
|
1590
|
+
worldW: number;
|
|
1591
|
+
worldH: number;
|
|
1592
|
+
}
|
|
1593
|
+
interface ShapedBlock {
|
|
1594
|
+
glyphs: ShapedGlyph[];
|
|
1595
|
+
/** Content AABB in world space. Used for LOD culling + gradient extents. */
|
|
1596
|
+
bbox: {
|
|
1597
|
+
minX: number;
|
|
1598
|
+
minY: number;
|
|
1599
|
+
maxX: number;
|
|
1600
|
+
maxY: number;
|
|
1601
|
+
};
|
|
1602
|
+
/** Total laid-out height (lines × lineHeight). */
|
|
1603
|
+
height: number;
|
|
1604
|
+
/** Number of laid-out lines (after wrapping). */
|
|
1605
|
+
lineCount: number;
|
|
1606
|
+
/** Width of the widest laid-out line, in world units. */
|
|
1607
|
+
maxLineWidth: number;
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Shape `text` starting at world-space origin (x, y).
|
|
1611
|
+
*
|
|
1612
|
+
* Origin convention: y is the *top* of the first line. Subsequent lines are
|
|
1613
|
+
* below (higher y).
|
|
1614
|
+
*/
|
|
1615
|
+
declare function shapeText(text: string, font: TextFont, atlas: GlyphAtlas, x: number, y: number, options: ShapeOptions): ShapedBlock;
|
|
1616
|
+
//#endregion
|
|
1617
|
+
//#region src/shared/group.d.ts
|
|
1618
|
+
interface Group {
|
|
1619
|
+
/** Mutable — update between frames. */
|
|
1620
|
+
transform: Mat3;
|
|
1621
|
+
/** Parent group. Immutable after creation. */
|
|
1622
|
+
readonly group: Group | null;
|
|
1623
|
+
}
|
|
1624
|
+
//#endregion
|
|
1625
|
+
//#region src/text/text-pack.d.ts
|
|
1626
|
+
/**
|
|
1627
|
+
* Public text shape — what callers pass to `Layer.pushText`.
|
|
1628
|
+
*
|
|
1629
|
+
* `font` is optional: when omitted, the layer's (or renderer's) default font
|
|
1630
|
+
* is used. Effects (`outline`, `shadow`, `gradient`) all default off.
|
|
1631
|
+
*/
|
|
1632
|
+
interface TextShape {
|
|
1633
|
+
text: string;
|
|
1634
|
+
/** World-space x. Aligned per `align`. */
|
|
1635
|
+
x: number;
|
|
1636
|
+
/** World-space y of the top of the first line (Y-up). */
|
|
1637
|
+
y: number;
|
|
1638
|
+
font?: TextFont;
|
|
1639
|
+
/** Font size in world units per em. Default 16. */
|
|
1640
|
+
fontSize?: number;
|
|
1641
|
+
/** Fill color. Default BLACK. */
|
|
1642
|
+
color?: Color;
|
|
1643
|
+
outline?: TextOutline;
|
|
1644
|
+
shadow?: TextShadow;
|
|
1645
|
+
gradient?: TextGradient;
|
|
1646
|
+
align?: "left" | "center" | "right";
|
|
1647
|
+
/** Hard wrap width in world units. */
|
|
1648
|
+
maxWidth?: number;
|
|
1649
|
+
/** Line spacing in world units. Default fontSize * 1.2. */
|
|
1650
|
+
lineHeight?: number;
|
|
1651
|
+
/**
|
|
1652
|
+
* Skip kerning, multi-line splits, and wrap when laying out this shape.
|
|
1653
|
+
* Significant speedup for tight inner-loop text (tip/tick labels) where
|
|
1654
|
+
* exact kerned widths and `\n` handling aren't needed.
|
|
1655
|
+
*/
|
|
1656
|
+
simple?: boolean;
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Layout metrics returned by `Layer.pushText` / `TextPack.pushText`.
|
|
1660
|
+
*
|
|
1661
|
+
* Use these to advance a layout cursor without guessing line counts.
|
|
1662
|
+
*
|
|
1663
|
+
* - `width` / `height`: tight content AABB extents (ink bounds).
|
|
1664
|
+
* - `layoutHeight`: `lineCount * lineHeight` — what to advance by when
|
|
1665
|
+
* stacking blocks vertically. Includes line gap; never zero for non-empty
|
|
1666
|
+
* input, even when no glyphs render (e.g. all-whitespace).
|
|
1667
|
+
* - `bbox`: the content AABB itself, in world space.
|
|
1668
|
+
*/
|
|
1669
|
+
interface TextBlockMetrics {
|
|
1670
|
+
width: number;
|
|
1671
|
+
height: number;
|
|
1672
|
+
layoutHeight: number;
|
|
1673
|
+
lineCount: number;
|
|
1674
|
+
bbox: {
|
|
1675
|
+
minX: number;
|
|
1676
|
+
minY: number;
|
|
1677
|
+
maxX: number;
|
|
1678
|
+
maxY: number;
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
/** A range of glyph instances in the pack, plus the group they belong to. */
|
|
1682
|
+
interface TextDrawCommand {
|
|
1683
|
+
/** First glyph instance index. */
|
|
1684
|
+
start: number;
|
|
1685
|
+
/** Glyph count for this command. */
|
|
1686
|
+
count: number;
|
|
1687
|
+
/** Group transform applied to all glyphs in this command. */
|
|
1688
|
+
group: Group | null;
|
|
1689
|
+
/** Block AABB in world space — used for cull and gradient extents. */
|
|
1690
|
+
bbox: {
|
|
1691
|
+
minX: number;
|
|
1692
|
+
minY: number;
|
|
1693
|
+
maxX: number;
|
|
1694
|
+
maxY: number;
|
|
1695
|
+
};
|
|
1696
|
+
/**
|
|
1697
|
+
* Original text shape input. Retained alongside the GPU-shaped glyph stream
|
|
1698
|
+
* so non-GPU consumers (SVG export, accessibility, copy-paste) can recover
|
|
1699
|
+
* the source string + style without re-parsing the packed data.
|
|
1700
|
+
*/
|
|
1701
|
+
source: TextShape;
|
|
1702
|
+
}
|
|
1703
|
+
interface TextPackOptions {
|
|
1704
|
+
initialGlyphCapacity?: number;
|
|
1705
|
+
}
|
|
1706
|
+
declare class TextPack {
|
|
1707
|
+
readonly atlas: GlyphAtlas;
|
|
1708
|
+
readonly styles: TextStyleTable;
|
|
1709
|
+
private readonly root;
|
|
1710
|
+
private instanceData;
|
|
1711
|
+
private instanceU32;
|
|
1712
|
+
private _glyphCount;
|
|
1713
|
+
private _commands;
|
|
1714
|
+
private _version;
|
|
1715
|
+
private gpuBuf;
|
|
1716
|
+
private gpuCapacity;
|
|
1717
|
+
private uploadedVersion;
|
|
1718
|
+
private dataBindGroup;
|
|
1719
|
+
private dataBindGlyphBuf;
|
|
1720
|
+
private dataBindStyleBuf;
|
|
1721
|
+
private shapeCache;
|
|
1722
|
+
private shapeScratch;
|
|
1723
|
+
constructor(owner: GPUOwner, atlas: GlyphAtlas, options?: TextPackOptions);
|
|
1724
|
+
/** Number of glyph instances currently packed. */
|
|
1725
|
+
get glyphCount(): number;
|
|
1726
|
+
/** Mutates as the buffer grows; consumer should re-bind after each frame. */
|
|
1727
|
+
get instanceBuffer(): Float32Array;
|
|
1728
|
+
get commands(): readonly TextDrawCommand[];
|
|
1729
|
+
/** Increments whenever any push mutates the pack. */
|
|
1730
|
+
get version(): number;
|
|
1731
|
+
/**
|
|
1732
|
+
* Append one text block. The shape's `font` overrides the pack atlas only
|
|
1733
|
+
* if it matches the atlas's font; otherwise the override is ignored (v1
|
|
1734
|
+
* limitation — multi-font requires multiple layers).
|
|
1735
|
+
*/
|
|
1736
|
+
pushText(shape: TextShape, group?: Group | null): TextBlockMetrics;
|
|
1737
|
+
addTexts(shapes: readonly TextShape[], group?: Group | null): this;
|
|
1738
|
+
/**
|
|
1739
|
+
* Lookup or shape (and cache) the origin-relative glyph stream for a single
|
|
1740
|
+
* line of text. Cache is per (fontSize, align, kerning, text); entries are
|
|
1741
|
+
* stable across atlas growth (glyph metrics are immutable once allocated).
|
|
1742
|
+
*/
|
|
1743
|
+
private getShapedEntry;
|
|
1744
|
+
/** Drop all glyphs and styles. Retains allocated capacity. */
|
|
1745
|
+
clear(): this;
|
|
1746
|
+
/** Upload the dirty style table to GPU. Called by the renderer pre-draw. */
|
|
1747
|
+
flush(): void;
|
|
1748
|
+
/**
|
|
1749
|
+
* Upload the per-glyph instance buffer to the GPU when its version changed
|
|
1750
|
+
* since the last upload. Allocates / grows the GPU buffer as needed.
|
|
1751
|
+
* No-op when no glyphs are packed.
|
|
1752
|
+
*/
|
|
1753
|
+
flushInstances(): void;
|
|
1754
|
+
/**
|
|
1755
|
+
* Combined glyphs+styles bind group (matches `TextDataLayout`). Null until
|
|
1756
|
+
* first flush. Rebuilt lazily when either underlying buffer is reallocated.
|
|
1757
|
+
*/
|
|
1758
|
+
get bindGroup(): TgpuBindGroup | null;
|
|
1759
|
+
destroy(): void;
|
|
1760
|
+
private ensureGpuCapacity;
|
|
1761
|
+
}
|
|
1762
|
+
//#endregion
|
|
1763
|
+
//#region src/math/bezier.d.ts
|
|
1764
|
+
/**
|
|
1765
|
+
* Evaluate a cubic Bezier at parameter `t`. `t` is not clamped; pass a value
|
|
1766
|
+
* outside `[0, 1]` to extrapolate the curve (rarely useful — the tight AABB
|
|
1767
|
+
* assumes `t ∈ [0, 1]`).
|
|
1768
|
+
*/
|
|
1769
|
+
declare function cubicEval(p0: Vec2, p1: Vec2, p2: Vec2, p3: Vec2, t: number): Vec2;
|
|
1770
|
+
/**
|
|
1771
|
+
* Exact axis-aligned bounding box of a cubic Bezier on `t ∈ [0, 1]`.
|
|
1772
|
+
*
|
|
1773
|
+
* Starts from the endpoint hull and expands for each real root of `B'(t) = 0`
|
|
1774
|
+
* in the open interval. `B'(t) = 3(α t² + 2β t + γ)` per axis, with
|
|
1775
|
+
* α = -p0 + 3p1 - 3p2 + p3
|
|
1776
|
+
* β = p0 - 2p1 + p2
|
|
1777
|
+
* γ = -p0 + p1
|
|
1778
|
+
* — solve `α t² + 2β t + γ = 0` per axis.
|
|
1779
|
+
*
|
|
1780
|
+
* For axis-aligned-tangent cubics (phylo's case: p1.y = p0.y, p2.y = p3.y),
|
|
1781
|
+
* the derivative roots land at the endpoints and the returned box equals
|
|
1782
|
+
* the endpoint rect exactly.
|
|
1783
|
+
*/
|
|
1784
|
+
declare function cubicAABB(p0: Vec2, p1: Vec2, p2: Vec2, p3: Vec2): AABB;
|
|
1785
|
+
//#endregion
|
|
1786
|
+
//#region src/math/contrast.d.ts
|
|
1787
|
+
/** WCAG relative luminance of an sRGB color (channels in `[0, 1]`). */
|
|
1788
|
+
declare function relativeLuminance(c: Color): number;
|
|
1789
|
+
/** WCAG contrast ratio between two colors. Symmetric; result is `[1, 21]`. */
|
|
1790
|
+
declare function contrastRatio(a: Color, b: Color): number;
|
|
1791
|
+
type WcagLevel = "AA" | "AAA";
|
|
1792
|
+
interface WcagTextOptions {
|
|
1793
|
+
/** Font size in CSS pixels. Defaults to 14 (treated as "normal" body text). */
|
|
1794
|
+
fontSizePx?: number;
|
|
1795
|
+
/** Whether the text is rendered with bold weight. */
|
|
1796
|
+
bold?: boolean;
|
|
1797
|
+
/**
|
|
1798
|
+
* Conformance level. `"AA"` / `"AAA"` apply the WCAG thresholds with the
|
|
1799
|
+
* large-text exception. A raw number is treated as an absolute minimum
|
|
1800
|
+
* ratio with no large-text relaxation — useful when callers want a single
|
|
1801
|
+
* fine-grained target across all label sizes. Default `"AA"`.
|
|
1802
|
+
*/
|
|
1803
|
+
level?: WcagLevel | number;
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Minimum contrast ratio required by WCAG for the given text. Large text gets
|
|
1807
|
+
* the relaxed threshold (3:1 AA / 4.5:1 AAA); otherwise normal text needs
|
|
1808
|
+
* 4.5:1 AA / 7:1 AAA.
|
|
1809
|
+
*/
|
|
1810
|
+
declare function wcagMinContrast(opts?: WcagTextOptions): number;
|
|
1811
|
+
/** Whether `fg` against `bg` meets the WCAG threshold for the given text. */
|
|
1812
|
+
declare function meetsContrast(fg: Color, bg: Color, opts?: WcagTextOptions): boolean;
|
|
1813
|
+
/**
|
|
1814
|
+
* Find a color close to `target` that meets `minRatio` contrast against
|
|
1815
|
+
* `fixed`. Preserves hue + saturation; nudges lightness in steps of `0.01`,
|
|
1816
|
+
* picking the smallest L-shift that works. Falls back to black or white —
|
|
1817
|
+
* whichever has more contrast — if no nearby shade qualifies.
|
|
1818
|
+
*/
|
|
1819
|
+
declare function findAccessibleColor(target: Color, fixed: Color, minRatio: number): Color;
|
|
1820
|
+
/**
|
|
1821
|
+
* Find the color closest to `target` whose contrast against `fixed` lands
|
|
1822
|
+
* *at* `ratio` (not just `>=`). Used to equalize a group of labels so they
|
|
1823
|
+
* all carry the same visual weight regardless of their starting color.
|
|
1824
|
+
*
|
|
1825
|
+
* Hue and saturation are preserved; lightness is swept and the L-value
|
|
1826
|
+
* minimizing `|contrast - ratio|` is selected. When two L-values tie on
|
|
1827
|
+
* contrast error, the one closer to the original lightness wins.
|
|
1828
|
+
*/
|
|
1829
|
+
declare function colorAtContrast(target: Color, fixed: Color, ratio: number): Color;
|
|
1830
|
+
//#endregion
|
|
1831
|
+
//#region src/math/oklab.d.ts
|
|
1832
|
+
type BlendSpace = "srgb" | "linear-rgb" | "oklab" | "oklch" | "hsl";
|
|
1833
|
+
interface Lab {
|
|
1834
|
+
L: number;
|
|
1835
|
+
a: number;
|
|
1836
|
+
b: number;
|
|
1837
|
+
}
|
|
1838
|
+
declare function srgbToOklab(c: Color): Lab;
|
|
1839
|
+
declare function oklabToSrgb(lab: Lab, alpha?: number): Color;
|
|
1840
|
+
interface Lch {
|
|
1841
|
+
L: number;
|
|
1842
|
+
C: number;
|
|
1843
|
+
/** Hue in turns (`[0, 1)`), to match the rest of the math/color API. */
|
|
1844
|
+
h: number;
|
|
1845
|
+
}
|
|
1846
|
+
declare function oklabToOklch(lab: Lab): Lch;
|
|
1847
|
+
declare function oklchToOklab(lch: Lch): Lab;
|
|
1848
|
+
/** Shorthand: interpolate two sRGB colors through OKLab. The default for
|
|
1849
|
+
* `transition<Color>` and `tweenTo<Color>`. Alpha lerps linearly. */
|
|
1850
|
+
declare function lerpColorOklab(a: Color, b: Color, t: number): Color;
|
|
1851
|
+
/** Shorthand: naive sRGB channel lerp. Faster but visually muddier. */
|
|
1852
|
+
declare function lerpColorRgb(a: Color, b: Color, t: number): Color;
|
|
1853
|
+
/**
|
|
1854
|
+
* Interpolate two colors `t` ∈ `[0, 1]` of the way through the chosen color
|
|
1855
|
+
* space. Use `oklab` (default candidate) or `oklch` for visually clean
|
|
1856
|
+
* gradients; `oklch` preserves hue along the path which is great for
|
|
1857
|
+
* staying "in family" but can pass through invalid colors at high chroma —
|
|
1858
|
+
* the result is gamut-clipped on conversion back to sRGB.
|
|
1859
|
+
*
|
|
1860
|
+
* Spaces:
|
|
1861
|
+
* - `srgb` — naive channel-wise lerp; cheapest, often muddy.
|
|
1862
|
+
* - `linear-rgb` — physically correct mixing; brighter midpoints than sRGB.
|
|
1863
|
+
* - `oklab` — perceptually uniform, recommended default.
|
|
1864
|
+
* - `oklch` — OKLab in polar form; preserves hue along the path.
|
|
1865
|
+
* - `hsl` — HSL polar lerp; legacy, can desaturate.
|
|
1866
|
+
*/
|
|
1867
|
+
declare function lerpInSpace(a: Color, b: Color, t: number, space: BlendSpace): Color;
|
|
1868
|
+
//#endregion
|
|
1869
|
+
//#region src/math/apca.d.ts
|
|
1870
|
+
type ApcaPolarity = "normal" | "reverse";
|
|
1871
|
+
/**
|
|
1872
|
+
* Compute APCA Lc for `fg` text against `bg` background.
|
|
1873
|
+
*
|
|
1874
|
+
* Returns a signed value: positive for normal polarity (dark text on light
|
|
1875
|
+
* bg), negative for reverse polarity (light text on dark bg). Magnitude is
|
|
1876
|
+
* the perceptual contrast — Lc 75 is the conventional body-text minimum.
|
|
1877
|
+
*
|
|
1878
|
+
* Returns 0 for foreground/background pairs too close to be readable
|
|
1879
|
+
* (|Y_bg − Y_fg| below the soft-clip floor).
|
|
1880
|
+
*/
|
|
1881
|
+
declare function apcaContrast(fg: Color, bg: Color): number;
|
|
1882
|
+
/** Polarity of `fg` against `bg` — which side of zero APCA Lc would land on. */
|
|
1883
|
+
declare function apcaPolarity(fg: Color, bg: Color): ApcaPolarity;
|
|
1884
|
+
/**
|
|
1885
|
+
* Minimum |Lc| recommended for `fontSizePx` at `weight` per APCA's font
|
|
1886
|
+
* lookup table. Body text at 14–16px regular (weight 400) → ~75–90.
|
|
1887
|
+
*
|
|
1888
|
+
* Falls back to the closest tabulated row. For size/weight combinations
|
|
1889
|
+
* APCA marks "do not use" (very thin text at small sizes), returns the
|
|
1890
|
+
* strictest recommended value (Lc 100) — the resolver should warn anyway.
|
|
1891
|
+
*/
|
|
1892
|
+
declare function apcaFontLookup(fontSizePx: number, weight?: number): number;
|
|
1893
|
+
/**
|
|
1894
|
+
* Find a color near `target` whose APCA |Lc| against `bg` meets `minLc`.
|
|
1895
|
+
* Preserves hue + saturation; nudges HSL lightness in steps of 0.01,
|
|
1896
|
+
* returning the smallest L-shift that qualifies. Falls back to BLACK or
|
|
1897
|
+
* WHITE — whichever yields larger |Lc| — if no nearby shade works.
|
|
1898
|
+
*/
|
|
1899
|
+
declare function findApcaColor(target: Color, bg: Color, minLc: number): Color;
|
|
1900
|
+
/**
|
|
1901
|
+
* Find the color closest to `target` whose APCA |Lc| against `bg` lands
|
|
1902
|
+
* *at* `lc` (not just `>=`). Used to equalize a group of labels so they
|
|
1903
|
+
* carry the same perceptual weight regardless of starting color.
|
|
1904
|
+
*
|
|
1905
|
+
* Polarity is auto-selected: whichever side of `bg` (lighter or darker)
|
|
1906
|
+
* minimizes the L-shift from `target`. Hue and saturation preserved.
|
|
1907
|
+
*/
|
|
1908
|
+
declare function colorAtApca(target: Color, bg: Color, lc: number): Color;
|
|
1909
|
+
//#endregion
|
|
1910
|
+
//#region src/math/scalar.d.ts
|
|
1911
|
+
declare const TAU: number;
|
|
1912
|
+
declare const PI: number;
|
|
1913
|
+
declare const HALF_PI: number;
|
|
1914
|
+
declare const DEG_TO_RAD: number;
|
|
1915
|
+
declare const RAD_TO_DEG: number;
|
|
1916
|
+
declare function clamp(value: number, min: number, max: number): number;
|
|
1917
|
+
declare function clamp01(value: number): number;
|
|
1918
|
+
declare function lerp(a: number, b: number, t: number): number;
|
|
1919
|
+
/** Inverse of `lerp`: where does `value` sit between `a` and `b`? */
|
|
1920
|
+
declare function inverseLerp(a: number, b: number, value: number): number;
|
|
1921
|
+
/** Map `value` from `[a0, a1]` to `[b0, b1]` (unclamped). */
|
|
1922
|
+
declare function remap(value: number, a0: number, a1: number, b0: number, b1: number): number;
|
|
1923
|
+
/** Hermite smoothstep. `t` is clamped to `[edge0, edge1]`. */
|
|
1924
|
+
declare function smoothstep(edge0: number, edge1: number, value: number): number;
|
|
1925
|
+
/** Ken Perlin's 5th-order smoothstep — zero 1st & 2nd derivatives at edges. */
|
|
1926
|
+
declare function smootherstep(edge0: number, edge1: number, value: number): number;
|
|
1927
|
+
declare function degToRad(deg: number): number;
|
|
1928
|
+
declare function radToDeg(rad: number): number;
|
|
1929
|
+
/** Wraps `value` into `[0, modulus)` (handles negatives, unlike `%`). */
|
|
1930
|
+
declare function wrap(value: number, modulus: number): number;
|
|
1931
|
+
declare function sign(value: number): number;
|
|
1932
|
+
declare function approxEqual(a: number, b: number, epsilon?: number): boolean;
|
|
1933
|
+
//#endregion
|
|
1934
|
+
//#region src/math/random.d.ts
|
|
1935
|
+
type Rng = () => number;
|
|
1936
|
+
/** Mulberry32 — 32-bit, period 2^32. Tiny, fast, good distribution. */
|
|
1937
|
+
declare function mulberry32(seed: number): Rng;
|
|
1938
|
+
/** SplitMix32 — used to seed other generators. Avalanches small seeds well. */
|
|
1939
|
+
declare function splitmix32(seed: number): Rng;
|
|
1940
|
+
/** xorshift32 — minimal, period 2^32 - 1. Fastest of the bunch. */
|
|
1941
|
+
declare function xorshift32(seed: number): Rng;
|
|
1942
|
+
/** SFC32 — 128-bit state, period ~2^128. The default for high-quality streams. */
|
|
1943
|
+
declare function sfc32(seedA: number, seedB?: number, seedC?: number, seedD?: number): Rng;
|
|
1944
|
+
/** Uniform float in `[min, max)`. */
|
|
1945
|
+
declare function uniform(rng: Rng, min: number, max: number): number;
|
|
1946
|
+
/** Uniform integer in `[min, max]` (inclusive). */
|
|
1947
|
+
declare function uniformInt(rng: Rng, min: number, max: number): number;
|
|
1948
|
+
/** Standard normal (Box–Muller). Pass `mean`/`std` to shift/scale. */
|
|
1949
|
+
declare function gaussian(rng: Rng, mean?: number, std?: number): number;
|
|
1950
|
+
/** Exponential with rate `lambda` (mean = 1/lambda). */
|
|
1951
|
+
declare function exponential(rng: Rng, lambda?: number): number;
|
|
1952
|
+
/** Log-normal — exponentiated gaussian. `mu`/`sigma` are on the log scale. */
|
|
1953
|
+
declare function logNormal(rng: Rng, mu?: number, sigma?: number): number;
|
|
1954
|
+
/** Bates distribution — mean of `n` uniforms. `n=1` is uniform; large `n` ≈ normal. */
|
|
1955
|
+
declare function bates(rng: Rng, n?: number): number;
|
|
1956
|
+
/** Triangular distribution on `[min, max]` peaked at `mode`. */
|
|
1957
|
+
declare function triangular(rng: Rng, min: number, max: number, mode: number): number;
|
|
1958
|
+
/** Pick one element uniformly from `arr`. Throws on empty input. */
|
|
1959
|
+
declare function pick<T>(rng: Rng, arr: readonly T[]): T;
|
|
1960
|
+
/** Fisher–Yates shuffle. Returns a new array — does not mutate input. */
|
|
1961
|
+
declare function shuffle<T>(rng: Rng, arr: readonly T[]): T[];
|
|
1962
|
+
/** Sample `count` distinct elements without replacement. */
|
|
1963
|
+
declare function sample<T>(rng: Rng, arr: readonly T[], count: number): T[];
|
|
1964
|
+
/**
|
|
1965
|
+
* Pick by weight. `weights[i]` corresponds to `arr[i]`. Weights need not sum to 1.
|
|
1966
|
+
* O(n) per call — fine for small arrays. For high-volume sampling, build an
|
|
1967
|
+
* alias table externally.
|
|
1968
|
+
*/
|
|
1969
|
+
declare function weighted<T>(rng: Rng, arr: readonly T[], weights: readonly number[]): T;
|
|
1970
|
+
/** Uniform point on a unit circle. */
|
|
1971
|
+
declare function onUnitCircle(rng: Rng): [number, number];
|
|
1972
|
+
/** Uniform point inside a unit disk (square-root trick — preserves density). */
|
|
1973
|
+
declare function inUnitDisk(rng: Rng): [number, number];
|
|
1974
|
+
//#endregion
|
|
1975
|
+
//#region src/interactions.d.ts
|
|
1976
|
+
interface FlingOptions {
|
|
1977
|
+
/**
|
|
1978
|
+
* Exponential velocity decay rate in 1/s. Higher settles faster. Default 4.
|
|
1979
|
+
* Travel distance after release is roughly `velocity / friction`.
|
|
1980
|
+
*/
|
|
1981
|
+
friction?: number;
|
|
1982
|
+
/**
|
|
1983
|
+
* Minimum release velocity (px/s) needed to start a fling. Below this the
|
|
1984
|
+
* release is treated as "stop here". Default 50.
|
|
1985
|
+
*/
|
|
1986
|
+
minVelocity?: number;
|
|
1987
|
+
/**
|
|
1988
|
+
* Sample window (seconds) the velocity tracker uses to estimate release
|
|
1989
|
+
* velocity. Shorter = more reactive / noisier. Default 0.08.
|
|
1990
|
+
*/
|
|
1991
|
+
windowSeconds?: number;
|
|
1992
|
+
}
|
|
1993
|
+
interface BindViewportOptions {
|
|
1994
|
+
/** Enable pointer drag pan. Default: true. */
|
|
1995
|
+
drag?: boolean;
|
|
1996
|
+
/** Enable mouse-wheel zoom. Default: true. */
|
|
1997
|
+
wheel?: boolean;
|
|
1998
|
+
/** Enable two-finger pinch (touch). Default: true. */
|
|
1999
|
+
pinch?: boolean;
|
|
2000
|
+
/** SmoothDamp time constant (seconds). `0` = instant (no smoothing). Default: `0.1`. */
|
|
2001
|
+
smoothTime?: number;
|
|
2002
|
+
/**
|
|
2003
|
+
* Drag-release fling. `true` enables with defaults, `false` disables, or
|
|
2004
|
+
* pass an object to tune. Default: on.
|
|
2005
|
+
*/
|
|
2006
|
+
fling?: boolean | FlingOptions;
|
|
2007
|
+
/** Minimum camera zoom. Default: 0.1. */
|
|
2008
|
+
minZoom?: number;
|
|
2009
|
+
/** Maximum camera zoom. Default: 10. */
|
|
2010
|
+
maxZoom?: number;
|
|
2011
|
+
/** Initial camera state to write to the viewport. */
|
|
2012
|
+
initial?: CameraState;
|
|
2013
|
+
/**
|
|
2014
|
+
* Custom zoom gesture handler, fired before the binding's default camera
|
|
2015
|
+
* zoom for both wheel and pinch. Call `preventDefault()` on the supplied
|
|
2016
|
+
* gesture to skip the default uniform-camera-zoom behavior — useful when
|
|
2017
|
+
* the embedding scene wants to translate the gesture into something else
|
|
2018
|
+
* (e.g. adjusting row spacing in a rectilinear phylogram).
|
|
2019
|
+
*
|
|
2020
|
+
* Pinch translation (`deltaX`/`deltaY` pan component) is still applied
|
|
2021
|
+
* even if zoom is prevented.
|
|
2022
|
+
*/
|
|
2023
|
+
onZoom?: (gesture: ZoomGesture) => void;
|
|
2024
|
+
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Unified wheel/pinch zoom gesture. `scale` is a multiplicative factor
|
|
2027
|
+
* (>1 zoom in, <1 zoom out). For wheel events `scale` is derived from
|
|
2028
|
+
* `WHEEL_ZOOM_PER_PX × dy`; for pinch it's `deltaScale` from the gesture
|
|
2029
|
+
* tick.
|
|
2030
|
+
*/
|
|
2031
|
+
interface ZoomGesture {
|
|
2032
|
+
readonly source: "wheel" | "pinch";
|
|
2033
|
+
/** Multiplicative zoom factor for this gesture tick. */
|
|
2034
|
+
readonly scale: number;
|
|
2035
|
+
/** Anchor (element-local CSS px) — the point the user expects to stay put. */
|
|
2036
|
+
readonly anchorX: number;
|
|
2037
|
+
readonly anchorY: number;
|
|
2038
|
+
/** Suppress the binding's default uniform-camera-zoom. */
|
|
2039
|
+
preventDefault(): void;
|
|
2040
|
+
}
|
|
2041
|
+
interface CameraViewportBinding {
|
|
2042
|
+
readonly mode: "camera";
|
|
2043
|
+
readonly interacting: boolean;
|
|
2044
|
+
readonly flinging: boolean;
|
|
2045
|
+
/**
|
|
2046
|
+
* True while any damper is still converging on its target (e.g. after a
|
|
2047
|
+
* programmatic `setTarget` for a pan/zoom-to). Lets on-demand rAF loops
|
|
2048
|
+
* keep ticking until the camera settles — without it, a `setTarget` call
|
|
2049
|
+
* outside an active gesture would render one frame then stop mid-animation.
|
|
2050
|
+
*/
|
|
2051
|
+
readonly animating: boolean;
|
|
2052
|
+
enabled: boolean;
|
|
2053
|
+
/** Interpolated camera state currently applied to the viewport. */
|
|
2054
|
+
readonly camera: ResolvedCameraState;
|
|
2055
|
+
/** Target the damping is moving toward. */
|
|
2056
|
+
readonly target: ResolvedCameraState;
|
|
2057
|
+
/** Set the target state. Omitted fields keep their current target value. */
|
|
2058
|
+
setTarget(state: CameraState): void;
|
|
2059
|
+
/** Jump to state immediately, skipping interpolation. Cancels any fling. */
|
|
2060
|
+
jumpTo(state: CameraState): void;
|
|
2061
|
+
/** Step damping toward the target and push to the viewport. Call per frame. */
|
|
2062
|
+
update(dt: number): void;
|
|
2063
|
+
/** Immediately cancel any pending smoothing and fling. */
|
|
2064
|
+
stopAnimation(): void;
|
|
2065
|
+
/**
|
|
2066
|
+
* Jump back to the initial target captured on the first `setTarget` /
|
|
2067
|
+
* `jumpTo` call after binding. No-op until an initial state has been set.
|
|
2068
|
+
*/
|
|
2069
|
+
resetToInitial(): void;
|
|
2070
|
+
/**
|
|
2071
|
+
* Convert a CSS-pixel coordinate (element-local) to world coords using the
|
|
2072
|
+
* binding's currently interpolated camera.
|
|
2073
|
+
*/
|
|
2074
|
+
screenToWorld(cssX: number, cssY: number): Vec2;
|
|
2075
|
+
/** Inverse of {@link screenToWorld}. Returns CSS pixels (element-local). */
|
|
2076
|
+
worldToScreen(wx: number, wy: number): Vec2;
|
|
2077
|
+
destroy(): void;
|
|
2078
|
+
}
|
|
2079
|
+
type ViewportBinding = CameraViewportBinding;
|
|
2080
|
+
declare function bindViewport(viewport: CameraViewport, element: HTMLElement, options?: BindViewportOptions): CameraViewportBinding;
|
|
2081
|
+
//#endregion
|
|
2082
|
+
//#region src/watch-dpr.d.ts
|
|
2083
|
+
interface WatchDprOptions {
|
|
2084
|
+
/**
|
|
2085
|
+
* Maximum device-pixel ratio to allow. Defaults to `2`.
|
|
2086
|
+
*
|
|
2087
|
+
* At DPR=3 (common on modern phones) the per-frame fill cost is 9× that of
|
|
2088
|
+
* DPR=1 and the OIT A-buffer may exceed the 128 MiB device storage limit.
|
|
2089
|
+
* Capping at 2 keeps costs predictable while still looking crisp.
|
|
2090
|
+
*/
|
|
2091
|
+
maxDpr?: number;
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Auto-track device-pixel ratio and canvas size for a renderer.
|
|
2095
|
+
*
|
|
2096
|
+
* Wires:
|
|
2097
|
+
* - `ResizeObserver` on `canvas` (debounced via `requestAnimationFrame`)
|
|
2098
|
+
* - `visualViewport` resize (iOS keyboard viewport shrink)
|
|
2099
|
+
* - `screen.orientation` change or `window.orientationchange` fallback
|
|
2100
|
+
* (Android WebView landscape↔portrait)
|
|
2101
|
+
*
|
|
2102
|
+
* On each event the helper computes the effective DPR, calls `renderer.setDpr`
|
|
2103
|
+
* then `renderer.resize(canvas.clientWidth, canvas.clientHeight)`.
|
|
2104
|
+
*
|
|
2105
|
+
* @returns A `dispose()` function that removes all listeners. Call it when
|
|
2106
|
+
* tearing down the renderer or unmounting the canvas.
|
|
2107
|
+
*/
|
|
2108
|
+
declare function watchDpr(renderer: Renderer2D, canvas: HTMLCanvasElement, opts?: WatchDprOptions): {
|
|
2109
|
+
dispose(): void;
|
|
2110
|
+
};
|
|
2111
|
+
//#endregion
|
|
2112
|
+
//#region src/animation/easings.d.ts
|
|
2113
|
+
type Easing = (t: number) => number;
|
|
2114
|
+
declare const linear: Easing;
|
|
2115
|
+
declare const easeInQuad: Easing;
|
|
2116
|
+
declare const easeOutQuad: Easing;
|
|
2117
|
+
declare const easeInOutQuad: Easing;
|
|
2118
|
+
declare const easeInCubic: Easing;
|
|
2119
|
+
declare const easeOutCubic: Easing;
|
|
2120
|
+
declare const easeInOutCubic: Easing;
|
|
2121
|
+
declare const easeInQuart: Easing;
|
|
2122
|
+
declare const easeOutQuart: Easing;
|
|
2123
|
+
declare const easeInOutQuart: Easing;
|
|
2124
|
+
declare const easeInQuint: Easing;
|
|
2125
|
+
declare const easeOutQuint: Easing;
|
|
2126
|
+
declare const easeInOutQuint: Easing;
|
|
2127
|
+
declare const easeInExpo: Easing;
|
|
2128
|
+
declare const easeOutExpo: Easing;
|
|
2129
|
+
declare const easeInOutExpo: Easing;
|
|
2130
|
+
declare const easeInSine: Easing;
|
|
2131
|
+
declare const easeOutSine: Easing;
|
|
2132
|
+
declare const easeInOutSine: Easing;
|
|
2133
|
+
declare const easeInCirc: Easing;
|
|
2134
|
+
declare const easeOutCirc: Easing;
|
|
2135
|
+
declare const easeInOutCirc: Easing;
|
|
2136
|
+
declare const easeInBack: Easing;
|
|
2137
|
+
declare const easeOutBack: Easing;
|
|
2138
|
+
declare const easeInOutBack: Easing;
|
|
2139
|
+
declare const easeInElastic: Easing;
|
|
2140
|
+
declare const easeOutElastic: Easing;
|
|
2141
|
+
declare const easeInOutElastic: Easing;
|
|
2142
|
+
declare const easeOutBounce: Easing;
|
|
2143
|
+
declare const easeInBounce: Easing;
|
|
2144
|
+
declare const easeInOutBounce: Easing;
|
|
2145
|
+
/**
|
|
2146
|
+
* Build an easing from four CSS-style control points. Endpoints are fixed at
|
|
2147
|
+
* `(0,0)` and `(1,1)`; `(x1,y1)` / `(x2,y2)` control the curve. `x1` and `x2`
|
|
2148
|
+
* are clamped to `[0, 1]` to keep the curve single-valued in the time axis.
|
|
2149
|
+
*
|
|
2150
|
+
* Implemented as Newton iteration on the x-parametric cubic, with a bisection
|
|
2151
|
+
* fallback when Newton fails. Fast enough to call per frame.
|
|
2152
|
+
*/
|
|
2153
|
+
declare function cubicBezier(x1: number, y1: number, x2: number, y2: number): Easing;
|
|
2154
|
+
//#endregion
|
|
2155
|
+
//#region src/animation/tween.d.ts
|
|
2156
|
+
interface TweenOptions {
|
|
2157
|
+
/** Starting value. */
|
|
2158
|
+
from: number;
|
|
2159
|
+
/** Target value. */
|
|
2160
|
+
to: number;
|
|
2161
|
+
/** Duration in seconds. Must be > 0. */
|
|
2162
|
+
duration: number;
|
|
2163
|
+
/** Easing. Default: {@link linear}. */
|
|
2164
|
+
easing?: Easing;
|
|
2165
|
+
/** Pre-roll delay in seconds before the tween begins advancing. Default 0. */
|
|
2166
|
+
delay?: number;
|
|
2167
|
+
}
|
|
2168
|
+
interface Tween {
|
|
2169
|
+
readonly value: number;
|
|
2170
|
+
readonly from: number;
|
|
2171
|
+
readonly to: number;
|
|
2172
|
+
readonly duration: number;
|
|
2173
|
+
readonly elapsed: number;
|
|
2174
|
+
/** True once elapsed ≥ duration (after delay). */
|
|
2175
|
+
readonly done: boolean;
|
|
2176
|
+
/** Advance by `dt` seconds and return the new value. */
|
|
2177
|
+
step(dt: number): number;
|
|
2178
|
+
/** Rewind to `from`, cleared elapsed, cleared delay. */
|
|
2179
|
+
reset(): void;
|
|
2180
|
+
/** Sample the raw easing curve at absolute progress `u ∈ [0, 1]`. */
|
|
2181
|
+
sampleAt(u: number): number;
|
|
2182
|
+
/**
|
|
2183
|
+
* Redirect mid-flight: the current value becomes the new `from`, and a new
|
|
2184
|
+
* tween begins toward `to`. Elapsed resets to 0. If `duration` is omitted
|
|
2185
|
+
* the previous value is kept.
|
|
2186
|
+
*/
|
|
2187
|
+
retarget(to: number, duration?: number): void;
|
|
2188
|
+
}
|
|
2189
|
+
declare function tween(opts: TweenOptions): Tween;
|
|
2190
|
+
//#endregion
|
|
2191
|
+
//#region src/animation/spring.d.ts
|
|
2192
|
+
interface SpringOptions {
|
|
2193
|
+
/** Hooke constant. Default 170. */
|
|
2194
|
+
stiffness?: number;
|
|
2195
|
+
/** Viscous damping coefficient. Default 26 (≈ critical at k=170, m=1). */
|
|
2196
|
+
damping?: number;
|
|
2197
|
+
/** Point mass. Default 1. */
|
|
2198
|
+
mass?: number;
|
|
2199
|
+
/** Starting value. Defaults to `target` (or 0 if both omitted). */
|
|
2200
|
+
initialValue?: number;
|
|
2201
|
+
/** Starting velocity. Default 0. */
|
|
2202
|
+
initialVelocity?: number;
|
|
2203
|
+
/** Initial target. Defaults to `initialValue` (or 0). */
|
|
2204
|
+
target?: number;
|
|
2205
|
+
/** |velocity| below which the spring can settle. Default 0.01. */
|
|
2206
|
+
restVelocity?: number;
|
|
2207
|
+
/** |value - target| below which the spring can settle. Default 0.01. */
|
|
2208
|
+
restDisplacement?: number;
|
|
2209
|
+
}
|
|
2210
|
+
interface Spring {
|
|
2211
|
+
readonly value: number;
|
|
2212
|
+
readonly velocity: number;
|
|
2213
|
+
readonly target: number;
|
|
2214
|
+
readonly settled: boolean;
|
|
2215
|
+
readonly stiffness: number;
|
|
2216
|
+
readonly damping: number;
|
|
2217
|
+
readonly mass: number;
|
|
2218
|
+
/** Advance by `dt` seconds. Returns the new value. */
|
|
2219
|
+
step(dt: number): number;
|
|
2220
|
+
/** Move the target. Motion stays continuous. */
|
|
2221
|
+
setTarget(target: number): void;
|
|
2222
|
+
/** Jump position without changing velocity. */
|
|
2223
|
+
setValue(value: number): void;
|
|
2224
|
+
/** Inject velocity (e.g. fling handoff). */
|
|
2225
|
+
setVelocity(velocity: number): void;
|
|
2226
|
+
/** Snap to current target with zero velocity. */
|
|
2227
|
+
reset(): void;
|
|
2228
|
+
}
|
|
2229
|
+
declare function spring(opts?: SpringOptions): Spring;
|
|
2230
|
+
interface DurationSpringSpec {
|
|
2231
|
+
/** Undamped natural period (seconds). Must be > 0. */
|
|
2232
|
+
duration: number;
|
|
2233
|
+
/** Overshoot control in `(-∞, 1]`. Default 0 (critical). */
|
|
2234
|
+
bounce?: number;
|
|
2235
|
+
/** Point mass. Default 1. */
|
|
2236
|
+
mass?: number;
|
|
2237
|
+
}
|
|
2238
|
+
interface PhysicalSpringParams {
|
|
2239
|
+
stiffness: number;
|
|
2240
|
+
damping: number;
|
|
2241
|
+
mass: number;
|
|
2242
|
+
}
|
|
2243
|
+
declare function springFromDuration(spec: DurationSpringSpec): PhysicalSpringParams;
|
|
2244
|
+
//#endregion
|
|
2245
|
+
//#region src/animation/smoothDamp.d.ts
|
|
2246
|
+
interface SmoothDampOptions {
|
|
2247
|
+
/** Approximate time to converge. Seconds. Must be > 0. Default 0.1. */
|
|
2248
|
+
smoothTime?: number;
|
|
2249
|
+
/** Clamp on integrated speed (|change| / s). Default `Infinity`. */
|
|
2250
|
+
maxSpeed?: number;
|
|
2251
|
+
/** Starting value. Defaults to `target` (or 0). */
|
|
2252
|
+
initialValue?: number;
|
|
2253
|
+
/** Starting velocity. Default 0. */
|
|
2254
|
+
initialVelocity?: number;
|
|
2255
|
+
/** Initial target. Defaults to `initialValue`. */
|
|
2256
|
+
target?: number;
|
|
2257
|
+
/** |velocity| below which the filter can settle. Default 1e-4. */
|
|
2258
|
+
restVelocity?: number;
|
|
2259
|
+
/** |value - target| below which the filter can settle. Default 1e-4. */
|
|
2260
|
+
restDisplacement?: number;
|
|
2261
|
+
}
|
|
2262
|
+
interface SmoothDamp {
|
|
2263
|
+
readonly value: number;
|
|
2264
|
+
readonly velocity: number;
|
|
2265
|
+
readonly target: number;
|
|
2266
|
+
readonly settled: boolean;
|
|
2267
|
+
smoothTime: number;
|
|
2268
|
+
maxSpeed: number;
|
|
2269
|
+
/** Advance by `dt` seconds. Returns the new value. */
|
|
2270
|
+
step(dt: number): number;
|
|
2271
|
+
/** Move the target; velocity is preserved. */
|
|
2272
|
+
setTarget(target: number): void;
|
|
2273
|
+
/** Jump value without touching velocity. */
|
|
2274
|
+
setValue(value: number): void;
|
|
2275
|
+
/** Inject velocity (fling handoff). */
|
|
2276
|
+
setVelocity(velocity: number): void;
|
|
2277
|
+
/** Snap to target with zero velocity. */
|
|
2278
|
+
reset(): void;
|
|
2279
|
+
}
|
|
2280
|
+
declare function smoothDamp(opts?: SmoothDampOptions): SmoothDamp;
|
|
2281
|
+
//#endregion
|
|
2282
|
+
//#region src/animation/decay.d.ts
|
|
2283
|
+
interface DecayOptions {
|
|
2284
|
+
/** Starting value. Default 0. */
|
|
2285
|
+
initialValue?: number;
|
|
2286
|
+
/** Starting velocity. Default 0. */
|
|
2287
|
+
initialVelocity?: number;
|
|
2288
|
+
/** Decay rate (1/s). Higher = stops sooner. Default 4. */
|
|
2289
|
+
friction?: number;
|
|
2290
|
+
/** |velocity| at or below which the decay settles. Default 1e-3. */
|
|
2291
|
+
restVelocity?: number;
|
|
2292
|
+
}
|
|
2293
|
+
interface Decay {
|
|
2294
|
+
readonly value: number;
|
|
2295
|
+
readonly velocity: number;
|
|
2296
|
+
readonly settled: boolean;
|
|
2297
|
+
friction: number;
|
|
2298
|
+
/** Advance by `dt` seconds. Returns the new value. */
|
|
2299
|
+
step(dt: number): number;
|
|
2300
|
+
/** Replace value. */
|
|
2301
|
+
setValue(value: number): void;
|
|
2302
|
+
/** Replace velocity (reactivates the decay if it was settled). */
|
|
2303
|
+
setVelocity(velocity: number): void;
|
|
2304
|
+
/** Clear velocity without touching value. */
|
|
2305
|
+
reset(): void;
|
|
2306
|
+
/**
|
|
2307
|
+
* Predict the total travel remaining if velocity decays undisturbed.
|
|
2308
|
+
* `∫₀^∞ v₀·e^(-μt) dt = v₀/μ`. Useful for targeting a clamp boundary.
|
|
2309
|
+
*/
|
|
2310
|
+
projectedDistance(): number;
|
|
2311
|
+
}
|
|
2312
|
+
declare function decay(opts?: DecayOptions): Decay;
|
|
2313
|
+
//#endregion
|
|
2314
|
+
//#region src/animation/velocity-tracker.d.ts
|
|
2315
|
+
interface VelocityTrackerOptions {
|
|
2316
|
+
/**
|
|
2317
|
+
* Drop samples older than this (seconds) when estimating. Default 0.1.
|
|
2318
|
+
* Shorter = more reactive, noisier; longer = smoother, laggier.
|
|
2319
|
+
*/
|
|
2320
|
+
windowSeconds?: number;
|
|
2321
|
+
/** Maximum ring-buffer size. Default 16. */
|
|
2322
|
+
capacity?: number;
|
|
2323
|
+
}
|
|
2324
|
+
interface VelocityTracker {
|
|
2325
|
+
/** Record a pointer sample. `t` is in seconds (monotonic). */
|
|
2326
|
+
sample(t: number, x: number, y: number): void;
|
|
2327
|
+
/** Estimated velocity in units/s. Zero if not enough recent samples. */
|
|
2328
|
+
velocity(): Vec2;
|
|
2329
|
+
/** Clear all samples. */
|
|
2330
|
+
reset(): void;
|
|
2331
|
+
}
|
|
2332
|
+
declare function createVelocityTracker(opts?: VelocityTrackerOptions): VelocityTracker;
|
|
2333
|
+
//#endregion
|
|
2334
|
+
//#region src/animation/transition.d.ts
|
|
2335
|
+
interface TransitionOptions<T> {
|
|
2336
|
+
/** Starting value. Also the initial target. */
|
|
2337
|
+
initial: T;
|
|
2338
|
+
/** Duration in seconds. Must be > 0. */
|
|
2339
|
+
duration: number;
|
|
2340
|
+
/** Easing curve applied to progress. Default {@link linear}. */
|
|
2341
|
+
easing?: Easing;
|
|
2342
|
+
/**
|
|
2343
|
+
* Linear interpolation between two values at parameter `t ∈ [0, 1]`.
|
|
2344
|
+
* Required when `T` is not `number`. Default lerps numbers.
|
|
2345
|
+
*/
|
|
2346
|
+
interpolate?: (a: T, b: T, t: number) => T;
|
|
2347
|
+
/**
|
|
2348
|
+
* Equality predicate used to short-circuit `setTarget` and to decide whether
|
|
2349
|
+
* a snap is needed. Default {@link Object.is}.
|
|
2350
|
+
*/
|
|
2351
|
+
equals?: (a: T, b: T) => boolean;
|
|
2352
|
+
/**
|
|
2353
|
+
* Optional invalidator. Marked dirty when motion starts and on each step
|
|
2354
|
+
* while active, so on-demand RAF loops keep ticking until settled.
|
|
2355
|
+
*/
|
|
2356
|
+
invalidator?: Invalidator;
|
|
2357
|
+
}
|
|
2358
|
+
interface Transition<T> extends ReadableSignal<T> {
|
|
2359
|
+
/** Current displayed value (animates toward `target`). */
|
|
2360
|
+
readonly value: T;
|
|
2361
|
+
/** The value `step` is moving toward. */
|
|
2362
|
+
readonly target: T;
|
|
2363
|
+
/** True while the transition has not yet reached `target`. */
|
|
2364
|
+
readonly active: boolean;
|
|
2365
|
+
/**
|
|
2366
|
+
* Set a new target. If `immediate` is true (or the new target equals the
|
|
2367
|
+
* current value), the value snaps and motion stops. Otherwise a fresh tween
|
|
2368
|
+
* begins from the current value over `duration`.
|
|
2369
|
+
*/
|
|
2370
|
+
setTarget(target: T, immediate?: boolean): void;
|
|
2371
|
+
/** Snap value (and target) without animation. */
|
|
2372
|
+
setValue(value: T): void;
|
|
2373
|
+
/** Advance by `dt` seconds. Returns the new value. */
|
|
2374
|
+
step(dt: number): T;
|
|
2375
|
+
}
|
|
2376
|
+
declare function transition<T>(opts: TransitionOptions<T>): Transition<T>;
|
|
2377
|
+
//#endregion
|
|
2378
|
+
//#region src/animation/tween-once.d.ts
|
|
2379
|
+
/**
|
|
2380
|
+
* Scheduler contract: invoked once with a `tick(dt)` callback. The scheduler
|
|
2381
|
+
* calls `tick` each frame with elapsed seconds; `tick` returns `true` while
|
|
2382
|
+
* the tween wants more frames, `false` once done. The scheduler returns a
|
|
2383
|
+
* stop function that the tween calls on cancel/complete to detach.
|
|
2384
|
+
*/
|
|
2385
|
+
type TweenScheduler = (tick: (dt: number) => boolean) => () => void;
|
|
2386
|
+
interface TweenToOptions<T> {
|
|
2387
|
+
from: T;
|
|
2388
|
+
to: T;
|
|
2389
|
+
/** Duration in seconds. Must be > 0. */
|
|
2390
|
+
duration: number;
|
|
2391
|
+
/** Easing curve. Default {@link linear}. */
|
|
2392
|
+
easing?: Easing;
|
|
2393
|
+
/** Pre-roll delay in seconds before motion begins. Default 0. */
|
|
2394
|
+
delay?: number;
|
|
2395
|
+
/** Custom interpolator. Defaults to numeric lerp / OKLab color lerp. */
|
|
2396
|
+
interpolate?: (a: T, b: T, t: number) => T;
|
|
2397
|
+
/** Called every frame the value changes (including the final value). */
|
|
2398
|
+
onChange: (value: T) => void;
|
|
2399
|
+
/** Called once at completion with the final value. Skipped on cancel. */
|
|
2400
|
+
onComplete?: (value: T) => void;
|
|
2401
|
+
/** Custom scheduler. Default: rAF in browser, microtask fallback. */
|
|
2402
|
+
scheduler?: TweenScheduler;
|
|
2403
|
+
}
|
|
2404
|
+
interface TweenHandle {
|
|
2405
|
+
/** True until the tween completes or is cancelled. */
|
|
2406
|
+
readonly active: boolean;
|
|
2407
|
+
/** Stop the tween. Suppresses any further `onChange` and `onComplete`. */
|
|
2408
|
+
cancel(): void;
|
|
2409
|
+
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Default scheduler used by `tweenTo` when no `scheduler` option is provided.
|
|
2412
|
+
* Exposed for callers that want to plug it into a different host loop.
|
|
2413
|
+
*/
|
|
2414
|
+
declare function defaultTweenScheduler(): TweenScheduler;
|
|
2415
|
+
declare function tweenTo<T>(opts: TweenToOptions<T>): TweenHandle;
|
|
2416
|
+
//#endregion
|
|
2417
|
+
export { easeOutElastic as $, MenuItemLayout as $n, RegionCloudNode as $r, colorAtApca as $t, easeInBounce as A, getLastGPUHandle as Ai, DEFAULT_REGION_PAD as An, CrosshairOptions as Ar, weighted as At, easeInOutExpo as B, NavigatorCacheOptions as Bn, Selection as Br, degToRad as Bt, springFromDuration as C, MirrorSimplify as Ci, ShapedBlock as Cn, BrushOptions as Cr, sample as Ct, Easing as D, createSVGRenderer as Di, RasterFont as Dn, Crosshair as Dr, triangular as Dt, tween as E, SVGRendererOptions as Ei, SdfGlyphAtlas as En, createBrush as Er, splitmix32 as Et, easeInOutBack as F, LabelCullerOptions as Fn, Invalidator as Fr, RAD_TO_DEG as Ft, easeInQuad as G, createNavigator as Gn, HitFanOutEventContext as Gr, sign as Gt, easeInOutQuart as H, NavigatorInteractionOptions as Hn, createSelection as Hr, lerp as Ht, easeInOutBounce as I, LodLayer as In, animateToward as Ir, TAU as It, easeInSine as J, Menu as Jn, HitFanOutSubscriber as Jr, wrap as Jt, easeInQuart as K, isCoarsePointer as Kn, HitFanOutSpec as Kr, smootherstep as Kt, easeInOutCirc as L, LodLevel as Ln, createInvalidator as Lr, approxEqual as Lt, easeInCubic as M, unionRegionFromAabbs as Mn, createCrosshair as Mr, DEG_TO_RAD as Mt, easeInElastic as N, viewportAabb as Nn, AnimateTowardOptions as Nr, HALF_PI as Nt, cubicBezier as O, GPUHandle as Oi, RasterFontOptions as On, CrosshairAxis as Or, uniform as Ot, easeInExpo as P, LabelCuller as Pn, AnimatedValue as Pr, PI as Pt, easeOutCubic as Q, MenuItem as Qn, createHitFanOut as Qr, apcaPolarity as Qt, easeInOutCubic as R, createLodLayer as Rn, HoverTracker as Rr, clamp as Rt, spring as S, MirrorLayerOptions as Si, ShapeOptions as Sn, BrushEdge as Sr, pick as St, TweenOptions as T, SVGRenderer as Ti, shapeText as Tn, BrushStyle as Tr, shuffle as Tt, easeInOutQuint as U, NavigatorOptions as Un, HitFanOut as Ur, radToDeg as Ut, easeInOutQuad as V, NavigatorIndicatorOptions as Vn, SelectionMods as Vr, inverseLerp as Vt, easeInOutSine as W, NavigatorRetainHost as Wn, HitFanOutActive as Wr, remap as Wt, easeOutBounce as X, MenuBounds as Xn, PickAtOptions as Xr, apcaContrast as Xt, easeOutBack as Y, MenuAnchor as Yn, HitSlot as Yr, ApcaPolarity as Yt, easeOutCirc as Z, MenuContent as Zn, PickAtResult as Zr, apcaFontLookup as Zt, smoothDamp as _, CAMERA_Z as _i, cubicAABB as _n, TooltipStyle as _r, gaussian as _t, tweenTo as a, InteractionManager as ai, oklabToOklch as an, createMenu as ar, linear as at, Spring as b, VIEWPORT_Z as bi, TextPack as bn, Brush as br, mulberry32 as bt, transition as c, PointCloudSpec as ci, srgbToOklab as cn, TooltipAnchor as cr, BindViewportOptions as ct, createVelocityTracker as d, InteractionNode as di, colorAtContrast as dn, TooltipLayout as dr, ViewportBinding as dt, RegionCloudSpec as ei, findApcaColor as en, MenuLayout as er, easeOutExpo as et, Decay as f, InteractionNodeSpec as fi, contrastRatio as fn, TooltipMeasure as fr, ZoomGesture as ft, SmoothDampOptions as g, ScrollInfo as gi, wcagMinContrast as gn, TooltipRowLayout as gr, exponential as gt, SmoothDamp as h, PointerInfo as hi, relativeLuminance as hn, TooltipRow as hr, bates as ht, defaultTweenScheduler as i, GestureEventInfo as ii, lerpInSpace as in, MenuStyle as ir, easeOutSine as it, easeInCirc as j, initGPU as ji, projectAabbToBounds as jn, CrosshairStyle as jr, xorshift32 as jt, easeInBack as k, InitGPUOptions as ki, loadSystemFont as kn, CrosshairBounds as kr, uniformInt as kt, VelocityTracker as l, registerPointCloud as li, WcagLevel as ln, TooltipBounds as lr, CameraViewportBinding as lt, decay as m, PinchInfo as mi, meetsContrast as mn, TooltipPlacement as mr, Rng as mt, TweenScheduler as n, BackgroundTapInfo as ni, lerpColorOklab as nn, MenuOptions as nr, easeOutQuart as nt, Transition as o, createInteractionManager as oi, oklabToSrgb as on, layoutMenu as or, WatchDprOptions as ot, DecayOptions as p, Mods as pi, findAccessibleColor as pn, TooltipOptions as pr, bindViewport as pt, easeInQuint as q, watchCoarsePointer as qn, HitFanOutState as qr, smoothstep as qt, TweenToOptions as r, ContextMenuOpts as ri, lerpColorRgb as rn, MenuPlacement as rr, easeOutQuint as rt, TransitionOptions as s, PointCloudNode as si, oklchToOklab as sn, Tooltip as sr, watchDpr as st, TweenHandle as t, registerRegionCloud as ti, BlendSpace as tn, MenuMeasure as tr, easeOutQuad as tt, VelocityTrackerOptions as u, DragPointerInfo as ui, WcagTextOptions as un, TooltipContent as ur, FlingOptions as ut, DurationSpringSpec as v, PRESS_MOVEMENT_THRESHOLD_PX as vi, cubicEval as vn, createTooltip as vr, inUnitDisk as vt, Tween as w, mirrorLayer as wi, ShapedGlyph as wn, BrushRect as wr, sfc32 as wt, SpringOptions as x, MirrorLayer as xi, TextShape as xn, BrushAxis as xr, onUnitCircle as xt, PhysicalSpringParams as y, USER_DEFAULT_Z as yi, TextBlockMetrics as yn, layoutTooltip as yr, logNormal as yt, easeInOutElastic as z, Navigator as zn, trackHover as zr, clamp01 as zt };
|