@teammodlr/ui-kit 1.0.2

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 (129) hide show
  1. package/env.d.ts +1 -0
  2. package/lib/__storybook__/argTypes.d.ts +31 -0
  3. package/lib/__storybook__/createStory.d.ts +8 -0
  4. package/lib/__storybook__/index.d.ts +2 -0
  5. package/lib/components/avatar/Avatar.vue.d.ts +70 -0
  6. package/lib/components/avatargroup/AvatarGroup.vue.d.ts +34 -0
  7. package/lib/components/breadcrumb/Breadcrumb.vue.d.ts +25 -0
  8. package/lib/components/breadcrumbitem/BreadcrumbItem.vue.d.ts +18 -0
  9. package/lib/components/breadcrumbitem/index.d.ts +1 -0
  10. package/lib/components/button/Button.vue.d.ts +121 -0
  11. package/lib/components/buttongroup/ButtonGroup.vue.d.ts +29 -0
  12. package/lib/components/checkbox/Checkbox.vue.d.ts +97 -0
  13. package/lib/components/dropdown/Dropdown.vue.d.ts +114 -0
  14. package/lib/components/dropdown/DropdownItem.vue.d.ts +61 -0
  15. package/lib/components/dropdown/DropdownList.vue.d.ts +2 -0
  16. package/lib/components/dropdown/DropdownWrapper.vue.d.ts +2 -0
  17. package/lib/components/dropdown/hooks.d.ts +2 -0
  18. package/lib/components/form/Form.vue.d.ts +67 -0
  19. package/lib/components/image/Image.vue.d.ts +50 -0
  20. package/lib/components/index.d.ts +26 -0
  21. package/lib/components/input/Input.vue.d.ts +126 -0
  22. package/lib/components/link/Link.vue.d.ts +51 -0
  23. package/lib/components/messageprovider/Message.vue.d.ts +61 -0
  24. package/lib/components/messageprovider/MessageProvider.vue.d.ts +51 -0
  25. package/lib/components/messageprovider/types.d.ts +28 -0
  26. package/lib/components/messageprovider/useMessage.d.ts +2 -0
  27. package/lib/components/select/Select.vue.d.ts +180 -0
  28. package/lib/components/skeleton/Skeleton.vue.d.ts +39 -0
  29. package/lib/components/tab/Tab.vue.d.ts +41 -0
  30. package/lib/components/tab/TabGroup.vue.d.ts +43 -0
  31. package/lib/components/table/Table.vue.d.ts +63 -0
  32. package/lib/components/table/TableBody.vue.d.ts +2 -0
  33. package/lib/components/table/TableCell.vue.d.ts +2 -0
  34. package/lib/components/table/TableHead.vue.d.ts +20 -0
  35. package/lib/components/table/TableHeadRow.vue.d.ts +2 -0
  36. package/lib/components/table/TableRow.vue.d.ts +2 -0
  37. package/lib/components/table/types/index.d.ts +5 -0
  38. package/lib/components/tooltip/Tooltip.vue.d.ts +54 -0
  39. package/lib/components/virtualscroll/VirtualScroll.vue.d.ts +43 -0
  40. package/lib/composables/element.d.ts +2 -0
  41. package/lib/composables/useCachedUid.d.ts +13 -0
  42. package/lib/composables/useEventListener.d.ts +1 -0
  43. package/lib/composables/useInput.d.ts +36 -0
  44. package/lib/composables/useIsServer.d.ts +1 -0
  45. package/lib/composables/useMount.d.ts +13 -0
  46. package/lib/composables/usePopper.d.ts +31 -0
  47. package/lib/create.d.ts +9 -0
  48. package/lib/index.cjs.js +5 -0
  49. package/lib/index.d.ts +4 -0
  50. package/lib/index.es.js +3164 -0
  51. package/lib/install.d.ts +4 -0
  52. package/lib/nuxt.js +33 -0
  53. package/lib/nuxt.plugin.js +8 -0
  54. package/lib/style.css +1 -0
  55. package/lib/types/props.d.ts +2 -0
  56. package/package.json +75 -0
  57. package/src/__storybook__/argTypes.ts +42 -0
  58. package/src/__storybook__/createStory.ts +15 -0
  59. package/src/__storybook__/index.ts +2 -0
  60. package/src/components/avatar/Avatar.css +69 -0
  61. package/src/components/avatar/Avatar.vue +102 -0
  62. package/src/components/avatargroup/AvatarGroup.css +8 -0
  63. package/src/components/avatargroup/AvatarGroup.vue +32 -0
  64. package/src/components/breadcrumb/Breadcrumb.css +46 -0
  65. package/src/components/breadcrumb/Breadcrumb.vue +51 -0
  66. package/src/components/breadcrumbitem/BreadcrumbItem.vue +66 -0
  67. package/src/components/breadcrumbitem/index.ts +1 -0
  68. package/src/components/button/Button.css +378 -0
  69. package/src/components/button/Button.vue +108 -0
  70. package/src/components/buttongroup/ButtonGroup.vue +47 -0
  71. package/src/components/checkbox/Checkbox.css +185 -0
  72. package/src/components/checkbox/Checkbox.vue +140 -0
  73. package/src/components/dropdown/Dropdown.css +68 -0
  74. package/src/components/dropdown/Dropdown.vue +237 -0
  75. package/src/components/dropdown/DropdownItem.vue +118 -0
  76. package/src/components/dropdown/DropdownList.vue +20 -0
  77. package/src/components/dropdown/DropdownWrapper.vue +17 -0
  78. package/src/components/dropdown/hooks.ts +30 -0
  79. package/src/components/form/Form.vue +141 -0
  80. package/src/components/image/Image.vue +71 -0
  81. package/src/components/index.ts +27 -0
  82. package/src/components/input/Input.css +173 -0
  83. package/src/components/input/Input.vue +176 -0
  84. package/src/components/link/Link.css +147 -0
  85. package/src/components/link/Link.vue +54 -0
  86. package/src/components/messageprovider/Message.vue +76 -0
  87. package/src/components/messageprovider/MessageProvider.css +36 -0
  88. package/src/components/messageprovider/MessageProvider.vue +142 -0
  89. package/src/components/messageprovider/types.ts +39 -0
  90. package/src/components/messageprovider/useMessage.ts +12 -0
  91. package/src/components/select/Select.css +226 -0
  92. package/src/components/select/Select.vue +474 -0
  93. package/src/components/skeleton/Skeleton.css +73 -0
  94. package/src/components/skeleton/Skeleton.vue +47 -0
  95. package/src/components/tab/Tab.css +113 -0
  96. package/src/components/tab/Tab.vue +98 -0
  97. package/src/components/tab/TabGroup.vue +90 -0
  98. package/src/components/table/Table.css +45 -0
  99. package/src/components/table/Table.vue +108 -0
  100. package/src/components/table/TableBody.vue +13 -0
  101. package/src/components/table/TableCell.vue +13 -0
  102. package/src/components/table/TableHead.vue +22 -0
  103. package/src/components/table/TableHeadRow.vue +15 -0
  104. package/src/components/table/TableRow.vue +13 -0
  105. package/src/components/table/types/index.ts +5 -0
  106. package/src/components/tooltip/Tooltip.css +49 -0
  107. package/src/components/tooltip/Tooltip.vue +85 -0
  108. package/src/components/virtualscroll/VirtualScroll.css +13 -0
  109. package/src/components/virtualscroll/VirtualScroll.vue +61 -0
  110. package/src/composables/element.ts +13 -0
  111. package/src/composables/useCachedUid.ts +48 -0
  112. package/src/composables/useEventListener.ts +14 -0
  113. package/src/composables/useInput.ts +134 -0
  114. package/src/composables/useIsServer.ts +7 -0
  115. package/src/composables/useMount.ts +43 -0
  116. package/src/composables/usePopper.ts +194 -0
  117. package/src/create.ts +34 -0
  118. package/src/css/components.css +14 -0
  119. package/src/css/index.css +4 -0
  120. package/src/css/normalize.css +379 -0
  121. package/src/css/transitions.css +40 -0
  122. package/src/css/variables.css +80 -0
  123. package/src/exports/nuxt.js +33 -0
  124. package/src/exports/nuxt.plugin.js +8 -0
  125. package/src/index.ts +5 -0
  126. package/src/install.ts +8 -0
  127. package/src/shims-vue.d.ts +6 -0
  128. package/src/types/props.ts +3 -0
  129. package/volar.d.ts +16 -0
