@ouestfrance/sipa-bms-ui 8.6.0 → 8.8.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 -1
- 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/composables/search.composable.d.ts +1 -0
- package/dist/mockServiceWorker.js +16 -12
- 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 +220 -168
- package/dist/sipa-bms-ui.es.js +729 -524
- package/dist/sipa-bms-ui.es.js.map +1 -1
- package/dist/sipa-bms-ui.umd.js +734 -529
- package/dist/sipa-bms-ui.umd.js.map +1 -1
- package/package.json +11 -11
- package/src/assets/scss/global-variables.scss +6 -0
- package/src/components/feedback/UiTooltip.vue +1 -1
- 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 +66 -28
- 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/layout/BmsOverlay.vue +2 -2
- package/src/components/layout/UiPopoverMenu.vue +1 -1
- package/src/components/navigation/BmsMenu.vue +1 -1
- 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/composables/search.composable.spec.ts +75 -0
- package/src/composables/search.composable.ts +54 -11
- package/src/plugins/field/FieldComponent.vue +7 -5
- 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/plugins/notifications/NotificationWidget.vue +1 -1
- package/src/showroom/pages/autocomplete.vue +22 -1
- package/src/showroom/pages/server-table.vue +53 -22
- package/src/showroom/pages/table.vue +42 -3
- package/src/showroom/pages/zindex.vue +39 -0
- package/dist/plugins/field/FieldDatalist.spec.d.ts +0 -1
- package/src/plugins/field/FieldDatalist.spec.ts +0 -35
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<RawSelect
|
|
3
3
|
v-bind="$props"
|
|
4
4
|
:options="displayedOptions"
|
|
5
5
|
:model-value="modelValue"
|
|
6
|
-
|
|
6
|
+
:open="isDatalistOpen"
|
|
7
|
+
@select="onSelect"
|
|
7
8
|
>
|
|
8
9
|
<template #input>
|
|
9
10
|
<div class="tags">
|
|
@@ -16,12 +17,35 @@
|
|
|
16
17
|
<component v-if="tag?.icon" :is="tag.icon" />
|
|
17
18
|
{{ tag.label }}
|
|
18
19
|
</BmsTag>
|
|
19
|
-
<input
|
|
20
|
+
<input
|
|
21
|
+
type="text"
|
|
22
|
+
ref="inputElement"
|
|
23
|
+
v-model="searching"
|
|
24
|
+
class="search"
|
|
25
|
+
@focus="openDatalist"
|
|
26
|
+
@click="openDatalist"
|
|
27
|
+
@keyup.down="openDatalist"
|
|
28
|
+
@input="openDatalist"
|
|
29
|
+
@keyup.backspace="onBackspace"
|
|
30
|
+
/>
|
|
20
31
|
</div>
|
|
21
32
|
|
|
22
33
|
<span class="icon-container">
|
|
23
|
-
<
|
|
24
|
-
|
|
34
|
+
<template v-if="modelValue.length">
|
|
35
|
+
<X class="icon icon-clear" @click.stop="clearInput" />
|
|
36
|
+
</template>
|
|
37
|
+
<template v-else>
|
|
38
|
+
<ChevronUp
|
|
39
|
+
v-if="isDatalistOpen"
|
|
40
|
+
class="icon icon-toggle-button"
|
|
41
|
+
@click="closeDatalist"
|
|
42
|
+
/>
|
|
43
|
+
<ChevronDown
|
|
44
|
+
v-else
|
|
45
|
+
class="icon icon-toggle-button"
|
|
46
|
+
@click="openDatalist"
|
|
47
|
+
/>
|
|
48
|
+
</template>
|
|
25
49
|
</span>
|
|
26
50
|
</template>
|
|
27
51
|
<template #option="{ option }: { option: InputOption }">
|
|
@@ -30,18 +54,18 @@
|
|
|
30
54
|
{{ option.label }}
|
|
31
55
|
</BmsTag>
|
|
32
56
|
</template>
|
|
33
|
-
</
|
|
57
|
+
</RawSelect>
|
|
34
58
|
</template>
|
|
35
59
|
|
|
36
60
|
<script lang="ts" setup>
|
|
37
|
-
import { computed, ref } from 'vue';
|
|
38
|
-
import { ChevronDown, X } from 'lucide-vue-next';
|
|
39
|
-
import BmsSelect from './BmsSelect.vue';
|
|
61
|
+
import { computed, ref, useTemplateRef } from 'vue';
|
|
62
|
+
import { ChevronDown, ChevronUp, X } from 'lucide-vue-next';
|
|
40
63
|
import BmsTag from './BmsTag.vue';
|
|
41
|
-
import {
|
|
64
|
+
import { InputOption } from '@/models';
|
|
42
65
|
import _ from 'lodash';
|
|
43
66
|
import { searchString } from '@/helpers';
|
|
44
67
|
import { FieldComponentProps } from '@/plugins/field/field-component.model';
|
|
68
|
+
import RawSelect from './RawSelect.vue';
|
|
45
69
|
|
|
46
70
|
export interface Props extends FieldComponentProps {
|
|
47
71
|
options: InputOption[];
|
|
@@ -53,6 +77,12 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
53
77
|
disabled: false,
|
|
54
78
|
required: false,
|
|
55
79
|
});
|
|
80
|
+
const inputElement = useTemplateRef('inputElement');
|
|
81
|
+
|
|
82
|
+
const isDatalistOpen = ref(false);
|
|
83
|
+
|
|
84
|
+
const closeDatalist = () => (isDatalistOpen.value = false);
|
|
85
|
+
const openDatalist = () => (isDatalistOpen.value = true);
|
|
56
86
|
|
|
57
87
|
const searching = ref('');
|
|
58
88
|
|
|
@@ -60,26 +90,34 @@ const modelValue = defineModel<InputOption[]>('modelValue', {
|
|
|
60
90
|
required: true,
|
|
61
91
|
});
|
|
62
92
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
const onBackspace = () => {
|
|
94
|
+
modelValue.value.splice(-1);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const setFocus = () => {
|
|
98
|
+
if (inputElement.value) {
|
|
99
|
+
(inputElement.value as any).focus();
|
|
68
100
|
}
|
|
69
101
|
};
|
|
70
102
|
|
|
103
|
+
const onSelect = (option: any) => {
|
|
104
|
+
modelValue.value.push(option);
|
|
105
|
+
searching.value = '';
|
|
106
|
+
setFocus();
|
|
107
|
+
closeDatalist();
|
|
108
|
+
};
|
|
109
|
+
|
|
71
110
|
const removeOption = (value: string) => {
|
|
72
111
|
modelValue.value = modelValue.value.filter((o) => o.value !== value);
|
|
73
112
|
};
|
|
74
113
|
|
|
75
|
-
const
|
|
114
|
+
const clearInput = () => {
|
|
76
115
|
modelValue.value = [];
|
|
77
116
|
searching.value = '';
|
|
78
117
|
};
|
|
79
118
|
|
|
80
119
|
const displayedOptions = computed(() =>
|
|
81
120
|
props.options
|
|
82
|
-
|
|
83
121
|
.filter((o) => searchString(o.label, searching.value))
|
|
84
122
|
.filter(
|
|
85
123
|
(o) => !modelValue.value.find((option) => option.value === o.value),
|
|
@@ -108,20 +146,20 @@ const displayValues = computed<InputOption[]>(() =>
|
|
|
108
146
|
flex-grow: 1;
|
|
109
147
|
}
|
|
110
148
|
}
|
|
111
|
-
.icon {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
cursor: pointer;
|
|
119
|
-
}
|
|
149
|
+
.icon-container {
|
|
150
|
+
height: 100%;
|
|
151
|
+
display: flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
|
|
154
|
+
&:hover {
|
|
155
|
+
cursor: pointer;
|
|
120
156
|
}
|
|
121
|
-
|
|
157
|
+
|
|
158
|
+
.icon {
|
|
159
|
+
display: block;
|
|
122
160
|
width: 1em;
|
|
161
|
+
height: 1em;
|
|
123
162
|
margin: 0 1em 0 0.5em;
|
|
124
|
-
display: block;
|
|
125
163
|
}
|
|
126
164
|
}
|
|
127
165
|
</style>
|
|
@@ -1,49 +1,50 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
<RawSelect
|
|
3
|
+
v-bind="$props"
|
|
4
|
+
:open="isDatalistOpen"
|
|
5
|
+
@select="onSelect"
|
|
6
|
+
@click="setFocus"
|
|
7
|
+
>
|
|
8
|
+
<template #input>
|
|
9
|
+
<input
|
|
10
|
+
ref="inputElement"
|
|
11
|
+
type="text"
|
|
12
|
+
role="input"
|
|
13
|
+
readonly
|
|
14
|
+
:value="displayValue"
|
|
15
|
+
:placeholder="placeholder"
|
|
16
|
+
:required="required"
|
|
17
|
+
@focus="openDatalist"
|
|
18
|
+
@click="openDatalist"
|
|
19
|
+
@keyup.down="openDatalist"
|
|
20
|
+
/>
|
|
21
|
+
<span class="icon-toggle-container">
|
|
22
|
+
<ChevronUp
|
|
23
|
+
v-if="isDatalistOpen"
|
|
24
|
+
class="icon-toggle-button"
|
|
25
|
+
@click="closeDatalist"
|
|
13
26
|
/>
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
</span>
|
|
17
|
-
</slot>
|
|
18
|
-
</span>
|
|
19
|
-
<template #datalist>
|
|
20
|
-
<FieldDatalist
|
|
21
|
-
v-show="open"
|
|
22
|
-
:options="options"
|
|
23
|
-
:is-input-focused="open"
|
|
24
|
-
:small="small"
|
|
25
|
-
@select="(option: any) => selectItem(option)"
|
|
26
|
-
>
|
|
27
|
-
<template #option="{ option }: { option: any }">
|
|
28
|
-
<slot name="option" :option="option"></slot>
|
|
29
|
-
</template>
|
|
30
|
-
</FieldDatalist>
|
|
27
|
+
<ChevronDown v-else class="icon-toggle-button" @click="openDatalist" />
|
|
28
|
+
</span>
|
|
31
29
|
</template>
|
|
32
|
-
</
|
|
30
|
+
</RawSelect>
|
|
33
31
|
</template>
|
|
34
32
|
|
|
35
33
|
<script lang="ts" setup>
|
|
36
|
-
import { ChevronDown } from 'lucide-vue-next';
|
|
37
|
-
import {
|
|
34
|
+
import { ChevronDown, ChevronUp } from 'lucide-vue-next';
|
|
35
|
+
import { computed, Ref, ref, useTemplateRef } from 'vue';
|
|
38
36
|
import _ from 'lodash';
|
|
39
|
-
import FieldDatalist from '@/plugins/field/FieldDatalist.vue';
|
|
40
37
|
import { InputOption } from '@/models';
|
|
41
38
|
import { FieldComponentProps } from '@/plugins/field/field-component.model';
|
|
39
|
+
import RawSelect from './RawSelect.vue';
|
|
40
|
+
import { onClickOutside } from '@vueuse/core';
|
|
41
|
+
import { v4 as uuid } from 'uuid';
|
|
42
42
|
|
|
43
43
|
export interface Props extends FieldComponentProps {
|
|
44
44
|
options: InputOption[];
|
|
45
45
|
optional?: boolean;
|
|
46
46
|
placeholder?: string;
|
|
47
|
+
open?: boolean;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -51,26 +52,28 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
51
52
|
disabled: false,
|
|
52
53
|
required: false,
|
|
53
54
|
small: false,
|
|
55
|
+
open: false,
|
|
54
56
|
});
|
|
57
|
+
|
|
55
58
|
const modelValue = defineModel<any>('modelValue', { required: true });
|
|
56
|
-
const
|
|
59
|
+
const inputElement = useTemplateRef('inputElement');
|
|
57
60
|
|
|
58
|
-
const
|
|
59
|
-
const selectWrapper: Ref<HTMLElement | null> = ref(null);
|
|
61
|
+
const isDatalistOpen = ref(props.open);
|
|
60
62
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
}
|
|
63
|
+
const closeDatalist = () => {
|
|
64
|
+
isDatalistOpen.value = false;
|
|
65
|
+
};
|
|
66
|
+
const openDatalist = () => {
|
|
67
|
+
isDatalistOpen.value = true;
|
|
68
|
+
setFocus();
|
|
69
|
+
};
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
open.value = false;
|
|
72
|
-
}
|
|
73
|
-
});
|
|
71
|
+
const emits = defineEmits<{
|
|
72
|
+
select: [value: any];
|
|
73
|
+
}>();
|
|
74
|
+
|
|
75
|
+
onClickOutside(inputElement, () => closeDatalist, {
|
|
76
|
+
ignore: ['.datalist-option', '.icon-toggle-button'],
|
|
74
77
|
});
|
|
75
78
|
|
|
76
79
|
const displayValue = computed(() => {
|
|
@@ -81,19 +84,19 @@ const displayValue = computed(() => {
|
|
|
81
84
|
return '';
|
|
82
85
|
});
|
|
83
86
|
|
|
84
|
-
const selectItem = (option: any) => {
|
|
85
|
-
modelValue.value = option.value;
|
|
86
|
-
setFocus();
|
|
87
|
-
open.value = false;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
87
|
const setFocus = () => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
selectInput.value.focus();
|
|
88
|
+
if (inputElement.value) {
|
|
89
|
+
(inputElement.value as any).focus();
|
|
94
90
|
}
|
|
95
91
|
};
|
|
96
92
|
|
|
93
|
+
const onSelect = (option: any) => {
|
|
94
|
+
modelValue.value = option.value;
|
|
95
|
+
emits('select', option.value);
|
|
96
|
+
setFocus();
|
|
97
|
+
closeDatalist();
|
|
98
|
+
};
|
|
99
|
+
|
|
97
100
|
defineExpose({
|
|
98
101
|
setFocus,
|
|
99
102
|
});
|
|
@@ -117,7 +120,7 @@ defineExpose({
|
|
|
117
120
|
align-items: center;
|
|
118
121
|
justify-content: space-between;
|
|
119
122
|
|
|
120
|
-
.icon-
|
|
123
|
+
.icon-toggle {
|
|
121
124
|
&-container {
|
|
122
125
|
height: 100%;
|
|
123
126
|
display: flex;
|
|
@@ -40,14 +40,6 @@ describe('RawAutocomplete', () => {
|
|
|
40
40
|
expect(wrapper.text()).toContain('tutu');
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
it('should show options on click', async () => {
|
|
44
|
-
const { wrapper, inputElement, menu } = factory();
|
|
45
|
-
|
|
46
|
-
expect(menu.isVisible()).toBeFalsy();
|
|
47
|
-
await inputElement.trigger('input');
|
|
48
|
-
expect(menu.isVisible()).toBeTruthy();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
43
|
it('should load with default value', async () => {
|
|
52
44
|
const { inputElement } = factory({ modelValue: 'i' } as PropsAndModel);
|
|
53
45
|
expect(inputElement.element.value).toStrictEqual('titi');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<field v-bind="$props">
|
|
3
3
|
<RawInputText
|
|
4
|
-
ref="
|
|
4
|
+
ref="rawInput"
|
|
5
5
|
v-model="inputText"
|
|
6
6
|
:type="InputType.TEXT"
|
|
7
7
|
:disabled="disabled"
|
|
@@ -10,33 +10,37 @@
|
|
|
10
10
|
:required="required"
|
|
11
11
|
:small="small"
|
|
12
12
|
@input="onInput"
|
|
13
|
+
@focus="onFocus"
|
|
13
14
|
>
|
|
14
15
|
<template #icon-start>
|
|
15
16
|
<slot name="icon-start"></slot>
|
|
16
17
|
</template>
|
|
17
18
|
<template #icon-end>
|
|
18
19
|
<slot name="icon-end">
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
<X v-if="inputText.length" class="icon" @click.stop="clearInput" />
|
|
21
|
+
<template v-else>
|
|
22
|
+
<ChevronUp
|
|
23
|
+
v-if="isDatalistOpen"
|
|
24
|
+
class="icon"
|
|
25
|
+
@click="closeDatalist"
|
|
26
|
+
/>
|
|
27
|
+
<ChevronDown v-else class="icon" @click="openDatalist" />
|
|
28
|
+
</template>
|
|
26
29
|
</slot>
|
|
27
30
|
</template>
|
|
28
31
|
</RawInputText>
|
|
29
32
|
<template #datalist>
|
|
30
33
|
<FieldDatalist
|
|
31
|
-
v-show="
|
|
34
|
+
v-show="isDatalistOpen"
|
|
32
35
|
data-testid="autocomplete-menu"
|
|
33
|
-
:is-input-focused="
|
|
36
|
+
:is-input-focused="isDatalistOpen"
|
|
34
37
|
:can-add-new-option="canAddNewOption"
|
|
35
38
|
:new-option="inputText"
|
|
36
39
|
:options="filteredMenuItems"
|
|
37
40
|
:small="small"
|
|
38
41
|
@select="selectItem"
|
|
39
42
|
@add-new-option="(option: string) => emits('addNewOption', option)"
|
|
43
|
+
@blur="closeDatalist"
|
|
40
44
|
>
|
|
41
45
|
<template #option="{ option }: { option: any }">
|
|
42
46
|
<slot name="option" :option="option">{{ option }}</slot>
|
|
@@ -49,11 +53,12 @@
|
|
|
49
53
|
<script setup lang="ts">
|
|
50
54
|
import { searchString } from '@/helpers/string.helper';
|
|
51
55
|
import FieldDatalist from '@/plugins/field/FieldDatalist.vue';
|
|
52
|
-
import { computed, ref,
|
|
56
|
+
import { computed, ref, useTemplateRef, watch } from 'vue';
|
|
53
57
|
import RawInputText from '@/components/form/RawInputText.vue';
|
|
54
58
|
import { InputOption, InputType } from '@/models';
|
|
55
59
|
import { ChevronDown, ChevronUp, X } from 'lucide-vue-next';
|
|
56
60
|
import { FieldComponentProps } from '@/plugins/field/field-component.model';
|
|
61
|
+
import { MaybeElementRef, onClickOutside } from '@vueuse/core';
|
|
57
62
|
|
|
58
63
|
export interface Props extends FieldComponentProps {
|
|
59
64
|
options: InputOption[];
|
|
@@ -71,6 +76,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
71
76
|
|
|
72
77
|
const modelValue = defineModel<string | null>('modelValue', { required: true });
|
|
73
78
|
|
|
79
|
+
const rawInput = useTemplateRef('rawInput');
|
|
80
|
+
|
|
74
81
|
const emits = defineEmits<{
|
|
75
82
|
addNewOption: [newOption: string];
|
|
76
83
|
select: [option: InputOption];
|
|
@@ -85,8 +92,14 @@ const getValidOptionByValue = (value: string | null) =>
|
|
|
85
92
|
const inputText = ref<string>(
|
|
86
93
|
getValidOptionByValue(modelValue.value)?.label ?? '',
|
|
87
94
|
);
|
|
88
|
-
const
|
|
89
|
-
|
|
95
|
+
const isDatalistOpen = ref(props.open);
|
|
96
|
+
|
|
97
|
+
const closeDatalist = () => (isDatalistOpen.value = false);
|
|
98
|
+
const openDatalist = () => (isDatalistOpen.value = true);
|
|
99
|
+
|
|
100
|
+
onClickOutside(rawInput as MaybeElementRef, closeDatalist, {
|
|
101
|
+
ignore: ['.datalist-option', '.icon-toggle-button', '.icon-clear'],
|
|
102
|
+
});
|
|
90
103
|
|
|
91
104
|
const classes = computed(() => {
|
|
92
105
|
return { 'is-error': props.errors?.length, 'is-disabled': props.disabled };
|
|
@@ -97,12 +110,14 @@ const filteredMenuItems = computed(() =>
|
|
|
97
110
|
);
|
|
98
111
|
|
|
99
112
|
const selectItem = (option: InputOption) => {
|
|
100
|
-
emits('select', option);
|
|
101
113
|
const existingOption =
|
|
102
114
|
getValidOptionByLabel(option.label) || getValidOptionByValue(option.value);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
if (existingOption) {
|
|
116
|
+
modelValue.value = existingOption?.value ?? null;
|
|
117
|
+
emits('select', existingOption);
|
|
118
|
+
setFocus();
|
|
119
|
+
closeDatalist();
|
|
120
|
+
}
|
|
106
121
|
};
|
|
107
122
|
|
|
108
123
|
const displayItem = (option: InputOption) => {
|
|
@@ -131,16 +146,21 @@ watch(
|
|
|
131
146
|
}
|
|
132
147
|
},
|
|
133
148
|
);
|
|
149
|
+
|
|
150
|
+
const onFocus = () => {
|
|
151
|
+
openDatalist();
|
|
152
|
+
};
|
|
153
|
+
|
|
134
154
|
const onInput = () => {
|
|
135
|
-
|
|
155
|
+
openDatalist();
|
|
136
156
|
if (inputText.value === '') {
|
|
137
157
|
clearInput();
|
|
138
158
|
}
|
|
139
159
|
};
|
|
140
160
|
|
|
141
161
|
const setFocus = () => {
|
|
142
|
-
if (
|
|
143
|
-
(
|
|
162
|
+
if (rawInput.value) {
|
|
163
|
+
(rawInput.value as any).setFocus();
|
|
144
164
|
}
|
|
145
165
|
};
|
|
146
166
|
|
|
@@ -148,11 +168,9 @@ const clearInput = () => {
|
|
|
148
168
|
inputText.value = '';
|
|
149
169
|
modelValue.value = null;
|
|
150
170
|
setFocus();
|
|
171
|
+
closeDatalist();
|
|
151
172
|
};
|
|
152
|
-
|
|
153
|
-
showDataList.value = !showDataList.value;
|
|
154
|
-
setFocus();
|
|
155
|
-
};
|
|
173
|
+
|
|
156
174
|
defineExpose({
|
|
157
175
|
setFocus,
|
|
158
176
|
});
|
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
:placeholder="placeholder"
|
|
15
15
|
:required="required"
|
|
16
16
|
:disabled="disabled"
|
|
17
|
+
:max="max"
|
|
18
|
+
:min="min"
|
|
19
|
+
:minlength="minlength"
|
|
20
|
+
:maxlength="maxlength"
|
|
17
21
|
@blur="$emits('blur')"
|
|
18
22
|
@input="onInput"
|
|
19
|
-
@
|
|
20
|
-
@keydown.down="$emits('keyDown')"
|
|
21
|
-
@keydown.enter="$emits('keyEnter')"
|
|
23
|
+
@focus="$emits('focus')"
|
|
22
24
|
/>
|
|
23
25
|
<span class="field__input-icon field__input-icon--end">
|
|
24
26
|
<slot name="icon-end"></slot>
|
|
@@ -27,7 +29,7 @@
|
|
|
27
29
|
</template>
|
|
28
30
|
|
|
29
31
|
<script lang="ts" setup>
|
|
30
|
-
import { computed,
|
|
32
|
+
import { computed, Ref, ref } from 'vue';
|
|
31
33
|
import { InputType } from '@/models';
|
|
32
34
|
import { FieldComponentProps } from '@/plugins/field/field-component.model';
|
|
33
35
|
|
|
@@ -35,6 +37,10 @@ export interface Props extends FieldComponentProps {
|
|
|
35
37
|
modelValue: string | number;
|
|
36
38
|
placeholder?: string;
|
|
37
39
|
focus?: boolean;
|
|
40
|
+
min?: number;
|
|
41
|
+
max?: number;
|
|
42
|
+
minlength?: number;
|
|
43
|
+
maxlength?: number;
|
|
38
44
|
type?:
|
|
39
45
|
| InputType.TEXT
|
|
40
46
|
| InputType.NUMBER
|
|
@@ -51,28 +57,15 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
51
57
|
const input: Ref<HTMLElement | null> = ref(null);
|
|
52
58
|
|
|
53
59
|
const $emits = defineEmits<{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
(e: 'keyDown'): void;
|
|
59
|
-
(e: 'keyEnter'): void;
|
|
60
|
+
'update:modelValue': [value: string];
|
|
61
|
+
blur: [];
|
|
62
|
+
focus: [];
|
|
63
|
+
click: [];
|
|
60
64
|
}>();
|
|
61
65
|
|
|
62
|
-
onMounted(() => {
|
|
63
|
-
document.body.addEventListener('click', (ev) => {
|
|
64
|
-
if ((ev.target as any) === input.value) {
|
|
65
|
-
$emits('update:focus', !props.focus);
|
|
66
|
-
ev.stopPropagation();
|
|
67
|
-
} else {
|
|
68
|
-
$emits('update:focus', false);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
66
|
const setFocus = () => {
|
|
73
67
|
if (input.value) {
|
|
74
68
|
input.value.focus();
|
|
75
|
-
$emits('update:focus', true);
|
|
76
69
|
}
|
|
77
70
|
};
|
|
78
71
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<field v-bind="$props">
|
|
3
|
+
<span class="select-wrapper" :class="classes">
|
|
4
|
+
<slot name="input"> </slot>
|
|
5
|
+
</span>
|
|
6
|
+
<template #datalist>
|
|
7
|
+
<FieldDatalist
|
|
8
|
+
v-show="open"
|
|
9
|
+
:options="options"
|
|
10
|
+
:is-input-focused="open"
|
|
11
|
+
:small="small"
|
|
12
|
+
@select="selectItem"
|
|
13
|
+
>
|
|
14
|
+
<template #option="{ option }: { option: any }">
|
|
15
|
+
<slot name="option" :option="option"></slot>
|
|
16
|
+
</template>
|
|
17
|
+
</FieldDatalist>
|
|
18
|
+
</template>
|
|
19
|
+
</field>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script lang="ts" setup>
|
|
23
|
+
import { computed } from 'vue';
|
|
24
|
+
import FieldDatalist from '@/plugins/field/FieldDatalist.vue';
|
|
25
|
+
import { InputOption } from '@/models';
|
|
26
|
+
import { FieldComponentProps } from '@/plugins/field/field-component.model';
|
|
27
|
+
|
|
28
|
+
export interface Props extends FieldComponentProps {
|
|
29
|
+
options: InputOption[];
|
|
30
|
+
optional?: boolean;
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
open?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
36
|
+
label: '',
|
|
37
|
+
disabled: false,
|
|
38
|
+
required: false,
|
|
39
|
+
small: false,
|
|
40
|
+
open: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const emits = defineEmits<{
|
|
44
|
+
select: [selectedItem: any];
|
|
45
|
+
}>();
|
|
46
|
+
|
|
47
|
+
const classes = computed(() => {
|
|
48
|
+
return { 'is-error': props.errors?.length, 'is-disabled': props.disabled };
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const selectItem = (option: any) => {
|
|
52
|
+
emits('select', option);
|
|
53
|
+
};
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<style lang="scss" scoped>
|
|
57
|
+
.field__input {
|
|
58
|
+
--field-border-color: var(--bms-grey-50);
|
|
59
|
+
--field-border-color-active: var(--bms-main-100);
|
|
60
|
+
--input-background-color: var(--bms-white);
|
|
61
|
+
|
|
62
|
+
.select-wrapper {
|
|
63
|
+
width: 100%;
|
|
64
|
+
padding: 0 0 0 1em;
|
|
65
|
+
border-radius: var(--bms-border-radius);
|
|
66
|
+
border: 1px solid var(--field-border-color);
|
|
67
|
+
background-color: var(--input-background-color);
|
|
68
|
+
min-height: var(--field-height);
|
|
69
|
+
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
justify-content: space-between;
|
|
73
|
+
|
|
74
|
+
.icon-down {
|
|
75
|
+
&-container {
|
|
76
|
+
height: 100%;
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
|
|
80
|
+
&:hover {
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
&-button {
|
|
85
|
+
width: 1em;
|
|
86
|
+
margin: 0 var(--field-padding);
|
|
87
|
+
display: block;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
&:hover {
|
|
92
|
+
--field-border-color: var(--bms-grey-100);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&:has(input:focus) {
|
|
96
|
+
--field-border-color: var(--field-border-color-active);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&.is-error {
|
|
100
|
+
--field-border-color: var(--bms-red-100);
|
|
101
|
+
--input-background-color: var(--bms-red-25);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
&.is-disabled {
|
|
105
|
+
--field-border-color: var(--bms-grey-25);
|
|
106
|
+
--input-background-color: var(--bms-grey-25);
|
|
107
|
+
pointer-events: none;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
@@ -18,11 +18,11 @@ defineProps<{
|
|
|
18
18
|
left: 0;
|
|
19
19
|
width: 100vw;
|
|
20
20
|
height: 100vh;
|
|
21
|
-
z-index:
|
|
21
|
+
z-index: var(--bms-z-index-modal);
|
|
22
22
|
background: rgba(82, 100, 118, 0.3);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
.priority {
|
|
26
|
-
z-index:
|
|
26
|
+
z-index: calc(var(--bms-z-index-modal) + 10);
|
|
27
27
|
}
|
|
28
28
|
</style>
|