@ouestfrance/sipa-bms-ui 7.14.1 → 8.0.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 (36) hide show
  1. package/README.md +18 -12
  2. package/dist/components/form/BmsAutocomplete.vue.d.ts +8 -8
  3. package/dist/components/form/BmsInputText.vue.d.ts +13 -13
  4. package/dist/components/form/BmsSearch.vue.d.ts +13 -13
  5. package/dist/components/form/BmsSelect.vue.d.ts +9 -9
  6. package/dist/components/form/RawAutocomplete.vue.d.ts +30 -21
  7. package/dist/components/form/RawInputText.vue.d.ts +6 -6
  8. package/dist/components/navigation/UiTenantSwitcher.vue.d.ts +13 -13
  9. package/dist/components/table/BmsTableFilters.vue.d.ts +13 -13
  10. package/dist/mockServiceWorker.js +1 -1
  11. package/dist/models/form.model.d.ts +6 -0
  12. package/dist/plugins/field/FieldDatalist.spec.d.ts +1 -0
  13. package/dist/plugins/field/FieldDatalist.vue.d.ts +13 -12
  14. package/dist/sipa-bms-ui.css +81 -73
  15. package/dist/sipa-bms-ui.es.js +3405 -3315
  16. package/dist/sipa-bms-ui.es.js.map +1 -1
  17. package/dist/sipa-bms-ui.umd.js +3404 -3314
  18. package/dist/sipa-bms-ui.umd.js.map +1 -1
  19. package/package.json +13 -13
  20. package/src/components/form/BmsAutocomplete.vue +14 -8
  21. package/src/components/form/BmsSelect.spec.ts +5 -14
  22. package/src/components/form/BmsSelect.vue +8 -52
  23. package/src/components/form/RawAutocomplete.spec.ts +20 -17
  24. package/src/components/form/RawAutocomplete.vue +56 -53
  25. package/src/components/layout/BmsForm.stories.js +1 -1
  26. package/src/components/layout/BmsForm.vue +9 -5
  27. package/src/components/layout/BmsForm_retrocompat.stories.js +32 -0
  28. package/src/components/table/BmsTableFilters.vue +2 -2
  29. package/src/components/table/UiBmsTable.vue +4 -1
  30. package/src/models/form.model.ts +7 -0
  31. package/src/pages/Form.stories.js +37 -37
  32. package/src/plugins/field/FieldDatalist.spec.ts +35 -0
  33. package/src/plugins/field/FieldDatalist.stories.js +10 -0
  34. package/src/plugins/field/FieldDatalist.vue +85 -7
  35. package/src/showroom/pages/autocomplete.vue +7 -0
  36. package/src/showroom/pages/forms.vue +1 -1
