@weasel-js/core 0.7.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +541 -0
- package/dist/{DrawCommand-CJtqqt8H.d.ts → DrawCommand-DCmiNkBj.d.ts} +38 -170
- package/dist/{chunk-CQNKCG34.js → chunk-6SHZHXXR.js} +47 -10
- package/dist/chunk-6SHZHXXR.js.map +1 -0
- package/dist/{chunk-5F3PIERN.js → chunk-D2C6K6FO.js} +1420 -652
- package/dist/chunk-D2C6K6FO.js.map +1 -0
- package/dist/chunk-DCIU3SRK.js +505 -0
- package/dist/chunk-DCIU3SRK.js.map +1 -0
- package/dist/chunk-IO5Z75X4.js +167 -0
- package/dist/chunk-IO5Z75X4.js.map +1 -0
- package/dist/{chunk-VOVKONXA.js → chunk-UB6A6L77.js} +3 -3
- package/dist/{chunk-VOVKONXA.js.map → chunk-UB6A6L77.js.map} +1 -1
- package/dist/{chunk-BGGZ4CVF.js → chunk-Z3KNFTTS.js} +6 -6
- package/dist/chunk-Z3KNFTTS.js.map +1 -0
- package/dist/clipboard.d.ts +2 -2
- package/dist/clone.d.ts +1 -1
- package/dist/{geometry-D9BDMiQi.d.ts → geometry-DVeZ2i-c.d.ts} +1 -1
- package/dist/{grid-CaSK9bHV.d.ts → grid-XcFQVS0f.d.ts} +1 -1
- package/dist/{index-DOYRTfP0.d.ts → index-ChM3ZJ2v.d.ts} +278 -93
- package/dist/index.d.ts +442 -57
- package/dist/index.js +6 -6
- package/dist/insert.d.ts +2 -2
- package/dist/insert.js +5 -5
- package/dist/insert.js.map +1 -1
- package/dist/move.d.ts +3 -3
- package/dist/move.js +2 -2
- package/dist/{options-DMWeTELe.d.ts → options-wAvCnOZd.d.ts} +1 -1
- package/dist/paint-types-CnLIzqq1.d.ts +261 -0
- package/dist/patterns-builtin.d.ts +57 -2
- package/dist/patterns-builtin.js +1 -97
- package/dist/patterns-builtin.js.map +1 -1
- package/dist/{pointSnapToGrid-C3EruUwt.d.ts → pointSnapToGrid-BuDZWBrL.d.ts} +2 -2
- package/dist/renderer.d.ts +25 -8
- package/dist/renderer.js +6 -6
- package/dist/resize.d.ts +3 -3
- package/dist/resize.js +2 -2
- package/dist/routing.d.ts +7 -7
- package/dist/routing.js +1 -1
- package/dist/{types-Dcaa0tPq.d.ts → types-BhJJ2OCM.d.ts} +9 -1
- package/dist/{types-Cpb4hii1.d.ts → types-DUpI7Apc.d.ts} +3 -95
- package/dist/{viewToMat3-D4lrBigW.d.ts → viewToMat3-BvaWCN99.d.ts} +1 -1
- package/package.json +6 -6
- package/dist/chunk-5F3PIERN.js.map +0 -1
- package/dist/chunk-BGGZ4CVF.js.map +0 -1
- package/dist/chunk-CQNKCG34.js.map +0 -1
- package/dist/chunk-SYM6RAM4.js +0 -232
- package/dist/chunk-SYM6RAM4.js.map +0 -1
- package/dist/chunk-Y52N27PF.js +0 -39
- package/dist/chunk-Y52N27PF.js.map +0 -1
- package/dist/registerTexture-BzHTLhD9.d.ts +0 -25
- /package/dist/{types-B6MMiodD.d.ts → path-B6MMiodD.d.ts} +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { defineViewportTool,
|
|
2
|
-
import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-
|
|
3
|
-
import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-
|
|
4
|
-
import { AUTO_POSE_DESCRIPTOR, RECT_POSE_DESCRIPTOR, resolveUnit,
|
|
1
|
+
import { ActionDisabledReason, defineViewportTool, useOptionalDepRegistry, DepRegistryProvider, useActionsRegistry, ActionsProvider, useDepRegistry, defineTool, reportRouteConflicts, buildDepsFromRequires, useDepSource, actionBindings } from './chunk-DCIU3SRK.js';
|
|
2
|
+
import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-UB6A6L77.js';
|
|
3
|
+
import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-Z3KNFTTS.js';
|
|
4
|
+
import { AUTO_POSE_DESCRIPTOR, RECT_POSE_DESCRIPTOR, resolveUnit, pathPoseDescriptor, polygonFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, strokeHitTest, withinPxRadius, boundsOfPath, PATH_M, PATH_L, PATH_Z, PATH_Q, PATH_C, DEFAULT_FLATTEN_TOLERANCE, flattenQuadratic, flattenCubic, PathBuilder, pointInPath, transformPath, translatePath, pxExtent, remapRotatedLeaf, withinPxBox, flattenCubicWithArcLen, flattenQuadraticWithArcLen, PATH_CMD_LENGTHS, pointToGridCell } from './chunk-6SHZHXXR.js';
|
|
5
5
|
import { dispatchApplyBatch, dlog, dwarn, usePointerContext, PointerContextProvider, applyOpsTo, extractWeaselClipboardFromSvg, parseWeaselClipboardText, materializePaste, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, sniffWeaselClipboardText } from './chunk-775XXAHR.js';
|
|
6
6
|
import { createSetSelectionOp } from './chunk-BHVYVFGV.js';
|
|
7
7
|
import { registerOpFactory, createDeleteOp, createInsertOp, rebuildOp } from './chunk-GVCNT7UH.js';
|
|
8
|
-
import { getTexture } from './chunk-
|
|
8
|
+
import { getTexture, chunks, dots, crosshatch, hatch } from './chunk-IO5Z75X4.js';
|
|
9
9
|
import { createContext, createElement, forwardRef, useMemo, useRef, useState, useCallback, useImperativeHandle, useEffect, useSyncExternalStore, useContext, useReducer, useLayoutEffect } 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, glyphGeneration, syncDynamicPageTexture, ensureFontTexture, dynamicPageTextureId, textureCacheKey, resolveFontVariant, resolveGlyphFallback, glyphOutline } 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 earcut from 'earcut';
|
|
13
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
14
13
|
import { applyToPoint, rotateAboutPoint, boxToBox, pointInPolygon, segmentsCross } from '@weasel-js/geom';
|
|
14
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
15
15
|
import polygonClipping from 'polygon-clipping';
|
|
16
16
|
import { matchSpec, mimeMatchesGlob } from '@weasel-js/gestures';
|
|
17
17
|
import { NORMAL, IMPLICIT_TAGS } from '@weasel-js/modes';
|
|
@@ -19,7 +19,7 @@ import { createHistory } from '@weasel-js/history';
|
|
|
19
19
|
export * from '@weasel-js/history';
|
|
20
20
|
|
|
21
21
|
// src/version.ts
|
|
22
|
-
var VERSION = "0.
|
|
22
|
+
var VERSION = "1.0.0" ;
|
|
23
23
|
|
|
24
24
|
// src/features/grid/roundToCell.ts
|
|
25
25
|
function roundToCell(value, cellSize) {
|
|
@@ -333,6 +333,60 @@ var GRAD_FILL_UNIFORMS = [
|
|
|
333
333
|
];
|
|
334
334
|
var GRAD_FILL_ATTRIBUTES = ["a_position"];
|
|
335
335
|
|
|
336
|
+
// src/renderer/shaders/patternFill.ts
|
|
337
|
+
var PATTERN_VERT_SRC = (
|
|
338
|
+
/* glsl */
|
|
339
|
+
`#version 300 es
|
|
340
|
+
in vec2 a_position;
|
|
341
|
+
uniform mat3 u_proj;
|
|
342
|
+
uniform mat3 u_model;
|
|
343
|
+
uniform mat3 u_worldInv;
|
|
344
|
+
out vec2 v_world;
|
|
345
|
+
void main() {
|
|
346
|
+
vec3 screen = u_model * vec3(a_position, 1.0);
|
|
347
|
+
vec3 clip = u_proj * vec3(screen.xy, 1.0);
|
|
348
|
+
gl_Position = vec4(clip.xy, 0.0, 1.0);
|
|
349
|
+
vec3 world = u_worldInv * vec3(screen.xy, 1.0);
|
|
350
|
+
v_world = world.xy;
|
|
351
|
+
}
|
|
352
|
+
`
|
|
353
|
+
);
|
|
354
|
+
var PATTERN_FRAG_SRC = (
|
|
355
|
+
/* glsl */
|
|
356
|
+
`#version 300 es
|
|
357
|
+
precision highp float;
|
|
358
|
+
in vec2 v_world;
|
|
359
|
+
uniform sampler2D u_sampler;
|
|
360
|
+
uniform vec2 u_tileOrigin;
|
|
361
|
+
uniform vec2 u_tileSize;
|
|
362
|
+
uniform float u_opacity;
|
|
363
|
+
uniform float u_alpha;
|
|
364
|
+
uniform mat4 u_colorMatrix;
|
|
365
|
+
uniform vec4 u_colorBias;
|
|
366
|
+
out vec4 outColor;
|
|
367
|
+
void main() {
|
|
368
|
+
vec2 uv = (v_world - u_tileOrigin) / max(u_tileSize, vec2(0.0001));
|
|
369
|
+
vec4 texel = texture(u_sampler, uv);
|
|
370
|
+
vec4 mapped = clamp(u_colorMatrix * texel + u_colorBias, 0.0, 1.0);
|
|
371
|
+
float a = mapped.a * u_opacity * u_alpha;
|
|
372
|
+
outColor = vec4(mapped.rgb * a, a);
|
|
373
|
+
}
|
|
374
|
+
`
|
|
375
|
+
);
|
|
376
|
+
var PATTERN_FILL_UNIFORMS = [
|
|
377
|
+
"u_proj",
|
|
378
|
+
"u_model",
|
|
379
|
+
"u_worldInv",
|
|
380
|
+
"u_sampler",
|
|
381
|
+
"u_tileOrigin",
|
|
382
|
+
"u_tileSize",
|
|
383
|
+
"u_opacity",
|
|
384
|
+
"u_alpha",
|
|
385
|
+
"u_colorMatrix",
|
|
386
|
+
"u_colorBias"
|
|
387
|
+
];
|
|
388
|
+
var PATTERN_FILL_ATTRIBUTES = ["a_position"];
|
|
389
|
+
|
|
336
390
|
// src/renderer/cache/GLMeshCache.ts
|
|
337
391
|
var GLMeshCache = class {
|
|
338
392
|
constructor(gl, aPositionLoc) {
|
|
@@ -458,17 +512,20 @@ var GLTextureCache = class {
|
|
|
458
512
|
has(id) {
|
|
459
513
|
return this.map.has(id);
|
|
460
514
|
}
|
|
461
|
-
|
|
515
|
+
/** Uploads once per id; `wrap` therefore applies on first upload only.
|
|
516
|
+
* An id is either a pattern tile or a shader texture, never both. */
|
|
517
|
+
upload(id, source, wrap = "clamp") {
|
|
462
518
|
if (this.map.has(id)) return id;
|
|
463
519
|
const gl = this.gl;
|
|
464
520
|
const tex = gl.createTexture();
|
|
465
521
|
if (!tex) throw new Error(`GLTextureCache: createTexture failed for id="${id}"`);
|
|
522
|
+
const wrapMode = wrap === "repeat" ? gl.REPEAT : gl.CLAMP_TO_EDGE;
|
|
466
523
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
467
524
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
|
|
468
525
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
469
526
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
470
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,
|
|
471
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,
|
|
527
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapMode);
|
|
528
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapMode);
|
|
472
529
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
473
530
|
this.map.set(id, tex);
|
|
474
531
|
return id;
|
|
@@ -1100,6 +1157,18 @@ function scale(m, sx, sy) {
|
|
|
1100
1157
|
const s = create(sx, 0, 0, sy, 0, 0);
|
|
1101
1158
|
return multiply(m, s);
|
|
1102
1159
|
}
|
|
1160
|
+
function invert(m) {
|
|
1161
|
+
const a = m[0], b = m[1];
|
|
1162
|
+
const c = m[3], d = m[4];
|
|
1163
|
+
const tx = m[6], ty = m[7];
|
|
1164
|
+
const det = a * d - b * c;
|
|
1165
|
+
if (det === 0 || !Number.isFinite(det)) return identity();
|
|
1166
|
+
const ia = d / det;
|
|
1167
|
+
const ib = -b / det;
|
|
1168
|
+
const ic = -c / det;
|
|
1169
|
+
const id = a / det;
|
|
1170
|
+
return create(ia, ib, ic, id, -(ia * tx + ic * ty), -(ib * tx + id * ty));
|
|
1171
|
+
}
|
|
1103
1172
|
function apply(m, x, y) {
|
|
1104
1173
|
const a = m[0], b = m[1];
|
|
1105
1174
|
const c = m[3], d = m[4];
|
|
@@ -1127,6 +1196,7 @@ var mat3 = {
|
|
|
1127
1196
|
multiply,
|
|
1128
1197
|
translate,
|
|
1129
1198
|
scale,
|
|
1199
|
+
invert,
|
|
1130
1200
|
apply,
|
|
1131
1201
|
screenToClip
|
|
1132
1202
|
};
|
|
@@ -1501,6 +1571,16 @@ function extractPolygon(p, opts) {
|
|
|
1501
1571
|
out.push(next);
|
|
1502
1572
|
return next;
|
|
1503
1573
|
};
|
|
1574
|
+
const dropDuplicateClosingPoint = () => {
|
|
1575
|
+
if (!pts || !aA || !aB || !aT) return;
|
|
1576
|
+
const n = pts.length / 2;
|
|
1577
|
+
if (n < 2) return;
|
|
1578
|
+
if (pts[0] !== pts[(n - 1) * 2] || pts[1] !== pts[(n - 1) * 2 + 1]) return;
|
|
1579
|
+
pts.length -= 2;
|
|
1580
|
+
aA.length -= 1;
|
|
1581
|
+
aB.length -= 1;
|
|
1582
|
+
aT.length -= 1;
|
|
1583
|
+
};
|
|
1504
1584
|
const commit = (target) => {
|
|
1505
1585
|
if (!pts || !aA || !aB || !aT) return;
|
|
1506
1586
|
target.anchorA = new Uint32Array(aA);
|
|
@@ -1584,7 +1664,10 @@ function extractPolygon(p, opts) {
|
|
|
1584
1664
|
break;
|
|
1585
1665
|
}
|
|
1586
1666
|
case PATH_Z: {
|
|
1587
|
-
if (current)
|
|
1667
|
+
if (current) {
|
|
1668
|
+
current.closed = true;
|
|
1669
|
+
dropDuplicateClosingPoint();
|
|
1670
|
+
}
|
|
1588
1671
|
break;
|
|
1589
1672
|
}
|
|
1590
1673
|
default:
|
|
@@ -2067,8 +2150,20 @@ function sameFill(a, b) {
|
|
|
2067
2150
|
}
|
|
2068
2151
|
return false;
|
|
2069
2152
|
}
|
|
2070
|
-
function
|
|
2071
|
-
|
|
2153
|
+
function strokeKey(s) {
|
|
2154
|
+
if (!s) return "-";
|
|
2155
|
+
return [
|
|
2156
|
+
fillKey(s.paint),
|
|
2157
|
+
s.width ?? 1,
|
|
2158
|
+
s.join ?? "miter",
|
|
2159
|
+
s.cap ?? "butt",
|
|
2160
|
+
s.miterLimit ?? "",
|
|
2161
|
+
s.align ?? "center",
|
|
2162
|
+
(s.dash ?? []).join(",")
|
|
2163
|
+
].join(":");
|
|
2164
|
+
}
|
|
2165
|
+
function groupKey(family, weight, style, synthetic, fill, stroke, source, page) {
|
|
2166
|
+
return `${family}|${weight}|${style}|${synthetic.bold ? 1 : 0}${synthetic.italic ? 1 : 0}|${fillKey(fill)}|${strokeKey(stroke)}|${source}|${page}`;
|
|
2072
2167
|
}
|
|
2073
2168
|
function getOrCreateGroup(ctx, run, resolved, page, sourceOverride) {
|
|
2074
2169
|
const atlasFamily = resolved.resolved.family;
|
|
@@ -2081,6 +2176,7 @@ function getOrCreateGroup(ctx, run, resolved, page, sourceOverride) {
|
|
|
2081
2176
|
resolvedStyle,
|
|
2082
2177
|
resolved.synthetic,
|
|
2083
2178
|
run.fill,
|
|
2179
|
+
source === "outline" ? run.stroke : void 0,
|
|
2084
2180
|
source,
|
|
2085
2181
|
page
|
|
2086
2182
|
);
|
|
@@ -2094,6 +2190,9 @@ function getOrCreateGroup(ctx, run, resolved, page, sourceOverride) {
|
|
|
2094
2190
|
source,
|
|
2095
2191
|
page,
|
|
2096
2192
|
fill: run.fill,
|
|
2193
|
+
// Only the outline tier can paint it, and carrying it on an SDF group
|
|
2194
|
+
// would be a promise the renderer cannot keep.
|
|
2195
|
+
...source === "outline" && run.stroke !== void 0 ? { stroke: run.stroke } : {},
|
|
2097
2196
|
quads: [],
|
|
2098
2197
|
glyphs: []
|
|
2099
2198
|
};
|
|
@@ -2698,6 +2797,36 @@ function outlineMesh(key, d) {
|
|
|
2698
2797
|
return mesh;
|
|
2699
2798
|
}
|
|
2700
2799
|
|
|
2800
|
+
// src/renderer/cache/outlineStrokeMeshCache.ts
|
|
2801
|
+
var OUTLINE_STROKE_MESH_CACHE_LIMIT = 2048;
|
|
2802
|
+
var WIDTH_QUANTUM = 1 / 1024;
|
|
2803
|
+
var cache4 = /* @__PURE__ */ new Map();
|
|
2804
|
+
function quantizeEmWidth(emWidth) {
|
|
2805
|
+
return Math.round(emWidth / WIDTH_QUANTUM) * WIDTH_QUANTUM;
|
|
2806
|
+
}
|
|
2807
|
+
function outlineStrokeMesh(glyphKey, d, emWidth, stroke) {
|
|
2808
|
+
if (!(emWidth > 0)) return null;
|
|
2809
|
+
const key = [
|
|
2810
|
+
glyphKey,
|
|
2811
|
+
emWidth,
|
|
2812
|
+
stroke.join ?? "miter",
|
|
2813
|
+
stroke.cap ?? "butt",
|
|
2814
|
+
stroke.miterLimit ?? "",
|
|
2815
|
+
stroke.align ?? "center",
|
|
2816
|
+
(stroke.dash ?? []).join(",")
|
|
2817
|
+
].join("|");
|
|
2818
|
+
const cached = cache4.get(key);
|
|
2819
|
+
if (cached !== void 0) return cached.indices.length === 0 ? null : cached;
|
|
2820
|
+
if (cache4.size >= OUTLINE_STROKE_MESH_CACHE_LIMIT) cache4 = /* @__PURE__ */ new Map();
|
|
2821
|
+
const mesh = tessellateStroke(
|
|
2822
|
+
pathFromD(d),
|
|
2823
|
+
{ ...stroke, width: emWidth },
|
|
2824
|
+
{ flattenTolerance: OUTLINE_FLATTEN_TOLERANCE }
|
|
2825
|
+
);
|
|
2826
|
+
cache4.set(key, mesh);
|
|
2827
|
+
return mesh.indices.length === 0 ? null : mesh;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2701
2830
|
// src/renderer/draw.ts
|
|
2702
2831
|
function setColorMatrixUniforms(ctx, prog) {
|
|
2703
2832
|
const gl = ctx.gl;
|
|
@@ -2989,28 +3118,42 @@ function drawPathFillPattern(ctx, fill, handle) {
|
|
|
2989
3118
|
if (isDev3) console.warn(`weasel: pattern TextureHandle "${tex.id}" not registered`);
|
|
2990
3119
|
return;
|
|
2991
3120
|
}
|
|
2992
|
-
ctx.textureCache.upload(tex.id, entry.source);
|
|
3121
|
+
ctx.textureCache.upload(tex.id, entry.source, "repeat");
|
|
2993
3122
|
const gl = ctx.gl;
|
|
2994
|
-
gl.useProgram(ctx.
|
|
3123
|
+
gl.useProgram(ctx.patternFill.handle);
|
|
2995
3124
|
gl.bindVertexArray(handle.vao);
|
|
2996
|
-
setProjAndModel(ctx, ctx.
|
|
2997
|
-
setColorMatrixUniforms(ctx, ctx.
|
|
3125
|
+
setProjAndModel(ctx, ctx.patternFill);
|
|
3126
|
+
setColorMatrixUniforms(ctx, ctx.patternFill);
|
|
3127
|
+
gl.uniformMatrix3fv(ctx.patternFill.uniform("u_worldInv"), false, gradientSpaceInverse(ctx, fill.units));
|
|
3128
|
+
const [tw, th] = textureSize(entry.source);
|
|
3129
|
+
const origin = fill.origin ?? { x: 0, y: 0 };
|
|
3130
|
+
gl.uniform2f(ctx.patternFill.uniform("u_tileOrigin"), origin.x, origin.y);
|
|
3131
|
+
gl.uniform2f(ctx.patternFill.uniform("u_tileSize"), tw, th);
|
|
2998
3132
|
ctx.textureCache.bind(tex.id, 0);
|
|
2999
|
-
gl.uniform1i(ctx.
|
|
3000
|
-
gl.uniform1f(ctx.
|
|
3001
|
-
gl.uniform1f(ctx.
|
|
3133
|
+
gl.uniform1i(ctx.patternFill.uniform("u_sampler"), 0);
|
|
3134
|
+
gl.uniform1f(ctx.patternFill.uniform("u_opacity"), fill.opacity ?? 1);
|
|
3135
|
+
gl.uniform1f(ctx.patternFill.uniform("u_alpha"), ctx.state.alpha);
|
|
3002
3136
|
applyClipTest(ctx);
|
|
3003
3137
|
gl.drawElements(gl.TRIANGLES, handle.indexCount, gl.UNSIGNED_INT, 0);
|
|
3004
3138
|
gl.bindVertexArray(null);
|
|
3005
3139
|
}
|
|
3140
|
+
function textureSize(source) {
|
|
3141
|
+
const w = "naturalWidth" in source ? source.naturalWidth : source.width;
|
|
3142
|
+
const h = "naturalHeight" in source ? source.naturalHeight : source.height;
|
|
3143
|
+
return [w || 1, h || 1];
|
|
3144
|
+
}
|
|
3145
|
+
function gradientSpaceInverse(ctx, units) {
|
|
3146
|
+
if (units === "local") return mat3.invert(ctx.state.transform);
|
|
3147
|
+
if (units === "world" && ctx.viewMatrix) return mat3.invert(ctx.viewMatrix);
|
|
3148
|
+
return mat3.identity();
|
|
3149
|
+
}
|
|
3006
3150
|
function drawPathFillGradient(ctx, fill, handle) {
|
|
3007
3151
|
const gl = ctx.gl;
|
|
3008
3152
|
const key = ctx.gradRampCache.upload(fill.stops);
|
|
3009
3153
|
gl.useProgram(ctx.gradFill.handle);
|
|
3010
3154
|
gl.bindVertexArray(handle.vao);
|
|
3011
3155
|
setProjAndModel(ctx, ctx.gradFill);
|
|
3012
|
-
|
|
3013
|
-
gl.uniformMatrix3fv(ctx.gradFill.uniform("u_worldInv"), false, identity3);
|
|
3156
|
+
gl.uniformMatrix3fv(ctx.gradFill.uniform("u_worldInv"), false, gradientSpaceInverse(ctx, fill.units));
|
|
3014
3157
|
ctx.gradRampCache.bind(key, 0);
|
|
3015
3158
|
gl.uniform1i(ctx.gradFill.uniform("u_ramp"), 0);
|
|
3016
3159
|
gl.uniform1f(ctx.gradFill.uniform("u_alpha"), ctx.state.alpha);
|
|
@@ -3272,16 +3415,30 @@ function drawText(ctx, cmd) {
|
|
|
3272
3415
|
var SYNTHETIC_ITALIC_RADIANS = 0.2094;
|
|
3273
3416
|
function drawTextOutlineGroup(ctx, group, dy) {
|
|
3274
3417
|
const mesh = outlineGroupMesh(group, dy);
|
|
3275
|
-
if (
|
|
3276
|
-
|
|
3418
|
+
if (mesh) drawPathFillByKind(ctx, group.fill, ctx.meshCache.uploadTransient(mesh));
|
|
3419
|
+
if (!group.stroke) return;
|
|
3420
|
+
const strokeMesh = outlineGroupStrokeMesh(group, dy);
|
|
3421
|
+
if (strokeMesh) {
|
|
3422
|
+
drawPathFillByKind(ctx, group.stroke.paint, ctx.meshCache.uploadTransient(strokeMesh));
|
|
3423
|
+
}
|
|
3277
3424
|
}
|
|
3278
3425
|
function outlineGroupMesh(group, dy) {
|
|
3426
|
+
return mergeGlyphMeshes(group, dy, (glyph) => outlineMesh(glyph.key, glyph.d));
|
|
3427
|
+
}
|
|
3428
|
+
function outlineGroupStrokeMesh(group, dy) {
|
|
3429
|
+
const stroke = group.stroke;
|
|
3430
|
+
if (!stroke) return null;
|
|
3431
|
+
const width = stroke.width ?? 1;
|
|
3432
|
+
if (!(width > 0)) return null;
|
|
3433
|
+
return mergeGlyphMeshes(group, dy, (glyph) => glyph.scale > 0 ? outlineStrokeMesh(glyph.key, glyph.d, quantizeEmWidth(width / glyph.scale), stroke) : null);
|
|
3434
|
+
}
|
|
3435
|
+
function mergeGlyphMeshes(group, dy, meshFor) {
|
|
3279
3436
|
const parts = [];
|
|
3280
3437
|
let vertexFloats = 0;
|
|
3281
3438
|
let indexCount = 0;
|
|
3282
3439
|
for (const glyph of group.glyphs) {
|
|
3283
|
-
const mesh =
|
|
3284
|
-
if (mesh.indices.length === 0) continue;
|
|
3440
|
+
const mesh = meshFor(glyph);
|
|
3441
|
+
if (!mesh || mesh.indices.length === 0) continue;
|
|
3285
3442
|
parts.push({ mesh, glyph });
|
|
3286
3443
|
vertexFloats += mesh.vertices.length;
|
|
3287
3444
|
indexCount += mesh.indices.length;
|
|
@@ -3516,6 +3673,11 @@ function drawImage(ctx, cmd) {
|
|
|
3516
3673
|
setProjAndModel(ctx, ctx.imageFill);
|
|
3517
3674
|
setColorMatrixUniforms(ctx, ctx.imageFill);
|
|
3518
3675
|
ctx.imageCache.bind(cmd.image, 0);
|
|
3676
|
+
gl.texParameteri(
|
|
3677
|
+
gl.TEXTURE_2D,
|
|
3678
|
+
gl.TEXTURE_MAG_FILTER,
|
|
3679
|
+
cmd.sampling === "nearest" ? gl.NEAREST : gl.LINEAR
|
|
3680
|
+
);
|
|
3519
3681
|
gl.uniform1i(ctx.imageFill.uniform("u_sampler"), 0);
|
|
3520
3682
|
gl.uniform1f(ctx.imageFill.uniform("u_opacity"), cmd.opacity ?? 1);
|
|
3521
3683
|
gl.uniform1f(ctx.imageFill.uniform("u_alpha"), ctx.state.alpha);
|
|
@@ -3616,6 +3778,7 @@ var WeaselRenderer = class {
|
|
|
3616
3778
|
textSdfR8;
|
|
3617
3779
|
imageFill;
|
|
3618
3780
|
gradFill;
|
|
3781
|
+
patternFill;
|
|
3619
3782
|
meshCache;
|
|
3620
3783
|
textureCache;
|
|
3621
3784
|
imageCache;
|
|
@@ -3691,6 +3854,9 @@ var WeaselRenderer = class {
|
|
|
3691
3854
|
this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
|
|
3692
3855
|
this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
|
|
3693
3856
|
this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
|
|
3857
|
+
this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
|
|
3858
|
+
this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
|
|
3859
|
+
this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
|
|
3694
3860
|
const aPos = this.pathFill.attribute("a_position");
|
|
3695
3861
|
if (aPos === void 0) throw new Error("WeaselRenderer: a_position not found in path-fill shader");
|
|
3696
3862
|
this.meshCache = new GLMeshCache(this.gl, aPos);
|
|
@@ -3799,6 +3965,9 @@ var WeaselRenderer = class {
|
|
|
3799
3965
|
this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
|
|
3800
3966
|
this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
|
|
3801
3967
|
this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
|
|
3968
|
+
this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
|
|
3969
|
+
this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
|
|
3970
|
+
this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
|
|
3802
3971
|
const aPos = this.pathFill.attribute("a_position");
|
|
3803
3972
|
if (aPos === void 0) throw new Error("a_position missing after restore");
|
|
3804
3973
|
this.meshCache = new GLMeshCache(this.gl, aPos);
|
|
@@ -3871,7 +4040,15 @@ var WeaselRenderer = class {
|
|
|
3871
4040
|
if (this.rectIbo) gl.deleteBuffer(this.rectIbo);
|
|
3872
4041
|
if (this.rectVao) gl.deleteVertexArray(this.rectVao);
|
|
3873
4042
|
}
|
|
3874
|
-
|
|
4043
|
+
/**
|
|
4044
|
+
* Draw one frame.
|
|
4045
|
+
*
|
|
4046
|
+
* `viewMatrix` is the frame's world→screen transform. It is only consulted
|
|
4047
|
+
* by `units: 'world'` gradients, which fall back to screen space without
|
|
4048
|
+
* it — every other command carries its own transform in the stream, so
|
|
4049
|
+
* callers with no view concept can keep calling `render(commands)`.
|
|
4050
|
+
*/
|
|
4051
|
+
render(commands, viewMatrix) {
|
|
3875
4052
|
if (this.contextLost || this.disposed) return;
|
|
3876
4053
|
const gl = this.gl;
|
|
3877
4054
|
this.meshCache.drainPendingDeletes();
|
|
@@ -3886,6 +4063,7 @@ var WeaselRenderer = class {
|
|
|
3886
4063
|
textSdfR8: this.textSdfR8,
|
|
3887
4064
|
imageFill: this.imageFill,
|
|
3888
4065
|
gradFill: this.gradFill,
|
|
4066
|
+
patternFill: this.patternFill,
|
|
3889
4067
|
meshCache: this.meshCache,
|
|
3890
4068
|
textureCache: this.textureCache,
|
|
3891
4069
|
imageCache: this.imageCache,
|
|
@@ -3900,7 +4078,8 @@ var WeaselRenderer = class {
|
|
|
3900
4078
|
heightCss: this.heightCss,
|
|
3901
4079
|
clipDepth: 0,
|
|
3902
4080
|
flattenTolerance: this.flattenTolerance,
|
|
3903
|
-
textOutlineMinScreenSize: this.textOutlineMinScreenSize
|
|
4081
|
+
textOutlineMinScreenSize: this.textOutlineMinScreenSize,
|
|
4082
|
+
viewMatrix
|
|
3904
4083
|
};
|
|
3905
4084
|
for (const cmd of commands) dispatch(ctx, cmd);
|
|
3906
4085
|
this.meshCache.freeTransient();
|
|
@@ -3948,6 +4127,10 @@ var WeaselRenderer = class {
|
|
|
3948
4127
|
return this.gradFill;
|
|
3949
4128
|
}
|
|
3950
4129
|
/** @internal */
|
|
4130
|
+
_patternFill() {
|
|
4131
|
+
return this.patternFill;
|
|
4132
|
+
}
|
|
4133
|
+
/** @internal */
|
|
3951
4134
|
_meshCache() {
|
|
3952
4135
|
return this.meshCache;
|
|
3953
4136
|
}
|
|
@@ -4013,6 +4196,11 @@ function alignedStrokeRect(rect, align, width = 1) {
|
|
|
4013
4196
|
};
|
|
4014
4197
|
}
|
|
4015
4198
|
|
|
4199
|
+
// src/core/viewport/meanScale.ts
|
|
4200
|
+
function meanScale(s) {
|
|
4201
|
+
return Math.sqrt(s.x * s.y);
|
|
4202
|
+
}
|
|
4203
|
+
|
|
4016
4204
|
// src/features/grid/layer.ts
|
|
4017
4205
|
var DEFAULT_LINE = {
|
|
4018
4206
|
paint: { fill: "solid", color: "rgba(255, 255, 255, 0.08)" },
|
|
@@ -4731,16 +4919,115 @@ function startThresholdDrag(e, opts) {
|
|
|
4731
4919
|
document.addEventListener("pointercancel", onCancel);
|
|
4732
4920
|
return { isDragging: () => activated };
|
|
4733
4921
|
}
|
|
4922
|
+
|
|
4923
|
+
// src/core/device/profile.ts
|
|
4924
|
+
var COARSE_TARGET_SCALE = 1.75;
|
|
4925
|
+
function resolveDeviceProfile(detected, overrides) {
|
|
4926
|
+
const coarsePointer2 = overrides?.coarsePointer ?? detected.coarsePointer;
|
|
4927
|
+
const canHover2 = overrides?.canHover ?? detected.canHover;
|
|
4928
|
+
const dpr = overrides?.dpr ?? detected.dpr;
|
|
4929
|
+
const targetScale = overrides?.targetScale ?? (coarsePointer2 ? COARSE_TARGET_SCALE : 1);
|
|
4930
|
+
return { coarsePointer: coarsePointer2, canHover: canHover2, dpr, targetScale };
|
|
4931
|
+
}
|
|
4932
|
+
var DEFAULT_DEVICE_PROFILE = resolveDeviceProfile({
|
|
4933
|
+
coarsePointer: false,
|
|
4934
|
+
canHover: true,
|
|
4935
|
+
dpr: 1
|
|
4936
|
+
});
|
|
4937
|
+
|
|
4938
|
+
// src/core/device/useDeviceProfile.ts
|
|
4939
|
+
var COARSE_QUERY = "(pointer: coarse)";
|
|
4940
|
+
var HOVER_QUERY = "(hover: hover)";
|
|
4941
|
+
function hasMatchMedia() {
|
|
4942
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function";
|
|
4943
|
+
}
|
|
4944
|
+
function detectOnce() {
|
|
4945
|
+
const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
|
|
4946
|
+
if (!hasMatchMedia()) {
|
|
4947
|
+
return {
|
|
4948
|
+
coarsePointer: DEFAULT_DEVICE_PROFILE.coarsePointer,
|
|
4949
|
+
canHover: DEFAULT_DEVICE_PROFILE.canHover,
|
|
4950
|
+
dpr
|
|
4951
|
+
};
|
|
4952
|
+
}
|
|
4953
|
+
return {
|
|
4954
|
+
coarsePointer: window.matchMedia(COARSE_QUERY).matches,
|
|
4955
|
+
canHover: window.matchMedia(HOVER_QUERY).matches,
|
|
4956
|
+
dpr
|
|
4957
|
+
};
|
|
4958
|
+
}
|
|
4959
|
+
function useDetectedFacts() {
|
|
4960
|
+
const [facts, setFacts] = useState(detectOnce);
|
|
4961
|
+
useEffect(() => {
|
|
4962
|
+
if (!hasMatchMedia()) return;
|
|
4963
|
+
const coarseMq = window.matchMedia(COARSE_QUERY);
|
|
4964
|
+
const hoverMq = window.matchMedia(HOVER_QUERY);
|
|
4965
|
+
const onCoarse = (e) => setFacts((f) => ({ ...f, coarsePointer: e.matches }));
|
|
4966
|
+
const onHover = (e) => setFacts((f) => ({ ...f, canHover: e.matches }));
|
|
4967
|
+
coarseMq.addEventListener("change", onCoarse);
|
|
4968
|
+
hoverMq.addEventListener("change", onHover);
|
|
4969
|
+
let disposed = false;
|
|
4970
|
+
let densityMq = null;
|
|
4971
|
+
const onDensity = () => {
|
|
4972
|
+
if (disposed) return;
|
|
4973
|
+
const next = window.devicePixelRatio || 1;
|
|
4974
|
+
setFacts((f) => f.dpr === next ? f : { ...f, dpr: next });
|
|
4975
|
+
arm();
|
|
4976
|
+
};
|
|
4977
|
+
function arm() {
|
|
4978
|
+
if (disposed) return;
|
|
4979
|
+
densityMq?.removeEventListener("change", onDensity);
|
|
4980
|
+
densityMq = window.matchMedia(
|
|
4981
|
+
`(resolution: ${window.devicePixelRatio || 1}dppx)`
|
|
4982
|
+
);
|
|
4983
|
+
densityMq.addEventListener("change", onDensity);
|
|
4984
|
+
}
|
|
4985
|
+
arm();
|
|
4986
|
+
setFacts({
|
|
4987
|
+
coarsePointer: coarseMq.matches,
|
|
4988
|
+
canHover: hoverMq.matches,
|
|
4989
|
+
dpr: window.devicePixelRatio || 1
|
|
4990
|
+
});
|
|
4991
|
+
return () => {
|
|
4992
|
+
disposed = true;
|
|
4993
|
+
coarseMq.removeEventListener("change", onCoarse);
|
|
4994
|
+
hoverMq.removeEventListener("change", onHover);
|
|
4995
|
+
densityMq?.removeEventListener("change", onDensity);
|
|
4996
|
+
};
|
|
4997
|
+
}, []);
|
|
4998
|
+
return facts;
|
|
4999
|
+
}
|
|
5000
|
+
var DeviceProfileContext = createContext(null);
|
|
5001
|
+
function useDeviceProfile(overrides) {
|
|
5002
|
+
const provided = useContext(DeviceProfileContext);
|
|
5003
|
+
const detected = useDetectedFacts();
|
|
5004
|
+
const base = provided ?? detected;
|
|
5005
|
+
const { coarsePointer: coarsePointer2, canHover: canHover2, dpr, targetScale } = overrides ?? {};
|
|
5006
|
+
return useMemo(
|
|
5007
|
+
() => resolveDeviceProfile(base, {
|
|
5008
|
+
...coarsePointer2 !== void 0 ? { coarsePointer: coarsePointer2 } : {},
|
|
5009
|
+
...canHover2 !== void 0 ? { canHover: canHover2 } : {},
|
|
5010
|
+
...dpr !== void 0 ? { dpr } : {},
|
|
5011
|
+
...targetScale !== void 0 ? { targetScale } : {}
|
|
5012
|
+
}),
|
|
5013
|
+
[base.coarsePointer, base.canHover, base.dpr, coarsePointer2, canHover2, dpr, targetScale]
|
|
5014
|
+
);
|
|
5015
|
+
}
|
|
5016
|
+
function DeviceProfileProvider({ value, children }) {
|
|
5017
|
+
const profile = useDeviceProfile(value);
|
|
5018
|
+
return createElement(DeviceProfileContext.Provider, { value: profile }, children);
|
|
5019
|
+
}
|
|
5020
|
+
|
|
5021
|
+
// src/core/viewport/useCanvasSize.ts
|
|
4734
5022
|
function useCanvasSize(containerRef) {
|
|
4735
|
-
const
|
|
5023
|
+
const { dpr } = useDeviceProfile();
|
|
5024
|
+
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
4736
5025
|
const measure3 = useCallback(() => {
|
|
4737
5026
|
if (!containerRef.current) return;
|
|
4738
5027
|
const rect = containerRef.current.getBoundingClientRect();
|
|
4739
|
-
setSize(
|
|
4740
|
-
width: rect.width,
|
|
4741
|
-
|
|
4742
|
-
dpr: window.devicePixelRatio || 1
|
|
4743
|
-
});
|
|
5028
|
+
setSize(
|
|
5029
|
+
(prev) => prev.width === rect.width && prev.height === rect.height ? prev : { width: rect.width, height: rect.height }
|
|
5030
|
+
);
|
|
4744
5031
|
}, [containerRef]);
|
|
4745
5032
|
useEffect(() => {
|
|
4746
5033
|
measure3();
|
|
@@ -4750,9 +5037,14 @@ function useCanvasSize(containerRef) {
|
|
|
4750
5037
|
}
|
|
4751
5038
|
return () => observer.disconnect();
|
|
4752
5039
|
}, [measure3, containerRef]);
|
|
4753
|
-
return size;
|
|
5040
|
+
return { width: size.width, height: size.height, dpr };
|
|
4754
5041
|
}
|
|
4755
5042
|
|
|
5043
|
+
// src/core/device/targets.ts
|
|
5044
|
+
var HANDLE_BASE_PX = 8;
|
|
5045
|
+
var ANCHOR_HIT_BASE_PX = 8;
|
|
5046
|
+
var ROTATION_HANDLE_BASE_PX = 24;
|
|
5047
|
+
|
|
4756
5048
|
// src/core/viewport/fitToBounds.ts
|
|
4757
5049
|
function fitZoom(availW, availH, contentW, contentH, clamp2) {
|
|
4758
5050
|
const raw = Math.min(availW / contentW, availH / contentH);
|
|
@@ -5047,254 +5339,6 @@ function matchesKeyBinding(e, b) {
|
|
|
5047
5339
|
}
|
|
5048
5340
|
return true;
|
|
5049
5341
|
}
|
|
5050
|
-
var DepRegistryContext = createContext(null);
|
|
5051
|
-
function DepRegistryProvider({ children }) {
|
|
5052
|
-
const sourcesRef = useRef(/* @__PURE__ */ new Map());
|
|
5053
|
-
const registry2 = useMemo(() => ({
|
|
5054
|
-
register: (name, source) => {
|
|
5055
|
-
sourcesRef.current.set(name, source);
|
|
5056
|
-
return () => {
|
|
5057
|
-
sourcesRef.current.delete(name);
|
|
5058
|
-
};
|
|
5059
|
-
},
|
|
5060
|
-
get: (name) => sourcesRef.current.get(name)?.()
|
|
5061
|
-
}), []);
|
|
5062
|
-
return /* @__PURE__ */ jsx(DepRegistryContext.Provider, { value: registry2, children });
|
|
5063
|
-
}
|
|
5064
|
-
function useDepRegistry() {
|
|
5065
|
-
const r = useContext(DepRegistryContext);
|
|
5066
|
-
if (r === null) {
|
|
5067
|
-
throw new Error("useDepRegistry: no DepRegistryProvider in scope. Wrap your tree with <DepRegistryProvider> (typically inside <SceneCanvas>).");
|
|
5068
|
-
}
|
|
5069
|
-
return r;
|
|
5070
|
-
}
|
|
5071
|
-
function useOptionalDepRegistry() {
|
|
5072
|
-
return useContext(DepRegistryContext);
|
|
5073
|
-
}
|
|
5074
|
-
function useDepSource(name, source) {
|
|
5075
|
-
const registry2 = useDepRegistry();
|
|
5076
|
-
const sourceRef = useRef(source);
|
|
5077
|
-
sourceRef.current = source;
|
|
5078
|
-
useEffect(() => {
|
|
5079
|
-
return registry2.register(name, () => sourceRef.current());
|
|
5080
|
-
}, [name, registry2]);
|
|
5081
|
-
}
|
|
5082
|
-
|
|
5083
|
-
// src/interactions/actions/buildDeps.ts
|
|
5084
|
-
var DEV = (() => {
|
|
5085
|
-
try {
|
|
5086
|
-
return Boolean(import.meta.env?.DEV);
|
|
5087
|
-
} catch {
|
|
5088
|
-
return false;
|
|
5089
|
-
}
|
|
5090
|
-
})();
|
|
5091
|
-
function buildDepsFromRequires(action, depRegistry) {
|
|
5092
|
-
const requires = action.requires ?? [];
|
|
5093
|
-
const entries = requires.map((name) => [name, depRegistry.get(name)]);
|
|
5094
|
-
const deps = Object.fromEntries(entries);
|
|
5095
|
-
if (DEV) {
|
|
5096
|
-
const declared = new Set(requires);
|
|
5097
|
-
return new Proxy(deps, {
|
|
5098
|
-
get(target, prop, receiver) {
|
|
5099
|
-
if (typeof prop === "string" && !declared.has(prop) && prop !== "then") {
|
|
5100
|
-
const message = `[weasel:dispatcher] action "${action.id}" read deps.${prop} but did not declare it in \`requires\`. Add \`requires: [...'${prop}']\` to the descriptor or the dep will be undefined at runtime.`;
|
|
5101
|
-
console.warn(message);
|
|
5102
|
-
throw new Error(message);
|
|
5103
|
-
}
|
|
5104
|
-
return Reflect.get(target, prop, receiver);
|
|
5105
|
-
}
|
|
5106
|
-
});
|
|
5107
|
-
}
|
|
5108
|
-
return deps;
|
|
5109
|
-
}
|
|
5110
|
-
var ActionDisabledReason = {
|
|
5111
|
-
SelectionRequired: "selection-required",
|
|
5112
|
-
SceneEmpty: "scene-empty",
|
|
5113
|
-
NotApplicable: "not-applicable",
|
|
5114
|
-
/** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
|
|
5115
|
-
PredicateThrew: "predicate-threw"
|
|
5116
|
-
};
|
|
5117
|
-
var enabledSlowWarned = /* @__PURE__ */ new Set();
|
|
5118
|
-
var enabledThrewWarned = /* @__PURE__ */ new Set();
|
|
5119
|
-
var ENABLED_BUDGET_MS = 4;
|
|
5120
|
-
function evaluateEnabled(action, deps) {
|
|
5121
|
-
if (!action.enabled) return { enabled: true };
|
|
5122
|
-
const isDev3 = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
5123
|
-
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
5124
|
-
const start = isDev3 ? now() : 0;
|
|
5125
|
-
try {
|
|
5126
|
-
const result = action.enabled(deps);
|
|
5127
|
-
if (isDev3) {
|
|
5128
|
-
const elapsed = now() - start;
|
|
5129
|
-
if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {
|
|
5130
|
-
enabledSlowWarned.add(action.id);
|
|
5131
|
-
console.warn(
|
|
5132
|
-
`weasel actions: enabled() for action "${action.id}" took ${elapsed.toFixed(2)}ms (budget ${ENABLED_BUDGET_MS}ms). The predicate must be pure and fast \u2014 see Action.enabled JSDoc.`
|
|
5133
|
-
);
|
|
5134
|
-
}
|
|
5135
|
-
}
|
|
5136
|
-
if (result === true) return { enabled: true };
|
|
5137
|
-
return { enabled: false, reason: result };
|
|
5138
|
-
} catch (e) {
|
|
5139
|
-
if (isDev3 && !enabledThrewWarned.has(action.id)) {
|
|
5140
|
-
enabledThrewWarned.add(action.id);
|
|
5141
|
-
console.warn(
|
|
5142
|
-
`weasel actions: enabled() for action "${action.id}" threw; treating as disabled. The predicate must not throw \u2014 see Action.enabled JSDoc.`,
|
|
5143
|
-
e
|
|
5144
|
-
);
|
|
5145
|
-
}
|
|
5146
|
-
return { enabled: false, reason: ActionDisabledReason.PredicateThrew };
|
|
5147
|
-
}
|
|
5148
|
-
}
|
|
5149
|
-
function validateActionId(id) {
|
|
5150
|
-
if (id.length === 0) {
|
|
5151
|
-
throw new Error(`weasel: action id may not be empty`);
|
|
5152
|
-
}
|
|
5153
|
-
if (RESERVED_ID_PREFIXES.has(id[0])) {
|
|
5154
|
-
throw new Error(
|
|
5155
|
-
`weasel: action id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
|
|
5156
|
-
);
|
|
5157
|
-
}
|
|
5158
|
-
if (RESERVED_ID_NAMES.has(id)) {
|
|
5159
|
-
throw new Error(
|
|
5160
|
-
`weasel: action id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
|
|
5161
|
-
);
|
|
5162
|
-
}
|
|
5163
|
-
}
|
|
5164
|
-
function validateActionDefaultBinding(action) {
|
|
5165
|
-
const gs = action.defaultBinding;
|
|
5166
|
-
if (!gs) return;
|
|
5167
|
-
const entries = Array.isArray(gs) ? gs : [gs];
|
|
5168
|
-
for (const entry of entries) {
|
|
5169
|
-
const spec = "kind" in entry ? entry : entry.spec;
|
|
5170
|
-
const phase = spec.phase;
|
|
5171
|
-
if (phase === void 0) continue;
|
|
5172
|
-
const atoms = Array.isArray(phase) ? phase : [{ channel: "&", phase }];
|
|
5173
|
-
for (const atom of atoms) {
|
|
5174
|
-
if (atom.channel === "&") {
|
|
5175
|
-
throw new Error(
|
|
5176
|
-
`weasel: action "${action.id}" defaultBinding uses '&' channel which has no owning tool to resolve to. Use a named tool channel (e.g. '[rect:engaged]') or '*' instead, or move this binding to the tool's own Tool.bindings.`
|
|
5177
|
-
);
|
|
5178
|
-
}
|
|
5179
|
-
}
|
|
5180
|
-
}
|
|
5181
|
-
}
|
|
5182
|
-
var ActionsContext = createContext(null);
|
|
5183
|
-
function ActionsProvider({ children }) {
|
|
5184
|
-
const actionsRef = useRef(/* @__PURE__ */ new Map());
|
|
5185
|
-
const versionRef = useRef(0);
|
|
5186
|
-
const cachedRef = useRef([]);
|
|
5187
|
-
const cachedVerRef = useRef(-1);
|
|
5188
|
-
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
5189
|
-
const depReg = useOptionalDepRegistry();
|
|
5190
|
-
const depRegRef = useRef(depReg);
|
|
5191
|
-
depRegRef.current = depReg;
|
|
5192
|
-
const wiredDepRegRef = useRef(null);
|
|
5193
|
-
const dispatcherRef = useRef(null);
|
|
5194
|
-
const registry2 = useMemo(() => {
|
|
5195
|
-
const snapshot = () => {
|
|
5196
|
-
const v = versionRef.current;
|
|
5197
|
-
if (cachedVerRef.current === v) return cachedRef.current;
|
|
5198
|
-
const out = Object.freeze(Array.from(actionsRef.current.values()));
|
|
5199
|
-
cachedRef.current = out;
|
|
5200
|
-
cachedVerRef.current = v;
|
|
5201
|
-
return out;
|
|
5202
|
-
};
|
|
5203
|
-
const notify2 = () => {
|
|
5204
|
-
for (const l of listenersRef.current) {
|
|
5205
|
-
try {
|
|
5206
|
-
l();
|
|
5207
|
-
} catch (err) {
|
|
5208
|
-
console.error("weasel ActionsRegistry: subscriber threw", err);
|
|
5209
|
-
}
|
|
5210
|
-
}
|
|
5211
|
-
};
|
|
5212
|
-
return {
|
|
5213
|
-
register: (action) => {
|
|
5214
|
-
validateActionId(action.id);
|
|
5215
|
-
validateActionDefaultBinding(action);
|
|
5216
|
-
actionsRef.current.set(action.id, action);
|
|
5217
|
-
versionRef.current++;
|
|
5218
|
-
notify2();
|
|
5219
|
-
return () => {
|
|
5220
|
-
const cur = actionsRef.current.get(action.id);
|
|
5221
|
-
if (cur === action) {
|
|
5222
|
-
actionsRef.current.delete(action.id);
|
|
5223
|
-
versionRef.current++;
|
|
5224
|
-
notify2();
|
|
5225
|
-
}
|
|
5226
|
-
};
|
|
5227
|
-
},
|
|
5228
|
-
unregister: (id) => {
|
|
5229
|
-
if (actionsRef.current.delete(id)) {
|
|
5230
|
-
versionRef.current++;
|
|
5231
|
-
notify2();
|
|
5232
|
-
}
|
|
5233
|
-
},
|
|
5234
|
-
list: () => snapshot(),
|
|
5235
|
-
trigger: (id, params) => {
|
|
5236
|
-
const a = actionsRef.current.get(id);
|
|
5237
|
-
if (!a) return false;
|
|
5238
|
-
try {
|
|
5239
|
-
if (a.invoker && a.invoker.timing === "immediate") {
|
|
5240
|
-
const r = wiredDepRegRef.current ?? depRegRef.current;
|
|
5241
|
-
const deps = !r ? {} : a.requires ? buildDepsFromRequires(a, r) : {
|
|
5242
|
-
selection: r.get("selection"),
|
|
5243
|
-
scene: r.get("scene"),
|
|
5244
|
-
history: r.get("history"),
|
|
5245
|
-
view: r.get("view"),
|
|
5246
|
-
pointer: r.get("pointer"),
|
|
5247
|
-
activeTool: r.get("activeTool"),
|
|
5248
|
-
booleansAdapter: r.get("booleansAdapter")
|
|
5249
|
-
};
|
|
5250
|
-
a.invoker.run(deps, params);
|
|
5251
|
-
}
|
|
5252
|
-
} catch (err) {
|
|
5253
|
-
console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
|
|
5254
|
-
}
|
|
5255
|
-
return true;
|
|
5256
|
-
},
|
|
5257
|
-
subscribe: (listener) => {
|
|
5258
|
-
listenersRef.current.add(listener);
|
|
5259
|
-
return () => {
|
|
5260
|
-
listenersRef.current.delete(listener);
|
|
5261
|
-
};
|
|
5262
|
-
},
|
|
5263
|
-
setDispatcher: (d) => {
|
|
5264
|
-
dispatcherRef.current = d;
|
|
5265
|
-
},
|
|
5266
|
-
setDepRegistry: (r) => {
|
|
5267
|
-
wiredDepRegRef.current = r;
|
|
5268
|
-
},
|
|
5269
|
-
begin: (id, params) => {
|
|
5270
|
-
const disp = dispatcherRef.current;
|
|
5271
|
-
if (!disp) return null;
|
|
5272
|
-
const r = wiredDepRegRef.current ?? depRegRef.current;
|
|
5273
|
-
const deps = r ? {
|
|
5274
|
-
selection: r.get("selection"),
|
|
5275
|
-
scene: r.get("scene"),
|
|
5276
|
-
history: r.get("history"),
|
|
5277
|
-
view: r.get("view"),
|
|
5278
|
-
pointer: r.get("pointer"),
|
|
5279
|
-
activeTool: r.get("activeTool"),
|
|
5280
|
-
booleansAdapter: r.get("booleansAdapter")
|
|
5281
|
-
} : {};
|
|
5282
|
-
return disp.beginUiOngoing(id, deps, params);
|
|
5283
|
-
}
|
|
5284
|
-
};
|
|
5285
|
-
}, []);
|
|
5286
|
-
return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: registry2, children });
|
|
5287
|
-
}
|
|
5288
|
-
function useActionsRegistry() {
|
|
5289
|
-
return useContext(ActionsContext);
|
|
5290
|
-
}
|
|
5291
|
-
function useAction(action) {
|
|
5292
|
-
const reg = useActionsRegistry();
|
|
5293
|
-
useEffect(() => {
|
|
5294
|
-
if (!reg) return;
|
|
5295
|
-
return reg.register(action);
|
|
5296
|
-
}, [reg, action]);
|
|
5297
|
-
}
|
|
5298
5342
|
|
|
5299
5343
|
// src/interactions/actions/invoker.ts
|
|
5300
5344
|
function resolveParams(params) {
|
|
@@ -5867,6 +5911,62 @@ var moveAction = {
|
|
|
5867
5911
|
enabled: () => true
|
|
5868
5912
|
};
|
|
5869
5913
|
|
|
5914
|
+
// src/features/paths/poseRotation.ts
|
|
5915
|
+
function poseRotationOf(pose) {
|
|
5916
|
+
const p = pose;
|
|
5917
|
+
if (p.rotation && p.x != null && p.y != null && p.width != null && p.height != null) {
|
|
5918
|
+
return { cx: p.x + p.width / 2, cy: p.y + p.height / 2, rotation: p.rotation };
|
|
5919
|
+
}
|
|
5920
|
+
return null;
|
|
5921
|
+
}
|
|
5922
|
+
function rotatePathAround(path, cx, cy, rotation) {
|
|
5923
|
+
if (path.kind === "rect") {
|
|
5924
|
+
return rotateRectCorners(path.x, path.y, path.width, path.height, cx, cy, rotation);
|
|
5925
|
+
}
|
|
5926
|
+
return rotatePolygon(path, cx, cy, rotation);
|
|
5927
|
+
}
|
|
5928
|
+
function rotatePolygon(path, cx, cy, rotation) {
|
|
5929
|
+
const cos = Math.cos(rotation);
|
|
5930
|
+
const sin = Math.sin(rotation);
|
|
5931
|
+
const { commands, coords } = path;
|
|
5932
|
+
const next = new Float32Array(coords.length);
|
|
5933
|
+
let ci = 0;
|
|
5934
|
+
for (let i = 0; i < commands.length; i++) {
|
|
5935
|
+
const len = COORD_COUNT[commands[i]];
|
|
5936
|
+
for (let k = 0; k < len; k += 2) {
|
|
5937
|
+
const dx = coords[ci + k] - cx;
|
|
5938
|
+
const dy = coords[ci + k + 1] - cy;
|
|
5939
|
+
next[ci + k] = cx + dx * cos - dy * sin;
|
|
5940
|
+
next[ci + k + 1] = cy + dx * sin + dy * cos;
|
|
5941
|
+
}
|
|
5942
|
+
ci += len;
|
|
5943
|
+
}
|
|
5944
|
+
return { kind: "polygon", commands: path.commands, coords: next, fillRule: path.fillRule };
|
|
5945
|
+
}
|
|
5946
|
+
function rotateRectCorners(x, y, w, h, cx, cy, rotation) {
|
|
5947
|
+
const cos = Math.cos(rotation);
|
|
5948
|
+
const sin = Math.sin(rotation);
|
|
5949
|
+
const rotate = (px, py) => {
|
|
5950
|
+
const dx = px - cx;
|
|
5951
|
+
const dy = py - cy;
|
|
5952
|
+
return [cx + dx * cos - dy * sin, cy + dx * sin + dy * cos];
|
|
5953
|
+
};
|
|
5954
|
+
const [x0, y0] = rotate(x, y);
|
|
5955
|
+
const [x1, y1] = rotate(x + w, y);
|
|
5956
|
+
const [x2, y2] = rotate(x + w, y + h);
|
|
5957
|
+
const [x3, y3] = rotate(x, y + h);
|
|
5958
|
+
const commands = new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]);
|
|
5959
|
+
const coords = new Float32Array([x0, y0, x1, y1, x2, y2, x3, y3]);
|
|
5960
|
+
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
5961
|
+
}
|
|
5962
|
+
var COORD_COUNT = {
|
|
5963
|
+
[PATH_M]: 2,
|
|
5964
|
+
[PATH_L]: 2,
|
|
5965
|
+
[PATH_C]: 6,
|
|
5966
|
+
[PATH_Q]: 4,
|
|
5967
|
+
[PATH_Z]: 0
|
|
5968
|
+
};
|
|
5969
|
+
|
|
5870
5970
|
// src/interactions/actions/rotate/geometry.ts
|
|
5871
5971
|
function aabbCenter(bounds) {
|
|
5872
5972
|
return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
|
|
@@ -6183,6 +6283,13 @@ var resizeAction = {
|
|
|
6183
6283
|
const computePose = (id) => {
|
|
6184
6284
|
const sp = scratch.startPoses.get(id);
|
|
6185
6285
|
if (sp === void 0) return void 0;
|
|
6286
|
+
if (isGroupPath && poseRotationOf(sp) !== null) {
|
|
6287
|
+
return remapRotatedLeaf(
|
|
6288
|
+
sp,
|
|
6289
|
+
scratch.originBounds,
|
|
6290
|
+
proposedBounds
|
|
6291
|
+
);
|
|
6292
|
+
}
|
|
6186
6293
|
return scratch.geometry.remapBounds(sp, scratch.originBounds, proposedBounds);
|
|
6187
6294
|
};
|
|
6188
6295
|
scratch.previews.clear();
|
|
@@ -6812,6 +6919,12 @@ var clearSelectionAction = {
|
|
|
6812
6919
|
}
|
|
6813
6920
|
};
|
|
6814
6921
|
|
|
6922
|
+
// src/interactions/actions/defaults/freshNodeId.ts
|
|
6923
|
+
var counter = 0;
|
|
6924
|
+
function freshNodeId() {
|
|
6925
|
+
return asNodeId(`n${(counter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);
|
|
6926
|
+
}
|
|
6927
|
+
|
|
6815
6928
|
// src/interactions/actions/defaults/clone.ts
|
|
6816
6929
|
function translatePose(pose, dx, dy) {
|
|
6817
6930
|
const p = pose;
|
|
@@ -6821,10 +6934,6 @@ function translatePose(pose, dx, dy) {
|
|
|
6821
6934
|
y: (p["y"] ?? 0) + dy
|
|
6822
6935
|
};
|
|
6823
6936
|
}
|
|
6824
|
-
var cloneIdCounter = 0;
|
|
6825
|
-
function freshCloneId() {
|
|
6826
|
-
return asNodeId(`n${(cloneIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);
|
|
6827
|
-
}
|
|
6828
6937
|
var cloneAction = {
|
|
6829
6938
|
id: "clone",
|
|
6830
6939
|
label: "Clone",
|
|
@@ -6891,7 +7000,7 @@ var cloneAction = {
|
|
|
6891
7000
|
if (origin === void 0 || !originNode) continue;
|
|
6892
7001
|
const newPose = translatePose(origin, dx, dy);
|
|
6893
7002
|
const node = {
|
|
6894
|
-
id:
|
|
7003
|
+
id: freshNodeId(),
|
|
6895
7004
|
kind: originNode.kind,
|
|
6896
7005
|
layer: originNode.layer,
|
|
6897
7006
|
pose: newPose,
|
|
@@ -7038,7 +7147,13 @@ function makeViewportZoomAction(opts = {}) {
|
|
|
7038
7147
|
var viewportZoomAction = makeViewportZoomAction();
|
|
7039
7148
|
|
|
7040
7149
|
// src/interactions/dispatcher/predicates.ts
|
|
7041
|
-
|
|
7150
|
+
function bodyPredicate(fn) {
|
|
7151
|
+
return Object.assign(fn, { readsAffordance: false });
|
|
7152
|
+
}
|
|
7153
|
+
var isBody = bodyPredicate((_target, bodyTarget) => bodyTarget === "selected-body" || bodyTarget === "unselected-body");
|
|
7154
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "selected-body");
|
|
7155
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "unselected-body");
|
|
7156
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "empty");
|
|
7042
7157
|
function kindOf(target) {
|
|
7043
7158
|
if (typeof target !== "object" || target === null) return void 0;
|
|
7044
7159
|
const k = target.kind;
|
|
@@ -7079,17 +7194,17 @@ function splitCubicAtT(p0, p1, p2, p3, t) {
|
|
|
7079
7194
|
function fitCubicThroughDeletion(prev, next) {
|
|
7080
7195
|
const dx = next.x - prev.x;
|
|
7081
7196
|
const dy = next.y - prev.y;
|
|
7082
|
-
const
|
|
7197
|
+
const dist3 = Math.sqrt(dx * dx + dy * dy);
|
|
7083
7198
|
const c1DirX = prev.outHandle ? prev.outHandle.x - prev.x : dx;
|
|
7084
7199
|
const c1DirY = prev.outHandle ? prev.outHandle.y - prev.y : dy;
|
|
7085
|
-
const c1 = controlOnSide(prev, c1DirX, c1DirY,
|
|
7200
|
+
const c1 = controlOnSide(prev, c1DirX, c1DirY, dist3, 1 / 3);
|
|
7086
7201
|
const c2DirX = next.inHandle ? next.inHandle.x - next.x : -dx;
|
|
7087
7202
|
const c2DirY = next.inHandle ? next.inHandle.y - next.y : -dy;
|
|
7088
|
-
const c2 = controlOnSide(next, c2DirX, c2DirY,
|
|
7203
|
+
const c2 = controlOnSide(next, c2DirX, c2DirY, dist3, 1 / 3);
|
|
7089
7204
|
return { c1, c2 };
|
|
7090
7205
|
}
|
|
7091
|
-
function controlOnSide(anchor, dirX, dirY,
|
|
7092
|
-
const targetDist =
|
|
7206
|
+
function controlOnSide(anchor, dirX, dirY, dist3, fraction) {
|
|
7207
|
+
const targetDist = dist3 * fraction;
|
|
7093
7208
|
const len = Math.hypot(dirX, dirY) || 1;
|
|
7094
7209
|
const ux = dirX / len, uy = dirY / len;
|
|
7095
7210
|
return { x: anchor.x + ux * targetDist, y: anchor.y + uy * targetDist };
|
|
@@ -7360,64 +7475,6 @@ function anchorsInRect(set, rect) {
|
|
|
7360
7475
|
}
|
|
7361
7476
|
return hits;
|
|
7362
7477
|
}
|
|
7363
|
-
|
|
7364
|
-
// src/features/paths/poseRotation.ts
|
|
7365
|
-
function poseRotationOf(pose) {
|
|
7366
|
-
const p = pose;
|
|
7367
|
-
if (p.rotation && p.x != null && p.y != null && p.width != null && p.height != null) {
|
|
7368
|
-
return { cx: p.x + p.width / 2, cy: p.y + p.height / 2, rotation: p.rotation };
|
|
7369
|
-
}
|
|
7370
|
-
return null;
|
|
7371
|
-
}
|
|
7372
|
-
function rotatePathAround(path, cx, cy, rotation) {
|
|
7373
|
-
if (path.kind === "rect") {
|
|
7374
|
-
return rotateRectCorners(path.x, path.y, path.width, path.height, cx, cy, rotation);
|
|
7375
|
-
}
|
|
7376
|
-
return rotatePolygon(path, cx, cy, rotation);
|
|
7377
|
-
}
|
|
7378
|
-
function rotatePolygon(path, cx, cy, rotation) {
|
|
7379
|
-
const cos = Math.cos(rotation);
|
|
7380
|
-
const sin = Math.sin(rotation);
|
|
7381
|
-
const { commands, coords } = path;
|
|
7382
|
-
const next = new Float32Array(coords.length);
|
|
7383
|
-
let ci = 0;
|
|
7384
|
-
for (let i = 0; i < commands.length; i++) {
|
|
7385
|
-
const len = COORD_COUNT[commands[i]];
|
|
7386
|
-
for (let k = 0; k < len; k += 2) {
|
|
7387
|
-
const dx = coords[ci + k] - cx;
|
|
7388
|
-
const dy = coords[ci + k + 1] - cy;
|
|
7389
|
-
next[ci + k] = cx + dx * cos - dy * sin;
|
|
7390
|
-
next[ci + k + 1] = cy + dx * sin + dy * cos;
|
|
7391
|
-
}
|
|
7392
|
-
ci += len;
|
|
7393
|
-
}
|
|
7394
|
-
return { kind: "polygon", commands: path.commands, coords: next, fillRule: path.fillRule };
|
|
7395
|
-
}
|
|
7396
|
-
function rotateRectCorners(x, y, w, h, cx, cy, rotation) {
|
|
7397
|
-
const cos = Math.cos(rotation);
|
|
7398
|
-
const sin = Math.sin(rotation);
|
|
7399
|
-
const rotate = (px, py) => {
|
|
7400
|
-
const dx = px - cx;
|
|
7401
|
-
const dy = py - cy;
|
|
7402
|
-
return [cx + dx * cos - dy * sin, cy + dx * sin + dy * cos];
|
|
7403
|
-
};
|
|
7404
|
-
const [x0, y0] = rotate(x, y);
|
|
7405
|
-
const [x1, y1] = rotate(x + w, y);
|
|
7406
|
-
const [x2, y2] = rotate(x + w, y + h);
|
|
7407
|
-
const [x3, y3] = rotate(x, y + h);
|
|
7408
|
-
const commands = new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]);
|
|
7409
|
-
const coords = new Float32Array([x0, y0, x1, y1, x2, y2, x3, y3]);
|
|
7410
|
-
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
7411
|
-
}
|
|
7412
|
-
var COORD_COUNT = {
|
|
7413
|
-
[PATH_M]: 2,
|
|
7414
|
-
[PATH_L]: 2,
|
|
7415
|
-
[PATH_C]: 6,
|
|
7416
|
-
[PATH_Q]: 4,
|
|
7417
|
-
[PATH_Z]: 0
|
|
7418
|
-
};
|
|
7419
|
-
|
|
7420
|
-
// src/features/paths/pathInWorld.ts
|
|
7421
7478
|
function pathInPoseFrame(path, pose) {
|
|
7422
7479
|
if (path.kind === "rect") {
|
|
7423
7480
|
if (path.x === pose.x && path.y === pose.y && path.width === pose.width && path.height === pose.height) return path;
|
|
@@ -7754,6 +7811,125 @@ var pinchZoomAction = {
|
|
|
7754
7811
|
enabled: () => true
|
|
7755
7812
|
};
|
|
7756
7813
|
|
|
7814
|
+
// src/interactions/actions/defaults/delete.ts
|
|
7815
|
+
function hostIndex(scene, id) {
|
|
7816
|
+
const node = scene.get(id);
|
|
7817
|
+
const siblings = node?.parent == null ? scene.roots : scene.childrenOf(node.parent);
|
|
7818
|
+
return siblings.indexOf(id);
|
|
7819
|
+
}
|
|
7820
|
+
function hasSelectedAncestor(scene, id, set) {
|
|
7821
|
+
let parent = scene.get(id)?.parent ?? null;
|
|
7822
|
+
while (parent != null) {
|
|
7823
|
+
if (set.has(parent)) return true;
|
|
7824
|
+
parent = scene.get(parent)?.parent ?? null;
|
|
7825
|
+
}
|
|
7826
|
+
return false;
|
|
7827
|
+
}
|
|
7828
|
+
function buildDeleteOps(scene, ids, label) {
|
|
7829
|
+
const set = new Set(ids);
|
|
7830
|
+
const ops = [];
|
|
7831
|
+
for (const id of ids) {
|
|
7832
|
+
const node = scene.get(id);
|
|
7833
|
+
if (!node) continue;
|
|
7834
|
+
if (hasSelectedAncestor(scene, id, set)) continue;
|
|
7835
|
+
ops.push(createDeleteOp({
|
|
7836
|
+
node,
|
|
7837
|
+
index: hostIndex(scene, id),
|
|
7838
|
+
label
|
|
7839
|
+
}));
|
|
7840
|
+
}
|
|
7841
|
+
return ops;
|
|
7842
|
+
}
|
|
7843
|
+
var deleteAction = {
|
|
7844
|
+
id: "delete",
|
|
7845
|
+
label: "Delete",
|
|
7846
|
+
// Suppressed while any tool is mid-gesture — accidentally hitting
|
|
7847
|
+
// Delete during a drag shouldn't wipe the selection out from under
|
|
7848
|
+
// the in-flight handle.
|
|
7849
|
+
defaultBinding: {
|
|
7850
|
+
kind: "key",
|
|
7851
|
+
key: ["Delete", "Backspace"],
|
|
7852
|
+
phase: [{ channel: "*", phase: "initial" }]
|
|
7853
|
+
},
|
|
7854
|
+
eligible: { capability: "edits-page" },
|
|
7855
|
+
requires: ["scene", "selection", "applyOps"],
|
|
7856
|
+
invoker: {
|
|
7857
|
+
timing: "immediate",
|
|
7858
|
+
run: (deps) => {
|
|
7859
|
+
const selection = deps.selection;
|
|
7860
|
+
const scene = deps.scene;
|
|
7861
|
+
const applyOps = deps.applyOps;
|
|
7862
|
+
if (!selection || !scene) return;
|
|
7863
|
+
const ids = selection.get();
|
|
7864
|
+
if (ids.length === 0) return;
|
|
7865
|
+
const ops = buildDeleteOps(scene, ids, "Delete");
|
|
7866
|
+
if (ops.length > 0) {
|
|
7867
|
+
if (applyOps) applyOps(ops, "Delete");
|
|
7868
|
+
else scene.applyBatch(ops, "Delete", defaultCommitAdapter(scene));
|
|
7869
|
+
}
|
|
7870
|
+
selection.set([]);
|
|
7871
|
+
}
|
|
7872
|
+
},
|
|
7873
|
+
enabled: () => true
|
|
7874
|
+
};
|
|
7875
|
+
|
|
7876
|
+
// src/interactions/actions/defaults/requiresSelection.ts
|
|
7877
|
+
function requiresSelection(deps) {
|
|
7878
|
+
const selection = deps?.selection;
|
|
7879
|
+
if (!selection || selection.get().length === 0) {
|
|
7880
|
+
return ActionDisabledReason.SelectionRequired;
|
|
7881
|
+
}
|
|
7882
|
+
return true;
|
|
7883
|
+
}
|
|
7884
|
+
|
|
7885
|
+
// src/interactions/actions/defaults/clipboard.ts
|
|
7886
|
+
var clipboardCopyAction = {
|
|
7887
|
+
id: "clipboard.copy",
|
|
7888
|
+
label: "Copy",
|
|
7889
|
+
defaultBinding: { kind: "key", key: "c", mods: { mod: true } },
|
|
7890
|
+
// Same gate as `duplicate`: a mode that can neither edit the page nor own a
|
|
7891
|
+
// selection has nothing to copy. Notably excludes `text-edit`, where Cmd+C
|
|
7892
|
+
// must stay the browser's own copy of the caret range.
|
|
7893
|
+
eligible: { capability: ["edits-page", "creates-selection"] },
|
|
7894
|
+
// `selection` is read by the `enabled` gate, not the invoker — the
|
|
7895
|
+
// dispatcher builds deps for both, and an undeclared read is undefined.
|
|
7896
|
+
requires: ["clipboard", "selection"],
|
|
7897
|
+
invoker: {
|
|
7898
|
+
timing: "immediate",
|
|
7899
|
+
run: (deps) => {
|
|
7900
|
+
deps.clipboard?.copy();
|
|
7901
|
+
}
|
|
7902
|
+
},
|
|
7903
|
+
enabled: requiresSelection
|
|
7904
|
+
};
|
|
7905
|
+
var clipboardCutAction = {
|
|
7906
|
+
id: "clipboard.cut",
|
|
7907
|
+
label: "Cut",
|
|
7908
|
+
defaultBinding: { kind: "key", key: "x", mods: { mod: true } },
|
|
7909
|
+
eligible: { capability: "edits-page" },
|
|
7910
|
+
requires: ["clipboard", "scene", "selection", "applyOps"],
|
|
7911
|
+
invoker: {
|
|
7912
|
+
timing: "immediate",
|
|
7913
|
+
run: (deps) => {
|
|
7914
|
+
const clipboard = deps.clipboard;
|
|
7915
|
+
const selection = deps.selection;
|
|
7916
|
+
const scene = deps.scene;
|
|
7917
|
+
const applyOps = deps.applyOps;
|
|
7918
|
+
if (!clipboard || !selection || !scene) return;
|
|
7919
|
+
const ids = selection.get();
|
|
7920
|
+
if (ids.length === 0) return;
|
|
7921
|
+
clipboard.copy();
|
|
7922
|
+
const ops = buildDeleteOps(scene, ids, "Cut");
|
|
7923
|
+
if (ops.length > 0) {
|
|
7924
|
+
if (applyOps) applyOps(ops, "Cut");
|
|
7925
|
+
else scene.applyBatch(ops, "Cut", defaultCommitAdapter(scene));
|
|
7926
|
+
}
|
|
7927
|
+
selection.set([]);
|
|
7928
|
+
}
|
|
7929
|
+
},
|
|
7930
|
+
enabled: requiresSelection
|
|
7931
|
+
};
|
|
7932
|
+
|
|
7757
7933
|
// src/interactions/actions/defaults/enterTextEdit.ts
|
|
7758
7934
|
var enterTextEditAction = {
|
|
7759
7935
|
id: "enterTextEdit",
|
|
@@ -7886,25 +8062,46 @@ var selectAllAction = {
|
|
|
7886
8062
|
}
|
|
7887
8063
|
};
|
|
7888
8064
|
|
|
7889
|
-
// src/interactions/actions/defaults/
|
|
7890
|
-
|
|
8065
|
+
// src/interactions/actions/defaults/duplicate.ts
|
|
8066
|
+
var DUPLICATE_OFFSET = 12;
|
|
8067
|
+
function translatePose2(pose, dx, dy) {
|
|
8068
|
+
const p = pose;
|
|
8069
|
+
return { ...p, x: (p["x"] ?? 0) + dx, y: (p["y"] ?? 0) + dy };
|
|
8070
|
+
}
|
|
8071
|
+
function hasSelectedAncestor2(scene, id, set) {
|
|
8072
|
+
let parent = scene.get(id)?.parent ?? null;
|
|
8073
|
+
while (parent != null) {
|
|
8074
|
+
if (set.has(parent)) return true;
|
|
8075
|
+
parent = scene.get(parent)?.parent ?? null;
|
|
8076
|
+
}
|
|
8077
|
+
return false;
|
|
8078
|
+
}
|
|
8079
|
+
function copySubtree(scene, id, parent, offset, offsetDescendants, ops) {
|
|
7891
8080
|
const node = scene.get(id);
|
|
7892
|
-
|
|
7893
|
-
|
|
8081
|
+
if (!node) return null;
|
|
8082
|
+
const newId = freshNodeId();
|
|
8083
|
+
const copy = {
|
|
8084
|
+
...node,
|
|
8085
|
+
id: newId,
|
|
8086
|
+
pose: translatePose2(node.pose, offset.dx, offset.dy),
|
|
8087
|
+
parent
|
|
8088
|
+
};
|
|
8089
|
+
ops.push(createInsertOp({ node: copy, label: "Duplicate" }));
|
|
8090
|
+
const childOffset = offsetDescendants ? offset : { dx: 0, dy: 0 };
|
|
8091
|
+
for (const child of scene.childrenOf(id)) {
|
|
8092
|
+
copySubtree(scene, child, newId, childOffset, offsetDescendants, ops);
|
|
8093
|
+
}
|
|
8094
|
+
return newId;
|
|
7894
8095
|
}
|
|
7895
|
-
var
|
|
7896
|
-
id: "
|
|
7897
|
-
label: "
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
// the
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
phase: [{ channel: "*", phase: "initial" }]
|
|
7905
|
-
},
|
|
7906
|
-
eligible: { capability: "edits-page" },
|
|
7907
|
-
requires: ["scene", "selection", "applyOps"],
|
|
8096
|
+
var duplicateAction = {
|
|
8097
|
+
id: "duplicate",
|
|
8098
|
+
label: "Duplicate",
|
|
8099
|
+
defaultBinding: { kind: "key", key: "d", mods: { mod: true } },
|
|
8100
|
+
eligible: { capability: ["edits-page", "creates-selection"] },
|
|
8101
|
+
// `selection` is read by the `enabled` gate as well as the invoker. An
|
|
8102
|
+
// undeclared read throws inside the dev-build deps Proxy before the gate can
|
|
8103
|
+
// answer, which is how this action spent a while doing nothing at all.
|
|
8104
|
+
requires: ["scene", "selection", "applyOps", "poseComposition"],
|
|
7908
8105
|
invoker: {
|
|
7909
8106
|
timing: "immediate",
|
|
7910
8107
|
run: (deps) => {
|
|
@@ -7914,44 +8111,22 @@ var deleteAction = {
|
|
|
7914
8111
|
if (!selection || !scene) return;
|
|
7915
8112
|
const ids = selection.get();
|
|
7916
8113
|
if (ids.length === 0) return;
|
|
8114
|
+
const set = new Set(ids);
|
|
8115
|
+
const offset = { dx: DUPLICATE_OFFSET, dy: DUPLICATE_OFFSET };
|
|
7917
8116
|
const ops = [];
|
|
8117
|
+
const newIds = [];
|
|
7918
8118
|
for (const id of ids) {
|
|
7919
|
-
const
|
|
8119
|
+
const nid = asNodeId(id);
|
|
8120
|
+
const node = scene.get(nid);
|
|
7920
8121
|
if (!node) continue;
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
label: "Delete"
|
|
7925
|
-
}));
|
|
8122
|
+
if (hasSelectedAncestor2(scene, nid, set)) continue;
|
|
8123
|
+
const newId = copySubtree(scene, nid, node.parent ?? null, offset, deps.poseComposition === void 0, ops);
|
|
8124
|
+
if (newId) newIds.push(newId);
|
|
7926
8125
|
}
|
|
7927
|
-
if (ops.length
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
selection.set([]);
|
|
7932
|
-
}
|
|
7933
|
-
},
|
|
7934
|
-
enabled: () => true
|
|
7935
|
-
};
|
|
7936
|
-
|
|
7937
|
-
// src/interactions/actions/defaults/requiresSelection.ts
|
|
7938
|
-
function requiresSelection(deps) {
|
|
7939
|
-
const selection = deps?.selection;
|
|
7940
|
-
if (!selection || selection.get().length === 0) {
|
|
7941
|
-
return ActionDisabledReason.SelectionRequired;
|
|
7942
|
-
}
|
|
7943
|
-
return true;
|
|
7944
|
-
}
|
|
7945
|
-
|
|
7946
|
-
// src/interactions/actions/defaults/duplicate.ts
|
|
7947
|
-
var duplicateAction = {
|
|
7948
|
-
id: "duplicate",
|
|
7949
|
-
label: "Duplicate",
|
|
7950
|
-
defaultBinding: { kind: "key", key: "d", mods: { mod: true } },
|
|
7951
|
-
eligible: { capability: ["edits-page", "creates-selection"] },
|
|
7952
|
-
invoker: {
|
|
7953
|
-
timing: "immediate",
|
|
7954
|
-
run: (_deps, params) => {
|
|
8126
|
+
if (ops.length === 0) return;
|
|
8127
|
+
if (applyOps) applyOps(ops, "Duplicate");
|
|
8128
|
+
else scene.applyBatch(ops, "Duplicate", defaultCommitAdapter(scene));
|
|
8129
|
+
selection.set(newIds);
|
|
7955
8130
|
}
|
|
7956
8131
|
},
|
|
7957
8132
|
enabled: requiresSelection
|
|
@@ -8984,7 +9159,7 @@ var LABEL = {
|
|
|
8984
9159
|
divide: "Divide",
|
|
8985
9160
|
crop: "Crop"
|
|
8986
9161
|
};
|
|
8987
|
-
function
|
|
9162
|
+
function isEmpty2(p) {
|
|
8988
9163
|
return p.commands.length === 0;
|
|
8989
9164
|
}
|
|
8990
9165
|
function applyBooleanOp(adapter, op) {
|
|
@@ -9022,7 +9197,7 @@ function applyBooleanOp(adapter, op) {
|
|
|
9022
9197
|
break;
|
|
9023
9198
|
}
|
|
9024
9199
|
}
|
|
9025
|
-
results = results.filter((p) => !
|
|
9200
|
+
results = results.filter((p) => !isEmpty2(p));
|
|
9026
9201
|
if (results.length === 0) return { kind: "noop", reason: "empty-result" };
|
|
9027
9202
|
const topmostId = entries[entries.length - 1].id;
|
|
9028
9203
|
const topAnchor = adapter.getZOrder?.(topmostId);
|
|
@@ -9573,14 +9748,24 @@ var DEFAULT_PALETTE = [
|
|
|
9573
9748
|
var GHOST_STROKE = DEFAULT_PALETTE[0];
|
|
9574
9749
|
|
|
9575
9750
|
// src/interactions/actions/defaults/setFill.ts
|
|
9751
|
+
function resolveFill(scratch, prev) {
|
|
9752
|
+
if (scratch.currentPaint) return scratch.currentPaint;
|
|
9753
|
+
const prevColor = typeof prev?.fill === "string" ? prev.fill : DEFAULT_FILL_COLOR;
|
|
9754
|
+
return mergeAlphaFromPrev(scratch.currentColor, prevColor);
|
|
9755
|
+
}
|
|
9576
9756
|
function refreshPreviews(scratch) {
|
|
9577
9757
|
scratch.previews.clear();
|
|
9578
9758
|
for (const id of scratch.ids) {
|
|
9579
9759
|
const prev = scratch.startData.get(id);
|
|
9580
|
-
|
|
9581
|
-
scratch.previews.set(id, { ...prev ?? {}, fill: next });
|
|
9760
|
+
scratch.previews.set(id, { ...prev ?? {}, fill: resolveFill(scratch, prev) });
|
|
9582
9761
|
}
|
|
9583
9762
|
}
|
|
9763
|
+
function readParams(params) {
|
|
9764
|
+
return {
|
|
9765
|
+
color: params?.color,
|
|
9766
|
+
paint: params?.paint
|
|
9767
|
+
};
|
|
9768
|
+
}
|
|
9584
9769
|
var setFillAction = {
|
|
9585
9770
|
id: "setFill",
|
|
9586
9771
|
label: "Set fill",
|
|
@@ -9595,9 +9780,10 @@ var setFillAction = {
|
|
|
9595
9780
|
if (!selection || !scene) return {};
|
|
9596
9781
|
const ids = selection.get();
|
|
9597
9782
|
if (ids.length === 0) return {};
|
|
9598
|
-
const
|
|
9599
|
-
const
|
|
9600
|
-
const
|
|
9783
|
+
const fromCtx = readParams(ctx.params);
|
|
9784
|
+
const fromOpts = readParams(opts?.params);
|
|
9785
|
+
const initialPaint = fromCtx.paint ?? fromOpts.paint;
|
|
9786
|
+
const initialColor = fromCtx.color ?? fromOpts.color ?? DEFAULT_STROKE_COLOR;
|
|
9601
9787
|
const startData = /* @__PURE__ */ new Map();
|
|
9602
9788
|
for (const id of ids) {
|
|
9603
9789
|
const node = scene.get(id);
|
|
@@ -9608,15 +9794,20 @@ var setFillAction = {
|
|
|
9608
9794
|
scene,
|
|
9609
9795
|
startData,
|
|
9610
9796
|
currentColor: initialColor,
|
|
9797
|
+
currentPaint: initialPaint,
|
|
9611
9798
|
previews: /* @__PURE__ */ new Map(),
|
|
9612
9799
|
applyOps
|
|
9613
9800
|
};
|
|
9614
9801
|
refreshPreviews(scratch);
|
|
9615
9802
|
return {
|
|
9616
9803
|
onMove(moveCtx) {
|
|
9617
|
-
const
|
|
9618
|
-
if (
|
|
9619
|
-
scratch.
|
|
9804
|
+
const { color, paint } = readParams(moveCtx.params);
|
|
9805
|
+
if (color === void 0 && paint === void 0) return;
|
|
9806
|
+
if (paint !== void 0) scratch.currentPaint = paint;
|
|
9807
|
+
if (color !== void 0) {
|
|
9808
|
+
scratch.currentColor = color;
|
|
9809
|
+
if (paint === void 0) scratch.currentPaint = void 0;
|
|
9810
|
+
}
|
|
9620
9811
|
refreshPreviews(scratch);
|
|
9621
9812
|
},
|
|
9622
9813
|
onEnd(_endCtx, reason) {
|
|
@@ -9626,8 +9817,7 @@ var setFillAction = {
|
|
|
9626
9817
|
}
|
|
9627
9818
|
const ops = [];
|
|
9628
9819
|
for (const id of scratch.ids) {
|
|
9629
|
-
const
|
|
9630
|
-
const merged = mergeAlphaFromPrev(scratch.currentColor, prev?.fill ?? DEFAULT_FILL_COLOR);
|
|
9820
|
+
const merged = resolveFill(scratch, scratch.startData.get(id));
|
|
9631
9821
|
const nodeNow = scratch.scene.get(id);
|
|
9632
9822
|
if (!nodeNow) continue;
|
|
9633
9823
|
const from = { ...nodeNow.data };
|
|
@@ -10021,7 +10211,8 @@ function resolveTextStyle(style) {
|
|
|
10021
10211
|
selectionColor: style.selectionColor ?? null,
|
|
10022
10212
|
letterSpacing: style.letterSpacing ?? DEFAULT_TEXT_STYLE.letterSpacing,
|
|
10023
10213
|
underline: style.underline ?? DEFAULT_TEXT_STYLE.underline,
|
|
10024
|
-
strikethrough: style.strikethrough ?? DEFAULT_TEXT_STYLE.strikethrough
|
|
10214
|
+
strikethrough: style.strikethrough ?? DEFAULT_TEXT_STYLE.strikethrough,
|
|
10215
|
+
...style.stroke !== void 0 ? { stroke: style.stroke } : {}
|
|
10025
10216
|
};
|
|
10026
10217
|
}
|
|
10027
10218
|
function fontString(s) {
|
|
@@ -10047,6 +10238,9 @@ function resolveRuns(runs, style) {
|
|
|
10047
10238
|
fontWeight: run.bold ? 700 : baseWeight,
|
|
10048
10239
|
fontStyle: run.italic ? "italic" : style.fontStyle,
|
|
10049
10240
|
fill: run.fill ?? style.fill,
|
|
10241
|
+
// Unlike the decorations below, a run's stroke *replaces* the node's
|
|
10242
|
+
// rather than adding to it — there is only one outline to paint.
|
|
10243
|
+
...(run.stroke ?? style.stroke) !== void 0 ? { stroke: run.stroke ?? style.stroke } : {},
|
|
10050
10244
|
letterSpacing: run.letterSpacing ?? style.letterSpacing,
|
|
10051
10245
|
// `||`, not `??`: run-level decorations are additive over the node style.
|
|
10052
10246
|
underline: run.underline || style.underline,
|
|
@@ -10435,7 +10629,9 @@ var KIT_STANDARD_DESCRIPTORS = [
|
|
|
10435
10629
|
setStrokeAction,
|
|
10436
10630
|
setFillOpacityAction,
|
|
10437
10631
|
setStrokeOpacityAction,
|
|
10438
|
-
ingestAction
|
|
10632
|
+
ingestAction,
|
|
10633
|
+
clipboardCopyAction,
|
|
10634
|
+
clipboardCutAction
|
|
10439
10635
|
];
|
|
10440
10636
|
function useStandardActions(opts) {
|
|
10441
10637
|
const optsRef = useRef(opts);
|
|
@@ -10515,6 +10711,54 @@ function targetRank(target) {
|
|
|
10515
10711
|
if (target.startsWith("affordance:")) return 2;
|
|
10516
10712
|
return 1;
|
|
10517
10713
|
}
|
|
10714
|
+
function targetConsultsAffordance(specTarget) {
|
|
10715
|
+
if (specTarget === void 0) return false;
|
|
10716
|
+
if (typeof specTarget === "object" && specTarget !== null && "kindOf" in specTarget) {
|
|
10717
|
+
const kindOf2 = specTarget.kindOf;
|
|
10718
|
+
return kindOf2?.readsAffordance !== false;
|
|
10719
|
+
}
|
|
10720
|
+
return typeof specTarget === "string" && specTarget.startsWith("affordance:");
|
|
10721
|
+
}
|
|
10722
|
+
function specTargetOf(spec) {
|
|
10723
|
+
return "target" in spec ? spec.target : void 0;
|
|
10724
|
+
}
|
|
10725
|
+
function claimOf(e) {
|
|
10726
|
+
return "affordance" in e ? e.affordance : void 0;
|
|
10727
|
+
}
|
|
10728
|
+
function claimGestureOf(e) {
|
|
10729
|
+
switch (e.kind) {
|
|
10730
|
+
case "pointerdown":
|
|
10731
|
+
case "click":
|
|
10732
|
+
return "pointer";
|
|
10733
|
+
case "doubleclick":
|
|
10734
|
+
return "doubleClick";
|
|
10735
|
+
case "contextmenu":
|
|
10736
|
+
return "contextMenu";
|
|
10737
|
+
case "longpress":
|
|
10738
|
+
return "longPress";
|
|
10739
|
+
case "wheel":
|
|
10740
|
+
return "wheel";
|
|
10741
|
+
default:
|
|
10742
|
+
return null;
|
|
10743
|
+
}
|
|
10744
|
+
}
|
|
10745
|
+
function isExclusiveClaim(e) {
|
|
10746
|
+
const claim = claimOf(e);
|
|
10747
|
+
if (claim?.strength !== "exclusive") return false;
|
|
10748
|
+
const gesture = claimGestureOf(e);
|
|
10749
|
+
if (gesture === null) return false;
|
|
10750
|
+
return claim.claimedKinds === void 0 || claim.claimedKinds.includes(gesture);
|
|
10751
|
+
}
|
|
10752
|
+
var warnedDeadClaims = /* @__PURE__ */ new Set();
|
|
10753
|
+
function reportDeadClaim(owner, warn) {
|
|
10754
|
+
if (process.env.NODE_ENV === "production") return;
|
|
10755
|
+
const key = String(owner);
|
|
10756
|
+
if (warnedDeadClaims.has(key)) return;
|
|
10757
|
+
warnedDeadClaims.add(key);
|
|
10758
|
+
warn(
|
|
10759
|
+
`[weasel] exclusive claim by "${key}" matched no binding: no \`affordance:\` or \`kindOf\` target resolved against it, so the press was dropped.`
|
|
10760
|
+
);
|
|
10761
|
+
}
|
|
10518
10762
|
function specificity(spec) {
|
|
10519
10763
|
const t = targetRank("target" in spec ? spec.target : void 0);
|
|
10520
10764
|
const mods = "mods" in spec ? spec.mods : void 0;
|
|
@@ -10531,24 +10775,53 @@ function compareSpecificity(a, b) {
|
|
|
10531
10775
|
}
|
|
10532
10776
|
return 0;
|
|
10533
10777
|
}
|
|
10534
|
-
function matchSorted(e, bindings, isMac, engagedChannels) {
|
|
10778
|
+
function matchSorted(e, bindings, isMac, engagedChannels, warn = (m) => console.warn(m)) {
|
|
10535
10779
|
const out = [];
|
|
10536
10780
|
const engaged = engagedChannels ?? EMPTY_ENGAGED;
|
|
10781
|
+
const exclusive = isExclusiveClaim(e);
|
|
10782
|
+
const pool = exclusive ? bindings.filter((sb) => targetConsultsAffordance(specTargetOf(sb.binding.spec))) : bindings;
|
|
10537
10783
|
for (const scope of SCOPE_PRIORITY) {
|
|
10538
10784
|
const scopeMatches = [];
|
|
10539
|
-
for (const sb of
|
|
10785
|
+
for (const sb of pool) {
|
|
10540
10786
|
if (sb.scope !== scope) continue;
|
|
10541
10787
|
const phaseCtx = { selfChannel: sb.ownerToolId, engagedChannels: engaged };
|
|
10542
10788
|
if (matchSpec(e, sb.binding.spec, isMac, phaseCtx)) {
|
|
10543
10789
|
scopeMatches.push({ binding: sb.binding, scope, ownerToolId: sb.ownerToolId });
|
|
10544
10790
|
}
|
|
10545
10791
|
}
|
|
10546
|
-
scopeMatches.sort((a, b) => compareSpecificity(a.binding.spec, b.binding.spec));
|
|
10547
|
-
out.push(...scopeMatches);
|
|
10792
|
+
scopeMatches.sort((a, b) => compareSpecificity(a.binding.spec, b.binding.spec));
|
|
10793
|
+
out.push(...scopeMatches);
|
|
10794
|
+
}
|
|
10795
|
+
if (exclusive && out.length === 0 && bindings.length > 0) {
|
|
10796
|
+
reportDeadClaim(claimOf(e)?.owner, warn);
|
|
10797
|
+
}
|
|
10798
|
+
return out;
|
|
10799
|
+
}
|
|
10800
|
+
var EMPTY_ENGAGED = /* @__PURE__ */ new Set();
|
|
10801
|
+
|
|
10802
|
+
// src/contributions/eligibility.ts
|
|
10803
|
+
function liveScope(id, eligibility, state) {
|
|
10804
|
+
const tags = eligibility.capabilities;
|
|
10805
|
+
if (tags && tags.length > 0 && state.allows && !state.allows(tags)) return null;
|
|
10806
|
+
if (state.engagedIds?.has(id)) return "hotkey";
|
|
10807
|
+
if (eligibility.offhand && state.heldTriggers.has(eligibility.offhand)) return "hotkey";
|
|
10808
|
+
if (eligibility.focus && state.focusedId === id) return "active";
|
|
10809
|
+
if (eligibility.always || eligibility.claimed) return "ambient";
|
|
10810
|
+
return null;
|
|
10811
|
+
}
|
|
10812
|
+
|
|
10813
|
+
// src/contributions/assemble.ts
|
|
10814
|
+
function scopeBindings(entries, state) {
|
|
10815
|
+
const out = [];
|
|
10816
|
+
for (const entry of entries) {
|
|
10817
|
+
const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
|
|
10818
|
+
if (scope === null) continue;
|
|
10819
|
+
for (const binding of entry.bindings ?? []) {
|
|
10820
|
+
out.push({ binding, scope, ownerToolId: entry.id });
|
|
10821
|
+
}
|
|
10548
10822
|
}
|
|
10549
10823
|
return out;
|
|
10550
10824
|
}
|
|
10551
|
-
var EMPTY_ENGAGED = /* @__PURE__ */ new Set();
|
|
10552
10825
|
|
|
10553
10826
|
// src/features/chrome-caps/rule.ts
|
|
10554
10827
|
var ALWAYS = { all: [] };
|
|
@@ -10607,6 +10880,14 @@ function evaluateSelector(s, ctx) {
|
|
|
10607
10880
|
const resizable2 = ctx.selectionResizable ?? true;
|
|
10608
10881
|
if (resizable2 !== s.resizable) return false;
|
|
10609
10882
|
}
|
|
10883
|
+
if (s.coarsePointer !== void 0) {
|
|
10884
|
+
const device = ctx.device ?? DEFAULT_DEVICE_PROFILE;
|
|
10885
|
+
if (device.coarsePointer !== s.coarsePointer) return false;
|
|
10886
|
+
}
|
|
10887
|
+
if (s.canHover !== void 0) {
|
|
10888
|
+
const device = ctx.device ?? DEFAULT_DEVICE_PROFILE;
|
|
10889
|
+
if (device.canHover !== s.canHover) return false;
|
|
10890
|
+
}
|
|
10610
10891
|
return true;
|
|
10611
10892
|
}
|
|
10612
10893
|
var MAX_RULE_DEPTH = 10;
|
|
@@ -10702,6 +10983,8 @@ var capabilityIs = (cap) => cond({ capability: cap });
|
|
|
10702
10983
|
var capabilityIn = (caps) => cond({ capability: { in: caps } });
|
|
10703
10984
|
var capabilityAll = (caps) => cond({ capability: [...caps] });
|
|
10704
10985
|
var capabilityNot = (cap) => cond({ capability: { not: cap } });
|
|
10986
|
+
var coarsePointer = cond({ coarsePointer: true });
|
|
10987
|
+
var canHover = cond({ canHover: true });
|
|
10705
10988
|
var DEFAULT_ALLOWED_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
10706
10989
|
...NORMAL.allows,
|
|
10707
10990
|
...IMPLICIT_TAGS
|
|
@@ -10718,7 +11001,8 @@ function buildRuleCtx(args) {
|
|
|
10718
11001
|
mode: args.mode,
|
|
10719
11002
|
allowedCapabilities: args.allowedCapabilities,
|
|
10720
11003
|
selectionResizable: args.selectionResizable,
|
|
10721
|
-
editingAnchors: args.editingAnchors
|
|
11004
|
+
editingAnchors: args.editingAnchors,
|
|
11005
|
+
device: args.device
|
|
10722
11006
|
};
|
|
10723
11007
|
}
|
|
10724
11008
|
|
|
@@ -10843,7 +11127,7 @@ function useHoverTracking(args) {
|
|
|
10843
11127
|
function pointerGestureId(pointerId) {
|
|
10844
11128
|
return `pointer-${pointerId ?? "mouse"}`;
|
|
10845
11129
|
}
|
|
10846
|
-
var
|
|
11130
|
+
var DEV = (() => {
|
|
10847
11131
|
try {
|
|
10848
11132
|
return Boolean(import.meta.env?.DEV);
|
|
10849
11133
|
} catch {
|
|
@@ -10851,9 +11135,9 @@ var DEV2 = (() => {
|
|
|
10851
11135
|
}
|
|
10852
11136
|
})();
|
|
10853
11137
|
var TRACE_LIMIT = 200;
|
|
10854
|
-
var traceLog =
|
|
11138
|
+
var traceLog = DEV && typeof window !== "undefined" ? window.__weaselDispatchLog__ ??= [] : [];
|
|
10855
11139
|
function recordTrace(entry) {
|
|
10856
|
-
if (!
|
|
11140
|
+
if (!DEV) return;
|
|
10857
11141
|
traceLog.push(entry);
|
|
10858
11142
|
if (traceLog.length > TRACE_LIMIT) traceLog.shift();
|
|
10859
11143
|
}
|
|
@@ -10884,6 +11168,7 @@ function filterEligible(matches, actionLookup, ruleCtx) {
|
|
|
10884
11168
|
return isEligible(action, ruleCtx);
|
|
10885
11169
|
});
|
|
10886
11170
|
}
|
|
11171
|
+
var EMPTY_TRIGGERS = /* @__PURE__ */ new Set();
|
|
10887
11172
|
var EMPTY_ENGAGED2 = /* @__PURE__ */ new Set();
|
|
10888
11173
|
function modifiersOf(e) {
|
|
10889
11174
|
return { alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey, shift: e.shiftKey };
|
|
@@ -10991,34 +11276,23 @@ function createDispatcher(opts) {
|
|
|
10991
11276
|
return `ongoing-${event.kind}`;
|
|
10992
11277
|
}
|
|
10993
11278
|
function assembleScopedBindings(ctx) {
|
|
10994
|
-
const
|
|
10995
|
-
for (const
|
|
10996
|
-
const tool = ctx.toolsById.get(
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
for (const binding of tool?.bindings ?? []) {
|
|
11008
|
-
result.push({ binding, scope: "ambient", ownerToolId: toolId });
|
|
11009
|
-
}
|
|
11010
|
-
}
|
|
11279
|
+
const ordered = [];
|
|
11280
|
+
for (const id of ctx.hotkeyStack) {
|
|
11281
|
+
const tool = ctx.toolsById.get(id);
|
|
11282
|
+
if (tool && !ordered.includes(tool)) ordered.push(tool);
|
|
11283
|
+
}
|
|
11284
|
+
for (const tool of ctx.toolsById.values()) {
|
|
11285
|
+
if (!ordered.includes(tool)) ordered.push(tool);
|
|
11286
|
+
}
|
|
11287
|
+
const result = scopeBindings(ordered, {
|
|
11288
|
+
focusedId: ctx.activeToolId,
|
|
11289
|
+
heldTriggers: EMPTY_TRIGGERS,
|
|
11290
|
+
engagedIds: new Set(ctx.hotkeyStack)
|
|
11291
|
+
});
|
|
11011
11292
|
for (const action of ctx.actions.list()) {
|
|
11012
|
-
const
|
|
11013
|
-
|
|
11014
|
-
|
|
11015
|
-
for (const entry of raw) {
|
|
11016
|
-
const isBoundObj = !("kind" in entry);
|
|
11017
|
-
const spec = isBoundObj ? entry.spec : entry;
|
|
11018
|
-
const opts2 = isBoundObj ? entry.opts : void 0;
|
|
11019
|
-
const defaultBinding = { spec, actionId: action.id, ...opts2 !== void 0 ? { opts: opts2 } : {} };
|
|
11020
|
-
const targetScope = action.scope === "hotkey" ? "hotkey" : "ambient";
|
|
11021
|
-
result.push({ binding: defaultBinding, scope: targetScope, ownerToolId: null });
|
|
11293
|
+
const targetScope = action.scope === "hotkey" ? "hotkey" : "ambient";
|
|
11294
|
+
for (const binding of actionBindings(action)) {
|
|
11295
|
+
result.push({ binding, scope: targetScope, ownerToolId: null });
|
|
11022
11296
|
}
|
|
11023
11297
|
}
|
|
11024
11298
|
return result;
|
|
@@ -11173,15 +11447,26 @@ function createDispatcher(opts) {
|
|
|
11173
11447
|
deltaY: event.deltaY,
|
|
11174
11448
|
clientX: event.clientX,
|
|
11175
11449
|
clientY: event.clientY,
|
|
11450
|
+
affordance: event.affordance,
|
|
11176
11451
|
...resolved
|
|
11177
11452
|
};
|
|
11178
11453
|
} else if (event.kind === "click" || event.kind === "doubleclick") {
|
|
11179
11454
|
params = {
|
|
11180
11455
|
worldX: event.worldX,
|
|
11181
11456
|
worldY: event.worldY,
|
|
11457
|
+
affordance: event.affordance,
|
|
11182
11458
|
// Press point as well as release point — an action that places
|
|
11183
11459
|
// geometry at the click wants the former. See `ClickEvent`.
|
|
11184
|
-
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY
|
|
11460
|
+
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY } : {},
|
|
11461
|
+
mods: modifiersOf(event),
|
|
11462
|
+
...resolved
|
|
11463
|
+
};
|
|
11464
|
+
} else if (event.kind === "contextmenu" || event.kind === "longpress") {
|
|
11465
|
+
params = {
|
|
11466
|
+
worldX: event.kind === "contextmenu" ? event.worldX : event.x,
|
|
11467
|
+
worldY: event.kind === "contextmenu" ? event.worldY : event.y,
|
|
11468
|
+
affordance: event.affordance,
|
|
11469
|
+
bodyTarget: event.bodyTarget,
|
|
11185
11470
|
mods: modifiersOf(event),
|
|
11186
11471
|
...resolved
|
|
11187
11472
|
};
|
|
@@ -11553,7 +11838,7 @@ function computeMultiTouchGeometry(positions) {
|
|
|
11553
11838
|
return { centroid, spread };
|
|
11554
11839
|
}
|
|
11555
11840
|
function useGestureDispatcher(opts) {
|
|
11556
|
-
const { canvasRef, actions, toolsById,
|
|
11841
|
+
const { canvasRef, actions, toolsById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld: clientToWorld2, requestRedraw, getRuleCtx, onDoubleClick } = opts;
|
|
11557
11842
|
const onDoubleClickRef = useRef(onDoubleClick);
|
|
11558
11843
|
onDoubleClickRef.current = onDoubleClick;
|
|
11559
11844
|
const activeTool = useActiveToolContext();
|
|
@@ -11568,7 +11853,6 @@ function useGestureDispatcher(opts) {
|
|
|
11568
11853
|
activeToolId: activeTool.active,
|
|
11569
11854
|
hotkeyStack: activeTool.hotkeyStack,
|
|
11570
11855
|
toolsById,
|
|
11571
|
-
ambientToolIds,
|
|
11572
11856
|
isMac: IS_MAC,
|
|
11573
11857
|
getRuleCtx
|
|
11574
11858
|
});
|
|
@@ -11578,7 +11862,6 @@ function useGestureDispatcher(opts) {
|
|
|
11578
11862
|
activeToolId: activeTool.active,
|
|
11579
11863
|
hotkeyStack: activeTool.hotkeyStack,
|
|
11580
11864
|
toolsById,
|
|
11581
|
-
ambientToolIds,
|
|
11582
11865
|
isMac: IS_MAC,
|
|
11583
11866
|
getRuleCtx
|
|
11584
11867
|
};
|
|
@@ -11634,6 +11917,49 @@ function useGestureDispatcher(opts) {
|
|
|
11634
11917
|
const DRAG_THRESHOLD_PX = 4;
|
|
11635
11918
|
const bufferedDown = /* @__PURE__ */ new Map();
|
|
11636
11919
|
const lastPointerDown = /* @__PURE__ */ new Map();
|
|
11920
|
+
const LONG_PRESS_MS = 500;
|
|
11921
|
+
const longPressTimers = /* @__PURE__ */ new Map();
|
|
11922
|
+
const cancelLongPress = (pointerId) => {
|
|
11923
|
+
const t = longPressTimers.get(pointerId);
|
|
11924
|
+
if (t !== void 0) {
|
|
11925
|
+
clearTimeout(t);
|
|
11926
|
+
longPressTimers.delete(pointerId);
|
|
11927
|
+
}
|
|
11928
|
+
};
|
|
11929
|
+
const cancelAllLongPress = () => {
|
|
11930
|
+
for (const t of longPressTimers.values()) clearTimeout(t);
|
|
11931
|
+
longPressTimers.clear();
|
|
11932
|
+
};
|
|
11933
|
+
const fireLongPress = (pointerId) => {
|
|
11934
|
+
const down = lastPointerDown.get(pointerId);
|
|
11935
|
+
if (!down) return;
|
|
11936
|
+
const shared = {
|
|
11937
|
+
target: down.target,
|
|
11938
|
+
altKey: down.altKey,
|
|
11939
|
+
ctrlKey: down.ctrlKey,
|
|
11940
|
+
metaKey: down.metaKey,
|
|
11941
|
+
shiftKey: down.shiftKey,
|
|
11942
|
+
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
11943
|
+
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
11944
|
+
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
11945
|
+
};
|
|
11946
|
+
const result = dispatch2({
|
|
11947
|
+
kind: "longpress",
|
|
11948
|
+
x: down.worldX,
|
|
11949
|
+
y: down.worldY,
|
|
11950
|
+
clientX: down.clientX,
|
|
11951
|
+
clientY: down.clientY,
|
|
11952
|
+
...shared
|
|
11953
|
+
});
|
|
11954
|
+
if (result === "unhandled") {
|
|
11955
|
+
dispatch2({
|
|
11956
|
+
kind: "contextmenu",
|
|
11957
|
+
worldX: down.worldX,
|
|
11958
|
+
worldY: down.worldY,
|
|
11959
|
+
...shared
|
|
11960
|
+
});
|
|
11961
|
+
}
|
|
11962
|
+
};
|
|
11637
11963
|
const DOUBLE_CLICK_MAX_MS = 600;
|
|
11638
11964
|
const DOUBLE_CLICK_MAX_PX = 8;
|
|
11639
11965
|
const onKeyDown = (e) => {
|
|
@@ -11686,6 +12012,9 @@ function useGestureDispatcher(opts) {
|
|
|
11686
12012
|
const onWheel = (e) => {
|
|
11687
12013
|
const rect = canvas?.getBoundingClientRect();
|
|
11688
12014
|
const [localX, localY] = rect ? clientToCanvasRect(rect, e.clientX, e.clientY) : [e.clientX, e.clientY];
|
|
12015
|
+
const screenPoint = { x: e.clientX, y: e.clientY };
|
|
12016
|
+
const affordance = affordanceAtRef.current?.(screenPoint) ?? void 0;
|
|
12017
|
+
const body = classifyTargetRef.current?.(screenPoint);
|
|
11689
12018
|
const ev = {
|
|
11690
12019
|
kind: "wheel",
|
|
11691
12020
|
altKey: e.altKey,
|
|
@@ -11695,7 +12024,10 @@ function useGestureDispatcher(opts) {
|
|
|
11695
12024
|
deltaX: e.deltaX,
|
|
11696
12025
|
deltaY: e.deltaY,
|
|
11697
12026
|
clientX: localX,
|
|
11698
|
-
clientY: localY
|
|
12027
|
+
clientY: localY,
|
|
12028
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
12029
|
+
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
12030
|
+
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
11699
12031
|
};
|
|
11700
12032
|
const result = dispatch2(ev);
|
|
11701
12033
|
if (result === "handled") {
|
|
@@ -11737,6 +12069,7 @@ function useGestureDispatcher(opts) {
|
|
|
11737
12069
|
bufferedDown.set(e.pointerId, { ev, clientX: e.clientX, clientY: e.clientY });
|
|
11738
12070
|
dispatch2({ ...ev, stage: "press" });
|
|
11739
12071
|
lastPointerDown.set(e.pointerId, {
|
|
12072
|
+
target: e.target,
|
|
11740
12073
|
clientX: e.clientX,
|
|
11741
12074
|
clientY: e.clientY,
|
|
11742
12075
|
worldX: w.x,
|
|
@@ -11749,7 +12082,19 @@ function useGestureDispatcher(opts) {
|
|
|
11749
12082
|
metaKey: e.metaKey,
|
|
11750
12083
|
shiftKey: e.shiftKey
|
|
11751
12084
|
});
|
|
12085
|
+
if ((e.pointerType === "touch" || e.pointerType === "pen") && activePointers.size === 1) {
|
|
12086
|
+
cancelLongPress(e.pointerId);
|
|
12087
|
+
longPressTimers.set(
|
|
12088
|
+
e.pointerId,
|
|
12089
|
+
setTimeout(() => {
|
|
12090
|
+
longPressTimers.delete(e.pointerId);
|
|
12091
|
+
if (disposed) return;
|
|
12092
|
+
fireLongPress(e.pointerId);
|
|
12093
|
+
}, LONG_PRESS_MS)
|
|
12094
|
+
);
|
|
12095
|
+
}
|
|
11752
12096
|
if (activePointers.size >= 2) {
|
|
12097
|
+
cancelAllLongPress();
|
|
11753
12098
|
for (const id of activePointers) bufferedDown.delete(id);
|
|
11754
12099
|
const { centroid, spread } = computeMultiTouchGeometry(pointerPositions);
|
|
11755
12100
|
if (!multiTouchStart) {
|
|
@@ -11780,6 +12125,7 @@ function useGestureDispatcher(opts) {
|
|
|
11780
12125
|
const dy = e.clientY - buffered.clientY;
|
|
11781
12126
|
if (Math.hypot(dx, dy) >= DRAG_THRESHOLD_PX) {
|
|
11782
12127
|
bufferedDown.delete(e.pointerId);
|
|
12128
|
+
cancelLongPress(e.pointerId);
|
|
11783
12129
|
dispatch2(buffered.ev);
|
|
11784
12130
|
}
|
|
11785
12131
|
}
|
|
@@ -11893,6 +12239,7 @@ function useGestureDispatcher(opts) {
|
|
|
11893
12239
|
clearHoverCursor();
|
|
11894
12240
|
};
|
|
11895
12241
|
const onPointerUp = (e) => {
|
|
12242
|
+
cancelLongPress(e.pointerId);
|
|
11896
12243
|
const prevSize = activePointers.size;
|
|
11897
12244
|
activePointers.delete(e.pointerId);
|
|
11898
12245
|
pointerPositions.delete(e.pointerId);
|
|
@@ -11974,6 +12321,7 @@ function useGestureDispatcher(opts) {
|
|
|
11974
12321
|
shiftKey: e.shiftKey,
|
|
11975
12322
|
worldX: wClick.x,
|
|
11976
12323
|
worldY: wClick.y,
|
|
12324
|
+
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
11977
12325
|
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
11978
12326
|
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
11979
12327
|
};
|
|
@@ -11997,6 +12345,7 @@ function useGestureDispatcher(opts) {
|
|
|
11997
12345
|
}, 0);
|
|
11998
12346
|
};
|
|
11999
12347
|
const onPointerCancel = (e) => {
|
|
12348
|
+
cancelLongPress(e.pointerId);
|
|
12000
12349
|
activePointers.delete(e.pointerId);
|
|
12001
12350
|
pointerPositions.delete(e.pointerId);
|
|
12002
12351
|
lastPointerDown.delete(e.pointerId);
|
|
@@ -12014,8 +12363,10 @@ function useGestureDispatcher(opts) {
|
|
|
12014
12363
|
};
|
|
12015
12364
|
const onContextMenu = (e) => {
|
|
12016
12365
|
e.preventDefault();
|
|
12017
|
-
const
|
|
12018
|
-
const
|
|
12366
|
+
const screenPoint = { x: e.clientX, y: e.clientY };
|
|
12367
|
+
const affordance = affordanceAtRef.current?.(screenPoint) ?? void 0;
|
|
12368
|
+
const menuBody = classifyTargetRef.current?.(screenPoint);
|
|
12369
|
+
const w = toWorld(e.clientX, e.clientY);
|
|
12019
12370
|
const ev = {
|
|
12020
12371
|
kind: "contextmenu",
|
|
12021
12372
|
target: e.target,
|
|
@@ -12023,6 +12374,9 @@ function useGestureDispatcher(opts) {
|
|
|
12023
12374
|
ctrlKey: e.ctrlKey,
|
|
12024
12375
|
metaKey: e.metaKey,
|
|
12025
12376
|
shiftKey: e.shiftKey,
|
|
12377
|
+
worldX: w.x,
|
|
12378
|
+
worldY: w.y,
|
|
12379
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
12026
12380
|
...menuBody?.body !== void 0 ? { bodyTarget: menuBody.body } : {},
|
|
12027
12381
|
...menuBody?.kind !== void 0 ? { bodyKind: menuBody.kind } : {}
|
|
12028
12382
|
};
|
|
@@ -12119,6 +12473,7 @@ function useGestureDispatcher(opts) {
|
|
|
12119
12473
|
window.removeEventListener("paste", onPaste);
|
|
12120
12474
|
canvas?.classList.remove(DROPOVER_CLASS);
|
|
12121
12475
|
clearHoverCursor();
|
|
12476
|
+
cancelAllLongPress();
|
|
12122
12477
|
disposed = true;
|
|
12123
12478
|
dispatcher.cancelAll("cancel");
|
|
12124
12479
|
};
|
|
@@ -12421,47 +12776,209 @@ function useViewAnimation(setView) {
|
|
|
12421
12776
|
);
|
|
12422
12777
|
return { animateTo, animateToBounds, cancel };
|
|
12423
12778
|
}
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12779
|
+
|
|
12780
|
+
// src/interactions/actions/defaults/toolOffhand.ts
|
|
12781
|
+
var TOOL_OFFHAND_ID = "tool.offhand";
|
|
12782
|
+
function offhandKeyFor(trigger) {
|
|
12783
|
+
switch (trigger) {
|
|
12784
|
+
case "space":
|
|
12785
|
+
return " ";
|
|
12786
|
+
case "alt":
|
|
12787
|
+
return "Alt";
|
|
12788
|
+
case "ctrl":
|
|
12789
|
+
return "Control";
|
|
12790
|
+
case "meta":
|
|
12791
|
+
return "Meta";
|
|
12792
|
+
case "shift":
|
|
12793
|
+
return "Shift";
|
|
12427
12794
|
}
|
|
12795
|
+
}
|
|
12796
|
+
function buildToolOffhandBindings(specs) {
|
|
12797
|
+
return specs.map(({ toolId, key }) => ({
|
|
12798
|
+
spec: { kind: "key-held", key },
|
|
12799
|
+
opts: { params: { toolId } }
|
|
12800
|
+
}));
|
|
12801
|
+
}
|
|
12802
|
+
function makeToolOffhandAction(bindings) {
|
|
12803
|
+
return {
|
|
12804
|
+
id: TOOL_OFFHAND_ID,
|
|
12805
|
+
label: "Engage tool (offhand)",
|
|
12806
|
+
defaultBinding: bindings,
|
|
12807
|
+
scope: "hotkey",
|
|
12808
|
+
invoker: {
|
|
12809
|
+
timing: "ongoing",
|
|
12810
|
+
start: (ctx, opts) => {
|
|
12811
|
+
const activeTool = ctx.deps.activeTool;
|
|
12812
|
+
const toolId = resolveParams(opts?.params)?.toolId;
|
|
12813
|
+
if (!activeTool || !toolId) return {};
|
|
12814
|
+
activeTool.pushHotkey(toolId);
|
|
12815
|
+
return {
|
|
12816
|
+
onEnd: () => {
|
|
12817
|
+
activeTool.popHotkey();
|
|
12818
|
+
}
|
|
12819
|
+
};
|
|
12820
|
+
}
|
|
12821
|
+
},
|
|
12822
|
+
requires: ["activeTool"]
|
|
12823
|
+
};
|
|
12824
|
+
}
|
|
12825
|
+
|
|
12826
|
+
// src/contributions/useContributions.ts
|
|
12827
|
+
var NO_TRIGGERS = /* @__PURE__ */ new Set();
|
|
12828
|
+
function useContributions(opts) {
|
|
12428
12829
|
const ctx = useActiveToolContext();
|
|
12830
|
+
const actionsRegistry = useActionsRegistry();
|
|
12429
12831
|
const hasInitializedRef = useRef(false);
|
|
12430
12832
|
const isFirstRender = !hasInitializedRef.current;
|
|
12431
12833
|
const needsFirstMountSyncRef = useRef(false);
|
|
12432
12834
|
if (isFirstRender) {
|
|
12433
12835
|
hasInitializedRef.current = true;
|
|
12434
|
-
needsFirstMountSyncRef.current = ctx.active === "select" && opts.
|
|
12836
|
+
needsFirstMountSyncRef.current = ctx.active === "select" && opts.focused !== "select";
|
|
12435
12837
|
}
|
|
12436
12838
|
useEffect(() => {
|
|
12437
12839
|
if (!needsFirstMountSyncRef.current) return;
|
|
12438
12840
|
needsFirstMountSyncRef.current = false;
|
|
12439
|
-
ctx.setActive(opts.
|
|
12841
|
+
ctx.setActive(opts.focused);
|
|
12842
|
+
}, []);
|
|
12843
|
+
const focused2 = isFirstRender && ctx.active === "select" && opts.focused !== "select" ? opts.focused : ctx.active;
|
|
12844
|
+
const entriesRef = useRef(opts.entries);
|
|
12845
|
+
entriesRef.current = opts.entries;
|
|
12846
|
+
const focusedRef = useRef(focused2);
|
|
12847
|
+
focusedRef.current = focused2;
|
|
12848
|
+
const engagedRef = useRef(ctx.hotkeyStack);
|
|
12849
|
+
engagedRef.current = ctx.hotkeyStack;
|
|
12850
|
+
const setFocused = useCallback((id) => {
|
|
12851
|
+
ctx.setActive(id);
|
|
12852
|
+
}, [ctx]);
|
|
12853
|
+
const snapshot = useCallback(() => {
|
|
12854
|
+
const engaged = engagedRef.current;
|
|
12855
|
+
const engagedIds = new Set(engaged);
|
|
12856
|
+
const ordered = [];
|
|
12857
|
+
for (const id of engaged) {
|
|
12858
|
+
const entry = entriesRef.current.find((e) => e.id === id);
|
|
12859
|
+
if (entry && !ordered.includes(entry)) ordered.push(entry);
|
|
12860
|
+
}
|
|
12861
|
+
for (const entry of entriesRef.current) {
|
|
12862
|
+
if (!ordered.includes(entry)) ordered.push(entry);
|
|
12863
|
+
}
|
|
12864
|
+
return {
|
|
12865
|
+
ordered,
|
|
12866
|
+
state: { focusedId: focusedRef.current, heldTriggers: NO_TRIGGERS, engagedIds }
|
|
12867
|
+
};
|
|
12440
12868
|
}, []);
|
|
12441
|
-
const
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12869
|
+
const scopedBindings = useCallback(() => {
|
|
12870
|
+
const { ordered, state } = snapshot();
|
|
12871
|
+
return scopeBindings(ordered, state);
|
|
12872
|
+
}, [snapshot]);
|
|
12873
|
+
const overlays = useCallback(() => {
|
|
12874
|
+
const { ordered, state } = snapshot();
|
|
12875
|
+
const active = [];
|
|
12876
|
+
const hotkey = [];
|
|
12877
|
+
const ambient = [];
|
|
12878
|
+
for (const entry of ordered) {
|
|
12879
|
+
if (!entry.overlay) continue;
|
|
12880
|
+
const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
|
|
12881
|
+
if (scope === "active") active.push(entry.overlay);
|
|
12882
|
+
else if (scope === "hotkey") hotkey.push(entry.overlay);
|
|
12883
|
+
else if (scope === "ambient") ambient.push(entry.overlay);
|
|
12884
|
+
}
|
|
12885
|
+
return [...active, ...hotkey, ...ambient];
|
|
12886
|
+
}, [snapshot]);
|
|
12887
|
+
const offhandSig = opts.entries.map((e) => e.eligibility?.offhand ? `${e.id}:${e.eligibility.offhand}` : "").filter(Boolean).join(",");
|
|
12888
|
+
useEffect(() => {
|
|
12889
|
+
if (!actionsRegistry || offhandSig === "") return;
|
|
12890
|
+
const specs = offhandSig.split(",").map((entry) => {
|
|
12891
|
+
const [toolId, trigger] = entry.split(":");
|
|
12892
|
+
return { toolId, key: offhandKeyFor(trigger) };
|
|
12893
|
+
});
|
|
12894
|
+
return actionsRegistry.register(makeToolOffhandAction(buildToolOffhandBindings(specs)));
|
|
12895
|
+
}, [actionsRegistry, offhandSig]);
|
|
12896
|
+
const lastConflictSigRef = useRef(null);
|
|
12897
|
+
const entrySig = opts.entries.map((e) => e.id).join(",");
|
|
12898
|
+
useEffect(() => {
|
|
12899
|
+
if (process.env.NODE_ENV === "production") return;
|
|
12900
|
+
if (lastConflictSigRef.current === entrySig) return;
|
|
12901
|
+
lastConflictSigRef.current = entrySig;
|
|
12902
|
+
const registry2 = [];
|
|
12903
|
+
const ambient = [];
|
|
12904
|
+
for (const entry of entriesRef.current) {
|
|
12905
|
+
(entry.eligibility?.focus ? registry2 : ambient).push(entry);
|
|
12906
|
+
}
|
|
12907
|
+
reportRouteConflicts({
|
|
12908
|
+
registry: registry2,
|
|
12909
|
+
ambient,
|
|
12910
|
+
actions: actionsRegistry?.list() ?? []
|
|
12911
|
+
});
|
|
12912
|
+
}, [entrySig, actionsRegistry]);
|
|
12913
|
+
return useMemo(
|
|
12914
|
+
() => ({
|
|
12915
|
+
entries: entriesRef.current,
|
|
12916
|
+
focused: focused2,
|
|
12917
|
+
setFocused,
|
|
12918
|
+
scopedBindings,
|
|
12919
|
+
overlays
|
|
12920
|
+
}),
|
|
12921
|
+
[focused2, setFocused, scopedBindings, overlays]
|
|
12922
|
+
);
|
|
12923
|
+
}
|
|
12924
|
+
|
|
12925
|
+
// src/tools/useTools.ts
|
|
12926
|
+
function declareSlot(tool, slot) {
|
|
12927
|
+
const hotkey = tool.def?.hotkey;
|
|
12928
|
+
const eligibility = {
|
|
12929
|
+
...tool.eligibility,
|
|
12930
|
+
[slot]: true,
|
|
12931
|
+
...hotkey ? { offhand: hotkey } : {}
|
|
12932
|
+
};
|
|
12933
|
+
return sameEligibility(tool.eligibility, eligibility) ? tool : { ...tool, eligibility };
|
|
12934
|
+
}
|
|
12935
|
+
function sameEligibility(a, b) {
|
|
12936
|
+
if (!a) return false;
|
|
12937
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
12938
|
+
for (const key of keys) if (a[key] !== b[key]) return false;
|
|
12939
|
+
return true;
|
|
12940
|
+
}
|
|
12941
|
+
function useTools(opts) {
|
|
12942
|
+
if (!(opts.active in opts.registry)) {
|
|
12943
|
+
throw new Error(`useTools: active "${opts.active}" not in registry`);
|
|
12944
|
+
}
|
|
12945
|
+
const ctx = useActiveToolContext();
|
|
12946
|
+
const slotted = useMemo(() => {
|
|
12947
|
+
const registry2 = {};
|
|
12948
|
+
for (const [id, tool] of Object.entries(opts.registry)) {
|
|
12949
|
+
registry2[id] = declareSlot(tool, "focus");
|
|
12950
|
+
}
|
|
12951
|
+
const ambient = (opts.ambient ?? []).map((t) => declareSlot(t, "always"));
|
|
12952
|
+
const byId = /* @__PURE__ */ new Map();
|
|
12953
|
+
for (const tool of [...Object.values(registry2), ...ambient]) {
|
|
12954
|
+
const prior = byId.get(tool.id);
|
|
12955
|
+
if (prior) byId.set(tool.id, { ...tool, eligibility: { ...prior.eligibility, ...tool.eligibility } });
|
|
12956
|
+
else byId.set(tool.id, tool);
|
|
12957
|
+
}
|
|
12958
|
+
return { registry: registry2, ambient, entries: [...byId.values()] };
|
|
12959
|
+
}, [opts.registry, opts.ambient]);
|
|
12960
|
+
const contributions = useContributions({ entries: slotted.entries, focused: opts.active });
|
|
12961
|
+
const hotkeyEngaged = ctx.hotkeyStack.at(-1) ?? null;
|
|
12962
|
+
const slottedRef = useRef(slotted);
|
|
12963
|
+
slottedRef.current = slotted;
|
|
12964
|
+
const activeRef = useRef(contributions.focused);
|
|
12965
|
+
activeRef.current = contributions.focused;
|
|
12450
12966
|
const hotkeyRef = useRef(hotkeyEngaged);
|
|
12451
12967
|
hotkeyRef.current = hotkeyEngaged;
|
|
12968
|
+
const setFocused = contributions.setFocused;
|
|
12452
12969
|
const setActive = useCallback(
|
|
12453
12970
|
(id) => {
|
|
12454
|
-
if (!(id in
|
|
12971
|
+
if (!(id in slottedRef.current.registry)) {
|
|
12455
12972
|
throw new Error(`setActive: "${id}" not in registry`);
|
|
12456
12973
|
}
|
|
12457
12974
|
dlog("tools", "active:", activeRef.current, "\u2192", id);
|
|
12458
|
-
|
|
12975
|
+
setFocused(id);
|
|
12459
12976
|
},
|
|
12460
|
-
[
|
|
12977
|
+
[setFocused]
|
|
12461
12978
|
);
|
|
12462
12979
|
const engageHotkey = useCallback(
|
|
12463
12980
|
(id) => {
|
|
12464
|
-
if (!(id in
|
|
12981
|
+
if (!(id in slottedRef.current.registry)) {
|
|
12465
12982
|
throw new Error(`engageHotkey: "${id}" not in registry`);
|
|
12466
12983
|
}
|
|
12467
12984
|
dlog("tools", "hotkey engaged:", id);
|
|
@@ -12473,32 +12990,12 @@ function useTools(opts) {
|
|
|
12473
12990
|
if (hotkeyRef.current) dlog("tools", "hotkey disengaged:", hotkeyRef.current);
|
|
12474
12991
|
ctx.popHotkey();
|
|
12475
12992
|
}, [ctx]);
|
|
12476
|
-
const lastConflictSigRef = useRef(null);
|
|
12477
|
-
const toolSetSig = Object.keys(opts.registry).join(",") + "|" + (opts.ambient ?? []).map((t) => t.id).join(",");
|
|
12478
|
-
useEffect(() => {
|
|
12479
|
-
if (process.env.NODE_ENV === "production") return;
|
|
12480
|
-
if (lastConflictSigRef.current === toolSetSig) return;
|
|
12481
|
-
lastConflictSigRef.current = toolSetSig;
|
|
12482
|
-
reportRouteConflicts({
|
|
12483
|
-
registry: registryRef.current,
|
|
12484
|
-
ambient: ambientRef.current
|
|
12485
|
-
});
|
|
12486
|
-
}, [toolSetSig]);
|
|
12487
12993
|
const has = useCallback(
|
|
12488
|
-
(id) => id in
|
|
12994
|
+
(id) => id in slottedRef.current.registry || slottedRef.current.ambient.some((t) => t.id === id),
|
|
12489
12995
|
[]
|
|
12490
12996
|
);
|
|
12491
|
-
const
|
|
12492
|
-
|
|
12493
|
-
const activeTool = registryRef.current[activeRef.current];
|
|
12494
|
-
if (activeTool?.overlay) out.push(activeTool.overlay);
|
|
12495
|
-
const mod = hotkeyRef.current ? registryRef.current[hotkeyRef.current] : null;
|
|
12496
|
-
if (mod?.overlay) out.push(mod.overlay);
|
|
12497
|
-
for (const t of ambientRef.current) {
|
|
12498
|
-
if (t.overlay) out.push(t.overlay);
|
|
12499
|
-
}
|
|
12500
|
-
return out;
|
|
12501
|
-
}, []);
|
|
12997
|
+
const active = contributions.focused;
|
|
12998
|
+
const getActiveOverlays = contributions.overlays;
|
|
12502
12999
|
return useMemo(
|
|
12503
13000
|
() => ({
|
|
12504
13001
|
active,
|
|
@@ -12506,8 +13003,8 @@ function useTools(opts) {
|
|
|
12506
13003
|
hotkeyEngaged,
|
|
12507
13004
|
engageHotkey,
|
|
12508
13005
|
disengageHotkey,
|
|
12509
|
-
ambient:
|
|
12510
|
-
registry:
|
|
13006
|
+
ambient: slottedRef.current.ambient,
|
|
13007
|
+
registry: slottedRef.current.registry,
|
|
12511
13008
|
has,
|
|
12512
13009
|
getActiveOverlays
|
|
12513
13010
|
}),
|
|
@@ -12515,38 +13012,6 @@ function useTools(opts) {
|
|
|
12515
13012
|
);
|
|
12516
13013
|
}
|
|
12517
13014
|
|
|
12518
|
-
// src/interactions/actions/defaults/toolOffhand.ts
|
|
12519
|
-
var TOOL_OFFHAND_ID = "tool.offhand";
|
|
12520
|
-
function buildToolOffhandBindings(specs) {
|
|
12521
|
-
return specs.map(({ toolId, key }) => ({
|
|
12522
|
-
spec: { kind: "key-held", key },
|
|
12523
|
-
opts: { params: { toolId } }
|
|
12524
|
-
}));
|
|
12525
|
-
}
|
|
12526
|
-
function makeToolOffhandAction(bindings) {
|
|
12527
|
-
return {
|
|
12528
|
-
id: TOOL_OFFHAND_ID,
|
|
12529
|
-
label: "Engage tool (offhand)",
|
|
12530
|
-
defaultBinding: bindings,
|
|
12531
|
-
scope: "hotkey",
|
|
12532
|
-
invoker: {
|
|
12533
|
-
timing: "ongoing",
|
|
12534
|
-
start: (ctx, opts) => {
|
|
12535
|
-
const activeTool = ctx.deps.activeTool;
|
|
12536
|
-
const toolId = resolveParams(opts?.params)?.toolId;
|
|
12537
|
-
if (!activeTool || !toolId) return {};
|
|
12538
|
-
activeTool.pushHotkey(toolId);
|
|
12539
|
-
return {
|
|
12540
|
-
onEnd: () => {
|
|
12541
|
-
activeTool.popHotkey();
|
|
12542
|
-
}
|
|
12543
|
-
};
|
|
12544
|
-
}
|
|
12545
|
-
},
|
|
12546
|
-
requires: ["activeTool"]
|
|
12547
|
-
};
|
|
12548
|
-
}
|
|
12549
|
-
|
|
12550
13015
|
// src/interactions/actions/defaults/toolActivate.ts
|
|
12551
13016
|
var TOOL_ACTIVATE_ID = "tool.activate";
|
|
12552
13017
|
function buildToolActivateBindings(specs) {
|
|
@@ -12623,9 +13088,6 @@ var BUILTIN_SELECT_KEYS = {
|
|
|
12623
13088
|
hand: { key: "H" },
|
|
12624
13089
|
pen: { key: "P" }
|
|
12625
13090
|
};
|
|
12626
|
-
var BUILTIN_OFFHAND_ACTIONS = {
|
|
12627
|
-
hand: " "
|
|
12628
|
-
};
|
|
12629
13091
|
function useKeybindings(tools, options = {}) {
|
|
12630
13092
|
const toolsRef = useRef(tools);
|
|
12631
13093
|
toolsRef.current = tools;
|
|
@@ -12633,19 +13095,6 @@ function useKeybindings(tools, options = {}) {
|
|
|
12633
13095
|
optionsRef.current = options;
|
|
12634
13096
|
const initialActiveRef = useRef(tools.active);
|
|
12635
13097
|
const registry2 = useActionsRegistry();
|
|
12636
|
-
useEffect(() => {
|
|
12637
|
-
if (optionsRef.current.disable) return;
|
|
12638
|
-
if (!registry2) return;
|
|
12639
|
-
const specs = [];
|
|
12640
|
-
for (const [toolId, key] of Object.entries(BUILTIN_OFFHAND_ACTIONS)) {
|
|
12641
|
-
if (toolsRef.current.has(toolId)) {
|
|
12642
|
-
specs.push({ toolId, key });
|
|
12643
|
-
}
|
|
12644
|
-
}
|
|
12645
|
-
if (specs.length === 0) return;
|
|
12646
|
-
const bindings = buildToolOffhandBindings(specs);
|
|
12647
|
-
return registry2.register(makeToolOffhandAction(bindings));
|
|
12648
|
-
}, [registry2, tools]);
|
|
12649
13098
|
useEffect(() => {
|
|
12650
13099
|
if (optionsRef.current.disable) return;
|
|
12651
13100
|
if (!registry2) return;
|
|
@@ -13207,8 +13656,108 @@ function textLineBoxes(pose, opts = {}) {
|
|
|
13207
13656
|
return out;
|
|
13208
13657
|
}
|
|
13209
13658
|
|
|
13659
|
+
// src/core/fillInPoseFrame.ts
|
|
13660
|
+
function fillInPoseFrame(fill, box) {
|
|
13661
|
+
if (isBoundsPattern(fill)) {
|
|
13662
|
+
const o = fill.origin ?? { x: 0, y: 0 };
|
|
13663
|
+
return { ...fill, origin: { x: box.x + o.x, y: box.y + o.y }, units: "local" };
|
|
13664
|
+
}
|
|
13665
|
+
if (!isBoundsGradient(fill)) return fill;
|
|
13666
|
+
const toBox = (p) => ({
|
|
13667
|
+
x: box.x + p.x * box.width,
|
|
13668
|
+
y: box.y + p.y * box.height
|
|
13669
|
+
});
|
|
13670
|
+
if (fill.fill === "linear-gradient") {
|
|
13671
|
+
return { ...fill, from: toBox(fill.from), to: toBox(fill.to), units: "local" };
|
|
13672
|
+
}
|
|
13673
|
+
if (fill.fill === "radial-gradient") {
|
|
13674
|
+
const scale2 = Math.hypot(box.width, box.height) / Math.SQRT2;
|
|
13675
|
+
return { ...fill, center: toBox(fill.center), radius: fill.radius * scale2, units: "local" };
|
|
13676
|
+
}
|
|
13677
|
+
return { ...fill, center: toBox(fill.center), units: "local" };
|
|
13678
|
+
}
|
|
13679
|
+
function fillToBoundsFrame(fill, box) {
|
|
13680
|
+
if (fill.fill === "pattern") {
|
|
13681
|
+
const o = fill.origin ?? { x: 0, y: 0 };
|
|
13682
|
+
return { ...fill, origin: { x: o.x - box.x, y: o.y - box.y }, units: "bounds" };
|
|
13683
|
+
}
|
|
13684
|
+
const isGradient = fill.fill === "linear-gradient" || fill.fill === "radial-gradient" || fill.fill === "conic-gradient";
|
|
13685
|
+
if (!isGradient) return fill;
|
|
13686
|
+
if (box.width === 0 || box.height === 0) return fill;
|
|
13687
|
+
const fromBox = (p) => ({
|
|
13688
|
+
x: (p.x - box.x) / box.width,
|
|
13689
|
+
y: (p.y - box.y) / box.height
|
|
13690
|
+
});
|
|
13691
|
+
if (fill.fill === "linear-gradient") {
|
|
13692
|
+
return { ...fill, from: fromBox(fill.from), to: fromBox(fill.to), units: "bounds" };
|
|
13693
|
+
}
|
|
13694
|
+
if (fill.fill === "radial-gradient") {
|
|
13695
|
+
const scale2 = Math.hypot(box.width, box.height) / Math.SQRT2;
|
|
13696
|
+
return { ...fill, center: fromBox(fill.center), radius: fill.radius / scale2, units: "bounds" };
|
|
13697
|
+
}
|
|
13698
|
+
return { ...fill, center: fromBox(fill.center), units: "bounds" };
|
|
13699
|
+
}
|
|
13700
|
+
function isBoundsPattern(fill) {
|
|
13701
|
+
return fill.fill === "pattern" && fill.units === "bounds";
|
|
13702
|
+
}
|
|
13703
|
+
function isBoundsGradient(fill) {
|
|
13704
|
+
if (fill === void 0) return false;
|
|
13705
|
+
const kind = fill.fill;
|
|
13706
|
+
if (kind !== "linear-gradient" && kind !== "radial-gradient" && kind !== "conic-gradient") {
|
|
13707
|
+
return false;
|
|
13708
|
+
}
|
|
13709
|
+
return fill.units === "bounds";
|
|
13710
|
+
}
|
|
13711
|
+
|
|
13712
|
+
// src/features/patterns/resolveSpec.ts
|
|
13713
|
+
var cache5 = /* @__PURE__ */ new Map();
|
|
13714
|
+
function specKey(spec) {
|
|
13715
|
+
const keys = Object.keys(spec).sort();
|
|
13716
|
+
const bag = spec;
|
|
13717
|
+
return keys.map((k) => `${k}=${String(bag[k])}`).join("&");
|
|
13718
|
+
}
|
|
13719
|
+
function resolvePatternSpec(spec) {
|
|
13720
|
+
const key = specKey(spec);
|
|
13721
|
+
const hit = cache5.get(key);
|
|
13722
|
+
if (hit !== void 0) return hit;
|
|
13723
|
+
const built = buildTile(spec);
|
|
13724
|
+
cache5.set(key, built);
|
|
13725
|
+
return built;
|
|
13726
|
+
}
|
|
13727
|
+
function buildTile(spec) {
|
|
13728
|
+
const { color, size } = spec;
|
|
13729
|
+
switch (spec.tile) {
|
|
13730
|
+
case "hatch":
|
|
13731
|
+
return hatch({ color, size, lineWidth: spec.lineWidth });
|
|
13732
|
+
case "crosshatch":
|
|
13733
|
+
return crosshatch({ color, size, lineWidth: spec.lineWidth });
|
|
13734
|
+
case "dots":
|
|
13735
|
+
return dots({ color, size, radius: spec.radius });
|
|
13736
|
+
case "chunks":
|
|
13737
|
+
return chunks({
|
|
13738
|
+
color,
|
|
13739
|
+
size,
|
|
13740
|
+
bg: spec.bg,
|
|
13741
|
+
density: spec.density,
|
|
13742
|
+
chunkSize: spec.chunkSize,
|
|
13743
|
+
seed: spec.seed
|
|
13744
|
+
});
|
|
13745
|
+
default:
|
|
13746
|
+
return null;
|
|
13747
|
+
}
|
|
13748
|
+
}
|
|
13749
|
+
function isPatternSpec(payload) {
|
|
13750
|
+
return "tile" in payload;
|
|
13751
|
+
}
|
|
13752
|
+
function resolveFillPattern(fill) {
|
|
13753
|
+
if (fill.fill !== "pattern") return fill;
|
|
13754
|
+
if (!isPatternSpec(fill.pattern)) return fill;
|
|
13755
|
+
const handle = resolvePatternSpec(fill.pattern);
|
|
13756
|
+
return handle ? { ...fill, pattern: handle } : null;
|
|
13757
|
+
}
|
|
13758
|
+
|
|
13210
13759
|
// src/features/images/imageCache.ts
|
|
13211
|
-
var
|
|
13760
|
+
var cache6 = /* @__PURE__ */ new Map();
|
|
13212
13761
|
var subscribers = /* @__PURE__ */ new Set();
|
|
13213
13762
|
var defaultLoader = async (src) => {
|
|
13214
13763
|
if (typeof Image === "undefined" || typeof createImageBitmap === "undefined") {
|
|
@@ -13226,28 +13775,28 @@ function notify() {
|
|
|
13226
13775
|
}
|
|
13227
13776
|
function startLoad(src) {
|
|
13228
13777
|
const entry = { status: "loading" };
|
|
13229
|
-
|
|
13778
|
+
cache6.set(src, entry);
|
|
13230
13779
|
loader(src).then(
|
|
13231
13780
|
(bitmap) => {
|
|
13232
|
-
if (
|
|
13233
|
-
|
|
13781
|
+
if (cache6.get(src) !== entry) return;
|
|
13782
|
+
cache6.set(src, { status: "ready", bitmap });
|
|
13234
13783
|
notify();
|
|
13235
13784
|
},
|
|
13236
13785
|
() => {
|
|
13237
|
-
if (
|
|
13238
|
-
|
|
13786
|
+
if (cache6.get(src) !== entry) return;
|
|
13787
|
+
cache6.set(src, { status: "error" });
|
|
13239
13788
|
notify();
|
|
13240
13789
|
}
|
|
13241
13790
|
);
|
|
13242
13791
|
}
|
|
13243
13792
|
function getImageBitmap(src) {
|
|
13244
|
-
const entry =
|
|
13793
|
+
const entry = cache6.get(src);
|
|
13245
13794
|
if (entry) return entry.bitmap;
|
|
13246
13795
|
startLoad(src);
|
|
13247
13796
|
return void 0;
|
|
13248
13797
|
}
|
|
13249
13798
|
function imageStatus(src) {
|
|
13250
|
-
return
|
|
13799
|
+
return cache6.get(src)?.status ?? "idle";
|
|
13251
13800
|
}
|
|
13252
13801
|
function subscribeImageReady(cb) {
|
|
13253
13802
|
subscribers.add(cb);
|
|
@@ -13348,6 +13897,13 @@ function shapeCoversPoint(node, pose, x, y, opts = {}) {
|
|
|
13348
13897
|
function getNodeShapes() {
|
|
13349
13898
|
return [...PAINTERS.high, ...PAINTERS.normal];
|
|
13350
13899
|
}
|
|
13900
|
+
function withLeafStroke(style, stroke, strokeWidth) {
|
|
13901
|
+
if (style?.stroke !== void 0) return style;
|
|
13902
|
+
if (!stroke || stroke === "none") return style;
|
|
13903
|
+
const width = strokeWidth ?? 1;
|
|
13904
|
+
if (width <= 0) return style;
|
|
13905
|
+
return { ...style, stroke: { paint: { color: stroke }, width } };
|
|
13906
|
+
}
|
|
13351
13907
|
var TEXT_PAINTER = {
|
|
13352
13908
|
id: "kit:text",
|
|
13353
13909
|
matches: (node) => {
|
|
@@ -13366,7 +13922,8 @@ var TEXT_PAINTER = {
|
|
|
13366
13922
|
const d = node.data;
|
|
13367
13923
|
const p = pose;
|
|
13368
13924
|
const y = p.y;
|
|
13369
|
-
|
|
13925
|
+
const style = withLeafStroke(d.style, d.stroke, d.strokeWidth);
|
|
13926
|
+
return d.runs && d.runs.length > 0 ? [textCommandFromRuns(p.x, y, d.runs, style, void 0, p.height)] : [textCommand(p.x, y, d.text, style, void 0, p.height)];
|
|
13370
13927
|
}),
|
|
13371
13928
|
// The pose is a *wrap box*, not a bounding box — "Away" in a 300-unit box
|
|
13372
13929
|
// leaves most of it empty, and a pose-rect silhouette claims all of it. The
|
|
@@ -13424,6 +13981,17 @@ function rectsToPath(rects) {
|
|
|
13424
13981
|
}
|
|
13425
13982
|
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
13426
13983
|
}
|
|
13984
|
+
function isFillStyle(v) {
|
|
13985
|
+
return typeof v === "object" && v !== null;
|
|
13986
|
+
}
|
|
13987
|
+
function resolveNodeFill(fill, color, hasStroke, fallback) {
|
|
13988
|
+
if (isFillStyle(fill)) return fill;
|
|
13989
|
+
const c = fill ?? color;
|
|
13990
|
+
if (c === "none") return null;
|
|
13991
|
+
if (c !== void 0) return { color: c };
|
|
13992
|
+
if (hasStroke && fallback === null) return null;
|
|
13993
|
+
return fallback === null ? null : { color: fallback };
|
|
13994
|
+
}
|
|
13427
13995
|
var PATH_PAINTER = {
|
|
13428
13996
|
id: "kit:path",
|
|
13429
13997
|
matches: (node) => {
|
|
@@ -13437,12 +14005,12 @@ var PATH_PAINTER = {
|
|
|
13437
14005
|
const d = node.data;
|
|
13438
14006
|
const projected = pathInPoseFrame(d.path, pose);
|
|
13439
14007
|
const hasStroke = !!d.stroke && d.stroke !== "none" && (d.strokeWidth ?? 0) > 0;
|
|
13440
|
-
const
|
|
13441
|
-
const
|
|
14008
|
+
const declared = resolveNodeFill(d.fill, d.color, hasStroke, hasStroke ? null : "#888");
|
|
14009
|
+
const fill = declared && resolveFillPattern(fillInPoseFrame(declared, pose));
|
|
13442
14010
|
const cmd = {
|
|
13443
14011
|
kind: "path",
|
|
13444
14012
|
path: projected,
|
|
13445
|
-
...
|
|
14013
|
+
...fill ? { fill } : {},
|
|
13446
14014
|
...hasStroke ? { stroke: { paint: { color: d.stroke }, width: d.strokeWidth ?? 1 } } : {}
|
|
13447
14015
|
};
|
|
13448
14016
|
return [cmd];
|
|
@@ -13458,9 +14026,8 @@ var PATH_PAINTER = {
|
|
|
13458
14026
|
ink: (node) => {
|
|
13459
14027
|
const d = node.data;
|
|
13460
14028
|
const hasStroke = !!d.stroke && d.stroke !== "none" && (d.strokeWidth ?? 0) > 0;
|
|
13461
|
-
const fillColor = d.fill ?? d.color;
|
|
13462
14029
|
return {
|
|
13463
|
-
filled:
|
|
14030
|
+
filled: resolveNodeFill(d.fill, d.color, hasStroke, hasStroke ? null : "#888") !== null,
|
|
13464
14031
|
strokeWidth: hasStroke ? d.strokeWidth ?? 1 : 0
|
|
13465
14032
|
};
|
|
13466
14033
|
}
|
|
@@ -13477,10 +14044,12 @@ var SHAPE_PAINTER = {
|
|
|
13477
14044
|
paint: (node, pose) => nodeMemo(node, PAINT_SLOT, pose, () => {
|
|
13478
14045
|
const d = node.data;
|
|
13479
14046
|
const path = pathForShape(d, pose);
|
|
14047
|
+
const declaredFill = resolveNodeFill(d.fill, d.color, false, "#888");
|
|
14048
|
+
const shapeFill = declaredFill && resolveFillPattern(fillInPoseFrame(declaredFill, pose));
|
|
13480
14049
|
return [{
|
|
13481
14050
|
kind: "path",
|
|
13482
14051
|
path,
|
|
13483
|
-
|
|
14052
|
+
...shapeFill ? { fill: shapeFill } : {},
|
|
13484
14053
|
...d.stroke && (d.strokeWidth ?? 0) > 0 ? { stroke: { paint: { color: d.stroke }, width: d.strokeWidth ?? 1 } } : {}
|
|
13485
14054
|
}];
|
|
13486
14055
|
}),
|
|
@@ -13881,11 +14450,8 @@ function useSelectTool(adapter, options) {
|
|
|
13881
14450
|
...moveOpts
|
|
13882
14451
|
},
|
|
13883
14452
|
{ spec: { kind: "drag", target: "empty" }, actionId: "areaSelect" },
|
|
13884
|
-
//
|
|
13885
|
-
//
|
|
13886
|
-
// widget that happens to float over empty canvas isn't a click on
|
|
13887
|
-
// empty canvas, and declining in the SPEC lets the chrome's owner
|
|
13888
|
-
// have the click instead of it being consumed here.
|
|
14453
|
+
// resize/rotate bind only drag (no click), so a click on either handle
|
|
14454
|
+
// (body genuinely empty) would hit target:'empty' and wrongly clear selection.
|
|
13889
14455
|
{
|
|
13890
14456
|
spec: {
|
|
13891
14457
|
kind: "click",
|
|
@@ -13971,17 +14537,17 @@ function worldToLocal(xf, wx, wy) {
|
|
|
13971
14537
|
function annulusSemiAxes(shape, view) {
|
|
13972
14538
|
const bandPx = shape.minBandPx ?? 0;
|
|
13973
14539
|
if (bandPx === 0) return { rx: shape.rx, ry: shape.ry };
|
|
13974
|
-
const band = bandPx
|
|
14540
|
+
const band = pxExtent(bandPx, view.scale);
|
|
13975
14541
|
return {
|
|
13976
|
-
rx: Math.max(shape.rx, shape.innerWidth / 2 + band),
|
|
13977
|
-
ry: Math.max(shape.ry, shape.innerHeight / 2 + band)
|
|
14542
|
+
rx: Math.max(shape.rx, shape.innerWidth / 2 + band.x),
|
|
14543
|
+
ry: Math.max(shape.ry, shape.innerHeight / 2 + band.y)
|
|
13978
14544
|
};
|
|
13979
14545
|
}
|
|
13980
14546
|
function hitRegion(region, wx, wy, xf, view) {
|
|
13981
14547
|
const local = worldToLocal(xf, wx, wy);
|
|
13982
14548
|
if (region.shape.kind === "point") {
|
|
13983
|
-
const
|
|
13984
|
-
return
|
|
14549
|
+
const anchor = localToWorld(xf, region.shape.x, region.shape.y);
|
|
14550
|
+
return withinPxBox(anchor.x - wx, anchor.y - wy, region.shape.hitRadiusPx, view.scale);
|
|
13985
14551
|
}
|
|
13986
14552
|
if (region.shape.kind === "annulus") {
|
|
13987
14553
|
const s = region.shape;
|
|
@@ -14047,14 +14613,13 @@ function asIsVisible(data) {
|
|
|
14047
14613
|
function recordRegionHitbox(debug, affordanceId, region, xf, view) {
|
|
14048
14614
|
if (region.shape.kind === "point") {
|
|
14049
14615
|
const w2 = localToWorld(xf, region.shape.x, region.shape.y);
|
|
14050
|
-
const
|
|
14616
|
+
const e = pxExtent(region.shape.hitRadiusPx, view.scale);
|
|
14051
14617
|
debug.recordHitbox(affordanceId, "handle", {
|
|
14052
14618
|
kind: "rect",
|
|
14053
|
-
x: w2.x -
|
|
14054
|
-
y: w2.y -
|
|
14055
|
-
width:
|
|
14056
|
-
height:
|
|
14057
|
-
...xf.identity ? {} : { rotation: Math.atan2(xf.sin, xf.cos) }
|
|
14619
|
+
x: w2.x - e.x,
|
|
14620
|
+
y: w2.y - e.y,
|
|
14621
|
+
width: e.x * 2,
|
|
14622
|
+
height: e.y * 2
|
|
14058
14623
|
});
|
|
14059
14624
|
return;
|
|
14060
14625
|
}
|
|
@@ -14113,7 +14678,7 @@ function paintRegion(region, xf, view, state, viewT, out) {
|
|
|
14113
14678
|
if (paint.kind === "annulus") {
|
|
14114
14679
|
if (region.shape.kind !== "annulus") return;
|
|
14115
14680
|
const s = region.shape;
|
|
14116
|
-
const insetLocal = (paint.insetPx ?? 0
|
|
14681
|
+
const insetLocal = pxExtent(paint.insetPx ?? 0, view.scale);
|
|
14117
14682
|
const cmd = annulusCommand(s, annulusSemiAxes(s, view), insetLocal, xf, viewT, paint.fill, paint.stroke);
|
|
14118
14683
|
if (cmd) out.push(cmd);
|
|
14119
14684
|
return;
|
|
@@ -14130,13 +14695,13 @@ function paintRegion(region, xf, view, state, viewT, out) {
|
|
|
14130
14695
|
}
|
|
14131
14696
|
}
|
|
14132
14697
|
function annulusCommand(shape, semiAxes, insetLocal, xf, viewT, fill, stroke) {
|
|
14133
|
-
const rx = semiAxes.rx - insetLocal;
|
|
14134
|
-
const ry = semiAxes.ry - insetLocal;
|
|
14698
|
+
const rx = semiAxes.rx - insetLocal.x;
|
|
14699
|
+
const ry = semiAxes.ry - insetLocal.y;
|
|
14135
14700
|
if (rx <= 0 || ry <= 0) return null;
|
|
14136
14701
|
const cx = shape.cx;
|
|
14137
14702
|
const cy = shape.cy;
|
|
14138
|
-
const rectHW = shape.innerWidth / 2 + insetLocal;
|
|
14139
|
-
const rectHH = shape.innerHeight / 2 + insetLocal;
|
|
14703
|
+
const rectHW = shape.innerWidth / 2 + insetLocal.x;
|
|
14704
|
+
const rectHH = shape.innerHeight / 2 + insetLocal.y;
|
|
14140
14705
|
const haveTopBottom = rectHH < ry;
|
|
14141
14706
|
const haveLeftRight = rectHW < rx;
|
|
14142
14707
|
if (!haveTopBottom && !haveLeftRight) return null;
|
|
@@ -14397,6 +14962,7 @@ function useHandTool(opts = {}) {
|
|
|
14397
14962
|
() => Object.assign(defineViewportTool({
|
|
14398
14963
|
id: "hand",
|
|
14399
14964
|
capabilities: ["navigation"],
|
|
14965
|
+
hotkey: "space",
|
|
14400
14966
|
hookName: "useHandTool",
|
|
14401
14967
|
presentation: {
|
|
14402
14968
|
label: "Hand",
|
|
@@ -14521,10 +15087,6 @@ function mirrorHandle(anchor, out) {
|
|
|
14521
15087
|
if (!out || anchor.altBroken) return void 0;
|
|
14522
15088
|
return { x: 2 * anchor.x - out.x, y: 2 * anchor.y - out.y };
|
|
14523
15089
|
}
|
|
14524
|
-
function dist2(ax, ay, bx, by) {
|
|
14525
|
-
const dx = ax - bx, dy = ay - by;
|
|
14526
|
-
return Math.hypot(dx, dy);
|
|
14527
|
-
}
|
|
14528
15090
|
function usePenTool(options) {
|
|
14529
15091
|
const { wrapPath, adapter, autoSelect = true, autoCommitOnClose = true, closeHitRadius = 8, snapPoint: snapPoint2 } = options;
|
|
14530
15092
|
const scratchRef = useRef(null);
|
|
@@ -14548,11 +15110,14 @@ function usePenTool(options) {
|
|
|
14548
15110
|
const fn = optsRef.current.snapPoint;
|
|
14549
15111
|
return fn ? fn({ x, y }) : { x, y };
|
|
14550
15112
|
}, []);
|
|
14551
|
-
const
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
|
|
15113
|
+
const withinCloseRadius = useCallback(
|
|
15114
|
+
(deps, ax, ay, wx, wy) => {
|
|
15115
|
+
const view = deps.view?.get();
|
|
15116
|
+
const scale2 = view?.scale ?? { x: 1, y: 1 };
|
|
15117
|
+
return withinPxRadius(ax - wx, ay - wy, optsRef.current.closeHitRadius, scale2);
|
|
15118
|
+
},
|
|
15119
|
+
[]
|
|
15120
|
+
);
|
|
14556
15121
|
const actions = useMemo(() => {
|
|
14557
15122
|
const s = () => scratchRef.current;
|
|
14558
15123
|
return [
|
|
@@ -14568,10 +15133,9 @@ function usePenTool(options) {
|
|
|
14568
15133
|
if (p?.pressX === void 0 || p.pressY === void 0) return;
|
|
14569
15134
|
const { x: wx, y: wy } = snap2(p.pressX, p.pressY);
|
|
14570
15135
|
const scratch = s();
|
|
14571
|
-
const radius = closeRadius(deps);
|
|
14572
15136
|
if (scratch.current && scratch.current.anchors.length >= 3) {
|
|
14573
15137
|
const first = scratch.current.anchors[0];
|
|
14574
|
-
if (
|
|
15138
|
+
if (withinCloseRadius(deps, first.x, first.y, wx, wy)) {
|
|
14575
15139
|
scratch.current.closed = true;
|
|
14576
15140
|
scratch.finishedSubpaths.push(scratch.current);
|
|
14577
15141
|
scratch.current = null;
|
|
@@ -14690,7 +15254,7 @@ function usePenTool(options) {
|
|
|
14690
15254
|
}
|
|
14691
15255
|
}
|
|
14692
15256
|
];
|
|
14693
|
-
}, [commit, snap2,
|
|
15257
|
+
}, [commit, snap2, withinCloseRadius]);
|
|
14694
15258
|
return useMemo(() => {
|
|
14695
15259
|
return defineTool({
|
|
14696
15260
|
id: "pen",
|
|
@@ -15548,7 +16112,7 @@ function buildChromeState(args) {
|
|
|
15548
16112
|
}
|
|
15549
16113
|
|
|
15550
16114
|
// src/interactions/actions/rotate/handle.ts
|
|
15551
|
-
var DEFAULT_ROTATION_HANDLE_DISTANCE =
|
|
16115
|
+
var DEFAULT_ROTATION_HANDLE_DISTANCE = ROTATION_HANDLE_BASE_PX;
|
|
15552
16116
|
function rotationHandle(pose, distance = DEFAULT_ROTATION_HANDLE_DISTANCE) {
|
|
15553
16117
|
const rotation = pose.rotation ?? 0;
|
|
15554
16118
|
const c = aabbCenter(pose);
|
|
@@ -15663,7 +16227,7 @@ var DEFAULT_HANDLE_OUTLINE = {
|
|
|
15663
16227
|
paint: { fill: "solid", color: "#1a130d" },
|
|
15664
16228
|
width: 1
|
|
15665
16229
|
};
|
|
15666
|
-
var DEFAULT_HANDLE_SIZE =
|
|
16230
|
+
var DEFAULT_HANDLE_SIZE = HANDLE_BASE_PX;
|
|
15667
16231
|
function defaultHandlesOf(b) {
|
|
15668
16232
|
return [
|
|
15669
16233
|
{ x: b.x, y: b.y },
|
|
@@ -16467,8 +17031,8 @@ function CanvasInner(props, ref) {
|
|
|
16467
17031
|
const isVisible = getIsVisibleRef.current?.() ?? alwaysVisible;
|
|
16468
17032
|
for (const layer of layers2) {
|
|
16469
17033
|
if (!layer.hitTest) continue;
|
|
16470
|
-
const
|
|
16471
|
-
if (
|
|
17034
|
+
const hit = layer.hitTest(worldX, worldY, void 0, view, dims, isVisible);
|
|
17035
|
+
if (hit) return { layerId: layer.id, hit };
|
|
16472
17036
|
}
|
|
16473
17037
|
return null;
|
|
16474
17038
|
}, []);
|
|
@@ -16904,7 +17468,7 @@ function CanvasInner(props, ref) {
|
|
|
16904
17468
|
effectiveView,
|
|
16905
17469
|
{ width, height }
|
|
16906
17470
|
);
|
|
16907
|
-
renderer.render(commands);
|
|
17471
|
+
renderer.render(commands, viewToMat3(effectiveView));
|
|
16908
17472
|
}, [layersWithDebug, width, height, effectiveView, debugSink, redrawNonce, dprProp]);
|
|
16909
17473
|
const shaderIdKey = shaders?.map((h) => h.id).join("|") ?? "";
|
|
16910
17474
|
useEffect(() => {
|
|
@@ -19168,8 +19732,8 @@ var DEFAULT_FILL3 = "#d4c4a8";
|
|
|
19168
19732
|
var DEFAULT_STROKE = "#1a130d";
|
|
19169
19733
|
function createCornerResizeAffordance(opts = {}) {
|
|
19170
19734
|
const {
|
|
19171
|
-
handleHitRadius =
|
|
19172
|
-
handleSize =
|
|
19735
|
+
handleHitRadius = HANDLE_BASE_PX,
|
|
19736
|
+
handleSize = HANDLE_BASE_PX,
|
|
19173
19737
|
fill = DEFAULT_FILL3,
|
|
19174
19738
|
stroke = DEFAULT_STROKE
|
|
19175
19739
|
} = opts;
|
|
@@ -19311,8 +19875,8 @@ function createPathAnchorAffordances(getAnchorState, opts = {}) {
|
|
|
19311
19875
|
}
|
|
19312
19876
|
|
|
19313
19877
|
// src/canvas/affordanceAt.ts
|
|
19314
|
-
var HANDLE_HIT_RADIUS =
|
|
19315
|
-
var ANCHOR_HIT_RADIUS =
|
|
19878
|
+
var HANDLE_HIT_RADIUS = HANDLE_BASE_PX;
|
|
19879
|
+
var ANCHOR_HIT_RADIUS = ANCHOR_HIT_BASE_PX;
|
|
19316
19880
|
function buildAffordanceAt(opts) {
|
|
19317
19881
|
const {
|
|
19318
19882
|
getChromeState,
|
|
@@ -19344,6 +19908,8 @@ function toAffordanceHit(hit) {
|
|
|
19344
19908
|
const scratch = hit.binding.initialScratch ?? {};
|
|
19345
19909
|
return {
|
|
19346
19910
|
kind: hit.region.hitKind ?? `${hit.affordanceId}:${hit.regionId}`,
|
|
19911
|
+
owner: hit.affordanceId,
|
|
19912
|
+
strength: "shared",
|
|
19347
19913
|
...scratch.targetId !== void 0 ? { targetIds: [scratch.targetId] } : {},
|
|
19348
19914
|
...scratch.anchor !== void 0 ? { anchor: scratch.anchor } : {},
|
|
19349
19915
|
...scratch.fixedPoint !== void 0 ? { fixedPoint: scratch.fixedPoint } : {},
|
|
@@ -19872,7 +20438,7 @@ function chordLengthParam(samples) {
|
|
|
19872
20438
|
ts[0] = 0;
|
|
19873
20439
|
let total = 0;
|
|
19874
20440
|
for (let i = 1; i < n; i++) {
|
|
19875
|
-
total +=
|
|
20441
|
+
total += dist2(samples[i - 1], samples[i]);
|
|
19876
20442
|
ts[i] = total;
|
|
19877
20443
|
}
|
|
19878
20444
|
if (total === 0) return null;
|
|
@@ -19905,13 +20471,13 @@ function solveLS(samples, ts, a, e, tStart, tEnd) {
|
|
|
19905
20471
|
let alpha1;
|
|
19906
20472
|
let alpha2;
|
|
19907
20473
|
if (Math.abs(det) < 1e-12) {
|
|
19908
|
-
const chord =
|
|
20474
|
+
const chord = dist2(a, e);
|
|
19909
20475
|
alpha1 = alpha2 = chord / 3;
|
|
19910
20476
|
} else {
|
|
19911
20477
|
alpha1 = (c22 * x1 - c12 * x2) / det;
|
|
19912
20478
|
alpha2 = (c11 * x2 - c12 * x1) / det;
|
|
19913
20479
|
if (alpha1 < 1e-6 || alpha2 < 1e-6) {
|
|
19914
|
-
const chord =
|
|
20480
|
+
const chord = dist2(a, e);
|
|
19915
20481
|
alpha1 = alpha2 = chord / 3;
|
|
19916
20482
|
}
|
|
19917
20483
|
}
|
|
@@ -19947,7 +20513,7 @@ function newtonRefineT(p, t, p0, p1, p2, p3) {
|
|
|
19947
20513
|
function sub(a, b) {
|
|
19948
20514
|
return { x: a.x - b.x, y: a.y - b.y };
|
|
19949
20515
|
}
|
|
19950
|
-
function
|
|
20516
|
+
function dist2(a, b) {
|
|
19951
20517
|
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
19952
20518
|
}
|
|
19953
20519
|
function unit(v) {
|
|
@@ -20584,7 +21150,7 @@ function makeGetNodeAtPoint(pickEvery, nodeResolver) {
|
|
|
20584
21150
|
return { id, kind, pose, data };
|
|
20585
21151
|
};
|
|
20586
21152
|
}
|
|
20587
|
-
var DEFAULT_HANDLE_SIZE2 =
|
|
21153
|
+
var DEFAULT_HANDLE_SIZE2 = HANDLE_BASE_PX;
|
|
20588
21154
|
var IS_DEV = (() => {
|
|
20589
21155
|
try {
|
|
20590
21156
|
return Boolean(import.meta.env?.DEV);
|
|
@@ -20603,10 +21169,10 @@ function recordCoordTrace(entry) {
|
|
|
20603
21169
|
coordTrace.push(entry);
|
|
20604
21170
|
if (coordTrace.length > COORD_TRACE_LIMIT) coordTrace.shift();
|
|
20605
21171
|
}
|
|
20606
|
-
function mergeLayersWithDefaults(user) {
|
|
21172
|
+
function mergeLayersWithDefaults(user, targetScale = 1) {
|
|
20607
21173
|
const defaults = {
|
|
20608
21174
|
scene: { drawOne: defaultDrawOne },
|
|
20609
|
-
selectionOverlay: { handles: { size:
|
|
21175
|
+
selectionOverlay: { handles: { size: HANDLE_BASE_PX * targetScale } }
|
|
20610
21176
|
};
|
|
20611
21177
|
if (!user) return defaults;
|
|
20612
21178
|
const result = { ...user };
|
|
@@ -20694,8 +21260,10 @@ function SceneCanvasInner(props, ref) {
|
|
|
20694
21260
|
chromeVisibility,
|
|
20695
21261
|
getActiveMode,
|
|
20696
21262
|
getFocused: getFocusedProp,
|
|
21263
|
+
device,
|
|
20697
21264
|
...rest
|
|
20698
21265
|
} = props;
|
|
21266
|
+
const deviceProfile = useDeviceProfile(device);
|
|
20699
21267
|
const isSerialized = (s) => typeof s === "object" && s != null && s.version === 1 && Array.isArray(s.nodes);
|
|
20700
21268
|
const [bakedScene] = useState(
|
|
20701
21269
|
() => isSerialized(sceneInput) ? sceneFromJSON(sceneInput, {}) : null
|
|
@@ -20813,7 +21381,7 @@ function SceneCanvasInner(props, ref) {
|
|
|
20813
21381
|
}
|
|
20814
21382
|
}), []);
|
|
20815
21383
|
const selectToolWithDefaults = useMemo(() => ({
|
|
20816
|
-
handleHitRadius:
|
|
21384
|
+
handleHitRadius: HANDLE_BASE_PX * deviceProfile.targetScale,
|
|
20817
21385
|
// Shift-click belongs to the anchor selection while a path is being
|
|
20818
21386
|
// anchor-edited; see `UseSelectToolOptions.extendClickLocked`.
|
|
20819
21387
|
extendClickLocked: () => effectivePathEditingId() !== "",
|
|
@@ -20823,7 +21391,7 @@ function SceneCanvasInner(props, ref) {
|
|
|
20823
21391
|
// pipeline (audit 3.4). The classifier is now `select.pick`, which
|
|
20824
21392
|
// declares that capability itself — one rule, evaluated in one place.
|
|
20825
21393
|
...selectToolOpts
|
|
20826
|
-
}), [selectToolOpts]);
|
|
21394
|
+
}), [selectToolOpts, deviceProfile.targetScale]);
|
|
20827
21395
|
const selectionRef = useRef(selection);
|
|
20828
21396
|
selectionRef.current = selection;
|
|
20829
21397
|
const kindClassifier = useMemo(() => {
|
|
@@ -20955,7 +21523,7 @@ function SceneCanvasInner(props, ref) {
|
|
|
20955
21523
|
const getMode = getActiveModeRef.current;
|
|
20956
21524
|
if (!getMode) return true;
|
|
20957
21525
|
const registry2 = toolsForEligibilityRef.current?.registry;
|
|
20958
|
-
const caps = registry2?.[toolId]?.capabilities ?? [];
|
|
21526
|
+
const caps = registry2?.[toolId]?.eligibility.capabilities ?? [];
|
|
20959
21527
|
if (caps.length === 0) return false;
|
|
20960
21528
|
const allowed = getMode().allowedCapabilities;
|
|
20961
21529
|
for (const c of caps) if (allowed.has(c)) return true;
|
|
@@ -20981,8 +21549,8 @@ function SceneCanvasInner(props, ref) {
|
|
|
20981
21549
|
};
|
|
20982
21550
|
}, [Boolean(onDoubleClick)]);
|
|
20983
21551
|
const mergedLayers = useMemo(
|
|
20984
|
-
() => mergeLayersWithDefaults(layers),
|
|
20985
|
-
[layers]
|
|
21552
|
+
() => mergeLayersWithDefaults(layers, deviceProfile.targetScale),
|
|
21553
|
+
[layers, deviceProfile.targetScale]
|
|
20986
21554
|
);
|
|
20987
21555
|
const resolvedActions = useMemo(() => {
|
|
20988
21556
|
if (selectToolOpts?.rotate !== false) return actions;
|
|
@@ -21041,6 +21609,8 @@ function SceneCanvasInner(props, ref) {
|
|
|
21041
21609
|
chromeVisibilityRef.current = effectiveChromeVisibility;
|
|
21042
21610
|
const getActiveModeRef = useRef(getActiveMode);
|
|
21043
21611
|
getActiveModeRef.current = getActiveMode;
|
|
21612
|
+
const deviceProfileRef = useRef(deviceProfile);
|
|
21613
|
+
deviceProfileRef.current = deviceProfile;
|
|
21044
21614
|
const resizablePredRef = useRef(selectToolOpts?.resize?.resizable);
|
|
21045
21615
|
resizablePredRef.current = selectToolOpts?.resize?.resizable;
|
|
21046
21616
|
const buildCurrentRuleCtx = useCallback(() => {
|
|
@@ -21062,7 +21632,8 @@ function SceneCanvasInner(props, ref) {
|
|
|
21062
21632
|
mode: modeInfo.id,
|
|
21063
21633
|
allowedCapabilities: modeInfo.allowedCapabilities,
|
|
21064
21634
|
selectionResizable,
|
|
21065
|
-
editingAnchors: effectivePathEditingId() !== ""
|
|
21635
|
+
editingAnchors: effectivePathEditingId() !== "",
|
|
21636
|
+
device: deviceProfileRef.current
|
|
21066
21637
|
};
|
|
21067
21638
|
}, [dispatcher, getHover]);
|
|
21068
21639
|
const anchorEditingAllowed = useMemo(
|
|
@@ -21317,7 +21888,7 @@ function SceneCanvasInner(props, ref) {
|
|
|
21317
21888
|
testHookRef.current?._markReady();
|
|
21318
21889
|
}
|
|
21319
21890
|
}, []);
|
|
21320
|
-
return /* @__PURE__ */ jsx(DepRegistryProviderIfRoot, { children: /* @__PURE__ */ jsx(PointerProviderIfRoot, { children: /* @__PURE__ */ jsxs(ActionsProviderIfRoot, { children: [
|
|
21891
|
+
return /* @__PURE__ */ jsx(DeviceProfileProvider, { value: device, children: /* @__PURE__ */ jsx(DepRegistryProviderIfRoot, { children: /* @__PURE__ */ jsx(PointerProviderIfRoot, { children: /* @__PURE__ */ jsxs(ActionsProviderIfRoot, { children: [
|
|
21321
21892
|
canvas,
|
|
21322
21893
|
/* @__PURE__ */ jsx(PointerPublisher, { canvasRef: internalCanvasRef, viewRef: currentViewRef }),
|
|
21323
21894
|
/* @__PURE__ */ jsx(
|
|
@@ -21380,7 +21951,7 @@ function SceneCanvasInner(props, ref) {
|
|
|
21380
21951
|
}
|
|
21381
21952
|
),
|
|
21382
21953
|
children
|
|
21383
|
-
] }) }) });
|
|
21954
|
+
] }) }) }) });
|
|
21384
21955
|
}
|
|
21385
21956
|
function ToolKeybindingsMounter({
|
|
21386
21957
|
internalTools,
|
|
@@ -21426,10 +21997,6 @@ function GestureDispatcherMounter({
|
|
|
21426
21997
|
for (const tool of tools.ambient) m.set(tool.id, tool);
|
|
21427
21998
|
return m;
|
|
21428
21999
|
}, [tools.registry, tools.ambient]);
|
|
21429
|
-
const ambientToolIds = useMemo(
|
|
21430
|
-
() => tools.ambient.map((t) => t.id),
|
|
21431
|
-
[tools.ambient]
|
|
21432
|
-
);
|
|
21433
22000
|
const boundsOfRef = useRef(boundsOf2);
|
|
21434
22001
|
boundsOfRef.current = boundsOf2;
|
|
21435
22002
|
const pickEveryRef = useRef(pickEvery);
|
|
@@ -21541,9 +22108,14 @@ function GestureDispatcherMounter({
|
|
|
21541
22108
|
const worldPoint = clientToWorld2(screenPoint.x, screenPoint.y);
|
|
21542
22109
|
const extra = canvasApiRef?.current?.hitTestExtras?.(worldPoint.x, worldPoint.y);
|
|
21543
22110
|
if (extra) {
|
|
22111
|
+
const claim = extra.hit;
|
|
21544
22112
|
return {
|
|
21545
22113
|
kind: `layer:${extra.layerId}`,
|
|
21546
|
-
|
|
22114
|
+
owner: extra.layerId,
|
|
22115
|
+
strength: claim.strength ?? "shared",
|
|
22116
|
+
...claim.claimedKinds !== void 0 ? { claimedKinds: claim.claimedKinds } : {},
|
|
22117
|
+
...claim.cursor !== void 0 ? { cursor: claim.cursor } : {},
|
|
22118
|
+
...claim.initialScratch !== void 0 ? { payload: claim.initialScratch } : {}
|
|
21547
22119
|
};
|
|
21548
22120
|
}
|
|
21549
22121
|
return affordanceAt ? affordanceAt(worldPoint) : null;
|
|
@@ -21563,7 +22135,6 @@ function GestureDispatcherMounter({
|
|
|
21563
22135
|
canvasRef,
|
|
21564
22136
|
actions: registry2,
|
|
21565
22137
|
toolsById,
|
|
21566
|
-
ambientToolIds,
|
|
21567
22138
|
enabled,
|
|
21568
22139
|
keyboard,
|
|
21569
22140
|
affordanceAt: wrappedAffordanceAt,
|
|
@@ -21821,7 +22392,7 @@ function renderSceneToCanvas(args) {
|
|
|
21821
22392
|
entry.dpr = dpr;
|
|
21822
22393
|
}
|
|
21823
22394
|
const commands = buildSceneViewCommands(scene, view, drawOne, extraCommands);
|
|
21824
|
-
entry.renderer.render(commands);
|
|
22395
|
+
entry.renderer.render(commands, viewToMat3(view));
|
|
21825
22396
|
}
|
|
21826
22397
|
function SceneViewCanvasInner(props) {
|
|
21827
22398
|
const { scene, view, width, height, drawOne, extraCommands, className, canvasRef } = props;
|
|
@@ -22161,7 +22732,7 @@ function renderSceneToPixels(args) {
|
|
|
22161
22732
|
bakeBudget: Infinity
|
|
22162
22733
|
});
|
|
22163
22734
|
try {
|
|
22164
|
-
renderer.render(plan.commands);
|
|
22735
|
+
renderer.render(plan.commands, viewToMat3(plan.view));
|
|
22165
22736
|
if (typeof gl.isContextLost === "function" && gl.isContextLost()) {
|
|
22166
22737
|
throw new Error("renderSceneToPixels: WebGL2 context was lost during render");
|
|
22167
22738
|
}
|
|
@@ -22760,7 +23331,8 @@ var STYLE_KEYS = [
|
|
|
22760
23331
|
"fontFamily",
|
|
22761
23332
|
"fontSize",
|
|
22762
23333
|
"letterSpacing",
|
|
22763
|
-
"fill"
|
|
23334
|
+
"fill",
|
|
23335
|
+
"stroke"
|
|
22764
23336
|
];
|
|
22765
23337
|
var FLAG_KEYS = /* @__PURE__ */ new Set([
|
|
22766
23338
|
"bold",
|
|
@@ -22788,8 +23360,9 @@ function valueAt(run, key) {
|
|
|
22788
23360
|
if (FLAG_KEYS.has(key)) return value ?? false;
|
|
22789
23361
|
return value;
|
|
22790
23362
|
}
|
|
23363
|
+
var STRUCTURAL_KEYS = /* @__PURE__ */ new Set(["fill", "stroke"]);
|
|
22791
23364
|
function sameValue(key, a, b) {
|
|
22792
|
-
return key
|
|
23365
|
+
return STRUCTURAL_KEYS.has(key) ? deepEqual(a, b) : Object.is(a, b);
|
|
22793
23366
|
}
|
|
22794
23367
|
function sameStyle(a, b) {
|
|
22795
23368
|
return STYLE_KEYS.every((key) => sameValue(key, valueAt(a, key), valueAt(b, key)));
|
|
@@ -22883,7 +23456,79 @@ function applyStyleToRange(runs, start, end, patch) {
|
|
|
22883
23456
|
return coalesceRuns(out);
|
|
22884
23457
|
}
|
|
22885
23458
|
|
|
23459
|
+
// src/features/text/runs/flagRange.ts
|
|
23460
|
+
function nodeHasFlag(style, key) {
|
|
23461
|
+
switch (key) {
|
|
23462
|
+
case "bold":
|
|
23463
|
+
return isBoldWeight(style.fontWeight);
|
|
23464
|
+
case "italic":
|
|
23465
|
+
return style.fontStyle === "italic";
|
|
23466
|
+
case "underline":
|
|
23467
|
+
return style.underline === true;
|
|
23468
|
+
case "strikethrough":
|
|
23469
|
+
return style.strikethrough === true;
|
|
23470
|
+
}
|
|
23471
|
+
}
|
|
23472
|
+
function isBoldWeight(w) {
|
|
23473
|
+
if (w === void 0) return false;
|
|
23474
|
+
if (typeof w === "number") return w >= 600;
|
|
23475
|
+
return w === "bold" || w === "bolder";
|
|
23476
|
+
}
|
|
23477
|
+
function weightIsExpressibleAsRunBold(w) {
|
|
23478
|
+
return w === 700 || w === "bold";
|
|
23479
|
+
}
|
|
23480
|
+
function clearNodeFlag(style, key) {
|
|
23481
|
+
const next = { ...style };
|
|
23482
|
+
switch (key) {
|
|
23483
|
+
case "bold":
|
|
23484
|
+
next.fontWeight = 400;
|
|
23485
|
+
break;
|
|
23486
|
+
case "italic":
|
|
23487
|
+
next.fontStyle = "normal";
|
|
23488
|
+
break;
|
|
23489
|
+
case "underline":
|
|
23490
|
+
delete next.underline;
|
|
23491
|
+
break;
|
|
23492
|
+
case "strikethrough":
|
|
23493
|
+
delete next.strikethrough;
|
|
23494
|
+
break;
|
|
23495
|
+
}
|
|
23496
|
+
return next;
|
|
23497
|
+
}
|
|
23498
|
+
function setFlagOverRange(runs, style, start, end, key, value) {
|
|
23499
|
+
if (value || !nodeHasFlag(style, key)) {
|
|
23500
|
+
return {
|
|
23501
|
+
runs: applyStyleToRange(runs, start, end, { [key]: value }),
|
|
23502
|
+
style,
|
|
23503
|
+
applied: true
|
|
23504
|
+
};
|
|
23505
|
+
}
|
|
23506
|
+
if (key === "bold" && !weightIsExpressibleAsRunBold(style.fontWeight)) {
|
|
23507
|
+
return { runs: [...runs], style, applied: false };
|
|
23508
|
+
}
|
|
23509
|
+
const total = runs.reduce((n, r) => n + r.text.length, 0);
|
|
23510
|
+
const lo = Math.max(0, start);
|
|
23511
|
+
const hi = Math.min(total, end);
|
|
23512
|
+
let next = applyStyleToRange(runs, 0, total, { [key]: true });
|
|
23513
|
+
if (lo < hi) next = applyStyleToRange(next, lo, hi, { [key]: false });
|
|
23514
|
+
return { runs: next, style: clearNodeFlag(style, key), applied: true };
|
|
23515
|
+
}
|
|
23516
|
+
|
|
22886
23517
|
// src/features/text/useTextEdit.ts
|
|
23518
|
+
var FLAG_SHORTCUTS = [
|
|
23519
|
+
{ key: "b", shift: false, flag: "bold" },
|
|
23520
|
+
{ key: "i", shift: false, flag: "italic" },
|
|
23521
|
+
{ key: "u", shift: false, flag: "underline" },
|
|
23522
|
+
{ key: "x", shift: true, flag: "strikethrough" }
|
|
23523
|
+
];
|
|
23524
|
+
function flagForKey(e) {
|
|
23525
|
+
if (!e.metaKey && !e.ctrlKey) return null;
|
|
23526
|
+
const key = e.key.toLowerCase();
|
|
23527
|
+
return FLAG_SHORTCUTS.find((s) => s.key === key && s.shift === e.shiftKey)?.flag ?? null;
|
|
23528
|
+
}
|
|
23529
|
+
function pendingKey(flag) {
|
|
23530
|
+
return `pending${flag[0].toUpperCase()}${flag.slice(1)}`;
|
|
23531
|
+
}
|
|
22887
23532
|
function toggleFlagInRange(runs, start, end, flag) {
|
|
22888
23533
|
const current = styleAtRange(runs, start, end)[flag];
|
|
22889
23534
|
return applyStyleToRange(runs, start, end, { [flag]: current !== true });
|
|
@@ -23030,12 +23675,26 @@ function useTextEdit(opts) {
|
|
|
23030
23675
|
togglePending(flag);
|
|
23031
23676
|
return;
|
|
23032
23677
|
}
|
|
23033
|
-
|
|
23678
|
+
if (editingId === null) return;
|
|
23679
|
+
const runs = domToRuns(overlay);
|
|
23680
|
+
const nodeStyle = optsRef.current.getStyle(editingId) ?? {};
|
|
23681
|
+
const setStyle = optsRef.current.setStyle;
|
|
23682
|
+
const current = styleAtRange(runs, range2.start, range2.end)[flag];
|
|
23683
|
+
const unsetting = current === true;
|
|
23684
|
+
if (unsetting && setStyle && nodeHasFlag(nodeStyle, flag)) {
|
|
23685
|
+
const r = setFlagOverRange(runs, nodeStyle, range2.start, range2.end, flag, false);
|
|
23686
|
+
if (!r.applied) return;
|
|
23687
|
+
setStyle(editingId, r.style);
|
|
23688
|
+
writeRunsPreservingSelection(overlay, r.runs, range2.start, range2.end);
|
|
23689
|
+
syncSelection();
|
|
23690
|
+
return;
|
|
23691
|
+
}
|
|
23692
|
+
const next = toggleFlagInRange(runs, range2.start, range2.end, flag);
|
|
23034
23693
|
writeRunsPreservingSelection(overlay, next, range2.start, range2.end);
|
|
23035
23694
|
syncSelection();
|
|
23036
23695
|
}
|
|
23037
23696
|
function togglePending(flag) {
|
|
23038
|
-
const key = flag
|
|
23697
|
+
const key = pendingKey(flag);
|
|
23039
23698
|
if (overlay.dataset[key] === "1") {
|
|
23040
23699
|
delete overlay.dataset[key];
|
|
23041
23700
|
} else {
|
|
@@ -23053,9 +23712,9 @@ function useTextEdit(opts) {
|
|
|
23053
23712
|
cancelEdit();
|
|
23054
23713
|
return;
|
|
23055
23714
|
}
|
|
23056
|
-
|
|
23715
|
+
const flag = flagForKey(e);
|
|
23716
|
+
if (flag) {
|
|
23057
23717
|
e.preventDefault();
|
|
23058
|
-
const flag = e.key.toLowerCase() === "b" ? "bold" : "italic";
|
|
23059
23718
|
handleStyleToggle(flag);
|
|
23060
23719
|
}
|
|
23061
23720
|
};
|
|
@@ -23067,18 +23726,21 @@ function useTextEdit(opts) {
|
|
|
23067
23726
|
};
|
|
23068
23727
|
const onBeforeInput = (ie) => {
|
|
23069
23728
|
if (ie.inputType !== "insertText" || !ie.data) return;
|
|
23070
|
-
const
|
|
23071
|
-
const pendingItalic = overlay.dataset.pendingItalic === "1";
|
|
23729
|
+
const pending = FLAG_SHORTCUTS.map((s) => s.flag).filter((flag) => overlay.dataset[pendingKey(flag)] === "1");
|
|
23072
23730
|
ie.preventDefault();
|
|
23073
23731
|
const sel2 = window.getSelection();
|
|
23074
23732
|
if (!sel2 || sel2.rangeCount === 0) return;
|
|
23075
23733
|
const range2 = sel2.getRangeAt(0);
|
|
23076
23734
|
range2.deleteContents();
|
|
23077
|
-
if (
|
|
23735
|
+
if (pending.length > 0) {
|
|
23078
23736
|
const span = document.createElement("span");
|
|
23079
23737
|
span.setAttribute("data-run", "");
|
|
23080
|
-
if (
|
|
23081
|
-
if (
|
|
23738
|
+
if (pending.includes("bold")) span.style.fontWeight = "700";
|
|
23739
|
+
if (pending.includes("italic")) span.style.fontStyle = "italic";
|
|
23740
|
+
const decorations = [];
|
|
23741
|
+
if (pending.includes("underline")) decorations.push("underline");
|
|
23742
|
+
if (pending.includes("strikethrough")) decorations.push("line-through");
|
|
23743
|
+
if (decorations.length > 0) span.style.textDecoration = decorations.join(" ");
|
|
23082
23744
|
span.textContent = ie.data;
|
|
23083
23745
|
range2.insertNode(span);
|
|
23084
23746
|
const after = document.createRange();
|
|
@@ -23086,8 +23748,7 @@ function useTextEdit(opts) {
|
|
|
23086
23748
|
after.collapse(true);
|
|
23087
23749
|
sel2.removeAllRanges();
|
|
23088
23750
|
sel2.addRange(after);
|
|
23089
|
-
delete overlay.dataset
|
|
23090
|
-
delete overlay.dataset.pendingItalic;
|
|
23751
|
+
for (const flag of pending) delete overlay.dataset[pendingKey(flag)];
|
|
23091
23752
|
} else {
|
|
23092
23753
|
const textNode = document.createTextNode(ie.data);
|
|
23093
23754
|
range2.insertNode(textNode);
|
|
@@ -23306,6 +23967,95 @@ function useSceneTextEdit(scene, container, options = {}) {
|
|
|
23306
23967
|
return { ...edit, onDoubleClick };
|
|
23307
23968
|
}
|
|
23308
23969
|
|
|
23970
|
+
// src/core/gradient.ts
|
|
23971
|
+
function sampleGradientStops(stops, t) {
|
|
23972
|
+
if (stops.length === 0) return "rgba(0,0,0,0)";
|
|
23973
|
+
const sorted = [...stops].sort((a, b) => a.offset - b.offset);
|
|
23974
|
+
if (sorted.length === 1) return sorted[0].color;
|
|
23975
|
+
const first = sorted[0];
|
|
23976
|
+
const last = sorted[sorted.length - 1];
|
|
23977
|
+
if (t <= first.offset) return first.color;
|
|
23978
|
+
if (t >= last.offset) return last.color;
|
|
23979
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
23980
|
+
const lo = sorted[i];
|
|
23981
|
+
const hi = sorted[i + 1];
|
|
23982
|
+
if (t < lo.offset || t > hi.offset) continue;
|
|
23983
|
+
const span = hi.offset - lo.offset;
|
|
23984
|
+
if (span <= 0) return hi.color;
|
|
23985
|
+
return lerpColor(lo.color, hi.color, (t - lo.offset) / span);
|
|
23986
|
+
}
|
|
23987
|
+
return last.color;
|
|
23988
|
+
}
|
|
23989
|
+
function lerpColor(a, b, t) {
|
|
23990
|
+
const [ar, ag, ab, aa] = hexToRgba(a);
|
|
23991
|
+
const [br, bg, bb, ba] = hexToRgba(b);
|
|
23992
|
+
return rgbaToHex([
|
|
23993
|
+
ar + (br - ar) * t,
|
|
23994
|
+
ag + (bg - ag) * t,
|
|
23995
|
+
ab + (bb - ab) * t,
|
|
23996
|
+
aa + (ba - aa) * t
|
|
23997
|
+
]);
|
|
23998
|
+
}
|
|
23999
|
+
function withGradientKind(fill, kind) {
|
|
24000
|
+
if (fill.fill === kind) return fill;
|
|
24001
|
+
const common = { stops: fill.stops, units: fill.units, opacity: fill.opacity };
|
|
24002
|
+
const { center, radius, angle } = gradientGeometry(fill);
|
|
24003
|
+
if (kind === "linear-gradient") {
|
|
24004
|
+
const dx = Math.cos(angle) * radius;
|
|
24005
|
+
const dy = Math.sin(angle) * radius;
|
|
24006
|
+
return {
|
|
24007
|
+
fill: "linear-gradient",
|
|
24008
|
+
from: { x: center.x - dx, y: center.y - dy },
|
|
24009
|
+
to: { x: center.x + dx, y: center.y + dy },
|
|
24010
|
+
...common
|
|
24011
|
+
};
|
|
24012
|
+
}
|
|
24013
|
+
if (kind === "radial-gradient") {
|
|
24014
|
+
return { fill: "radial-gradient", center, radius, ...common };
|
|
24015
|
+
}
|
|
24016
|
+
return { fill: "conic-gradient", center, angle, ...common };
|
|
24017
|
+
}
|
|
24018
|
+
function gradientGeometry(fill) {
|
|
24019
|
+
if (fill.fill === "linear-gradient") {
|
|
24020
|
+
const dx = fill.to.x - fill.from.x;
|
|
24021
|
+
const dy = fill.to.y - fill.from.y;
|
|
24022
|
+
return {
|
|
24023
|
+
center: { x: (fill.from.x + fill.to.x) / 2, y: (fill.from.y + fill.to.y) / 2 },
|
|
24024
|
+
radius: Math.hypot(dx, dy) / 2,
|
|
24025
|
+
angle: Math.atan2(dy, dx)
|
|
24026
|
+
};
|
|
24027
|
+
}
|
|
24028
|
+
if (fill.fill === "radial-gradient") {
|
|
24029
|
+
return { center: fill.center, radius: fill.radius, angle: 0 };
|
|
24030
|
+
}
|
|
24031
|
+
return { center: fill.center, radius: conicArmRadius(fill.units), angle: fill.angle };
|
|
24032
|
+
}
|
|
24033
|
+
function conicArmRadius(units) {
|
|
24034
|
+
return units === "bounds" ? 0.5 : 50;
|
|
24035
|
+
}
|
|
24036
|
+
function gradientForBounds(kind, bounds, stops, units) {
|
|
24037
|
+
const center = { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
|
|
24038
|
+
if (kind === "linear-gradient") {
|
|
24039
|
+
return {
|
|
24040
|
+
fill: "linear-gradient",
|
|
24041
|
+
from: { x: bounds.x, y: center.y },
|
|
24042
|
+
to: { x: bounds.x + bounds.width, y: center.y },
|
|
24043
|
+
stops,
|
|
24044
|
+
units
|
|
24045
|
+
};
|
|
24046
|
+
}
|
|
24047
|
+
if (kind === "radial-gradient") {
|
|
24048
|
+
return {
|
|
24049
|
+
fill: "radial-gradient",
|
|
24050
|
+
center,
|
|
24051
|
+
radius: Math.max(bounds.width, bounds.height) / 2,
|
|
24052
|
+
stops,
|
|
24053
|
+
units
|
|
24054
|
+
};
|
|
24055
|
+
}
|
|
24056
|
+
return { fill: "conic-gradient", center, angle: 0, stops, units };
|
|
24057
|
+
}
|
|
24058
|
+
|
|
23309
24059
|
// src/core/ops/setText.ts
|
|
23310
24060
|
function createSetTextOp(args) {
|
|
23311
24061
|
const { id, from, to, label, coalesceKey = `setText:${id}` } = args;
|
|
@@ -24370,7 +25120,7 @@ function arrayAdapter(config) {
|
|
|
24370
25120
|
poseBounds = defaultPoseBounds2,
|
|
24371
25121
|
intersectsRect,
|
|
24372
25122
|
nextId = defaultNextId,
|
|
24373
|
-
translatePose:
|
|
25123
|
+
translatePose: translatePose3 = defaultTranslatePose
|
|
24374
25124
|
} = config;
|
|
24375
25125
|
const getSelection = selectionRef ? () => selectionRef.current : () => [];
|
|
24376
25126
|
const adapter = {
|
|
@@ -24446,7 +25196,7 @@ function arrayAdapter(config) {
|
|
|
24446
25196
|
for (const n of src) {
|
|
24447
25197
|
const cloned = JSON.parse(JSON.stringify(n));
|
|
24448
25198
|
cloned.id = nextId();
|
|
24449
|
-
const translated =
|
|
25199
|
+
const translated = translatePose3(toPose(cloned), dx, dy);
|
|
24450
25200
|
out.push(fromPose(cloned, translated));
|
|
24451
25201
|
}
|
|
24452
25202
|
return out;
|
|
@@ -24640,8 +25390,8 @@ function bestAxis(b, axis, anchors, candidates, worldTolerance) {
|
|
|
24640
25390
|
return { delta: bestGuide ? bestDelta : 0, guide: bestGuide };
|
|
24641
25391
|
}
|
|
24642
25392
|
function matchAlignment(bounds, candidates, worldTolerance, anchors) {
|
|
24643
|
-
const x = bestAxis(bounds, "x", anchors.x, candidates, worldTolerance);
|
|
24644
|
-
const y = bestAxis(bounds, "y", anchors.y, candidates, worldTolerance);
|
|
25393
|
+
const x = bestAxis(bounds, "x", anchors.x, candidates, worldTolerance.x);
|
|
25394
|
+
const y = bestAxis(bounds, "y", anchors.y, candidates, worldTolerance.y);
|
|
24645
25395
|
return { dx: x.delta, dy: y.delta, activeX: x.guide, activeY: y.guide };
|
|
24646
25396
|
}
|
|
24647
25397
|
|
|
@@ -24649,7 +25399,7 @@ function matchAlignment(bounds, candidates, worldTolerance, anchors) {
|
|
|
24649
25399
|
var activeList = (m) => [m.activeX, m.activeY].filter((g) => g !== null);
|
|
24650
25400
|
function worldTol(base) {
|
|
24651
25401
|
const t = base.tolerance ?? 6;
|
|
24652
|
-
return base.getView ? t
|
|
25402
|
+
return base.getView ? pxExtent(t, base.getView().scale) : { x: t, y: t };
|
|
24653
25403
|
}
|
|
24654
25404
|
function alignMoveBehavior(args) {
|
|
24655
25405
|
const proj = args.projection ?? RECT_ALIGN_PROJECTION;
|
|
@@ -26870,6 +27620,24 @@ function snapPoint(opts) {
|
|
|
26870
27620
|
};
|
|
26871
27621
|
}
|
|
26872
27622
|
|
|
26873
|
-
|
|
26874
|
-
|
|
26875
|
-
|
|
27623
|
+
// src/contributions/merge.ts
|
|
27624
|
+
function mergeContributions(...bundles) {
|
|
27625
|
+
const out = [];
|
|
27626
|
+
const seen = /* @__PURE__ */ new Set();
|
|
27627
|
+
for (const bundle of bundles) {
|
|
27628
|
+
for (const entry of bundle) {
|
|
27629
|
+
if (seen.has(entry.id)) {
|
|
27630
|
+
throw new Error(
|
|
27631
|
+
`mergeContributions: duplicate contribution id "${entry.id}". Two bundles registered the same id; rename one or merge them by hand.`
|
|
27632
|
+
);
|
|
27633
|
+
}
|
|
27634
|
+
seen.add(entry.id);
|
|
27635
|
+
out.push(entry);
|
|
27636
|
+
}
|
|
27637
|
+
}
|
|
27638
|
+
return out;
|
|
27639
|
+
}
|
|
27640
|
+
|
|
27641
|
+
export { ALWAYS, ANCHOR_HIT_BASE_PX, ActionsProviderIfRoot, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, BUNDLE_TOOLS, COARSE_TARGET_SCALE, CURVE_REPS, ColorOverrideRegistry, CropIcon, CursorCoordsHud, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_THEME, DEFAULT_DEVICE_PROFILE, DEFAULT_FILL_COLOR, DEFAULT_HANDLE_SIZE2 as DEFAULT_HANDLE_SIZE, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_STROKE_COLOR, DEFAULT_TEXT_STYLE, DEFAULT_VELOCITY_DECAY, DepRegistryProviderIfRoot, DeviceProfileProvider, DivideIcon, EASINGS, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, GHOST_STROKE, HANDLE_BASE_PX, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_POSE_COMPOSITION, 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, TextIcon, UnionIcon, UnknownIcon, VERSION, WeaselProvider, WeaselRenderer, aabbCenter, actionIs, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, alignedStrokeRect, always, and, animateLifecycle, animateOnSetPose, annulusSemiAxes, applyBooleanOp, applyHitExistingGate, applyStyleToRange, areaSelectAction, arrayAdapter, asNodeId, bezierCubic, bezierQuadratic, 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, computeFitView2 as computeFitView, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDispatcher, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createMarkdownRenderer, createMoveToIndexOp, createNodeProperties, createNodeRouting, createParallaxLayer, createPathAnchorAffordances, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createTextLayer, createViewportLayer, cycleVertexColors, decomposePath, decomposeRectPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, deriveAlignmentGuides, deriveParallaxView, describeRule, domPositionToCharOffset, domToRuns, drawLayers, 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, easeOutCubic2 as easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, enterTextEditAction, enumerateAnchors, evaluate, fillInPoseFrame, fillToBoundsFrame, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, fontString, forEachCoalesced, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getNodeShapes, getStylusData, gradientForBounds, gradientGeometry, hexToRgba, hitAffordanceRegions, hitAnchor, hitRotationHandle, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, isEditableTarget2 as isEditableTarget, isPatternSpec, lassoSelectAction, layoutMarkdown, lerpColorArray, lerpOklab, lerpOklch, linear, liveScope, makeViewportZoomAction, markdownToRuns, mat3, matchAlignment, matchesKeyBinding, meanScale, measureText, measureTextBounds, measuredWidth, mergeAlphaFromPrev, mergeContributions, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, nodeHasFlag, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, or, 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, registerNodeShape, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveDeviceProfile, resolveFillPattern, resolvePatternSpec, resolveRuns, resolveTextStyle, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, runsToMarkdown, runsToPlainText, sampleGradientStops, sceneFromJSON, sceneNodeClientRect, sceneToAdapter, scopeBindings, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, setFlagOverRange, shapeCoversPoint, sliceAction, snapPoint, solidVertexColors, specificity, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, startThresholdDrag, styleAtRange, subscribeImageReady, tessellate, tessellateStroke, textCommand, textLineBoxes, tileGrid, toHex8, toRuns, translatePoseViaDescriptor, translateRectPose, tweenPose, tweenVertexColors, 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, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, 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, useViewTween, useZoom, verticalAlignOffset, viewToMat3, viewToTransform, viewportDragPanAction, viewportZoomAction, when, withAlpha01, withCoord, withGradientKind, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
|
|
27642
|
+
//# sourceMappingURL=chunk-D2C6K6FO.js.map
|
|
27643
|
+
//# sourceMappingURL=chunk-D2C6K6FO.js.map
|