@v1nt1248/3nclient-lib 0.3.40 → 0.3.42

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@v1nt1248/3nclient-lib",
3
3
  "license": "AGPL-3.0-or-later",
4
4
  "author": "v1nt1248",
5
- "version": "0.3.40",
5
+ "version": "0.3.42",
6
6
  "description": "Library for 3NWeb clients",
7
7
  "type": "module",
8
8
  "files": [
@@ -26,6 +26,7 @@
26
26
  items: () => [],
27
27
  itemTitle: 'name',
28
28
  itemValue: 'id',
29
+ addNewValue: false,
29
30
  });
30
31
  const emits = defineEmits<Ui3nAutocompleteEmits<T>>();
31
32
  defineSlots<Ui3nAutocompleteSlots<T>>();
@@ -40,6 +41,7 @@
40
41
  const activeItemsIndex = ref<Nullable<number>>(null);
41
42
  const isLastChipHighlighted = ref(false);
42
43
  const isNewValueValid = ref(true);
44
+ const selectedFromMenu = ref(false);
43
45
 
44
46
  const ids = computed(() =>
45
47
  props.returnObject ? (props.modelValue as T[]).map(item => item.id) : (props.modelValue as Array<T[keyof T]>),
@@ -89,12 +91,60 @@
89
91
  activeItemsIndex.value = null;
90
92
  isLastChipHighlighted.value = false;
91
93
 
94
+ if (!selectedFromMenu.value) {
95
+ tryAddNewValue();
96
+ }
97
+
92
98
  setTimeout(() => {
93
99
  isMenuOpen.value = false;
94
100
  query.value = '';
101
+ selectedFromMenu.value = false;
95
102
  }, 150);
96
103
  }
97
104
 
105
+ function tryAddNewValue(): boolean {
106
+ if (!props.addNewValue || props.disabled) return false;
107
+
108
+ const text = query.value.trim();
109
+ if (!text) return false;
110
+
111
+ isNewValueValid.value = !props.newValueValidator ? true : props.newValueValidator(text);
112
+ emits('valid:new-value', isNewValueValid.value);
113
+ if (!isNewValueValid.value) return false;
114
+
115
+ const item = {
116
+ id: getRandomId(6),
117
+ [props.itemTitle]: text,
118
+ [props.itemValue]: text,
119
+ } as unknown as T;
120
+
121
+ if (!props.multiple) {
122
+ const newValue = props.returnObject ? [item] : [item[props.itemValue]];
123
+ emits('update:modelValue', newValue);
124
+ } else {
125
+ const alreadyPresent = props.returnObject
126
+ ? (props.modelValue as T[]).some(
127
+ v => v[props.itemValue] === item[props.itemValue] || v[props.itemTitle] === item[props.itemTitle],
128
+ )
129
+ : props.modelValue.some(v => v === item[props.itemValue]);
130
+
131
+ if (!alreadyPresent) {
132
+ const updatedValue = cloneDeep(props.modelValue);
133
+ // @ts-ignore
134
+ updatedValue.push(props.returnObject ? item : item[props.itemValue]);
135
+ emits('update:modelValue', updatedValue);
136
+ }
137
+ }
138
+
139
+ isNewValueValid.value = true;
140
+ props.clearOnSelect && (query.value = '');
141
+ return true;
142
+ }
143
+
144
+ function onItemMouseDown() {
145
+ selectedFromMenu.value = true;
146
+ }
147
+
98
148
  function onItemClick(item: T) {
99
149
  if (props.disabled) return;
100
150
 
@@ -155,7 +205,7 @@
155
205
  }
156
206
  }
157
207
 
