@rogieking/figui3 3.19.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.
Files changed (3) hide show
  1. package/components.css +29 -6
  2. package/fig.js +94 -36
  3. package/package.json +1 -1
package/components.css CHANGED
@@ -233,8 +233,8 @@
233
233
  --handle-color: #fff;
234
234
  --bg-selected: #f5f5f5;
235
235
  --bg-selected-active: #e5f4ff;
236
- --bg-tooltip: #1e1e1e;
237
- --color-tooltip: #ffffff;
236
+ --bg-tooltip: light-dark(#ffffff, #1e1e1e);
237
+ --color-tooltip: var(--figma-color-text);
238
238
 
239
239
  /* Transparent Backgrounds - use light-dark() */
240
240
  --figma-color-bordertranslucent: light-dark(
@@ -2776,7 +2776,8 @@ fig-tooltip {
2776
2776
  inset: unset;
2777
2777
  display: inline-block;
2778
2778
  overflow: visible;
2779
- color: var(--color-tooltip);
2779
+ color-scheme: dark;
2780
+ color: var(--figma-color-text);
2780
2781
  background-color: var(--bg-tooltip);
2781
2782
  padding: var(--spacer-1) var(--spacer-2);
2782
2783
  line-height: 1rem !important;
@@ -2784,7 +2785,8 @@ fig-tooltip {
2784
2785
  border-radius: var(--radius-medium, 0.3125rem);
2785
2786
  max-width: 180px;
2786
2787
  width: max-content;
2787
- box-shadow: inset 0 0.5px 0 0 rgba(255, 255, 255, 0.1);
2788
+ box-shadow: inset 0 0.5px 0 0
2789
+ light-dark(rgba(255, 255, 255, 0.1), rgba(0, 0, 0, 0.06));
2788
2790
  filter: drop-shadow(0px 1px 1.5px rgba(0, 0, 0, 0.1))
2789
2791
  drop-shadow(0px 2.5px 6px rgba(0, 0, 0, 0.13))
2790
2792
  drop-shadow(0px 0px 0.5px rgba(0, 0, 0, 0.15));
@@ -2814,6 +2816,12 @@ fig-tooltip {
2814
2816
  transform: translate(-50%);
2815
2817
  }
2816
2818
 
2819
+ &[pointer="false"] {
2820
+ &:after {
2821
+ display: none !important;
2822
+ }
2823
+ }
2824
+
2817
2825
  &[position="bottom"] {
2818
2826
  &:after {
2819
2827
  top: 0px;
@@ -2821,6 +2829,18 @@ fig-tooltip {
2821
2829
  transform: translate(-50%, -100%) scaleY(-1);
2822
2830
  }
2823
2831
  }
2832
+ &[theme="dark"] {
2833
+ color-scheme: dark;
2834
+ }
2835
+
2836
+ &[theme="light"] {
2837
+ color-scheme: light;
2838
+ }
2839
+
2840
+ &[theme="brand"] {
2841
+ background-color: var(--figma-color-bg-brand);
2842
+ color-scheme: dark;
2843
+ }
2824
2844
  }
2825
2845
 
2826
2846
  /* UI Icons */
@@ -4981,10 +5001,13 @@ fig-color-tip {
4981
5001
  /* Canvas Control */
4982
5002
  fig-canvas-control {
4983
5003
  display: contents;
4984
- --fig-canvas-control-line-stroke: rgba(255, 255, 255, 0.5);
5004
+ --fig-canvas-control-line-stroke: light-dark(
5005
+ rgba(255, 255, 255, 1),
5006
+ rgba(255, 255, 255, 0.5)
5007
+ );
4985
5008
  --fig-canvas-control-line-stroke-hover: var(--figma-color-bg-brand);
4986
5009
  --fig-canvas-control-line-stroke-active: var(--figma-color-bg-brand);
4987
- --fig-canvas-control-line-stroke-width: 1.5px;
5010
+ --fig-canvas-control-line-stroke-width: 1.25px;
4988
5011
 
4989
5012
  & > fig-handle,
4990
5013
  & > fig-tooltip:has(fig-handle:not([size="small"])) {
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) {
@@ -13465,8 +13481,8 @@ class FigCanvasControl extends HTMLElement {
13465
13481
  }
13466
13482
 
13467
13483
  #formatRadius() {
13468
- if (this.#radiusIsPercent) return `${Math.round(this.#radius)}%`;
13469
- return `${Math.round(this.#radius)}px`;
13484
+ if (this.#radiusIsPercent) return `Radius ${Math.round(this.#radius)}%`;
13485
+ return `Radius ${Math.round(this.#radius)}`;
13470
13486
  }
13471
13487
 
13472
13488
  connectedCallback() {
@@ -13535,6 +13551,10 @@ class FigCanvasControl extends HTMLElement {
13535
13551
 
13536
13552
  get value() {
13537
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
+ }
13538
13558
  if (this.#hasRadius) {
13539
13559
  v.radius = this.#radiusIsPercent ? `${this.#radius}%` : this.#radius;
13540
13560
  }
@@ -13601,6 +13621,8 @@ class FigCanvasControl extends HTMLElement {
13601
13621
  if (tooltips) {
13602
13622
  const tip = document.createElement("fig-tooltip");
13603
13623
  tip.setAttribute("action", "manual");
13624
+ tip.setAttribute("theme", "brand");
13625
+ tip.setAttribute("pointer", "false");
13604
13626
  tip.setAttribute("text", this.#pointTipText);
13605
13627
  tip.appendChild(handle);
13606
13628
  this.appendChild(tip);
@@ -13636,6 +13658,8 @@ class FigCanvasControl extends HTMLElement {
13636
13658
  if (this.#tooltipsEnabled) {
13637
13659
  const tip = document.createElement("fig-tooltip");
13638
13660
  tip.setAttribute("action", "manual");
13661
+ tip.setAttribute("theme", "brand");
13662
+ tip.setAttribute("pointer", "false");
13639
13663
  tip.setAttribute("text", this.#formatRadius());
13640
13664
  tip.appendChild(svg);
13641
13665
  this.appendChild(tip);
@@ -13675,6 +13699,8 @@ class FigCanvasControl extends HTMLElement {
13675
13699
  if (tooltips) {
13676
13700
  const tip = document.createElement("fig-tooltip");
13677
13701
  tip.setAttribute("action", "manual");
13702
+ tip.setAttribute("theme", "brand");
13703
+ tip.setAttribute("pointer", "false");
13678
13704
  tip.setAttribute("text", `${Math.round(this.#angle)}°`);
13679
13705
  tip.appendChild(handle);
13680
13706
  this.appendChild(tip);
@@ -13699,6 +13725,8 @@ class FigCanvasControl extends HTMLElement {
13699
13725
  if (tooltips) {
13700
13726
  const tip = document.createElement("fig-tooltip");
13701
13727
  tip.setAttribute("action", "manual");
13728
+ tip.setAttribute("theme", "brand");
13729
+ tip.setAttribute("pointer", "false");
13702
13730
  tip.setAttribute("text", this.#secondTipText);
13703
13731
  tip.appendChild(handle);
13704
13732
  this.appendChild(tip);
@@ -13741,12 +13769,19 @@ class FigCanvasControl extends HTMLElement {
13741
13769
  setHitCursor(this.#secondHandle, deg);
13742
13770
  }
13743
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
+
13744
13779
  #syncPositions() {
13745
13780
  const container = this.#container;
13746
13781
  if (!container || !this.#pointHandle) return;
13747
13782
  const rect = container.getBoundingClientRect();
13748
13783
 
13749
- this.#pointHandle.setAttribute("value", `${this.#x}% ${this.#y}%`);
13784
+ this.#positionHandle(this.#pointHandle, this.#x, this.#y, rect);
13750
13785
 
13751
13786
  if (this.#radiusSvg) {
13752
13787
  const cx = (this.#x / 100) * rect.width;
@@ -13766,9 +13801,6 @@ class FigCanvasControl extends HTMLElement {
13766
13801
  c.setAttribute("cy", String(r));
13767
13802
  c.setAttribute("r", String(Math.max(r - 1, 0)));
13768
13803
  }
13769
- if (this.#radiusTooltip) {
13770
- this.#radiusTooltip.setAttribute("text", this.#formatRadius());
13771
- }
13772
13804
  }
13773
13805
 
13774
13806
  if (this.#angleSvg && this.#hasLine) {
@@ -13809,18 +13841,11 @@ class FigCanvasControl extends HTMLElement {
13809
13841
  const ay = cy + r * Math.sin(angleRad);
13810
13842
  const pxPct = rect.width > 0 ? (ax / rect.width) * 100 : 0;
13811
13843
  const pyPct = rect.height > 0 ? (ay / rect.height) * 100 : 0;
13812
- this.#angleHandle.setAttribute("value", `${pxPct}% ${pyPct}%`);
13813
-
13814
- if (this.#angleTooltip) {
13815
- this.#angleTooltip.setAttribute("text", `${Math.round(this.#angle)}°`);
13816
- }
13844
+ this.#positionHandle(this.#angleHandle, pxPct, pyPct, rect);
13817
13845
  }
13818
13846
 
13819
13847
  if (this.#secondHandle && this.#hasSecondPoint) {
13820
- this.#secondHandle.setAttribute("value", `${this.#x2}% ${this.#y2}%`);
13821
- if (this.#secondTooltip) {
13822
- this.#secondTooltip.setAttribute("text", this.#secondTipText);
13823
- }
13848
+ this.#positionHandle(this.#secondHandle, this.#x2, this.#y2, rect);
13824
13849
  }
13825
13850
 
13826
13851
  this.#syncAngleCursor();
@@ -13844,6 +13869,11 @@ class FigCanvasControl extends HTMLElement {
13844
13869
 
13845
13870
  this.#pointHandle.addEventListener("input", (e) => {
13846
13871
  e.stopPropagation();
13872
+ if (e.detail?.color) {
13873
+ this.setAttribute("color", e.detail.color);
13874
+ this.#emitInput();
13875
+ return;
13876
+ }
13847
13877
  if (!this.#isDragging && this.#hasSecondPoint) {
13848
13878
  this.#prevBodyCursor = document.body.style.cursor;
13849
13879
  }
@@ -13852,7 +13882,7 @@ class FigCanvasControl extends HTMLElement {
13852
13882
  const py = e.detail?.py ?? this.#y / 100;
13853
13883
  this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
13854
13884
  this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
13855
- if (this.#pointTooltip) {
13885
+ if (this.#pointTooltip && this.#type !== "color") {
13856
13886
  this.#pointTooltip.setAttribute("text", this.#pointTipText);
13857
13887
  this.#pointTooltip.setAttribute("show", "true");
13858
13888
  this.#pointTooltip.showPopup?.();
@@ -13866,11 +13896,16 @@ class FigCanvasControl extends HTMLElement {
13866
13896
 
13867
13897
  this.#pointHandle.addEventListener("change", (e) => {
13868
13898
  e.stopPropagation();
13899
+ if (e.detail?.color) {
13900
+ this.setAttribute("color", e.detail.color);
13901
+ this.#emitChange();
13902
+ return;
13903
+ }
13869
13904
  const px = e.detail?.px ?? this.#x / 100;
13870
13905
  const py = e.detail?.py ?? this.#y / 100;
13871
13906
  this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
13872
13907
  this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
13873
- if (this.#pointTooltip) this.#pointTooltip.removeAttribute("show");
13908
+ if (this.#pointTooltip && this.#type !== "color") this.#pointTooltip.removeAttribute("show");
13874
13909
  if (this.#hasSecondPoint) {
13875
13910
  document.body.style.cursor = this.#prevBodyCursor ?? "";
13876
13911
  }
@@ -13919,15 +13954,12 @@ class FigCanvasControl extends HTMLElement {
13919
13954
  this.#radius = Math.max(0, dist);
13920
13955
  }
13921
13956
 
13922
- this.#syncPositions();
13923
13957
  if (this.#angleTooltip) {
13924
- this.#angleTooltip.setAttribute("text", `${Math.round(this.#angle)}°`);
13958
+ this.#angleTooltip.setAttribute("text", `Angle ${Math.round(this.#angle)}°`);
13925
13959
  this.#angleTooltip.setAttribute("show", "true");
13926
13960
  this.#angleTooltip.showPopup?.();
13927
13961
  }
13928
- if (this.#radiusTooltip) {
13929
- this.#radiusTooltip.setAttribute("text", this.#formatRadius());
13930
- }
13962
+ this.#syncPositions();
13931
13963
  this.#emitInput();
13932
13964
  });
13933
13965
 
@@ -13957,7 +13989,8 @@ class FigCanvasControl extends HTMLElement {
13957
13989
  }
13958
13990
 
13959
13991
  const prevBodyCursor = document.body.style.cursor;
13960
- document.body.style.cursor = this.#rotateCursorSvg(this.#angle);
13992
+ let lastCursorDeg = Math.round(this.#angle);
13993
+ document.body.style.cursor = this.#rotateCursorSvg(lastCursorDeg);
13961
13994
 
13962
13995
  const onMove = (ev) => {
13963
13996
  const rect = container.getBoundingClientRect();
@@ -13970,10 +14003,12 @@ class FigCanvasControl extends HTMLElement {
13970
14003
  angle = Math.round(angle / 15) * 15;
13971
14004
  }
13972
14005
  this.#angle = angle;
14006
+ if (this.#angleTooltip) this.#angleTooltip.setAttribute("text", `Angle ${Math.round(angle)}°`);
13973
14007
  this.#syncPositions();
13974
- document.body.style.cursor = this.#rotateCursorSvg(this.#angle);
13975
- if (this.#angleTooltip) {
13976
- 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);
13977
14012
  }
13978
14013
  this.#emitInput();
13979
14014
  };
@@ -14060,7 +14095,8 @@ class FigCanvasControl extends HTMLElement {
14060
14095
 
14061
14096
  const prevBodyCursor = document.body.style.cursor;
14062
14097
  const initDeg = this.#pointPointLineDeg();
14063
- document.body.style.cursor = this.#rotateCursorSvg(isFirst ? initDeg + 180 : initDeg);
14098
+ let lastCursorDeg = Math.round(isFirst ? initDeg + 180 : initDeg);
14099
+ document.body.style.cursor = this.#rotateCursorSvg(lastCursorDeg);
14064
14100
 
14065
14101
  const onMove = (ev) => {
14066
14102
  const r = container.getBoundingClientRect();
@@ -14075,8 +14111,8 @@ class FigCanvasControl extends HTMLElement {
14075
14111
  }
14076
14112
  const nx = px + fixedLen * Math.cos(angle);
14077
14113
  const ny = py + fixedLen * Math.sin(angle);
14078
- const newPctX = Math.round(Math.max(0, Math.min(100, (nx / r.width) * 100)));
14079
- const newPctY = Math.round(Math.max(0, Math.min(100, (ny / r.height) * 100)));
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));
14080
14116
  if (isFirst) {
14081
14117
  this.#x = newPctX;
14082
14118
  this.#y = newPctY;
@@ -14085,8 +14121,11 @@ class FigCanvasControl extends HTMLElement {
14085
14121
  this.#y2 = newPctY;
14086
14122
  }
14087
14123
  this.#syncPositions();
14088
- const curDeg = this.#pointPointLineDeg();
14089
- document.body.style.cursor = this.#rotateCursorSvg(isFirst ? curDeg + 180 : curDeg);
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
+ }
14090
14129
  this.#emitInput();
14091
14130
  };
14092
14131
 
@@ -14137,7 +14176,8 @@ class FigCanvasControl extends HTMLElement {
14137
14176
  const cx0 = (this.#x / 100) * rect0.width;
14138
14177
  const cy0 = (this.#y / 100) * rect0.height;
14139
14178
  const initDeg = (Math.atan2(e.clientY - rect0.top - cy0, e.clientX - rect0.left - cx0) * 180) / Math.PI;
14140
- document.body.style.cursor = this.#resizeCursorSvg(initDeg);
14179
+ let lastCursorDeg = Math.round(initDeg);
14180
+ document.body.style.cursor = this.#resizeCursorSvg(lastCursorDeg);
14141
14181
 
14142
14182
  const onMove = (ev) => {
14143
14183
  const rect = container.getBoundingClientRect();
@@ -14145,7 +14185,11 @@ class FigCanvasControl extends HTMLElement {
14145
14185
  const cy = (this.#y / 100) * rect.height;
14146
14186
  const dx = ev.clientX - rect.left - cx;
14147
14187
  const dy = ev.clientY - rect.top - cy;
14148
- 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
+ }
14149
14193
  let dist = Math.sqrt(dx * dx + dy * dy);
14150
14194
  if (this.#shouldSnap(ev.shiftKey)) {
14151
14195
  const step = this.#radiusIsPercent ? 5 : 10;
@@ -14162,6 +14206,7 @@ class FigCanvasControl extends HTMLElement {
14162
14206
  } else {
14163
14207
  this.#radius = Math.max(0, dist);
14164
14208
  }
14209
+ if (this.#radiusTooltip) this.#radiusTooltip.setAttribute("text", this.#formatRadius());
14165
14210
  this.#syncPositions();
14166
14211
  this.#emitInput();
14167
14212
  };
@@ -14532,6 +14577,10 @@ class FigHandle extends HTMLElement {
14532
14577
  const offsetX = e.clientX - handleCenterX;
14533
14578
  const offsetY = e.clientY - handleCenterY;
14534
14579
 
14580
+ const startX = e.clientX;
14581
+ const startY = e.clientY;
14582
+ const DRAG_THRESHOLD = 3;
14583
+
14535
14584
  const clampAndApply = (clientX, clientY, shiftKey = false) => {
14536
14585
  const rect = container.getBoundingClientRect();
14537
14586
  lastRect = rect;
@@ -14581,6 +14630,9 @@ class FigHandle extends HTMLElement {
14581
14630
  const onMove = (e) => {
14582
14631
  if (!this.#isDragging) return;
14583
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;
14584
14636
  this.classList.add("dragging");
14585
14637
  this.style.cursor = "grabbing";
14586
14638
  if (!this.hasAttribute("selected")) this.select();
@@ -14674,12 +14726,18 @@ class FigHandle extends HTMLElement {
14674
14726
 
14675
14727
  #handleColorTipInput = (e) => {
14676
14728
  e.stopPropagation();
14677
- 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
+ }
14678
14733
  };
14679
14734
 
14680
14735
  #handleColorTipChange = (e) => {
14681
14736
  e.stopPropagation();
14682
- 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
+ }
14683
14741
  };
14684
14742
 
14685
14743
  #handleColorTipControl = (e) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "3.19.0",
3
+ "version": "3.20.0",
4
4
  "description": "A lightweight web components library for building Figma plugin and widget UIs with native look and feel",
5
5
  "author": "Rogie King",
6
6
  "license": "MIT",