@rogieking/figui3 3.18.0 → 3.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/fig.js CHANGED
@@ -587,6 +587,10 @@ class FigTooltip extends HTMLElement {
587
587
  this.popup.style.visibility = "hidden";
588
588
  this.popup.style.display = "inline-flex";
589
589
  this.popup.style.pointerEvents = "none";
590
+ const theme = this.getAttribute("theme");
591
+ if (theme) this.popup.setAttribute("theme", theme);
592
+ const pointer = this.getAttribute("pointer");
593
+ if (pointer !== null) this.popup.setAttribute("pointer", pointer);
590
594
  this.popup.append(content);
591
595
  content.innerText = this.getAttribute("text");
592
596
  // Set aria-describedby on the trigger element
@@ -851,7 +855,7 @@ class FigTooltip extends HTMLElement {
851
855
  }
852
856
 
853
857
  static get observedAttributes() {
854
- return ["action", "delay", "open", "show", "text"];
858
+ return ["action", "delay", "open", "pointer", "show", "text", "theme"];
855
859
  }
856
860
  get text() {
857
861
  return this.getAttribute("text") ?? "";
@@ -910,6 +914,18 @@ class FigTooltip extends HTMLElement {
910
914
  if (name === "text") {
911
915
  this.#updateText(newValue ?? "");
912
916
  }
917
+ if (name === "pointer") {
918
+ if (this.popup) {
919
+ if (newValue !== null) this.popup.setAttribute("pointer", newValue);
920
+ else this.popup.removeAttribute("pointer");
921
+ }
922
+ }
923
+ if (name === "theme") {
924
+ if (this.popup) {
925
+ if (newValue) this.popup.setAttribute("theme", newValue);
926
+ else this.popup.removeAttribute("theme");
927
+ }
928
+ }
913
929
  }
914
930
 
915
931
  #hideOnChromeOpen(e) {
@@ -13343,8 +13359,8 @@ class FigChooser extends HTMLElement {
13343
13359
  }
13344
13360
  customElements.define("fig-chooser", FigChooser);
13345
13361
 
13346
- /* Canvas Point */
13347
- class FigCanvasPoint extends HTMLElement {
13362
+ /* Canvas Control */
13363
+ class FigCanvasControl extends HTMLElement {
13348
13364
  static observedAttributes = [
13349
13365
  "type",
13350
13366
  "value",
@@ -13358,19 +13374,25 @@ class FigCanvasPoint extends HTMLElement {
13358
13374
 
13359
13375
  #x = 50;
13360
13376
  #y = 50;
13377
+ #x2 = 75;
13378
+ #y2 = 75;
13361
13379
  #radius = 0;
13362
13380
  #radiusIsPercent = false;
13363
13381
  #angle = 0;
13364
13382
  #pointHandle = null;
13383
+ #secondHandle = null;
13365
13384
  #angleHandle = null;
13366
13385
  #radiusSvg = null;
13367
13386
  #angleSvg = null;
13368
13387
  #pointTooltip = null;
13388
+ #secondTooltip = null;
13369
13389
  #radiusTooltip = null;
13370
13390
  #angleTooltip = null;
13371
13391
  #isDragging = false;
13392
+ #isSecondDragging = false;
13372
13393
  #isRadiusDragging = false;
13373
13394
  #isAngleDragging = false;
13395
+ #prevBodyCursor = "";
13374
13396
 
13375
13397
  get #type() {
13376
13398
  return this.getAttribute("type") || "point";
@@ -13384,6 +13406,14 @@ class FigCanvasPoint extends HTMLElement {
13384
13406
  return this.#type === "point-radius-angle";
13385
13407
  }
13386
13408
 
13409
+ get #hasSecondPoint() {
13410
+ return this.#type === "point-point";
13411
+ }
13412
+
13413
+ get #hasLine() {
13414
+ return this.#type === "point-radius-angle" || this.#type === "point-point";
13415
+ }
13416
+
13387
13417
  get #tooltipsEnabled() {
13388
13418
  const v = this.getAttribute("tooltips");
13389
13419
  return v === null || v !== "false";
@@ -13407,10 +13437,22 @@ class FigCanvasPoint extends HTMLElement {
13407
13437
 
13408
13438
  get #pointTipText() {
13409
13439
  const name = this.getAttribute("name");
13410
- if (name) return name;
13440
+ if (name) {
13441
+ const parts = name.split(",");
13442
+ return parts[0].trim();
13443
+ }
13411
13444
  return `${Math.round(this.#x)}%, ${Math.round(this.#y)}%`;
13412
13445
  }
13413
13446
 
13447
+ get #secondTipText() {
13448
+ const name = this.getAttribute("name");
13449
+ if (name) {
13450
+ const parts = name.split(",");
13451
+ if (parts.length > 1) return parts[1].trim();
13452
+ }
13453
+ return `${Math.round(this.#x2)}%, ${Math.round(this.#y2)}%`;
13454
+ }
13455
+
13414
13456
  get #dragSurface() {
13415
13457
  return this.getAttribute("drag-surface") || "parent";
13416
13458
  }
@@ -13426,8 +13468,8 @@ class FigCanvasPoint extends HTMLElement {
13426
13468
  if (surface === "parent") {
13427
13469
  const container = this.parentElement;
13428
13470
  if (container) {
13429
- container.setAttribute("data-fig-canvas-point-surface", "");
13430
- return "[data-fig-canvas-point-surface]";
13471
+ container.setAttribute("data-fig-canvas-control-surface", "");
13472
+ return "[data-fig-canvas-control-surface]";
13431
13473
  }
13432
13474
  }
13433
13475
  return surface;
@@ -13439,8 +13481,8 @@ class FigCanvasPoint extends HTMLElement {
13439
13481
  }
13440
13482
 
13441
13483
  #formatRadius() {
13442
- if (this.#radiusIsPercent) return `${Math.round(this.#radius)}%`;
13443
- return `${Math.round(this.#radius)}px`;
13484
+ if (this.#radiusIsPercent) return `Radius ${Math.round(this.#radius)}%`;
13485
+ return `Radius ${Math.round(this.#radius)}`;
13444
13486
  }
13445
13487
 
13446
13488
  connectedCallback() {
@@ -13454,7 +13496,7 @@ class FigCanvasPoint extends HTMLElement {
13454
13496
 
13455
13497
  attributeChangedCallback(name, oldVal, newVal) {
13456
13498
  if (oldVal === newVal) return;
13457
- if (name === "value" && !this.#isDragging && !this.#isRadiusDragging && !this.#isAngleDragging) {
13499
+ if (name === "value" && !this.#isDragging && !this.#isSecondDragging && !this.#isRadiusDragging && !this.#isAngleDragging) {
13458
13500
  this.#parseValue();
13459
13501
  if (this.#pointHandle) this.#syncPositions();
13460
13502
  else this.#render();
@@ -13475,9 +13517,11 @@ class FigCanvasPoint extends HTMLElement {
13475
13517
  }
13476
13518
  if (name === "snapping" && this.#pointHandle) {
13477
13519
  this.#pointHandle.setAttribute("drag-snapping", newVal || "false");
13520
+ if (this.#secondHandle) this.#secondHandle.setAttribute("drag-snapping", newVal || "false");
13478
13521
  }
13479
- if (name === "name" && this.#pointTooltip) {
13480
- this.#pointTooltip.setAttribute("text", this.#pointTipText);
13522
+ if (name === "name") {
13523
+ if (this.#pointTooltip) this.#pointTooltip.setAttribute("text", this.#pointTipText);
13524
+ if (this.#secondTooltip) this.#secondTooltip.setAttribute("text", this.#secondTipText);
13481
13525
  }
13482
13526
  }
13483
13527
 
@@ -13500,15 +13544,25 @@ class FigCanvasPoint extends HTMLElement {
13500
13544
  if (!Number.isFinite(this.#radius)) this.#radius = 0;
13501
13545
  }
13502
13546
  if (typeof v.angle === "number") this.#angle = v.angle;
13547
+ if (typeof v.x2 === "number") this.#x2 = v.x2;
13548
+ if (typeof v.y2 === "number") this.#y2 = v.y2;
13503
13549
  } catch { /* ignore */ }
13504
13550
  }
13505
13551
 
13506
13552
  get value() {
13507
13553
  const v = { x: this.#x, y: this.#y };
13554
+ if (this.#type === "color") {
13555
+ const color = this.getAttribute("color") || this.#pointHandle?.getAttribute("color");
13556
+ if (color) v.color = color;
13557
+ }
13508
13558
  if (this.#hasRadius) {
13509
13559
  v.radius = this.#radiusIsPercent ? `${this.#radius}%` : this.#radius;
13510
13560
  }
13511
13561
  if (this.#hasAngle) v.angle = this.#angle;
13562
+ if (this.#hasSecondPoint) {
13563
+ v.x2 = this.#x2;
13564
+ v.y2 = this.#y2;
13565
+ }
13512
13566
  return v;
13513
13567
  }
13514
13568
 
@@ -13523,10 +13577,12 @@ class FigCanvasPoint extends HTMLElement {
13523
13577
  #render() {
13524
13578
  this.innerHTML = "";
13525
13579
  this.#pointHandle = null;
13580
+ this.#secondHandle = null;
13526
13581
  this.#angleHandle = null;
13527
13582
  this.#radiusSvg = null;
13528
13583
  this.#angleSvg = null;
13529
13584
  this.#pointTooltip = null;
13585
+ this.#secondTooltip = null;
13530
13586
  this.#radiusTooltip = null;
13531
13587
  this.#angleTooltip = null;
13532
13588
 
@@ -13548,19 +13604,25 @@ class FigCanvasPoint extends HTMLElement {
13548
13604
  const color = this.getAttribute("color");
13549
13605
  if (color) handle.setAttribute("color", color);
13550
13606
  }
13607
+ if (this.#hasSecondPoint) {
13608
+ handle.setAttribute("hit-area", "12 circle");
13609
+ handle.setAttribute("hit-area-mode", "delegate");
13610
+ }
13551
13611
  this.#pointHandle = handle;
13552
13612
 
13553
13613
  if (this.#hasRadius) {
13554
13614
  this.#createRadiusSvg();
13555
13615
  }
13556
13616
 
13557
- if (this.#hasAngle) {
13617
+ if (this.#hasLine) {
13558
13618
  this.#createAngleSvg();
13559
13619
  }
13560
13620
 
13561
13621
  if (tooltips) {
13562
13622
  const tip = document.createElement("fig-tooltip");
13563
13623
  tip.setAttribute("action", "manual");
13624
+ tip.setAttribute("theme", "brand");
13625
+ tip.setAttribute("pointer", "false");
13564
13626
  tip.setAttribute("text", this.#pointTipText);
13565
13627
  tip.appendChild(handle);
13566
13628
  this.appendChild(tip);
@@ -13573,6 +13635,10 @@ class FigCanvasPoint extends HTMLElement {
13573
13635
  this.#createAngleHandle(disabled, tooltips, handleSurface);
13574
13636
  }
13575
13637
 
13638
+ if (this.#hasSecondPoint) {
13639
+ this.#createSecondHandle(disabled, tooltips, handleSurface);
13640
+ }
13641
+
13576
13642
  this.#setupEventListeners();
13577
13643
  requestAnimationFrame(() => this.#syncPositions());
13578
13644
  }
@@ -13580,10 +13646,10 @@ class FigCanvasPoint extends HTMLElement {
13580
13646
  #createRadiusSvg() {
13581
13647
  const ns = "http://www.w3.org/2000/svg";
13582
13648
  const svg = document.createElementNS(ns, "svg");
13583
- svg.classList.add("fig-canvas-point-radius");
13649
+ svg.classList.add("fig-canvas-control-radius");
13584
13650
  svg.setAttribute("overflow", "visible");
13585
13651
  const hitCircle = document.createElementNS(ns, "circle");
13586
- hitCircle.classList.add("fig-canvas-point-radius-hit");
13652
+ hitCircle.classList.add("fig-canvas-control-radius-hit");
13587
13653
  svg.appendChild(hitCircle);
13588
13654
  const circle = document.createElementNS(ns, "circle");
13589
13655
  svg.appendChild(circle);
@@ -13592,6 +13658,8 @@ class FigCanvasPoint extends HTMLElement {
13592
13658
  if (this.#tooltipsEnabled) {
13593
13659
  const tip = document.createElement("fig-tooltip");
13594
13660
  tip.setAttribute("action", "manual");
13661
+ tip.setAttribute("theme", "brand");
13662
+ tip.setAttribute("pointer", "false");
13595
13663
  tip.setAttribute("text", this.#formatRadius());
13596
13664
  tip.appendChild(svg);
13597
13665
  this.appendChild(tip);
@@ -13606,12 +13674,12 @@ class FigCanvasPoint extends HTMLElement {
13606
13674
  #createAngleSvg() {
13607
13675
  const ns = "http://www.w3.org/2000/svg";
13608
13676
  const svg = document.createElementNS(ns, "svg");
13609
- svg.classList.add("fig-canvas-point-angle-svg");
13677
+ svg.classList.add("fig-canvas-control-angle-svg");
13610
13678
  svg.setAttribute("overflow", "visible");
13611
13679
  svg.style.position = "absolute";
13612
13680
  svg.style.pointerEvents = "none";
13613
13681
  const line = document.createElementNS(ns, "line");
13614
- line.classList.add("fig-canvas-point-angle-line");
13682
+ line.classList.add("fig-canvas-control-angle-line");
13615
13683
  svg.appendChild(line);
13616
13684
  this.#angleSvg = svg;
13617
13685
  this.appendChild(svg);
@@ -13631,6 +13699,8 @@ class FigCanvasPoint extends HTMLElement {
13631
13699
  if (tooltips) {
13632
13700
  const tip = document.createElement("fig-tooltip");
13633
13701
  tip.setAttribute("action", "manual");
13702
+ tip.setAttribute("theme", "brand");
13703
+ tip.setAttribute("pointer", "false");
13634
13704
  tip.setAttribute("text", `${Math.round(this.#angle)}°`);
13635
13705
  tip.appendChild(handle);
13636
13706
  this.appendChild(tip);
@@ -13640,6 +13710,32 @@ class FigCanvasPoint extends HTMLElement {
13640
13710
  }
13641
13711
  }
13642
13712
 
13713
+ #createSecondHandle(disabled, tooltips, handleSurface) {
13714
+ const handle = document.createElement("fig-handle");
13715
+ handle.setAttribute("drag", "true");
13716
+ handle.setAttribute("drag-surface", handleSurface);
13717
+ handle.setAttribute("drag-axes", "x,y");
13718
+ handle.setAttribute("drag-snapping", this.#snappingMode);
13719
+ handle.setAttribute("hit-area", "12 circle");
13720
+ handle.setAttribute("hit-area-mode", "delegate");
13721
+ handle.setAttribute("value", `${this.#x2}% ${this.#y2}%`);
13722
+ if (disabled) handle.setAttribute("disabled", "");
13723
+ this.#secondHandle = handle;
13724
+
13725
+ if (tooltips) {
13726
+ const tip = document.createElement("fig-tooltip");
13727
+ tip.setAttribute("action", "manual");
13728
+ tip.setAttribute("theme", "brand");
13729
+ tip.setAttribute("pointer", "false");
13730
+ tip.setAttribute("text", this.#secondTipText);
13731
+ tip.appendChild(handle);
13732
+ this.appendChild(tip);
13733
+ this.#secondTooltip = tip;
13734
+ } else {
13735
+ this.appendChild(handle);
13736
+ }
13737
+ }
13738
+
13643
13739
  #resizeCursorSvg(deg) {
13644
13740
  const r = Math.round(deg);
13645
13741
  return `url("data:image/svg+xml,%3Csvg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform='rotate(${r} 16 16)'%3E%3Cg filter='url(%23f)'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M11.1212 16.9998L11.5607 17.4394C12.1465 18.0252 12.1464 18.975 11.5606 19.5607C10.9748 20.1465 10.0251 20.1465 9.4393 19.5606L6.4393 16.5604C5.85354 15.9746 5.85357 15.0249 6.43938 14.4391L9.43938 11.4393C10.0252 10.8535 10.9749 10.8536 11.5607 11.4394C12.1465 12.0252 12.1464 12.9749 11.5606 13.5607L11.1215 13.9998L20.8786 13.9999L20.4394 13.5607C19.8536 12.9749 19.8535 12.0252 20.4393 11.4394C21.0251 10.8536 21.9749 10.8536 22.5606 11.4394L25.5606 14.4393C25.842 14.7206 26 15.1021 26 15.4999C26 15.8978 25.842 16.2793 25.5607 16.5606L22.5607 19.5607C21.9749 20.1465 21.0251 20.1465 20.4393 19.5607C19.8536 18.9749 19.8535 18.0252 20.4393 17.4394L20.8788 16.9999L11.1212 16.9998Z' fill='white'/%3E%3C/g%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.8536 12.1465C11.0488 12.3417 11.0488 12.6583 10.8535 12.8536L8.70715 14.9998L23.2929 14.9999L21.1465 12.8536C20.9512 12.6583 20.9512 12.3417 21.1464 12.1465C21.3417 11.9512 21.6583 11.9512 21.8535 12.1465L24.8535 15.1464C24.9473 15.2402 25 15.3673 25 15.4999C25 15.6326 24.9473 15.7597 24.8536 15.8535L21.8536 18.8536C21.6583 19.0488 21.3417 19.0488 21.1465 18.8536C20.9512 18.6583 20.9512 18.3417 21.1464 18.1465L23.2929 15.9999L8.70705 15.9998L10.8536 18.1465C11.0488 18.3417 11.0488 18.6583 10.8535 18.8536C10.6583 19.0488 10.3417 19.0488 10.1464 18.8535L7.14643 15.8533C6.95118 15.658 6.95119 15.3415 7.14646 15.1462L10.1465 12.1464C10.3417 11.9512 10.6583 11.9512 10.8536 12.1465Z' fill='black'/%3E%3C/g%3E%3Cdefs%3E%3Cfilter id='f' x='3' y='9' width='26' height='15' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'%3E%3CfeFlood flood-opacity='0' result='a'/%3E%3CfeColorMatrix in='SourceAlpha' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0' result='b'/%3E%3CfeOffset dy='1'/%3E%3CfeGaussianBlur stdDeviation='1.5'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.35 0'/%3E%3CfeBlend in2='a' result='c'/%3E%3CfeBlend in='SourceGraphic' in2='c'/%3E%3C/filter%3E%3C/defs%3E%3C/svg%3E") 16 16, nwse-resize`;
@@ -13657,12 +13753,35 @@ class FigCanvasPoint extends HTMLElement {
13657
13753
  hitArea.style.cursor = this.#rotateCursorSvg(this.#angle);
13658
13754
  }
13659
13755
 
13756
+ #pointPointLineDeg() {
13757
+ return (Math.atan2(this.#y2 - this.#y, this.#x2 - this.#x) * 180) / Math.PI;
13758
+ }
13759
+
13760
+ #syncPointPointCursors() {
13761
+ if (!this.#hasSecondPoint) return;
13762
+ const deg = this.#pointPointLineDeg();
13763
+ const setHitCursor = (handle, rotateDeg) => {
13764
+ if (!handle) return;
13765
+ const hitArea = handle.querySelector(".fig-handle-hit-area");
13766
+ if (hitArea) hitArea.style.cursor = this.#rotateCursorSvg(rotateDeg);
13767
+ };
13768
+ setHitCursor(this.#pointHandle, deg + 180);
13769
+ setHitCursor(this.#secondHandle, deg);
13770
+ }
13771
+
13772
+ #positionHandle(handle, xPct, yPct, rect) {
13773
+ const hw = handle.offsetWidth / 2;
13774
+ const hh = handle.offsetHeight / 2;
13775
+ handle.style.left = `${(xPct / 100) * rect.width - hw}px`;
13776
+ handle.style.top = `${(yPct / 100) * rect.height - hh}px`;
13777
+ }
13778
+
13660
13779
  #syncPositions() {
13661
13780
  const container = this.#container;
13662
13781
  if (!container || !this.#pointHandle) return;
13663
13782
  const rect = container.getBoundingClientRect();
13664
13783
 
13665
- this.#pointHandle.setAttribute("value", `${this.#x}% ${this.#y}%`);
13784
+ this.#positionHandle(this.#pointHandle, this.#x, this.#y, rect);
13666
13785
 
13667
13786
  if (this.#radiusSvg) {
13668
13787
  const cx = (this.#x / 100) * rect.width;
@@ -13682,18 +13801,21 @@ class FigCanvasPoint extends HTMLElement {
13682
13801
  c.setAttribute("cy", String(r));
13683
13802
  c.setAttribute("r", String(Math.max(r - 1, 0)));
13684
13803
  }
13685
- if (this.#radiusTooltip) {
13686
- this.#radiusTooltip.setAttribute("text", this.#formatRadius());
13687
- }
13688
13804
  }
13689
13805
 
13690
- if (this.#angleSvg && this.#hasAngle) {
13806
+ if (this.#angleSvg && this.#hasLine) {
13691
13807
  const cx = (this.#x / 100) * rect.width;
13692
13808
  const cy = (this.#y / 100) * rect.height;
13693
- const r = this.#resolveRadius(rect.width);
13694
- const angleRad = (this.#angle * Math.PI) / 180;
13695
- const ax = cx + r * Math.cos(angleRad);
13696
- const ay = cy + r * Math.sin(angleRad);
13809
+ let lx2, ly2;
13810
+ if (this.#hasSecondPoint) {
13811
+ lx2 = (this.#x2 / 100) * rect.width;
13812
+ ly2 = (this.#y2 / 100) * rect.height;
13813
+ } else {
13814
+ const r = this.#resolveRadius(rect.width);
13815
+ const angleRad = (this.#angle * Math.PI) / 180;
13816
+ lx2 = cx + r * Math.cos(angleRad);
13817
+ ly2 = cy + r * Math.sin(angleRad);
13818
+ }
13697
13819
 
13698
13820
  const svg = this.#angleSvg;
13699
13821
  svg.style.width = `${rect.width}px`;
@@ -13705,8 +13827,8 @@ class FigCanvasPoint extends HTMLElement {
13705
13827
  if (line) {
13706
13828
  line.setAttribute("x1", String(cx));
13707
13829
  line.setAttribute("y1", String(cy));
13708
- line.setAttribute("x2", String(ax));
13709
- line.setAttribute("y2", String(ay));
13830
+ line.setAttribute("x2", String(lx2));
13831
+ line.setAttribute("y2", String(ly2));
13710
13832
  }
13711
13833
  }
13712
13834
 
@@ -13719,14 +13841,15 @@ class FigCanvasPoint extends HTMLElement {
13719
13841
  const ay = cy + r * Math.sin(angleRad);
13720
13842
  const pxPct = rect.width > 0 ? (ax / rect.width) * 100 : 0;
13721
13843
  const pyPct = rect.height > 0 ? (ay / rect.height) * 100 : 0;
13722
- this.#angleHandle.setAttribute("value", `${pxPct}% ${pyPct}%`);
13844
+ this.#positionHandle(this.#angleHandle, pxPct, pyPct, rect);
13845
+ }
13723
13846
 
13724
- if (this.#angleTooltip) {
13725
- this.#angleTooltip.setAttribute("text", `${Math.round(this.#angle)}°`);
13726
- }
13847
+ if (this.#secondHandle && this.#hasSecondPoint) {
13848
+ this.#positionHandle(this.#secondHandle, this.#x2, this.#y2, rect);
13727
13849
  }
13728
13850
 
13729
13851
  this.#syncAngleCursor();
13852
+ this.#syncPointPointCursors();
13730
13853
  }
13731
13854
 
13732
13855
  #emitInput() {
@@ -13746,27 +13869,46 @@ class FigCanvasPoint extends HTMLElement {
13746
13869
 
13747
13870
  this.#pointHandle.addEventListener("input", (e) => {
13748
13871
  e.stopPropagation();
13872
+ if (e.detail?.color) {
13873
+ this.setAttribute("color", e.detail.color);
13874
+ this.#emitInput();
13875
+ return;
13876
+ }
13877
+ if (!this.#isDragging && this.#hasSecondPoint) {
13878
+ this.#prevBodyCursor = document.body.style.cursor;
13879
+ }
13749
13880
  this.#isDragging = true;
13750
13881
  const px = e.detail?.px ?? this.#x / 100;
13751
13882
  const py = e.detail?.py ?? this.#y / 100;
13752
13883
  this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
13753
13884
  this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
13754
- if (this.#pointTooltip) {
13885
+ if (this.#pointTooltip && this.#type !== "color") {
13755
13886
  this.#pointTooltip.setAttribute("text", this.#pointTipText);
13756
13887
  this.#pointTooltip.setAttribute("show", "true");
13757
13888
  this.#pointTooltip.showPopup?.();
13758
13889
  }
13759
13890
  this.#syncPositions();
13891
+ if (this.#hasSecondPoint) {
13892
+ document.body.style.cursor = this.#resizeCursorSvg(this.#pointPointLineDeg());
13893
+ }
13760
13894
  this.#emitInput();
13761
13895
  });
13762
13896
 
13763
13897
  this.#pointHandle.addEventListener("change", (e) => {
13764
13898
  e.stopPropagation();
13899
+ if (e.detail?.color) {
13900
+ this.setAttribute("color", e.detail.color);
13901
+ this.#emitChange();
13902
+ return;
13903
+ }
13765
13904
  const px = e.detail?.px ?? this.#x / 100;
13766
13905
  const py = e.detail?.py ?? this.#y / 100;
13767
13906
  this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
13768
13907
  this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
13769
- if (this.#pointTooltip) this.#pointTooltip.removeAttribute("show");
13908
+ if (this.#pointTooltip && this.#type !== "color") this.#pointTooltip.removeAttribute("show");
13909
+ if (this.#hasSecondPoint) {
13910
+ document.body.style.cursor = this.#prevBodyCursor ?? "";
13911
+ }
13770
13912
  this.#syncPositions();
13771
13913
  this.#syncValueAttribute();
13772
13914
  this.#emitChange();
@@ -13777,7 +13919,7 @@ class FigCanvasPoint extends HTMLElement {
13777
13919
  this.#angleHandle.addEventListener("input", (e) => {
13778
13920
  e.stopPropagation();
13779
13921
  this.#isAngleDragging = true;
13780
- this.classList.add("fig-canvas-point-ring-active");
13922
+ this.classList.add("fig-canvas-control-ring-active");
13781
13923
  const container = this.#container;
13782
13924
  if (!container) return;
13783
13925
  const rect = container.getBoundingClientRect();
@@ -13812,21 +13954,18 @@ class FigCanvasPoint extends HTMLElement {
13812
13954
  this.#radius = Math.max(0, dist);
13813
13955
  }
13814
13956
 
13815
- this.#syncPositions();
13816
13957
  if (this.#angleTooltip) {
13817
- this.#angleTooltip.setAttribute("text", `${Math.round(this.#angle)}°`);
13958
+ this.#angleTooltip.setAttribute("text", `Angle ${Math.round(this.#angle)}°`);
13818
13959
  this.#angleTooltip.setAttribute("show", "true");
13819
13960
  this.#angleTooltip.showPopup?.();
13820
13961
  }
13821
- if (this.#radiusTooltip) {
13822
- this.#radiusTooltip.setAttribute("text", this.#formatRadius());
13823
- }
13962
+ this.#syncPositions();
13824
13963
  this.#emitInput();
13825
13964
  });
13826
13965
 
13827
13966
  this.#angleHandle.addEventListener("change", (e) => {
13828
13967
  e.stopPropagation();
13829
- this.classList.remove("fig-canvas-point-ring-active");
13968
+ this.classList.remove("fig-canvas-control-ring-active");
13830
13969
  if (this.#angleTooltip) this.#angleTooltip.removeAttribute("show");
13831
13970
  this.#syncPositions();
13832
13971
  this.#syncValueAttribute();
@@ -13840,7 +13979,7 @@ class FigCanvasPoint extends HTMLElement {
13840
13979
  if (!origEvent) return;
13841
13980
  origEvent.preventDefault();
13842
13981
  this.#isAngleDragging = true;
13843
- this.classList.add("fig-canvas-point-ring-active");
13982
+ this.classList.add("fig-canvas-control-ring-active");
13844
13983
  const container = this.#container;
13845
13984
  if (!container) return;
13846
13985
 
@@ -13850,7 +13989,8 @@ class FigCanvasPoint extends HTMLElement {
13850
13989
  }
13851
13990
 
13852
13991
  const prevBodyCursor = document.body.style.cursor;
13853
- document.body.style.cursor = this.#rotateCursorSvg(this.#angle);
13992
+ let lastCursorDeg = Math.round(this.#angle);
13993
+ document.body.style.cursor = this.#rotateCursorSvg(lastCursorDeg);
13854
13994
 
13855
13995
  const onMove = (ev) => {
13856
13996
  const rect = container.getBoundingClientRect();
@@ -13863,17 +14003,19 @@ class FigCanvasPoint extends HTMLElement {
13863
14003
  angle = Math.round(angle / 15) * 15;
13864
14004
  }
13865
14005
  this.#angle = angle;
14006
+ if (this.#angleTooltip) this.#angleTooltip.setAttribute("text", `Angle ${Math.round(angle)}°`);
13866
14007
  this.#syncPositions();
13867
- document.body.style.cursor = this.#rotateCursorSvg(this.#angle);
13868
- if (this.#angleTooltip) {
13869
- this.#angleTooltip.setAttribute("text", `${Math.round(this.#angle)}°`);
14008
+ const curDeg = Math.round(angle);
14009
+ if (curDeg !== lastCursorDeg) {
14010
+ lastCursorDeg = curDeg;
14011
+ document.body.style.cursor = this.#rotateCursorSvg(curDeg);
13870
14012
  }
13871
14013
  this.#emitInput();
13872
14014
  };
13873
14015
 
13874
14016
  const onUp = () => {
13875
14017
  this.#isAngleDragging = false;
13876
- this.classList.remove("fig-canvas-point-ring-active");
14018
+ this.classList.remove("fig-canvas-control-ring-active");
13877
14019
  document.body.style.cursor = prevBodyCursor;
13878
14020
  if (this.#angleTooltip) this.#angleTooltip.removeAttribute("show");
13879
14021
  this.#syncValueAttribute();
@@ -13886,6 +14028,120 @@ class FigCanvasPoint extends HTMLElement {
13886
14028
  window.addEventListener("pointerup", onUp);
13887
14029
  });
13888
14030
  }
14031
+
14032
+ if (this.#secondHandle) {
14033
+ this.#secondHandle.addEventListener("input", (e) => {
14034
+ e.stopPropagation();
14035
+ if (!this.#isSecondDragging) {
14036
+ this.#prevBodyCursor = document.body.style.cursor;
14037
+ }
14038
+ this.#isSecondDragging = true;
14039
+ const px = e.detail?.px ?? this.#x2 / 100;
14040
+ const py = e.detail?.py ?? this.#y2 / 100;
14041
+ this.#x2 = Math.round(Math.max(0, Math.min(100, px * 100)));
14042
+ this.#y2 = Math.round(Math.max(0, Math.min(100, py * 100)));
14043
+ if (this.#secondTooltip) {
14044
+ this.#secondTooltip.setAttribute("text", this.#secondTipText);
14045
+ this.#secondTooltip.setAttribute("show", "true");
14046
+ this.#secondTooltip.showPopup?.();
14047
+ }
14048
+ this.#syncPositions();
14049
+ document.body.style.cursor = this.#resizeCursorSvg(this.#pointPointLineDeg());
14050
+ this.#emitInput();
14051
+ });
14052
+
14053
+ this.#secondHandle.addEventListener("change", (e) => {
14054
+ e.stopPropagation();
14055
+ document.body.style.cursor = this.#prevBodyCursor ?? "";
14056
+ if (this.#secondTooltip) this.#secondTooltip.removeAttribute("show");
14057
+ this.#syncPositions();
14058
+ this.#syncValueAttribute();
14059
+ this.#emitChange();
14060
+ requestAnimationFrame(() => { this.#isSecondDragging = false; });
14061
+ });
14062
+
14063
+ this.#setupPointPointHitArea(this.#pointHandle, true);
14064
+ this.#setupPointPointHitArea(this.#secondHandle, false);
14065
+ }
14066
+ }
14067
+
14068
+ #setupPointPointHitArea(handle, isFirst) {
14069
+ if (!handle) return;
14070
+ handle.addEventListener("hitareadown", (e) => {
14071
+ e.stopPropagation();
14072
+ const origEvent = e.detail?.originalEvent;
14073
+ if (!origEvent) return;
14074
+ origEvent.preventDefault();
14075
+ this.#isDragging = true;
14076
+ const container = this.#container;
14077
+ if (!container) return;
14078
+ const rect = container.getBoundingClientRect();
14079
+
14080
+ const pivotX = isFirst ? this.#x2 : this.#x;
14081
+ const pivotY = isFirst ? this.#y2 : this.#y;
14082
+ const movingX = isFirst ? this.#x : this.#x2;
14083
+ const movingY = isFirst ? this.#y : this.#y2;
14084
+ const pcx = (pivotX / 100) * rect.width;
14085
+ const pcy = (pivotY / 100) * rect.height;
14086
+ const mcx = (movingX / 100) * rect.width;
14087
+ const mcy = (movingY / 100) * rect.height;
14088
+ const fixedLen = Math.sqrt((mcx - pcx) ** 2 + (mcy - pcy) ** 2);
14089
+
14090
+ const tooltip = isFirst ? this.#pointTooltip : this.#secondTooltip;
14091
+ if (tooltip) {
14092
+ tooltip.setAttribute("show", "true");
14093
+ tooltip.showPopup?.();
14094
+ }
14095
+
14096
+ const prevBodyCursor = document.body.style.cursor;
14097
+ const initDeg = this.#pointPointLineDeg();
14098
+ let lastCursorDeg = Math.round(isFirst ? initDeg + 180 : initDeg);
14099
+ document.body.style.cursor = this.#rotateCursorSvg(lastCursorDeg);
14100
+
14101
+ const onMove = (ev) => {
14102
+ const r = container.getBoundingClientRect();
14103
+ const px = (pivotX / 100) * r.width;
14104
+ const py = (pivotY / 100) * r.height;
14105
+ const dx = ev.clientX - r.left - px;
14106
+ const dy = ev.clientY - r.top - py;
14107
+ let angle = Math.atan2(dy, dx);
14108
+ if (this.#shouldSnap(ev.shiftKey)) {
14109
+ const snapDeg = Math.round((angle * 180) / Math.PI / 15) * 15;
14110
+ angle = (snapDeg * Math.PI) / 180;
14111
+ }
14112
+ const nx = px + fixedLen * Math.cos(angle);
14113
+ const ny = py + fixedLen * Math.sin(angle);
14114
+ const newPctX = Math.max(0, Math.min(100, (nx / r.width) * 100));
14115
+ const newPctY = Math.max(0, Math.min(100, (ny / r.height) * 100));
14116
+ if (isFirst) {
14117
+ this.#x = newPctX;
14118
+ this.#y = newPctY;
14119
+ } else {
14120
+ this.#x2 = newPctX;
14121
+ this.#y2 = newPctY;
14122
+ }
14123
+ this.#syncPositions();
14124
+ const curDeg = Math.round(isFirst ? this.#pointPointLineDeg() + 180 : this.#pointPointLineDeg());
14125
+ if (curDeg !== lastCursorDeg) {
14126
+ lastCursorDeg = curDeg;
14127
+ document.body.style.cursor = this.#rotateCursorSvg(curDeg);
14128
+ }
14129
+ this.#emitInput();
14130
+ };
14131
+
14132
+ const onUp = () => {
14133
+ this.#isDragging = false;
14134
+ document.body.style.cursor = prevBodyCursor;
14135
+ if (tooltip) tooltip.removeAttribute("show");
14136
+ this.#syncValueAttribute();
14137
+ this.#emitChange();
14138
+ window.removeEventListener("pointermove", onMove);
14139
+ window.removeEventListener("pointerup", onUp);
14140
+ };
14141
+
14142
+ window.addEventListener("pointermove", onMove);
14143
+ window.addEventListener("pointerup", onUp);
14144
+ });
13889
14145
  }
13890
14146
 
13891
14147
  #setupRadiusDrag(circle) {
@@ -13905,7 +14161,7 @@ class FigCanvasPoint extends HTMLElement {
13905
14161
  e.preventDefault();
13906
14162
  e.stopPropagation();
13907
14163
  this.#isRadiusDragging = true;
13908
- this.classList.add("fig-canvas-point-ring-active");
14164
+ this.classList.add("fig-canvas-control-ring-active");
13909
14165
  const container = this.#container;
13910
14166
  if (!container) return;
13911
14167
 
@@ -13920,7 +14176,8 @@ class FigCanvasPoint extends HTMLElement {
13920
14176
  const cx0 = (this.#x / 100) * rect0.width;
13921
14177
  const cy0 = (this.#y / 100) * rect0.height;
13922
14178
  const initDeg = (Math.atan2(e.clientY - rect0.top - cy0, e.clientX - rect0.left - cx0) * 180) / Math.PI;
13923
- document.body.style.cursor = this.#resizeCursorSvg(initDeg);
14179
+ let lastCursorDeg = Math.round(initDeg);
14180
+ document.body.style.cursor = this.#resizeCursorSvg(lastCursorDeg);
13924
14181
 
13925
14182
  const onMove = (ev) => {
13926
14183
  const rect = container.getBoundingClientRect();
@@ -13928,7 +14185,11 @@ class FigCanvasPoint extends HTMLElement {
13928
14185
  const cy = (this.#y / 100) * rect.height;
13929
14186
  const dx = ev.clientX - rect.left - cx;
13930
14187
  const dy = ev.clientY - rect.top - cy;
13931
- document.body.style.cursor = this.#resizeCursorSvg((Math.atan2(dy, dx) * 180) / Math.PI);
14188
+ const curDeg = Math.round((Math.atan2(dy, dx) * 180) / Math.PI);
14189
+ if (curDeg !== lastCursorDeg) {
14190
+ lastCursorDeg = curDeg;
14191
+ document.body.style.cursor = this.#resizeCursorSvg(curDeg);
14192
+ }
13932
14193
  let dist = Math.sqrt(dx * dx + dy * dy);
13933
14194
  if (this.#shouldSnap(ev.shiftKey)) {
13934
14195
  const step = this.#radiusIsPercent ? 5 : 10;
@@ -13945,13 +14206,14 @@ class FigCanvasPoint extends HTMLElement {
13945
14206
  } else {
13946
14207
  this.#radius = Math.max(0, dist);
13947
14208
  }
14209
+ if (this.#radiusTooltip) this.#radiusTooltip.setAttribute("text", this.#formatRadius());
13948
14210
  this.#syncPositions();
13949
14211
  this.#emitInput();
13950
14212
  };
13951
14213
 
13952
14214
  const onUp = () => {
13953
14215
  this.#isRadiusDragging = false;
13954
- this.classList.remove("fig-canvas-point-ring-active");
14216
+ this.classList.remove("fig-canvas-control-ring-active");
13955
14217
  circle.style.pointerEvents = "";
13956
14218
  document.body.style.cursor = prevBodyCursor;
13957
14219
  if (this.#radiusTooltip) this.#radiusTooltip.removeAttribute("show");
@@ -13975,7 +14237,7 @@ class FigCanvasPoint extends HTMLElement {
13975
14237
  }
13976
14238
  }
13977
14239
  }
13978
- customElements.define("fig-canvas-point", FigCanvasPoint);
14240
+ customElements.define("fig-canvas-control", FigCanvasControl);
13979
14241
 
13980
14242
  /* Handle */
13981
14243
  class FigHandle extends HTMLElement {
@@ -14315,6 +14577,10 @@ class FigHandle extends HTMLElement {
14315
14577
  const offsetX = e.clientX - handleCenterX;
14316
14578
  const offsetY = e.clientY - handleCenterY;
14317
14579
 
14580
+ const startX = e.clientX;
14581
+ const startY = e.clientY;
14582
+ const DRAG_THRESHOLD = 3;
14583
+
14318
14584
  const clampAndApply = (clientX, clientY, shiftKey = false) => {
14319
14585
  const rect = container.getBoundingClientRect();
14320
14586
  lastRect = rect;
@@ -14364,6 +14630,9 @@ class FigHandle extends HTMLElement {
14364
14630
  const onMove = (e) => {
14365
14631
  if (!this.#isDragging) return;
14366
14632
  if (!this.#didDrag) {
14633
+ const dx = e.clientX - startX;
14634
+ const dy = e.clientY - startY;
14635
+ if (dx * dx + dy * dy < DRAG_THRESHOLD * DRAG_THRESHOLD) return;
14367
14636
  this.classList.add("dragging");
14368
14637
  this.style.cursor = "grabbing";
14369
14638
  if (!this.hasAttribute("selected")) this.select();
@@ -14457,12 +14726,18 @@ class FigHandle extends HTMLElement {
14457
14726
 
14458
14727
  #handleColorTipInput = (e) => {
14459
14728
  e.stopPropagation();
14460
- if (e.detail?.color) this.setAttribute("color", e.detail.color);
14729
+ if (e.detail?.color) {
14730
+ this.setAttribute("color", e.detail.color);
14731
+ this.dispatchEvent(new CustomEvent("input", { bubbles: true, detail: { color: e.detail.color } }));
14732
+ }
14461
14733
  };
14462
14734
 
14463
14735
  #handleColorTipChange = (e) => {
14464
14736
  e.stopPropagation();
14465
- if (e.detail?.color) this.setAttribute("color", e.detail.color);
14737
+ if (e.detail?.color) {
14738
+ this.setAttribute("color", e.detail.color);
14739
+ this.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: { color: e.detail.color } }));
14740
+ }
14466
14741
  };
14467
14742
 
14468
14743
  #handleColorTipControl = (e) => {