@operato/image-marker 8.0.0-alpha.17 → 8.0.0-alpha.22

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.
@@ -15,16 +15,31 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
15
15
  this.startY = 0;
16
16
  this.offsetX = 0;
17
17
  this.offsetY = 0;
18
- // 마우스 이동 이벤트 핸들러 (도형 그리기 중)
19
- this.onMouseMove = (event) => {
20
- if (!this.drawing)
18
+ this.onSvgTouchStart = (event) => {
19
+ if (event.touches.length === 1) {
20
+ this.selectedShape = null;
21
+ this.shapes = this.shapes.map(s => {
22
+ return { ...s, selected: false };
23
+ });
24
+ if (this.currentMode === 'move') {
25
+ this.startPanning(event);
26
+ }
27
+ else if (this.currentMode === 'draw') {
28
+ this.startDrawing(event);
29
+ }
30
+ }
31
+ else if (event.touches.length === 2) {
32
+ this.startPinchZooming(event);
33
+ }
34
+ };
35
+ this.onDrawingMouseMove = (event) => {
36
+ if (!this.drawing) {
21
37
  return;
38
+ }
22
39
  const { x, y } = this.getSvgPoint(event);
23
- // 최소 거리 확인 (예: 5px 이상이어야 도형을 생성)
24
40
  const minDistance = 5;
25
41
  const distance = Math.sqrt(Math.pow(x - this.startX, 2) + Math.pow(y - this.startY, 2));
26
42
  if (!this.selectedShape && distance >= minDistance) {
27
- // 거리가 충분할 때만 새로운 도형을 생성
28
43
  let newShape;
29
44
  switch (this.currentShapeType) {
30
45
  case 'rectangle':
@@ -81,13 +96,11 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
81
96
  return;
82
97
  }
83
98
  this.selectedShape = newShape;
84
- this.shapes = [...this.shapes, newShape]; // 새로운 도형 추가
99
+ this.shapes = [...this.shapes, newShape];
85
100
  }
86
101
  if (this.selectedShape) {
87
- // 선택된 도형의 크기를 업데이트
88
102
  switch (this.selectedShape.type) {
89
103
  case 'rectangle':
90
- // x, y를 업데이트하여 음의 방향으로 드래그할 때도 사각형이 올바르게 그려지도록 함
91
104
  this.selectedShape.x = Math.min(x, this.startX);
92
105
  this.selectedShape.y = Math.min(y, this.startY);
93
106
  this.selectedShape.width = Math.abs(x - this.startX);
@@ -111,14 +124,20 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
111
124
  this.selectedShape.y2 = y;
112
125
  break;
113
126
  }
114
- this.requestUpdate(); // 화면 업데이트
127
+ this.requestUpdate();
115
128
  }
116
129
  };
117
- this.onMouseUp = () => {
130
+ this.onDrawingMouseUp = (event) => {
118
131
  this.drawing = false;
119
132
  const svg = this.svg;
120
- svg.removeEventListener('mousemove', this.onMouseMove);
121
- svg.removeEventListener('mouseup', this.onMouseUp);
133
+ if ('touches' in event) {
134
+ svg.removeEventListener('touchmove', this.onDrawingMouseMove);
135
+ svg.removeEventListener('touchend', this.onDrawingMouseUp);
136
+ }
137
+ else {
138
+ svg.removeEventListener('mousemove', this.onDrawingMouseMove);
139
+ svg.removeEventListener('mouseup', this.onDrawingMouseUp);
140
+ }
122
141
  if (this.selectedShape) {
123
142
  this.selectedShape.selected = true;
124
143
  }
@@ -129,7 +148,6 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
129
148
  return;
130
149
  }
131
150
  const { x, y } = this.getSvgPoint(event);
132
- // 마우스 이동에 따라 도형의 위치 업데이트
133
151
  switch (this.selectedShape.type) {
134
152
  case 'text':
135
153
  case 'rectangle':
@@ -159,26 +177,66 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
159
177
  this.selectedShape.y2 = this.selectedShape.y2 + deltaY;
160
178
  break;
161
179
  }
162
- this.requestUpdate(); // 화면 업데이트
180
+ this.requestUpdate();
163
181
  };
164
- this.onShapeMouseUp = () => {
182
+ this.onShapeMouseUp = (event) => {
165
183
  const svg = this.svg;
166
- svg.removeEventListener('mousemove', this.onShapeMouseMove);
167
- svg.removeEventListener('mouseup', this.onShapeMouseUp);
184
+ if ('touches' in event) {
185
+ svg.removeEventListener('touchmove', this.onShapeMouseMove);
186
+ svg.removeEventListener('touchend', this.onShapeMouseUp);
187
+ }
188
+ else {
189
+ svg.removeEventListener('mousemove', this.onShapeMouseMove);
190
+ svg.removeEventListener('mouseup', this.onShapeMouseUp);
191
+ }
168
192
  };
169
193
  this.onHandleMouseMove = (event) => {
170
- if (!this.selectedShape)
194
+ if (!this.selectedShape) {
171
195
  return;
196
+ }
172
197
  const { x, y } = this.getSvgPoint(event);
173
198
  switch (this.selectedShape.type) {
174
- case 'text':
199
+ // case 'text':
200
+ // this.selectedShape.width = Math.abs(x - this.selectedShape.x!)
201
+ // this.selectedShape.height = Math.abs(y - this.selectedShape.y!)
202
+ // if (x < this.selectedShape.x!) this.selectedShape.x = x
203
+ // if (y < this.selectedShape.y!) this.selectedShape.y = y
204
+ // break
175
205
  case 'rectangle':
176
- this.selectedShape.width = Math.abs(x - this.selectedShape.x);
177
- this.selectedShape.height = Math.abs(y - this.selectedShape.y);
178
- if (x < this.selectedShape.x)
206
+ const startX = this.selectedShape.x;
207
+ const startY = this.selectedShape.y;
208
+ const width = this.selectedShape.width;
209
+ const height = this.selectedShape.height;
210
+ // 핸들의 좌표에 따라 너비 및 높이 조정
211
+ if (this.offsetX === startX && this.offsetY === startY) {
212
+ // 상좌 핸들 (왼쪽 위)
179
213
  this.selectedShape.x = x;
180
- if (y < this.selectedShape.y)
181
214
  this.selectedShape.y = y;
215
+ this.selectedShape.width = width + (startX - x);
216
+ this.selectedShape.height = height + (startY - y);
217
+ }
218
+ else if (this.offsetX === startX + width && this.offsetY === startY) {
219
+ // 상우 핸들 (오른쪽 위)
220
+ this.selectedShape.y = y;
221
+ this.selectedShape.width = Math.abs(x - startX);
222
+ this.selectedShape.height = height + (startY - y);
223
+ }
224
+ else if (this.offsetX === startX && this.offsetY === startY + height) {
225
+ // 하좌 핸들 (왼쪽 아래)
226
+ this.selectedShape.x = x;
227
+ this.selectedShape.width = width + (startX - x);
228
+ this.selectedShape.height = Math.abs(y - startY);
229
+ }
230
+ else if (this.offsetX === startX + width && this.offsetY === startY + height) {
231
+ // 하우 핸들 (오른쪽 아래)
232
+ this.selectedShape.width = Math.abs(x - startX);
233
+ this.selectedShape.height = Math.abs(y - startY);
234
+ }
235
+ // 최소 너비 및 높이 유지
236
+ this.selectedShape.width = Math.max(10, this.selectedShape.width);
237
+ this.selectedShape.height = Math.max(10, this.selectedShape.height);
238
+ this.offsetX = x;
239
+ this.offsetY = y;
182
240
  break;
183
241
  case 'circle':
184
242
  const dx = x - this.selectedShape.cx;
@@ -211,12 +269,29 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
211
269
  }
212
270
  this.requestUpdate();
213
271
  };
214
- this.onHandleMouseUp = () => {
272
+ this.onHandleMouseUp = (event) => {
215
273
  const svg = this.svg;
216
- svg.removeEventListener('mousemove', this.onHandleMouseMove);
217
- svg.removeEventListener('mouseup', this.onHandleMouseUp);
274
+ if ('touches' in event) {
275
+ svg.removeEventListener('touchmove', this.onHandleMouseMove);
276
+ svg.removeEventListener('touchend', this.onHandleMouseUp);
277
+ }
278
+ else {
279
+ svg.removeEventListener('mousemove', this.onHandleMouseMove);
280
+ svg.removeEventListener('mouseup', this.onHandleMouseUp);
281
+ }
218
282
  this.selectedShape = null;
219
283
  };
284
+ this.onFocus = () => {
285
+ this.addEventListener('keydown', this.handleKeyDown);
286
+ };
287
+ this.onBlur = () => {
288
+ this.removeEventListener('keydown', this.handleKeyDown);
289
+ };
290
+ this.handleKeyDown = (event) => {
291
+ if ((event.key === 'Delete' || event.key === 'Backspace') && this.selectedShape) {
292
+ this.deleteSelectedShape();
293
+ }
294
+ };
220
295
  }
221
296
  static { this.styles = [
222
297
  ...BaseImageMarker.styles,
@@ -247,13 +322,25 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
247
322
  }
248
323
  `
249
324
  ]; }
325
+ connectedCallback() {
326
+ super.connectedCallback();
327
+ this.setAttribute('tabindex', '0');
328
+ this.addEventListener('focus', this.onFocus);
329
+ this.addEventListener('blur', this.onBlur);
330
+ }
331
+ disconnectedCallback() {
332
+ super.disconnectedCallback();
333
+ this.removeEventListener('focus', this.onFocus);
334
+ this.removeEventListener('blur', this.onBlur);
335
+ }
250
336
  render() {
251
337
  return html `
252
338
  <div class="container">
253
339
  <svg
254
340
  @mousedown=${this.onSvgMouseDown}
255
- @wheel=${this.onWheel}
256
- @dblclick=${this.onDoubleClick}
341
+ @touchstart=${this.onSvgTouchStart}
342
+ @wheel=${this.onSvgWheel}
343
+ @dblclick=${this.onSvgDoubleClick}
257
344
  viewBox=${this.viewBox}
258
345
  >
259
346
  <image href=${this.imageUrl} @load=${this.onImageLoad}></image>
@@ -268,13 +355,8 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
268
355
  const { mode, shapeType } = e.detail;
269
356
  this.setMode(mode, shapeType);
270
357
  }}
271
- @clicked-delete=${() => {
272
- if (this.selectedShape) {
273
- this.shapes = this.shapes.filter(s => s.id !== this.selectedShape.id);
274
- this.requestUpdate();
275
- }
276
- }}
277
- @clicked-save=${() => {
358
+ @tap-delete=${() => this.deleteSelectedShape()}
359
+ @tap-save=${() => {
278
360
  this.dispatchEvent(new CustomEvent('shapes-changed', {
279
361
  detail: this.shapes,
280
362
  bubbles: true,
@@ -285,72 +367,56 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
285
367
  `;
286
368
  }
287
369
  onSvgMouseDown(event) {
288
- const svg = this.svg;
289
370
  this.selectedShape = null;
290
371
  this.shapes = this.shapes.map(s => {
291
372
  return { ...s, selected: false };
292
373
  });
293
- this.selectedShape = null;
294
374
  if (this.currentMode === 'move') {
295
- this.isPanning = true;
296
- this.panStart = { x: event.clientX, y: event.clientY };
297
- svg.classList.add('dragging');
298
- svg.addEventListener('mousemove', this.onPanMouseMove);
299
- svg.addEventListener('mouseup', this.onPanMouseUp);
300
- }
301
- else if (this.currentMode === 'select') {
302
- this.startShapeDragging(event);
303
- svg.addEventListener('mousemove', this.onShapeMouseMove);
304
- svg.addEventListener('mouseup', this.onShapeMouseUp);
375
+ this.startPanning(event);
305
376
  }
306
377
  else if (this.currentMode === 'draw') {
307
378
  this.startDrawing(event);
308
- svg.addEventListener('mousemove', this.onMouseMove);
309
- svg.addEventListener('mouseup', this.onMouseUp);
310
- }
311
- }
312
- startShapeDragging(event) {
313
- const { x, y } = this.getSvgPoint(event);
314
- if (this.selectedShape) {
315
- this.offsetX = x - (this.selectedShape.x || this.selectedShape.cx || 0);
316
- this.offsetY = y - (this.selectedShape.y || this.selectedShape.cy || 0);
317
379
  }
318
380
  }
319
381
  startDrawing(event) {
320
382
  const { x, y } = this.getSvgPoint(event);
321
- // currentShapeType이 'text'일 경우에만 input을 보여 텍스트 입력을 받음
322
383
  if (this.currentShapeType === 'text') {
323
384
  const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
324
385
  textElement.setAttribute('x', x.toString());
325
386
  textElement.setAttribute('y', y.toString());
326
- textElement.textContent = 'ABC'; /* should not be empty space */
387
+ textElement.textContent = 'ABC';
327
388
  this.svg.appendChild(textElement);
328
- // input 요소 생성
329
389
  requestAnimationFrame(() => {
330
390
  this.showTextInput(textElement, null, { x, y });
331
391
  });
332
392
  }
333
393
  else {
334
- // 'text'가 아닌 경우 기존 도형 그리기 동작 유지
335
394
  this.startX = x;
336
395
  this.startY = y;
337
396
  this.drawing = true;
338
397
  }
398
+ if ('touches' in event) {
399
+ this.svg.addEventListener('touchmove', this.onDrawingMouseMove);
400
+ this.svg.addEventListener('touchend', this.onDrawingMouseUp);
401
+ }
402
+ else {
403
+ this.svg.addEventListener('mousemove', this.onDrawingMouseMove);
404
+ this.svg.addEventListener('mouseup', this.onDrawingMouseUp);
405
+ }
339
406
  }
340
407
  setMode(mode, shapeType) {
341
408
  this.currentMode = mode;
342
409
  this.currentShapeType = shapeType;
343
410
  }
344
- // SVG 좌표계로 마우스 좌표 변환
345
411
  getSvgPoint(event) {
412
+ const { x, y } = this.getPointFromEvent(event);
346
413
  const svg = this.svg;
347
414
  const point = svg.createSVGPoint();
348
- point.x = event.clientX;
349
- point.y = event.clientY;
415
+ point.x = x;
416
+ point.y = y;
350
417
  const ctm = svg.getScreenCTM().inverse();
351
418
  return point.matrixTransform(ctm);
352
419
  }
353
- // 도형 렌더링 함수
354
420
  renderShape(shape) {
355
421
  const commonAttrs = {
356
422
  stroke: shape.selected ? 'blue' : 'red',
@@ -367,6 +433,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
367
433
  fill="${commonAttrs.stroke}"
368
434
  style="cursor: ${commonAttrs.cursor}; font-size: 16px;"
369
435
  @mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
436
+ @touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
370
437
  @dblclick="${(e) => this.onTextDoubleClick(e, shape)}"
371
438
  >${shape.text}</text>
372
439
  `;
@@ -382,6 +449,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
382
449
  fill="${commonAttrs.fill}"
383
450
  style="cursor: ${commonAttrs.cursor}"
384
451
  @mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
452
+ @touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
385
453
  ></rect>
386
454
  ${shape.selected ? this.renderResizeHandles(shape) : ''}
387
455
  `;
@@ -396,6 +464,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
396
464
  fill="${commonAttrs.fill}"
397
465
  style="cursor: ${commonAttrs.cursor}"
398
466
  @mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
467
+ @touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
399
468
  ></circle>
400
469
  ${shape.selected ? this.renderResizeHandles(shape) : ''}
401
470
  `;
@@ -408,6 +477,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
408
477
  fill="${commonAttrs.fill}"
409
478
  style="cursor: ${commonAttrs.cursor}"
410
479
  @mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
480
+ @touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
411
481
  ></polygon>
412
482
  ${shape.selected ? this.renderResizeHandles(shape) : ''}
413
483
  `;
@@ -422,6 +492,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
422
492
  stroke-width="${commonAttrs['stroke-width']}"
423
493
  style="cursor: ${commonAttrs.cursor}"
424
494
  @mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
495
+ @touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
425
496
  ></line>
426
497
  ${shape.selected ? this.renderResizeHandles(shape) : ''}
427
498
  `;
@@ -442,6 +513,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
442
513
  marker-end="url(#arrowhead-${shape.id})"
443
514
  style="cursor: ${commonAttrs.cursor}"
444
515
  @mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
516
+ @touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
445
517
  ></line>
446
518
  ${shape.selected ? this.renderResizeHandles(shape) : ''}
447
519
  `;
@@ -456,7 +528,6 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
456
528
  selected: s.id === shape.id
457
529
  }));
458
530
  this.selectedShape = this.shapes.find(s => s.id == shape.id);
459
- // 마우스 클릭 좌표와 도형의 현재 좌표 차이 계산
460
531
  const { x, y } = this.getSvgPoint(event);
461
532
  switch (shape.type) {
462
533
  case 'text':
@@ -477,19 +548,15 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
477
548
  this.offsetY = y - shape.y1;
478
549
  break;
479
550
  }
480
- // 드래그 이벤트 리스너 추가
481
551
  const svg = this.svg;
482
- svg.addEventListener('mousemove', this.onShapeMouseMove);
483
- svg.addEventListener('mouseup', this.onShapeMouseUp);
484
- }
485
- getScale() {
486
- if (!this.svg) {
487
- return 1;
552
+ if ('touches' in event) {
553
+ svg.addEventListener('touchmove', this.onShapeMouseMove);
554
+ svg.addEventListener('touchend', this.onShapeMouseUp);
555
+ }
556
+ else {
557
+ svg.addEventListener('mousemove', this.onShapeMouseMove);
558
+ svg.addEventListener('mouseup', this.onShapeMouseUp);
488
559
  }
489
- const [vbWidth, vbHeight] = this.viewBox.split(' ').map(Number);
490
- const scaleX = vbWidth / this.svg.clientWidth;
491
- const scaleY = vbHeight / this.svg.clientHeight;
492
- return Math.max(scaleX, scaleY);
493
560
  }
494
561
  renderResizeHandles(shape) {
495
562
  let handles = [];
@@ -539,6 +606,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
539
606
  class="resize-handle"
540
607
  style="cursor: ${handle.cursor};"
541
608
  @mousedown="${(e) => this.onHandleMouseDown(e, shape, handle)}"
609
+ @touchstart="${(e) => this.onHandleMouseDown(e, shape, handle)}"
542
610
  ></rect>
543
611
  </g>
544
612
  `)}
@@ -550,8 +618,14 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
550
618
  this.offsetX = handle.x;
551
619
  this.offsetY = handle.y;
552
620
  const svg = this.svg;
553
- svg.addEventListener('mousemove', this.onHandleMouseMove);
554
- svg.addEventListener('mouseup', this.onHandleMouseUp);
621
+ if ('touches' in event) {
622
+ svg.addEventListener('touchmove', this.onHandleMouseMove);
623
+ svg.addEventListener('touchend', this.onHandleMouseUp);
624
+ }
625
+ else {
626
+ svg.addEventListener('mousemove', this.onHandleMouseMove);
627
+ svg.addEventListener('mouseup', this.onHandleMouseUp);
628
+ }
555
629
  }
556
630
  onTextDoubleClick(event, shape) {
557
631
  event.stopPropagation();
@@ -559,26 +633,20 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
559
633
  this.showTextInput(textElement, shape);
560
634
  }
561
635
  showTextInput(textElement, shape = null, options) {
562
- // input 요소 생성
563
636
  const input = document.createElement('input');
564
637
  input.style.position = 'absolute';
565
- // SVG 텍스트의 CTM 얻기
566
638
  const ctm = textElement.getScreenCTM() || { a: 1, e: 0, d: 1, f: 0 };
567
639
  const bbox = textElement.getBBox();
568
- // 텍스트의 화면 위치 계산
569
640
  const screenX = bbox.x * ctm.a + ctm.e;
570
641
  const screenY = bbox.y * ctm.d + ctm.f;
571
- // input 위치 조정 (scale과 transform을 고려한 정확한 좌표로 변환)
572
642
  input.style.left = `${screenX}px`;
573
643
  input.style.top = `${screenY}px`;
574
- // 텍스트 폰트 스타일을 가져와 input에 적용
575
644
  const computedStyle = window.getComputedStyle(textElement);
576
645
  const fontSize = parseFloat(computedStyle.fontSize) * ctm.a;
577
646
  const fontFamily = computedStyle.fontFamily;
578
647
  const fontWeight = computedStyle.fontWeight;
579
648
  const textColor = computedStyle.fill || computedStyle.color;
580
649
  const lineHeight = parseFloat(computedStyle.lineHeight) * ctm.d;
581
- // input 스타일을 텍스트와 일치시킴
582
650
  input.style.fontSize = `${fontSize}px`;
583
651
  input.style.fontFamily = fontFamily;
584
652
  input.style.fontWeight = fontWeight;
@@ -592,13 +660,10 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
592
660
  if (shape) {
593
661
  input.value = shape.text || '';
594
662
  }
595
- // SVG 텍스트 숨김
596
663
  textElement.style.visibility = 'hidden';
597
- // input 요소를 추가하고, 텍스트 길이에 따라 너비 조정
598
664
  document.body.appendChild(input);
599
- this.adjustInputWidth(input); // 최초 너비 조정
665
+ this.adjustInputWidth(input);
600
666
  requestAnimationFrame(() => input.focus());
601
- // 입력 중에 동적으로 input의 너비 조정
602
667
  input.addEventListener('input', () => {
603
668
  this.adjustInputWidth(input);
604
669
  });
@@ -656,19 +721,24 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
656
721
  });
657
722
  });
658
723
  }
659
- // 텍스트 길이에 맞춰 input 박스의 너비를 조정하는 함수 (최소 길이 유지)
660
724
  adjustInputWidth(input) {
661
- const minWidth = 50; // 최소 너비 (픽셀 단위로 설정)
725
+ const minWidth = 50;
662
726
  const textWidth = this.measureTextWidth(input.value || input.placeholder, input);
663
- input.style.width = `${Math.max(textWidth + 10, minWidth)}px`; // 텍스트 길이 + 여유 공간, 최소 너비 적용
727
+ input.style.width = `${Math.max(textWidth + 10, minWidth)}px`;
664
728
  }
665
- // 텍스트 길이를 측정하는 함수
666
729
  measureTextWidth(text, input) {
667
730
  const canvas = document.createElement('canvas');
668
731
  const context = canvas.getContext('2d');
669
732
  context.font = window.getComputedStyle(input).font;
670
733
  return context.measureText(text).width;
671
734
  }
735
+ deleteSelectedShape() {
736
+ if (this.selectedShape) {
737
+ this.shapes = this.shapes.filter(s => s.id !== this.selectedShape.id);
738
+ this.selectedShape = null;
739
+ this.requestUpdate();
740
+ }
741
+ }
672
742
  };
673
743
  __decorate([
674
744
  state()