fix(Picker): fix some bugs

This commit is contained in:
chenjiahan 2022-01-19 11:27:19 +08:00 committed by neverland
parent c061412138
commit 22654bf518
5 changed files with 176 additions and 115 deletions

View File

@ -9,7 +9,6 @@ import {
// Utils // Utils
import { import {
isDef,
extend, extend,
unitToPx, unitToPx,
truthProp, truthProp,
@ -21,6 +20,12 @@ import {
HAPTICS_FEEDBACK, HAPTICS_FEEDBACK,
BORDER_UNSET_TOP_BOTTOM, BORDER_UNSET_TOP_BOTTOM,
} from '../utils'; } from '../utils';
import {
getColumnsType,
findOptionByValue,
formatCascadeColumns,
getFirstEnabledOption,
} from './utils';
// Composables // Composables
import { useChildren } from '@vant/use'; import { useChildren } from '@vant/use';
@ -34,7 +39,6 @@ import Column, { PICKER_KEY } from './PickerColumn';
import type { import type {
PickerColumn, PickerColumn,
PickerExpose, PickerExpose,
PickerOption,
PickerFieldNames, PickerFieldNames,
PickerToolbarPosition, PickerToolbarPosition,
} from './types'; } from './types';
@ -76,77 +80,41 @@ export default defineComponent({
const selectedValues = ref(props.modelValue); const selectedValues = ref(props.modelValue);
const currentColumns = ref<PickerColumn[]>([]); const currentColumns = ref<PickerColumn[]>([]);
const {
text: textKey,
value: valueKey,
children: childrenKey,
} = extend(
{
text: 'text',
value: 'value',
children: 'children',
},
props.columnsFieldNames
);
const { children, linkChildren } = useChildren(PICKER_KEY); const { children, linkChildren } = useChildren(PICKER_KEY);
linkChildren(); linkChildren();
const fields = computed(
(): Required<PickerFieldNames> =>
extend(
{
text: 'text',
value: 'value',
children: 'children',
},
props.columnsFieldNames
)
);
const optionHeight = computed(() => unitToPx(props.optionHeight)); const optionHeight = computed(() => unitToPx(props.optionHeight));
const columnsType = computed(() =>
const dataType = computed(() => { getColumnsType(props.columns, fields.value)
const firstColumn = props.columns[0]; );
if (Array.isArray(firstColumn)) {
return 'multiple';
}
if (childrenKey in firstColumn) {
return 'cascade';
}
return 'default';
});
const findOption = (options: PickerOption[], value: number | string) =>
options.find((option) => option[valueKey] === value);
const formatCascade = () => {
const formatted: PickerColumn[] = [];
let cursor: PickerOption | undefined = {
[childrenKey]: props.columns,
};
let columnIndex = 0;
while (cursor && cursor[childrenKey]) {
const options: PickerOption[] = cursor[childrenKey];
const value = selectedValues.value[columnIndex];
cursor = isDef(value) ? findOption(options, value) : undefined;
if (!cursor && options.length) {
const firstValue = options[0][valueKey];
selectedValues.value[columnIndex] = firstValue;
cursor = findOption(options, firstValue);
}
columnIndex++;
formatted.push(options);
}
return formatted;
};
const selectedOptions = computed(() => const selectedOptions = computed(() =>
currentColumns.value.map((options, index) => currentColumns.value.map((options, index) =>
findOption(options, selectedValues.value[index]) findOptionByValue(options, selectedValues.value[index], fields.value)
) )
); );
const onChange = (value: number | string, columnIndex: number) => { const onChange = (value: number | string, columnIndex: number) => {
selectedValues.value[columnIndex] = value; selectedValues.value[columnIndex] = value;
if (dataType.value === 'cascade') { if (columnsType.value === 'cascade') {
currentColumns.value = formatCascade(); currentColumns.value = formatCascadeColumns(
props.columns,
fields.value,
selectedValues
);
} }
emit('change', { emit('change', {
@ -222,10 +190,9 @@ export default defineComponent({
<Column <Column
v-slots={{ option: slots.option }} v-slots={{ option: slots.option }}
value={selectedValues.value[columnIndex]} value={selectedValues.value[columnIndex]}
textKey={textKey} fields={fields.value}
options={options} options={options}
readonly={props.readonly} readonly={props.readonly}
valueKey={valueKey}
allowHtml={props.allowHtml} allowHtml={props.allowHtml}
optionHeight={optionHeight.value} optionHeight={optionHeight.value}
swipeDuration={props.swipeDuration} swipeDuration={props.swipeDuration}
@ -265,26 +232,27 @@ export default defineComponent({
); );
}; };
const formatColumns = (
columns: PickerColumn | PickerColumn[]
): PickerColumn[] => {
switch (columnsType.value) {
case 'multiple':
return columns as PickerColumn[];
case 'cascade':
return formatCascadeColumns(columns, fields.value, selectedValues);
default:
return [columns];
}
};
watch( watch(
() => props.columns, () => props.columns,
() => { (columns) => {
const { columns } = props; currentColumns.value = formatColumns(columns);
switch (dataType.value) {
case 'multiple':
currentColumns.value = columns;
break;
case 'cascade':
currentColumns.value = formatCascade();
break;
default:
currentColumns.value = [columns];
break;
}
currentColumns.value.forEach((options, index) => { currentColumns.value.forEach((options, index) => {
if (selectedValues.value[index] === undefined && options.length) { if (selectedValues.value[index] === undefined && options.length) {
selectedValues.value[index] = options[0][valueKey]; selectedValues.value[index] =
getFirstEnabledOption(options)[fields.value.value];
} }
}); });

View File

@ -1,4 +1,10 @@
import { ref, watch, defineComponent, type InjectionKey } from 'vue'; import {
ref,
watchEffect,
defineComponent,
type PropType,
type InjectionKey,
} from 'vue';
// Utils // Utils
import { import {
@ -9,6 +15,7 @@ import {
createNamespace, createNamespace,
makeRequiredProp, makeRequiredProp,
} from '../utils'; } from '../utils';
import { getElementTranslateY, findIndexOfEnabledOption } from './utils';
// Composables // Composables
import { useParent } from '@vant/use'; import { useParent } from '@vant/use';
@ -16,7 +23,11 @@ import { useTouch } from '../composables/use-touch';
import { useExpose } from '../composables/use-expose'; import { useExpose } from '../composables/use-expose';
// Types // Types
import type { PickerOption, PickerColumnProvide } from './types'; import type {
PickerOption,
PickerFieldNames,
PickerColumnProvide,
} from './types';
const DEFAULT_DURATION = 200; const DEFAULT_DURATION = 200;
@ -28,12 +39,6 @@ const MOMENTUM_DISTANCE = 15;
const [name, bem] = createNamespace('picker-column'); const [name, bem] = createNamespace('picker-column');
function getElementTranslateY(element: Element) {
const { transform } = window.getComputedStyle(element);
const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
return Number(translateY);
}
export const PICKER_KEY: InjectionKey<PickerColumnProvide> = Symbol(name); export const PICKER_KEY: InjectionKey<PickerColumnProvide> = Symbol(name);
export default defineComponent({ export default defineComponent({
@ -41,10 +46,9 @@ export default defineComponent({
props: { props: {
value: numericProp, value: numericProp,
textKey: makeRequiredProp(String), fields: makeRequiredProp(Object as PropType<Required<PickerFieldNames>>),
options: makeArrayProp<PickerOption>(), options: makeArrayProp<PickerOption>(),
readonly: Boolean, readonly: Boolean,
valueKey: makeRequiredProp(String),
allowHtml: Boolean, allowHtml: Boolean,
optionHeight: makeRequiredProp(Number), optionHeight: makeRequiredProp(Number),
swipeDuration: makeRequiredProp(numericProp), swipeDuration: makeRequiredProp(numericProp),
@ -70,24 +74,12 @@ export default defineComponent({
const baseOffset = () => const baseOffset = () =>
(props.optionHeight * (+props.visibleOptionNum - 1)) / 2; (props.optionHeight * (+props.visibleOptionNum - 1)) / 2;
const adjustIndex = (index: number) => {
index = clamp(index, 0, count());
for (let i = index; i < count(); i++) {
if (!props.options[i].disabled) return i;
}
for (let i = index - 1; i >= 0; i--) {
if (!props.options[i].disabled) return i;
}
return 0;
};
const updateValueByIndex = (index: number) => { const updateValueByIndex = (index: number) => {
index = adjustIndex(index); const enabledIndex = findIndexOfEnabledOption(props.options, index);
const offset = -enabledIndex * props.optionHeight;
const offset = -index * props.optionHeight;
const trigger = () => { const trigger = () => {
const { value } = props.options[index]; const value = props.options[enabledIndex][props.fields.value];
if (value !== props.value) { if (value !== props.value) {
emit('change', value); emit('change', value);
} }
@ -214,9 +206,9 @@ export default defineComponent({
}; };
return props.options.map((option, index) => { return props.options.map((option, index) => {
const text = option[props.textKey]; const text = option[props.fields.text];
const { disabled } = option; const { disabled } = option;
const value: string | number = option[props.valueKey]; const value: string | number = option[props.fields.value];
const data = { const data = {
role: 'button', role: 'button',
style: optionStyle, style: optionStyle,
@ -244,16 +236,14 @@ export default defineComponent({
useParent(PICKER_KEY); useParent(PICKER_KEY);
useExpose({ stopMomentum }); useExpose({ stopMomentum });
watch( watchEffect(() => {
() => props.value, const index = props.options.findIndex(
(value) => { (option) => option[props.fields.value] === props.value
const index = props.options.findIndex( );
(option) => option[props.valueKey] === value const enabledIndex = findIndexOfEnabledOption(props.options, index);
); const offset = -enabledIndex * props.optionHeight;
const offset = -adjustIndex(index) * props.optionHeight; currentOffset.value = offset;
currentOffset.value = offset; });
}
);
return () => ( return () => (
<div <div

View File

@ -144,7 +144,7 @@ export const cascadeColumns = {
], ],
}; };
export const cascadeColumnsCustomKey = { export const customKeyColumns = {
'zh-CN': [ 'zh-CN': [
{ {
cityName: '浙江', cityName: '浙江',
@ -202,3 +202,16 @@ export const cascadeColumnsCustomKey = {
}, },
], ],
}; };
export const disabledColumns = {
'zh-CN': [
{ text: '杭州', value: 'Hangzhou', disabled: true },
{ text: '宁波', value: 'Ningbo' },
{ text: '温州', value: 'Wenzhou' },
],
'en-US': [
{ text: 'Delaware', value: 'Delaware', disabled: true },
{ text: 'Florida', value: 'Florida' },
{ text: 'Georqia', value: 'Georqia' },
],
};

View File

@ -10,7 +10,9 @@ export type {
PickerInstance, PickerInstance,
PickerFieldNames, PickerFieldNames,
PickerToolbarPosition, PickerToolbarPosition,
PickerCancelEventParams,
PickerChangeEventParams, PickerChangeEventParams,
PickerConfirmEventParams,
} from './types'; } from './types';
declare module 'vue' { declare module 'vue' {

View File

@ -0,0 +1,88 @@
import { isDef, clamp } from '../utils';
import type { Ref } from 'vue';
import type { PickerOption, PickerColumn, PickerFieldNames } from './types';
export function getFirstEnabledOption(options: PickerOption[]) {
return options.find((option) => !option.disabled) || options[0];
}
export function getColumnsType(
columns: PickerColumn | PickerColumn[],
fields: Required<PickerFieldNames>
) {
const firstColumn = columns[0];
if (firstColumn) {
if (Array.isArray(firstColumn)) {
return 'multiple';
}
if (fields.children in firstColumn) {
return 'cascade';
}
}
return 'default';
}
export function findIndexOfEnabledOption(
options: PickerOption[],
index: number
) {
index = clamp(index, 0, options.length);
for (let i = index; i < options.length; i++) {
if (!options[i].disabled) return i;
}
for (let i = index - 1; i >= 0; i--) {
if (!options[i].disabled) return i;
}
return 0;
}
export function findOptionByValue(
options: PickerOption[],
value: number | string,
fields: Required<PickerFieldNames>
) {
const index = options.findIndex((option) => option[fields.value] === value);
const enabledIndex = findIndexOfEnabledOption(options, index);
return options[enabledIndex];
}
export function formatCascadeColumns(
columns: PickerColumn | PickerColumn[],
fields: Required<PickerFieldNames>,
selectedValues: Ref<Array<number | string>>
) {
const formatted: PickerColumn[] = [];
let cursor: PickerOption | undefined = {
[fields.children]: columns,
};
let columnIndex = 0;
while (cursor && cursor[fields.children]) {
const options: PickerOption[] = cursor[fields.children];
const value = selectedValues.value[columnIndex];
cursor = isDef(value)
? findOptionByValue(options, value, fields)
: undefined;
if (!cursor && options.length) {
const firstValue = getFirstEnabledOption(options)[fields.value];
selectedValues.value[columnIndex] = firstValue;
cursor = findOptionByValue(options, firstValue, fields);
}
columnIndex++;
formatted.push(options);
}
return formatted;
}
export function getElementTranslateY(element: Element) {
const { transform } = window.getComputedStyle(element);
const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
return Number(translateY);
}