diff --git a/packages/core/client/src/schema-component/antd/input/Input.tsx b/packages/core/client/src/schema-component/antd/input/Input.tsx index 1a7a072ca2..2870f7abf9 100644 --- a/packages/core/client/src/schema-component/antd/input/Input.tsx +++ b/packages/core/client/src/schema-component/antd/input/Input.tsx @@ -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 & { +type ComposedInput = React.FC & { ReadPretty: InputReadPrettyComposed['Input']; TextArea: React.FC & { ReadPretty: InputReadPrettyComposed['TextArea'] }; URL: React.FC & { ReadPretty: InputReadPrettyComposed['URL'] }; JSON: React.FC & { ReadPretty: InputReadPrettyComposed['JSON'] }; }; +export type NocoBaseInputProps = InputProps & { + trim?: boolean; +}; + +function InputInner(props: NocoBaseInputProps) { + const { onChange, trim, ...others } = props; + const handleChange = useCallback( + (ev: React.ChangeEvent) => { + if (trim) { + ev.target.value = ev.target.value.trim(); + } + onChange?.(ev); + }, + [onChange, trim], + ); + return ; +} + export const Input: ComposedInput = Object.assign( connect( - AntdInput, + InputInner, mapProps((props, field) => { return { ...props, diff --git a/packages/core/client/src/schema-component/antd/input/demos/new-demos/input.tsx b/packages/core/client/src/schema-component/antd/input/demos/new-demos/input.tsx index 053a736c4c..5fba3f3699 100644 --- a/packages/core/client/src/schema-component/antd/input/demos/new-demos/input.tsx +++ b/packages/core/client/src/schema-component/antd/input/demos/new-demos/input.tsx @@ -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 ; }; class DemoPlugin extends Plugin { async load() { - this.app.router.add('root', { path: '/', Component: Demo }) + this.app.router.add('root', { path: '/', Component: Demo }); } } diff --git a/packages/core/client/src/schema-component/antd/variable/TextArea.tsx b/packages/core/client/src/schema-component/antd/variable/TextArea.tsx index 53656f75e5..a68a1dd475 100644 --- a/packages/core/client/src/schema-component/antd/variable/TextArea.tsx +++ b/packages/core/client/src/schema-component/antd/variable/TextArea.tsx @@ -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(); 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[] | (() => Partial[]); + 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(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(