Merge branch 'preset-vision/0.9.0' into fiebai

This commit is contained in:
飞百 2020-05-13 15:21:19 +08:00
commit 813f362758
1863 changed files with 543887 additions and 2605 deletions

3
.gitignore vendored
View File

@ -100,3 +100,6 @@ typings/
# mac config files # mac config files
.DS_Store .DS_Store
# codealike
codealike.json

View File

@ -3,6 +3,50 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="0.8.18"></a>
## [0.8.18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.17...@ali/lowcode-demo@0.8.18) (2020-05-13)
**Note:** Version bump only for package @ali/lowcode-demo
<a name="0.8.17"></a>
## [0.8.17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.16...@ali/lowcode-demo@0.8.17) (2020-05-13)
**Note:** Version bump only for package @ali/lowcode-demo
<a name="0.8.16"></a>
## [0.8.16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.15...@ali/lowcode-demo@0.8.16) (2020-05-08)
**Note:** Version bump only for package @ali/lowcode-demo
<a name="0.8.15"></a>
## [0.8.15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.14...@ali/lowcode-demo@0.8.15) (2020-05-08)
**Note:** Version bump only for package @ali/lowcode-demo
<a name="0.8.14"></a>
## [0.8.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.13...@ali/lowcode-demo@0.8.14) (2020-05-07)
### Bug Fixes
* 🐛 add history pane for vision demo ([3ce7079](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3ce7079))
* 🐛 清理无用代码 ([015b58a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/015b58a))
<a name="0.8.13"></a> <a name="0.8.13"></a>
## [0.8.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.12...@ali/lowcode-demo@0.8.13) (2020-04-27) ## [0.8.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.12...@ali/lowcode-demo@0.8.13) (2020-04-27)

View File

