This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
@@ -0,0 +1,10 @@
import * as React from 'react';
import type { DefaultOptionType, SingleValueType, CascaderProps, InternalFieldNames } from '../Cascader';
declare const _default: (rawValues: SingleValueType[], options: DefaultOptionType[], fieldNames: InternalFieldNames, multiple: boolean, displayRender: CascaderProps['displayRender']) => {
label: React.ReactNode;
value: string;
key: string;
valueCells: SingleValueType;
disabled: boolean | undefined;
}[];
export default _default;
@@ -0,0 +1,44 @@
import { toPathOptions } from "../utils/treeUtil";
import * as React from 'react';
import { toPathKey } from "../utils/commonUtil";
export default ((rawValues, options, fieldNames, multiple, displayRender) => {
return React.useMemo(() => {
const mergedDisplayRender = displayRender || (
// Default displayRender
labels => {
const mergedLabels = multiple ? labels.slice(-1) : labels;
const SPLIT = ' / ';
if (mergedLabels.every(label => ['string', 'number'].includes(typeof label))) {
return mergedLabels.join(SPLIT);
}
// If exist non-string value, use ReactNode instead
return mergedLabels.reduce((list, label, index) => {
const keyedLabel = /*#__PURE__*/React.isValidElement(label) ? /*#__PURE__*/React.cloneElement(label, {
key: index
}) : label;
if (index === 0) {
return [keyedLabel];
}
return [...list, SPLIT, keyedLabel];
}, []);
});
return rawValues.map(valueCells => {
const valueOptions = toPathOptions(valueCells, options, fieldNames);
const label = mergedDisplayRender(valueOptions.map(({
option,
value
}) => option?.[fieldNames.label] ?? value), valueOptions.map(({
option
}) => option));
const value = toPathKey(valueCells);
return {
label,
value,
key: value,
valueCells,
disabled: valueOptions[valueOptions.length - 1]?.option?.disabled
};
});
}, [rawValues, options, fieldNames, displayRender, multiple]);
});
@@ -0,0 +1,10 @@
import type { DefaultOptionType, InternalFieldNames } from '../Cascader';
import type { DataEntity } from '@rc-component/tree/lib/interface';
export interface OptionsInfo {
keyEntities: Record<string, DataEntity>;
pathKeyEntities: Record<string, DataEntity>;
}
export type GetEntities = () => OptionsInfo['pathKeyEntities'];
/** Lazy parse options data into conduct-able info to avoid perf issue in single mode */
declare const _default: (options: DefaultOptionType[], fieldNames: InternalFieldNames) => GetEntities;
export default _default;
@@ -0,0 +1,35 @@
import * as React from 'react';
import { convertDataToEntities } from "@rc-component/tree/es/utils/treeUtil";
import { VALUE_SPLIT } from "../utils/commonUtil";
/** Lazy parse options data into conduct-able info to avoid perf issue in single mode */
export default ((options, fieldNames) => {
const cacheRef = React.useRef({
options: [],
info: {
keyEntities: {},
pathKeyEntities: {}
}
});
const getEntities = React.useCallback(() => {
if (cacheRef.current.options !== options) {
cacheRef.current.options = options;
cacheRef.current.info = convertDataToEntities(options, {
fieldNames: fieldNames,
initWrapper: wrapper => ({
...wrapper,
pathKeyEntities: {}
}),
processEntity: (entity, wrapper) => {
const pathKey = entity.nodes.map(node => node[fieldNames.value]).join(VALUE_SPLIT);
wrapper.pathKeyEntities[pathKey] = entity;
// Overwrite origin key.
// this is very hack but we need let conduct logic work with connect path
entity.key = pathKey;
}
});
}
return cacheRef.current.info.pathKeyEntities;
}, [fieldNames, options]);
return getEntities;
});
@@ -0,0 +1,3 @@
import type { DefaultOptionType, InternalFieldNames, SingleValueType } from '../Cascader';
export type GetMissValues = ReturnType<typeof useMissingValues>;
export default function useMissingValues(options: DefaultOptionType[], fieldNames: InternalFieldNames): (rawValues: SingleValueType[]) => [SingleValueType[], SingleValueType[]];
@@ -0,0 +1,17 @@
import * as React from 'react';
import { toPathOptions } from "../utils/treeUtil";
export default function useMissingValues(options, fieldNames) {
return React.useCallback(rawValues => {
const missingValues = [];
const existsValues = [];
rawValues.forEach(valueCell => {
const pathOptions = toPathOptions(valueCell, options, fieldNames);
if (pathOptions.every(opt => opt.option)) {
existsValues.push(valueCell);
} else {
missingValues.push(valueCell);
}
});
return [existsValues, missingValues];
}, [options, fieldNames]);
}
@@ -0,0 +1,8 @@
import type { DefaultOptionType } from '..';
import type { InternalFieldNames, SingleValueType, LegacyKey } from '../Cascader';
import { type GetEntities } from './useEntities';
export default function useOptions(mergedFieldNames: InternalFieldNames, options?: DefaultOptionType[]): [
mergedOptions: DefaultOptionType[],
getPathKeyEntities: GetEntities,
getValueByKeyPath: (pathKeys: LegacyKey[]) => SingleValueType[]
];
@@ -0,0 +1,20 @@
import * as React from 'react';
import useEntities from "./useEntities";
export default function useOptions(mergedFieldNames, options) {
const mergedOptions = React.useMemo(() => options || [], [options]);
// Only used in multiple mode, this fn will not call in single mode
const getPathKeyEntities = useEntities(mergedOptions, mergedFieldNames);
/** Convert path key back to value format */
const getValueByKeyPath = React.useCallback(pathKeys => {
const keyPathEntities = getPathKeyEntities();
return pathKeys.map(pathKey => {
const {
nodes
} = keyPathEntities[pathKey];
return nodes.map(node => node[mergedFieldNames.value]);
});
}, [getPathKeyEntities, mergedFieldNames]);
return [mergedOptions, getPathKeyEntities, getValueByKeyPath];
}
@@ -0,0 +1,2 @@
import type { CascaderProps, SearchConfig } from '../Cascader';
export default function useSearchConfig(showSearch?: CascaderProps['showSearch'], props?: any): [boolean, SearchConfig<import("../Cascader").DefaultOptionType, string>];
@@ -0,0 +1,35 @@
import warning from "@rc-component/util/es/warning";
import * as React from 'react';
// Convert `showSearch` to unique config
export default function useSearchConfig(showSearch, props) {
const {
autoClearSearchValue,
searchValue,
onSearch
} = props;
return React.useMemo(() => {
if (!showSearch) {
return [false, {}];
}
let searchConfig = {
matchInputWidth: true,
limit: 50,
autoClearSearchValue,
searchValue,
onSearch
};
if (showSearch && typeof showSearch === 'object') {
searchConfig = {
...searchConfig,
...showSearch
};
}
if (searchConfig.limit <= 0) {
searchConfig.limit = false;
if (process.env.NODE_ENV !== 'production') {
warning(false, "'limit' of showSearch should be positive number or false.");
}
}
return [true, searchConfig];
}, [showSearch, autoClearSearchValue, searchValue, onSearch]);
}
@@ -0,0 +1,4 @@
import type { DefaultOptionType, InternalFieldNames, SearchConfig } from '../Cascader';
export declare const SEARCH_MARK = "__rc_cascader_search_mark__";
declare const useSearchOptions: (search: string, options: DefaultOptionType[], fieldNames: InternalFieldNames, prefixCls: string, config: SearchConfig, enableHalfPath?: boolean) => DefaultOptionType[];
export default useSearchOptions;
@@ -0,0 +1,63 @@
import * as React from 'react';
export const SEARCH_MARK = '__rc_cascader_search_mark__';
const defaultFilter = (search, options, {
label = ''
}) => options.some(opt => String(opt[label]).toLowerCase().includes(search.toLowerCase()));
const defaultRender = (inputValue, path, prefixCls, fieldNames) => path.map(opt => opt[fieldNames.label]).join(' / ');
const useSearchOptions = (search, options, fieldNames, prefixCls, config, enableHalfPath) => {
const {
filter = defaultFilter,
render = defaultRender,
limit = 50,
sort
} = config;
return React.useMemo(() => {
const filteredOptions = [];
if (!search) {
return [];
}
function dig(list, pathOptions, parentDisabled = false) {
list.forEach(option => {
// Perf saving when `sort` is disabled and `limit` is provided
if (!sort && limit !== false && limit > 0 && filteredOptions.length >= limit) {
return;
}
const connectedPathOptions = [...pathOptions, option];
const children = option[fieldNames.children];
const mergedDisabled = parentDisabled || option.disabled;
// If current option is filterable
if (
// If is leaf option
!children || children.length === 0 ||
// If is changeOnSelect or multiple
enableHalfPath) {
if (filter(search, connectedPathOptions, {
label: fieldNames.label
})) {
filteredOptions.push({
...option,
disabled: mergedDisabled,
[fieldNames.label]: render(search, connectedPathOptions, prefixCls, fieldNames),
[SEARCH_MARK]: connectedPathOptions,
[fieldNames.children]: undefined
});
}
}
if (children) {
dig(option[fieldNames.children], connectedPathOptions, mergedDisabled);
}
});
}
dig(options, []);
// Do sort
if (sort) {
filteredOptions.sort((a, b) => {
return sort(a[SEARCH_MARK], b[SEARCH_MARK], search, fieldNames);
});
}
return limit !== false && limit > 0 ? filteredOptions.slice(0, limit) : filteredOptions;
}, [search, options, fieldNames, prefixCls, render, enableHalfPath, filter, sort, limit]);
};
export default useSearchOptions;
@@ -0,0 +1,3 @@
import type { InternalValueType, LegacyKey, ShowCheckedStrategy, SingleValueType } from '../Cascader';
import type { GetEntities } from './useEntities';
export default function useSelect(multiple: boolean, triggerChange: (nextValues: InternalValueType) => void, checkedValues: SingleValueType[], halfCheckedValues: SingleValueType[], missingCheckedValues: SingleValueType[], getPathKeyEntities: GetEntities, getValueByKeyPath: (pathKeys: LegacyKey[]) => SingleValueType[], showCheckedStrategy?: ShowCheckedStrategy): (valuePath: SingleValueType) => void;
@@ -0,0 +1,49 @@
import { conductCheck } from "@rc-component/tree/es/utils/conductUtil";
import { toPathKey, toPathKeys } from "../utils/commonUtil";
import { formatStrategyValues } from "../utils/treeUtil";
export default function useSelect(multiple, triggerChange, checkedValues, halfCheckedValues, missingCheckedValues, getPathKeyEntities, getValueByKeyPath, showCheckedStrategy) {
return valuePath => {
if (!multiple) {
triggerChange(valuePath);
} else {
// Prepare conduct required info
const pathKey = toPathKey(valuePath);
const checkedPathKeys = toPathKeys(checkedValues);
const halfCheckedPathKeys = toPathKeys(halfCheckedValues);
const existInChecked = checkedPathKeys.includes(pathKey);
const existInMissing = missingCheckedValues.some(valueCells => toPathKey(valueCells) === pathKey);
// Do update
let nextCheckedValues = checkedValues;
let nextMissingValues = missingCheckedValues;
if (existInMissing && !existInChecked) {
// Missing value only do filter
nextMissingValues = missingCheckedValues.filter(valueCells => toPathKey(valueCells) !== pathKey);
} else {
// Update checked key first
const nextRawCheckedKeys = existInChecked ? checkedPathKeys.filter(key => key !== pathKey) : [...checkedPathKeys, pathKey];
const pathKeyEntities = getPathKeyEntities();
// Conduction by selected or not
let checkedKeys;
if (existInChecked) {
({
checkedKeys
} = conductCheck(nextRawCheckedKeys, {
checked: false,
halfCheckedKeys: halfCheckedPathKeys
}, pathKeyEntities));
} else {
({
checkedKeys
} = conductCheck(nextRawCheckedKeys, true, pathKeyEntities));
}
// Roll up to parent level keys
const deDuplicatedKeys = formatStrategyValues(checkedKeys, getPathKeyEntities, showCheckedStrategy);
nextCheckedValues = getValueByKeyPath(deDuplicatedKeys);
}
triggerChange([...nextMissingValues, ...nextCheckedValues]);
}
};
}
@@ -0,0 +1,8 @@
import type { DataEntity } from '@rc-component/tree/lib/interface';
import type { LegacyKey, SingleValueType } from '../Cascader';
import type { GetMissValues } from './useMissingValues';
export default function useValues(multiple: boolean, rawValues: SingleValueType[], getPathKeyEntities: () => Record<string, DataEntity>, getValueByKeyPath: (pathKeys: LegacyKey[]) => SingleValueType[], getMissingValues: GetMissValues): [
checkedValues: SingleValueType[],
halfCheckedValues: SingleValueType[],
missingCheckedValues: SingleValueType[]
];
@@ -0,0 +1,21 @@
import { conductCheck } from "@rc-component/tree/es/utils/conductUtil";
import * as React from 'react';
import { toPathKeys } from "../utils/commonUtil";
export default function useValues(multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues) {
// Fill `rawValues` with checked conduction values
return React.useMemo(() => {
const [existValues, missingValues] = getMissingValues(rawValues);
if (!multiple || !rawValues.length) {
return [existValues, [], missingValues];
}
const keyPathValues = toPathKeys(existValues);
const keyPathEntities = getPathKeyEntities();
const {
checkedKeys,
halfCheckedKeys
} = conductCheck(keyPathValues, true, keyPathEntities);
// Convert key back to value cells
return [getValueByKeyPath(checkedKeys), getValueByKeyPath(halfCheckedKeys), missingValues];
}, [multiple, rawValues, getPathKeyEntities, getValueByKeyPath, getMissingValues]);
}