@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.
Files changed (62) 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 -4
  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/components/table/UiBmsTableCell.vue.d.ts +7 -0
  19. package/dist/composables/search.composable.d.ts +1 -0
  20. package/dist/models/table.model.d.ts +6 -0
  21. package/dist/plugins/field/FieldDatalist.vue.d.ts +2 -0
  22. package/dist/plugins/field/field-component.model.d.ts +2 -2
  23. package/dist/sipa-bms-ui.css +173 -126
  24. package/dist/sipa-bms-ui.es.js +4875 -4484
  25. package/dist/sipa-bms-ui.es.js.map +1 -1
  26. package/dist/sipa-bms-ui.umd.js +4882 -4490
  27. package/dist/sipa-bms-ui.umd.js.map +1 -1
  28. package/package.json +11 -11
  29. package/src/components/form/BmsAutocomplete.vue +3 -0
  30. package/src/components/form/BmsInputNumber.spec.ts +26 -0
  31. package/src/components/form/BmsInputNumber.stories.js +20 -3
  32. package/src/components/form/BmsInputNumber.vue +36 -4
  33. package/src/components/form/BmsInputRadio.vue +1 -1
  34. package/src/components/form/BmsInputText.spec.ts +25 -0
  35. package/src/components/form/BmsInputText.stories.js +28 -3
  36. package/src/components/form/BmsInputText.vue +73 -12
  37. package/src/components/form/BmsMultiSelect.vue +67 -30
  38. package/src/components/form/BmsSelect.vue +60 -57
  39. package/src/components/form/RawAutocomplete.spec.ts +0 -8
  40. package/src/components/form/RawAutocomplete.vue +42 -24
  41. package/src/components/form/RawInputText.vue +14 -21
  42. package/src/components/form/RawSelect.vue +111 -0
  43. package/src/components/table/BmsServerTable.vue +18 -3
  44. package/src/components/table/BmsTable.vue +15 -2
  45. package/src/components/table/BmsTableFilters.vue +19 -7
  46. package/src/components/table/UiBmsTable.stories.js +37 -0
  47. package/src/components/table/UiBmsTableCell.vue +25 -0
  48. package/src/components/table/UiBmsTableRow.stories.js +30 -0
  49. package/src/components/table/UiBmsTableRow.vue +3 -2
  50. package/src/components/utils/BmsRelativeTime.vue +1 -1
  51. package/src/composables/search.composable.spec.ts +75 -0
  52. package/src/composables/search.composable.ts +54 -11
  53. package/src/models/table.model.ts +7 -0
  54. package/src/plugins/field/FieldComponent.vue +6 -4
  55. package/src/plugins/field/FieldDatalist.stories.js +0 -9
  56. package/src/plugins/field/FieldDatalist.vue +16 -13
  57. package/src/plugins/field/field-component.model.ts +2 -2
  58. package/src/showroom/pages/autocomplete.vue +22 -1
  59. package/src/showroom/pages/server-table.vue +53 -22
  60. package/src/showroom/pages/table.vue +46 -21
  61. package/dist/plugins/field/FieldDatalist.spec.d.ts +0 -1
  62. package/src/plugins/field/FieldDatalist.spec.ts +0 -35
@@ -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
  };
@@ -1,6 +1,7 @@
1
1
  import UiBmsTable from '@/components/table/UiBmsTable.vue';
2
2
  import { Save, Trash } from 'lucide-vue-next';
3
3
  import BmsIconButton from '@/components/button/BmsIconButton.vue';
4
+ import { ColumnType } from '@/models/table.model';
4
5
 
5
6
  import template from '@/documentation/template_internal_component.mdx';
6
7
 
@@ -379,6 +380,42 @@ SelectableDisabled.args = {
379
380
  selectableDisabled: true,
380
381
  totalSize: 2,
381
382
  };
