@lilydesignsystem/web-components-theme-picker 0.1.0 → 0.2.0
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/index.d.ts +9 -0
- package/dist/index.js +121 -12
- package/package.json +4 -1
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,14 @@
|
|
|
7
7
|
*
|
|
8
8
|
* The control is an icon button that opens a dropdown listbox
|
|
9
9
|
* (WAI-ARIA APG listbox pattern). It is not a native `<select>`.
|
|
10
|
+
*
|
|
11
|
+
* Depends on `@lilydesignsystem/web-components-headless`'s
|
|
12
|
+
* `<lily-icon-button>` for the trigger button, composed in `#render()`
|
|
13
|
+
* rather than hand-rolled. Imported here (not only from `index.ts`) so
|
|
14
|
+
* `lily-icon-button` is registered regardless of which module a
|
|
15
|
+
* consumer (or this package's own tests) imports first.
|
|
10
16
|
*/
|
|
17
|
+
|
|
11
18
|
/**
|
|
12
19
|
* Default button icon: a bundled SVG (contrast/half-circle), not a
|
|
13
20
|
* Unicode character. Reversed 2026-09-16 from the font-dependent-glyph
|
|
@@ -70,6 +77,8 @@ declare function nextThemePickerId(): string;
|
|
|
70
77
|
declare class ThemePicker extends HTMLElement {
|
|
71
78
|
#private;
|
|
72
79
|
static get observedAttributes(): string[];
|
|
80
|
+
/** id of the rendered `<div class="theme-picker-tooltip">`. */
|
|
81
|
+
get tooltipId(): string;
|
|
73
82
|
get label(): string;
|
|
74
83
|
set label(v: string);
|
|
75
84
|
get themesUrl(): string;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
// lily-design-system-web-components-theme-picker/index.ts
|
|
2
|
+
import "@lilydesignsystem/web-components-headless";
|
|
3
|
+
|
|
1
4
|
// lily-design-system-web-components-theme-picker/theme-picker.ts
|
|
5
|
+
import "@lilydesignsystem/web-components-headless";
|
|
2
6
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
3
7
|
function themeName(theme) {
|
|
4
8
|
return theme.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
@@ -45,6 +49,8 @@ var ThemePicker = class extends HTMLElement {
|
|
|
45
49
|
// Rendered-DOM references. Null until #render() has run.
|
|
46
50
|
#rootEl = null;
|
|
47
51
|
#inputEl = null;
|
|
52
|
+
// The real <button>, nested one level inside the composed
|
|
53
|
+
// <lily-icon-button> host (see #render()) — not #rootEl's direct child.
|
|
48
54
|
#buttonEl = null;
|
|
49
55
|
#listEl = null;
|
|
50
56
|
#optionEls = [];
|
|
@@ -53,6 +59,59 @@ var ThemePicker = class extends HTMLElement {
|
|
|
53
59
|
#activeIndex = -1;
|
|
54
60
|
// Stable ids for the button/listbox aria wiring.
|
|
55
61
|
#baseId = nextThemePickerId();
|
|
62
|
+
// Tooltip: shown while the pointer is over the button or the tooltip
|
|
63
|
+
// itself (hoverable, WCAG 1.4.13) or while the button has keyboard
|
|
64
|
+
// focus; Escape dismisses it without moving focus; never shown while
|
|
65
|
+
// the popup is open, since the popup then explains the control.
|
|
66
|
+
#tooltipEl = null;
|
|
67
|
+
#hoverButton = false;
|
|
68
|
+
#hoverTooltip = false;
|
|
69
|
+
#focusButton = false;
|
|
70
|
+
#tipDismissed = false;
|
|
71
|
+
// Escape anywhere dismisses a visible tooltip (WCAG 1.4.13: dismissable
|
|
72
|
+
// without moving pointer or focus), so a hover-only tooltip (focus
|
|
73
|
+
// elsewhere) still closes. The document listener exists only while
|
|
74
|
+
// the tooltip is visible; it neither prevents default nor stops
|
|
75
|
+
// propagation, and never moves focus.
|
|
76
|
+
#tipDocListening = false;
|
|
77
|
+
#onTipDocKeydown = (event) => {
|
|
78
|
+
if (event.key === "Escape" && this.#tooltipVisible()) {
|
|
79
|
+
this.#tipDismissed = true;
|
|
80
|
+
this.#syncTooltip();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
#setTipDocListener(on) {
|
|
84
|
+
if (on === this.#tipDocListening || typeof document === "undefined") return;
|
|
85
|
+
this.#tipDocListening = on;
|
|
86
|
+
if (on) document.addEventListener("keydown", this.#onTipDocKeydown);
|
|
87
|
+
else document.removeEventListener("keydown", this.#onTipDocKeydown);
|
|
88
|
+
}
|
|
89
|
+
#tooltipVisible() {
|
|
90
|
+
return !this.#open && !this.#tipDismissed && (this.#hoverButton || this.#hoverTooltip || this.#focusButton);
|
|
91
|
+
}
|
|
92
|
+
/** Write the tooltip's text and `hidden` in place; idempotent. */
|
|
93
|
+
#syncTooltip() {
|
|
94
|
+
const el = this.#tooltipEl;
|
|
95
|
+
if (!el) return;
|
|
96
|
+
if (el.textContent !== this.label) el.textContent = this.label;
|
|
97
|
+
const hide = !this.#tooltipVisible();
|
|
98
|
+
this.#setTipDocListener(!hide);
|
|
99
|
+
if (el.hasAttribute("hidden") !== hide) {
|
|
100
|
+
if (hide) el.setAttribute("hidden", "");
|
|
101
|
+
else el.removeAttribute("hidden");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
#resetTooltip() {
|
|
105
|
+
this.#setTipDocListener(false);
|
|
106
|
+
this.#hoverButton = false;
|
|
107
|
+
this.#hoverTooltip = false;
|
|
108
|
+
this.#focusButton = false;
|
|
109
|
+
this.#tipDismissed = false;
|
|
110
|
+
}
|
|
111
|
+
/** id of the rendered `<div class="theme-picker-tooltip">`. */
|
|
112
|
+
get tooltipId() {
|
|
113
|
+
return `${this.#baseId}-tooltip`;
|
|
114
|
+
}
|
|
56
115
|
// Typeahead buffer: APG listbox behaviour. Reset after a pause.
|
|
57
116
|
#typeahead = "";
|
|
58
117
|
#typeaheadTimer;
|
|
@@ -250,6 +309,7 @@ var ThemePicker = class extends HTMLElement {
|
|
|
250
309
|
}
|
|
251
310
|
}
|
|
252
311
|
disconnectedCallback() {
|
|
312
|
+
this.#resetTooltip();
|
|
253
313
|
document.removeEventListener("click", this.#onDocumentClick);
|
|
254
314
|
clearTimeout(this.#typeaheadTimer);
|
|
255
315
|
this.#appliedValue = "";
|
|
@@ -459,6 +519,7 @@ var ThemePicker = class extends HTMLElement {
|
|
|
459
519
|
*/
|
|
460
520
|
#syncState() {
|
|
461
521
|
if (!this.#rootEl) return;
|
|
522
|
+
this.#syncTooltip();
|
|
462
523
|
const value = this.value;
|
|
463
524
|
if (this.#inputEl) this.#inputEl.value = value;
|
|
464
525
|
if (this.#buttonEl) {
|
|
@@ -496,20 +557,68 @@ var ThemePicker = class extends HTMLElement {
|
|
|
496
557
|
input.name = this.name;
|
|
497
558
|
input.value = this.value;
|
|
498
559
|
root.appendChild(input);
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
button.addEventListener("click", () => {
|
|
560
|
+
const iconButtonHost = document.createElement("lily-icon-button");
|
|
561
|
+
iconButtonHost.setAttribute("base-class", "theme-picker-button");
|
|
562
|
+
iconButtonHost.setAttribute("label", this.label);
|
|
563
|
+
iconButtonHost.setAttribute("aria-haspopup", "listbox");
|
|
564
|
+
iconButtonHost.setAttribute("aria-expanded", "false");
|
|
565
|
+
iconButtonHost.setAttribute("aria-controls", this.listId);
|
|
566
|
+
iconButtonHost.appendChild(this.renderButtonContent());
|
|
567
|
+
iconButtonHost.addEventListener("click", () => {
|
|
508
568
|
if (this.#open) this.closeList();
|
|
509
569
|
else this.openList();
|
|
510
570
|
});
|
|
511
|
-
|
|
512
|
-
root.appendChild(
|
|
571
|
+
iconButtonHost.addEventListener("keydown", this.#onButtonKeydown);
|
|
572
|
+
root.appendChild(iconButtonHost);
|
|
573
|
+
this.#resetTooltip();
|
|
574
|
+
const tooltip = document.createElement("div");
|
|
575
|
+
tooltip.className = "theme-picker-tooltip";
|
|
576
|
+
tooltip.setAttribute("role", "tooltip");
|
|
577
|
+
tooltip.id = this.tooltipId;
|
|
578
|
+
tooltip.setAttribute("hidden", "");
|
|
579
|
+
tooltip.textContent = this.label;
|
|
580
|
+
tooltip.addEventListener("mouseenter", () => {
|
|
581
|
+
this.#hoverTooltip = true;
|
|
582
|
+
this.#syncTooltip();
|
|
583
|
+
});
|
|
584
|
+
tooltip.addEventListener("mouseleave", () => {
|
|
585
|
+
this.#hoverTooltip = false;
|
|
586
|
+
this.#syncTooltip();
|
|
587
|
+
});
|
|
588
|
+
this.#tooltipEl = tooltip;
|
|
589
|
+
iconButtonHost.addEventListener("mouseenter", () => {
|
|
590
|
+
this.#hoverButton = true;
|
|
591
|
+
this.#tipDismissed = false;
|
|
592
|
+
this.#syncTooltip();
|
|
593
|
+
});
|
|
594
|
+
iconButtonHost.addEventListener("mouseleave", () => {
|
|
595
|
+
this.#hoverButton = false;
|
|
596
|
+
this.#syncTooltip();
|
|
597
|
+
});
|
|
598
|
+
iconButtonHost.addEventListener("click", () => {
|
|
599
|
+
this.#hoverButton = false;
|
|
600
|
+
this.#syncTooltip();
|
|
601
|
+
});
|
|
602
|
+
iconButtonHost.addEventListener("focusin", () => {
|
|
603
|
+
try {
|
|
604
|
+
this.#focusButton = this.#buttonEl?.matches(":focus-visible") ?? false;
|
|
605
|
+
} catch {
|
|
606
|
+
this.#focusButton = true;
|
|
607
|
+
}
|
|
608
|
+
this.#syncTooltip();
|
|
609
|
+
});
|
|
610
|
+
iconButtonHost.addEventListener("focusout", () => {
|
|
611
|
+
this.#focusButton = false;
|
|
612
|
+
this.#tipDismissed = false;
|
|
613
|
+
this.#syncTooltip();
|
|
614
|
+
});
|
|
615
|
+
iconButtonHost.addEventListener("keydown", (event) => {
|
|
616
|
+
if (event.key === "Escape" && this.#tooltipVisible()) {
|
|
617
|
+
this.#tipDismissed = true;
|
|
618
|
+
this.#syncTooltip();
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
root.appendChild(tooltip);
|
|
513
622
|
const list = document.createElement("ul");
|
|
514
623
|
list.className = "theme-picker-list";
|
|
515
624
|
list.id = this.listId;
|
|
@@ -533,10 +642,10 @@ var ThemePicker = class extends HTMLElement {
|
|
|
533
642
|
root.appendChild(list);
|
|
534
643
|
this.#rootEl = root;
|
|
535
644
|
this.#inputEl = input;
|
|
536
|
-
this.#buttonEl = button;
|
|
537
645
|
this.#listEl = list;
|
|
538
646
|
this.#optionEls = optionEls;
|
|
539
647
|
this.replaceChildren(root);
|
|
648
|
+
this.#buttonEl = iconButtonHost.querySelector("button");
|
|
540
649
|
}
|
|
541
650
|
};
|
|
542
651
|
function parseCsv(s) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lilydesignsystem/web-components-theme-picker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": "=26"
|
|
6
6
|
},
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"scripts": {
|
|
24
24
|
"prepublishOnly": "cd .. && npm run build"
|
|
25
25
|
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@lilydesignsystem/web-components-headless": "^0.1.0"
|
|
28
|
+
},
|
|
26
29
|
"keywords": [
|
|
27
30
|
"lily",
|
|
28
31
|
"design",
|