@rogieking/figui3 3.19.0 → 3.20.1

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 +102 -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) {
@@ -3640,6 +3656,10 @@ class FigInputText extends HTMLElement {
3640
3656
  this[name] = this.input[name] = newValue;
3641
3657
  this.input.setAttribute("name", newValue);
3642
3658
  break;
3659
+ case "placeholder":
3660
+ this.placeholder = newValue ?? "";
3661
+ this.input.placeholder = this.placeholder;
3662
+ break;
3643
3663
  default:
3644
3664
  this[name] = this.input[name] = newValue;
3645
3665
  break;
@@ -4125,6 +4145,10 @@ class FigInputNumber extends HTMLElement {
4125
4145
  this[name] = this.input[name] = newValue;
4126
4146
  this.input.setAttribute("name", newValue);
4127
4147
  break;
4148
+ case "placeholder":
4149
+ this.placeholder = newValue ?? "";
4150
+ this.input.placeholder = this.placeholder;
4151
+ break;
4128
4152
  default:
4129
4153
  this[name] = this.input[name] = newValue;
4130
4154
  break;
@@ -13465,8 +13489,8 @@ class FigCanvasControl extends HTMLElement {
13465
13489
  }
13466
13490
 
13467
13491
  #formatRadius() {
13468
- if (this.#radiusIsPercent) return `${Math.round(this.#radius)}%`;
13469
- return `${Math.round(this.#radius)}px`;
13492
+ if (this.#radiusIsPercent) return `Radius ${Math.round(this.#radius)}%`;
13493
+ return `Radius ${Math.round(this.#radius)}`;
13470
13494
  }
13471
13495
 
13472
13496
  connectedCallback() {
@@ -13535,6 +13559,10 @@ class FigCanvasControl extends HTMLElement {
13535
13559
 
13536
13560
  get value() {
13537
13561
  const v = { x: this.#x, y: this.#y };
13562
+ if (this.#type === "color") {
13563
+ const color = this.getAttribute("color") || this.#pointHandle?.getAttribute("color");
13564
+ if (color) v.color = color;
13565
+ }
13538
13566
  if (this.#hasRadius) {
13539
13567
  v.radius = this.#radiusIsPercent ? `${this.#radius}%` : this.#radius;
13540
13568
  }
@@ -13601,6 +13629,8 @@ class FigCanvasControl extends HTMLElement {
13601
13629
  if (tooltips) {
13602
13630
  const tip = document.createElement("fig-tooltip");
13603
13631
  tip.setAttribute("action", "manual");
13632
+ tip.setAttribute("theme", "brand");
13633
+ tip.setAttribute("pointer", "false");
13604
13634
  tip.setAttribute("text", this.#pointTipText);
13605
13635
  tip.appendChild(handle);
13606
13636
  this.appendChild(tip);
@@ -13636,6 +13666,8 @@ class FigCanvasControl extends HTMLElement {
13636
13666
  if (this.#tooltipsEnabled) {
13637
13667
  const tip = document.createElement("fig-tooltip");
13638
13668
  tip.setAttribute("action", "manual");
13669
+ tip.setAttribute("theme", "brand");
13670
+ tip.setAttribute("pointer", "false");
13639
13671
  tip.setAttribute("text", this.#formatRadius());
13640
13672
  tip.appendChild(svg);
13641
13673
  this.appendChild(tip);
@@ -13675,6 +13707,8 @@ class FigCanvasControl extends HTMLElement {
13675
13707
  if (tooltips) {
13676
13708
  const tip = document.createElement("fig-tooltip");
13677
13709
  tip.setAttribute("action", "manual");
13710
+ tip.setAttribute("theme", "brand");
13711
+ tip.setAttribute("pointer", "false");
13678
13712
  tip.setAttribute("text", `${Math.round(this.#angle)}°`);
13679
13713
  tip.appendChild(handle);
13680
13714
  this.appendChild(tip);
@@ -13699,6 +13733,8 @@ class FigCanvasControl extends HTMLElement {
13699
13733
  if (tooltips) {
13700
13734
  const tip = document.createElement("fig-tooltip");
13701
13735
  tip.setAttribute("action", "manual");
13736
+ tip.setAttribute("theme", "brand");
13737
+ tip.setAttribute("pointer", "false");
13702
13738
  tip.setAttribute("text", this.#secondTipText);
13703
13739
  tip.appendChild(handle);
13704
13740
  this.appendChild(tip);
@@ -13741,12 +13777,19 @@ class FigCanvasControl extends HTMLElement {
13741
13777
  setHitCursor(this.#secondHandle, deg);
13742
13778
  }
13743
13779
 
13780
+ #positionHandle(handle, xPct, yPct, rect) {
13781
+ const hw = handle.offsetWidth / 2;
13782
+ const hh = handle.offsetHeight / 2;
13783
+ handle.style.left = `${(xPct / 100) * rect.width - hw}px`;
13784
+ handle.style.top = `${(yPct / 100) * rect.height - hh}px`;
13785
+ }
13786
+
13744
13787
  #syncPositions() {
13745
13788
  const container = this.#container;
13746
13789
  if (!container || !this.#pointHandle) return;
13747
13790
  const rect = container.getBoundingClientRect();
13748
13791
 
13749
- this.#pointHandle.setAttribute("value", `${this.#x}% ${this.#y}%`);
13792
+ this.#positionHandle(this.#pointHandle, this.#x, this.#y, rect);
13750
13793
 
13751
13794
  if (this.#radiusSvg) {
13752
13795
  const cx = (this.#x / 100) * rect.width;
@@ -13766,9 +13809,6 @@ class FigCanvasControl extends HTMLElement {
13766
13809
  c.setAttribute("cy", String(r));
13767
13810
  c.setAttribute("r", String(Math.max(r - 1, 0)));
13768
13811
  }
13769
- if (this.#radiusTooltip) {
13770
- this.#radiusTooltip.setAttribute("text", this.#formatRadius());
13771
- }
13772
13812
  }
13773
13813
 
13774
13814
  if (this.#angleSvg && this.#hasLine) {
@@ -13809,18 +13849,11 @@ class FigCanvasControl extends HTMLElement {
13809
13849
  const ay = cy + r * Math.sin(angleRad);
13810
13850
  const pxPct = rect.width > 0 ? (ax / rect.width) * 100 : 0;
13811
13851
  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
- }
13852
+ this.#positionHandle(this.#angleHandle, pxPct, pyPct, rect);
13817
13853
  }
13818
13854
 
13819
13855
  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
- }
13856
+ this.#positionHandle(this.#secondHandle, this.#x2, this.#y2, rect);
13824
13857
  }
13825
13858
 
13826
13859
  this.#syncAngleCursor();
@@ -13844,6 +13877,11 @@ class FigCanvasControl extends HTMLElement {
13844
13877
 
13845
13878
  this.#pointHandle.addEventListener("input", (e) => {
13846
13879
  e.stopPropagation();
13880
+ if (e.detail?.color) {
13881
+ this.setAttribute("color", e.detail.color);
13882
+ this.#emitInput();
13883
+ return;
13884
+ }
13847
13885
  if (!this.#isDragging && this.#hasSecondPoint) {
13848
13886
  this.#prevBodyCursor = document.body.style.cursor;
13849
13887
  }
@@ -13852,7 +13890,7 @@ class FigCanvasControl extends HTMLElement {
13852
13890
  const py = e.detail?.py ?? this.#y / 100;
13853
13891
  this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
13854
13892
  this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
13855
- if (this.#pointTooltip) {
13893
+ if (this.#pointTooltip && this.#type !== "color") {
13856
13894
  this.#pointTooltip.setAttribute("text", this.#pointTipText);
13857
13895
  this.#pointTooltip.setAttribute("show", "true");
13858
13896
  this.#pointTooltip.showPopup?.();
@@ -13866,11 +13904,16 @@ class FigCanvasControl extends HTMLElement {
13866
13904
 
13867
13905
  this.#pointHandle.addEventListener("change", (e) => {
13868
13906
  e.stopPropagation();
13907
+ if (e.detail?.color) {
13908
+ this.setAttribute("color", e.detail.color);
13909
+ this.#emitChange();
13910
+ return;
13911
+ }
13869
13912
  const px = e.detail?.px ?? this.#x / 100;
13870
13913
  const py = e.detail?.py ?? this.#y / 100;
13871
13914
  this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
13872
13915
  this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
13873
- if (this.#pointTooltip) this.#pointTooltip.removeAttribute("show");
13916
+ if (this.#pointTooltip && this.#type !== "color") this.#pointTooltip.removeAttribute("show");
13874
13917
  if (this.#hasSecondPoint) {
13875
13918
  document.body.style.cursor = this.#prevBodyCursor ?? "";
13876
13919
  }
@@ -13919,15 +13962,12 @@ class FigCanvasControl extends HTMLElement {
13919
13962
  this.#radius = Math.max(0, dist);
13920
13963
  }
13921
13964
 
13922
- this.#syncPositions();
13923
13965
  if (this.#angleTooltip) {
13924
- this.#angleTooltip.setAttribute("text", `${Math.round(this.#angle)}°`);
13966
+ this.#angleTooltip.setAttribute("text", `Angle ${Math.round(this.#angle)}°`);
13925
13967
  this.#angleTooltip.setAttribute("show", "true");
13926
13968
  this.#angleTooltip.showPopup?.();
13927
13969
  }
13928
- if (this.#radiusTooltip) {
13929
- this.#radiusTooltip.setAttribute("text", this.#formatRadius());
13930
- }
13970
+ this.#syncPositions();
13931
13971
  this.#emitInput();
13932
13972
  });
13933
13973
 
@@ -13957,7 +13997,8 @@ class FigCanvasControl extends HTMLElement {
13957
13997
  }
13958
13998
 
13959
13999
  const prevBodyCursor = document.body.style.cursor;
13960
- document.body.style.cursor = this.#rotateCursorSvg(this.#angle);
14000
+ let lastCursorDeg = Math.round(this.#angle);
14001
+ document.body.style.cursor = this.#rotateCursorSvg(lastCursorDeg);
13961
14002
 
13962
14003
  const onMove = (ev) => {
13963
14004
  const rect = container.getBoundingClientRect();
@@ -13970,10 +14011,12 @@ class FigCanvasControl extends HTMLElement {
13970
14011
  angle = Math.round(angle / 15) * 15;
13971
14012
  }
13972
14013
  this.#angle = angle;
14014
+ if (this.#angleTooltip) this.#angleTooltip.setAttribute("text", `Angle ${Math.round(angle)}°`);
13973
14015
  this.#syncPositions();
13974
- document.body.style.cursor = this.#rotateCursorSvg(this.#angle);
13975
- if (this.#angleTooltip) {
13976
- this.#angleTooltip.setAttribute("text", `${Math.round(this.#angle)}°`);
14016
+ const curDeg = Math.round(angle);
14017
+ if (curDeg !== lastCursorDeg) {
14018
+ lastCursorDeg = curDeg;
14019
+ document.body.style.cursor = this.#rotateCursorSvg(curDeg);
13977
14020
  }
13978
14021
  this.#emitInput();
13979
14022
  };
@@ -14060,7 +14103,8 @@ class FigCanvasControl extends HTMLElement {
14060
14103
 
14061
14104
  const prevBodyCursor = document.body.style.cursor;
14062
14105
  const initDeg = this.#pointPointLineDeg();
14063
- document.body.style.cursor = this.#rotateCursorSvg(isFirst ? initDeg + 180 : initDeg);
14106
+ let lastCursorDeg = Math.round(isFirst ? initDeg + 180 : initDeg);
14107
+ document.body.style.cursor = this.#rotateCursorSvg(lastCursorDeg);
14064
14108
 
14065
14109
  const onMove = (ev) => {
14066
14110
  const r = container.getBoundingClientRect();
@@ -14075,8 +14119,8 @@ class FigCanvasControl extends HTMLElement {
14075
14119
  }
14076
14120
  const nx = px + fixedLen * Math.cos(angle);
14077
14121
  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)));
14122
+ const newPctX = Math.max(0, Math.min(100, (nx / r.width) * 100));
14123
+ const newPctY = Math.max(0, Math.min(100, (ny / r.height) * 100));
14080
14124
  if (isFirst) {
14081
14125
  this.#x = newPctX;
14082
14126
  this.#y = newPctY;
@@ -14085,8 +14129,11 @@ class FigCanvasControl extends HTMLElement {
14085
14129
  this.#y2 = newPctY;
14086
14130
  }
14087
14131
  this.#syncPositions();
14088
- const curDeg = this.#pointPointLineDeg();
14089
- document.body.style.cursor = this.#rotateCursorSvg(isFirst ? curDeg + 180 : curDeg);
14132
+ const curDeg = Math.round(isFirst ? this.#pointPointLineDeg() + 180 : this.#pointPointLineDeg());
14133
+ if (curDeg !== lastCursorDeg) {
14134
+ lastCursorDeg = curDeg;
14135
+ document.body.style.cursor = this.#rotateCursorSvg(curDeg);
14136
+ }
14090
14137
  this.#emitInput();
14091
14138
  };
14092
14139
 
@@ -14137,7 +14184,8 @@ class FigCanvasControl extends HTMLElement {
14137
14184
  const cx0 = (this.#x / 100) * rect0.width;
14138
14185
  const cy0 = (this.#y / 100) * rect0.height;
14139
14186
  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);
14187
+ let lastCursorDeg = Math.round(initDeg);
14188
+ document.body.style.cursor = this.#resizeCursorSvg(lastCursorDeg);
14141
14189
 
14142
14190
  const onMove = (ev) => {
14143
14191
  const rect = container.getBoundingClientRect();
@@ -14145,7 +14193,11 @@ class FigCanvasControl extends HTMLElement {
14145
14193
  const cy = (this.#y / 100) * rect.height;
14146
14194
  const dx = ev.clientX - rect.left - cx;
14147
14195
  const dy = ev.clientY - rect.top - cy;
14148
- document.body.style.cursor = this.#resizeCursorSvg((Math.atan2(dy, dx) * 180) / Math.PI);
14196
+ const curDeg = Math.round((Math.atan2(dy, dx) * 180) / Math.PI);
14197
+ if (curDeg !== lastCursorDeg) {
14198
+ lastCursorDeg = curDeg;
14199
+ document.body.style.cursor = this.#resizeCursorSvg(curDeg);
14200
+ }
14149
14201
  let dist = Math.sqrt(dx * dx + dy * dy);
14150
14202
  if (this.#shouldSnap(ev.shiftKey)) {
14151
14203
  const step = this.#radiusIsPercent ? 5 : 10;
@@ -14162,6 +14214,7 @@ class FigCanvasControl extends HTMLElement {
14162
14214
  } else {
14163
14215
  this.#radius = Math.max(0, dist);
14164
14216
  }
14217
+ if (this.#radiusTooltip) this.#radiusTooltip.setAttribute("text", this.#formatRadius());
14165
14218
  this.#syncPositions();
14166
14219
  this.#emitInput();
14167
14220
  };
@@ -14532,6 +14585,10 @@ class FigHandle extends HTMLElement {
14532
14585
  const offsetX = e.clientX - handleCenterX;
14533
14586
  const offsetY = e.clientY - handleCenterY;
14534
14587
 
14588
+ const startX = e.clientX;
14589
+ const startY = e.clientY;
14590
+ const DRAG_THRESHOLD = 3;
14591
+
14535
14592
  const clampAndApply = (clientX, clientY, shiftKey = false) => {
14536
14593
  const rect = container.getBoundingClientRect();
14537
14594
  lastRect = rect;
@@ -14581,6 +14638,9 @@ class FigHandle extends HTMLElement {
14581
14638
  const onMove = (e) => {
14582
14639
  if (!this.#isDragging) return;
14583
14640
  if (!this.#didDrag) {
14641
+ const dx = e.clientX - startX;
14642
+ const dy = e.clientY - startY;
14643
+ if (dx * dx + dy * dy < DRAG_THRESHOLD * DRAG_THRESHOLD) return;
14584
14644
  this.classList.add("dragging");
14585
14645
  this.style.cursor = "grabbing";
14586
14646
  if (!this.hasAttribute("selected")) this.select();
@@ -14674,12 +14734,18 @@ class FigHandle extends HTMLElement {
14674
14734
 
14675
14735
  #handleColorTipInput = (e) => {
14676
14736
  e.stopPropagation();
14677
- 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("input", { bubbles: true, detail: { color: e.detail.color } }));
14740
+ }
14678
14741
  };
14679
14742
 
14680
14743
  #handleColorTipChange = (e) => {
14681
14744
  e.stopPropagation();
14682
- if (e.detail?.color) this.setAttribute("color", e.detail.color);
14745
+ if (e.detail?.color) {
14746
+ this.setAttribute("color", e.detail.color);
14747
+ this.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: { color: e.detail.color } }));
14748
+ }
14683
14749
  };
14684
14750
 
14685
14751
  #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.1",
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",