@bagelink/vue 1.6.43 → 1.6.47
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/ModalForm.vue.d.ts.map +1 -1
- package/dist/components/Swiper.vue.d.ts +12 -4
- package/dist/components/Swiper.vue.d.ts.map +1 -1
- package/dist/components/Zoomer.vue.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/index.cjs +10 -10
- package/dist/index.mjs +4178 -4134
- package/dist/plugins/modalTypes.d.ts +1 -8
- package/dist/plugins/modalTypes.d.ts.map +1 -1
- package/dist/plugins/useModal.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/AccordionItem.vue +3 -3
- package/src/components/ModalForm.vue +8 -5
- package/src/components/Swiper.vue +4 -4
- package/src/components/Zoomer.vue +191 -88
- package/src/components/form/inputs/RichText/utils/media.ts +7 -7
- package/src/components/layout/AppContent.vue +6 -3
- package/src/components/layout/AppSidebar.vue +7 -1
- package/src/components/lightbox/Lightbox.vue +270 -120
- package/src/plugins/modalTypes.ts +1 -8
- package/src/plugins/useModal.ts +43 -18
package/package.json
CHANGED
|
@@ -28,11 +28,11 @@ interface AccordionState {
|
|
|
28
28
|
openItem?: string
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const accordionState = inject<AccordionState>('accordionState')
|
|
31
|
+
const accordionState = inject<AccordionState | undefined>('accordionState', undefined)
|
|
32
32
|
const id = props.id || Math.random().toString(36).slice(7)
|
|
33
33
|
|
|
34
34
|
const computedIcon = computed(() => {
|
|
35
|
-
return 'plus_minus'
|
|
35
|
+
return props.iconType === 'plus_minus'
|
|
36
36
|
? (isOpen ? 'remove' : 'add')
|
|
37
37
|
: 'expand_more'
|
|
38
38
|
})
|
|
@@ -44,7 +44,7 @@ if (accordionState) {
|
|
|
44
44
|
watch(
|
|
45
45
|
() => accordionState.openItem,
|
|
46
46
|
(currentOpenId) => {
|
|
47
|
-
if (currentOpenId !== id) {isOpen = false}
|
|
47
|
+
if (currentOpenId !== id) { isOpen = false }
|
|
48
48
|
},
|
|
49
49
|
{ immediate: true }
|
|
50
50
|
)
|
|
@@ -5,7 +5,10 @@ import type { ModalFormOptions } from '../plugins/modalTypes'
|
|
|
5
5
|
import { Btn, Modal, useBagel, BagelForm } from '@bagelink/vue'
|
|
6
6
|
|
|
7
7
|
// eslint-disable-next-line vue/prop-name-casing
|
|
8
|
-
const props = defineProps<ModalFormOptions<T>>()
|
|
8
|
+
const props = withDefaults(defineProps<ModalFormOptions<T>>(), {
|
|
9
|
+
visible: true,
|
|
10
|
+
dismissable: true,
|
|
11
|
+
})
|
|
9
12
|
|
|
10
13
|
const emit = defineEmits<{
|
|
11
14
|
'update:modelValue': [value: T]
|
|
@@ -28,16 +31,16 @@ const closeModal = () => modal?.closeModal()
|
|
|
28
31
|
let submitting = $ref(false)
|
|
29
32
|
|
|
30
33
|
async function runSubmit() {
|
|
31
|
-
if (submitting) {return}
|
|
32
|
-
if (
|
|
34
|
+
if (submitting) { return }
|
|
35
|
+
if (form?.validateForm() === false) { return }
|
|
33
36
|
submitting = true
|
|
34
37
|
try {
|
|
35
38
|
await props.onSubmit?.(formData.value)
|
|
36
39
|
closeModal()
|
|
37
40
|
} catch (err: any) {
|
|
38
41
|
submitting = false
|
|
39
|
-
if (props.onError) {props.onError(err)}
|
|
40
|
-
else {bagel.onError?.(err)}
|
|
42
|
+
if (props.onError) { props.onError(err) }
|
|
43
|
+
else { bagel.onError?.(err) }
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -116,8 +116,8 @@ const emit = defineEmits<{
|
|
|
116
116
|
|
|
117
117
|
defineSlots<{
|
|
118
118
|
'default'?: (props: { item: T, index: number, currentIndex: number }) => any
|
|
119
|
-
'prev-button'?: () => any
|
|
120
|
-
'next-button'?: () => any
|
|
119
|
+
'prev-button'?: (props: { prev: () => void }) => any
|
|
120
|
+
'next-button'?: (props: { next: () => void }) => any
|
|
121
121
|
'navigation'?: (props: {
|
|
122
122
|
items: T[]
|
|
123
123
|
currentIndex: number
|
|
@@ -434,12 +434,12 @@ defineExpose({
|
|
|
434
434
|
<!-- Default Navigation -->
|
|
435
435
|
<div v-if="navigation" class="swi-ctrl">
|
|
436
436
|
<div class="swi-prev hover" @click="handleSlideNav('prev')">
|
|
437
|
-
<slot name="prev-button">
|
|
437
|
+
<slot name="prev-button" :prev="() => handleSlideNav('prev')">
|
|
438
438
|
<Icon name="chevron_left" class="user-select-none hover z-99 color-primary" :size="2" />
|
|
439
439
|
</slot>
|
|
440
440
|
</div>
|
|
441
441
|
<div class="swi-next hover" @click="handleSlideNav('next')">
|
|
442
|
-
<slot name="next-button">
|
|
442
|
+
<slot name="next-button" :next="() => handleSlideNav('next')">
|
|
443
443
|
<Icon name="chevron_right" class="user-select-none hover z-99 color-primary" :size="2" />
|
|
444
444
|
</slot>
|
|
445
445
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { useDebounceFn } from '@bagelink/vue'
|
|
3
|
-
import { onMounted, onUnmounted, watch } from 'vue'
|
|
3
|
+
import { onMounted as vueOnMounted, onUnmounted as vueOnUnmounted, watch as vueWatch } from 'vue'
|
|
4
4
|
import TapDetector from '../utils/tapDetector'
|
|
5
5
|
|
|
6
6
|
// Props interface
|
|
@@ -44,9 +44,9 @@ let translateX = $ref(0)
|
|
|
44
44
|
let animTranslateX = $ref(0)
|
|
45
45
|
let translateY = $ref(0)
|
|
46
46
|
let animTranslateY = $ref(0)
|
|
47
|
-
let _zoom = $ref(zoom
|
|
47
|
+
let _zoom = $ref(zoom ?? 1)
|
|
48
48
|
let scale = $computed({
|
|
49
|
-
get: () => zoom
|
|
49
|
+
get: () => zoom ?? _zoom,
|
|
50
50
|
set: (val) => {
|
|
51
51
|
_zoom = val
|
|
52
52
|
emit('update:zoom', _zoom)
|
|
@@ -68,7 +68,15 @@ let tapDetector = $ref<TapDetector | undefined>()
|
|
|
68
68
|
let translateXValue = $ref(0)
|
|
69
69
|
let translateYValue = $ref(0)
|
|
70
70
|
|
|
71
|
+
// Momentum/inertia tracking
|
|
72
|
+
let velocityX = $ref(0)
|
|
73
|
+
let velocityY = $ref(0)
|
|
74
|
+
let lastMoveTime = $ref(0)
|
|
75
|
+
let momentumRaf = $ref<number | undefined>()
|
|
76
|
+
|
|
71
77
|
const wrapperStyle = $computed(() => {
|
|
78
|
+
// Translation is in normalized coordinates (0.5 = half container)
|
|
79
|
+
// Convert to pixels
|
|
72
80
|
translateXValue = containerWidth * animTranslateX
|
|
73
81
|
translateYValue = containerHeight * animTranslateY
|
|
74
82
|
return {
|
|
@@ -76,14 +84,15 @@ const wrapperStyle = $computed(() => {
|
|
|
76
84
|
`translate(${translateXValue}px, ${translateYValue}px)`,
|
|
77
85
|
`scale(${animScale})`,
|
|
78
86
|
].join(' '),
|
|
87
|
+
transformOrigin: '50% 50%',
|
|
79
88
|
}
|
|
80
89
|
})
|
|
81
90
|
|
|
82
|
-
|
|
91
|
+
vueWatch(() => scale, (newScale) => {
|
|
83
92
|
const { x, y } = calcTranslateLimit()
|
|
84
93
|
translateX = Math.max(-x, Math.min(x, translateX))
|
|
85
94
|
translateY = Math.max(-y, Math.min(y, translateY))
|
|
86
|
-
if (
|
|
95
|
+
if (newScale !== 1) {
|
|
87
96
|
panLocked = false
|
|
88
97
|
}
|
|
89
98
|
})
|
|
@@ -98,22 +107,25 @@ function reset() {
|
|
|
98
107
|
defineExpose({ reset })
|
|
99
108
|
|
|
100
109
|
function tryToScale(scaleDelta: number) {
|
|
101
|
-
if (disabled) {return}
|
|
110
|
+
if (disabled) { return }
|
|
102
111
|
let newScale = scale * scaleDelta
|
|
103
112
|
if (zoomingElastic) {
|
|
104
|
-
if (newScale < minScale
|
|
113
|
+
if (newScale < minScale || newScale > maxScale) {
|
|
105
114
|
let log = Math.log2(scaleDelta)
|
|
106
115
|
log *= 0.2
|
|
107
116
|
scaleDelta = 2 ** log
|
|
108
117
|
newScale = scale * scaleDelta
|
|
109
118
|
}
|
|
110
119
|
} else {
|
|
111
|
-
if (newScale < minScale
|
|
112
|
-
|
|
120
|
+
if (newScale < minScale) {
|
|
121
|
+
newScale = minScale
|
|
122
|
+
} else if (newScale > maxScale) {
|
|
123
|
+
newScale = maxScale
|
|
124
|
+
}
|
|
113
125
|
}
|
|
114
126
|
scaleDelta = newScale / scale
|
|
115
127
|
scale = newScale
|
|
116
|
-
if ('image-center'
|
|
128
|
+
if (pivot !== 'image-center') {
|
|
117
129
|
const normMousePosX = (pointerPosX - containerLeft) / containerWidth
|
|
118
130
|
const normMousePosY = (pointerPosY - containerTop) / containerHeight
|
|
119
131
|
translateX = (0.5 + translateX - normMousePosX) * scaleDelta + normMousePosX - 0.5
|
|
@@ -128,32 +140,78 @@ function setPointerPosCenter() {
|
|
|
128
140
|
|
|
129
141
|
function onPointerMove(newMousePosX: number, newMousePosY: number) {
|
|
130
142
|
if (isPointerDown) {
|
|
143
|
+
const currentTime = Date.now()
|
|
131
144
|
const pixelDeltaX = newMousePosX - pointerPosX
|
|
132
145
|
const pixelDeltaY = newMousePosY - pointerPosY
|
|
146
|
+
|
|
133
147
|
if (!panLocked) {
|
|
148
|
+
// Calculate velocity for momentum
|
|
149
|
+
const timeDelta = currentTime - lastMoveTime
|
|
150
|
+
if (timeDelta > 0) {
|
|
151
|
+
velocityX = pixelDeltaX / timeDelta
|
|
152
|
+
velocityY = pixelDeltaY / timeDelta
|
|
153
|
+
}
|
|
154
|
+
|
|
134
155
|
const translateLimit = calcTranslateLimit()
|
|
135
156
|
const maxTranslateX = translateLimit.x
|
|
136
157
|
const maxTranslateY = translateLimit.y
|
|
137
158
|
const newTranslateX = translateX + pixelDeltaX / containerWidth
|
|
138
159
|
const newTranslateY = translateY + pixelDeltaY / containerHeight
|
|
160
|
+
|
|
139
161
|
translateX = Math.max(-maxTranslateX, Math.min(maxTranslateX, newTranslateX))
|
|
140
162
|
translateY = Math.max(-maxTranslateY, Math.min(maxTranslateY, newTranslateY))
|
|
163
|
+
|
|
164
|
+
lastMoveTime = currentTime
|
|
141
165
|
}
|
|
142
166
|
}
|
|
143
167
|
pointerPosX = newMousePosX
|
|
144
168
|
pointerPosY = newMousePosY
|
|
145
169
|
}
|
|
146
170
|
|
|
171
|
+
function applyMomentum() {
|
|
172
|
+
if (Math.abs(velocityX) < 0.01 && Math.abs(velocityY) < 0.01) {
|
|
173
|
+
velocityX = 0
|
|
174
|
+
velocityY = 0
|
|
175
|
+
if (momentumRaf) {
|
|
176
|
+
cancelAnimationFrame(momentumRaf)
|
|
177
|
+
momentumRaf = undefined
|
|
178
|
+
}
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const translateLimit = calcTranslateLimit()
|
|
183
|
+
const newTranslateX = translateX + velocityX * 16 / containerWidth
|
|
184
|
+
const newTranslateY = translateY + velocityY * 16 / containerHeight
|
|
185
|
+
|
|
186
|
+
translateX = Math.max(-translateLimit.x, Math.min(translateLimit.x, newTranslateX))
|
|
187
|
+
translateY = Math.max(-translateLimit.y, Math.min(translateLimit.y, newTranslateY))
|
|
188
|
+
|
|
189
|
+
// Apply friction
|
|
190
|
+
velocityX *= 0.95
|
|
191
|
+
velocityY *= 0.95
|
|
192
|
+
|
|
193
|
+
momentumRaf = requestAnimationFrame(applyMomentum)
|
|
194
|
+
}
|
|
195
|
+
|
|
147
196
|
const onInteractionEnd = useDebounceFn(() => {
|
|
148
197
|
limit()
|
|
149
|
-
panLocked =
|
|
150
|
-
|
|
198
|
+
panLocked = scale === 1
|
|
199
|
+
|
|
200
|
+
// Start momentum animation if there's significant velocity
|
|
201
|
+
if (!panLocked && (Math.abs(velocityX) > 0.1 || Math.abs(velocityY) > 0.1)) {
|
|
202
|
+
if (momentumRaf) cancelAnimationFrame(momentumRaf)
|
|
203
|
+
applyMomentum()
|
|
204
|
+
} else {
|
|
205
|
+
velocityX = 0
|
|
206
|
+
velocityY = 0
|
|
207
|
+
}
|
|
208
|
+
}, 50)
|
|
151
209
|
|
|
152
210
|
function limit() {
|
|
153
|
-
if (scale < minScale
|
|
154
|
-
scale = minScale
|
|
155
|
-
} else if (scale > maxScale
|
|
156
|
-
tryToScale(maxScale
|
|
211
|
+
if (scale < minScale) {
|
|
212
|
+
scale = minScale
|
|
213
|
+
} else if (scale > maxScale) {
|
|
214
|
+
tryToScale(maxScale / scale)
|
|
157
215
|
}
|
|
158
216
|
|
|
159
217
|
if (limitTranslation) {
|
|
@@ -168,42 +226,39 @@ function limit() {
|
|
|
168
226
|
}
|
|
169
227
|
|
|
170
228
|
function calcTranslateLimit() {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
229
|
+
// When scale = 1, no panning needed (image fits in container)
|
|
230
|
+
// When scale > 1, we can pan to see parts of the image outside the viewport
|
|
231
|
+
|
|
232
|
+
// The image is scaled from center, so:
|
|
233
|
+
// - At scale 2, the image is 2x larger
|
|
234
|
+
// - The amount that extends beyond the container on EACH side is: (scale - 1) * containerSize / 2
|
|
235
|
+
// - In normalized coordinates (where 1.0 = full container), this is: (scale - 1) / 2
|
|
236
|
+
|
|
237
|
+
// Simple and correct approach:
|
|
238
|
+
// Max pan distance in normalized coords = (scale - 1) / 2
|
|
239
|
+
// This works for both X and Y directions regardless of aspect ratio
|
|
240
|
+
|
|
241
|
+
const basePanLimit = Math.max(0, (scale - 1) / 2)
|
|
242
|
+
|
|
183
243
|
return {
|
|
184
|
-
x:
|
|
185
|
-
y:
|
|
244
|
+
x: basePanLimit,
|
|
245
|
+
y: basePanLimit,
|
|
186
246
|
}
|
|
187
247
|
}
|
|
188
248
|
|
|
189
|
-
function getMarginDirection() {
|
|
190
|
-
const containerRatio = containerWidth / containerHeight
|
|
191
|
-
return containerRatio > aspectRatio ? 'x' : 'y'
|
|
192
|
-
}
|
|
193
|
-
|
|
194
249
|
function onMouseWheel(ev: WheelEvent) {
|
|
195
|
-
if (!mouseWheelToZoom) {return}
|
|
250
|
+
if (!mouseWheelToZoom) { return }
|
|
196
251
|
ev.preventDefault()
|
|
197
252
|
const currTime = Date.now()
|
|
198
|
-
if (
|
|
199
|
-
if (
|
|
253
|
+
if (Math.abs(ev.deltaY) === 120) {
|
|
254
|
+
if (currTime - lastFullWheelTime > 50) {
|
|
200
255
|
onMouseWheelDo(ev.deltaY)
|
|
201
256
|
lastFullWheelTime = currTime
|
|
202
257
|
}
|
|
203
258
|
} else {
|
|
204
|
-
if (
|
|
259
|
+
if (currTime - lastWheelTime > 50) {
|
|
205
260
|
lastWheelDirection = ev.deltaX > ev.deltaY ? 'x' : 'y'
|
|
206
|
-
if ('y'
|
|
261
|
+
if (lastWheelDirection === 'y') {
|
|
207
262
|
onMouseWheelDo(ev.deltaY)
|
|
208
263
|
}
|
|
209
264
|
}
|
|
@@ -212,7 +267,8 @@ function onMouseWheel(ev: WheelEvent) {
|
|
|
212
267
|
}
|
|
213
268
|
|
|
214
269
|
function onMouseWheelDo(wheelDelta: number) {
|
|
215
|
-
|
|
270
|
+
// More responsive zoom speed
|
|
271
|
+
const scaleDelta = 1.15 ** (wheelDelta / 120)
|
|
216
272
|
tryToScale(scaleDelta)
|
|
217
273
|
onInteractionEnd()
|
|
218
274
|
}
|
|
@@ -222,6 +278,13 @@ function onMouseDown(ev: MouseEvent) {
|
|
|
222
278
|
isPointerDown = true
|
|
223
279
|
pointerPosX = ev.clientX
|
|
224
280
|
pointerPosY = ev.clientY
|
|
281
|
+
lastMoveTime = Date.now()
|
|
282
|
+
velocityX = 0
|
|
283
|
+
velocityY = 0
|
|
284
|
+
if (momentumRaf) {
|
|
285
|
+
cancelAnimationFrame(momentumRaf)
|
|
286
|
+
momentumRaf = undefined
|
|
287
|
+
}
|
|
225
288
|
document.addEventListener('mousemove', onMouseMove)
|
|
226
289
|
document.addEventListener('mouseup', onMouseUp)
|
|
227
290
|
}
|
|
@@ -234,16 +297,33 @@ function onMouseUp() {
|
|
|
234
297
|
}
|
|
235
298
|
|
|
236
299
|
function onMouseMove(ev: MouseEvent) {
|
|
300
|
+
// Prevent swiper from handling mouse drag when panning zoomed image
|
|
301
|
+
if (!panLocked && scale > 1) {
|
|
302
|
+
ev.stopPropagation()
|
|
303
|
+
}
|
|
237
304
|
onPointerMove(ev.clientX, ev.clientY)
|
|
238
305
|
}
|
|
239
306
|
|
|
240
307
|
function onTouchStart(ev: TouchEvent) {
|
|
241
|
-
if (
|
|
308
|
+
if (ev.touches.length === 1) {
|
|
242
309
|
refreshContainerPos()
|
|
243
310
|
pointerPosX = ev.touches[0].clientX
|
|
244
311
|
pointerPosY = ev.touches[0].clientY
|
|
312
|
+
lastMoveTime = Date.now()
|
|
313
|
+
velocityX = 0
|
|
314
|
+
velocityY = 0
|
|
315
|
+
if (momentumRaf) {
|
|
316
|
+
cancelAnimationFrame(momentumRaf)
|
|
317
|
+
momentumRaf = undefined
|
|
318
|
+
}
|
|
245
319
|
isPointerDown = true
|
|
246
|
-
} else if (
|
|
320
|
+
} else if (ev.touches.length === 2) {
|
|
321
|
+
if (momentumRaf) {
|
|
322
|
+
cancelAnimationFrame(momentumRaf)
|
|
323
|
+
momentumRaf = undefined
|
|
324
|
+
}
|
|
325
|
+
velocityX = 0
|
|
326
|
+
velocityY = 0
|
|
247
327
|
isPointerDown = true
|
|
248
328
|
pointerPosX = (ev.touches[0].clientX + ev.touches[1].clientX) / 2
|
|
249
329
|
pointerPosY = (ev.touches[0].clientY + ev.touches[1].clientY) / 2
|
|
@@ -255,11 +335,11 @@ function onTouchStart(ev: TouchEvent) {
|
|
|
255
335
|
}
|
|
256
336
|
|
|
257
337
|
function onTouchEnd(ev: TouchEvent) {
|
|
258
|
-
if (
|
|
338
|
+
if (ev.touches.length === 0) {
|
|
259
339
|
isPointerDown = false
|
|
260
|
-
if (
|
|
340
|
+
if (Math.abs(scale - 1) < 0.1) { scale = 1 }
|
|
261
341
|
onInteractionEnd()
|
|
262
|
-
} else if (
|
|
342
|
+
} else if (ev.touches.length === 1) {
|
|
263
343
|
pointerPosX = ev.touches[0].clientX
|
|
264
344
|
pointerPosY = ev.touches[0].clientY
|
|
265
345
|
}
|
|
@@ -267,11 +347,15 @@ function onTouchEnd(ev: TouchEvent) {
|
|
|
267
347
|
}
|
|
268
348
|
|
|
269
349
|
function onTouchMove(ev: TouchEvent) {
|
|
270
|
-
if (
|
|
350
|
+
if (ev.touches.length === 1) {
|
|
351
|
+
// Prevent swiper from handling touch when panning zoomed image
|
|
352
|
+
if (!panLocked && scale > 1) {
|
|
353
|
+
ev.stopPropagation()
|
|
354
|
+
}
|
|
271
355
|
onPointerMove(ev.touches[0].clientX, ev.touches[0].clientY)
|
|
272
|
-
} else if (2
|
|
273
|
-
|
|
274
|
-
|
|
356
|
+
} else if (ev.touches.length === 2) {
|
|
357
|
+
// Always prevent default during pinch zoom
|
|
358
|
+
ev.stopPropagation()
|
|
275
359
|
const distX = ev.touches[0].clientX - ev.touches[1].clientX
|
|
276
360
|
const distY = ev.touches[0].clientY - ev.touches[1].clientY
|
|
277
361
|
const newTwoFingerDist = Math.sqrt(distX * distX + distY * distY)
|
|
@@ -296,16 +380,21 @@ function loop() {
|
|
|
296
380
|
}
|
|
297
381
|
|
|
298
382
|
function gainOn(from: number, to: number) {
|
|
299
|
-
|
|
300
|
-
|
|
383
|
+
// Smoother easing with better responsiveness
|
|
384
|
+
const delta = (to - from) * 0.4
|
|
385
|
+
return Math.abs(delta) > 1e-5 ? from + delta : to
|
|
301
386
|
}
|
|
302
387
|
|
|
303
388
|
// Lifecycle hooks
|
|
304
|
-
|
|
389
|
+
vueOnMounted(() => {
|
|
305
390
|
tapDetector = new TapDetector()
|
|
306
|
-
if (zoomElement) {tapDetector.attach(zoomElement)}
|
|
391
|
+
if (zoomElement) { tapDetector.attach(zoomElement) }
|
|
307
392
|
if (doubleClickToZoom) {
|
|
308
393
|
tapDetector.onDoubleTap(onDoubleTap as any)
|
|
394
|
+
// Also add native dblclick event for mouse
|
|
395
|
+
if (zoomElement) {
|
|
396
|
+
zoomElement.addEventListener('dblclick', onDoubleTap as any)
|
|
397
|
+
}
|
|
309
398
|
}
|
|
310
399
|
window.addEventListener('resize', onWindowResize)
|
|
311
400
|
onWindowResize()
|
|
@@ -313,20 +402,38 @@ onMounted(() => {
|
|
|
313
402
|
loop()
|
|
314
403
|
})
|
|
315
404
|
|
|
316
|
-
|
|
317
|
-
if (zoomElement) {
|
|
405
|
+
vueOnUnmounted(() => {
|
|
406
|
+
if (zoomElement) {
|
|
407
|
+
tapDetector?.detach(zoomElement)
|
|
408
|
+
if (doubleClickToZoom) {
|
|
409
|
+
zoomElement.removeEventListener('dblclick', onDoubleTap as any)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
318
412
|
window.removeEventListener('resize', onWindowResize)
|
|
319
|
-
if (raf) {window.cancelAnimationFrame(raf)}
|
|
413
|
+
if (raf) { window.cancelAnimationFrame(raf) }
|
|
414
|
+
if (momentumRaf) { window.cancelAnimationFrame(momentumRaf) }
|
|
320
415
|
})
|
|
321
416
|
|
|
322
417
|
function onDoubleTap(ev: MouseEvent) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
418
|
+
// Update pointer position if available
|
|
419
|
+
if (ev.clientX > 0) {
|
|
420
|
+
pointerPosX = ev.clientX
|
|
421
|
+
pointerPosY = ev.clientY
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Calculate zoom levels
|
|
425
|
+
const mediumZoom = Math.min(2.5, maxScale / 2 + 0.5)
|
|
426
|
+
const highZoom = maxScale
|
|
427
|
+
|
|
428
|
+
// Cycle through three states: 1x -> medium -> max -> 1x
|
|
429
|
+
if (scale === 1) {
|
|
430
|
+
// From no zoom -> medium zoom
|
|
431
|
+
tryToScale(mediumZoom)
|
|
432
|
+
} else if (scale < highZoom - 0.1) {
|
|
433
|
+
// From medium zoom -> max zoom
|
|
434
|
+
tryToScale(highZoom / scale)
|
|
329
435
|
} else {
|
|
436
|
+
// From max zoom -> no zoom (reset)
|
|
330
437
|
reset()
|
|
331
438
|
}
|
|
332
439
|
}
|
|
@@ -344,16 +451,9 @@ function onWindowResize() {
|
|
|
344
451
|
|
|
345
452
|
<template>
|
|
346
453
|
<div
|
|
347
|
-
ref="zoomElement"
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
@mousewheel="onMouseWheel"
|
|
351
|
-
@DOMMouseScroll="onMouseWheel"
|
|
352
|
-
@mousedown="onMouseDown"
|
|
353
|
-
@mouseout="setPointerPosCenter"
|
|
354
|
-
@focusout="setPointerPosCenter"
|
|
355
|
-
@touchstart="onTouchStart"
|
|
356
|
-
@touchmove="onTouchMove"
|
|
454
|
+
ref="zoomElement" class="vue-zoomer" :style="{ backgroundColor }" @mousewheel="onMouseWheel"
|
|
455
|
+
@DOMMouseScroll="onMouseWheel" @mousedown="onMouseDown" @mouseout="setPointerPosCenter"
|
|
456
|
+
@focusout="setPointerPosCenter" @touchstart="onTouchStart" @touchmove="onTouchMove"
|
|
357
457
|
>
|
|
358
458
|
<div class="zoomer" :style="wrapperStyle">
|
|
359
459
|
<slot />
|
|
@@ -371,24 +471,27 @@ function onWindowResize() {
|
|
|
371
471
|
z-index: 1000;
|
|
372
472
|
height: 100px
|
|
373
473
|
}
|
|
474
|
+
|
|
374
475
|
.vue-zoomer {
|
|
375
|
-
|
|
476
|
+
overflow: hidden;
|
|
376
477
|
}
|
|
478
|
+
|
|
377
479
|
.zoomer {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
480
|
+
transform-origin: 50% 50%;
|
|
481
|
+
width: 100%;
|
|
482
|
+
height: 100%;
|
|
483
|
+
max-width: 100vw;
|
|
484
|
+
max-height: 100vh;
|
|
383
485
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
486
|
+
|
|
487
|
+
.zoomer>img {
|
|
488
|
+
vertical-align: top;
|
|
489
|
+
user-select: none;
|
|
490
|
+
-moz-user-drag: none;
|
|
491
|
+
-webkit-user-select: none;
|
|
492
|
+
-moz-user-select: none;
|
|
493
|
+
-ms-user-select: none;
|
|
494
|
+
-webkit-user-drag: none;
|
|
495
|
+
-moz-user-drag: none;
|
|
393
496
|
}
|
|
394
497
|
</style>
|
|
@@ -4,7 +4,7 @@ import { bagelFormUtils as frm } from '../../../../../utils'
|
|
|
4
4
|
|
|
5
5
|
export function insertImage(modal: ModalApi, state: EditorState) {
|
|
6
6
|
const { range, doc } = state
|
|
7
|
-
if (!range || !doc) {return}
|
|
7
|
+
if (!range || !doc) { return }
|
|
8
8
|
|
|
9
9
|
modal.showModalForm<{
|
|
10
10
|
src: string
|
|
@@ -73,7 +73,7 @@ export function insertLink(modal: ModalApi, state: EditorState) {
|
|
|
73
73
|
if (!success) {
|
|
74
74
|
console.log('Failed to select word at cursor')
|
|
75
75
|
// Show a helpful tooltip to the user
|
|
76
|
-
const showTooltipMessage =
|
|
76
|
+
const { showTooltipMessage } = state as any
|
|
77
77
|
if (showTooltipMessage) {
|
|
78
78
|
showTooltipMessage('To add a link, please select text first or place cursor within a word')
|
|
79
79
|
} else {
|
|
@@ -107,7 +107,7 @@ export function insertLink(modal: ModalApi, state: EditorState) {
|
|
|
107
107
|
|
|
108
108
|
// Helper function to select word at cursor
|
|
109
109
|
function selectWordAtCursor(doc: Document, range: Range): boolean {
|
|
110
|
-
if (!range.collapsed) {return true} // Already has selection
|
|
110
|
+
if (!range.collapsed) { return true } // Already has selection
|
|
111
111
|
|
|
112
112
|
let textNode = range.startContainer
|
|
113
113
|
let offset = range.startOffset
|
|
@@ -115,7 +115,7 @@ function selectWordAtCursor(doc: Document, range: Range): boolean {
|
|
|
115
115
|
// If we're in an element node, try to find a text node
|
|
116
116
|
if (textNode.nodeType !== Node.TEXT_NODE) {
|
|
117
117
|
const element = textNode as Element
|
|
118
|
-
if (
|
|
118
|
+
if (element.childNodes.length > 0 && offset < element.childNodes.length) {
|
|
119
119
|
const childNode = element.childNodes[offset]
|
|
120
120
|
if (childNode.nodeType === Node.TEXT_NODE) {
|
|
121
121
|
textNode = childNode
|
|
@@ -129,7 +129,7 @@ function selectWordAtCursor(doc: Document, range: Range): boolean {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
const text = textNode.textContent || ''
|
|
132
|
-
if (!text) {return false}
|
|
132
|
+
if (!text) { return false }
|
|
133
133
|
|
|
134
134
|
// Find word boundaries - support Hebrew, English, and numbers
|
|
135
135
|
let start = offset
|
|
@@ -139,7 +139,7 @@ function selectWordAtCursor(doc: Document, range: Range): boolean {
|
|
|
139
139
|
const wordRegex = /[\u0590-\u05FF\u0600-\u06FF\w]/
|
|
140
140
|
|
|
141
141
|
// Move start backwards to find beginning of word
|
|
142
|
-
while (
|
|
142
|
+
while (start > 0 && wordRegex.test(text[start - 1])) {
|
|
143
143
|
start--
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -173,7 +173,7 @@ export interface InsertImbedModalData {
|
|
|
173
173
|
|
|
174
174
|
export function insertEmbed(modal: ModalApi, state: EditorState) {
|
|
175
175
|
const { range, doc } = state
|
|
176
|
-
if (!range || !doc) {return}
|
|
176
|
+
if (!range || !doc) { return }
|
|
177
177
|
|
|
178
178
|
modal.showModalForm<InsertImbedModalData>({
|
|
179
179
|
title: 'Embed iframe',
|
|
@@ -18,7 +18,11 @@ withDefaults(defineProps<Props>(), {
|
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
// Inject menu state from parent
|
|
21
|
-
const menuState = inject('menuState'
|
|
21
|
+
const menuState = inject('menuState', {
|
|
22
|
+
isOpen: { value: true },
|
|
23
|
+
isMobile: { value: false },
|
|
24
|
+
toggleMenu: () => { },
|
|
25
|
+
}) as {
|
|
22
26
|
isOpen: { value: boolean }
|
|
23
27
|
isMobile: { value: boolean }
|
|
24
28
|
toggleMenu: () => void
|
|
@@ -83,8 +87,7 @@ const hasSidebarCard = computed(() => {
|
|
|
83
87
|
|
|
84
88
|
<!-- Page Content -->
|
|
85
89
|
<main
|
|
86
|
-
class="pageContent flex-grow overflow pt-1 pb-05 w-100p m_p-05 m_scrollbar-gutter-auto m_vw100"
|
|
87
|
-
:class="{
|
|
90
|
+
class="pageContent flex-grow overflow pt-1 pb-05 w-100p m_p-05 m_scrollbar-gutter-auto m_vw100" :class="{
|
|
88
91
|
'px-1': !hasSidebarCard,
|
|
89
92
|
}"
|
|
90
93
|
>
|
|
@@ -38,7 +38,13 @@ const route = useRoute()
|
|
|
38
38
|
const isTransitioning = ref(false)
|
|
39
39
|
|
|
40
40
|
// Inject menu state from parent
|
|
41
|
-
const menuState = inject('menuState'
|
|
41
|
+
const menuState = inject('menuState', {
|
|
42
|
+
isOpen: { value: true },
|
|
43
|
+
isMobile: { value: false },
|
|
44
|
+
closeOnMobile: () => {},
|
|
45
|
+
sidebarWidth: '260px',
|
|
46
|
+
sidebarCollapsedWidth: '66px',
|
|
47
|
+
}) as {
|
|
42
48
|
isOpen: { value: boolean }
|
|
43
49
|
isMobile: { value: boolean }
|
|
44
50
|
closeOnMobile: () => void
|