@substrate-system/select 0.0.0-stage → 0.0.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/LICENSE +168 -0
- package/README.md +312 -2
- package/dist/index.cjs +405 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.css +146 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +376 -0
- package/dist/index.js.map +7 -0
- package/dist/index.min.css +1 -0
- package/dist/index.min.js +2 -0
- package/dist/index.min.js.map +7 -0
- package/dist/meta.json +42 -0
- package/package.json +88 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var index_exports = {};
|
|
31
|
+
__export(index_exports, {
|
|
32
|
+
ARIA_ATTRIBUTES: () => ARIA_ATTRIBUTES,
|
|
33
|
+
SubstrateSelect: () => SubstrateSelect
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
var import_web_component = require("@substrate-system/web-component");
|
|
37
|
+
var import_debug = __toESM(require("@substrate-system/debug"), 1);
|
|
38
|
+
const debug = (0, import_debug.default)("substrate-select");
|
|
39
|
+
const ARIA_ATTRIBUTES = [
|
|
40
|
+
"aria-activedescendant",
|
|
41
|
+
"aria-atomic",
|
|
42
|
+
"aria-autocomplete",
|
|
43
|
+
"aria-braillelabel",
|
|
44
|
+
"aria-brailleroledescription",
|
|
45
|
+
"aria-busy",
|
|
46
|
+
"aria-checked",
|
|
47
|
+
"aria-colcount",
|
|
48
|
+
"aria-colindex",
|
|
49
|
+
"aria-colindextext",
|
|
50
|
+
"aria-colspan",
|
|
51
|
+
"aria-controls",
|
|
52
|
+
"aria-current",
|
|
53
|
+
"aria-describedby",
|
|
54
|
+
"aria-description",
|
|
55
|
+
"aria-details",
|
|
56
|
+
"aria-disabled",
|
|
57
|
+
"aria-dropeffect",
|
|
58
|
+
"aria-errormessage",
|
|
59
|
+
"aria-expanded",
|
|
60
|
+
"aria-flowto",
|
|
61
|
+
"aria-grabbed",
|
|
62
|
+
"aria-haspopup",
|
|
63
|
+
"aria-hidden",
|
|
64
|
+
"aria-invalid",
|
|
65
|
+
"aria-keyshortcuts",
|
|
66
|
+
"aria-label",
|
|
67
|
+
"aria-labelledby",
|
|
68
|
+
"aria-level",
|
|
69
|
+
"aria-live",
|
|
70
|
+
"aria-modal",
|
|
71
|
+
"aria-multiline",
|
|
72
|
+
"aria-multiselectable",
|
|
73
|
+
"aria-orientation",
|
|
74
|
+
"aria-owns",
|
|
75
|
+
"aria-placeholder",
|
|
76
|
+
"aria-posinset",
|
|
77
|
+
"aria-pressed",
|
|
78
|
+
"aria-readonly",
|
|
79
|
+
"aria-relevant",
|
|
80
|
+
"aria-required",
|
|
81
|
+
"aria-roledescription",
|
|
82
|
+
"aria-rowcount",
|
|
83
|
+
"aria-rowindex",
|
|
84
|
+
"aria-rowindextext",
|
|
85
|
+
"aria-rowspan",
|
|
86
|
+
"aria-selected",
|
|
87
|
+
"aria-setsize",
|
|
88
|
+
"aria-sort",
|
|
89
|
+
"aria-valuemax",
|
|
90
|
+
"aria-valuemin",
|
|
91
|
+
"aria-valuenow",
|
|
92
|
+
"aria-valuetext"
|
|
93
|
+
];
|
|
94
|
+
const FORWARDED_ATTRIBUTES = [
|
|
95
|
+
"name",
|
|
96
|
+
"disabled",
|
|
97
|
+
"required",
|
|
98
|
+
"autocomplete",
|
|
99
|
+
"form"
|
|
100
|
+
];
|
|
101
|
+
const UPGRADED_PROPERTIES = [
|
|
102
|
+
"value",
|
|
103
|
+
"label",
|
|
104
|
+
"name",
|
|
105
|
+
"disabled",
|
|
106
|
+
"required"
|
|
107
|
+
];
|
|
108
|
+
function isOptionNode(node) {
|
|
109
|
+
return node instanceof HTMLOptionElement || node instanceof HTMLOptGroupElement;
|
|
110
|
+
}
|
|
111
|
+
__name(isOptionNode, "isOptionNode");
|
|
112
|
+
class SubstrateSelect extends import_web_component.WebComponent.create("substrate-select") {
|
|
113
|
+
static {
|
|
114
|
+
__name(this, "SubstrateSelect");
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The base class generates the accessors for these and derives
|
|
118
|
+
* `observedAttributes` from them.
|
|
119
|
+
*
|
|
120
|
+
* @see docs/adr/ADR-004-attributes-as-state.md
|
|
121
|
+
*/
|
|
122
|
+
static reflectedStringAttributes = ["label", "name"];
|
|
123
|
+
static reflectedBooleanAttributes = ["disabled", "required"];
|
|
124
|
+
static get observedAttributes() {
|
|
125
|
+
return [
|
|
126
|
+
...super.observedAttributes,
|
|
127
|
+
"id",
|
|
128
|
+
"autocomplete",
|
|
129
|
+
"form",
|
|
130
|
+
...ARIA_ATTRIBUTES
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The inner select's value. Before the first render this holds the
|
|
135
|
+
* value and `render` applies it once the options are in.
|
|
136
|
+
*/
|
|
137
|
+
get value() {
|
|
138
|
+
if (this.#select) return this.#select.value;
|
|
139
|
+
return this.#pendingValue ?? "";
|
|
140
|
+
}
|
|
141
|
+
set value(value) {
|
|
142
|
+
const normalized = String(value ?? "");
|
|
143
|
+
if (this.#select) {
|
|
144
|
+
this.#adopt(Array.from(this.childNodes));
|
|
145
|
+
this.#pendingValue = null;
|
|
146
|
+
this.#select.value = normalized;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.#pendingValue = normalized;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The inner select, or `null` before the first render. An escape
|
|
153
|
+
* hatch, for example to remove options.
|
|
154
|
+
*/
|
|
155
|
+
get select() {
|
|
156
|
+
return this.#select;
|
|
157
|
+
}
|
|
158
|
+
#select = null;
|
|
159
|
+
#label = null;
|
|
160
|
+
#pendingValue = null;
|
|
161
|
+
#observer = null;
|
|
162
|
+
#generatedId = "substrate-select-" + Math.random().toString(36).slice(2, 10);
|
|
163
|
+
connectedCallback() {
|
|
164
|
+
debug("connected");
|
|
165
|
+
for (const prop of UPGRADED_PROPERTIES) {
|
|
166
|
+
this.#upgradeProperty(prop);
|
|
167
|
+
}
|
|
168
|
+
this.render();
|
|
169
|
+
this.#adopt(Array.from(this.childNodes));
|
|
170
|
+
this.#observer ??= new MutationObserver((records) => {
|
|
171
|
+
this.#handleRecords(records);
|
|
172
|
+
});
|
|
173
|
+
this.#observer.observe(this, { childList: true });
|
|
174
|
+
if (this.#select) {
|
|
175
|
+
this.#observer.observe(this.#select, {
|
|
176
|
+
childList: true,
|
|
177
|
+
subtree: true
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
disconnectedCallback() {
|
|
182
|
+
debug("disconnected");
|
|
183
|
+
if (!this.#observer) return;
|
|
184
|
+
this.#handleRecords(this.#observer.takeRecords());
|
|
185
|
+
this.#observer.disconnect();
|
|
186
|
+
}
|
|
187
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
188
|
+
if (name === "id" || name.startsWith("aria-")) {
|
|
189
|
+
this.#moveToSelect(name, newValue);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
super.attributeChangedCallback(
|
|
193
|
+
name,
|
|
194
|
+
oldValue,
|
|
195
|
+
newValue
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
handleChange_label(_oldValue, newValue) {
|
|
199
|
+
const select = this.#select;
|
|
200
|
+
if (!select) return;
|
|
201
|
+
if (!newValue) {
|
|
202
|
+
this.#label?.remove();
|
|
203
|
+
this.#label = null;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (!this.#label) {
|
|
207
|
+
this.#label = this.#createLabel(select.id);
|
|
208
|
+
this.insertBefore(this.#label, select);
|
|
209
|
+
}
|
|
210
|
+
this.#label.textContent = newValue;
|
|
211
|
+
}
|
|
212
|
+
handleChange_name(_oldValue, newValue) {
|
|
213
|
+
this.#forward("name", newValue);
|
|
214
|
+
}
|
|
215
|
+
handleChange_disabled(_oldValue, newValue) {
|
|
216
|
+
this.#forward("disabled", newValue);
|
|
217
|
+
}
|
|
218
|
+
handleChange_required(_oldValue, newValue) {
|
|
219
|
+
this.#forward("required", newValue);
|
|
220
|
+
}
|
|
221
|
+
handleChange_autocomplete(_oldValue, newValue) {
|
|
222
|
+
this.#forward("autocomplete", newValue);
|
|
223
|
+
}
|
|
224
|
+
handleChange_form(_oldValue, newValue) {
|
|
225
|
+
this.#forward("form", newValue);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Copy a forwarded attribute to the select. Before the first render
|
|
229
|
+
* there is no select, and `render` copies the host's attributes.
|
|
230
|
+
*/
|
|
231
|
+
#forward(name, value) {
|
|
232
|
+
const select = this.#select;
|
|
233
|
+
if (!select) return;
|
|
234
|
+
if (value === null) {
|
|
235
|
+
select.removeAttribute(name);
|
|
236
|
+
} else {
|
|
237
|
+
select.setAttribute(name, value);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Move `id` or an `aria-*` attribute from the host to the select.
|
|
242
|
+
*
|
|
243
|
+
* A `null` value is ignored. The host never keeps these attributes
|
|
244
|
+
* after the first render, so a removal callback is always the one
|
|
245
|
+
* this method or `render` just caused. A consumer's removal goes
|
|
246
|
+
* through the `removeAttribute` override instead, because the host
|
|
247
|
+
* no longer has the attribute and no callback would fire.
|
|
248
|
+
*
|
|
249
|
+
* Before the first render there is no select; `render` reads the
|
|
250
|
+
* host attributes itself.
|
|
251
|
+
*/
|
|
252
|
+
#moveToSelect(name, value) {
|
|
253
|
+
const select = this.#select;
|
|
254
|
+
if (value === null || !select) return;
|
|
255
|
+
if (name === "id") {
|
|
256
|
+
this.#setSelectId(value || this.#generatedId);
|
|
257
|
+
} else {
|
|
258
|
+
select.setAttribute(name, value);
|
|
259
|
+
}
|
|
260
|
+
super.removeAttribute(name);
|
|
261
|
+
}
|
|
262
|
+
#setSelectId(id) {
|
|
263
|
+
if (!this.#select) return;
|
|
264
|
+
this.#select.id = id;
|
|
265
|
+
if (this.#label) this.#label.htmlFor = id;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* After the first render, `id` and `aria-*` live on the inner select,
|
|
269
|
+
* so removing one from the host removes it from the select. This is
|
|
270
|
+
* how frameworks clear an attribute (Preact calls `removeAttribute`
|
|
271
|
+
* for `undefined`). A removed `id` falls back to the generated one,
|
|
272
|
+
* so the label stays connected.
|
|
273
|
+
*
|
|
274
|
+
* Internal code calls `super.removeAttribute` to strip the host
|
|
275
|
+
* without touching the select. `toggleAttribute(name, false)` and
|
|
276
|
+
* `removeAttributeNode` on the host are not covered.
|
|
277
|
+
*/
|
|
278
|
+
removeAttribute(qualifiedName) {
|
|
279
|
+
const name = qualifiedName.toLowerCase();
|
|
280
|
+
const select = this.#select;
|
|
281
|
+
if (select && name === "id") {
|
|
282
|
+
this.#setSelectId(this.#generatedId);
|
|
283
|
+
} else if (select && name.startsWith("aria-")) {
|
|
284
|
+
select.removeAttribute(name);
|
|
285
|
+
}
|
|
286
|
+
super.removeAttribute(qualifiedName);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Apply a value that was set before the first render. It stays
|
|
290
|
+
* pending until an option with that value exists, so a value set
|
|
291
|
+
* before the options arrive (an empty first render, then appended
|
|
292
|
+
* options) still lands. A later `value` set or a user change clears
|
|
293
|
+
* it.
|
|
294
|
+
*/
|
|
295
|
+
#applyPendingValue() {
|
|
296
|
+
const select = this.#select;
|
|
297
|
+
const pending = this.#pendingValue;
|
|
298
|
+
if (!select || pending === null) return;
|
|
299
|
+
const options = Array.from(select.options);
|
|
300
|
+
if (!options.some((option) => option.value === pending)) return;
|
|
301
|
+
select.value = pending;
|
|
302
|
+
this.#pendingValue = null;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* A property set before the element was defined is an own data
|
|
306
|
+
* property that hides the class accessor. Remove it and set it
|
|
307
|
+
* again so the accessor sees it.
|
|
308
|
+
*/
|
|
309
|
+
#upgradeProperty(prop) {
|
|
310
|
+
if (!Object.prototype.hasOwnProperty.call(this, prop)) return;
|
|
311
|
+
const value = Reflect.get(this, prop);
|
|
312
|
+
Reflect.deleteProperty(this, prop);
|
|
313
|
+
Reflect.set(this, prop, value);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Move options added to the host into the select, and retry a
|
|
317
|
+
* waiting value when nodes were added inside the select.
|
|
318
|
+
*/
|
|
319
|
+
#handleRecords(records) {
|
|
320
|
+
const fromHost = records.filter((record) => record.target === this);
|
|
321
|
+
this.#adopt(
|
|
322
|
+
fromHost.flatMap((record) => Array.from(record.addedNodes))
|
|
323
|
+
);
|
|
324
|
+
const intoSelect = records.some((record) => {
|
|
325
|
+
return record.target !== this && record.addedNodes.length > 0;
|
|
326
|
+
});
|
|
327
|
+
if (intoSelect) this.#applyPendingValue();
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Move `<option>` and `<optgroup>` children of the host to the end of
|
|
331
|
+
* the select. The component's own label and select, and anything
|
|
332
|
+
* that is not an option, stay where they are.
|
|
333
|
+
*/
|
|
334
|
+
#adopt(nodes) {
|
|
335
|
+
const select = this.#select;
|
|
336
|
+
if (!select) return;
|
|
337
|
+
let moved = false;
|
|
338
|
+
for (const node of nodes) {
|
|
339
|
+
if (node.parentNode !== this) continue;
|
|
340
|
+
if (!isOptionNode(node)) continue;
|
|
341
|
+
select.append(node);
|
|
342
|
+
moved = true;
|
|
343
|
+
}
|
|
344
|
+
if (moved) this.#applyPendingValue();
|
|
345
|
+
}
|
|
346
|
+
#createLabel(selectId) {
|
|
347
|
+
const label = document.createElement("label");
|
|
348
|
+
label.htmlFor = selectId;
|
|
349
|
+
return label;
|
|
350
|
+
}
|
|
351
|
+
render() {
|
|
352
|
+
if (this.#select) return;
|
|
353
|
+
const authored = [];
|
|
354
|
+
const stray = [];
|
|
355
|
+
for (const node of Array.from(this.childNodes)) {
|
|
356
|
+
if (isOptionNode(node)) {
|
|
357
|
+
authored.push(node);
|
|
358
|
+
} else if (node.nodeType !== Node.TEXT_NODE || node.textContent?.trim()) {
|
|
359
|
+
stray.push(node);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (stray.length) debug("dropping non-option children", stray);
|
|
363
|
+
const chosen = authored.flatMap((node) => {
|
|
364
|
+
return node instanceof HTMLOptionElement ? [node] : Array.from(node.querySelectorAll("option"));
|
|
365
|
+
}).filter((option) => option.selected).pop();
|
|
366
|
+
const select = document.createElement("select");
|
|
367
|
+
select.id = this.getAttribute("id") ?? this.#generatedId;
|
|
368
|
+
for (const attr of Array.from(this.attributes)) {
|
|
369
|
+
if (attr.name.startsWith("aria-")) {
|
|
370
|
+
select.setAttribute(attr.name, attr.value);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
for (const name of FORWARDED_ATTRIBUTES) {
|
|
374
|
+
const value = this.getAttribute(name);
|
|
375
|
+
if (value !== null) select.setAttribute(name, value);
|
|
376
|
+
}
|
|
377
|
+
select.addEventListener("change", () => {
|
|
378
|
+
this.#pendingValue = null;
|
|
379
|
+
this.emit("change", {
|
|
380
|
+
detail: { value: select.value }
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
select.append(...authored);
|
|
384
|
+
if (chosen) chosen.selected = true;
|
|
385
|
+
this.#select = select;
|
|
386
|
+
this.#applyPendingValue();
|
|
387
|
+
if (this.#pendingValue !== null) select.selectedIndex = -1;
|
|
388
|
+
const labelText = this.getAttribute("label");
|
|
389
|
+
if (labelText) {
|
|
390
|
+
this.#label = this.#createLabel(select.id);
|
|
391
|
+
this.#label.textContent = labelText;
|
|
392
|
+
this.replaceChildren(this.#label, select);
|
|
393
|
+
} else {
|
|
394
|
+
this.replaceChildren(select);
|
|
395
|
+
}
|
|
396
|
+
super.removeAttribute("id");
|
|
397
|
+
for (const attr of Array.from(this.attributes)) {
|
|
398
|
+
if (attr.name.startsWith("aria-")) {
|
|
399
|
+
super.removeAttribute(attr.name);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
SubstrateSelect.define();
|
|
405
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["// pattern: Imperative Shell\nimport { WebComponent } from '@substrate-system/web-component'\nimport Debug from '@substrate-system/debug'\nconst debug = Debug('substrate-select')\n\n// for document.querySelector\ndeclare global {\n interface HTMLElementTagNameMap {\n 'substrate-select':SubstrateSelect\n }\n}\n\n/**\n * ARIA attributes the component observes, so a change after upgrade is\n * moved to the inner select too. `attributeChangedCallback` only sees\n * attributes named in `observedAttributes`. Copied from\n * `@substrate-system/input` (`src/util.ts`). At first render every\n * `aria-*` attribute moves, listed here or not.\n */\nexport const ARIA_ATTRIBUTES = [\n 'aria-activedescendant',\n 'aria-atomic',\n 'aria-autocomplete',\n 'aria-braillelabel',\n 'aria-brailleroledescription',\n 'aria-busy',\n 'aria-checked',\n 'aria-colcount',\n 'aria-colindex',\n 'aria-colindextext',\n 'aria-colspan',\n 'aria-controls',\n 'aria-current',\n 'aria-describedby',\n 'aria-description',\n 'aria-details',\n 'aria-disabled',\n 'aria-dropeffect',\n 'aria-errormessage',\n 'aria-expanded',\n 'aria-flowto',\n 'aria-grabbed',\n 'aria-haspopup',\n 'aria-hidden',\n 'aria-invalid',\n 'aria-keyshortcuts',\n 'aria-label',\n 'aria-labelledby',\n 'aria-level',\n 'aria-live',\n 'aria-modal',\n 'aria-multiline',\n 'aria-multiselectable',\n 'aria-orientation',\n 'aria-owns',\n 'aria-placeholder',\n 'aria-posinset',\n 'aria-pressed',\n 'aria-readonly',\n 'aria-relevant',\n 'aria-required',\n 'aria-roledescription',\n 'aria-rowcount',\n 'aria-rowindex',\n 'aria-rowindextext',\n 'aria-rowspan',\n 'aria-selected',\n 'aria-setsize',\n 'aria-sort',\n 'aria-valuemax',\n 'aria-valuemin',\n 'aria-valuenow',\n 'aria-valuetext'\n]\n\n/**\n * Attributes copied to the inner select and also left on the host.\n */\nconst FORWARDED_ATTRIBUTES = [\n 'name',\n 'disabled',\n 'required',\n 'autocomplete',\n 'form'\n]\n\n/**\n * Properties a framework may set before this module defines the\n * element. See `#upgradeProperty`.\n */\nconst UPGRADED_PROPERTIES = [\n 'value',\n 'label',\n 'name',\n 'disabled',\n 'required'\n]\n\nfunction isOptionNode (\n node:Node\n):node is HTMLOptionElement|HTMLOptGroupElement {\n return (\n node instanceof HTMLOptionElement ||\n node instanceof HTMLOptGroupElement\n )\n}\n\nexport interface SubstrateSelectChangeDetail {\n value:string;\n}\n\n/**\n * Use the `create` factory rather than `class X extends WebComponent`\n * with `static TAG`. The base class declares `TAG` as an *instance*\n * field defaulting to `''`, and `emit`/`on` namespace from that instance\n * field while `static event()` reads the static one. Setting only\n * `static TAG` therefore emits `':change'` while listeners built from\n * `SubstrateSelect.event('change')` expect `'substrate-select:change'`,\n * and nothing fires. `create` sets both.\n */\nexport class SubstrateSelect extends WebComponent.create('substrate-select') {\n /**\n * The base class generates the accessors for these and derives\n * `observedAttributes` from them.\n *\n * @see docs/adr/ADR-004-attributes-as-state.md\n */\n static reflectedStringAttributes = ['label', 'name']\n static reflectedBooleanAttributes = ['disabled', 'required']\n\n static get observedAttributes ():string[] {\n return [\n ...super.observedAttributes,\n 'id',\n 'autocomplete',\n 'form',\n ...ARIA_ATTRIBUTES\n ]\n }\n\n /**\n * Installed at runtime by `define()`. `declare` types them without\n * emitting a field that would shadow the generated accessor.\n */\n declare label:string|null\n declare name:string|null\n declare disabled:boolean\n declare required:boolean\n\n /**\n * The inner select's value. Before the first render this holds the\n * value and `render` applies it once the options are in.\n */\n get value ():string {\n if (this.#select) return this.#select.value\n return this.#pendingValue ?? ''\n }\n\n set value (value:string) {\n const normalized = String(value ?? '')\n if (this.#select) {\n // Options appended this tick are still in the host, waiting\n // for the observer. Move them now so the value can match.\n this.#adopt(Array.from(this.childNodes))\n // A later set replaces any value still waiting for its option\n this.#pendingValue = null\n this.#select.value = normalized\n return\n }\n this.#pendingValue = normalized\n }\n\n /**\n * The inner select, or `null` before the first render. An escape\n * hatch, for example to remove options.\n */\n get select ():HTMLSelectElement|null {\n return this.#select\n }\n\n #select:HTMLSelectElement|null = null\n #label:HTMLLabelElement|null = null\n #pendingValue:string|null = null\n #observer:MutationObserver|null = null\n #generatedId = 'substrate-select-' +\n Math.random().toString(36).slice(2, 10)\n\n connectedCallback () {\n debug('connected')\n for (const prop of UPGRADED_PROPERTIES) {\n this.#upgradeProperty(prop)\n }\n\n // Not `super.connectedCallback()`: that renders on every\n // connect. `render` is idempotent, so a reconnect keeps the\n // existing select, its options, and the user's selection.\n this.render()\n\n // Options appended while disconnected\n this.#adopt(Array.from(this.childNodes))\n\n this.#observer ??= new MutationObserver(records => {\n this.#handleRecords(records)\n })\n this.#observer.observe(this, { childList: true })\n // Options added through `el.select` may be what a waiting value\n // is waiting for. `subtree` covers options inside an optgroup.\n if (this.#select) {\n this.#observer.observe(this.#select, {\n childList: true,\n subtree: true\n })\n }\n }\n\n disconnectedCallback () {\n debug('disconnected')\n if (!this.#observer) return\n // Deliver additions the observer has queued but not reported\n this.#handleRecords(this.#observer.takeRecords())\n this.#observer.disconnect()\n }\n\n attributeChangedCallback (\n name:string,\n oldValue:string|null,\n newValue:string|null\n ) {\n if (name === 'id' || name.startsWith('aria-')) {\n this.#moveToSelect(name, newValue)\n return\n }\n\n // The base class types these as `string`; the DOM passes `null`\n // for an absent attribute, and the handlers here accept it.\n super.attributeChangedCallback(\n name,\n oldValue as string,\n newValue as string\n )\n }\n\n handleChange_label (_oldValue:string|null, newValue:string|null) {\n const select = this.#select\n if (!select) return // render reads the attribute\n\n if (!newValue) {\n this.#label?.remove()\n this.#label = null\n return\n }\n\n if (!this.#label) {\n this.#label = this.#createLabel(select.id)\n this.insertBefore(this.#label, select)\n }\n\n this.#label.textContent = newValue\n }\n\n handleChange_name (_oldValue:string|null, newValue:string|null) {\n this.#forward('name', newValue)\n }\n\n handleChange_disabled (_oldValue:string|null, newValue:string|null) {\n this.#forward('disabled', newValue)\n }\n\n handleChange_required (_oldValue:string|null, newValue:string|null) {\n this.#forward('required', newValue)\n }\n\n handleChange_autocomplete (\n _oldValue:string|null,\n newValue:string|null\n ) {\n this.#forward('autocomplete', newValue)\n }\n\n handleChange_form (_oldValue:string|null, newValue:string|null) {\n this.#forward('form', newValue)\n }\n\n /**\n * Copy a forwarded attribute to the select. Before the first render\n * there is no select, and `render` copies the host's attributes.\n */\n #forward (name:string, value:string|null) {\n const select = this.#select\n if (!select) return\n\n if (value === null) {\n select.removeAttribute(name)\n } else {\n select.setAttribute(name, value)\n }\n }\n\n /**\n * Move `id` or an `aria-*` attribute from the host to the select.\n *\n * A `null` value is ignored. The host never keeps these attributes\n * after the first render, so a removal callback is always the one\n * this method or `render` just caused. A consumer's removal goes\n * through the `removeAttribute` override instead, because the host\n * no longer has the attribute and no callback would fire.\n *\n * Before the first render there is no select; `render` reads the\n * host attributes itself.\n */\n #moveToSelect (name:string, value:string|null) {\n const select = this.#select\n if (value === null || !select) return\n\n if (name === 'id') {\n this.#setSelectId(value || this.#generatedId)\n } else {\n select.setAttribute(name, value)\n }\n super.removeAttribute(name)\n }\n\n #setSelectId (id:string) {\n if (!this.#select) return\n this.#select.id = id\n if (this.#label) this.#label.htmlFor = id\n }\n\n /**\n * After the first render, `id` and `aria-*` live on the inner select,\n * so removing one from the host removes it from the select. This is\n * how frameworks clear an attribute (Preact calls `removeAttribute`\n * for `undefined`). A removed `id` falls back to the generated one,\n * so the label stays connected.\n *\n * Internal code calls `super.removeAttribute` to strip the host\n * without touching the select. `toggleAttribute(name, false)` and\n * `removeAttributeNode` on the host are not covered.\n */\n removeAttribute (qualifiedName:string) {\n const name = qualifiedName.toLowerCase()\n const select = this.#select\n if (select && name === 'id') {\n this.#setSelectId(this.#generatedId)\n } else if (select && name.startsWith('aria-')) {\n select.removeAttribute(name)\n }\n super.removeAttribute(qualifiedName)\n }\n\n /**\n * Apply a value that was set before the first render. It stays\n * pending until an option with that value exists, so a value set\n * before the options arrive (an empty first render, then appended\n * options) still lands. A later `value` set or a user change clears\n * it.\n */\n #applyPendingValue () {\n const select = this.#select\n const pending = this.#pendingValue\n if (!select || pending === null) return\n // Write only on a match. A write with no match would clear\n // whatever is selected now, including a pick made through\n // `el.select`, with no event.\n const options = Array.from(select.options)\n if (!options.some(option => option.value === pending)) return\n select.value = pending\n this.#pendingValue = null\n }\n\n /**\n * A property set before the element was defined is an own data\n * property that hides the class accessor. Remove it and set it\n * again so the accessor sees it.\n */\n #upgradeProperty (prop:string) {\n if (!Object.prototype.hasOwnProperty.call(this, prop)) return\n const value = Reflect.get(this, prop)\n Reflect.deleteProperty(this, prop)\n Reflect.set(this, prop, value)\n }\n\n /**\n * Move options added to the host into the select, and retry a\n * waiting value when nodes were added inside the select.\n */\n #handleRecords (records:MutationRecord[]) {\n const fromHost = records.filter(record => record.target === this)\n this.#adopt(\n fromHost.flatMap(record => Array.from(record.addedNodes))\n )\n const intoSelect = records.some(record => {\n return record.target !== this && record.addedNodes.length > 0\n })\n if (intoSelect) this.#applyPendingValue()\n }\n\n /**\n * Move `<option>` and `<optgroup>` children of the host to the end of\n * the select. The component's own label and select, and anything\n * that is not an option, stay where they are.\n */\n #adopt (nodes:Node[]) {\n const select = this.#select\n if (!select) return\n let moved = false\n for (const node of nodes) {\n // already moved, or removed again before delivery\n if (node.parentNode !== this) continue\n if (!isOptionNode(node)) continue\n select.append(node)\n moved = true\n }\n // a value set before connect may be waiting for these options\n if (moved) this.#applyPendingValue()\n }\n\n #createLabel (selectId:string):HTMLLabelElement {\n const label = document.createElement('label')\n label.htmlFor = selectId\n return label\n }\n\n render () {\n if (this.#select) return\n\n const authored:(HTMLOptionElement|HTMLOptGroupElement)[] = []\n const stray:Node[] = []\n for (const node of Array.from(this.childNodes)) {\n if (isOptionNode(node)) {\n authored.push(node)\n } else if (\n node.nodeType !== Node.TEXT_NODE ||\n node.textContent?.trim()\n ) {\n stray.push(node)\n }\n }\n if (stray.length) debug('dropping non-option children', stray)\n\n // The option the parser marked selected. Reapplied after the\n // move instead of relying on insertion to keep it.\n const chosen = authored\n .flatMap(node => {\n return node instanceof HTMLOptionElement ?\n [node] :\n Array.from(node.querySelectorAll('option'))\n })\n .filter(option => option.selected)\n .pop()\n\n // DOM APIs only, so attribute values are never parsed as HTML\n const select = document.createElement('select')\n select.id = this.getAttribute('id') ?? this.#generatedId\n for (const attr of Array.from(this.attributes)) {\n if (attr.name.startsWith('aria-')) {\n select.setAttribute(attr.name, attr.value)\n }\n }\n for (const name of FORWARDED_ATTRIBUTES) {\n const value = this.getAttribute(name)\n if (value !== null) select.setAttribute(name, value)\n }\n\n // Native `change` and `input` bubble out of the host as they\n // are. Also emit the namespaced event (ADR-005).\n select.addEventListener('change', () => {\n // the user's pick replaces a value still waiting for its\n // option\n this.#pendingValue = null\n this.emit<SubstrateSelectChangeDetail>('change', {\n detail: { value: select.value }\n })\n })\n\n select.append(...authored)\n if (chosen) chosen.selected = true\n this.#select = select\n this.#applyPendingValue()\n // A value set from code wins over an authored `selected`, as on\n // a native select, so while it waits nothing is selected\n if (this.#pendingValue !== null) select.selectedIndex = -1\n\n const labelText = this.getAttribute('label')\n if (labelText) {\n this.#label = this.#createLabel(select.id)\n this.#label.textContent = labelText\n this.replaceChildren(this.#label, select)\n } else {\n this.replaceChildren(select)\n }\n\n // `super`, not `this`: strip the host only; the select keeps\n // the values. The removal callbacks this causes carry `null`,\n // which `#moveToSelect` ignores.\n super.removeAttribute('id')\n for (const attr of Array.from(this.attributes)) {\n if (attr.name.startsWith('aria-')) {\n super.removeAttribute(attr.name)\n }\n }\n }\n}\n\nSubstrateSelect.define()\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["Debug"]
|
|
7
|
+
}
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
substrate-select {
|
|
2
|
+
--substrate-select-text: var(--substrate-text, black);
|
|
3
|
+
--substrate-select-surface: var(--substrate-surface, white);
|
|
4
|
+
--substrate-select-border: var(--substrate-border, #9b9b9b);
|
|
5
|
+
--substrate-select-accent: var(--substrate-accent, #2563eb);
|
|
6
|
+
--substrate-select-focus: var(--substrate-focus, #2563eb);
|
|
7
|
+
--substrate-select-focus-width: var(--substrate-focus-width, 2px);
|
|
8
|
+
--substrate-select-focus-offset: var(--substrate-focus-offset, 2px);
|
|
9
|
+
--substrate-select-radius: var(--substrate-radius, 0);
|
|
10
|
+
--substrate-select-option-hover: #f3f4f6;
|
|
11
|
+
--substrate-select-checked: #1d4ed8;
|
|
12
|
+
--substrate-select-shadow: 0 4px 12px #21212114;
|
|
13
|
+
gap: .25rem;
|
|
14
|
+
display: grid;
|
|
15
|
+
|
|
16
|
+
& select {
|
|
17
|
+
appearance: none;
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
max-width: 100%;
|
|
20
|
+
font: inherit;
|
|
21
|
+
color: var(--substrate-select-text);
|
|
22
|
+
background-color: var(--substrate-select-surface);
|
|
23
|
+
border: 1px solid var(--substrate-select-border);
|
|
24
|
+
border-radius: var(--substrate-select-radius);
|
|
25
|
+
outline: var(--substrate-select-focus-width) solid transparent;
|
|
26
|
+
outline-offset: var(--substrate-select-focus-offset);
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
background-image: linear-gradient(45deg, #0000 calc(50% - .07rem), currentColor calc(50% - .07rem) calc(50% + .07rem), #0000 calc(50% + .07rem)), linear-gradient(-45deg, #0000 calc(50% - .07rem), currentColor calc(50% - .07rem) calc(50% + .07rem), #0000 calc(50% + .07rem));
|
|
29
|
+
background-position: calc(100% - 1.4rem) calc(50% + .1rem), calc(100% - 1rem) calc(50% + .1rem);
|
|
30
|
+
background-repeat: no-repeat;
|
|
31
|
+
background-size: .4rem .4rem;
|
|
32
|
+
padding: 1rem 2.75rem .8rem 1rem;
|
|
33
|
+
transition: border-color .2s, outline-color .2s;
|
|
34
|
+
|
|
35
|
+
&:hover:not(:disabled) {
|
|
36
|
+
border-color: var(--substrate-select-accent);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&:focus-visible {
|
|
40
|
+
outline-color: var(--substrate-select-focus);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&:disabled {
|
|
44
|
+
cursor: not-allowed;
|
|
45
|
+
opacity: .6;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@supports (appearance: base-select) {
|
|
50
|
+
& select, & select::picker(select) {
|
|
51
|
+
appearance: base-select;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
& select {
|
|
55
|
+
background-image: none;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: .75rem;
|
|
58
|
+
min-block-size: calc(1lh + 1.8rem + 2px);
|
|
59
|
+
padding-inline-end: 1rem;
|
|
60
|
+
display: flex;
|
|
61
|
+
|
|
62
|
+
&::picker-icon {
|
|
63
|
+
content: "";
|
|
64
|
+
box-sizing: border-box;
|
|
65
|
+
border-bottom: .125rem solid;
|
|
66
|
+
border-right: .125rem solid;
|
|
67
|
+
width: .5rem;
|
|
68
|
+
height: .5rem;
|
|
69
|
+
margin-inline-start: auto;
|
|
70
|
+
transition: transform .2s;
|
|
71
|
+
transform: translateY(-.125rem) rotate(45deg);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
&:open::picker-icon {
|
|
75
|
+
transform: translateY(.125rem) rotate(225deg);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
& option {
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
gap: .5rem;
|
|
81
|
+
padding: .5rem .75rem;
|
|
82
|
+
|
|
83
|
+
&:hover, &:focus-visible {
|
|
84
|
+
background-color: var(--substrate-select-option-hover);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&:checked {
|
|
88
|
+
color: var(--substrate-select-checked);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
&::checkmark {
|
|
92
|
+
content: "✓";
|
|
93
|
+
color: var(--substrate-select-accent);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
& select::picker(select) {
|
|
99
|
+
color: var(--substrate-select-text);
|
|
100
|
+
background-color: var(--substrate-select-surface);
|
|
101
|
+
border: 1px solid var(--substrate-select-border);
|
|
102
|
+
border-radius: var(--substrate-select-radius);
|
|
103
|
+
box-shadow: var(--substrate-select-shadow);
|
|
104
|
+
opacity: 0;
|
|
105
|
+
transition: opacity .15s,
|
|
106
|
+
translate .15s,
|
|
107
|
+
display .15s allow-discrete,
|
|
108
|
+
overlay .15s allow-discrete;
|
|
109
|
+
margin-block: .25rem;
|
|
110
|
+
padding-block: .25rem;
|
|
111
|
+
translate: 0 -.5rem;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
& select:open::picker(select) {
|
|
115
|
+
opacity: 1;
|
|
116
|
+
translate: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@starting-style {
|
|
120
|
+
& select:open::picker(select) {
|
|
121
|
+
opacity: 0;
|
|
122
|
+
translate: 0 -.5rem;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@media (prefers-reduced-motion: reduce) {
|
|
127
|
+
& select::picker-icon, & select::picker(select) {
|
|
128
|
+
transition: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
& select::picker(select) {
|
|
132
|
+
translate: 0;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@media (prefers-reduced-motion: reduce) {
|
|
138
|
+
& select {
|
|
139
|
+
transition: none;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
substrate-select:not(:defined) {
|
|
145
|
+
display: none;
|
|
146
|
+
}
|