@operato/mini-map 7.1.1 → 8.0.0-alpha.29

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.
@@ -44,8 +44,8 @@ class OxMiniMapComponent extends LitElement {
44
44
  connectedCallback() {
45
45
  super.connectedCallback()
46
46
  window.addEventListener('resize', this.handleResize.bind(this))
47
- this.addEventListener('mousemove', this.handleMouseMove.bind(this))
48
- this.addEventListener('mouseup', this.handleMouseUp.bind(this))
47
+ this.addEventListener('pointermove', this.handlePointerMove.bind(this))
48
+ this.addEventListener('pointerup', this.handlePointerUp.bind(this))
49
49
  if (this.target) {
50
50
  this.observeTarget(this.target)
51
51
  }
@@ -54,8 +54,8 @@ class OxMiniMapComponent extends LitElement {
54
54
  disconnectedCallback() {
55
55
  super.disconnectedCallback()
56
56
  window.removeEventListener('resize', this.handleResize.bind(this))
57
- window.removeEventListener('mousemove', this.handleMouseMove.bind(this))
58
- window.removeEventListener('mouseup', this.handleMouseUp.bind(this))
57
+ window.removeEventListener('pointermove', this.handlePointerMove.bind(this))
58
+ window.removeEventListener('pointerup', this.handlePointerUp.bind(this))
59
59
  if (this.observer) {
60
60
  this.observer.disconnect()
61
61
  this.observer = null
@@ -154,7 +154,7 @@ class OxMiniMapComponent extends LitElement {
154
154
  viewport.style.transform = `scale(${scale}) translate(${left * scaleMinimap}px, ${top * scaleMinimap}px)`
155
155
  }
156
156
 
157
- handleMouseDown(event: MouseEvent) {
157
+ handlePointerDown(event: MouseEvent) {
158
158
  this.isDragging = true
159
159
  this.startX = event.clientX
160
160
  this.startY = event.clientY
@@ -165,13 +165,13 @@ class OxMiniMapComponent extends LitElement {
165
165
  this.initialScrollTop = target.scrollTop
166
166
  }
167
167
 
168
- window.addEventListener('mousemove', this.handleMouseMove.bind(this))
169
- window.addEventListener('mouseup', this.handleMouseUp.bind(this))
168
+ window.addEventListener('pointermove', this.handlePointerMove.bind(this))
169
+ window.addEventListener('pointerup', this.handlePointerUp.bind(this))
170
170
 
171
171
  event.preventDefault()
172
172
  }
173
173
 
174
- handleMouseMove(event: MouseEvent) {
174
+ handlePointerMove(event: MouseEvent) {
175
175
  if (!this.isDragging) return
176
176
 
177
177
  const deltaX = event.clientX - this.startX
@@ -197,17 +197,17 @@ class OxMiniMapComponent extends LitElement {
197
197
  event.preventDefault()
198
198
  }
199
199
 
200
- handleMouseUp(event: MouseEvent) {
200
+ handlePointerUp(event: MouseEvent) {
201
201
  this.isDragging = false
202
- window.removeEventListener('mousemove', this.handleMouseMove.bind(this))
203
- window.removeEventListener('mouseup', this.handleMouseUp.bind(this))
202
+ window.removeEventListener('pointermove', this.handlePointerMove.bind(this))
203
+ window.removeEventListener('pointerup', this.handlePointerUp.bind(this))
204
204
  event.preventDefault()
205
205
  }
206
206
 
207
207
  render() {
208
208
  return html`
209
209
  <div id="minimap">
210
- <div id="viewport" @mousedown=${this.handleMouseDown}></div>
210
+ <div id="viewport" @pointerdown=${this.handlePointerDown}></div>
211
211
  </div>
212
212
  `
213
213
  }
@@ -1,16 +1,7 @@
1
1
  import { css, LitElement, PropertyValues } from 'lit'
2
2
  import { customElement, property, state } from 'lit/decorators.js'
3
3
 
4
- // Utility function to get the point from MouseEvent or TouchEvent
5
- function getPoint(e: MouseEvent | TouchEvent): { x: number; y: number } | undefined {
6
- if (e instanceof MouseEvent && e.button === 0) {
7
- return { x: e.clientX, y: e.clientY }
8
- } else if (e instanceof TouchEvent && e.touches.length === 1) {
9
- const touch = e.touches[0]
10
- return { x: touch.clientX, y: touch.clientY }
11
- }
12
- return undefined
13
- }
4
+ import { gesture, GestureEventDoubleTap, GestureEventDrag, GestureEventPinch } from '@operato/utils/gesture-mixin.js'
14
5
 
15
6
  /**
16
7
  * Example usage:
@@ -34,13 +25,13 @@ function getPoint(e: MouseEvent | TouchEvent): { x: number; y: number } | undefi
34
25
  * </style>
35
26
  * </head>
36
27
  * <body>
37
- * <ox-zoomable-image src="path/to/your/image.jpg"></ox-zoomable-image>
28
+ * <ox-zoomable-image src="path/to/your/image.jpg" restrict-boundary></ox-zoomable-image>
38
29
  * </body>
39
30
  * </html>
40
31
  */
41
32
 
42
33
  @customElement('ox-zoomable-image')
43
- export class OxZoomableImage extends LitElement {
34
+ export class OxZoomableImage extends gesture(LitElement) {
44
35
  // Define CSS styles for the component
45
36
  static styles = css`
46
37
  :host {
@@ -49,16 +40,20 @@ export class OxZoomableImage extends LitElement {
49
40
  width: 100%;
50
41
  height: 100%;
51
42
  overflow: hidden;
43
+
44
+ touch-action: none;
52
45
  }
53
46
 
54
47
  :host::before {
55
48
  content: '';
56
49
  display: block;
50
+
57
51
  position: absolute;
58
52
  top: 0;
59
53
  right: 0;
60
54
  width: 100%;
61
55
  height: 100%;
56
+
62
57
  background-image: var(
63
58
  --background-image,
64
59
  linear-gradient(var(--primary-color, lightgray), var(--primary-color, lightgray))
@@ -67,56 +62,38 @@ export class OxZoomableImage extends LitElement {
67
62
  background-repeat: no-repeat;
68
63
  background-position: center;
69
64
  background-size: contain;
65
+
66
+ touch-action: none;
70
67
  }
71
68
  `
72
69
 
73
70
  @property({ type: String }) src?: string // Source URL for the image
71
+ @property({ type: Boolean, attribute: 'restrict-boundary' }) restrictBoundary: boolean = false // Ensure the image stays within bounds
72
+
74
73
  @state() private zoom = 1 // Current zoom level
75
74
  @state() private left = 0 // Horizontal position
76
75
  @state() private top = 0 // Vertical position
77
- @state() private minZoom = 1 // Minimum zoom level
78
76
  @state() private imageWidth = 0
79
77
  @state() private imageHeight = 0
80
78
 
81
- private dragStart?: { x: number; y: number } // Starting point of drag
82
-
83
- // Event listeners bound to the component
84
- private readonly dragEndHandler = this.onDragEnd.bind(this) as EventListener
85
- private readonly dragMoveHandler = this.onDragMove.bind(this) as EventListener
86
- private readonly dragStartHandler = this.onDragStart.bind(this) as EventListener
87
- private readonly wheelEventHandler = this.onWheelEvent.bind(this) as EventListener
88
- private readonly resetHandler = this.onReset.bind(this) as EventListener
89
-
90
79
  connectedCallback(): void {
91
80
  super.connectedCallback()
92
81
 
93
82
  // Register event listeners for zooming and dragging
94
- this.addEventListener('wheel', this.wheelEventHandler)
95
- this.addEventListener('mousedown', this.dragStartHandler)
96
- this.addEventListener('touchstart', this.dragStartHandler)
97
- this.addEventListener('dblclick', this.resetHandler)
98
-
99
- document.addEventListener('mouseup', this.dragEndHandler)
100
- document.addEventListener('touchend', this.dragEndHandler)
101
- document.addEventListener('touchcancel', this.dragEndHandler)
102
- document.addEventListener('mousemove', this.dragMoveHandler)
103
- document.addEventListener('touchmove', this.dragMoveHandler)
83
+ this.addEventListener('pinch', this.pinchHandler as EventListener)
84
+ this.addEventListener('drag', this.dragHandler as EventListener)
85
+ this.addEventListener('doubletap', this.doubleTapHandler as EventListener)
86
+ this.addEventListener('wheel', this.wheelHandler as EventListener)
104
87
 
105
88
  this.calculateMinZoom() // Calculate the minimum zoom level based on the image and container size
106
89
  }
107
90
 
108
91
  disconnectedCallback(): void {
109
92
  // Remove event listeners when the component is disconnected
110
- this.removeEventListener('wheel', this.wheelEventHandler)
111
- this.removeEventListener('mousedown', this.dragStartHandler)
112
- this.removeEventListener('touchstart', this.dragStartHandler)
113
- this.removeEventListener('dblclick', this.resetHandler)
114
-
115
- document.removeEventListener('mouseup', this.dragEndHandler)
116
- document.removeEventListener('touchend', this.dragEndHandler)
117
- document.removeEventListener('touchcancel', this.dragEndHandler)
118
- document.removeEventListener('mousemove', this.dragMoveHandler)
119
- document.removeEventListener('touchmove', this.dragMoveHandler)
93
+ this.removeEventListener('pinch', this.pinchHandler as EventListener)
94
+ this.removeEventListener('drag', this.dragHandler as EventListener)
95
+ this.removeEventListener('doubletap', this.doubleTapHandler as EventListener)
96
+ this.removeEventListener('wheel', this.wheelHandler as EventListener)
120
97
 
121
98
  super.disconnectedCallback()
122
99
  }
@@ -127,6 +104,7 @@ export class OxZoomableImage extends LitElement {
127
104
  this.style.setProperty('--background-image', `url('${this.src}')`)
128
105
  this.calculateMinZoom() // Recalculate the minimum zoom level when the source changes
129
106
  }
107
+
130
108
  this.updateTransform() // Apply the current zoom and position
131
109
  }
132
110
 
@@ -138,88 +116,69 @@ export class OxZoomableImage extends LitElement {
138
116
  this.imageWidth = img.width
139
117
  this.imageHeight = img.height
140
118
 
141
- const aspectRatio = img.width / img.height
142
- const hostAspectRatio = this.offsetWidth / this.offsetHeight
143
- if (aspectRatio < hostAspectRatio) {
144
- this.minZoom = this.offsetHeight / img.height
145
- } else {
146
- this.minZoom = this.offsetWidth / img.width
147
- }
119
+ console.log(this.imageWidth, this.imageHeight)
120
+
148
121
  this.zoom = 1 // this.minZoom
149
122
  this.left = 0 // Center the image
150
123
  this.top = 0 // Center the image
124
+
151
125
  this.updateTransform()
152
126
  }
153
127
  }
154
128
 
155
- private onWheelEvent(e: WheelEvent): void {
156
- // Handle zoom in and out based on mouse wheel events
157
- e.preventDefault()
158
- const delta = Math.sign(e.deltaY)
159
- const newZoom = Math.max(1, this.zoom * (1 - delta / 20))
160
- const zoomFactor = newZoom / this.zoom
161
-
162
- const rect = this.getBoundingClientRect()
163
- const offsetX = e.clientX - rect.left - rect.width / 2
164
- const offsetY = e.clientY - rect.top - rect.height / 2
129
+ private pinchHandler = (e: GestureEventPinch) => {
130
+ const { scale, centerX, centerY } = e.detail
131
+ this.zoomAt(centerX, centerY, scale)
132
+ }
165
133
 
166
- // Adjust the position to zoom in/out relative to the center of the client rect
167
- this.left -= offsetX * (zoomFactor - 1)
168
- this.top -= offsetY * (zoomFactor - 1)
134
+ private zoomAt(clientX: number, clientY: number, scale: number) {
135
+ const { offsetLeft: left, offsetTop: top, offsetWidth: width, offsetHeight: height } = this
169
136
 
170
- this.zoom = newZoom
171
- this.applyBounds() // Ensure the image stays within bounds
172
- this.updateTransform() // Apply the new zoom and position
173
- }
137
+ const centerX = left + width / 2
138
+ const centerY = top + height / 2
174
139
 
175
- private onDragStart(e: MouseEvent | TouchEvent): void {
176
- // Start dragging the image
177
- const point = getPoint(e)
178
- if (point) {
179
- this.dragStart = point
180
- e.stopPropagation()
181
- }
182
- }
140
+ // this.imageWidth
141
+ // this.imageHeight
142
+ const dx = ((centerX - clientX) / width) * 100 * (1 - scale)
143
+ const dy = ((centerY - clientY) / height) * 100 * (1 - scale)
183
144
 
184
- private onDragMove(e: MouseEvent | TouchEvent): void {
185
- // Move the image based on drag events
186
- if (!this.dragStart) return
145
+ this.left += dx
146
+ this.top += dy
187
147
 
188
- const point = getPoint(e)
189
- if (!point) return
148
+ this.zoom *= scale
149
+ }
190
150
 
191
- e.stopPropagation()
151
+ private wheelHandler = (e: WheelEvent): void => {
152
+ // Handle zoom in and out based on mouse wheel events
192
153
  e.preventDefault()
193
154
 
194
- const { x, y } = point
195
- this.left += x - this.dragStart.x
196
- this.top += y - this.dragStart.y
197
-
198
- this.dragStart = point
199
- this.applyBounds() // Ensure the image stays within bounds
200
- this.updateTransform() // Apply the new position
155
+ const delta = Math.sign(e.deltaY)
156
+ this.zoomAt(e.clientX, e.clientY, 1 - delta / 20)
201
157
  }
202
158
 
203
- private onDragEnd(e: Event): void {
204
- // End the drag operation
205
- if (this.dragStart) {
206
- e.stopPropagation()
207
- e.preventDefault()
208
- this.dragStart = undefined
209
- }
159
+ private dragHandler = (e: GestureEventDrag): void => {
160
+ const { deltaX, deltaY } = e.detail
161
+
162
+ this.top += deltaY
163
+ this.left += deltaX
164
+
165
+ this.updateTransform()
210
166
  }
211
167
 
212
- private onReset(e: Event): void {
168
+ private doubleTapHandler = (e: GestureEventDoubleTap): void => {
213
169
  // Reset zoom and position to initial state
214
170
  this.zoom = 1
215
171
  this.left = 0
216
172
  this.top = 0
217
- this.updateTransform()
218
173
  }
219
174
 
220
175
  private updateTransform() {
176
+ if (this.restrictBoundary) {
177
+ this.applyBounds() // Ensure the image stays within bounds
178
+ }
179
+
221
180
  // Apply the current zoom and position to the image
222
- this.style.setProperty('--image-transform', `scale(${this.zoom}) translate(${this.left}px, ${this.top}px)`)
181
+ this.style.setProperty('--image-transform', `translate(${this.left}px, ${this.top}px) scale(${this.zoom})`)
223
182
  }
224
183
 
225
184
  private applyBounds() {
@@ -232,21 +191,21 @@ export class OxZoomableImage extends LitElement {
232
191
 
233
192
  if (aspectRatio < hostAspectRatio) {
234
193
  /* image ratio is wider than host */
235
- var minX = Math.min(0, (-hostWidth * (this.zoom - 1)) / 2 / this.zoom)
194
+ var minX = Math.min(0, (-hostWidth * (1 - this.zoom)) / 2)
236
195
  var maxX = -minX
237
196
 
238
- var minY = (hostHeight - hostWidth * aspectRatio * this.zoom) / 2 / this.zoom
197
+ var minY = (hostHeight - hostWidth * aspectRatio * this.zoom) / 2
239
198
  minY = Math.min(minY, -minY)
240
199
  var maxY = -minY
241
200
 
242
201
  this.left = Math.max(minX, Math.min(this.left, maxX))
243
202
  this.top = Math.max(minY, Math.min(this.top, maxY))
244
203
  } else {
245
- var minX = (hostWidth - (hostHeight / aspectRatio) * this.zoom) / 2 / this.zoom
204
+ var minX = (hostWidth - (hostHeight / aspectRatio) * this.zoom) / 2
246
205
  minX = Math.min(minX, -minX)
247
206
  var maxX = -minX
248
207
 
249
- var minY = Math.min(0, (-hostHeight * (this.zoom - 1)) / 2 / this.zoom)
208
+ var minY = Math.min(0, (-hostHeight * (1 - this.zoom)) / 2)
250
209
  var maxY = -minY
251
210
 
252
211
  this.left = Math.max(minX, Math.min(this.left, maxX))
@@ -7,7 +7,8 @@ export default {
7
7
  argTypes: {
8
8
  src: { control: 'text' },
9
9
  width: { control: 'number' },
10
- height: { control: 'number' }
10
+ height: { control: 'number' },
11
+ restrictBoundary: { control: 'boolean' }
11
12
  }
12
13
  }
13
14
 
@@ -21,9 +22,10 @@ interface OxZoomableImageProps {
21
22
  src: string
22
23
  width: number
23
24
  height: number
25
+ restrictBoundary: boolean
24
26
  }
25
27
 
26
- const Template: Story<OxZoomableImageProps> = ({ src, width, height }: OxZoomableImageProps) => html`
28
+ const Template: Story<OxZoomableImageProps> = ({ src, width, height, restrictBoundary }: OxZoomableImageProps) => html`
27
29
  <style>
28
30
  ox-zoomable-image {
29
31
  width: ${width}px;
@@ -33,26 +35,29 @@ const Template: Story<OxZoomableImageProps> = ({ src, width, height }: OxZoomabl
33
35
  margin: 100px;
34
36
  }
35
37
  </style>
36
- <ox-zoomable-image src=${src}></ox-zoomable-image>
38
+ <ox-zoomable-image src=${src} ?restrict-boundary=${restrictBoundary}></ox-zoomable-image>
37
39
  `
38
40
 
39
41
  export const Default = Template.bind({})
40
42
  Default.args = {
41
43
  src: 'https://via.placeholder.com/800x600.png', // 기본 이미지 URL을 실제 경로로 변경하세요.
42
44
  width: 400,
43
- height: 600
45
+ height: 600,
46
+ restrictBoundary: true
44
47
  }
45
48
 
46
49
  export const Wide = Template.bind({})
47
50
  Wide.args = {
48
51
  src: 'https://via.placeholder.com/800x600.png', // 기본 이미지 URL을 실제 경로로 변경하세요.
49
52
  width: 600,
50
- height: 300
53
+ height: 300,
54
+ restrictBoundary: true
51
55
  }
52
56
 
53
57
  export const CustomImage = Template.bind({})
54
58
  CustomImage.args = {
55
59
  src: 'https://via.placeholder.com/1200x800.png', // 다른 테스트 이미지 URL을 실제 경로로 변경하세요.
56
60
  width: 400,
57
- height: 600
61
+ height: 600,
62
+ restrictBoundary: true
58
63
  }