158
- function handlePressingEscOrTabKeys() {
208
+ function handlePressingEscKey() {
159
209
  emits('update:focused', false);
160
210
  isMenuOpen.value = false;
161
211
  activeItemsIndex.value = null;
@@ -164,34 +214,35 @@
164
214
  activatorEl.value && activatorEl.value.blur();
165
215
  }
166
216
 
167
- function handlePressingEnterKey() {
168
- if (activeItemsIndex.value === null && !isMenuOpen.value) {
169
- isMenuOpen.value = true;
170
- activeItemsIndex.value = 0;
171
- menuBodyEl.value && menuBodyEl.value.focus({ preventScroll: true });
172
- return;
217
+ function handlePressingTabKey() {
218
+ if (props.addNewValue) {
219
+ tryAddNewValue();
173
220
  }
174
221
 
175
- if (activeItemsIndex.value === null && isMenuOpen.value && query.value) {
176
- isNewValueValid.value = !props.newValueValidator ? true : props.newValueValidator(query.value);
177
- emits('valid:new-value', isNewValueValid.value);
178
- if (isNewValueValid.value) {
179
- const item = {
180
- id: getRandomId(6),
181
- [props.itemTitle]: query.value,
182
- [props.itemValue]: query.value,
183
- };
184
- onItemClick(item as unknown as T);
185
- }
186
- props.clearOnSelect && (query.value = '');
187
- return;
188
- }
222
+ emits('update:focused', false);
223
+ isMenuOpen.value = false;
224
+ activeItemsIndex.value = null;
225
+ query.value = '';
226
+ }
189
227
 
228
+ function handlePressingEnterKey() {
190
229
  if (activeItemsIndex.value !== null) {
191
230
  const item = filteredItems.value[activeItemsIndex.value!];
192
231
  onItemClick(item);
193
232
  activeItemsIndex.value = null;
194
233
  props.clearOnSelect && (query.value = '');
234
+ return;
235
+ }
236
+
237
+ if (props.addNewValue && query.value.trim()) {
238
+ tryAddNewValue();
239
+ return;
240
+ }
241
+
242
+ if (!isMenuOpen.value) {
243
+ isMenuOpen.value = true;
244
+ activeItemsIndex.value = 0;
245
+ menuBodyEl.value && menuBodyEl.value.focus({ preventScroll: true });
195
246
  }
196
247
  }
197
248
 
@@ -229,8 +280,11 @@
229
280
  }
230
281
 
231
282
  case 'esc':
283
+ handlePressingEscKey();
284
+ break;
285
+
232
286
  case 'tab':
233
- handlePressingEscOrTabKeys();
287
+ handlePressingTabKey();
234
288
  break;
235
289
 
236
290
  case 'enter': {
@@ -251,6 +305,7 @@
251
305
  isMenuOpen.value = false;
252
306
  activeItemsIndex.value = null;
253
307
  isNewValueValid.value = true;
308
+ selectedFromMenu.value = false;
254
309
 
255
310
  emits('update:modelValue', [] as unknown as T[] & Array<T[keyof T]>);
256
311
  emits('update:search', '');
@@ -337,7 +392,7 @@
337
392
  :class="$style.trigger"
338
393
  >
339
394
  <transition-group
340
- v-if="chips"
395
+ v-if="chips && !isEmpty(modelValue)"
341
396
  name="chipAnim"
342
397
  tag="div"
343
398
  :class="$style.chipsContainer"
@@ -414,6 +469,7 @@
414
469
  activeItemsIndex === index && $style.itemSelected,
415
470
  disabled && $style.itemDisabled,
416
471
  ]"
472
+ @mousedown="onItemMouseDown"
417
473
  @click.stop.prevent="onItemClick(item)"
418
474
  >
419
475
  <slot
@@ -455,6 +511,7 @@
455
511
  --ui3n-autocomplete-border-radius: 4px;
456
512
 
457
513
  position: relative;
514
+ display: flex;
458
515
  width: 100%;
459
516
  min-height: var(--ui3n-autocomplete-min-height);
460
517
 
@@ -491,8 +548,8 @@
491
548
  border-radius: var(--ui3n-autocomplete-border-radius);
492
549
  padding: 0 var(--ui3n-autocomplete-padding-inline);
493
550
  flex: 1 1 60px;
551
+ min-width: 60px;
494
552
  height: var(--ui3n-autocomplete-min-height);
495
- width: 100%;
496
553
  font-size: var(--ui3n-autocomplete-font-size);
497
554
  line-height: var(--ui3n-autocomplete-min-height);
498
555
  font-weight: 400;
@@ -572,6 +629,10 @@
572
629
  flex-wrap: wrap;
573
630
  gap: 4px;
574
631
  align-items: flex-start;
632
+
633
+ &:empty {
634
+ display: none;
635
+ }
575
636
  }
576
637
 
577
638
  .chipWrapper {