383
+ export const Typed = Template.bind({});
384
+ Typed.args = {
385
+ headers: [
386
+ {
387
+ label: 'Column boolean',
388
+ key: 'col1',
389
+ align: 'start',
390
+ columnType: ColumnType.Boolean,
391
+ },
392
+ {
393
+ label: 'Column date',
394
+ key: 'col2',
395
+ align: 'center',
396
+ columnType: ColumnType.Date,
397
+ },
398
+ {
399
+ label: 'Column 3',
400
+ key: 'col3',
401
+ align: 'end',
402
+ },
403
+ ],
404
+ items: [
405
+ {
406
+ col1: true,
407
+ col2: '2024-10-10T12:00:00Z',
408
+ col3: 'Value3',
409
+ },
410
+ {
411
+ col1: false,
412
+ col2: '2025-01-01T18:00:00Z',
413
+ col3: 'Value3',
414
+ },
415
+ ],
416
+ hasFilters: true,
417
+ totalSize: 2,
418
+ };
382
419
 
383
420
  const TemplateWithActionColumn = (args) => ({
384
421
  components: {
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <template v-if="cell.columnType === ColumnType.Boolean">
3
+ <BmsChip v-if="cellValue" :color="ChipColor.Green">Oui</BmsChip>
4
+ <BmsChip v-else>Non</BmsChip>
5
+ </template>
6
+ <template v-else-if="cell.columnType === ColumnType.Date">
7
+ <BmsRelativeTime :relativeTo="relativeToTime" />
8
+ </template>
9
+ <template v-else>
10
+ {{ cellValue }}
11
+ </template>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ import { ChipColor, ColumnType, TableHeader } from '@/models';
16
+ import _get from 'lodash/get';
17
+ import BmsChip from '../form/BmsChip.vue';
18
+ import { computed } from 'vue';
19
+ import BmsRelativeTime from '../utils/BmsRelativeTime.vue';
20
+
21
+ const props = defineProps<{ cell: TableHeader; item: any }>();
22
+
23
+ const cellValue = computed(() => _get(props.item, props.cell.key) ?? '');
24
+ const relativeToTime = computed(() => new Date(cellValue.value)?.getTime());
25
+ </script>
@@ -1,4 +1,5 @@
1
1
  import UiBmsTableRow from '@/components/table/UiBmsTableRow.vue';
2
+ import { ColumnType } from '@/models/table.model';
2
3
 
3
4
  export default {
4
5
  title: 'Composants/table/UiBmsTableRow',
@@ -141,3 +142,32 @@ IsChildElement.args = {
141
142
  },
142
143
  },
143
144
  };
145
+
146
+ export const Typed = Template.bind({});
147
+ Typed.args = {
148
+ headers: [
149
+ {
150
+ label: 'Column boolean',
151
+ key: 'col1',
152
+ align: 'start',
153
+ columnType: ColumnType.Boolean,
154
+ },
155
+ {
156
+ label: 'Column date',
157
+ key: 'col2',
158
+ align: 'center',
159
+ columnType: ColumnType.Date,
160
+ },
161
+ {
162
+ label: 'Column 3',
163
+ key: 'col3',
164
+ align: 'end',
165
+ },
166
+ ],
167
+ selectedItems: [],
168
+ item: {
169
+ col1: true,
170
+ col2: '2022-01-01',
171
+ col3: 'Value3',
172
+ },
173
+ };
@@ -37,7 +37,7 @@
37
37
  :row="item.childElement"
38
38
  :isChildElement="isChildElement"
39
39
  >
40
- {{ _get(item.childElement, cell.key) || '' }}
40
+ <UiBmsTableCell :item="item.childElement" :cell="cell" />
41
41
  </slot>
42
42
  </div>
43
43
  <div v-else-if="cell?.action" class="bms-table__row__cell--action">
@@ -53,7 +53,7 @@
53
53
  :row="item"
54
54
  :isChildElement="isChildElement"
55
55
  >
56
- {{ _get(item, cell.key) || '' }}
56
+ <UiBmsTableCell :item="item" :cell="cell" />
57
57
  </slot>
58
58
  </td>
59
59
  </slot>
@@ -68,6 +68,7 @@ import _get from 'lodash/get';
68
68
  import UiBmsInputCheckbox from '../form/UiBmsInputCheckbox.vue';
69
69
  import BmsTooltip from '../feedback/BmsTooltip.vue';
70
70
  import { CornerDownRight } from 'lucide-vue-next';
71
+ import UiBmsTableCell from './UiBmsTableCell.vue';
71
72
 
72
73
  interface Props {
73
74
  item: any;
@@ -25,7 +25,7 @@ const timeout = ref<any>();
25
25
  const date = ref<string>();
26
26
 
27
27
  const formattedDate = computed(() => {
28
- return new Date(props.relativeTo).toLocaleString();
28
+ return new Date(props.relativeTo)?.toLocaleString() ?? '';
29
29
  });
30
30
 
31
31
  onMounted(() => {
@@ -6,6 +6,7 @@ import {
6
6
  } from './search.composable';
7
7
  import { defineComponent, nextTick } from 'vue';
8
8
  import { mount } from '@vue/test-utils';
9
+ import { Filter } from '@/models';
9
10
 
10
11
  vi.mock('vue-router', () => ({
11
12
  useRoute: vi.fn(),
@@ -1292,5 +1293,79 @@ describe('search and filter composable', () => {
1292
1293
  ]);
1293
1294
  });
1294
1295
  });
1296
+
1297
+ describe('updateFiltersFromProps', () => {
1298
+ test('should update select options', () => {
1299
+ const wrapper = factory();
1300
+ const filter: Filter = {
1301
+ key: 'filter0',
1302
+ label: 'Zéro',
1303
+ type: 'select',
1304
+ selectOptions: [{ label: 'option1', value: '1' }],
1305
+ value: 1,
1306
+ };
1307
+
1308
+ wrapper.vm.filters = [filter];
1309
+ wrapper.vm.updateFiltersFromProps([{ ...filter, selectOptions: [] }]);
1310
+ expect(wrapper.vm.filters).toStrictEqual([
1311
+ { ...filter, selectOptions: [], value: null },
1312
+ ]);
1313
+ });
1314
+
1315
+ test('should not change filter other than options', () => {
1316
+ const wrapper = factory();
1317
+ const filter: Filter = {
1318
+ key: 'filter0',
1319
+ label: 'Zéro',
1320
+ type: 'select',
1321
+ selectOptions: [{ label: 'option1', value: '1' }],
1322
+ };
1323
+
1324
+ wrapper.vm.filters = [filter];
1325
+ wrapper.vm.updateFiltersFromProps([
1326
+ {
1327
+ key: 'filter0',
1328
+ label: 'new label',
1329
+ type: 'input',
1330
+ selectOptions: [{ label: 'option1', value: '1' }],
1331
+ },
1332
+ ]);
1333
+ expect(wrapper.vm.filters).toStrictEqual([filter]);
1334
+ });
1335
+ test('should keep value if is in new options', () => {
1336
+ const wrapper = factory();
1337
+ const filter: Filter = {
1338
+ key: 'filter0',
1339
+ label: 'Zéro',
1340
+ type: 'select',
1341
+ selectOptions: [
1342
+ { label: 'option1', value: '1' },
1343
+ { label: 'option2', value: '2' },
1344
+ ],
1345
+ };
1346
+
1347
+ wrapper.vm.filters = [filter];
1348
+ wrapper.vm.updateFiltersFromProps([
1349
+ {
1350
+ key: 'filter0',
1351
+ label: 'Zéro',
1352
+ type: 'select',
1353
+ selectOptions: [
1354
+ { label: 'option1', value: '1' },
1355
+ { label: 'option3', value: '3' },
1356
+ ],
1357
+ },
1358
+ ]);
1359
+ expect(wrapper.vm.filters).toStrictEqual([
1360
+ {
1361
+ ...filter,
1362
+ selectOptions: [
1363
+ { label: 'option1', value: '1' },
1364
+ { label: 'option3', value: '3' },
1365
+ ],
1366
+ },
1367
+ ]);
1368
+ });
1369
+ });
1295
1370
  });
1296
1371
  });
