@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.
Files changed (54) hide show
  1. package/dist/components/form/BmsAutocomplete.vue.d.ts +2 -0
  2. package/dist/components/form/BmsInputBooleanCheckbox.vue.d.ts +1 -1
  3. package/dist/components/form/BmsInputCheckboxGroup.vue.d.ts +2 -2
  4. package/dist/components/form/BmsInputCode.vue.d.ts +2 -2
  5. package/dist/components/form/BmsInputNumber.vue.d.ts +2 -2
  6. package/dist/components/form/BmsInputRadio.vue.d.ts +2 -2
  7. package/dist/components/form/BmsInputText.vue.d.ts +24 -22
  8. package/dist/components/form/BmsMultiSelect.vue.d.ts +3 -1
  9. package/dist/components/form/BmsSearch.vue.d.ts +28 -24
  10. package/dist/components/form/BmsSelect.vue.d.ts +7 -17
  11. package/dist/components/form/RawAutocomplete.vue.d.ts +17 -21
  12. package/dist/components/form/RawInputText.vue.d.ts +9 -9
  13. package/dist/components/form/RawSelect.vue.d.ts +30 -0
  14. package/dist/components/navigation/UiTenantSwitcher.vue.d.ts +28 -24
  15. package/dist/components/table/BmsServerTable.vue.d.ts +18 -0
  16. package/dist/components/table/BmsTable.vue.d.ts +18 -1
  17. package/dist/components/table/BmsTableFilters.vue.d.ts +47 -25
  18. package/dist/composables/search.composable.d.ts +1 -0
  19. package/dist/plugins/field/FieldDatalist.vue.d.ts +2 -0
  20. package/dist/plugins/field/field-component.model.d.ts +2 -2
  21. package/dist/sipa-bms-ui.css +163 -116
  22. package/dist/sipa-bms-ui.es.js +725 -520
  23. package/dist/sipa-bms-ui.es.js.map +1 -1
  24. package/dist/sipa-bms-ui.umd.js +730 -525
  25. package/dist/sipa-bms-ui.umd.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/components/form/BmsAutocomplete.vue +3 -0
  28. package/src/components/form/BmsInputNumber.spec.ts +26 -0
  29. package/src/components/form/BmsInputNumber.stories.js +20 -3
  30. package/src/components/form/BmsInputNumber.vue +36 -4
  31. package/src/components/form/BmsInputRadio.vue +1 -1
  32. package/src/components/form/BmsInputText.spec.ts +25 -0
  33. package/src/components/form/BmsInputText.stories.js +28 -3
  34. package/src/components/form/BmsInputText.vue +73 -12
  35. package/src/components/form/BmsMultiSelect.vue +66 -28
  36. package/src/components/form/BmsSelect.vue +60 -57
  37. package/src/components/form/RawAutocomplete.spec.ts +0 -8
  38. package/src/components/form/RawAutocomplete.vue +42 -24
  39. package/src/components/form/RawInputText.vue +14 -21
  40. package/src/components/form/RawSelect.vue +111 -0
  41. package/src/components/table/BmsServerTable.vue +18 -3
  42. package/src/components/table/BmsTable.vue +15 -2
  43. package/src/components/table/BmsTableFilters.vue +19 -7
  44. package/src/composables/search.composable.spec.ts +75 -0
  45. package/src/composables/search.composable.ts +54 -11
  46. package/src/plugins/field/FieldComponent.vue +6 -4
  47. package/src/plugins/field/FieldDatalist.stories.js +0 -9
  48. package/src/plugins/field/FieldDatalist.vue +16 -13
  49. package/src/plugins/field/field-component.model.ts +2 -2
  50. package/src/showroom/pages/autocomplete.vue +22 -1
  51. package/src/showroom/pages/server-table.vue +53 -22
  52. package/src/showroom/pages/table.vue +42 -3
  53. package/dist/plugins/field/FieldDatalist.spec.d.ts +0 -1
  54. package/src/plugins/field/FieldDatalist.spec.ts +0 -35
