@weasel-js/core 1.4.0-pre.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { ActionDisabledReason, useOptionalDepRegistry, DepRegistryProvider, useActionsRegistry, ActionsProvider, useDepRegistry, reportRouteConflicts, buildDepsFromRequires, useDepSource, actionBindings } from './chunk-H6E6KPTU.js';
1
+ import { ActionDisabledReason, useOptionalDepRegistry, DepRegistryProvider, useActionsRegistry, ActionsProvider, useDepRegistry, reportRouteConflicts, buildDepsFromRequires, useDepSource, actionBindings } from './chunk-TORKRNFY.js';
2
2
  import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-Y5KS66G7.js';
3
3
  import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-BLJNRMKB.js';
4
4
  import { AUTO_POSE_DESCRIPTOR, resolveUnit, polylineFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, polygonFromPoints, PATH_M, PATH_L, PATH_Z, RECT_POSE_DESCRIPTOR, pathPoseDescriptor, withinPxRadius, boundsOfPath, PATH_Q, PATH_C, DEFAULT_FLATTEN_TOLERANCE, flattenQuadratic, flattenCubic, PathBuilder, pointInPath, strokeHitTest, transformPath, translatePath, pxExtent, remapRotatedLeaf, withinPxBox, flattenCubicWithArcLen, flattenQuadraticWithArcLen, PATH_CMD_LENGTHS, pointToGridCell } from './chunk-SBFC6J3C.js';
