@milaboratories/uikit 2.0.13 → 2.1.0
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/CHANGELOG.md +6 -0
- package/dist/pl-uikit.js +470 -454
- package/dist/pl-uikit.umd.cjs +4 -4
- package/dist/src/components/PlTextField/PlTextField.vue.d.ts +17 -11
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/components/PlFileDialog/PlFileDialog.vue +3 -3
- package/src/components/PlTextField/PlTextField.vue +63 -27
- package/src/components/PlTextField/__tests__/TextField.spec.ts +15 -4
package/package.json
CHANGED
|
@@ -41,7 +41,7 @@ const props = withDefaults(
|
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
const defaultData = () => ({
|
|
44
|
-
dirPath: '',
|
|
44
|
+
dirPath: '' as string,
|
|
45
45
|
storageEntry: undefined as StorageEntry | undefined,
|
|
46
46
|
items: [] as FileDialogItem[],
|
|
47
47
|
error: '',
|
|
@@ -119,12 +119,12 @@ const query = (handle: StorageHandle, dirPath: string) => {
|
|
|
119
119
|
|
|
120
120
|
const load = () => {
|
|
121
121
|
const { storageHandle, dirPath, modelValue } = lookup.value;
|
|
122
|
-
if (storageHandle && modelValue) {
|
|
122
|
+
if (storageHandle && modelValue && dirPath) {
|
|
123
123
|
query(storageHandle, dirPath);
|
|
124
124
|
}
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
-
const updateDirPathDebounced = debounce((v: string
|
|
127
|
+
const updateDirPathDebounced = debounce((v: string) => {
|
|
128
128
|
if (v) {
|
|
129
129
|
data.dirPath = v;
|
|
130
130
|
}
|
|
@@ -5,32 +5,29 @@ export default {
|
|
|
5
5
|
};
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
|
-
<script lang="ts" setup generic="M
|
|
8
|
+
<script lang="ts" setup generic="M, E = string, C = E">
|
|
9
9
|
import './pl-text-field.scss';
|
|
10
|
-
import { computed, ref, useSlots } from 'vue';
|
|
10
|
+
import { computed, reactive, ref, useSlots } from 'vue';
|
|
11
11
|
import { PlTooltip } from '@/components/PlTooltip';
|
|
12
12
|
import DoubleContour from '@/utils/DoubleContour.vue';
|
|
13
13
|
import { useLabelNotch } from '@/utils/useLabelNotch';
|
|
14
14
|
import { useValidation } from '@/utils/useValidation';
|
|
15
15
|
import { PlIcon16 } from '../PlIcon16';
|
|
16
16
|
import { PlIcon24 } from '../PlIcon24';
|
|
17
|
+
import type { Equal } from '@milaboratories/helpers';
|
|
17
18
|
|
|
18
19
|
const slots = useSlots();
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
21
|
+
type Model = Equal<M, E | C> extends true ? M : never; // basically in === out
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The current value of the input field.
|
|
25
|
+
*/
|
|
26
|
+
const model = defineModel<Model>({
|
|
27
|
+
required: true,
|
|
28
|
+
});
|
|
28
29
|
|
|
29
30
|
const props = defineProps<{
|
|
30
|
-
/**
|
|
31
|
-
* The current value of the input field.
|
|
32
|
-
*/
|
|
33
|
-
modelValue: M;
|
|
34
31
|
/**
|
|
35
32
|
* The label to display above the input field.
|
|
36
33
|
*/
|
|
@@ -39,7 +36,12 @@ const props = defineProps<{
|
|
|
39
36
|
* If `true`, a clear icon will appear in the input field to clear the value (set it to empty string).
|
|
40
37
|
* Or you can pass a callback that returns a custom "empty" value (null | undefined | string)
|
|
41
38
|
*/
|
|
42
|
-
clearable?: boolean | (() =>
|
|
39
|
+
clearable?: boolean | (() => C);
|
|
40
|
+
/**
|
|
41
|
+
* An optional callback to parse and/or cast the value, the return type overrides the model type.
|
|
42
|
+
* The callback must throw an exception if the value is invalid
|
|
43
|
+
*/
|
|
44
|
+
parse?: (v: string) => E;
|
|
43
45
|
/**
|
|
44
46
|
* If `true`, the input field is marked as required.
|
|
45
47
|
*/
|
|
@@ -75,7 +77,7 @@ const props = defineProps<{
|
|
|
75
77
|
/**
|
|
76
78
|
* The string specifies whether the field should be a password or not, value could be "password" or undefined.
|
|
77
79
|
*/
|
|
78
|
-
type?: 'password';
|
|
80
|
+
type?: 'password' | 'number';
|
|
79
81
|
}>();
|
|
80
82
|
|
|
81
83
|
const rootRef = ref<HTMLInputElement | undefined>(undefined);
|
|
@@ -84,12 +86,32 @@ const inputRef = ref<HTMLInputElement | undefined>();
|
|
|
84
86
|
|
|
85
87
|
const showPassword = ref(false);
|
|
86
88
|
|
|
89
|
+
const data = reactive({
|
|
90
|
+
cached: undefined as { error: string; value: string } | undefined,
|
|
91
|
+
});
|
|
92
|
+
|
|
87
93
|
const valueRef = computed<string>({
|
|
88
94
|
get() {
|
|
89
|
-
|
|
95
|
+
if (data.cached) {
|
|
96
|
+
return data.cached.value;
|
|
97
|
+
}
|
|
98
|
+
return model.value === undefined || model.value === null ? '' : String(model.value);
|
|
90
99
|
},
|
|
91
|
-
set(
|
|
92
|
-
|
|
100
|
+
set(value) {
|
|
101
|
+
data.cached = undefined;
|
|
102
|
+
|
|
103
|
+
if (props.parse) {
|
|
104
|
+
try {
|
|
105
|
+
model.value = props.parse(value) as Model;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
data.cached = {
|
|
108
|
+
error: err instanceof Error ? err.message : String(err),
|
|
109
|
+
value,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
model.value = value as Model;
|
|
114
|
+
}
|
|
93
115
|
},
|
|
94
116
|
});
|
|
95
117
|
|
|
@@ -105,7 +127,8 @@ const passwordIcon = computed(() => (showPassword.value ? 'view-on' : 'view-off'
|
|
|
105
127
|
|
|
106
128
|
const clear = () => {
|
|
107
129
|
if (props.clearable) {
|
|
108
|
-
|
|
130
|
+
data.cached = undefined;
|
|
131
|
+
model.value = props.clearable === true ? ('' as Model) : (props.clearable() as Model);
|
|
109
132
|
}
|
|
110
133
|
};
|
|
111
134
|
|
|
@@ -113,10 +136,10 @@ const validationData = useValidation(valueRef, props.rules || []);
|
|
|
113
136
|
|
|
114
137
|
const isEmpty = computed(() => {
|
|
115
138
|
if (props.clearable) {
|
|
116
|
-
return props.clearable === true ?
|
|
139
|
+
return props.clearable === true ? model.value === '' : model.value === props.clearable();
|
|
117
140
|
}
|
|
118
141
|
|
|
119
|
-
return
|
|
142
|
+
return model.value === '';
|
|
120
143
|
});
|
|
121
144
|
|
|
122
145
|
const nonEmpty = computed(() => !isEmpty.value);
|
|
@@ -126,6 +149,9 @@ const displayErrors = computed(() => {
|
|
|
126
149
|
if (props.error) {
|
|
127
150
|
errors.push(props.error);
|
|
128
151
|
}
|
|
152
|
+
if (data.cached) {
|
|
153
|
+
errors.push(data.cached.error);
|
|
154
|
+
}
|
|
129
155
|
if (!validationData.value.isValid) {
|
|
130
156
|
errors.push(...validationData.value.errors);
|
|
131
157
|
}
|
|
@@ -136,11 +162,13 @@ const hasErrors = computed(() => displayErrors.value.length > 0);
|
|
|
136
162
|
|
|
137
163
|
const canShowClearable = computed(() => props.clearable && nonEmpty.value && props.type !== 'password');
|
|
138
164
|
|
|
139
|
-
|
|
165
|
+
const togglePasswordVisibility = () => (showPassword.value = !showPassword.value);
|
|
166
|
+
|
|
167
|
+
const onFocusOut = () => {
|
|
168
|
+
data.cached = undefined;
|
|
169
|
+
};
|
|
140
170
|
|
|
141
|
-
|
|
142
|
-
showPassword.value = !showPassword.value;
|
|
143
|
-
}
|
|
171
|
+
useLabelNotch(rootRef);
|
|
144
172
|
</script>
|
|
145
173
|
|
|
146
174
|
<template>
|
|
@@ -167,7 +195,15 @@ function togglePasswordVisibility() {
|
|
|
167
195
|
<div v-if="prefix" class="pl-text-field__prefix">
|
|
168
196
|
{{ prefix }}
|
|
169
197
|
</div>
|
|
170
|
-
<input
|
|
198
|
+
<input
|
|
199
|
+
ref="inputRef"
|
|
200
|
+
v-model="valueRef"
|
|
201
|
+
:disabled="disabled"
|
|
202
|
+
:placeholder="placeholder || '...'"
|
|
203
|
+
:type="fieldType"
|
|
204
|
+
spellcheck="false"
|
|
205
|
+
@focusout="onFocusOut"
|
|
206
|
+
/>
|
|
171
207
|
<div class="pl-text-field__append">
|
|
172
208
|
<PlIcon16 v-if="canShowClearable" name="delete-clear" @click="clear" />
|
|
173
209
|
<PlIcon24 v-if="type === 'password'" :name="passwordIcon" style="cursor: pointer" @click="togglePasswordVisibility" />
|
|
@@ -14,8 +14,8 @@ describe('TextField', () => {
|
|
|
14
14
|
expect(wrapper.text()).toContain('TextField Label');
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
it('modelValue', async () => {
|
|
18
|
-
const wrapper = mount(PlTextField
|
|
17
|
+
it('modelValue:string', async () => {
|
|
18
|
+
const wrapper = mount(PlTextField<string>, {
|
|
19
19
|
props: {
|
|
20
20
|
modelValue: 'initialText',
|
|
21
21
|
'onUpdate:modelValue': (e: string) => wrapper.setProps({ modelValue: e }),
|
|
@@ -23,8 +23,19 @@ describe('TextField', () => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
await wrapper.find('input').setValue('test');
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
expect(wrapper.props('modelValue')).toBe('test');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('modelValue:string?', async () => {
|
|
30
|
+
const wrapper = mount(PlTextField, {
|
|
31
|
+
props: {
|
|
32
|
+
modelValue: 'initialText' as string | undefined,
|
|
33
|
+
clearable: () => undefined,
|
|
34
|
+
'onUpdate:modelValue': (e: unknown) => wrapper.setProps({ modelValue: e }),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
await wrapper.find('input').setValue('test');
|
|
28
39
|
expect(wrapper.props('modelValue')).toBe('test');
|
|
29
40
|
});
|
|
30
41
|
});
|