@ouestfrance/sipa-bms-ui 8.6.0 → 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 -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/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 +163 -116
- package/dist/sipa-bms-ui.es.js +725 -520
- package/dist/sipa-bms-ui.es.js.map +1 -1
- package/dist/sipa-bms-ui.umd.js +730 -525
- package/dist/sipa-bms-ui.umd.js.map +1 -1
- package/package.json +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/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 +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 +42 -3
- package/dist/plugins/field/FieldDatalist.spec.d.ts +0 -1
- package/src/plugins/field/FieldDatalist.spec.ts +0 -35
|
@@ -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>
|
|
@@ -4,7 +4,10 @@ import BmsPagination from '@/components/table/BmsPagination.vue';
|
|
|
4
4
|
import UiBmsTable from '@/components/table/UiBmsTable.vue';
|
|
5
5
|
import UiFilterButton from '@/components/table/UiFilterButton.vue';
|
|
6
6
|
import { usePagination } from '@/composables/pagination.composable';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
reflectFiltersToPath,
|
|
9
|
+
useSearch,
|
|
10
|
+
} from '@/composables/search.composable';
|
|
8
11
|
import { useSort } from '@/composables/sort.composable';
|
|
9
12
|
import {
|
|
10
13
|
Filter,
|
|
@@ -151,12 +154,15 @@ const {
|
|
|
151
154
|
isSavedFilterActive,
|
|
152
155
|
resetFilters,
|
|
153
156
|
selectSavedFilter,
|
|
157
|
+
updateFiltersFromProps,
|
|
154
158
|
} = useSearch(props.persistent, props.filters, props.defaultFiltersOpened);
|
|
155
159
|
|
|
156
160
|
const emits = defineEmits<{
|
|
157
161
|
deleteSavedFilter: [value: SavedFilter];
|
|
158
162
|
saveFilter: [value: SavedFilter];
|
|
159
163
|
'update:selectMode': [selectMode: SelectMode];
|
|
164
|
+
filterInput: [{ filterKey: string; value: any; e: InputEvent }];
|
|
165
|
+
filterChange: [{ filterKey: string; value: any }];
|
|
160
166
|
}>();
|
|
161
167
|
|
|
162
168
|
const loading = ref<boolean>(false);
|
|
@@ -196,6 +202,14 @@ watch([currentPage, isMounting], () => {
|
|
|
196
202
|
fetchData();
|
|
197
203
|
}
|
|
198
204
|
});
|
|
205
|
+
watch(
|
|
206
|
+
() => props.filters,
|
|
207
|
+
() => {
|
|
208
|
+
updateFiltersFromProps(props.filters);
|
|
209
|
+
reflectFiltersToPath(filters.value);
|
|
210
|
+
},
|
|
211
|
+
{ deep: true },
|
|
212
|
+
);
|
|
199
213
|
|
|
200
214
|
watch(
|
|
201
215
|
[() => filters.value, () => sort.value, size, search],
|
|
@@ -324,8 +338,9 @@ const onSelectAll = () => emits('update:selectMode', SelectMode.ALL);
|
|
|
324
338
|
:canSaveFilters="canSaveFilters"
|
|
325
339
|
@saveFilter="onSaveFilter"
|
|
326
340
|
@resetFilters="resetFilters"
|
|
327
|
-
|
|
328
|
-
|
|
341
|
+
@filterInput="(filterEvent) => emits('filterInput', filterEvent)"
|
|
342
|
+
@filterChange="(filterEvent) => emits('filterChange', filterEvent)"
|
|
343
|
+
/>
|
|
329
344
|
</div>
|
|
330
345
|
</Transition>
|
|
331
346
|
</template>
|
|
@@ -85,11 +85,14 @@ const {
|
|
|
85
85
|
resetFilters,
|
|
86
86
|
selectSavedFilter,
|
|
87
87
|
resetAllFilters,
|
|
88
|
+
updateFiltersFromProps,
|
|
88
89
|
} = useSearch(props.persistent, props.filters, props.defaultFiltersOpened);
|
|
89
90
|
|
|
90
91
|
const emits = defineEmits<{
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
deleteSavedFilter: [value: SavedFilter];
|
|
93
|
+
saveFilter: [value: SavedFilter];
|
|
94
|
+
filterInput: [{ filterKey: string; value: any; e: InputEvent }];
|
|
95
|
+
filterChange: [{ filterKey: string; value: any }];
|
|
93
96
|
}>();
|
|
94
97
|
|
|
95
98
|
const selectedItems: Ref<unknown[]> = defineModel('selectedItems', {
|
|
@@ -116,6 +119,14 @@ const getFilteredItems = () => {
|
|
|
116
119
|
|
|
117
120
|
const isMounting = ref(true);
|
|
118
121
|
|
|
122
|
+
watch(
|
|
123
|
+
() => props.filters,
|
|
124
|
+
() => {
|
|
125
|
+
updateFiltersFromProps(props.filters);
|
|
126
|
+
},
|
|
127
|
+
{ deep: true },
|
|
128
|
+
);
|
|
129
|
+
|
|
119
130
|
watchEffect(
|
|
120
131
|
async () => {
|
|
121
132
|
isMounting.value =
|
|
@@ -237,6 +248,8 @@ const onSelectAll = () => {
|
|
|
237
248
|
:canSaveFilters="canSaveFilters"
|
|
238
249
|
@saveFilter="onSaveFilter"
|
|
239
250
|
@reset-filters="resetFilters"
|
|
251
|
+
@filterInput="(filterEvent) => emits('filterInput', filterEvent)"
|
|
252
|
+
@filterChange="(filterEvent) => emits('filterChange', filterEvent)"
|
|
240
253
|
/>
|
|
241
254
|
</div>
|
|
242
255
|
</Transition>
|
|
@@ -51,6 +51,14 @@
|
|
|
51
51
|
"
|
|
52
52
|
:valueTo="transformValueForComponent(filter?.valueTo, filter.type)"
|
|
53
53
|
:options="getFilterOptions(filter) as any"
|
|
54
|
+
@input="
|
|
55
|
+
(e: InputEvent) =>
|
|
56
|
+
$emits('filterInput', {
|
|
57
|
+
filterKey: filter.key,
|
|
58
|
+
value: (e.target as HTMLInputElement).value,
|
|
59
|
+
e,
|
|
60
|
+
})
|
|
61
|
+
"
|
|
54
62
|
/>
|
|
55
63
|
</span>
|
|
56
64
|
</div>
|
|
@@ -101,9 +109,11 @@ const props = withDefaults(
|
|
|
101
109
|
);
|
|
102
110
|
|
|
103
111
|
const $emits = defineEmits<{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
'update:modelValue': [filters: Filter[]];
|
|
113
|
+
saveFilter: [value: SavedFilter];
|
|
114
|
+
resetFilters: [];
|
|
115
|
+
filterInput: [{ filterKey: string; value: any; e: InputEvent }];
|
|
116
|
+
filterChange: [{ filterKey: string; value: any }];
|
|
107
117
|
}>();
|
|
108
118
|
|
|
109
119
|
const savedModalOpened: Ref<boolean> = ref<boolean>(false);
|
|
@@ -112,10 +122,11 @@ const nameInput: Ref<typeof BmsInputText | null> = ref(null);
|
|
|
112
122
|
|
|
113
123
|
const onFilter = (key: string, inputValue: string, valueKey: any): void => {
|
|
114
124
|
let filter = props.modelValue.find((f) => f.key === key);
|
|
125
|
+
let updatedValue;
|
|
115
126
|
if (filter) {
|
|
116
127
|
switch (filter.type) {
|
|
117
128
|
case 'boolean':
|
|
118
|
-
|
|
129
|
+
updatedValue =
|
|
119
130
|
inputValue === 'true' ? true : inputValue === 'false' ? false : null;
|
|
120
131
|
break;
|
|
121
132
|
case 'select':
|
|
@@ -123,13 +134,14 @@ const onFilter = (key: string, inputValue: string, valueKey: any): void => {
|
|
|
123
134
|
case 'betweenDate':
|
|
124
135
|
case 'betweenDateTime':
|
|
125
136
|
case 'betweenNumber':
|
|
126
|
-
|
|
127
|
-
inputValue === 'null' ? null : inputValue;
|
|
137
|
+
updatedValue = inputValue === 'null' ? null : inputValue;
|
|
128
138
|
break;
|
|
129
139
|
default:
|
|
130
|
-
|
|
140
|
+
updatedValue = inputValue;
|
|
131
141
|
break;
|
|
132
142
|
}
|
|
143
|
+
filter[valueKey as keyof Filter] = updatedValue;
|
|
144
|
+
$emits('filterChange', { filterKey: key, value: updatedValue });
|
|
133
145
|
}
|
|
134
146
|
$emits('update:modelValue', props.modelValue);
|
|
135
147
|
};
|