@@ -183,6 +183,7 @@ export const useSearch = (
183
183
  reflectSearchToUserPref(search.value);
184
184
  }
185
185
  });
186
+
186
187
  watch(
187
188
  () => filters,
188
189
  () => {
@@ -233,19 +234,23 @@ export const useSearch = (
233
234
  });
234
235
  };
235
236
 
237
+ const resetFilterValue = (filter: Filter) => {
238
+ let resetFilter = { ...filter };
239
+ const valueKeys = ['value', 'valueFrom', 'valueTo'];
240
+ valueKeys.forEach((key) => {
241
+ if (
242
+ resetFilter[key as keyof Filter] !== null &&
243
+ resetFilter[key as keyof Filter] !== undefined
244
+ ) {
245
+ resetFilter[key as keyof Filter] = null;
246
+ }
247
+ });
248
+ return resetFilter;
249
+ };
250
+
236
251
  const resetFilters = () => {
237
252
  filters.value = filters.value.map((filter) => {
238
- let resetFilter = { ...filter };
239
- const valueKeys = ['value', 'valueFrom', 'valueTo'];
240
- valueKeys.forEach((key) => {
241
- if (
242
- resetFilter[key as keyof Filter] !== null &&
243
- resetFilter[key as keyof Filter] !== undefined
244
- ) {
245
- resetFilter[key as keyof Filter] = null;
246
- }
247
- });
248
- return resetFilter;
253
+ return resetFilterValue(filter);
249
254
  });
250
255
  };
