mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:48:55 +08:00
feat: input-text支持事件动作 & demo doc
This commit is contained in:
parent
8cc8283ded
commit
5a5cc4f2cb
@ -449,7 +449,7 @@ exports[`Renderer:text type is password 1`] = `
|
||||
placeholder=""
|
||||
size="10"
|
||||
type="password"
|
||||
value="abcd"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -1243,12 +1243,12 @@ exports[`Renderer:text with counter 2`] = `
|
||||
placeholder=""
|
||||
size="10"
|
||||
type="text"
|
||||
value="abcd"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
class="cxd-TextControl-counter"
|
||||
>
|
||||
4
|
||||
0
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -1507,12 +1507,12 @@ exports[`Renderer:text with counter and maxLength 2`] = `
|
||||
placeholder=""
|
||||
size="10"
|
||||
type="text"
|
||||
value="abcd"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
class="cxd-TextControl-counter"
|
||||
>
|
||||
4/10
|
||||
0/10
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -1776,10 +1776,11 @@ exports[`Renderer:text with options and multiple: first option selected 1`] = `
|
||||
class="cxd-Form-control cxd-TextControl is-focused"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="downshift-1-label"
|
||||
class="cxd-TextControl-input cxd-TextControl-input--withAC cxd-TextControl-input--multiple"
|
||||
aria-owns="downshift-1-menu"
|
||||
class="cxd-TextControl-input cxd-TextControl-input--withAC is-opened cxd-TextControl-input--multiple"
|
||||
role="combobox"
|
||||
>
|
||||
<div
|
||||
@ -1798,6 +1799,7 @@ exports[`Renderer:text with options and multiple: first option selected 1`] = `
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="downshift-1-menu"
|
||||
aria-labelledby="downshift-1-label"
|
||||
autocomplete="off"
|
||||
id="downshift-1-input"
|
||||
@ -1806,6 +1808,40 @@ exports[`Renderer:text with options and multiple: first option selected 1`] = `
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="cxd-TextControl-sugs"
|
||||
>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="cxd-TextControl-sugItem"
|
||||
id="downshift-1-item-0"
|
||||
role="option"
|
||||
>
|
||||
<span>
|
||||
OptionB
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="cxd-TextControl-sugItem"
|
||||
id="downshift-1-item-1"
|
||||
role="option"
|
||||
>
|
||||
<span>
|
||||
OptionC
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="cxd-TextControl-sugItem"
|
||||
id="downshift-1-item-2"
|
||||
role="option"
|
||||
>
|
||||
<span>
|
||||
OptionD
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -2304,10 +2340,11 @@ exports[`Renderer:text with options and multiple: second option selected 1`] = `
|
||||
class="cxd-Form-control cxd-TextControl is-focused"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="downshift-1-label"
|
||||
class="cxd-TextControl-input cxd-TextControl-input--withAC cxd-TextControl-input--multiple"
|
||||
aria-owns="downshift-1-menu"
|
||||
class="cxd-TextControl-input cxd-TextControl-input--withAC is-opened cxd-TextControl-input--multiple"
|
||||
role="combobox"
|
||||
>
|
||||
<div
|
||||
@ -2340,6 +2377,7 @@ exports[`Renderer:text with options and multiple: second option selected 1`] = `
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="downshift-1-menu"
|
||||
aria-labelledby="downshift-1-label"
|
||||
autocomplete="off"
|
||||
id="downshift-1-input"
|
||||
@ -2348,6 +2386,30 @@ exports[`Renderer:text with options and multiple: second option selected 1`] = `
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="cxd-TextControl-sugs"
|
||||
>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="cxd-TextControl-sugItem"
|
||||
id="downshift-1-item-0"
|
||||
role="option"
|
||||
>
|
||||
<span>
|
||||
OptionC
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="cxd-TextControl-sugItem"
|
||||
id="downshift-1-item-1"
|
||||
role="option"
|
||||
>
|
||||
<span>
|
||||
OptionD
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -2636,14 +2698,16 @@ exports[`Renderer:text with options: select first option 1`] = `
|
||||
class="cxd-Form-control cxd-TextControl is-focused"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="downshift-0-label"
|
||||
class="cxd-TextControl-input cxd-TextControl-input--withAC"
|
||||
aria-owns="downshift-0-menu"
|
||||
class="cxd-TextControl-input cxd-TextControl-input--withAC is-opened"
|
||||
role="combobox"
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="downshift-0-menu"
|
||||
aria-labelledby="downshift-0-label"
|
||||
autocomplete="off"
|
||||
id="downshift-0-input"
|
||||
|
@ -55,6 +55,7 @@ test('Control:onChange', async () => {
|
||||
value: '123'
|
||||
}
|
||||
});
|
||||
await wait(100);
|
||||
expect(onChange).toBeCalledTimes(1);
|
||||
|
||||
fireEvent.click(getByText('Submit'));
|
||||
|
@ -36,7 +36,9 @@ const setup = (inputOptions: any = {}, formOptions: any = {}) => {
|
||||
'input[name="text"]'
|
||||
) as HTMLInputElement;
|
||||
|
||||
const submitBtn = utils.container.querySelector('button[type="submit"]');
|
||||
const submitBtn = utils.container.querySelector(
|
||||
'button[type="submit"]'
|
||||
) as HTMLElement;
|
||||
|
||||
return {
|
||||
input,
|
||||
@ -48,13 +50,14 @@ const setup = (inputOptions: any = {}, formOptions: any = {}) => {
|
||||
/**
|
||||
* 基本使用
|
||||
*/
|
||||
test('Renderer:text', () => {
|
||||
test('Renderer:text', async () => {
|
||||
const {container, input} = setup();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
// 输入是否正常
|
||||
fireEvent.change(input, {target: {value: 'AbCd'}});
|
||||
// 事件机制导致hanleChange变为异步
|
||||
await wait(100);
|
||||
expect(input.value).toBe('AbCd');
|
||||
});
|
||||
|
||||
@ -128,10 +131,12 @@ test('Renderer:text with clearable', async () => {
|
||||
clearable: true
|
||||
});
|
||||
fireEvent.change(input, {target: {value: 'abcd'}}); // 有值之后才会显示clear的icon
|
||||
|
||||
await wait(100);
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
fireEvent.click(container.querySelector('a.cxd-TextControl-clear'));
|
||||
fireEvent.click(
|
||||
container.querySelector('a.cxd-TextControl-clear') as HTMLElement
|
||||
);
|
||||
await wait(100);
|
||||
expect(input.value).toBe('');
|
||||
});
|
||||
@ -158,13 +163,19 @@ test('Renderer:text with options', async () => {
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
// 展开 options
|
||||
fireEvent.click(container.querySelector('.cxd-TextControl-input'));
|
||||
fireEvent.click(
|
||||
container.querySelector('.cxd-TextControl-input') as HTMLElement
|
||||
);
|
||||
await wait(100);
|
||||
expect(container).toMatchSnapshot('options is open');
|
||||
|
||||
// 选中一项
|
||||
fireEvent.click(
|
||||
container.querySelector('.cxd-TextControl-sugs .cxd-TextControl-sugItem')
|
||||
container.querySelector(
|
||||
'.cxd-TextControl-sugs .cxd-TextControl-sugItem'
|
||||
) as HTMLElement
|
||||
);
|
||||
await wait(100);
|
||||
// expect(input.value).toBe('a');
|
||||
expect(container).toMatchSnapshot('select first option');
|
||||
});
|
||||
@ -198,29 +209,39 @@ test('Renderer:text with options and multiple', async () => {
|
||||
{debug: true}
|
||||
);
|
||||
|
||||
const textControl = container.querySelector('.cxd-TextControl-input');
|
||||
const textControl = container.querySelector(
|
||||
'.cxd-TextControl-input'
|
||||
) as HTMLElement;
|
||||
|
||||
// 展开 options
|
||||
fireEvent.click(textControl);
|
||||
await wait(100);
|
||||
expect(container).toMatchSnapshot('options is opened');
|
||||
|
||||
// 选中第一项
|
||||
fireEvent.click(
|
||||
container.querySelector('.cxd-TextControl-sugs .cxd-TextControl-sugItem')
|
||||
container.querySelector(
|
||||
'.cxd-TextControl-sugs .cxd-TextControl-sugItem'
|
||||
) as HTMLElement
|
||||
);
|
||||
await wait(100);
|
||||
// expect(input.value).toBe('a');
|
||||
expect(container).toMatchSnapshot('first option selected');
|
||||
|
||||
// 再次打开 options
|
||||
fireEvent.click(textControl);
|
||||
await wait(100);
|
||||
expect(container).toMatchSnapshot(
|
||||
'options is opened again, and first option already selected'
|
||||
);
|
||||
|
||||
// 选中 options 中的第一项
|
||||
fireEvent.click(
|
||||
container.querySelector('.cxd-TextControl-sugs .cxd-TextControl-sugItem')
|
||||
container.querySelector(
|
||||
'.cxd-TextControl-sugs .cxd-TextControl-sugItem'
|
||||
) as HTMLElement
|
||||
);
|
||||
await wait(100);
|
||||
// expect(input.value).toBe('a,b');
|
||||
expect(container).toMatchSnapshot('second option selected');
|
||||
});
|
||||
@ -267,19 +288,21 @@ test('Renderer:text with counter and maxLength', () => {
|
||||
/**
|
||||
* 转小写
|
||||
*/
|
||||
test('Renderer:text with transform lowerCase', () => {
|
||||
test('Renderer:text with transform lowerCase', async () => {
|
||||
const {input} = setup({transform: {lowerCase: true}});
|
||||
|
||||
fireEvent.change(input, {target: {value: 'AbCd'}});
|
||||
await wait(100);
|
||||
expect(input.value).toBe('abcd');
|
||||
});
|
||||
|
||||
/**
|
||||
* 转大写
|
||||
*/
|
||||
test('Renderer:text with transform upperCase', () => {
|
||||
test('Renderer:text with transform upperCase', async () => {
|
||||
const {input} = setup({transform: {upperCase: true}});
|
||||
|
||||
fireEvent.change(input, {target: {value: 'AbCd'}});
|
||||
await wait(100);
|
||||
expect(input.value).toBe('ABCD');
|
||||
});
|
||||
|
81
examples/components/EventAction/InputEvent.jsx
Normal file
81
examples/components/EventAction/InputEvent.jsx
Normal file
@ -0,0 +1,81 @@
|
||||
export default {
|
||||
type: 'page',
|
||||
title: '输入类组件事件',
|
||||
regions: ['body', 'toolbar', 'header'],
|
||||
body: [
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: 'InputText输入框',
|
||||
inline: false,
|
||||
wrapperComponent: 'h2'
|
||||
},
|
||||
{
|
||||
type: 'form',
|
||||
debug: true,
|
||||
api: '/api/mock2/form/saveForm',
|
||||
body: [
|
||||
{
|
||||
type: 'group',
|
||||
body: [
|
||||
{
|
||||
name: 'trigger1',
|
||||
id: 'trigger1',
|
||||
type: 'action',
|
||||
label: 'clear触发器',
|
||||
level: 'primary',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'clear',
|
||||
componentId: 'clear-receiver',
|
||||
description: '点击清空指定输入框的内容'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'clear-receiver',
|
||||
id: 'clear-receiver',
|
||||
type: 'input-text',
|
||||
label: 'clear动作测试',
|
||||
mode: 'row',
|
||||
value: 'chunk of text ready to be cleared.'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
body: [
|
||||
{
|
||||
name: 'trigger2',
|
||||
id: 'trigger2',
|
||||
type: 'action',
|
||||
label: 'focus触发器',
|
||||
level: 'primary',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'focus',
|
||||
componentId: 'focus-receiver',
|
||||
description: '点击使指定输入框聚焦'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'focus-receiver',
|
||||
id: 'focus-receiver',
|
||||
type: 'input-text',
|
||||
label: 'focus动作测试',
|
||||
mode: 'row'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
@ -73,6 +73,7 @@ import CustomEventActionSchema from './EventAction/Custom';
|
||||
import LogicEventActionSchema from './EventAction/Logic';
|
||||
import StopEventActionSchema from './EventAction/Stop';
|
||||
import DataFlowEventActionSchema from './EventAction/DataFlow';
|
||||
import InputEventSchema from './EventAction/InputEvent';
|
||||
import WizardSchema from './Wizard';
|
||||
import ChartSchema from './Chart';
|
||||
import EChartsEditorSchema from './ECharts';
|
||||
@ -508,22 +509,29 @@ export const examples = [
|
||||
|
||||
{
|
||||
label: '事件动作机制',
|
||||
icon: 'fa fa-bolt',
|
||||
icon: 'fa fa-bullhorn',
|
||||
children: [
|
||||
{
|
||||
label: '执行通用动作',
|
||||
label: '执行通用动作',
|
||||
path: '/examples/event-action/common',
|
||||
component: makeSchemaRenderer(CommonEventActionSchema)
|
||||
},
|
||||
{
|
||||
label: '广播(自定义事件)',
|
||||
label: '广播(自定义事件)',
|
||||
path: '/examples/event-action/broadcat',
|
||||
component: makeSchemaRenderer(BroadcastEventActionSchema)
|
||||
},
|
||||
{
|
||||
label: '执行其他组件动作',
|
||||
label: '执行其他组件动作',
|
||||
path: '/examples/event-action/cmpt',
|
||||
component: makeSchemaRenderer(CmptEventActionSchema)
|
||||
component: makeSchemaRenderer(CmptEventActionSchema),
|
||||
children: [
|
||||
{
|
||||
label: '输入类组件',
|
||||
path: '/examples/event/input',
|
||||
component: makeSchemaRenderer(InputEventSchema)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '自定义JS',
|
||||
@ -531,7 +539,7 @@ export const examples = [
|
||||
component: makeSchemaRenderer(CustomEventActionSchema)
|
||||
},
|
||||
{
|
||||
label: '执行逻辑编排动作',
|
||||
label: '执行逻辑编排动作',
|
||||
path: '/examples/event-action/logic',
|
||||
component: makeSchemaRenderer(LogicEventActionSchema)
|
||||
},
|
||||
|
@ -18,9 +18,10 @@ import {Schema, SchemaNode} from './types';
|
||||
import {DebugWrapper, enableAMISDebug} from './utils/debug';
|
||||
import getExprProperties from './utils/filter-schema';
|
||||
import {anyChanged, chainEvents, autobind} from './utils/helper';
|
||||
import {RendererEvent} from './utils/renderer-event';
|
||||
import {SimpleMap} from './utils/SimpleMap';
|
||||
|
||||
import type {RendererEvent} from './utils/renderer-event';
|
||||
|
||||
interface SchemaRendererProps extends Partial<RendererProps> {
|
||||
schema: Schema;
|
||||
$path: string;
|
||||
|
@ -21,7 +21,10 @@ export class CmptAction implements Action {
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
// 根据唯一ID查找指定组件
|
||||
/**
|
||||
* 根据唯一ID查找指定组件
|
||||
* 触发组件未指定id或未指定响应组件componentId,则使用触发组件响应
|
||||
*/
|
||||
const component =
|
||||
action.componentId && renderer.props.$schema.id !== action.componentId
|
||||
? event.context.scoped?.getComponentById(action.componentId)
|
||||
|
@ -21,6 +21,11 @@ import {ActionSchema} from '../Action';
|
||||
import {SchemaApi} from '../../Schema';
|
||||
import {generateIcon} from '../../utils/icon';
|
||||
|
||||
import type {Option} from '../../components/Select';
|
||||
import type {RendererEvent} from '../../utils/renderer-event';
|
||||
import type {IScopedContext} from '../../Scoped';
|
||||
import type {ListenerAction} from '../../actions/Action';
|
||||
|
||||
// declare function matchSorter(items:Array<any>, input:any, options:any): Array<any>;
|
||||
|
||||
/**
|
||||
@ -97,6 +102,71 @@ export interface TextState {
|
||||
isFocused?: boolean;
|
||||
}
|
||||
|
||||
export type InputTextRendererEvent =
|
||||
| 'blur'
|
||||
| 'focus'
|
||||
| 'click'
|
||||
| 'change'
|
||||
| 'clear'
|
||||
| 'enter';
|
||||
|
||||
/**
|
||||
* 渲染器事件派发
|
||||
*/
|
||||
async function rendererEventDispatcher<T extends OptionsControlProps>(
|
||||
props: T,
|
||||
e: InputTextRendererEvent,
|
||||
ctx: Record<string, any> = {}
|
||||
): Promise<RendererEvent<any> | undefined> {
|
||||
const {dispatchEvent, data} = props;
|
||||
|
||||
return dispatchEvent(e, createObject(data, ctx));
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染器事件装饰器
|
||||
*
|
||||
* @param {InputTextRendererEvent} e 事件类型
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function bindRendererEvent<T extends OptionsControlProps, P = any>(
|
||||
e: InputTextRendererEvent,
|
||||
ctx: Record<string, any> = {}
|
||||
) {
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string,
|
||||
descriptor: TypedPropertyDescriptor<any>
|
||||
) {
|
||||
let fn = descriptor.value;
|
||||
|
||||
if (!fn || typeof fn !== 'function') {
|
||||
throw new Error(
|
||||
`decorator can only be applied to methods not: ${typeof fn}`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...descriptor,
|
||||
|
||||
value: async function boundFn(...params: any[]) {
|
||||
const context = (this as TypedPropertyDescriptor<any> & {props: T})
|
||||
?.props;
|
||||
let value = e === 'clear' ? context?.resetValue : context?.value;
|
||||
const dispatcher = await rendererEventDispatcher<T>(context, e, {
|
||||
value
|
||||
});
|
||||
|
||||
if (dispatcher?.prevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
return fn.apply(this, [...params]);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default class TextControl extends React.PureComponent<
|
||||
TextProps,
|
||||
TextState
|
||||
@ -192,6 +262,19 @@ export default class TextControl extends React.PureComponent<
|
||||
this.input = ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动作处理
|
||||
*/
|
||||
doAction(action: ListenerAction, args: any) {
|
||||
const actionType = action?.actionType as string;
|
||||
|
||||
if (!!~['clear', 'reset'].indexOf(actionType)) {
|
||||
this.clearValue();
|
||||
} else if (actionType === 'focus') {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (!this.input) {
|
||||
return;
|
||||
@ -204,6 +287,7 @@ export default class TextControl extends React.PureComponent<
|
||||
len && this.input.setSelectionRange(len, len);
|
||||
}
|
||||
|
||||
@bindRendererEvent<TextProps>('clear')
|
||||
clearValue() {
|
||||
const {onChange, resetValue} = this.props;
|
||||
|
||||
@ -220,29 +304,15 @@ export default class TextControl extends React.PureComponent<
|
||||
}
|
||||
|
||||
removeItem(index: number) {
|
||||
const {
|
||||
selectedOptions,
|
||||
onChange,
|
||||
joinValues,
|
||||
extractValue,
|
||||
delimiter,
|
||||
valueField
|
||||
} = this.props;
|
||||
const {selectedOptions, onChange} = this.props;
|
||||
|
||||
const newValue = selectedOptions.concat();
|
||||
newValue.splice(index, 1);
|
||||
|
||||
onChange(
|
||||
joinValues
|
||||
? newValue
|
||||
.map(item => item[valueField || 'value'])
|
||||
.join(delimiter || ',')
|
||||
: extractValue
|
||||
? newValue.map(item => item[valueField || 'value'])
|
||||
: newValue
|
||||
);
|
||||
onChange(this.normalizeValue(newValue));
|
||||
}
|
||||
|
||||
@bindRendererEvent<TextProps>('click')
|
||||
handleClick() {
|
||||
this.focus();
|
||||
this.setState({
|
||||
@ -250,6 +320,7 @@ export default class TextControl extends React.PureComponent<
|
||||
});
|
||||
}
|
||||
|
||||
@bindRendererEvent<TextProps>('focus')
|
||||
handleFocus(e: any) {
|
||||
this.setState({
|
||||
isOpen: true,
|
||||
@ -259,6 +330,7 @@ export default class TextControl extends React.PureComponent<
|
||||
this.props.onFocus && this.props.onFocus(e);
|
||||
}
|
||||
|
||||
@bindRendererEvent<TextProps>('blur')
|
||||
handleBlur(e: any) {
|
||||
const {onBlur, trimContents, value, onChange} = this.props;
|
||||
|
||||
@ -294,32 +366,16 @@ export default class TextControl extends React.PureComponent<
|
||||
);
|
||||
}
|
||||
|
||||
handleKeyDown(evt: React.KeyboardEvent<HTMLInputElement>) {
|
||||
const {
|
||||
selectedOptions,
|
||||
onChange,
|
||||
joinValues,
|
||||
extractValue,
|
||||
delimiter,
|
||||
multiple,
|
||||
valueField,
|
||||
creatable
|
||||
} = this.props;
|
||||
async handleKeyDown(evt: React.KeyboardEvent<HTMLInputElement>) {
|
||||
const {selectedOptions, onChange, multiple, creatable} = this.props;
|
||||
|
||||
if (selectedOptions.length && !this.state.inputValue && evt.keyCode === 8) {
|
||||
evt.preventDefault();
|
||||
const newValue = selectedOptions.concat();
|
||||
newValue.pop();
|
||||
|
||||
onChange(
|
||||
joinValues
|
||||
? newValue
|
||||
.map(item => item[valueField || 'value'])
|
||||
.join(delimiter || ',')
|
||||
: extractValue
|
||||
? newValue.map(item => item[valueField || 'value'])
|
||||
: newValue
|
||||
);
|
||||
onChange(this.normalizeValue(newValue));
|
||||
|
||||
this.setState(
|
||||
{
|
||||
inputValue: ''
|
||||
@ -327,35 +383,39 @@ export default class TextControl extends React.PureComponent<
|
||||
this.loadAutoComplete
|
||||
);
|
||||
} else if (
|
||||
evt.keyCode === 13 &&
|
||||
evt.key === 'Enter' &&
|
||||
this.state.inputValue &&
|
||||
typeof this.highlightedIndex !== 'number'
|
||||
) {
|
||||
evt.preventDefault();
|
||||
const value = this.state.inputValue;
|
||||
let value: string | Array<string | any> = this.state.inputValue;
|
||||
|
||||
if (multiple) {
|
||||
if (value && !find(selectedOptions, item => item.value == value)) {
|
||||
const newValue = selectedOptions.concat();
|
||||
newValue.push({
|
||||
label: value,
|
||||
value: value
|
||||
});
|
||||
if (
|
||||
multiple &&
|
||||
value &&
|
||||
!find(selectedOptions, item => item.value == value)
|
||||
) {
|
||||
const newValue = selectedOptions.concat();
|
||||
newValue.push({
|
||||
label: value,
|
||||
value: value
|
||||
});
|
||||
|
||||
onChange(
|
||||
joinValues
|
||||
? newValue
|
||||
.map(item => item[valueField || 'value'])
|
||||
.join(delimiter || ',')
|
||||
: extractValue
|
||||
? newValue.map(item => item[valueField || 'value'])
|
||||
: newValue
|
||||
);
|
||||
}
|
||||
} else {
|
||||
onChange(value);
|
||||
value = this.normalizeValue(newValue).concat();
|
||||
}
|
||||
|
||||
const dispatcher = await rendererEventDispatcher<TextProps>(
|
||||
this.props,
|
||||
'enter',
|
||||
{value}
|
||||
);
|
||||
|
||||
if (dispatcher?.prevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(value);
|
||||
|
||||
if (creatable === false || multiple) {
|
||||
this.setState(
|
||||
{
|
||||
@ -366,7 +426,7 @@ export default class TextControl extends React.PureComponent<
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
evt.keyCode === 13 &&
|
||||
evt.key === 'Enter' &&
|
||||
this.state.isOpen &&
|
||||
typeof this.highlightedIndex !== 'number'
|
||||
) {
|
||||
@ -377,16 +437,7 @@ export default class TextControl extends React.PureComponent<
|
||||
}
|
||||
|
||||
handleChange(value: any) {
|
||||
const {
|
||||
onChange,
|
||||
multiple,
|
||||
joinValues,
|
||||
extractValue,
|
||||
delimiter,
|
||||
selectedOptions,
|
||||
valueField,
|
||||
creatable
|
||||
} = this.props;
|
||||
const {onChange, multiple, selectedOptions, creatable} = this.props;
|
||||
|
||||
if (multiple) {
|
||||
const newValue = selectedOptions.concat();
|
||||
@ -395,15 +446,7 @@ export default class TextControl extends React.PureComponent<
|
||||
value: value
|
||||
});
|
||||
|
||||
onChange(
|
||||
joinValues
|
||||
? newValue
|
||||
.map(item => item[valueField || 'value'])
|
||||
.join(delimiter || ',')
|
||||
: extractValue
|
||||
? newValue.map(item => item[valueField || 'value'])
|
||||
: newValue
|
||||
);
|
||||
onChange(this.normalizeValue(newValue));
|
||||
} else {
|
||||
onChange(value);
|
||||
}
|
||||
@ -458,14 +501,32 @@ export default class TextControl extends React.PureComponent<
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleNormalInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
async handleNormalInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const {onChange} = this.props;
|
||||
|
||||
let value = e.currentTarget.value;
|
||||
const dispatcher = await rendererEventDispatcher<TextProps>(
|
||||
this.props,
|
||||
'change',
|
||||
{value: this.transformValue(value)}
|
||||
);
|
||||
|
||||
if (dispatcher?.prevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(this.transformValue(value));
|
||||
}
|
||||
|
||||
normalizeValue(value: Option[]) {
|
||||
const {delimiter, joinValues, extractValue, valueField} = this.props;
|
||||
|
||||
return joinValues
|
||||
? value.map(item => item[valueField || 'value']).join(delimiter || ',')
|
||||
: extractValue
|
||||
? value.map(item => item[valueField || 'value'])
|
||||
: value;
|
||||
}
|
||||
|
||||
transformValue(value: string) {
|
||||
const {transform} = this.props;
|
||||
|
||||
|
@ -30,10 +30,13 @@ import {
|
||||
} from '../../Schema';
|
||||
import {HocStoreFactory} from '../../WithStore';
|
||||
import {wrapControl} from './wrapControl';
|
||||
import type {OnEventProps} from '../../utils/renderer-event';
|
||||
|
||||
export type FormControlSchemaAlias = SchemaObject;
|
||||
|
||||
export interface FormBaseControl extends Omit<BaseSchema, 'type'> {
|
||||
export interface FormBaseControl
|
||||
extends Omit<BaseSchema, 'type'>,
|
||||
OnEventProps {
|
||||
/**
|
||||
* 表单项类型
|
||||
*/
|
||||
|
@ -26,6 +26,7 @@ import {
|
||||
FormBaseControl
|
||||
} from './Item';
|
||||
import {IFormItemStore} from '../../store/formItem';
|
||||
|
||||
export type OptionsControlComponent = React.ComponentType<FormControlProps>;
|
||||
|
||||
import React from 'react';
|
||||
|
@ -21,6 +21,11 @@ export interface LinkSchema extends BaseSchema {
|
||||
*/
|
||||
blank?: boolean;
|
||||
|
||||
/**
|
||||
* 链接地址
|
||||
*/
|
||||
href?: string;
|
||||
|
||||
/**
|
||||
* 链接内容,如果不配置将显示链接地址。
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user