@rogieking/figui3 3.20.3 → 3.21.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.
- package/components.css +57 -18
- package/dist/components.css +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +63 -61
- package/fig.js +343 -69
- package/package.json +1 -1
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));
|
|
@@ -5259,14 +5389,15 @@ class FigInputFill extends HTMLElement {
|
|
|
5259
5389
|
${opacityHtml(Math.round(this.#solid.alpha * 100))}`;
|
|
5260
5390
|
break;
|
|
5261
5391
|
|
|
5262
|
-
case "gradient":
|
|
5392
|
+
case "gradient": {
|
|
5263
5393
|
const gradientLabel =
|
|
5264
5394
|
this.#gradient.type.charAt(0).toUpperCase() +
|
|
5265
5395
|
this.#gradient.type.slice(1);
|
|
5266
5396
|
controlsHtml = `
|
|
5267
5397
|
<label class="fig-input-fill-label">${gradientLabel}</label>
|
|
5268
|
-
${opacityHtml(
|
|
5398
|
+
${opacityHtml(100)}`;
|
|
5269
5399
|
break;
|
|
5400
|
+
}
|
|
5270
5401
|
|
|
5271
5402
|
case "image":
|
|
5272
5403
|
controlsHtml = `
|
|
@@ -5399,10 +5530,6 @@ class FigInputFill extends HTMLElement {
|
|
|
5399
5530
|
this.#solid.alpha = alpha;
|
|
5400
5531
|
break;
|
|
5401
5532
|
case "gradient":
|
|
5402
|
-
// Apply to all stops
|
|
5403
|
-
this.#gradient.stops.forEach((stop) => {
|
|
5404
|
-
stop.opacity = opacity;
|
|
5405
|
-
});
|
|
5406
5533
|
break;
|
|
5407
5534
|
case "image":
|
|
5408
5535
|
this.#image.opacity = alpha;
|
|
@@ -5445,12 +5572,6 @@ class FigInputFill extends HTMLElement {
|
|
|
5445
5572
|
}
|
|
5446
5573
|
break;
|
|
5447
5574
|
case "gradient": {
|
|
5448
|
-
if (this.#opacityInput) {
|
|
5449
|
-
this.#opacityInput.setAttribute(
|
|
5450
|
-
"value",
|
|
5451
|
-
this.#gradient.stops[0]?.opacity ?? 100,
|
|
5452
|
-
);
|
|
5453
|
-
}
|
|
5454
5575
|
const label = this.querySelector(".fig-input-fill-label");
|
|
5455
5576
|
if (label) {
|
|
5456
5577
|
const newLabel =
|
|
@@ -5527,7 +5648,7 @@ class FigInputFill extends HTMLElement {
|
|
|
5527
5648
|
</fig-input-number>
|
|
5528
5649
|
</fig-tooltip>`;
|
|
5529
5650
|
break;
|
|
5530
|
-
case "gradient":
|
|
5651
|
+
case "gradient": {
|
|
5531
5652
|
const gradientLabel =
|
|
5532
5653
|
this.#gradient.type.charAt(0).toUpperCase() +
|
|
5533
5654
|
this.#gradient.type.slice(1);
|
|
@@ -5539,12 +5660,13 @@ class FigInputFill extends HTMLElement {
|
|
|
5539
5660
|
placeholder="##"
|
|
5540
5661
|
min="0"
|
|
5541
5662
|
max="100"
|
|
5542
|
-
value="
|
|
5663
|
+
value="100"
|
|
5543
5664
|
units="%"
|
|
5544
5665
|
${disabled ? "disabled" : ""}>
|
|
5545
5666
|
</fig-input-number>
|
|
5546
5667
|
</fig-tooltip>`;
|
|
5547
5668
|
break;
|
|
5669
|
+
}
|
|
5548
5670
|
case "image":
|
|
5549
5671
|
controlsHtml = `
|
|
5550
5672
|
<label class="fig-input-fill-label">Image</label>
|
|
@@ -5638,9 +5760,6 @@ class FigInputFill extends HTMLElement {
|
|
|
5638
5760
|
this.#solid.alpha = alpha;
|
|
5639
5761
|
break;
|
|
5640
5762
|
case "gradient":
|
|
5641
|
-
this.#gradient.stops.forEach((stop) => {
|
|
5642
|
-
stop.opacity = opacity;
|
|
5643
|
-
});
|
|
5644
5763
|
break;
|
|
5645
5764
|
case "image":
|
|
5646
5765
|
this.#image.opacity = alpha;
|
|
@@ -6124,13 +6243,24 @@ class FigInputGradient extends HTMLElement {
|
|
|
6124
6243
|
}
|
|
6125
6244
|
|
|
6126
6245
|
static get observedAttributes() {
|
|
6127
|
-
return ["value", "disabled"];
|
|
6246
|
+
return ["value", "disabled", "edit"];
|
|
6247
|
+
}
|
|
6248
|
+
|
|
6249
|
+
get #editMode() {
|
|
6250
|
+
const attr = this.getAttribute("edit");
|
|
6251
|
+
if (attr === "false") return "false";
|
|
6252
|
+
if (attr === "picker") return "picker";
|
|
6253
|
+
return "true";
|
|
6254
|
+
}
|
|
6255
|
+
|
|
6256
|
+
get #isEditable() {
|
|
6257
|
+
return this.#editMode === "true";
|
|
6128
6258
|
}
|
|
6129
6259
|
|
|
6130
6260
|
connectedCallback() {
|
|
6131
6261
|
this.#parseValue();
|
|
6132
6262
|
this.#render();
|
|
6133
|
-
document.addEventListener("keydown", this.#onKeyDown);
|
|
6263
|
+
if (this.#isEditable) document.addEventListener("keydown", this.#onKeyDown);
|
|
6134
6264
|
}
|
|
6135
6265
|
|
|
6136
6266
|
disconnectedCallback() {
|
|
@@ -6251,8 +6381,23 @@ class FigInputGradient extends HTMLElement {
|
|
|
6251
6381
|
const sorted = [...this.#gradient.stops].sort(
|
|
6252
6382
|
(a, b) => a.position - b.position,
|
|
6253
6383
|
);
|
|
6254
|
-
const stops = sorted
|
|
6255
|
-
|
|
6384
|
+
const stops = sorted
|
|
6385
|
+
.map((s) => {
|
|
6386
|
+
const alpha = (s.opacity ?? 100) / 100;
|
|
6387
|
+
if (alpha >= 1) return `${s.color} ${s.position}%`;
|
|
6388
|
+
const { r, g, b } = figHexToRGB(s.color);
|
|
6389
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha}) ${s.position}%`;
|
|
6390
|
+
})
|
|
6391
|
+
.join(", ");
|
|
6392
|
+
const interp = gradientInterpolationClause(this.#gradient);
|
|
6393
|
+
return `linear-gradient(${this.#gradient.angle}deg ${interp}, ${stops})`;
|
|
6394
|
+
}
|
|
6395
|
+
|
|
6396
|
+
#stopColorCSS(stop) {
|
|
6397
|
+
const alpha = (stop.opacity ?? 100) / 100;
|
|
6398
|
+
if (alpha >= 1) return stop.color;
|
|
6399
|
+
const { r, g, b } = figHexToRGB(stop.color);
|
|
6400
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
6256
6401
|
}
|
|
6257
6402
|
|
|
6258
6403
|
#buildStopHandles() {
|
|
@@ -6260,7 +6405,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
6260
6405
|
return this.#gradient.stops
|
|
6261
6406
|
.map(
|
|
6262
6407
|
(stop, i) =>
|
|
6263
|
-
`<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
|
|
6408
|
+
`<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>`,
|
|
6264
6409
|
)
|
|
6265
6410
|
.join("");
|
|
6266
6411
|
}
|
|
@@ -6270,31 +6415,69 @@ class FigInputGradient extends HTMLElement {
|
|
|
6270
6415
|
|
|
6271
6416
|
#render() {
|
|
6272
6417
|
const disabled = this.hasAttribute("disabled");
|
|
6418
|
+
const mode = this.#editMode;
|
|
6419
|
+
|
|
6420
|
+
if (mode === "picker") {
|
|
6421
|
+
const experimental = this.getAttribute("experimental");
|
|
6422
|
+
const expAttr = experimental ? ` experimental="${experimental}"` : "";
|
|
6423
|
+
const gradientValue = JSON.stringify(this.value);
|
|
6424
|
+
this.innerHTML = `
|
|
6425
|
+
<fig-fill-picker mode="gradient"${expAttr} value='${gradientValue}'${disabled ? " disabled" : ""}>
|
|
6426
|
+
<fig-chit size="medium" background="${this.#buildGradientCSS()}"${disabled ? " disabled" : ""}></fig-chit>
|
|
6427
|
+
</fig-fill-picker>`;
|
|
6428
|
+
this.#chit = this.querySelector("fig-chit");
|
|
6429
|
+
this.#track = null;
|
|
6430
|
+
this.#setupPickerEvents();
|
|
6431
|
+
return;
|
|
6432
|
+
}
|
|
6433
|
+
|
|
6273
6434
|
this.innerHTML = `
|
|
6274
6435
|
<fig-chit size="medium" background="${this.#buildGradientCSS()}"${disabled ? " disabled" : ""}></fig-chit>
|
|
6275
|
-
|
|
6436
|
+
${mode === "true" ? `<div class="fig-input-gradient-track">${this.#buildStopHandles()}</div>` : ""}`;
|
|
6276
6437
|
this.#chit = this.querySelector("fig-chit");
|
|
6277
6438
|
this.#track = this.querySelector(".fig-input-gradient-track");
|
|
6278
|
-
|
|
6279
|
-
|
|
6439
|
+
|
|
6440
|
+
if (mode === "true") {
|
|
6441
|
+
this.#setupGhostHandle();
|
|
6442
|
+
this.#setupEventListeners();
|
|
6443
|
+
requestAnimationFrame(() => this.#repositionHandles());
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
|
|
6447
|
+
#setupPickerEvents() {
|
|
6448
|
+
const picker = this.querySelector("fig-fill-picker");
|
|
6449
|
+
if (!picker) return;
|
|
6450
|
+
picker.anchorElement = this;
|
|
6451
|
+
|
|
6452
|
+
const syncFromPicker = (e) => {
|
|
6453
|
+
e.stopPropagation();
|
|
6454
|
+
const detail = e.detail;
|
|
6455
|
+
if (!detail?.gradient) return;
|
|
6456
|
+
this.#gradient = normalizeGradientConfig({
|
|
6457
|
+
...this.#gradient,
|
|
6458
|
+
...detail.gradient,
|
|
6459
|
+
});
|
|
6460
|
+
this.#syncChit();
|
|
6461
|
+
};
|
|
6462
|
+
|
|
6463
|
+
picker.addEventListener("input", (e) => {
|
|
6464
|
+
syncFromPicker(e);
|
|
6465
|
+
this.#emitInput();
|
|
6466
|
+
});
|
|
6467
|
+
|
|
6468
|
+
picker.addEventListener("change", (e) => {
|
|
6469
|
+
syncFromPicker(e);
|
|
6470
|
+
this.#emitChange();
|
|
6471
|
+
});
|
|
6280
6472
|
}
|
|
6281
6473
|
|
|
6282
6474
|
#sampleGradientColor(position) {
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
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();
|
|
6475
|
+
return figSampleGradientAt(
|
|
6476
|
+
this.#gradient.stops,
|
|
6477
|
+
position,
|
|
6478
|
+
this.#gradient.interpolationSpace,
|
|
6479
|
+
this.#gradient.hueInterpolation,
|
|
6480
|
+
);
|
|
6298
6481
|
}
|
|
6299
6482
|
|
|
6300
6483
|
#setupGhostHandle() {
|
|
@@ -6430,6 +6613,18 @@ class FigInputGradient extends HTMLElement {
|
|
|
6430
6613
|
});
|
|
6431
6614
|
};
|
|
6432
6615
|
|
|
6616
|
+
#repositionHandles() {
|
|
6617
|
+
if (!this.#track) return;
|
|
6618
|
+
const stops = this.#gradient.stops;
|
|
6619
|
+
this.#track
|
|
6620
|
+
.querySelectorAll("fig-handle:not(.fig-input-gradient-ghost)")
|
|
6621
|
+
.forEach((h, i) => {
|
|
6622
|
+
if (i >= stops.length) return;
|
|
6623
|
+
h.removeAttribute("value");
|
|
6624
|
+
h.setAttribute("value", `${stops[i].position}% 50%`);
|
|
6625
|
+
});
|
|
6626
|
+
}
|
|
6627
|
+
|
|
6433
6628
|
#syncHandles() {
|
|
6434
6629
|
if (!this.#track) return;
|
|
6435
6630
|
const handles = this.#track.querySelectorAll(
|
|
@@ -6442,6 +6637,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
6442
6637
|
this.#track.innerHTML = this.#buildStopHandles();
|
|
6443
6638
|
if (ghost) this.#track.appendChild(ghost);
|
|
6444
6639
|
this.#reobserveHandleColors();
|
|
6640
|
+
requestAnimationFrame(() => this.#repositionHandles());
|
|
6445
6641
|
return;
|
|
6446
6642
|
}
|
|
6447
6643
|
|
|
@@ -6450,7 +6646,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
6450
6646
|
const stop = stops[i];
|
|
6451
6647
|
h.dataset.stopIndex = i;
|
|
6452
6648
|
h.setAttribute("value", `${stop.position}% 50%`);
|
|
6453
|
-
h.setAttribute("color", stop
|
|
6649
|
+
h.setAttribute("color", this.#stopColorCSS(stop));
|
|
6454
6650
|
const tip = h.closest("fig-tooltip");
|
|
6455
6651
|
if (tip) tip.setAttribute("text", `${Math.round(stop.position)}%`);
|
|
6456
6652
|
}
|
|
@@ -6567,6 +6763,10 @@ class FigInputGradient extends HTMLElement {
|
|
|
6567
6763
|
const idx = parseInt(handle.dataset.stopIndex, 10);
|
|
6568
6764
|
if (!isNaN(idx) && this.#gradient.stops[idx]) {
|
|
6569
6765
|
this.#gradient.stops[idx].color = e.detail.color;
|
|
6766
|
+
if (e.detail.opacity !== undefined) {
|
|
6767
|
+
this.#gradient.stops[idx].opacity = e.detail.opacity;
|
|
6768
|
+
}
|
|
6769
|
+
handle.setAttribute("color", this.#stopColorCSS(this.#gradient.stops[idx]));
|
|
6570
6770
|
this.#syncChit();
|
|
6571
6771
|
this.#emitInput();
|
|
6572
6772
|
}
|
|
@@ -6618,6 +6818,10 @@ class FigInputGradient extends HTMLElement {
|
|
|
6618
6818
|
const idx = parseInt(handle.dataset.stopIndex, 10);
|
|
6619
6819
|
if (!isNaN(idx) && this.#gradient.stops[idx]) {
|
|
6620
6820
|
this.#gradient.stops[idx].color = e.detail.color;
|
|
6821
|
+
if (e.detail.opacity !== undefined) {
|
|
6822
|
+
this.#gradient.stops[idx].opacity = e.detail.opacity;
|
|
6823
|
+
}
|
|
6824
|
+
handle.setAttribute("color", this.#stopColorCSS(this.#gradient.stops[idx]));
|
|
6621
6825
|
this.#syncChit();
|
|
6622
6826
|
this.#emitChange();
|
|
6623
6827
|
}
|
|
@@ -6659,7 +6863,8 @@ class FigInputGradient extends HTMLElement {
|
|
|
6659
6863
|
const idx = parseInt(handle.dataset.stopIndex, 10);
|
|
6660
6864
|
if (isNaN(idx) || !this.#gradient.stops[idx]) continue;
|
|
6661
6865
|
const newColor = handle.getAttribute("color");
|
|
6662
|
-
if (newColor
|
|
6866
|
+
if (!newColor || !newColor.startsWith("#")) continue;
|
|
6867
|
+
if (newColor !== this.#gradient.stops[idx].color) {
|
|
6663
6868
|
this.#gradient.stops[idx].color = newColor;
|
|
6664
6869
|
this.#syncChit();
|
|
6665
6870
|
this.#emitInput();
|
|
@@ -6720,6 +6925,14 @@ class FigInputGradient extends HTMLElement {
|
|
|
6720
6925
|
case "disabled":
|
|
6721
6926
|
this.#syncDisabled();
|
|
6722
6927
|
break;
|
|
6928
|
+
case "edit":
|
|
6929
|
+
this.#render();
|
|
6930
|
+
if (this.#isEditable) {
|
|
6931
|
+
document.addEventListener("keydown", this.#onKeyDown);
|
|
6932
|
+
} else {
|
|
6933
|
+
document.removeEventListener("keydown", this.#onKeyDown);
|
|
6934
|
+
}
|
|
6935
|
+
break;
|
|
6723
6936
|
}
|
|
6724
6937
|
}
|
|
6725
6938
|
|
|
@@ -7377,8 +7590,8 @@ class FigChit extends HTMLElement {
|
|
|
7377
7590
|
}
|
|
7378
7591
|
}
|
|
7379
7592
|
|
|
7380
|
-
|
|
7381
|
-
this.style.setProperty("--chit-background", rawBg);
|
|
7593
|
+
const isImage = /^(linear-gradient|radial-gradient|conic-gradient|repeating-|url)\s*\(/i.test(rawBg);
|
|
7594
|
+
this.style.setProperty("--chit-background", isImage ? rawBg : `linear-gradient(${rawBg}, ${rawBg})`);
|
|
7382
7595
|
}
|
|
7383
7596
|
|
|
7384
7597
|
#handleInput(e) {
|
|
@@ -7404,9 +7617,9 @@ class FigChit extends HTMLElement {
|
|
|
7404
7617
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
7405
7618
|
if (oldValue === newValue) return;
|
|
7406
7619
|
if (name === "background") {
|
|
7407
|
-
// Skip full re-render if this was triggered by internal input
|
|
7408
7620
|
if (this.#internalUpdate) {
|
|
7409
|
-
|
|
7621
|
+
const isImg = /^(linear-gradient|radial-gradient|conic-gradient|repeating-|url)\s*\(/i.test(newValue);
|
|
7622
|
+
this.style.setProperty("--chit-background", isImg ? newValue : `linear-gradient(${newValue}, ${newValue})`);
|
|
7410
7623
|
return;
|
|
7411
7624
|
}
|
|
7412
7625
|
this.#render();
|
|
@@ -7428,6 +7641,10 @@ class FigChit extends HTMLElement {
|
|
|
7428
7641
|
}
|
|
7429
7642
|
}
|
|
7430
7643
|
customElements.define("fig-chit", FigChit);
|
|
7644
|
+
class FigSwatch extends FigChit{
|
|
7645
|
+
|
|
7646
|
+
}
|
|
7647
|
+
customElements.define("fig-swatch", FigSwatch);
|
|
7431
7648
|
|
|
7432
7649
|
/* Upload */
|
|
7433
7650
|
/**
|
|
@@ -10698,7 +10915,6 @@ class FigFillPicker extends HTMLElement {
|
|
|
10698
10915
|
this.#chit.style.setProperty("--chit-bg-size", bgSize);
|
|
10699
10916
|
this.#chit.style.setProperty("--chit-bg-position", bgPosition);
|
|
10700
10917
|
|
|
10701
|
-
// For solid colors, also update the alpha
|
|
10702
10918
|
if (this.#fillType === "solid") {
|
|
10703
10919
|
this.#chit.setAttribute("alpha", this.#color.a);
|
|
10704
10920
|
} else {
|
|
@@ -11522,8 +11738,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
11522
11738
|
</fig-tooltip>
|
|
11523
11739
|
</fig-field>
|
|
11524
11740
|
<fig-preview class="fig-fill-picker-gradient-preview">
|
|
11525
|
-
<
|
|
11526
|
-
<div class="fig-fill-picker-gradient-stops-handles"></div>
|
|
11741
|
+
<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
11742
|
</fig-preview>
|
|
11528
11743
|
<fig-field class="fig-fill-picker-gradient-interpolation" direction="horizontal">
|
|
11529
11744
|
<label>Mixing</label>
|
|
@@ -11654,6 +11869,30 @@ class FigFillPicker extends HTMLElement {
|
|
|
11654
11869
|
this.#updateGradientUI();
|
|
11655
11870
|
this.#emitInput();
|
|
11656
11871
|
});
|
|
11872
|
+
|
|
11873
|
+
// Embedded gradient bar input
|
|
11874
|
+
const gradientBarInput = container.querySelector(".fig-fill-picker-gradient-bar-input");
|
|
11875
|
+
if (gradientBarInput) {
|
|
11876
|
+
const syncFromBarInput = (e) => {
|
|
11877
|
+
e.stopPropagation();
|
|
11878
|
+
const detail = e.detail;
|
|
11879
|
+
if (!detail?.gradient) return;
|
|
11880
|
+
this.#gradient = normalizeGradientConfig({
|
|
11881
|
+
...this.#gradient,
|
|
11882
|
+
...detail.gradient,
|
|
11883
|
+
});
|
|
11884
|
+
this.#updateChit();
|
|
11885
|
+
this.#updateGradientStopsList();
|
|
11886
|
+
};
|
|
11887
|
+
gradientBarInput.addEventListener("input", (e) => {
|
|
11888
|
+
syncFromBarInput(e);
|
|
11889
|
+
this.#emitInput();
|
|
11890
|
+
});
|
|
11891
|
+
gradientBarInput.addEventListener("change", (e) => {
|
|
11892
|
+
syncFromBarInput(e);
|
|
11893
|
+
this.#emitChange();
|
|
11894
|
+
});
|
|
11895
|
+
}
|
|
11657
11896
|
}
|
|
11658
11897
|
|
|
11659
11898
|
#updateGradientUI() {
|
|
@@ -11699,14 +11938,14 @@ class FigFillPicker extends HTMLElement {
|
|
|
11699
11938
|
#updateGradientPreview() {
|
|
11700
11939
|
if (!this.#dialog) return;
|
|
11701
11940
|
|
|
11702
|
-
const
|
|
11703
|
-
".fig-fill-picker-gradient-
|
|
11941
|
+
const barInput = this.#dialog.querySelector(
|
|
11942
|
+
".fig-fill-picker-gradient-bar-input",
|
|
11704
11943
|
);
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11944
|
+
if (barInput) {
|
|
11945
|
+
barInput.setAttribute(
|
|
11946
|
+
"value",
|
|
11947
|
+
JSON.stringify({ type: "gradient", gradient: gradientToValueShape(this.#gradient) }),
|
|
11948
|
+
);
|
|
11710
11949
|
}
|
|
11711
11950
|
|
|
11712
11951
|
this.#updateChit();
|
|
@@ -11720,6 +11959,31 @@ class FigFillPicker extends HTMLElement {
|
|
|
11720
11959
|
);
|
|
11721
11960
|
if (!list) return;
|
|
11722
11961
|
|
|
11962
|
+
const existingRows = list.querySelectorAll(
|
|
11963
|
+
".fig-fill-picker-gradient-stop-row",
|
|
11964
|
+
);
|
|
11965
|
+
|
|
11966
|
+
if (existingRows.length === this.#gradient.stops.length) {
|
|
11967
|
+
this.#gradient.stops.forEach((stop, index) => {
|
|
11968
|
+
const row = existingRows[index];
|
|
11969
|
+
row.dataset.index = index;
|
|
11970
|
+
const posInput = row.querySelector(".fig-fill-picker-stop-position");
|
|
11971
|
+
if (posInput) posInput.setAttribute("value", stop.position);
|
|
11972
|
+
const colorInput = row.querySelector(".fig-fill-picker-stop-color");
|
|
11973
|
+
if (colorInput) colorInput.setAttribute("value", stop.color);
|
|
11974
|
+
const removeBtn = row.querySelector(".fig-fill-picker-stop-remove");
|
|
11975
|
+
if (removeBtn) {
|
|
11976
|
+
if (this.#gradient.stops.length <= 2) removeBtn.setAttribute("disabled", "");
|
|
11977
|
+
else removeBtn.removeAttribute("disabled");
|
|
11978
|
+
}
|
|
11979
|
+
});
|
|
11980
|
+
return;
|
|
11981
|
+
}
|
|
11982
|
+
|
|
11983
|
+
this.#rebuildGradientStopsList(list);
|
|
11984
|
+
}
|
|
11985
|
+
|
|
11986
|
+
#rebuildGradientStopsList(list) {
|
|
11723
11987
|
list.innerHTML = this.#gradient.stops
|
|
11724
11988
|
.map(
|
|
11725
11989
|
(stop, index) => `
|
|
@@ -11740,7 +12004,6 @@ class FigFillPicker extends HTMLElement {
|
|
|
11740
12004
|
)
|
|
11741
12005
|
.join("");
|
|
11742
12006
|
|
|
11743
|
-
// Setup event listeners for each stop
|
|
11744
12007
|
list
|
|
11745
12008
|
.querySelectorAll(".fig-fill-picker-gradient-stop-row")
|
|
11746
12009
|
.forEach((row) => {
|
|
@@ -11769,10 +12032,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
11769
12032
|
stopColor.addEventListener("input", (e) => {
|
|
11770
12033
|
this.#gradient.stops[index].color =
|
|
11771
12034
|
e.target.hexOpaque || e.target.value;
|
|
11772
|
-
const
|
|
11773
|
-
|
|
11774
|
-
|
|
11775
|
-
|
|
12035
|
+
const a = e.detail?.rgba?.a;
|
|
12036
|
+
if (a !== undefined) {
|
|
12037
|
+
this.#gradient.stops[index].opacity = Math.round(a * 100);
|
|
12038
|
+
}
|
|
11776
12039
|
this.#updateGradientPreview();
|
|
11777
12040
|
this.#emitInput();
|
|
11778
12041
|
});
|
|
@@ -11798,9 +12061,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
11798
12061
|
const isP3 = this.#gamut === "display-p3";
|
|
11799
12062
|
const stops = gradient.stops
|
|
11800
12063
|
.map((s) => {
|
|
12064
|
+
const alpha = (s.opacity ?? 100) / 100;
|
|
11801
12065
|
const color = isP3
|
|
11802
|
-
? this.#hexToP3(s.color,
|
|
11803
|
-
: this.#hexToRGBA(s.color,
|
|
12066
|
+
? this.#hexToP3(s.color, alpha)
|
|
12067
|
+
: this.#hexToRGBA(s.color, alpha);
|
|
11804
12068
|
return `${color} ${s.position}%`;
|
|
11805
12069
|
})
|
|
11806
12070
|
.join(", ");
|
|
@@ -12762,8 +13026,12 @@ class FigColorTip extends HTMLElement {
|
|
|
12762
13026
|
}
|
|
12763
13027
|
|
|
12764
13028
|
const eventDetail = { color: this.value };
|
|
12765
|
-
if (this.#alphaEnabled
|
|
12766
|
-
|
|
13029
|
+
if (this.#alphaEnabled) {
|
|
13030
|
+
if (detail?.opacity !== undefined) {
|
|
13031
|
+
eventDetail.opacity = detail.opacity;
|
|
13032
|
+
} else if (detail?.alpha !== undefined) {
|
|
13033
|
+
eventDetail.opacity = Math.round(detail.alpha * 100);
|
|
13034
|
+
}
|
|
12767
13035
|
}
|
|
12768
13036
|
|
|
12769
13037
|
this.dispatchEvent(
|
|
@@ -14756,19 +15024,25 @@ class FigHandle extends HTMLElement {
|
|
|
14756
15024
|
this.#colorTip = null;
|
|
14757
15025
|
}
|
|
14758
15026
|
|
|
15027
|
+
#colorWithOpacity(hex, opacity) {
|
|
15028
|
+
if (opacity === undefined || opacity >= 100) return hex;
|
|
15029
|
+
const { r, g, b } = figHexToRGB(hex);
|
|
15030
|
+
return `rgba(${r}, ${g}, ${b}, ${opacity / 100})`;
|
|
15031
|
+
}
|
|
15032
|
+
|
|
14759
15033
|
#handleColorTipInput = (e) => {
|
|
14760
15034
|
e.stopPropagation();
|
|
14761
15035
|
if (e.detail?.color) {
|
|
14762
|
-
this.setAttribute("color", e.detail.color);
|
|
14763
|
-
this.dispatchEvent(new CustomEvent("input", { bubbles: true, detail: { color: e.detail.color } }));
|
|
15036
|
+
this.setAttribute("color", this.#colorWithOpacity(e.detail.color, e.detail.opacity));
|
|
15037
|
+
this.dispatchEvent(new CustomEvent("input", { bubbles: true, detail: { color: e.detail.color, opacity: e.detail.opacity } }));
|
|
14764
15038
|
}
|
|
14765
15039
|
};
|
|
14766
15040
|
|
|
14767
15041
|
#handleColorTipChange = (e) => {
|
|
14768
15042
|
e.stopPropagation();
|
|
14769
15043
|
if (e.detail?.color) {
|
|
14770
|
-
this.setAttribute("color", e.detail.color);
|
|
14771
|
-
this.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: { color: e.detail.color } }));
|
|
15044
|
+
this.setAttribute("color", this.#colorWithOpacity(e.detail.color, e.detail.opacity));
|
|
15045
|
+
this.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: { color: e.detail.color, opacity: e.detail.opacity } }));
|
|
14772
15046
|
}
|
|
14773
15047
|
};
|
|
14774
15048
|
|
package/package.json
CHANGED