@rogieking/figui3 4.13.1 → 4.15.3
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/figui3/SKILL.md +1 -1
- package/README.md +28 -3
- package/components.css +95 -72
- package/dist/components.css +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +69 -69
- package/fig.js +411 -74
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -16,6 +16,15 @@ function figIsWebKitOrIOSBrowser() {
|
|
|
16
16
|
return isIOSBrowser || isDesktopWebKit;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/** @param {string} name @param {{ size?: string, className?: string }} [options] */
|
|
20
|
+
function createFigIcon(name, options = {}) {
|
|
21
|
+
const icon = document.createElement("fig-icon");
|
|
22
|
+
if (name) icon.setAttribute("name", name);
|
|
23
|
+
if (options.size) icon.setAttribute("size", options.size);
|
|
24
|
+
if (options.className) icon.className = options.className;
|
|
25
|
+
return icon;
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
function figSupportsCustomizedBuiltIns() {
|
|
20
29
|
if (
|
|
21
30
|
typeof window === "undefined" ||
|
|
@@ -1234,10 +1243,7 @@ class FigDialog extends HTMLDialogElement {
|
|
|
1234
1243
|
btn.setAttribute("variant", "ghost");
|
|
1235
1244
|
btn.setAttribute("icon", "");
|
|
1236
1245
|
btn.setAttribute("close-dialog", "");
|
|
1237
|
-
|
|
1238
|
-
icon.className = "fig-mask-icon";
|
|
1239
|
-
icon.style.setProperty("--icon", "var(--icon-close)");
|
|
1240
|
-
btn.appendChild(icon);
|
|
1246
|
+
btn.appendChild(createFigIcon("close"));
|
|
1241
1247
|
tooltip.appendChild(btn);
|
|
1242
1248
|
header.appendChild(h3);
|
|
1243
1249
|
header.appendChild(tooltip);
|
|
@@ -1778,6 +1784,10 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1778
1784
|
this.hasAttribute("popover") &&
|
|
1779
1785
|
typeof this.showPopover === "function" &&
|
|
1780
1786
|
!this.matches?.(":popover-open");
|
|
1787
|
+
const positionBeforeReveal = this.shouldAutoReposition();
|
|
1788
|
+
if (positionBeforeReveal) {
|
|
1789
|
+
this.style.visibility = "hidden";
|
|
1790
|
+
}
|
|
1781
1791
|
if (usePopover) {
|
|
1782
1792
|
try {
|
|
1783
1793
|
this.showPopover();
|
|
@@ -1792,6 +1802,10 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1792
1802
|
// Ignore when dialog cannot be shown yet.
|
|
1793
1803
|
}
|
|
1794
1804
|
}
|
|
1805
|
+
if (positionBeforeReveal && (this.matches?.(":open") || this.matches?.(":popover-open"))) {
|
|
1806
|
+
this.positionPopup();
|
|
1807
|
+
this.style.visibility = "";
|
|
1808
|
+
}
|
|
1795
1809
|
|
|
1796
1810
|
this.setupObservers();
|
|
1797
1811
|
document.addEventListener(
|
|
@@ -1811,6 +1825,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1811
1825
|
const anchor = this.resolveAnchor();
|
|
1812
1826
|
if (anchor?.classList) anchor.classList.remove("has-popup-open");
|
|
1813
1827
|
|
|
1828
|
+
this.style.visibility = "";
|
|
1814
1829
|
this._isPopupActive = false;
|
|
1815
1830
|
this._wasDragged = false;
|
|
1816
1831
|
this.teardownObservers();
|
|
@@ -1831,7 +1846,10 @@ class FigPopup extends HTMLDialogElement {
|
|
|
1831
1846
|
// Ignore.
|
|
1832
1847
|
}
|
|
1833
1848
|
}
|
|
1834
|
-
|
|
1849
|
+
// Use :open, not super.open — the custom open getter reads the attribute
|
|
1850
|
+
// removed just before hidePopup(), so super.open can be false while the
|
|
1851
|
+
// native dialog is still open and no "close" event fires.
|
|
1852
|
+
if (this.matches?.(":open")) {
|
|
1835
1853
|
try {
|
|
1836
1854
|
this.close();
|
|
1837
1855
|
} catch (e) {
|
|
@@ -2478,7 +2496,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
2478
2496
|
}
|
|
2479
2497
|
|
|
2480
2498
|
positionPopup() {
|
|
2481
|
-
if (!this.open || !
|
|
2499
|
+
if (!this.open || !this.matches?.(":open")) return;
|
|
2482
2500
|
|
|
2483
2501
|
const popupRect = this.getBoundingClientRect();
|
|
2484
2502
|
const offset = this.parseOffset();
|
|
@@ -4907,8 +4925,10 @@ class FigField extends HTMLElement {
|
|
|
4907
4925
|
this.#toggleable = !!(this.input && "open" in this.input);
|
|
4908
4926
|
|
|
4909
4927
|
if (this.#toggleable && this.label) {
|
|
4910
|
-
this.#chevron =
|
|
4911
|
-
|
|
4928
|
+
this.#chevron = createFigIcon("chevron", {
|
|
4929
|
+
size: "small",
|
|
4930
|
+
className: "fig-field-chevron",
|
|
4931
|
+
});
|
|
4912
4932
|
this.insertBefore(this.#chevron, this.label);
|
|
4913
4933
|
|
|
4914
4934
|
this.#boundToggle = (e) => {
|
|
@@ -5115,6 +5135,14 @@ class FigInputColor extends HTMLElement {
|
|
|
5115
5135
|
// Setup swatch (native picker)
|
|
5116
5136
|
if (this.#swatch) {
|
|
5117
5137
|
this.#swatch.disabled = this.hasAttribute("disabled");
|
|
5138
|
+
const swatchInput = this.#swatch.querySelector('input[type="color"]');
|
|
5139
|
+
if (this.#textInput || this.hasAttribute("swatch-disabled")) {
|
|
5140
|
+
swatchInput?.setAttribute("tabindex", "-1");
|
|
5141
|
+
}
|
|
5142
|
+
if (this.hasAttribute("swatch-disabled")) {
|
|
5143
|
+
swatchInput?.setAttribute("disabled", "");
|
|
5144
|
+
if (swatchInput) swatchInput.style.pointerEvents = "none";
|
|
5145
|
+
}
|
|
5118
5146
|
this.#swatch.addEventListener("input", this.#handleInput.bind(this));
|
|
5119
5147
|
}
|
|
5120
5148
|
|
|
@@ -5232,7 +5260,11 @@ class FigInputColor extends HTMLElement {
|
|
|
5232
5260
|
}
|
|
5233
5261
|
|
|
5234
5262
|
focus() {
|
|
5235
|
-
this.#
|
|
5263
|
+
if (this.#textInput) {
|
|
5264
|
+
this.#textInput.focus();
|
|
5265
|
+
return;
|
|
5266
|
+
}
|
|
5267
|
+
this.#swatch?.focus();
|
|
5236
5268
|
}
|
|
5237
5269
|
|
|
5238
5270
|
#handleInput(event) {
|
|
@@ -6423,9 +6455,10 @@ customElements.define("fig-input-fill", FigInputFill);
|
|
|
6423
6455
|
/* Input Palette */
|
|
6424
6456
|
/**
|
|
6425
6457
|
* A palette of solid colors, each rendered as a fig-input-color swatch.
|
|
6426
|
-
* Manages an internal array of colors with add support.
|
|
6458
|
+
* Manages an internal array of colors with optional add/remove support.
|
|
6427
6459
|
* @attr {string} value - JSON array of hex strings or {color,alpha} objects, or comma-separated hex
|
|
6428
6460
|
* @attr {boolean} disabled - Whether the palette is disabled
|
|
6461
|
+
* @attr {boolean} fixed - When set (or `fixed="true"`), palette length is locked — no add or remove
|
|
6429
6462
|
* @attr {number} min - Minimum number of colors (default: 2)
|
|
6430
6463
|
* @attr {number} max - Maximum number of colors (default: 8); add button hidden at max
|
|
6431
6464
|
* @fires input - During color editing (detail: full color array)
|
|
@@ -6438,7 +6471,7 @@ class FigInputPalette extends HTMLElement {
|
|
|
6438
6471
|
#renderRAF = null;
|
|
6439
6472
|
|
|
6440
6473
|
static get observedAttributes() {
|
|
6441
|
-
return ["value", "disabled", "min", "max", "open", "
|
|
6474
|
+
return ["value", "disabled", "min", "max", "open", "fixed"];
|
|
6442
6475
|
}
|
|
6443
6476
|
|
|
6444
6477
|
get open() {
|
|
@@ -6462,8 +6495,10 @@ class FigInputPalette extends HTMLElement {
|
|
|
6462
6495
|
}
|
|
6463
6496
|
}
|
|
6464
6497
|
|
|
6465
|
-
get #
|
|
6466
|
-
return
|
|
6498
|
+
get #isFixed() {
|
|
6499
|
+
return (
|
|
6500
|
+
this.hasAttribute("fixed") && this.getAttribute("fixed") !== "false"
|
|
6501
|
+
);
|
|
6467
6502
|
}
|
|
6468
6503
|
|
|
6469
6504
|
get #min() {
|
|
@@ -6508,7 +6543,7 @@ class FigInputPalette extends HTMLElement {
|
|
|
6508
6543
|
break;
|
|
6509
6544
|
case "min":
|
|
6510
6545
|
case "max":
|
|
6511
|
-
case "
|
|
6546
|
+
case "fixed":
|
|
6512
6547
|
this.#render();
|
|
6513
6548
|
break;
|
|
6514
6549
|
case "open":
|
|
@@ -6607,6 +6642,14 @@ class FigInputPalette extends HTMLElement {
|
|
|
6607
6642
|
|
|
6608
6643
|
const inlineWrap = document.createElement("div");
|
|
6609
6644
|
inlineWrap.className = "palette-colors-inline";
|
|
6645
|
+
inlineWrap.addEventListener("click", () => {
|
|
6646
|
+
if (
|
|
6647
|
+
this.hasAttribute("disabled") &&
|
|
6648
|
+
this.getAttribute("disabled") !== "false"
|
|
6649
|
+
)
|
|
6650
|
+
return;
|
|
6651
|
+
this.open = true;
|
|
6652
|
+
});
|
|
6610
6653
|
|
|
6611
6654
|
const wrap = document.createElement("div");
|
|
6612
6655
|
wrap.className = "palette-colors";
|
|
@@ -6618,13 +6661,15 @@ class FigInputPalette extends HTMLElement {
|
|
|
6618
6661
|
inlineWrap.appendChild(wrap);
|
|
6619
6662
|
this.appendChild(inlineWrap);
|
|
6620
6663
|
|
|
6621
|
-
if (this.#
|
|
6664
|
+
if (!this.#isFixed) this.#createAddButton(disabled, this);
|
|
6622
6665
|
|
|
6623
6666
|
const expandedWrap = document.createElement("div");
|
|
6624
6667
|
expandedWrap.className = "palette-colors-expanded";
|
|
6625
6668
|
this.#colors.forEach((entry, i) => {
|
|
6626
6669
|
expandedWrap.appendChild(this.#createPicker(entry, i, disabled));
|
|
6627
|
-
|
|
6670
|
+
if (!this.#isFixed) {
|
|
6671
|
+
expandedWrap.appendChild(this.#createRemoveButton(i, disabled));
|
|
6672
|
+
}
|
|
6628
6673
|
});
|
|
6629
6674
|
this.appendChild(expandedWrap);
|
|
6630
6675
|
}
|
|
@@ -6639,14 +6684,13 @@ class FigInputPalette extends HTMLElement {
|
|
|
6639
6684
|
: entry.color;
|
|
6640
6685
|
const ic = document.createElement("fig-input-color");
|
|
6641
6686
|
ic.setAttribute("value", hexAlpha);
|
|
6642
|
-
ic.setAttribute("picker", "figma");
|
|
6643
|
-
ic.setAttribute("picker-anchor", "self");
|
|
6644
6687
|
if (inline) {
|
|
6645
6688
|
ic.setAttribute("text", "false");
|
|
6646
6689
|
ic.setAttribute("alpha", "true");
|
|
6690
|
+
ic.setAttribute("swatch-disabled", "");
|
|
6647
6691
|
} else {
|
|
6648
6692
|
ic.setAttribute("text", "true");
|
|
6649
|
-
ic.setAttribute("alpha", "false");
|
|
6693
|
+
ic.setAttribute("alpha", this.#isFixed ? "true" : "false");
|
|
6650
6694
|
ic.setAttribute("full", "");
|
|
6651
6695
|
}
|
|
6652
6696
|
if (disabled) ic.setAttribute("disabled", "");
|
|
@@ -6696,15 +6740,19 @@ class FigInputPalette extends HTMLElement {
|
|
|
6696
6740
|
btn.setAttribute("aria-label", "Remove color");
|
|
6697
6741
|
btn.className = "palette-remove-btn";
|
|
6698
6742
|
if (disabled || this.#colors.length <= this.#min) btn.setAttribute("disabled", "");
|
|
6699
|
-
btn.
|
|
6743
|
+
btn.appendChild(createFigIcon("minus"));
|
|
6700
6744
|
btn.addEventListener("click", () => {
|
|
6701
6745
|
if (this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false") return;
|
|
6702
6746
|
this.#removeColor(index);
|
|
6703
6747
|
});
|
|
6704
|
-
|
|
6748
|
+
const tooltip = document.createElement("fig-tooltip");
|
|
6749
|
+
tooltip.setAttribute("text", "Remove color");
|
|
6750
|
+
tooltip.appendChild(btn);
|
|
6751
|
+
return tooltip;
|
|
6705
6752
|
}
|
|
6706
6753
|
|
|
6707
6754
|
#removeColor(index) {
|
|
6755
|
+
if (this.#isFixed) return;
|
|
6708
6756
|
if (index < 0 || index >= this.#colors.length) return;
|
|
6709
6757
|
if (this.#colors.length <= this.#min) return;
|
|
6710
6758
|
this.#colors.splice(index, 1);
|
|
@@ -6722,7 +6770,7 @@ class FigInputPalette extends HTMLElement {
|
|
|
6722
6770
|
addBtn.setAttribute("aria-label", "Add color");
|
|
6723
6771
|
addBtn.className = "palette-add-btn";
|
|
6724
6772
|
if (disabled || atMax) addBtn.setAttribute("disabled", "");
|
|
6725
|
-
addBtn.
|
|
6773
|
+
addBtn.appendChild(createFigIcon("add"));
|
|
6726
6774
|
addBtn.addEventListener("click", () => {
|
|
6727
6775
|
if (
|
|
6728
6776
|
this.hasAttribute("disabled") &&
|
|
@@ -6730,6 +6778,7 @@ class FigInputPalette extends HTMLElement {
|
|
|
6730
6778
|
)
|
|
6731
6779
|
return;
|
|
6732
6780
|
if (this.#colors.length >= this.#max) return;
|
|
6781
|
+
this.open = true;
|
|
6733
6782
|
this.#addColor({ color: "#D9D9D9", alpha: 1 });
|
|
6734
6783
|
});
|
|
6735
6784
|
const tooltip = document.createElement("fig-tooltip");
|
|
@@ -6739,6 +6788,7 @@ class FigInputPalette extends HTMLElement {
|
|
|
6739
6788
|
}
|
|
6740
6789
|
|
|
6741
6790
|
#addColor(entry) {
|
|
6791
|
+
if (this.#isFixed) return;
|
|
6742
6792
|
this.#colors.push(entry);
|
|
6743
6793
|
const disabled =
|
|
6744
6794
|
this.hasAttribute("disabled") &&
|
|
@@ -6762,6 +6812,7 @@ class FigInputPalette extends HTMLElement {
|
|
|
6762
6812
|
const addBtn = this.querySelector(".palette-add-btn");
|
|
6763
6813
|
if (addBtn) addBtn.setAttribute("disabled", "");
|
|
6764
6814
|
}
|
|
6815
|
+
this.#syncRemoveButtons(disabled);
|
|
6765
6816
|
this.#emitChange();
|
|
6766
6817
|
}
|
|
6767
6818
|
|
|
@@ -6801,9 +6852,18 @@ class FigInputPalette extends HTMLElement {
|
|
|
6801
6852
|
});
|
|
6802
6853
|
const addBtn = this.querySelector(".palette-add-btn");
|
|
6803
6854
|
if (addBtn) {
|
|
6804
|
-
if (disabled) addBtn.setAttribute("disabled", "");
|
|
6855
|
+
if (disabled || this.#colors.length >= this.#max) addBtn.setAttribute("disabled", "");
|
|
6805
6856
|
else addBtn.removeAttribute("disabled");
|
|
6806
6857
|
}
|
|
6858
|
+
this.#syncRemoveButtons(disabled);
|
|
6859
|
+
}
|
|
6860
|
+
|
|
6861
|
+
#syncRemoveButtons(disabled = this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false") {
|
|
6862
|
+
const shouldDisable = disabled || this.#colors.length <= this.#min;
|
|
6863
|
+
this.querySelectorAll(".palette-remove-btn").forEach((btn) => {
|
|
6864
|
+
if (shouldDisable) btn.setAttribute("disabled", "");
|
|
6865
|
+
else btn.removeAttribute("disabled");
|
|
6866
|
+
});
|
|
6807
6867
|
}
|
|
6808
6868
|
|
|
6809
6869
|
#emitInput() {
|
|
@@ -7025,7 +7085,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
7025
7085
|
return this.#gradient.stops
|
|
7026
7086
|
.map(
|
|
7027
7087
|
(stop, i) =>
|
|
7028
|
-
`<fig-tooltip action="manual" text="${Math.round(stop.position)}%"><fig-handle drag drag-axes="x" drag-surface=".fig-input-gradient-track" type="color" color="${this.#stopColorCSS(stop)}" value="${stop.position}% 50%" hit-area="4" data-stop-index="${i}"${disabled ? " disabled" : ""}></fig-handle></fig-tooltip>`,
|
|
7088
|
+
`<fig-tooltip action="manual" text="${Math.round(stop.position)}%"><fig-handle drag drag-axes="x" drag-surface=".fig-input-gradient-track" type="color" color-tip color="${this.#stopColorCSS(stop)}" value="${stop.position}% 50%" hit-area="4" data-stop-index="${i}"${disabled ? " disabled" : ""}></fig-handle></fig-tooltip>`,
|
|
7029
7089
|
)
|
|
7030
7090
|
.join("");
|
|
7031
7091
|
}
|
|
@@ -7106,6 +7166,7 @@ class FigInputGradient extends HTMLElement {
|
|
|
7106
7166
|
const ghost = document.createElement("fig-handle");
|
|
7107
7167
|
ghost.classList.add("fig-input-gradient-ghost");
|
|
7108
7168
|
ghost.setAttribute("type", "color");
|
|
7169
|
+
ghost.setAttribute("color-tip", "");
|
|
7109
7170
|
ghost.setAttribute("control", "add");
|
|
7110
7171
|
ghost.style.position = "absolute";
|
|
7111
7172
|
ghost.style.top = "50%";
|
|
@@ -9024,10 +9085,9 @@ class FigMediaControls extends HTMLElement {
|
|
|
9024
9085
|
btn.setAttribute("size", "small");
|
|
9025
9086
|
btn.setAttribute("icon", "true");
|
|
9026
9087
|
btn.setAttribute("aria-label", "Play");
|
|
9027
|
-
const icon =
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
icon.style.setProperty("--size", "1.5rem");
|
|
9088
|
+
const icon = createFigIcon("play", {
|
|
9089
|
+
className: "fig-media-controls-play-icon",
|
|
9090
|
+
});
|
|
9031
9091
|
btn.append(icon);
|
|
9032
9092
|
tooltip.append(btn);
|
|
9033
9093
|
btn.addEventListener("click", (e) => {
|
|
@@ -9090,10 +9150,7 @@ class FigMediaControls extends HTMLElement {
|
|
|
9090
9150
|
this.#playTooltip?.setAttribute("text", playing ? "Pause" : "Play");
|
|
9091
9151
|
const icon = this.#playBtn.querySelector(".fig-media-controls-play-icon");
|
|
9092
9152
|
if (icon) {
|
|
9093
|
-
icon.
|
|
9094
|
-
"--icon",
|
|
9095
|
-
playing ? "var(--icon-pause)" : "var(--icon-play)",
|
|
9096
|
-
);
|
|
9153
|
+
icon.setAttribute("name", playing ? "pause" : "play");
|
|
9097
9154
|
}
|
|
9098
9155
|
}
|
|
9099
9156
|
|
|
@@ -9338,7 +9395,7 @@ class FigInputFile extends HTMLElement {
|
|
|
9338
9395
|
this.#clearBtn.setAttribute("icon", "true");
|
|
9339
9396
|
this.#clearBtn.className = "fig-input-file-clear";
|
|
9340
9397
|
if (disabled) this.#clearBtn.setAttribute("disabled", "");
|
|
9341
|
-
this.#clearBtn.
|
|
9398
|
+
this.#clearBtn.replaceChildren(createFigIcon("minus"));
|
|
9342
9399
|
this.#clearBtn.addEventListener("click", this.#onClear);
|
|
9343
9400
|
clearTooltip.appendChild(this.#clearBtn);
|
|
9344
9401
|
this.appendChild(clearTooltip);
|
|
@@ -11194,7 +11251,7 @@ class FigInputJoystick extends HTMLElement {
|
|
|
11194
11251
|
</div>
|
|
11195
11252
|
<fig-tooltip text="Reset">
|
|
11196
11253
|
<fig-button variant="ghost" icon="true" class="fig-joystick-reset" aria-label="Reset to default">
|
|
11197
|
-
<
|
|
11254
|
+
<fig-icon name="reset" size="small"></fig-icon>
|
|
11198
11255
|
</fig-button>
|
|
11199
11256
|
</fig-tooltip>
|
|
11200
11257
|
</div>
|
|
@@ -11709,8 +11766,10 @@ class FigGroup extends HTMLElement {
|
|
|
11709
11766
|
|
|
11710
11767
|
if (isCollapsible) {
|
|
11711
11768
|
if (!h3.querySelector(".fig-group-chevron")) {
|
|
11712
|
-
const chevron =
|
|
11713
|
-
|
|
11769
|
+
const chevron = createFigIcon("chevron", {
|
|
11770
|
+
size: "small",
|
|
11771
|
+
className: "fig-group-chevron",
|
|
11772
|
+
});
|
|
11714
11773
|
h3.prepend(chevron);
|
|
11715
11774
|
}
|
|
11716
11775
|
this.#chevron = h3.querySelector(".fig-group-chevron");
|
|
@@ -11752,7 +11811,71 @@ customElements.define("fig-footer", FigFooter);
|
|
|
11752
11811
|
class FigSpinner extends HTMLElement {}
|
|
11753
11812
|
customElements.define("fig-spinner", FigSpinner);
|
|
11754
11813
|
|
|
11755
|
-
|
|
11814
|
+
/** @type {Record<string, string>} */
|
|
11815
|
+
const FIG_ICON_TOKENS = {
|
|
11816
|
+
chevron: "--icon-16-chevron",
|
|
11817
|
+
checkmark: "--icon-16-checkmark",
|
|
11818
|
+
reset: "--icon-16-reset",
|
|
11819
|
+
"arrow-left": "--icon-16-arrow-left",
|
|
11820
|
+
steppers: "--icon-24-steppers",
|
|
11821
|
+
eyedropper: "--icon-24-eyedropper",
|
|
11822
|
+
add: "--icon-24-add",
|
|
11823
|
+
minus: "--icon-24-minus",
|
|
11824
|
+
back: "--icon-24-back",
|
|
11825
|
+
forward: "--icon-24-forward",
|
|
11826
|
+
close: "--icon-24-close",
|
|
11827
|
+
rotate: "--icon-24-rotate",
|
|
11828
|
+
swap: "--icon-24-swap",
|
|
11829
|
+
play: "--icon-24-play",
|
|
11830
|
+
pause: "--icon-24-pause",
|
|
11831
|
+
};
|
|
11832
|
+
|
|
11833
|
+
function figIconCssVar(name) {
|
|
11834
|
+
const token = name && FIG_ICON_TOKENS[name];
|
|
11835
|
+
return token ? `var(${token})` : "";
|
|
11836
|
+
}
|
|
11837
|
+
|
|
11838
|
+
/**
|
|
11839
|
+
* Masked icon using design-token SVGs from :root.
|
|
11840
|
+
* @attr {string} name - Icon name (chevron, add, close, …)
|
|
11841
|
+
* @attr {'small'|'medium'} size - Display size; medium (default) uses --spacer-4, small uses --spacer-3
|
|
11842
|
+
* @attr {string} color - Icon fill color (applied as background-color for the mask)
|
|
11843
|
+
*/
|
|
11844
|
+
class FigIcon extends HTMLElement {
|
|
11845
|
+
static get observedAttributes() {
|
|
11846
|
+
return ["name", "size", "color"];
|
|
11847
|
+
}
|
|
11848
|
+
|
|
11849
|
+
connectedCallback() {
|
|
11850
|
+
this.#sync();
|
|
11851
|
+
}
|
|
11852
|
+
|
|
11853
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
11854
|
+
if (oldValue !== newValue) this.#sync();
|
|
11855
|
+
}
|
|
11856
|
+
|
|
11857
|
+
#sync() {
|
|
11858
|
+
const iconName = this.getAttribute("name");
|
|
11859
|
+
const cssVar = figIconCssVar(iconName);
|
|
11860
|
+
if (cssVar) this.style.setProperty("--icon", cssVar);
|
|
11861
|
+
else this.style.removeProperty("--icon");
|
|
11862
|
+
|
|
11863
|
+
const size = this.getAttribute("size") || "medium";
|
|
11864
|
+
if (size === "small") {
|
|
11865
|
+
this.style.setProperty("--size", "var(--spacer-3)");
|
|
11866
|
+
} else {
|
|
11867
|
+
this.style.removeProperty("--size");
|
|
11868
|
+
}
|
|
11869
|
+
|
|
11870
|
+
const color = this.getAttribute("color");
|
|
11871
|
+
if (color) this.style.backgroundColor = color;
|
|
11872
|
+
else this.style.removeProperty("background-color");
|
|
11873
|
+
|
|
11874
|
+
if (!this.hasAttribute("aria-hidden")) {
|
|
11875
|
+
this.setAttribute("aria-hidden", "true");
|
|
11876
|
+
}
|
|
11877
|
+
}
|
|
11878
|
+
}
|
|
11756
11879
|
customElements.define("fig-icon", FigIcon);
|
|
11757
11880
|
|
|
11758
11881
|
class FigContent extends HTMLElement {}
|
|
@@ -12048,6 +12171,14 @@ class FigFillPicker extends HTMLElement {
|
|
|
12048
12171
|
});
|
|
12049
12172
|
}
|
|
12050
12173
|
|
|
12174
|
+
open() {
|
|
12175
|
+
this.#openDialog();
|
|
12176
|
+
}
|
|
12177
|
+
|
|
12178
|
+
close() {
|
|
12179
|
+
if (this.#dialog) this.#dialog.open = false;
|
|
12180
|
+
}
|
|
12181
|
+
|
|
12051
12182
|
#createDialog() {
|
|
12052
12183
|
// Collect slotted custom mode content before any DOM changes
|
|
12053
12184
|
this.#customSlots = {};
|
|
@@ -12135,7 +12266,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
12135
12266
|
${headerContent}
|
|
12136
12267
|
${gamutDropdown}
|
|
12137
12268
|
<fig-button icon variant="ghost" class="fig-fill-picker-close">
|
|
12138
|
-
<
|
|
12269
|
+
<fig-icon name="close"></fig-icon>
|
|
12139
12270
|
</fig-button>
|
|
12140
12271
|
</fig-header>
|
|
12141
12272
|
<fig-content>
|
|
@@ -12195,6 +12326,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
12195
12326
|
const onDialogClose = () => {
|
|
12196
12327
|
if (this.#chit) this.#chit.removeAttribute("selected");
|
|
12197
12328
|
this.#emitChange();
|
|
12329
|
+
this.dispatchEvent(new CustomEvent("close"));
|
|
12198
12330
|
};
|
|
12199
12331
|
this.#dialog.addEventListener("close", onDialogClose);
|
|
12200
12332
|
|
|
@@ -12305,7 +12437,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
12305
12437
|
></fig-handle>
|
|
12306
12438
|
</fig-preview>
|
|
12307
12439
|
<div class="fig-fill-picker-sliders">
|
|
12308
|
-
<fig-tooltip text="Sample color"><fig-button icon variant="ghost" class="fig-fill-picker-eyedropper"><
|
|
12440
|
+
<fig-tooltip text="Sample color"><fig-button icon variant="ghost" class="fig-fill-picker-eyedropper"><fig-icon name="eyedropper"></fig-icon></fig-button></fig-tooltip>
|
|
12309
12441
|
<fig-slider type="hue" variant="neue" min="0" max="360" value="${
|
|
12310
12442
|
this.#color.h
|
|
12311
12443
|
}"></fig-slider>
|
|
@@ -12821,7 +12953,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
12821
12953
|
</div>
|
|
12822
12954
|
<fig-tooltip text="Flip gradient">
|
|
12823
12955
|
<fig-button icon variant="ghost" class="fig-fill-picker-gradient-flip">
|
|
12824
|
-
<
|
|
12956
|
+
<fig-icon name="swap"></fig-icon>
|
|
12825
12957
|
</fig-button>
|
|
12826
12958
|
</fig-tooltip>
|
|
12827
12959
|
</fig-field>
|
|
@@ -12853,7 +12985,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
12853
12985
|
<fig-header class="fig-fill-picker-gradient-stops-header" borderless>
|
|
12854
12986
|
<span>Stops</span>
|
|
12855
12987
|
<fig-button icon variant="ghost" class="fig-fill-picker-gradient-add" title="Add stop">
|
|
12856
|
-
<
|
|
12988
|
+
<fig-icon name="add"></fig-icon>
|
|
12857
12989
|
</fig-button>
|
|
12858
12990
|
</fig-header>
|
|
12859
12991
|
<div class="fig-fill-picker-gradient-stops-list"></div>
|
|
@@ -13090,7 +13222,7 @@ class FigFillPicker extends HTMLElement {
|
|
|
13090
13222
|
<fig-button icon variant="ghost" class="fig-fill-picker-stop-remove" ${
|
|
13091
13223
|
this.#gradient.stops.length <= 2 ? "disabled" : ""
|
|
13092
13224
|
}>
|
|
13093
|
-
<
|
|
13225
|
+
<fig-icon name="minus"></fig-icon>
|
|
13094
13226
|
</fig-button>
|
|
13095
13227
|
</fig-field>
|
|
13096
13228
|
`,
|
|
@@ -14004,8 +14136,8 @@ class FigColorTip extends HTMLElement {
|
|
|
14004
14136
|
#render() {
|
|
14005
14137
|
const mode = this.#controlMode;
|
|
14006
14138
|
if (mode === "add" || mode === "remove") {
|
|
14007
|
-
const
|
|
14008
|
-
this.innerHTML = `<fig-button icon variant="ghost"><
|
|
14139
|
+
const iconName = mode === "add" ? "add" : "minus";
|
|
14140
|
+
this.innerHTML = `<fig-button icon variant="ghost"><fig-icon name="${iconName}"></fig-icon></fig-button>`;
|
|
14009
14141
|
this.#fillPicker = null;
|
|
14010
14142
|
this.#chit = null;
|
|
14011
14143
|
this.addEventListener("click", this.#handleControlClick);
|
|
@@ -14758,11 +14890,10 @@ class FigChooser extends HTMLElement {
|
|
|
14758
14890
|
if (this.#navStart) return;
|
|
14759
14891
|
|
|
14760
14892
|
const makeChevron = () => {
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
return icon;
|
|
14893
|
+
return createFigIcon("chevron", {
|
|
14894
|
+
size: "small",
|
|
14895
|
+
className: "fig-chooser-nav-chevron",
|
|
14896
|
+
});
|
|
14766
14897
|
};
|
|
14767
14898
|
|
|
14768
14899
|
this.#navStart = document.createElement("button");
|
|
@@ -14862,6 +14993,7 @@ class FigHandle extends HTMLElement {
|
|
|
14862
14993
|
"value",
|
|
14863
14994
|
"type",
|
|
14864
14995
|
"control",
|
|
14996
|
+
"color-tip",
|
|
14865
14997
|
"hit-area",
|
|
14866
14998
|
"hit-area-mode",
|
|
14867
14999
|
];
|
|
@@ -14871,6 +15003,7 @@ class FigHandle extends HTMLElement {
|
|
|
14871
15003
|
#boundPointerDown = null;
|
|
14872
15004
|
#applyingValue = false;
|
|
14873
15005
|
#colorTip = null;
|
|
15006
|
+
#directColorPicker = null;
|
|
14874
15007
|
#hitAreaEl = null;
|
|
14875
15008
|
|
|
14876
15009
|
get #controlMode() {
|
|
@@ -14881,6 +15014,14 @@ class FigHandle extends HTMLElement {
|
|
|
14881
15014
|
return this.#controlMode === "add" || this.#controlMode === "remove";
|
|
14882
15015
|
}
|
|
14883
15016
|
|
|
15017
|
+
get #usesColorTip() {
|
|
15018
|
+
return (
|
|
15019
|
+
this.#hasControlMode ||
|
|
15020
|
+
(this.hasAttribute("color-tip") &&
|
|
15021
|
+
this.getAttribute("color-tip") !== "false")
|
|
15022
|
+
);
|
|
15023
|
+
}
|
|
15024
|
+
|
|
14884
15025
|
get #isGhost() {
|
|
14885
15026
|
return this.classList.contains("fig-input-gradient-ghost");
|
|
14886
15027
|
}
|
|
@@ -15087,7 +15228,6 @@ class FigHandle extends HTMLElement {
|
|
|
15087
15228
|
}
|
|
15088
15229
|
|
|
15089
15230
|
connectedCallback() {
|
|
15090
|
-
if (!this.hasAttribute("type")) this.setAttribute("type", "canvas");
|
|
15091
15231
|
this.#syncDrag();
|
|
15092
15232
|
this.#syncHitArea();
|
|
15093
15233
|
this.addEventListener("click", this.#handleSelect);
|
|
@@ -15101,6 +15241,7 @@ class FigHandle extends HTMLElement {
|
|
|
15101
15241
|
disconnectedCallback() {
|
|
15102
15242
|
this.#teardownDrag();
|
|
15103
15243
|
this.#hideColorTip();
|
|
15244
|
+
this.#removeDirectColorPicker();
|
|
15104
15245
|
if (this.#hitAreaEl) {
|
|
15105
15246
|
this.#hitAreaEl.remove();
|
|
15106
15247
|
this.#hitAreaEl = null;
|
|
@@ -15113,7 +15254,7 @@ class FigHandle extends HTMLElement {
|
|
|
15113
15254
|
select() {
|
|
15114
15255
|
if (this.hasAttribute("disabled")) return;
|
|
15115
15256
|
this.setAttribute("selected", "");
|
|
15116
|
-
if (this.getAttribute("type") === "color" && !this.#isDragging)
|
|
15257
|
+
if (this.getAttribute("type") === "color" && !this.#isDragging && this.#usesColorTip)
|
|
15117
15258
|
this.#showColorTip();
|
|
15118
15259
|
}
|
|
15119
15260
|
|
|
@@ -15128,23 +15269,30 @@ class FigHandle extends HTMLElement {
|
|
|
15128
15269
|
this.#didDrag = false;
|
|
15129
15270
|
return;
|
|
15130
15271
|
}
|
|
15272
|
+
if (this.getAttribute("type") === "color" && !this.#usesColorTip) {
|
|
15273
|
+
this.#openDirectColorPicker();
|
|
15274
|
+
return;
|
|
15275
|
+
}
|
|
15131
15276
|
this.select();
|
|
15132
15277
|
};
|
|
15133
15278
|
|
|
15134
15279
|
#handleDeselect = (e) => {
|
|
15135
15280
|
if (this.#hasControlMode) return;
|
|
15136
15281
|
if (this.contains(e.target)) return;
|
|
15137
|
-
if (this.#colorTip && e.target.closest?.("dialog, [popover]")) return;
|
|
15282
|
+
if ((this.#colorTip || this.#directColorPicker) && e.target.closest?.("dialog, [popover]")) return;
|
|
15138
15283
|
this.deselect();
|
|
15139
15284
|
};
|
|
15140
15285
|
|
|
15141
15286
|
#handleKeyDown = (e) => {
|
|
15142
|
-
if (e.key !== "Enter") return;
|
|
15287
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
15143
15288
|
if (!this.hasAttribute("selected")) return;
|
|
15144
15289
|
if (this.getAttribute("type") !== "color") return;
|
|
15145
|
-
if (this.#colorTip) return;
|
|
15146
15290
|
e.preventDefault();
|
|
15147
|
-
this.#
|
|
15291
|
+
if (this.#usesColorTip) {
|
|
15292
|
+
if (!this.#colorTip) this.#showColorTip();
|
|
15293
|
+
} else {
|
|
15294
|
+
this.#openDirectColorPicker();
|
|
15295
|
+
}
|
|
15148
15296
|
};
|
|
15149
15297
|
|
|
15150
15298
|
attributeChangedCallback(name, _old, value) {
|
|
@@ -15157,6 +15305,7 @@ class FigHandle extends HTMLElement {
|
|
|
15157
15305
|
if (this.#colorTip && value) {
|
|
15158
15306
|
this.#colorTip.setAttribute("value", value);
|
|
15159
15307
|
}
|
|
15308
|
+
this.#syncDirectColorPickerValue();
|
|
15160
15309
|
}
|
|
15161
15310
|
if (name === "drag") this.#syncDrag();
|
|
15162
15311
|
if (name === "hit-area") this.#syncHitArea();
|
|
@@ -15165,12 +15314,20 @@ class FigHandle extends HTMLElement {
|
|
|
15165
15314
|
}
|
|
15166
15315
|
if (name === "control") {
|
|
15167
15316
|
if (this.#hasControlMode) {
|
|
15317
|
+
this.#removeDirectColorPicker();
|
|
15168
15318
|
this.#hideColorTip();
|
|
15169
15319
|
this.#showColorTip();
|
|
15170
15320
|
} else {
|
|
15171
15321
|
this.#hideColorTip();
|
|
15172
15322
|
}
|
|
15173
15323
|
}
|
|
15324
|
+
if (name === "color-tip") {
|
|
15325
|
+
if (this.#usesColorTip) {
|
|
15326
|
+
this.#removeDirectColorPicker();
|
|
15327
|
+
} else {
|
|
15328
|
+
this.#hideColorTip();
|
|
15329
|
+
}
|
|
15330
|
+
}
|
|
15174
15331
|
}
|
|
15175
15332
|
|
|
15176
15333
|
#syncDrag() {
|
|
@@ -15327,6 +15484,117 @@ class FigHandle extends HTMLElement {
|
|
|
15327
15484
|
this.#colorTip.style.display = "none";
|
|
15328
15485
|
}
|
|
15329
15486
|
|
|
15487
|
+
#normalizeColorForPicker(rawValue = this.getAttribute("color")) {
|
|
15488
|
+
const fallback = { color: "#D9D9D9", opacity: 100 };
|
|
15489
|
+
const value = String(rawValue || "").trim();
|
|
15490
|
+
if (!value) return fallback;
|
|
15491
|
+
|
|
15492
|
+
const normalizeHex = (hex) => {
|
|
15493
|
+
const raw = hex.replace("#", "").trim();
|
|
15494
|
+
if (raw.length === 3 || raw.length === 4) {
|
|
15495
|
+
const [r, g, b, a] = raw;
|
|
15496
|
+
return {
|
|
15497
|
+
color: `#${r}${r}${g}${g}${b}${b}`.toUpperCase(),
|
|
15498
|
+
opacity: a ? Math.round((parseInt(`${a}${a}`, 16) / 255) * 100) : 100,
|
|
15499
|
+
};
|
|
15500
|
+
}
|
|
15501
|
+
if (raw.length === 6 || raw.length === 8) {
|
|
15502
|
+
return {
|
|
15503
|
+
color: `#${raw.slice(0, 6)}`.toUpperCase(),
|
|
15504
|
+
opacity:
|
|
15505
|
+
raw.length === 8
|
|
15506
|
+
? Math.round((parseInt(raw.slice(6, 8), 16) / 255) * 100)
|
|
15507
|
+
: 100,
|
|
15508
|
+
};
|
|
15509
|
+
}
|
|
15510
|
+
return fallback;
|
|
15511
|
+
};
|
|
15512
|
+
|
|
15513
|
+
const rgbToHex = (r, g, b) => {
|
|
15514
|
+
const toHex = (v) =>
|
|
15515
|
+
Math.max(0, Math.min(255, Math.round(Number(v))))
|
|
15516
|
+
.toString(16)
|
|
15517
|
+
.padStart(2, "0");
|
|
15518
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
|
|
15519
|
+
};
|
|
15520
|
+
|
|
15521
|
+
if (value.startsWith("#")) return normalizeHex(value);
|
|
15522
|
+
|
|
15523
|
+
try {
|
|
15524
|
+
const { ctx } = figGetSharedCanvas(1, 1);
|
|
15525
|
+
ctx.fillStyle = "#000000";
|
|
15526
|
+
ctx.fillStyle = value;
|
|
15527
|
+
const resolved = ctx.fillStyle;
|
|
15528
|
+
if (resolved.startsWith("#")) return normalizeHex(resolved);
|
|
15529
|
+
const rgb = resolved.match(
|
|
15530
|
+
/rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?/i,
|
|
15531
|
+
);
|
|
15532
|
+
if (rgb) {
|
|
15533
|
+
return {
|
|
15534
|
+
color: rgbToHex(rgb[1], rgb[2], rgb[3]),
|
|
15535
|
+
opacity: rgb[4] !== undefined ? Math.round(parseFloat(rgb[4]) * 100) : 100,
|
|
15536
|
+
};
|
|
15537
|
+
}
|
|
15538
|
+
} catch {
|
|
15539
|
+
// Fall through to fallback.
|
|
15540
|
+
}
|
|
15541
|
+
|
|
15542
|
+
return fallback;
|
|
15543
|
+
}
|
|
15544
|
+
|
|
15545
|
+
#directColorPickerValue() {
|
|
15546
|
+
const { color, opacity } = this.#normalizeColorForPicker();
|
|
15547
|
+
return JSON.stringify(
|
|
15548
|
+
opacity < 100 ? { type: "solid", color, opacity } : { type: "solid", color },
|
|
15549
|
+
);
|
|
15550
|
+
}
|
|
15551
|
+
|
|
15552
|
+
#syncDirectColorPickerValue() {
|
|
15553
|
+
if (!this.#directColorPicker) return;
|
|
15554
|
+
this.#directColorPicker.setAttribute("value", this.#directColorPickerValue());
|
|
15555
|
+
}
|
|
15556
|
+
|
|
15557
|
+
#ensureDirectColorPicker() {
|
|
15558
|
+
if (this.#directColorPicker) return this.#directColorPicker;
|
|
15559
|
+
|
|
15560
|
+
const picker = document.createElement("fig-fill-picker");
|
|
15561
|
+
picker.setAttribute("mode", "solid");
|
|
15562
|
+
picker.setAttribute("alpha", "true");
|
|
15563
|
+
picker.setAttribute("value", this.#directColorPickerValue());
|
|
15564
|
+
picker.anchorElement = this;
|
|
15565
|
+
|
|
15566
|
+
const trigger = document.createElement("span");
|
|
15567
|
+
trigger.hidden = true;
|
|
15568
|
+
picker.appendChild(trigger);
|
|
15569
|
+
|
|
15570
|
+
picker.addEventListener("input", this.#handleDirectColorPickerInput);
|
|
15571
|
+
picker.addEventListener("change", this.#handleDirectColorPickerChange);
|
|
15572
|
+
picker.addEventListener("close", this.#handleDirectColorPickerClose);
|
|
15573
|
+
this.appendChild(picker);
|
|
15574
|
+
this.#directColorPicker = picker;
|
|
15575
|
+
return picker;
|
|
15576
|
+
}
|
|
15577
|
+
|
|
15578
|
+
#openDirectColorPicker() {
|
|
15579
|
+
if (this.hasAttribute("disabled")) return;
|
|
15580
|
+
this.#hideColorTip();
|
|
15581
|
+
const picker = this.#ensureDirectColorPicker();
|
|
15582
|
+
this.setAttribute("selected", "");
|
|
15583
|
+
this.#syncDirectColorPickerValue();
|
|
15584
|
+
picker.open();
|
|
15585
|
+
}
|
|
15586
|
+
|
|
15587
|
+
#removeDirectColorPicker() {
|
|
15588
|
+
if (!this.#directColorPicker) return;
|
|
15589
|
+
this.#directColorPicker.removeEventListener("input", this.#handleDirectColorPickerInput);
|
|
15590
|
+
this.#directColorPicker.removeEventListener("change", this.#handleDirectColorPickerChange);
|
|
15591
|
+
this.#directColorPicker.removeEventListener("close", this.#handleDirectColorPickerClose);
|
|
15592
|
+
this.#directColorPicker.close();
|
|
15593
|
+
this.#directColorPicker.remove();
|
|
15594
|
+
this.#directColorPicker = null;
|
|
15595
|
+
this.removeAttribute("selected");
|
|
15596
|
+
}
|
|
15597
|
+
|
|
15330
15598
|
#showColorTip() {
|
|
15331
15599
|
if (this.#colorTip) return;
|
|
15332
15600
|
const tip = document.createElement("fig-color-tip");
|
|
@@ -15361,6 +15629,47 @@ class FigHandle extends HTMLElement {
|
|
|
15361
15629
|
return `rgba(${r}, ${g}, ${b}, ${opacity / 100})`;
|
|
15362
15630
|
}
|
|
15363
15631
|
|
|
15632
|
+
#detailFromPicker(detail) {
|
|
15633
|
+
if (!detail?.color) return null;
|
|
15634
|
+
const opacity =
|
|
15635
|
+
detail.opacity !== undefined
|
|
15636
|
+
? detail.opacity
|
|
15637
|
+
: detail.alpha !== undefined
|
|
15638
|
+
? Math.round(detail.alpha * 100)
|
|
15639
|
+
: undefined;
|
|
15640
|
+
return { color: detail.color, opacity };
|
|
15641
|
+
}
|
|
15642
|
+
|
|
15643
|
+
#handleDirectColorPickerInput = (e) => {
|
|
15644
|
+
e.stopPropagation();
|
|
15645
|
+
const detail = this.#detailFromPicker(e.detail);
|
|
15646
|
+
if (!detail) return;
|
|
15647
|
+
this.setAttribute("color", this.#colorWithOpacity(detail.color, detail.opacity));
|
|
15648
|
+
this.dispatchEvent(
|
|
15649
|
+
new CustomEvent("input", {
|
|
15650
|
+
bubbles: true,
|
|
15651
|
+
detail,
|
|
15652
|
+
}),
|
|
15653
|
+
);
|
|
15654
|
+
};
|
|
15655
|
+
|
|
15656
|
+
#handleDirectColorPickerChange = (e) => {
|
|
15657
|
+
e.stopPropagation();
|
|
15658
|
+
const detail = this.#detailFromPicker(e.detail);
|
|
15659
|
+
if (!detail) return;
|
|
15660
|
+
this.setAttribute("color", this.#colorWithOpacity(detail.color, detail.opacity));
|
|
15661
|
+
this.dispatchEvent(
|
|
15662
|
+
new CustomEvent("change", {
|
|
15663
|
+
bubbles: true,
|
|
15664
|
+
detail,
|
|
15665
|
+
}),
|
|
15666
|
+
);
|
|
15667
|
+
};
|
|
15668
|
+
|
|
15669
|
+
#handleDirectColorPickerClose = () => {
|
|
15670
|
+
this.removeAttribute("selected");
|
|
15671
|
+
};
|
|
15672
|
+
|
|
15364
15673
|
#handleColorTipInput = (e) => {
|
|
15365
15674
|
e.stopPropagation();
|
|
15366
15675
|
if (e.detail?.color) {
|
|
@@ -15455,7 +15764,7 @@ class FigMenu extends HTMLElement {
|
|
|
15455
15764
|
#observer = null;
|
|
15456
15765
|
#boundTriggerClick;
|
|
15457
15766
|
#boundPopupClick;
|
|
15458
|
-
#
|
|
15767
|
+
#boundMenuKeydown;
|
|
15459
15768
|
#boundPopupClose;
|
|
15460
15769
|
#focusedIndex = -1;
|
|
15461
15770
|
|
|
@@ -15467,7 +15776,7 @@ class FigMenu extends HTMLElement {
|
|
|
15467
15776
|
super();
|
|
15468
15777
|
this.#boundTriggerClick = this.#handleTriggerClick.bind(this);
|
|
15469
15778
|
this.#boundPopupClick = this.#handlePopupClick.bind(this);
|
|
15470
|
-
this.#
|
|
15779
|
+
this.#boundMenuKeydown = this.#handleMenuKeydown.bind(this);
|
|
15471
15780
|
this.#boundPopupClose = this.#handlePopupClose.bind(this);
|
|
15472
15781
|
}
|
|
15473
15782
|
|
|
@@ -15588,6 +15897,7 @@ class FigMenu extends HTMLElement {
|
|
|
15588
15897
|
}
|
|
15589
15898
|
|
|
15590
15899
|
#setupListeners() {
|
|
15900
|
+
this.addEventListener("keydown", this.#boundMenuKeydown);
|
|
15591
15901
|
if (this.#trigger) {
|
|
15592
15902
|
this.#trigger.addEventListener("click", this.#boundTriggerClick);
|
|
15593
15903
|
this.#trigger.setAttribute("aria-haspopup", "menu");
|
|
@@ -15595,17 +15905,16 @@ class FigMenu extends HTMLElement {
|
|
|
15595
15905
|
}
|
|
15596
15906
|
if (this.#popup) {
|
|
15597
15907
|
this.#popup.addEventListener("click", this.#boundPopupClick);
|
|
15598
|
-
this.#popup.addEventListener("keydown", this.#boundPopupKeydown);
|
|
15599
15908
|
}
|
|
15600
15909
|
}
|
|
15601
15910
|
|
|
15602
15911
|
#teardownListeners() {
|
|
15912
|
+
this.removeEventListener("keydown", this.#boundMenuKeydown);
|
|
15603
15913
|
if (this.#trigger) {
|
|
15604
15914
|
this.#trigger.removeEventListener("click", this.#boundTriggerClick);
|
|
15605
15915
|
}
|
|
15606
15916
|
if (this.#popup) {
|
|
15607
15917
|
this.#popup.removeEventListener("click", this.#boundPopupClick);
|
|
15608
|
-
this.#popup.removeEventListener("keydown", this.#boundPopupKeydown);
|
|
15609
15918
|
}
|
|
15610
15919
|
}
|
|
15611
15920
|
|
|
@@ -15640,6 +15949,27 @@ class FigMenu extends HTMLElement {
|
|
|
15640
15949
|
return Array.from(this.#popup.querySelectorAll("fig-menu-item:not([disabled]):not([disabled='true'])"));
|
|
15641
15950
|
}
|
|
15642
15951
|
|
|
15952
|
+
#syncFocusedIndex() {
|
|
15953
|
+
const items = this.#getItems();
|
|
15954
|
+
if (!items.length) {
|
|
15955
|
+
this.#focusedIndex = -1;
|
|
15956
|
+
return;
|
|
15957
|
+
}
|
|
15958
|
+
const active = document.activeElement;
|
|
15959
|
+
const idx = items.findIndex(
|
|
15960
|
+
(item) => item === active || item.contains(active),
|
|
15961
|
+
);
|
|
15962
|
+
this.#focusedIndex = idx >= 0 ? idx : -1;
|
|
15963
|
+
}
|
|
15964
|
+
|
|
15965
|
+
#focusItemAt(index) {
|
|
15966
|
+
const items = this.#getItems();
|
|
15967
|
+
if (!items.length) return;
|
|
15968
|
+
const clamped = Math.max(0, Math.min(index, items.length - 1));
|
|
15969
|
+
this.#focusedIndex = clamped;
|
|
15970
|
+
items[clamped].focus();
|
|
15971
|
+
}
|
|
15972
|
+
|
|
15643
15973
|
#syncDisabled() {
|
|
15644
15974
|
if (!this.#trigger) return;
|
|
15645
15975
|
const disabled = this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
@@ -15653,7 +15983,12 @@ class FigMenu extends HTMLElement {
|
|
|
15653
15983
|
#handleTriggerClick(e) {
|
|
15654
15984
|
if (this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false") return;
|
|
15655
15985
|
e.stopPropagation();
|
|
15656
|
-
|
|
15986
|
+
const popupShowing = this.#popup?.matches?.(":open") ?? false;
|
|
15987
|
+
if (this.open && !popupShowing) {
|
|
15988
|
+
this.removeAttribute("open");
|
|
15989
|
+
}
|
|
15990
|
+
const effectiveOpen = this.open && popupShowing;
|
|
15991
|
+
if (effectiveOpen) {
|
|
15657
15992
|
this.open = false;
|
|
15658
15993
|
} else {
|
|
15659
15994
|
this.open = true;
|
|
@@ -15668,40 +16003,42 @@ class FigMenu extends HTMLElement {
|
|
|
15668
16003
|
this.#selectItem(item);
|
|
15669
16004
|
}
|
|
15670
16005
|
|
|
15671
|
-
#
|
|
16006
|
+
#handleMenuKeydown(e) {
|
|
16007
|
+
if (!this.open || !this.#popup?.matches?.(":open")) return;
|
|
16008
|
+
|
|
15672
16009
|
const items = this.#getItems();
|
|
15673
16010
|
if (!items.length) return;
|
|
15674
16011
|
|
|
15675
16012
|
switch (e.key) {
|
|
15676
16013
|
case "ArrowDown": {
|
|
15677
16014
|
e.preventDefault();
|
|
15678
|
-
this.#
|
|
15679
|
-
|
|
16015
|
+
this.#syncFocusedIndex();
|
|
16016
|
+
this.#focusItemAt(this.#focusedIndex + 1);
|
|
15680
16017
|
break;
|
|
15681
16018
|
}
|
|
15682
16019
|
case "ArrowUp": {
|
|
15683
16020
|
e.preventDefault();
|
|
15684
|
-
this.#
|
|
15685
|
-
|
|
16021
|
+
this.#syncFocusedIndex();
|
|
16022
|
+
this.#focusItemAt(this.#focusedIndex - 1);
|
|
15686
16023
|
break;
|
|
15687
16024
|
}
|
|
15688
16025
|
case "Home": {
|
|
15689
16026
|
e.preventDefault();
|
|
15690
|
-
this.#
|
|
15691
|
-
items[0]?.focus();
|
|
16027
|
+
this.#focusItemAt(0);
|
|
15692
16028
|
break;
|
|
15693
16029
|
}
|
|
15694
16030
|
case "End": {
|
|
15695
16031
|
e.preventDefault();
|
|
15696
|
-
this.#
|
|
15697
|
-
items[this.#focusedIndex]?.focus();
|
|
16032
|
+
this.#focusItemAt(items.length - 1);
|
|
15698
16033
|
break;
|
|
15699
16034
|
}
|
|
15700
16035
|
case "Enter":
|
|
15701
16036
|
case " ": {
|
|
15702
|
-
|
|
16037
|
+
this.#syncFocusedIndex();
|
|
15703
16038
|
const focused = items[this.#focusedIndex];
|
|
15704
|
-
if (focused)
|
|
16039
|
+
if (!focused) return;
|
|
16040
|
+
e.preventDefault();
|
|
16041
|
+
this.#selectItem(focused);
|
|
15705
16042
|
break;
|
|
15706
16043
|
}
|
|
15707
16044
|
}
|
|
@@ -15736,10 +16073,10 @@ class FigMenu extends HTMLElement {
|
|
|
15736
16073
|
if (this.#trigger) {
|
|
15737
16074
|
this.#trigger.setAttribute("aria-expanded", "true");
|
|
15738
16075
|
}
|
|
15739
|
-
this.#focusedIndex =
|
|
16076
|
+
this.#focusedIndex = -1;
|
|
15740
16077
|
requestAnimationFrame(() => {
|
|
15741
|
-
|
|
15742
|
-
|
|
16078
|
+
if (!this.#trigger?.matches?.(":focus-visible")) return;
|
|
16079
|
+
this.#focusItemAt(0);
|
|
15743
16080
|
});
|
|
15744
16081
|
}
|
|
15745
16082
|
|