@ouestfrance/sipa-bms-ui 8.20.0 → 8.21.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 (34) hide show
  1. package/dist/components/form/BmsFilePicker.vue.d.ts +4 -0
  2. package/dist/components/form/BmsMultiSelect.vue.d.ts +4 -0
  3. package/dist/components/form/RawAutocomplete.vue.d.ts +8 -0
  4. package/dist/mockServiceWorker.js +1 -1
  5. package/dist/sipa-bms-ui.css +54 -368
  6. package/dist/sipa-bms-ui.es.js +113 -44
  7. package/dist/sipa-bms-ui.es.js.map +1 -1
  8. package/dist/sipa-bms-ui.umd.js +112 -43
  9. package/dist/sipa-bms-ui.umd.js.map +1 -1
  10. package/package.json +12 -12
  11. package/src/assets/scss/_conf.scss +0 -1
  12. package/src/assets/scss/app.scss +0 -1
  13. package/src/components/button/BmsAllButtons.stories.js +50 -23
  14. package/src/components/button/BmsButton.stories.js +153 -59
  15. package/src/components/form/BmsFilePicker.stories.js +6 -1
  16. package/src/components/form/BmsFilePicker.vue +10 -5
  17. package/src/components/form/BmsMultiSelect.vue +32 -25
  18. package/src/components/form/BmsSelect.vue +18 -16
  19. package/src/components/form/RawAutocomplete.vue +16 -4
  20. package/src/components/form/RawInputText.vue +1 -0
  21. package/src/components/layout/BmsModal.stories.js +2 -1
  22. package/src/components/layout/BmsSplitWindow.vue +0 -1
  23. package/src/components/table/BmsTableFilters.vue +1 -1
  24. package/src/documentation/button/primaryButton.mdx +142 -0
  25. package/src/documentation/{secondaryButton.mdx → button/secondaryButton.mdx} +2 -2
  26. package/src/documentation/foundation/contributing.mdx +72 -0
  27. package/src/documentation/foundation/gettingstarted.mdx +7 -0
  28. package/src/documentation/{principles.mdx → foundation/principles.mdx} +9 -9
  29. package/src/documentation/icons.mdx +43 -0
  30. package/src/showroom/pages/forms.vue +10 -1
  31. package/src/assets/scss/_formkit.scss +0 -353
  32. package/src/components/form/Form.stories.js +0 -35
  33. package/src/documentation/primaryButton.mdx +0 -20
  34. /package/src/documentation/{button.mdx → button/button.mdx} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouestfrance/sipa-bms-ui",
3
- "version": "8.20.0",
3
+ "version": "8.21.0",
4
4
  "author": "Ouest-France BMS",
5
5
  "license": "ISC",
6
6
  "scripts": {
@@ -30,16 +30,15 @@
30
30
  "url": "https://gitlab.ouest-france.fr/sipa-ouest-france/platform/platform-library-vuejs-bms.git"
31
31
  },
32
32
  "devDependencies": {
33
- "@chromatic-com/storybook": "^4.1.2",
33
+ "@chromatic-com/storybook": "^4.1.3",
34
34
  "@codemirror/lang-html": "6.4.11",
35
35
  "@codemirror/lang-json": "6.0.2",
36
36
  "@commitlint/cli": "20.1.0",
37
37
  "@commitlint/config-conventional": "20.0.0",
38
- "@formkit/vue": "1.6.9",
39
38
  "@mdx-js/react": "3.1.1",
40
- "@storybook/addon-docs": "10.0.8",
41
- "@storybook/addon-links": "10.0.8",
42
- "@storybook/vue3-vite": "10.0.8",
39
+ "@storybook/addon-docs": "10.1.6",
40
+ "@storybook/addon-links": "10.1.6",
41
+ "@storybook/vue3-vite": "10.1.6",
43
42
  "@types/lodash": "4.17.21",
44
43
  "@types/uuid": "11.0.0",
45
44
  "@vitejs/plugin-vue": "6.0.2",
@@ -61,15 +60,17 @@
61
60
  "lint-staged": "16.2.7",
62
61
  "lodash": "4.17.21",
63
62
  "lucide-vue-next": "0.554.0",
63
+ "msw": "^2.12.4",
64
64
  "msw-storybook-addon": "^2.0.3",
65
65
  "normalize.css": "8.0.1",
66
66
  "path": "0.12.7",
67
67
  "prettier": "3.6.2",
68
+ "remark-gfm": "^4.0.1",
68
69
  "sass": "1.94.2",
69
70
  "semantic-release": "25.0.2",
70
71
  "start-server-and-test": "2.1.3",
71
- "storybook": "10.0.8",
72
- "storybook-addon-pseudo-states": "10.0.8",
72
+ "storybook": "10.1.6",
73
+ "storybook-addon-pseudo-states": "10.1.6",
73
74
  "storybook-addon-tag-badges": "^3.0.2",
74
75
  "storybook-vue3-router": "^7.0.0",
75
76
  "typescript": "5.2.2",
@@ -118,9 +119,8 @@
118
119
  ]
