mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
193 lines
4.8 KiB
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>
|