@ouestfrance/sipa-bms-ui 8.5.4 → 8.7.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/dist/components/form/BmsAutocomplete.vue.d.ts +2 -0
- package/dist/components/form/BmsInputBooleanCheckbox.vue.d.ts +1 -1
- package/dist/components/form/BmsInputCheckboxGroup.vue.d.ts +2 -2
- package/dist/components/form/BmsInputCode.vue.d.ts +2 -2
- package/dist/components/form/BmsInputNumber.vue.d.ts +2 -2
- package/dist/components/form/BmsInputRadio.vue.d.ts +2 -2
- package/dist/components/form/BmsInputText.vue.d.ts +24 -22
- package/dist/components/form/BmsMultiSelect.vue.d.ts +3 -4
- package/dist/components/form/BmsSearch.vue.d.ts +28 -24
- package/dist/components/form/BmsSelect.vue.d.ts +7 -17
- package/dist/components/form/RawAutocomplete.vue.d.ts +17 -21
- package/dist/components/form/RawInputText.vue.d.ts +9 -9
- package/dist/components/form/RawSelect.vue.d.ts +30 -0
- package/dist/components/navigation/UiTenantSwitcher.vue.d.ts +28 -24
- package/dist/components/table/BmsServerTable.vue.d.ts +18 -0
- package/dist/components/table/BmsTable.vue.d.ts +18 -1
- package/dist/components/table/BmsTableFilters.vue.d.ts +47 -25
- package/dist/components/table/UiBmsTableCell.vue.d.ts +7 -0
- package/dist/composables/search.composable.d.ts +1 -0
- package/dist/models/table.model.d.ts +6 -0
- package/dist/plugins/field/FieldDatalist.vue.d.ts +2 -0
- package/dist/plugins/field/field-component.model.d.ts +2 -2
- package/dist/sipa-bms-ui.css +173 -126
- package/dist/sipa-bms-ui.es.js +4875 -4484
- package/dist/sipa-bms-ui.es.js.map +1 -1
- package/dist/sipa-bms-ui.umd.js +4882 -4490
- package/dist/sipa-bms-ui.umd.js.map +1 -1
- package/package.json +11 -11
- package/src/components/form/BmsAutocomplete.vue +3 -0
- package/src/components/form/BmsInputNumber.spec.ts +26 -0
- package/src/components/form/BmsInputNumber.stories.js +20 -3
- package/src/components/form/BmsInputNumber.vue +36 -4
- package/src/components/form/BmsInputRadio.vue +1 -1
- package/src/components/form/BmsInputText.spec.ts +25 -0
- package/src/components/form/BmsInputText.stories.js +28 -3
- package/src/components/form/BmsInputText.vue +73 -12
- package/src/components/form/BmsMultiSelect.vue +67 -30
- package/src/components/form/BmsSelect.vue +60 -57
- package/src/components/form/RawAutocomplete.spec.ts +0 -8
- package/src/components/form/RawAutocomplete.vue +42 -24
- package/src/components/form/RawInputText.vue +14 -21
- package/src/components/form/RawSelect.vue +111 -0
- package/src/components/table/BmsServerTable.vue +18 -3
- package/src/components/table/BmsTable.vue +15 -2
- package/src/components/table/BmsTableFilters.vue +19 -7
- package/src/components/table/UiBmsTable.stories.js +37 -0
- package/src/components/table/UiBmsTableCell.vue +25 -0
- package/src/components/table/UiBmsTableRow.stories.js +30 -0
- package/src/components/table/UiBmsTableRow.vue +3 -2
- package/src/components/utils/BmsRelativeTime.vue +1 -1
- package/src/composables/search.composable.spec.ts +75 -0
- package/src/composables/search.composable.ts +54 -11
- package/src/models/table.model.ts +7 -0
- package/src/plugins/field/FieldComponent.vue +6 -4
- package/src/plugins/field/FieldDatalist.stories.js +0 -9
- package/src/plugins/field/FieldDatalist.vue +16 -13
- package/src/plugins/field/field-component.model.ts +2 -2
- package/src/showroom/pages/autocomplete.vue +22 -1
- package/src/showroom/pages/server-table.vue +53 -22
- package/src/showroom/pages/table.vue +46 -21
- package/dist/plugins/field/FieldDatalist.spec.d.ts +0 -1
- package/src/plugins/field/FieldDatalist.spec.ts +0 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouestfrance/sipa-bms-ui",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.7.0",
|
|
4
4
|
"author": "Ouest-France BMS",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"scripts": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@chromatic-com/storybook": "^4.0.0",
|
|
33
|
-
"@codemirror/lang-html": "6.4.
|
|
33
|
+
"@codemirror/lang-html": "6.4.10",
|
|
34
34
|
"@codemirror/lang-json": "6.0.2",
|
|
35
35
|
"@commitlint/cli": "19.8.1",
|
|
36
36
|
"@commitlint/config-conventional": "19.8.1",
|
|
@@ -40,39 +40,39 @@
|
|
|
40
40
|
"@storybook/addon-links": "9.1.5",
|
|
41
41
|
"@storybook/vue3-vite": "9.1.5",
|
|
42
42
|
"@types/lodash": "4.17.20",
|
|
43
|
-
"@types/uuid": "
|
|
43
|
+
"@types/uuid": "11.0.0",
|
|
44
44
|
"@vitejs/plugin-vue": "6.0.1",
|
|
45
45
|
"@vue/test-utils": "2.4.6",
|
|
46
46
|
"@vueuse/core": "13.9.0",
|
|
47
47
|
"@vueuse/motion": "^3.0.0",
|
|
48
|
-
"axios": "1.
|
|
48
|
+
"axios": "1.12.2",
|
|
49
49
|
"blob-util": "^2.0.2",
|
|
50
50
|
"chromatic": "13.1.4",
|
|
51
51
|
"codemirror": "6.0.2",
|
|
52
52
|
"cors": "^2.8.5",
|
|
53
53
|
"cross-env": "^10.0.0",
|
|
54
54
|
"cy2": "^4.0.0",
|
|
55
|
-
"cypress": "15.
|
|
55
|
+
"cypress": "15.2.0",
|
|
56
56
|
"express": "^5.0.0",
|
|
57
57
|
"husky": "9.1.7",
|
|
58
|
-
"jsdom": "
|
|
58
|
+
"jsdom": "27.0.0",
|
|
59
59
|
"keycloak-js": "26.1.2",
|
|
60
60
|
"lint-staged": "16.1.6",
|
|
61
61
|
"lodash": "4.17.21",
|
|
62
|
-
"lucide-vue-next": "0.
|
|
62
|
+
"lucide-vue-next": "0.544.0",
|
|
63
63
|
"msw-storybook-addon": "^2.0.3",
|
|
64
64
|
"normalize.css": "8.0.1",
|
|
65
65
|
"path": "0.12.7",
|
|
66
66
|
"prettier": "3.6.2",
|
|
67
67
|
"sass": "1.92.1",
|
|
68
|
-
"semantic-release": "24.2.
|
|
68
|
+
"semantic-release": "24.2.8",
|
|
69
69
|
"start-server-and-test": "2.1.0",
|
|
70
70
|
"storybook": "9.1.5",
|
|
71
71
|
"storybook-addon-pseudo-states": "9.1.5",
|
|
72
72
|
"storybook-vue3-router": "^6.0.2",
|
|
73
73
|
"typescript": "5.2.2",
|
|
74
|
-
"uuid": "
|
|
75
|
-
"vite": "7.1.
|
|
74
|
+
"uuid": "13.0.0",
|
|
75
|
+
"vite": "7.1.5",
|
|
76
76
|
"vite-plugin-dts": "^4.1.0",
|
|
77
77
|
"vite-plugin-mkcert": "1.17.8",
|
|
78
78
|
"vite-plugin-pages": "0.33.1",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"vue-codemirror": "6.1.1",
|
|
83
83
|
"vue-loader": "17.4.2",
|
|
84
84
|
"vue-router": "4.5.1",
|
|
85
|
-
"vue-tsc": "3.0.
|
|
85
|
+
"vue-tsc": "3.0.7"
|
|
86
86
|
},
|
|
87
87
|
"files": [
|
|
88
88
|
"dist",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
:small="small"
|
|
16
16
|
@select="(option: any) => emits('select', option)"
|
|
17
17
|
@add-new-option="(newOption: string) => emits('addNewOption', newOption)"
|
|
18
|
+
@input="(e: InputEvent) => emits('input', e)"
|
|
18
19
|
>
|
|
19
20
|
<template #icon-start v-if="currentOptionIcon">
|
|
20
21
|
<span
|
|
@@ -60,9 +61,11 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
60
61
|
const modelValue = defineModel<string>('modelValue', {
|
|
61
62
|
default: null,
|
|
62
63
|
});
|
|
64
|
+
|
|
63
65
|
const emits = defineEmits<{
|
|
64
66
|
addNewOption: [newOption: string];
|
|
65
67
|
select: [option: InputOption];
|
|
68
|
+
input: [e: InputEvent];
|
|
66
69
|
}>();
|
|
67
70
|
|
|
68
71
|
const currentOptionIcon = computed(() => {
|
|
@@ -37,4 +37,30 @@ describe('BmsInputNumber', () => {
|
|
|
37
37
|
await nextTick();
|
|
38
38
|
expect(wrapper.emitted()['update:modelValue']).toBeUndefined();
|
|
39
39
|
});
|
|
40
|
+
|
|
41
|
+
it('should try limit of the input when value change', async () => {
|
|
42
|
+
const { wrapper, inputElement } = factory({
|
|
43
|
+
modelValue: 8,
|
|
44
|
+
disabled: true,
|
|
45
|
+
max: 10,
|
|
46
|
+
min: 5,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect((wrapper.vm as any).internalErrors.length).toBe(0);
|
|
50
|
+
await wrapper.setProps({ modelValue: 2 });
|
|
51
|
+
await nextTick();
|
|
52
|
+
|
|
53
|
+
expect((wrapper.vm as any).internalErrors.length).toBe(1);
|
|
54
|
+
expect((wrapper.vm as any).internalErrors[0]).toBe(
|
|
55
|
+
'Valeur inférieure au minimum autorisé (min : 5)',
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await wrapper.setProps({ modelValue: 15 });
|
|
59
|
+
await nextTick();
|
|
60
|
+
|
|
61
|
+
expect((wrapper.vm as any).internalErrors.length).toBe(1);
|
|
62
|
+
expect((wrapper.vm as any).internalErrors[0]).toBe(
|
|
63
|
+
'Valeur supérieure au maximum autorisé (max : 10)',
|
|
64
|
+
);
|
|
65
|
+
});
|
|
40
66
|
});
|
|
@@ -3,6 +3,7 @@ import { within } from 'storybook/test';
|
|
|
3
3
|
import { BookOpen, CloudLightning } from 'lucide-vue-next';
|
|
4
4
|
|
|
5
5
|
import template from '@/documentation/template_field_dependency.mdx';
|
|
6
|
+
import { ref } from 'vue';
|
|
6
7
|
|
|
7
8
|
export default {
|
|
8
9
|
parameters: {
|
|
@@ -16,7 +17,9 @@ export default {
|
|
|
16
17
|
|
|
17
18
|
const Template = (args) => ({
|
|
18
19
|
setup() {
|
|
19
|
-
|
|
20
|
+
const modelValue = ref(args.modelValue);
|
|
21
|
+
const modelValueSmall = ref(args.modelValueSmall);
|
|
22
|
+
return { args, modelValue, modelValueSmall };
|
|
20
23
|
},
|
|
21
24
|
components: {
|
|
22
25
|
BmsInputNumber,
|
|
@@ -24,12 +27,12 @@ const Template = (args) => ({
|
|
|
24
27
|
CloudLightning,
|
|
25
28
|
},
|
|
26
29
|
template: `
|
|
27
|
-
<BmsInputNumber v-bind="args">
|
|
30
|
+
<BmsInputNumber v-bind="args" v-model="modelValue">
|
|
28
31
|
<template v-if="args.iconStart" #icon-start><BookOpen/></template>
|
|
29
32
|
<template v-if="args.iconEnd" #icon-end><CloudLightning/></template>
|
|
30
33
|
</BmsInputNumber>
|
|
31
34
|
<br/>
|
|
32
|
-
<BmsInputNumber v-bind="{...args, small:true}">
|
|
35
|
+
<BmsInputNumber v-bind="{...args, small:true}" v-model="modelValueSmall">
|
|
33
36
|
<template v-if="args.iconStart" #icon-start><BookOpen/></template>
|
|
34
37
|
<template v-if="args.iconEnd" #icon-end><CloudLightning/></template>
|
|
35
38
|
</BmsInputNumber>
|
|
@@ -47,12 +50,14 @@ export const WithValue = Template.bind({});
|
|
|
47
50
|
WithValue.args = {
|
|
48
51
|
label: 'My label',
|
|
49
52
|
modelValue: 10,
|
|
53
|
+
modelValueSmall: 10,
|
|
50
54
|
};
|
|
51
55
|
|
|
52
56
|
export const Disabled = Template.bind({});
|
|
53
57
|
Disabled.args = {
|
|
54
58
|
label: 'My label',
|
|
55
59
|
modelValue: 10,
|
|
60
|
+
modelValueSmall: 10,
|
|
56
61
|
disabled: true,
|
|
57
62
|
};
|
|
58
63
|
|
|
@@ -60,14 +65,26 @@ export const WithIcons = Template.bind({});
|
|
|
60
65
|
WithIcons.args = {
|
|
61
66
|
label: 'My label',
|
|
62
67
|
modelValue: 10,
|
|
68
|
+
modelValueSmall: 10,
|
|
63
69
|
iconStart: true,
|
|
64
70
|
iconEnd: true,
|
|
65
71
|
};
|
|
66
72
|
|
|
73
|
+
export const WithLimitExceeded = Template.bind({});
|
|
74
|
+
WithLimitExceeded.args = {
|
|
75
|
+
label: 'My label',
|
|
76
|
+
modelValue: 10,
|
|
77
|
+
modelValueSmall: 2,
|
|
78
|
+
min: 5,
|
|
79
|
+
max: 8,
|
|
80
|
+
errors: [{ label: 'Error 1' }],
|
|
81
|
+
};
|
|
82
|
+
|
|
67
83
|
export const WithErrors = Template.bind({});
|
|
68
84
|
WithErrors.args = {
|
|
69
85
|
label: 'My label',
|
|
70
86
|
modelValue: 10,
|
|
87
|
+
modelValueSmall: 10,
|
|
71
88
|
errors: ['Error 1', 'Error 2'],
|
|
72
89
|
};
|
|
73
90
|
|
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
:helperText="helperText"
|
|
9
9
|
:placeholder="placeholder"
|
|
10
10
|
:captions="captions"
|
|
11
|
-
:errors="
|
|
11
|
+
:errors="computedErrors"
|
|
12
12
|
:inputType="InputType.NUMBER"
|
|
13
|
+
:min="min"
|
|
14
|
+
:max="max"
|
|
13
15
|
@input="onInput"
|
|
14
16
|
/>
|
|
15
17
|
</template>
|
|
16
18
|
|
|
17
19
|
<script lang="ts" setup>
|
|
18
|
-
import { Ref, computed, ref } from 'vue';
|
|
20
|
+
import { Ref, computed, ref, watch, onMounted } from 'vue';
|
|
19
21
|
import { Caption } from '@/models/caption.model';
|
|
20
22
|
import BmsInputText from '@/components/form/BmsInputText.vue';
|
|
21
23
|
import { InputType } from '@/models';
|
|
@@ -28,8 +30,8 @@ export interface Props {
|
|
|
28
30
|
disabled?: boolean;
|
|
29
31
|
helperText?: string;
|
|
30
32
|
placeholder?: string;
|
|
31
|
-
captions?: string
|
|
32
|
-
errors?: string
|
|
33
|
+
captions?: (string | Caption)[];
|
|
34
|
+
errors?: (string | Caption)[];
|
|
33
35
|
max?: number;
|
|
34
36
|
min?: number;
|
|
35
37
|
}
|
|
@@ -42,6 +44,17 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
42
44
|
|
|
43
45
|
const input: Ref<HTMLElement | null> = ref(null);
|
|
44
46
|
|
|
47
|
+
const internalErrors: Ref<(string | Caption)[]> = ref([]);
|
|
48
|
+
const computedErrors: Ref<(string | Caption)[]> = computed(() => {
|
|
49
|
+
return props.errors
|
|
50
|
+
? internalErrors.value.concat(props.errors)
|
|
51
|
+
: internalErrors.value;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
onMounted(() => {
|
|
55
|
+
checkLimit();
|
|
56
|
+
});
|
|
57
|
+
|
|
45
58
|
const $emits = defineEmits<{
|
|
46
59
|
(e: 'update:modelValue', value: number): void;
|
|
47
60
|
}>();
|
|
@@ -55,6 +68,20 @@ const classes = computed(() => {
|
|
|
55
68
|
return { 'is-error': props.errors?.length, 'is-disabled': props.disabled };
|
|
56
69
|
});
|
|
57
70
|
|
|
71
|
+
const checkLimit = () => {
|
|
72
|
+
internalErrors.value = [];
|
|
73
|
+
if (props.min !== undefined && props.modelValue < props.min) {
|
|
74
|
+
internalErrors.value = [
|
|
75
|
+
'Valeur inférieure au minimum autorisé (min : ' + props.min + ')',
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
if (props.max !== undefined && props.modelValue > props.max) {
|
|
79
|
+
internalErrors.value = [
|
|
80
|
+
'Valeur supérieure au maximum autorisé (max : ' + props.max + ')',
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
58
85
|
const setFocus = () => {
|
|
59
86
|
if (input.value) {
|
|
60
87
|
input.value.focus();
|
|
@@ -64,4 +91,9 @@ const setFocus = () => {
|
|
|
64
91
|
defineExpose({
|
|
65
92
|
setFocus,
|
|
66
93
|
});
|
|
94
|
+
|
|
95
|
+
watch(
|
|
96
|
+
() => props.modelValue,
|
|
97
|
+
() => checkLimit(),
|
|
98
|
+
);
|
|
67
99
|
</script>
|
|
@@ -36,6 +36,31 @@ describe('BmsInputText', () => {
|
|
|
36
36
|
expect(wrapper.emitted()['update:modelValue']).toBeUndefined();
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
it('should try limit of the input when value change', async () => {
|
|
40
|
+
const { wrapper, inputElement } = factory({
|
|
41
|
+
modelValue: 'toto',
|
|
42
|
+
disabled: true,
|
|
43
|
+
maxlength: 5,
|
|
44
|
+
minlength: 2,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect((wrapper.vm as any).internalErrors.length).toBe(0);
|
|
48
|
+
await wrapper.setProps({ modelValue: 'superToto' });
|
|
49
|
+
await nextTick();
|
|
50
|
+
|
|
51
|
+
expect((wrapper.vm as any).internalErrors.length).toBe(1);
|
|
52
|
+
expect((wrapper.vm as any).internalErrors[0]).toBe(
|
|
53
|
+
'Longueur supérieure au maximum autorisé (max:5)',
|
|
54
|
+
);
|
|
55
|
+
await wrapper.setProps({ modelValue: 't' });
|
|
56
|
+
await nextTick();
|
|
57
|
+
|
|
58
|
+
expect((wrapper.vm as any).internalErrors.length).toBe(1);
|
|
59
|
+
expect((wrapper.vm as any).internalErrors[0]).toBe(
|
|
60
|
+
'Longueur inférieur au minimum autorisé (min:2)',
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
39
64
|
it('should emit blur event for form validation', async () => {
|
|
40
65
|
const { wrapper, inputElement } = factory({
|
|
41
66
|
modelValue: 'toto',
|
|
@@ -2,6 +2,7 @@ import BmsInputText from '@/components/form/BmsInputText.vue';
|
|
|
2
2
|
import { within } from 'storybook/test';
|
|
3
3
|
import { BookOpen, CloudLightning } from 'lucide-vue-next';
|
|
4
4
|
import template from '@/documentation/template_field_dependency.mdx';
|
|
5
|
+
import { ref } from 'vue';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
title: 'Composants/form/InputText',
|
|
@@ -15,7 +16,9 @@ export default {
|
|
|
15
16
|
|
|
16
17
|
const Template = (args) => ({
|
|
17
18
|
setup() {
|
|
18
|
-
|
|
19
|
+
const modelValue = ref(args.modelValue);
|
|
20
|
+
const modelValueSmall = ref(args.modelValueSmall);
|
|
21
|
+
return { args, modelValue, modelValueSmall };
|
|
19
22
|
},
|
|
20
23
|
components: {
|
|
21
24
|
BmsInputText,
|
|
@@ -23,12 +26,12 @@ const Template = (args) => ({
|
|
|
23
26
|
CloudLightning,
|
|
24
27
|
},
|
|
25
28
|
template: `
|
|
26
|
-
<BmsInputText v-bind="args">
|
|
29
|
+
<BmsInputText v-bind="args" v-model="modelValue">
|
|
27
30
|
<template v-if="args.iconStart" #icon-start><BookOpen/></template>
|
|
28
31
|
<template v-if="args.iconEnd" #icon-end><CloudLightning/></template>
|
|
29
32
|
</BmsInputText>
|
|
30
33
|
<br/>
|
|
31
|
-
<BmsInputText v-bind="{...args, small:true}">
|
|
34
|
+
<BmsInputText v-bind="{...args, small:true}" v-model="modelValueSmall">
|
|
32
35
|
<template v-if="args.iconStart" #icon-start><BookOpen/></template>
|
|
33
36
|
<template v-if="args.iconEnd" #icon-end><CloudLightning/></template>
|
|
34
37
|
</BmsInputText>
|
|
@@ -46,12 +49,14 @@ export const WithValue = Template.bind({});
|
|
|
46
49
|
WithValue.args = {
|
|
47
50
|
label: 'My label',
|
|
48
51
|
modelValue: 'Une valeur',
|
|
52
|
+
modelValueSmall: 'Une valeur',
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
export const Disabled = Template.bind({});
|
|
52
56
|
Disabled.args = {
|
|
53
57
|
label: 'My label',
|
|
54
58
|
modelValue: 'Une valeur',
|
|
59
|
+
modelValueSmall: 'Une valeur',
|
|
55
60
|
disabled: true,
|
|
56
61
|
};
|
|
57
62
|
|
|
@@ -59,20 +64,40 @@ export const WithIcons = Template.bind({});
|
|
|
59
64
|
WithIcons.args = {
|
|
60
65
|
label: 'My label',
|
|
61
66
|
modelValue: 'Une valeur',
|
|
67
|
+
modelValueSmall: 'Une valeur',
|
|
62
68
|
iconStart: true,
|
|
63
69
|
iconEnd: true,
|
|
64
70
|
};
|
|
65
71
|
|
|
72
|
+
export const WithLimitEqual = Template.bind({});
|
|
73
|
+
WithLimitEqual.args = {
|
|
74
|
+
label: 'My label',
|
|
75
|
+
modelValue: 'Un texte pilpoil',
|
|
76
|
+
modelValueSmall: 'too short',
|
|
77
|
+
maxlength: 16,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const WithLimitExceeded = Template.bind({});
|
|
81
|
+
WithLimitExceeded.args = {
|
|
82
|
+
label: 'My label',
|
|
83
|
+
modelValue: 'Une texte trop long pour la borne max',
|
|
84
|
+
modelValueSmall: 'too short',
|
|
85
|
+
maxlength: 15,
|
|
86
|
+
minlength: 10,
|
|
87
|
+
};
|
|
88
|
+
|
|
66
89
|
export const WithErrors = Template.bind({});
|
|
67
90
|
WithErrors.args = {
|
|
68
91
|
label: 'My label',
|
|
69
92
|
modelValue: 'Une valeur',
|
|
93
|
+
modelValueSmall: 'Une valeur',
|
|
70
94
|
errors: ['Error 1', 'Error 2'],
|
|
71
95
|
};
|
|
72
96
|
|
|
73
97
|
export const WithFocusState = {
|
|
74
98
|
args: {
|
|
75
99
|
modelValue: '',
|
|
100
|
+
modelValueSmall: '',
|
|
76
101
|
},
|
|
77
102
|
|
|
78
103
|
play: async ({ canvasElement }) => {
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<field v-bind="$props">
|
|
2
|
+
<field v-bind="$props" :captions="computedCaptions" :errors="computedErrors">
|
|
3
3
|
<RawInputText
|
|
4
4
|
ref="input"
|
|
5
5
|
:type="inputType"
|
|
6
6
|
:modelValue="modelValue"
|
|
7
|
+
@update:model-value="(val) => $emits('update:modelValue', val)"
|
|
7
8
|
:required="required"
|
|
8
9
|
:placeholder="placeholder"
|
|
9
10
|
:disabled="disabled"
|
|
10
|
-
:errors="
|
|
11
|
+
:errors="computedErrors"
|
|
11
12
|
:hasDate="false"
|
|
12
13
|
:small="small"
|
|
14
|
+
:min="min"
|
|
15
|
+
:max="max"
|
|
16
|
+
:minlength="minlength"
|
|
17
|
+
:maxlength="maxlength"
|
|
13
18
|
@blur="$emits('blur')"
|
|
14
|
-
@
|
|
15
|
-
@keyup="onInput"
|
|
19
|
+
@focus="$emits('focus')"
|
|
16
20
|
>
|
|
17
21
|
<template #icon-start>
|
|
18
22
|
<slot name="icon-start"></slot>
|
|
@@ -25,9 +29,9 @@
|
|
|
25
29
|
</template>
|
|
26
30
|
|
|
27
31
|
<script lang="ts" setup>
|
|
28
|
-
import { type Ref, ref } from 'vue';
|
|
32
|
+
import { computed, onMounted, type Ref, ref, watch } from 'vue';
|
|
29
33
|
import RawInputText from '@/components/form/RawInputText.vue';
|
|
30
|
-
import { InputType } from '@/models';
|
|
34
|
+
import { Caption, InputType, StatusType } from '@/models';
|
|
31
35
|
import type { FieldComponentProps } from '@/plugins/field/field-component.model';
|
|
32
36
|
|
|
33
37
|
export interface Props extends FieldComponentProps {
|
|
@@ -38,6 +42,10 @@ export interface Props extends FieldComponentProps {
|
|
|
38
42
|
| InputType.DATETIME;
|
|
39
43
|
modelValue: string | number;
|
|
40
44
|
placeholder?: string;
|
|
45
|
+
min?: number;
|
|
46
|
+
max?: number;
|
|
47
|
+
minlength?: number;
|
|
48
|
+
maxlength?: number;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -48,12 +56,65 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
48
56
|
});
|
|
49
57
|
|
|
50
58
|
const input: Ref<HTMLElement | null> = ref(null);
|
|
59
|
+
const internalErrors: Ref<(string | Caption)[]> = ref([]);
|
|
60
|
+
const computedErrors: Ref<(string | Caption)[]> = computed(() => {
|
|
61
|
+
return props.errors
|
|
62
|
+
? internalErrors.value.concat(props.errors)
|
|
63
|
+
: internalErrors.value;
|
|
64
|
+
});
|
|
65
|
+
const internalCaptions: Ref<(string | Caption)[]> = ref([]);
|
|
66
|
+
const computedCaptions: Ref<(string | Caption)[]> = computed(() => {
|
|
67
|
+
return props.captions
|
|
68
|
+
? internalCaptions.value.concat(props.captions)
|
|
69
|
+
: internalCaptions.value;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
onMounted(() => {
|
|
73
|
+
checkLimit();
|
|
74
|
+
});
|
|
51
75
|
|
|
52
76
|
const $emits = defineEmits<{
|
|
53
|
-
|
|
54
|
-
|
|
77
|
+
'update:modelValue': [value: string];
|
|
78
|
+
blur: [];
|
|
79
|
+
focus: [];
|
|
55
80
|
}>();
|
|
56
81
|
|
|
82
|
+
const onInput = (e: Event) => {
|
|
83
|
+
if (!props.disabled)
|
|
84
|
+
$emits('update:modelValue', (e?.target as HTMLInputElement).value);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const checkLimit = () => {
|
|
88
|
+
internalErrors.value = [];
|
|
89
|
+
internalCaptions.value = [];
|
|
90
|
+
if (
|
|
91
|
+
props.inputType === InputType.TEXT &&
|
|
92
|
+
typeof props.modelValue === 'string'
|
|
93
|
+
) {
|
|
94
|
+
if (
|
|
95
|
+
props.minlength !== undefined &&
|
|
96
|
+
props.modelValue.length < props.minlength
|
|
97
|
+
) {
|
|
98
|
+
internalErrors.value = [
|
|
99
|
+
'Longueur inférieur au minimum autorisé (min:' + props.minlength + ')',
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
if (props.maxlength !== undefined) {
|
|
103
|
+
if (props.modelValue.length > props.maxlength) {
|
|
104
|
+
internalErrors.value = [
|
|
105
|
+
`Longueur supérieure au maximum autorisé (max:${props.maxlength})`,
|
|
106
|
+
];
|
|
107
|
+
} else if (props.modelValue.length === props.maxlength) {
|
|
108
|
+
internalCaptions.value = [
|
|
109
|
+
{
|
|
110
|
+
label: `Attention, vous avez atteint la limite de ${props.maxlength} caractères.`,
|
|
111
|
+
mode: StatusType.Warning,
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
57
118
|
const setFocus = () => {
|
|
58
119
|
if (input.value) {
|
|
59
120
|
(input.value as any).setFocus();
|
|
@@ -64,8 +125,8 @@ defineExpose({
|
|
|
64
125
|
setFocus,
|
|
65
126
|
});
|
|
66
127
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
128
|
+
watch(
|
|
129
|
+
() => props.modelValue,
|
|
130
|
+
() => checkLimit(),
|
|
131
|
+
);
|
|
71
132
|
</script>
|