@weni/unnnic-system 3.32.0 → 3.32.1

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.
@@ -29,7 +29,7 @@
29
29
  :disabled="disabled"
30
30
  :readonly="readonly"
31
31
  :showClear="showClear"
32
- @clear="$emit('clear')"
32
+ @clear="emit('clear')"
33
33
  />
34
34
 
35
35
  <template
@@ -41,145 +41,97 @@
41
41
  </UnnnicFormElement>
42
42
  </template>
43
43
 
44
- <script>
44
+ <script setup lang="ts">
45
+ import { computed, ref, watch } from 'vue';
45
46
  import { fullySanitize } from '../../utils/sanitize';
46
47
  import TextInput from './TextInput.vue';
47
48
  import UnnnicFormElement from '../FormElement/FormElement.vue';
49
+ import { type TooltipProps } from '../ToolTip/ToolTip.vue';
50
+ import type { InputSize, InputVariant } from './BaseInput.vue';
48
51
 
49
- export default {
52
+ defineOptions({
50
53
  name: 'UnnnicInput',
51
- components: {
52
- TextInput,
53
- UnnnicFormElement,
54
- },
55
- props: {
56
- placeholder: {
57
- type: String,
58
- default: '',
59
- },
60
- type: {
61
- type: String,
62
- default: 'normal',
63
- validator(value) {
64
- return ['normal', 'error'].indexOf(value) !== -1;
65
- },
66
- },
67
- modelValue: {
68
- type: String,
69
- default: '',
70
- },
71
- nativeType: {
72
- type: String,
73
- default: 'text',
74
- },
75
- message: {
76
- type: String,
77
- default: '',
78
- },
79
- errors: {
80
- type: [String, Array],
81
- default: '',
82
- },
83
- label: {
84
- type: String,
85
- default: '',
86
- },
87
- iconLeft: {
88
- type: String,
89
- default: '',
90
- },
91
- iconRight: {
92
- type: String,
93
- default: '',
94
- },
95
- allowTogglePassword: {
96
- type: Boolean,
97
- default: false,
98
- },
99
- iconLeftClickable: {
100
- type: Boolean,
101
- default: false,
102
- },
103
- iconRightClickable: {
104
- type: Boolean,
105
- default: false,
106
- },
107
- hasCloudyColor: {
108
- type: Boolean,
109
- default: false,
110
- },
111
- size: {
112
- type: String,
113
- default: 'md',
114
- },
115
- mask: {
116
- type: [String, Array],
117
- default: '',
118
- },
119
- tooltip: {
120
- type: Object,
121
- default: undefined,
122
- },
123
- maxlength: {
124
- type: [Number, null],
125
- default: null,
126
- },
127
- showMaxlengthCounter: {
128
- type: Boolean,
129
- default: false,
130
- },
131
- disabled: {
132
- type: Boolean,
133
- default: false,
134
- },
135
- readonly: {
136
- type: Boolean,
137
- default: false,
138
- },
139
- useFocusProp: {
140
- type: Boolean,
141
- default: false,
142
- },
143
- focus: {
144
- type: Boolean,
145
- default: false,
146
- },
147
- showClear: {
148
- type: Boolean,
149
- default: false,
150
- },
151
- },
152
- emits: ['update:modelValue', 'clear'],
54
+ });
153
55
 
154
- data() {
155
- return {
156
- val: this.modelValue,
157
- };
158
- },
159
- computed: {
160
- computedError() {
161
- if (Array.isArray(this.errors)) {
162
- return this.errors.join(', ') || this.type === 'error';
163
- }
56
+ export interface InputProps {
57
+ placeholder?: string;
58
+ type?: InputVariant;
59
+ modelValue?: string;
60
+ nativeType?: string;
61
+ message?: string;
62
+ errors?: string | string[];
63
+ label?: string;
64
+ iconLeft?: string;
65
+ iconRight?: string;
66
+ allowTogglePassword?: boolean;
67
+ iconLeftClickable?: boolean;
68
+ iconRightClickable?: boolean;
69
+ hasCloudyColor?: boolean;
70
+ size?: InputSize | string;
71
+ mask?: string | string[];
72
+ tooltip?: TooltipProps;
73
+ maxlength?: number | null;
74
+ showMaxlengthCounter?: boolean;
75
+ disabled?: boolean;
76
+ readonly?: boolean;
77
+ useFocusProp?: boolean;
78
+ focus?: boolean;
79
+ showClear?: boolean;
80
+ }
164
81
 
165
- return this.errors || this.type === 'error';
166
- },
167
- },
168
- watch: {
169
- val() {
170
- this.$emit('update:modelValue', fullySanitize(this.val));
171
- },
172
- modelValue() {
173
- this.val = this.modelValue;
174
- },
175
- },
176
- mounted() {
177
- this.val = this.modelValue;
178
- },
179
- methods: {
180
- fullySanitize,
82
+ const props = withDefaults(defineProps<InputProps>(), {
83
+ placeholder: '',
84
+ type: 'normal',
85
+ modelValue: '',
86
+ nativeType: 'text',
87
+ message: '',
88
+ errors: '',
89
+ label: '',
90
+ iconLeft: '',
91
+ iconRight: '',
92
+ allowTogglePassword: false,
93
+ iconLeftClickable: false,
94
+ iconRightClickable: false,
95
+ hasCloudyColor: false,
96
+ size: 'md',
97
+ mask: '',
98
+ tooltip: undefined,
99
+ maxlength: null,
100
+ showMaxlengthCounter: false,
101
+ disabled: false,
102
+ readonly: false,
103
+ useFocusProp: false,
104
+ focus: false,
105
+ showClear: false,
106
+ });
107
+
108
+ const emit = defineEmits<{
109
+ 'update:modelValue': [value: string];
110
+ clear: [];
111
+ }>();
112
+
113
+ const val = ref(props.modelValue);
114
+
115
+ const computedError = computed(() => {
116
+ if (Array.isArray(props.errors)) {
117
+ return props.errors.join(', ') || props.type === 'error';
118
+ }
119
+
120
+ return props.errors || props.type === 'error';
121
+ });
122
+
123
+ watch(val, () => {
124
+ emit('update:modelValue', fullySanitize(val.value));
125
+ });
126
+
127
+ watch(
128
+ () => props.modelValue,
129
+ () => {
130
+ val.value = props.modelValue;
181
131
  },
182
- };
132
+ );
133
+
134
+ val.value = props.modelValue;
183
135
  </script>
184
136
 
185
137
  <style lang="scss" scoped>
@@ -54,144 +54,127 @@
54
54
  </div>
55
55
  </template>
56
56
 
57
- <script>
58
- import BaseInput from './BaseInput.vue';
57
+ <script setup lang="ts">
58
+ import { computed, ref, useAttrs, useTemplateRef } from 'vue';
59
+ import BaseInput, { type InputSize, type InputVariant } from './BaseInput.vue';
59
60
  import UnnnicIcon from '../Icon.vue';
60
61
 
61
- export default {
62
- components: {
63
- BaseInput,
64
- UnnnicIcon,
65
- },
66
- props: {
67
- placeholder: {
68
- type: String,
69
- default: null,
70
- },
71
- type: {
72
- type: String,
73
- default: 'normal',
74
- validator(value) {
75
- return ['normal', 'error'].indexOf(value) !== -1;
76
- },
77
- },
78
- modelValue: {
79
- type: String,
80
- default: '',
81
- },
82
- nativeType: {
83
- type: String,
84
- default: '',
85
- },
86
- iconLeft: {
87
- type: String,
88
- default: null,
89
- },
90
- iconRight: {
91
- type: String,
92
- default: null,
93
- },
94
- iconLeftClickable: {
95
- type: Boolean,
96
- default: null,
97
- },
98
- iconRightClickable: {
99
- type: Boolean,
100
- default: null,
101
- },
102
- allowTogglePassword: {
103
- type: Boolean,
104
- default: null,
105
- },
106
- size: {
107
- type: String,
108
- default: 'md',
109
- },
110
- maxlength: {
111
- type: Number,
112
- default: null,
113
- },
114
- disabled: {
115
- type: Boolean,
116
- default: false,
117
- },
118
- readonly: {
119
- type: Boolean,
120
- default: false,
121
- },
122
- useFocusProp: {
123
- type: Boolean,
124
- default: false,
125
- },
126
- focus: {
127
- type: Boolean,
128
- default: false,
129
- },
130
- showClear: {
131
- type: Boolean,
132
- default: false,
133
- },
134
- },
135
- emits: ['icon-left-click', 'icon-right-click', 'clear'],
136
- data() {
137
- return {
138
- isFocused: false,
139
- showPassword: false,
140
- };
141
- },
142
- computed: {
143
- isDisabled() {
144
- return this.$attrs.disabled || this.disabled;
145
- },
146
-
147
- iconRightSvg() {
148
- if (this.allowTogglePassword) {
149
- return this.showPassword ? 'view-off-1' : 'view-1-1';
150
- }
151
-
152
- return this.iconRight;
153
- },
154
-
155
- iconScheme() {
156
- if (this.isDisabled) {
157
- return 'fg-muted';
158
- }
159
- return 'fg-base';
160
- },
161
-
162
- attributes() {
163
- return { ...this.$attrs, ...this.$attrs['v-bind'], ...this.$props };
164
- },
165
- },
166
-
167
- methods: {
168
- focusInput() {
169
- this.$refs['base-input'].$el.focus();
170
- },
171
-
172
- onFocus() {
173
- this.isFocused = true;
174
- },
175
-
176
- onBlur() {
177
- this.isFocused = false;
178
- },
179
-
180
- onIconLeftClick() {
181
- if (this.iconLeftClickable) this.$emit('icon-left-click');
182
- },
183
-
184
- onClearClick() {
185
- this.$emit('clear');
186
- },
187
-
188
- onIconRightClick() {
189
- if (this.attributes.disabled) return;
190
- if (this.allowTogglePassword) this.showPassword = !this.showPassword;
191
- else if (this.iconRightClickable) this.$emit('icon-right-click');
192
- },
193
- },
194
- };
62
+ defineOptions({
63
+ name: 'UnnnicTextInput',
64
+ });
65
+
66
+ export interface TextInputProps {
67
+ placeholder?: string | null;
68
+ type?: InputVariant;
69
+ modelValue?: string;
70
+ nativeType?: string;
71
+ iconLeft?: string | null;
72
+ iconRight?: string | null;
73
+ iconLeftClickable?: boolean | null;
74
+ iconRightClickable?: boolean | null;
75
+ allowTogglePassword?: boolean | null;
76
+ size?: InputSize | string;
77
+ maxlength?: number | null;
78
+ disabled?: boolean;
79
+ readonly?: boolean;
80
+ useFocusProp?: boolean;
81
+ focus?: boolean;
82
+ showClear?: boolean;
83
+ }
84
+
85
+ const props = withDefaults(defineProps<TextInputProps>(), {
86
+ placeholder: null,
87
+ type: 'normal',
88
+ modelValue: '',
89
+ nativeType: '',
90
+ iconLeft: null,
91
+ iconRight: null,
92
+ iconLeftClickable: null,
93
+ iconRightClickable: null,
94
+ allowTogglePassword: null,
95
+ size: 'md',
96
+ maxlength: null,
97
+ disabled: false,
98
+ readonly: false,
99
+ useFocusProp: false,
100
+ focus: false,
101
+ showClear: false,
102
+ });
103
+
104
+ const emit = defineEmits<{
105
+ 'icon-left-click': [];
106
+ 'icon-right-click': [];
107
+ clear: [];
108
+ }>();
109
+
110
+ const attrs = useAttrs();
111
+ const baseInput = useTemplateRef<{ $el: HTMLInputElement }>('base-input');
112
+ const isFocused = ref(false);
113
+ const showPassword = ref(false);
114
+
115
+ const isDisabled = computed(() => {
116
+ return Boolean(attrs.disabled || props.disabled);
117
+ });
118
+
119
+ const iconRightSvg = computed(() => {
120
+ if (props.allowTogglePassword) {
121
+ return showPassword.value ? 'view-off-1' : 'view-1-1';
122
+ }
123
+
124
+ return props.iconRight;
125
+ });
126
+
127
+ const iconScheme = computed(() => {
128
+ if (isDisabled.value) {
129
+ return 'fg-muted';
130
+ }
131
+ return 'fg-base';
132
+ });
133
+
134
+ const attributes = computed(() => {
135
+ return {
136
+ ...attrs,
137
+ ...(attrs['v-bind'] as Record<string, unknown> | undefined),
138
+ ...props,
139
+ };
140
+ });
141
+
142
+ function focusInput() {
143
+ baseInput.value?.$el.focus();
144
+ }
145
+
146
+ function onFocus() {
147
+ isFocused.value = true;
148
+ }
149
+
150
+ function onBlur() {
151
+ isFocused.value = false;
152
+ }
153
+
154
+ function onIconLeftClick() {
155
+ if (props.iconLeftClickable) emit('icon-left-click');
156
+ }
157
+
158
+ function onClearClick() {
159
+ emit('clear');
160
+ }
161
+
162
+ function onIconRightClick() {
163
+ if (attributes.value.disabled) return;
164
+ if (props.allowTogglePassword) showPassword.value = !showPassword.value;
165
+ else if (props.iconRightClickable) emit('icon-right-click');
166
+ }
167
+
168
+ defineExpose({
169
+ focusInput,
170
+ onFocus,
171
+ onBlur,
172
+ showPassword,
173
+ isFocused,
174
+ iconRightSvg,
175
+ iconScheme,
176
+ attributes,
177
+ });
195
178
  </script>
196
179
 
197
180
  <style lang="scss" scoped>
@@ -67,7 +67,8 @@ describe('TextInput.vue', () => {
67
67
  await wrapper.setProps({ allowTogglePassword: true });
68
68
  expect(wrapper.vm.iconRightSvg).toBe('view-1-1');
69
69
 
70
- await wrapper.setData({ showPassword: true });
70
+ wrapper.vm.showPassword = true;
71
+ await wrapper.vm.$nextTick();
71
72
  expect(wrapper.vm.iconRightSvg).toBe('view-off-1');
72
73
  });
73
74
 
@@ -76,14 +77,13 @@ describe('TextInput.vue', () => {
76
77
  expect(wrapper.vm.iconScheme).toBe('fg-base');
77
78
 
78
79
  await wrapper.setProps({ type: 'normal' });
79
- await wrapper.setData({ isDisabled: true });
80
80
  expect(wrapper.vm.iconScheme).toBe('fg-base');
81
81
 
82
- await wrapper.setData({ isDisabled: false });
83
82
  await wrapper.setProps({ modelValue: 'text' });
84
83
  expect(wrapper.vm.iconScheme).toBe('fg-base');
85
84
 
86
- await wrapper.setData({ isFocused: true });
85
+ wrapper.vm.isFocused = true;
86
+ await wrapper.vm.$nextTick();
87
87
  expect(wrapper.vm.iconScheme).toBe('fg-base');
88
88
 
89
89
  await wrapper.setProps({ disabled: true });
@@ -106,9 +106,7 @@ describe('TextInput.vue', () => {
106
106
 
107
107
  await wrapper.find('.icon-right').trigger('click');
108
108
  expect(wrapper.vm.showPassword).toBe(true);
109
- expect(
110
- wrapper.findComponent({ name: 'BaseInput' }).attributes('type'),
111
- ).toBe('text');
109
+ expect(wrapper.findComponent(BaseInput).props('nativeType')).toBe('text');
112
110
 
113
111
  await wrapper.find('.icon-right').trigger('click');
114
112
  expect(wrapper.vm.showPassword).toBe(false);
@@ -145,7 +143,7 @@ describe('TextInput.vue', () => {
145
143
  });
146
144
 
147
145
  test('onBlur method sets isFocused to false', async () => {
148
- wrapper.setData({ isFocused: true });
146
+ wrapper.vm.isFocused = true;
149
147
  await wrapper.vm.$nextTick();
150
148
  expect(wrapper.vm.isFocused).toBe(true);
151
149
 
@@ -191,16 +189,14 @@ describe('TextInput.vue', () => {
191
189
  });
192
190
 
193
191
  test('onIconRightClick method does nothing if the input is disabled', async () => {
194
- await wrapper.setData({ attributes: { disabled: true } });
195
-
196
- const iconRightClickSpy = vi.spyOn(wrapper.vm, 'onIconRightClick');
192
+ await wrapper.setProps({
193
+ disabled: true,
194
+ allowTogglePassword: true,
195
+ });
197
196
 
198
197
  wrapper.vm.onIconRightClick();
199
198
 
200
- expect(iconRightClickSpy).toHaveBeenCalled();
201
199
  expect(wrapper.vm.showPassword).toBe(false);
202
-
203
- iconRightClickSpy.mockRestore();
204
200
  });
205
201
 
206
202
  test('matches the snapshot', () => {