@c2n/autocomplete 0.0.7
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 +21 -0
- package/README.md +34 -0
- package/dist/autocomplete.js +569 -0
- package/package.json +79 -0
- package/react.d.ts +24 -0
- package/react.js +3 -0
- package/types/src/autocomplete.d.ts +248 -0
- package/types/src/autocomplete.d.ts.map +1 -0
- package/vue.d.ts +46 -0
- package/vue.js +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Nguyen Thai Vinh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @c2n/autocomplete
|
|
2
|
+
|
|
3
|
+
`<c2-autocomplete>` is a keyboard-accessible combobox composed from `c2-overlay`, `c2-list` and `c2-list-item`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @c2n/autocomplete
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<c2-autocomplete aria-label="Search" placeholder="Search…"></c2-autocomplete>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import '@c2n/autocomplete'
|
|
15
|
+
|
|
16
|
+
const autocomplete = document.querySelector('c2-autocomplete')
|
|
17
|
+
autocomplete.suggestions = [
|
|
18
|
+
{ label: 'Ada Lovelace', description: 'Platform engineering' },
|
|
19
|
+
{ label: 'Product roadmap', description: 'Updated yesterday' },
|
|
20
|
+
]
|
|
21
|
+
autocomplete.labelField = 'label'
|
|
22
|
+
autocomplete.descriptionField = 'description'
|
|
23
|
+
|
|
24
|
+
autocomplete.dataSource = async (query, signal) => {
|
|
25
|
+
const response = await fetch(`/api/suggestions?q=${encodeURIComponent(query)}`, { signal })
|
|
26
|
+
return response.json()
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
As in `c2-virtual-list`, the default row reads only `labelField` and optional `descriptionField`. No other presentation fields have special meaning. For arbitrary objects, configure `itemKey`, `labelField`, `descriptionField`, `disabledField` and `searchFields`, then assign `renderItem({ item, index, search })` for richer row contents. The contents stay wrapped in a selectable `c2-list-item`.
|
|
31
|
+
|
|
32
|
+
`selection-behavior="replace"` is the default and replaces the typed value with the selected key. Use `selection-behavior="preserve"` to keep the query unchanged and communicate selection only through `suggestion-select`; clicking the input then reopens the same results.
|
|
33
|
+
|
|
34
|
+
Add `slot="header"` and `slot="footer"` content for controls or actions around the scrolling list. Listen for `suggestion-select` to receive the original item as `event.detail.item`.
|
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import { LitElement, html, nothing, unsafeCSS } from "lit";
|
|
2
|
+
import { property, query, state } from "lit/decorators.js";
|
|
3
|
+
import { customElement } from "@c2n/core/element-helper.js";
|
|
4
|
+
import { getFieldValue } from "@c2n/core/data-helper.js";
|
|
5
|
+
import { redispatchEvent } from "@c2n/core/dom-helper.js";
|
|
6
|
+
import { arrayPropertyConverter, jsonPropertyConverter } from "@c2n/core/lit-helper.js";
|
|
7
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
8
|
+
import { live } from "lit/directives/live.js";
|
|
9
|
+
import "@c2n/list";
|
|
10
|
+
import "@c2n/list-item";
|
|
11
|
+
import "@c2n/overlay";
|
|
12
|
+
//#region src/autocomplete.scss?inline
|
|
13
|
+
var autocomplete_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: inline-block;\n vertical-align: middle;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.field {\n display: flex;\n box-sizing: border-box;\n width: 100%;\n min-height: var(--c2-autocomplete--min-height,40px);\n align-items: center;\n gap: var(--c2-autocomplete--gap,8px);\n padding: var(--c2-autocomplete--padding,8px 10px);\n color: var(--c2-autocomplete--color,#18181b);\n background: var(--c2-autocomplete--background,#ffffff);\n border: var(--c2-autocomplete--border,1px solid #bcbcc6);\n border-radius: var(--c2-autocomplete--border-radius,8px);\n transition: border 250ms cubic-bezier(0.2, 0, 0, 1), outline 250ms cubic-bezier(0.2, 0, 0, 1);\n cursor: text;\n}\n.field:hover {\n border: var(--c2-autocomplete__hover--border,1px solid #a1a1aa);\n}\n.field:focus-within {\n border: var(--c2-autocomplete__focus--border,1px solid rgb(2, 101, 220));\n outline: var(--c2-autocomplete__focus--outline,2px solid rgba(2, 101, 220, 0.4));\n outline-offset: 1px;\n}\n\n:host([disabled]) .field {\n cursor: default;\n opacity: var(--c2-autocomplete__disabled--opacity,0.38);\n}\n\n:host([readonly]) .field {\n cursor: default;\n}\n\n.input {\n min-width: 0;\n flex: 1;\n padding: 0;\n color: inherit;\n background: transparent;\n border: 0;\n outline: 0;\n font: inherit;\n font-size: var(--c2-autocomplete--font-size,14px);\n line-height: 20px;\n}\n.input::placeholder {\n color: var(--c2-autocomplete__placeholder--color,#71717a);\n}\n\n::slotted([slot=prefix-icon]),\n::slotted([slot=suffix-icon]),\n.clear {\n flex: none;\n width: var(--c2-autocomplete__icon--size,18px);\n height: var(--c2-autocomplete__icon--size,18px);\n color: var(--c2-autocomplete__icon--color,#71717a);\n}\n\n::slotted([slot=prefix-icon]),\n::slotted([slot=suffix-icon]) {\n --c2-feather-icon--size: var(--c2-autocomplete__icon--size,18px);\n --c2-mat-icon--font-size: var(--c2-autocomplete__icon--size,18px);\n}\n\n.clear {\n display: grid;\n place-items: center;\n padding: 0;\n border: 0;\n background: transparent;\n cursor: pointer;\n}\n.clear svg {\n width: 100%;\n height: 100%;\n}\n\nc2-overlay {\n --c2-overlay--offset-y: 6px;\n}\n\n.panel {\n display: flex;\n box-sizing: border-box;\n overflow: hidden;\n max-height: min(var(--c2-autocomplete__panel--max-height,320px), var(--c2-overlay--available-height, var(--c2-autocomplete__panel--max-height,320px)));\n flex-direction: column;\n color: var(--c2-autocomplete--color,#18181b);\n background: var(--c2-autocomplete__panel--background,#ffffff);\n border: var(--c2-autocomplete__panel--border,1px solid #e4e4e7);\n border-radius: var(--c2-autocomplete__panel--border-radius,8px);\n box-shadow: var(--c2-autocomplete__panel--box-shadow,0 12px 32px rgba(24, 24, 27, 0.14));\n font-size: var(--c2-autocomplete--font-size,14px);\n}\n\n.panel-header,\n.panel-footer {\n flex: none;\n}\n.panel-header[hidden],\n.panel-footer[hidden] {\n display: none;\n}\n\n.panel-header {\n padding: var(--c2-autocomplete__header--padding,10px 12px);\n color: var(--c2-autocomplete__header--color,#71717a);\n background: var(--c2-autocomplete__header--background,transparent);\n border-bottom: var(--c2-autocomplete__header--border-bottom,1px solid #e4e4e7);\n}\n\n.panel-footer {\n padding: var(--c2-autocomplete__footer--padding,10px 12px);\n color: var(--c2-autocomplete__footer--color,#71717a);\n background: var(--c2-autocomplete__footer--background,transparent);\n border-top: var(--c2-autocomplete__footer--border-top,1px solid #e4e4e7);\n}\n\n.list {\n min-height: 0;\n flex: 1 1 auto;\n --c2-list--background: transparent;\n --c2-list--padding-top: var(--c2-autocomplete__list--padding,4px);\n --c2-list--padding-right: var(--c2-autocomplete__list--padding,4px);\n --c2-list--padding-bottom: var(--c2-autocomplete__list--padding,4px);\n --c2-list--padding-left: var(--c2-autocomplete__list--padding,4px);\n --c2-list-item--min-height: var(--c2-autocomplete__option--min-height,44px);\n --c2-list-item--padding-top: var(--c2-autocomplete__option--padding-top,8px);\n --c2-list-item--padding-right: var(--c2-autocomplete__option--padding-right,10px);\n --c2-list-item--padding-bottom: var(--c2-autocomplete__option--padding-bottom,8px);\n --c2-list-item--padding-left: var(--c2-autocomplete__option--padding-left,10px);\n --c2-list-item--gap: var(--c2-autocomplete__option--gap,10px);\n --c2-list-item--border-top-left-radius: var(--c2-autocomplete__option--border-radius,6px);\n --c2-list-item--border-top-right-radius: var(--c2-autocomplete__option--border-radius,6px);\n --c2-list-item--border-bottom-left-radius: var(--c2-autocomplete__option--border-radius,6px);\n --c2-list-item--border-bottom-right-radius: var(--c2-autocomplete__option--border-radius,6px);\n --c2-list-item__selected--background: var(--c2-autocomplete__option__active--background,#f4f4f5);\n --c2-list-item__selected--color: var(--c2-autocomplete__option__active--color,#18181b);\n --c2-list-item__selected__hover--background: var(--c2-autocomplete__option__active--background,#f4f4f5);\n --c2-list-item__description--color: var(--c2-autocomplete__description--color,#71717a);\n --c2-list-item__description--font-size: var(--c2-autocomplete__description--font-size,12px);\n --c2-list-item__icon--size: var(--c2-autocomplete__icon--size,18px);\n --c2-list-item__icon--color: var(--c2-autocomplete__icon--color,#71717a);\n}\n\nmark {\n color: var(--c2-autocomplete__highlight--color,rgb(2, 101, 220));\n background: transparent;\n font-weight: var(--c2-autocomplete__highlight--font-weight,600);\n}\n\n.status {\n padding: var(--c2-autocomplete__status--padding,14px 12px);\n color: var(--c2-autocomplete__status--color,#71717a);\n text-align: center;\n}";
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorate.js
|
|
16
|
+
function __decorate(decorators, target, key, desc) {
|
|
17
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
18
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
19
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
20
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/autocomplete.ts
|
|
24
|
+
var IMPLICIT_LABEL_FIELDS = [
|
|
25
|
+
"label",
|
|
26
|
+
"name",
|
|
27
|
+
"title",
|
|
28
|
+
"value"
|
|
29
|
+
];
|
|
30
|
+
var autocompleteId = 0;
|
|
31
|
+
var Autocomplete = class Autocomplete extends LitElement {
|
|
32
|
+
constructor(..._args) {
|
|
33
|
+
super(..._args);
|
|
34
|
+
this.internals = this.attachInternals();
|
|
35
|
+
this.uid = ++autocompleteId;
|
|
36
|
+
this.defaultValue = "";
|
|
37
|
+
this.defaultValueCaptured = false;
|
|
38
|
+
this.requestSequence = 0;
|
|
39
|
+
this.customValidityMessage = "";
|
|
40
|
+
this.value = "";
|
|
41
|
+
this.name = "";
|
|
42
|
+
this.placeholder = "";
|
|
43
|
+
this.ariaLabel = null;
|
|
44
|
+
this.suggestions = [];
|
|
45
|
+
this.itemKey = "";
|
|
46
|
+
this.labelField = "";
|
|
47
|
+
this.descriptionField = "";
|
|
48
|
+
this.disabledField = "";
|
|
49
|
+
this.searchFields = [];
|
|
50
|
+
this.selectionBehavior = "replace";
|
|
51
|
+
this.debounce = 200;
|
|
52
|
+
this.minQueryLength = 1;
|
|
53
|
+
this.maxResults = 20;
|
|
54
|
+
this.showEmpty = false;
|
|
55
|
+
this.readonly = false;
|
|
56
|
+
this.disabled = false;
|
|
57
|
+
this.required = false;
|
|
58
|
+
this.open = false;
|
|
59
|
+
this.results = [];
|
|
60
|
+
this.loading = false;
|
|
61
|
+
this.focused = false;
|
|
62
|
+
this.activeIndex = -1;
|
|
63
|
+
this.disabledByForm = false;
|
|
64
|
+
this.hasHeader = false;
|
|
65
|
+
this.hasFooter = false;
|
|
66
|
+
this.handleInput = (event) => {
|
|
67
|
+
this.value = event.currentTarget.value;
|
|
68
|
+
this.selectedItem = void 0;
|
|
69
|
+
this.dispatchEvent(new CustomEvent("query-change", {
|
|
70
|
+
detail: { query: this.value },
|
|
71
|
+
bubbles: true,
|
|
72
|
+
composed: true
|
|
73
|
+
}));
|
|
74
|
+
redispatchEvent(this, event);
|
|
75
|
+
this.scheduleLoad();
|
|
76
|
+
};
|
|
77
|
+
this.handleChange = (event) => redispatchEvent(this, event);
|
|
78
|
+
this.handleFocus = () => {
|
|
79
|
+
this.focused = true;
|
|
80
|
+
if (this.value.trim().length >= this.minQueryLength) this.scheduleLoad();
|
|
81
|
+
};
|
|
82
|
+
this.handleBlur = () => {
|
|
83
|
+
this.focused = false;
|
|
84
|
+
queueMicrotask(() => {
|
|
85
|
+
if (!this.matches(":focus-within")) this.close();
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
this.handleFieldClick = () => {
|
|
89
|
+
this.focus();
|
|
90
|
+
if (this.value.trim().length < this.minQueryLength || this.effectiveDisabled || this.readonly) return;
|
|
91
|
+
if (!this.loading && this.results.length === 0 && !this.showEmpty && !this.loadError) this.scheduleLoad();
|
|
92
|
+
else {
|
|
93
|
+
if (this.activeIndex < 0) this.activeIndex = this.results.findIndex((item) => !this.isDisabled(item));
|
|
94
|
+
this.open = true;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
this.handleDocumentPointerDown = (event) => {
|
|
98
|
+
if (this.open && !event.composedPath().includes(this)) this.close();
|
|
99
|
+
};
|
|
100
|
+
this.handleDocumentFocusIn = (event) => {
|
|
101
|
+
if (this.open && !event.composedPath().includes(this)) this.close();
|
|
102
|
+
};
|
|
103
|
+
this.handleDocumentKeydown = (event) => {
|
|
104
|
+
if (this.open && event.key === "Escape") this.close();
|
|
105
|
+
};
|
|
106
|
+
this.handleKeydown = (event) => {
|
|
107
|
+
const enabledIndexes = this.results.map((item, index) => ({
|
|
108
|
+
item,
|
|
109
|
+
index
|
|
110
|
+
})).filter(({ item }) => !this.isDisabled(item));
|
|
111
|
+
if (event.key === "Escape" || event.key === "Tab") {
|
|
112
|
+
this.close();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
if (!this.open) {
|
|
118
|
+
this.scheduleLoad();
|
|
119
|
+
this.open = true;
|
|
120
|
+
}
|
|
121
|
+
if (enabledIndexes.length === 0) return;
|
|
122
|
+
const current = enabledIndexes.findIndex(({ index }) => index === this.activeIndex);
|
|
123
|
+
const offset = event.key === "ArrowDown" ? 1 : -1;
|
|
124
|
+
const next = current < 0 ? offset > 0 ? 0 : enabledIndexes.length - 1 : (current + offset + enabledIndexes.length) % enabledIndexes.length;
|
|
125
|
+
this.activeIndex = enabledIndexes[next].index;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (event.key === "Home" && this.open && enabledIndexes.length) {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
this.activeIndex = enabledIndexes[0].index;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (event.key === "End" && this.open && enabledIndexes.length) {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
this.activeIndex = enabledIndexes[enabledIndexes.length - 1].index;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (event.key === "Enter" && this.open && this.activeIndex >= 0) {
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
this.selectItem(this.results[this.activeIndex], this.activeIndex);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
this.handleListSelection = (event) => {
|
|
144
|
+
const key = event.detail.value[0];
|
|
145
|
+
const index = this.results.findIndex((item, itemIndex) => this.keyOf(item, itemIndex) === key);
|
|
146
|
+
if (index >= 0) this.selectItem(this.results[index], index);
|
|
147
|
+
};
|
|
148
|
+
this.handleClear = (event) => {
|
|
149
|
+
event.stopPropagation();
|
|
150
|
+
this.value = "";
|
|
151
|
+
this.selectedItem = void 0;
|
|
152
|
+
this.results = [];
|
|
153
|
+
this.cancelPendingRequest();
|
|
154
|
+
this.close();
|
|
155
|
+
this.input?.focus();
|
|
156
|
+
this.dispatchEvent(new CustomEvent("query-change", {
|
|
157
|
+
detail: { query: "" },
|
|
158
|
+
bubbles: true,
|
|
159
|
+
composed: true
|
|
160
|
+
}));
|
|
161
|
+
this.dispatchEvent(new Event("input", {
|
|
162
|
+
bubbles: true,
|
|
163
|
+
composed: true
|
|
164
|
+
}));
|
|
165
|
+
this.dispatchEvent(new Event("change", {
|
|
166
|
+
bubbles: true,
|
|
167
|
+
composed: true
|
|
168
|
+
}));
|
|
169
|
+
};
|
|
170
|
+
this.handleOverlayToggle = (event) => {
|
|
171
|
+
this.open = event.newState === "open";
|
|
172
|
+
if (!this.open) this.activeIndex = -1;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
static {
|
|
176
|
+
this.formAssociated = true;
|
|
177
|
+
}
|
|
178
|
+
static {
|
|
179
|
+
this.styles = unsafeCSS(autocomplete_default);
|
|
180
|
+
}
|
|
181
|
+
static {
|
|
182
|
+
this.shadowRootOptions = {
|
|
183
|
+
...LitElement.shadowRootOptions,
|
|
184
|
+
delegatesFocus: true
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
connectedCallback() {
|
|
188
|
+
super.connectedCallback();
|
|
189
|
+
this.ownerDocument.addEventListener("pointerdown", this.handleDocumentPointerDown, true);
|
|
190
|
+
this.ownerDocument.addEventListener("focusin", this.handleDocumentFocusIn, true);
|
|
191
|
+
this.ownerDocument.addEventListener("keydown", this.handleDocumentKeydown, true);
|
|
192
|
+
if (!this.defaultValueCaptured) {
|
|
193
|
+
this.defaultValue = this.getAttribute("value") ?? this.value;
|
|
194
|
+
this.defaultValueCaptured = true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
disconnectedCallback() {
|
|
198
|
+
super.disconnectedCallback();
|
|
199
|
+
this.ownerDocument.removeEventListener("pointerdown", this.handleDocumentPointerDown, true);
|
|
200
|
+
this.ownerDocument.removeEventListener("focusin", this.handleDocumentFocusIn, true);
|
|
201
|
+
this.ownerDocument.removeEventListener("keydown", this.handleDocumentKeydown, true);
|
|
202
|
+
this.cancelPendingRequest();
|
|
203
|
+
}
|
|
204
|
+
formResetCallback() {
|
|
205
|
+
this.value = this.defaultValue;
|
|
206
|
+
this.selectedItem = void 0;
|
|
207
|
+
this.close();
|
|
208
|
+
}
|
|
209
|
+
formDisabledCallback(disabled) {
|
|
210
|
+
this.disabledByForm = disabled && !this.hasAttribute("disabled");
|
|
211
|
+
}
|
|
212
|
+
formStateRestoreCallback(state) {
|
|
213
|
+
if (typeof state === "string") this.value = state;
|
|
214
|
+
}
|
|
215
|
+
get form() {
|
|
216
|
+
return this.internals.form;
|
|
217
|
+
}
|
|
218
|
+
get labels() {
|
|
219
|
+
return this.internals.labels;
|
|
220
|
+
}
|
|
221
|
+
get validity() {
|
|
222
|
+
return this.internals.validity;
|
|
223
|
+
}
|
|
224
|
+
get validationMessage() {
|
|
225
|
+
return this.internals.validationMessage;
|
|
226
|
+
}
|
|
227
|
+
get willValidate() {
|
|
228
|
+
return this.internals.willValidate;
|
|
229
|
+
}
|
|
230
|
+
checkValidity() {
|
|
231
|
+
return this.internals.checkValidity();
|
|
232
|
+
}
|
|
233
|
+
reportValidity() {
|
|
234
|
+
return this.internals.reportValidity();
|
|
235
|
+
}
|
|
236
|
+
setCustomValidity(message) {
|
|
237
|
+
this.customValidityMessage = message;
|
|
238
|
+
this.syncFormState();
|
|
239
|
+
}
|
|
240
|
+
/** Focuses the inner text input. */
|
|
241
|
+
focus(options) {
|
|
242
|
+
this.input?.focus(options);
|
|
243
|
+
}
|
|
244
|
+
/** Runs local matching or the remote data source immediately, bypassing the debounce. */
|
|
245
|
+
async load(query = this.value) {
|
|
246
|
+
this.cancelPendingRequest();
|
|
247
|
+
const normalizedQuery = query.trim();
|
|
248
|
+
if (normalizedQuery.length < this.minQueryLength || this.effectiveDisabled || this.readonly) {
|
|
249
|
+
this.results = [];
|
|
250
|
+
this.loading = false;
|
|
251
|
+
this.loadError = void 0;
|
|
252
|
+
this.close();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (!this.dataSource) {
|
|
256
|
+
this.loading = false;
|
|
257
|
+
this.loadError = void 0;
|
|
258
|
+
this.results = this.suggestions.filter((item) => this.itemMatches(item, normalizedQuery)).slice(0, Math.max(0, this.maxResults));
|
|
259
|
+
this.finishResults();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const controller = new AbortController();
|
|
263
|
+
const sequence = ++this.requestSequence;
|
|
264
|
+
this.requestController = controller;
|
|
265
|
+
this.loading = true;
|
|
266
|
+
this.loadError = void 0;
|
|
267
|
+
this.results = [];
|
|
268
|
+
this.open = this.focused;
|
|
269
|
+
try {
|
|
270
|
+
const loaded = await this.dataSource(normalizedQuery, controller.signal);
|
|
271
|
+
if (controller.signal.aborted || sequence !== this.requestSequence) return;
|
|
272
|
+
this.results = loaded.slice(0, Math.max(0, this.maxResults));
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (controller.signal.aborted || sequence !== this.requestSequence) return;
|
|
275
|
+
this.results = [];
|
|
276
|
+
this.loadError = error;
|
|
277
|
+
} finally {
|
|
278
|
+
if (!controller.signal.aborted && sequence === this.requestSequence) {
|
|
279
|
+
this.loading = false;
|
|
280
|
+
this.finishResults();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/** Closes the result panel and clears keyboard highlighting. */
|
|
285
|
+
close() {
|
|
286
|
+
this.open = false;
|
|
287
|
+
this.activeIndex = -1;
|
|
288
|
+
}
|
|
289
|
+
get effectiveDisabled() {
|
|
290
|
+
return this.disabled || this.disabledByForm;
|
|
291
|
+
}
|
|
292
|
+
get listboxId() {
|
|
293
|
+
return `c2-autocomplete-listbox-${this.uid}`;
|
|
294
|
+
}
|
|
295
|
+
get activeOptionId() {
|
|
296
|
+
return this.activeIndex >= 0 ? `${this.listboxId}-option-${this.activeIndex}` : void 0;
|
|
297
|
+
}
|
|
298
|
+
textOf(value) {
|
|
299
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
300
|
+
}
|
|
301
|
+
keyOf(item, index) {
|
|
302
|
+
const fieldValue = this.itemKey ? getFieldValue(item, this.itemKey) : void 0;
|
|
303
|
+
if (fieldValue !== void 0 && fieldValue !== null) return String(fieldValue);
|
|
304
|
+
if (item === null || typeof item !== "object") return this.textOf(item);
|
|
305
|
+
return String(index);
|
|
306
|
+
}
|
|
307
|
+
labelOf(item) {
|
|
308
|
+
if (this.labelField) return this.textOf(getFieldValue(item, this.labelField));
|
|
309
|
+
if (item === null || typeof item !== "object") return this.textOf(item);
|
|
310
|
+
const record = item;
|
|
311
|
+
for (const field of IMPLICIT_LABEL_FIELDS) if (record[field] !== void 0) return this.textOf(record[field]);
|
|
312
|
+
return "";
|
|
313
|
+
}
|
|
314
|
+
isDisabled(item) {
|
|
315
|
+
return this.disabledField ? Boolean(getFieldValue(item, this.disabledField)) : false;
|
|
316
|
+
}
|
|
317
|
+
itemMatches(item, query) {
|
|
318
|
+
const needle = query.toLocaleLowerCase();
|
|
319
|
+
if (this.matcher) return this.matcher(item, needle);
|
|
320
|
+
const fields = this.searchFields.length ? this.searchFields : [this.labelField, this.descriptionField].filter(Boolean);
|
|
321
|
+
if (fields.length) return fields.some((field) => this.textOf(getFieldValue(item, field)).toLocaleLowerCase().includes(needle));
|
|
322
|
+
if (item === null || typeof item !== "object") return this.textOf(item).toLocaleLowerCase().includes(needle);
|
|
323
|
+
return Object.values(item).some((fieldValue) => (typeof fieldValue === "string" || typeof fieldValue === "number") && String(fieldValue).toLocaleLowerCase().includes(needle));
|
|
324
|
+
}
|
|
325
|
+
finishResults() {
|
|
326
|
+
this.activeIndex = this.results.findIndex((item) => !this.isDisabled(item));
|
|
327
|
+
this.open = this.focused && (this.results.length > 0 || this.showEmpty || !!this.loadError);
|
|
328
|
+
}
|
|
329
|
+
scheduleLoad() {
|
|
330
|
+
this.cancelPendingRequest();
|
|
331
|
+
if (this.value.trim().length < this.minQueryLength) {
|
|
332
|
+
this.load();
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (!this.dataSource || this.debounce <= 0) {
|
|
336
|
+
this.load();
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
this.loading = true;
|
|
340
|
+
this.results = [];
|
|
341
|
+
this.loadError = void 0;
|
|
342
|
+
this.open = this.focused;
|
|
343
|
+
this.debounceTimer = setTimeout(() => void this.load(), this.debounce);
|
|
344
|
+
}
|
|
345
|
+
cancelPendingRequest() {
|
|
346
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
347
|
+
this.debounceTimer = void 0;
|
|
348
|
+
this.requestController?.abort();
|
|
349
|
+
this.requestController = void 0;
|
|
350
|
+
}
|
|
351
|
+
selectItem(item, index) {
|
|
352
|
+
if (this.isDisabled(item)) return;
|
|
353
|
+
const value = this.keyOf(item, index);
|
|
354
|
+
const replaceValue = this.selectionBehavior !== "preserve";
|
|
355
|
+
if (replaceValue) this.value = value;
|
|
356
|
+
this.selectedItem = item;
|
|
357
|
+
this.close();
|
|
358
|
+
this.input?.focus();
|
|
359
|
+
this.dispatchEvent(new CustomEvent("suggestion-select", {
|
|
360
|
+
detail: {
|
|
361
|
+
value,
|
|
362
|
+
item,
|
|
363
|
+
index
|
|
364
|
+
},
|
|
365
|
+
bubbles: true,
|
|
366
|
+
composed: true
|
|
367
|
+
}));
|
|
368
|
+
if (replaceValue) {
|
|
369
|
+
this.dispatchEvent(new Event("input", {
|
|
370
|
+
bubbles: true,
|
|
371
|
+
composed: true
|
|
372
|
+
}));
|
|
373
|
+
this.dispatchEvent(new Event("change", {
|
|
374
|
+
bubbles: true,
|
|
375
|
+
composed: true
|
|
376
|
+
}));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
slotHasContent(event) {
|
|
380
|
+
return event.target.assignedNodes({ flatten: true }).some((node) => node.nodeType === Node.ELEMENT_NODE || !!node.textContent?.trim());
|
|
381
|
+
}
|
|
382
|
+
renderHighlighted(text) {
|
|
383
|
+
const query = this.value.trim();
|
|
384
|
+
if (!query) return text;
|
|
385
|
+
const index = text.toLocaleLowerCase().indexOf(query.toLocaleLowerCase());
|
|
386
|
+
if (index < 0) return text;
|
|
387
|
+
return html`${text.slice(0, index)}<mark>${text.slice(index, index + query.length)}</mark>${text.slice(index + query.length)}`;
|
|
388
|
+
}
|
|
389
|
+
renderListItem(item, index) {
|
|
390
|
+
const label = this.labelOf(item);
|
|
391
|
+
const description = this.descriptionField ? this.textOf(getFieldValue(item, this.descriptionField)) : "";
|
|
392
|
+
const customContent = this.renderItem?.({
|
|
393
|
+
item,
|
|
394
|
+
index,
|
|
395
|
+
search: this.value
|
|
396
|
+
});
|
|
397
|
+
return html`<c2-list-item
|
|
398
|
+
id=${`${this.listboxId}-option-${index}`}
|
|
399
|
+
data-index=${index}
|
|
400
|
+
.value=${this.keyOf(item, index)}
|
|
401
|
+
.label=${label}
|
|
402
|
+
.data=${item}
|
|
403
|
+
.disabled=${this.isDisabled(item)}
|
|
404
|
+
@pointerenter=${() => {
|
|
405
|
+
if (!this.isDisabled(item)) this.activeIndex = index;
|
|
406
|
+
}}
|
|
407
|
+
>${customContent ?? html`${this.renderHighlighted(label)}${description ? html`<span slot="description">${description}</span>` : nothing}`}</c2-list-item
|
|
408
|
+
>`;
|
|
409
|
+
}
|
|
410
|
+
renderRows() {
|
|
411
|
+
if (this.loading) return html`<div class="status" role="status"><slot name="loading">Loading suggestions…</slot></div>`;
|
|
412
|
+
if (this.loadError) return html`<div class="status status--error" role="status"><slot name="error">Could not load suggestions.</slot></div>`;
|
|
413
|
+
if (this.results.length === 0) return html`<div class="status" role="status"><slot name="empty">No suggestions found.</slot></div>`;
|
|
414
|
+
return this.results.map((item, index) => this.renderListItem(item, index));
|
|
415
|
+
}
|
|
416
|
+
syncFormState() {
|
|
417
|
+
this.internals.setFormValue(this.effectiveDisabled ? null : this.value, this.value);
|
|
418
|
+
if (this.effectiveDisabled) {
|
|
419
|
+
this.internals.setValidity({});
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (this.customValidityMessage) this.internals.setValidity({ customError: true }, this.customValidityMessage, this.input);
|
|
423
|
+
else if (this.required && !this.value) this.internals.setValidity({ valueMissing: true }, "Please fill out this field.", this.input);
|
|
424
|
+
else this.internals.setValidity({});
|
|
425
|
+
}
|
|
426
|
+
updated(changed) {
|
|
427
|
+
if (changed.has("suggestions") && !this.dataSource && this.focused && this.value.trim().length >= this.minQueryLength) this.load();
|
|
428
|
+
if (changed.has("open") && this.overlay && this.overlay.open !== this.open) this.overlay.open = this.open;
|
|
429
|
+
if (changed.has("activeIndex") && this.activeIndex >= 0) this.renderRoot.querySelector(`#${this.activeOptionId}`)?.scrollIntoView({ block: "nearest" });
|
|
430
|
+
this.syncFormState();
|
|
431
|
+
}
|
|
432
|
+
render() {
|
|
433
|
+
const selectedValue = this.activeIndex >= 0 ? [this.keyOf(this.results[this.activeIndex], this.activeIndex)] : [];
|
|
434
|
+
return html`<div class="field" @click=${this.handleFieldClick}>
|
|
435
|
+
<slot name="prefix-icon"></slot>
|
|
436
|
+
<input
|
|
437
|
+
class="input"
|
|
438
|
+
type="text"
|
|
439
|
+
role="combobox"
|
|
440
|
+
autocomplete="off"
|
|
441
|
+
aria-autocomplete="list"
|
|
442
|
+
aria-label=${ifDefined(this.ariaLabel || void 0)}
|
|
443
|
+
aria-controls=${this.listboxId}
|
|
444
|
+
aria-expanded=${this.open ? "true" : "false"}
|
|
445
|
+
aria-activedescendant=${ifDefined(this.activeOptionId)}
|
|
446
|
+
aria-busy=${this.loading ? "true" : "false"}
|
|
447
|
+
placeholder=${this.placeholder || nothing}
|
|
448
|
+
.value=${live(this.value)}
|
|
449
|
+
?disabled=${this.effectiveDisabled}
|
|
450
|
+
?readonly=${this.readonly}
|
|
451
|
+
?required=${this.required}
|
|
452
|
+
@input=${this.handleInput}
|
|
453
|
+
@change=${this.handleChange}
|
|
454
|
+
@focus=${this.handleFocus}
|
|
455
|
+
@blur=${this.handleBlur}
|
|
456
|
+
@keydown=${this.handleKeydown}
|
|
457
|
+
/>
|
|
458
|
+
${this.value && !this.effectiveDisabled && !this.readonly ? html`<button class="clear" type="button" aria-label="Clear" tabindex="-1" @click=${this.handleClear}>
|
|
459
|
+
<slot name="clear-icon">
|
|
460
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
461
|
+
<path d="m6 6 12 12M18 6 6 18"></path>
|
|
462
|
+
</svg>
|
|
463
|
+
</slot>
|
|
464
|
+
</button>` : nothing}
|
|
465
|
+
<slot name="suffix-icon"></slot>
|
|
466
|
+
</div>
|
|
467
|
+
<c2-overlay
|
|
468
|
+
popover="manual"
|
|
469
|
+
fit-anchor
|
|
470
|
+
.anchor=${this.renderRoot.querySelector(".field")}
|
|
471
|
+
.open=${this.open}
|
|
472
|
+
@toggle=${this.handleOverlayToggle}
|
|
473
|
+
>
|
|
474
|
+
<div class="panel">
|
|
475
|
+
<header class="panel-header" ?hidden=${!this.hasHeader}>
|
|
476
|
+
<slot name="header" @slotchange=${(event) => this.hasHeader = this.slotHasContent(event)}></slot>
|
|
477
|
+
</header>
|
|
478
|
+
<c2-list
|
|
479
|
+
id=${this.listboxId}
|
|
480
|
+
class="list"
|
|
481
|
+
required
|
|
482
|
+
aria-label=${ifDefined(this.ariaLabel ? `${this.ariaLabel} suggestions` : "Suggestions")}
|
|
483
|
+
.value=${selectedValue}
|
|
484
|
+
@pointerdown=${(event) => event.preventDefault()}
|
|
485
|
+
@selection-change=${this.handleListSelection}
|
|
486
|
+
>${this.renderRows()}</c2-list
|
|
487
|
+
>
|
|
488
|
+
<footer class="panel-footer" ?hidden=${!this.hasFooter}>
|
|
489
|
+
<slot name="footer" @slotchange=${(event) => this.hasFooter = this.slotHasContent(event)}></slot>
|
|
490
|
+
</footer>
|
|
491
|
+
</div>
|
|
492
|
+
</c2-overlay>`;
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
__decorate([property({ type: String })], Autocomplete.prototype, "value", void 0);
|
|
496
|
+
__decorate([property({ type: String })], Autocomplete.prototype, "name", void 0);
|
|
497
|
+
__decorate([property({ type: String })], Autocomplete.prototype, "placeholder", void 0);
|
|
498
|
+
__decorate([property({ attribute: "aria-label" })], Autocomplete.prototype, "ariaLabel", void 0);
|
|
499
|
+
__decorate([property({ converter: jsonPropertyConverter })], Autocomplete.prototype, "suggestions", void 0);
|
|
500
|
+
__decorate([property({
|
|
501
|
+
type: String,
|
|
502
|
+
attribute: "item-key"
|
|
503
|
+
})], Autocomplete.prototype, "itemKey", void 0);
|
|
504
|
+
__decorate([property({
|
|
505
|
+
type: String,
|
|
506
|
+
attribute: "label-field"
|
|
507
|
+
})], Autocomplete.prototype, "labelField", void 0);
|
|
508
|
+
__decorate([property({
|
|
509
|
+
type: String,
|
|
510
|
+
attribute: "description-field"
|
|
511
|
+
})], Autocomplete.prototype, "descriptionField", void 0);
|
|
512
|
+
__decorate([property({
|
|
513
|
+
type: String,
|
|
514
|
+
attribute: "disabled-field"
|
|
515
|
+
})], Autocomplete.prototype, "disabledField", void 0);
|
|
516
|
+
__decorate([property({
|
|
517
|
+
converter: arrayPropertyConverter,
|
|
518
|
+
attribute: "search-fields"
|
|
519
|
+
})], Autocomplete.prototype, "searchFields", void 0);
|
|
520
|
+
__decorate([property({ attribute: false })], Autocomplete.prototype, "matcher", void 0);
|
|
521
|
+
__decorate([property({ attribute: false })], Autocomplete.prototype, "dataSource", void 0);
|
|
522
|
+
__decorate([property({ attribute: false })], Autocomplete.prototype, "renderItem", void 0);
|
|
523
|
+
__decorate([property({
|
|
524
|
+
attribute: "selection-behavior",
|
|
525
|
+
reflect: true
|
|
526
|
+
})], Autocomplete.prototype, "selectionBehavior", void 0);
|
|
527
|
+
__decorate([property({ type: Number })], Autocomplete.prototype, "debounce", void 0);
|
|
528
|
+
__decorate([property({
|
|
529
|
+
type: Number,
|
|
530
|
+
attribute: "min-query-length"
|
|
531
|
+
})], Autocomplete.prototype, "minQueryLength", void 0);
|
|
532
|
+
__decorate([property({
|
|
533
|
+
type: Number,
|
|
534
|
+
attribute: "max-results"
|
|
535
|
+
})], Autocomplete.prototype, "maxResults", void 0);
|
|
536
|
+
__decorate([property({
|
|
537
|
+
type: Boolean,
|
|
538
|
+
attribute: "show-empty"
|
|
539
|
+
})], Autocomplete.prototype, "showEmpty", void 0);
|
|
540
|
+
__decorate([property({
|
|
541
|
+
type: Boolean,
|
|
542
|
+
reflect: true
|
|
543
|
+
})], Autocomplete.prototype, "readonly", void 0);
|
|
544
|
+
__decorate([property({
|
|
545
|
+
type: Boolean,
|
|
546
|
+
reflect: true
|
|
547
|
+
})], Autocomplete.prototype, "disabled", void 0);
|
|
548
|
+
__decorate([property({
|
|
549
|
+
type: Boolean,
|
|
550
|
+
reflect: true
|
|
551
|
+
})], Autocomplete.prototype, "required", void 0);
|
|
552
|
+
__decorate([property({
|
|
553
|
+
type: Boolean,
|
|
554
|
+
reflect: true
|
|
555
|
+
})], Autocomplete.prototype, "open", void 0);
|
|
556
|
+
__decorate([property({ attribute: false })], Autocomplete.prototype, "selectedItem", void 0);
|
|
557
|
+
__decorate([state()], Autocomplete.prototype, "results", void 0);
|
|
558
|
+
__decorate([state()], Autocomplete.prototype, "loading", void 0);
|
|
559
|
+
__decorate([state()], Autocomplete.prototype, "loadError", void 0);
|
|
560
|
+
__decorate([state()], Autocomplete.prototype, "focused", void 0);
|
|
561
|
+
__decorate([state()], Autocomplete.prototype, "activeIndex", void 0);
|
|
562
|
+
__decorate([state()], Autocomplete.prototype, "disabledByForm", void 0);
|
|
563
|
+
__decorate([state()], Autocomplete.prototype, "hasHeader", void 0);
|
|
564
|
+
__decorate([state()], Autocomplete.prototype, "hasFooter", void 0);
|
|
565
|
+
__decorate([query(".input")], Autocomplete.prototype, "input", void 0);
|
|
566
|
+
__decorate([query("c2-overlay")], Autocomplete.prototype, "overlay", void 0);
|
|
567
|
+
Autocomplete = __decorate([customElement("c2-autocomplete")], Autocomplete);
|
|
568
|
+
//#endregion
|
|
569
|
+
export { Autocomplete };
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@c2n/autocomplete",
|
|
3
|
+
"version": "0.0.7",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/autocomplete.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./types/src/autocomplete.d.ts",
|
|
9
|
+
"default": "./dist/autocomplete.js"
|
|
10
|
+
},
|
|
11
|
+
"./react": {
|
|
12
|
+
"types": "./react.d.ts",
|
|
13
|
+
"default": "./react.js"
|
|
14
|
+
},
|
|
15
|
+
"./vue": {
|
|
16
|
+
"types": "./vue.d.ts",
|
|
17
|
+
"default": "./vue.js"
|
|
18
|
+
},
|
|
19
|
+
"./custom-elements.json": "./custom-elements.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"types",
|
|
24
|
+
"react.d.ts",
|
|
25
|
+
"react.js",
|
|
26
|
+
"vue.d.ts",
|
|
27
|
+
"vue.js"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"autocomplete",
|
|
31
|
+
"web component",
|
|
32
|
+
"lit"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"author": "code2nguyen@gmail.com",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"registry": "https://registry.npmjs.org",
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/code2nguyen/web-components.git"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"dev": "vite",
|
|
46
|
+
"build": "wireit",
|
|
47
|
+
"build:only": "vite build",
|
|
48
|
+
"type-check": "wireit"
|
|
49
|
+
},
|
|
50
|
+
"wireit": {
|
|
51
|
+
"type-check": {
|
|
52
|
+
"dependencies": [
|
|
53
|
+
"../../core:build",
|
|
54
|
+
"../list:build",
|
|
55
|
+
"../list-item:build",
|
|
56
|
+
"../overlay:build"
|
|
57
|
+
],
|
|
58
|
+
"command": "tsc -p tsconfig.lib.json --composite false"
|
|
59
|
+
},
|
|
60
|
+
"build": {
|
|
61
|
+
"dependencies": [
|
|
62
|
+
"type-check"
|
|
63
|
+
],
|
|
64
|
+
"command": "vite build"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@c2n/core": "0.0.7",
|
|
69
|
+
"@c2n/list": "0.0.7",
|
|
70
|
+
"@c2n/list-item": "0.0.7",
|
|
71
|
+
"@c2n/overlay": "0.0.7",
|
|
72
|
+
"lit": "3.3.3"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@c2n/config": "*"
|
|
76
|
+
},
|
|
77
|
+
"customElements": "custom-elements.json",
|
|
78
|
+
"gitHead": "c8db398a27f7cd417d665fdf5da455fee2d4e910"
|
|
79
|
+
}
|
package/react.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// GENERATED by @c2n/framework-types. Do not edit by hand.
|
|
2
|
+
//
|
|
3
|
+
// JSX types for the custom elements of @c2n/autocomplete. Import once, anywhere in the program:
|
|
4
|
+
//
|
|
5
|
+
// import '@c2n/autocomplete/react'
|
|
6
|
+
//
|
|
7
|
+
// Register the elements at module scope before React renders, or React writes an object prop as an
|
|
8
|
+
// attribute and it stringifies.
|
|
9
|
+
|
|
10
|
+
import type { DetailedHTMLProps, HTMLAttributes } from 'react'
|
|
11
|
+
import type { Autocomplete } from '@c2n/autocomplete'
|
|
12
|
+
|
|
13
|
+
/** Standard React host-element attributes plus the element's own public properties. */
|
|
14
|
+
type C2Props<T> = DetailedHTMLProps<HTMLAttributes<T>, T> & Partial<Omit<T, keyof HTMLElement>>
|
|
15
|
+
|
|
16
|
+
declare module 'react' {
|
|
17
|
+
namespace JSX {
|
|
18
|
+
interface IntrinsicElements {
|
|
19
|
+
'c2-autocomplete': C2Props<Autocomplete>
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {}
|
package/react.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { LitElement, type PropertyValues } from 'lit';
|
|
2
|
+
import type { TypedAddEventListener, TypedRemoveEventListener } from '@c2n/core/event-helper.js';
|
|
3
|
+
import '@c2n/list';
|
|
4
|
+
import '@c2n/list-item';
|
|
5
|
+
import '@c2n/overlay';
|
|
6
|
+
/** Convenient default shape; `suggestions` and `dataSource` may also contain any other item type. */
|
|
7
|
+
export interface AutocompleteSuggestion {
|
|
8
|
+
label: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface AutocompleteItemContext {
|
|
12
|
+
/** Original item returned by `suggestions` or `dataSource`. */
|
|
13
|
+
item: unknown;
|
|
14
|
+
/** Index after filtering. */
|
|
15
|
+
index: number;
|
|
16
|
+
/** Current query, so custom renderers can highlight it. */
|
|
17
|
+
search: string;
|
|
18
|
+
}
|
|
19
|
+
/** Returns anything Lit can render. The result is placed inside a real `c2-list-item`. */
|
|
20
|
+
export type AutocompleteItemRenderer = (context: AutocompleteItemContext) => unknown;
|
|
21
|
+
export type AutocompleteMatcher = (item: unknown, query: string) => boolean;
|
|
22
|
+
export type AutocompleteDataSource = (query: string, signal: AbortSignal) => unknown[] | Promise<unknown[]>;
|
|
23
|
+
export type AutocompleteSelectionBehavior = 'preserve' | 'replace';
|
|
24
|
+
export interface AutocompleteSelectEventDetail {
|
|
25
|
+
value: string;
|
|
26
|
+
item: unknown;
|
|
27
|
+
index: number;
|
|
28
|
+
}
|
|
29
|
+
/** Events fired by {@link Autocomplete}, keyed for `addEventListener`. */
|
|
30
|
+
export interface AutocompleteEventMap {
|
|
31
|
+
'query-change': CustomEvent<{
|
|
32
|
+
query: string;
|
|
33
|
+
}>;
|
|
34
|
+
'suggestion-select': CustomEvent<AutocompleteSelectEventDetail>;
|
|
35
|
+
input: InputEvent;
|
|
36
|
+
change: Event;
|
|
37
|
+
}
|
|
38
|
+
export interface Autocomplete {
|
|
39
|
+
addEventListener: TypedAddEventListener<Autocomplete, AutocompleteEventMap>;
|
|
40
|
+
removeEventListener: TypedRemoveEventListener<Autocomplete, AutocompleteEventMap>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A combobox backed by a composed `c2-list` inside `c2-overlay`. As in `c2-virtual-list`, arbitrary objects use
|
|
44
|
+
* `itemKey`, `labelField`, `descriptionField` and `disabledField`; `renderItem({ item, index, search })` replaces only
|
|
45
|
+
* the row content, so selection, keyboard behaviour and accessibility remain owned by the surrounding
|
|
46
|
+
* `c2-list-item`.
|
|
47
|
+
*
|
|
48
|
+
* Local suggestions are filtered in the browser. `dataSource(query, signal)` delegates matching to a server and is
|
|
49
|
+
* debounced; every superseded request is aborted. The overlay is structurally a header slot, the list, and a footer
|
|
50
|
+
* slot, with loading / empty / error states rendered inside the list surface.
|
|
51
|
+
*
|
|
52
|
+
* @tag c2-autocomplete
|
|
53
|
+
*
|
|
54
|
+
* @slot prefix-icon - Icon or adornment shown before the text input.
|
|
55
|
+
* @slot suffix-icon - Icon or adornment shown after the clear button.
|
|
56
|
+
* @slot clear-icon - Replaces the default cross in the clear button.
|
|
57
|
+
* @slot header - Content above the suggestion list, such as filter chips or a result summary.
|
|
58
|
+
* @slot footer - Content below the suggestion list, such as an “all results” action.
|
|
59
|
+
* @slot loading - Replaces the loading message inside the list.
|
|
60
|
+
* @slot empty - Replaces the empty-result message inside the list.
|
|
61
|
+
* @slot error - Replaces the remote-source error message inside the list.
|
|
62
|
+
*
|
|
63
|
+
* @event {CustomEvent<{ query: string }>} query-change - Fired on every user edit, before local filtering or a remote request.
|
|
64
|
+
* @event {CustomEvent<AutocompleteSelectEventDetail>} suggestion-select - Fired after a row is chosen. The detail contains the selected key, original item and visible index.
|
|
65
|
+
* @event {InputEvent} input - Re-dispatched from the inner input on every edit; also fired after a suggestion replaces the value.
|
|
66
|
+
* @event {Event} change - Fired after the typed value is committed or a suggestion replaces it.
|
|
67
|
+
*
|
|
68
|
+
* @cssproperty {pixel} [--c2-autocomplete--min-height=40px]
|
|
69
|
+
* @cssproperty {padding} [--c2-autocomplete--padding=8px 10px]
|
|
70
|
+
* @cssproperty {pixel} [--c2-autocomplete--gap=8px]
|
|
71
|
+
* @cssproperty {color} [--c2-autocomplete--background=#ffffff]
|
|
72
|
+
* @cssproperty {color} [--c2-autocomplete--color=#18181b]
|
|
73
|
+
* @cssproperty {font-size} [--c2-autocomplete--font-size=14px]
|
|
74
|
+
* @cssproperty {border} [--c2-autocomplete--border=1px solid #bcbcc6]
|
|
75
|
+
* @cssproperty {border-radius} [--c2-autocomplete--border-radius=8px]
|
|
76
|
+
* @cssproperty {border} [--c2-autocomplete__hover--border=1px solid #a1a1aa]
|
|
77
|
+
* @cssproperty {border} [--c2-autocomplete__focus--border=1px solid rgb(2, 101, 220)]
|
|
78
|
+
* @cssproperty {outline} [--c2-autocomplete__focus--outline=2px solid rgba(2, 101, 220, 0.4)]
|
|
79
|
+
* @cssproperty {color} [--c2-autocomplete__placeholder--color=#71717a]
|
|
80
|
+
* @cssproperty {opacity} [--c2-autocomplete__disabled--opacity=0.38]
|
|
81
|
+
* @cssproperty {pixel} [--c2-autocomplete__icon--size=18px]
|
|
82
|
+
* @cssproperty {color} [--c2-autocomplete__icon--color=#71717a]
|
|
83
|
+
* @cssproperty {color} [--c2-autocomplete__panel--background=#ffffff]
|
|
84
|
+
* @cssproperty {border} [--c2-autocomplete__panel--border=1px solid #e4e4e7]
|
|
85
|
+
* @cssproperty {border-radius} [--c2-autocomplete__panel--border-radius=8px]
|
|
86
|
+
* @cssproperty {box-shadow} [--c2-autocomplete__panel--box-shadow=0 12px 32px rgba(24, 24, 27, 0.14)]
|
|
87
|
+
* @cssproperty {pixel} [--c2-autocomplete__panel--max-height=320px]
|
|
88
|
+
* @cssproperty {padding} [--c2-autocomplete__list--padding=4px]
|
|
89
|
+
* @cssproperty {color} [--c2-autocomplete__header--background=transparent]
|
|
90
|
+
* @cssproperty {color} [--c2-autocomplete__header--color=#71717a]
|
|
91
|
+
* @cssproperty {padding} [--c2-autocomplete__header--padding=10px 12px]
|
|
92
|
+
* @cssproperty {border} [--c2-autocomplete__header--border-bottom=1px solid #e4e4e7]
|
|
93
|
+
* @cssproperty {color} [--c2-autocomplete__footer--background=transparent]
|
|
94
|
+
* @cssproperty {color} [--c2-autocomplete__footer--color=#71717a]
|
|
95
|
+
* @cssproperty {padding} [--c2-autocomplete__footer--padding=10px 12px]
|
|
96
|
+
* @cssproperty {border} [--c2-autocomplete__footer--border-top=1px solid #e4e4e7]
|
|
97
|
+
* @cssproperty {pixel} [--c2-autocomplete__option--min-height=44px]
|
|
98
|
+
* @cssproperty {pixel} [--c2-autocomplete__option--padding-top=8px]
|
|
99
|
+
* @cssproperty {pixel} [--c2-autocomplete__option--padding-right=10px]
|
|
100
|
+
* @cssproperty {pixel} [--c2-autocomplete__option--padding-bottom=8px]
|
|
101
|
+
* @cssproperty {pixel} [--c2-autocomplete__option--padding-left=10px]
|
|
102
|
+
* @cssproperty {pixel} [--c2-autocomplete__option--gap=10px]
|
|
103
|
+
* @cssproperty {border-radius} [--c2-autocomplete__option--border-radius=6px]
|
|
104
|
+
* @cssproperty {color} [--c2-autocomplete__option__active--background=#f4f4f5]
|
|
105
|
+
* @cssproperty {color} [--c2-autocomplete__option__active--color=#18181b]
|
|
106
|
+
* @cssproperty {color} [--c2-autocomplete__description--color=#71717a]
|
|
107
|
+
* @cssproperty {font-size} [--c2-autocomplete__description--font-size=12px]
|
|
108
|
+
* @cssproperty {color} [--c2-autocomplete__highlight--color=rgb(2, 101, 220)]
|
|
109
|
+
* @cssproperty {font-weight} [--c2-autocomplete__highlight--font-weight=600]
|
|
110
|
+
* @cssproperty {color} [--c2-autocomplete__status--color=#71717a]
|
|
111
|
+
* @cssproperty {padding} [--c2-autocomplete__status--padding=14px 12px]
|
|
112
|
+
*
|
|
113
|
+
* @internalcomponent c2-overlay
|
|
114
|
+
* @internalcomponent c2-list
|
|
115
|
+
* @internalcomponent c2-list-item
|
|
116
|
+
*/
|
|
117
|
+
export declare class Autocomplete extends LitElement {
|
|
118
|
+
static formAssociated: boolean;
|
|
119
|
+
static styles: import("lit").CSSResult;
|
|
120
|
+
static shadowRootOptions: {
|
|
121
|
+
delegatesFocus: boolean;
|
|
122
|
+
clonable?: boolean;
|
|
123
|
+
customElementRegistry?: CustomElementRegistry | null;
|
|
124
|
+
mode: ShadowRootMode;
|
|
125
|
+
serializable?: boolean;
|
|
126
|
+
slotAssignment?: SlotAssignmentMode;
|
|
127
|
+
};
|
|
128
|
+
private readonly internals;
|
|
129
|
+
private readonly uid;
|
|
130
|
+
private defaultValue;
|
|
131
|
+
private defaultValueCaptured;
|
|
132
|
+
private debounceTimer;
|
|
133
|
+
private requestController;
|
|
134
|
+
private requestSequence;
|
|
135
|
+
private customValidityMessage;
|
|
136
|
+
/** Current input text and form value. A selection replaces it only when `selectionBehavior` is `replace`. */
|
|
137
|
+
value: string;
|
|
138
|
+
/** Input name used for form submission. */
|
|
139
|
+
name: string;
|
|
140
|
+
/** Text shown while the input is empty. */
|
|
141
|
+
placeholder: string;
|
|
142
|
+
/** Accessible name forwarded to the combobox input and composed list. */
|
|
143
|
+
ariaLabel: string | null;
|
|
144
|
+
/** Local items to search. An array in the property, JSON in the attribute; items may have any structure. */
|
|
145
|
+
suggestions: unknown[];
|
|
146
|
+
/** Field used as a result's stable value. An empty value falls back to a primitive item or its index. */
|
|
147
|
+
itemKey: string;
|
|
148
|
+
/** Field used as primary row text. Defaults to `label`, `name`, `title` or `value`. */
|
|
149
|
+
labelField: string;
|
|
150
|
+
/** Field used for the optional secondary row line. */
|
|
151
|
+
descriptionField: string;
|
|
152
|
+
/** Field whose truthy value makes a row unavailable. */
|
|
153
|
+
disabledField: string;
|
|
154
|
+
/** Fields used by local matching; a `;`-separated attribute or string array property. Defaults to every scalar field. */
|
|
155
|
+
searchFields: string[];
|
|
156
|
+
/** Custom local matching for arbitrary item structures. */
|
|
157
|
+
matcher: AutocompleteMatcher | undefined;
|
|
158
|
+
/** Async item provider. It receives the query and an abort signal; server results are not filtered again locally. */
|
|
159
|
+
dataSource: AutocompleteDataSource | undefined;
|
|
160
|
+
/** Replaces a row's content. The returned content is wrapped in a selectable `c2-list-item`. */
|
|
161
|
+
renderItem: AutocompleteItemRenderer | undefined;
|
|
162
|
+
/** Whether selection preserves the typed query or replaces it with the selected item's key. */
|
|
163
|
+
selectionBehavior: AutocompleteSelectionBehavior;
|
|
164
|
+
/** Delay before calling `dataSource`, in milliseconds. */
|
|
165
|
+
debounce: number;
|
|
166
|
+
/** Query length required before results are shown or requested. */
|
|
167
|
+
minQueryLength: number;
|
|
168
|
+
/** Maximum number of results shown after filtering or loading. */
|
|
169
|
+
maxResults: number;
|
|
170
|
+
/** Keep the result panel open to show an empty state when nothing matches. */
|
|
171
|
+
showEmpty: boolean;
|
|
172
|
+
/** Shows the value without allowing edits or opening suggestions. */
|
|
173
|
+
readonly: boolean;
|
|
174
|
+
/** Disables the control and excludes it from form submission. */
|
|
175
|
+
disabled: boolean;
|
|
176
|
+
/** Marks the control required for native form validation. */
|
|
177
|
+
required: boolean;
|
|
178
|
+
/** Whether the suggestion panel is open. May also be controlled programmatically. */
|
|
179
|
+
open: boolean;
|
|
180
|
+
/** Original item selected most recently, or `undefined` after the input is edited. */
|
|
181
|
+
selectedItem: unknown;
|
|
182
|
+
private results;
|
|
183
|
+
private loading;
|
|
184
|
+
private loadError;
|
|
185
|
+
private focused;
|
|
186
|
+
private activeIndex;
|
|
187
|
+
private disabledByForm;
|
|
188
|
+
private hasHeader;
|
|
189
|
+
private hasFooter;
|
|
190
|
+
private input?;
|
|
191
|
+
private overlay?;
|
|
192
|
+
connectedCallback(): void;
|
|
193
|
+
disconnectedCallback(): void;
|
|
194
|
+
formResetCallback(): void;
|
|
195
|
+
formDisabledCallback(disabled: boolean): void;
|
|
196
|
+
formStateRestoreCallback(state: string | File | FormData | null): void;
|
|
197
|
+
get form(): HTMLFormElement | null;
|
|
198
|
+
get labels(): NodeList;
|
|
199
|
+
get validity(): ValidityState;
|
|
200
|
+
get validationMessage(): string;
|
|
201
|
+
get willValidate(): boolean;
|
|
202
|
+
checkValidity(): boolean;
|
|
203
|
+
reportValidity(): boolean;
|
|
204
|
+
setCustomValidity(message: string): void;
|
|
205
|
+
/** Focuses the inner text input. */
|
|
206
|
+
focus(options?: FocusOptions): void;
|
|
207
|
+
/** Runs local matching or the remote data source immediately, bypassing the debounce. */
|
|
208
|
+
load(query?: string): Promise<void>;
|
|
209
|
+
/** Closes the result panel and clears keyboard highlighting. */
|
|
210
|
+
close(): void;
|
|
211
|
+
private get effectiveDisabled();
|
|
212
|
+
private get listboxId();
|
|
213
|
+
private get activeOptionId();
|
|
214
|
+
private textOf;
|
|
215
|
+
private keyOf;
|
|
216
|
+
private labelOf;
|
|
217
|
+
private isDisabled;
|
|
218
|
+
private itemMatches;
|
|
219
|
+
private finishResults;
|
|
220
|
+
private scheduleLoad;
|
|
221
|
+
private cancelPendingRequest;
|
|
222
|
+
private handleInput;
|
|
223
|
+
private handleChange;
|
|
224
|
+
private handleFocus;
|
|
225
|
+
private handleBlur;
|
|
226
|
+
private handleFieldClick;
|
|
227
|
+
private handleDocumentPointerDown;
|
|
228
|
+
private handleDocumentFocusIn;
|
|
229
|
+
private handleDocumentKeydown;
|
|
230
|
+
private handleKeydown;
|
|
231
|
+
private handleListSelection;
|
|
232
|
+
private selectItem;
|
|
233
|
+
private handleClear;
|
|
234
|
+
private handleOverlayToggle;
|
|
235
|
+
private slotHasContent;
|
|
236
|
+
private renderHighlighted;
|
|
237
|
+
private renderListItem;
|
|
238
|
+
private renderRows;
|
|
239
|
+
private syncFormState;
|
|
240
|
+
protected updated(changed: PropertyValues): void;
|
|
241
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
242
|
+
}
|
|
243
|
+
declare global {
|
|
244
|
+
interface HTMLElementTagNameMap {
|
|
245
|
+
'c2-autocomplete': Autocomplete;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=autocomplete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../../src/autocomplete.ts"],"names":[],"mappings":"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"}
|
package/vue.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// GENERATED by @c2n/framework-types. Do not edit by hand.
|
|
2
|
+
//
|
|
3
|
+
// Vue template types for the custom elements of @c2n/autocomplete. Import once, anywhere in the program:
|
|
4
|
+
//
|
|
5
|
+
// import '@c2n/autocomplete/vue'
|
|
6
|
+
//
|
|
7
|
+
// Tell the compiler about the tags as well, or every c2-* tag is resolved as a Vue component and renders
|
|
8
|
+
// nothing: template.compilerOptions.isCustomElement = (tag) => tag.startsWith('c2-') in vite.config.ts.
|
|
9
|
+
|
|
10
|
+
import type { DefineComponent, HTMLAttributes } from 'vue'
|
|
11
|
+
import type { Autocomplete, AutocompleteEventMap } from '@c2n/autocomplete'
|
|
12
|
+
|
|
13
|
+
/** The element's own public properties, plus every attribute Vue understands on a host element. */
|
|
14
|
+
type C2Props<T> = Partial<Omit<T, keyof HTMLElement>> & HTMLAttributes
|
|
15
|
+
|
|
16
|
+
declare module 'vue' {
|
|
17
|
+
interface GlobalComponents {
|
|
18
|
+
'c2-autocomplete': DefineComponent<
|
|
19
|
+
C2Props<Autocomplete> & {
|
|
20
|
+
'aria-label'?: unknown
|
|
21
|
+
'item-key'?: unknown
|
|
22
|
+
'label-field'?: unknown
|
|
23
|
+
'description-field'?: unknown
|
|
24
|
+
'disabled-field'?: unknown
|
|
25
|
+
'search-fields'?: unknown
|
|
26
|
+
'selection-behavior'?: unknown
|
|
27
|
+
'min-query-length'?: unknown
|
|
28
|
+
'max-results'?: unknown
|
|
29
|
+
'show-empty'?: unknown
|
|
30
|
+
onSuggestionSelect?: (event: AutocompleteEventMap['suggestion-select']) => void
|
|
31
|
+
onInput?: (event: AutocompleteEventMap['input']) => void
|
|
32
|
+
onChange?: (event: AutocompleteEventMap['change']) => void
|
|
33
|
+
onQueryChange?: (event: AutocompleteEventMap['query-change']) => void
|
|
34
|
+
}
|
|
35
|
+
>
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare module '@vue/runtime-dom' {
|
|
40
|
+
interface HTMLAttributes {
|
|
41
|
+
/** Vue's own definition omits it, and slotting a plain element into a component needs it. */
|
|
42
|
+
slot?: string
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export {}
|
package/vue.js
ADDED