AmSelect.vue
894 lines
| 1 | <template> |
| 2 | <!-- Select --> |
| 3 | <div ref="amSelectWrapper" class="am-select-wrapper" :class="props.parentClass"> |
| 4 | <el-select |
| 5 | :id="id" |
| 6 | ref="amSelect" |
| 7 | v-model="model" |
| 8 | class="am-select" |
| 9 | :class="[ |
| 10 | `am-select--${props.size}`, |
| 11 | { 'am-select--disabled': props.disabled }, |
| 12 | { 'am-select--suffix': props.suffixIcon }, |
| 13 | { 'am-select--prefix': props.prefixIcon }, |
| 14 | { 'am-select--multiple': props.multiple && !props.collapseTags }, |
| 15 | { 'am-rtl': isRtl }, |
| 16 | props.customClass, |
| 17 | ]" |
| 18 | :popper-class="`am-select-popper${popperClass ? ' ' + popperClass : popperClass}`" |
| 19 | :popper-options="props.popperOptions" |
| 20 | :multiple="props.multiple" |
| 21 | :disabled="props.disabled" |
| 22 | :value-key="props.valueKey" |
| 23 | :clearable="props.clearable" |
| 24 | :collapse-tags="props.collapseTags" |
| 25 | :multiple-limit="props.multipleLimit" |
| 26 | :name="props.name" |
| 27 | :autocomplete="props.autocomplete" |
| 28 | :placeholder="props.placeholder ? props.placeholder : amLabels.select" |
| 29 | :filterable="props.filterable" |
| 30 | :allow-create="props.allowCreate" |
| 31 | :filter-method="props.filterMethod" |
| 32 | :remote="props.remote" |
| 33 | :remote-method="props.remoteMethod" |
| 34 | :loading="props.loading" |
| 35 | :loading-text="props.loadingText" |
| 36 | :no-match-text="props.noMatchText" |
| 37 | :no-data-text="props.noDataText" |
| 38 | :collapse-tags-tooltip="props.collapseTagsTooltip" |
| 39 | :reserve-keyword="props.reserveKeyword" |
| 40 | :default-first-option="props.defaultFirstOption" |
| 41 | :teleported="props.teleported" |
| 42 | :automatic-dropdown="props.automaticDropdown" |
| 43 | :clear-icon="props.clearIcon" |
| 44 | :fit-input-width="props.fitInputWidth" |
| 45 | :suffix-icon="props.suffixIcon" |
| 46 | :tag-type="props.tagType" |
| 47 | :prefix-icon="props.prefixIcon" |
| 48 | :aria-label="props.ariaLabel" |
| 49 | :offset="props.offset" |
| 50 | :show-arrow="props.showArrow" |
| 51 | :placement="props.placement" |
| 52 | :fallback-placements="props.fallbackPlacements" |
| 53 | :validate-event="props.validateEvent" |
| 54 | :append-to="props.appendTo" |
| 55 | :persistent="props.persistent" |
| 56 | :tabindex="props.tabindex" |
| 57 | :empty-values="props.emptyValues" |
| 58 | :value-on-clear="props.valueOnClear" |
| 59 | :effect="props.tagEffect" |
| 60 | :max-collapse-tags="props.maxCollapseTags" |
| 61 | :remote-show-suffix="props.remoteShowSuffix" |
| 62 | :tag-effect="props.tagEffect" |
| 63 | :style="{ ...cssVars }" |
| 64 | @change="(val) => $emit('change', val)" |
| 65 | @visible-change="visibleChange" |
| 66 | @remove-tag="(eventValue) => $emit('remove-tag', eventValue)" |
| 67 | @clear="handleClear" |
| 68 | @blur="handleBlur" |
| 69 | @focus="(e) => $emit('focus', e)" |
| 70 | @click="(e) => $emit('click', e)" |
| 71 | @keydown="handleKeydown" |
| 72 | > |
| 73 | <template v-if="prefixIcon" #prefix> |
| 74 | <component :is="prefixIcon" v-if="typeof prefixIcon === 'object'" /> |
| 75 | <span |
| 76 | v-if="typeof prefixIcon === 'string'" |
| 77 | :class="`am-icon-${prefixIcon}`" |
| 78 | :style="`color: ${prefixIconColor}`" |
| 79 | /> |
| 80 | </template> |
| 81 | <slot /> |
| 82 | <template v-if="$slots.footer" #footer> |
| 83 | <slot name="footer" /> |
| 84 | </template> |
| 85 | </el-select> |
| 86 | </div> |
| 87 | <!-- /Select --> |
| 88 | </template> |
| 89 | |
| 90 | <script setup> |
| 91 | // * Import form Vue |
| 92 | import { ref, toRefs, computed, inject, onMounted, onBeforeUnmount } from 'vue' |
| 93 | |
| 94 | // * Components |
| 95 | import IconComponent from '../icons/IconComponent.vue' |
| 96 | |
| 97 | // * Composable |
| 98 | import { useColorTransparency } from '../../../assets/js/common/colorManipulation' |
| 99 | |
| 100 | // * Labels |
| 101 | let amLabels = inject('labels') |
| 102 | |
| 103 | /** |
| 104 | * Component Props |
| 105 | */ |
| 106 | const props = defineProps({ |
| 107 | id: { |
| 108 | type: String, |
| 109 | }, |
| 110 | modelValue: { |
| 111 | type: [String, Array, Object, Number, null], |
| 112 | }, |
| 113 | multiple: { |
| 114 | // * whether multiple-select is activated |
| 115 | type: Boolean, |
| 116 | default: false, |
| 117 | }, |
| 118 | disabled: { |
| 119 | type: Boolean, |
| 120 | default: false, |
| 121 | }, |
| 122 | valueKey: { |
| 123 | // * unique identity key name for value, required when value is an object |
| 124 | type: String, |
| 125 | default: 'value', |
| 126 | }, |
| 127 | size: { |
| 128 | // default / medium / small / mini / micro |
| 129 | type: String, |
| 130 | default: 'default', |
| 131 | validator(value) { |
| 132 | return ['default', 'medium', 'small', 'mini', 'micro'].includes(value) |
| 133 | }, |
| 134 | }, |
| 135 | clearable: { |
| 136 | // * whether select can be cleared |
| 137 | type: Boolean, |
| 138 | default: false, |
| 139 | }, |
| 140 | collapseTags: { |
| 141 | // * whether to collapse tags to a text when multiple selecting |
| 142 | type: Boolean, |
| 143 | default: false, |
| 144 | }, |
| 145 | collapseTagsTooltip: { |
| 146 | // * whether show all selected tags when mouse hover text of collapse-tags. To use this, collapse-tags must be true |
| 147 | type: Boolean, |
| 148 | default: false, |
| 149 | }, |
| 150 | multipleLimit: { |
| 151 | // * maximum number of options user can select when multiple is true. No limit when set to 0 |
| 152 | type: Number, |
| 153 | default: 0, |
| 154 | }, |
| 155 | name: { |
| 156 | // * the name attribute of select input |
| 157 | type: String, |
| 158 | }, |
| 159 | autocomplete: { |
| 160 | // * the autocomplete attribute of select input |
| 161 | type: String, |
| 162 | default: 'off', |
| 163 | }, |
| 164 | placeholder: { |
| 165 | // * placeholder, default is '' |
| 166 | type: String, |
| 167 | default: '', |
| 168 | }, |
| 169 | filterable: { |
| 170 | // * whether Select is filterable |
| 171 | type: Boolean, |
| 172 | default: false, |
| 173 | }, |
| 174 | allowCreate: { |
| 175 | // * whether creating new items is allowed. To use this, filterable must be true |
| 176 | type: Boolean, |
| 177 | default: false, |
| 178 | }, |
| 179 | filterMethod: { |
| 180 | // * custom filter method |
| 181 | type: Function, |
| 182 | }, |
| 183 | remote: { |
| 184 | // * whether options are loaded from server |
| 185 | type: Boolean, |
| 186 | default: false, |
| 187 | }, |
| 188 | remoteMethod: { |
| 189 | // * custom remote search method |
| 190 | type: Function, |
| 191 | }, |
| 192 | remoteShowSuffix: { |
| 193 | // * in remote search method show suffix icon |
| 194 | type: Boolean, |
| 195 | default: false, |
| 196 | }, |
| 197 | loading: { |
| 198 | // * whether Select is loading data from server |
| 199 | type: Boolean, |
| 200 | default: false, |
| 201 | }, |
| 202 | loadingText: { |
| 203 | // * displayed text while loading data from server, default is 'Loading' |
| 204 | type: String, |
| 205 | default: 'Loading...', |
| 206 | }, |
| 207 | noMatchText: { |
| 208 | // * displayed text when no data matches the filtering query, you can also use slot empty, default is 'No matching data' |
| 209 | type: String, |
| 210 | default: 'No matching data', |
| 211 | }, |
| 212 | noDataText: { |
| 213 | // * displayed text when there is no options, you can also use slot empty, default is 'No data' |
| 214 | type: String, |
| 215 | default: 'No data', |
| 216 | }, |
| 217 | popperClass: { |
| 218 | // * custom class name for Select's dropdown |
| 219 | type: String, |
| 220 | default: '', |
| 221 | }, |
| 222 | reserveKeyword: { |
| 223 | // * when multiple and filterable is true, whether to reserve current keyword after selecting an option |
| 224 | type: Boolean, |
| 225 | default: true, |
| 226 | }, |
| 227 | defaultFirstOption: { |
| 228 | // * select first matching option on enter key. Use with filterable or remote |
| 229 | type: Boolean, |
| 230 | default: false, |
| 231 | }, |
| 232 | teleported: { |
| 233 | // * whether select dropdown is teleported, if true it will be teleported to where append-to sets |
| 234 | type: Boolean, |
| 235 | default: true, |
| 236 | }, |
| 237 | appendTo: { |
| 238 | // * which element the select dropdown appends to, default is body |
| 239 | type: String, |
| 240 | default: 'body', |
| 241 | }, |
| 242 | persistent: { |
| 243 | // * when select dropdown is inactive and persistent is false, select dropdown will be destroyed |
| 244 | type: Boolean, |
| 245 | default: true, |
| 246 | }, |
| 247 | automaticDropdown: { |
| 248 | // * for non-filterable Select, this prop decides if the option menu pops up when the input is focused |
| 249 | type: Boolean, |
| 250 | default: false, |
| 251 | }, |
| 252 | clearIcon: { |
| 253 | type: [String, Object], |
| 254 | default: () => { |
| 255 | return { |
| 256 | components: { IconComponent }, |
| 257 | template: `<IconComponent icon="close"></IconComponent>`, |
| 258 | } |
| 259 | }, |
| 260 | }, |
| 261 | fitInputWidth: { |
| 262 | // * whether the width of the dropdown is the same as the input |
| 263 | type: Boolean, |
| 264 | default: false, |
| 265 | }, |
| 266 | suffixIcon: { |
| 267 | type: [String, Object], |
| 268 | default: () => { |
| 269 | return { |
| 270 | components: { IconComponent }, |
| 271 | template: `<IconComponent icon="arrow-down"></IconComponent>`, |
| 272 | } |
| 273 | }, |
| 274 | }, |
| 275 | tagType: { |
| 276 | // * tag type when multiple is true, default is 'info' |
| 277 | type: String, |
| 278 | default: 'info', |
| 279 | validator(value) { |
| 280 | return ['info', 'success', 'warning', 'danger', ''].includes(value) |
| 281 | }, |
| 282 | }, |
| 283 | tagEffect: { |
| 284 | type: String, |
| 285 | default: 'light', |
| 286 | validator(value) { |
| 287 | return ['light', 'dark', 'plain'].includes(value) |
| 288 | }, |
| 289 | }, |
| 290 | validateEvent: { |
| 291 | // * whether to trigger form validation |
| 292 | type: Boolean, |
| 293 | default: true, |
| 294 | }, |
| 295 | offset: { |
| 296 | // * offset of the dropdown |
| 297 | type: Number, |
| 298 | default: 12, |
| 299 | }, |
| 300 | showArrow: { |
| 301 | // * whether the dropdown has an arrow |
| 302 | type: Boolean, |
| 303 | default: false, |
| 304 | }, |
| 305 | placement: { |
| 306 | // * placement of the dropdown, default is bottom-start |
| 307 | type: String, |
| 308 | default: 'bottom-start', |
| 309 | validator(value) { |
| 310 | return [ |
| 311 | 'top', |
| 312 | 'top-start', |
| 313 | 'top-end', |
| 314 | 'bottom', |
| 315 | 'bottom-start', |
| 316 | 'bottom-end', |
| 317 | 'left', |
| 318 | 'left-start', |
| 319 | 'left-end', |
| 320 | 'right', |
| 321 | 'right-start', |
| 322 | 'right-end', |
| 323 | ].includes(value) |
| 324 | }, |
| 325 | }, |
| 326 | fallbackPlacements: { |
| 327 | // * list of possible positions for dropdown popper.js |
| 328 | type: Array, |
| 329 | default: () => { |
| 330 | return ['bottom-start', 'top-start', 'right', 'left'] |
| 331 | }, |
| 332 | }, |
| 333 | maxCollapseTags: { |
| 334 | // * the max tags number to be shown. To use this, collapse-tags must be true |
| 335 | type: Number, |
| 336 | default: 1, |
| 337 | }, |
| 338 | popperOptions: { |
| 339 | // * popper.js parameters |
| 340 | type: Object, |
| 341 | default: () => { |
| 342 | return {} |
| 343 | // * eg. |
| 344 | // return { |
| 345 | // gpuAcceleration: false, |
| 346 | // boundariesElement: 'body', |
| 347 | // modifiers: { |
| 348 | // computeStyle: { |
| 349 | // gpuAcceleration: false, |
| 350 | // }, |
| 351 | // flip: { |
| 352 | // behavior: ['top', 'bottom', 'left', 'right'], |
| 353 | // }, |
| 354 | // preventOverflow: { |
| 355 | // boundariesElement: 'viewport', |
| 356 | // }, |
| 357 | // }, |
| 358 | // } |
| 359 | }, |
| 360 | }, |
| 361 | ariaLabel: { |
| 362 | // * aria-label for select input |
| 363 | type: String, |
| 364 | default: 'dropdown', |
| 365 | }, |
| 366 | emptyValues: { |
| 367 | // * empty values for select input |
| 368 | type: Array, |
| 369 | }, |
| 370 | valueOnClear: { |
| 371 | // * value when select input is cleared |
| 372 | type: [String, Number, Object, Function], |
| 373 | }, |
| 374 | tabindex: { |
| 375 | // * tabindex for select input |
| 376 | type: [String, Number], |
| 377 | }, |
| 378 | customClass: { |
| 379 | type: String, |
| 380 | default: '', |
| 381 | }, |
| 382 | parentClass: { |
| 383 | type: String, |
| 384 | default: '', |
| 385 | }, |
| 386 | prefixIcon: { |
| 387 | type: [String, Object, Function], |
| 388 | }, |
| 389 | prefixIconColor: { |
| 390 | type: [String, Object, Function], |
| 391 | default: '', |
| 392 | }, |
| 393 | dropdownArrowVisibility: { |
| 394 | type: Boolean, |
| 395 | default: false, |
| 396 | }, |
| 397 | }) |
| 398 | |
| 399 | /** |
| 400 | * Component Emits |
| 401 | * */ |
| 402 | const emits = defineEmits([ |
| 403 | 'change', |
| 404 | 'visible-change', |
| 405 | 'remove-tag', |
| 406 | 'clear', |
| 407 | 'blur', |
| 408 | 'focus', |
| 409 | 'update:modelValue', |
| 410 | 'click', |
| 411 | ]) |
| 412 | |
| 413 | /** |
| 414 | * Component model |
| 415 | */ |
| 416 | let { modelValue } = toRefs(props) |
| 417 | let model = computed({ |
| 418 | get: () => modelValue.value, |
| 419 | set: (val) => { |
| 420 | emits('update:modelValue', val) |
| 421 | }, |
| 422 | }) |
| 423 | |
| 424 | /** |
| 425 | * Component reference |
| 426 | */ |
| 427 | const amSelect = ref(null) |
| 428 | const amSelectWrapper = ref(null) |
| 429 | const clearHandledKey = '__amSelectClearHandled' |
| 430 | |
| 431 | // * Component text orientation |
| 432 | let isRtl = computed(() => { |
| 433 | if (document) { |
| 434 | return document.documentElement.dir === 'rtl' |
| 435 | } |
| 436 | |
| 437 | return false |
| 438 | }) |
| 439 | |
| 440 | // * Font Vars |
| 441 | let amFonts = inject( |
| 442 | 'amFonts', |
| 443 | ref({ |
| 444 | fontFamily: 'Amelia Roboto, sans-serif', |
| 445 | fontUrl: '', |
| 446 | customFontFamily: '', |
| 447 | fontFormat: '', |
| 448 | customFontSelected: false, |
| 449 | }), |
| 450 | ) |
| 451 | |
| 452 | // * Color Vars |
| 453 | let amColors = inject( |
| 454 | 'amColors', |
| 455 | ref({ |
| 456 | colorPrimary: '#1246D6', |
| 457 | colorSuccess: '#019719', |
| 458 | colorError: '#B4190F', |
| 459 | colorWarning: '#CCA20C', |
| 460 | colorMainBgr: '#FFFFFF', |
| 461 | colorMainHeadingText: '#33434C', |
| 462 | colorMainText: '#1A2C37', |
| 463 | colorSbBgr: '#17295A', |
| 464 | colorSbText: '#FFFFFF', |
| 465 | colorInpBgr: '#FFFFFF', |
| 466 | colorInpBorder: '#D1D5D7', |
| 467 | colorInpText: '#1A2C37', |
| 468 | colorInpPlaceHolder: '#808A90', |
| 469 | colorDropBgr: '#FFFFFF', |
| 470 | colorDropBorder: '#D1D5D7', |
| 471 | colorDropText: '#0E1920', |
| 472 | colorBtnPrim: '#265CF2', |
| 473 | colorBtnPrimText: '#FFFFFF', |
| 474 | colorBtnSec: '#1A2C37', |
| 475 | colorBtnSecText: '#FFFFFF', |
| 476 | }), |
| 477 | ) |
| 478 | |
| 479 | // * Css Variables |
| 480 | let cssVars = computed(() => { |
| 481 | return { |
| 482 | '--am-c-select-bgr': amColors.value.colorInpBgr, |
| 483 | '--am-c-select-border': amColors.value.colorInpBorder, |
| 484 | '--am-c-select-text': amColors.value.colorInpText, |
| 485 | '--am-c-select-placeholder': amColors.value.colorInpPlaceHolder, |
| 486 | '--am-c-select-shadow': useColorTransparency(amColors.value.colorInpText, 0.05), |
| 487 | '--am-c-select-text-op60': useColorTransparency(amColors.value.colorInpText, 0.6), |
| 488 | '--am-c-select-text-op50': useColorTransparency(amColors.value.colorInpText, 0.5), |
| 489 | '--am-c-select-text-op40': useColorTransparency(amColors.value.colorInpText, 0.4), |
| 490 | '--am-c-select-text-op03': useColorTransparency(amColors.value.colorInpText, 0.03), |
| 491 | '--am-c-select-text-op06': useColorTransparency(amColors.value.colorInpText, 0.06), |
| 492 | '--am-font-family': amFonts.value.fontFamily, |
| 493 | } |
| 494 | }) |
| 495 | |
| 496 | function getClearValue() { |
| 497 | if (typeof props.valueOnClear === 'function') { |
| 498 | return props.valueOnClear() |
| 499 | } |
| 500 | |
| 501 | if (props.valueOnClear !== undefined) { |
| 502 | return props.valueOnClear |
| 503 | } |
| 504 | |
| 505 | return props.multiple ? [] : null |
| 506 | } |
| 507 | |
| 508 | function hasSelectedValue() { |
| 509 | if (props.multiple) { |
| 510 | return Array.isArray(model.value) && model.value.length > 0 |
| 511 | } |
| 512 | |
| 513 | const emptyValues = props.emptyValues ?? [undefined, null, ''] |
| 514 | return !emptyValues.includes(model.value) |
| 515 | } |
| 516 | |
| 517 | function handleClear() { |
| 518 | emits('clear') |
| 519 | } |
| 520 | |
| 521 | function canClearFromKeyboard(event) { |
| 522 | if (!props.clearable || props.disabled || !hasSelectedValue()) { |
| 523 | return false |
| 524 | } |
| 525 | |
| 526 | if (!['Backspace', 'Delete'].includes(event.key)) { |
| 527 | return false |
| 528 | } |
| 529 | |
| 530 | // Keep Backspace usable for search input editing in filterable mode. |
| 531 | return !( |
| 532 | props.filterable && |
| 533 | event.target && |
| 534 | typeof event.target.value === 'string' && |
| 535 | event.target.value.length |
| 536 | ) |
| 537 | } |
| 538 | |
| 539 | function clearFromKeyboard(event) { |
| 540 | if (event && event[clearHandledKey]) { |
| 541 | return |
| 542 | } |
| 543 | |
| 544 | if (!canClearFromKeyboard(event)) { |
| 545 | return |
| 546 | } |
| 547 | |
| 548 | if (event) { |
| 549 | event[clearHandledKey] = true |
| 550 | } |
| 551 | |
| 552 | const clearedValue = getClearValue() |
| 553 | model.value = clearedValue |
| 554 | emits('change', clearedValue) |
| 555 | emits('clear') |
| 556 | } |
| 557 | |
| 558 | function handleKeydown(event) { |
| 559 | clearFromKeyboard(event) |
| 560 | } |
| 561 | |
| 562 | function handleWrapperKeydownCapture(event) { |
| 563 | clearFromKeyboard(event) |
| 564 | } |
| 565 | |
| 566 | function handleBlur(event) { |
| 567 | emits('blur', event) |
| 568 | } |
| 569 | |
| 570 | function visibleChange(eventValue) { |
| 571 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 572 | '--am-c-success', |
| 573 | amColors.value.colorSuccess, |
| 574 | ) |
| 575 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 576 | '--am-c-error', |
| 577 | amColors.value.colorError, |
| 578 | ) |
| 579 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 580 | '--am-c-warning', |
| 581 | amColors.value.colorWarning, |
| 582 | ) |
| 583 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 584 | '--am-c-option-bgr', |
| 585 | amColors.value.colorDropBgr, |
| 586 | ) |
| 587 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 588 | '--am-c-option-border', |
| 589 | amColors.value.colorDropBorder, |
| 590 | ) |
| 591 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 592 | '--am-c-option-text', |
| 593 | amColors.value.colorDropText, |
| 594 | ) |
| 595 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 596 | '--am-c-option-text-op65', |
| 597 | useColorTransparency(amColors.value.colorDropText, 0.65), |
| 598 | ) |
| 599 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 600 | '--am-c-option-text-op15', |
| 601 | useColorTransparency(amColors.value.colorDropText, 0.15), |
| 602 | ) |
| 603 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 604 | '--am-c-option-hover', |
| 605 | useColorTransparency(amColors.value.colorDropText, 0.1), |
| 606 | ) |
| 607 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 608 | '--am-c-option-selected', |
| 609 | amColors.value.colorPrimary, |
| 610 | ) |
| 611 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 612 | '--am-c-option-selected-op10', |
| 613 | useColorTransparency(amColors.value.colorPrimary, 0.1), |
| 614 | ) |
| 615 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 616 | '--am-c-option-img-bgr', |
| 617 | amColors.value.colorSuccess, |
| 618 | ) |
| 619 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 620 | '--am-c-option-img-text', |
| 621 | amColors.value.colorMainBgr, |
| 622 | ) |
| 623 | amSelect.value.tooltipRef.popperRef.contentRef.style.setProperty( |
| 624 | '--am-font-family', |
| 625 | amFonts.value.fontFamily, |
| 626 | ) |
| 627 | emits('visible-change', eventValue) |
| 628 | } |
| 629 | |
| 630 | onMounted(() => { |
| 631 | // check if wrapper element exist if element plus is updated |
| 632 | const wrapper = amSelectWrapper.value?.querySelector('.el-select__wrapper') |
| 633 | |
| 634 | if (wrapper) { |
| 635 | wrapper.addEventListener('keydown', handleWrapperKeydownCapture, true) |
| 636 | } |
| 637 | }) |
| 638 | |
| 639 | onBeforeUnmount(() => { |
| 640 | // check if wrapper element exist if element plus is updated |
| 641 | const wrapper = amSelectWrapper.value?.querySelector('.el-select__wrapper') |
| 642 | |
| 643 | if (wrapper) { |
| 644 | wrapper.removeEventListener('keydown', handleWrapperKeydownCapture, true) |
| 645 | } |
| 646 | }) |
| 647 | </script> |
| 648 | |
| 649 | <script> |
| 650 | export default { |
| 651 | inheritAttrs: false, |
| 652 | } |
| 653 | </script> |
| 654 | |
| 655 | <style lang="scss"> |
| 656 | @mixin am-select-block { |
| 657 | .am-select { |
| 658 | // -c- color |
| 659 | // -rad- border radius |
| 660 | // -h- height |
| 661 | // -fs- font size |
| 662 | // -padd- padding |
| 663 | // -bgr background |
| 664 | --am-c-select-bgr: var(--am-c-inp-bgr); |
| 665 | --am-c-select-border: var(--am-c-inp-border); |
| 666 | --am-c-select-text: var(--am-c-inp-text); |
| 667 | --am-c-select-placeholder: var(--am-color-input-placeholder); |
| 668 | --am-rad-select: var(--am-rad-inp); |
| 669 | --am-fs-select: var(--am-fs-inp); |
| 670 | --am-h-select: var(--am-h-inp); |
| 671 | --am-padd-select: 0 12px; |
| 672 | width: 100%; |
| 673 | box-shadow: 0 2px 2px var(--am-c-select-shadow); |
| 674 | border-radius: var(--am-rad-select); |
| 675 | |
| 676 | // Select Wrapper |
| 677 | &-wrapper { |
| 678 | width: 100%; |
| 679 | } |
| 680 | |
| 681 | // Size - default / medium / small / mini / micro |
| 682 | &--default { |
| 683 | --am-h-select: 40px; |
| 684 | } |
| 685 | &--medium { |
| 686 | --am-h-select: 36px; |
| 687 | } |
| 688 | &--small { |
| 689 | --am-h-select: 32px; |
| 690 | } |
| 691 | &--mini { |
| 692 | --am-h-select: 28px; |
| 693 | } |
| 694 | &--micro { |
| 695 | --am-h-select: 24px; |
| 696 | } |
| 697 | |
| 698 | // Padding - depends on icon appearance |
| 699 | &--prefix { |
| 700 | --am-padd-select: 0 12px 0 8px; |
| 701 | &.am-select--suffix { |
| 702 | --am-padd-select: 0 8px; |
| 703 | } |
| 704 | } |
| 705 | |
| 706 | &--suffix { |
| 707 | --am-padd-select: 0 8px 0 12px; |
| 708 | &.am-select--prefix { |
| 709 | --am-padd-select: 0 8px; |
| 710 | } |
| 711 | } |
| 712 | |
| 713 | // Disabled |
| 714 | &--disabled { |
| 715 | --am-c-select-bgr: var(--am-c-select-text-op03) !important; |
| 716 | --am-c-select-text: var(--am-c-select-text-op60) !important; |
| 717 | } |
| 718 | |
| 719 | // Multiple select - collapse tags not set to true |
| 720 | &--multiple { |
| 721 | // Select Wrapper - unset height |
| 722 | &.am-select { |
| 723 | .el-select { |
| 724 | &__wrapper { |
| 725 | min-height: var(--am-h-select); |
| 726 | height: unset; |
| 727 | padding-top: 8px; |
| 728 | padding-bottom: 8px; |
| 729 | } |
| 730 | } |
| 731 | } |
| 732 | } |
| 733 | |
| 734 | .el-select { |
| 735 | // Select Wrapper |
| 736 | &__wrapper { |
| 737 | display: flex; |
| 738 | align-items: center; |
| 739 | gap: 0 6px; |
| 740 | position: relative; |
| 741 | height: var(--am-h-select); |
| 742 | min-height: 24px; |
| 743 | padding: var(--am-padd-select); |
| 744 | background-color: var(--am-c-select-bgr); |
| 745 | border: none; |
| 746 | border-radius: var(--am-rad-select); |
| 747 | box-shadow: 0 0 0 1px var(--am-c-select-border); |
| 748 | box-sizing: border-box; |
| 749 | |
| 750 | &:hover:not(.is-focused), |
| 751 | &.is-hovering:not(.is-focused) { |
| 752 | --am-c-select-border: var(--am-c-select-text-op40); |
| 753 | } |
| 754 | |
| 755 | &:focus, |
| 756 | &.is-focused { |
| 757 | --am-c-select-border: var(--am-c-primary); |
| 758 | } |
| 759 | |
| 760 | &.is-disabled { |
| 761 | cursor: not-allowed; |
| 762 | } |
| 763 | } |
| 764 | |
| 765 | // Select Input |
| 766 | &__input { |
| 767 | width: 100%; |
| 768 | height: 24px; |
| 769 | min-height: auto; |
| 770 | font-size: var(--am-fs-select); |
| 771 | font-weight: 400; |
| 772 | line-height: 1.6; |
| 773 | text-overflow: ellipsis; |
| 774 | white-space: nowrap; |
| 775 | overflow: hidden; |
| 776 | color: var(--am-c-select-text); |
| 777 | border: none; |
| 778 | border-radius: var(--am-rad-select); |
| 779 | background-color: transparent; |
| 780 | padding: 0; |
| 781 | box-shadow: none; |
| 782 | |
| 783 | &::-webkit-input-placeholder { |
| 784 | /* Chrome/Opera/Safari */ |
| 785 | color: var(--am-c-select-placeholder); |
| 786 | } |
| 787 | &::-moz-placeholder { |
| 788 | /* Firefox 19+ */ |
| 789 | color: var(--am-c-select-placeholder); |
| 790 | } |
| 791 | &:-ms-input-placeholder { |
| 792 | /* IE 10+ */ |
| 793 | color: var(--am-c-select-placeholder); |
| 794 | } |
| 795 | &:-moz-placeholder { |
| 796 | /* Firefox 18- */ |
| 797 | color: var(--am-c-select-placeholder); |
| 798 | } |
| 799 | } |
| 800 | |
| 801 | // Select Items |
| 802 | &__selected-item { |
| 803 | &.el-select__placeholder { |
| 804 | &.is-transparent { |
| 805 | --am-c-select-text: var(--am-c-select-placeholder); |
| 806 | } |
| 807 | |
| 808 | span { |
| 809 | font-size: var(--am-fs-select) !important; |
| 810 | font-weight: 400; |
| 811 | line-height: 1.6; |
| 812 | color: var(--am-c-select-text) !important; |
| 813 | } |
| 814 | } |
| 815 | } |
| 816 | |
| 817 | // Select Suffix icon |
| 818 | &__suffix { |
| 819 | .el-icon { |
| 820 | font-size: 18px; |
| 821 | color: var(--am-c-select-text); |
| 822 | } |
| 823 | [class^='am-icon']^='am-icon'] { |
| 824 | font-size: 18px; |
| 825 | color: var(--am-c-select-text); |
| 826 | } |
| 827 | } |
| 828 | |
| 829 | // Select Prefix icon |
| 830 | &__prefix { |
| 831 | [class^='am-icon']^='am-icon'] { |
| 832 | font-size: 24px; |
| 833 | line-height: 1; |
| 834 | color: var(--am-c-select-text); |
| 835 | } |
| 836 | } |
| 837 | } |
| 838 | |
| 839 | // Multiple select |
| 840 | .el-tag { |
| 841 | display: flex; |
| 842 | gap: 0 4px; |
| 843 | padding: 4px 6px; |
| 844 | border-radius: 4px; |
| 845 | background-color: var(--am-c-select-text-op06); |
| 846 | box-sizing: border-box; |
| 847 | |
| 848 | .el-select { |
| 849 | &__tags { |
| 850 | &-text { |
| 851 | font-size: var(--am-fs-select); |
| 852 | font-weight: 400; |
| 853 | line-height: 1; |
| 854 | color: var(--am-c-select-text); |
| 855 | } |
| 856 | } |
| 857 | } |
| 858 | |
| 859 | &__close { |
| 860 | color: var(--am-c-select-text-op50); |
| 861 | transition: color 0.3s ease-in-out; |
| 862 | |
| 863 | &:hover { |
| 864 | color: var(--am-c-select-text); |
| 865 | background: none; |
| 866 | } |
| 867 | } |
| 868 | } |
| 869 | |
| 870 | &.am-rtl { |
| 871 | .el-input { |
| 872 | &__suffix { |
| 873 | right: unset; |
| 874 | left: 12px; |
| 875 | } |
| 876 | } |
| 877 | } |
| 878 | } |
| 879 | } |
| 880 | .am-select-popper { |
| 881 | z-index: 9999999999 !important; |
| 882 | } |
| 883 | |
| 884 | // public |
| 885 | .amelia-v2-booking #amelia-container { |
| 886 | @include am-select-block; |
| 887 | } |
| 888 | |
| 889 | // admin |
| 890 | #amelia-app-backend-new { |
| 891 | @include am-select-block; |
| 892 | } |
| 893 | </style> |
| 894 |