@logicflow/vue-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 (59) hide show
  1. package/.turbo/turbo-build$colon$dev.log +2 -2
  2. package/.turbo/turbo-build.log +4 -4
  3. package/CHANGELOG.md +8 -0
  4. package/dist/index.css +122 -0
  5. package/es/components/container.d.ts +19 -0
  6. package/es/components/container.js +27 -0
  7. package/es/components/container.js.map +1 -0
  8. package/es/components/titleBar.d.ts +24 -0
  9. package/es/components/titleBar.js +160 -0
  10. package/es/components/titleBar.js.map +1 -0
  11. package/es/index.css +122 -0
  12. package/es/index.less +1 -0
  13. package/es/model.d.ts +18 -1
  14. package/es/model.js +45 -5
  15. package/es/model.js.map +1 -1
  16. package/es/style/index.css +122 -0
  17. package/es/style/index.less +140 -0
  18. package/es/style/raw.d.ts +4 -0
  19. package/es/style/raw.js +6 -0
  20. package/es/style/raw.js.map +1 -0
  21. package/es/utils/size.d.ts +2 -0
  22. package/es/utils/size.js +14 -0
  23. package/es/utils/size.js.map +1 -0
  24. package/es/view.d.ts +9 -0
  25. package/es/view.js +137 -12
  26. package/es/view.js.map +1 -1
  27. package/lib/components/container.d.ts +19 -0
  28. package/lib/components/container.js +30 -0
  29. package/lib/components/container.js.map +1 -0
  30. package/lib/components/titleBar.d.ts +24 -0
  31. package/lib/components/titleBar.js +163 -0
  32. package/lib/components/titleBar.js.map +1 -0
  33. package/lib/index.css +122 -0
  34. package/lib/index.less +1 -0
  35. package/lib/model.d.ts +18 -1
  36. package/lib/model.js +43 -3
  37. package/lib/model.js.map +1 -1
  38. package/lib/style/index.css +122 -0
  39. package/lib/style/index.less +140 -0
  40. package/lib/style/raw.d.ts +4 -0
  41. package/lib/style/raw.js +9 -0
  42. package/lib/style/raw.js.map +1 -0
  43. package/lib/utils/size.d.ts +2 -0
  44. package/lib/utils/size.js +19 -0
  45. package/lib/utils/size.js.map +1 -0
  46. package/lib/view.d.ts +9 -0
  47. package/lib/view.js +136 -11
  48. package/lib/view.js.map +1 -1
  49. package/package.json +3 -3
  50. package/rollup.config.js +52 -0
  51. package/src/assets/arrow.svg +10 -0
  52. package/src/components/container.ts +36 -0
  53. package/src/components/titleBar.ts +189 -0
  54. package/src/index.less +1 -0
  55. package/src/model.ts +81 -3
  56. package/src/style/index.less +140 -0
  57. package/src/style/raw.ts +129 -0
  58. package/src/utils/size.ts +22 -0
  59. package/src/view.ts +138 -10
package/src/model.ts CHANGED
@@ -1,5 +1,17 @@
1
- import LogicFlow, { HtmlNodeModel, IHtmlNodeProperties } from '@logicflow/core'
2
- import { cloneDeep, isNil } from 'lodash-es'
1
+ import LogicFlow, {
2
+ GraphModel,
3
+ BaseNodeModel,
4
+ HtmlNodeModel,
5
+ IHtmlNodeProperties,
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
+ }
3
15
 
