@weasel-js/core 0.7.2 → 0.8.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/dist/{DrawCommand-CJtqqt8H.d.ts → DrawCommand-BKJ5JPkC.d.ts} +24 -1
  3. package/dist/{chunk-SYM6RAM4.js → chunk-6DAUIMTO.js} +3 -2
  4. package/dist/chunk-6DAUIMTO.js.map +1 -0
  5. package/dist/{chunk-VOVKONXA.js → chunk-BPRHGTLQ.js} +3 -3
  6. package/dist/{chunk-VOVKONXA.js.map → chunk-BPRHGTLQ.js.map} +1 -1
  7. package/dist/{chunk-BGGZ4CVF.js → chunk-CMBE45MT.js} +3 -3
  8. package/dist/{chunk-BGGZ4CVF.js.map → chunk-CMBE45MT.js.map} +1 -1
  9. package/dist/{chunk-5F3PIERN.js → chunk-DLV5F4NY.js} +520 -120
  10. package/dist/chunk-DLV5F4NY.js.map +1 -0
  11. package/dist/{chunk-CQNKCG34.js → chunk-GLZLSPGJ.js} +3 -3
  12. package/dist/chunk-GLZLSPGJ.js.map +1 -0
  13. package/dist/clipboard.d.ts +2 -2
  14. package/dist/clone.d.ts +1 -1
  15. package/dist/{geometry-D9BDMiQi.d.ts → geometry-DVeZ2i-c.d.ts} +1 -1
  16. package/dist/{grid-CaSK9bHV.d.ts → grid-XcFQVS0f.d.ts} +1 -1
  17. package/dist/{index-DOYRTfP0.d.ts → index-Clf_koyR.d.ts} +178 -6
  18. package/dist/index.d.ts +135 -31
  19. package/dist/index.js +5 -5
  20. package/dist/insert.d.ts +2 -2
  21. package/dist/insert.js +1 -1
  22. package/dist/move.d.ts +3 -3
  23. package/dist/move.js +2 -2
  24. package/dist/{options-DMWeTELe.d.ts → options-wAvCnOZd.d.ts} +1 -1
  25. package/dist/{pointSnapToGrid-C3EruUwt.d.ts → pointSnapToGrid-BuDZWBrL.d.ts} +2 -2
  26. package/dist/renderer.d.ts +5 -5
  27. package/dist/renderer.js +5 -5
  28. package/dist/resize.d.ts +3 -3
  29. package/dist/resize.js +2 -2
  30. package/dist/routing.d.ts +6 -6
  31. package/dist/routing.js +1 -1
  32. package/dist/{types-Dcaa0tPq.d.ts → types-BhJJ2OCM.d.ts} +9 -1
  33. package/dist/{types-Cpb4hii1.d.ts → types-DUpI7Apc.d.ts} +3 -95
  34. package/dist/{viewToMat3-D4lrBigW.d.ts → viewToMat3-DFMdZvVl.d.ts} +1 -1
  35. package/package.json +6 -6
  36. package/dist/chunk-5F3PIERN.js.map +0 -1
  37. package/dist/chunk-CQNKCG34.js.map +0 -1
  38. package/dist/chunk-SYM6RAM4.js.map +0 -1
  39. /package/dist/{types-B6MMiodD.d.ts → path-B6MMiodD.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
- import { defineViewportTool, reportRouteConflicts, defineTool, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from './chunk-SYM6RAM4.js';
2
- import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-VOVKONXA.js';
3
- import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-BGGZ4CVF.js';
4
- import { AUTO_POSE_DESCRIPTOR, RECT_POSE_DESCRIPTOR, resolveUnit, meanScale, pathPoseDescriptor, polygonFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, strokeHitTest, boundsOfPath, PATH_M, PATH_L, PATH_Z, PATH_Q, PATH_C, DEFAULT_FLATTEN_TOLERANCE, flattenQuadratic, flattenCubic, PathBuilder, pointInPath, transformPath, flattenCubicWithArcLen, flattenQuadraticWithArcLen, translatePath, PATH_CMD_LENGTHS, pointToGridCell } from './chunk-CQNKCG34.js';
1
+ import { defineViewportTool, reportRouteConflicts, defineTool, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from './chunk-6DAUIMTO.js';
2
+ import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-BPRHGTLQ.js';
3
+ import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-CMBE45MT.js';
4
+ import { AUTO_POSE_DESCRIPTOR, RECT_POSE_DESCRIPTOR, resolveUnit, meanScale, pathPoseDescriptor, polygonFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, strokeHitTest, boundsOfPath, PATH_M, PATH_L, PATH_Z, PATH_Q, PATH_C, DEFAULT_FLATTEN_TOLERANCE, flattenQuadratic, flattenCubic, PathBuilder, pointInPath, transformPath, flattenCubicWithArcLen, flattenQuadraticWithArcLen, translatePath, PATH_CMD_LENGTHS, pointToGridCell } from './chunk-GLZLSPGJ.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';
@@ -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.7.2" ;
22
+ var VERSION = "0.8.0" ;
23
23
 
24
24
  // src/features/grid/roundToCell.ts
25
25
  function roundToCell(value, cellSize) {
@@ -1501,6 +1501,16 @@ function extractPolygon(p, opts) {
1501
1501
  out.push(next);
1502
1502
  return next;
1503
1503
  };
1504
+ const dropDuplicateClosingPoint = () => {
1505
+ if (!pts || !aA || !aB || !aT) return;
1506
+ const n = pts.length / 2;
1507
+ if (n < 2) return;
1508
+ if (pts[0] !== pts[(n - 1) * 2] || pts[1] !== pts[(n - 1) * 2 + 1]) return;
1509
+ pts.length -= 2;
1510
+ aA.length -= 1;
1511
+ aB.length -= 1;
1512
+ aT.length -= 1;
1513
+ };
1504
1514
  const commit = (target) => {
1505
1515
  if (!pts || !aA || !aB || !aT) return;
1506
1516
  target.anchorA = new Uint32Array(aA);
@@ -1584,7 +1594,10 @@ function extractPolygon(p, opts) {
1584
1594
  break;
1585
1595
  }
1586
1596
  case PATH_Z: {
1587
- if (current) current.closed = true;
1597
+ if (current) {
1598
+ current.closed = true;
1599
+ dropDuplicateClosingPoint();
1600
+ }
1588
1601
  break;
1589
1602
  }
1590
1603
  default:
@@ -2067,8 +2080,20 @@ function sameFill(a, b) {
2067
2080
  }
2068
2081
  return false;
2069
2082
  }
2070
- function groupKey(family, weight, style, synthetic, fill, source, page) {
2071
- return `${family}|${weight}|${style}|${synthetic.bold ? 1 : 0}${synthetic.italic ? 1 : 0}|${fillKey(fill)}|${source}|${page}`;
2083
+ function strokeKey(s) {
2084
+ if (!s) return "-";
2085
+ return [
2086
+ fillKey(s.paint),
2087
+ s.width ?? 1,
2088
+ s.join ?? "miter",
2089
+ s.cap ?? "butt",
2090
+ s.miterLimit ?? "",
2091
+ s.align ?? "center",
2092
+ (s.dash ?? []).join(",")
2093
+ ].join(":");
2094
+ }
2095
+ function groupKey(family, weight, style, synthetic, fill, stroke, source, page) {
2096
+ return `${family}|${weight}|${style}|${synthetic.bold ? 1 : 0}${synthetic.italic ? 1 : 0}|${fillKey(fill)}|${strokeKey(stroke)}|${source}|${page}`;
2072
2097
  }
2073
2098
  function getOrCreateGroup(ctx, run, resolved, page, sourceOverride) {
2074
2099
  const atlasFamily = resolved.resolved.family;
@@ -2081,6 +2106,7 @@ function getOrCreateGroup(ctx, run, resolved, page, sourceOverride) {
2081
2106
  resolvedStyle,
2082
2107
  resolved.synthetic,
2083
2108
  run.fill,
2109
+ source === "outline" ? run.stroke : void 0,
2084
2110
  source,
2085
2111
  page
2086
2112
  );
@@ -2094,6 +2120,9 @@ function getOrCreateGroup(ctx, run, resolved, page, sourceOverride) {
2094
2120
  source,
2095
2121
  page,
2096
2122
  fill: run.fill,
2123
+ // Only the outline tier can paint it, and carrying it on an SDF group
2124
+ // would be a promise the renderer cannot keep.
2125
+ ...source === "outline" && run.stroke !== void 0 ? { stroke: run.stroke } : {},
2097
2126
  quads: [],
2098
2127
  glyphs: []
2099
2128
  };
@@ -2698,6 +2727,36 @@ function outlineMesh(key, d) {
2698
2727
  return mesh;
2699
2728
  }
2700
2729
 
2730
+ // src/renderer/cache/outlineStrokeMeshCache.ts
2731
+ var OUTLINE_STROKE_MESH_CACHE_LIMIT = 2048;
2732
+ var WIDTH_QUANTUM = 1 / 1024;
2733
+ var cache4 = /* @__PURE__ */ new Map();
2734
+ function quantizeEmWidth(emWidth) {
2735
+ return Math.round(emWidth / WIDTH_QUANTUM) * WIDTH_QUANTUM;
2736
+ }
2737
+ function outlineStrokeMesh(glyphKey, d, emWidth, stroke) {
2738
+ if (!(emWidth > 0)) return null;
2739
+ const key = [
2740
+ glyphKey,
2741
+ emWidth,
2742
+ stroke.join ?? "miter",
2743
+ stroke.cap ?? "butt",
2744
+ stroke.miterLimit ?? "",
2745
+ stroke.align ?? "center",
2746
+ (stroke.dash ?? []).join(",")
2747
+ ].join("|");
2748
+ const cached = cache4.get(key);
2749
+ if (cached !== void 0) return cached.indices.length === 0 ? null : cached;
2750
+ if (cache4.size >= OUTLINE_STROKE_MESH_CACHE_LIMIT) cache4 = /* @__PURE__ */ new Map();
2751
+ const mesh = tessellateStroke(
2752
+ pathFromD(d),
2753
+ { ...stroke, width: emWidth },
2754
+ { flattenTolerance: OUTLINE_FLATTEN_TOLERANCE }
2755
+ );
2756
+ cache4.set(key, mesh);
2757
+ return mesh.indices.length === 0 ? null : mesh;
2758
+ }
2759
+
2701
2760
  // src/renderer/draw.ts
2702
2761
  function setColorMatrixUniforms(ctx, prog) {
2703
2762
  const gl = ctx.gl;
@@ -3272,16 +3331,30 @@ function drawText(ctx, cmd) {
3272
3331
  var SYNTHETIC_ITALIC_RADIANS = 0.2094;
3273
3332
  function drawTextOutlineGroup(ctx, group, dy) {
3274
3333
  const mesh = outlineGroupMesh(group, dy);
3275
- if (!mesh) return;
3276
- drawPathFillByKind(ctx, group.fill, ctx.meshCache.uploadTransient(mesh));
3334
+ if (mesh) drawPathFillByKind(ctx, group.fill, ctx.meshCache.uploadTransient(mesh));
3335
+ if (!group.stroke) return;
3336
+ const strokeMesh = outlineGroupStrokeMesh(group, dy);
3337
+ if (strokeMesh) {
3338
+ drawPathFillByKind(ctx, group.stroke.paint, ctx.meshCache.uploadTransient(strokeMesh));
3339
+ }
3277
3340
  }
3278
3341
  function outlineGroupMesh(group, dy) {
3342
+ return mergeGlyphMeshes(group, dy, (glyph) => outlineMesh(glyph.key, glyph.d));
3343
+ }
3344
+ function outlineGroupStrokeMesh(group, dy) {
3345
+ const stroke = group.stroke;
3346
+ if (!stroke) return null;
3347
+ const width = stroke.width ?? 1;
3348
+ if (!(width > 0)) return null;
3349
+ return mergeGlyphMeshes(group, dy, (glyph) => glyph.scale > 0 ? outlineStrokeMesh(glyph.key, glyph.d, quantizeEmWidth(width / glyph.scale), stroke) : null);
3350
+ }
3351
+ function mergeGlyphMeshes(group, dy, meshFor) {
3279
3352
  const parts = [];
3280
3353
  let vertexFloats = 0;
3281
3354
  let indexCount = 0;
3282
3355
  for (const glyph of group.glyphs) {
3283
- const mesh = outlineMesh(glyph.key, glyph.d);
3284
- if (mesh.indices.length === 0) continue;
3356
+ const mesh = meshFor(glyph);
3357
+ if (!mesh || mesh.indices.length === 0) continue;
3285
3358
  parts.push({ mesh, glyph });
3286
3359
  vertexFloats += mesh.vertices.length;
3287
3360
  indexCount += mesh.indices.length;
@@ -4731,16 +4804,115 @@ function startThresholdDrag(e, opts) {
4731
4804
  document.addEventListener("pointercancel", onCancel);
4732
4805
  return { isDragging: () => activated };
4733
4806
  }
4807
+
4808
+ // src/core/device/profile.ts
4809
+ var COARSE_TARGET_SCALE = 1.75;
4810
+ function resolveDeviceProfile(detected, overrides) {
4811
+ const coarsePointer2 = overrides?.coarsePointer ?? detected.coarsePointer;
4812
+ const canHover2 = overrides?.canHover ?? detected.canHover;
4813
+ const dpr = overrides?.dpr ?? detected.dpr;
4814
+ const targetScale = overrides?.targetScale ?? (coarsePointer2 ? COARSE_TARGET_SCALE : 1);
4815
+ return { coarsePointer: coarsePointer2, canHover: canHover2, dpr, targetScale };
4816
+ }
4817
+ var DEFAULT_DEVICE_PROFILE = resolveDeviceProfile({
4818
+ coarsePointer: false,
4819
+ canHover: true,
4820
+ dpr: 1
4821
+ });
4822
+
4823
+ // src/core/device/useDeviceProfile.ts
4824
+ var COARSE_QUERY = "(pointer: coarse)";
4825
+ var HOVER_QUERY = "(hover: hover)";
4826
+ function hasMatchMedia() {
4827
+ return typeof window !== "undefined" && typeof window.matchMedia === "function";
4828
+ }
4829
+ function detectOnce() {
4830
+ const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
4831
+ if (!hasMatchMedia()) {
4832
+ return {
4833
+ coarsePointer: DEFAULT_DEVICE_PROFILE.coarsePointer,
4834
+ canHover: DEFAULT_DEVICE_PROFILE.canHover,
4835
+ dpr
4836
+ };
4837
+ }
4838
+ return {
4839
+ coarsePointer: window.matchMedia(COARSE_QUERY).matches,
4840
+ canHover: window.matchMedia(HOVER_QUERY).matches,
4841
+ dpr
4842
+ };
4843
+ }
4844
+ function useDetectedFacts() {
4845
+ const [facts, setFacts] = useState(detectOnce);
4846
+ useEffect(() => {
4847
+ if (!hasMatchMedia()) return;
4848
+ const coarseMq = window.matchMedia(COARSE_QUERY);
4849
+ const hoverMq = window.matchMedia(HOVER_QUERY);
4850
+ const onCoarse = (e) => setFacts((f) => ({ ...f, coarsePointer: e.matches }));
4851
+ const onHover = (e) => setFacts((f) => ({ ...f, canHover: e.matches }));
4852
+ coarseMq.addEventListener("change", onCoarse);
4853
+ hoverMq.addEventListener("change", onHover);
4854
+ let disposed = false;
4855
+ let densityMq = null;
4856
+ const onDensity = () => {
4857
+ if (disposed) return;
4858
+ const next = window.devicePixelRatio || 1;
4859
+ setFacts((f) => f.dpr === next ? f : { ...f, dpr: next });
4860
+ arm();
4861
+ };
4862
+ function arm() {
4863
+ if (disposed) return;
4864
+ densityMq?.removeEventListener("change", onDensity);
4865
+ densityMq = window.matchMedia(
4866
+ `(resolution: ${window.devicePixelRatio || 1}dppx)`
4867
+ );
4868
+ densityMq.addEventListener("change", onDensity);
4869
+ }
4870
+ arm();
4871
+ setFacts({
4872
+ coarsePointer: coarseMq.matches,
4873
+ canHover: hoverMq.matches,
4874
+ dpr: window.devicePixelRatio || 1
4875
+ });
4876
+ return () => {
4877
+ disposed = true;
4878
+ coarseMq.removeEventListener("change", onCoarse);
4879
+ hoverMq.removeEventListener("change", onHover);
4880
+ densityMq?.removeEventListener("change", onDensity);
4881
+ };
4882
+ }, []);
4883
+ return facts;
4884
+ }
4885
+ var DeviceProfileContext = createContext(null);
4886
+ function useDeviceProfile(overrides) {
4887
+ const provided = useContext(DeviceProfileContext);
4888
+ const detected = useDetectedFacts();
4889
+ const base = provided ?? detected;
4890
+ const { coarsePointer: coarsePointer2, canHover: canHover2, dpr, targetScale } = overrides ?? {};
4891
+ return useMemo(
4892
+ () => resolveDeviceProfile(base, {
4893
+ ...coarsePointer2 !== void 0 ? { coarsePointer: coarsePointer2 } : {},
4894
+ ...canHover2 !== void 0 ? { canHover: canHover2 } : {},
4895
+ ...dpr !== void 0 ? { dpr } : {},
4896
+ ...targetScale !== void 0 ? { targetScale } : {}
4897
+ }),
4898
+ [base.coarsePointer, base.canHover, base.dpr, coarsePointer2, canHover2, dpr, targetScale]
4899
+ );
4900
+ }
4901
+ function DeviceProfileProvider({ value, children }) {
4902
+ const profile = useDeviceProfile(value);
4903
+ return createElement(DeviceProfileContext.Provider, { value: profile }, children);
4904
+ }
4905
+
4906
+ // src/core/viewport/useCanvasSize.ts
4734
4907
  function useCanvasSize(containerRef) {
4735
- const [size, setSize] = useState({ width: 0, height: 0, dpr: 1 });
4908
+ const { dpr } = useDeviceProfile();
4909
+ const [size, setSize] = useState({ width: 0, height: 0 });
4736
4910
  const measure3 = useCallback(() => {
4737
4911
  if (!containerRef.current) return;
4738
4912
  const rect = containerRef.current.getBoundingClientRect();
4739
- setSize({
4740
- width: rect.width,
4741
- height: rect.height,
4742
- dpr: window.devicePixelRatio || 1
4743
- });
4913
+ setSize(
4914
+ (prev) => prev.width === rect.width && prev.height === rect.height ? prev : { width: rect.width, height: rect.height }
4915
+ );
4744
4916
  }, [containerRef]);
4745
4917
  useEffect(() => {
4746
4918
  measure3();
@@ -4750,9 +4922,14 @@ function useCanvasSize(containerRef) {
4750
4922
  }
4751
4923
  return () => observer.disconnect();
4752
4924
  }, [measure3, containerRef]);
4753
- return size;
4925
+ return { width: size.width, height: size.height, dpr };
4754
4926
  }
4755
4927
 
4928
+ // src/core/device/targets.ts
4929
+ var HANDLE_BASE_PX = 8;
4930
+ var ANCHOR_HIT_BASE_PX = 8;
4931
+ var ROTATION_HANDLE_BASE_PX = 24;
4932
+
4756
4933
  // src/core/viewport/fitToBounds.ts
4757
4934
  function fitZoom(availW, availH, contentW, contentH, clamp2) {
4758
4935
  const raw = Math.min(availW / contentW, availH / contentH);
@@ -6812,6 +6989,12 @@ var clearSelectionAction = {
6812
6989
  }
6813
6990
  };
6814
6991
 
6992
+ // src/interactions/actions/defaults/freshNodeId.ts
6993
+ var counter = 0;
6994
+ function freshNodeId() {
6995
+ return asNodeId(`n${(counter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);
6996
+ }
6997
+
6815
6998
  // src/interactions/actions/defaults/clone.ts
6816
6999
  function translatePose(pose, dx, dy) {
6817
7000
  const p = pose;
@@ -6821,10 +7004,6 @@ function translatePose(pose, dx, dy) {
6821
7004
  y: (p["y"] ?? 0) + dy
6822
7005
  };
6823
7006
  }
6824
- var cloneIdCounter = 0;
6825
- function freshCloneId() {
6826
- return asNodeId(`n${(cloneIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);
6827
- }
6828
7007
  var cloneAction = {
6829
7008
  id: "clone",
6830
7009
  label: "Clone",
@@ -6891,7 +7070,7 @@ var cloneAction = {
6891
7070
  if (origin === void 0 || !originNode) continue;
6892
7071
  const newPose = translatePose(origin, dx, dy);
6893
7072
  const node = {
6894
- id: freshCloneId(),
7073
+ id: freshNodeId(),
6895
7074
  kind: originNode.kind,
6896
7075
  layer: originNode.layer,
6897
7076
  pose: newPose,
@@ -7754,6 +7933,125 @@ var pinchZoomAction = {
7754
7933
  enabled: () => true
7755
7934
  };
7756
7935
 
7936
+ // src/interactions/actions/defaults/delete.ts
7937
+ function hostIndex(scene, id) {
7938
+ const node = scene.get(id);
7939
+ const siblings = node?.parent == null ? scene.roots : scene.childrenOf(node.parent);
7940
+ return siblings.indexOf(id);
7941
+ }
7942
+ function hasSelectedAncestor(scene, id, set) {
7943
+ let parent = scene.get(id)?.parent ?? null;
7944
+ while (parent != null) {
7945
+ if (set.has(parent)) return true;
7946
+ parent = scene.get(parent)?.parent ?? null;
7947
+ }
7948
+ return false;
7949
+ }
7950
+ function buildDeleteOps(scene, ids, label) {
7951
+ const set = new Set(ids);
7952
+ const ops = [];
7953
+ for (const id of ids) {
7954
+ const node = scene.get(id);
7955
+ if (!node) continue;
7956
+ if (hasSelectedAncestor(scene, id, set)) continue;
7957
+ ops.push(createDeleteOp({
7958
+ node,
7959
+ index: hostIndex(scene, id),
7960
+ label
7961
+ }));
7962
+ }
7963
+ return ops;
7964
+ }
7965
+ var deleteAction = {
7966
+ id: "delete",
7967
+ label: "Delete",
7968
+ // Suppressed while any tool is mid-gesture — accidentally hitting
7969
+ // Delete during a drag shouldn't wipe the selection out from under
7970
+ // the in-flight handle.
7971
+ defaultBinding: {
7972
+ kind: "key",
7973
+ key: ["Delete", "Backspace"],
7974
+ phase: [{ channel: "*", phase: "initial" }]
7975
+ },
7976
+ eligible: { capability: "edits-page" },
7977
+ requires: ["scene", "selection", "applyOps"],
7978
+ invoker: {
7979
+ timing: "immediate",
7980
+ run: (deps) => {
7981
+ const selection = deps.selection;
7982
+ const scene = deps.scene;
7983
+ const applyOps = deps.applyOps;
7984
+ if (!selection || !scene) return;
7985
+ const ids = selection.get();
7986
+ if (ids.length === 0) return;
7987
+ const ops = buildDeleteOps(scene, ids, "Delete");
7988
+ if (ops.length > 0) {
7989
+ if (applyOps) applyOps(ops, "Delete");
7990
+ else scene.applyBatch(ops, "Delete", defaultCommitAdapter(scene));
7991
+ }
7992
+ selection.set([]);
7993
+ }
7994
+ },
7995
+ enabled: () => true
7996
+ };
7997
+
7998
+ // src/interactions/actions/defaults/requiresSelection.ts
7999
+ function requiresSelection(deps) {
8000
+ const selection = deps?.selection;
8001
+ if (!selection || selection.get().length === 0) {
8002
+ return ActionDisabledReason.SelectionRequired;
8003
+ }
8004
+ return true;
8005
+ }
8006
+
8007
+ // src/interactions/actions/defaults/clipboard.ts
8008
+ var clipboardCopyAction = {
8009
+ id: "clipboard.copy",
8010
+ label: "Copy",
8011
+ defaultBinding: { kind: "key", key: "c", mods: { mod: true } },
8012
+ // Same gate as `duplicate`: a mode that can neither edit the page nor own a
8013
+ // selection has nothing to copy. Notably excludes `text-edit`, where Cmd+C
8014
+ // must stay the browser's own copy of the caret range.
8015
+ eligible: { capability: ["edits-page", "creates-selection"] },
8016
+ // `selection` is read by the `enabled` gate, not the invoker — the
8017
+ // dispatcher builds deps for both, and an undeclared read is undefined.
8018
+ requires: ["clipboard", "selection"],
8019
+ invoker: {
8020
+ timing: "immediate",
8021
+ run: (deps) => {
8022
+ deps.clipboard?.copy();
8023
+ }
8024
+ },
8025
+ enabled: requiresSelection
8026
+ };
8027
+ var clipboardCutAction = {
8028
+ id: "clipboard.cut",
8029
+ label: "Cut",
8030
+ defaultBinding: { kind: "key", key: "x", mods: { mod: true } },
8031
+ eligible: { capability: "edits-page" },
8032
+ requires: ["clipboard", "scene", "selection", "applyOps"],
8033
+ invoker: {
8034
+ timing: "immediate",
8035
+ run: (deps) => {
8036
+ const clipboard = deps.clipboard;
8037
+ const selection = deps.selection;
8038
+ const scene = deps.scene;
8039
+ const applyOps = deps.applyOps;
8040
+ if (!clipboard || !selection || !scene) return;
8041
+ const ids = selection.get();
8042
+ if (ids.length === 0) return;
8043
+ clipboard.copy();
8044
+ const ops = buildDeleteOps(scene, ids, "Cut");
8045
+ if (ops.length > 0) {
8046
+ if (applyOps) applyOps(ops, "Cut");
8047
+ else scene.applyBatch(ops, "Cut", defaultCommitAdapter(scene));
8048
+ }
8049
+ selection.set([]);
8050
+ }
8051
+ },
8052
+ enabled: requiresSelection
8053
+ };
8054
+
7757
8055
  // src/interactions/actions/defaults/enterTextEdit.ts
7758
8056
  var enterTextEditAction = {
7759
8057
  id: "enterTextEdit",
@@ -7886,25 +8184,46 @@ var selectAllAction = {
7886
8184
  }
7887
8185
  };
7888
8186
 
7889
- // src/interactions/actions/defaults/delete.ts
7890
- function hostIndex(scene, id) {
8187
+ // src/interactions/actions/defaults/duplicate.ts
8188
+ var DUPLICATE_OFFSET = 12;
8189
+ function translatePose2(pose, dx, dy) {
8190
+ const p = pose;
8191
+ return { ...p, x: (p["x"] ?? 0) + dx, y: (p["y"] ?? 0) + dy };
8192
+ }
8193
+ function hasSelectedAncestor2(scene, id, set) {
8194
+ let parent = scene.get(id)?.parent ?? null;
8195
+ while (parent != null) {
8196
+ if (set.has(parent)) return true;
8197
+ parent = scene.get(parent)?.parent ?? null;
8198
+ }
8199
+ return false;
8200
+ }
8201
+ function copySubtree(scene, id, parent, offset, offsetDescendants, ops) {
7891
8202
  const node = scene.get(id);
7892
- const siblings = node?.parent == null ? scene.roots : scene.childrenOf(node.parent);
7893
- return siblings.indexOf(id);
8203
+ if (!node) return null;
8204
+ const newId = freshNodeId();
8205
+ const copy = {
8206
+ ...node,
8207
+ id: newId,
8208
+ pose: translatePose2(node.pose, offset.dx, offset.dy),
8209
+ parent
8210
+ };
8211
+ ops.push(createInsertOp({ node: copy, label: "Duplicate" }));
8212
+ const childOffset = offsetDescendants ? offset : { dx: 0, dy: 0 };
8213
+ for (const child of scene.childrenOf(id)) {
8214
+ copySubtree(scene, child, newId, childOffset, offsetDescendants, ops);
8215
+ }
8216
+ return newId;
7894
8217
  }
7895
- var deleteAction = {
7896
- id: "delete",
7897
- label: "Delete",
7898
- // Suppressed while any tool is mid-gesture accidentally hitting
7899
- // Delete during a drag shouldn't wipe the selection out from under
7900
- // the in-flight handle.
7901
- defaultBinding: {
7902
- kind: "key",
7903
- key: ["Delete", "Backspace"],
7904
- phase: [{ channel: "*", phase: "initial" }]
7905
- },
7906
- eligible: { capability: "edits-page" },
7907
- requires: ["scene", "selection", "applyOps"],
8218
+ var duplicateAction = {
8219
+ id: "duplicate",
8220
+ label: "Duplicate",
8221
+ defaultBinding: { kind: "key", key: "d", mods: { mod: true } },
8222
+ eligible: { capability: ["edits-page", "creates-selection"] },
8223
+ // `selection` is read by the `enabled` gate as well as the invoker. An
8224
+ // undeclared read throws inside the dev-build deps Proxy before the gate can
8225
+ // answer, which is how this action spent a while doing nothing at all.
8226
+ requires: ["scene", "selection", "applyOps", "poseComposition"],
7908
8227
  invoker: {
7909
8228
  timing: "immediate",
7910
8229
  run: (deps) => {
@@ -7914,44 +8233,22 @@ var deleteAction = {
7914
8233
  if (!selection || !scene) return;
7915
8234
  const ids = selection.get();
7916
8235
  if (ids.length === 0) return;
8236
+ const set = new Set(ids);
8237
+ const offset = { dx: DUPLICATE_OFFSET, dy: DUPLICATE_OFFSET };
7917
8238
  const ops = [];
8239
+ const newIds = [];
7918
8240
  for (const id of ids) {
7919
- const node = scene.get(id);
8241
+ const nid = asNodeId(id);
8242
+ const node = scene.get(nid);
7920
8243
  if (!node) continue;
7921
- ops.push(createDeleteOp({
7922
- node,
7923
- index: hostIndex(scene, id),
7924
- label: "Delete"
7925
- }));
8244
+ if (hasSelectedAncestor2(scene, nid, set)) continue;
8245
+ const newId = copySubtree(scene, nid, node.parent ?? null, offset, deps.poseComposition === void 0, ops);
8246
+ if (newId) newIds.push(newId);
7926
8247
  }
7927
- if (ops.length > 0) {
7928
- if (applyOps) applyOps(ops, "Delete");
7929
- else scene.applyBatch(ops, "Delete", defaultCommitAdapter(scene));
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) => {
8248
+ if (ops.length === 0) return;
8249
+ if (applyOps) applyOps(ops, "Duplicate");
8250
+ else scene.applyBatch(ops, "Duplicate", defaultCommitAdapter(scene));
8251
+ selection.set(newIds);
7955
8252
  }
7956
8253
  },
7957
8254
  enabled: requiresSelection
@@ -10021,7 +10318,8 @@ function resolveTextStyle(style) {
10021
10318
  selectionColor: style.selectionColor ?? null,
10022
10319
  letterSpacing: style.letterSpacing ?? DEFAULT_TEXT_STYLE.letterSpacing,
10023
10320
  underline: style.underline ?? DEFAULT_TEXT_STYLE.underline,
10024
- strikethrough: style.strikethrough ?? DEFAULT_TEXT_STYLE.strikethrough
10321
+ strikethrough: style.strikethrough ?? DEFAULT_TEXT_STYLE.strikethrough,
10322
+ ...style.stroke !== void 0 ? { stroke: style.stroke } : {}
10025
10323
  };
10026
10324
  }
10027
10325
  function fontString(s) {
@@ -10047,6 +10345,9 @@ function resolveRuns(runs, style) {
10047
10345
  fontWeight: run.bold ? 700 : baseWeight,
10048
10346
  fontStyle: run.italic ? "italic" : style.fontStyle,
10049
10347
  fill: run.fill ?? style.fill,
10348
+ // Unlike the decorations below, a run's stroke *replaces* the node's
10349
+ // rather than adding to it — there is only one outline to paint.
10350
+ ...(run.stroke ?? style.stroke) !== void 0 ? { stroke: run.stroke ?? style.stroke } : {},
10050
10351
  letterSpacing: run.letterSpacing ?? style.letterSpacing,
10051
10352
  // `||`, not `??`: run-level decorations are additive over the node style.
10052
10353
  underline: run.underline || style.underline,
@@ -10435,7 +10736,9 @@ var KIT_STANDARD_DESCRIPTORS = [
10435
10736
  setStrokeAction,
10436
10737
  setFillOpacityAction,
10437
10738
  setStrokeOpacityAction,
10438
- ingestAction
10739
+ ingestAction,
10740
+ clipboardCopyAction,
10741
+ clipboardCutAction
10439
10742
  ];
10440
10743
  function useStandardActions(opts) {
10441
10744
  const optsRef = useRef(opts);
@@ -10607,6 +10910,14 @@ function evaluateSelector(s, ctx) {
10607
10910
  const resizable2 = ctx.selectionResizable ?? true;
10608
10911
  if (resizable2 !== s.resizable) return false;
10609
10912
  }
10913
+ if (s.coarsePointer !== void 0) {
10914
+ const device = ctx.device ?? DEFAULT_DEVICE_PROFILE;
10915
+ if (device.coarsePointer !== s.coarsePointer) return false;
10916
+ }
10917
+ if (s.canHover !== void 0) {
10918
+ const device = ctx.device ?? DEFAULT_DEVICE_PROFILE;
10919
+ if (device.canHover !== s.canHover) return false;
10920
+ }
10610
10921
  return true;
10611
10922
  }
10612
10923
  var MAX_RULE_DEPTH = 10;
@@ -10702,6 +11013,8 @@ var capabilityIs = (cap) => cond({ capability: cap });
10702
11013
  var capabilityIn = (caps) => cond({ capability: { in: caps } });
10703
11014
  var capabilityAll = (caps) => cond({ capability: [...caps] });
10704
11015
  var capabilityNot = (cap) => cond({ capability: { not: cap } });
11016
+ var coarsePointer = cond({ coarsePointer: true });
11017
+ var canHover = cond({ canHover: true });
10705
11018
  var DEFAULT_ALLOWED_CAPABILITIES = /* @__PURE__ */ new Set([
10706
11019
  ...NORMAL.allows,
10707
11020
  ...IMPLICIT_TAGS
@@ -10718,7 +11031,8 @@ function buildRuleCtx(args) {
10718
11031
  mode: args.mode,
10719
11032
  allowedCapabilities: args.allowedCapabilities,
10720
11033
  selectionResizable: args.selectionResizable,
10721
- editingAnchors: args.editingAnchors
11034
+ editingAnchors: args.editingAnchors,
11035
+ device: args.device
10722
11036
  };
10723
11037
  }
10724
11038
 
@@ -11634,6 +11948,44 @@ function useGestureDispatcher(opts) {
11634
11948
  const DRAG_THRESHOLD_PX = 4;
11635
11949
  const bufferedDown = /* @__PURE__ */ new Map();
11636
11950
  const lastPointerDown = /* @__PURE__ */ new Map();
11951
+ const LONG_PRESS_MS = 500;
11952
+ const longPressTimers = /* @__PURE__ */ new Map();
11953
+ const cancelLongPress = (pointerId) => {
11954
+ const t = longPressTimers.get(pointerId);
11955
+ if (t !== void 0) {
11956
+ clearTimeout(t);
11957
+ longPressTimers.delete(pointerId);
11958
+ }
11959
+ };
11960
+ const cancelAllLongPress = () => {
11961
+ for (const t of longPressTimers.values()) clearTimeout(t);
11962
+ longPressTimers.clear();
11963
+ };
11964
+ const fireLongPress = (pointerId) => {
11965
+ const down = lastPointerDown.get(pointerId);
11966
+ if (!down) return;
11967
+ const shared = {
11968
+ target: down.target,
11969
+ altKey: down.altKey,
11970
+ ctrlKey: down.ctrlKey,
11971
+ metaKey: down.metaKey,
11972
+ shiftKey: down.shiftKey,
11973
+ ...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
11974
+ ...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
11975
+ };
11976
+ const result = dispatch2({
11977
+ kind: "longpress",
11978
+ x: down.worldX,
11979
+ y: down.worldY,
11980
+ clientX: down.clientX,
11981
+ clientY: down.clientY,
11982
+ ...down.affordance !== void 0 ? { affordance: down.affordance } : {},
11983
+ ...shared
11984
+ });
11985
+ if (result === "unhandled") {
11986
+ dispatch2({ kind: "contextmenu", ...shared });
11987
+ }
11988
+ };
11637
11989
  const DOUBLE_CLICK_MAX_MS = 600;
11638
11990
  const DOUBLE_CLICK_MAX_PX = 8;
11639
11991
  const onKeyDown = (e) => {
@@ -11737,6 +12089,7 @@ function useGestureDispatcher(opts) {
11737
12089
  bufferedDown.set(e.pointerId, { ev, clientX: e.clientX, clientY: e.clientY });
11738
12090
  dispatch2({ ...ev, stage: "press" });
11739
12091
  lastPointerDown.set(e.pointerId, {
12092
+ target: e.target,
11740
12093
  clientX: e.clientX,
11741
12094
  clientY: e.clientY,
11742
12095
  worldX: w.x,
@@ -11749,7 +12102,19 @@ function useGestureDispatcher(opts) {
11749
12102
  metaKey: e.metaKey,
11750
12103
  shiftKey: e.shiftKey
11751
12104
  });
12105
+ if ((e.pointerType === "touch" || e.pointerType === "pen") && activePointers.size === 1) {
12106
+ cancelLongPress(e.pointerId);
12107
+ longPressTimers.set(
12108
+ e.pointerId,
12109
+ setTimeout(() => {
12110
+ longPressTimers.delete(e.pointerId);
12111
+ if (disposed) return;
12112
+ fireLongPress(e.pointerId);
12113
+ }, LONG_PRESS_MS)
12114
+ );
12115
+ }
11752
12116
  if (activePointers.size >= 2) {
12117
+ cancelAllLongPress();
11753
12118
  for (const id of activePointers) bufferedDown.delete(id);
11754
12119
  const { centroid, spread } = computeMultiTouchGeometry(pointerPositions);
11755
12120
  if (!multiTouchStart) {
@@ -11780,6 +12145,7 @@ function useGestureDispatcher(opts) {
11780
12145
  const dy = e.clientY - buffered.clientY;
11781
12146
  if (Math.hypot(dx, dy) >= DRAG_THRESHOLD_PX) {
11782
12147
  bufferedDown.delete(e.pointerId);
12148
+ cancelLongPress(e.pointerId);
11783
12149
  dispatch2(buffered.ev);
11784
12150
  }
11785
12151
  }
@@ -11893,6 +12259,7 @@ function useGestureDispatcher(opts) {
11893
12259
  clearHoverCursor();
11894
12260
  };
11895
12261
  const onPointerUp = (e) => {
12262
+ cancelLongPress(e.pointerId);
11896
12263
  const prevSize = activePointers.size;
11897
12264
  activePointers.delete(e.pointerId);
11898
12265
  pointerPositions.delete(e.pointerId);
@@ -11997,6 +12364,7 @@ function useGestureDispatcher(opts) {
11997
12364
  }, 0);
11998
12365
  };
11999
12366
  const onPointerCancel = (e) => {
12367
+ cancelLongPress(e.pointerId);
12000
12368
  activePointers.delete(e.pointerId);
12001
12369
  pointerPositions.delete(e.pointerId);
12002
12370
  lastPointerDown.delete(e.pointerId);
@@ -12119,6 +12487,7 @@ function useGestureDispatcher(opts) {
12119
12487
  window.removeEventListener("paste", onPaste);
12120
12488
  canvas?.classList.remove(DROPOVER_CLASS);
12121
12489
  clearHoverCursor();
12490
+ cancelAllLongPress();
12122
12491
  disposed = true;
12123
12492
  dispatcher.cancelAll("cancel");
12124
12493
  };
@@ -13208,7 +13577,7 @@ function textLineBoxes(pose, opts = {}) {
13208
13577
  }
13209
13578
 
13210
13579
  // src/features/images/imageCache.ts
13211
- var cache4 = /* @__PURE__ */ new Map();
13580
+ var cache5 = /* @__PURE__ */ new Map();
13212
13581
  var subscribers = /* @__PURE__ */ new Set();
13213
13582
  var defaultLoader = async (src) => {
13214
13583
  if (typeof Image === "undefined" || typeof createImageBitmap === "undefined") {
@@ -13226,28 +13595,28 @@ function notify() {
13226
13595
  }
13227
13596
  function startLoad(src) {
13228
13597
  const entry = { status: "loading" };
13229
- cache4.set(src, entry);
13598
+ cache5.set(src, entry);
13230
13599
  loader(src).then(
13231
13600
  (bitmap) => {
13232
- if (cache4.get(src) !== entry) return;
13233
- cache4.set(src, { status: "ready", bitmap });
13601
+ if (cache5.get(src) !== entry) return;
13602
+ cache5.set(src, { status: "ready", bitmap });
13234
13603
  notify();
13235
13604
  },
13236
13605
  () => {
13237
- if (cache4.get(src) !== entry) return;
13238
- cache4.set(src, { status: "error" });
13606
+ if (cache5.get(src) !== entry) return;
13607
+ cache5.set(src, { status: "error" });
13239
13608
  notify();
13240
13609
  }
13241
13610
  );
13242
13611
  }
13243
13612
  function getImageBitmap(src) {
13244
- const entry = cache4.get(src);
13613
+ const entry = cache5.get(src);
13245
13614
  if (entry) return entry.bitmap;
13246
13615
  startLoad(src);
13247
13616
  return void 0;
13248
13617
  }
13249
13618
  function imageStatus(src) {
13250
- return cache4.get(src)?.status ?? "idle";
13619
+ return cache5.get(src)?.status ?? "idle";
13251
13620
  }
13252
13621
  function subscribeImageReady(cb) {
13253
13622
  subscribers.add(cb);
@@ -13348,6 +13717,13 @@ function shapeCoversPoint(node, pose, x, y, opts = {}) {
13348
13717
  function getNodeShapes() {
13349
13718
  return [...PAINTERS.high, ...PAINTERS.normal];
13350
13719
  }
13720
+ function withLeafStroke(style, stroke, strokeWidth) {
13721
+ if (style?.stroke !== void 0) return style;
13722
+ if (!stroke || stroke === "none") return style;
13723
+ const width = strokeWidth ?? 1;
13724
+ if (width <= 0) return style;
13725
+ return { ...style, stroke: { paint: { color: stroke }, width } };
13726
+ }
13351
13727
  var TEXT_PAINTER = {
13352
13728
  id: "kit:text",
13353
13729
  matches: (node) => {
@@ -13366,7 +13742,8 @@ var TEXT_PAINTER = {
13366
13742
  const d = node.data;
13367
13743
  const p = pose;
13368
13744
  const y = p.y;
13369
- return d.runs && d.runs.length > 0 ? [textCommandFromRuns(p.x, y, d.runs, d.style, void 0, p.height)] : [textCommand(p.x, y, d.text, d.style, void 0, p.height)];
13745
+ const style = withLeafStroke(d.style, d.stroke, d.strokeWidth);
13746
+ 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
13747
  }),
13371
13748
  // The pose is a *wrap box*, not a bounding box — "Away" in a 300-unit box
13372
13749
  // leaves most of it empty, and a pose-rect silhouette claims all of it. The
@@ -15548,7 +15925,7 @@ function buildChromeState(args) {
15548
15925
  }
15549
15926
 
15550
15927
  // src/interactions/actions/rotate/handle.ts
15551
- var DEFAULT_ROTATION_HANDLE_DISTANCE = 24;
15928
+ var DEFAULT_ROTATION_HANDLE_DISTANCE = ROTATION_HANDLE_BASE_PX;
15552
15929
  function rotationHandle(pose, distance = DEFAULT_ROTATION_HANDLE_DISTANCE) {
15553
15930
  const rotation = pose.rotation ?? 0;
15554
15931
  const c = aabbCenter(pose);
@@ -15663,7 +16040,7 @@ var DEFAULT_HANDLE_OUTLINE = {
15663
16040
  paint: { fill: "solid", color: "#1a130d" },
15664
16041
  width: 1
15665
16042
  };
15666
- var DEFAULT_HANDLE_SIZE = 8;
16043
+ var DEFAULT_HANDLE_SIZE = HANDLE_BASE_PX;
15667
16044
  function defaultHandlesOf(b) {
15668
16045
  return [
15669
16046
  { x: b.x, y: b.y },
@@ -19168,8 +19545,8 @@ var DEFAULT_FILL3 = "#d4c4a8";
19168
19545
  var DEFAULT_STROKE = "#1a130d";
19169
19546
  function createCornerResizeAffordance(opts = {}) {
19170
19547
  const {
19171
- handleHitRadius = 8,
19172
- handleSize = 8,
19548
+ handleHitRadius = HANDLE_BASE_PX,
19549
+ handleSize = HANDLE_BASE_PX,
19173
19550
  fill = DEFAULT_FILL3,
19174
19551
  stroke = DEFAULT_STROKE
19175
19552
  } = opts;
@@ -19311,8 +19688,8 @@ function createPathAnchorAffordances(getAnchorState, opts = {}) {
19311
19688
  }
19312
19689
 
19313
19690
  // src/canvas/affordanceAt.ts
19314
- var HANDLE_HIT_RADIUS = 8;
19315
- var ANCHOR_HIT_RADIUS = 8;
19691
+ var HANDLE_HIT_RADIUS = HANDLE_BASE_PX;
19692
+ var ANCHOR_HIT_RADIUS = ANCHOR_HIT_BASE_PX;
19316
19693
  function buildAffordanceAt(opts) {
19317
19694
  const {
19318
19695
  getChromeState,
@@ -20584,7 +20961,7 @@ function makeGetNodeAtPoint(pickEvery, nodeResolver) {
20584
20961
  return { id, kind, pose, data };
20585
20962
  };
20586
20963
  }
20587
- var DEFAULT_HANDLE_SIZE2 = 8;
20964
+ var DEFAULT_HANDLE_SIZE2 = HANDLE_BASE_PX;
20588
20965
  var IS_DEV = (() => {
20589
20966
  try {
20590
20967
  return Boolean(import.meta.env?.DEV);
@@ -20603,10 +20980,10 @@ function recordCoordTrace(entry) {
20603
20980
  coordTrace.push(entry);
20604
20981
  if (coordTrace.length > COORD_TRACE_LIMIT) coordTrace.shift();
20605
20982
  }
20606
- function mergeLayersWithDefaults(user) {
20983
+ function mergeLayersWithDefaults(user, targetScale = 1) {
20607
20984
  const defaults = {
20608
20985
  scene: { drawOne: defaultDrawOne },
20609
- selectionOverlay: { handles: { size: DEFAULT_HANDLE_SIZE2 } }
20986
+ selectionOverlay: { handles: { size: HANDLE_BASE_PX * targetScale } }
20610
20987
  };
20611
20988
  if (!user) return defaults;
20612
20989
  const result = { ...user };
@@ -20694,8 +21071,10 @@ function SceneCanvasInner(props, ref) {
20694
21071
  chromeVisibility,
20695
21072
  getActiveMode,
20696
21073
  getFocused: getFocusedProp,
21074
+ device,
20697
21075
  ...rest
20698
21076
  } = props;
21077
+ const deviceProfile = useDeviceProfile(device);
20699
21078
  const isSerialized = (s) => typeof s === "object" && s != null && s.version === 1 && Array.isArray(s.nodes);
20700
21079
  const [bakedScene] = useState(
20701
21080
  () => isSerialized(sceneInput) ? sceneFromJSON(sceneInput, {}) : null
@@ -20813,7 +21192,7 @@ function SceneCanvasInner(props, ref) {
20813
21192
  }
20814
21193
  }), []);
20815
21194
  const selectToolWithDefaults = useMemo(() => ({
20816
- handleHitRadius: DEFAULT_HANDLE_SIZE2,
21195
+ handleHitRadius: HANDLE_BASE_PX * deviceProfile.targetScale,
20817
21196
  // Shift-click belongs to the anchor selection while a path is being
20818
21197
  // anchor-edited; see `UseSelectToolOptions.extendClickLocked`.
20819
21198
  extendClickLocked: () => effectivePathEditingId() !== "",
@@ -20823,7 +21202,7 @@ function SceneCanvasInner(props, ref) {
20823
21202
  // pipeline (audit 3.4). The classifier is now `select.pick`, which
20824
21203
  // declares that capability itself — one rule, evaluated in one place.
20825
21204
  ...selectToolOpts
20826
- }), [selectToolOpts]);
21205
+ }), [selectToolOpts, deviceProfile.targetScale]);
20827
21206
  const selectionRef = useRef(selection);
20828
21207
  selectionRef.current = selection;
20829
21208
  const kindClassifier = useMemo(() => {
@@ -20981,8 +21360,8 @@ function SceneCanvasInner(props, ref) {
20981
21360
  };
20982
21361
  }, [Boolean(onDoubleClick)]);
20983
21362
  const mergedLayers = useMemo(
20984
- () => mergeLayersWithDefaults(layers),
20985
- [layers]
21363
+ () => mergeLayersWithDefaults(layers, deviceProfile.targetScale),
21364
+ [layers, deviceProfile.targetScale]
20986
21365
  );
20987
21366
  const resolvedActions = useMemo(() => {
20988
21367
  if (selectToolOpts?.rotate !== false) return actions;
@@ -21041,6 +21420,8 @@ function SceneCanvasInner(props, ref) {
21041
21420
  chromeVisibilityRef.current = effectiveChromeVisibility;
21042
21421
  const getActiveModeRef = useRef(getActiveMode);
21043
21422
  getActiveModeRef.current = getActiveMode;
21423
+ const deviceProfileRef = useRef(deviceProfile);
21424
+ deviceProfileRef.current = deviceProfile;
21044
21425
  const resizablePredRef = useRef(selectToolOpts?.resize?.resizable);
21045
21426
  resizablePredRef.current = selectToolOpts?.resize?.resizable;
21046
21427
  const buildCurrentRuleCtx = useCallback(() => {
@@ -21062,7 +21443,8 @@ function SceneCanvasInner(props, ref) {
21062
21443
  mode: modeInfo.id,
21063
21444
  allowedCapabilities: modeInfo.allowedCapabilities,
21064
21445
  selectionResizable,
21065
- editingAnchors: effectivePathEditingId() !== ""
21446
+ editingAnchors: effectivePathEditingId() !== "",
21447
+ device: deviceProfileRef.current
21066
21448
  };
21067
21449
  }, [dispatcher, getHover]);
21068
21450
  const anchorEditingAllowed = useMemo(
@@ -21317,7 +21699,7 @@ function SceneCanvasInner(props, ref) {
21317
21699
  testHookRef.current?._markReady();
21318
21700
  }
21319
21701
  }, []);
21320
- return /* @__PURE__ */ jsx(DepRegistryProviderIfRoot, { children: /* @__PURE__ */ jsx(PointerProviderIfRoot, { children: /* @__PURE__ */ jsxs(ActionsProviderIfRoot, { children: [
21702
+ return /* @__PURE__ */ jsx(DeviceProfileProvider, { value: device, children: /* @__PURE__ */ jsx(DepRegistryProviderIfRoot, { children: /* @__PURE__ */ jsx(PointerProviderIfRoot, { children: /* @__PURE__ */ jsxs(ActionsProviderIfRoot, { children: [
21321
21703
  canvas,
21322
21704
  /* @__PURE__ */ jsx(PointerPublisher, { canvasRef: internalCanvasRef, viewRef: currentViewRef }),
21323
21705
  /* @__PURE__ */ jsx(
@@ -21380,7 +21762,7 @@ function SceneCanvasInner(props, ref) {
21380
21762
  }
21381
21763
  ),
21382
21764
  children
21383
- ] }) }) });
21765
+ ] }) }) }) });
21384
21766
  }
21385
21767
  function ToolKeybindingsMounter({
21386
21768
  internalTools,
@@ -22760,7 +23142,8 @@ var STYLE_KEYS = [
22760
23142
  "fontFamily",
22761
23143
  "fontSize",
22762
23144
  "letterSpacing",
22763
- "fill"
23145
+ "fill",
23146
+ "stroke"
22764
23147
  ];
22765
23148
  var FLAG_KEYS = /* @__PURE__ */ new Set([
22766
23149
  "bold",
@@ -22788,8 +23171,9 @@ function valueAt(run, key) {
22788
23171
  if (FLAG_KEYS.has(key)) return value ?? false;
22789
23172
  return value;
22790
23173
  }
23174
+ var STRUCTURAL_KEYS = /* @__PURE__ */ new Set(["fill", "stroke"]);
22791
23175
  function sameValue(key, a, b) {
22792
- return key === "fill" ? deepEqual(a, b) : Object.is(a, b);
23176
+ return STRUCTURAL_KEYS.has(key) ? deepEqual(a, b) : Object.is(a, b);
22793
23177
  }
22794
23178
  function sameStyle(a, b) {
22795
23179
  return STYLE_KEYS.every((key) => sameValue(key, valueAt(a, key), valueAt(b, key)));
@@ -22884,6 +23268,20 @@ function applyStyleToRange(runs, start, end, patch) {
22884
23268
  }
22885
23269
 
22886
23270
  // src/features/text/useTextEdit.ts
23271
+ var FLAG_SHORTCUTS = [
23272
+ { key: "b", shift: false, flag: "bold" },
23273
+ { key: "i", shift: false, flag: "italic" },
23274
+ { key: "u", shift: false, flag: "underline" },
23275
+ { key: "x", shift: true, flag: "strikethrough" }
23276
+ ];
23277
+ function flagForKey(e) {
23278
+ if (!e.metaKey && !e.ctrlKey) return null;
23279
+ const key = e.key.toLowerCase();
23280
+ return FLAG_SHORTCUTS.find((s) => s.key === key && s.shift === e.shiftKey)?.flag ?? null;
23281
+ }
23282
+ function pendingKey(flag) {
23283
+ return `pending${flag[0].toUpperCase()}${flag.slice(1)}`;
23284
+ }
22887
23285
  function toggleFlagInRange(runs, start, end, flag) {
22888
23286
  const current = styleAtRange(runs, start, end)[flag];
22889
23287
  return applyStyleToRange(runs, start, end, { [flag]: current !== true });
@@ -23035,7 +23433,7 @@ function useTextEdit(opts) {
23035
23433
  syncSelection();
23036
23434
  }
23037
23435
  function togglePending(flag) {
23038
- const key = flag === "bold" ? "pendingBold" : "pendingItalic";
23436
+ const key = pendingKey(flag);
23039
23437
  if (overlay.dataset[key] === "1") {
23040
23438
  delete overlay.dataset[key];
23041
23439
  } else {
@@ -23053,9 +23451,9 @@ function useTextEdit(opts) {
23053
23451
  cancelEdit();
23054
23452
  return;
23055
23453
  }
23056
- if ((e.metaKey || e.ctrlKey) && (e.key === "b" || e.key === "i" || e.key === "B" || e.key === "I")) {
23454
+ const flag = flagForKey(e);
23455
+ if (flag) {
23057
23456
  e.preventDefault();
23058
- const flag = e.key.toLowerCase() === "b" ? "bold" : "italic";
23059
23457
  handleStyleToggle(flag);
23060
23458
  }
23061
23459
  };
@@ -23067,18 +23465,21 @@ function useTextEdit(opts) {
23067
23465
  };
23068
23466
  const onBeforeInput = (ie) => {
23069
23467
  if (ie.inputType !== "insertText" || !ie.data) return;
23070
- const pendingBold = overlay.dataset.pendingBold === "1";
23071
- const pendingItalic = overlay.dataset.pendingItalic === "1";
23468
+ const pending = FLAG_SHORTCUTS.map((s) => s.flag).filter((flag) => overlay.dataset[pendingKey(flag)] === "1");
23072
23469
  ie.preventDefault();
23073
23470
  const sel2 = window.getSelection();
23074
23471
  if (!sel2 || sel2.rangeCount === 0) return;
23075
23472
  const range2 = sel2.getRangeAt(0);
23076
23473
  range2.deleteContents();
23077
- if (pendingBold || pendingItalic) {
23474
+ if (pending.length > 0) {
23078
23475
  const span = document.createElement("span");
23079
23476
  span.setAttribute("data-run", "");
23080
- if (pendingBold) span.style.fontWeight = "700";
23081
- if (pendingItalic) span.style.fontStyle = "italic";
23477
+ if (pending.includes("bold")) span.style.fontWeight = "700";
23478
+ if (pending.includes("italic")) span.style.fontStyle = "italic";
23479
+ const decorations = [];
23480
+ if (pending.includes("underline")) decorations.push("underline");
23481
+ if (pending.includes("strikethrough")) decorations.push("line-through");
23482
+ if (decorations.length > 0) span.style.textDecoration = decorations.join(" ");
23082
23483
  span.textContent = ie.data;
23083
23484
  range2.insertNode(span);
23084
23485
  const after = document.createRange();
@@ -23086,8 +23487,7 @@ function useTextEdit(opts) {
23086
23487
  after.collapse(true);
23087
23488
  sel2.removeAllRanges();
23088
23489
  sel2.addRange(after);
23089
- delete overlay.dataset.pendingBold;
23090
- delete overlay.dataset.pendingItalic;
23490
+ for (const flag of pending) delete overlay.dataset[pendingKey(flag)];
23091
23491
  } else {
23092
23492
  const textNode = document.createTextNode(ie.data);
23093
23493
  range2.insertNode(textNode);
@@ -24370,7 +24770,7 @@ function arrayAdapter(config) {
24370
24770
  poseBounds = defaultPoseBounds2,
24371
24771
  intersectsRect,
24372
24772
  nextId = defaultNextId,
24373
- translatePose: translatePose2 = defaultTranslatePose
24773
+ translatePose: translatePose3 = defaultTranslatePose
24374
24774
  } = config;
24375
24775
  const getSelection = selectionRef ? () => selectionRef.current : () => [];
24376
24776
  const adapter = {
@@ -24446,7 +24846,7 @@ function arrayAdapter(config) {
24446
24846
  for (const n of src) {
24447
24847
  const cloned = JSON.parse(JSON.stringify(n));
24448
24848
  cloned.id = nextId();
24449
- const translated = translatePose2(toPose(cloned), dx, dy);
24849
+ const translated = translatePose3(toPose(cloned), dx, dy);
24450
24850
  out.push(fromPose(cloned, translated));
24451
24851
  }
24452
24852
  return out;
@@ -26870,6 +27270,6 @@ function snapPoint(opts) {
26870
27270
  };
26871
27271
  }
26872
27272
 
26873
- export { ALWAYS, ActionDisabledReason, ActionsProvider, ActionsProviderIfRoot, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, BUNDLE_TOOLS, CURVE_REPS, ColorOverrideRegistry, CropIcon, CursorCoordsHud, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_THEME, 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, DepRegistryProvider, DepRegistryProviderIfRoot, DivideIcon, EASINGS, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, GHOST_STROKE, 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, 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, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, cloneAction, 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, evaluateEnabled, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, fontString, forEachCoalesced, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getNodeShapes, getStylusData, hexToRgba, hitAffordanceRegions, hitAnchor, hitRotationHandle, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, isEditableTarget2 as isEditableTarget, lassoSelectAction, layoutMarkdown, lerpColorArray, lerpOklab, lerpOklch, linear, makeViewportZoomAction, markdownToRuns, mat3, matchAlignment, matchesKeyBinding, measureText, measureTextBounds, measuredWidth, mergeAlphaFromPrev, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, 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, resolveRuns, resolveTextStyle, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, runsToMarkdown, runsToPlainText, sceneFromJSON, sceneNodeClientRect, sceneToAdapter, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, 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, useAction, useActionsRegistry, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDepRegistry, useDepSource, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, useOptionalActiveToolContext, useOptionalDepRegistry, 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, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
26874
- //# sourceMappingURL=chunk-5F3PIERN.js.map
26875
- //# sourceMappingURL=chunk-5F3PIERN.js.map
27273
+ export { ALWAYS, ANCHOR_HIT_BASE_PX, ActionDisabledReason, ActionsProvider, 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, DepRegistryProvider, 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, evaluateEnabled, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, fontString, forEachCoalesced, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getNodeShapes, getStylusData, hexToRgba, hitAffordanceRegions, hitAnchor, hitRotationHandle, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, isEditableTarget2 as isEditableTarget, lassoSelectAction, layoutMarkdown, lerpColorArray, lerpOklab, lerpOklch, linear, makeViewportZoomAction, markdownToRuns, mat3, matchAlignment, matchesKeyBinding, measureText, measureTextBounds, measuredWidth, mergeAlphaFromPrev, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, 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, resolveRuns, resolveTextStyle, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, runsToMarkdown, runsToPlainText, sceneFromJSON, sceneNodeClientRect, sceneToAdapter, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, 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, useAction, useActionsRegistry, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDepRegistry, useDepSource, useDeviceProfile, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, useOptionalActiveToolContext, useOptionalDepRegistry, 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, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
27274
+ //# sourceMappingURL=chunk-DLV5F4NY.js.map
27275
+ //# sourceMappingURL=chunk-DLV5F4NY.js.map