@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,604 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Disposable, domUtils, Emitter, PromiseDeferred, Rectangle } from '@flowgram-vue/utils'
7
+ import {
8
+ ConfigEntity,
9
+ Entity,
10
+ PositionData,
11
+ PositionSchema,
12
+ SizeData,
13
+ SizeSchema,
14
+ TransformData,
15
+ } from '../../../common'
16
+ import { MouseTouchEvent, startTween } from '../../utils'
17
+ // import { Selectable } from '../../able'
18
+
19
+ export interface PlaygroundConfigEntityData {
20
+ scrollX: number // 滚动 x
21
+ scrollY: number // 滚动 y
22
+ originX: number // 左上角默认开始的原点坐标
23
+ originY: number // 左上角默认开始原点坐标
24
+ width: number // 编辑区宽,在 onResize 触发后重制
25
+ height: number // 编辑区高,在 onResize 触发后重制
26
+ reverseScroll: boolean // 支持反方向滚动
27
+ overflowX: 'hidden' | 'scroll'
28
+ overflowY: 'hidden' | 'scroll'
29
+ minZoom: number // 最大
30
+ maxZoom: number // 最小
31
+ zoom: number // 缩放比
32
+ scrollLimitX?: number // 水平滚动限制
33
+ scrollLimitY?: number // 垂直滚动限制
34
+ mouseScrollDelta?: number | ((zoom: number) => number); // 鼠标滚动时的 delta 值
35
+ pageBounds?: { x: number; y: number; width: number; height: number } // 编辑的画布边框,用于处理外部对齐问题
36
+ disabled: boolean // 禁用状态
37
+ readonly: boolean // readonly 状态
38
+ scrollDisable: boolean
39
+ grabDisable: boolean // 禁用抓取拖拽画布能力
40
+ zoomDisable: boolean
41
+ }
42
+
43
+ export interface PlaygroundConfigRevealOpts {
44
+ entities?: Entity[]
45
+ position?: PositionSchema // 滚动到指定位置,并居中
46
+ bounds?: Rectangle // 滚动的 bounds
47
+ // selection?: boolean 是否回到选择器所在位置,默认 true
48
+ scrollDelta?: PositionSchema
49
+ zoom?: number // 需要缩放的比例
50
+ easing?: boolean // 是否开启缓动,默认开启
51
+ easingDuration?: number // 默认 500 ms
52
+ scrollToCenter?: boolean // 是否滚动到中心
53
+ }
54
+ export const SCALE_WIDTH = 0
55
+
56
+ /** 鼠标缩放 delta */
57
+ export const MOUSE_SCROLL_DELTA = (zoom: number) => zoom / 20;
58
+ export type PlaygroundScrollLimitFn = (scroll: { scrollX: number; scrollY: number }) => {
59
+ scrollX: number
60
+ scrollY: number
61
+ }
62
+ export type Cursors = Record<string, string>;
63
+ /**
64
+ * 全局画布的配置信息
65
+ */
66
+ export class PlaygroundConfigEntity extends ConfigEntity<PlaygroundConfigEntityData> {
67
+ static type = 'PlaygroundConfigEntity'
68
+
69
+ public getCursors: (() => Cursors | undefined) | undefined;
70
+ private _loading = false
71
+ private _zoomEnable = true
72
+
73
+ private _scrollLimitFn?: PlaygroundScrollLimitFn
74
+
75
+ private _onReadonlyOrDisabledChangeEmitter = new Emitter<{ readonly: boolean, disabled: boolean }>()
76
+ private _onGrabDisableChangeEmitter = new Emitter<boolean>()
77
+ readonly onGrabDisableChange = this._onGrabDisableChangeEmitter.event;
78
+ readonly onReadonlyOrDisabledChange = this._onReadonlyOrDisabledChangeEmitter.event
79
+ playgroundDomNode: HTMLElement = document.createElement('div')
80
+
81
+ cursor = 'default'
82
+ constructor(opts: any) {
83
+ super(opts);
84
+ this.toDispose.push(this._onReadonlyOrDisabledChangeEmitter)
85
+ }
86
+
87
+ /**
88
+ * 是否禁用抓取拖拽画布能力
89
+ */
90
+ get grabDisable(): boolean {
91
+ return this.config.grabDisable;
92
+ }
93
+
94
+ /**
95
+ * 是否禁用抓取拖拽画布能力
96
+ */
97
+ set grabDisable(grabDisable: boolean) {
98
+ this.updateConfig({
99
+ grabDisable
100
+ })
101
+ }
102
+ get scrollDisable(): boolean {
103
+ return this.config.scrollDisable;
104
+ }
105
+ set scrollDisable(scrollDisable: boolean) {
106
+ this.updateConfig({
107
+ scrollDisable
108
+ })
109
+ }
110
+ get zoomDisable(): boolean {
111
+ return this.config.zoomDisable;
112
+ }
113
+ set zoomDisable(zoomDisable: boolean) {
114
+ this.updateConfig({
115
+ zoomDisable
116
+ })
117
+ }
118
+ getDefaultConfig(): PlaygroundConfigEntityData {
119
+ return {
120
+ scrollX: 0,
121
+ scrollY: 0,
122
+ originX: 0,
123
+ originY: 0,
124
+ width: 0,
125
+ height: 0,
126
+ minZoom: 0.25,
127
+ maxZoom: 2,
128
+ zoom: 1,
129
+ reverseScroll: true,
130
+ overflowX: 'scroll',
131
+ overflowY: 'scroll',
132
+ disabled: false,
133
+ readonly: false,
134
+ grabDisable: false,
135
+ scrollDisable: false,
136
+ zoomDisable: false,
137
+ mouseScrollDelta: MOUSE_SCROLL_DELTA
138
+ }
139
+ }
140
+
141
+ /**
142
+ * 添加滚动限制逻辑
143
+ * @param fn
144
+ */
145
+ addScrollLimit(fn: PlaygroundScrollLimitFn): void {
146
+ this._scrollLimitFn = fn
147
+ }
148
+
149
+ /**
150
+ * 更新实体配置
151
+ * @param props
152
+ */
153
+ updateConfig(props: Partial<PlaygroundConfigEntityData>): void {
154
+ if (props.zoom !== undefined) {
155
+ props = { ...props, zoom: this.normalizeZoom(props.zoom) }
156
+ }
157
+ props = { ...this.config, ...props }
158
+ if (!props.reverseScroll) {
159
+ if (props.scrollX! < this.config.originX) {
160
+ props.scrollX = this.config.originX
161
+ }
162
+ if (props.scrollY! < this.config.originY) {
163
+ props.scrollY = this.config.originY
164
+ }
165
+ }
166
+ if (props.scrollLimitX !== undefined && props.scrollX! < props.scrollLimitX) {
167
+ props.scrollX = props.scrollLimitX
168
+ }
169
+ if (props.scrollLimitY !== undefined && props.scrollY! < props.scrollLimitY) {
170
+ props.scrollY = props.scrollLimitY
171
+ }
172
+ if (props.overflowX === 'hidden') {
173
+ props.scrollX = this.config.originX
174
+ }
175
+ if (props.overflowY === 'hidden') {
176
+ props.scrollY = this.config.originY
177
+ }
178
+ const scrollDisable = props.scrollDisable || this.scrollDisable
179
+ const { readonly, disabled, grabDisable } = this
180
+ if (scrollDisable) {
181
+ props.scrollX = this.config.scrollX
182
+ props.scrollY = this.config.scrollY
183
+ }
184
+ super.updateConfig(
185
+ this._scrollLimitFn
186
+ ? { ...props, ...this._scrollLimitFn({ scrollX: props.scrollX!, scrollY: props.scrollY! }) }
187
+ : props
188
+ )
189
+ const readonlyOrDisableChanged = readonly !== this.readonly || disabled !== this.disabled
190
+ if (readonlyOrDisableChanged) this._onReadonlyOrDisabledChangeEmitter.fire({ readonly: this.readonly, disabled: this.disabled })
191
+ if (grabDisable !== this.grabDisable) this._onGrabDisableChangeEmitter.fire(this.grabDisable)
192
+ }
193
+
194
+ /**
195
+ * 缩放比例
196
+ * 使用 zoom 替代
197
+ * @deprecated
198
+ */
199
+ get finalScale(): number {
200
+ if (!this.zoomEnable) return 1
201
+ return this.config.zoom
202
+ }
203
+
204
+ /**
205
+ * 缩放比例
206
+ */
207
+ get zoom(): number {
208
+ if (!this.zoomEnable) return 1
209
+ return this.config.zoom
210
+ }
211
+
212
+ get scrollData(): { scrollX: number; scrollY: number } {
213
+ return {
214
+ scrollX: this.config.scrollX,
215
+ scrollY: this.config.scrollY,
216
+ }
217
+ }
218
+
219
+ protected normalizeZoom(zoom: number): number {
220
+ if (!this.zoomEnable) return 1
221
+ if (this.zoomDisable) return this.config.zoom;
222
+ if (zoom < this.config.minZoom) {
223
+ zoom = this.config.minZoom
224
+ } else if (zoom > this.config.maxZoom) {
225
+ zoom = this.config.maxZoom
226
+ }
227
+ return zoom
228
+ }
229
+
230
+ /**
231
+ * 修改画布光标
232
+ * @param cursor
233
+ */
234
+ updateCursor(cursor: string): void {
235
+ if (this.cursor !== cursor) {
236
+ this.cursor = cursor
237
+ this.fireChange()
238
+ }
239
+ }
240
+
241
+ /**
242
+ * 获取相对画布的位置
243
+ * @param event
244
+ * @param widthScale 是否要计算缩放
245
+ */
246
+ getPosFromMouseEvent(
247
+ event:
248
+ | MouseEvent
249
+ | TouchEvent
250
+ | {
251
+ clientX: number;
252
+ clientY: number;
253
+ },
254
+ withScale = true
255
+ ): PositionSchema {
256
+ const { config } = this
257
+ const scale = withScale ? this.zoom : 1
258
+ const { clientX, clientY } = MouseTouchEvent.getEventCoord(event)
259
+ const clientRect = this.playgroundDomNode.getBoundingClientRect()
260
+ return {
261
+ x: (clientX + config.scrollX - clientRect.x) / scale,
262
+ y: (clientY + config.scrollY - clientRect.y) / scale,
263
+ }
264
+ }
265
+ getClientBounds(): Rectangle {
266
+ const clientRect = this.playgroundDomNode.getBoundingClientRect()
267
+ return new Rectangle(clientRect.x, clientRect.y, clientRect.width, clientRect.height)
268
+ }
269
+ /**
270
+ * 将画布中的位置转成相对 window 的位置
271
+ * @param pos
272
+ */
273
+ toFixedPos(pos: PositionSchema): PositionSchema {
274
+ const { config } = this
275
+ const clientRect = this.playgroundDomNode.getBoundingClientRect()
276
+ return {
277
+ x: pos.x - config.scrollX + clientRect.x,
278
+ y: pos.y - config.scrollY + clientRect.y,
279
+ }
280
+ }
281
+
282
+ /**
283
+ * 获取可视区域
284
+ */
285
+ getViewport(withScale: boolean = true): Rectangle {
286
+ const { config } = this
287
+ const scale = withScale ? this.finalScale : 1
288
+ return new Rectangle(
289
+ config.scrollX / scale,
290
+ config.scrollY / scale,
291
+ config.width / scale,
292
+ config.height / scale
293
+ )
294
+ }
295
+
296
+ /**
297
+ * 判断矩形是否在可视区域,如果有擦边页代表在可是区域
298
+ * @param bounds
299
+ * @param rotation
300
+ * @param includeAll - 是否包含在里边,默认 false
301
+ */
302
+ isViewportVisible(bounds: Rectangle, rotation: number = 0, includeAll: boolean = false): boolean {
303
+ return Rectangle.isViewportVisible(bounds, this.getViewport(), rotation, includeAll)
304
+ }
305
+
306
+ /**
307
+ * 按下边顺序执行
308
+ * 1. 指定的 entity 位置或 pos 位置
309
+ * 3. 初始化位置
310
+ */
311
+ scrollToView(opts: PlaygroundConfigRevealOpts = {}): Promise<void> {
312
+ const {
313
+ scrollDelta,
314
+ position: pos,
315
+ // selection = true,
316
+ easing = true,
317
+ easingDuration = 300,
318
+ entities,
319
+ } = opts
320
+ const { config } = this
321
+ const scale = opts.zoom ? opts.zoom : this.finalScale
322
+ let bounds: Rectangle | undefined
323
+ if (entities && entities.length > 0) {
324
+ const entitiesBounds = entities
325
+ .map((e) => {
326
+ const transform = e.getData<TransformData>(TransformData)
327
+ if (transform) return transform.bounds
328
+ const position = e.getData<PositionData>(PositionData)
329
+ const size = e.getData<SizeData>(SizeData) || { width: 0, height: 0 }
330
+ if (!position) return
331
+ return new Rectangle(position.x, position.y, size.width, size.height || 0)
332
+ })
333
+ .filter((e) => !!e) as Rectangle[]
334
+ if (entitiesBounds.length > 0) {
335
+ bounds = Rectangle.enlarge(entitiesBounds)
336
+ }
337
+ } else if (pos) {
338
+ bounds = new Rectangle(pos.x, pos.y, 0, 0)
339
+ } else if (opts.bounds) {
340
+ bounds = opts.bounds
341
+ } // else if (selection) {
342
+ // bounds = Selectable.getSelectedBounds(this.entityManager)
343
+ // }
344
+ if (!bounds) {
345
+ const defaultConfig = this.getDefaultConfig()
346
+ bounds = new Rectangle(
347
+ (defaultConfig.scrollX + config.width / 2) / scale,
348
+ (defaultConfig.scrollY + config.height / 2) / scale,
349
+ 0,
350
+ 0
351
+ )
352
+ }
353
+ if (!opts.scrollToCenter) {
354
+ const boundsVisible = this.getViewport()
355
+ // 判断是否看得见
356
+ if (boundsVisible.containsRectangle(bounds)) {
357
+ return Promise.resolve()
358
+ }
359
+ }
360
+ // TODO 微调滚动,而不是直接滚动到中心
361
+ const toValues = {
362
+ scrollX:
363
+ (bounds.x + bounds.width / 2 + (scrollDelta ? scrollDelta.x : 0)) * scale -
364
+ config.width / 2,
365
+ scrollY:
366
+ (bounds.y + bounds.height / 2 + (scrollDelta ? scrollDelta.y : 0)) * scale -
367
+ config.height / 2,
368
+ zoom: opts.zoom,
369
+ }
370
+ return this.scroll(toValues, easing, easingDuration)
371
+ }
372
+
373
+ /**
374
+ * 这只画布边框,元素编辑的时候回吸附画布边框
375
+ * @param bounds
376
+ */
377
+ setPageBounds(bounds: Rectangle): void {
378
+ this.updateConfig({
379
+ pageBounds: {
380
+ x: bounds.x,
381
+ y: bounds.y,
382
+ width: bounds.width,
383
+ height: bounds.height,
384
+ },
385
+ })
386
+ }
387
+
388
+ getPageBounds(): Rectangle | undefined {
389
+ const { pageBounds } = this.config
390
+ if (pageBounds) {
391
+ return new Rectangle(pageBounds.x, pageBounds.y, pageBounds.width, pageBounds.height)
392
+ }
393
+ }
394
+
395
+ /**
396
+ * 滚动到画布中央
397
+ * @param zoomToFit 是否缩放并适配外围大小
398
+ * @param fitPadding 适配外围的留白
399
+ * @param easing 是否缓动
400
+ */
401
+ scrollPageBoundsToCenter(
402
+ zoomToFit: boolean = true,
403
+ fitPadding = 16,
404
+ easing = true
405
+ ): Promise<void> {
406
+ const pageBounds = this.getPageBounds()
407
+ if (pageBounds) {
408
+ let zoom: number | undefined
409
+ const fitPaddingDouble = fitPadding * 2
410
+ if (zoomToFit) {
411
+ const fixedScale = SizeSchema.fixSize(
412
+ {
413
+ width: pageBounds.width,
414
+ height: pageBounds.height,
415
+ },
416
+ {
417
+ width:
418
+ fitPaddingDouble > this.config.width
419
+ ? fitPaddingDouble
420
+ : this.config.width - fitPaddingDouble,
421
+ height:
422
+ fitPaddingDouble > this.config.height
423
+ ? fitPaddingDouble
424
+ : this.config.height - fitPaddingDouble,
425
+ }
426
+ )
427
+ zoom = fixedScale
428
+ }
429
+ return this.scrollToView({
430
+ bounds: pageBounds,
431
+ zoom,
432
+ scrollToCenter: true,
433
+ // selection: false,
434
+ easing,
435
+ })
436
+ }
437
+ return this.scrollToView({ easing })
438
+ }
439
+
440
+ private cancelScrollTeeen?: Disposable
441
+
442
+ /**
443
+ * 滚动
444
+ * @param scroll
445
+ * @param easing - 是否开启缓动,默认开启
446
+ * @param easingDuration - 滚动持续时间,默认 300ms
447
+ */
448
+ scroll(
449
+ scroll: Partial<{ scrollX: number; scrollY: number; zoom: number }>,
450
+ easing: boolean = true,
451
+ easingDuration = 300
452
+ ): Promise<void> {
453
+ const deferred = new PromiseDeferred<void>()
454
+ if (this.cancelScrollTeeen) this.cancelScrollTeeen.dispose()
455
+ if (easing) {
456
+ const fromValues = {
457
+ scrollX: this.config.scrollX,
458
+ scrollY: this.config.scrollY,
459
+ zoom: this.config.zoom,
460
+ }
461
+ this.cancelScrollTeeen = startTween({
462
+ from: fromValues,
463
+ to: {
464
+ ...fromValues,
465
+ ...scroll,
466
+ },
467
+ onUpdate: (v) => {
468
+ this.updateConfig(v)
469
+ },
470
+ onComplete: () => {
471
+ this.cancelScrollTeeen = undefined
472
+ deferred.resolve()
473
+ },
474
+ onDispose: () => {
475
+ deferred.resolve()
476
+ },
477
+ duration: easingDuration,
478
+ })
479
+ } else {
480
+ this.updateConfig(scroll)
481
+ deferred.resolve()
482
+ }
483
+ return deferred.promise
484
+ }
485
+
486
+ /**
487
+ * 让 layer 的 node 节点不随着画布滚动条滚动
488
+ * @param layerNode
489
+ */
490
+ fixLayerPosition(layerNode: HTMLElement): void {
491
+ domUtils.setStyle(layerNode, {
492
+ left: this.config.scrollX,
493
+ top: this.config.scrollY,
494
+ })
495
+ }
496
+
497
+ get loading(): boolean {
498
+ return this._loading
499
+ }
500
+
501
+ set loading(loading: boolean) {
502
+ if (this.loading !== loading) {
503
+ this._loading = loading
504
+ this.fireChange()
505
+ }
506
+ }
507
+
508
+ /**
509
+ * @deprecated use 'zoomDisable' instead
510
+ */
511
+ get zoomEnable(): boolean {
512
+ return this._zoomEnable
513
+ }
514
+
515
+ /**
516
+ * 开启缩放
517
+ * @deprecated use 'zoomDisable' instead
518
+ */
519
+ set zoomEnable(zoomEnable: boolean) {
520
+ if (this._zoomEnable !== zoomEnable) {
521
+ this._zoomEnable = zoomEnable
522
+ this.fireChange()
523
+ }
524
+ }
525
+
526
+ /**
527
+ * 放大
528
+ */
529
+ zoomin(easing?: boolean, easingDuration?: number): void {
530
+ const unit = this.config.zoom / 10
531
+ const newZoom = Math.ceil((this.config.zoom + unit) * 10) / 10
532
+ this.updateZoom(newZoom, easing, easingDuration)
533
+ }
534
+
535
+ /**
536
+ * 缩小
537
+ */
538
+ zoomout(easing?: boolean, easingDuration?: number): void {
539
+ const unit = this.config.zoom / 10
540
+ const newZoom = Math.floor((this.config.zoom - unit) * 10) / 10
541
+ this.updateZoom(newZoom, easing, easingDuration)
542
+ }
543
+
544
+ updateZoom(newZoom: number, easing: boolean = true, easingDuration = 200): void {
545
+ newZoom = this.normalizeZoom(newZoom)
546
+ const { center } = this.getViewport()
547
+ const oldScale = this.finalScale
548
+ const newScale = !this.zoomEnable ? oldScale : newZoom
549
+ if (newScale !== oldScale) {
550
+ const delta = {
551
+ x: center.x * newScale - center.x * oldScale,
552
+ y: center.y * newScale - center.y * oldScale,
553
+ }
554
+ this.scroll(
555
+ {
556
+ scrollX: this.config.scrollX + delta.x,
557
+ scrollY: this.config.scrollY + delta.y,
558
+ zoom: newZoom,
559
+ },
560
+ easing,
561
+ easingDuration
562
+ )
563
+ }
564
+ }
565
+ get disabled(): boolean {
566
+ return this.config.disabled
567
+ }
568
+ get readonly(): boolean {
569
+ return this.config.readonly
570
+ }
571
+ get readonlyOrDisabled(): boolean {
572
+ return this.config.readonly || this.config.disabled
573
+ }
574
+ set readonly(readonly) {
575
+ this.updateConfig({
576
+ readonly
577
+ })
578
+ }
579
+ set disabled(disabled) {
580
+ this.updateConfig({
581
+ disabled
582
+ })
583
+ }
584
+
585
+ /**
586
+ * 适应大小
587
+ * @param bounds 目标大小
588
+ * @param easing 是否开启动画,默认开启
589
+ * @param padding 边界空白
590
+ * @param easingDuration
591
+ */
592
+ fitView(bounds: Rectangle, easing = true, padding = 0, easingDuration = 300): Promise<void> {
593
+ const viewport = this.getViewport(false);
594
+ const zoom = SizeSchema.fixSize(bounds.clone().pad(padding, padding), viewport);
595
+ return this.scrollToView({
596
+ bounds,
597
+ zoom,
598
+ easing,
599
+ easingDuration,
600
+ scrollToCenter: true,
601
+ });
602
+
603
+ }
604
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export * from './config';
7
+ export * from './layer';
8
+ export * from './playground-layer';