@rogieking/figui3 3.21.0 → 3.21.2

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
@@ -5389,14 +5389,15 @@ class FigInputFill extends HTMLElement {
5389
5389
  ${opacityHtml(Math.round(this.#solid.alpha * 100))}`;
5390
5390
  break;
5391
5391
 
5392
- case "gradient":
5392
+ case "gradient": {
5393
5393
  const gradientLabel =
5394
5394
  this.#gradient.type.charAt(0).toUpperCase() +
5395
5395
  this.#gradient.type.slice(1);
5396
5396
  controlsHtml = `
5397
5397
  <label class="fig-input-fill-label">${gradientLabel}</label>
5398
- ${opacityHtml(this.#gradient.stops[0]?.opacity ?? 100)}`;
5398
+ ${opacityHtml(100)}`;
5399
5399
  break;
5400
+ }
5400
5401
 
5401
5402
  case "image":
5402
5403
  controlsHtml = `
@@ -5529,10 +5530,6 @@ class FigInputFill extends HTMLElement {
5529
5530
  this.#solid.alpha = alpha;
5530
5531
  break;
5531
5532
  case "gradient":
5532
- // Apply to all stops
5533
- this.#gradient.stops.forEach((stop) => {
5534
- stop.opacity = opacity;
5535
- });
5536
5533
  break;
5537
5534
  case "image":
5538
5535
  this.#image.opacity = alpha;
@@ -5575,12 +5572,6 @@ class FigInputFill extends HTMLElement {
5575
5572
  }
5576
5573
  break;
5577
5574
  case "gradient": {
5578
- if (this.#opacityInput) {
5579
- this.#opacityInput.setAttribute(
5580
- "value",
5581
- this.#gradient.stops[0]?.opacity ?? 100,
5582
- );
5583
- }
5584
5575
  const label = this.querySelector(".fig-input-fill-label");
5585
5576
  if (label) {
5586
5577
  const newLabel =
@@ -5657,7 +5648,7 @@ class FigInputFill extends HTMLElement {
5657
5648
  </fig-input-number>
5658
5649
  </fig-tooltip>`;
5659
5650
  break;
5660
- case "gradient":
5651
+ case "gradient": {
5661
5652
  const gradientLabel =
5662
5653
  this.#gradient.type.charAt(0).toUpperCase() +
5663
5654
  this.#gradient.type.slice(1);
@@ -5669,12 +5660,13 @@ class FigInputFill extends HTMLElement {
5669
5660
  placeholder="##"
5670
5661
  min="0"
5671
5662
  max="100"
5672
- value="${this.#gradient.stops[0]?.opacity ?? 100}"
5663
+ value="100"
5673
5664
  units="%"
5674
5665
  ${disabled ? "disabled" : ""}>
5675
5666
  </fig-input-number>
5676
5667
  </fig-tooltip>`;
5677
5668
  break;
5669
+ }
5678
5670
  case "image":
5679
5671
  controlsHtml = `
5680
5672
  <label class="fig-input-fill-label">Image</label>
@@ -5768,9 +5760,6 @@ class FigInputFill extends HTMLElement {
5768
5760
  this.#solid.alpha = alpha;
5769
5761
  break;
5770
5762
  case "gradient":
5771
- this.#gradient.stops.forEach((stop) => {
5772
- stop.opacity = opacity;
5773
- });
5774
5763
  break;
5775
5764
  case "image":
5776
5765
  this.#image.opacity = alpha;
@@ -6375,6 +6364,8 @@ class FigInputGradient extends HTMLElement {
6375
6364
  ...this.#gradient,
6376
6365
  ...parsed.gradient,
6377
6366
  });
6367
+ this.#gradient.type = "linear";
6368
+ this.#gradient.angle = 90;
6378
6369
  return;
6379
6370
  }
6380
6371
  if (parsed?.gradient) {
@@ -6382,6 +6373,8 @@ class FigInputGradient extends HTMLElement {
6382
6373
  ...this.#gradient,
6383
6374
  ...parsed.gradient,
6384
6375
  });
6376
+ this.#gradient.type = "linear";
6377
+ this.#gradient.angle = 90;
6385
6378
  }
6386
6379
  } catch (e) {
6387
6380
  // Ignore invalid JSON and keep current/default gradient.
@@ -6392,17 +6385,31 @@ class FigInputGradient extends HTMLElement {
6392
6385
  const sorted = [...this.#gradient.stops].sort(
6393
6386
  (a, b) => a.position - b.position,
6394
6387
  );
6395
- const stops = sorted.map((s) => `${s.color} ${s.position}%`).join(", ");
6388
+ const stops = sorted
6389
+ .map((s) => {
6390
+ const alpha = (s.opacity ?? 100) / 100;
6391
+ if (alpha >= 1) return `${s.color} ${s.position}%`;
6392
+ const { r, g, b } = figHexToRGB(s.color);
6393
+ return `rgba(${r}, ${g}, ${b}, ${alpha}) ${s.position}%`;
6394
+ })
6395
+ .join(", ");
6396
6396
  const interp = gradientInterpolationClause(this.#gradient);
6397
6397
  return `linear-gradient(${this.#gradient.angle}deg ${interp}, ${stops})`;
6398
6398
  }
6399
6399
 
6400
+ #stopColorCSS(stop) {
6401
+ const alpha = (stop.opacity ?? 100) / 100;
6402
+ if (alpha >= 1) return stop.color;
6403
+ const { r, g, b } = figHexToRGB(stop.color);
6404
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
6405
+ }
6406
+
6400
6407
  #buildStopHandles() {
6401
6408
  const disabled = this.hasAttribute("disabled");
6402
6409
  return this.#gradient.stops
6403
6410
  .map(
6404
6411
  (stop, i) =>
6405
- `<fig-tooltip action="manual" text="${Math.round(stop.position)}%"><fig-handle drag drag-axes="x" drag-surface=".fig-input-gradient-track" type="color" color="${stop.color}" value="${stop.position}% 50%" hit-area="4" data-stop-index="${i}"${disabled ? " disabled" : ""}></fig-handle></fig-tooltip>`,
6412
+ `<fig-tooltip action="manual" text="${Math.round(stop.position)}%"><fig-handle drag drag-axes="x" drag-surface=".fig-input-gradient-track" type="color" color="${this.#stopColorCSS(stop)}" value="${stop.position}% 50%" hit-area="4" data-stop-index="${i}"${disabled ? " disabled" : ""}></fig-handle></fig-tooltip>`,
6406
6413
  )
6407
6414
  .join("");
6408
6415
  }
@@ -6643,7 +6650,7 @@ class FigInputGradient extends HTMLElement {
6643
6650
  const stop = stops[i];
6644
6651
  h.dataset.stopIndex = i;
6645
6652
  h.setAttribute("value", `${stop.position}% 50%`);
6646
- h.setAttribute("color", stop.color);
6653
+ h.setAttribute("color", this.#stopColorCSS(stop));
6647
6654
  const tip = h.closest("fig-tooltip");
6648
6655
  if (tip) tip.setAttribute("text", `${Math.round(stop.position)}%`);
6649
6656
  }
@@ -6760,6 +6767,10 @@ class FigInputGradient extends HTMLElement {
6760
6767
  const idx = parseInt(handle.dataset.stopIndex, 10);
6761
6768
  if (!isNaN(idx) && this.#gradient.stops[idx]) {
6762
6769
  this.#gradient.stops[idx].color = e.detail.color;
6770
+ if (e.detail.opacity !== undefined) {
6771
+ this.#gradient.stops[idx].opacity = e.detail.opacity;
6772
+ }
6773
+ handle.setAttribute("color", this.#stopColorCSS(this.#gradient.stops[idx]));
6763
6774
  this.#syncChit();
6764
6775
  this.#emitInput();
6765
6776
  }
@@ -6811,6 +6822,10 @@ class FigInputGradient extends HTMLElement {
6811
6822
  const idx = parseInt(handle.dataset.stopIndex, 10);
6812
6823
  if (!isNaN(idx) && this.#gradient.stops[idx]) {
6813
6824
  this.#gradient.stops[idx].color = e.detail.color;
6825
+ if (e.detail.opacity !== undefined) {
6826
+ this.#gradient.stops[idx].opacity = e.detail.opacity;
6827
+ }
6828
+ handle.setAttribute("color", this.#stopColorCSS(this.#gradient.stops[idx]));
6814
6829
  this.#syncChit();
6815
6830
  this.#emitChange();
6816
6831
  }
@@ -6852,7 +6867,8 @@ class FigInputGradient extends HTMLElement {
6852
6867
  const idx = parseInt(handle.dataset.stopIndex, 10);
6853
6868
  if (isNaN(idx) || !this.#gradient.stops[idx]) continue;
6854
6869
  const newColor = handle.getAttribute("color");
6855
- if (newColor && newColor !== this.#gradient.stops[idx].color) {
6870
+ if (!newColor || !newColor.startsWith("#")) continue;
6871
+ if (newColor !== this.#gradient.stops[idx].color) {
6856
6872
  this.#gradient.stops[idx].color = newColor;
6857
6873
  this.#syncChit();
6858
6874
  this.#emitInput();
@@ -7578,8 +7594,8 @@ class FigChit extends HTMLElement {
7578
7594
  }
7579
7595
  }
7580
7596
 
7581
- // Always update CSS variable with raw value so vars stay reactive
7582
- this.style.setProperty("--chit-background", rawBg);
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})`);
7583
7599
  }
7584
7600
 
7585
7601
  #handleInput(e) {
@@ -7605,9 +7621,9 @@ class FigChit extends HTMLElement {
7605
7621
  attributeChangedCallback(name, oldValue, newValue) {
7606
7622
  if (oldValue === newValue) return;
7607
7623
  if (name === "background") {
7608
- // Skip full re-render if this was triggered by internal input
7609
7624
  if (this.#internalUpdate) {
7610
- this.style.setProperty("--chit-background", newValue);
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})`);
7611
7627
  return;
7612
7628
  }
7613
7629
  this.#render();
@@ -7629,6 +7645,10 @@ class FigChit extends HTMLElement {
7629
7645
  }
7630
7646
  }
7631
7647
  customElements.define("fig-chit", FigChit);
7648
+ class FigSwatch extends FigChit{
7649
+
7650
+ }
7651
+ customElements.define("fig-swatch", FigSwatch);
7632
7652
 
7633
7653
  /* Upload */
7634
7654
  /**
@@ -10899,7 +10919,6 @@ class FigFillPicker extends HTMLElement {
10899
10919
  this.#chit.style.setProperty("--chit-bg-size", bgSize);
10900
10920
  this.#chit.style.setProperty("--chit-bg-position", bgPosition);
10901
10921
 
10902
- // For solid colors, also update the alpha
10903
10922
  if (this.#fillType === "solid") {
10904
10923
  this.#chit.setAttribute("alpha", this.#color.a);
10905
10924
  } else {
@@ -12017,10 +12036,10 @@ class FigFillPicker extends HTMLElement {
12017
12036
  stopColor.addEventListener("input", (e) => {
12018
12037
  this.#gradient.stops[index].color =
12019
12038
  e.target.hexOpaque || e.target.value;
12020
- const parsedAlpha = parseFloat(e.target.alpha);
12021
- this.#gradient.stops[index].opacity = isNaN(parsedAlpha)
12022
- ? 100
12023
- : parsedAlpha;
12039
+ const a = e.detail?.rgba?.a;
12040
+ if (a !== undefined) {
12041
+ this.#gradient.stops[index].opacity = Math.round(a * 100);
12042
+ }
12024
12043
  this.#updateGradientPreview();
12025
12044
  this.#emitInput();
12026
12045
  });
@@ -12887,10 +12906,15 @@ class FigColorTip extends HTMLElement {
12887
12906
  }
12888
12907
  this.removeEventListener("click", this.#handleControlClick);
12889
12908
 
12890
- const color = this.#normalizeColor(this.getAttribute("value"));
12909
+ const rawValue = (this.getAttribute("value") || "").trim();
12910
+ const color = this.#normalizeColor(rawValue);
12911
+ const alpha = this.#extractAlpha(rawValue);
12891
12912
  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 });
12892
12916
  this.innerHTML = `
12893
- <fig-fill-picker mode="solid" ${alphaAttr} value='${JSON.stringify({ type: "solid", color })}'>
12917
+ <fig-fill-picker mode="solid" ${alphaAttr} value='${pickerValue}'>
12894
12918
  <fig-chit background="${color}"></fig-chit>
12895
12919
  </fig-fill-picker>`;
12896
12920
 
@@ -12925,6 +12949,17 @@ class FigColorTip extends HTMLElement {
12925
12949
  return "#D9D9D9";
12926
12950
  }
12927
12951
 
12952
+ #extractAlpha(colorValue) {
12953
+ if (!colorValue) return 1;
12954
+ const v = String(colorValue).trim();
12955
+ if (v.startsWith("#") && v.length === 9) {
12956
+ return parseInt(v.slice(7, 9), 16) / 255;
12957
+ }
12958
+ const rgbaMatch = v.match(/rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)/i);
12959
+ if (rgbaMatch) return parseFloat(rgbaMatch[1]);
12960
+ return 1;
12961
+ }
12962
+
12928
12963
  #normalizeColor(colorValue) {
12929
12964
  if (!colorValue) return "#D9D9D9";
12930
12965
  const value = String(colorValue).trim();
@@ -12968,17 +13003,19 @@ class FigColorTip extends HTMLElement {
12968
13003
  }
12969
13004
 
12970
13005
  #syncFromAttributes() {
12971
- const color = this.#normalizeColor(this.getAttribute("value"));
12972
- if (this.getAttribute("value") !== color) {
13006
+ const rawAttr = this.getAttribute("value");
13007
+ const color = this.#normalizeColor(rawAttr);
13008
+ const alpha = this.#extractAlpha(rawAttr);
13009
+ if (rawAttr !== color && alpha >= 1) {
12973
13010
  this.setAttribute("value", color);
12974
13011
  return;
12975
13012
  }
12976
13013
 
12977
13014
  if (this.#fillPicker) {
12978
- this.#fillPicker.setAttribute(
12979
- "value",
12980
- JSON.stringify({ type: "solid", color }),
12981
- );
13015
+ const pickerVal = alpha < 1
13016
+ ? { type: "solid", color, opacity: Math.round(alpha * 100) }
13017
+ : { type: "solid", color };
13018
+ this.#fillPicker.setAttribute("value", JSON.stringify(pickerVal));
12982
13019
  if (this.#alphaEnabled) {
12983
13020
  this.#fillPicker.removeAttribute("alpha");
12984
13021
  } else {
@@ -13011,8 +13048,12 @@ class FigColorTip extends HTMLElement {
13011
13048
  }
13012
13049
 
13013
13050
  const eventDetail = { color: this.value };
13014
- if (this.#alphaEnabled && detail?.opacity !== undefined) {
13015
- eventDetail.opacity = detail.opacity;
13051
+ if (this.#alphaEnabled) {
13052
+ if (detail?.opacity !== undefined) {
13053
+ eventDetail.opacity = detail.opacity;
13054
+ } else if (detail?.alpha !== undefined) {
13055
+ eventDetail.opacity = Math.round(detail.alpha * 100);
13056
+ }
13016
13057
  }
13017
13058
 
13018
13059
  this.dispatchEvent(
@@ -15005,19 +15046,25 @@ class FigHandle extends HTMLElement {
15005
15046
  this.#colorTip = null;
15006
15047
  }
15007
15048
 
15049
+ #colorWithOpacity(hex, opacity) {
15050
+ if (opacity === undefined || opacity >= 100) return hex;
15051
+ const { r, g, b } = figHexToRGB(hex);
15052
+ return `rgba(${r}, ${g}, ${b}, ${opacity / 100})`;
15053
+ }
15054
+
15008
15055
  #handleColorTipInput = (e) => {
15009
15056
  e.stopPropagation();
15010
15057
  if (e.detail?.color) {
15011
- this.setAttribute("color", e.detail.color);
15012
- this.dispatchEvent(new CustomEvent("input", { bubbles: true, detail: { color: 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 } }));
15013
15060
  }
15014
15061
  };
15015
15062
 
15016
15063
  #handleColorTipChange = (e) => {
15017
15064
  e.stopPropagation();
15018
15065
  if (e.detail?.color) {
15019
- this.setAttribute("color", e.detail.color);
15020
- this.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: { color: 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 } }));
15021
15068
  }
15022
15069
  };
15023
15070
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "3.21.0",
3
+ "version": "3.21.2",
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",