@@ -36,43 +36,43 @@ const Template = (args) => ({
36
36
  return { args };
37
37
  },
38
38
  template: `
39
- <BmsContentPageLayout>
40
- <template #header>
41
- <BmsHeaderTitle title="Le Titre de la page"/>
42
- </template>
43
- <template #breadcrumb>
44
- <BmsBreadcrumb :breadcrumbs="args.breadcrumb"/>
45
- </template>
46
- <template #default>
47
-
48
- <BmsForm>
49
-
50
- <template #form>
51
- Here the form slot for input
52
- <br/>
53
- <BmsInputText v-model="args.inputValue"
54
- label="Mon premier champ de formulaire"
55
- helper-text="help me here !"
56
- placeholder="ici votre placeholder"
57
- />
58
- <BmsAutocomplete v-model="args.selectValue"
59
- label="Mon second champ de formulaire"
60
- placeholder="ici votre placeholder"
61
- helper-text="help me here !"
62
- :options="['option1','option2']"/>
63
- </template>
64
- <template #actions>
65
- <BmsButton :type="'secondary'" >
66
- Annuler
67
- </BmsButton>
68
- <BmsButton :type="'primary'">
69
- Modifier
70
- </BmsButton>
71
- </template>
72
- </BmsForm>
73
- </template>
74
- </BmsContentPageLayout>
75
- `,
39
+ <BmsContentPageLayout>
40
+ <template #header>
41
+ <BmsHeaderTitle title="Le Titre de la page" />
42
+ </template>
43
+ <template #breadcrumb>
44
+ <BmsBreadcrumb :breadcrumbs="args.breadcrumb" />
45
+ </template>
46
+ <template #default>
47
+ <BmsForm>
48
+ <template #form_1>
49
+ Here the form slot for input
50
+ <br />
51
+ <BmsInputText v-model="args.inputValue"
52
+ label="Mon premier champ de formulaire"
53
+ helper-text="help me here !"
54
+ placeholder="ici votre placeholder"
55
+ />
56
+ </template>
57
+ <template #form_2>
58
+ <BmsAutocomplete v-model="args.selectValue"
59
+ label="Mon second champ de formulaire"
60
+ placeholder="ici votre placeholder"
61
+ helper-text="help me here !"
62
+ :options="['option1','option2']" />
63
+ </template>
64
+ <template #actions>
65
+ <BmsButton :type="'secondary'">
66
+ Annuler
67
+ </BmsButton>
68
+ <BmsButton :type="'primary'">
69
+ Modifier
70
+ </BmsButton>
71
+ </template>
72
+ </BmsForm>
73
+ </template>
74
+ </BmsContentPageLayout>
75
+ `,
76
76
  });
77
77
 
78
78
  export const Default = Template.bind({});
@@ -0,0 +1,35 @@
1
+ import FieldDatalist, { type Props } from '@/plugins/field/FieldDatalist.vue';
2
+ import { mount } from '@vue/test-utils';
3
+ import { field } from '@/plugins/field';
4
+
5
+ const factory = (props?: Props) => {
6
+ const wrapper = mount(FieldDatalist, {
7
+ global: {
8
+ plugins: [field],
9
+ },
10
+ props: {
11
+ isInputFocused: false,
12
+ options: [
13
+ { label: 'titi', value: 'i' },
14
+ { label: 'toto', value: 'o' },
15
+ { label: 'tutu', value: 'u' },
16
+ ],
17
+ modelValue: '',
18
+ ...props,
19
+ },
20
+ attachTo: document.body,
21
+ });
22
+
23
+ return { wrapper };
24
+ };
25
+
26
+ describe('FieldDatalist', () => {
27
+ it('should be able to select with keyboard', async () => {
28
+ const { wrapper } = factory();
29
+ const titi = wrapper.get('[data-testid="i"]');
30
+
31
+ expect(titi.classes()).toStrictEqual([]);
32
+ await wrapper.trigger('keydown.down');
33
+ expect(titi.classes()).toStrictEqual(['selected']);
34
+ });
35
+ });
@@ -33,3 +33,13 @@ WithSelectedItem.args = {
33
33
  value: i,
34
34
  })),
35
35
  };
36
+
37
+ export const CanAddNewOption = Template.bind({});
38
+ CanAddNewOption.args = {
39
+ options: ['toto', 'titi', 'tutu', 'tirlitititututatoooo'].map((i) => ({
40
+ label: i,
41
+ value: i,
42
+ })),
43
+ canAddNewOption: true,
44
+ newOption: 'manual input',
45
+ };
@@ -1,5 +1,11 @@
1
1
  <template>
2
- <ul class="options-list" data-testid="select-options">
2
+ <ul
3
+ class="options-list"
4
+ data-testid="select-options"
5
+ @keydown.up="keyUp"
6
+ @keydown.down="keyDown"
7
+ @keydown.enter="keyEnter"
8
+ >
3
9
  <li
4
10
  v-for="(option, index) in options"
5
11
  :key="index"
@@ -7,24 +13,96 @@
7
13
  :class="{
8
14
  selected: index === currentSelectedItemIndex,
9
15
  }"
10
- @click.prevent="() => $emits('select', option.value)"
16
+ @click.prevent="onClick(option)"
11
17
  >
12
18
  <slot name="option" :option="option">
13
19
  {{ option.label === null ? 'N/A' : option.label }}
14
20
  </slot>
15
21
  </li>
22
+ <li
23
+ v-if="displayNewOption"
24
+ @click.prevent="$emits('addNewOption', newOption)"
25
+ >
26
+ <slot name="new-option"> {{ newOption }} (nouvelle option) </slot>
27
+ </li>
16
28
  </ul>
17
29
  </template>
18
30
 
19
31
  <script setup lang="ts">
20
- interface Props {
21
- options: { label: string; value: any }[];
22
- currentSelectedItemIndex?: number;
32
+ import { InputOption } from '@/models';
33
+ import { computed, onMounted, ref } from 'vue';
34
+
35
+ export interface Props {
36
+ options: InputOption[];
37
+ isInputFocused: boolean;
38
+ canAddNewOption?: boolean;
39
+ newOption?: string;
23
40
  }
24
41
 
25
- withDefaults(defineProps<Props>(), { currentSelectedItemIndex: -1 });
42
+ const props = withDefaults(defineProps<Props>(), {
43
+ canAddNewOption: false,
44
+ newOption: '',
45
+ });
46
+
47
+ const currentSelectedItemIndex = ref<number>(-1);
48
+
49
+ const $emits = defineEmits<{
50
+ select: [option: any];
51
+ addNewOption: [option: string];
52
+ }>();
53
+
54
+ const keyDownEventListener = (ev: KeyboardEvent) => {
55
+ if (props.isInputFocused) {
56
+ switch (ev.key) {
57
+ case 'ArrowDown':
58
+ return keyDown();
59
+ case 'ArrowUp':
60
+ return keyUp();
61
+ case 'Enter':
62
+ return keyEnter();
63
+ default:
64
+ return;
65
+ }
66
+ }
67
+ };
68
+
69
+ onMounted(() => {
70
+ document.addEventListener('keydown', keyDownEventListener);
71
+ });
72
+
73
+ const keyDown = () => {
74
+ if (currentSelectedItemIndex.value < props.options.length - 1) {
75
+ currentSelectedItemIndex.value++;
76
+ } else {
77
+ currentSelectedItemIndex.value = 0;
78
+ }
79
+ };
80
+
81
+ const keyUp = () => {
82
+ if (currentSelectedItemIndex.value > 0) {
83
+ currentSelectedItemIndex.value--;
84
+ } else {
85
+ currentSelectedItemIndex.value = props.options.length - 1;
86
+ }
87
+ };
88
+
89
+ const keyEnter = () => {
90
+ if (currentSelectedItemIndex.value >= 0) {
91
+ $emits('select', props.options[currentSelectedItemIndex.value]);
92
+ currentSelectedItemIndex.value = -1;
93
+ }
94
+ };
95
+
96
+ const onClick = (option: { label: string; value: any }) => {
97
+ $emits('select', option);
98
+ };
26
99
 
27
- const $emits = defineEmits<{ (e: 'select', option: any): void }>();
100
+ const displayNewOption = computed(
101
+ () =>
102
+ props.canAddNewOption &&
103
+ props.newOption &&
104
+ !props.options.find((o) => o.value === props.newOption),
105
+ );
28
106
  </script>
29
107
 
30
108
  <style lang="scss" scoped>
@@ -4,6 +4,8 @@
4
4
  label="Autocomplete avec label/value"
5
5
  :options="optionsLabelValue"
6
6
  v-model="inputLabelValue"
7
+ :can-add-new-option="true"
8
+ @add-new-option="onAddNewOption"
7
9
  />
8
10
 
9
11
  Valeur: {{ inputLabelValue }}
@@ -12,6 +14,7 @@
12
14
  label="Autocomplete avec icones"
13
15
  :options="optionsIcon"
14
16
  v-model="inputValueIcon"
17
+ @select="(o) => console.log('select', o.value)"
15
18
  />
16
19
  Valeur: {{ inputValueIcon }}
17
20
 
@@ -51,4 +54,8 @@ const optionsIcon = ref(
51
54
  ),
52
55
  );
53
56
  const inputValueIcon = ref('');
57
+
58
+ const onAddNewOption = (newOption: string) => {
59
+ optionsLabelValue.value.push({ label: newOption, value: newOption });
60
+ };
54
61
  </script>
@@ -322,7 +322,7 @@ import BmsIconButton from '@/components/button/BmsIconButton.vue';
322
322
 
323
323
  const { success } = useNotifications();
324
324
  const text = ref('');
325
- const selected = ref('');
325
+ const selected = ref('value1');
326
326
  const selected2 = ref('');
327
327
  const files: Ref<File[]> = ref([]);
328
328
  const selectedRadio = ref('titi');