FieldSelectInput.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { ref, toRef } from 'vue'
  4. import CommonTicketStateIndicator from '#shared/components/CommonTicketStateIndicator/CommonTicketStateIndicator.vue'
  5. import useValue from '#shared/components/Form/composables/useValue.ts'
  6. import type { SelectContext } from '#shared/components/Form/fields/FieldSelect/types.ts'
  7. import useSelectOptions from '#shared/composables/useSelectOptions.ts'
  8. import useSelectPreselect from '#shared/composables/useSelectPreselect.ts'
  9. import { useFormBlock } from '#shared/form/useFormBlock.ts'
  10. import { EnumTicketStateColorCode } from '#shared/graphql/types.ts'
  11. import { i18n } from '#shared/i18n.ts'
  12. import CommonSelect from '#mobile/components/CommonSelect/CommonSelect.vue'
  13. import type { CommonSelectInstance } from '#mobile/components/CommonSelect/types.ts'
  14. interface Props {
  15. context: SelectContext
  16. }
  17. const props = defineProps<Props>()
  18. const contextReactive = toRef(props, 'context')
  19. const { hasValue, valueContainer, currentValue, clearValue } =
  20. useValue(contextReactive)
  21. const {
  22. hasStatusProperty,
  23. sortedOptions,
  24. selectOption,
  25. getSelectedOptionIcon,
  26. getSelectedOptionLabel,
  27. getSelectedOptionStatus,
  28. setupMissingOrDisabledOptionHandling,
  29. } = useSelectOptions(toRef(props.context, 'options'), contextReactive)
  30. const select = ref<CommonSelectInstance>()
  31. const openSelectDialog = () => {
  32. if (
  33. select.value?.isOpen ||
  34. !props.context.options?.length ||
  35. props.context.disabled
  36. )
  37. return
  38. select.value?.openDialog()
  39. }
  40. useFormBlock(contextReactive, openSelectDialog)
  41. useSelectPreselect(sortedOptions, contextReactive)
  42. setupMissingOrDisabledOptionHandling()
  43. </script>
  44. <template>
  45. <div
  46. :class="[
  47. context.classes.input,
  48. 'flex h-auto',
  49. {
  50. 'ltr:pr-9 rtl:pl-9': context.clearable && hasValue && !context.disabled,
  51. },
  52. ]"
  53. data-test-id="field-select"
  54. >
  55. <CommonSelect
  56. ref="select"
  57. #default="{ state: expanded }"
  58. :model-value="currentValue"
  59. :options="sortedOptions"
  60. :multiple="context.multiple"
  61. :owner="context.id"
  62. no-options-label-translation
  63. passive
  64. @select="selectOption"
  65. >
  66. <output
  67. :id="context.id"
  68. ref="outputElement"
  69. role="combobox"
  70. aria-controls="common-select"
  71. aria-owns="common-select"
  72. aria-haspopup="dialog"
  73. :aria-expanded="expanded"
  74. :name="context.node.name"
  75. class="formkit-disabled:pointer-events-none flex grow items-center focus:outline-none"
  76. :aria-labelledby="`label-${context.id}`"
  77. :aria-disabled="context.disabled"
  78. :data-multiple="context.multiple"
  79. tabindex="0"
  80. v-bind="context.attrs"
  81. @keyup.shift.down.prevent="openSelectDialog()"
  82. @keypress.space.prevent="openSelectDialog()"
  83. @blur="context.handlers.blur"
  84. >
  85. <div v-if="hasValue" class="flex grow flex-wrap gap-1" role="list">
  86. <template v-if="hasValue && hasStatusProperty">
  87. <CommonTicketStateIndicator
  88. v-for="selectedValue in valueContainer"
  89. :key="selectedValue"
  90. :color-code="
  91. getSelectedOptionStatus(
  92. selectedValue,
  93. ) as EnumTicketStateColorCode
  94. "
  95. :label="
  96. getSelectedOptionLabel(selectedValue) ||
  97. i18n.t('%s (unknown)', selectedValue)
  98. "
  99. :data-test-status="getSelectedOptionStatus(selectedValue)"
  100. role="listitem"
  101. pill
  102. />
  103. </template>
  104. <template v-else-if="hasValue">
  105. <div
  106. v-for="(selectedValue, idx) in valueContainer"
  107. :key="selectedValue"
  108. class="flex items-center text-base leading-[19px]"
  109. role="listitem"
  110. >
  111. <CommonIcon
  112. v-if="getSelectedOptionIcon(selectedValue)"
  113. :name="getSelectedOptionIcon(selectedValue)"
  114. size="tiny"
  115. class="ltr:mr-1 rtl:ml-1"
  116. decorative
  117. />{{
  118. getSelectedOptionLabel(selectedValue) ||
  119. i18n.t('%s (unknown)', selectedValue)
  120. }}{{ idx === valueContainer.length - 1 ? '' : ',' }}
  121. </div>
  122. </template>
  123. </div>
  124. <CommonIcon
  125. v-if="context.clearable && hasValue && !context.disabled"
  126. :aria-label="i18n.t('Clear Selection')"
  127. class="text-gray absolute -mt-5 shrink-0 ltr:right-2 rtl:left-2"
  128. name="close-small"
  129. size="base"
  130. role="button"
  131. tabindex="0"
  132. @click.stop="clearValue()"
  133. @keypress.space.prevent.stop="clearValue()"
  134. />
  135. </output>
  136. </CommonSelect>
  137. </div>
  138. </template>