119
120
  },
120
121
  "msw": {
121
- "workerDirectory": "public"
122
- },
123
- "dependencies": {
124
- "msw": "^2.3.1"
122
+ "workerDirectory": [
123
+ "public"
124
+ ]
125
125
  }
126
126
  }
@@ -1,4 +1,3 @@
1
- @forward './formkit';
2
1
  @forward './global-variables.scss';
3
2
  @use 'sass:list';
4
3
 
@@ -7,5 +7,4 @@ $customFontFamily: 'Roboto', Arial, Helvetica, sans-serif;
7
7
  @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
8
8
 
9
9
  @include sbu.styles;
10
- @include sbu.formkit;
11
10
  @include sbu.global-variables;
@@ -1,12 +1,12 @@
1
1
  import BmsButton from '@/components/button/BmsButton.vue';
2
2
  import BmsIconButton from '@/components/button/BmsIconButton.vue';
3
- import { Heart } from 'lucide-vue-next';
3
+ import { Heart, ArrowLeft, ArrowRight } from 'lucide-vue-next';
4
4
 
5
5
  const types = ['primary', 'secondary', 'tertiary'];
6
6
  const modes = ['default', 'danger'];
7
7
 
8
8
  export default {
9
- title: 'Composants/button',
9
+ title: 'Composants/button/Button',
10
10
  component: BmsButton,
11
11
  };
12
12
 
