@rogieking/figui3 3.20.3 → 3.21.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 +45 -14
  2. package/fig.js +282 -33
  3. package/package.json +1 -1
package/components.css CHANGED
@@ -3146,6 +3146,7 @@ fig-input-fill {
3146
3146
  }
3147
3147
  fig-input-gradient {
3148
3148
  --height: 1.5rem;
3149
+ --chit-height: var(--height);
3149
3150
  position: relative;
3150
3151
  height: var(--height);
3151
3152
  width: 100%;
@@ -3157,12 +3158,34 @@ fig-input-gradient {
3157
3158
  outline: 1px solid var(--figma-color-border-selected) !important;
3158
3159
  outline-offset: -1px !important;
3159
3160
  }
3160
- & > fig-chit {
3161
+
3162
+ &[edit="false"] {
3163
+ pointer-events: none;
3164
+
3165
+ &:focus,
3166
+ &:active,
3167
+ &:focus-within {
3168
+ outline: none !important;
3169
+ }
3170
+ }
3171
+
3172
+ &[edit="picker"] {
3173
+ & > fig-fill-picker {
3174
+ display: contents;
3175
+ }
3176
+ }
3177
+ & > fig-chit,
3178
+ & > fig-fill-picker > fig-chit {
3161
3179
  --padding: 0;
3162
3180
  --width: 100%;
3181
+ --height: var(--chit-height);
3163
3182
  flex: 1 1 auto;
3164
3183
  width: 100% !important;
3165
3184
  min-width: 0 !important;
3185
+ min-height: var(--chit-height) !important;
3186
+ }
3187
+ &[size="large"] {
3188
+ --height: 3rem;
3166
3189
  }
3167
3190
 
3168
3191
  .fig-input-gradient-track {
@@ -3194,7 +3217,7 @@ fig-input-palette {
3194
3217
  display: flex;
3195
3218
  flex-wrap: nowrap;
3196
3219
  gap: 0;
3197
- border-radius: var(--radius-medium);
3220
+ border-radius: var(--radius-small);
3198
3221
  overflow: hidden;
3199
3222
  grid-area: inputs;
3200
3223
  min-width: 0;
@@ -3211,9 +3234,15 @@ fig-input-palette {
3211
3234
  border-radius: var(--radius-medium);
3212
3235
  background-color: var(--figma-color-bg-secondary);
3213
3236
  width: 100%;
3237
+ gap: var(--spacer-1);
3238
+ place-items: center;
3214
3239
 
3215
3240
  .palette-colors {
3216
3241
  display: flex;
3242
+ --palette-colors-height: calc(1.5rem - var(--spacer-1));
3243
+ height: var(--palette-colors-height);
3244
+ margin-left: var(--spacer-1);
3245
+
3217
3246
  background-color: var(--figma-color-bg-secondary);
3218
3247
  fig-input-color {
3219
3248
  width: 100%;
@@ -3223,10 +3252,11 @@ fig-input-palette {
3223
3252
  --padding: 0px;
3224
3253
  --border-radius: 0px;
3225
3254
  --width: 100%;
3255
+ --height: var(--palette-colors-height);
3256
+ --size: var(--palette-colors-height);
3226
3257
  flex: 1;
3227
3258
  min-width: 0;
3228
3259
  width: 100% !important;
3229
- height: var(--size);
3230
3260
  border-radius: 0 !important;
3231
3261
  input,
3232
3262
  &::before,
@@ -3248,6 +3278,11 @@ fig-input-palette {
3248
3278
  grid-template-areas: "inputs inputs";
3249
3279
  }
3250
3280
  }
3281
+ &[color-strip="false"] {
3282
+ .palette-colors-inline {
3283
+ display: none !important;
3284
+ }
3285
+ }
3251
3286
  .palette-colors-expanded {
3252
3287
  display: none;
3253
3288
  flex-direction: column;
@@ -4375,17 +4410,13 @@ fig-preview {
4375
4410
  }
4376
4411
  }
4377
4412
 
4378
- .fig-fill-picker-gradient-bar {
4379
- background: linear-gradient(90deg, #d9d9d9 0%, #737373 100%);
4380
- }
4381
-
4382
- .fig-fill-picker-gradient-stops-handles {
4383
- position: absolute;
4384
- top: 0;
4385
- left: 0;
4386
- right: 0;
4387
- bottom: 0;
4388
- pointer-events: none;
4413
+ .fig-fill-picker-gradient-preview {
4414
+ display: block;
4415
+ .fig-fill-picker-gradient-bar-input {
4416
+ --width: 100%;
4417
+ --height: 100%;
4418
+ aspect-ratio: 1/1;
4419
+ }
4389
4420
  }
4390
4421
 
4391
4422
  .fig-fill-picker-gradient-stops {
package/fig.js CHANGED
@@ -5053,6 +5053,136 @@ function gradientInterpolationClause(gradient) {
5053
5053
  return `in ${normalized.interpolationSpace}`;
5054
5054
  }
5055
5055
 
5056
+ function figHexToRGB(hex) {
5057
+ const h = hex.replace(/^#/, "");
5058
+ return {
5059
+ r: parseInt(h.substring(0, 2), 16),
5060
+ g: parseInt(h.substring(2, 4), 16),
5061
+ b: parseInt(h.substring(4, 6), 16),
5062
+ };
5063
+ }
5064
+
5065
+ function figRGBToLinear(c) {
5066
+ const s = c / 255;
5067
+ return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
5068
+ }
5069
+
5070
+ function figLinearToSRGB(c) {
5071
+ const v = c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
5072
+ return Math.round(Math.max(0, Math.min(1, v)) * 255);
5073
+ }
5074
+
5075
+ function figRGBToOklab(r, g, b) {
5076
+ const lr = figRGBToLinear(r);
5077
+ const lg = figRGBToLinear(g);
5078
+ 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);
5082
+ return {
5083
+ l: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
5084
+ a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
5085
+ b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_,
5086
+ };
5087
+ }
5088
+
5089
+ function figOklabToRGB(L, a, b) {
5090
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
5091
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
5092
+ const s_ = L - 0.0894841775 * a - 1.291485548 * b;
5093
+ const l = l_ * l_ * l_;
5094
+ const m = m_ * m_ * m_;
5095
+ const s = s_ * s_ * s_;
5096
+ return {
5097
+ r: figLinearToSRGB(+4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
5098
+ g: figLinearToSRGB(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
5099
+ b: figLinearToSRGB(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),
5100
+ };
5101
+ }
5102
+
5103
+ function figOklabToOklch(L, a, b) {
5104
+ return { l: L, c: Math.sqrt(a * a + b * b), h: (Math.atan2(b, a) * 180) / Math.PI };
5105
+ }
5106
+
5107
+ function figOklchToOklab(l, c, h) {
5108
+ const hRad = (h * Math.PI) / 180;
5109
+ return { l, a: c * Math.cos(hRad), b: c * Math.sin(hRad) };
5110
+ }
5111
+
5112
+ function figInterpolateHue(h1, h2, t, mode) {
5113
+ let a = ((h1 % 360) + 360) % 360;
5114
+ let b = ((h2 % 360) + 360) % 360;
5115
+ let diff = b - a;
5116
+ switch (mode) {
5117
+ case "longer":
5118
+ if (diff > 0 && diff < 180) diff -= 360;
5119
+ else if (diff < 0 && diff > -180) diff += 360;
5120
+ else if (diff === 0) diff = 0;
5121
+ break;
5122
+ case "increasing":
5123
+ if (diff < 0) diff += 360;
5124
+ break;
5125
+ case "decreasing":
5126
+ if (diff > 0) diff -= 360;
5127
+ break;
5128
+ default:
5129
+ if (diff > 180) diff -= 360;
5130
+ else if (diff < -180) diff += 360;
5131
+ break;
5132
+ }
5133
+ return ((a + diff * t) % 360 + 360) % 360;
5134
+ }
5135
+
5136
+ function figSampleGradientAt(stops, position, interpolationSpace, hueInterpolation) {
5137
+ const sorted = [...stops].sort((a, b) => a.position - b.position);
5138
+ const pos = position * 100;
5139
+ if (sorted.length === 0) return "#888888";
5140
+ if (pos <= sorted[0].position) return sorted[0].color;
5141
+ if (pos >= sorted[sorted.length - 1].position) return sorted[sorted.length - 1].color;
5142
+
5143
+ let i = 0;
5144
+ while (i < sorted.length - 1 && sorted[i + 1].position < pos) i++;
5145
+ const s1 = sorted[i];
5146
+ const s2 = sorted[i + 1];
5147
+ const range = s2.position - s1.position;
5148
+ const t = range > 0 ? (pos - s1.position) / range : 0;
5149
+
5150
+ const c1 = figHexToRGB(s1.color);
5151
+ const c2 = figHexToRGB(s2.color);
5152
+
5153
+ let r, g, b;
5154
+ const space = interpolationSpace || "oklab";
5155
+
5156
+ 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);
5159
+ r = figLinearToSRGB(lr1 + (lr2 - lr1) * t);
5160
+ g = figLinearToSRGB(lg1 + (lg2 - lg1) * t);
5161
+ b = figLinearToSRGB(lb1 + (lb2 - lb1) * t);
5162
+ } else if (space === "oklch") {
5163
+ const lab1 = figRGBToOklab(c1.r, c1.g, c1.b);
5164
+ const lab2 = figRGBToOklab(c2.r, c2.g, c2.b);
5165
+ const lch1 = figOklabToOklch(lab1.l, lab1.a, lab1.b);
5166
+ const lch2 = figOklabToOklch(lab2.l, lab2.a, lab2.b);
5167
+ const L = lch1.l + (lch2.l - lch1.l) * t;
5168
+ const C = lch1.c + (lch2.c - lch1.c) * t;
5169
+ const H = figInterpolateHue(lch1.h, lch2.h, t, hueInterpolation || "shorter");
5170
+ const lab = figOklchToOklab(L, C, H);
5171
+ const rgb = figOklabToRGB(lab.l, lab.a, lab.b);
5172
+ r = rgb.r; g = rgb.g; b = rgb.b;
5173
+ } else {
5174
+ const lab1 = figRGBToOklab(c1.r, c1.g, c1.b);
5175
+ const lab2 = figRGBToOklab(c2.r, c2.g, c2.b);
5176
+ const L = lab1.l + (lab2.l - lab1.l) * t;
5177
+ const a = lab1.a + (lab2.a - lab1.a) * t;
5178
+ const bv = lab1.b + (lab2.b - lab1.b) * t;
5179
+ const rgb = figOklabToRGB(L, a, bv);
5180
+ r = rgb.r; g = rgb.g; b = rgb.b;
5181
+ }
5182
+
5183
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`.toUpperCase();
5184
+ }
5185
+
5056
5186
  function hslToP3(h, s, l) {
5057
5187
  const sRGB = hslToSRGB(h, s, l);
5058
5188
  return sRGB.map((c) => +(c / 255).toFixed(4));
@@ -6124,13 +6254,24 @@ class FigInputGradient extends HTMLElement {
6124
6254
  }
6125
6255
 
6126
6256
  static get observedAttributes() {
6127
- return ["value", "disabled"];
6257
+ return ["value", "disabled", "edit"];
6258
+ }
6259
+
6260
+ get #editMode() {
6261
+ const attr = this.getAttribute("edit");
6262
+ if (attr === "false") return "false";
6263
+ if (attr === "picker") return "picker";
6264
+ return "true";
6265
+ }
6266
+
6267
+ get #isEditable() {
6268
+ return this.#editMode === "true";
6128
6269
  }
6129
6270
 
6130
6271
  connectedCallback() {
6131
6272
  this.#parseValue();
6132
6273
  this.#render();
6133
- document.addEventListener("keydown", this.#onKeyDown);
6274
+ if (this.#isEditable) document.addEventListener("keydown", this.#onKeyDown);
6134
6275
  }
6135
6276
 
6136
6277
  disconnectedCallback() {
@@ -6252,7 +6393,8 @@ class FigInputGradient extends HTMLElement {
6252
6393
  (a, b) => a.position - b.position,
6253
6394
  );
6254
6395
  const stops = sorted.map((s) => `${s.color} ${s.position}%`).join(", ");
6255
- return `linear-gradient(${this.#gradient.angle}deg, ${stops})`;
6396
+ const interp = gradientInterpolationClause(this.#gradient);
6397
+ return `linear-gradient(${this.#gradient.angle}deg ${interp}, ${stops})`;
6256
6398
  }
6257
6399
 
6258
6400
  #buildStopHandles() {
@@ -6270,31 +6412,69 @@ class FigInputGradient extends HTMLElement {
6270
6412
 
6271
6413
  #render() {
6272
6414
  const disabled = this.hasAttribute("disabled");
6415
+ const mode = this.#editMode;
6416
+
6417
+ if (mode === "picker") {
6418
+ const experimental = this.getAttribute("experimental");
6419
+ const expAttr = experimental ? ` experimental="${experimental}"` : "";
6420
+ const gradientValue = JSON.stringify(this.value);
6421
+ this.innerHTML = `
6422
+ <fig-fill-picker mode="gradient"${expAttr} value='${gradientValue}'${disabled ? " disabled" : ""}>
6423
+ <fig-chit size="medium" background="${this.#buildGradientCSS()}"${disabled ? " disabled" : ""}></fig-chit>
6424
+ </fig-fill-picker>`;
6425
+ this.#chit = this.querySelector("fig-chit");
6426
+ this.#track = null;
6427
+ this.#setupPickerEvents();
6428
+ return;
6429
+ }
6430
+
6273
6431
  this.innerHTML = `
6274
6432
  <fig-chit size="medium" background="${this.#buildGradientCSS()}"${disabled ? " disabled" : ""}></fig-chit>
6275
- <div class="fig-input-gradient-track">${this.#buildStopHandles()}</div>`;
6433
+ ${mode === "true" ? `<div class="fig-input-gradient-track">${this.#buildStopHandles()}</div>` : ""}`;
6276
6434
  this.#chit = this.querySelector("fig-chit");
6277
6435
  this.#track = this.querySelector(".fig-input-gradient-track");
6278
- this.#setupGhostHandle();
6279
- this.#setupEventListeners();
6436
+
6437
+ if (mode === "true") {
6438
+ this.#setupGhostHandle();
6439
+ this.#setupEventListeners();
6440
+ requestAnimationFrame(() => this.#repositionHandles());
6441
+ }
6442
+ }
6443
+
6444
+ #setupPickerEvents() {
6445
+ const picker = this.querySelector("fig-fill-picker");
6446
+ if (!picker) return;
6447
+ picker.anchorElement = this;
6448
+
6449
+ const syncFromPicker = (e) => {
6450
+ e.stopPropagation();
6451
+ const detail = e.detail;
6452
+ if (!detail?.gradient) return;
6453
+ this.#gradient = normalizeGradientConfig({
6454
+ ...this.#gradient,
6455
+ ...detail.gradient,
6456
+ });
6457
+ this.#syncChit();
6458
+ };
6459
+
6460
+ picker.addEventListener("input", (e) => {
6461
+ syncFromPicker(e);
6462
+ this.#emitInput();
6463
+ });
6464
+
6465
+ picker.addEventListener("change", (e) => {
6466
+ syncFromPicker(e);
6467
+ this.#emitChange();
6468
+ });
6280
6469
  }
6281
6470
 
6282
6471
  #sampleGradientColor(position) {
6283
- const { ctx } = figGetSharedCanvas(256, 1);
6284
- ctx.clearRect(0, 0, 256, 1);
6285
- const grad = ctx.createLinearGradient(0, 0, 256, 0);
6286
- for (const stop of this.#gradient.stops) {
6287
- try {
6288
- grad.addColorStop(stop.position / 100, stop.color);
6289
- } catch {
6290
- /* skip invalid */
6291
- }
6292
- }
6293
- ctx.fillStyle = grad;
6294
- ctx.fillRect(0, 0, 256, 1);
6295
- const px = Math.round(Math.max(0, Math.min(255, position * 255)));
6296
- const [r, g, b] = ctx.getImageData(px, 0, 1, 1).data;
6297
- return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`.toUpperCase();
6472
+ return figSampleGradientAt(
6473
+ this.#gradient.stops,
6474
+ position,
6475
+ this.#gradient.interpolationSpace,
6476
+ this.#gradient.hueInterpolation,
6477
+ );
6298
6478
  }
6299
6479
 
6300
6480
  #setupGhostHandle() {
@@ -6430,6 +6610,18 @@ class FigInputGradient extends HTMLElement {
6430
6610
  });
6431
6611
  };
6432
6612
 
6613
+ #repositionHandles() {
6614
+ if (!this.#track) return;
6615
+ const stops = this.#gradient.stops;
6616
+ this.#track
6617
+ .querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)")
6618
+ .forEach((h, i) => {
6619
+ if (i >= stops.length) return;
6620
+ h.removeAttribute("value");
6621
+ h.setAttribute("value", `${stops[i].position}% 50%`);
6622
+ });
6623
+ }
6624
+
6433
6625
  #syncHandles() {
6434
6626
  if (!this.#track) return;
6435
6627
  const handles = this.#track.querySelectorAll(
@@ -6442,6 +6634,7 @@ class FigInputGradient extends HTMLElement {
6442
6634
  this.#track.innerHTML = this.#buildStopHandles();
6443
6635
  if (ghost) this.#track.appendChild(ghost);
6444
6636
  this.#reobserveHandleColors();
6637
+ requestAnimationFrame(() => this.#repositionHandles());
6445
6638
  return;
6446
6639
  }
6447
6640
 
@@ -6720,6 +6913,14 @@ class FigInputGradient extends HTMLElement {
6720
6913
  case "disabled":
6721
6914
  this.#syncDisabled();
6722
6915
  break;
6916
+ case "edit":
6917
+ this.#render();
6918
+ if (this.#isEditable) {
6919
+ document.addEventListener("keydown", this.#onKeyDown);
6920
+ } else {
6921
+ document.removeEventListener("keydown", this.#onKeyDown);
6922
+ }
6923
+ break;
6723
6924
  }
6724
6925
  }
6725
6926
 
@@ -11522,8 +11723,7 @@ class FigFillPicker extends HTMLElement {
11522
11723
  </fig-tooltip>
11523
11724
  </fig-field>
11524
11725
  <fig-preview class="fig-fill-picker-gradient-preview">
11525
- <div class="fig-fill-picker-gradient-bar"></div>
11526
- <div class="fig-fill-picker-gradient-stops-handles"></div>
11726
+ <fig-input-gradient class="fig-fill-picker-gradient-bar-input" edit="true" size="large" value='${JSON.stringify({ type: "gradient", gradient: gradientToValueShape(this.#gradient) })}'></fig-input-gradient>
11527
11727
  </fig-preview>
11528
11728
  <fig-field class="fig-fill-picker-gradient-interpolation" direction="horizontal">
11529
11729
  <label>Mixing</label>
@@ -11654,6 +11854,30 @@ class FigFillPicker extends HTMLElement {
11654
11854
  this.#updateGradientUI();
11655
11855
  this.#emitInput();
11656
11856
  });
11857
+
11858
+ // Embedded gradient bar input
11859
+ const gradientBarInput = container.querySelector(".fig-fill-picker-gradient-bar-input");
11860
+ if (gradientBarInput) {
11861
+ const syncFromBarInput = (e) => {
11862
+ e.stopPropagation();
11863
+ const detail = e.detail;
11864
+ if (!detail?.gradient) return;
11865
+ this.#gradient = normalizeGradientConfig({
11866
+ ...this.#gradient,
11867
+ ...detail.gradient,
11868
+ });
11869
+ this.#updateChit();
11870
+ this.#updateGradientStopsList();
11871
+ };
11872
+ gradientBarInput.addEventListener("input", (e) => {
11873
+ syncFromBarInput(e);
11874
+ this.#emitInput();
11875
+ });
11876
+ gradientBarInput.addEventListener("change", (e) => {
11877
+ syncFromBarInput(e);
11878
+ this.#emitChange();
11879
+ });
11880
+ }
11657
11881
  }
11658
11882
 
11659
11883
  #updateGradientUI() {
@@ -11699,14 +11923,14 @@ class FigFillPicker extends HTMLElement {
11699
11923
  #updateGradientPreview() {
11700
11924
  if (!this.#dialog) return;
11701
11925
 
11702
- const preview = this.#dialog.querySelector(
11703
- ".fig-fill-picker-gradient-preview",
11926
+ const barInput = this.#dialog.querySelector(
11927
+ ".fig-fill-picker-gradient-bar-input",
11704
11928
  );
11705
- const bar = this.#dialog.querySelector(".fig-fill-picker-gradient-bar");
11706
- if (preview || bar) {
11707
- const css = this.#getGradientCSS();
11708
- if (bar) bar.style.background = css;
11709
- if (preview) preview.style.background = css;
11929
+ if (barInput) {
11930
+ barInput.setAttribute(
11931
+ "value",
11932
+ JSON.stringify({ type: "gradient", gradient: gradientToValueShape(this.#gradient) }),
11933
+ );
11710
11934
  }
11711
11935
 
11712
11936
  this.#updateChit();
@@ -11720,6 +11944,31 @@ class FigFillPicker extends HTMLElement {
11720
11944
  );
11721
11945
  if (!list) return;
11722
11946
 
11947
+ const existingRows = list.querySelectorAll(
11948
+ ".fig-fill-picker-gradient-stop-row",
11949
+ );
11950
+
11951
+ if (existingRows.length === this.#gradient.stops.length) {
11952
+ this.#gradient.stops.forEach((stop, index) => {
11953
+ const row = existingRows[index];
11954
+ row.dataset.index = index;
11955
+ const posInput = row.querySelector(".fig-fill-picker-stop-position");
11956
+ if (posInput) posInput.setAttribute("value", stop.position);
11957
+ const colorInput = row.querySelector(".fig-fill-picker-stop-color");
11958
+ if (colorInput) colorInput.setAttribute("value", stop.color);
11959
+ const removeBtn = row.querySelector(".fig-fill-picker-stop-remove");
11960
+ if (removeBtn) {
11961
+ if (this.#gradient.stops.length <= 2) removeBtn.setAttribute("disabled", "");
11962
+ else removeBtn.removeAttribute("disabled");
11963
+ }
11964
+ });
11965
+ return;
11966
+ }
11967
+
11968
+ this.#rebuildGradientStopsList(list);
11969
+ }
11970
+
11971
+ #rebuildGradientStopsList(list) {
11723
11972
  list.innerHTML = this.#gradient.stops
11724
11973
  .map(
11725
11974
  (stop, index) => `
@@ -11740,7 +11989,6 @@ class FigFillPicker extends HTMLElement {
11740
11989
  )
11741
11990
  .join("");
11742
11991
 
11743
- // Setup event listeners for each stop
11744
11992
  list
11745
11993
  .querySelectorAll(".fig-fill-picker-gradient-stop-row")
11746
11994
  .forEach((row) => {
@@ -11798,9 +12046,10 @@ class FigFillPicker extends HTMLElement {
11798
12046
  const isP3 = this.#gamut === "display-p3";
11799
12047
  const stops = gradient.stops
11800
12048
  .map((s) => {
12049
+ const alpha = (s.opacity ?? 100) / 100;
11801
12050
  const color = isP3
11802
- ? this.#hexToP3(s.color, s.opacity / 100)
11803
- : this.#hexToRGBA(s.color, s.opacity / 100);
12051
+ ? this.#hexToP3(s.color, alpha)
12052
+ : this.#hexToRGBA(s.color, alpha);
11804
12053
  return `${color} ${s.position}%`;
11805
12054
  })
11806
12055
  .join(", ");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "3.20.3",
3
+ "version": "3.21.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",