@dogsbay/elements 0.2.0-beta.14 → 0.2.0-beta.141

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.
Files changed (68) hide show
  1. package/dist/{base-BtvRtAsC.d.ts → base-D1Rz70fn.d.ts} +1 -0
  2. package/dist/chunk-2ZMWJ74J.js +233 -0
  3. package/dist/{chunk-7M7SFLKN.js → chunk-5KHESKQR.js} +4 -3
  4. package/dist/{chunk-TTNTPXBA.js → chunk-5WY3DHVB.js} +4 -3
  5. package/dist/chunk-6QAYHFIQ.js +115 -0
  6. package/dist/{chunk-LVXR7KTB.js → chunk-6ZKFQBCF.js} +40 -12
  7. package/dist/{chunk-5T4OXQA2.js → chunk-AVQ7KRRE.js} +5 -6
  8. package/dist/{chunk-FWAAU5UY.js → chunk-CD3G6HKT.js} +4 -3
  9. package/dist/chunk-EPHDSTM7.js +85 -0
  10. package/dist/chunk-GC5HL7FU.js +255 -0
  11. package/dist/chunk-HMNRE3FZ.js +382 -0
  12. package/dist/chunk-HMV62CBN.js +139 -0
  13. package/dist/{chunk-7BIVQSGP.js → chunk-JRAYNKQ6.js} +4 -3
  14. package/dist/chunk-KFY2MTR3.js +190 -0
  15. package/dist/chunk-KXCPLIQD.js +77 -0
  16. package/dist/chunk-MZDGSTI4.js +72 -0
  17. package/dist/chunk-RZ3KZOM3.js +207 -0
  18. package/dist/{chunk-LFXZQ4YV.js → chunk-US4H4ZPZ.js} +4 -3
  19. package/dist/db-accordion.d.ts +1 -1
  20. package/dist/db-accordion.js +2 -3
  21. package/dist/db-card.d.ts +1 -1
  22. package/dist/db-card.js +2 -3
  23. package/dist/db-code-block.d.ts +1 -1
  24. package/dist/db-code-block.js +2 -3
  25. package/dist/db-collapsible.d.ts +1 -1
  26. package/dist/db-collapsible.js +2 -3
  27. package/dist/db-combobox.d.ts +70 -0
  28. package/dist/db-combobox.js +8 -0
  29. package/dist/db-details-sheet.d.ts +53 -0
  30. package/dist/db-details-sheet.js +8 -0
  31. package/dist/db-dialog.d.ts +62 -0
  32. package/dist/db-dialog.js +8 -0
  33. package/dist/db-link-button.d.ts +1 -1
  34. package/dist/db-link-button.js +2 -3
  35. package/dist/db-nav-tree.d.ts +83 -0
  36. package/dist/db-nav-tree.js +8 -0
  37. package/dist/db-popover.d.ts +42 -0
  38. package/dist/db-popover.js +8 -0
  39. package/dist/db-select.d.ts +72 -0
  40. package/dist/db-select.js +8 -0
  41. package/dist/db-steps.d.ts +1 -1
  42. package/dist/db-steps.js +2 -3
  43. package/dist/db-tabs.d.ts +28 -1
  44. package/dist/db-tabs.js +2 -3
  45. package/dist/db-toc.d.ts +32 -0
  46. package/dist/db-toc.js +8 -0
  47. package/dist/db-tooltip.d.ts +48 -0
  48. package/dist/db-tooltip.js +8 -0
  49. package/dist/index.d.ts +9 -1
  50. package/dist/index.js +44 -12
  51. package/package.json +38 -6
  52. package/dist/chunk-5T4OXQA2.js.map +0 -1
  53. package/dist/chunk-7BIVQSGP.js.map +0 -1
  54. package/dist/chunk-7M7SFLKN.js.map +0 -1
  55. package/dist/chunk-FWAAU5UY.js.map +0 -1
  56. package/dist/chunk-LFXZQ4YV.js.map +0 -1
  57. package/dist/chunk-LVXR7KTB.js.map +0 -1
  58. package/dist/chunk-TTNTPXBA.js.map +0 -1
  59. package/dist/chunk-XBMLE4N3.js +0 -28
  60. package/dist/chunk-XBMLE4N3.js.map +0 -1
  61. package/dist/db-accordion.js.map +0 -1
  62. package/dist/db-card.js.map +0 -1
  63. package/dist/db-code-block.js.map +0 -1
  64. package/dist/db-collapsible.js.map +0 -1
  65. package/dist/db-link-button.js.map +0 -1
  66. package/dist/db-steps.js.map +0 -1
  67. package/dist/db-tabs.js.map +0 -1
  68. package/dist/index.js.map +0 -1