@@ -14,41 +14,68 @@ const AllButtonsTemplate = () => ({
14
14
  components: {
15
15
  BmsButton,
16
16
  BmsIconButton,
17
+ ArrowLeft,
18
+ ArrowRight,
17
19
  Heart,
18
20
  },
19
21
  setup() {
20
22
  return { types, modes };
21
23
  },
22
24
  template: `
23
- <div style="display:grid;grid-template-columns:repeat(3, auto); gap:1em; justify-items: start;">
25
+ <div style="display:grid;grid-template-columns:repeat(4, auto); gap:1em; justify-items: start; align-items: center;">
24
26
  <template v-for="mode in modes" :key="mode">
25
27
  <template v-for="type in types">
26
- <template v-for="to in [null,'/path']" :key="to">
27
- <BmsButton :mode="mode" :type="type" :to="to">
28
- Button {{ mode }} {{ type }} {{ to ? ' avec une URL ' : '' }}
28
+ <BmsButton :mode="mode" :type="type" small>
29
+ Button {{ mode }} {{ type }} small
29
30
  </BmsButton>
30
- <BmsButton :mode="mode" :type="type" class="_hover" :to="to">
31
- Button {{ mode }} {{ type }} {{ to ? ' avec une URL ' : '' }} hovered
31
+ <BmsButton :mode="mode" :type="type">
32
+ Button {{ mode }} {{ type }}
32
33
  </BmsButton>
33
- <BmsButton :mode="mode" :type="type" disabled :to="to">
34
- Button {{ mode }} {{ type }} {{ to ? ' avec une URL ' : '' }} disabled
34
+ <BmsButton :mode="mode" :type="type" class="_hover">
35
+ Button {{ mode }} {{ type }} hovered
36
+ </BmsButton>
37
+ <BmsButton :mode="mode" :type="type" disabled>
38
+ Button {{ mode }} {{ type }} disabled
35
39
  </BmsButton>
36
- </template>
37
40
  </template>
38
41
  </template>
42
+
43
+ <BmsButton small>
44
+ <template #start> <ArrowLeft/> </template>
45
+ Button small
46
+ <template #end> <ArrowRight/> </template>
47
+ </BmsButton>
48
+ <BmsButton>
49
+ <template #start> <ArrowLeft/> </template>
50
+ Button
51
+ <template #end> <ArrowRight/> </template>
52
+ </BmsButton>
53
+ <BmsButton class="_hover">
54
+ <template #start> <ArrowLeft/> </template>
55
+ Button hovered
56
+ <template #end> <ArrowRight/> </template>
57
+ </BmsButton>
58
+ <BmsButton disabled>
59
+ <template #start> <ArrowLeft/> </template>
60
+ Button disabled
61
+ <template #end> <ArrowRight/> </template>
62
+ </BmsButton>
63
+
39
64
 
40
- <BmsIconButton v-bind="args" :to="to">
41
- <Heart />
42
- </BmsIconButton>
43
- <BmsIconButton v-bind="args" class="_hover" :to="to">
44
- <Heart />
45
- </BmsIconButton>
46
- <BmsIconButton v-bind="args" disabled>
47
- <Heart />
48
- </BmsIconButton>
49
- <BmsIconButton v-bind="args" disabled :to="to">
50
- <Heart />
51
- </BmsIconButton>
65
+ <template v-for="mode in modes" :key="mode">
66
+ <BmsIconButton v-bind="args" :mode="mode" small>
67
+ <Heart />
68
+ </BmsIconButton>
69
+ <BmsIconButton v-bind="args" :mode="mode">
70
+ <Heart />
71
+ </BmsIconButton>
72
+ <BmsIconButton v-bind="args" :mode="mode" class="_hover">
73
+ <Heart />
74
+ </BmsIconButton>
75
+ <BmsIconButton v-bind="args" :mode="mode" disabled>
76
+ <Heart />
77
+ </BmsIconButton>
78
+ </template>
52
79
  </div>
53
80
  `,
54
81
  });
@@ -1,11 +1,21 @@
1
1
  import BmsButton from '@/components/button/BmsButton.vue';
2
- import { ArrowLeft, ArrowRight, Camera, Heart, Wand } from 'lucide-vue-next';
2
+ import {
3
+ ArrowLeft,
4
+ ArrowRight,
5
+ Camera,
6
+ Heart,
7
+ Wand,
8
+ Check,
9
+ } from 'lucide-vue-next';
3
10
  import { StatusType } from '@/models';
4
11
 