251
256
 
@@ -264,6 +269,43 @@ export const useSearch = (
264
269
  }
265
270
  };
266
271
 
272
+ const updateFiltersFromProps = (updatedFilters: Filter[]) => {
273
+ filters.value = filters.value.map((filter) => {
274
+ const updatedFilter = updatedFilters.find((f) => f.key === filter.key);
275
+
276
+ if (updatedFilter) {
277
+ let mergedFilter = { ...filter };
278
+
279
+ if (updatedFilter.autocompleteOptions)
280
+ mergedFilter.autocompleteOptions = updatedFilter.autocompleteOptions;
281
+ if (updatedFilter.selectOptions)
282
+ mergedFilter.selectOptions = updatedFilter.selectOptions;
283
+
284
+ const value = filter.value || filter.valueFrom || filter.valueTo;
285
+
286
+ const isValueValidSelectOption =
287
+ updatedFilter.type === 'select' &&
288
+ updatedFilter.selectOptions?.find((option) => option.value === value);
289
+
290
+ const isValueValidAutocompleteOption =
291
+ updatedFilter.type === 'autocomplete' &&
292
+ updatedFilter.autocompleteOptions?.find((option) => option === value);
293
+
294
+ if (
295
+ value &&
296
+ !isValueValidSelectOption &&
297
+ !isValueValidAutocompleteOption
298
+ ) {
299
+ mergedFilter = resetFilterValue(mergedFilter);
300
+ }
301
+
302
+ return mergedFilter;
303
+ } else {
304
+ return filter;
305
+ }
306
+ });
307
+ };
308
+
267
309
  return {
268
310
  search,
269
311
  reflect,
@@ -277,6 +319,7 @@ export const useSearch = (
277
319
  resetFilters,
278
320
  selectSavedFilter,
279
321
  resetAllFilters,
322
+ updateFiltersFromProps,
280
323
  };
281
324
  };
282
325
 
@@ -1,6 +1,12 @@
1
1
  import { InputType } from './form.model';
2
2
  import { Sort, SortFunction } from './sort.model';
3
3
 
4
+ export enum ColumnType {
5
+ Boolean = 'Boolean',
6
+ Date = 'Date',
7
+ String = 'String',
8
+ }
9
+
4
10
  export interface TableHeader {
5
11
  label: string;
6
12
  key: string;
@@ -10,6 +16,7 @@ export interface TableHeader {
10
16
  sortable?: boolean;
11
17
  sortFunction?: SortFunction;
12
18
  action?: boolean;
19
+ columnType?: ColumnType;
13
20
  }
14
21
 
15
22
  export interface ServerTableRequestParams {
@@ -24,17 +24,17 @@
24
24
  </span>
25
25
  </div>
26
26
 
27
- <div v-if="captions?.length" class="field__captions">
27
+ <div v-if="captions?.length || errors?.length" class="field__captions">
28
28
  <div
29
+ v-if="captions?.length"
29
30
  v-for="caption in captions"
30
31
  :key="getCaptionIdentifier(caption)"
31
32
  class="information field__caption"
32
33
  >
33
34
  <BmsCaption :caption="caption" />
34
35
  </div>
35
- </div>
36
- <div v-if="errors?.length" class="field__errors">
37
36
  <div
37
+ v-if="errors?.length"
38
38
  v-for="error in computedErrors"
39
39
  :key="error.label"
40
40
  class="field__error"
@@ -186,7 +186,9 @@ const getCaptionIdentifier = (caption: string | Caption): string => {
186
186
  --field-padding: 0.5em;
187
187
  --field-margin: 0;
188
188
  --field-label-font-weight: normal;
189
- // color: var(--bms-grey-75);
189
+ .field__captions {
190
+ margin-top: 0.5rem;
191
+ }
190
192
  }
191
193
 
192
194
  &.is-error {
@@ -27,15 +27,6 @@ Default.args = {
27
27
  })),
28
28
  };
