@rogieking/figui3 6.4.6 → 6.4.8
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/.cursor/skills/a11y/SKILL.md +96 -0
- package/.cursor/skills/css-render-performance/SKILL.md +46 -0
- package/.cursor/skills/frontend-performance-testing/SKILL.md +46 -0
- package/.cursor/skills/js-runtime-performance/SKILL.md +45 -0
- package/.cursor/skills/web-component-performance/SKILL.md +47 -0
- package/README.md +126 -17
- package/components.css +241 -320
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig-editor.js +62 -60
- package/dist/fig-lab.css +1 -1
- package/dist/fig-layer.css +1 -0
- package/dist/fig-layer.js +1 -0
- package/dist/fig.css +1 -1
- package/dist/fig.js +57 -55
- package/fig-editor.css +61 -0
- package/fig-editor.js +369 -61
- package/fig-lab.css +33 -5
- package/fig-layer.css +111 -0
- package/fig-layer.js +155 -0
- package/fig.js +2240 -919
- package/package.json +13 -4
package/fig-editor.js
CHANGED
|
@@ -1,3 +1,244 @@
|
|
|
1
|
+
import "./fig-layer.js";
|
|
2
|
+
|
|
3
|
+
function figEditorIsWebKitOrIOSBrowser() {
|
|
4
|
+
if (typeof navigator === "undefined") {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
const userAgent = navigator.userAgent || "";
|
|
8
|
+
const isIOSBrowser =
|
|
9
|
+
/\b(iPad|iPhone|iPod)\b/.test(userAgent) ||
|
|
10
|
+
(/\bMacintosh\b/.test(userAgent) && /\bMobile\b/.test(userAgent));
|
|
11
|
+
const isDesktopWebKit =
|
|
12
|
+
/\bAppleWebKit\b/.test(userAgent) &&
|
|
13
|
+
!/\b(Chrome|Chromium|Edg|OPR|SamsungBrowser)\b/.test(userAgent);
|
|
14
|
+
return isIOSBrowser || isDesktopWebKit;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function figEditorSupportsCustomizedBuiltIns() {
|
|
18
|
+
if (
|
|
19
|
+
typeof window === "undefined" ||
|
|
20
|
+
!window.customElements ||
|
|
21
|
+
typeof HTMLButtonElement === "undefined"
|
|
22
|
+
) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const testName = `fig-editor-builtin-probe-${Math.random().toString(36).slice(2)}`;
|
|
27
|
+
class FigEditorCustomizedBuiltInProbe extends HTMLButtonElement {}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
customElements.define(testName, FigEditorCustomizedBuiltInProbe, {
|
|
31
|
+
extends: "button",
|
|
32
|
+
});
|
|
33
|
+
const probe = document.createElement("button", { is: testName });
|
|
34
|
+
return probe instanceof FigEditorCustomizedBuiltInProbe;
|
|
35
|
+
} catch (_error) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const figEditorNeedsBuiltInPolyfill =
|
|
41
|
+
figEditorIsWebKitOrIOSBrowser() && !figEditorSupportsCustomizedBuiltIns();
|
|
42
|
+
const figEditorBuiltInPolyfillReady = (
|
|
43
|
+
figEditorNeedsBuiltInPolyfill
|
|
44
|
+
? import("./polyfills/custom-elements-webkit.js")
|
|
45
|
+
: Promise.resolve()
|
|
46
|
+
)
|
|
47
|
+
.then(() => {})
|
|
48
|
+
.catch((error) => {
|
|
49
|
+
throw error;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function figEditorDefineCustomizedBuiltIn(name, constructor, options) {
|
|
53
|
+
const define = () => {
|
|
54
|
+
if (!customElements.get(name)) {
|
|
55
|
+
customElements.define(name, constructor, options);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (!figEditorNeedsBuiltInPolyfill) {
|
|
60
|
+
define();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
figEditorBuiltInPolyfillReady.then(define).catch((error) => {
|
|
65
|
+
console.error(
|
|
66
|
+
`[figui3] Failed to load customized built-in polyfill for "${name}".`,
|
|
67
|
+
error,
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Toast */
|
|
73
|
+
/**
|
|
74
|
+
* A toast notification element for non-modal, time-based messages.
|
|
75
|
+
* Always positioned at bottom center of the screen.
|
|
76
|
+
* @attr {number} duration - Auto-dismiss duration in ms (0 = no auto-dismiss, default: 5000)
|
|
77
|
+
* @attr {number} offset - Distance from bottom edge in pixels (default: 16)
|
|
78
|
+
* @attr {string} theme - Visual theme: "dark" (default), "light", "danger", "brand"
|
|
79
|
+
* @attr {boolean} open - Whether the toast is visible
|
|
80
|
+
*/
|
|
81
|
+
class FigToast extends HTMLDialogElement {
|
|
82
|
+
constructor() {
|
|
83
|
+
super();
|
|
84
|
+
this._figInit();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_figInit() {
|
|
88
|
+
if (this._figInitialized) return;
|
|
89
|
+
this._figInitialized = true;
|
|
90
|
+
this._defaultOffset = 16;
|
|
91
|
+
this._autoCloseTimer = null;
|
|
92
|
+
this._boundHandleClose = this.handleClose.bind(this);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getOffset() {
|
|
96
|
+
return parseInt(this.getAttribute("offset") ?? this._defaultOffset);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
connectedCallback() {
|
|
100
|
+
this._figInit();
|
|
101
|
+
|
|
102
|
+
if (!this.hasAttribute("theme")) {
|
|
103
|
+
this.setAttribute("theme", "dark");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.syncLiveRegion();
|
|
107
|
+
|
|
108
|
+
const shouldOpen =
|
|
109
|
+
this.getAttribute("open") === "true" || this.getAttribute("open") === "";
|
|
110
|
+
if (this.hasAttribute("open") && !shouldOpen) {
|
|
111
|
+
this.removeAttribute("open");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!shouldOpen) {
|
|
115
|
+
this.close();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
requestAnimationFrame(() => {
|
|
119
|
+
this.addCloseListeners();
|
|
120
|
+
this.applyPosition();
|
|
121
|
+
|
|
122
|
+
if (shouldOpen) {
|
|
123
|
+
this.showToast();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
disconnectedCallback() {
|
|
129
|
+
this._figInit();
|
|
130
|
+
this.clearAutoClose();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
addCloseListeners() {
|
|
134
|
+
this.querySelectorAll("[close-toast]").forEach((button) => {
|
|
135
|
+
button.removeEventListener("click", this._boundHandleClose);
|
|
136
|
+
button.addEventListener("click", this._boundHandleClose);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
handleClose() {
|
|
141
|
+
this.hideToast();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
applyPosition() {
|
|
145
|
+
this.style.position = "fixed";
|
|
146
|
+
this.style.margin = "0";
|
|
147
|
+
this.style.top = "auto";
|
|
148
|
+
this.style.bottom = `${this.getOffset()}px`;
|
|
149
|
+
this.style.left = "50%";
|
|
150
|
+
this.style.right = "auto";
|
|
151
|
+
this.style.transform = "translateX(-50%)";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
startAutoClose() {
|
|
155
|
+
this.clearAutoClose();
|
|
156
|
+
|
|
157
|
+
const duration = parseInt(this.getAttribute("duration") ?? "5000");
|
|
158
|
+
if (duration > 0) {
|
|
159
|
+
this._autoCloseTimer = setTimeout(() => {
|
|
160
|
+
this.hideToast();
|
|
161
|
+
}, duration);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
syncLiveRegion() {
|
|
166
|
+
const assertive =
|
|
167
|
+
this.getAttribute("live") === "assertive" ||
|
|
168
|
+
this.getAttribute("theme") === "danger";
|
|
169
|
+
if (!this.hasAttribute("role")) {
|
|
170
|
+
this.setAttribute("role", assertive ? "alert" : "status");
|
|
171
|
+
}
|
|
172
|
+
if (!this.hasAttribute("aria-live")) {
|
|
173
|
+
this.setAttribute("aria-live", assertive ? "assertive" : "polite");
|
|
174
|
+
}
|
|
175
|
+
if (!this.hasAttribute("aria-atomic")) {
|
|
176
|
+
this.setAttribute("aria-atomic", "true");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
clearAutoClose() {
|
|
181
|
+
if (this._autoCloseTimer) {
|
|
182
|
+
clearTimeout(this._autoCloseTimer);
|
|
183
|
+
this._autoCloseTimer = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
_resolveAutoTheme() {
|
|
188
|
+
if (this.getAttribute("theme") !== "auto") return;
|
|
189
|
+
const cs = getComputedStyle(document.documentElement).colorScheme || "";
|
|
190
|
+
const isDark = cs.includes("dark");
|
|
191
|
+
this.style.colorScheme = isDark ? "light" : "dark";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
showToast() {
|
|
195
|
+
this._resolveAutoTheme();
|
|
196
|
+
if (!this.open) this.show();
|
|
197
|
+
this.applyPosition();
|
|
198
|
+
this.startAutoClose();
|
|
199
|
+
this.dispatchEvent(new CustomEvent("toast-show", { bubbles: true }));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
hideToast() {
|
|
203
|
+
this.clearAutoClose();
|
|
204
|
+
this.close();
|
|
205
|
+
this.dispatchEvent(new CustomEvent("toast-hide", { bubbles: true }));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
static get observedAttributes() {
|
|
209
|
+
return ["duration", "offset", "open", "theme", "live"];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
213
|
+
this._figInit();
|
|
214
|
+
if (!this.isConnected) return;
|
|
215
|
+
if (name === "offset") {
|
|
216
|
+
this.applyPosition();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (name === "open") {
|
|
220
|
+
if (newValue !== null && newValue !== "false") {
|
|
221
|
+
this.showToast();
|
|
222
|
+
} else {
|
|
223
|
+
this.hideToast();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (name === "theme") {
|
|
228
|
+
if (newValue === "auto") {
|
|
229
|
+
this._resolveAutoTheme();
|
|
230
|
+
} else {
|
|
231
|
+
this.style.removeProperty("color-scheme");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (name === "theme" || name === "live") {
|
|
236
|
+
this.syncLiveRegion();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
figEditorDefineCustomizedBuiltIn("fig-toast", FigToast, { extends: "dialog" });
|
|
241
|
+
|
|
1
242
|
// FigFillPicker
|
|
2
243
|
const GRADIENT_INTERPOLATION_SPACES = [
|
|
3
244
|
"srgb",
|
|
@@ -16,13 +257,10 @@ const GRADIENT_HUE_INTERPOLATIONS = [
|
|
|
16
257
|
function normalizeGradientConfig(gradient) {
|
|
17
258
|
const next = { ...(gradient ?? {}) };
|
|
18
259
|
let interpolationSpace = String(
|
|
19
|
-
next.interpolationSpace ?? "
|
|
260
|
+
next.interpolationSpace ?? "srgb",
|
|
20
261
|
).toLowerCase();
|
|
21
262
|
if (!GRADIENT_INTERPOLATION_SPACES.includes(interpolationSpace)) {
|
|
22
|
-
interpolationSpace = "
|
|
23
|
-
}
|
|
24
|
-
if (interpolationSpace === "srgb" || interpolationSpace === "display-p3") {
|
|
25
|
-
interpolationSpace = "oklab";
|
|
263
|
+
interpolationSpace = "srgb";
|
|
26
264
|
}
|
|
27
265
|
next.interpolationSpace = interpolationSpace;
|
|
28
266
|
|
|
@@ -51,6 +289,9 @@ function gradientToValueShape(gradient) {
|
|
|
51
289
|
|
|
52
290
|
function gradientInterpolationClause(gradient) {
|
|
53
291
|
const normalized = normalizeGradientConfig(gradient);
|
|
292
|
+
if (normalized.interpolationSpace === "srgb") {
|
|
293
|
+
return "";
|
|
294
|
+
}
|
|
54
295
|
if (normalized.interpolationSpace === "oklch") {
|
|
55
296
|
return `in oklch ${normalized.hueInterpolation} hue`;
|
|
56
297
|
}
|
|
@@ -83,7 +324,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
83
324
|
angle: 0,
|
|
84
325
|
centerX: 50,
|
|
85
326
|
centerY: 50,
|
|
86
|
-
interpolationSpace: "
|
|
327
|
+
interpolationSpace: "srgb",
|
|
87
328
|
hueInterpolation: "shorter",
|
|
88
329
|
stops: [
|
|
89
330
|
{ position: 0, color: "#D9D9D9", opacity: 100 },
|
|
@@ -107,13 +348,26 @@ class FigFillPicker extends HTMLElement {
|
|
|
107
348
|
#teardownColorAreaEvents = null;
|
|
108
349
|
#dialogOpenObserver = null;
|
|
109
350
|
#webcamTabObserver = null;
|
|
351
|
+
#boundTriggerClick = null;
|
|
352
|
+
#boundTriggerKeydown = null;
|
|
110
353
|
|
|
111
354
|
constructor() {
|
|
112
355
|
super();
|
|
356
|
+
this.#boundTriggerClick = this.#handleTriggerClick.bind(this);
|
|
357
|
+
this.#boundTriggerKeydown = this.#handleTriggerKeydown.bind(this);
|
|
113
358
|
}
|
|
114
359
|
|
|
115
360
|
static get observedAttributes() {
|
|
116
|
-
return [
|
|
361
|
+
return [
|
|
362
|
+
"value",
|
|
363
|
+
"disabled",
|
|
364
|
+
"alpha",
|
|
365
|
+
"mode",
|
|
366
|
+
"experimental",
|
|
367
|
+
"aria-label",
|
|
368
|
+
"aria-labelledby",
|
|
369
|
+
"aria-describedby",
|
|
370
|
+
];
|
|
117
371
|
}
|
|
118
372
|
|
|
119
373
|
connectedCallback() {
|
|
@@ -152,6 +406,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
152
406
|
URL.revokeObjectURL(this.#video.url);
|
|
153
407
|
}
|
|
154
408
|
if (this.#chit) this.#chit.removeAttribute("selected");
|
|
409
|
+
if (this.#trigger) {
|
|
410
|
+
this.#trigger.removeEventListener("click", this.#boundTriggerClick);
|
|
411
|
+
this.#trigger.removeEventListener("keydown", this.#boundTriggerKeydown);
|
|
412
|
+
}
|
|
155
413
|
if (this.#dialog) {
|
|
156
414
|
this.#dialog.close();
|
|
157
415
|
this.#dialog.remove();
|
|
@@ -180,24 +438,67 @@ class FigFillPicker extends HTMLElement {
|
|
|
180
438
|
this.#chit = null;
|
|
181
439
|
}
|
|
182
440
|
|
|
183
|
-
this.#
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
});
|
|
441
|
+
this.#syncTriggerA11y();
|
|
442
|
+
this.#trigger.removeEventListener("click", this.#boundTriggerClick);
|
|
443
|
+
this.#trigger.addEventListener("click", this.#boundTriggerClick);
|
|
444
|
+
this.#trigger.removeEventListener("keydown", this.#boundTriggerKeydown);
|
|
445
|
+
this.#trigger.addEventListener("keydown", this.#boundTriggerKeydown);
|
|
189
446
|
|
|
190
447
|
// Prevent fig-chit's internal color input from opening system picker
|
|
191
448
|
if (this.#chit) {
|
|
192
449
|
requestAnimationFrame(() => {
|
|
193
450
|
const input = this.#chit.querySelector('input[type="color"]');
|
|
194
451
|
if (input) {
|
|
195
|
-
input.
|
|
452
|
+
input.remove();
|
|
196
453
|
}
|
|
454
|
+
this.#syncTriggerA11y();
|
|
197
455
|
});
|
|
198
456
|
}
|
|
199
457
|
}
|
|
200
458
|
|
|
459
|
+
#triggerLabel() {
|
|
460
|
+
return this.getAttribute("aria-label") || "Fill picker";
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
#syncTriggerA11y() {
|
|
464
|
+
if (!this.#trigger) return;
|
|
465
|
+
const disabled = this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
466
|
+
const labelledBy = this.getAttribute("aria-labelledby");
|
|
467
|
+
if (!this.#trigger.hasAttribute("role")) this.#trigger.setAttribute("role", "button");
|
|
468
|
+
this.#trigger.setAttribute("tabindex", disabled ? "-1" : "0");
|
|
469
|
+
this.#trigger.setAttribute("aria-disabled", disabled ? "true" : "false");
|
|
470
|
+
if (labelledBy) {
|
|
471
|
+
this.#trigger.setAttribute("aria-labelledby", labelledBy);
|
|
472
|
+
this.#trigger.removeAttribute("aria-label");
|
|
473
|
+
} else if (this.hasAttribute("aria-label")) {
|
|
474
|
+
this.#trigger.setAttribute("aria-label", `Open ${this.#triggerLabel()}`);
|
|
475
|
+
this.#trigger.removeAttribute("aria-labelledby");
|
|
476
|
+
} else {
|
|
477
|
+
this.#trigger.removeAttribute("aria-labelledby");
|
|
478
|
+
if (!this.#trigger.hasAttribute("aria-label")) {
|
|
479
|
+
this.#trigger.setAttribute("aria-label", `Open ${this.#triggerLabel()}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const describedBy = this.getAttribute("aria-describedby");
|
|
483
|
+
if (describedBy) this.#trigger.setAttribute("aria-describedby", describedBy);
|
|
484
|
+
else this.#trigger.removeAttribute("aria-describedby");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
#handleTriggerClick(e) {
|
|
488
|
+
if (this.hasAttribute("disabled")) return;
|
|
489
|
+
e.stopPropagation();
|
|
490
|
+
e.preventDefault();
|
|
491
|
+
this.#openDialog();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
#handleTriggerKeydown(e) {
|
|
495
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
496
|
+
if (this.hasAttribute("disabled")) return;
|
|
497
|
+
e.preventDefault();
|
|
498
|
+
e.stopPropagation();
|
|
499
|
+
this.#openDialog();
|
|
500
|
+
}
|
|
501
|
+
|
|
201
502
|
#parseValue() {
|
|
202
503
|
const valueAttr = this.getAttribute("value");
|
|
203
504
|
if (!valueAttr) return;
|
|
@@ -417,7 +718,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
417
718
|
const options = allowedModes
|
|
418
719
|
.map((m) => `<option value="${m}">${modeLabels[m]}</option>`)
|
|
419
720
|
.join("\n ");
|
|
420
|
-
headerContent = `<fig-dropdown class="fig-fill-picker-type" ${expAttr} value="${this.#fillType}">
|
|
721
|
+
headerContent = `<fig-dropdown class="fig-fill-picker-type" label="Fill type" ${expAttr} value="${this.#fillType}">
|
|
421
722
|
${options}
|
|
422
723
|
</fig-dropdown>`;
|
|
423
724
|
}
|
|
@@ -427,7 +728,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
427
728
|
.map((m) => `<div class="fig-fill-picker-tab" data-tab="${m}"></div>`)
|
|
428
729
|
.join("\n ");
|
|
429
730
|
|
|
430
|
-
const gamutDropdown = `<fig-dropdown class="fig-fill-picker-gamut" ${expAttr} value="${this.#gamut}">
|
|
731
|
+
const gamutDropdown = `<fig-dropdown class="fig-fill-picker-gamut" label="Color gamut" ${expAttr} value="${this.#gamut}">
|
|
431
732
|
<option value="srgb">sRGB</option>
|
|
432
733
|
<option value="display-p3">Display P3</option>
|
|
433
734
|
</fig-dropdown>`;
|
|
@@ -436,7 +737,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
436
737
|
<fig-header>
|
|
437
738
|
${headerContent}
|
|
438
739
|
${gamutDropdown}
|
|
439
|
-
<fig-button icon variant="ghost" class="fig-fill-picker-close">
|
|
740
|
+
<fig-button icon variant="ghost" class="fig-fill-picker-close" aria-label="Close fill picker">
|
|
440
741
|
<fig-icon name="close"></fig-icon>
|
|
441
742
|
</fig-button>
|
|
442
743
|
</fig-header>
|
|
@@ -604,6 +905,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
604
905
|
<fig-preview class="fig-fill-picker-color-area">
|
|
605
906
|
<canvas width="200" height="200"></canvas>
|
|
606
907
|
<fig-handle
|
|
908
|
+
aria-label="Color saturation and brightness"
|
|
607
909
|
type="color"
|
|
608
910
|
color="${this.#hsvToHex({ ...this.#color, a: 1 })}"
|
|
609
911
|
data-no-color-picker
|
|
@@ -614,20 +916,20 @@ class FigFillPicker extends HTMLElement {
|
|
|
614
916
|
></fig-handle>
|
|
615
917
|
</fig-preview>
|
|
616
918
|
<div class="fig-fill-picker-sliders">
|
|
617
|
-
<fig-tooltip text="Sample color"><fig-button icon variant="ghost" class="fig-fill-picker-eyedropper"><fig-icon name="eyedropper"></fig-icon></fig-button></fig-tooltip>
|
|
618
|
-
<fig-slider type="hue" text="false" min="0" max="360" value="${
|
|
919
|
+
<fig-tooltip text="Sample color"><fig-button icon variant="ghost" class="fig-fill-picker-eyedropper" aria-label="Sample color"><fig-icon name="eyedropper"></fig-icon></fig-button></fig-tooltip>
|
|
920
|
+
<fig-slider type="hue" text="false" min="0" max="360" aria-label="Hue" value="${
|
|
619
921
|
this.#color.h
|
|
620
922
|
}"></fig-slider>
|
|
621
923
|
${
|
|
622
924
|
showAlpha
|
|
623
|
-
? `<fig-slider type="opacity" text="true" units="%" min="0" max="100" value="${
|
|
925
|
+
? `<fig-slider type="opacity" text="true" units="%" min="0" max="100" aria-label="Opacity" value="${
|
|
624
926
|
this.#color.a * 100
|
|
625
927
|
}" color="${this.#hsvToHex(this.#color)}"></fig-slider>`
|
|
626
928
|
: ""
|
|
627
929
|
}
|
|
628
930
|
</div>
|
|
629
931
|
<fig-field class="fig-fill-picker-inputs">
|
|
630
|
-
<fig-dropdown class="fig-fill-picker-input-mode" ${expAttr} value="${this.#colorInputMode}">
|
|
932
|
+
<fig-dropdown class="fig-fill-picker-input-mode" label="Color value format" ${expAttr} value="${this.#colorInputMode}">
|
|
631
933
|
<option value="hex">Hex</option>
|
|
632
934
|
<option value="rgb">RGB</option>
|
|
633
935
|
<option value="hsl">HSL</option>
|
|
@@ -910,48 +1212,48 @@ class FigFillPicker extends HTMLElement {
|
|
|
910
1212
|
const wrap = (tooltip, html) =>
|
|
911
1213
|
`<fig-tooltip text="${tooltip}">${html}</fig-tooltip>`;
|
|
912
1214
|
|
|
913
|
-
const num = (cls, min, max, step) =>
|
|
914
|
-
`<fig-input-number class="${cls}" min="${min}" max="${max}"${step != null ? ` step="${step}"` : ""}></fig-input-number>`;
|
|
1215
|
+
const num = (cls, label, min, max, step) =>
|
|
1216
|
+
`<fig-input-number class="${cls}" aria-label="${label}" min="${min}" max="${max}"${step != null ? ` step="${step}"` : ""}></fig-input-number>`;
|
|
915
1217
|
|
|
916
1218
|
let html;
|
|
917
1219
|
switch (this.#colorInputMode) {
|
|
918
1220
|
case "rgb":
|
|
919
1221
|
html = `<div class="input-combo">
|
|
920
|
-
${wrap("Red", num("fig-fill-picker-ci-r", 0, 255))}
|
|
921
|
-
${wrap("Green", num("fig-fill-picker-ci-g", 0, 255))}
|
|
922
|
-
${wrap("Blue", num("fig-fill-picker-ci-b", 0, 255))}
|
|
1222
|
+
${wrap("Red", num("fig-fill-picker-ci-r", "Red", 0, 255))}
|
|
1223
|
+
${wrap("Green", num("fig-fill-picker-ci-g", "Green", 0, 255))}
|
|
1224
|
+
${wrap("Blue", num("fig-fill-picker-ci-b", "Blue", 0, 255))}
|
|
923
1225
|
</div>`;
|
|
924
1226
|
break;
|
|
925
1227
|
case "hsl":
|
|
926
1228
|
html = `<div class="input-combo">
|
|
927
|
-
${wrap("Hue", num("fig-fill-picker-ci-h", 0, 360))}
|
|
928
|
-
${wrap("Saturation", num("fig-fill-picker-ci-s", 0, 100))}
|
|
929
|
-
${wrap("Lightness", num("fig-fill-picker-ci-l", 0, 100))}
|
|
1229
|
+
${wrap("Hue", num("fig-fill-picker-ci-h", "Hue", 0, 360))}
|
|
1230
|
+
${wrap("Saturation", num("fig-fill-picker-ci-s", "Saturation", 0, 100))}
|
|
1231
|
+
${wrap("Lightness", num("fig-fill-picker-ci-l", "Lightness", 0, 100))}
|
|
930
1232
|
</div>`;
|
|
931
1233
|
break;
|
|
932
1234
|
case "hsb":
|
|
933
1235
|
html = `<div class="input-combo">
|
|
934
|
-
${wrap("Hue", num("fig-fill-picker-ci-h", 0, 360))}
|
|
935
|
-
${wrap("Saturation", num("fig-fill-picker-ci-s", 0, 100))}
|
|
936
|
-
${wrap("Brightness", num("fig-fill-picker-ci-v", 0, 100))}
|
|
1236
|
+
${wrap("Hue", num("fig-fill-picker-ci-h", "Hue", 0, 360))}
|
|
1237
|
+
${wrap("Saturation", num("fig-fill-picker-ci-s", "Saturation", 0, 100))}
|
|
1238
|
+
${wrap("Brightness", num("fig-fill-picker-ci-v", "Brightness", 0, 100))}
|
|
937
1239
|
</div>`;
|
|
938
1240
|
break;
|
|
939
1241
|
case "lab":
|
|
940
1242
|
html = `<div class="input-combo">
|
|
941
|
-
${wrap("Lightness", num("fig-fill-picker-ci-okl", 0, 100))}
|
|
942
|
-
${wrap("Green-Red axis", num("fig-fill-picker-ci-oka", -0.4, 0.4, 0.001))}
|
|
943
|
-
${wrap("Blue-Yellow axis", num("fig-fill-picker-ci-okb", -0.4, 0.4, 0.001))}
|
|
1243
|
+
${wrap("Lightness", num("fig-fill-picker-ci-okl", "Lightness", 0, 100))}
|
|
1244
|
+
${wrap("Green-Red axis", num("fig-fill-picker-ci-oka", "Green-Red axis", -0.4, 0.4, 0.001))}
|
|
1245
|
+
${wrap("Blue-Yellow axis", num("fig-fill-picker-ci-okb", "Blue-Yellow axis", -0.4, 0.4, 0.001))}
|
|
944
1246
|
</div>`;
|
|
945
1247
|
break;
|
|
946
1248
|
case "lch":
|
|
947
1249
|
html = `<div class="input-combo">
|
|
948
|
-
${wrap("Lightness", num("fig-fill-picker-ci-okl", 0, 100))}
|
|
949
|
-
${wrap("Chroma", num("fig-fill-picker-ci-okc", 0, 0.4, 0.001))}
|
|
950
|
-
${wrap("Hue", num("fig-fill-picker-ci-okh", 0, 360))}
|
|
1250
|
+
${wrap("Lightness", num("fig-fill-picker-ci-okl", "Lightness", 0, 100))}
|
|
1251
|
+
${wrap("Chroma", num("fig-fill-picker-ci-okc", "Chroma", 0, 0.4, 0.001))}
|
|
1252
|
+
${wrap("Hue", num("fig-fill-picker-ci-okh", "Hue", 0, 360))}
|
|
951
1253
|
</div>`;
|
|
952
1254
|
break;
|
|
953
1255
|
default: // hex
|
|
954
|
-
html = `<fig-input-text class="fig-fill-picker-ci-hex" placeholder="FFFFFF"></fig-input-text>`;
|
|
1256
|
+
html = `<fig-input-text class="fig-fill-picker-ci-hex" aria-label="Hex color" placeholder="FFFFFF"></fig-input-text>`;
|
|
955
1257
|
break;
|
|
956
1258
|
}
|
|
957
1259
|
|
|
@@ -1108,7 +1410,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
1108
1410
|
|
|
1109
1411
|
container.innerHTML = `
|
|
1110
1412
|
<fig-field class="fig-fill-picker-gradient-header">
|
|
1111
|
-
<fig-dropdown class="fig-fill-picker-gradient-type" ${expAttr} value="${
|
|
1413
|
+
<fig-dropdown class="fig-fill-picker-gradient-type" label="Gradient type" ${expAttr} value="${
|
|
1112
1414
|
this.#gradient.type
|
|
1113
1415
|
}">
|
|
1114
1416
|
<option value="linear" selected>Linear</option>
|
|
@@ -1116,35 +1418,36 @@ class FigFillPicker extends HTMLElement {
|
|
|
1116
1418
|
<option value="angular">Angular</option>
|
|
1117
1419
|
</fig-dropdown>
|
|
1118
1420
|
<fig-tooltip text="Rotate gradient">
|
|
1119
|
-
<fig-input-number class="fig-fill-picker-gradient-angle" value="${
|
|
1421
|
+
<fig-input-number class="fig-fill-picker-gradient-angle" aria-label="Gradient angle" value="${
|
|
1120
1422
|
(this.#gradient.angle - 90 + 360) % 360
|
|
1121
1423
|
}" min="0" max="360" units="°" wrap></fig-input-number>
|
|
1122
1424
|
</fig-tooltip>
|
|
1123
1425
|
<div class="fig-fill-picker-gradient-center input-combo" style="display: none;">
|
|
1124
|
-
<fig-input-number min="0" max="100" value="${
|
|
1426
|
+
<fig-input-number min="0" max="100" aria-label="Gradient center X" value="${
|
|
1125
1427
|
this.#gradient.centerX
|
|
1126
1428
|
}" units="%" class="fig-fill-picker-gradient-cx"></fig-input-number>
|
|
1127
|
-
<fig-input-number min="0" max="100" value="${
|
|
1429
|
+
<fig-input-number min="0" max="100" aria-label="Gradient center Y" value="${
|
|
1128
1430
|
this.#gradient.centerY
|
|
1129
1431
|
}" units="%" class="fig-fill-picker-gradient-cy"></fig-input-number>
|
|
1130
1432
|
</div>
|
|
1131
1433
|
<fig-tooltip text="Flip gradient">
|
|
1132
|
-
<fig-button icon variant="ghost" class="fig-fill-picker-gradient-flip">
|
|
1434
|
+
<fig-button icon variant="ghost" class="fig-fill-picker-gradient-flip" aria-label="Flip gradient">
|
|
1133
1435
|
<fig-icon name="swap"></fig-icon>
|
|
1134
1436
|
</fig-button>
|
|
1135
1437
|
</fig-tooltip>
|
|
1136
1438
|
</fig-field>
|
|
1137
1439
|
<fig-preview class="fig-fill-picker-gradient-preview">
|
|
1138
|
-
<fig-input-gradient class="fig-fill-picker-gradient-bar-input" edit="true" mode="tip" size="large" value='${JSON.stringify({ type: "gradient", gradient: gradientToValueShape(this.#gradient) })}'></fig-input-gradient>
|
|
1440
|
+
<fig-input-gradient class="fig-fill-picker-gradient-bar-input" aria-label="Gradient stops" edit="true" mode="tip" size="large" value='${JSON.stringify({ type: "gradient", gradient: gradientToValueShape(this.#gradient) })}'></fig-input-gradient>
|
|
1139
1441
|
</fig-preview>
|
|
1140
1442
|
<fig-field class="fig-fill-picker-gradient-interpolation">
|
|
1141
1443
|
<label>Mixing</label>
|
|
1142
|
-
<fig-dropdown class="fig-fill-picker-gradient-space" full ${expAttr} value="${
|
|
1444
|
+
<fig-dropdown class="fig-fill-picker-gradient-space" label="Gradient mixing" full ${expAttr} value="${
|
|
1143
1445
|
this.#gradient.interpolationSpace === "oklch"
|
|
1144
1446
|
? `oklch-${this.#gradient.hueInterpolation || "shorter"}`
|
|
1145
1447
|
: this.#gradient.interpolationSpace
|
|
1146
1448
|
}">
|
|
1147
1449
|
<optgroup label="sRGB">
|
|
1450
|
+
<option value="srgb">Classic</option>
|
|
1148
1451
|
<option value="srgb-linear">Linear</option>
|
|
1149
1452
|
</optgroup>
|
|
1150
1453
|
<optgroup label="OKLab">
|
|
@@ -1161,7 +1464,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
1161
1464
|
<div class="fig-fill-picker-gradient-stops">
|
|
1162
1465
|
<fig-header class="fig-fill-picker-gradient-stops-header" borderless>
|
|
1163
1466
|
<span>Stops</span>
|
|
1164
|
-
<fig-button icon variant="ghost" class="fig-fill-picker-gradient-add" title="Add stop">
|
|
1467
|
+
<fig-button icon variant="ghost" class="fig-fill-picker-gradient-add" aria-label="Add gradient stop" title="Add stop">
|
|
1165
1468
|
<fig-icon name="add"></fig-icon>
|
|
1166
1469
|
</fig-button>
|
|
1167
1470
|
</fig-header>
|
|
@@ -1390,15 +1693,15 @@ class FigFillPicker extends HTMLElement {
|
|
|
1390
1693
|
.map(
|
|
1391
1694
|
(stop, index) => `
|
|
1392
1695
|
<fig-field class="fig-fill-picker-gradient-stop-row" data-index="${index}">
|
|
1393
|
-
<fig-input-number class="fig-fill-picker-stop-position" min="0" max="100" value="${
|
|
1696
|
+
<fig-input-number class="fig-fill-picker-stop-position" aria-label="Gradient stop position" min="0" max="100" value="${
|
|
1394
1697
|
stop.position
|
|
1395
1698
|
}" units="%"></fig-input-number>
|
|
1396
|
-
<fig-input-color class="fig-fill-picker-stop-color" text="true" alpha="true" picker="figma" picker-dialog-position="right" value="${
|
|
1699
|
+
<fig-input-color class="fig-fill-picker-stop-color" aria-label="Gradient stop color" text="true" alpha="true" picker="figma" picker-dialog-position="right" value="${
|
|
1397
1700
|
stop.color
|
|
1398
1701
|
}"></fig-input-color>
|
|
1399
1702
|
<fig-button icon variant="ghost" class="fig-fill-picker-stop-remove" ${
|
|
1400
1703
|
this.#gradient.stops.length <= 2 ? "disabled" : ""
|
|
1401
|
-
}>
|
|
1704
|
+
} aria-label="Remove gradient stop">
|
|
1402
1705
|
<fig-icon name="minus"></fig-icon>
|
|
1403
1706
|
</fig-button>
|
|
1404
1707
|
</fig-field>
|
|
@@ -1470,9 +1773,9 @@ class FigFillPicker extends HTMLElement {
|
|
|
1470
1773
|
return `${color} ${s.position}%`;
|
|
1471
1774
|
})
|
|
1472
1775
|
.join(", ");
|
|
1473
|
-
const
|
|
1474
|
-
|
|
1475
|
-
: "";
|
|
1776
|
+
const interpolationClause = gradientInterpolationClause(gradient);
|
|
1777
|
+
const interpolation =
|
|
1778
|
+
includeInterpolation && interpolationClause ? ` ${interpolationClause}` : "";
|
|
1476
1779
|
switch (gradient.type) {
|
|
1477
1780
|
case "linear":
|
|
1478
1781
|
return `linear-gradient(${gradient.angle}deg${interpolation}, ${stops})`;
|
|
@@ -1514,7 +1817,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
1514
1817
|
|
|
1515
1818
|
container.innerHTML = `
|
|
1516
1819
|
<fig-field class="fig-fill-picker-media-header">
|
|
1517
|
-
<fig-dropdown class="fig-fill-picker-scale-mode" ${expAttr} value="${
|
|
1820
|
+
<fig-dropdown class="fig-fill-picker-scale-mode" label="Image scale mode" ${expAttr} value="${
|
|
1518
1821
|
this.#image.scaleMode
|
|
1519
1822
|
}">
|
|
1520
1823
|
<option value="fill" selected>Fill</option>
|
|
@@ -1522,7 +1825,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
1522
1825
|
<option value="crop">Crop</option>
|
|
1523
1826
|
<option value="tile">Tile</option>
|
|
1524
1827
|
</fig-dropdown>
|
|
1525
|
-
<fig-input-number class="fig-fill-picker-scale" min="1" max="200" value="${
|
|
1828
|
+
<fig-input-number class="fig-fill-picker-scale" aria-label="Image tile scale" min="1" max="200" value="${
|
|
1526
1829
|
this.#image.scale
|
|
1527
1830
|
}" units="%" ${
|
|
1528
1831
|
this.#image.scaleMode === "tile" ? "" : 'style="display: none;"'
|
|
@@ -1531,7 +1834,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
1531
1834
|
<fig-icon name="rotate"></fig-icon>
|
|
1532
1835
|
</fig-button>
|
|
1533
1836
|
</fig-field>
|
|
1534
|
-
<fig-image class="fig-fill-picker-media-preview fig-fill-picker-image-preview" upload="true" label="Upload from computer" size="auto" aspect-ratio="1/1" fit="cover" checkerboard="true"></fig-image>
|
|
1837
|
+
<fig-image class="fig-fill-picker-media-preview fig-fill-picker-image-preview" upload="true" label="Upload from computer" alt="Image fill preview" size="auto" aspect-ratio="1/1" fit="cover" checkerboard="true"></fig-image>
|
|
1535
1838
|
`;
|
|
1536
1839
|
|
|
1537
1840
|
this.#setupImageEvents(container);
|
|
@@ -1682,7 +1985,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
1682
1985
|
|
|
1683
1986
|
container.innerHTML = `
|
|
1684
1987
|
<fig-field class="fig-fill-picker-media-header">
|
|
1685
|
-
<fig-dropdown class="fig-fill-picker-scale-mode" ${expAttr} value="${
|
|
1988
|
+
<fig-dropdown class="fig-fill-picker-scale-mode" label="Video scale mode" ${expAttr} value="${
|
|
1686
1989
|
this.#video.scaleMode
|
|
1687
1990
|
}">
|
|
1688
1991
|
<option value="fill" selected>Fill</option>
|
|
@@ -1693,7 +1996,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
1693
1996
|
<fig-icon name="rotate"></fig-icon>
|
|
1694
1997
|
</fig-button>
|
|
1695
1998
|
</fig-field>
|
|
1696
|
-
<fig-media class="fig-fill-picker-media-preview fig-fill-picker-video-preview" type="video" upload="true" label="Upload from computer" size="auto" aspect-ratio="1/1" fit="cover" checkerboard="true" autoplay="true" muted="true" loop="true"></fig-media>
|
|
1999
|
+
<fig-media class="fig-fill-picker-media-preview fig-fill-picker-video-preview" type="video" upload="true" label="Upload from computer" aria-label="Video fill preview" size="auto" aspect-ratio="1/1" fit="cover" checkerboard="true" autoplay="true" controls muted="true" loop="true"></fig-media>
|
|
1697
2000
|
`;
|
|
1698
2001
|
|
|
1699
2002
|
this.#setupVideoEvents(container);
|
|
@@ -1741,10 +2044,10 @@ class FigFillPicker extends HTMLElement {
|
|
|
1741
2044
|
|
|
1742
2045
|
container.innerHTML = `
|
|
1743
2046
|
<fig-field class="fig-fill-picker-webcam-camera" style="display: none;">
|
|
1744
|
-
<fig-dropdown class="fig-fill-picker-camera-select" full ${expAttr}>
|
|
2047
|
+
<fig-dropdown class="fig-fill-picker-camera-select" label="Camera" full ${expAttr}>
|
|
1745
2048
|
</fig-dropdown>
|
|
1746
2049
|
</fig-field>
|
|
1747
|
-
<fig-video class="fig-fill-picker-webcam-preview" aspect-ratio="1/1" fit="cover" checkerboard="true" autoplay="true" muted="true">
|
|
2050
|
+
<fig-video class="fig-fill-picker-webcam-preview" aria-label="Webcam preview" aspect-ratio="1/1" fit="cover" checkerboard="true" autoplay="true" muted="true">
|
|
1748
2051
|
<video class="fig-fill-picker-webcam-video" autoplay muted playsinline></video>
|
|
1749
2052
|
<div class="fig-fill-picker-webcam-status">
|
|
1750
2053
|
<span>Camera access required</span>
|
|
@@ -2243,7 +2546,12 @@ class FigFillPicker extends HTMLElement {
|
|
|
2243
2546
|
}
|
|
2244
2547
|
break;
|
|
2245
2548
|
case "disabled":
|
|
2246
|
-
|
|
2549
|
+
this.#syncTriggerA11y();
|
|
2550
|
+
break;
|
|
2551
|
+
case "aria-label":
|
|
2552
|
+
case "aria-labelledby":
|
|
2553
|
+
case "aria-describedby":
|
|
2554
|
+
this.#syncTriggerA11y();
|
|
2247
2555
|
break;
|
|
2248
2556
|
}
|
|
2249
2557
|
}
|