@@ -6,7 +6,7 @@ import { dispatchApplyBatch, dlog, dwarn, usePointerContext, PointerContextProvi
6
6
  import { createSetSelectionOp } from './chunk-ZB7UYJVG.js';
7
7
  import { registerOpFactory, createDeleteOp, createInsertOp, rebuildOp } from './chunk-2KKYDDDD.js';
8
8
  import { getTexture, chunks, dots, crosshatch, hatch } from './chunk-BJN5ZRIJ.js';
9
- import { createContext, createElement, forwardRef, useMemo, useRef, useCallback, useState, useImperativeHandle, useSyncExternalStore, useEffect, useLayoutEffect, useContext, useId, useReducer } from 'react';
9
+ import { createContext, createElement, forwardRef, useMemo, useRef, useCallback, useState, useEffect, useImperativeHandle, useSyncExternalStore, useLayoutEffect, useContext, useId, useReducer } from 'react';
10
10
  import { subscribeGlyphReady, DEFAULT_BAKE_BUDGET, TEXT_VERT_SRC, TEXT_FRAG_SRC, TEXT_SDF_UNIFORMS, TEXT_SDF_ATTRIBUTES, TEXT_FRAG_R8_SRC, markAllFontsNotUploaded, resetBakeBudget, syncDynamicPageTexture, ensureFontTexture, dynamicPageTextureId, textureCacheKey } from '@weasel-js/font';
11
11
  export { canQueryLocalFonts, canQueryLocalFonts as canQueryLocalFonts2, enableLocalFontOutlines, enableLocalFontOutlines as enableLocalFontOutlines2, hasFontOutlines, hasFontOutlines as hasFontOutlines2, isCanvasFont, isCanvasFont as isCanvasFont2, listFontOutlines, listFontOutlines as listFontOutlines2, outlineStatus, outlineStatus as outlineStatus2, registerCanvasFont, registerCanvasFont as registerCanvasFont2, registerFont, registerFont as registerFont2, registerFontOutlines, registerFontOutlines as registerFontOutlines2, subscribeGlyphReady, subscribeGlyphReady as subscribeGlyphReady2, unregisterCanvasFont, unregisterCanvasFont as unregisterCanvasFont2, unregisterFontOutlines, unregisterFontOutlines as unregisterFontOutlines2 } from '@weasel-js/font';
12
12
  import { resolveTextStyle, resolveAlign, resolveRuns, cachedLayoutRuns, verticalAlignOffset, runsToPlainText, toRuns, fontString, textLineBoxes } from '@weasel-js/text';
@@ -19,12 +19,13 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
19
19
  import polygonClipping from 'polygon-clipping';
20
20
  import { matchSpec, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES, parseTargetSpec, mimeMatchesGlob } from '@weasel-js/gestures';
21
21
  import { NORMAL, IMPLICIT_TAGS } from '@weasel-js/modes';
22
- import { cursorFor } from '@weasel-js/cursor';
22
+ import { GLYPHS, cursorWorldSize, cursorPaintOps, chromeLineWidthScale, cursorPaintMatrix, resolveCursorTier, cursorFor, resolveCursor } from '@weasel-js/cursor';
23
+ export { bakeCursor, cursorFor, resolveCursor, resolveCursorTier } from '@weasel-js/cursor';
23
24
  import { createHistory } from '@weasel-js/history';
24
25
  export * from '@weasel-js/history';
25
26
 
26
27
  // src/version.ts
27
- var VERSION = "1.4.0-pre.1" ;
28
+ var VERSION = "1.4.1" ;
28
29
 
29
30
  // src/features/grid/roundToCell.ts
30
31
  function roundToCell(value, cellSize) {
@@ -257,6 +258,44 @@ void main() {
257
258
  }
258
259
  `
259
260
  );
261
+ var IMAGE_VOPACITY_VERT_SRC = (
262
+ /* glsl */
263
+ `#version 300 es
264
+ in vec2 a_position;
265
+ in vec2 a_uv;
266
+ in float a_opacity;
267
+ uniform mat3 u_proj;
268
+ uniform mat3 u_model;
269
+ out vec2 v_uv;
270
+ out float v_opacity;
271
+ void main() {
272
+ vec3 screen = u_model * vec3(a_position, 1.0);
273
+ vec3 clip = u_proj * vec3(screen.xy, 1.0);
274
+ gl_Position = vec4(clip.xy, 0.0, 1.0);
275
+ v_uv = a_uv;
276
+ v_opacity = a_opacity;
277
+ }
278
+ `
279
+ );
280
+ var IMAGE_VOPACITY_FRAG_SRC = (
281
+ /* glsl */
282
+ `#version 300 es
283
+ precision highp float;
284
+ in vec2 v_uv;
285
+ in float v_opacity;
286
+ uniform sampler2D u_sampler;
287
+ uniform float u_alpha;
288
+ uniform mat4 u_colorMatrix;
289
+ uniform vec4 u_colorBias;
290
+ out vec4 outColor;
291
+ void main() {
292
+ vec4 texel = texture(u_sampler, v_uv);
293
+ vec4 mapped = clamp(u_colorMatrix * texel + u_colorBias, 0.0, 1.0);
294
+ float a = mapped.a * v_opacity * u_alpha;
295
+ outColor = vec4(mapped.rgb * a, a);
296
+ }
297
+ `
298
+ );
260
299
  var IMAGE_FILL_UNIFORMS = [
261
300
  "u_proj",
262
301
  "u_model",
@@ -267,6 +306,15 @@ var IMAGE_FILL_UNIFORMS = [
267
306
  "u_colorBias"
268
307
  ];
269
308
  var IMAGE_FILL_ATTRIBUTES = ["a_position", "a_uv"];
309
+ var IMAGE_VOPACITY_UNIFORMS = [
310
+ "u_proj",
311
+ "u_model",
312
+ "u_sampler",
313
+ "u_alpha",
314
+ "u_colorMatrix",
315
+ "u_colorBias"
316
+ ];
317
+ var IMAGE_VOPACITY_ATTRIBUTES = ["a_position", "a_uv", "a_opacity"];
270
318
 
271
319
  // src/renderer/shaders/gradFill.ts
272
320
  var GRAD_VERT_SRC = (
@@ -1516,6 +1564,9 @@ function _resetPaintKindsForTests() {
1516
1564
  seedBuiltins();
1517
1565
  bumpNodeMemoGeneration();
1518
1566
  }
1567
+
1568
+ // src/renderer/DrawCommand.ts
1569
+ var SPRITE_STRIDE = 9;
1519
1570
  function tessellate(path, opts = {}) {
1520
1571
  if (path.kind === "rect") return tessellateRect(path);
1521
1572
  return tessellatePolygon(path, opts);
@@ -3240,6 +3291,210 @@ var SolidBatch = class {
3240
3291
  }
3241
3292
  };
3242
3293
 
3294
+ // src/renderer/imageBatch.ts
3295
+ var MAX_IMAGE_VERTICES_PER_BATCH = 32768;
3296
+ var FLOATS_PER_VERTEX2 = 5;
3297
+ var INITIAL_QUADS = 64;
3298
+ var TIERS = [
3299
+ { quads: 1, slots: 64 },
3300
+ { quads: 16, slots: 64 },
3301
+ { quads: 256, slots: 16 }
3302
+ ];
3303
+ TIERS[0].slots;
3304
+ var IMAGE_LARGE_RING_SIZE = 4;
3305
+ function doubledTo2(from, need) {
3306
+ let capacity = from;
3307
+ while (capacity < need) capacity *= 2;
3308
+ return capacity;
3309
+ }
3310
+ function quadIndices(quads) {
3311
+ const out = new Uint32Array(quads * 6);
3312
+ for (let q = 0; q < quads; q++) {
3313
+ const b = q * 4;
3314
+ const j = q * 6;
3315
+ out[j] = b;
3316
+ out[j + 1] = b + 1;
3317
+ out[j + 2] = b + 2;
3318
+ out[j + 3] = b;
3319
+ out[j + 4] = b + 2;
3320
+ out[j + 5] = b + 3;
3321
+ }
3322
+ return out;
3323
+ }
3324
+ var ImageBatch = class {
3325
+ gl;
3326
+ aPos;
3327
+ aUv;
3328
+ aOpacity;
3329
+ /** One ring per tier, cycled per flush. Slots are created on first use, so a
3330
+ * renderer that flushes rarely allocates as few as it flushes. */
3331
+ rings = TIERS.map((t) => new Array(t.slots).fill(void 0));
3332
+ nextInRing = TIERS.map(() => 0);
3333
+ /** For flushes past the largest tier; these sets grow to fit. */
3334
+ largeRing = new Array(IMAGE_LARGE_RING_SIZE).fill(void 0);
3335
+ nextLarge = 0;
3336
+ verts = new Float32Array(INITIAL_QUADS * 4 * FLOATS_PER_VERTEX2);
3337
+ nQuads = 0;
3338
+ constructor(gl, prog) {
3339
+ const aPos = prog.attribute("a_position");
3340
+ const aUv = prog.attribute("a_uv");
3341
+ const aOpacity = prog.attribute("a_opacity");
3342
+ if (aPos === void 0 || aUv === void 0 || aOpacity === void 0) {
3343
+ throw new Error(
3344
+ "ImageBatch: vertex-opacity program is missing a_position / a_uv / a_opacity"
3345
+ );
3346
+ }
3347
+ this.gl = gl;
3348
+ this.aPos = aPos;
3349
+ this.aUv = aUv;
3350
+ this.aOpacity = aOpacity;
3351
+ }
3352
+ /** Indices staged — what a caller passes to `drawElements`. */
3353
+ get length() {
3354
+ return this.nQuads * 6;
3355
+ }
3356
+ get quads() {
3357
+ return this.nQuads;
3358
+ }
3359
+ /** Whether staging one more quad would put the run past the per-flush cap. */
3360
+ wouldOverflow() {
3361
+ return (this.nQuads + 1) * 4 > MAX_IMAGE_VERTICES_PER_BATCH;
3362
+ }
3363
+ /**
3364
+ * Append one image quad: the destination rect `(x, y, w, h)` mapped through
3365
+ * `m`, sampling `(u0, v0)`-`(u1, v1)`, every corner carrying `opacity`.
3366
+ *
3367
+ * An affine maps a rect to a parallelogram, so two triangles still cover it
3368
+ * and the batch draws at `u_model` identity. Corners wind top-left,
3369
+ * top-right, bottom-right, bottom-left, with UVs following — the flips a
3370
+ * command asks for are already in the `u`/`v` the caller passes.
3371
+ */
3372
+ pushQuad(x, y, w, h, m, u0, v0, u1, v1, opacity) {
3373
+ this.reserve();
3374
+ const v = this.verts;
3375
+ let i = this.nQuads * 4 * FLOATS_PER_VERTEX2;
3376
+ const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];
3377
+ const x1 = x + w;
3378
+ const y1 = y + h;
3379
+ const ax = ma * x + mc * y + mtx, ay = mb * x + md * y + mty;
3380
+ const bx = ma * x1 + mc * y + mtx, by = mb * x1 + md * y + mty;
3381
+ const cx = ma * x1 + mc * y1 + mtx, cy = mb * x1 + md * y1 + mty;
3382
+ const dx = ma * x + mc * y1 + mtx, dy = mb * x + md * y1 + mty;
3383
+ v[i++] = ax;
3384
+ v[i++] = ay;
3385
+ v[i++] = u0;
3386
+ v[i++] = v0;
3387
+ v[i++] = opacity;
3388
+ v[i++] = bx;
3389
+ v[i++] = by;
3390
+ v[i++] = u1;
3391
+ v[i++] = v0;
3392
+ v[i++] = opacity;
3393
+ v[i++] = cx;
3394
+ v[i++] = cy;
3395
+ v[i++] = u1;
3396
+ v[i++] = v1;
3397
+ v[i++] = opacity;
3398
+ v[i++] = dx;
3399
+ v[i++] = dy;
3400
+ v[i++] = u0;
3401
+ v[i++] = v1;
3402
+ v[i++] = opacity;
3403
+ this.nQuads += 1;
3404
+ }
3405
+ /** Upload the staged geometry into the next set of buffers and bind its VAO.
3406
+ * Returns the index count for the caller's `drawElements`. */
3407
+ uploadAndBind() {
3408
+ const gl = this.gl;
3409
+ const tier = TIERS.findIndex((t) => this.nQuads <= t.quads);
3410
+ const set = tier < 0 ? this.nextLargeSlot() : this.nextRingSlot(tier);
3411
+ gl.bindVertexArray(set.vao);
3412
+ gl.bindBuffer(gl.ARRAY_BUFFER, set.vbo);
3413
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verts, 0, this.nQuads * 4 * FLOATS_PER_VERTEX2);
3414
+ return this.nQuads * 6;
3415
+ }
3416
+ reset() {
3417
+ this.nQuads = 0;
3418
+ }
3419
+ dispose() {
3420
+ for (const set of [...this.rings.flat(), ...this.largeRing]) {
3421
+ if (set) this.deleteSet(set);
3422
+ }
3423
+ for (const ring of this.rings) ring.fill(void 0);
3424
+ this.largeRing.fill(void 0);
3425
+ }
3426
+ /** Grow the CPU arrays so one more quad fits. */
3427
+ reserve() {
3428
+ const need = (this.nQuads + 1) * 4 * FLOATS_PER_VERTEX2;
3429
+ if (need <= this.verts.length) return;
3430
+ const grown = new Float32Array(doubledTo2(this.verts.length, need));
3431
+ grown.set(this.verts);
3432
+ this.verts = grown;
3433
+ }
3434
+ nextRingSlot(tier) {
3435
+ const ring = this.rings[tier];
3436
+ const slot = this.nextInRing[tier];
3437
+ this.nextInRing[tier] = (slot + 1) % ring.length;
3438
+ const existing = ring[slot];
3439
+ if (existing) return existing;
3440
+ const set = this.createSet(TIERS[tier].quads);
3441
+ ring[slot] = set;
3442
+ return set;
3443
+ }
3444
+ nextLargeSlot() {
3445
+ const gl = this.gl;
3446
+ const slot = this.nextLarge;
3447
+ this.nextLarge = (slot + 1) % IMAGE_LARGE_RING_SIZE;
3448
+ let set = this.largeRing[slot];
3449
+ if (!set) {
3450
+ set = this.createSet(doubledTo2(TIERS[TIERS.length - 1].quads, this.nQuads));
3451
+ this.largeRing[slot] = set;
3452
+ return set;
3453
+ }
3454
+ if (this.nQuads > set.quadCapacity) {
3455
+ set.quadCapacity = doubledTo2(set.quadCapacity, this.nQuads);
3456
+ gl.bindVertexArray(set.vao);
3457
+ gl.bindBuffer(gl.ARRAY_BUFFER, set.vbo);
3458
+ gl.bufferData(
3459
+ gl.ARRAY_BUFFER,
3460
+ set.quadCapacity * 4 * FLOATS_PER_VERTEX2 * 4,
3461
+ gl.DYNAMIC_DRAW
3462
+ );
3463
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, set.ibo);
3464
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, quadIndices(set.quadCapacity), gl.STATIC_DRAW);
3465
+ gl.bindVertexArray(null);
3466
+ }
3467
+ return set;
3468
+ }
3469
+ createSet(quads) {
3470
+ const gl = this.gl;
3471
+ const vao = gl.createVertexArray();
3472
+ const vbo = gl.createBuffer();
3473
+ const ibo = gl.createBuffer();
3474
+ if (!vao || !vbo || !ibo) throw new Error("ImageBatch: failed to create GL objects");
3475
+ const stride = FLOATS_PER_VERTEX2 * 4;
3476
+ gl.bindVertexArray(vao);
3477
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
3478
+ gl.bufferData(gl.ARRAY_BUFFER, quads * 4 * stride, gl.DYNAMIC_DRAW);
3479
+ gl.enableVertexAttribArray(this.aPos);
3480
+ gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, stride, 0);
3481
+ gl.enableVertexAttribArray(this.aUv);
3482
+ gl.vertexAttribPointer(this.aUv, 2, gl.FLOAT, false, stride, 8);
3483
+ gl.enableVertexAttribArray(this.aOpacity);
3484
+ gl.vertexAttribPointer(this.aOpacity, 1, gl.FLOAT, false, stride, 16);
3485
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
3486
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, quadIndices(quads), gl.STATIC_DRAW);
3487
+ gl.bindVertexArray(null);
3488
+ return { vao, vbo, ibo, quadCapacity: quads };
3489
+ }
3490
+ deleteSet(set) {
3491
+ const gl = this.gl;
3492
+ gl.deleteBuffer(set.vbo);
3493
+ gl.deleteBuffer(set.ibo);
3494
+ gl.deleteVertexArray(set.vao);
3495
+ }
3496
+ };
3497
+
3243
3498
  // src/renderer/draw.ts
3244
3499
  var FRAME_UPLOADS = /* @__PURE__ */ new WeakMap();
3245
3500
  var FRAME_PROJ = /* @__PURE__ */ new WeakMap();
@@ -3334,13 +3589,14 @@ function dispatch(ctx, cmd) {
3334
3589
  case "path":
3335
3590
  return drawPath(ctx, cmd);
3336
3591
  case "text":
3337
- flushSolids(ctx);
3592
+ flushBatches(ctx);
3338
3593
  return drawText(ctx, cmd);
3339
3594
  case "image":
3340
- flushSolids(ctx);
3341
3595
  return drawImage(ctx, cmd);
3596
+ case "sprites":
3597
+ return drawSprites(ctx, cmd);
3342
3598
  case "shader":
3343
- flushSolids(ctx);
3599
+ flushBatches(ctx);
3344
3600
  return drawShader(ctx, cmd);
3345
3601
  }
3346
3602
  }
@@ -3517,6 +3773,7 @@ function canBatchMesh(mesh) {
3517
3773
  return !mesh.requiresStencil && mesh.vertices.length >> 1 <= MAX_BATCHED_MESH_VERTICES;
3518
3774
  }
3519
3775
  function stageSolid(ctx, vertices) {
3776
+ flushImages(ctx);
3520
3777
  if (ctx.solidState !== void 0 && !stagedStateIsLive(ctx, ctx.solidState)) flushSolids(ctx);
3521
3778
  if (ctx.solidBatch.wouldOverflow(vertices)) flushSolids(ctx);
3522
3779
  if (ctx.solidState === void 0) {
@@ -3560,7 +3817,7 @@ function tryStageSolid(ctx, mesh, paint) {
3560
3817
  pushMesh(ctx, mesh, paint);
3561
3818
  return true;
3562
3819
  }
3563
- flushSolids(ctx);
3820
+ flushBatches(ctx);
3564
3821
  return false;
3565
3822
  }
3566
3823
  function flushSolids(ctx) {
@@ -3581,6 +3838,56 @@ function flushSolids(ctx) {
3581
3838
  batch.reset();
3582
3839
  ctx.solidState = void 0;
3583
3840
  }
3841
+ function stagedImageStateIsLive(ctx, staged, image, sampling) {
3842
+ if (staged.image !== image) return false;
3843
+ if (staged.sampling !== sampling) return false;
3844
+ if (staged.clipDepth !== ctx.clipDepth) return false;
3845
+ const colorMatrix = ctx.state.colorMatrix;
3846
+ return staged.colorMatrix === colorMatrix || sameValues(staged.colorMatrix, colorMatrix);
3847
+ }
3848
+ function stageImage(ctx, image, sampling) {
3849
+ flushSolids(ctx);
3850
+ if (ctx.imageState !== void 0 && !stagedImageStateIsLive(ctx, ctx.imageState, image, sampling)) {
3851
+ flushImages(ctx);
3852
+ }
3853
+ if (ctx.imageBatch.wouldOverflow()) flushImages(ctx);
3854
+ if (ctx.imageState === void 0) {
3855
+ ctx.imageState = {
3856
+ image,
3857
+ sampling,
3858
+ colorMatrix: ctx.state.colorMatrix,
3859
+ clipDepth: ctx.clipDepth
3860
+ };
3861
+ }
3862
+ }
3863
+ function flushImages(ctx) {
3864
+ const batch = ctx.imageBatch;
3865
+ const staged = ctx.imageState;
3866
+ if (batch.length === 0 || staged === void 0) return;
3867
+ const gl = ctx.gl;
3868
+ const prog = ctx.imageFillVOpacity;
3869
+ gl.useProgram(prog.handle);
3870
+ const indexCount = batch.uploadAndBind();
3871
+ setProjAndModel(ctx, prog, BATCH_MODEL);
3872
+ setAlphaUniform(ctx, prog, 1);
3873
+ setColorMatrixUniforms(ctx, prog, staged.colorMatrix);
3874
+ ctx.imageCache.bind(staged.image, 0);
3875
+ gl.texParameteri(
3876
+ gl.TEXTURE_2D,
3877
+ gl.TEXTURE_MAG_FILTER,
3878
+ staged.sampling === "nearest" ? gl.NEAREST : gl.LINEAR
3879
+ );
3880
+ gl.uniform1i(prog.uniform("u_sampler"), 0);
3881
+ applyClipTest(ctx, staged.clipDepth);
3882
+ gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_INT, 0);
3883
+ gl.bindVertexArray(null);
3884
+ batch.reset();
3885
+ ctx.imageState = void 0;
3886
+ }
3887
+ function flushBatches(ctx) {
3888
+ flushSolids(ctx);
3889
+ flushImages(ctx);
3890
+ }
3584
3891
  function expandAnchorColors(perAnchor, handle) {
3585
3892
  const aA = handle.anchorA;
3586
3893
  const aB = handle.anchorB;
@@ -3784,7 +4091,7 @@ function rasterizePathToStencil(ctx, path) {
3784
4091
  gl.bindVertexArray(null);
3785
4092
  }
3786
4093
  function pushClip(ctx, path, newDepth) {
3787
- flushSolids(ctx);
4094
+ flushBatches(ctx);
3788
4095
  const gl = ctx.gl;
3789
4096
  const ancestors = ancestorMask(newDepth - 1);
3790
4097
  const newBit = 1 << newDepth;
@@ -3799,7 +4106,7 @@ function pushClip(ctx, path, newDepth) {
3799
4106
  gl.stencilMask(255);
3800
4107
  }
3801
4108
  function popClip(ctx, path, oldDepth) {
3802
- flushSolids(ctx);
4109
+ flushBatches(ctx);
3803
4110
  const gl = ctx.gl;
3804
4111
  gl.enable(gl.STENCIL_TEST);
3805
4112
  const oldBit = 1 << oldDepth + 1;
@@ -3846,7 +4153,7 @@ function drawPathStroke(ctx, rawCmd) {
3846
4153
  const stroke = cmd.stroke;
3847
4154
  const align = stroke.align ?? "center";
3848
4155
  if (cmd.path.kind === "polygon" && align !== "center") {
3849
- flushSolids(ctx);
4156
+ flushBatches(ctx);
3850
4157
  drawPathStrokeStenciled(ctx, cmd, align);
3851
4158
  return;
3852
4159
  }
@@ -4194,45 +4501,6 @@ function drawTextGroup(ctx, group, prog, dx, dy) {
4194
4501
  gl.drawElements(gl.TRIANGLES, group.quads.length * 6, gl.UNSIGNED_INT, 0);
4195
4502
  gl.bindVertexArray(null);
4196
4503
  }
4197
- var IMAGE_QUAD_RING_SIZE = 64;
4198
- var IMAGE_QUAD_RINGS = /* @__PURE__ */ new WeakMap();
4199
- var IMAGE_QUAD_INDICES = new Uint32Array([0, 1, 2, 1, 3, 2]);
4200
- var IMAGE_QUAD_STRIDE = 16;
4201
- function imageQuadRing(gl, prog) {
4202
- const existing = IMAGE_QUAD_RINGS.get(prog);
4203
- if (existing) return existing;
4204
- const aPosLoc = prog.attribute("a_position");
4205
- const aUvLoc = prog.attribute("a_uv");
4206
- const ibo = gl.createBuffer();
4207
- if (!ibo) throw new Error("drawImage: createBuffer (IBO) returned null");
4208
- const vaos = [];
4209
- const vbos = [];
4210
- for (let slot = 0; slot < IMAGE_QUAD_RING_SIZE; slot++) {
4211
- const vao = gl.createVertexArray();
4212
- const vbo = gl.createBuffer();
4213
- if (!vao) throw new Error("drawImage: createVertexArray returned null");
4214
- if (!vbo) throw new Error("drawImage: createBuffer returned null");
4215
- gl.bindVertexArray(vao);
4216
- gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
4217
- gl.bufferData(gl.ARRAY_BUFFER, 4 * IMAGE_QUAD_STRIDE, gl.DYNAMIC_DRAW);
4218
- if (aPosLoc !== void 0) {
4219
- gl.enableVertexAttribArray(aPosLoc);
4220
- gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, IMAGE_QUAD_STRIDE, 0);
4221
- }
4222
- if (aUvLoc !== void 0) {
4223
- gl.enableVertexAttribArray(aUvLoc);
4224
- gl.vertexAttribPointer(aUvLoc, 2, gl.FLOAT, false, IMAGE_QUAD_STRIDE, 8);
4225
- }
4226
- gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
4227
- if (slot === 0) gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, IMAGE_QUAD_INDICES, gl.STATIC_DRAW);
4228
- gl.bindVertexArray(null);
4229
- vaos.push(vao);
4230
- vbos.push(vbo);
4231
- }
4232
- const ring = { vaos, vbos, ibo, next: 0 };
4233
- IMAGE_QUAD_RINGS.set(prog, ring);
4234
- return ring;
4235
- }
4236
4504
  var TEXT_QUAD_RING_SIZE = 64;
4237
4505
  var TEXT_QUAD_RINGS = /* @__PURE__ */ new WeakMap();
4238
4506
  function grownTo(need) {
@@ -4310,22 +4578,9 @@ function disposeTextQuads(gl, prog) {
4310
4578
  gl.deleteBuffer(ring.ibo);
4311
4579
  TEXT_QUAD_RINGS.delete(prog);
4312
4580
  }
4313
- function disposeImageQuads(gl, prog) {
4314
- const ring = IMAGE_QUAD_RINGS.get(prog);
4315
- if (!ring) return;
4316
- for (const vao of ring.vaos) gl.deleteVertexArray(vao);
4317
- for (const vbo of ring.vbos) gl.deleteBuffer(vbo);
4318
- gl.deleteBuffer(ring.ibo);
4319
- IMAGE_QUAD_RINGS.delete(prog);
4320
- }
4321
- var IMAGE_QUAD_VERTICES = new Float32Array(16);
4322
4581
  function drawImage(ctx, cmd) {
4323
4582
  ctx.imageCache.upload(cmd.image, cmd.image);
4324
- const gl = ctx.gl;
4325
- const prog = ctx.imageFill;
4326
- gl.useProgram(prog.handle);
4327
- const x0 = cmd.x, y0 = cmd.y;
4328
- const x1 = cmd.x + cmd.w, y1 = cmd.y + cmd.h;
4583
+ stageImage(ctx, cmd.image, cmd.sampling ?? "linear");
4329
4584
  const src = cmd.source;
4330
4585
  let u0 = 0, v0 = 0, u1 = 1, v1 = 1;
4331
4586
  if (src) {
@@ -4345,43 +4600,50 @@ function drawImage(ctx, cmd) {
4345
4600
  v0 = v1;
4346
4601
  v1 = t;
4347
4602
  }
4348
- const v = IMAGE_QUAD_VERTICES;
4349
- v[0] = x0;
4350
- v[1] = y0;
4351
- v[2] = u0;
4352
- v[3] = v0;
4353
- v[4] = x1;
4354
- v[5] = y0;
4355
- v[6] = u1;
4356
- v[7] = v0;
4357
- v[8] = x0;
4358
- v[9] = y1;
4359
- v[10] = u0;
4360
- v[11] = v1;
4361
- v[12] = x1;
4362
- v[13] = y1;
4363
- v[14] = u1;
4364
- v[15] = v1;
4365
- const ring = imageQuadRing(gl, prog);
4366
- const slot = ring.next;
4367
- ring.next = (slot + 1) % IMAGE_QUAD_RING_SIZE;
4368
- gl.bindVertexArray(ring.vaos[slot]);
4369
- gl.bindBuffer(gl.ARRAY_BUFFER, ring.vbos[slot]);
4370
- gl.bufferSubData(gl.ARRAY_BUFFER, 0, v);
4371
- setProjAndModel(ctx, prog);
4372
- setColorMatrixUniforms(ctx, prog);
4373
- ctx.imageCache.bind(cmd.image, 0);
4374
- gl.texParameteri(
4375
- gl.TEXTURE_2D,
4376
- gl.TEXTURE_MAG_FILTER,
4377
- cmd.sampling === "nearest" ? gl.NEAREST : gl.LINEAR
4603
+ ctx.imageBatch.pushQuad(
4604
+ cmd.x,
4605
+ cmd.y,
4606
+ cmd.w,
4607
+ cmd.h,
4608
+ ctx.state.transform,
4609
+ u0,
4610
+ v0,
4611
+ u1,
4612
+ v1,
4613
+ (cmd.opacity ?? 1) * ctx.state.alpha
4378
4614
  );
4379
- gl.uniform1i(prog.uniform("u_sampler"), 0);
4380
- gl.uniform1f(prog.uniform("u_opacity"), cmd.opacity ?? 1);
4381
- setAlphaUniform(ctx, prog, ctx.state.alpha);
4382
- applyClipTest(ctx);
4383
- gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 0);
4384
- gl.bindVertexArray(null);
4615
+ }
4616
+ function drawSprites(ctx, cmd) {
4617
+ const data = cmd.sprites;
4618
+ const count = Math.floor(data.length / SPRITE_STRIDE);
4619
+ if (count === 0) return;
4620
+ ctx.imageCache.upload(cmd.image, cmd.image);
4621
+ const sampling = cmd.sampling ?? "linear";
4622
+ stageImage(ctx, cmd.image, sampling);
4623
+ const batch = ctx.imageBatch;
4624
+ const m = ctx.state.transform;
4625
+ const groupAlpha = ctx.state.alpha;
4626
+ const tw = cmd.image.width;
4627
+ const th = cmd.image.height;
4628
+ for (let s = 0, i = 0; s < count; s++, i += SPRITE_STRIDE) {
4629
+ if (batch.wouldOverflow()) {
4630
+ flushImages(ctx);
4631
+ stageImage(ctx, cmd.image, sampling);
4632
+ }
4633
+ const sx = data[i + 4], sy = data[i + 5], sw = data[i + 6], sh = data[i + 7];
4634
+ batch.pushQuad(
4635
+ data[i],
4636
+ data[i + 1],
4637
+ data[i + 2],
4638
+ data[i + 3],
4639
+ m,
4640
+ sx / tw,
4641
+ sy / th,
4642
+ (sx + sw) / tw,
4643
+ (sy + sh) / th,
4644
+ data[i + 8] * groupAlpha
4645
+ );
4646
+ }
4385
4647
  }
4386
4648
 
4387
4649
  // src/renderer/shaders/customPrelude.ts
@@ -4484,6 +4746,7 @@ var WeaselRenderer = class {
4484
4746
  textSdf;
4485
4747
  textSdfR8;
4486
4748
  imageFill;
4749
+ imageFillVOpacity;
4487
4750
  gradFill;
4488
4751
  patternFill;
4489
4752
  meshCache;
@@ -4494,6 +4757,7 @@ var WeaselRenderer = class {
4494
4757
  quadVbo = null;
4495
4758
  quadIbo = null;
4496
4759
  solidBatch;
4760
+ imageBatch;
4497
4761
  groupState = new GroupState();
4498
4762
  widthCss;
4499
4763
  heightCss;
@@ -4558,6 +4822,13 @@ var WeaselRenderer = class {
4558
4822
  this.imageFill = new ShaderProgram(this.gl, IMAGE_VERT_SRC, IMAGE_FRAG_SRC);
4559
4823
  this.imageFill.lookupUniforms(IMAGE_FILL_UNIFORMS);
4560
4824
  this.imageFill.lookupAttributes(IMAGE_FILL_ATTRIBUTES);
4825
+ this.imageFillVOpacity = new ShaderProgram(
4826
+ this.gl,
4827
+ IMAGE_VOPACITY_VERT_SRC,
4828
+ IMAGE_VOPACITY_FRAG_SRC
4829
+ );
4830
+ this.imageFillVOpacity.lookupUniforms(IMAGE_VOPACITY_UNIFORMS);
4831
+ this.imageFillVOpacity.lookupAttributes(IMAGE_VOPACITY_ATTRIBUTES);
4561
4832
  this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
4562
4833
  this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
4563
4834
  this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
@@ -4572,6 +4843,7 @@ var WeaselRenderer = class {
4572
4843
  this.gradRampCache = new GradientRampCache(this.gl);
4573
4844
  this.uploadQuadGeometry();
4574
4845
  this.solidBatch = new SolidBatch(this.gl, this.pathFillVColor);
4846
+ this.imageBatch = new ImageBatch(this.gl, this.imageFillVOpacity);
4575
4847
  }
4576
4848
  uploadQuadGeometry() {
4577
4849
  const gl = this.gl;
@@ -4695,6 +4967,13 @@ var WeaselRenderer = class {
4695
4967
  this.imageFill = new ShaderProgram(this.gl, IMAGE_VERT_SRC, IMAGE_FRAG_SRC);
4696
4968
  this.imageFill.lookupUniforms(IMAGE_FILL_UNIFORMS);
4697
4969
  this.imageFill.lookupAttributes(IMAGE_FILL_ATTRIBUTES);
4970
+ this.imageFillVOpacity = new ShaderProgram(
4971
+ this.gl,
4972
+ IMAGE_VOPACITY_VERT_SRC,
4973
+ IMAGE_VOPACITY_FRAG_SRC
4974
+ );
4975
+ this.imageFillVOpacity.lookupUniforms(IMAGE_VOPACITY_UNIFORMS);
4976
+ this.imageFillVOpacity.lookupAttributes(IMAGE_VOPACITY_ATTRIBUTES);
4698
4977
  this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
4699
4978
  this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
4700
4979
  this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
@@ -4710,6 +4989,7 @@ var WeaselRenderer = class {
4710
4989
  markAllFontsNotUploaded();
4711
4990
  this.uploadQuadGeometry();
4712
4991
  this.solidBatch = new SolidBatch(this.gl, this.pathFillVColor);
4992
+ this.imageBatch = new ImageBatch(this.gl, this.imageFillVOpacity);
4713
4993
  for (const id of this.programRegistry.keys()) {
4714
4994
  const src = getProgramSource(id);
4715
4995
  if (!src) continue;
@@ -4758,7 +5038,7 @@ var WeaselRenderer = class {
4758
5038
  }
4759
5039
  this.meshCache.freeTransient();
4760
5040
  this.meshCache.drainPendingDeletes();
4761
- for (const prog of [this.pathFill, this.pathFillVColor, this.textSdf, this.textSdfR8, this.imageFill, this.gradFill, this.patternFill]) {
5041
+ for (const prog of [this.pathFill, this.pathFillVColor, this.textSdf, this.textSdfR8, this.imageFill, this.imageFillVOpacity, this.gradFill, this.patternFill]) {
4762
5042
  gl.deleteProgram(prog.handle);
4763
5043
  }
4764
5044
  for (const prog of this.programRegistry.values()) {
@@ -4769,9 +5049,9 @@ var WeaselRenderer = class {
4769
5049
  this.gradRampCache.free();
4770
5050
  if (this.quadVbo) gl.deleteBuffer(this.quadVbo);
4771
5051
  if (this.quadIbo) gl.deleteBuffer(this.quadIbo);
4772
- disposeImageQuads(gl, this.imageFill);
4773
5052
  for (const prog of [this.textSdf, this.textSdfR8, this.pathFill]) disposeTextQuads(gl, prog);
4774
5053
  this.solidBatch.dispose();
5054
+ this.imageBatch.dispose();
4775
5055
  }
4776
5056
  /**
4777
5057
  * Draw one frame.
@@ -4800,6 +5080,7 @@ var WeaselRenderer = class {
4800
5080
  textSdf: this.textSdf,
4801
5081
  textSdfR8: this.textSdfR8,
4802
5082
  imageFill: this.imageFill,
5083
+ imageFillVOpacity: this.imageFillVOpacity,
4803
5084
  gradFill: this.gradFill,
4804
5085
  patternFill: this.patternFill,
4805
5086
  meshCache: this.meshCache,
@@ -4811,6 +5092,7 @@ var WeaselRenderer = class {
4811
5092
  quadVbo: this.quadVbo,
4812
5093
  quadIbo: this.quadIbo,
4813
5094
  solidBatch: this.solidBatch,
5095
+ imageBatch: this.imageBatch,
4814
5096
  state: this.groupState,
4815
5097
  widthCss: this.widthCss,
4816
5098
  heightCss: this.heightCss,
@@ -4820,7 +5102,7 @@ var WeaselRenderer = class {
4820
5102
  viewMatrix: viewMatrix2
4821
5103
  };
4822
5104
  for (const cmd of commands) dispatch(ctx, cmd);
4823
- flushSolids(ctx);
5105
+ flushBatches(ctx);
4824
5106
  this.meshCache.freeTransient();
4825
5107
  }
4826
5108
  resize(dims) {
@@ -4854,6 +5136,10 @@ var WeaselRenderer = class {
4854
5136
  return this.solidBatch;
4855
5137
  }
4856
5138
  /** @internal */
5139
+ _imageBatch() {
5140
+ return this.imageBatch;
5141
+ }
5142
+ /** @internal */
4857
5143
  _textSdf() {
4858
5144
  return this.textSdf;
4859
5145
  }
@@ -4866,6 +5152,10 @@ var WeaselRenderer = class {
4866
5152
  return this.imageFill;
4867
5153
  }
4868
5154
  /** @internal */
5155
+ _imageFillVOpacity() {
5156
+ return this.imageFillVOpacity;
5157
+ }
5158
+ /** @internal */
4869
5159
  _gradFill() {
4870
5160
  return this.gradFill;
4871
5161
  }
@@ -5914,6 +6204,86 @@ function clientToCanvasRect(rect, clientX, clientY) {
5914
6204
  return [clientX - rect.left, clientY - rect.top];
5915
6205
  }
5916
6206
 
6207
+ // src/interactions/gestures/pointerSession/recovery.ts
6208
+ var LOST_CAPTURE_EVENT = "lostpointercapture";
6209
+ function reportsButtons(down) {
6210
+ return !!down.buttons;
6211
+ }
6212
+ function isMissedRelease(move) {
6213
+ return move.buttons === 0;
6214
+ }
6215
+
6216
+ // src/interactions/gestures/pointerSession/pointerSession.ts
6217
+ function openPointerSession(origin, down, cb, opts = {}) {
6218
+ const pointerId = down.pointerId;
6219
+ const doc = origin.ownerDocument ?? document;
6220
+ const buttonsReported = reportsButtons(down);
6221
+ let active = true;
6222
+ const teardown = () => {
6223
+ active = false;
6224
+ doc.removeEventListener("pointermove", onMove, true);
6225
+ doc.removeEventListener("pointerup", onUp, true);
6226
+ doc.removeEventListener("pointercancel", onPointerCancel, true);
6227
+ doc.removeEventListener("pointerdown", onRepress, true);
6228
+ origin.removeEventListener(LOST_CAPTURE_EVENT, onLostCapture);
6229
+ if (opts.capture !== false) {
6230
+ try {
6231
+ origin.releasePointerCapture?.(pointerId);
6232
+ } catch {
6233
+ }
6234
+ }
6235
+ };
6236
+ const mine = (e) => e.pointerId === pointerId;
6237
+ const end = (e) => {
6238
+ if (!active) return;
6239
+ teardown();
6240
+ cb.onEnd?.(e);
6241
+ };
6242
+ const abort = (reason) => {
6243
+ if (!active) return;
6244
+ teardown();
6245
+ cb.onCancel?.(reason);
6246
+ };
6247
+ function onMove(e) {
6248
+ if (!active || !mine(e)) return;
6249
+ if (buttonsReported && isMissedRelease(e)) {
6250
+ end(e);
6251
+ return;
6252
+ }
6253
+ cb.onMove?.(e);
6254
+ }
6255
+ function onUp(e) {
6256
+ if (mine(e)) end(e);
6257
+ }
6258
+ function onPointerCancel(e) {
6259
+ if (mine(e)) abort("pointercancel");
6260
+ }
6261
+ function onRepress(e) {
6262
+ if (mine(e)) abort("superseded");
6263
+ }
6264
+ function onLostCapture(e) {
6265
+ if (e.pointerId === pointerId) abort("lostcapture");
6266
+ }
6267
+ if (opts.capture !== false) {
6268
+ try {
6269
+ origin.setPointerCapture?.(pointerId);
6270
+ } catch {
6271
+ }
6272
+ }
6273
+ doc.addEventListener("pointermove", onMove, true);
6274
+ doc.addEventListener("pointerup", onUp, true);
6275
+ doc.addEventListener("pointercancel", onPointerCancel, true);
6276
+ doc.addEventListener("pointerdown", onRepress, true);
6277
+ origin.addEventListener(LOST_CAPTURE_EVENT, onLostCapture);
6278
+ return {
6279
+ pointerId,
6280
+ get active() {
6281
+ return active;
6282
+ },
6283
+ cancel: () => abort("aborted")
6284
+ };
6285
+ }
6286
+
5917
6287
  // src/interactions/gestures/handleDrag/handleDrag.ts
5918
6288
  function defaultRectEl(target) {
5919
6289
  if (target instanceof SVGElement && target.ownerSVGElement) {
@@ -5922,10 +6292,15 @@ function defaultRectEl(target) {
5922
6292
  return target;
5923
6293
  }
5924
6294
  function useHandleDrag(opts) {
5925
- const { onMove, onStart, onEnd, getRect } = opts;
6295
+ const { onMove, onStart, onEnd, onCancel, getRect } = opts;
6296
+ const live = useRef(null);
6297
+ useEffect(() => () => {
6298
+ live.current?.cancel();
6299
+ }, []);
5926
6300
  const onPointerDown = useCallback(
5927
6301
  (e) => {
5928
6302
  e.preventDefault();
6303
+ live.current?.cancel();
5929
6304
  const target = e.currentTarget;
5930
6305
  const rectEl = getRect ? getRect(target) : defaultRectEl(target);
5931
6306
  const rect = rectEl.getBoundingClientRect();
@@ -5933,26 +6308,24 @@ function useHandleDrag(opts) {
5933
6308
  const [x, y] = clientToCanvasRect(rect, clientX, clientY);
5934
6309
  return { x, y };
5935
6310
  };
5936
- target.setPointerCapture(e.pointerId);
5937
- onStart?.(localOf(e.clientX, e.clientY), e);
5938
- const move = (ev) => {
5939
- onMove(localOf(ev.clientX, ev.clientY), ev);
5940
- };
5941
- const end = (ev) => {
5942
- target.removeEventListener("pointermove", move);
5943
- target.removeEventListener("pointerup", end);
5944
- target.removeEventListener("pointercancel", end);
5945
- try {
5946
- target.releasePointerCapture(ev.pointerId);
5947
- } catch {
6311
+ let moved = false;
6312
+ live.current = openPointerSession(target, e, {
6313
+ onMove: (ev) => {
6314
+ moved = true;
6315
+ onMove(localOf(ev.clientX, ev.clientY), ev);
6316
+ },
6317
+ onEnd: (ev) => {
6318
+ live.current = null;
6319
+ onEnd?.({ point: localOf(ev.clientX, ev.clientY), moved, event: ev });
6320
+ },
6321
+ onCancel: (reason) => {
6322
+ live.current = null;
6323
+ onCancel?.(reason);
5948
6324
  }
5949
- onEnd?.(ev);
5950
- };
5951
- target.addEventListener("pointermove", move);
5952
- target.addEventListener("pointerup", end);
5953
- target.addEventListener("pointercancel", end);
6325
+ });
6326
+ onStart?.(localOf(e.clientX, e.clientY), e);
5954
6327
  },
5955
- [onMove, onStart, onEnd, getRect]
6328
+ [onMove, onStart, onEnd, onCancel, getRect]
5956
6329
  );
5957
6330
  return { onPointerDown };
5958
6331
  }
@@ -6008,57 +6381,53 @@ function positionGhost(el, x, y) {
6008
6381
  el.style.transform = `translate(${x - el.offsetWidth / 2}px, ${y - el.offsetHeight / 2}px) scale(0.92)`;
6009
6382
  }
6010
6383
  function beginPointerDrag(payload, source, startX, startY, ghostFn) {
6011
- if (activeDrag) return;
6384
+ if (activeDrag) return null;
6012
6385
  const ghost = ghostFn(source, payload);
6013
6386
  document.body.appendChild(ghost);
6014
6387
  positionGhost(ghost, startX, startY);
6015
6388
  activeDrag = { payload, ghost, lastZone: null };
6016
- function onMove(e) {
6017
- if (!activeDrag) return;
6018
- positionGhost(activeDrag.ghost, e.clientX, e.clientY);
6019
- const zone = findZone(e.clientX, e.clientY, payload.kind);
6020
- if (zone !== activeDrag.lastZone) {
6021
- activeDrag.lastZone?.onOver?.(false);
6022
- zone?.onOver?.(true);
6023
- activeDrag.lastZone = zone;
6024
- }
6025
- zone?.onMove?.(payload, e.clientX, e.clientY);
6026
- }
6027
- function onUp(e) {
6028
- if (!activeDrag) return;
6029
- const zone = findZone(e.clientX, e.clientY, payload.kind);
6030
- const dropped = activeDrag.payload;
6031
- activeDrag.lastZone?.onOver?.(false);
6032
- try {
6033
- if (zone) zone.onDrop(dropped, e.clientX, e.clientY);
6034
- } catch (err) {
6035
- console.error("weasel pointerDrag: drop zone onDrop threw", err);
6036
- } finally {
6037
- cleanup();
6038
- }
6039
- }
6040
- function onCancel() {
6041
- activeDrag?.lastZone?.onOver?.(false);
6042
- cleanup();
6043
- }
6044
- function cleanup() {
6389
+ const teardown = () => {
6045
6390
  activeDrag?.ghost.remove();
6046
6391
  activeDrag = null;
6047
- document.removeEventListener("pointermove", onMove);
6048
- document.removeEventListener("pointerup", onUp);
6049
- document.removeEventListener("pointercancel", onCancel);
6050
- }
6051
- document.addEventListener("pointermove", onMove);
6052
- document.addEventListener("pointerup", onUp);
6053
- document.addEventListener("pointercancel", onCancel);
6392
+ };
6393
+ return {
6394
+ move(e) {
6395
+ if (!activeDrag) return;
6396
+ positionGhost(activeDrag.ghost, e.clientX, e.clientY);
6397
+ const zone = findZone(e.clientX, e.clientY, payload.kind);
6398
+ if (zone !== activeDrag.lastZone) {
6399
+ activeDrag.lastZone?.onOver?.(false);
6400
+ zone?.onOver?.(true);
6401
+ activeDrag.lastZone = zone;
6402
+ }
6403
+ zone?.onMove?.(payload, e.clientX, e.clientY);
6404
+ },
6405
+ drop(e) {
6406
+ if (!activeDrag) return;
6407
+ const zone = findZone(e.clientX, e.clientY, payload.kind);
6408
+ const dropped = activeDrag.payload;
6409
+ activeDrag.lastZone?.onOver?.(false);
6410
+ try {
6411
+ if (zone) zone.onDrop(dropped, e.clientX, e.clientY);
6412
+ } catch (err) {
6413
+ console.error("weasel pointerDrag: drop zone onDrop threw", err);
6414
+ } finally {
6415
+ teardown();
6416
+ }
6417
+ },
6418
+ cancel() {
6419
+ activeDrag?.lastZone?.onOver?.(false);
6420
+ teardown();
6421
+ }
6422
+ };
6054
6423
  }
6055
6424
  var DRAG_THRESHOLD_PX_SQ = 25;
6056
6425
  function useDragHandle(getPayload, options) {
6057
6426
  const optsRef = useRef(options);
6058
6427
  optsRef.current = options;
6059
- const pendingTeardownRef = useRef(null);
6428
+ const live = useRef(null);
6060
6429
  useEffect(() => () => {
6061
- pendingTeardownRef.current?.();
6430
+ live.current?.cancel();
6062
6431
  }, []);
6063
6432
  const onPointerDown = useCallback((e) => {
6064
6433
  if (e.pointerType === "mouse" && e.button !== 0) return;
@@ -6066,41 +6435,46 @@ function useDragHandle(getPayload, options) {
6066
6435
  if (e.target instanceof HTMLElement && e.target.closest("input, button, select, textarea")) return;
6067
6436
  const startX = e.clientX;
6068
6437
  const startY = e.clientY;
6069
- let started = false;
6070
- function onMove(ev) {
6071
- if (started) return;
6072
- const dx = ev.clientX - startX;
6073
- const dy = ev.clientY - startY;
6074
- if (dx * dx + dy * dy > DRAG_THRESHOLD_PX_SQ) {
6075
- started = true;
6076
- const payload = getPayload();
6077
- cleanup();
6078
- if (payload) {
6079
- target.addEventListener(
6080
- "click",
6081
- (ce) => {
6082
- ce.stopPropagation();
6083
- ce.preventDefault();
6084
- },
6085
- { capture: true, once: true }
6086
- );
6087
- beginPointerDrag(payload, target, ev.clientX, ev.clientY, optsRef.current?.createGhost ?? defaultGhost);
6438
+ let ghost = null;
6439
+ let past = false;
6440
+ live.current = openPointerSession(target, e, {
6441
+ onMove: (ev) => {
6442
+ if (ghost) {
6443
+ ghost.move(ev);
6444
+ return;
6088
6445
  }
6446
+ if (past) return;
6447
+ const dx = ev.clientX - startX;
6448
+ const dy = ev.clientY - startY;
6449
+ if (dx * dx + dy * dy <= DRAG_THRESHOLD_PX_SQ) return;
6450
+ past = true;
6451
+ const payload = getPayload();
6452
+ if (!payload) return;
6453
+ target.addEventListener(
6454
+ "click",
6455
+ (ce) => {
6456
+ ce.stopPropagation();
6457
+ ce.preventDefault();
6458
+ },
6459
+ { capture: true, once: true }
6460
+ );
6461
+ ghost = beginPointerDrag(
6462
+ payload,
6463
+ target,
6464
+ ev.clientX,
6465
+ ev.clientY,
6466
+ optsRef.current?.createGhost ?? defaultGhost
6467
+ );
6468
+ },
6469
+ onEnd: (ev) => {
6470
+ live.current = null;
6471
+ ghost?.drop(ev);
6472
+ },
6473
+ onCancel: () => {
6474
+ live.current = null;
6475
+ ghost?.cancel();
6089
6476
  }
6090
- }
6091
- function onEnd() {
6092
- cleanup();
6093
- }
6094
- function cleanup() {
6095
- document.removeEventListener("pointermove", onMove);
6096
- document.removeEventListener("pointerup", onEnd);
6097
- document.removeEventListener("pointercancel", onEnd);
6098
- pendingTeardownRef.current = null;
6099
- }
6100
- document.addEventListener("pointermove", onMove);
6101
- document.addEventListener("pointerup", onEnd);
6102
- document.addEventListener("pointercancel", onEnd);
6103
- pendingTeardownRef.current = cleanup;
6477
+ }, { capture: false });
6104
6478
  }, [getPayload]);
6105
6479
  return { onPointerDown, style: { touchAction: "none" } };
6106
6480
  }
@@ -6127,47 +6501,34 @@ function startThresholdDrag(e, opts) {
6127
6501
  const startX = e.clientX;
6128
6502
  const startY = e.clientY;
6129
6503
  const threshold = opts.threshold ?? 4;
6130
- const target = e.currentTarget;
6131
- target.setPointerCapture(e.pointerId);
6132
6504
  let activated = false;
6133
- function maybeActivate(ev) {
6505
+ const maybeActivate = (ev) => {
6134
6506
  if (activated) return;
6135
6507
  const dx = ev.clientX - startX;
6136
6508
  const dy = ev.clientY - startY;
6137
6509
  if (dx * dx + dy * dy < threshold * threshold) return;
6138
6510
  activated = true;
6139
6511
  opts.onActivate?.(ev);
6140
- }
6141
- function onMove(ev) {
6142
- maybeActivate(ev);
6143
- if (!activated) return;
6144
- opts.onMove(ev);
6145
- }
6146
- function cleanup() {
6147
- document.removeEventListener("pointermove", onMove);
6148
- document.removeEventListener("pointerup", onUp);
6149
- document.removeEventListener("pointercancel", onCancel);
6150
- }
6151
- function onUp(ev) {
6152
- cleanup();
6153
- try {
6154
- target.releasePointerCapture(ev.pointerId);
6155
- } catch {
6156
- }
6157
- if (!activated) {
6512
+ };
6513
+ const session = openPointerSession(e.currentTarget, e, {
6514
+ onMove: (ev) => {
6515
+ maybeActivate(ev);
6516
+ if (activated) opts.onMove(ev);
6517
+ },
6518
+ onEnd: (ev) => {
6519
+ if (activated) opts.onCommit(ev);
6520
+ else opts.onCancel?.();
6521
+ },
6522
+ onCancel: () => {
6158
6523
  opts.onCancel?.();
6159
- return;
6160
6524
  }
6161
- opts.onCommit(ev);
6162
- }
6163
- function onCancel() {
6164
- cleanup();
6165
- opts.onCancel?.();
6166
- }
6167
- document.addEventListener("pointermove", onMove);
6168
- document.addEventListener("pointerup", onUp);
6169
- document.addEventListener("pointercancel", onCancel);
6170
- return { isDragging: () => activated };
6525
+ });
6526
+ return {
6527
+ isDragging: () => activated,
6528
+ cancel: () => {
6529
+ session.cancel();
6530
+ }
6531
+ };
6171
6532
  }
6172
6533
 
6173
6534
  // src/core/device/targets.ts
@@ -6732,8 +7093,8 @@ function runLayoutPass(scratch, moveCtx) {
6732
7093
  for (const [cid, pose] of srcLayout.childPoses(srcContainer, srcChildren)) {
6733
7094
  const cur = composeWorldPose(poseAdapter, cid, pc.compose);
6734
7095
  const next = pose;
6735
- const same = cur.x === next.x && cur.y === next.y && cur.width === next.width && cur.height === next.height;
6736
- if (same) continue;
7096
+ const same2 = cur.x === next.x && cur.y === next.y && cur.width === next.width && cur.height === next.height;
7097
+ if (same2) continue;
6737
7098
  sourceReflow.set(cid, pose);
6738
7099
  const parent = scene.get(asNodeId(cid))?.parent ?? null;
6739
7100
  scratch.previews.set(asNodeId(cid), rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose));
@@ -8126,6 +8487,11 @@ var cloneAction = {
8126
8487
  id: "clone",
8127
8488
  label: "Clone",
8128
8489
  defaultBinding: { kind: "drag" },
8490
+ // The platform's duplicate cursor, and the whole reason this field is
8491
+ // modifier-aware: the select tool binds clone behind `mods: { alt: true }`,
8492
+ // so the pump predicts it — and shows this — only while Alt is down.
8493
+ cursor: "copy",
8494
+ activeCursor: "copy",
8129
8495
  eligible: { capability: ["edits-page", "creates-selection"] },
8130
8496
  requires: ["selection", "scene", "applyOps"],
8131
8497
  invoker: {
@@ -8211,6 +8577,30 @@ var cloneAction = {
8211
8577
  enabled: () => true
8212
8578
  };
8213
8579
 
8580
+ // src/core/viewport/createVelocityTracker.ts
8581
+ function createVelocityTracker() {
8582
+ let samples = [];
8583
+ return {
8584
+ record(dx, dy, t) {
8585
+ samples.push({ dx, dy, t });
8586
+ const cutoff = t - 100;
8587
+ samples = samples.filter((s) => s.t >= cutoff);
8588
+ },
8589
+ getVelocity() {
8590
+ const s = samples;
8591
+ if (s.length < 2) return { vx: 0, vy: 0 };
8592
+ const dt = s[s.length - 1].t - s[0].t;
8593
+ if (dt === 0) return { vx: 0, vy: 0 };
8594
+ const totalDx = s.slice(1).reduce((acc, p) => acc + p.dx, 0);
8595
+ const totalDy = s.slice(1).reduce((acc, p) => acc + p.dy, 0);
8596
+ return { vx: totalDx / dt, vy: totalDy / dt };
8597
+ },
8598
+ reset() {
8599
+ samples = [];
8600
+ }
8601
+ };
8602
+ }
8603
+
8214
8604
  // src/interactions/actions/defaults/viewportDragPan.ts
8215
8605
  var viewportDragPanAction = {
8216
8606
  id: "viewport.dragPan",
@@ -8229,17 +8619,37 @@ var viewportDragPanAction = {
8229
8619
  requires: ["view"],
8230
8620
  invoker: {
8231
8621
  timing: "ongoing",
8232
- start(ctx) {
8622
+ start(ctx, opts) {
8233
8623
  const view = ctx.deps.view;
8234
8624
  if (!view) return {};
8235
8625
  const startView = view.get();
8236
- const scratch = { startView, view };
8626
+ const params = resolveParams(opts?.params) ?? {};
8627
+ const inertia = params.inertia === false ? void 0 : params.inertia;
8628
+ const scratch = {
8629
+ startView,
8630
+ view,
8631
+ axis: params.axis ?? "both",
8632
+ inertia,
8633
+ // Only track when a coast could actually happen, so the common
8634
+ // no-inertia drag does no per-move work.
8635
+ tracker: inertia && view.decay ? createVelocityTracker() : void 0,
8636
+ lastScreen: { x: 0, y: 0 }
8637
+ };
8237
8638
  return {
8238
8639
  kind: "pan",
8239
8640
  onMove(moveCtx) {
8240
8641
  if (!moveCtx.drag) return;
8241
8642
  const screen = moveCtx.drag.screenDelta ?? moveCtx.drag.delta;
8242
- const { x: screenDx, y: screenDy } = screen;
8643
+ const screenDx = scratch.axis === "y" ? 0 : screen.x;
8644
+ const screenDy = scratch.axis === "x" ? 0 : screen.y;
8645
+ if (scratch.tracker) {
8646
+ scratch.tracker.record(
8647
+ screenDx - scratch.lastScreen.x,
8648
+ screenDy - scratch.lastScreen.y,
8649
+ performance.now()
8650
+ );
8651
+ scratch.lastScreen = { x: screenDx, y: screenDy };
8652
+ }
8243
8653
  const sv = scratch.startView;
8244
8654
  scratch.view.set({
8245
8655
  ...sv,
@@ -8247,7 +8657,24 @@ var viewportDragPanAction = {
8247
8657
  y: sv.y - screenDy / sv.scale.y
8248
8658
  });
8249
8659
  },
8250
- onEnd() {
8660
+ onEnd(_endCtx, reason) {
8661
+ const { tracker, inertia: inertia2, view: v } = scratch;
8662
+ if (reason === "cancel" || !tracker || !inertia2 || !v.decay) return;
8663
+ const { vx, vy } = tracker.getVelocity();
8664
+ const scale2 = scratch.startView.scale;
8665
+ v.decay({
8666
+ // Screen px/ms → world units/ms, matching the onMove conversion.
8667
+ velocity: { vx: -vx / scale2.x, vy: -vy / scale2.y },
8668
+ friction: inertia2.friction,
8669
+ minSpeed: inertia2.minSpeed,
8670
+ boundary: inertia2.boundary,
8671
+ viewBounds: inertia2.bounds,
8672
+ initialPosition: { x: v.get().x, y: v.get().y },
8673
+ onTick: (dx, dy) => {
8674
+ const cur = v.get();
8675
+ v.set({ ...cur, x: cur.x + dx, y: cur.y + dy });
8676
+ }
8677
+ });
8251
8678
  }
8252
8679
  };
8253
8680
  }
@@ -8351,6 +8778,54 @@ function makeViewportZoomAction(opts = {}) {
8351
8778
  }
8352
8779
  var viewportZoomAction = makeViewportZoomAction();
8353
8780
 
8781
+ // src/interactions/actions/defaults/viewportWheelPan.ts
8782
+ var viewportWheelPanAction = {
8783
+ id: "viewport.wheelPan",
8784
+ label: "Pan (wheel)",
8785
+ group: "viewport",
8786
+ /**
8787
+ * Plain wheel → pan; Shift+wheel → horizontal pan (axis swap).
8788
+ * Cmd+wheel is claimed by `viewportZoomAction`. The matcher's strict
8789
+ * modifier check keeps the bindings exclusive: omitting `mods` forbids
8790
+ * any modifier, and `mods: { shift: true }` forbids the others.
8791
+ */
8792
+ defaultBinding: [
8793
+ { spec: { kind: "wheel" }, opts: {} },
8794
+ { spec: { kind: "wheel", mods: { shift: true } }, opts: { params: { swapAxis: true } } }
8795
+ ],
8796
+ requires: ["view"],
8797
+ invoker: {
8798
+ timing: "immediate",
8799
+ run(deps, params) {
8800
+ const view = deps.view;
8801
+ if (!view) return;
8802
+ const axis = params?.axis ?? "both";
8803
+ const deltaX = params?.deltaX ?? 0;
8804
+ const deltaY = params?.deltaY ?? 0;
8805
+ const current = view.get();
8806
+ const swap = params?.swapAxis === true;
8807
+ const dx = swap ? (deltaX !== 0 ? deltaX : deltaY) / current.scale.x : deltaX / current.scale.x;
8808
+ const dy = swap ? 0 : deltaY / current.scale.y;
8809
+ view.set({
8810
+ ...current,
8811
+ x: current.x + (axis === "y" ? 0 : dx),
8812
+ y: current.y + (axis === "x" ? 0 : dy)
8813
+ });
8814
+ }
8815
+ },
8816
+ enabled: () => true
8817
+ };
8818
+ function makeViewportWheelPanAction(opts = {}) {
8819
+ const axis = opts.axis ?? "both";
8820
+ return {
8821
+ ...viewportWheelPanAction,
8822
+ defaultBinding: [
8823
+ { spec: { kind: "wheel" }, opts: { params: { axis } } },
8824
+ { spec: { kind: "wheel", mods: { shift: true } }, opts: { params: { swapAxis: true, axis } } }
8825
+ ]
8826
+ };
8827
+ }
8828
+
8354
8829
  // src/interactions/dispatcher/predicates.ts
8355
8830
  function bodyPredicate(fn) {
8356
8831
  return Object.assign(fn, { readsAffordance: false });
@@ -12286,8 +12761,6 @@ function useHoverTracking(args) {
12286
12761
  }, [canvasRef, enabled]);
12287
12762
  return () => hoverRef.current;
12288
12763
  }
12289
-
12290
- // src/interactions/dispatcher/dispatcher.ts
12291
12764
  function pointerGestureId(pointerId) {
12292
12765
  return `pointer-${pointerId ?? "mouse"}`;
12293
12766
  }
@@ -12792,7 +13265,7 @@ function createDispatcher(opts) {
12792
13265
  }
12793
13266
  function inFlightCursor() {
12794
13267
  for (const action of inFlightActions.values()) {
12795
- const cursor = action.activeCursor ?? action.cursor;
13268
+ const cursor = resolveCursor(action.activeCursor ?? action.cursor);
12796
13269
  if (cursor) return cursor;
12797
13270
  }
12798
13271
  return null;
@@ -12975,8 +13448,6 @@ function itemsFromClipboardData(cd) {
12975
13448
  }
12976
13449
  return out;
12977
13450
  }
12978
-
12979
- // src/interactions/dispatcher/useGestureDispatcher.tsx
12980
13451
  var DROPOVER_CLASS = "weasel-dropover";
12981
13452
  function stylusOf(e) {
12982
13453
  return {
@@ -13018,7 +13489,7 @@ function computeMultiTouchGeometry(positions) {
13018
13489
  return { centroid, spread };
13019
13490
  }
13020
13491
  function useGestureDispatcher(opts) {
13021
- const { canvasRef, actions, toolsById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld: clientToWorld2, requestRedraw, getRuleCtx, onDoubleClick, views } = opts;
13492
+ const { canvasRef, actions, toolsById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld: clientToWorld2, requestRedraw, paintedCursor, getRuleCtx, onDoubleClick, views } = opts;
13022
13493
  const onDoubleClickRef = useRef(onDoubleClick);
13023
13494
  onDoubleClickRef.current = onDoubleClick;
13024
13495
  const activeTool = useActiveToolContext();
@@ -13067,6 +13538,8 @@ function useGestureDispatcher(opts) {
13067
13538
  ];
13068
13539
  const requestRedrawRef = useRef(requestRedraw);
13069
13540
  requestRedrawRef.current = requestRedraw;
13541
+ const paintedCursorRef = useRef(paintedCursor);
13542
+ paintedCursorRef.current = paintedCursor;
13070
13543
  const lastClickRef = useRef(null);
13071
13544
  const prevActiveRef = useRef(activeTool.active);
13072
13545
  useEffect(() => {
@@ -13127,6 +13600,7 @@ function useGestureDispatcher(opts) {
13127
13600
  };
13128
13601
  const heldKeys = /* @__PURE__ */ new Set();
13129
13602
  const activePointers = /* @__PURE__ */ new Set();
13603
+ const buttonsReportedFor = /* @__PURE__ */ new Set();
13130
13604
  const pointerPositions = /* @__PURE__ */ new Map();
13131
13605
  let multiTouchStart = null;
13132
13606
  const TAP_THRESHOLD_PX = 8;
@@ -13269,8 +13743,10 @@ function useGestureDispatcher(opts) {
13269
13743
  };
13270
13744
  const onPointerDown = (e) => {
13271
13745
  if ((e.button ?? 0) !== 0) return;
13746
+ if (activePointers.has(e.pointerId)) onPointerUp(e);
13272
13747
  routeDown(e.pointerId, e.clientX, e.clientY);
13273
13748
  activePointers.add(e.pointerId);
13749
+ if (reportsButtons(e)) buttonsReportedFor.add(e.pointerId);
13274
13750
  pointerPositions.set(e.pointerId, { x: e.clientX, y: e.clientY });
13275
13751
  try {
13276
13752
  canvas?.setPointerCapture?.(e.pointerId);
@@ -13349,6 +13825,10 @@ function useGestureDispatcher(opts) {
13349
13825
  }
13350
13826
  };
13351
13827
  const onPointerMove = (e) => {
13828
+ if (activePointers.has(e.pointerId) && buttonsReportedFor.has(e.pointerId) && isMissedRelease(e)) {
13829
+ onPointerUp(e);
13830
+ return;
13831
+ }
13352
13832
  routeAt(e.pointerId, e.clientX, e.clientY);
13353
13833
  if (activePointers.has(e.pointerId)) {
13354
13834
  pointerPositions.set(e.pointerId, { x: e.clientX, y: e.clientY });
@@ -13406,20 +13886,39 @@ function useGestureDispatcher(opts) {
13406
13886
  let lastHover = null;
13407
13887
  let cursorOverridden = false;
13408
13888
  const clearHoverCursor = () => {
13889
+ paintedCursorRef.current?.()?.setOverride(null);
13409
13890
  if (cursorOverridden && canvas) {
13410
13891
  canvas.style.cursor = "";
13411
13892
  cursorOverridden = false;
13412
13893
  }
13413
13894
  };
13414
- const applyHoverCursor = (cursor) => {
13415
- if (cursor && canvas) {
13416
- canvas.style.cursor = cursor;
13417
- cursorOverridden = true;
13418
- } else {
13895
+ const applyHoverCursor = (spec) => {
13896
+ const tier = spec === null ? null : resolveCursorTier(spec);
13897
+ if (!tier || !canvas) {
13898
+ clearHoverCursor();
13899
+ return;
13900
+ }
13901
+ const painted = tier.kind === "painted" ? tier : null;
13902
+ paintedCursorRef.current?.()?.setOverride(painted);
13903
+ const cursor = tier.kind === "css" ? tier.css : "none";
13904
+ if (!cursor) {
13419
13905
  clearHoverCursor();
13906
+ return;
13420
13907
  }
13908
+ canvas.style.cursor = cursor;
13909
+ cursorOverridden = true;
13421
13910
  };
13911
+ function pushPaintedPointer() {
13912
+ const painted = paintedCursorRef.current?.();
13913
+ if (!painted?.active() || !canvas || !lastHover) return;
13914
+ const r = canvas.getBoundingClientRect();
13915
+ painted.setPointer(lastHover.clientX - r.left, lastHover.clientY - r.top);
13916
+ }
13422
13917
  function refreshHoverCursor() {
13918
+ resolveHoverCursor();
13919
+ pushPaintedPointer();
13920
+ }
13921
+ function resolveHoverCursor() {
13423
13922
  if (!canvas || !lastHover) return;
13424
13923
  if (activePointers.size > 0 || dispatcherNow().inFlight().size > 0) {
13425
13924
  applyHoverCursor(dispatcherNow().inFlightCursor());
@@ -13471,6 +13970,7 @@ function useGestureDispatcher(opts) {
13471
13970
  };
13472
13971
  const onHoverPointerLeave = () => {
13473
13972
  lastHover = null;
13973
+ paintedCursorRef.current?.()?.clearPointer();
13474
13974
  clearHoverCursor();
13475
13975
  };
13476
13976
  const onPointerUp = (e) => {
@@ -13478,6 +13978,7 @@ function useGestureDispatcher(opts) {
13478
13978
  cancelLongPress(e.pointerId);
13479
13979
  const prevSize = activePointers.size;
13480
13980
  activePointers.delete(e.pointerId);
13981
+ buttonsReportedFor.delete(e.pointerId);
13481
13982
  pointerPositions.delete(e.pointerId);
13482
13983
  const staysUnderThreshold = bufferedDown.delete(e.pointerId);
13483
13984
  if (prevSize >= 2 && activePointers.size < 2) {
@@ -13585,6 +14086,7 @@ function useGestureDispatcher(opts) {
13585
14086
  routeAt(e.pointerId, e.clientX, e.clientY);
13586
14087
  cancelLongPress(e.pointerId);
13587
14088
  activePointers.delete(e.pointerId);
14089
+ buttonsReportedFor.delete(e.pointerId);
13588
14090
  pointerPositions.delete(e.pointerId);
13589
14091
  lastPointerDown.delete(e.pointerId);
13590
14092
  bufferedDown.delete(e.pointerId);
@@ -13600,6 +14102,11 @@ function useGestureDispatcher(opts) {
13600
14102
  dispatch2(ev);
13601
14103
  routeRelease(e.pointerId);
13602
14104
  };
14105
+ const onLostCapture = (e) => {
14106
+ const pe = e;
14107
+ if (!activePointers.has(pe.pointerId)) return;
14108
+ onPointerCancel(pe);
14109
+ };
13603
14110
  const onContextMenu = (e) => {
13604
14111
  e.preventDefault();
13605
14112
  routeAt(null, e.clientX, e.clientY);
@@ -13689,6 +14196,7 @@ function useGestureDispatcher(opts) {
13689
14196
  canvas?.addEventListener("pointermove", onPointerMove);
13690
14197
  canvas?.addEventListener("pointerup", onPointerUp);
13691
14198
  canvas?.addEventListener("pointercancel", onPointerCancel);
14199
+ canvas?.addEventListener(LOST_CAPTURE_EVENT, onLostCapture);
13692
14200
  canvas?.addEventListener("contextmenu", onContextMenu);
13693
14201
  canvas?.addEventListener("dragenter", onDragOver);
13694
14202
  canvas?.addEventListener("dragover", onDragOver);
@@ -13709,6 +14217,7 @@ function useGestureDispatcher(opts) {
13709
14217
  canvas?.removeEventListener("pointermove", onPointerMove);
13710
14218
  canvas?.removeEventListener("pointerup", onPointerUp);
13711
14219
  canvas?.removeEventListener("pointercancel", onPointerCancel);
14220
+ canvas?.removeEventListener(LOST_CAPTURE_EVENT, onLostCapture);
13712
14221
  canvas?.removeEventListener("contextmenu", onContextMenu);
13713
14222
  canvas?.removeEventListener("dragenter", onDragOver);
13714
14223
  canvas?.removeEventListener("dragover", onDragOver);
@@ -13751,26 +14260,7 @@ function computeWheelAction(state, input, bounds = { min: DEFAULT_MIN_ZOOM, max:
13751
14260
  };
13752
14261
  }
13753
14262
  function useVelocityTracker() {
13754
- const samplesRef = useRef([]);
13755
- return useMemo(() => ({
13756
- record(dx, dy, t) {
13757
- samplesRef.current.push({ dx, dy, t });
13758
- const cutoff = t - 100;
13759
- samplesRef.current = samplesRef.current.filter((s) => s.t >= cutoff);
13760
- },
13761
- getVelocity() {
13762
- const s = samplesRef.current;
13763
- if (s.length < 2) return { vx: 0, vy: 0 };
13764
- const dt = s[s.length - 1].t - s[0].t;
13765
- if (dt === 0) return { vx: 0, vy: 0 };
13766
- const totalDx = s.slice(1).reduce((acc, p) => acc + p.dx, 0);
13767
- const totalDy = s.slice(1).reduce((acc, p) => acc + p.dy, 0);
13768
- return { vx: totalDx / dt, vy: totalDy / dt };
13769
- },
13770
- reset() {
13771
- samplesRef.current = [];
13772
- }
13773
- }), []);
14263
+ return useMemo(() => createVelocityTracker(), []);
13774
14264
  }
13775
14265
  var SPRING_DAMPING = 0.5;
13776
14266
  function useDecayLoop() {
@@ -15200,7 +15690,7 @@ function validateId(id, kind) {
15200
15690
  }
15201
15691
  function defineTool(def) {
15202
15692
  validateId(def.id, "tool");
15203
- const resolveCursor = (ctx) => {
15693
+ const resolveCursor3 = (ctx) => {
15204
15694
  if (def.cursor == null) return "";
15205
15695
  return typeof def.cursor === "function" ? def.cursor(ctx) : def.cursor;
15206
15696
  };
@@ -15218,7 +15708,7 @@ function defineTool(def) {
15218
15708
  onActivate: def.onActivate,
15219
15709
  onDeactivate: def.onDeactivate,
15220
15710
  initScratch: def.initScratch ?? (() => null),
15221
- cursor: resolveCursor,
15711
+ cursor: resolveCursor3,
15222
15712
  bindings: def.bindings,
15223
15713
  overlay: def.overlay,
15224
15714
  overlayPosition: def.overlayPosition
@@ -17288,6 +17778,9 @@ function transformOf(state, targetId) {
17288
17778
  identity: false
17289
17779
  };
17290
17780
  }
17781
+ function angleOf(xf) {
17782
+ return xf.identity ? 0 : Math.atan2(xf.sin, xf.cos);
17783
+ }
17291
17784
  function localToWorld(xf, lx, ly) {
17292
17785
  if (xf.identity) return { x: lx, y: ly };
17293
17786
  const dx = lx - xf.cx;
@@ -17591,11 +18084,7 @@ var DEFAULT_PAINT = {
17591
18084
  insetPx: 10
17592
18085
  };
17593
18086
  function createRotationAffordance(opts = {}) {
17594
- const {
17595
- bandPx = 24,
17596
- paint = DEFAULT_PAINT,
17597
- cursor = "grab"
17598
- } = opts;
18087
+ const { bandPx = 24, paint = DEFAULT_PAINT, cursor } = opts;
17599
18088
  const annulusPaint = (() => {
17600
18089
  if (!paint) return null;
17601
18090
  if ("paint" in paint) {
@@ -17633,7 +18122,14 @@ function createRotationAffordance(opts = {}) {
17633
18122
  },
17634
18123
  ...annulusPaint ? { paint: annulusPaint } : {},
17635
18124
  hitKind: "rotate-handle",
17636
- cursor,
18125
+ // Turned with the target, so the arc follows the selection it rotates.
18126
+ // Resolved per region rather than defaulted in the option list because
18127
+ // the angle is only known once a target is picked.
18128
+ cursor: cursor ?? {
18129
+ glyph: "rotate",
18130
+ angle: angleOf(transformOf(state, target.id)),
18131
+ fallback: "grab"
18132
+ },
17637
18133
  bind: () => ({
17638
18134
  initialScratch: {
17639
18135
  targetId: target.id,
@@ -17732,7 +18228,9 @@ function useRotateTool(adapter, options) {
17732
18228
  [overlay]
17733
18229
  );
17734
18230
  }
17735
- function useHandTool(_opts = {}) {
18231
+ function useHandTool(opts = {}) {
18232
+ const optsRef = useRef(opts);
18233
+ optsRef.current = opts;
17736
18234
  return useMemo(
17737
18235
  () => Object.assign(defineViewportTool({
17738
18236
  id: "hand",
@@ -17750,7 +18248,16 @@ function useHandTool(_opts = {}) {
17750
18248
  // viewportDragPanAction, which also owns the grabbing cursor
17751
18249
  // (`Action.activeCursor`) the tool used to declare as an `engaged`
17752
18250
  // phase entry.
17753
- bindings: [{ spec: { kind: "drag" }, actionId: "viewport.dragPan" }]
18251
+ bindings: [{
18252
+ spec: { kind: "drag" },
18253
+ actionId: "viewport.dragPan",
18254
+ opts: {
18255
+ params: () => ({
18256
+ axis: optsRef.current.axis ?? "both",
18257
+ inertia: optsRef.current.inertia
18258
+ })
18259
+ }
18260
+ }]
17754
18261
  }),
17755
18262
  []
17756
18263
  );
@@ -18116,7 +18623,7 @@ function useRectTool() {
18116
18623
  id: "rect",
18117
18624
  capabilities: ["creates-shapes"],
18118
18625
  hookName: "useRectTool",
18119
- cursor: "crosshair",
18626
+ cursor: cursorFor("crosshairRect", { fallback: "crosshair" }),
18120
18627
  presentation: {
18121
18628
  label: "Rectangle",
18122
18629
  icon: createElement(RectIcon),
@@ -18142,7 +18649,7 @@ function useEllipseTool() {
18142
18649
  id: "ellipse",
18143
18650
  capabilities: ["creates-shapes"],
18144
18651
  hookName: "useEllipseTool",
18145
- cursor: "crosshair",
18652
+ cursor: cursorFor("crosshairEllipse", { fallback: "crosshair" }),
18146
18653
  presentation: {
18147
18654
  label: "Ellipse",
18148
18655
  group: "shape",
@@ -18250,7 +18757,7 @@ function useLineTool() {
18250
18757
  id: "line",
18251
18758
  capabilities: ["creates-shapes"],
18252
18759
  hookName: "useLineTool",
18253
- cursor: "crosshair",
18760
+ cursor: cursorFor("crosshairLine", { fallback: "crosshair" }),
18254
18761
  presentation: {
18255
18762
  label: "Line",
18256
18763
  group: "shape",
@@ -18365,7 +18872,7 @@ function usePolygonTool(options = {}) {
18365
18872
  id: "polygon",
18366
18873
  capabilities: ["creates-shapes"],
18367
18874
  hookName: "usePolygonTool",
18368
- cursor: "crosshair",
18875
+ cursor: cursorFor("crosshairPolygon", { fallback: "crosshair" }),
18369
18876
  // Registered by <ToolActionsMounter>, inside the ActionsProvider —
18370
18877
  // see `ToolDef.actions` for why the hook can't register it itself.
18371
18878
  actions: [adjustSidesAction],
@@ -18465,7 +18972,7 @@ function useStarTool(options = {}) {
18465
18972
  id: "star",
18466
18973
  capabilities: ["creates-shapes"],
18467
18974
  hookName: "useStarTool",
18468
- cursor: "crosshair",
18975
+ cursor: cursorFor("crosshairStar", { fallback: "crosshair" }),
18469
18976
  // Registered by <ToolActionsMounter>, inside the ActionsProvider —
18470
18977
  // see `ToolDef.actions` for why the hook can't register it itself.
18471
18978
  actions: [adjustPointsAction],
@@ -19121,24 +19628,25 @@ function projectBounds(b, view) {
19121
19628
  const [sx, sy] = worldToScreen(b.x, b.y, t);
19122
19629
  return { ...b, x: sx, y: sy, width: b.width * view.scale.x, height: b.height * view.scale.y };
19123
19630
  }
19631
+ function leavesOf(id, getChildren, isContainer) {
19632
+ if (!isContainer(id)) return [id];
19633
+ const out = [];
19634
+ const visit = (nid) => {
19635
+ const kids = getChildren(nid);
19636
+ if (kids.length === 0) {
19637
+ if (!isContainer(nid)) out.push(nid);
19638
+ return;
19639
+ }
19640
+ for (const k of kids) visit(k);
19641
+ };
19642
+ visit(id);
19643
+ return out;
19644
+ }
19124
19645
  function composeSelectionPose(opts) {
19125
19646
  const { moveOverlay, resizeOverlay, getStoredPose, getChildren, isContainer } = opts;
19126
19647
  const getBounds = opts.getBounds ?? ((pose) => pose);
19127
19648
  const fromBounds = opts.fromBounds ?? ((bounds) => bounds);
19128
- const leavesOf = (id) => {
19129
- if (!getChildren || !isContainer || !isContainer(id)) return [id];
19130
- const out = [];
19131
- const visit = (nid) => {
19132
- const kids = getChildren(nid);
19133
- if (kids.length === 0) {
19134
- if (!isContainer(nid)) out.push(nid);
19135
- return;
19136
- }
19137
- for (const k of kids) visit(k);
19138
- };
19139
- visit(id);
19140
- return out;
19141
- };
19649
+ const selectionLeaves = (id) => getChildren && isContainer ? leavesOf(id, getChildren, isContainer) : [id];
19142
19650
  const resolveLeaf = (id) => {
19143
19651
  const moved = moveOverlay?.poses.get(id);
19144
19652
  if (moved !== void 0) return moved;
@@ -19147,7 +19655,7 @@ function composeSelectionPose(opts) {
19147
19655
  };
19148
19656
  return (id) => {
19149
19657
  if (isContainer?.(id)) {
19150
- const leaves = leavesOf(id);
19658
+ const leaves = selectionLeaves(id);
19151
19659
  if (leaves.length === 0) return null;
19152
19660
  const containerResizeLeafPoses = resizeOverlay && resizeOverlay.id === id ? resizeOverlay.leafPoses : void 0;
19153
19661
  const leafBounds = [];
@@ -19178,26 +19686,12 @@ function makeContainerAwareBoundsResolver(getPose, getBounds, getChildren, isCon
19178
19686
  return p === null ? null : getBounds(p);
19179
19687
  };
19180
19688
  }
19181
- const leavesOf = (id) => {
19182
- if (!isContainer(id)) return [id];
19183
- const out = [];
19184
- const visit = (nid) => {
19185
- const kids = getChildren(nid);
19186
- if (kids.length === 0) {
19187
- if (!isContainer(nid)) out.push(nid);
19188
- return;
19189
- }
19190
- for (const k of kids) visit(k);
19191
- };
19192
- visit(id);
19193
- return out;
19194
- };
19195
19689
  return (id) => {
19196
19690
  if (!isContainer(id)) {
19197
19691
  const p = getPose(id);
19198
19692
  return p === null ? null : getBounds(p);
19199
19693
  }
19200
- const leaves = leavesOf(id);
19694
+ const leaves = leavesOf(id, getChildren, isContainer);
19201
19695
  if (leaves.length === 0) return null;
19202
19696
  const leafBounds = [];
19203
19697
  for (const leafId of leaves) {
@@ -19592,6 +20086,113 @@ function buildSceneTree(adapter, drawOne, view, forLayer, derivedPathOf) {
19592
20086
  }
19593
20087
  return out;
19594
20088
  }
20089
+
20090
+ // src/features/cursor/paintedCursorState.ts
20091
+ function same(a, b) {
20092
+ if (a === b) return true;
20093
+ if (!a || !b) return false;
20094
+ return a.glyph === b.glyph && a.angle === b.angle && a.size === b.size && a.worldRadius === b.worldRadius;
20095
+ }
20096
+ function createPaintedCursorState() {
20097
+ let base = null;
20098
+ let override = null;
20099
+ let at = null;
20100
+ const subs = /* @__PURE__ */ new Set();
20101
+ const notify2 = () => {
20102
+ for (const fn of subs) fn();
20103
+ };
20104
+ return {
20105
+ setBase(cursor) {
20106
+ if (same(base, cursor)) return;
20107
+ base = cursor;
20108
+ notify2();
20109
+ },
20110
+ setOverride(cursor) {
20111
+ if (same(override, cursor)) return;
20112
+ override = cursor;
20113
+ notify2();
20114
+ },
20115
+ setPointer(x, y) {
20116
+ if (at && at.x === x && at.y === y) return;
20117
+ at = { x, y };
20118
+ if (override ?? base) notify2();
20119
+ },
20120
+ clearPointer() {
20121
+ if (at === null) return;
20122
+ at = null;
20123
+ notify2();
20124
+ },
20125
+ active() {
20126
+ return (override ?? base) !== null;
20127
+ },
20128
+ current() {
20129
+ const cursor = override ?? base;
20130
+ if (!cursor || !at) return null;
20131
+ return { cursor, at };
20132
+ },
20133
+ subscribe(fn) {
20134
+ subs.add(fn);
20135
+ return () => {
20136
+ subs.delete(fn);
20137
+ };
20138
+ }
20139
+ };
20140
+ }
20141
+ var PAINTED_CURSOR_LAYER_ID = "painted-cursor";
20142
+ var pathCache = /* @__PURE__ */ new Map();
20143
+ function pathFor(d) {
20144
+ let p = pathCache.get(d);
20145
+ if (p === void 0) {
20146
+ p = pathFromD(d);
20147
+ pathCache.set(d, p);
20148
+ }
20149
+ return p;
20150
+ }
20151
+ function commandFor2(op) {
20152
+ return {
20153
+ kind: "path",
20154
+ path: pathFor(op.d),
20155
+ ...op.fill !== void 0 ? { fill: { color: op.fill } } : {},
20156
+ ...op.stroke !== void 0 ? {
20157
+ stroke: {
20158
+ paint: { color: op.stroke.color },
20159
+ width: op.stroke.width,
20160
+ cap: "round",
20161
+ join: "round"
20162
+ }
20163
+ } : {}
20164
+ };
20165
+ }
20166
+ function createPaintedCursorLayer(state) {
20167
+ return {
20168
+ id: PAINTED_CURSOR_LAYER_ID,
20169
+ label: "Painted cursor",
20170
+ space: "screen",
20171
+ draw: (_data, view) => {
20172
+ const frame = state.current();
20173
+ if (!frame) return [];
20174
+ const { cursor, at } = frame;
20175
+ const glyph = GLYPHS[cursor.glyph];
20176
+ if (!glyph) return [];
20177
+ const scale2 = (view.scale.x + view.scale.y) / 2;
20178
+ const worldSized = cursor.worldRadius !== void 0;
20179
+ const size = worldSized ? cursorWorldSize(glyph, cursor.worldRadius, scale2) : cursor.size ?? 24;
20180
+ if (!(size > 0) || !Number.isFinite(size)) return [];
20181
+ const ops = cursorPaintOps(
20182
+ glyph,
20183
+ worldSized ? { lineWidthScale: chromeLineWidthScale(glyph, size) } : {}
20184
+ );
20185
+ const [a, b, c, d, e, f] = cursorPaintMatrix(glyph, {
20186
+ size,
20187
+ angle: cursor.angle,
20188
+ at
20189
+ });
20190
+ const transform = new Float32Array([a, b, 0, c, d, 0, e, f, 1]);
20191
+ const children = ops.map(commandFor2);
20192
+ return [{ kind: "group", transform, children }];
20193
+ }
20194
+ };
20195
+ }
19595
20196
  function mat3ToRenderMatrix(m) {
19596
20197
  return new Float32Array([m[0], m[1], 0, m[2], m[3], 0, m[4], m[5], 1]);
19597
20198
  }
@@ -19972,11 +20573,11 @@ function resolveToolsCursor(tools, ctxBase) {
19972
20573
  const id = tools.hotkeyEngaged ?? tools.active;
19973
20574
  const tool = tools.registry[id];
19974
20575
  if (!tool?.cursor) return void 0;
19975
- if (typeof tool.cursor === "string") return tool.cursor;
20576
+ if (typeof tool.cursor !== "function") return resolveCursorTier(tool.cursor);
19976
20577
  if (!ctxBase) return void 0;
19977
20578
  try {
19978
20579
  const base = ctxBase();
19979
- return tool.cursor({ ...base, scratch: null });
20580
+ return resolveCursorTier(tool.cursor({ ...base, scratch: null }));
19980
20581
  } catch {
19981
20582
  return void 0;
19982
20583
  }
@@ -20141,6 +20742,14 @@ function CanvasInner(props, ref) {
20141
20742
  viewSubsRef.current.delete(fn);
20142
20743
  };
20143
20744
  }, []);
20745
+ const paintedCursor = useMemo(() => createPaintedCursorState(), []);
20746
+ const paintedCursorLayer = useMemo(
20747
+ () => createPaintedCursorLayer(paintedCursor),
20748
+ [paintedCursor]
20749
+ );
20750
+ useEffect(() => paintedCursor.subscribe(() => {
20751
+ requestRedraw();
20752
+ }), [paintedCursor, requestRedraw]);
20144
20753
  useImperativeHandle(ref, () => ({
20145
20754
  // Named rather than read off `canvasRef` so the handle rebuilds when a
20146
20755
  // detached surface's input element arrives, which is a render later.
@@ -20153,7 +20762,8 @@ function CanvasInner(props, ref) {
20153
20762
  getView,
20154
20763
  setView,
20155
20764
  subscribeView,
20156
- getPaintedVersion
20765
+ getPaintedVersion,
20766
+ paintedCursor
20157
20767
  }), [
20158
20768
  canvasRef,
20159
20769
  ownCanvasRef,
@@ -20167,7 +20777,8 @@ function CanvasInner(props, ref) {
20167
20777
  getView,
20168
20778
  setView,
20169
20779
  subscribeView,
20170
- getPaintedVersion
20780
+ getPaintedVersion,
20781
+ paintedCursor
20171
20782
  ]);
20172
20783
  const glRendererRef = useRef(null);
20173
20784
  const layerCacheRef = useRef(/* @__PURE__ */ new Map());
@@ -20247,7 +20858,7 @@ function CanvasInner(props, ref) {
20247
20858
  return {
20248
20859
  worldX,
20249
20860
  worldY,
20250
- modifiers: m ? { alt: m.alt, shift: m.shift, meta: m.meta, ctrl: m.ctrl, space: false } : { alt: false, shift: false, meta: false, ctrl: false, space: false },
20861
+ modifiers: m ? { alt: m.alt, shift: m.shift, meta: m.meta, ctrl: m.ctrl } : { alt: false, shift: false, meta: false, ctrl: false },
20251
20862
  selection: effectiveSelectionRefForCtx.current,
20252
20863
  adapter: effectiveAdapterRefForCtx.current,
20253
20864
  applyOps: (ops, label) => {
@@ -20431,8 +21042,16 @@ function CanvasInner(props, ref) {
20431
21042
  const layersWithDebug = useMemo(() => {
20432
21043
  const withViews = viewRegistry ? [...layers, ...viewRegistry.list().map((r) => r.layer)] : layers;
20433
21044
  const base = debugSink && resolvedDebugConfig ? [...withViews, createDebugOverlayLayer({ sink: debugSink, config: resolvedDebugConfig })] : withViews;
20434
- return [...base, ...extrasRef.current];
20435
- }, [layers, debugSink, resolvedDebugConfig, extrasVersion, viewRegistry, viewRegistryVersion]);
21045
+ return [...base, ...extrasRef.current, paintedCursorLayer];
21046
+ }, [
21047
+ layers,
21048
+ debugSink,
21049
+ resolvedDebugConfig,
21050
+ extrasVersion,
21051
+ viewRegistry,
21052
+ viewRegistryVersion,
21053
+ paintedCursorLayer
21054
+ ]);
20436
21055
  const shaderIdKey = shaders?.map((h) => h.id).join("|") ?? "";
20437
21056
  const paintInputsRef = useRef({
20438
21057
  layers: layersWithDebug,
@@ -20554,8 +21173,14 @@ function CanvasInner(props, ref) {
20554
21173
  if (!renderer) return;
20555
21174
  registerShadersOnRenderer(renderer, shaders);
20556
21175
  }, [shaderIdKey]);
20557
- const toolsCursor = tools ? resolveToolsCursor(tools, toolsCtxBase) : void 0;
21176
+ const toolsCursorTier = tools ? resolveToolsCursor(tools, toolsCtxBase) : void 0;
21177
+ const toolsCursor = toolsCursorTier === void 0 ? void 0 : toolsCursorTier.kind === "css" ? toolsCursorTier.css : "none";
20558
21178
  const effectiveStyle = toolsCursor ? { ...style, cursor: toolsCursor } : style;
21179
+ const toolsPainted = toolsCursorTier?.kind === "painted" ? toolsCursorTier : null;
21180
+ const toolsPaintedKey = toolsPainted ? `${toolsPainted.glyph}|${toolsPainted.angle}|${toolsPainted.size}|${toolsPainted.worldRadius}` : "";
21181
+ useEffect(() => {
21182
+ paintedCursor.setBase(toolsPainted);
21183
+ }, [paintedCursor, toolsPaintedKey]);
20559
21184
  const layersForMoveRef = useRef(layersWithDebug);
20560
21185
  layersForMoveRef.current = layersWithDebug;
20561
21186
  useEffect(() => {
@@ -21894,10 +22519,16 @@ function createCornerResizeAffordance(opts = {}) {
21894
22519
  hitKind: c.kind,
21895
22520
  // Diagonal by fixed-corner parity: a matched-axis anchor (min-min /
21896
22521
  // max-max fixed) means the dragged corner sits on the ↘ diagonal;
21897
- // mixed axes sit on the ↗ diagonal. Not rotation-aware — a rotated
21898
- // target keeps the unrotated hint, the same policy as every
21899
- // mainstream editor short of Figma.
21900
- cursor: c.anchor.x === c.anchor.y ? "nwse-resize" : "nesw-resize",
22522
+ // mixed axes sit on the ↗ diagonal. The glyph is horizontal at angle
22523
+ // 0, so the diagonal is a half-quarter-turn either way, plus the
22524
+ // target's own rotation. The keyword is the fallback rather than the
22525
+ // answer: CSS has four of them, which cannot express a target
22526
+ // rotated to anything but a multiple of 45°.
22527
+ cursor: {
22528
+ glyph: "resize",
22529
+ angle: (c.anchor.x === c.anchor.y ? Math.PI / 4 : -Math.PI / 4) + angleOf(xf),
22530
+ fallback: c.anchor.x === c.anchor.y ? "nwse-resize" : "nesw-resize"
22531
+ },
21901
22532
  bind: () => ({
21902
22533
  initialScratch: {
21903
22534
  anchor: c.anchor,
@@ -23930,10 +24561,10 @@ function boundsOf(coords) {
23930
24561
  }
23931
24562
  return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
23932
24563
  }
23933
- function useViewDepSource(currentViewRef, onViewChange, recenter, hostSize, animation) {
23934
- const wiring = useRef({ currentViewRef, onViewChange, recenter, hostSize, animation });
23935
- wiring.current = { currentViewRef, onViewChange, recenter, hostSize, animation };
23936
- const shape = `${recenter ? "r" : ""}${hostSize ? "h" : ""}${animation ? "a" : ""}`;
24564
+ function useViewDepSource(currentViewRef, onViewChange, recenter, hostSize, animation, decay) {
24565
+ const wiring = useRef({ currentViewRef, onViewChange, recenter, hostSize, animation, decay });
24566
+ wiring.current = { currentViewRef, onViewChange, recenter, hostSize, animation, decay };
24567
+ const shape = `${recenter ? "r" : ""}${hostSize ? "h" : ""}${animation ? "a" : ""}${decay ? "d" : ""}`;
23937
24568
  const shapeRef = useRef(null);
23938
24569
  const viewApiRef = useRef(null);
23939
24570
  if (viewApiRef.current === null || shapeRef.current !== shape) {
@@ -23952,6 +24583,10 @@ function useViewDepSource(currentViewRef, onViewChange, recenter, hostSize, anim
23952
24583
  animate: (to, opts) => wiring.current.animation.animate(to, opts),
23953
24584
  stopAnimation: () => wiring.current.animation.stop(),
23954
24585
  animationTarget: () => wiring.current.animation.target()
24586
+ } : {},
24587
+ ...decay ? {
24588
+ decay: (config) => wiring.current.decay.start(config),
24589
+ stopDecay: () => wiring.current.decay.cancel()
23955
24590
  } : {}
23956
24591
  };
23957
24592
  }
@@ -24715,7 +25350,6 @@ function useActionsPropResolver(actions) {
24715
25350
  }
24716
25351
  if (!prop) return;
24717
25352
  const unregisters = [];
24718
- const restores = [];
24719
25353
  const warnedIds = /* @__PURE__ */ new Set();
24720
25354
  for (const [slotId, entry] of Object.entries(prop)) {
24721
25355
  if (entry === null) {
@@ -24743,64 +25377,30 @@ function useActionsPropResolver(actions) {
24743
25377
  }
24744
25378
  const { id: _drop, ...rest } = entry;
24745
25379
  const merged = { ...existing, ...rest };
24746
- restores.push(() => {
24747
- reg.register(existing);
24748
- });
24749
25380
  unregisters.push(reg.register(merged));
24750
25381
  }
24751
25382
  return () => {
24752
25383
  for (const u of unregisters) u();
24753
- for (const r of restores) r();
24754
25384
  };
24755
25385
  }, [reg, actions]);
24756
25386
  }
24757
-
24758
- // src/interactions/actions/defaults/viewportWheelPan.ts
24759
- var viewportWheelPanAction = {
24760
- id: "viewport.wheelPan",
24761
- label: "Pan (wheel)",
24762
- group: "viewport",
24763
- /**
24764
- * Plain wheel → pan; Shift+wheel → horizontal pan (axis swap).
24765
- * Cmd+wheel is claimed by `viewportZoomAction`. The matcher's strict
24766
- * modifier check keeps the bindings exclusive: omitting `mods` forbids
24767
- * any modifier, and `mods: { shift: true }` forbids the others.
24768
- */
24769
- defaultBinding: [
24770
- { spec: { kind: "wheel" }, opts: {} },
24771
- { spec: { kind: "wheel", mods: { shift: true } }, opts: { params: { swapAxis: true } } }
24772
- ],
24773
- requires: ["view"],
24774
- invoker: {
24775
- timing: "immediate",
24776
- run(deps, params) {
24777
- const view = deps.view;
24778
- if (!view) return;
24779
- const deltaX = params?.deltaX ?? 0;
24780
- const deltaY = params?.deltaY ?? 0;
24781
- const current = view.get();
24782
- const swap = params?.swapAxis === true;
24783
- const dx = swap ? (deltaX !== 0 ? deltaX : deltaY) / current.scale.x : deltaX / current.scale.x;
24784
- const dy = swap ? 0 : deltaY / current.scale.y;
24785
- view.set({ ...current, x: current.x + dx, y: current.y + dy });
24786
- }
24787
- },
24788
- enabled: () => true
24789
- };
24790
-
24791
- // src/canvas/SceneCanvas/useViewportActions.ts
24792
25387
  function useViewportActions(args) {
24793
25388
  const { pan, zoom, pinchZoom } = args;
24794
25389
  const reg = useActionsRegistry();
24795
25390
  const regRef = useRef(reg);
24796
25391
  regRef.current = reg;
25392
+ const panKey = typeof pan === "object" ? JSON.stringify(pan) : String(pan);
24797
25393
  const zoomKey = typeof zoom === "object" ? JSON.stringify(zoom) : String(zoom);
24798
25394
  const pinchKey = typeof pinchZoom === "object" ? JSON.stringify(pinchZoom) : String(pinchZoom);
24799
25395
  useEffect(() => {
24800
25396
  const r = regRef.current;
24801
25397
  if (!r) return;
24802
25398
  const unregisters = [];
24803
- if (pan) unregisters.push(r.register(viewportWheelPanAction));
25399
+ if (pan) {
25400
+ unregisters.push(
25401
+ r.register(makeViewportWheelPanAction(typeof pan === "object" ? pan : {}))
25402
+ );
25403
+ }
24804
25404
  if (zoom) {
24805
25405
  const zoomAction = typeof zoom === "object" ? makeViewportZoomAction(zoom) : makeViewportZoomAction();
24806
25406
  unregisters.push(r.register(zoomAction));
@@ -24815,7 +25415,7 @@ function useViewportActions(args) {
24815
25415
  return () => {
24816
25416
  for (const u of unregisters) u();
24817
25417
  };
24818
- }, [pan, zoomKey, pinchKey]);
25418
+ }, [panKey, zoomKey, pinchKey]);
24819
25419
  }
24820
25420
 
24821
25421
  // src/core/scene/NodeRouting.ts
@@ -25250,13 +25850,13 @@ function SceneCanvasInner(props, ref) {
25250
25850
  const viewportRegistered = !!viewport;
25251
25851
  const inertiaEnabled = !!viewport?.inertia;
25252
25852
  const inertiaObj = typeof viewport?.inertia === "object" ? viewport.inertia : void 0;
25253
- const handToolInertia = inertiaEnabled && inertiaObj ? {
25254
- friction: inertiaObj.friction,
25255
- minSpeed: inertiaObj.minSpeed,
25256
- boundary: inertiaObj.boundary,
25257
- bounds: inertiaObj.bounds
25853
+ const handToolInertia = inertiaEnabled ? {
25854
+ friction: inertiaObj?.friction,
25855
+ minSpeed: inertiaObj?.minSpeed,
25856
+ boundary: inertiaObj?.boundary,
25857
+ bounds: inertiaObj?.bounds
25258
25858
  } : void 0;
25259
- const handTool = useHandTool(handToolInertia ? { } : {});
25859
+ const handTool = useHandTool(handToolInertia ? { inertia: handToolInertia } : {});
25260
25860
  const viewportAmbient = [];
25261
25861
  const baseRequestedTools = defaultTools ?? (toolBundle ? BUNDLE_TOOLS[toolBundle] : null) ?? (viewportRegistered ? ["select", "rotate", "hand"] : ["select", "rotate"]);
25262
25862
  const KNOWN_BUILTIN_IDS = /* @__PURE__ */ new Set([
@@ -25709,7 +26309,7 @@ function SceneCanvasInner(props, ref) {
25709
26309
  dispatcher,
25710
26310
  getActionRef,
25711
26311
  pickEvery: internalPickEvery,
25712
- viewportPanEnabled: viewport?.pan !== false,
26312
+ viewportPanEnabled: viewport?.pan ?? true,
25713
26313
  viewportZoom: resolvedViewportZoom,
25714
26314
  viewportPinchZoom: viewport?.pinchZoom ?? true,
25715
26315
  viewportRecenter: viewport?.recenter,
@@ -25920,6 +26520,10 @@ function GestureDispatcherMounter({
25920
26520
  const requestRedraw = useCallback(() => {
25921
26521
  canvasApiRef?.current?.requestRedraw?.();
25922
26522
  }, [canvasApiRef]);
26523
+ const paintedCursor = useCallback(
26524
+ () => canvasApiRef?.current?.paintedCursor,
26525
+ [canvasApiRef]
26526
+ );
25923
26527
  const views = useMemo(() => {
25924
26528
  if (!viewRegistry) return void 0;
25925
26529
  return {
@@ -25938,6 +26542,7 @@ function GestureDispatcherMounter({
25938
26542
  dispatcher,
25939
26543
  clientToWorld: clientToWorld2,
25940
26544
  requestRedraw,
26545
+ paintedCursor,
25941
26546
  getRuleCtx,
25942
26547
  onDoubleClick,
25943
26548
  ...views ? { views } : {}
@@ -25995,6 +26600,7 @@ function StandardActionsRegistrar({
25995
26600
  getActionRef.current = null;
25996
26601
  };
25997
26602
  }, [registry2, getActionRef]);
26603
+ const decayLoop = useDecayLoop();
25998
26604
  const view = useViewDepSource(
25999
26605
  currentViewRef,
26000
26606
  onViewChange,
@@ -26003,7 +26609,8 @@ function StandardActionsRegistrar({
26003
26609
  const el = canvasRef.current;
26004
26610
  return el ? { width: el.clientWidth, height: el.clientHeight } : null;
26005
26611
  },
26006
- viewAnimation
26612
+ viewAnimation,
26613
+ decayLoop
26007
26614
  );
26008
26615
  const sceneAsHistory = scene;
26009
26616
  useStandardActions({ selection, scene, view, history: sceneAsHistory });
@@ -26463,8 +27070,7 @@ function MinimapCanvasInner(props) {
26463
27070
  }, [mainView, mainViewDims, indicatorStyle]);
26464
27071
  const extraCommands = useMemo(() => [indicatorCmd], [indicatorCmd]);
26465
27072
  const localCanvasRef = useRef(null);
26466
- const dragActiveRef = useRef(false);
26467
- const capturedPointerIdRef = useRef(null);
27073
+ const sessionRef = useRef(null);
26468
27074
  const mainViewRef = useRef(mainView);
26469
27075
  const mainViewDimsRef = useRef(mainViewDims);
26470
27076
  const onMainViewChangeRef = useRef(onMainViewChange);
@@ -26493,36 +27099,18 @@ function MinimapCanvasInner(props) {
26493
27099
  }, []);
26494
27100
  const handlePointerDown = useCallback((ev) => {
26495
27101
  const canvas = localCanvasRef.current;
26496
- if (!canvas) return;
27102
+ if (!canvas || sessionRef.current) return;
26497
27103
  if (ev.pointerType === "mouse" && ev.button !== 0) return;
26498
- try {
26499
- canvas.setPointerCapture(ev.pointerId);
26500
- } catch {
26501
- }
26502
- dragActiveRef.current = true;
26503
- capturedPointerIdRef.current = ev.pointerId;
26504
- recenterFromPointer(ev);
26505
- }, [recenterFromPointer]);
26506
- const handlePointerMove = useCallback((ev) => {
26507
- if (!dragActiveRef.current) return;
26508
- if (capturedPointerIdRef.current !== null && ev.pointerId !== capturedPointerIdRef.current) {
26509
- return;
26510
- }
27104
+ const done = () => {
27105
+ sessionRef.current = null;
27106
+ };
27107
+ sessionRef.current = openPointerSession(canvas, ev, {
27108
+ onMove: recenterFromPointer,
27109
+ onEnd: done,
27110
+ onCancel: done
27111
+ });
26511
27112
  recenterFromPointer(ev);
26512
27113
  }, [recenterFromPointer]);
26513
- const releaseCapture = useCallback((ev) => {
26514
- const canvas = localCanvasRef.current;
26515
- if (canvas) {
26516
- try {
26517
- if (canvas.hasPointerCapture?.(ev.pointerId)) {
26518
- canvas.releasePointerCapture(ev.pointerId);
26519
- }
26520
- } catch {
26521
- }
26522
- }
26523
- dragActiveRef.current = false;
26524
- capturedPointerIdRef.current = null;
26525
- }, []);
26526
27114
  const setCanvasRef = useCallback((el) => {
26527
27115
  localCanvasRef.current = el;
26528
27116
  if (typeof canvasRef === "function") {
@@ -26535,16 +27123,11 @@ function MinimapCanvasInner(props) {
26535
27123
  const canvas = localCanvasRef.current;
26536
27124
  if (!canvas) return;
26537
27125
  canvas.addEventListener("pointerdown", handlePointerDown);
26538
- canvas.addEventListener("pointermove", handlePointerMove);
26539
- canvas.addEventListener("pointerup", releaseCapture);
26540
- canvas.addEventListener("pointercancel", releaseCapture);
26541
27126
  return () => {
26542
27127
  canvas.removeEventListener("pointerdown", handlePointerDown);
26543
- canvas.removeEventListener("pointermove", handlePointerMove);
26544
- canvas.removeEventListener("pointerup", releaseCapture);
26545
- canvas.removeEventListener("pointercancel", releaseCapture);
27128
+ sessionRef.current?.cancel();
26546
27129
  };
26547
- }, [handlePointerDown, handlePointerMove, releaseCapture]);
27130
+ }, [handlePointerDown]);
26548
27131
  return /* @__PURE__ */ jsx(
26549
27132
  SceneViewCanvas,
26550
27133
  {
@@ -30684,6 +31267,6 @@ function mergeContributions(...bundles) {
30684
31267
  return out;
30685
31268
  }
30686
31269
 
30687
- export { ALWAYS, ANCHOR_HIT_BASE_PX, ActionsProviderIfRoot, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, ArrowIcon, BUILTIN_MARKERS, BUNDLE_TOOLS, COARSE_TARGET_SCALE, CURVE_REPS, CanvasView, ColorOverrideRegistry, CropIcon, CursorCoordsHud, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_STROKES, DEFAULT_DEBUG_THEME, DEFAULT_DEVICE_PROFILE, DEFAULT_FILL_COLOR, DEFAULT_HANDLE_SIZE2 as DEFAULT_HANDLE_SIZE, DEFAULT_MAX_ZOOM, DEFAULT_MIN_ZOOM, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_SHAPE_FILL, DEFAULT_STROKE_COLOR, DEFAULT_VELOCITY_DECAY, DepRegistryProviderIfRoot, DeviceProfileProvider, DivideIcon, EASINGS, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, GHOST_STROKE, HANDLE_BASE_PX, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_JOINT, IDENTITY_POSE_COMPOSITION, ImageIcon, IntersectIcon, KIT_SHAPE_KINDS, LassoIcon, LineIcon, MIXED, MOVE_ANCHORS, MinimapCanvas, NEVER, OUTLINE_MIN_SCREEN_PX, PATH_ANCHOR_CHROME_ID, PenIcon, PencilIcon, PickHud, PointerProviderIfRoot, PolygonIcon, RECT_ALIGN_PROJECTION, ROTATION_HANDLE_BASE_PX, RectIcon, SPRING_PRESETS, SceneCanvas, SceneViewCanvas, SelectIcon, SelectionContextProvider, SelectionContextProviderIfRoot, ShaderCompileError, StarIcon, SubtractIcon, TOOL_PREF_KINDS, TextIcon, UnionIcon, UnknownIcon, VERSION, VIEW_ANIMATION_KEY, WeaselProvider, WeaselRenderer, _resetMarkersForTests, _resetPaintKindsForTests, aabbCenter, actionIs, actionShortcuts, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, always, and, animateLifecycle, animateOnSetPose, annulusSemiAxes, applyBooleanOp, applyStyleToRange, areaSelectAction, arrayAdapter, asNodeId, asPaint, axisAlignedBounds, bezierCubic, bezierQuadratic, blendPoses, buildChromeCtx, buildGradientRamp, buildRuleCtx, buildSceneViewCommands, canBringForward, canHover, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, clipboardCopyAction, clipboardCutAction, cloneAction, coarsePointer, composeAffordanceLayer, composePath, composeRectPose, composeSelectionPose, composeWorldPose, computeFitView, computeFitViewport, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDispatcher, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createMoveToIndexOp, createNodeProperties, createNodeRouting, createParallaxLayer, createPathAnchorAffordances, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createTextLayer, createViewResolver, createViewportLayer, cubicBezierEasing, cycleVertexColors, decomposePath, decomposeRectPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, defineTool, defineViewportTool, deriveAlignmentGuides, deriveParallaxView, describeRule, domPositionToCharOffset, domToRuns, drawLayers, drawOneLayer, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, enterTextEditAction, enumerateAnchors, evaluate, fillInPoseFrame, fillToBoundsFrame, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, forEachCoalesced, frameRect, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getMarker, getNodeShapes, getPaintKind, getStylusData, gradientForBounds, gradientGeometry, hexToRgba, hitAffordanceRegions, hitAnchor, hitRotationHandle, hostAnchorCss, hostAnchorRect, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, interpolateView, isBuiltinToolPref, isEditableTarget2 as isEditableTarget, isGradientFill, isLayerPainted, isLayerVisible, isPatternSpec, keySpecShortcut, lassoSelectAction, lerpColorArray, lerpOklab, lerpOklch, linear, listMarkers, listPaintKinds, liveScope, makePinchZoomAction, makeViewportZoomAction, markerDrawCommands, markerInset, markerKeyOf, markerSites, mat3, matchAlignment, matchesKeyBinding, meanScale, mergeAlphaFromPrev, mergeContributions, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, nodeHasFlag, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, or, paintAlpha, paintKindOf, paintWithAlpha, paintWithColor, parseColor, parseColorToRgba255, parseDebugFlags, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInRotatedRect, pointInTextPose, poseRotationOf, pressureToWidth, rainbowVertexColors, rebaseLocalPose, rectCorners, registerContentHandler, registerMarker, registerNodeShape, registerPaintKind, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveDeviceProfile, resolveEasing, resolveFillPattern, resolveMarkerSize, resolveParams, resolvePatternSpec, resolveSkeleton, resolveStrokeWidth, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, sampleGradientStops, sampleTrack, sceneFromJSON, sceneNodeClientRect, sceneSelectionStore, sceneToAdapter, scopeBindings, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, setFlagOverRange, shapeCoversPoint, sliceAction, snapPoint, solid, solidVertexColors, specificity, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, startThresholdDrag, strokeInsets, strokeOf, strokeWith, styleAtRange, subscribeImageReady, tessellate, tessellateStroke, textCommand, textCommandFromRuns, tileGrid, toHex8, translatePoseViaDescriptor, translateRectPose, trimPolyline, tweenPose, tweenVertexColors, unionAABB, unionBounds, unionBoundsPath, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDeviceProfile, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHostAnchor, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, useNodeOverlayFrame, useOptionalActiveToolContext, usePenTool, usePencilTool, usePinchGesture, usePinchZoomTool, usePointerStylus, usePolygonTool, usePublishSelection, useRectTool, useResizePolicy, useRotateTool, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelection, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useVisibleRaf, useZoom, viewToMat3, viewToTransform, viewportDragPanAction, viewportZoomAction, viewportsAt, visualBoundsViaDescriptor, when, withAlpha01, withCoord, withGradientKind, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
30688
- //# sourceMappingURL=chunk-FUQWXDG3.js.map
30689
- //# sourceMappingURL=chunk-FUQWXDG3.js.map
31270
+ export { ALWAYS, ANCHOR_HIT_BASE_PX, ActionsProviderIfRoot, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, ArrowIcon, BUILTIN_MARKERS, BUNDLE_TOOLS, COARSE_TARGET_SCALE, CURVE_REPS, CanvasView, ColorOverrideRegistry, CropIcon, CursorCoordsHud, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_STROKES, DEFAULT_DEBUG_THEME, DEFAULT_DEVICE_PROFILE, DEFAULT_FILL_COLOR, DEFAULT_HANDLE_SIZE2 as DEFAULT_HANDLE_SIZE, DEFAULT_MAX_ZOOM, DEFAULT_MIN_ZOOM, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_SHAPE_FILL, DEFAULT_STROKE_COLOR, DEFAULT_VELOCITY_DECAY, DepRegistryProviderIfRoot, DeviceProfileProvider, DivideIcon, EASINGS, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, GHOST_STROKE, HANDLE_BASE_PX, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_JOINT, IDENTITY_POSE_COMPOSITION, ImageIcon, IntersectIcon, KIT_SHAPE_KINDS, LassoIcon, LineIcon, MIXED, MOVE_ANCHORS, MinimapCanvas, NEVER, OUTLINE_MIN_SCREEN_PX, PAINTED_CURSOR_LAYER_ID, PATH_ANCHOR_CHROME_ID, PenIcon, PencilIcon, PickHud, PointerProviderIfRoot, PolygonIcon, RECT_ALIGN_PROJECTION, ROTATION_HANDLE_BASE_PX, RectIcon, SPRING_PRESETS, SPRITE_STRIDE, SceneCanvas, SceneViewCanvas, SelectIcon, SelectionContextProvider, SelectionContextProviderIfRoot, ShaderCompileError, StarIcon, SubtractIcon, TOOL_PREF_KINDS, TextIcon, UnionIcon, UnknownIcon, VERSION, VIEW_ANIMATION_KEY, WeaselProvider, WeaselRenderer, _resetMarkersForTests, _resetPaintKindsForTests, aabbCenter, actionIs, actionShortcuts, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, always, and, animateLifecycle, animateOnSetPose, annulusSemiAxes, applyBooleanOp, applyStyleToRange, areaSelectAction, arrayAdapter, asNodeId, asPaint, axisAlignedBounds, bezierCubic, bezierQuadratic, blendPoses, buildChromeCtx, buildGradientRamp, buildRuleCtx, buildSceneViewCommands, canBringForward, canHover, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, clipboardCopyAction, clipboardCutAction, cloneAction, coarsePointer, composeAffordanceLayer, composePath, composeRectPose, composeSelectionPose, composeWorldPose, computeFitView, computeFitViewport, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDispatcher, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createMoveToIndexOp, createNodeProperties, createNodeRouting, createPaintedCursorLayer, createPaintedCursorState, createParallaxLayer, createPathAnchorAffordances, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createTextLayer, createVelocityTracker, createViewResolver, createViewportLayer, cubicBezierEasing, cycleVertexColors, decomposePath, decomposeRectPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, defineTool, defineViewportTool, deriveAlignmentGuides, deriveParallaxView, describeRule, domPositionToCharOffset, domToRuns, drawLayers, drawOneLayer, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, enterTextEditAction, enumerateAnchors, evaluate, fillInPoseFrame, fillToBoundsFrame, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, forEachCoalesced, frameRect, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getMarker, getNodeShapes, getPaintKind, getStylusData, gradientForBounds, gradientGeometry, hexToRgba, hitAffordanceRegions, hitAnchor, hitRotationHandle, hostAnchorCss, hostAnchorRect, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, interpolateView, isBuiltinToolPref, isEditableTarget2 as isEditableTarget, isGradientFill, isLayerPainted, isLayerVisible, isPatternSpec, keySpecShortcut, lassoSelectAction, lerpColorArray, lerpOklab, lerpOklch, linear, listMarkers, listPaintKinds, liveScope, makePinchZoomAction, makeViewportWheelPanAction, makeViewportZoomAction, markerDrawCommands, markerInset, markerKeyOf, markerSites, mat3, matchAlignment, matchesKeyBinding, meanScale, mergeAlphaFromPrev, mergeContributions, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, nodeHasFlag, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, openPointerSession, or, paintAlpha, paintKindOf, paintWithAlpha, paintWithColor, parseColor, parseColorToRgba255, parseDebugFlags, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInRotatedRect, pointInTextPose, poseRotationOf, pressureToWidth, rainbowVertexColors, rebaseLocalPose, rectCorners, registerContentHandler, registerMarker, registerNodeShape, registerPaintKind, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveDeviceProfile, resolveEasing, resolveFillPattern, resolveMarkerSize, resolveParams, resolvePatternSpec, resolveSkeleton, resolveStrokeWidth, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, sampleGradientStops, sampleTrack, sceneFromJSON, sceneNodeClientRect, sceneSelectionStore, sceneToAdapter, scopeBindings, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, setFlagOverRange, shapeCoversPoint, sliceAction, snapPoint, solid, solidVertexColors, specificity, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, startThresholdDrag, strokeInsets, strokeOf, strokeWith, styleAtRange, subscribeImageReady, tessellate, tessellateStroke, textCommand, textCommandFromRuns, tileGrid, toHex8, translatePoseViaDescriptor, translateRectPose, trimPolyline, tweenPose, tweenVertexColors, unionAABB, unionBounds, unionBoundsPath, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDeviceProfile, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHostAnchor, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, useNodeOverlayFrame, useOptionalActiveToolContext, usePenTool, usePencilTool, usePinchGesture, usePinchZoomTool, usePointerStylus, usePolygonTool, usePublishSelection, useRectTool, useResizePolicy, useRotateTool, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelection, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useVisibleRaf, useZoom, viewToMat3, viewToTransform, viewportDragPanAction, viewportWheelPanAction, viewportZoomAction, viewportsAt, visualBoundsViaDescriptor, when, withAlpha01, withCoord, withGradientKind, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
31271
+ //# sourceMappingURL=chunk-NU7D64RL.js.map
31272
+ //# sourceMappingURL=chunk-NU7D64RL.js.map