@pure-ds/core 0.7.42 → 0.7.44
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/dist/types/src/js/pds-core/pds-enhancers-meta.d.ts +12 -2
- package/dist/types/src/js/pds-core/pds-enhancers-meta.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-live.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-ontology.d.ts.map +1 -1
- package/package.json +1 -1
- package/packages/pds-cli/lib/pds-mcp-core.js +1 -0
- package/public/assets/js/app.js +1 -1
- package/public/assets/js/pds-enhancers.js +1 -1
- package/public/assets/js/pds-manager.js +69 -72
- package/public/assets/pds/components/pds-drawer.js +7 -2
- package/public/assets/pds/core/pds-enhancers.js +1 -1
- package/public/assets/pds/core/pds-manager.js +69 -72
- package/readme.md +1 -0
- package/src/js/pds-core/pds-enhancers-meta.js +13 -8
- package/src/js/pds-core/pds-enhancers.js +97 -192
- package/src/js/pds-core/pds-generator.js +13 -13
- package/src/js/pds-core/pds-live.js +6 -2
- package/src/js/pds-core/pds-ontology.js +8 -2
package/readme.md
CHANGED
|
@@ -469,6 +469,7 @@ Features:
|
|
|
469
469
|
- Horizontal alignment (`.align-right` class)
|
|
470
470
|
- Keyboard support (Escape to close)
|
|
471
471
|
- Click-outside to close
|
|
472
|
+
- Declarative close on selection via `data-dropdown-close` on clickable menu/panel items
|
|
472
473
|
- Scrollable when content exceeds viewport
|
|
473
474
|
- Panel can be any last child element (menu, card, form, etc.)
|
|
474
475
|
|
|
@@ -31,17 +31,22 @@ export const defaultPDSEnhancerMetadata = [
|
|
|
31
31
|
{
|
|
32
32
|
selector: "nav[data-dropdown]",
|
|
33
33
|
description:
|
|
34
|
-
"Enhances a nav element with data-dropdown to toggle its last child as a dropdown panel (menu, card, form, etc.).",
|
|
34
|
+
"Enhances a nav element with data-dropdown to toggle its last child as a dropdown panel (menu, card, form, etc.). Add data-dropdown-close to any clickable descendant that should close the menu on selection.",
|
|
35
|
+
attributes: [
|
|
36
|
+
{
|
|
37
|
+
name: "data-dropdown-close",
|
|
38
|
+
description:
|
|
39
|
+
"When clicked (or when a descendant is clicked), closes the currently open dropdown popover.",
|
|
40
|
+
appliesTo: "Any clickable element inside nav[data-dropdown] menu/panel content",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
35
43
|
demoHtml: `
|
|
36
44
|
<nav data-dropdown>
|
|
37
45
|
<button class="btn-primary">Menu</button>
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
<button class="btn-outline btn-sm">Schedule</button>
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
46
|
+
<menu>
|
|
47
|
+
<li><a href="#open">Open</a></li>
|
|
48
|
+
<li><a href="#settings" data-dropdown-close>Open settings and close</a></li>
|
|
49
|
+
</menu>
|
|
45
50
|
</nav>
|
|
46
51
|
`.trim(),
|
|
47
52
|
},
|
|
@@ -61,6 +61,11 @@ function enhanceDropdown(elem) {
|
|
|
61
61
|
elem.querySelector("[data-dropdown-toggle]") ||
|
|
62
62
|
elem.querySelector("button");
|
|
63
63
|
|
|
64
|
+
const supportsPopover =
|
|
65
|
+
typeof HTMLElement !== "undefined" &&
|
|
66
|
+
"showPopover" in HTMLElement.prototype &&
|
|
67
|
+
"hidePopover" in HTMLElement.prototype;
|
|
68
|
+
|
|
64
69
|
if (trigger && !trigger.hasAttribute("type")) {
|
|
65
70
|
trigger.setAttribute("type", "button");
|
|
66
71
|
}
|
|
@@ -84,6 +89,19 @@ function enhanceDropdown(elem) {
|
|
|
84
89
|
trigger.setAttribute("aria-expanded", "false");
|
|
85
90
|
}
|
|
86
91
|
|
|
92
|
+
if (!supportsPopover) {
|
|
93
|
+
const warnKey = "__PDS_DROPDOWN_POPOVER_WARNED__";
|
|
94
|
+
if (!globalThis[warnKey]) {
|
|
95
|
+
globalThis[warnKey] = true;
|
|
96
|
+
console.warn(
|
|
97
|
+
"[PDS] nav[data-dropdown] requires the Popover API. Add a popover polyfill (recommended: @oddbird/popover-polyfill) for browsers without support.",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
menu.setAttribute("popover", "auto");
|
|
104
|
+
|
|
87
105
|
const measureMenuSize = () => {
|
|
88
106
|
const previousStyle = menu.getAttribute("style");
|
|
89
107
|
menu.style.visibility = "hidden";
|
|
@@ -108,6 +126,24 @@ function enhanceDropdown(elem) {
|
|
|
108
126
|
return { width, height };
|
|
109
127
|
};
|
|
110
128
|
|
|
129
|
+
const isPopoverOpen = () => {
|
|
130
|
+
try {
|
|
131
|
+
return menu.matches(":popover-open");
|
|
132
|
+
} catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const syncClosedState = () => {
|
|
138
|
+
menu.setAttribute("aria-hidden", "true");
|
|
139
|
+
trigger?.setAttribute("aria-expanded", "false");
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const syncOpenState = () => {
|
|
143
|
+
menu.setAttribute("aria-hidden", "false");
|
|
144
|
+
trigger?.setAttribute("aria-expanded", "true");
|
|
145
|
+
};
|
|
146
|
+
|
|
111
147
|
const resolveDirection = () => {
|
|
112
148
|
const mode = (
|
|
113
149
|
elem.getAttribute("data-direction") ||
|
|
@@ -170,136 +206,23 @@ function enhanceDropdown(elem) {
|
|
|
170
206
|
};
|
|
171
207
|
|
|
172
208
|
const clearFloatingMenuPosition = () => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const getContainingAncestor = (node) => {
|
|
187
|
-
if (!node) return null;
|
|
188
|
-
if (node.parentElement) return node.parentElement;
|
|
189
|
-
const root = node.getRootNode?.();
|
|
190
|
-
return root instanceof ShadowRoot ? root.host : null;
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const hasNonViewportFixedContainingBlock = () => {
|
|
194
|
-
let current = getContainingAncestor(menu);
|
|
195
|
-
while (current && current !== document.documentElement) {
|
|
196
|
-
const style = getComputedStyle(current);
|
|
197
|
-
const contain = style.contain || "";
|
|
198
|
-
const willChange = style.willChange || "";
|
|
199
|
-
const createsContainingBlock =
|
|
200
|
-
style.transform !== "none" ||
|
|
201
|
-
style.perspective !== "none" ||
|
|
202
|
-
style.filter !== "none" ||
|
|
203
|
-
style.backdropFilter !== "none" ||
|
|
204
|
-
contain.includes("paint") ||
|
|
205
|
-
contain.includes("layout") ||
|
|
206
|
-
contain.includes("strict") ||
|
|
207
|
-
contain.includes("content") ||
|
|
208
|
-
willChange.includes("transform") ||
|
|
209
|
-
willChange.includes("perspective") ||
|
|
210
|
-
willChange.includes("filter");
|
|
211
|
-
|
|
212
|
-
if (createsContainingBlock) {
|
|
213
|
-
return true;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
current = getContainingAncestor(current);
|
|
217
|
-
}
|
|
218
|
-
return false;
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const collectClippingAncestors = (startNode, stopAt = null) => {
|
|
222
|
-
const targets = [];
|
|
223
|
-
let current = getContainingAncestor(startNode);
|
|
224
|
-
|
|
225
|
-
while (current instanceof Element) {
|
|
226
|
-
const tagName = String(current.tagName || "").toUpperCase();
|
|
227
|
-
if (tagName === "HTML" || tagName === "BODY") {
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const style = getComputedStyle(current);
|
|
232
|
-
const clips = [style.overflow, style.overflowX, style.overflowY].some(
|
|
233
|
-
(value) => value && value !== "visible",
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
if (clips) {
|
|
237
|
-
targets.push(current);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (stopAt && current === stopAt) {
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
current = getContainingAncestor(current);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return targets;
|
|
209
|
+
[
|
|
210
|
+
"position",
|
|
211
|
+
"left",
|
|
212
|
+
"top",
|
|
213
|
+
"right",
|
|
214
|
+
"bottom",
|
|
215
|
+
"margin-top",
|
|
216
|
+
"margin-bottom",
|
|
217
|
+
"max-width",
|
|
218
|
+
"max-inline-size",
|
|
219
|
+
"max-height",
|
|
220
|
+
"overflow",
|
|
221
|
+
].forEach((prop) => menu.style.removeProperty(prop));
|
|
248
222
|
};
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
252
|
-
if (overlayOverflowOverrides.size) return;
|
|
253
|
-
|
|
254
|
-
const targets = collectClippingAncestors(menu, stopAt);
|
|
255
|
-
targets.forEach((element) => {
|
|
256
|
-
overlayOverflowOverrides.set(element, {
|
|
257
|
-
overflow: element.style.overflow,
|
|
258
|
-
overflowX: element.style.overflowX,
|
|
259
|
-
overflowY: element.style.overflowY,
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
element.style.overflow = "visible";
|
|
263
|
-
element.style.overflowX = "visible";
|
|
264
|
-
element.style.overflowY = "visible";
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
overlayOverflowOverrides.forEach((previous, element) => {
|
|
271
|
-
element.style.overflow = previous.overflow;
|
|
272
|
-
element.style.overflowX = previous.overflowX;
|
|
273
|
-
element.style.overflowY = previous.overflowY;
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
overlayOverflowOverrides.clear();
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
const reattachFloatingMenu = () => {
|
|
280
|
-
if (menu.getAttribute("aria-hidden") !== "false") return;
|
|
281
|
-
clearFloatingMenuPosition();
|
|
282
|
-
requestAnimationFrame(() => {
|
|
283
|
-
requestAnimationFrame(() => {
|
|
284
|
-
positionFloatingMenu();
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const positionFloatingMenu = () => {
|
|
290
|
-
if (menu.getAttribute("aria-hidden") !== "false") return;
|
|
291
|
-
const hasFixedContainingBlock = hasNonViewportFixedContainingBlock();
|
|
292
|
-
|
|
293
|
-
if (hasFixedContainingBlock) {
|
|
294
|
-
// Fixed overlay is unsafe in this context; keep local positioning,
|
|
295
|
-
// but temporarily unclip ancestor overflow containers.
|
|
296
|
-
setOverlayClippingOverride(true);
|
|
297
|
-
clearFloatingMenuPosition();
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Fixed overlay path does not need clipping overrides.
|
|
302
|
-
setOverlayClippingOverride(false);
|
|
223
|
+
|
|
224
|
+
const positionPopoverMenu = () => {
|
|
225
|
+
if (!isPopoverOpen()) return;
|
|
303
226
|
|
|
304
227
|
const anchorRect = (trigger || elem).getBoundingClientRect();
|
|
305
228
|
const viewport = window.visualViewport;
|
|
@@ -350,19 +273,21 @@ function enhanceDropdown(elem) {
|
|
|
350
273
|
),
|
|
351
274
|
);
|
|
352
275
|
|
|
353
|
-
menu.style
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
276
|
+
Object.assign(menu.style, {
|
|
277
|
+
position: "fixed",
|
|
278
|
+
left: `${Math.round(left)}px`,
|
|
279
|
+
top: `${Math.round(top)}px`,
|
|
280
|
+
right: "auto",
|
|
281
|
+
bottom: "auto",
|
|
282
|
+
marginTop: "0",
|
|
283
|
+
marginBottom: "0",
|
|
284
|
+
});
|
|
360
285
|
};
|
|
361
286
|
|
|
362
287
|
let repositionHandler = null;
|
|
363
288
|
const bindReposition = () => {
|
|
364
289
|
if (repositionHandler) return;
|
|
365
|
-
repositionHandler = () =>
|
|
290
|
+
repositionHandler = () => positionPopoverMenu();
|
|
366
291
|
window.addEventListener("resize", repositionHandler);
|
|
367
292
|
window.addEventListener("scroll", repositionHandler, true);
|
|
368
293
|
};
|
|
@@ -376,11 +301,10 @@ function enhanceDropdown(elem) {
|
|
|
376
301
|
|
|
377
302
|
let configChangedHandler = null;
|
|
378
303
|
let configRepositionFrame = null;
|
|
379
|
-
const overlayOverflowOverrides = new Map();
|
|
380
304
|
const bindConfigChanged = () => {
|
|
381
305
|
if (configChangedHandler || typeof document === "undefined") return;
|
|
382
306
|
configChangedHandler = () => {
|
|
383
|
-
if (
|
|
307
|
+
if (!isPopoverOpen()) return;
|
|
384
308
|
elem.dataset.dropdownDirection = resolveDirection();
|
|
385
309
|
elem.dataset.dropdownAlign = resolveAlign();
|
|
386
310
|
|
|
@@ -389,8 +313,8 @@ function enhanceDropdown(elem) {
|
|
|
389
313
|
}
|
|
390
314
|
configRepositionFrame = requestAnimationFrame(() => {
|
|
391
315
|
configRepositionFrame = null;
|
|
392
|
-
if (
|
|
393
|
-
|
|
316
|
+
if (!isPopoverOpen()) return;
|
|
317
|
+
positionPopoverMenu();
|
|
394
318
|
});
|
|
395
319
|
};
|
|
396
320
|
document.addEventListener("pds:config-changed", configChangedHandler);
|
|
@@ -406,60 +330,54 @@ function enhanceDropdown(elem) {
|
|
|
406
330
|
}
|
|
407
331
|
};
|
|
408
332
|
|
|
409
|
-
|
|
410
|
-
|
|
333
|
+
menu.addEventListener("toggle", (event) => {
|
|
334
|
+
const isOpen = event.newState === "open";
|
|
411
335
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
bindReposition();
|
|
419
|
-
bindConfigChanged();
|
|
420
|
-
reattachFloatingMenu();
|
|
421
|
-
|
|
422
|
-
// Add click-outside handler when opening
|
|
423
|
-
if (!clickHandler) {
|
|
424
|
-
clickHandler = (event) => {
|
|
425
|
-
// Use composedPath() to handle Shadow DOM
|
|
426
|
-
const path = event.composedPath ? event.composedPath() : [event.target];
|
|
427
|
-
const clickedInside = path.some((node) => node === elem);
|
|
428
|
-
|
|
429
|
-
if (!clickedInside) {
|
|
430
|
-
closeMenu();
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
// Use a slight delay to avoid closing immediately if this was triggered by a click
|
|
434
|
-
setTimeout(() => {
|
|
435
|
-
document.addEventListener("click", clickHandler);
|
|
436
|
-
}, 0);
|
|
336
|
+
if (isOpen) {
|
|
337
|
+
syncOpenState();
|
|
338
|
+
positionPopoverMenu();
|
|
339
|
+
bindReposition();
|
|
340
|
+
bindConfigChanged();
|
|
341
|
+
return;
|
|
437
342
|
}
|
|
438
|
-
};
|
|
439
343
|
|
|
440
|
-
|
|
441
|
-
menu.setAttribute("aria-hidden", "true");
|
|
442
|
-
trigger?.setAttribute("aria-expanded", "false");
|
|
344
|
+
syncClosedState();
|
|
443
345
|
unbindReposition();
|
|
444
346
|
unbindConfigChanged();
|
|
445
347
|
clearFloatingMenuPosition();
|
|
446
|
-
|
|
348
|
+
});
|
|
447
349
|
|
|
448
|
-
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
350
|
+
const openMenu = () => {
|
|
351
|
+
if (isPopoverOpen()) return;
|
|
352
|
+
elem.dataset.dropdownDirection = resolveDirection();
|
|
353
|
+
elem.dataset.dropdownAlign = resolveAlign();
|
|
354
|
+
menu.showPopover();
|
|
355
|
+
requestAnimationFrame(() => positionPopoverMenu());
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const closeMenu = () => {
|
|
359
|
+
if (!isPopoverOpen()) return;
|
|
360
|
+
menu.hidePopover();
|
|
453
361
|
};
|
|
454
362
|
|
|
455
363
|
const toggleMenu = () => {
|
|
456
|
-
if (
|
|
364
|
+
if (isPopoverOpen()) {
|
|
457
365
|
closeMenu();
|
|
458
366
|
} else {
|
|
459
367
|
openMenu();
|
|
460
368
|
}
|
|
461
369
|
};
|
|
462
370
|
|
|
371
|
+
syncClosedState();
|
|
372
|
+
|
|
373
|
+
menu.addEventListener("click", (event) => {
|
|
374
|
+
const target =
|
|
375
|
+
event.target instanceof Element ? event.target : event.target?.parentElement;
|
|
376
|
+
if (!target) return;
|
|
377
|
+
if (!target.closest("[data-dropdown-close]")) return;
|
|
378
|
+
closeMenu();
|
|
379
|
+
});
|
|
380
|
+
|
|
463
381
|
trigger?.addEventListener("click", (event) => {
|
|
464
382
|
event.preventDefault();
|
|
465
383
|
event.stopPropagation();
|
|
@@ -472,19 +390,6 @@ function enhanceDropdown(elem) {
|
|
|
472
390
|
trigger?.focus();
|
|
473
391
|
}
|
|
474
392
|
});
|
|
475
|
-
|
|
476
|
-
elem.addEventListener("focusout", (event) => {
|
|
477
|
-
// Only close if focus is explicitly moving to an element outside the dropdown
|
|
478
|
-
// Don't close if relatedTarget is null (which happens when clicking non-focusable elements inside)
|
|
479
|
-
// Use composedPath() to handle Shadow DOM properly
|
|
480
|
-
if (event.relatedTarget) {
|
|
481
|
-
const path = event.composedPath ? event.composedPath() : [event.relatedTarget];
|
|
482
|
-
const focusedInside = path.some((node) => node === elem);
|
|
483
|
-
if (!focusedInside) {
|
|
484
|
-
closeMenu();
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
393
|
}
|
|
489
394
|
|
|
490
395
|
function enhanceToggle(elem) {
|
|
@@ -3582,12 +3582,11 @@ dialog {
|
|
|
3582
3582
|
|
|
3583
3583
|
/*
|
|
3584
3584
|
* Overlay safety valve:
|
|
3585
|
-
* Some controls (e.g. pds-daterange panel
|
|
3585
|
+
* Some controls (e.g. pds-daterange panel) need to escape
|
|
3586
3586
|
* the dialog bounds. Scope overflow visibility to custom dialogs that contain
|
|
3587
3587
|
* those controls instead of enabling it for all dialogs.
|
|
3588
3588
|
*/
|
|
3589
|
-
dialog.dialog-custom:has(pds-daterange)
|
|
3590
|
-
dialog.dialog-custom:has([data-dropdown]) {
|
|
3589
|
+
dialog.dialog-custom:has(pds-daterange) {
|
|
3591
3590
|
overflow: visible;
|
|
3592
3591
|
}
|
|
3593
3592
|
|
|
@@ -3657,13 +3656,6 @@ dialog {
|
|
|
3657
3656
|
overflow-x: visible;
|
|
3658
3657
|
}
|
|
3659
3658
|
|
|
3660
|
-
/* Allow overlay menus (e.g. data-dropdown) to escape dialog-body clipping while open */
|
|
3661
|
-
article:has([data-dropdown] > :last-child[aria-hidden="false"]),
|
|
3662
|
-
form > article:has([data-dropdown] > :last-child[aria-hidden="false"]),
|
|
3663
|
-
.dialog-body:has([data-dropdown] > :last-child[aria-hidden="false"]) {
|
|
3664
|
-
overflow: visible;
|
|
3665
|
-
}
|
|
3666
|
-
|
|
3667
3659
|
article:has(pds-daterange),
|
|
3668
3660
|
form > article:has(pds-daterange),
|
|
3669
3661
|
.dialog-body:has(pds-daterange) {
|
|
@@ -3751,7 +3743,8 @@ dialog.dialog-full { width: calc(100vw - var(--spacing-8)); max-width: calc(100v
|
|
|
3751
3743
|
dialog, dialog::backdrop { transition-duration: 0.01s !important; }
|
|
3752
3744
|
}
|
|
3753
3745
|
|
|
3754
|
-
html:has(dialog[open]:modal)
|
|
3746
|
+
html:has(dialog[open]:modal),
|
|
3747
|
+
html:has(pds-drawer[open]) {
|
|
3755
3748
|
overflow: hidden;
|
|
3756
3749
|
scrollbar-gutter: stable;
|
|
3757
3750
|
}
|
|
@@ -4096,7 +4089,13 @@ nav[data-dropdown] {
|
|
|
4096
4089
|
transition-behavior: allow-discrete;
|
|
4097
4090
|
}
|
|
4098
4091
|
|
|
4099
|
-
& > :last-child[
|
|
4092
|
+
& > :last-child[popover] {
|
|
4093
|
+
inset: auto;
|
|
4094
|
+
margin: 0;
|
|
4095
|
+
}
|
|
4096
|
+
|
|
4097
|
+
& > :last-child[aria-hidden="false"],
|
|
4098
|
+
& > :last-child:popover-open {
|
|
4100
4099
|
display: inline-block;
|
|
4101
4100
|
opacity: 1;
|
|
4102
4101
|
visibility: visible;
|
|
@@ -4198,7 +4197,8 @@ nav[data-dropdown] {
|
|
|
4198
4197
|
}
|
|
4199
4198
|
|
|
4200
4199
|
@starting-style {
|
|
4201
|
-
nav[data-dropdown] > :last-child[aria-hidden="false"]
|
|
4200
|
+
nav[data-dropdown] > :last-child[aria-hidden="false"],
|
|
4201
|
+
nav[data-dropdown] > :last-child:popover-open {
|
|
4202
4202
|
opacity: 0;
|
|
4203
4203
|
}
|
|
4204
4204
|
}
|
|
@@ -319,8 +319,12 @@ async function ensureLiveEditToggleButton() {
|
|
|
319
319
|
};
|
|
320
320
|
|
|
321
321
|
menu.appendChild(createItem("toggle", msg("Toggle live editing"), "pencil"));
|
|
322
|
-
|
|
323
|
-
|
|
322
|
+
|
|
323
|
+
const settingsItem = createItem("open-settings", msg("Open Settings"), "gear");
|
|
324
|
+
settingsItem.setAttribute("data-dropdown-close", "");
|
|
325
|
+
|
|
326
|
+
menu.appendChild(settingsItem);
|
|
327
|
+
|
|
324
328
|
menu.appendChild(createItem("reset-config", msg("Reset Config"), "arrow-counter-clockwise"));
|
|
325
329
|
|
|
326
330
|
await ensureSharedQuickModeToggleMenuItem(menu);
|
|
@@ -452,8 +452,14 @@ export const ontology = {
|
|
|
452
452
|
{
|
|
453
453
|
id: "dropdown",
|
|
454
454
|
selector: "nav[data-dropdown]",
|
|
455
|
-
description: "Dropdown menu from nav element",
|
|
456
|
-
tags: ["menu", "interactive", "navigation"]
|
|
455
|
+
description: "Dropdown menu from nav element. Use data-dropdown-close on clickable descendants to dismiss on selection.",
|
|
456
|
+
tags: ["menu", "interactive", "navigation", "dismiss", "close"]
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
id: "dropdown-close",
|
|
460
|
+
selector: "[data-dropdown-close]",
|
|
461
|
+
description: "Declarative close marker for nav[data-dropdown] panels; clicking marked targets closes the open dropdown.",
|
|
462
|
+
tags: ["dropdown", "menu", "dismiss", "close", "attribute"]
|
|
457
463
|
},
|
|
458
464
|
{
|
|
459
465
|
id: "toggle",
|