html-combobox-element 0.0.7 → 0.0.8
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/README.md +52 -94
- package/README.md.rej +34 -0
- package/dist/cjs/combobox/Boolean.attribute.value.normalizer.d.ts +14 -1
- package/dist/cjs/combobox/Boolean.attribute.value.normalizer.js +17 -1
- package/dist/cjs/combobox/HTML.combobox.element.js +25 -25
- package/dist/cjs/combobox/HTML.combobox.option.element.js +11 -10
- package/dist/cjs/combobox/HTML.combobox.options.collection.js +2 -4
- package/dist/cjs/index.d.ts +208 -106
- package/dist/cjs/index.js +0 -8
- package/dist/esm/combobox/Boolean.attribute.value.normalizer.js +16 -1
- package/dist/esm/combobox/HTML.combobox.element.js +11 -9
- package/dist/esm/combobox/HTML.combobox.option.element.js +8 -5
- package/dist/esm/index.js +0 -8
- package/dist/types/combobox/Boolean.attribute.value.normalizer.d.ts +14 -1
- package/dist/types/index.d.ts +208 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ This registers the `<combo-box>`, `<box-option>` and `<box-tag>` custom elements
|
|
|
45
45
|
|
|
46
46
|
### Differences from `<select>`
|
|
47
47
|
- In `multiple` mode, the `combobox.value` lists every selected value, where `select.value` gives only the first;
|
|
48
|
+
- In `multiple` mode, the selected values are displayed as chips;
|
|
48
49
|
- In `single`mode, the `<combo-box>` without a selected option stays empty, where a `<select>` selects its first option.
|
|
49
50
|
|
|
50
51
|
### Events – type: when
|
|
@@ -55,23 +56,24 @@ This registers the `<combo-box>`, `<box-option>` and `<box-tag>` custom elements
|
|
|
55
56
|
- `reset`: The form was reset
|
|
56
57
|
|
|
57
58
|
Read from event:
|
|
58
|
-
- `event.target.changed`: tells whether the value differs from when it opened
|
|
59
|
-
- `event.target.query`: tells the search field current text
|
|
60
|
-
- `event.target.
|
|
59
|
+
- `event.target.changed`: tells whether the value differs from when it opened;
|
|
60
|
+
- `event.target.query`: tells the search field current text;
|
|
61
|
+
- `event.target.value`: the combobox selection;
|
|
62
|
+
- `event.target.valueAsArray`: the combobox selection as an array of strings.
|
|
61
63
|
|
|
62
|
-
Like native form controls, changes made by script dispatch no `change`.
|
|
63
|
-
Framework bindings that assume `input` means "the value changed" need the `change` event instead.
|
|
64
|
+
Like native form controls, changes made by script dispatch no `change`. Framework bindings that assume `input` means "the value changed" need the `change` event instead.
|
|
64
65
|
|
|
65
66
|
### Attributes
|
|
66
67
|
All `<select>`'s attributes are supported, and few extra:
|
|
67
|
-
- `filterable`: Displays search field in picker; options are automatically filtered against input value
|
|
68
|
-
- `searchable`: Displays search field in picker; you listen for input events and implement own filter. Usefully for fuzzy search, etc
|
|
69
|
-
- `placeholder`: The combobox own placeholder, shown when there is no selection
|
|
70
|
-
- `query`: The search field input value
|
|
68
|
+
- `filterable`: Displays search field in picker; options are automatically filtered against input value;
|
|
69
|
+
- `searchable`: Displays search field in picker; you listen for input events and implement own filter. Usefully for fuzzy search, etc.;
|
|
70
|
+
- `placeholder`: The combobox own placeholder, shown when there is no selection;
|
|
71
|
+
- `query`: The search field input value.
|
|
71
72
|
|
|
72
73
|
## Options API
|
|
73
74
|
Like `select.options`, `combobox.options` is a live collection too, but a fully compatible polyfill – `HTMLComboboxOptionsCollection`, because `HTMLOptionsCollection` can't be constructed or subclassed.
|
|
74
75
|
The only difference between `select.options` and `combobox.options`, is that `combobox.options` will false on `instanceof HTMLOptionsCollection` check.
|
|
76
|
+
The `<box-option>` itself has the same API as native `<option>`. It exists, because the `<option>` can't be styled at all.
|
|
75
77
|
|
|
76
78
|
## Accessibility
|
|
77
79
|
- Roles and states are set through `ElementInternals`, and mirrored as `role` and `aria`attributes, so tools that read the DOM can see it too.
|
|
@@ -144,96 +146,52 @@ That teaches it the `<select>` API of `<combo-box>` through the DOM only. Your p
|
|
|
144
146
|
Playwright names a custom element from `<label for>` only for native controls,
|
|
145
147
|
so query `getByRole('combobox', { name })` with `aria-label` or `aria-labelledby` on the `<combo-box>`, or without a name.
|
|
146
148
|
|
|
147
|
-
## TypeScript and JSX
|
|
148
|
-
`import 'html-combobox-element'` also brings the typings:
|
|
149
|
+
## TypeScript and JSX frameworks
|
|
150
|
+
`import 'html-combobox-element'` also brings the typings for:
|
|
151
|
+
- `document.createElement('combo-box')`;
|
|
152
|
+
- `querySelector`;
|
|
153
|
+
- `addEventListener` with a typed `event.target`.
|
|
154
|
+
|
|
155
|
+
### Event listeners attached through the props
|
|
156
|
+
The props are the same everywhere, but which handlers get called, and with what, depends on the framework:
|
|
157
|
+
|
|
158
|
+
| Handler prop | React 19 | React 18 | Vue/Preact/SolidJS |
|
|
159
|
+
|--------------|---|---|--------------------|
|
|
160
|
+
| `onInput` | SyntheticEvent | SyntheticEvent | DOM event |
|
|
161
|
+
| `onChange` | SyntheticEvent | never called | DOM event |
|
|
162
|
+
| `onsearch` | DOM event | never called | DOM event |
|
|
163
|
+
| `oninvalid` | DOM event | never called | DOM event |
|
|
164
|
+
| `onreset` | DOM event | never called | DOM event |
|
|
165
|
+
| `onclose` | DOM event | never called | DOM event |
|
|
166
|
+
| `onClose` | never called | never called | DOM event |
|
|
167
|
+
| `onSearch` | never called | never called | DOM event |
|
|
168
|
+
| `onReset` | never called | never called | DOM event |
|
|
169
|
+
| `onInvalid` | never called | never called | DOM event |
|
|
170
|
+
|
|
171
|
+
The `combobox.addEventListener(type, cb)` works seamlessly in all frameworks.
|
|
172
|
+
|
|
173
|
+
### Boolean props
|
|
174
|
+
The `multiple`, `clearable`, `disabled`, ... behave like native ones in every framework, except that the string `'false'` is also `false`, i.e. turns the feature off.
|
|
149
175
|
|
|
150
176
|
### React
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
React wraps events in its own SyntheticEvents, so handlers are typed accordingly:
|
|
154
|
-
- `onChange`, `onInput`, `onFocus`, `onClick`, ... receive a SyntheticEvent: `event.target` is the `<combo-box>` (`event.target.value`, `event.target.query`), `event.nativeEvent` is the DOM event.
|
|
155
|
-
- Events React doesn't know are attached by React 19 as native listeners under the exact, case-sensitive name: use `onclose` and `onsearch` (lowercase). `onClose`/`onSearch` never fire, and are type errors.
|
|
156
|
-
|
|
157
|
-
```tsx
|
|
158
|
-
<combo-box
|
|
159
|
-
name="fruit"
|
|
160
|
-
multiple
|
|
161
|
-
onChange={(event) => setFruits(event.target.valueAsArray)}
|
|
162
|
-
onclose={(event) => event.target.changed && save()}
|
|
163
|
-
>
|
|
164
|
-
<box-option value="apple">Apple</box-option>
|
|
165
|
-
</combo-box>
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
With `@types/react` 18 the framework-agnostic typings are used, as in previous versions.
|
|
169
|
-
|
|
170
|
-
If you copied the React `combo-box.d.ts` snippet from the README of a previous version, delete it: it still compiles, but it keeps the old typings.
|
|
171
|
-
|
|
172
|
-
### Monorepos
|
|
173
|
-
The typings are part of a TypeScript program only if the program includes this package. A workspace package that renders `<combo-box>` but leaves the import to the app shell type-checks inside the app, but not on its own (its CI, editor, project references). Add a type-only import there - it's removed from the JS output, nothing is registered twice:
|
|
174
|
-
```ts
|
|
175
|
-
import type {} from 'html-combobox-element';
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Other frameworks
|
|
179
|
-
JSX/template type-checking for these needs one small file in your project, with the snippet for your framework:
|
|
180
|
-
|
|
181
|
-
<details>
|
|
182
|
-
<summary><strong>Preact</strong> (<code>jsx: "react-jsx"</code>, <code>jsxImportSource: "preact"</code>)</summary>
|
|
183
|
-
|
|
184
|
-
```ts
|
|
185
|
-
// combo-box.d.ts
|
|
186
|
-
declare module 'preact/jsx-runtime' {
|
|
187
|
-
namespace JSX {
|
|
188
|
-
interface IntrinsicElements {
|
|
189
|
-
'combo-box': ComboboxElementAttributes;
|
|
190
|
-
'box-option': ComboboxOptionElementAttributes;
|
|
191
|
-
'box-tag': ComboboxTagElementAttributes;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
</details>
|
|
177
|
+
Package ships types, for `jsx: "react-jsx"`, `"react-jsxdev"` and classic `"react"`.
|
|
178
|
+
Handlers are typed accordingly: `onChange`, `onInput`, `onFocus`, `onClick`, ... receive a SyntheticEvent: `event.target` is the `<combo-box>`, `event.nativeEvent` is the DOM event.
|
|
198
179
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
```ts
|
|
203
|
-
// combo-box.d.ts
|
|
204
|
-
declare module 'solid-js/jsx-runtime' {
|
|
205
|
-
namespace JSX {
|
|
206
|
-
interface IntrinsicElements {
|
|
207
|
-
'combo-box': ComboboxElementAttributes;
|
|
208
|
-
'box-option': ComboboxOptionElementAttributes;
|
|
209
|
-
'box-tag': ComboboxTagElementAttributes;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
```
|
|
180
|
+
- In React 19, events it doesn't know ("close", "search") are attached as native listeners under the exact, case-sensitive name, so use `onclose` and `onsearch`.
|
|
181
|
+
- In React 18, listen to `change`, `close` and `search` on the element itself through the ref.
|
|
182
|
+
- In a project written in React, but aliased to _Preact_ at build time – types may confuse. Rely on runtime behaviour, rather than on Typescript errors.
|
|
214
183
|
|
|
215
|
-
|
|
184
|
+
### Preact and SolidJS
|
|
185
|
+
Nothing to do, everything just work.
|
|
216
186
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
Vue templates aren't JSX, so they're type-checked through `GlobalComponents` instead:
|
|
187
|
+
### Vue
|
|
188
|
+
Vue template compiler treats unknown tags as Vue components by default. You must tell it to treat `<combo-box>`, `<box-option>` and `<box-tag>` tags as custom elements.
|
|
221
189
|
|
|
190
|
+
### Monorepos and microservices
|
|
191
|
+
The typings are part of a TypeScript program only if the program includes this package.
|
|
192
|
+
A workspace package that renders `<combo-box>` but leaves the import to the app shell type-checks inside the app,
|
|
193
|
+
but not on its own (its CI, editor, project references).
|
|
194
|
+
Add a type-only import there - it's removed from the JS output, nothing is registered twice:
|
|
222
195
|
```ts
|
|
223
|
-
|
|
224
|
-
declare module 'vue' {
|
|
225
|
-
export interface GlobalComponents {
|
|
226
|
-
'combo-box': new () => { $props: ComboboxElementAttributes };
|
|
227
|
-
'box-option': new () => { $props: ComboboxOptionElementAttributes };
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
196
|
+
import type {} from 'html-combobox-element';
|
|
231
197
|
```
|
|
232
|
-
|
|
233
|
-
This checks required props and value types for props you pass; like any native custom
|
|
234
|
-
element, Vue still allows extra/unrecognized attributes through (that's expected Vue
|
|
235
|
-
behavior for custom elements, not a gap in this typing). The trailing `export {};` may be
|
|
236
|
-
required here (without it the file is treated as a script, not a module, and the
|
|
237
|
-
augmentation breaks Vue's own types).
|
|
238
|
-
|
|
239
|
-
</details>
|
package/README.md.rej
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
diff a/README.md b/README.md (rejected hunks)
|
|
2
|
+
@@ -152,7 +152,7 @@ Nothing to add: `<combo-box>`, `<box-option>` and `<box-tag>` are typed with Rea
|
|
3
|
+
|
|
4
|
+
React wraps events in its own SyntheticEvents, so handlers are typed accordingly:
|
|
5
|
+
- `onChange`, `onInput`, `onFocus`, `onClick`, ... receive a SyntheticEvent: `event.target` is the `<combo-box>` (`event.target.value`, `event.target.query`), `event.nativeEvent` is the DOM event.
|
|
6
|
+
-- Events React doesn't know are attached by React 19 as native listeners under the exact, case-sensitive name: use `onclose` and `onsearch` (lowercase). `onClose`/`onSearch` never fire, and are type errors.
|
|
7
|
+
+- Events React doesn't handle for custom elements are attached by React 19 as native listeners under the exact, case-sensitive name: use `onclose`, `onsearch`, `onreset` and `oninvalid` (lowercase). `onClose`/`onSearch`/`onReset`/`onInvalid` never fire, and are type errors. (`reset` and `invalid` don't bubble, and React handles them only for its own form elements.)
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<combo-box
|
|
11
|
+
@@ -170,14 +170,16 @@ With `@types/react` 18 the framework-agnostic typings are used, as in previous v
|
|
12
|
+
### What each runtime does
|
|
13
|
+
The props are the same everywhere, but which handlers get called, and with what, depends on the framework (verified in `tests/frameworks.spec.ts`):
|
|
14
|
+
|
|
15
|
+
-| Handler prop | React 19 | React 18 | Preact (also as `preact/compat`) | Vue |
|
|
16
|
+
-|---|---|---|---|---|
|
|
17
|
+
-| `onChange` | SyntheticEvent | **never called** | DOM event | DOM event |
|
|
18
|
+
-| `onInput` | SyntheticEvent | SyntheticEvent | DOM event | DOM event |
|
|
19
|
+
-| `onClose` / `onSearch` | never called | never called | DOM event | DOM event |
|
|
20
|
+
-| `onclose` / `onsearch` | DOM event | never called | DOM event | DOM event |
|
|
21
|
+
+| Handler prop | React 19 | React 18 | Preact (also as `preact/compat`) | Solid | Vue |
|
|
22
|
+
+|---|---|---|---|---|---|
|
|
23
|
+
+| `onChange` | SyntheticEvent | **never called** | DOM event | DOM event | DOM event |
|
|
24
|
+
+| `onInput` | SyntheticEvent | SyntheticEvent | DOM event | DOM event | DOM event |
|
|
25
|
+
+| `onClose` / `onSearch` / `onReset` / `onInvalid` | never called | never called | DOM event | DOM event | DOM event |
|
|
26
|
+
+| `onclose` / `onsearch` / `onreset` / `oninvalid` | DOM event | never called | DOM event | DOM event | DOM event |
|
|
27
|
+
|
|
28
|
+
-With React 18, listen to `change`, `close` and `search` on the element itself:
|
|
29
|
+
+In Solid, `on:close` (its explicit native-listener syntax) works too.
|
|
30
|
+
+
|
|
31
|
+
+With React 18, listen to `change`, `close`, `search`, `reset` and `invalid` on the element itself:
|
|
32
|
+
```tsx
|
|
33
|
+
function useComboboxEvent<K extends keyof ComboboxEventMap>(
|
|
34
|
+
ref: React.RefObject<HTMLElementTagNameMap['combo-box'] | null>,
|
|
@@ -1,2 +1,15 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* An ATTRIBUTE value as a boolean: present means true (`multiple=""`, `multiple="multiple"`),
|
|
3
|
+
* absent (`null`) means false. The string "false" also means false, so `multiple="false"` from a
|
|
4
|
+
* template or a framework that stringifies values doesn't turn the feature on.
|
|
5
|
+
*/
|
|
2
6
|
export declare function toBoolean(value: any): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* A PROPERTY value as a boolean, like native boolean properties (`select.multiple = ''` is false):
|
|
9
|
+
* JavaScript truthiness, except the string "false", which stays false as with attributes.
|
|
10
|
+
*
|
|
11
|
+
* Frameworks that set properties rely on this: Preact clears a removed or `undefined` prop with
|
|
12
|
+
* `element.multiple = ''`, which must turn the feature off. Attribute values (`setAttribute`,
|
|
13
|
+
* the initial attributes) go through `toBoolean` instead.
|
|
14
|
+
*/
|
|
15
|
+
export declare function propertyToBoolean(value: unknown): boolean;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.toBoolean = toBoolean;
|
|
4
|
-
|
|
4
|
+
exports.propertyToBoolean = propertyToBoolean;
|
|
5
|
+
/**
|
|
6
|
+
* An ATTRIBUTE value as a boolean: present means true (`multiple=""`, `multiple="multiple"`),
|
|
7
|
+
* absent (`null`) means false. The string "false" also means false, so `multiple="false"` from a
|
|
8
|
+
* template or a framework that stringifies values doesn't turn the feature on.
|
|
9
|
+
*/
|
|
5
10
|
function toBoolean(value) {
|
|
6
11
|
if (value == null || value === 'false' || value === false)
|
|
7
12
|
value = false;
|
|
@@ -9,3 +14,14 @@ function toBoolean(value) {
|
|
|
9
14
|
value = true;
|
|
10
15
|
return Boolean(value);
|
|
11
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* A PROPERTY value as a boolean, like native boolean properties (`select.multiple = ''` is false):
|
|
19
|
+
* JavaScript truthiness, except the string "false", which stays false as with attributes.
|
|
20
|
+
*
|
|
21
|
+
* Frameworks that set properties rely on this: Preact clears a removed or `undefined` prop with
|
|
22
|
+
* `element.multiple = ''`, which must turn the feature off. Attribute values (`setAttribute`,
|
|
23
|
+
* the initial attributes) go through `toBoolean` instead.
|
|
24
|
+
*/
|
|
25
|
+
function propertyToBoolean(value) {
|
|
26
|
+
return value !== 'false' && Boolean(value);
|
|
27
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var _a;
|
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
3
|
exports.HTMLComboboxElement = void 0;
|
|
5
4
|
const Combobox_markup_js_1 = require("./Combobox.markup.js");
|
|
@@ -11,7 +10,7 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
11
10
|
// Setters (and the setAttribute/removeAttribute overrides routed to them) only normalize and write
|
|
12
11
|
// the attribute; attributeChangedCallback applies the side effects, whatever changed the attribute
|
|
13
12
|
// (setters, native Element.prototype methods, DevTools, other JavaScript worlds).
|
|
14
|
-
static observedAttributes = [...
|
|
13
|
+
static observedAttributes = [...HTMLComboboxElement.OWN_IDL];
|
|
15
14
|
static #BOOLEAN_ATTRIBUTES = new Set(['required', 'disabled', 'clearable', 'multiple', 'filterable', 'searchable']);
|
|
16
15
|
static observerOptions = { childList: true };
|
|
17
16
|
static styleSheet = [new CSSStyleSheet];
|
|
@@ -40,7 +39,7 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
40
39
|
this.shadowRoot = this.attachShadow({ mode: 'open' });
|
|
41
40
|
this.#markup = new Combobox_markup_js_1.ComboboxMarkup(this.shadowRoot, this.#internals);
|
|
42
41
|
this.shadowRoot.appendChild(Combobox_markup_js_1.ComboboxMarkup.markup.content.cloneNode(true));
|
|
43
|
-
this.shadowRoot.adoptedStyleSheets =
|
|
42
|
+
this.shadowRoot.adoptedStyleSheets = HTMLComboboxElement.styleSheet;
|
|
44
43
|
this.#observer = new MutationObserver(this.#onOptionsChanges);
|
|
45
44
|
this.#markup.connect();
|
|
46
45
|
this.#syncPopupSemantics();
|
|
@@ -57,7 +56,7 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
57
56
|
this.shadowRoot.addEventListener('tagcleared', this.#onClearTag);
|
|
58
57
|
this.shadowRoot.addEventListener('click', this.#onOptionClick, true);
|
|
59
58
|
for (const child of Array.from(this.children)) {
|
|
60
|
-
if (
|
|
59
|
+
if (HTMLComboboxElement.#isOptionLike(child)) {
|
|
61
60
|
this.#markup.optionsContainer?.appendChild(child);
|
|
62
61
|
}
|
|
63
62
|
}
|
|
@@ -70,7 +69,7 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
70
69
|
if (!this.hasAttribute('role'))
|
|
71
70
|
super.setAttribute('role', 'combobox');
|
|
72
71
|
this.#initialAttributesSynchronization();
|
|
73
|
-
this.#observer.observe(this.#markup.optionsContainer,
|
|
72
|
+
this.#observer.observe(this.#markup.optionsContainer, HTMLComboboxElement.observerOptions);
|
|
74
73
|
this.addEventListener('keydown', this.#onKeyDown);
|
|
75
74
|
this.addEventListener('click', this.#onHostClick);
|
|
76
75
|
this.#markup.clearAllButton.addEventListener('click', this.#onClearAll);
|
|
@@ -104,7 +103,7 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
104
103
|
// - Solid mostly bypasses all of them for static subtrees (see connectedCallback above),
|
|
105
104
|
// but dynamic option lists still go through insert()/insertBefore().
|
|
106
105
|
insertBefore = (node, referenceNode) => {
|
|
107
|
-
if (
|
|
106
|
+
if (HTMLComboboxElement.#isOptionLike(node)) {
|
|
108
107
|
this.#markup.optionsContainer?.insertBefore(node, referenceNode);
|
|
109
108
|
}
|
|
110
109
|
else {
|
|
@@ -113,7 +112,7 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
113
112
|
return node;
|
|
114
113
|
};
|
|
115
114
|
appendChild = (node) => {
|
|
116
|
-
if (
|
|
115
|
+
if (HTMLComboboxElement.#isOptionLike(node)) {
|
|
117
116
|
this.#markup.optionsContainer?.appendChild(node);
|
|
118
117
|
}
|
|
119
118
|
else {
|
|
@@ -127,7 +126,7 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
127
126
|
prepend = (...nodes) => {
|
|
128
127
|
[...nodes].reverse().forEach(node => {
|
|
129
128
|
const n = typeof node === 'string' ? new Text(node) : node;
|
|
130
|
-
if (
|
|
129
|
+
if (HTMLComboboxElement.#isOptionLike(n)) {
|
|
131
130
|
this.#markup.optionsContainer?.insertBefore(n, this.#markup.optionsContainer.firstChild);
|
|
132
131
|
}
|
|
133
132
|
else {
|
|
@@ -179,7 +178,7 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
179
178
|
// A path that skipped the setters can leave e.g. multiple="false": fix it once. The removal
|
|
180
179
|
// calls back again with a valid value, which applies the change; that one isn't fixed again,
|
|
181
180
|
// so this can't loop. The side effects below are safe to repeat.
|
|
182
|
-
if (
|
|
181
|
+
if (HTMLComboboxElement.#BOOLEAN_ATTRIBUTES.has(name) && newValue !== null && !(0, Boolean_attribute_value_normalizer_js_1.toBoolean)(newValue)) {
|
|
183
182
|
super.removeAttribute(name);
|
|
184
183
|
return;
|
|
185
184
|
}
|
|
@@ -253,26 +252,26 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
253
252
|
return this.hasAttribute('clearable');
|
|
254
253
|
}
|
|
255
254
|
set clearable(value) {
|
|
256
|
-
super.toggleAttribute('clearable', (0, Boolean_attribute_value_normalizer_js_1.
|
|
255
|
+
super.toggleAttribute('clearable', (0, Boolean_attribute_value_normalizer_js_1.propertyToBoolean)(value));
|
|
257
256
|
}
|
|
258
257
|
get filterable() {
|
|
259
258
|
return this.hasAttribute('filterable');
|
|
260
259
|
}
|
|
261
260
|
set filterable(value) {
|
|
262
|
-
super.toggleAttribute('filterable', (0, Boolean_attribute_value_normalizer_js_1.
|
|
261
|
+
super.toggleAttribute('filterable', (0, Boolean_attribute_value_normalizer_js_1.propertyToBoolean)(value));
|
|
263
262
|
}
|
|
264
263
|
get searchable() {
|
|
265
264
|
return this.hasAttribute('searchable');
|
|
266
265
|
}
|
|
267
266
|
set searchable(value) {
|
|
268
|
-
super.toggleAttribute('searchable', (0, Boolean_attribute_value_normalizer_js_1.
|
|
267
|
+
super.toggleAttribute('searchable', (0, Boolean_attribute_value_normalizer_js_1.propertyToBoolean)(value));
|
|
269
268
|
}
|
|
270
269
|
// <select> specific properties (implements HTMLSelectElement)
|
|
271
270
|
get disabled() {
|
|
272
271
|
return this.hasAttribute('disabled');
|
|
273
272
|
}
|
|
274
273
|
set disabled(value) {
|
|
275
|
-
super.toggleAttribute('disabled', (0, Boolean_attribute_value_normalizer_js_1.
|
|
274
|
+
super.toggleAttribute('disabled', (0, Boolean_attribute_value_normalizer_js_1.propertyToBoolean)(value));
|
|
276
275
|
}
|
|
277
276
|
get form() {
|
|
278
277
|
return this.#internals.form;
|
|
@@ -291,7 +290,7 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
291
290
|
return this.hasAttribute('multiple');
|
|
292
291
|
}
|
|
293
292
|
set multiple(value) {
|
|
294
|
-
super.toggleAttribute('multiple', (0, Boolean_attribute_value_normalizer_js_1.
|
|
293
|
+
super.toggleAttribute('multiple', (0, Boolean_attribute_value_normalizer_js_1.propertyToBoolean)(value));
|
|
295
294
|
}
|
|
296
295
|
/** The options, as a live `HTMLOptionsCollection`-like list (like `select.options`) */
|
|
297
296
|
get options() {
|
|
@@ -305,7 +304,7 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
305
304
|
return this.hasAttribute('required');
|
|
306
305
|
}
|
|
307
306
|
set required(value) {
|
|
308
|
-
super.toggleAttribute('required', (0, Boolean_attribute_value_normalizer_js_1.
|
|
307
|
+
super.toggleAttribute('required', (0, Boolean_attribute_value_normalizer_js_1.propertyToBoolean)(value));
|
|
309
308
|
}
|
|
310
309
|
/** Index of the first selected option, or -1. Setting it selects only that option. */
|
|
311
310
|
get selectedIndex() {
|
|
@@ -396,15 +395,16 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
396
395
|
}
|
|
397
396
|
// Overwritten methods
|
|
398
397
|
setAttribute(name, value) {
|
|
399
|
-
if (
|
|
400
|
-
|
|
398
|
+
if (HTMLComboboxElement.OWN_IDL.has(name)) {
|
|
399
|
+
// through the setter, with attribute semantics: setAttribute('multiple', '') turns it on
|
|
400
|
+
Reflect.set(this, name, HTMLComboboxElement.#BOOLEAN_ATTRIBUTES.has(name) ? (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(value) : value);
|
|
401
401
|
}
|
|
402
402
|
else {
|
|
403
403
|
super.setAttribute(name, value);
|
|
404
404
|
}
|
|
405
405
|
}
|
|
406
406
|
removeAttribute(name) {
|
|
407
|
-
if (
|
|
407
|
+
if (HTMLComboboxElement.#BOOLEAN_ATTRIBUTES.has(name)) {
|
|
408
408
|
Reflect.set(this, name, false);
|
|
409
409
|
}
|
|
410
410
|
else if (name === 'placeholder' || name === 'query') {
|
|
@@ -833,12 +833,13 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
833
833
|
}
|
|
834
834
|
}
|
|
835
835
|
#initialAttributesSynchronization() {
|
|
836
|
-
for (const key of
|
|
836
|
+
for (const key of HTMLComboboxElement.OWN_IDL) {
|
|
837
837
|
// Without a value attribute, the selection comes from the options' `selected` attributes:
|
|
838
838
|
// `value = null` would clear it
|
|
839
839
|
if (key === 'value' && !this.hasAttribute('value'))
|
|
840
840
|
continue;
|
|
841
|
-
|
|
841
|
+
const attribute = this.getAttribute(key);
|
|
842
|
+
Reflect.set(this, key, HTMLComboboxElement.#BOOLEAN_ATTRIBUTES.has(key) ? (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(attribute) : attribute);
|
|
842
843
|
}
|
|
843
844
|
}
|
|
844
845
|
static loadCssFromUrls(urls) {
|
|
@@ -846,17 +847,17 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
846
847
|
}
|
|
847
848
|
static loadCssFromDocumentStyleSheets() {
|
|
848
849
|
if (document.readyState === 'complete') {
|
|
849
|
-
|
|
850
|
+
HTMLComboboxElement.#loadDocumentStyleSheets();
|
|
850
851
|
}
|
|
851
852
|
if (document.readyState === 'loading') {
|
|
852
|
-
document.addEventListener('DOMContentLoaded',
|
|
853
|
+
document.addEventListener('DOMContentLoaded', HTMLComboboxElement.#loadDocumentStyleSheets);
|
|
853
854
|
}
|
|
854
855
|
if (document.readyState === 'interactive') {
|
|
855
|
-
queueMicrotask(
|
|
856
|
+
queueMicrotask(HTMLComboboxElement.#loadDocumentStyleSheets);
|
|
856
857
|
}
|
|
857
858
|
}
|
|
858
859
|
static #loadDocumentStyleSheets() {
|
|
859
|
-
const [innerSheet] =
|
|
860
|
+
const [innerSheet] = HTMLComboboxElement.styleSheet;
|
|
860
861
|
for (const outerSheet of document.styleSheets) {
|
|
861
862
|
for (const rule of outerSheet.cssRules) {
|
|
862
863
|
innerSheet.insertRule(rule.cssText, innerSheet.cssRules.length);
|
|
@@ -865,7 +866,6 @@ class HTMLComboboxElement extends HTMLElement {
|
|
|
865
866
|
}
|
|
866
867
|
}
|
|
867
868
|
exports.HTMLComboboxElement = HTMLComboboxElement;
|
|
868
|
-
_a = HTMLComboboxElement;
|
|
869
869
|
if (!window.customElements.get('combo-box')) {
|
|
870
870
|
window.customElements.define('combo-box', HTMLComboboxElement);
|
|
871
871
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var _a;
|
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
3
|
exports.HTMLComboboxOptionElement = void 0;
|
|
5
4
|
const Boolean_attribute_value_normalizer_js_1 = require("./Boolean.attribute.value.normalizer.js");
|
|
@@ -22,6 +21,7 @@ const Boolean_attribute_value_normalizer_js_1 = require("./Boolean.attribute.val
|
|
|
22
21
|
*/
|
|
23
22
|
class HTMLComboboxOptionElement extends HTMLElement {
|
|
24
23
|
static OWN_IDL = new Set(['value', 'label', 'selected', 'disabled']);
|
|
24
|
+
static #BOOLEAN_ATTRIBUTES = new Set(['selected', 'disabled']);
|
|
25
25
|
static observedAttributes = ['selected', 'disabled'];
|
|
26
26
|
static EventInit = { bubbles: true, cancelable: true, composed: true };
|
|
27
27
|
#internals;
|
|
@@ -47,7 +47,7 @@ class HTMLComboboxOptionElement extends HTMLElement {
|
|
|
47
47
|
// (<box-option label="Plum"></box-option> displays its label through CSS, see the markup styles.)
|
|
48
48
|
if (!this.textContent)
|
|
49
49
|
this.textContent = this.getAttribute('value') ?? '';
|
|
50
|
-
this.dispatchEvent(new Event('optionselectedstatechange',
|
|
50
|
+
this.dispatchEvent(new Event('optionselectedstatechange', HTMLComboboxOptionElement.EventInit));
|
|
51
51
|
}
|
|
52
52
|
/** Like <option>: the value attribute, or the text when there is none */
|
|
53
53
|
get value() {
|
|
@@ -79,13 +79,13 @@ class HTMLComboboxOptionElement extends HTMLElement {
|
|
|
79
79
|
}
|
|
80
80
|
// Normalization only: the side effects run in attributeChangedCallback
|
|
81
81
|
set selected(value) {
|
|
82
|
-
super.toggleAttribute('selected', (0, Boolean_attribute_value_normalizer_js_1.
|
|
82
|
+
super.toggleAttribute('selected', (0, Boolean_attribute_value_normalizer_js_1.propertyToBoolean)(value));
|
|
83
83
|
}
|
|
84
84
|
get disabled() {
|
|
85
85
|
return this.hasAttribute('disabled');
|
|
86
86
|
}
|
|
87
87
|
set disabled(value) {
|
|
88
|
-
super.toggleAttribute('disabled', (0, Boolean_attribute_value_normalizer_js_1.
|
|
88
|
+
super.toggleAttribute('disabled', (0, Boolean_attribute_value_normalizer_js_1.propertyToBoolean)(value));
|
|
89
89
|
}
|
|
90
90
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
91
91
|
// A path that skipped the setters can leave a value they'd never write, e.g. selected="false".
|
|
@@ -103,7 +103,7 @@ class HTMLComboboxOptionElement extends HTMLElement {
|
|
|
103
103
|
if (name === 'selected') {
|
|
104
104
|
this.#syncSelected(now);
|
|
105
105
|
if (this.isConnected) {
|
|
106
|
-
this.dispatchEvent(new Event('optionselectedstatechange',
|
|
106
|
+
this.dispatchEvent(new Event('optionselectedstatechange', HTMLComboboxOptionElement.EventInit));
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
else {
|
|
@@ -139,13 +139,15 @@ class HTMLComboboxOptionElement extends HTMLElement {
|
|
|
139
139
|
super.setAttribute('aria-selected', ariaSelected);
|
|
140
140
|
}
|
|
141
141
|
#initialAttributesSynchronization() {
|
|
142
|
-
for (const key of
|
|
143
|
-
|
|
142
|
+
for (const key of HTMLComboboxOptionElement.OWN_IDL) {
|
|
143
|
+
const attribute = this.getAttribute(key);
|
|
144
|
+
Reflect.set(this, key, HTMLComboboxOptionElement.#BOOLEAN_ATTRIBUTES.has(key) ? (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(attribute) : attribute);
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
setAttribute(name, value) {
|
|
147
|
-
if (
|
|
148
|
-
|
|
148
|
+
if (HTMLComboboxOptionElement.OWN_IDL.has(name)) {
|
|
149
|
+
// through the setter, with attribute semantics: setAttribute('selected', '') selects
|
|
150
|
+
Reflect.set(this, name, HTMLComboboxOptionElement.#BOOLEAN_ATTRIBUTES.has(name) ? (0, Boolean_attribute_value_normalizer_js_1.toBoolean)(value) : value);
|
|
149
151
|
}
|
|
150
152
|
else {
|
|
151
153
|
super.setAttribute(name, value);
|
|
@@ -162,7 +164,6 @@ class HTMLComboboxOptionElement extends HTMLElement {
|
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
exports.HTMLComboboxOptionElement = HTMLComboboxOptionElement;
|
|
165
|
-
_a = HTMLComboboxOptionElement;
|
|
166
167
|
if (!window.customElements.get('box-option')) {
|
|
167
168
|
window.customElements.define('box-option', HTMLComboboxOptionElement);
|
|
168
169
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var _a;
|
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
3
|
exports.HTMLComboboxOptionsCollection = void 0;
|
|
5
4
|
const HTML_combobox_option_element_js_1 = require("./HTML.combobox.option.element.js");
|
|
@@ -86,7 +85,7 @@ class HTMLComboboxOptionsCollection {
|
|
|
86
85
|
const length = Number(value) >>> 0;
|
|
87
86
|
const options = Array.from(source(this).options());
|
|
88
87
|
if (length > options.length) {
|
|
89
|
-
source(this).container().append(...
|
|
88
|
+
source(this).container().append(...HTMLComboboxOptionsCollection.#blankOptions(length - options.length));
|
|
90
89
|
}
|
|
91
90
|
else {
|
|
92
91
|
options.slice(length).forEach((option) => option.remove());
|
|
@@ -161,7 +160,7 @@ class HTMLComboboxOptionsCollection {
|
|
|
161
160
|
}
|
|
162
161
|
const length = this.length;
|
|
163
162
|
if (index >= length) {
|
|
164
|
-
source(this).container().append(...
|
|
163
|
+
source(this).container().append(...HTMLComboboxOptionsCollection.#blankOptions(index - length), value);
|
|
165
164
|
return;
|
|
166
165
|
}
|
|
167
166
|
const current = this.item(index);
|
|
@@ -173,4 +172,3 @@ class HTMLComboboxOptionsCollection {
|
|
|
173
172
|
}
|
|
174
173
|
}
|
|
175
174
|
exports.HTMLComboboxOptionsCollection = HTMLComboboxOptionsCollection;
|
|
176
|
-
_a = HTMLComboboxOptionsCollection;
|