@rogieking/figui3 8.9.11 → 8.9.13
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 +3 -3
- package/.cursor/skills/figui3/reference.md +4 -3
- package/README.md +24 -0
- package/components.css +96 -31
- package/dist/components.css +1 -1
- package/dist/fig-editor.css +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +162 -3
- package/fig-editor.css +2 -91
- package/fig.js +523 -121
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -204,6 +204,195 @@ function createFigOverflowButtons({
|
|
|
204
204
|
};
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/* Shared by light-DOM fig-select-options and shadow fig-menu .fig-menu-options.
|
|
208
|
+
Adopted on document and on fig-menu's shadow so hover/icon/overflow stay one source. */
|
|
209
|
+
const FIG_VERTICAL_OVERFLOW_CSS = `
|
|
210
|
+
fig-select-options,
|
|
211
|
+
.fig-menu-options {
|
|
212
|
+
--fig-vertical-overflow-size: var(--spacer-4);
|
|
213
|
+
box-sizing: border-box;
|
|
214
|
+
display: flex;
|
|
215
|
+
flex-direction: column;
|
|
216
|
+
gap: 0;
|
|
217
|
+
width: 100%;
|
|
218
|
+
max-width: 100%;
|
|
219
|
+
max-height: calc(100vh - 1rem);
|
|
220
|
+
padding: 0;
|
|
221
|
+
overflow: hidden auto;
|
|
222
|
+
scrollbar-width: none;
|
|
223
|
+
}
|
|
224
|
+
fig-select-options::-webkit-scrollbar,
|
|
225
|
+
.fig-menu-options::-webkit-scrollbar {
|
|
226
|
+
display: none;
|
|
227
|
+
}
|
|
228
|
+
fig-select-options > :not(.fig-overflow),
|
|
229
|
+
.fig-menu-options > :not(.fig-overflow) {
|
|
230
|
+
flex-shrink: 0;
|
|
231
|
+
}
|
|
232
|
+
fig-select-options > :nth-child(1 of :not(.fig-overflow)),
|
|
233
|
+
.fig-menu-options > :nth-child(1 of :not(.fig-overflow)) {
|
|
234
|
+
margin-top: var(--spacer-2);
|
|
235
|
+
}
|
|
236
|
+
fig-select-options > :not(.fig-overflow):last-child,
|
|
237
|
+
.fig-menu-options > :not(.fig-overflow):last-child,
|
|
238
|
+
fig-select-options > :not(.fig-overflow):has(+ .fig-overflow-end),
|
|
239
|
+
.fig-menu-options > :not(.fig-overflow):has(+ .fig-overflow-end) {
|
|
240
|
+
margin-bottom: var(--spacer-2);
|
|
241
|
+
}
|
|
242
|
+
fig-select-options > .fig-overflow,
|
|
243
|
+
.fig-menu-options > .fig-overflow {
|
|
244
|
+
all: unset;
|
|
245
|
+
box-sizing: border-box;
|
|
246
|
+
position: sticky;
|
|
247
|
+
left: 0;
|
|
248
|
+
z-index: 3;
|
|
249
|
+
flex: 0 0 var(--fig-vertical-overflow-size);
|
|
250
|
+
display: flex;
|
|
251
|
+
align-items: center;
|
|
252
|
+
justify-content: center;
|
|
253
|
+
width: 100%;
|
|
254
|
+
height: var(--fig-vertical-overflow-size);
|
|
255
|
+
margin: 0;
|
|
256
|
+
cursor: pointer;
|
|
257
|
+
opacity: 0;
|
|
258
|
+
pointer-events: none;
|
|
259
|
+
color: var(--figma-color-text-menu);
|
|
260
|
+
background: var(--figma-color-bg-menu) !important;
|
|
261
|
+
}
|
|
262
|
+
fig-select-options > .fig-overflow::before,
|
|
263
|
+
.fig-menu-options > .fig-overflow::before {
|
|
264
|
+
content: "";
|
|
265
|
+
position: absolute;
|
|
266
|
+
inset: 0;
|
|
267
|
+
z-index: -1;
|
|
268
|
+
border-radius: 0;
|
|
269
|
+
background: var(--figma-color-bg-menu) !important;
|
|
270
|
+
}
|
|
271
|
+
fig-select-options > .fig-overflow:hover,
|
|
272
|
+
.fig-menu-options > .fig-overflow:hover {
|
|
273
|
+
color: var(--figma-color-text-menu);
|
|
274
|
+
}
|
|
275
|
+
fig-select-options > .fig-overflow:hover::before,
|
|
276
|
+
.fig-menu-options > .fig-overflow:hover::before {
|
|
277
|
+
background: light-dark(
|
|
278
|
+
rgba(0, 0, 0, 0.06),
|
|
279
|
+
rgba(255, 255, 255, 0.08)
|
|
280
|
+
) !important;
|
|
281
|
+
}
|
|
282
|
+
fig-select-options > .fig-overflow .fig-overflow-chevron,
|
|
283
|
+
.fig-menu-options > .fig-overflow .fig-overflow-chevron {
|
|
284
|
+
position: relative;
|
|
285
|
+
}
|
|
286
|
+
fig-select-options > .fig-overflow:active .fig-overflow-chevron,
|
|
287
|
+
.fig-menu-options > .fig-overflow:active .fig-overflow-chevron {
|
|
288
|
+
transform: translateY(1px);
|
|
289
|
+
}
|
|
290
|
+
fig-select-options.overflow-start > .fig-overflow-start,
|
|
291
|
+
.fig-menu-options.overflow-start > .fig-overflow-start,
|
|
292
|
+
fig-select-options.overflow-end > .fig-overflow-end,
|
|
293
|
+
.fig-menu-options.overflow-end > .fig-overflow-end {
|
|
294
|
+
opacity: 1;
|
|
295
|
+
pointer-events: auto;
|
|
296
|
+
}
|
|
297
|
+
fig-select-options > .fig-overflow-start,
|
|
298
|
+
.fig-menu-options > .fig-overflow-start {
|
|
299
|
+
order: -1;
|
|
300
|
+
top: 0;
|
|
301
|
+
margin-block-end: calc(-1 * var(--fig-vertical-overflow-size));
|
|
302
|
+
}
|
|
303
|
+
fig-select-options > .fig-overflow-start::before,
|
|
304
|
+
.fig-menu-options > .fig-overflow-start::before {
|
|
305
|
+
border-radius: var(--radius-large) var(--radius-large) 0 0;
|
|
306
|
+
}
|
|
307
|
+
fig-select-options > .fig-overflow-start .fig-overflow-chevron,
|
|
308
|
+
.fig-menu-options > .fig-overflow-start .fig-overflow-chevron {
|
|
309
|
+
rotate: 180deg;
|
|
310
|
+
}
|
|
311
|
+
fig-select-options > .fig-overflow-end,
|
|
312
|
+
.fig-menu-options > .fig-overflow-end {
|
|
313
|
+
order: 1;
|
|
314
|
+
bottom: 0;
|
|
315
|
+
margin-block-start: calc(-1 * var(--fig-vertical-overflow-size));
|
|
316
|
+
}
|
|
317
|
+
fig-select-options > .fig-overflow-end::before,
|
|
318
|
+
.fig-menu-options > .fig-overflow-end::before {
|
|
319
|
+
border-radius: 0 0 var(--radius-large) var(--radius-large);
|
|
320
|
+
}
|
|
321
|
+
`;
|
|
322
|
+
|
|
323
|
+
const FIG_SHADOW_ICON_CSS = `
|
|
324
|
+
fig-icon {
|
|
325
|
+
contain: strict;
|
|
326
|
+
--size: var(--spacer-4);
|
|
327
|
+
display: inline-flex;
|
|
328
|
+
width: var(--size);
|
|
329
|
+
height: var(--size);
|
|
330
|
+
flex-shrink: 0;
|
|
331
|
+
background: currentColor;
|
|
332
|
+
mask-image: var(--icon);
|
|
333
|
+
mask-size: contain;
|
|
334
|
+
mask-repeat: no-repeat;
|
|
335
|
+
mask-position: center;
|
|
336
|
+
-webkit-mask-image: var(--icon);
|
|
337
|
+
-webkit-mask-size: contain;
|
|
338
|
+
-webkit-mask-repeat: no-repeat;
|
|
339
|
+
-webkit-mask-position: center;
|
|
340
|
+
}
|
|
341
|
+
fig-icon[size="small"] {
|
|
342
|
+
--size: var(--spacer-3);
|
|
343
|
+
}
|
|
344
|
+
`;
|
|
345
|
+
|
|
346
|
+
const figVerticalOverflowSheet =
|
|
347
|
+
typeof CSSStyleSheet === "function" &&
|
|
348
|
+
typeof CSSStyleSheet.prototype.replaceSync === "function"
|
|
349
|
+
? new CSSStyleSheet()
|
|
350
|
+
: null;
|
|
351
|
+
figVerticalOverflowSheet?.replaceSync(FIG_VERTICAL_OVERFLOW_CSS);
|
|
352
|
+
|
|
353
|
+
const figShadowIconSheet =
|
|
354
|
+
typeof CSSStyleSheet === "function" &&
|
|
355
|
+
typeof CSSStyleSheet.prototype.replaceSync === "function"
|
|
356
|
+
? new CSSStyleSheet()
|
|
357
|
+
: null;
|
|
358
|
+
figShadowIconSheet?.replaceSync(FIG_SHADOW_ICON_CSS);
|
|
359
|
+
|
|
360
|
+
function figAdoptConstructedSheet(root, sheet, cssText, marker) {
|
|
361
|
+
if (!root || !cssText) return;
|
|
362
|
+
if (sheet && "adoptedStyleSheets" in root) {
|
|
363
|
+
if ([...root.adoptedStyleSheets].includes(sheet)) return;
|
|
364
|
+
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const host = root === document ? document.documentElement : root;
|
|
368
|
+
if (host.querySelector?.(`style[data-fig-${marker}]`)) return;
|
|
369
|
+
const style = document.createElement("style");
|
|
370
|
+
style.setAttribute(`data-fig-${marker}`, "");
|
|
371
|
+
style.textContent = cssText;
|
|
372
|
+
host.prepend(style);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function figAdoptVerticalOverflowStyles(root, options = {}) {
|
|
376
|
+
figAdoptConstructedSheet(
|
|
377
|
+
root,
|
|
378
|
+
figVerticalOverflowSheet,
|
|
379
|
+
FIG_VERTICAL_OVERFLOW_CSS,
|
|
380
|
+
"vertical-overflow",
|
|
381
|
+
);
|
|
382
|
+
if (options.icons) {
|
|
383
|
+
figAdoptConstructedSheet(
|
|
384
|
+
root,
|
|
385
|
+
figShadowIconSheet,
|
|
386
|
+
FIG_SHADOW_ICON_CSS,
|
|
387
|
+
"shadow-icon",
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (typeof document !== "undefined") {
|
|
393
|
+
figAdoptVerticalOverflowStyles(document);
|
|
394
|
+
}
|
|
395
|
+
|
|
207
396
|
function figSyncOverflowState(host, scrollEl, axis = "x", threshold = 2) {
|
|
208
397
|
if (!host || !scrollEl) return false;
|
|
209
398
|
const isHorizontal = axis === "x";
|
|
@@ -1664,6 +1853,27 @@ class FigTruncate extends HTMLElement {
|
|
|
1664
1853
|
}
|
|
1665
1854
|
figDefineElement("fig-truncate", FigTruncate);
|
|
1666
1855
|
|
|
1856
|
+
function figEnsureAutoDialogHeader(host) {
|
|
1857
|
+
if (host.querySelector("fig-header[dialog-header]")) return;
|
|
1858
|
+
const header = document.createElement("fig-header");
|
|
1859
|
+
header.setAttribute("dialog-header", "");
|
|
1860
|
+
header.setAttribute("data-auto", "");
|
|
1861
|
+
const h3 = document.createElement("h3");
|
|
1862
|
+
h3.textContent = host.getAttribute("title") || "Dialog";
|
|
1863
|
+
const tooltip = document.createElement("fig-tooltip");
|
|
1864
|
+
tooltip.setAttribute("text", "Close");
|
|
1865
|
+
const btn = document.createElement("fig-button");
|
|
1866
|
+
btn.setAttribute("variant", "ghost");
|
|
1867
|
+
btn.setAttribute("icon", "");
|
|
1868
|
+
btn.setAttribute("aria-label", "Close dialog");
|
|
1869
|
+
btn.setAttribute("close-dialog", "");
|
|
1870
|
+
btn.appendChild(createFigIcon("close"));
|
|
1871
|
+
tooltip.appendChild(btn);
|
|
1872
|
+
header.appendChild(h3);
|
|
1873
|
+
header.appendChild(tooltip);
|
|
1874
|
+
host.prepend(header);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1667
1877
|
/* Dialog */
|
|
1668
1878
|
/**
|
|
1669
1879
|
* A custom dialog element for modal and non-modal dialogs.
|
|
@@ -1973,24 +2183,7 @@ class FigDialog extends HTMLDialogElement {
|
|
|
1973
2183
|
}
|
|
1974
2184
|
|
|
1975
2185
|
_ensureHeader() {
|
|
1976
|
-
|
|
1977
|
-
const header = document.createElement("fig-header");
|
|
1978
|
-
header.setAttribute("dialog-header", "");
|
|
1979
|
-
header.setAttribute("data-auto", "");
|
|
1980
|
-
const h3 = document.createElement("h3");
|
|
1981
|
-
h3.textContent = this.getAttribute("title") || "Dialog";
|
|
1982
|
-
const tooltip = document.createElement("fig-tooltip");
|
|
1983
|
-
tooltip.setAttribute("text", "Close");
|
|
1984
|
-
const btn = document.createElement("fig-button");
|
|
1985
|
-
btn.setAttribute("variant", "ghost");
|
|
1986
|
-
btn.setAttribute("icon", "");
|
|
1987
|
-
btn.setAttribute("aria-label", "Close dialog");
|
|
1988
|
-
btn.setAttribute("close-dialog", "");
|
|
1989
|
-
btn.appendChild(createFigIcon("close"));
|
|
1990
|
-
tooltip.appendChild(btn);
|
|
1991
|
-
header.appendChild(h3);
|
|
1992
|
-
header.appendChild(tooltip);
|
|
1993
|
-
this.prepend(header);
|
|
2186
|
+
figEnsureAutoDialogHeader(this);
|
|
1994
2187
|
}
|
|
1995
2188
|
|
|
1996
2189
|
_addCloseListeners() {
|
|
@@ -2588,6 +2781,7 @@ figDefineCustomizedBuiltIn("fig-toast", FigToast, { extends: "dialog" });
|
|
|
2588
2781
|
* @attr {string} offset - Horizontal and vertical offset as "x y" (default: "0 0").
|
|
2589
2782
|
* @attr {string} variant - Visual variant. Use variant="popover" to show an anchor beak.
|
|
2590
2783
|
* @attr {string} theme - Visual theme: "light", "dark", or "menu".
|
|
2784
|
+
* @attr {string} title - Title text for the auto-generated header (same as fig-dialog).
|
|
2591
2785
|
* @attr {boolean|string} open - Open when present and not "false".
|
|
2592
2786
|
*/
|
|
2593
2787
|
class FigPopup extends HTMLDialogElement {
|
|
@@ -2635,6 +2829,9 @@ class FigPopup extends HTMLDialogElement {
|
|
|
2635
2829
|
this._boundPointerUp = this.handlePointerUp.bind(this);
|
|
2636
2830
|
this._boundDocumentKeydown = this.handleDocumentKeydown.bind(this);
|
|
2637
2831
|
this._boundNativeClose = this.handleNativeClose.bind(this);
|
|
2832
|
+
this._boundAutoClose = () => {
|
|
2833
|
+
this.open = false;
|
|
2834
|
+
};
|
|
2638
2835
|
}
|
|
2639
2836
|
|
|
2640
2837
|
ensureInitialized() {
|
|
@@ -2694,6 +2891,11 @@ class FigPopup extends HTMLDialogElement {
|
|
|
2694
2891
|
if (typeof this._boundNativeClose !== "function") {
|
|
2695
2892
|
this._boundNativeClose = this.handleNativeClose.bind(this);
|
|
2696
2893
|
}
|
|
2894
|
+
if (typeof this._boundAutoClose !== "function") {
|
|
2895
|
+
this._boundAutoClose = () => {
|
|
2896
|
+
this.open = false;
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2697
2899
|
}
|
|
2698
2900
|
|
|
2699
2901
|
static get observedAttributes() {
|
|
@@ -2708,6 +2910,7 @@ class FigPopup extends HTMLDialogElement {
|
|
|
2708
2910
|
"handle",
|
|
2709
2911
|
"autoresize",
|
|
2710
2912
|
"viewport-margin",
|
|
2913
|
+
"title",
|
|
2711
2914
|
];
|
|
2712
2915
|
}
|
|
2713
2916
|
|
|
@@ -2778,6 +2981,8 @@ class FigPopup extends HTMLDialogElement {
|
|
|
2778
2981
|
this.drag =
|
|
2779
2982
|
this.hasAttribute("drag") && this.getAttribute("drag") !== "false";
|
|
2780
2983
|
|
|
2984
|
+
this._ensureHeader();
|
|
2985
|
+
|
|
2781
2986
|
this.removeEventListener("close", this._boundNativeClose);
|
|
2782
2987
|
this.addEventListener("close", this._boundNativeClose);
|
|
2783
2988
|
|
|
@@ -2836,6 +3041,13 @@ class FigPopup extends HTMLDialogElement {
|
|
|
2836
3041
|
return;
|
|
2837
3042
|
}
|
|
2838
3043
|
|
|
3044
|
+
if (name === "title") {
|
|
3045
|
+
this._ensureHeader();
|
|
3046
|
+
const autoHeader = this.querySelector("fig-header[data-auto] h3");
|
|
3047
|
+
if (autoHeader) autoHeader.textContent = newValue || "Dialog";
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
2839
3051
|
if (this.open) {
|
|
2840
3052
|
this.queueReposition();
|
|
2841
3053
|
this.setupObservers();
|
|
@@ -3110,6 +3322,20 @@ class FigPopup extends HTMLDialogElement {
|
|
|
3110
3322
|
this._lastAnchorRect = null;
|
|
3111
3323
|
}
|
|
3112
3324
|
|
|
3325
|
+
_ensureHeader() {
|
|
3326
|
+
if (
|
|
3327
|
+
!this.hasAttribute("title") ||
|
|
3328
|
+
this.getAttribute("variant") === "tooltip"
|
|
3329
|
+
) {
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
figEnsureAutoDialogHeader(this);
|
|
3333
|
+
this.querySelectorAll("fig-button[close-dialog]").forEach((button) => {
|
|
3334
|
+
button.removeEventListener("click", this._boundAutoClose);
|
|
3335
|
+
button.addEventListener("click", this._boundAutoClose);
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3113
3339
|
handleOutsidePointerDown(event) {
|
|
3114
3340
|
if (
|
|
3115
3341
|
!this.open ||
|
|
@@ -18729,10 +18955,15 @@ figDefineElement("fig-menu-item", FigMenuItem);
|
|
|
18729
18955
|
* @attr {string} label - Optional group label shown in secondary text under the line.
|
|
18730
18956
|
* @attr {string} direction - Orientation: "vertical" for a tall rule; default is horizontal.
|
|
18731
18957
|
* @attr {boolean} borderless - Hides the separator line when present or "true".
|
|
18958
|
+
* @attr {boolean} sticky - Stick to the top of the nearest scrollport while scrolling.
|
|
18732
18959
|
*/
|
|
18733
18960
|
class FigSeparator extends HTMLElement {
|
|
18961
|
+
#scrollRoot = null;
|
|
18962
|
+
#resizeObserver = null;
|
|
18963
|
+
#boundSyncStuck = this.#syncStuck.bind(this);
|
|
18964
|
+
|
|
18734
18965
|
static get observedAttributes() {
|
|
18735
|
-
return ["label", "direction"];
|
|
18966
|
+
return ["label", "direction", "sticky"];
|
|
18736
18967
|
}
|
|
18737
18968
|
|
|
18738
18969
|
get label() {
|
|
@@ -18746,11 +18977,17 @@ class FigSeparator extends HTMLElement {
|
|
|
18746
18977
|
|
|
18747
18978
|
connectedCallback() {
|
|
18748
18979
|
this.#sync();
|
|
18980
|
+
this.#bindStuck();
|
|
18981
|
+
}
|
|
18982
|
+
|
|
18983
|
+
disconnectedCallback() {
|
|
18984
|
+
this.#unbindStuck();
|
|
18749
18985
|
}
|
|
18750
18986
|
|
|
18751
18987
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
18752
18988
|
if (oldValue === newValue) return;
|
|
18753
18989
|
this.#sync();
|
|
18990
|
+
if (name === "sticky") this.#bindStuck();
|
|
18754
18991
|
}
|
|
18755
18992
|
|
|
18756
18993
|
#sync() {
|
|
@@ -18767,36 +19004,106 @@ class FigSeparator extends HTMLElement {
|
|
|
18767
19004
|
if (label) this.setAttribute("aria-label", label);
|
|
18768
19005
|
else this.removeAttribute("aria-label");
|
|
18769
19006
|
}
|
|
19007
|
+
|
|
19008
|
+
#isSticky() {
|
|
19009
|
+
return this.hasAttribute("sticky") && this.getAttribute("sticky") !== "false";
|
|
19010
|
+
}
|
|
19011
|
+
|
|
19012
|
+
#bindStuck() {
|
|
19013
|
+
this.#unbindStuck();
|
|
19014
|
+
if (!this.isConnected || !this.#isSticky()) {
|
|
19015
|
+
this.removeAttribute("stuck");
|
|
19016
|
+
return;
|
|
19017
|
+
}
|
|
19018
|
+
const root = figNearestScrollRoot(this);
|
|
19019
|
+
this.#scrollRoot = root;
|
|
19020
|
+
root?.addEventListener("scroll", this.#boundSyncStuck, { passive: true });
|
|
19021
|
+
this.#resizeObserver = new ResizeObserver(this.#boundSyncStuck);
|
|
19022
|
+
this.#resizeObserver.observe(this);
|
|
19023
|
+
if (root instanceof Element) this.#resizeObserver.observe(root);
|
|
19024
|
+
requestAnimationFrame(this.#boundSyncStuck);
|
|
19025
|
+
}
|
|
19026
|
+
|
|
19027
|
+
#unbindStuck() {
|
|
19028
|
+
this.#scrollRoot?.removeEventListener?.("scroll", this.#boundSyncStuck);
|
|
19029
|
+
this.#scrollRoot = null;
|
|
19030
|
+
this.#resizeObserver?.disconnect();
|
|
19031
|
+
this.#resizeObserver = null;
|
|
19032
|
+
}
|
|
19033
|
+
|
|
19034
|
+
#syncStuck() {
|
|
19035
|
+
if (!this.#isSticky() || !this.#scrollRoot) {
|
|
19036
|
+
this.removeAttribute("stuck");
|
|
19037
|
+
return;
|
|
19038
|
+
}
|
|
19039
|
+
const stickyTop = parseFloat(getComputedStyle(this).top) || 0;
|
|
19040
|
+
const root = this.#scrollRoot;
|
|
19041
|
+
const rootRect =
|
|
19042
|
+
root === document.scrollingElement || root === document.documentElement
|
|
19043
|
+
? { top: 0 }
|
|
19044
|
+
: root.getBoundingClientRect();
|
|
19045
|
+
const paddingTop =
|
|
19046
|
+
root instanceof Element ? parseFloat(getComputedStyle(root).paddingTop) || 0 : 0;
|
|
19047
|
+
const line = rootRect.top + paddingTop + stickyTop;
|
|
19048
|
+
this.toggleAttribute("stuck", this.getBoundingClientRect().top <= line + 1);
|
|
19049
|
+
}
|
|
18770
19050
|
}
|
|
18771
19051
|
figDefineElement("fig-separator", FigSeparator);
|
|
18772
19052
|
|
|
19053
|
+
function figNearestScrollRoot(el) {
|
|
19054
|
+
let node = el.parentElement;
|
|
19055
|
+
while (node) {
|
|
19056
|
+
const style = getComputedStyle(node);
|
|
19057
|
+
const y = style.overflowY || style.overflow;
|
|
19058
|
+
if (y && y !== "visible" && y !== "clip") return node;
|
|
19059
|
+
node = node.parentElement;
|
|
19060
|
+
}
|
|
19061
|
+
return document.scrollingElement;
|
|
19062
|
+
}
|
|
19063
|
+
|
|
18773
19064
|
// Backwards-compatible alias. Custom element constructors cannot be registered
|
|
18774
19065
|
// under multiple tag names, so the legacy tag uses an otherwise empty subclass.
|
|
18775
19066
|
class FigMenuSeparator extends FigSeparator {}
|
|
18776
19067
|
figDefineElement("fig-menu-separator", FigMenuSeparator);
|
|
18777
19068
|
|
|
18778
|
-
const FIG_MENU_CHILD_SELECTOR =
|
|
18779
|
-
":scope > fig-menu-item, :scope > fig-separator, :scope > fig-menu-separator";
|
|
18780
19069
|
const FIG_MENU_CHILD_TAGS = new Set([
|
|
18781
19070
|
"FIG-MENU-ITEM",
|
|
18782
19071
|
"FIG-SEPARATOR",
|
|
18783
19072
|
"FIG-MENU-SEPARATOR",
|
|
18784
19073
|
]);
|
|
19074
|
+
const FIG_MENU_TRIGGER_SLOT = "trigger";
|
|
19075
|
+
|
|
19076
|
+
function figIsMenuChild(node) {
|
|
19077
|
+
return node?.nodeType === 1 && FIG_MENU_CHILD_TAGS.has(node.tagName);
|
|
19078
|
+
}
|
|
19079
|
+
|
|
19080
|
+
function figMenuTriggerHost(menu, trigger) {
|
|
19081
|
+
if (!menu || !trigger) return null;
|
|
19082
|
+
let host = trigger;
|
|
19083
|
+
while (host.parentElement && host.parentElement !== menu) {
|
|
19084
|
+
host = host.parentElement;
|
|
19085
|
+
}
|
|
19086
|
+
return host.parentElement === menu ? host : null;
|
|
19087
|
+
}
|
|
18785
19088
|
|
|
18786
19089
|
class FigMenu extends HTMLElement {
|
|
18787
19090
|
#popup = null;
|
|
18788
19091
|
#panel = null;
|
|
19092
|
+
#itemSlot = null;
|
|
18789
19093
|
#trigger = null;
|
|
19094
|
+
#triggerHost = null;
|
|
18790
19095
|
#virtualAnchor = null;
|
|
18791
19096
|
#observer = null;
|
|
18792
19097
|
#resizeObserver = null;
|
|
18793
19098
|
#navStart = null;
|
|
18794
19099
|
#navEnd = null;
|
|
19100
|
+
#initialized = false;
|
|
18795
19101
|
#boundTriggerClick;
|
|
18796
19102
|
#boundTriggerContextMenu;
|
|
18797
|
-
#
|
|
19103
|
+
#boundHostClick;
|
|
18798
19104
|
#boundMenuKeydown;
|
|
18799
19105
|
#boundPopupClose;
|
|
19106
|
+
#boundSlotChange;
|
|
18800
19107
|
#boundSyncOverflow = this.#syncOverflow.bind(this);
|
|
18801
19108
|
#focusedIndex = -1;
|
|
18802
19109
|
|
|
@@ -18808,9 +19115,11 @@ class FigMenu extends HTMLElement {
|
|
|
18808
19115
|
super();
|
|
18809
19116
|
this.#boundTriggerClick = this.#handleTriggerClick.bind(this);
|
|
18810
19117
|
this.#boundTriggerContextMenu = this.#handleTriggerContextMenu.bind(this);
|
|
18811
|
-
this.#
|
|
19118
|
+
this.#boundHostClick = this.#handleHostClick.bind(this);
|
|
18812
19119
|
this.#boundMenuKeydown = this.#handleMenuKeydown.bind(this);
|
|
18813
19120
|
this.#boundPopupClose = this.#handlePopupClose.bind(this);
|
|
19121
|
+
this.#boundSlotChange = this.#syncOverflow.bind(this);
|
|
19122
|
+
this.#initialize();
|
|
18814
19123
|
}
|
|
18815
19124
|
|
|
18816
19125
|
get value() {
|
|
@@ -18836,8 +19145,6 @@ class FigMenu extends HTMLElement {
|
|
|
18836
19145
|
|
|
18837
19146
|
connectedCallback() {
|
|
18838
19147
|
this.#detectTrigger();
|
|
18839
|
-
this.#createPopup();
|
|
18840
|
-
this.#moveItemsToPopup();
|
|
18841
19148
|
this.#setupListeners();
|
|
18842
19149
|
this.#setupOverflow();
|
|
18843
19150
|
this.#setupObserver();
|
|
@@ -18856,16 +19163,6 @@ class FigMenu extends HTMLElement {
|
|
|
18856
19163
|
this.#observer.disconnect();
|
|
18857
19164
|
this.#observer = null;
|
|
18858
19165
|
}
|
|
18859
|
-
if (this.#popup) {
|
|
18860
|
-
this.#popup.removeEventListener("close", this.#boundPopupClose);
|
|
18861
|
-
const items = Array.from(
|
|
18862
|
-
this.#panel?.querySelectorAll(FIG_MENU_CHILD_SELECTOR) ?? [],
|
|
18863
|
-
);
|
|
18864
|
-
for (const item of items) this.insertBefore(item, this.#popup);
|
|
18865
|
-
this.#popup.remove();
|
|
18866
|
-
this.#popup = null;
|
|
18867
|
-
this.#panel = null;
|
|
18868
|
-
}
|
|
18869
19166
|
}
|
|
18870
19167
|
|
|
18871
19168
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -18898,112 +19195,164 @@ class FigMenu extends HTMLElement {
|
|
|
18898
19195
|
}
|
|
18899
19196
|
}
|
|
18900
19197
|
|
|
18901
|
-
#
|
|
18902
|
-
this.#
|
|
18903
|
-
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
18913
|
-
|
|
18914
|
-
|
|
18915
|
-
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
|
|
18920
|
-
|
|
18921
|
-
|
|
18922
|
-
|
|
19198
|
+
#initialize() {
|
|
19199
|
+
if (this.#initialized) return;
|
|
19200
|
+
this.#initialized = true;
|
|
19201
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
19202
|
+
figAdoptVerticalOverflowStyles(shadow, { icons: true });
|
|
19203
|
+
const style = document.createElement("style");
|
|
19204
|
+
style.textContent = `
|
|
19205
|
+
:host {
|
|
19206
|
+
display: contents;
|
|
19207
|
+
}
|
|
19208
|
+
/* Listbox chrome comes from document fig-menu::part(listbox).
|
|
19209
|
+
Stay in this shadow so item slots keep working — unlike tooltips,
|
|
19210
|
+
we cannot portal this popup to the overlay root.
|
|
19211
|
+
Overflow/hover/icon chrome lives in figVerticalOverflowSheet. */
|
|
19212
|
+
dialog[is="fig-popup"] {
|
|
19213
|
+
flex-direction: column;
|
|
19214
|
+
overflow: hidden;
|
|
19215
|
+
padding: 0;
|
|
19216
|
+
}
|
|
19217
|
+
dialog[is="fig-popup"][open] {
|
|
19218
|
+
display: flex;
|
|
19219
|
+
}
|
|
19220
|
+
.fig-menu-items {
|
|
19221
|
+
display: flex;
|
|
19222
|
+
flex-direction: column;
|
|
19223
|
+
flex: 0 0 auto;
|
|
19224
|
+
}
|
|
19225
|
+
.fig-menu-items > ::slotted(fig-menu-item),
|
|
19226
|
+
.fig-menu-items > ::slotted(fig-separator),
|
|
19227
|
+
.fig-menu-items > ::slotted(fig-menu-separator) {
|
|
19228
|
+
flex-shrink: 0;
|
|
19229
|
+
}
|
|
19230
|
+
`;
|
|
19231
|
+
|
|
19232
|
+
const triggerSlot = document.createElement("slot");
|
|
19233
|
+
triggerSlot.setAttribute("name", FIG_MENU_TRIGGER_SLOT);
|
|
19234
|
+
|
|
19235
|
+
const popup = document.createElement("dialog", { is: "fig-popup" });
|
|
19236
|
+
popup.setAttribute("is", "fig-popup");
|
|
19237
|
+
popup.classList.add("fig-menu-popup");
|
|
19238
|
+
popup.setAttribute("part", "listbox");
|
|
19239
|
+
popup.setAttribute("theme", "menu");
|
|
19240
|
+
popup.setAttribute("role", "menu");
|
|
19241
|
+
popup.id = figUniqueId();
|
|
18923
19242
|
if ("popover" in HTMLElement.prototype) {
|
|
18924
|
-
|
|
19243
|
+
popup.setAttribute("popover", "manual");
|
|
18925
19244
|
}
|
|
18926
19245
|
|
|
19246
|
+
const panel = document.createElement("div");
|
|
19247
|
+
panel.className = "fig-menu-options";
|
|
19248
|
+
panel.setAttribute("part", "options");
|
|
19249
|
+
panel.setAttribute("role", "presentation");
|
|
19250
|
+
|
|
19251
|
+
const items = document.createElement("div");
|
|
19252
|
+
items.className = "fig-menu-items";
|
|
19253
|
+
const itemSlot = document.createElement("slot");
|
|
19254
|
+
items.appendChild(itemSlot);
|
|
19255
|
+
panel.appendChild(items);
|
|
19256
|
+
popup.appendChild(panel);
|
|
19257
|
+
|
|
19258
|
+
shadow.append(style, triggerSlot, popup);
|
|
19259
|
+
|
|
19260
|
+
this.#popup = popup;
|
|
19261
|
+
this.#panel = panel;
|
|
19262
|
+
this.#itemSlot = itemSlot;
|
|
19263
|
+
this.#syncPopupAttrs();
|
|
19264
|
+
popup.addEventListener("close", this.#boundPopupClose);
|
|
19265
|
+
itemSlot.addEventListener("slotchange", this.#boundSlotChange);
|
|
19266
|
+
}
|
|
19267
|
+
|
|
19268
|
+
#syncPopupAttrs() {
|
|
19269
|
+
if (!this.#popup) return;
|
|
18927
19270
|
const position = this.getAttribute("position") || "bottom left";
|
|
18928
19271
|
this.#popup.setAttribute("position", position);
|
|
18929
|
-
|
|
18930
19272
|
const offset = this.getAttribute("offset");
|
|
18931
19273
|
if (offset) this.#popup.setAttribute("offset", offset);
|
|
18932
|
-
|
|
19274
|
+
else this.#popup.removeAttribute("offset");
|
|
18933
19275
|
const closedby = this.getAttribute("closedby");
|
|
18934
19276
|
if (closedby) this.#popup.setAttribute("closedby", closedby);
|
|
19277
|
+
else this.#popup.removeAttribute("closedby");
|
|
19278
|
+
}
|
|
18935
19279
|
|
|
18936
|
-
|
|
18937
|
-
|
|
19280
|
+
#detectTrigger() {
|
|
19281
|
+
this.#trigger =
|
|
19282
|
+
this.querySelector("[fig-menu-trigger]") ||
|
|
19283
|
+
this.querySelector(
|
|
19284
|
+
`:scope > :not(fig-menu-item):not(fig-separator):not(fig-menu-separator):not([slot="${FIG_MENU_TRIGGER_SLOT}"])`,
|
|
19285
|
+
) ||
|
|
19286
|
+
this.querySelector(`[slot="${FIG_MENU_TRIGGER_SLOT}"]`);
|
|
19287
|
+
this.#assignTriggerSlot();
|
|
19288
|
+
}
|
|
19289
|
+
|
|
19290
|
+
#assignTriggerSlot() {
|
|
19291
|
+
const host = figMenuTriggerHost(this, this.#trigger);
|
|
19292
|
+
this.#triggerHost = host;
|
|
19293
|
+
if (!host) return;
|
|
19294
|
+
if (host.getAttribute("slot") !== FIG_MENU_TRIGGER_SLOT) {
|
|
19295
|
+
host.setAttribute("slot", FIG_MENU_TRIGGER_SLOT);
|
|
18938
19296
|
}
|
|
19297
|
+
}
|
|
18939
19298
|
|
|
18940
|
-
|
|
18941
|
-
this
|
|
18942
|
-
this
|
|
18943
|
-
this.#popup.appendChild(this.#panel);
|
|
18944
|
-
this.#popup.addEventListener("close", this.#boundPopupClose);
|
|
18945
|
-
this.appendChild(this.#popup);
|
|
19299
|
+
#usesContextMenuTrigger() {
|
|
19300
|
+
const trigger = (this.getAttribute("trigger") || "").toLowerCase();
|
|
19301
|
+
return trigger === "contextmenu" || this.hasAttribute("contextmenu");
|
|
18946
19302
|
}
|
|
18947
19303
|
|
|
18948
|
-
#
|
|
18949
|
-
if (!this.#
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
19304
|
+
#bindTrigger() {
|
|
19305
|
+
if (!this.#trigger || !this.#popup) return;
|
|
19306
|
+
this.#trigger.addEventListener("click", this.#boundTriggerClick);
|
|
19307
|
+
this.#trigger.addEventListener("contextmenu", this.#boundTriggerContextMenu);
|
|
19308
|
+
this.#trigger.setAttribute("aria-haspopup", "menu");
|
|
19309
|
+
this.#trigger.setAttribute("aria-expanded", this.open ? "true" : "false");
|
|
19310
|
+
this.#trigger.setAttribute("aria-controls", this.#popup.id);
|
|
19311
|
+
this.#popup.anchor = this.#trigger;
|
|
19312
|
+
}
|
|
19313
|
+
|
|
19314
|
+
#unbindTrigger() {
|
|
19315
|
+
if (!this.#trigger) return;
|
|
19316
|
+
this.#trigger.removeEventListener("click", this.#boundTriggerClick);
|
|
19317
|
+
this.#trigger.removeEventListener("contextmenu", this.#boundTriggerContextMenu);
|
|
18954
19318
|
}
|
|
18955
19319
|
|
|
18956
19320
|
#setupListeners() {
|
|
18957
19321
|
this.addEventListener("keydown", this.#boundMenuKeydown);
|
|
18958
|
-
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
this.#trigger.setAttribute("aria-haspopup", "menu");
|
|
18962
|
-
this.#trigger.setAttribute("aria-expanded", "false");
|
|
18963
|
-
this.#trigger.setAttribute("aria-controls", this.#popup.getAttribute("id"));
|
|
18964
|
-
}
|
|
18965
|
-
if (this.#popup) {
|
|
18966
|
-
this.#popup.addEventListener("click", this.#boundPopupClick);
|
|
18967
|
-
this.#popup.addEventListener("keydown", this.#boundMenuKeydown);
|
|
18968
|
-
}
|
|
19322
|
+
this.addEventListener("click", this.#boundHostClick);
|
|
19323
|
+
this.#bindTrigger();
|
|
19324
|
+
this.#popup?.addEventListener("keydown", this.#boundMenuKeydown);
|
|
18969
19325
|
}
|
|
18970
19326
|
|
|
18971
19327
|
#teardownListeners() {
|
|
18972
19328
|
this.removeEventListener("keydown", this.#boundMenuKeydown);
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
}
|
|
18977
|
-
if (this.#popup) {
|
|
18978
|
-
this.#popup.removeEventListener("click", this.#boundPopupClick);
|
|
18979
|
-
this.#popup.removeEventListener("keydown", this.#boundMenuKeydown);
|
|
18980
|
-
}
|
|
19329
|
+
this.removeEventListener("click", this.#boundHostClick);
|
|
19330
|
+
this.#unbindTrigger();
|
|
19331
|
+
this.#popup?.removeEventListener("keydown", this.#boundMenuKeydown);
|
|
18981
19332
|
}
|
|
18982
19333
|
|
|
18983
19334
|
#setupObserver() {
|
|
19335
|
+
if (this.#observer) return;
|
|
18984
19336
|
this.#observer = new MutationObserver((mutations) => {
|
|
19337
|
+
let triggerChanged = false;
|
|
18985
19338
|
for (const mutation of mutations) {
|
|
18986
19339
|
for (const node of mutation.addedNodes) {
|
|
18987
|
-
if (node.nodeType !== 1
|
|
18988
|
-
if (
|
|
18989
|
-
|
|
18990
|
-
|
|
18991
|
-
|
|
18992
|
-
|
|
18993
|
-
|
|
18994
|
-
this.#detectTrigger();
|
|
18995
|
-
if (this.#trigger) {
|
|
18996
|
-
this.#trigger.addEventListener("click", this.#boundTriggerClick);
|
|
18997
|
-
this.#trigger.addEventListener("contextmenu", this.#boundTriggerContextMenu);
|
|
18998
|
-
this.#trigger.setAttribute("aria-haspopup", "menu");
|
|
18999
|
-
this.#trigger.setAttribute("aria-expanded", "false");
|
|
19000
|
-
this.#trigger.setAttribute("aria-controls", this.#popup.getAttribute("id"));
|
|
19001
|
-
this.#popup.anchor = this.#trigger;
|
|
19002
|
-
this.#syncDisabled();
|
|
19003
|
-
}
|
|
19340
|
+
if (node.nodeType !== 1) continue;
|
|
19341
|
+
if (figIsMenuChild(node)) continue;
|
|
19342
|
+
if (node.parentElement === this) triggerChanged = true;
|
|
19343
|
+
}
|
|
19344
|
+
for (const node of mutation.removedNodes) {
|
|
19345
|
+
if (node === this.#trigger || node === this.#triggerHost) {
|
|
19346
|
+
triggerChanged = true;
|
|
19004
19347
|
}
|
|
19005
19348
|
}
|
|
19006
19349
|
}
|
|
19350
|
+
if (triggerChanged || !this.#trigger) {
|
|
19351
|
+
this.#unbindTrigger();
|
|
19352
|
+
this.#detectTrigger();
|
|
19353
|
+
this.#bindTrigger();
|
|
19354
|
+
this.#syncDisabled();
|
|
19355
|
+
}
|
|
19007
19356
|
this.#syncOverflow();
|
|
19008
19357
|
});
|
|
19009
19358
|
this.#observer.observe(this, { childList: true });
|
|
@@ -19028,8 +19377,25 @@ class FigMenu extends HTMLElement {
|
|
|
19028
19377
|
this.#removeNavButtons();
|
|
19029
19378
|
}
|
|
19030
19379
|
|
|
19380
|
+
#markFirstSeparatorBorderless() {
|
|
19381
|
+
const first = Array.from(this.children).find((child) => figIsMenuChild(child));
|
|
19382
|
+
if (first && (first.tagName === "FIG-SEPARATOR" || first.tagName === "FIG-MENU-SEPARATOR")) {
|
|
19383
|
+
first.setAttribute("borderless", "");
|
|
19384
|
+
}
|
|
19385
|
+
}
|
|
19386
|
+
|
|
19031
19387
|
#syncOverflow() {
|
|
19032
|
-
|
|
19388
|
+
this.#markFirstSeparatorBorderless();
|
|
19389
|
+
const scrollable = figSyncOverflowState(this.#panel, this.#panel, "y");
|
|
19390
|
+
this.classList.toggle(
|
|
19391
|
+
"overflow-start",
|
|
19392
|
+
Boolean(this.#panel?.classList.contains("overflow-start")),
|
|
19393
|
+
);
|
|
19394
|
+
this.classList.toggle(
|
|
19395
|
+
"overflow-end",
|
|
19396
|
+
Boolean(this.#panel?.classList.contains("overflow-end")),
|
|
19397
|
+
);
|
|
19398
|
+
return scrollable;
|
|
19033
19399
|
}
|
|
19034
19400
|
|
|
19035
19401
|
#ensureNavButtons() {
|
|
@@ -19061,13 +19427,14 @@ class FigMenu extends HTMLElement {
|
|
|
19061
19427
|
this.#navStart = null;
|
|
19062
19428
|
this.#navEnd = null;
|
|
19063
19429
|
this.#panel?.classList.remove("overflow-start", "overflow-end");
|
|
19430
|
+
this.classList.remove("overflow-start", "overflow-end");
|
|
19064
19431
|
}
|
|
19065
19432
|
|
|
19066
19433
|
#getItems() {
|
|
19067
|
-
|
|
19068
|
-
return Array.from(this.#panel.querySelectorAll("fig-menu-item")).filter(
|
|
19434
|
+
return Array.from(this.querySelectorAll(":scope > fig-menu-item")).filter(
|
|
19069
19435
|
(item) =>
|
|
19070
|
-
!item.
|
|
19436
|
+
!item.hidden &&
|
|
19437
|
+
(!item.hasAttribute("disabled") || item.getAttribute("disabled") === "false"),
|
|
19071
19438
|
);
|
|
19072
19439
|
}
|
|
19073
19440
|
|
|
@@ -19091,7 +19458,31 @@ class FigMenu extends HTMLElement {
|
|
|
19091
19458
|
this.#focusedIndex = wrapped;
|
|
19092
19459
|
const item = items[wrapped];
|
|
19093
19460
|
item.focus();
|
|
19094
|
-
|
|
19461
|
+
this.#scrollItemIntoView(item);
|
|
19462
|
+
}
|
|
19463
|
+
|
|
19464
|
+
#scrollItemIntoView(item) {
|
|
19465
|
+
const panel = this.#panel;
|
|
19466
|
+
if (!panel || !item) return;
|
|
19467
|
+
requestAnimationFrame(() => {
|
|
19468
|
+
if (!panel.isConnected || !item.isConnected) return;
|
|
19469
|
+
const scrollSize = panel.scrollHeight;
|
|
19470
|
+
const clientSize = panel.clientHeight;
|
|
19471
|
+
if (scrollSize <= clientSize + 1) {
|
|
19472
|
+
this.#syncOverflow();
|
|
19473
|
+
return;
|
|
19474
|
+
}
|
|
19475
|
+
const itemRect = item.getBoundingClientRect();
|
|
19476
|
+
const hostRect = panel.getBoundingClientRect();
|
|
19477
|
+
const elementStart = itemRect.top - hostRect.top + panel.scrollTop;
|
|
19478
|
+
const maxScroll = scrollSize - clientSize;
|
|
19479
|
+
const nextScroll = Math.max(
|
|
19480
|
+
0,
|
|
19481
|
+
Math.min(elementStart + itemRect.height / 2 - clientSize / 2, maxScroll),
|
|
19482
|
+
);
|
|
19483
|
+
panel.scrollTo({ top: nextScroll, behavior: "auto" });
|
|
19484
|
+
this.#syncOverflow();
|
|
19485
|
+
});
|
|
19095
19486
|
}
|
|
19096
19487
|
|
|
19097
19488
|
#syncDisabled() {
|
|
@@ -19171,19 +19562,30 @@ class FigMenu extends HTMLElement {
|
|
|
19171
19562
|
fallbackTimer = window.setTimeout(openMenu, 180);
|
|
19172
19563
|
}
|
|
19173
19564
|
|
|
19174
|
-
#
|
|
19565
|
+
#handleHostClick(e) {
|
|
19175
19566
|
if (this.#isDisabled()) return;
|
|
19176
|
-
|
|
19177
|
-
|
|
19178
|
-
|
|
19179
|
-
|
|
19567
|
+
if (this.#trigger?.contains(e.target) || this.#triggerHost?.contains(e.target)) {
|
|
19568
|
+
return;
|
|
19569
|
+
}
|
|
19570
|
+
const item = e.target.closest?.("fig-menu-item");
|
|
19571
|
+
// Direct children only — nested fig-menu inside an item has its own host.
|
|
19572
|
+
if (!item || item.parentElement !== this) return;
|
|
19573
|
+
if (item.hasAttribute("disabled") && item.getAttribute("disabled") !== "false") {
|
|
19574
|
+
return;
|
|
19575
|
+
}
|
|
19576
|
+
e.stopPropagation();
|
|
19180
19577
|
this.#selectItem(item);
|
|
19181
19578
|
}
|
|
19182
19579
|
|
|
19183
19580
|
#handleMenuKeydown(e) {
|
|
19184
19581
|
if (this.#isDisabled()) return;
|
|
19185
19582
|
if (e.currentTarget === document && e.key !== "Escape") return;
|
|
19186
|
-
|
|
19583
|
+
// Slotted item keydown crosses the shadow slot, so the popup listener
|
|
19584
|
+
// already handles it. Skip the host to avoid double-stepping.
|
|
19585
|
+
if (e.currentTarget === this) {
|
|
19586
|
+
const path = typeof e.composedPath === "function" ? e.composedPath() : [];
|
|
19587
|
+
if (this.#popup && path.includes(this.#popup)) return;
|
|
19588
|
+
}
|
|
19187
19589
|
if (!this.open || !this.#popup?.matches?.(":open")) {
|
|
19188
19590
|
if (
|
|
19189
19591
|
this.#trigger?.contains(e.target) &&
|