mirror of
https://github.com/youzan/vant.git
synced 2025-10-18 09:24:25 +00:00
types(NumberKeyboard): use tsx (#8156)
This commit is contained in:
296
src/number-keyboard/index.tsx
Normal file
296
src/number-keyboard/index.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import {
|
||||
ref,
|
||||
Slot,
|
||||
watch,
|
||||
computed,
|
||||
Teleport,
|
||||
PropType,
|
||||
Transition,
|
||||
TeleportProps,
|
||||
} from 'vue';
|
||||
import { createNamespace, stopPropagation } from '../utils';
|
||||
import { useClickAway } from '@vant/use';
|
||||
import Key, { KeyType } from './Key';
|
||||
|
||||
const [createComponent, bem] = createNamespace('number-keyboard');
|
||||
|
||||
export type NumberKeyboardTheme = 'default' | 'custom';
|
||||
|
||||
type KeyConfig = {
|
||||
text?: number | string;
|
||||
type?: KeyType;
|
||||
color?: string;
|
||||
wider?: boolean;
|
||||
};
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
title: String,
|
||||
zIndex: [Number, String],
|
||||
teleport: [String, Object] as PropType<TeleportProps['to']>,
|
||||
randomKeyOrder: Boolean,
|
||||
closeButtonText: String,
|
||||
deleteButtonText: String,
|
||||
closeButtonLoading: Boolean,
|
||||
theme: {
|
||||
type: String as PropType<NumberKeyboardTheme>,
|
||||
default: 'default',
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
extraKey: {
|
||||
type: [String, Array] as PropType<string | string[]>,
|
||||
default: '',
|
||||
},
|
||||
maxlength: {
|
||||
type: [Number, String],
|
||||
default: Number.MAX_VALUE,
|
||||
},
|
||||
transition: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
blurOnClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showDeleteKey: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hideOnClickOutside: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
emits: [
|
||||
'show',
|
||||
'hide',
|
||||
'blur',
|
||||
'input',
|
||||
'close',
|
||||
'delete',
|
||||
'update:modelValue',
|
||||
],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
const root = ref<HTMLElement>();
|
||||
|
||||
const genBasicKeys = () => {
|
||||
const keys: KeyConfig[] = [];
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
keys.push({ text: i });
|
||||
}
|
||||
|
||||
if (props.randomKeyOrder) {
|
||||
keys.sort(() => (Math.random() > 0.5 ? 1 : -1));
|
||||
}
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
||||
const genDefaultKeys = (): KeyConfig[] => [
|
||||
...genBasicKeys(),
|
||||
{ text: props.extraKey as string, type: 'extra' },
|
||||
{ text: 0 },
|
||||
{
|
||||
text: props.showDeleteKey ? props.deleteButtonText : '',
|
||||
type: props.showDeleteKey ? 'delete' : '',
|
||||
},
|
||||
];
|
||||
|
||||
const genCustomKeys = () => {
|
||||
const keys = genBasicKeys();
|
||||
const { extraKey } = props;
|
||||
const extraKeys = Array.isArray(extraKey) ? extraKey : [extraKey];
|
||||
|
||||
if (extraKeys.length === 1) {
|
||||
keys.push(
|
||||
{ text: 0, wider: true },
|
||||
{ text: extraKeys[0], type: 'extra' }
|
||||
);
|
||||
} else if (extraKeys.length === 2) {
|
||||
keys.push(
|
||||
{ text: extraKeys[0], type: 'extra' },
|
||||
{ text: 0 },
|
||||
{ text: extraKeys[1], type: 'extra' }
|
||||
);
|
||||
}
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
||||
const keys = computed(() =>
|
||||
props.theme === 'custom' ? genCustomKeys() : genDefaultKeys()
|
||||
);
|
||||
|
||||
const onBlur = () => {
|
||||
if (props.show) {
|
||||
emit('blur');
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
emit('close');
|
||||
|
||||
if (props.blurOnClose) {
|
||||
onBlur();
|
||||
}
|
||||
};
|
||||
|
||||
const onAnimationEnd = () => {
|
||||
emit(props.show ? 'show' : 'hide');
|
||||
};
|
||||
|
||||
const onPress = (text: string, type: KeyType) => {
|
||||
if (text === '') {
|
||||
if (type === 'extra') {
|
||||
onBlur();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const value = props.modelValue;
|
||||
|
||||
if (type === 'delete') {
|
||||
emit('delete');
|
||||
emit('update:modelValue', value.slice(0, value.length - 1));
|
||||
} else if (type === 'close') {
|
||||
onClose();
|
||||
} else if (value.length < props.maxlength) {
|
||||
emit('input', text);
|
||||
emit('update:modelValue', value + text);
|
||||
}
|
||||
};
|
||||
|
||||
const renderTitle = () => {
|
||||
const { title, theme, closeButtonText } = props;
|
||||
const leftSlot = slots['title-left'];
|
||||
const showClose = closeButtonText && theme === 'default';
|
||||
const showTitle = title || showClose || leftSlot;
|
||||
|
||||
if (!showTitle) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={bem('header')}>
|
||||
{leftSlot && <span class={bem('title-left')}>{leftSlot()}</span>}
|
||||
{title && <h2 class={bem('title')}>{title}</h2>}
|
||||
{showClose && (
|
||||
<button type="button" class={bem('close')} onClick={onClose}>
|
||||
{closeButtonText}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderKeys = () => {
|
||||
return keys.value.map((key) => {
|
||||
const keySlots: Record<string, Slot | undefined> = {};
|
||||
|
||||
if (key.type === 'delete') {
|
||||
keySlots.default = slots.delete;
|
||||
}
|
||||
if (key.type === 'extra') {
|
||||
keySlots.default = slots['extra-key'];
|
||||
}
|
||||
|
||||
return (
|
||||
<Key
|
||||
v-slots={keySlots}
|
||||
key={key.text}
|
||||
text={key.text}
|
||||
type={key.type}
|
||||
wider={key.wider}
|
||||
color={key.color}
|
||||
onPress={onPress}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const renderSidebar = () => {
|
||||
if (props.theme === 'custom') {
|
||||
return (
|
||||
<div class={bem('sidebar')}>
|
||||
{props.showDeleteKey && (
|
||||
<Key
|
||||
v-slots={{ delete: slots.delete }}
|
||||
large
|
||||
text={props.deleteButtonText}
|
||||
type="delete"
|
||||
onPress={onPress}
|
||||
/>
|
||||
)}
|
||||
<Key
|
||||
large
|
||||
text={props.closeButtonText}
|
||||
type="close"
|
||||
color="blue"
|
||||
loading={props.closeButtonLoading}
|
||||
onPress={onPress}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
(value) => {
|
||||
if (!props.transition) {
|
||||
emit(value ? 'show' : 'hide');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (props.hideOnClickOutside) {
|
||||
useClickAway(root, onClose, { eventName: 'touchstart' });
|
||||
}
|
||||
|
||||
return () => {
|
||||
const Title = renderTitle();
|
||||
const Content = (
|
||||
<Transition name={props.transition ? 'van-slide-up' : ''}>
|
||||
<div
|
||||
v-show={props.show}
|
||||
ref={root}
|
||||
style={{
|
||||
zIndex: props.zIndex !== undefined ? +props.zIndex : undefined,
|
||||
}}
|
||||
class={bem({
|
||||
unfit: !props.safeAreaInsetBottom,
|
||||
'with-title': !!Title,
|
||||
})}
|
||||
onTouchstart={stopPropagation}
|
||||
onAnimationend={onAnimationEnd}
|
||||
// @ts-ignore
|
||||
onWebkitAnimationEnd={onAnimationEnd}
|
||||
>
|
||||
{Title}
|
||||
<div class={bem('body')}>
|
||||
<div class={bem('keys')}>{renderKeys()}</div>
|
||||
{renderSidebar()}
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
|
||||
if (props.teleport) {
|
||||
return <Teleport to={props.teleport}>{Content}</Teleport>;
|
||||
}
|
||||
|
||||
return Content;
|
||||
};
|
||||
},
|
||||
});
|
Reference in New Issue
Block a user