@rogieking/figui3 3.21.2 → 3.22.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
@@ -947,6 +947,78 @@ class FigTooltip extends HTMLElement {
947
947
  this.hidePopup();
948
948
  }
949
949
  }
950
+
951
+ static #programmatic = new WeakMap();
952
+
953
+ static show(anchor, text, options = {}) {
954
+ FigTooltip.hide(anchor);
955
+ const delay = options.delay ?? 500;
956
+ const warm =
957
+ Date.now() - FigTooltip.#lastShownAt < FigTooltip.#warmupWindow;
958
+ const effectiveDelay = warm ? 0 : delay;
959
+
960
+ const state = { timeout: null, popup: null };
961
+ FigTooltip.#programmatic.set(anchor, state);
962
+
963
+ state.timeout = setTimeout(() => {
964
+ const popup = document.createElement("span");
965
+ popup.setAttribute("class", "fig-tooltip");
966
+ popup.setAttribute("role", "tooltip");
967
+ popup.style.position = "fixed";
968
+ popup.style.pointerEvents = "none";
969
+ const content = document.createElement("span");
970
+ content.innerText = text;
971
+ popup.append(content);
972
+
973
+ const parentDialog = anchor.closest("dialog");
974
+ if (parentDialog && parentDialog.open) {
975
+ parentDialog.append(popup);
976
+ } else {
977
+ document.body.append(popup);
978
+ }
979
+
980
+ const rect = anchor.getBoundingClientRect();
981
+ const popupRect = popup.getBoundingClientRect();
982
+ const container = popup.parentElement;
983
+ const containerRect =
984
+ container && container !== document.body
985
+ ? container.getBoundingClientRect()
986
+ : { left: 0, top: 0 };
987
+
988
+ let top = rect.top - popupRect.height - 4 - containerRect.top;
989
+ let left =
990
+ rect.left + (rect.width - popupRect.width) / 2 - containerRect.left;
991
+ popup.setAttribute("position", "top");
992
+
993
+ if (top + containerRect.top < 0) {
994
+ popup.setAttribute("position", "bottom");
995
+ top = rect.bottom + 4 - containerRect.top;
996
+ }
997
+ if (left + containerRect.left < 8) {
998
+ left = 8 - containerRect.left;
999
+ }
1000
+ if (left + popupRect.width + containerRect.left > window.innerWidth - 8) {
1001
+ left = window.innerWidth - popupRect.width - 8 - containerRect.left;
1002
+ }
1003
+
1004
+ const targetCenter = rect.left - containerRect.left + rect.width / 2;
1005
+ popup.style.setProperty("--beak-offset", `${targetCenter - left}px`);
1006
+ popup.style.top = `${top}px`;
1007
+ popup.style.left = `${left}px`;
1008
+ popup.style.zIndex = figGetHighestZIndex() + 1;
1009
+
1010
+ state.popup = popup;
1011
+ FigTooltip.#lastShownAt = Date.now();
1012
+ }, effectiveDelay);
1013
+ }
1014
+
1015
+ static hide(anchor) {
1016
+ const state = FigTooltip.#programmatic.get(anchor);
1017
+ if (!state) return;
1018
+ clearTimeout(state.timeout);
1019
+ if (state.popup) state.popup.remove();
1020
+ FigTooltip.#programmatic.delete(anchor);
1021
+ }
950
1022
  }
951
1023
 
952
1024
  customElements.define("fig-tooltip", FigTooltip);
@@ -3533,8 +3605,12 @@ class FigInputText extends HTMLElement {
3533
3605
  valueTransformed = this.#formatNumber(valueTransformed);
3534
3606
  this.value = value;
3535
3607
  this.input.value = valueTransformed;
3536
- this.dispatchEvent(new CustomEvent("input", { detail: this.value, bubbles: true }));
3537
- this.dispatchEvent(new CustomEvent("change", { detail: this.value, bubbles: true }));
3608
+ this.dispatchEvent(
3609
+ new CustomEvent("input", { detail: this.value, bubbles: true }),
3610
+ );
3611
+ this.dispatchEvent(
3612
+ new CustomEvent("change", { detail: this.value, bubbles: true }),
3613
+ );
3538
3614
  }
3539
3615
  #handleMouseDown(e) {
3540
3616
  if (this.type !== "number") return;
@@ -4023,8 +4099,12 @@ class FigInputNumber extends HTMLElement {
4023
4099
  value = this.#sanitizeInput(value, false);
4024
4100
  this.value = value;
4025
4101
  this.input.value = this.#formatWithUnit(this.value);
4026
- this.dispatchEvent(new CustomEvent("input", { detail: this.value, bubbles: true }));
4027
- this.dispatchEvent(new CustomEvent("change", { detail: this.value, bubbles: true }));
4102
+ this.dispatchEvent(
4103
+ new CustomEvent("input", { detail: this.value, bubbles: true }),
4104
+ );
4105
+ this.dispatchEvent(
4106
+ new CustomEvent("change", { detail: this.value, bubbles: true }),
4107
+ );
4028
4108
  }
4029
4109
 
4030
4110
  #handleMouseDown(e) {
@@ -4209,6 +4289,10 @@ customElements.define("fig-avatar", FigAvatar);
4209
4289
 
4210
4290
  /* Form Field */
