@purestack/ts-components 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +10 -0
- package/dist/ts-components.d.mts +1662 -0
- package/dist/ts-components.d.mts.map +1 -0
- package/dist/ts-components.mjs +9200 -0
- package/dist/ts-components.mjs.map +1 -0
- package/package.json +130 -0
- package/src/defineComponents.ts +87 -0
- package/src/index.ts +270 -0
- package/src/registerStyles.ts +63 -0
- package/src/standard/alertBox/alertBox.ts +64 -0
- package/src/standard/autoId.ts +25 -0
- package/src/standard/badge/badge.ts +41 -0
- package/src/standard/badge/badgeStyle.ts +21 -0
- package/src/standard/barChart/barChart.ts +338 -0
- package/src/standard/barChart/barChartStyle.ts +81 -0
- package/src/standard/btn/btn.ts +191 -0
- package/src/standard/btn/btnStyle.ts +59 -0
- package/src/standard/btnGroup/btnGroup.ts +208 -0
- package/src/standard/btnGroup/btnGroupStyle.ts +85 -0
- package/src/standard/chart/cartesianChart.ts +99 -0
- package/src/standard/chart/chartDefaults.ts +25 -0
- package/src/standard/chart/chartUtils.ts +84 -0
- package/src/standard/classicLogo/classicLogo.ts +429 -0
- package/src/standard/classicLogo/classicLogoStyle.ts +292 -0
- package/src/standard/componentVariant.ts +153 -0
- package/src/standard/composer/composer.ts +1035 -0
- package/src/standard/composer/composerCanvasInitialStyle.ts +225 -0
- package/src/standard/composer/composerCanvasRevertStyle.ts +14 -0
- package/src/standard/composer/composerCanvasSharedStyle.ts +60 -0
- package/src/standard/composer/composerCanvasStyleTypes.ts +8 -0
- package/src/standard/composer/composerCanvasThemeStyle.ts +53 -0
- package/src/standard/composer/composerStyle.ts +156 -0
- package/src/standard/composer/emailHtmlPolicy.ts +474 -0
- package/src/standard/consent/consent.ts +173 -0
- package/src/standard/consent/consentStyle.ts +44 -0
- package/src/standard/contactForm/contactForm.ts +135 -0
- package/src/standard/contactForm/contactFormStyle.ts +116 -0
- package/src/standard/doughnutChart/doughnutChart.ts +527 -0
- package/src/standard/doughnutChart/doughnutChartStyle.ts +64 -0
- package/src/standard/dropFiles/dropFiles.ts +480 -0
- package/src/standard/dropFiles/dropFilesStyle.ts +171 -0
- package/src/standard/expandablePanel/expandablePanel.ts +106 -0
- package/src/standard/expandablePanel/expandablePanelStyle.ts +56 -0
- package/src/standard/flex/flex.ts +269 -0
- package/src/standard/flex/flexStyle.ts +556 -0
- package/src/standard/footer/footer.ts +93 -0
- package/src/standard/footer/footerStyle.ts +39 -0
- package/src/standard/form/autoCompleteInput.ts +567 -0
- package/src/standard/form/form.ts +205 -0
- package/src/standard/form/formCheck.ts +96 -0
- package/src/standard/form/formInputField.ts +184 -0
- package/src/standard/form/formSelectField.ts +152 -0
- package/src/standard/form/formStyle.ts +585 -0
- package/src/standard/form/multiAutoCompleteInput.ts +880 -0
- package/src/standard/grid/grid.ts +177 -0
- package/src/standard/grid/gridStyle.ts +56 -0
- package/src/standard/icon/icon.ts +96 -0
- package/src/standard/icon/iconStyle.ts +50 -0
- package/src/standard/landing/codeShowcase.ts +54 -0
- package/src/standard/landing/comparisonColumn.ts +30 -0
- package/src/standard/landing/comparisonFeature.ts +22 -0
- package/src/standard/landing/comparisonTable.ts +38 -0
- package/src/standard/landing/ctaSection.ts +59 -0
- package/src/standard/landing/featureCard.ts +41 -0
- package/src/standard/landing/landing.ts +52 -0
- package/src/standard/landing/landingBand.ts +217 -0
- package/src/standard/landing/landingSection.ts +43 -0
- package/src/standard/landing/landingTypes.ts +141 -0
- package/src/standard/landing/metricItem.ts +25 -0
- package/src/standard/landing/metricStrip.ts +20 -0
- package/src/standard/lineChart/lineChart.ts +462 -0
- package/src/standard/lineChart/lineChartStyle.ts +91 -0
- package/src/standard/logo/logo.ts +192 -0
- package/src/standard/logo/logoStyle.ts +164 -0
- package/src/standard/modal/modal.ts +171 -0
- package/src/standard/modal/modalStore.ts +154 -0
- package/src/standard/modal/modalStyle.ts +190 -0
- package/src/standard/navMenu/navMenu.ts +278 -0
- package/src/standard/navMenu/navMenuStyle.ts +323 -0
- package/src/standard/pageLinks/pageLinks.ts +90 -0
- package/src/standard/pageScript/pageScript.ts +155 -0
- package/src/standard/pageToc/pageToc.ts +154 -0
- package/src/standard/pageToc/pageTocStyle.ts +372 -0
- package/src/standard/panel/panel.ts +55 -0
- package/src/standard/panel/panelStyle.ts +15 -0
- package/src/standard/pricing/pricing.ts +146 -0
- package/src/standard/searchBox/searchBox.ts +54 -0
- package/src/standard/searchBox/searchBoxStyle.ts +180 -0
- package/src/standard/sectionHeader/sectionHeader.ts +99 -0
- package/src/standard/signIn/signIn.ts +167 -0
- package/src/standard/signIn/signInStyle.ts +70 -0
- package/src/standard/tabs/tabs.ts +193 -0
- package/src/standard/tabs/tabsStyle.ts +231 -0
- package/src/standard/themeToggle/themeToggle.ts +33 -0
- package/src/standard/themeToggle/themeToggleStyle.ts +88 -0
- package/src/standard/toast/toastHost.ts +42 -0
- package/src/standard/toast/toastStore.ts +50 -0
- package/src/standard/topBar/topBar.ts +101 -0
- package/src/standard/topBar/topBarStyle.ts +169 -0
- package/src/standard/virtualList/variableVirtualTable.ts +371 -0
- package/src/standard/virtualList/virtualList.ts +423 -0
- package/src/standard/virtualList/virtualListStyle.ts +34 -0
- package/src/standard/virtualList/virtualTable.ts +179 -0
- package/src/standard/virtualList/virtualTableStyle.ts +24 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
import type { SemanticTone } from '@purestack/ts-style'
|
|
2
|
+
import {
|
|
3
|
+
type ComponentHead,
|
|
4
|
+
type ComputedRef,
|
|
5
|
+
computed,
|
|
6
|
+
defineComponent,
|
|
7
|
+
type Emits,
|
|
8
|
+
html,
|
|
9
|
+
isRef,
|
|
10
|
+
type Ref,
|
|
11
|
+
type RefOrValue,
|
|
12
|
+
ref,
|
|
13
|
+
type SRef,
|
|
14
|
+
sref,
|
|
15
|
+
unref,
|
|
16
|
+
} from 'regor'
|
|
17
|
+
import { createAutoId } from '../autoId'
|
|
18
|
+
import {
|
|
19
|
+
type ComponentVariant,
|
|
20
|
+
resolveComponentClasses,
|
|
21
|
+
} from '../componentVariant'
|
|
22
|
+
import type {
|
|
23
|
+
AutoCompleteOption,
|
|
24
|
+
AutoCompleteValue,
|
|
25
|
+
ResolvedAutoCompleteOption,
|
|
26
|
+
} from './autoCompleteInput'
|
|
27
|
+
|
|
28
|
+
const resolveMultiAutoCompleteInputId = createAutoId(
|
|
29
|
+
'multi-auto-complete-input',
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
export interface MultiAutoCompleteItem {
|
|
33
|
+
label: RefOrValue<string>
|
|
34
|
+
value?: RefOrValue<AutoCompleteValue>
|
|
35
|
+
disabled?: RefOrValue<boolean>
|
|
36
|
+
invalid?: RefOrValue<boolean>
|
|
37
|
+
tone?: RefOrValue<SemanticTone>
|
|
38
|
+
keywords?: RefOrValue<string | string[]>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ResolvedMultiAutoCompleteItem {
|
|
42
|
+
label: string
|
|
43
|
+
value: AutoCompleteValue
|
|
44
|
+
disabled?: boolean
|
|
45
|
+
invalid?: boolean
|
|
46
|
+
tone?: SemanticTone
|
|
47
|
+
keywords: string[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type MultiAutoCompleteCreateItem = (
|
|
51
|
+
query: string,
|
|
52
|
+
) => MultiAutoCompleteItem | null | undefined
|
|
53
|
+
|
|
54
|
+
export type MultiAutoCompleteOptionToItem = (
|
|
55
|
+
option: ResolvedAutoCompleteOption,
|
|
56
|
+
) => MultiAutoCompleteItem | null | undefined
|
|
57
|
+
|
|
58
|
+
export type MultiAutoCompleteSplitInput = (value: string) => string[]
|
|
59
|
+
|
|
60
|
+
export type MultiAutoCompleteItemsChange = (
|
|
61
|
+
items: MultiAutoCompleteItem[],
|
|
62
|
+
) => void
|
|
63
|
+
|
|
64
|
+
export type MultiAutoCompleteSelect = (
|
|
65
|
+
option: ResolvedAutoCompleteOption,
|
|
66
|
+
item: MultiAutoCompleteItem,
|
|
67
|
+
) => void
|
|
68
|
+
|
|
69
|
+
export type MultiAutoCompleteSearch = (query: string) => void
|
|
70
|
+
|
|
71
|
+
export interface MultiAutoCompleteSelectedRow {
|
|
72
|
+
id: string
|
|
73
|
+
index: number
|
|
74
|
+
item: ResolvedMultiAutoCompleteItem
|
|
75
|
+
classes: string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface MultiAutoCompleteOptionRow {
|
|
79
|
+
id: string
|
|
80
|
+
index: number
|
|
81
|
+
option: ResolvedAutoCompleteOption
|
|
82
|
+
active: boolean
|
|
83
|
+
disabled?: boolean
|
|
84
|
+
classes: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface MultiAutoCompleteInput {
|
|
88
|
+
id?: RefOrValue<string>
|
|
89
|
+
label?: RefOrValue<string>
|
|
90
|
+
tone?: RefOrValue<SemanticTone>
|
|
91
|
+
variant?: RefOrValue<ComponentVariant>
|
|
92
|
+
items?: SRef<MultiAutoCompleteItem[]>
|
|
93
|
+
model?: Ref<string>
|
|
94
|
+
name?: RefOrValue<string>
|
|
95
|
+
required?: RefOrValue<boolean>
|
|
96
|
+
autocomplete?: RefOrValue<string>
|
|
97
|
+
placeholder?: RefOrValue<string>
|
|
98
|
+
disabled?: RefOrValue<boolean>
|
|
99
|
+
icon?: RefOrValue<string>
|
|
100
|
+
iconEnd?: RefOrValue<string>
|
|
101
|
+
removeIcon?: RefOrValue<string>
|
|
102
|
+
options?: RefOrValue<AutoCompleteOption[]>
|
|
103
|
+
rowComponent?: RefOrValue<string>
|
|
104
|
+
minLength?: RefOrValue<number | string>
|
|
105
|
+
maxResults?: RefOrValue<number | string>
|
|
106
|
+
openOnFocus?: RefOrValue<boolean>
|
|
107
|
+
allowCustomValues?: RefOrValue<boolean>
|
|
108
|
+
allowDuplicates?: RefOrValue<boolean>
|
|
109
|
+
separators?: RefOrValue<string>
|
|
110
|
+
loading?: RefOrValue<boolean>
|
|
111
|
+
emptyText?: RefOrValue<string>
|
|
112
|
+
loadingText?: RefOrValue<string>
|
|
113
|
+
onCreateItem?: RefOrValue<MultiAutoCompleteCreateItem>
|
|
114
|
+
onOptionToItem?: RefOrValue<MultiAutoCompleteOptionToItem>
|
|
115
|
+
onSplitInput?: RefOrValue<MultiAutoCompleteSplitInput>
|
|
116
|
+
onItemsChange?: RefOrValue<MultiAutoCompleteItemsChange>
|
|
117
|
+
onSelect?: RefOrValue<MultiAutoCompleteSelect>
|
|
118
|
+
onSearch?: RefOrValue<MultiAutoCompleteSearch>
|
|
119
|
+
emits?: Emits<
|
|
120
|
+
'itemschange' | 'itemremove' | 'itemcreate' | 'optionselect' | 'querychange'
|
|
121
|
+
>
|
|
122
|
+
inputId?: string
|
|
123
|
+
listboxId?: string
|
|
124
|
+
inputElement?: SRef<HTMLInputElement | null>
|
|
125
|
+
isOpen?: Ref<boolean>
|
|
126
|
+
activeIndex?: Ref<number>
|
|
127
|
+
query?: ComputedRef<string>
|
|
128
|
+
classes?: ComputedRef<string>
|
|
129
|
+
resolvedItems?: ComputedRef<ResolvedMultiAutoCompleteItem[]>
|
|
130
|
+
selectedRows?: ComputedRef<MultiAutoCompleteSelectedRow[]>
|
|
131
|
+
resolvedOptions?: ComputedRef<ResolvedAutoCompleteOption[]>
|
|
132
|
+
filteredOptions?: ComputedRef<ResolvedAutoCompleteOption[]>
|
|
133
|
+
optionRows?: ComputedRef<MultiAutoCompleteOptionRow[]>
|
|
134
|
+
activeOptionId?: ComputedRef<string | undefined>
|
|
135
|
+
shouldRenderPopup?: ComputedRef<boolean>
|
|
136
|
+
showLoading?: ComputedRef<boolean>
|
|
137
|
+
showEmpty?: ComputedRef<boolean>
|
|
138
|
+
resolvedRowComponent?: ComputedRef<string>
|
|
139
|
+
resolvedIconEnd?: ComputedRef<string | undefined>
|
|
140
|
+
resolvedRemoveIcon?: ComputedRef<string>
|
|
141
|
+
focusInput?: () => void
|
|
142
|
+
handleFocusIn?: () => void
|
|
143
|
+
handleFocusOut?: () => void
|
|
144
|
+
handleInput?: () => void
|
|
145
|
+
handlePaste?: (event: ClipboardEvent) => void
|
|
146
|
+
handleKeydown?: (event: KeyboardEvent) => void
|
|
147
|
+
handleOptionMouseDown?: (event: MouseEvent) => void
|
|
148
|
+
selectOptionRow?: (row: MultiAutoCompleteOptionRow) => void
|
|
149
|
+
removeSelectedRow?: (row: MultiAutoCompleteSelectedRow) => void
|
|
150
|
+
removeItemLabel?: (row: MultiAutoCompleteSelectedRow) => string
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface MultiAutoCompleteDefaultOptionRow {
|
|
154
|
+
option: RefOrValue<ResolvedAutoCompleteOption | null>
|
|
155
|
+
active?: RefOrValue<boolean>
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const multiAutoCompleteInputTemplate = html`<label
|
|
159
|
+
class="multi-auto-complete-input form-block__field"
|
|
160
|
+
:for="inputId"
|
|
161
|
+
@focusin="handleFocusIn"
|
|
162
|
+
@focusout="handleFocusOut"
|
|
163
|
+
>
|
|
164
|
+
<span class="form-block__label" r-if="label">{{ label }}</span>
|
|
165
|
+
<div
|
|
166
|
+
class="multi-auto-complete-input__shell form-block__input-shell"
|
|
167
|
+
:class="classes"
|
|
168
|
+
@click="focusInput"
|
|
169
|
+
>
|
|
170
|
+
<Icon class="form-block__input-icon" :name="icon" r-if="icon"/>
|
|
171
|
+
<div class="multi-auto-complete-input__control">
|
|
172
|
+
<span
|
|
173
|
+
class="multi-auto-complete-input__item"
|
|
174
|
+
:class="row.classes"
|
|
175
|
+
r-for="row in selectedRows"
|
|
176
|
+
>
|
|
177
|
+
<span class="multi-auto-complete-input__item-label">
|
|
178
|
+
{{ row.item.label }}
|
|
179
|
+
</span>
|
|
180
|
+
<button
|
|
181
|
+
class="multi-auto-complete-input__remove"
|
|
182
|
+
type="button"
|
|
183
|
+
:title="removeItemLabel(row)"
|
|
184
|
+
:aria-label="removeItemLabel(row)"
|
|
185
|
+
:disabled="disabled || row.item.disabled"
|
|
186
|
+
@click="removeSelectedRow(row)"
|
|
187
|
+
>
|
|
188
|
+
<Icon :name="resolvedRemoveIcon"/>
|
|
189
|
+
</button>
|
|
190
|
+
<input
|
|
191
|
+
r-if="name"
|
|
192
|
+
type="hidden"
|
|
193
|
+
:name="name"
|
|
194
|
+
:value="row.item.value"/>
|
|
195
|
+
</span>
|
|
196
|
+
<input
|
|
197
|
+
:id="inputId"
|
|
198
|
+
:ref="inputElement"
|
|
199
|
+
class="multi-auto-complete-input__query form-block__input"
|
|
200
|
+
type="text"
|
|
201
|
+
role="combobox"
|
|
202
|
+
aria-autocomplete="list"
|
|
203
|
+
:aria-controls="listboxId"
|
|
204
|
+
:aria-expanded="isOpen"
|
|
205
|
+
:aria-activedescendant="activeOptionId"
|
|
206
|
+
:required="required && selectedRows.length === 0"
|
|
207
|
+
:autocomplete="autocomplete || 'off'"
|
|
208
|
+
:placeholder="selectedRows.length === 0 ? placeholder : ''"
|
|
209
|
+
:disabled="disabled"
|
|
210
|
+
r-model="model"
|
|
211
|
+
@input="handleInput"
|
|
212
|
+
@paste="handlePaste"
|
|
213
|
+
@keydown="handleKeydown"/>
|
|
214
|
+
</div>
|
|
215
|
+
<Icon
|
|
216
|
+
class="form-block__input-icon"
|
|
217
|
+
:name="resolvedIconEnd"
|
|
218
|
+
r-if="resolvedIconEnd"/>
|
|
219
|
+
</div>
|
|
220
|
+
<div
|
|
221
|
+
class="multi-auto-complete-input__popup"
|
|
222
|
+
r-if="shouldRenderPopup"
|
|
223
|
+
>
|
|
224
|
+
<div
|
|
225
|
+
class="multi-auto-complete-input__message"
|
|
226
|
+
r-if="showLoading"
|
|
227
|
+
role="status"
|
|
228
|
+
aria-live="polite"
|
|
229
|
+
>
|
|
230
|
+
{{ loadingText || 'Loading suggestions' }}
|
|
231
|
+
</div>
|
|
232
|
+
<div
|
|
233
|
+
class="multi-auto-complete-input__message"
|
|
234
|
+
r-if="showEmpty"
|
|
235
|
+
role="status"
|
|
236
|
+
aria-live="polite"
|
|
237
|
+
>
|
|
238
|
+
{{ emptyText || 'No suggestions' }}
|
|
239
|
+
</div>
|
|
240
|
+
<div
|
|
241
|
+
class="multi-auto-complete-input__listbox"
|
|
242
|
+
:id="listboxId"
|
|
243
|
+
role="listbox"
|
|
244
|
+
r-if="!showLoading && !showEmpty"
|
|
245
|
+
>
|
|
246
|
+
<div
|
|
247
|
+
class="multi-auto-complete-input__option"
|
|
248
|
+
:class="row.classes"
|
|
249
|
+
role="option"
|
|
250
|
+
:id="row.id"
|
|
251
|
+
:aria-selected="row.active"
|
|
252
|
+
:aria-disabled="row.disabled"
|
|
253
|
+
r-for="row in optionRows"
|
|
254
|
+
@mousedown="handleOptionMouseDown"
|
|
255
|
+
@click="selectOptionRow(row)"
|
|
256
|
+
>
|
|
257
|
+
<div
|
|
258
|
+
:is="resolvedRowComponent"
|
|
259
|
+
:option="row.option"
|
|
260
|
+
:item="row.option"
|
|
261
|
+
:index="row.index"
|
|
262
|
+
:query="query"
|
|
263
|
+
:active="row.active"
|
|
264
|
+
:selected="false"
|
|
265
|
+
:disabled="row.disabled"
|
|
266
|
+
></div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</label>`
|
|
271
|
+
|
|
272
|
+
const multiAutoCompleteOptionRowTemplate = html`<div class="multi-auto-complete-input__default-row">
|
|
273
|
+
<span class="multi-auto-complete-input__default-label">
|
|
274
|
+
{{ option?.label }}
|
|
275
|
+
</span>
|
|
276
|
+
</div>`
|
|
277
|
+
|
|
278
|
+
function defineMultiAutoCompleteInputComponent() {
|
|
279
|
+
return defineComponent<MultiAutoCompleteInput>(
|
|
280
|
+
multiAutoCompleteInputTemplate,
|
|
281
|
+
{
|
|
282
|
+
props: [
|
|
283
|
+
'id',
|
|
284
|
+
'label',
|
|
285
|
+
'tone',
|
|
286
|
+
'variant',
|
|
287
|
+
'items',
|
|
288
|
+
'model',
|
|
289
|
+
'name',
|
|
290
|
+
'required',
|
|
291
|
+
'autocomplete',
|
|
292
|
+
'placeholder',
|
|
293
|
+
'disabled',
|
|
294
|
+
'icon',
|
|
295
|
+
'iconEnd',
|
|
296
|
+
'removeIcon',
|
|
297
|
+
'options',
|
|
298
|
+
'rowComponent',
|
|
299
|
+
'minLength',
|
|
300
|
+
'maxResults',
|
|
301
|
+
'openOnFocus',
|
|
302
|
+
'allowCustomValues',
|
|
303
|
+
'allowDuplicates',
|
|
304
|
+
'separators',
|
|
305
|
+
'loading',
|
|
306
|
+
'emptyText',
|
|
307
|
+
'loadingText',
|
|
308
|
+
'onCreateItem',
|
|
309
|
+
'onOptionToItem',
|
|
310
|
+
'onSplitInput',
|
|
311
|
+
'onItemsChange',
|
|
312
|
+
'onSelect',
|
|
313
|
+
'onSearch',
|
|
314
|
+
],
|
|
315
|
+
context: (head) => new MultiAutoCompleteInputContext(head),
|
|
316
|
+
},
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function defineMultiAutoCompleteOptionRowComponent() {
|
|
321
|
+
return defineComponent<MultiAutoCompleteDefaultOptionRow>(
|
|
322
|
+
multiAutoCompleteOptionRowTemplate,
|
|
323
|
+
{
|
|
324
|
+
props: ['option', 'active', 'selected', 'query'],
|
|
325
|
+
context: (head) => head.props,
|
|
326
|
+
},
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function defineMultiAutoCompleteInputComponents() {
|
|
331
|
+
return {
|
|
332
|
+
multiAutoCompleteInput: defineMultiAutoCompleteInputComponent(),
|
|
333
|
+
multiAutoCompleteOptionRow: defineMultiAutoCompleteOptionRowComponent(),
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
class MultiAutoCompleteInputContext implements MultiAutoCompleteInput {
|
|
338
|
+
readonly id: string
|
|
339
|
+
readonly label?: RefOrValue<string>
|
|
340
|
+
readonly tone?: RefOrValue<SemanticTone>
|
|
341
|
+
readonly variant?: RefOrValue<ComponentVariant>
|
|
342
|
+
readonly items: SRef<MultiAutoCompleteItem[]>
|
|
343
|
+
readonly model: Ref<string>
|
|
344
|
+
readonly name?: RefOrValue<string>
|
|
345
|
+
readonly required?: RefOrValue<boolean>
|
|
346
|
+
readonly autocomplete?: RefOrValue<string>
|
|
347
|
+
readonly placeholder?: RefOrValue<string>
|
|
348
|
+
readonly disabled?: RefOrValue<boolean>
|
|
349
|
+
readonly icon?: RefOrValue<string>
|
|
350
|
+
readonly iconEnd?: RefOrValue<string>
|
|
351
|
+
readonly removeIcon?: RefOrValue<string>
|
|
352
|
+
readonly options?: RefOrValue<AutoCompleteOption[]>
|
|
353
|
+
readonly rowComponent?: RefOrValue<string>
|
|
354
|
+
readonly minLength?: RefOrValue<number | string>
|
|
355
|
+
readonly maxResults?: RefOrValue<number | string>
|
|
356
|
+
readonly openOnFocus?: RefOrValue<boolean>
|
|
357
|
+
readonly allowCustomValues?: RefOrValue<boolean>
|
|
358
|
+
readonly allowDuplicates?: RefOrValue<boolean>
|
|
359
|
+
readonly separators?: RefOrValue<string>
|
|
360
|
+
readonly loading?: RefOrValue<boolean>
|
|
361
|
+
readonly emptyText?: RefOrValue<string>
|
|
362
|
+
readonly loadingText?: RefOrValue<string>
|
|
363
|
+
readonly onCreateItem?: RefOrValue<MultiAutoCompleteCreateItem>
|
|
364
|
+
readonly onOptionToItem?: RefOrValue<MultiAutoCompleteOptionToItem>
|
|
365
|
+
readonly onSplitInput?: RefOrValue<MultiAutoCompleteSplitInput>
|
|
366
|
+
readonly onItemsChange?: RefOrValue<MultiAutoCompleteItemsChange>
|
|
367
|
+
readonly onSelect?: RefOrValue<MultiAutoCompleteSelect>
|
|
368
|
+
readonly onSearch?: RefOrValue<MultiAutoCompleteSearch>
|
|
369
|
+
readonly inputId: string
|
|
370
|
+
readonly listboxId: string
|
|
371
|
+
readonly inputElement = sref<HTMLInputElement | null>(null)
|
|
372
|
+
readonly isOpen = ref(false)
|
|
373
|
+
readonly activeIndex = ref(-1)
|
|
374
|
+
readonly query: ComputedRef<string>
|
|
375
|
+
readonly classes: ComputedRef<string>
|
|
376
|
+
readonly resolvedItems: ComputedRef<ResolvedMultiAutoCompleteItem[]>
|
|
377
|
+
readonly selectedRows: ComputedRef<MultiAutoCompleteSelectedRow[]>
|
|
378
|
+
readonly resolvedOptions: ComputedRef<ResolvedAutoCompleteOption[]>
|
|
379
|
+
readonly filteredOptions: ComputedRef<ResolvedAutoCompleteOption[]>
|
|
380
|
+
readonly optionRows: ComputedRef<MultiAutoCompleteOptionRow[]>
|
|
381
|
+
readonly activeOptionId: ComputedRef<string | undefined>
|
|
382
|
+
readonly shouldRenderPopup: ComputedRef<boolean>
|
|
383
|
+
readonly showLoading: ComputedRef<boolean>
|
|
384
|
+
readonly showEmpty: ComputedRef<boolean>
|
|
385
|
+
readonly resolvedRowComponent: ComputedRef<string>
|
|
386
|
+
readonly resolvedIconEnd: ComputedRef<string | undefined>
|
|
387
|
+
readonly resolvedRemoveIcon: ComputedRef<string>
|
|
388
|
+
private readonly emit: ComponentHead<MultiAutoCompleteInput>['emit']
|
|
389
|
+
private closingTimer: ReturnType<typeof setTimeout> | undefined
|
|
390
|
+
|
|
391
|
+
constructor(head: ComponentHead<MultiAutoCompleteInput>) {
|
|
392
|
+
const props = head.props
|
|
393
|
+
Object.assign(this, props)
|
|
394
|
+
this.emit = head.emit
|
|
395
|
+
this.id = resolveMultiAutoCompleteInputId(props.id)
|
|
396
|
+
this.inputId = `${this.id}-query`
|
|
397
|
+
this.listboxId = `${this.id}-listbox`
|
|
398
|
+
this.items = isRef(props.items)
|
|
399
|
+
? props.items
|
|
400
|
+
: sref<MultiAutoCompleteItem[]>([])
|
|
401
|
+
this.model = isRef(props.model) ? props.model : ref('')
|
|
402
|
+
this.query = computed(() => String(unref(this.model) ?? ''))
|
|
403
|
+
this.classes = computed(() =>
|
|
404
|
+
resolveComponentClasses(props, {
|
|
405
|
+
defaultVariant: 'surfaceAlt',
|
|
406
|
+
classes: [
|
|
407
|
+
unref(this.disabled)
|
|
408
|
+
? 'multi-auto-complete-input__shell--disabled'
|
|
409
|
+
: '',
|
|
410
|
+
],
|
|
411
|
+
}),
|
|
412
|
+
)
|
|
413
|
+
this.resolvedItems = computed(() =>
|
|
414
|
+
this.items().map(resolveMultiAutoCompleteItem),
|
|
415
|
+
)
|
|
416
|
+
this.selectedRows = computed(() => this.resolveSelectedRows())
|
|
417
|
+
this.resolvedOptions = computed(() => {
|
|
418
|
+
const options = unref(props.options)
|
|
419
|
+
return Array.isArray(options)
|
|
420
|
+
? options.map(resolveAutoCompleteOption)
|
|
421
|
+
: []
|
|
422
|
+
})
|
|
423
|
+
this.filteredOptions = computed(() => this.resolveFilteredOptions())
|
|
424
|
+
this.optionRows = computed(() => this.resolveOptionRows())
|
|
425
|
+
this.activeOptionId = computed(
|
|
426
|
+
() => this.optionRows()[this.activeIndex()]?.id,
|
|
427
|
+
)
|
|
428
|
+
this.showLoading = computed(() => this.isOpen() && !!unref(this.loading))
|
|
429
|
+
this.showEmpty = computed(
|
|
430
|
+
() =>
|
|
431
|
+
this.isOpen() &&
|
|
432
|
+
!this.showLoading() &&
|
|
433
|
+
this.meetsMinimumQueryLength() &&
|
|
434
|
+
this.filteredOptions().length === 0,
|
|
435
|
+
)
|
|
436
|
+
this.shouldRenderPopup = computed(
|
|
437
|
+
() =>
|
|
438
|
+
this.isOpen() &&
|
|
439
|
+
(this.showLoading() ||
|
|
440
|
+
this.showEmpty() ||
|
|
441
|
+
this.filteredOptions().length > 0),
|
|
442
|
+
)
|
|
443
|
+
this.resolvedRowComponent = computed(() => {
|
|
444
|
+
const component = unref(props.rowComponent)
|
|
445
|
+
return typeof component === 'string' && component.trim()
|
|
446
|
+
? component
|
|
447
|
+
: 'MultiAutoCompleteOptionRow'
|
|
448
|
+
})
|
|
449
|
+
this.resolvedIconEnd = computed(
|
|
450
|
+
() =>
|
|
451
|
+
unref(props.iconEnd) ||
|
|
452
|
+
(unref(props.loading) ? 'lucide:loader-circle' : 'lucide:chevron-down'),
|
|
453
|
+
)
|
|
454
|
+
this.resolvedRemoveIcon = computed(
|
|
455
|
+
() => unref(props.removeIcon) || 'lucide:x',
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
focusInput = () => {
|
|
460
|
+
if (unref(this.disabled)) return
|
|
461
|
+
this.inputElement()?.focus()
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
handleFocusIn = () => {
|
|
465
|
+
this.clearClosingTimer()
|
|
466
|
+
if (!this.shouldOpenOnFocus()) return
|
|
467
|
+
|
|
468
|
+
this.open()
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
handleFocusOut = () => {
|
|
472
|
+
this.clearClosingTimer()
|
|
473
|
+
this.closingTimer = setTimeout(() => {
|
|
474
|
+
this.close()
|
|
475
|
+
}, 0)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
handleInput = () => {
|
|
479
|
+
const query = this.query()
|
|
480
|
+
unref(this.onSearch)?.(query)
|
|
481
|
+
this.emit('querychange', { query })
|
|
482
|
+
if (this.meetsMinimumQueryLength()) this.open()
|
|
483
|
+
else this.close()
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
handlePaste = (event: ClipboardEvent) => {
|
|
487
|
+
if (unref(this.disabled) || !this.canCreateCustomValues()) return
|
|
488
|
+
|
|
489
|
+
const text = event.clipboardData?.getData('text') ?? ''
|
|
490
|
+
const values = this.splitValues(text)
|
|
491
|
+
if (values.length <= 1) return
|
|
492
|
+
|
|
493
|
+
event.preventDefault()
|
|
494
|
+
this.addCreatedValues(values)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
handleKeydown = (event: KeyboardEvent) => {
|
|
498
|
+
if (unref(this.disabled)) return
|
|
499
|
+
|
|
500
|
+
if (event.key === 'ArrowDown') {
|
|
501
|
+
event.preventDefault()
|
|
502
|
+
if (!this.isOpen()) {
|
|
503
|
+
this.open()
|
|
504
|
+
return
|
|
505
|
+
}
|
|
506
|
+
this.open()
|
|
507
|
+
this.moveActive(1)
|
|
508
|
+
return
|
|
509
|
+
}
|
|
510
|
+
if (event.key === 'ArrowUp') {
|
|
511
|
+
event.preventDefault()
|
|
512
|
+
if (!this.isOpen()) {
|
|
513
|
+
this.open()
|
|
514
|
+
this.activateLast()
|
|
515
|
+
return
|
|
516
|
+
}
|
|
517
|
+
this.open()
|
|
518
|
+
this.moveActive(-1)
|
|
519
|
+
return
|
|
520
|
+
}
|
|
521
|
+
if (event.key === 'Home' && this.isOpen()) {
|
|
522
|
+
event.preventDefault()
|
|
523
|
+
this.activateFirst()
|
|
524
|
+
return
|
|
525
|
+
}
|
|
526
|
+
if (event.key === 'End' && this.isOpen()) {
|
|
527
|
+
event.preventDefault()
|
|
528
|
+
this.activateLast()
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
if (event.key === 'Enter') {
|
|
532
|
+
const row = this.isOpen() ? this.optionRows()[this.activeIndex()] : null
|
|
533
|
+
if (row) {
|
|
534
|
+
event.preventDefault()
|
|
535
|
+
this.selectOptionRow(row)
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (this.commitQuery()) event.preventDefault()
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
if (event.key === 'Backspace' && !this.query()) {
|
|
543
|
+
const rows = this.selectedRows()
|
|
544
|
+
const row = rows[rows.length - 1]
|
|
545
|
+
if (!row) return
|
|
546
|
+
|
|
547
|
+
event.preventDefault()
|
|
548
|
+
this.removeSelectedRow(row)
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
if (event.key === 'Escape') {
|
|
552
|
+
this.close()
|
|
553
|
+
return
|
|
554
|
+
}
|
|
555
|
+
if (
|
|
556
|
+
event.key.length === 1 &&
|
|
557
|
+
this.resolvedSeparators().includes(event.key)
|
|
558
|
+
) {
|
|
559
|
+
if (this.commitQuery()) event.preventDefault()
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
handleOptionMouseDown = (event: MouseEvent) => {
|
|
564
|
+
event.preventDefault()
|
|
565
|
+
this.clearClosingTimer()
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
selectOptionRow = (row: MultiAutoCompleteOptionRow) => {
|
|
569
|
+
if (row.disabled || unref(this.disabled)) return
|
|
570
|
+
|
|
571
|
+
const item = unref(this.onOptionToItem)?.(row.option) ?? {
|
|
572
|
+
label: row.option.label,
|
|
573
|
+
value: row.option.value,
|
|
574
|
+
}
|
|
575
|
+
if (!this.addItem(item)) return
|
|
576
|
+
|
|
577
|
+
this.model('')
|
|
578
|
+
unref(this.onSelect)?.(row.option, item)
|
|
579
|
+
this.emit('optionselect', { option: row.option, item })
|
|
580
|
+
this.close()
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
removeSelectedRow = (row: MultiAutoCompleteSelectedRow) => {
|
|
584
|
+
if (unref(this.disabled) || row.item.disabled) return
|
|
585
|
+
|
|
586
|
+
const next = this.items().filter((_, index) => index !== row.index)
|
|
587
|
+
this.writeItems(next)
|
|
588
|
+
this.emit('itemremove', { item: row.item, index: row.index })
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
removeItemLabel = (row: MultiAutoCompleteSelectedRow) =>
|
|
592
|
+
`Remove ${row.item.label}`
|
|
593
|
+
|
|
594
|
+
private commitQuery() {
|
|
595
|
+
if (!this.canCreateCustomValues()) return false
|
|
596
|
+
|
|
597
|
+
const values = this.splitValues(this.query())
|
|
598
|
+
if (values.length === 0) return false
|
|
599
|
+
|
|
600
|
+
const added = this.addCreatedValues(values)
|
|
601
|
+
if (added > 0) this.model('')
|
|
602
|
+
return added > 0
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private addCreatedValues(values: string[]) {
|
|
606
|
+
const created: MultiAutoCompleteItem[] = []
|
|
607
|
+
for (const value of values) {
|
|
608
|
+
const item = unref(this.onCreateItem)?.(value) ?? {
|
|
609
|
+
label: value,
|
|
610
|
+
value,
|
|
611
|
+
}
|
|
612
|
+
if (!item || this.hasItemValue(item)) continue
|
|
613
|
+
created.push(item)
|
|
614
|
+
}
|
|
615
|
+
if (created.length === 0) return 0
|
|
616
|
+
|
|
617
|
+
const next = [...this.items(), ...created]
|
|
618
|
+
this.writeItems(next)
|
|
619
|
+
for (const item of created) this.emit('itemcreate', { item })
|
|
620
|
+
this.close()
|
|
621
|
+
return created.length
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private addItem(item: MultiAutoCompleteItem) {
|
|
625
|
+
if (!item || this.hasItemValue(item)) return false
|
|
626
|
+
|
|
627
|
+
this.writeItems([...this.items(), item])
|
|
628
|
+
return true
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private writeItems(items: MultiAutoCompleteItem[]) {
|
|
632
|
+
this.items(items)
|
|
633
|
+
unref(this.onItemsChange)?.(items)
|
|
634
|
+
this.emit('itemschange', { items })
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
private resolveSelectedRows() {
|
|
638
|
+
return this.resolvedItems().map(
|
|
639
|
+
(item, index): MultiAutoCompleteSelectedRow => ({
|
|
640
|
+
id: `${this.id}-item-${index}`,
|
|
641
|
+
index,
|
|
642
|
+
item,
|
|
643
|
+
classes: [
|
|
644
|
+
item.invalid ? 'multi-auto-complete-input__item--invalid' : '',
|
|
645
|
+
item.disabled ? 'multi-auto-complete-input__item--disabled' : '',
|
|
646
|
+
item.tone ? `tone-${item.tone}` : '',
|
|
647
|
+
]
|
|
648
|
+
.filter(Boolean)
|
|
649
|
+
.join(' '),
|
|
650
|
+
}),
|
|
651
|
+
)
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private resolveFilteredOptions() {
|
|
655
|
+
const query = normalizeSearchText(this.query())
|
|
656
|
+
if (!this.meetsMinimumQueryLength()) return []
|
|
657
|
+
|
|
658
|
+
const options = this.resolvedOptions().filter(
|
|
659
|
+
(option) =>
|
|
660
|
+
!this.hasOptionValue(option) &&
|
|
661
|
+
(!query ||
|
|
662
|
+
option.keywords.some((keyword) =>
|
|
663
|
+
normalizeSearchText(keyword).includes(query),
|
|
664
|
+
)),
|
|
665
|
+
)
|
|
666
|
+
return options.slice(0, this.readPositiveInteger(this.maxResults, 10))
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
private resolveOptionRows() {
|
|
670
|
+
const activeIndex = this.activeIndex()
|
|
671
|
+
return this.filteredOptions().map(
|
|
672
|
+
(option, index): MultiAutoCompleteOptionRow => ({
|
|
673
|
+
id: `${this.listboxId}-option-${index}`,
|
|
674
|
+
index,
|
|
675
|
+
option,
|
|
676
|
+
active: index === activeIndex,
|
|
677
|
+
disabled: option.disabled,
|
|
678
|
+
classes: [
|
|
679
|
+
index === activeIndex
|
|
680
|
+
? 'multi-auto-complete-input__option--active'
|
|
681
|
+
: '',
|
|
682
|
+
option.disabled ? 'multi-auto-complete-input__option--disabled' : '',
|
|
683
|
+
]
|
|
684
|
+
.filter(Boolean)
|
|
685
|
+
.join(' '),
|
|
686
|
+
}),
|
|
687
|
+
)
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private open() {
|
|
691
|
+
if (unref(this.disabled) || !this.meetsMinimumQueryLength()) return
|
|
692
|
+
|
|
693
|
+
this.isOpen(true)
|
|
694
|
+
this.ensureActiveIndex()
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
private close() {
|
|
698
|
+
this.isOpen(false)
|
|
699
|
+
this.activeIndex(-1)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private moveActive(direction: 1 | -1) {
|
|
703
|
+
const rows = this.optionRows()
|
|
704
|
+
if (rows.length === 0) {
|
|
705
|
+
this.activeIndex(-1)
|
|
706
|
+
return
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const current = this.activeIndex()
|
|
710
|
+
for (let offset = 1; offset <= rows.length; offset += 1) {
|
|
711
|
+
const next = wrapIndex(current + direction * offset, rows.length)
|
|
712
|
+
if (!rows[next]?.disabled) {
|
|
713
|
+
this.activeIndex(next)
|
|
714
|
+
return
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
private activateFirst() {
|
|
720
|
+
const index = this.optionRows().findIndex((row) => !row.disabled)
|
|
721
|
+
this.activeIndex(index)
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
private activateLast() {
|
|
725
|
+
const rows = this.optionRows()
|
|
726
|
+
for (let index = rows.length - 1; index >= 0; index -= 1) {
|
|
727
|
+
if (rows[index]?.disabled) continue
|
|
728
|
+
|
|
729
|
+
this.activeIndex(index)
|
|
730
|
+
return
|
|
731
|
+
}
|
|
732
|
+
this.activeIndex(-1)
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private ensureActiveIndex() {
|
|
736
|
+
const rows = this.optionRows()
|
|
737
|
+
const current = this.activeIndex()
|
|
738
|
+
if (current >= 0 && current < rows.length && !rows[current]?.disabled)
|
|
739
|
+
return
|
|
740
|
+
|
|
741
|
+
this.activateFirst()
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private shouldOpenOnFocus() {
|
|
745
|
+
return unref(this.openOnFocus) !== false && this.meetsMinimumQueryLength()
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
private meetsMinimumQueryLength() {
|
|
749
|
+
return (
|
|
750
|
+
this.query().trim().length >= this.readPositiveInteger(this.minLength, 0)
|
|
751
|
+
)
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private canCreateCustomValues() {
|
|
755
|
+
return unref(this.allowCustomValues) !== false
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
private hasOptionValue(option: ResolvedAutoCompleteOption) {
|
|
759
|
+
if (unref(this.allowDuplicates)) return false
|
|
760
|
+
|
|
761
|
+
const key = normalizeItemValue(option.value)
|
|
762
|
+
return this.resolvedItems().some(
|
|
763
|
+
(item) => normalizeItemValue(item.value) === key,
|
|
764
|
+
)
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
private hasItemValue(itemInput: MultiAutoCompleteItem) {
|
|
768
|
+
if (unref(this.allowDuplicates)) return false
|
|
769
|
+
|
|
770
|
+
const item = resolveMultiAutoCompleteItem(itemInput)
|
|
771
|
+
const key = normalizeItemValue(item.value)
|
|
772
|
+
return this.resolvedItems().some(
|
|
773
|
+
(existing) => normalizeItemValue(existing.value) === key,
|
|
774
|
+
)
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
private splitValues(value: string) {
|
|
778
|
+
const values =
|
|
779
|
+
unref(this.onSplitInput)?.(value) ??
|
|
780
|
+
splitBySeparators(value, this.resolvedSeparators())
|
|
781
|
+
return values.map((item) => item.trim()).filter(Boolean)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
private resolvedSeparators() {
|
|
785
|
+
const value = unref(this.separators)
|
|
786
|
+
return typeof value === 'string' ? value : ',;'
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
private readPositiveInteger(
|
|
790
|
+
value: RefOrValue<number | string> | undefined,
|
|
791
|
+
fallback: number,
|
|
792
|
+
) {
|
|
793
|
+
const raw = unref(value)
|
|
794
|
+
const parsed =
|
|
795
|
+
typeof raw === 'number' ? raw : Number.parseFloat(String(raw))
|
|
796
|
+
return Number.isFinite(parsed) && parsed >= 0
|
|
797
|
+
? Math.trunc(parsed)
|
|
798
|
+
: fallback
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
private clearClosingTimer() {
|
|
802
|
+
if (this.closingTimer === undefined) return
|
|
803
|
+
|
|
804
|
+
clearTimeout(this.closingTimer)
|
|
805
|
+
this.closingTimer = undefined
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function resolveMultiAutoCompleteItem(
|
|
810
|
+
itemInput: RefOrValue<MultiAutoCompleteItem>,
|
|
811
|
+
): ResolvedMultiAutoCompleteItem {
|
|
812
|
+
const item = unref(itemInput)
|
|
813
|
+
const label = String(unref(item.label) ?? '')
|
|
814
|
+
const value = unref(item.value) ?? label
|
|
815
|
+
return {
|
|
816
|
+
label,
|
|
817
|
+
value,
|
|
818
|
+
disabled: unref(item.disabled),
|
|
819
|
+
invalid: unref(item.invalid),
|
|
820
|
+
tone: unref(item.tone),
|
|
821
|
+
keywords: resolveKeywords(item, label, value),
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function resolveAutoCompleteOption(
|
|
826
|
+
optionInput: RefOrValue<AutoCompleteOption>,
|
|
827
|
+
): ResolvedAutoCompleteOption {
|
|
828
|
+
const option = unref(optionInput)
|
|
829
|
+
const label = String(unref(option.label) ?? '')
|
|
830
|
+
const value = unref(option.value) ?? label
|
|
831
|
+
return {
|
|
832
|
+
label,
|
|
833
|
+
value,
|
|
834
|
+
disabled: unref(option.disabled),
|
|
835
|
+
keywords: resolveKeywords(option, label, value),
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function resolveKeywords(
|
|
840
|
+
item: Pick<AutoCompleteOption, 'keywords'>,
|
|
841
|
+
label: string,
|
|
842
|
+
value: AutoCompleteValue,
|
|
843
|
+
) {
|
|
844
|
+
const keywords = unref(item.keywords)
|
|
845
|
+
const source: string[] = Array.isArray(keywords)
|
|
846
|
+
? keywords.map((keyword) => String(unref(keyword) ?? ''))
|
|
847
|
+
: typeof keywords === 'string'
|
|
848
|
+
? keywords.split(/\s+/)
|
|
849
|
+
: []
|
|
850
|
+
return [label, String(value), ...source].filter(Boolean)
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function splitBySeparators(value: string, separators: string) {
|
|
854
|
+
const values: string[] = []
|
|
855
|
+
let current = ''
|
|
856
|
+
for (const char of value) {
|
|
857
|
+
if (char === '\n' || separators.includes(char)) {
|
|
858
|
+
values.push(current)
|
|
859
|
+
current = ''
|
|
860
|
+
continue
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
current += char
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
values.push(current)
|
|
867
|
+
return values
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function normalizeSearchText(value: string) {
|
|
871
|
+
return value.trim().toLocaleLowerCase()
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function normalizeItemValue(value: AutoCompleteValue) {
|
|
875
|
+
return String(value).trim().toLocaleLowerCase()
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function wrapIndex(index: number, length: number) {
|
|
879
|
+
return ((index % length) + length) % length
|
|
880
|
+
}
|