4
16
  export interface VueCustomProperties extends IHtmlNodeProperties {
5
17
  // 形状属性
@@ -14,14 +26,69 @@ export interface VueCustomProperties extends IHtmlNodeProperties {
14
26
  // 样式属性
15
27
  style?: LogicFlow.CommonTheme
16
28
  textStyle?: LogicFlow.TextNodeTheme
29
+
30
+ // 标题配置
31
+ _showTitle?: boolean
32
+ _title?: string
33
+ _icon?: string
34
+ _titleHeight?: number
35
+ _expanded?: boolean
17
36
  }
18
37
 
19
38
  export class VueNodeModel<
20
39
  P extends VueCustomProperties = VueCustomProperties,
21
40
  > extends HtmlNodeModel<P> {
41
+ private __baseHeight?: number
42
+ public __actions?: {
43
+ name: string
44
+ callback?: (node: BaseNodeModel, graph: GraphModel) => void
45
+ }[]
46
+ constructor(data: NodeConfig<P>, graphModel: GraphModel) {
47
+ super(data, graphModel)
48
+ const { properties } = data
49
+ // 如果需要展示标题,则重新设置一个能把节点内容都展示出来的最小宽高
50
+ if (properties) {
51
+ const { _showTitle = false, style = {} } = properties
52
+ if (_showTitle) {
53
+ this.minWidth = 160
54
+ this.minHeight = 80
55
+ this.text.editable = false
56
+ // 判断当前节点宽高是否小于最小宽高,如果是,强制设置为最小宽高
57
+ const newWidth = this.width < this.minWidth ? this.minWidth : this.width
58
+ const newHeight =
59
+ this.height < this.minHeight ? this.minHeight : this.height
60
+
61
+ this.setProperties({
62
+ _expanded: false,
63
+ ...properties,
64
+ style: {
65
+ overflow: 'visible',
66
+ ...cloneDeep(style),
67
+ },
68
+ width: newWidth + 8,
69
+ height: newHeight + 8,
70
+ })
71
+ this.setNodeActions([
72
+ {
73
+ name: '复制',
74
+ callback: (nodeModel, graphModel) => {
75
+ graphModel.cloneNode(nodeModel.id)
76
+ },
77
+ },
78
+ {
79
+ name: '删除',
80
+ callback: (nodeModel, graphModel) => {
81
+ graphModel.deleteNode(nodeModel.id)
82
+ },
83
+ },
84
+ ])
85
+ }
86
+ }
87
+ console.log('nodeModel', this)
88
+ }
22
89
  setAttributes() {
23
90
  // DONE: 解决 width、height、radius 为 0 时的问题
24
- const { width, height, radius } = this.properties
91
+ const { width, height, radius, _showTitle, _titleHeight } = this.properties
25
92
  if (!isNil(width)) {
26
93
  this.width = width
27
94
  }
@@ -31,6 +98,12 @@ export class VueNodeModel<
31
98
  if (!isNil(radius)) {
32
99
  this.radius = radius
33
100
  }
101
+ if (this.__baseHeight === undefined) {
102
+ this.__baseHeight = isNil(height) ? this.height : height
103
+ }
104
+ const extra = _showTitle ? (_titleHeight ?? 28) : 0
105
+ const base = isNil(height) ? (this.__baseHeight as number) : height
106
+ this.height = base + extra
34
107
  }
35
108
 
36
109
  getTextStyle(): LogicFlow.TextNodeTheme {
@@ -59,6 +132,11 @@ export class VueNodeModel<
59
132
  // ry: radius,
60
133
  }
61
134
  }
135
+
136
+ setNodeActions(actions: NodeAction[]) {
137
+ this.__actions =
138
+ isArray(actions) && !isEmpty(actions) ? cloneDeep(actions) : []
139
+ }
62
140
  }
63
141
 
64
142
  export default VueNodeModel
