@knotx/plugins-canvas 0.3.4 → 0.3.6

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/dist/index.cjs CHANGED
@@ -7,6 +7,79 @@ const lodashEs = require('lodash-es');
7
7
  const react = require('react');
8
8
  const reactZoomPanPinch = require('react-zoom-pan-pinch');
9
9
 
10
+ function calculateTransform({
11
+ node,
12
+ container,
13
+ transform,
14
+ scale,
15
+ block = "nearest",
16
+ inline = "nearest",
17
+ offset = 0
18
+ }) {
19
+ var _a, _b, _c, _d, _e, _f;
20
+ const nodeX = node.position.x;
21
+ const nodeY = node.position.y;
22
+ const nodeWidth = (_b = (_a = node.measured) == null ? void 0 : _a.width) != null ? _b : 0;
23
+ const nodeHeight = (_d = (_c = node.measured) == null ? void 0 : _c.height) != null ? _d : 0;
24
+ const viewportWidth = container.width;
25
+ const viewportHeight = container.height;
26
+ let positionX = -transform.positionX;
27
+ let positionY = -transform.positionY;
28
+ const offsetX = typeof offset === "number" ? offset : (_e = offset.x) != null ? _e : 0;
29
+ const offsetY = typeof offset === "number" ? offset : (_f = offset.y) != null ? _f : 0;
30
+ switch (inline) {
31
+ case "center":
32
+ positionX = (nodeX + nodeWidth / 2) * scale - viewportWidth / 2;
33
+ break;
34
+ case "start":
35
+ positionX = nodeX * scale - offsetX;
36
+ break;
37
+ case "end":
38
+ positionX = (nodeX + nodeWidth) * scale - viewportWidth + offsetX;
39
+ break;
40
+ case "nearest": {
41
+ const nodeRight = (nodeX + nodeWidth) * scale;
42
+ const nodeLeft = nodeX * scale;
43
+ const currentLeft = -transform.positionX;
44
+ const currentRight = currentLeft + viewportWidth;
45
+ if (nodeRight > currentRight) {
46
+ positionX = nodeRight - viewportWidth + offsetX;
47
+ } else if (nodeLeft < currentLeft) {
48
+ positionX = nodeLeft - offsetX;
49
+ }
50
+ break;
51
+ }
52
+ }
53
+ switch (block) {
54
+ case "center":
55
+ positionY = (nodeY + nodeHeight / 2) * scale - viewportHeight / 2;
56
+ break;
57
+ case "start":
58
+ positionY = nodeY * scale - offsetY;
59
+ break;
60
+ case "end":
61
+ positionY = (nodeY + nodeHeight) * scale - viewportHeight + offsetY;
62
+ break;
63
+ case "nearest": {
64
+ const nodeBottom = (nodeY + nodeHeight) * scale;
65
+ const nodeTop = nodeY * scale;
66
+ const currentTop = -transform.positionY;
67
+ const currentBottom = currentTop + viewportHeight;
68
+ if (nodeBottom > currentBottom) {
69
+ positionY = nodeBottom - viewportHeight + offsetY;
70
+ } else if (nodeTop < currentTop) {
71
+ positionY = nodeTop - offsetY;
72
+ }
73
+ break;
74
+ }
75
+ }
76
+ return {
77
+ scale,
78
+ positionX,
79
+ positionY
80
+ };
81
+ }
82
+
10
83
  var __create = Object.create;
11
84
  var __defProp = Object.defineProperty;
12
85
  var __defProps = Object.defineProperties;
