fix(client/formula): set cursor focus on input (#959)

* fix(client/formula): set cursor focus on input

* fix(client/formula): when formula field name has contains the other one will case FormulaError

refactor partial implementation
This commit is contained in:
lyf-coder 2022-10-31 11:12:19 +08:00 committed by chenos
parent 7cb5ff554e
commit 9a81b1b8ee

View File

@ -1,7 +1,7 @@
import { Field, onFormSubmitValidateStart } from '@formily/core'; import { Field, onFormSubmitValidateStart } from '@formily/core';
import { connect, mapProps, mapReadPretty, useField, useFieldSchema, useFormEffects } from '@formily/react'; import { connect, mapProps, useField, useFormEffects } from '@formily/react';
import { Button, Input, Popover, Tag, Menu, Dropdown } from 'antd'; import { Menu, Dropdown } from 'antd';
import React, { useContext, useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import ContentEditable from 'react-contenteditable'; import ContentEditable from 'react-contenteditable';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import * as math from 'mathjs'; import * as math from 'mathjs';
@ -14,62 +14,65 @@ const AntdFormulaInput = (props) => {
const inputRef = useRef(); const inputRef = useRef();
const [dropdownVisible, setDropdownVisible] = useState(false); const [dropdownVisible, setDropdownVisible] = useState(false);
const [formula, setFormula] = useState(null);
const [html, setHtml] = useState(null); const [html, setHtml] = useState(null);
const numColumns = new Map<string, string>(); const numColumns = new Map<string, string>();
const scope = {}; const scope = {};
fields.filter(field => supports.includes(field.interface)).forEach(field => { fields
numColumns.set(field.name, field.uiSchema.title); .filter((field) => supports.includes(field.interface))
scope[field.name] = 1; .forEach((field) => {
}) numColumns.set(field.name, field.uiSchema.title);
scope[field.name] = 1;
});
const keys = Array.from(numColumns.keys()); const keys = Array.from(numColumns.keys());
let initHtml;
if (value) {
initHtml = value;
numColumns.forEach((value, key) => {
initHtml = initHtml.replaceAll(key, `<span contentEditable="false" style="border: 1px solid #aaa; padding: 2px 5px;">${value}</span>`)
})
}
useEffect(() => { useEffect(() => {
if (onChange && formula) { if (value) {
let v = formula || ''; let newHtml = value;
numColumns.forEach((value, key) => { numColumns.forEach((value, key) => {
v = v.replaceAll(value, key); newHtml = newHtml.replaceAll(
}) key,
if (v != value) { `<span contentEditable="false" ><input disabled="disabled" style="width:${
onChange(v); 18 * value.length
} }px;max-width: 120px" value="${value}"/><span hidden>${key}</span></span>`,
);
});
newHtml = `${newHtml}<span style="padding-left: 5px"></span>`; // set extra span for cursor focus on last position
setHtml(newHtml);
} else {
setHtml('');
} }
}, [formula]) }, [value]);
const menu = ( const menu = (
<Menu onClick={async (args) => { <Menu
const replaceFormula = formula.replace('@', numColumns.get(args.key)); onClick={async (args) => {
const replaceHtml = html.replace('@', `<span contentEditable="false" style="border: 1px solid #aaa; padding: 2px 5px;">${numColumns.get(args.key)}</span>`); const replaceFormula = field.value.replace('@', args.key);
setFormula(replaceFormula); if (onChange && replaceFormula != field.value) {
setHtml(replaceHtml); onChange(replaceFormula);
setDropdownVisible(false); }
}}> setDropdownVisible(false);
{ (inputRef.current as any).focus();
keys.map(key => (<Menu.Item key={key}>{numColumns.get(key)}</Menu.Item>)) }}
} >
{keys.map((key) => (
<Menu.Item key={key}>{numColumns.get(key)}</Menu.Item>
))}
</Menu> </Menu>
); );
const handleChange = (e) => { const handleChange = (e) => {
const current = inputRef.current as any; if (onChange) {
setFormula(e.currentTarget.textContent); if (e.currentTarget.textContent == '') {
setHtml(current.innerHTML); onChange(null);
if (e.currentTarget.textContent == '' && onChange) { } else {
onChange(null); onChange(e.currentTarget.textContent);
}
} }
} };
const handleKeyDown = (e) => { const handleKeyDown = (e) => {
const {key} = e; const { key } = e;
switch (key) { switch (key) {
case 'Enter': case 'Enter':
e.preventDefault(); e.preventDefault();
@ -82,22 +85,23 @@ const AntdFormulaInput = (props) => {
setDropdownVisible(false); setDropdownVisible(false);
break; break;
} }
} };
useFormEffects(() => { useFormEffects(() => {
onFormSubmitValidateStart(() => { onFormSubmitValidateStart(() => {
try { try {
math.evaluate(field.value, scope); math.evaluate(field.value, scope);
field.feedbacks = []; field.feedbacks = [];
} catch { } catch (e) {
console.error(field.value, scope, (e as Error).message);
field.setFeedback({ field.setFeedback({
type: 'error', type: 'error',
code: 'FormulaError', code: 'FormulaError',
messages: [t('Formula error.')], messages: [t('Formula error.')],
}); });
} }
}) });
}) });
return ( return (
<Dropdown overlay={menu} visible={dropdownVisible}> <Dropdown overlay={menu} visible={dropdownVisible}>
@ -106,16 +110,12 @@ const AntdFormulaInput = (props) => {
className="ant-input" className="ant-input"
onChange={handleChange} onChange={handleChange}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
html={html || initHtml || ''} html={html || ''}
/> />
</Dropdown> </Dropdown>
) );
} };
export const FormulaInput = connect( export const FormulaInput = connect(AntdFormulaInput, mapProps({}));
AntdFormulaInput,
mapProps({
}),
);
export default FormulaInput; export default FormulaInput;