@v1nt1248/3nclient-lib 0.3.19 → 0.3.20

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.
@@ -1,49 +1,106 @@
1
1
  <script lang="ts" setup>
2
- import { computed, onMounted, ref, watch } from 'vue';
2
+ import { computed, nextTick, onMounted, ref, watch } from 'vue';
3
3
  import Ui3nIcon from '../ui3n-icon/ui3n-icon.vue';
4
4
  import Ui3nButton from '../ui3n-button/ui3n-button.vue';
5
5
  import type { Ui3nInputEmits, Ui3nInputProps } from './types';
6
6
 
7
- const props = defineProps<Ui3nInputProps>();
7
+ const props = withDefaults(defineProps<Ui3nInputProps>(), {
8
+ modelValue: '',
9
+ type: 'text',
10
+ size: 'regular',
11
+ label: '',
12
+ placeholder: '',
13
+ icon: '',
14
+ iconColor: '',
15
+ displayStateMode: undefined,
16
+ displayStateMessage: '',
17
+ rules: () => [],
18
+ clearable: false,
19
+ autofocus: false,
20
+ validateAtStartup: false,
21
+ hideBottomSpace: false,
22
+ displayStateWithIcon: false,
23
+ disabled: false,
24
+ });
8
25
  const emits = defineEmits<Ui3nInputEmits>();
9
26
 
10
27
  const inputElement = ref<HTMLInputElement | null>(null);
11
- const text = ref<string>('');
28
+ const internalValue = ref('');
12
29
  const isDirty = ref(false);
13
30
  const isFocused = ref(false);
14
31
  const errorMessage = ref('');
15
32
 
16
- const isInitialized = ref(false);
17
-
18
33
  const cssIconColor = computed(() => {
19
34
  if (props.disabled) {
20
35
  return 'var(--color-icon-control-primary-disabled)';
21
36
  }
22
-
23
37
  return props.iconColor || 'var(--color-icon-control-primary-default)';
24
38
  });
25
39
 
40
+ const showSuccessIcon = computed(
41
+ () => props.displayStateMode === 'success' && props.displayStateWithIcon && !isFocused.value,
42
+ );
43
+
44
+ const showClearButton = computed(
45
+ () => !props.disabled && props.clearable && !!internalValue.value && !showSuccessIcon.value,
46
+ );
47
+
48
+ const showMessageBlock = computed(
49
+ () => !!errorMessage.value || !!(props.displayStateMode && props.displayStateMessage),
50
+ );
51
+
52
+ const isErrorMessage = computed(
53
+ () => !!errorMessage.value || (props.displayStateMode === 'error' && !!props.displayStateMessage),
54
+ );
55
+
56
+ const isSuccessMessage = computed(() => props.displayStateMode === 'success' && !!props.displayStateMessage);
57
+
58
+ function getTargetValue(event: Event | KeyboardEvent): string {
59
+ return (event.target as HTMLInputElement).value;
60
+ }
61
+
62
+ function validate(value: string) {
63
+ const errors: string[] = [];
64
+ for (const rule of props.rules) {
65
+ if (typeof rule !== 'function') {
66
+ continue;
67
+ }
68
+
69
+ const result = rule(value);
70
+ if (typeof result === 'string') {
71
+ errors.push(result);
72
+ } else if (!result) {
73
+ errors.push('Invalid');
74
+ }
75
+ }
76
+
77
+ errorMessage.value = errors.join('. ');
78
+ if (isDirty.value || props.validateAtStartup) {
79
+ emits('update:valid', errors.length === 0);
80
+ }
81
+ }
82
+
26
83
  function onFocus(event: Event) {
27
84
  isFocused.value = true;
28
85
  emits('focus', event);
29
86
  }
30
87
 
31
88
  function onBlur(event: Event) {
32
- setTimeout(() => {
89
+ nextTick(() => {
33
90
  isFocused.value = false;
34
91
  emits('blur', event);
35
- }, 100);
92
+ });
36
93
  }
37
94
 
38
95
  function onChange(event: Event) {
39
- const value = (event.target as HTMLInputElement).value;
96
+ const value = getTargetValue(event);
40
97
  validate(value);
41
98
  emits('change', value);
42
99
  }
43
100
 
