@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
@@ -1,9 +1,10 @@
1
1
  <template>
2
- <BmsSelect
3
- v-model:open="open"
2
+ <RawSelect
4
3
  v-bind="$props"
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,34 +77,47 @@ 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
 
59
89
  const modelValue = defineModel<InputOption[]>('modelValue', {
60
90
  required: true,
61
91
  });
62
- const open = defineModel<boolean>('open', { default: false });
63
92
 
64
- const onSelect = (value: string) => {
65
- const option = props.options.find((o) => o.value === value);
66
- if (option) {
67
- modelValue.value.push(option);
68
- 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();
69
100
  }
70
101
  };
71
102
 
103
+ const onSelect = (option: any) => {
104
+ modelValue.value.push(option);
105
+ searching.value = '';
106
+ setFocus();
107
+ closeDatalist();
108
+ };
109
+
72
110
  const removeOption = (value: string) => {
73
111
  modelValue.value = modelValue.value.filter((o) => o.value !== value);
74
112
  };
75
113
 
76
- const reset = () => {
114
+ const clearInput = () => {
77
115
  modelValue.value = [];
78
116
  searching.value = '';
79
117
  };
80
118
 
81
119
  const displayedOptions = computed(() =>
82
120
  props.options
83
-
84
121
  .filter((o) => searchString(o.label, searching.value))
85
122
  .filter(
86
123
  (o) => !modelValue.value.find((option) => option.value === o.value),
@@ -109,20 +146,20 @@ const displayValues = computed<InputOption[]>(() =>
109
146
  flex-grow: 1;
110
147
  }
111
148
  }
112
- .icon {
113
- &-container {
114
- height: 100%;
115
- display: flex;
116
- align-items: center;
117
-
118
- &:hover {
119
- cursor: pointer;
120
- }
149
+ .icon-container {
150
+ height: 100%;
151
+ display: flex;
152
+ align-items: center;
153
+
154
+ &:hover {
155
+ cursor: pointer;
121
156
  }
122
- &-button {
157
+
158
+ .icon {
159
+ display: block;
123
160
  width: 1em;
161
+ height: 1em;
124
162
  margin: 0 1em 0 0.5em;
125
- display: block;
126
163
  }
127
164
  }
128
165
  </style>
@@ -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>