@flowgram-vue/core 0.2.0

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 (133) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +0 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +0 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +63 -0
  8. package/src/common/config-entity.ts +80 -0
  9. package/src/common/entity-data.ts +171 -0
  10. package/src/common/entity-manager-contribution.ts +12 -0
  11. package/src/common/entity-manager.ts +475 -0
  12. package/src/common/entity.ts +482 -0
  13. package/src/common/index.ts +22 -0
  14. package/src/common/playground-context.ts +35 -0
  15. package/src/common/playground-decorator-helper.ts +113 -0
  16. package/src/common/playground-decorators.ts +85 -0
  17. package/src/common/playground-schedule.ts +30 -0
  18. package/src/common/protect-wheel-area.ts +11 -0
  19. package/src/common/schema/index.ts +14 -0
  20. package/src/common/schema/node.ts +15 -0
  21. package/src/common/schema/opacity-schema.ts +19 -0
  22. package/src/common/schema/origin-schema.ts +35 -0
  23. package/src/common/schema/position-schema.ts +35 -0
  24. package/src/common/schema/rotation-schema.ts +19 -0
  25. package/src/common/schema/scale-schema.ts +35 -0
  26. package/src/common/schema/size-schema.ts +42 -0
  27. package/src/common/schema/skew-schema.ts +35 -0
  28. package/src/common/schema/transform-schema.ts +613 -0
  29. package/src/common/utils/bounds.spec.ts +229 -0
  30. package/src/common/utils/bounds.ts +176 -0
  31. package/src/common/utils/index.ts +6 -0
  32. package/src/composables/index.ts +16 -0
  33. package/src/composables/use-config-entity.ts +31 -0
  34. package/src/composables/use-entities.ts +26 -0
  35. package/src/composables/use-entity-data-from-context.ts +34 -0
  36. package/src/composables/use-entity-from-context.ts +31 -0
  37. package/src/composables/use-listen-events.ts +22 -0
  38. package/src/composables/use-playground-container.ts +16 -0
  39. package/src/composables/use-playground-context.ts +15 -0
  40. package/src/composables/use-playground-drag.ts +31 -0
  41. package/src/composables/use-playground.ts +16 -0
  42. package/src/composables/use-refresh.ts +6 -0
  43. package/src/composables/use-service.ts +17 -0
  44. package/src/core/index.ts +8 -0
  45. package/src/core/layer/config/editor-state-config-entity.ts +180 -0
  46. package/src/core/layer/config/index.ts +7 -0
  47. package/src/core/layer/config/playground-config-entity.ts +604 -0
  48. package/src/core/layer/index.ts +8 -0
  49. package/src/core/layer/layer.ts +248 -0
  50. package/src/core/layer/playground-layer.ts +547 -0
  51. package/src/core/pipeline/index.ts +10 -0
  52. package/src/core/pipeline/pipeline-entities-selector.ts +200 -0
  53. package/src/core/pipeline/pipeline-entities.ts +230 -0
  54. package/src/core/pipeline/pipeline-registry.ts +344 -0
  55. package/src/core/pipeline/pipeline-renderer.ts +248 -0
  56. package/src/core/pipeline/pipeline-vue-utils.ts +96 -0
  57. package/src/core/pipeline/pipeline.ts +28 -0
  58. package/src/core/utils/index.ts +12 -0
  59. package/src/core/utils/inject-provider-decorators.ts +27 -0
  60. package/src/core/utils/lazy-inject-decorators.ts +38 -0
  61. package/src/core/utils/mouse-touch-event.ts +120 -0
  62. package/src/core/utils/playground-drag.ts +511 -0
  63. package/src/core/utils/playground-gesture.spec.ts +135 -0
  64. package/src/core/utils/playground-gesture.ts +118 -0
  65. package/src/core/utils/tween.ts +156 -0
  66. package/src/core/utils/use-gesture/core/Controller.ts +212 -0
  67. package/src/core/utils/use-gesture/core/EventStore.ts +46 -0
  68. package/src/core/utils/use-gesture/core/TimeoutStore.ts +28 -0
  69. package/src/core/utils/use-gesture/core/actions.ts +63 -0
  70. package/src/core/utils/use-gesture/core/config/commonConfigResolver.ts +86 -0
  71. package/src/core/utils/use-gesture/core/config/coordinatesConfigResolver.ts +48 -0
  72. package/src/core/utils/use-gesture/core/config/dragConfigResolver.ts +140 -0
  73. package/src/core/utils/use-gesture/core/config/hoverConfigResolver.ts +11 -0
  74. package/src/core/utils/use-gesture/core/config/moveConfigResolver.ts +11 -0
  75. package/src/core/utils/use-gesture/core/config/pinchConfigResolver.ts +59 -0
  76. package/src/core/utils/use-gesture/core/config/resolver.ts +70 -0
  77. package/src/core/utils/use-gesture/core/config/scrollConfigResolver.ts +8 -0
  78. package/src/core/utils/use-gesture/core/config/sharedConfigResolver.ts +28 -0
  79. package/src/core/utils/use-gesture/core/config/support.ts +52 -0
  80. package/src/core/utils/use-gesture/core/config/wheelConfigResolver.ts +8 -0
  81. package/src/core/utils/use-gesture/core/engines/CoordinatesEngine.ts +78 -0
  82. package/src/core/utils/use-gesture/core/engines/DragEngine.ts +403 -0
  83. package/src/core/utils/use-gesture/core/engines/Engine.ts +406 -0
  84. package/src/core/utils/use-gesture/core/engines/HoverEngine.ts +43 -0
  85. package/src/core/utils/use-gesture/core/engines/MoveEngine.ts +52 -0
  86. package/src/core/utils/use-gesture/core/engines/PinchEngine.ts +345 -0
  87. package/src/core/utils/use-gesture/core/engines/ScrollEngine.ts +42 -0
  88. package/src/core/utils/use-gesture/core/engines/WheelEngine.ts +48 -0
  89. package/src/core/utils/use-gesture/core/index.ts +7 -0
  90. package/src/core/utils/use-gesture/core/parser.ts +92 -0
  91. package/src/core/utils/use-gesture/core/types/action.ts +19 -0
  92. package/src/core/utils/use-gesture/core/types/config.ts +255 -0
  93. package/src/core/utils/use-gesture/core/types/handlers.ts +73 -0
  94. package/src/core/utils/use-gesture/core/types/index.ts +11 -0
  95. package/src/core/utils/use-gesture/core/types/internalConfig.ts +80 -0
  96. package/src/core/utils/use-gesture/core/types/state.ts +275 -0
  97. package/src/core/utils/use-gesture/core/types/utils.ts +204 -0
  98. package/src/core/utils/use-gesture/core/types.ts +8 -0
  99. package/src/core/utils/use-gesture/core/utils/events.ts +158 -0
  100. package/src/core/utils/use-gesture/core/utils/fn.ts +32 -0
  101. package/src/core/utils/use-gesture/core/utils/maths.ts +63 -0
  102. package/src/core/utils/use-gesture/core/utils/state.ts +24 -0
  103. package/src/core/utils/use-gesture/core/utils.ts +8 -0
  104. package/src/core/utils/use-gesture/index.ts +6 -0
  105. package/src/core/utils/use-gesture/vanilla/DragGesture.ts +27 -0
  106. package/src/core/utils/use-gesture/vanilla/Gesture.ts +45 -0
  107. package/src/core/utils/use-gesture/vanilla/HoverGesture.ts +27 -0
  108. package/src/core/utils/use-gesture/vanilla/MoveGesture.ts +27 -0
  109. package/src/core/utils/use-gesture/vanilla/PinchGesture.ts +27 -0
  110. package/src/core/utils/use-gesture/vanilla/Recognizer.ts +43 -0
  111. package/src/core/utils/use-gesture/vanilla/ScrollGesture.ts +27 -0
  112. package/src/core/utils/use-gesture/vanilla/WheelGesture.ts +27 -0
  113. package/src/core/utils/use-gesture/vanilla/createGesture.ts +18 -0
  114. package/src/core/utils/use-gesture/vanilla/index.ts +18 -0
  115. package/src/env.d.ts +10 -0
  116. package/src/index.ts +17 -0
  117. package/src/playground-config.ts +70 -0
  118. package/src/playground-container.ts +129 -0
  119. package/src/playground-contribution.ts +81 -0
  120. package/src/playground-mock-tools.ts +143 -0
  121. package/src/playground.ts +401 -0
  122. package/src/plugin/index.ts +6 -0
  123. package/src/plugin/plugin.ts +226 -0
  124. package/src/services/clipboard-service.ts +40 -0
  125. package/src/services/context-menu-service.ts +25 -0
  126. package/src/services/index.ts +10 -0
  127. package/src/services/logger-service.ts +49 -0
  128. package/src/services/selection-service.ts +54 -0
  129. package/src/services/storage-service.ts +65 -0
  130. package/src/vue/index.ts +8 -0
  131. package/src/vue/playground-vue-context.ts +22 -0
  132. package/src/vue/playground-vue-provider.ts +115 -0
  133. package/src/vue/playground-vue-renderer.vue +44 -0