44
- function onInput(ev: Event) {
45
- const value = (ev.target as HTMLInputElement).value;
46
- text.value = value;
101
+ function onInput(event: Event) {
102
+ const value = getTargetValue(event);
103
+ internalValue.value = value;
47
104
  isDirty.value = true;
48
105
  validate(value);
49
106
  emits('update:modelValue', value);
@@ -51,19 +108,15 @@
51
108
  }
52
109
 
53
110
  function onEnterKeydown(event: KeyboardEvent) {
54
- const { altKey, ctrlKey, metaKey, shiftKey, target } = event;
55
- const value = (target as HTMLInputElement).value;
111
+ const { altKey, ctrlKey, metaKey, shiftKey } = event;
56
112
  isDirty.value = true;
57
- validate(value);
58
- emits('update:modelValue', value);
59
- emits('enter', { value, altKey, ctrlKey, metaKey, shiftKey });
113
+ validate(internalValue.value);
114
+ emits('enter', { value: internalValue.value, altKey, ctrlKey, metaKey, shiftKey });
60
115
  }
61
116
 
62
117
  function onEscapeKeydown(event: KeyboardEvent) {
63
- const value = (event.target as HTMLInputElement).value;
64
118
  isDirty.value = true;
65
- validate(value);
66
- emits('update:modelValue', value);
119
+ validate(internalValue.value);
67
120
  emits('escape', event);
68
121
  }
69
122
 
@@ -72,31 +125,20 @@
72
125
  }
73
126
 
74
127
  function clearValue() {
75
- text.value = '';
128
+ internalValue.value = '';
76
129
  isDirty.value = false;
77
- validate('');
130
+ errorMessage.value = '';
131
+
132
+ if (inputElement.value) {
133
+ inputElement.value.value = '';
134
+ inputElement.value.focus();
135
+ }
136
+
78
137
  emits('update:modelValue', '');
79
138
  emits('input', '');
80
139
  emits('change', '');
81
140
  emits('clear');
82
- }
83
-
84
- function validate(text: string) {
85
- errorMessage.value = '';
86
- if (props.rules && props.rules.length) {
87
- for (const validateFunction of props.rules) {
88
- if (typeof validateFunction === 'function') {
89
- const res = validateFunction(text);
90
- if (typeof res === 'string') {
91
- errorMessage.value += `${res} `;
92
- }
93
- }
94
- }
95
- }
96
-
97
- if (isDirty.value) {
98
- emits('update:valid', !errorMessage.value);
99
- }
141
+ emits('update:valid', true);
100
142
  }
101
143
 
102
144
  defineExpose({
@@ -106,35 +148,34 @@
106
148
  });
107
149
 
