@itenthusiasm/custom-elements 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Combobox/Combobox.css +100 -0
- package/Combobox/ComboboxField.d.ts +243 -0
- package/Combobox/ComboboxField.js +1182 -0
- package/Combobox/ComboboxListbox.d.ts +15 -0
- package/Combobox/ComboboxListbox.js +34 -0
- package/Combobox/ComboboxOption.d.ts +39 -0
- package/Combobox/ComboboxOption.js +144 -0
- package/Combobox/README.md +17 -0
- package/Combobox/SelectEnhancer.d.ts +27 -0
- package/Combobox/SelectEnhancer.js +185 -0
- package/Combobox/index.d.ts +4 -0
- package/Combobox/index.js +4 -0
- package/Combobox/types/dom.d.ts +10 -0
- package/Combobox/types/helpers.d.ts +7 -0
- package/Combobox/types/preact.d.ts +43 -0
- package/Combobox/types/react.d.ts +54 -0
- package/Combobox/types/solid.d.ts +45 -0
- package/Combobox/types/svelte.d.ts +43 -0
- package/Combobox/types/vue.d.ts +93 -0
- package/LICENSE +16 -0
- package/README.md +3 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +38 -0
- package/utils/dom.d.ts +10 -0
- package/utils/dom.js +13 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
select-enhancer {
|
|
2
|
+
--blocks: 5; /* Number of option blocks to display before needing to scroll */
|
|
3
|
+
|
|
4
|
+
position: relative;
|
|
5
|
+
display: inline-block;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
width: inherit;
|
|
8
|
+
height: inherit;
|
|
9
|
+
|
|
10
|
+
@media only screen and (min-width: 600px) {
|
|
11
|
+
--blocks: 10;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
& > [role="combobox"] {
|
|
15
|
+
--border-width: 1.5px;
|
|
16
|
+
|
|
17
|
+
display: block;
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
width: 100%;
|
|
20
|
+
height: 100%;
|
|
21
|
+
padding: 8px;
|
|
22
|
+
border: var(--border-width) solid #cecece;
|
|
23
|
+
border-radius: 4px;
|
|
24
|
+
outline: none;
|
|
25
|
+
overflow-x: auto;
|
|
26
|
+
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
white-space: pre;
|
|
29
|
+
text-align: center;
|
|
30
|
+
font-size: inherit;
|
|
31
|
+
font-family: inherit;
|
|
32
|
+
color: currentcolor;
|
|
33
|
+
background-color: white;
|
|
34
|
+
|
|
35
|
+
&[filter] {
|
|
36
|
+
cursor: auto;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&:focus {
|
|
40
|
+
border-color: dodgerblue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
& + [role="listbox"] {
|
|
44
|
+
--listbox-border: 2px;
|
|
45
|
+
--option-height: 38px;
|
|
46
|
+
--option-padding: 8px;
|
|
47
|
+
|
|
48
|
+
position: absolute;
|
|
49
|
+
z-index: 2;
|
|
50
|
+
|
|
51
|
+
box-sizing: border-box;
|
|
52
|
+
width: 100%;
|
|
53
|
+
max-height: calc(var(--option-height) * var(--blocks) + var(--listbox-border) * 2);
|
|
54
|
+
padding: 0;
|
|
55
|
+
border: var(--listbox-border) solid #cecece;
|
|
56
|
+
border-radius: 4px;
|
|
57
|
+
margin: 0;
|
|
58
|
+
|
|
59
|
+
overflow: auto;
|
|
60
|
+
background-color: white;
|
|
61
|
+
|
|
62
|
+
&:is([role="combobox"]:not([aria-expanded="true"]) + [role="listbox"]) {
|
|
63
|
+
display: none;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
& > [role="option"],
|
|
67
|
+
&:where([role="combobox"][data-bad-filter] + [role="listbox"])::after {
|
|
68
|
+
display: block;
|
|
69
|
+
box-sizing: border-box;
|
|
70
|
+
height: var(--option-height);
|
|
71
|
+
padding: var(--option-padding);
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
|
|
74
|
+
&[data-active="true"]:not([aria-selected="true"]) {
|
|
75
|
+
background-color: #bddaff; /* `background-color` for `selected` items, brightened by 70% */
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&[aria-selected="true"] {
|
|
79
|
+
color: white;
|
|
80
|
+
background-color: #2684ff;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
&[data-filtered-out] {
|
|
84
|
+
display: none;
|
|
85
|
+
visibility: hidden; /* Needed to hide filtered-out `option`s in Safari + VoiceOver (Bug in Browser) */
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* TODO: Add clearer `disabled` styles. */
|
|
89
|
+
&[aria-disabled="true"] {
|
|
90
|
+
cursor: auto;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&:where([role="combobox"][data-bad-filter] + [role="listbox"])::after {
|
|
95
|
+
content: attr(nomatchesmessage, "No options found") / "";
|
|
96
|
+
cursor: auto;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
export default ComboboxField;
|
|
2
|
+
export type ExposedInternals = Pick<ElementInternals, "labels" | "form" | "validity" | "validationMessage" | "willValidate" | "checkValidity" | "reportValidity">;
|
|
3
|
+
export type FieldPropertiesAndMethods = Pick<HTMLInputElement, "name" | "required" | "disabled" | "setCustomValidity">;
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Pick<ElementInternals,
|
|
6
|
+
"labels" | "form" | "validity" | "validationMessage" | "willValidate" | "checkValidity" | "reportValidity"
|
|
7
|
+
>} ExposedInternals
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Pick<HTMLInputElement, "name" | "required" | "disabled" | "setCustomValidity">} FieldPropertiesAndMethods
|
|
11
|
+
*/
|
|
12
|
+
/** @implements {ExposedInternals} @implements {FieldPropertiesAndMethods} */
|
|
13
|
+
declare class ComboboxField extends HTMLElement implements ExposedInternals, FieldPropertiesAndMethods {
|
|
14
|
+
/** @returns {true} */
|
|
15
|
+
static get formAssociated(): true;
|
|
16
|
+
static get observedAttributes(): readonly ["id", "required", "filter", "valueis", "nomatchesmessage", "valuemissingerror"];
|
|
17
|
+
/** Internally used to indicate when the `combobox` is actively transitioning out of {@link filter} mode. */
|
|
18
|
+
static "__#private@#filterDisabedKey": symbol;
|
|
19
|
+
static defaultNoMatchesMessage: string;
|
|
20
|
+
/**
|
|
21
|
+
* @param {MouseEvent} event
|
|
22
|
+
* @returns {void}
|
|
23
|
+
*/
|
|
24
|
+
static "__#private@#handleClick"(event: MouseEvent): void;
|
|
25
|
+
/**
|
|
26
|
+
* Used to determine if a {@link filter filterable} `combobox` was `:focus`ed by a `click` event.
|
|
27
|
+
* @param {MouseEvent} event
|
|
28
|
+
* @returns {void}
|
|
29
|
+
*/
|
|
30
|
+
static "__#private@#handleMousedown"(event: MouseEvent): void;
|
|
31
|
+
/**
|
|
32
|
+
* (For {@link filter filtered} `combobox`es only)
|
|
33
|
+
* @param {FocusEvent} event
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
36
|
+
static "__#private@#handleFocus"(event: FocusEvent): void;
|
|
37
|
+
/**
|
|
38
|
+
* @param {FocusEvent} event
|
|
39
|
+
* @returns {void}
|
|
40
|
+
*/
|
|
41
|
+
static "__#private@#handleBlur"(event: FocusEvent): void;
|
|
42
|
+
/**
|
|
43
|
+
* @param {MouseEvent} event
|
|
44
|
+
* @returns {void}
|
|
45
|
+
*/
|
|
46
|
+
static "__#private@#handleDelegatedOptionHover"(event: MouseEvent): void;
|
|
47
|
+
/**
|
|
48
|
+
* @param {MouseEvent} event
|
|
49
|
+
* @returns {void}
|
|
50
|
+
*/
|
|
51
|
+
static "__#private@#handleDelegatedOptionClick"(event: MouseEvent): void;
|
|
52
|
+
/**
|
|
53
|
+
* @param {MouseEvent} event
|
|
54
|
+
* @returns {void}
|
|
55
|
+
*/
|
|
56
|
+
static "__#private@#handleDelegatedMousedown"(event: MouseEvent): void;
|
|
57
|
+
/**
|
|
58
|
+
* @param {MutationRecord[]} mutations
|
|
59
|
+
* @returns {void}
|
|
60
|
+
*/
|
|
61
|
+
static "__#private@#preserveTextNode"(mutations: MutationRecord[]): void;
|
|
62
|
+
/**
|
|
63
|
+
* @param {MutationRecord[]} mutations
|
|
64
|
+
* @returns {void}
|
|
65
|
+
*/
|
|
66
|
+
static "__#private@#watchActiveDescendant"(mutations: MutationRecord[]): void;
|
|
67
|
+
/**
|
|
68
|
+
* @param {typeof ComboboxField.observedAttributes[number]} name
|
|
69
|
+
* @param {string | null} oldValue
|
|
70
|
+
* @param {string | null} newValue
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
attributeChangedCallback(name: (typeof ComboboxField.observedAttributes)[number], oldValue: string | null, newValue: string | null): void;
|
|
74
|
+
/** @param {string} v */
|
|
75
|
+
set value(v: string);
|
|
76
|
+
/** Sets or retrieves the `value` of the `combobox` @returns {string | null} */
|
|
77
|
+
get value(): string | null;
|
|
78
|
+
/** "On Mount" for Custom Elements @returns {void} */
|
|
79
|
+
connectedCallback(): void;
|
|
80
|
+
set noMatchesMessage(value: string);
|
|
81
|
+
/**
|
|
82
|
+
* The message displayed to users when none of the `combobox`'s `option`s match their filter.
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
get noMatchesMessage(): string;
|
|
86
|
+
/** "On Unmount" for Custom Elements @returns {void} */
|
|
87
|
+
disconnectedCallback(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Updates the {@link ComboboxOption.filteredOut `filteredOut`} property for all of the `option`s,
|
|
90
|
+
* then returns the `option`s that match the user's current filter.
|
|
91
|
+
*
|
|
92
|
+
* @returns {GetFilteredOptionsReturnType}
|
|
93
|
+
*/
|
|
94
|
+
getFilteredOptions(): {
|
|
95
|
+
/**
|
|
96
|
+
* The `option`s which match the user's current filter
|
|
97
|
+
*/
|
|
98
|
+
matchingOptions: ComboboxOption[];
|
|
99
|
+
/**
|
|
100
|
+
* (Optional): The `option` which is a candidate for
|
|
101
|
+
* automatic selection. See: {@link ComboboxField.autoselectableOption}.
|
|
102
|
+
*/
|
|
103
|
+
autoselectableOption?: ComboboxOption | undefined;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* @typedef GetFilteredOptionsReturnType
|
|
107
|
+
* @property {ComboboxOption[]} matchingOptions The `option`s which match the user's current filter
|
|
108
|
+
* @property {ComboboxOption} [autoselectableOption] (Optional): The `option` which is a candidate for
|
|
109
|
+
* automatic selection. See: {@link ComboboxField.autoselectableOption}.
|
|
110
|
+
*/
|
|
111
|
+
/**
|
|
112
|
+
* The logic used by {@link filter filterable} `combobox`es to determine if an `option` matches the user's filter.
|
|
113
|
+
*
|
|
114
|
+
* **Note**: If {@link getFilteredOptions} is overridden, this method will do nothing unless it is
|
|
115
|
+
* used directly within the new implementation.
|
|
116
|
+
*
|
|
117
|
+
* @param {ComboboxOption} option
|
|
118
|
+
* @returns {boolean}
|
|
119
|
+
*/
|
|
120
|
+
optionMatchesFilter(option: ComboboxOption): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Coerces the value and filter of the `combobox` to an empty string, and deselects the currently-selected `option`
|
|
123
|
+
* if one exists (including any `option` whose value is an empty string).
|
|
124
|
+
*
|
|
125
|
+
* @returns {void}
|
|
126
|
+
* @throws {TypeError} if the `combobox` is not {@link filter filterable}, or if its value
|
|
127
|
+
* {@link valueIs cannot be cleared}.
|
|
128
|
+
*/
|
|
129
|
+
forceEmptyValue(): void;
|
|
130
|
+
/**
|
|
131
|
+
* Retrieves the `option` with the provided `value` (if it exists)
|
|
132
|
+
* @param {string} value
|
|
133
|
+
* @returns {ComboboxOption | null}
|
|
134
|
+
*/
|
|
135
|
+
getOptionByValue(value: string): ComboboxOption | null;
|
|
136
|
+
set name(value: HTMLInputElement["name"]);
|
|
137
|
+
/** @returns {HTMLInputElement["name"]} */
|
|
138
|
+
get name(): HTMLInputElement["name"];
|
|
139
|
+
set disabled(value: HTMLInputElement["disabled"]);
|
|
140
|
+
/** @returns {HTMLInputElement["disabled"]} */
|
|
141
|
+
get disabled(): HTMLInputElement["disabled"];
|
|
142
|
+
set required(value: HTMLInputElement["required"]);
|
|
143
|
+
/** @returns {HTMLInputElement["required"]} */
|
|
144
|
+
get required(): HTMLInputElement["required"];
|
|
145
|
+
/**
|
|
146
|
+
* The `listbox` that this `combobox` controls.
|
|
147
|
+
* @returns {ListboxWithChildren<ComboboxOption>}
|
|
148
|
+
*/
|
|
149
|
+
get listbox(): ListboxWithChildren<ComboboxOption>;
|
|
150
|
+
/**
|
|
151
|
+
* The _singular_ {@link Text} Node associated with the `combobox`.
|
|
152
|
+
*
|
|
153
|
+
* To alter the `combobox`'s text content, update this node **_instead of_** using {@link textContent}.
|
|
154
|
+
* @returns {Text}
|
|
155
|
+
*/
|
|
156
|
+
get text(): Text;
|
|
157
|
+
set filter(value: boolean);
|
|
158
|
+
/** Activates a textbox that can be used to filter the list of `combobox` `option`s. @returns {boolean} */
|
|
159
|
+
get filter(): boolean;
|
|
160
|
+
set filterMethod(value: Extract<number | typeof Symbol.iterator | "length" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "valueOf" | "codePointAt" | "includes" | "endsWith" | "normalize" | "repeat" | "startsWith" | "anchor" | "big" | "blink" | "bold" | "fixed" | "fontcolor" | "fontsize" | "italics" | "link" | "small" | "strike" | "sub" | "sup" | "padStart" | "padEnd" | "trimEnd" | "trimStart" | "trimLeft" | "trimRight" | "matchAll" | "replaceAll" | "at" | "isWellFormed" | "toWellFormed", "startsWith" | "includes">);
|
|
161
|
+
/**
|
|
162
|
+
* Determines the method used to filter the `option`s as the user types.
|
|
163
|
+
* - `startsWith`: {@link String.startsWith} will be used to filter the `option`s.
|
|
164
|
+
* - `includes`: {@link String.includes} will be used to filter the `option`s.
|
|
165
|
+
*
|
|
166
|
+
* **Note**: This property does nothing if {@link optionMatchesFilter} or {@link getFilteredOptions} is overridden.
|
|
167
|
+
*
|
|
168
|
+
* @returns {Extract<keyof String, "startsWith" | "includes">}
|
|
169
|
+
*/
|
|
170
|
+
get filterMethod(): Extract<number | typeof Symbol.iterator | "length" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr" | "valueOf" | "codePointAt" | "includes" | "endsWith" | "normalize" | "repeat" | "startsWith" | "anchor" | "big" | "blink" | "bold" | "fixed" | "fontcolor" | "fontsize" | "italics" | "link" | "small" | "strike" | "sub" | "sup" | "padStart" | "padEnd" | "trimEnd" | "trimStart" | "trimLeft" | "trimRight" | "matchAll" | "replaceAll" | "at" | "isWellFormed" | "toWellFormed", "startsWith" | "includes">;
|
|
171
|
+
set valueIs(value: "unclearable" | "clearable" | "anyvalue");
|
|
172
|
+
/**
|
|
173
|
+
* Indicates how a `combobox`'s value will behave.
|
|
174
|
+
* - `unclearable`: The field's {@link value `value`} must be a string matching one of the `option`s,
|
|
175
|
+
* and it cannot be cleared. (Default when {@link filter `filter`} mode is off.)
|
|
176
|
+
* - `clearable`: The field's `value` must be a string matching one of the `option`s,
|
|
177
|
+
* but it can be cleared. (Default in `filter` mode. Requires enabling `filter` mode.)
|
|
178
|
+
* - `anyvalue`: The field's `value` can be any string, and it will automatically be set to
|
|
179
|
+
* whatever value the user types. (Requires enabling `filter` mode.)
|
|
180
|
+
*
|
|
181
|
+
* <!--
|
|
182
|
+
* TODO: Link to Documentation for More Details (like TS does for MDN). The deeper details of the behavior
|
|
183
|
+
* are too sophisticated to place them all in a JSDoc, which should be [sufficiently] clear and succinct
|
|
184
|
+
* -->
|
|
185
|
+
*
|
|
186
|
+
* @returns {"unclearable" | "clearable" | "anyvalue"}
|
|
187
|
+
*/
|
|
188
|
+
get valueIs(): "unclearable" | "clearable" | "anyvalue";
|
|
189
|
+
/**
|
|
190
|
+
* @param {string} value
|
|
191
|
+
* @returns {boolean} `true` if the `combobox` will accept the provided `value` when no corresponding `option` exists.
|
|
192
|
+
* Otherwise, returns `false`.
|
|
193
|
+
*/
|
|
194
|
+
acceptsValue(value: string): boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Returns the `option` whose `label` matches the user's most recent filter input (if one exists).
|
|
197
|
+
*
|
|
198
|
+
* Value will be `null` if:
|
|
199
|
+
* - The user's filter didn't match any `option`s
|
|
200
|
+
* - The `combobox`'s text content was altered by a `value` change
|
|
201
|
+
* - The `combobox` was just recently expanded
|
|
202
|
+
* @returns {ComboboxOption | null}
|
|
203
|
+
*/
|
|
204
|
+
get autoselectableOption(): ComboboxOption | null;
|
|
205
|
+
set valueMissingError(value: string);
|
|
206
|
+
/** The error message displayed to users when the `combobox`'s `required` constraint is broken. @returns {string} */
|
|
207
|
+
get valueMissingError(): string;
|
|
208
|
+
/** @returns {ElementInternals["labels"]} */
|
|
209
|
+
get labels(): ElementInternals["labels"];
|
|
210
|
+
/** @returns {ElementInternals["form"]} */
|
|
211
|
+
get form(): ElementInternals["form"];
|
|
212
|
+
/** @returns {ElementInternals["validity"]} */
|
|
213
|
+
get validity(): ElementInternals["validity"];
|
|
214
|
+
/** @returns {ElementInternals["validationMessage"]} */
|
|
215
|
+
get validationMessage(): ElementInternals["validationMessage"];
|
|
216
|
+
/** @returns {ElementInternals["willValidate"]} */
|
|
217
|
+
get willValidate(): ElementInternals["willValidate"];
|
|
218
|
+
checkValidity(): boolean;
|
|
219
|
+
reportValidity(): boolean;
|
|
220
|
+
setCustomValidity(error: string): void;
|
|
221
|
+
/** @returns {void} */
|
|
222
|
+
formResetCallback(): void;
|
|
223
|
+
/**
|
|
224
|
+
* @param {string} state
|
|
225
|
+
* @param {"restore" | "autocomplete"} _mode
|
|
226
|
+
* @returns {void}
|
|
227
|
+
*/
|
|
228
|
+
formStateRestoreCallback(state: string, _mode: "restore" | "autocomplete"): void;
|
|
229
|
+
/**
|
|
230
|
+
* @param {boolean} disabled
|
|
231
|
+
* @returns {void}
|
|
232
|
+
*/
|
|
233
|
+
formDisabledCallback(disabled: boolean): void;
|
|
234
|
+
/** @private @type {string | null} */ private [valueOnFocusKey];
|
|
235
|
+
/** @private @type {boolean} */ private [editingKey];
|
|
236
|
+
#private;
|
|
237
|
+
}
|
|
238
|
+
import ComboboxOption from "./ComboboxOption.js";
|
|
239
|
+
import type { ListboxWithChildren } from "./types/helpers.js";
|
|
240
|
+
/** Internally used to retrieve the value that the `combobox` had when it was focused. */
|
|
241
|
+
declare const valueOnFocusKey: unique symbol;
|
|
242
|
+
/** Internally used to determine if the `combobox`'s value is actively being modified through user's filter changes. */
|
|
243
|
+
declare const editingKey: unique symbol;
|