@v1nt1248/3nclient-lib 0.1.80 → 0.1.82
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/components/ui3n-menu/types.d.ts +2 -0
- package/dist/components/ui3n-menu/ui3n-menu.vue.d.ts +2 -0
- package/dist/style.css +1 -1
- package/dist/ui3n-components.js +3413 -3384
- package/package.json +1 -1
- package/src/components/ui3n-autocomplete/types.ts +3 -0
- package/src/components/ui3n-autocomplete/ui3n-autocomplete.vue +46 -14
- package/src/components/ui3n-menu/types.ts +2 -0
- package/src/components/ui3n-menu/ui3n-menu.vue +18 -4
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> {
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
const inputEl = ref<Nullable<HTMLInputElement>>(null);
|
|
53
53
|
const menuBodyEl = ref<Nullable<HTMLDivElement>>(null);
|
|
54
54
|
const activeItemsIndex = ref<Nullable<number>>(null);
|
|
55
|
+
const isNewValueValid = ref(true);
|
|
55
56
|
|
|
56
57
|
const ids = computed(() => props.returnObject
|
|
57
58
|
? (props.modelValue as T[]).map(item => item.id)
|
|
@@ -96,7 +97,7 @@
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
function onBlur() {
|
|
99
|
-
emits('update:focused', false)
|
|
100
|
+
emits('update:focused', false);
|
|
100
101
|
activeItemsIndex.value = null;
|
|
101
102
|
|
|
102
103
|
setTimeout(() => {
|
|
@@ -166,7 +167,7 @@
|
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
function handlePressingEscOrTabKeys() {
|
|
169
|
-
emits('update:focused', false)
|
|
170
|
+
emits('update:focused', false);
|
|
170
171
|
isMenuOpen.value = false;
|
|
171
172
|
activeItemsIndex.value = null;
|
|
172
173
|
query.value = '';
|
|
@@ -175,21 +176,35 @@
|
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
function handlePressingEnterKey() {
|
|
178
|
-
if (activeItemsIndex.value === null) {
|
|
179
|
+
if (activeItemsIndex.value === null && !isMenuOpen.value) {
|
|
179
180
|
isMenuOpen.value = true;
|
|
180
181
|
activeItemsIndex.value = 0;
|
|
181
182
|
menuBodyEl.value && menuBodyEl.value.focus();
|
|
182
183
|
return;
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
+
}
|
|
188
205
|
}
|
|
189
206
|
|
|
190
207
|
function onKeydown(event: KeyboardEvent, key: 'down' | 'up' | 'esc' | 'enter' | 'tab') {
|
|
191
|
-
if (isEmpty(filteredItems.value)) return;
|
|
192
|
-
|
|
193
208
|
switch (key) {
|
|
194
209
|
case 'down':
|
|
195
210
|
event.preventDefault();
|
|
@@ -219,18 +234,29 @@
|
|
|
219
234
|
if (val !== oVal) {
|
|
220
235
|
activeItemsIndex.value = null;
|
|
221
236
|
}
|
|
237
|
+
|
|
238
|
+
if (val && !isNewValueValid.value) {
|
|
239
|
+
isNewValueValid.value = true;
|
|
240
|
+
}
|
|
222
241
|
},
|
|
223
242
|
);
|
|
224
243
|
</script>
|
|
225
244
|
|
|
226
245
|
<template>
|
|
227
246
|
<div :class="$style.ui3nAutocomplete">
|
|
228
|
-
<ui3n-menu
|
|
247
|
+
<ui3n-menu
|
|
248
|
+
v-model="isMenuOpen"
|
|
249
|
+
:offset-x="2"
|
|
250
|
+
:offset-y="4"
|
|
251
|
+
:content-border-radius="[0, 0, 8, 8]"
|
|
252
|
+
:disabled="disabled"
|
|
253
|
+
>
|
|
229
254
|
<div ref="activatorEl" :class="$style.trigger">
|
|
230
255
|
<template v-if="chips">
|
|
231
256
|
<template v-for="(item, index) in modelValue" :key="item.id">
|
|
232
257
|
<slot name="chip" :item="item" :index="index">
|
|
233
258
|
<ui3n-chip
|
|
259
|
+
height="32"
|
|
234
260
|
round
|
|
235
261
|
closeable
|
|
236
262
|
@close="onChipClose(item)"
|
|
@@ -249,7 +275,8 @@
|
|
|
249
275
|
ref="inputEl"
|
|
250
276
|
v-model="query"
|
|
251
277
|
type="text"
|
|
252
|
-
:class="$style.input"
|
|
278
|
+
:class="[$style.input, !isNewValueValid && $style.inputWithError]"
|
|
279
|
+
:placeholder="isEmpty(modelValue) && placeholder ? placeholder : ''"
|
|
253
280
|
:disabled="disabled"
|
|
254
281
|
@input="onInput"
|
|
255
282
|
@focusin="emits('update:focused', true)"
|
|
@@ -295,7 +322,7 @@
|
|
|
295
322
|
|
|
296
323
|
<style lang="scss" module>
|
|
297
324
|
.ui3nAutocomplete {
|
|
298
|
-
--autocomplete-min-height: var(--spacing-
|
|
325
|
+
--autocomplete-min-height: var(--spacing-l);
|
|
299
326
|
|
|
300
327
|
position: relative;
|
|
301
328
|
width: 100%;
|
|
@@ -329,7 +356,7 @@
|
|
|
329
356
|
flex-grow: 1;
|
|
330
357
|
height: var(--autocomplete-min-height);
|
|
331
358
|
font-size: var(--font-12);
|
|
332
|
-
line-height: var(--
|
|
359
|
+
line-height: var(--autocomplete-min-height);
|
|
333
360
|
font-weight: 400;
|
|
334
361
|
color: var(--color-text-control-primary-default);
|
|
335
362
|
background-color: transparent;
|
|
@@ -342,14 +369,19 @@
|
|
|
342
369
|
|
|
343
370
|
&:hover,
|
|
344
371
|
&:focus {
|
|
372
|
+
background-color: var(--color-bg-control-secondary-default);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
&.inputWithError {
|
|
345
376
|
height: calc(var(--autocomplete-min-height) - 2px);
|
|
346
|
-
|
|
377
|
+
padding: 0 calc(var(--spacing-s) - 1px);
|
|
378
|
+
border: 1px solid var(--error-content-default);
|
|
347
379
|
}
|
|
348
380
|
}
|
|
349
381
|
|
|
350
382
|
.body {
|
|
351
383
|
position: relative;
|
|
352
|
-
background-color: var(--color-bg-
|
|
384
|
+
background-color: var(--color-bg-block-primary-default);
|
|
353
385
|
padding: var(--spacing-xs);
|
|
354
386
|
}
|
|
355
387
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import { ref, watch } from 'vue';
|
|
2
|
+
import { computed, ref, watch } from 'vue';
|
|
3
3
|
import { autoUpdate, flip, useFloating, offset, shift } from '@floating-ui/vue';
|
|
4
4
|
import { default as vClickOutside } from '../../directives/ui3n-click-outside';
|
|
5
5
|
import type { Ui3nMenuEmits, Ui3nMenuProps, Ui3nMenuSlots } from './types';
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
positionStrategy: 'absolute',
|
|
9
9
|
offsetX: 0,
|
|
10
10
|
offsetY: 0,
|
|
11
|
+
contentBorderRadius: 4,
|
|
12
|
+
zIndex: 1000,
|
|
11
13
|
closeOnClick: true,
|
|
12
14
|
closeOnClickOutside: true,
|
|
13
15
|
disabled: false,
|
|
@@ -20,6 +22,16 @@
|
|
|
20
22
|
const menuContentElement = ref<HTMLDivElement | null>(null);
|
|
21
23
|
const isShow = ref(false);
|
|
22
24
|
|
|
25
|
+
const contentBorderRadiusCss = computed(() => {
|
|
26
|
+
if (typeof props.contentBorderRadius === 'number') {
|
|
27
|
+
return `${props.contentBorderRadius}px`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const [tl, tr, br, bl] = props.contentBorderRadius;
|
|
31
|
+
return `${tl}px ${tr}px ${br}px ${bl}px`;
|
|
32
|
+
});
|
|
33
|
+
const zIndexCss = computed(() => props.zIndex);
|
|
34
|
+
|
|
23
35
|
const { floatingStyles, isPositioned } = useFloating(
|
|
24
36
|
menuTriggerElement,
|
|
25
37
|
menuContentElement,
|
|
@@ -112,6 +124,7 @@
|
|
|
112
124
|
|
|
113
125
|
.ui3nMenu {
|
|
114
126
|
--ui3n-menu-content-bg: var(--color-bg-control-secondary-default);
|
|
127
|
+
--ui3n-menu-content-border-raduis: v-bind(contentBorderRadiusCss);
|
|
115
128
|
|
|
116
129
|
position: relative;
|
|
117
130
|
max-width: max-content;
|
|
@@ -130,9 +143,10 @@
|
|
|
130
143
|
|
|
131
144
|
.ui3nMenuContent {
|
|
132
145
|
position: absolute;
|
|
133
|
-
border-radius:
|
|
146
|
+
border-radius: var(--ui3n-menu-content-border-raduis);
|
|
134
147
|
background-color: var(--ui3n-menu-content-bg);
|
|
135
|
-
z-index:
|
|
136
|
-
|
|
148
|
+
z-index: v-bind(zIndexCss);
|
|
149
|
+
overflow: hidden;
|
|
150
|
+
@include mixins.dropShadow();
|
|
137
151
|
}
|
|
138
152
|
</style>
|