@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.
@@ -50,13 +50,27 @@ export class OxImageMarker extends BaseImageMarker {
50
50
  private offsetX = 0
51
51
  private offsetY = 0
52
52
 
53
+ connectedCallback() {
54
+ super.connectedCallback()
55
+ this.setAttribute('tabindex', '0')
56
+ this.addEventListener('focus', this.onFocus)
57
+ this.addEventListener('blur', this.onBlur)
58
+ }
59
+
60
+ disconnectedCallback() {
61
+ super.disconnectedCallback()
62
+ this.removeEventListener('focus', this.onFocus)
63
+ this.removeEventListener('blur', this.onBlur)
64
+ }
65
+
53
66
  render() {
54
67
  return html`
55
68
  <div class="container">
56
69
  <svg
57
70
  @mousedown=${this.onSvgMouseDown}
58
- @wheel=${this.onWheel}
59
- @dblclick=${this.onDoubleClick}
71
+ @touchstart=${this.onSvgTouchStart}
72
+ @wheel=${this.onSvgWheel}
73
+ @dblclick=${this.onSvgDoubleClick}
60
74
  viewBox=${this.viewBox}
61
75
  >
62
76
  <image href=${this.imageUrl} @load=${this.onImageLoad}></image>
@@ -71,13 +85,8 @@ export class OxImageMarker extends BaseImageMarker {
71
85
  const { mode, shapeType } = e.detail
72
86
  this.setMode(mode, shapeType)
73
87
  }}
74
- @clicked-delete=${() => {
75
- if (this.selectedShape) {
76
- this.shapes = this.shapes.filter(s => s.id !== this.selectedShape!.id)
77
- this.requestUpdate()
78
- }
79
- }}
80
- @clicked-save=${() => {
88
+ @tap-delete=${() => this.deleteSelectedShape()}
89
+ @tap-save=${() => {
81
90
  this.dispatchEvent(
82
91
  new CustomEvent('shapes-changed', {
83
92
  detail: this.shapes,
@@ -91,63 +100,63 @@ export class OxImageMarker extends BaseImageMarker {
91
100
  }
92
101
 
93
102
  protected onSvgMouseDown(event: MouseEvent) {
94
- const svg = this.svg
95
-
96
103
  this.selectedShape = null
97
104
  this.shapes = this.shapes.map(s => {
98
105
  return { ...s, selected: false }
99
106
  })
100
107
 
101
- this.selectedShape = null
102
108
  if (this.currentMode === 'move') {
103
- this.isPanning = true
104
- this.panStart = { x: event.clientX, y: event.clientY }
105
- svg.classList.add('dragging')
106
- svg.addEventListener('mousemove', this.onPanMouseMove)
107
- svg.addEventListener('mouseup', this.onPanMouseUp)
108
- } else if (this.currentMode === 'select') {
109
- this.startShapeDragging(event)
110
- svg.addEventListener('mousemove', this.onShapeMouseMove)
111
- svg.addEventListener('mouseup', this.onShapeMouseUp)
109
+ this.startPanning(event)
112
110
  } else if (this.currentMode === 'draw') {
113
111
  this.startDrawing(event)
114
- svg.addEventListener('mousemove', this.onMouseMove)
115
- svg.addEventListener('mouseup', this.onMouseUp)
116
112
  }
117
113
  }
118
114
 
119
- private startShapeDragging(event: MouseEvent) {
120
- const { x, y } = this.getSvgPoint(event)
121
- if (this.selectedShape) {
122
- this.offsetX = x - (this.selectedShape.x || this.selectedShape.cx || 0)
123
- this.offsetY = y - (this.selectedShape.y || this.selectedShape.cy || 0)
115
+ protected onSvgTouchStart = (event: TouchEvent) => {
116
+ if (event.touches.length === 1) {
117
+ this.selectedShape = null
118
+ this.shapes = this.shapes.map(s => {
119
+ return { ...s, selected: false }
120
+ })
121
+
122
+ if (this.currentMode === 'move') {
123
+ this.startPanning(event)
124
+ } else if (this.currentMode === 'draw') {
125
+ this.startDrawing(event)
126
+ }
127
+ } else if (event.touches.length === 2) {
128
+ this.startPinchZooming(event)
124
129
  }
125
130
  }
126
131
 
127
- private startDrawing(event: MouseEvent) {
132
+ private startDrawing(event: MouseEvent | TouchEvent) {
128
133
  const { x, y } = this.getSvgPoint(event)
129
134
 
130
- // currentShapeType이 'text'일 경우에만 input을 보여 텍스트 입력을 받음
131
135
  if (this.currentShapeType === 'text') {
132
136
  const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text')
133
137
 
134
138
  textElement.setAttribute('x', x.toString())
135
139
  textElement.setAttribute('y', y.toString())
136
- textElement.textContent = 'ABC' /* should not be empty space */
140
+ textElement.textContent = 'ABC'
137
141
 
138
142
  this.svg.appendChild(textElement)
139
143
 
140
- // input 요소 생성
141
144
  requestAnimationFrame(() => {
142
145
  this.showTextInput(textElement, null, { x, y })
143
146
  })
144
147
  } else {
145
- // 'text'가 아닌 경우 기존 도형 그리기 동작 유지
146
148
  this.startX = x
147
149
  this.startY = y
148
-
149
150
  this.drawing = true
150
151
  }
152
+
153
+ if ('touches' in event) {
154
+ this.svg.addEventListener('touchmove', this.onDrawingMouseMove)
155
+ this.svg.addEventListener('touchend', this.onDrawingMouseUp)
156
+ } else {
157
+ this.svg.addEventListener('mousemove', this.onDrawingMouseMove)
158
+ this.svg.addEventListener('mouseup', this.onDrawingMouseUp)
159
+ }
151
160
  }
152
161
 
153
162
  private setMode(mode: ImageMarkerMode, shapeType: ShapeType) {
@@ -155,18 +164,17 @@ export class OxImageMarker extends BaseImageMarker {
155
164
  this.currentShapeType = shapeType
156
165
  }
157
166
 
158
- // 마우스 이동 이벤트 핸들러 (도형 그리기 중)
159
- private onMouseMove = (event: MouseEvent) => {
160
- if (!this.drawing) return
167
+ private onDrawingMouseMove = (event: MouseEvent | TouchEvent) => {
168
+ if (!this.drawing) {
169
+ return
170
+ }
161
171
 
162
172
  const { x, y } = this.getSvgPoint(event)
163
173
 
164
- // 최소 거리 확인 (예: 5px 이상이어야 도형을 생성)
165
174
  const minDistance = 5
166
175
  const distance = Math.sqrt(Math.pow(x - this.startX, 2) + Math.pow(y - this.startY, 2))
167
176
 
168
177
  if (!this.selectedShape && distance >= minDistance) {
169
- // 거리가 충분할 때만 새로운 도형을 생성
170
178
  let newShape: Shape
171
179
 
172
180
  switch (this.currentShapeType) {
@@ -225,14 +233,12 @@ export class OxImageMarker extends BaseImageMarker {
225
233
  }
226
234
 
227
235
  this.selectedShape = newShape
228
- this.shapes = [...this.shapes, newShape] // 새로운 도형 추가
236
+ this.shapes = [...this.shapes, newShape]
229
237
  }
230
238
 
231
239
  if (this.selectedShape) {
232
- // 선택된 도형의 크기를 업데이트
233
240
  switch (this.selectedShape.type) {
234
241
  case 'rectangle':
235
- // x, y를 업데이트하여 음의 방향으로 드래그할 때도 사각형이 올바르게 그려지도록 함
236
242
  this.selectedShape.x = Math.min(x, this.startX)
237
243
  this.selectedShape.y = Math.min(y, this.startY)
238
244
  this.selectedShape.width = Math.abs(x - this.startX)
@@ -257,15 +263,22 @@ export class OxImageMarker extends BaseImageMarker {
257
263
  break
258
264
  }
259
265
 
260
- this.requestUpdate() // 화면 업데이트
266
+ this.requestUpdate()
261
267
  }
262
268
  }
263
269
 
264
- private onMouseUp = () => {
270
+ private onDrawingMouseUp = (event: MouseEvent | TouchEvent) => {
265
271
  this.drawing = false
272
+
266
273
  const svg = this.svg as SVGSVGElement
267
- svg.removeEventListener('mousemove', this.onMouseMove)
268
- svg.removeEventListener('mouseup', this.onMouseUp)
274
+
275
+ if ('touches' in event) {
276
+ svg.removeEventListener('touchmove', this.onDrawingMouseMove)
277
+ svg.removeEventListener('touchend', this.onDrawingMouseUp)
278
+ } else {
279
+ svg.removeEventListener('mousemove', this.onDrawingMouseMove)
280
+ svg.removeEventListener('mouseup', this.onDrawingMouseUp)
281
+ }
269
282
 
270
283
  if (this.selectedShape) {
271
284
  this.selectedShape.selected = true
@@ -274,17 +287,18 @@ export class OxImageMarker extends BaseImageMarker {
274
287
  this.requestUpdate()
275
288
  }
276
289
 
277
- // SVG 좌표계로 마우스 좌표 변환
278
- private getSvgPoint(event: MouseEvent) {
290
+ private getSvgPoint(event: MouseEvent | TouchEvent) {
291
+ const { x, y } = this.getPointFromEvent(event)
292
+
279
293
  const svg = this.svg
280
294
  const point = svg.createSVGPoint()
281
- point.x = event.clientX
282
- point.y = event.clientY
295
+ point.x = x
296
+ point.y = y
283
297
  const ctm = svg.getScreenCTM()!.inverse()
298
+
284
299
  return point.matrixTransform(ctm)
285
300
  }
286
301
 
287
- // 도형 렌더링 함수
288
302
  protected renderShape(shape: Shape) {
289
303
  const commonAttrs = {
290
304
  stroke: shape.selected ? 'blue' : 'red',
@@ -302,6 +316,7 @@ export class OxImageMarker extends BaseImageMarker {
302
316
  fill="${commonAttrs.stroke}"
303
317
  style="cursor: ${commonAttrs.cursor}; font-size: 16px;"
304
318
  @mousedown="${(e: MouseEvent) => this.onShapeMouseDown(e, shape)}"
319
+ @touchstart="${(e: TouchEvent) => this.onShapeMouseDown(e, shape)}"
305
320
  @dblclick="${(e: MouseEvent) => this.onTextDoubleClick(e, shape)}"
306
321
  >${shape.text}</text>
307
322
  `
@@ -318,6 +333,7 @@ export class OxImageMarker extends BaseImageMarker {
318
333
  fill="${commonAttrs.fill}"
319
334
  style="cursor: ${commonAttrs.cursor}"
320
335
  @mousedown="${(e: MouseEvent) => this.onShapeMouseDown(e, shape)}"
336
+ @touchstart="${(e: TouchEvent) => this.onShapeMouseDown(e, shape)}"
321
337
  ></rect>
322
338
  ${shape.selected ? this.renderResizeHandles(shape) : ''}
323
339
  `
@@ -333,6 +349,7 @@ export class OxImageMarker extends BaseImageMarker {
333
349
  fill="${commonAttrs.fill}"
334
350
  style="cursor: ${commonAttrs.cursor}"
335
351
  @mousedown="${(e: MouseEvent) => this.onShapeMouseDown(e, shape)}"
352
+ @touchstart="${(e: TouchEvent) => this.onShapeMouseDown(e, shape)}"
336
353
  ></circle>
337
354
  ${shape.selected ? this.renderResizeHandles(shape) : ''}
338
355
  `
@@ -346,6 +363,7 @@ export class OxImageMarker extends BaseImageMarker {
346
363
  fill="${commonAttrs.fill}"
347
364
  style="cursor: ${commonAttrs.cursor}"
348
365
  @mousedown="${(e: MouseEvent) => this.onShapeMouseDown(e, shape)}"
366
+ @touchstart="${(e: TouchEvent) => this.onShapeMouseDown(e, shape)}"
349
367
  ></polygon>
350
368
  ${shape.selected ? this.renderResizeHandles(shape) : ''}
351
369
  `
@@ -361,6 +379,7 @@ export class OxImageMarker extends BaseImageMarker {
361
379
  stroke-width="${commonAttrs['stroke-width']}"
362
380
  style="cursor: ${commonAttrs.cursor}"
363
381
  @mousedown="${(e: MouseEvent) => this.onShapeMouseDown(e, shape)}"
382
+ @touchstart="${(e: TouchEvent) => this.onShapeMouseDown(e, shape)}"
364
383
  ></line>
365
384
  ${shape.selected ? this.renderResizeHandles(shape) : ''}
366
385
  `
@@ -382,6 +401,7 @@ export class OxImageMarker extends BaseImageMarker {
382
401
  marker-end="url(#arrowhead-${shape.id})"
383
402
  style="cursor: ${commonAttrs.cursor}"
384
403
  @mousedown="${(e: MouseEvent) => this.onShapeMouseDown(e, shape)}"
404
+ @touchstart="${(e: TouchEvent) => this.onShapeMouseDown(e, shape)}"
385
405
  ></line>
386
406
  ${shape.selected ? this.renderResizeHandles(shape) : ''}
387
407
  `
@@ -391,7 +411,7 @@ export class OxImageMarker extends BaseImageMarker {
391
411
  }
392
412
  }
393
413
 
394
- private onShapeMouseDown(event: MouseEvent, shape: Shape) {
414
+ private onShapeMouseDown(event: MouseEvent | TouchEvent, shape: Shape) {
395
415
  event.stopPropagation()
396
416
 
397
417
  this.shapes = this.shapes.map(s => ({
@@ -401,7 +421,6 @@ export class OxImageMarker extends BaseImageMarker {
401
421
 
402
422
  this.selectedShape = this.shapes.find(s => s.id == shape.id)!
403
423
 
404
- // 마우스 클릭 좌표와 도형의 현재 좌표 차이 계산
405
424
  const { x, y } = this.getSvgPoint(event)
406
425
 
407
426
  switch (shape.type) {
@@ -428,20 +447,24 @@ export class OxImageMarker extends BaseImageMarker {
428
447
  break
429
448
  }
430
449
 
431
- // 드래그 이벤트 리스너 추가
432
450
  const svg = this.svg as SVGSVGElement
433
- svg.addEventListener('mousemove', this.onShapeMouseMove)
434
- svg.addEventListener('mouseup', this.onShapeMouseUp)
451
+
452
+ if ('touches' in event) {
453
+ svg.addEventListener('touchmove', this.onShapeMouseMove)
454
+ svg.addEventListener('touchend', this.onShapeMouseUp)
455
+ } else {
456
+ svg.addEventListener('mousemove', this.onShapeMouseMove)
457
+ svg.addEventListener('mouseup', this.onShapeMouseUp)
458
+ }
435
459
  }
436
460
 
437
- private onShapeMouseMove = (event: MouseEvent) => {
461
+ private onShapeMouseMove = (event: MouseEvent | TouchEvent) => {
438
462
  if (!this.selectedShape) {
439
463
  return
440
464
  }
441
465
 
442
466
  const { x, y } = this.getSvgPoint(event)
443
467
 
444
- // 마우스 이동에 따라 도형의 위치 업데이트
445
468
  switch (this.selectedShape.type) {
446
469
  case 'text':
447
470
  case 'rectangle':
@@ -473,26 +496,19 @@ export class OxImageMarker extends BaseImageMarker {
473
496
  break
474
497
  }
475
498
 
476
- this.requestUpdate() // 화면 업데이트
499
+ this.requestUpdate()
477
500
  }
478
501
 
479
- private onShapeMouseUp = () => {
502
+ private onShapeMouseUp = (event: MouseEvent | TouchEvent) => {
480
503
  const svg = this.svg as SVGSVGElement
481
- svg.removeEventListener('mousemove', this.onShapeMouseMove)
482
- svg.removeEventListener('mouseup', this.onShapeMouseUp)
483
- }
484
504
 
485
- private getScale(): number {
486
- if (!this.svg) {
487
- return 1
505
+ if ('touches' in event) {
506
+ svg.removeEventListener('touchmove', this.onShapeMouseMove)
507
+ svg.removeEventListener('touchend', this.onShapeMouseUp)
508
+ } else {
509
+ svg.removeEventListener('mousemove', this.onShapeMouseMove)
510
+ svg.removeEventListener('mouseup', this.onShapeMouseUp)
488
511
  }
489
-
490
- const [vbWidth, vbHeight] = this.viewBox.split(' ').map(Number)
491
-
492
- const scaleX = vbWidth / this.svg.clientWidth
493
- const scaleY = vbHeight / this.svg.clientHeight
494
-
495
- return Math.max(scaleX, scaleY)
496
512
  }
497
513
 
498
514
  private renderResizeHandles(shape: Shape) {
@@ -546,6 +562,7 @@ export class OxImageMarker extends BaseImageMarker {
546
562
  class="resize-handle"
547
563
  style="cursor: ${handle.cursor};"
548
564
  @mousedown="${(e: MouseEvent) => this.onHandleMouseDown(e, shape, handle)}"
565
+ @touchstart="${(e: TouchEvent) => this.onHandleMouseDown(e, shape, handle)}"
549
566
  ></rect>
550
567
  </g>
551
568
  `
@@ -553,27 +570,73 @@ export class OxImageMarker extends BaseImageMarker {
553
570
  `
554
571
  }
555
572
 
556
- private onHandleMouseDown(event: MouseEvent, shape: Shape, handle: any) {
573
+ private onHandleMouseDown(event: MouseEvent | TouchEvent, shape: Shape, handle: any) {
557
574
  event.stopPropagation()
575
+
558
576
  this.selectedShape = shape
559
577
  this.offsetX = handle.x
560
578
  this.offsetY = handle.y
561
579
  const svg = this.svg as SVGSVGElement
562
- svg.addEventListener('mousemove', this.onHandleMouseMove)
563
- svg.addEventListener('mouseup', this.onHandleMouseUp)
580
+
581
+ if ('touches' in event) {
582
+ svg.addEventListener('touchmove', this.onHandleMouseMove)
583
+ svg.addEventListener('touchend', this.onHandleMouseUp)
584
+ } else {
585
+ svg.addEventListener('mousemove', this.onHandleMouseMove)
586
+ svg.addEventListener('mouseup', this.onHandleMouseUp)
587
+ }
564
588
  }
565
589
 
566
- private onHandleMouseMove = (event: MouseEvent) => {
567
- if (!this.selectedShape) return
590
+ private onHandleMouseMove = (event: MouseEvent | TouchEvent) => {
591
+ if (!this.selectedShape) {
592
+ return
593
+ }
594
+
568
595
  const { x, y } = this.getSvgPoint(event)
569
596
 
570
597
  switch (this.selectedShape.type) {
571
- case 'text':
598
+ // case 'text':
599
+ // this.selectedShape.width = Math.abs(x - this.selectedShape.x!)
600
+ // this.selectedShape.height = Math.abs(y - this.selectedShape.y!)
601
+ // if (x < this.selectedShape.x!) this.selectedShape.x = x
602
+ // if (y < this.selectedShape.y!) this.selectedShape.y = y
603
+ // break
572
604
  case 'rectangle':
573
- this.selectedShape.width = Math.abs(x - this.selectedShape.x!)
574
- this.selectedShape.height = Math.abs(y - this.selectedShape.y!)
575
- if (x < this.selectedShape.x!) this.selectedShape.x = x
576
- if (y < this.selectedShape.y!) this.selectedShape.y = y
605
+ const startX = this.selectedShape.x!
606
+ const startY = this.selectedShape.y!
607
+ const width = this.selectedShape.width!
608
+ const height = this.selectedShape.height!
609
+
610
+ // 핸들의 좌표에 따라 너비 및 높이 조정
611
+ if (this.offsetX === startX && this.offsetY === startY) {
612
+ // 상좌 핸들 (왼쪽 위)
613
+ this.selectedShape.x = x
614
+ this.selectedShape.y = y
615
+ this.selectedShape.width = width + (startX - x)
616
+ this.selectedShape.height = height + (startY - y)
617
+ } else if (this.offsetX === startX + width && this.offsetY === startY) {
618
+ // 상우 핸들 (오른쪽 위)
619
+ this.selectedShape.y = y
620
+ this.selectedShape.width = Math.abs(x - startX)
621
+ this.selectedShape.height = height + (startY - y)
622
+ } else if (this.offsetX === startX && this.offsetY === startY + height) {
623
+ // 하좌 핸들 (왼쪽 아래)
624
+ this.selectedShape.x = x
625
+ this.selectedShape.width = width + (startX - x)
626
+ this.selectedShape.height = Math.abs(y - startY)
627
+ } else if (this.offsetX === startX + width && this.offsetY === startY + height) {
628
+ // 하우 핸들 (오른쪽 아래)
629
+ this.selectedShape.width = Math.abs(x - startX)
630
+ this.selectedShape.height = Math.abs(y - startY)
631
+ }
632
+
633
+ // 최소 너비 및 높이 유지
634
+ this.selectedShape.width = Math.max(10, this.selectedShape.width!)
635
+ this.selectedShape.height = Math.max(10, this.selectedShape.height!)
636
+
637
+ this.offsetX = x
638
+ this.offsetY = y
639
+
577
640
  break
578
641
  case 'circle':
579
642
  const dx = x - this.selectedShape.cx!
@@ -607,10 +670,17 @@ export class OxImageMarker extends BaseImageMarker {
607
670
  this.requestUpdate()
608
671
  }
609
672
 
610
- private onHandleMouseUp = () => {
673
+ private onHandleMouseUp = (event: MouseEvent | TouchEvent) => {
611
674
  const svg = this.svg as SVGSVGElement
612
- svg.removeEventListener('mousemove', this.onHandleMouseMove)
613
- svg.removeEventListener('mouseup', this.onHandleMouseUp)
675
+
676
+ if ('touches' in event) {
677
+ svg.removeEventListener('touchmove', this.onHandleMouseMove)
678
+ svg.removeEventListener('touchend', this.onHandleMouseUp)
679
+ } else {
680
+ svg.removeEventListener('mousemove', this.onHandleMouseMove)
681
+ svg.removeEventListener('mouseup', this.onHandleMouseUp)
682
+ }
683
+
614
684
  this.selectedShape = null
615
685
  }
616
686
 
@@ -623,23 +693,18 @@ export class OxImageMarker extends BaseImageMarker {
623
693
  }
624
694
 
625
695
  private showTextInput(textElement: SVGTextElement, shape: Shape | null = null, options?: { x: number; y: number }) {
626
- // input 요소 생성
627
696
  const input: HTMLInputElement = document.createElement('input')
628
697
  input.style.position = 'absolute'
629
698
 
630
- // SVG 텍스트의 CTM 얻기
631
699
  const ctm = textElement.getScreenCTM() || { a: 1, e: 0, d: 1, f: 0 }
632
700
  const bbox = textElement.getBBox()
633
701
 
634
- // 텍스트의 화면 위치 계산
635
702
  const screenX = bbox.x * ctm.a + ctm.e
636
703
  const screenY = bbox.y * ctm.d + ctm.f
637
704
 
638
- // input 위치 조정 (scale과 transform을 고려한 정확한 좌표로 변환)
639
705
  input.style.left = `${screenX}px`
640
706
  input.style.top = `${screenY}px`
641
707
 
642
- // 텍스트 폰트 스타일을 가져와 input에 적용
643
708
  const computedStyle = window.getComputedStyle(textElement)
644
709
  const fontSize = parseFloat(computedStyle.fontSize) * ctm.a
645
710
  const fontFamily = computedStyle.fontFamily
@@ -647,7 +712,6 @@ export class OxImageMarker extends BaseImageMarker {
647
712
  const textColor = computedStyle.fill || computedStyle.color
648
713
  const lineHeight = parseFloat(computedStyle.lineHeight) * ctm.d
649
714
 
650
- // input 스타일을 텍스트와 일치시킴
651
715
  input.style.fontSize = `${fontSize}px`
652
716
  input.style.fontFamily = fontFamily
653
717
  input.style.fontWeight = fontWeight
@@ -663,15 +727,12 @@ export class OxImageMarker extends BaseImageMarker {
663
727
  input.value = shape.text || ''
664
728
  }
665
729
 
666
- // SVG 텍스트 숨김
667
730
  textElement.style.visibility = 'hidden'
668
731
 
669
- // input 요소를 추가하고, 텍스트 길이에 따라 너비 조정
670
732
  document.body.appendChild(input)
671
- this.adjustInputWidth(input) // 최초 너비 조정
733
+ this.adjustInputWidth(input)
672
734
  requestAnimationFrame(() => input.focus())
673
735
 
674
- // 입력 중에 동적으로 input의 너비 조정
675
736
  input.addEventListener('input', () => {
676
737
  this.adjustInputWidth(input)
677
738
  })
@@ -733,14 +794,12 @@ export class OxImageMarker extends BaseImageMarker {
733
794
  })
734
795
  }
735
796
 
736
- // 텍스트 길이에 맞춰 input 박스의 너비를 조정하는 함수 (최소 길이 유지)
737
797
  private adjustInputWidth(input: HTMLInputElement) {
738
- const minWidth = 50 // 최소 너비 (픽셀 단위로 설정)
798
+ const minWidth = 50
739
799
  const textWidth = this.measureTextWidth(input.value || input.placeholder, input)
740
- input.style.width = `${Math.max(textWidth + 10, minWidth)}px` // 텍스트 길이 + 여유 공간, 최소 너비 적용
800
+ input.style.width = `${Math.max(textWidth + 10, minWidth)}px`
741
801
  }
742
802
 
743
- // 텍스트 길이를 측정하는 함수
744
803
  private measureTextWidth(text: string, input: HTMLInputElement): number {
745
804
  const canvas = document.createElement('canvas')
746
805
  const context = canvas.getContext('2d')!
@@ -748,4 +807,26 @@ export class OxImageMarker extends BaseImageMarker {
748
807
 
749
808
  return context.measureText(text).width
750
809
  }
810
+
811
+ private onFocus = () => {
812
+ this.addEventListener('keydown', this.handleKeyDown)
813
+ }
814
+
815
+ private onBlur = () => {
816
+ this.removeEventListener('keydown', this.handleKeyDown)
817
+ }
818
+
819
+ private handleKeyDown = (event: KeyboardEvent) => {
820
+ if ((event.key === 'Delete' || event.key === 'Backspace') && this.selectedShape) {
821
+ this.deleteSelectedShape()
822
+ }
823
+ }
824
+
825
+ private deleteSelectedShape() {
826
+ if (this.selectedShape) {
827
+ this.shapes = this.shapes.filter(s => s.id !== this.selectedShape!.id)
828
+ this.selectedShape = null
829
+ this.requestUpdate()
830
+ }
831
+ }
751
832
  }
@@ -55,6 +55,22 @@ const Template: Story<ArgTypes> = ({ imageUrl, shapes = [] }: ArgTypes) => html`
55
55
  </style>
56
56
 
57
57
  <style>
58
+ html,
59
+ body {
60
+ margin: 0;
61
+ padding: 0;
62
+ overflow: hidden;
63
+
64
+ overscroll-behavior-y: none;
65
+
66
+ /* This is a font-stack that tries to use the system-default sans-serifs first */
67
+ font-family: Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
68
+ line-height: 1.5;
69
+ -webkit-font-smoothing: antialiased;
70
+
71
+ accent-color: var(--md-sys-color-primary);
72
+ }
73
+
58
74
  body {
59
75
  width: 100%;
60
76
  height: 800px;