@operato/mini-map 8.0.0 → 9.0.0-beta.10

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.
@@ -1,17 +1,7 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { css, LitElement } from 'lit';
3
3
  import { customElement, property, state } from 'lit/decorators.js';
4
- // Utility function to get the point from MouseEvent or TouchEvent
5
- function getPoint(e) {
6
- if (e instanceof MouseEvent && e.button === 0) {
7
- return { x: e.clientX, y: e.clientY };
8
- }
9
- else if (e instanceof TouchEvent && e.touches.length === 1) {
10
- const touch = e.touches[0];
11
- return { x: touch.clientX, y: touch.clientY };
12
- }
13
- return undefined;
14
- }
4
+ import { gesture } from '@operato/utils/gesture-mixin.js';
15
5
  /**
16
6
  * Example usage:
17
7
  *
@@ -34,51 +24,113 @@ function getPoint(e) {
34
24
  * </style>
35
25
  * </head>
36
26
  * <body>
37
- * <ox-zoomable-image src="path/to/your/image.jpg"></ox-zoomable-image>
27
+ * <ox-zoomable-image src="path/to/your/image.jpg" restrict-boundary></ox-zoomable-image>
38
28
  * </body>
39
29
  * </html>
40
30
  */
41
- let OxZoomableImage = class OxZoomableImage extends LitElement {
31
+ let OxZoomableImage = class OxZoomableImage extends gesture(LitElement) {
42
32
  constructor() {
43
33
  super(...arguments);
44
- this.zoom = 1; // Current zoom level
45
- this.left = 0; // Horizontal position
46
- this.top = 0; // Vertical position
47
- this.minZoom = 1; // Minimum zoom level
48
- this.imageWidth = 0;
49
- this.imageHeight = 0;
50
- // Event listeners bound to the component
51
- this.dragEndHandler = this.onDragEnd.bind(this);
52
- this.dragMoveHandler = this.onDragMove.bind(this);
53
- this.dragStartHandler = this.onDragStart.bind(this);
54
- this.wheelEventHandler = this.onWheelEvent.bind(this);
55
- this.resetHandler = this.onReset.bind(this);
34
+ Object.defineProperty(this, "src", {
35
+ enumerable: true,
36
+ configurable: true,
37
+ writable: true,
38
+ value: void 0
39
+ }); // Source URL for the image
40
+ Object.defineProperty(this, "restrictBoundary", {
41
+ enumerable: true,
42
+ configurable: true,
43
+ writable: true,
44
+ value: false
45
+ }); // Ensure the image stays within bounds
46
+ Object.defineProperty(this, "zoom", {
47
+ enumerable: true,
48
+ configurable: true,
49
+ writable: true,
50
+ value: 1
51
+ }); // Current zoom level
52
+ Object.defineProperty(this, "left", {
53
+ enumerable: true,
54
+ configurable: true,
55
+ writable: true,
56
+ value: 0
57
+ }); // Horizontal position
58
+ Object.defineProperty(this, "top", {
59
+ enumerable: true,
60
+ configurable: true,
61
+ writable: true,
62
+ value: 0
63
+ }); // Vertical position
64
+ Object.defineProperty(this, "imageWidth", {
65
+ enumerable: true,
66
+ configurable: true,
67
+ writable: true,
68
+ value: 0
69
+ });
70
+ Object.defineProperty(this, "imageHeight", {
71
+ enumerable: true,
72
+ configurable: true,
73
+ writable: true,
74
+ value: 0
75
+ });
76
+ Object.defineProperty(this, "pinchHandler", {
77
+ enumerable: true,
78
+ configurable: true,
79
+ writable: true,
80
+ value: (e) => {
81
+ const { scale, centerX, centerY } = e.detail;
82
+ this.zoomAt(centerX, centerY, scale);
83
+ }
84
+ });
85
+ Object.defineProperty(this, "wheelHandler", {
86
+ enumerable: true,
87
+ configurable: true,
88
+ writable: true,
89
+ value: (e) => {
90
+ // Handle zoom in and out based on mouse wheel events
91
+ e.preventDefault();
92
+ const delta = Math.sign(e.deltaY);
93
+ this.zoomAt(e.clientX, e.clientY, 1 - delta / 20);
94
+ }
95
+ });
96
+ Object.defineProperty(this, "dragHandler", {
97
+ enumerable: true,
98
+ configurable: true,
99
+ writable: true,
100
+ value: (e) => {
101
+ const { deltaX, deltaY } = e.detail;
102
+ this.top += deltaY;
103
+ this.left += deltaX;
104
+ this.updateTransform();
105
+ }
106
+ });
107
+ Object.defineProperty(this, "doubleTapHandler", {
108
+ enumerable: true,
109
+ configurable: true,
110
+ writable: true,
111
+ value: (e) => {
112
+ // Reset zoom and position to initial state
113
+ this.zoom = 1;
114
+ this.left = 0;
115
+ this.top = 0;
116
+ }
117
+ });
56
118
  }
57
119
  connectedCallback() {
58
120
  super.connectedCallback();
59
121
  // Register event listeners for zooming and dragging
60
- this.addEventListener('wheel', this.wheelEventHandler);
61
- this.addEventListener('mousedown', this.dragStartHandler);
62
- this.addEventListener('touchstart', this.dragStartHandler);
63
- this.addEventListener('dblclick', this.resetHandler);
64
- document.addEventListener('mouseup', this.dragEndHandler);
65
- document.addEventListener('touchend', this.dragEndHandler);
66
- document.addEventListener('touchcancel', this.dragEndHandler);
67
- document.addEventListener('mousemove', this.dragMoveHandler);
68
- document.addEventListener('touchmove', this.dragMoveHandler);
122
+ this.addEventListener('pinch', this.pinchHandler);
123
+ this.addEventListener('drag', this.dragHandler);
124
+ this.addEventListener('doubletap', this.doubleTapHandler);
125
+ this.addEventListener('wheel', this.wheelHandler);
69
126
  this.calculateMinZoom(); // Calculate the minimum zoom level based on the image and container size
70
127
  }
71
128
  disconnectedCallback() {
72
129
  // Remove event listeners when the component is disconnected
73
- this.removeEventListener('wheel', this.wheelEventHandler);
74
- this.removeEventListener('mousedown', this.dragStartHandler);
75
- this.removeEventListener('touchstart', this.dragStartHandler);
76
- this.removeEventListener('dblclick', this.resetHandler);
77
- document.removeEventListener('mouseup', this.dragEndHandler);
78
- document.removeEventListener('touchend', this.dragEndHandler);
79
- document.removeEventListener('touchcancel', this.dragEndHandler);
80
- document.removeEventListener('mousemove', this.dragMoveHandler);
81
- document.removeEventListener('touchmove', this.dragMoveHandler);
130
+ this.removeEventListener('pinch', this.pinchHandler);
131
+ this.removeEventListener('drag', this.dragHandler);
132
+ this.removeEventListener('doubletap', this.doubleTapHandler);
133
+ this.removeEventListener('wheel', this.wheelHandler);
82
134
  super.disconnectedCallback();
83
135
  }
84
136
  updated(changes) {
@@ -96,78 +148,31 @@ let OxZoomableImage = class OxZoomableImage extends LitElement {
96
148
  img.onload = () => {
97
149
  this.imageWidth = img.width;
98
150
  this.imageHeight = img.height;
99
- const aspectRatio = img.width / img.height;
100
- const hostAspectRatio = this.offsetWidth / this.offsetHeight;
101
- if (aspectRatio < hostAspectRatio) {
102
- this.minZoom = this.offsetHeight / img.height;
103
- }
104
- else {
105
- this.minZoom = this.offsetWidth / img.width;
106
- }
151
+ console.log(this.imageWidth, this.imageHeight);
107
152
  this.zoom = 1; // this.minZoom
108
153
  this.left = 0; // Center the image
109
154
  this.top = 0; // Center the image
110
155
  this.updateTransform();
111
156
  };
112
157
  }
113
- onWheelEvent(e) {
114
- // Handle zoom in and out based on mouse wheel events
115
- e.preventDefault();
116
- const delta = Math.sign(e.deltaY);
117
- const newZoom = Math.max(1, this.zoom * (1 - delta / 20));
118
- const zoomFactor = newZoom / this.zoom;
119
- const rect = this.getBoundingClientRect();
120
- const offsetX = e.clientX - rect.left - rect.width / 2;
121
- const offsetY = e.clientY - rect.top - rect.height / 2;
122
- // Adjust the position to zoom in/out relative to the center of the client rect
123
- this.left -= offsetX * (zoomFactor - 1);
124
- this.top -= offsetY * (zoomFactor - 1);
125
- this.zoom = newZoom;
126
- this.applyBounds(); // Ensure the image stays within bounds
127
- this.updateTransform(); // Apply the new zoom and position
128
- }
129
- onDragStart(e) {
130
- // Start dragging the image
131
- const point = getPoint(e);
132
- if (point) {
133
- this.dragStart = point;
134
- e.stopPropagation();
135
- }
136
- }
137
- onDragMove(e) {
138
- // Move the image based on drag events
139
- if (!this.dragStart)
140
- return;
141
- const point = getPoint(e);
142
- if (!point)
143
- return;
144
- e.stopPropagation();
145
- e.preventDefault();
146
- const { x, y } = point;
147
- this.left += x - this.dragStart.x;
148
- this.top += y - this.dragStart.y;
149
- this.dragStart = point;
150
- this.applyBounds(); // Ensure the image stays within bounds
151
- this.updateTransform(); // Apply the new position
152
- }
153
- onDragEnd(e) {
154
- // End the drag operation
155
- if (this.dragStart) {
156
- e.stopPropagation();
157
- e.preventDefault();
158
- this.dragStart = undefined;
159
- }
160
- }
161
- onReset(e) {
162
- // Reset zoom and position to initial state
163
- this.zoom = 1;
164
- this.left = 0;
165
- this.top = 0;
166
- this.updateTransform();
158
+ zoomAt(clientX, clientY, scale) {
159
+ const { offsetLeft: left, offsetTop: top, offsetWidth: width, offsetHeight: height } = this;
160
+ const centerX = left + width / 2;
161
+ const centerY = top + height / 2;
162
+ // this.imageWidth
163
+ // this.imageHeight
164
+ const dx = ((centerX - clientX) / width) * 100 * (1 - scale);
165
+ const dy = ((centerY - clientY) / height) * 100 * (1 - scale);
166
+ this.left += dx;
167
+ this.top += dy;
168
+ this.zoom *= scale;
167
169
  }
168
170
  updateTransform() {
171
+ if (this.restrictBoundary) {
172
+ this.applyBounds(); // Ensure the image stays within bounds
173
+ }
169
174
  // Apply the current zoom and position to the image
170
- this.style.setProperty('--image-transform', `scale(${this.zoom}) translate(${this.left}px, ${this.top}px)`);
175
+ this.style.setProperty('--image-transform', `translate(${this.left}px, ${this.top}px) scale(${this.zoom})`);
171
176
  }
172
177
  applyBounds() {
173
178
  // Ensure the image stays within the bounds of the container
@@ -180,14 +185,14 @@ let OxZoomableImage = class OxZoomableImage extends LitElement {
180
185
  var minX = (hostWidth * (1 - this.zoom)) / 2;
181
186
  minX = minX > 0 ? -minX : minX;
182
187
  var maxX = -minX;
183
- var minY = (hostHeight - hostWidth * aspectRatio * this.zoom) / 2 / this.zoom;
188
+ var minY = (hostHeight - hostWidth * aspectRatio * this.zoom) / 2;
184
189
  minY = Math.min(minY, -minY);
185
190
  var maxY = -minY;
186
191
  this.left = Math.max(minX, Math.min(this.left, maxX));
187
192
  this.top = Math.max(minY, Math.min(this.top, maxY));
188
193
  }
189
194
  else {
190
- var minX = (hostWidth - (hostHeight / aspectRatio) * this.zoom) / 2 / this.zoom;
195
+ var minX = (hostWidth - (hostHeight / aspectRatio) * this.zoom) / 2;
191
196
  minX = Math.min(minX, -minX);
192
197
  var maxX = -minX;
193
198
  var minY = (-hostHeight * (1 - this.zoom)) / 2;
@@ -199,23 +204,31 @@ let OxZoomableImage = class OxZoomableImage extends LitElement {
199
204
  }
200
205
  };
201
206
  // Define CSS styles for the component
202
- OxZoomableImage.styles = css `
207
+ Object.defineProperty(OxZoomableImage, "styles", {
208
+ enumerable: true,
209
+ configurable: true,
210
+ writable: true,
211
+ value: css `
203
212
  :host {
204
213
  display: block;
205
214
  position: relative;
206
215
  width: 100%;
207
216
  height: 100%;
208
217
  overflow: hidden;
218
+
219
+ touch-action: none;
209
220
  }
210
221
 
211
222
  :host::before {
212
223
  content: '';
213
224
  display: block;
225
+
214
226
  position: absolute;
215
227
  top: 0;
216
228
  right: 0;
217
229
  width: 100%;
218
230
  height: 100%;
231
+
219
232
  background-image: var(
220
233
  --background-image,
221
234
  linear-gradient(var(--primary-color, lightgray), var(--primary-color, lightgray))
@@ -224,11 +237,17 @@ OxZoomableImage.styles = css `
224
237
  background-repeat: no-repeat;
225
238
  background-position: center;
226
239
  background-size: contain;
240
+
241
+ touch-action: none;
227
242
  }
228
- `;
243
+ `
244
+ });
229
245
  __decorate([
230
246
  property({ type: String })
231
247
  ], OxZoomableImage.prototype, "src", void 0);
248
+ __decorate([
249
+ property({ type: Boolean, attribute: 'restrict-boundary' })
250
+ ], OxZoomableImage.prototype, "restrictBoundary", void 0);
232
251
  __decorate([
233
252
  state()
234
253
  ], OxZoomableImage.prototype, "zoom", void 0);
@@ -238,9 +257,6 @@ __decorate([
238
257
  __decorate([
239
258
  state()
240
259
  ], OxZoomableImage.prototype, "top", void 0);
241
- __decorate([
242
- state()
243
- ], OxZoomableImage.prototype, "minZoom", void 0);
244
260
  __decorate([
245
261
  state()
246
262
  ], OxZoomableImage.prototype, "imageWidth", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"ox-zoomable-image.js","sourceRoot":"","sources":["../../src/ox-zoomable-image.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, LitElement, PropertyValues } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\n// Utility function to get the point from MouseEvent or TouchEvent\nfunction getPoint(e: MouseEvent | TouchEvent): { x: number; y: number } | undefined {\n if (e instanceof MouseEvent && e.button === 0) {\n return { x: e.clientX, y: e.clientY }\n } else if (e instanceof TouchEvent && e.touches.length === 1) {\n const touch = e.touches[0]\n return { x: touch.clientX, y: touch.clientY }\n }\n return undefined\n}\n\n/**\n * Example usage:\n *\n * <html>\n * <head>\n * <script type=\"module\" src=\"path/to/ox-zoomable-image.js\"></script>\n * <style>\n * body {\n * margin: 0;\n * overflow: hidden;\n * display: flex;\n * justify-content: center;\n * align-items: center;\n * height: 100vh;\n * }\n * ox-zoomable-image {\n * width: 80%;\n * height: 80%;\n * }\n * </style>\n * </head>\n * <body>\n * <ox-zoomable-image src=\"path/to/your/image.jpg\"></ox-zoomable-image>\n * </body>\n * </html>\n */\n\n@customElement('ox-zoomable-image')\nexport class OxZoomableImage extends LitElement {\n // Define CSS styles for the component\n static styles = css`\n :host {\n display: block;\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n\n :host::before {\n content: '';\n display: block;\n position: absolute;\n top: 0;\n right: 0;\n width: 100%;\n height: 100%;\n background-image: var(\n --background-image,\n linear-gradient(var(--primary-color, lightgray), var(--primary-color, lightgray))\n );\n transform: var(--image-transform, '');\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n }\n `\n\n @property({ type: String }) src?: string // Source URL for the image\n @state() private zoom = 1 // Current zoom level\n @state() private left = 0 // Horizontal position\n @state() private top = 0 // Vertical position\n @state() private minZoom = 1 // Minimum zoom level\n @state() private imageWidth = 0\n @state() private imageHeight = 0\n\n private dragStart?: { x: number; y: number } // Starting point of drag\n\n // Event listeners bound to the component\n private readonly dragEndHandler = this.onDragEnd.bind(this) as EventListener\n private readonly dragMoveHandler = this.onDragMove.bind(this) as EventListener\n private readonly dragStartHandler = this.onDragStart.bind(this) as EventListener\n private readonly wheelEventHandler = this.onWheelEvent.bind(this) as EventListener\n private readonly resetHandler = this.onReset.bind(this) as EventListener\n\n connectedCallback(): void {\n super.connectedCallback()\n\n // Register event listeners for zooming and dragging\n this.addEventListener('wheel', this.wheelEventHandler)\n this.addEventListener('mousedown', this.dragStartHandler)\n this.addEventListener('touchstart', this.dragStartHandler)\n this.addEventListener('dblclick', this.resetHandler)\n\n document.addEventListener('mouseup', this.dragEndHandler)\n document.addEventListener('touchend', this.dragEndHandler)\n document.addEventListener('touchcancel', this.dragEndHandler)\n document.addEventListener('mousemove', this.dragMoveHandler)\n document.addEventListener('touchmove', this.dragMoveHandler)\n\n this.calculateMinZoom() // Calculate the minimum zoom level based on the image and container size\n }\n\n disconnectedCallback(): void {\n // Remove event listeners when the component is disconnected\n this.removeEventListener('wheel', this.wheelEventHandler)\n this.removeEventListener('mousedown', this.dragStartHandler)\n this.removeEventListener('touchstart', this.dragStartHandler)\n this.removeEventListener('dblclick', this.resetHandler)\n\n document.removeEventListener('mouseup', this.dragEndHandler)\n document.removeEventListener('touchend', this.dragEndHandler)\n document.removeEventListener('touchcancel', this.dragEndHandler)\n document.removeEventListener('mousemove', this.dragMoveHandler)\n document.removeEventListener('touchmove', this.dragMoveHandler)\n\n super.disconnectedCallback()\n }\n\n updated(changes: PropertyValues<this>): void {\n // Update the background image and transform style when properties change\n if (changes.has('src') && this.src) {\n this.style.setProperty('--background-image', `url('${this.src}')`)\n this.calculateMinZoom() // Recalculate the minimum zoom level when the source changes\n }\n this.updateTransform() // Apply the current zoom and position\n }\n\n private calculateMinZoom() {\n // Calculate the minimum zoom level to ensure the image always fills the container\n const img = new Image()\n img.src = this.src || ''\n img.onload = () => {\n this.imageWidth = img.width\n this.imageHeight = img.height\n\n const aspectRatio = img.width / img.height\n const hostAspectRatio = this.offsetWidth / this.offsetHeight\n if (aspectRatio < hostAspectRatio) {\n this.minZoom = this.offsetHeight / img.height\n } else {\n this.minZoom = this.offsetWidth / img.width\n }\n this.zoom = 1 // this.minZoom\n this.left = 0 // Center the image\n this.top = 0 // Center the image\n this.updateTransform()\n }\n }\n\n private onWheelEvent(e: WheelEvent): void {\n // Handle zoom in and out based on mouse wheel events\n e.preventDefault()\n const delta = Math.sign(e.deltaY)\n const newZoom = Math.max(1, this.zoom * (1 - delta / 20))\n const zoomFactor = newZoom / this.zoom\n\n const rect = this.getBoundingClientRect()\n const offsetX = e.clientX - rect.left - rect.width / 2\n const offsetY = e.clientY - rect.top - rect.height / 2\n\n // Adjust the position to zoom in/out relative to the center of the client rect\n this.left -= offsetX * (zoomFactor - 1)\n this.top -= offsetY * (zoomFactor - 1)\n\n this.zoom = newZoom\n this.applyBounds() // Ensure the image stays within bounds\n this.updateTransform() // Apply the new zoom and position\n }\n\n private onDragStart(e: MouseEvent | TouchEvent): void {\n // Start dragging the image\n const point = getPoint(e)\n if (point) {\n this.dragStart = point\n e.stopPropagation()\n }\n }\n\n private onDragMove(e: MouseEvent | TouchEvent): void {\n // Move the image based on drag events\n if (!this.dragStart) return\n\n const point = getPoint(e)\n if (!point) return\n\n e.stopPropagation()\n e.preventDefault()\n\n const { x, y } = point\n this.left += x - this.dragStart.x\n this.top += y - this.dragStart.y\n\n this.dragStart = point\n this.applyBounds() // Ensure the image stays within bounds\n this.updateTransform() // Apply the new position\n }\n\n private onDragEnd(e: Event): void {\n // End the drag operation\n if (this.dragStart) {\n e.stopPropagation()\n e.preventDefault()\n this.dragStart = undefined\n }\n }\n\n private onReset(e: Event): void {\n // Reset zoom and position to initial state\n this.zoom = 1\n this.left = 0\n this.top = 0\n this.updateTransform()\n }\n\n private updateTransform() {\n // Apply the current zoom and position to the image\n this.style.setProperty('--image-transform', `scale(${this.zoom}) translate(${this.left}px, ${this.top}px)`)\n }\n\n private applyBounds() {\n // Ensure the image stays within the bounds of the container\n const hostWidth = this.offsetWidth\n const hostHeight = this.offsetHeight\n\n const aspectRatio = this.imageHeight / this.imageWidth\n const hostAspectRatio = hostHeight / hostWidth\n\n if (aspectRatio < hostAspectRatio) {\n /* image ratio is wider than host */\n var minX = (hostWidth * (1 - this.zoom)) / 2\n minX = minX > 0 ? -minX : minX\n var maxX = -minX\n\n var minY = (hostHeight - hostWidth * aspectRatio * this.zoom) / 2 / this.zoom\n minY = Math.min(minY, -minY)\n var maxY = -minY\n\n this.left = Math.max(minX, Math.min(this.left, maxX))\n this.top = Math.max(minY, Math.min(this.top, maxY))\n } else {\n var minX = (hostWidth - (hostHeight / aspectRatio) * this.zoom) / 2 / this.zoom\n minX = Math.min(minX, -minX)\n var maxX = -minX\n\n var minY = (-hostHeight * (1 - this.zoom)) / 2\n minY = minY > 0 ? -minY : minY\n var maxY = -minY\n\n this.left = Math.max(minX, Math.min(this.left, maxX))\n this.top = Math.max(minY, Math.min(this.top, maxY))\n }\n }\n}\n"]}
1
+ {"version":3,"file":"ox-zoomable-image.js","sourceRoot":"","sources":["../../src/ox-zoomable-image.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, LitElement, PropertyValues } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { gesture, GestureEventDoubleTap, GestureEventDrag, GestureEventPinch } from '@operato/utils/gesture-mixin.js'\n\n/**\n * Example usage:\n *\n * <html>\n * <head>\n * <script type=\"module\" src=\"path/to/ox-zoomable-image.js\"></script>\n * <style>\n * body {\n * margin: 0;\n * overflow: hidden;\n * display: flex;\n * justify-content: center;\n * align-items: center;\n * height: 100vh;\n * }\n * ox-zoomable-image {\n * width: 80%;\n * height: 80%;\n * }\n * </style>\n * </head>\n * <body>\n * <ox-zoomable-image src=\"path/to/your/image.jpg\" restrict-boundary></ox-zoomable-image>\n * </body>\n * </html>\n */\n\n@customElement('ox-zoomable-image')\nexport class OxZoomableImage extends gesture(LitElement) {\n // Define CSS styles for the component\n static styles = css`\n :host {\n display: block;\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n\n touch-action: none;\n }\n\n :host::before {\n content: '';\n display: block;\n\n position: absolute;\n top: 0;\n right: 0;\n width: 100%;\n height: 100%;\n\n background-image: var(\n --background-image,\n linear-gradient(var(--primary-color, lightgray), var(--primary-color, lightgray))\n );\n transform: var(--image-transform, '');\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n\n touch-action: none;\n }\n `\n\n @property({ type: String }) src?: string // Source URL for the image\n @property({ type: Boolean, attribute: 'restrict-boundary' }) restrictBoundary: boolean = false // Ensure the image stays within bounds\n\n @state() private zoom = 1 // Current zoom level\n @state() private left = 0 // Horizontal position\n @state() private top = 0 // Vertical position\n @state() private imageWidth = 0\n @state() private imageHeight = 0\n\n connectedCallback(): void {\n super.connectedCallback()\n\n // Register event listeners for zooming and dragging\n this.addEventListener('pinch', this.pinchHandler as EventListener)\n this.addEventListener('drag', this.dragHandler as EventListener)\n this.addEventListener('doubletap', this.doubleTapHandler as EventListener)\n this.addEventListener('wheel', this.wheelHandler as EventListener)\n\n this.calculateMinZoom() // Calculate the minimum zoom level based on the image and container size\n }\n\n disconnectedCallback(): void {\n // Remove event listeners when the component is disconnected\n this.removeEventListener('pinch', this.pinchHandler as EventListener)\n this.removeEventListener('drag', this.dragHandler as EventListener)\n this.removeEventListener('doubletap', this.doubleTapHandler as EventListener)\n this.removeEventListener('wheel', this.wheelHandler as EventListener)\n\n super.disconnectedCallback()\n }\n\n updated(changes: PropertyValues<this>): void {\n // Update the background image and transform style when properties change\n if (changes.has('src') && this.src) {\n this.style.setProperty('--background-image', `url('${this.src}')`)\n this.calculateMinZoom() // Recalculate the minimum zoom level when the source changes\n }\n\n this.updateTransform() // Apply the current zoom and position\n }\n\n private calculateMinZoom() {\n // Calculate the minimum zoom level to ensure the image always fills the container\n const img = new Image()\n img.src = this.src || ''\n img.onload = () => {\n this.imageWidth = img.width\n this.imageHeight = img.height\n\n console.log(this.imageWidth, this.imageHeight)\n\n this.zoom = 1 // this.minZoom\n this.left = 0 // Center the image\n this.top = 0 // Center the image\n\n this.updateTransform()\n }\n }\n\n private pinchHandler = (e: GestureEventPinch) => {\n const { scale, centerX, centerY } = e.detail\n this.zoomAt(centerX, centerY, scale)\n }\n\n private zoomAt(clientX: number, clientY: number, scale: number) {\n const { offsetLeft: left, offsetTop: top, offsetWidth: width, offsetHeight: height } = this\n\n const centerX = left + width / 2\n const centerY = top + height / 2\n\n // this.imageWidth\n // this.imageHeight\n const dx = ((centerX - clientX) / width) * 100 * (1 - scale)\n const dy = ((centerY - clientY) / height) * 100 * (1 - scale)\n\n this.left += dx\n this.top += dy\n\n this.zoom *= scale\n }\n\n private wheelHandler = (e: WheelEvent): void => {\n // Handle zoom in and out based on mouse wheel events\n e.preventDefault()\n\n const delta = Math.sign(e.deltaY)\n this.zoomAt(e.clientX, e.clientY, 1 - delta / 20)\n }\n\n private dragHandler = (e: GestureEventDrag): void => {\n const { deltaX, deltaY } = e.detail\n\n this.top += deltaY\n this.left += deltaX\n\n this.updateTransform()\n }\n\n private doubleTapHandler = (e: GestureEventDoubleTap): void => {\n // Reset zoom and position to initial state\n this.zoom = 1\n this.left = 0\n this.top = 0\n }\n\n private updateTransform() {\n if (this.restrictBoundary) {\n this.applyBounds() // Ensure the image stays within bounds\n }\n\n // Apply the current zoom and position to the image\n this.style.setProperty('--image-transform', `translate(${this.left}px, ${this.top}px) scale(${this.zoom})`)\n }\n\n private applyBounds() {\n // Ensure the image stays within the bounds of the container\n const hostWidth = this.offsetWidth\n const hostHeight = this.offsetHeight\n\n const aspectRatio = this.imageHeight / this.imageWidth\n const hostAspectRatio = hostHeight / hostWidth\n\n if (aspectRatio < hostAspectRatio) {\n /* image ratio is wider than host */\n var minX = (hostWidth * (1 - this.zoom)) / 2\n minX = minX > 0 ? -minX : minX\n var maxX = -minX\n\n var minY = (hostHeight - hostWidth * aspectRatio * this.zoom) / 2\n minY = Math.min(minY, -minY)\n var maxY = -minY\n\n this.left = Math.max(minX, Math.min(this.left, maxX))\n this.top = Math.max(minY, Math.min(this.top, maxY))\n } else {\n var minX = (hostWidth - (hostHeight / aspectRatio) * this.zoom) / 2\n minX = Math.min(minX, -minX)\n var maxX = -minX\n\n var minY = (-hostHeight * (1 - this.zoom)) / 2\n minY = minY > 0 ? -minY : minY\n var maxY = -minY\n\n this.left = Math.max(minX, Math.min(this.left, maxX))\n this.top = Math.max(minY, Math.min(this.top, maxY))\n }\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { TemplateResult } from 'lit-html';
2
- import '../src/ox-zoomable-image';
2
+ import '../src/ox-zoomable-image.js';
3
3
  declare const _default: {
4
4
  title: string;
5
5
  component: string;
@@ -13,6 +13,9 @@ declare const _default: {
13
13
  height: {
14
14
  control: string;
15
15
  };
16
+ restrictBoundary: {
17
+ control: string;
18
+ };
16
19
  };
17
20
  };
18
21
  export default _default;
@@ -25,6 +28,7 @@ interface OxZoomableImageProps {
25
28
  src: string;
26
29
  width: number;
27
30
  height: number;
31
+ restrictBoundary: boolean;
28
32
  }
29
33
  export declare const Default: Story<OxZoomableImageProps>;
30
34
  export declare const Wide: Story<OxZoomableImageProps>;
@@ -1,15 +1,16 @@
1
1
  import { html } from 'lit-html';
2
- import '../src/ox-zoomable-image'; // 경로를 실제 경로로 변경하세요.
2
+ import '../src/ox-zoomable-image.js'; // 경로를 실제 경로로 변경하세요.
3
3
  export default {
4
4
  title: 'OxZoomableImage',
5
5
  component: 'ox-zoomable-image',
6
6
  argTypes: {
7
7
  src: { control: 'text' },
8
8
  width: { control: 'number' },
9
- height: { control: 'number' }
9
+ height: { control: 'number' },
10
+ restrictBoundary: { control: 'boolean' }
10
11
  }
11
12
  };
12
- const Template = ({ src, width, height }) => html `
13
+ const Template = ({ src, width, height, restrictBoundary }) => html `
13
14
  <style>
14
15
  ox-zoomable-image {
15
16
  width: ${width}px;
@@ -19,24 +20,27 @@ const Template = ({ src, width, height }) => html `
19
20
  margin: 100px;
20
21
  }
21
22
  </style>
22
- <ox-zoomable-image src=${src}></ox-zoomable-image>
23
+ <ox-zoomable-image src=${src} ?restrict-boundary=${restrictBoundary}></ox-zoomable-image>
23
24
  `;
24
25
  export const Default = Template.bind({});
25
26
  Default.args = {
26
27
  src: 'https://via.placeholder.com/800x600.png', // 기본 이미지 URL을 실제 경로로 변경하세요.
27
28
  width: 400,
28
- height: 600
29
+ height: 600,
30
+ restrictBoundary: true
29
31
  };
30
32
  export const Wide = Template.bind({});
31
33
  Wide.args = {
32
34
  src: 'https://via.placeholder.com/800x600.png', // 기본 이미지 URL을 실제 경로로 변경하세요.
33
35
  width: 600,
34
- height: 300
36
+ height: 300,
37
+ restrictBoundary: true
35
38
  };
36
39
  export const CustomImage = Template.bind({});
37
40
  CustomImage.args = {
38
41
  src: 'https://via.placeholder.com/1200x800.png', // 다른 테스트 이미지 URL을 실제 경로로 변경하세요.
39
42
  width: 400,
40
- height: 600
43
+ height: 600,
44
+ restrictBoundary: true
41
45
  };
42
46
  //# sourceMappingURL=ox-zoomable-image.stories.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ox-zoomable-image.stories.js","sourceRoot":"","sources":["../../stories/ox-zoomable-image.stories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAkB,MAAM,UAAU,CAAA;AAC/C,OAAO,0BAA0B,CAAA,CAAC,oBAAoB;AAEtD,eAAe;IACb,KAAK,EAAE,iBAAiB;IACxB,SAAS,EAAE,mBAAmB;IAC9B,QAAQ,EAAE;QACR,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;QACxB,KAAK,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE;QAC5B,MAAM,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE;KAC9B;CACF,CAAA;AAcD,MAAM,QAAQ,GAAgC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAwB,EAAE,EAAE,CAAC,IAAI,CAAA;;;eAGrF,KAAK;gBACJ,MAAM;;;;;;2BAMK,GAAG;CAC7B,CAAA;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACxC,OAAO,CAAC,IAAI,GAAG;IACb,GAAG,EAAE,yCAAyC,EAAE,4BAA4B;IAC5E,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;CACZ,CAAA;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACrC,IAAI,CAAC,IAAI,GAAG;IACV,GAAG,EAAE,yCAAyC,EAAE,4BAA4B;IAC5E,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;CACZ,CAAA;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAC5C,WAAW,CAAC,IAAI,GAAG;IACjB,GAAG,EAAE,0CAA0C,EAAE,gCAAgC;IACjF,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;CACZ,CAAA","sourcesContent":["import { html, TemplateResult } from 'lit-html'\nimport '../src/ox-zoomable-image' // 경로를 실제 경로로 변경하세요.\n\nexport default {\n title: 'OxZoomableImage',\n component: 'ox-zoomable-image',\n argTypes: {\n src: { control: 'text' },\n width: { control: 'number' },\n height: { control: 'number' }\n }\n}\n\ninterface Story<T> {\n (args: T): TemplateResult\n args?: Partial<T>\n argTypes?: Record<string, unknown>\n}\n\ninterface OxZoomableImageProps {\n src: string\n width: number\n height: number\n}\n\nconst Template: Story<OxZoomableImageProps> = ({ src, width, height }: OxZoomableImageProps) => html`\n <style>\n ox-zoomable-image {\n width: ${width}px;\n height: ${height}px;\n background-color: red;\n padding: 0;\n margin: 100px;\n }\n </style>\n <ox-zoomable-image src=${src}></ox-zoomable-image>\n`\n\nexport const Default = Template.bind({})\nDefault.args = {\n src: 'https://via.placeholder.com/800x600.png', // 기본 이미지 URL을 실제 경로로 변경하세요.\n width: 400,\n height: 600\n}\n\nexport const Wide = Template.bind({})\nWide.args = {\n src: 'https://via.placeholder.com/800x600.png', // 기본 이미지 URL을 실제 경로로 변경하세요.\n width: 600,\n height: 300\n}\n\nexport const CustomImage = Template.bind({})\nCustomImage.args = {\n src: 'https://via.placeholder.com/1200x800.png', // 다른 테스트 이미지 URL을 실제 경로로 변경하세요.\n width: 400,\n height: 600\n}\n"]}
1
+ {"version":3,"file":"ox-zoomable-image.stories.js","sourceRoot":"","sources":["../../stories/ox-zoomable-image.stories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAkB,MAAM,UAAU,CAAA;AAC/C,OAAO,6BAA6B,CAAA,CAAC,oBAAoB;AAEzD,eAAe;IACb,KAAK,EAAE,iBAAiB;IACxB,SAAS,EAAE,mBAAmB;IAC9B,QAAQ,EAAE;QACR,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;QACxB,KAAK,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE;QAC5B,MAAM,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE;QAC7B,gBAAgB,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE;KACzC;CACF,CAAA;AAeD,MAAM,QAAQ,GAAgC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAwB,EAAE,EAAE,CAAC,IAAI,CAAA;;;eAGvG,KAAK;gBACJ,MAAM;;;;;;2BAMK,GAAG,uBAAuB,gBAAgB;CACpE,CAAA;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACxC,OAAO,CAAC,IAAI,GAAG;IACb,GAAG,EAAE,yCAAyC,EAAE,4BAA4B;IAC5E,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,gBAAgB,EAAE,IAAI;CACvB,CAAA;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACrC,IAAI,CAAC,IAAI,GAAG;IACV,GAAG,EAAE,yCAAyC,EAAE,4BAA4B;IAC5E,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,gBAAgB,EAAE,IAAI;CACvB,CAAA;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAC5C,WAAW,CAAC,IAAI,GAAG;IACjB,GAAG,EAAE,0CAA0C,EAAE,gCAAgC;IACjF,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,gBAAgB,EAAE,IAAI;CACvB,CAAA","sourcesContent":["import { html, TemplateResult } from 'lit-html'\nimport '../src/ox-zoomable-image.js' // 경로를 실제 경로로 변경하세요.\n\nexport default {\n title: 'OxZoomableImage',\n component: 'ox-zoomable-image',\n argTypes: {\n src: { control: 'text' },\n width: { control: 'number' },\n height: { control: 'number' },\n restrictBoundary: { control: 'boolean' }\n }\n}\n\ninterface Story<T> {\n (args: T): TemplateResult\n args?: Partial<T>\n argTypes?: Record<string, unknown>\n}\n\ninterface OxZoomableImageProps {\n src: string\n width: number\n height: number\n restrictBoundary: boolean\n}\n\nconst Template: Story<OxZoomableImageProps> = ({ src, width, height, restrictBoundary }: OxZoomableImageProps) => html`\n <style>\n ox-zoomable-image {\n width: ${width}px;\n height: ${height}px;\n background-color: red;\n padding: 0;\n margin: 100px;\n }\n </style>\n <ox-zoomable-image src=${src} ?restrict-boundary=${restrictBoundary}></ox-zoomable-image>\n`\n\nexport const Default = Template.bind({})\nDefault.args = {\n src: 'https://via.placeholder.com/800x600.png', // 기본 이미지 URL을 실제 경로로 변경하세요.\n width: 400,\n height: 600,\n restrictBoundary: true\n}\n\nexport const Wide = Template.bind({})\nWide.args = {\n src: 'https://via.placeholder.com/800x600.png', // 기본 이미지 URL을 실제 경로로 변경하세요.\n width: 600,\n height: 300,\n restrictBoundary: true\n}\n\nexport const CustomImage = Template.bind({})\nCustomImage.args = {\n src: 'https://via.placeholder.com/1200x800.png', // 다른 테스트 이미지 URL을 실제 경로로 변경하세요.\n width: 400,\n height: 600,\n restrictBoundary: true\n}\n"]}