@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.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import "./fig-layer.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Generates a unique ID string using timestamp and random values
|
|
3
5
|
* @returns {string} A unique identifier
|
|
@@ -132,15 +134,20 @@ function figSupportsPopover() {
|
|
|
132
134
|
class FigButton extends HTMLElement {
|
|
133
135
|
type;
|
|
134
136
|
#selected;
|
|
137
|
+
#a11yAttributes = ["aria-label", "aria-labelledby", "aria-describedby", "title"];
|
|
138
|
+
#boundHandleControlKeydown = this.#handleControlKeydown.bind(this);
|
|
135
139
|
constructor() {
|
|
136
140
|
super();
|
|
137
141
|
this.attachShadow({ mode: "open", delegatesFocus: true });
|
|
138
142
|
}
|
|
139
143
|
connectedCallback() {
|
|
140
144
|
this.type = this.getAttribute("type") || "button";
|
|
145
|
+
const isControlWrapper = this.type === "select" || this.type === "upload";
|
|
146
|
+
const controlTag = isControlWrapper ? "span" : "button";
|
|
147
|
+
const typeAttr = isControlWrapper ? "" : ` type="${this.type}"`;
|
|
141
148
|
this.shadowRoot.innerHTML = `
|
|
142
149
|
<style>
|
|
143
|
-
button, button:hover, button:active {
|
|
150
|
+
button, button:hover, button:active, .fig-button-control {
|
|
144
151
|
padding: 0 var(--spacer-2);
|
|
145
152
|
appearance: none;
|
|
146
153
|
display: flex;
|
|
@@ -162,36 +169,38 @@ class FigButton extends HTMLElement {
|
|
|
162
169
|
width: 100%;
|
|
163
170
|
min-width: 0;
|
|
164
171
|
}
|
|
165
|
-
:host([size="large"]) button
|
|
172
|
+
:host([size="large"]) button,
|
|
173
|
+
:host([size="large"]) .fig-button-control {
|
|
166
174
|
height: var(--spacer-5);
|
|
167
175
|
}
|
|
168
|
-
:host([size="large"][icon]) button
|
|
176
|
+
:host([size="large"][icon]) button,
|
|
177
|
+
:host([size="large"][icon]) .fig-button-control {
|
|
169
178
|
padding: 0;
|
|
170
179
|
}
|
|
171
180
|
</style>
|
|
172
|
-
|
|
181
|
+
<${controlTag} class="fig-button-control"${typeAttr}>
|
|
173
182
|
<slot></slot>
|
|
174
|
-
|
|
183
|
+
</${controlTag}>
|
|
175
184
|
`;
|
|
176
185
|
|
|
177
186
|
this.#selected =
|
|
178
187
|
this.hasAttribute("selected") &&
|
|
179
188
|
this.getAttribute("selected") !== "false";
|
|
180
189
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
190
|
+
this.button = this.shadowRoot.querySelector("button, .fig-button-control");
|
|
191
|
+
this.#syncButtonAttributes();
|
|
192
|
+
this.button.addEventListener("click", this.#handleClick.bind(this));
|
|
184
193
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
});
|
|
191
|
-
this.button.addEventListener("blur", () => {
|
|
192
|
-
this.removeAttribute("data-focus-visible");
|
|
193
|
-
});
|
|
194
|
+
// Forward focus-visible state to host element
|
|
195
|
+
this.button.addEventListener("focus", () => {
|
|
196
|
+
if (this.button.matches(":focus-visible")) {
|
|
197
|
+
this.setAttribute("data-focus-visible", "");
|
|
198
|
+
}
|
|
194
199
|
});
|
|
200
|
+
this.button.addEventListener("blur", () => {
|
|
201
|
+
this.removeAttribute("data-focus-visible");
|
|
202
|
+
});
|
|
203
|
+
this.addEventListener("keydown", this.#boundHandleControlKeydown);
|
|
195
204
|
}
|
|
196
205
|
|
|
197
206
|
get type() {
|
|
@@ -207,7 +216,12 @@ class FigButton extends HTMLElement {
|
|
|
207
216
|
this.setAttribute("selected", value);
|
|
208
217
|
}
|
|
209
218
|
|
|
219
|
+
#isDisabled() {
|
|
220
|
+
return this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
221
|
+
}
|
|
222
|
+
|
|
210
223
|
#handleClick() {
|
|
224
|
+
if (this.#isDisabled()) return;
|
|
211
225
|
if (this.type === "toggle") {
|
|
212
226
|
this.toggleAttribute("selected", !this.hasAttribute("selected"));
|
|
213
227
|
}
|
|
@@ -229,28 +243,103 @@ class FigButton extends HTMLElement {
|
|
|
229
243
|
}
|
|
230
244
|
}
|
|
231
245
|
}
|
|
246
|
+
#getSlottedControl() {
|
|
247
|
+
if (this.type === "select") {
|
|
248
|
+
return this.querySelector("select, fig-dropdown");
|
|
249
|
+
}
|
|
250
|
+
if (this.type === "upload") {
|
|
251
|
+
return this.querySelector('input[type="file"], input');
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
#getSlottedSelect() {
|
|
256
|
+
const control = this.#getSlottedControl();
|
|
257
|
+
if (control instanceof HTMLSelectElement) return control;
|
|
258
|
+
if (control?.tagName === "FIG-DROPDOWN") {
|
|
259
|
+
return control.select || control.querySelector?.("select") || null;
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
#handleControlKeydown(e) {
|
|
264
|
+
if (this.type !== "select") return;
|
|
265
|
+
if (e.key !== "Enter") return;
|
|
266
|
+
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
|
267
|
+
const select = this.#getSlottedSelect();
|
|
268
|
+
if (!select || select.disabled || select.multiple) return;
|
|
269
|
+
if (typeof select.showPicker !== "function") return;
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
try {
|
|
272
|
+
select.showPicker();
|
|
273
|
+
} catch {
|
|
274
|
+
// showPicker can be blocked outside trusted user activation.
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
#syncPressedState() {
|
|
278
|
+
if (!this.button) return;
|
|
279
|
+
if (this.type !== "toggle") {
|
|
280
|
+
this.removeAttribute("aria-pressed");
|
|
281
|
+
this.button.removeAttribute("aria-pressed");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const pressed = this.hasAttribute("selected") && this.getAttribute("selected") !== "false";
|
|
285
|
+
this.setAttribute("aria-pressed", pressed ? "true" : "false");
|
|
286
|
+
this.button.setAttribute("aria-pressed", pressed ? "true" : "false");
|
|
287
|
+
}
|
|
288
|
+
#syncA11yAttributes() {
|
|
289
|
+
if (!this.button) return;
|
|
290
|
+
if (!(this.button instanceof HTMLButtonElement)) return;
|
|
291
|
+
this.#a11yAttributes.forEach((name) => {
|
|
292
|
+
const value = this.getAttribute(name);
|
|
293
|
+
if (value === null) {
|
|
294
|
+
this.button.removeAttribute(name);
|
|
295
|
+
} else {
|
|
296
|
+
this.button.setAttribute(name, value);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
#syncButtonAttributes() {
|
|
301
|
+
if (!this.button) return;
|
|
302
|
+
const disabled =
|
|
303
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
304
|
+
this.disabled = disabled;
|
|
305
|
+
if (this.button instanceof HTMLButtonElement) {
|
|
306
|
+
this.button.disabled = disabled;
|
|
307
|
+
this.button.type = this.type;
|
|
308
|
+
this.button.setAttribute("type", this.type);
|
|
309
|
+
}
|
|
310
|
+
this.#syncA11yAttributes();
|
|
311
|
+
this.#syncPressedState();
|
|
312
|
+
}
|
|
232
313
|
static get observedAttributes() {
|
|
233
|
-
return [
|
|
314
|
+
return [
|
|
315
|
+
"disabled",
|
|
316
|
+
"selected",
|
|
317
|
+
"type",
|
|
318
|
+
"aria-label",
|
|
319
|
+
"aria-labelledby",
|
|
320
|
+
"aria-describedby",
|
|
321
|
+
"title",
|
|
322
|
+
];
|
|
234
323
|
}
|
|
235
324
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
this.#selected = newValue === "true";
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
325
|
+
if (oldValue === newValue) return;
|
|
326
|
+
switch (name) {
|
|
327
|
+
case "disabled":
|
|
328
|
+
case "type":
|
|
329
|
+
this.#syncButtonAttributes();
|
|
330
|
+
break;
|
|
331
|
+
case "selected":
|
|
332
|
+
this.#selected = newValue !== null && newValue !== "false";
|
|
333
|
+
this.#syncPressedState();
|
|
334
|
+
break;
|
|
335
|
+
default:
|
|
336
|
+
this.#syncA11yAttributes();
|
|
337
|
+
break;
|
|
252
338
|
}
|
|
253
339
|
}
|
|
340
|
+
disconnectedCallback() {
|
|
341
|
+
this.removeEventListener("keydown", this.#boundHandleControlKeydown);
|
|
342
|
+
}
|
|
254
343
|
}
|
|
255
344
|
customElements.define("fig-button", FigButton);
|
|
256
345
|
|
|
@@ -264,6 +353,7 @@ class FigDropdown extends HTMLElement {
|
|
|
264
353
|
#selectedValue = null; // Stores last selected value for dropdown type
|
|
265
354
|
#boundHandleSelectInput;
|
|
266
355
|
#boundHandleSelectChange;
|
|
356
|
+
#boundHandleSelectKeydown;
|
|
267
357
|
#selectedContentEnabled = false;
|
|
268
358
|
#selectedContentEl = null;
|
|
269
359
|
|
|
@@ -281,6 +371,7 @@ class FigDropdown extends HTMLElement {
|
|
|
281
371
|
this.attachShadow({ mode: "open" });
|
|
282
372
|
this.#boundHandleSelectInput = this.#handleSelectInput.bind(this);
|
|
283
373
|
this.#boundHandleSelectChange = this.#handleSelectChange.bind(this);
|
|
374
|
+
this.#boundHandleSelectKeydown = this.#handleSelectKeydown.bind(this);
|
|
284
375
|
this.#boundSlotChange = this.slotChange.bind(this);
|
|
285
376
|
}
|
|
286
377
|
|
|
@@ -333,6 +424,7 @@ class FigDropdown extends HTMLElement {
|
|
|
333
424
|
#addEventListeners() {
|
|
334
425
|
this.select.addEventListener("input", this.#boundHandleSelectInput);
|
|
335
426
|
this.select.addEventListener("change", this.#boundHandleSelectChange);
|
|
427
|
+
this.select.addEventListener("keydown", this.#boundHandleSelectKeydown);
|
|
336
428
|
}
|
|
337
429
|
|
|
338
430
|
#hasPersistentControl(optionEl) {
|
|
@@ -360,6 +452,7 @@ class FigDropdown extends HTMLElement {
|
|
|
360
452
|
|
|
361
453
|
this.#label = this.getAttribute("label") || this.#label;
|
|
362
454
|
this.select.setAttribute("aria-label", this.#label);
|
|
455
|
+
this.#syncDisabled();
|
|
363
456
|
|
|
364
457
|
this.appendChild(this.select);
|
|
365
458
|
this.shadowRoot.appendChild(this.optionsSlot);
|
|
@@ -448,6 +541,21 @@ class FigDropdown extends HTMLElement {
|
|
|
448
541
|
);
|
|
449
542
|
}
|
|
450
543
|
|
|
544
|
+
#handleSelectKeydown(e) {
|
|
545
|
+
if (this.closest('fig-button[type="select"]')) return;
|
|
546
|
+
if (e.key !== "Enter" || e.defaultPrevented) return;
|
|
547
|
+
if (this.#selectedContentEnabled && this.select.matches(":open")) return;
|
|
548
|
+
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
|
549
|
+
if (this.select.disabled || this.select.multiple) return;
|
|
550
|
+
if (typeof this.select.showPicker !== "function") return;
|
|
551
|
+
e.preventDefault();
|
|
552
|
+
try {
|
|
553
|
+
this.select.showPicker();
|
|
554
|
+
} catch {
|
|
555
|
+
// showPicker can be unavailable during non-user-initiated key events.
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
451
559
|
focus() {
|
|
452
560
|
this.select.focus();
|
|
453
561
|
}
|
|
@@ -469,7 +577,12 @@ class FigDropdown extends HTMLElement {
|
|
|
469
577
|
this.setAttribute("value", value);
|
|
470
578
|
}
|
|
471
579
|
static get observedAttributes() {
|
|
472
|
-
return ["value", "type", "experimental"];
|
|
580
|
+
return ["value", "type", "experimental", "label", "disabled"];
|
|
581
|
+
}
|
|
582
|
+
#syncDisabled() {
|
|
583
|
+
const disabled =
|
|
584
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
585
|
+
this.select.disabled = disabled;
|
|
473
586
|
}
|
|
474
587
|
#syncSelectedValue(value) {
|
|
475
588
|
// For dropdown type, don't sync the visual selection - it should always show the hidden placeholder
|
|
@@ -499,12 +612,16 @@ class FigDropdown extends HTMLElement {
|
|
|
499
612
|
this.#label = newValue;
|
|
500
613
|
this.select.setAttribute("aria-label", this.#label);
|
|
501
614
|
}
|
|
615
|
+
if (name === "disabled") {
|
|
616
|
+
this.#syncDisabled();
|
|
617
|
+
}
|
|
502
618
|
}
|
|
503
619
|
|
|
504
620
|
disconnectedCallback() {
|
|
505
621
|
this.optionsSlot.removeEventListener("slotchange", this.#boundSlotChange);
|
|
506
622
|
this.select.removeEventListener("input", this.#boundHandleSelectInput);
|
|
507
623
|
this.select.removeEventListener("change", this.#boundHandleSelectChange);
|
|
624
|
+
this.select.removeEventListener("keydown", this.#boundHandleSelectKeydown);
|
|
508
625
|
}
|
|
509
626
|
}
|
|
510
627
|
|
|
@@ -533,6 +650,7 @@ class FigTooltip extends HTMLElement {
|
|
|
533
650
|
#boundHandleTouchEnd;
|
|
534
651
|
#boundHandleTouchCancel;
|
|
535
652
|
#boundHandleDialogClose;
|
|
653
|
+
#boundHandleEscape;
|
|
536
654
|
#parentDialog = null;
|
|
537
655
|
#touchTimeout;
|
|
538
656
|
#isTouching = false;
|
|
@@ -550,6 +668,7 @@ class FigTooltip extends HTMLElement {
|
|
|
550
668
|
this.#boundHandleTouchMove = this.#handleTouchMove.bind(this);
|
|
551
669
|
this.#boundHandleTouchEnd = this.#handleTouchEnd.bind(this);
|
|
552
670
|
this.#boundHandleTouchCancel = this.#handleTouchCancel.bind(this);
|
|
671
|
+
this.#boundHandleEscape = this.#handleEscape.bind(this);
|
|
553
672
|
this.#boundHandleDialogClose = () => {
|
|
554
673
|
clearTimeout(this.timeout);
|
|
555
674
|
this.destroy();
|
|
@@ -573,6 +692,7 @@ class FigTooltip extends HTMLElement {
|
|
|
573
692
|
this.#boundHideOnChromeOpen,
|
|
574
693
|
true,
|
|
575
694
|
);
|
|
695
|
+
document.removeEventListener("keydown", this.#boundHandleEscape, true);
|
|
576
696
|
if (this.#parentDialog) {
|
|
577
697
|
this.#parentDialog.removeEventListener("close", this.#boundHandleDialogClose);
|
|
578
698
|
this.#parentDialog = null;
|
|
@@ -620,7 +740,7 @@ class FigTooltip extends HTMLElement {
|
|
|
620
740
|
this.popup.setAttribute("variant", "tooltip");
|
|
621
741
|
this.popup.setAttribute("data-tooltip-managed", "");
|
|
622
742
|
this.popup.setAttribute("role", "tooltip");
|
|
623
|
-
this.popup.setAttribute("closedby", "
|
|
743
|
+
this.popup.setAttribute("closedby", "closerequest");
|
|
624
744
|
if (supportsPopover) this.popup.setAttribute("popover", "manual");
|
|
625
745
|
|
|
626
746
|
const tooltipId = figUniqueId();
|
|
@@ -708,6 +828,7 @@ class FigTooltip extends HTMLElement {
|
|
|
708
828
|
}
|
|
709
829
|
|
|
710
830
|
document.addEventListener("mousedown", this.#boundHideOnChromeOpen, true);
|
|
831
|
+
document.addEventListener("keydown", this.#boundHandleEscape, true);
|
|
711
832
|
}
|
|
712
833
|
|
|
713
834
|
get #showPersisted() {
|
|
@@ -804,6 +925,13 @@ class FigTooltip extends HTMLElement {
|
|
|
804
925
|
}
|
|
805
926
|
}
|
|
806
927
|
|
|
928
|
+
#handleEscape(event) {
|
|
929
|
+
if ((!this.isOpen && !this.popup) || event.key !== "Escape") return;
|
|
930
|
+
event.preventDefault();
|
|
931
|
+
this.hidePopup();
|
|
932
|
+
this.firstElementChild?.focus?.();
|
|
933
|
+
}
|
|
934
|
+
|
|
807
935
|
static get observedAttributes() {
|
|
808
936
|
return ["action", "delay", "open", "pointer", "show", "text", "theme"];
|
|
809
937
|
}
|
|
@@ -912,7 +1040,7 @@ class FigTooltip extends HTMLElement {
|
|
|
912
1040
|
popup.setAttribute("variant", "tooltip");
|
|
913
1041
|
popup.setAttribute("data-tooltip-managed", "");
|
|
914
1042
|
popup.setAttribute("role", "tooltip");
|
|
915
|
-
popup.setAttribute("closedby", "
|
|
1043
|
+
popup.setAttribute("closedby", "closerequest");
|
|
916
1044
|
if (supportsPopover) popup.setAttribute("popover", "manual");
|
|
917
1045
|
const content = document.createElement("span");
|
|
918
1046
|
content.innerText = text;
|
|
@@ -1068,6 +1196,8 @@ class FigDialog extends HTMLDialogElement {
|
|
|
1068
1196
|
this._boundIframeMessage = this._handleIframeMessage.bind(this);
|
|
1069
1197
|
this._boundContentMutation = this._scheduleAutoResize.bind(this);
|
|
1070
1198
|
this._boundContentResize = this._scheduleAutoResize.bind(this);
|
|
1199
|
+
this._boundRestoreFocus = this._restoreFocus.bind(this);
|
|
1200
|
+
this._previousFocus = null;
|
|
1071
1201
|
}
|
|
1072
1202
|
|
|
1073
1203
|
get autoresize() {
|
|
@@ -1093,6 +1223,7 @@ class FigDialog extends HTMLDialogElement {
|
|
|
1093
1223
|
this._setupDragListeners();
|
|
1094
1224
|
this._applyPosition();
|
|
1095
1225
|
this._syncAutoResize();
|
|
1226
|
+
this._syncA11y();
|
|
1096
1227
|
});
|
|
1097
1228
|
|
|
1098
1229
|
window.addEventListener("message", this._boundIframeMessage);
|
|
@@ -1106,6 +1237,36 @@ class FigDialog extends HTMLDialogElement {
|
|
|
1106
1237
|
});
|
|
1107
1238
|
window.removeEventListener("message", this._boundIframeMessage);
|
|
1108
1239
|
this._teardownAutoResize();
|
|
1240
|
+
this.removeEventListener("close", this._boundRestoreFocus);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
_captureFocusBeforeOpen() {
|
|
1244
|
+
const active = document.activeElement;
|
|
1245
|
+
this._previousFocus =
|
|
1246
|
+
active instanceof HTMLElement && active !== document.body && !this.contains(active)
|
|
1247
|
+
? active
|
|
1248
|
+
: null;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
_restoreFocus() {
|
|
1252
|
+
const target = this._previousFocus;
|
|
1253
|
+
this._previousFocus = null;
|
|
1254
|
+
if (!target?.isConnected) return;
|
|
1255
|
+
const active = document.activeElement;
|
|
1256
|
+
if (active && active !== document.body && !this.contains(active)) return;
|
|
1257
|
+
requestAnimationFrame(() => target.focus?.());
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
show() {
|
|
1261
|
+
this._captureFocusBeforeOpen();
|
|
1262
|
+
this.addEventListener("close", this._boundRestoreFocus, { once: true });
|
|
1263
|
+
return super.show();
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
showModal() {
|
|
1267
|
+
this._captureFocusBeforeOpen();
|
|
1268
|
+
this.addEventListener("close", this._boundRestoreFocus, { once: true });
|
|
1269
|
+
return super.showModal();
|
|
1109
1270
|
}
|
|
1110
1271
|
|
|
1111
1272
|
_handleIframeMessage(event) {
|
|
@@ -1246,6 +1407,7 @@ class FigDialog extends HTMLDialogElement {
|
|
|
1246
1407
|
const btn = document.createElement("fig-button");
|
|
1247
1408
|
btn.setAttribute("variant", "ghost");
|
|
1248
1409
|
btn.setAttribute("icon", "");
|
|
1410
|
+
btn.setAttribute("aria-label", "Close dialog");
|
|
1249
1411
|
btn.setAttribute("close-dialog", "");
|
|
1250
1412
|
btn.appendChild(createFigIcon("close"));
|
|
1251
1413
|
tooltip.appendChild(btn);
|
|
@@ -1256,11 +1418,25 @@ class FigDialog extends HTMLDialogElement {
|
|
|
1256
1418
|
|
|
1257
1419
|
_addCloseListeners() {
|
|
1258
1420
|
this.querySelectorAll("fig-button[close-dialog]").forEach((button) => {
|
|
1421
|
+
if (!button.hasAttribute("aria-label")) {
|
|
1422
|
+
button.setAttribute("aria-label", "Close dialog");
|
|
1423
|
+
}
|
|
1259
1424
|
button.removeEventListener("click", this._boundClose);
|
|
1260
1425
|
button.addEventListener("click", this._boundClose);
|
|
1261
1426
|
});
|
|
1262
1427
|
}
|
|
1263
1428
|
|
|
1429
|
+
_syncA11y() {
|
|
1430
|
+
if (!this.hasAttribute("aria-label") && !this.hasAttribute("aria-labelledby")) {
|
|
1431
|
+
const heading = this.querySelector("fig-header[dialog-header] h1, fig-header[dialog-header] h2, fig-header[dialog-header] h3, fig-header[dialog-header] h4, fig-header[dialog-header] h5, fig-header[dialog-header] h6");
|
|
1432
|
+
if (heading) {
|
|
1433
|
+
const id = heading.getAttribute("id") || figUniqueId();
|
|
1434
|
+
heading.setAttribute("id", id);
|
|
1435
|
+
this.setAttribute("aria-labelledby", id);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1264
1440
|
_applyPosition() {
|
|
1265
1441
|
const position = this.getAttribute("position") || "";
|
|
1266
1442
|
|
|
@@ -1479,6 +1655,8 @@ class FigDialog extends HTMLDialogElement {
|
|
|
1479
1655
|
"resizable",
|
|
1480
1656
|
"closedby",
|
|
1481
1657
|
"autoresize",
|
|
1658
|
+
"aria-label",
|
|
1659
|
+
"aria-labelledby",
|
|
1482
1660
|
];
|
|
1483
1661
|
}
|
|
1484
1662
|
|
|
@@ -1525,11 +1703,125 @@ class FigDialog extends HTMLDialogElement {
|
|
|
1525
1703
|
if (autoHeader) {
|
|
1526
1704
|
autoHeader.textContent = newValue || "Dialog";
|
|
1527
1705
|
}
|
|
1706
|
+
this._syncA11y();
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
if (name === "aria-label" || name === "aria-labelledby") {
|
|
1710
|
+
this._syncA11y();
|
|
1528
1711
|
}
|
|
1529
1712
|
}
|
|
1530
1713
|
}
|
|
1531
1714
|
figDefineCustomizedBuiltIn("fig-dialog", FigDialog, { extends: "dialog" });
|
|
1532
1715
|
|
|
1716
|
+
/* Toast */
|
|
1717
|
+
class FigToast extends HTMLDialogElement {
|
|
1718
|
+
constructor() {
|
|
1719
|
+
super();
|
|
1720
|
+
this._figInit();
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
_figInit() {
|
|
1724
|
+
if (this._figInitialized) return;
|
|
1725
|
+
this._figInitialized = true;
|
|
1726
|
+
this._defaultOffset = 16;
|
|
1727
|
+
this._autoCloseTimer = null;
|
|
1728
|
+
this._boundHandleClose = this.handleClose.bind(this);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
connectedCallback() {
|
|
1732
|
+
this._figInit();
|
|
1733
|
+
if (!this.hasAttribute("theme")) this.setAttribute("theme", "dark");
|
|
1734
|
+
this.syncLiveRegion();
|
|
1735
|
+
this.addCloseListeners();
|
|
1736
|
+
this.applyPosition();
|
|
1737
|
+
if (this.hasAttribute("open") && this.getAttribute("open") !== "false") {
|
|
1738
|
+
this.showToast();
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
disconnectedCallback() {
|
|
1743
|
+
this._figInit();
|
|
1744
|
+
this.clearAutoClose();
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
addCloseListeners() {
|
|
1748
|
+
this.querySelectorAll("[close-toast]").forEach((button) => {
|
|
1749
|
+
if (!button.hasAttribute("aria-label")) button.setAttribute("aria-label", "Close notification");
|
|
1750
|
+
button.removeEventListener("click", this._boundHandleClose);
|
|
1751
|
+
button.addEventListener("click", this._boundHandleClose);
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
handleClose() {
|
|
1756
|
+
this.hideToast();
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
applyPosition() {
|
|
1760
|
+
this.style.position = "fixed";
|
|
1761
|
+
this.style.margin = "0";
|
|
1762
|
+
this.style.top = "auto";
|
|
1763
|
+
this.style.bottom = `${parseInt(this.getAttribute("offset") ?? this._defaultOffset)}px`;
|
|
1764
|
+
this.style.left = "50%";
|
|
1765
|
+
this.style.right = "auto";
|
|
1766
|
+
this.style.transform = "translateX(-50%)";
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
syncLiveRegion() {
|
|
1770
|
+
const assertive =
|
|
1771
|
+
this.getAttribute("live") === "assertive" ||
|
|
1772
|
+
this.getAttribute("theme") === "danger";
|
|
1773
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", assertive ? "alert" : "status");
|
|
1774
|
+
if (!this.hasAttribute("aria-live")) this.setAttribute("aria-live", assertive ? "assertive" : "polite");
|
|
1775
|
+
if (!this.hasAttribute("aria-atomic")) this.setAttribute("aria-atomic", "true");
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
startAutoClose() {
|
|
1779
|
+
this.clearAutoClose();
|
|
1780
|
+
const duration = parseInt(this.getAttribute("duration") ?? "5000");
|
|
1781
|
+
if (duration > 0) {
|
|
1782
|
+
this._autoCloseTimer = setTimeout(() => this.hideToast(), duration);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
clearAutoClose() {
|
|
1787
|
+
if (this._autoCloseTimer) {
|
|
1788
|
+
clearTimeout(this._autoCloseTimer);
|
|
1789
|
+
this._autoCloseTimer = null;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
showToast() {
|
|
1794
|
+
this.syncLiveRegion();
|
|
1795
|
+
if (!this.open) this.show();
|
|
1796
|
+
this.applyPosition();
|
|
1797
|
+
this.startAutoClose();
|
|
1798
|
+
this.dispatchEvent(new CustomEvent("toast-show", { bubbles: true }));
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
hideToast() {
|
|
1802
|
+
this.clearAutoClose();
|
|
1803
|
+
if (this.open) this.close();
|
|
1804
|
+
this.dispatchEvent(new CustomEvent("toast-hide", { bubbles: true }));
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
static get observedAttributes() {
|
|
1808
|
+
return ["duration", "offset", "open", "theme", "live"];
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1812
|
+
this._figInit();
|
|
1813
|
+
if (oldValue === newValue) return;
|
|
1814
|
+
if (!this.isConnected) return;
|
|
1815
|
+
if (name === "offset") this.applyPosition();
|
|
1816
|
+
if (name === "open") {
|
|
1817
|
+
if (newValue !== null && newValue !== "false") this.showToast();
|
|
1818
|
+
else this.hideToast();
|
|
1819
|
+
}
|
|
1820
|
+
if (name === "theme" || name === "live") this.syncLiveRegion();
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
figDefineCustomizedBuiltIn("fig-toast", FigToast, { extends: "dialog" });
|
|
1824
|
+
|
|
1533
1825
|
/* Popup */
|
|
1534
1826
|
/**
|
|
1535
1827
|
* A floating popup foundation component based on <dialog>.
|
|
@@ -1562,14 +1854,17 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1562
1854
|
_boundPointerMove;
|
|
1563
1855
|
_boundPointerUp;
|
|
1564
1856
|
_wasDragged = false;
|
|
1857
|
+
_previousFocus = null;
|
|
1858
|
+
_boundDocumentKeydown;
|
|
1565
1859
|
|
|
1566
1860
|
constructor() {
|
|
1567
1861
|
super();
|
|
1568
1862
|
this._boundReposition = this.queueReposition.bind(this);
|
|
1569
1863
|
this._boundScroll = (e) => {
|
|
1864
|
+
const target = e.target;
|
|
1570
1865
|
if (
|
|
1571
1866
|
this.open &&
|
|
1572
|
-
!this.contains(
|
|
1867
|
+
(!(target instanceof Node) || !this.contains(target)) &&
|
|
1573
1868
|
this.shouldAutoReposition()
|
|
1574
1869
|
) {
|
|
1575
1870
|
this.queueReposition();
|
|
@@ -1579,6 +1874,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1579
1874
|
this._boundPointerDown = this.handlePointerDown.bind(this);
|
|
1580
1875
|
this._boundPointerMove = this.handlePointerMove.bind(this);
|
|
1581
1876
|
this._boundPointerUp = this.handlePointerUp.bind(this);
|
|
1877
|
+
this._boundDocumentKeydown = this.handleDocumentKeydown.bind(this);
|
|
1582
1878
|
}
|
|
1583
1879
|
|
|
1584
1880
|
ensureInitialized() {
|
|
@@ -1603,15 +1899,17 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1603
1899
|
this._dragOffset = { x: 0, y: 0 };
|
|
1604
1900
|
if (typeof this._dragThreshold !== "number") this._dragThreshold = 3;
|
|
1605
1901
|
if (typeof this._wasDragged === "undefined") this._wasDragged = false;
|
|
1902
|
+
if (typeof this._previousFocus === "undefined") this._previousFocus = null;
|
|
1606
1903
|
|
|
1607
1904
|
if (typeof this._boundReposition !== "function") {
|
|
1608
1905
|
this._boundReposition = this.queueReposition.bind(this);
|
|
1609
1906
|
}
|
|
1610
1907
|
if (typeof this._boundScroll !== "function") {
|
|
1611
1908
|
this._boundScroll = (e) => {
|
|
1909
|
+
const target = e.target;
|
|
1612
1910
|
if (
|
|
1613
1911
|
this.open &&
|
|
1614
|
-
!this.contains(
|
|
1912
|
+
(!(target instanceof Node) || !this.contains(target)) &&
|
|
1615
1913
|
this.shouldAutoReposition()
|
|
1616
1914
|
) {
|
|
1617
1915
|
this.queueReposition();
|
|
@@ -1630,6 +1928,9 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1630
1928
|
if (typeof this._boundPointerUp !== "function") {
|
|
1631
1929
|
this._boundPointerUp = this.handlePointerUp.bind(this);
|
|
1632
1930
|
}
|
|
1931
|
+
if (typeof this._boundDocumentKeydown !== "function") {
|
|
1932
|
+
this._boundDocumentKeydown = this.handleDocumentKeydown.bind(this);
|
|
1933
|
+
}
|
|
1633
1934
|
}
|
|
1634
1935
|
|
|
1635
1936
|
static get observedAttributes() {
|
|
@@ -1699,7 +2000,10 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1699
2000
|
this.setAttribute("position", "top center");
|
|
1700
2001
|
}
|
|
1701
2002
|
if (!this.hasAttribute("role")) {
|
|
1702
|
-
this.setAttribute(
|
|
2003
|
+
this.setAttribute(
|
|
2004
|
+
"role",
|
|
2005
|
+
this.getAttribute("variant") === "tooltip" ? "tooltip" : "dialog",
|
|
2006
|
+
);
|
|
1703
2007
|
}
|
|
1704
2008
|
if (!this.hasAttribute("closedby")) {
|
|
1705
2009
|
this.setAttribute("closedby", "any");
|
|
@@ -1735,6 +2039,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1735
2039
|
this._boundOutsidePointerDown,
|
|
1736
2040
|
true,
|
|
1737
2041
|
);
|
|
2042
|
+
document.removeEventListener("keydown", this._boundDocumentKeydown, true);
|
|
1738
2043
|
if (this._rafId !== null) {
|
|
1739
2044
|
cancelAnimationFrame(this._rafId);
|
|
1740
2045
|
this._rafId = null;
|
|
@@ -1780,6 +2085,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1780
2085
|
this.style.inset = "auto";
|
|
1781
2086
|
this.style.margin = "0";
|
|
1782
2087
|
this.style.zIndex = String(figGetHighestZIndex() + 1);
|
|
2088
|
+
this.captureFocusBeforeOpen();
|
|
1783
2089
|
|
|
1784
2090
|
// When the popup opts into the native popover API, prefer showPopover()
|
|
1785
2091
|
// so the element is promoted into the browser's top layer (above any
|
|
@@ -1817,6 +2123,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1817
2123
|
this._boundOutsidePointerDown,
|
|
1818
2124
|
true,
|
|
1819
2125
|
);
|
|
2126
|
+
document.addEventListener("keydown", this._boundDocumentKeydown, true);
|
|
1820
2127
|
this._wasDragged = false;
|
|
1821
2128
|
this.queueReposition();
|
|
1822
2129
|
this._isPopupActive = true;
|
|
@@ -1826,6 +2133,10 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1826
2133
|
}
|
|
1827
2134
|
|
|
1828
2135
|
hidePopup() {
|
|
2136
|
+
const wasActive =
|
|
2137
|
+
this._isPopupActive ||
|
|
2138
|
+
this.matches?.(":open") ||
|
|
2139
|
+
this.matches?.(":popover-open");
|
|
1829
2140
|
const anchor = this.resolveAnchor();
|
|
1830
2141
|
if (anchor?.classList) anchor.classList.remove("has-popup-open");
|
|
1831
2142
|
|
|
@@ -1838,6 +2149,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1838
2149
|
this._boundOutsidePointerDown,
|
|
1839
2150
|
true,
|
|
1840
2151
|
);
|
|
2152
|
+
document.removeEventListener("keydown", this._boundDocumentKeydown, true);
|
|
1841
2153
|
|
|
1842
2154
|
if (
|
|
1843
2155
|
this.hasAttribute("popover") &&
|
|
@@ -1860,6 +2172,39 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1860
2172
|
// Ignore when dialog is not in an open state.
|
|
1861
2173
|
}
|
|
1862
2174
|
}
|
|
2175
|
+
if (wasActive) this.restoreFocusAfterClose();
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
shouldRestoreFocus() {
|
|
2179
|
+
return this.getAttribute("variant") !== "tooltip";
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
captureFocusBeforeOpen() {
|
|
2183
|
+
if (!this.shouldRestoreFocus()) return;
|
|
2184
|
+
const active = document.activeElement;
|
|
2185
|
+
this._previousFocus =
|
|
2186
|
+
active instanceof HTMLElement && active !== document.body && !this.contains(active)
|
|
2187
|
+
? active
|
|
2188
|
+
: null;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
restoreFocusAfterClose() {
|
|
2192
|
+
if (!this.shouldRestoreFocus()) {
|
|
2193
|
+
this._previousFocus = null;
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
const anchor = this.resolveAnchor();
|
|
2197
|
+
const target =
|
|
2198
|
+
this._previousFocus?.isConnected
|
|
2199
|
+
? this._previousFocus
|
|
2200
|
+
: anchor instanceof HTMLElement
|
|
2201
|
+
? anchor
|
|
2202
|
+
: null;
|
|
2203
|
+
this._previousFocus = null;
|
|
2204
|
+
if (!target?.isConnected) return;
|
|
2205
|
+
const active = document.activeElement;
|
|
2206
|
+
if (active && active !== document.body && !this.contains(active)) return;
|
|
2207
|
+
requestAnimationFrame(() => target.focus?.());
|
|
1863
2208
|
}
|
|
1864
2209
|
|
|
1865
2210
|
get autoresize() {
|
|
@@ -2002,6 +2347,27 @@ class FigPopup extends HTMLDialogElement {
|
|
|
2002
2347
|
this.open = false;
|
|
2003
2348
|
}
|
|
2004
2349
|
|
|
2350
|
+
handleDocumentKeydown(event) {
|
|
2351
|
+
if (event.key !== "Escape" || event.defaultPrevented) return;
|
|
2352
|
+
if (!this.open) return;
|
|
2353
|
+
if (this.getAttribute("role") === "menu") return;
|
|
2354
|
+
const closedby = this.getAttribute("closedby");
|
|
2355
|
+
if (closedby === "none") return;
|
|
2356
|
+
const openPopups = Array.from(
|
|
2357
|
+
document.querySelectorAll('dialog[is="fig-popup"][open]'),
|
|
2358
|
+
).filter((popup) => popup.open);
|
|
2359
|
+
const topPopup = openPopups
|
|
2360
|
+
.map((popup) => ({
|
|
2361
|
+
popup,
|
|
2362
|
+
z: Number.parseInt(getComputedStyle(popup).zIndex || "0", 10) || 0,
|
|
2363
|
+
}))
|
|
2364
|
+
.sort((a, b) => a.z - b.z)
|
|
2365
|
+
.at(-1)?.popup;
|
|
2366
|
+
if (topPopup && topPopup !== this) return;
|
|
2367
|
+
event.preventDefault();
|
|
2368
|
+
this.open = false;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2005
2371
|
isInsideDescendantPopup(target) {
|
|
2006
2372
|
const targetDialog = target.closest?.('dialog[is="fig-popup"]');
|
|
2007
2373
|
if (!targetDialog || targetDialog === this) return false;
|
|
@@ -2676,14 +3042,20 @@ class FigTab extends HTMLElement {
|
|
|
2676
3042
|
connectedCallback() {
|
|
2677
3043
|
this.setAttribute("label", this.innerText);
|
|
2678
3044
|
this.setAttribute("role", "tab");
|
|
2679
|
-
this.setAttribute("tabindex", "
|
|
3045
|
+
if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "-1");
|
|
2680
3046
|
this.addEventListener("click", this.#boundHandleClick);
|
|
2681
3047
|
|
|
2682
3048
|
requestAnimationFrame(() => {
|
|
2683
3049
|
if (typeof this.getAttribute("content") === "string") {
|
|
2684
3050
|
this.content = document.querySelector(this.getAttribute("content"));
|
|
2685
3051
|
if (this.content) {
|
|
3052
|
+
const tabId = this.getAttribute("id") || figUniqueId();
|
|
3053
|
+
const panelId = this.content.getAttribute("id") || figUniqueId();
|
|
3054
|
+
this.setAttribute("id", tabId);
|
|
3055
|
+
this.content.setAttribute("id", panelId);
|
|
3056
|
+
this.setAttribute("aria-controls", panelId);
|
|
2686
3057
|
this.content.setAttribute("role", "tabpanel");
|
|
3058
|
+
this.content.setAttribute("aria-labelledby", tabId);
|
|
2687
3059
|
if (this.#selected) {
|
|
2688
3060
|
this.content.style.display = "block";
|
|
2689
3061
|
this.setAttribute("aria-selected", "true");
|
|
@@ -2713,16 +3085,27 @@ class FigTab extends HTMLElement {
|
|
|
2713
3085
|
}
|
|
2714
3086
|
|
|
2715
3087
|
static get observedAttributes() {
|
|
2716
|
-
return ["selected"];
|
|
3088
|
+
return ["selected", "disabled"];
|
|
2717
3089
|
}
|
|
2718
3090
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
2719
3091
|
if (name === "selected") {
|
|
2720
3092
|
this.#selected = newValue !== null && newValue !== "false";
|
|
2721
3093
|
this.setAttribute("aria-selected", this.#selected ? "true" : "false");
|
|
3094
|
+
this.setAttribute("tabindex", this.#selected ? "0" : "-1");
|
|
2722
3095
|
if (this?.content) {
|
|
2723
3096
|
this.content.style.display = this.#selected ? "block" : "none";
|
|
2724
3097
|
}
|
|
2725
3098
|
}
|
|
3099
|
+
if (name === "disabled") {
|
|
3100
|
+
const disabled = newValue !== null && newValue !== "false";
|
|
3101
|
+
if (disabled) {
|
|
3102
|
+
this.setAttribute("aria-disabled", "true");
|
|
3103
|
+
this.setAttribute("tabindex", "-1");
|
|
3104
|
+
} else {
|
|
3105
|
+
this.removeAttribute("aria-disabled");
|
|
3106
|
+
this.setAttribute("tabindex", this.#selected ? "0" : "-1");
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
2726
3109
|
}
|
|
2727
3110
|
}
|
|
2728
3111
|
customElements.define("fig-tab", FigTab);
|
|
@@ -2770,9 +3153,24 @@ class FigTabs extends HTMLElement {
|
|
|
2770
3153
|
} else {
|
|
2771
3154
|
tab.removeAttribute("disabled");
|
|
2772
3155
|
tab.removeAttribute("aria-disabled");
|
|
2773
|
-
tab.setAttribute("tabindex", "0");
|
|
2774
3156
|
}
|
|
2775
3157
|
});
|
|
3158
|
+
this.#syncTabIndexes();
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
#availableTabs() {
|
|
3162
|
+
return Array.from(this.querySelectorAll("fig-tab")).filter(
|
|
3163
|
+
(tab) => !tab.hasAttribute("disabled") || tab.getAttribute("disabled") === "false",
|
|
3164
|
+
);
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
#syncTabIndexes() {
|
|
3168
|
+
const tabs = Array.from(this.querySelectorAll("fig-tab"));
|
|
3169
|
+
const selected = tabs.find((tab) => tab.hasAttribute("selected")) || this.#availableTabs()[0];
|
|
3170
|
+
tabs.forEach((tab) => {
|
|
3171
|
+
const disabled = tab.hasAttribute("disabled") && tab.getAttribute("disabled") !== "false";
|
|
3172
|
+
tab.setAttribute("tabindex", !disabled && tab === selected ? "0" : "-1");
|
|
3173
|
+
});
|
|
2776
3174
|
}
|
|
2777
3175
|
|
|
2778
3176
|
disconnectedCallback() {
|
|
@@ -2781,9 +3179,10 @@ class FigTabs extends HTMLElement {
|
|
|
2781
3179
|
}
|
|
2782
3180
|
|
|
2783
3181
|
#handleKeyDown(event) {
|
|
2784
|
-
const tabs =
|
|
3182
|
+
const tabs = this.#availableTabs();
|
|
3183
|
+
if (!tabs.length) return;
|
|
2785
3184
|
const currentIndex = tabs.findIndex((tab) => tab.hasAttribute("selected"));
|
|
2786
|
-
let newIndex = currentIndex;
|
|
3185
|
+
let newIndex = currentIndex >= 0 ? currentIndex : 0;
|
|
2787
3186
|
|
|
2788
3187
|
switch (event.key) {
|
|
2789
3188
|
case "ArrowLeft":
|
|
@@ -2809,12 +3208,13 @@ class FigTabs extends HTMLElement {
|
|
|
2809
3208
|
}
|
|
2810
3209
|
|
|
2811
3210
|
if (newIndex !== currentIndex && tabs[newIndex]) {
|
|
2812
|
-
|
|
3211
|
+
this.querySelectorAll("fig-tab").forEach((tab) => tab.removeAttribute("selected"));
|
|
2813
3212
|
this.selectedTab = tabs[newIndex];
|
|
2814
3213
|
tabs[newIndex].setAttribute("selected", "true");
|
|
2815
3214
|
const val = tabs[newIndex].getAttribute("value");
|
|
2816
3215
|
if (val) this.setAttribute("value", val);
|
|
2817
3216
|
tabs[newIndex].focus();
|
|
3217
|
+
this.#syncTabIndexes();
|
|
2818
3218
|
}
|
|
2819
3219
|
}
|
|
2820
3220
|
|
|
@@ -2836,6 +3236,7 @@ class FigTabs extends HTMLElement {
|
|
|
2836
3236
|
tab.removeAttribute("selected");
|
|
2837
3237
|
}
|
|
2838
3238
|
}
|
|
3239
|
+
this.#syncTabIndexes();
|
|
2839
3240
|
}
|
|
2840
3241
|
|
|
2841
3242
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -2866,6 +3267,7 @@ class FigTabs extends HTMLElement {
|
|
|
2866
3267
|
}
|
|
2867
3268
|
const val = target.getAttribute("value");
|
|
2868
3269
|
if (val) this.setAttribute("value", val);
|
|
3270
|
+
this.#syncTabIndexes();
|
|
2869
3271
|
}
|
|
2870
3272
|
}
|
|
2871
3273
|
customElements.define("fig-tabs", FigTabs);
|
|
@@ -2885,6 +3287,9 @@ class FigSegment extends HTMLElement {
|
|
|
2885
3287
|
this.#boundHandleClick = this.handleClick.bind(this);
|
|
2886
3288
|
}
|
|
2887
3289
|
connectedCallback() {
|
|
3290
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "radio");
|
|
3291
|
+
if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "-1");
|
|
3292
|
+
this.#syncA11yState();
|
|
2888
3293
|
this.addEventListener("click", this.#boundHandleClick);
|
|
2889
3294
|
}
|
|
2890
3295
|
disconnectedCallback() {
|
|
@@ -2916,7 +3321,21 @@ class FigSegment extends HTMLElement {
|
|
|
2916
3321
|
this.setAttribute("selected", value);
|
|
2917
3322
|
}
|
|
2918
3323
|
static get observedAttributes() {
|
|
2919
|
-
return ["selected", "value"];
|
|
3324
|
+
return ["selected", "value", "disabled"];
|
|
3325
|
+
}
|
|
3326
|
+
#syncA11yState() {
|
|
3327
|
+
const selected =
|
|
3328
|
+
this.hasAttribute("selected") && this.getAttribute("selected") !== "false";
|
|
3329
|
+
const disabled =
|
|
3330
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
3331
|
+
this.setAttribute("aria-checked", selected ? "true" : "false");
|
|
3332
|
+
if (disabled) {
|
|
3333
|
+
this.setAttribute("aria-disabled", "true");
|
|
3334
|
+
this.setAttribute("tabindex", "-1");
|
|
3335
|
+
} else {
|
|
3336
|
+
this.removeAttribute("aria-disabled");
|
|
3337
|
+
this.setAttribute("tabindex", selected ? "0" : "-1");
|
|
3338
|
+
}
|
|
2920
3339
|
}
|
|
2921
3340
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
2922
3341
|
switch (name) {
|
|
@@ -2925,6 +3344,10 @@ class FigSegment extends HTMLElement {
|
|
|
2925
3344
|
break;
|
|
2926
3345
|
case "selected":
|
|
2927
3346
|
this.#selected = newValue;
|
|
3347
|
+
this.#syncA11yState();
|
|
3348
|
+
break;
|
|
3349
|
+
case "disabled":
|
|
3350
|
+
this.#syncA11yState();
|
|
2928
3351
|
break;
|
|
2929
3352
|
}
|
|
2930
3353
|
}
|
|
@@ -2941,6 +3364,7 @@ customElements.define("fig-segment", FigSegment);
|
|
|
2941
3364
|
class FigSegmentedControl extends HTMLElement {
|
|
2942
3365
|
#selectedSegment = null;
|
|
2943
3366
|
#boundHandleClick = this.handleClick.bind(this);
|
|
3367
|
+
#boundHandleKeyDown = this.#handleKeyDown.bind(this);
|
|
2944
3368
|
#mutationObserver = null;
|
|
2945
3369
|
#resizeObserver = null;
|
|
2946
3370
|
#indicatorFrame = 0;
|
|
@@ -2957,7 +3381,9 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
2957
3381
|
|
|
2958
3382
|
connectedCallback() {
|
|
2959
3383
|
this.name = this.getAttribute("name") || "segmented-control";
|
|
3384
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "radiogroup");
|
|
2960
3385
|
this.addEventListener("click", this.#boundHandleClick);
|
|
3386
|
+
this.addEventListener("keydown", this.#boundHandleKeyDown);
|
|
2961
3387
|
this.#applyDisabled(
|
|
2962
3388
|
this.hasAttribute("disabled") &&
|
|
2963
3389
|
this.getAttribute("disabled") !== "false",
|
|
@@ -2975,6 +3401,7 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
2975
3401
|
|
|
2976
3402
|
disconnectedCallback() {
|
|
2977
3403
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
3404
|
+
this.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
2978
3405
|
this.#mutationObserver?.disconnect();
|
|
2979
3406
|
this.#mutationObserver = null;
|
|
2980
3407
|
this.#resizeObserver?.disconnect();
|
|
@@ -3004,6 +3431,7 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
3004
3431
|
}
|
|
3005
3432
|
this.#selectedSegment =
|
|
3006
3433
|
segment instanceof HTMLElement && this.contains(segment) ? segment : null;
|
|
3434
|
+
this.#syncSegmentA11y();
|
|
3007
3435
|
this.#queueIndicatorSync();
|
|
3008
3436
|
}
|
|
3009
3437
|
|
|
@@ -3054,39 +3482,133 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
3054
3482
|
return null;
|
|
3055
3483
|
}
|
|
3056
3484
|
|
|
3057
|
-
#
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
const segmentValue = this.#resolveSegmentValue(segment);
|
|
3064
|
-
if (!segmentValue) continue;
|
|
3065
|
-
if (segmentValue === normalizedValue) {
|
|
3066
|
-
this.selectedSegment = segment;
|
|
3067
|
-
return true;
|
|
3068
|
-
}
|
|
3069
|
-
}
|
|
3070
|
-
|
|
3071
|
-
return false;
|
|
3072
|
-
}
|
|
3073
|
-
|
|
3074
|
-
#isAnimatedEnabled() {
|
|
3075
|
-
const rawAnimated = this.getAttribute("animated");
|
|
3076
|
-
if (rawAnimated === null) return false;
|
|
3077
|
-
if (rawAnimated === "") return true;
|
|
3078
|
-
return rawAnimated.trim().toLowerCase() === "true";
|
|
3485
|
+
#availableSegments() {
|
|
3486
|
+
return Array.from(this.querySelectorAll("fig-segment")).filter(
|
|
3487
|
+
(segment) =>
|
|
3488
|
+
!segment.hasAttribute("disabled") ||
|
|
3489
|
+
segment.getAttribute("disabled") === "false",
|
|
3490
|
+
);
|
|
3079
3491
|
}
|
|
3080
3492
|
|
|
3081
|
-
#
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3493
|
+
#syncSegmentA11y() {
|
|
3494
|
+
const segments = Array.from(this.querySelectorAll("fig-segment"));
|
|
3495
|
+
const selected = segments.find((segment) => segment.hasAttribute("selected"));
|
|
3496
|
+
segments.forEach((segment) => {
|
|
3497
|
+
const disabled =
|
|
3498
|
+
segment.hasAttribute("disabled") &&
|
|
3499
|
+
segment.getAttribute("disabled") !== "false";
|
|
3500
|
+
const isSelected = segment === selected;
|
|
3501
|
+
segment.setAttribute("aria-checked", isSelected ? "true" : "false");
|
|
3502
|
+
segment.setAttribute("tabindex", !disabled && isSelected ? "0" : "-1");
|
|
3503
|
+
});
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
#selectSegment(segment) {
|
|
3507
|
+
if (!segment) return;
|
|
3508
|
+
const previousSegment = this.selectedSegment;
|
|
3509
|
+
const previousValue = this.value;
|
|
3510
|
+
this.selectedSegment = segment;
|
|
3511
|
+
const resolvedValue = this.#resolveSegmentValue(segment);
|
|
3512
|
+
|
|
3513
|
+
if (resolvedValue) {
|
|
3514
|
+
this.setAttribute("value", resolvedValue);
|
|
3515
|
+
} else {
|
|
3516
|
+
this.removeAttribute("value");
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
const nextValue = this.value;
|
|
3520
|
+
if (previousSegment !== segment || previousValue !== nextValue) {
|
|
3521
|
+
this.#emitSelectionEvents(nextValue);
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
#handleKeyDown(event) {
|
|
3526
|
+
if (
|
|
3527
|
+
this.hasAttribute("disabled") &&
|
|
3528
|
+
this.getAttribute("disabled") !== "false"
|
|
3529
|
+
) {
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
const segments = this.#availableSegments();
|
|
3533
|
+
if (!segments.length) return;
|
|
3534
|
+
const currentIndex = segments.findIndex((segment) =>
|
|
3535
|
+
segment.hasAttribute("selected"),
|
|
3536
|
+
);
|
|
3537
|
+
let nextIndex = currentIndex >= 0 ? currentIndex : 0;
|
|
3538
|
+
|
|
3539
|
+
switch (event.key) {
|
|
3540
|
+
case "ArrowLeft":
|
|
3541
|
+
case "ArrowUp":
|
|
3542
|
+
event.preventDefault();
|
|
3543
|
+
nextIndex = nextIndex > 0 ? nextIndex - 1 : segments.length - 1;
|
|
3544
|
+
break;
|
|
3545
|
+
case "ArrowRight":
|
|
3546
|
+
case "ArrowDown":
|
|
3547
|
+
event.preventDefault();
|
|
3548
|
+
nextIndex = nextIndex < segments.length - 1 ? nextIndex + 1 : 0;
|
|
3549
|
+
break;
|
|
3550
|
+
case "Home":
|
|
3551
|
+
event.preventDefault();
|
|
3552
|
+
nextIndex = 0;
|
|
3553
|
+
break;
|
|
3554
|
+
case "End":
|
|
3555
|
+
event.preventDefault();
|
|
3556
|
+
nextIndex = segments.length - 1;
|
|
3557
|
+
break;
|
|
3558
|
+
case " ":
|
|
3559
|
+
case "Enter": {
|
|
3560
|
+
const active = event.target.closest("fig-segment");
|
|
3561
|
+
if (active && this.contains(active)) {
|
|
3562
|
+
event.preventDefault();
|
|
3563
|
+
this.#selectSegment(active);
|
|
3564
|
+
}
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
default:
|
|
3568
|
+
return;
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
const next = segments[nextIndex];
|
|
3572
|
+
this.#selectSegment(next);
|
|
3573
|
+
next.focus();
|
|
3574
|
+
requestAnimationFrame(() => {
|
|
3575
|
+
if (this.contains(next)) next.focus();
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
#selectByValue(value) {
|
|
3580
|
+
const normalizedValue = String(value ?? "").trim();
|
|
3581
|
+
if (!normalizedValue) return false;
|
|
3582
|
+
|
|
3583
|
+
const segments = this.querySelectorAll("fig-segment");
|
|
3584
|
+
for (const segment of segments) {
|
|
3585
|
+
const segmentValue = this.#resolveSegmentValue(segment);
|
|
3586
|
+
if (!segmentValue) continue;
|
|
3587
|
+
if (segmentValue === normalizedValue) {
|
|
3588
|
+
this.selectedSegment = segment;
|
|
3589
|
+
return true;
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
return false;
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
#isAnimatedEnabled() {
|
|
3597
|
+
const rawAnimated = this.getAttribute("animated");
|
|
3598
|
+
if (rawAnimated === null) return false;
|
|
3599
|
+
if (rawAnimated === "") return true;
|
|
3600
|
+
return rawAnimated.trim().toLowerCase() === "true";
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
#queueIndicatorSync({ forceInstant = false } = {}) {
|
|
3604
|
+
this.#indicatorSyncInstant = this.#indicatorSyncInstant || forceInstant;
|
|
3605
|
+
if (this.#indicatorFrame) return;
|
|
3606
|
+
|
|
3607
|
+
this.#indicatorFrame = requestAnimationFrame(() => {
|
|
3608
|
+
this.#indicatorFrame = 0;
|
|
3609
|
+
const nextForceInstant = this.#indicatorSyncInstant;
|
|
3610
|
+
this.#indicatorSyncInstant = false;
|
|
3611
|
+
this.#syncIndicator({ forceInstant: nextForceInstant });
|
|
3090
3612
|
});
|
|
3091
3613
|
}
|
|
3092
3614
|
|
|
@@ -3226,6 +3748,7 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
3226
3748
|
);
|
|
3227
3749
|
this.#refreshResizeObserverTargets();
|
|
3228
3750
|
this.#syncSelectionFromAttributes({ enforceFallback: true });
|
|
3751
|
+
this.#syncSegmentA11y();
|
|
3229
3752
|
}
|
|
3230
3753
|
});
|
|
3231
3754
|
|
|
@@ -3248,22 +3771,7 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
3248
3771
|
const segment = event.target.closest("fig-segment");
|
|
3249
3772
|
if (!segment || !this.contains(segment)) return;
|
|
3250
3773
|
|
|
3251
|
-
|
|
3252
|
-
const previousValue = this.value;
|
|
3253
|
-
|
|
3254
|
-
this.selectedSegment = segment;
|
|
3255
|
-
const resolvedValue = this.#resolveSegmentValue(segment);
|
|
3256
|
-
|
|
3257
|
-
if (resolvedValue) {
|
|
3258
|
-
this.setAttribute("value", resolvedValue);
|
|
3259
|
-
} else {
|
|
3260
|
-
this.removeAttribute("value");
|
|
3261
|
-
}
|
|
3262
|
-
|
|
3263
|
-
const nextValue = this.value;
|
|
3264
|
-
if (previousSegment !== segment || previousValue !== nextValue) {
|
|
3265
|
-
this.#emitSelectionEvents(nextValue);
|
|
3266
|
-
}
|
|
3774
|
+
this.#selectSegment(segment);
|
|
3267
3775
|
}
|
|
3268
3776
|
|
|
3269
3777
|
#applyDisabled(disabled) {
|
|
@@ -3277,6 +3785,7 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
3277
3785
|
segment.removeAttribute("aria-disabled");
|
|
3278
3786
|
}
|
|
3279
3787
|
});
|
|
3788
|
+
this.#syncSegmentA11y();
|
|
3280
3789
|
}
|
|
3281
3790
|
|
|
3282
3791
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -3634,6 +4143,14 @@ class FigSlider extends HTMLElement {
|
|
|
3634
4143
|
#showEmptyTextValue = false;
|
|
3635
4144
|
#isSyncingValueAttribute = false;
|
|
3636
4145
|
#value = "";
|
|
4146
|
+
#a11yAttributes = [
|
|
4147
|
+
"aria-label",
|
|
4148
|
+
"aria-labelledby",
|
|
4149
|
+
"aria-describedby",
|
|
4150
|
+
"aria-invalid",
|
|
4151
|
+
"aria-required",
|
|
4152
|
+
"aria-valuetext",
|
|
4153
|
+
];
|
|
3637
4154
|
// Private fields declarations
|
|
3638
4155
|
#typeDefaults = {
|
|
3639
4156
|
range: { min: 0, max: 100, step: 1 },
|
|
@@ -3647,6 +4164,12 @@ class FigSlider extends HTMLElement {
|
|
|
3647
4164
|
#boundHandleChange;
|
|
3648
4165
|
#boundHandleTextInput;
|
|
3649
4166
|
#boundHandleTextChange;
|
|
4167
|
+
#boundHandleKeyDown;
|
|
4168
|
+
#boundRangePointerDown;
|
|
4169
|
+
#boundRangePointerUp;
|
|
4170
|
+
#lastSliderComplete = null;
|
|
4171
|
+
#lastSliderDefault = null;
|
|
4172
|
+
#lastSliderUnchanged = null;
|
|
3650
4173
|
|
|
3651
4174
|
constructor() {
|
|
3652
4175
|
super();
|
|
@@ -3662,6 +4185,9 @@ class FigSlider extends HTMLElement {
|
|
|
3662
4185
|
e.stopPropagation();
|
|
3663
4186
|
this.#handleChange();
|
|
3664
4187
|
};
|
|
4188
|
+
this.#boundHandleKeyDown = (e) => {
|
|
4189
|
+
this.#handleKeyDown(e);
|
|
4190
|
+
};
|
|
3665
4191
|
|
|
3666
4192
|
this.#boundHandleTextInput = (e) => {
|
|
3667
4193
|
e.stopPropagation();
|
|
@@ -3672,6 +4198,12 @@ class FigSlider extends HTMLElement {
|
|
|
3672
4198
|
e.stopPropagation();
|
|
3673
4199
|
this.#handleTextChange();
|
|
3674
4200
|
};
|
|
4201
|
+
this.#boundRangePointerDown = () => {
|
|
4202
|
+
this.#isInteracting = true;
|
|
4203
|
+
};
|
|
4204
|
+
this.#boundRangePointerUp = () => {
|
|
4205
|
+
this.#isInteracting = false;
|
|
4206
|
+
};
|
|
3675
4207
|
}
|
|
3676
4208
|
|
|
3677
4209
|
#regenerateInnerHTML() {
|
|
@@ -3701,8 +4233,9 @@ class FigSlider extends HTMLElement {
|
|
|
3701
4233
|
? 0
|
|
3702
4234
|
: this.min;
|
|
3703
4235
|
this.#showEmptyTextValue =
|
|
3704
|
-
|
|
3705
|
-
(
|
|
4236
|
+
this.type !== "range" &&
|
|
4237
|
+
(rawValue === null ||
|
|
4238
|
+
(typeof rawValue === "string" && rawValue.trim() === ""));
|
|
3706
4239
|
this.value = this.#normalizeSliderValue(rawValue);
|
|
3707
4240
|
|
|
3708
4241
|
if (this.color) {
|
|
@@ -3742,86 +4275,85 @@ class FigSlider extends HTMLElement {
|
|
|
3742
4275
|
}
|
|
3743
4276
|
this.innerHTML = html;
|
|
3744
4277
|
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
}
|
|
4278
|
+
this.input = this.querySelector("[type=range]");
|
|
4279
|
+
this.inputContainer = this.querySelector(".fig-slider-input-container");
|
|
4280
|
+
this.#syncInputA11yAttributes();
|
|
4281
|
+
this.input.removeEventListener("input", this.#boundHandleInput);
|
|
4282
|
+
this.input.addEventListener("input", this.#boundHandleInput);
|
|
4283
|
+
this.input.removeEventListener("change", this.#boundHandleChange);
|
|
4284
|
+
this.input.addEventListener("change", this.#boundHandleChange);
|
|
4285
|
+
this.input.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
4286
|
+
this.input.addEventListener("keydown", this.#boundHandleKeyDown);
|
|
4287
|
+
this.input.removeEventListener("pointerdown", this.#boundRangePointerDown);
|
|
4288
|
+
this.input.addEventListener("pointerdown", this.#boundRangePointerDown);
|
|
4289
|
+
this.input.removeEventListener("pointerup", this.#boundRangePointerUp);
|
|
4290
|
+
this.input.addEventListener("pointerup", this.#boundRangePointerUp);
|
|
4291
|
+
|
|
4292
|
+
if (this.default) {
|
|
4293
|
+
this.style.setProperty(
|
|
4294
|
+
"--default",
|
|
4295
|
+
this.#calculateNormal(this.default),
|
|
4296
|
+
);
|
|
4297
|
+
}
|
|
3766
4298
|
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
let option = document.createElement("option");
|
|
3782
|
-
option.setAttribute("value", this.min + i * this.step);
|
|
3783
|
-
this.datalist.append(option);
|
|
3784
|
-
}
|
|
3785
|
-
this.inputContainer.append(this.datalist);
|
|
3786
|
-
this.input.setAttribute("list", this.datalist.getAttribute("id"));
|
|
3787
|
-
} else if (this.type === "delta") {
|
|
3788
|
-
this.datalist = document.createElement("datalist");
|
|
3789
|
-
this.datalist.setAttribute("id", figUniqueId());
|
|
4299
|
+
this.datalist = this.querySelector("datalist");
|
|
4300
|
+
this.figInputNumber = this.querySelector("fig-input-number");
|
|
4301
|
+
if (this.datalist) {
|
|
4302
|
+
this.inputContainer.append(this.datalist);
|
|
4303
|
+
this.datalist.setAttribute(
|
|
4304
|
+
"id",
|
|
4305
|
+
this.datalist.getAttribute("id") || figUniqueId(),
|
|
4306
|
+
);
|
|
4307
|
+
this.input.setAttribute("list", this.datalist.getAttribute("id"));
|
|
4308
|
+
} else if (this.type === "stepper") {
|
|
4309
|
+
this.datalist = document.createElement("datalist");
|
|
4310
|
+
this.datalist.setAttribute("id", figUniqueId());
|
|
4311
|
+
let steps = (this.max - this.min) / this.step + 1;
|
|
4312
|
+
for (let i = 0; i < steps; i++) {
|
|
3790
4313
|
let option = document.createElement("option");
|
|
3791
|
-
option.setAttribute("value", this.
|
|
4314
|
+
option.setAttribute("value", this.min + i * this.step);
|
|
3792
4315
|
this.datalist.append(option);
|
|
3793
|
-
this.inputContainer.append(this.datalist);
|
|
3794
|
-
this.input.setAttribute("list", this.datalist.getAttribute("id"));
|
|
3795
4316
|
}
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
this.
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
);
|
|
3813
|
-
this.figInputNumber.removeEventListener(
|
|
3814
|
-
"change",
|
|
3815
|
-
this.#boundHandleTextChange,
|
|
3816
|
-
);
|
|
3817
|
-
this.figInputNumber.addEventListener(
|
|
3818
|
-
"change",
|
|
3819
|
-
this.#boundHandleTextChange,
|
|
3820
|
-
);
|
|
4317
|
+
this.inputContainer.append(this.datalist);
|
|
4318
|
+
this.input.setAttribute("list", this.datalist.getAttribute("id"));
|
|
4319
|
+
} else if (this.type === "delta") {
|
|
4320
|
+
this.datalist = document.createElement("datalist");
|
|
4321
|
+
this.datalist.setAttribute("id", figUniqueId());
|
|
4322
|
+
let option = document.createElement("option");
|
|
4323
|
+
option.setAttribute("value", this.default);
|
|
4324
|
+
this.datalist.append(option);
|
|
4325
|
+
this.inputContainer.append(this.datalist);
|
|
4326
|
+
this.input.setAttribute("list", this.datalist.getAttribute("id"));
|
|
4327
|
+
}
|
|
4328
|
+
if (this.datalist) {
|
|
4329
|
+
let defaultOption = this.datalist.querySelector(
|
|
4330
|
+
`option[value='${this.default}']`,
|
|
4331
|
+
);
|
|
4332
|
+
if (defaultOption) {
|
|
4333
|
+
defaultOption.setAttribute("default", "true");
|
|
3821
4334
|
}
|
|
4335
|
+
}
|
|
4336
|
+
if (this.figInputNumber) {
|
|
4337
|
+
this.#syncTextInputA11yAttributes();
|
|
4338
|
+
this.figInputNumber.removeEventListener(
|
|
4339
|
+
"input",
|
|
4340
|
+
this.#boundHandleTextInput,
|
|
4341
|
+
);
|
|
4342
|
+
this.figInputNumber.addEventListener(
|
|
4343
|
+
"input",
|
|
4344
|
+
this.#boundHandleTextInput,
|
|
4345
|
+
);
|
|
4346
|
+
this.figInputNumber.removeEventListener(
|
|
4347
|
+
"change",
|
|
4348
|
+
this.#boundHandleTextChange,
|
|
4349
|
+
);
|
|
4350
|
+
this.figInputNumber.addEventListener(
|
|
4351
|
+
"change",
|
|
4352
|
+
this.#boundHandleTextChange,
|
|
4353
|
+
);
|
|
4354
|
+
}
|
|
3822
4355
|
|
|
3823
|
-
|
|
3824
|
-
});
|
|
4356
|
+
this.#syncValue();
|
|
3825
4357
|
}
|
|
3826
4358
|
|
|
3827
4359
|
connectedCallback() {
|
|
@@ -3864,6 +4396,9 @@ class FigSlider extends HTMLElement {
|
|
|
3864
4396
|
if (this.input) {
|
|
3865
4397
|
this.input.removeEventListener("input", this.#boundHandleInput);
|
|
3866
4398
|
this.input.removeEventListener("change", this.#boundHandleChange);
|
|
4399
|
+
this.input.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
4400
|
+
this.input.removeEventListener("pointerdown", this.#boundRangePointerDown);
|
|
4401
|
+
this.input.removeEventListener("pointerup", this.#boundRangePointerUp);
|
|
3867
4402
|
}
|
|
3868
4403
|
if (this.figInputNumber) {
|
|
3869
4404
|
this.figInputNumber.removeEventListener(
|
|
@@ -3925,6 +4460,10 @@ class FigSlider extends HTMLElement {
|
|
|
3925
4460
|
if (deltaDefault !== null) return this.#clampToBounds(deltaDefault);
|
|
3926
4461
|
return this.#clampToBounds(0);
|
|
3927
4462
|
}
|
|
4463
|
+
if (this.type === "range") {
|
|
4464
|
+
const { min, max } = this.#getBounds();
|
|
4465
|
+
return this.#clampToBounds(min + (max - min) / 2);
|
|
4466
|
+
}
|
|
3928
4467
|
const { min } = this.#getBounds();
|
|
3929
4468
|
return min;
|
|
3930
4469
|
}
|
|
@@ -3934,11 +4473,22 @@ class FigSlider extends HTMLElement {
|
|
|
3934
4473
|
return this.#clampToBounds(parsed);
|
|
3935
4474
|
}
|
|
3936
4475
|
#syncProperties() {
|
|
3937
|
-
|
|
3938
|
-
this.
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
this
|
|
4476
|
+
const complete = this.#calculateNormal(this.value);
|
|
4477
|
+
const defaultValue = this.#calculateNormal(this.default);
|
|
4478
|
+
const unchanged = complete === defaultValue ? 1 : 0;
|
|
4479
|
+
|
|
4480
|
+
if (this.#lastSliderComplete !== complete) {
|
|
4481
|
+
this.style.setProperty("--slider-complete", complete);
|
|
4482
|
+
this.#lastSliderComplete = complete;
|
|
4483
|
+
}
|
|
4484
|
+
if (this.#lastSliderDefault !== defaultValue) {
|
|
4485
|
+
this.style.setProperty("--default", defaultValue);
|
|
4486
|
+
this.#lastSliderDefault = defaultValue;
|
|
4487
|
+
}
|
|
4488
|
+
if (this.#lastSliderUnchanged !== unchanged) {
|
|
4489
|
+
this.style.setProperty("--unchanged", unchanged);
|
|
4490
|
+
this.#lastSliderUnchanged = unchanged;
|
|
4491
|
+
}
|
|
3942
4492
|
}
|
|
3943
4493
|
#syncValue() {
|
|
3944
4494
|
let val = this.input.value;
|
|
@@ -3953,6 +4503,39 @@ class FigSlider extends HTMLElement {
|
|
|
3953
4503
|
);
|
|
3954
4504
|
}
|
|
3955
4505
|
}
|
|
4506
|
+
#syncInputA11yAttributes() {
|
|
4507
|
+
if (!this.input) return;
|
|
4508
|
+
if (this.text) {
|
|
4509
|
+
this.input.setAttribute("aria-hidden", "true");
|
|
4510
|
+
["aria-label", "aria-labelledby", "aria-describedby", "aria-valuetext"].forEach(
|
|
4511
|
+
(name) => this.input.removeAttribute(name),
|
|
4512
|
+
);
|
|
4513
|
+
this.#syncTextInputA11yAttributes();
|
|
4514
|
+
return;
|
|
4515
|
+
}
|
|
4516
|
+
this.input.removeAttribute("aria-hidden");
|
|
4517
|
+
this.#a11yAttributes.forEach((name) => {
|
|
4518
|
+
const value = this.getAttribute(name);
|
|
4519
|
+
if (value === null) {
|
|
4520
|
+
this.input.removeAttribute(name);
|
|
4521
|
+
} else {
|
|
4522
|
+
this.input.setAttribute(name, value);
|
|
4523
|
+
}
|
|
4524
|
+
});
|
|
4525
|
+
}
|
|
4526
|
+
#syncTextInputA11yAttributes() {
|
|
4527
|
+
if (!this.figInputNumber) return;
|
|
4528
|
+
["aria-label", "aria-labelledby", "aria-describedby", "aria-invalid", "aria-required"].forEach(
|
|
4529
|
+
(name) => {
|
|
4530
|
+
const value = this.getAttribute(name);
|
|
4531
|
+
if (value === null) {
|
|
4532
|
+
this.figInputNumber.removeAttribute(name);
|
|
4533
|
+
} else {
|
|
4534
|
+
this.figInputNumber.setAttribute(name, value);
|
|
4535
|
+
}
|
|
4536
|
+
},
|
|
4537
|
+
);
|
|
4538
|
+
}
|
|
3956
4539
|
|
|
3957
4540
|
#handleInput() {
|
|
3958
4541
|
this.#showEmptyTextValue = false;
|
|
@@ -3971,6 +4554,34 @@ class FigSlider extends HTMLElement {
|
|
|
3971
4554
|
);
|
|
3972
4555
|
}
|
|
3973
4556
|
|
|
4557
|
+
#handleKeyDown(event) {
|
|
4558
|
+
if (this.disabled || !event.shiftKey) return;
|
|
4559
|
+
if (
|
|
4560
|
+
!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key)
|
|
4561
|
+
) {
|
|
4562
|
+
return;
|
|
4563
|
+
}
|
|
4564
|
+
|
|
4565
|
+
event.preventDefault();
|
|
4566
|
+
this.#showEmptyTextValue = false;
|
|
4567
|
+
|
|
4568
|
+
const direction =
|
|
4569
|
+
event.key === "ArrowRight" || event.key === "ArrowUp" ? 1 : -1;
|
|
4570
|
+
const current = this.#toFiniteNumber(this.input.value) ?? this.#getFallbackValue();
|
|
4571
|
+
const step = this.#toFiniteNumber(this.step) ?? 1;
|
|
4572
|
+
const nextValue = this.#normalizeSliderValue(current + step * 10 * direction);
|
|
4573
|
+
|
|
4574
|
+
this.value = nextValue;
|
|
4575
|
+
this.input.value = String(nextValue);
|
|
4576
|
+
this.#syncValue();
|
|
4577
|
+
this.dispatchEvent(
|
|
4578
|
+
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
4579
|
+
);
|
|
4580
|
+
this.dispatchEvent(
|
|
4581
|
+
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
4582
|
+
);
|
|
4583
|
+
}
|
|
4584
|
+
|
|
3974
4585
|
#handleTextChange() {
|
|
3975
4586
|
if (this.figInputNumber) {
|
|
3976
4587
|
const rawTextValue = this.figInputNumber.value;
|
|
@@ -4004,10 +4615,20 @@ class FigSlider extends HTMLElement {
|
|
|
4004
4615
|
"placeholder",
|
|
4005
4616
|
"default",
|
|
4006
4617
|
"precision",
|
|
4618
|
+
"aria-label",
|
|
4619
|
+
"aria-labelledby",
|
|
4620
|
+
"aria-describedby",
|
|
4621
|
+
"aria-invalid",
|
|
4622
|
+
"aria-required",
|
|
4623
|
+
"aria-valuetext",
|
|
4007
4624
|
];
|
|
4008
4625
|
}
|
|
4009
4626
|
|
|
4010
4627
|
focus() {
|
|
4628
|
+
if (this.text && this.figInputNumber) {
|
|
4629
|
+
this.figInputNumber.focus();
|
|
4630
|
+
return;
|
|
4631
|
+
}
|
|
4011
4632
|
this.input.focus();
|
|
4012
4633
|
}
|
|
4013
4634
|
|
|
@@ -4082,6 +4703,14 @@ class FigSlider extends HTMLElement {
|
|
|
4082
4703
|
this.text = newValue !== "false";
|
|
4083
4704
|
this.#regenerateInnerHTML();
|
|
4084
4705
|
break;
|
|
4706
|
+
case "aria-label":
|
|
4707
|
+
case "aria-labelledby":
|
|
4708
|
+
case "aria-describedby":
|
|
4709
|
+
case "aria-invalid":
|
|
4710
|
+
case "aria-required":
|
|
4711
|
+
case "aria-valuetext":
|
|
4712
|
+
this.#syncInputA11yAttributes();
|
|
4713
|
+
break;
|
|
4085
4714
|
default:
|
|
4086
4715
|
this[name] = this.input[name] = newValue;
|
|
4087
4716
|
this.#syncValue();
|
|
@@ -4113,6 +4742,14 @@ class FigInputText extends HTMLElement {
|
|
|
4113
4742
|
#boundMouseDown;
|
|
4114
4743
|
#boundInputChange;
|
|
4115
4744
|
#boundNativeInput;
|
|
4745
|
+
#boundFocusControl;
|
|
4746
|
+
#a11yAttributes = [
|
|
4747
|
+
"aria-label",
|
|
4748
|
+
"aria-labelledby",
|
|
4749
|
+
"aria-describedby",
|
|
4750
|
+
"aria-invalid",
|
|
4751
|
+
"aria-required",
|
|
4752
|
+
];
|
|
4116
4753
|
|
|
4117
4754
|
constructor() {
|
|
4118
4755
|
super();
|
|
@@ -4128,6 +4765,7 @@ class FigInputText extends HTMLElement {
|
|
|
4128
4765
|
this.#boundNativeInput = () => {
|
|
4129
4766
|
this.#syncSearchClearVisibility();
|
|
4130
4767
|
};
|
|
4768
|
+
this.#boundFocusControl = this.focus.bind(this);
|
|
4131
4769
|
}
|
|
4132
4770
|
|
|
4133
4771
|
connectedCallback() {
|
|
@@ -4144,16 +4782,6 @@ class FigInputText extends HTMLElement {
|
|
|
4144
4782
|
if (this.getAttribute("step")) {
|
|
4145
4783
|
this.step = Number(this.getAttribute("step"));
|
|
4146
4784
|
}
|
|
4147
|
-
|
|
4148
|
-
if (this.getAttribute("min")) {
|
|
4149
|
-
this.input.setAttribute("min", String(this.min));
|
|
4150
|
-
}
|
|
4151
|
-
if (this.getAttribute("max")) {
|
|
4152
|
-
this.input.setAttribute("max", String(this.max));
|
|
4153
|
-
}
|
|
4154
|
-
if (this.getAttribute("step")) {
|
|
4155
|
-
this.input.setAttribute("step", String(this.step));
|
|
4156
|
-
}
|
|
4157
4785
|
if (this.getAttribute("min")) {
|
|
4158
4786
|
this.min = Number(this.getAttribute("min"));
|
|
4159
4787
|
}
|
|
@@ -4178,46 +4806,46 @@ class FigInputText extends HTMLElement {
|
|
|
4178
4806
|
placeholder="${this.placeholder}">${this.value}</textarea>`;
|
|
4179
4807
|
}
|
|
4180
4808
|
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
let append = this.querySelector("[slot=append]");
|
|
4184
|
-
let prepend = this.querySelector("[slot=prepend]");
|
|
4809
|
+
let append = this.querySelector("[slot=append]");
|
|
4810
|
+
let prepend = this.querySelector("[slot=prepend]");
|
|
4185
4811
|
|
|
4186
|
-
|
|
4812
|
+
this.innerHTML = html;
|
|
4187
4813
|
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4814
|
+
if (prepend) {
|
|
4815
|
+
prepend.removeEventListener("click", this.#boundFocusControl);
|
|
4816
|
+
prepend.addEventListener("click", this.#boundFocusControl);
|
|
4817
|
+
this.prepend(prepend);
|
|
4818
|
+
}
|
|
4819
|
+
if (append) {
|
|
4820
|
+
append.removeEventListener("click", this.#boundFocusControl);
|
|
4821
|
+
append.addEventListener("click", this.#boundFocusControl);
|
|
4822
|
+
this.append(append);
|
|
4823
|
+
}
|
|
4196
4824
|
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4825
|
+
this.input = this.querySelector("input,textarea");
|
|
4826
|
+
this.input.readOnly = this.readonly;
|
|
4827
|
+
this.#syncInputA11yAttributes();
|
|
4828
|
+
this.#syncSearchPrefix();
|
|
4829
|
+
this.#syncSearchClear();
|
|
4830
|
+
this.#syncSearchClearVisibility();
|
|
4831
|
+
this.#syncPasswordToggle();
|
|
4203
4832
|
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
}
|
|
4208
|
-
if (this.getAttribute("max")) {
|
|
4209
|
-
this.input.setAttribute("max", this.#transformNumber(this.max));
|
|
4210
|
-
}
|
|
4211
|
-
if (this.getAttribute("step")) {
|
|
4212
|
-
this.input.setAttribute("step", this.#transformNumber(this.step));
|
|
4213
|
-
}
|
|
4214
|
-
this.addEventListener("pointerdown", this.#boundMouseDown);
|
|
4833
|
+
if (this.type === "number") {
|
|
4834
|
+
if (this.getAttribute("min")) {
|
|
4835
|
+
this.input.setAttribute("min", this.#transformNumber(this.min));
|
|
4215
4836
|
}
|
|
4216
|
-
this.
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
this.
|
|
4220
|
-
|
|
4837
|
+
if (this.getAttribute("max")) {
|
|
4838
|
+
this.input.setAttribute("max", this.#transformNumber(this.max));
|
|
4839
|
+
}
|
|
4840
|
+
if (this.getAttribute("step")) {
|
|
4841
|
+
this.input.setAttribute("step", this.#transformNumber(this.step));
|
|
4842
|
+
}
|
|
4843
|
+
this.addEventListener("pointerdown", this.#boundMouseDown);
|
|
4844
|
+
}
|
|
4845
|
+
this.input.removeEventListener("change", this.#boundInputChange);
|
|
4846
|
+
this.input.addEventListener("change", this.#boundInputChange);
|
|
4847
|
+
this.input.removeEventListener("input", this.#boundNativeInput);
|
|
4848
|
+
this.input.addEventListener("input", this.#boundNativeInput);
|
|
4221
4849
|
}
|
|
4222
4850
|
|
|
4223
4851
|
disconnectedCallback() {
|
|
@@ -4234,6 +4862,17 @@ class FigInputText extends HTMLElement {
|
|
|
4234
4862
|
focus() {
|
|
4235
4863
|
this.input.focus();
|
|
4236
4864
|
}
|
|
4865
|
+
#syncInputA11yAttributes() {
|
|
4866
|
+
if (!this.input) return;
|
|
4867
|
+
this.#a11yAttributes.forEach((name) => {
|
|
4868
|
+
const value = this.getAttribute(name);
|
|
4869
|
+
if (value === null) {
|
|
4870
|
+
this.input.removeAttribute(name);
|
|
4871
|
+
} else {
|
|
4872
|
+
this.input.setAttribute(name, value);
|
|
4873
|
+
}
|
|
4874
|
+
});
|
|
4875
|
+
}
|
|
4237
4876
|
#syncSearchPrefix() {
|
|
4238
4877
|
const generated = this.querySelector(
|
|
4239
4878
|
'[slot="prepend"][data-generated="search-prefix"]',
|
|
@@ -4483,6 +5122,11 @@ class FigInputText extends HTMLElement {
|
|
|
4483
5122
|
"max",
|
|
4484
5123
|
"transform",
|
|
4485
5124
|
"name",
|
|
5125
|
+
"aria-label",
|
|
5126
|
+
"aria-labelledby",
|
|
5127
|
+
"aria-describedby",
|
|
5128
|
+
"aria-invalid",
|
|
5129
|
+
"aria-required",
|
|
4486
5130
|
];
|
|
4487
5131
|
}
|
|
4488
5132
|
|
|
@@ -4540,6 +5184,13 @@ class FigInputText extends HTMLElement {
|
|
|
4540
5184
|
this.#syncSearchClearVisibility();
|
|
4541
5185
|
this.#syncPasswordToggle();
|
|
4542
5186
|
break;
|
|
5187
|
+
case "aria-label":
|
|
5188
|
+
case "aria-labelledby":
|
|
5189
|
+
case "aria-describedby":
|
|
5190
|
+
case "aria-invalid":
|
|
5191
|
+
case "aria-required":
|
|
5192
|
+
this.#syncInputA11yAttributes();
|
|
5193
|
+
break;
|
|
4543
5194
|
default:
|
|
4544
5195
|
this[name] = this.input[name] = newValue;
|
|
4545
5196
|
break;
|
|
@@ -4574,6 +5225,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
4574
5225
|
#boundFocus;
|
|
4575
5226
|
#boundBlur;
|
|
4576
5227
|
#boundKeyDown;
|
|
5228
|
+
#boundFocusControl;
|
|
4577
5229
|
#units;
|
|
4578
5230
|
#rawUnits;
|
|
4579
5231
|
#unitsDisallow;
|
|
@@ -4581,6 +5233,13 @@ class FigInputNumber extends HTMLElement {
|
|
|
4581
5233
|
#precision;
|
|
4582
5234
|
#isInteracting = false;
|
|
4583
5235
|
#stepperEl = null;
|
|
5236
|
+
#a11yAttributes = [
|
|
5237
|
+
"aria-label",
|
|
5238
|
+
"aria-labelledby",
|
|
5239
|
+
"aria-describedby",
|
|
5240
|
+
"aria-invalid",
|
|
5241
|
+
"aria-required",
|
|
5242
|
+
];
|
|
4584
5243
|
static #DEFAULT_UNITS_DISALLOW = "px";
|
|
4585
5244
|
|
|
4586
5245
|
#parseUnitsDisallowList(value) {
|
|
@@ -4659,6 +5318,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
4659
5318
|
this.value = value;
|
|
4660
5319
|
this.input.value = this.#formatWithUnit(this.value);
|
|
4661
5320
|
this.#syncStepperState();
|
|
5321
|
+
this.#syncSpinbuttonAria();
|
|
4662
5322
|
this.dispatchEvent(
|
|
4663
5323
|
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
4664
5324
|
);
|
|
@@ -4691,6 +5351,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
4691
5351
|
this.#boundKeyDown = (e) => {
|
|
4692
5352
|
this.#handleKeyDown(e);
|
|
4693
5353
|
};
|
|
5354
|
+
this.#boundFocusControl = this.focus.bind(this);
|
|
4694
5355
|
}
|
|
4695
5356
|
|
|
4696
5357
|
connectedCallback() {
|
|
@@ -4734,55 +5395,56 @@ class FigInputNumber extends HTMLElement {
|
|
|
4734
5395
|
placeholder="${this.placeholder}"
|
|
4735
5396
|
value="${this.#formatWithUnit(this.value)}" />`;
|
|
4736
5397
|
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
let append = this.querySelector("[slot=append]");
|
|
4740
|
-
let prepend = this.querySelector("[slot=prepend]");
|
|
5398
|
+
let append = this.querySelector("[slot=append]");
|
|
5399
|
+
let prepend = this.querySelector("[slot=prepend]");
|
|
4741
5400
|
|
|
4742
|
-
|
|
5401
|
+
this.innerHTML = html;
|
|
4743
5402
|
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
5403
|
+
if (prepend) {
|
|
5404
|
+
prepend.removeEventListener("click", this.#boundFocusControl);
|
|
5405
|
+
prepend.addEventListener("click", this.#boundFocusControl);
|
|
5406
|
+
this.prepend(prepend);
|
|
5407
|
+
}
|
|
5408
|
+
if (append) {
|
|
5409
|
+
append.removeEventListener("click", this.#boundFocusControl);
|
|
5410
|
+
append.addEventListener("click", this.#boundFocusControl);
|
|
5411
|
+
this.append(append);
|
|
5412
|
+
}
|
|
4752
5413
|
|
|
4753
|
-
|
|
5414
|
+
this.input = this.querySelector("input");
|
|
5415
|
+
this.#syncInputA11yAttributes();
|
|
4754
5416
|
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
5417
|
+
if (this.getAttribute("min")) {
|
|
5418
|
+
this.min = Number(this.getAttribute("min"));
|
|
5419
|
+
}
|
|
5420
|
+
if (this.getAttribute("max")) {
|
|
5421
|
+
this.max = Number(this.getAttribute("max"));
|
|
5422
|
+
}
|
|
5423
|
+
if (this.getAttribute("step")) {
|
|
5424
|
+
this.step = Number(this.getAttribute("step"));
|
|
5425
|
+
}
|
|
4764
5426
|
|
|
4765
|
-
|
|
5427
|
+
this.#syncSteppers(hasSteppers);
|
|
4766
5428
|
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
5429
|
+
// Set disabled state if present
|
|
5430
|
+
if (this.hasAttribute("disabled")) {
|
|
5431
|
+
const disabledAttr = this.getAttribute("disabled");
|
|
5432
|
+
this.disabled = this.input.disabled = disabledAttr !== "false";
|
|
5433
|
+
}
|
|
5434
|
+
this.#syncStepperState();
|
|
5435
|
+
this.#syncSpinbuttonAria();
|
|
4773
5436
|
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
});
|
|
5437
|
+
this.addEventListener("pointerdown", this.#boundMouseDown);
|
|
5438
|
+
this.input.removeEventListener("change", this.#boundInputChange);
|
|
5439
|
+
this.input.addEventListener("change", this.#boundInputChange);
|
|
5440
|
+
this.input.removeEventListener("input", this.#boundInput);
|
|
5441
|
+
this.input.addEventListener("input", this.#boundInput);
|
|
5442
|
+
this.input.removeEventListener("focus", this.#boundFocus);
|
|
5443
|
+
this.input.addEventListener("focus", this.#boundFocus);
|
|
5444
|
+
this.input.removeEventListener("blur", this.#boundBlur);
|
|
5445
|
+
this.input.addEventListener("blur", this.#boundBlur);
|
|
5446
|
+
this.input.removeEventListener("keydown", this.#boundKeyDown);
|
|
5447
|
+
this.input.addEventListener("keydown", this.#boundKeyDown);
|
|
4786
5448
|
}
|
|
4787
5449
|
|
|
4788
5450
|
disconnectedCallback() {
|
|
@@ -4803,6 +5465,41 @@ class FigInputNumber extends HTMLElement {
|
|
|
4803
5465
|
this.input.focus();
|
|
4804
5466
|
}
|
|
4805
5467
|
|
|
5468
|
+
#syncInputA11yAttributes() {
|
|
5469
|
+
if (!this.input) return;
|
|
5470
|
+
this.#a11yAttributes.forEach((name) => {
|
|
5471
|
+
const value = this.getAttribute(name);
|
|
5472
|
+
if (value === null) {
|
|
5473
|
+
this.input.removeAttribute(name);
|
|
5474
|
+
} else {
|
|
5475
|
+
this.input.setAttribute(name, value);
|
|
5476
|
+
}
|
|
5477
|
+
});
|
|
5478
|
+
}
|
|
5479
|
+
|
|
5480
|
+
#syncSpinbuttonAria() {
|
|
5481
|
+
if (!this.input) return;
|
|
5482
|
+
this.input.setAttribute("role", "spinbutton");
|
|
5483
|
+
if (typeof this.min === "number") {
|
|
5484
|
+
this.input.setAttribute("aria-valuemin", String(this.min));
|
|
5485
|
+
} else {
|
|
5486
|
+
this.input.removeAttribute("aria-valuemin");
|
|
5487
|
+
}
|
|
5488
|
+
if (typeof this.max === "number") {
|
|
5489
|
+
this.input.setAttribute("aria-valuemax", String(this.max));
|
|
5490
|
+
} else {
|
|
5491
|
+
this.input.removeAttribute("aria-valuemax");
|
|
5492
|
+
}
|
|
5493
|
+
const value = this.value === "" ? null : Number(this.value);
|
|
5494
|
+
if (Number.isFinite(value)) {
|
|
5495
|
+
this.input.setAttribute("aria-valuenow", String(value));
|
|
5496
|
+
this.input.setAttribute("aria-valuetext", this.#formatWithUnit(this.value));
|
|
5497
|
+
} else {
|
|
5498
|
+
this.input.removeAttribute("aria-valuenow");
|
|
5499
|
+
this.input.removeAttribute("aria-valuetext");
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
|
|
4806
5503
|
#getNumericValue(str) {
|
|
4807
5504
|
if (!str) return "";
|
|
4808
5505
|
if (!this.#units) {
|
|
@@ -4882,6 +5579,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
4882
5579
|
e.target.value = "";
|
|
4883
5580
|
}
|
|
4884
5581
|
this.#syncStepperState();
|
|
5582
|
+
this.#syncSpinbuttonAria();
|
|
4885
5583
|
this.dispatchEvent(
|
|
4886
5584
|
new CustomEvent("change", { detail: this.value, bubbles: true }),
|
|
4887
5585
|
);
|
|
@@ -4908,6 +5606,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
4908
5606
|
this.value = value;
|
|
4909
5607
|
this.input.value = this.#formatWithUnit(this.value);
|
|
4910
5608
|
this.#syncStepperState();
|
|
5609
|
+
this.#syncSpinbuttonAria();
|
|
4911
5610
|
|
|
4912
5611
|
this.dispatchEvent(
|
|
4913
5612
|
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
@@ -4925,6 +5624,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
4925
5624
|
this.value = "";
|
|
4926
5625
|
}
|
|
4927
5626
|
this.#syncStepperState();
|
|
5627
|
+
this.#syncSpinbuttonAria();
|
|
4928
5628
|
this.dispatchEvent(
|
|
4929
5629
|
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
4930
5630
|
);
|
|
@@ -4943,6 +5643,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
4943
5643
|
e.target.value = "";
|
|
4944
5644
|
}
|
|
4945
5645
|
this.#syncStepperState();
|
|
5646
|
+
this.#syncSpinbuttonAria();
|
|
4946
5647
|
this.dispatchEvent(
|
|
4947
5648
|
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
4948
5649
|
);
|
|
@@ -4964,6 +5665,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
4964
5665
|
this.value = value;
|
|
4965
5666
|
this.input.value = this.#formatWithUnit(this.value);
|
|
4966
5667
|
this.#syncStepperState();
|
|
5668
|
+
this.#syncSpinbuttonAria();
|
|
4967
5669
|
this.dispatchEvent(
|
|
4968
5670
|
new CustomEvent("input", { detail: this.value, bubbles: true }),
|
|
4969
5671
|
);
|
|
@@ -5037,6 +5739,11 @@ class FigInputNumber extends HTMLElement {
|
|
|
5037
5739
|
"unit-position",
|
|
5038
5740
|
"steppers",
|
|
5039
5741
|
"precision",
|
|
5742
|
+
"aria-label",
|
|
5743
|
+
"aria-labelledby",
|
|
5744
|
+
"aria-describedby",
|
|
5745
|
+
"aria-invalid",
|
|
5746
|
+
"aria-required",
|
|
5040
5747
|
];
|
|
5041
5748
|
}
|
|
5042
5749
|
|
|
@@ -5052,6 +5759,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
5052
5759
|
this.#rawUnits = newValue || "";
|
|
5053
5760
|
this.#setUnitsFromAttributes();
|
|
5054
5761
|
this.input.value = this.#formatWithUnit(this.value);
|
|
5762
|
+
this.#syncSpinbuttonAria();
|
|
5055
5763
|
break;
|
|
5056
5764
|
case "units-disallow":
|
|
5057
5765
|
this.#unitsDisallow = this.#parseUnitsDisallowList(
|
|
@@ -5061,14 +5769,17 @@ class FigInputNumber extends HTMLElement {
|
|
|
5061
5769
|
);
|
|
5062
5770
|
this.#setUnitsFromAttributes();
|
|
5063
5771
|
this.input.value = this.#formatWithUnit(this.value);
|
|
5772
|
+
this.#syncSpinbuttonAria();
|
|
5064
5773
|
break;
|
|
5065
5774
|
case "unit-position":
|
|
5066
5775
|
this.#unitPosition = newValue || "suffix";
|
|
5067
5776
|
this.input.value = this.#formatWithUnit(this.value);
|
|
5777
|
+
this.#syncSpinbuttonAria();
|
|
5068
5778
|
break;
|
|
5069
5779
|
case "transform":
|
|
5070
5780
|
this.transform = Number(newValue) || 1;
|
|
5071
5781
|
this.input.value = this.#formatWithUnit(this.value);
|
|
5782
|
+
this.#syncSpinbuttonAria();
|
|
5072
5783
|
break;
|
|
5073
5784
|
case "value":
|
|
5074
5785
|
if (this.#isInteracting) break;
|
|
@@ -5080,6 +5791,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
5080
5791
|
this.value = value;
|
|
5081
5792
|
this.input.value = this.#formatWithUnit(this.value);
|
|
5082
5793
|
this.#syncStepperState();
|
|
5794
|
+
this.#syncSpinbuttonAria();
|
|
5083
5795
|
break;
|
|
5084
5796
|
case "min":
|
|
5085
5797
|
case "max":
|
|
@@ -5087,10 +5799,12 @@ class FigInputNumber extends HTMLElement {
|
|
|
5087
5799
|
if (newValue === null || newValue === "") {
|
|
5088
5800
|
this[name] = undefined;
|
|
5089
5801
|
this.#syncStepperState();
|
|
5802
|
+
this.#syncSpinbuttonAria();
|
|
5090
5803
|
break;
|
|
5091
5804
|
}
|
|
5092
5805
|
this[name] = Number(newValue);
|
|
5093
5806
|
this.#syncStepperState();
|
|
5807
|
+
this.#syncSpinbuttonAria();
|
|
5094
5808
|
break;
|
|
5095
5809
|
case "steppers": {
|
|
5096
5810
|
const hasSteppers = newValue !== null && newValue !== "false";
|
|
@@ -5100,6 +5814,7 @@ class FigInputNumber extends HTMLElement {
|
|
|
5100
5814
|
case "precision":
|
|
5101
5815
|
this.#precision = newValue !== null ? Number(newValue) : 2;
|
|
5102
5816
|
this.input.value = this.#formatWithUnit(this.value);
|
|
5817
|
+
this.#syncSpinbuttonAria();
|
|
5103
5818
|
break;
|
|
5104
5819
|
case "name":
|
|
5105
5820
|
this[name] = this.input[name] = newValue;
|
|
@@ -5109,6 +5824,13 @@ class FigInputNumber extends HTMLElement {
|
|
|
5109
5824
|
this.placeholder = newValue ?? "";
|
|
5110
5825
|
this.input.placeholder = this.placeholder;
|
|
5111
5826
|
break;
|
|
5827
|
+
case "aria-label":
|
|
5828
|
+
case "aria-labelledby":
|
|
5829
|
+
case "aria-describedby":
|
|
5830
|
+
case "aria-invalid":
|
|
5831
|
+
case "aria-required":
|
|
5832
|
+
this.#syncInputA11yAttributes();
|
|
5833
|
+
break;
|
|
5112
5834
|
default:
|
|
5113
5835
|
this[name] = this.input[name] = newValue;
|
|
5114
5836
|
break;
|
|
@@ -5129,9 +5851,7 @@ class FigAvatar extends HTMLElement {
|
|
|
5129
5851
|
this.initials = this.getInitials(this.name);
|
|
5130
5852
|
this.setAttribute("initials", this.initials);
|
|
5131
5853
|
this.setSrc(this.src);
|
|
5132
|
-
|
|
5133
|
-
this.img = this.querySelector("img");
|
|
5134
|
-
});
|
|
5854
|
+
this.img = this.querySelector("img");
|
|
5135
5855
|
}
|
|
5136
5856
|
setSrc(src) {
|
|
5137
5857
|
this.src = src;
|
|
@@ -5172,9 +5892,15 @@ class FigField extends HTMLElement {
|
|
|
5172
5892
|
#toggleable = false;
|
|
5173
5893
|
#chevron = null;
|
|
5174
5894
|
#boundToggle = null;
|
|
5895
|
+
#boundFocus = null;
|
|
5896
|
+
#boundLabelEnter = null;
|
|
5897
|
+
#boundLabelLeave = null;
|
|
5175
5898
|
|
|
5176
5899
|
constructor() {
|
|
5177
5900
|
super();
|
|
5901
|
+
this.#boundFocus = this.focus.bind(this);
|
|
5902
|
+
this.#boundLabelEnter = this.#onLabelEnter.bind(this);
|
|
5903
|
+
this.#boundLabelLeave = this.#onLabelLeave.bind(this);
|
|
5178
5904
|
}
|
|
5179
5905
|
|
|
5180
5906
|
static get observedAttributes() {
|
|
@@ -5182,51 +5908,52 @@ class FigField extends HTMLElement {
|
|
|
5182
5908
|
}
|
|
5183
5909
|
|
|
5184
5910
|
connectedCallback() {
|
|
5185
|
-
|
|
5186
|
-
this.
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5911
|
+
queueMicrotask(() => {
|
|
5912
|
+
if (this.isConnected) this.#setup();
|
|
5913
|
+
});
|
|
5914
|
+
}
|
|
5915
|
+
|
|
5916
|
+
#setup() {
|
|
5917
|
+
this.label = this.querySelector(":scope>label");
|
|
5918
|
+
this.input = Array.from(this.childNodes).find((node) =>
|
|
5919
|
+
node.nodeName.toLowerCase().startsWith("fig-"),
|
|
5920
|
+
);
|
|
5190
5921
|
|
|
5191
|
-
|
|
5922
|
+
this.#toggleable = !!(this.input && "open" in this.input);
|
|
5192
5923
|
|
|
5193
|
-
|
|
5924
|
+
if (this.#toggleable && this.label) {
|
|
5925
|
+
if (!this.#chevron || !this.#chevron.isConnected) {
|
|
5194
5926
|
this.#chevron = createFigIcon("chevron", {
|
|
5195
5927
|
size: "small",
|
|
5196
5928
|
className: "fig-field-chevron",
|
|
5197
5929
|
});
|
|
5198
5930
|
this.insertBefore(this.#chevron, this.label);
|
|
5199
|
-
|
|
5200
|
-
this.#boundToggle = (e) => {
|
|
5201
|
-
e.preventDefault();
|
|
5202
|
-
e.stopPropagation();
|
|
5203
|
-
if (this.input && typeof this.input.open !== "undefined") {
|
|
5204
|
-
this.input.open = !this.input.open;
|
|
5205
|
-
}
|
|
5206
|
-
};
|
|
5207
|
-
this.#chevron.addEventListener("click", this.#boundToggle);
|
|
5208
|
-
this.label.addEventListener("click", this.#boundToggle);
|
|
5209
|
-
} else if (this.input && this.label) {
|
|
5210
|
-
this.label.addEventListener("click", this.focus.bind(this));
|
|
5211
5931
|
}
|
|
5212
5932
|
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
this.
|
|
5217
|
-
|
|
5933
|
+
this.#boundToggle = (e) => {
|
|
5934
|
+
e.preventDefault();
|
|
5935
|
+
e.stopPropagation();
|
|
5936
|
+
if (this.input && typeof this.input.open !== "undefined") {
|
|
5937
|
+
this.input.open = !this.input.open;
|
|
5938
|
+
}
|
|
5939
|
+
};
|
|
5940
|
+
this.#chevron.addEventListener("click", this.#boundToggle);
|
|
5941
|
+
this.label.addEventListener("click", this.#boundToggle);
|
|
5942
|
+
} else if (this.input && this.label) {
|
|
5943
|
+
this.label.removeEventListener("click", this.#boundFocus);
|
|
5944
|
+
this.label.addEventListener("click", this.#boundFocus);
|
|
5945
|
+
}
|
|
5946
|
+
|
|
5947
|
+
if (this.input && this.label && !this.#toggleable) {
|
|
5948
|
+
this.#syncLabelAssociation();
|
|
5949
|
+
}
|
|
5218
5950
|
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
"pointerleave",
|
|
5226
|
-
this.#onLabelLeave.bind(this),
|
|
5227
|
-
);
|
|
5228
|
-
}
|
|
5229
|
-
});
|
|
5951
|
+
if (this.label) {
|
|
5952
|
+
this.label.removeEventListener("pointerenter", this.#boundLabelEnter);
|
|
5953
|
+
this.label.addEventListener("pointerenter", this.#boundLabelEnter);
|
|
5954
|
+
this.label.removeEventListener("pointerleave", this.#boundLabelLeave);
|
|
5955
|
+
this.label.addEventListener("pointerleave", this.#boundLabelLeave);
|
|
5956
|
+
}
|
|
5230
5957
|
}
|
|
5231
5958
|
|
|
5232
5959
|
disconnectedCallback() {
|
|
@@ -5234,6 +5961,15 @@ class FigField extends HTMLElement {
|
|
|
5234
5961
|
if (this.label && this.#boundToggle) {
|
|
5235
5962
|
this.label.removeEventListener("click", this.#boundToggle);
|
|
5236
5963
|
}
|
|
5964
|
+
if (this.label && this.#boundFocus) {
|
|
5965
|
+
this.label.removeEventListener("click", this.#boundFocus);
|
|
5966
|
+
}
|
|
5967
|
+
if (this.label && this.#boundLabelEnter) {
|
|
5968
|
+
this.label.removeEventListener("pointerenter", this.#boundLabelEnter);
|
|
5969
|
+
}
|
|
5970
|
+
if (this.label && this.#boundLabelLeave) {
|
|
5971
|
+
this.label.removeEventListener("pointerleave", this.#boundLabelLeave);
|
|
5972
|
+
}
|
|
5237
5973
|
if (this.#chevron && this.#boundToggle) {
|
|
5238
5974
|
this.#chevron.removeEventListener("click", this.#boundToggle);
|
|
5239
5975
|
}
|
|
@@ -5248,11 +5984,36 @@ class FigField extends HTMLElement {
|
|
|
5248
5984
|
if (this.label) FigTooltip.hide(this.label);
|
|
5249
5985
|
}
|
|
5250
5986
|
|
|
5987
|
+
#syncLabelAssociation() {
|
|
5988
|
+
if (!this.input || !this.label) return;
|
|
5989
|
+
const labelId = this.label.getAttribute("id") || figUniqueId();
|
|
5990
|
+
this.label.setAttribute("id", labelId);
|
|
5991
|
+
const nativeInputs = this.input.querySelectorAll("input, select, textarea");
|
|
5992
|
+
if (nativeInputs.length === 1) {
|
|
5993
|
+
const nativeInput = nativeInputs[0];
|
|
5994
|
+
const inputId = nativeInput.getAttribute("id") || figUniqueId();
|
|
5995
|
+
nativeInput.setAttribute("id", inputId);
|
|
5996
|
+
this.label.setAttribute("for", inputId);
|
|
5997
|
+
if (this.input.getAttribute("aria-labelledby") === labelId) {
|
|
5998
|
+
this.input.removeAttribute("aria-labelledby");
|
|
5999
|
+
}
|
|
6000
|
+
if (!nativeInput.hasAttribute("aria-labelledby")) {
|
|
6001
|
+
nativeInput.setAttribute("aria-labelledby", labelId);
|
|
6002
|
+
}
|
|
6003
|
+
return;
|
|
6004
|
+
}
|
|
6005
|
+
this.label.removeAttribute("for");
|
|
6006
|
+
if (!this.input.hasAttribute("aria-label") && !this.input.hasAttribute("aria-labelledby")) {
|
|
6007
|
+
this.input.setAttribute("aria-labelledby", labelId);
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
|
|
5251
6011
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
5252
6012
|
switch (name) {
|
|
5253
6013
|
case "label":
|
|
5254
6014
|
if (this.label) {
|
|
5255
6015
|
this.label.textContent = newValue;
|
|
6016
|
+
this.#syncLabelAssociation();
|
|
5256
6017
|
}
|
|
5257
6018
|
break;
|
|
5258
6019
|
}
|
|
@@ -5323,15 +6084,9 @@ class FigInputColor extends HTMLElement {
|
|
|
5323
6084
|
}
|
|
5324
6085
|
|
|
5325
6086
|
connectedCallback() {
|
|
5326
|
-
|
|
5327
|
-
this.#renderRAF = requestAnimationFrame(() => {
|
|
5328
|
-
this.#renderRAF = null;
|
|
5329
|
-
this.#buildUI();
|
|
5330
|
-
});
|
|
6087
|
+
this.#buildUI();
|
|
5331
6088
|
}
|
|
5332
6089
|
|
|
5333
|
-
#renderRAF = null;
|
|
5334
|
-
|
|
5335
6090
|
#buildUI() {
|
|
5336
6091
|
this.#setValues(this.getAttribute("value"));
|
|
5337
6092
|
|
|
@@ -5371,61 +6126,60 @@ class FigInputColor extends HTMLElement {
|
|
|
5371
6126
|
}
|
|
5372
6127
|
this.innerHTML = html;
|
|
5373
6128
|
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
6129
|
+
this.#swatch = this.querySelector("fig-chit");
|
|
6130
|
+
this.#fillPicker = this.querySelector("fig-fill-picker");
|
|
6131
|
+
this.#textInput = this.querySelector("fig-input-text:not([type=number])");
|
|
6132
|
+
this.#alphaInput = this.querySelector("fig-input-number");
|
|
6133
|
+
this.#syncA11yAttributes();
|
|
5379
6134
|
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
}
|
|
5387
|
-
if (this.hasAttribute("swatch-disabled")) {
|
|
5388
|
-
swatchInput?.setAttribute("disabled", "");
|
|
5389
|
-
if (swatchInput) swatchInput.style.pointerEvents = "none";
|
|
5390
|
-
}
|
|
5391
|
-
this.#swatch.addEventListener("pointerdown", this.#handleSwatchPointerDown.bind(this), {
|
|
5392
|
-
capture: true,
|
|
5393
|
-
});
|
|
5394
|
-
this.#swatch.addEventListener("click", this.#handleSwatchClick.bind(this), {
|
|
5395
|
-
capture: true,
|
|
5396
|
-
});
|
|
5397
|
-
swatchInput?.addEventListener("keydown", this.#handleSwatchKeyDown.bind(this));
|
|
5398
|
-
this.#swatch.addEventListener("input", this.#handleInput.bind(this));
|
|
6135
|
+
// Setup swatch (native picker)
|
|
6136
|
+
if (this.#swatch) {
|
|
6137
|
+
this.#swatch.disabled = this.hasAttribute("disabled");
|
|
6138
|
+
const swatchInput = this.#swatch.querySelector('input[type="color"]');
|
|
6139
|
+
if (this.#textInput || this.hasAttribute("swatch-disabled")) {
|
|
6140
|
+
swatchInput?.setAttribute("tabindex", "-1");
|
|
5399
6141
|
}
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
// Display without # prefix
|
|
5404
|
-
this.#textInput.value = hex.slice(1).toUpperCase();
|
|
5405
|
-
if (this.#swatch) {
|
|
5406
|
-
this.#swatch.background = hex;
|
|
5407
|
-
}
|
|
5408
|
-
this.#textInput.addEventListener(
|
|
5409
|
-
"input",
|
|
5410
|
-
this.#handleTextInput.bind(this),
|
|
5411
|
-
);
|
|
5412
|
-
this.#textInput.addEventListener(
|
|
5413
|
-
"change",
|
|
5414
|
-
this.#handleChange.bind(this),
|
|
5415
|
-
);
|
|
6142
|
+
if (this.hasAttribute("swatch-disabled")) {
|
|
6143
|
+
swatchInput?.setAttribute("disabled", "");
|
|
6144
|
+
if (swatchInput) swatchInput.style.pointerEvents = "none";
|
|
5416
6145
|
}
|
|
6146
|
+
this.#swatch.addEventListener("pointerdown", this.#handleSwatchPointerDown.bind(this), {
|
|
6147
|
+
capture: true,
|
|
6148
|
+
});
|
|
6149
|
+
this.#swatch.addEventListener("click", this.#handleSwatchClick.bind(this), {
|
|
6150
|
+
capture: true,
|
|
6151
|
+
});
|
|
6152
|
+
swatchInput?.addEventListener("keydown", this.#handleSwatchKeyDown.bind(this));
|
|
6153
|
+
this.#swatch.addEventListener("input", this.#handleInput.bind(this));
|
|
6154
|
+
}
|
|
5417
6155
|
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
this.#
|
|
5424
|
-
"change",
|
|
5425
|
-
this.#handleChange.bind(this),
|
|
5426
|
-
);
|
|
6156
|
+
if (this.#textInput) {
|
|
6157
|
+
const hex = this.rgbAlphaToHex(this.rgba, 1);
|
|
6158
|
+
// Display without # prefix
|
|
6159
|
+
this.#textInput.value = hex.slice(1).toUpperCase();
|
|
6160
|
+
if (this.#swatch) {
|
|
6161
|
+
this.#swatch.background = hex;
|
|
5427
6162
|
}
|
|
5428
|
-
|
|
6163
|
+
this.#textInput.addEventListener(
|
|
6164
|
+
"input",
|
|
6165
|
+
this.#handleTextInput.bind(this),
|
|
6166
|
+
);
|
|
6167
|
+
this.#textInput.addEventListener(
|
|
6168
|
+
"change",
|
|
6169
|
+
this.#handleChange.bind(this),
|
|
6170
|
+
);
|
|
6171
|
+
}
|
|
6172
|
+
|
|
6173
|
+
if (this.#alphaInput) {
|
|
6174
|
+
this.#alphaInput.addEventListener(
|
|
6175
|
+
"input",
|
|
6176
|
+
this.#handleAlphaInput.bind(this),
|
|
6177
|
+
);
|
|
6178
|
+
this.#alphaInput.addEventListener(
|
|
6179
|
+
"change",
|
|
6180
|
+
this.#handleChange.bind(this),
|
|
6181
|
+
);
|
|
6182
|
+
}
|
|
5429
6183
|
}
|
|
5430
6184
|
|
|
5431
6185
|
#syncFillPicker() {
|
|
@@ -5590,6 +6344,56 @@ class FigInputColor extends HTMLElement {
|
|
|
5590
6344
|
this.#swatch?.focus();
|
|
5591
6345
|
}
|
|
5592
6346
|
|
|
6347
|
+
#accessibleName() {
|
|
6348
|
+
return this.getAttribute("aria-label") || "Color";
|
|
6349
|
+
}
|
|
6350
|
+
|
|
6351
|
+
#syncA11yAttributes() {
|
|
6352
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "group");
|
|
6353
|
+
if (this.#disabled) this.setAttribute("aria-disabled", "true");
|
|
6354
|
+
else this.removeAttribute("aria-disabled");
|
|
6355
|
+
|
|
6356
|
+
const describedBy = this.getAttribute("aria-describedby");
|
|
6357
|
+
const invalid = this.getAttribute("aria-invalid");
|
|
6358
|
+
const required = this.getAttribute("aria-required");
|
|
6359
|
+
const labelledBy = this.getAttribute("aria-labelledby");
|
|
6360
|
+
const name = this.#accessibleName();
|
|
6361
|
+
|
|
6362
|
+
if (this.#textInput) {
|
|
6363
|
+
this.#textInput.setAttribute("aria-label", `${name} hex color`);
|
|
6364
|
+
if (describedBy) this.#textInput.setAttribute("aria-describedby", describedBy);
|
|
6365
|
+
else this.#textInput.removeAttribute("aria-describedby");
|
|
6366
|
+
if (invalid) this.#textInput.setAttribute("aria-invalid", invalid);
|
|
6367
|
+
else this.#textInput.removeAttribute("aria-invalid");
|
|
6368
|
+
if (required) this.#textInput.setAttribute("aria-required", required);
|
|
6369
|
+
else this.#textInput.removeAttribute("aria-required");
|
|
6370
|
+
}
|
|
6371
|
+
|
|
6372
|
+
if (this.#alphaInput) {
|
|
6373
|
+
this.#alphaInput.setAttribute("aria-label", `${name} opacity`);
|
|
6374
|
+
if (describedBy) this.#alphaInput.setAttribute("aria-describedby", describedBy);
|
|
6375
|
+
else this.#alphaInput.removeAttribute("aria-describedby");
|
|
6376
|
+
if (invalid) this.#alphaInput.setAttribute("aria-invalid", invalid);
|
|
6377
|
+
else this.#alphaInput.removeAttribute("aria-invalid");
|
|
6378
|
+
if (required) this.#alphaInput.setAttribute("aria-required", required);
|
|
6379
|
+
else this.#alphaInput.removeAttribute("aria-required");
|
|
6380
|
+
}
|
|
6381
|
+
|
|
6382
|
+
if (!this.#textInput) {
|
|
6383
|
+
const swatchInput = this.#swatch?.querySelector('input[type="color"]');
|
|
6384
|
+
if (!swatchInput) return;
|
|
6385
|
+
if (labelledBy) {
|
|
6386
|
+
swatchInput.setAttribute("aria-labelledby", labelledBy);
|
|
6387
|
+
swatchInput.removeAttribute("aria-label");
|
|
6388
|
+
} else {
|
|
6389
|
+
swatchInput.setAttribute("aria-label", name);
|
|
6390
|
+
swatchInput.removeAttribute("aria-labelledby");
|
|
6391
|
+
}
|
|
6392
|
+
if (describedBy) swatchInput.setAttribute("aria-describedby", describedBy);
|
|
6393
|
+
else swatchInput.removeAttribute("aria-describedby");
|
|
6394
|
+
}
|
|
6395
|
+
}
|
|
6396
|
+
|
|
5593
6397
|
#handleInput(event) {
|
|
5594
6398
|
//do not propagate to onInput handler for web component
|
|
5595
6399
|
event.stopPropagation();
|
|
@@ -5662,6 +6466,11 @@ class FigInputColor extends HTMLElement {
|
|
|
5662
6466
|
"alpha",
|
|
5663
6467
|
"text",
|
|
5664
6468
|
"disabled",
|
|
6469
|
+
"aria-label",
|
|
6470
|
+
"aria-labelledby",
|
|
6471
|
+
"aria-describedby",
|
|
6472
|
+
"aria-invalid",
|
|
6473
|
+
"aria-required",
|
|
5665
6474
|
];
|
|
5666
6475
|
}
|
|
5667
6476
|
|
|
@@ -5713,6 +6522,13 @@ class FigInputColor extends HTMLElement {
|
|
|
5713
6522
|
case "disabled":
|
|
5714
6523
|
this.#syncDisabled();
|
|
5715
6524
|
break;
|
|
6525
|
+
case "aria-label":
|
|
6526
|
+
case "aria-labelledby":
|
|
6527
|
+
case "aria-describedby":
|
|
6528
|
+
case "aria-invalid":
|
|
6529
|
+
case "aria-required":
|
|
6530
|
+
this.#syncA11yAttributes();
|
|
6531
|
+
break;
|
|
5716
6532
|
}
|
|
5717
6533
|
}
|
|
5718
6534
|
|
|
@@ -5729,6 +6545,7 @@ class FigInputColor extends HTMLElement {
|
|
|
5729
6545
|
if (disabled) child.setAttribute("disabled", "");
|
|
5730
6546
|
else child.removeAttribute("disabled");
|
|
5731
6547
|
}
|
|
6548
|
+
this.#syncA11yAttributes();
|
|
5732
6549
|
if (this.#fillPicker) {
|
|
5733
6550
|
this.#syncFillPicker();
|
|
5734
6551
|
}
|
|
@@ -5848,18 +6665,15 @@ const GRADIENT_HUE_INTERPOLATIONS = [
|
|
|
5848
6665
|
"decreasing",
|
|
5849
6666
|
];
|
|
5850
6667
|
|
|
5851
|
-
const GRADIENT_PICKER_SPACES = ["srgb-linear", "oklab", "oklch"];
|
|
6668
|
+
const GRADIENT_PICKER_SPACES = ["srgb", "srgb-linear", "oklab", "oklch"];
|
|
5852
6669
|
|
|
5853
6670
|
function normalizeGradientConfig(gradient) {
|
|
5854
6671
|
const next = { ...(gradient ?? {}) };
|
|
5855
6672
|
let interpolationSpace = String(
|
|
5856
|
-
next.interpolationSpace ?? "
|
|
6673
|
+
next.interpolationSpace ?? "srgb",
|
|
5857
6674
|
).toLowerCase();
|
|
5858
6675
|
if (!GRADIENT_INTERPOLATION_SPACES.includes(interpolationSpace)) {
|
|
5859
|
-
interpolationSpace = "
|
|
5860
|
-
}
|
|
5861
|
-
if (interpolationSpace === "srgb" || interpolationSpace === "display-p3") {
|
|
5862
|
-
interpolationSpace = "oklab";
|
|
6676
|
+
interpolationSpace = "srgb";
|
|
5863
6677
|
}
|
|
5864
6678
|
next.interpolationSpace = interpolationSpace;
|
|
5865
6679
|
|
|
@@ -5888,6 +6702,9 @@ function gradientToValueShape(gradient) {
|
|
|
5888
6702
|
|
|
5889
6703
|
function gradientInterpolationClause(gradient) {
|
|
5890
6704
|
const normalized = normalizeGradientConfig(gradient);
|
|
6705
|
+
if (normalized.interpolationSpace === "srgb") {
|
|
6706
|
+
return "";
|
|
6707
|
+
}
|
|
5891
6708
|
if (normalized.interpolationSpace === "oklch") {
|
|
5892
6709
|
return `in oklch ${normalized.hueInterpolation} hue`;
|
|
5893
6710
|
}
|
|
@@ -6115,7 +6932,7 @@ class FigInputFill extends HTMLElement {
|
|
|
6115
6932
|
#gradient = {
|
|
6116
6933
|
type: "linear",
|
|
6117
6934
|
angle: 180,
|
|
6118
|
-
interpolationSpace: "
|
|
6935
|
+
interpolationSpace: "srgb",
|
|
6119
6936
|
hueInterpolation: "shorter",
|
|
6120
6937
|
stops: [
|
|
6121
6938
|
{ position: 0, color: "#D9D9D9", opacity: 100 },
|
|
@@ -6131,10 +6948,21 @@ class FigInputFill extends HTMLElement {
|
|
|
6131
6948
|
}
|
|
6132
6949
|
|
|
6133
6950
|
static get observedAttributes() {
|
|
6134
|
-
return [
|
|
6951
|
+
return [
|
|
6952
|
+
"value",
|
|
6953
|
+
"disabled",
|
|
6954
|
+
"mode",
|
|
6955
|
+
"experimental",
|
|
6956
|
+
"alpha",
|
|
6957
|
+
"aria-label",
|
|
6958
|
+
"aria-describedby",
|
|
6959
|
+
"aria-invalid",
|
|
6960
|
+
"aria-required",
|
|
6961
|
+
];
|
|
6135
6962
|
}
|
|
6136
6963
|
|
|
6137
6964
|
connectedCallback() {
|
|
6965
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "group");
|
|
6138
6966
|
this.#parseValue();
|
|
6139
6967
|
this.#render();
|
|
6140
6968
|
}
|
|
@@ -6257,6 +7085,7 @@ class FigInputFill extends HTMLElement {
|
|
|
6257
7085
|
|
|
6258
7086
|
#syncDisabled() {
|
|
6259
7087
|
const disabled = this.hasAttribute("disabled");
|
|
7088
|
+
this.setAttribute("aria-disabled", disabled ? "true" : "false");
|
|
6260
7089
|
for (const child of [
|
|
6261
7090
|
this.#fillPicker,
|
|
6262
7091
|
this.#opacityInput,
|
|
@@ -6268,6 +7097,28 @@ class FigInputFill extends HTMLElement {
|
|
|
6268
7097
|
}
|
|
6269
7098
|
}
|
|
6270
7099
|
|
|
7100
|
+
#syncA11y() {
|
|
7101
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "group");
|
|
7102
|
+
this.#syncDisabled();
|
|
7103
|
+
const name = this.getAttribute("aria-label") || "Fill";
|
|
7104
|
+
const describedBy = this.getAttribute("aria-describedby");
|
|
7105
|
+
const invalid = this.getAttribute("aria-invalid");
|
|
7106
|
+
const required = this.getAttribute("aria-required");
|
|
7107
|
+
const syncState = (el, label) => {
|
|
7108
|
+
if (!el) return;
|
|
7109
|
+
el.setAttribute("aria-label", label);
|
|
7110
|
+
if (describedBy) el.setAttribute("aria-describedby", describedBy);
|
|
7111
|
+
else el.removeAttribute("aria-describedby");
|
|
7112
|
+
if (invalid) el.setAttribute("aria-invalid", invalid);
|
|
7113
|
+
else el.removeAttribute("aria-invalid");
|
|
7114
|
+
if (required) el.setAttribute("aria-required", required);
|
|
7115
|
+
else el.removeAttribute("aria-required");
|
|
7116
|
+
};
|
|
7117
|
+
syncState(this.#fillPicker, `${name} picker`);
|
|
7118
|
+
syncState(this.#hexInput, `${name} hex color`);
|
|
7119
|
+
syncState(this.#opacityInput, `${name} opacity`);
|
|
7120
|
+
}
|
|
7121
|
+
|
|
6271
7122
|
#render() {
|
|
6272
7123
|
const disabled = this.hasAttribute("disabled");
|
|
6273
7124
|
const fillPickerValue = JSON.stringify(this.value);
|
|
@@ -6347,127 +7198,126 @@ class FigInputFill extends HTMLElement {
|
|
|
6347
7198
|
}
|
|
6348
7199
|
|
|
6349
7200
|
#setupEventListeners() {
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
7201
|
+
this.#fillPicker = this.querySelector("fig-fill-picker");
|
|
7202
|
+
this.#opacityInput = this.querySelector(".fig-input-fill-opacity");
|
|
7203
|
+
this.#hexInput = this.querySelector(".fig-input-fill-hex");
|
|
7204
|
+
const label = this.querySelector(".fig-input-fill-label");
|
|
7205
|
+
this.#syncA11y();
|
|
7206
|
+
|
|
7207
|
+
// Label click triggers fill picker
|
|
7208
|
+
if (label && this.#fillPicker) {
|
|
7209
|
+
label.addEventListener("click", () => {
|
|
7210
|
+
const chit = this.#fillPicker.querySelector("fig-chit");
|
|
7211
|
+
if (chit) {
|
|
7212
|
+
chit.click();
|
|
7213
|
+
}
|
|
7214
|
+
});
|
|
7215
|
+
}
|
|
7216
|
+
|
|
7217
|
+
if (this.#fillPicker) {
|
|
7218
|
+
const anchor = this.getAttribute("picker-anchor");
|
|
7219
|
+
if (!anchor || anchor === "self") {
|
|
7220
|
+
this.#fillPicker.anchorElement = this;
|
|
7221
|
+
} else {
|
|
7222
|
+
const el = document.querySelector(anchor);
|
|
7223
|
+
if (el) this.#fillPicker.anchorElement = el;
|
|
6364
7224
|
}
|
|
6365
7225
|
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
7226
|
+
this.#fillPicker.addEventListener("input", (e) => {
|
|
7227
|
+
e.stopPropagation();
|
|
7228
|
+
const detail = e.detail;
|
|
7229
|
+
if (!detail) return;
|
|
7230
|
+
|
|
7231
|
+
const newType = detail.type;
|
|
7232
|
+
const typeChanged = newType !== this.#fillType;
|
|
7233
|
+
|
|
7234
|
+
// Update internal state
|
|
7235
|
+
this.#fillType = newType;
|
|
7236
|
+
switch (newType) {
|
|
7237
|
+
case "solid":
|
|
7238
|
+
this.#solid.color = detail.color;
|
|
7239
|
+
this.#solid.alpha = detail.alpha;
|
|
7240
|
+
break;
|
|
7241
|
+
case "gradient":
|
|
7242
|
+
if (detail.gradient) {
|
|
7243
|
+
this.#gradient = normalizeGradientConfig({
|
|
7244
|
+
...this.#gradient,
|
|
7245
|
+
...detail.gradient,
|
|
7246
|
+
});
|
|
7247
|
+
}
|
|
7248
|
+
break;
|
|
7249
|
+
case "image":
|
|
7250
|
+
if (detail.image) this.#image = detail.image;
|
|
7251
|
+
break;
|
|
7252
|
+
case "video":
|
|
7253
|
+
if (detail.video) this.#video = detail.video;
|
|
7254
|
+
break;
|
|
6373
7255
|
}
|
|
6374
7256
|
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
const typeChanged = newType !== this.#fillType;
|
|
6382
|
-
|
|
6383
|
-
// Update internal state
|
|
6384
|
-
this.#fillType = newType;
|
|
6385
|
-
switch (newType) {
|
|
6386
|
-
case "solid":
|
|
6387
|
-
this.#solid.color = detail.color;
|
|
6388
|
-
this.#solid.alpha = detail.alpha;
|
|
6389
|
-
break;
|
|
6390
|
-
case "gradient":
|
|
6391
|
-
if (detail.gradient) {
|
|
6392
|
-
this.#gradient = normalizeGradientConfig({
|
|
6393
|
-
...this.#gradient,
|
|
6394
|
-
...detail.gradient,
|
|
6395
|
-
});
|
|
6396
|
-
}
|
|
6397
|
-
break;
|
|
6398
|
-
case "image":
|
|
6399
|
-
if (detail.image) this.#image = detail.image;
|
|
6400
|
-
break;
|
|
6401
|
-
case "video":
|
|
6402
|
-
if (detail.video) this.#video = detail.video;
|
|
6403
|
-
break;
|
|
6404
|
-
}
|
|
6405
|
-
|
|
6406
|
-
// Update controls (don't re-render to keep dialog open)
|
|
6407
|
-
if (typeChanged) {
|
|
6408
|
-
this.#updateControlsForType();
|
|
6409
|
-
} else {
|
|
6410
|
-
this.#updateControls();
|
|
6411
|
-
}
|
|
7257
|
+
// Update controls (don't re-render to keep dialog open)
|
|
7258
|
+
if (typeChanged) {
|
|
7259
|
+
this.#updateControlsForType();
|
|
7260
|
+
} else {
|
|
7261
|
+
this.#updateControls();
|
|
7262
|
+
}
|
|
6412
7263
|
|
|
6413
|
-
|
|
6414
|
-
|
|
7264
|
+
this.#emitInput();
|
|
7265
|
+
});
|
|
6415
7266
|
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
7267
|
+
this.#fillPicker.addEventListener("change", (e) => {
|
|
7268
|
+
e.stopPropagation();
|
|
7269
|
+
this.#emitChange();
|
|
7270
|
+
});
|
|
7271
|
+
}
|
|
6421
7272
|
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
7273
|
+
// Hex input (solid only)
|
|
7274
|
+
if (this.#hexInput) {
|
|
7275
|
+
this.#hexInput.addEventListener("input", (e) => {
|
|
7276
|
+
e.stopPropagation();
|
|
7277
|
+
const hex = "#" + e.target.value.replace("#", "");
|
|
7278
|
+
this.#solid.color = hex;
|
|
7279
|
+
this.#updateFillPicker();
|
|
7280
|
+
this.#emitInput();
|
|
7281
|
+
});
|
|
7282
|
+
this.#hexInput.addEventListener("change", (e) => {
|
|
7283
|
+
e.stopPropagation();
|
|
7284
|
+
this.#emitChange();
|
|
7285
|
+
});
|
|
7286
|
+
}
|
|
6436
7287
|
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
});
|
|
7288
|
+
// Opacity input (all fill types)
|
|
7289
|
+
if (this.#opacityInput) {
|
|
7290
|
+
this.#opacityInput.addEventListener("input", (e) => {
|
|
7291
|
+
e.stopPropagation();
|
|
7292
|
+
const parsed = parseFloat(e.target.value);
|
|
7293
|
+
const opacity = isNaN(parsed) ? 100 : parsed;
|
|
7294
|
+
const alpha = opacity / 100;
|
|
7295
|
+
switch (this.#fillType) {
|
|
7296
|
+
case "solid":
|
|
7297
|
+
this.#solid.alpha = alpha;
|
|
7298
|
+
break;
|
|
7299
|
+
case "gradient":
|
|
7300
|
+
break;
|
|
7301
|
+
case "image":
|
|
7302
|
+
this.#image.opacity = alpha;
|
|
7303
|
+
break;
|
|
7304
|
+
case "video":
|
|
7305
|
+
this.#video.opacity = alpha;
|
|
7306
|
+
break;
|
|
7307
|
+
case "webcam":
|
|
7308
|
+
this.#webcam.opacity = alpha;
|
|
7309
|
+
break;
|
|
7310
|
+
}
|
|
7311
|
+
this.#updateFillPicker();
|
|
7312
|
+
// Update the chit's alpha
|
|
7313
|
+
this.#updateChitAlpha(alpha);
|
|
7314
|
+
this.#emitInput();
|
|
7315
|
+
});
|
|
7316
|
+
this.#opacityInput.addEventListener("change", (e) => {
|
|
7317
|
+
e.stopPropagation();
|
|
7318
|
+
this.#emitChange();
|
|
7319
|
+
});
|
|
7320
|
+
}
|
|
6471
7321
|
}
|
|
6472
7322
|
|
|
6473
7323
|
#updateControls() {
|
|
@@ -6634,69 +7484,68 @@ class FigInputFill extends HTMLElement {
|
|
|
6634
7484
|
combo.insertAdjacentHTML("beforeend", controlsHtml);
|
|
6635
7485
|
|
|
6636
7486
|
// Re-setup event listeners for the new controls
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
7487
|
+
this.#opacityInput = this.querySelector(".fig-input-fill-opacity");
|
|
7488
|
+
this.#hexInput = this.querySelector(".fig-input-fill-hex");
|
|
7489
|
+
const label = this.querySelector(".fig-input-fill-label");
|
|
7490
|
+
this.#syncA11y();
|
|
7491
|
+
|
|
7492
|
+
// Label click triggers fill picker
|
|
7493
|
+
if (label && this.#fillPicker) {
|
|
7494
|
+
label.addEventListener("click", () => {
|
|
7495
|
+
const chit = this.#fillPicker.querySelector("fig-chit");
|
|
7496
|
+
if (chit) {
|
|
7497
|
+
chit.click();
|
|
7498
|
+
}
|
|
7499
|
+
});
|
|
7500
|
+
}
|
|
6651
7501
|
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
7502
|
+
// Hex input (solid only)
|
|
7503
|
+
if (this.#hexInput) {
|
|
7504
|
+
this.#hexInput.addEventListener("input", (e) => {
|
|
7505
|
+
e.stopPropagation();
|
|
7506
|
+
const hex = "#" + e.target.value.replace("#", "");
|
|
7507
|
+
this.#solid.color = hex;
|
|
7508
|
+
this.#updateFillPicker();
|
|
7509
|
+
this.#emitInput();
|
|
7510
|
+
});
|
|
7511
|
+
this.#hexInput.addEventListener("change", (e) => {
|
|
7512
|
+
e.stopPropagation();
|
|
7513
|
+
this.#emitChange();
|
|
7514
|
+
});
|
|
7515
|
+
}
|
|
6666
7516
|
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
});
|
|
7517
|
+
// Opacity input
|
|
7518
|
+
if (this.#opacityInput) {
|
|
7519
|
+
this.#opacityInput.addEventListener("input", (e) => {
|
|
7520
|
+
e.stopPropagation();
|
|
7521
|
+
const parsed = parseFloat(e.target.value);
|
|
7522
|
+
const opacity = isNaN(parsed) ? 100 : parsed;
|
|
7523
|
+
const alpha = opacity / 100;
|
|
7524
|
+
switch (this.#fillType) {
|
|
7525
|
+
case "solid":
|
|
7526
|
+
this.#solid.alpha = alpha;
|
|
7527
|
+
break;
|
|
7528
|
+
case "gradient":
|
|
7529
|
+
break;
|
|
7530
|
+
case "image":
|
|
7531
|
+
this.#image.opacity = alpha;
|
|
7532
|
+
break;
|
|
7533
|
+
case "video":
|
|
7534
|
+
this.#video.opacity = alpha;
|
|
7535
|
+
break;
|
|
7536
|
+
case "webcam":
|
|
7537
|
+
this.#webcam.opacity = alpha;
|
|
7538
|
+
break;
|
|
7539
|
+
}
|
|
7540
|
+
this.#updateFillPicker();
|
|
7541
|
+
this.#updateChitAlpha(alpha);
|
|
7542
|
+
this.#emitInput();
|
|
7543
|
+
});
|
|
7544
|
+
this.#opacityInput.addEventListener("change", (e) => {
|
|
7545
|
+
e.stopPropagation();
|
|
7546
|
+
this.#emitChange();
|
|
7547
|
+
});
|
|
7548
|
+
}
|
|
6700
7549
|
}
|
|
6701
7550
|
|
|
6702
7551
|
#updateFillPicker() {
|
|
@@ -6804,6 +7653,12 @@ class FigInputFill extends HTMLElement {
|
|
|
6804
7653
|
}
|
|
6805
7654
|
}
|
|
6806
7655
|
break;
|
|
7656
|
+
case "aria-label":
|
|
7657
|
+
case "aria-describedby":
|
|
7658
|
+
case "aria-invalid":
|
|
7659
|
+
case "aria-required":
|
|
7660
|
+
this.#syncA11y();
|
|
7661
|
+
break;
|
|
6807
7662
|
}
|
|
6808
7663
|
}
|
|
6809
7664
|
}
|
|
@@ -6826,6 +7681,7 @@ class FigInputPalette extends HTMLElement {
|
|
|
6826
7681
|
#inlinePickers = [];
|
|
6827
7682
|
#expandedPickers = [];
|
|
6828
7683
|
#renderRAF = null;
|
|
7684
|
+
#boundHandleKeyDown = this.#handleKeyDown.bind(this);
|
|
6829
7685
|
|
|
6830
7686
|
static get observedAttributes() {
|
|
6831
7687
|
return ["value", "disabled", "min", "max", "open", "fixed"];
|
|
@@ -6870,6 +7726,8 @@ class FigInputPalette extends HTMLElement {
|
|
|
6870
7726
|
|
|
6871
7727
|
connectedCallback() {
|
|
6872
7728
|
if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0");
|
|
7729
|
+
this.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
7730
|
+
this.addEventListener("keydown", this.#boundHandleKeyDown);
|
|
6873
7731
|
if (this.#renderRAF) cancelAnimationFrame(this.#renderRAF);
|
|
6874
7732
|
this.#renderRAF = requestAnimationFrame(() => {
|
|
6875
7733
|
this.#renderRAF = null;
|
|
@@ -6883,10 +7741,21 @@ class FigInputPalette extends HTMLElement {
|
|
|
6883
7741
|
cancelAnimationFrame(this.#renderRAF);
|
|
6884
7742
|
this.#renderRAF = null;
|
|
6885
7743
|
}
|
|
7744
|
+
this.removeEventListener("keydown", this.#boundHandleKeyDown);
|
|
6886
7745
|
this.#inlinePickers = [];
|
|
6887
7746
|
this.#expandedPickers = [];
|
|
6888
7747
|
}
|
|
6889
7748
|
|
|
7749
|
+
#handleKeyDown(event) {
|
|
7750
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
7751
|
+
if (event.target !== this && !event.target?.closest?.(".palette-colors-inline")) return;
|
|
7752
|
+
if (this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false") return;
|
|
7753
|
+
event.preventDefault();
|
|
7754
|
+
event.stopPropagation();
|
|
7755
|
+
this.open = true;
|
|
7756
|
+
this.querySelector(".palette-colors-inline")?.setAttribute("aria-expanded", "true");
|
|
7757
|
+
}
|
|
7758
|
+
|
|
6890
7759
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
6891
7760
|
if (oldValue === newValue) return;
|
|
6892
7761
|
|
|
@@ -6999,13 +7868,24 @@ class FigInputPalette extends HTMLElement {
|
|
|
6999
7868
|
|
|
7000
7869
|
const inlineWrap = document.createElement("div");
|
|
7001
7870
|
inlineWrap.className = "palette-colors-inline";
|
|
7002
|
-
inlineWrap.
|
|
7871
|
+
inlineWrap.setAttribute("role", "button");
|
|
7872
|
+
inlineWrap.setAttribute("aria-expanded", String(this.open));
|
|
7873
|
+
inlineWrap.setAttribute("aria-label", "Edit palette colors");
|
|
7874
|
+
const openPalette = () => {
|
|
7003
7875
|
if (
|
|
7004
7876
|
this.hasAttribute("disabled") &&
|
|
7005
7877
|
this.getAttribute("disabled") !== "false"
|
|
7006
7878
|
)
|
|
7007
7879
|
return;
|
|
7008
7880
|
this.open = true;
|
|
7881
|
+
inlineWrap.setAttribute("aria-expanded", "true");
|
|
7882
|
+
};
|
|
7883
|
+
inlineWrap.addEventListener("click", openPalette);
|
|
7884
|
+
inlineWrap.addEventListener("keydown", (event) => {
|
|
7885
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
7886
|
+
event.preventDefault();
|
|
7887
|
+
event.stopPropagation();
|
|
7888
|
+
openPalette();
|
|
7009
7889
|
});
|
|
7010
7890
|
|
|
7011
7891
|
const wrap = document.createElement("div");
|
|
@@ -7258,10 +8138,11 @@ class FigInputGradient extends HTMLElement {
|
|
|
7258
8138
|
#handleDragging = false;
|
|
7259
8139
|
#arrowTooltipTimer = null;
|
|
7260
8140
|
#colorObserver = null;
|
|
8141
|
+
#repositionRAF = null;
|
|
7261
8142
|
#gradient = {
|
|
7262
8143
|
type: "linear",
|
|
7263
|
-
angle:
|
|
7264
|
-
interpolationSpace: "
|
|
8144
|
+
angle: 90,
|
|
8145
|
+
interpolationSpace: "srgb",
|
|
7265
8146
|
hueInterpolation: "shorter",
|
|
7266
8147
|
stops: [
|
|
7267
8148
|
{ position: 0, color: "#D9D9D9", opacity: 100 },
|
|
@@ -7292,18 +8173,49 @@ class FigInputGradient extends HTMLElement {
|
|
|
7292
8173
|
return this.getAttribute("mode") === "tip" ? "tip" : "handle";
|
|
7293
8174
|
}
|
|
7294
8175
|
|
|
8176
|
+
#firstStopHandle() {
|
|
8177
|
+
if (!this.#track) return null;
|
|
8178
|
+
return this.#track.querySelector(
|
|
8179
|
+
"fig-handle:not(.fig-input-gradient-ghost):not([disabled])",
|
|
8180
|
+
);
|
|
8181
|
+
}
|
|
8182
|
+
|
|
8183
|
+
#syncFocusTarget() {
|
|
8184
|
+
const disabled = this.hasAttribute("disabled");
|
|
8185
|
+
if (disabled) {
|
|
8186
|
+
this.setAttribute("tabindex", "-1");
|
|
8187
|
+
return;
|
|
8188
|
+
}
|
|
8189
|
+
this.setAttribute("tabindex", this.#isEditable ? "-1" : "0");
|
|
8190
|
+
}
|
|
8191
|
+
|
|
8192
|
+
#normalizeGradient(gradient) {
|
|
8193
|
+
return {
|
|
8194
|
+
...normalizeGradientConfig(gradient),
|
|
8195
|
+
type: "linear",
|
|
8196
|
+
angle: 90,
|
|
8197
|
+
};
|
|
8198
|
+
}
|
|
8199
|
+
|
|
7295
8200
|
connectedCallback() {
|
|
7296
8201
|
this.#parseValue();
|
|
7297
8202
|
this.#render();
|
|
8203
|
+
this.removeEventListener("keydown", this.#onPickerKeyDown);
|
|
8204
|
+
this.addEventListener("keydown", this.#onPickerKeyDown);
|
|
7298
8205
|
if (this.#isEditable) document.addEventListener("keydown", this.#onKeyDown);
|
|
7299
8206
|
}
|
|
7300
8207
|
|
|
7301
8208
|
disconnectedCallback() {
|
|
7302
8209
|
document.removeEventListener("keydown", this.#onKeyDown);
|
|
8210
|
+
this.removeEventListener("keydown", this.#onPickerKeyDown);
|
|
7303
8211
|
if (this.#colorObserver) {
|
|
7304
8212
|
this.#colorObserver.disconnect();
|
|
7305
8213
|
this.#colorObserver = null;
|
|
7306
8214
|
}
|
|
8215
|
+
if (this.#repositionRAF !== null) {
|
|
8216
|
+
cancelAnimationFrame(this.#repositionRAF);
|
|
8217
|
+
this.#repositionRAF = null;
|
|
8218
|
+
}
|
|
7307
8219
|
clearTimeout(this.#arrowTooltipTimer);
|
|
7308
8220
|
this.removeEventListener("pointerenter", this.#onTrackEnter);
|
|
7309
8221
|
this.removeEventListener("pointermove", this.#onTrackMove);
|
|
@@ -7391,27 +8303,34 @@ class FigInputGradient extends HTMLElement {
|
|
|
7391
8303
|
this.#emitChange();
|
|
7392
8304
|
};
|
|
7393
8305
|
|
|
8306
|
+
#onPickerKeyDown = (e) => {
|
|
8307
|
+
if (this.#editMode !== "picker") return;
|
|
8308
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
8309
|
+
if (e.altKey || e.ctrlKey || e.metaKey) return;
|
|
8310
|
+
if (this.hasAttribute("disabled")) return;
|
|
8311
|
+
const picker = this.querySelector("fig-fill-picker");
|
|
8312
|
+
if (!picker || typeof picker.open !== "function") return;
|
|
8313
|
+
e.preventDefault();
|
|
8314
|
+
picker.open();
|
|
8315
|
+
};
|
|
8316
|
+
|
|
7394
8317
|
#parseValue() {
|
|
7395
8318
|
const valueAttr = this.getAttribute("value");
|
|
7396
8319
|
if (!valueAttr) return;
|
|
7397
8320
|
try {
|
|
7398
8321
|
const parsed = JSON.parse(valueAttr);
|
|
7399
8322
|
if (parsed?.type === "gradient" && parsed.gradient) {
|
|
7400
|
-
this.#gradient =
|
|
8323
|
+
this.#gradient = this.#normalizeGradient({
|
|
7401
8324
|
...this.#gradient,
|
|
7402
8325
|
...parsed.gradient,
|
|
7403
8326
|
});
|
|
7404
|
-
this.#gradient.type = "linear";
|
|
7405
|
-
this.#gradient.angle = 90;
|
|
7406
8327
|
return;
|
|
7407
8328
|
}
|
|
7408
8329
|
if (parsed?.gradient) {
|
|
7409
|
-
this.#gradient =
|
|
8330
|
+
this.#gradient = this.#normalizeGradient({
|
|
7410
8331
|
...this.#gradient,
|
|
7411
8332
|
...parsed.gradient,
|
|
7412
8333
|
});
|
|
7413
|
-
this.#gradient.type = "linear";
|
|
7414
|
-
this.#gradient.angle = 90;
|
|
7415
8334
|
}
|
|
7416
8335
|
} catch (e) {
|
|
7417
8336
|
// Ignore invalid JSON and keep current/default gradient.
|
|
@@ -7419,7 +8338,8 @@ class FigInputGradient extends HTMLElement {
|
|
|
7419
8338
|
}
|
|
7420
8339
|
|
|
7421
8340
|
#buildGradientCSS() {
|
|
7422
|
-
const
|
|
8341
|
+
const gradient = this.#normalizeGradient(this.#gradient);
|
|
8342
|
+
const sorted = [...gradient.stops].sort(
|
|
7423
8343
|
(a, b) => a.position - b.position,
|
|
7424
8344
|
);
|
|
7425
8345
|
const stops = sorted
|
|
@@ -7430,8 +8350,9 @@ class FigInputGradient extends HTMLElement {
|
|
|
7430
8350
|
return `rgba(${r}, ${g}, ${b}, ${alpha}) ${s.position}%`;
|
|
7431
8351
|
})
|
|
7432
8352
|
.join(", ");
|
|
7433
|
-
const interp = gradientInterpolationClause(
|
|
7434
|
-
|
|
8353
|
+
const interp = gradientInterpolationClause(gradient);
|
|
8354
|
+
const interpolation = interp ? ` ${interp}` : "";
|
|
8355
|
+
return `linear-gradient(${gradient.angle}deg${interpolation}, ${stops})`;
|
|
7435
8356
|
}
|
|
7436
8357
|
|
|
7437
8358
|
#stopColorCSS(stop) {
|
|
@@ -7470,6 +8391,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
7470
8391
|
this.#chit = this.querySelector("fig-chit");
|
|
7471
8392
|
this.#track = null;
|
|
7472
8393
|
this.#setupPickerEvents();
|
|
8394
|
+
this.#syncFocusTarget();
|
|
7473
8395
|
return;
|
|
7474
8396
|
}
|
|
7475
8397
|
|
|
@@ -7482,8 +8404,8 @@ class FigInputGradient extends HTMLElement {
|
|
|
7482
8404
|
if (mode === "true" || mode === "picker") {
|
|
7483
8405
|
this.#setupGhostHandle();
|
|
7484
8406
|
this.#setupEventListeners();
|
|
7485
|
-
requestAnimationFrame(() => this.#repositionHandles());
|
|
7486
8407
|
}
|
|
8408
|
+
this.#syncFocusTarget();
|
|
7487
8409
|
}
|
|
7488
8410
|
|
|
7489
8411
|
#setupPickerEvents() {
|
|
@@ -7495,7 +8417,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
7495
8417
|
e.stopPropagation();
|
|
7496
8418
|
const detail = e.detail;
|
|
7497
8419
|
if (!detail?.gradient) return;
|
|
7498
|
-
this.#gradient =
|
|
8420
|
+
this.#gradient = this.#normalizeGradient({
|
|
7499
8421
|
...this.#gradient,
|
|
7500
8422
|
...detail.gradient,
|
|
7501
8423
|
});
|
|
@@ -7658,6 +8580,8 @@ class FigInputGradient extends HTMLElement {
|
|
|
7658
8580
|
};
|
|
7659
8581
|
|
|
7660
8582
|
#repositionHandles() {
|
|
8583
|
+
this.#repositionRAF = null;
|
|
8584
|
+
if (!this.isConnected) return;
|
|
7661
8585
|
if (!this.#track) return;
|
|
7662
8586
|
const stops = this.#gradient.stops;
|
|
7663
8587
|
this.#track
|
|
@@ -7673,6 +8597,11 @@ class FigInputGradient extends HTMLElement {
|
|
|
7673
8597
|
this.#repositionHandles();
|
|
7674
8598
|
}
|
|
7675
8599
|
|
|
8600
|
+
#scheduleRepositionHandles() {
|
|
8601
|
+
if (this.#repositionRAF !== null) return;
|
|
8602
|
+
this.#repositionRAF = requestAnimationFrame(() => this.#repositionHandles());
|
|
8603
|
+
}
|
|
8604
|
+
|
|
7676
8605
|
#syncHandles() {
|
|
7677
8606
|
if (!this.#track) return;
|
|
7678
8607
|
const handles = this.#track.querySelectorAll(
|
|
@@ -7686,7 +8615,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
7686
8615
|
if (ghost) this.#track.appendChild(ghost);
|
|
7687
8616
|
this.#syncHandleMode();
|
|
7688
8617
|
this.#reobserveHandleColors();
|
|
7689
|
-
|
|
8618
|
+
this.#scheduleRepositionHandles();
|
|
7690
8619
|
return;
|
|
7691
8620
|
}
|
|
7692
8621
|
|
|
@@ -7987,7 +8916,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
7987
8916
|
get value() {
|
|
7988
8917
|
return {
|
|
7989
8918
|
type: "gradient",
|
|
7990
|
-
gradient: gradientToValueShape(this.#gradient),
|
|
8919
|
+
gradient: gradientToValueShape(this.#normalizeGradient(this.#gradient)),
|
|
7991
8920
|
};
|
|
7992
8921
|
}
|
|
7993
8922
|
|
|
@@ -7999,6 +8928,18 @@ class FigInputGradient extends HTMLElement {
|
|
|
7999
8928
|
}
|
|
8000
8929
|
}
|
|
8001
8930
|
|
|
8931
|
+
focus(options) {
|
|
8932
|
+
if (this.hasAttribute("disabled")) return;
|
|
8933
|
+
if (this.#isEditable) {
|
|
8934
|
+
const firstHandle = this.#firstStopHandle();
|
|
8935
|
+
if (firstHandle) {
|
|
8936
|
+
firstHandle.focus(options);
|
|
8937
|
+
return;
|
|
8938
|
+
}
|
|
8939
|
+
}
|
|
8940
|
+
HTMLElement.prototype.focus.call(this, options);
|
|
8941
|
+
}
|
|
8942
|
+
|
|
8002
8943
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
8003
8944
|
if (oldValue === newValue) return;
|
|
8004
8945
|
switch (name) {
|
|
@@ -8026,6 +8967,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
8026
8967
|
|
|
8027
8968
|
#syncDisabled() {
|
|
8028
8969
|
const disabled = this.hasAttribute("disabled");
|
|
8970
|
+
this.#syncFocusTarget();
|
|
8029
8971
|
if (this.#chit) {
|
|
8030
8972
|
if (disabled) this.#chit.setAttribute("disabled", "");
|
|
8031
8973
|
else this.#chit.removeAttribute("disabled");
|
|
@@ -8061,7 +9003,6 @@ class FigCheckbox extends HTMLElement {
|
|
|
8061
9003
|
this.input.setAttribute("id", figUniqueId());
|
|
8062
9004
|
this.input.setAttribute("name", this.name);
|
|
8063
9005
|
this.input.setAttribute("type", "checkbox");
|
|
8064
|
-
this.input.setAttribute("role", "checkbox");
|
|
8065
9006
|
this.#boundHandleInput = this.handleInput.bind(this);
|
|
8066
9007
|
}
|
|
8067
9008
|
connectedCallback() {
|
|
@@ -8073,18 +9014,9 @@ class FigCheckbox extends HTMLElement {
|
|
|
8073
9014
|
this.append(this.input);
|
|
8074
9015
|
}
|
|
8075
9016
|
|
|
8076
|
-
this.input.checked =
|
|
8077
|
-
this.hasAttribute("checked") && this.getAttribute("checked") !== "false";
|
|
8078
9017
|
this.input.removeEventListener("change", this.#boundHandleInput);
|
|
8079
9018
|
this.input.addEventListener("change", this.#boundHandleInput);
|
|
8080
|
-
|
|
8081
|
-
if (this.hasAttribute("disabled")) {
|
|
8082
|
-
this.input.disabled = true;
|
|
8083
|
-
}
|
|
8084
|
-
if (this.hasAttribute("indeterminate")) {
|
|
8085
|
-
this.input.indeterminate = true;
|
|
8086
|
-
this.input.setAttribute("indeterminate", "true");
|
|
8087
|
-
}
|
|
9019
|
+
this.#syncInputState();
|
|
8088
9020
|
|
|
8089
9021
|
const existingLabel = this.querySelector(":scope > label");
|
|
8090
9022
|
if (existingLabel) {
|
|
@@ -8119,7 +9051,36 @@ class FigCheckbox extends HTMLElement {
|
|
|
8119
9051
|
}
|
|
8120
9052
|
}
|
|
8121
9053
|
|
|
8122
|
-
render() {}
|
|
9054
|
+
render() {}
|
|
9055
|
+
|
|
9056
|
+
#isAttrOn(name) {
|
|
9057
|
+
return this.hasAttribute(name) && this.getAttribute(name) !== "false";
|
|
9058
|
+
}
|
|
9059
|
+
|
|
9060
|
+
#syncAriaChecked() {
|
|
9061
|
+
if (this.input.indeterminate) {
|
|
9062
|
+
this.input.setAttribute("aria-checked", "mixed");
|
|
9063
|
+
} else {
|
|
9064
|
+
this.input.setAttribute("aria-checked", this.input.checked ? "true" : "false");
|
|
9065
|
+
}
|
|
9066
|
+
}
|
|
9067
|
+
|
|
9068
|
+
#syncInputState() {
|
|
9069
|
+
const checked = this.#isAttrOn("checked");
|
|
9070
|
+
const indeterminate = this.#isAttrOn("indeterminate") && !checked;
|
|
9071
|
+
const disabled = this.#isAttrOn("disabled");
|
|
9072
|
+
this.input.checked = checked;
|
|
9073
|
+
this.input.indeterminate = indeterminate;
|
|
9074
|
+
this.input.disabled = disabled;
|
|
9075
|
+
this.input.value = this.getAttribute("value") || "";
|
|
9076
|
+
this.input.setAttribute("name", this.getAttribute("name") || this.name);
|
|
9077
|
+
if (indeterminate) {
|
|
9078
|
+
this.input.setAttribute("indeterminate", "true");
|
|
9079
|
+
} else {
|
|
9080
|
+
this.input.removeAttribute("indeterminate");
|
|
9081
|
+
}
|
|
9082
|
+
this.#syncAriaChecked();
|
|
9083
|
+
}
|
|
8123
9084
|
|
|
8124
9085
|
focus() {
|
|
8125
9086
|
this.input.focus();
|
|
@@ -8164,35 +9125,22 @@ class FigCheckbox extends HTMLElement {
|
|
|
8164
9125
|
}
|
|
8165
9126
|
break;
|
|
8166
9127
|
case "checked":
|
|
8167
|
-
this
|
|
8168
|
-
this.hasAttribute("checked") &&
|
|
8169
|
-
this.getAttribute("checked") !== "false";
|
|
8170
|
-
if (this.input.checked && this.hasAttribute("indeterminate")) {
|
|
9128
|
+
if (this.#isAttrOn("checked") && this.hasAttribute("indeterminate")) {
|
|
8171
9129
|
this.removeAttribute("indeterminate");
|
|
8172
9130
|
}
|
|
8173
|
-
this
|
|
8174
|
-
this.hasAttribute("indeterminate") &&
|
|
8175
|
-
this.getAttribute("indeterminate") !== "false" &&
|
|
8176
|
-
!this.input.checked;
|
|
8177
|
-
if (this.input.indeterminate) {
|
|
8178
|
-
this.input.setAttribute("indeterminate", "true");
|
|
8179
|
-
} else {
|
|
8180
|
-
this.input.removeAttribute("indeterminate");
|
|
8181
|
-
}
|
|
9131
|
+
this.#syncInputState();
|
|
8182
9132
|
break;
|
|
8183
9133
|
case "indeterminate":
|
|
8184
|
-
this
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
this.input.removeAttribute("indeterminate");
|
|
8192
|
-
}
|
|
9134
|
+
this.#syncInputState();
|
|
9135
|
+
break;
|
|
9136
|
+
case "disabled":
|
|
9137
|
+
this.#syncInputState();
|
|
9138
|
+
break;
|
|
9139
|
+
case "name":
|
|
9140
|
+
this.input.setAttribute("name", newValue || this.name);
|
|
8193
9141
|
break;
|
|
8194
9142
|
case "value":
|
|
8195
|
-
this.input.value = newValue;
|
|
9143
|
+
this.input.value = newValue || "";
|
|
8196
9144
|
break;
|
|
8197
9145
|
default:
|
|
8198
9146
|
this.input[name] = newValue;
|
|
@@ -8205,8 +9153,10 @@ class FigCheckbox extends HTMLElement {
|
|
|
8205
9153
|
e.stopPropagation();
|
|
8206
9154
|
this.input.indeterminate = false;
|
|
8207
9155
|
this.input.removeAttribute("indeterminate");
|
|
8208
|
-
|
|
8209
|
-
|
|
9156
|
+
if (this.hasAttribute("indeterminate")) {
|
|
9157
|
+
this.removeAttribute("indeterminate");
|
|
9158
|
+
}
|
|
9159
|
+
this.#syncAriaChecked();
|
|
8210
9160
|
// Sync attribute with input state (without triggering setter loop)
|
|
8211
9161
|
if (this.input.checked) {
|
|
8212
9162
|
this.setAttribute("checked", "");
|
|
@@ -8267,166 +9217,6 @@ class FigSwitch extends FigCheckbox {
|
|
|
8267
9217
|
}
|
|
8268
9218
|
customElements.define("fig-switch", FigSwitch);
|
|
8269
9219
|
|
|
8270
|
-
/* Toast */
|
|
8271
|
-
/**
|
|
8272
|
-
* A toast notification element for non-modal, time-based messages.
|
|
8273
|
-
* Always positioned at bottom center of the screen.
|
|
8274
|
-
* @attr {number} duration - Auto-dismiss duration in ms (0 = no auto-dismiss, default: 5000)
|
|
8275
|
-
* @attr {number} offset - Distance from bottom edge in pixels (default: 16)
|
|
8276
|
-
* @attr {string} theme - Visual theme: "dark" (default), "light", "danger", "brand"
|
|
8277
|
-
* @attr {boolean} open - Whether the toast is visible
|
|
8278
|
-
*/
|
|
8279
|
-
class FigToast extends HTMLDialogElement {
|
|
8280
|
-
constructor() {
|
|
8281
|
-
super();
|
|
8282
|
-
this._figInit();
|
|
8283
|
-
}
|
|
8284
|
-
|
|
8285
|
-
_figInit() {
|
|
8286
|
-
if (this._figInitialized) return;
|
|
8287
|
-
this._figInitialized = true;
|
|
8288
|
-
this._defaultOffset = 16;
|
|
8289
|
-
this._autoCloseTimer = null;
|
|
8290
|
-
this._boundHandleClose = this.handleClose.bind(this);
|
|
8291
|
-
}
|
|
8292
|
-
|
|
8293
|
-
getOffset() {
|
|
8294
|
-
return parseInt(this.getAttribute("offset") ?? this._defaultOffset);
|
|
8295
|
-
}
|
|
8296
|
-
|
|
8297
|
-
connectedCallback() {
|
|
8298
|
-
this._figInit();
|
|
8299
|
-
|
|
8300
|
-
// Set default theme if not specified
|
|
8301
|
-
if (!this.hasAttribute("theme")) {
|
|
8302
|
-
this.setAttribute("theme", "dark");
|
|
8303
|
-
}
|
|
8304
|
-
|
|
8305
|
-
// Ensure toast is closed by default
|
|
8306
|
-
// Remove native open attribute if present and not explicitly "true"
|
|
8307
|
-
const shouldOpen =
|
|
8308
|
-
this.getAttribute("open") === "true" || this.getAttribute("open") === "";
|
|
8309
|
-
if (this.hasAttribute("open") && !shouldOpen) {
|
|
8310
|
-
this.removeAttribute("open");
|
|
8311
|
-
}
|
|
8312
|
-
|
|
8313
|
-
// Close the dialog initially (override native behavior)
|
|
8314
|
-
if (!shouldOpen) {
|
|
8315
|
-
this.close();
|
|
8316
|
-
}
|
|
8317
|
-
|
|
8318
|
-
requestAnimationFrame(() => {
|
|
8319
|
-
this.addCloseListeners();
|
|
8320
|
-
this.applyPosition();
|
|
8321
|
-
|
|
8322
|
-
// Auto-show if open attribute is explicitly true
|
|
8323
|
-
if (shouldOpen) {
|
|
8324
|
-
this.showToast();
|
|
8325
|
-
}
|
|
8326
|
-
});
|
|
8327
|
-
}
|
|
8328
|
-
|
|
8329
|
-
disconnectedCallback() {
|
|
8330
|
-
this._figInit();
|
|
8331
|
-
this.clearAutoClose();
|
|
8332
|
-
}
|
|
8333
|
-
|
|
8334
|
-
addCloseListeners() {
|
|
8335
|
-
this.querySelectorAll("[close-toast]").forEach((button) => {
|
|
8336
|
-
button.removeEventListener("click", this._boundHandleClose);
|
|
8337
|
-
button.addEventListener("click", this._boundHandleClose);
|
|
8338
|
-
});
|
|
8339
|
-
}
|
|
8340
|
-
|
|
8341
|
-
handleClose() {
|
|
8342
|
-
this.hideToast();
|
|
8343
|
-
}
|
|
8344
|
-
|
|
8345
|
-
applyPosition() {
|
|
8346
|
-
// Always bottom center
|
|
8347
|
-
this.style.position = "fixed";
|
|
8348
|
-
this.style.margin = "0";
|
|
8349
|
-
this.style.top = "auto";
|
|
8350
|
-
this.style.bottom = `${this.getOffset()}px`;
|
|
8351
|
-
this.style.left = "50%";
|
|
8352
|
-
this.style.right = "auto";
|
|
8353
|
-
this.style.transform = "translateX(-50%)";
|
|
8354
|
-
}
|
|
8355
|
-
|
|
8356
|
-
startAutoClose() {
|
|
8357
|
-
this.clearAutoClose();
|
|
8358
|
-
|
|
8359
|
-
const duration = parseInt(this.getAttribute("duration") ?? "5000");
|
|
8360
|
-
if (duration > 0) {
|
|
8361
|
-
this._autoCloseTimer = setTimeout(() => {
|
|
8362
|
-
this.hideToast();
|
|
8363
|
-
}, duration);
|
|
8364
|
-
}
|
|
8365
|
-
}
|
|
8366
|
-
|
|
8367
|
-
clearAutoClose() {
|
|
8368
|
-
if (this._autoCloseTimer) {
|
|
8369
|
-
clearTimeout(this._autoCloseTimer);
|
|
8370
|
-
this._autoCloseTimer = null;
|
|
8371
|
-
}
|
|
8372
|
-
}
|
|
8373
|
-
|
|
8374
|
-
_resolveAutoTheme() {
|
|
8375
|
-
if (this.getAttribute("theme") !== "auto") return;
|
|
8376
|
-
const cs = getComputedStyle(document.documentElement).colorScheme || "";
|
|
8377
|
-
const isDark = cs.includes("dark");
|
|
8378
|
-
this.style.colorScheme = isDark ? "light" : "dark";
|
|
8379
|
-
}
|
|
8380
|
-
|
|
8381
|
-
/**
|
|
8382
|
-
* Show the toast notification (non-modal)
|
|
8383
|
-
*/
|
|
8384
|
-
showToast() {
|
|
8385
|
-
this._resolveAutoTheme();
|
|
8386
|
-
this.show(); // Non-modal show
|
|
8387
|
-
this.applyPosition();
|
|
8388
|
-
this.startAutoClose();
|
|
8389
|
-
this.dispatchEvent(new CustomEvent("toast-show", { bubbles: true }));
|
|
8390
|
-
}
|
|
8391
|
-
|
|
8392
|
-
/**
|
|
8393
|
-
* Hide the toast notification
|
|
8394
|
-
*/
|
|
8395
|
-
hideToast() {
|
|
8396
|
-
this.clearAutoClose();
|
|
8397
|
-
this.close();
|
|
8398
|
-
this.dispatchEvent(new CustomEvent("toast-hide", { bubbles: true }));
|
|
8399
|
-
}
|
|
8400
|
-
|
|
8401
|
-
static get observedAttributes() {
|
|
8402
|
-
return ["duration", "offset", "open", "theme"];
|
|
8403
|
-
}
|
|
8404
|
-
|
|
8405
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
8406
|
-
this._figInit();
|
|
8407
|
-
if (name === "offset") {
|
|
8408
|
-
this.applyPosition();
|
|
8409
|
-
}
|
|
8410
|
-
|
|
8411
|
-
if (name === "open") {
|
|
8412
|
-
if (newValue !== null && newValue !== "false") {
|
|
8413
|
-
this.showToast();
|
|
8414
|
-
} else {
|
|
8415
|
-
this.hideToast();
|
|
8416
|
-
}
|
|
8417
|
-
}
|
|
8418
|
-
|
|
8419
|
-
if (name === "theme") {
|
|
8420
|
-
if (newValue === "auto") {
|
|
8421
|
-
this._resolveAutoTheme();
|
|
8422
|
-
} else {
|
|
8423
|
-
this.style.removeProperty("color-scheme");
|
|
8424
|
-
}
|
|
8425
|
-
}
|
|
8426
|
-
}
|
|
8427
|
-
}
|
|
8428
|
-
figDefineCustomizedBuiltIn("fig-toast", FigToast, { extends: "dialog" });
|
|
8429
|
-
|
|
8430
9220
|
/* Combo Input */
|
|
8431
9221
|
/**
|
|
8432
9222
|
* A custom combo input with text and dropdown.
|
|
@@ -8443,6 +9233,11 @@ class FigComboInput extends HTMLElement {
|
|
|
8443
9233
|
"value",
|
|
8444
9234
|
"disabled",
|
|
8445
9235
|
"experimental",
|
|
9236
|
+
"aria-label",
|
|
9237
|
+
"aria-labelledby",
|
|
9238
|
+
"aria-describedby",
|
|
9239
|
+
"aria-invalid",
|
|
9240
|
+
"aria-required",
|
|
8446
9241
|
];
|
|
8447
9242
|
|
|
8448
9243
|
#usesCustomDropdown = false;
|
|
@@ -8451,6 +9246,13 @@ class FigComboInput extends HTMLElement {
|
|
|
8451
9246
|
#button = null;
|
|
8452
9247
|
#customDropdown = null;
|
|
8453
9248
|
#internalUpdate = false;
|
|
9249
|
+
#a11yAttributes = [
|
|
9250
|
+
"aria-label",
|
|
9251
|
+
"aria-labelledby",
|
|
9252
|
+
"aria-describedby",
|
|
9253
|
+
"aria-invalid",
|
|
9254
|
+
"aria-required",
|
|
9255
|
+
];
|
|
8454
9256
|
|
|
8455
9257
|
#boundHandleDropdownInput = this.#handleDropdownInput.bind(this);
|
|
8456
9258
|
#boundHandleTextInput = this.#handleTextInput.bind(this);
|
|
@@ -8492,10 +9294,11 @@ class FigComboInput extends HTMLElement {
|
|
|
8492
9294
|
const currentValue = this.value;
|
|
8493
9295
|
const experimental = this.getAttribute("experimental");
|
|
8494
9296
|
const expAttr = experimental ? ` experimental="${experimental}"` : "";
|
|
9297
|
+
const dropdownLabel = this.#dropdownLabel();
|
|
8495
9298
|
|
|
8496
9299
|
const dropdownHTML = this.#usesCustomDropdown
|
|
8497
9300
|
? ""
|
|
8498
|
-
: `<fig-dropdown type="dropdown"${expAttr}>${options.map((o) => `<option>${o.trim()}</option>`).join("")}</fig-dropdown>`;
|
|
9301
|
+
: `<fig-dropdown type="dropdown" label="${dropdownLabel}"${expAttr}>${options.map((o) => `<option>${o.trim()}</option>`).join("")}</fig-dropdown>`;
|
|
8499
9302
|
|
|
8500
9303
|
this.innerHTML = `<div class="input-combo">
|
|
8501
9304
|
<fig-input-text placeholder="${placeholder}" value="${currentValue}"></fig-input-text>
|
|
@@ -8514,6 +9317,9 @@ class FigComboInput extends HTMLElement {
|
|
|
8514
9317
|
if (!this.#customDropdown.hasAttribute("type")) {
|
|
8515
9318
|
this.#customDropdown.setAttribute("type", "dropdown");
|
|
8516
9319
|
}
|
|
9320
|
+
if (!this.#customDropdown.hasAttribute("label")) {
|
|
9321
|
+
this.#customDropdown.setAttribute("label", dropdownLabel);
|
|
9322
|
+
}
|
|
8517
9323
|
if (experimental) {
|
|
8518
9324
|
this.#customDropdown.setAttribute("experimental", experimental);
|
|
8519
9325
|
}
|
|
@@ -8521,6 +9327,7 @@ class FigComboInput extends HTMLElement {
|
|
|
8521
9327
|
}
|
|
8522
9328
|
|
|
8523
9329
|
this.#dropdown = this.querySelector("fig-dropdown");
|
|
9330
|
+
this.#syncA11yAttributes();
|
|
8524
9331
|
}
|
|
8525
9332
|
|
|
8526
9333
|
#setupListeners() {
|
|
@@ -8591,6 +9398,31 @@ class FigComboInput extends HTMLElement {
|
|
|
8591
9398
|
.filter(Boolean);
|
|
8592
9399
|
}
|
|
8593
9400
|
|
|
9401
|
+
#controlName() {
|
|
9402
|
+
return (
|
|
9403
|
+
this.getAttribute("aria-label") ||
|
|
9404
|
+
this.getAttribute("placeholder") ||
|
|
9405
|
+
"Combo input"
|
|
9406
|
+
).trim();
|
|
9407
|
+
}
|
|
9408
|
+
|
|
9409
|
+
#dropdownLabel() {
|
|
9410
|
+
return `${this.#controlName()} options`;
|
|
9411
|
+
}
|
|
9412
|
+
|
|
9413
|
+
#syncA11yAttributes() {
|
|
9414
|
+
if (this.#input) {
|
|
9415
|
+
this.#a11yAttributes.forEach((name) => {
|
|
9416
|
+
const value = this.getAttribute(name);
|
|
9417
|
+
if (value === null) this.#input.removeAttribute(name);
|
|
9418
|
+
else this.#input.setAttribute(name, value);
|
|
9419
|
+
});
|
|
9420
|
+
}
|
|
9421
|
+
if (this.#dropdown && !this.#dropdown.hasAttribute("aria-label")) {
|
|
9422
|
+
this.#dropdown.setAttribute("label", this.#dropdownLabel());
|
|
9423
|
+
}
|
|
9424
|
+
}
|
|
9425
|
+
|
|
8594
9426
|
#applyDisabled(disabled) {
|
|
8595
9427
|
if (this.#input) {
|
|
8596
9428
|
if (disabled) this.#input.setAttribute("disabled", "");
|
|
@@ -8600,6 +9432,10 @@ class FigComboInput extends HTMLElement {
|
|
|
8600
9432
|
if (disabled) this.#button.setAttribute("disabled", "");
|
|
8601
9433
|
else this.#button.removeAttribute("disabled");
|
|
8602
9434
|
}
|
|
9435
|
+
if (this.#dropdown) {
|
|
9436
|
+
if (disabled) this.#dropdown.setAttribute("disabled", "");
|
|
9437
|
+
else this.#dropdown.removeAttribute("disabled");
|
|
9438
|
+
}
|
|
8603
9439
|
}
|
|
8604
9440
|
|
|
8605
9441
|
focus() {
|
|
@@ -8619,6 +9455,7 @@ class FigComboInput extends HTMLElement {
|
|
|
8619
9455
|
break;
|
|
8620
9456
|
case "placeholder":
|
|
8621
9457
|
if (this.#input) this.#input.setAttribute("placeholder", newValue || "");
|
|
9458
|
+
this.#syncA11yAttributes();
|
|
8622
9459
|
break;
|
|
8623
9460
|
case "value":
|
|
8624
9461
|
if (!this.#internalUpdate && this.#input) {
|
|
@@ -8635,6 +9472,13 @@ class FigComboInput extends HTMLElement {
|
|
|
8635
9472
|
this.#dropdown.removeAttribute("experimental");
|
|
8636
9473
|
}
|
|
8637
9474
|
break;
|
|
9475
|
+
case "aria-label":
|
|
9476
|
+
case "aria-labelledby":
|
|
9477
|
+
case "aria-describedby":
|
|
9478
|
+
case "aria-invalid":
|
|
9479
|
+
case "aria-required":
|
|
9480
|
+
this.#syncA11yAttributes();
|
|
9481
|
+
break;
|
|
8638
9482
|
}
|
|
8639
9483
|
}
|
|
8640
9484
|
}
|
|
@@ -8660,12 +9504,22 @@ class FigChit extends HTMLElement {
|
|
|
8660
9504
|
}
|
|
8661
9505
|
|
|
8662
9506
|
static get observedAttributes() {
|
|
8663
|
-
return [
|
|
9507
|
+
return [
|
|
9508
|
+
"background",
|
|
9509
|
+
"size",
|
|
9510
|
+
"selected",
|
|
9511
|
+
"disabled",
|
|
9512
|
+
"alpha",
|
|
9513
|
+
"aria-label",
|
|
9514
|
+
"aria-labelledby",
|
|
9515
|
+
"aria-describedby",
|
|
9516
|
+
];
|
|
8664
9517
|
}
|
|
8665
9518
|
|
|
8666
9519
|
connectedCallback() {
|
|
8667
9520
|
this.#render();
|
|
8668
9521
|
this.#updateAlpha();
|
|
9522
|
+
this.#syncA11y();
|
|
8669
9523
|
}
|
|
8670
9524
|
|
|
8671
9525
|
#updateAlpha() {
|
|
@@ -8732,9 +9586,11 @@ class FigChit extends HTMLElement {
|
|
|
8732
9586
|
if (!isVar) {
|
|
8733
9587
|
this.input.addEventListener("input", this.#boundHandleInput);
|
|
8734
9588
|
}
|
|
9589
|
+
this.#syncA11y();
|
|
8735
9590
|
} else {
|
|
8736
9591
|
this.innerHTML = "<div></div>";
|
|
8737
9592
|
this.input = null;
|
|
9593
|
+
this.#syncA11y();
|
|
8738
9594
|
}
|
|
8739
9595
|
} else if (this.#type === "color" && this.input) {
|
|
8740
9596
|
const hex = this.#toHex(bg);
|
|
@@ -8753,6 +9609,36 @@ class FigChit extends HTMLElement {
|
|
|
8753
9609
|
);
|
|
8754
9610
|
}
|
|
8755
9611
|
|
|
9612
|
+
#syncA11y() {
|
|
9613
|
+
const disabled =
|
|
9614
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
9615
|
+
this.setAttribute("aria-disabled", disabled ? "true" : "false");
|
|
9616
|
+
if (this.input) {
|
|
9617
|
+
this.input.disabled = disabled;
|
|
9618
|
+
const labelledBy = this.getAttribute("aria-labelledby");
|
|
9619
|
+
const label = this.getAttribute("aria-label") || "Color swatch";
|
|
9620
|
+
const describedBy = this.getAttribute("aria-describedby");
|
|
9621
|
+
if (labelledBy) {
|
|
9622
|
+
this.input.setAttribute("aria-labelledby", labelledBy);
|
|
9623
|
+
this.input.removeAttribute("aria-label");
|
|
9624
|
+
} else {
|
|
9625
|
+
this.input.setAttribute("aria-label", label);
|
|
9626
|
+
this.input.removeAttribute("aria-labelledby");
|
|
9627
|
+
}
|
|
9628
|
+
if (describedBy) this.input.setAttribute("aria-describedby", describedBy);
|
|
9629
|
+
else this.input.removeAttribute("aria-describedby");
|
|
9630
|
+
this.removeAttribute("role");
|
|
9631
|
+
this.removeAttribute("aria-hidden");
|
|
9632
|
+
return;
|
|
9633
|
+
}
|
|
9634
|
+
if (!this.hasAttribute("aria-label") && !this.hasAttribute("aria-labelledby")) {
|
|
9635
|
+
this.setAttribute("aria-hidden", "true");
|
|
9636
|
+
} else {
|
|
9637
|
+
this.setAttribute("role", "img");
|
|
9638
|
+
this.removeAttribute("aria-hidden");
|
|
9639
|
+
}
|
|
9640
|
+
}
|
|
9641
|
+
|
|
8756
9642
|
#handleInput(e) {
|
|
8757
9643
|
// Update background attribute without triggering full re-render
|
|
8758
9644
|
this.#internalUpdate = true;
|
|
@@ -8790,6 +9676,13 @@ class FigChit extends HTMLElement {
|
|
|
8790
9676
|
this.#render();
|
|
8791
9677
|
} else if (name === "alpha") {
|
|
8792
9678
|
this.#updateAlpha();
|
|
9679
|
+
} else if (
|
|
9680
|
+
name === "disabled" ||
|
|
9681
|
+
name === "aria-label" ||
|
|
9682
|
+
name === "aria-labelledby" ||
|
|
9683
|
+
name === "aria-describedby"
|
|
9684
|
+
) {
|
|
9685
|
+
this.#syncA11y();
|
|
8793
9686
|
}
|
|
8794
9687
|
}
|
|
8795
9688
|
|
|
@@ -8825,6 +9718,8 @@ customElements.define("fig-swatch", FigSwatch);
|
|
|
8825
9718
|
* @attr {boolean} loop - Video loop
|
|
8826
9719
|
* @attr {boolean} muted - Video muted
|
|
8827
9720
|
* @attr {string} poster - Video poster image URL
|
|
9721
|
+
* @attr {string} aria-label - Accessible label forwarded to generated video
|
|
9722
|
+
* @attr {string} aria-labelledby - Accessible label reference forwarded to generated video
|
|
8828
9723
|
*
|
|
8829
9724
|
* Sizing model:
|
|
8830
9725
|
* - Default: host shrinkwraps to its inner <img>/<video> intrinsic size.
|
|
@@ -8834,6 +9729,7 @@ customElements.define("fig-swatch", FigSwatch);
|
|
|
8834
9729
|
class FigMedia extends HTMLElement {
|
|
8835
9730
|
#src = null;
|
|
8836
9731
|
#mediaEl = null;
|
|
9732
|
+
#previewEl = null;
|
|
8837
9733
|
#fileInput = null;
|
|
8838
9734
|
#blobUrl = null;
|
|
8839
9735
|
#previewSrc = null;
|
|
@@ -8865,6 +9761,9 @@ class FigMedia extends HTMLElement {
|
|
|
8865
9761
|
"loop",
|
|
8866
9762
|
"muted",
|
|
8867
9763
|
"poster",
|
|
9764
|
+
"aria-label",
|
|
9765
|
+
"aria-labelledby",
|
|
9766
|
+
"title",
|
|
8868
9767
|
];
|
|
8869
9768
|
}
|
|
8870
9769
|
|
|
@@ -8939,8 +9838,10 @@ class FigMedia extends HTMLElement {
|
|
|
8939
9838
|
}
|
|
8940
9839
|
|
|
8941
9840
|
this.querySelectorAll("fig-chit[data-generated]").forEach((el) => el.remove());
|
|
9841
|
+
this.#ensurePreviewElement();
|
|
8942
9842
|
this.#ensureMediaElement();
|
|
8943
9843
|
this.#syncGeneratedMediaElement();
|
|
9844
|
+
this.#syncMediaAccessibility();
|
|
8944
9845
|
|
|
8945
9846
|
const isUpload = this.hasAttribute("upload") && this.getAttribute("upload") !== "false";
|
|
8946
9847
|
if (isUpload && !this.querySelector("fig-input-file[data-generated]")) {
|
|
@@ -8968,6 +9869,19 @@ class FigMedia extends HTMLElement {
|
|
|
8968
9869
|
}
|
|
8969
9870
|
}
|
|
8970
9871
|
|
|
9872
|
+
#ensurePreviewElement() {
|
|
9873
|
+
if (this.#previewEl?.isConnected) return;
|
|
9874
|
+
const existing = this.querySelector(":scope > fig-preview");
|
|
9875
|
+
if (existing) {
|
|
9876
|
+
this.#previewEl = existing;
|
|
9877
|
+
return;
|
|
9878
|
+
}
|
|
9879
|
+
const preview = document.createElement("fig-preview");
|
|
9880
|
+
preview.setAttribute("data-generated", "");
|
|
9881
|
+
this.prepend(preview);
|
|
9882
|
+
this.#previewEl = preview;
|
|
9883
|
+
}
|
|
9884
|
+
|
|
8971
9885
|
#userProvidedMediaEl() {
|
|
8972
9886
|
const tag = this.mediaKind === "video" ? "video" : "img";
|
|
8973
9887
|
return this.querySelector(`${tag}:not([data-generated])`);
|
|
@@ -8976,6 +9890,7 @@ class FigMedia extends HTMLElement {
|
|
|
8976
9890
|
#ensureMediaElement() {
|
|
8977
9891
|
const userEl = this.#userProvidedMediaEl();
|
|
8978
9892
|
if (userEl) {
|
|
9893
|
+
this.#ensurePreviewElement();
|
|
8979
9894
|
if (this.#mediaEl && this.#mediaEl !== userEl) {
|
|
8980
9895
|
this.#removeMediaElementListeners();
|
|
8981
9896
|
if (this.#mediaEl.hasAttribute("data-generated")) {
|
|
@@ -8983,9 +9898,14 @@ class FigMedia extends HTMLElement {
|
|
|
8983
9898
|
}
|
|
8984
9899
|
}
|
|
8985
9900
|
this.#mediaEl = userEl;
|
|
9901
|
+
if (this.#previewEl && userEl.parentElement !== this.#previewEl) {
|
|
9902
|
+
this.#previewEl.append(userEl);
|
|
9903
|
+
}
|
|
9904
|
+
this.#syncMediaAccessibility();
|
|
8986
9905
|
return;
|
|
8987
9906
|
}
|
|
8988
9907
|
|
|
9908
|
+
this.#ensurePreviewElement();
|
|
8989
9909
|
const expectedTag = this.mediaKind === "video" ? "VIDEO" : "IMG";
|
|
8990
9910
|
if (this.#mediaEl && this.#mediaEl.tagName !== expectedTag) {
|
|
8991
9911
|
this.#removeMediaElementListeners();
|
|
@@ -9002,7 +9922,7 @@ class FigMedia extends HTMLElement {
|
|
|
9002
9922
|
video.className = "fig-media-element";
|
|
9003
9923
|
video.setAttribute("playsinline", "");
|
|
9004
9924
|
video.preload = "auto";
|
|
9005
|
-
this.
|
|
9925
|
+
this.#previewEl.append(video);
|
|
9006
9926
|
this.#mediaEl = video;
|
|
9007
9927
|
this.#mediaEl.addEventListener("play", this.#boundHandleMediaPlay);
|
|
9008
9928
|
this.#mediaEl.addEventListener("pause", this.#boundHandleMediaPause);
|
|
@@ -9021,11 +9941,33 @@ class FigMedia extends HTMLElement {
|
|
|
9021
9941
|
img.loading = "lazy";
|
|
9022
9942
|
img.decoding = "async";
|
|
9023
9943
|
img.alt = this.getAttribute("alt") || "";
|
|
9024
|
-
this.
|
|
9944
|
+
this.#previewEl.append(img);
|
|
9025
9945
|
this.#mediaEl = img;
|
|
9026
9946
|
}
|
|
9027
9947
|
}
|
|
9028
9948
|
|
|
9949
|
+
#syncMediaAccessibility() {
|
|
9950
|
+
if (!this.#mediaEl) return;
|
|
9951
|
+
if (this.#mediaEl.tagName === "IMG") {
|
|
9952
|
+
if (
|
|
9953
|
+
this.hasAttribute("alt") ||
|
|
9954
|
+
this.#mediaEl.hasAttribute("data-generated")
|
|
9955
|
+
) {
|
|
9956
|
+
this.#mediaEl.alt = this.getAttribute("alt") || "";
|
|
9957
|
+
}
|
|
9958
|
+
return;
|
|
9959
|
+
}
|
|
9960
|
+
if (this.#mediaEl.tagName !== "VIDEO") return;
|
|
9961
|
+
["aria-label", "aria-labelledby", "title"].forEach((name) => {
|
|
9962
|
+
const value = this.getAttribute(name);
|
|
9963
|
+
if (value === null) {
|
|
9964
|
+
this.#mediaEl.removeAttribute(name);
|
|
9965
|
+
} else {
|
|
9966
|
+
this.#mediaEl.setAttribute(name, value);
|
|
9967
|
+
}
|
|
9968
|
+
});
|
|
9969
|
+
}
|
|
9970
|
+
|
|
9029
9971
|
#isEnabledAttr(name, defaultEnabled = false) {
|
|
9030
9972
|
if (!this.hasAttribute(name)) return defaultEnabled;
|
|
9031
9973
|
return this.getAttribute(name) !== "false";
|
|
@@ -9044,7 +9986,7 @@ class FigMedia extends HTMLElement {
|
|
|
9044
9986
|
}
|
|
9045
9987
|
}
|
|
9046
9988
|
if (this.#mediaEl.tagName === "IMG") {
|
|
9047
|
-
this.#
|
|
9989
|
+
this.#syncMediaAccessibility();
|
|
9048
9990
|
return;
|
|
9049
9991
|
}
|
|
9050
9992
|
const poster = this.getAttribute("poster");
|
|
@@ -9059,6 +10001,7 @@ class FigMedia extends HTMLElement {
|
|
|
9059
10001
|
this.#mediaEl.loop = this.#isEnabledAttr("loop", false);
|
|
9060
10002
|
this.#mediaEl.muted = this.#isEnabledAttr("muted", false);
|
|
9061
10003
|
this.#mediaEl.playsInline = true;
|
|
10004
|
+
this.#syncMediaAccessibility();
|
|
9062
10005
|
this.#syncControlsVisibility();
|
|
9063
10006
|
}
|
|
9064
10007
|
|
|
@@ -9096,7 +10039,6 @@ class FigMedia extends HTMLElement {
|
|
|
9096
10039
|
}
|
|
9097
10040
|
const controls = document.createElement("fig-media-controls");
|
|
9098
10041
|
controls.setAttribute("data-generated", "");
|
|
9099
|
-
controls.setAttribute("overlay", "");
|
|
9100
10042
|
this.append(controls);
|
|
9101
10043
|
this.#controlsEl = controls;
|
|
9102
10044
|
this.#wireControlsToMedia();
|
|
@@ -9225,7 +10167,8 @@ class FigMedia extends HTMLElement {
|
|
|
9225
10167
|
fi.setAttribute("url", this.#src);
|
|
9226
10168
|
}
|
|
9227
10169
|
fi.addEventListener("change", this.#boundHandleFileInput);
|
|
9228
|
-
this
|
|
10170
|
+
this.#ensurePreviewElement();
|
|
10171
|
+
this.#previewEl.append(fi);
|
|
9229
10172
|
this.#fileInput = fi;
|
|
9230
10173
|
}
|
|
9231
10174
|
|
|
@@ -9366,8 +10309,8 @@ class FigMedia extends HTMLElement {
|
|
|
9366
10309
|
}
|
|
9367
10310
|
}
|
|
9368
10311
|
|
|
9369
|
-
if (
|
|
9370
|
-
this.#
|
|
10312
|
+
if (["alt", "aria-label", "aria-labelledby", "title"].includes(name)) {
|
|
10313
|
+
this.#syncMediaAccessibility();
|
|
9371
10314
|
}
|
|
9372
10315
|
|
|
9373
10316
|
if (name === "upload") {
|
|
@@ -9501,6 +10444,10 @@ class FigMediaControls extends HTMLElement {
|
|
|
9501
10444
|
#render() {
|
|
9502
10445
|
if (this.#rendered) return;
|
|
9503
10446
|
this.#rendered = true;
|
|
10447
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "group");
|
|
10448
|
+
if (!this.hasAttribute("aria-label") && !this.hasAttribute("aria-labelledby")) {
|
|
10449
|
+
this.setAttribute("aria-label", "Media controls");
|
|
10450
|
+
}
|
|
9504
10451
|
|
|
9505
10452
|
const tooltip = document.createElement("fig-tooltip");
|
|
9506
10453
|
tooltip.setAttribute("text", "Play");
|
|
@@ -9524,10 +10471,15 @@ class FigMediaControls extends HTMLElement {
|
|
|
9524
10471
|
slider.setAttribute("text", "false");
|
|
9525
10472
|
slider.setAttribute("min", "0");
|
|
9526
10473
|
slider.setAttribute("max", String(this.duration));
|
|
9527
|
-
slider.setAttribute("step", "
|
|
10474
|
+
slider.setAttribute("step", "1");
|
|
9528
10475
|
slider.setAttribute("value", String(this.time));
|
|
9529
10476
|
slider.setAttribute("full", "");
|
|
9530
|
-
|
|
10477
|
+
slider.setAttribute("aria-label", "Seek");
|
|
10478
|
+
slider.setAttribute(
|
|
10479
|
+
"aria-valuetext",
|
|
10480
|
+
this.#formatTimeValueText(this.time, this.duration),
|
|
10481
|
+
);
|
|
10482
|
+
const timeEl = document.createElement("span");
|
|
9531
10483
|
timeEl.className = "fig-media-controls-time";
|
|
9532
10484
|
timeEl.textContent = this.#formatTime(this.time);
|
|
9533
10485
|
|
|
@@ -9567,6 +10519,12 @@ class FigMediaControls extends HTMLElement {
|
|
|
9567
10519
|
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
9568
10520
|
}
|
|
9569
10521
|
|
|
10522
|
+
#formatTimeValueText(time, duration = 0) {
|
|
10523
|
+
const current = this.#formatTime(time);
|
|
10524
|
+
if (!Number.isFinite(duration) || duration <= 0) return current;
|
|
10525
|
+
return `${current} of ${this.#formatTime(duration)}`;
|
|
10526
|
+
}
|
|
10527
|
+
|
|
9570
10528
|
#syncPlayingUi() {
|
|
9571
10529
|
if (!this.#playBtn) return;
|
|
9572
10530
|
const playing = this.playing;
|
|
@@ -9588,6 +10546,10 @@ class FigMediaControls extends HTMLElement {
|
|
|
9588
10546
|
if (!this.#userSeeking) {
|
|
9589
10547
|
this.#timeSlider.setAttribute("value", String(t));
|
|
9590
10548
|
}
|
|
10549
|
+
this.#timeSlider.setAttribute(
|
|
10550
|
+
"aria-valuetext",
|
|
10551
|
+
this.#formatTimeValueText(t, duration),
|
|
10552
|
+
);
|
|
9591
10553
|
if (this.#timeEl) this.#timeEl.textContent = this.#formatTime(t);
|
|
9592
10554
|
}
|
|
9593
10555
|
|
|
@@ -9823,6 +10785,7 @@ class FigInputFile extends HTMLElement {
|
|
|
9823
10785
|
this.#clearBtn = document.createElement("fig-button");
|
|
9824
10786
|
this.#clearBtn.setAttribute("variant", variant === "overlay" ? "overlay" : "ghost");
|
|
9825
10787
|
this.#clearBtn.setAttribute("icon", "true");
|
|
10788
|
+
this.#clearBtn.setAttribute("aria-label", "Remove");
|
|
9826
10789
|
this.#clearBtn.className = "fig-input-file-clear";
|
|
9827
10790
|
if (disabled) this.#clearBtn.setAttribute("disabled", "");
|
|
9828
10791
|
this.#clearBtn.replaceChildren(createFigIcon("minus"));
|
|
@@ -10284,8 +11247,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
10284
11247
|
<line class="fig-easing-curve-target" x1="0" y1="${targetY}" x2="${size}" y2="${targetY}"/>
|
|
10285
11248
|
<line class="fig-easing-curve-diagonal" x1="0" y1="${startY}" x2="0" y2="${startY}"/>
|
|
10286
11249
|
<path class="fig-easing-curve-path"/>
|
|
10287
|
-
<foreignObject class="fig-easing-curve-handle" data-handle="bounce" width="20" height="20"><fig-handle size="small"></fig-handle></foreignObject>
|
|
10288
|
-
<foreignObject class="fig-easing-curve-handle fig-easing-curve-duration-bar" data-handle="duration" width="20" height="20"><fig-handle size="small"></fig-handle></foreignObject>
|
|
11250
|
+
<foreignObject class="fig-easing-curve-handle" data-handle="bounce" width="20" height="20"><fig-handle size="small" drag aria-label="Spring bounce handle"></fig-handle></foreignObject>
|
|
11251
|
+
<foreignObject class="fig-easing-curve-handle fig-easing-curve-duration-bar" data-handle="duration" width="20" height="20"><fig-handle size="small" drag drag-axes="x" aria-label="Spring duration handle"></fig-handle></foreignObject>
|
|
10289
11252
|
</svg></div>${valueInput}`;
|
|
10290
11253
|
}
|
|
10291
11254
|
|
|
@@ -10297,8 +11260,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
10297
11260
|
<path class="fig-easing-curve-path"/>
|
|
10298
11261
|
<circle class="fig-easing-curve-endpoint" data-endpoint="start" r="${this.#bezierEndpointRadius}"/>
|
|
10299
11262
|
<circle class="fig-easing-curve-endpoint" data-endpoint="end" r="${this.#bezierEndpointRadius}"/>
|
|
10300
|
-
<foreignObject class="fig-easing-curve-handle" data-handle="1" width="20" height="20"><fig-handle size="small"></fig-handle></foreignObject>
|
|
10301
|
-
<foreignObject class="fig-easing-curve-handle" data-handle="2" width="20" height="20"><fig-handle size="small"></fig-handle></foreignObject>
|
|
11263
|
+
<foreignObject class="fig-easing-curve-handle" data-handle="1" width="20" height="20"><fig-handle size="small" drag aria-label="First easing control point"></fig-handle></foreignObject>
|
|
11264
|
+
<foreignObject class="fig-easing-curve-handle" data-handle="2" width="20" height="20"><fig-handle size="small" drag aria-label="Second easing control point"></fig-handle></foreignObject>
|
|
10302
11265
|
</svg></div>${valueInput}`;
|
|
10303
11266
|
}
|
|
10304
11267
|
|
|
@@ -10473,6 +11436,18 @@ class FigEasingCurve extends HTMLElement {
|
|
|
10473
11436
|
this.#bezierEndpointEnd.setAttribute("cx", p3.x);
|
|
10474
11437
|
this.#bezierEndpointEnd.setAttribute("cy", p3.y);
|
|
10475
11438
|
}
|
|
11439
|
+
this.#syncBezierHandleTabOrder();
|
|
11440
|
+
}
|
|
11441
|
+
|
|
11442
|
+
#syncBezierHandleTabOrder() {
|
|
11443
|
+
if (!this.#svg || !this.#handle1 || !this.#handle2) return;
|
|
11444
|
+
const handles =
|
|
11445
|
+
this.#cp1.y >= this.#cp2.y
|
|
11446
|
+
? [this.#handle1, this.#handle2]
|
|
11447
|
+
: [this.#handle2, this.#handle1];
|
|
11448
|
+
for (const handle of handles) {
|
|
11449
|
+
this.#svg.append(handle);
|
|
11450
|
+
}
|
|
10476
11451
|
}
|
|
10477
11452
|
|
|
10478
11453
|
#updateSpringPaths() {
|
|
@@ -10611,29 +11586,160 @@ class FigEasingCurve extends HTMLElement {
|
|
|
10611
11586
|
}
|
|
10612
11587
|
}
|
|
10613
11588
|
|
|
10614
|
-
#refreshCustomPresetIcons() {
|
|
10615
|
-
if (!this.#dropdown) return;
|
|
10616
|
-
if (!this.#isEditEnabled()) return;
|
|
10617
|
-
const bezierIcon = FigEasingCurve.curveIcon(
|
|
10618
|
-
this.#cp1.x,
|
|
10619
|
-
this.#cp1.y,
|
|
10620
|
-
this.#cp2.x,
|
|
10621
|
-
this.#cp2.y,
|
|
10622
|
-
);
|
|
10623
|
-
const springIcon = FigEasingCurve.#springIcon(this.#spring);
|
|
11589
|
+
#refreshCustomPresetIcons() {
|
|
11590
|
+
if (!this.#dropdown) return;
|
|
11591
|
+
if (!this.#isEditEnabled()) return;
|
|
11592
|
+
const bezierIcon = FigEasingCurve.curveIcon(
|
|
11593
|
+
this.#cp1.x,
|
|
11594
|
+
this.#cp1.y,
|
|
11595
|
+
this.#cp2.x,
|
|
11596
|
+
this.#cp2.y,
|
|
11597
|
+
);
|
|
11598
|
+
const springIcon = FigEasingCurve.#springIcon(this.#spring);
|
|
11599
|
+
|
|
11600
|
+
// Update both slotted options and the cloned native select options.
|
|
11601
|
+
this.#setOptionIconByValue(this.#dropdown, "Custom bezier", bezierIcon);
|
|
11602
|
+
this.#setOptionIconByValue(this.#dropdown, "Custom spring", springIcon);
|
|
11603
|
+
this.#setOptionIconByValue(
|
|
11604
|
+
this.#dropdown.select,
|
|
11605
|
+
"Custom bezier",
|
|
11606
|
+
bezierIcon,
|
|
11607
|
+
);
|
|
11608
|
+
this.#setOptionIconByValue(
|
|
11609
|
+
this.#dropdown.select,
|
|
11610
|
+
"Custom spring",
|
|
11611
|
+
springIcon,
|
|
11612
|
+
);
|
|
11613
|
+
}
|
|
11614
|
+
|
|
11615
|
+
#syncAfterHandleInput(eventType) {
|
|
11616
|
+
this.#updatePaths();
|
|
11617
|
+
this.#presetName = this.#matchPreset();
|
|
11618
|
+
this.#syncDropdown();
|
|
11619
|
+
this.#syncValueInput();
|
|
11620
|
+
this.#emit(eventType);
|
|
11621
|
+
}
|
|
11622
|
+
|
|
11623
|
+
#handleBezierKeyboard(event, handle) {
|
|
11624
|
+
const step = event.shiftKey ? 0.1 : 0.01;
|
|
11625
|
+
const point = handle === 1 ? this.#cp1 : this.#cp2;
|
|
11626
|
+
|
|
11627
|
+
switch (event.key) {
|
|
11628
|
+
case "ArrowLeft":
|
|
11629
|
+
point.x -= step;
|
|
11630
|
+
break;
|
|
11631
|
+
case "ArrowRight":
|
|
11632
|
+
point.x += step;
|
|
11633
|
+
break;
|
|
11634
|
+
case "ArrowUp":
|
|
11635
|
+
point.y += step;
|
|
11636
|
+
break;
|
|
11637
|
+
case "ArrowDown":
|
|
11638
|
+
point.y -= step;
|
|
11639
|
+
break;
|
|
11640
|
+
case "Home":
|
|
11641
|
+
point.x = 0;
|
|
11642
|
+
point.y = 0;
|
|
11643
|
+
break;
|
|
11644
|
+
case "End":
|
|
11645
|
+
point.x = 1;
|
|
11646
|
+
point.y = 1;
|
|
11647
|
+
break;
|
|
11648
|
+
default:
|
|
11649
|
+
return false;
|
|
11650
|
+
}
|
|
11651
|
+
|
|
11652
|
+
point.x = Math.max(0, Math.min(1, Math.round(point.x * 100) / 100));
|
|
11653
|
+
point.y = Math.round(point.y * 100) / 100;
|
|
11654
|
+
this.#syncAfterHandleInput("input");
|
|
11655
|
+
this.#emit("change");
|
|
11656
|
+
return true;
|
|
11657
|
+
}
|
|
11658
|
+
|
|
11659
|
+
#handleSpringKeyboard(event, handleType) {
|
|
11660
|
+
const unit = event.shiftKey ? 10 : 1;
|
|
11661
|
+
|
|
11662
|
+
if (handleType === "bounce") {
|
|
11663
|
+
switch (event.key) {
|
|
11664
|
+
case "ArrowUp":
|
|
11665
|
+
this.#spring.damping = Math.max(1, Math.round(this.#spring.damping - unit));
|
|
11666
|
+
break;
|
|
11667
|
+
case "ArrowDown":
|
|
11668
|
+
this.#spring.damping = Math.max(1, Math.round(this.#spring.damping + unit));
|
|
11669
|
+
break;
|
|
11670
|
+
case "Home":
|
|
11671
|
+
this.#spring.damping = 1;
|
|
11672
|
+
break;
|
|
11673
|
+
case "End":
|
|
11674
|
+
this.#spring.damping = 50;
|
|
11675
|
+
break;
|
|
11676
|
+
default:
|
|
11677
|
+
return false;
|
|
11678
|
+
}
|
|
11679
|
+
} else {
|
|
11680
|
+
const dx = unit * 2;
|
|
11681
|
+
switch (event.key) {
|
|
11682
|
+
case "ArrowLeft":
|
|
11683
|
+
this.#springDuration = Math.max(0.05, this.#springDuration - dx / 200);
|
|
11684
|
+
this.#spring.stiffness = Math.max(10, Math.round(this.#spring.stiffness + dx * 1.5));
|
|
11685
|
+
break;
|
|
11686
|
+
case "ArrowRight":
|
|
11687
|
+
this.#springDuration = Math.min(0.95, this.#springDuration + dx / 200);
|
|
11688
|
+
this.#spring.stiffness = Math.max(10, Math.round(this.#spring.stiffness - dx * 1.5));
|
|
11689
|
+
break;
|
|
11690
|
+
case "Home":
|
|
11691
|
+
this.#springDuration = 0.05;
|
|
11692
|
+
break;
|
|
11693
|
+
case "End":
|
|
11694
|
+
this.#springDuration = 0.95;
|
|
11695
|
+
break;
|
|
11696
|
+
default:
|
|
11697
|
+
return false;
|
|
11698
|
+
}
|
|
11699
|
+
}
|
|
11700
|
+
|
|
11701
|
+
this.#syncAfterHandleInput("input");
|
|
11702
|
+
this.#emit("change");
|
|
11703
|
+
return true;
|
|
11704
|
+
}
|
|
11705
|
+
|
|
11706
|
+
#setupHandleInteraction(handleContainer, handle) {
|
|
11707
|
+
const handleEl = handleContainer?.querySelector("fig-handle");
|
|
11708
|
+
if (!handleEl) return;
|
|
10624
11709
|
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
11710
|
+
handleEl.addEventListener(
|
|
11711
|
+
"pointerdown",
|
|
11712
|
+
(event) => {
|
|
11713
|
+
event.preventDefault();
|
|
11714
|
+
event.stopImmediatePropagation();
|
|
11715
|
+
if (this.#mode === "bezier") {
|
|
11716
|
+
this.#startBezierDrag(event, Number(handle));
|
|
11717
|
+
} else {
|
|
11718
|
+
this.#startSpringDrag(event, handle);
|
|
11719
|
+
}
|
|
11720
|
+
},
|
|
11721
|
+
{ capture: true },
|
|
10632
11722
|
);
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
"
|
|
10636
|
-
|
|
11723
|
+
|
|
11724
|
+
handleEl.addEventListener(
|
|
11725
|
+
"keydown",
|
|
11726
|
+
(event) => {
|
|
11727
|
+
if (
|
|
11728
|
+
!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(
|
|
11729
|
+
event.key,
|
|
11730
|
+
)
|
|
11731
|
+
) {
|
|
11732
|
+
return;
|
|
11733
|
+
}
|
|
11734
|
+
const handled =
|
|
11735
|
+
this.#mode === "bezier"
|
|
11736
|
+
? this.#handleBezierKeyboard(event, Number(handle))
|
|
11737
|
+
: this.#handleSpringKeyboard(event, handle);
|
|
11738
|
+
if (!handled) return;
|
|
11739
|
+
event.preventDefault();
|
|
11740
|
+
event.stopImmediatePropagation();
|
|
11741
|
+
},
|
|
11742
|
+
{ capture: true },
|
|
10637
11743
|
);
|
|
10638
11744
|
}
|
|
10639
11745
|
|
|
@@ -10655,6 +11761,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
10655
11761
|
|
|
10656
11762
|
#setupEvents() {
|
|
10657
11763
|
if (this.#svg && this.#mode === "bezier") {
|
|
11764
|
+
this.#setupHandleInteraction(this.#handle1, "1");
|
|
11765
|
+
this.#setupHandleInteraction(this.#handle2, "2");
|
|
10658
11766
|
this.#handle1.addEventListener("pointerdown", (e) =>
|
|
10659
11767
|
this.#startBezierDrag(e, 1),
|
|
10660
11768
|
);
|
|
@@ -10673,6 +11781,8 @@ class FigEasingCurve extends HTMLElement {
|
|
|
10673
11781
|
});
|
|
10674
11782
|
}
|
|
10675
11783
|
} else if (this.#svg) {
|
|
11784
|
+
this.#setupHandleInteraction(this.#handle1, "bounce");
|
|
11785
|
+
this.#setupHandleInteraction(this.#handle2, "duration");
|
|
10676
11786
|
this.#handle1.addEventListener("pointerdown", (e) => {
|
|
10677
11787
|
e.stopPropagation();
|
|
10678
11788
|
this.#startSpringDrag(e, "bounce");
|
|
@@ -11518,6 +12628,46 @@ class FigOriginGrid extends HTMLElement {
|
|
|
11518
12628
|
};
|
|
11519
12629
|
}
|
|
11520
12630
|
|
|
12631
|
+
#moveHandleByKeyboard(event) {
|
|
12632
|
+
if (!this.#dragEnabled) return false;
|
|
12633
|
+
const step = event.shiftKey ? 10 : 1;
|
|
12634
|
+
let nextX = this.#x;
|
|
12635
|
+
let nextY = this.#y;
|
|
12636
|
+
|
|
12637
|
+
switch (event.key) {
|
|
12638
|
+
case "ArrowLeft":
|
|
12639
|
+
nextX -= step;
|
|
12640
|
+
break;
|
|
12641
|
+
case "ArrowRight":
|
|
12642
|
+
nextX += step;
|
|
12643
|
+
break;
|
|
12644
|
+
case "ArrowUp":
|
|
12645
|
+
nextY -= step;
|
|
12646
|
+
break;
|
|
12647
|
+
case "ArrowDown":
|
|
12648
|
+
nextY += step;
|
|
12649
|
+
break;
|
|
12650
|
+
case "Home":
|
|
12651
|
+
nextX = 0;
|
|
12652
|
+
nextY = 0;
|
|
12653
|
+
break;
|
|
12654
|
+
case "End":
|
|
12655
|
+
nextX = 100;
|
|
12656
|
+
nextY = 100;
|
|
12657
|
+
break;
|
|
12658
|
+
default:
|
|
12659
|
+
return false;
|
|
12660
|
+
}
|
|
12661
|
+
|
|
12662
|
+
this.#setFromPercent(
|
|
12663
|
+
this.#clampPercentage(nextX),
|
|
12664
|
+
this.#clampPercentage(nextY),
|
|
12665
|
+
"input",
|
|
12666
|
+
);
|
|
12667
|
+
this.#emit("change");
|
|
12668
|
+
return true;
|
|
12669
|
+
}
|
|
12670
|
+
|
|
11521
12671
|
#detachHandleDragListeners() {
|
|
11522
12672
|
if (
|
|
11523
12673
|
!this.#grid ||
|
|
@@ -11621,6 +12771,12 @@ class FigOriginGrid extends HTMLElement {
|
|
|
11621
12771
|
this.#clearHoveredCells();
|
|
11622
12772
|
});
|
|
11623
12773
|
|
|
12774
|
+
this.#handle.addEventListener("keydown", (e) => {
|
|
12775
|
+
if (!this.#moveHandleByKeyboard(e)) return;
|
|
12776
|
+
e.preventDefault();
|
|
12777
|
+
e.stopPropagation();
|
|
12778
|
+
});
|
|
12779
|
+
|
|
11624
12780
|
const bindValueInput = (inputEl, axis) => {
|
|
11625
12781
|
if (!inputEl) return;
|
|
11626
12782
|
const handle = (e) => {
|
|
@@ -11652,7 +12808,7 @@ customElements.define("fig-origin-grid", FigOriginGrid);
|
|
|
11652
12808
|
* @attr {number} transform - A scaling factor for the output.
|
|
11653
12809
|
* @attr {boolean} fields - Whether to display X and Y inputs.
|
|
11654
12810
|
* @attr {string} aspect-ratio - Aspect ratio for the joystick plane container.
|
|
11655
|
-
* @attr {string} axis-labels -
|
|
12811
|
+
* @attr {string} axis-labels - Comma- or space-delimited labels. 1 token: top. 2 tokens: x y. 4 tokens: left right top bottom.
|
|
11656
12812
|
*/
|
|
11657
12813
|
class FigInputJoystick extends HTMLElement {
|
|
11658
12814
|
#boundPlanePointerDown = null;
|
|
@@ -11736,7 +12892,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
11736
12892
|
if (!raw) {
|
|
11737
12893
|
return { left: "", right: "", top: "", bottom: "", leftNoRotate: false };
|
|
11738
12894
|
}
|
|
11739
|
-
const tokens = raw.split(
|
|
12895
|
+
const tokens = raw.split(/[\s,]+/).filter(Boolean);
|
|
11740
12896
|
if (tokens.length === 1) {
|
|
11741
12897
|
return {
|
|
11742
12898
|
left: "",
|
|
@@ -11775,7 +12931,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
11775
12931
|
].join("");
|
|
11776
12932
|
|
|
11777
12933
|
return `
|
|
11778
|
-
<div class="fig-input-joystick-plane-container"
|
|
12934
|
+
<div class="fig-input-joystick-plane-container">
|
|
11779
12935
|
${labelsMarkup}
|
|
11780
12936
|
<div class="fig-input-joystick-plane">
|
|
11781
12937
|
<div class="fig-input-joystick-guides"></div>
|
|
@@ -11977,8 +13133,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
11977
13133
|
}
|
|
11978
13134
|
|
|
11979
13135
|
focus() {
|
|
11980
|
-
|
|
11981
|
-
container?.focus();
|
|
13136
|
+
this.cursor?.focus();
|
|
11982
13137
|
}
|
|
11983
13138
|
static get observedAttributes() {
|
|
11984
13139
|
return [
|
|
@@ -12069,10 +13224,11 @@ class FigShimmer extends HTMLElement {
|
|
|
12069
13224
|
if (duration) {
|
|
12070
13225
|
this.style.setProperty(this.durationPropertyName, duration);
|
|
12071
13226
|
}
|
|
13227
|
+
this.#syncA11y();
|
|
12072
13228
|
}
|
|
12073
13229
|
|
|
12074
13230
|
static get observedAttributes() {
|
|
12075
|
-
return ["duration", "playing"];
|
|
13231
|
+
return ["duration", "playing", "aria-label", "aria-labelledby"];
|
|
12076
13232
|
}
|
|
12077
13233
|
|
|
12078
13234
|
get playing() {
|
|
@@ -12091,138 +13247,44 @@ class FigShimmer extends HTMLElement {
|
|
|
12091
13247
|
if (name === "duration") {
|
|
12092
13248
|
this.style.setProperty(this.durationPropertyName, newValue || "1.5s");
|
|
12093
13249
|
}
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
}
|
|
12097
|
-
customElements.define("fig-shimmer", FigShimmer);
|
|
12098
|
-
|
|
12099
|
-
// FigSkeleton
|
|
12100
|
-
class FigSkeleton extends FigShimmer {}
|
|
12101
|
-
customElements.define("fig-skeleton", FigSkeleton);
|
|
12102
|
-
|
|
12103
|
-
// FigLayer
|
|
12104
|
-
class FigLayer extends HTMLElement {
|
|
12105
|
-
static get observedAttributes() {
|
|
12106
|
-
return ["open", "visible"];
|
|
12107
|
-
}
|
|
12108
|
-
|
|
12109
|
-
#chevron = null;
|
|
12110
|
-
#boundHandleChevronClick = null;
|
|
12111
|
-
|
|
12112
|
-
connectedCallback() {
|
|
12113
|
-
// Use requestAnimationFrame to ensure child elements have rendered
|
|
12114
|
-
requestAnimationFrame(() => {
|
|
12115
|
-
this.#injectChevron();
|
|
12116
|
-
});
|
|
12117
|
-
}
|
|
12118
|
-
|
|
12119
|
-
disconnectedCallback() {
|
|
12120
|
-
if (this.#chevron && this.#boundHandleChevronClick) {
|
|
12121
|
-
this.#chevron.removeEventListener("click", this.#boundHandleChevronClick);
|
|
12122
|
-
}
|
|
12123
|
-
}
|
|
12124
|
-
|
|
12125
|
-
#injectChevron() {
|
|
12126
|
-
const row = this.querySelector(":scope > .fig-layer-row");
|
|
12127
|
-
if (!row) return;
|
|
12128
|
-
|
|
12129
|
-
// Check if chevron already exists
|
|
12130
|
-
if (row.querySelector(".fig-layer-chevron")) return;
|
|
12131
|
-
|
|
12132
|
-
// Always create chevron element - CSS handles visibility via :has(fig-layer)
|
|
12133
|
-
this.#chevron = document.createElement("span");
|
|
12134
|
-
this.#chevron.className = "fig-layer-chevron";
|
|
12135
|
-
row.prepend(this.#chevron);
|
|
12136
|
-
|
|
12137
|
-
// Add click listener to chevron only
|
|
12138
|
-
this.#boundHandleChevronClick = this.#handleChevronClick.bind(this);
|
|
12139
|
-
this.#chevron.addEventListener("click", this.#boundHandleChevronClick);
|
|
12140
|
-
}
|
|
12141
|
-
|
|
12142
|
-
#handleChevronClick(e) {
|
|
12143
|
-
e.stopPropagation();
|
|
12144
|
-
this.open = !this.open;
|
|
12145
|
-
}
|
|
12146
|
-
|
|
12147
|
-
get open() {
|
|
12148
|
-
const attr = this.getAttribute("open");
|
|
12149
|
-
return attr !== null && attr !== "false";
|
|
12150
|
-
}
|
|
12151
|
-
|
|
12152
|
-
set open(value) {
|
|
12153
|
-
const oldValue = this.open;
|
|
12154
|
-
if (value) {
|
|
12155
|
-
this.setAttribute("open", "true");
|
|
12156
|
-
} else {
|
|
12157
|
-
this.setAttribute("open", "false");
|
|
12158
|
-
}
|
|
12159
|
-
if (oldValue !== value) {
|
|
12160
|
-
this.dispatchEvent(
|
|
12161
|
-
new CustomEvent("openchange", {
|
|
12162
|
-
detail: { open: value },
|
|
12163
|
-
bubbles: true,
|
|
12164
|
-
}),
|
|
12165
|
-
);
|
|
13250
|
+
if (name === "playing" || name === "aria-label" || name === "aria-labelledby") {
|
|
13251
|
+
this.#syncA11y();
|
|
12166
13252
|
}
|
|
12167
13253
|
}
|
|
12168
13254
|
|
|
12169
|
-
|
|
12170
|
-
const
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
const oldValue = this.visible;
|
|
12176
|
-
if (value) {
|
|
12177
|
-
this.setAttribute("visible", "true");
|
|
13255
|
+
#syncA11y() {
|
|
13256
|
+
const playing = this.playing;
|
|
13257
|
+
this.setAttribute("aria-busy", playing ? "true" : "false");
|
|
13258
|
+
if (this.hasAttribute("aria-label") || this.hasAttribute("aria-labelledby")) {
|
|
13259
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "status");
|
|
13260
|
+
this.removeAttribute("aria-hidden");
|
|
12178
13261
|
} else {
|
|
12179
|
-
this.
|
|
12180
|
-
|
|
12181
|
-
if (oldValue !== value) {
|
|
12182
|
-
this.dispatchEvent(
|
|
12183
|
-
new CustomEvent("visibilitychange", {
|
|
12184
|
-
detail: { visible: value },
|
|
12185
|
-
bubbles: true,
|
|
12186
|
-
}),
|
|
12187
|
-
);
|
|
13262
|
+
this.removeAttribute("role");
|
|
13263
|
+
this.setAttribute("aria-hidden", "true");
|
|
12188
13264
|
}
|
|
12189
13265
|
}
|
|
13266
|
+
}
|
|
13267
|
+
customElements.define("fig-shimmer", FigShimmer);
|
|
12190
13268
|
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
new CustomEvent("openchange", {
|
|
12198
|
-
detail: { open: isOpen },
|
|
12199
|
-
bubbles: true,
|
|
12200
|
-
}),
|
|
12201
|
-
);
|
|
12202
|
-
}
|
|
12203
|
-
|
|
12204
|
-
if (name === "visible") {
|
|
12205
|
-
const isVisible = newValue !== "false";
|
|
12206
|
-
this.dispatchEvent(
|
|
12207
|
-
new CustomEvent("visibilitychange", {
|
|
12208
|
-
detail: { visible: isVisible },
|
|
12209
|
-
bubbles: true,
|
|
12210
|
-
}),
|
|
12211
|
-
);
|
|
12212
|
-
}
|
|
13269
|
+
// FigSkeleton
|
|
13270
|
+
class FigSkeleton extends FigShimmer {
|
|
13271
|
+
connectedCallback() {
|
|
13272
|
+
super.connectedCallback();
|
|
13273
|
+
this.inert = true;
|
|
13274
|
+
this.setAttribute("inert", "");
|
|
12213
13275
|
}
|
|
12214
13276
|
}
|
|
12215
|
-
customElements.define("fig-
|
|
13277
|
+
customElements.define("fig-skeleton", FigSkeleton);
|
|
12216
13278
|
|
|
12217
13279
|
// FigGroup
|
|
12218
13280
|
class FigGroup extends HTMLElement {
|
|
12219
|
-
static observedAttributes = ["name", "collapsible"];
|
|
13281
|
+
static observedAttributes = ["name", "collapsible", "open"];
|
|
12220
13282
|
|
|
12221
13283
|
#header = null;
|
|
12222
13284
|
#chevron = null;
|
|
12223
13285
|
|
|
12224
13286
|
connectedCallback() {
|
|
12225
|
-
|
|
13287
|
+
this.#render();
|
|
12226
13288
|
}
|
|
12227
13289
|
|
|
12228
13290
|
disconnectedCallback() {
|
|
@@ -12231,11 +13293,17 @@ class FigGroup extends HTMLElement {
|
|
|
12231
13293
|
}
|
|
12232
13294
|
if (this.#header) {
|
|
12233
13295
|
this.#header.removeEventListener("click", this.#handleToggle);
|
|
13296
|
+
this.#header.removeEventListener("keydown", this.#handleHeaderKeyDown);
|
|
13297
|
+
this.#header.querySelector("h3")?.removeEventListener("click", this.#handleToggle);
|
|
12234
13298
|
}
|
|
12235
13299
|
}
|
|
12236
13300
|
|
|
12237
13301
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
12238
13302
|
if (oldValue === newValue) return;
|
|
13303
|
+
if (name === "open") {
|
|
13304
|
+
this.#header?.setAttribute("aria-expanded", String(this.open));
|
|
13305
|
+
return;
|
|
13306
|
+
}
|
|
12239
13307
|
this.#render();
|
|
12240
13308
|
}
|
|
12241
13309
|
|
|
@@ -12251,6 +13319,7 @@ class FigGroup extends HTMLElement {
|
|
|
12251
13319
|
} else {
|
|
12252
13320
|
this.setAttribute("open", "false");
|
|
12253
13321
|
}
|
|
13322
|
+
this.#header?.setAttribute("aria-expanded", String(!!value));
|
|
12254
13323
|
if (was !== !!value) {
|
|
12255
13324
|
this.dispatchEvent(
|
|
12256
13325
|
new CustomEvent("openchange", {
|
|
@@ -12266,6 +13335,13 @@ class FigGroup extends HTMLElement {
|
|
|
12266
13335
|
this.open = !this.open;
|
|
12267
13336
|
};
|
|
12268
13337
|
|
|
13338
|
+
#handleHeaderKeyDown = (e) => {
|
|
13339
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
13340
|
+
e.preventDefault();
|
|
13341
|
+
e.stopPropagation();
|
|
13342
|
+
this.open = !this.open;
|
|
13343
|
+
};
|
|
13344
|
+
|
|
12269
13345
|
#render() {
|
|
12270
13346
|
const isCollapsible = this.hasAttribute("collapsible");
|
|
12271
13347
|
const nameAttr = this.getAttribute("name");
|
|
@@ -12298,9 +13374,14 @@ class FigGroup extends HTMLElement {
|
|
|
12298
13374
|
h3 = document.createElement("h3");
|
|
12299
13375
|
this.#header.prepend(h3);
|
|
12300
13376
|
}
|
|
13377
|
+
if (!h3.id) h3.id = figUniqueId();
|
|
12301
13378
|
if (this.#header.dataset.generated) {
|
|
12302
13379
|
h3.textContent = label;
|
|
12303
13380
|
}
|
|
13381
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "group");
|
|
13382
|
+
if (!this.hasAttribute("aria-label") && !this.hasAttribute("aria-labelledby")) {
|
|
13383
|
+
this.setAttribute("aria-labelledby", h3.id);
|
|
13384
|
+
}
|
|
12304
13385
|
|
|
12305
13386
|
if (isCollapsible) {
|
|
12306
13387
|
if (!h3.querySelector(".fig-group-chevron")) {
|
|
@@ -12311,12 +13392,26 @@ class FigGroup extends HTMLElement {
|
|
|
12311
13392
|
h3.prepend(chevron);
|
|
12312
13393
|
}
|
|
12313
13394
|
this.#chevron = h3.querySelector(".fig-group-chevron");
|
|
12314
|
-
h3.
|
|
13395
|
+
h3.removeEventListener("click", this.#handleToggle);
|
|
13396
|
+
this.#header.removeEventListener("click", this.#handleToggle);
|
|
13397
|
+
this.#header.addEventListener("click", this.#handleToggle);
|
|
13398
|
+
this.#header.setAttribute("role", "button");
|
|
13399
|
+
this.#header.setAttribute("tabindex", "0");
|
|
13400
|
+
this.#header.setAttribute("aria-expanded", String(this.open));
|
|
13401
|
+
this.#header.removeEventListener("keydown", this.#handleHeaderKeyDown);
|
|
13402
|
+
this.#header.addEventListener("keydown", this.#handleHeaderKeyDown);
|
|
12315
13403
|
|
|
12316
13404
|
if (!this.hasAttribute("open")) {
|
|
12317
13405
|
this.setAttribute("open", "false");
|
|
13406
|
+
this.#header.setAttribute("aria-expanded", "false");
|
|
12318
13407
|
}
|
|
12319
13408
|
} else {
|
|
13409
|
+
h3.removeEventListener("click", this.#handleToggle);
|
|
13410
|
+
this.#header.removeEventListener("click", this.#handleToggle);
|
|
13411
|
+
this.#header.removeAttribute("role");
|
|
13412
|
+
this.#header.removeAttribute("tabindex");
|
|
13413
|
+
this.#header.removeAttribute("aria-expanded");
|
|
13414
|
+
this.#header.removeEventListener("keydown", this.#handleHeaderKeyDown);
|
|
12320
13415
|
if (this.#chevron) {
|
|
12321
13416
|
this.#chevron.remove();
|
|
12322
13417
|
this.#chevron = null;
|
|
@@ -12346,7 +13441,14 @@ class FigFooter extends HTMLElement {}
|
|
|
12346
13441
|
customElements.define("fig-footer", FigFooter);
|
|
12347
13442
|
|
|
12348
13443
|
/* Presentational elements (CSS-only, no behavior) */
|
|
12349
|
-
class FigSpinner extends HTMLElement {
|
|
13444
|
+
class FigSpinner extends HTMLElement {
|
|
13445
|
+
connectedCallback() {
|
|
13446
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "status");
|
|
13447
|
+
if (!this.hasAttribute("aria-label") && !this.hasAttribute("aria-labelledby")) {
|
|
13448
|
+
this.setAttribute("aria-label", "Loading");
|
|
13449
|
+
}
|
|
13450
|
+
}
|
|
13451
|
+
}
|
|
12350
13452
|
customElements.define("fig-spinner", FigSpinner);
|
|
12351
13453
|
|
|
12352
13454
|
/**
|
|
@@ -12486,7 +13588,16 @@ class FigColorTip extends HTMLElement {
|
|
|
12486
13588
|
#boundHandleChange = this.#handlePickerChange.bind(this);
|
|
12487
13589
|
|
|
12488
13590
|
static get observedAttributes() {
|
|
12489
|
-
return [
|
|
13591
|
+
return [
|
|
13592
|
+
"value",
|
|
13593
|
+
"selected",
|
|
13594
|
+
"disabled",
|
|
13595
|
+
"alpha",
|
|
13596
|
+
"control",
|
|
13597
|
+
"aria-label",
|
|
13598
|
+
"aria-labelledby",
|
|
13599
|
+
"aria-describedby",
|
|
13600
|
+
];
|
|
12490
13601
|
}
|
|
12491
13602
|
|
|
12492
13603
|
get #controlMode() {
|
|
@@ -12549,10 +13660,12 @@ class FigColorTip extends HTMLElement {
|
|
|
12549
13660
|
const mode = this.#controlMode;
|
|
12550
13661
|
if (mode === "add" || mode === "remove") {
|
|
12551
13662
|
const iconName = mode === "add" ? "add" : "minus";
|
|
12552
|
-
|
|
13663
|
+
const label = this.getAttribute("aria-label") || (mode === "add" ? "Add color stop" : "Remove color stop");
|
|
13664
|
+
this.innerHTML = `<fig-button icon variant="ghost" aria-label="${label}"><fig-icon name="${iconName}"></fig-icon></fig-button>`;
|
|
12553
13665
|
this.#fillPicker = null;
|
|
12554
13666
|
this.#chit = null;
|
|
12555
13667
|
this.addEventListener("click", this.#handleControlClick);
|
|
13668
|
+
this.#syncA11y();
|
|
12556
13669
|
return;
|
|
12557
13670
|
}
|
|
12558
13671
|
this.removeEventListener("click", this.#handleControlClick);
|
|
@@ -12586,6 +13699,7 @@ class FigColorTip extends HTMLElement {
|
|
|
12586
13699
|
this.#chit?.addEventListener("change", this.#boundHandleChange);
|
|
12587
13700
|
}
|
|
12588
13701
|
this.#observeChitSelected();
|
|
13702
|
+
this.#syncA11y();
|
|
12589
13703
|
}
|
|
12590
13704
|
|
|
12591
13705
|
#handleControlClick = () => {
|
|
@@ -12681,6 +13795,7 @@ class FigColorTip extends HTMLElement {
|
|
|
12681
13795
|
}
|
|
12682
13796
|
|
|
12683
13797
|
if (this.#fillPicker) {
|
|
13798
|
+
this.#syncA11y();
|
|
12684
13799
|
const pickerVal =
|
|
12685
13800
|
alpha < 1
|
|
12686
13801
|
? { type: "solid", color, opacity: Math.round(alpha * 100) }
|
|
@@ -12699,6 +13814,7 @@ class FigColorTip extends HTMLElement {
|
|
|
12699
13814
|
}
|
|
12700
13815
|
|
|
12701
13816
|
if (this.#chit) {
|
|
13817
|
+
this.#syncA11y();
|
|
12702
13818
|
this.#chit.setAttribute("background", color);
|
|
12703
13819
|
if (alpha < 1) {
|
|
12704
13820
|
this.#chit.setAttribute("alpha", String(alpha));
|
|
@@ -12713,6 +13829,39 @@ class FigColorTip extends HTMLElement {
|
|
|
12713
13829
|
}
|
|
12714
13830
|
}
|
|
12715
13831
|
|
|
13832
|
+
#syncA11y() {
|
|
13833
|
+
const mode = this.#controlMode;
|
|
13834
|
+
const disabled =
|
|
13835
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
13836
|
+
const selected =
|
|
13837
|
+
this.hasAttribute("selected") && this.getAttribute("selected") !== "false";
|
|
13838
|
+
this.setAttribute("aria-disabled", disabled ? "true" : "false");
|
|
13839
|
+
this.setAttribute("aria-pressed", selected ? "true" : "false");
|
|
13840
|
+
if (mode === "add" || mode === "remove") {
|
|
13841
|
+
const button = this.querySelector("fig-button");
|
|
13842
|
+
const label = this.getAttribute("aria-label") || (mode === "add" ? "Add color stop" : "Remove color stop");
|
|
13843
|
+
button?.setAttribute("aria-label", label);
|
|
13844
|
+
if (disabled) button?.setAttribute("disabled", "");
|
|
13845
|
+
else button?.removeAttribute("disabled");
|
|
13846
|
+
return;
|
|
13847
|
+
}
|
|
13848
|
+
|
|
13849
|
+
const target = this.#fillPicker || this.#chit;
|
|
13850
|
+
if (!target) return;
|
|
13851
|
+
const label = this.getAttribute("aria-label") || "Color stop";
|
|
13852
|
+
const labelledBy = this.getAttribute("aria-labelledby");
|
|
13853
|
+
const describedBy = this.getAttribute("aria-describedby");
|
|
13854
|
+
if (labelledBy) {
|
|
13855
|
+
target.setAttribute("aria-labelledby", labelledBy);
|
|
13856
|
+
target.removeAttribute("aria-label");
|
|
13857
|
+
} else {
|
|
13858
|
+
target.setAttribute("aria-label", label);
|
|
13859
|
+
target.removeAttribute("aria-labelledby");
|
|
13860
|
+
}
|
|
13861
|
+
if (describedBy) target.setAttribute("aria-describedby", describedBy);
|
|
13862
|
+
else target.removeAttribute("aria-describedby");
|
|
13863
|
+
}
|
|
13864
|
+
|
|
12716
13865
|
#updateColorFromPicker(detail, type) {
|
|
12717
13866
|
const nextColor = this.#normalizeColor(detail?.color);
|
|
12718
13867
|
const prevColor = this.#normalizeColor(this.getAttribute("value"));
|
|
@@ -12762,7 +13911,11 @@ class FigColorTip extends HTMLElement {
|
|
|
12762
13911
|
case "value":
|
|
12763
13912
|
case "selected":
|
|
12764
13913
|
case "disabled":
|
|
13914
|
+
case "aria-label":
|
|
13915
|
+
case "aria-labelledby":
|
|
13916
|
+
case "aria-describedby":
|
|
12765
13917
|
this.#syncFromAttributes();
|
|
13918
|
+
this.#syncA11y();
|
|
12766
13919
|
break;
|
|
12767
13920
|
}
|
|
12768
13921
|
}
|
|
@@ -13426,6 +14579,8 @@ class FigHandle extends HTMLElement {
|
|
|
13426
14579
|
"tip",
|
|
13427
14580
|
"hit-area",
|
|
13428
14581
|
"hit-area-mode",
|
|
14582
|
+
"aria-label",
|
|
14583
|
+
"aria-labelledby",
|
|
13429
14584
|
];
|
|
13430
14585
|
|
|
13431
14586
|
#isDragging = false;
|
|
@@ -13653,6 +14808,7 @@ class FigHandle extends HTMLElement {
|
|
|
13653
14808
|
}
|
|
13654
14809
|
|
|
13655
14810
|
connectedCallback() {
|
|
14811
|
+
this.#syncA11y();
|
|
13656
14812
|
this.#syncDrag();
|
|
13657
14813
|
this.#syncHitArea();
|
|
13658
14814
|
this.addEventListener("click", this.#handleSelect);
|
|
@@ -13709,7 +14865,32 @@ class FigHandle extends HTMLElement {
|
|
|
13709
14865
|
};
|
|
13710
14866
|
|
|
13711
14867
|
#handleKeyDown = (e) => {
|
|
14868
|
+
if (e.defaultPrevented) return;
|
|
14869
|
+
if (
|
|
14870
|
+
e.target === this &&
|
|
14871
|
+
this.#dragEnabled &&
|
|
14872
|
+
["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(e.key)
|
|
14873
|
+
) {
|
|
14874
|
+
if (this.#moveByKeyboard(e)) {
|
|
14875
|
+
e.preventDefault();
|
|
14876
|
+
if (!this.hasAttribute("selected")) this.select();
|
|
14877
|
+
}
|
|
14878
|
+
return;
|
|
14879
|
+
}
|
|
13712
14880
|
if (e.key !== "Enter" && e.key !== " ") return;
|
|
14881
|
+
if (e.target === this && !this.hasAttribute("selected")) {
|
|
14882
|
+
e.preventDefault();
|
|
14883
|
+
if (
|
|
14884
|
+
this.getAttribute("type") === "color" &&
|
|
14885
|
+
this.#canOpenColorPicker &&
|
|
14886
|
+
!this.#tipMode
|
|
14887
|
+
) {
|
|
14888
|
+
this.#openDirectColorPicker();
|
|
14889
|
+
} else {
|
|
14890
|
+
this.select();
|
|
14891
|
+
}
|
|
14892
|
+
return;
|
|
14893
|
+
}
|
|
13713
14894
|
if (!this.hasAttribute("selected")) return;
|
|
13714
14895
|
if (this.getAttribute("type") !== "color") return;
|
|
13715
14896
|
if (!this.#canOpenColorPicker) return;
|
|
@@ -13719,6 +14900,64 @@ class FigHandle extends HTMLElement {
|
|
|
13719
14900
|
}
|
|
13720
14901
|
};
|
|
13721
14902
|
|
|
14903
|
+
#moveByKeyboard(event) {
|
|
14904
|
+
if (this.hasAttribute("disabled")) return false;
|
|
14905
|
+
const container = this.#getContainer();
|
|
14906
|
+
if (!container) return false;
|
|
14907
|
+
const rect = container.getBoundingClientRect();
|
|
14908
|
+
if (rect.width <= 0 || rect.height <= 0) return false;
|
|
14909
|
+
|
|
14910
|
+
const axes = this.#axes;
|
|
14911
|
+
const current = this.#positionDetail(rect);
|
|
14912
|
+
const pctStep = event.shiftKey ? 0.1 : 0.01;
|
|
14913
|
+
let px = current.px;
|
|
14914
|
+
let py = current.py;
|
|
14915
|
+
|
|
14916
|
+
switch (event.key) {
|
|
14917
|
+
case "ArrowLeft":
|
|
14918
|
+
if (!axes.x) return false;
|
|
14919
|
+
px -= pctStep;
|
|
14920
|
+
break;
|
|
14921
|
+
case "ArrowRight":
|
|
14922
|
+
if (!axes.x) return false;
|
|
14923
|
+
px += pctStep;
|
|
14924
|
+
break;
|
|
14925
|
+
case "ArrowUp":
|
|
14926
|
+
if (!axes.y) return false;
|
|
14927
|
+
py -= pctStep;
|
|
14928
|
+
break;
|
|
14929
|
+
case "ArrowDown":
|
|
14930
|
+
if (!axes.y) return false;
|
|
14931
|
+
py += pctStep;
|
|
14932
|
+
break;
|
|
14933
|
+
case "Home":
|
|
14934
|
+
if (axes.x) px = 0;
|
|
14935
|
+
if (axes.y) py = 0;
|
|
14936
|
+
break;
|
|
14937
|
+
case "End":
|
|
14938
|
+
if (axes.x) px = 1;
|
|
14939
|
+
if (axes.y) py = 1;
|
|
14940
|
+
break;
|
|
14941
|
+
default:
|
|
14942
|
+
return false;
|
|
14943
|
+
}
|
|
14944
|
+
|
|
14945
|
+
px = Math.max(0, Math.min(1, px));
|
|
14946
|
+
py = Math.max(0, Math.min(1, py));
|
|
14947
|
+
this.#syncPositionTranslate(axes);
|
|
14948
|
+
if (axes.x) this.style.left = `${Math.round(px * rect.width)}px`;
|
|
14949
|
+
if (axes.y) this.style.top = `${Math.round(py * rect.height)}px`;
|
|
14950
|
+
this.#syncValueAttribute();
|
|
14951
|
+
const detail = {
|
|
14952
|
+
...this.#positionDetail(rect),
|
|
14953
|
+
shiftKey: event.shiftKey,
|
|
14954
|
+
keyboard: true,
|
|
14955
|
+
};
|
|
14956
|
+
this.dispatchEvent(new CustomEvent("input", { bubbles: true, detail }));
|
|
14957
|
+
this.dispatchEvent(new CustomEvent("change", { bubbles: true, detail }));
|
|
14958
|
+
return true;
|
|
14959
|
+
}
|
|
14960
|
+
|
|
13722
14961
|
attributeChangedCallback(name, _old, value) {
|
|
13723
14962
|
if (name === "color") {
|
|
13724
14963
|
if (!value || value === "false" || value === "true") {
|
|
@@ -13734,6 +14973,16 @@ class FigHandle extends HTMLElement {
|
|
|
13734
14973
|
if (name === "drag") this.#syncDrag();
|
|
13735
14974
|
if (name === "hit-area") this.#syncHitArea();
|
|
13736
14975
|
if (name === "selected") this.#syncColorTipSelected();
|
|
14976
|
+
if (
|
|
14977
|
+
name === "selected" ||
|
|
14978
|
+
name === "disabled" ||
|
|
14979
|
+
name === "type" ||
|
|
14980
|
+
name === "tip" ||
|
|
14981
|
+
name === "aria-label" ||
|
|
14982
|
+
name === "aria-labelledby"
|
|
14983
|
+
) {
|
|
14984
|
+
this.#syncA11y();
|
|
14985
|
+
}
|
|
13737
14986
|
if (name === "value" && !this.#applyingValue && !this.#isDragging) {
|
|
13738
14987
|
this.#applyValue(value);
|
|
13739
14988
|
}
|
|
@@ -13755,6 +15004,25 @@ class FigHandle extends HTMLElement {
|
|
|
13755
15004
|
}
|
|
13756
15005
|
}
|
|
13757
15006
|
|
|
15007
|
+
#syncA11y() {
|
|
15008
|
+
const disabled =
|
|
15009
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
15010
|
+
const selected =
|
|
15011
|
+
this.hasAttribute("selected") && this.getAttribute("selected") !== "false";
|
|
15012
|
+
if (!this.hasAttribute("role")) this.setAttribute("role", "button");
|
|
15013
|
+
if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", disabled ? "-1" : "0");
|
|
15014
|
+
else if (disabled) this.setAttribute("tabindex", "-1");
|
|
15015
|
+
this.setAttribute("aria-disabled", disabled ? "true" : "false");
|
|
15016
|
+
this.setAttribute("aria-pressed", selected ? "true" : "false");
|
|
15017
|
+
if (!this.hasAttribute("aria-label") && !this.hasAttribute("aria-labelledby")) {
|
|
15018
|
+
const mode = this.#tipMode || this.getAttribute("type") || "handle";
|
|
15019
|
+
this.setAttribute(
|
|
15020
|
+
"aria-label",
|
|
15021
|
+
mode === "color" ? "Color handle" : mode === "add" ? "Add handle" : mode === "remove" ? "Remove handle" : "Handle",
|
|
15022
|
+
);
|
|
15023
|
+
}
|
|
15024
|
+
}
|
|
15025
|
+
|
|
13758
15026
|
#teardownDrag() {
|
|
13759
15027
|
if (this.#boundPointerDown) {
|
|
13760
15028
|
this.removeEventListener("pointerdown", this.#boundPointerDown);
|
|
@@ -14240,9 +15508,29 @@ class FigMenuItem extends HTMLElement {
|
|
|
14240
15508
|
if (!this.hasAttribute("tabindex")) {
|
|
14241
15509
|
this.setAttribute("tabindex", "-1");
|
|
14242
15510
|
}
|
|
15511
|
+
this.#syncDisabled();
|
|
15512
|
+
}
|
|
15513
|
+
|
|
15514
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
15515
|
+
if (oldValue === newValue) return;
|
|
15516
|
+
if (name === "disabled") {
|
|
15517
|
+
this.#syncDisabled();
|
|
15518
|
+
}
|
|
14243
15519
|
}
|
|
14244
15520
|
|
|
14245
|
-
|
|
15521
|
+
#syncDisabled() {
|
|
15522
|
+
const disabled =
|
|
15523
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
15524
|
+
if (disabled) {
|
|
15525
|
+
this.setAttribute("aria-disabled", "true");
|
|
15526
|
+
this.setAttribute("tabindex", "-1");
|
|
15527
|
+
} else {
|
|
15528
|
+
this.removeAttribute("aria-disabled");
|
|
15529
|
+
if (!this.hasAttribute("tabindex")) {
|
|
15530
|
+
this.setAttribute("tabindex", "-1");
|
|
15531
|
+
}
|
|
15532
|
+
}
|
|
15533
|
+
}
|
|
14246
15534
|
}
|
|
14247
15535
|
customElements.define("fig-menu-item", FigMenuItem);
|
|
14248
15536
|
|
|
@@ -14312,6 +15600,7 @@ class FigMenu extends HTMLElement {
|
|
|
14312
15600
|
|
|
14313
15601
|
disconnectedCallback() {
|
|
14314
15602
|
this.#teardownListeners();
|
|
15603
|
+
document.removeEventListener("keydown", this.#boundMenuKeydown, true);
|
|
14315
15604
|
if (this.#observer) {
|
|
14316
15605
|
this.#observer.disconnect();
|
|
14317
15606
|
this.#observer = null;
|
|
@@ -14366,6 +15655,7 @@ class FigMenu extends HTMLElement {
|
|
|
14366
15655
|
this.#popup.setAttribute("is", "fig-popup");
|
|
14367
15656
|
this.#popup.setAttribute("theme", "menu");
|
|
14368
15657
|
this.#popup.setAttribute("role", "menu");
|
|
15658
|
+
this.#popup.setAttribute("id", this.#popup.getAttribute("id") || figUniqueId());
|
|
14369
15659
|
|
|
14370
15660
|
const position = this.getAttribute("position") || "bottom left";
|
|
14371
15661
|
this.#popup.setAttribute("position", position);
|
|
@@ -14399,9 +15689,11 @@ class FigMenu extends HTMLElement {
|
|
|
14399
15689
|
this.#trigger.addEventListener("click", this.#boundTriggerClick);
|
|
14400
15690
|
this.#trigger.setAttribute("aria-haspopup", "menu");
|
|
14401
15691
|
this.#trigger.setAttribute("aria-expanded", "false");
|
|
15692
|
+
this.#trigger.setAttribute("aria-controls", this.#popup.getAttribute("id"));
|
|
14402
15693
|
}
|
|
14403
15694
|
if (this.#popup) {
|
|
14404
15695
|
this.#popup.addEventListener("click", this.#boundPopupClick);
|
|
15696
|
+
this.#popup.addEventListener("keydown", this.#boundMenuKeydown);
|
|
14405
15697
|
}
|
|
14406
15698
|
}
|
|
14407
15699
|
|
|
@@ -14412,6 +15704,7 @@ class FigMenu extends HTMLElement {
|
|
|
14412
15704
|
}
|
|
14413
15705
|
if (this.#popup) {
|
|
14414
15706
|
this.#popup.removeEventListener("click", this.#boundPopupClick);
|
|
15707
|
+
this.#popup.removeEventListener("keydown", this.#boundMenuKeydown);
|
|
14415
15708
|
}
|
|
14416
15709
|
}
|
|
14417
15710
|
|
|
@@ -14431,6 +15724,7 @@ class FigMenu extends HTMLElement {
|
|
|
14431
15724
|
this.#trigger.addEventListener("click", this.#boundTriggerClick);
|
|
14432
15725
|
this.#trigger.setAttribute("aria-haspopup", "menu");
|
|
14433
15726
|
this.#trigger.setAttribute("aria-expanded", "false");
|
|
15727
|
+
this.#trigger.setAttribute("aria-controls", this.#popup.getAttribute("id"));
|
|
14434
15728
|
this.#popup.anchor = this.#trigger;
|
|
14435
15729
|
this.#syncDisabled();
|
|
14436
15730
|
}
|
|
@@ -14443,7 +15737,10 @@ class FigMenu extends HTMLElement {
|
|
|
14443
15737
|
|
|
14444
15738
|
#getItems() {
|
|
14445
15739
|
if (!this.#popup) return [];
|
|
14446
|
-
return Array.from(this.#popup.querySelectorAll("fig-menu-item
|
|
15740
|
+
return Array.from(this.#popup.querySelectorAll("fig-menu-item")).filter(
|
|
15741
|
+
(item) =>
|
|
15742
|
+
!item.hasAttribute("disabled") || item.getAttribute("disabled") === "false",
|
|
15743
|
+
);
|
|
14447
15744
|
}
|
|
14448
15745
|
|
|
14449
15746
|
#syncFocusedIndex() {
|
|
@@ -14462,9 +15759,9 @@ class FigMenu extends HTMLElement {
|
|
|
14462
15759
|
#focusItemAt(index) {
|
|
14463
15760
|
const items = this.#getItems();
|
|
14464
15761
|
if (!items.length) return;
|
|
14465
|
-
const
|
|
14466
|
-
this.#focusedIndex =
|
|
14467
|
-
items[
|
|
15762
|
+
const wrapped = (index + items.length) % items.length;
|
|
15763
|
+
this.#focusedIndex = wrapped;
|
|
15764
|
+
items[wrapped].focus();
|
|
14468
15765
|
}
|
|
14469
15766
|
|
|
14470
15767
|
#syncDisabled() {
|
|
@@ -14472,8 +15769,11 @@ class FigMenu extends HTMLElement {
|
|
|
14472
15769
|
const disabled = this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
14473
15770
|
if (disabled) {
|
|
14474
15771
|
this.#trigger.setAttribute("disabled", "");
|
|
15772
|
+
this.#trigger.setAttribute("aria-disabled", "true");
|
|
15773
|
+
this.#trigger.setAttribute("aria-expanded", "false");
|
|
14475
15774
|
} else {
|
|
14476
15775
|
this.#trigger.removeAttribute("disabled");
|
|
15776
|
+
this.#trigger.removeAttribute("aria-disabled");
|
|
14477
15777
|
}
|
|
14478
15778
|
}
|
|
14479
15779
|
|
|
@@ -14501,7 +15801,19 @@ class FigMenu extends HTMLElement {
|
|
|
14501
15801
|
}
|
|
14502
15802
|
|
|
14503
15803
|
#handleMenuKeydown(e) {
|
|
14504
|
-
if (
|
|
15804
|
+
if (e.currentTarget === document && e.key !== "Escape") return;
|
|
15805
|
+
if (e.currentTarget === this && this.#popup?.contains(e.target)) return;
|
|
15806
|
+
if (!this.open || !this.#popup?.matches?.(":open")) {
|
|
15807
|
+
if (
|
|
15808
|
+
this.#trigger?.contains(e.target) &&
|
|
15809
|
+
(e.key === "ArrowDown" || e.key === "Enter" || e.key === " ")
|
|
15810
|
+
) {
|
|
15811
|
+
e.preventDefault();
|
|
15812
|
+
this.open = true;
|
|
15813
|
+
requestAnimationFrame(() => this.#focusItemAt(0));
|
|
15814
|
+
}
|
|
15815
|
+
return;
|
|
15816
|
+
}
|
|
14505
15817
|
|
|
14506
15818
|
const items = this.#getItems();
|
|
14507
15819
|
if (!items.length) return;
|
|
@@ -14529,6 +15841,12 @@ class FigMenu extends HTMLElement {
|
|
|
14529
15841
|
this.#focusItemAt(items.length - 1);
|
|
14530
15842
|
break;
|
|
14531
15843
|
}
|
|
15844
|
+
case "Escape": {
|
|
15845
|
+
e.preventDefault();
|
|
15846
|
+
this.open = false;
|
|
15847
|
+
this.#trigger?.focus();
|
|
15848
|
+
break;
|
|
15849
|
+
}
|
|
14532
15850
|
case "Enter":
|
|
14533
15851
|
case " ": {
|
|
14534
15852
|
this.#syncFocusedIndex();
|
|
@@ -14567,6 +15885,7 @@ class FigMenu extends HTMLElement {
|
|
|
14567
15885
|
#openMenu() {
|
|
14568
15886
|
if (!this.#popup) return;
|
|
14569
15887
|
this.#popup.open = true;
|
|
15888
|
+
document.addEventListener("keydown", this.#boundMenuKeydown, true);
|
|
14570
15889
|
if (this.#trigger) {
|
|
14571
15890
|
this.#trigger.setAttribute("aria-expanded", "true");
|
|
14572
15891
|
}
|
|
@@ -14579,7 +15898,9 @@ class FigMenu extends HTMLElement {
|
|
|
14579
15898
|
|
|
14580
15899
|
#closeMenu() {
|
|
14581
15900
|
if (!this.#popup) return;
|
|
15901
|
+
document.removeEventListener("keydown", this.#boundMenuKeydown, true);
|
|
14582
15902
|
this.#popup.open = false;
|
|
15903
|
+
this.#trigger?.setAttribute("aria-expanded", "false");
|
|
14583
15904
|
}
|
|
14584
15905
|
}
|
|
14585
15906
|
customElements.define("fig-menu", FigMenu);
|