@@ -1,49 +1,50 @@
1
1
  <template>
2
- <field v-bind="$props">
3
- <span class="select-wrapper" ref="selectWrapper" :class="classes">
4
- <slot name="input">
5
- <input
6
- ref="selectInput"
7
- type="text"
8
- role="input"
9
- :value="displayValue"
10
- :placeholder="placeholder"
11
- onkeypress="return false;"
12
- :required="required"
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
- <span class="icon-down-container">
15
- <ChevronDown class="icon-down-button" />
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
- </field>
30
+ </RawSelect>
33
31
  </template>
34
32
 
35
33
  <script lang="ts" setup>
36
- import { ChevronDown } from 'lucide-vue-next';
37
- import { Ref, computed, onMounted, ref, watch } from 'vue';
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 open = defineModel<boolean>('open', { default: false });
59
+ const inputElement = useTemplateRef('inputElement');
57
60
 
58
- const selectInput: Ref<HTMLElement | null> = ref(null);
59
- const selectWrapper: Ref<HTMLElement | null> = ref(null);
61
+ const isDatalistOpen = ref(props.open);
60
62
 
61
- const classes = computed(() => {
62
- return { 'is-error': props.errors?.length, 'is-disabled': props.disabled };
63
- });
63
+ const closeDatalist = () => {
64
+ isDatalistOpen.value = false;
65
+ };
66
+ const openDatalist = () => {
67
+ isDatalistOpen.value = true;
68
+ setFocus();
69
+ };
64
70
 
65
- onMounted(() => {
66
- document.body.addEventListener('click', (ev) => {
67
- if ((ev.target as any).closest('.select-wrapper') === selectWrapper.value) {
68
- open.value = !open.value;
69
- ev.stopPropagation();
70
- } else {
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
- open.value = true;
92
- if (selectInput.value) {
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-down {
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="autocompleteInput"
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
- <span v-if="inputText.length" class="icon" @click="clearInput">
20
- <X />
21
- </span>
22
- <span v-else class="icon" @click.stop="toggleList">
23
- <ChevronDown v-if="!showDataList" />
24
- <ChevronUp v-else />
25
- </span>
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="showDataList"
34
+ v-show="isDatalistOpen"
32
35
  data-testid="autocomplete-menu"
33
- :is-input-focused="showDataList"
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, Ref, watch } from 'vue';
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 showDataList = ref<boolean>(props.open);
89
- const autocompleteInput: Ref<HTMLElement | null> = ref(null);
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
- modelValue.value = existingOption?.value ?? null;
104
- showDataList.value = false;
105
- setFocus();
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
- showDataList.value = true;
155
+ openDatalist();
136
156
  if (inputText.value === '') {
137
157
  clearInput();
138
158
  }
139
159
  };
140
160
 
141
161
  const setFocus = () => {
142
- if (autocompleteInput.value) {
143
- (autocompleteInput.value as any).setFocus();
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
- const toggleList = () => {
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
- @keydown.up="$emits('keyUp')"
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, onMounted, Ref, ref } from 'vue';
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
- (e: 'update:modelValue', value: string): void;
55
- (e: 'update:focus', value: boolean): void;
56
- (e: 'blur'): void;
57
- (e: 'keyUp'): void;
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 { useSearch } from '@/composables/search.composable';
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
- </BmsTableFilters>
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
- (e: 'deleteSavedFilter', value: SavedFilter): void;
92
- (e: 'saveFilter', value: SavedFilter): void;
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
- (e: 'update:modelValue', filters: Filter[]): void;
105
- (e: 'saveFilter', value: SavedFilter): void;
106
- (e: 'resetFilters'): void;
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
- filter.value =
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
- filter[valueKey as keyof Filter] =
127
- inputValue === 'null' ? null : inputValue;
137
+ updatedValue = inputValue === 'null' ? null : inputValue;
128
138
  break;
129
139
  default:
130
- filter.value = inputValue;
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
  };