@akonwi/mica 0.7.0 → 0.8.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/combobox.js +52 -18
- package/package.json +29 -8
package/combobox.js
CHANGED
|
@@ -20,13 +20,16 @@ let uid = 0;
|
|
|
20
20
|
class MCombobox extends HTMLElement {
|
|
21
21
|
#input;
|
|
22
22
|
#list;
|
|
23
|
+
#datalist;
|
|
24
|
+
#listId;
|
|
23
25
|
#options = [];
|
|
24
26
|
#active = -1;
|
|
27
|
+
#observer;
|
|
25
28
|
|
|
26
29
|
connectedCallback() {
|
|
27
30
|
this.#input = this.querySelector("input");
|
|
28
|
-
|
|
29
|
-
if (!this.#input || !datalist) return;
|
|
31
|
+
this.#datalist = this.querySelector("datalist");
|
|
32
|
+
if (!this.#input || !this.#datalist) return;
|
|
30
33
|
|
|
31
34
|
// disable the native popup; the module takes over
|
|
32
35
|
this.#input.removeAttribute("list");
|
|
@@ -35,16 +38,49 @@ class MCombobox extends HTMLElement {
|
|
|
35
38
|
this.#input.setAttribute("aria-autocomplete", "list");
|
|
36
39
|
this.#input.autocomplete = "off";
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
this.#listId = `m-cb-${++uid}`;
|
|
39
42
|
this.#list = document.createElement("div");
|
|
40
|
-
this.#list.id = listId;
|
|
43
|
+
this.#list.id = this.#listId;
|
|
41
44
|
this.#list.setAttribute("role", "listbox");
|
|
42
45
|
this.#list.hidden = true;
|
|
43
46
|
|
|
44
|
-
this.#
|
|
47
|
+
this.#buildOptions();
|
|
48
|
+
this.append(this.#list);
|
|
49
|
+
this.#input.setAttribute("aria-controls", this.#listId);
|
|
50
|
+
|
|
51
|
+
// Options may be rendered asynchronously (a search-backed combobox):
|
|
52
|
+
// rebuild the listbox whenever the author's datalist changes.
|
|
53
|
+
this.#observer = new MutationObserver(() => {
|
|
54
|
+
this.#buildOptions();
|
|
55
|
+
if (document.activeElement === this.#input) this.#openAndFilter();
|
|
56
|
+
});
|
|
57
|
+
this.#observer.observe(this.#datalist, {
|
|
58
|
+
childList: true,
|
|
59
|
+
subtree: true,
|
|
60
|
+
attributes: true,
|
|
61
|
+
attributeFilter: ["value"],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
this.#input.addEventListener("input", (e) => {
|
|
65
|
+
// ignore our own dispatched events from #choose — they'd reopen
|
|
66
|
+
if (e.isTrusted) this.#openAndFilter();
|
|
67
|
+
});
|
|
68
|
+
this.#input.addEventListener("keydown", (e) => this.#onKey(e));
|
|
69
|
+
this.addEventListener("focusout", (e) => {
|
|
70
|
+
if (!this.contains(e.relatedTarget)) this.#close();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
disconnectedCallback() {
|
|
75
|
+
this.#observer?.disconnect();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#buildOptions() {
|
|
79
|
+
this.#list.replaceChildren();
|
|
80
|
+
this.#options = [...this.#datalist.options].map((o, i) => {
|
|
45
81
|
const d = document.createElement("div");
|
|
46
82
|
d.setAttribute("role", "option");
|
|
47
|
-
d.id = `${listId}-${i}`;
|
|
83
|
+
d.id = `${this.#listId}-${i}`;
|
|
48
84
|
d.textContent = o.value || o.textContent;
|
|
49
85
|
// pointerdown so the input never loses focus
|
|
50
86
|
d.addEventListener("pointerdown", (e) => {
|
|
@@ -58,17 +94,7 @@ class MCombobox extends HTMLElement {
|
|
|
58
94
|
this.#list.append(d);
|
|
59
95
|
return d;
|
|
60
96
|
});
|
|
61
|
-
this
|
|
62
|
-
this.#input.setAttribute("aria-controls", listId);
|
|
63
|
-
|
|
64
|
-
this.#input.addEventListener("input", (e) => {
|
|
65
|
-
// ignore our own dispatched events from #choose — they'd reopen
|
|
66
|
-
if (e.isTrusted) this.#openAndFilter();
|
|
67
|
-
});
|
|
68
|
-
this.#input.addEventListener("keydown", (e) => this.#onKey(e));
|
|
69
|
-
this.addEventListener("focusout", (e) => {
|
|
70
|
-
if (!this.contains(e.relatedTarget)) this.#close();
|
|
71
|
-
});
|
|
97
|
+
this.#active = -1;
|
|
72
98
|
}
|
|
73
99
|
|
|
74
100
|
#visible() {
|
|
@@ -127,7 +153,15 @@ class MCombobox extends HTMLElement {
|
|
|
127
153
|
}
|
|
128
154
|
|
|
129
155
|
#choose(option) {
|
|
130
|
-
|
|
156
|
+
// Write through the native prototype setter: frameworks that patch
|
|
157
|
+
// the value property to track controlled inputs (React) only observe
|
|
158
|
+
// the change when it lands on the native setter.
|
|
159
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
160
|
+
HTMLInputElement.prototype,
|
|
161
|
+
"value",
|
|
162
|
+
)?.set;
|
|
163
|
+
if (setter) setter.call(this.#input, option.textContent);
|
|
164
|
+
else this.#input.value = option.textContent;
|
|
131
165
|
this.#close();
|
|
132
166
|
this.#input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
133
167
|
this.#input.dispatchEvent(new Event("change", { bubbles: true }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akonwi/mica",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Custom elements. Native behavior. Nearly no JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -21,13 +21,34 @@
|
|
|
21
21
|
"exports": {
|
|
22
22
|
".": "./mica.css",
|
|
23
23
|
"./mica.css": "./mica.css",
|
|
24
|
-
"./invoker.js": {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"./
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
"./invoker.js": {
|
|
25
|
+
"types": "./invoker.d.ts",
|
|
26
|
+
"default": "./invoker.js"
|
|
27
|
+
},
|
|
28
|
+
"./drawer.js": {
|
|
29
|
+
"types": "./drawer.d.ts",
|
|
30
|
+
"default": "./drawer.js"
|
|
31
|
+
},
|
|
32
|
+
"./field.js": {
|
|
33
|
+
"types": "./field.d.ts",
|
|
34
|
+
"default": "./field.js"
|
|
35
|
+
},
|
|
36
|
+
"./select.js": {
|
|
37
|
+
"types": "./select.d.ts",
|
|
38
|
+
"default": "./select.js"
|
|
39
|
+
},
|
|
40
|
+
"./tabs.js": {
|
|
41
|
+
"types": "./tabs.d.ts",
|
|
42
|
+
"default": "./tabs.js"
|
|
43
|
+
},
|
|
44
|
+
"./toast.js": {
|
|
45
|
+
"types": "./toast.d.ts",
|
|
46
|
+
"default": "./toast.js"
|
|
47
|
+
},
|
|
48
|
+
"./combobox.js": {
|
|
49
|
+
"types": "./combobox.d.ts",
|
|
50
|
+
"default": "./combobox.js"
|
|
51
|
+
},
|
|
31
52
|
"./types/react": {
|
|
32
53
|
"types": "./types/react.d.ts"
|
|
33
54
|
}
|