refactor: tree-select
parent
8e38ed883c
commit
1581943eb0
|
@ -2,13 +2,12 @@ import type { App, PropType, Plugin, ExtractPropTypes } from 'vue';
|
|||
import { computed, defineComponent, ref } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import { selectProps as vcSelectProps } from '../vc-select';
|
||||
import RcSelect, { Option, OptGroup, selectBaseProps } from '../vc-select';
|
||||
import RcSelect, { Option, OptGroup } from '../vc-select';
|
||||
import type { OptionProps as OptionPropsType } from '../vc-select/Option';
|
||||
import getIcons from './utils/iconUtil';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { tuple } from '../_util/type';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import type { SizeType } from '../config-provider';
|
||||
import omit from '../_util/omit';
|
||||
|
||||
type RawValue = string | number;
|
||||
|
@ -62,7 +61,7 @@ const Select = defineComponent({
|
|||
'option',
|
||||
],
|
||||
setup(props, { attrs, emit, slots, expose }) {
|
||||
const selectRef = ref(null);
|
||||
const selectRef = ref();
|
||||
|
||||
const focus = () => {
|
||||
if (selectRef.value) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import getIcons from '../select/utils/iconUtil';
|
|||
import renderSwitcherIcon from '../tree/utils/iconUtil';
|
||||
import type { AntTreeNodeProps } from '../tree/Tree';
|
||||
import { warning } from '../vc-util/warning';
|
||||
import { flattenChildren } from '../_util/props-util';
|
||||
|
||||
const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => {
|
||||
if (transitionName !== undefined) {
|
||||
|
@ -80,7 +81,7 @@ const TreeSelect = defineComponent({
|
|||
setup(props, { attrs, slots, expose, emit }) {
|
||||
warning(
|
||||
!(props.treeData === undefined && slots.default),
|
||||
'`children` of Tree is deprecated. Please use `treeData` instead.',
|
||||
'`children` of TreeSelect is deprecated. Please use `treeData` instead.',
|
||||
);
|
||||
watchEffect(() => {
|
||||
devWarning(
|
||||
|
@ -194,6 +195,10 @@ const TreeSelect = defineComponent({
|
|||
attrs.class,
|
||||
);
|
||||
const rootPrefixCls = configProvider.getPrefixCls();
|
||||
const otherProps: any = {};
|
||||
if (props.treeData === undefined && slots.default) {
|
||||
otherProps.children = flattenChildren(slots.default());
|
||||
}
|
||||
return (
|
||||
<VcTreeSelect
|
||||
{...attrs}
|
||||
|
@ -227,7 +232,7 @@ const TreeSelect = defineComponent({
|
|||
...slots,
|
||||
treeCheckable: () => <span class={`${prefixCls.value}-tree-checkbox-inner`} />,
|
||||
}}
|
||||
children={slots.default?.()}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -42,7 +42,8 @@ import {
|
|||
flattenOptions,
|
||||
fillOptionsWithMissingValue,
|
||||
} from './utils/valueUtil';
|
||||
import { selectBaseProps, SelectProps } from './generate';
|
||||
import type { SelectProps } from './generate';
|
||||
import { selectBaseProps } from './generate';
|
||||
import generateSelector from './generate';
|
||||
import type { DefaultValueType } from './interface/generator';
|
||||
import warningProps from './utils/warningPropsUtil';
|
||||
|
@ -79,7 +80,7 @@ const Select = defineComponent({
|
|||
OptGroup,
|
||||
props: RefSelect.props,
|
||||
setup(props, { attrs, expose, slots }) {
|
||||
const selectRef = ref(null);
|
||||
const selectRef = ref();
|
||||
expose({
|
||||
focus: () => {
|
||||
selectRef.value?.focus();
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import pickAttrs from '../../_util/pickAttrs';
|
||||
import Input from './Input';
|
||||
import type { InnerSelectorProps } from './interface';
|
||||
import { Fragment, Suspense, VNodeChild } from 'vue';
|
||||
import type { VNodeChild } from 'vue';
|
||||
import { Fragment } from 'vue';
|
||||
import { computed, defineComponent, ref, watch } from 'vue';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { useInjectTreeSelectContext } from 'ant-design-vue/es/vc-tree-select/Context';
|
||||
|
|
|
@ -52,7 +52,7 @@ export interface SelectorProps {
|
|||
// Motion
|
||||
choiceTransitionName?: string;
|
||||
|
||||
onToggleOpen: (open?: boolean) => void;
|
||||
onToggleOpen: (open?: boolean) => void | any;
|
||||
/** `onSearch` returns go next step boolean to check if need do toggle open */
|
||||
onSearch: (searchText: string, fromTyping: boolean, isCompositing: boolean) => boolean;
|
||||
onSearchSubmit: (searchText: string) => void;
|
||||
|
|
|
@ -37,6 +37,7 @@ import useSelectTriggerControl from './hooks/useSelectTriggerControl';
|
|||
import useCacheDisplayValue from './hooks/useCacheDisplayValue';
|
||||
import useCacheOptions from './hooks/useCacheOptions';
|
||||
import type { CSSProperties, DefineComponent, PropType, VNode, VNodeChild } from 'vue';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
|
@ -275,7 +276,7 @@ export default function generateSelector<
|
|||
slots: ['option'],
|
||||
inheritAttrs: false,
|
||||
props: selectBaseProps<OptionType, DefaultValueType>(),
|
||||
setup(props, { expose }) {
|
||||
setup(props, { expose, attrs, slots }) {
|
||||
const useInternalProps = computed(
|
||||
() => props.internalProps && props.internalProps.mark === INTERNAL_PROPS_MARK,
|
||||
);
|
||||
|
@ -284,10 +285,10 @@ export default function generateSelector<
|
|||
'Select',
|
||||
'optionFilterProp not support children, please use label instead',
|
||||
);
|
||||
const containerRef = ref(null);
|
||||
const triggerRef = ref(null);
|
||||
const selectorRef = ref(null);
|
||||
const listRef = ref(null);
|
||||
const containerRef = ref();
|
||||
const triggerRef = ref();
|
||||
const selectorRef = ref();
|
||||
const listRef = ref();
|
||||
const tokenWithEnter = computed(() =>
|
||||
(props.tokenSeparators || []).some(tokenSeparator =>
|
||||
['\n', '\r\n'].includes(tokenSeparator),
|
||||
|
@ -353,7 +354,7 @@ export default function generateSelector<
|
|||
|
||||
// ============================= Option =============================
|
||||
// Set by option list active, it will merge into search input when mode is `combobox`
|
||||
const activeValue = ref(null);
|
||||
const activeValue = ref();
|
||||
const setActiveValue = (val: string) => {
|
||||
activeValue.value = val;
|
||||
};
|
||||
|
@ -925,7 +926,7 @@ export default function generateSelector<
|
|||
};
|
||||
|
||||
// ============================= Popup ==============================
|
||||
const containerWidth = ref(null);
|
||||
const containerWidth = ref<number>(null);
|
||||
onMounted(() => {
|
||||
watch(
|
||||
triggerOpen,
|
||||
|
@ -952,342 +953,270 @@ export default function generateSelector<
|
|||
blur,
|
||||
scrollTo: (...args: any[]) => listRef.value?.scrollTo(...args),
|
||||
});
|
||||
return {
|
||||
tokenWithEnter,
|
||||
mockFocused,
|
||||
mergedId,
|
||||
containerWidth,
|
||||
onActiveValue,
|
||||
accessibilityIndex,
|
||||
mergedDefaultActiveFirstOption,
|
||||
onInternalMouseDown,
|
||||
onContainerFocus,
|
||||
onContainerBlur,
|
||||
onInternalKeyDown,
|
||||
isMultiple,
|
||||
mergedOpen,
|
||||
displayOptions,
|
||||
displayFlattenOptions,
|
||||
rawValues,
|
||||
onInternalOptionSelect,
|
||||
onToggleOpen,
|
||||
mergedSearchValue,
|
||||
useInternalProps,
|
||||
triggerChange,
|
||||
triggerSearch,
|
||||
mergedRawValue,
|
||||
mergedShowSearch,
|
||||
onInternalKeyUp,
|
||||
triggerOpen,
|
||||
mergedOptions,
|
||||
onInternalSelectionSelect,
|
||||
selectorDomRef,
|
||||
displayValues,
|
||||
activeValue,
|
||||
onSearchSubmit,
|
||||
containerRef,
|
||||
listRef,
|
||||
triggerRef,
|
||||
selectorRef,
|
||||
const instance = getCurrentInstance();
|
||||
const onPopupMouseEnter = () => {
|
||||
// We need force update here since popup dom is render async
|
||||
instance.update();
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// We need force update here since popup dom is render async
|
||||
onPopupMouseEnter() {
|
||||
(this as any).$forceUpdate();
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const {
|
||||
tokenWithEnter,
|
||||
mockFocused,
|
||||
mergedId,
|
||||
containerWidth,
|
||||
onActiveValue,
|
||||
accessibilityIndex,
|
||||
mergedDefaultActiveFirstOption,
|
||||
onInternalMouseDown,
|
||||
onInternalKeyDown,
|
||||
isMultiple,
|
||||
mergedOpen,
|
||||
displayOptions,
|
||||
displayFlattenOptions,
|
||||
rawValues,
|
||||
onInternalOptionSelect,
|
||||
onToggleOpen,
|
||||
mergedSearchValue,
|
||||
onPopupMouseEnter,
|
||||
useInternalProps,
|
||||
triggerChange,
|
||||
triggerSearch,
|
||||
mergedRawValue,
|
||||
mergedShowSearch,
|
||||
onInternalKeyUp,
|
||||
triggerOpen,
|
||||
mergedOptions,
|
||||
onInternalSelectionSelect,
|
||||
selectorDomRef,
|
||||
displayValues,
|
||||
activeValue,
|
||||
onSearchSubmit,
|
||||
$slots: slots,
|
||||
} = this as any;
|
||||
const {
|
||||
prefixCls = defaultPrefixCls,
|
||||
id,
|
||||
return () => {
|
||||
const {
|
||||
prefixCls = defaultPrefixCls,
|
||||
id,
|
||||
|
||||
open,
|
||||
defaultOpen,
|
||||
options,
|
||||
children,
|
||||
open,
|
||||
defaultOpen,
|
||||
options,
|
||||
children,
|
||||
|
||||
mode,
|
||||
value,
|
||||
defaultValue,
|
||||
labelInValue,
|
||||
mode,
|
||||
value,
|
||||
defaultValue,
|
||||
labelInValue,
|
||||
|
||||
// Search related
|
||||
showSearch,
|
||||
inputValue,
|
||||
searchValue,
|
||||
filterOption,
|
||||
optionFilterProp,
|
||||
autoClearSearchValue,
|
||||
onSearch,
|
||||
// Search related
|
||||
showSearch,
|
||||
inputValue,
|
||||
searchValue,
|
||||
filterOption,
|
||||
optionFilterProp,
|
||||
autoClearSearchValue,
|
||||
onSearch,
|
||||
|
||||
// Icons
|
||||
allowClear,
|
||||
clearIcon,
|
||||
showArrow,
|
||||
inputIcon,
|
||||
menuItemSelectedIcon,
|
||||
// Icons
|
||||
allowClear,
|
||||
clearIcon,
|
||||
showArrow,
|
||||
inputIcon,
|
||||
menuItemSelectedIcon,
|
||||
|
||||
// Others
|
||||
disabled,
|
||||
loading,
|
||||
defaultActiveFirstOption,
|
||||
notFoundContent = 'Not Found',
|
||||
optionLabelProp,
|
||||
backfill,
|
||||
getInputElement,
|
||||
getPopupContainer,
|
||||
// Others
|
||||
disabled,
|
||||
loading,
|
||||
defaultActiveFirstOption,
|
||||
notFoundContent = 'Not Found',
|
||||
optionLabelProp,
|
||||
backfill,
|
||||
getInputElement,
|
||||
getPopupContainer,
|
||||
|
||||
// Dropdown
|
||||
listHeight = 200,
|
||||
listItemHeight = 20,
|
||||
animation,
|
||||
transitionName,
|
||||
virtual,
|
||||
dropdownStyle,
|
||||
dropdownClassName,
|
||||
dropdownMatchSelectWidth,
|
||||
dropdownRender,
|
||||
dropdownAlign,
|
||||
showAction,
|
||||
direction,
|
||||
// Dropdown
|
||||
listHeight = 200,
|
||||
listItemHeight = 20,
|
||||
animation,
|
||||
transitionName,
|
||||
virtual,
|
||||
dropdownStyle,
|
||||
dropdownClassName,
|
||||
dropdownMatchSelectWidth,
|
||||
dropdownRender,
|
||||
dropdownAlign,
|
||||
showAction,
|
||||
direction,
|
||||
|
||||
// Tags
|
||||
tokenSeparators,
|
||||
tagRender,
|
||||
// Tags
|
||||
tokenSeparators,
|
||||
tagRender,
|
||||
|
||||
// Events
|
||||
onPopupScroll,
|
||||
onDropdownVisibleChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onKeyup,
|
||||
onKeydown,
|
||||
onMousedown,
|
||||
// Events
|
||||
onPopupScroll,
|
||||
onDropdownVisibleChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onKeyup,
|
||||
onKeydown,
|
||||
onMousedown,
|
||||
|
||||
onChange,
|
||||
onSelect,
|
||||
onDeselect,
|
||||
onClear,
|
||||
onChange,
|
||||
onSelect,
|
||||
onDeselect,
|
||||
onClear,
|
||||
|
||||
internalProps = {},
|
||||
internalProps = {},
|
||||
|
||||
...restProps
|
||||
} = this.$props; //as SelectProps<OptionType[], ValueType>;
|
||||
...restProps
|
||||
} = props; //as SelectProps<OptionType[], ValueType>;
|
||||
// ============================= Input ==============================
|
||||
// Only works in `combobox`
|
||||
const customizeInputElement: VNodeChild | JSX.Element =
|
||||
(mode === 'combobox' && getInputElement && getInputElement()) || null;
|
||||
|
||||
// ============================= Input ==============================
|
||||
// Only works in `combobox`
|
||||
const customizeInputElement: VNodeChild | JSX.Element =
|
||||
(mode === 'combobox' && getInputElement && getInputElement()) || null;
|
||||
|
||||
const domProps = omitDOMProps ? omitDOMProps(restProps) : restProps;
|
||||
DEFAULT_OMIT_PROPS.forEach(prop => {
|
||||
delete domProps[prop];
|
||||
});
|
||||
const popupNode = (
|
||||
<OptionList
|
||||
ref="listRef"
|
||||
prefixCls={prefixCls}
|
||||
id={mergedId}
|
||||
open={mergedOpen}
|
||||
childrenAsData={!options}
|
||||
options={displayOptions}
|
||||
flattenOptions={displayFlattenOptions}
|
||||
multiple={isMultiple}
|
||||
values={rawValues}
|
||||
height={listHeight}
|
||||
itemHeight={listItemHeight}
|
||||
onSelect={onInternalOptionSelect}
|
||||
onToggleOpen={onToggleOpen}
|
||||
onActiveValue={onActiveValue}
|
||||
defaultActiveFirstOption={mergedDefaultActiveFirstOption}
|
||||
notFoundContent={notFoundContent}
|
||||
onScroll={onPopupScroll}
|
||||
searchValue={mergedSearchValue}
|
||||
menuItemSelectedIcon={menuItemSelectedIcon}
|
||||
virtual={virtual !== false && dropdownMatchSelectWidth !== false}
|
||||
onMouseenter={onPopupMouseEnter}
|
||||
v-slots={{ ...slots, option: slots.option }}
|
||||
/>
|
||||
);
|
||||
|
||||
// ============================= Clear ==============================
|
||||
let clearNode: VNode | JSX.Element;
|
||||
const onClearMouseDown = () => {
|
||||
// Trigger internal `onClear` event
|
||||
if (useInternalProps && internalProps.onClear) {
|
||||
internalProps.onClear();
|
||||
}
|
||||
|
||||
if (onClear) {
|
||||
onClear();
|
||||
}
|
||||
|
||||
triggerChange([]);
|
||||
triggerSearch('', false, false);
|
||||
};
|
||||
|
||||
if (!disabled && allowClear && (mergedRawValue.length || mergedSearchValue)) {
|
||||
clearNode = (
|
||||
<TransBtn
|
||||
class={`${prefixCls}-clear`}
|
||||
onMousedown={onClearMouseDown}
|
||||
customizeIcon={clearIcon}
|
||||
>
|
||||
×
|
||||
</TransBtn>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================= Arrow ==============================
|
||||
const mergedShowArrow =
|
||||
showArrow !== undefined ? showArrow : loading || (!isMultiple && mode !== 'combobox');
|
||||
let arrowNode: VNode | JSX.Element;
|
||||
|
||||
if (mergedShowArrow) {
|
||||
arrowNode = (
|
||||
<TransBtn
|
||||
class={classNames(`${prefixCls}-arrow`, {
|
||||
[`${prefixCls}-arrow-loading`]: loading,
|
||||
})}
|
||||
customizeIcon={inputIcon}
|
||||
customizeIconProps={{
|
||||
loading,
|
||||
searchValue: mergedSearchValue,
|
||||
open: mergedOpen,
|
||||
focused: mockFocused,
|
||||
showSearch: mergedShowSearch,
|
||||
}}
|
||||
const domProps = omitDOMProps ? omitDOMProps(restProps) : restProps;
|
||||
DEFAULT_OMIT_PROPS.forEach(prop => {
|
||||
delete domProps[prop];
|
||||
});
|
||||
const popupNode = (
|
||||
<OptionList
|
||||
ref={listRef}
|
||||
prefixCls={prefixCls}
|
||||
id={mergedId.value}
|
||||
open={mergedOpen.value}
|
||||
childrenAsData={!options}
|
||||
options={displayOptions.value}
|
||||
flattenOptions={displayFlattenOptions.value}
|
||||
multiple={isMultiple.value}
|
||||
values={rawValues.value}
|
||||
height={listHeight}
|
||||
itemHeight={listItemHeight}
|
||||
onSelect={onInternalOptionSelect}
|
||||
onToggleOpen={onToggleOpen}
|
||||
onActiveValue={onActiveValue}
|
||||
defaultActiveFirstOption={mergedDefaultActiveFirstOption.value}
|
||||
notFoundContent={notFoundContent}
|
||||
onScroll={onPopupScroll}
|
||||
searchValue={mergedSearchValue.value}
|
||||
menuItemSelectedIcon={menuItemSelectedIcon}
|
||||
virtual={virtual !== false && dropdownMatchSelectWidth !== false}
|
||||
onMouseenter={onPopupMouseEnter}
|
||||
v-slots={{ ...slots, option: slots.option }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================ Warning =============================
|
||||
if (process.env.NODE_ENV !== 'production' && warningProps) {
|
||||
warningProps(this.$props);
|
||||
}
|
||||
// ============================= Clear ==============================
|
||||
let clearNode: VNode | JSX.Element;
|
||||
const onClearMouseDown = () => {
|
||||
// Trigger internal `onClear` event
|
||||
if (useInternalProps.value && internalProps.onClear) {
|
||||
internalProps.onClear();
|
||||
}
|
||||
|
||||
// ============================= Render =============================
|
||||
const mergedClassName = classNames(prefixCls, this.$attrs.class, {
|
||||
[`${prefixCls}-focused`]: mockFocused,
|
||||
[`${prefixCls}-multiple`]: isMultiple,
|
||||
[`${prefixCls}-single`]: !isMultiple,
|
||||
[`${prefixCls}-allow-clear`]: allowClear,
|
||||
[`${prefixCls}-show-arrow`]: mergedShowArrow,
|
||||
[`${prefixCls}-disabled`]: disabled,
|
||||
[`${prefixCls}-loading`]: loading,
|
||||
[`${prefixCls}-open`]: mergedOpen,
|
||||
[`${prefixCls}-customize-input`]: customizeInputElement,
|
||||
[`${prefixCls}-show-search`]: mergedShowSearch,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
{...this.$attrs}
|
||||
class={mergedClassName}
|
||||
{...domProps}
|
||||
ref="containerRef"
|
||||
onMousedown={onInternalMouseDown}
|
||||
onKeydown={onInternalKeyDown}
|
||||
onKeyup={onInternalKeyUp}
|
||||
// onFocus={onContainerFocus} // trigger by input
|
||||
// onBlur={onContainerBlur} // trigger by input
|
||||
>
|
||||
{mockFocused && !mergedOpen && (
|
||||
<span
|
||||
style={{
|
||||
width: 0,
|
||||
height: 0,
|
||||
display: 'flex',
|
||||
overflow: 'hidden',
|
||||
opacity: 0,
|
||||
}}
|
||||
aria-live="polite"
|
||||
if (onClear) {
|
||||
onClear();
|
||||
}
|
||||
|
||||
triggerChange([]);
|
||||
triggerSearch('', false, false);
|
||||
};
|
||||
|
||||
if (!disabled && allowClear && (mergedRawValue.value.length || mergedSearchValue.value)) {
|
||||
clearNode = (
|
||||
<TransBtn
|
||||
class={`${prefixCls}-clear`}
|
||||
onMousedown={onClearMouseDown}
|
||||
customizeIcon={clearIcon}
|
||||
>
|
||||
{/* Merge into one string to make screen reader work as expect */}
|
||||
{`${mergedRawValue.join(', ')}`}
|
||||
</span>
|
||||
)}
|
||||
<SelectTrigger
|
||||
ref="triggerRef"
|
||||
disabled={disabled}
|
||||
prefixCls={prefixCls}
|
||||
visible={triggerOpen}
|
||||
popupElement={popupNode}
|
||||
containerWidth={containerWidth}
|
||||
animation={animation}
|
||||
transitionName={transitionName}
|
||||
dropdownStyle={dropdownStyle}
|
||||
dropdownClassName={dropdownClassName}
|
||||
direction={direction}
|
||||
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
|
||||
dropdownRender={dropdownRender as any}
|
||||
dropdownAlign={dropdownAlign}
|
||||
getPopupContainer={getPopupContainer}
|
||||
empty={!mergedOptions.length}
|
||||
getTriggerDOMNode={() => selectorDomRef.current}
|
||||
>
|
||||
<Selector
|
||||
{...this.$props}
|
||||
domRef={selectorDomRef}
|
||||
prefixCls={prefixCls}
|
||||
inputElement={customizeInputElement}
|
||||
ref="selectorRef"
|
||||
id={mergedId}
|
||||
showSearch={mergedShowSearch}
|
||||
mode={mode}
|
||||
accessibilityIndex={accessibilityIndex}
|
||||
multiple={isMultiple}
|
||||
tagRender={tagRender}
|
||||
values={displayValues}
|
||||
open={mergedOpen}
|
||||
onToggleOpen={onToggleOpen}
|
||||
searchValue={mergedSearchValue}
|
||||
activeValue={activeValue}
|
||||
onSearch={triggerSearch}
|
||||
onSearchSubmit={onSearchSubmit}
|
||||
onSelect={onInternalSelectionSelect}
|
||||
tokenWithEnter={tokenWithEnter}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
×
|
||||
</TransBtn>
|
||||
);
|
||||
}
|
||||
|
||||
{arrowNode}
|
||||
{clearNode}
|
||||
</div>
|
||||
);
|
||||
// ============================= Arrow ==============================
|
||||
const mergedShowArrow =
|
||||
showArrow !== undefined
|
||||
? showArrow
|
||||
: loading || (!isMultiple.value && mode !== 'combobox');
|
||||
let arrowNode: VNode | JSX.Element;
|
||||
|
||||
if (mergedShowArrow) {
|
||||
arrowNode = (
|
||||
<TransBtn
|
||||
class={classNames(`${prefixCls}-arrow`, {
|
||||
[`${prefixCls}-arrow-loading`]: loading,
|
||||
})}
|
||||
customizeIcon={inputIcon}
|
||||
customizeIconProps={{
|
||||
loading,
|
||||
searchValue: mergedSearchValue.value,
|
||||
open: mergedOpen.value,
|
||||
focused: mockFocused.value,
|
||||
showSearch: mergedShowSearch.value,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================ Warning =============================
|
||||
if (process.env.NODE_ENV !== 'production' && warningProps) {
|
||||
warningProps(props);
|
||||
}
|
||||
|
||||
// ============================= Render =============================
|
||||
const mergedClassName = classNames(prefixCls, attrs.class, {
|
||||
[`${prefixCls}-focused`]: mockFocused.value,
|
||||
[`${prefixCls}-multiple`]: isMultiple.value,
|
||||
[`${prefixCls}-single`]: !isMultiple.value,
|
||||
[`${prefixCls}-allow-clear`]: allowClear,
|
||||
[`${prefixCls}-show-arrow`]: mergedShowArrow,
|
||||
[`${prefixCls}-disabled`]: disabled,
|
||||
[`${prefixCls}-loading`]: loading,
|
||||
[`${prefixCls}-open`]: mergedOpen.value,
|
||||
[`${prefixCls}-customize-input`]: customizeInputElement,
|
||||
[`${prefixCls}-show-search`]: mergedShowSearch.value,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
{...attrs}
|
||||
class={mergedClassName}
|
||||
{...domProps}
|
||||
ref={containerRef}
|
||||
onMousedown={onInternalMouseDown}
|
||||
onKeydown={onInternalKeyDown}
|
||||
onKeyup={onInternalKeyUp}
|
||||
// onFocus={onContainerFocus} // trigger by input
|
||||
// onBlur={onContainerBlur} // trigger by input
|
||||
>
|
||||
{mockFocused.value && !mergedOpen.value && (
|
||||
<span
|
||||
style={{
|
||||
width: 0,
|
||||
height: 0,
|
||||
display: 'flex',
|
||||
overflow: 'hidden',
|
||||
opacity: 0,
|
||||
}}
|
||||
aria-live="polite"
|
||||
>
|
||||
{/* Merge into one string to make screen reader work as expect */}
|
||||
{`${mergedRawValue.value.join(', ')}`}
|
||||
</span>
|
||||
)}
|
||||
<SelectTrigger
|
||||
ref={triggerRef}
|
||||
disabled={disabled}
|
||||
prefixCls={prefixCls}
|
||||
visible={triggerOpen.value}
|
||||
popupElement={popupNode}
|
||||
containerWidth={containerWidth.value}
|
||||
animation={animation}
|
||||
transitionName={transitionName}
|
||||
dropdownStyle={dropdownStyle}
|
||||
dropdownClassName={dropdownClassName}
|
||||
direction={direction}
|
||||
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
|
||||
dropdownRender={dropdownRender as any}
|
||||
dropdownAlign={dropdownAlign}
|
||||
getPopupContainer={getPopupContainer}
|
||||
empty={!mergedOptions.value.length}
|
||||
getTriggerDOMNode={() => selectorDomRef.current}
|
||||
>
|
||||
<Selector
|
||||
{...props}
|
||||
domRef={selectorDomRef}
|
||||
prefixCls={prefixCls}
|
||||
inputElement={customizeInputElement}
|
||||
ref={selectorRef}
|
||||
id={mergedId.value}
|
||||
showSearch={mergedShowSearch.value}
|
||||
mode={mode}
|
||||
accessibilityIndex={accessibilityIndex.value}
|
||||
multiple={isMultiple.value}
|
||||
tagRender={tagRender}
|
||||
values={displayValues.value}
|
||||
open={mergedOpen.value}
|
||||
onToggleOpen={onToggleOpen}
|
||||
searchValue={mergedSearchValue.value}
|
||||
activeValue={activeValue.value}
|
||||
onSearch={triggerSearch}
|
||||
onSearchSubmit={onSearchSubmit}
|
||||
onSelect={onInternalSelectionSelect}
|
||||
tokenWithEnter={tokenWithEnter.value}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
|
||||
{arrowNode}
|
||||
{clearNode}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
return Select;
|
||||
|
|
|
@ -34,7 +34,6 @@ export default defineComponent({
|
|||
inheritAttrs: false,
|
||||
props: optionListProps<DataNode>(),
|
||||
slots: ['notFoundContent', 'menuItemSelectedIcon'],
|
||||
expose: ['scrollTo', 'onKeydown', 'onKeyup'],
|
||||
setup(props, { slots, expose }) {
|
||||
const context = useInjectTreeSelectContext();
|
||||
|
||||
|
@ -153,7 +152,7 @@ export default defineComponent({
|
|||
case KeyCode.DOWN:
|
||||
case KeyCode.LEFT:
|
||||
case KeyCode.RIGHT:
|
||||
treeRef.value?.onKeyDown(event);
|
||||
treeRef.value?.onKeydown(event);
|
||||
break;
|
||||
|
||||
// >>> Select item
|
||||
|
|
|
@ -8,7 +8,8 @@ export interface TreeNodeProps extends Omit<DataNode, 'children'> {
|
|||
}
|
||||
|
||||
/** This is a placeholder, not real render in dom */
|
||||
const TreeNode: FunctionalComponent<TreeNodeProps> = () => null;
|
||||
const TreeNode: FunctionalComponent<TreeNodeProps> & { isTreeSelectNode: boolean } = () => null;
|
||||
TreeNode.inheritAttrs = false;
|
||||
TreeNode.displayName = 'ATreeSelectNode';
|
||||
TreeNode.isTreeSelectNode = true;
|
||||
export default TreeNode;
|
||||
|
|
|
@ -161,7 +161,7 @@ export default function generate(config: {
|
|||
});
|
||||
|
||||
// ========================== Ref ==========================
|
||||
const selectRef = ref(null);
|
||||
const selectRef = ref();
|
||||
|
||||
expose({
|
||||
scrollTo: (...args: any[]) => selectRef.value.scrollTo?.(...args),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { VNodeChild } from 'vue';
|
||||
import { camelize } from 'vue';
|
||||
import { warning } from '../../vc-util/warning';
|
||||
import { isValidElement } from '../../_util/props-util';
|
||||
import type {
|
||||
DataNode,
|
||||
LegacyDataNode,
|
||||
|
@ -10,32 +11,58 @@ import type {
|
|||
} from '../interface';
|
||||
import TreeNode from '../TreeNode';
|
||||
|
||||
export function convertChildrenToData(nodes): DataNode[] {
|
||||
return nodes
|
||||
.map(node => {
|
||||
if (!isValidElement(node) || !node.type) {
|
||||
function isTreeSelectNode(node: any) {
|
||||
return node && node.type && (node.type as any).isTreeSelectNode;
|
||||
}
|
||||
export function convertChildrenToData(rootNodes: VNodeChild): DataNode[] {
|
||||
function dig(treeNodes: any[] = []): DataNode[] {
|
||||
return treeNodes.map(treeNode => {
|
||||
// Filter invalidate node
|
||||
if (!isTreeSelectNode(treeNode)) {
|
||||
warning(!treeNode, 'TreeSelect/TreeSelectNode can only accept TreeSelectNode as children.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const slots = (treeNode.children as any) || {};
|
||||
const key = treeNode.key as string | number;
|
||||
const props: any = {};
|
||||
for (const [k, v] of Object.entries(treeNode.props)) {
|
||||
props[camelize(k)] = v;
|
||||
}
|
||||
const { isLeaf, checkable, selectable, disabled, disableCheckbox } = props;
|
||||
// 默认值为 undefined
|
||||
const newProps = {
|
||||
isLeaf: isLeaf || isLeaf === '' || undefined,
|
||||
checkable: checkable || checkable === '' || undefined,
|
||||
selectable: selectable || selectable === '' || undefined,
|
||||
disabled: disabled || disabled === '' || undefined,
|
||||
disableCheckbox: disableCheckbox || disableCheckbox === '' || undefined,
|
||||
};
|
||||
const slotsProps = { ...props, ...newProps };
|
||||
const {
|
||||
title = slots.title?.(slotsProps),
|
||||
switcherIcon = slots.switcherIcon?.(slotsProps),
|
||||
...rest
|
||||
} = props;
|
||||
const children = slots.default?.();
|
||||
const dataNode: DataNode = {
|
||||
...rest,
|
||||
title,
|
||||
switcherIcon,
|
||||
key,
|
||||
props: { children, value, ...restProps },
|
||||
} = node;
|
||||
|
||||
const data = {
|
||||
key,
|
||||
value,
|
||||
...restProps,
|
||||
isLeaf,
|
||||
...newProps,
|
||||
};
|
||||
|
||||
const childData = convertChildrenToData(children);
|
||||
if (childData.length) {
|
||||
data.children = childData;
|
||||
const parsedChildren = dig(children);
|
||||
if (parsedChildren.length) {
|
||||
dataNode.children = parsedChildren;
|
||||
}
|
||||
|
||||
return data;
|
||||
})
|
||||
.filter(data => data);
|
||||
return dataNode;
|
||||
});
|
||||
}
|
||||
|
||||
return dig(rootNodes as any[]);
|
||||
}
|
||||
|
||||
export function fillLegacyProps(dataNode: DataNode): LegacyDataNode {
|
||||
|
|
|
@ -96,8 +96,8 @@ export default defineComponent({
|
|||
props: nodeListProps,
|
||||
setup(props, { expose, attrs }) {
|
||||
// =============================== Ref ================================
|
||||
const listRef = ref(null);
|
||||
const indentMeasurerRef = ref(null);
|
||||
const listRef = ref();
|
||||
const indentMeasurerRef = ref();
|
||||
expose({
|
||||
scrollTo: scroll => {
|
||||
listRef.value.scrollTo(scroll);
|
||||
|
|
|
@ -870,8 +870,8 @@ export default defineComponent({
|
|||
active: true,
|
||||
});
|
||||
});
|
||||
const onKeyDown = event => {
|
||||
const { onKeyDown, checkable, selectable } = props;
|
||||
const onKeydown = event => {
|
||||
const { onKeydown, checkable, selectable } = props;
|
||||
|
||||
// >>>>>>>>>> Direction
|
||||
switch (event.which) {
|
||||
|
@ -943,13 +943,14 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
if (onKeyDown) {
|
||||
onKeyDown(event);
|
||||
if (onKeydown) {
|
||||
onKeydown(event);
|
||||
}
|
||||
};
|
||||
expose({
|
||||
onNodeExpand,
|
||||
scrollTo,
|
||||
onKeydown,
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('dragend', onWindowDragEnd);
|
||||
|
@ -1068,7 +1069,7 @@ export default defineComponent({
|
|||
activeItem={activeItem.value}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onKeydown={onKeyDown}
|
||||
onKeydown={onKeydown}
|
||||
onActiveChange={onActiveChange}
|
||||
onListChangeStart={onListChangeStart}
|
||||
onListChangeEnd={onListChangeEnd}
|
||||
|
|
|
@ -146,7 +146,7 @@ export const treeProps = () => ({
|
|||
},
|
||||
onFocus: { type: Function as PropType<(e: FocusEvent) => void> },
|
||||
onBlur: { type: Function as PropType<(e: FocusEvent) => void> },
|
||||
onKeyDown: { type: Function as PropType<EventHandlerNonNull> },
|
||||
onKeydown: { type: Function as PropType<EventHandlerNonNull> },
|
||||
onContextmenu: { type: Function as PropType<EventHandlerNonNull> },
|
||||
onClick: { type: Function as PropType<NodeMouseEventHandler> },
|
||||
onDblclick: { type: Function as PropType<NodeMouseEventHandler> },
|
||||
|
|
|
@ -12,6 +12,7 @@ import { getPosition, isTreeNode } from '../util';
|
|||
import { warning } from '../../vc-util/warning';
|
||||
import Omit from 'omit.js';
|
||||
import type { VNodeChild } from 'vue';
|
||||
import { camelize } from 'vue';
|
||||
import type { TreeNodeProps } from '../props';
|
||||
|
||||
export function getKey(key: Key, pos: string) {
|
||||
|
@ -60,74 +61,58 @@ export function warningWithoutKey(treeData: DataNode[], fieldNames: FieldNames)
|
|||
dig(treeData);
|
||||
}
|
||||
|
||||
const cacheStringFunction = (fn: (s: string) => string) => {
|
||||
const cache = Object.create(null);
|
||||
return (str: string) => {
|
||||
const hit = cache[str];
|
||||
return hit || (cache[str] = fn(str));
|
||||
};
|
||||
};
|
||||
|
||||
const camelizeRE = /-(\w)/g;
|
||||
|
||||
const camelize = cacheStringFunction((str: string) => {
|
||||
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert `children` of Tree into `treeData` structure.
|
||||
*/
|
||||
export function convertTreeToData(rootNodes: VNodeChild): DataNode[] {
|
||||
function dig(node: VNodeChild = []): DataNode[] {
|
||||
const treeNodes = node as NodeElement[];
|
||||
return treeNodes
|
||||
.map(treeNode => {
|
||||
// Filter invalidate node
|
||||
if (!isTreeNode(treeNode)) {
|
||||
warning(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.');
|
||||
return null;
|
||||
}
|
||||
const slots = (treeNode.children as any) || {};
|
||||
const key = treeNode.key as string | number;
|
||||
const props: any = {};
|
||||
for (const [k, v] of Object.entries(treeNode.props)) {
|
||||
props[camelize(k)] = v;
|
||||
}
|
||||
const { isLeaf, checkable, selectable, disabled, disableCheckbox } = props;
|
||||
// 默认值为 undefined
|
||||
const newProps = {
|
||||
isLeaf: isLeaf || isLeaf === '' || undefined,
|
||||
checkable: checkable || checkable === '' || undefined,
|
||||
selectable: selectable || selectable === '' || undefined,
|
||||
disabled: disabled || disabled === '' || undefined,
|
||||
disableCheckbox: disableCheckbox || disableCheckbox === '' || undefined,
|
||||
};
|
||||
const slotsProps = { ...props, ...newProps };
|
||||
const {
|
||||
title = slots.title?.(slotsProps),
|
||||
icon = slots.icon?.(slotsProps),
|
||||
switcherIcon = slots.switcherIcon?.(slotsProps),
|
||||
...rest
|
||||
} = props;
|
||||
const children = slots.default?.();
|
||||
const dataNode: DataNode = {
|
||||
...rest,
|
||||
title,
|
||||
icon,
|
||||
switcherIcon,
|
||||
key,
|
||||
isLeaf,
|
||||
...newProps,
|
||||
};
|
||||
return treeNodes.map(treeNode => {
|
||||
// Filter invalidate node
|
||||
if (!isTreeNode(treeNode)) {
|
||||
warning(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.');
|
||||
return null;
|
||||
}
|
||||
const slots = (treeNode.children as any) || {};
|
||||
const key = treeNode.key as string | number;
|
||||
const props: any = {};
|
||||
for (const [k, v] of Object.entries(treeNode.props)) {
|
||||
props[camelize(k)] = v;
|
||||
}
|
||||
const { isLeaf, checkable, selectable, disabled, disableCheckbox } = props;
|
||||
// 默认值为 undefined
|
||||
const newProps = {
|
||||
isLeaf: isLeaf || isLeaf === '' || undefined,
|
||||
checkable: checkable || checkable === '' || undefined,
|
||||
selectable: selectable || selectable === '' || undefined,
|
||||
disabled: disabled || disabled === '' || undefined,
|
||||
disableCheckbox: disableCheckbox || disableCheckbox === '' || undefined,
|
||||
};
|
||||
const slotsProps = { ...props, ...newProps };
|
||||
const {
|
||||
title = slots.title?.(slotsProps),
|
||||
icon = slots.icon?.(slotsProps),
|
||||
switcherIcon = slots.switcherIcon?.(slotsProps),
|
||||
...rest
|
||||
} = props;
|
||||
const children = slots.default?.();
|
||||
const dataNode: DataNode = {
|
||||
...rest,
|
||||
title,
|
||||
icon,
|
||||
switcherIcon,
|
||||
key,
|
||||
isLeaf,
|
||||
...newProps,
|
||||
};
|
||||
|
||||
const parsedChildren = dig(children);
|
||||
if (parsedChildren.length) {
|
||||
dataNode.children = parsedChildren;
|
||||
}
|
||||
const parsedChildren = dig(children);
|
||||
if (parsedChildren.length) {
|
||||
dataNode.children = parsedChildren;
|
||||
}
|
||||
|
||||
return dataNode;
|
||||
})
|
||||
.filter((dataNode: DataNode) => dataNode);
|
||||
return dataNode;
|
||||
});
|
||||
}
|
||||
|
||||
return dig(rootNodes);
|
||||
|
|
|
@ -1,64 +1,40 @@
|
|||
<template>
|
||||
<a-tree-select
|
||||
v-model:value="value"
|
||||
show-search
|
||||
style="width: 100%"
|
||||
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:tree-data="treeData"
|
||||
placeholder="Please select"
|
||||
allow-clear
|
||||
multiple
|
||||
tree-default-expand-all
|
||||
>
|
||||
<template #title="{ key, value }">
|
||||
<span v-if="key === '0-0-1'" style="color: #08c">Child Node1 {{ value }}</span>
|
||||
</template>
|
||||
<a-tree-select-node value="parent 1" title="parent 1">
|
||||
<a-tree-select-node value="parent 1-0" title="parent 1-0">
|
||||
<a-tree-select-node value="leaf1" title="my leaf" />
|
||||
<a-tree-select-node value="leaf2" title="your leaf" />
|
||||
</a-tree-select-node>
|
||||
<a-tree-select-node value="parent 1-1" title="parent 1-1">
|
||||
<a-tree-select-node value="sss">
|
||||
<template #title><b style="color: #08c">sss</b></template>
|
||||
</a-tree-select-node>
|
||||
</a-tree-select-node>
|
||||
</a-tree-select-node>
|
||||
</a-tree-select>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from 'vue';
|
||||
|
||||
interface TreeDataItem {
|
||||
value: string;
|
||||
key: string;
|
||||
title?: string;
|
||||
slots?: Record<string, string>;
|
||||
children?: TreeDataItem[];
|
||||
}
|
||||
|
||||
const treeData: TreeDataItem[] = [
|
||||
{
|
||||
title: 'Node1',
|
||||
value: '0-0',
|
||||
key: '0-0',
|
||||
children: [
|
||||
{
|
||||
value: '0-0-1',
|
||||
key: '0-0-1',
|
||||
slots: {
|
||||
title: 'title1',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Child Node2',
|
||||
value: '0-0-2',
|
||||
key: '0-0-2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Node2',
|
||||
value: '0-1',
|
||||
key: '0-1',
|
||||
},
|
||||
];
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const value = ref<string>();
|
||||
const value = ref<string[]>([]);
|
||||
|
||||
watch(value, () => {
|
||||
console.log(value.value);
|
||||
console.log('select', value.value);
|
||||
});
|
||||
|
||||
return {
|
||||
value,
|
||||
treeData,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue