@operato/mini-map 2.0.0-beta.0 → 2.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/src/ox-mini-map.js +77 -65
- package/dist/src/ox-mini-map.js.map +1 -1
- package/dist/src/ox-zoomable-image.d.ts +54 -0
- package/dist/src/ox-zoomable-image.js +252 -0
- package/dist/src/ox-zoomable-image.js.map +1 -0
- package/dist/stories/ox-mini-map.stories.js +44 -0
- package/dist/stories/ox-mini-map.stories.js.map +1 -0
- package/dist/stories/ox-zoomable-image.stories.d.ts +31 -0
- package/dist/stories/ox-zoomable-image.stories.js +42 -0
- package/dist/stories/ox-zoomable-image.stories.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -3
- package/src/ox-mini-map.ts +88 -69
- package/src/ox-zoomable-image.ts +256 -0
- package/stories/ox-mini-map.stories.ts +54 -0
- package/stories/ox-zoomable-image.stories.ts +58 -0
- package/dist/stories/index.stories.js +0 -16
- package/dist/stories/index.stories.js.map +0 -1
- package/stories/index.stories.ts +0 -26
- /package/dist/stories/{index.stories.d.ts → ox-mini-map.stories.d.ts} +0 -0
package/src/ox-mini-map.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { html, css, LitElement, PropertyValueMap } from 'lit'
|
|
|
2
2
|
import { customElement, property } from 'lit/decorators.js'
|
|
3
3
|
|
|
4
4
|
@customElement('ox-mini-map')
|
|
5
|
-
class
|
|
5
|
+
class OxMiniMapComponent extends LitElement {
|
|
6
6
|
static styles = css`
|
|
7
7
|
:host {
|
|
8
8
|
display: flex;
|
|
9
9
|
position: relative;
|
|
10
10
|
background-color: gray;
|
|
11
|
+
width: 200px; /* 기본 너비 */
|
|
12
|
+
height: 150px; /* 기본 높이 */
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
#minimap {
|
|
@@ -16,7 +18,8 @@ class OxminimapComponent extends LitElement {
|
|
|
16
18
|
left: 50%;
|
|
17
19
|
transform: translate(-50%, -50%);
|
|
18
20
|
box-sizing: border-box;
|
|
19
|
-
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: 100%;
|
|
20
23
|
background-color: var(--md-sys-color-surface);
|
|
21
24
|
overflow: hidden;
|
|
22
25
|
}
|
|
@@ -25,10 +28,8 @@ class OxminimapComponent extends LitElement {
|
|
|
25
28
|
position: absolute;
|
|
26
29
|
border: 1px dashed blue;
|
|
27
30
|
box-sizing: border-box;
|
|
28
|
-
pointer-events: none;
|
|
29
|
-
background-color: var(--md-sys-color-surface);
|
|
30
|
-
pointer-events: auto;
|
|
31
31
|
cursor: move;
|
|
32
|
+
background-color: rgba(255, 255, 255, 0.5);
|
|
32
33
|
}
|
|
33
34
|
`
|
|
34
35
|
|
|
@@ -38,33 +39,55 @@ class OxminimapComponent extends LitElement {
|
|
|
38
39
|
private startY = 0
|
|
39
40
|
private initialScrollLeft = 0
|
|
40
41
|
private initialScrollTop = 0
|
|
42
|
+
private observer: MutationObserver | null = null
|
|
41
43
|
|
|
42
44
|
connectedCallback() {
|
|
43
45
|
super.connectedCallback()
|
|
44
46
|
window.addEventListener('resize', this.handleResize.bind(this))
|
|
47
|
+
this.addEventListener('mousemove', this.handleMouseMove.bind(this))
|
|
48
|
+
this.addEventListener('mouseup', this.handleMouseUp.bind(this))
|
|
49
|
+
if (this.target) {
|
|
50
|
+
this.observeTarget(this.target)
|
|
51
|
+
}
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
disconnectedCallback() {
|
|
48
55
|
super.disconnectedCallback()
|
|
49
56
|
window.removeEventListener('resize', this.handleResize.bind(this))
|
|
57
|
+
window.removeEventListener('mousemove', this.handleMouseMove.bind(this))
|
|
58
|
+
window.removeEventListener('mouseup', this.handleMouseUp.bind(this))
|
|
59
|
+
if (this.observer) {
|
|
60
|
+
this.observer.disconnect()
|
|
61
|
+
this.observer = null
|
|
62
|
+
}
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
firstUpdated(changes: PropertyValueMap<any> | Map<PropertyKey, unknown>) {
|
|
53
66
|
super.firstUpdated(changes)
|
|
54
|
-
|
|
55
67
|
this.updateTarget()
|
|
56
|
-
this.checkVisibilityX()
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
updated(changedProperties: Map<string, any>) {
|
|
60
71
|
if (changedProperties.has('target')) {
|
|
61
72
|
this.updateTarget()
|
|
73
|
+
if (this.target) {
|
|
74
|
+
this.observeTarget(this.target)
|
|
75
|
+
}
|
|
62
76
|
}
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
handleResize() {
|
|
66
80
|
this.updateTarget()
|
|
67
|
-
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
observeTarget(target: HTMLElement) {
|
|
84
|
+
if (this.observer) {
|
|
85
|
+
this.observer.disconnect()
|
|
86
|
+
}
|
|
87
|
+
this.observer = new MutationObserver(() => {
|
|
88
|
+
this.updateTarget()
|
|
89
|
+
})
|
|
90
|
+
this.observer.observe(target, { attributes: true, attributeFilter: ['style'] })
|
|
68
91
|
}
|
|
69
92
|
|
|
70
93
|
updateTarget() {
|
|
@@ -73,17 +96,35 @@ class OxminimapComponent extends LitElement {
|
|
|
73
96
|
return
|
|
74
97
|
}
|
|
75
98
|
|
|
99
|
+
const targetStyle = window.getComputedStyle(target)
|
|
100
|
+
const transform = targetStyle.transform
|
|
101
|
+
const scaleMatch = /scale\(([^,]+),?([^)]+)?\)/.exec(transform)
|
|
102
|
+
const leftMatch = /translate\(([^,]+)px, ?([^)]+)?px\)/.exec(transform)
|
|
103
|
+
|
|
104
|
+
let scale = 1
|
|
105
|
+
let left = 0
|
|
106
|
+
let top = 0
|
|
107
|
+
|
|
108
|
+
if (scaleMatch) {
|
|
109
|
+
scale = parseFloat(scaleMatch[1])
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (leftMatch) {
|
|
113
|
+
left = parseFloat(leftMatch[1])
|
|
114
|
+
top = parseFloat(leftMatch[2] || '0')
|
|
115
|
+
}
|
|
116
|
+
|
|
76
117
|
const targetRatio = target.scrollHeight / target.scrollWidth
|
|
77
118
|
const thisRatio = this.offsetHeight / this.offsetWidth
|
|
78
119
|
|
|
79
|
-
const
|
|
120
|
+
const scaleMinimap =
|
|
80
121
|
targetRatio > thisRatio ? this.offsetHeight / target.scrollHeight : this.offsetWidth / target.scrollWidth
|
|
81
122
|
|
|
82
|
-
const minimapHeight = target.scrollHeight *
|
|
83
|
-
const minimapWidth = target.scrollWidth *
|
|
123
|
+
const minimapHeight = target.scrollHeight * scaleMinimap
|
|
124
|
+
const minimapWidth = target.scrollWidth * scaleMinimap
|
|
84
125
|
|
|
85
|
-
const viewportHeight = target.
|
|
86
|
-
const viewportWidth = target.
|
|
126
|
+
const viewportHeight = target.clientHeight * scaleMinimap
|
|
127
|
+
const viewportWidth = target.clientWidth * scaleMinimap
|
|
87
128
|
|
|
88
129
|
const minimap = this.shadowRoot?.getElementById('minimap') as HTMLElement
|
|
89
130
|
const viewport = this.shadowRoot?.getElementById('viewport') as HTMLElement
|
|
@@ -94,56 +135,50 @@ class OxminimapComponent extends LitElement {
|
|
|
94
135
|
viewport.style.width = `${viewportWidth}px`
|
|
95
136
|
viewport.style.height = `${viewportHeight}px`
|
|
96
137
|
|
|
97
|
-
|
|
98
|
-
const viewportTop = target.scrollTop * scale
|
|
99
|
-
|
|
100
|
-
viewport.style.width = `${viewportWidth}px`
|
|
101
|
-
viewport.style.height = `${viewportHeight}px`
|
|
102
|
-
viewport.style.left = `${viewportLeft}px`
|
|
103
|
-
viewport.style.top = `${viewportTop}px`
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
checkVisibilityX() {
|
|
107
|
-
const parentElement = this.target || this.parentElement
|
|
108
|
-
if (!parentElement) return
|
|
109
|
-
|
|
110
|
-
const parentRect = parentElement.getBoundingClientRect()
|
|
111
|
-
const minimap = this.shadowRoot?.getElementById('minimap') as HTMLElement
|
|
112
|
-
if (parentRect.width >= minimap.offsetWidth && parentRect.height >= minimap.offsetHeight) {
|
|
113
|
-
this.style.display = 'block'
|
|
114
|
-
} else {
|
|
115
|
-
this.style.display = 'none'
|
|
116
|
-
}
|
|
138
|
+
this.updateViewportPosition(scale, left, top)
|
|
117
139
|
}
|
|
118
140
|
|
|
119
|
-
|
|
141
|
+
updateViewportPosition(scale: number, left: number, top: number) {
|
|
120
142
|
const target = this.target || this.parentElement
|
|
121
|
-
|
|
122
143
|
if (!target) {
|
|
123
144
|
return
|
|
124
145
|
}
|
|
125
146
|
|
|
126
|
-
const
|
|
147
|
+
const targetRatio = target.scrollHeight / target.scrollWidth
|
|
148
|
+
const thisRatio = this.offsetHeight / this.offsetWidth
|
|
149
|
+
|
|
150
|
+
const scaleMinimap =
|
|
151
|
+
targetRatio > thisRatio ? this.offsetHeight / target.scrollHeight : this.offsetWidth / target.scrollWidth
|
|
152
|
+
|
|
153
|
+
const viewport = this.shadowRoot?.getElementById('viewport') as HTMLElement
|
|
154
|
+
viewport.style.transform = `scale(${scale}) translate(${left * scaleMinimap}px, ${top * scaleMinimap}px)`
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
handleMouseDown(event: MouseEvent) {
|
|
158
|
+
this.isDragging = true
|
|
127
159
|
this.startX = event.clientX
|
|
128
160
|
this.startY = event.clientY
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
161
|
+
|
|
162
|
+
const target = this.target || this.parentElement
|
|
163
|
+
if (target) {
|
|
164
|
+
this.initialScrollLeft = target.scrollLeft
|
|
165
|
+
this.initialScrollTop = target.scrollTop
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
window.addEventListener('mousemove', this.handleMouseMove.bind(this))
|
|
169
|
+
window.addEventListener('mouseup', this.handleMouseUp.bind(this))
|
|
170
|
+
|
|
133
171
|
event.preventDefault()
|
|
134
|
-
event.stopPropagation()
|
|
135
172
|
}
|
|
136
173
|
|
|
137
174
|
handleMouseMove(event: MouseEvent) {
|
|
138
175
|
if (!this.isDragging) return
|
|
176
|
+
|
|
139
177
|
const deltaX = event.clientX - this.startX
|
|
140
178
|
const deltaY = event.clientY - this.startY
|
|
141
179
|
|
|
142
180
|
const target = this.target || this.parentElement
|
|
143
|
-
|
|
144
|
-
if (!target) {
|
|
145
|
-
return
|
|
146
|
-
}
|
|
181
|
+
if (!target) return
|
|
147
182
|
|
|
148
183
|
const targetRatio = target.scrollHeight / target.scrollWidth
|
|
149
184
|
const thisRatio = this.offsetHeight / this.offsetWidth
|
|
@@ -151,44 +186,28 @@ class OxminimapComponent extends LitElement {
|
|
|
151
186
|
const scale =
|
|
152
187
|
targetRatio > thisRatio ? this.offsetHeight / target.scrollHeight : this.offsetWidth / target.scrollWidth
|
|
153
188
|
|
|
154
|
-
const targetRect = target.getBoundingClientRect()
|
|
155
|
-
const viewportWidth = (targetRect.width * target.clientWidth) / target.scrollWidth
|
|
156
|
-
const viewportHeight = (targetRect.height * target.clientHeight) / target.scrollHeight
|
|
157
189
|
const scrollX = deltaX / scale
|
|
158
190
|
const scrollY = deltaY / scale
|
|
159
|
-
target.scrollTo(this.initialScrollLeft + scrollX, this.initialScrollTop + scrollY)
|
|
160
|
-
const minimapViewport = this.shadowRoot?.getElementById('viewport') as HTMLElement
|
|
161
|
-
minimapViewport.style.left = `${Math.max(
|
|
162
|
-
0,
|
|
163
|
-
Math.min(event.clientX - viewportWidth / 2, target.clientWidth - viewportWidth)
|
|
164
|
-
)}px`
|
|
165
|
-
minimapViewport.style.top = `${Math.max(
|
|
166
|
-
0,
|
|
167
|
-
Math.min(event.clientY - viewportHeight / 2, target.clientHeight - viewportHeight)
|
|
168
|
-
)}px`
|
|
169
191
|
|
|
170
|
-
this.
|
|
192
|
+
target.scrollLeft = this.initialScrollLeft + scrollX
|
|
193
|
+
target.scrollTop = this.initialScrollTop + scrollY
|
|
194
|
+
|
|
195
|
+
this.updateViewportPosition(1, target.scrollLeft, target.scrollTop) // Assuming no additional transform during drag
|
|
196
|
+
|
|
171
197
|
event.preventDefault()
|
|
172
|
-
event.stopPropagation()
|
|
173
198
|
}
|
|
174
199
|
|
|
175
200
|
handleMouseUp(event: MouseEvent) {
|
|
176
|
-
const minimapViewport = this.shadowRoot?.getElementById('minimap') as HTMLElement
|
|
177
201
|
this.isDragging = false
|
|
178
|
-
|
|
202
|
+
window.removeEventListener('mousemove', this.handleMouseMove.bind(this))
|
|
203
|
+
window.removeEventListener('mouseup', this.handleMouseUp.bind(this))
|
|
179
204
|
event.preventDefault()
|
|
180
|
-
event.stopPropagation()
|
|
181
205
|
}
|
|
182
206
|
|
|
183
207
|
render() {
|
|
184
208
|
return html`
|
|
185
209
|
<div id="minimap">
|
|
186
|
-
<div
|
|
187
|
-
id="viewport"
|
|
188
|
-
@mousedown=${this.handleMouseDown}
|
|
189
|
-
@mousemove=${this.handleMouseMove}
|
|
190
|
-
@mouseup=${this.handleMouseUp}
|
|
191
|
-
></div>
|
|
210
|
+
<div id="viewport" @mousedown=${this.handleMouseDown}></div>
|
|
192
211
|
</div>
|
|
193
212
|
`
|
|
194
213
|
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { css, LitElement, PropertyValues } from 'lit'
|
|
2
|
+
import { customElement, property, state } from 'lit/decorators.js'
|
|
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
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Example usage:
|
|
17
|
+
*
|
|
18
|
+
* <html>
|
|
19
|
+
* <head>
|
|
20
|
+
* <script type="module" src="path/to/ox-zoomable-image.js"></script>
|
|
21
|
+
* <style>
|
|
22
|
+
* body {
|
|
23
|
+
* margin: 0;
|
|
24
|
+
* overflow: hidden;
|
|
25
|
+
* display: flex;
|
|
26
|
+
* justify-content: center;
|
|
27
|
+
* align-items: center;
|
|
28
|
+
* height: 100vh;
|
|
29
|
+
* }
|
|
30
|
+
* ox-zoomable-image {
|
|
31
|
+
* width: 80%;
|
|
32
|
+
* height: 80%;
|
|
33
|
+
* }
|
|
34
|
+
* </style>
|
|
35
|
+
* </head>
|
|
36
|
+
* <body>
|
|
37
|
+
* <ox-zoomable-image src="path/to/your/image.jpg"></ox-zoomable-image>
|
|
38
|
+
* </body>
|
|
39
|
+
* </html>
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
@customElement('ox-zoomable-image')
|
|
43
|
+
export class OxZoomableImage extends LitElement {
|
|
44
|
+
// Define CSS styles for the component
|
|
45
|
+
static styles = css`
|
|
46
|
+
:host {
|
|
47
|
+
display: block;
|
|
48
|
+
position: relative;
|
|
49
|
+
width: 100%;
|
|
50
|
+
height: 100%;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
:host::before {
|
|
55
|
+
content: '';
|
|
56
|
+
display: block;
|
|
57
|
+
position: absolute;
|
|
58
|
+
top: 0;
|
|
59
|
+
right: 0;
|
|
60
|
+
width: 100%;
|
|
61
|
+
height: 100%;
|
|
62
|
+
background-image: var(
|
|
63
|
+
--background-image,
|
|
64
|
+
linear-gradient(var(--primary-color, lightgray), var(--primary-color, lightgray))
|
|
65
|
+
);
|
|
66
|
+
transform: var(--image-transform, '');
|
|
67
|
+
background-repeat: no-repeat;
|
|
68
|
+
background-position: center;
|
|
69
|
+
background-size: contain;
|
|
70
|
+
}
|
|
71
|
+
`
|
|
72
|
+
|
|
73
|
+
@property({ type: String }) src?: string // Source URL for the image
|
|
74
|
+
@state() private zoom = 1 // Current zoom level
|
|
75
|
+
@state() private left = 0 // Horizontal position
|
|
76
|
+
@state() private top = 0 // Vertical position
|
|
77
|
+
@state() private minZoom = 1 // Minimum zoom level
|
|
78
|
+
@state() private imageWidth = 0
|
|
79
|
+
@state() private imageHeight = 0
|
|
80
|
+
|
|
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
|
+
connectedCallback(): void {
|
|
91
|
+
super.connectedCallback()
|
|
92
|
+
|
|
93
|
+
// 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)
|
|
104
|
+
|
|
105
|
+
this.calculateMinZoom() // Calculate the minimum zoom level based on the image and container size
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
disconnectedCallback(): void {
|
|
109
|
+
// 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)
|
|
120
|
+
|
|
121
|
+
super.disconnectedCallback()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
updated(changes: PropertyValues<this>): void {
|
|
125
|
+
// Update the background image and transform style when properties change
|
|
126
|
+
if (changes.has('src') && this.src) {
|
|
127
|
+
this.style.setProperty('--background-image', `url('${this.src}')`)
|
|
128
|
+
this.calculateMinZoom() // Recalculate the minimum zoom level when the source changes
|
|
129
|
+
}
|
|
130
|
+
this.updateTransform() // Apply the current zoom and position
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private calculateMinZoom() {
|
|
134
|
+
// Calculate the minimum zoom level to ensure the image always fills the container
|
|
135
|
+
const img = new Image()
|
|
136
|
+
img.src = this.src || ''
|
|
137
|
+
img.onload = () => {
|
|
138
|
+
this.imageWidth = img.width
|
|
139
|
+
this.imageHeight = img.height
|
|
140
|
+
|
|
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
|
+
}
|
|
148
|
+
this.zoom = 1 // this.minZoom
|
|
149
|
+
this.left = 0 // Center the image
|
|
150
|
+
this.top = 0 // Center the image
|
|
151
|
+
this.updateTransform()
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
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
|
|
165
|
+
|
|
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)
|
|
169
|
+
|
|
170
|
+
this.zoom = newZoom
|
|
171
|
+
this.applyBounds() // Ensure the image stays within bounds
|
|
172
|
+
this.updateTransform() // Apply the new zoom and position
|
|
173
|
+
}
|
|
174
|
+
|
|
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
|
+
}
|
|
183
|
+
|
|
184
|
+
private onDragMove(e: MouseEvent | TouchEvent): void {
|
|
185
|
+
// Move the image based on drag events
|
|
186
|
+
if (!this.dragStart) return
|
|
187
|
+
|
|
188
|
+
const point = getPoint(e)
|
|
189
|
+
if (!point) return
|
|
190
|
+
|
|
191
|
+
e.stopPropagation()
|
|
192
|
+
e.preventDefault()
|
|
193
|
+
|
|
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
|
|
201
|
+
}
|
|
202
|
+
|
|
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
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private onReset(e: Event): void {
|
|
213
|
+
// Reset zoom and position to initial state
|
|
214
|
+
this.zoom = 1
|
|
215
|
+
this.left = 0
|
|
216
|
+
this.top = 0
|
|
217
|
+
this.updateTransform()
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private updateTransform() {
|
|
221
|
+
// 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)`)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private applyBounds() {
|
|
226
|
+
// Ensure the image stays within the bounds of the container
|
|
227
|
+
const hostWidth = this.offsetWidth
|
|
228
|
+
const hostHeight = this.offsetHeight
|
|
229
|
+
|
|
230
|
+
const aspectRatio = this.imageHeight / this.imageWidth
|
|
231
|
+
const hostAspectRatio = hostHeight / hostWidth
|
|
232
|
+
|
|
233
|
+
if (aspectRatio < hostAspectRatio) {
|
|
234
|
+
/* image ratio is wider than host */
|
|
235
|
+
var minX = Math.min(0, (-hostWidth * (this.zoom - 1)) / 2 / this.zoom)
|
|
236
|
+
var maxX = -minX
|
|
237
|
+
|
|
238
|
+
var minY = (hostHeight - hostWidth * aspectRatio * this.zoom) / 2 / this.zoom
|
|
239
|
+
minY = Math.min(minY, -minY)
|
|
240
|
+
var maxY = -minY
|
|
241
|
+
|
|
242
|
+
this.left = Math.max(minX, Math.min(this.left, maxX))
|
|
243
|
+
this.top = Math.max(minY, Math.min(this.top, maxY))
|
|
244
|
+
} else {
|
|
245
|
+
var minX = (hostWidth - (hostHeight / aspectRatio) * this.zoom) / 2 / this.zoom
|
|
246
|
+
minX = Math.min(minX, -minX)
|
|
247
|
+
var maxX = -minX
|
|
248
|
+
|
|
249
|
+
var minY = Math.min(0, (-hostHeight * (this.zoom - 1)) / 2 / this.zoom)
|
|
250
|
+
var maxY = -minY
|
|
251
|
+
|
|
252
|
+
this.left = Math.max(minX, Math.min(this.left, maxX))
|
|
253
|
+
this.top = Math.max(minY, Math.min(this.top, maxY))
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { html, TemplateResult } from 'lit'
|
|
2
|
+
import '../src/ox-mini-map.js'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'MiniMap',
|
|
6
|
+
component: 'ox-mini-map',
|
|
7
|
+
argTypes: {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Story<T> {
|
|
11
|
+
(args: T): TemplateResult
|
|
12
|
+
args?: Partial<T>
|
|
13
|
+
argTypes?: Record<string, unknown>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ArgTypes {}
|
|
17
|
+
|
|
18
|
+
const Template: Story<ArgTypes> = ({}: ArgTypes) => html`
|
|
19
|
+
<style>
|
|
20
|
+
#parent {
|
|
21
|
+
padding: 0;
|
|
22
|
+
margin: 0;
|
|
23
|
+
width: 600px;
|
|
24
|
+
height: 400px;
|
|
25
|
+
background-color: red;
|
|
26
|
+
position: relative; /* Make the parent element the reference for the mini map */
|
|
27
|
+
overflow: auto;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#content {
|
|
31
|
+
flex: 1;
|
|
32
|
+
width: 1200px;
|
|
33
|
+
height: 1000px;
|
|
34
|
+
background-image: url('https://via.placeholder.com/800x600.png'); /* Fix the background image URL */
|
|
35
|
+
background-size: cover;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
ox-mini-map {
|
|
39
|
+
position: fixed; /* Fix to the bottom right of the parent element */
|
|
40
|
+
width: 160px;
|
|
41
|
+
height: 120px;
|
|
42
|
+
bottom: 10px;
|
|
43
|
+
right: 10px;
|
|
44
|
+
}
|
|
45
|
+
</style>
|
|
46
|
+
|
|
47
|
+
<div id="parent">
|
|
48
|
+
<div id="content"></div>
|
|
49
|
+
<ox-mini-map .target=${document.getElementById('parent')}></ox-mini-map>
|
|
50
|
+
</div>
|
|
51
|
+
`
|
|
52
|
+
|
|
53
|
+
export const Regular = Template.bind({})
|
|
54
|
+
Regular.args = {}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { html, TemplateResult } from 'lit-html'
|
|
2
|
+
import '../src/ox-zoomable-image' // 경로를 실제 경로로 변경하세요.
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'OxZoomableImage',
|
|
6
|
+
component: 'ox-zoomable-image',
|
|
7
|
+
argTypes: {
|
|
8
|
+
src: { control: 'text' },
|
|
9
|
+
width: { control: 'number' },
|
|
10
|
+
height: { control: 'number' }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Story<T> {
|
|
15
|
+
(args: T): TemplateResult
|
|
16
|
+
args?: Partial<T>
|
|
17
|
+
argTypes?: Record<string, unknown>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface OxZoomableImageProps {
|
|
21
|
+
src: string
|
|
22
|
+
width: number
|
|
23
|
+
height: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const Template: Story<OxZoomableImageProps> = ({ src, width, height }: OxZoomableImageProps) => html`
|
|
27
|
+
<style>
|
|
28
|
+
ox-zoomable-image {
|
|
29
|
+
width: ${width}px;
|
|
30
|
+
height: ${height}px;
|
|
31
|
+
background-color: red;
|
|
32
|
+
padding: 0;
|
|
33
|
+
margin: 100px;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
<ox-zoomable-image src=${src}></ox-zoomable-image>
|
|
37
|
+
`
|
|
38
|
+
|
|
39
|
+
export const Default = Template.bind({})
|
|
40
|
+
Default.args = {
|
|
41
|
+
src: 'https://via.placeholder.com/800x600.png', // 기본 이미지 URL을 실제 경로로 변경하세요.
|
|
42
|
+
width: 400,
|
|
43
|
+
height: 600
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const Wide = Template.bind({})
|
|
47
|
+
Wide.args = {
|
|
48
|
+
src: 'https://via.placeholder.com/800x600.png', // 기본 이미지 URL을 실제 경로로 변경하세요.
|
|
49
|
+
width: 600,
|
|
50
|
+
height: 300
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const CustomImage = Template.bind({})
|
|
54
|
+
CustomImage.args = {
|
|
55
|
+
src: 'https://via.placeholder.com/1200x800.png', // 다른 테스트 이미지 URL을 실제 경로로 변경하세요.
|
|
56
|
+
width: 400,
|
|
57
|
+
height: 600
|
|
58
|
+
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { html } from 'lit';
|
|
2
|
-
import '../src/ox-mini-map.js';
|
|
3
|
-
export default {
|
|
4
|
-
title: 'MiniMap',
|
|
5
|
-
component: 'mini-map',
|
|
6
|
-
argTypes: {}
|
|
7
|
-
};
|
|
8
|
-
const Template = ({}) => html `
|
|
9
|
-
<div id="parent" style="overflow: auto; width: 100%; height: 500px;">
|
|
10
|
-
<div id="content" style="width: 1200px; height: 1000px"></div>
|
|
11
|
-
<ox-mini-map style="width: 100px; height: 100px; position: absolute; bottom: 10px; right: 10px;"> </ox-mini-map>
|
|
12
|
-
</div>
|
|
13
|
-
`;
|
|
14
|
-
export const Regular = Template.bind({});
|
|
15
|
-
Regular.args = {};
|
|
16
|
-
//# sourceMappingURL=index.stories.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.stories.js","sourceRoot":"","sources":["../../stories/index.stories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAkB,MAAM,KAAK,CAAA;AAC1C,OAAO,uBAAuB,CAAA;AAE9B,eAAe;IACb,KAAK,EAAE,SAAS;IAChB,SAAS,EAAE,UAAU;IACrB,QAAQ,EAAE,EAAE;CACb,CAAA;AAUD,MAAM,QAAQ,GAAoB,CAAC,EAAY,EAAE,EAAE,CAAC,IAAI,CAAA;;;;;CAKvD,CAAA;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACxC,OAAO,CAAC,IAAI,GAAG,EAAE,CAAA","sourcesContent":["import { html, TemplateResult } from 'lit'\nimport '../src/ox-mini-map.js'\n\nexport default {\n title: 'MiniMap',\n component: 'mini-map',\n argTypes: {}\n}\n\ninterface Story<T> {\n (args: T): TemplateResult\n args?: Partial<T>\n argTypes?: Record<string, unknown>\n}\n\ninterface ArgTypes {}\n\nconst Template: Story<ArgTypes> = ({}: ArgTypes) => html`\n <div id=\"parent\" style=\"overflow: auto; width: 100%; height: 500px;\">\n <div id=\"content\" style=\"width: 1200px; height: 1000px\"></div>\n <ox-mini-map style=\"width: 100px; height: 100px; position: absolute; bottom: 10px; right: 10px;\"> </ox-mini-map>\n </div>\n`\n\nexport const Regular = Template.bind({})\nRegular.args = {}\n"]}
|
package/stories/index.stories.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { html, TemplateResult } from 'lit'
|
|
2
|
-
import '../src/ox-mini-map.js'
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
title: 'MiniMap',
|
|
6
|
-
component: 'mini-map',
|
|
7
|
-
argTypes: {}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface Story<T> {
|
|
11
|
-
(args: T): TemplateResult
|
|
12
|
-
args?: Partial<T>
|
|
13
|
-
argTypes?: Record<string, unknown>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface ArgTypes {}
|
|
17
|
-
|
|
18
|
-
const Template: Story<ArgTypes> = ({}: ArgTypes) => html`
|
|
19
|
-
<div id="parent" style="overflow: auto; width: 100%; height: 500px;">
|
|
20
|
-
<div id="content" style="width: 1200px; height: 1000px"></div>
|
|
21
|
-
<ox-mini-map style="width: 100px; height: 100px; position: absolute; bottom: 10px; right: 10px;"> </ox-mini-map>
|
|
22
|
-
</div>
|
|
23
|
-
`
|
|
24
|
-
|
|
25
|
-
export const Regular = Template.bind({})
|
|
26
|
-
Regular.args = {}
|
|
File without changes
|