@@ -0,0 +1,547 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { inject, injectable, optional } from 'inversify';
7
+ import { Disposable, domUtils, PositionSchema } from '@flowgram-vue/utils';
8
+
9
+ import { Gesture } from '../utils/use-gesture';
10
+ import { PlaygroundGesture } from '../utils/playground-gesture';
11
+ import { MouseTouchEvent, PlaygroundDrag } from '../utils';
12
+ import { PipelineLayerPriority } from '../pipeline';
13
+ import { ProtectWheelArea } from '../../common/protect-wheel-area';
14
+ import { observeEntity } from '../../common';
15
+ import { Layer, LayerOptions } from './layer';
16
+ import {
17
+ EditorState,
18
+ type EditorStateChangeEvent,
19
+ EditorStateConfigEntity,
20
+ PlaygroundConfigEntity,
21
+ type PlaygroundConfigEntityData,
22
+ } from './config';
23
+
24
+ /**
25
+ * MOUSE: 鼠标友好模式,鼠标左键拖动画布,滚动缩放 (适合 windows )
26
+ * PAD: 双指同向移动拖动,双指张开捏合缩放 (适合 mac)
27
+ */
28
+ export type PlaygroundInteractiveType = 'MOUSE' | 'PAD';
29
+
30
+ export interface PlaygroundLayerOptions extends LayerOptions {
31
+ /**
32
+ * 阻止浏览器默认的手势(苹果触摸板),包含:放大缩小、左右滑动翻页,默认为 false
33
+ */
34
+ preventGlobalGesture?: boolean;
35
+
36
+ ineractiveType?: PlaygroundInteractiveType;
37
+
38
+ /** 悬浮服务 */
39
+ hoverService?: {
40
+ /** 精确判断当前鼠标位置是否有元素存在 */
41
+ isSomeHovered: () => boolean;
42
+ updateHoverPosition: (position: PositionSchema, target?: HTMLElement) => void;
43
+ clearHovered: () => void;
44
+ };
45
+ }
46
+
47
+ /**
48
+ * 基础层,控制画布缩放/滚动等操作
49
+ */
50
+ @injectable()
51
+ export class PlaygroundLayer extends Layer<PlaygroundLayerOptions> {
52
+ @observeEntity(PlaygroundConfigEntity)
53
+ protected playgroundConfigEntity: PlaygroundConfigEntity;
54
+
55
+ @observeEntity(EditorStateConfigEntity)
56
+ protected editorStateConfig: EditorStateConfigEntity;
57
+
58
+ @optional()
59
+ @inject(ProtectWheelArea)
60
+ protectWheelArea?: ProtectWheelArea;
61
+
62
+ private cancelStateListen?: Disposable;
63
+
64
+ private lastShortcutState?: EditorState;
65
+
66
+ private currentGesture?: PlaygroundGesture;
67
+
68
+ private startGrabScroll: { scrollX: number; scrollY: number } = {
69
+ scrollX: 0,
70
+ scrollY: 0,
71
+ };
72
+
73
+ private cursorStyle: HTMLStyleElement = document.createElement('style');
74
+
75
+ private maskNode: HTMLDivElement = document.createElement('div');
76
+
77
+ onReady(): void {
78
+ this.options = {
79
+ preventGlobalGesture: false,
80
+ ...this.options,
81
+ };
82
+ /**
83
+ * 阻止默认的浏览器手势缩放
84
+ */
85
+ if (this.options.preventGlobalGesture) {
86
+ const gesturePreventGlobal = new Gesture(document.body, {
87
+ /* v8 ignore next 3 */
88
+ onPinch: () => {
89
+ // Do nothing
90
+ },
91
+ });
92
+ if (document.documentElement) {
93
+ document.documentElement.style.overscrollBehaviorX = 'none';
94
+ }
95
+ document.body.style.overscrollBehaviorX = 'none';
96
+ this.toDispose.push(Disposable.create(() => gesturePreventGlobal.destroy()));
97
+ }
98
+ this.toDispose.pushAll([
99
+ this.config.onGrabDisableChange((disable) => {
100
+ if (disable) {
101
+ this.grabDragger.stop(0, 0);
102
+ }
103
+ }),
104
+ /**
105
+ * 防止滚动事件被透出到业务层滚动
106
+ */
107
+ domUtils.addStandardDisposableListener(this.playgroundNode, 'wheel', (event: WheelEvent) => {
108
+ // 判断当前 scrollParent,有滚动条则停止滚动
109
+ if (this.getScrollParent(event.target as HTMLElement)) {
110
+ return;
111
+ }
112
+ event.preventDefault();
113
+ event.stopPropagation();
114
+ }),
115
+ /**
116
+ * 在父节点上监听滚动事件
117
+ */
118
+ this.listenPlaygroundEvent(
119
+ 'wheel',
120
+ this.handleWheelEvent.bind(this),
121
+ PipelineLayerPriority.BASE_LAYER,
122
+ { passive: true }
123
+ ),
124
+ /**
125
+ * 监听触控拖动画布操作
126
+ */
127
+ this.listenPlaygroundEvent(
128
+ 'touchstart',
129
+ (e: TouchEvent) => {
130
+ const { clientX: x, clientY: y } = MouseTouchEvent.getEventCoord(e);
131
+ if (!this.options?.hoverService) {
132
+ return;
133
+ }
134
+ this.options.hoverService.updateHoverPosition(
135
+ {
136
+ x,
137
+ y,
138
+ },
139
+ e.target as HTMLElement
140
+ );
141
+ const isSomeHovered = this.options.hoverService?.isSomeHovered();
142
+ if (isSomeHovered) {
143
+ return;
144
+ }
145
+ this.grabDragger.start(x, y);
146
+ },
147
+ // 这里必须监听 NORMAL_LAYER,该图层最先触发
148
+ PipelineLayerPriority.NORMAL_LAYER
149
+ ),
150
+ this.listenPlaygroundEvent('touchend', (e: TouchEvent) => {
151
+ this.options.hoverService?.clearHovered();
152
+ }),
153
+ this.listenPlaygroundEvent('touchcancel', (e: TouchEvent) => {
154
+ this.options.hoverService?.clearHovered();
155
+ }),
156
+ this.listenPlaygroundEvent(
157
+ 'mousedown',
158
+ (e: MouseEvent) => {
159
+ const isMouseCenterButton = e.button === 1;
160
+
161
+ // 按住中键,进入拖拽模式,鼠标模式不支持
162
+ if (isMouseCenterButton && !this.isMouseMode()) {
163
+ this.editorStateConfig.changeState(EditorState.STATE_GRAB.id);
164
+ }
165
+
166
+ // 触控板模式下,目前支持按住 space 键或者鼠标中键后拖动
167
+ if (this.isGrab() && (this.editorStateConfig.isPressingSpaceBar || isMouseCenterButton)) {
168
+ this.grabDragger.start(e.clientX, e.clientY);
169
+ }
170
+ },
171
+ PipelineLayerPriority.BASE_LAYER
172
+ ),
173
+ this.listenPlaygroundEvent(
174
+ 'mousedown',
175
+ (e: MouseEvent) => {
176
+ const isSomeHovered = this.options?.hoverService?.isSomeHovered();
177
+
178
+ // 如果是鼠标优先模式,当前位置不是节点,并且没有按下 shift,才启动拖拽
179
+ if (this.isMouseMode() && !isSomeHovered && !this.editorStateConfig.isPressingShift) {
180
+ this.grabDragger.start(e.clientX, e.clientY);
181
+ }
182
+ },
183
+ // 这里必须监听 NORMAL_LAYER,该图层最先触发
184
+ PipelineLayerPriority.NORMAL_LAYER
185
+ ),
186
+
187
+ this.editorStateConfig.onStateChange(this.onStateChanged.bind(this)),
188
+
189
+ // 单独监听 shift 按键
190
+ // 只有 keydown 能监听到 shift 按键,keypress 无法监听到
191
+ this.listenGlobalEvent(
192
+ 'keydown',
193
+ (e: KeyboardEvent) => {
194
+ if (e.shiftKey) {
195
+ this.editorStateConfig.isPressingShift = true;
196
+
197
+ // 如果是鼠标优先,按住 shift 键需要更新鼠标为默认
198
+ if (this.isMouseMode()) {
199
+ this.config.updateCursor('');
200
+ }
201
+ }
202
+ },
203
+ PipelineLayerPriority.BASE_LAYER
204
+ ),
205
+
206
+ // 监听快捷键
207
+ this.listenGlobalEvent(
208
+ 'keypress',
209
+ (e: KeyboardEvent) => {
210
+ if (!this.isFocused || e.target !== this.playgroundNode) return;
211
+
212
+ // PS: 如果是鼠标优先模式,不监听快捷键
213
+ if (this.isMouseMode()) {
214
+ return;
215
+ }
216
+
217
+ const state = this.editorStateConfig.getStateFromShortcut(e);
218
+
219
+ // 使用场景:
220
+ // 在按住空格时(进入 grab 模式),此时点击工具栏的手型工具,需禁止退出 grab 模式
221
+ // 需要让业务侧感知是否按住空格
222
+ if (e.key === ' ') {
223
+ this.editorStateConfig.isPressingSpaceBar = true;
224
+ }
225
+
226
+ // 部分状态不允许重复进入
227
+ if (
228
+ state?.shortcutWorksOnlyOnStateChanged === true &&
229
+ state === this.editorStateConfig.getCurrentState()
230
+ ) {
231
+ return;
232
+ }
233
+
234
+ this.lastShortcutState = state;
235
+ if (state) {
236
+ this.editorStateConfig.changeState(state.id);
237
+ }
238
+ },
239
+ PipelineLayerPriority.BASE_LAYER
240
+ ),
241
+ this.listenGlobalEvent('keyup', (e: KeyboardEvent) => {
242
+ if (e.key === ' ') {
243
+ this.editorStateConfig.isPressingSpaceBar = false;
244
+ }
245
+
246
+ this.editorStateConfig.isPressingShift = false;
247
+
248
+ if (this.lastShortcutState && this.lastShortcutState.shortcutAutoEsc) {
249
+ this.editorStateConfig.toDefaultState();
250
+ }
251
+
252
+ this.lastShortcutState = undefined;
253
+ }),
254
+ {
255
+ // 在进入 grab 模式后,此时后退页面,需清理样式
256
+ dispose: () => {
257
+ if (this.maskNode.parentNode) {
258
+ this.maskNode.parentNode.removeChild(this.maskNode);
259
+ }
260
+ if (this.cursorStyle.parentNode) {
261
+ this.cursorStyle.parentNode.removeChild(this.cursorStyle);
262
+ }
263
+ },
264
+ },
265
+ ]);
266
+ // 切换到鼠标模式
267
+ if (this.options.ineractiveType === 'MOUSE') {
268
+ this.editorStateConfig.changeState(EditorState.STATE_MOUSE_FRIENDLY_SELECT.id);
269
+ }
270
+ }
271
+
272
+ private getCursor(cursor: string | undefined) {
273
+ if (!cursor) {
274
+ return '';
275
+ }
276
+ return this.playgroundConfigEntity.getCursors?.()?.[cursor] ?? cursor;
277
+ }
278
+
279
+ /** 是否为鼠标优先模式 */
280
+ private isMouseMode() {
281
+ return this.editorStateConfig.isMouseFriendlyMode();
282
+ }
283
+
284
+ onStateChanged(e: EditorStateChangeEvent): void {
285
+ const { state } = e;
286
+ if (this.cancelStateListen) {
287
+ this.cancelStateListen.dispose();
288
+ this.cancelStateListen = undefined;
289
+ }
290
+ if (state.handle) {
291
+ state.handle(this.config, e);
292
+ }
293
+ if (state.cursor) {
294
+ this.playgroundConfigEntity.updateCursor(state.cursor);
295
+ if (this.currentGesture) {
296
+ this.playgroundNode.style.cursor = this.getCursor(state.cursor);
297
+ }
298
+ } else {
299
+ this.playgroundConfigEntity.updateCursor('');
300
+ this.playgroundNode.style.cursor = '';
301
+ }
302
+
303
+ // 避免触发控件交互
304
+ if (state.cursor === 'grab' || state.cursor === 'grabbing') {
305
+ // 在鼠标优先交互模式下,应该要允许控件交互,可以选择节点拖动
306
+ if (state === EditorState.STATE_MOUSE_FRIENDLY_SELECT) {
307
+ return;
308
+ }
309
+
310
+ this.maskNode.style.cssText = `
311
+ position: absolute;
312
+ width: 100%;
313
+ height: 100%;
314
+ z-index: 100;
315
+ `;
316
+ this.playgroundNode.appendChild(this.maskNode);
317
+ } else {
318
+ if (this.maskNode.parentNode) {
319
+ this.maskNode.parentNode.removeChild(this.maskNode);
320
+ }
321
+ }
322
+ // 按 esc 退出
323
+ if (state.cancelMode === 'esc') {
324
+ this.cancelStateListen = domUtils.addStandardDisposableListener(
325
+ document.body,
326
+ 'keydown',
327
+ (keyboard: KeyboardEvent) => {
328
+ if (keyboard.key === 'Escape') {
329
+ this.editorStateConfig.toDefaultState();
330
+ }
331
+ },
332
+ true
333
+ );
334
+ } else if (state.cancelMode === 'once') {
335
+ // 只执行一次
336
+ this.editorStateConfig.toDefaultState();
337
+ }
338
+ }
339
+
340
+ protected grabDragger = new PlaygroundDrag({
341
+ onDragStart: (e) => {
342
+ if (this.config.grabDisable) return;
343
+ this.config.updateCursor('grabbing');
344
+ this.startGrabScroll = {
345
+ scrollX: this.config.config.scrollX,
346
+ scrollY: this.config.config.scrollY,
347
+ };
348
+ },
349
+ onDrag: (e) => {
350
+ if (this.config.grabDisable) return;
351
+ this.config.updateConfig({
352
+ scrollX: this.startGrabScroll.scrollX - e.endPos.x + e.startPos.x,
353
+ scrollY: this.startGrabScroll.scrollY - e.endPos.y + e.startPos.y,
354
+ });
355
+ },
356
+ onDragEnd: (e) => {
357
+ if (this.isGrab()) {
358
+ // 可能已经取消了
359
+ this.config.updateCursor('grab');
360
+ }
361
+
362
+ // 如果拖拽触发自中键,需从拖拽态退出,且重置光标
363
+ const isMouseCenterButton = e.button === 1;
364
+ if (isMouseCenterButton) {
365
+ if (this.isMouseMode()) {
366
+ this.editorStateConfig.changeState(EditorState.STATE_MOUSE_FRIENDLY_SELECT.id);
367
+ this.config.updateCursor('grab');
368
+ } else {
369
+ this.editorStateConfig.toDefaultState();
370
+ this.config.updateCursor('');
371
+ }
372
+ }
373
+ },
374
+ });
375
+
376
+ protected isGrab(): boolean {
377
+ const currentState = this.editorStateConfig.getCurrentState();
378
+
379
+ // STATE_GRAB 和 STATE_MOUSE_FRIENDLY_SELECT 都允许拖动
380
+ return (
381
+ currentState === EditorState.STATE_GRAB ||
382
+ currentState === EditorState.STATE_MOUSE_FRIENDLY_SELECT
383
+ );
384
+ }
385
+
386
+ createGesture(): void {
387
+ if (!this.currentGesture) {
388
+ this.currentGesture = new PlaygroundGesture(this.playgroundNode, this.config);
389
+ this.currentGesture.onDispose(() => {
390
+ this.currentGesture = undefined;
391
+ });
392
+ this.toDispose.push(this.currentGesture);
393
+ }
394
+ }
395
+
396
+ protected handleScrollEvent(event: WheelEvent): void {
397
+ const { playgroundConfigEntity } = this;
398
+ const scrollX = playgroundConfigEntity.config.scrollX + event.deltaX;
399
+ const scrollY = playgroundConfigEntity.config.scrollY + event.deltaY;
400
+ const state: Partial<PlaygroundConfigEntityData> = {
401
+ scrollX,
402
+ scrollY,
403
+ };
404
+ playgroundConfigEntity.updateConfig(state);
405
+ }
406
+
407
+ protected getMouseScaleDelta(): number {
408
+ const { mouseScrollDelta, zoom } = this.config.config;
409
+ if (typeof mouseScrollDelta === 'function') {
410
+ return mouseScrollDelta(zoom);
411
+ }
412
+ return mouseScrollDelta!;
413
+ }
414
+
415
+ /**
416
+ * 监听滚动事件
417
+ * @param event
418
+ */
419
+ protected handleWheelEvent(event: WheelEvent): void {
420
+ const e = event as any;
421
+ if ((this.currentGesture && this.currentGesture.pinching) || event.ctrlKey || event.metaKey)
422
+ return;
423
+
424
+ // 判断当前 scrollParent,有滚动条则停止滚动
425
+ if (this.getScrollParent(event.target as HTMLElement)) {
426
+ return;
427
+ }
428
+
429
+ // 鼠标优先模式,使用滚轮缩放,并且在当前鼠标位置放大缩小
430
+ if (this.isMouseMode()) {
431
+ // 这里没有使用 this.config.zoomin 和 zoomout 方法
432
+ // 因为这两个方法目前看没有实现居中缩放的效果,且体验有些卡顿
433
+ const { zoom, minZoom, maxZoom, scrollX, scrollY } = this.playgroundConfigEntity.config;
434
+
435
+ // 鼠标模式下,为了避免过快缩放,这里比例相对触控板模式缩小一倍,这个参数从业务侧传过来,同时提供默认值
436
+ const scaleStep = this.getMouseScaleDelta();
437
+ const scaleMin = minZoom;
438
+ const scaleMax = maxZoom;
439
+
440
+ // 处理横向和竖向滚轮
441
+ const getDelta = (wheelDelta: number): number => (wheelDelta > 0 ? -scaleStep : scaleStep);
442
+
443
+ // 优先使用垂直滚动,如果垂直滚动为0则使用水平滚动
444
+ const wheelDelta = Math.abs(e.deltaY) > 0 ? e.deltaY : e.deltaX;
445
+ const delta = getDelta(wheelDelta);
446
+
447
+ const oldScale = this.config.finalScale;
448
+ const originX = event.clientX;
449
+ const originY = event.clientY;
450
+
451
+ const newScale = Math.max(scaleMin, Math.min(scaleMax, zoom + delta));
452
+
453
+ const origin = this.config.getPosFromMouseEvent(
454
+ { clientX: originX, clientY: originY },
455
+ false
456
+ );
457
+
458
+ // 计算放大后的位置,鼠标位置居中缩放
459
+ // 参见 packages-ide-editor/common/core/src/core/utils/playground-gesture.ts
460
+ const finalPos = {
461
+ x: (origin.x / oldScale) * newScale,
462
+ y: (origin.y / oldScale) * newScale,
463
+ };
464
+ this.config.updateConfig({
465
+ scrollX: scrollX + finalPos.x - origin.x,
466
+ scrollY: scrollY + finalPos.y - origin.y,
467
+ zoom: newScale,
468
+ });
469
+ return;
470
+ }
471
+
472
+ this.handleScrollEvent(e);
473
+ }
474
+
475
+ /**
476
+ * 获取 wheel 事件滚动的父元素
477
+ * @param dom
478
+ */
479
+ protected getScrollParent(ele?: HTMLElement | null): HTMLElement | null {
480
+ if (!ele || ele === this.pipelineNode.parentElement) {
481
+ return null;
482
+ }
483
+
484
+ const hasScrollableXContent = ele.scrollWidth > ele.clientWidth;
485
+ const hasScrollableYContent = ele.scrollHeight > ele.clientHeight;
486
+ const overflowXStyle = window.getComputedStyle(ele).overflowX;
487
+ const overflowYStyle = window.getComputedStyle(ele).overflowY;
488
+ const isOverflowXScrollable = ['auto', 'scroll', 'overlay'].includes(overflowXStyle);
489
+ const isOverflowYScrollable = ['auto', 'scroll', 'overlay'].includes(overflowYStyle);
490
+
491
+ const hasScrollableContent =
492
+ (hasScrollableXContent && isOverflowXScrollable) ||
493
+ (hasScrollableYContent && isOverflowYScrollable);
494
+
495
+ if (hasScrollableContent || this.protectWheelArea?.(ele)) {
496
+ return ele;
497
+ }
498
+
499
+ return this.getScrollParent(ele.parentElement);
500
+ }
501
+
502
+ autorun(): void {
503
+ const playgroundConfig = this.playgroundConfigEntity.config;
504
+ const { cursor } = this.playgroundConfigEntity;
505
+ const finalCursor = this.getCursor(cursor);
506
+
507
+ // 创建手势
508
+ if (this.config.zoomEnable) {
509
+ this.createGesture();
510
+ } else if (this.currentGesture) {
511
+ this.currentGesture.dispose();
512
+ }
513
+ // // 设置 pipeline 的样式
514
+ // if (scaleVisible) {
515
+ // domUtils.setStyle(this.pipelineNode, {
516
+ // left: SCALE_WIDTH - playgroundConfig.scrollX,
517
+ // top: SCALE_WIDTH - playgroundConfig.scrollY,
518
+ // width: playgroundConfig.width,
519
+ // height: playgroundConfig.height,
520
+ // });
521
+ // } else {
522
+ // }
523
+ domUtils.setStyle(this.pipelineNode, {
524
+ left: -playgroundConfig.scrollX,
525
+ top: -playgroundConfig.scrollY,
526
+ width: playgroundConfig.width,
527
+ height: playgroundConfig.height,
528
+ });
529
+ this.playgroundNode.style.cursor = finalCursor;
530
+ // Note: 为什么要通过 style 注入样式
531
+ // 原因:在 pipelineNode.parentElement 上设置 style.cursor,子元素继承样式时 cursor 样式优先级不够(子元素自身也存在 cursor 配置)
532
+ if (cursor === 'grab' || cursor === 'grabbing') {
533
+ let classSelector = '';
534
+ this.playgroundNode.classList.forEach((className) => {
535
+ classSelector += `.${className}`;
536
+ });
537
+ this.cursorStyle.innerText = `.${classSelector} * { cursor: ${finalCursor} }`;
538
+ if (!this.cursorStyle.parentNode) {
539
+ document.head.appendChild(this.cursorStyle);
540
+ }
541
+ } else {
542
+ if (this.cursorStyle.parentNode) {
543
+ this.cursorStyle.parentNode.removeChild(this.cursorStyle);
544
+ }
545
+ }
546
+ }
547
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export * from './pipeline';
7
+ export * from './pipeline-renderer';
8
+ export * from './pipeline-registry';
9
+ export * from './pipeline-entities-selector';
10
+ export * from './pipeline-entities';