mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-03-02 23:37:04 +00:00
Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3681622 * chore: remove unnecessary code * refactor: react-render using TypeScript * chore: build-script * refactor: editor-skeleton * refactor: designer * refactor: material-parser * refactor: editor-setters * refactor: js to ts for rax-provider Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3678180 * refactor: rax-provider * feat: add build command * chore: compilerOptions for rax-provider * refactor: JS to TS for Rax Renderer Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3678935 * refactor: rax-renderer * Merge remote-tracking branch 'origin/refactor/js-to-ts' into refactor/js2ts-rax-renderer * Merge remote-tracking branch 'origin/refactor/js-to-ts' into refactor/js2ts-rax-renderer * refactor: ts-nocheck * chore: ts compile error * fix: ts rootDir * fix: compile error * chore: using same tsconfig for rax component * refactor: ts compile rax-renderer && rax-provider * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/js-to-ts # Conflicts: # packages/rax-render/src/utils/appHelper.js # packages/rax-render/src/utils/appHelper.ts # packages/utils/src/appHelper.ts * refactor: no JS file * refactor: remove lint * feat: add xima * feat: add eslint ignore * style: fix by lint * feat: lint command * fix: using the same eslint config * style: eslint settings * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/code-style # Conflicts: # packages/plugin-event-bind-dialog/src/index.tsx # packages/plugin-source-editor/src/index.tsx # packages/runtime/src/core/container.ts # packages/runtime/src/core/provider.ts * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/code-style # Conflicts: # packages/designer/src/document/document-model.ts # packages/designer/src/document/node/node-children.ts # packages/designer/src/document/node/props/prop.ts # packages/plugin-source-editor/src/index.tsx * fix: 修改dataSource items -> list * Merge remote-tracking branch 'origin/relase/1.0.0' into refactor/code-style # Conflicts: # packages/react-renderer/package.json * refactor: component-panel plugin-component-pane 代码规范化 Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3703771 * feat: support bizcomps * refactor: component-panel * style: eslint * Merge branch 'refactor/code-style' into fix/ducheng-source-style * style: code style * Merge branch 'fix/ducheng-source-style' into 'refactor/code-style' Code review title: Fix/ducheng source style Code review Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3705972 * style: for demo * style: for demo-server * style: for plugin-event-bind-dialog * style: for plugin-sample-preview * style: for plugin-undo-redo * style: plugin-variable-bind-dialog * style: types * style: for utils * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/code-style # Conflicts: # packages/editor-setters/src/expression-setter/locale/snippets.ts # packages/editor-setters/src/json-setter/locale/snippets.ts # packages/editor-setters/src/locale/snippets.ts # packages/plugin-components-pane/package.json # packages/rax-render/src/hoc/compWrapper.tsx # packages/rax-render/src/utils/index.ts # packages/react-renderer/src/context/appContext.ts * style: editor-preset-general editor-preset-general 代码规范化 Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3707974 * style: editor-preset-general * fix: should set field * fix: should set field - demo-server * refactor(style): fix code style for designer refactor(style): fix code style for editor-core refactor(style): fix code style for editor-skeleton refactor(style): fix code style for react-simulator-renderer refactor(style): fix code style for rax-simulator-renderer * Merge branch 'refactor/lihao-code-style' into 'refactor/code-style' Code review title: refactor(style): fix code style for designer Code review description: refactor(style): fix code style for editor-core refactor(style): fix code style for editor-skeleton refactor(style): fix code style for react-simulator-renderer refactor(style): fix code style for rax-simulator-renderer Code review Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3709472 * style: react/no-multi-comp set to 0 for designer * style: ignore editor-prset-vision * style: fix for plugin-outline-pane * style: fix for rax-provider * style: react-provider * style: runtime * style: rax-render * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/code-style # Conflicts: # packages/editor-setters/src/expression-setter/index.tsx # packages/plugin-source-editor/src/index.tsx # packages/plugin-source-editor/src/transform.ts * refactor: material parser code style 1. 修复eslint问题 2. instanceOf => any 3. 修复node类型解析失败问题 Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3716330 * refactor: material parser code style * refactor: code-generator code style 1. rax 出码合并 2. code style 修复 注:合并的代码中带了 datasource 的 Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3717159 * Merge branch 'feat/rax-code-generator' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into feat/rax-code-generator # Conflicts: # packages/code-generator/src/generator/ProjectBuilder.ts # packages/code-generator/src/parser/SchemaParser.ts # packages/code-generator/src/plugins/component/rax/jsx.ts # packages/code-generator/src/plugins/project/constants.ts # packages/code-generator/src/plugins/project/framework/rax/plugins/packageJSON.ts # packages/code-generator/src/plugins/project/i18n.ts # packages/code-generator/src/publisher/disk/index.ts # packages/code-generator/src/publisher/disk/utils.ts # packages/code-generator/src/types/core.ts # packages/code-generator/src/types/schema.ts # packages/code-generator/src/utils/compositeType.ts # packages/code-generator/src/utils/nodeToJSX.ts * refactor: code-generator * Merge remote-tracking branch 'origin/refactor/code-style' into refactor/code-style-code-generator # Conflicts: # .vscode/launch.json * Revert "refactor: code-generator code style " This reverts commit ebc78e8788f83e8fda0e146758af43b878125c10. * chore: eslintrc && eslintignore * style: for plugin-source-editor * style: fix by eslint --fix * style: scripts/ * style: datasource-engine * feat: pre-commit * Merge branch 'refactor/code-style' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into refactor/code-style # Conflicts: # .eslintignore # packages/code-generator/src/parser/SchemaParser.ts # packages/code-generator/src/plugins/component/rax/containerInitState.ts # packages/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts # packages/code-generator/src/plugins/component/rax/containerLifeCycle.ts # packages/code-generator/src/plugins/project/framework/rax/plugins/buildConfig.ts # packages/code-generator/src/utils/OrderedSet.ts # packages/code-generator/src/utils/ScopeBindings.ts # packages/code-generator/src/utils/expressionParser.ts # packages/code-generator/src/utils/schema.ts # packages/datasource-engine/src/core/DataSourceEngine.ts # packages/datasource-engine/src/core/RuntimeDataSource.ts # packages/datasource-engine/src/types/IRuntimeContext.ts # packages/datasource-engine/src/types/index.ts * refactor: code style code-generator 对 code style 分支上次合并的 code-generator 修改做了 revert 重新在 code style 分支基础上对代码样式做了修复 rax 合并分支会另行 fix 后向 release 分支提 MR Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3719702 * refactor: code style fix * style: for code-generator * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/code-style # Conflicts: # packages/editor-skeleton/src/transducers/addon-combine.ts # packages/plugin-components-pane/package.json # packages/plugin-components-pane/src/components/base/index.tsx # packages/plugin-components-pane/src/components/component-list/index.tsx # packages/plugin-components-pane/src/i18n/index.ts # packages/plugin-components-pane/src/i18n/strings/index.ts # packages/plugin-components-pane/src/index.tsx # packages/plugin-event-bind-dialog/src/index.tsx # packages/plugin-source-editor/src/index.tsx # packages/plugin-source-editor/src/transform.ts * style: auto fix
640 lines
20 KiB
TypeScript
640 lines
20 KiB
TypeScript
import * as React from 'react';
|
|
import RcCascader from 'rc-cascader';
|
|
import arrayTreeFilter from 'array-tree-filter';
|
|
import classNames from 'classnames';
|
|
import omit from 'omit.js';
|
|
import isEqual from 'lodash/isEqual';
|
|
import KeyCode from 'rc-util/lib/KeyCode';
|
|
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
|
|
import DownOutlined from '@ant-design/icons/DownOutlined';
|
|
import RightOutlined from '@ant-design/icons/RightOutlined';
|
|
import RedoOutlined from '@ant-design/icons/RedoOutlined';
|
|
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
|
|
|
import Input from '../input';
|
|
import { ConfigConsumer, ConfigConsumerProps, RenderEmptyHandler } from '../config-provider';
|
|
import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
|
import warning from '../_util/warning';
|
|
import SizeContext, { SizeType } from '../config-provider/SizeContext';
|
|
|
|
export interface CascaderOptionType {
|
|
value?: string;
|
|
label?: React.ReactNode;
|
|
disabled?: boolean;
|
|
isLeaf?: boolean;
|
|
loading?: boolean;
|
|
children?: CascaderOptionType[];
|
|
[key: string]: any;
|
|
}
|
|
|
|
export interface FieldNamesType {
|
|
value?: string;
|
|
label?: string;
|
|
children?: string;
|
|
}
|
|
|
|
export interface FilledFieldNamesType {
|
|
value: string;
|
|
label: string;
|
|
children: string;
|
|
}
|
|
|
|
export type CascaderExpandTrigger = 'click' | 'hover';
|
|
|
|
export interface ShowSearchType {
|
|
filter?: (inputValue: string, path: CascaderOptionType[], names: FilledFieldNamesType) => boolean;
|
|
render?: (
|
|
inputValue: string,
|
|
path: CascaderOptionType[],
|
|
prefixCls: string | undefined,
|
|
names: FilledFieldNamesType,
|
|
) => React.ReactNode;
|
|
sort?: (
|
|
a: CascaderOptionType[],
|
|
b: CascaderOptionType[],
|
|
inputValue: string,
|
|
names: FilledFieldNamesType,
|
|
) => number;
|
|
matchInputWidth?: boolean;
|
|
limit?: number | false;
|
|
}
|
|
|
|
export interface CascaderProps {
|
|
/** 可选项数据源 */
|
|
options: CascaderOptionType[];
|
|
/** 默认的选中项 */
|
|
defaultValue?: string[];
|
|
/** 指定选中项 */
|
|
value?: string[];
|
|
/** 选择完成后的回调 */
|
|
onChange?: (value: string[], selectedOptions?: CascaderOptionType[]) => void;
|
|
/** 选择后展示的渲染函数 */
|
|
displayRender?: (label: string[], selectedOptions?: CascaderOptionType[]) => React.ReactNode;
|
|
/** 自定义样式 */
|
|
style?: React.CSSProperties;
|
|
/** 自定义类名 */
|
|
className?: string;
|
|
/** 自定义浮层类名 */
|
|
popupClassName?: string;
|
|
/** 浮层预设位置:`bottomLeft` `bottomRight` `topLeft` `topRight` */
|
|
popupPlacement?: string;
|
|
/** 输入框占位文本 */
|
|
placeholder?: string;
|
|
/** 输入框大小,可选 `large` `default` `small` */
|
|
size?: SizeType;
|
|
/** whether has border style */
|
|
bordered?: boolean;
|
|
/** 禁用 */
|
|
disabled?: boolean;
|
|
/** 是否支持清除 */
|
|
allowClear?: boolean;
|
|
showSearch?: boolean | ShowSearchType;
|
|
notFoundContent?: React.ReactNode;
|
|
loadData?: (selectedOptions?: CascaderOptionType[]) => void;
|
|
/** 次级菜单的展开方式,可选 'click' 和 'hover' */
|
|
expandTrigger?: CascaderExpandTrigger;
|
|
/** 当此项为 true 时,点选每级菜单选项值都会发生变化 */
|
|
changeOnSelect?: boolean;
|
|
/** 浮层可见变化时回调 */
|
|
onPopupVisibleChange?: (popupVisible: boolean) => void;
|
|
prefixCls?: string;
|
|
inputPrefixCls?: string;
|
|
getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
|
|
popupVisible?: boolean;
|
|
/** use this after antd@3.7.0 */
|
|
fieldNames?: FieldNamesType;
|
|
suffixIcon?: React.ReactNode;
|
|
}
|
|
|
|
export interface CascaderState {
|
|
inputFocused: boolean;
|
|
inputValue: string;
|
|
value: string[];
|
|
popupVisible: boolean | undefined;
|
|
flattenOptions: CascaderOptionType[][] | undefined;
|
|
prevProps: CascaderProps;
|
|
}
|
|
|
|
interface CascaderLocale {
|
|
placeholder?: string;
|
|
}
|
|
|
|
// We limit the filtered item count by default
|
|
const defaultLimit = 50;
|
|
|
|
function highlightKeyword(str: string, keyword: string, prefixCls: string | undefined) {
|
|
return str.split(keyword).map((node: string, index: number) => (index === 0
|
|
? node
|
|
: [
|
|
<span className={`${prefixCls}-menu-item-keyword`} key="seperator">
|
|
{keyword}
|
|
</span>,
|
|
node,
|
|
]));
|
|
}
|
|
|
|
function defaultFilterOption(
|
|
inputValue: string,
|
|
path: CascaderOptionType[],
|
|
names: FilledFieldNamesType,
|
|
) {
|
|
return path.some(option => (option[names.label] as string).indexOf(inputValue) > -1);
|
|
}
|
|
|
|
function defaultRenderFilteredOption(
|
|
inputValue: string,
|
|
path: CascaderOptionType[],
|
|
prefixCls: string | undefined,
|
|
names: FilledFieldNamesType,
|
|
) {
|
|
return path.map((option, index) => {
|
|
const label = option[names.label];
|
|
const node =
|
|
(label as string).indexOf(inputValue) > -1
|
|
? highlightKeyword(label as string, inputValue, prefixCls)
|
|
: label;
|
|
return index === 0 ? node : [' / ', node];
|
|
});
|
|
}
|
|
|
|
function defaultSortFilteredOption(
|
|
a: CascaderOptionType[],
|
|
b: CascaderOptionType[],
|
|
inputValue: string,
|
|
names: FilledFieldNamesType,
|
|
) {
|
|
function callback(elem: CascaderOptionType) {
|
|
return (elem[names.label] as string).indexOf(inputValue) > -1;
|
|
}
|
|
|
|
return a.findIndex(callback) - b.findIndex(callback);
|
|
}
|
|
|
|
function getFieldNames({ fieldNames }: CascaderProps) {
|
|
return fieldNames;
|
|
}
|
|
|
|
function getFilledFieldNames(props: CascaderProps) {
|
|
const fieldNames = getFieldNames(props) || {};
|
|
const names: FilledFieldNamesType = {
|
|
children: fieldNames.children || 'children',
|
|
label: fieldNames.label || 'label',
|
|
value: fieldNames.value || 'value',
|
|
};
|
|
return names;
|
|
}
|
|
|
|
function flattenTree(
|
|
options: CascaderOptionType[],
|
|
props: CascaderProps,
|
|
ancestor: CascaderOptionType[] = [],
|
|
) {
|
|
const names: FilledFieldNamesType = getFilledFieldNames(props);
|
|
let flattenOptions: CascaderOptionType[][] = [];
|
|
const childrenName = names.children;
|
|
options.forEach(option => {
|
|
const path = ancestor.concat(option);
|
|
if (props.changeOnSelect || !option[childrenName] || !option[childrenName].length) {
|
|
flattenOptions.push(path);
|
|
}
|
|
if (option[childrenName]) {
|
|
flattenOptions = flattenOptions.concat(flattenTree(option[childrenName], props, path));
|
|
}
|
|
});
|
|
return flattenOptions;
|
|
}
|
|
|
|
const defaultDisplayRender = (label: string[]) => label.join(' / ');
|
|
|
|
function warningValueNotExist(list: CascaderOptionType[], fieldNames: FieldNamesType = {}) {
|
|
(list || []).forEach(item => {
|
|
const valueFieldName = fieldNames.value || 'value';
|
|
warning(valueFieldName in item, 'Cascader', 'Not found `value` in `options`.');
|
|
warningValueNotExist(item[fieldNames.children || 'children'], fieldNames);
|
|
});
|
|
}
|
|
|
|
class Cascader extends React.Component<CascaderProps, CascaderState> {
|
|
static defaultProps = {
|
|
transitionName: 'slide-up',
|
|
options: [],
|
|
disabled: false,
|
|
allowClear: true,
|
|
bordered: true,
|
|
};
|
|
|
|
static getDerivedStateFromProps(nextProps: CascaderProps, { prevProps }: CascaderState) {
|
|
const newState: Partial<CascaderState> = {
|
|
prevProps: nextProps,
|
|
};
|
|
|
|
if ('value' in nextProps) {
|
|
newState.value = nextProps.value || [];
|
|
}
|
|
if ('popupVisible' in nextProps) {
|
|
newState.popupVisible = nextProps.popupVisible;
|
|
}
|
|
if (nextProps.showSearch && prevProps.options !== nextProps.options) {
|
|
newState.flattenOptions = flattenTree(nextProps.options, nextProps);
|
|
}
|
|
|
|
if (process.env.NODE_ENV !== 'production' && nextProps.options) {
|
|
warningValueNotExist(nextProps.options, getFieldNames(nextProps));
|
|
}
|
|
|
|
return newState;
|
|
}
|
|
|
|
cachedOptions: CascaderOptionType[] = [];
|
|
|
|
private input: Input;
|
|
|
|
constructor(props: CascaderProps) {
|
|
super(props);
|
|
this.state = {
|
|
value: props.value || props.defaultValue || [],
|
|
inputValue: '',
|
|
inputFocused: false,
|
|
popupVisible: props.popupVisible,
|
|
flattenOptions: props.showSearch ? flattenTree(props.options, props) : undefined,
|
|
prevProps: props,
|
|
};
|
|
}
|
|
|
|
setValue = (value: string[], selectedOptions: CascaderOptionType[] = []) => {
|
|
if (!('value' in this.props)) {
|
|
this.setState({ value });
|
|
}
|
|
const { onChange } = this.props;
|
|
if (onChange) {
|
|
onChange(value, selectedOptions);
|
|
}
|
|
};
|
|
|
|
getLabel() {
|
|
const { options, displayRender = defaultDisplayRender as Function } = this.props;
|
|
const names = getFilledFieldNames(this.props);
|
|
const { value } = this.state;
|
|
const unwrappedValue = Array.isArray(value[0]) ? value[0] : value;
|
|
const selectedOptions: CascaderOptionType[] = arrayTreeFilter(
|
|
options,
|
|
(o: CascaderOptionType, level: number) => o[names.value] === unwrappedValue[level],
|
|
{ childrenKeyName: names.children },
|
|
);
|
|
const label = selectedOptions.map(o => o[names.label]);
|
|
return displayRender(label, selectedOptions);
|
|
}
|
|
|
|
saveInput = (node: Input) => {
|
|
this.input = node;
|
|
};
|
|
|
|
handleChange = (value: any, selectedOptions: CascaderOptionType[]) => {
|
|
this.setState({ inputValue: '' });
|
|
if (selectedOptions[0].__IS_FILTERED_OPTION) {
|
|
const unwrappedValue = value[0];
|
|
const unwrappedSelectedOptions = selectedOptions[0].path;
|
|
this.setValue(unwrappedValue, unwrappedSelectedOptions);
|
|
return;
|
|
}
|
|
this.setValue(value, selectedOptions);
|
|
};
|
|
|
|
handlePopupVisibleChange = (popupVisible: boolean) => {
|
|
if (!('popupVisible' in this.props)) {
|
|
this.setState(state => ({
|
|
popupVisible,
|
|
inputFocused: popupVisible,
|
|
inputValue: popupVisible ? state.inputValue : '',
|
|
}));
|
|
}
|
|
|
|
const { onPopupVisibleChange } = this.props;
|
|
if (onPopupVisibleChange) {
|
|
onPopupVisibleChange(popupVisible);
|
|
}
|
|
};
|
|
|
|
handleInputBlur = () => {
|
|
this.setState({
|
|
inputFocused: false,
|
|
});
|
|
};
|
|
|
|
handleInputClick = (e: React.MouseEvent<HTMLInputElement>) => {
|
|
const { inputFocused, popupVisible } = this.state;
|
|
// Prevent `Trigger` behaviour.
|
|
if (inputFocused || popupVisible) {
|
|
e.stopPropagation();
|
|
if (e.nativeEvent.stopImmediatePropagation) {
|
|
e.nativeEvent.stopImmediatePropagation();
|
|
}
|
|
}
|
|
};
|
|
|
|
handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
// SPACE => https://github.com/ant-design/ant-design/issues/16871
|
|
if (e.keyCode === KeyCode.BACKSPACE || e.keyCode === KeyCode.SPACE) {
|
|
e.stopPropagation();
|
|
}
|
|
};
|
|
|
|
handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const inputValue = e.target.value;
|
|
this.setState({ inputValue });
|
|
};
|
|
|
|
clearSelection = (e: React.MouseEvent<HTMLElement>) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if (!this.state.inputValue) {
|
|
this.setValue([]);
|
|
this.handlePopupVisibleChange(false);
|
|
} else {
|
|
this.setState({ inputValue: '' });
|
|
}
|
|
};
|
|
|
|
generateFilteredOptions(prefixCls: string | undefined, renderEmpty: RenderEmptyHandler) {
|
|
const { showSearch, notFoundContent } = this.props;
|
|
const names: FilledFieldNamesType = getFilledFieldNames(this.props);
|
|
const {
|
|
filter = defaultFilterOption,
|
|
render = defaultRenderFilteredOption,
|
|
sort = defaultSortFilteredOption,
|
|
limit = defaultLimit,
|
|
} = showSearch as ShowSearchType;
|
|
const { flattenOptions = [], inputValue } = this.state;
|
|
|
|
// Limit the filter if needed
|
|
let filtered: CascaderOptionType[][];
|
|
if (limit > 0) {
|
|
filtered = [];
|
|
let matchCount = 0;
|
|
|
|
// Perf optimization to filter items only below the limit
|
|
flattenOptions.some(path => {
|
|
const match = filter(this.state.inputValue, path, names);
|
|
if (match) {
|
|
filtered.push(path);
|
|
matchCount += 1;
|
|
}
|
|
return matchCount >= limit;
|
|
});
|
|
} else {
|
|
warning(
|
|
typeof limit !== 'number',
|
|
'Cascader',
|
|
"'limit' of showSearch should be positive number or false.",
|
|
);
|
|
filtered = flattenOptions.filter(path => filter(this.state.inputValue, path, names));
|
|
}
|
|
|
|
filtered.sort((a, b) => sort(a, b, inputValue, names));
|
|
|
|
if (filtered.length > 0) {
|
|
return filtered.map((path: CascaderOptionType[]) => {
|
|
return {
|
|
__IS_FILTERED_OPTION: true,
|
|
path,
|
|
[names.value]: path.map((o: CascaderOptionType) => o[names.value]),
|
|
[names.label]: render(inputValue, path, prefixCls, names),
|
|
disabled: path.some((o: CascaderOptionType) => !!o.disabled),
|
|
isEmptyNode: true,
|
|
} as CascaderOptionType;
|
|
});
|
|
}
|
|
return [
|
|
{
|
|
[names.value]: 'ANT_CASCADER_NOT_FOUND',
|
|
[names.label]: notFoundContent || renderEmpty('Cascader'),
|
|
disabled: true,
|
|
isEmptyNode: true,
|
|
},
|
|
];
|
|
}
|
|
|
|
focus() {
|
|
this.input.focus();
|
|
}
|
|
|
|
blur() {
|
|
this.input.blur();
|
|
}
|
|
|
|
getPopupPlacement(direction = 'ltr') {
|
|
const { popupPlacement } = this.props;
|
|
if (popupPlacement !== undefined) {
|
|
return popupPlacement;
|
|
}
|
|
return direction === 'rtl' ? 'bottomRight' : 'bottomLeft';
|
|
}
|
|
|
|
renderCascader = (
|
|
{
|
|
getPopupContainer: getContextPopupContainer,
|
|
getPrefixCls,
|
|
renderEmpty,
|
|
direction,
|
|
}: ConfigConsumerProps,
|
|
locale: CascaderLocale,
|
|
) => (
|
|
<SizeContext.Consumer>
|
|
{size => {
|
|
const { props, state } = this;
|
|
const {
|
|
prefixCls: customizePrefixCls,
|
|
inputPrefixCls: customizeInputPrefixCls,
|
|
children,
|
|
placeholder = locale.placeholder || 'Please select',
|
|
size: customizeSize,
|
|
disabled,
|
|
className,
|
|
style,
|
|
allowClear,
|
|
showSearch = false,
|
|
suffixIcon,
|
|
notFoundContent,
|
|
popupClassName,
|
|
bordered,
|
|
...otherProps
|
|
} = props;
|
|
const mergedSize = customizeSize || size;
|
|
|
|
const { value, inputFocused } = state;
|
|
|
|
const isRtlLayout = direction === 'rtl';
|
|
|
|
const prefixCls = getPrefixCls('cascader', customizePrefixCls);
|
|
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
|
|
|
|
const sizeCls = classNames({
|
|
[`${inputPrefixCls}-lg`]: mergedSize === 'large',
|
|
[`${inputPrefixCls}-sm`]: mergedSize === 'small',
|
|
});
|
|
const clearIcon =
|
|
(allowClear && !disabled && value.length > 0) || state.inputValue ? (
|
|
<CloseCircleFilled
|
|
className={`${prefixCls}-picker-clear`}
|
|
onClick={this.clearSelection}
|
|
/>
|
|
) : null;
|
|
const arrowCls = classNames({
|
|
[`${prefixCls}-picker-arrow`]: true,
|
|
[`${prefixCls}-picker-arrow-expand`]: state.popupVisible,
|
|
});
|
|
const pickerCls = classNames(className, `${prefixCls}-picker`, {
|
|
[`${prefixCls}-picker-rtl`]: isRtlLayout,
|
|
[`${prefixCls}-picker-with-value`]: state.inputValue,
|
|
[`${prefixCls}-picker-disabled`]: disabled,
|
|
[`${prefixCls}-picker-${mergedSize}`]: !!mergedSize,
|
|
[`${prefixCls}-picker-show-search`]: !!showSearch,
|
|
[`${prefixCls}-picker-focused`]: inputFocused,
|
|
[`${prefixCls}-picker-borderless`]: !bordered,
|
|
});
|
|
|
|
// Fix bug of https://github.com/facebook/react/pull/5004
|
|
// and https://fb.me/react-unknown-prop
|
|
const inputProps = omit(otherProps, [
|
|
'onChange',
|
|
'options',
|
|
'popupPlacement',
|
|
'transitionName',
|
|
'displayRender',
|
|
'onPopupVisibleChange',
|
|
'changeOnSelect',
|
|
'expandTrigger',
|
|
'popupVisible',
|
|
'getPopupContainer',
|
|
'loadData',
|
|
'popupClassName',
|
|
'filterOption',
|
|
'renderFilteredOption',
|
|
'sortFilteredOption',
|
|
'notFoundContent',
|
|
'fieldNames',
|
|
'bordered',
|
|
]);
|
|
|
|
let { options } = props;
|
|
const names: FilledFieldNamesType = getFilledFieldNames(this.props);
|
|
if (options && options.length > 0) {
|
|
if (state.inputValue) {
|
|
const filteredOptions = this.generateFilteredOptions(prefixCls, renderEmpty);
|
|
options = isEqual(filteredOptions, this.cachedOptions) ? this.cachedOptions : filteredOptions;
|
|
}
|
|
} else {
|
|
options = [
|
|
{
|
|
[names.label]: notFoundContent || renderEmpty('Cascader'),
|
|
[names.value]: 'ANT_CASCADER_NOT_FOUND',
|
|
disabled: true,
|
|
},
|
|
];
|
|
}
|
|
// Dropdown menu should keep previous status until it is fully closed.
|
|
if (!state.popupVisible) {
|
|
options = this.cachedOptions;
|
|
} else {
|
|
this.cachedOptions = options;
|
|
}
|
|
|
|
const dropdownMenuColumnStyle: { width?: number; height?: string } = {};
|
|
const isNotFound = (options || []).length === 1 && options[0].isEmptyNode;
|
|
if (isNotFound) {
|
|
dropdownMenuColumnStyle.height = 'auto'; // Height of one row.
|
|
}
|
|
// The default value of `matchInputWidth` is `true`
|
|
const resultListMatchInputWidth = (showSearch as ShowSearchType).matchInputWidth !== false;
|
|
if (resultListMatchInputWidth && (state.inputValue || isNotFound) && this.input) {
|
|
dropdownMenuColumnStyle.width = this.input.input.offsetWidth;
|
|
}
|
|
|
|
const inputIcon = (suffixIcon &&
|
|
(React.isValidElement<{ className?: string }>(suffixIcon) ? (
|
|
React.cloneElement(suffixIcon, {
|
|
className: classNames({
|
|
[suffixIcon.props.className!]: suffixIcon.props.className,
|
|
[`${prefixCls}-picker-arrow`]: true,
|
|
}),
|
|
})
|
|
) : (
|
|
<span className={`${prefixCls}-picker-arrow`}>{suffixIcon}</span>
|
|
))) || <DownOutlined className={arrowCls} />;
|
|
|
|
const input = children || (
|
|
<span style={style} className={pickerCls}>
|
|
<span className={`${prefixCls}-picker-label`}>{this.getLabel()}</span>
|
|
<Input
|
|
{...inputProps}
|
|
tabIndex="-1"
|
|
ref={this.saveInput}
|
|
prefixCls={inputPrefixCls}
|
|
placeholder={value && value.length > 0 ? undefined : placeholder}
|
|
className={`${prefixCls}-input ${sizeCls}`}
|
|
value={state.inputValue}
|
|
disabled={disabled}
|
|
readOnly={!showSearch}
|
|
autoComplete={inputProps.autoComplete || 'off'}
|
|
onClick={showSearch ? this.handleInputClick : undefined}
|
|
onBlur={showSearch ? this.handleInputBlur : undefined}
|
|
onKeyDown={this.handleKeyDown}
|
|
onChange={showSearch ? this.handleInputChange : undefined}
|
|
/>
|
|
{clearIcon}
|
|
{inputIcon}
|
|
</span>
|
|
);
|
|
|
|
let expandIcon = <RightOutlined />;
|
|
if (isRtlLayout) {
|
|
expandIcon = <LeftOutlined />;
|
|
}
|
|
|
|
const loadingIcon = (
|
|
<span className={`${prefixCls}-menu-item-loading-icon`}>
|
|
<RedoOutlined spin />
|
|
</span>
|
|
);
|
|
|
|
const getPopupContainer = props.getPopupContainer || getContextPopupContainer;
|
|
const rest = omit(props, ['inputIcon', 'expandIcon', 'loadingIcon', 'bordered']);
|
|
const rcCascaderRtlPopupClassName = classNames(popupClassName, {
|
|
[`${prefixCls}-menu-${direction}`]: direction === 'rtl',
|
|
});
|
|
return (
|
|
<RcCascader
|
|
{...rest}
|
|
prefixCls={prefixCls}
|
|
getPopupContainer={getPopupContainer}
|
|
options={options}
|
|
value={value}
|
|
popupVisible={state.popupVisible}
|
|
onPopupVisibleChange={this.handlePopupVisibleChange}
|
|
onChange={this.handleChange}
|
|
dropdownMenuColumnStyle={dropdownMenuColumnStyle}
|
|
expandIcon={expandIcon}
|
|
loadingIcon={loadingIcon}
|
|
popupClassName={rcCascaderRtlPopupClassName}
|
|
popupPlacement={this.getPopupPlacement(direction)}
|
|
>
|
|
{input}
|
|
</RcCascader>
|
|
);
|
|
}}
|
|
</SizeContext.Consumer>
|
|
);
|
|
|
|
render() {
|
|
return (
|
|
<ConfigConsumer>
|
|
{(configArgument: ConfigConsumerProps) => (
|
|
<LocaleReceiver>{locale => this.renderCascader(configArgument, locale)}</LocaleReceiver>
|
|
)}
|
|
</ConfigConsumer>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default Cascader;
|