108
150
  onMounted(() => {
109
- if (props.autofocus && inputElement.value) {
110
- inputElement.value.focus();
151
+ internalValue.value = props.modelValue ?? '';
152
+ if (inputElement.value) {
153
+ inputElement.value.value = internalValue.value;
154
+ if (props.autofocus) {
155
+ inputElement.value.focus();
156
+ }
157
+ emits('init', inputElement.value);
111
158
  }
112
159
 
113
- emits('init', inputElement.value!);
114
- if (props.validateAtStartup && !isInitialized.value) {
115
- validate(text.value);
116
- }
117
- if (!isInitialized.value) {
118
- isInitialized.value = true;
160
+ if (props.validateAtStartup) {
161
+ isDirty.value = true;
162
+ validate(internalValue.value);
119
163
  }
120
164
  });
121
165
 
122
166
  watch(
123
167
  () => props.modelValue,
124
- (val, oldVal) => {
125
- if ((val ?? '') !== (oldVal ?? '')) {
126
- text.value = val ?? '';
168
+ val => {
169
+ const normalized = val ?? '';
170
+ if (normalized !== internalValue.value) {
171
+ internalValue.value = normalized;
127
172
 
128
- if ((props.validateAtStartup && !isInitialized.value) || isInitialized.value) {
129
- validate(text.value);
130
- }
131
-
132
- if (!isInitialized.value) {
133
- isInitialized.value = true;
173
+ if (inputElement.value) {
174
+ inputElement.value.value = normalized;
134
175
  }
176
+ validate(normalized);
135
177
  }
136
178
  },
137
- { immediate: true },
138
179
  );
139
180
  </script>
140
181
 
@@ -142,10 +183,11 @@
142
183
  <div
143
184
  :class="[
144
185
  $style.ui3nInput,
186
+ size === 'large' && $style.large,
145
187
  label && $style.withLabel,
146
188
  hideBottomSpace && $style.withoutBottomSpace,
147
189
  icon && $style.withIcon,
148
- clearable && text && $style.clearable,
190
+ clearable && internalValue && $style.clearable,
149
191
  disabled && $style.disabled,
150
192
  (!!errorMessage || displayStateMode === 'error') && $style.error,
151
193
  displayStateMode === 'success' && $style.success,
@@ -161,8 +203,7 @@
161
203
  <input
162
204
  ref="inputElement"
163
205
  autocomplete="off"
164
- :type="type || 'text'"
165
- :value="text"
206
+ :type="type"
166
207
  :placeholder="placeholder"
167
208
  :disabled="disabled"
168
209
  :class="$style.ui3nInputField"
@@ -178,22 +219,20 @@
178
219
  <ui3n-icon
179
220
  v-if="icon"
180
221
  :icon="icon"
181
- :width="16"
182
- :height="16"
222
+ :size="16"
183
223
  :class="[$style.ui3nInputIcon, disabled && $style.ui3nInputIconDisabled]"
184
224
  />
185
225
 
186
226
  <ui3n-icon
187
- v-if="displayStateMode === 'success' && displayStateWithIcon && !isFocused"
227
+ v-if="showSuccessIcon"
188
228
  icon="round-check-circle-outline"
189
- :width="16"
190
- :height="16"
229
+ :size="16"
191
230
  color="var(--success-content-default)"
192
231
  :class="$style.ui3nInputSuccessIcon"
193
232
  />
194
233
 
195
234
  <ui3n-button
196
- v-if="clearable && !!text && !(displayStateMode === 'success' && displayStateWithIcon && !isFocused)"
235
+ v-if="showClearButton"
197
236
  type="icon"
198
237
  size="small"
199
238
  color="transparent"
@@ -201,15 +240,17 @@
201
240
  icon-size="16"
202
241
  icon-color="var(--color-icon-control-accent-default)"
203
242
  :class="$style.clearBtn"
243
+ @mousedown.prevent
244
+ @touchstart.prevent
204
245
  @click="clearValue"
205
246
  />
206
247
 
207
248
  <div
208
- v-if="(text && errorMessage) || (isDirty && errorMessage) || (displayStateMode && displayStateMessage)"
249
+ v-if="showMessageBlock"
209
250
  :class="[
210
251
  $style.ui3nInputFieldMessage,
211
- (errorMessage || (displayStateMode === 'error' && displayStateMessage)) && $style.ui3nInputErrorMessage,
212
- displayStateMode === 'success' && displayStateMessage && $style.ui3nInputSuccessMessage,
252
+ isErrorMessage && $style.ui3nInputErrorMessage,
253
+ isSuccessMessage && $style.ui3nInputSuccessMessage,
213
254
  ]"
214
255
  >
215
256
  {{ errorMessage || displayStateMessage }}
@@ -219,14 +260,41 @@
219
260
 
220
261
  <style lang="scss" module>
221
262
  .ui3nInput {
222
- --ui3n-input-height: var(--spacing-l);
263
+ --ui3n-input-height: 32px;
264
+ --ui3n-input-font-size: var(--font-13);
265
+ --ui3n-input-border-radius: var(--spacing-xs);
223
266
  --ui3n-input-padding-left: var(--spacing-s);
224
267
  --ui3n-input-padding-right: var(--spacing-s);
268
+ --ui3n-input-icon-size: 16px;
269
+ --ui3n-input-label-offset: calc(var(--font-16) + var(--spacing-xs));
270
+ --ui3n-input-message-top: calc(var(--ui3n-input-height) + var(--ui3n-input-label-offset) + 2px);
271
+
272
+ &:not(.withLabel) {
273
+ --ui3n-input-label-offset: 0;
274
+ }
275
+
276
+ &.large {
277
+ --ui3n-input-height: 48px;
278
+ --ui3n-input-font-size: var(--font-16);
279
+ --ui3n-input-border-radius: 8px;
280
+ --ui3n-input-padding-left: 12px;
281
+ --ui3n-input-padding-right: 12px;
282
+
283
+ .clearBtn {
284
+ right: 2px;
285
+ }
286
+
287
+ &.withIcon {
288
+ .ui3nInputIcon {
289
+ left: 6px;
290
+ }
291
+ }
292
+ }
225
293
 
226
294
  position: relative;
227
295
  width: 100%;
228
296
  padding: 1px 1px 15px 1px;
229
- border-radius: var(--spacing-xs);
297
+ border-radius: var(--ui3n-input-border-radius);
230
298
 
231
299
  &.withoutBottomSpace {
232
300
  padding-bottom: 0;
@@ -249,7 +317,7 @@
249
317
  }
250
318
 
251
319
  .ui3nInputIcon {
252
- color: v-bind(cssIconColor) !important;
320
+ color: v-bind(cssIconColor);
253
321
  }
254
322
  }
255
323
  }
@@ -273,22 +341,27 @@
273
341
  outline: none;
274
342
  height: var(--ui3n-input-height);
275
343
  padding: 0 var(--ui3n-input-padding-right) 0 var(--ui3n-input-padding-left);
276
- border-radius: var(--spacing-xs);
344
+ border-radius: var(--ui3n-input-border-radius);
277
345
  background-color: var(--color-bg-control-secondary-default);
278
- font-size: var(--font-13);
279
- line-height: var(--font-16);
346
+ font-size: var(--ui3n-input-font-size);
347
+ line-height: var(--spacing-m);
280
348
  font-weight: 400;
281
349
  color: var(--color-text-control-primary-default);
282
- transition: all 0.2s ease-in-out;
350
+ transition:
351
+ background-color 0.2s ease-in-out,
352
+ color 0.2s ease-in-out;
283
353
 
284
354
  &::placeholder {
285
355
  color: var(--color-text-control-secondary-default);
286
356
  font-style: italic;
287
- font-size: var(--font-13);
288
- line-height: var(--font-16);
357
+ font-size: var(--ui3n-input-font-size);
289
358
  font-weight: 400;
290
359
  }
291
360
 
361
+ &:focus {
362
+ outline: none;
363
+ }
364
+
292
365
  &[disabled] {
293
366
  background-color: var(--color-bg-control-secondary-disabled);
294
367
  color: var(--color-text-control-secondary-disabled);
@@ -297,13 +370,13 @@
297
370
 
298
371
  .ui3nInputIcon {
299
372
  position: absolute;
300
- left: var(--half-size, 4px);
301
- top: calc((var(--ui3n-input-height) - 16px) / 2);
302
- color: var(--color-icon-control-secondary-default) !important;
373
+ left: var(--spacing-xs);
374
+ top: calc((var(--ui3n-input-height) - var(--ui3n-input-icon-size)) / 2);
375
+ color: var(--color-icon-control-secondary-default);
303
376
  }
304
377
 
305
378
  .ui3nInputIconDisabled {
306
- color: v-bind(cssIconColor) !important;
379
+ color: v-bind(cssIconColor);
307
380
  }
308
381
 
309
382
  .clearBtn {
@@ -317,42 +390,50 @@
317
390
  position: absolute;
318
391
  left: 0;
319
392
  width: 100%;
320
- top: 55px;
393
+ top: calc(var(--ui3n-input-message-top) + 2px);
321
394
  font-style: italic;
322
395
  font-size: var(--font-10);
323
396
  font-weight: 400;
324
397
  line-height: 1.1;
398
+ }
325
399
 
326
- &.ui3nInputErrorMessage {
327
- color: var(--error-content-default);
328
- }
400
+ .ui3nInputErrorMessage {
401
+ color: var(--error-content-default);
402
+ }
329
403
 
330
- &.ui3nInputSuccessMessage {
331
- color: var(--success-content-default);
332
- }
404
+ .ui3nInputSuccessMessage {
405
+ color: var(--success-content-default);
333
406
  }
334
407
 
335
408
  .clearable {
336
409
  --ui3n-input-padding-right: var(--spacing-ml);
410
+
411
+ &.large {
412
+ --ui3n-input-padding-right: calc(var(--spacing-ml) + 4px);
413
+ }
337
414
  }
338
415
 
339
416
  .withIcon {
340
417
  --ui3n-input-padding-left: var(--spacing-ml);
418
+
419
+ &.large {
420
+ --ui3n-input-padding-left: calc(var(--spacing-ml) + 4px);
421
+ }
341
422
  }
342
423
 
343
424
  .error {
344
425
  .ui3nInputLabel {
345
- color: var(--error-content-default) !important;
426
+ color: var(--error-content-default);
346
427
  }
347
428
 
348
429
  .ui3nInputField {
349
- outline: 1px solid var(--error-content-default) !important;
430
+ outline: 1px solid var(--error-content-default);
350
431
  }
351
432
  }
352
433
 
353
434
  .success {
354
435
  .ui3nInputLabel {
355
- color: var(--success-content-default) !important;
436
+ color: var(--success-content-default);
356
437
  }
357
438
  }
358
439
 
@@ -369,16 +450,16 @@
369
450
 
370
451
  .withLabel {
371
452
  .ui3nInputIcon {
372
- top: calc((var(--ui3n-input-height) - 16px) / 2 + 20px);
453
+ top: calc((var(--ui3n-input-height) - var(--ui3n-input-icon-size)) / 2 + var(--ui3n-input-label-offset));
373
454
  }
374
455
 
375
456
  .ui3nInputSuccessIcon {
376
457
  bottom: auto;
377
- top: calc((var(--ui3n-input-height) - 16px) / 2 + 20px);
458
+ top: calc((var(--ui3n-input-height) - var(--ui3n-input-icon-size)) / 2 + var(--ui3n-input-label-offset));
378
459
  }
379
460
 
380
461
  .clearBtn {
381
- top: calc((var(--ui3n-input-height) - 24px) / 2 + 20px);
462
+ top: calc((var(--ui3n-input-height) - 24px) / 2 + var(--ui3n-input-label-offset));
382
463
  z-index: 1;
383
464
  }
384
465
  }