@logicflow/react-node-registry 1.2.0-alpha.2 → 1.2.0-alpha.3

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.
Files changed (57) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +8 -0
  3. package/dist/index.css +122 -0
  4. package/es/components/Container.d.ts +9 -0
  5. package/es/components/Container.js +16 -0
  6. package/es/components/Container.js.map +1 -0
  7. package/es/components/TitleBar.d.ts +8 -0
  8. package/es/components/TitleBar.js +87 -0
  9. package/es/components/TitleBar.js.map +1 -0
  10. package/es/index.css +122 -0
  11. package/es/index.less +1 -0
  12. package/es/model.d.ts +18 -1
  13. package/es/model.js +46 -8
  14. package/es/model.js.map +1 -1
  15. package/es/style/index.css +122 -0
  16. package/es/style/index.less +140 -0
  17. package/es/style/raw.d.ts +4 -0
  18. package/es/style/raw.js +6 -0
  19. package/es/style/raw.js.map +1 -0
  20. package/es/view.d.ts +10 -1
  21. package/es/view.js +123 -9
  22. package/es/view.js.map +1 -1
  23. package/es/wrapper.d.ts +1 -1
  24. package/es/wrapper.js +4 -2
  25. package/es/wrapper.js.map +1 -1
  26. package/lib/components/Container.d.ts +9 -0
  27. package/lib/components/Container.js +23 -0
  28. package/lib/components/Container.js.map +1 -0
  29. package/lib/components/TitleBar.d.ts +8 -0
  30. package/lib/components/TitleBar.js +114 -0
  31. package/lib/components/TitleBar.js.map +1 -0
  32. package/lib/index.css +122 -0
  33. package/lib/index.less +1 -0
  34. package/lib/model.d.ts +18 -1
  35. package/lib/model.js +44 -6
  36. package/lib/model.js.map +1 -1
  37. package/lib/style/index.css +122 -0
  38. package/lib/style/index.less +140 -0
  39. package/lib/style/raw.d.ts +4 -0
  40. package/lib/style/raw.js +9 -0
  41. package/lib/style/raw.js.map +1 -0
  42. package/lib/view.d.ts +10 -1
  43. package/lib/view.js +123 -9
  44. package/lib/view.js.map +1 -1
  45. package/lib/wrapper.d.ts +1 -1
  46. package/lib/wrapper.js +7 -2
  47. package/lib/wrapper.js.map +1 -1
  48. package/package.json +3 -3
  49. package/rollup.config.js +52 -0
  50. package/src/components/Container.tsx +33 -0
  51. package/src/components/TitleBar.tsx +149 -0
  52. package/src/index.less +1 -0
  53. package/src/model.ts +82 -6
  54. package/src/style/index.less +140 -0
  55. package/src/style/raw.ts +129 -0
  56. package/src/view.ts +102 -9
  57. package/src/wrapper.tsx +17 -2
@@ -0,0 +1,140 @@
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
+ }
@@ -0,0 +1,129 @@
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 CHANGED
@@ -1,12 +1,28 @@
1
1
  import { createElement, ReactPortal } from 'react'
2
2
  import { createRoot, Root } from 'react-dom/client'
3
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'
4
13
  import { Wrapper } from './wrapper'
5
14
  import { Portal } from './portal'
6
15
  import { createPortal } from 'react-dom'
7
16
 
8
17
  export class ReactNodeView extends HtmlNode {
9
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)
10
26
 
11
27
  protected targetId() {
12
28
  return `${this.props.graphModel.flowId}:${this.props.model.id}`
@@ -18,17 +34,25 @@ export class ReactNodeView extends HtmlNode {
18
34
  }
19
35
 
20
36
  setHtml(rootEl: SVGForeignObjectElement) {
21
- const el = document.createElement('div')
22
- el.className = 'custom-react-node-content'
23
-
24
- this.renderReactComponent(el)
25
- rootEl.appendChild(el)
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()
26
50
  }
27
51
 
28
- confirmUpdate(_rootEl: SVGForeignObjectElement) {
29
- // TODO: 如有需要,可以先通过继承的方式,自定义该节点的更新逻辑;我们后续会根据实际需求,丰富该功能
30
- console.log('_rootEl', _rootEl)
31
- }
52
+ // confirmUpdate(_rootEl: SVGForeignObjectElement) {
53
+ // // TODO: 如有需要,可以先通过继承的方式,自定义该节点的更新逻辑;我们后续会根据实际需求,丰富该功能
54
+ // console.log('_rootEl', _rootEl)
55
+ // }
32
56
 
33
57
  protected renderReactComponent(container: HTMLElement) {
34
58
  this.unmountReactComponent()
@@ -55,6 +79,7 @@ export class ReactNodeView extends HtmlNode {
55
79
 
56
80
  protected unmountReactComponent() {
57
81
  if (this.rootEl && this.root) {
82
+ this.stopResizeObserver()
58
83
  this.root.unmount()
59
84
  this.root = undefined
60
85
  this.rootEl.innerHTML = ''
@@ -66,6 +91,74 @@ export class ReactNodeView extends HtmlNode {
66
91
  this.unmountReactComponent()
67
92
  }
68
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
+
69
162
  // TODO: 确认是否需要重写 onMouseDown 方法
70
163
  // handleMouseDown(ev: MouseEvent, x: number, y: number) {
71
164
  // const target = ev.target as Element
package/src/wrapper.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import React, { PureComponent } from 'react'
2
2
  import { BaseNodeModel, EventType, GraphModel } from '@logicflow/core'
3
3
  import { reactNodesMap } from './registry'
4
+ import Container from './components/Container'
4
5
 
5
6
  export interface IWrapperProps {
6
7
  node: BaseNodeModel
@@ -49,12 +50,26 @@ export class Wrapper extends PureComponent<IWrapperProps, IWrapperState> {
49
50
 
50
51
  if (!content) return null
51
52
 
53
+ const { _showTitle = false } = node.properties || {}
54
+
52
55
  const { component } = content
53
56
  if (React.isValidElement(component)) {
54
- return this.clone(component)
57
+ return _showTitle ? (
58
+ <Container node={this.props.node} graph={this.props.graph}>
59
+ {this.clone(component)}
60
+ </Container>
61
+ ) : (
62
+ this.clone(component)
63
+ )
55
64
  }
56
65
  const FC = component as React.FC
57
- return this.clone(<FC />)
66
+ return _showTitle ? (
67
+ <Container node={this.props.node} graph={this.props.graph}>
68
+ {this.clone(<FC />)}
69
+ </Container>
70
+ ) : (
71
+ this.clone(<FC />)
72
+ )
58
73
  }
59
74
  }
60
75