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
import {
isDef,
extend,
unitToPx,
truthProp,
@ -21,6 +20,12 @@ import {
HAPTICS_FEEDBACK,
BORDER_UNSET_TOP_BOTTOM,
} from '../utils';
import {
getColumnsType,
findOptionByValue,
formatCascadeColumns,
getFirstEnabledOption,
} from './utils';
// Composables
import { useChildren } from '@vant/use';
@ -34,7 +39,6 @@ import Column, { PICKER_KEY } from './PickerColumn';
import type {
PickerColumn,
PickerExpose,
PickerOption,
PickerFieldNames,
PickerToolbarPosition,
} from './types';
@ -76,77 +80,41 @@ export default defineComponent({
const selectedValues = ref(props.modelValue);
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);
linkChildren();
const fields = computed(
(): Required<PickerFieldNames> =>
extend(
{
text: 'text',
value: 'value',
children: 'children',
},
props.columnsFieldNames
)
);
const optionHeight = computed(() => unitToPx(props.optionHeight));
const dataType = computed(() => {
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 columnsType = computed(() =>
getColumnsType(props.columns, fields.value)
);
const selectedOptions = computed(() =>
currentColumns.value.map((options, index) =>
findOption(options, selectedValues.value[index])
findOptionByValue(options, selectedValues.value[index], fields.value)
)
);
const onChange = (value: number | string, columnIndex: number) => {
selectedValues.value[columnIndex] = value;
if (dataType.value === 'cascade') {
currentColumns.value = formatCascade();
if (columnsType.value === 'cascade') {
currentColumns.value = formatCascadeColumns(
props.columns,
fields.value,
selectedValues
);
}
emit('change', {
@ -222,10 +190,9 @@ export default defineComponent({
<Column
v-slots={{ option: slots.option }}
value={selectedValues.value[columnIndex]}
textKey={textKey}
fields={fields.value}
options={options}
readonly={props.readonly}
valueKey={valueKey}
allowHtml={props.allowHtml}
optionHeight={optionHeight.value}
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(
() => props.columns,
() => {
const { columns } = props;
switch (dataType.value) {
case 'multiple':
currentColumns.value = columns;
break;
case 'cascade':
currentColumns.value = formatCascade();
break;
default:
currentColumns.value = [columns];
break;
}
(columns) => {
currentColumns.value = formatColumns(columns);
currentColumns.value.forEach((options, index) => {
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
import {
@ -9,6 +15,7 @@ import {
createNamespace,
makeRequiredProp,
} from '../utils';
import { getElementTranslateY, findIndexOfEnabledOption } from './utils';
// Composables
import { useParent } from '@vant/use';
@ -16,7 +23,11 @@ import { useTouch } from '../composables/use-touch';
import { useExpose } from '../composables/use-expose';
// Types
import type { PickerOption, PickerColumnProvide } from './types';
import type {
PickerOption,
PickerFieldNames,
PickerColumnProvide,
} from './types';
const DEFAULT_DURATION = 200;
@ -28,12 +39,6 @@ const MOMENTUM_DISTANCE = 15;
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 default defineComponent({
@ -41,10 +46,9 @@ export default defineComponent({
props: {
value: numericProp,
textKey: makeRequiredProp(String),
fields: makeRequiredProp(Object as PropType<Required<PickerFieldNames>>),
options: makeArrayProp<PickerOption>(),
readonly: Boolean,
valueKey: makeRequiredProp(String),
allowHtml: Boolean,
optionHeight: makeRequiredProp(Number),
swipeDuration: makeRequiredProp(numericProp),
@ -70,24 +74,12 @@ export default defineComponent({
const baseOffset = () =>
(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) => {
index = adjustIndex(index);
const enabledIndex = findIndexOfEnabledOption(props.options, index);
const offset = -enabledIndex * props.optionHeight;
const offset = -index * props.optionHeight;
const trigger = () => {
const { value } = props.options[index];
const value = props.options[enabledIndex][props.fields.value];
if (value !== props.value) {
emit('change', value);
}
@ -214,9 +206,9 @@ export default defineComponent({
};
return props.options.map((option, index) => {
const text = option[props.textKey];
const text = option[props.fields.text];
const { disabled } = option;
const value: string | number = option[props.valueKey];
const value: string | number = option[props.fields.value];
const data = {
role: 'button',
style: optionStyle,
@ -244,16 +236,14 @@ export default defineComponent({
useParent(PICKER_KEY);
useExpose({ stopMomentum });
watch(
() => props.value,
(value) => {
const index = props.options.findIndex(
(option) => option[props.valueKey] === value
);
const offset = -adjustIndex(index) * props.optionHeight;
currentOffset.value = offset;
}
);
watchEffect(() => {
const index = props.options.findIndex(
(option) => option[props.fields.value] === props.value
);
const enabledIndex = findIndexOfEnabledOption(props.options, index);
const offset = -enabledIndex * props.optionHeight;
currentOffset.value = offset;
});
return () => (
<div

View File

@ -144,7 +144,7 @@ export const cascadeColumns = {
],
};
export const cascadeColumnsCustomKey = {
export const customKeyColumns = {
'zh-CN': [
{
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,
PickerFieldNames,
PickerToolbarPosition,
PickerCancelEventParams,
PickerChangeEventParams,
PickerConfirmEventParams,
} from './types';
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);
}