diff --git a/packages/vision-preset/src/field.tsx b/packages/vision-preset/src/field.tsx
deleted file mode 100644
index 346ed36b1..000000000
--- a/packages/vision-preset/src/field.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import { Component, ReactNode } from 'react';
-import {
- PopupField,
- Field as NormalField,
- EntryField,
- PlainField,
- createSettingFieldView,
- SettingsPane,
- createField,
-} from '@ali/lowcode-editor-skeleton';
-import { createSetterContent } from '@ali/lowcode-editor-core';
-import { isPlainObject } from '@ali/lowcode-utils';
-import { isSetterConfig } from '@ali/lowcode-types';
-import context from './context';
-import { VE_HOOKS } from './base/const';
-
-export class Placeholder extends Component {
- render() {
- console.info(this.props);
- return 'rending placeholder here';
- }
-}
-
-export class SettingField extends Component<{
- prop: any;
- selected?: boolean;
- forceDisplay?: string;
- className?: string;
- children?: ReactNode;
- compact?: boolean;
- key?: string;
- addonProps?: object;
-}> {
- constructor(props: any) {
- super(props);
-
- console.info(props);
- }
-
- render() {
- const { prop, selected, addonProps } = this.props;
- const display = this.props.forceDisplay || prop.getDisplay();
-
- if (display === 'none') {
- return null;
- }
-
- // 标准的属性,即每一个 Field 在 VE 下都拥有的属性
- const standardProps = {
- className: this.props.className,
- compact: this.props.compact,
-
- isSupportMultiSetter: this.supportMultiSetter(),
- isSupportVariable: prop.isSupportVariable(),
- isUseVariable: prop.isUseVariable(),
- prop,
- setUseVariable: () => prop.setUseVariable(!prop.isUseVariable()),
- tip: prop.getTip(),
- title: prop.getTitle(),
- };
-
- // 部分 Field 所需要的额外 fieldProps
- const extraProps = {};
- const ctx = context;
- const plugin = ctx.getPlugin(VE_HOOKS.VE_SETTING_FIELD_PROVIDER);
- let Field;
- if (typeof plugin === 'function') {
- Field = plugin(display, FIELD_TYPE_MAP, prop);
- }
- if (!Field) {
- Field = FIELD_TYPE_MAP[display] || PlainField;
- }
- createField()
- this._prepareProps(display, extraProps);
-
- if (display === 'entry') {
- return ;
- }
-
- let setter;
- const props: any = {
- prop,
- selected,
- };
- const fieldProps = { ...standardProps, ...extraProps };
-
- if (prop.isUseVariable() && !this.variableSetter.isPopup) {
- props.placeholder = '请输入表达式: ${var}';
- props.key = `${prop.getId()}-variable`;
- setter = React.createElement(this.variableSetter, props);
- return {setter} ;
- }
-
- // for composited prop
- if (prop.getVisibleItems) {
- setter = prop
- .getVisibleItems()
- .map((item: any) => );
- return {setter} ;
- }
-
- setter = createSetterContent(prop.getSetter(), {
- ...addonProps,
- ...props,
- });
-
- return {setter} ;
- }
-
- private supportMultiSetter() {
- const { prop } = this.props;
- const setter = prop && prop.getConfig && prop.getConfig('setter');
- return prop.isSupportVariable() || Array.isArray(setter);
- }
-
- private _prepareProps(displayType: string, extraProps: IExtraProps): void {
- const { prop } = this.props;
- extraProps.propName = prop.isGroup() ? '组合属性,无属性名称' : prop.getName();
- switch (displayType) {
- case 'title':
- break;
- case 'block':
- assign(extraProps, { isGroup: prop.isGroup() });
- break;
- case 'accordion':
- assign(extraProps, {
- headDIY: true,
- isExpand: prop.isExpand(),
- isGroup: prop.isGroup(),
- onExpandChange: () => prop.onExpandChange(() => this.forceUpdate()),
- toggleExpand: () => {
- prop.toggleExpand();
- },
- });
- break;
- case 'entry':
- assign(extraProps, { stageName: prop.getName() });
- break;
- default:
- break;
- }
- }
-}
-
-const Field = {
- SettingField: Placeholder,
- Stage: Placeholder,
- PopupField: Placeholder,
- EntryField: Placeholder,
- AccordionField: Placeholder,
- BlockField: Placeholder,
- InlineField: Placeholder,
-};
-
-export default Field;
diff --git a/packages/vision-preset/src/fields/field.tsx b/packages/vision-preset/src/fields/field.tsx
new file mode 100644
index 000000000..3fc13ea7e
--- /dev/null
+++ b/packages/vision-preset/src/fields/field.tsx
@@ -0,0 +1,150 @@
+import classnames from 'classnames';
+import * as React from 'react';
+import { Component } from 'react';
+import InlineTip from './inlinetip';
+import { isPlainObject } from '@ali/lowcode-utils';
+
+interface IHelpTip {
+ url?: string;
+ content?: string;
+}
+
+function splitWord(title: string): JSX.Element[] {
+ return (title || '').split('').map((w, i) => {w} );
+}
+
+function getFieldTitle(title: string, tip: IHelpTip, compact?: boolean, propName?: string): JSX.Element {
+ const className = classnames('engine-field-title', { 've-compact': compact });
+ let titleContent = null;
+
+ if (!compact && typeof title === 'string') {
+ titleContent = splitWord(title);
+ }
+
+ let tipUrl = null;
+ let tipContent = null;
+
+ tipContent = (
+
+ );
+
+ if (isPlainObject(tip)) {
+ tipUrl = tip.url;
+ tipContent = (
+
+
属性:{propName}
+
说明:{tip.content}
+
+ );
+ } else if (tip) {
+ tipContent = (
+
+
属性:{propName}
+
说明:{tip}
+
+ );
+ }
+ return (
+
+ {titleContent || (typeof title === 'object' ? '' : title)}
+ {tipContent}
+
+ );
+}
+
+export interface IVEFieldProps {
+ prop: any;
+ children: JSX.Element | string;
+ title?: string;
+ tip?: any;
+ propName?: string;
+ className?: string;
+ compact?: boolean;
+ stageName?: string;
+ /**
+ * render the top-header by jsx
+ */
+ headDIY?: boolean;
+
+ isSupportVariable?: boolean;
+ isSupportMultiSetter?: boolean;
+ isUseVariable?: boolean;
+
+ isGroup?: boolean;
+ isExpand?: boolean;
+
+ toggleExpand?: () => any;
+ onExpandChange?: (fn: () => any) => any;
+}
+
+interface IVEFieldState {
+ hasError?: boolean;
+}
+
+export default class VEField extends Component {
+ public static displayName = 'VEField';
+
+ public readonly props: IVEFieldProps;
+ public classNames: string[] = [];
+
+ public state: IVEFieldState = {
+ hasError: false,
+ };
+
+ public componentDidCatch(error: Error, info: React.ErrorInfo) {
+ console.error(error);
+ console.warn(info.componentStack);
+ }
+
+ public renderHead(): JSX.Element | JSX.Element[] | null {
+ const { title, tip, compact, propName } = this.props;
+ return getFieldTitle(title!, tip, compact, propName);
+ }
+
+ public renderBody(): JSX.Element | string {
+ return this.props.children;
+ }
+
+ public renderFoot(): any {
+ return null;
+ }
+
+ public render(): JSX.Element {
+ const { stageName, headDIY } = this.props;
+ const classNameList = classnames(...this.classNames, this.props.className);
+ const fieldProps: any = {};
+
+ if (stageName) {
+ // 为 stage 切换奠定基础
+ fieldProps['data-stage-target'] = this.props.stageName;
+ }
+
+ if (this.state.hasError) {
+ return (
+ Field render error, please open console to find out.
+ );
+ }
+
+ const headContent = headDIY ? this.renderHead()
+ : {this.renderHead()}
;
+
+ return (
+
+ {headContent}
+
+ {this.renderBody()}
+
+
+ {this.renderFoot()}
+
+
+ );
+ }
+}
diff --git a/packages/vision-preset/src/fields/fields.less b/packages/vision-preset/src/fields/fields.less
new file mode 100644
index 000000000..c02417425
--- /dev/null
+++ b/packages/vision-preset/src/fields/fields.less
@@ -0,0 +1,272 @@
+@import '~@ali/ve-less-variables/index.less';
+
+.engine-setting-field {
+ white-space: nowrap;
+ position: relative;
+
+ &:after, &:before {
+ content: " ";
+ display: table;
+ }
+ &:after {
+ clear: both;
+ }
+
+ .engine-field-title {
+ font-size: 12px;
+ font-family: @font-family;
+ line-height: 1em;
+ user-select: none;
+ color: var(--color-text, @dark-alpha-3);
+ width: fit-content;
+ white-space: initial;
+ word-break: break-word;
+ &::first-letter {
+ text-transform: capitalize;
+ }
+
+ .engine-word {
+ flex: 1;
+ text-align: center;
+ font-weight: normal;
+ &:first-child {
+ text-align: left;
+ }
+ &:last-of-type {
+ text-align: right;
+ }
+ &:only-of-type {
+ text-align: center;
+ }
+ overflow: hidden;
+ }
+ }
+
+ a.engine-field-title {
+ border-bottom: 1px dashed var(--color-line-normal, @normal-alpha-7);
+ text-decoration: none;
+ padding-bottom: 2px;
+ &:hover {
+ cursor: help;
+ }
+ }
+
+ .engine-field-variable-wrapper {
+ margin-left: 5px;
+ }
+
+ .engine-field-variable {
+ cursor: pointer;
+ opacity: 0.6;
+ &.engine-active {
+ opacity: 1;
+ color: var(--color-brand, @brand-color-1);
+ }
+ }
+
+ .engine-field-head {
+ padding-left: 10px;
+ height: 32px;
+ background: var(--color-block-background-shallow, @normal-alpha-8);
+ display: flex;
+ align-items: center;
+ font-weight: 500;
+ border-top: 1px solid var(--color-line-normal, @normal-alpha-7);
+ border-bottom: 1px solid var(--color-line-normal, @normal-alpha-7);
+ color: var(--color-title, @dark-alpha-2);
+ >.engine-icontip {
+ margin-left: 2px;
+ }
+ }
+
+ .engine-field-body {
+ min-height: 20px;
+ margin: 6px 0;
+
+ &:after, &:before {
+ content: " ";
+ display: table;
+ }
+ &:after {
+ clear: both;
+ }
+
+ .engine-field-head {
+ height: 28px;
+ border: none;
+ font-weight: 400;
+ }
+ }
+
+ &.engine-plain-field {
+ >.engine-field-variable {
+ position: absolute;
+ right: 5px;
+ top: 8px;
+ }
+ &:hover {
+ >.engine-field-variable {
+ opacity: 1;
+ }
+ }
+ }
+
+ &.engine-entry-field {
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ height: 32px;
+ padding-left: 10px;
+ font-weight: 500;
+ border-top: 1px solid var(--color-line-normal, @normal-alpha-7);
+ border-bottom: 1px solid var(--color-line-normal, @normal-alpha-7);
+ background: var(--color-block-background-shallow, @normal-alpha-8);
+ margin-bottom: 6px;
+
+ >.engine-field-title {
+ letter-spacing: 1px;
+ }
+
+ >.engine-icontip {
+ margin-left: 2px;
+ }
+
+ >.engine-field-arrow {
+ position: absolute;
+ right: 5px;
+ top: 50%;
+ transform: translateY(-50%) rotate(-90deg);
+ opacity: 0.4;
+ }
+ &:hover {
+ >.engine-field-arrow {
+ opacity: 1;
+ }
+ }
+ }
+
+ &.engine-popup-field {
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ height: 32px;
+ padding-left: 10px;
+ background: var(--color-block-background-shallow, @normal-alpha-8);
+ margin-bottom: 1px;
+
+ >.engine-field-title {
+ letter-spacing: 1px;
+ }
+
+ >.engine-icontip {
+ margin-left: 2px;
+ }
+
+ >.engine-field-icon {
+ position: absolute;
+ right: 5px;
+ top: 50%;
+ transform: translateY(-50%);
+ opacity: 0.6;
+ }
+ &:hover {
+ >.engine-field-icon {
+ opacity: 1;
+ }
+ }
+ }
+
+ &.engine-block-field {
+ >.engine-field-head{
+ > .engine-field-title {
+ letter-spacing: 1px;
+ }
+ >.engine-field-variable {
+ margin-left: 2px;
+ }
+ }
+ >.engine-field-body {
+ margin: 6px;
+ }
+ }
+
+ &.engine-inline-field {
+ display: flex;
+ align-items: center;
+ margin: 10px;
+ >.engine-field-head {
+ display: inline-flex;
+ background: none;
+ padding: 0;
+ border: none;
+
+ >.engine-field-title {
+ display: inline-flex;
+ width: 50px;
+ margin-right: 5px;
+ }
+ }
+ >.engine-field-body {
+ width: 100%;
+ display: inline-flex;
+ align-items: flex-start;
+ padding: 0;
+ margin: 0;
+ flex: 1;
+ position: relative;
+ }
+ >.engine-field-variable {
+ margin-left: 2px;
+ }
+ &:hover {
+ >.engine-field-variable {
+ opacity: 1;
+ }
+ }
+ }
+
+ &.engine-accordion-field {
+ >.engine-field-head {
+ position: relative;
+ cursor: pointer;
+ >.engine-field-title {
+ letter-spacing: 1px;
+ }
+ >.engine-field-arrow {
+ transform: rotate(180deg);
+ position: absolute;
+ right: 7px;
+ top: 7px;
+ transition: transform 0.1s ease;
+ opacity: 0.6;
+ }
+ >.engine-field-variable {
+ margin-left: 2px;
+ }
+ }
+ &.engine-collapsed {
+ >.engine-field-head {
+ margin-bottom: 6px;
+ }
+ >.engine-field-head > .engine-field-arrow {
+ transform: rotate(0);
+ }
+ >.engine-field-body {
+ display: none;
+ }
+ }
+ >.engine-field-body {
+ margin: 6px;
+ }
+ }
+}
+
+.engine-block-field,.engine-accordion-field,.engine-entry-field {
+ .engine-input-control {
+ margin: 10px;
+ }
+}
+
+.engine-field-tip-icon {
+ margin-left: 2px;
+}
diff --git a/packages/vision-preset/src/fields/fields.tsx b/packages/vision-preset/src/fields/fields.tsx
new file mode 100644
index 000000000..e106fe7f7
--- /dev/null
+++ b/packages/vision-preset/src/fields/fields.tsx
@@ -0,0 +1,376 @@
+import Icons from '@ali/ve-icons';
+import classNames from 'classnames';
+import { Component } from 'react';
+import { testType } from '@ali/ve-utils';
+import VEField, { IVEFieldProps } from './field';
+import { SettingField } from './settingField';
+import VariableSwitcher from './variableSwitcher';
+import popups from '@ali/ve-popups';
+
+import './fields.less';
+
+interface IHelpTip {
+ url?: string;
+ content?: string | JSX.Element;
+}
+
+function renderTip(tip: IHelpTip, prop?: { propName?: string }) {
+ const propName = prop && prop.propName;
+ if (!tip) {
+ return (
+
+
+
+ );
+ }
+ if (testType(tip) === 'object') {
+ return (
+
+
+
属性:{propName}
+
说明:{tip.content}
+
+
+ );
+ }
+ return (
+
+
+
属性:{propName}
+
说明:{tip}
+
+
+ );
+}
+
+export class PlainField extends VEField {
+ public static defaultProps = {
+ headDIY: true,
+ };
+
+ public static displayName: string = 'PlainField';
+
+ public renderHead(): null {
+ return null;
+ }
+}
+
+export class InlineField extends VEField {
+ public static displayName = 'InlineField';
+ constructor(props: any) {
+ super(props);
+ this.classNames = ['engine-setting-field', 'engine-inline-field'];
+ }
+
+ public renderFoot() {
+ return (
+
+
+
+ );
+ }
+}
+
+export class BlockField extends VEField {
+ public static displayName = 'BlockField';
+
+ constructor(props: IVEFieldProps) {
+ super(props);
+ this.classNames = ['engine-setting-field', 'engine-block-field', props.isGroup ? 'engine-group-field' : ''];
+ }
+
+ public renderHead() {
+ const { title, tip, propName } = this.props;
+ return [
+
+ {title}
+ ,
+ renderTip(tip, { propName }),
+ ,
+ ];
+ }
+}
+
+export class AccordionField extends VEField {
+ public readonly props: IVEFieldProps;
+
+ private willDetach?: () => any;
+
+ constructor(props: IVEFieldProps) {
+ super(props);
+ this._generateClassNames(props);
+ if (this.props.onExpandChange) {
+ this.willDetach = this.props.onExpandChange(() => this.forceUpdate());
+ }
+ }
+
+ public componentWillReceiveProps(nextProps: IVEFieldProps) {
+ this.classNames = this._generateClassNames(nextProps);
+ }
+
+ public componentWillUnmount() {
+ if (this.willDetach) {
+ this.willDetach();
+ }
+ }
+
+ public renderHead() {
+ const { title, tip, toggleExpand, propName } = this.props;
+ return (
+ toggleExpand && toggleExpand()}>
+
+ {title}
+ {renderTip(tip, { propName })}
+ { }
+
+ );
+ }
+
+ private _generateClassNames(props: IVEFieldProps) {
+ this.classNames = [
+ 'engine-setting-field',
+ 'engine-accordion-field',
+ props.isGroup ? 'engine-group-field' : '',
+ !props.isExpand ? 'engine-collapsed' : '',
+ ];
+ return this.classNames;
+ }
+}
+
+export class EntryField extends VEField {
+ constructor(props: any) {
+ super(props);
+ this.classNames = ['engine-setting-field', 'engine-entry-field'];
+ }
+
+ public render() {
+ const { propName, stageName, tip, title } = this.props;
+ const classNameList = classNames(...this.classNames, this.props.className);
+ const fieldProps: any = {};
+
+ if (stageName) {
+ // 为 stage 切换奠定基础
+ fieldProps['data-stage-target'] = this.props.stageName;
+ }
+
+ const innerElements = [
+
+ {title}
+ ,
+ renderTip(tip, { propName }),
+ ,
+ ];
+
+ return (
+
+ {innerElements}
+
+ );
+ }
+}
+
+export class PopupField extends VEField {
+ constructor(props: any) {
+ super(props);
+ this.classNames = ['engine-setting-field', 'engine-popup-field'];
+ }
+
+ public renderBody() {
+ return '';
+ }
+
+ public render() {
+ const { propName, stageName, tip, title } = this.props;
+ const classNameList = classNames(...this.classNames, this.props.className);
+ const fieldProps: any = {};
+
+ if (stageName) {
+ // 为 stage 切换奠定基础
+ fieldProps['data-stage-target'] = this.props.stageName;
+ }
+
+ return (
+
+ popups.popup({
+ cancelOnBlur: true,
+ content: this.props.children,
+ position: 'left bottom',
+ showClose: true,
+ sizeFixed: true,
+ target: e.currentTarget,
+ })
+ }
+ >
+ {title}
+ {renderTip(tip, { propName })}
+
+
+
+ );
+ }
+}
+
+export class CaptionField extends VEField {
+ constructor(props: IVEFieldProps) {
+ super(props);
+ this.classNames = ['engine-setting-field', 'engine-caption-field'];
+ }
+
+ public renderHead() {
+ const { title, tip, propName } = this.props;
+ return (
+
+ {title}
+ {renderTip(tip, { propName })}
+
+ );
+ }
+}
+
+export class Stage extends Component {
+ public readonly props: {
+ key: any;
+ stage: any;
+ current?: boolean;
+ direction?: any;
+ };
+
+ public stage: any;
+ public additionClassName: string;
+ public shell: Element | null = null;
+ private willDetach: () => any;
+
+ public componentWillMount() {
+ this.stage = this.props.stage;
+ if (this.stage.onCurrentTabChange) {
+ this.willDetach = this.stage.onCurrentTabChange(() => this.forceUpdate());
+ }
+ }
+
+ public componentDidMount() {
+ this.doSkate();
+ }
+
+ public componentWillReceiveProps(props: any) {
+ if (props.stage !== this.stage) {
+ this.stage = props.stage;
+ if (this.willDetach) {
+ this.willDetach();
+ }
+ if (this.stage.onCurrentTabChange) {
+ this.willDetach = this.stage.onCurrentTabChange(() => this.forceUpdate());
+ }
+ }
+ }
+
+ public componentDidUpdate() {
+ this.doSkate();
+ }
+
+ public componentWillUnmount() {
+ if (this.willDetach) {
+ this.willDetach();
+ }
+ }
+
+ public doSkate() {
+ if (this.additionClassName) {
+ setTimeout(() => {
+ const elem = this.shell;
+ if (elem && elem.classList) {
+ if (this.props.current) {
+ elem.classList.remove(this.additionClassName);
+ } else {
+ elem.classList.add(this.additionClassName);
+ }
+ this.additionClassName = '';
+ }
+ }, 10);
+ }
+ }
+
+ public render() {
+ const stage = this.stage;
+ let content = null;
+ let tabs = null;
+
+ let className = 'engine-settings-stage';
+
+ if (stage.getTabs) {
+ const selected = stage.getNode();
+ // stat for cache
+ stage.stat();
+ const currentTab = stage.getCurrentTab();
+
+ if (stage.hasTabs()) {
+ className += ' engine-has-tabs';
+ tabs = (
+
+ {stage.getTabs().map((tab: any) => (
+
stage.setCurrentTab(tab)}
+ >
+ {tab.getTitle()}
+ {renderTip(tab.getTip())}
+
+ ))}
+
+ );
+ }
+
+ if (currentTab) {
+ if (currentTab.getVisibleItems) {
+ content = currentTab
+ .getVisibleItems()
+ .map((item: any) => );
+ } else if (currentTab.getSetter) {
+ content = (
+
+ );
+ }
+ }
+ } else {
+ content = stage.getContent();
+ }
+
+ if (this.props.current) {
+ if (this.props.direction) {
+ this.additionClassName = `engine-stagein-${this.props.direction}`;
+ className += ` ${this.additionClassName}`;
+ }
+ } else if (this.props.direction) {
+ this.additionClassName = `engine-stageout-${this.props.direction}`;
+ }
+
+ let stageBacker = null;
+ if (stage.hasBack()) {
+ className += ' engine-has-backer';
+ stageBacker = (
+
+
+ {stage.getTitle()}
+ {renderTip(stage.getTip())}
+
+ );
+ }
+
+ return (
+ {
+ this.shell = ref;
+ }}
+ className={className}
+ >
+ {stageBacker}
+ {tabs}
+
{content}
+
+ );
+ }
+}
diff --git a/packages/vision-preset/src/fields/index.ts b/packages/vision-preset/src/fields/index.ts
new file mode 100644
index 000000000..682cba49d
--- /dev/null
+++ b/packages/vision-preset/src/fields/index.ts
@@ -0,0 +1,2 @@
+export * from './settingField';
+export * from './fields';
diff --git a/packages/vision-preset/src/fields/inlinetip.tsx b/packages/vision-preset/src/fields/inlinetip.tsx
new file mode 100644
index 000000000..f3344b1c3
--- /dev/null
+++ b/packages/vision-preset/src/fields/inlinetip.tsx
@@ -0,0 +1,30 @@
+import { Component } from 'react';
+
+export interface InlineTipProps {
+ position: string;
+ theme?: 'green' | 'black';
+ children: React.ReactNode;
+}
+
+export default class InlineTip extends Component {
+ public static displayName = 'InlineTip';
+
+ public static defaultProps = {
+ position: 'auto',
+ theme: 'black',
+ };
+
+ public render(): React.ReactNode {
+ const { position, theme, children } = this.props;
+ return (
+
+ {children}
+
+ );
+ }
+}
diff --git a/packages/vision-preset/src/fields/settingField.tsx b/packages/vision-preset/src/fields/settingField.tsx
new file mode 100644
index 000000000..4edd0c52d
--- /dev/null
+++ b/packages/vision-preset/src/fields/settingField.tsx
@@ -0,0 +1,186 @@
+import VariableSetter from './variableSetter';
+import context from '../context';
+import { VE_HOOKS } from '../base/const';
+import {
+ AccordionField,
+ BlockField,
+ EntryField,
+ InlineField,
+ PlainField,
+ PopupField
+} from "./fields";
+
+import { ComponentClass, Component, isValidElement, createElement } from 'react';
+import { createSetterContent, getSetter } from '@ali/lowcode-editor-core';
+
+function isReactClass(obj: any): obj is ComponentClass {
+ return (
+ obj &&
+ obj.prototype &&
+ (obj.prototype.isReactComponent || obj.prototype instanceof Component)
+ );
+}
+
+interface IExtraProps {
+ stageName?: string;
+ isGroup?: boolean;
+ isExpand?: boolean;
+ propName?: string;
+ toggleExpand?: () => any;
+ onExpandChange?: () => any;
+}
+
+const FIELD_TYPE_MAP: any = {
+ accordion: AccordionField,
+ block: BlockField,
+ entry: EntryField,
+ inline: InlineField,
+ plain: PlainField,
+ popup: PopupField,
+ tab: AccordionField
+};
+
+export class SettingField extends Component {
+ public readonly props: {
+ prop: any;
+ selected?: boolean;
+ forceDisplay?: string;
+ className?: string;
+ children?: JSX.Element | string;
+ compact?: boolean;
+ key?: string;
+ addonProps?: object;
+ };
+
+ /**
+ * VariableSetter placeholder
+ */
+ public variableSetter: any;
+
+ constructor(props: any) {
+ super(props);
+
+ this.variableSetter = getSetter('VariableSetter')?.component || VariableSetter;
+ }
+
+ public render() {
+ const { prop, selected, addonProps } = this.props;
+ const display = this.props.forceDisplay || prop.getDisplay();
+
+ if (display === "none") {
+ return null;
+ }
+
+ // 标准的属性,即每一个 Field 在 VE 下都拥有的属性
+ const standardProps = {
+ className: this.props.className,
+ compact: this.props.compact,
+
+ isSupportMultiSetter: this.supportMultiSetter(),
+ isSupportVariable: prop.isSupportVariable(),
+ isUseVariable: prop.isUseVariable(),
+ prop,
+ setUseVariable: () => prop.setUseVariable(!prop.isUseVariable()),
+ tip: prop.getTip(),
+ title: prop.getTitle()
+ };
+
+ // 部分 Field 所需要的额外 fieldProps
+ const extraProps = {};
+ const ctx = context;
+ const plugin = ctx.getPlugin(VE_HOOKS.VE_SETTING_FIELD_PROVIDER);
+ let Field;
+ if (typeof plugin === "function") {
+ Field = plugin(display, FIELD_TYPE_MAP, prop);
+ }
+ if (!Field) {
+ Field = FIELD_TYPE_MAP[display] || PlainField;
+ }
+ this._prepareProps(display, extraProps);
+
+ if (display === "entry") {
+ return ;
+ }
+
+ let setter;
+ const props: any = {
+ prop,
+ selected,
+ };
+ const fieldProps = { ...standardProps, ...extraProps };
+
+ if (prop.isUseVariable() && !this.variableSetter.isPopup) {
+ props.placeholder = "请输入表达式: ${var}";
+ props.key = `${prop.getId()}-variable`;
+ setter = createElement(this.variableSetter, props);
+ return {setter} ;
+ }
+
+ // for composited prop
+ if (prop.getVisibleItems) {
+ setter = prop
+ .getVisibleItems()
+ .map((item: any) => (
+
+ ));
+ return {setter} ;
+ }
+
+ setter = prop.getSetter();
+ if (
+ typeof setter === "object" &&
+ "componentName" in setter &&
+ !(isValidElement(setter) || isReactClass(setter))
+ ) {
+ const { componentName: setterType, props: setterProps } = setter as any;
+ setter = createSetterContent(setterType, {
+ ...addonProps,
+ ...setterProps,
+ ...props
+ });
+ } else {
+ setter = createSetterContent(setter, {
+ ...addonProps,
+ ...props
+ });
+ }
+
+ return {setter} ;
+ }
+
+ private supportMultiSetter() {
+ const { prop } = this.props;
+ const setter = prop && prop.getConfig && prop.getConfig("setter");
+ return prop.isSupportVariable() || Array.isArray(setter);
+ }
+
+ private _prepareProps(displayType: string, extraProps: IExtraProps): void {
+ const { prop } = this.props;
+ extraProps.propName = prop.isGroup()
+ ? "组合属性,无属性名称"
+ : prop.getName();
+ switch (displayType) {
+ case "title":
+ break;
+ case "block":
+ Object.assign(extraProps, { isGroup: prop.isGroup() });
+ break;
+ case "accordion":
+ Object.assign(extraProps, {
+ headDIY: true,
+ isExpand: prop.isExpand(),
+ isGroup: prop.isGroup(),
+ onExpandChange: () => prop.onExpandChange(() => this.forceUpdate()),
+ toggleExpand: () => {
+ prop.toggleExpand();
+ }
+ });
+ break;
+ case "entry":
+ Object.assign(extraProps, { stageName: prop.getName() });
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/packages/vision-preset/src/fields/variableSetter.less b/packages/vision-preset/src/fields/variableSetter.less
new file mode 100644
index 000000000..6ea5653d3
--- /dev/null
+++ b/packages/vision-preset/src/fields/variableSetter.less
@@ -0,0 +1,43 @@
+@import '~@ali/ve-less-variables/index.less';
+
+.engine-input-control {
+ box-sizing: border-box;
+ font-size: 12px;
+ font-family: Consolas, "Courier New", Courier, FreeMono, monospace;
+ color: var(--color-text, @dark-alpha-3);
+ background: var(--color-field-background, @white-alpha-1);
+ border: 1px solid var(--color-field-border, @normal-alpha-5);
+ flex: 1;
+ border-radius: @global-border-radius;
+ max-height: 200px;
+
+ &:hover {
+ border-color: var(--color-field-border-hover, @normal-alpha-4);
+ }
+
+ &.engine-focused {
+ border-color: var(--color-field-border-active, @normal-alpha-3);
+ }
+
+ textarea {
+ resize: none;
+ }
+
+ >.engine-input {
+ box-sizing: border-box;
+ padding: 6px;
+ display: block;
+ font-size: 12px;
+ line-height: 16px;
+ color: var(--color-text, @dark-alpha-3);
+ width: 100%;
+ border: 0;
+ margin: 0;
+ background: transparent;
+ outline: none;
+
+ &::-webkit-input-placeholder {
+ color: var(--color-field-placeholder, @normal-alpha-5);
+ }
+ }
+}
diff --git a/packages/vision-preset/src/fields/variableSetter.tsx b/packages/vision-preset/src/fields/variableSetter.tsx
new file mode 100644
index 000000000..51643bd16
--- /dev/null
+++ b/packages/vision-preset/src/fields/variableSetter.tsx
@@ -0,0 +1,85 @@
+import './variableSetter.less';
+import { Component } from 'react';
+
+class Input extends Component {
+ public props: {
+ value: string;
+ placeholder: string;
+ onChange: (val: any) => any;
+ };
+
+ public state: { focused: boolean };
+
+ constructor(props: object) {
+ super(props);
+ this.state = {
+ focused: false,
+ };
+ }
+
+ public componentDidMount() {
+ this.adjustTextAreaHeight();
+ }
+
+ private domRef: HTMLTextAreaElement | null = null;
+ public adjustTextAreaHeight() {
+ if (!this.domRef) {
+ return;
+ }
+ this.domRef.style.height = '1px';
+ const calculatedHeight = this.domRef.scrollHeight;
+ this.domRef.style.height = calculatedHeight >= 200 ? '200px' : calculatedHeight + 'px';
+ }
+
+ public render() {
+ const { value, placeholder, onChange } = this.props;
+ return (
+
+
+
+ );
+ }
+}
+
+export default class VariableSetter extends Component<{
+ prop: any;
+ placeholder: string;
+}> {
+ public willDetach: () => any;
+
+ public componentWillMount() {
+ this.willDetach = this.props.prop.onValueChange(() => this.forceUpdate());
+ }
+
+ public componentWillUnmount() {
+ if (this.willDetach) {
+ this.willDetach();
+ }
+ }
+
+ public render() {
+ const prop = this.props.prop;
+ return (
+ prop.setVariableValue(val)}
+ />
+ );
+ }
+}
diff --git a/packages/vision-preset/src/fields/variableSwitcher.less b/packages/vision-preset/src/fields/variableSwitcher.less
new file mode 100644
index 000000000..0991b0ed6
--- /dev/null
+++ b/packages/vision-preset/src/fields/variableSwitcher.less
@@ -0,0 +1,20 @@
+@import '~@ali/ve-less-variables/index.less';
+
+.engine-field-variable-switcher {
+ cursor: pointer;
+ opacity: 0.6;
+ margin-left: 2px;
+
+ &.engine-active {
+ opacity: 1;
+ background: var(--color-brand, @brand-color-1);
+ color: #fff !important;
+ border-radius: 3px;
+ margin-left: 4px;
+
+ svg {
+ height: 22px !important;
+ width: 22px !important;
+ }
+ }
+}
diff --git a/packages/vision-preset/src/fields/variableSwitcher.tsx b/packages/vision-preset/src/fields/variableSwitcher.tsx
new file mode 100644
index 000000000..146ad7502
--- /dev/null
+++ b/packages/vision-preset/src/fields/variableSwitcher.tsx
@@ -0,0 +1,57 @@
+import VariableSetter from './variableSetter';
+import Icons from '@ali/ve-icons';
+import { IVEFieldProps } from './field';
+import './variableSwitcher.less';
+import { Component } from 'react';
+import { getSetter } from '@ali/lowcode-editor-core';
+
+interface IState {
+ visible: boolean;
+}
+
+export default class VariableSwitcher extends Component {
+ private ref: HTMLElement | null = null;
+ private VariableSetter: any;
+
+ constructor(props: IVEFieldProps) {
+ super(props);
+
+ this.VariableSetter = getSetter('VariableSetter')?.component || VariableSetter;
+
+ this.state = {
+ visible: false,
+ };
+ }
+
+ public render() {
+ const { isUseVariable, prop } = this.props;
+ const { visible } = this.state;
+ const isSupportVariable = prop.isSupportVariable();
+ const tip = !isUseVariable ? '绑定变量' : prop.getVariableValue();
+ if (!isSupportVariable) {
+ return null;
+ }
+ return (
+
+ {
+ e.stopPropagation();
+ if (this.VariableSetter.isPopup) {
+ this.VariableSetter.show({
+ prop,
+ });
+ } else {
+ prop.setUseVariable(!isUseVariable);
+ }
+ }}>
+ 绑定变量
+
+
+ );
+ }
+}
diff --git a/packages/vision-preset/src/index.ts b/packages/vision-preset/src/index.ts
index 31185f2d2..3015995cb 100644
--- a/packages/vision-preset/src/index.ts
+++ b/packages/vision-preset/src/index.ts
@@ -17,7 +17,7 @@ import Trunk from './bundle/trunk';
import Prototype from './bundle/prototype';
import Bundle from './bundle/bundle';
import Pages from './pages';
-import Field from './field';
+import * as Field from './fields';
import Prop from './prop';
import Env from './env';
import DragEngine from './drag-engine';