@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/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.1.80",
5
+ "version": "0.1.82",
6
6
  "description": "Library for 3NWeb clients",
7
7
  "type": "module",
8
8
  "files": [
@@ -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
- const item = filteredItems.value[activeItemsIndex.value!];
186
- onItemClick(item);
187
- activeItemsIndex.value = null;
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 v-model="isMenuOpen" :offset-x="2" :offset-y="4" :disabled="disabled">
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-ml);
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(--font-16);
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
- border: 1px solid var(--color-border-control-accent-focused);
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-control-secondary-default);
384
+ background-color: var(--color-bg-block-primary-default);
353
385
  padding: var(--spacing-xs);
354
386
  }
355
387
 
@@ -5,6 +5,8 @@ export interface Ui3nMenuProps {
5
5
  positionStrategy?: 'absolute' | 'fixed';
6
6
  offsetX?: number;
7
7
  offsetY?: number;
8
+ contentBorderRadius?: number | number[];
9
+ zIndex?: number;
8
10
  closeOnClick?: boolean;
9
11
  closeOnClickOutside?: boolean;
10
12
  disabled?: boolean;
@@ -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: 4px;
146
+ border-radius: var(--ui3n-menu-content-border-raduis);
134
147
  background-color: var(--ui3n-menu-content-bg);
135
- z-index: 1000;
136
- @include mixins.elevation(3);
148
+ z-index: v-bind(zIndexCss);
149
+ overflow: hidden;
150
+ @include mixins.dropShadow();
137
151
  }
138
152
  </style>