@@ -0,0 +1,255 @@
1
+ import {
2
+ isDisabled,
3
+ ownOne,
4
+ uid
5
+ } from "./chunk-KXCPLIQD.js";
6
+ import {
7
+ DogsbayElement,
8
+ ensureDefaultDisplay
9
+ } from "./chunk-MZDGSTI4.js";
10
+
11
+ // src/db-combobox.ts
12
+ var DbCombobox = class extends DogsbayElement {
13
+ input = null;
14
+ listbox = null;
15
+ empty = null;
16
+ _open = false;
17
+ highlighted = null;
18
+ observer = null;
19
+ base = uid("db-combobox");
20
+ optionSeq = 0;
21
+ get open() {
22
+ return this._open;
23
+ }
24
+ get inline() {
25
+ return this.hasAttribute("inline");
26
+ }
27
+ get filtering() {
28
+ return (this.getAttribute("filter") ?? "contains") !== "none";
29
+ }
30
+ /** All options, in order. Read live, so page-supplied results are always current. */
31
+ options() {
32
+ if (!this.listbox) return [];
33
+ return Array.from(this.listbox.querySelectorAll("[data-part='option']")).filter((o) => o.closest("db-combobox") === this);
34
+ }
35
+ visible() {
36
+ return this.options().filter((o) => !o.hidden && !isDisabled(o));
37
+ }
38
+ setup() {
39
+ this.input = ownOne(this, "[data-part='input']");
40
+ this.listbox = ownOne(this, "[data-part='listbox']");
41
+ this.empty = ownOne(this, "[data-part='empty']");
42
+ if (!this.input || !this.listbox) return;
43
+ const { input, listbox } = this;
44
+ listbox.id ||= `${this.base}-listbox`;
45
+ listbox.setAttribute("role", "listbox");
46
+ input.setAttribute("role", "combobox");
47
+ input.setAttribute("aria-autocomplete", "list");
48
+ input.setAttribute("aria-controls", listbox.id);
49
+ input.setAttribute("autocomplete", "off");
50
+ if (!this.inline) listbox.hidden = true;
51
+ this.prepareOptions();
52
+ this.syncExpanded();
53
+ }
54
+ prepareOptions() {
55
+ this.options().forEach((o) => {
56
+ o.id ||= `${this.base}-option-${++this.optionSeq}`;
57
+ o.setAttribute("role", "option");
58
+ if (!o.hasAttribute("aria-selected")) o.setAttribute("aria-selected", "false");
59
+ if (o.matches("a[href], button")) o.setAttribute("tabindex", "-1");
60
+ o.querySelectorAll("a, button").forEach((el) => el.setAttribute("tabindex", "-1"));
61
+ if (o.parentElement && o.parentElement !== this.listbox && o.parentElement.localName === "li" && !o.parentElement.hasAttribute("role")) {
62
+ o.parentElement.setAttribute("role", "none");
63
+ }
64
+ });
65
+ }
66
+ onInput = () => {
67
+ if (this.filtering) this.filter(this.input.value);
68
+ else this.show();
69
+ };
70
+ onFocus = () => {
71
+ if (this.hasAttribute("open-on-focus") || this.filtering && this.input.value) {
72
+ if (this.filtering) this.filter(this.input.value);
73
+ else this.show();
74
+ }
75
+ };
76
+ onKey = (e) => {
77
+ const vis = this.visible();
78
+ const cur = this.highlighted ? vis.indexOf(this.highlighted) : -1;
79
+ switch (e.key) {
80
+ case "ArrowDown":
81
+ e.preventDefault();
82
+ if (!this._open) this.show();
83
+ if (vis.length) this.highlight(vis[cur < 0 ? 0 : Math.min(cur + 1, vis.length - 1)]);
84
+ break;
85
+ case "ArrowUp":
86
+ e.preventDefault();
87
+ if (!this._open) this.show();
88
+ if (vis.length) this.highlight(vis[cur < 0 ? vis.length - 1 : Math.max(cur - 1, 0)]);
89
+ break;
90
+ case "Home":
91
+ case "End":
92
+ if (this._open && this.highlighted && vis.length) {
93
+ e.preventDefault();
94
+ this.highlight(e.key === "Home" ? vis[0] : vis[vis.length - 1]);
95
+ }
96
+ break;
97
+ case "Enter":
98
+ if (this._open && this.highlighted) {
99
+ e.preventDefault();
100
+ this.choose(this.highlighted);
101
+ }
102
+ break;
103
+ case "Escape":
104
+ if (this._open && !this.inline) {
105
+ e.preventDefault();
106
+ e.stopPropagation();
107
+ this.hide();
108
+ } else if (this.input.value) {
109
+ e.preventDefault();
110
+ e.stopPropagation();
111
+ this.input.value = "";
112
+ this.input.dispatchEvent(new Event("input", { bubbles: true }));
113
+ this.hide();
114
+ }
115
+ break;
116
+ case "Tab":
117
+ if (!this.inline) this.hide();
118
+ break;
119
+ }
120
+ };
121
+ onListClick = (e) => {
122
+ const o = e.target.closest("[data-part='option']");
123
+ if (!o || o.closest("db-combobox") !== this || isDisabled(o)) return;
124
+ const link = e.target.closest("a[href]");
125
+ if (!this.choose(o, !link) && link) e.preventDefault();
126
+ };
127
+ onListPointer = (e) => {
128
+ const o = e.target.closest("[data-part='option']");
129
+ if (o && o.closest("db-combobox") === this && !isDisabled(o) && !o.hidden) this.highlight(o);
130
+ };
131
+ onListDown = (e) => e.preventDefault();
132
+ // keep focus in the input
133
+ onDocDown = (e) => {
134
+ if (this._open && !this.inline && !this.contains(e.target)) this.hide();
135
+ };
136
+ connect() {
137
+ const { input, listbox } = this;
138
+ if (!input || !listbox) return;
139
+ input.addEventListener("input", this.onInput);
140
+ input.addEventListener("focus", this.onFocus);
141
+ input.addEventListener("keydown", this.onKey);
142
+ listbox.addEventListener("click", this.onListClick);
143
+ listbox.addEventListener("pointermove", this.onListPointer);
144
+ listbox.addEventListener("mousedown", this.onListDown);
145
+ document.addEventListener("pointerdown", this.onDocDown);
146
+ if (typeof MutationObserver !== "undefined") {
147
+ this.observer = new MutationObserver(() => this.onOptionsChanged());
148
+ this.observer.observe(listbox, { childList: true, subtree: true });
149
+ }
150
+ }
151
+ disconnect() {
152
+ const { input, listbox } = this;
153
+ input?.removeEventListener("input", this.onInput);
154
+ input?.removeEventListener("focus", this.onFocus);
155
+ input?.removeEventListener("keydown", this.onKey);
156
+ listbox?.removeEventListener("click", this.onListClick);
157
+ listbox?.removeEventListener("pointermove", this.onListPointer);
158
+ listbox?.removeEventListener("mousedown", this.onListDown);
159
+ document.removeEventListener("pointerdown", this.onDocDown);
160
+ this.observer?.disconnect();
161
+ this.observer = null;
162
+ }
163
+ /** Re-scan options after the page replaced them (filter="none"). */
164
+ refresh() {
165
+ this.onOptionsChanged();
166
+ }
167
+ onOptionsChanged() {
168
+ this.prepareOptions();
169
+ if (this.highlighted && !this.options().includes(this.highlighted)) this.clearHighlight();
170
+ const vis = this.visible();
171
+ if (!this.filtering) {
172
+ if (vis.length || this.inline) this.show();
173
+ else this.hide();
174
+ if (vis.length && !this.highlighted && !this.hasAttribute("no-autohighlight")) this.highlight(vis[0]);
175
+ }
176
+ this.syncExpanded();
177
+ this.dispatchEvent(new CustomEvent("db-combobox:update", { detail: { count: vis.length }, bubbles: true }));
178
+ }
179
+ filter(query) {
180
+ const q = query.toLowerCase().trim();
181
+ let count = 0;
182
+ for (const o of this.options()) {
183
+ const hit = !q || (o.textContent ?? "").toLowerCase().includes(q) || (o.dataset.value ?? "").toLowerCase().includes(q);
184
+ o.hidden = !hit;
185
+ if (hit) count++;
186
+ }
187
+ if (this.empty) this.empty.hidden = count > 0 || !q;
188
+ this.clearHighlight();
189
+ if (count || q && this.empty) this.show();
190
+ else this.hide();
191
+ const vis = this.visible();
192
+ if (vis.length && !this.hasAttribute("no-autohighlight")) this.highlight(vis[0]);
193
+ this.dispatchEvent(new CustomEvent("db-combobox:update", { detail: { count }, bubbles: true }));
194
+ }
195
+ syncExpanded() {
196
+ if (!this.input) return;
197
+ const expanded = this.inline ? this.visible().length > 0 : this._open;
198
+ this.input.setAttribute("aria-expanded", String(expanded));
199
+ this.setAttribute("data-state", expanded ? "open" : "closed");
200
+ }
201
+ show() {
202
+ if (!this.listbox || this._open) return this.syncExpanded();
203
+ this._open = true;
204
+ this.listbox.hidden = false;
205
+ this.syncExpanded();
206
+ }
207
+ hide() {
208
+ if (!this.listbox) return;
209
+ this.clearHighlight();
210
+ if (!this._open) return this.syncExpanded();
211
+ this._open = false;
212
+ if (!this.inline) this.listbox.hidden = true;
213
+ this.syncExpanded();
214
+ }
215
+ highlight(o) {
216
+ if (!this.input) return;
217
+ this.highlighted?.removeAttribute("data-highlighted");
218
+ this.highlighted?.setAttribute("aria-selected", "false");
219
+ this.highlighted = o;
220
+ o.setAttribute("data-highlighted", "");
221
+ o.setAttribute("aria-selected", "true");
222
+ this.input.setAttribute("aria-activedescendant", o.id);
223
+ o.scrollIntoView?.({ block: "nearest" });
224
+ }
225
+ clearHighlight() {
226
+ this.highlighted?.removeAttribute("data-highlighted");
227
+ this.highlighted?.setAttribute("aria-selected", "false");
228
+ this.highlighted = null;
229
+ this.input?.removeAttribute("aria-activedescendant");
230
+ }
231
+ /** Returns false when a `db-combobox:select` listener cancelled the choice. */
232
+ choose(o, followLink = true) {
233
+ const value = o.dataset.value ?? "";
234
+ const text = o.textContent?.trim() ?? value;
235
+ const ev = new CustomEvent("db-combobox:select", { detail: { value, text, option: o }, bubbles: true, cancelable: true });
236
+ if (!this.dispatchEvent(ev)) return false;
237
+ const link = o.matches("a[href]") ? o : o.querySelector("a[href]");
238
+ if (link) {
239
+ if (followLink) link.click();
240
+ return true;
241
+ }
242
+ if (this.input) this.input.value = o.getAttribute("data-label") ?? text;
243
+ if (!this.inline) this.hide();
244
+ this.dispatchEvent(new CustomEvent("db-combobox:change", { detail: { value, text }, bubbles: true }));
245
+ return true;
246
+ }
247
+ };
248
+ ensureDefaultDisplay();
249
+ if (!customElements.get("db-combobox")) {
250
+ customElements.define("db-combobox", DbCombobox);
251
+ }
252
+
253
+ export {
254
+ DbCombobox
255
+ };
@@ -0,0 +1,382 @@
1
+ import {
2
+ hideFloating,
3
+ isDisabled,
4
+ own,
5
+ ownOne,
6
+ showFloating,
7
+ uid
8
+ } from "./chunk-KXCPLIQD.js";
9
+ import {
10
+ DogsbayElement,
11
+ ensureDefaultDisplay
12
+ } from "./chunk-MZDGSTI4.js";
13
+
14
+ // src/db-select.ts
15
+ import { DismissController } from "@dogsbay/primitives/dismiss";
16
+ import { setupFloating } from "@dogsbay/primitives/floating";
17
+ import { Typeahead } from "@dogsbay/primitives/typeahead";
18
+ var DbSelect = class extends DogsbayElement {
19
+ trigger = null;
20
+ content = null;
21
+ valueEl = null;
22
+ items = [];
23
+ nativeSelect = null;
24
+ hiddenInput = null;
25
+ labelEl = null;
26
+ onLabelClick = (e) => {
27
+ if (e.target.closest("[data-part='trigger']")) return;
28
+ e.preventDefault();
29
+ this.trigger?.focus();
30
+ };
31
+ dismiss = null;
32
+ typeahead = null;
33
+ cleanupFloating = null;
34
+ _open = false;
35
+ _value = "";
36
+ highlighted = -1;
37
+ get open() {
38
+ return this._open;
39
+ }
40
+ get value() {
41
+ return this._value;
42
+ }
43
+ set value(v) {
44
+ const item = this.items.find((i) => i.dataset.value === v);
45
+ if (item) this.select(item, false);
46
+ }
47
+ /** Disabled when the host or the enhanced native select is. */
48
+ get isDisabled() {
49
+ return this.hasAttribute("disabled") || !!this.nativeSelect?.disabled;
50
+ }
51
+ syncing = false;
52
+ /** Re-read the native select's value (after script changes it or a form reset). */
53
+ refresh() {
54
+ const sel = this.nativeSelect;
55
+ if (!sel) return;
56
+ const item = this.items.find((i) => i.dataset.value === sel.value);
57
+ if (item) this.select(item, false);
58
+ this.trigger?.setAttribute("aria-disabled", String(this.isDisabled));
59
+ }
60
+ onNativeChange = () => {
61
+ if (!this.syncing) this.refresh();
62
+ };
63
+ onFormReset = () => setTimeout(() => this.refresh());
64
+ enabled() {
65
+ return this.items.filter((i) => !isDisabled(i));
66
+ }
67
+ setup() {
68
+ this.nativeSelect = ownOne(this, "select");
69
+ if (this.nativeSelect && !ownOne(this, "[data-part='trigger']")) this.buildFromNative(this.nativeSelect);
70
+ this.trigger = ownOne(this, "[data-part='trigger']");
71
+ this.content = ownOne(this, "[data-part='content']");
72
+ this.valueEl = ownOne(this, "[data-part='value']");
73
+ if (!this.trigger || !this.content) return;
74
+ const { trigger, content } = this;
75
+ this.items = own(this, "[data-part='item']");
76
+ const base = uid("db-select");
77
+ content.id ||= `${base}-listbox`;
78
+ content.setAttribute("role", "listbox");
79
+ content.hidden = true;
80
+ trigger.setAttribute("role", "combobox");
81
+ trigger.setAttribute("aria-haspopup", "listbox");
82
+ trigger.setAttribute("aria-expanded", "false");
83
+ trigger.setAttribute("aria-controls", content.id);
84
+ if (trigger instanceof HTMLButtonElement && !trigger.hasAttribute("type")) trigger.type = "button";
85
+ if (this.isDisabled) trigger.setAttribute("aria-disabled", "true");
86
+ this.items.forEach((item, i) => {
87
+ item.id ||= `${base}-item-${i}`;
88
+ item.setAttribute("role", "option");
89
+ item.setAttribute("aria-selected", "false");
90
+ });
91
+ own(this, "[data-part='group']").forEach((group) => {
92
+ group.setAttribute("role", "group");
93
+ const label = group.querySelector("[data-part='label']");
94
+ if (label) {
95
+ label.id ||= uid("db-select-label");
96
+ group.setAttribute("aria-labelledby", label.id);
97
+ }
98
+ });
99
+ const name = this.getAttribute("name");
100
+ if (name && !this.nativeSelect) {
101
+ this.hiddenInput = document.createElement("input");
102
+ this.hiddenInput.type = "hidden";
103
+ this.hiddenInput.name = name;
104
+ if (this.hasAttribute("required")) this.hiddenInput.required = true;
105
+ this.appendChild(this.hiddenInput);
106
+ }
107
+ this.typeahead = new Typeahead({
108
+ getItemCount: () => this.enabled().length,
109
+ getItemText: (i) => this.enabled()[i]?.textContent?.trim() ?? "",
110
+ onMatch: (i) => {
111
+ const item = this.enabled()[i];
112
+ if (!item) return;
113
+ if (this._open) this.highlight(this.items.indexOf(item));
114
+ else this.select(item);
115
+ }
116
+ });
117
+ this.dismiss = new DismissController(content, { onDismiss: (reason) => this.close(reason !== "outside-press") });
118
+ const initial = this.nativeSelect?.value ?? this.getAttribute("default-value") ?? "";
119
+ const initialItem = this.items.find((i) => i.dataset.value === initial);
120
+ if (initialItem && isDisabled(initialItem)) {
121
+ if (this.valueEl) {
122
+ this.valueEl.textContent = initialItem.textContent?.trim() ?? "";
123
+ this.valueEl.setAttribute("data-placeholder", "");
124
+ }
125
+ } else if (initialItem) this.select(initialItem, false);
126
+ this.setState("closed");
127
+ }
128
+ /** Build the trigger and listbox from a native <select>, and hide it. */
129
+ buildFromNative(sel) {
130
+ const trigger = document.createElement("button");
131
+ trigger.type = "button";
132
+ trigger.setAttribute("data-part", "trigger");
133
+ trigger.className = "db-select-trigger";
134
+ const label = sel.getAttribute("aria-label");
135
+ let labelledBy = sel.getAttribute("aria-labelledby");
136
+ const forLabel = !labelledBy && sel.id ? document.querySelector(`label[for="${CSS.escape(sel.id)}"]`) : null;
137
+ if (forLabel) labelledBy = forLabel.id ||= uid("db-select-for");
138
+ if (label) trigger.setAttribute("aria-label", label);
139
+ if (labelledBy) trigger.setAttribute("aria-labelledby", labelledBy);
140
+ const wrap = sel.closest("label");
141
+ if (wrap) {
142
+ this.labelEl = wrap;
143
+ if (!label && !labelledBy) {
144
+ const text = Array.from(wrap.childNodes).filter((n) => !(n instanceof Element && (n === sel || n.contains(sel) || n.localName === "db-select"))).map((n) => n.textContent ?? "").join(" ").replace(/\s+/g, " ").trim();
145
+ if (text) trigger.setAttribute("aria-label", text);
146
+ }
147
+ } else if (forLabel) this.labelEl = forLabel;
148
+ const value = document.createElement("span");
149
+ value.setAttribute("data-part", "value");
150
+ value.className = "db-select-value";
151
+ const chevron = document.createElementNS("http://www.w3.org/2000/svg", "svg");
152
+ chevron.setAttribute("viewBox", "0 0 24 24");
153
+ chevron.setAttribute("aria-hidden", "true");
154
+ chevron.setAttribute("class", "db-select-chevron");
155
+ chevron.innerHTML = '<path d="m6 9 6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>';
156
+ trigger.append(value, chevron);
157
+ const list = document.createElement("div");
158
+ list.setAttribute("data-part", "content");
159
+ list.className = "db-select-content";
160
+ const addOption = (o, parent) => {
161
+ const item = document.createElement("div");
162
+ item.setAttribute("data-part", "item");
163
+ item.className = "db-select-item";
164
+ item.dataset.value = o.value;
165
+ for (const [k, v] of Object.entries(o.dataset)) if (v != null) item.dataset[k] = v;
166
+ if (o.disabled) item.setAttribute("aria-disabled", "true");
167
+ item.textContent = o.textContent ?? o.value;
168
+ parent.appendChild(item);
169
+ };
170
+ for (const child of Array.from(sel.children)) {
171
+ if (child instanceof HTMLOptGroupElement) {
172
+ const group = document.createElement("div");
173
+ group.setAttribute("data-part", "group");
174
+ const gl = document.createElement("div");
175
+ gl.setAttribute("data-part", "label");
176
+ gl.className = "db-select-label";
177
+ gl.textContent = child.label;
178
+ group.appendChild(gl);
179
+ for (const o of Array.from(child.children)) if (o instanceof HTMLOptionElement) addOption(o, group);
180
+ list.appendChild(group);
181
+ } else if (child instanceof HTMLOptionElement) {
182
+ addOption(child, list);
183
+ }
184
+ }
185
+ sel.hidden = true;
186
+ sel.tabIndex = -1;
187
+ sel.setAttribute("aria-hidden", "true");
188
+ this.append(trigger, list);
189
+ }
190
+ onTriggerClick = () => {
191
+ if (this.isDisabled) return;
192
+ if (this._open) this.close();
193
+ else this.show();
194
+ };
195
+ // Firefox fires click on keyup of Space even after keydown was cancelled; cancel it
196
+ // there too so Space does not toggle twice.
197
+ onTriggerKeyUp = (e) => {
198
+ if (e.key === " ") e.preventDefault();
199
+ };
200
+ onTriggerKey = (e) => {
201
+ if (this.isDisabled) return;
202
+ const enabled = this.enabled();
203
+ if (!this._open) {
204
+ if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Enter" || e.key === " " || e.altKey && e.key === "ArrowDown") {
205
+ e.preventDefault();
206
+ this.show();
207
+ if (e.key === "ArrowUp" && enabled.length) this.highlight(this.items.indexOf(enabled[enabled.length - 1]));
208
+ } else if (e.key === "Home" || e.key === "End") {
209
+ e.preventDefault();
210
+ this.show();
211
+ const t = e.key === "Home" ? enabled[0] : enabled[enabled.length - 1];
212
+ if (t) this.highlight(this.items.indexOf(t));
213
+ } else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
214
+ this.typeahead?.handle(e.key);
215
+ }
216
+ return;
217
+ }
218
+ if (!enabled.length) return;
219
+ const cur = this.highlighted >= 0 ? enabled.indexOf(this.items[this.highlighted]) : -1;
220
+ const go = (i) => {
221
+ e.preventDefault();
222
+ this.highlight(this.items.indexOf(enabled[i]));
223
+ };
224
+ switch (e.key) {
225
+ case "ArrowDown":
226
+ return go(Math.min(cur + 1, enabled.length - 1));
227
+ case "ArrowUp":
228
+ if (e.altKey) {
229
+ e.preventDefault();
230
+ if (this.highlighted >= 0) this.select(this.items[this.highlighted]);
231
+ return this.close();
232
+ }
233
+ return go(Math.max(cur - 1, 0));
234
+ case "Home":
235
+ return go(0);
236
+ case "End":
237
+ return go(enabled.length - 1);
238
+ case "PageDown":
239
+ return go(Math.min(cur + 10, enabled.length - 1));
240
+ case "PageUp":
241
+ return go(Math.max(cur - 10, 0));
242
+ case "Enter":
243
+ case " ":
244
+ e.preventDefault();
245
+ if (this.highlighted >= 0) this.select(this.items[this.highlighted]);
246
+ return this.close();
247
+ case "Tab":
248
+ if (this.highlighted >= 0) this.select(this.items[this.highlighted]);
249
+ return this.close(false);
250
+ default:
251
+ if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) this.typeahead?.handle(e.key);
252
+ }
253
+ };
254
+ onListClick = (e) => {
255
+ const item = e.target.closest("[data-part='item']");
256
+ if (!item || !this.items.includes(item) || isDisabled(item)) return;
257
+ this.select(item);
258
+ this.close();
259
+ };
260
+ onListPointer = (e) => {
261
+ const item = e.target.closest("[data-part='item']");
262
+ if (item && this.items.includes(item) && !isDisabled(item)) this.highlight(this.items.indexOf(item));
263
+ };
264
+ connect() {
265
+ this.trigger?.addEventListener("click", this.onTriggerClick);
266
+ this.trigger?.addEventListener("keydown", this.onTriggerKey);
267
+ this.trigger?.addEventListener("keyup", this.onTriggerKeyUp);
268
+ this.nativeSelect?.addEventListener("change", this.onNativeChange);
269
+ this.nativeSelect?.form?.addEventListener("reset", this.onFormReset);
270
+ this.labelEl?.addEventListener("click", this.onLabelClick);
271
+ this.content?.addEventListener("click", this.onListClick);
272
+ this.content?.addEventListener("pointermove", this.onListPointer);
273
+ this.content?.addEventListener("mousedown", this.preventBlur);
274
+ }
275
+ disconnect() {
276
+ this.trigger?.removeEventListener("click", this.onTriggerClick);
277
+ this.trigger?.removeEventListener("keydown", this.onTriggerKey);
278
+ this.trigger?.removeEventListener("keyup", this.onTriggerKeyUp);
279
+ this.nativeSelect?.removeEventListener("change", this.onNativeChange);
280
+ this.nativeSelect?.form?.removeEventListener("reset", this.onFormReset);
281
+ this.labelEl?.removeEventListener("click", this.onLabelClick);
282
+ this.content?.removeEventListener("click", this.onListClick);
283
+ this.content?.removeEventListener("pointermove", this.onListPointer);
284
+ this.content?.removeEventListener("mousedown", this.preventBlur);
285
+ if (this._open) this.close(false);
286
+ }
287
+ preventBlur = (e) => e.preventDefault();
288
+ setState(state) {
289
+ this.setAttribute("data-state", state);
290
+ this.content?.setAttribute("data-state", state);
291
+ }
292
+ show() {
293
+ const { trigger, content } = this;
294
+ if (!trigger || !content || this._open) return;
295
+ this._open = true;
296
+ const layer = showFloating(content);
297
+ this.setState("open");
298
+ trigger.setAttribute("aria-expanded", "true");
299
+ this.cleanupFloating = setupFloating(trigger, content, {
300
+ placement: "bottom-start",
301
+ offset: 4,
302
+ matchWidth: !this.hasAttribute("auto-width"),
303
+ strategy: layer === "top-layer" ? "fixed" : "absolute"
304
+ });
305
+ const selected = this.items.find((i) => i.dataset.value === this._value && !isDisabled(i));
306
+ const first = selected ?? this.enabled()[0];
307
+ if (first) this.highlight(this.items.indexOf(first));
308
+ this.dismiss?.activate();
309
+ this.typeahead?.reset();
310
+ trigger.focus();
311
+ }
312
+ /** Close the listbox. `refocus` is false for outside presses, which keep focus where the user clicked. */
313
+ close(refocus = true) {
314
+ const { trigger, content } = this;
315
+ if (!trigger || !content || !this._open) return;
316
+ this._open = false;
317
+ this.dismiss?.deactivate();
318
+ this.cleanupFloating?.();
319
+ this.cleanupFloating = null;
320
+ hideFloating(content);
321
+ this.setState("closed");
322
+ trigger.setAttribute("aria-expanded", "false");
323
+ trigger.removeAttribute("aria-activedescendant");
324
+ this.items.forEach((i) => i.removeAttribute("data-highlighted"));
325
+ this.highlighted = -1;
326
+ this.typeahead?.reset();
327
+ if (refocus) trigger.focus();
328
+ }
329
+ highlight(index) {
330
+ const item = this.items[index];
331
+ const content = this.content;
332
+ if (!item || !content || !this.trigger) return;
333
+ if (this.highlighted >= 0) this.items[this.highlighted]?.removeAttribute("data-highlighted");
334
+ this.highlighted = index;
335
+ item.setAttribute("data-highlighted", "");
336
+ this.trigger.setAttribute("aria-activedescendant", item.id);
337
+ const top = item.offsetTop;
338
+ const bottom = top + item.offsetHeight;
339
+ if (top < content.scrollTop) content.scrollTop = top;
340
+ else if (bottom > content.scrollTop + content.clientHeight) content.scrollTop = bottom - content.clientHeight;
341
+ }
342
+ select(item, notify = true) {
343
+ if (isDisabled(item)) return;
344
+ const value = item.dataset.value ?? "";
345
+ const changed = value !== this._value;
346
+ this._value = value;
347
+ for (const i of this.items) {
348
+ const on = i === item;
349
+ i.setAttribute("aria-selected", String(on));
350
+ if (on) i.setAttribute("data-state", "checked");
351
+ else i.removeAttribute("data-state");
352
+ }
353
+ if (this.valueEl) {
354
+ this.valueEl.textContent = item.textContent?.trim() ?? "";
355
+ this.valueEl.removeAttribute("data-placeholder");
356
+ }
357
+ if (this.hiddenInput) this.hiddenInput.value = value;
358
+ if (!notify || !changed) {
359
+ if (this.nativeSelect) this.nativeSelect.value = value;
360
+ return;
361
+ }
362
+ if (this.nativeSelect) {
363
+ this.nativeSelect.value = value;
364
+ this.syncing = true;
365
+ try {
366
+ this.nativeSelect.dispatchEvent(new Event("input", { bubbles: true }));
367
+ this.nativeSelect.dispatchEvent(new Event("change", { bubbles: true }));
368
+ } finally {
369
+ this.syncing = false;
370
+ }
371
+ }
372
+ this.dispatchEvent(new CustomEvent("db-select:change", { detail: { value }, bubbles: true }));
373
+ }
374
+ };
375
+ ensureDefaultDisplay();
376
+ if (!customElements.get("db-select")) {
377
+ customElements.define("db-select", DbSelect);
378
+ }
379
+
380
+ export {
381
+ DbSelect
382
+ };