@logicflow/react-node-registry 1.2.0-alpha.7 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/package.json +13 -6
- package/.turbo/turbo-build.log +0 -34
- package/CHANGELOG.md +0 -251
- package/rollup.config.js +0 -52
- package/src/components/Container.tsx +0 -33
- package/src/components/TitleBar.tsx +0 -149
- package/src/index.less +0 -1
- package/src/index.ts +0 -5
- package/src/model.ts +0 -140
- package/src/portal.ts +0 -79
- package/src/registry.ts +0 -47
- package/src/style/index.less +0 -140
- package/src/style/raw.ts +0 -129
- package/src/view.ts +0 -190
- package/src/wrapper.tsx +0 -76
- package/stats.html +0 -4842
- package/tsconfig.json +0 -21
package/src/model.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import LogicFlow, {
|
|
2
|
-
HtmlNodeModel,
|
|
3
|
-
IHtmlNodeProperties,
|
|
4
|
-
BaseNodeModel,
|
|
5
|
-
GraphModel,
|
|
6
|
-
} from '@logicflow/core'
|
|
7
|
-
import { cloneDeep, isNil, isArray, isEmpty } from 'lodash-es'
|
|
8
|
-
|
|
9
|
-
import NodeConfig = LogicFlow.NodeConfig
|
|
10
|
-
|
|
11
|
-
export type NodeAction = {
|
|
12
|
-
name: string
|
|
13
|
-
callback?: (node: BaseNodeModel, graph: GraphModel) => void
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ReactCustomProperties extends IHtmlNodeProperties {
|
|
17
|
-
// 形状属性
|
|
18
|
-
width?: number
|
|
19
|
-
height?: number
|
|
20
|
-
radius?: number
|
|
21
|
-
|
|
22
|
-
// 文字位置属性
|
|
23
|
-
refX?: number
|
|
24
|
-
refY?: number
|
|
25
|
-
|
|
26
|
-
// 样式属性
|
|
27
|
-
style?: LogicFlow.CommonTheme
|
|
28
|
-
textStyle?: LogicFlow.TextNodeTheme
|
|
29
|
-
// 标题配置
|
|
30
|
-
_showTitle?: boolean
|
|
31
|
-
_title?: string
|
|
32
|
-
_icon?: string
|
|
33
|
-
_titleHeight?: number
|
|
34
|
-
_expanded?: boolean
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export class ReactNodeModel<
|
|
38
|
-
P extends ReactCustomProperties = ReactCustomProperties,
|
|
39
|
-
> extends HtmlNodeModel<P> {
|
|
40
|
-
private __baseHeight?: number
|
|
41
|
-
public __actions?: {
|
|
42
|
-
name: string
|
|
43
|
-
callback?: (node: BaseNodeModel, graph: GraphModel) => void
|
|
44
|
-
}[]
|
|
45
|
-
constructor(data: NodeConfig<P>, graphModel: GraphModel) {
|
|
46
|
-
super(data, graphModel)
|
|
47
|
-
const { properties } = data
|
|
48
|
-
if (properties) {
|
|
49
|
-
const { _showTitle = false, style = {} } = properties
|
|
50
|
-
if (_showTitle) {
|
|
51
|
-
this.minWidth = 160
|
|
52
|
-
this.minHeight = 80
|
|
53
|
-
this.text.editable = false
|
|
54
|
-
// 判断当前节点宽高是否小于最小宽高,如果是,强制设置为最小宽高
|
|
55
|
-
const newWidth = this.width < this.minWidth ? this.minWidth : this.width
|
|
56
|
-
const newHeight =
|
|
57
|
-
this.height < this.minHeight ? this.minHeight : this.height
|
|
58
|
-
|
|
59
|
-
this.setProperties({
|
|
60
|
-
_expanded: false,
|
|
61
|
-
...properties,
|
|
62
|
-
style: {
|
|
63
|
-
overflow: 'visible',
|
|
64
|
-
...cloneDeep(style),
|
|
65
|
-
},
|
|
66
|
-
width: newWidth,
|
|
67
|
-
height: newHeight,
|
|
68
|
-
})
|
|
69
|
-
this.setNodeActions([
|
|
70
|
-
{
|
|
71
|
-
name: '复制',
|
|
72
|
-
callback: (nodeModel: BaseNodeModel, graphModel: GraphModel) => {
|
|
73
|
-
graphModel.cloneNode(nodeModel.id)
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: '删除',
|
|
78
|
-
callback: (nodeModel: BaseNodeModel, graphModel: GraphModel) => {
|
|
79
|
-
graphModel.deleteNode(nodeModel.id)
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
])
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
setAttributes() {
|
|
88
|
-
const { width, height, radius, _showTitle, _titleHeight } = this.properties
|
|
89
|
-
if (!isNil(width)) {
|
|
90
|
-
this.width = width
|
|
91
|
-
}
|
|
92
|
-
if (!isNil(height)) {
|
|
93
|
-
this.height = height
|
|
94
|
-
}
|
|
95
|
-
if (!isNil(radius)) {
|
|
96
|
-
this.radius = radius
|
|
97
|
-
}
|
|
98
|
-
if (this.__baseHeight === undefined) {
|
|
99
|
-
this.__baseHeight = isNil(height) ? this.height : height
|
|
100
|
-
}
|
|
101
|
-
const extra = _showTitle ? (_titleHeight ?? 28) : 0
|
|
102
|
-
const base = isNil(height) ? this.__baseHeight : height
|
|
103
|
-
this.height = base + extra
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
getTextStyle(): LogicFlow.TextNodeTheme {
|
|
107
|
-
// const { x, y, width, height } = this
|
|
108
|
-
const { refX = 0, refY = 0, textStyle } = this.properties
|
|
109
|
-
const style = super.getTextStyle()
|
|
110
|
-
|
|
111
|
-
// 通过 transform 重新设置 text 的位置
|
|
112
|
-
return {
|
|
113
|
-
...style,
|
|
114
|
-
...(cloneDeep(textStyle) || {}),
|
|
115
|
-
transform: `matrix(1 0 0 1 ${refX} ${refY})`,
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
getNodeStyle(): LogicFlow.CommonTheme {
|
|
120
|
-
const style = super.getNodeStyle()
|
|
121
|
-
const {
|
|
122
|
-
style: customNodeStyle,
|
|
123
|
-
// radius = 0, // 第二种方式,设置圆角
|
|
124
|
-
} = this.properties
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
...style,
|
|
128
|
-
...(cloneDeep(customNodeStyle) || {}),
|
|
129
|
-
// rx: radius,
|
|
130
|
-
// ry: radius,
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
setNodeActions(actions: NodeAction[]) {
|
|
135
|
-
this.__actions =
|
|
136
|
-
isArray(actions) && !isEmpty(actions) ? cloneDeep(actions) : []
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export default ReactNodeModel
|
package/src/portal.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import React, { useReducer } from 'react'
|
|
2
|
-
|
|
3
|
-
export namespace Portal {
|
|
4
|
-
let active = false
|
|
5
|
-
let dispatch: React.Dispatch<Action>
|
|
6
|
-
|
|
7
|
-
interface Action {
|
|
8
|
-
type: 'add' | 'remove'
|
|
9
|
-
payload: Partial<Payload>
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Payload {
|
|
13
|
-
id: string
|
|
14
|
-
portal: React.ReactPortal
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const reducer = (state: Payload[], action: Action) => {
|
|
18
|
-
const payload = action.payload as Payload
|
|
19
|
-
switch (action.type) {
|
|
20
|
-
case 'add': {
|
|
21
|
-
const index = state.findIndex((item) => item.id === payload.id)
|
|
22
|
-
if (index >= 0) {
|
|
23
|
-
state[index] = payload
|
|
24
|
-
return [...state]
|
|
25
|
-
}
|
|
26
|
-
return [...state, payload]
|
|
27
|
-
}
|
|
28
|
-
case 'remove': {
|
|
29
|
-
const index = state.findIndex((item) => item.id === payload.id)
|
|
30
|
-
if (index >= 0) {
|
|
31
|
-
const result = [...state]
|
|
32
|
-
result.splice(index, 1)
|
|
33
|
-
return result
|
|
34
|
-
}
|
|
35
|
-
break
|
|
36
|
-
}
|
|
37
|
-
default: {
|
|
38
|
-
break
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return state
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function connect(id: string, portal: React.ReactPortal) {
|
|
45
|
-
if (active) {
|
|
46
|
-
dispatch({
|
|
47
|
-
type: 'add',
|
|
48
|
-
payload: {
|
|
49
|
-
id,
|
|
50
|
-
portal,
|
|
51
|
-
},
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function disconnect(id: string) {
|
|
57
|
-
if (active) {
|
|
58
|
-
dispatch({
|
|
59
|
-
type: 'remove',
|
|
60
|
-
payload: { id },
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function isActive() {
|
|
66
|
-
return active
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function getProvider() {
|
|
70
|
-
return () => {
|
|
71
|
-
active = true
|
|
72
|
-
const [items, mutate] = useReducer(reducer, [])
|
|
73
|
-
dispatch = mutate
|
|
74
|
-
return React.createElement(React.Fragment, {
|
|
75
|
-
children: items.map((item) => item.portal),
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
package/src/registry.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import LogicFlow, { BaseNodeModel, GraphModel } from '@logicflow/core'
|
|
2
|
-
import ReactNodeView from './view'
|
|
3
|
-
import ReactNodeModel from './model'
|
|
4
|
-
import RegisterConfig = LogicFlow.RegisterConfig
|
|
5
|
-
|
|
6
|
-
export type ReactNodeProps = {
|
|
7
|
-
node: BaseNodeModel
|
|
8
|
-
graph: GraphModel
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type ReactNodeConfig = {
|
|
12
|
-
type: string
|
|
13
|
-
component: React.ComponentType<ReactNodeProps>
|
|
14
|
-
effect?: (keyof LogicFlow.PropertiesType)[]
|
|
15
|
-
} & Partial<RegisterConfig>
|
|
16
|
-
|
|
17
|
-
export const reactNodesMap: Record<
|
|
18
|
-
string,
|
|
19
|
-
{
|
|
20
|
-
component: React.ComponentType<ReactNodeProps>
|
|
21
|
-
effect?: (keyof LogicFlow.PropertiesType)[]
|
|
22
|
-
}
|
|
23
|
-
> = {}
|
|
24
|
-
|
|
25
|
-
export function register(config: ReactNodeConfig, lf: LogicFlow) {
|
|
26
|
-
const {
|
|
27
|
-
type,
|
|
28
|
-
component,
|
|
29
|
-
effect,
|
|
30
|
-
view: CustomNodeView,
|
|
31
|
-
model: CustomNodeModel,
|
|
32
|
-
} = config
|
|
33
|
-
|
|
34
|
-
if (!type) {
|
|
35
|
-
throw new Error('You should specify type in config')
|
|
36
|
-
}
|
|
37
|
-
reactNodesMap[type] = {
|
|
38
|
-
component,
|
|
39
|
-
effect,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
lf.register({
|
|
43
|
-
type,
|
|
44
|
-
view: CustomNodeView || ReactNodeView,
|
|
45
|
-
model: CustomNodeModel || ReactNodeModel,
|
|
46
|
-
})
|
|
47
|
-
}
|
package/src/style/index.less
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
.lf-vue-node-container {
|
|
2
|
-
position: relative;
|
|
3
|
-
display: flex;
|
|
4
|
-
flex-direction: column;
|
|
5
|
-
box-sizing: border-box;
|
|
6
|
-
padding: 6px;
|
|
7
|
-
color: #474747;
|
|
8
|
-
border-radius: 12px;
|
|
9
|
-
box-shadow: 0 0 10px #cad2e15f;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.lf-vue-node-content-wrap {
|
|
13
|
-
display: flex;
|
|
14
|
-
flex: 1 1 auto;
|
|
15
|
-
justify-content: center;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
.lf-vue-node-title {
|
|
19
|
-
display: flex;
|
|
20
|
-
align-items: flex-start;
|
|
21
|
-
justify-content: space-between;
|
|
22
|
-
box-sizing: border-box;
|
|
23
|
-
margin-bottom: 4px;
|
|
24
|
-
padding: 0 8px;
|
|
25
|
-
backdrop-filter: saturate(180%) blur(4px);
|
|
26
|
-
|
|
27
|
-
&-expanded {
|
|
28
|
-
margin-bottom: 6px;
|
|
29
|
-
padding-bottom: 8px;
|
|
30
|
-
border-bottom: 1px solid #eaeaea;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
@supports not (backdrop-filter: blur(1px)) {
|
|
35
|
-
.lf-vue-node-title {
|
|
36
|
-
backdrop-filter: none;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.lf-vue-node-title-left {
|
|
41
|
-
display: flex;
|
|
42
|
-
gap: 6px;
|
|
43
|
-
align-items: center;
|
|
44
|
-
min-width: 0;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.lf-vue-node-title-icon {
|
|
48
|
-
display: inline-block;
|
|
49
|
-
width: 16px;
|
|
50
|
-
height: 16px;
|
|
51
|
-
color: #666;
|
|
52
|
-
font-style: normal;
|
|
53
|
-
line-height: 16px;
|
|
54
|
-
text-align: center;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.lf-vue-node-title-text {
|
|
58
|
-
overflow: hidden;
|
|
59
|
-
color: #333;
|
|
60
|
-
font-weight: 500;
|
|
61
|
-
font-size: 14px;
|
|
62
|
-
white-space: nowrap;
|
|
63
|
-
text-overflow: ellipsis;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.lf-vue-node-title-actions {
|
|
67
|
-
display: flex;
|
|
68
|
-
gap: 6px;
|
|
69
|
-
align-items: center;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.lf-vue-node-title-expand,
|
|
73
|
-
.lf-vue-node-title-more {
|
|
74
|
-
display: inline-flex;
|
|
75
|
-
align-items: center;
|
|
76
|
-
justify-content: center;
|
|
77
|
-
width: 20px;
|
|
78
|
-
height: 20px;
|
|
79
|
-
padding: 2px;
|
|
80
|
-
background: transparent;
|
|
81
|
-
border: none;
|
|
82
|
-
border-radius: 4px;
|
|
83
|
-
cursor: pointer;
|
|
84
|
-
transition: background 0.15s ease;
|
|
85
|
-
appearance: none;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.lf-vue-node-title-expand:hover,
|
|
89
|
-
.lf-vue-node-title-more:hover {
|
|
90
|
-
background: rgb(0 0 0 / 6%);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.lf-vue-node-title-expand-icon {
|
|
94
|
-
color: #666;
|
|
95
|
-
font-style: normal;
|
|
96
|
-
transition: transform 0.3s ease;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.lf-vue-node-title-more-icon {
|
|
100
|
-
color: #666;
|
|
101
|
-
font-style: normal;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.lf-vue-node-title-tooltip {
|
|
105
|
-
position: absolute;
|
|
106
|
-
top: -50px;
|
|
107
|
-
right: -135px;
|
|
108
|
-
min-width: 120px;
|
|
109
|
-
max-width: 240px;
|
|
110
|
-
padding: 6px 8px;
|
|
111
|
-
background: #fff;
|
|
112
|
-
border: 1px solid rgb(0 0 0 / 10%);
|
|
113
|
-
border-radius: 6px;
|
|
114
|
-
box-shadow: 0 6px 20px rgb(0 0 0 / 12%);
|
|
115
|
-
transform: translateY(calc(100% + 4px));
|
|
116
|
-
transition:
|
|
117
|
-
opacity 0.15s ease,
|
|
118
|
-
transform 0.15s ease;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
.lf-vue-node-title-tooltip-list {
|
|
122
|
-
display: flex;
|
|
123
|
-
flex-direction: column;
|
|
124
|
-
gap: 4px;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.lf-vue-node-title-tooltip-item {
|
|
128
|
-
display: flex;
|
|
129
|
-
align-items: center;
|
|
130
|
-
justify-content: flex-start;
|
|
131
|
-
padding: 6px;
|
|
132
|
-
color: #333;
|
|
133
|
-
font-size: 12px;
|
|
134
|
-
border-radius: 4px;
|
|
135
|
-
cursor: pointer;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.lf-vue-node-title-tooltip-item:hover {
|
|
139
|
-
background: rgb(0 0 0 / 5%);
|
|
140
|
-
}
|
package/src/style/raw.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Auto generated file, do not modify it!
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export const content = `.lf-vue-node-container {
|
|
8
|
-
position: relative;
|
|
9
|
-
display: flex;
|
|
10
|
-
flex-direction: column;
|
|
11
|
-
box-sizing: border-box;
|
|
12
|
-
padding: 6px;
|
|
13
|
-
color: #474747;
|
|
14
|
-
border-radius: 12px;
|
|
15
|
-
box-shadow: 0 0 10px #cad2e15f;
|
|
16
|
-
}
|
|
17
|
-
.lf-vue-node-content-wrap {
|
|
18
|
-
display: flex;
|
|
19
|
-
flex: 1 1 auto;
|
|
20
|
-
justify-content: center;
|
|
21
|
-
}
|
|
22
|
-
.lf-vue-node-title {
|
|
23
|
-
display: flex;
|
|
24
|
-
align-items: flex-start;
|
|
25
|
-
justify-content: space-between;
|
|
26
|
-
box-sizing: border-box;
|
|
27
|
-
margin-bottom: 4px;
|
|
28
|
-
padding: 0 8px;
|
|
29
|
-
backdrop-filter: saturate(180%) blur(4px);
|
|
30
|
-
}
|
|
31
|
-
.lf-vue-node-title-expanded {
|
|
32
|
-
margin-bottom: 6px;
|
|
33
|
-
padding-bottom: 8px;
|
|
34
|
-
border-bottom: 1px solid #eaeaea;
|
|
35
|
-
}
|
|
36
|
-
@supports not (backdrop-filter: blur(1px)) {
|
|
37
|
-
.lf-vue-node-title {
|
|
38
|
-
backdrop-filter: none;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
.lf-vue-node-title-left {
|
|
42
|
-
display: flex;
|
|
43
|
-
gap: 6px;
|
|
44
|
-
align-items: center;
|
|
45
|
-
min-width: 0;
|
|
46
|
-
}
|
|
47
|
-
.lf-vue-node-title-icon {
|
|
48
|
-
display: inline-block;
|
|
49
|
-
width: 16px;
|
|
50
|
-
height: 16px;
|
|
51
|
-
color: #666;
|
|
52
|
-
font-style: normal;
|
|
53
|
-
line-height: 16px;
|
|
54
|
-
text-align: center;
|
|
55
|
-
}
|
|
56
|
-
.lf-vue-node-title-text {
|
|
57
|
-
overflow: hidden;
|
|
58
|
-
color: #333;
|
|
59
|
-
font-weight: 500;
|
|
60
|
-
font-size: 14px;
|
|
61
|
-
white-space: nowrap;
|
|
62
|
-
text-overflow: ellipsis;
|
|
63
|
-
}
|
|
64
|
-
.lf-vue-node-title-actions {
|
|
65
|
-
display: flex;
|
|
66
|
-
gap: 6px;
|
|
67
|
-
align-items: center;
|
|
68
|
-
}
|
|
69
|
-
.lf-vue-node-title-expand,
|
|
70
|
-
.lf-vue-node-title-more {
|
|
71
|
-
display: inline-flex;
|
|
72
|
-
align-items: center;
|
|
73
|
-
justify-content: center;
|
|
74
|
-
width: 20px;
|
|
75
|
-
height: 20px;
|
|
76
|
-
padding: 2px;
|
|
77
|
-
background: transparent;
|
|
78
|
-
border: none;
|
|
79
|
-
border-radius: 4px;
|
|
80
|
-
cursor: pointer;
|
|
81
|
-
transition: background 0.15s ease;
|
|
82
|
-
appearance: none;
|
|
83
|
-
}
|
|
84
|
-
.lf-vue-node-title-expand:hover,
|
|
85
|
-
.lf-vue-node-title-more:hover {
|
|
86
|
-
background: rgba(0, 0, 0, 0.06);
|
|
87
|
-
}
|
|
88
|
-
.lf-vue-node-title-expand-icon {
|
|
89
|
-
color: #666;
|
|
90
|
-
font-style: normal;
|
|
91
|
-
transition: transform 0.3s ease;
|
|
92
|
-
}
|
|
93
|
-
.lf-vue-node-title-more-icon {
|
|
94
|
-
color: #666;
|
|
95
|
-
font-style: normal;
|
|
96
|
-
}
|
|
97
|
-
.lf-vue-node-title-tooltip {
|
|
98
|
-
position: absolute;
|
|
99
|
-
top: -50px;
|
|
100
|
-
right: -135px;
|
|
101
|
-
min-width: 120px;
|
|
102
|
-
max-width: 240px;
|
|
103
|
-
padding: 6px 8px;
|
|
104
|
-
background: #fff;
|
|
105
|
-
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
106
|
-
border-radius: 6px;
|
|
107
|
-
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
|
|
108
|
-
transform: translateY(calc(100% + 4px));
|
|
109
|
-
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
110
|
-
}
|
|
111
|
-
.lf-vue-node-title-tooltip-list {
|
|
112
|
-
display: flex;
|
|
113
|
-
flex-direction: column;
|
|
114
|
-
gap: 4px;
|
|
115
|
-
}
|
|
116
|
-
.lf-vue-node-title-tooltip-item {
|
|
117
|
-
display: flex;
|
|
118
|
-
align-items: center;
|
|
119
|
-
justify-content: flex-start;
|
|
120
|
-
padding: 6px;
|
|
121
|
-
color: #333;
|
|
122
|
-
font-size: 12px;
|
|
123
|
-
border-radius: 4px;
|
|
124
|
-
cursor: pointer;
|
|
125
|
-
}
|
|
126
|
-
.lf-vue-node-title-tooltip-item:hover {
|
|
127
|
-
background: rgba(0, 0, 0, 0.05);
|
|
128
|
-
}
|
|
129
|
-
`
|
package/src/view.ts
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { createElement, ReactPortal } from 'react'
|
|
2
|
-
import { createRoot, Root } from 'react-dom/client'
|
|
3
|
-
import { HtmlNode } from '@logicflow/core'
|
|
4
|
-
import {
|
|
5
|
-
throttle,
|
|
6
|
-
round,
|
|
7
|
-
get,
|
|
8
|
-
isFunction,
|
|
9
|
-
isArray,
|
|
10
|
-
clamp,
|
|
11
|
-
isNumber,
|
|
12
|
-
} from 'lodash-es'
|
|
13
|
-
import { Wrapper } from './wrapper'
|
|
14
|
-
import { Portal } from './portal'
|
|
15
|
-
import { createPortal } from 'react-dom'
|
|
16
|
-
|
|
17
|
-
export class ReactNodeView extends HtmlNode {
|
|
18
|
-
root?: Root
|
|
19
|
-
private containerEl?: HTMLElement
|
|
20
|
-
private __resizeObserver?: ResizeObserver
|
|
21
|
-
private __resizeRafId?: number
|
|
22
|
-
private __lastWidth?: number
|
|
23
|
-
private __lastHeight?: number
|
|
24
|
-
private __fallbackUnlisten?: () => void
|
|
25
|
-
private __throttledUpdate = throttle(() => this.measureAndUpdate(), 80)
|
|
26
|
-
|
|
27
|
-
protected targetId() {
|
|
28
|
-
return `${this.props.graphModel.flowId}:${this.props.model.id}`
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
componentWillUnmount() {
|
|
32
|
-
super.componentWillUnmount()
|
|
33
|
-
this.unmount()
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
setHtml(rootEl: SVGForeignObjectElement) {
|
|
37
|
-
const existed = rootEl.querySelector(
|
|
38
|
-
'.custom-react-node-content',
|
|
39
|
-
) as HTMLElement | null
|
|
40
|
-
if (existed) {
|
|
41
|
-
this.containerEl = existed
|
|
42
|
-
} else {
|
|
43
|
-
const el = document.createElement('div')
|
|
44
|
-
el.className = 'custom-react-node-content'
|
|
45
|
-
this.containerEl = el
|
|
46
|
-
this.renderReactComponent(el)
|
|
47
|
-
rootEl.appendChild(el)
|
|
48
|
-
}
|
|
49
|
-
this.startResizeObserver()
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// confirmUpdate(_rootEl: SVGForeignObjectElement) {
|
|
53
|
-
// // TODO: 如有需要,可以先通过继承的方式,自定义该节点的更新逻辑;我们后续会根据实际需求,丰富该功能
|
|
54
|
-
// console.log('_rootEl', _rootEl)
|
|
55
|
-
// }
|
|
56
|
-
|
|
57
|
-
protected renderReactComponent(container: HTMLElement) {
|
|
58
|
-
this.unmountReactComponent()
|
|
59
|
-
const { model, graphModel } = this.props
|
|
60
|
-
|
|
61
|
-
if (container) {
|
|
62
|
-
// 基于自定义节点新建 React 元素
|
|
63
|
-
const elem = createElement(Wrapper, {
|
|
64
|
-
node: model,
|
|
65
|
-
graph: graphModel,
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
if (Portal.isActive()) {
|
|
69
|
-
// 使用 Portal
|
|
70
|
-
const portal = createPortal(elem, container, model.id) as ReactPortal
|
|
71
|
-
Portal.connect(this.targetId(), portal)
|
|
72
|
-
} else {
|
|
73
|
-
// 创建 Root 元素
|
|
74
|
-
this.root = createRoot(container)
|
|
75
|
-
this.root.render(elem)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
protected unmountReactComponent() {
|
|
81
|
-
if (this.rootEl && this.root) {
|
|
82
|
-
this.stopResizeObserver()
|
|
83
|
-
this.root.unmount()
|
|
84
|
-
this.root = undefined
|
|
85
|
-
this.rootEl.innerHTML = ''
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// DONE: 是否需要 unmount 或 destroy 方法,在销毁后做一些处理
|
|
90
|
-
unmount() {
|
|
91
|
-
this.unmountReactComponent()
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private measureAndUpdate = () => {
|
|
95
|
-
try {
|
|
96
|
-
const root = this.containerEl as HTMLElement
|
|
97
|
-
if (!root) return
|
|
98
|
-
const target = (root.firstElementChild as HTMLElement) || root
|
|
99
|
-
const rect = target.getBoundingClientRect()
|
|
100
|
-
const width = round(rect.width)
|
|
101
|
-
const height = round(rect.height)
|
|
102
|
-
if (width <= 0 || height <= 0) return
|
|
103
|
-
if (width === this.__lastWidth && height === this.__lastHeight) return
|
|
104
|
-
this.__lastWidth = width
|
|
105
|
-
this.__lastHeight = height
|
|
106
|
-
const props = this.props.model.properties as any
|
|
107
|
-
const extra = get(props, '_showTitle')
|
|
108
|
-
? isNumber(get(props, '_titleHeight'))
|
|
109
|
-
? get(props, '_titleHeight')
|
|
110
|
-
: 28
|
|
111
|
-
: 0
|
|
112
|
-
const baseHeight = clamp(height - extra, 1, Number.MAX_SAFE_INTEGER)
|
|
113
|
-
this.props.model.setProperties({ width, height: baseHeight })
|
|
114
|
-
} catch (err) {
|
|
115
|
-
console.error('measureAndUpdate error', err)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private startResizeObserver() {
|
|
120
|
-
const root = this.containerEl as HTMLElement
|
|
121
|
-
if (!root) return
|
|
122
|
-
try {
|
|
123
|
-
if (isFunction((window as any).ResizeObserver)) {
|
|
124
|
-
this.__resizeObserver = new (window as any).ResizeObserver(
|
|
125
|
-
(entries: any[]) => {
|
|
126
|
-
if (!isArray(entries) || !entries.length) return
|
|
127
|
-
if (this.__resizeRafId) cancelAnimationFrame(this.__resizeRafId)
|
|
128
|
-
this.__resizeRafId = requestAnimationFrame(this.__throttledUpdate)
|
|
129
|
-
},
|
|
130
|
-
)
|
|
131
|
-
const target = (root.firstElementChild as HTMLElement) || root
|
|
132
|
-
this.__resizeObserver?.observe(target)
|
|
133
|
-
} else {
|
|
134
|
-
window.addEventListener('resize', () => this.__throttledUpdate())
|
|
135
|
-
this.__fallbackUnlisten = () =>
|
|
136
|
-
window.removeEventListener('resize', () => this.__throttledUpdate())
|
|
137
|
-
}
|
|
138
|
-
} catch (err) {
|
|
139
|
-
console.error('startResizeObserver error', err)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private stopResizeObserver() {
|
|
144
|
-
try {
|
|
145
|
-
if (this.__resizeObserver) {
|
|
146
|
-
this.__resizeObserver.disconnect()
|
|
147
|
-
this.__resizeObserver = undefined
|
|
148
|
-
}
|
|
149
|
-
if (this.__resizeRafId) {
|
|
150
|
-
cancelAnimationFrame(this.__resizeRafId)
|
|
151
|
-
this.__resizeRafId = undefined
|
|
152
|
-
}
|
|
153
|
-
if (this.__fallbackUnlisten) {
|
|
154
|
-
this.__fallbackUnlisten()
|
|
155
|
-
this.__fallbackUnlisten = undefined
|
|
156
|
-
}
|
|
157
|
-
} catch (err) {
|
|
158
|
-
console.error('stopResizeObserver error', err)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// TODO: 确认是否需要重写 onMouseDown 方法
|
|
163
|
-
// handleMouseDown(ev: MouseEvent, x: number, y: number) {
|
|
164
|
-
// const target = ev.target as Element
|
|
165
|
-
// const tagName = target.tagName.toLowerCase()
|
|
166
|
-
// if (tagName === 'input') {
|
|
167
|
-
// const type = target.getAttribute('type')
|
|
168
|
-
// if (
|
|
169
|
-
// type == null ||
|
|
170
|
-
// [
|
|
171
|
-
// 'text',
|
|
172
|
-
// 'password',
|
|
173
|
-
// 'number',
|
|
174
|
-
// 'email',
|
|
175
|
-
// 'search',
|
|
176
|
-
// 'tel',
|
|
177
|
-
// 'url',
|
|
178
|
-
// ].includes(type)
|
|
179
|
-
// ) {
|
|
180
|
-
// return
|
|
181
|
-
// }
|
|
182
|
-
// }
|
|
183
|
-
//
|
|
184
|
-
// console.log('pointer position, x:', x, 'y: ', y)
|
|
185
|
-
// // TODO
|
|
186
|
-
// // super.handleMouseDown(ev)
|
|
187
|
-
// }
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export default ReactNodeView
|