mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-04-19 20:08:05 +00:00
Merge branch 'preset-vision/0.9.0' into fiebai
This commit is contained in:
commit
813f362758
3
.gitignore
vendored
3
.gitignore
vendored
@ -100,3 +100,6 @@ typings/
|
||||
|
||||
# mac config files
|
||||
.DS_Store
|
||||
|
||||
# codealike
|
||||
codealike.json
|
||||
@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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>
|
||||
## [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)
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
{
|
||||
"entry": {
|
||||
"index": "src/vision/index.ts",
|
||||
"vision-preset": "../vision-preset/src/index.ts",
|
||||
"react-simulator-renderer": "../react-simulator-renderer/src/index.ts"
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ali/lowcode-demo",
|
||||
"version": "0.8.13",
|
||||
"version": "0.8.18",
|
||||
"private": true,
|
||||
"description": "低代码引擎 DEMO",
|
||||
"scripts": {
|
||||
@ -11,38 +11,38 @@
|
||||
},
|
||||
"config": {},
|
||||
"dependencies": {
|
||||
"@ali/lowcode-editor-core": "^0.8.9",
|
||||
"@ali/lowcode-editor-skeleton": "^0.8.10",
|
||||
"@ali/lowcode-plugin-components-pane": "^0.8.8",
|
||||
"@ali/lowcode-plugin-designer": "^0.9.6",
|
||||
"@ali/lowcode-plugin-event-bind-dialog": "^0.8.9",
|
||||
"@ali/lowcode-plugin-outline-pane": "^0.8.12",
|
||||
"@ali/lowcode-plugin-sample-logo": "^0.8.8",
|
||||
"@ali/lowcode-plugin-sample-preview": "^0.8.11",
|
||||
"@ali/lowcode-editor-core": "^0.8.12",
|
||||
"@ali/lowcode-editor-skeleton": "^0.8.15",
|
||||
"@ali/lowcode-plugin-components-pane": "^0.8.11",
|
||||
"@ali/lowcode-plugin-designer": "^0.9.9",
|
||||
"@ali/lowcode-plugin-event-bind-dialog": "^0.8.12",
|
||||
"@ali/lowcode-plugin-outline-pane": "^0.8.15",
|
||||
"@ali/lowcode-plugin-sample-logo": "^0.8.11",
|
||||
"@ali/lowcode-plugin-sample-preview": "^0.8.14",
|
||||
"@ali/lowcode-plugin-settings-pane": "^0.8.8",
|
||||
"@ali/lowcode-plugin-undo-redo": "^0.8.9",
|
||||
"@ali/lowcode-plugin-variable-bind-dialog": "^0.8.7",
|
||||
"@ali/lowcode-plugin-zh-en": "^0.8.11",
|
||||
"@ali/lowcode-react-renderer": "^0.8.5",
|
||||
"@ali/lowcode-plugin-undo-redo": "^0.8.14",
|
||||
"@ali/lowcode-plugin-variable-bind-dialog": "^0.8.10",
|
||||
"@ali/lowcode-plugin-zh-en": "^0.8.14",
|
||||
"@ali/lowcode-react-renderer": "^0.8.7",
|
||||
"@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-datapool-pane": "^6.4.3",
|
||||
"@ali/ve-history-pane": "4.0.0",
|
||||
"@ali/ve-i18n-manage-pane": "^4.3.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/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/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",
|
||||
"@alife/theme-lowcode-dark": "^0.1.0",
|
||||
"@alife/theme-lowcode-light": "^0.1.0",
|
||||
"compare-versions": "^3.0.1",
|
||||
"react": "^16.8.1",
|
||||
"react-dom": "^16.8.1",
|
||||
"@ali/vu-function-parser": "^2.5.0-beta.0",
|
||||
"compare-versions": "^3.0.1"
|
||||
"react-dom": "^16.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ali/iceluna-cli": "^0.0.16",
|
||||
|
||||
@ -3,6 +3,64 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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>
|
||||
## [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)
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ali/lowcode-designer",
|
||||
"version": "0.9.6",
|
||||
"version": "0.9.9",
|
||||
"description": "Designer for Ali LowCode Engine",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.js",
|
||||
@ -15,9 +15,9 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ali/lowcode-editor-core": "^0.8.9",
|
||||
"@ali/lowcode-types": "^0.8.1",
|
||||
"@ali/lowcode-utils": "^0.8.2",
|
||||
"@ali/lowcode-editor-core": "^0.8.12",
|
||||
"@ali/lowcode-types": "^0.8.3",
|
||||
"@ali/lowcode-utils": "^0.8.4",
|
||||
"classnames": "^2.2.6",
|
||||
"event": "^1.0.0",
|
||||
"react": "^16",
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { Component, Fragment, PureComponent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { computed, observer, Title } from '@ali/lowcode-editor-core';
|
||||
import { SimulatorContext } from '../context';
|
||||
import { BuiltinSimulatorHost } from '../host';
|
||||
import { TitleContent } from '@ali/lowcode-types';
|
||||
|
||||
export class BorderHoveringInstance extends PureComponent<{
|
||||
export class BorderDetectingInstance extends PureComponent<{
|
||||
title: TitleContent;
|
||||
rect: DOMRect | null;
|
||||
scale: number;
|
||||
@ -24,7 +23,7 @@ export class BorderHoveringInstance extends PureComponent<{
|
||||
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:
|
||||
// 1. thinkof icon
|
||||
@ -39,7 +38,7 @@ export class BorderHoveringInstance extends PureComponent<{
|
||||
}
|
||||
|
||||
@observer
|
||||
export class BorderHovering extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
export class BorderDetecting extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
@ -60,7 +59,7 @@ export class BorderHovering extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
const host = this.props.host;
|
||||
const doc = host.document;
|
||||
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)) {
|
||||
return null;
|
||||
}
|
||||
@ -70,36 +69,36 @@ export class BorderHovering extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
render() {
|
||||
const host = this.props.host;
|
||||
const current = this.current;
|
||||
if (!current || host.viewport.scrolling) {
|
||||
return <Fragment />;
|
||||
if (!current || host.viewport.scrolling || host.liveEditing.editing) {
|
||||
return null;
|
||||
}
|
||||
const instances = host.getComponentInstances(current);
|
||||
if (!instances || instances.length < 1) {
|
||||
return <Fragment />;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (instances.length === 1) {
|
||||
return (
|
||||
<BorderHoveringInstance
|
||||
<BorderDetectingInstance
|
||||
key="line-h"
|
||||
title={current.title}
|
||||
scale={this.scale}
|
||||
scrollX={this.scrollX}
|
||||
scrollY={this.scrollY}
|
||||
rect={host.computeComponentInstanceRect(instances[0], current.componentMeta.rectSelector)}
|
||||
rect={host.computeComponentInstanceRect(instances[0], current.componentMeta.rootSelector)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
{instances.map((inst, i) => (
|
||||
<BorderHoveringInstance
|
||||
<BorderDetectingInstance
|
||||
key={`line-h-${i}`}
|
||||
title={current.title}
|
||||
scale={this.scale}
|
||||
scrollX={this.scrollX}
|
||||
scrollY={this.scrollY}
|
||||
rect={host.computeComponentInstanceRect(inst, current.componentMeta.rectSelector)}
|
||||
rect={host.computeComponentInstanceRect(inst, current.componentMeta.rootSelector)}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
@ -12,7 +12,6 @@ import classNames from 'classnames';
|
||||
import { observer, computed, Tip } from '@ali/lowcode-editor-core';
|
||||
import { createIcon, isReactComponent } from '@ali/lowcode-utils';
|
||||
import { ActionContentObject, isActionContentObject } from '@ali/lowcode-types';
|
||||
import { SimulatorContext } from '../context';
|
||||
import { BuiltinSimulatorHost } from '../host';
|
||||
import { OffsetObserver } from '../../designer';
|
||||
import { Node } from '../../document';
|
||||
@ -188,7 +187,7 @@ export class BorderSelecting extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
|
||||
@computed get selecting() {
|
||||
const doc = this.host.document;
|
||||
if (doc.suspensed) {
|
||||
if (doc.suspensed || this.host.liveEditing.editing) {
|
||||
return null;
|
||||
}
|
||||
const selection = doc.selection;
|
||||
@ -202,8 +201,7 @@ export class BorderSelecting extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
render() {
|
||||
const selecting = this.selecting;
|
||||
if (!selecting || selecting.length < 1) {
|
||||
// DIRTY FIX, recore has a bug!
|
||||
return <Fragment />;
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -49,28 +49,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.lc-resize-box {
|
||||
border-width: 0;
|
||||
z-index: 1;
|
||||
cursor: ew-resize;
|
||||
pointer-events: auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
&.lc-resize-box {
|
||||
border-width: 0;
|
||||
z-index: 1;
|
||||
cursor: ew-resize;
|
||||
pointer-events: auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
height: calc(100% - 20px);
|
||||
min-height: 50%;
|
||||
width: 4px;
|
||||
background: #738397;
|
||||
border-radius: 2px;
|
||||
// animation: flashing 1.5s infinite linear;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
height: calc(100% - 20px);
|
||||
min-height: 50%;
|
||||
width: 4px;
|
||||
background: #738397;
|
||||
border-radius: 2px;
|
||||
// animation: flashing 1.5s infinite linear;
|
||||
}
|
||||
}
|
||||
|
||||
&&-hovering {
|
||||
// &&-hovering {
|
||||
&&-detecting {
|
||||
z-index: 1;
|
||||
border-style: dashed;
|
||||
background: rgba(0,121,242,.04);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Component } from 'react';
|
||||
import { observer } from '@ali/lowcode-editor-core';
|
||||
import { BorderHovering } from './border-hovering';
|
||||
import { BorderDetecting } from './border-detecting';
|
||||
import { BuiltinSimulatorHost } from '../host';
|
||||
import { BorderSelecting } from './border-selecting';
|
||||
import BorderResizing from './border-resizing';
|
||||
@ -19,10 +19,9 @@ export class BemTools extends Component<{ host: BuiltinSimulatorHost }> {
|
||||
const { scrollX, scrollY, scale } = host.viewport;
|
||||
return (
|
||||
<div className="lc-bem-tools" style={{ transform: `translate(${-scrollX * scale}px,${-scrollY * scale}px)` }}>
|
||||
<BorderHovering key="hovering" />
|
||||
<BorderSelecting key="selecting" />
|
||||
<InsertionView key="insertion" />
|
||||
<BorderResizing key="resizing" />
|
||||
<BorderDetecting key="hovering" host={host} />
|
||||
<BorderSelecting key="selecting" host={host} />
|
||||
<InsertionView key="insertion" host={host} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ function processDetail({ target, detail, document }: DropLocation): InsertionDat
|
||||
if (!instances) {
|
||||
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 } : {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 Viewport from './viewport';
|
||||
import { createSimulator } from './create-simulator';
|
||||
@ -25,6 +25,7 @@ import { parseMetadata } from './utils/parse-metadata';
|
||||
import { ComponentMetadata } from '@ali/lowcode-types';
|
||||
import { BuiltinSimulatorRenderer } from './renderer';
|
||||
import clipboard from '../designer/clipboard';
|
||||
import { LiveEditing } from './live-editing/live-editing';
|
||||
|
||||
export interface LibraryItem {
|
||||
package: string;
|
||||
@ -214,6 +215,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
|
||||
// bind hotkey & clipboard
|
||||
hotkey.mount(this._contentWindow);
|
||||
focusTracker.mount(this._contentWindow);
|
||||
clipboard.injectCopyPaster(this._contentDocument);
|
||||
// TODO: dispose the bindings
|
||||
}
|
||||
@ -223,7 +225,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
// just listen special callback
|
||||
// because iframe maybe reload
|
||||
this.setupDragAndClick();
|
||||
this.setupHovering();
|
||||
this.setupDetecting();
|
||||
this.setupLiveEditing();
|
||||
}
|
||||
|
||||
setupDragAndClick() {
|
||||
@ -237,6 +240,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
doc.addEventListener(
|
||||
'mousedown',
|
||||
(downEvent: MouseEvent) => {
|
||||
if (this.liveEditing.editing) {
|
||||
return;
|
||||
}
|
||||
// stop response document focus event
|
||||
downEvent.stopPropagation();
|
||||
downEvent.preventDefault();
|
||||
@ -249,7 +255,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
doc.removeEventListener('mouseup', checkSelect, true);
|
||||
if (!isShaken(downEvent, e)) {
|
||||
const id = node.id;
|
||||
designer.activeTracker.track(node);
|
||||
designer.activeTracker.track({ node, instance: nodeInst?.instance });
|
||||
if (isMulti && !isRootNode(node) && selection.has(id)) {
|
||||
selection.remove(id);
|
||||
} else {
|
||||
@ -264,7 +270,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
if (isMulti) {
|
||||
// multi select mode, directily add
|
||||
if (!selection.has(node.id)) {
|
||||
designer.activeTracker.track(node);
|
||||
designer.activeTracker.track({ node, instance: nodeInst?.instance });
|
||||
selection.add(node.id);
|
||||
ignoreUpSelected = true;
|
||||
}
|
||||
@ -304,36 +310,24 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
// cause edit
|
||||
doc.addEventListener(
|
||||
'dblclick',
|
||||
(e: MouseEvent) => {
|
||||
// stop response document dblclick event
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
// todo: quick editing
|
||||
},
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
private disableHovering?: () => void;
|
||||
/**
|
||||
* 设置悬停处理
|
||||
*/
|
||||
setupHovering() {
|
||||
setupDetecting() {
|
||||
const doc = this.contentDocument!;
|
||||
const hovering = this.document.designer.hovering;
|
||||
const detecting = this.document.designer.detecting;
|
||||
const hover = (e: MouseEvent) => {
|
||||
if (!hovering.enable) {
|
||||
if (!detecting.enable) {
|
||||
return;
|
||||
}
|
||||
const nodeInst = this.getNodeInstanceFromElement(e.target as Element);
|
||||
hovering.hover(nodeInst?.node || null);
|
||||
detecting.capture(nodeInst?.node || null);
|
||||
e.stopPropagation();
|
||||
};
|
||||
const leave = () => hovering.leave(this.document);
|
||||
const leave = () => detecting.leave(this.document);
|
||||
|
||||
doc.addEventListener('mouseover', hover, true);
|
||||
doc.addEventListener('mouseleave', leave, false);
|
||||
@ -348,13 +342,47 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
);
|
||||
|
||||
this.disableHovering = () => {
|
||||
hovering.leave(this.document);
|
||||
detecting.leave(this.document);
|
||||
doc.removeEventListener('mouseover', hover, true);
|
||||
doc.removeEventListener('mouseleave', leave, false);
|
||||
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
|
||||
*/
|
||||
@ -367,7 +395,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
} else {
|
||||
// weekup some autorun reaction
|
||||
if (!this.disableHovering) {
|
||||
this.setupHovering();
|
||||
this.setupDetecting();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -454,7 +482,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
if (!instances) {
|
||||
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 {
|
||||
const renderer = this.renderer!;
|
||||
const elements = renderer.findDOMNodes(instance);
|
||||
const elements = this.findDOMNodes(instance, selector);
|
||||
if (!elements) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let elems = elements.slice();
|
||||
if (selector) {
|
||||
const matched = getMatched(elems, selector);
|
||||
if (!matched) {
|
||||
return null;
|
||||
}
|
||||
elems = [matched];
|
||||
}
|
||||
const elems = elements.slice();
|
||||
let rects: DOMRect[] | undefined;
|
||||
let last: { x: number; y: number; r: number; b: number } | undefined;
|
||||
let computed = false;
|
||||
@ -533,8 +554,20 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
/**
|
||||
* @see ISimulator
|
||||
*/
|
||||
findDOMNodes(instance: ComponentInstance): Array<Element | Text> | null {
|
||||
return this._renderer?.findDOMNodes(instance) || null;
|
||||
findDOMNodes(instance: ComponentInstance, selector?: string): Array<Element | Text> | 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 edge = this.computeComponentInstanceRect(containerInstance, container.componentMeta.rectSelector);
|
||||
const edge = this.computeComponentInstanceRect(containerInstance, container.componentMeta.rootSelector);
|
||||
|
||||
if (!edge) {
|
||||
return null;
|
||||
@ -757,7 +790,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
? instances.find((inst) => this.getClosestNodeInstance(inst, container.id)?.instance === containerInstance)
|
||||
: instances[0]
|
||||
: null;
|
||||
const rect = inst ? this.computeComponentInstanceRect(inst, node.componentMeta.rectSelector) : null;
|
||||
const rect = inst ? this.computeComponentInstanceRect(inst, node.componentMeta.rootSelector) : null;
|
||||
|
||||
if (!rect) {
|
||||
continue;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './host';
|
||||
export * from './host-view';
|
||||
export * from './renderer';
|
||||
export * from './live-editing/live-editing';
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -9,6 +9,8 @@ import {
|
||||
NestingFilter,
|
||||
isTitleConfig,
|
||||
I18nData,
|
||||
LiveTextEditingConfig,
|
||||
FieldConfig,
|
||||
} from '@ali/lowcode-types';
|
||||
import { computed } from '@ali/lowcode-editor-core';
|
||||
import { Node, ParentalNode } from './document';
|
||||
@ -81,9 +83,9 @@ export class ComponentMeta {
|
||||
get descriptor(): string | undefined {
|
||||
return this._descriptor;
|
||||
}
|
||||
private _rectSelector?: string;
|
||||
get rectSelector(): string | undefined {
|
||||
return this._rectSelector;
|
||||
private _rootSelector?: string;
|
||||
get rootSelector(): string | undefined {
|
||||
return this._rootSelector;
|
||||
}
|
||||
private _transformedMetadata?: TransformedComponentMetadata;
|
||||
get configure() {
|
||||
@ -91,6 +93,11 @@ export class ComponentMeta {
|
||||
return config?.combined || config?.props || [];
|
||||
}
|
||||
|
||||
private _liveTextEditing?: LiveTextEditingConfig[];
|
||||
get liveTextEditing() {
|
||||
return this._liveTextEditing;
|
||||
}
|
||||
|
||||
private parentWhitelist?: NestingFilter | null;
|
||||
private childWhitelist?: NestingFilter | null;
|
||||
|
||||
@ -150,6 +157,26 @@ export class ComponentMeta {
|
||||
: 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;
|
||||
this._acceptable = false;
|
||||
|
||||
@ -158,7 +185,7 @@ export class ComponentMeta {
|
||||
this._isContainer = component.isContainer ? true : false;
|
||||
this._isModal = component.isModal ? true : false;
|
||||
this._descriptor = component.descriptor;
|
||||
this._rectSelector = component.rectSelector;
|
||||
this._rootSelector = component.rootSelector;
|
||||
if (component.nestingRule) {
|
||||
const { parentWhitelist, childWhitelist } = component.nestingRule;
|
||||
this.parentWhitelist = buildFilter(parentWhitelist);
|
||||
|
||||
@ -1,17 +1,38 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { LocationDetail } from './location';
|
||||
import { Node, isNode } from '../document/node/node';
|
||||
import { ComponentInstance } from '../simulator';
|
||||
import { obx } from '@ali/lowcode-editor-core';
|
||||
|
||||
export interface ActiveTarget {
|
||||
node: Node;
|
||||
detail?: LocationDetail;
|
||||
instance?: ComponentInstance;
|
||||
}
|
||||
|
||||
export class ActiveTracker {
|
||||
private emitter = new EventEmitter();
|
||||
|
||||
@obx.ref private _target?: ActiveTarget;
|
||||
|
||||
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 {
|
||||
|
||||
@ -36,7 +36,6 @@ function getNextForSelect(next: any, head?: any, parent?: any): any {
|
||||
|
||||
function getPrevForSelect(prev: any, head?: any, parent?: any): any {
|
||||
if (prev) {
|
||||
debugger;
|
||||
let ret;
|
||||
if (!head && prev.isContainer()) {
|
||||
const children = prev.getChildren() || [];
|
||||
@ -67,6 +66,7 @@ function getPrevForSelect(prev: any, head?: any, parent?: any): any {
|
||||
|
||||
// hotkey binding
|
||||
hotkey.bind(['backspace', 'del'], (e: KeyboardEvent) => {
|
||||
// TODO: use focus-tracker
|
||||
const doc = focusing.focusDesigner?.currentDocument;
|
||||
if (isFormEvent(e) || !doc) {
|
||||
return;
|
||||
@ -102,14 +102,6 @@ hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
/*
|
||||
const doc = getCurrentDocument();
|
||||
if (isFormEvent(e) || !doc || !(focusing.id === 'outline' || focusing.id === 'canvas')) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
*/
|
||||
|
||||
const selected = doc.selection.getTopNodes(true);
|
||||
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);
|
||||
|
||||
const cutMode = action.indexOf('x') > 0;
|
||||
const cutMode = action && action.indexOf('x') > 0;
|
||||
if (cutMode) {
|
||||
selected.forEach((node) => {
|
||||
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 doc = designer?.currentDocument;
|
||||
if (isFormEvent(e) || !doc) {
|
||||
@ -225,24 +217,17 @@ hotkey.bind(['option+up', 'option+down', 'option+left', 'option+right'], (e, act
|
||||
return;
|
||||
}
|
||||
// TODO: 此处需要增加判断当前节点是否可被操作移动,原ve里是用 node.canOperating()来判断
|
||||
// TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断
|
||||
|
||||
const firstNode = selected[0];
|
||||
const parent = firstNode.getParent();
|
||||
if (!parent) return;
|
||||
|
||||
const isPrev = /(up|left)$/.test(action);
|
||||
const isTravel = /(up|down)$/.test(action);
|
||||
const isPrev = action && /(left)$/.test(action);
|
||||
|
||||
const silbing = isPrev ? firstNode.prevSibling : firstNode.nextSibling;
|
||||
if (silbing) {
|
||||
if (isTravel && silbing.isContainer()) {
|
||||
const place = silbing.getSuitablePlace(firstNode, null);
|
||||
if (isPrev) {
|
||||
place.container.insertAfter(firstNode, place.ref);
|
||||
} else {
|
||||
place.container.insertBefore(firstNode, place.ref);
|
||||
}
|
||||
} else if (isPrev) {
|
||||
if (isPrev) {
|
||||
parent.insertBefore(firstNode, silbing);
|
||||
} else {
|
||||
parent.insertAfter(firstNode, silbing);
|
||||
@ -250,14 +235,82 @@ hotkey.bind(['option+up', 'option+down', 'option+left', 'option+right'], (e, act
|
||||
firstNode?.select();
|
||||
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
|
||||
if (place) {
|
||||
if (isPrev) {
|
||||
place.container.insertBefore(firstNode, place.ref);
|
||||
} else {
|
||||
place.container.insertAfter(firstNode, place.ref);
|
||||
}
|
||||
place.container.insertBefore(firstNode, place.ref);
|
||||
firstNode?.select();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ import { INodeSelector, Component } from '../simulator';
|
||||
import { Scroller, IScrollable } from './scroller';
|
||||
import { Dragon, isDragNodeObject, isDragNodeDataObject, LocateEvent, DragObject } from './dragon';
|
||||
import { ActiveTracker } from './active-tracker';
|
||||
import { Hovering } from './hovering';
|
||||
import { Detecting } from './detecting';
|
||||
import { DropLocation, LocationData, isLocationChildrenDetail } from './location';
|
||||
import { OffsetObserver, createOffsetObserver } from './offset-observer';
|
||||
import { focusing } from './focusing';
|
||||
@ -44,7 +44,7 @@ export interface DesignerProps {
|
||||
export class Designer {
|
||||
readonly dragon = new Dragon(this);
|
||||
readonly activeTracker = new ActiveTracker();
|
||||
readonly hovering = new Hovering();
|
||||
readonly detecting = new Detecting();
|
||||
readonly project: Project;
|
||||
readonly editor: IEditor;
|
||||
|
||||
@ -68,7 +68,7 @@ export class Designer {
|
||||
this.project = new Project(this, props.defaultSchema);
|
||||
|
||||
this.dragon.onDragstart((e) => {
|
||||
this.hovering.enable = false;
|
||||
this.detecting.enable = false;
|
||||
const { dragObject } = e;
|
||||
if (isDragNodeObject(dragObject)) {
|
||||
if (dragObject.nodes.length === 1) {
|
||||
@ -99,7 +99,7 @@ export class Designer {
|
||||
const { dragObject, copy } = e;
|
||||
const loc = this._dropLocation;
|
||||
if (loc) {
|
||||
if (isLocationChildrenDetail(loc.detail)) {
|
||||
if (isLocationChildrenDetail(loc.detail) && loc.detail.valid !== false) {
|
||||
let nodes: Node[] | undefined;
|
||||
if (isDragNodeObject(dragObject)) {
|
||||
nodes = insertChildren(loc.target, dragObject.nodes, loc.detail.index, copy);
|
||||
@ -114,12 +114,11 @@ export class Designer {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearLocation();
|
||||
if (this.props?.onDragend) {
|
||||
this.props.onDragend(e, loc);
|
||||
}
|
||||
this.postEvent('dragend', e, loc);
|
||||
this.hovering.enable = true;
|
||||
this.detecting.enable = true;
|
||||
});
|
||||
|
||||
this.activeTracker.onChange(({ node, detail }) => {
|
||||
@ -174,6 +173,10 @@ export class Designer {
|
||||
}
|
||||
|
||||
private _dropLocation?: DropLocation;
|
||||
|
||||
get dropLocation() {
|
||||
return this._dropLocation;
|
||||
}
|
||||
/**
|
||||
* 创建插入位置,考虑放到 dragon 中
|
||||
*/
|
||||
@ -202,8 +205,30 @@ export class Designer {
|
||||
return new Scroller(scrollable);
|
||||
}
|
||||
|
||||
private oobxList: OffsetObserver[] = [];
|
||||
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[]) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { obx } from '@ali/lowcode-editor-core';
|
||||
import { Node, DocumentModel } from '../document';
|
||||
|
||||
export class Hovering {
|
||||
export class Detecting {
|
||||
@obx.ref private _enable = true;
|
||||
get enable() {
|
||||
return this._enable;
|
||||
@ -19,11 +19,11 @@ export class Hovering {
|
||||
return this._current;
|
||||
}
|
||||
|
||||
hover(node: Node | null) {
|
||||
capture(node: Node | null) {
|
||||
this._current = node;
|
||||
}
|
||||
|
||||
unhover(node: Node) {
|
||||
release(node: Node) {
|
||||
if (this._current === node) {
|
||||
this._current = null;
|
||||
}
|
||||
@ -8,7 +8,7 @@
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
opacity: 0.5;
|
||||
//opacity: 0.9;
|
||||
box-shadow: 0 0 6px grey;
|
||||
transform: translate(-10%, -50%);
|
||||
.lc-ghost {
|
||||
|
||||
@ -365,6 +365,7 @@ export class Dragon {
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
designer.clearLocation();
|
||||
|
||||
handleEvents((doc) => {
|
||||
if (isBoostFromDragAPI) {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { Designer } from './designer';
|
||||
|
||||
// TODO:
|
||||
// 当前激活区域管理
|
||||
// TODO: use focus-tracker replace
|
||||
class Focusing {
|
||||
focusDesigner?: Designer;
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import './builtin-hotkey';
|
||||
export * from './designer';
|
||||
export * from './designer-view';
|
||||
export * from './dragon';
|
||||
export * from './hovering';
|
||||
export * from './detecting';
|
||||
export * from './location';
|
||||
export * from './offset-observer';
|
||||
export * from './scroller';
|
||||
|
||||
@ -82,6 +82,8 @@ export class OffsetObserver {
|
||||
private isRoot: boolean;
|
||||
readonly node: Node;
|
||||
|
||||
readonly compute: () => void;
|
||||
|
||||
constructor(readonly nodeInstance: INodeSelector) {
|
||||
const { node, instance } = nodeInstance;
|
||||
this.node = node;
|
||||
@ -103,7 +105,7 @@ export class OffsetObserver {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = host.computeComponentInstanceRect(instance!, node.componentMeta.rectSelector);
|
||||
const rect = host.computeComponentInstanceRect(instance!, node.componentMeta.rootSelector);
|
||||
|
||||
if (!rect) {
|
||||
this.hasOffset = false;
|
||||
@ -121,6 +123,8 @@ export class OffsetObserver {
|
||||
this.pid = pid = (window as any).requestIdleCallback(compute);
|
||||
};
|
||||
|
||||
this.compute = compute;
|
||||
|
||||
// try first
|
||||
compute();
|
||||
// try second, ensure the dom mounted
|
||||
@ -133,6 +137,10 @@ export class OffsetObserver {
|
||||
}
|
||||
this.pid = undefined;
|
||||
}
|
||||
|
||||
isPurged() {
|
||||
return this.pid == null;
|
||||
}
|
||||
}
|
||||
|
||||
export function createOffsetObserver(nodeInstance: INodeSelector): OffsetObserver | null {
|
||||
|
||||
@ -3,6 +3,7 @@ import { Transducer } from './utils';
|
||||
import { SettingPropEntry } from './setting-prop-entry';
|
||||
import { SettingEntry } from './setting-entry';
|
||||
import { computed, obx } from '@ali/lowcode-editor-core';
|
||||
import { cloneDeep } from '@ali/lowcode-utils';
|
||||
|
||||
export class SettingField extends SettingPropEntry implements SettingEntry {
|
||||
readonly isSettingField = true;
|
||||
@ -83,46 +84,33 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
|
||||
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() {
|
||||
this.disposeItems();
|
||||
}
|
||||
|
||||
// ======= compatibles for vision ======
|
||||
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) {
|
||||
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 {
|
||||
|
||||
@ -62,14 +62,6 @@ export class SettingPropEntry implements SettingEntry {
|
||||
this.isSingle = parent.isSingle;
|
||||
this.designer = parent.designer;
|
||||
this.top = parent.top;
|
||||
|
||||
autorun(({ firstRun }) => {
|
||||
const value = this.getValue();
|
||||
if (firstRun) {
|
||||
return;
|
||||
}
|
||||
this.emitter.emit('valuechange', value);
|
||||
});
|
||||
}
|
||||
|
||||
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 {
|
||||
let val: any = null;
|
||||
let val: any = undefined;
|
||||
if (this.type === 'field') {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除已设置值
|
||||
*/
|
||||
clearPropValue(propName: string | number) {
|
||||
const path = this.path.concat(propName).join('.');
|
||||
this.top.clearPropValue(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子级属性值
|
||||
*/
|
||||
@ -180,11 +225,11 @@ export class SettingPropEntry implements SettingEntry {
|
||||
return this.top;
|
||||
}
|
||||
|
||||
// add settingfield props
|
||||
// add settingfield props
|
||||
get props() {
|
||||
return this.top;
|
||||
}
|
||||
|
||||
|
||||
onValueChange(func: () => any) {
|
||||
this.emitter.on('valuechange', func);
|
||||
|
||||
@ -197,8 +242,6 @@ export class SettingPropEntry implements SettingEntry {
|
||||
* @deprecated
|
||||
*/
|
||||
valueChange() {
|
||||
console.warn('valueChange deprecated');
|
||||
|
||||
this.emitter.emit('valuechange');
|
||||
}
|
||||
|
||||
|
||||
@ -136,6 +136,15 @@ export class SettingTopEntry implements SettingEntry {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除已设置值
|
||||
*/
|
||||
clearPropValue(propName: string) {
|
||||
this.nodes.forEach((node) => {
|
||||
node.clearPropValue(propName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子级属性值
|
||||
*/
|
||||
|
||||
@ -46,7 +46,7 @@ export class Transducer {
|
||||
setter = setter.componentName;
|
||||
}
|
||||
if (typeof setter === 'string') {
|
||||
setter = getSetter(setter);
|
||||
setter = getSetter(setter).component;
|
||||
}
|
||||
|
||||
this.setterTransducer = combineTransducer(
|
||||
|
||||
@ -41,7 +41,6 @@ export class DocumentModel {
|
||||
private seqId = 0;
|
||||
private _simulator?: ISimulatorHost;
|
||||
|
||||
|
||||
/**
|
||||
* 模拟器
|
||||
*/
|
||||
@ -119,7 +118,6 @@ export class DocumentModel {
|
||||
// return this.addonsData[name];
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* 生成唯一id
|
||||
*/
|
||||
@ -240,12 +238,12 @@ export class DocumentModel {
|
||||
node.remove();
|
||||
}
|
||||
getAddonData(name: string) {
|
||||
const addon = this.getNode(name)
|
||||
const addon = this.getNode(name);
|
||||
if (addon) {
|
||||
// 无法确定是否有这个api
|
||||
// return addon.exportData();
|
||||
}
|
||||
return addon
|
||||
return addon;
|
||||
}
|
||||
|
||||
@obx.ref private _dropLocation: DropLocation | null = null;
|
||||
@ -485,7 +483,8 @@ export class DocumentModel {
|
||||
|
||||
// add toData
|
||||
toData() {
|
||||
return { componentsTree: [this.project?.currentDocument?.export(TransformStage.Save)] };
|
||||
const node = this.project?.currentDocument?.export(TransformStage.Serilize);
|
||||
return { componentsTree: [node] };
|
||||
}
|
||||
|
||||
getHistory(): History {
|
||||
|
||||
@ -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 {
|
||||
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) {
|
||||
let changed = false;
|
||||
if (remover) {
|
||||
|
||||
@ -284,9 +284,9 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
*/
|
||||
hover(flag = true) {
|
||||
if (flag) {
|
||||
this.document.designer.hovering.hover(this);
|
||||
this.document.designer.detecting.capture(this);
|
||||
} 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除已设置的值
|
||||
*/
|
||||
clearPropValue(path: string): void {
|
||||
this.getProp(path, false)?.unset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置多个属性值,和原有值合并
|
||||
*/
|
||||
@ -511,7 +518,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
||||
const schema: any = {
|
||||
...baseSchema,
|
||||
props: this.document.designer.transformProps(props, this, stage),
|
||||
..._extras_,
|
||||
...this.document.designer.transformProps(_extras_, this, stage),
|
||||
};
|
||||
|
||||
if (this.isParental() && this.children.size > 0) {
|
||||
|
||||
@ -245,7 +245,9 @@ export class Prop implements IPropParent {
|
||||
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 {
|
||||
if (!other || other.isUnset()) {
|
||||
return this.isUnset() ? 0 : 2;
|
||||
|
||||
@ -134,7 +134,7 @@ export interface ISimulatorHost<P = object> extends ISensor {
|
||||
|
||||
computeComponentInstanceRect(instance: ComponentInstance, selector?: string): DOMRect | null;
|
||||
|
||||
findDOMNodes(instance: ComponentInstance): Array<Element | Text> | null;
|
||||
findDOMNodes(instance: ComponentInstance, selector?: string): Array<Element | Text> | null;
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
|
||||
@ -3,6 +3,37 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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>
|
||||
## [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)
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ali/lowcode-editor-core",
|
||||
"version": "0.8.9",
|
||||
"version": "0.8.12",
|
||||
"description": "Core Api for Ali lowCode engine",
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
@ -15,11 +15,11 @@
|
||||
"cloud-build": "build-scripts build --skip-demo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ali/lowcode-types": "^0.8.1",
|
||||
"@ali/lowcode-utils": "^0.8.2",
|
||||
"@ali/lowcode-types": "^0.8.3",
|
||||
"@ali/lowcode-utils": "^0.8.4",
|
||||
"@alifd/next": "^1.19.16",
|
||||
"@recore/obx": "^1.0.8",
|
||||
"@recore/obx-react": "^1.0.7",
|
||||
"@recore/obx": "^1.0.9",
|
||||
"@recore/obx-react": "^1.0.8",
|
||||
"classnames": "^2.2.6",
|
||||
"debug": "^4.1.1",
|
||||
"intl-messageformat": "^8.3.1",
|
||||
|
||||
@ -35,7 +35,16 @@ class AliGlobalLocale {
|
||||
if (this._locale != null) {
|
||||
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)) {
|
||||
const store = window.localStorage;
|
||||
let config: any;
|
||||
@ -47,20 +56,14 @@ class AliGlobalLocale {
|
||||
if (config?.locale) {
|
||||
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) {
|
||||
const lang = (navigator.language as string);
|
||||
return languageMap[lang] || lang.replace('_', '-');
|
||||
}
|
||||
|
||||
// IE10 及更低版本使用 browserLanguage
|
||||
if (navigator.browserLanguage) {
|
||||
} else if (navigator.browserLanguage) {
|
||||
const it = navigator.browserLanguage.split('-');
|
||||
locale = it[0];
|
||||
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 {
|
||||
return obj.localStorage;
|
||||
}
|
||||
|
||||
140
packages/editor-core/src/utils/focus-tracker.ts
Normal file
140
packages/editor-core/src/utils/focus-tracker.ts
Normal 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);
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -2,3 +2,4 @@ export * from './get-public-path';
|
||||
export * from './goldlog';
|
||||
export * from './obx';
|
||||
export * from './request';
|
||||
export * from './focus-tracker';
|
||||
|
||||
@ -64,8 +64,17 @@ function resolveDirection(popup: any, target: any, edge: any, bounds: any, prefe
|
||||
return prefer;
|
||||
}
|
||||
|
||||
function resolvePrefer(prefer: any) {
|
||||
function resolvePrefer(prefer: any, targetRect: any, bounds: any) {
|
||||
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 {};
|
||||
}
|
||||
const force = prefer[0] === '!';
|
||||
@ -105,7 +114,7 @@ export function resolvePosition(popup: any, target: any, arrow: any, bounds: any
|
||||
width: popup.width,
|
||||
};
|
||||
|
||||
const prefers = resolvePrefer(prefer);
|
||||
const prefers = resolvePrefer(prefer, target, bounds);
|
||||
|
||||
const edge = resolveEdge(popup, target, arrow, bounds);
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ export class Title extends Component<{ title: TitleContent; className?: string;
|
||||
typeof title.tip === 'object' && !(isValidElement(title.tip) || isI18nData(title.tip))
|
||||
? title.tip
|
||||
: { children: title.tip };
|
||||
tip = <Tip direction="top" theme="black" {...tipProps} />;
|
||||
tip = <Tip {...tipProps} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,69 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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>
|
||||
## [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)
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ali/lowcode-editor-skeleton",
|
||||
"version": "0.8.10",
|
||||
"version": "0.8.15",
|
||||
"description": "alibaba lowcode editor skeleton",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.js",
|
||||
@ -19,10 +19,10 @@
|
||||
"editor"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ali/lowcode-designer": "^0.9.6",
|
||||
"@ali/lowcode-editor-core": "^0.8.9",
|
||||
"@ali/lowcode-types": "^0.8.1",
|
||||
"@ali/lowcode-utils": "^0.8.2",
|
||||
"@ali/lowcode-designer": "^0.9.9",
|
||||
"@ali/lowcode-editor-core": "^0.8.12",
|
||||
"@ali/lowcode-types": "^0.8.3",
|
||||
"@ali/lowcode-utils": "^0.8.4",
|
||||
"@alifd/next": "^1.x",
|
||||
"classnames": "^2.2.6",
|
||||
"react": "^16.8.1",
|
||||
|
||||
@ -1,17 +1,25 @@
|
||||
import { Component } from 'react';
|
||||
import { isObject } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
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 { PopupPipe, PopupContext } from '../popup';
|
||||
import { intlNode } from '../../locale';
|
||||
import './index.less';
|
||||
import { IconClear } from '../../icons/clear';
|
||||
import InlineTip from './inlinetip';
|
||||
|
||||
export interface FieldProps {
|
||||
className?: string;
|
||||
title?: TitleContent | null;
|
||||
defaultDisplay?: 'accordion' | 'inline' | 'block';
|
||||
collapsed?: boolean;
|
||||
valueState?: number;
|
||||
name?: string;
|
||||
tip?: any;
|
||||
onExpandChange?: (expandState: boolean) => void;
|
||||
onClear?: () => void;
|
||||
}
|
||||
|
||||
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() {
|
||||
const { className, children, title } = this.props;
|
||||
const { className, children, title, valueState, onClear, name: propName, tip } = this.props;
|
||||
const { display, collapsed } = this.state;
|
||||
const isAccordion = display === 'accordion';
|
||||
const tipContent = this.getTipContent(propName!, tip);
|
||||
return (
|
||||
<div
|
||||
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-title">
|
||||
{createValueState(valueState, onClear)}
|
||||
<Title title={title || ''} />
|
||||
<InlineTip position="top">{tipContent}</InlineTip>
|
||||
</div>
|
||||
{isAccordion && <Icon className="lc-field-icon" type="arrow-up" size="xs" />}
|
||||
</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 {
|
||||
width?: number;
|
||||
}
|
||||
|
||||
@ -11,6 +11,54 @@
|
||||
.lc-field-title {
|
||||
display: flex;
|
||||
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 {
|
||||
// margin-right: @x-gap;
|
||||
@ -75,7 +123,7 @@
|
||||
// background: var(--color-block-background-shallow, rgba(31,56,88,.06));
|
||||
// border-bottom: 1px solid var(--color-line-normal);
|
||||
// color: var(--color-title);
|
||||
padding: 0 16px;
|
||||
padding: 0 16px 0 10px;
|
||||
background-color: #F7F9FC;
|
||||
color: #8F9BB3;
|
||||
user-select: none;
|
||||
|
||||
@ -8,7 +8,9 @@ export interface FieldProps {
|
||||
title?: TitleContent | null;
|
||||
display?: 'accordion' | 'inline' | 'block' | 'plain' | 'popup' | 'entry';
|
||||
collapsed?: boolean;
|
||||
valueState?: number;
|
||||
onExpandChange?: (collapsed: boolean) => void;
|
||||
onClear?: () => void;
|
||||
[extra: string]: any;
|
||||
}
|
||||
|
||||
|
||||
25
packages/editor-skeleton/src/components/field/inlinetip.tsx
Normal file
25
packages/editor-skeleton/src/components/field/inlinetip.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,11 @@
|
||||
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 { Field, createField } from '../field';
|
||||
import PopupService from '../popup';
|
||||
import { SettingField, isSettingField, SettingTopEntry, SettingEntry } from '@ali/lowcode-designer';
|
||||
import { isSetterConfig, CustomView } from '@ali/lowcode-types';
|
||||
import { intl } from '../../locale';
|
||||
|
||||
@observer
|
||||
class SettingFieldView extends Component<{ field: SettingField }> {
|
||||
@ -41,26 +42,19 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
||||
setterType = setter;
|
||||
}
|
||||
let value = null;
|
||||
if (field.type === 'field') {
|
||||
if (defaultValue != null && !('defaultValue' in setterProps)) {
|
||||
setterProps.defaultValue = defaultValue;
|
||||
if (initialValue == null) {
|
||||
initialValue = defaultValue;
|
||||
}
|
||||
if (defaultValue != null && !('defaultValue' in setterProps)) {
|
||||
setterProps.defaultValue = defaultValue;
|
||||
if (initialValue == null) {
|
||||
initialValue = defaultValue;
|
||||
}
|
||||
if (field.valueState > 0) {
|
||||
value = field.getValue();
|
||||
} else {
|
||||
setterProps.multiValue = true;
|
||||
if (!('placeholder' in setterProps)) {
|
||||
// FIXME! move to locale file
|
||||
setterProps.placeholder = intl({
|
||||
type: 'i18n',
|
||||
'zh-CN': '多种值',
|
||||
'en-US': 'Multiple Value',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (field.valueState === -1) {
|
||||
setterProps.multiValue = true;
|
||||
if (!('placeholder' in setterProps)) {
|
||||
setterProps.placeholder = intl('Multiple Value');
|
||||
}
|
||||
} else {
|
||||
value = field.getValue();
|
||||
}
|
||||
|
||||
// todo: error handling
|
||||
@ -69,7 +63,10 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
||||
{
|
||||
title: field.title,
|
||||
collapsed: !field.expanded,
|
||||
valueState: field.isRequired ? 10 : field.valueState,
|
||||
onExpandChange: (expandState) => field.setExpanded(expandState),
|
||||
onClear: () => field.clearValue(),
|
||||
...extraProps,
|
||||
},
|
||||
createSetterContent(setterType, {
|
||||
...shallowIntl(setterProps),
|
||||
@ -95,7 +92,7 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
||||
value,
|
||||
});
|
||||
field.setValue(value);
|
||||
}
|
||||
},
|
||||
}),
|
||||
extraProps.forceInline ? 'plain' : extraProps.display,
|
||||
);
|
||||
|
||||
11
packages/editor-skeleton/src/icons/clear.tsx
Normal file
11
packages/editor-skeleton/src/icons/clear.tsx
Normal 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';
|
||||
@ -3,12 +3,7 @@ import { SVGIcon, IconProps } from "@ali/lowcode-utils";
|
||||
export function IconConvert(props: IconProps) {
|
||||
return (
|
||||
<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="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" />
|
||||
<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" />
|
||||
</SVGIcon>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,12 +5,17 @@ import { Button, Icon } from '@alifd/next';
|
||||
import Area from '../area';
|
||||
import { PanelConfig } from '../types';
|
||||
import Panel from '../widget/panel';
|
||||
import { Designer } from '@ali/lowcode-designer';
|
||||
|
||||
@observer
|
||||
export default class LeftFixedPane extends Component<{ area: Area<PanelConfig, Panel> }> {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
componentDidUpdate() {
|
||||
// FIXME: dirty fix, need deep think
|
||||
this.props.area.skeleton.editor.get(Designer)?.touchOffsetObserver();
|
||||
}
|
||||
render() {
|
||||
const { area } = this.props;
|
||||
const hideTitleBar = area.current?.config.props?.hideTitleBar;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Component, Fragment } from 'react';
|
||||
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 Area from '../area';
|
||||
import Panel from '../widget/panel';
|
||||
@ -12,7 +12,7 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
|
||||
}
|
||||
|
||||
private dispose?: () => void;
|
||||
// private focusing?: FocusingItem;
|
||||
private focusing?: Focusable;
|
||||
private shell: HTMLElement | null = null;
|
||||
componentDidMount() {
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
this.focusing = focusingTrack.create(this.shell!, {
|
||||
this.focusing = focusTracker.create({
|
||||
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: () => {
|
||||
this.props.area.setVisible(false);
|
||||
},
|
||||
onBlur: () => {
|
||||
this.props.area.setVisible(false);
|
||||
},
|
||||
// modal: boolean
|
||||
});
|
||||
*/
|
||||
|
||||
this.onEffect();
|
||||
}
|
||||
|
||||
onEffect() {
|
||||
/*
|
||||
const { area } = this.props;
|
||||
if (area.visible) {
|
||||
this.focusing?.active();
|
||||
} else {
|
||||
this.focusing?.suspense();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@ -53,7 +62,7 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// this.focusing?.purge();
|
||||
this.focusing?.purge();
|
||||
this.dispose?.();
|
||||
}
|
||||
|
||||
|
||||
@ -127,122 +127,6 @@ body {
|
||||
&.hidden {
|
||||
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;
|
||||
top: 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);
|
||||
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;
|
||||
display: none;
|
||||
// padding-top: 36px;
|
||||
@ -449,6 +334,12 @@ body {
|
||||
display: none;
|
||||
flex-shrink: 0;
|
||||
margin-left: 2px;
|
||||
position: relative;
|
||||
>.lc-panel {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
&.lc-area-visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
{
|
||||
"Binded: {expr}": "Binded: {expr}",
|
||||
"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"
|
||||
}
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
{
|
||||
"Binded: {expr}": "已绑定: {expr}",
|
||||
"Variable Binding": "变量绑定",
|
||||
"Switch Setter": "切换设置器"
|
||||
"Switch Setter": "切换设置器",
|
||||
"Multiple Value, Click to Clear": "多种值, 点击清除",
|
||||
"Required": "必填项",
|
||||
"Setted Value, Click to Clear": "已设置值,点击清除",
|
||||
"Multiple Value": "多种值"
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ export class Skeleton {
|
||||
}
|
||||
return this.createPanel(config);
|
||||
},
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
this.mainArea = new Area(
|
||||
@ -192,6 +192,7 @@ export class Skeleton {
|
||||
this.editor.emit(event, ...args);
|
||||
}
|
||||
|
||||
readonly widgets: IWidget[] = [];
|
||||
createWidget(config: IWidgetBaseConfig | IWidget) {
|
||||
if (isWidget(config)) {
|
||||
return config;
|
||||
@ -220,6 +221,7 @@ export class Skeleton {
|
||||
} else {
|
||||
widget = new Widget(this, config as WidgetConfig);
|
||||
}
|
||||
this.widgets.push(widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
|
||||
name: 'children',
|
||||
title: { type: 'i18n', 'zh-CN': '内容设置', 'en-US': 'Content' },
|
||||
setter: {
|
||||
componentName: 'MixinSetter',
|
||||
componentName: 'MixedSetter',
|
||||
props: {
|
||||
// TODO:
|
||||
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 eventsDefinition: any[] = [];
|
||||
const supportedLifecycles =
|
||||
events.supportedLifecycles ||
|
||||
supports.lifecycles ||
|
||||
(isRoot
|
||||
? /*[
|
||||
{
|
||||
@ -73,11 +73,11 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
|
||||
list: supportedLifecycles.map((event: any) => (typeof event === 'string' ? { name: event } : event)),
|
||||
});
|
||||
}
|
||||
if (events.supportedEvents) {
|
||||
if (supports.events) {
|
||||
eventsDefinition.push({
|
||||
type: 'events',
|
||||
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[] = [
|
||||
{
|
||||
title: { type: 'i18n', 'zh-CN': '属性', 'en-US': 'Props' },
|
||||
@ -132,15 +150,14 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
|
||||
items: propsGroup,
|
||||
},
|
||||
];
|
||||
const stylesGroup: FieldConfig[] = [];
|
||||
if (styles?.supportClassName) {
|
||||
if (supports.className) {
|
||||
stylesGroup.push({
|
||||
name: 'className',
|
||||
title: { type: 'i18n', 'zh-CN': '类名绑定', 'en-US': 'ClassName' },
|
||||
setter: 'ClassNameSetter',
|
||||
});
|
||||
}
|
||||
if (styles?.supportInlineStyle) {
|
||||
if (supports.style) {
|
||||
stylesGroup.push({
|
||||
name: 'style',
|
||||
title: { type: 'i18n', 'zh-CN': '行内样式', 'en-US': 'Style' },
|
||||
@ -183,79 +200,75 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
|
||||
});
|
||||
}
|
||||
|
||||
if (isRoot) {
|
||||
/*
|
||||
if (!isRoot) {
|
||||
if (supports.condition !== false) {
|
||||
advanceGroup.push({
|
||||
name: '___condition',
|
||||
title: { type: 'i18n', 'zh-CN': '是否渲染', 'en-US': 'Condition' },
|
||||
defaultValue: true,
|
||||
setter: [{
|
||||
componentName: 'BoolSetter',
|
||||
}, {
|
||||
componentName: 'VariableSetter'
|
||||
}],
|
||||
});
|
||||
}
|
||||
if (supports.loop !== false) {
|
||||
advanceGroup.push({
|
||||
name: '#loop',
|
||||
title: { type: 'i18n', 'zh-CN': '循环', 'en-US': 'Loop' },
|
||||
items: [
|
||||
{
|
||||
name: '___loop',
|
||||
title: { type: 'i18n', 'zh-CN': '循环数据', 'en-US': 'Loop Data' },
|
||||
defaultValue: [],
|
||||
setter: [{
|
||||
componentName: 'JsonSetter',
|
||||
props: {
|
||||
label: { type: 'i18n', 'zh-CN': '编辑数据', 'en-US': 'Edit Data'},
|
||||
},
|
||||
}, {
|
||||
componentName: 'VariableSetter'
|
||||
}],
|
||||
},
|
||||
{
|
||||
name: '___loopArgs.0',
|
||||
title: { type: 'i18n', 'zh-CN': '迭代变量名', 'en-US': 'Loop Item' },
|
||||
setter: {
|
||||
componentName: 'StringSetter',
|
||||
props: {
|
||||
placeholder: { type: 'i18n', 'zh-CN': '默认为: item', 'en-US': 'Defaults: item' },
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '___loopArgs.1',
|
||||
title: { type: 'i18n', 'zh-CN': '索引变量名', 'en-US': 'Loop Index' },
|
||||
setter: {
|
||||
componentName: 'StringSetter',
|
||||
props: {
|
||||
placeholder: { type: 'i18n', 'zh-CN': '默认为: index', 'en-US': 'Defaults: index' },
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'key',
|
||||
title: '循环 Key',
|
||||
setter: [{
|
||||
componentName: 'StringSetter',
|
||||
}, {
|
||||
componentName: 'VariableSetter'
|
||||
}],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
if (advanceGroup.length > 0) {
|
||||
combined.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',
|
||||
title: { type: 'i18n', 'zh-CN': '是否渲染', 'en-US': 'Condition' },
|
||||
setter: [{
|
||||
componentName: 'BoolSetter',
|
||||
props: {
|
||||
defaultValue: true,
|
||||
}
|
||||
}, {
|
||||
componentName: 'VariableSetter'
|
||||
}],
|
||||
},
|
||||
{
|
||||
name: '#loop',
|
||||
title: { type: 'i18n', 'zh-CN': '循环', 'en-US': 'Loop' },
|
||||
items: [
|
||||
{
|
||||
name: '___loop',
|
||||
title: { type: 'i18n', 'zh-CN': '循环数据', 'en-US': 'Loop Data' },
|
||||
setter: [{
|
||||
componentName: 'JsonSetter',
|
||||
props: {
|
||||
label: { type: 'i18n', 'zh-CN': '编辑数据', 'en-US': 'Edit Data'},
|
||||
},
|
||||
}, {
|
||||
componentName: 'VariableSetter'
|
||||
}],
|
||||
},
|
||||
{
|
||||
name: '___loopArgs.0',
|
||||
title: { type: 'i18n', 'zh-CN': '迭代变量名', 'en-US': 'Loop Item' },
|
||||
setter: {
|
||||
componentName: 'StringSetter',
|
||||
props: {
|
||||
placeholder: { type: 'i18n', 'zh-CN': '默认为: item', 'en-US': 'Defaults: item' },
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '___loopArgs.1',
|
||||
title: { type: 'i18n', 'zh-CN': '索引变量名', 'en-US': 'Loop Index' },
|
||||
setter: {
|
||||
componentName: 'StringSetter',
|
||||
props: {
|
||||
placeholder: { type: 'i18n', 'zh-CN': '默认为: index', 'en-US': 'Defaults: index' },
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'key',
|
||||
title: '循环 Key',
|
||||
setter: [{
|
||||
componentName: 'StringSetter',
|
||||
}, {
|
||||
componentName: 'VariableSetter'
|
||||
}],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
items: advanceGroup,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -143,7 +143,7 @@ function propTypeToSetter(propType: PropType): SetterType {
|
||||
};
|
||||
case 'oneOfType':
|
||||
return {
|
||||
componentName: 'MixinSetter',
|
||||
componentName: 'MixedSetter',
|
||||
props: {
|
||||
// TODO:
|
||||
// setters: (propType as OneOfType).value.map(item => propTypeToSetter(item)),
|
||||
@ -153,7 +153,7 @@ function propTypeToSetter(propType: PropType): SetterType {
|
||||
}
|
||||
|
||||
return {
|
||||
componentName: 'MixinSetter',
|
||||
componentName: 'MixedSetter',
|
||||
isRequired,
|
||||
};
|
||||
}
|
||||
@ -175,8 +175,8 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
|
||||
},
|
||||
};
|
||||
}
|
||||
const { component = {}, events = {}, styles = {} } = configure;
|
||||
const supportedEvents: any[] | null = (events as any).supportedEvents ? null : [];
|
||||
const { component = {}, supports = {} } = configure;
|
||||
const supportedEvents: any[] | null = (supports as any).events ? null : [];
|
||||
const props: FieldConfig[] = [];
|
||||
|
||||
metadata.props.forEach((prop) => {
|
||||
@ -197,21 +197,21 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
|
||||
name,
|
||||
description,
|
||||
});
|
||||
(events as any).supportedEvents = supportedEvents;
|
||||
(supports as any).events = supportedEvents;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === 'className' && (propType === 'string' || propType === 'any')) {
|
||||
if ((styles as any).supportClassName == null) {
|
||||
(styles as any).supportClassName = true;
|
||||
if ((supports as any).className == null) {
|
||||
(supports as any).className = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (name === 'style' && (propType === 'object' || propType === 'any')) {
|
||||
if ((styles as any).supportInlineStyle == null) {
|
||||
(styles as any).supportInlineStyle = true;
|
||||
if ((supports as any).style == null) {
|
||||
(supports as any).style = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -224,8 +224,7 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
|
||||
configure: {
|
||||
...configure,
|
||||
props,
|
||||
events,
|
||||
styles,
|
||||
supports,
|
||||
component,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ReactElement, ComponentType } from 'react';
|
||||
import { TitleContent, IconType, I18nData, TipContent } from '@ali/lowcode-types';
|
||||
import { IWidget } from './widget/widget';
|
||||
|
||||
export interface IWidgetBaseConfig {
|
||||
type: string;
|
||||
@ -16,6 +17,7 @@ export interface WidgetConfig extends IWidgetBaseConfig {
|
||||
type: "Widget";
|
||||
props?: {
|
||||
align?: "left" | "right" | "bottom" | "center" | "top";
|
||||
onInit?: (widget: IWidget) => void;
|
||||
};
|
||||
content?: string | ReactElement | ComponentType<any>; // children
|
||||
}
|
||||
@ -47,6 +49,7 @@ export function isDividerConfig(obj: any): obj is DividerConfig {
|
||||
export interface IDockBaseConfig extends IWidgetBaseConfig {
|
||||
props?: DockProps & {
|
||||
align?: "left" | "right" | "bottom" | "center" | "top";
|
||||
onInit?: (widget: IWidget) => void;
|
||||
};
|
||||
}
|
||||
|
||||
@ -95,7 +98,8 @@ export interface PanelProps {
|
||||
height?: number; // panel.props
|
||||
maxWidth?: number; // panel.props
|
||||
maxHeight?: number; // panel.props
|
||||
onInit?: () => any;
|
||||
condition?: (widget: IWidget) => any;
|
||||
onInit?: (widget: IWidget) => any;
|
||||
onDestroy?: () => any;
|
||||
shortcut?: string; // 只有在特定位置,可触发 toggle show
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ export default class Dock implements IWidget {
|
||||
this._body = createElement(DockView, props);
|
||||
}
|
||||
this.inited = true;
|
||||
|
||||
|
||||
return this._body;
|
||||
}
|
||||
|
||||
@ -54,6 +54,9 @@ export default class Dock implements IWidget {
|
||||
const { props = {}, name } = config;
|
||||
this.name = name;
|
||||
this.align = props.align;
|
||||
if (props.onInit) {
|
||||
props.onInit.call(this, this);
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(flag: boolean) {
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
import { obx, computed } from '@ali/lowcode-editor-core';
|
||||
import { uniqueId } from '@ali/lowcode-utils';
|
||||
import { createElement, ReactNode } from 'react';
|
||||
import { createElement, ReactNode, ReactInstance } from 'react';
|
||||
import { Skeleton } from '../skeleton';
|
||||
import { PanelDockConfig } from '../types';
|
||||
import Panel from './panel';
|
||||
import { PanelDockView, WidgetView } from '../components/widget-views';
|
||||
import { IWidget } from './widget';
|
||||
import { composeTitle } from './utils';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
|
||||
export default class PanelDock implements IWidget {
|
||||
readonly isWidget = true;
|
||||
readonly isPanelDock = true;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly align?: string;
|
||||
@ -31,13 +33,21 @@ export default class PanelDock implements IWidget {
|
||||
return this._body;
|
||||
}
|
||||
|
||||
private _shell: ReactInstance | null = null;
|
||||
get content(): ReactNode {
|
||||
return createElement(WidgetView, {
|
||||
widget: this,
|
||||
ref: (ref) => {
|
||||
this._shell = ref;
|
||||
},
|
||||
key: this.id,
|
||||
});
|
||||
}
|
||||
|
||||
getDOMNode() {
|
||||
return this._shell ? findDOMNode(this._shell) : null;
|
||||
}
|
||||
|
||||
@obx.ref private _visible: boolean = true;
|
||||
get visible() {
|
||||
return this._visible;
|
||||
@ -58,20 +68,24 @@ export default class PanelDock implements IWidget {
|
||||
this.name = name;
|
||||
this.id = uniqueId(`dock:${name}$`);
|
||||
this.panelName = config.panelName || name;
|
||||
this.align = props?.align;
|
||||
if (content) {
|
||||
const _panelProps: any = { ...panelProps };
|
||||
if (_panelProps.title == null && props) {
|
||||
_panelProps.title = composeTitle(props.title, undefined, props.description, true, true);
|
||||
}
|
||||
this._panel = this.skeleton.add({
|
||||
type: "Panel",
|
||||
type: 'Panel',
|
||||
name: this.panelName,
|
||||
props: _panelProps,
|
||||
contentProps,
|
||||
content,
|
||||
area: panelProps?.area
|
||||
area: panelProps?.area,
|
||||
}) as Panel;
|
||||
}
|
||||
if (props?.onInit) {
|
||||
props.onInit.call(this, this);
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(flag: boolean) {
|
||||
@ -117,3 +131,8 @@ export default class PanelDock implements IWidget {
|
||||
this.panel?.setActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function isPanelDock(obj: any): obj is PanelDock {
|
||||
return obj && obj.isPanelDock;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { EventEmitter } from 'events';
|
||||
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 { TitleContent } from '@ali/lowcode-types';
|
||||
import WidgetContainer from './widget-container';
|
||||
@ -9,20 +9,25 @@ import { TitledPanelView, TabsPanelView, PanelView } from '../components/widget-
|
||||
import { Skeleton } from '../skeleton';
|
||||
import { composeTitle } from './utils';
|
||||
import { IWidget } from './widget';
|
||||
import PanelDock, { isPanelDock } from './panel-dock';
|
||||
|
||||
export default class Panel implements IWidget {
|
||||
readonly isWidget = true;
|
||||
readonly name: string;
|
||||
readonly id: string;
|
||||
@obx.ref inited = false;
|
||||
@obx.ref private _actived = false;
|
||||
@obx.ref private _actived: boolean = false;
|
||||
private emitter = new EventEmitter();
|
||||
get actived(): boolean {
|
||||
return this._actived;
|
||||
}
|
||||
|
||||
get visible(): boolean {
|
||||
if (this.parent?.visible) {
|
||||
@computed get visible(): boolean {
|
||||
if (!this.parent || this.parent.visible) {
|
||||
const { props } = this.config;
|
||||
if (props?.condition) {
|
||||
return props.condition(this);
|
||||
}
|
||||
return this._actived;
|
||||
}
|
||||
return false;
|
||||
@ -30,10 +35,21 @@ export default class Panel implements IWidget {
|
||||
|
||||
readonly isPanel = true;
|
||||
|
||||
private _body?: ReactNode;
|
||||
get body() {
|
||||
this.initBody();
|
||||
return this._body;
|
||||
if (this.container) {
|
||||
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 {
|
||||
@ -79,30 +95,12 @@ export default class Panel implements IWidget {
|
||||
);
|
||||
content.forEach((item) => this.add(item));
|
||||
}
|
||||
if (props.onInit) {
|
||||
props.onInit.call(this, this);
|
||||
}
|
||||
// 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) {
|
||||
if (parent === this.parent) {
|
||||
return;
|
||||
@ -147,11 +145,11 @@ export default class Panel implements IWidget {
|
||||
return;
|
||||
}
|
||||
if (flag) {
|
||||
if (!this.inited) {
|
||||
this.initBody();
|
||||
}
|
||||
this._actived = true;
|
||||
this.parent?.active(this);
|
||||
if (!this.inited) {
|
||||
this.inited = true;
|
||||
}
|
||||
this.emitter.emit('activechange', true);
|
||||
} else if (this.inited) {
|
||||
this._actived = false;
|
||||
@ -172,6 +170,12 @@ export default class Panel implements IWidget {
|
||||
this.setActive(true);
|
||||
}
|
||||
|
||||
getAssocDocks(): PanelDock[] {
|
||||
return this.skeleton.widgets.filter(item => {
|
||||
return isPanelDock(item) && item.panelName === this.name;
|
||||
}) as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
@ -60,6 +60,9 @@ export default class Widget implements IWidget {
|
||||
const { props = {}, name } = config;
|
||||
this.name = name;
|
||||
this.align = props.align;
|
||||
if (props.onInit) {
|
||||
props.onInit.call(this, this);
|
||||
}
|
||||
}
|
||||
|
||||
getId() {
|
||||
|
||||
@ -3,17 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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>
|
||||
# [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)
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ali/lowcode-material-parser",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.3",
|
||||
"description": "material parser for Ali lowCode engine",
|
||||
"main": "lib/index.js",
|
||||
"files": [
|
||||
@ -54,7 +54,8 @@
|
||||
"lodash": "^4.17.15",
|
||||
"react-docgen": "^5.3.0",
|
||||
"semver": "^7.1.3",
|
||||
"short-uuid": "^3.1.1"
|
||||
"short-uuid": "^3.1.1",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npm.alibaba-inc.com"
|
||||
|
||||
@ -45,10 +45,10 @@ export async function genManifest(
|
||||
npm: {
|
||||
package: matScanModel.pkgName,
|
||||
version: matScanModel.pkgVersion,
|
||||
exportName: matParsedModel.componentName,
|
||||
exportName: matParsedModel.meta?.exportName || matParsedModel.componentName,
|
||||
main: matScanModel.mainFilePath,
|
||||
destructuring: false,
|
||||
subName: '',
|
||||
destructuring: matParsedModel.meta?.exportName !== 'default',
|
||||
subName: matParsedModel.meta?.subName || '',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -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));
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
@ -1,23 +1,23 @@
|
||||
import {
|
||||
propTypeHandler,
|
||||
contextTypeHandler,
|
||||
childContextTypeHandler,
|
||||
} from './propTypeHandler';
|
||||
import { propTypeHandler, contextTypeHandler, childContextTypeHandler } from './propTypeHandler';
|
||||
import defaultPropsHandler from './defaultPropsHandler';
|
||||
import flowTypeHandler from './flowTypeHandler';
|
||||
import componentMethodsHandler from './componentMethodsHandler';
|
||||
import preProcessHandler from './preProcessHandler';
|
||||
|
||||
const { handlers } = require('react-docgen');
|
||||
|
||||
const defaultHandlers = [
|
||||
preProcessHandler,
|
||||
propTypeHandler,
|
||||
contextTypeHandler,
|
||||
childContextTypeHandler,
|
||||
handlers.propTypeCompositionHandler,
|
||||
handlers.propDocBlockHandler,
|
||||
handlers.flowTypeHandler,
|
||||
flowTypeHandler,
|
||||
defaultPropsHandler,
|
||||
handlers.componentDocblockHandler,
|
||||
handlers.displayNameHandler,
|
||||
handlers.componentMethodsHandler,
|
||||
componentMethodsHandler,
|
||||
handlers.componentMethodsJsDocHandler,
|
||||
];
|
||||
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export default function preProcessHandler(documentation: any, path: any) {
|
||||
documentation.set('meta', path.__meta);
|
||||
}
|
||||
@ -14,6 +14,9 @@ export default function parse(params: { fileContent: string; filePath: string })
|
||||
return resolver(ast);
|
||||
},
|
||||
handlers,
|
||||
{
|
||||
filename: filePath,
|
||||
},
|
||||
);
|
||||
const coms = result.reduce((res: any[], info: any) => {
|
||||
if (!info || !info.props) return res;
|
||||
@ -29,6 +32,7 @@ export default function parse(params: { fileContent: string; filePath: string })
|
||||
res.push({
|
||||
componentName: info.displayName,
|
||||
props,
|
||||
meta: info.meta || {},
|
||||
});
|
||||
return res;
|
||||
}, []);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export default function checkIsIIFE(path: any) {
|
||||
return (
|
||||
path.value &&
|
||||
path.value.callee &&
|
||||
path.value.callee.type === 'FunctionExpression' &&
|
||||
path.node.type === 'CallExpression'
|
||||
|
||||
@ -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 };
|
||||
@ -7,11 +7,19 @@
|
||||
*/
|
||||
|
||||
import { namedTypes as t, visit } from 'ast-types';
|
||||
import { uniqBy } from 'lodash';
|
||||
import checkIsIIFE from './checkIsIIFE';
|
||||
import resolveHOC from './resolveHOC';
|
||||
import resolveIIFE from './resolveIIFE';
|
||||
import resolveImport from './resolveImport';
|
||||
import resolveImport, { isImportLike } from './resolveImport';
|
||||
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 {
|
||||
isExportsOrModuleAssignment,
|
||||
@ -20,8 +28,8 @@ const {
|
||||
isReactForwardRefCall,
|
||||
isStatelessComponent,
|
||||
normalizeClassDefinition,
|
||||
resolveExportDeclaration,
|
||||
resolveToValue,
|
||||
getMemberValuePath,
|
||||
} = require('react-docgen').utils;
|
||||
|
||||
function ignore() {
|
||||
@ -47,16 +55,14 @@ function resolveDefinition(definition: any) {
|
||||
} else if (isReactComponentClass(definition)) {
|
||||
normalizeClassDefinition(definition);
|
||||
return definition;
|
||||
} else if (
|
||||
isStatelessComponent(definition) ||
|
||||
isReactForwardRefCall(definition)
|
||||
) {
|
||||
} else if (isStatelessComponent(definition) || isReactForwardRefCall(definition)) {
|
||||
return definition;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getDefinition(definition: any): any {
|
||||
function getDefinition(definition: any, cache: ICache = {}): any {
|
||||
const { __meta: exportMeta = {} } = definition;
|
||||
if (checkIsIIFE(definition)) {
|
||||
definition = resolveToValue(resolveIIFE(definition));
|
||||
if (!isComponentDefinition(definition)) {
|
||||
@ -64,24 +70,195 @@ function getDefinition(definition: any): any {
|
||||
}
|
||||
} else {
|
||||
definition = resolveToValue(resolveHOC(definition));
|
||||
if (isComponentDefinition(definition)) {
|
||||
definition = makeProxy(definition, {
|
||||
__meta: exportMeta,
|
||||
});
|
||||
return definition;
|
||||
}
|
||||
if (checkIsIIFE(definition)) {
|
||||
definition = resolveToValue(resolveIIFE(definition));
|
||||
if (!isComponentDefinition(definition)) {
|
||||
definition = resolveTranspiledClass(definition);
|
||||
}
|
||||
} else if (t.SequenceExpression.check(definition.node)) {
|
||||
return getDefinition(
|
||||
resolveToValue(definition.get('expressions').get(0)),
|
||||
);
|
||||
return getDefinition(resolveToValue(definition.get('expressions').get(0)), cache);
|
||||
} else {
|
||||
definition = resolveImport(
|
||||
definition,
|
||||
findAllExportedComponentDefinition,
|
||||
);
|
||||
return resolveImport(definition, (ast: any, sourcePath: string) => {
|
||||
const importMeta: any[] = [];
|
||||
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;
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
@ -99,6 +276,8 @@ function getDefinition(definition: any): any {
|
||||
*/
|
||||
export default function findAllExportedComponentDefinition(ast: any) {
|
||||
const components: any[] = [];
|
||||
const cache: ICache = {};
|
||||
let programScope: any;
|
||||
|
||||
function exportDeclaration(path: any) {
|
||||
const definitions = resolveExportDeclaration(path)
|
||||
@ -106,7 +285,7 @@ export default function findAllExportedComponentDefinition(ast: any) {
|
||||
if (isComponentDefinition(definition)) {
|
||||
acc.push(definition);
|
||||
} else {
|
||||
definition = getDefinition(definition);
|
||||
definition = getDefinition(definition, cache);
|
||||
if (!Array.isArray(definition)) {
|
||||
definition = [definition];
|
||||
}
|
||||
@ -118,7 +297,11 @@ export default function findAllExportedComponentDefinition(ast: any) {
|
||||
}
|
||||
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) {
|
||||
return false;
|
||||
@ -132,6 +315,10 @@ export default function findAllExportedComponentDefinition(ast: any) {
|
||||
}
|
||||
|
||||
visit(ast, {
|
||||
visitProgram: function(path) {
|
||||
programScope = path.scope;
|
||||
return this.traverse(path);
|
||||
},
|
||||
visitFunctionDeclaration: ignore,
|
||||
visitFunctionExpression: ignore,
|
||||
visitClassDeclaration: ignore,
|
||||
@ -149,9 +336,7 @@ export default function findAllExportedComponentDefinition(ast: any) {
|
||||
visitExportNamedDeclaration: exportDeclaration,
|
||||
visitExportDefaultDeclaration: exportDeclaration,
|
||||
visitExportAllDeclaration: function(path) {
|
||||
components.push(
|
||||
...resolveImport(path, findAllExportedComponentDefinition),
|
||||
);
|
||||
components.push(...resolveImport(path, findAllExportedComponentDefinition));
|
||||
return false;
|
||||
},
|
||||
|
||||
@ -161,20 +346,44 @@ export default function findAllExportedComponentDefinition(ast: any) {
|
||||
if (!isExportsOrModuleAssignment(path)) {
|
||||
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
|
||||
// expression, something like React.createClass
|
||||
path = resolveToValue(path.get('right'));
|
||||
if (!isComponentDefinition(path)) {
|
||||
path = getDefinition(path);
|
||||
path = getDefinition(path, cache);
|
||||
}
|
||||
|
||||
const definition = resolveDefinition(path);
|
||||
if (definition && components.indexOf(definition) === -1) {
|
||||
components.push(definition);
|
||||
let definitions = resolveDefinition(path);
|
||||
if (!Array.isArray(definitions)) {
|
||||
definitions = [definitions];
|
||||
}
|
||||
definitions.forEach((definition: any) => {
|
||||
if (definition && components.indexOf(definition) === -1) {
|
||||
// if (definition.__meta) {
|
||||
definition = makeProxy(definition, {
|
||||
__meta: meta,
|
||||
});
|
||||
// }
|
||||
components.push(definition);
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
@ -1,21 +1,10 @@
|
||||
import { namedTypes as t } from 'ast-types';
|
||||
import fs from 'fs';
|
||||
import p from 'path';
|
||||
import getRoot from '../utils/getRoot';
|
||||
|
||||
function getRoot(node: any) {
|
||||
let root = node.parent;
|
||||
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)
|
||||
);
|
||||
export function isImportLike(node: any) {
|
||||
return t.ImportDeclaration.check(node) || t.ExportAllDeclaration.check(node) || t.ExportNamedDeclaration.check(node);
|
||||
}
|
||||
|
||||
function getPath(path: any, name: any) {
|
||||
@ -27,7 +16,7 @@ function getPath(path: any, name: any) {
|
||||
if (fs.existsSync(p.resolve(__path, name))) {
|
||||
name = name + '/index';
|
||||
}
|
||||
const suffix = suffixes.find(suf => {
|
||||
const suffix = suffixes.find((suf) => {
|
||||
return fs.existsSync(p.resolve(__path, name + suf));
|
||||
});
|
||||
if (!suffix) return;
|
||||
@ -35,9 +24,12 @@ function getPath(path: any, name: any) {
|
||||
}
|
||||
|
||||
const buildParser = require('react-docgen/dist/babelParser').default;
|
||||
const parser = buildParser();
|
||||
const suffixes = ['.js', '.jsx', '.ts', '.tsx'];
|
||||
|
||||
const cache: {
|
||||
[name: string]: any;
|
||||
} = {};
|
||||
|
||||
export default function resolveImport(path: any, callback: any) {
|
||||
let name;
|
||||
if (path.name === 'local') {
|
||||
@ -50,11 +42,19 @@ export default function resolveImport(path: any, callback: any) {
|
||||
if (name) {
|
||||
const __path = getPath(path, name);
|
||||
if (!__path) return path;
|
||||
const fileContent = fs.readFileSync(__path, 'utf8');
|
||||
const ast = parser.parse(fileContent);
|
||||
ast.__src = fileContent;
|
||||
ast.__path = __path;
|
||||
return callback(ast);
|
||||
let ast;
|
||||
if (!cache[__path]) {
|
||||
const fileContent = fs.readFileSync(__path, 'utf8');
|
||||
const parser = buildParser({ filename: __path });
|
||||
ast = parser.parse(fileContent);
|
||||
ast.__src = fileContent;
|
||||
ast.__path = __path;
|
||||
cache[__path] = ast;
|
||||
} else {
|
||||
ast = cache[__path];
|
||||
}
|
||||
|
||||
return callback(ast, __path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
export function transformType(type: any) {
|
||||
if (typeof type === 'string') return type;
|
||||
const { name, elements, value = elements, computed, required } = type;
|
||||
if (!value && !required) {
|
||||
return name;
|
||||
}
|
||||
export function transformType(itemType: any) {
|
||||
if (typeof itemType === 'string') return itemType;
|
||||
const { name, elements, value = elements, computed, required, type } = itemType;
|
||||
// if (!value && !required && !type) {
|
||||
// return name;
|
||||
// }
|
||||
if (computed !== undefined && value) {
|
||||
return eval(value);
|
||||
}
|
||||
@ -21,6 +21,7 @@ export function transformType(type: any) {
|
||||
case 'func':
|
||||
case 'symbol':
|
||||
case 'object':
|
||||
case 'null':
|
||||
break;
|
||||
case 'literal':
|
||||
return eval(value);
|
||||
@ -36,13 +37,24 @@ export function transformType(type: any) {
|
||||
case 'boolean':
|
||||
result.type = 'bool';
|
||||
break;
|
||||
case 'Array': {
|
||||
case 'Function':
|
||||
result.type = 'func';
|
||||
break;
|
||||
case 'unknown':
|
||||
result.type = 'any';
|
||||
break;
|
||||
case 'Array':
|
||||
case 'arrayOf': {
|
||||
result.type = 'arrayOf';
|
||||
const v = transformType(value[0]);
|
||||
if (typeof v.type === 'string') result.value = v.type;
|
||||
break;
|
||||
}
|
||||
case 'signature': {
|
||||
if (typeof type === 'string') {
|
||||
result.type = type;
|
||||
break;
|
||||
}
|
||||
result.type = 'shape';
|
||||
const {
|
||||
signature: { properties },
|
||||
@ -103,22 +115,28 @@ export function transformType(type: any) {
|
||||
result.value = name;
|
||||
break;
|
||||
}
|
||||
if (Object.keys(result).length === 1) {
|
||||
return result.type;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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 = {
|
||||
name,
|
||||
propType: transformType({
|
||||
};
|
||||
|
||||
if (type) {
|
||||
result.propType = transformType({
|
||||
...type,
|
||||
required: !!required,
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
if (description) {
|
||||
result.description = description;
|
||||
}
|
||||
if (defaultValue) {
|
||||
if (defaultValue !== undefined) {
|
||||
try {
|
||||
const value = eval(defaultValue.value);
|
||||
result.defaultValue = value;
|
||||
@ -127,6 +145,5 @@ export function transformItem(name: string, item: any) {
|
||||
if (result.propType === undefined) {
|
||||
delete result.propType;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
18
packages/material-parser/src/parse/utils/cache.ts
Normal file
18
packages/material-parser/src/parse/utils/cache.ts
Normal 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);
|
||||
}
|
||||
49
packages/material-parser/src/parse/utils/flowUtilityTypes.ts
Normal file
49
packages/material-parser/src/parse/utils/flowUtilityTypes.ts
Normal 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 };
|
||||
@ -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 };
|
||||
@ -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),
|
||||
};
|
||||
}
|
||||
14
packages/material-parser/src/parse/utils/getName.ts
Normal file
14
packages/material-parser/src/parse/utils/getName.ts
Normal 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;
|
||||
}
|
||||
7
packages/material-parser/src/parse/utils/getRoot.ts
Normal file
7
packages/material-parser/src/parse/utils/getRoot.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default function getRoot(path: any) {
|
||||
let root = path.parent;
|
||||
while (root.parent) {
|
||||
root = root.parent;
|
||||
}
|
||||
return root.node;
|
||||
}
|
||||
355
packages/material-parser/src/parse/utils/getTSType.js
Normal file
355
packages/material-parser/src/parse/utils/getTSType.js
Normal 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;
|
||||
}
|
||||
19
packages/material-parser/src/parse/utils/makeProxy.ts
Normal file
19
packages/material-parser/src/parse/utils/makeProxy.ts
Normal 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;
|
||||
@ -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;
|
||||
}
|
||||
@ -15,35 +15,8 @@ export interface IMaterialParsedModel {
|
||||
// filePath: string;
|
||||
componentName: string;
|
||||
props?: PropsSection['props'];
|
||||
// componentNames: {
|
||||
// exportedName: string;
|
||||
// localName: 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;
|
||||
// }[];
|
||||
meta?: {
|
||||
exportName?: string;
|
||||
subName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
14
packages/material-parser/src/types/Meta.ts
Normal file
14
packages/material-parser/src/types/Meta.ts
Normal 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
Binary file not shown.
@ -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",
|
||||
]
|
||||
`;
|
||||
21
packages/material-parser/test/fixtures/antd-component/components/__tests__/index.test.js
vendored
Normal file
21
packages/material-parser/test/fixtures/antd-component/components/__tests__/index.test.js
vendored
Normal 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();
|
||||
});
|
||||
});
|
||||
71
packages/material-parser/test/fixtures/antd-component/components/__tests__/util/domHook.ts
vendored
Normal file
71
packages/material-parser/test/fixtures/antd-component/components/__tests__/util/domHook.ts
vendored
Normal 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,
|
||||
});
|
||||
}
|
||||
13
packages/material-parser/test/fixtures/antd-component/components/_util/__tests__/easings.test.js
vendored
Normal file
13
packages/material-parser/test/fixtures/antd-component/components/_util/__tests__/easings.test.js
vendored
Normal 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]);
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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\\""`);
|
||||
});
|
||||
});
|
||||
206
packages/material-parser/test/fixtures/antd-component/components/_util/__tests__/util.test.js
vendored
Normal file
206
packages/material-parser/test/fixtures/antd-component/components/_util/__tests__/util.test.js
vendored
Normal 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
Loading…
x
Reference in New Issue
Block a user