html-combobox-element 0.0.6 → 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.
@@ -1,67 +1,86 @@
1
+ import { HTMLComboboxElement as ComboboxElementClass } from './combobox/HTML.combobox.element.js';
2
+ import type { HTMLComboboxOptionsCollection } from './combobox/HTML.combobox.options.collection.js';
1
3
  export * from './combobox/index.js';
2
- interface HTMLComboboxTagElement extends HTMLElement {
3
- }
4
- interface HTMLComboboxOptionElement extends HTMLOptionElement {
5
- value: string;
6
- label: string;
7
- }
8
- interface HTMLComboboxElement extends HTMLSelectElement {
9
- /** List of selected options value attribute */
10
- valueAsArray: string[];
11
- multiple: boolean;
12
- /** The value of internal input
13
- *
14
- * Internal input is visible when either "searchable" or "filterable" attribute is presented */
15
- query: string;
16
- /** The placeholder of internal input
17
- *
18
- * Internal input is visible when either "searchable" or "filterable" attribute is presented
19
- *
20
- * When <combo-box> is empty, this value is also shown as <combo-box> placeholder itself
21
- * */
22
- placeholder: string;
23
- /** When is true, the clear-all-button is shown in the <combo-box> itself,
24
- * and the clear-button is shown for each selected tag.
25
- *
26
- * If multiple attribute is set to "true", this is also enabled.
27
- *
28
- * If multiple attribute is set to "false", i.e. the <combo-box> is used as a <select>,
29
- * then setting this attribute to "true" will make the clear-button of selected tag visible
30
- * */
31
- clearable: boolean;
32
- /** When is true, the internal input is shown.
33
- *
34
- * When user type something, available options are not filtered by this query.
35
- * You have to set an "input" listener to the <combo-box> and filter options by yourself
36
- *
37
- * Setting both "searchable" and "filterable" make no sense, the "searchable" will have priority
38
- * */
39
- searchable: boolean;
40
- /** When is true, the internal input is shown.
41
- *
42
- * When user type something, available options are filtered by this query.
43
- *
44
- * Setting both "filterable" and "searchable" make no sense, the "searchable" will have priority
45
- * */
46
- filterable: boolean;
47
- selectedOptions: HTMLCollectionOf<HTMLComboboxOptionElement>;
48
- addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLComboboxElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
49
- addEventListener<K extends keyof ComboboxEventMap>(type: K, listener: (this: HTMLComboboxElement, ev: ComboboxEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
50
- addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
51
- removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLComboboxElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
52
- removeEventListener<K extends keyof ComboboxEventMap>(type: K, listener: (this: HTMLComboboxElement, ev: ComboboxEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
53
- removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
54
- }
55
- interface ComboboxEventMap extends HTMLElementEventMap {
56
- "change": ComboboxEvent;
57
- "blur": ComboboxFocusEvent;
58
- "focus": ComboboxFocusEvent;
59
- "focusin": ComboboxFocusEvent;
60
- "focusout": ComboboxFocusEvent;
61
- "input": ComboboxInputEvent;
62
- "invalid": ComboboxEvent;
63
- "selectionchange": ComboboxEvent;
64
- "selectstart": ComboboxEvent;
4
+ export declare const HTMLComboboxElement: Omit<typeof ComboboxElementClass, 'prototype'> & {
5
+ prototype: HTMLComboboxElement;
6
+ new (): HTMLComboboxElement;
7
+ };
8
+ export type HTMLComboboxElement = globalThis.HTMLComboboxElement;
9
+ declare global {
10
+ interface HTMLComboboxTagElement extends HTMLElement {
11
+ }
12
+ interface HTMLComboboxOptionElement extends HTMLOptionElement {
13
+ value: string;
14
+ label: string;
15
+ }
16
+ interface HTMLComboboxElement extends HTMLSelectElement {
17
+ /** List of selected options value attribute */
18
+ valueAsArray: string[];
19
+ /** Whether the value differs from when the picker opened (read it in a `close` listener). */
20
+ readonly changed: boolean;
21
+ /** A live `HTMLOptionsCollection`-compatible collection (not `instanceof HTMLOptionsCollection`). */
22
+ readonly options: HTMLOptionsCollection & HTMLComboboxOptionsCollection;
23
+ multiple: boolean;
24
+ /** The value of internal input
25
+ *
26
+ * Internal input is visible when either "searchable" or "filterable" attribute is presented */
27
+ query: string;
28
+ /** The placeholder of internal input
29
+ *
30
+ * Internal input is visible when either "searchable" or "filterable" attribute is presented
31
+ *
32
+ * When <combo-box> is empty, this value is also shown as <combo-box> placeholder itself
33
+ * */
34
+ placeholder: string;
35
+ /** When is true, the clear-all-button is shown in the <combo-box> itself,
36
+ * and the clear-button is shown for each selected tag.
37
+ *
38
+ * If multiple attribute is set to "true", this is also enabled.
39
+ *
40
+ * If multiple attribute is set to "false", i.e. the <combo-box> is used as a <select>,
41
+ * then setting this attribute to "true" will make the clear-button of selected tag visible
42
+ * */
43
+ clearable: boolean;
44
+ /** When is true, the internal input is shown.
45
+ *
46
+ * When user type something, available options are not filtered by this query.
47
+ * You have to set an "input" listener to the <combo-box> and filter options by yourself
48
+ *
49
+ * Setting both "searchable" and "filterable" make no sense, the "searchable" will have priority
50
+ * */
51
+ searchable: boolean;
52
+ /** When is true, the internal input is shown.
53
+ *
54
+ * When user type something, available options are filtered by this query.
55
+ *
56
+ * Setting both "filterable" and "searchable" make no sense, the "searchable" will have priority
57
+ * */
58
+ filterable: boolean;
59
+ selectedOptions: HTMLCollectionOf<HTMLComboboxOptionElement>;
60
+ addEventListener<K extends keyof ComboboxEventMap>(type: K, listener: (this: HTMLComboboxElement, ev: ComboboxEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
61
+ addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLComboboxElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
62
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
63
+ removeEventListener<K extends keyof ComboboxEventMap>(type: K, listener: (this: HTMLComboboxElement, ev: ComboboxEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
64
+ removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLComboboxElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
65
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
66
+ }
67
+ interface ComboboxEventMap extends HTMLElementEventMap {
68
+ "change": ComboboxEvent;
69
+ "blur": ComboboxFocusEvent;
70
+ "focus": ComboboxFocusEvent;
71
+ "focusin": ComboboxFocusEvent;
72
+ "focusout": ComboboxFocusEvent;
73
+ "input": ComboboxInputEvent;
74
+ "invalid": ComboboxEvent;
75
+ "selectionchange": ComboboxEvent;
76
+ "selectstart": ComboboxEvent;
77
+ /** The picker closed (read `event.target.changed`) */
78
+ "close": ComboboxEvent;
79
+ /** Enter was pressed in the search field */
80
+ "search": ComboboxEvent;
81
+ /** The owner form was reset */
82
+ "reset": ComboboxEvent;
83
+ }
65
84
  }
66
85
  /** Own attributes/properties of `<combo-box>`, shared by every JSX integration below. */
67
86
  export interface ComboboxJsxAttributes {
@@ -109,11 +128,20 @@ export interface ComboboxJsxAttributes {
109
128
  /** A Boolean attribute indicating that at least one option must be selected. */
110
129
  required?: boolean;
111
130
  disabled?: boolean;
131
+ /** The name the value is submitted under (`<combo-box>` is form-associated). */
132
+ name?: string;
133
+ /** The id of the `<form>` this combobox belongs to, when it isn't inside it. */
134
+ form?: string;
112
135
  }
113
136
  export interface ComboboxOptionJsxAttributes {
137
+ /** The value submitted and reported (`combobox.value`, `valueAsArray`) when this option is selected. */
114
138
  value: string;
139
+ /** The option's label; defaults to its text content. */
115
140
  label?: string;
141
+ /** Initially selected. `false`, `undefined` or removing the prop deselects it. */
116
142
  selected?: boolean;
143
+ /** Can't be selected by the user. `false`, `undefined` or removing the prop enables it. */
144
+ disabled?: boolean;
117
145
  }
118
146
  /**
119
147
  * Generic HTML/JSX passthrough attributes (class, style, event handlers, `data-*`, `aria-*`, ...).
@@ -125,21 +153,72 @@ export interface ComboboxOptionJsxAttributes {
125
153
  */
126
154
  export interface ComboboxCommonAttributes {
127
155
  id?: string;
156
+ /** CSS classes (Preact, Vue, Solid, React 19). */
128
157
  class?: string;
158
+ /** CSS classes (React). */
129
159
  className?: string;
160
+ /** Inline styles, in the framework's own format (a string or an object). */
130
161
  style?: unknown;
131
162
  slot?: string;
132
163
  title?: string;
133
164
  tabIndex?: number;
134
165
  hidden?: boolean;
135
166
  children?: unknown;
167
+ /** The element: `HTMLElementTagNameMap['combo-box']` (a ref object or callback, per framework). */
136
168
  ref?: unknown;
137
169
  key?: string | number;
170
+ /**
171
+ * The user changed the selection; `event.target.value` / `.valueAsArray` is the new value.
172
+ * - Preact, Solid, Vue: called with the DOM event.
173
+ * - React 19: called with a SyntheticEvent (`event.nativeEvent` is the DOM event).
174
+ * - React 18: **never called** - listen with `ref` + `addEventListener('change', ...)`.
175
+ */
138
176
  onChange?: (event: ComboboxEvent) => void;
177
+ /**
178
+ * The user typed in the search field; `event.target.query` is the text.
179
+ * Preact, Solid, Vue: the DOM InputEvent. React 18 and 19: a SyntheticEvent.
180
+ */
139
181
  onInput?: (event: ComboboxInputEvent) => void;
182
+ /** Focus moved into the combobox. Preact, Solid, Vue: the DOM event. React: a SyntheticEvent. */
140
183
  onFocus?: (event: ComboboxFocusEvent) => void;
184
+ /** Focus left the combobox. Preact, Solid, Vue: the DOM event. React: a SyntheticEvent. */
141
185
  onBlur?: (event: ComboboxFocusEvent) => void;
186
+ /**
187
+ * Validation failed (e.g. `required` and empty) on form submit or `checkValidity()`.
188
+ * - Preact, Solid, Vue: called, in either casing (`onInvalid` or `oninvalid`), with the DOM event.
189
+ * - React 19: only as `oninvalid` (lowercase).
190
+ * - React 18: **never called** - use `ref` + `addEventListener('invalid', ...)`.
191
+ */
142
192
  onInvalid?: (event: ComboboxEvent) => void;
193
+ /** Same event as `onInvalid`. React 19 needs this lowercase spelling; React 18 never calls it. */
194
+ oninvalid?: (event: ComboboxEvent) => void;
195
+ /**
196
+ * The value was reset: the owner `<form>` was reset, or the user cleared all (`clearable`).
197
+ * - Preact, Solid, Vue: called, in either casing (`onReset` or `onreset`), with the DOM event.
198
+ * - React 19: only as `onreset` (lowercase).
199
+ * - React 18: **never called** - use `ref` + `addEventListener('reset', ...)`.
200
+ */
201
+ onReset?: (event: ComboboxEvent) => void;
202
+ /** Same event as `onReset`. React 19 needs this lowercase spelling; React 18 never calls it. */
203
+ onreset?: (event: ComboboxEvent) => void;
204
+ /**
205
+ * The picker closed; `event.target.changed` tells whether the value changed meanwhile.
206
+ * - Preact, Solid, Vue: called, in either casing (`onClose` or `onclose`), with the DOM event.
207
+ * - React 19: only as `onclose` (lowercase).
208
+ * - React 18: **never called** - use `ref` + `addEventListener('close', ...)`.
209
+ */
210
+ onClose?: (event: ComboboxEvent) => void;
211
+ /** Same event as `onClose`. React 19 needs this lowercase spelling; React 18 never calls it. */
212
+ onclose?: (event: ComboboxEvent) => void;
213
+ /**
214
+ * Enter was pressed in the search field; `event.target.query` is the text.
215
+ * - Preact, Solid, Vue: called, in either casing, with the DOM event.
216
+ * - React 19: only as `onsearch` (lowercase).
217
+ * - React 18: **never called** - use `ref` + `addEventListener('search', ...)`.
218
+ */
219
+ onSearch?: (event: ComboboxEvent) => void;
220
+ /** Same event as `onSearch`. React 19 needs this lowercase spelling; React 18 never calls it. */
221
+ onsearch?: (event: ComboboxEvent) => void;
143
222
  [key: `data-${string}`]: unknown;
144
223
  [key: `aria-${string}`]: unknown;
145
224
  [key: `on${string}`]: ((event: any) => void) | undefined;
@@ -172,30 +251,97 @@ declare global {
172
251
  }
173
252
  }
174
253
  }
254
+ // #region react-type-fallbacks
255
+ /*
256
+ * Module-private stand-ins for the React types the augmentation below uses. Inside
257
+ * `declare module 'react'` React's own HTMLAttributes/Key/Ref take precedence; these only resolve
258
+ * when React's types aren't in the program (React not installed, or - on TypeScript 7 - not
259
+ * imported by any checked file). Then the augmentation is inert, and without them a project with
260
+ * `skipLibCheck: false` would get "Cannot find name"/"Cannot find module 'react'" errors from it.
261
+ * (`import('react').X` can't be used for the same reason.)
262
+ * The declaration emitter drops them as unused (React is installed here), so
263
+ * scripts/fix-dual-package.mjs copies this region into the emitted index.d.ts files.
264
+ */
265
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
266
+ type HTMLAttributes<T> = object;
267
+ type Key = string | number | bigint;
268
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
269
+ type Ref<T> = unknown;
270
+ // #endregion react-type-fallbacks
175
271
  declare module 'react' {
176
- namespace JSX {
177
- interface IntrinsicElements {
178
- 'combo-box': ComboboxElementAttributes;
179
- 'box-option': ComboboxOptionElementAttributes;
180
- 'box-tag': ComboboxTagElementAttributes;
181
- }
272
+ /**
273
+ * The event React passes to the handlers it manages: React's SyntheticEvent (same members), with
274
+ * `target` being the element (events from the shadow DOM are retargeted to it).
275
+ */
276
+ interface ComboboxSyntheticEvent<T, E extends globalThis.Event = globalThis.Event> {
277
+ nativeEvent: E;
278
+ currentTarget: EventTarget & T;
279
+ target: EventTarget & T;
280
+ bubbles: boolean;
281
+ cancelable: boolean;
282
+ defaultPrevented: boolean;
283
+ eventPhase: number;
284
+ isTrusted: boolean;
285
+ timeStamp: number;
286
+ type: string;
287
+ preventDefault(): void;
288
+ isDefaultPrevented(): boolean;
289
+ stopPropagation(): void;
290
+ isPropagationStopped(): boolean;
291
+ persist(): void;
182
292
  }
183
- }
184
- declare module 'react/jsx-runtime' {
185
- namespace JSX {
186
- interface IntrinsicElements {
187
- 'combo-box': ComboboxElementAttributes;
188
- 'box-option': ComboboxOptionElementAttributes;
189
- 'box-tag': ComboboxTagElementAttributes;
190
- }
293
+ /** Bivariant like React's EventHandler, so React's own handler types (ChangeEventHandler...) fit. */
294
+ type ComboboxEventHandler<E> = {
295
+ bivarianceHack(event: E): void;
296
+ }['bivarianceHack'];
297
+ /** React's ClassAttributes: `key` and a typed `ref`. */
298
+ interface ComboboxClassAttributes<T> {
299
+ key?: Key | null | undefined;
300
+ ref?: Ref<T> | undefined;
191
301
  }
192
- }
193
- declare module 'react/jsx-dev-runtime' {
302
+ type ComboboxBaseHTMLAttributes<T extends EventTarget, Own> = Omit<HTMLAttributes<T>, keyof Own | 'class'> & Own & {
303
+ class?: string | undefined;
304
+ };
305
+ type ComboboxHTMLAttributes<T extends EventTarget> = ComboboxBaseHTMLAttributes<T, ComboboxJsxAttributes & {
306
+ /** The user changed the selection (`event.target.value` is the new value). */
307
+ onChange?: ComboboxEventHandler<ComboboxSyntheticEvent<T>> | undefined;
308
+ /** The user typed in the search field (`event.target.query`); `nativeEvent` is its InputEvent. */
309
+ onInput?: ComboboxEventHandler<ComboboxSyntheticEvent<T, globalThis.InputEvent>> | undefined;
310
+ /** Never called by React 19 for custom elements (the event doesn't bubble) - use `oninvalid`. */
311
+ onInvalid?: undefined;
312
+ /** Never called by React 19 for custom elements (the event doesn't bubble) - use `onreset`. */
313
+ onReset?: undefined;
314
+ /** Native listener: the picker closed (`event.target.changed`). Lowercase - `onClose` never fires. */
315
+ onclose?: ((event: ComboboxEvent) => void) | undefined;
316
+ /** Native listener: Enter in the search field. Lowercase - `onSearch` never fires. */
317
+ onsearch?: ((event: ComboboxEvent) => void) | undefined;
318
+ /** Native listener: the owner form was reset, or the user cleared all. Lowercase - `onReset` never fires. */
319
+ onreset?: ((event: ComboboxEvent) => void) | undefined;
320
+ /** Native listener: validation failed (e.g. `required` and empty). Lowercase - `onInvalid` never fires. */
321
+ oninvalid?: ((event: ComboboxEvent) => void) | undefined;
322
+ }>;
323
+ type ComboboxOptionHTMLAttributes<T extends EventTarget> = ComboboxBaseHTMLAttributes<T, ComboboxOptionJsxAttributes>;
324
+ type ComboboxTagHTMLAttributes<T extends EventTarget> = ComboboxBaseHTMLAttributes<T, {}>;
325
+ /**
326
+ * Which props to use:
327
+ * - @types/react 19: React-typed props (SyntheticEvents, typed ref, React's attributes).
328
+ * - @types/react 18 builds React.JSX.IntrinsicElements on top of the global JSX namespace, so
329
+ * its entries must stay assignable to the framework-agnostic global ones: those are kept.
330
+ * (React 18 types declare the HTML elements in the global namespace, React 19 types don't.)
331
+ * - preact/compat aliased as 'react': its JSX is Preact's, and Preact passes native DOM events,
332
+ * so the framework-agnostic props - the same ones the 'preact' augmentation declares.
333
+ * (Preact's JSX accepts `class`, React's doesn't.)
334
+ */
335
+ type ComboboxIntrinsicElement<Global, Typed> = globalThis.JSX.IntrinsicElements extends {
336
+ div: unknown;
337
+ } ? Global : JSX.IntrinsicElements extends {
338
+ div: infer Div;
339
+ } ? ('class' extends keyof Div ? Global : Typed) : Typed;
194
340
  namespace JSX {
195
341
  interface IntrinsicElements {
196
- 'combo-box': ComboboxElementAttributes;
197
- 'box-option': ComboboxOptionElementAttributes;
198
- 'box-tag': ComboboxTagElementAttributes;
342
+ 'combo-box': ComboboxIntrinsicElement<ComboboxElementAttributes, ComboboxClassAttributes<HTMLComboboxElement> & ComboboxHTMLAttributes<HTMLComboboxElement>>;
343
+ 'box-option': ComboboxIntrinsicElement<ComboboxOptionElementAttributes, ComboboxClassAttributes<HTMLComboboxOptionElement> & ComboboxOptionHTMLAttributes<HTMLComboboxOptionElement>>;
344
+ 'box-tag': ComboboxIntrinsicElement<ComboboxTagElementAttributes, ComboboxClassAttributes<HTMLComboboxTagElement> & ComboboxTagHTMLAttributes<HTMLComboboxTagElement>>;
199
345
  }
200
346
  }
201
347
  }
package/dist/cjs/index.js CHANGED
@@ -14,4 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.HTMLComboboxElement = void 0;
18
+ const HTML_combobox_element_js_1 = require("./combobox/HTML.combobox.element.js");
17
19
  __exportStar(require("./combobox/index.js"), exports);
20
+ exports.HTMLComboboxElement = HTML_combobox_element_js_1.HTMLComboboxElement;
@@ -1,4 +1,8 @@
1
- /** Transform invalid (nullish or stringed) values to correct boolean value */
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 function toBoolean(value) {
3
7
  if (value == null || value === 'false' || value === false)
4
8
  value = false;
@@ -6,3 +10,14 @@ export function toBoolean(value) {
6
10
  value = true;
7
11
  return Boolean(value);
8
12
  }
13
+ /**
14
+ * A PROPERTY value as a boolean, like native boolean properties (`select.multiple = ''` is false):
15
+ * JavaScript truthiness, except the string "false", which stays false as with attributes.
16
+ *
17
+ * Frameworks that set properties rely on this: Preact clears a removed or `undefined` prop with
18
+ * `element.multiple = ''`, which must turn the feature off. Attribute values (`setAttribute`,
19
+ * the initial attributes) go through `toBoolean` instead.
20
+ */
21
+ export function propertyToBoolean(value) {
22
+ return value !== 'false' && Boolean(value);
23
+ }
@@ -1,6 +1,6 @@
1
1
  import { ComboboxMarkup } from './Combobox.markup.js';
2
2
  import { HTMLComboboxOptionElement } from './HTML.combobox.option.element.js';
3
- import { toBoolean } from './Boolean.attribute.value.normalizer.js';
3
+ import { propertyToBoolean, toBoolean } from './Boolean.attribute.value.normalizer.js';
4
4
  import { HTMLComboboxOptionsCollection } from './HTML.combobox.options.collection.js';
5
5
  export class HTMLComboboxElement extends HTMLElement {
6
6
  static OWN_IDL = new Set(['required', 'disabled', 'clearable', 'multiple', 'filterable', 'searchable', 'value', 'placeholder', 'query']);
@@ -249,26 +249,26 @@ export class HTMLComboboxElement extends HTMLElement {
249
249
  return this.hasAttribute('clearable');
250
250
  }
251
251
  set clearable(value) {
252
- super.toggleAttribute('clearable', toBoolean(value));
252
+ super.toggleAttribute('clearable', propertyToBoolean(value));
253
253
  }
254
254
  get filterable() {
255
255
  return this.hasAttribute('filterable');
256
256
  }
257
257
  set filterable(value) {
258
- super.toggleAttribute('filterable', toBoolean(value));
258
+ super.toggleAttribute('filterable', propertyToBoolean(value));
259
259
  }
260
260
  get searchable() {
261
261
  return this.hasAttribute('searchable');
262
262
  }
263
263
  set searchable(value) {
264
- super.toggleAttribute('searchable', toBoolean(value));
264
+ super.toggleAttribute('searchable', propertyToBoolean(value));
265
265
  }
266
266
  // <select> specific properties (implements HTMLSelectElement)
267
267
  get disabled() {
268
268
  return this.hasAttribute('disabled');
269
269
  }
270
270
  set disabled(value) {
271
- super.toggleAttribute('disabled', toBoolean(value));
271
+ super.toggleAttribute('disabled', propertyToBoolean(value));
272
272
  }
273
273
  get form() {
274
274
  return this.#internals.form;
@@ -287,7 +287,7 @@ export class HTMLComboboxElement extends HTMLElement {
287
287
  return this.hasAttribute('multiple');
288
288
  }
289
289
  set multiple(value) {
290
- super.toggleAttribute('multiple', toBoolean(value));
290
+ super.toggleAttribute('multiple', propertyToBoolean(value));
291
291
  }
292
292
  /** The options, as a live `HTMLOptionsCollection`-like list (like `select.options`) */
293
293
  get options() {
@@ -301,7 +301,7 @@ export class HTMLComboboxElement extends HTMLElement {
301
301
  return this.hasAttribute('required');
302
302
  }
303
303
  set required(value) {
304
- super.toggleAttribute('required', toBoolean(value));
304
+ super.toggleAttribute('required', propertyToBoolean(value));
305
305
  }
306
306
  /** Index of the first selected option, or -1. Setting it selects only that option. */
307
307
  get selectedIndex() {
@@ -393,7 +393,8 @@ export class HTMLComboboxElement extends HTMLElement {
393
393
  // Overwritten methods
394
394
  setAttribute(name, value) {
395
395
  if (HTMLComboboxElement.OWN_IDL.has(name)) {
396
- Reflect.set(this, name, value);
396
+ // through the setter, with attribute semantics: setAttribute('multiple', '') turns it on
397
+ Reflect.set(this, name, HTMLComboboxElement.#BOOLEAN_ATTRIBUTES.has(name) ? toBoolean(value) : value);
397
398
  }
398
399
  else {
399
400
  super.setAttribute(name, value);
@@ -834,7 +835,8 @@ export class HTMLComboboxElement extends HTMLElement {
834
835
  // `value = null` would clear it
835
836
  if (key === 'value' && !this.hasAttribute('value'))
836
837
  continue;
837
- this[key] = this.getAttribute(key);
838
+ const attribute = this.getAttribute(key);
839
+ Reflect.set(this, key, HTMLComboboxElement.#BOOLEAN_ATTRIBUTES.has(key) ? toBoolean(attribute) : attribute);
838
840
  }
839
841
  }
840
842
  static loadCssFromUrls(urls) {
@@ -1,4 +1,4 @@
1
- import { toBoolean } from './Boolean.attribute.value.normalizer.js';
1
+ import { propertyToBoolean, toBoolean } from './Boolean.attribute.value.normalizer.js';
2
2
  /**
3
3
  * The `<option>` of `<combo-box>`.
4
4
  *
@@ -18,6 +18,7 @@ import { toBoolean } from './Boolean.attribute.value.normalizer.js';
18
18
  */
19
19
  export class HTMLComboboxOptionElement extends HTMLElement {
20
20
  static OWN_IDL = new Set(['value', 'label', 'selected', 'disabled']);
21
+ static #BOOLEAN_ATTRIBUTES = new Set(['selected', 'disabled']);
21
22
  static observedAttributes = ['selected', 'disabled'];
22
23
  static EventInit = { bubbles: true, cancelable: true, composed: true };
23
24
  #internals;
@@ -75,13 +76,13 @@ export class HTMLComboboxOptionElement extends HTMLElement {
75
76
  }
76
77
  // Normalization only: the side effects run in attributeChangedCallback
77
78
  set selected(value) {
78
- super.toggleAttribute('selected', toBoolean(value));
79
+ super.toggleAttribute('selected', propertyToBoolean(value));
79
80
  }
80
81
  get disabled() {
81
82
  return this.hasAttribute('disabled');
82
83
  }
83
84
  set disabled(value) {
84
- super.toggleAttribute('disabled', toBoolean(value));
85
+ super.toggleAttribute('disabled', propertyToBoolean(value));
85
86
  }
86
87
  attributeChangedCallback(name, _oldValue, newValue) {
87
88
  // A path that skipped the setters can leave a value they'd never write, e.g. selected="false".
@@ -136,12 +137,14 @@ export class HTMLComboboxOptionElement extends HTMLElement {
136
137
  }
137
138
  #initialAttributesSynchronization() {
138
139
  for (const key of HTMLComboboxOptionElement.OWN_IDL) {
139
- this[key] = this.getAttribute(key);
140
+ const attribute = this.getAttribute(key);
141
+ Reflect.set(this, key, HTMLComboboxOptionElement.#BOOLEAN_ATTRIBUTES.has(key) ? toBoolean(attribute) : attribute);
140
142
  }
141
143
  }
142
144
  setAttribute(name, value) {
143
145
  if (HTMLComboboxOptionElement.OWN_IDL.has(name)) {
144
- this[name] = value;
146
+ // through the setter, with attribute semantics: setAttribute('selected', '') selects
147
+ Reflect.set(this, name, HTMLComboboxOptionElement.#BOOLEAN_ATTRIBUTES.has(name) ? toBoolean(value) : value);
145
148
  }
146
149
  else {
147
150
  super.setAttribute(name, value);
package/dist/esm/index.js CHANGED
@@ -1 +1,3 @@
1
+ import { HTMLComboboxElement as ComboboxElementClass } from './combobox/HTML.combobox.element.js';
1
2
  export * from './combobox/index.js';
3
+ export const HTMLComboboxElement = ComboboxElementClass;
@@ -1,2 +1,15 @@
1
- /** Transform invalid (nullish or stringed) values to correct boolean value */
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;