@@ -75,8 +148,8 @@ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use
75
148
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
76
149
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
77
150
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
78
- var _init_dec, _render_dec, _setTransform_dec, _zoomOut_dec, _zoomIn_dec, _scrollNodeIntoView_dec, _removeListener_dec, _addListener_dec, _interaction_dec, _container_dec, _getNode_dec, _transform_dec, _ref_dec, _a, _init;
79
- class Canvas extends (_a = core.BasePlugin, _ref_dec = [decorators.register("ref")], _transform_dec = [decorators.register("transform")], _getNode_dec = [decorators.inject.getNode()], _container_dec = [decorators.inject.container()], _interaction_dec = [decorators.inject.interaction()], _addListener_dec = [decorators.register("addListener")], _removeListener_dec = [decorators.register("removeListener")], _scrollNodeIntoView_dec = [decorators.tool("Scroll node into viewport center", {
151
+ var _init_dec, _render_dec, _setTransform_dec, _zoomOut_dec, _zoomIn_dec, _scrollNodeIntoView_dec, _removeListener_dec, _addListener_dec, _interaction_dec, _container_dec, _getNode_dec, _edgeScroll_dec, _transform_dec, _ref_dec, _a, _init;
152
+ class Canvas extends (_a = core.BasePlugin, _ref_dec = [decorators.register("ref")], _transform_dec = [decorators.register("transform")], _edgeScroll_dec = [decorators.register("edgeScroll")], _getNode_dec = [decorators.inject.getNode()], _container_dec = [decorators.inject.container()], _interaction_dec = [decorators.inject.interaction()], _addListener_dec = [decorators.register("addListener")], _removeListener_dec = [decorators.register("removeListener")], _scrollNodeIntoView_dec = [decorators.tool("Scroll node into viewport center", {
80
153
  type: "object",
81
154
  properties: {
82
155
  nodeId: { type: "string" },
@@ -122,83 +195,93 @@ class Canvas extends (_a = core.BasePlugin, _ref_dec = [decorators.register("ref
122
195
  __publicField(this, "name", "canvas");
123
196
  __publicField(this, "ref", __runInitializers(_init, 8, this, null)), __runInitializers(_init, 11, this);
124
197
  __publicField(this, "transform", __runInitializers(_init, 12, this)), __runInitializers(_init, 15, this);
125
- __publicField(this, "getNode", __runInitializers(_init, 16, this)), __runInitializers(_init, 19, this);
126
- __publicField(this, "container", __runInitializers(_init, 20, this)), __runInitializers(_init, 23, this);
127
- __publicField(this, "interaction", __runInitializers(_init, 24, this)), __runInitializers(_init, 27, this);
198
+ __publicField(this, "edgeScroll", __runInitializers(_init, 16, this, {
199
+ config: {
200
+ enabled: false,
201
+ edgeSize: 50,
202
+ maxSpeed: 10,
203
+ cornerSize: 25
204
+ },
205
+ setConfig: (config) => {
206
+ this.edgeScroll.config = __spreadValues(__spreadValues({}, this.edgeScroll.config), config);
207
+ if (this.edgeScroll.config.enabled && this.edgeScrollAnimationFrame !== null) {
208
+ cancelAnimationFrame(this.edgeScrollAnimationFrame);
209
+ this.edgeScrollAnimationFrame = null;
210
+ }
211
+ }
212
+ })), __runInitializers(_init, 19, this);
213
+ __publicField(this, "getNode", __runInitializers(_init, 20, this)), __runInitializers(_init, 23, this);
214
+ __publicField(this, "container", __runInitializers(_init, 24, this)), __runInitializers(_init, 27, this);
215
+ __publicField(this, "interaction", __runInitializers(_init, 28, this)), __runInitializers(_init, 31, this);
128
216
  __publicField(this, "listeners", {
129
217
  click: /* @__PURE__ */ new Set(),
130
218
  contextmenu: /* @__PURE__ */ new Set()
131
219
  });
132
- __publicField(this, "addListener", __runInitializers(_init, 28, this, (type, listener) => {
220
+ __publicField(this, "edgeScrollAnimationFrame", null);
221
+ __publicField(this, "mousePosition", null);
222
+ __publicField(this, "addListener", __runInitializers(_init, 32, this, (type, listener) => {
133
223
  this.listeners[type].add(listener);
134
- })), __runInitializers(_init, 31, this);
135
- __publicField(this, "removeListener", __runInitializers(_init, 32, this, (type, listener) => {
136
- this.listeners[type].delete(listener);
137
224
  })), __runInitializers(_init, 35, this);
225
+ __publicField(this, "removeListener", __runInitializers(_init, 36, this, (type, listener) => {
226
+ this.listeners[type].delete(listener);
227
+ })), __runInitializers(_init, 39, this);
228
+ __publicField(this, "handleEdgeScroll", () => {
229
+ if (!this.edgeScroll.config.enabled || !this.mousePosition || !this.ref) {
230
+ return;
231
+ }
232
+ const { x, y } = this.mousePosition;
233
+ const { edgeSize, maxSpeed, cornerSize = edgeSize / 2 } = this.edgeScroll.config;
234
+ const viewportWidth = this.container.width;
235
+ const viewportHeight = this.container.height;
236
+ let deltaX = 0;
237
+ let deltaY = 0;
238
+ let isCorner = false;
239
+ if (x <= cornerSize && y <= cornerSize || x <= cornerSize && y >= viewportHeight - cornerSize || x >= viewportWidth - cornerSize && y <= cornerSize || x >= viewportWidth - cornerSize && y >= viewportHeight - cornerSize) {
240
+ isCorner = true;
241
+ }
242
+ if (x <= edgeSize) {
243
+ const distance = isCorner ? cornerSize - x : edgeSize - x;
244
+ const factor = Math.min(distance / (isCorner ? cornerSize : edgeSize), 1);
245
+ deltaX = maxSpeed * factor;
246
+ } else if (x >= viewportWidth - edgeSize) {
247
+ const distance = isCorner ? x - (viewportWidth - cornerSize) : x - (viewportWidth - edgeSize);
248
+ const factor = Math.min(distance / (isCorner ? cornerSize : edgeSize), 1);
249
+ deltaX = -maxSpeed * factor;
250
+ }
251
+ if (y <= edgeSize) {
252
+ const distance = isCorner ? cornerSize - y : edgeSize - y;
253
+ const factor = Math.min(distance / (isCorner ? cornerSize : edgeSize), 1);
254
+ deltaY = maxSpeed * factor;
255
+ } else if (y >= viewportHeight - edgeSize) {
256
+ const distance = isCorner ? y - (viewportHeight - cornerSize) : y - (viewportHeight - edgeSize);
257
+ const factor = Math.min(distance / (isCorner ? cornerSize : edgeSize), 1);
258
+ deltaY = -maxSpeed * factor;
259
+ }
260
+ if (deltaX !== 0 || deltaY !== 0) {
261
+ const currentState = this.ref.instance.transformState;
262
+ const newPositionX = currentState.positionX + deltaX;
263
+ const newPositionY = currentState.positionY + deltaY;
264
+ this.ref.setTransform(newPositionX, newPositionY, currentState.scale, 0);
265
+ }
266
+ this.edgeScrollAnimationFrame = requestAnimationFrame(this.handleEdgeScroll);
267
+ });
138
268
  }
139
- scrollNodeIntoView({ nodeId, scale = this.transform.scale, block = "nearest", inline = "nearest", offset = 0, animationTime }) {
140
- var _a2, _b, _c, _d, _e, _f, _g;
269
+ scrollNodeIntoView({ nodeId, scale = this.transform.scale, block, inline, offset, animationTime }) {
270
+ var _a2;
141
271
  const node = nodeId && this.getNode({ id: nodeId });
142
272
  if (!node) {
143
273
  return;
144
274
  }
145
- const nodeX = node.position.x;
146
- const nodeY = node.position.y;
147
- const nodeWidth = (_b = (_a2 = node.measured) == null ? void 0 : _a2.width) != null ? _b : 0;
148
- const nodeHeight = (_d = (_c = node.measured) == null ? void 0 : _c.height) != null ? _d : 0;
149
- const viewportWidth = this.container.width;
150
- const viewportHeight = this.container.height;
151
- let positionX = -this.transform.positionX;
152
- let positionY = -this.transform.positionY;
153
- const offsetX = typeof offset === "number" ? offset : (_e = offset.x) != null ? _e : 0;
154
- const offsetY = typeof offset === "number" ? offset : (_f = offset.y) != null ? _f : 0;
155
- switch (inline) {
156
- case "center":
157
- positionX = (nodeX + nodeWidth / 2) * scale - viewportWidth / 2;
158
- break;
159
- case "start":
160
- positionX = nodeX * scale - offsetX;
161
- break;
162
- case "end":
163
- positionX = (nodeX + nodeWidth) * scale - viewportWidth + offsetX;
164
- break;
165
- case "nearest": {
166
- const nodeRight = (nodeX + nodeWidth) * scale;
167
- const nodeLeft = nodeX * scale;
168
- const currentLeft = -this.transform.positionX;
169
- const currentRight = currentLeft + viewportWidth;
170
- if (nodeRight > currentRight) {
171
- positionX = nodeRight - viewportWidth + offsetX;
172
- } else if (nodeLeft < currentLeft) {
173
- positionX = nodeLeft - offsetX;
174
- }
175
- break;
176
- }
177
- }
178
- switch (block) {
179
- case "center":
180
- positionY = (nodeY + nodeHeight / 2) * scale - viewportHeight / 2;
181
- break;
182
- case "start":
183
- positionY = nodeY * scale - offsetY;
184
- break;
185
- case "end":
186
- positionY = (nodeY + nodeHeight) * scale - viewportHeight + offsetY;
187
- break;
188
- case "nearest": {
189
- const nodeBottom = (nodeY + nodeHeight) * scale;
190
- const nodeTop = nodeY * scale;
191
- const currentTop = -this.transform.positionY;
192
- const currentBottom = currentTop + viewportHeight;
193
- if (nodeBottom > currentBottom) {
194
- positionY = nodeBottom - viewportHeight + offsetY;
195
- } else if (nodeTop < currentTop) {
196
- positionY = nodeTop - offsetY;
197
- }
198
- break;
199
- }
200
- }
201
- (_g = this.ref) == null ? void 0 : _g.setTransform(-positionX, -positionY, scale, animationTime);
275
+ const { positionX, positionY, scale: newScale } = calculateTransform({
276
+ node,
277
+ container: this.container,
278
+ transform: this.transform,
279
+ scale,
280
+ block,
281
+ inline,
282
+ offset
283
+ });
284
+ (_a2 = this.ref) == null ? void 0 : _a2.setTransform(-positionX, -positionY, newScale, animationTime);
202
285
  }
203
286
  zoomIn({ step = 0.1, animationTime }) {
204
287
  var _a2;
@@ -213,7 +296,38 @@ class Canvas extends (_a = core.BasePlugin, _ref_dec = [decorators.register("ref
213
296
  (_a2 = this.ref) == null ? void 0 : _a2.setTransform(positionX != null ? positionX : this.transform.positionX, positionY != null ? positionY : this.transform.positionY, scale != null ? scale : this.transform.scale, animationTime);
214
297
  }
215
298
  render({ children } = { children: null }) {
216
- const [transform, setTransform] = react.useState(null);
299
+ const defaultTransform = react.useMemo(() => {
300
+ const defaultLocated = this.config.defaultLocated;
301
+ if (!(defaultLocated == null ? void 0 : defaultLocated.nodeId)) {
302
+ return this.transform;
303
+ }
304
+ const node = this.getNode({ id: defaultLocated.nodeId });
305
+ if (!node) {
306
+ return this.transform;
307
+ }
308
+ defaultLocated.inline || (defaultLocated.inline = "center");
309
+ defaultLocated.block || (defaultLocated.block = "center");
310
+ defaultLocated.scale || (defaultLocated.scale = this.transform.scale);
311
+ const { positionX, positionY, scale: newScale } = calculateTransform(__spreadValues({
312
+ node,
313
+ container: this.container,
314
+ transform: this.transform
315
+ }, defaultLocated));
316
+ const newTransform = {
317
+ previousScale: this.transform.scale,
318
+ scale: newScale,
319
+ positionX: -positionX,
320
+ positionY: -positionY
321
+ };
322
+ this.transform = newTransform;
323
+ return newTransform;
324
+ }, []);
325
+ const [transformRef, setTransformRef] = react.useReducer((_, ref) => {
326
+ if (this.ref !== ref) {
327
+ this.ref = ref;
328
+ }
329
+ return ref;
330
+ }, null);
217
331
  const [fixedLayers, childrenLayers] = react.useMemo(() => {
218
332
  const fixedLayers2 = [];
219
333
  const childrenLayers2 = [];
@@ -229,36 +343,37 @@ class Canvas extends (_a = core.BasePlugin, _ref_dec = [decorators.register("ref
229
343
  }
230
344
  return [fixedLayers2, childrenLayers2];
231
345
  }, [children]);
232
- react.useEffect(() => {
233
- this.ref = transform;
234
- const wrapperElement = transform == null ? void 0 : transform.instance.wrapperComponent;
235
- if (!wrapperElement) {
236
- return;
346
+ react.useLayoutEffect(() => {
347
+ const wrapperElement = transformRef == null ? void 0 : transformRef.instance.wrapperComponent;
348
+ if (wrapperElement) {
349
+ Object.defineProperty(wrapperElement, "scrollTop", {
350
+ get() {
351
+ return -transformRef.instance.transformState.positionY;
352
+ },
353
+ set(value) {
354
+ transformRef.instance.setTransformState(transformRef.instance.transformState.scale, transformRef.instance.transformState.positionX, -value);
355
+ }
356
+ });
357
+ Object.defineProperty(wrapperElement, "scrollLeft", {
358
+ get() {
359
+ return -transformRef.instance.transformState.positionX;
360
+ },
361
+ set(value) {
362
+ transformRef.instance.setTransformState(transformRef.instance.transformState.scale, -value, transformRef.instance.transformState.positionY);
363
+ }
364
+ });
237
365
  }
238
- Object.defineProperty(wrapperElement, "scrollTop", {
239
- get() {
240
- return -transform.instance.transformState.positionY;
241
- },
242
- set(value) {
243
- transform.instance.setTransformState(transform.instance.transformState.scale, transform.instance.transformState.positionX, -value);
244
- }
245
- });
246
- Object.defineProperty(wrapperElement, "scrollLeft", {
247
- get() {
248
- return -transform.instance.transformState.positionX;
249
- },
250
- set(value) {
251
- transform.instance.setTransformState(transform.instance.transformState.scale, -value, transform.instance.transformState.positionY);
252
- }
253
- });
254
366
  let frame;
255
- return transform == null ? void 0 : transform.instance.onChange((ref) => {
367
+ return transformRef == null ? void 0 : transformRef.instance.onChange((ref) => {
256
368
  cancelAnimationFrame(frame);
257
369
  frame = requestAnimationFrame(() => {
370
+ if (lodashEs.isEqual(this.transform, ref.state)) {
371
+ return;
372
+ }
258
373
  this.transform = __spreadValues({}, ref.state);
259
374
  });
260
375
  });
261
- }, [transform]);
376
+ }, [transformRef]);
262
377
  const onClick = react.useCallback((e) => {
263
378
  if (!this.isCanvasEvent(e)) {
264
379
  return;
@@ -281,10 +396,32 @@ class Canvas extends (_a = core.BasePlugin, _ref_dec = [decorators.register("ref
281
396
  listener(e);
282
397
  });
283
398
  }, []);
399
+ const onMouseMove = react.useCallback((e) => {
400
+ if (this.edgeScroll.config.enabled) {
401
+ const rect = e.currentTarget.getBoundingClientRect();
402
+ this.mousePosition = {
403
+ x: e.clientX - rect.left,
404
+ y: e.clientY - rect.top
405
+ };
406
+ if (this.edgeScrollAnimationFrame === null) {
407
+ this.edgeScrollAnimationFrame = requestAnimationFrame(this.handleEdgeScroll);
408
+ }
409
+ }
410
+ }, []);
411
+ const onMouseLeave = react.useCallback(() => {
412
+ this.mousePosition = null;
413
+ if (this.edgeScrollAnimationFrame !== null) {
414
+ cancelAnimationFrame(this.edgeScrollAnimationFrame);
415
+ this.edgeScrollAnimationFrame = null;
416
+ }
417
+ }, []);
284
418
  return /* @__PURE__ */ jsxRuntime.jsxs(
285
419
  reactZoomPanPinch.TransformWrapper,
286
420
  __spreadProps(__spreadValues({}, this.config), {
287
- ref: setTransform,
421
+ ref: setTransformRef,
422
+ initialPositionX: defaultTransform.positionX,
423
+ initialPositionY: defaultTransform.positionY,
424
+ initialScale: defaultTransform.scale,
288
425
  children: [
289
426
  /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: fixedLayers }),
290
427
  /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -299,7 +436,9 @@ class Canvas extends (_a = core.BasePlugin, _ref_dec = [decorators.register("ref
299
436
  },
300
437
  wrapperProps: {
301
438
  onClick,
302
- onContextMenu
439
+ onContextMenu,
440
+ onMouseMove,
441
+ onMouseLeave
303
442
  },
304
443
  contentClass: core.bem(this.name, "content"),
305
444
  contentStyle: {
@@ -329,6 +468,10 @@ class Canvas extends (_a = core.BasePlugin, _ref_dec = [decorators.register("ref
329
468
  };
330
469
  this.subscriptions.push(() => {
331
470
  Object.values(this.listeners).forEach((set) => set.clear());
471
+ if (this.edgeScrollAnimationFrame !== null) {
472
+ cancelAnimationFrame(this.edgeScrollAnimationFrame);
473
+ this.edgeScrollAnimationFrame = null;
474
+ }
332
475
  });
333
476
  }
334
477
  isCanvasEvent(e) {
@@ -344,6 +487,7 @@ __decorateElement(_init, 1, "render", _render_dec, Canvas);
344
487
  __decorateElement(_init, 1, "init", _init_dec, Canvas);
345
488
  __decorateElement(_init, 5, "ref", _ref_dec, Canvas);
346
489
  __decorateElement(_init, 5, "transform", _transform_dec, Canvas);
490
+ __decorateElement(_init, 5, "edgeScroll", _edgeScroll_dec, Canvas);
347
491
  __decorateElement(_init, 5, "getNode", _getNode_dec, Canvas);
348
492
  __decorateElement(_init, 5, "container", _container_dec, Canvas);
349
493
  __decorateElement(_init, 5, "interaction", _interaction_dec, Canvas);
package/dist/index.d.cts CHANGED
@@ -3,9 +3,27 @@ import { InferParamsFromSchema } from '@knotx/decorators';
3
3
  import { MouseEvent, ReactNode } from 'react';
4
4
  import { ReactZoomPanPinchContentRef, ReactZoomPanPinchState, ReactZoomPanPinchProps } from 'react-zoom-pan-pinch';
5
5
 
6
- type CanvasConfig = ReactZoomPanPinchProps;
6
+ type CanvasConfig = ReactZoomPanPinchProps & {
7
+ defaultLocated?: {
8
+ nodeId: string;
9
+ scale?: number;
10
+ block?: 'center' | 'end' | 'nearest' | 'start';
11
+ inline?: 'center' | 'end' | 'nearest' | 'start';
12
+ offset?: number | {
13
+ x: number;
14
+ y: number;
15
+ };
16
+ animationTime?: number;
17
+ };
18
+ };
7
19
  type CanvasTransformState = ReactZoomPanPinchState;
8
20
  type CanvasRef = ReactZoomPanPinchContentRef;
21
+ interface EdgeScrollConfig {
22
+ enabled: boolean;
23
+ edgeSize: number;
24
+ maxSpeed: number;
25
+ cornerSize?: number;
26
+ }
9
27
  declare module '@knotx/core' {
10
28
  interface PluginData {
11
29
  canvas: {
@@ -13,6 +31,10 @@ declare module '@knotx/core' {
13
31
  transform: CanvasTransformState;
14
32
  addListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
15
33
  removeListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
34
+ edgeScroll: {
35
+ config: EdgeScrollConfig;
36
+ setConfig: (config: Partial<EdgeScrollConfig>) => void;
37
+ };
16
38
  };
17
39
  }
18
40
  interface PluginTools {
@@ -52,10 +74,16 @@ declare class Canvas extends BasePlugin<'canvas', CanvasConfig> {
52
74
  name: "canvas";
53
75
  ref: CanvasRef | null;
54
76
  transform: CanvasTransformState;
77
+ edgeScroll: {
78
+ config: EdgeScrollConfig;
79
+ setConfig: (config: Partial<EdgeScrollConfig>) => void;
80
+ };
55
81
  private getNode;
56
82
  private container;
57
83
  interaction: Engine['interaction'];
58
84
  private listeners;
85
+ private edgeScrollAnimationFrame;
86
+ private mousePosition;
59
87
  addListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
60
88
  removeListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
61
89
  scrollNodeIntoView({ nodeId, scale, block, inline, offset, animationTime }: InferParamsFromSchema<{
@@ -131,6 +159,7 @@ declare class Canvas extends BasePlugin<'canvas', CanvasConfig> {
131
159
  };
132
160
  };
133
161
  }>): void;
162
+ private handleEdgeScroll;
134
163
  render({ children }?: {
135
164
  children: ReactNode;
136
165
  }): JSX.Element;
@@ -138,4 +167,4 @@ declare class Canvas extends BasePlugin<'canvas', CanvasConfig> {
138
167
  private isCanvasEvent;
139
168
  }
140
169
 
141
- export { Canvas, type CanvasConfig, type CanvasEventListener, type CanvasEventType, type CanvasRef, type CanvasTransformState };
170
+ export { Canvas, type CanvasConfig, type CanvasEventListener, type CanvasEventType, type CanvasRef, type CanvasTransformState, type EdgeScrollConfig };
package/dist/index.d.mts CHANGED
@@ -3,9 +3,27 @@ import { InferParamsFromSchema } from '@knotx/decorators';
3
3
  import { MouseEvent, ReactNode } from 'react';
4
4
  import { ReactZoomPanPinchContentRef, ReactZoomPanPinchState, ReactZoomPanPinchProps } from 'react-zoom-pan-pinch';
5
5
 
6
- type CanvasConfig = ReactZoomPanPinchProps;
6
+ type CanvasConfig = ReactZoomPanPinchProps & {
7
+ defaultLocated?: {
8
+ nodeId: string;
9
+ scale?: number;
10
+ block?: 'center' | 'end' | 'nearest' | 'start';
11
+ inline?: 'center' | 'end' | 'nearest' | 'start';
12
+ offset?: number | {
13
+ x: number;
14
+ y: number;
15
+ };
16
+ animationTime?: number;
17
+ };
18
+ };
7
19
  type CanvasTransformState = ReactZoomPanPinchState;
8
20
  type CanvasRef = ReactZoomPanPinchContentRef;
21
+ interface EdgeScrollConfig {
22
+ enabled: boolean;
23
+ edgeSize: number;
24
+ maxSpeed: number;
25
+ cornerSize?: number;
26
+ }
9
27
  declare module '@knotx/core' {
10
28
  interface PluginData {
11
29
  canvas: {
@@ -13,6 +31,10 @@ declare module '@knotx/core' {
13
31
  transform: CanvasTransformState;
14
32
  addListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
15
33
  removeListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
34
+ edgeScroll: {
35
+ config: EdgeScrollConfig;
36
+ setConfig: (config: Partial<EdgeScrollConfig>) => void;
37
+ };
16
38
  };
17
39
  }
18
40
  interface PluginTools {
@@ -52,10 +74,16 @@ declare class Canvas extends BasePlugin<'canvas', CanvasConfig> {
52
74
  name: "canvas";
53
75
  ref: CanvasRef | null;
54
76
  transform: CanvasTransformState;
77
+ edgeScroll: {
78
+ config: EdgeScrollConfig;
79
+ setConfig: (config: Partial<EdgeScrollConfig>) => void;
80
+ };
55
81
  private getNode;
56
82
  private container;
57
83
  interaction: Engine['interaction'];
58
84
  private listeners;
85
+ private edgeScrollAnimationFrame;
86
+ private mousePosition;
59
87
  addListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
60
88
  removeListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
61
89
  scrollNodeIntoView({ nodeId, scale, block, inline, offset, animationTime }: InferParamsFromSchema<{
@@ -131,6 +159,7 @@ declare class Canvas extends BasePlugin<'canvas', CanvasConfig> {
131
159
  };
132
160
  };
133
161
  }>): void;
162
+ private handleEdgeScroll;
134
163
  render({ children }?: {
135
164
  children: ReactNode;
136
165
  }): JSX.Element;
@@ -138,4 +167,4 @@ declare class Canvas extends BasePlugin<'canvas', CanvasConfig> {
138
167
  private isCanvasEvent;
139
168
  }
140
169
 
141
- export { Canvas, type CanvasConfig, type CanvasEventListener, type CanvasEventType, type CanvasRef, type CanvasTransformState };
170
+ export { Canvas, type CanvasConfig, type CanvasEventListener, type CanvasEventType, type CanvasRef, type CanvasTransformState, type EdgeScrollConfig };
package/dist/index.d.ts CHANGED
@@ -3,9 +3,27 @@ import { InferParamsFromSchema } from '@knotx/decorators';
3
3
  import { MouseEvent, ReactNode } from 'react';
4
4
  import { ReactZoomPanPinchContentRef, ReactZoomPanPinchState, ReactZoomPanPinchProps } from 'react-zoom-pan-pinch';
5
5
 
6
- type CanvasConfig = ReactZoomPanPinchProps;
6
+ type CanvasConfig = ReactZoomPanPinchProps & {
7
+ defaultLocated?: {
8
+ nodeId: string;
9
+ scale?: number;
10
+ block?: 'center' | 'end' | 'nearest' | 'start';
11
+ inline?: 'center' | 'end' | 'nearest' | 'start';
12
+ offset?: number | {
13
+ x: number;
14
+ y: number;
15
+ };
16
+ animationTime?: number;
17
+ };
18
+ };
7
19
  type CanvasTransformState = ReactZoomPanPinchState;
8
20
  type CanvasRef = ReactZoomPanPinchContentRef;
21
+ interface EdgeScrollConfig {
22
+ enabled: boolean;
23
+ edgeSize: number;
24
+ maxSpeed: number;
25
+ cornerSize?: number;
26
+ }
9
27
  declare module '@knotx/core' {
10
28
  interface PluginData {
11
29
  canvas: {
@@ -13,6 +31,10 @@ declare module '@knotx/core' {
13
31
  transform: CanvasTransformState;
14
32
  addListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
15
33
  removeListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
34
+ edgeScroll: {
35
+ config: EdgeScrollConfig;
36
+ setConfig: (config: Partial<EdgeScrollConfig>) => void;
37
+ };
16
38
  };
17
39
  }
18
40
  interface PluginTools {
@@ -52,10 +74,16 @@ declare class Canvas extends BasePlugin<'canvas', CanvasConfig> {
52
74
  name: "canvas";
53
75
  ref: CanvasRef | null;
54
76
  transform: CanvasTransformState;
77
+ edgeScroll: {
78
+ config: EdgeScrollConfig;
79
+ setConfig: (config: Partial<EdgeScrollConfig>) => void;
80
+ };
55
81
  private getNode;
56
82
  private container;
57
83
  interaction: Engine['interaction'];
58
84
  private listeners;
85
+ private edgeScrollAnimationFrame;
86
+ private mousePosition;
59
87
  addListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
60
88
  removeListener: (event: CanvasEventType, listener: CanvasEventListener) => void;
61
89
  scrollNodeIntoView({ nodeId, scale, block, inline, offset, animationTime }: InferParamsFromSchema<{
@@ -131,6 +159,7 @@ declare class Canvas extends BasePlugin<'canvas', CanvasConfig> {
131
159
  };
132
160
  };
133
161
  }>): void;
162
+ private handleEdgeScroll;
134
163
  render({ children }?: {
135
164
  children: ReactNode;
136
165
  }): JSX.Element;
@@ -138,4 +167,4 @@ declare class Canvas extends BasePlugin<'canvas', CanvasConfig> {
138
167
  private isCanvasEvent;
139
168
  }
140
169
 
141
- export { Canvas, type CanvasConfig, type CanvasEventListener, type CanvasEventType, type CanvasRef, type CanvasTransformState };
170
+ export { Canvas, type CanvasConfig, type CanvasEventListener, type CanvasEventType, type CanvasRef, type CanvasTransformState, type EdgeScrollConfig };
package/dist/index.js CHANGED
@@ -1,10 +1,83 @@
1
1
  import { jsxs, jsx } from '@knotx/jsx/jsx-runtime';
2
2
  import { Layer, InteractionPriority, bem, BasePlugin } from '@knotx/core';
3
3
  import { register, inject, tool, layer, OnInit } from '@knotx/decorators';
4
- import { merge } from 'lodash-es';
5
- import { useState, useMemo, useEffect, useCallback, Fragment } from 'react';
4
+ import { isEqual, merge } from 'lodash-es';
5
+ import { useMemo, useReducer, useLayoutEffect, useCallback, Fragment } from 'react';
6
6
  import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch';
7
7
 
8
+ function calculateTransform({
9
+ node,
10
+ container,
11
+ transform,
12
+ scale,
13
+ block = "nearest",
14
+ inline = "nearest",
15
+ offset = 0
16
+ }) {
17
+ var _a, _b, _c, _d, _e, _f;
18
+ const nodeX = node.position.x;
19
+ const nodeY = node.position.y;
20
+ const nodeWidth = (_b = (_a = node.measured) == null ? void 0 : _a.width) != null ? _b : 0;
21
+ const nodeHeight = (_d = (_c = node.measured) == null ? void 0 : _c.height) != null ? _d : 0;
22
+ const viewportWidth = container.width;
23
+ const viewportHeight = container.height;
24
+ let positionX = -transform.positionX;
25
+ let positionY = -transform.positionY;
26
+ const offsetX = typeof offset === "number" ? offset : (_e = offset.x) != null ? _e : 0;
27
+ const offsetY = typeof offset === "number" ? offset : (_f = offset.y) != null ? _f : 0;
28
+ switch (inline) {
29
+ case "center":
30
+ positionX = (nodeX + nodeWidth / 2) * scale - viewportWidth / 2;
31
+ break;
32
+ case "start":
33
+ positionX = nodeX * scale - offsetX;
34
+ break;
35
+ case "end":
36
+ positionX = (nodeX + nodeWidth) * scale - viewportWidth + offsetX;
37
+ break;
38
+ case "nearest": {
39
+ const nodeRight = (nodeX + nodeWidth) * scale;
40
+ const nodeLeft = nodeX * scale;
41
+ const currentLeft = -transform.positionX;
42
+ const currentRight = currentLeft + viewportWidth;
43
+ if (nodeRight > currentRight) {
44
+ positionX = nodeRight - viewportWidth + offsetX;
45
+ } else if (nodeLeft < currentLeft) {
46
+ positionX = nodeLeft - offsetX;
47
+ }
48
+ break;
49
+ }
50
+ }
51
+ switch (block) {
52
+ case "center":
53
+ positionY = (nodeY + nodeHeight / 2) * scale - viewportHeight / 2;
54
+ break;
55
+ case "start":
56
+ positionY = nodeY * scale - offsetY;
57
+ break;
58
+ case "end":
59
+ positionY = (nodeY + nodeHeight) * scale - viewportHeight + offsetY;
60
+ break;
61
+ case "nearest": {
62
+ const nodeBottom = (nodeY + nodeHeight) * scale;
63
+ const nodeTop = nodeY * scale;
64
+ const currentTop = -transform.positionY;
65
+ const currentBottom = currentTop + viewportHeight;
66
+ if (nodeBottom > currentBottom) {
67
+ positionY = nodeBottom - viewportHeight + offsetY;
68
+ } else if (nodeTop < currentTop) {
69
+ positionY = nodeTop - offsetY;
70
+ }
71
+ break;
72
+ }
73
+ }
74
+ return {
75
+ scale,
76
+ positionX,
77
+ positionY
78
+ };
79
+ }
80
+
8
81
  var __create = Object.create;
9
82
  var __defProp = Object.defineProperty;
10
83
  var __defProps = Object.defineProperties;
@@ -73,8 +146,8 @@ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use
73
146
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
74
147
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
75
148
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
76
- var _init_dec, _render_dec, _setTransform_dec, _zoomOut_dec, _zoomIn_dec, _scrollNodeIntoView_dec, _removeListener_dec, _addListener_dec, _interaction_dec, _container_dec, _getNode_dec, _transform_dec, _ref_dec, _a, _init;
77
- class Canvas extends (_a = BasePlugin, _ref_dec = [register("ref")], _transform_dec = [register("transform")], _getNode_dec = [inject.getNode()], _container_dec = [inject.container()], _interaction_dec = [inject.interaction()], _addListener_dec = [register("addListener")], _removeListener_dec = [register("removeListener")], _scrollNodeIntoView_dec = [tool("Scroll node into viewport center", {
149
+ var _init_dec, _render_dec, _setTransform_dec, _zoomOut_dec, _zoomIn_dec, _scrollNodeIntoView_dec, _removeListener_dec, _addListener_dec, _interaction_dec, _container_dec, _getNode_dec, _edgeScroll_dec, _transform_dec, _ref_dec, _a, _init;
150
+ class Canvas extends (_a = BasePlugin, _ref_dec = [register("ref")], _transform_dec = [register("transform")], _edgeScroll_dec = [register("edgeScroll")], _getNode_dec = [inject.getNode()], _container_dec = [inject.container()], _interaction_dec = [inject.interaction()], _addListener_dec = [register("addListener")], _removeListener_dec = [register("removeListener")], _scrollNodeIntoView_dec = [tool("Scroll node into viewport center", {
78
151
  type: "object",
79
152
  properties: {
80
153
  nodeId: { type: "string" },
@@ -120,83 +193,93 @@ class Canvas extends (_a = BasePlugin, _ref_dec = [register("ref")], _transform_
120
193
  __publicField(this, "name", "canvas");
121
194
  __publicField(this, "ref", __runInitializers(_init, 8, this, null)), __runInitializers(_init, 11, this);
122
195
  __publicField(this, "transform", __runInitializers(_init, 12, this)), __runInitializers(_init, 15, this);
123
- __publicField(this, "getNode", __runInitializers(_init, 16, this)), __runInitializers(_init, 19, this);
124
- __publicField(this, "container", __runInitializers(_init, 20, this)), __runInitializers(_init, 23, this);
125
- __publicField(this, "interaction", __runInitializers(_init, 24, this)), __runInitializers(_init, 27, this);
196
+ __publicField(this, "edgeScroll", __runInitializers(_init, 16, this, {
197
+ config: {
198
+ enabled: false,
199
+ edgeSize: 50,
200
+ maxSpeed: 10,
201
+ cornerSize: 25
202
+ },
203
+ setConfig: (config) => {
204
+ this.edgeScroll.config = __spreadValues(__spreadValues({}, this.edgeScroll.config), config);
205
+ if (this.edgeScroll.config.enabled && this.edgeScrollAnimationFrame !== null) {
206
+ cancelAnimationFrame(this.edgeScrollAnimationFrame);
207
+ this.edgeScrollAnimationFrame = null;
208
+ }
209
+ }
210
+ })), __runInitializers(_init, 19, this);
211
+ __publicField(this, "getNode", __runInitializers(_init, 20, this)), __runInitializers(_init, 23, this);
212
+ __publicField(this, "container", __runInitializers(_init, 24, this)), __runInitializers(_init, 27, this);
213
+ __publicField(this, "interaction", __runInitializers(_init, 28, this)), __runInitializers(_init, 31, this);
126
214
  __publicField(this, "listeners", {
127
215
  click: /* @__PURE__ */ new Set(),
128
216
  contextmenu: /* @__PURE__ */ new Set()
129
217
  });
130
- __publicField(this, "addListener", __runInitializers(_init, 28, this, (type, listener) => {
218
+ __publicField(this, "edgeScrollAnimationFrame", null);
219
+ __publicField(this, "mousePosition", null);
220
+ __publicField(this, "addListener", __runInitializers(_init, 32, this, (type, listener) => {
131
221
  this.listeners[type].add(listener);
132
- })), __runInitializers(_init, 31, this);
133
- __publicField(this, "removeListener", __runInitializers(_init, 32, this, (type, listener) => {
134
- this.listeners[type].delete(listener);
135
222
  })), __runInitializers(_init, 35, this);
223
+ __publicField(this, "removeListener", __runInitializers(_init, 36, this, (type, listener) => {
224
+ this.listeners[type].delete(listener);
225
+ })), __runInitializers(_init, 39, this);
226
+ __publicField(this, "handleEdgeScroll", () => {
227
+ if (!this.edgeScroll.config.enabled || !this.mousePosition || !this.ref) {
228
+ return;
229
+ }
230
+ const { x, y } = this.mousePosition;
231
+ const { edgeSize, maxSpeed, cornerSize = edgeSize / 2 } = this.edgeScroll.config;
232
+ const viewportWidth = this.container.width;
233
+ const viewportHeight = this.container.height;
234
+ let deltaX = 0;
235
+ let deltaY = 0;
236
+ let isCorner = false;
237
+ if (x <= cornerSize && y <= cornerSize || x <= cornerSize && y >= viewportHeight - cornerSize || x >= viewportWidth - cornerSize && y <= cornerSize || x >= viewportWidth - cornerSize && y >= viewportHeight - cornerSize) {
238
+ isCorner = true;
239
+ }
240
+ if (x <= edgeSize) {
241
+ const distance = isCorner ? cornerSize - x : edgeSize - x;
242
+ const factor = Math.min(distance / (isCorner ? cornerSize : edgeSize), 1);
243
+ deltaX = maxSpeed * factor;
244
+ } else if (x >= viewportWidth - edgeSize) {
245
+ const distance = isCorner ? x - (viewportWidth - cornerSize) : x - (viewportWidth - edgeSize);
246
+ const factor = Math.min(distance / (isCorner ? cornerSize : edgeSize), 1);
247
+ deltaX = -maxSpeed * factor;
248
+ }
249
+ if (y <= edgeSize) {
250
+ const distance = isCorner ? cornerSize - y : edgeSize - y;
251
+ const factor = Math.min(distance / (isCorner ? cornerSize : edgeSize), 1);
252
+ deltaY = maxSpeed * factor;
253
+ } else if (y >= viewportHeight - edgeSize) {
254
+ const distance = isCorner ? y - (viewportHeight - cornerSize) : y - (viewportHeight - edgeSize);
255
+ const factor = Math.min(distance / (isCorner ? cornerSize : edgeSize), 1);
256
+ deltaY = -maxSpeed * factor;
257
+ }
258
+ if (deltaX !== 0 || deltaY !== 0) {
259
+ const currentState = this.ref.instance.transformState;
260
+ const newPositionX = currentState.positionX + deltaX;
261
+ const newPositionY = currentState.positionY + deltaY;
262
+ this.ref.setTransform(newPositionX, newPositionY, currentState.scale, 0);
263
+ }
264
+ this.edgeScrollAnimationFrame = requestAnimationFrame(this.handleEdgeScroll);
265
+ });
136
266
  }
137
- scrollNodeIntoView({ nodeId, scale = this.transform.scale, block = "nearest", inline = "nearest", offset = 0, animationTime }) {
138
- var _a2, _b, _c, _d, _e, _f, _g;
267
+ scrollNodeIntoView({ nodeId, scale = this.transform.scale, block, inline, offset, animationTime }) {
268
+ var _a2;
139
269
  const node = nodeId && this.getNode({ id: nodeId });
140
270
  if (!node) {
141
271
  return;
142
272
  }
143
- const nodeX = node.position.x;
144
- const nodeY = node.position.y;
145
- const nodeWidth = (_b = (_a2 = node.measured) == null ? void 0 : _a2.width) != null ? _b : 0;
146
- const nodeHeight = (_d = (_c = node.measured) == null ? void 0 : _c.height) != null ? _d : 0;
147
- const viewportWidth = this.container.width;
148
- const viewportHeight = this.container.height;
149
- let positionX = -this.transform.positionX;
150
- let positionY = -this.transform.positionY;
151
- const offsetX = typeof offset === "number" ? offset : (_e = offset.x) != null ? _e : 0;
152
- const offsetY = typeof offset === "number" ? offset : (_f = offset.y) != null ? _f : 0;
153
- switch (inline) {
154
- case "center":
155
- positionX = (nodeX + nodeWidth / 2) * scale - viewportWidth / 2;
156
- break;
157
- case "start":
158
- positionX = nodeX * scale - offsetX;
159
- break;
160
- case "end":
161
- positionX = (nodeX + nodeWidth) * scale - viewportWidth + offsetX;
162
- break;
163
- case "nearest": {
164
- const nodeRight = (nodeX + nodeWidth) * scale;
165
- const nodeLeft = nodeX * scale;
166
- const currentLeft = -this.transform.positionX;
167
- const currentRight = currentLeft + viewportWidth;
168
- if (nodeRight > currentRight) {
169
- positionX = nodeRight - viewportWidth + offsetX;
170
- } else if (nodeLeft < currentLeft) {
171
- positionX = nodeLeft - offsetX;
172
- }
173
- break;
174
- }
175
- }
176
- switch (block) {
177
- case "center":
178
- positionY = (nodeY + nodeHeight / 2) * scale - viewportHeight / 2;
179
- break;
180
- case "start":
181
- positionY = nodeY * scale - offsetY;
182
- break;
183
- case "end":
184
- positionY = (nodeY + nodeHeight) * scale - viewportHeight + offsetY;
185
- break;
186
- case "nearest": {
187
- const nodeBottom = (nodeY + nodeHeight) * scale;
188
- const nodeTop = nodeY * scale;
189
- const currentTop = -this.transform.positionY;
190
- const currentBottom = currentTop + viewportHeight;
191
- if (nodeBottom > currentBottom) {
192
- positionY = nodeBottom - viewportHeight + offsetY;
193
- } else if (nodeTop < currentTop) {
194
- positionY = nodeTop - offsetY;
195
- }
196
- break;
197
- }
198
- }
199
- (_g = this.ref) == null ? void 0 : _g.setTransform(-positionX, -positionY, scale, animationTime);
273
+ const { positionX, positionY, scale: newScale } = calculateTransform({
274
+ node,
275
+ container: this.container,
276
+ transform: this.transform,
277
+ scale,
278
+ block,
279
+ inline,
280
+ offset
281
+ });
282
+ (_a2 = this.ref) == null ? void 0 : _a2.setTransform(-positionX, -positionY, newScale, animationTime);
200
283
  }
201
284
  zoomIn({ step = 0.1, animationTime }) {
202
285
  var _a2;
@@ -211,7 +294,38 @@ class Canvas extends (_a = BasePlugin, _ref_dec = [register("ref")], _transform_
211
294
  (_a2 = this.ref) == null ? void 0 : _a2.setTransform(positionX != null ? positionX : this.transform.positionX, positionY != null ? positionY : this.transform.positionY, scale != null ? scale : this.transform.scale, animationTime);
212
295
  }
213
296
  render({ children } = { children: null }) {
214
- const [transform, setTransform] = useState(null);
297
+ const defaultTransform = useMemo(() => {
298
+ const defaultLocated = this.config.defaultLocated;
299
+ if (!(defaultLocated == null ? void 0 : defaultLocated.nodeId)) {
300
+ return this.transform;
301
+ }
302
+ const node = this.getNode({ id: defaultLocated.nodeId });
303
+ if (!node) {
304
+ return this.transform;
305
+ }
306
+ defaultLocated.inline || (defaultLocated.inline = "center");
307
+ defaultLocated.block || (defaultLocated.block = "center");
308
+ defaultLocated.scale || (defaultLocated.scale = this.transform.scale);
309
+ const { positionX, positionY, scale: newScale } = calculateTransform(__spreadValues({
310
+ node,
311
+ container: this.container,
312
+ transform: this.transform
313
+ }, defaultLocated));
314
+ const newTransform = {
315
+ previousScale: this.transform.scale,
316
+ scale: newScale,
317
+ positionX: -positionX,
318
+ positionY: -positionY
319
+ };
320
+ this.transform = newTransform;
321
+ return newTransform;
322
+ }, []);
323
+ const [transformRef, setTransformRef] = useReducer((_, ref) => {
324
+ if (this.ref !== ref) {
325
+ this.ref = ref;
326
+ }
327
+ return ref;
328
+ }, null);
215
329
  const [fixedLayers, childrenLayers] = useMemo(() => {
216
330
  const fixedLayers2 = [];
217
331
  const childrenLayers2 = [];
@@ -227,36 +341,37 @@ class Canvas extends (_a = BasePlugin, _ref_dec = [register("ref")], _transform_
227
341
  }
228
342
  return [fixedLayers2, childrenLayers2];
229
343
  }, [children]);
230
- useEffect(() => {
231
- this.ref = transform;
232
- const wrapperElement = transform == null ? void 0 : transform.instance.wrapperComponent;
233
- if (!wrapperElement) {
234
- return;
344
+ useLayoutEffect(() => {
345
+ const wrapperElement = transformRef == null ? void 0 : transformRef.instance.wrapperComponent;
346
+ if (wrapperElement) {
347
+ Object.defineProperty(wrapperElement, "scrollTop", {
348
+ get() {
349
+ return -transformRef.instance.transformState.positionY;
350
+ },
351
+ set(value) {
352
+ transformRef.instance.setTransformState(transformRef.instance.transformState.scale, transformRef.instance.transformState.positionX, -value);
353
+ }
354
+ });
355
+ Object.defineProperty(wrapperElement, "scrollLeft", {
356
+ get() {
357
+ return -transformRef.instance.transformState.positionX;
358
+ },
359
+ set(value) {
360
+ transformRef.instance.setTransformState(transformRef.instance.transformState.scale, -value, transformRef.instance.transformState.positionY);
361
+ }
362
+ });
235
363
  }
236
- Object.defineProperty(wrapperElement, "scrollTop", {
237
- get() {
238
- return -transform.instance.transformState.positionY;
239
- },
240
- set(value) {
241
- transform.instance.setTransformState(transform.instance.transformState.scale, transform.instance.transformState.positionX, -value);
242
- }
243
- });
244
- Object.defineProperty(wrapperElement, "scrollLeft", {
245
- get() {
246
- return -transform.instance.transformState.positionX;
247
- },
248
- set(value) {
249
- transform.instance.setTransformState(transform.instance.transformState.scale, -value, transform.instance.transformState.positionY);
250
- }
251
- });
252
364
  let frame;
253
- return transform == null ? void 0 : transform.instance.onChange((ref) => {
365
+ return transformRef == null ? void 0 : transformRef.instance.onChange((ref) => {
254
366
  cancelAnimationFrame(frame);
255
367
  frame = requestAnimationFrame(() => {
368
+ if (isEqual(this.transform, ref.state)) {
369
+ return;
370
+ }
256
371
  this.transform = __spreadValues({}, ref.state);
257
372
  });
258
373
  });
259
- }, [transform]);
374
+ }, [transformRef]);
260
375
  const onClick = useCallback((e) => {
261
376
  if (!this.isCanvasEvent(e)) {
262
377
  return;
@@ -279,10 +394,32 @@ class Canvas extends (_a = BasePlugin, _ref_dec = [register("ref")], _transform_
279
394
  listener(e);
280
395
  });
281
396
  }, []);
397
+ const onMouseMove = useCallback((e) => {
398
+ if (this.edgeScroll.config.enabled) {
399
+ const rect = e.currentTarget.getBoundingClientRect();
400
+ this.mousePosition = {
401
+ x: e.clientX - rect.left,
402
+ y: e.clientY - rect.top
403
+ };
404
+ if (this.edgeScrollAnimationFrame === null) {
405
+ this.edgeScrollAnimationFrame = requestAnimationFrame(this.handleEdgeScroll);
406
+ }
407
+ }
408
+ }, []);
409
+ const onMouseLeave = useCallback(() => {
410
+ this.mousePosition = null;
411
+ if (this.edgeScrollAnimationFrame !== null) {
412
+ cancelAnimationFrame(this.edgeScrollAnimationFrame);
413
+ this.edgeScrollAnimationFrame = null;
414
+ }
415
+ }, []);
282
416
  return /* @__PURE__ */ jsxs(
283
417
  TransformWrapper,
284
418
  __spreadProps(__spreadValues({}, this.config), {
285
- ref: setTransform,
419
+ ref: setTransformRef,
420
+ initialPositionX: defaultTransform.positionX,
421
+ initialPositionY: defaultTransform.positionY,
422
+ initialScale: defaultTransform.scale,
286
423
  children: [
287
424
  /* @__PURE__ */ jsx(Fragment, { children: fixedLayers }),
288
425
  /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
@@ -297,7 +434,9 @@ class Canvas extends (_a = BasePlugin, _ref_dec = [register("ref")], _transform_
297
434
  },
298
435
  wrapperProps: {
299
436
  onClick,
300
- onContextMenu
437
+ onContextMenu,
438
+ onMouseMove,
439
+ onMouseLeave
301
440
  },
302
441
  contentClass: bem(this.name, "content"),
303
442
  contentStyle: {
@@ -327,6 +466,10 @@ class Canvas extends (_a = BasePlugin, _ref_dec = [register("ref")], _transform_
327
466
  };
328
467
  this.subscriptions.push(() => {
329
468
  Object.values(this.listeners).forEach((set) => set.clear());
469
+ if (this.edgeScrollAnimationFrame !== null) {
470
+ cancelAnimationFrame(this.edgeScrollAnimationFrame);
471
+ this.edgeScrollAnimationFrame = null;
472
+ }
330
473
  });
331
474
  }
332
475
  isCanvasEvent(e) {
@@ -342,6 +485,7 @@ __decorateElement(_init, 1, "render", _render_dec, Canvas);
342
485
  __decorateElement(_init, 1, "init", _init_dec, Canvas);
343
486
  __decorateElement(_init, 5, "ref", _ref_dec, Canvas);
344
487
  __decorateElement(_init, 5, "transform", _transform_dec, Canvas);
488
+ __decorateElement(_init, 5, "edgeScroll", _edgeScroll_dec, Canvas);
345
489
  __decorateElement(_init, 5, "getNode", _getNode_dec, Canvas);
346
490
  __decorateElement(_init, 5, "container", _container_dec, Canvas);
347
491
  __decorateElement(_init, 5, "interaction", _interaction_dec, Canvas);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knotx/plugins-canvas",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Canvas Plugin for Knotx",
5
5
  "author": "boenfu",
6
6
  "license": "MIT",
@@ -29,23 +29,23 @@
29
29
  ],
30
30
  "peerDependencies": {
31
31
  "react": ">=17.0.0",
32
- "@knotx/jsx": "0.3.4"
32
+ "@knotx/jsx": "0.3.6"
33
33
  },
34
34
  "dependencies": {
35
35
  "lodash-es": "^4.17.21",
36
36
  "react-zoom-pan-pinch": "^3.7.0",
37
37
  "rxjs": "^7.8.1",
38
- "@knotx/core": "0.3.4",
39
- "@knotx/decorators": "0.3.4"
38
+ "@knotx/core": "0.3.6",
39
+ "@knotx/decorators": "0.3.6"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/lodash-es": "^4.17.12",
43
43
  "@types/react": "^17.0.0",
44
44
  "react": "^17.0.0",
45
- "@knotx/build-config": "0.3.4",
46
- "@knotx/eslint-config": "0.3.4",
47
- "@knotx/jsx": "0.3.4",
48
- "@knotx/typescript-config": "0.3.4"
45
+ "@knotx/build-config": "0.3.6",
46
+ "@knotx/eslint-config": "0.3.6",
47
+ "@knotx/jsx": "0.3.6",
48
+ "@knotx/typescript-config": "0.3.6"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "unbuild",