@logicflow/extension 2.2.0-alpha.0 → 2.2.0-alpha.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logicflow/extension",
3
- "version": "2.2.0-alpha.0",
3
+ "version": "2.2.0-alpha.1",
4
4
  "description": "LogicFlow Extensions",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -20,8 +20,8 @@
20
20
  "author": "Logicflow-Team",
21
21
  "license": "Apache-2.0",
22
22
  "peerDependencies": {
23
- "@logicflow/core": "2.2.0-alpha.0",
24
- "@logicflow/vue-node-registry": "1.2.0-alpha.0"
23
+ "@logicflow/vue-node-registry": "1.2.0-alpha.1",
24
+ "@logicflow/core": "2.2.0-alpha.1"
25
25
  },
26
26
  "dependencies": {
27
27
  "@antv/hierarchy": "^0.6.11",
@@ -32,8 +32,8 @@
32
32
  "preact": "^10.17.1",
33
33
  "rangy": "^1.3.1",
34
34
  "vanilla-picker": "^2.12.3",
35
- "@logicflow/core": "2.2.0-alpha.0",
36
- "@logicflow/vue-node-registry": "1.2.0-alpha.0"
35
+ "@logicflow/core": "2.2.0-alpha.1",
36
+ "@logicflow/vue-node-registry": "1.2.0-alpha.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "less": "^4.1.1",
@@ -3,6 +3,7 @@ import LogicFlow, {
3
3
  PolylineEdgeModel,
4
4
  EventType,
5
5
  formatAnchorConnectValidateData,
6
+ getClosestAnchor,
6
7
  } from '@logicflow/core'
7
8
  import { cloneDeep } from 'lodash-es'
8
9
  import { isNodeInSegment } from './edge'
@@ -132,7 +133,6 @@ export class InsertNodeInPolyline {
132
133
  } = edges[i]
133
134
  // fix https://github.com/didi/LogicFlow/issues/996
134
135
  const startPoint = cloneDeep(pointsList[0])
135
- const endPoint = cloneDeep(crossPoints.startCrossPoint)
136
136
  this._lf.deleteEdge(id)
137
137
  const checkResult = this.checkRuleBeforeInsetNode(
138
138
  sourceNodeId,
@@ -141,27 +141,33 @@ export class InsertNodeInPolyline {
141
141
  targetAnchorId!,
142
142
  nodeData,
143
143
  )
144
+ // 基于插入节点的进入交点计算出最近的“进入锚点”,用于重连原边的前半段
145
+ const entryAnchorInfo = getClosestAnchor(
146
+ crossPoints.startCrossPoint,
147
+ nodeModel,
148
+ )
149
+ const entryAnchor = entryAnchorInfo.anchor
150
+ // 构造第一条边:原 source → 插入节点(终点为进入锚点)
144
151
  this._lf.addEdge({
145
152
  type,
146
153
  sourceNodeId,
147
154
  targetNodeId: nodeData.id,
148
155
  startPoint,
149
- endPoint,
150
- pointsList: [
151
- ...pointsList.slice(0, crossIndex),
152
- crossPoints.startCrossPoint,
153
- ],
156
+ endPoint: entryAnchor,
154
157
  })
158
+ // 基于插入节点的离开交点计算出最近的“离开锚点”,用于重连原边的后半段
159
+ const exitAnchorInfo = getClosestAnchor(
160
+ crossPoints.endCrossPoint,
161
+ nodeModel,
162
+ )
163
+ const exitAnchor = exitAnchorInfo.anchor
164
+ // 构造第二条边:插入节点 → 原 target(起点为离开锚点)
155
165
  this._lf.addEdge({
156
166
  type,
157
167
  sourceNodeId: nodeData.id,
158
168
  targetNodeId,
159
- startPoint: cloneDeep(crossPoints.endCrossPoint),
169
+ startPoint: cloneDeep(exitAnchor),
160
170
  endPoint: cloneDeep(pointsList[pointsList.length - 1]),
161
- pointsList: [
162
- crossPoints.endCrossPoint,
163
- ...pointsList.slice(crossIndex),
164
- ],
165
171
  })
166
172
  if (!checkResult.isPass) {
167
173
  this._lf.graphModel.eventCenter.emit(
@@ -4,7 +4,11 @@ import { forEach, merge } from 'lodash-es'
4
4
  import LabelPlugin from '.'
5
5
  import Label from './Label'
6
6
  import LabelModel from './LabelModel'
7
- import { MediumEditor, defaultOptions, ColorPickerButton } from './mediumEditor'
7
+ import {
8
+ MediumEditor,
9
+ defaultOptions,
10
+ createColorPickerButtonClass,
11
+ } from './mediumEditor'
8
12
 
9
13
  import LabelConfig = LogicFlow.LabelConfig
10
14
 
@@ -47,7 +51,7 @@ export class LabelOverlay extends Component<IToolProps, ILabelOverlayState> {
47
51
  merge(defaultOptions, {
48
52
  autoLink: true,
49
53
  extensions: {
50
- colorPicker: new ColorPickerButton(),
54
+ colorPicker: new (createColorPickerButtonClass(MediumEditor))(),
51
55
  },
52
56
  }),
53
57
  )
@@ -82,7 +86,7 @@ export class LabelOverlay extends Component<IToolProps, ILabelOverlayState> {
82
86
  merge(defaultOptions, {
83
87
  autoLink: true,
84
88
  extensions: {
85
- colorPicker: new ColorPickerButton(),
89
+ colorPicker: new (createColorPickerButtonClass(MediumEditor))(),
86
90
  },
87
91
  }),
88
92
  )
@@ -39,56 +39,83 @@ export const defaultOptions = {
39
39
  disableEditing: true,
40
40
  }
41
41
 
42
- export const ColorPickerButton = MediumEditor.extensions.button.extend({
43
- name: 'colorpicker',
44
- tagNames: ['mark'],
45
- contentDefault: '<b>Color</b>',
46
- aria: 'Color Picker',
47
- action: 'colorPicker',
48
- init: function () {
49
- rangy.init()
50
- MediumEditor.extensions.button.prototype.init.call(this)
51
- this.colorPicker = new Picker({
52
- parent: this.button,
53
- color: '#000',
54
- onDone: (res) => {
55
- if (this.coloredText && this.coloredText.isAppliedToSelection()) {
56
- this.coloredText.undoToSelection()
57
- }
58
- this.coloredText = rangy.createClassApplier('colored', {
59
- elementTagName: 'span',
60
- elementProperties: {
61
- style: {
62
- color: res.hex,
63
- },
64
- },
65
- normalize: true,
66
- })
67
- this.coloredText.toggleSelection()
68
- this.base.checkContentChanged()
69
- this.setInactive()
70
- },
71
- })
72
- },
73
- getButton: function () {
74
- return this.button
75
- },
76
- handleClick: function () {
77
- this.setActive()
78
- this.colorPicker.show()
79
- },
80
- isAlreadyApplied: function (node) {
81
- return node.nodeName.toLowerCase() === 'mark'
82
- },
83
- isActive: function () {
84
- return this.button.classList.contains('medium-editor-button-active')
85
- },
86
- setInactive: function () {
87
- this.button.classList.remove('medium-editor-button-active')
88
- },
89
- setActive: function () {
90
- this.button.classList.add('medium-editor-button-active')
91
- },
92
- })
42
+ export function createColorPickerButtonClass(MediumEditor?: any) {
43
+ const ButtonBase =
44
+ MediumEditor?.extensions?.button || MediumEditor?.extensions?.button
45
+ const ExtensionBase =
46
+ MediumEditor?.Extension || (MediumEditor as any)?.Extension
47
+
48
+ // Button 扩展基类不可用时,回退到 Extension 基类,避免在模块加载阶段抛错
49
+ const Base = ButtonBase || ExtensionBase
50
+ if (!Base) {
51
+ console.warn(
52
+ 'MediumEditor button/extension base not available; using noop extension',
53
+ )
54
+ return class {}
55
+ }
56
+
57
+ return Base.extend({
58
+ name: 'colorpicker',
59
+ tagNames: ['mark'],
60
+ contentDefault: '<b>Color</b>',
61
+ aria: 'Color Picker',
62
+ action: 'colorPicker',
63
+ init: function () {
64
+ try {
65
+ rangy.init()
66
+ } catch {
67
+ console.error('rangy.init failed')
68
+ }
69
+ // 初始化按钮(ButtonBase 才有 prototype.init)
70
+ try {
71
+ ;(ButtonBase as any)?.prototype?.init?.call(this)
72
+ } catch {
73
+ console.error('ButtonBase.init failed')
74
+ }
75
+ this.colorPicker = new Picker({
76
+ parent: (this as any).button || undefined,
77
+ color: '#000',
78
+ onDone: (res) => {
79
+ try {
80
+ if (this.coloredText && this.coloredText.isAppliedToSelection?.()) {
81
+ this.coloredText.undoToSelection()
82
+ }
83
+ this.coloredText = rangy.createClassApplier('colored', {
84
+ elementTagName: 'span',
85
+ elementProperties: { style: { color: res.hex } },
86
+ normalize: true,
87
+ })
88
+ this.coloredText.toggleSelection()
89
+ this.base?.checkContentChanged?.()
90
+ this.setInactive?.()
91
+ } catch {
92
+ console.error('Picker.onDone failed')
93
+ }
94
+ },
95
+ })
96
+ },
97
+ getButton: function () {
98
+ return (this as any).button
99
+ },
100
+ handleClick: function () {
101
+ this.setActive?.()
102
+ this.colorPicker?.show?.()
103
+ },
104
+ isAlreadyApplied: function (node) {
105
+ return node?.nodeName?.toLowerCase?.() === 'mark'
106
+ },
107
+ isActive: function () {
108
+ return (this as any).button?.classList?.contains(
109
+ 'medium-editor-button-active',
110
+ )
111
+ },
112
+ setInactive: function () {
113
+ ;(this as any).button?.classList?.remove('medium-editor-button-active')
114
+ },
115
+ setActive: function () {
116
+ ;(this as any).button?.classList?.add('medium-editor-button-active')
117
+ },
118
+ })
119
+ }
93
120
 
94
121
  export { MediumEditor }
@@ -646,6 +646,18 @@ export class Snapshot {
646
646
  const { fileType = baseFileType } = toImageOptions ?? {}
647
647
  const svg = this.getSvgRootElement(this.lf)
648
648
  await updateImageSource(svg as SVGElement)
649
+ if (fileType === 'svg') {
650
+ const copy = await this.cloneSvg(svg)
651
+ const svgString = new XMLSerializer().serializeToString(copy)
652
+ const blob = new Blob([svgString], {
653
+ type: 'image/svg+xml;charset=utf-8',
654
+ })
655
+ return {
656
+ data: blob,
657
+ width: 0,
658
+ height: 0,
659
+ }
660
+ }
649
661
  return new Promise((resolve) => {
650
662
  this.getCanvasData(svg, {
651
663
  backgroundColor,