@@ -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
+ `
@@ -0,0 +1,22 @@
1
+ export function computeBaseHeight(
2
+ measuredHeight: number,
3
+ _showTitle?: boolean,
4
+ _titleHeight?: number,
5
+ ) {
6
+ const extra = _showTitle
7
+ ? typeof _titleHeight === 'number'
8
+ ? _titleHeight
9
+ : 28
10
+ : 0
11
+ return Math.max(1, Math.round(measuredHeight) - extra)
12
+ }
13
+
14
+ export function shouldUpdateSize(
15
+ prevW?: number,
16
+ prevH?: number,
17
+ w?: number,
18
+ h?: number,
19
+ ) {
20
+ if (!w || !h) return false
21
+ return !(prevW === Math.round(w) && prevH === Math.round(h))
22
+ }
package/src/view.ts CHANGED
@@ -1,11 +1,30 @@
1
- import { isVue2, isVue3, createApp, h, Vue2 } from 'vue-demi'
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'
2
12
  import { HtmlNode } from '@logicflow/core'
3
13
  import { vueNodesMap } from './registry'
4
14
  import { isActive, connect, disconnect } from './teleport'
15
+ import { Container } from './components/container'
5
16
 
6
17
  export class VueNodeView extends HtmlNode {
7
18
  root?: any
8
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
9
28
 
10
29
  getComponentContainer() {
11
30
  return this.root
@@ -21,34 +40,62 @@ export class VueNodeView extends HtmlNode {
21
40
  }
22
41
 
23
42
  setHtml(rootEl: SVGForeignObjectElement) {
24
- const el = document.createElement('div')
25
- el.className = 'custom-vue-node-content'
26
- this.root = el
27
- rootEl.appendChild(el)
28
-
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 组件并启用尺寸监听
29
52
  this.renderVueComponent()
53
+ this.startResizeObserver()
30
54
  }
31
55
 
32
56
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
33
57
  confirmUpdate(_rootEl: SVGForeignObjectElement) {
34
58
  // TODO: 如有需要,可以先通过继承的方式,自定义该节点的更新逻辑;我们后续会根据实际需求,丰富该功能
35
- // console.log('_rootEl', _rootEl)
59
+ const { model } = this.props
60
+ const { _showTitle = false } = model.properties || {}
61
+ if (_showTitle) {
62
+ this.setHtml(_rootEl)
63
+ }
36
64
  }
37
65
 
38
66
  protected renderVueComponent() {
39
67
  this.unmountVueComponent()
40
68
  const root = this.getComponentContainer()
41
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
+ })
42
88
 
43
89
  if (root) {
44
90
  const { component } = vueNodesMap[model.type]
45
91
  if (component) {
46
92
  if (isVue2) {
47
93
  const Vue = Vue2 as any
94
+ const Composed = wrapWithContainer(component)
48
95
  this.vm = new Vue({
49
96
  el: root,
50
97
  render(h: any) {
51
- return h(component, {
98
+ return h(Composed, {
52
99
  node: model,
53
100
  graph: graphModel,
54
101
  })
@@ -61,12 +108,15 @@ export class VueNodeView extends HtmlNode {
61
108
  },
62
109
  })
63
110
  } else if (isVue3) {
111
+ console.log('isActive', isActive())
64
112
  if (isActive()) {
65
- connect(this.targetId(), component, root, model, graphModel)
113
+ const Composed = wrapWithContainer(component)
114
+ connect(this.targetId(), Composed, root, model, graphModel)
66
115
  } else {
116
+ const Composed = wrapWithContainer(component)
67
117
  this.vm = createApp({
68
118
  render() {
69
- return h(component, {
119
+ return h(Composed, {
70
120
  node: model,
71
121
  graph: graphModel,
72
122
  })
@@ -79,14 +129,91 @@ export class VueNodeView extends HtmlNode {
79
129
  },
80
130
  })
81
131
  this.vm?.mount(root)
132
+ // this.isMounted = true
82
133
  }
83
134
  }
84
135
  }
85
136
  }
86
137
  }
87
138
 
139
+ private measureAndUpdate = () => {
140
+ try {
141
+ // 读取子组件(或容器本身)的实际尺寸并更新模型属性
142
+ const root = this.getComponentContainer() as HTMLElement
143
+ if (!root) return
144
+ const target = (root.firstElementChild as HTMLElement) || root
145
+ const rect = target.getBoundingClientRect()
146
+ const width = round(rect.width)
147
+ const height = round(rect.height)
148
+ if (width <= 0 || height <= 0) return
149
+ if (width === this.__lastWidth && height === this.__lastHeight) return
150
+ this.__lastWidth = width
151
+ this.__lastHeight = height
152
+ const props = this.props.model.properties as any
153
+ const extra = get(props, '_showTitle')
154
+ ? isNumber(get(props, '_titleHeight'))
155
+ ? get(props, '_titleHeight')
156
+ : 28
157
+ : 0
158
+ // 去掉标题占用的高度,保证内容区域与模型高度一致
159
+ const baseHeight = clamp(height - extra, 1, Number.MAX_SAFE_INTEGER)
160
+ this.props.model.setProperties({ width, height: baseHeight })
161
+ } catch (err) {
162
+ // swallow error
163
+ }
164
+ }
165
+
166
+ private startResizeObserver() {
167
+ // 启动尺寸监听:优先使用 ResizeObserver,退化到 window.resize
168
+ const root = this.getComponentContainer() as HTMLElement
169
+ if (!root) return
170
+ try {
171
+ if (isFunction((window as any).ResizeObserver)) {
172
+ this.__resizeObserver = new (window as any).ResizeObserver(
173
+ (entries: any[]) => {
174
+ if (!isArray(entries) || !entries.length) return
175
+ if (this.__resizeRafId) cancelAnimationFrame(this.__resizeRafId)
176
+ // 使用 RAF 对齐绘制帧,再用节流函数合并频繁变更
177
+ this.__resizeRafId = requestAnimationFrame(this.__throttledUpdate)
178
+ },
179
+ )
180
+ const target = (root.firstElementChild as HTMLElement) || root
181
+ this.__resizeObserver?.observe(target)
182
+ } else {
183
+ // 退化监听:在窗口尺寸变化时尝试更新
184
+ window.addEventListener('resize', () => this.__throttledUpdate())
185
+ this.__fallbackUnlisten = () =>
186
+ window.removeEventListener('resize', () => this.__throttledUpdate())
187
+ }
188
+ } catch (err) {
189
+ // swallow error
190
+ }
191
+ }
192
+
193
+ private stopResizeObserver() {
194
+ try {
195
+ // 停止所有监听与异步回调,避免内存泄漏
196
+ if (this.__resizeObserver) {
197
+ this.__resizeObserver.disconnect()
198
+ this.__resizeObserver = undefined
199
+ }
200
+ if (this.__resizeRafId) {
201
+ cancelAnimationFrame(this.__resizeRafId)
202
+ this.__resizeRafId = undefined
203
+ }
204
+ if (this.__fallbackUnlisten) {
205
+ this.__fallbackUnlisten()
206
+ this.__fallbackUnlisten = undefined
207
+ }
208
+ } catch (err) {
209
+ // swallow error
210
+ }
211
+ }
212
+
88
213
  protected unmountVueComponent() {
89
214
  const root = this.getComponentContainer()
215
+ // 在卸载 Vue 实例前先停止尺寸监听
216
+ this.stopResizeObserver()
90
217
  if (this.vm) {
91
218
  isVue2 && this.vm.$destroy()
92
219
  isVue3 && this.vm.unmount()
@@ -99,6 +226,7 @@ export class VueNodeView extends HtmlNode {
99
226
  }
100
227
 
101
228
  unmount() {
229
+ // Teleport 模式下断开连接,并清理视图与监听
102
230
  if (isActive()) {
103
231
  disconnect(this.targetId(), this.props.graphModel.flowId as string)
104
232
  }