Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add KeyboardEvents to select; fix table some bugs #2683

Merged
merged 14 commits into from
Aug 15, 2023
6 changes: 5 additions & 1 deletion src/checkbox/hooks/useKeyboardEvent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export const CHECKED_CODE_REG = /(enter|space)/i;

export function useKeyboardEvent(handleChange: (e: Event) => void) {
const keyboardEventListener = (e: KeyboardEvent) => {
if (e.code === 'Enter') {
const isCheckedCode = CHECKED_CODE_REG.test(e.key) || CHECKED_CODE_REG.test(e.code);
if (isCheckedCode) {
e.preventDefault();
const { disabled } = (e.currentTarget as HTMLElement).querySelector('input');
!disabled && handleChange(e);
}
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useElementLazyRender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function useElementLazyRender(labelRef: Ref<HTMLElement>, lazyLoad: Ref<b
const showElement = ref(true);

const handleLazyLoad = () => {
if (!lazyLoad.value) return;
if (!lazyLoad.value || !labelRef.value || ioObserver.value) return;
showElement.value = false;
const io = observe(
labelRef.value,
Expand All @@ -23,11 +23,11 @@ export function useElementLazyRender(labelRef: Ref<HTMLElement>, lazyLoad: Ref<b

onMounted(handleLazyLoad);

lazyLoad.value && watch([lazyLoad], handleLazyLoad);
lazyLoad.value && watch([lazyLoad, labelRef], handleLazyLoad);

onBeforeUnmount(() => {
if (!lazyLoad.value) return;
ioObserver.value?.unobserve(labelRef.value);
ioObserver.value?.unobserve?.(labelRef.value);
});

return {
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useVirtualScrollNew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const useVirtualScroll = (container: Ref<HTMLElement>, params: UseVirtualScrollP
/** 注意测试:数据长度为空;数据长度小于表格高度等情况。即期望只有数据量达到一定程度才允许开启虚拟滚动 */
const visibleData = ref<any[]>([]);
// 用于显示表格列
const translateY = ref(0);
const translateY = ref((params.value.data?.length || 0) * (params.value.scroll?.rowHeight || 50));
// 滚动高度,用于显示滚动条
const scrollHeight = ref(0);
const trScrollTopHeightList = ref<number[]>([]);
Expand Down
21 changes: 10 additions & 11 deletions src/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -525,17 +525,16 @@ export default mixins(
<span class={[`${this.componentName}__prefix`, `${this.componentName}__prefix-icon`]}>{prefixIcon}</span>
) : null}
{labelContent}
{this.showInput && (
<input
attrs={this.inputAttrs}
on={inputEvents}
ref="inputRef"
class={`${this.componentName}__inner`}
value={inputTextValue}
onInput={this.handleInput}
title={this.disabled ? inputTextValue : undefined}
/>
)}
{/* input element must exist, or other select components can not focus by keyboard operation */}
<input
attrs={this.inputAttrs}
on={inputEvents}
ref="inputRef"
class={[`${this.componentName}__inner`, { [`${this.componentName}--soft-hidden`]: !this.showInput }]}
value={inputTextValue}
onInput={this.handleInput}
title={this.disabled ? inputTextValue : undefined}
/>
{this.autoWidth && (
<span ref="inputPreRef" class={`${this.classPrefix}-input__input-pre`}>
{this.preValue || this.tPlaceholder}
Expand Down
5 changes: 4 additions & 1 deletion src/radio/group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { emitEvent } from '../utils/event';
import { getClassPrefixMixins } from '../config-provider/config-receiver';
import mixins from '../utils/mixins';
import { off, on } from '../utils/dom';
import { CHECKED_CODE_REG } from '../checkbox/hooks/useKeyboardEvent';

const classPrefixMixins = getClassPrefixMixins('radio-group');

Expand Down Expand Up @@ -113,7 +114,9 @@ export default mixins(classPrefixMixins).extend({

// 注意:此处会还原区分 数字 和 数字字符串
checkRadioInGroup(e: KeyboardEvent) {
if (/enter/i.test(e.key) || /enter/i.test(e.code)) {
const isCheckedCode = CHECKED_CODE_REG.test(e.key) || CHECKED_CODE_REG.test(e.code);
if (isCheckedCode) {
e.preventDefault();
const inputNode = (e.target as HTMLElement).querySelector('input');
const data = inputNode.dataset;
if (inputNode.checked && data.allowUncheck) {
Expand Down
4 changes: 4 additions & 0 deletions src/select-input/_example/single.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@popup-visible-change="onPopupVisibleChange"
@clear="onClear"
@input-change="onInputChange"
@focus="onFocus"
>
<template #panel>
<ul class="tdesign-demo__select-input-ul-single">
Expand Down Expand Up @@ -61,6 +62,9 @@ export default {
// 过滤功能
console.log(val, context);
},
onFocus(val, context) {
console.log('focus:', val, context);
},
},
};
</script>
Expand Down
49 changes: 45 additions & 4 deletions src/select-input/select-input.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import {
computed, defineComponent, ref, SetupContext, toRefs,
computed,
defineComponent,
onBeforeUnmount,
onMounted,
ref,
SetupContext,
toRefs,
watch,
} from '@vue/composition-api';
import Popup, { PopupVisibleChangeContext } from '../popup';
import props from './props';
Expand Down Expand Up @@ -29,20 +36,27 @@ export default defineComponent({
} = toRefs(props);

const {
commonInputProps, singleInputValue, onInnerClear, renderSelectSingle,
} = useSingle(props, context);
const { multipleInputValue, renderSelectMultiple } = useMultiple(props, context);
isSingleFocus, commonInputProps, singleInputValue, onInnerClear, renderSelectSingle,
} = useSingle(
props,
context,
);
const { multipleInputValue, isMultipleFocus, renderSelectMultiple } = useMultiple(props, context);
const { tOverlayInnerStyle, innerPopupVisible, onInnerPopupVisibleChange } = useOverlayInnerStyle(props, {
afterHidePopup: onInnerBlur,
});

const isFocus = computed(() => (props.multiple ? isMultipleFocus.value : isSingleFocus.value));

// SelectInput.blur is not equal to Input or TagInput, example: click popup panel.
// if trigger blur on click popup panel, filter data of tree select can not be checked.
function onInnerBlur(ctx: PopupVisibleChangeContext) {
const inputValue = props.multiple ? multipleInputValue.value : singleInputValue.value;
const params: Parameters<TdSelectInputProps['onBlur']>[1] = { e: ctx.e, inputValue };
props.onBlur?.(props.value, params);
context.emit('blur', props.value, params);
isSingleFocus.value = false;
isMultipleFocus.value = false;
}

const classes = computed(() => [
Expand All @@ -55,6 +69,33 @@ export default defineComponent({
},
]);

const addKeyboardEventListener = (e: KeyboardEvent) => {
if (/(ArrowDown|ArrowUp)/.test(e.code || e.key)) {
const ctx: PopupVisibleChangeContext = { ...context, trigger: 'trigger-element-focus' };
props.onPopupVisibleChange?.(true, ctx);
context.emit('popup-visible-change', true, ctx);
}
};

watch([isFocus], ([isFocus]) => {
if (popupVisible.value) return;
if (isFocus) {
selectInputRef.value.addEventListener('keydown', addKeyboardEventListener);
} else {
selectInputRef.value.removeEventListener('keydown', addKeyboardEventListener);
}
});

onMounted(() => {
if (!popupVisible.value && isFocus) {
selectInputRef.value.addEventListener('keydown', addKeyboardEventListener);
}
});

onBeforeUnmount(() => {
selectInputRef.value.removeEventListener('keydown', addKeyboardEventListener);
});

return {
selectInputRef,
innerPopupVisible,
Expand Down
51 changes: 30 additions & 21 deletions src/select-input/useMultiple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import isObject from 'lodash/isObject';
import Vue from 'vue';
import { TdSelectInputProps, SelectInputKeys } from './type';
import { SelectInputCommonProperties } from './interface';
import { InputValue } from '../input';
import TagInput, { TagInputValue, InputValueChangeContext, TagInputProps } from '../tag-input';
import TagInput, { TagInputValue, TagInputProps } from '../tag-input';
import Loading from '../loading';
import useDefaultValue from '../hooks/useDefaultValue';
import { usePrefixClass } from '../hooks/useConfig';
Expand All @@ -27,6 +26,7 @@ export default function useMultiple(props: TdSelectInputProps, context: SetupCon
const { inputValue } = toRefs(props);
const classPrefix = usePrefixClass();
const tagInputRef = ref();
const isMultipleFocus = ref(props.autofocus);
const [tInputValue, setTInputValue] = useDefaultValue(
inputValue,
props.defaultInputValue,
Expand All @@ -52,6 +52,30 @@ export default function useMultiple(props: TdSelectInputProps, context: SetupCon
props.onTagChange?.(val, ctx);
context.emit('tag-change', val, ctx);
};

/**
* 筛选器统一特性:
* 1. 筛选器按下回车时不清空输入框;
* 2. SelectInput 的失焦不等于 TagInput。如点击下拉面板时,TagInput 失去焦点,但 SelectInput 依旧保持聚焦,允许继续选择。
*/
const onInputChange: TagInputProps['onInputChange'] = (val, ctx) => {
if (ctx.trigger === 'enter' || ctx.trigger === 'blur') return;
setTInputValue(val, { trigger: ctx.trigger, e: ctx.e });
};

const onFocus: TagInputProps['onFocus'] = (val, ctx) => {
isMultipleFocus.value = true;
const params = { ...ctx, tagInputValue: val };
props.onFocus?.(props.value, params);
context.emit('focus', props.value, params);
};

const onEnter: TagInputProps['onEnter'] = (val, ctx) => {
const params = { ...ctx, tagInputValue: val };
props.onEnter?.(props.value, params);
context.emit('focus', props.value, params);
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const renderSelectMultiple = (p: RenderSelectMultipleParams, h: Vue.CreateElement) => {
const tagInputProps = {
Expand Down Expand Up @@ -88,29 +112,13 @@ export default function useMultiple(props: TdSelectInputProps, context: SetupCon
scopedSlots={slots}
props={tagInputProps}
on={{
'input-change': (val: InputValue, ctx: InputValueChangeContext) => {
/**
* 筛选器统一特性:
* 1. 筛选器按下回车时不清空输入框;
* 2. SelectInput 的失焦不等于 TagInput。如点击下拉面板时,TagInput 失去焦点,但 SelectInput 依旧保持聚焦,允许继续选择。
*/
if (ctx.trigger === 'enter' || ctx.trigger === 'blur') return;
setTInputValue(val, { trigger: ctx.trigger, e: ctx.e });
},
'input-change': onInputChange,
...newListeners,
change: onTagInputChange,
clear: p.onInnerClear,
// [Important Info]: SelectInput.blur is not equal to TagInput, example: click popup panel
focus: (val: TagInputValue, ctx: { inputValue: InputValue; e: FocusEvent }) => {
const params = { ...ctx, tagInputValue: val };
props.onFocus?.(props.value, params);
context.emit('focus', props.value, params);
},
enter: (val: TagInputValue, ctx: { e: KeyboardEvent; inputValue: InputValue }) => {
const params = { ...ctx, tagInputValue: val };
props.onEnter?.(props.value, params);
context.emit('focus', props.value, params);
},
focus: onFocus,
enter: onEnter,
}}
/>
);
Expand All @@ -121,6 +129,7 @@ export default function useMultiple(props: TdSelectInputProps, context: SetupCon
tPlaceholder,
tagInputRef,
multipleInputValue: tInputValue,
isMultipleFocus,
renderSelectMultiple,
};
}
60 changes: 34 additions & 26 deletions src/select-input/useSingle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import isObject from 'lodash/isObject';
import pick from 'lodash/pick';
import { SelectInputCommonProperties } from './interface';
import { TdSelectInputProps } from './type';

import Input, { InputValue } from '../input';
import Input, { InputProps, InputValue } from '../input';
import Loading from '../loading';

import { useTNodeJSX } from '../hooks/tnode';
import { usePrefixClass } from '../hooks/useConfig';
import useDefaultValue from '../hooks/useDefaultValue';
Expand Down Expand Up @@ -39,7 +37,6 @@ function getInputValue(value: TdSelectInputProps['value'], keys: TdSelectInputPr

export default function useSingle(props: TdSelectInputProps, context: SetupContext) {
const instance = getCurrentInstance();

const { value, keys, inputValue: propsInputValue } = toRefs(props);
const classPrefix = usePrefixClass();

Expand All @@ -51,6 +48,7 @@ export default function useSingle(props: TdSelectInputProps, context: SetupConte
'input-change',
);

const isSingleFocus = ref(props.autofocus);
const inputRef = ref();
const renderTNode = useTNodeJSX();

Expand All @@ -69,6 +67,32 @@ export default function useSingle(props: TdSelectInputProps, context: SetupConte
}
};

const onEnter: InputProps['onEnter'] = (val, context) => {
props.onEnter?.(value.value, { ...context, inputValue: val });
instance.emit('enter', value.value, { ...context, inputValue: val });
};

const onFocus: InputProps['onFocus'] = (val, context) => {
isSingleFocus.value = true;
props.onFocus?.(value.value, { ...context, inputValue: val });
instance.emit('focus', value.value, { ...context, tagInputValue: val });
};

const onPaste: InputProps['onPaste'] = (context) => {
props.onPaste?.(context);
instance.emit('paste', context);
};

const onMouseenter: InputProps['onMouseenter'] = (context) => {
props.onMouseenter?.(context);
instance.emit('mouseenter', context);
};

const onMouseleave: InputProps['onMouseleave'] = (context: { e: MouseEvent }) => {
props.onMouseleave?.(context);
instance.emit('mouseenter', context);
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const renderSelectSingle = (h: Vue.CreateElement, popupVisible: boolean) => {
const singleValueDisplay = renderTNode('valueDisplay');
Expand Down Expand Up @@ -100,29 +124,12 @@ export default function useSingle(props: TdSelectInputProps, context: SetupConte
scopedSlots={slots}
onChange={onInnerInputChange}
onClear={onInnerClear}
onEnter={(val: InputValue, context: { e: KeyboardEvent }) => {
props.onEnter?.(value.value, { ...context, inputValue: val });
instance.emit('enter', value.value, { ...context, inputValue: val });
}}
onEnter={onEnter}
// [Important Info]: SelectInput.blur is not equal to Input, example: click popup panel
onFocus={(val: InputValue, context: { e: MouseEvent }) => {
props.onFocus?.(value.value, { ...context, inputValue: val });
instance.emit('focus', value.value, { ...context, tagInputValue: val });
// TO Discuss: focus might not need to change input value. it will caught some curious errors in tree-select
// !popupVisible && setInputValue(getInputValue(value.value, keys.value), { ...context, trigger: 'focus' }); // 聚焦时拿到value
}}
onPaste={(context: { e: ClipboardEvent; pasteValue: string }) => {
props.onPaste?.(context);
instance.emit('paste', context);
}}
onMouseenter={(context: { e: MouseEvent }) => {
props.onMouseenter?.(context);
instance.emit('mouseenter', context);
}}
onMouseleave={(context: { e: MouseEvent }) => {
props.onMouseleave?.(context);
instance.emit('mouseenter', context);
}}
onFocus={onFocus}
onPaste={onPaste}
onMouseenter={onMouseenter}
onMouseleave={onMouseleave}
/>
);
};
Expand All @@ -131,6 +138,7 @@ export default function useSingle(props: TdSelectInputProps, context: SetupConte
inputRef,
commonInputProps,
singleInputValue: inputValue,
isSingleFocus,
onInnerClear,
renderSelectSingle,
};
Expand Down
Loading
Loading