Files
element-plus/packages/components/segmented/src/segmented.vue

193 lines
4.8 KiB
Vue

<template>
<div
v-if="options.length"
:id="inputId"
ref="segmentedRef"
:class="segmentedCls"
role="radiogroup"
:aria-label="!isLabeledByFormItem ? ariaLabel || 'segmented' : undefined"
:aria-labelledby="isLabeledByFormItem ? formItem!.labelId : undefined"
>
<div :class="[ns.e('group'), ns.m(direction)]">
<div :style="selectedStyle" :class="selectedCls" />
<label
v-for="(item, index) in options"
:key="index"
:class="getItemCls(item)"
>
<input
:class="ns.e('item-input')"
type="radio"
:name="name"
:disabled="getDisabled(item)"
:checked="getSelected(item)"
@change="handleChange(item)"
/>
<div :class="ns.e('item-label')">
<slot :item="intoAny(item)">{{ getLabel(item) }}</slot>
</div>
</label>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue'
import { useActiveElement, useResizeObserver } from '@vueuse/core'
import { useId, useNamespace } from '@element-plus/hooks'
import {
useFormDisabled,
useFormItem,
useFormItemInputId,
useFormSize,
} from '@element-plus/components/form'
import { debugWarn, isObject } from '@element-plus/utils'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
import { defaultProps, segmentedEmits, segmentedProps } from './segmented'
import type { Option } from './types'
defineOptions({
name: 'ElSegmented',
})
const props = defineProps(segmentedProps)
const emit = defineEmits(segmentedEmits)
const ns = useNamespace('segmented')
const segmentedId = useId()
const segmentedSize = useFormSize()
const _disabled = useFormDisabled()
const { formItem } = useFormItem()
const { inputId, isLabeledByFormItem } = useFormItemInputId(props, {
formItemContext: formItem,
})
const segmentedRef = ref<HTMLElement | null>(null)
const activeElement = useActiveElement()
const state = reactive({
isInit: false,
width: 0,
height: 0,
translateX: 0,
translateY: 0,
focusVisible: false,
})
const handleChange = (item: Option) => {
const value = getValue(item)
emit(UPDATE_MODEL_EVENT, value)
emit(CHANGE_EVENT, value)
}
const aliasProps = computed(() => ({ ...defaultProps, ...props.props }))
//FIXME: remove this when vue >=3.3
const intoAny = (item: any) => item
const getValue = (item: Option) => {
return isObject(item) ? item[aliasProps.value.value] : item
}
const getLabel = (item: Option) => {
return isObject(item) ? item[aliasProps.value.label] : item
}
const getDisabled = (item: Option | undefined) => {
return !!(
_disabled.value ||
(isObject(item) ? item[aliasProps.value.disabled] : false)
)
}
const getSelected = (item: Option) => {
return props.modelValue === getValue(item)
}
const getOption = (value: any) => {
return props.options.find((item) => getValue(item) === value)
}
const getItemCls = (item: Option) => {
return [
ns.e('item'),
ns.is('selected', getSelected(item)),
ns.is('disabled', getDisabled(item)),
]
}
const updateSelect = () => {
if (!segmentedRef.value) return
const selectedItem = segmentedRef.value.querySelector(
'.is-selected'
) as HTMLElement
const selectedItemInput = segmentedRef.value.querySelector(
'.is-selected input'
) as HTMLElement
if (!selectedItem || !selectedItemInput) {
state.width = 0
state.height = 0
state.translateX = 0
state.translateY = 0
state.focusVisible = false
return
}
state.isInit = true
if (props.direction === 'vertical') {
state.height = selectedItem.offsetHeight
state.translateY = selectedItem.offsetTop
} else {
state.width = selectedItem.offsetWidth
state.translateX = selectedItem.offsetLeft
}
try {
// This will failed in test
state.focusVisible = selectedItemInput.matches(':focus-visible')
} catch {}
}
const segmentedCls = computed(() => [
ns.b(),
ns.m(segmentedSize.value),
ns.is('block', props.block),
])
const selectedStyle = computed(() => ({
width: props.direction === 'vertical' ? '100%' : `${state.width}px`,
height: props.direction === 'vertical' ? `${state.height}px` : '100%',
transform:
props.direction === 'vertical'
? `translateY(${state.translateY}px)`
: `translateX(${state.translateX}px)`,
display: state.isInit ? 'block' : 'none',
}))
const selectedCls = computed(() => [
ns.e('item-selected'),
ns.is('disabled', getDisabled(getOption(props.modelValue))),
ns.is('focus-visible', state.focusVisible),
])
const name = computed(() => {
return props.name || segmentedId.value
})
useResizeObserver(segmentedRef, updateSelect)
watch(activeElement, updateSelect)
watch(
() => props.modelValue,
() => {
updateSelect()
if (props.validateEvent) {
formItem?.validate?.('change').catch((err) => debugWarn(err))
}
},
{
flush: 'post',
}
)
</script>