29
29
 
30
- export const WithSelectedItem = Template.bind({});
31
- WithSelectedItem.args = {
32
- currentSelectedItemIndex: 1,
33
- options: ['toto', 'titi', 'tutu', 'tirlitititututatoooo'].map((i) => ({
34
- label: i,
35
- value: i,
36
- })),
37
- };
38
-
39
30
  export const CanAddNewOption = Template.bind({});
40
31
  CanAddNewOption.args = {
41
32
  options: ['toto', 'titi', 'tutu', 'tirlitititututatoooo'].map((i) => ({
@@ -1,29 +1,21 @@
1
1
  <template>
2
- <ul
3
- class="options-list"
4
- data-testid="select-options"
5
- @keydown.up="keyUp"
6
- @keydown.down="keyDown"
7
- @keydown.enter="keyEnter"
8
- >
2
+ <ul class="options-list" data-testid="select-options">
9
3
  <li
10
4
  v-for="(option, index) in options"
11
5
  :key="index"
12
6
  :data-testid="option.value"
13
7
  :class="{
8
+ 'datalist-option': true,
14
9
  selected: index === currentSelectedItemIndex,
15
10
  small,
16
11
  }"
17
- @click.prevent="onClick(option)"
12
+ @click.stop="onClick(option)"
18
13
  >
19
14
  <slot name="option" :option="option">
20
15
  {{ option.label === null ? 'N/A' : option.label }}
21
16
  </slot>
22
17
  </li>
23
- <li
24
- v-if="displayNewOption"
25
- @click.prevent="$emits('addNewOption', newOption)"
26
- >
18
+ <li v-if="displayNewOption" @click.stop="$emits('addNewOption', newOption)">
27
19
  <slot name="new-option"> {{ newOption }} (nouvelle option) </slot>
28
20
  </li>
29
21
  </ul>
@@ -31,7 +23,7 @@
31
23
 
32
24
  <script setup lang="ts">
33
25
  import { InputOption } from '@/models';
34
- import { computed, onMounted, ref } from 'vue';
26
+ import { computed, onMounted, onUnmounted, ref } from 'vue';
35
27
 
36
28
  export interface Props {
37
29
  options: InputOption[];
@@ -52,17 +44,24 @@ const currentSelectedItemIndex = ref<number>(-1);
52
44
  const $emits = defineEmits<{
53
45
  select: [option: any];
54
46
  addNewOption: [option: string];
47
+ blur: [];
55
48
  }>();
56
49
 
57
50
  const keyDownEventListener = (ev: KeyboardEvent) => {
58
51
  if (props.isInputFocused) {
59
52
  switch (ev.key) {
60
53
  case 'ArrowDown':
54
+ ev.preventDefault();
61
55
  return keyDown();
62
56
  case 'ArrowUp':
57
+ ev.preventDefault();
63
58
  return keyUp();
64
59
  case 'Enter':
60
+ ev.preventDefault();
65
61
  return keyEnter();
62
+ case 'Escape':
63
+ ev.preventDefault();
64
+ return keyEscape();
66
65
  default:
67
66
  return;
68
67
  }
@@ -96,6 +95,10 @@ const keyEnter = () => {
96
95
  }
97
96
  };
98
97
 
98
+ const keyEscape = () => {
99
+ $emits('blur');
100
+ };
101
+
99
102
  const onClick = (option: { label: string; value: any }) => {
100
103
  $emits('select', option);
101
104
  };
@@ -5,8 +5,8 @@ export interface FieldComponentProps {
5
5
  required?: boolean;
6
6
  optional?: boolean;
7
7
  helperText?: string;
8
- errors?: string[] | Caption[];
9
- captions?: string[] | Caption[];
8
+ errors?: (string | Caption)[];
9
+ captions?: (string | Caption)[];
10
10
  disabled?: boolean;
11
11
  small?: boolean;
12
12
  }