@operato/image-marker 8.0.0-alpha.33 → 8.0.0-alpha.38

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/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [8.0.0-alpha.38](https://github.com/hatiolab/operato/compare/v8.0.0-alpha.37...v8.0.0-alpha.38) (2024-10-20)
7
+
8
+
9
+ ### :bug: Bug Fix
10
+
11
+ * support scaled images for ox-image-marker ([b908134](https://github.com/hatiolab/operato/commit/b908134c05bb33786bc4632ac3e9a980aa9a78a7))
12
+
13
+
14
+
15
+ ## [8.0.0-alpha.37](https://github.com/hatiolab/operato/compare/v8.0.0-alpha.36...v8.0.0-alpha.37) (2024-10-20)
16
+
17
+
18
+ ### :bug: Bug Fix
19
+
20
+ * pointer-event & pointer-event for ox-image-marker ([c503d0a](https://github.com/hatiolab/operato/commit/c503d0a235ddadd97fdcd84ae98a303a7ffe7a6f))
21
+ * update dev-dependencies version ([d6fe342](https://github.com/hatiolab/operato/commit/d6fe342de738c6bad3528550a862b5a1d2573161))
22
+
23
+
24
+
6
25
  ## [8.0.0-alpha.33](https://github.com/hatiolab/operato/compare/v8.0.0-alpha.32...v8.0.0-alpha.33) (2024-10-14)
7
26
 
8
27
  **Note:** Version bump only for package @operato/image-marker
@@ -1,41 +1,36 @@
1
- import { LitElement, nothing } from 'lit';
2
- import { Shape } from './types';
1
+ import { LitElement, nothing, PropertyValues } from 'lit';
2
+ import { GestureEventDoubleTap, GestureEventDrag, GestureEventPinch } from '@operato/utils/gesture-helper.js';
3
+ import { ImageProvider, Shape } from './types';
3
4
  export declare class BaseImageMarker extends LitElement {
4
5
  static styles: import("lit").CSSResult[];
5
6
  imageUrl: string;
6
7
  shapes: Shape[];
8
+ protected currentImage?: string;
7
9
  protected viewBox: string;
8
10
  protected zoomLevel: number;
11
+ private imageProvider?;
12
+ protected debouncedUpdateImage: import("lodash").DebouncedFunc<() => Promise<void>>;
9
13
  protected zoomFactor: number;
10
14
  protected zoomThreshold: number;
11
- protected isPanning: boolean;
12
- protected panStart: {
13
- x: number;
14
- y: number;
15
- };
16
- protected panInitiated: boolean;
17
- protected panThreshold: number;
18
- protected initialViewBox: string;
19
- private initialPinchDistance;
20
- private initialZoomLevel;
21
- private lastTouchEnd;
15
+ protected initialViewBox: string | null;
22
16
  protected svg: SVGSVGElement;
17
+ protected image: SVGImageElement;
18
+ private gestureHelper;
19
+ connectedCallback(): void;
20
+ disconnectedCallback(): void;
21
+ protected updated(changes: PropertyValues): void;
22
+ setImageProvider(provider: ImageProvider): void;
23
23
  protected onImageLoad(event: Event): void;
24
+ protected updateImage(): Promise<void>;
24
25
  protected onSvgWheel(event: WheelEvent): void;
25
- protected onSvgMouseDown(event: MouseEvent): void;
26
- protected isDoubleTouch(event: TouchEvent): boolean;
27
- protected onSvgTouchStart: (event: TouchEvent) => void;
28
- protected startPinchZooming(event: TouchEvent): void;
29
- protected onZoomTouchMove: (event: TouchEvent) => void;
30
- protected onZoomTouchEnd: (event: TouchEvent) => void;
31
- protected applyZoom(): void;
32
- protected onSvgDoubleClick(): void;
33
- protected resetZoom(): void;
34
- protected startPanning(event: MouseEvent | TouchEvent): void;
35
- protected onPanMouseMove: (event: MouseEvent | TouchEvent) => void;
36
- protected onPanMouseUp: (event: MouseEvent | TouchEvent) => void;
26
+ protected onSvgPinch(e: GestureEventPinch): void;
27
+ protected onSvgDoubleTap(e: GestureEventDoubleTap): void;
28
+ protected onSvgDrag(e: GestureEventDrag): void;
29
+ zoom(scale: number, offsetX: number, offsetY: number): void;
30
+ protected reset(): void;
31
+ protected handleLinkClick(shape: Shape): void;
37
32
  protected renderShape(shape: Shape): import("lit-html").TemplateResult<2> | typeof nothing;
38
- protected getPointFromEvent(event: MouseEvent | TouchEvent): {
33
+ protected getPointFromEvent(event: MouseEvent | PointerEvent | GestureEventDrag): {
39
34
  x: number;
40
35
  y: number;
41
36
  };
@@ -1,6 +1,9 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { LitElement, css, svg, nothing } from 'lit';
3
3
  import { property, state, query } from 'lit/decorators.js';
4
+ import { debounce } from 'lodash-es';
5
+ import { GestureHelper } from '@operato/utils/gesture-helper.js';
6
+ import { DefaultImageProvider } from './default-image-provider';
4
7
  export class BaseImageMarker extends LitElement {
5
8
  constructor() {
6
9
  super(...arguments);
@@ -8,87 +11,12 @@ export class BaseImageMarker extends LitElement {
8
11
  this.shapes = [];
9
12
  this.viewBox = '0 0 800 600';
10
13
  this.zoomLevel = 1;
14
+ this.imageProvider = new DefaultImageProvider();
15
+ this.debouncedUpdateImage = debounce(this.updateImage.bind(this), 300);
11
16
  this.zoomFactor = 0.1;
12
17
  this.zoomThreshold = 0.05;
13
- this.isPanning = false;
14
- this.panStart = { x: 0, y: 0 };
15
- this.panInitiated = false;
16
- this.panThreshold = 20;
17
- this.initialViewBox = '0 0 800 600';
18
- this.initialPinchDistance = null;
19
- this.initialZoomLevel = 1;
20
- this.lastTouchEnd = 0;
21
- this.onSvgTouchStart = (event) => {
22
- event.preventDefault();
23
- if (this.isDoubleTouch(event)) {
24
- this.resetZoom();
25
- }
26
- if (event.touches.length === 1) {
27
- this.startPanning(event);
28
- }
29
- else if (event.touches.length === 2) {
30
- this.startPinchZooming(event);
31
- }
32
- };
33
- this.onZoomTouchMove = (event) => {
34
- event.preventDefault();
35
- if (event.touches.length === 2 && this.initialPinchDistance) {
36
- const touch1 = event.touches[0];
37
- const touch2 = event.touches[1];
38
- const dx = touch2.clientX - touch1.clientX;
39
- const dy = touch2.clientY - touch1.clientY;
40
- const currentPinchDistance = Math.sqrt(dx * dx + dy * dy);
41
- const scaleFactor = currentPinchDistance / this.initialPinchDistance;
42
- // 줌 threshold 확인 후 줌 적용
43
- if (Math.abs(scaleFactor - 1) > this.zoomThreshold) {
44
- this.zoomLevel = this.initialZoomLevel * scaleFactor;
45
- this.applyZoom();
46
- }
47
- }
48
- };
49
- this.onZoomTouchEnd = (event) => {
50
- event.preventDefault();
51
- if (event.touches.length < 2) {
52
- this.initialPinchDistance = null;
53
- }
54
- this.svg.removeEventListener('touchmove', this.onZoomTouchMove);
55
- this.svg.removeEventListener('touchend', this.onZoomTouchEnd);
56
- };
57
- this.onPanMouseMove = (event) => {
58
- if (!this.isPanning) {
59
- return;
60
- }
61
- event.preventDefault();
62
- const { x, y } = this.getPointFromEvent(event);
63
- const dx = x - this.panStart.x;
64
- const dy = y - this.panStart.y;
65
- if (!this.panInitiated && Math.sqrt(dx * dx + dy * dy) < this.panThreshold) {
66
- return;
67
- }
68
- this.panInitiated = true;
69
- const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number);
70
- const scaleX = vbWidth / this.svg.clientWidth;
71
- const scaleY = vbHeight / this.svg.clientHeight;
72
- const scale = Math.max(scaleX, scaleY);
73
- const newVbX = vbX - dx * scale;
74
- const newVbY = vbY - dy * scale;
75
- this.viewBox = `${newVbX} ${newVbY} ${vbWidth} ${vbHeight}`;
76
- this.panStart = { x, y };
77
- };
78
- this.onPanMouseUp = (event) => {
79
- event.preventDefault();
80
- const svg = this.svg;
81
- this.isPanning = false;
82
- svg.classList.remove('dragging');
83
- if ('touches' in event) {
84
- svg.removeEventListener('touchmove', this.onPanMouseMove);
85
- svg.removeEventListener('touchend', this.onPanMouseUp);
86
- }
87
- else {
88
- svg.removeEventListener('mousemove', this.onPanMouseMove);
89
- svg.removeEventListener('mouseup', this.onPanMouseUp);
90
- }
91
- };
18
+ this.initialViewBox = null;
19
+ this.gestureHelper = null;
92
20
  }
93
21
  static { this.styles = [
94
22
  css `
@@ -108,24 +36,102 @@ export class BaseImageMarker extends LitElement {
108
36
  cursor: pointer;
109
37
  width: 100%;
110
38
  height: 100%;
39
+ touch-action: none;
40
+ }
41
+
42
+ .link:hover {
43
+ cursor: pointer;
111
44
  }
112
45
  `
113
46
  ]; }
47
+ connectedCallback() {
48
+ super.connectedCallback();
49
+ requestAnimationFrame(() => {
50
+ this.gestureHelper = new GestureHelper(this.svg);
51
+ });
52
+ }
53
+ disconnectedCallback() {
54
+ super.disconnectedCallback();
55
+ this.gestureHelper?.dispose();
56
+ this.gestureHelper = null;
57
+ }
58
+ updated(changes) {
59
+ if (changes.has('imageUrl') || changes.has('imageProvider')) {
60
+ this.initialViewBox = null;
61
+ this.updateImage();
62
+ }
63
+ }
64
+ setImageProvider(provider) {
65
+ this.imageProvider = provider;
66
+ }
114
67
  onImageLoad(event) {
68
+ const element = event.target;
115
69
  const img = new Image();
116
70
  img.onload = () => {
117
71
  const width = img.naturalWidth;
118
72
  const height = img.naturalHeight;
119
- this.viewBox = `0 0 ${width} ${height}`;
120
- this.initialViewBox = `0 0 ${width} ${height}`;
73
+ if (!this.initialViewBox) {
74
+ this.initialViewBox = `0 0 ${width} ${height}`;
75
+ this.viewBox = this.initialViewBox;
76
+ element.setAttribute('width', width.toString());
77
+ element.setAttribute('height', height.toString());
78
+ element.setAttribute('preserveAspectRatio', 'xMidYMid meet');
79
+ }
80
+ else {
81
+ const [initialX, initialY, initialWidth, initialHeight] = this.initialViewBox.split(' ').map(Number);
82
+ const scale = initialWidth / width;
83
+ element.setAttribute('width', (width * scale).toString());
84
+ element.setAttribute('height', (height * scale).toString());
85
+ }
121
86
  };
122
- img.src = this.imageUrl;
87
+ img.src = this.currentImage;
88
+ }
89
+ async updateImage() {
90
+ if (!this.imageProvider) {
91
+ throw new Error('ImageProvider is not set');
92
+ }
93
+ if (!this.imageUrl) {
94
+ return;
95
+ }
96
+ const options = {
97
+ id: this.imageUrl, // 이미지 ID는 imageUrl 속성을 사용
98
+ scale: this.zoomLevel, // scale은 zoomLevel 속성을 사용
99
+ offsetX: 0, // offsetX는 0으로 설정
100
+ offsetY: 0, // offsetY는 0으로 설정
101
+ currentImage: this.currentImage // 기존 이미지 객체 전달
102
+ };
103
+ this.currentImage = await this.imageProvider.getImage(options);
123
104
  }
124
105
  onSvgWheel(event) {
125
106
  event.preventDefault();
126
107
  const { offsetX, offsetY, deltaY } = event;
127
108
  const scale = deltaY > 0 ? 1 - this.zoomFactor : 1 + this.zoomFactor;
128
- if (Math.abs(scale - 1) > this.zoomThreshold) {
109
+ this.zoom(scale, offsetX, offsetY);
110
+ }
111
+ onSvgPinch(e) {
112
+ e.stopPropagation();
113
+ const { scale, centerX, centerY } = e.detail;
114
+ console.log('pinch', e.detail);
115
+ this.zoom(scale, centerX, centerY);
116
+ }
117
+ onSvgDoubleTap(e) {
118
+ e.stopPropagation();
119
+ this.reset();
120
+ }
121
+ onSvgDrag(e) {
122
+ const { deltaX, deltaY } = e.detail;
123
+ console.log('drag', e.detail);
124
+ const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number);
125
+ const scaleX = vbWidth / this.svg.clientWidth;
126
+ const scaleY = vbHeight / this.svg.clientHeight;
127
+ const scale = Math.max(scaleX, scaleY);
128
+ const newVbX = vbX - deltaX * scale;
129
+ const newVbY = vbY - deltaY * scale;
130
+ this.viewBox = `${newVbX} ${newVbY} ${vbWidth} ${vbHeight}`;
131
+ this.debouncedUpdateImage();
132
+ }
133
+ zoom(scale, offsetX, offsetY) {
134
+ if (Math.abs(scale) > this.zoomThreshold) {
129
135
  this.zoomLevel *= scale;
130
136
  const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number);
131
137
  const mouseX = vbX + (offsetX / this.svg.clientWidth) * vbWidth;
@@ -135,61 +141,20 @@ export class BaseImageMarker extends LitElement {
135
141
  const newVbX = mouseX - (offsetX / this.svg.clientWidth) * newVbWidth;
136
142
  const newVbY = mouseY - (offsetY / this.svg.clientHeight) * newVbHeight;
137
143
  this.viewBox = `${newVbX} ${newVbY} ${newVbWidth} ${newVbHeight}`;
144
+ this.debouncedUpdateImage();
138
145
  }
139
146
  }
140
- onSvgMouseDown(event) {
141
- event.preventDefault();
142
- this.startPanning(event);
143
- this.svg.addEventListener('mousemove', this.onPanMouseMove);
144
- this.svg.addEventListener('mouseup', this.onPanMouseUp);
145
- }
146
- isDoubleTouch(event) {
147
- const currentTime = new Date().getTime();
148
- const tapLength = currentTime - this.lastTouchEnd;
149
- const result = tapLength < 300 && event.touches.length == 1;
150
- this.lastTouchEnd = currentTime;
151
- return result;
152
- }
153
- startPinchZooming(event) {
154
- const touch1 = event.touches[0];
155
- const touch2 = event.touches[1];
156
- const dx = touch2.clientX - touch1.clientX;
157
- const dy = touch2.clientY - touch1.clientY;
158
- this.initialPinchDistance = Math.sqrt(dx * dx + dy * dy);
159
- this.initialZoomLevel = this.zoomLevel;
160
- this.svg.addEventListener('touchmove', this.onZoomTouchMove);
161
- this.svg.addEventListener('touchend', this.onZoomTouchEnd);
162
- }
163
- applyZoom() {
164
- const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number);
165
- const initialViewBoxNumbers = this.initialViewBox.split(' ').map(Number);
166
- const newVbWidth = initialViewBoxNumbers[2] / this.zoomLevel;
167
- const newVbHeight = initialViewBoxNumbers[3] / this.zoomLevel;
168
- const newVbX = vbX + (vbWidth - newVbWidth) / 2;
169
- const newVbY = vbY + (vbHeight - newVbHeight) / 2;
170
- this.viewBox = `${newVbX} ${newVbY} ${newVbWidth} ${newVbHeight}`;
171
- }
172
- onSvgDoubleClick() {
173
- this.resetZoom();
174
- }
175
- resetZoom() {
147
+ reset() {
176
148
  this.viewBox = this.initialViewBox;
177
149
  this.zoomLevel = 1;
150
+ this.debouncedUpdateImage();
178
151
  }
179
- startPanning(event) {
180
- const { x, y } = this.getPointFromEvent(event);
181
- this.isPanning = true;
182
- this.panStart = { x, y };
183
- this.panInitiated = false; // 팬 동작이 아직 시작되지 않았음을 나타냄
184
- this.svg.classList.add('dragging');
185
- if ('touches' in event) {
186
- this.svg.addEventListener('touchmove', this.onPanMouseMove);
187
- this.svg.addEventListener('touchend', this.onPanMouseUp);
188
- }
189
- else {
190
- this.svg.addEventListener('mousemove', this.onPanMouseMove);
191
- this.svg.addEventListener('mouseup', this.onPanMouseUp);
192
- }
152
+ handleLinkClick(shape) {
153
+ this.dispatchEvent(new CustomEvent('link-clicked', {
154
+ detail: { shapeId: shape.id, link: shape.link },
155
+ bubbles: true,
156
+ composed: true
157
+ }));
193
158
  }
194
159
  renderShape(shape) {
195
160
  const commonAttrs = {
@@ -214,21 +179,28 @@ export class BaseImageMarker extends LitElement {
214
179
  </marker>
215
180
  </defs>
216
181
  <line x1="${shape.x1}" y1="${shape.y1}" x2="${shape.x2}" y2="${shape.y2}" stroke="${commonAttrs.stroke}" stroke-width="${commonAttrs['stroke-width']}" marker-end="url(#arrowhead-${shape.id})"></line>`;
182
+ case 'link':
183
+ return svg `
184
+ <rect class="link" x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}"
185
+ stroke="${commonAttrs.stroke}" stroke-width="${commonAttrs['stroke-width']}"
186
+ fill="${commonAttrs.fill}" fill-opacity="0.5" @click="${() => this.handleLinkClick(shape)}">
187
+ </rect>`;
217
188
  default:
218
189
  return nothing;
219
190
  }
220
191
  }
221
192
  getPointFromEvent(event) {
222
- if ('touches' in event) {
193
+ if ('clientX' in event) {
223
194
  return {
224
- x: event.touches[0].clientX,
225
- y: event.touches[0].clientY
195
+ x: event.clientX,
196
+ y: event.clientY
226
197
  };
227
198
  }
228
199
  else {
200
+ const { clientX, clientY } = event.detail;
229
201
  return {
230
- x: event.clientX,
231
- y: event.clientY
202
+ x: clientX,
203
+ y: clientY
232
204
  };
233
205
  }
234
206
  }
@@ -239,13 +211,22 @@ __decorate([
239
211
  __decorate([
240
212
  property({ type: Array })
241
213
  ], BaseImageMarker.prototype, "shapes", void 0);
214
+ __decorate([
215
+ state()
216
+ ], BaseImageMarker.prototype, "currentImage", void 0);
242
217
  __decorate([
243
218
  state()
244
219
  ], BaseImageMarker.prototype, "viewBox", void 0);
245
220
  __decorate([
246
221
  state()
247
222
  ], BaseImageMarker.prototype, "zoomLevel", void 0);
223
+ __decorate([
224
+ state()
225
+ ], BaseImageMarker.prototype, "imageProvider", void 0);
248
226
  __decorate([
249
227
  query('svg')
250
228
  ], BaseImageMarker.prototype, "svg", void 0);
229
+ __decorate([
230
+ query('image')
231
+ ], BaseImageMarker.prototype, "image", void 0);
251
232
  //# sourceMappingURL=base-image-marker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-image-marker.js","sourceRoot":"","sources":["../../src/base-image-marker.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, svg, nothing } from 'lit'\nimport { property, state, query } from 'lit/decorators.js'\n\nimport { Shape } from './types'\n\nexport class BaseImageMarker extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n }\n\n .container {\n flex: 1;\n position: relative;\n }\n\n svg {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n width: 100%;\n height: 100%;\n }\n `\n ]\n\n @property({ type: String }) imageUrl: string = ''\n @property({ type: Array }) shapes: Shape[] = []\n\n @state() protected viewBox = '0 0 800 600'\n @state() protected zoomLevel = 1\n\n protected zoomFactor = 0.1\n protected zoomThreshold = 0.05\n protected isPanning = false\n protected panStart = { x: 0, y: 0 }\n protected panInitiated = false\n protected panThreshold = 20\n\n protected initialViewBox = '0 0 800 600'\n private initialPinchDistance: number | null = null\n private initialZoomLevel: number = 1\n private lastTouchEnd: number = 0\n\n @query('svg') protected svg!: SVGSVGElement\n\n protected onImageLoad(event: Event) {\n const img = new Image()\n img.onload = () => {\n const width = img.naturalWidth\n const height = img.naturalHeight\n this.viewBox = `0 0 ${width} ${height}`\n this.initialViewBox = `0 0 ${width} ${height}`\n }\n img.src = this.imageUrl\n }\n\n protected onSvgWheel(event: WheelEvent) {\n event.preventDefault()\n\n const { offsetX, offsetY, deltaY } = event\n const scale = deltaY > 0 ? 1 - this.zoomFactor : 1 + this.zoomFactor\n if (Math.abs(scale - 1) > this.zoomThreshold) {\n this.zoomLevel *= scale\n\n const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number)\n const mouseX = vbX + (offsetX / this.svg.clientWidth) * vbWidth\n const mouseY = vbY + (offsetY / this.svg.clientHeight) * vbHeight\n\n const newVbWidth = vbWidth / scale\n const newVbHeight = vbHeight / scale\n\n const newVbX = mouseX - (offsetX / this.svg.clientWidth) * newVbWidth\n const newVbY = mouseY - (offsetY / this.svg.clientHeight) * newVbHeight\n\n this.viewBox = `${newVbX} ${newVbY} ${newVbWidth} ${newVbHeight}`\n }\n }\n\n protected onSvgMouseDown(event: MouseEvent) {\n event.preventDefault()\n\n this.startPanning(event)\n\n this.svg.addEventListener('mousemove', this.onPanMouseMove)\n this.svg.addEventListener('mouseup', this.onPanMouseUp)\n }\n\n protected isDoubleTouch(event: TouchEvent) {\n const currentTime = new Date().getTime()\n const tapLength = currentTime - this.lastTouchEnd\n\n const result = tapLength < 300 && event.touches.length == 1\n this.lastTouchEnd = currentTime\n\n return result\n }\n\n protected onSvgTouchStart = (event: TouchEvent) => {\n event.preventDefault()\n\n if (this.isDoubleTouch(event)) {\n this.resetZoom()\n }\n\n if (event.touches.length === 1) {\n this.startPanning(event)\n } else if (event.touches.length === 2) {\n this.startPinchZooming(event)\n }\n }\n\n protected startPinchZooming(event: TouchEvent) {\n const touch1 = event.touches[0]\n const touch2 = event.touches[1]\n\n const dx = touch2.clientX - touch1.clientX\n const dy = touch2.clientY - touch1.clientY\n\n this.initialPinchDistance = Math.sqrt(dx * dx + dy * dy)\n this.initialZoomLevel = this.zoomLevel\n\n this.svg.addEventListener('touchmove', this.onZoomTouchMove)\n this.svg.addEventListener('touchend', this.onZoomTouchEnd)\n }\n\n protected onZoomTouchMove = (event: TouchEvent) => {\n event.preventDefault()\n\n if (event.touches.length === 2 && this.initialPinchDistance) {\n const touch1 = event.touches[0]\n const touch2 = event.touches[1]\n const dx = touch2.clientX - touch1.clientX\n const dy = touch2.clientY - touch1.clientY\n const currentPinchDistance = Math.sqrt(dx * dx + dy * dy)\n\n const scaleFactor = currentPinchDistance / this.initialPinchDistance\n\n // 줌 threshold 확인 후 줌 적용\n if (Math.abs(scaleFactor - 1) > this.zoomThreshold) {\n this.zoomLevel = this.initialZoomLevel * scaleFactor\n this.applyZoom()\n }\n }\n }\n\n protected onZoomTouchEnd = (event: TouchEvent) => {\n event.preventDefault()\n\n if (event.touches.length < 2) {\n this.initialPinchDistance = null\n }\n\n this.svg.removeEventListener('touchmove', this.onZoomTouchMove)\n this.svg.removeEventListener('touchend', this.onZoomTouchEnd)\n }\n\n protected applyZoom() {\n const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number)\n const initialViewBoxNumbers = this.initialViewBox.split(' ').map(Number)\n const newVbWidth = initialViewBoxNumbers[2] / this.zoomLevel\n const newVbHeight = initialViewBoxNumbers[3] / this.zoomLevel\n const newVbX = vbX + (vbWidth - newVbWidth) / 2\n const newVbY = vbY + (vbHeight - newVbHeight) / 2\n\n this.viewBox = `${newVbX} ${newVbY} ${newVbWidth} ${newVbHeight}`\n }\n\n protected onSvgDoubleClick() {\n this.resetZoom()\n }\n\n protected resetZoom() {\n this.viewBox = this.initialViewBox\n this.zoomLevel = 1\n }\n\n protected startPanning(event: MouseEvent | TouchEvent) {\n const { x, y } = this.getPointFromEvent(event)\n\n this.isPanning = true\n this.panStart = { x, y }\n this.panInitiated = false // 팬 동작이 아직 시작되지 않았음을 나타냄\n this.svg.classList.add('dragging')\n\n if ('touches' in event) {\n this.svg.addEventListener('touchmove', this.onPanMouseMove)\n this.svg.addEventListener('touchend', this.onPanMouseUp)\n } else {\n this.svg.addEventListener('mousemove', this.onPanMouseMove)\n this.svg.addEventListener('mouseup', this.onPanMouseUp)\n }\n }\n\n protected onPanMouseMove = (event: MouseEvent | TouchEvent) => {\n if (!this.isPanning) {\n return\n }\n event.preventDefault()\n\n const { x, y } = this.getPointFromEvent(event)\n\n const dx = x - this.panStart.x\n const dy = y - this.panStart.y\n\n if (!this.panInitiated && Math.sqrt(dx * dx + dy * dy) < this.panThreshold) {\n return\n }\n\n this.panInitiated = true\n\n const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number)\n const scaleX = vbWidth / this.svg.clientWidth\n const scaleY = vbHeight / this.svg.clientHeight\n const scale = Math.max(scaleX, scaleY)\n\n const newVbX = vbX - dx * scale\n const newVbY = vbY - dy * scale\n\n this.viewBox = `${newVbX} ${newVbY} ${vbWidth} ${vbHeight}`\n this.panStart = { x, y }\n }\n\n protected onPanMouseUp = (event: MouseEvent | TouchEvent) => {\n event.preventDefault()\n\n const svg = this.svg\n\n this.isPanning = false\n svg.classList.remove('dragging')\n\n if ('touches' in event) {\n svg.removeEventListener('touchmove', this.onPanMouseMove)\n svg.removeEventListener('touchend', this.onPanMouseUp)\n } else {\n svg.removeEventListener('mousemove', this.onPanMouseMove)\n svg.removeEventListener('mouseup', this.onPanMouseUp)\n }\n }\n\n protected renderShape(shape: Shape) {\n const commonAttrs = {\n stroke: 'red',\n 'stroke-width': 1,\n fill: 'transparent'\n }\n\n switch (shape.type) {\n case 'text':\n return svg`<text x=\"${shape.x}\" y=\"${shape.y}\" text-anchor=\"middle\" alignment-baseline=\"middle\" fill=\"black\">${shape.text}</text>`\n case 'rectangle':\n return svg`<rect x=\"${shape.x}\" y=\"${shape.y}\" width=\"${shape.width}\" height=\"${shape.height}\" stroke=\"${commonAttrs.stroke}\" stroke-width=\"${commonAttrs['stroke-width']}\" fill=\"${commonAttrs.fill}\"></rect>`\n case 'circle':\n return svg`<circle cx=\"${shape.cx}\" cy=\"${shape.cy}\" r=\"${shape.r}\" stroke=\"${commonAttrs.stroke}\" stroke-width=\"${commonAttrs['stroke-width']}\" fill=\"${commonAttrs.fill}\"></circle>`\n case 'line':\n return svg`<line x1=\"${shape.x1}\" y1=\"${shape.y1}\" x2=\"${shape.x2}\" y2=\"${shape.y2}\" stroke=\"${commonAttrs.stroke}\" stroke-width=\"${commonAttrs['stroke-width']}\"></line>`\n case 'arrow':\n return svg`\n <defs>\n <marker id=\"arrowhead-${shape.id}\" markerWidth=\"10\" markerHeight=\"7\" refX=\"10\" refY=\"3.5\" orient=\"auto\">\n <polygon points=\"0 0, 10 3.5, 0 7\" fill=\"${commonAttrs.stroke}\"></polygon>\n </marker>\n </defs>\n <line x1=\"${shape.x1}\" y1=\"${shape.y1}\" x2=\"${shape.x2}\" y2=\"${shape.y2}\" stroke=\"${commonAttrs.stroke}\" stroke-width=\"${commonAttrs['stroke-width']}\" marker-end=\"url(#arrowhead-${shape.id})\"></line>`\n default:\n return nothing\n }\n }\n\n protected getPointFromEvent(event: MouseEvent | TouchEvent) {\n if ('touches' in event) {\n return {\n x: event.touches[0].clientX,\n y: event.touches[0].clientY\n }\n } else {\n return {\n x: event.clientX,\n y: event.clientY\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"base-image-marker.js","sourceRoot":"","sources":["../../src/base-image-marker.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, svg, nothing, PropertyValues } from 'lit'\nimport { property, state, query } from 'lit/decorators.js'\nimport { debounce } from 'lodash-es'\n\nimport {\n GestureEventDoubleTap,\n GestureEventDrag,\n GestureEventPinch,\n GestureHelper\n} from '@operato/utils/gesture-helper.js'\nimport { ImageProvider, Shape } from './types'\nimport { DefaultImageProvider } from './default-image-provider'\n\nexport class BaseImageMarker extends LitElement {\n static styles = [\n css`\n :host {\n display: flex;\n }\n\n .container {\n flex: 1;\n position: relative;\n }\n\n svg {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n width: 100%;\n height: 100%;\n touch-action: none;\n }\n\n .link:hover {\n cursor: pointer;\n }\n `\n ]\n\n @property({ type: String }) imageUrl: string = ''\n @property({ type: Array }) shapes: Shape[] = []\n\n @state() protected currentImage?: string\n @state() protected viewBox = '0 0 800 600'\n @state() protected zoomLevel = 1\n @state() private imageProvider?: ImageProvider = new DefaultImageProvider()\n\n protected debouncedUpdateImage = debounce(this.updateImage.bind(this), 300)\n protected zoomFactor = 0.1\n protected zoomThreshold = 0.05\n\n protected initialViewBox: string | null = null\n\n @query('svg') protected svg!: SVGSVGElement\n @query('image') protected image!: SVGImageElement\n\n private gestureHelper: GestureHelper | null = null\n\n connectedCallback(): void {\n super.connectedCallback()\n requestAnimationFrame(() => {\n this.gestureHelper = new GestureHelper(this.svg)\n })\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback()\n\n this.gestureHelper?.dispose()\n this.gestureHelper = null\n }\n\n protected updated(changes: PropertyValues): void {\n if (changes.has('imageUrl') || changes.has('imageProvider')) {\n this.initialViewBox = null\n this.updateImage()\n }\n }\n\n public setImageProvider(provider: ImageProvider) {\n this.imageProvider = provider\n }\n\n protected onImageLoad(event: Event) {\n const element = event.target as SVGImageElement\n const img = new Image()\n\n img.onload = () => {\n const width = img.naturalWidth\n const height = img.naturalHeight\n\n if (!this.initialViewBox) {\n this.initialViewBox = `0 0 ${width} ${height}`\n this.viewBox = this.initialViewBox\n\n element.setAttribute('width', width.toString())\n element.setAttribute('height', height.toString())\n element.setAttribute('preserveAspectRatio', 'xMidYMid meet')\n } else {\n const [initialX, initialY, initialWidth, initialHeight] = this.initialViewBox.split(' ').map(Number)\n const scale = initialWidth / width\n\n element.setAttribute('width', (width * scale).toString())\n element.setAttribute('height', (height * scale).toString())\n }\n }\n\n img.src = this.currentImage!\n }\n\n protected async updateImage() {\n if (!this.imageProvider) {\n throw new Error('ImageProvider is not set')\n }\n\n if (!this.imageUrl) {\n return\n }\n\n const options = {\n id: this.imageUrl, // 이미지 ID는 imageUrl 속성을 사용\n scale: this.zoomLevel, // scale은 zoomLevel 속성을 사용\n offsetX: 0, // offsetX는 0으로 설정\n offsetY: 0, // offsetY는 0으로 설정\n currentImage: this.currentImage // 기존 이미지 객체 전달\n }\n\n this.currentImage = await this.imageProvider.getImage(options)\n }\n\n protected onSvgWheel(event: WheelEvent) {\n event.preventDefault()\n\n const { offsetX, offsetY, deltaY } = event\n const scale = deltaY > 0 ? 1 - this.zoomFactor : 1 + this.zoomFactor\n\n this.zoom(scale, offsetX, offsetY)\n }\n\n protected onSvgPinch(e: GestureEventPinch) {\n e.stopPropagation()\n\n const { scale, centerX, centerY } = e.detail\n\n console.log('pinch', e.detail)\n\n this.zoom(scale, centerX, centerY)\n }\n\n protected onSvgDoubleTap(e: GestureEventDoubleTap) {\n e.stopPropagation()\n\n this.reset()\n }\n\n protected onSvgDrag(e: GestureEventDrag) {\n const { deltaX, deltaY } = e.detail\n console.log('drag', e.detail)\n\n const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number)\n\n const scaleX = vbWidth / this.svg.clientWidth\n const scaleY = vbHeight / this.svg.clientHeight\n const scale = Math.max(scaleX, scaleY)\n\n const newVbX = vbX - deltaX * scale\n const newVbY = vbY - deltaY * scale\n\n this.viewBox = `${newVbX} ${newVbY} ${vbWidth} ${vbHeight}`\n\n this.debouncedUpdateImage()\n }\n\n zoom(scale: number, offsetX: number, offsetY: number) {\n if (Math.abs(scale) > this.zoomThreshold) {\n this.zoomLevel *= scale\n\n const [vbX, vbY, vbWidth, vbHeight] = this.viewBox.split(' ').map(Number)\n const mouseX = vbX + (offsetX / this.svg.clientWidth) * vbWidth\n const mouseY = vbY + (offsetY / this.svg.clientHeight) * vbHeight\n\n const newVbWidth = vbWidth / scale\n const newVbHeight = vbHeight / scale\n\n const newVbX = mouseX - (offsetX / this.svg.clientWidth) * newVbWidth\n const newVbY = mouseY - (offsetY / this.svg.clientHeight) * newVbHeight\n\n this.viewBox = `${newVbX} ${newVbY} ${newVbWidth} ${newVbHeight}`\n\n this.debouncedUpdateImage()\n }\n }\n\n protected reset() {\n this.viewBox = this.initialViewBox!\n this.zoomLevel = 1\n\n this.debouncedUpdateImage()\n }\n\n protected handleLinkClick(shape: Shape) {\n this.dispatchEvent(\n new CustomEvent('link-clicked', {\n detail: { shapeId: shape.id, link: shape.link },\n bubbles: true,\n composed: true\n })\n )\n }\n\n protected renderShape(shape: Shape) {\n const commonAttrs = {\n stroke: 'red',\n 'stroke-width': 1,\n fill: 'transparent'\n }\n\n switch (shape.type) {\n case 'text':\n return svg`<text x=\"${shape.x}\" y=\"${shape.y}\" text-anchor=\"middle\" alignment-baseline=\"middle\" fill=\"black\">${shape.text}</text>`\n case 'rectangle':\n return svg`<rect x=\"${shape.x}\" y=\"${shape.y}\" width=\"${shape.width}\" height=\"${shape.height}\" stroke=\"${commonAttrs.stroke}\" stroke-width=\"${commonAttrs['stroke-width']}\" fill=\"${commonAttrs.fill}\"></rect>`\n case 'circle':\n return svg`<circle cx=\"${shape.cx}\" cy=\"${shape.cy}\" r=\"${shape.r}\" stroke=\"${commonAttrs.stroke}\" stroke-width=\"${commonAttrs['stroke-width']}\" fill=\"${commonAttrs.fill}\"></circle>`\n case 'line':\n return svg`<line x1=\"${shape.x1}\" y1=\"${shape.y1}\" x2=\"${shape.x2}\" y2=\"${shape.y2}\" stroke=\"${commonAttrs.stroke}\" stroke-width=\"${commonAttrs['stroke-width']}\"></line>`\n case 'arrow':\n return svg`\n <defs>\n <marker id=\"arrowhead-${shape.id}\" markerWidth=\"10\" markerHeight=\"7\" refX=\"10\" refY=\"3.5\" orient=\"auto\">\n <polygon points=\"0 0, 10 3.5, 0 7\" fill=\"${commonAttrs.stroke}\"></polygon>\n </marker>\n </defs>\n <line x1=\"${shape.x1}\" y1=\"${shape.y1}\" x2=\"${shape.x2}\" y2=\"${shape.y2}\" stroke=\"${commonAttrs.stroke}\" stroke-width=\"${commonAttrs['stroke-width']}\" marker-end=\"url(#arrowhead-${shape.id})\"></line>`\n case 'link':\n return svg`\n <rect class=\"link\" x=\"${shape.x}\" y=\"${shape.y}\" width=\"${shape.width}\" height=\"${shape.height}\" \n stroke=\"${commonAttrs.stroke}\" stroke-width=\"${commonAttrs['stroke-width']}\" \n fill=\"${commonAttrs.fill}\" fill-opacity=\"0.5\" @click=\"${() => this.handleLinkClick(shape)}\">\n </rect>`\n default:\n return nothing\n }\n }\n\n protected getPointFromEvent(event: MouseEvent | PointerEvent | GestureEventDrag) {\n if ('clientX' in event) {\n return {\n x: event.clientX,\n y: event.clientY\n }\n } else {\n const { clientX, clientY } = event.detail\n return {\n x: clientX,\n y: clientY\n }\n }\n }\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import { ImageOptions, ImageProvider } from './types';
2
+ export declare class DefaultImageProvider implements ImageProvider {
3
+ getImage(options: ImageOptions): Promise<string>;
4
+ }
@@ -0,0 +1,6 @@
1
+ export class DefaultImageProvider {
2
+ async getImage(options) {
3
+ return options.id;
4
+ }
5
+ }
6
+ //# sourceMappingURL=default-image-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-image-provider.js","sourceRoot":"","sources":["../../src/default-image-provider.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,oBAAoB;IAC/B,KAAK,CAAC,QAAQ,CAAC,OAAqB;QAClC,OAAO,OAAO,CAAC,EAAE,CAAA;IACnB,CAAC;CACF","sourcesContent":["import { ImageOptions, ImageProvider } from './types'\n\nexport class DefaultImageProvider implements ImageProvider {\n async getImage(options: ImageOptions): Promise<string> {\n return options.id\n }\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './base-image-marker.js';
3
+ export * from './ox-image-marker.js';
4
+ export * from './ox-image-marker-view.js';
5
+ export * from './ox-image-marker-mode.js';
package/dist/src/index.js CHANGED
@@ -1,2 +1,6 @@
1
- "use strict";
1
+ export * from './types.js';
2
+ export * from './base-image-marker.js';
3
+ export * from './ox-image-marker.js';
4
+ export * from './ox-image-marker-view.js';
5
+ export * from './ox-image-marker-mode.js';
2
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"","sourcesContent":[""]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,wBAAwB,CAAA;AAEtC,cAAc,sBAAsB,CAAA;AACpC,cAAc,2BAA2B,CAAA;AACzC,cAAc,2BAA2B,CAAA","sourcesContent":["export * from './types.js'\nexport * from './base-image-marker.js'\n\nexport * from './ox-image-marker.js'\nexport * from './ox-image-marker-view.js'\nexport * from './ox-image-marker-mode.js'\n"]}
@@ -8,12 +8,12 @@ let OxImageMarkerView = class OxImageMarkerView extends BaseImageMarker {
8
8
  <div class="container">
9
9
  <svg
10
10
  @wheel=${this.onSvgWheel}
11
- @mousedown=${this.onSvgMouseDown}
12
- @touchstart=${this.onSvgTouchStart}
13
- @dblclick=${this.onSvgDoubleClick}
11
+ @doubletap=${this.onSvgDoubleTap}
12
+ @pinch=${this.onSvgPinch}
13
+ @drag=${this.onSvgDrag}
14
14
  viewBox=${this.viewBox}
15
15
  >
16
- <image href=${this.imageUrl} @load=${this.onImageLoad}></image>
16
+ <image href=${this.currentImage} @load=${this.onImageLoad}></image>
17
17
  ${this.shapes.map(shape => this.renderShape(shape))}
18
18
  </svg>
19
19
  </div>
@@ -1 +1 @@
1
- {"version":3,"file":"ox-image-marker-view.js","sourceRoot":"","sources":["../../src/ox-image-marker-view.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAGjD,IAAM,iBAAiB,GAAvB,MAAM,iBAAkB,SAAQ,eAAe;IACpD,MAAM;QACJ,OAAO,IAAI,CAAA;;;mBAGI,IAAI,CAAC,UAAU;uBACX,IAAI,CAAC,cAAc;wBAClB,IAAI,CAAC,eAAe;sBACtB,IAAI,CAAC,gBAAgB;oBACvB,IAAI,CAAC,OAAO;;wBAER,IAAI,CAAC,QAAQ,UAAU,IAAI,CAAC,WAAW;YACnD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;;;KAGxD,CAAA;IACH,CAAC;CACF,CAAA;AAjBY,iBAAiB;IAD7B,aAAa,CAAC,sBAAsB,CAAC;GACzB,iBAAiB,CAiB7B","sourcesContent":["import { html } from 'lit'\nimport { customElement } from 'lit/decorators.js'\n\nimport { BaseImageMarker } from './base-image-marker.js'\n\n@customElement('ox-image-marker-view')\nexport class OxImageMarkerView extends BaseImageMarker {\n render() {\n return html`\n <div class=\"container\">\n <svg\n @wheel=${this.onSvgWheel}\n @mousedown=${this.onSvgMouseDown}\n @touchstart=${this.onSvgTouchStart}\n @dblclick=${this.onSvgDoubleClick}\n viewBox=${this.viewBox}\n >\n <image href=${this.imageUrl} @load=${this.onImageLoad}></image>\n ${this.shapes.map(shape => this.renderShape(shape))}\n </svg>\n </div>\n `\n }\n}\n"]}
1
+ {"version":3,"file":"ox-image-marker-view.js","sourceRoot":"","sources":["../../src/ox-image-marker-view.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAGjD,IAAM,iBAAiB,GAAvB,MAAM,iBAAkB,SAAQ,eAAe;IACpD,MAAM;QACJ,OAAO,IAAI,CAAA;;;mBAGI,IAAI,CAAC,UAAU;uBACX,IAAI,CAAC,cAAc;mBACvB,IAAI,CAAC,UAAU;kBAChB,IAAI,CAAC,SAAS;oBACZ,IAAI,CAAC,OAAO;;wBAER,IAAI,CAAC,YAAY,UAAU,IAAI,CAAC,WAAW;YACvD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;;;KAGxD,CAAA;IACH,CAAC;CACF,CAAA;AAjBY,iBAAiB;IAD7B,aAAa,CAAC,sBAAsB,CAAC;GACzB,iBAAiB,CAiB7B","sourcesContent":["import { html } from 'lit'\nimport { customElement } from 'lit/decorators.js'\n\nimport { BaseImageMarker } from './base-image-marker.js'\n\n@customElement('ox-image-marker-view')\nexport class OxImageMarkerView extends BaseImageMarker {\n render() {\n return html`\n <div class=\"container\">\n <svg\n @wheel=${this.onSvgWheel}\n @doubletap=${this.onSvgDoubleTap}\n @pinch=${this.onSvgPinch}\n @drag=${this.onSvgDrag}\n viewBox=${this.viewBox}\n >\n <image href=${this.currentImage} @load=${this.onImageLoad}></image>\n ${this.shapes.map(shape => this.renderShape(shape))}\n </svg>\n </div>\n `\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import './ox-image-marker-mode';
2
2
  import { nothing } from 'lit';
3
+ import { GestureEventDrag, GestureEventPinch } from '@operato/utils/gesture-helper.js';
3
4
  import { Shape } from './types';
4
5
  import { BaseImageMarker } from './base-image-marker';
5
6
  import { OxImageMarkerMode } from './ox-image-marker-mode';
@@ -19,22 +20,21 @@ export declare class OxImageMarker extends BaseImageMarker {
19
20
  connectedCallback(): void;
20
21
  disconnectedCallback(): void;
21
22
  render(): import("lit-html").TemplateResult<1>;
22
- protected onSvgMouseDown(event: MouseEvent): void;
23
- protected onSvgTouchStart: (event: TouchEvent) => void;
24
- private startDrawing;
25
23
  private setMode;
26
- private onMouseMoveForDrawing;
27
- private onMouseUpForDrawing;
24
+ protected onSvgPinch(e: GestureEventPinch): void;
25
+ protected onSvgDrag(e: GestureEventDrag): void;
26
+ protected onSvgClick(e: MouseEvent): void;
27
+ private drawShape;
28
28
  private setSelectedShape;
29
29
  private getSvgPoint;
30
30
  protected renderShape(shape: Shape): import("lit-html").TemplateResult<2> | typeof nothing;
31
- private onMouseDownOnShape;
32
- private onMouseMoveOnShape;
33
- private onMouseUpOnShape;
31
+ private onPointerDownOnShape;
32
+ private onPointerMoveOnShape;
33
+ private onPointerUpOnShape;
34
34
  private renderResizeHandles;
35
- private onResizeHandleMouseDown;
36
- private onResizeHandleMouseMove;
37
- private onResizeHandleMouseUp;
35
+ private onResizeHandlePointerDown;
36
+ private onResizeHandlePointerMove;
37
+ private onResizeHandlePointerUp;
38
38
  private onTextDoubleClick;
39
39
  private showTextInput;
40
40
  private adjustInputWidth;