@lilydesignsystem/web-components-theme-picker 0.1.1 → 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 CHANGED
@@ -77,6 +77,8 @@ declare function nextThemePickerId(): string;
77
77
  declare class ThemePicker extends HTMLElement {
78
78
  #private;
79
79
  static get observedAttributes(): string[];
80
+ /** id of the rendered `<div class="theme-picker-tooltip">`. */
81
+ get tooltipId(): string;
80
82
  get label(): string;
81
83
  set label(v: string);
82
84
  get themesUrl(): string;
package/dist/index.js CHANGED
@@ -59,6 +59,59 @@ var ThemePicker = class extends HTMLElement {
59
59
  #activeIndex = -1;
60
60
  // Stable ids for the button/listbox aria wiring.
61
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
+ }
62
115
  // Typeahead buffer: APG listbox behaviour. Reset after a pause.
63
116
  #typeahead = "";
64
117
  #typeaheadTimer;
@@ -256,6 +309,7 @@ var ThemePicker = class extends HTMLElement {
256
309
  }
257
310
  }
258
311
  disconnectedCallback() {
312
+ this.#resetTooltip();
259
313
  document.removeEventListener("click", this.#onDocumentClick);
260
314
  clearTimeout(this.#typeaheadTimer);
261
315
  this.#appliedValue = "";
@@ -465,6 +519,7 @@ var ThemePicker = class extends HTMLElement {
465
519
  */
466
520
  #syncState() {
467
521
  if (!this.#rootEl) return;
522
+ this.#syncTooltip();
468
523
  const value = this.value;
469
524
  if (this.#inputEl) this.#inputEl.value = value;
470
525
  if (this.#buttonEl) {
@@ -515,6 +570,55 @@ var ThemePicker = class extends HTMLElement {
515
570
  });
516
571
  iconButtonHost.addEventListener("keydown", this.#onButtonKeydown);
517
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);
518
622
  const list = document.createElement("ul");
519
623
  list.className = "theme-picker-list";
520
624
  list.id = this.listId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lilydesignsystem/web-components-theme-picker",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "engines": {
5
5
  "node": "=26"
6
6
  },