4211
4291
  class FigField extends HTMLElement {
4292
+ #toggleable = false;
4293
+ #chevron = null;
4294
+ #boundToggle = null;
4295
+
4212
4296
  constructor() {
4213
4297
  super();
4214
4298
  }
@@ -4223,15 +4307,65 @@ class FigField extends HTMLElement {
4223
4307
  this.input = Array.from(this.childNodes).find((node) =>
4224
4308
  node.nodeName.toLowerCase().startsWith("fig-"),
4225
4309
  );
4226
- if (this.input && this.label) {
4310
+
4311
+ this.#toggleable = !!(this.input && "open" in this.input);
4312
+
4313
+ if (this.#toggleable && this.label) {
4314
+ this.#chevron = document.createElement("span");
4315
+ this.#chevron.className = "fig-mask-icon fig-field-chevron";
4316
+ this.insertBefore(this.#chevron, this.label);
4317
+
4318
+ this.#boundToggle = (e) => {
4319
+ e.preventDefault();
4320
+ e.stopPropagation();
4321
+ if (this.input && typeof this.input.open !== "undefined") {
4322
+ this.input.open = !this.input.open;
4323
+ }
4324
+ };
4325
+ this.#chevron.addEventListener("click", this.#boundToggle);
4326
+ this.label.addEventListener("click", this.#boundToggle);
4327
+ } else if (this.input && this.label) {
4227
4328
  this.label.addEventListener("click", this.focus.bind(this));
4329
+ }
4330
+
4331
+ if (this.input && this.label && !this.#toggleable) {
4228
4332
  let inputId = this.input.getAttribute("id") || figUniqueId();
4229
4333
  this.input.setAttribute("id", inputId);
4230
4334
  this.label.setAttribute("for", inputId);
4231
4335
  }
4336
+
4337
+ if (this.label) {
4338
+ this.label.addEventListener(
4339
+ "pointerenter",
4340
+ this.#onLabelEnter.bind(this),
4341
+ );
4342
+ this.label.addEventListener(
4343
+ "pointerleave",
4344
+ this.#onLabelLeave.bind(this),
4345
+ );
4346
+ }
4232
4347
  });
4233
4348
  }
4234
4349
 
4350
+ disconnectedCallback() {
4351
+ if (this.label) FigTooltip.hide(this.label);
4352
+ if (this.label && this.#boundToggle) {
4353
+ this.label.removeEventListener("click", this.#boundToggle);
4354
+ }
4355
+ if (this.#chevron && this.#boundToggle) {
4356
+ this.#chevron.removeEventListener("click", this.#boundToggle);
4357
+ }
4358
+ }
4359
+
4360
+ #onLabelEnter() {
4361
+ if (!this.label || this.label.scrollWidth <= this.label.clientWidth) return;
4362
+ FigTooltip.show(this.label, this.label.textContent.trim());
4363
+ }
4364
+
4365
+ #onLabelLeave() {
4366
+ if (this.label) FigTooltip.hide(this.label);
4367
+ }
4368
+
4235
4369
  attributeChangedCallback(name, oldValue, newValue) {
4236
4370
  switch (name) {
4237
4371
  case "label":
@@ -4243,7 +4377,18 @@ class FigField extends HTMLElement {
4243
4377
  }
4244
4378
 
4245
4379
  focus() {
4246
- this.input.focus();
4380
+ if (!this.input) return;
4381
+ if (this.input.contains(document.activeElement)) return;
4382
+ const nativeInputs = this.input.querySelectorAll("input, select, textarea");
4383
+ if (nativeInputs.length === 1) {
4384
+ nativeInputs[0].focus();
4385
+ nativeInputs[0].click();
4386
+ } else {
4387
+ this.input.focus();
4388
+ if (nativeInputs.length === 0) {
4389
+ this.input.click();
4390
+ }
4391
+ }
4247
4392
  }
4248
4393
  }
4249
4394
  customElements.define("fig-field", FigField);
@@ -4578,7 +4723,7 @@ class FigInputColor extends HTMLElement {
4578
4723
  const disabledAttr = disabled ? " disabled" : "";
4579
4724
 
4580
4725
  let html = ``;
4581
- const showText = this.getAttribute("text") === "true";
4726
+ const showText = this.getAttribute("text") !== "false";
4582
4727
  if (showText) {
4583
4728
  let label = `<fig-input-text
4584
4729
  type="text"
@@ -4801,22 +4946,35 @@ class FigInputColor extends HTMLElement {
4801
4946
  }
4802
4947
 
4803
4948
  #emitInputEvent() {
4804
- this.dispatchEvent(new CustomEvent("input", {
4805
- bubbles: true,
4806
- cancelable: true,
4807
- detail: { value: this.value, hex: this.hex, rgba: this.rgba },
4808
- }));
4949
+ this.dispatchEvent(
4950
+ new CustomEvent("input", {
4951
+ bubbles: true,
4952
+ cancelable: true,
4953
+ detail: { value: this.value, hex: this.hex, rgba: this.rgba },
4954
+ }),
4955
+ );
4809
4956
  }
4810
4957
  #emitChangeEvent() {
4811
- this.dispatchEvent(new CustomEvent("change", {
4812
- bubbles: true,
4813
- cancelable: true,
4814
- detail: { value: this.value, hex: this.hex, rgba: this.rgba },
4815
- }));
4958
+ this.dispatchEvent(
4959
+ new CustomEvent("change", {
4960
+ bubbles: true,
4961
+ cancelable: true,
4962
+ detail: { value: this.value, hex: this.hex, rgba: this.rgba },
4963
+ }),
4964
+ );
4816
4965
  }
4817
4966
 
4818
4967
  static get observedAttributes() {
4819
- return ["value", "style", "mode", "picker", "experimental", "alpha", "text", "disabled"];
4968
+ return [
4969
+ "value",
4970
+ "style",
4971
+ "mode",
4972
+ "picker",
4973
+ "experimental",
4974
+ "alpha",
4975
+ "text",
4976
+ "disabled",
4977
+ ];
4820
4978
  }
4821
4979
 
4822
4980
  get mode() {
@@ -4877,7 +5035,9 @@ class FigInputColor extends HTMLElement {
4877
5035
  }
4878
5036
 
4879
5037
  get #disabled() {
4880
- return this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
5038
+ return (
5039
+ this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false"
5040
+ );
4881
5041
  }
4882
5042
 
4883
5043
  #syncDisabled() {
@@ -5076,9 +5236,15 @@ function figRGBToOklab(r, g, b) {
5076
5236
  const lr = figRGBToLinear(r);
5077
5237
  const lg = figRGBToLinear(g);
5078
5238
  const lb = figRGBToLinear(b);
5079
- const l_ = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
5080
- const m_ = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
5081
- const s_ = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
5239
+ const l_ = Math.cbrt(
5240
+ 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb,
5241
+ );
5242
+ const m_ = Math.cbrt(
5243
+ 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb,
5244
+ );
5245
+ const s_ = Math.cbrt(
5246
+ 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb,
5247
+ );
5082
5248
  return {
5083
5249
  l: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
5084
5250
  a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
@@ -5101,7 +5267,11 @@ function figOklabToRGB(L, a, b) {
5101
5267
  }
5102
5268
 
5103
5269
  function figOklabToOklch(L, a, b) {
5104
- return { l: L, c: Math.sqrt(a * a + b * b), h: (Math.atan2(b, a) * 180) / Math.PI };
5270
+ return {
5271
+ l: L,
5272
+ c: Math.sqrt(a * a + b * b),
5273
+ h: (Math.atan2(b, a) * 180) / Math.PI,
5274
+ };
5105
5275
  }
5106
5276
 
5107
5277
  function figOklchToOklab(l, c, h) {
@@ -5130,15 +5300,21 @@ function figInterpolateHue(h1, h2, t, mode) {
5130
5300
  else if (diff < -180) diff += 360;
5131
5301
  break;
5132
5302
  }
5133
- return ((a + diff * t) % 360 + 360) % 360;
5303
+ return (((a + diff * t) % 360) + 360) % 360;
5134
5304
  }
5135
5305
 
5136
- function figSampleGradientAt(stops, position, interpolationSpace, hueInterpolation) {
5306
+ function figSampleGradientAt(
5307
+ stops,
5308
+ position,
5309
+ interpolationSpace,
5310
+ hueInterpolation,
5311
+ ) {
5137
5312
  const sorted = [...stops].sort((a, b) => a.position - b.position);
5138
5313
  const pos = position * 100;
5139
5314
  if (sorted.length === 0) return "#888888";
5140
5315
  if (pos <= sorted[0].position) return sorted[0].color;
5141
- if (pos >= sorted[sorted.length - 1].position) return sorted[sorted.length - 1].color;
5316
+ if (pos >= sorted[sorted.length - 1].position)
5317
+ return sorted[sorted.length - 1].color;
5142
5318
 
5143
5319
  let i = 0;
5144
5320
  while (i < sorted.length - 1 && sorted[i + 1].position < pos) i++;
@@ -5154,8 +5330,12 @@ function figSampleGradientAt(stops, position, interpolationSpace, hueInterpolati
5154
5330
  const space = interpolationSpace || "oklab";
5155
5331
 
5156
5332
  if (space === "srgb-linear") {
5157
- const lr1 = figRGBToLinear(c1.r), lg1 = figRGBToLinear(c1.g), lb1 = figRGBToLinear(c1.b);
5158
- const lr2 = figRGBToLinear(c2.r), lg2 = figRGBToLinear(c2.g), lb2 = figRGBToLinear(c2.b);
5333
+ const lr1 = figRGBToLinear(c1.r),
5334
+ lg1 = figRGBToLinear(c1.g),
5335
+ lb1 = figRGBToLinear(c1.b);
5336
+ const lr2 = figRGBToLinear(c2.r),
5337
+ lg2 = figRGBToLinear(c2.g),
5338
+ lb2 = figRGBToLinear(c2.b);
5159
5339
  r = figLinearToSRGB(lr1 + (lr2 - lr1) * t);
5160
5340
  g = figLinearToSRGB(lg1 + (lg2 - lg1) * t);
5161
5341
  b = figLinearToSRGB(lb1 + (lb2 - lb1) * t);
@@ -5166,10 +5346,17 @@ function figSampleGradientAt(stops, position, interpolationSpace, hueInterpolati
5166
5346
  const lch2 = figOklabToOklch(lab2.l, lab2.a, lab2.b);
5167
5347
  const L = lch1.l + (lch2.l - lch1.l) * t;
5168
5348
  const C = lch1.c + (lch2.c - lch1.c) * t;
5169
- const H = figInterpolateHue(lch1.h, lch2.h, t, hueInterpolation || "shorter");
5349
+ const H = figInterpolateHue(
5350
+ lch1.h,
5351
+ lch2.h,
5352
+ t,
5353
+ hueInterpolation || "shorter",
5354
+ );
5170
5355
  const lab = figOklchToOklab(L, C, H);
5171
5356
  const rgb = figOklabToRGB(lab.l, lab.a, lab.b);
5172
- r = rgb.r; g = rgb.g; b = rgb.b;
5357
+ r = rgb.r;
5358
+ g = rgb.g;
5359
+ b = rgb.b;
5173
5360
  } else {
5174
5361
  const lab1 = figRGBToOklab(c1.r, c1.g, c1.b);
5175
5362
  const lab2 = figRGBToOklab(c2.r, c2.g, c2.b);
@@ -5177,7 +5364,9 @@ function figSampleGradientAt(stops, position, interpolationSpace, hueInterpolati
5177
5364
  const a = lab1.a + (lab2.a - lab1.a) * t;
5178
5365
  const bv = lab1.b + (lab2.b - lab1.b) * t;
5179
5366
  const rgb = figOklabToRGB(L, a, bv);
5180
- r = rgb.r; g = rgb.g; b = rgb.b;
5367
+ r = rgb.r;
5368
+ g = rgb.g;
5369
+ b = rgb.b;
5181
5370
  }
5182
5371
 
5183
5372
  return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`.toUpperCase();
@@ -5347,7 +5536,11 @@ class FigInputFill extends HTMLElement {
5347
5536
 
5348
5537
  #syncDisabled() {
5349
5538
  const disabled = this.hasAttribute("disabled");
5350
- for (const child of [this.#fillPicker, this.#opacityInput, this.#hexInput]) {
5539
+ for (const child of [
5540
+ this.#fillPicker,
5541
+ this.#opacityInput,
5542
+ this.#hexInput,
5543
+ ]) {
5351
5544
  if (!child) continue;
5352
5545
  if (disabled) child.setAttribute("disabled", "");
5353
5546
  else child.removeAttribute("disabled");
@@ -5911,11 +6104,28 @@ class FigInputPalette extends HTMLElement {
5911
6104
  #renderRAF = null;
5912
6105
 
5913
6106
  static get observedAttributes() {
5914
- return ["value", "disabled", "min", "max", "expanded", "add"];
6107
+ return ["value", "disabled", "min", "max", "open", "add"];
6108
+ }
6109
+
6110
+ get open() {
6111
+ return this.hasAttribute("open") && this.getAttribute("open") !== "false";
5915
6112
  }
5916
6113
 
5917
- get #expanded() {
5918
- return this.hasAttribute("expanded") && this.getAttribute("expanded") !== "false";
6114
+ set open(value) {
6115
+ const was = this.open;
6116
+ if (value) {
6117
+ this.setAttribute("open", "");
6118
+ } else {
6119
+ this.removeAttribute("open");
6120
+ }
6121
+ if (was !== !!value) {
6122
+ this.dispatchEvent(
6123
+ new CustomEvent("openchange", {
6124
+ detail: { open: !!value },
6125
+ bubbles: true,
6126
+ }),
6127
+ );
6128
+ }
5919
6129
  }
5920
6130
 
5921
6131
  get #showAdd() {
@@ -5933,6 +6143,7 @@ class FigInputPalette extends HTMLElement {
5933
6143
  }
5934
6144
 
5935
6145
  connectedCallback() {
6146
+ if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0");
5936
6147
  if (this.#renderRAF) cancelAnimationFrame(this.#renderRAF);
5937
6148
  this.#renderRAF = requestAnimationFrame(() => {
5938
6149
  this.#renderRAF = null;
@@ -5963,7 +6174,7 @@ class FigInputPalette extends HTMLElement {
5963
6174
  break;
5964
6175
  case "min":
5965
6176
  case "max":
5966
- case "expanded":
6177
+ case "open":
5967
6178
  case "add":
5968
6179
  this.#render();
5969
6180
  break;
@@ -5985,12 +6196,21 @@ class FigInputPalette extends HTMLElement {
5985
6196
  if (Array.isArray(parsed)) {
5986
6197
  this.#colors = parsed.map((entry) => {
5987
6198
  if (typeof entry === "string") {
5988
- return { color: entry.slice(0, 7), alpha: entry.length > 7 ? parseInt(entry.slice(7, 9), 16) / 255 : 1 };
6199
+ return {
6200
+ color: entry.slice(0, 7),
6201
+ alpha:
6202
+ entry.length > 7 ? parseInt(entry.slice(7, 9), 16) / 255 : 1,
6203
+ };
5989
6204
  }
5990
6205
  if (entry && typeof entry === "object") {
5991
6206
  return {
5992
6207
  color: entry.color || "#D9D9D9",
5993
- alpha: entry.alpha !== undefined ? entry.alpha : (entry.opacity !== undefined ? entry.opacity / 100 : 1),
6208
+ alpha:
6209
+ entry.alpha !== undefined
6210
+ ? entry.alpha
6211
+ : entry.opacity !== undefined
6212
+ ? entry.opacity / 100
6213
+ : 1,
5994
6214
  };
5995
6215
  }
5996
6216
  return { color: "#D9D9D9", alpha: 1 };
@@ -6015,10 +6235,13 @@ class FigInputPalette extends HTMLElement {
6015
6235
 
6016
6236
  // Single hex
6017
6237
  if (trimmed.startsWith("#")) {
6018
- this.#colors = [{
6019
- color: trimmed.slice(0, 7),
6020
- alpha: trimmed.length > 7 ? parseInt(trimmed.slice(7, 9), 16) / 255 : 1,
6021
- }];
6238
+ this.#colors = [
6239
+ {
6240
+ color: trimmed.slice(0, 7),
6241
+ alpha:
6242
+ trimmed.length > 7 ? parseInt(trimmed.slice(7, 9), 16) / 255 : 1,
6243
+ },
6244
+ ];
6022
6245
  return;
6023
6246
  }
6024
6247
 
@@ -6038,7 +6261,9 @@ class FigInputPalette extends HTMLElement {
6038
6261
  }
6039
6262
 
6040
6263
  #render() {
6041
- const disabled = this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
6264
+ const disabled =
6265
+ this.hasAttribute("disabled") &&
6266
+ this.getAttribute("disabled") !== "false";
6042
6267
 
6043
6268
  this.innerHTML = "";
6044
6269
  this.#inlinePickers = [];
@@ -6050,7 +6275,9 @@ class FigInputPalette extends HTMLElement {
6050
6275
  const wrap = document.createElement("div");
6051
6276
  wrap.className = "palette-colors";
6052
6277
  this.#colors.forEach((entry, i) => {
6053
- wrap.appendChild(this.#createPicker(entry, i, disabled, { inline: true }));
6278
+ wrap.appendChild(
6279
+ this.#createPicker(entry, i, disabled, { inline: true }),
6280
+ );
6054
6281
  });
6055
6282
  inlineWrap.appendChild(wrap);
6056
6283
 
@@ -6066,9 +6293,13 @@ class FigInputPalette extends HTMLElement {
6066
6293
  }
6067
6294
 
6068
6295
  #createPicker(entry, index, disabled, { inline = false } = {}) {
6069
- const hexAlpha = entry.alpha < 1
6070
- ? entry.color + Math.round(entry.alpha * 255).toString(16).padStart(2, "0")
6071
- : entry.color;
6296
+ const hexAlpha =
6297
+ entry.alpha < 1
6298
+ ? entry.color +
6299
+ Math.round(entry.alpha * 255)
6300
+ .toString(16)
6301
+ .padStart(2, "0")
6302
+ : entry.color;
6072
6303
  const ic = document.createElement("fig-input-color");
6073
6304
  ic.setAttribute("value", hexAlpha);
6074
6305
  ic.setAttribute("picker", "figma");
@@ -6095,9 +6326,13 @@ class FigInputPalette extends HTMLElement {
6095
6326
  const sibling = siblingList[index];
6096
6327
  if (sibling) {
6097
6328
  const entry = this.#colors[index];
6098
- const hex = entry.alpha < 1
6099
- ? entry.color + Math.round(entry.alpha * 255).toString(16).padStart(2, "0")
6100
- : entry.color;
6329
+ const hex =
6330
+ entry.alpha < 1
6331
+ ? entry.color +
6332
+ Math.round(entry.alpha * 255)
6333
+ .toString(16)
6334
+ .padStart(2, "0")
6335
+ : entry.color;
6101
6336
  sibling.setAttribute("value", hex);
6102
6337
  }
6103
6338
  };
@@ -6120,14 +6355,18 @@ class FigInputPalette extends HTMLElement {
6120
6355
  #createAddButton(disabled, parent = this) {
6121
6356
  const atMax = this.#colors.length >= this.#max;
6122
6357
  const addBtn = document.createElement("fig-button");
6123
- addBtn.setAttribute("variant", "ghost");
6358
+ addBtn.setAttribute("variant", "input");
6124
6359
  addBtn.setAttribute("icon", "true");
6125
6360
  addBtn.setAttribute("aria-label", "Add color");
6126
6361
  addBtn.className = "palette-add-btn";
6127
6362
  if (disabled || atMax) addBtn.setAttribute("disabled", "");
6128
6363
  addBtn.innerHTML = `<span class="fig-mask-icon" style="--icon: var(--icon-add)"></span>`;
6129
6364
  addBtn.addEventListener("click", () => {
6130
- if (this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false") return;
6365
+ if (
6366
+ this.hasAttribute("disabled") &&
6367
+ this.getAttribute("disabled") !== "false"
6368
+ )
6369
+ return;
6131
6370
  if (this.#colors.length >= this.#max) return;
6132
6371
  this.#addColor({ color: "#D9D9D9", alpha: 1 });
6133
6372
  });
@@ -6139,10 +6378,14 @@ class FigInputPalette extends HTMLElement {
6139
6378
 
6140
6379
  #addColor(entry) {
6141
6380
  this.#colors.push(entry);
6142
- const disabled = this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
6381
+ const disabled =
6382
+ this.hasAttribute("disabled") &&
6383
+ this.getAttribute("disabled") !== "false";
6143
6384
  const index = this.#colors.length - 1;
6144
6385
 
6145
- const inlineIc = this.#createPicker(entry, index, disabled, { inline: true });
6386
+ const inlineIc = this.#createPicker(entry, index, disabled, {
6387
+ inline: true,
6388
+ });
6146
6389
  const wrap = this.querySelector(".palette-colors");
6147
6390
  if (wrap) wrap.appendChild(inlineIc);
6148
6391
 
@@ -6160,9 +6403,13 @@ class FigInputPalette extends HTMLElement {
6160
6403
  #updateChit(index) {
6161
6404
  const entry = this.#colors[index];
6162
6405
  if (!entry) return;
6163
- const hexAlpha = entry.alpha < 1
6164
- ? entry.color + Math.round(entry.alpha * 255).toString(16).padStart(2, "0")
6165
- : entry.color;
6406
+ const hexAlpha =
6407
+ entry.alpha < 1
6408
+ ? entry.color +
6409
+ Math.round(entry.alpha * 255)
6410
+ .toString(16)
6411
+ .padStart(2, "0")
6412
+ : entry.color;
6166
6413
  const inl = this.#inlinePickers[index];
6167
6414
  if (inl) inl.setAttribute("value", hexAlpha);
6168
6415
  const exp = this.#expandedPickers[index];
@@ -6180,7 +6427,9 @@ class FigInputPalette extends HTMLElement {
6180
6427
  }
6181
6428
 
6182
6429
  #syncDisabled() {
6183
- const disabled = this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
6430
+ const disabled =
6431
+ this.hasAttribute("disabled") &&
6432
+ this.getAttribute("disabled") !== "false";
6184
6433
  [...this.#inlinePickers, ...this.#expandedPickers].forEach((fp) => {
6185
6434
  if (disabled) fp.setAttribute("disabled", "");
6186
6435
  else fp.removeAttribute("disabled");
@@ -6314,7 +6563,9 @@ class FigInputGradient extends HTMLElement {
6314
6563
  const idx = parseInt(selected.dataset.stopIndex, 10);
6315
6564
  if (isNaN(idx) || !this.#gradient.stops[idx]) return;
6316
6565
  e.preventDefault();
6317
- const delta = (e.key === "ArrowRight" ? 1 : -1) * (e.shiftKey ? FigInputGradient.SHIFT_SNAP : 1);
6566
+ const delta =
6567
+ (e.key === "ArrowRight" ? 1 : -1) *
6568
+ (e.shiftKey ? FigInputGradient.SHIFT_SNAP : 1);
6318
6569
  const stop = this.#gradient.stops[idx];
6319
6570
  stop.position = Math.max(0, Math.min(100, stop.position + delta));
6320
6571
  selected.setAttribute("value", `${stop.position}% 50%`);
@@ -6582,10 +6833,12 @@ class FigInputGradient extends HTMLElement {
6582
6833
  const stopIdx = parseInt(clickedHandle?.dataset.stopIndex, 10);
6583
6834
  this.#distributeStops();
6584
6835
  if (!isNaN(stopIdx)) {
6585
- this.#track.querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)").forEach((h) => {
6586
- if (parseInt(h.dataset.stopIndex, 10) === stopIdx) h.select();
6587
- else h.deselect();
6588
- });
6836
+ this.#track
6837
+ .querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)")
6838
+ .forEach((h) => {
6839
+ if (parseInt(h.dataset.stopIndex, 10) === stopIdx) h.select();
6840
+ else h.deselect();
6841
+ });
6589
6842
  }
6590
6843
  e.stopPropagation();
6591
6844
  }
@@ -6724,7 +6977,10 @@ class FigInputGradient extends HTMLElement {
6724
6977
  e.stopPropagation();
6725
6978
 
6726
6979
  const trackRect = this.#track.getBoundingClientRect();
6727
- const pct = Math.max(0, Math.min(1, (e.clientX - trackRect.left) / trackRect.width));
6980
+ const pct = Math.max(
6981
+ 0,
6982
+ Math.min(1, (e.clientX - trackRect.left) / trackRect.width),
6983
+ );
6728
6984
  const position = Math.round(pct * 100);
6729
6985
  const color = this.#sampleGradientColor(pct);
6730
6986
  this.#gradient.stops.push({ position, color, opacity: 100 });
@@ -6743,19 +6999,23 @@ class FigInputGradient extends HTMLElement {
6743
6999
  );
6744
7000
  const newHandle = handles[newIndex];
6745
7001
  if (newHandle) {
6746
- this.#track.querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)").forEach((h) => {
6747
- if (h !== newHandle) h.deselect();
6748
- });
7002
+ this.#track
7003
+ .querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)")
7004
+ .forEach((h) => {
7005
+ if (h !== newHandle) h.deselect();
7006
+ });
6749
7007
  newHandle.select();
6750
- newHandle.dispatchEvent(new PointerEvent("pointerdown", {
6751
- bubbles: true,
6752
- clientX: e.clientX,
6753
- clientY: e.clientY,
6754
- pointerId: e.pointerId,
6755
- pointerType: e.pointerType,
6756
- button: e.button,
6757
- buttons: e.buttons,
6758
- }));
7008
+ newHandle.dispatchEvent(
7009
+ new PointerEvent("pointerdown", {
7010
+ bubbles: true,
7011
+ clientX: e.clientX,
7012
+ clientY: e.clientY,
7013
+ pointerId: e.pointerId,
7014
+ pointerType: e.pointerType,
7015
+ button: e.button,
7016
+ buttons: e.buttons,
7017
+ }),
7018
+ );
6759
7019
  }
6760
7020
  });
6761
7021
 
@@ -6770,7 +7030,10 @@ class FigInputGradient extends HTMLElement {
6770
7030
  if (e.detail.opacity !== undefined) {
6771
7031
  this.#gradient.stops[idx].opacity = e.detail.opacity;
6772
7032
  }
6773
- handle.setAttribute("color", this.#stopColorCSS(this.#gradient.stops[idx]));
7033
+ handle.setAttribute(
7034
+ "color",
7035
+ this.#stopColorCSS(this.#gradient.stops[idx]),
7036
+ );
6774
7037
  this.#syncChit();
6775
7038
  this.#emitInput();
6776
7039
  }
@@ -6785,7 +7048,9 @@ class FigInputGradient extends HTMLElement {
6785
7048
  let position = rawPosition;
6786
7049
  const trackW = this.#track.getBoundingClientRect().width;
6787
7050
  if (e.detail?.shiftKey) {
6788
- position = Math.round(position / FigInputGradient.SHIFT_SNAP) * FigInputGradient.SHIFT_SNAP;
7051
+ position =
7052
+ Math.round(position / FigInputGradient.SHIFT_SNAP) *
7053
+ FigInputGradient.SHIFT_SNAP;
6789
7054
  } else {
6790
7055
  const snapPct = trackW > 0 ? (5 / trackW) * 100 : 0;
6791
7056
  for (let i = 0; i < this.#gradient.stops.length; i++) {
@@ -6825,7 +7090,10 @@ class FigInputGradient extends HTMLElement {
6825
7090
  if (e.detail.opacity !== undefined) {
6826
7091
  this.#gradient.stops[idx].opacity = e.detail.opacity;
6827
7092
  }
6828
- handle.setAttribute("color", this.#stopColorCSS(this.#gradient.stops[idx]));
7093
+ handle.setAttribute(
7094
+ "color",
7095
+ this.#stopColorCSS(this.#gradient.stops[idx]),
7096
+ );
6829
7097
  this.#syncChit();
6830
7098
  this.#emitChange();
6831
7099
  }
@@ -7578,13 +7846,13 @@ class FigChit extends HTMLElement {
7578
7846
 
7579
7847
  if (this.#type === "color") {
7580
7848
  const hex = this.#toHex(bg);
7581
- this.innerHTML = `<input type="color" value="${hex}" />`;
7849
+ this.innerHTML = `<div></div><input type="color" value="${hex}" />`;
7582
7850
  this.input = this.querySelector("input");
7583
7851
  if (!isVar) {
7584
7852
  this.input.addEventListener("input", this.#boundHandleInput);
7585
7853
  }
7586
7854
  } else {
7587
- this.innerHTML = "";
7855
+ this.innerHTML = "<div></div>";
7588
7856
  this.input = null;
7589
7857
  }
7590
7858
  } else if (this.#type === "color" && this.input) {
@@ -7594,8 +7862,14 @@ class FigChit extends HTMLElement {
7594
7862
  }
7595
7863
  }
7596
7864
 
7597
- const isImage = /^(linear-gradient|radial-gradient|conic-gradient|repeating-|url)\s*\(/i.test(rawBg);
7598
- this.style.setProperty("--chit-background", isImage ? rawBg : `linear-gradient(${rawBg}, ${rawBg})`);
7865
+ const isImage =
7866
+ /^(linear-gradient|radial-gradient|conic-gradient|repeating-|url)\s*\(/i.test(
7867
+ rawBg,
7868
+ );
7869
+ this.style.setProperty(
7870
+ "--chit-background",
7871
+ isImage ? rawBg : `linear-gradient(${rawBg}, ${rawBg})`,
7872
+ );
7599
7873
  }
7600
7874
 
7601
7875
  #handleInput(e) {
@@ -7622,8 +7896,14 @@ class FigChit extends HTMLElement {
7622
7896
  if (oldValue === newValue) return;
7623
7897
  if (name === "background") {
7624
7898
  if (this.#internalUpdate) {
7625
- const isImg = /^(linear-gradient|radial-gradient|conic-gradient|repeating-|url)\s*\(/i.test(newValue);
7626
- this.style.setProperty("--chit-background", isImg ? newValue : `linear-gradient(${newValue}, ${newValue})`);
7899
+ const isImg =
7900
+ /^(linear-gradient|radial-gradient|conic-gradient|repeating-|url)\s*\(/i.test(
7901
+ newValue,
7902
+ );
7903
+ this.style.setProperty(
7904
+ "--chit-background",
7905
+ isImg ? newValue : `linear-gradient(${newValue}, ${newValue})`,
7906
+ );
7627
7907
  return;
7628
7908
  }
7629
7909
  this.#render();
@@ -7645,9 +7925,7 @@ class FigChit extends HTMLElement {
7645
7925
  }
7646
7926
  }
7647
7927
  customElements.define("fig-chit", FigChit);
7648
- class FigSwatch extends FigChit{
7649
-
7650
- }
7928
+ class FigSwatch extends FigChit {}
7651
7929
  customElements.define("fig-swatch", FigSwatch);
7652
7930
 
7653
7931
  /* Upload */
@@ -8828,7 +9106,11 @@ class Fig3DRotate extends HTMLElement {
8828
9106
  this.#precision = parseInt(this.getAttribute("precision") || "1");
8829
9107
  figSyncCssVar(this, "--aspect-ratio", this.getAttribute("aspect-ratio"));
8830
9108
  figSyncCssVar(this, "--perspective", this.getAttribute("perspective"));
8831
- figSyncCssVar(this, "--perspective-origin", this.getAttribute("perspective-origin"));
9109
+ figSyncCssVar(
9110
+ this,
9111
+ "--perspective-origin",
9112
+ this.getAttribute("perspective-origin"),
9113
+ );
8832
9114
  this.#syncTransformOrigin(this.getAttribute("transform-origin"));
8833
9115
  this.#parseFields(this.getAttribute("fields"));
8834
9116
  const val = this.getAttribute("value");
@@ -8846,8 +9128,6 @@ class Fig3DRotate extends HTMLElement {
8846
9128
  }
8847
9129
  }
8848
9130
 
8849
-
8850
-
8851
9131
  #syncTransformOrigin(value) {
8852
9132
  if (!value || !value.trim()) {
8853
9133
  this.style.removeProperty("--transform-origin");
@@ -10682,6 +10962,117 @@ class FigLayer extends HTMLElement {
10682
10962
  }
10683
10963
  customElements.define("fig-layer", FigLayer);
10684
10964
 
10965
+ // FigGroup
10966
+ class FigGroup extends HTMLElement {
10967
+ static observedAttributes = ["name", "collapsible"];
10968
+
10969
+ #header = null;
10970
+ #chevron = null;
10971
+
10972
+ connectedCallback() {
10973
+ requestAnimationFrame(() => this.#render());
10974
+ }
10975
+
10976
+ disconnectedCallback() {
10977
+ if (this.#chevron) {
10978
+ this.#chevron.removeEventListener("click", this.#handleToggle);
10979
+ }
10980
+ if (this.#header) {
10981
+ this.#header.removeEventListener("click", this.#handleToggle);
10982
+ }
10983
+ }
10984
+
10985
+ attributeChangedCallback(name, oldValue, newValue) {
10986
+ if (oldValue === newValue) return;
10987
+ this.#render();
10988
+ }
10989
+
10990
+ get open() {
10991
+ const attr = this.getAttribute("open");
10992
+ return attr !== null && attr !== "false";
10993
+ }
10994
+
10995
+ set open(value) {
10996
+ const was = this.open;
10997
+ if (value) {
10998
+ this.setAttribute("open", "true");
10999
+ } else {
11000
+ this.setAttribute("open", "false");
11001
+ }
11002
+ if (was !== !!value) {
11003
+ this.dispatchEvent(
11004
+ new CustomEvent("openchange", {
11005
+ detail: { open: !!value },
11006
+ bubbles: true,
11007
+ }),
11008
+ );
11009
+ }
11010
+ }
11011
+
11012
+ #handleToggle = (e) => {
11013
+ e.stopPropagation();
11014
+ this.open = !this.open;
11015
+ };
11016
+
11017
+ #render() {
11018
+ const isCollapsible = this.hasAttribute("collapsible");
11019
+ const nameAttr = this.getAttribute("name");
11020
+ const label = nameAttr || (isCollapsible ? "Group" : null);
11021
+
11022
+ // Check if user supplied their own fig-header
11023
+ const userHeader = this.querySelector(":scope > fig-header");
11024
+
11025
+ if (!label && !isCollapsible && !userHeader) {
11026
+ if (this.#header && this.#header.dataset.generated) {
11027
+ this.#header.remove();
11028
+ this.#header = null;
11029
+ this.#chevron = null;
11030
+ }
11031
+ return;
11032
+ }
11033
+
11034
+ if (userHeader) {
11035
+ this.#header = userHeader;
11036
+ } else if (!this.#header || !this.#header.dataset.generated) {
11037
+ this.#header = document.createElement("fig-header");
11038
+ this.#header.setAttribute("borderless", "");
11039
+ this.#header.dataset.generated = "true";
11040
+ this.prepend(this.#header);
11041
+ }
11042
+
11043
+ // Ensure h3 exists inside header
11044
+ let h3 = this.#header.querySelector("h3");
11045
+ if (!h3) {
11046
+ h3 = document.createElement("h3");
11047
+ this.#header.prepend(h3);
11048
+ }
11049
+ if (this.#header.dataset.generated) {
11050
+ h3.textContent = label;
11051
+ }
11052
+
11053
+ if (isCollapsible) {
11054
+ if (!h3.querySelector(".fig-group-chevron")) {
11055
+ const chevron = document.createElement("span");
11056
+ chevron.className = "fig-mask-icon fig-group-chevron";
11057
+ h3.prepend(chevron);
11058
+ }
11059
+ this.#chevron = h3.querySelector(".fig-group-chevron");
11060
+ h3.addEventListener("click", this.#handleToggle);
11061
+
11062
+ if (!this.hasAttribute("open")) {
11063
+ this.setAttribute("open", "false");
11064
+ }
11065
+ } else {
11066
+ if (this.#chevron) {
11067
+ this.#chevron.remove();
11068
+ this.#chevron = null;
11069
+ }
11070
+ this.removeAttribute("open");
11071
+ }
11072
+ }
11073
+ }
11074
+ customElements.define("fig-group", FigGroup);
11075
+
10685
11076
  // FigFillPicker
10686
11077
  /**
10687
11078
  * A comprehensive fill picker component supporting solid colors, gradients, images, video, and webcam.
@@ -11114,10 +11505,15 @@ class FigFillPicker extends HTMLElement {
11114
11505
  this.#dialog.addEventListener("close", onDialogClose);
11115
11506
 
11116
11507
  this.#dialogOpenObserver = new MutationObserver(() => {
11117
- const isOpen = this.#dialog.hasAttribute("open") && this.#dialog.getAttribute("open") !== "false";
11508
+ const isOpen =
11509
+ this.#dialog.hasAttribute("open") &&
11510
+ this.#dialog.getAttribute("open") !== "false";
11118
11511
  if (!isOpen) onDialogClose();
11119
11512
  });
11120
- this.#dialogOpenObserver.observe(this.#dialog, { attributes: true, attributeFilter: ["open"] });
11513
+ this.#dialogOpenObserver.observe(this.#dialog, {
11514
+ attributes: true,
11515
+ attributeFilter: ["open"],
11516
+ });
11121
11517
 
11122
11518
  // Initialize built-in tabs (skip any overridden by custom slots)
11123
11519
  const builtinInits = {
@@ -11213,6 +11609,8 @@ class FigFillPicker extends HTMLElement {
11213
11609
  <fig-preview class="fig-fill-picker-color-area">
11214
11610
  <canvas width="200" height="200"></canvas>
11215
11611
  <fig-handle
11612
+ type="color"
11613
+ color="${this.#hsvToHex({ ...this.#color, a: 1 })}"
11216
11614
  drag
11217
11615
  drag-surface=".fig-fill-picker-color-area"
11218
11616
  drag-axes="x,y"
@@ -11875,7 +12273,9 @@ class FigFillPicker extends HTMLElement {
11875
12273
  });
11876
12274
 
11877
12275
  // Embedded gradient bar input
11878
- const gradientBarInput = container.querySelector(".fig-fill-picker-gradient-bar-input");
12276
+ const gradientBarInput = container.querySelector(
12277
+ ".fig-fill-picker-gradient-bar-input",
12278
+ );
11879
12279
  if (gradientBarInput) {
11880
12280
  const syncFromBarInput = (e) => {
11881
12281
  e.stopPropagation();
@@ -11948,7 +12348,10 @@ class FigFillPicker extends HTMLElement {
11948
12348
  if (barInput) {
11949
12349
  barInput.setAttribute(
11950
12350
  "value",
11951
- JSON.stringify({ type: "gradient", gradient: gradientToValueShape(this.#gradient) }),
12351
+ JSON.stringify({
12352
+ type: "gradient",
12353
+ gradient: gradientToValueShape(this.#gradient),
12354
+ }),
11952
12355
  );
11953
12356
  }
11954
12357
 
@@ -11977,7 +12380,8 @@ class FigFillPicker extends HTMLElement {
11977
12380
  if (colorInput) colorInput.setAttribute("value", stop.color);
11978
12381
  const removeBtn = row.querySelector(".fig-fill-picker-stop-remove");
11979
12382
  if (removeBtn) {
11980
- if (this.#gradient.stops.length <= 2) removeBtn.setAttribute("disabled", "");
12383
+ if (this.#gradient.stops.length <= 2)
12384
+ removeBtn.setAttribute("disabled", "");
11981
12385
  else removeBtn.removeAttribute("disabled");
11982
12386
  }
11983
12387
  });
@@ -12910,9 +13314,14 @@ class FigColorTip extends HTMLElement {
12910
13314
  const color = this.#normalizeColor(rawValue);
12911
13315
  const alpha = this.#extractAlpha(rawValue);
12912
13316
  const alphaAttr = this.#alphaEnabled ? "" : 'alpha="false"';
12913
- const pickerValue = alpha < 1
12914
- ? JSON.stringify({ type: "solid", color, opacity: Math.round(alpha * 100) })
12915
- : JSON.stringify({ type: "solid", color });
13317
+ const pickerValue =
13318
+ alpha < 1
13319
+ ? JSON.stringify({
13320
+ type: "solid",
13321
+ color,
13322
+ opacity: Math.round(alpha * 100),
13323
+ })
13324
+ : JSON.stringify({ type: "solid", color });
12916
13325
  this.innerHTML = `
12917
13326
  <fig-fill-picker mode="solid" ${alphaAttr} value='${pickerValue}'>
12918
13327
  <fig-chit background="${color}"></fig-chit>
@@ -12955,7 +13364,9 @@ class FigColorTip extends HTMLElement {
12955
13364
  if (v.startsWith("#") && v.length === 9) {
12956
13365
  return parseInt(v.slice(7, 9), 16) / 255;
12957
13366
  }
12958
- const rgbaMatch = v.match(/rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)/i);
13367
+ const rgbaMatch = v.match(
13368
+ /rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)/i,
13369
+ );
12959
13370
  if (rgbaMatch) return parseFloat(rgbaMatch[1]);
12960
13371
  return 1;
12961
13372
  }
@@ -13012,9 +13423,10 @@ class FigColorTip extends HTMLElement {
13012
13423
  }
13013
13424
 
13014
13425
  if (this.#fillPicker) {
13015
- const pickerVal = alpha < 1
13016
- ? { type: "solid", color, opacity: Math.round(alpha * 100) }
13017
- : { type: "solid", color };
13426
+ const pickerVal =
13427
+ alpha < 1
13428
+ ? { type: "solid", color, opacity: Math.round(alpha * 100) }
13429
+ : { type: "solid", color };
13018
13430
  this.#fillPicker.setAttribute("value", JSON.stringify(pickerVal));
13019
13431
  if (this.#alphaEnabled) {
13020
13432
  this.#fillPicker.removeAttribute("alpha");
@@ -13192,7 +13604,15 @@ class FigChooser extends HTMLElement {
13192
13604
  }
13193
13605
 
13194
13606
  static get observedAttributes() {
13195
- return ["value", "disabled", "choice-element", "drag", "overflow", "loop", "padding"];
13607
+ return [
13608
+ "value",
13609
+ "disabled",
13610
+ "choice-element",
13611
+ "drag",
13612
+ "overflow",
13613
+ "loop",
13614
+ "padding",
13615
+ ];
13196
13616
  }
13197
13617
 
13198
13618
  get #overflowMode() {
@@ -13818,7 +14238,13 @@ class FigCanvasControl extends HTMLElement {
13818
14238
 
13819
14239
  attributeChangedCallback(name, oldVal, newVal) {
13820
14240
  if (oldVal === newVal) return;
13821
- if (name === "value" && !this.#isDragging && !this.#isSecondDragging && !this.#isRadiusDragging && !this.#isAngleDragging) {
14241
+ if (
14242
+ name === "value" &&
14243
+ !this.#isDragging &&
14244
+ !this.#isSecondDragging &&
14245
+ !this.#isRadiusDragging &&
14246
+ !this.#isAngleDragging
14247
+ ) {
13822
14248
  this.#parseValue();
13823
14249
  if (this.#pointHandle) this.#syncPositions();
13824
14250
  else this.#render();
@@ -13839,11 +14265,14 @@ class FigCanvasControl extends HTMLElement {
13839
14265
  }
13840
14266
  if (name === "snapping" && this.#pointHandle) {
13841
14267
  this.#pointHandle.setAttribute("drag-snapping", newVal || "false");
13842
- if (this.#secondHandle) this.#secondHandle.setAttribute("drag-snapping", newVal || "false");
14268
+ if (this.#secondHandle)
14269
+ this.#secondHandle.setAttribute("drag-snapping", newVal || "false");
13843
14270
  }
13844
14271
  if (name === "name") {
13845
- if (this.#pointTooltip) this.#pointTooltip.setAttribute("text", this.#pointTipText);
13846
- if (this.#secondTooltip) this.#secondTooltip.setAttribute("text", this.#secondTipText);
14272
+ if (this.#pointTooltip)
14273
+ this.#pointTooltip.setAttribute("text", this.#pointTipText);
14274
+ if (this.#secondTooltip)
14275
+ this.#secondTooltip.setAttribute("text", this.#secondTipText);
13847
14276
  }
13848
14277
  }
13849
14278
 
@@ -13868,13 +14297,16 @@ class FigCanvasControl extends HTMLElement {
13868
14297
  if (typeof v.angle === "number") this.#angle = v.angle;
13869
14298
  if (typeof v.x2 === "number") this.#x2 = v.x2;
13870
14299
  if (typeof v.y2 === "number") this.#y2 = v.y2;
13871
- } catch { /* ignore */ }
14300
+ } catch {
14301
+ /* ignore */
14302
+ }
13872
14303
  }
13873
14304
 
13874
14305
  get value() {
13875
14306
  const v = { x: this.#x, y: this.#y };
13876
14307
  if (this.#type === "color") {
13877
- const color = this.getAttribute("color") || this.#pointHandle?.getAttribute("color");
14308
+ const color =
14309
+ this.getAttribute("color") || this.#pointHandle?.getAttribute("color");
13878
14310
  if (color) v.color = color;
13879
14311
  }
13880
14312
  if (this.#hasRadius) {
@@ -14175,11 +14607,15 @@ class FigCanvasControl extends HTMLElement {
14175
14607
  }
14176
14608
 
14177
14609
  #emitInput() {
14178
- this.dispatchEvent(new CustomEvent("input", { bubbles: true, detail: this.value }));
14610
+ this.dispatchEvent(
14611
+ new CustomEvent("input", { bubbles: true, detail: this.value }),
14612
+ );
14179
14613
  }
14180
14614
 
14181
14615
  #emitChange() {
14182
- this.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: this.value }));
14616
+ this.dispatchEvent(
14617
+ new CustomEvent("change", { bubbles: true, detail: this.value }),
14618
+ );
14183
14619
  }
14184
14620
 
14185
14621
  #syncValueAttribute() {
@@ -14211,7 +14647,9 @@ class FigCanvasControl extends HTMLElement {
14211
14647
  }
14212
14648
  this.#syncPositions();
14213
14649
  if (this.#hasSecondPoint) {
14214
- document.body.style.cursor = this.#resizeCursorSvg(this.#pointPointLineDeg());
14650
+ document.body.style.cursor = this.#resizeCursorSvg(
14651
+ this.#pointPointLineDeg(),
14652
+ );
14215
14653
  }
14216
14654
  this.#emitInput();
14217
14655
  });
@@ -14227,14 +14665,17 @@ class FigCanvasControl extends HTMLElement {
14227
14665
  const py = e.detail?.py ?? this.#y / 100;
14228
14666
  this.#x = Math.round(Math.max(0, Math.min(100, px * 100)));
14229
14667
  this.#y = Math.round(Math.max(0, Math.min(100, py * 100)));
14230
- if (this.#pointTooltip && this.#type !== "color") this.#pointTooltip.removeAttribute("show");
14668
+ if (this.#pointTooltip && this.#type !== "color")
14669
+ this.#pointTooltip.removeAttribute("show");
14231
14670
  if (this.#hasSecondPoint) {
14232
14671
  document.body.style.cursor = this.#prevBodyCursor ?? "";
14233
14672
  }
14234
14673
  this.#syncPositions();
14235
14674
  this.#syncValueAttribute();
14236
14675
  this.#emitChange();
14237
- requestAnimationFrame(() => { this.#isDragging = false; });
14676
+ requestAnimationFrame(() => {
14677
+ this.#isDragging = false;
14678
+ });
14238
14679
  });
14239
14680
 
14240
14681
  if (this.#angleHandle) {
@@ -14251,8 +14692,8 @@ class FigCanvasControl extends HTMLElement {
14251
14692
  const hy = e.detail?.y ?? 0;
14252
14693
  const hw = this.#angleHandle.offsetWidth / 2;
14253
14694
  const hh = this.#angleHandle.offsetHeight / 2;
14254
- const dx = (hx + hw) - cx;
14255
- const dy = (hy + hh) - cy;
14695
+ const dx = hx + hw - cx;
14696
+ const dy = hy + hh - cy;
14256
14697
  let angle = (Math.atan2(dy, dx) * 180) / Math.PI;
14257
14698
  if (this.#shouldSnap(e.detail?.shiftKey)) {
14258
14699
  angle = Math.round(angle / 15) * 15;
@@ -14277,7 +14718,10 @@ class FigCanvasControl extends HTMLElement {
14277
14718
  }
14278
14719
 
14279
14720
  if (this.#angleTooltip) {
14280
- this.#angleTooltip.setAttribute("text", `Angle ${Math.round(this.#angle)}°`);
14721
+ this.#angleTooltip.setAttribute(
14722
+ "text",
14723
+ `Angle ${Math.round(this.#angle)}°`,
14724
+ );
14281
14725
  this.#angleTooltip.setAttribute("show", "true");
14282
14726
  this.#angleTooltip.showPopup?.();
14283
14727
  }
@@ -14292,7 +14736,9 @@ class FigCanvasControl extends HTMLElement {
14292
14736
  this.#syncPositions();
14293
14737
  this.#syncValueAttribute();
14294
14738
  this.#emitChange();
14295
- requestAnimationFrame(() => { this.#isAngleDragging = false; });
14739
+ requestAnimationFrame(() => {
14740
+ this.#isAngleDragging = false;
14741
+ });
14296
14742
  });
14297
14743
 
14298
14744
  this.#angleHandle.addEventListener("hitareadown", (e) => {
@@ -14325,7 +14771,11 @@ class FigCanvasControl extends HTMLElement {
14325
14771
  angle = Math.round(angle / 15) * 15;
14326
14772
  }
14327
14773
  this.#angle = angle;
14328
- if (this.#angleTooltip) this.#angleTooltip.setAttribute("text", `Angle ${Math.round(angle)}°`);
14774
+ if (this.#angleTooltip)
14775
+ this.#angleTooltip.setAttribute(
14776
+ "text",
14777
+ `Angle ${Math.round(angle)}°`,
14778
+ );
14329
14779
  this.#syncPositions();
14330
14780
  const curDeg = Math.round(angle);
14331
14781
  if (curDeg !== lastCursorDeg) {
@@ -14368,7 +14818,9 @@ class FigCanvasControl extends HTMLElement {
14368
14818
  this.#secondTooltip.showPopup?.();
14369
14819
  }
14370
14820
  this.#syncPositions();
14371
- document.body.style.cursor = this.#resizeCursorSvg(this.#pointPointLineDeg());
14821
+ document.body.style.cursor = this.#resizeCursorSvg(
14822
+ this.#pointPointLineDeg(),
14823
+ );
14372
14824
  this.#emitInput();
14373
14825
  });
14374
14826
 
@@ -14379,7 +14831,9 @@ class FigCanvasControl extends HTMLElement {
14379
14831
  this.#syncPositions();
14380
14832
  this.#syncValueAttribute();
14381
14833
  this.#emitChange();
14382
- requestAnimationFrame(() => { this.#isSecondDragging = false; });
14834
+ requestAnimationFrame(() => {
14835
+ this.#isSecondDragging = false;
14836
+ });
14383
14837
  });
14384
14838
 
14385
14839
  this.#setupPointPointHitArea(this.#pointHandle, true);
@@ -14443,7 +14897,9 @@ class FigCanvasControl extends HTMLElement {
14443
14897
  this.#y2 = newPctY;
14444
14898
  }
14445
14899
  this.#syncPositions();
14446
- const curDeg = Math.round(isFirst ? this.#pointPointLineDeg() + 180 : this.#pointPointLineDeg());
14900
+ const curDeg = Math.round(
14901
+ isFirst ? this.#pointPointLineDeg() + 180 : this.#pointPointLineDeg(),
14902
+ );
14447
14903
  if (curDeg !== lastCursorDeg) {
14448
14904
  lastCursorDeg = curDeg;
14449
14905
  document.body.style.cursor = this.#rotateCursorSvg(curDeg);
@@ -14475,7 +14931,10 @@ class FigCanvasControl extends HTMLElement {
14475
14931
  const rect = container.getBoundingClientRect();
14476
14932
  const cx = (this.#x / 100) * rect.width;
14477
14933
  const cy = (this.#y / 100) * rect.height;
14478
- const deg = (Math.atan2(e.clientY - rect.top - cy, e.clientX - rect.left - cx) * 180) / Math.PI;
14934
+ const deg =
14935
+ (Math.atan2(e.clientY - rect.top - cy, e.clientX - rect.left - cx) *
14936
+ 180) /
14937
+ Math.PI;
14479
14938
  circle.style.cursor = this.#resizeCursorSvg(deg);
14480
14939
  });
14481
14940
  const onDown = (e) => {
@@ -14497,7 +14956,10 @@ class FigCanvasControl extends HTMLElement {
14497
14956
  const rect0 = container.getBoundingClientRect();
14498
14957
  const cx0 = (this.#x / 100) * rect0.width;
14499
14958
  const cy0 = (this.#y / 100) * rect0.height;
14500
- const initDeg = (Math.atan2(e.clientY - rect0.top - cy0, e.clientX - rect0.left - cx0) * 180) / Math.PI;
14959
+ const initDeg =
14960
+ (Math.atan2(e.clientY - rect0.top - cy0, e.clientX - rect0.left - cx0) *
14961
+ 180) /
14962
+ Math.PI;
14501
14963
  let lastCursorDeg = Math.round(initDeg);
14502
14964
  document.body.style.cursor = this.#resizeCursorSvg(lastCursorDeg);
14503
14965
 
@@ -14528,7 +14990,8 @@ class FigCanvasControl extends HTMLElement {
14528
14990
  } else {
14529
14991
  this.#radius = Math.max(0, dist);
14530
14992
  }
14531
- if (this.#radiusTooltip) this.#radiusTooltip.setAttribute("text", this.#formatRadius());
14993
+ if (this.#radiusTooltip)
14994
+ this.#radiusTooltip.setAttribute("text", this.#formatRadius());
14532
14995
  this.#syncPositions();
14533
14996
  this.#emitInput();
14534
14997
  };
@@ -14549,7 +15012,8 @@ class FigCanvasControl extends HTMLElement {
14549
15012
  window.addEventListener("pointerup", onUp);
14550
15013
  };
14551
15014
  circle.addEventListener("pointerdown", onDown);
14552
- this._radiusDragCleanup = () => circle.removeEventListener("pointerdown", onDown);
15015
+ this._radiusDragCleanup = () =>
15016
+ circle.removeEventListener("pointerdown", onDown);
14553
15017
  }
14554
15018
 
14555
15019
  #teardownRadiusDrag() {
@@ -14711,8 +15175,10 @@ class FigHandle extends HTMLElement {
14711
15175
  };
14712
15176
 
14713
15177
  const axes = this.#axes;
14714
- if (axes.x) this.style.left = `${Math.round(resolve(xToken, rect.width, hw))}px`;
14715
- if (axes.y) this.style.top = `${Math.round(resolve(yToken, rect.height, hh))}px`;
15178
+ if (axes.x)
15179
+ this.style.left = `${Math.round(resolve(xToken, rect.width, hw))}px`;
15180
+ if (axes.y)
15181
+ this.style.top = `${Math.round(resolve(yToken, rect.height, hh))}px`;
14716
15182
  }
14717
15183
 
14718
15184
  #syncValueAttribute() {
@@ -14729,16 +15195,25 @@ class FigHandle extends HTMLElement {
14729
15195
  const raw = this.getAttribute("hit-area");
14730
15196
  if (!raw) return null;
14731
15197
  const tokens = raw.trim().split(/\s+/);
14732
- let vPad = 0, hPad = 0, circle = false;
15198
+ let vPad = 0,
15199
+ hPad = 0,
15200
+ circle = false;
14733
15201
  const nums = [];
14734
15202
  for (const t of tokens) {
14735
- if (t === "circle") { circle = true; continue; }
15203
+ if (t === "circle") {
15204
+ circle = true;
15205
+ continue;
15206
+ }
14736
15207
  const n = parseFloat(t);
14737
15208
  if (Number.isFinite(n)) nums.push(n);
14738
15209
  }
14739
- if (nums.length >= 2) { vPad = nums[0]; hPad = nums[1]; }
14740
- else if (nums.length === 1) { vPad = nums[0]; hPad = nums[0]; }
14741
- else return null;
15210
+ if (nums.length >= 2) {
15211
+ vPad = nums[0];
15212
+ hPad = nums[1];
15213
+ } else if (nums.length === 1) {
15214
+ vPad = nums[0];
15215
+ hPad = nums[0];
15216
+ } else return null;
14742
15217
  return { vPad, hPad, circle };
14743
15218
  }
14744
15219
 
@@ -14759,7 +15234,10 @@ class FigHandle extends HTMLElement {
14759
15234
  this.prepend(el);
14760
15235
  this.#hitAreaEl = el;
14761
15236
  }
14762
- this.style.setProperty("--fig-handle-hit-area-size", String(parsed.hPad * 2));
15237
+ this.style.setProperty(
15238
+ "--fig-handle-hit-area-size",
15239
+ String(parsed.hPad * 2),
15240
+ );
14763
15241
  if (parsed.circle) {
14764
15242
  this.#hitAreaEl.style.borderRadius = "50%";
14765
15243
  } else {
@@ -14773,10 +15251,12 @@ class FigHandle extends HTMLElement {
14773
15251
  if (this.#hitAreaMode === "delegate") {
14774
15252
  e.preventDefault();
14775
15253
  e.stopPropagation();
14776
- this.dispatchEvent(new CustomEvent("hitareadown", {
14777
- bubbles: true,
14778
- detail: { originalEvent: e },
14779
- }));
15254
+ this.dispatchEvent(
15255
+ new CustomEvent("hitareadown", {
15256
+ bubbles: true,
15257
+ detail: { originalEvent: e },
15258
+ }),
15259
+ );
14780
15260
  } else {
14781
15261
  this.#onPointerDown(e);
14782
15262
  }
@@ -14796,7 +15276,10 @@ class FigHandle extends HTMLElement {
14796
15276
  disconnectedCallback() {
14797
15277
  this.#teardownDrag();
14798
15278
  this.#hideColorTip();
14799
- if (this.#hitAreaEl) { this.#hitAreaEl.remove(); this.#hitAreaEl = null; }
15279
+ if (this.#hitAreaEl) {
15280
+ this.#hitAreaEl.remove();
15281
+ this.#hitAreaEl = null;
15282
+ }
14800
15283
  this.removeEventListener("click", this.#handleSelect);
14801
15284
  document.removeEventListener("pointerdown", this.#handleDeselect);
14802
15285
  document.removeEventListener("keydown", this.#handleKeyDown);
@@ -14805,7 +15288,8 @@ class FigHandle extends HTMLElement {
14805
15288
  select() {
14806
15289
  if (this.hasAttribute("disabled")) return;
14807
15290
  this.setAttribute("selected", "");
14808
- if (this.getAttribute("type") === "color" && !this.#isDragging) this.#showColorTip();
15291
+ if (this.getAttribute("type") === "color" && !this.#isDragging)
15292
+ this.#showColorTip();
14809
15293
  }
14810
15294
 
14811
15295
  deselect() {
@@ -14908,8 +15392,8 @@ class FigHandle extends HTMLElement {
14908
15392
  lastRect = rect;
14909
15393
  const currentLeft = parseFloat(this.style.left) || 0;
14910
15394
  const currentTop = parseFloat(this.style.top) || 0;
14911
- const rawX = (clientX - offsetX) - rect.left - handleW / 2;
14912
- const rawY = (clientY - offsetY) - rect.top - handleH / 2;
15395
+ const rawX = clientX - offsetX - rect.left - handleW / 2;
15396
+ const rawY = clientY - offsetY - rect.top - handleH / 2;
14913
15397
 
14914
15398
  const clampedX = Math.max(
14915
15399
  -handleW / 2,
@@ -15055,16 +15539,32 @@ class FigHandle extends HTMLElement {
15055
15539
  #handleColorTipInput = (e) => {
15056
15540
  e.stopPropagation();
15057
15541
  if (e.detail?.color) {
15058
- this.setAttribute("color", this.#colorWithOpacity(e.detail.color, e.detail.opacity));
15059
- this.dispatchEvent(new CustomEvent("input", { bubbles: true, detail: { color: e.detail.color, opacity: e.detail.opacity } }));
15542
+ this.setAttribute(
15543
+ "color",
15544
+ this.#colorWithOpacity(e.detail.color, e.detail.opacity),
15545
+ );
15546
+ this.dispatchEvent(
15547
+ new CustomEvent("input", {
15548
+ bubbles: true,
15549
+ detail: { color: e.detail.color, opacity: e.detail.opacity },
15550
+ }),
15551
+ );
15060
15552
  }
15061
15553
  };
15062
15554
 
15063
15555
  #handleColorTipChange = (e) => {
15064
15556
  e.stopPropagation();
15065
15557
  if (e.detail?.color) {
15066
- this.setAttribute("color", this.#colorWithOpacity(e.detail.color, e.detail.opacity));
15067
- this.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: { color: e.detail.color, opacity: e.detail.opacity } }));
15558
+ this.setAttribute(
15559
+ "color",
15560
+ this.#colorWithOpacity(e.detail.color, e.detail.opacity),
15561
+ );
15562
+ this.dispatchEvent(
15563
+ new CustomEvent("change", {
15564
+ bubbles: true,
15565
+ detail: { color: e.detail.color, opacity: e.detail.opacity },
15566
+ }),
15567
+ );
15068
15568
  }
15069
15569
  };
15070
15570