@@ -0,0 +1,226 @@
1
+ .m-select__container {
2
+ position: relative;
3
+ display: flex;
4
+ flex-direction: column;
5
+ width: 100%;
6
+ }
7
+
8
+ .m-select__container:focus {
9
+ outline: none;
10
+ }
11
+
12
+ .m-select {
13
+ --select-height: 40px;
14
+ --select-padding: 0 8px;
15
+ --select-font-size: 15px;
16
+ --select-border-radius: 4px;
17
+ --select-clear-size: 1.2rem;
18
+ --select-background-color: var(--white);
19
+ --select-border-color: var(--gray-200);
20
+
21
+ position: relative;
22
+ display: inline-flex;
23
+ align-items: center;
24
+ height: var(--select-height);
25
+ font-size: var(--select-font-size);
26
+ background-color: var(--select-background-color);
27
+ border: 1px solid var(--select-border-color);
28
+ box-shadow: none;
29
+ border-radius: var(--select-border-radius);
30
+ outline: 0;
31
+ cursor: text;
32
+ width: 100%;
33
+ transition: color .3s cubic-bezier(0.4, 0, 0.2, 1), background-color .3s cubic-bezier(0.4, 0, 0.2, 1), border-color .3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow .3s cubic-bezier(0.4, 0, 0.2, 1);
34
+ }
35
+
36
+ .m-select:not(.m-select--disabled):not(.m-select--focused):hover {
37
+ --select-border-color: var(--gray-400);
38
+ }
39
+
40
+ .m-select:not(.m-select--disabled):focus,
41
+ .m-select:not(.m-select--disabled):focus-within,
42
+ .m-select:not(.m-select--disabled).m-select--focused {
43
+ --select-border-color: var(--blue);
44
+ box-shadow: 0 0 0 2px var(--blue-outline);
45
+ outline: none;
46
+ }
47
+
48
+ .m-select.m-select--disabled {
49
+ --select-border-color: var(--gray-300);
50
+ --select-background-color: var(--gray-100);
51
+ opacity: 0.6;
52
+ cursor: not-allowed;
53
+ }
54
+
55
+ .m-select__clearable {
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ padding: 0 6px;
60
+ }
61
+
62
+ .m-select .m-select__clearable i.m-icon--close {
63
+ cursor: pointer;
64
+ width: var(--select-clear-size);
65
+ height: var(--select-clear-size);
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ border-radius: 100%;
70
+ color: var(--gray-500);
71
+ background-color: transparent;
72
+ transition: background-color .125s cubic-bezier(0.4, 0, 0.2, 1);
73
+ }
74
+
75
+ .m-select .m-select__clearable i.m-icon--close:hover {
76
+ color: var(--gray-500);
77
+ background-color: var(--gray-150);
78
+ }
79
+
80
+ .m-select i.m-icon--close::before {
81
+ --close-icon-size: 16px;
82
+ background-color: currentColor;
83
+ content: "";
84
+ width: var(--close-icon-size);
85
+ height: var(--close-icon-size);
86
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='w-5 h-5'%3E%3Cpath d='M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z' /%3E%3C/svg%3E%0A");
87
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor' class='w-5 h-5'%3E%3Cpath d='M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z' /%3E%3C/svg%3E%0A");
88
+ -webkit-mask-position: center center;
89
+ mask-position: center center;
90
+ -webkit-mask-repeat: no-repeat;
91
+ mask-repeat: no-repeat;
92
+ -webkit-mask-size: var(--close-icon-size) var(--close-icon-size);
93
+ mask-size: var(--close-icon-size) var(--close-icon-size);
94
+ }
95
+
96
+ .m-select--xs {
97
+ --select-height: 32px;
98
+ --select-padding: 0 8px;
99
+ --select-font-size: 13px;
100
+ --select-border-radius: 4px;
101
+ --select-clear-size: 1.2rem;
102
+ }
103
+
104
+ .m-select--sm {
105
+ --select-height: 36px;
106
+ --select-padding: 0 8px;
107
+ --select-font-size: 14px;
108
+ --select-border-radius: 4px;
109
+ --select-clear-size: 1.2rem;
110
+ }
111
+
112
+ .m-select--md {
113
+ --select-height: 40px;
114
+ --select-padding: 0 8px;
115
+ --select-font-size: 15px;
116
+ --select-border-radius: 4px;
117
+ --select-clear-size: 1.2rem;
118
+ }
119
+
120
+ .m-select--lg {
121
+ --select-height: 42px;
122
+ --select-padding: 0 8px;
123
+ --select-font-size: 16px;
124
+ --select-border-radius: 4px;
125
+ --select-clear-size: 1.2rem;
126
+ }
127
+
128
+ .m-select--xl {
129
+ --select-height: 46px;
130
+ --select-padding: 0 8px;
131
+ --select-font-size: 18px;
132
+ --select-border-radius: 6px;
133
+ --select-clear-size: 1.2rem;
134
+ }
135
+
136
+ .m-select__label {
137
+ margin-bottom: 5px;
138
+ color: var(--gray-600);
139
+ font-size: 15px;
140
+ font-weight: 600;
141
+ }
142
+
143
+ .m-select__input {
144
+ padding: var(--select-padding);
145
+ font-size: 15px;
146
+ color: var(--gray-600);
147
+ }
148
+
149
+ .m-select__help {
150
+ margin-top: 4px;
151
+ color: var(--gray-500);
152
+ font-size: 15px;
153
+ font-weight: 400;
154
+ }
155
+
156
+ .m-select__error {
157
+ margin: 4px 0 8px 0;
158
+ color: var(--red);
159
+ font-size: 15px;
160
+ font-weight: 400;
161
+ }
162
+
163
+ .m-form .m-select__form {
164
+ margin-bottom: 16px;
165
+ }
166
+
167
+ .m-form .m-select__form .m-select__error {
168
+ margin: 4px 0 0 0;
169
+ }
170
+
171
+ .m-select__dropdown {
172
+ z-index: 100;
173
+ max-height: 400px;
174
+ min-width: 200px;
175
+ overflow-y: auto;
176
+ border-radius: 4px;
177
+ background-color: var(--white);
178
+ box-shadow: 0 0 0 1px var(--gray-200), 0 4px 8px 0 rgba(0, 0, 0, 0.1);
179
+ display: flex;
180
+ flex-direction: column;
181
+ }
182
+
183
+ .m-select__dropdown[data-popper-placement^="bottom-"] {
184
+ margin-top: 5px!important;
185
+ }
186
+
187
+ .m-select__dropdown[data-popper-placement^="top-"] {
188
+ margin-bottom: 5px!important;
189
+ }
190
+
191
+ .m-select__search {
192
+ position: sticky;
193
+ top: 0;
194
+ z-index: 1;
195
+ background-color: var(--white);
196
+ padding: 6px 6px 0;
197
+ }
198
+
199
+ .m-select__options {
200
+ padding: 4px 0;
201
+ }
202
+
203
+ .m-select__option {
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: space-between;
207
+ padding: 8px 8px;
208
+ margin: 0 4px;
209
+ font-size: 15px;
210
+ color: var(--gray-600);
211
+ cursor: pointer;
212
+ transition: background-color .125s cubic-bezier(0.4, 0, 0.2, 1);
213
+ border-radius: 4px;
214
+ }
215
+
216
+ .m-select__option--selected {
217
+ color: var(--blue)!important;
218
+ }
219
+
220
+ .m-select__option--focused {
221
+ background-color: var(--gray-100);
222
+ }
223
+
224
+ .m-select__placeholder-text {
225
+ color: var(--gray-500);
226
+ }
@@ -0,0 +1,474 @@
1
+ <template>
2
+ <div :class="containerComputedClasses" ref="selectRef" tabindex="0">
3
+ <label v-if="label" :for="id" class="m-select__label" v-on="listeners" :id="`${id}-label`">
4
+ {{ label }}
5
+ </label>
6
+
7
+ <div :class="selectComputedClasses" :id="id" ref="container" @click="openSelect" :aria-disabled="disabled ? 'true' : 'false'" :aria-labelledby="`${id}-label`">
8
+ <div class="m-select__wrapper">
9
+ <div class="m-select__input">
10
+ <div class="m-select__input-wrapper">
11
+ <template v-if="multiple && Array.isArray(selected) && selected.length > 0">
12
+ <span
13
+ v-for="value in selected"
14
+ :key="value"
15
+ class="m-select__tag">
16
+ {{ getSelectedLabel(value) }}
17
+ </span>
18
+ </template>
19
+ <template v-else-if="!multiple && selected">
20
+ {{ getSelectedLabel(selected) }}
21
+ </template>
22
+ <template v-else>
23
+ <span class="m-select__placeholder-text">{{ placeholder }}</span>
24
+ </template>
25
+ </div>
26
+ </div>
27
+ <Teleport :to="'body'">
28
+ <Transition :name="transitionName">
29
+ <div class="m-select__dropdown" v-if="open" ref="popper" :style="computedConsistentWidth" :aria-hidden="open ? 'false' : 'true'">
30
+ <div v-if="searchable" class="m-select__search">
31
+ <MInput v-model="search" placeholder="Search" size="sm" ref="searchRef" />
32
+ </div>
33
+ <div class="m-select__options">
34
+ <div v-for="option, index in computedOptions" :key="index" ref="optionsRef"
35
+ class="m-select__option" :class="{
36
+ 'm-select__option--selected': option.active,
37
+ 'm-select__option--focused': selectedIndex === index
38
+ }"
39
+ @click="option.onClick"
40
+ @mouseover="selectedIndex = index">
41
+ {{ option.label }}
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </Transition>
46
+ </Teleport>
47
+ </div>
48
+ </div>
49
+ <p v-if="inputError" class="m-select__error">
50
+ {{ inputError }}
51
+ </p>
52
+ </div>
53
+ </template>
54
+
55
+ <script lang="ts">
56
+ import { useInput } from "@/composables/useInput";
57
+ import { usePopper, type Placement } from "@/composables/usePopper";
58
+ import type { Size } from "@/types/props";
59
+ import { onClickOutside, useEventListener } from '@vueuse/core';
60
+ import {
61
+ computed,
62
+ defineComponent,
63
+ nextTick,
64
+ ref,
65
+ watch,
66
+ watchEffect,
67
+ type PropType
68
+ } from "vue";
69
+ import { MInput } from "..";
70
+
71
+ export default defineComponent({
72
+ name: 'MSelect',
73
+ emits: useInput.emits(),
74
+ props: {
75
+ ...useInput.props(),
76
+ label: {
77
+ type: String
78
+ },
79
+ placeholder: {
80
+ type: String,
81
+ default: 'Select an option'
82
+ },
83
+ options: {
84
+ type: Array as PropType<Array<any>>,
85
+ required: true
86
+ },
87
+ searchable: {
88
+ type: Boolean,
89
+ default: false
90
+ },
91
+ multiple: {
92
+ type: Boolean,
93
+ default: false
94
+ },
95
+ size: {
96
+ type: String as PropType<Size>,
97
+ default: 'md',
98
+ },
99
+ help: {
100
+ type: String,
101
+ default: ''
102
+ },
103
+ optionValue: {
104
+ type: String,
105
+ default: 'value'
106
+ },
107
+ optionLabel: {
108
+ type: String,
109
+ default: 'label'
110
+ },
111
+ transitionName: {
112
+ type: String,
113
+ default: 'fade'
114
+ },
115
+ consistentWidth: {
116
+ type: Boolean,
117
+ default: true
118
+ },
119
+ disabled: {
120
+ type: Boolean,
121
+ default: false
122
+ },
123
+ loading: {
124
+ type: Boolean,
125
+ default: false
126
+ },
127
+ },
128
+ setup(props, { emit }) {
129
+ const container = ref<HTMLDivElement>();
130
+ const wrapper = ref<HTMLDivElement>();
131
+ const optionsRef = ref<HTMLDivElement[]>();
132
+ const searchRef = ref<HTMLDivElement>();
133
+ const selectRef = ref<HTMLDivElement>();
134
+ const selectedIndex = ref<number | undefined>(0);
135
+ const open = ref<boolean>(false);
136
+ const search = ref<string>('');
137
+ const placement = ref<Placement>("bottom-start");
138
+ const transfer = ref(false);
139
+ const width = ref<number>(0);
140
+ const keyboardNavigation = ref<boolean>(false);
141
+
142
+ const focus = () => { };
143
+
144
+ // TODO: Add debounce on keyboard movement (hover flicks it back up)
145
+ const {
146
+ id,
147
+ insideForm,
148
+ listeners,
149
+ formError,
150
+ formDisabled,
151
+ formLoading
152
+ } = useInput(props, { focus, emit, withListeners: false });
153
+
154
+ const { popper } = usePopper({
155
+ placement,
156
+ transfer,
157
+ reference: container,
158
+ wrapper
159
+ });
160
+
161
+ const disabled = computed(() => formDisabled || props.disabled);
162
+ const loading = computed(() => formLoading || props.loading);
163
+
164
+ const selected = computed<any | any[]>({
165
+ get() {
166
+ if (props.multiple) {
167
+ if (!props.modelValue) {
168
+ return [];
169
+ }
170
+
171
+ if (Array.isArray(props.modelValue)) {
172
+ return props.modelValue;
173
+ }
174
+
175
+ return [props.modelValue];
176
+ }
177
+
178
+ return props.modelValue || null;
179
+ },
180
+ set(value: string | number | []) {
181
+ emit('update:modelValue', value);
182
+ }
183
+ });
184
+
185
+ const computedConsistentWidth = computed(() => {
186
+ if (props.consistentWidth) {
187
+ return {
188
+ width: width.value + 'px'
189
+ }
190
+ }
191
+
192
+ return {};
193
+ });
194
+
195
+ const computedOptions = computed(() => {
196
+ if (!props.options || props.options.length == 0) {
197
+ return [];
198
+ }
199
+
200
+ // Filter options by search
201
+ if (props.searchable && search.value) {
202
+ return props.options.filter((option) => {
203
+ return option[props.optionLabel].toLowerCase().includes(search.value.toLowerCase());
204
+ }).map((option) => {
205
+ if (option[props.optionValue] === undefined) {
206
+ throw new Error('Option value is undefined');
207
+ }
208
+
209
+ if (option[props.optionLabel] === undefined) {
210
+ throw new Error('Option label is undefined');
211
+ }
212
+
213
+ let value = option[props.optionValue];
214
+ let active = false;
215
+ if (props.multiple && Array.isArray(selected.value)) {
216
+ active = selected.value.includes(value);
217
+ } else {
218
+ active = selected.value === value;
219
+ }
220
+
221
+ return {
222
+ value: value,
223
+ label: option[props.optionLabel],
224
+ active: active,
225
+ onClick: () => handleOptionClick(value)
226
+ }
227
+ })
228
+ }
229
+
230
+ return props.options.map((option) => {
231
+ if (option[props.optionValue] === undefined) {
232
+ throw new Error('Option value is undefined');
233
+ }
234
+
235
+ if (option[props.optionLabel] === undefined) {
236
+ throw new Error('Option label is undefined');
237
+ }
238
+
239
+ let value = option[props.optionValue];
240
+ let active = false;
241
+ if (props.multiple && Array.isArray(selected.value)) {
242
+ active = selected.value.includes(value);
243
+ } else {
244
+ active = selected.value === value;
245
+ }
246
+
247
+ return {
248
+ value: value,
249
+ label: option[props.optionLabel],
250
+ active: active,
251
+ onClick: () => handleOptionClick(value)
252
+ }
253
+ })
254
+ });
255
+
256
+ function openSelect() {
257
+ if (disabled.value || loading.value) {
258
+ return;
259
+ }
260
+
261
+ open.value = !open.value;
262
+ }
263
+
264
+ function getSelectedIndex(start: number | undefined, direction = 'down') {
265
+ if (typeof start === 'undefined') {
266
+ start = direction === 'down' ? -1 : 1;
267
+ }
268
+
269
+ if (direction === 'down') {
270
+ let next = start + 1;
271
+
272
+ if (next > computedOptions.value.length - 1) {
273
+ next = 0;
274
+ }
275
+
276
+ selectedIndex.value = next;
277
+ } else {
278
+ let next = start - 1;
279
+
280
+ if (next < 0) {
281
+ next = computedOptions.value.length - 1;
282
+ }
283
+
284
+ selectedIndex.value = next;
285
+ }
286
+ }
287
+
288
+ function handleOptionClick(value: string | number) {
289
+ const option = computedOptions.value.find((item) => item.value === value);
290
+ if (!option) {
291
+ return;
292
+ }
293
+
294
+ if (props.multiple) {
295
+ if (Array.isArray(selected.value)) {
296
+ const index = selected.value.indexOf(value);
297
+
298
+ if (index !== -1) {
299
+ selected.value.splice(index, 1);
300
+ } else {
301
+ selected.value.push(value);
302
+ emit('update:modelValue', selected.value);
303
+ }
304
+ } else {
305
+ selected.value = [value];
306
+ }
307
+ } else {
308
+ selected.value = value;
309
+ open.value = false;
310
+ }
311
+ }
312
+
313
+ function handleKeyDown(e: Event) {
314
+ if (computedOptions.value.length === 0) {
315
+ return;
316
+ }
317
+
318
+ if (props.disabled) {
319
+ return;
320
+ }
321
+
322
+ keyboardNavigation.value = true;
323
+
324
+ const event: KeyboardEvent = e as KeyboardEvent;
325
+
326
+ switch (event.code) {
327
+ case 'ArrowDown':
328
+ e.preventDefault();
329
+ if (!open.value) {
330
+ open.value = true;
331
+ return;
332
+ }
333
+
334
+ getSelectedIndex(selectedIndex.value, 'down');
335
+ break;
336
+ case 'ArrowUp':
337
+ e.preventDefault();
338
+ if (!open.value) {
339
+ return;
340
+ }
341
+
342
+ getSelectedIndex(selectedIndex.value, 'up');
343
+ break;
344
+ case 'Enter':
345
+ e.preventDefault();
346
+ e.stopPropagation();
347
+
348
+ if (!open.value) {
349
+ open.value = true;
350
+ return;
351
+ }
352
+
353
+ if (typeof selectedIndex.value !== 'undefined') {
354
+ handleOptionClick(computedOptions.value[selectedIndex.value].value);
355
+ if (!props.multiple) {
356
+ open.value = false;
357
+ }
358
+ }
359
+ break;
360
+ case 'Escape':
361
+ e.preventDefault();
362
+ e.stopPropagation();
363
+
364
+ if (!open.value) {
365
+ return;
366
+ }
367
+
368
+ open.value = false;
369
+ break;
370
+ }
371
+ }
372
+
373
+ const transitionName = computed(() => {
374
+ return `m-transition__${props.transitionName}`;
375
+ });
376
+
377
+ const containerComputedClasses = computed(() => {
378
+ return [
379
+ 'm-select__container',
380
+ {
381
+ 'm-form__input': insideForm
382
+ }
383
+ ]
384
+ });
385
+
386
+ const selectComputedClasses = computed(() => {
387
+ return [
388
+ 'm-select',
389
+ {
390
+ [`m-select--${props.size}`]: props.size,
391
+ 'm-select--disabled': disabled.value || loading.value,
392
+ 'm-select--focused': open.value,
393
+ 'm-select--multiple': props.multiple,
394
+ 'm-select--error': formError.value
395
+ }
396
+ ]
397
+ });
398
+
399
+ function getSelectedLabel(value: string | number | []) {
400
+ const option = props.options.find((i) => i[props.optionValue] === value)
401
+ if (!option) {
402
+ return null;
403
+ }
404
+
405
+ return option[props.optionLabel];
406
+ }
407
+
408
+ useEventListener(selectRef, 'keydown', handleKeyDown)
409
+ useEventListener(searchRef, 'keydown', handleKeyDown)
410
+
411
+ onClickOutside(container, () => {
412
+ open.value = false;
413
+ }, { ignore: [searchRef, popper] });
414
+
415
+ watchEffect(() => {
416
+ if (container.value && open.value) {
417
+ width.value = container.value.offsetWidth;
418
+ }
419
+ });
420
+
421
+ watch(selectedIndex, (index: number | undefined) => {
422
+ if (!keyboardNavigation.value) {
423
+ return;
424
+ }
425
+ if (typeof index !== 'undefined' && optionsRef.value) {
426
+ optionsRef.value[index].scrollIntoView({ block: 'nearest', inline: 'nearest' });
427
+ }
428
+ })
429
+
430
+ watch(open, (value: boolean) => {
431
+ nextTick(() => {
432
+ if (value && optionsRef.value) {
433
+ const index = computedOptions.value.findIndex((i) => i.value === selected.value);
434
+ if (index !== -1) {
435
+ optionsRef.value[index].scrollIntoView({ block: 'center', inline: 'nearest' });
436
+ selectedIndex.value = index;
437
+ }
438
+ }
439
+
440
+ keyboardNavigation.value = false;
441
+ });
442
+ });
443
+
444
+ const inputError = computed(() => {
445
+ return formError.value || props.error;
446
+ });
447
+
448
+ return {
449
+ container,
450
+ optionsRef,
451
+ searchRef,
452
+ selectRef,
453
+ popper,
454
+
455
+ id,
456
+ open,
457
+ selected,
458
+ computedOptions,
459
+ selectedIndex,
460
+ search,
461
+
462
+ containerComputedClasses,
463
+ selectComputedClasses,
464
+ computedConsistentWidth,
465
+
466
+ getSelectedLabel,
467
+ openSelect,
468
+ inputError,
469
+ transitionName,
470
+ listeners
471
+ }
472
+ }
473
+ });
474
+ </script>