import { ref, watch, computed } from 'vue'; import { pickerProps, PICKER_KEY } from './shared'; // Utils import { unitToPx, preventDefault, createNamespace } from '../utils'; import { BORDER_UNSET_TOP_BOTTOM } from '../utils/constant'; // Composition import { useChildren } from '@vant/use'; import { useExpose } from '../composables/use-expose'; // Components import Loading from '../loading'; import PickerColumn from './PickerColumn'; const [createComponent, bem, t] = createNamespace('picker'); export default createComponent({ props: { ...pickerProps, columns: { type: Array, default: () => [], }, defaultIndex: { type: [Number, String], default: 0, }, toolbarPosition: { type: String, default: 'top', }, valueKey: { type: String, default: 'text', }, }, emits: ['confirm', 'cancel', 'change'], setup(props, { emit, slots }) { const formattedColumns = ref([]); const { children, linkChildren } = useChildren(PICKER_KEY); linkChildren(); const itemHeight = computed(() => unitToPx(props.itemHeight)); const dataType = computed(() => { const { columns } = props; const firstColumn = columns[0] || {}; if (firstColumn.children) { return 'cascade'; } if (firstColumn.values) { return 'object'; } return 'text'; }); const formatCascade = () => { const formatted = []; let cursor = { children: props.columns }; while (cursor && cursor.children) { const { children } = cursor; let defaultIndex = cursor.defaultIndex ?? +props.defaultIndex; while (children[defaultIndex] && children[defaultIndex].disabled) { if (defaultIndex < children.length - 1) { defaultIndex++; } else { defaultIndex = 0; break; } } formatted.push({ values: cursor.children, className: cursor.className, defaultIndex, }); cursor = children[defaultIndex]; } formattedColumns.value = formatted; }; const format = () => { const { columns } = props; if (dataType.value === 'text') { formattedColumns.value = [{ values: columns }]; } else if (dataType.value === 'cascade') { formatCascade(); } else { formattedColumns.value = columns; } }; // get indexes of all columns const getIndexes = () => children.map((child) => child.state.index); // set options of column by index const setColumnValues = (index, options) => { const column = children[index]; if (column) { column.setOptions(options); } }; const onCascadeChange = (columnIndex) => { let cursor = { children: props.columns }; const indexes = getIndexes(); for (let i = 0; i <= columnIndex; i++) { cursor = cursor.children[indexes[i]]; } while (cursor && cursor.children) { columnIndex++; setColumnValues(columnIndex, cursor.children); cursor = cursor.children[cursor.defaultIndex || 0]; } }; // get column instance by index const getColumn = (index) => children[index]; // get column value by index const getColumnValue = (index) => { const column = getColumn(index); return column && column.getValue(); }; // set column value by index const setColumnValue = (index, value) => { const column = getColumn(index); if (column) { column.setValue(value); if (dataType.value === 'cascade') { onCascadeChange(index); } } }; // get column option index by column index const getColumnIndex = (index) => (getColumn(index) || {}).state.index; // set column option index by column index const setColumnIndex = (columnIndex, optionIndex) => { const column = getColumn(columnIndex); if (column) { column.setIndex(optionIndex); if (props.dataType === 'cascade') { onCascadeChange(columnIndex); } } }; // get options of column by index const getColumnValues = (index) => (children[index] || {}).state.options; // get values of all columns const getValues = () => children.map((child) => child.getValue()); // set values of all columns const setValues = (values) => { values.forEach((value, index) => { setColumnValue(index, value); }); }; // set indexes of all columns const setIndexes = (indexes) => { indexes.forEach((optionIndex, columnIndex) => { setColumnIndex(columnIndex, optionIndex); }); }; const emitAction = (event) => { if (dataType.value === 'text') { emit(event, getColumnValue(0), getColumnIndex(0)); } else { emit(event, getValues(), getIndexes()); } }; const onChange = (columnIndex) => { if (dataType.value === 'cascade') { onCascadeChange(columnIndex); } if (dataType.value === 'text') { emit('change', getColumnValue(0), getColumnIndex(0)); } else { emit('change', getValues(), columnIndex); } }; const confirm = () => { children.forEach((child) => child.stopMomentum()); emitAction('confirm'); }; const cancel = () => { emitAction('cancel'); }; const renderTitle = () => { if (slots.title) { return slots.title(); } if (props.title) { return
{props.title}
; } }; const renderCancel = () => { const text = props.cancelButtonText || t('cancel'); return ( ); }; const renderConfirm = () => { const text = props.confirmButtonText || t('confirm'); return ( ); }; const renderToolbar = () => { if (props.showToolbar) { return (
{slots.default ? slots.default() : [renderCancel(), renderTitle(), renderConfirm()]}
); } }; const renderColumnItems = () => formattedColumns.value.map((item, columnIndex) => ( { onChange(columnIndex); }} /> )); const renderColumns = () => { const wrapHeight = itemHeight.value * props.visibleItemCount; const frameStyle = { height: `${itemHeight.value}px` }; const columnsStyle = { height: `${wrapHeight}px` }; const maskStyle = { backgroundSize: `100% ${(wrapHeight - itemHeight.value) / 2}px`, }; return (
{renderColumnItems()}
); }; watch(() => props.columns, format, { immediate: true }); useExpose({ confirm, getValues, setValues, getIndexes, setIndexes, getColumnIndex, setColumnIndex, getColumnValue, setColumnValue, getColumnValues, setColumnValues, }); return () => (
{props.toolbarPosition === 'top' ? renderToolbar() : null} {props.loading ? : null} {slots['columns-top']?.()} {renderColumns()} {slots['columns-bottom']?.()} {props.toolbarPosition === 'bottom' ? renderToolbar() : null}
); }, });