@ -1,6 +1,5 @@
{ {
"entry": { "entry": {
"index": "src/vision/index.ts",
"vision-preset": "../vision-preset/src/index.ts", "vision-preset": "../vision-preset/src/index.ts",
"react-simulator-renderer": "../react-simulator-renderer/src/index.ts" "react-simulator-renderer": "../react-simulator-renderer/src/index.ts"
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "@ali/lowcode-demo", "name": "@ali/lowcode-demo",
"version": "0.8.13", "version": "0.8.18",
"private": true, "private": true,
"description": "低代码引擎 DEMO", "description": "低代码引擎 DEMO",
"scripts": { "scripts": {
@ -11,38 +11,38 @@
}, },
"config": {}, "config": {},
"dependencies": { "dependencies": {
"@ali/lowcode-editor-core": "^0.8.9", "@ali/lowcode-editor-core": "^0.8.12",
"@ali/lowcode-editor-skeleton": "^0.8.10", "@ali/lowcode-editor-skeleton": "^0.8.15",
"@ali/lowcode-plugin-components-pane": "^0.8.8", "@ali/lowcode-plugin-components-pane": "^0.8.11",
"@ali/lowcode-plugin-designer": "^0.9.6", "@ali/lowcode-plugin-designer": "^0.9.9",
"@ali/lowcode-plugin-event-bind-dialog": "^0.8.9", "@ali/lowcode-plugin-event-bind-dialog": "^0.8.12",
"@ali/lowcode-plugin-outline-pane": "^0.8.12", "@ali/lowcode-plugin-outline-pane": "^0.8.15",
"@ali/lowcode-plugin-sample-logo": "^0.8.8", "@ali/lowcode-plugin-sample-logo": "^0.8.11",
"@ali/lowcode-plugin-sample-preview": "^0.8.11", "@ali/lowcode-plugin-sample-preview": "^0.8.14",
"@ali/lowcode-plugin-settings-pane": "^0.8.8", "@ali/lowcode-plugin-settings-pane": "^0.8.8",
"@ali/lowcode-plugin-undo-redo": "^0.8.9", "@ali/lowcode-plugin-undo-redo": "^0.8.14",
"@ali/lowcode-plugin-variable-bind-dialog": "^0.8.7", "@ali/lowcode-plugin-variable-bind-dialog": "^0.8.10",
"@ali/lowcode-plugin-zh-en": "^0.8.11", "@ali/lowcode-plugin-zh-en": "^0.8.14",
"@ali/lowcode-react-renderer": "^0.8.5", "@ali/lowcode-react-renderer": "^0.8.7",
"@ali/lowcode-runtime": "^0.8.13", "@ali/lowcode-runtime": "^0.8.13",
"@ali/lowcode-utils": "^0.8.2", "@ali/lowcode-utils": "^0.8.4",
"@ali/ve-action-pane": "^4.7.0-beta.0", "@ali/ve-action-pane": "^4.7.0-beta.0",
"@ali/ve-datapool-pane": "^6.4.3", "@ali/ve-datapool-pane": "^6.4.3",
"@ali/ve-history-pane": "4.0.0",
"@ali/ve-i18n-manage-pane": "^4.3.0", "@ali/ve-i18n-manage-pane": "^4.3.0",
"@ali/ve-i18n-pane": "^4.0.0-beta.0", "@ali/ve-i18n-pane": "^4.0.0-beta.0",
"@ali/ve-page-history": "1.2.0",
"@ali/ve-page-history-pane": "^5.0.0-beta.0",
"@ali/ve-trunk-pane": "^5.1.0-beta.14", "@ali/ve-trunk-pane": "^5.1.0-beta.14",
"@ali/vs-variable-setter": "^3.1.0", "@ali/vs-variable-setter": "^3.1.0",
"@ali/vu-function-parser": "^2.5.0-beta.0",
"@ali/vu-legao-design-fetch-context": "^1.0.3", "@ali/vu-legao-design-fetch-context": "^1.0.3",
"@ali/ve-page-history": "1.2.0",
"@ali/ve-history-pane": "4.0.0",
"@ali/ve-page-history-pane": "^5.0.0-beta.0",
"@alifd/next": "^1.19.12", "@alifd/next": "^1.19.12",
"@alife/theme-lowcode-dark": "^0.1.0", "@alife/theme-lowcode-dark": "^0.1.0",
"@alife/theme-lowcode-light": "^0.1.0", "@alife/theme-lowcode-light": "^0.1.0",
"compare-versions": "^3.0.1",
"react": "^16.8.1", "react": "^16.8.1",
"react-dom": "^16.8.1", "react-dom": "^16.8.1"
"@ali/vu-function-parser": "^2.5.0-beta.0",
"compare-versions": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {
"@ali/iceluna-cli": "^0.0.16", "@ali/iceluna-cli": "^0.0.16",

View File

@ -3,6 +3,64 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="0.9.9"></a>
## [0.9.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-designer@0.9.8...@ali/lowcode-designer@0.9.9) (2020-05-13)
### Bug Fixes
* 🐛 add hotkey up/down/left/right ([9c8afe8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9c8afe8))
* 🐛 error when quick search ([801d954](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/801d954))
* 🐛 快捷键支持 ([73374dd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/73374dd))
* 🐛 移动快捷键 ([7c8a27c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7c8a27c))
* cancel dragging on invalid position ([f961096](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f961096))
* panel visible time ([18ac1fa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/18ac1fa))
* quickSearch error ([a8009ef](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a8009ef))
* supports ([371b84c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/371b84c))
### Features
* show value state ([bd49e50](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd49e50))
* support global inline editing ([4f7179b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4f7179b))
* support plaintext liveediting ([ea62f12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ea62f12))
<a name="0.9.8"></a>
## [0.9.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-designer@0.9.7...@ali/lowcode-designer@0.9.8) (2020-05-08)
**Note:** Version bump only for package @ali/lowcode-designer
<a name="0.9.7"></a>
## [0.9.7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-designer@0.9.6...@ali/lowcode-designer@0.9.7) (2020-05-07)
### Bug Fixes
* 🐛 add pollyfill for vision page.getHistory ([0b905d0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b905d0))
* 🐛 title缺少icon字段临时转接一下 ([2f9bb25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2f9bb25))
* 🐛 增加 getAddonData api ([68b7e29](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/68b7e29))
* 🐛 增加剪切快捷键 ([a73a82e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a73a82e))
* border action style ([6b91535](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6b91535))
* documentModel toData 方法 ([1ea0d73](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/1ea0d73))
* settingfield添加props修复地区组件切换类型报错 ([88348f7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/88348f7))
* 在Transducer中添加对MixedSetter的支持 ([7317f2f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7317f2f))
### Features
* 🎸 增加icon获取api ([f1a0823](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f1a0823))
* duplicate ([ec932aa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ec932aa))
* 修复状态切换失效 ([2e3f60d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e3f60d))
<a name="0.9.6"></a> <a name="0.9.6"></a>
## [0.9.6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-designer@0.9.5...@ali/lowcode-designer@0.9.6) (2020-04-27) ## [0.9.6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-designer@0.9.5...@ali/lowcode-designer@0.9.6) (2020-04-27)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ali/lowcode-designer", "name": "@ali/lowcode-designer",
"version": "0.9.6", "version": "0.9.9",
"description": "Designer for Ali LowCode Engine", "description": "Designer for Ali LowCode Engine",
"main": "lib/index.js", "main": "lib/index.js",
"module": "es/index.js", "module": "es/index.js",
@ -15,9 +15,9 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ali/lowcode-editor-core": "^0.8.9", "@ali/lowcode-editor-core": "^0.8.12",
"@ali/lowcode-types": "^0.8.1", "@ali/lowcode-types": "^0.8.3",
"@ali/lowcode-utils": "^0.8.2", "@ali/lowcode-utils": "^0.8.4",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"event": "^1.0.0", "event": "^1.0.0",
"react": "^16", "react": "^16",

View File

@ -1,11 +1,10 @@
import { Component, Fragment, PureComponent } from 'react'; import { Component, Fragment, PureComponent } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { computed, observer, Title } from '@ali/lowcode-editor-core'; import { computed, observer, Title } from '@ali/lowcode-editor-core';
import { SimulatorContext } from '../context';
import { BuiltinSimulatorHost } from '../host'; import { BuiltinSimulatorHost } from '../host';
import { TitleContent } from '@ali/lowcode-types'; import { TitleContent } from '@ali/lowcode-types';
export class BorderHoveringInstance extends PureComponent<{ export class BorderDetectingInstance extends PureComponent<{
title: TitleContent; title: TitleContent;
rect: DOMRect | null; rect: DOMRect | null;
scale: number; scale: number;
@ -24,7 +23,7 @@ export class BorderHoveringInstance extends PureComponent<{
transform: `translate(${(scrollX + rect.left) * scale}px, ${(scrollY + rect.top) * scale}px)`, transform: `translate(${(scrollX + rect.left) * scale}px, ${(scrollY + rect.top) * scale}px)`,
}; };
const className = classNames('lc-borders lc-borders-hovering'); const className = classNames('lc-borders lc-borders-detecting');
// TODO: // TODO:
// 1. thinkof icon // 1. thinkof icon
@ -39,7 +38,7 @@ export class BorderHoveringInstance extends PureComponent<{
} }
@observer @observer
export class BorderHovering extends Component<{ host: BuiltinSimulatorHost }> { export class BorderDetecting extends Component<{ host: BuiltinSimulatorHost }> {
shouldComponentUpdate() { shouldComponentUpdate() {
return false; return false;
} }
@ -60,7 +59,7 @@ export class BorderHovering extends Component<{ host: BuiltinSimulatorHost }> {
const host = this.props.host; const host = this.props.host;
const doc = host.document; const doc = host.document;
const selection = doc.selection; const selection = doc.selection;
const current = host.designer.hovering.current; const current = host.designer.detecting.current;
if (!current || current.document !== doc || selection.has(current.id)) { if (!current || current.document !== doc || selection.has(current.id)) {
return null; return null;
} }
@ -70,36 +69,36 @@ export class BorderHovering extends Component<{ host: BuiltinSimulatorHost }> {
render() { render() {
const host = this.props.host; const host = this.props.host;
const current = this.current; const current = this.current;
if (!current || host.viewport.scrolling) { if (!current || host.viewport.scrolling || host.liveEditing.editing) {
return <Fragment />; return null;
} }
const instances = host.getComponentInstances(current); const instances = host.getComponentInstances(current);
if (!instances || instances.length < 1) { if (!instances || instances.length < 1) {
return <Fragment />; return null;
} }
if (instances.length === 1) { if (instances.length === 1) {
return ( return (
<BorderHoveringInstance <BorderDetectingInstance
key="line-h" key="line-h"
title={current.title} title={current.title}
scale={this.scale} scale={this.scale}
scrollX={this.scrollX} scrollX={this.scrollX}
scrollY={this.scrollY} scrollY={this.scrollY}
rect={host.computeComponentInstanceRect(instances[0], current.componentMeta.rectSelector)} rect={host.computeComponentInstanceRect(instances[0], current.componentMeta.rootSelector)}
/> />
); );
} }
return ( return (
<Fragment> <Fragment>
{instances.map((inst, i) => ( {instances.map((inst, i) => (
<BorderHoveringInstance <BorderDetectingInstance
key={`line-h-${i}`} key={`line-h-${i}`}
title={current.title} title={current.title}
scale={this.scale} scale={this.scale}
scrollX={this.scrollX} scrollX={this.scrollX}
scrollY={this.scrollY} scrollY={this.scrollY}
rect={host.computeComponentInstanceRect(inst, current.componentMeta.rectSelector)} rect={host.computeComponentInstanceRect(inst, current.componentMeta.rootSelector)}
/> />
))} ))}
</Fragment> </Fragment>

View File

@ -12,7 +12,6 @@ import classNames from 'classnames';
import { observer, computed, Tip } from '@ali/lowcode-editor-core'; import { observer, computed, Tip } from '@ali/lowcode-editor-core';
import { createIcon, isReactComponent } from '@ali/lowcode-utils'; import { createIcon, isReactComponent } from '@ali/lowcode-utils';
import { ActionContentObject, isActionContentObject } from '@ali/lowcode-types'; import { ActionContentObject, isActionContentObject } from '@ali/lowcode-types';
import { SimulatorContext } from '../context';
import { BuiltinSimulatorHost } from '../host'; import { BuiltinSimulatorHost } from '../host';
import { OffsetObserver } from '../../designer'; import { OffsetObserver } from '../../designer';
import { Node } from '../../document'; import { Node } from '../../document';
@ -188,7 +187,7 @@ export class BorderSelecting extends Component<{ host: BuiltinSimulatorHost }> {
@computed get selecting() { @computed get selecting() {
const doc = this.host.document; const doc = this.host.document;
if (doc.suspensed) { if (doc.suspensed || this.host.liveEditing.editing) {
return null; return null;
} }
const selection = doc.selection; const selection = doc.selection;
@ -202,8 +201,7 @@ export class BorderSelecting extends Component<{ host: BuiltinSimulatorHost }> {
render() { render() {
const selecting = this.selecting; const selecting = this.selecting;
if (!selecting || selecting.length < 1) { if (!selecting || selecting.length < 1) {
// DIRTY FIX, recore has a bug! return null;
return <Fragment />;
} }
return ( return (

View File

@ -70,7 +70,8 @@
} }
} }
&&-hovering { // &&-hovering {
&&-detecting {
z-index: 1; z-index: 1;
border-style: dashed; border-style: dashed;
background: rgba(0,121,242,.04); background: rgba(0,121,242,.04);

View File

@ -1,6 +1,6 @@
import { Component } from 'react'; import { Component } from 'react';
import { observer } from '@ali/lowcode-editor-core'; import { observer } from '@ali/lowcode-editor-core';
import { BorderHovering } from './border-hovering'; import { BorderDetecting } from './border-detecting';
import { BuiltinSimulatorHost } from '../host'; import { BuiltinSimulatorHost } from '../host';
import { BorderSelecting } from './border-selecting'; import { BorderSelecting } from './border-selecting';
import BorderResizing from './border-resizing'; import BorderResizing from './border-resizing';
@ -19,10 +19,9 @@ export class BemTools extends Component<{ host: BuiltinSimulatorHost }> {
const { scrollX, scrollY, scale } = host.viewport; const { scrollX, scrollY, scale } = host.viewport;
return ( return (
<div className="lc-bem-tools" style={{ transform: `translate(${-scrollX * scale}px,${-scrollY * scale}px)` }}> <div className="lc-bem-tools" style={{ transform: `translate(${-scrollX * scale}px,${-scrollY * scale}px)` }}>
<BorderHovering key="hovering" /> <BorderDetecting key="hovering" host={host} />
<BorderSelecting key="selecting" /> <BorderSelecting key="selecting" host={host} />
<InsertionView key="insertion" /> <InsertionView key="insertion" host={host} />
<BorderResizing key="resizing" />
</div> </div>
); );
} }

View File

@ -106,7 +106,7 @@ function processDetail({ target, detail, document }: DropLocation): InsertionDat
if (!instances) { if (!instances) {
return {}; return {};
} }
const edge = sim.computeComponentInstanceRect(instances[0], target.componentMeta.rectSelector); const edge = sim.computeComponentInstanceRect(instances[0], target.componentMeta.rootSelector);
return edge ? { edge, insertType: 'cover', coverRect: edge } : {}; return edge ? { edge, insertType: 'cover', coverRect: edge } : {};
} }
} }

View File

@ -1,4 +1,4 @@
import { obx, autorun, computed, getPublicPath, hotkey } from '@ali/lowcode-editor-core'; import { obx, autorun, computed, getPublicPath, hotkey, focusTracker } from '@ali/lowcode-editor-core';
import { ISimulatorHost, Component, NodeInstance, ComponentInstance } from '../simulator'; import { ISimulatorHost, Component, NodeInstance, ComponentInstance } from '../simulator';
import Viewport from './viewport'; import Viewport from './viewport';
import { createSimulator } from './create-simulator'; import { createSimulator } from './create-simulator';
@ -25,6 +25,7 @@ import { parseMetadata } from './utils/parse-metadata';
import { ComponentMetadata } from '@ali/lowcode-types'; import { ComponentMetadata } from '@ali/lowcode-types';
import { BuiltinSimulatorRenderer } from './renderer'; import { BuiltinSimulatorRenderer } from './renderer';
import clipboard from '../designer/clipboard'; import clipboard from '../designer/clipboard';
import { LiveEditing } from './live-editing/live-editing';
export interface LibraryItem { export interface LibraryItem {
package: string; package: string;
@ -214,6 +215,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
// bind hotkey & clipboard // bind hotkey & clipboard
hotkey.mount(this._contentWindow); hotkey.mount(this._contentWindow);
focusTracker.mount(this._contentWindow);
clipboard.injectCopyPaster(this._contentDocument); clipboard.injectCopyPaster(this._contentDocument);
// TODO: dispose the bindings // TODO: dispose the bindings
} }
@ -223,7 +225,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
// just listen special callback // just listen special callback
// because iframe maybe reload // because iframe maybe reload
this.setupDragAndClick(); this.setupDragAndClick();
this.setupHovering(); this.setupDetecting();
this.setupLiveEditing();
} }
setupDragAndClick() { setupDragAndClick() {
@ -237,6 +240,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
doc.addEventListener( doc.addEventListener(
'mousedown', 'mousedown',
(downEvent: MouseEvent) => { (downEvent: MouseEvent) => {
if (this.liveEditing.editing) {
return;
}
// stop response document focus event // stop response document focus event
downEvent.stopPropagation(); downEvent.stopPropagation();
downEvent.preventDefault(); downEvent.preventDefault();
@ -249,7 +255,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
doc.removeEventListener('mouseup', checkSelect, true); doc.removeEventListener('mouseup', checkSelect, true);
if (!isShaken(downEvent, e)) { if (!isShaken(downEvent, e)) {
const id = node.id; const id = node.id;
designer.activeTracker.track(node); designer.activeTracker.track({ node, instance: nodeInst?.instance });
if (isMulti && !isRootNode(node) && selection.has(id)) { if (isMulti && !isRootNode(node) && selection.has(id)) {
selection.remove(id); selection.remove(id);
} else { } else {
@ -264,7 +270,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
if (isMulti) { if (isMulti) {
// multi select mode, directily add // multi select mode, directily add
if (!selection.has(node.id)) { if (!selection.has(node.id)) {
designer.activeTracker.track(node); designer.activeTracker.track({ node, instance: nodeInst?.instance });
selection.add(node.id); selection.add(node.id);
ignoreUpSelected = true; ignoreUpSelected = true;
} }
@ -304,36 +310,24 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
}, },
true, true,
); );
// cause edit
doc.addEventListener(
'dblclick',
(e: MouseEvent) => {
// stop response document dblclick event
e.stopPropagation();
e.preventDefault();
// todo: quick editing
},
true,
);
} }
private disableHovering?: () => void; private disableHovering?: () => void;
/** /**
* *
*/ */
setupHovering() { setupDetecting() {
const doc = this.contentDocument!; const doc = this.contentDocument!;
const hovering = this.document.designer.hovering; const detecting = this.document.designer.detecting;
const hover = (e: MouseEvent) => { const hover = (e: MouseEvent) => {
if (!hovering.enable) { if (!detecting.enable) {
return; return;
} }
const nodeInst = this.getNodeInstanceFromElement(e.target as Element); const nodeInst = this.getNodeInstanceFromElement(e.target as Element);
hovering.hover(nodeInst?.node || null); detecting.capture(nodeInst?.node || null);
e.stopPropagation(); e.stopPropagation();
}; };
const leave = () => hovering.leave(this.document); const leave = () => detecting.leave(this.document);
doc.addEventListener('mouseover', hover, true); doc.addEventListener('mouseover', hover, true);
doc.addEventListener('mouseleave', leave, false); doc.addEventListener('mouseleave', leave, false);
@ -348,13 +342,47 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
); );
this.disableHovering = () => { this.disableHovering = () => {
hovering.leave(this.document); detecting.leave(this.document);
doc.removeEventListener('mouseover', hover, true); doc.removeEventListener('mouseover', hover, true);
doc.removeEventListener('mouseleave', leave, false); doc.removeEventListener('mouseleave', leave, false);
this.disableHovering = undefined; this.disableHovering = undefined;
}; };
} }
readonly liveEditing = new LiveEditing();
setupLiveEditing() {
const doc = this.contentDocument!;
// cause edit
doc.addEventListener(
'dblclick',
(e: MouseEvent) => {
// stop response document dblclick event
e.stopPropagation();
e.preventDefault();
const targetElement = e.target as HTMLElement;
const nodeInst = this.getNodeInstanceFromElement(targetElement);
if (!nodeInst) {
return;
}
const node = nodeInst.node || this.document.rootNode;
const rootElement = this.findDOMNodes(nodeInst.instance, node.componentMeta.rootSelector)?.find(item => item.contains(targetElement)) as HTMLElement;
if (!rootElement) {
return;
}
this.liveEditing.apply({
node,
rootElement,
event: e,
});
},
true,
);
}
/** /**
* @see ISimulator * @see ISimulator
*/ */
@ -367,7 +395,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
} else { } else {
// weekup some autorun reaction // weekup some autorun reaction
if (!this.disableHovering) { if (!this.disableHovering) {
this.setupHovering(); this.setupDetecting();
} }
} }
} }
@ -454,7 +482,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
if (!instances) { if (!instances) {
return null; return null;
} }
return this.computeComponentInstanceRect(instances[0], node.componentMeta.rectSelector); return this.computeComponentInstanceRect(instances[0], node.componentMeta.rootSelector);
} }
/** /**
@ -462,19 +490,12 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
*/ */
computeComponentInstanceRect(instance: ComponentInstance, selector?: string): Rect | null { computeComponentInstanceRect(instance: ComponentInstance, selector?: string): Rect | null {
const renderer = this.renderer!; const renderer = this.renderer!;
const elements = renderer.findDOMNodes(instance); const elements = this.findDOMNodes(instance, selector);
if (!elements) { if (!elements) {
return null; return null;
} }
let elems = elements.slice(); const elems = elements.slice();
if (selector) {
const matched = getMatched(elems, selector);
if (!matched) {
return null;
}
elems = [matched];
}
let rects: DOMRect[] | undefined; let rects: DOMRect[] | undefined;
let last: { x: number; y: number; r: number; b: number } | undefined; let last: { x: number; y: number; r: number; b: number } | undefined;
let computed = false; let computed = false;
@ -533,8 +554,20 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
/** /**
* @see ISimulator * @see ISimulator
*/ */
findDOMNodes(instance: ComponentInstance): Array<Element | Text> | null { findDOMNodes(instance: ComponentInstance, selector?: string): Array<Element | Text> | null {
return this._renderer?.findDOMNodes(instance) || null; const elements = this._renderer?.findDOMNodes(instance);
if (!elements) {
return null;
}
if (selector) {
const matched = getMatched(elements, selector);
if (!matched) {
return null;
}
return [matched];
}
return elements;
} }
/** /**
@ -716,7 +749,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
const { container, instance: containerInstance } = dropContainer; const { container, instance: containerInstance } = dropContainer;
const edge = this.computeComponentInstanceRect(containerInstance, container.componentMeta.rectSelector); const edge = this.computeComponentInstanceRect(containerInstance, container.componentMeta.rootSelector);
if (!edge) { if (!edge) {
return null; return null;
@ -757,7 +790,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
? instances.find((inst) => this.getClosestNodeInstance(inst, container.id)?.instance === containerInstance) ? instances.find((inst) => this.getClosestNodeInstance(inst, container.id)?.instance === containerInstance)
: instances[0] : instances[0]
: null; : null;
const rect = inst ? this.computeComponentInstanceRect(inst, node.componentMeta.rectSelector) : null; const rect = inst ? this.computeComponentInstanceRect(inst, node.componentMeta.rootSelector) : null;
if (!rect) { if (!rect) {
continue; continue;

View File

@ -1,3 +1,4 @@
export * from './host'; export * from './host';
export * from './host-view'; export * from './host-view';
export * from './renderer'; export * from './renderer';
export * from './live-editing/live-editing';

View File

@ -0,0 +1,197 @@
import { obx } from '@ali/lowcode-editor-core';
import { LiveTextEditingConfig } from '@ali/lowcode-types';
import { Node, Prop } from '../../document';
const EDITOR_KEY = 'data-setter-prop';
function getSetterPropElement(ele: HTMLElement, root: HTMLElement): HTMLElement | null {
const box = ele.closest(`[${EDITOR_KEY}]`);
if (!box || !root.contains(box)) {
return null;
}
return box as HTMLElement;
}
function defaultSaveContent(content: string, prop: Prop) {
prop.setValue(content);
}
export interface EditingTarget {
node: Node;
rootElement: HTMLElement;
event: MouseEvent;
}
const saveHandlers: SaveHandler[] = [];
function addLiveEditingSaveHandler(handler: SaveHandler) {
saveHandlers.push(handler);
}
const specificRules: SpecificRule[] = [];
function addLiveEditingSpecificRule(rule: SpecificRule) {
specificRules.push(rule);
}
export class LiveEditing {
static addLiveEditingSpecificRule = addLiveEditingSpecificRule;
static addLiveEditingSaveHandler = addLiveEditingSaveHandler;
@obx.ref private _editing: Prop | null = null;
apply(target: EditingTarget) {
const { node, event, rootElement } = target;
const targetElement = event.target as HTMLElement;
const liveTextEditing = node.componentMeta.liveTextEditing;
let setterPropElement = getSetterPropElement(targetElement, rootElement);
let propTarget = setterPropElement?.dataset.setterProp;
let matched: (LiveTextEditingConfig & { propElement?: HTMLElement; }) | undefined | null;
if (liveTextEditing) {
if (propTarget) {
// 已埋点命中 data-setter-prop="proptarget", 从 liveTextEditing 读取配置mode|onSaveContent
matched = liveTextEditing.find(config => config.propTarget == propTarget);
} else {
// 执行 embedTextEditing selector 规则,获得第一个节点 是否 contains e.target若匹配读取配置
matched = liveTextEditing.find(config => {
if (!config.selector) {
return false;
}
setterPropElement = queryPropElement(rootElement, targetElement, config.selector);
return setterPropElement ? true : false;
});
propTarget = matched?.propTarget;
}
} else {
specificRules.some((rule) => {
matched = rule(target);
return matched ? true : false;
});
if (matched) {
propTarget = matched.propTarget;
setterPropElement = matched.propElement || queryPropElement(rootElement, targetElement, matched.selector);
}
}
if (!propTarget) {
// 自动纯文本编辑满足一下情况:
// 1. children 内容都是 Leaf 且都是文本(一期)
// 2. DOM 节点是单层容器,子集都是文本节点 (已满足)
const isAllText = node.children?.every(item => {
return item.isLeaf() && item.getProp('children')?.type === 'literal';
});
// TODO:
}
if (propTarget && setterPropElement) {
const prop = node.getProp(propTarget, true)!;
if (this._editing === prop) {
return;
}
// 进入编辑
// 1. 设置contentEditable="plaintext|..."
// 2. 添加类名
// 3. focus & cursor locate
// 4. 监听 blur 事件
// 5. 设置编辑锁定disable hover | disable select | disable canvas drag
const onSaveContent = matched?.onSaveContent || saveHandlers.find(item => item.condition(prop))?.onSaveContent || defaultSaveContent;
setterPropElement.setAttribute('contenteditable', matched?.mode && matched.mode !== 'plaintext' ? 'true' : 'plaintext-only');
setterPropElement.classList.add('engine-live-editing');
// be sure
setterPropElement.focus();
setCaret(event);
this._save = () => {
onSaveContent(setterPropElement!.innerText, prop);
};
this._dispose = () => {
setterPropElement!.removeAttribute('contenteditable');
setterPropElement!.classList.remove('engine-live-editing');
};
setterPropElement.addEventListener('focusout', (e) => {
this.saveAndDispose();
});
this._editing = prop;
}
// TODO: process enter | esc events & joint the FocusTracker
// TODO: upward testing for b/i/a html elements
// 非文本编辑
// 国际化数据,改变当前
// JSExpression, 改变 mock 或 弹出绑定变量
}
get editing() {
return this._editing;
}
private _dispose?: () => void;
private _save?: () => void;
saveAndDispose() {
if (this._save) {
this._save();
this._save = undefined;
}
this.dispose();
}
dispose() {
if (this._dispose) {
this._dispose();
this._dispose = undefined;
}
this._editing = null;
}
}
export type SpecificRule = (target: EditingTarget) => (LiveTextEditingConfig & {
propElement?: HTMLElement;
}) | null;
export interface SaveHandler {
condition: (prop: Prop) => boolean;
onSaveContent: (content: string, prop: Prop) => void;
}
function setCaret(event: MouseEvent) {
const doc = event.view?.document!;
const range = doc.caretRangeFromPoint(event.clientX, event.clientY);
if (range) {
selectRange(doc, range);
setTimeout(() => selectRange(doc, range), 1);
}
}
function selectRange(doc: Document, range: Range) {
const selection = doc.getSelection();
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}
}
function queryPropElement(rootElement: HTMLElement, targetElement: HTMLElement, selector?: string) {
if (!selector) {
return null;
}
let propElement = selector === ':root' ? rootElement : rootElement.querySelector(selector);
if (!propElement) {
return null;
}
if (!propElement.contains(targetElement)) {
// try selectorAll
propElement = Array.from(rootElement.querySelectorAll(selector)).find(item => item.contains(targetElement)) as HTMLElement;
if (!propElement) {
return null;
}
}
return propElement as HTMLElement;
}

View File

@ -9,6 +9,8 @@ import {
NestingFilter, NestingFilter,
isTitleConfig, isTitleConfig,
I18nData, I18nData,
LiveTextEditingConfig,
FieldConfig,
} from '@ali/lowcode-types'; } from '@ali/lowcode-types';
import { computed } from '@ali/lowcode-editor-core'; import { computed } from '@ali/lowcode-editor-core';
import { Node, ParentalNode } from './document'; import { Node, ParentalNode } from './document';
@ -81,9 +83,9 @@ export class ComponentMeta {
get descriptor(): string | undefined { get descriptor(): string | undefined {
return this._descriptor; return this._descriptor;
} }
private _rectSelector?: string; private _rootSelector?: string;
get rectSelector(): string | undefined { get rootSelector(): string | undefined {
return this._rectSelector; return this._rootSelector;
} }
private _transformedMetadata?: TransformedComponentMetadata; private _transformedMetadata?: TransformedComponentMetadata;
get configure() { get configure() {
@ -91,6 +93,11 @@ export class ComponentMeta {
return config?.combined || config?.props || []; return config?.combined || config?.props || [];
} }
private _liveTextEditing?: LiveTextEditingConfig[];
get liveTextEditing() {
return this._liveTextEditing;
}
private parentWhitelist?: NestingFilter | null; private parentWhitelist?: NestingFilter | null;
private childWhitelist?: NestingFilter | null; private childWhitelist?: NestingFilter | null;
@ -150,6 +157,26 @@ export class ComponentMeta {
: title; : title;
} }
const liveTextEditing = this._transformedMetadata.experimental?.liveTextEditing || [];
function collectLiveTextEditing(items: FieldConfig[]) {
items.forEach(config => {
if (config.items) {
collectLiveTextEditing(config.items);
} else {
const liveConfig = config.liveTextEditing || config.extraProps?.liveTextEditing;
if (liveConfig) {
liveTextEditing.push({
propTarget: String(config.name),
...liveConfig,
});
}
}
});
}
collectLiveTextEditing(this.configure);
this._liveTextEditing = liveTextEditing.length > 0 ? liveTextEditing : undefined;
const { configure = {} } = this._transformedMetadata; const { configure = {} } = this._transformedMetadata;
this._acceptable = false; this._acceptable = false;
@ -158,7 +185,7 @@ export class ComponentMeta {
this._isContainer = component.isContainer ? true : false; this._isContainer = component.isContainer ? true : false;
this._isModal = component.isModal ? true : false; this._isModal = component.isModal ? true : false;
this._descriptor = component.descriptor; this._descriptor = component.descriptor;
this._rectSelector = component.rectSelector; this._rootSelector = component.rootSelector;
if (component.nestingRule) { if (component.nestingRule) {
const { parentWhitelist, childWhitelist } = component.nestingRule; const { parentWhitelist, childWhitelist } = component.nestingRule;
this.parentWhitelist = buildFilter(parentWhitelist); this.parentWhitelist = buildFilter(parentWhitelist);

View File

@ -1,17 +1,38 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { LocationDetail } from './location'; import { LocationDetail } from './location';
import { Node, isNode } from '../document/node/node'; import { Node, isNode } from '../document/node/node';
import { ComponentInstance } from '../simulator';
import { obx } from '@ali/lowcode-editor-core';
export interface ActiveTarget { export interface ActiveTarget {
node: Node; node: Node;
detail?: LocationDetail; detail?: LocationDetail;
instance?: ComponentInstance;
} }
export class ActiveTracker { export class ActiveTracker {
private emitter = new EventEmitter(); private emitter = new EventEmitter();
@obx.ref private _target?: ActiveTarget;
track(target: ActiveTarget | Node) { track(target: ActiveTarget | Node) {
this.emitter.emit('change', isNode(target) ? { node: target } : target); if (isNode(target)) {
target = { node: target };
}
this._target = target;
this.emitter.emit('change', target);
}
get currentNode() {
return this._target?.node;
}
get detail() {
return this._target?.detail;
}
get intance() {
return this._target?.instance;
} }
onChange(fn: (target: ActiveTarget) => void): () => void { onChange(fn: (target: ActiveTarget) => void): () => void {

View File

@ -36,7 +36,6 @@ function getNextForSelect(next: any, head?: any, parent?: any): any {
function getPrevForSelect(prev: any, head?: any, parent?: any): any { function getPrevForSelect(prev: any, head?: any, parent?: any): any {
if (prev) { if (prev) {
debugger;
let ret; let ret;
if (!head && prev.isContainer()) { if (!head && prev.isContainer()) {
const children = prev.getChildren() || []; const children = prev.getChildren() || [];
@ -67,6 +66,7 @@ function getPrevForSelect(prev: any, head?: any, parent?: any): any {
// hotkey binding // hotkey binding
hotkey.bind(['backspace', 'del'], (e: KeyboardEvent) => { hotkey.bind(['backspace', 'del'], (e: KeyboardEvent) => {
// TODO: use focus-tracker
const doc = focusing.focusDesigner?.currentDocument; const doc = focusing.focusDesigner?.currentDocument;
if (isFormEvent(e) || !doc) { if (isFormEvent(e) || !doc) {
return; return;
@ -102,14 +102,6 @@ hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
} }
e.preventDefault(); e.preventDefault();
/*
const doc = getCurrentDocument();
if (isFormEvent(e) || !doc || !(focusing.id === 'outline' || focusing.id === 'canvas')) {
return;
}
e.preventDefault();
*/
const selected = doc.selection.getTopNodes(true); const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) return; if (!selected || selected.length < 1) return;
@ -120,7 +112,7 @@ hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
clipboard.setData(data); clipboard.setData(data);
const cutMode = action.indexOf('x') > 0; const cutMode = action && action.indexOf('x') > 0;
if (cutMode) { if (cutMode) {
selected.forEach((node) => { selected.forEach((node) => {
const parentNode = node.getParent(); const parentNode = node.getParent();
@ -213,7 +205,7 @@ hotkey.bind(['up', 'down'], (e, action) => {
} }
}); });
hotkey.bind(['option+up', 'option+down', 'option+left', 'option+right'], (e, action) => { hotkey.bind(['option+left', 'option+right'], (e, action) => {
const designer = focusing.focusDesigner; const designer = focusing.focusDesigner;
const doc = designer?.currentDocument; const doc = designer?.currentDocument;
if (isFormEvent(e) || !doc) { if (isFormEvent(e) || !doc) {
@ -225,24 +217,17 @@ hotkey.bind(['option+up', 'option+down', 'option+left', 'option+right'], (e, act
return; return;
} }
// TODO: 此处需要增加判断当前节点是否可被操作移动原ve里是用 node.canOperating()来判断 // TODO: 此处需要增加判断当前节点是否可被操作移动原ve里是用 node.canOperating()来判断
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
const firstNode = selected[0]; const firstNode = selected[0];
const parent = firstNode.getParent(); const parent = firstNode.getParent();
if (!parent) return; if (!parent) return;
const isPrev = /(up|left)$/.test(action); const isPrev = action && /(left)$/.test(action);
const isTravel = /(up|down)$/.test(action);
const silbing = isPrev ? firstNode.prevSibling : firstNode.nextSibling; const silbing = isPrev ? firstNode.prevSibling : firstNode.nextSibling;
if (silbing) { if (silbing) {
if (isTravel && silbing.isContainer()) {
const place = silbing.getSuitablePlace(firstNode, null);
if (isPrev) { if (isPrev) {
place.container.insertAfter(firstNode, place.ref);
} else {
place.container.insertBefore(firstNode, place.ref);
}
} else if (isPrev) {
parent.insertBefore(firstNode, silbing); parent.insertBefore(firstNode, silbing);
} else { } else {
parent.insertAfter(firstNode, silbing); parent.insertAfter(firstNode, silbing);
@ -250,14 +235,82 @@ hotkey.bind(['option+up', 'option+down', 'option+left', 'option+right'], (e, act
firstNode?.select(); firstNode?.select();
return; return;
} }
if (isTravel) { });
hotkey.bind(['option+up'], (e, action) => {
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
// TODO: 此处需要增加判断当前节点是否可被操作移动原ve里是用 node.canOperating()来判断
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
const firstNode = selected[0];
const parent = firstNode.getParent();
if (!parent) {
return;
}
const silbing = firstNode.prevSibling;
if (silbing) {
if (silbing.isContainer()) {
const place = silbing.getSuitablePlace(firstNode, null);
place.container.insertAfter(firstNode, place.ref);
} else {
parent.insertBefore(firstNode, silbing);
}
firstNode?.select();
return;
} else {
const place = parent.getSuitablePlace(firstNode, null); // upwards const place = parent.getSuitablePlace(firstNode, null); // upwards
if (place) { if (place) {
if (isPrev) {
place.container.insertBefore(firstNode, place.ref); place.container.insertBefore(firstNode, place.ref);
} else { firstNode?.select();
place.container.insertAfter(firstNode, place.ref); }
} }
});
hotkey.bind(['option+down'], (e, action) => {
const designer = focusing.focusDesigner;
const doc = designer?.currentDocument;
if (isFormEvent(e) || !doc) {
return;
}
e.preventDefault();
const selected = doc.selection.getTopNodes(true);
if (!selected || selected.length < 1) {
return;
}
// TODO: 此处需要增加判断当前节点是否可被操作移动原ve里是用 node.canOperating()来判断
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
const firstNode = selected[0];
const parent = firstNode.getParent();
if (!parent) {
return;
}
const silbing = firstNode.nextSibling;
if (silbing) {
if (silbing.isContainer()) {
// const place = silbing.getSuitablePlace(firstNode, null);
silbing.insertBefore(firstNode, undefined);
// place.container.insertBefore(firstNode, place.ref);
} else {
parent.insertAfter(firstNode, silbing);
}
firstNode?.select();
return;
} else {
const place = parent.getSuitablePlace(firstNode, null); // upwards
if (place) {
place.container.insertAfter(firstNode, place.ref);
firstNode?.select(); firstNode?.select();
} }
} }

View File

@ -16,7 +16,7 @@ import { INodeSelector, Component } from '../simulator';
import { Scroller, IScrollable } from './scroller'; import { Scroller, IScrollable } from './scroller';
import { Dragon, isDragNodeObject, isDragNodeDataObject, LocateEvent, DragObject } from './dragon'; import { Dragon, isDragNodeObject, isDragNodeDataObject, LocateEvent, DragObject } from './dragon';
import { ActiveTracker } from './active-tracker'; import { ActiveTracker } from './active-tracker';
import { Hovering } from './hovering'; import { Detecting } from './detecting';
import { DropLocation, LocationData, isLocationChildrenDetail } from './location'; import { DropLocation, LocationData, isLocationChildrenDetail } from './location';
import { OffsetObserver, createOffsetObserver } from './offset-observer'; import { OffsetObserver, createOffsetObserver } from './offset-observer';
import { focusing } from './focusing'; import { focusing } from './focusing';
@ -44,7 +44,7 @@ export interface DesignerProps {
export class Designer { export class Designer {
readonly dragon = new Dragon(this); readonly dragon = new Dragon(this);
readonly activeTracker = new ActiveTracker(); readonly activeTracker = new ActiveTracker();
readonly hovering = new Hovering(); readonly detecting = new Detecting();
readonly project: Project; readonly project: Project;
readonly editor: IEditor; readonly editor: IEditor;
@ -68,7 +68,7 @@ export class Designer {
this.project = new Project(this, props.defaultSchema); this.project = new Project(this, props.defaultSchema);
this.dragon.onDragstart((e) => { this.dragon.onDragstart((e) => {
this.hovering.enable = false; this.detecting.enable = false;
const { dragObject } = e; const { dragObject } = e;
if (isDragNodeObject(dragObject)) { if (isDragNodeObject(dragObject)) {
if (dragObject.nodes.length === 1) { if (dragObject.nodes.length === 1) {
@ -99,7 +99,7 @@ export class Designer {
const { dragObject, copy } = e; const { dragObject, copy } = e;
const loc = this._dropLocation; const loc = this._dropLocation;
if (loc) { if (loc) {
if (isLocationChildrenDetail(loc.detail)) { if (isLocationChildrenDetail(loc.detail) && loc.detail.valid !== false) {
let nodes: Node[] | undefined; let nodes: Node[] | undefined;
if (isDragNodeObject(dragObject)) { if (isDragNodeObject(dragObject)) {
nodes = insertChildren(loc.target, dragObject.nodes, loc.detail.index, copy); nodes = insertChildren(loc.target, dragObject.nodes, loc.detail.index, copy);
@ -114,12 +114,11 @@ export class Designer {
} }
} }
} }
this.clearLocation();
if (this.props?.onDragend) { if (this.props?.onDragend) {
this.props.onDragend(e, loc); this.props.onDragend(e, loc);
} }
this.postEvent('dragend', e, loc); this.postEvent('dragend', e, loc);
this.hovering.enable = true; this.detecting.enable = true;
}); });
this.activeTracker.onChange(({ node, detail }) => { this.activeTracker.onChange(({ node, detail }) => {
@ -174,6 +173,10 @@ export class Designer {
} }
private _dropLocation?: DropLocation; private _dropLocation?: DropLocation;
get dropLocation() {
return this._dropLocation;
}
/** /**
* dragon * dragon
*/ */
@ -202,8 +205,30 @@ export class Designer {
return new Scroller(scrollable); return new Scroller(scrollable);
} }
private oobxList: OffsetObserver[] = [];
createOffsetObserver(nodeInstance: INodeSelector): OffsetObserver | null { createOffsetObserver(nodeInstance: INodeSelector): OffsetObserver | null {
return createOffsetObserver(nodeInstance); const oobx = createOffsetObserver(nodeInstance);
this.clearOobxList();
if (oobx) {
this.oobxList.push(oobx);
}
return oobx;
}
private clearOobxList(force?: boolean) {
let l = this.oobxList.length;
if (l > 20 || force) {
while (l-- > 0) {
if (this.oobxList[l].isPurged()) {
this.oobxList.splice(l, 1);
}
}
}
}
touchOffsetObserver() {
this.clearOobxList(true);
this.oobxList.forEach(item => item.compute());
} }
createSettingEntry(editor: IEditor, nodes: Node[]) { createSettingEntry(editor: IEditor, nodes: Node[]) {

View File

@ -1,7 +1,7 @@
import { obx } from '@ali/lowcode-editor-core'; import { obx } from '@ali/lowcode-editor-core';
import { Node, DocumentModel } from '../document'; import { Node, DocumentModel } from '../document';
export class Hovering { export class Detecting {
@obx.ref private _enable = true; @obx.ref private _enable = true;
get enable() { get enable() {
return this._enable; return this._enable;
@ -19,11 +19,11 @@ export class Hovering {
return this._current; return this._current;
} }
hover(node: Node | null) { capture(node: Node | null) {
this._current = node; this._current = node;
} }
unhover(node: Node) { release(node: Node) {
if (this._current === node) { if (this._current === node) {
this._current = null; this._current = null;
} }

View File

@ -8,7 +8,7 @@
align-items: center; align-items: center;
pointer-events: none; pointer-events: none;
background-color: rgba(0, 0, 0, 0.4); background-color: rgba(0, 0, 0, 0.4);
opacity: 0.5; //opacity: 0.9;
box-shadow: 0 0 6px grey; box-shadow: 0 0 6px grey;
transform: translate(-10%, -50%); transform: translate(-10%, -50%);
.lc-ghost { .lc-ghost {

View File

@ -365,6 +365,7 @@ export class Dragon {
exception = ex; exception = ex;
} }
} }
designer.clearLocation();
handleEvents((doc) => { handleEvents((doc) => {
if (isBoostFromDragAPI) { if (isBoostFromDragAPI) {

View File

@ -1,7 +1,6 @@
import { Designer } from './designer'; import { Designer } from './designer';
// TODO: // TODO: use focus-tracker replace
// 当前激活区域管理
class Focusing { class Focusing {
focusDesigner?: Designer; focusDesigner?: Designer;
} }

View File

@ -2,7 +2,7 @@ import './builtin-hotkey';
export * from './designer'; export * from './designer';
export * from './designer-view'; export * from './designer-view';
export * from './dragon'; export * from './dragon';
export * from './hovering'; export * from './detecting';
export * from './location'; export * from './location';
export * from './offset-observer'; export * from './offset-observer';
export * from './scroller'; export * from './scroller';

View File

@ -82,6 +82,8 @@ export class OffsetObserver {
private isRoot: boolean; private isRoot: boolean;
readonly node: Node; readonly node: Node;
readonly compute: () => void;
constructor(readonly nodeInstance: INodeSelector) { constructor(readonly nodeInstance: INodeSelector) {
const { node, instance } = nodeInstance; const { node, instance } = nodeInstance;
this.node = node; this.node = node;
@ -103,7 +105,7 @@ export class OffsetObserver {
return; return;
} }
const rect = host.computeComponentInstanceRect(instance!, node.componentMeta.rectSelector); const rect = host.computeComponentInstanceRect(instance!, node.componentMeta.rootSelector);
if (!rect) { if (!rect) {
this.hasOffset = false; this.hasOffset = false;
@ -121,6 +123,8 @@ export class OffsetObserver {
this.pid = pid = (window as any).requestIdleCallback(compute); this.pid = pid = (window as any).requestIdleCallback(compute);
}; };
this.compute = compute;
// try first // try first
compute(); compute();
// try second, ensure the dom mounted // try second, ensure the dom mounted
@ -133,6 +137,10 @@ export class OffsetObserver {
} }
this.pid = undefined; this.pid = undefined;
} }
isPurged() {
return this.pid == null;
}
} }
export function createOffsetObserver(nodeInstance: INodeSelector): OffsetObserver | null { export function createOffsetObserver(nodeInstance: INodeSelector): OffsetObserver | null {

View File

@ -3,6 +3,7 @@ import { Transducer } from './utils';
import { SettingPropEntry } from './setting-prop-entry'; import { SettingPropEntry } from './setting-prop-entry';
import { SettingEntry } from './setting-entry'; import { SettingEntry } from './setting-entry';
import { computed, obx } from '@ali/lowcode-editor-core'; import { computed, obx } from '@ali/lowcode-editor-core';
import { cloneDeep } from '@ali/lowcode-utils';
export class SettingField extends SettingPropEntry implements SettingEntry { export class SettingField extends SettingPropEntry implements SettingEntry {
readonly isSettingField = true; readonly isSettingField = true;
@ -83,46 +84,33 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
return new SettingField(this, config); return new SettingField(this, config);
} }
// ====== 当前属性读写 =====
/**
*
* 0 /
* 1
* 2
*/
get valueState(): number {
if (this.type !== 'field') {
return 0;
}
const propName = this.path.join('.');
const first = this.nodes[0].getProp(propName)!;
let l = this.nodes.length;
let state = 2;
while (l-- > 1) {
const next = this.nodes[l].getProp(propName, false);
const s = first.compare(next);
if (s > 1) {
return 0;
}
if (s === 1) {
state = 1;
}
}
return state;
}
purge() { purge() {
this.disposeItems(); this.disposeItems();
} }
// ======= compatibles for vision ====== // ======= compatibles for vision ======
getHotValue(): any { getHotValue(): any {
return this.transducer.toHot(this.getValue()); // avoid View modify
let v = cloneDeep(this.getMockOrValue());
if (v == null) {
v = this.extraProps.defaultValue;
}
return this.transducer.toHot(v);
} }
setHotValue(data: any) { setHotValue(data: any) {
this.setValue(this.transducer.toNative(data)); const v = this.transducer.toNative(data);
if (this.isUseVariable()) {
const ov = this.getValue();
this.setValue({
type: 'JSExpression',
value: ov.value,
mock: v,
});
} else {
this.setValue(v);
}
this.valueChange();
} }
onEffect(action: () => void): () => void { onEffect(action: () => void): () => void {

View File

@ -62,14 +62,6 @@ export class SettingPropEntry implements SettingEntry {
this.isSingle = parent.isSingle; this.isSingle = parent.isSingle;
this.designer = parent.designer; this.designer = parent.designer;
this.top = parent.top; this.top = parent.top;
autorun(({ firstRun }) => {
const value = this.getValue();
if (firstRun) {
return;
}
this.emitter.emit('valuechange', value);
});
} }
getId() { getId() {
@ -105,11 +97,43 @@ export class SettingPropEntry implements SettingEntry {
// ====== 当前属性读写 ===== // ====== 当前属性读写 =====
/**
*
* -1
* 0
* 1
* 2
*/
@computed get valueState(): number {
if (this.type !== 'field') {
const { getValue } = this.extraProps;
return getValue ? (getValue(this, undefined) === undefined ? 0 : 1) : 0;
}
const propName = this.path.join('.');
const first = this.nodes[0].getProp(propName)!;
let l = this.nodes.length;
let state = 2;
while (l-- > 1) {
const next = this.nodes[l].getProp(propName, false);
const s = first.compare(next);
if (s > 1) {
return -1;
}
if (s === 1) {
state = 1;
}
}
if (state === 2 && first.isUnset()) {
return 0;
}
return state;
}
/** /**
* *
*/ */
@computed getValue(): any { @computed getValue(): any {
let val: any = null; let val: any = undefined;
if (this.type === 'field') { if (this.type === 'field') {
val = this.parent.getPropValue(this.name); val = this.parent.getPropValue(this.name);
} }
@ -130,6 +154,19 @@ export class SettingPropEntry implements SettingEntry {
} }
} }
/**
*
*/
clearValue() {
if (this.type === 'field') {
this.parent.clearPropValue(this.name);
}
const { setValue } = this.extraProps;
if (setValue) {
setValue(this, undefined);
}
}
/** /**
* *
*/ */
@ -146,6 +183,14 @@ export class SettingPropEntry implements SettingEntry {
this.top.setPropValue(path, value); this.top.setPropValue(path, value);
} }
/**
*
*/
clearPropValue(propName: string | number) {
const path = this.path.concat(propName).join('.');
this.top.clearPropValue(path);
}
/** /**
* *
*/ */
@ -197,8 +242,6 @@ export class SettingPropEntry implements SettingEntry {
* @deprecated * @deprecated
*/ */
valueChange() { valueChange() {
console.warn('valueChange deprecated');
this.emitter.emit('valuechange'); this.emitter.emit('valuechange');
} }

View File

@ -136,6 +136,15 @@ export class SettingTopEntry implements SettingEntry {
}); });
} }
/**
*
*/
clearPropValue(propName: string) {
this.nodes.forEach((node) => {
node.clearPropValue(propName);
});
}
/** /**
* *
*/ */

View File

@ -46,7 +46,7 @@ export class Transducer {
setter = setter.componentName; setter = setter.componentName;
} }
if (typeof setter === 'string') { if (typeof setter === 'string') {
setter = getSetter(setter); setter = getSetter(setter).component;
} }
this.setterTransducer = combineTransducer( this.setterTransducer = combineTransducer(

View File

@ -41,7 +41,6 @@ export class DocumentModel {
private seqId = 0; private seqId = 0;
private _simulator?: ISimulatorHost; private _simulator?: ISimulatorHost;
/** /**
* *
*/ */
@ -119,7 +118,6 @@ export class DocumentModel {
// return this.addonsData[name]; // return this.addonsData[name];
// } // }
/** /**
* id * id
*/ */
@ -240,12 +238,12 @@ export class DocumentModel {
node.remove(); node.remove();
} }
getAddonData(name: string) { getAddonData(name: string) {
const addon = this.getNode(name) const addon = this.getNode(name);
if (addon) { if (addon) {
// 无法确定是否有这个api // 无法确定是否有这个api
// return addon.exportData(); // return addon.exportData();
} }
return addon return addon;
} }
@obx.ref private _dropLocation: DropLocation | null = null; @obx.ref private _dropLocation: DropLocation | null = null;
@ -485,7 +483,8 @@ export class DocumentModel {
// add toData // add toData
toData() { toData() {
return { componentsTree: [this.project?.currentDocument?.export(TransformStage.Save)] }; const node = this.project?.currentDocument?.export(TransformStage.Serilize);
return { componentsTree: [node] };
} }
getHistory(): History { getHistory(): History {

View File

@ -213,10 +213,18 @@ export class NodeChildren {
}); });
} }
every(fn: (item: Node, index: number) => any): boolean {
return this.children.every((child, index) => fn(child, index));
}
some(fn: (item: Node, index: number) => any): boolean { some(fn: (item: Node, index: number) => any): boolean {
return this.children.some((child, index) => fn(child, index)); return this.children.some((child, index) => fn(child, index));
} }
filter(fn: (item: Node, index: number) => item is Node) {
return this.children.filter(fn);
}
mergeChildren(remover: () => any, adder: (children: Node[]) => NodeData[] | null, sorter: () => any) { mergeChildren(remover: () => any, adder: (children: Node[]) => NodeData[] | null, sorter: () => any) {
let changed = false; let changed = false;
if (remover) { if (remover) {

View File

@ -284,9 +284,9 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
*/ */
hover(flag = true) { hover(flag = true) {
if (flag) { if (flag) {
this.document.designer.hovering.hover(this); this.document.designer.detecting.capture(this);
} else { } else {
this.document.designer.hovering.unhover(this); this.document.designer.detecting.release(this);
} }
} }
@ -395,6 +395,13 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
this.getProp(path, true)!.setValue(value); this.getProp(path, true)!.setValue(value);
} }
/**
*
*/
clearPropValue(path: string): void {
this.getProp(path, false)?.unset();
}
/** /**
* *
*/ */
@ -511,7 +518,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
const schema: any = { const schema: any = {
...baseSchema, ...baseSchema,
props: this.document.designer.transformProps(props, this, stage), props: this.document.designer.transformProps(props, this, stage),
..._extras_, ...this.document.designer.transformProps(_extras_, this, stage),
}; };
if (this.isParental() && this.children.size > 0) { if (this.isParental() && this.children.size > 0) {

View File

@ -245,7 +245,9 @@ export class Prop implements IPropParent {
return typeof this.key === 'string' && this.key.charAt(0) === '!'; return typeof this.key === 'string' && this.key.charAt(0) === '!';
} }
// TODO: improve this logic /**
* @returns 0: the same 1: maybe & like 2: not the same
*/
compare(other: Prop | null): number { compare(other: Prop | null): number {
if (!other || other.isUnset()) { if (!other || other.isUnset()) {
return this.isUnset() ? 0 : 2; return this.isUnset() ? 0 : 2;

View File

@ -134,7 +134,7 @@ export interface ISimulatorHost<P = object> extends ISensor {
computeComponentInstanceRect(instance: ComponentInstance, selector?: string): DOMRect | null; computeComponentInstanceRect(instance: ComponentInstance, selector?: string): DOMRect | null;
findDOMNodes(instance: ComponentInstance): Array<Element | Text> | null; findDOMNodes(instance: ComponentInstance, selector?: string): Array<Element | Text> | null;
/** /**
* *

View File

@ -3,6 +3,37 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="0.8.12"></a>
## [0.8.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.11...@ali/lowcode-editor-core@0.8.12) (2020-05-13)
### Bug Fixes
* supports ([371b84c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/371b84c))
* tip direction ([f51d496](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f51d496))
<a name="0.8.11"></a>
## [0.8.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.10...@ali/lowcode-editor-core@0.8.11) (2020-05-08)
**Note:** Version bump only for package @ali/lowcode-editor-core
<a name="0.8.10"></a>
## [0.8.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.9...@ali/lowcode-editor-core@0.8.10) (2020-05-07)
### Bug Fixes
* intl ([8a061ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8a061ab))
<a name="0.8.9"></a> <a name="0.8.9"></a>
## [0.8.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.8...@ali/lowcode-editor-core@0.8.9) (2020-04-27) ## [0.8.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.8...@ali/lowcode-editor-core@0.8.9) (2020-04-27)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ali/lowcode-editor-core", "name": "@ali/lowcode-editor-core",
"version": "0.8.9", "version": "0.8.12",
"description": "Core Api for Ali lowCode engine", "description": "Core Api for Ali lowCode engine",
"license": "MIT", "license": "MIT",
"main": "lib/index.js", "main": "lib/index.js",
@ -15,11 +15,11 @@
"cloud-build": "build-scripts build --skip-demo" "cloud-build": "build-scripts build --skip-demo"
}, },
"dependencies": { "dependencies": {
"@ali/lowcode-types": "^0.8.1", "@ali/lowcode-types": "^0.8.3",
"@ali/lowcode-utils": "^0.8.2", "@ali/lowcode-utils": "^0.8.4",
"@alifd/next": "^1.19.16", "@alifd/next": "^1.19.16",
"@recore/obx": "^1.0.8", "@recore/obx": "^1.0.9",
"@recore/obx-react": "^1.0.7", "@recore/obx-react": "^1.0.8",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"debug": "^4.1.1", "debug": "^4.1.1",
"intl-messageformat": "^8.3.1", "intl-messageformat": "^8.3.1",

View File

@ -35,7 +35,16 @@ class AliGlobalLocale {
if (this._locale != null) { if (this._locale != null) {
return this._locale; return this._locale;
} }
const { g_config, navigator } = window as any;
// TODO: store 1 & store 2 abstract out as custom implements
// store 1: config from window
let locale: string = getConfig('locale');
if (locale) {
return languageMap[locale] || locale.replace('_', '-');
}
// store 2: config from storage
if (hasLocalStorage(window)) { if (hasLocalStorage(window)) {
const store = window.localStorage; const store = window.localStorage;
let config: any; let config: any;
@ -47,20 +56,14 @@ class AliGlobalLocale {
if (config?.locale) { if (config?.locale) {
return (config.locale || '').replace('_', '-'); return (config.locale || '').replace('_', '-');
} }
} else if (g_config) {
if (g_config.locale) {
return languageMap[g_config.locale] || g_config.locale.replace('_', '-');
}
} }
let locale: string = ''; // store 2: config from system
const { navigator } = window as any;
if (navigator.language) { if (navigator.language) {
const lang = (navigator.language as string); const lang = (navigator.language as string);
return languageMap[lang] || lang.replace('_', '-'); return languageMap[lang] || lang.replace('_', '-');
} } else if (navigator.browserLanguage) {
// IE10 及更低版本使用 browserLanguage
if (navigator.browserLanguage) {
const it = navigator.browserLanguage.split('-'); const it = navigator.browserLanguage.split('-');
locale = it[0]; locale = it[0];
if (it[1]) { if (it[1]) {
@ -116,6 +119,15 @@ class AliGlobalLocale {
} }
} }
function getConfig(name: string) {
const win: any = window;
return (
win[name]
|| (win.g_config || {})[name]
|| (win.pageConfig || {})[name]
);
}
function hasLocalStorage(obj: any): obj is WindowLocalStorage { function hasLocalStorage(obj: any): obj is WindowLocalStorage {
return obj.localStorage; return obj.localStorage;
} }

View File

@ -0,0 +1,140 @@
export class FocusTracker {
mount(win: Window) {
const checkDown = (e: MouseEvent) => {
if (this.checkModalDown(e)) {
return;
}
const first = this.first;
if (first && !first.internalCheckInRange(e)) {
this.internalSuspenseItem(first);
first.internalTriggerBlur();
}
};
win.document.addEventListener('click', checkDown, true);
return () => {
win.document.removeEventListener('click', checkDown, true);
};
}
private actives: Focusable[] = [];
get first() {
return this.actives[0];
}
private modals: Array<{ checkDown: (e: MouseEvent) => boolean; checkOpen: () => boolean }> = [];
addModal(checkDown: (e: MouseEvent) => boolean, checkOpen: () => boolean) {
this.modals.push({
checkDown,
checkOpen,
});
}
private checkModalOpen(): boolean {
return this.modals.some(item => item.checkOpen());
}
private checkModalDown(e: MouseEvent): boolean {
return this.modals.some(item => item.checkDown(e));
}
execSave() {
// has Modal return;
if (this.checkModalOpen()) {
return;
}
// catch
if (this.first) {
this.first.internalTriggerSave();
}
}
execEsc() {
const first = this.first;
if (first) {
this.internalSuspenseItem(first);
first.internalTriggerEsc();
}
}
create(config: FocusableConfig) {
return new Focusable(this, config);
}
internalActiveItem(item: Focusable) {
const first = this.actives[0];
if (first === item) {
return;
}
const i = this.actives.indexOf(item);
if (i > -1) {
this.actives.splice(i, 1);
}
this.actives.unshift(item);
if (!item.isModal && first) {
// trigger Blur
first.internalTriggerBlur();
}
// trigger onActive
item.internalTriggerActive();
}
internalSuspenseItem(item: Focusable) {
const i = this.actives.indexOf(item);
if (i > -1) {
this.actives.splice(i, 1);
this.first?.internalTriggerActive();
}
}
}
export interface FocusableConfig {
range: HTMLElement | ((e: MouseEvent) => boolean);
modal?: boolean; // 模态窗口级别
onEsc?: () => void;
onBlur?: () => void;
onSave?: () => void;
onActive?: () => void;
}
export class Focusable {
readonly isModal: boolean;
constructor(private tracker: FocusTracker, private config: FocusableConfig) {
this.isModal = config.modal == null ? false : config.modal;
}
active() {
this.tracker.internalActiveItem(this);
}
suspense() {
this.tracker.internalSuspenseItem(this);
}
purge() {
this.tracker.internalSuspenseItem(this);
}
internalCheckInRange(e: MouseEvent) {
const { range } = this.config;
if (!range) {
return false;
}
if (typeof range === 'function') {
return range(e);
}
return range.contains(e.target as HTMLElement);
}
internalTriggerBlur() {
if (this.config.onBlur) {
this.config.onBlur();
}
}
internalTriggerSave() {
if (this.config.onSave) {
this.config.onSave();
return true;
}
return false;
}
internalTriggerEsc() {
if (this.config.onEsc) {
this.config.onEsc();
}
}
internalTriggerActive() {
if (this.config.onActive) {
this.config.onActive();
}
}
}
export const focusTracker = new FocusTracker();
focusTracker.mount(window);

View File

@ -1,48 +0,0 @@
class FocusingManager {
deploy() {
}
send(e: MouseEvent | KeyboardEvent) {
}
addModalCheck() {
}
create(config: FocusableConfig) {
}
activeItem() {
}
suspenceItem() {
}
}
export interface FocusableConfig {
range: HTMLElement | ((e: MouseEvent) => boolean);
modal?: boolean;
onEsc?: () => void;
onBlur?: () => void;
}
class Focusable {
readonly isModal: boolean;
constructor(private manager: FocusingManager, { range, modal }: FocusableConfig) {
this.isModal = modal == null ? false : modal;
}
checkRange(e: MouseEvent) {
}
active() {
this.manager.activeItem(this);
}
suspence() {
this.manager.suspenceItem(this);
}
purge() {
}
}

View File

@ -2,3 +2,4 @@ export * from './get-public-path';
export * from './goldlog'; export * from './goldlog';
export * from './obx'; export * from './obx';
export * from './request'; export * from './request';
export * from './focus-tracker';

View File

@ -64,8 +64,17 @@ function resolveDirection(popup: any, target: any, edge: any, bounds: any, prefe
return prefer; return prefer;
} }
function resolvePrefer(prefer: any) { function resolvePrefer(prefer: any, targetRect: any, bounds: any) {
if (!prefer) { if (!prefer) {
if (targetRect.left - bounds.left < 10) {
return { dir: 'right' };
} else if (targetRect.top - bounds.top < 10) {
return { dir: 'bottom' };
} else if (bounds.bottom - targetRect.bottom < 10) {
return { dir: 'top' };
} else if (bounds.right - targetRect.right < 10) {
return { dir: 'left' };
}
return {}; return {};
} }
const force = prefer[0] === '!'; const force = prefer[0] === '!';
@ -105,7 +114,7 @@ export function resolvePosition(popup: any, target: any, arrow: any, bounds: any
width: popup.width, width: popup.width,
}; };
const prefers = resolvePrefer(prefer); const prefers = resolvePrefer(prefer, target, bounds);
const edge = resolveEdge(popup, target, arrow, bounds); const edge = resolveEdge(popup, target, arrow, bounds);

View File

@ -27,7 +27,7 @@ export class Title extends Component<{ title: TitleContent; className?: string;
typeof title.tip === 'object' && !(isValidElement(title.tip) || isI18nData(title.tip)) typeof title.tip === 'object' && !(isValidElement(title.tip) || isI18nData(title.tip))
? title.tip ? title.tip
: { children: title.tip }; : { children: title.tip };
tip = <Tip direction="top" theme="black" {...tipProps} />; tip = <Tip {...tipProps} />;
} }
} }

View File

@ -3,6 +3,69 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="0.8.15"></a>
## [0.8.15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@0.8.14...@ali/lowcode-editor-skeleton@0.8.15) (2020-05-13)
**Note:** Version bump only for package @ali/lowcode-editor-skeleton
<a name="0.8.14"></a>
## [0.8.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@0.8.13...@ali/lowcode-editor-skeleton@0.8.14) (2020-05-13)
### Bug Fixes
* 🐛 add tip on setter title ([c93c1d0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c93c1d0))
* panel visible time ([18ac1fa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/18ac1fa))
* set i18n setter value when change mixed setter ([72d81c2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/72d81c2))
* supports ([371b84c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/371b84c))
### Features
* show value state ([bd49e50](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd49e50))
<a name="0.8.13"></a>
## [0.8.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@0.8.12...@ali/lowcode-editor-skeleton@0.8.13) (2020-05-08)
**Note:** Version bump only for package @ali/lowcode-editor-skeleton
<a name="0.8.12"></a>
## [0.8.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@0.8.11...@ali/lowcode-editor-skeleton@0.8.12) (2020-05-08)
**Note:** Version bump only for package @ali/lowcode-editor-skeleton
<a name="0.8.11"></a>
## [0.8.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@0.8.10...@ali/lowcode-editor-skeleton@0.8.11) (2020-05-07)
### Bug Fixes
* 🐛 history pane zindex ([48f3be1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/48f3be1))
* 🐛 修复编辑面板 ([a0bad77](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a0bad77))
* 🐛 绑定动作无法打开代码面板 ([160d6f7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/160d6f7))
* 在Transducer中添加对MixedSetter的支持 ([7317f2f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/7317f2f))
### Features
* left pane style ([c149f64](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c149f64))
* left pane title style; setting pane style ([66e8c25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/66e8c25))
<a name="0.8.10"></a> <a name="0.8.10"></a>
## [0.8.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@0.8.9...@ali/lowcode-editor-skeleton@0.8.10) (2020-04-27) ## [0.8.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@0.8.9...@ali/lowcode-editor-skeleton@0.8.10) (2020-04-27)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ali/lowcode-editor-skeleton", "name": "@ali/lowcode-editor-skeleton",
"version": "0.8.10", "version": "0.8.15",
"description": "alibaba lowcode editor skeleton", "description": "alibaba lowcode editor skeleton",
"main": "lib/index.js", "main": "lib/index.js",
"module": "es/index.js", "module": "es/index.js",
@ -19,10 +19,10 @@
"editor" "editor"
], ],
"dependencies": { "dependencies": {
"@ali/lowcode-designer": "^0.9.6", "@ali/lowcode-designer": "^0.9.9",
"@ali/lowcode-editor-core": "^0.8.9", "@ali/lowcode-editor-core": "^0.8.12",
"@ali/lowcode-types": "^0.8.1", "@ali/lowcode-types": "^0.8.3",
"@ali/lowcode-utils": "^0.8.2", "@ali/lowcode-utils": "^0.8.4",
"@alifd/next": "^1.x", "@alifd/next": "^1.x",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"react": "^16.8.1", "react": "^16.8.1",

View File

@ -1,17 +1,25 @@
import { Component } from 'react'; import { Component } from 'react';
import { isObject } from 'lodash';
import classNames from 'classnames'; import classNames from 'classnames';
import { Icon } from '@alifd/next'; import { Icon } from '@alifd/next';
import { Title } from '@ali/lowcode-editor-core'; import { Title, Tip } from '@ali/lowcode-editor-core';
import { TitleContent } from '@ali/lowcode-types'; import { TitleContent } from '@ali/lowcode-types';
import { PopupPipe, PopupContext } from '../popup'; import { PopupPipe, PopupContext } from '../popup';
import { intlNode } from '../../locale';
import './index.less'; import './index.less';
import { IconClear } from '../../icons/clear';
import InlineTip from './inlinetip';
export interface FieldProps { export interface FieldProps {
className?: string; className?: string;
title?: TitleContent | null; title?: TitleContent | null;
defaultDisplay?: 'accordion' | 'inline' | 'block'; defaultDisplay?: 'accordion' | 'inline' | 'block';
collapsed?: boolean; collapsed?: boolean;
valueState?: number;
name?: string;
tip?: any;
onExpandChange?: (expandState: boolean) => void; onExpandChange?: (expandState: boolean) => void;
onClear?: () => void;
} }
export class Field extends Component<FieldProps> { export class Field extends Component<FieldProps> {
@ -72,10 +80,36 @@ export class Field extends Component<FieldProps> {
} }
} }
getTipContent(propName: string, tip?: any): any {
let tipContent = (
<div>
<div>{propName}</div>
</div>
);
if (isObject(tip)) {
tipContent = (
<div>
<div>{propName}</div>
<div>{(tip as any).content}</div>
</div>
);
} else if (tip) {
tipContent = (
<div>
<div>{propName}</div>
<div>{tip}</div>
</div>
);
}
return tipContent;
}
render() { render() {
const { className, children, title } = this.props; const { className, children, title, valueState, onClear, name: propName, tip } = this.props;
const { display, collapsed } = this.state; const { display, collapsed } = this.state;
const isAccordion = display === 'accordion'; const isAccordion = display === 'accordion';
const tipContent = this.getTipContent(propName!, tip);
return ( return (
<div <div
className={classNames(`lc-field lc-${display}-field`, className, { className={classNames(`lc-field lc-${display}-field`, className, {
@ -84,7 +118,9 @@ export class Field extends Component<FieldProps> {
> >
<div className="lc-field-head" onClick={isAccordion ? this.toggleExpand : undefined}> <div className="lc-field-head" onClick={isAccordion ? this.toggleExpand : undefined}>
<div className="lc-field-title"> <div className="lc-field-title">
{createValueState(valueState, onClear)}
<Title title={title || ''} /> <Title title={title || ''} />
<InlineTip position="top">{tipContent}</InlineTip>
</div> </div>
{isAccordion && <Icon className="lc-field-icon" type="arrow-up" size="xs" />} {isAccordion && <Icon className="lc-field-icon" type="arrow-up" size="xs" />}
</div> </div>
@ -96,6 +132,51 @@ export class Field extends Component<FieldProps> {
} }
} }
/**
* ****
*
* -1
* 0 | null
* 1
* 2
* 10
*
* TODO: turn number to enum
*/
function createValueState(valueState?: number, onClear?: () => void) {
let tip: any = null;
let className = 'lc-valuestate';
let icon: any = null;
if (valueState) {
if (valueState < 0) {
// multiple value 橘黄色点: tip多种值点击清除
tip = intlNode('Multiple Value, Click to Clear');
className += ' valuestate-multiple';
icon = <IconClear size={6} />;
} else if (valueState === 10) {
// isset orangered tip: 必填项
tip = intlNode('Required');
className += ' valuestate-required';
onClear = undefined;
} else if (valueState > 0) {
// isset 蓝点 tip: 已设置值,点击清除
tip = intlNode('Setted Value, Click to Clear');
className += ' valuestate-isset';
icon = <IconClear size={6} />;
}
} else {
onClear = undefined;
// unset 占位空间
}
return (
<i className={className} onClick={onClear}>
{icon}
{tip && <Tip>{tip}</Tip>}
</i>
);
}
export interface PopupFieldProps extends FieldProps { export interface PopupFieldProps extends FieldProps {
width?: number; width?: number;
} }

View File

@ -11,6 +11,54 @@
.lc-field-title { .lc-field-title {
display: flex; display: flex;
align-items: center; align-items: center;
.lc-valuestate {
height: 6px;
width: 6px;
min-width: 6px;
border-radius: 100%;
margin-right: 2px;
pointer-events: none;
display: inline-flex;
align-items: center;
justify-content: center;
color: white;
> svg {
display: none;
}
&.valuestate-multiple {
background-color: rgb(232, 145, 83);
pointer-events: auto;
&:hover {
background-color: rgb(223, 139, 30);
cursor: pointer;
transform: scale(2);
transform-origin: center;
> svg {
display: block;
}
}
}
&.valuestate-isset {
background-color: rgba(124, 177, 238, 0.6);
pointer-events: auto;
&:hover {
background-color: rgb(45, 126, 219);
cursor: pointer;
transform: scale(2);
transform-origin: center;
> svg {
display: block;
}
}
}
&.valuestate-required {
background-color: rgb(250, 82, 76);
pointer-events: auto;
&:hover {
background-color: rgb(224, 46, 40);
}
}
}
} }
.lc-field-icon { .lc-field-icon {
// margin-right: @x-gap; // margin-right: @x-gap;
@ -75,7 +123,7 @@
// background: var(--color-block-background-shallow, rgba(31,56,88,.06)); // background: var(--color-block-background-shallow, rgba(31,56,88,.06));
// border-bottom: 1px solid var(--color-line-normal); // border-bottom: 1px solid var(--color-line-normal);
// color: var(--color-title); // color: var(--color-title);
padding: 0 16px; padding: 0 16px 0 10px;
background-color: #F7F9FC; background-color: #F7F9FC;
color: #8F9BB3; color: #8F9BB3;
user-select: none; user-select: none;

View File

@ -8,7 +8,9 @@ export interface FieldProps {
title?: TitleContent | null; title?: TitleContent | null;
display?: 'accordion' | 'inline' | 'block' | 'plain' | 'popup' | 'entry'; display?: 'accordion' | 'inline' | 'block' | 'plain' | 'popup' | 'entry';
collapsed?: boolean; collapsed?: boolean;
valueState?: number;
onExpandChange?: (collapsed: boolean) => void; onExpandChange?: (collapsed: boolean) => void;
onClear?: () => void;
[extra: string]: any; [extra: string]: any;
} }

View File

@ -0,0 +1,25 @@
import * as React from 'react';
export interface InlineTipProps {
position: string;
theme?: 'green' | 'black';
children: React.ReactNode;
}
export default class InlineTip extends React.Component<InlineTipProps> {
static displayName = 'InlineTip';
static defaultProps = {
position: 'auto',
theme: 'black',
};
render(): React.ReactNode {
const { position, theme, children } = this.props;
return (
<div style={{ display: 'none' }} data-role="tip" data-position={position} data-theme={theme}>
{children}
</div>
);
}
}

View File

@ -1,10 +1,11 @@
import { Component } from 'react'; import { Component } from 'react';
import { intl, shallowIntl, createSetterContent, observer } from '@ali/lowcode-editor-core'; import { shallowIntl, createSetterContent, observer } from '@ali/lowcode-editor-core';
import { createContent } from '@ali/lowcode-utils'; import { createContent } from '@ali/lowcode-utils';
import { Field, createField } from '../field'; import { Field, createField } from '../field';
import PopupService from '../popup'; import PopupService from '../popup';
import { SettingField, isSettingField, SettingTopEntry, SettingEntry } from '@ali/lowcode-designer'; import { SettingField, isSettingField, SettingTopEntry, SettingEntry } from '@ali/lowcode-designer';
import { isSetterConfig, CustomView } from '@ali/lowcode-types'; import { isSetterConfig, CustomView } from '@ali/lowcode-types';
import { intl } from '../../locale';
@observer @observer
class SettingFieldView extends Component<{ field: SettingField }> { class SettingFieldView extends Component<{ field: SettingField }> {
@ -41,26 +42,19 @@ class SettingFieldView extends Component<{ field: SettingField }> {
setterType = setter; setterType = setter;
} }
let value = null; let value = null;
if (field.type === 'field') {
if (defaultValue != null && !('defaultValue' in setterProps)) { if (defaultValue != null && !('defaultValue' in setterProps)) {
setterProps.defaultValue = defaultValue; setterProps.defaultValue = defaultValue;
if (initialValue == null) { if (initialValue == null) {
initialValue = defaultValue; initialValue = defaultValue;
} }
} }
if (field.valueState > 0) { if (field.valueState === -1) {
value = field.getValue();
} else {
setterProps.multiValue = true; setterProps.multiValue = true;
if (!('placeholder' in setterProps)) { if (!('placeholder' in setterProps)) {
// FIXME! move to locale file setterProps.placeholder = intl('Multiple Value');
setterProps.placeholder = intl({
type: 'i18n',
'zh-CN': '多种值',
'en-US': 'Multiple Value',
});
}
} }
} else {
value = field.getValue();
} }
// todo: error handling // todo: error handling
@ -69,7 +63,10 @@ class SettingFieldView extends Component<{ field: SettingField }> {
{ {
title: field.title, title: field.title,
collapsed: !field.expanded, collapsed: !field.expanded,
valueState: field.isRequired ? 10 : field.valueState,
onExpandChange: (expandState) => field.setExpanded(expandState), onExpandChange: (expandState) => field.setExpanded(expandState),
onClear: () => field.clearValue(),
...extraProps,
}, },
createSetterContent(setterType, { createSetterContent(setterType, {
...shallowIntl(setterProps), ...shallowIntl(setterProps),
@ -95,7 +92,7 @@ class SettingFieldView extends Component<{ field: SettingField }> {
value, value,
}); });
field.setValue(value); field.setValue(value);
} },
}), }),
extraProps.forceInline ? 'plain' : extraProps.display, extraProps.forceInline ? 'plain' : extraProps.display,
); );

View File

@ -0,0 +1,11 @@
import { SVGIcon, IconProps } from "@ali/lowcode-utils";
export function IconClear(props: IconProps) {
return (
<SVGIcon viewBox="0 0 1024 1024" {...props}>
<path d="M761.6 701.44a21.333333 21.333333 0 0 1 0 30.293333l-29.866667 29.866667a21.333333 21.333333 0 0 1-30.293333 0L512 572.16l-189.44 189.44a21.333333 21.333333 0 0 1-30.293333 0l-29.866667-29.866667a21.333333 21.333333 0 0 1 0-30.293333L451.84 512 262.4 322.56a21.333333 21.333333 0 0 1 0-30.293333l29.866667-29.866667a21.333333 21.333333 0 0 1 30.293333 0L512 451.84l189.44-189.44a21.333333 21.333333 0 0 1 30.293333 0l29.866667 29.866667a21.333333 21.333333 0 0 1 0 30.293333L572.16 512z" />
</SVGIcon>
);
}
IconClear.displayName = 'Clear';

View File

@ -3,12 +3,7 @@ import { SVGIcon, IconProps } from "@ali/lowcode-utils";
export function IconConvert(props: IconProps) { export function IconConvert(props: IconProps) {
return ( return (
<SVGIcon viewBox="0 0 1024 1024" {...props}> <SVGIcon viewBox="0 0 1024 1024" {...props}>
<path d="M508.16 889.6C291.84 889.6 115.2 714.24 115.2 497.92 115.2 281.6 291.84 106.24 509.44 106.24c43.52 0 85.76 6.4 124.16 20.48l-10.24 30.72c-35.84-11.52-72.96-17.92-113.92-17.92-199.68 0-362.24 161.28-362.24 359.68s162.56 358.4 360.96 358.4 359.68-161.28 359.68-359.68c0-66.56-17.92-131.84-51.2-185.6L844.8 294.4c37.12 60.16 56.32 130.56 56.32 203.52-1.28 216.32-176.64 391.68-392.96 391.68z" /> <path d="M620.8 256c-12.8-12.8-32-12.8-44.8 0s-12.8 32 0 44.8l83.2 83.2H288c-19.2 0-32 12.8-32 32s12.8 32 32 32h448c6.4 0 32 0 32-32 0-19.2-6.4-25.6-6.4-25.6L620.8 256zM736 576H288c-6.4 0-32 0-32 32 0 19.2 6.4 25.6 6.4 25.6L403.2 768c12.8 12.8 32 12.8 44.8 0s12.8-32 0-44.8L364.8 640H736c19.2 0 32-12.8 32-32s-12.8-32-32-32zM512 64C262.4 64 64 262.4 64 512s198.4 448 448 448 448-198.4 448-448S761.6 64 512 64z m0 832c-211.2 0-384-172.8-384-384s172.8-384 384-384 384 172.8 384 384-172.8 384-384 384z" />
<path d="M627.2 140.8m-15.36 0a15.36 15.36 0 1 0 30.72 0 15.36 15.36 0 1 0-30.72 0Z" />
<path d="M832 304.64m-15.36 0a15.36 15.36 0 1 0 30.72 0 15.36 15.36 0 1 0-30.72 0Z" />
<path d="M348.16 497.92m-35.84 0a35.84 35.84 0 1 0 71.68 0 35.84 35.84 0 1 0-71.68 0Z" fill="#35A2D4" />
<path d="M508.16 497.92m-35.84 0a35.84 35.84 0 1 0 71.68 0 35.84 35.84 0 1 0-71.68 0Z" fill="#35A2D4" />
<path d="M668.16 497.92m-35.84 0a35.84 35.84 0 1 0 71.68 0 35.84 35.84 0 1 0-71.68 0Z" fill="#35A2D4" />
</SVGIcon> </SVGIcon>
); );
} }

View File

@ -5,12 +5,17 @@ import { Button, Icon } from '@alifd/next';
import Area from '../area'; import Area from '../area';
import { PanelConfig } from '../types'; import { PanelConfig } from '../types';
import Panel from '../widget/panel'; import Panel from '../widget/panel';
import { Designer } from '@ali/lowcode-designer';
@observer @observer
export default class LeftFixedPane extends Component<{ area: Area<PanelConfig, Panel> }> { export default class LeftFixedPane extends Component<{ area: Area<PanelConfig, Panel> }> {
shouldComponentUpdate() { shouldComponentUpdate() {
return false; return false;
} }
componentDidUpdate() {
// FIXME: dirty fix, need deep think
this.props.area.skeleton.editor.get(Designer)?.touchOffsetObserver();
}
render() { render() {
const { area } = this.props; const { area } = this.props;
const hideTitleBar = area.current?.config.props?.hideTitleBar; const hideTitleBar = area.current?.config.props?.hideTitleBar;

View File

@ -1,6 +1,6 @@
import { Component, Fragment } from 'react'; import { Component, Fragment } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { observer } from '@ali/lowcode-editor-core'; import { observer, Focusable, focusTracker } from '@ali/lowcode-editor-core';
import { Button, Icon } from '@alifd/next'; import { Button, Icon } from '@alifd/next';
import Area from '../area'; import Area from '../area';
import Panel from '../widget/panel'; import Panel from '../widget/panel';
@ -12,7 +12,7 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
} }
private dispose?: () => void; private dispose?: () => void;
// private focusing?: FocusingItem; private focusing?: Focusable;
private shell: HTMLElement | null = null; private shell: HTMLElement | null = null;
componentDidMount() { componentDidMount() {
const { area } = this.props; const { area } = this.props;
@ -22,30 +22,39 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
area.skeleton.editor.removeListener('designer.dragstart', triggerClose); area.skeleton.editor.removeListener('designer.dragstart', triggerClose);
} }
/* this.focusing = focusTracker.create({
this.focusing = focusingTrack.create(this.shell!, { range: (e) => {
const target = e.target as HTMLElement;
if (!target) {
return false;
}
if (this.shell?.contains(target)) {
return true;
}
const docks = area.current?.getAssocDocks();
if (docks && docks?.length) {
return docks.some(dock => dock.getDOMNode()?.contains(target));
}
return false;
},
onEsc: () => { onEsc: () => {
this.props.area.setVisible(false); this.props.area.setVisible(false);
}, },
onBlur: () => { onBlur: () => {
this.props.area.setVisible(false); this.props.area.setVisible(false);
}, },
// modal: boolean
}); });
*/
this.onEffect(); this.onEffect();
} }
onEffect() { onEffect() {
/*
const { area } = this.props; const { area } = this.props;
if (area.visible) { if (area.visible) {
this.focusing?.active(); this.focusing?.active();
} else { } else {
this.focusing?.suspense(); this.focusing?.suspense();
} }
*/
} }
componentDidUpdate() { componentDidUpdate() {
@ -53,7 +62,7 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
} }
componentWillUnmount() { componentWillUnmount() {
// this.focusing?.purge(); this.focusing?.purge();
this.dispose?.(); this.dispose?.();
} }

View File

@ -127,122 +127,6 @@ body {
&.hidden { &.hidden {
display: none; display: none;
} }
/*
.my-tabs {
width: 100%;
height: 100%;
position: relative;
.tabs-title {
display: flex;
height: var(--pane-title-height);
margin-right: 30px;
> .tab-title {
cursor: pointer;
padding: 0;
flex: 1;
min-width: 0;
justify-content: center;
border-bottom: 2px solid transparent;
&.actived {
cursor: default;
color: var(--color-text-avtived);
border-bottom-color: #3896ee;
}
}
}
.tabs-content {
position: absolute;
top: var(--pane-title-height);
bottom: 0;
left: 0;
right: 0;
height: calc(100% - var(--pane-title-height));
overflow: hidden;
}
}
*/
/*覆盖旧面板*/
/*组件面板*/
// .ve-component-list {
// .ve-component-list-body{
// .ve-component-list-sidebar{
// .ve-component-list-navigator{
// .navigator-group{
// &:last-child{
// &::after{
// display: none;
// }
// }
// &::after{
// content: '';
// display: block;
// height: 1px;
// background-color: #EDEFF3;
// line-height: 0;
// margin: 4px 12px 0;
// }
// .navigator-group-head{
// .navigator-group-title{
// border-bottom: none;
// }
// }
// .navigator-group-item{
// border-left: 2px solid transparent;
// &.active{
// border-left-color: #0079f2;
// border-right: none;
// }
// }
// }
// }
// }
// }
// }
/*数据源*/
// .engine-datapool{
// .engine-datapool-view-group{
// padding-top: 48px;
// .engine-datapool-view-group-title{
// height: 48px;
// line-height: 48px;
// font-size: 16px;
// background-color: transparent;
// padding: 0 16px;
// border-bottom: 1px solid #EDEFF3;
// }
// }
// }
/*动作面板*/
// .ve-action-pane{
// border-top: none;
// .rc-tabs{
// .rc-tabs-bar{
// background-color: transparent;
// .rc-tabs-tab{
// line-height: 1;
// &.rc-tabs-tab-active{
// }
// }
// }
// }
// }
/*设置面板*/
// .ve-field .ve-field-head,
// .ve-field.ve-accordion2-field > .ve-field-head .ve-field-title-content{
// padding: 0;
// }
// .ve-field.ve-accordion2-field > .ve-field-split-line{
// display: none;
// }
// .vs-style .vs-style-source{
// margin: 0 0 16px;
// }
// .vs-code-button,
// .vs-json-button{
// margin: 0;
// }
} }
@ -286,10 +170,11 @@ body {
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
// width: var(--dock-pane-width); width: var(--dock-pane-width);
min-width: var(--dock-fixed-pane-width);
left: calc(var(--left-area-width) + 1px); left: calc(var(--left-area-width) + 1px);
background-color: var(--color-pane-background); background-color: var(--color-pane-background);
box-shadow: 4px 0 16px 0 rgba(31,50,88,0.08); box-shadow: 4px 6px 6px 0 rgba(31,50,88,0.08);
z-index: 820; z-index: 820;
display: none; display: none;
// padding-top: 36px; // padding-top: 36px;
@ -449,6 +334,12 @@ body {
display: none; display: none;
flex-shrink: 0; flex-shrink: 0;
margin-left: 2px; margin-left: 2px;
position: relative;
>.lc-panel {
position: absolute;
left: 0;
top: 0;
}
&.lc-area-visible { &.lc-area-visible {
display: block; display: block;
} }

View File

@ -1,5 +1,9 @@
{ {
"Binded: {expr}": "Binded: {expr}", "Binded: {expr}": "Binded: {expr}",
"Variable Binding": "Variable Binding", "Variable Binding": "Variable Binding",
"Switch Setter": "Switch Setter" "Switch Setter": "Switch Setter",
"Multiple Value, Click to Clear": "Multiple Value, Click to Clear",
"Required": "Required",
"Setted Value, Click to Clear": "Setted Value, Click to Clear",
"Multiple Value": "Multiple Value"
} }

View File

@ -1,5 +1,9 @@
{ {
"Binded: {expr}": "已绑定: {expr}", "Binded: {expr}": "已绑定: {expr}",
"Variable Binding": "变量绑定", "Variable Binding": "变量绑定",
"Switch Setter": "切换设置器" "Switch Setter": "切换设置器",
"Multiple Value, Click to Clear": "多种值, 点击清除",
"Required": "必填项",
"Setted Value, Click to Clear": "已设置值,点击清除",
"Multiple Value": "多种值"
} }

View File

@ -110,7 +110,7 @@ export class Skeleton {
} }
return this.createPanel(config); return this.createPanel(config);
}, },
true, false,
true, true,
); );
this.mainArea = new Area( this.mainArea = new Area(
@ -192,6 +192,7 @@ export class Skeleton {
this.editor.emit(event, ...args); this.editor.emit(event, ...args);
} }
readonly widgets: IWidget[] = [];
createWidget(config: IWidgetBaseConfig | IWidget) { createWidget(config: IWidgetBaseConfig | IWidget) {
if (isWidget(config)) { if (isWidget(config)) {
return config; return config;
@ -220,6 +221,7 @@ export class Skeleton {
} else { } else {
widget = new Widget(this, config as WidgetConfig); widget = new Widget(this, config as WidgetConfig);
} }
this.widgets.push(widget);
return widget; return widget;
} }

View File

@ -13,7 +13,7 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
name: 'children', name: 'children',
title: { type: 'i18n', 'zh-CN': '内容设置', 'en-US': 'Content' }, title: { type: 'i18n', 'zh-CN': '内容设置', 'en-US': 'Content' },
setter: { setter: {
componentName: 'MixinSetter', componentName: 'MixedSetter',
props: { props: {
// TODO: // TODO:
setters: [ setters: [
@ -41,11 +41,11 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
}; };
} }
const { props, events = {}, styles } = configure as any; const { props, supports = {} } = configure as any;
const isRoot: boolean = componentName === 'Page' || componentName === 'Component'; const isRoot: boolean = componentName === 'Page' || componentName === 'Component';
const eventsDefinition: any[] = []; const eventsDefinition: any[] = [];
const supportedLifecycles = const supportedLifecycles =
events.supportedLifecycles || supports.lifecycles ||
(isRoot (isRoot
? /*[ ? /*[
{ {
@ -73,11 +73,11 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
list: supportedLifecycles.map((event: any) => (typeof event === 'string' ? { name: event } : event)), list: supportedLifecycles.map((event: any) => (typeof event === 'string' ? { name: event } : event)),
}); });
} }
if (events.supportedEvents) { if (supports.events) {
eventsDefinition.push({ eventsDefinition.push({
type: 'events', type: 'events',
title: '事件', title: '事件',
list: (events.supportedEvents || []).map((event: any) => (typeof event === 'string' ? { name: event } : event)), list: (supports.events || []).map((event: any) => (typeof event === 'string' ? { name: event } : event)),
}); });
} }
// 通用设置 // 通用设置
@ -125,6 +125,24 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
], ],
}); });
*/ */
const stylesGroup: FieldConfig[] = [];
let advanceGroup: FieldConfig[] = [];
if (propsGroup) {
let l = propsGroup.length;
while (l-- > 0) {
const item = propsGroup[l];
if (item.type === 'group' && (item.title === '高级' || item.title?.label === '高级')) {
advanceGroup = item.items || [];
propsGroup.splice(l, 1);
} else if (item.name === '__style__' || item.name === 'containerStyle' || item.name === 'pageStyle') {
propsGroup.splice(l, 1);
stylesGroup.push(item);
if (item.extraProps?.defaultCollapsed && item.name !== 'containerStyle') {
item.extraProps.defaultCollapsed = false;
}
}
}
}
const combined: FieldConfig[] = [ const combined: FieldConfig[] = [
{ {
title: { type: 'i18n', 'zh-CN': '属性', 'en-US': 'Props' }, title: { type: 'i18n', 'zh-CN': '属性', 'en-US': 'Props' },
@ -132,15 +150,14 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
items: propsGroup, items: propsGroup,
}, },
]; ];
const stylesGroup: FieldConfig[] = []; if (supports.className) {
if (styles?.supportClassName) {
stylesGroup.push({ stylesGroup.push({
name: 'className', name: 'className',
title: { type: 'i18n', 'zh-CN': '类名绑定', 'en-US': 'ClassName' }, title: { type: 'i18n', 'zh-CN': '类名绑定', 'en-US': 'ClassName' },
setter: 'ClassNameSetter', setter: 'ClassNameSetter',
}); });
} }
if (styles?.supportInlineStyle) { if (supports.style) {
stylesGroup.push({ stylesGroup.push({
name: 'style', name: 'style',
title: { type: 'i18n', 'zh-CN': '行内样式', 'en-US': 'Style' }, title: { type: 'i18n', 'zh-CN': '行内样式', 'en-US': 'Style' },
@ -183,38 +200,28 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
}); });
} }
if (isRoot) { if (!isRoot) {
/* if (supports.condition !== false) {
combined.push({ advanceGroup.push({
name: '#advanced',
title: { type: 'i18n', 'zh-CN': '高级', 'en-US': 'Advance' },
items: [],
});
*/
} else {
combined.push({
name: '#advanced',
title: { type: 'i18n', 'zh-CN': '高级', 'en-US': 'Advance' },
items: [
{
name: '___condition', name: '___condition',
title: { type: 'i18n', 'zh-CN': '是否渲染', 'en-US': 'Condition' }, title: { type: 'i18n', 'zh-CN': '是否渲染', 'en-US': 'Condition' },
defaultValue: true,
setter: [{ setter: [{
componentName: 'BoolSetter', componentName: 'BoolSetter',
props: {
defaultValue: true,
}
}, { }, {
componentName: 'VariableSetter' componentName: 'VariableSetter'
}], }],
}, });
{ }
if (supports.loop !== false) {
advanceGroup.push({
name: '#loop', name: '#loop',
title: { type: 'i18n', 'zh-CN': '循环', 'en-US': 'Loop' }, title: { type: 'i18n', 'zh-CN': '循环', 'en-US': 'Loop' },
items: [ items: [
{ {
name: '___loop', name: '___loop',
title: { type: 'i18n', 'zh-CN': '循环数据', 'en-US': 'Loop Data' }, title: { type: 'i18n', 'zh-CN': '循环数据', 'en-US': 'Loop Data' },
defaultValue: [],
setter: [{ setter: [{
componentName: 'JsonSetter', componentName: 'JsonSetter',
props: { props: {
@ -254,8 +261,14 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
}], }],
}, },
], ],
}, })
], }
}
if (advanceGroup.length > 0) {
combined.push({
name: '#advanced',
title: { type: 'i18n', 'zh-CN': '高级', 'en-US': 'Advance' },
items: advanceGroup,
}); });
} }

View File

@ -143,7 +143,7 @@ function propTypeToSetter(propType: PropType): SetterType {
}; };
case 'oneOfType': case 'oneOfType':
return { return {
componentName: 'MixinSetter', componentName: 'MixedSetter',
props: { props: {
// TODO: // TODO:
// setters: (propType as OneOfType).value.map(item => propTypeToSetter(item)), // setters: (propType as OneOfType).value.map(item => propTypeToSetter(item)),
@ -153,7 +153,7 @@ function propTypeToSetter(propType: PropType): SetterType {
} }
return { return {
componentName: 'MixinSetter', componentName: 'MixedSetter',
isRequired, isRequired,
}; };
} }
@ -175,8 +175,8 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
}, },
}; };
} }
const { component = {}, events = {}, styles = {} } = configure; const { component = {}, supports = {} } = configure;
const supportedEvents: any[] | null = (events as any).supportedEvents ? null : []; const supportedEvents: any[] | null = (supports as any).events ? null : [];
const props: FieldConfig[] = []; const props: FieldConfig[] = [];
metadata.props.forEach((prop) => { metadata.props.forEach((prop) => {
@ -197,21 +197,21 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
name, name,
description, description,
}); });
(events as any).supportedEvents = supportedEvents; (supports as any).events = supportedEvents;
} }
return; return;
} }
if (name === 'className' && (propType === 'string' || propType === 'any')) { if (name === 'className' && (propType === 'string' || propType === 'any')) {
if ((styles as any).supportClassName == null) { if ((supports as any).className == null) {
(styles as any).supportClassName = true; (supports as any).className = true;
} }
return; return;
} }
if (name === 'style' && (propType === 'object' || propType === 'any')) { if (name === 'style' && (propType === 'object' || propType === 'any')) {
if ((styles as any).supportInlineStyle == null) { if ((supports as any).style == null) {
(styles as any).supportInlineStyle = true; (supports as any).style = true;
} }
return; return;
} }
@ -224,8 +224,7 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
configure: { configure: {
...configure, ...configure,
props, props,
events, supports,
styles,
component, component,
}, },
}; };

View File

@ -1,5 +1,6 @@
import { ReactElement, ComponentType } from 'react'; import { ReactElement, ComponentType } from 'react';
import { TitleContent, IconType, I18nData, TipContent } from '@ali/lowcode-types'; import { TitleContent, IconType, I18nData, TipContent } from '@ali/lowcode-types';
import { IWidget } from './widget/widget';
export interface IWidgetBaseConfig { export interface IWidgetBaseConfig {
type: string; type: string;
@ -16,6 +17,7 @@ export interface WidgetConfig extends IWidgetBaseConfig {
type: "Widget"; type: "Widget";
props?: { props?: {
align?: "left" | "right" | "bottom" | "center" | "top"; align?: "left" | "right" | "bottom" | "center" | "top";
onInit?: (widget: IWidget) => void;
}; };
content?: string | ReactElement | ComponentType<any>; // children content?: string | ReactElement | ComponentType<any>; // children
} }
@ -47,6 +49,7 @@ export function isDividerConfig(obj: any): obj is DividerConfig {
export interface IDockBaseConfig extends IWidgetBaseConfig { export interface IDockBaseConfig extends IWidgetBaseConfig {
props?: DockProps & { props?: DockProps & {
align?: "left" | "right" | "bottom" | "center" | "top"; align?: "left" | "right" | "bottom" | "center" | "top";
onInit?: (widget: IWidget) => void;
}; };
} }
@ -95,7 +98,8 @@ export interface PanelProps {
height?: number; // panel.props height?: number; // panel.props
maxWidth?: number; // panel.props maxWidth?: number; // panel.props
maxHeight?: number; // panel.props maxHeight?: number; // panel.props
onInit?: () => any; condition?: (widget: IWidget) => any;
onInit?: (widget: IWidget) => any;
onDestroy?: () => any; onDestroy?: () => any;
shortcut?: string; // 只有在特定位置,可触发 toggle show shortcut?: string; // 只有在特定位置,可触发 toggle show
} }

View File

@ -54,6 +54,9 @@ export default class Dock implements IWidget {
const { props = {}, name } = config; const { props = {}, name } = config;
this.name = name; this.name = name;
this.align = props.align; this.align = props.align;
if (props.onInit) {
props.onInit.call(this, this);
}
} }
setVisible(flag: boolean) { setVisible(flag: boolean) {

View File

@ -1,15 +1,17 @@
import { obx, computed } from '@ali/lowcode-editor-core'; import { obx, computed } from '@ali/lowcode-editor-core';
import { uniqueId } from '@ali/lowcode-utils'; import { uniqueId } from '@ali/lowcode-utils';
import { createElement, ReactNode } from 'react'; import { createElement, ReactNode, ReactInstance } from 'react';
import { Skeleton } from '../skeleton'; import { Skeleton } from '../skeleton';
import { PanelDockConfig } from '../types'; import { PanelDockConfig } from '../types';
import Panel from './panel'; import Panel from './panel';
import { PanelDockView, WidgetView } from '../components/widget-views'; import { PanelDockView, WidgetView } from '../components/widget-views';
import { IWidget } from './widget'; import { IWidget } from './widget';
import { composeTitle } from './utils'; import { composeTitle } from './utils';
import { findDOMNode } from 'react-dom';
export default class PanelDock implements IWidget { export default class PanelDock implements IWidget {
readonly isWidget = true; readonly isWidget = true;
readonly isPanelDock = true;
readonly id: string; readonly id: string;
readonly name: string; readonly name: string;
readonly align?: string; readonly align?: string;
@ -31,13 +33,21 @@ export default class PanelDock implements IWidget {
return this._body; return this._body;
} }
private _shell: ReactInstance | null = null;
get content(): ReactNode { get content(): ReactNode {
return createElement(WidgetView, { return createElement(WidgetView, {
widget: this, widget: this,
ref: (ref) => {
this._shell = ref;
},
key: this.id, key: this.id,
}); });
} }
getDOMNode() {
return this._shell ? findDOMNode(this._shell) : null;
}
@obx.ref private _visible: boolean = true; @obx.ref private _visible: boolean = true;
get visible() { get visible() {
return this._visible; return this._visible;
@ -58,20 +68,24 @@ export default class PanelDock implements IWidget {
this.name = name; this.name = name;
this.id = uniqueId(`dock:${name}$`); this.id = uniqueId(`dock:${name}$`);
this.panelName = config.panelName || name; this.panelName = config.panelName || name;
this.align = props?.align;
if (content) { if (content) {
const _panelProps: any = { ...panelProps }; const _panelProps: any = { ...panelProps };
if (_panelProps.title == null && props) { if (_panelProps.title == null && props) {
_panelProps.title = composeTitle(props.title, undefined, props.description, true, true); _panelProps.title = composeTitle(props.title, undefined, props.description, true, true);
} }
this._panel = this.skeleton.add({ this._panel = this.skeleton.add({
type: "Panel", type: 'Panel',
name: this.panelName, name: this.panelName,
props: _panelProps, props: _panelProps,
contentProps, contentProps,
content, content,
area: panelProps?.area area: panelProps?.area,
}) as Panel; }) as Panel;
} }
if (props?.onInit) {
props.onInit.call(this, this);
}
} }
setVisible(flag: boolean) { setVisible(flag: boolean) {
@ -117,3 +131,8 @@ export default class PanelDock implements IWidget {
this.panel?.setActive(true); this.panel?.setActive(true);
} }
} }
export function isPanelDock(obj: any): obj is PanelDock {
return obj && obj.isPanelDock;
}

View File

@ -1,6 +1,6 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { createElement, ReactNode } from 'react'; import { createElement, ReactNode } from 'react';
import { obx } from '@ali/lowcode-editor-core'; import { obx, computed } from '@ali/lowcode-editor-core';
import { uniqueId, createContent } from '@ali/lowcode-utils'; import { uniqueId, createContent } from '@ali/lowcode-utils';
import { TitleContent } from '@ali/lowcode-types'; import { TitleContent } from '@ali/lowcode-types';
import WidgetContainer from './widget-container'; import WidgetContainer from './widget-container';
@ -9,20 +9,25 @@ import { TitledPanelView, TabsPanelView, PanelView } from '../components/widget-
import { Skeleton } from '../skeleton'; import { Skeleton } from '../skeleton';
import { composeTitle } from './utils'; import { composeTitle } from './utils';
import { IWidget } from './widget'; import { IWidget } from './widget';
import PanelDock, { isPanelDock } from './panel-dock';
export default class Panel implements IWidget { export default class Panel implements IWidget {
readonly isWidget = true; readonly isWidget = true;
readonly name: string; readonly name: string;
readonly id: string; readonly id: string;
@obx.ref inited = false; @obx.ref inited = false;
@obx.ref private _actived = false; @obx.ref private _actived: boolean = false;
private emitter = new EventEmitter(); private emitter = new EventEmitter();
get actived(): boolean { get actived(): boolean {
return this._actived; return this._actived;
} }
get visible(): boolean { @computed get visible(): boolean {
if (this.parent?.visible) { if (!this.parent || this.parent.visible) {
const { props } = this.config;
if (props?.condition) {
return props.condition(this);
}
return this._actived; return this._actived;
} }
return false; return false;
@ -30,10 +35,21 @@ export default class Panel implements IWidget {
readonly isPanel = true; readonly isPanel = true;
private _body?: ReactNode;
get body() { get body() {
this.initBody(); if (this.container) {
return this._body; return createElement(TabsPanelView, {
container: this.container,
});
}
const { content, contentProps } = this.config;
return createContent(content, {
...contentProps,
editor: this.skeleton.editor,
config: this.config,
panel: this,
pane: this,
});
} }
get content(): ReactNode { get content(): ReactNode {
@ -79,30 +95,12 @@ export default class Panel implements IWidget {
); );
content.forEach((item) => this.add(item)); content.forEach((item) => this.add(item));
} }
if (props.onInit) {
props.onInit.call(this, this);
}
// todo: process shortcut // todo: process shortcut
} }
private initBody() {
if (this.inited) {
return;
}
this.inited = true;
if (this.container) {
this._body = createElement(TabsPanelView, {
container: this.container,
});
} else {
const { content, contentProps } = this.config;
this._body = createContent(content, {
...contentProps,
editor: this.skeleton.editor,
config: this.config,
panel: this,
pane: this,
});
}
}
setParent(parent: WidgetContainer) { setParent(parent: WidgetContainer) {
if (parent === this.parent) { if (parent === this.parent) {
return; return;
@ -147,11 +145,11 @@ export default class Panel implements IWidget {
return; return;
} }
if (flag) { if (flag) {
if (!this.inited) {
this.initBody();
}
this._actived = true; this._actived = true;
this.parent?.active(this); this.parent?.active(this);
if (!this.inited) {
this.inited = true;
}
this.emitter.emit('activechange', true); this.emitter.emit('activechange', true);
} else if (this.inited) { } else if (this.inited) {
this._actived = false; this._actived = false;
@ -172,6 +170,12 @@ export default class Panel implements IWidget {
this.setActive(true); this.setActive(true);
} }
getAssocDocks(): PanelDock[] {
return this.skeleton.widgets.filter(item => {
return isPanelDock(item) && item.panelName === this.name;
}) as any;
}
/** /**
* @deprecated * @deprecated
*/ */

View File

@ -60,6 +60,9 @@ export default class Widget implements IWidget {
const { props = {}, name } = config; const { props = {}, name } = config;
this.name = name; this.name = name;
this.align = props.align; this.align = props.align;
if (props.onInit) {
props.onInit.call(this, this);
}
} }
getId() { getId() {

View File

@ -3,17 +3,25 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.9.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-material-parser@0.9.0...@ali/lowcode-material-parser@0.9.1) (2020-04-01) ## [0.9.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-material-parser@0.9.2...@ali/lowcode-material-parser@0.9.3) (2020-05-08)
### Bug Fixes ### Features
* fix bug of missing ajv ([a37d655](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a37d655)) * 🎸 support parsing sub components ([70f3e32](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/70f3e325c64bafe6a098e7eb872a81308566e811))
<a name="0.9.2"></a>
## [0.9.2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-material-parser@0.9.1...@ali/lowcode-material-parser@0.9.2) (2020-05-07)
**Note:** Version bump only for package @ali/lowcode-material-parser
<a name="0.9.0"></a> <a name="0.9.0"></a>
# [0.9.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-material-parser@0.8.4...@ali/lowcode-material-parser@0.9.0) (2020-03-31) # [0.9.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-material-parser@0.8.4...@ali/lowcode-material-parser@0.9.0) (2020-03-31)

View File

@ -1,6 +1,6 @@
{ {
"name": "@ali/lowcode-material-parser", "name": "@ali/lowcode-material-parser",
"version": "0.9.1", "version": "0.9.3",
"description": "material parser for Ali lowCode engine", "description": "material parser for Ali lowCode engine",
"main": "lib/index.js", "main": "lib/index.js",
"files": [ "files": [
@ -54,7 +54,8 @@
"lodash": "^4.17.15", "lodash": "^4.17.15",
"react-docgen": "^5.3.0", "react-docgen": "^5.3.0",
"semver": "^7.1.3", "semver": "^7.1.3",
"short-uuid": "^3.1.1" "short-uuid": "^3.1.1",
"typescript": "^3.8.3"
}, },
"publishConfig": { "publishConfig": {
"registry": "https://registry.npm.alibaba-inc.com" "registry": "https://registry.npm.alibaba-inc.com"

View File

@ -45,10 +45,10 @@ export async function genManifest(
npm: { npm: {
package: matScanModel.pkgName, package: matScanModel.pkgName,
version: matScanModel.pkgVersion, version: matScanModel.pkgVersion,
exportName: matParsedModel.componentName, exportName: matParsedModel.meta?.exportName || matParsedModel.componentName,
main: matScanModel.mainFilePath, main: matScanModel.mainFilePath,
destructuring: false, destructuring: matParsedModel.meta?.exportName !== 'default',
subName: '', subName: matParsedModel.meta?.subName || '',
}, },
}; };

View File

@ -0,0 +1,106 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import { namedTypes as t } from 'ast-types';
const {
getMemberValuePath,
isReactComponentClass,
isReactComponentMethod,
resolveToValue,
match,
} = require('react-docgen').utils;
import getMethodDocumentation from '../utils/getMethodDocumentation';
const { traverseShallow } = require('react-docgen/dist/utils/traverse');
/**
* The following values/constructs are considered methods:
*
* - Method declarations in classes (except "constructor" and React lifecycle
* methods
* - Public class fields in classes whose value are a functions
* - Object properties whose values are functions
*/
function isMethod(path: any) {
const isProbablyMethod =
(t.MethodDefinition.check(path.node) && path.node.kind !== 'constructor') ||
((t.ClassProperty.check(path.node) || t.Property.check(path.node)) && t.Function.check(path.get('value').node));
return isProbablyMethod && !isReactComponentMethod(path);
}
function findAssignedMethods(scope: any, idPath: any) {
const results: any[] = [];
if (!t.Identifier.check(idPath.node)) {
return results;
}
const name = idPath.node.name;
const idScope = idPath.scope.lookup(idPath.node.name);
traverseShallow(scope.path, {
visitAssignmentExpression: function(path: any) {
const node = path.node;
if (
match(node.left, {
type: 'MemberExpression',
object: { type: 'Identifier', name },
}) &&
path.scope.lookup(name) === idScope &&
t.Function.check(resolveToValue(path.get('right')).node)
) {
results.push(path);
return false;
}
return this.traverse(path);
},
});
return results;
}
/**
* Extract all flow types for the methods of a react component. Doesn't
* return any react specific lifecycle methods.
*/
export default function componentMethodsHandler(documentation: any, path: any) {
// Extract all methods from the class or object.
let methodPaths = [];
if (isReactComponentClass(path)) {
methodPaths = path.get('body', 'body').filter(isMethod);
} else if (t.ObjectExpression.check(path.node)) {
methodPaths = path.get('properties').filter(isMethod);
// Add the statics object properties.
const statics = getMemberValuePath(path, 'statics');
if (statics) {
statics.get('properties').each((p: any) => {
if (isMethod(p)) {
p.node.static = true;
methodPaths.push(p);
}
});
}
} else if (
t.VariableDeclarator.check(path.parent.node) &&
path.parent.node.init === path.node &&
t.Identifier.check(path.parent.node.id)
) {
methodPaths = findAssignedMethods(path.parent.scope, path.parent.get('id'));
} else if (
t.AssignmentExpression.check(path.parent.node) &&
path.parent.node.right === path.node &&
t.Identifier.check(path.parent.node.left)
) {
methodPaths = findAssignedMethods(path.parent.scope, path.parent.get('left'));
} else if (t.FunctionDeclaration.check(path.node)) {
methodPaths = findAssignedMethods(path.parent.scope, path.get('id'));
}
documentation.set('methods', methodPaths.map(getMethodDocumentation).filter(Boolean));
}

View File

@ -0,0 +1,95 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import { namedTypes as t } from 'ast-types';
import getTSType from '../utils/getTSType';
const { unwrapUtilityType } = require('react-docgen/dist/utils/flowUtilityTypes');
const { getFlowType, getPropertyName, resolveToValue } = require('react-docgen').utils;
const setPropDescription = require('react-docgen/dist/utils/setPropDescription').default;
import getFlowTypeFromReactComponent from '../utils/getFlowTypeFromReactComponent';
import { applyToFlowTypeProperties } from '../utils/getFlowTypeFromReactComponent';
function setPropDescriptor(documentation: any, path: any, typeParams: any) {
if (t.ObjectTypeSpreadProperty.check(path.node)) {
const argument = unwrapUtilityType(path.get('argument'));
if (t.ObjectTypeAnnotation.check(argument.node)) {
applyToFlowTypeProperties(
documentation,
argument,
(propertyPath: any, innerTypeParams: any) => {
setPropDescriptor(documentation, propertyPath, innerTypeParams);
},
typeParams,
);
return;
}
const name = argument.get('id').get('name');
const resolvedPath = resolveToValue(name);
if (resolvedPath && t.TypeAlias.check(resolvedPath.node)) {
const right = resolvedPath.get('right');
applyToFlowTypeProperties(
documentation,
right,
(propertyPath: any, innerTypeParams: any) => {
setPropDescriptor(documentation, propertyPath, innerTypeParams);
},
typeParams,
);
} else {
documentation.addComposes(name.node.name);
}
} else if (t.ObjectTypeProperty.check(path.node)) {
const type = getFlowType(path.get('value'), typeParams);
const propName = getPropertyName(path);
if (!propName) return;
const propDescriptor = documentation.getPropDescriptor(propName);
propDescriptor.required = !path.node.optional;
propDescriptor.flowType = type;
// We are doing this here instead of in a different handler
// to not need to duplicate the logic for checking for
// imported types that are spread in to props.
setPropDescription(documentation, path);
} else if (t.TSPropertySignature.check(path.node)) {
const type = getTSType(path.get('typeAnnotation'), typeParams);
const propName = getPropertyName(path);
if (!propName) return;
const propDescriptor = documentation.getPropDescriptor(propName);
propDescriptor.required = !path.node.optional;
propDescriptor.tsType = type;
// We are doing this here instead of in a different handler
// to not need to duplicate the logic for checking for
// imported types that are spread in to props.
setPropDescription(documentation, path);
}
}
/**
* This handler tries to find flow Type annotated react components and extract
* its types to the documentation. It also extracts docblock comments which are
* inlined in the type definition.
*/
export default function flowTypeHandler(documentation: any, path: any) {
const flowTypesPath = getFlowTypeFromReactComponent(path);
if (!flowTypesPath) {
return;
}
applyToFlowTypeProperties(documentation, flowTypesPath, (propertyPath: any, typeParams: any) => {
setPropDescriptor(documentation, propertyPath, typeParams);
});
}

View File

@ -1,23 +1,23 @@
import { import { propTypeHandler, contextTypeHandler, childContextTypeHandler } from './propTypeHandler';
propTypeHandler,
contextTypeHandler,
childContextTypeHandler,
} from './propTypeHandler';
import defaultPropsHandler from './defaultPropsHandler'; import defaultPropsHandler from './defaultPropsHandler';
import flowTypeHandler from './flowTypeHandler';
import componentMethodsHandler from './componentMethodsHandler';
import preProcessHandler from './preProcessHandler';
const { handlers } = require('react-docgen'); const { handlers } = require('react-docgen');
const defaultHandlers = [ const defaultHandlers = [
preProcessHandler,
propTypeHandler, propTypeHandler,
contextTypeHandler, contextTypeHandler,
childContextTypeHandler, childContextTypeHandler,
handlers.propTypeCompositionHandler, handlers.propTypeCompositionHandler,
handlers.propDocBlockHandler, handlers.propDocBlockHandler,
handlers.flowTypeHandler, flowTypeHandler,
defaultPropsHandler, defaultPropsHandler,
handlers.componentDocblockHandler, handlers.componentDocblockHandler,
handlers.displayNameHandler, handlers.displayNameHandler,
handlers.componentMethodsHandler, componentMethodsHandler,
handlers.componentMethodsJsDocHandler, handlers.componentMethodsJsDocHandler,
]; ];

View File

@ -0,0 +1,3 @@
export default function preProcessHandler(documentation: any, path: any) {
documentation.set('meta', path.__meta);
}

View File

@ -14,6 +14,9 @@ export default function parse(params: { fileContent: string; filePath: string })
return resolver(ast); return resolver(ast);
}, },
handlers, handlers,
{
filename: filePath,
},
); );
const coms = result.reduce((res: any[], info: any) => { const coms = result.reduce((res: any[], info: any) => {
if (!info || !info.props) return res; if (!info || !info.props) return res;
@ -29,6 +32,7 @@ export default function parse(params: { fileContent: string; filePath: string })
res.push({ res.push({
componentName: info.displayName, componentName: info.displayName,
props, props,
meta: info.meta || {},
}); });
return res; return res;
}, []); }, []);

View File

@ -1,5 +1,6 @@
export default function checkIsIIFE(path: any) { export default function checkIsIIFE(path: any) {
return ( return (
path.value &&
path.value.callee && path.value.callee &&
path.value.callee.type === 'FunctionExpression' && path.value.callee.type === 'FunctionExpression' &&
path.node.type === 'CallExpression' path.node.type === 'CallExpression'

View File

@ -0,0 +1,54 @@
import { namedTypes as t } from 'ast-types';
const { match, resolveToValue } = require('react-docgen').utils;
const { traverseShallow } = require('react-docgen/dist/utils/traverse');
import isReactComponentStaticMember from './isReactComponentStaticMember';
import getRoot from '../utils/getRoot';
function findAssignedMethods(scope: any, idPath: any) {
const results: any[] = [];
if (!t.Identifier.check(idPath.node)) {
return results;
}
const name = idPath.node.name;
const idScope = idPath.scope.lookup(idPath.node.name);
traverseShallow(scope.path, {
visitAssignmentExpression: function(path: any) {
const node = path.node;
if (
match(node.left, {
type: 'MemberExpression',
object: { type: 'Identifier', name },
})
// && path.scope.lookup(name) === idScope
) {
results.push(path);
return false;
}
return this.traverse(path);
},
});
return results.filter((x) => !isReactComponentStaticMember(x.get('left')));
}
export default findAssignedMethods;
// const findAssignedMethodsFromScopes = (scope: any, idPath: any) => {
// const rootNode = getRoot(idPath);
// let { __scope: scopes = [] } = rootNode;
// if (!scopes.find((x: any) => x.scope === scope && x.idPath === idPath)) {
// scopes = [
// ...scopes,
// {
// scope,
// idPath,
// },
// ];
// }
// return scopes.map(({ scope: s, idPath: id }: any) => findAssignedMethods(s, id)).flatMap((x: any) => x);
// };
// export { findAssignedMethodsFromScopes };

View File

@ -7,11 +7,19 @@
*/ */
import { namedTypes as t, visit } from 'ast-types'; import { namedTypes as t, visit } from 'ast-types';
import { uniqBy } from 'lodash';
import checkIsIIFE from './checkIsIIFE'; import checkIsIIFE from './checkIsIIFE';
import resolveHOC from './resolveHOC'; import resolveHOC from './resolveHOC';
import resolveIIFE from './resolveIIFE'; import resolveIIFE from './resolveIIFE';
import resolveImport from './resolveImport'; import resolveImport, { isImportLike } from './resolveImport';
import resolveTranspiledClass from './resolveTranspiledClass'; import resolveTranspiledClass from './resolveTranspiledClass';
import isStaticMethod from './isStaticMethod';
import findAssignedMethods from './findAssignedMethods';
import resolveExportDeclaration from './resolveExportDeclaration';
import makeProxy from '../utils/makeProxy';
const expressionTo = require('react-docgen/dist/utils/expressionTo');
import { get, set, has, ICache } from '../utils/cache';
import getName from '../utils/getName';
const { const {
isExportsOrModuleAssignment, isExportsOrModuleAssignment,
@ -20,8 +28,8 @@ const {
isReactForwardRefCall, isReactForwardRefCall,
isStatelessComponent, isStatelessComponent,
normalizeClassDefinition, normalizeClassDefinition,
resolveExportDeclaration,
resolveToValue, resolveToValue,
getMemberValuePath,
} = require('react-docgen').utils; } = require('react-docgen').utils;
function ignore() { function ignore() {
@ -47,16 +55,14 @@ function resolveDefinition(definition: any) {
} else if (isReactComponentClass(definition)) { } else if (isReactComponentClass(definition)) {
normalizeClassDefinition(definition); normalizeClassDefinition(definition);
return definition; return definition;
} else if ( } else if (isStatelessComponent(definition) || isReactForwardRefCall(definition)) {
isStatelessComponent(definition) ||
isReactForwardRefCall(definition)
) {
return definition; return definition;
} }
return null; return null;
} }
function getDefinition(definition: any): any { function getDefinition(definition: any, cache: ICache = {}): any {
const { __meta: exportMeta = {} } = definition;
if (checkIsIIFE(definition)) { if (checkIsIIFE(definition)) {
definition = resolveToValue(resolveIIFE(definition)); definition = resolveToValue(resolveIIFE(definition));
if (!isComponentDefinition(definition)) { if (!isComponentDefinition(definition)) {
@ -64,24 +70,195 @@ function getDefinition(definition: any): any {
} }
} else { } else {
definition = resolveToValue(resolveHOC(definition)); definition = resolveToValue(resolveHOC(definition));
if (isComponentDefinition(definition)) {
definition = makeProxy(definition, {
__meta: exportMeta,
});
return definition;
}
if (checkIsIIFE(definition)) { if (checkIsIIFE(definition)) {
definition = resolveToValue(resolveIIFE(definition)); definition = resolveToValue(resolveIIFE(definition));
if (!isComponentDefinition(definition)) { if (!isComponentDefinition(definition)) {
definition = resolveTranspiledClass(definition); definition = resolveTranspiledClass(definition);
} }
} else if (t.SequenceExpression.check(definition.node)) { } else if (t.SequenceExpression.check(definition.node)) {
return getDefinition( return getDefinition(resolveToValue(definition.get('expressions').get(0)), cache);
resolveToValue(definition.get('expressions').get(0)),
);
} else { } else {
definition = resolveImport( return resolveImport(definition, (ast: any, sourcePath: string) => {
definition, const importMeta: any[] = [];
findAllExportedComponentDefinition, if (t.ImportDeclaration.check(definition.node)) {
); // @ts-ignore
const specifiers = definition.get('specifiers');
specifiers.each((spec: any) => {
const { node } = spec;
importMeta.push({
localName: node.local.name,
importedName: node.imported ? node.imported.name : 'default',
});
});
} }
let result;
if (has('ast-export', ast.__path)) {
result = get('ast-export', ast.__path);
} else {
result = findAllExportedComponentDefinition(ast);
set('ast-export', ast.__path, result);
}
const exportList: any[] = [];
const importList: any[] = [];
result = result.forEach((def: any) => {
let { __meta: meta = {} } = def;
let exportName = meta.exportName;
for (let item of importMeta) {
if (exportName === item.importedName) {
exportName = item.localName;
break;
}
}
if (exportName) {
importList.push(makeProxy(def, { __meta: { exportName } }));
}
const nextMeta: any = {
exportName,
};
if (exportName === exportMeta.localName) {
nextMeta.exportName = exportMeta.exportName;
} else {
return;
}
if (exportMeta.subName) {
nextMeta.subName = exportMeta.subName;
} else if (meta.subName) {
nextMeta.subName = meta.subName;
}
exportList.push(makeProxy(def, { __meta: nextMeta }));
});
cache[sourcePath] = importList;
// result = result.filter((x) => !x.__shouldDelete);
return exportList;
});
}
}
if (definition && !definition.__meta) {
definition.__meta = exportMeta;
} }
return definition; return definition;
} }
export interface IMethodsPath {
subName: string;
localName: string;
value: any;
}
/**
* Extract all flow types for the methods of a react component. Doesn't
* return any react specific lifecycle methods.
*/
function getSubComponents(path: any, scope: any, cache: ICache) {
// Extract all methods from the class or object.
let methodPaths = [];
if (isReactComponentClass(path)) {
methodPaths = path.get('body', 'body').filter(isStaticMethod);
methodPaths = [...methodPaths, ...findAssignedMethods(scope || path.scope, path.get('id'))];
} else if (t.ObjectExpression.check(path.node)) {
methodPaths = path.get('properties').filter(isStaticMethod);
methodPaths = [...methodPaths, ...findAssignedMethods(scope || path.scope, path.get('id'))];
// Add the statics object properties.
const statics = getMemberValuePath(path, 'statics');
if (statics) {
statics.get('properties').each((p: any) => {
if (isStaticMethod(p)) {
p.node.static = true;
methodPaths.push(p);
}
});
}
} else if (
t.VariableDeclarator.check(path.parent.node) &&
path.parent.node.init === path.node &&
t.Identifier.check(path.parent.node.id)
) {
methodPaths = findAssignedMethods(scope || path.parent.scope, path.parent.get('id'));
} else if (
t.AssignmentExpression.check(path.parent.node) &&
path.parent.node.right === path.node &&
t.Identifier.check(path.parent.node.left)
) {
methodPaths = findAssignedMethods(scope || path.parent.scope, path.parent.get('left'));
} else if (t.FunctionDeclaration.check(path.node)) {
methodPaths = findAssignedMethods(scope || path.parent.scope, path.get('id'));
} else if (t.ArrowFunctionExpression.check(path.node)) {
methodPaths = findAssignedMethods(scope || path.parent.scope, path.parent.get('id'));
}
return (
methodPaths
.map((x: any) => {
if (t.ClassProperty.check(x.node)) {
return {
value: x.get('value'),
subName: x.node.key.name,
localName: getName(x.get('value')),
};
}
return {
value: x,
subName: x.node.left.property.name,
localName: getName(x.get('right')),
};
})
.map(({ subName, localName, value }: IMethodsPath) => ({
subName,
localName,
value: resolveToValue(value),
}))
.map(({ subName, localName, value }: IMethodsPath) => {
let def = getDefinition(
makeProxy(value, {
__meta: {
localName,
subName,
exportName: path.__meta && path.__meta.exportName,
},
}),
cache,
);
if (!Array.isArray(def)) {
def = [def];
}
return {
subName,
localName,
value: def.flatMap((x: any) => x).filter((x: any) => isComponentDefinition(x)),
};
})
.map(({ subName, localName, value }: IMethodsPath) =>
value.map((x: any) => ({
subName,
localName,
value: x,
})),
)
// @ts-ignore
.flatMap((x: any) => x)
.map(({ subName, localName, value }: IMethodsPath) => {
const __meta = {
subName: subName,
exportName: path.__meta && path.__meta.exportName,
};
return makeProxy(value, { __meta });
})
);
}
/** /**
* Given an AST, this function tries to find the exported component definition. * Given an AST, this function tries to find the exported component definition.
* *
@ -99,6 +276,8 @@ function getDefinition(definition: any): any {
*/ */
export default function findAllExportedComponentDefinition(ast: any) { export default function findAllExportedComponentDefinition(ast: any) {
const components: any[] = []; const components: any[] = [];
const cache: ICache = {};
let programScope: any;
function exportDeclaration(path: any) { function exportDeclaration(path: any) {
const definitions = resolveExportDeclaration(path) const definitions = resolveExportDeclaration(path)
@ -106,7 +285,7 @@ export default function findAllExportedComponentDefinition(ast: any) {
if (isComponentDefinition(definition)) { if (isComponentDefinition(definition)) {
acc.push(definition); acc.push(definition);
} else { } else {
definition = getDefinition(definition); definition = getDefinition(definition, cache);
if (!Array.isArray(definition)) { if (!Array.isArray(definition)) {
definition = [definition]; definition = [definition];
} }
@ -118,7 +297,11 @@ export default function findAllExportedComponentDefinition(ast: any) {
} }
return acc; return acc;
}, []) }, [])
.map((definition: any) => resolveDefinition(definition)); .map((definition: any) => {
const { __meta: meta } = definition;
const def = resolveDefinition(definition);
return makeProxy(def, { __meta: meta });
});
if (definitions.length === 0) { if (definitions.length === 0) {
return false; return false;
@ -132,6 +315,10 @@ export default function findAllExportedComponentDefinition(ast: any) {
} }
visit(ast, { visit(ast, {
visitProgram: function(path) {
programScope = path.scope;
return this.traverse(path);
},
visitFunctionDeclaration: ignore, visitFunctionDeclaration: ignore,
visitFunctionExpression: ignore, visitFunctionExpression: ignore,
visitClassDeclaration: ignore, visitClassDeclaration: ignore,
@ -149,9 +336,7 @@ export default function findAllExportedComponentDefinition(ast: any) {
visitExportNamedDeclaration: exportDeclaration, visitExportNamedDeclaration: exportDeclaration,
visitExportDefaultDeclaration: exportDeclaration, visitExportDefaultDeclaration: exportDeclaration,
visitExportAllDeclaration: function(path) { visitExportAllDeclaration: function(path) {
components.push( components.push(...resolveImport(path, findAllExportedComponentDefinition));
...resolveImport(path, findAllExportedComponentDefinition),
);
return false; return false;
}, },
@ -161,20 +346,44 @@ export default function findAllExportedComponentDefinition(ast: any) {
if (!isExportsOrModuleAssignment(path)) { if (!isExportsOrModuleAssignment(path)) {
return false; return false;
} }
const arr = expressionTo.Array(path.get('left'));
const meta: any = {
exportName: arr[1] === 'exports' ? 'default' : arr[1],
};
// Resolve the value of the right hand side. It should resolve to a call // Resolve the value of the right hand side. It should resolve to a call
// expression, something like React.createClass // expression, something like React.createClass
path = resolveToValue(path.get('right')); path = resolveToValue(path.get('right'));
if (!isComponentDefinition(path)) { if (!isComponentDefinition(path)) {
path = getDefinition(path); path = getDefinition(path, cache);
} }
const definition = resolveDefinition(path); let definitions = resolveDefinition(path);
if (!Array.isArray(definitions)) {
definitions = [definitions];
}
definitions.forEach((definition: any) => {
if (definition && components.indexOf(definition) === -1) { if (definition && components.indexOf(definition) === -1) {
// if (definition.__meta) {
definition = makeProxy(definition, {
__meta: meta,
});
// }
components.push(definition); components.push(definition);
} }
});
return false; return false;
}, },
}); });
return components; const result = components.reduce((acc, item) => {
let subModuleDefinitions = [];
subModuleDefinitions = getSubComponents(item, programScope, cache);
return [...acc, item, ...subModuleDefinitions];
}, []);
const res = uniqBy(result, (x: any) => {
return `${x.__meta.exportName}/${x.__meta.subName}`;
});
return res;
} }

View File

@ -0,0 +1,13 @@
import { namedTypes as t } from 'ast-types';
const { getPropertyName } = require('react-docgen').utils;
const reactStaticMembers = ['propTypes', 'defaultProps', 'contextTypes'];
export default function isReactComponentStaticMember(methodPath: any) {
let name;
if (t.MemberExpression.check(methodPath.node)) {
name = methodPath.node.property.name;
} else {
name = getPropertyName(methodPath);
}
return !!name && reactStaticMembers.indexOf(name) !== -1;
}

View File

@ -0,0 +1,14 @@
import { namedTypes as t } from 'ast-types';
import isReactComponentStaticMember from './isReactComponentStaticMember';
const { isReactComponentMethod } = require('react-docgen').utils;
/**
* judge if static method
*/
function isStaticMethod(path: any) {
const isProbablyStaticMethod = t.ClassProperty.check(path.node) && path.node.static === true;
return isProbablyStaticMethod && !isReactComponentStaticMember(path) && !isReactComponentMethod(path);
}
export default isStaticMethod;

View File

@ -0,0 +1,53 @@
import { namedTypes as t } from 'ast-types';
import makeProxy from '../utils/makeProxy';
import getName from '../utils/getName';
export default function resolveExportDeclaration(path: any) {
const definitions = [];
if (path.node.default || t.ExportDefaultDeclaration.check(path.node)) {
const def = path.get('declaration');
const meta: { [name: string]: string } = {
exportName: 'default',
localName: getName(def),
};
definitions.push(makeProxy(def, { __meta: meta }));
} else if (path.node.declaration) {
if (t.VariableDeclaration.check(path.node.declaration)) {
path.get('declaration', 'declarations').each((declarator: any) => {
definitions.push(
makeProxy(declarator, {
__meta: {
exportName: declarator.get('id').node.name,
},
}),
);
});
} else {
const def = path.get('declaration');
definitions.push(
makeProxy(def, {
__meta: {
exportName: 'default',
},
}),
);
}
} else if (path.node.specifiers) {
path.get('specifiers').each((specifier: any) => {
const def = specifier.node.id ? specifier.get('id') : specifier.get('local');
const exportName = specifier.get('exported').node.name;
const localName = def.get('local').node.name;
definitions.push(
makeProxy(def, {
__meta: {
exportName: exportName,
localName: localName,
},
}),
);
});
}
return definitions;
}

View File

@ -1,21 +1,10 @@
import { namedTypes as t } from 'ast-types'; import { namedTypes as t } from 'ast-types';
import fs from 'fs'; import fs from 'fs';
import p from 'path'; import p from 'path';
import getRoot from '../utils/getRoot';
function getRoot(node: any) { export function isImportLike(node: any) {
let root = node.parent; return t.ImportDeclaration.check(node) || t.ExportAllDeclaration.check(node) || t.ExportNamedDeclaration.check(node);
while (root.parent) {
root = root.parent;
}
return root.node;
}
function isImportLike(node: any) {
return (
t.ImportDeclaration.check(node) ||
t.ExportAllDeclaration.check(node) ||
t.ExportNamedDeclaration.check(node)
);
} }
function getPath(path: any, name: any) { function getPath(path: any, name: any) {
@ -27,7 +16,7 @@ function getPath(path: any, name: any) {
if (fs.existsSync(p.resolve(__path, name))) { if (fs.existsSync(p.resolve(__path, name))) {
name = name + '/index'; name = name + '/index';
} }
const suffix = suffixes.find(suf => { const suffix = suffixes.find((suf) => {
return fs.existsSync(p.resolve(__path, name + suf)); return fs.existsSync(p.resolve(__path, name + suf));
}); });
if (!suffix) return; if (!suffix) return;
@ -35,9 +24,12 @@ function getPath(path: any, name: any) {
} }
const buildParser = require('react-docgen/dist/babelParser').default; const buildParser = require('react-docgen/dist/babelParser').default;
const parser = buildParser();
const suffixes = ['.js', '.jsx', '.ts', '.tsx']; const suffixes = ['.js', '.jsx', '.ts', '.tsx'];
const cache: {
[name: string]: any;
} = {};
export default function resolveImport(path: any, callback: any) { export default function resolveImport(path: any, callback: any) {
let name; let name;
if (path.name === 'local') { if (path.name === 'local') {
@ -50,11 +42,19 @@ export default function resolveImport(path: any, callback: any) {
if (name) { if (name) {
const __path = getPath(path, name); const __path = getPath(path, name);
if (!__path) return path; if (!__path) return path;
let ast;
if (!cache[__path]) {
const fileContent = fs.readFileSync(__path, 'utf8'); const fileContent = fs.readFileSync(__path, 'utf8');
const ast = parser.parse(fileContent); const parser = buildParser({ filename: __path });
ast = parser.parse(fileContent);
ast.__src = fileContent; ast.__src = fileContent;
ast.__path = __path; ast.__path = __path;
return callback(ast); cache[__path] = ast;
} else {
ast = cache[__path];
}
return callback(ast, __path);
} }
return path; return path;
} }

View File

@ -1,9 +1,9 @@
export function transformType(type: any) { export function transformType(itemType: any) {
if (typeof type === 'string') return type; if (typeof itemType === 'string') return itemType;
const { name, elements, value = elements, computed, required } = type; const { name, elements, value = elements, computed, required, type } = itemType;
if (!value && !required) { // if (!value && !required && !type) {
return name; // return name;
} // }
if (computed !== undefined && value) { if (computed !== undefined && value) {
return eval(value); return eval(value);
} }
@ -21,6 +21,7 @@ export function transformType(type: any) {
case 'func': case 'func':
case 'symbol': case 'symbol':
case 'object': case 'object':
case 'null':
break; break;
case 'literal': case 'literal':
return eval(value); return eval(value);
@ -36,13 +37,24 @@ export function transformType(type: any) {
case 'boolean': case 'boolean':
result.type = 'bool'; result.type = 'bool';
break; break;
case 'Array': { case 'Function':
result.type = 'func';
break;
case 'unknown':
result.type = 'any';
break;
case 'Array':
case 'arrayOf': {
result.type = 'arrayOf'; result.type = 'arrayOf';
const v = transformType(value[0]); const v = transformType(value[0]);
if (typeof v.type === 'string') result.value = v.type; if (typeof v.type === 'string') result.value = v.type;
break; break;
} }
case 'signature': { case 'signature': {
if (typeof type === 'string') {
result.type = type;
break;
}
result.type = 'shape'; result.type = 'shape';
const { const {
signature: { properties }, signature: { properties },
@ -103,22 +115,28 @@ export function transformType(type: any) {
result.value = name; result.value = name;
break; break;
} }
if (Object.keys(result).length === 1) {
return result.type;
}
return result; return result;
} }
export function transformItem(name: string, item: any) { export function transformItem(name: string, item: any) {
const { description, flowType, type = flowType, required, defaultValue } = item; const { description, flowType, tsType, type = tsType || flowType, required, defaultValue } = item;
const result: any = { const result: any = {
name, name,
propType: transformType({ };
if (type) {
result.propType = transformType({
...type, ...type,
required: !!required, required: !!required,
}), });
}; }
if (description) { if (description) {
result.description = description; result.description = description;
} }
if (defaultValue) { if (defaultValue !== undefined) {
try { try {
const value = eval(defaultValue.value); const value = eval(defaultValue.value);
result.defaultValue = value; result.defaultValue = value;
@ -127,6 +145,5 @@ export function transformItem(name: string, item: any) {
if (result.propType === undefined) { if (result.propType === undefined) {
delete result.propType; delete result.propType;
} }
return result; return result;
} }

View File

@ -0,0 +1,18 @@
export interface ICache {
[name: string]: any;
}
const cache: ICache = {};
export function set(scope: string, name: string, value: any) {
cache[scope] = cache[scope] || {};
cache[scope][name] = value;
}
export function get(scope: string, name: string) {
return (cache[scope] || {})[name];
}
export function has(scope: string, name: string) {
return cache[scope] && cache[scope].hasOwnProperty(name);
}

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import { namedTypes as t } from 'ast-types';
const supportedUtilityTypes = new Set(['$Exact', '$ReadOnly']);
/**
* See `supportedUtilityTypes` for which types are supported and
* https://flow.org/en/docs/types/utilities/ for which types are available.
*/
function isSupportedUtilityType(path: any) {
if (t.GenericTypeAnnotation.check(path.node)) {
const idPath = path.get('id');
return !!idPath && supportedUtilityTypes.has(idPath.node.name);
}
return false;
}
export { isSupportedUtilityType };
function isReactUtilityType(path: any) {
if (t.TSTypeReference.check(path.node)) {
const objName = path.get('typeName', 'left').node.name;
if (objName === 'React') {
return true;
}
}
return false;
}
/**
* Unwraps well known utility types. For example:
*
* $ReadOnly<T> => T
*/
function unwrapUtilityType(path: any) {
while (isSupportedUtilityType(path) || isReactUtilityType(path)) {
path = path.get('typeParameters', 'params', 0);
}
return path;
}
export { unwrapUtilityType };

View File

@ -0,0 +1,119 @@
import { namedTypes as t } from 'ast-types';
const {
isReactComponentClass,
isReactForwardRefCall,
getTypeAnnotation,
resolveToValue,
getMemberValuePath,
} = require('react-docgen').utils;
import resolveGenericTypeAnnotation from './resolveGenericTypeAnnotation';
const getTypeParameters = require('react-docgen/dist/utils/getTypeParameters').default;
function getStatelessPropsPath(componentDefinition: any) {
const value = resolveToValue(componentDefinition);
if (isReactForwardRefCall(value)) {
const inner = resolveToValue(value.get('arguments', 0));
return inner.get('params', 0);
}
if (t.VariableDeclarator.check(componentDefinition.parent.node)) {
const id = componentDefinition.parent.get('id');
if (id.node.typeAnnotation) {
return id;
}
}
return value.get('params', 0);
}
/**
* Given an React component (stateless or class) tries to find the
* flow type for the props. If not found or not one of the supported
* component types returns null.
*/
export default (path: any) => {
let typePath = null;
if (isReactComponentClass(path)) {
const superTypes = path.get('superTypeParameters');
if (superTypes.value) {
const params = superTypes.get('params');
if (params.value.length === 3) {
typePath = params.get(1);
} else {
typePath = params.get(0);
}
} else {
const propsMemberPath = getMemberValuePath(path, 'props');
if (!propsMemberPath) {
return null;
}
typePath = getTypeAnnotation(propsMemberPath.parentPath);
}
return typePath;
}
const propsParam = getStatelessPropsPath(path);
if (propsParam) {
typePath = getTypeAnnotation(propsParam);
}
return typePath;
};
function applyToFlowTypeProperties(documentation: any, path: any, callback: any, typeParams?: any) {
if (path.node.properties) {
path.get('properties').each((propertyPath: any) => callback(propertyPath, typeParams));
} else if (path.node.members) {
path.get('members').each((propertyPath: any) => callback(propertyPath, typeParams));
} else if (path.node.type === 'InterfaceDeclaration') {
if (path.node.extends) {
applyExtends(documentation, path, callback, typeParams);
}
path.get('body', 'properties').each((propertyPath: any) => callback(propertyPath, typeParams));
} else if (path.node.type === 'TSInterfaceDeclaration') {
if (path.node.extends) {
applyExtends(documentation, path, callback, typeParams);
}
path.get('body', 'body').each((propertyPath: any) => callback(propertyPath, typeParams));
} else if (path.node.type === 'IntersectionTypeAnnotation' || path.node.type === 'TSIntersectionType') {
path
.get('types')
.each((typesPath: any) => applyToFlowTypeProperties(documentation, typesPath, callback, typeParams));
} else if (path.node.type !== 'UnionTypeAnnotation') {
// The react-docgen output format does not currently allow
// for the expression of union types
const typePath = resolveGenericTypeAnnotation(path);
if (typePath) {
applyToFlowTypeProperties(documentation, typePath, callback, typeParams);
}
}
}
function applyExtends(documentation: any, path: any, callback: any, typeParams: any) {
path.get('extends').each((extendsPath: any) => {
const resolvedPath = resolveGenericTypeAnnotation(extendsPath);
if (resolvedPath) {
if (resolvedPath.node.typeParameters && extendsPath.node.typeParameters) {
typeParams = getTypeParameters(
resolvedPath.get('typeParameters'),
extendsPath.get('typeParameters'),
typeParams,
);
}
applyToFlowTypeProperties(documentation, resolvedPath, callback, typeParams);
} else {
const id = extendsPath.node.id || extendsPath.node.typeName || extendsPath.node.expression;
if (id && id.type === 'Identifier') {
documentation.addComposes(id.name);
}
}
});
}
export { applyToFlowTypeProperties };

View File

@ -0,0 +1,164 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import getTSType from './getTSType';
const { namedTypes: t } = require('ast-types');
const {
resolveToValue,
getFlowType,
getParameterName,
getPropertyName,
getTypeAnnotation,
} = require('react-docgen').utils;
const { getDocblock } = require('react-docgen/dist/utils/docblock');
function getMethodFunctionExpression(methodPath) {
if (t.AssignmentExpression.check(methodPath.node)) {
return resolveToValue(methodPath.get('right'));
}
// Otherwise this is a method/property node
return methodPath.get('value');
}
function getMethodParamsDoc(methodPath) {
const params = [];
const functionExpression = getMethodFunctionExpression(methodPath);
// Extract param flow types.
functionExpression.get('params').each((paramPath) => {
let type = null;
const typePath = getTypeAnnotation(paramPath);
if (typePath && t.Flow.check(typePath.node)) {
type = getFlowType(typePath);
if (t.GenericTypeAnnotation.check(typePath.node)) {
type.alias = typePath.node.id.name;
}
} else if (typePath) {
type = getTSType(typePath);
if (t.TSTypeReference.check(typePath.node)) {
type.alias = typePath.node.typeName.name;
}
}
const param = {
name: getParameterName(paramPath),
optional: paramPath.node.optional,
type,
};
params.push(param);
});
return params;
}
// Extract flow return type.
function getMethodReturnDoc(methodPath) {
const functionExpression = getMethodFunctionExpression(methodPath);
if (functionExpression.node.returnType) {
const returnType = getTypeAnnotation(functionExpression.get('returnType'));
if (returnType && t.Flow.check(returnType.node)) {
return { type: getFlowType(returnType) };
}
if (returnType) {
return { type: getTSType(returnType) };
}
}
return null;
}
function getMethodModifiers(methodPath) {
if (t.AssignmentExpression.check(methodPath.node)) {
return ['static'];
}
// Otherwise this is a method/property node
const modifiers = [];
if (methodPath.node.static) {
modifiers.push('static');
}
if (methodPath.node.kind === 'get' || methodPath.node.kind === 'set') {
modifiers.push(methodPath.node.kind);
}
const functionExpression = methodPath.get('value').node;
if (functionExpression.generator) {
modifiers.push('generator');
}
if (functionExpression.async) {
modifiers.push('async');
}
return modifiers;
}
function getMethodName(methodPath) {
if (t.AssignmentExpression.check(methodPath.node) && t.MemberExpression.check(methodPath.node.left)) {
const { left } = methodPath.node;
const { property } = left;
if (!left.computed) {
return property.name;
}
if (t.Literal.check(property)) {
return String(property.value);
}
return null;
}
return getPropertyName(methodPath);
}
function getMethodAccessibility(methodPath) {
if (t.AssignmentExpression.check(methodPath.node)) {
return null;
}
// Otherwise this is a method/property node
return methodPath.node.accessibility;
}
function getMethodDocblock(methodPath) {
if (t.AssignmentExpression.check(methodPath.node)) {
let path = methodPath;
do {
path = path.parent;
} while (path && !t.ExpressionStatement.check(path.node));
if (path) {
return getDocblock(path);
}
return null;
}
// Otherwise this is a method/property node
return getDocblock(methodPath);
}
// Gets the documentation object for a component method.
// Component methods may be represented as class/object method/property nodes
// or as assignment expresions of the form `Component.foo = function() {}`
export default function getMethodDocumentation(methodPath) {
if (getMethodAccessibility(methodPath) === 'private') {
return null;
}
const name = getMethodName(methodPath);
if (!name) return null;
return {
name,
docblock: getMethodDocblock(methodPath),
modifiers: getMethodModifiers(methodPath),
params: getMethodParamsDoc(methodPath),
returns: getMethodReturnDoc(methodPath),
};
}

View File

@ -0,0 +1,14 @@
import { namedTypes as t } from 'ast-types';
export default function(def: any) {
let name = '';
if (def.node.name) {
name = def.node.name;
// hoc
} else if (t.CallExpression.check(def.node)) {
if (def.node.arguments && def.node.arguments.length && t.Identifier.check(def.get('arguments', 0).node))
name = def.get('arguments', 0).node.name;
}
return name;
}

View File

@ -0,0 +1,7 @@
export default function getRoot(path: any) {
let root = path.parent;
while (root.parent) {
root = root.parent;
}
return root.node;
}

View File

@ -0,0 +1,355 @@
/* eslint-disable */
const { namedTypes: t } = require('ast-types');
const {
getPropertyName,
printValue,
resolveToValue,
getTypeAnnotation,
resolveObjectToNameArray,
getTypeParameters,
} = require('react-docgen').utils;
const tsTypes = {
TSAnyKeyword: 'any',
TSBooleanKeyword: 'boolean',
TSUnknownKeyword: 'unknown',
TSNeverKeyword: 'never',
TSNullKeyword: 'null',
TSUndefinedKeyword: 'undefined',
TSNumberKeyword: 'number',
TSStringKeyword: 'string',
TSSymbolKeyword: 'symbol',
TSThisType: 'this',
TSObjectKeyword: 'object',
TSVoidKeyword: 'void',
};
const namedTypes = {
TSArrayType: handleTSArrayType,
TSTypeReference: handleTSTypeReference,
TSTypeLiteral: handleTSTypeLiteral,
TSInterfaceDeclaration: handleTSInterfaceDeclaration,
TSUnionType: handleTSUnionType,
TSFunctionType: handleTSFunctionType,
TSIntersectionType: handleTSIntersectionType,
TSMappedType: handleTSMappedType,
TSTupleType: handleTSTupleType,
TSTypeQuery: handleTSTypeQuery,
TSTypeOperator: handleTSTypeOperator,
TSIndexedAccessType: handleTSIndexedAccessType,
};
function handleTSArrayType(path, typeParams) {
return {
name: 'Array',
elements: [getTSTypeWithResolvedTypes(path.get('elementType'), typeParams)],
raw: printValue(path),
};
}
function handleTSTypeReference(path, typeParams) {
let type;
if (t.TSQualifiedName.check(path.node.typeName)) {
const typeName = path.get('typeName');
if (typeName.node.left.name === 'React') {
type = {
name: `${typeName.node.left.name}${typeName.node.right.name}`,
raw: printValue(typeName),
};
} else {
type = { name: printValue(typeName).replace(/<.*>$/, '') };
}
} else {
type = { name: path.node.typeName.name };
}
const resolvedPath = (typeParams && typeParams[type.name]) || resolveToValue(path.get('typeName'));
if (path.node.typeParameters && resolvedPath.node.typeParameters) {
typeParams = getTypeParameters(resolvedPath.get('typeParameters'), path.get('typeParameters'), typeParams);
}
if (typeParams && typeParams[type.name]) {
type = getTSTypeWithResolvedTypes(resolvedPath);
}
if (resolvedPath && resolvedPath.node.typeAnnotation) {
type = getTSTypeWithResolvedTypes(resolvedPath.get('typeAnnotation'), typeParams);
} else if (path.node.typeParameters) {
const params = path.get('typeParameters').get('params');
type = {
...type,
elements: params.map((param) => getTSTypeWithResolvedTypes(param, typeParams)),
raw: printValue(path),
};
}
return type;
}
function getTSTypeWithRequirements(path, typeParams) {
const type = getTSTypeWithResolvedTypes(path, typeParams);
type.required = !path.parentPath.node.optional;
return type;
}
function handleTSTypeLiteral(path, typeParams) {
const type = {
name: 'signature',
type: 'object',
raw: printValue(path),
signature: { properties: [] },
};
path.get('members').each((param) => {
if (t.TSPropertySignature.check(param.node) || t.TSMethodSignature.check(param.node)) {
const propName = getPropertyName(param);
if (!propName) {
return;
}
type.signature.properties.push({
key: propName,
value: getTSTypeWithRequirements(param.get('typeAnnotation'), typeParams),
});
} else if (t.TSCallSignatureDeclaration.check(param.node)) {
type.signature.constructor = handleTSFunctionType(param, typeParams);
} else if (t.TSIndexSignature.check(param.node)) {
type.signature.properties.push({
key: getTSTypeWithResolvedTypes(
param
.get('parameters')
.get(0)
.get('typeAnnotation'),
typeParams,
),
value: getTSTypeWithRequirements(param.get('typeAnnotation'), typeParams),
});
}
});
return type;
}
function handleTSInterfaceDeclaration(path) {
// Interfaces are handled like references which would be documented separately,
// rather than inlined like type aliases.
return {
name: path.node.id.name,
};
}
function handleTSUnionType(path, typeParams) {
return {
name: 'union',
raw: printValue(path),
elements: path.get('types').map((subType) => getTSTypeWithResolvedTypes(subType, typeParams)),
};
}
function handleTSIntersectionType(path, typeParams) {
return {
name: 'intersection',
raw: printValue(path),
elements: path.get('types').map((subType) => getTSTypeWithResolvedTypes(subType, typeParams)),
};
}
function handleTSMappedType(path, typeParams) {
const key = getTSTypeWithResolvedTypes(path.get('typeParameter').get('constraint'), typeParams);
key.required = !path.node.optional;
return {
name: 'signature',
type: 'object',
raw: printValue(path),
signature: {
properties: [
{
key,
value: getTSTypeWithResolvedTypes(path.get('typeAnnotation'), typeParams),
},
],
},
};
}
function handleTSFunctionType(path, typeParams) {
const type = {
name: 'signature',
type: 'function',
raw: printValue(path),
signature: {
arguments: [],
return: getTSTypeWithResolvedTypes(path.get('typeAnnotation'), typeParams),
},
};
path.get('parameters').each((param) => {
const typeAnnotation = getTypeAnnotation(param);
const arg = {
name: param.node.name || '',
type: typeAnnotation ? getTSTypeWithResolvedTypes(typeAnnotation, typeParams) : undefined,
};
if (param.node.name === 'this') {
type.signature.this = arg.type;
return;
}
if (param.node.type === 'RestElement') {
arg.name = param.node.argument.name;
arg.rest = true;
}
type.signature.arguments.push(arg);
});
return type;
}
function handleTSTupleType(path, typeParams) {
const type = {
name: 'tuple',
raw: printValue(path),
elements: [],
};
path.get('elementTypes').each((param) => {
type.elements.push(getTSTypeWithResolvedTypes(param, typeParams));
});
return type;
}
function handleTSTypeQuery(path, typeParams) {
const resolvedPath = resolveToValue(path.get('exprName'));
if (resolvedPath && resolvedPath.node.typeAnnotation) {
return getTSTypeWithResolvedTypes(resolvedPath.get('typeAnnotation'), typeParams);
}
return { name: path.node.exprName.name };
}
function handleTSTypeOperator(path) {
if (path.node.operator !== 'keyof') {
return null;
}
let value = path.get('typeAnnotation');
if (t.TSTypeQuery.check(value.node)) {
value = value.get('exprName');
} else if (value.node.id) {
value = value.get('id');
}
const resolvedPath = resolveToValue(value);
if (resolvedPath && (t.ObjectExpression.check(resolvedPath.node) || t.TSTypeLiteral.check(resolvedPath.node))) {
const keys = resolveObjectToNameArray(resolvedPath, true);
if (keys) {
return {
name: 'union',
raw: printValue(path),
elements: keys.map((key) => ({ name: 'literal', value: key })),
};
}
}
}
function handleTSIndexedAccessType(path, typeParams) {
// eslint-disable-next-line no-undef
const objectType = getTSTypeWithResolvedTypes(path.get('objectType'), typeParams);
// eslint-disable-next-line no-undef
const indexType = getTSTypeWithResolvedTypes(path.get('indexType'), typeParams);
// We only get the signature if the objectType is a type (vs interface)
if (!objectType.signature) {
return {
name: `${objectType.name}[${(indexType.value || indexType.name).toString()}]`,
raw: printValue(path),
};
}
const resolvedType = objectType.signature.properties.find(
(p) =>
// indexType.value = "'foo'"
p.key === indexType.value.replace(/['"]+/g, ''),
);
if (!resolvedType) {
return { name: 'unknown' };
}
return {
name: resolvedType.value.name,
raw: printValue(path),
};
}
let visitedTypes = {};
function getTSTypeWithResolvedTypes(path, typeParams) {
if (t.TSTypeAnnotation.check(path.node)) {
path = path.get('typeAnnotation');
}
const { node } = path;
let type;
const isTypeAlias = t.TSTypeAliasDeclaration.check(path.parentPath.node);
// When we see a typealias mark it as visited so that the next
// call of this function does not run into an endless loop
if (isTypeAlias) {
if (visitedTypes[path.parentPath.node.id.name] === true) {
// if we are currently visiting this node then just return the name
// as we are starting to endless loop
return { name: path.parentPath.node.id.name };
}
if (typeof visitedTypes[path.parentPath.node.id.name] === 'object') {
// if we already resolved the type simple return it
return visitedTypes[path.parentPath.node.id.name];
}
// mark the type as visited
visitedTypes[path.parentPath.node.id.name] = true;
}
if (node.type in tsTypes) {
type = { name: tsTypes[node.type] };
} else if (t.TSLiteralType.check(node)) {
type = {
name: 'literal',
value: node.literal.raw || `${node.literal.value}`,
};
} else if (node.type in namedTypes) {
type = namedTypes[node.type](path, typeParams);
}
if (!type) {
type = { name: 'unknown' };
}
if (isTypeAlias) {
// mark the type as unvisited so that further calls can resolve the type again
visitedTypes[path.parentPath.node.id.name] = type;
}
return type;
}
/**
* Tries to identify the typescript type by inspecting the path for known
* typescript type names. This method doesn't check whether the found type is actually
* existing. It simply assumes that a match is always valid.
*
* If there is no match, "unknown" is returned.
*/
export default function getTSType(path, typeParamMap) {
// Empty visited types before an after run
// Before: in case the detection threw and we rerun again
// After: cleanup memory after we are done here
visitedTypes = {};
const type = getTSTypeWithResolvedTypes(path, typeParamMap);
visitedTypes = {};
return type;
}

View File

@ -0,0 +1,19 @@
function makeProxy(target: { [name: string]: any }, meta: any = {}): any {
if (target.__isProxy) {
const value = target.__getRaw();
const rawMeta = target.__getMeta();
return makeProxy(value, Object.assign({}, rawMeta, meta));
}
return new Proxy(target, {
get: (obj, prop: string | number) => {
if (prop === '__isProxy') return true;
if (prop === '__getRaw') return () => target;
if (prop === '__getMeta') return () => meta;
return meta.hasOwnProperty(prop) ? meta[prop] : obj[prop];
// return obj[prop];
},
has: (obj, prop) => obj.hasOwnProperty(prop) || meta.hasOwnProperty(prop),
});
}
export default makeProxy;

View File

@ -0,0 +1,57 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import { namedTypes as t } from 'ast-types';
const { resolveToValue } = require('react-docgen').utils;
const { unwrapUtilityType } = require('./flowUtilityTypes');
const isUnreachableFlowType = require('react-docgen/dist/utils/isUnreachableFlowType').default;
function tryResolveGenericTypeAnnotation(path: any): any {
let typePath = unwrapUtilityType(path);
let idPath;
if (typePath.node.id) {
idPath = typePath.get('id');
} else if (t.TSTypeReference.check(typePath.node)) {
idPath = typePath.get('typeName');
} else if (t.TSExpressionWithTypeArguments.check(typePath.node)) {
idPath = typePath.get('expression');
}
if (idPath) {
typePath = resolveToValue(idPath);
if (isUnreachableFlowType(typePath)) {
return;
}
if (t.TypeAlias.check(typePath.node)) {
return tryResolveGenericTypeAnnotation(typePath.get('right'));
} else if (t.TSTypeAliasDeclaration.check(typePath.node)) {
return tryResolveGenericTypeAnnotation(typePath.get('typeAnnotation'));
}
return typePath;
}
return typePath;
}
/**
* Given an React component (stateless or class) tries to find the
* flow type for the props. If not found or not one of the supported
* component types returns undefined.
*/
export default function resolveGenericTypeAnnotation(path: any) {
if (!path) return;
const typePath = tryResolveGenericTypeAnnotation(path);
if (!typePath || typePath === path) return;
return typePath;
}

View File

@ -15,35 +15,8 @@ export interface IMaterialParsedModel {
// filePath: string; // filePath: string;
componentName: string; componentName: string;
props?: PropsSection['props']; props?: PropsSection['props'];
// componentNames: { meta?: {
// exportedName: string; exportName?: string;
// localName: string; subName?: string;
// source?: string; };
// }[];
// importModules: {
// importDefaultName?: string;
// importName?: string;
// localName?: string;
// source: string;
// }[];
// exportModules: {
// exportedName: string;
// localName: string;
// source?: string;
// }[];
// /**
// * 子模块形如Demo.SubModule = value; 或者 Demo.SubModule.Sub = subValue;
// */
// subModules: {
// objectName: string[];
// propertyName: string;
// value?: string;
// // value 是否对应匿名函数
// isValueAnonymousFunc: boolean;
// }[];
// propsTypes: IPropTypes;
// propsDefaults: {
// name: string;
// defaultValue: any;
// }[];
} }

View File

@ -0,0 +1,14 @@
import { NodePath, Path } from 'ast-types';
export interface IFileMeta {
src: string;
path: string;
exports: IDefinitionMeta[];
}
export interface IDefinitionMeta {
subDefinitions: IDefinitionMeta[];
nodePath: typeof Path;
exportName: string;
id: string;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,70 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`antd exports modules correctly 1`] = `
Array [
"Affix",
"Anchor",
"AutoComplete",
"Alert",
"Avatar",
"BackTop",
"Badge",
"Breadcrumb",
"Button",
"Calendar",
"Card",
"Collapse",
"Carousel",
"Cascader",
"Checkbox",
"Col",
"Comment",
"ConfigProvider",
"DatePicker",
"Descriptions",
"Divider",
"Dropdown",
"Drawer",
"Empty",
"Form",
"Grid",
"Input",
"InputNumber",
"Layout",
"List",
"message",
"Menu",
"Mentions",
"Modal",
"Statistic",
"notification",
"PageHeader",
"Pagination",
"Popconfirm",
"Popover",
"Progress",
"Radio",
"Rate",
"Result",
"Row",
"Select",
"Skeleton",
"Slider",
"Space",
"Spin",
"Steps",
"Switch",
"Table",
"Transfer",
"Tree",
"TreeSelect",
"Tabs",
"Tag",
"TimePicker",
"Timeline",
"Tooltip",
"Typography",
"Upload",
"version",
]
`;

View File

@ -0,0 +1,21 @@
const OLD_NODE_ENV = process.env.NODE_ENV;
process.env.NODE_ENV = 'development';
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const antd = require('..');
describe('antd', () => {
afterAll(() => {
process.env.NODE_ENV = OLD_NODE_ENV;
});
it('exports modules correctly', () => {
expect(Object.keys(antd)).toMatchSnapshot();
});
it('should hint when import all components in dev mode', () => {
expect(warnSpy).toHaveBeenCalledWith(
'You are using a whole package of antd, please use https://www.npmjs.com/package/babel-plugin-import to reduce app bundle size.',
);
warnSpy.mockRestore();
});
});

View File

@ -0,0 +1,71 @@
const __NULL__ = { notExist: true };
type ElementType<P> = {
prototype: P;
};
export function spyElementPrototypes<P extends {}>(Element: ElementType<P>, properties: P) {
const propNames = Object.keys(properties);
const originDescriptors = {};
propNames.forEach(propName => {
const originDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, propName);
originDescriptors[propName] = originDescriptor || __NULL__;
const spyProp = properties[propName];
if (typeof spyProp === 'function') {
// If is a function
Element.prototype[propName] = function spyFunc(...args) {
return spyProp.call(this, originDescriptor, ...args);
};
} else {
// Otherwise tread as a property
Object.defineProperty(Element.prototype, propName, {
...spyProp,
set(value) {
if (spyProp.set) {
return spyProp.set.call(this, originDescriptor, value);
}
return originDescriptor.set(value);
},
get() {
if (spyProp.get) {
return spyProp.get.call(this, originDescriptor);
}
return originDescriptor.get();
},
});
}
});
return {
mockRestore() {
propNames.forEach(propName => {
const originDescriptor = originDescriptors[propName];
if (originDescriptor === __NULL__) {
delete Element.prototype[propName];
} else if (typeof originDescriptor === 'function') {
Element.prototype[propName] = originDescriptor;
} else {
Object.defineProperty(Element.prototype, propName, originDescriptor);
}
});
},
};
}
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
}[keyof T] &
string;
export function spyElementPrototype<P extends {}, K extends FunctionPropertyNames<P>>(
Element: ElementType<P>,
propName: K,
property: P[K],
) {
return spyElementPrototypes(Element, {
[propName]: property,
});
}

View File

@ -0,0 +1,13 @@
import { easeInOutCubic } from '../easings';
describe('Test easings', () => {
it('easeInOutCubic return value', () => {
const nums = [];
// eslint-disable-next-line no-plusplus
for (let index = 0; index < 5; index++) {
nums.push(easeInOutCubic(index, 1, 5, 4));
}
expect(nums).toEqual([1, 1.25, 3, 4.75, 5]);
});
});

View File

@ -0,0 +1,11 @@
/**
* @jest-environment node
*/
import getScroll from '../getScroll';
describe('getScroll', () => {
it('getScroll return 0 in node envioronment', async () => {
expect(getScroll(null, true)).toBe(0);
expect(getScroll(null, false)).toBe(0);
});
});

View File

@ -0,0 +1,49 @@
import scrollTo from '../scrollTo';
import { sleep } from '../../../tests/utils';
describe('Test ScrollTo function', () => {
let dateNowMock;
beforeEach(() => {
dateNowMock = jest
.spyOn(Date, 'now')
.mockImplementationOnce(() => 0)
.mockImplementationOnce(() => 1000);
});
afterEach(() => {
dateNowMock.mockRestore();
});
it('test scrollTo', async () => {
const scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation((x, y) => {
window.scrollY = y;
window.pageYOffset = y;
});
scrollTo(1000);
await sleep(20);
expect(window.pageYOffset).toBe(1000);
scrollToSpy.mockRestore();
});
it('test callback - option', async () => {
const cbMock = jest.fn();
scrollTo(1000, {
callback: cbMock,
});
await sleep(20);
expect(cbMock).toHaveBeenCalledTimes(1);
});
it('test getContainer - option', async () => {
const div = document.createElement('div');
scrollTo(1000, {
getContainer: () => div,
});
await sleep(20);
expect(div.scrollTop).toBe(1000);
});
});

View File

@ -0,0 +1,8 @@
import UnreachableException from '../unreachableException';
describe('UnreachableException', () => {
it('error thrown matches snapshot', () => {
const exception = new UnreachableException('some value');
expect(exception.message).toMatchInlineSnapshot(`"unreachable case: \\"some value\\""`);
});
});

View File

@ -0,0 +1,206 @@
import raf from 'raf';
import React from 'react';
import { mount } from 'enzyme';
import KeyCode from 'rc-util/lib/KeyCode';
import delayRaf from '../raf';
import throttleByAnimationFrame from '../throttleByAnimationFrame';
import getDataOrAriaProps from '../getDataOrAriaProps';
import Wave from '../wave';
import TransButton from '../transButton';
import openAnimation from '../openAnimation';
import { sleep } from '../../../tests/utils';
import focusTest from '../../../tests/shared/focusTest';
describe('Test utils function', () => {
focusTest(TransButton);
it('throttle function should work', async () => {
const callback = jest.fn();
const throttled = throttleByAnimationFrame(callback);
expect(callback).not.toHaveBeenCalled();
throttled();
throttled();
await sleep(20);
expect(callback).toHaveBeenCalled();
expect(callback.mock.calls.length).toBe(1);
});
it('throttle function should be canceled', async () => {
const callback = jest.fn();
const throttled = throttleByAnimationFrame(callback);
throttled();
throttled.cancel();
await sleep(20);
expect(callback).not.toHaveBeenCalled();
});
describe('getDataOrAriaProps', () => {
it('returns all data-* properties from an object', () => {
const props = {
onClick: () => {},
isOpen: true,
'data-test': 'test-id',
'data-id': 1234,
};
const results = getDataOrAriaProps(props);
expect(results).toEqual({
'data-test': 'test-id',
'data-id': 1234,
});
});
it('does not return data-__ properties from an object', () => {
const props = {
onClick: () => {},
isOpen: true,
'data-__test': 'test-id',
'data-__id': 1234,
};
const results = getDataOrAriaProps(props);
expect(results).toEqual({});
});
it('returns all aria-* properties from an object', () => {
const props = {
onClick: () => {},
isOpen: true,
'aria-labelledby': 'label-id',
'aria-label': 'some-label',
};
const results = getDataOrAriaProps(props);
expect(results).toEqual({
'aria-labelledby': 'label-id',
'aria-label': 'some-label',
});
});
it('returns role property from an object', () => {
const props = {
onClick: () => {},
isOpen: true,
role: 'search',
};
const results = getDataOrAriaProps(props);
expect(results).toEqual({ role: 'search' });
});
});
it('delayRaf', done => {
jest.useRealTimers();
let bamboo = false;
delayRaf(() => {
bamboo = true;
}, 3);
// Do nothing, but insert in the frame
// https://github.com/ant-design/ant-design/issues/16290
delayRaf(() => {}, 3);
// Variable bamboo should be false in frame 2 but true in frame 4
raf(() => {
expect(bamboo).toBe(false);
// Frame 2
raf(() => {
expect(bamboo).toBe(false);
// Frame 3
raf(() => {
// Frame 4
raf(() => {
expect(bamboo).toBe(true);
done();
});
});
});
});
});
describe('wave', () => {
it('bindAnimationEvent should return when node is null', () => {
const wrapper = mount(
<Wave>
<button type="button" disabled>
button
</button>
</Wave>,
).instance();
expect(wrapper.bindAnimationEvent()).toBe(undefined);
});
it('bindAnimationEvent.onClick should return when children is hidden', () => {
const wrapper = mount(
<Wave>
<button type="button" style={{ display: 'none' }}>
button
</button>
</Wave>,
).instance();
expect(wrapper.bindAnimationEvent()).toBe(undefined);
});
it('bindAnimationEvent.onClick should return when children is input', () => {
const wrapper = mount(
<Wave>
<input />
</Wave>,
).instance();
expect(wrapper.bindAnimationEvent()).toBe(undefined);
});
it('should not throw when click it', () => {
expect(() => {
const wrapper = mount(
<Wave>
<div />
</Wave>,
);
wrapper.simulate('click');
}).not.toThrow();
});
it('should not throw when no children', () => {
expect(() => mount(<Wave />)).not.toThrow();
});
});
describe('TransButton', () => {
it('can be focus/blur', () => {
const wrapper = mount(<TransButton>TransButton</TransButton>);
expect(typeof wrapper.instance().focus).toBe('function');
expect(typeof wrapper.instance().blur).toBe('function');
});
it('should trigger onClick when press enter', () => {
const onClick = jest.fn();
const preventDefault = jest.fn();
const wrapper = mount(<TransButton onClick={onClick}>TransButton</TransButton>);
wrapper.simulate('keyUp', { keyCode: KeyCode.ENTER });
expect(onClick).toHaveBeenCalled();
wrapper.simulate('keyDown', { keyCode: KeyCode.ENTER, preventDefault });
expect(preventDefault).toHaveBeenCalled();
});
});
describe('openAnimation', () => {
it('should support openAnimation', () => {
const done = jest.fn();
const domNode = document.createElement('div');
expect(typeof openAnimation.enter).toBe('function');
expect(typeof openAnimation.leave).toBe('function');
expect(typeof openAnimation.appear).toBe('function');
const appear = openAnimation.appear(domNode, done);
const enter = openAnimation.enter(domNode, done);
const leave = openAnimation.leave(domNode, done);
expect(typeof appear.stop).toBe('function');
expect(typeof enter.stop).toBe('function');
expect(typeof leave.stop).toBe('function');
expect(done).toHaveBeenCalled();
});
});
});

Some files were not shown because too many files have changed in this diff Show More