@bagelink/vue 0.0.753 → 0.0.755
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/dist/components/Zoomer.vue.d.ts +40 -0
- package/dist/components/Zoomer.vue.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/components/lightbox/lightbox.types.d.ts +2 -0
- package/dist/components/lightbox/lightbox.types.d.ts.map +1 -1
- package/dist/index.cjs +716 -241
- package/dist/index.mjs +716 -241
- package/dist/style.css +33 -14
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/tapDetector.d.ts +30 -0
- package/dist/utils/tapDetector.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Zoomer.vue +377 -0
- package/src/components/index.ts +1 -0
- package/src/components/lightbox/Lightbox.vue +28 -4
- package/src/components/lightbox/index.ts +2 -2
- package/src/components/lightbox/lightbox.types.ts +2 -0
- package/src/utils/index.ts +12 -4
- package/src/utils/tapDetector.ts +119 -0
package/dist/style.css
CHANGED
|
@@ -3282,19 +3282,38 @@ tbody tr[data-v-680da9b9]:hover {
|
|
|
3282
3282
|
background: var(--bgl-gray-light);
|
|
3283
3283
|
}
|
|
3284
3284
|
|
|
3285
|
-
.
|
|
3285
|
+
.vue-zoomer[data-v-5e5bef88] {
|
|
3286
|
+
overflow: hidden;
|
|
3287
|
+
}
|
|
3288
|
+
.zoomer[data-v-5e5bef88] {
|
|
3289
|
+
transform-origin: 50% 50%;
|
|
3290
|
+
width: 100%;
|
|
3291
|
+
height: 100%;
|
|
3292
|
+
}
|
|
3293
|
+
.zoomer > img[data-v-5e5bef88] {
|
|
3294
|
+
vertical-align: top;
|
|
3295
|
+
user-select: none;
|
|
3296
|
+
-moz-user-drag: none;
|
|
3297
|
+
-webkit-user-select: none;
|
|
3298
|
+
-moz-user-select: none;
|
|
3299
|
+
-ms-user-select: none;
|
|
3300
|
+
-webkit-user-drag: none;
|
|
3301
|
+
-moz-user-drag: none;
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
.lightbox-image[data-v-d550287d]{
|
|
3286
3305
|
object-fit: contain;
|
|
3287
3306
|
}
|
|
3288
|
-
.bgl-lightbox-overlay[data-v-
|
|
3307
|
+
.bgl-lightbox-overlay[data-v-d550287d] {
|
|
3289
3308
|
background: rgba(0, 0, 0, 0.8);
|
|
3290
3309
|
}
|
|
3291
|
-
.bgl-lightbox[data-v-
|
|
3310
|
+
.bgl-lightbox[data-v-d550287d] {
|
|
3292
3311
|
max-height: 90%;
|
|
3293
3312
|
}
|
|
3294
|
-
.bgl-lightbox-item[data-v-
|
|
3295
|
-
animation: 500ms ease bgl-lightbox-load-
|
|
3313
|
+
.bgl-lightbox-item[data-v-d550287d]{
|
|
3314
|
+
animation: 500ms ease bgl-lightbox-load-d550287d;
|
|
3296
3315
|
}
|
|
3297
|
-
@keyframes bgl-lightbox-load-
|
|
3316
|
+
@keyframes bgl-lightbox-load-d550287d {
|
|
3298
3317
|
from {
|
|
3299
3318
|
scale:0.7;
|
|
3300
3319
|
}
|
|
@@ -3302,32 +3321,32 @@ to {
|
|
|
3302
3321
|
scale:1;
|
|
3303
3322
|
}
|
|
3304
3323
|
}
|
|
3305
|
-
.bgl-lightbox-item[data-v-
|
|
3324
|
+
.bgl-lightbox-item[data-v-d550287d] * {
|
|
3306
3325
|
max-width: 90%;
|
|
3307
3326
|
max-height: calc(80vh - 90px);
|
|
3308
3327
|
border-radius: 3px;
|
|
3309
3328
|
margin: auto;
|
|
3310
|
-
animation: 200ms ease bgl-lightbox-load-
|
|
3329
|
+
animation: 200ms ease bgl-lightbox-load-d550287d;
|
|
3311
3330
|
}
|
|
3312
|
-
.navigation[data-v-
|
|
3331
|
+
.navigation[data-v-d550287d] {
|
|
3313
3332
|
top: 50%;
|
|
3314
3333
|
transform: translateY(-50%);
|
|
3315
3334
|
}
|
|
3316
|
-
.navigation-btn[data-v-
|
|
3335
|
+
.navigation-btn[data-v-d550287d] {
|
|
3317
3336
|
width: var(--input-height);
|
|
3318
3337
|
height: var(--input-height);
|
|
3319
3338
|
}
|
|
3320
|
-
.thumbnail[data-v-
|
|
3339
|
+
.thumbnail[data-v-d550287d] {
|
|
3321
3340
|
height: 50px;
|
|
3322
3341
|
width: 50px;
|
|
3323
3342
|
}
|
|
3324
|
-
.thumbnail[data-v-
|
|
3343
|
+
.thumbnail[data-v-d550287d]:hover {
|
|
3325
3344
|
opacity: 1;
|
|
3326
3345
|
}
|
|
3327
|
-
.thumbnail[data-v-
|
|
3346
|
+
.thumbnail[data-v-d550287d]:active {
|
|
3328
3347
|
opacity: 0.8;
|
|
3329
3348
|
}
|
|
3330
|
-
.thumbnail.active[data-v-
|
|
3349
|
+
.thumbnail.active[data-v-d550287d] {
|
|
3331
3350
|
opacity: 1;
|
|
3332
3351
|
outline: 2px solid white;
|
|
3333
3352
|
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Attributes, BglFormSchemaT } from '..';
|
|
2
|
-
export declare function debounce(
|
|
2
|
+
export declare function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void;
|
|
3
3
|
export declare function keyToLabel(key?: string): string | undefined;
|
|
4
4
|
export declare function copyText(text: string, cb?: (msg: string) => void): Promise<void>;
|
|
5
5
|
export declare function initials(...strArr: string[]): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE/D,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAYpH;AAED,wBAAgB,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK3D;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,iBAGtE;AAED,wBAAgB,QAAQ,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,UAG3C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,IAAI,QAIrE;AAED,wBAAgB,QAAQ,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,UAKpE;AAED,wBAAgB,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,OAU7F;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,OAM9C;AAED,wBAAgB,SAAS,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,OAGzE;AAED,eAAO,MAAM,MAAM,eAAgB,GAAG,YAA0C,CAAA;AAEhF,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAA;AAElD,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AAEhC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAExC,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,CAa3F;AAED,eAAO,MAAM,KAAK,QAAQ,MAAM,qBAA0D,CAAA;AAE1F,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type TapCallback = (event: {
|
|
2
|
+
clientX: number;
|
|
3
|
+
clientY: number;
|
|
4
|
+
}) => void;
|
|
5
|
+
export default class TapDetector {
|
|
6
|
+
private singleTapCallbacks;
|
|
7
|
+
private doubleTapCallbacks;
|
|
8
|
+
private isTouchMode;
|
|
9
|
+
private lastTapTimestamp;
|
|
10
|
+
private tappedCount;
|
|
11
|
+
private touchMovedLength;
|
|
12
|
+
private lastPointerX;
|
|
13
|
+
private lastPointerY;
|
|
14
|
+
attach(dom: HTMLElement): void;
|
|
15
|
+
detach(dom: HTMLElement): void;
|
|
16
|
+
onSingleTap(callback: TapCallback): void;
|
|
17
|
+
onDoubleTap(callback: TapCallback): void;
|
|
18
|
+
private triggerCallbacks;
|
|
19
|
+
private onTouchStart;
|
|
20
|
+
private onTouchMove;
|
|
21
|
+
private onTouchEnd;
|
|
22
|
+
private onMouseDown;
|
|
23
|
+
private onMouseMove;
|
|
24
|
+
private onMouseUp;
|
|
25
|
+
private onPointerDown;
|
|
26
|
+
private onPointerUp;
|
|
27
|
+
private onPointerMove;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=tapDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tapDetector.d.ts","sourceRoot":"","sources":["../../src/utils/tapDetector.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GAAG,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAA;AAGxE,MAAM,CAAC,OAAO,OAAO,WAAW;IAC/B,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,YAAY,CAAI;IAExB,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI;IAa9B,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI;IAS9B,WAAW,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;IAMxC,WAAW,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;IAMxC,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,YAAY,CAKnB;IAED,OAAO,CAAC,WAAW,CAIlB;IAED,OAAO,CAAC,UAAU,CAEjB;IAED,OAAO,CAAC,WAAW,CAIlB;IAED,OAAO,CAAC,WAAW,CAIlB;IAED,OAAO,CAAC,SAAS,CAEhB;IAED,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,aAAa;CAOrB"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { debounce } from '@bagelink/vue'
|
|
3
|
+
import { onMounted, onUnmounted, watch } from 'vue'
|
|
4
|
+
import TapDetector from '../utils/tapDetector'
|
|
5
|
+
|
|
6
|
+
// Props interface
|
|
7
|
+
interface Props {
|
|
8
|
+
minScale?: number
|
|
9
|
+
maxScale?: number
|
|
10
|
+
zoom?: number
|
|
11
|
+
resetTrigger?: number
|
|
12
|
+
aspectRatio?: number
|
|
13
|
+
backgroundColor?: string
|
|
14
|
+
pivot?: string // 'cursor' | 'image-center'
|
|
15
|
+
zoomingElastic?: boolean
|
|
16
|
+
limitTranslation?: boolean
|
|
17
|
+
doubleClickToZoom?: boolean
|
|
18
|
+
mouseWheelToZoom?: boolean
|
|
19
|
+
disabled?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
minScale = 1,
|
|
24
|
+
maxScale = 5,
|
|
25
|
+
zoom,
|
|
26
|
+
disabled = false,
|
|
27
|
+
aspectRatio = 1,
|
|
28
|
+
backgroundColor = 'transparent',
|
|
29
|
+
pivot = 'cursor',
|
|
30
|
+
zoomingElastic = true,
|
|
31
|
+
limitTranslation = true,
|
|
32
|
+
doubleClickToZoom = true,
|
|
33
|
+
mouseWheelToZoom = true,
|
|
34
|
+
} = defineProps<Props>()
|
|
35
|
+
|
|
36
|
+
const emit = defineEmits(['update:zoom'])
|
|
37
|
+
// Reactive state using $ref (vue-macros)
|
|
38
|
+
const zoomElement = $ref<HTMLElement | null>()
|
|
39
|
+
let containerWidth = $ref(1)
|
|
40
|
+
let containerHeight = $ref(1)
|
|
41
|
+
let containerLeft = $ref(0)
|
|
42
|
+
let containerTop = $ref(0)
|
|
43
|
+
let translateX = $ref(0)
|
|
44
|
+
let animTranslateX = $ref(0)
|
|
45
|
+
let translateY = $ref(0)
|
|
46
|
+
let animTranslateY = $ref(0)
|
|
47
|
+
let _zoom = $ref(zoom || 1)
|
|
48
|
+
let scale = $computed({
|
|
49
|
+
get: () => zoom === undefined ? _zoom : zoom,
|
|
50
|
+
set: (val) => {
|
|
51
|
+
_zoom = val
|
|
52
|
+
emit('update:zoom', _zoom)
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
let animScale = $ref(1)
|
|
57
|
+
let lastFullWheelTime = $ref(0)
|
|
58
|
+
let lastWheelTime = $ref(0)
|
|
59
|
+
let lastWheelDirection = $ref<'x' | 'y'>('y')
|
|
60
|
+
let isPointerDown = $ref(false)
|
|
61
|
+
let pointerPosX = $ref(-1)
|
|
62
|
+
let pointerPosY = $ref(-1)
|
|
63
|
+
let twoFingerInitDist = $ref(0)
|
|
64
|
+
let panLocked = $ref(true)
|
|
65
|
+
let raf = $ref<number | null>(null)
|
|
66
|
+
let tapDetector = $ref<TapDetector | null>(null)
|
|
67
|
+
|
|
68
|
+
const wrapperStyle = $computed(() => {
|
|
69
|
+
const translateXValue = containerWidth * animTranslateX
|
|
70
|
+
const translateYValue = containerHeight * animTranslateY
|
|
71
|
+
return {
|
|
72
|
+
transform: [
|
|
73
|
+
`translate(${translateXValue}px, ${translateYValue}px)`,
|
|
74
|
+
`scale(${animScale})`,
|
|
75
|
+
].join(' '),
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
watch(() => scale, (newScale) => {
|
|
80
|
+
const { x, y } = calcTranslateLimit()
|
|
81
|
+
translateX = Math.max(-x, Math.min(x, translateX))
|
|
82
|
+
translateY = Math.max(-y, Math.min(y, translateY))
|
|
83
|
+
if (newScale !== 1) {
|
|
84
|
+
panLocked = false
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
function reset() {
|
|
89
|
+
scale = 1
|
|
90
|
+
panLocked = true
|
|
91
|
+
translateX = 0
|
|
92
|
+
translateY = 0
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
defineExpose({ reset })
|
|
96
|
+
|
|
97
|
+
function tryToScale(scaleDelta: number) {
|
|
98
|
+
if (disabled) return
|
|
99
|
+
let newScale = scale * scaleDelta
|
|
100
|
+
if (zoomingElastic) {
|
|
101
|
+
if (newScale < minScale! || newScale > maxScale!) {
|
|
102
|
+
let log = Math.log2(scaleDelta)
|
|
103
|
+
log *= 0.2
|
|
104
|
+
scaleDelta = 2 ** log
|
|
105
|
+
newScale = scale * scaleDelta
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
if (newScale < minScale!) newScale = minScale!
|
|
109
|
+
else if (newScale > maxScale!) newScale = maxScale!
|
|
110
|
+
}
|
|
111
|
+
scaleDelta = newScale / scale
|
|
112
|
+
scale = newScale
|
|
113
|
+
if (pivot !== 'image-center') {
|
|
114
|
+
const normMousePosX = (pointerPosX - containerLeft) / containerWidth
|
|
115
|
+
const normMousePosY = (pointerPosY - containerTop) / containerHeight
|
|
116
|
+
translateX = (0.5 + translateX - normMousePosX) * scaleDelta + normMousePosX - 0.5
|
|
117
|
+
translateY = (0.5 + translateY - normMousePosY) * scaleDelta + normMousePosY - 0.5
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function setPointerPosCenter() {
|
|
122
|
+
pointerPosX = containerLeft + containerWidth / 2
|
|
123
|
+
pointerPosY = containerTop + containerHeight / 2
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function onPointerMove(newMousePosX: number, newMousePosY: number) {
|
|
127
|
+
if (isPointerDown) {
|
|
128
|
+
const pixelDeltaX = newMousePosX - pointerPosX
|
|
129
|
+
const pixelDeltaY = newMousePosY - pointerPosY
|
|
130
|
+
if (!panLocked) {
|
|
131
|
+
const translateLimit = calcTranslateLimit()
|
|
132
|
+
const maxTranslateX = translateLimit.x
|
|
133
|
+
const maxTranslateY = translateLimit.y
|
|
134
|
+
const newTranslateX = translateX + pixelDeltaX / containerWidth
|
|
135
|
+
const newTranslateY = translateY + pixelDeltaY / containerHeight
|
|
136
|
+
translateX = Math.max(-maxTranslateX, Math.min(maxTranslateX, newTranslateX))
|
|
137
|
+
translateY = Math.max(-maxTranslateY, Math.min(maxTranslateY, newTranslateY))
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
pointerPosX = newMousePosX
|
|
141
|
+
pointerPosY = newMousePosY
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function onInteractionEnd() {
|
|
145
|
+
debounce(() => {
|
|
146
|
+
limit()
|
|
147
|
+
panLocked = scale === 1
|
|
148
|
+
}, 100)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function limit() {
|
|
152
|
+
if (scale < minScale!) {
|
|
153
|
+
scale = minScale!
|
|
154
|
+
} else if (scale > maxScale!) {
|
|
155
|
+
tryToScale(maxScale! / scale)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (limitTranslation) {
|
|
159
|
+
const translateLimit = calcTranslateLimit()
|
|
160
|
+
if (Math.abs(translateX) > translateLimit.x) {
|
|
161
|
+
translateX *= translateLimit.x / Math.abs(translateX)
|
|
162
|
+
}
|
|
163
|
+
if (Math.abs(translateY) > translateLimit.y) {
|
|
164
|
+
translateY *= translateLimit.y / Math.abs(translateY)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function calcTranslateLimit() {
|
|
170
|
+
if (getMarginDirection() === 'y') {
|
|
171
|
+
const imageToContainerRatio = containerWidth / aspectRatio / containerHeight
|
|
172
|
+
let translateLimitY = (scale * imageToContainerRatio - 1) / 2
|
|
173
|
+
if (translateLimitY < 0) translateLimitY = 0
|
|
174
|
+
return { x: (scale - 1) / 2, y: translateLimitY }
|
|
175
|
+
} else {
|
|
176
|
+
const imageToContainerRatio = containerHeight * aspectRatio / containerWidth
|
|
177
|
+
let translateLimitX = (scale * imageToContainerRatio - 1) / 2
|
|
178
|
+
if (translateLimitX < 0) translateLimitX = 0
|
|
179
|
+
return { x: translateLimitX, y: (scale - 1) / 2 }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getMarginDirection() {
|
|
184
|
+
const containerRatio = containerWidth / containerHeight
|
|
185
|
+
return containerRatio > aspectRatio ? 'x' : 'y'
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function onMouseWheel(ev: WheelEvent) {
|
|
189
|
+
if (!mouseWheelToZoom) return
|
|
190
|
+
ev.preventDefault()
|
|
191
|
+
const currTime = Date.now()
|
|
192
|
+
if (Math.abs(ev.deltaY) === 120) {
|
|
193
|
+
if (currTime - lastFullWheelTime > 50) {
|
|
194
|
+
onMouseWheelDo(ev.deltaY)
|
|
195
|
+
lastFullWheelTime = currTime
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
if (currTime - lastWheelTime > 50) {
|
|
199
|
+
lastWheelDirection = ev.deltaX > ev.deltaY ? 'x' : 'y'
|
|
200
|
+
if (lastWheelDirection === 'y') {
|
|
201
|
+
onMouseWheelDo(ev.deltaY)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
lastWheelTime = currTime
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function onMouseWheelDo(wheelDelta: number) {
|
|
209
|
+
const scaleDelta = 1.25 ** (wheelDelta / 120)
|
|
210
|
+
tryToScale(scaleDelta)
|
|
211
|
+
onInteractionEnd()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function onMouseDown(ev: MouseEvent) {
|
|
215
|
+
refreshContainerPos()
|
|
216
|
+
isPointerDown = true
|
|
217
|
+
pointerPosX = ev.clientX
|
|
218
|
+
pointerPosY = ev.clientY
|
|
219
|
+
document.addEventListener('mousemove', onMouseMove)
|
|
220
|
+
document.addEventListener('mouseup', onMouseUp)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function onMouseUp() {
|
|
224
|
+
isPointerDown = false
|
|
225
|
+
onInteractionEnd()
|
|
226
|
+
document.removeEventListener('mouseup', onMouseUp)
|
|
227
|
+
document.removeEventListener('mousemove', onMouseMove)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function onMouseMove(ev: MouseEvent) {
|
|
231
|
+
onPointerMove(ev.clientX, ev.clientY)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function onTouchStart(ev: TouchEvent) {
|
|
235
|
+
if (ev.touches.length === 1) {
|
|
236
|
+
refreshContainerPos()
|
|
237
|
+
pointerPosX = ev.touches[0].clientX
|
|
238
|
+
pointerPosY = ev.touches[0].clientY
|
|
239
|
+
isPointerDown = true
|
|
240
|
+
} else if (ev.touches.length === 2) {
|
|
241
|
+
isPointerDown = true
|
|
242
|
+
pointerPosX = (ev.touches[0].clientX + ev.touches[1].clientX) / 2
|
|
243
|
+
pointerPosY = (ev.touches[0].clientY + ev.touches[1].clientY) / 2
|
|
244
|
+
const distX = ev.touches[0].clientX - ev.touches[1].clientX
|
|
245
|
+
const distY = ev.touches[0].clientY - ev.touches[1].clientY
|
|
246
|
+
twoFingerInitDist = Math.sqrt(distX * distX + distY * distY)
|
|
247
|
+
}
|
|
248
|
+
document.addEventListener('touchend', onTouchEnd)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function onTouchEnd(ev: TouchEvent) {
|
|
252
|
+
if (ev.touches.length === 0) {
|
|
253
|
+
isPointerDown = false
|
|
254
|
+
if (Math.abs(scale - 1) < 0.1) scale = 1
|
|
255
|
+
onInteractionEnd()
|
|
256
|
+
} else if (ev.touches.length === 1) {
|
|
257
|
+
pointerPosX = ev.touches[0].clientX
|
|
258
|
+
pointerPosY = ev.touches[0].clientY
|
|
259
|
+
}
|
|
260
|
+
document.removeEventListener('touchend', onTouchEnd)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function onTouchMove(ev: TouchEvent) {
|
|
264
|
+
if (ev.touches.length === 1) {
|
|
265
|
+
onPointerMove(ev.touches[0].clientX, ev.touches[0].clientY)
|
|
266
|
+
} else if (ev.touches.length
|
|
267
|
+
|
|
268
|
+
=== 2) {
|
|
269
|
+
const distX = ev.touches[0].clientX - ev.touches[1].clientX
|
|
270
|
+
const distY = ev.touches[0].clientY - ev.touches[1].clientY
|
|
271
|
+
const newTwoFingerDist = Math.sqrt(distX * distX + distY * distY)
|
|
272
|
+
tryToScale(newTwoFingerDist / twoFingerInitDist)
|
|
273
|
+
twoFingerInitDist = newTwoFingerDist
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function refreshContainerPos() {
|
|
278
|
+
if (zoomElement) {
|
|
279
|
+
const rect = zoomElement.getBoundingClientRect()
|
|
280
|
+
containerLeft = rect.left
|
|
281
|
+
containerTop = rect.top
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function loop() {
|
|
286
|
+
animScale = gainOn(animScale, scale)
|
|
287
|
+
animTranslateX = gainOn(animTranslateX, translateX)
|
|
288
|
+
animTranslateY = gainOn(animTranslateY, translateY)
|
|
289
|
+
raf = window.requestAnimationFrame(loop)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function gainOn(from: number, to: number) {
|
|
293
|
+
const delta = (to - from) * 0.3
|
|
294
|
+
return Math.abs(delta) > 1e-5 ? from + delta : to
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Lifecycle hooks
|
|
298
|
+
onMounted(() => {
|
|
299
|
+
tapDetector = new TapDetector()
|
|
300
|
+
if (zoomElement) tapDetector.attach(zoomElement)
|
|
301
|
+
if (doubleClickToZoom) {
|
|
302
|
+
tapDetector.onDoubleTap(onDoubleTap as any)
|
|
303
|
+
}
|
|
304
|
+
window.addEventListener('resize', onWindowResize)
|
|
305
|
+
onWindowResize()
|
|
306
|
+
refreshContainerPos()
|
|
307
|
+
loop()
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
onUnmounted(() => {
|
|
311
|
+
if (zoomElement) tapDetector?.detach(zoomElement)
|
|
312
|
+
window.removeEventListener('resize', onWindowResize)
|
|
313
|
+
if (raf) window.cancelAnimationFrame(raf)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
function onDoubleTap(ev: MouseEvent) {
|
|
317
|
+
if (scale === 1) {
|
|
318
|
+
if (ev.clientX > 0) {
|
|
319
|
+
pointerPosX = ev.clientX
|
|
320
|
+
pointerPosY = ev.clientY
|
|
321
|
+
}
|
|
322
|
+
tryToScale(Math.min(3, maxScale!))
|
|
323
|
+
} else {
|
|
324
|
+
reset()
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function onWindowResize() {
|
|
329
|
+
if (zoomElement) {
|
|
330
|
+
const styles = window.getComputedStyle(zoomElement)
|
|
331
|
+
containerWidth = Number.parseFloat(styles.width)
|
|
332
|
+
containerHeight = Number.parseFloat(styles.height)
|
|
333
|
+
setPointerPosCenter()
|
|
334
|
+
limit()
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
</script>
|
|
338
|
+
|
|
339
|
+
<template>
|
|
340
|
+
<div
|
|
341
|
+
ref="zoomElement"
|
|
342
|
+
class="vue-zoomer"
|
|
343
|
+
:style="{ backgroundColor }"
|
|
344
|
+
@mousewheel="onMouseWheel"
|
|
345
|
+
@DOMMouseScroll="onMouseWheel"
|
|
346
|
+
@mousedown="onMouseDown"
|
|
347
|
+
@mouseout="setPointerPosCenter"
|
|
348
|
+
@focusout="setPointerPosCenter"
|
|
349
|
+
@touchstart="onTouchStart"
|
|
350
|
+
@touchmove="onTouchMove"
|
|
351
|
+
>
|
|
352
|
+
<div class="zoomer" :style="wrapperStyle">
|
|
353
|
+
<slot />
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
</template>
|
|
357
|
+
|
|
358
|
+
<style scoped>
|
|
359
|
+
.vue-zoomer {
|
|
360
|
+
overflow: hidden;
|
|
361
|
+
}
|
|
362
|
+
.zoomer {
|
|
363
|
+
transform-origin: 50% 50%;
|
|
364
|
+
width: 100%;
|
|
365
|
+
height: 100%;
|
|
366
|
+
}
|
|
367
|
+
.zoomer > img {
|
|
368
|
+
vertical-align: top;
|
|
369
|
+
user-select: none;
|
|
370
|
+
-moz-user-drag: none;
|
|
371
|
+
-webkit-user-select: none;
|
|
372
|
+
-moz-user-select: none;
|
|
373
|
+
-ms-user-select: none;
|
|
374
|
+
-webkit-user-drag: none;
|
|
375
|
+
-moz-user-drag: none;
|
|
376
|
+
}
|
|
377
|
+
</style>
|
package/src/components/index.ts
CHANGED
|
@@ -30,3 +30,4 @@ export { default as RouterWrapper } from './RouterWrapper.vue'
|
|
|
30
30
|
export { default as TableSchema } from './TableSchema.vue'
|
|
31
31
|
export { default as Title } from './Title.vue'
|
|
32
32
|
export { default as TopBar } from './TopBar.vue'
|
|
33
|
+
export { default as Zoomer } from './Zoomer.vue'
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { BglVideo, Btn, Icon } from '@bagelink/vue'
|
|
2
|
+
import { BglVideo, Btn, Icon, Zoomer } from '@bagelink/vue'
|
|
3
3
|
import { watch } from 'vue'
|
|
4
|
+
|
|
4
5
|
import type { LightboxItem } from './lightbox.types'
|
|
5
6
|
|
|
6
7
|
let isOpen = $ref(false)
|
|
@@ -62,6 +63,12 @@ function handleKeydown(event: KeyboardEvent) {
|
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
const zoom = $ref(1)
|
|
67
|
+
|
|
68
|
+
function clickOutside() {
|
|
69
|
+
if (zoom === 1) close()
|
|
70
|
+
}
|
|
71
|
+
|
|
65
72
|
defineExpose({ open, close })
|
|
66
73
|
</script>
|
|
67
74
|
|
|
@@ -73,7 +80,7 @@ defineExpose({ open, close })
|
|
|
73
80
|
@keydown.esc="close"
|
|
74
81
|
@keydown.left="prev"
|
|
75
82
|
@keydown.right="next"
|
|
76
|
-
@click="
|
|
83
|
+
@click="clickOutside"
|
|
77
84
|
>
|
|
78
85
|
<div v-if="group && group.length > 1" class="navigation flex space-between px-3 w-100 absolute">
|
|
79
86
|
<Btn
|
|
@@ -81,6 +88,7 @@ defineExpose({ open, close })
|
|
|
81
88
|
icon="arrow_back"
|
|
82
89
|
@click="prev"
|
|
83
90
|
/>
|
|
91
|
+
|
|
84
92
|
<Btn
|
|
85
93
|
class="navigation-btn oval"
|
|
86
94
|
icon="arrow_forward"
|
|
@@ -88,10 +96,26 @@ defineExpose({ open, close })
|
|
|
88
96
|
/>
|
|
89
97
|
</div>
|
|
90
98
|
<div class="bgl-lightbox relative txt-center" @click.stop>
|
|
91
|
-
<
|
|
99
|
+
<div class="flex start fixed top-1 w-100 space-between">
|
|
100
|
+
<Btn flat class="color-white" icon="close" @click="close" />
|
|
101
|
+
<div v-if="currentItem?.enableZoom && currentItem?.type === 'image'" class="center">
|
|
102
|
+
<Btn flat class="color-white" icon="remove" :disabled="zoom === 1" @click="zoom--" />
|
|
103
|
+
<Btn flat class="color-white" icon="zoom_in" :disabled="zoom === 1" @click="zoom = 1" />
|
|
104
|
+
<Btn flat class="color-white" icon="add" :disabled="zoom === 3" @click="zoom++" />
|
|
105
|
+
</div>
|
|
106
|
+
<Btn
|
|
107
|
+
v-if="currentItem?.openFile" class="color-white" round thin flat icon.end="arrow_outward"
|
|
108
|
+
value="Open File"
|
|
109
|
+
:href="currentItem?.src"
|
|
110
|
+
target="_blank"
|
|
111
|
+
/>
|
|
112
|
+
<div v-else />
|
|
113
|
+
</div>
|
|
92
114
|
<div class="bgl-lightbox-item">
|
|
93
115
|
<template v-if="currentItem?.type === 'image'">
|
|
94
|
-
<
|
|
116
|
+
<Zoomer v-model:zoom="zoom" :disabled="!currentItem?.enableZoom" :mouse-wheel-to-zoom="false">
|
|
117
|
+
<img :draggable="false" :src="currentItem?.src" alt="Preview" class="vw90 lightbox-image">
|
|
118
|
+
</Zoomer>
|
|
95
119
|
</template>
|
|
96
120
|
<template v-else-if="currentItem?.type === 'video'">
|
|
97
121
|
<BglVideo
|
|
@@ -43,10 +43,10 @@ function openClickHandler(e: MouseEvent, el: HTMLElement, binding: DirectiveBind
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function bindingToItem(binding: DirectiveBinding, el: HTMLElement): LightboxItem {
|
|
46
|
-
let { group, src, type, name, thumbnail } = binding.value || {}
|
|
46
|
+
let { group, src, type, name, thumbnail, enableZoom, openFile } = binding.value || {}
|
|
47
47
|
src = src || binding.value || el.getAttribute('src') || ''
|
|
48
48
|
type = type || determineFileType(src)
|
|
49
|
-
return { src, type, name, thumbnail, group }
|
|
49
|
+
return { src, type, name, thumbnail, group, enableZoom, openFile }
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const youtubeRegex = /youtube\.com|youtu\.be/
|
package/src/utils/index.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import type { Attributes, BglFormSchemaT } from '@bagelink/vue'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
export function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void {
|
|
4
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
5
|
+
|
|
6
|
+
return function (...args: Parameters<T>) {
|
|
7
|
+
if (timeoutId) {
|
|
8
|
+
clearTimeout(timeoutId)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
timeoutId = setTimeout(() => {
|
|
12
|
+
func(...args)
|
|
13
|
+
}, wait)
|
|
14
|
+
}
|
|
7
15
|
}
|
|
8
16
|
|
|
9
17
|
export function keyToLabel(key?: string): string | undefined {
|