@pixerate/editor 0.39.0 → 0.40.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.
@@ -829,7 +829,11 @@ var capExtent = (style, width) => style.strokeLinecap === "round" || style.strok
829
829
  function arrowheadWeight(style, end) {
830
830
  const scale = end === "start" ? style.startArrowScale : style.endArrowScale;
831
831
  const s = typeof scale === "number" && scale > 0 && Number.isFinite(scale) ? scale : 1;
832
- return Math.max(0.25, (_nullishCoalesce(style.strokeWidth, () => ( 1))) * s);
832
+ const width = _nullishCoalesce(style.strokeWidth, () => ( 1));
833
+ const profile = strokeWidthProfileOf(style);
834
+ const at2 = profile ? widthProfileAt(profile, end === "start" ? 0 : 1) : null;
835
+ const here = at2 ? Math.max(width, width * (at2.left + at2.right) / 2) : width;
836
+ return Math.max(0.25, here * s);
833
837
  }
834
838
  function arrowheadInset(kind, hw, cap) {
835
839
  const l = 4 * hw;
@@ -4078,6 +4082,12 @@ function vectorStrokeWidthGeometry(node) {
4078
4082
  const parts = vectorStrokeParts(subpathsToBezier(node.subpaths), node);
4079
4083
  return parts.line.length ? { line: parts.line, unit: parts.drawWidth / 2 } : null;
4080
4084
  }
4085
+ function shiftWidthPoints(profile, indices, dt) {
4086
+ const moving = new Set(indices);
4087
+ const tagged = profile.map((p, i) => ({ p: moving.has(i) ? { ...p, t: Math.min(Math.max(p.t + dt, 0), 1) } : { ...p }, i }));
4088
+ tagged.sort((a, b) => a.p.t - b.p.t || a.i - b.i);
4089
+ return { points: tagged.map((x) => x.p), renumber: new Map(tagged.map((x, k) => [x.i, k])) };
4090
+ }
4081
4091
  function getVectorWidthHandles(state) {
4082
4092
  if (state.tool !== "width") return [];
4083
4093
  const ids = /* @__PURE__ */ new Set();
@@ -4087,7 +4097,7 @@ function getVectorWidthHandles(state) {
4087
4097
  }
4088
4098
  if (_optionalChain([state, 'access', _72 => _72.hover, 'optionalAccess', _73 => _73.nodeId])) ids.add(state.hover.nodeId);
4089
4099
  if (_optionalChain([state, 'access', _74 => _74.interaction, 'optionalAccess', _75 => _75.type]) === "width") ids.add(state.interaction.ref.nodeId);
4090
- if (state.selectedWidthPoint) ids.add(state.selectedWidthPoint.nodeId);
4100
+ for (const r of state.selectedWidthPoints) ids.add(r.nodeId);
4091
4101
  const out = [];
4092
4102
  for (const id of ids) {
4093
4103
  const node = findVectorNode(state.document.nodes, id);
@@ -4106,7 +4116,6 @@ function getVectorWidthHandles(state) {
4106
4116
  const p = applyMatrix(m, c);
4107
4117
  const l = applyMatrix(m, { x: c.x + n.x * w.left * g.unit, y: c.y + n.y * w.left * g.unit });
4108
4118
  const r = applyMatrix(m, { x: c.x - n.x * w.right * g.unit, y: c.y - n.y * w.right * g.unit });
4109
- const sel = state.selectedWidthPoint;
4110
4119
  out.push({
4111
4120
  ref: { nodeId: id, index, subpath: sp },
4112
4121
  x: p.x,
@@ -4116,7 +4125,7 @@ function getVectorWidthHandles(state) {
4116
4125
  t: w.t,
4117
4126
  leftScale: w.left,
4118
4127
  rightScale: w.right,
4119
- selected: !!sel && sel.nodeId === id && sel.index === index
4128
+ selected: state.selectedWidthPoints.some((r2) => r2.nodeId === id && r2.index === index)
4120
4129
  });
4121
4130
  });
4122
4131
  }
@@ -4312,43 +4321,44 @@ var VectorEditorController = (_class3 = class {
4312
4321
 
4313
4322
  __init9() {this.selection = []}
4314
4323
  __init10() {this.anchors = []}
4315
- __init11() {this.widthPoint = null}
4316
- __init12() {this.editingId = null}
4317
- __init13() {this.pen = null}
4324
+ __init11() {this.widthPoints = []}
4325
+ __init12() {this.widthEdit = null}
4326
+ __init13() {this.editingId = null}
4327
+ __init14() {this.pen = null}
4318
4328
 
4319
4329
 
4320
4330
 
4321
4331
 
4322
4332
 
4323
4333
 
4324
- __init14() {this.hover = null}
4325
- __init15() {this.pointer = null}
4326
- __init16() {this.drag = null}
4327
- __init17() {this.interaction = null}
4328
- __init18() {this.guides = null}
4334
+ __init15() {this.hover = null}
4335
+ __init16() {this.pointer = null}
4336
+ __init17() {this.drag = null}
4337
+ __init18() {this.interaction = null}
4338
+ __init19() {this.guides = null}
4329
4339
  /** Whether the last `snapDrawn` landed on an object (guides may be hidden). */
4330
- __init19() {this.snappedToObject = false}
4340
+ __init20() {this.snappedToObject = false}
4331
4341
  /** Drawing targets for hovering the pen and shape tools, kept while the document and the excluded path stay the same. */
4332
- __init20() {this.hoverTargets = null}
4333
- __init21() {this.spaceHeld = false}
4334
- __init22() {this.clipboard = null}
4342
+ __init21() {this.hoverTargets = null}
4343
+ __init22() {this.spaceHeld = false}
4344
+ __init23() {this.clipboard = null}
4335
4345
  /** Shape Builder map of the selection, kept while the document and the selection stay the same. */
4336
- __init23() {this.builderCache = null}
4346
+ __init24() {this.builderCache = null}
4337
4347
  /** Paint bucket map of the visible outlines. */
4338
- __init24() {this.fillCache = null}
4339
- __init25() {this.builderPreview = null}
4348
+ __init25() {this.fillCache = null}
4349
+ __init26() {this.builderPreview = null}
4340
4350
  /** What the highlight shows, to skip notifying when the pointer stays over the same face. */
4341
- __init26() {this.builderHoverKey = null}
4351
+ __init27() {this.builderHoverKey = null}
4342
4352
 
4343
4353
 
4344
- __init27() {this.listeners = /* @__PURE__ */ new Set()}
4345
- __init28() {this.cleanups = []}
4346
- __init29() {this.applying = false}
4354
+ __init28() {this.listeners = /* @__PURE__ */ new Set()}
4355
+ __init29() {this.cleanups = []}
4356
+ __init30() {this.applying = false}
4347
4357
  /** Set while a commit records a change that was already applied, so `restore` still reports it. */
4348
- __init30() {this.committing = false}
4349
- __init31() {this.revision = 0}
4358
+ __init31() {this.committing = false}
4359
+ __init32() {this.revision = 0}
4350
4360
 
4351
- constructor(options = {}) {;_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);_class3.prototype.__init17.call(this);_class3.prototype.__init18.call(this);_class3.prototype.__init19.call(this);_class3.prototype.__init20.call(this);_class3.prototype.__init21.call(this);_class3.prototype.__init22.call(this);_class3.prototype.__init23.call(this);_class3.prototype.__init24.call(this);_class3.prototype.__init25.call(this);_class3.prototype.__init26.call(this);_class3.prototype.__init27.call(this);_class3.prototype.__init28.call(this);_class3.prototype.__init29.call(this);_class3.prototype.__init30.call(this);_class3.prototype.__init31.call(this);_class3.prototype.__init32.call(this);_class3.prototype.__init33.call(this);
4361
+ constructor(options = {}) {;_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);_class3.prototype.__init17.call(this);_class3.prototype.__init18.call(this);_class3.prototype.__init19.call(this);_class3.prototype.__init20.call(this);_class3.prototype.__init21.call(this);_class3.prototype.__init22.call(this);_class3.prototype.__init23.call(this);_class3.prototype.__init24.call(this);_class3.prototype.__init25.call(this);_class3.prototype.__init26.call(this);_class3.prototype.__init27.call(this);_class3.prototype.__init28.call(this);_class3.prototype.__init29.call(this);_class3.prototype.__init30.call(this);_class3.prototype.__init31.call(this);_class3.prototype.__init32.call(this);_class3.prototype.__init33.call(this);_class3.prototype.__init34.call(this);
4352
4362
  this.history = _nullishCoalesce(options.history, () => ( new (0, _chunkQKLRAA4Tcjs.HistoryManager)()));
4353
4363
  this.doc = options.document ? cloneVectorDocument(options.document) : { width: 800, height: 600, nodes: [] };
4354
4364
  this.tool = _nullishCoalesce(options.tool, () => ( "select"));
@@ -4377,9 +4387,9 @@ var VectorEditorController = (_class3 = class {
4377
4387
  // State
4378
4388
  // -------------------------------------------------------------------------
4379
4389
  /** The current snapshot (stable until the next change). */
4380
- __init32() {this.getState = () => this.state}
4390
+ __init33() {this.getState = () => this.state}
4381
4391
  /** Calls `listener` after every change. Returns an unsubscribe function. */
4382
- __init33() {this.subscribe = (listener) => {
4392
+ __init34() {this.subscribe = (listener) => {
4383
4393
  this.listeners.add(listener);
4384
4394
  return () => {
4385
4395
  this.listeners.delete(listener);
@@ -4398,7 +4408,8 @@ var VectorEditorController = (_class3 = class {
4398
4408
  tool: this.tool,
4399
4409
  selection: this.selection,
4400
4410
  selectedAnchors: this.anchors,
4401
- selectedWidthPoint: this.widthPoint,
4411
+ selectedWidthPoints: this.widthPoints,
4412
+ widthPointEdit: this.widthEdit,
4402
4413
  editingId: this.editingId,
4403
4414
  selectionBounds: this.boundsOf(this.selection),
4404
4415
  selectionFrame: this.frameOf(this.selectedRoots()),
@@ -4469,7 +4480,7 @@ var VectorEditorController = (_class3 = class {
4469
4480
  case "pen":
4470
4481
  return _optionalChain([h, 'optionalAccess', _102 => _102.anchor]) ? "pointer" : "crosshair";
4471
4482
  case "width":
4472
- if (_optionalChain([this, 'access', _103 => _103.drag, 'optionalAccess', _104 => _104.kind]) === "width") return this.drag.mode === "move" ? "grabbing" : "ew-resize";
4483
+ if (_optionalChain([this, 'access', _103 => _103.drag, 'optionalAccess', _104 => _104.kind]) === "width") return this.drag.mode === "width" ? "ew-resize" : "grabbing";
4473
4484
  if (_optionalChain([h, 'optionalAccess', _105 => _105.widthPoint])) return h.widthPoint.part === "center" ? "grab" : "ew-resize";
4474
4485
  return _optionalChain([h, 'optionalAccess', _106 => _106.strokeSpot]) ? "copy" : "default";
4475
4486
  case "shapeBuilder":
@@ -4489,6 +4500,7 @@ var VectorEditorController = (_class3 = class {
4489
4500
  }
4490
4501
  restore(s) {
4491
4502
  const docChanged = s.doc !== this.doc || this.committing;
4503
+ if (!this.committing) this.widthEdit = null;
4492
4504
  this.committing = false;
4493
4505
  this.applying = true;
4494
4506
  try {
@@ -4497,6 +4509,13 @@ var VectorEditorController = (_class3 = class {
4497
4509
  this.anchors = s.anchors.filter((r) => getAnchor(this.doc, r));
4498
4510
  this.editingId = s.editingId && this.getNode(s.editingId) ? s.editingId : null;
4499
4511
  this.pen = s.pen && _optionalChain([this, 'access', _113 => _113.getNode, 'call', _114 => _114(s.pen.nodeId), 'optionalAccess', _115 => _115.subpaths, 'optionalAccess', _116 => _116[s.pen.subpath]]) ? s.pen : null;
4512
+ if (this.widthPoints.length) {
4513
+ const kept = this.widthPoints.filter((r) => {
4514
+ const n = this.getNode(r.nodeId);
4515
+ return _optionalChain([n, 'optionalAccess', _117 => _117.type]) === "path" && !!_optionalChain([n, 'access', _118 => _118.strokeWidthProfile, 'optionalAccess', _119 => _119[r.index]]);
4516
+ });
4517
+ if (kept.length !== this.widthPoints.length) this.widthPoints = kept;
4518
+ }
4500
4519
  } finally {
4501
4520
  this.applying = false;
4502
4521
  }
@@ -4602,7 +4621,8 @@ var VectorEditorController = (_class3 = class {
4602
4621
  this.tool = tool;
4603
4622
  this.hover = null;
4604
4623
  this.guides = null;
4605
- this.widthPoint = null;
4624
+ this.widthPoints = [];
4625
+ this.widthEdit = null;
4606
4626
  this.builderPreview = null;
4607
4627
  this.builderHoverKey = null;
4608
4628
  if (tool !== "direct") {
@@ -4663,7 +4683,7 @@ var VectorEditorController = (_class3 = class {
4663
4683
  if (res.kind) {
4664
4684
  this.snappedToObject = true;
4665
4685
  this.setGuides(res);
4666
- return _optionalChain([leave, 'optionalAccess', _117 => _117.constrain]) ? res.point : { x: res.x ? res.point.x : this.gridValue(res.point.x), y: res.y ? res.point.y : this.gridValue(res.point.y) };
4686
+ return _optionalChain([leave, 'optionalAccess', _120 => _120.constrain]) ? res.point : { x: res.x ? res.point.x : this.gridValue(res.point.x), y: res.y ? res.point.y : this.gridValue(res.point.y) };
4667
4687
  }
4668
4688
  }
4669
4689
  this.setGuides(null);
@@ -4681,14 +4701,14 @@ var VectorEditorController = (_class3 = class {
4681
4701
  /** Where the pointer at `p` would place a point with the pen or a shape tool (and the guides showing why). */
4682
4702
  hoverSnap(p, input) {
4683
4703
  if (this.tool === "pen") {
4684
- const sp = this.pen ? _optionalChain([this, 'access', _118 => _118.getNode, 'call', _119 => _119(this.pen.nodeId), 'optionalAccess', _120 => _120.subpaths, 'access', _121 => _121[this.pen.subpath]]) : void 0;
4685
- if (this.pen && sp && _optionalChain([this, 'access', _122 => _122.hover, 'optionalAccess', _123 => _123.anchor])) {
4704
+ const sp = this.pen ? _optionalChain([this, 'access', _121 => _121.getNode, 'call', _122 => _122(this.pen.nodeId), 'optionalAccess', _123 => _123.subpaths, 'access', _124 => _124[this.pen.subpath]]) : void 0;
4705
+ if (this.pen && sp && _optionalChain([this, 'access', _125 => _125.hover, 'optionalAccess', _126 => _126.anchor])) {
4686
4706
  this.setGuides(null);
4687
4707
  return p;
4688
4708
  }
4689
4709
  const last = this.pen && sp ? getAnchorWorld(this.doc, { nodeId: this.pen.nodeId, subpath: this.pen.subpath, index: sp.anchors.length - 1 }) : void 0;
4690
4710
  const leave = last ? { at: { x: last.x, y: last.y }, constrain: input.shift ? "angle" : null } : null;
4691
- return this.snapDrawn(p, leave, input, () => this.drawingTargets(_nullishCoalesce(_optionalChain([this, 'access', _124 => _124.pen, 'optionalAccess', _125 => _125.nodeId]), () => ( null))));
4711
+ return this.snapDrawn(p, leave, input, () => this.drawingTargets(_nullishCoalesce(_optionalChain([this, 'access', _127 => _127.pen, 'optionalAccess', _128 => _128.nodeId]), () => ( null))));
4692
4712
  }
4693
4713
  if (SHAPE_TOOLS.has(this.tool)) return this.snapDrawn(p, null, input, () => this.drawingTargets(null));
4694
4714
  this.setGuides(null);
@@ -4836,7 +4856,7 @@ var VectorEditorController = (_class3 = class {
4836
4856
  if (d.fillRule) out.fillRule = d.fillRule;
4837
4857
  if (d.strokeAlign && d.strokeAlign !== "center") out.strokeAlign = d.strokeAlign;
4838
4858
  for (const key of ["startArrow", "endArrow", "arrowAlign", "startArrowScale", "endArrowScale"]) if (d[key]) out[key] = d[key];
4839
- if (_optionalChain([d, 'access', _126 => _126.strokeWidthProfile, 'optionalAccess', _127 => _127.length])) out.strokeWidthProfile = d.strokeWidthProfile.map((p) => ({ ...p }));
4859
+ if (_optionalChain([d, 'access', _129 => _129.strokeWidthProfile, 'optionalAccess', _130 => _130.length])) out.strokeWidthProfile = d.strokeWidthProfile.map((p) => ({ ...p }));
4840
4860
  return out;
4841
4861
  }
4842
4862
  /** Merges `patch` into a node (paths take style and geometry; groups name / visibility / opacity). */
@@ -4878,18 +4898,143 @@ var VectorEditorController = (_class3 = class {
4878
4898
  const p = profile;
4879
4899
  points = p.taper || p.wave ? generateWidthProfile(p) : [];
4880
4900
  }
4881
- this.widthPoint = null;
4901
+ this.widthPoints = [];
4902
+ this.widthEdit = null;
4882
4903
  return this.setStyle({ strokeWidthProfile: points.length ? points : null }, _nullishCoalesce(options.ids, () => ( this.selection)), { label: points.length ? "Width Profile" : "Uniform Width" });
4883
4904
  }
4884
- /** Removes one width point of a path's profile (the width tool's Delete). */
4905
+ /** Removes one width point of a path's profile. */
4885
4906
  removeWidthPoint(ref) {
4886
- const node = this.getNode(ref.nodeId);
4887
- if (_optionalChain([node, 'optionalAccess', _128 => _128.type]) !== "path" || !_optionalChain([node, 'access', _129 => _129.strokeWidthProfile, 'optionalAccess', _130 => _130[ref.index]])) return { ok: false, message: "There is no such width point." };
4888
- const rest = removeWidthPoints(sanitizeWidthProfile(node.strokeWidthProfile), [ref.index]);
4889
- this.widthPoint = null;
4890
- this.change("Delete Width Point", () => this.replaceNode(node.id, withStyle(node, { strokeWidthProfile: rest.length ? rest : null })));
4907
+ return this.removeWidthPoints([ref]);
4908
+ }
4909
+ /** Removes width points (default: the selected ones — the width tool's Delete), as one undo step. */
4910
+ removeWidthPoints(refs = this.widthPoints) {
4911
+ const byNode = /* @__PURE__ */ new Map();
4912
+ for (const r of refs) {
4913
+ const node = this.getNode(r.nodeId);
4914
+ if (_optionalChain([node, 'optionalAccess', _131 => _131.type]) !== "path" || !_optionalChain([node, 'access', _132 => _132.strokeWidthProfile, 'optionalAccess', _133 => _133[r.index]])) continue;
4915
+ byNode.set(r.nodeId, [..._nullishCoalesce(byNode.get(r.nodeId), () => ( [])), r.index]);
4916
+ }
4917
+ if (!byNode.size) return { ok: false, message: "There is no such width point." };
4918
+ const count = [...byNode.values()].reduce((n, l) => n + new Set(l).size, 0);
4919
+ this.widthPoints = [];
4920
+ this.widthEdit = null;
4921
+ this.change(count > 1 ? "Delete Width Points" : "Delete Width Point", () => {
4922
+ for (const [id, indices] of byNode) {
4923
+ const node = this.getNode(id);
4924
+ const rest = removeWidthPoints(sanitizeWidthProfile(node.strokeWidthProfile), indices);
4925
+ this.replaceNode(id, withStyle(node, { strokeWidthProfile: rest.length ? rest : null }));
4926
+ }
4927
+ });
4891
4928
  return { ok: true };
4892
4929
  }
4930
+ /** Selects width points (width tool); points on other paths than the first are dropped. */
4931
+ selectWidthPoints(refs) {
4932
+ const first = _optionalChain([refs, 'access', _134 => _134[0], 'optionalAccess', _135 => _135.nodeId]);
4933
+ const next = [];
4934
+ for (const r of refs) {
4935
+ if (r.nodeId !== first || next.some((q) => q.index === r.index)) continue;
4936
+ const n = this.getNode(r.nodeId);
4937
+ if (_optionalChain([n, 'optionalAccess', _136 => _136.type]) === "path" && _optionalChain([n, 'access', _137 => _137.strokeWidthProfile, 'optionalAccess', _138 => _138[r.index]])) next.push({ nodeId: r.nodeId, index: r.index, subpath: _nullishCoalesce(r.subpath, () => ( 0)) });
4938
+ }
4939
+ this.widthPoints = next;
4940
+ this.notify();
4941
+ }
4942
+ /**
4943
+ * Sets one width point numerically: `index` names an existing point of path `nodeId`, null
4944
+ * (or left out) adds one at `values.t`. `left` / `right` are side widths in document units
4945
+ * (how far the stroke reaches on each side of the path), `t` the position along each subpath
4946
+ * (0–1). A point moved onto another one's `t` makes a step there. Selects the point and
4947
+ * refreshes an open width-point editor; one undo step. Ported from ArtCraft vectorcraft's
4948
+ * Width Point Edit dialog (`crates/tools/src/distort/width.rs`, MIT OR Apache-2.0).
4949
+ */
4950
+ setWidthPoint(target, values) {
4951
+ const node = this.getNode(target.nodeId);
4952
+ if (_optionalChain([node, 'optionalAccess', _139 => _139.type]) !== "path") return { ok: false, message: "There is no such path." };
4953
+ const g = vectorStrokeWidthGeometry(node);
4954
+ if (!g || !(g.unit > 0)) return { ok: false, message: "The path has no visible stroke." };
4955
+ const num = (v2) => typeof v2 === "number" && Number.isFinite(v2) ? v2 : void 0;
4956
+ const t = num(values.t);
4957
+ const left = num(values.left);
4958
+ const right = num(values.right);
4959
+ const scale = (w) => w === void 0 ? void 0 : Math.max(0, w) / g.unit;
4960
+ const profile = sanitizeWidthProfile(node.strokeWidthProfile);
4961
+ let points;
4962
+ let index;
4963
+ const existing = _nullishCoalesce(target.index, () => ( null));
4964
+ if (existing === null) {
4965
+ if (t === void 0) return { ok: false, message: "A new width point needs a position t." };
4966
+ if (profile.length >= VECTOR_WIDTH_PROFILE_MAX_POINTS) return { ok: false, message: "The profile has too many width points." };
4967
+ ({ points, index } = insertWidthPoint(profile.length ? profile : null, t, { left: scale(left), right: scale(right) }, { step: true }));
4968
+ } else {
4969
+ const p = profile[existing];
4970
+ if (!p) return { ok: false, message: "There is no such width point." };
4971
+ const sides2 = { left: _nullishCoalesce(scale(left), () => ( p.left)), right: _nullishCoalesce(scale(right), () => ( p.right)) };
4972
+ const tt = t === void 0 ? p.t : Math.min(Math.max(t, 0), 1);
4973
+ if (Math.abs(tt - p.t) <= VECTOR_WIDTH_SAME_T) {
4974
+ points = setWidthPoint(profile, existing, sides2);
4975
+ index = existing;
4976
+ } else {
4977
+ const rest = removeWidthPoints(profile, [existing]);
4978
+ if (rest.length) ({ points, index } = insertWidthPoint(rest, tt, sides2, { step: true }));
4979
+ else {
4980
+ points = [{ t: tt, ...sides2 }];
4981
+ index = 0;
4982
+ }
4983
+ }
4984
+ }
4985
+ const subpath = _optionalChain([this, 'access', _140 => _140.widthEdit, 'optionalAccess', _141 => _141.nodeId]) === node.id ? this.widthEdit.subpath : _nullishCoalesce(_optionalChain([this, 'access', _142 => _142.widthPoints, 'access', _143 => _143.find, 'call', _144 => _144((r) => r.nodeId === node.id), 'optionalAccess', _145 => _145.subpath]), () => ( 0));
4986
+ const edit = this.widthEdit;
4987
+ this.change("Width Point", () => this.replaceNode(node.id, withStyle(node, { strokeWidthProfile: points })));
4988
+ this.widthPoints = [{ nodeId: node.id, index, subpath }];
4989
+ if (_optionalChain([edit, 'optionalAccess', _146 => _146.nodeId]) === node.id && (edit.index === existing || existing === null)) this.widthEdit = this.widthEditFor(node.id, subpath, index, null);
4990
+ this.notify();
4991
+ return { ok: true };
4992
+ }
4993
+ /**
4994
+ * Opens the width-point editor (what a double-click with the width tool does) on a width
4995
+ * point, or on a spot of a stroke (`t` without `index`: applying it adds a point there).
4996
+ * Returns false when the path has no visible stroke.
4997
+ */
4998
+ openWidthPointEdit(target) {
4999
+ const edit = this.widthEditFor(target.nodeId, _nullishCoalesce(target.subpath, () => ( 0)), _nullishCoalesce(target.index, () => ( null)), _nullishCoalesce(target.t, () => ( null)));
5000
+ if (!edit) return false;
5001
+ this.widthEdit = edit;
5002
+ this.widthPoints = edit.index === null ? [] : [{ nodeId: edit.nodeId, index: edit.index, subpath: edit.subpath }];
5003
+ this.notify();
5004
+ return true;
5005
+ }
5006
+ /** Closes the width-point editor without changing anything. */
5007
+ closeWidthPointEdit() {
5008
+ if (!this.widthEdit) return;
5009
+ this.widthEdit = null;
5010
+ this.notify();
5011
+ }
5012
+ /** Applies `values` to the width point being edited (see `setWidthPoint`) and closes the editor. */
5013
+ commitWidthPointEdit(values) {
5014
+ const edit = this.widthEdit;
5015
+ if (!edit) return { ok: false, message: "No width point is being edited." };
5016
+ const r = this.setWidthPoint({ nodeId: edit.nodeId, index: edit.index }, edit.index === null ? { t: edit.t, ...values } : values);
5017
+ if (r.ok) this.closeWidthPointEdit();
5018
+ return r;
5019
+ }
5020
+ /** The editor's view of point `index` (or of the stroke at `t` when index is null) of `nodeId`. */
5021
+ widthEditFor(nodeId, subpath, index, t) {
5022
+ const node = this.getNode(nodeId);
5023
+ if (_optionalChain([node, 'optionalAccess', _147 => _147.type]) !== "path") return null;
5024
+ const g = vectorStrokeWidthGeometry(node);
5025
+ if (!g) return null;
5026
+ const profile = sanitizeWidthProfile(node.strokeWidthProfile);
5027
+ const p = index === null ? null : profile[index];
5028
+ if (index !== null && !p) return null;
5029
+ const tt = p ? p.t : Math.min(Math.max(_nullishCoalesce(t, () => ( 0)), 0), 1);
5030
+ const sides2 = _nullishCoalesce(p, () => ( widthProfileAt(profile.length ? profile : null, tt)));
5031
+ const sp = Math.min(Math.max(0, subpath), Math.max(0, node.subpaths.length - 1));
5032
+ const at2 = strokePositionPoint(g.line, sp, tt);
5033
+ const round = (v2) => Math.round(v2 * 1e6) / 1e6;
5034
+ const q = at2 ? applyMatrix(this.worldOf(nodeId), { x: at2.point[0], y: at2.point[1] }) : { x: 0, y: 0 };
5035
+ const point = { x: round(q.x), y: round(q.y) };
5036
+ return { nodeId, subpath: sp, index, t: round(tt), left: round(sides2.left * g.unit), right: round(sides2.right * g.unit), point };
5037
+ }
4893
5038
  /**
4894
5039
  * Sets the live corner radius (and optionally the kind) of anchors: `refs`, else the selected
4895
5040
  * anchors (direct tool), else every anchor of the `ids` / selected paths. Radius 0 makes the
@@ -4909,7 +5054,7 @@ var VectorEditorController = (_class3 = class {
4909
5054
  const refs = this.cornerTargets(options);
4910
5055
  if (!refs.length) return { ok: false, message: "Nothing is selected." };
4911
5056
  this.change("Corner Kind", () => {
4912
- this.doc = this.updateAnchors(this.doc, refs, (a) => setAnchorCorner(a, _nullishCoalesce(_optionalChain([a, 'access', _131 => _131.corner, 'optionalAccess', _132 => _132.radius]), () => ( 0)), kind));
5057
+ this.doc = this.updateAnchors(this.doc, refs, (a) => setAnchorCorner(a, _nullishCoalesce(_optionalChain([a, 'access', _148 => _148.corner, 'optionalAccess', _149 => _149.radius]), () => ( 0)), kind));
4913
5058
  });
4914
5059
  return { ok: true };
4915
5060
  }
@@ -5048,10 +5193,10 @@ var VectorEditorController = (_class3 = class {
5048
5193
  return this.selection.filter((id) => {
5049
5194
  let covered = false;
5050
5195
  const loc = locateVectorNode(this.doc.nodes, id);
5051
- let parent = _nullishCoalesce(_optionalChain([loc, 'optionalAccess', _133 => _133.parent]), () => ( null));
5196
+ let parent = _nullishCoalesce(_optionalChain([loc, 'optionalAccess', _150 => _150.parent]), () => ( null));
5052
5197
  while (parent) {
5053
5198
  if (this.selection.includes(parent.id)) covered = true;
5054
- parent = _nullishCoalesce(_optionalChain([locateVectorNode, 'call', _134 => _134(this.doc.nodes, parent.id), 'optionalAccess', _135 => _135.parent]), () => ( null));
5199
+ parent = _nullishCoalesce(_optionalChain([locateVectorNode, 'call', _151 => _151(this.doc.nodes, parent.id), 'optionalAccess', _152 => _152.parent]), () => ( null));
5055
5200
  }
5056
5201
  return !covered;
5057
5202
  });
@@ -5234,8 +5379,8 @@ var VectorEditorController = (_class3 = class {
5234
5379
  if (!ids.length) return null;
5235
5380
  const locs = ids.map((id2) => locateVectorNode(this.doc.nodes, id2));
5236
5381
  if (locs.some((l) => !l)) return null;
5237
- const parent = _nullishCoalesce(_optionalChain([locs, 'access', _136 => _136[0], 'access', _137 => _137.parent, 'optionalAccess', _138 => _138.id]), () => ( null));
5238
- if (locs.some((l) => (_nullishCoalesce(_optionalChain([l, 'access', _139 => _139.parent, 'optionalAccess', _140 => _140.id]), () => ( null))) !== parent)) return null;
5382
+ const parent = _nullishCoalesce(_optionalChain([locs, 'access', _153 => _153[0], 'access', _154 => _154.parent, 'optionalAccess', _155 => _155.id]), () => ( null));
5383
+ if (locs.some((l) => (_nullishCoalesce(_optionalChain([l, 'access', _156 => _156.parent, 'optionalAccess', _157 => _157.id]), () => ( null))) !== parent)) return null;
5239
5384
  const id = this.newId();
5240
5385
  this.change("Group", () => {
5241
5386
  const set = new Set(ids);
@@ -5258,7 +5403,7 @@ var VectorEditorController = (_class3 = class {
5258
5403
  }
5259
5404
  /** Replaces selected groups with their children. */
5260
5405
  ungroup(options = {}) {
5261
- const groups = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess', _141 => _141.type]) === "group");
5406
+ const groups = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess', _158 => _158.type]) === "group");
5262
5407
  if (!groups.length) return { ok: false, message: "Select a group to ungroup." };
5263
5408
  const set = new Set(groups.map((g) => g.id));
5264
5409
  this.change("Ungroup", () => {
@@ -5372,7 +5517,7 @@ var VectorEditorController = (_class3 = class {
5372
5517
  }
5373
5518
  /** Splits selected compound paths into one path per subpath. */
5374
5519
  breakApart(options = {}) {
5375
- const paths = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess', _142 => _142.type]) === "path" && n.subpaths.length > 1);
5520
+ const paths = (_nullishCoalesce(options.ids, () => ( this.selection))).map((id) => this.getNode(id)).filter((n) => _optionalChain([n, 'optionalAccess', _159 => _159.type]) === "path" && n.subpaths.length > 1);
5376
5521
  if (!paths.length) return { ok: false, message: "Select a compound path to break apart." };
5377
5522
  const created = [];
5378
5523
  const set = new Set(paths.map((p) => p.id));
@@ -5438,7 +5583,7 @@ var VectorEditorController = (_class3 = class {
5438
5583
  shapeBuilderMap(ids) {
5439
5584
  const roots = _nullishCoalesce(ids, () => ( this.selectedRoots()));
5440
5585
  const key = roots.join("\0");
5441
- if (_optionalChain([this, 'access', _143 => _143.builderCache, 'optionalAccess', _144 => _144.doc]) === this.doc && this.builderCache.key === key) return this.builderCache.map;
5586
+ if (_optionalChain([this, 'access', _160 => _160.builderCache, 'optionalAccess', _161 => _161.doc]) === this.doc && this.builderCache.key === key) return this.builderCache.map;
5442
5587
  const entries = roots.length ? vectorPathEntries(this.doc, roots) : [];
5443
5588
  const map = entries.length ? vectorShapeBuilderMap(entries) : null;
5444
5589
  this.builderCache = { doc: this.doc, key, map };
@@ -5498,9 +5643,9 @@ var VectorEditorController = (_class3 = class {
5498
5643
  const list = [];
5499
5644
  const r = plan.replace.get(i);
5500
5645
  if (r === void 0) list.push(node);
5501
- else if (_optionalChain([r, 'optionalAccess', _145 => _145.kind]) === "fill") list.push({ ...node, fillRule: "nonzero", subpaths: bezierToSubpaths(toLocal(r.path)) });
5502
- else if (_optionalChain([r, 'optionalAccess', _146 => _146.kind]) === "outline") list.push({ ...node, subpaths: bezierToSubpaths(toLocal(r.path)) });
5503
- else if (_optionalChain([r, 'optionalAccess', _147 => _147.kind]) === "pieces") {
5646
+ else if (_optionalChain([r, 'optionalAccess', _162 => _162.kind]) === "fill") list.push({ ...node, fillRule: "nonzero", subpaths: bezierToSubpaths(toLocal(r.path)) });
5647
+ else if (_optionalChain([r, 'optionalAccess', _163 => _163.kind]) === "outline") list.push({ ...node, subpaths: bezierToSubpaths(toLocal(r.path)) });
5648
+ else if (_optionalChain([r, 'optionalAccess', _164 => _164.kind]) === "pieces") {
5504
5649
  r.paths.forEach(
5505
5650
  (path, k) => list.push({ ...node, id: k === 0 ? node.id : this.newId(), subpaths: bezierToSubpaths(toLocal(path)).map((sp) => ({ ...sp, closed: false })) })
5506
5651
  );
@@ -5534,7 +5679,7 @@ var VectorEditorController = (_class3 = class {
5534
5679
  }
5535
5680
  /** The paint bucket's map of the visible outlines, cached per document and gap. */
5536
5681
  fillMap(gap) {
5537
- if (_optionalChain([this, 'access', _148 => _148.fillCache, 'optionalAccess', _149 => _149.doc]) === this.doc && this.fillCache.gap === gap) return this.fillCache.map;
5682
+ if (_optionalChain([this, 'access', _165 => _165.fillCache, 'optionalAccess', _166 => _166.doc]) === this.doc && this.fillCache.gap === gap) return this.fillCache.map;
5538
5683
  const map = vectorFillMap(this.doc, gap);
5539
5684
  this.fillCache = { doc: this.doc, gap, map };
5540
5685
  return map;
@@ -5548,8 +5693,8 @@ var VectorEditorController = (_class3 = class {
5548
5693
  */
5549
5694
  fillRegion(point, options = {}) {
5550
5695
  if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) return null;
5551
- const region = _optionalChain([this, 'access', _150 => _150.fillMap, 'call', _151 => _151(_nullishCoalesce(options.gap, () => ( this.editing.fillGap))), 'optionalAccess', _152 => _152.regionAt, 'call', _153 => _153([point.x, point.y])]);
5552
- if (!_optionalChain([region, 'optionalAccess', _154 => _154.length])) return null;
5696
+ const region = _optionalChain([this, 'access', _167 => _167.fillMap, 'call', _168 => _168(_nullishCoalesce(options.gap, () => ( this.editing.fillGap))), 'optionalAccess', _169 => _169.regionAt, 'call', _170 => _170([point.x, point.y])]);
5697
+ if (!_optionalChain([region, 'optionalAccess', _171 => _171.length])) return null;
5553
5698
  let below = null;
5554
5699
  const roots = new Set(this.selectedRoots());
5555
5700
  walkVectorNodes(this.doc.nodes, (n) => {
@@ -5575,7 +5720,7 @@ var VectorEditorController = (_class3 = class {
5575
5720
  /** Updates the Shape Builder / paint bucket highlight for the pointer at `p`; true when it changed. */
5576
5721
  hoverBuilder(p, alt) {
5577
5722
  if (this.tool === "paintBucket") {
5578
- const region = _nullishCoalesce(_optionalChain([this, 'access', _155 => _155.fillMap, 'call', _156 => _156(this.editing.fillGap), 'optionalAccess', _157 => _157.regionAt, 'call', _158 => _158([p.x, p.y])]), () => ( null));
5723
+ const region = _nullishCoalesce(_optionalChain([this, 'access', _172 => _172.fillMap, 'call', _173 => _173(this.editing.fillGap), 'optionalAccess', _174 => _174.regionAt, 'call', _175 => _175([p.x, p.y])]), () => ( null));
5579
5724
  const key2 = region ? _chunkVNYRI265cjs.bezierPathToSvg.call(void 0, region, { precision: 2 }) : null;
5580
5725
  if (key2 === this.builderHoverKey) return false;
5581
5726
  this.builderHoverKey = key2;
@@ -5683,7 +5828,7 @@ var VectorEditorController = (_class3 = class {
5683
5828
  /** `doc` with anchor `ref` moved to document point `to` by a Curvature-tool move. */
5684
5829
  curvatureMoved(doc, ref, to) {
5685
5830
  const node = findVectorNode(doc.nodes, ref.nodeId);
5686
- if (_optionalChain([node, 'optionalAccess', _159 => _159.type]) !== "path" || node.locked || !node.subpaths[ref.subpath]) return doc;
5831
+ if (_optionalChain([node, 'optionalAccess', _176 => _176.type]) !== "path" || node.locked || !node.subpaths[ref.subpath]) return doc;
5687
5832
  const q = this.toLocal(ref.nodeId, to, doc);
5688
5833
  const subpaths = node.subpaths.map((sp, i) => i === ref.subpath ? curvatureMoveAnchor(sp, ref.index, q) : sp);
5689
5834
  const nodes = mapTree(doc.nodes, (n) => n.id === ref.nodeId ? { ...node, subpaths } : n);
@@ -5692,7 +5837,7 @@ var VectorEditorController = (_class3 = class {
5692
5837
  /** `doc` with segment `ref` bent through document point `p` at parameter `t`. */
5693
5838
  bent(doc, ref, t, p) {
5694
5839
  const node = findVectorNode(doc.nodes, ref.nodeId);
5695
- if (_optionalChain([node, 'optionalAccess', _160 => _160.type]) !== "path" || node.locked || !node.subpaths[ref.subpath]) return doc;
5840
+ if (_optionalChain([node, 'optionalAccess', _177 => _177.type]) !== "path" || node.locked || !node.subpaths[ref.subpath]) return doc;
5696
5841
  const q = this.toLocal(ref.nodeId, p, doc);
5697
5842
  const subpaths = node.subpaths.map((sp, i) => i === ref.subpath ? bendSegment(sp, ref.segment, t, q) : sp);
5698
5843
  return { ...doc, nodes: mapTree(doc.nodes, (n) => n.id === ref.nodeId ? { ...node, subpaths } : n) };
@@ -5703,7 +5848,7 @@ var VectorEditorController = (_class3 = class {
5703
5848
  */
5704
5849
  bendSegment(ref, to, t = 0.5) {
5705
5850
  const node = this.getNode(ref.nodeId);
5706
- const sp = _optionalChain([node, 'optionalAccess', _161 => _161.type]) === "path" ? node.subpaths[ref.subpath] : void 0;
5851
+ const sp = _optionalChain([node, 'optionalAccess', _178 => _178.type]) === "path" ? node.subpaths[ref.subpath] : void 0;
5707
5852
  if (!sp || ref.segment < 0 || ref.segment >= (sp.closed ? sp.anchors.length : sp.anchors.length - 1) || sp.anchors.length < 2) return { ok: false, message: "There is no such segment." };
5708
5853
  if (node.locked) return { ok: false, message: "The path is locked." };
5709
5854
  this.change("Bend Segment", () => {
@@ -5738,7 +5883,7 @@ var VectorEditorController = (_class3 = class {
5738
5883
  /** Adds an anchor on the nearest segment of `nodeId` to document point `point` (shape unchanged). */
5739
5884
  insertAnchorAt(nodeId, point) {
5740
5885
  const node = this.getNode(nodeId);
5741
- if (_optionalChain([node, 'optionalAccess', _162 => _162.type]) !== "path") return null;
5886
+ if (_optionalChain([node, 'optionalAccess', _179 => _179.type]) !== "path") return null;
5742
5887
  const hit = nearestSegment(node, this.toLocal(nodeId, point));
5743
5888
  if (!hit) return null;
5744
5889
  const { subpath, index } = insertAnchor(node.subpaths[hit.subpath], hit.segment, hit.t);
@@ -5782,7 +5927,7 @@ var VectorEditorController = (_class3 = class {
5782
5927
  setClosed(closed, nodeId) {
5783
5928
  const targets = nodeId ? (() => {
5784
5929
  const n = this.getNode(nodeId);
5785
- return _optionalChain([n, 'optionalAccess', _163 => _163.type]) === "path" ? n.subpaths.map((_, s) => ({ nodeId, subpath: s })) : [];
5930
+ return _optionalChain([n, 'optionalAccess', _180 => _180.type]) === "path" ? n.subpaths.map((_, s) => ({ nodeId, subpath: s })) : [];
5786
5931
  })() : this.anchors.map((r) => ({ nodeId: r.nodeId, subpath: r.subpath }));
5787
5932
  if (!targets.length) return { ok: false, message: "Select a path or points." };
5788
5933
  this.change(closed ? "Close Path" : "Open Path", () => {
@@ -5803,7 +5948,7 @@ var VectorEditorController = (_class3 = class {
5803
5948
  this.setDoc(
5804
5949
  mapTree(
5805
5950
  this.doc.nodes,
5806
- (n) => n.type === "path" && set.has(n.id) ? { ...n, subpaths: n.subpaths.map(reverseSubpath), ..._optionalChain([n, 'access', _164 => _164.strokeWidthProfile, 'optionalAccess', _165 => _165.length]) ? { strokeWidthProfile: reverseWidthProfile(n.strokeWidthProfile) } : {} } : n
5951
+ (n) => n.type === "path" && set.has(n.id) ? { ...n, subpaths: n.subpaths.map(reverseSubpath), ..._optionalChain([n, 'access', _181 => _181.strokeWidthProfile, 'optionalAccess', _182 => _182.length]) ? { strokeWidthProfile: reverseWidthProfile(n.strokeWidthProfile) } : {} } : n
5807
5952
  )
5808
5953
  );
5809
5954
  this.anchors = [];
@@ -5941,7 +6086,7 @@ var VectorEditorController = (_class3 = class {
5941
6086
  */
5942
6087
  cutPathAt(nodeId, point) {
5943
6088
  const node = this.getNode(nodeId);
5944
- if (_optionalChain([node, 'optionalAccess', _166 => _166.type]) !== "path" || node.locked) return null;
6089
+ if (_optionalChain([node, 'optionalAccess', _183 => _183.type]) !== "path" || node.locked) return null;
5945
6090
  const local = this.toLocal(nodeId, point);
5946
6091
  const hit = nearestSegment(node, local);
5947
6092
  if (!hit) return null;
@@ -5991,7 +6136,7 @@ var VectorEditorController = (_class3 = class {
5991
6136
  if (n.visible === false || n.locked || parent && blocked.has(parent.id)) blocked.add(n.id);
5992
6137
  });
5993
6138
  const visible = (p) => !blocked.has(p.id);
5994
- if (_optionalChain([ids, 'optionalAccess', _167 => _167.length]) || this.selection.length) return this.pathTargets(_nullishCoalesce(ids, () => ( this.selection))).filter(visible);
6139
+ if (_optionalChain([ids, 'optionalAccess', _184 => _184.length]) || this.selection.length) return this.pathTargets(_nullishCoalesce(ids, () => ( this.selection))).filter(visible);
5995
6140
  const box = polylineBounds(points.map(toPoint2), pad);
5996
6141
  if (!box) return [];
5997
6142
  const out = [];
@@ -6077,7 +6222,7 @@ var VectorEditorController = (_class3 = class {
6077
6222
  finishPath() {
6078
6223
  if (!this.pen) return;
6079
6224
  const node = this.getNode(this.pen.nodeId);
6080
- const sp = _optionalChain([node, 'optionalAccess', _168 => _168.subpaths, 'access', _169 => _169[this.pen.subpath]]);
6225
+ const sp = _optionalChain([node, 'optionalAccess', _185 => _185.subpaths, 'access', _186 => _186[this.pen.subpath]]);
6081
6226
  const pen = this.pen;
6082
6227
  this.pen = null;
6083
6228
  this.pointer = null;
@@ -6121,7 +6266,7 @@ var VectorEditorController = (_class3 = class {
6121
6266
  if (trimmed.startsWith("{")) {
6122
6267
  try {
6123
6268
  const data = JSON.parse(trimmed);
6124
- if (_optionalChain([data, 'optionalAccess', _170 => _170.type]) === VECTOR_CLIPBOARD_TYPE && Array.isArray(data.nodes)) nodes = data.nodes;
6269
+ if (_optionalChain([data, 'optionalAccess', _187 => _187.type]) === VECTOR_CLIPBOARD_TYPE && Array.isArray(data.nodes)) nodes = data.nodes;
6125
6270
  } catch (e3) {
6126
6271
  nodes = null;
6127
6272
  }
@@ -6133,7 +6278,7 @@ var VectorEditorController = (_class3 = class {
6133
6278
  }
6134
6279
  }
6135
6280
  } else nodes = this.clipboard;
6136
- if (!_optionalChain([nodes, 'optionalAccess', _171 => _171.length])) return [];
6281
+ if (!_optionalChain([nodes, 'optionalAccess', _188 => _188.length])) return [];
6137
6282
  const offset = _nullishCoalesce(options.offset, () => ( (text && !text.trim().startsWith("{") ? { x: 0, y: 0 } : { x: 10, y: 10 })));
6138
6283
  const copies = nodes.map((n) => transformVectorNode(this.freshIds(n), translateMatrix(offset.x, offset.y)));
6139
6284
  this.change("Paste", () => {
@@ -6184,7 +6329,7 @@ var VectorEditorController = (_class3 = class {
6184
6329
  hitHandle(screen) {
6185
6330
  for (const ref of getVisibleHandles(this.view()).reverse()) {
6186
6331
  const a = getAnchorWorld(this.doc, ref);
6187
- const h = _optionalChain([a, 'optionalAccess', _172 => _172[ref.handle]]);
6332
+ const h = _optionalChain([a, 'optionalAccess', _189 => _189[ref.handle]]);
6188
6333
  if (!h) continue;
6189
6334
  const s = this.documentToScreen(h);
6190
6335
  if (Math.hypot(s.x - screen.x, s.y - screen.y) <= this.hitRadius) return ref;
@@ -6298,13 +6443,19 @@ var VectorEditorController = (_class3 = class {
6298
6443
  }
6299
6444
  }
6300
6445
  widthView() {
6301
- return { document: this.doc, selection: this.selection, tool: this.tool, hover: this.hover, interaction: this.interaction, selectedWidthPoint: this.widthPoint };
6446
+ return { document: this.doc, selection: this.selection, tool: this.tool, hover: this.hover, interaction: this.interaction, selectedWidthPoints: this.widthPoints };
6302
6447
  }
6303
6448
  /** The width point centre or side end under `screen` (width tool). */
6304
6449
  hitWidthHandle(screen) {
6450
+ const shown = this.hitWidthHandleIn(screen, this.widthView());
6451
+ if (shown) return shown;
6452
+ const spot = this.strokeSpotAt(this.screenToDocument(screen));
6453
+ return spot && spot.nodeId !== _optionalChain([this, 'access', _190 => _190.hover, 'optionalAccess', _191 => _191.nodeId]) ? this.hitWidthHandleIn(screen, { ...this.widthView(), hover: { nodeId: spot.nodeId } }) : null;
6454
+ }
6455
+ hitWidthHandleIn(screen, view) {
6305
6456
  let best = null;
6306
6457
  let bestD = Infinity;
6307
- for (const h of getVectorWidthHandles(this.widthView())) {
6458
+ for (const h of getVectorWidthHandles(view)) {
6308
6459
  for (const [part, q] of [["center", h], ["left", h.left], ["right", h.right]]) {
6309
6460
  const s = this.documentToScreen(q);
6310
6461
  const d = Math.hypot(s.x - screen.x, s.y - screen.y);
@@ -6354,14 +6505,13 @@ var VectorEditorController = (_class3 = class {
6354
6505
  downWidth(screen, p, input) {
6355
6506
  const hit = this.hitWidthHandle(screen);
6356
6507
  const spot = hit ? null : this.strokeSpotAt(p);
6357
- const nodeId = _nullishCoalesce(_optionalChain([hit, 'optionalAccess', _173 => _173.ref, 'access', _174 => _174.nodeId]), () => ( _optionalChain([spot, 'optionalAccess', _175 => _175.nodeId])));
6508
+ const nodeId = _nullishCoalesce(_optionalChain([hit, 'optionalAccess', _192 => _192.ref, 'access', _193 => _193.nodeId]), () => ( _optionalChain([spot, 'optionalAccess', _194 => _194.nodeId])));
6358
6509
  const node = nodeId ? this.getNode(nodeId) : void 0;
6359
- const g = _optionalChain([node, 'optionalAccess', _176 => _176.type]) === "path" ? vectorStrokeWidthGeometry(node) : null;
6510
+ const g = _optionalChain([node, 'optionalAccess', _195 => _195.type]) === "path" ? vectorStrokeWidthGeometry(node) : null;
6511
+ if (this.widthEdit) this.widthEdit = null;
6360
6512
  if (!node || node.type !== "path" || !g) {
6361
- if (this.widthPoint) {
6362
- this.widthPoint = null;
6363
- this.notify();
6364
- }
6513
+ if (!input.shift) this.widthPoints = [];
6514
+ this.notify();
6365
6515
  return;
6366
6516
  }
6367
6517
  const before = this.snapshot();
@@ -6370,15 +6520,29 @@ var VectorEditorController = (_class3 = class {
6370
6520
  let ref;
6371
6521
  let mode = "width";
6372
6522
  let side = "both";
6523
+ let group = [];
6524
+ let shift = false;
6373
6525
  let t;
6374
6526
  let subpath;
6375
6527
  if (hit) {
6376
6528
  profile = sanitizeWidthProfile(node.strokeWidthProfile);
6377
6529
  ref = { nodeId: node.id, index: hit.ref.index, subpath: hit.ref.subpath };
6378
- t = _nullishCoalesce(_optionalChain([profile, 'access', _177 => _177[ref.index], 'optionalAccess', _178 => _178.t]), () => ( 0));
6530
+ t = _nullishCoalesce(_optionalChain([profile, 'access', _196 => _196[ref.index], 'optionalAccess', _197 => _197.t]), () => ( 0));
6379
6531
  subpath = hit.ref.subpath;
6380
- if (hit.ref.part === "center") mode = "move";
6532
+ if (hit.ref.part === "center") mode = input.alt && profile.length < VECTOR_WIDTH_PROFILE_MAX_POINTS ? "copy" : "move";
6381
6533
  else if (input.alt) side = hit.ref.part;
6534
+ const same = this.widthPoints.length > 0 && this.widthPoints[0].nodeId === node.id;
6535
+ const selected = same && this.widthPoints.some((r) => r.index === ref.index);
6536
+ if (input.shift && mode !== "copy") {
6537
+ if (selected) {
6538
+ this.widthPoints = this.widthPoints.filter((r) => r.index !== ref.index);
6539
+ this.notify();
6540
+ return;
6541
+ }
6542
+ this.widthPoints = same ? [...this.widthPoints, ref] : [ref];
6543
+ shift = true;
6544
+ } else if (mode === "copy" || !selected) this.widthPoints = [ref];
6545
+ if (mode !== "copy") group = this.widthPoints.filter((r) => r.index !== ref.index).map((r) => r.index);
6382
6546
  } else {
6383
6547
  const near2 = nearestStrokePosition(g.line, [local.x, local.y]);
6384
6548
  const inserted = insertWidthPoint(strokeWidthProfileOf(node), near2.t);
@@ -6387,17 +6551,21 @@ var VectorEditorController = (_class3 = class {
6387
6551
  t = near2.t;
6388
6552
  ref = { nodeId: node.id, index: inserted.index, subpath };
6389
6553
  if (input.alt) side = near2.side;
6554
+ this.widthPoints = [ref];
6390
6555
  }
6391
6556
  const at2 = strokePositionPoint(g.line, subpath, t);
6392
6557
  if (!at2) return;
6393
6558
  const base = { ...this.doc, nodes: mapTree(this.doc.nodes, (n) => n.id === node.id ? { ...n, strokeWidthProfile: profile } : n) };
6394
6559
  this.doc = base;
6395
- this.widthPoint = { nodeId: node.id, index: ref.index, subpath };
6396
6560
  this.drag = {
6397
6561
  kind: "width",
6398
6562
  ref,
6399
6563
  mode,
6400
6564
  side,
6565
+ source: ref.index,
6566
+ group,
6567
+ shift,
6568
+ renumber: null,
6401
6569
  origin: { x: at2.point[0], y: at2.point[1] },
6402
6570
  normal: { x: at2.tangent[1], y: -at2.tangent[0] },
6403
6571
  unit: g.unit,
@@ -6412,6 +6580,31 @@ var VectorEditorController = (_class3 = class {
6412
6580
  this.interaction = { type: "width", ref, mode, width: (w.left + w.right) * g.unit };
6413
6581
  this.notify(!hit);
6414
6582
  }
6583
+ /**
6584
+ * A width-tool slide to `t` on the dragged point's subpath lands exactly on another width
6585
+ * point (not `skip`) within the pick distance on screen: dropping a point onto another makes
6586
+ * a discontinuous point (two points at one `t`, the width steps there).
6587
+ */
6588
+ snapWidthT(drag, node, line2, t, skip) {
6589
+ const m = this.worldOf(node.id, drag.base);
6590
+ const at2 = strokePositionPoint(line2, drag.ref.subpath, t);
6591
+ if (!at2) return t;
6592
+ const here = this.documentToScreen(applyMatrix(m, { x: at2.point[0], y: at2.point[1] }));
6593
+ let best = t;
6594
+ let bestD = this.hitRadius;
6595
+ drag.profile.forEach((w, i) => {
6596
+ if (i === skip || drag.group.includes(i)) return;
6597
+ const q = strokePositionPoint(line2, drag.ref.subpath, w.t);
6598
+ if (!q) return;
6599
+ const s = this.documentToScreen(applyMatrix(m, { x: q.point[0], y: q.point[1] }));
6600
+ const d = Math.hypot(s.x - here.x, s.y - here.y);
6601
+ if (d <= bestD) {
6602
+ best = w.t;
6603
+ bestD = d;
6604
+ }
6605
+ });
6606
+ return best;
6607
+ }
6415
6608
  /** The nearest outline point of any visible, unlocked path within the pick distance of document point `p`. */
6416
6609
  nearestOutline(p) {
6417
6610
  let best = null;
@@ -6600,7 +6793,7 @@ var VectorEditorController = (_class3 = class {
6600
6793
  }
6601
6794
  downPen(screen, p, input) {
6602
6795
  const before = this.snapshot();
6603
- const sp = this.pen ? _optionalChain([this, 'access', _179 => _179.getNode, 'call', _180 => _180(this.pen.nodeId), 'optionalAccess', _181 => _181.subpaths, 'access', _182 => _182[this.pen.subpath]]) : void 0;
6796
+ const sp = this.pen ? _optionalChain([this, 'access', _198 => _198.getNode, 'call', _199 => _199(this.pen.nodeId), 'optionalAccess', _200 => _200.subpaths, 'access', _201 => _201[this.pen.subpath]]) : void 0;
6604
6797
  if (this.pen && sp) {
6605
6798
  const pen = this.pen;
6606
6799
  const first = { nodeId: pen.nodeId, subpath: pen.subpath, index: 0 };
@@ -6862,7 +7055,7 @@ var VectorEditorController = (_class3 = class {
6862
7055
  }
6863
7056
  case "shape": {
6864
7057
  const node = this.getNode(drag.nodeId);
6865
- if (_optionalChain([node, 'optionalAccess', _183 => _183.type]) !== "path") return;
7058
+ if (_optionalChain([node, 'optionalAccess', _202 => _202.type]) !== "path") return;
6866
7059
  const leave = { at: drag.start, constrain: input.shift ? drag.tool === "line" ? "angle" : "square" : null, curves: false };
6867
7060
  let end = this.snapDrawn(p, leave, input, () => drag.targets ??= this.drawingTargets(null));
6868
7061
  if (!this.snappedToObject && drag.tool !== "line" && !input.shift && !input.mod && this.snap.objects && drag.targets) end = this.matchShapeSize(drag.targets, drag.start, end, input.alt);
@@ -6896,19 +7089,35 @@ var VectorEditorController = (_class3 = class {
6896
7089
  drag.moved = true;
6897
7090
  const node = findVectorNode(drag.base.nodes, drag.ref.nodeId);
6898
7091
  const local = this.toLocal(node.id, p, drag.base);
7092
+ const src = drag.profile[drag.source];
6899
7093
  let profile;
6900
- if (drag.mode === "move") {
7094
+ let index = drag.source;
7095
+ if (drag.mode !== "width") {
6901
7096
  const g = vectorStrokeWidthGeometry(node);
6902
7097
  const near2 = g ? nearestStrokePosition(g.line, [local.x, local.y], drag.ref.subpath) : null;
6903
- if (!near2) return;
6904
- profile = setWidthPoint(drag.profile, drag.ref.index, { t: near2.t });
7098
+ if (!g || !near2) return;
7099
+ const t = drag.group.length ? near2.t : this.snapWidthT(drag, node, g.line, near2.t, drag.source);
7100
+ if (drag.mode === "copy") {
7101
+ ({ points: profile, index } = insertWidthPoint(drag.profile, t, { left: src.left, right: src.right }, { step: true }));
7102
+ } else if (drag.group.length) {
7103
+ const shifted = shiftWidthPoints(drag.profile, [drag.source, ...drag.group], t - src.t);
7104
+ profile = shifted.points;
7105
+ drag.renumber = shifted.renumber;
7106
+ index = _nullishCoalesce(shifted.renumber.get(drag.source), () => ( drag.source));
7107
+ } else profile = setWidthPoint(drag.profile, drag.source, { t });
6905
7108
  } else {
6906
7109
  const off = ((local.x - drag.origin.x) * drag.normal.x + (local.y - drag.origin.y) * drag.normal.y) / drag.unit;
6907
7110
  const patch = drag.side === "both" ? { left: Math.abs(off), right: Math.abs(off) } : drag.side === "left" ? { left: Math.max(0, off) } : { right: Math.max(0, -off) };
6908
- profile = setWidthPoint(drag.profile, drag.ref.index, patch);
7111
+ profile = setWidthPoint(drag.profile, drag.source, patch);
7112
+ if (drag.group.length) {
7113
+ const dl = profile[drag.source].left - src.left;
7114
+ const dr = profile[drag.source].right - src.right;
7115
+ for (const i of drag.group) profile = setWidthPoint(profile, i, { left: Math.max(0, drag.profile[i].left + dl), right: Math.max(0, drag.profile[i].right + dr) });
7116
+ }
6909
7117
  }
6910
7118
  this.doc = { ...drag.base, nodes: mapTree(drag.base.nodes, (n) => n.id === node.id ? { ...n, strokeWidthProfile: profile } : n) };
6911
- const w = profile[drag.ref.index];
7119
+ drag.ref = { ...drag.ref, index };
7120
+ const w = profile[index];
6912
7121
  this.interaction = { type: "width", ref: drag.ref, mode: drag.mode, width: (w.left + w.right) * drag.unit };
6913
7122
  this.notify(true);
6914
7123
  return;
@@ -6920,14 +7129,14 @@ var VectorEditorController = (_class3 = class {
6920
7129
  for (const q of drag.lazy.push([p.x, p.y])) {
6921
7130
  if (Math.hypot(q[0] - last[0], q[1] - last[1]) * this.viewport.zoom < 1.5) continue;
6922
7131
  drag.points.push(q);
6923
- _optionalChain([drag, 'access', _184 => _184.pressures, 'optionalAccess', _185 => _185.push, 'call', _186 => _186(drag.pressure)]);
7132
+ _optionalChain([drag, 'access', _203 => _203.pressures, 'optionalAccess', _204 => _204.push, 'call', _205 => _205(drag.pressure)]);
6924
7133
  moved = true;
6925
7134
  }
6926
7135
  const string = drag.lazy.string();
6927
7136
  const node = this.getNode(drag.nodeId);
6928
- if (_optionalChain([node, 'optionalAccess', _187 => _187.type]) !== "path") return;
7137
+ if (_optionalChain([node, 'optionalAccess', _206 => _206.type]) !== "path") return;
6929
7138
  if (!moved) {
6930
- if (string && _optionalChain([this, 'access', _188 => _188.interaction, 'optionalAccess', _189 => _189.type]) === "draw") {
7139
+ if (string && _optionalChain([this, 'access', _207 => _207.interaction, 'optionalAccess', _208 => _208.type]) === "draw") {
6931
7140
  this.interaction = { ...this.interaction, string: [{ x: string[0][0], y: string[0][1] }, { x: string[1][0], y: string[1][1] }] };
6932
7141
  this.notify();
6933
7142
  }
@@ -6963,8 +7172,8 @@ var VectorEditorController = (_class3 = class {
6963
7172
  }
6964
7173
  this.setGuides(res);
6965
7174
  return {
6966
- dx: _optionalChain([res, 'optionalAccess', _190 => _190.x]) ? dx + res.dx : axes.x ? this.gridValue(origin.x + dx) - origin.x : dx,
6967
- dy: _optionalChain([res, 'optionalAccess', _191 => _191.y]) ? dy + res.dy : axes.y ? this.gridValue(origin.y + dy) - origin.y : dy
7175
+ dx: _optionalChain([res, 'optionalAccess', _209 => _209.x]) ? dx + res.dx : axes.x ? this.gridValue(origin.x + dx) - origin.x : dx,
7176
+ dy: _optionalChain([res, 'optionalAccess', _210 => _210.y]) ? dy + res.dy : axes.y ? this.gridValue(origin.y + dy) - origin.y : dy
6968
7177
  };
6969
7178
  }
6970
7179
  /** The anchor a point drag snaps by, where it started: the grabbed one, else the moving one nearest the press. */
@@ -7043,7 +7252,7 @@ var VectorEditorController = (_class3 = class {
7043
7252
  if (drag.moved) return this.commit("Corner Radius", drag.before);
7044
7253
  if (drag.alt) {
7045
7254
  const kind = nextVectorCornerKind(drag.widget.kind);
7046
- this.doc = this.updateAnchors(drag.base, drag.refs, (a) => setAnchorCorner(a, _nullishCoalesce(_optionalChain([a, 'access', _192 => _192.corner, 'optionalAccess', _193 => _193.radius]), () => ( 0)), kind));
7255
+ this.doc = this.updateAnchors(drag.base, drag.refs, (a) => setAnchorCorner(a, _nullishCoalesce(_optionalChain([a, 'access', _211 => _211.corner, 'optionalAccess', _212 => _212.radius]), () => ( 0)), kind));
7047
7256
  return this.commit("Corner Kind", drag.before);
7048
7257
  }
7049
7258
  const ref = drag.widget.ref;
@@ -7054,12 +7263,23 @@ var VectorEditorController = (_class3 = class {
7054
7263
  return;
7055
7264
  }
7056
7265
  case "width": {
7057
- if (drag.moved) return this.commit(drag.mode === "move" ? "Move Width Point" : "Width Point", drag.before);
7266
+ if (drag.moved) {
7267
+ const several = drag.group.length > 0;
7268
+ if (drag.mode === "copy") this.widthPoints = [drag.ref];
7269
+ else if (drag.mode === "move" && several) {
7270
+ this.widthPoints = [drag.source, ...drag.group].map((i) => ({ ...drag.ref, index: _nullishCoalesce(_optionalChain([drag, 'access', _213 => _213.renumber, 'optionalAccess', _214 => _214.get, 'call', _215 => _215(i)]), () => ( i)) }));
7271
+ } else if (!several) this.widthPoints = [drag.ref];
7272
+ const label = drag.mode === "copy" ? "Copy Width Point" : drag.mode === "move" ? several ? "Move Width Points" : "Move Width Point" : several ? "Width Points" : "Width Point";
7273
+ return this.commit(label, drag.before);
7274
+ }
7058
7275
  if (drag.created) {
7059
7276
  this.doc = drag.before.doc;
7060
- this.widthPoint = null;
7277
+ this.widthPoints = [];
7061
7278
  this.notify(true);
7062
- } else this.notify();
7279
+ } else {
7280
+ if (!drag.shift && drag.group.length) this.widthPoints = [drag.ref];
7281
+ this.notify();
7282
+ }
7063
7283
  return;
7064
7284
  }
7065
7285
  case "builder":
@@ -7089,14 +7309,14 @@ var VectorEditorController = (_class3 = class {
7089
7309
  }
7090
7310
  case "pencil": {
7091
7311
  const node = this.getNode(drag.nodeId);
7092
- if (_optionalChain([node, 'optionalAccess', _194 => _194.type]) !== "path" || drag.points.length < 2) {
7312
+ if (_optionalChain([node, 'optionalAccess', _216 => _216.type]) !== "path" || drag.points.length < 2) {
7093
7313
  this.doc = drag.before.doc;
7094
7314
  this.notify(true);
7095
7315
  return;
7096
7316
  }
7097
7317
  for (const q of drag.lazy.finish()) {
7098
7318
  drag.points.push(q);
7099
- _optionalChain([drag, 'access', _195 => _195.pressures, 'optionalAccess', _196 => _196.push, 'call', _197 => _197(drag.pressure)]);
7319
+ _optionalChain([drag, 'access', _217 => _217.pressures, 'optionalAccess', _218 => _218.push, 'call', _219 => _219(drag.pressure)]);
7100
7320
  }
7101
7321
  const shape = this.editing.recognizeShapes ? recognizeInkShape(drag.points, { minSize: 6 / this.viewport.zoom }) : null;
7102
7322
  if (shape) {
@@ -7148,7 +7368,10 @@ var VectorEditorController = (_class3 = class {
7148
7368
  }
7149
7369
  this.restore(drag.before);
7150
7370
  }
7151
- /** Double click: select tool enters path editing, direct tool toggles an anchor or adds one. */
7371
+ /**
7372
+ * Double click: select tool enters path editing, direct tool toggles an anchor or adds one,
7373
+ * width tool opens the width-point editor (`widthPointEdit`).
7374
+ */
7152
7375
  doubleClick(screen) {
7153
7376
  const p = this.screenToDocument(screen);
7154
7377
  if (this.tool === "select") {
@@ -7168,7 +7391,7 @@ var VectorEditorController = (_class3 = class {
7168
7391
  this.convertAnchors(a.in || a.out ? "corner" : "smooth", [anchor]);
7169
7392
  return;
7170
7393
  }
7171
- const id = _nullishCoalesce(_optionalChain([this, 'access', _198 => _198.hover, 'optionalAccess', _199 => _199.segment, 'optionalAccess', _200 => _200.nodeId]), () => ( this.editingId));
7394
+ const id = _nullishCoalesce(_optionalChain([this, 'access', _220 => _220.hover, 'optionalAccess', _221 => _221.segment, 'optionalAccess', _222 => _222.nodeId]), () => ( this.editingId));
7172
7395
  if (id) {
7173
7396
  const node = this.getNode(id);
7174
7397
  const seg = node ? nearestSegment(node, this.toLocal(id, p)) : null;
@@ -7176,6 +7399,16 @@ var VectorEditorController = (_class3 = class {
7176
7399
  }
7177
7400
  return;
7178
7401
  }
7402
+ if (this.tool === "width") {
7403
+ const hit = this.hitWidthHandle(screen);
7404
+ if (hit) {
7405
+ this.openWidthPointEdit(hit.ref);
7406
+ return;
7407
+ }
7408
+ const spot = this.strokeSpotAt(p);
7409
+ if (spot) this.openWidthPointEdit({ nodeId: spot.nodeId, subpath: spot.subpath, t: spot.t });
7410
+ return;
7411
+ }
7179
7412
  if (this.tool === "pen") this.finishPath();
7180
7413
  }
7181
7414
  // -------------------------------------------------------------------------
@@ -7236,8 +7469,9 @@ var VectorEditorController = (_class3 = class {
7236
7469
  switch (key) {
7237
7470
  case "Escape":
7238
7471
  if (this.drag) this.cancelDrag();
7239
- else if (this.tool === "width" && this.widthPoint) {
7240
- this.widthPoint = null;
7472
+ else if (this.widthEdit) this.closeWidthPointEdit();
7473
+ else if (this.tool === "width" && this.widthPoints.length) {
7474
+ this.widthPoints = [];
7241
7475
  this.notify();
7242
7476
  } else if (this.pen) this.finishPath();
7243
7477
  else if (this.tool === "direct" && this.anchors.length) {
@@ -7251,7 +7485,7 @@ var VectorEditorController = (_class3 = class {
7251
7485
  this.finishPath();
7252
7486
  return true;
7253
7487
  }
7254
- if (this.tool === "select" && this.selection.length === 1 && _optionalChain([this, 'access', _201 => _201.getNode, 'call', _202 => _202(this.selection[0]), 'optionalAccess', _203 => _203.type]) === "path") {
7488
+ if (this.tool === "select" && this.selection.length === 1 && _optionalChain([this, 'access', _223 => _223.getNode, 'call', _224 => _224(this.selection[0]), 'optionalAccess', _225 => _225.type]) === "path") {
7255
7489
  this.tool = "direct";
7256
7490
  this.editingId = this.selection[0];
7257
7491
  this.notify();
@@ -7260,7 +7494,7 @@ var VectorEditorController = (_class3 = class {
7260
7494
  return false;
7261
7495
  case "Delete":
7262
7496
  case "Backspace":
7263
- if (this.tool === "width" && this.widthPoint) return this.removeWidthPoint(this.widthPoint).ok;
7497
+ if (this.tool === "width" && this.widthPoints.length) return this.removeWidthPoints().ok;
7264
7498
  if (this.pen) {
7265
7499
  const node = this.getNode(this.pen.nodeId);
7266
7500
  const sp = node.subpaths[this.pen.subpath];
@@ -7394,7 +7628,7 @@ function renderNode(node, defs, idPrefix) {
7394
7628
  strokeWidth: _nullishCoalesce(node.strokeWidth, () => ( 1)),
7395
7629
  ...node.strokeLinecap ? { strokeLinecap: node.strokeLinecap } : {},
7396
7630
  ...node.strokeLinejoin ? { strokeLinejoin: node.strokeLinejoin } : {},
7397
- ..._optionalChain([node, 'access', _204 => _204.strokeDasharray, 'optionalAccess', _205 => _205.length]) ? { strokeDasharray: node.strokeDasharray.join(" ") } : {},
7631
+ ..._optionalChain([node, 'access', _226 => _226.strokeDasharray, 'optionalAccess', _227 => _227.length]) ? { strokeDasharray: node.strokeDasharray.join(" ") } : {},
7398
7632
  fillRule: _nullishCoalesce(node.fillRule, () => ( "nonzero")),
7399
7633
  opacity: _nullishCoalesce(node.opacity, () => ( 1))
7400
7634
  };
@@ -7443,9 +7677,9 @@ function getVectorOverlayRenderData(state) {
7443
7677
  };
7444
7678
  for (const id of state.selection) outline(id, true);
7445
7679
  if (state.editingId) outline(state.editingId, true);
7446
- if (_optionalChain([state, 'access', _206 => _206.hover, 'optionalAccess', _207 => _207.nodeId]) && !state.interaction) outline(state.hover.nodeId, false);
7680
+ if (_optionalChain([state, 'access', _228 => _228.hover, 'optionalAccess', _229 => _229.nodeId]) && !state.interaction) outline(state.hover.nodeId, false);
7447
7681
  let selectionBox = null;
7448
- if (state.tool === "select" && state.selectionFrame && _optionalChain([state, 'access', _208 => _208.interaction, 'optionalAccess', _209 => _209.type]) !== "marquee") {
7682
+ if (state.tool === "select" && state.selectionFrame && _optionalChain([state, 'access', _230 => _230.interaction, 'optionalAccess', _231 => _231.type]) !== "marquee") {
7449
7683
  const g = getVectorSelectionBoxGeometry(state.selectionFrame, state.viewport);
7450
7684
  selectionBox = { ...g.box, angle: g.angle, transform: g.transform, corners: g.corners, handles: g.handles };
7451
7685
  }
@@ -7455,9 +7689,9 @@ function getVectorOverlayRenderData(state) {
7455
7689
  const a = getAnchorWorld(doc, ref);
7456
7690
  const s = toScreen(state, a);
7457
7691
  const closes = !!state.pen && state.pen.nodeId === ref.nodeId && state.pen.subpath === ref.subpath && ref.index === 0;
7458
- return { ref, x: s.x, y: s.y, type: _nullishCoalesce(a.type, () => ( inferAnchorType(a))), selected: selected(ref), hovered: sameAnchor(_optionalChain([state, 'access', _210 => _210.hover, 'optionalAccess', _211 => _211.anchor]), ref), closes };
7692
+ return { ref, x: s.x, y: s.y, type: _nullishCoalesce(a.type, () => ( inferAnchorType(a))), selected: selected(ref), hovered: sameAnchor(_optionalChain([state, 'access', _232 => _232.hover, 'optionalAccess', _233 => _233.anchor]), ref), closes };
7459
7693
  });
7460
- if (state.tool === "pen" && !state.pen && _optionalChain([state, 'access', _212 => _212.hover, 'optionalAccess', _213 => _213.anchor])) {
7694
+ if (state.tool === "pen" && !state.pen && _optionalChain([state, 'access', _234 => _234.hover, 'optionalAccess', _235 => _235.anchor])) {
7461
7695
  const a = getAnchorWorld(doc, state.hover.anchor);
7462
7696
  if (a) {
7463
7697
  const s = toScreen(state, a);
@@ -7466,15 +7700,15 @@ function getVectorOverlayRenderData(state) {
7466
7700
  }
7467
7701
  const handles = getVisibleHandles(state).flatMap((ref) => {
7468
7702
  const a = getAnchorWorld(doc, ref);
7469
- const h = _optionalChain([a, 'optionalAccess', _214 => _214[ref.handle]]);
7703
+ const h = _optionalChain([a, 'optionalAccess', _236 => _236[ref.handle]]);
7470
7704
  if (!a || !h) return [];
7471
7705
  const s = toScreen(state, h);
7472
7706
  const sa = toScreen(state, a);
7473
- const hovered = !!_optionalChain([state, 'access', _215 => _215.hover, 'optionalAccess', _216 => _216.handle]) && sameAnchor(state.hover.handle, ref) && state.hover.handle.handle === ref.handle;
7707
+ const hovered = !!_optionalChain([state, 'access', _237 => _237.hover, 'optionalAccess', _238 => _238.handle]) && sameAnchor(state.hover.handle, ref) && state.hover.handle.handle === ref.handle;
7474
7708
  return [{ ref, x: s.x, y: s.y, anchorX: sa.x, anchorY: sa.y, hovered }];
7475
7709
  });
7476
7710
  let marquee = null;
7477
- if (_optionalChain([state, 'access', _217 => _217.interaction, 'optionalAccess', _218 => _218.type]) === "marquee") {
7711
+ if (_optionalChain([state, 'access', _239 => _239.interaction, 'optionalAccess', _240 => _240.type]) === "marquee") {
7478
7712
  const b = state.interaction.bounds;
7479
7713
  const tl = toScreen(state, b);
7480
7714
  marquee = { x: tl.x, y: tl.y, width: b.width * z, height: b.height * z };
@@ -7482,7 +7716,7 @@ function getVectorOverlayRenderData(state) {
7482
7716
  let penPreview = null;
7483
7717
  if (state.tool === "pen" && state.pen && state.pointer && !state.interaction) {
7484
7718
  const node = findVectorNode(doc.nodes, state.pen.nodeId);
7485
- const sp = _optionalChain([node, 'optionalAccess', _219 => _219.type]) === "path" ? node.subpaths[state.pen.subpath] : void 0;
7719
+ const sp = _optionalChain([node, 'optionalAccess', _241 => _241.type]) === "path" ? node.subpaths[state.pen.subpath] : void 0;
7486
7720
  const last = sp ? getAnchorWorld(doc, { nodeId: state.pen.nodeId, subpath: state.pen.subpath, index: sp.anchors.length - 1 }) : void 0;
7487
7721
  if (last) {
7488
7722
  const a = toScreen(state, last);
@@ -7495,9 +7729,9 @@ function getVectorOverlayRenderData(state) {
7495
7729
  const artboard = { x: origin.x, y: origin.y, width: doc.width * z, height: doc.height * z };
7496
7730
  let status = null;
7497
7731
  const it = state.interaction;
7498
- if (_optionalChain([it, 'optionalAccess', _220 => _220.type]) === "rotate") status = `${Math.round(it.angle)}\xB0`;
7499
- else if (_optionalChain([it, 'optionalAccess', _221 => _221.type]) === "move" || _optionalChain([it, 'optionalAccess', _222 => _222.type]) === "anchors") status = `\u0394 ${fmt(it.dx)}, ${fmt(it.dy)}`;
7500
- else if (_optionalChain([it, 'optionalAccess', _223 => _223.type]) === "scale" || _optionalChain([it, 'optionalAccess', _224 => _224.type]) === "draw") {
7732
+ if (_optionalChain([it, 'optionalAccess', _242 => _242.type]) === "rotate") status = `${Math.round(it.angle)}\xB0`;
7733
+ else if (_optionalChain([it, 'optionalAccess', _243 => _243.type]) === "move" || _optionalChain([it, 'optionalAccess', _244 => _244.type]) === "anchors") status = `\u0394 ${fmt(it.dx)}, ${fmt(it.dy)}`;
7734
+ else if (_optionalChain([it, 'optionalAccess', _245 => _245.type]) === "scale" || _optionalChain([it, 'optionalAccess', _246 => _246.type]) === "draw") {
7501
7735
  const b = it.type === "draw" ? findVectorNode(doc.nodes, it.nodeId) ? vectorNodeBounds(findVectorNode(doc.nodes, it.nodeId)) : null : state.selectionFrame;
7502
7736
  if (b) status = `W ${fmt(b.width)} \xD7 H ${fmt(b.height)}`;
7503
7737
  }
@@ -7514,24 +7748,24 @@ function getVectorOverlayRenderData(state) {
7514
7748
  }
7515
7749
  }
7516
7750
  let toolStroke = null;
7517
- if (_optionalChain([it, 'optionalAccess', _225 => _225.type]) === "stroke") {
7751
+ if (_optionalChain([it, 'optionalAccess', _247 => _247.type]) === "stroke") {
7518
7752
  const pts = it.points.map((q) => toScreen(state, q));
7519
7753
  const d = pts.map((q, i) => `${i ? "L" : "M"}${fmt(q.x)} ${fmt(q.y)}`).join("") + (pts.length === 1 ? `L${fmt(pts[0].x)} ${fmt(pts[0].y)}` : "");
7520
7754
  toolStroke = { tool: it.tool, d, width: fmt(it.width * z) };
7521
7755
  }
7522
7756
  let pencilString = null;
7523
- if (_optionalChain([it, 'optionalAccess', _226 => _226.type]) === "draw" && it.string) {
7757
+ if (_optionalChain([it, 'optionalAccess', _248 => _248.type]) === "draw" && it.string) {
7524
7758
  const a = toScreen(state, it.string[0]);
7525
7759
  const b = toScreen(state, it.string[1]);
7526
7760
  pencilString = { x1: fmt(a.x), y1: fmt(a.y), x2: fmt(b.x), y2: fmt(b.y) };
7527
7761
  }
7528
- const dragged = _optionalChain([it, 'optionalAccess', _227 => _227.type]) === "corner" ? it.ref : null;
7762
+ const dragged = _optionalChain([it, 'optionalAccess', _249 => _249.type]) === "corner" ? it.ref : null;
7529
7763
  const cornerWidgets = getVectorCornerWidgets(state).map((w) => {
7530
7764
  const s = toScreen(state, w);
7531
- return { ref: w.ref, x: fmt(s.x), y: fmt(s.y), radius: w.radius, kind: w.kind, hovered: sameAnchor(_optionalChain([state, 'access', _228 => _228.hover, 'optionalAccess', _229 => _229.corner]), w.ref), active: !!dragged };
7765
+ return { ref: w.ref, x: fmt(s.x), y: fmt(s.y), radius: w.radius, kind: w.kind, hovered: sameAnchor(_optionalChain([state, 'access', _250 => _250.hover, 'optionalAccess', _251 => _251.corner]), w.ref), active: !!dragged };
7532
7766
  });
7533
- if (_optionalChain([it, 'optionalAccess', _230 => _230.type]) === "corner") status = `R ${fmt(it.radius)}`;
7534
- const cutPoint = state.tool === "scissors" && _optionalChain([state, 'access', _231 => _231.hover, 'optionalAccess', _232 => _232.segment]) && !it ? (({ x, y }) => ({ x: fmt(x), y: fmt(y) }))(toScreen(state, state.hover.segment.point)) : null;
7767
+ if (_optionalChain([it, 'optionalAccess', _252 => _252.type]) === "corner") status = `R ${fmt(it.radius)}`;
7768
+ const cutPoint = state.tool === "scissors" && _optionalChain([state, 'access', _253 => _253.hover, 'optionalAccess', _254 => _254.segment]) && !it ? (({ x, y }) => ({ x: fmt(x), y: fmt(y) }))(toScreen(state, state.hover.segment.point)) : null;
7535
7769
  const sc = (p) => {
7536
7770
  const q = toScreen(state, p);
7537
7771
  return { x: fmt(q.x), y: fmt(q.y) };
@@ -7539,13 +7773,13 @@ function getVectorOverlayRenderData(state) {
7539
7773
  const sameWidth = (a, b) => !!a && a.nodeId === b.nodeId && a.index === b.index;
7540
7774
  const widthPoints = getVectorWidthHandles(state).map((h) => {
7541
7775
  const c = sc(h);
7542
- const hw = _optionalChain([state, 'access', _233 => _233.hover, 'optionalAccess', _234 => _234.widthPoint]);
7776
+ const hw = _optionalChain([state, 'access', _255 => _255.hover, 'optionalAccess', _256 => _256.widthPoint]);
7543
7777
  const hovered = hw && sameWidth(hw, h.ref) && hw.subpath === h.ref.subpath ? hw.part : null;
7544
- return { ref: h.ref, x: c.x, y: c.y, left: sc(h.left), right: sc(h.right), selected: h.selected, hovered, active: _optionalChain([it, 'optionalAccess', _235 => _235.type]) === "width" && sameWidth(it.ref, h.ref) };
7778
+ return { ref: h.ref, x: c.x, y: c.y, left: sc(h.left), right: sc(h.right), selected: h.selected, hovered, active: _optionalChain([it, 'optionalAccess', _257 => _257.type]) === "width" && sameWidth(it.ref, h.ref) };
7545
7779
  });
7546
- const spot = state.tool === "width" && !it ? _optionalChain([state, 'access', _236 => _236.hover, 'optionalAccess', _237 => _237.strokeSpot]) : null;
7780
+ const spot = state.tool === "width" && !it ? _optionalChain([state, 'access', _258 => _258.hover, 'optionalAccess', _259 => _259.strokeSpot]) : null;
7547
7781
  const widthSpot = spot ? { ...sc(spot.point), left: sc(spot.left), right: sc(spot.right) } : null;
7548
- if (_optionalChain([it, 'optionalAccess', _238 => _238.type]) === "width") status = it.mode === "move" ? "Move width point" : `W ${fmt(it.width)}`;
7782
+ if (_optionalChain([it, 'optionalAccess', _260 => _260.type]) === "width") status = it.mode === "move" ? "Move width point" : it.mode === "copy" ? "Copy width point" : `W ${fmt(it.width)}`;
7549
7783
  let builder = null;
7550
7784
  if (state.builder) {
7551
7785
  const v2 = state.viewport;
@@ -7561,7 +7795,7 @@ function getVectorOverlayRenderData(state) {
7561
7795
  lines: state.builder.lines.map((l) => svg(_chunkVNYRI265cjs.transformBezierPath.call(void 0, l, view))),
7562
7796
  trail
7563
7797
  };
7564
- if (state.builder.mode !== "fill" && _optionalChain([it, 'optionalAccess', _239 => _239.type]) === "builder") {
7798
+ if (state.builder.mode !== "fill" && _optionalChain([it, 'optionalAccess', _261 => _261.type]) === "builder") {
7565
7799
  const n = state.builder.faces.length;
7566
7800
  status = state.builder.mode === "erase" ? `Delete ${n} ${n === 1 ? "face" : "faces"}` : `Merge ${n} ${n === 1 ? "face" : "faces"}`;
7567
7801
  }
@@ -7613,7 +7847,7 @@ function isVectorTextTarget(target) {
7613
7847
  if (!el || typeof el !== "object") return false;
7614
7848
  const tag = (_nullishCoalesce(el.tagName, () => ( ""))).toUpperCase();
7615
7849
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
7616
- return !!el.isContentEditable || _optionalChain([el, 'access', _240 => _240.getAttribute, 'optionalCall', _241 => _241("contenteditable")]) === "true";
7850
+ return !!el.isContentEditable || _optionalChain([el, 'access', _262 => _262.getAttribute, 'optionalCall', _263 => _263("contenteditable")]) === "true";
7617
7851
  }
7618
7852
  function getVectorSelectionStyle(state) {
7619
7853
  const paths = [];
@@ -7621,12 +7855,12 @@ function getVectorSelectionStyle(state) {
7621
7855
  const node = findVectorNode(state.document.nodes, id);
7622
7856
  if (node) paths.push(...collect(node));
7623
7857
  }
7624
- const anchorMode = state.tool === "direct" && !!_optionalChain([state, 'access', _242 => _242.selectedAnchors, 'optionalAccess', _243 => _243.length]);
7858
+ const anchorMode = state.tool === "direct" && !!_optionalChain([state, 'access', _264 => _264.selectedAnchors, 'optionalAccess', _265 => _265.length]);
7625
7859
  if (anchorMode) {
7626
7860
  for (const r of state.selectedAnchors) {
7627
7861
  if (paths.some((p) => p.id === r.nodeId)) continue;
7628
7862
  const node = findVectorNode(state.document.nodes, r.nodeId);
7629
- if (_optionalChain([node, 'optionalAccess', _244 => _244.type]) === "path") paths.push(node);
7863
+ if (_optionalChain([node, 'optionalAccess', _266 => _266.type]) === "path") paths.push(node);
7630
7864
  }
7631
7865
  }
7632
7866
  if (!paths.length) return null;
@@ -7637,7 +7871,7 @@ function getVectorSelectionStyle(state) {
7637
7871
  for (const c of vectorCorners(p.subpaths)) {
7638
7872
  if (anchorMode && !state.selectedAnchors.some((r) => r.nodeId === p.id && r.subpath === c.subpath && r.index === c.index)) continue;
7639
7873
  const corner = p.subpaths[c.subpath].anchors[c.index].corner;
7640
- corners2.push({ radius: _nullishCoalesce(_optionalChain([corner, 'optionalAccess', _245 => _245.radius]), () => ( 0)), kind: _nullishCoalesce(_optionalChain([corner, 'optionalAccess', _246 => _246.kind]), () => ( "round")) });
7874
+ corners2.push({ radius: _nullishCoalesce(_optionalChain([corner, 'optionalAccess', _267 => _267.radius]), () => ( 0)), kind: _nullishCoalesce(_optionalChain([corner, 'optionalAccess', _268 => _268.kind]), () => ( "round")) });
7641
7875
  }
7642
7876
  }
7643
7877
  const c0 = _nullishCoalesce(corners2[0], () => ( { radius: 0, kind: "round" }));
@@ -7785,17 +8019,17 @@ function createVectorCommands(controller, options = {}) {
7785
8019
  const base = { group, ...options.scope ? { scope: options.scope } : {} };
7786
8020
  const hasSelection = () => controller.getState().selection.length ? true : "Nothing is selected.";
7787
8021
  const ids = (params) => {
7788
- const list2 = _nullishCoalesce(_optionalChain([params, 'optionalAccess', _247 => _247.ids]), () => ( controller.getState().selection));
8022
+ const list2 = _nullishCoalesce(_optionalChain([params, 'optionalAccess', _269 => _269.ids]), () => ( controller.getState().selection));
7789
8023
  for (const id of list2) if (!controller.getNode(id)) fail(`There is no node "${id}".`);
7790
8024
  if (!list2.length) fail("Nothing is selected; pass ids.");
7791
8025
  return list2;
7792
8026
  };
7793
- const enabledFor = (_, params) => _optionalChain([params, 'optionalAccess', _248 => _248.ids, 'optionalAccess', _249 => _249.length]) ? true : hasSelection();
8027
+ const enabledFor = (_, params) => _optionalChain([params, 'optionalAccess', _270 => _270.ids, 'optionalAccess', _271 => _271.length]) ? true : hasSelection();
7794
8028
  const anchorsOf = (refs) => {
7795
8029
  if (!refs) return [...controller.getState().selectedAnchors];
7796
8030
  for (const r of refs) {
7797
8031
  const node = controller.getNode(r.nodeId);
7798
- if (_optionalChain([node, 'optionalAccess', _250 => _250.type]) !== "path" || !_optionalChain([node, 'access', _251 => _251.subpaths, 'access', _252 => _252[r.subpath], 'optionalAccess', _253 => _253.anchors, 'access', _254 => _254[r.index]])) fail(`There is no anchor ${r.index} on subpath ${r.subpath} of "${r.nodeId}".`);
8032
+ if (_optionalChain([node, 'optionalAccess', _272 => _272.type]) !== "path" || !_optionalChain([node, 'access', _273 => _273.subpaths, 'access', _274 => _274[r.subpath], 'optionalAccess', _275 => _275.anchors, 'access', _276 => _276[r.index]])) fail(`There is no anchor ${r.index} on subpath ${r.subpath} of "${r.nodeId}".`);
7799
8033
  }
7800
8034
  return refs;
7801
8035
  };
@@ -7886,7 +8120,7 @@ function createVectorCommands(controller, options = {}) {
7886
8120
  run: (_ctx, params) => {
7887
8121
  const id = controller.addPath(params.d, { ...pickStyle(params), ...params.name ? { name: params.name } : {} });
7888
8122
  const node = controller.getNode(id);
7889
- if (_optionalChain([node, 'optionalAccess', _255 => _255.type]) === "path" && !node.subpaths.length) {
8123
+ if (_optionalChain([node, 'optionalAccess', _277 => _277.type]) === "path" && !node.subpaths.length) {
7890
8124
  controller.undo();
7891
8125
  fail("The path data has no points.");
7892
8126
  }
@@ -7918,10 +8152,10 @@ function createVectorCommands(controller, options = {}) {
7918
8152
  params: { type: "object", properties: { radius: { type: "number", minimum: 0 }, kind: { type: "string", enum: VECTOR_CORNER_KINDS }, ids: IDS, anchors: ANCHORS } },
7919
8153
  undoable: true,
7920
8154
  run: (_ctx, params) => {
7921
- const refs = _optionalChain([params, 'optionalAccess', _256 => _256.anchors]) ? anchorsOf(params.anchors) : void 0;
7922
- const target = { refs, ids: _optionalChain([params, 'optionalAccess', _257 => _257.ids]) ? ids(params) : void 0 };
7923
- if (_optionalChain([params, 'optionalAccess', _258 => _258.radius]) !== void 0) check(controller.setCornerRadius(params.radius, { ...target, kind: params.kind }));
7924
- else if (_optionalChain([params, 'optionalAccess', _259 => _259.kind])) check(controller.setCornerKind(params.kind, target));
8155
+ const refs = _optionalChain([params, 'optionalAccess', _278 => _278.anchors]) ? anchorsOf(params.anchors) : void 0;
8156
+ const target = { refs, ids: _optionalChain([params, 'optionalAccess', _279 => _279.ids]) ? ids(params) : void 0 };
8157
+ if (_optionalChain([params, 'optionalAccess', _280 => _280.radius]) !== void 0) check(controller.setCornerRadius(params.radius, { ...target, kind: params.kind }));
8158
+ else if (_optionalChain([params, 'optionalAccess', _281 => _281.kind])) check(controller.setCornerKind(params.kind, target));
7925
8159
  else fail("Pass `radius` and / or `kind`.");
7926
8160
  return { ok: true };
7927
8161
  }
@@ -7956,12 +8190,37 @@ function createVectorCommands(controller, options = {}) {
7956
8190
  undoable: true,
7957
8191
  run: (_ctx, params) => {
7958
8192
  const target = { ids: ids(params) };
7959
- if (_optionalChain([params, 'optionalAccess', _260 => _260.preset])) check(controller.setWidthProfile(params.preset, target));
7960
- else if (_optionalChain([params, 'optionalAccess', _261 => _261.points]) !== void 0) check(controller.setWidthProfile(_nullishCoalesce(params.points, () => ( null)), target));
7961
- else if (_optionalChain([params, 'optionalAccess', _262 => _262.taper]) || _optionalChain([params, 'optionalAccess', _263 => _263.wave])) check(controller.setWidthProfile({ taper: params.taper, wave: params.wave }, target));
8193
+ if (_optionalChain([params, 'optionalAccess', _282 => _282.preset])) check(controller.setWidthProfile(params.preset, target));
8194
+ else if (_optionalChain([params, 'optionalAccess', _283 => _283.points]) !== void 0) check(controller.setWidthProfile(_nullishCoalesce(params.points, () => ( null)), target));
8195
+ else if (_optionalChain([params, 'optionalAccess', _284 => _284.taper]) || _optionalChain([params, 'optionalAccess', _285 => _285.wave])) check(controller.setWidthProfile({ taper: params.taper, wave: params.wave }, target));
7962
8196
  else fail("Pass `preset`, `points`, `taper` or `wave`.");
7963
8197
  const node = controller.getNode(target.ids[0]);
7964
- return { ok: true, points: _optionalChain([node, 'optionalAccess', _264 => _264.type]) === "path" ? _nullishCoalesce(node.strokeWidthProfile, () => ( [])) : [] };
8198
+ return { ok: true, points: _optionalChain([node, 'optionalAccess', _286 => _286.type]) === "path" ? _nullishCoalesce(node.strokeWidthProfile, () => ( [])) : [] };
8199
+ }
8200
+ },
8201
+ {
8202
+ ...base,
8203
+ id: `${prefix}setWidthPoint`,
8204
+ label: "Width Point\u2026",
8205
+ keywords: ["width point", "variable width", "stroke width at", "taper", "profile"],
8206
+ description: "Sets one width point of a path's stroke profile numerically: `index` names an existing point (see `vector.setWidthProfile`'s result), leave it out to add one at `t`. `left` / `right` are side widths in document units (how far the stroke reaches on each side), `t` the position along each subpath (0\u20131); a point moved onto another's `t` makes a step. Returns the profile.",
8207
+ params: {
8208
+ type: "object",
8209
+ properties: {
8210
+ id: { type: "string", description: "The path." },
8211
+ index: { type: "integer", minimum: 0 },
8212
+ t: { type: "number", minimum: 0, maximum: 1 },
8213
+ left: { type: "number", minimum: 0 },
8214
+ right: { type: "number", minimum: 0 }
8215
+ },
8216
+ required: ["id"]
8217
+ },
8218
+ undoable: true,
8219
+ run: (_ctx, params) => {
8220
+ if (!_optionalChain([params, 'optionalAccess', _287 => _287.id])) fail("Pass the path `id`.");
8221
+ check(controller.setWidthPoint({ nodeId: params.id, index: _nullishCoalesce(params.index, () => ( null)) }, { t: params.t, left: params.left, right: params.right }));
8222
+ const node = controller.getNode(params.id);
8223
+ return { ok: true, points: _optionalChain([node, 'optionalAccess', _288 => _288.type]) === "path" ? _nullishCoalesce(node.strokeWidthProfile, () => ( [])) : [] };
7965
8224
  }
7966
8225
  },
7967
8226
  {
@@ -7974,7 +8233,7 @@ function createVectorCommands(controller, options = {}) {
7974
8233
  enabled: enabledFor,
7975
8234
  undoable: true,
7976
8235
  run: (_ctx, params) => {
7977
- const created = controller.outlineStroke({ ids: _optionalChain([params, 'optionalAccess', _265 => _265.ids]) ? ids(params) : void 0 });
8236
+ const created = controller.outlineStroke({ ids: _optionalChain([params, 'optionalAccess', _289 => _289.ids]) ? ids(params) : void 0 });
7978
8237
  if (!created.length) fail("No selected path has a stroke.");
7979
8238
  return { ids: created };
7980
8239
  }
@@ -8145,7 +8404,7 @@ function createVectorCommands(controller, options = {}) {
8145
8404
  enabled: enabledFor,
8146
8405
  undoable: true,
8147
8406
  run: (_ctx, params) => {
8148
- const id = controller.group({ ids: ids(params), name: _optionalChain([params, 'optionalAccess', _266 => _266.name]) });
8407
+ const id = controller.group({ ids: ids(params), name: _optionalChain([params, 'optionalAccess', _290 => _290.name]) });
8149
8408
  if (!id) fail("Only nodes that share a parent can be grouped.");
8150
8409
  return { id };
8151
8410
  }
@@ -8173,7 +8432,7 @@ function createVectorCommands(controller, options = {}) {
8173
8432
  params: { type: "object", properties: { anchors: ANCHORS, keepShape: { type: "boolean" } } },
8174
8433
  undoable: true,
8175
8434
  run: (_ctx, params) => {
8176
- check(controller.deleteAnchors(anchorsOf(_optionalChain([params, 'optionalAccess', _267 => _267.anchors])), { keepShape: _optionalChain([params, 'optionalAccess', _268 => _268.keepShape]) }));
8435
+ check(controller.deleteAnchors(anchorsOf(_optionalChain([params, 'optionalAccess', _291 => _291.anchors])), { keepShape: _optionalChain([params, 'optionalAccess', _292 => _292.keepShape]) }));
8177
8436
  return { ok: true };
8178
8437
  }
8179
8438
  },
@@ -8228,7 +8487,7 @@ function createVectorCommands(controller, options = {}) {
8228
8487
  params: { type: "object", properties: { ids: IDS, tolerance: { type: "number", minimum: 0 } } },
8229
8488
  undoable: true,
8230
8489
  run: (_ctx, params) => {
8231
- check(controller.joinPaths({ ids: _optionalChain([params, 'optionalAccess', _269 => _269.ids]) ? ids(params) : void 0, tolerance: _optionalChain([params, 'optionalAccess', _270 => _270.tolerance]) }));
8490
+ check(controller.joinPaths({ ids: _optionalChain([params, 'optionalAccess', _293 => _293.ids]) ? ids(params) : void 0, tolerance: _optionalChain([params, 'optionalAccess', _294 => _294.tolerance]) }));
8232
8491
  return { selection: controller.getState().selection };
8233
8492
  }
8234
8493
  },
@@ -8242,7 +8501,7 @@ function createVectorCommands(controller, options = {}) {
8242
8501
  params: { type: "object", properties: { axis: { type: "string", enum: ["horizontal", "vertical", "both"] }, anchors: ANCHORS } },
8243
8502
  undoable: true,
8244
8503
  run: (_ctx, params) => {
8245
- check(controller.averageAnchors(_nullishCoalesce(_optionalChain([params, 'optionalAccess', _271 => _271.axis]), () => ( "both")), anchorsOf(_optionalChain([params, 'optionalAccess', _272 => _272.anchors]))));
8504
+ check(controller.averageAnchors(_nullishCoalesce(_optionalChain([params, 'optionalAccess', _295 => _295.axis]), () => ( "both")), anchorsOf(_optionalChain([params, 'optionalAccess', _296 => _296.anchors]))));
8246
8505
  return { ok: true };
8247
8506
  }
8248
8507
  },
@@ -8256,7 +8515,7 @@ function createVectorCommands(controller, options = {}) {
8256
8515
  enabled: enabledFor,
8257
8516
  undoable: true,
8258
8517
  run: (_ctx, params) => {
8259
- check(controller.removeRedundantPoints({ ids: _optionalChain([params, 'optionalAccess', _273 => _273.ids]) ? ids(params) : void 0, tolerance: _optionalChain([params, 'optionalAccess', _274 => _274.tolerance]) }));
8518
+ check(controller.removeRedundantPoints({ ids: _optionalChain([params, 'optionalAccess', _297 => _297.ids]) ? ids(params) : void 0, tolerance: _optionalChain([params, 'optionalAccess', _298 => _298.tolerance]) }));
8260
8519
  return { ok: true };
8261
8520
  }
8262
8521
  },
@@ -8269,7 +8528,7 @@ function createVectorCommands(controller, options = {}) {
8269
8528
  params: { type: "object", properties: { ids: IDS, amount: { type: "number", minimum: 0, maximum: 1 }, anchors: ANCHORS } },
8270
8529
  undoable: true,
8271
8530
  run: (_ctx, params) => {
8272
- check(controller.smoothPaths({ ids: _optionalChain([params, 'optionalAccess', _275 => _275.ids]) ? ids(params) : void 0, amount: _optionalChain([params, 'optionalAccess', _276 => _276.amount]), refs: _optionalChain([params, 'optionalAccess', _277 => _277.anchors]) }));
8531
+ check(controller.smoothPaths({ ids: _optionalChain([params, 'optionalAccess', _299 => _299.ids]) ? ids(params) : void 0, amount: _optionalChain([params, 'optionalAccess', _300 => _300.amount]), refs: _optionalChain([params, 'optionalAccess', _301 => _301.anchors]) }));
8273
8532
  return { ok: true };
8274
8533
  }
8275
8534
  },
@@ -8282,14 +8541,14 @@ function createVectorCommands(controller, options = {}) {
8282
8541
  params: { type: "object", properties: { nodeId: { type: "string" }, x: { type: "number" }, y: { type: "number" }, anchors: ANCHORS } },
8283
8542
  undoable: true,
8284
8543
  run: (_ctx, params) => {
8285
- if (_optionalChain([params, 'optionalAccess', _278 => _278.nodeId]) !== void 0) {
8544
+ if (_optionalChain([params, 'optionalAccess', _302 => _302.nodeId]) !== void 0) {
8286
8545
  if (!controller.getNode(params.nodeId)) fail(`There is no node "${params.nodeId}".`);
8287
8546
  if (params.x === void 0 || params.y === void 0) fail("Pass x and y with nodeId.");
8288
8547
  const created = controller.cutPathAt(params.nodeId, { x: params.x, y: params.y });
8289
8548
  if (!created) fail("Nothing to cut there (the end of an open path, or a locked path).");
8290
8549
  return { ids: created };
8291
8550
  }
8292
- check(controller.cutAtAnchors(anchorsOf(_optionalChain([params, 'optionalAccess', _279 => _279.anchors]))));
8551
+ check(controller.cutAtAnchors(anchorsOf(_optionalChain([params, 'optionalAccess', _303 => _303.anchors]))));
8293
8552
  return { ids: controller.getState().selection };
8294
8553
  }
8295
8554
  },
@@ -8340,7 +8599,7 @@ function createVectorCommands(controller, options = {}) {
8340
8599
  },
8341
8600
  run: (_ctx, params) => {
8342
8601
  const patch = {};
8343
- for (const key of ["keepShapeOnDelete", "curvatureMoves", "pencilSmoothing", "recognizeShapes", "eraserSize", "pencilPressure"]) if (_optionalChain([params, 'optionalAccess', _280 => _280[key]]) !== void 0) patch[key] = params[key];
8602
+ for (const key of ["keepShapeOnDelete", "curvatureMoves", "pencilSmoothing", "recognizeShapes", "eraserSize", "pencilPressure"]) if (_optionalChain([params, 'optionalAccess', _304 => _304[key]]) !== void 0) patch[key] = params[key];
8344
8603
  controller.setEditing(patch);
8345
8604
  return { editing: controller.getState().editing };
8346
8605
  }
@@ -8365,7 +8624,7 @@ function createVectorCommands(controller, options = {}) {
8365
8624
  keywords: ["download", "save", "markup"],
8366
8625
  description: "Returns the document as SVG markup.",
8367
8626
  params: { type: "object", properties: { precision: { type: "integer", minimum: 0, maximum: 8 } } },
8368
- run: (_ctx, params) => ({ svg: controller.toSvg({ precision: _optionalChain([params, 'optionalAccess', _281 => _281.precision]) }) })
8627
+ run: (_ctx, params) => ({ svg: controller.toSvg({ precision: _optionalChain([params, 'optionalAccess', _305 => _305.precision]) }) })
8369
8628
  },
8370
8629
  {
8371
8630
  ...base,