@v1nt1248/3nclient-lib 0.1.79 → 0.1.81
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/dist/components/ui3n-autocomplete/types.d.ts +3 -0
- package/dist/components/ui3n-autocomplete/ui3n-autocomplete.vue.d.ts +2 -1
- package/dist/style.css +1 -1
- package/dist/ui3n-components.js +3293 -3280
- package/package.json +1 -1
- package/src/components/ui3n-autocomplete/types.ts +3 -0
- package/src/components/ui3n-autocomplete/ui3n-autocomplete.vue +42 -19
package/package.json
CHANGED
|
@@ -20,6 +20,8 @@ export interface Ui3nAutocompleteProps<T extends Ui3nAutocompleteOptionBase> {
|
|
|
20
20
|
itemTitle?: keyof T;
|
|
21
21
|
itemValue?: keyof T;
|
|
22
22
|
modelValue: T[] | Array<T[keyof T]>;
|
|
23
|
+
addNewValue?: boolean;
|
|
24
|
+
newValueValidator?: (value: string) => boolean;
|
|
23
25
|
multiple?: boolean;
|
|
24
26
|
noDataText?: string;
|
|
25
27
|
placeholder?: string;
|
|
@@ -30,6 +32,7 @@ export interface Ui3nAutocompleteEmits<T extends Ui3nAutocompleteOptionBase> {
|
|
|
30
32
|
(event: 'update:modelValue', value: T[] | Array<T[keyof T]>): void;
|
|
31
33
|
(event: 'update:search', value: string): void;
|
|
32
34
|
(event: 'update:focused', value: boolean): void;
|
|
35
|
+
(event: 'valid:new-value', value: boolean): void;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export interface Ui3nAutocompleteSlots<T extends Ui3nAutocompleteOptionBase> {
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
import Ui3nChip from '../ui3n-chip/ui3n-chip.vue';
|
|
23
23
|
import Ui3nMenu from '../ui3n-menu/ui3n-menu.vue';
|
|
24
24
|
import Ui3nHtml from '@/directives/ui3n-html';
|
|
25
|
-
import Ui3nClickOutside from '@/directives/ui3n-click-outside';
|
|
26
25
|
import { markSearch, getRandomId } from '@/utils';
|
|
27
26
|
import type { Nullable } from '@/components/types';
|
|
28
27
|
import {
|
|
@@ -33,12 +32,10 @@
|
|
|
33
32
|
} from './types';
|
|
34
33
|
|
|
35
34
|
const vUi3nHtml = Ui3nHtml;
|
|
36
|
-
const vUi3nClickOutside = Ui3nClickOutside;
|
|
37
35
|
|
|
38
36
|
const props = withDefaults(defineProps<Ui3nAutocompleteProps<T>>(), {
|
|
39
37
|
clearOnSelect: false,
|
|
40
38
|
filterKeys: () => [],
|
|
41
|
-
filterMode: 'every',
|
|
42
39
|
hideSelected: false,
|
|
43
40
|
items: () => [],
|
|
44
41
|
itemTitle: 'name',
|
|
@@ -55,6 +52,7 @@
|
|
|
55
52
|
const inputEl = ref<Nullable<HTMLInputElement>>(null);
|
|
56
53
|
const menuBodyEl = ref<Nullable<HTMLDivElement>>(null);
|
|
57
54
|
const activeItemsIndex = ref<Nullable<number>>(null);
|
|
55
|
+
const isNewValueValid = ref(true);
|
|
58
56
|
|
|
59
57
|
const ids = computed(() => props.returnObject
|
|
60
58
|
? (props.modelValue as T[]).map(item => item.id)
|
|
@@ -99,9 +97,13 @@
|
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
function onBlur() {
|
|
102
|
-
emits('update:focused', false)
|
|
100
|
+
emits('update:focused', false);
|
|
103
101
|
activeItemsIndex.value = null;
|
|
104
|
-
|
|
102
|
+
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
isMenuOpen.value = false;
|
|
105
|
+
query.value = '';
|
|
106
|
+
}, 150);
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
function onItemClick(item: T) {
|
|
@@ -165,7 +167,7 @@
|
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
function handlePressingEscOrTabKeys() {
|
|
168
|
-
emits('update:focused', false)
|
|
170
|
+
emits('update:focused', false);
|
|
169
171
|
isMenuOpen.value = false;
|
|
170
172
|
activeItemsIndex.value = null;
|
|
171
173
|
query.value = '';
|
|
@@ -174,21 +176,35 @@
|
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
function handlePressingEnterKey() {
|
|
177
|
-
if (activeItemsIndex.value === null) {
|
|
179
|
+
if (activeItemsIndex.value === null && !isMenuOpen.value) {
|
|
178
180
|
isMenuOpen.value = true;
|
|
179
181
|
activeItemsIndex.value = 0;
|
|
180
182
|
menuBodyEl.value && menuBodyEl.value.focus();
|
|
181
183
|
return;
|
|
182
184
|
}
|
|
183
185
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
if (activeItemsIndex.value === null && isMenuOpen.value && query.value) {
|
|
187
|
+
isNewValueValid.value = !props.newValueValidator ? true : props.newValueValidator(query.value);
|
|
188
|
+
emits('valid:new-value', isNewValueValid.value);
|
|
189
|
+
if (isNewValueValid.value) {
|
|
190
|
+
const item = {
|
|
191
|
+
id: getRandomId(6),
|
|
192
|
+
[props.itemTitle]: query.value,
|
|
193
|
+
[props.itemValue]: query.value,
|
|
194
|
+
};
|
|
195
|
+
onItemClick(item as unknown as T);
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (activeItemsIndex.value !== null) {
|
|
201
|
+
const item = filteredItems.value[activeItemsIndex.value!];
|
|
202
|
+
onItemClick(item);
|
|
203
|
+
activeItemsIndex.value = null;
|
|
204
|
+
}
|
|
187
205
|
}
|
|
188
206
|
|
|
189
207
|
function onKeydown(event: KeyboardEvent, key: 'down' | 'up' | 'esc' | 'enter' | 'tab') {
|
|
190
|
-
if (isEmpty(filteredItems.value)) return;
|
|
191
|
-
|
|
192
208
|
switch (key) {
|
|
193
209
|
case 'down':
|
|
194
210
|
event.preventDefault();
|
|
@@ -212,16 +228,16 @@
|
|
|
212
228
|
}
|
|
213
229
|
}
|
|
214
230
|
|
|
215
|
-
function onClickOutside() {
|
|
216
|
-
handlePressingEscOrTabKeys();
|
|
217
|
-
}
|
|
218
|
-
|
|
219
231
|
watch(
|
|
220
232
|
() => size(filteredItems.value),
|
|
221
233
|
(val, oVal) => {
|
|
222
234
|
if (val !== oVal) {
|
|
223
235
|
activeItemsIndex.value = null;
|
|
224
236
|
}
|
|
237
|
+
|
|
238
|
+
if (val && !isNewValueValid.value) {
|
|
239
|
+
isNewValueValid.value = true;
|
|
240
|
+
}
|
|
225
241
|
},
|
|
226
242
|
);
|
|
227
243
|
</script>
|
|
@@ -252,7 +268,8 @@
|
|
|
252
268
|
ref="inputEl"
|
|
253
269
|
v-model="query"
|
|
254
270
|
type="text"
|
|
255
|
-
:class="$style.input"
|
|
271
|
+
:class="[$style.input, !isNewValueValid && $style.inputWithError]"
|
|
272
|
+
:placeholder="isEmpty(modelValue) && placeholder ? placeholder : ''"
|
|
256
273
|
:disabled="disabled"
|
|
257
274
|
@input="onInput"
|
|
258
275
|
@focusin="emits('update:focused', true)"
|
|
@@ -266,7 +283,7 @@
|
|
|
266
283
|
</div>
|
|
267
284
|
|
|
268
285
|
<template #menu>
|
|
269
|
-
<div ref="menuBodyEl"
|
|
286
|
+
<div ref="menuBodyEl" :class="$style.body">
|
|
270
287
|
<template v-if="size(filteredItems)">
|
|
271
288
|
<template v-for="(item, index) in filteredItems" :key="item.id">
|
|
272
289
|
<div
|
|
@@ -335,6 +352,7 @@
|
|
|
335
352
|
line-height: var(--font-16);
|
|
336
353
|
font-weight: 400;
|
|
337
354
|
color: var(--color-text-control-primary-default);
|
|
355
|
+
background-color: transparent;
|
|
338
356
|
border: none;
|
|
339
357
|
outline: none;
|
|
340
358
|
|
|
@@ -345,7 +363,12 @@
|
|
|
345
363
|
&:hover,
|
|
346
364
|
&:focus {
|
|
347
365
|
height: calc(var(--autocomplete-min-height) - 2px);
|
|
348
|
-
|
|
366
|
+
|
|
367
|
+
&:not(.inputWithError) {
|
|
368
|
+
border: 1px solid var(--color-border-control-accent-focused);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
border: 1px solid var(--error-content-default);
|
|
349
372
|
}
|
|
350
373
|
}
|
|
351
374
|
|