<!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ --> <script setup lang="ts"> import { computed, nextTick, toRef, watch } from 'vue' import stopEvent from '#shared/utils/events.ts' import useValue from '../../composables/useValue.ts' import { getToggleClasses } from './initializeToggleClasses.ts' import type { FormFieldContext } from '../../types/field.ts' const props = defineProps<{ context: FormFieldContext<{ // TODO: need to be changed to "options", because otherwise core workflow can not handle this variants?: { true?: string false?: string } size?: 'medium' | 'small' }> }>() const context = toRef(props, 'context') const { localValue } = useValue(context) const variants = computed(() => props.context.variants || {}) watch( () => props.context.variants, (variants) => { if (!variants) { console.warn( 'FieldToggleInput: variants prop is required, but not provided', ) return } if (localValue.value === undefined) { const options = Object.keys(variants) if (options.length === 1) { nextTick(() => { localValue.value = options[0] === 'true' }) } return } const valueString = localValue.value ? 'true' : 'false' // if current value is not removed from options, we don't need to reset it if (valueString in variants) return // current value was removed from options, so we need reset it // if other value exists, fallback to it, otherwise set to undefined const newValueString = localValue.value ? 'false' : 'true' const newValue = newValueString in variants ? !localValue.value : undefined localValue.value = newValue }, { immediate: true }, ) const disabled = computed(() => { if (props.context.disabled) return true const nextValueString = localValue.value ? 'false' : 'true' // if can't select next value, disable the toggle return !(nextValueString in variants.value) }) const updateLocalValue = (e: Event) => { stopEvent(e) if (disabled.value) return const newValue = localValue.value ? 'false' : 'true' if (newValue in variants.value) { localValue.value = newValue === 'true' } } const ariaChecked = computed(() => (localValue.value ? 'true' : 'false')) const buttonSizeClasses = computed(() => { if (context.value.size === 'small') return 'w-8 h-5' return 'w-10 h-6' }) const knobSizeClasses = computed(() => { if (context.value.size === 'small') return 'w-[18px] h-[18px]' return 'w-[22px] h-[22px]' }) const knobTranslateClasses = computed(() => { if (context.value.size === 'small') return 'ltr:translate-x-[13px] rtl:-translate-x-[13px]' return 'ltr:translate-x-[17px] rtl:-translate-x-[17px]' }) const classMap = getToggleClasses() </script> <template> <button :id="context.id" type="button" role="switch" class="formkit-disabled:pointer-events-none relative inline-flex flex-shrink-0 cursor-pointer items-center rounded-full transition-colors duration-200 ease-in-out" :class="[ context.classes.input, classMap.track, buttonSizeClasses, { [classMap.trackOn]: localValue, }, ]" :aria-labelledby="`label-${context.id}`" :aria-disabled="disabled" :aria-checked="ariaChecked" :aria-describedby="context.describedBy" :tabindex="context.disabled ? '-1' : '0'" :v-bind="context.attrs" @click="updateLocalValue" @keydown.space="updateLocalValue" > <div class="pointer-events-none inline-block transform rounded-full transition duration-200 ease-in-out" :class="[ classMap.knob, knobSizeClasses, { 'ltr:translate-x-px rtl:-translate-x-px': !localValue, [knobTranslateClasses]: localValue, }, ]" ></div> </button> </template>