mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 05:29:26 +08:00
refactor(client): add trim API for Input and Variable.TextArea (#6624)
* refactor(client): add trim API for Input and Variable.TextArea * fix(client): avoid trim property to be passed to inner component
This commit is contained in:
parent
8f5ae04743
commit
393b46a3bc
@ -11,22 +11,40 @@ import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||
import { Input as AntdInput } from 'antd';
|
||||
import { InputProps, TextAreaProps } from 'antd/es/input';
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { JSONTextAreaProps, Json } from './Json';
|
||||
import { InputReadPrettyComposed, ReadPretty } from './ReadPretty';
|
||||
|
||||
export { ReadPretty as InputReadPretty } from './ReadPretty';
|
||||
|
||||
type ComposedInput = React.FC<InputProps> & {
|
||||
type ComposedInput = React.FC<NocoBaseInputProps> & {
|
||||
ReadPretty: InputReadPrettyComposed['Input'];
|
||||
TextArea: React.FC<TextAreaProps> & { ReadPretty: InputReadPrettyComposed['TextArea'] };
|
||||
URL: React.FC<InputProps> & { ReadPretty: InputReadPrettyComposed['URL'] };
|
||||
JSON: React.FC<JSONTextAreaProps> & { ReadPretty: InputReadPrettyComposed['JSON'] };
|
||||
};
|
||||
|
||||
export type NocoBaseInputProps = InputProps & {
|
||||
trim?: boolean;
|
||||
};
|
||||
|
||||
function InputInner(props: NocoBaseInputProps) {
|
||||
const { onChange, trim, ...others } = props;
|
||||
const handleChange = useCallback(
|
||||
(ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (trim) {
|
||||
ev.target.value = ev.target.value.trim();
|
||||
}
|
||||
onChange?.(ev);
|
||||
},
|
||||
[onChange, trim],
|
||||
);
|
||||
return <AntdInput {...others} onChange={handleChange} />;
|
||||
}
|
||||
|
||||
export const Input: ComposedInput = Object.assign(
|
||||
connect(
|
||||
AntdInput,
|
||||
InputInner,
|
||||
mapProps((props, field) => {
|
||||
return {
|
||||
...props,
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
import React from 'react';
|
||||
import { mockApp } from '@nocobase/client/demo-utils';
|
||||
import { SchemaComponent, Plugin, ISchema } from '@nocobase/client';
|
||||
@ -15,15 +14,25 @@ const schema: ISchema = {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
trim: {
|
||||
type: 'string',
|
||||
title: `Trim heading and tailing spaces`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
trim: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const Demo = () => {
|
||||
return <SchemaComponent schema={schema} />;
|
||||
};
|
||||
|
||||
class DemoPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.router.add('root', { path: '/', Component: Demo })
|
||||
this.app.router.add('root', { path: '/', Component: Demo });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { useForm } from '@formily/react';
|
||||
import { Space, theme } from 'antd';
|
||||
import type { CascaderProps, DefaultOptionType } from 'antd/lib/cascader';
|
||||
import useInputStyle from 'antd/es/input/style';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
@ -110,7 +111,7 @@ function renderHTML(exp: string, keyLabelMap, delimiters: [string, string] = ['{
|
||||
});
|
||||
}
|
||||
|
||||
function createOptionsValueLabelMap(options: any[], fieldNames = { value: 'value', label: 'label' }) {
|
||||
function createOptionsValueLabelMap(options: any[], fieldNames: CascaderProps['fieldNames'] = defaultFieldNames) {
|
||||
const map = new Map<string, string[]>();
|
||||
for (const option of options) {
|
||||
map.set(option[fieldNames.value], [option[fieldNames.label]]);
|
||||
@ -220,10 +221,24 @@ function useVariablesFromValue(value: string, delimiters: [string, string] = ['{
|
||||
}, [value, delimitersString]);
|
||||
}
|
||||
|
||||
export function TextArea(props) {
|
||||
export type TextAreaProps = {
|
||||
value?: string;
|
||||
scope?: Partial<DefaultOptionType>[] | (() => Partial<DefaultOptionType>[]);
|
||||
onChange?(value: string): void;
|
||||
disabled?: boolean;
|
||||
changeOnSelect?: CascaderProps['changeOnSelect'];
|
||||
style?: React.CSSProperties;
|
||||
fieldNames?: CascaderProps['fieldNames'];
|
||||
trim?: boolean;
|
||||
delimiters?: [string, string];
|
||||
addonBefore?: React.ReactNode;
|
||||
};
|
||||
|
||||
export function TextArea(props: TextAreaProps) {
|
||||
const { wrapSSR, hashId, componentCls } = useStyles();
|
||||
const { scope, onChange, changeOnSelect, style, fieldNames, delimiters = ['{{', '}}'], addonBefore } = props;
|
||||
const value = typeof props.value === 'string' ? props.value : props.value == null ? '' : props.value.toString();
|
||||
const { scope, changeOnSelect, style, fieldNames, delimiters = ['{{', '}}'], addonBefore, trim = true } = props;
|
||||
const value =
|
||||
typeof props.value === 'string' ? props.value : props.value == null ? '' : (props.value as any).toString();
|
||||
const variables = useVariablesFromValue(value, delimiters);
|
||||
const inputRef = useRef<HTMLDivElement>(null);
|
||||
const [options, setOptions] = useState([]);
|
||||
@ -241,6 +256,14 @@ export function TextArea(props) {
|
||||
const { token } = theme.useToken();
|
||||
const delimitersString = delimiters.join(' ');
|
||||
|
||||
const onChange = useCallback(
|
||||
(target: HTMLDivElement) => {
|
||||
const v = getValue(target, delimiters);
|
||||
props.onChange?.(trim ? v.trim() : v);
|
||||
},
|
||||
[delimitersString, props.onChange, trim],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
preloadOptions(scope, variables)
|
||||
.then((preloaded) => {
|
||||
@ -324,9 +347,9 @@ export function TextArea(props) {
|
||||
|
||||
setChanged(true);
|
||||
setRange(getCurrentRange(current));
|
||||
onChange(getValue(current, delimiters));
|
||||
onChange(current);
|
||||
},
|
||||
[keyLabelMap, onChange, range, delimitersString],
|
||||
[keyLabelMap, onChange, range],
|
||||
);
|
||||
|
||||
const onInput = useCallback(
|
||||
@ -336,9 +359,9 @@ export function TextArea(props) {
|
||||
}
|
||||
setChanged(true);
|
||||
setRange(getCurrentRange(currentTarget));
|
||||
onChange(getValue(currentTarget, delimiters));
|
||||
onChange(currentTarget);
|
||||
},
|
||||
[ime, onChange, delimitersString],
|
||||
[ime, onChange],
|
||||
);
|
||||
|
||||
const onBlur = useCallback(function ({ currentTarget }) {
|
||||
@ -360,9 +383,9 @@ export function TextArea(props) {
|
||||
setIME(false);
|
||||
setChanged(true);
|
||||
setRange(getCurrentRange(currentTarget));
|
||||
onChange(getValue(currentTarget, delimiters));
|
||||
onChange(currentTarget);
|
||||
},
|
||||
[onChange, delimitersString],
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const onPaste = useCallback(
|
||||
@ -393,9 +416,9 @@ export function TextArea(props) {
|
||||
setChanged(true);
|
||||
pasteHTML(ev.currentTarget, sanitizedHTML);
|
||||
setRange(getCurrentRange(ev.currentTarget));
|
||||
onChange(getValue(ev.currentTarget, delimiters));
|
||||
onChange(ev.currentTarget);
|
||||
},
|
||||
[onChange, delimitersString],
|
||||
[onChange],
|
||||
);
|
||||
const disabled = props.disabled || form.disabled;
|
||||
return wrapSSR(
|
||||
|
Loading…
x
Reference in New Issue
Block a user