@logicflow/vue-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/src/view.ts DELETED
@@ -1,238 +0,0 @@
1
- import { isVue2, isVue3, createApp, h, Vue2, defineComponent } from 'vue-demi'
2
- // Vue2/3 兼容 API;使用 vue-demi 保持两端一致
3
- import {
4
- throttle,
5
- round,
6
- get,
7
- isFunction,
8
- isArray,
9
- clamp,
10
- isNumber,
11
- } from 'lodash-es'
12
- import { HtmlNode } from '@logicflow/core'
13
- import { vueNodesMap } from './registry'
14
- import { isActive, connect, disconnect } from './teleport'
15
- import { Container } from './components/container'
16
-
17
- export class VueNodeView extends HtmlNode {
18
- root?: any
19
- private vm: any
20
- // 尺寸监听相关状态
21
- private __resizeObserver?: ResizeObserver
22
- private __resizeRafId?: number
23
- private __lastWidth?: number
24
- private __lastHeight?: number
25
- private __fallbackUnlisten?: () => void
26
- private __throttledUpdate = throttle(() => this.measureAndUpdate(), 80)
27
- // private isMounted: boolean = false
28
-
29
- getComponentContainer() {
30
- return this.root
31
- }
32
-
33
- protected targetId() {
34
- return `${this.props.graphModel.flowId}:${this.props.model.id}`
35
- }
36
-
37
- componentWillUnmount() {
38
- super.componentWillUnmount()
39
- this.unmount()
40
- }
41
-
42
- setHtml(rootEl: SVGForeignObjectElement) {
43
- // 创建节点内容容器并注入到 foreignObject(若已存在则复用)
44
- const existed = rootEl.querySelector('.custom-vue-node-content')
45
- if (!existed) {
46
- const el = document.createElement('div')
47
- el.className = 'custom-vue-node-content'
48
- this.root = el
49
- rootEl.appendChild(el)
50
- }
51
- // 渲染 Vue 组件并启用尺寸监听
52
- this.renderVueComponent()
53
- this.startResizeObserver()
54
- }
55
-
56
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
57
- confirmUpdate(_rootEl: SVGForeignObjectElement) {
58
- // TODO: 如有需要,可以先通过继承的方式,自定义该节点的更新逻辑;我们后续会根据实际需求,丰富该功能
59
- const { model } = this.props
60
- const { _showTitle = false } = model.properties || {}
61
- if (_showTitle) {
62
- this.setHtml(_rootEl)
63
- }
64
- }
65
-
66
- protected renderVueComponent() {
67
- this.unmountVueComponent()
68
- const root = this.getComponentContainer()
69
- const { model, graphModel } = this.props
70
- const { _showTitle = false } = model.properties || {}
71
- const wrapWithContainer = (child: any) =>
72
- defineComponent({
73
- name: 'LFVueNodeContainerWrapper',
74
- props: {
75
- node: { type: Object, required: true },
76
- graph: { type: Object, required: true },
77
- },
78
- render(this: any) {
79
- // 根据 _showTitle 决定是否用 Container 包裹,避免无标题时额外结构
80
- if (!_showTitle) {
81
- return h(child, { node: this.node, graph: this.graph })
82
- }
83
- return h(Container, { node: this.node, graph: this.graph }, [
84
- h(child, { node: this.node, graph: this.graph }),
85
- ])
86
- },
87
- })
88
-
89
- if (root) {
90
- const { component } = vueNodesMap[model.type]
91
- if (component) {
92
- if (isVue2) {
93
- const Vue = Vue2 as any
94
- const Composed = wrapWithContainer(component)
95
- this.vm = new Vue({
96
- el: root,
97
- render(h: any) {
98
- return h(Composed, {
99
- props: {
100
- node: model,
101
- graph: graphModel,
102
- },
103
- })
104
- },
105
- provide() {
106
- return {
107
- getNode: () => model,
108
- getGraph: () => graphModel,
109
- }
110
- },
111
- })
112
- } else if (isVue3) {
113
- if (isActive()) {
114
- const Composed = wrapWithContainer(component)
115
- connect(this.targetId(), Composed, root, model, graphModel)
116
- } else {
117
- const Composed = wrapWithContainer(component)
118
- this.vm = createApp({
119
- render() {
120
- return h(Composed, {
121
- node: model,
122
- graph: graphModel,
123
- })
124
- },
125
- provide() {
126
- return {
127
- getNode: () => model,
128
- getGraph: () => graphModel,
129
- }
130
- },
131
- })
132
- this.vm?.mount(root)
133
- // this.isMounted = true
134
- }
135
- }
136
- }
137
- }
138
- }
139
-
140
- private measureAndUpdate = () => {
141
- try {
142
- // 读取子组件(或容器本身)的实际尺寸并更新模型属性
143
- const root = this.getComponentContainer() as HTMLElement
144
- if (!root) return
145
- const target = (root.firstElementChild as HTMLElement) || root
146
- const rect = target.getBoundingClientRect()
147
- const width = round(rect.width)
148
- const height = round(rect.height)
149
- if (width <= 0 || height <= 0) return
150
- if (width === this.__lastWidth && height === this.__lastHeight) return
151
- this.__lastWidth = width
152
- this.__lastHeight = height
153
- const props = this.props.model.properties as any
154
- const extra = get(props, '_showTitle')
155
- ? isNumber(get(props, '_titleHeight'))
156
- ? get(props, '_titleHeight')
157
- : 28
158
- : 0
159
- // 去掉标题占用的高度,保证内容区域与模型高度一致
160
- const baseHeight = clamp(height - extra, 1, Number.MAX_SAFE_INTEGER)
161
- this.props.model.setProperties({ width, height: baseHeight })
162
- } catch (err) {
163
- // swallow error
164
- }
165
- }
166
-
167
- private startResizeObserver() {
168
- // 启动尺寸监听:优先使用 ResizeObserver,退化到 window.resize
169
- const root = this.getComponentContainer() as HTMLElement
170
- if (!root) return
171
- try {
172
- if (isFunction((window as any).ResizeObserver)) {
173
- this.__resizeObserver = new (window as any).ResizeObserver(
174
- (entries: any[]) => {
175
- if (!isArray(entries) || !entries.length) return
176
- if (this.__resizeRafId) cancelAnimationFrame(this.__resizeRafId)
177
- // 使用 RAF 对齐绘制帧,再用节流函数合并频繁变更
178
- this.__resizeRafId = requestAnimationFrame(this.__throttledUpdate)
179
- },
180
- )
181
- const target = (root.firstElementChild as HTMLElement) || root
182
- this.__resizeObserver?.observe(target)
183
- } else {
184
- // 退化监听:在窗口尺寸变化时尝试更新
185
- window.addEventListener('resize', () => this.__throttledUpdate())
186
- this.__fallbackUnlisten = () =>
187
- window.removeEventListener('resize', () => this.__throttledUpdate())
188
- }
189
- } catch (err) {
190
- // swallow error
191
- }
192
- }
193
-
194
- private stopResizeObserver() {
195
- try {
196
- // 停止所有监听与异步回调,避免内存泄漏
197
- if (this.__resizeObserver) {
198
- this.__resizeObserver.disconnect()
199
- this.__resizeObserver = undefined
200
- }
201
- if (this.__resizeRafId) {
202
- cancelAnimationFrame(this.__resizeRafId)
203
- this.__resizeRafId = undefined
204
- }
205
- if (this.__fallbackUnlisten) {
206
- this.__fallbackUnlisten()
207
- this.__fallbackUnlisten = undefined
208
- }
209
- } catch (err) {
210
- // swallow error
211
- }
212
- }
213
-
214
- protected unmountVueComponent() {
215
- const root = this.getComponentContainer()
216
- // 在卸载 Vue 实例前先停止尺寸监听
217
- this.stopResizeObserver()
218
- if (this.vm) {
219
- isVue2 && this.vm.$destroy()
220
- isVue3 && this.vm.unmount()
221
- this.vm = null
222
- }
223
- if (root && !isActive()) {
224
- root.innerHTML = ''
225
- }
226
- return root
227
- }
228
-
229
- unmount() {
230
- // Teleport 模式下断开连接,并清理视图与监听
231
- if (isActive()) {
232
- disconnect(this.targetId(), this.props.graphModel.flowId as string)
233
- }
234
- this.unmountVueComponent()
235
- }
236
- }
237
-
238
- export default VueNodeView