5
12
  export default {
6
13
  title: 'Composants/button/Button',
7
14
  component: BmsButton,
8
15
  tags: ['with_useable_code'],
16
+ parameters: {
17
+ chromatic: { disable: true },
18
+ },
9
19
  argTypes: {
10
20
  type: {
11
21
  control: { type: 'select' },
@@ -25,78 +35,162 @@ export default {
25
35
  const Template = (args) => ({
26
36
  components: {
27
37
  BmsButton,
28
- ArrowRight,
29
- ArrowLeft,
30
- Camera,
31
- Heart,
32
- Wand,
33
38
  },
34
39
  setup() {
35
40
  return { args };
36
41
  },
37
42
  template: `
38
- <BmsButton v-bind="args">Save me</BmsButton>
39
- <br>
40
- <br>
41
- <BmsButton v-bind="args">
42
- Next page
43
- <template #end><ArrowRight /></template>
44
- </BmsButton>
45
- <br>
46
- <br>
47
- <BmsButton v-bind="args">
48
- <template #start>
49
- <Heart/>
50
- </template>
51
- Icons left & right
52
- <template #end>
53
- <Wand/>
54
- </template>
55
- </BmsButton>
56
- <br>
57
- <br>
58
43
  <BmsButton v-bind="args">
59
- <template #start><ArrowLeft /></template>
60
- Retour
44
+ Save
61
45
  </BmsButton>
62
- <br>
63
- `,
46
+ `,
64
47
  });
65
48
 
66
- const WITH_DEFAULT_SLOT = {
67
- start: '<Heart/>',
68
- default: 'Mon texte',
69
- end: '<Wand/>',
70
- };
49
+ export const Playground = Template.bind({});
50
+ Playground.args = {};
71
51
 
72
- export const Primary = Template.bind({});
73
- Primary.args = {
52
+ // Stories for documentation examples (Do/Don't)
53
+ export const DoSimple = Template.bind({});
54
+ DoSimple.args = {
74
55
  type: 'primary',
75
- ...WITH_DEFAULT_SLOT,
76
56
  };
77
-
78
- export const PrimaryDanger = Template.bind({});
79
- PrimaryDanger.args = {
80
- ...WITH_DEFAULT_SLOT,
81
- type: 'primary',
82
- mode: StatusType.Danger,
57
+ export const DoDanger = Template.bind({});
58
+ DoDanger.args = {
59
+ mode: 'danger',
83
60
  };
84
61
 
85
- export const Secondary = Template.bind({});
86
- Secondary.args = {
87
- ...WITH_DEFAULT_SLOT,
88
- type: 'secondary',
62
+ export const DoSmall = Template.bind({});
63
+ DoSmall.args = {
64
+ small: true,
89
65
  };
90
66
 
91
- export const Tertiary = Template.bind({});
92
- Tertiary.args = {
93
- ...WITH_DEFAULT_SLOT,
94
- type: 'tertiary',
95
- };
67
+ // Do: Button with icon at start
68
+ export const DoIconStart = () => ({
69
+ components: { BmsButton, Check },
70
+ template: `
71
+ <BmsButton type="primary">
72
+ <template #start><Check /></template>
73
+ Confirm
74
+ </BmsButton>
75
+ `,
76
+ });
96
77
 
97
- export const Small = Template.bind({});
98
- Small.args = {
99
- ...WITH_DEFAULT_SLOT,
100
- type: 'primary',
101
- small: true,
102
- };
78
+ // Do: Button with icon at end
79
+ export const DoIconEnd = () => ({
80
+ components: { BmsButton, ArrowRight },
81
+ template: `
82
+ <BmsButton type="primary">
83
+ Next
84
+ <template #end><ArrowRight /></template>
85
+ </BmsButton>
86
+ `,
87
+ });
88
+
89
+ // Do: Submit button
90
+ export const DoSubmit = () => ({
91
+ components: { BmsButton },
92
+ template: `
93
+ <form>
94
+ <BmsButton type="primary" :submit="true">Submit form</BmsButton>
95
+ </form>
96
+ `,
97
+ });
98
+
99
+ // Don't: Long labels
100
+ export const DontLongLabel = () => ({
101
+ components: { BmsButton },
102
+ template:
103
+ '<BmsButton type="primary">Click here to save your changes to the document</BmsButton>',
104
+ });
105
+
106
+ export const DoShortLabel = () => ({
107
+ components: { BmsButton },
108
+ template: '<BmsButton type="primary">Save changes</BmsButton>',
109
+ });
110
+
111
+ // Don't: Multiple primary buttons
112
+ export const DontMultiplePrimary = () => ({
113
+ components: { BmsButton },
114
+ template: `
115
+ <div style="display: flex; gap: 8px;">
116
+ <BmsButton type="primary">Save</BmsButton>
117
+ <BmsButton type="primary">Cancel</BmsButton>
118
+ <BmsButton type="primary">Delete</BmsButton>
119
+ </div>
120
+ `,
121
+ });
122
+
123
+ export const DoOnePrimary = () => ({
124
+ components: { BmsButton },
125
+ template: `
126
+ <div style="display: flex; gap: 8px;">
127
+ <BmsButton type="primary">Save</BmsButton>
128
+ <BmsButton type="secondary">Cancel</BmsButton>
129
+ <BmsButton type="secondary">Delete</BmsButton>
130
+ </div>
131
+ `,
132
+ });
133
+
134
+ // Don't: Too many icons
135
+ export const DontTooManyIcons = () => ({
136
+ components: { BmsButton, Heart, Wand, Check, ArrowRight },
137
+ template: `
138
+ <BmsButton type="primary">
139
+ <template #start><Heart /><Wand /></template>
140
+ Save changes
141
+ <template #end><Check /><ArrowRight /></template>
142
+ </BmsButton>
143
+ `,
144
+ });
145
+
146
+ export const DoOneIcon = () => ({
147
+ components: { BmsButton, ArrowRight },
148
+ template: `
149
+ <BmsButton type="primary">
150
+ <template #end><ArrowRight /></template>
151
+ Next
152
+ </BmsButton>
153
+ `,
154
+ });
155
+
156
+ // Don't: Generic labels
157
+ export const DontGenericLabel = () => ({
158
+ components: { BmsButton },
159
+ template: `
160
+ <div style="display: flex; gap: 8px;">
161
+ <BmsButton type="primary">Click here</BmsButton>
162
+ <BmsButton type="primary">Submit</BmsButton>
163
+ </div>
164
+ `,
165
+ });
166
+
167
+ export const DoSpecificLabel = () => ({
168
+ components: { BmsButton },
169
+ template: `
170
+ <div style="display: flex; gap: 8px;">
171
+ <BmsButton type="primary">Save changes</BmsButton>
172
+ <BmsButton type="primary">Confirm order</BmsButton>
173
+ </div>
174
+ `,
175
+ });
176
+
177
+ // Don't: Primary for cancel
178
+ export const DontPrimaryForCancel = () => ({
179
+ components: { BmsButton },
180
+ template: `
181
+ <div style="display: flex; gap: 8px;">
182
+ <BmsButton type="primary">Cancel</BmsButton>
183
+ <BmsButton type="secondary">Save</BmsButton>
184
+ </div>
185
+ `,
186
+ });
187
+
188
+ export const DoPrimaryForSave = () => ({
189
+ components: { BmsButton },
190
+ template: `
191
+ <div style="display: flex; gap: 8px;">
192
+ <BmsButton type="secondary">Cancel</BmsButton>
193
+ <BmsButton type="primary">Save</BmsButton>
194
+ </div>
195
+ `,
196
+ });
@@ -32,7 +32,12 @@ const Template = (args) => ({
32
32
  });
33
33
 
34
34
  export const Empty = Template.bind({});
35
- Empty.args = { modelValue: [], limit: 5 };
35
+ Empty.args = {
36
+ modelValue: [],
37
+ limit: 5,
38
+ dragOverMessage: 'Déposer vos fichiers ici',
39
+ dragOffMessage: 'Glissez votre fichier ici ou cliquez pour parcourir',
40
+ };
36
41
 
37
42
  export const WithFiles = Template.bind({});
38
43
  WithFiles.args = {
@@ -7,9 +7,16 @@ const files: Ref<File[] | undefined> = ref();
7
7
  const isDragOver = ref(false);
8
8
 
9
9
  const props = withDefaults(
10
- defineProps<{ modelValue?: File[]; limit: number }>(),
10
+ defineProps<{
11
+ dragOverMessage?: string;
12
+ dragOffMessage?: string;
13
+ modelValue?: File[];
14
+ limit: number;
15
+ }>(),
11
16
  {
12
17
  limit: 10,
18
+ dragOverMessage: 'Déposer votre image ici',
19
+ dragOffMessage: 'Glissez votre image ici ou cliquez pour parcourir',
13
20
  },
14
21
  );
15
22
 
@@ -101,10 +108,8 @@ function onDeleteFile(file: File) {
101
108
  @dragover.prevent
102
109
  >
103
110
  <label class="file-upload__label">
104
- <template v-if="isDragOver">Déposer votre image ici</template>
105
- <template v-else
106
- >Glissez votre image ici ou cliquez pour parcourir</template
107
- >
111
+ <template v-if="isDragOver">{{ dragOverMessage }}</template>
112
+ <template v-else>{{ dragOffMessage }}</template>
108
113
  <input
109
114
  data-testid="file-upload-input-file"
110
115
  type="file"
@@ -5,7 +5,7 @@
5
5
  :model-value="modelValue"
6
6
  :open="isDatalistOpen"
7
7
  @select="onSelect"
8
- @click="setFocus"
8
+ @click="onSelectClick"
9
9
  >
10
10
  <template #input>
11
11
  <div class="tags">
@@ -24,11 +24,8 @@
24
24
  v-model="searching"
25
25
  class="search"
26
26
  :disabled="disabled"
27
- @focus="openDatalist"
28
- @click="openDatalist"
27
+ @input="onInput"
29
28
  @keyup.down="openDatalist"
30
- @input="openDatalist"
31
- @keyup.backspace="onBackspace"
32
29
  />
33
30
  </div>
34
31
 
@@ -37,16 +34,8 @@
37
34
  <X class="icon icon-clear" @click.stop="clearInput" />
38
35
  </template>
39
36
  <template v-else>
40
- <ChevronUp
41
- v-if="isDatalistOpen"
42
- class="icon icon-toggle-button"
43
- @click="closeDatalist"
44
- />
45
- <ChevronDown
46
- v-else
47
- class="icon icon-toggle-button"
48
- @click="openDatalist"
49
- />
37
+ <ChevronUp v-if="isDatalistOpen" class="icon icon-toggle-button" />
38
+ <ChevronDown v-else class="icon icon-toggle-button" />
50
39
  </template>
51
40
  </span>
52
41
  </template>
@@ -67,7 +56,7 @@ import { InputOption } from '@/models';
67
56
  import { searchString } from '@/helpers';
68
57
  import { FieldComponentProps } from '@/plugins/field/field-component.model';
69
58
  import RawSelect from './RawSelect.vue';
70
- import { onClickOutside } from '@vueuse/core';
59
+ import { onClickOutside, onKeyDown, onKeyUp } from '@vueuse/core';
71
60
 
72
61
  export interface Props extends FieldComponentProps {
73
62
  options: InputOption[] | string[];
@@ -80,9 +69,16 @@ const props = withDefaults(defineProps<Props>(), {
80
69
  required: false,
81
70
  });
82
71
 
83
- const inputElement: Ref<HTMLElement | null> = ref(null);
72
+ const emits = defineEmits<{
73
+ select: [option: InputOption | string];
74
+ input: [e: InputEvent];
75
+ }>();
76
+
77
+ const modelValue = defineModel<string[] | null>('modelValue', { default: [] });
84
78
 
79
+ const inputElement: Ref<HTMLElement | null> = ref(null);
85
80
  const isDatalistOpen = ref(false);
81
+ const searching = ref('');
86
82
 
87
83
  const closeDatalist = () => (isDatalistOpen.value = false);
88
84
  const openDatalist = () => {
@@ -91,13 +87,9 @@ const openDatalist = () => {
91
87
  }
92
88
  };
93
89
 
94
- const searching = ref('');
95
-
96
- const modelValue = defineModel<string[] | null>('modelValue', { default: [] });
97
-
98
- onClickOutside(inputElement, closeDatalist, {
99
- ignore: ['.datalist-option', '.icon-toggle-button'],
100
- });
90
+ const onBlur = () => {
91
+ closeDatalist();
92
+ };
101
93
 
102
94
  const onBackspace = () => {
103
95
  if (
@@ -107,6 +99,21 @@ const onBackspace = () => {
107
99
  )
108
100
  modelValue.value.splice(-1);
109
101
  };
102
+ onClickOutside(inputElement, closeDatalist, {
103
+ ignore: ['.datalist-option', '.icon-toggle-button'],
104
+ });
105
+ onKeyUp('Escape', closeDatalist);
106
+ onKeyUp('Tab', closeDatalist);
107
+
108
+ onKeyDown('Backspace', onBackspace);
109
+
110
+ const onSelectClick = () => {
111
+ isDatalistOpen.value = !isDatalistOpen.value;
112
+ };
113
+ const onInput = (e: InputEvent) => {
114
+ emits('input', e);
115
+ openDatalist();
116
+ };
110
117
 
111
118
  const setFocus = () => {
112
119
  if (inputElement.value) {
@@ -122,8 +129,8 @@ const onSelect = (option: InputOption | string) => {
122
129
  }
123
130
 
124
131
  searching.value = '';
132
+ emits('select', option);
125
133
  setFocus();
126
- closeDatalist();
127
134
  };
128
135
 
129
136
  const removeOption = (value: string) => {
@@ -3,7 +3,7 @@
3
3
  v-bind="$props"
4
4
  :open="isDatalistOpen"
5
5
  @select="onSelect"
6
- @click="setFocus"
6
+ @click="onSelectClick"
7
7
  >
8
8
  <template #input>
9
9
  <input
@@ -15,17 +15,11 @@
15
15
  :placeholder="placeholder"
16
16
  :required="required"
17
17
  :disabled="disabled"
18
- @focus="openDatalist"
19
- @click="openDatalist"
20
18
  @keyup.down="openDatalist"
21
19
  />
22
20
  <span class="icon-toggle-container">
23
- <ChevronUp
24
- v-if="isDatalistOpen"
25
- class="icon-toggle-button"
26
- @click="closeDatalist"
27
- />
28
- <ChevronDown v-else class="icon-toggle-button" @click="openDatalist" />
21
+ <ChevronUp v-if="isDatalistOpen" class="icon-toggle-button" />
22
+ <ChevronDown v-else class="icon-toggle-button" />
29
23
  </span>
30
24
  </template>
31
25
  </RawSelect>
@@ -33,12 +27,12 @@
33
27
 
34
28
  <script lang="ts" setup>
35
29
  import { ChevronDown, ChevronUp } from 'lucide-vue-next';
36
- import { computed, Ref, ref } from 'vue';
30
+ import { computed, Ref, ref, watch } from 'vue';
37
31
  import _ from 'lodash';
38
32
  import { InputOption } from '@/models';
39
33
  import { FieldComponentProps } from '@/plugins/field/field-component.model';
40
34
  import RawSelect from './RawSelect.vue';
41
- import { onClickOutside } from '@vueuse/core';
35
+ import { onClickOutside, onKeyUp } from '@vueuse/core';
42
36
 
43
37
  export interface Props extends FieldComponentProps {
44
38
  options: InputOption[];
@@ -55,10 +49,13 @@ const props = withDefaults(defineProps<Props>(), {
55
49
  open: false,
56
50
  });
57
51
 
52
+ const emits = defineEmits<{
53
+ select: [value: any];
54
+ }>();
55
+
58
56
  const modelValue = defineModel<any>('modelValue', { required: true });
59
57
 
60
58
  const inputElement: Ref<HTMLElement | null> = ref(null);
61
-
62
59
  const isDatalistOpen = ref(props.open);
63
60
 
64
61
  const closeDatalist = () => {
@@ -71,13 +68,11 @@ const openDatalist = () => {
71
68
  }
72
69
  };
73
70
 
74
- const emits = defineEmits<{
75
- select: [value: any];
76
- }>();
77
-
78
71
  onClickOutside(inputElement, closeDatalist, {
79
72
  ignore: ['.datalist-option', '.icon-toggle-button'],
80
73
  });
74
+ onKeyUp('Escape', closeDatalist);
75
+ onKeyUp('Tab', closeDatalist);
81
76
 
82
77
  const displayValue = computed(() => {
83
78
  const option = props.options.find((o) =>
@@ -100,6 +95,13 @@ const onSelect = (option: any) => {
100
95
  closeDatalist();
101
96
  };
102
97
 
98
+ const onSelectClick = () => {
99
+ isDatalistOpen.value = !isDatalistOpen.value;
100
+ if (isDatalistOpen) {
101
+ setFocus();
102
+ }
103
+ };
104
+
103
105
  defineExpose({
104
106
  setFocus,
105
107
  });