@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouestfrance/sipa-bms-ui",
3
- "version": "8.6.0",
3
+ "version": "8.7.0",
4
4
  "author": "Ouest-France BMS",
5
5
  "license": "ISC",
6
6
  "scripts": {
@@ -15,6 +15,7 @@
15
15
  :small="small"
16
16
  @select="(option: any) => emits('select', option)"
17
17
  @add-new-option="(newOption: string) => emits('addNewOption', newOption)"
18
+ @input="(e: InputEvent) => emits('input', e)"
18
19
  >
19
20
  <template #icon-start v-if="currentOptionIcon">
20
21
  <span
@@ -60,9 +61,11 @@ const props = withDefaults(defineProps<Props>(), {
60
61
  const modelValue = defineModel<string>('modelValue', {
61
62
  default: null,
62
63
  });
64
+
63
65
  const emits = defineEmits<{
64
66
  addNewOption: [newOption: string];
65
67
  select: [option: InputOption];
68
+ input: [e: InputEvent];
66
69
  }>();
67
70
 
68
71
  const currentOptionIcon = computed(() => {
@@ -37,4 +37,30 @@ describe('BmsInputNumber', () => {
37
37
  await nextTick();
38
38
  expect(wrapper.emitted()['update:modelValue']).toBeUndefined();
39
39
  });
40
+
41
+ it('should try limit of the input when value change', async () => {
42
+ const { wrapper, inputElement } = factory({
43
+ modelValue: 8,
44
+ disabled: true,
45
+ max: 10,
46
+ min: 5,
47
+ });
48
+
49
+ expect((wrapper.vm as any).internalErrors.length).toBe(0);
50
+ await wrapper.setProps({ modelValue: 2 });
51
+ await nextTick();
52
+
53
+ expect((wrapper.vm as any).internalErrors.length).toBe(1);
54
+ expect((wrapper.vm as any).internalErrors[0]).toBe(
55
+ 'Valeur inférieure au minimum autorisé (min : 5)',
56
+ );
57
+
58
+ await wrapper.setProps({ modelValue: 15 });
59
+ await nextTick();
60
+
61
+ expect((wrapper.vm as any).internalErrors.length).toBe(1);
62
+ expect((wrapper.vm as any).internalErrors[0]).toBe(
63
+ 'Valeur supérieure au maximum autorisé (max : 10)',
64
+ );
65
+ });
40
66
  });
@@ -3,6 +3,7 @@ import { within } from 'storybook/test';
3
3
  import { BookOpen, CloudLightning } from 'lucide-vue-next';
4
4
 
5
5
  import template from '@/documentation/template_field_dependency.mdx';
6
+ import { ref } from 'vue';
6
7
 
7
8
  export default {
8
9
  parameters: {
@@ -16,7 +17,9 @@ export default {
16
17
 
17
18
  const Template = (args) => ({
18
19
  setup() {
19
- return { args };
20
+ const modelValue = ref(args.modelValue);
21
+ const modelValueSmall = ref(args.modelValueSmall);
22
+ return { args, modelValue, modelValueSmall };
20
23
  },
21
24
  components: {
22
25
  BmsInputNumber,
@@ -24,12 +27,12 @@ const Template = (args) => ({
24
27
  CloudLightning,
25
28
  },
26
29
  template: `
27
- <BmsInputNumber v-bind="args">
30
+ <BmsInputNumber v-bind="args" v-model="modelValue">
28
31
  <template v-if="args.iconStart" #icon-start><BookOpen/></template>
29
32
  <template v-if="args.iconEnd" #icon-end><CloudLightning/></template>
30
33
  </BmsInputNumber>
31
34
  <br/>
32
- <BmsInputNumber v-bind="{...args, small:true}">
35
+ <BmsInputNumber v-bind="{...args, small:true}" v-model="modelValueSmall">
33
36
  <template v-if="args.iconStart" #icon-start><BookOpen/></template>
34
37
  <template v-if="args.iconEnd" #icon-end><CloudLightning/></template>
35
38
  </BmsInputNumber>
@@ -47,12 +50,14 @@ export const WithValue = Template.bind({});
47
50
  WithValue.args = {
48
51
  label: 'My label',
49
52
  modelValue: 10,
53
+ modelValueSmall: 10,
50
54
  };
51
55
 
52
56
  export const Disabled = Template.bind({});
53
57
  Disabled.args = {
54
58
  label: 'My label',
55
59
  modelValue: 10,
60
+ modelValueSmall: 10,
56
61
  disabled: true,
57
62
  };
58
63
 
@@ -60,14 +65,26 @@ export const WithIcons = Template.bind({});
60
65
  WithIcons.args = {
61
66
  label: 'My label',
62
67
  modelValue: 10,
68
+ modelValueSmall: 10,
63
69
  iconStart: true,
64
70
  iconEnd: true,
65
71
  };
66
72
 
73
+ export const WithLimitExceeded = Template.bind({});
74
+ WithLimitExceeded.args = {
75
+ label: 'My label',
76
+ modelValue: 10,
77
+ modelValueSmall: 2,
78
+ min: 5,
79
+ max: 8,
80
+ errors: [{ label: 'Error 1' }],
81
+ };
82
+
67
83
  export const WithErrors = Template.bind({});
68
84
  WithErrors.args = {
69
85
  label: 'My label',
70
86
  modelValue: 10,
87
+ modelValueSmall: 10,
71
88
  errors: ['Error 1', 'Error 2'],
72
89
  };
73
90
 
@@ -8,14 +8,16 @@
8
8
  :helperText="helperText"
9
9
  :placeholder="placeholder"
10
10
  :captions="captions"
11
- :errors="errors"
11
+ :errors="computedErrors"
12
12
  :inputType="InputType.NUMBER"
13
+ :min="min"
14
+ :max="max"
13
15
  @input="onInput"
14
16
  />
15
17
  </template>
16
18
 
17
19
  <script lang="ts" setup>
18
- import { Ref, computed, ref } from 'vue';
20
+ import { Ref, computed, ref, watch, onMounted } from 'vue';
19
21
  import { Caption } from '@/models/caption.model';
20
22
  import BmsInputText from '@/components/form/BmsInputText.vue';
21
23
  import { InputType } from '@/models';
@@ -28,8 +30,8 @@ export interface Props {
28
30
  disabled?: boolean;
29
31
  helperText?: string;
30
32
  placeholder?: string;
31
- captions?: string[] | Caption[];
32
- errors?: string[] | Caption[];
33
+ captions?: (string | Caption)[];
34
+ errors?: (string | Caption)[];
33
35
  max?: number;
34
36
  min?: number;
35
37
  }
@@ -42,6 +44,17 @@ const props = withDefaults(defineProps<Props>(), {
42
44
 
43
45
  const input: Ref<HTMLElement | null> = ref(null);
44
46
 
47
+ const internalErrors: Ref<(string | Caption)[]> = ref([]);
48
+ const computedErrors: Ref<(string | Caption)[]> = computed(() => {
49
+ return props.errors
50
+ ? internalErrors.value.concat(props.errors)
51
+ : internalErrors.value;
52
+ });
53
+
54
+ onMounted(() => {
55
+ checkLimit();
56
+ });
57
+
45
58
  const $emits = defineEmits<{
46
59
  (e: 'update:modelValue', value: number): void;
47
60
  }>();
@@ -55,6 +68,20 @@ const classes = computed(() => {
55
68
  return { 'is-error': props.errors?.length, 'is-disabled': props.disabled };
56
69
  });
57
70
 
71
+ const checkLimit = () => {
72
+ internalErrors.value = [];
73
+ if (props.min !== undefined && props.modelValue < props.min) {
74
+ internalErrors.value = [
75
+ 'Valeur inférieure au minimum autorisé (min : ' + props.min + ')',
76
+ ];
77
+ }
78
+ if (props.max !== undefined && props.modelValue > props.max) {
79
+ internalErrors.value = [
80
+ 'Valeur supérieure au maximum autorisé (max : ' + props.max + ')',
81
+ ];
82
+ }
83
+ };
84
+
58
85
  const setFocus = () => {
59
86
  if (input.value) {
60
87
  input.value.focus();
@@ -64,4 +91,9 @@ const setFocus = () => {
64
91
  defineExpose({
65
92
  setFocus,
66
93
  });
94
+
95
+ watch(
96
+ () => props.modelValue,
97
+ () => checkLimit(),
98
+ );
67
99
  </script>
@@ -26,7 +26,7 @@ export interface Props {
26
26
  name: string;
27
27
  label?: string;
28
28
  disabled?: boolean;
29
- errors?: string[] | Caption[];
29
+ errors?: (string | Caption)[];
30
30
  required?: boolean;
31
31
  }
32
32
 
@@ -36,6 +36,31 @@ describe('BmsInputText', () => {
36
36
  expect(wrapper.emitted()['update:modelValue']).toBeUndefined();
37
37
  });
38
38
 
39
+ it('should try limit of the input when value change', async () => {
40
+ const { wrapper, inputElement } = factory({
41
+ modelValue: 'toto',
42
+ disabled: true,
43
+ maxlength: 5,
44
+ minlength: 2,
45
+ });
46
+
47
+ expect((wrapper.vm as any).internalErrors.length).toBe(0);
48
+ await wrapper.setProps({ modelValue: 'superToto' });
49
+ await nextTick();
50
+
51
+ expect((wrapper.vm as any).internalErrors.length).toBe(1);
52
+ expect((wrapper.vm as any).internalErrors[0]).toBe(
53
+ 'Longueur supérieure au maximum autorisé (max:5)',
54
+ );
55
+ await wrapper.setProps({ modelValue: 't' });
56
+ await nextTick();
57
+
58
+ expect((wrapper.vm as any).internalErrors.length).toBe(1);
59
+ expect((wrapper.vm as any).internalErrors[0]).toBe(
60
+ 'Longueur inférieur au minimum autorisé (min:2)',
61
+ );
62
+ });
63
+
39
64
  it('should emit blur event for form validation', async () => {
40
65
  const { wrapper, inputElement } = factory({
41
66
  modelValue: 'toto',
@@ -2,6 +2,7 @@ import BmsInputText from '@/components/form/BmsInputText.vue';
2
2
  import { within } from 'storybook/test';
3
3
  import { BookOpen, CloudLightning } from 'lucide-vue-next';
4
4
  import template from '@/documentation/template_field_dependency.mdx';
5
+ import { ref } from 'vue';
5
6
 
6
7
  export default {
7
8
  title: 'Composants/form/InputText',
@@ -15,7 +16,9 @@ export default {
15
16
 
16
17
  const Template = (args) => ({
17
18
  setup() {
18
- return { args };
19
+ const modelValue = ref(args.modelValue);
20
+ const modelValueSmall = ref(args.modelValueSmall);
21
+ return { args, modelValue, modelValueSmall };
19
22
  },
20
23
  components: {
21
24
  BmsInputText,
@@ -23,12 +26,12 @@ const Template = (args) => ({
23
26
  CloudLightning,
24
27
  },
25
28
  template: `
26
- <BmsInputText v-bind="args">
29
+ <BmsInputText v-bind="args" v-model="modelValue">
27
30
  <template v-if="args.iconStart" #icon-start><BookOpen/></template>
28
31
  <template v-if="args.iconEnd" #icon-end><CloudLightning/></template>
29
32
  </BmsInputText>
30
33
  <br/>
31
- <BmsInputText v-bind="{...args, small:true}">
34
+ <BmsInputText v-bind="{...args, small:true}" v-model="modelValueSmall">
32
35
  <template v-if="args.iconStart" #icon-start><BookOpen/></template>
33
36
  <template v-if="args.iconEnd" #icon-end><CloudLightning/></template>
34
37
  </BmsInputText>
@@ -46,12 +49,14 @@ export const WithValue = Template.bind({});
46
49
  WithValue.args = {
47
50
  label: 'My label',
48
51
  modelValue: 'Une valeur',
52
+ modelValueSmall: 'Une valeur',
49
53
  };
50
54
 
51
55
  export const Disabled = Template.bind({});
52
56
  Disabled.args = {
53
57
  label: 'My label',
54
58
  modelValue: 'Une valeur',
59
+ modelValueSmall: 'Une valeur',
55
60
  disabled: true,
56
61
  };
57
62
 
@@ -59,20 +64,40 @@ export const WithIcons = Template.bind({});
59
64
  WithIcons.args = {
60
65
  label: 'My label',
61
66
  modelValue: 'Une valeur',
67
+ modelValueSmall: 'Une valeur',
62
68
  iconStart: true,
63
69
  iconEnd: true,
64
70
  };
65
71
 
72
+ export const WithLimitEqual = Template.bind({});
73
+ WithLimitEqual.args = {
74
+ label: 'My label',
75
+ modelValue: 'Un texte pilpoil',
76
+ modelValueSmall: 'too short',
77
+ maxlength: 16,
78
+ };
79
+
80
+ export const WithLimitExceeded = Template.bind({});
81
+ WithLimitExceeded.args = {
82
+ label: 'My label',
83
+ modelValue: 'Une texte trop long pour la borne max',
84
+ modelValueSmall: 'too short',
85
+ maxlength: 15,
86
+ minlength: 10,
87
+ };
88
+
66
89
  export const WithErrors = Template.bind({});
67
90
  WithErrors.args = {
68
91
  label: 'My label',
69
92
  modelValue: 'Une valeur',
93
+ modelValueSmall: 'Une valeur',
70
94
  errors: ['Error 1', 'Error 2'],
71
95
  };
72
96
 
73
97
  export const WithFocusState = {
74
98
  args: {
75
99
  modelValue: '',
100
+ modelValueSmall: '',
76
101
  },
77
102
 
78
103
  play: async ({ canvasElement }) => {
@@ -1,18 +1,22 @@
1
1
  <template>
2
- <field v-bind="$props">
2
+ <field v-bind="$props" :captions="computedCaptions" :errors="computedErrors">
3
3
  <RawInputText
4
4
  ref="input"
5
5
  :type="inputType"
6
6
  :modelValue="modelValue"
7
+ @update:model-value="(val) => $emits('update:modelValue', val)"
7
8
  :required="required"
8
9
  :placeholder="placeholder"
9
10
  :disabled="disabled"
10
- :errors="errors"
11
+ :errors="computedErrors"
11
12
  :hasDate="false"
12
13
  :small="small"
14
+ :min="min"
15
+ :max="max"
16
+ :minlength="minlength"
17
+ :maxlength="maxlength"
13
18
  @blur="$emits('blur')"
14
- @input="onInput"
15
- @keyup="onInput"
19
+ @focus="$emits('focus')"
16
20
  >
17
21
  <template #icon-start>
18
22
  <slot name="icon-start"></slot>
@@ -25,9 +29,9 @@
25
29
  </template>
26
30
 
27
31
  <script lang="ts" setup>
28
- import { type Ref, ref } from 'vue';
32
+ import { computed, onMounted, type Ref, ref, watch } from 'vue';
29
33
  import RawInputText from '@/components/form/RawInputText.vue';
30
- import { InputType } from '@/models';
34
+ import { Caption, InputType, StatusType } from '@/models';
31
35
  import type { FieldComponentProps } from '@/plugins/field/field-component.model';
32
36
 
33
37
  export interface Props extends FieldComponentProps {
@@ -38,6 +42,10 @@ export interface Props extends FieldComponentProps {
38
42
  | InputType.DATETIME;
39
43
  modelValue: string | number;
40
44
  placeholder?: string;
45
+ min?: number;
46
+ max?: number;
47
+ minlength?: number;
48
+ maxlength?: number;
41
49
  }
42
50
 
43
51
  const props = withDefaults(defineProps<Props>(), {
@@ -48,12 +56,65 @@ const props = withDefaults(defineProps<Props>(), {
48
56
  });
49
57
 
50
58
  const input: Ref<HTMLElement | null> = ref(null);
59
+ const internalErrors: Ref<(string | Caption)[]> = ref([]);
60
+ const computedErrors: Ref<(string | Caption)[]> = computed(() => {
61
+ return props.errors
62
+ ? internalErrors.value.concat(props.errors)
63
+ : internalErrors.value;
64
+ });
65
+ const internalCaptions: Ref<(string | Caption)[]> = ref([]);
66
+ const computedCaptions: Ref<(string | Caption)[]> = computed(() => {
67
+ return props.captions
68
+ ? internalCaptions.value.concat(props.captions)
69
+ : internalCaptions.value;
70
+ });
71
+
72
+ onMounted(() => {
73
+ checkLimit();
74
+ });
51
75
 
52
76
  const $emits = defineEmits<{
53
- (e: 'update:modelValue', value: string): void;
54
- (e: 'blur'): void;
77
+ 'update:modelValue': [value: string];
78
+ blur: [];
79
+ focus: [];
55
80
  }>();
56
81
 
82
+ const onInput = (e: Event) => {
83
+ if (!props.disabled)
84
+ $emits('update:modelValue', (e?.target as HTMLInputElement).value);
85
+ };
86
+
87
+ const checkLimit = () => {
88
+ internalErrors.value = [];
89
+ internalCaptions.value = [];
90
+ if (
91
+ props.inputType === InputType.TEXT &&
92
+ typeof props.modelValue === 'string'
93
+ ) {
94
+ if (
95
+ props.minlength !== undefined &&
96
+ props.modelValue.length < props.minlength
97
+ ) {
98
+ internalErrors.value = [
99
+ 'Longueur inférieur au minimum autorisé (min:' + props.minlength + ')',
100
+ ];
101
+ }
102
+ if (props.maxlength !== undefined) {
103
+ if (props.modelValue.length > props.maxlength) {
104
+ internalErrors.value = [
105
+ `Longueur supérieure au maximum autorisé (max:${props.maxlength})`,
106
+ ];
107
+ } else if (props.modelValue.length === props.maxlength) {
108
+ internalCaptions.value = [
109
+ {
110
+ label: `Attention, vous avez atteint la limite de ${props.maxlength} caractères.`,
111
+ mode: StatusType.Warning,
112
+ },
113
+ ];
114
+ }
115
+ }
116
+ }
117
+ };
57
118
  const setFocus = () => {
58
119
  if (input.value) {
59
120
  (input.value as any).setFocus();
@@ -64,8 +125,8 @@ defineExpose({
64
125
  setFocus,
65
126
  });
66
127
 
67
- const onInput = (e: Event) => {
68
- if (!props.disabled)
69
- $emits('update:modelValue', (e?.target as HTMLInputElement).value);
70
- };
128
+ watch(
129
+ () => props.modelValue,
130
+ () => checkLimit(),
131
+ );
71
132
  </script>
@@ -1,9 +1,10 @@
1
1
  <template>
2
- <BmsSelect
2
+ <RawSelect
3
3
  v-bind="$props"
4
4
  :options="displayedOptions"
5
5
  :model-value="modelValue"
6
- @update:model-value="onSelect"
6
+ :open="isDatalistOpen"
7
+ @select="onSelect"
7
8
  >
8
9
  <template #input>
9
10
  <div class="tags">
@@ -16,12 +17,35 @@
16
17
  <component v-if="tag?.icon" :is="tag.icon" />
17
18
  {{ tag.label }}
18
19
  </BmsTag>
19
- <input type="text" v-model="searching" class="search" />
20
+ <input
21
+ type="text"
22
+ ref="inputElement"
23
+ v-model="searching"
24
+ class="search"
25
+ @focus="openDatalist"
26
+ @click="openDatalist"
27
+ @keyup.down="openDatalist"
28
+ @input="openDatalist"
29
+ @keyup.backspace="onBackspace"
30
+ />
20
31
  </div>
21
32
 
22
33
  <span class="icon-container">
23
- <ChevronDown class="icon-button" v-if="modelValue.length === 0" />
24
- <X v-else class="icon-button" @click="reset" />
34
+ <template v-if="modelValue.length">
35
+ <X class="icon icon-clear" @click.stop="clearInput" />
36
+ </template>
37
+ <template v-else>
38
+ <ChevronUp
39
+ v-if="isDatalistOpen"
40
+ class="icon icon-toggle-button"
41
+ @click="closeDatalist"
42
+ />
43
+ <ChevronDown
44
+ v-else
45
+ class="icon icon-toggle-button"
46
+ @click="openDatalist"
47
+ />
48
+ </template>
25
49
  </span>
26
50
  </template>
27
51
  <template #option="{ option }: { option: InputOption }">
@@ -30,18 +54,18 @@
30
54
  {{ option.label }}
31
55
  </BmsTag>
32
56
  </template>
33
- </BmsSelect>
57
+ </RawSelect>
34
58
  </template>
35
59
 
36
60
  <script lang="ts" setup>
37
- import { computed, ref } from 'vue';
38
- import { ChevronDown, X } from 'lucide-vue-next';
39
- import BmsSelect from './BmsSelect.vue';
61
+ import { computed, ref, useTemplateRef } from 'vue';
62
+ import { ChevronDown, ChevronUp, X } from 'lucide-vue-next';
40
63
  import BmsTag from './BmsTag.vue';
41
- import { Caption, InputOption } from '@/models';
64
+ import { InputOption } from '@/models';
42
65
  import _ from 'lodash';
43
66
  import { searchString } from '@/helpers';
44
67
  import { FieldComponentProps } from '@/plugins/field/field-component.model';
68
+ import RawSelect from './RawSelect.vue';
45
69
 
46
70
  export interface Props extends FieldComponentProps {
47
71
  options: InputOption[];
@@ -53,6 +77,12 @@ const props = withDefaults(defineProps<Props>(), {
53
77
  disabled: false,
54
78
  required: false,
55
79
  });
80
+ const inputElement = useTemplateRef('inputElement');
81
+
82
+ const isDatalistOpen = ref(false);
83
+
84
+ const closeDatalist = () => (isDatalistOpen.value = false);
85
+ const openDatalist = () => (isDatalistOpen.value = true);
56
86
 
57
87
  const searching = ref('');
58
88
 
@@ -60,26 +90,34 @@ const modelValue = defineModel<InputOption[]>('modelValue', {
60
90
  required: true,
61
91
  });
62
92
 
63
- const onSelect = (value: string) => {
64
- const option = props.options.find((o) => o.value === value);
65
- if (option) {
66
- modelValue.value.push(option);
67
- searching.value = '';
93
+ const onBackspace = () => {
94
+ modelValue.value.splice(-1);
95
+ };
96
+
97
+ const setFocus = () => {
98
+ if (inputElement.value) {
99
+ (inputElement.value as any).focus();
68
100
  }
69
101
  };
70
102
 
103
+ const onSelect = (option: any) => {
104
+ modelValue.value.push(option);
105
+ searching.value = '';
106
+ setFocus();
107
+ closeDatalist();
108
+ };
109
+
71
110
  const removeOption = (value: string) => {
72
111
  modelValue.value = modelValue.value.filter((o) => o.value !== value);
73
112
  };
74
113
 
75
- const reset = () => {
114
+ const clearInput = () => {
76
115
  modelValue.value = [];
77
116
  searching.value = '';
78
117
  };
79
118
 
80
119
  const displayedOptions = computed(() =>
81
120
  props.options
82
-
83
121
  .filter((o) => searchString(o.label, searching.value))
84
122
  .filter(
85
123
  (o) => !modelValue.value.find((option) => option.value === o.value),
@@ -108,20 +146,20 @@ const displayValues = computed<InputOption[]>(() =>
108
146
  flex-grow: 1;
109
147
  }
110
148
  }
111
- .icon {
112
- &-container {
113
- height: 100%;
114
- display: flex;
115
- align-items: center;
116
-
117
- &:hover {
118
- cursor: pointer;
119
- }
149
+ .icon-container {
150
+ height: 100%;
151
+ display: flex;
152
+ align-items: center;
153
+
154
+ &:hover {
155
+ cursor: pointer;
120
156
  }
121
- &-button {
157
+
158
+ .icon {
159
+ display: block;
122
160
  width: 1em;
161
+ height: 1em;
123
162
  margin: 0 1em 0 0.5em;
124
- display: block;
125
163
  }
126
164
  }
127
165
  </style>