@bagelink/vue 0.0.753 → 0.0.757

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.
Files changed (35) hide show
  1. package/dist/components/Zoomer.vue.d.ts +40 -0
  2. package/dist/components/Zoomer.vue.d.ts.map +1 -0
  3. package/dist/components/index.d.ts +1 -0
  4. package/dist/components/index.d.ts.map +1 -1
  5. package/dist/components/layout/Tabs.vue.d.ts +2 -0
  6. package/dist/components/layout/Tabs.vue.d.ts.map +1 -1
  7. package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
  8. package/dist/components/lightbox/lightbox.types.d.ts +2 -0
  9. package/dist/components/lightbox/lightbox.types.d.ts.map +1 -1
  10. package/dist/index.cjs +732 -255
  11. package/dist/index.mjs +732 -255
  12. package/dist/style.css +141 -97
  13. package/dist/utils/index.d.ts +1 -1
  14. package/dist/utils/index.d.ts.map +1 -1
  15. package/dist/utils/tapDetector.d.ts +30 -0
  16. package/dist/utils/tapDetector.d.ts.map +1 -0
  17. package/package.json +1 -1
  18. package/src/components/Btn.vue +1 -1
  19. package/src/components/Zoomer.vue +377 -0
  20. package/src/components/form/inputs/RadioGroup.vue +2 -2
  21. package/src/components/form/inputs/RichText2/Toolbar.vue +1 -1
  22. package/src/components/form/inputs/RichText2/index.vue +3 -3
  23. package/src/components/form/inputs/ToggleInput.vue +1 -1
  24. package/src/components/formkit/Toggle.vue +1 -1
  25. package/src/components/index.ts +1 -0
  26. package/src/components/layout/SidebarMenu.vue +1 -1
  27. package/src/components/layout/Tabs.vue +6 -3
  28. package/src/components/lightbox/Lightbox.vue +30 -6
  29. package/src/components/lightbox/index.ts +2 -2
  30. package/src/components/lightbox/lightbox.types.ts +2 -0
  31. package/src/styles/layout.css +18 -2
  32. package/src/styles/mobilLayout.css +16 -2
  33. package/src/utils/BagelFormUtils.ts +1 -1
  34. package/src/utils/index.ts +12 -4
  35. package/src/utils/tapDetector.ts +119 -0
@@ -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>
@@ -26,7 +26,7 @@ const selectedOption = defineModel('modelValue')
26
26
  <label
27
27
  v-for="opt in options"
28
28
  :key="opt.id"
29
- class="border round p-1 flex bg-gray-light mb-05 gap-075 active-list-item hover"
29
+ class="border rounded p-1 flex bg-gray-light mb-05 gap-075 active-list-item hover"
30
30
  :for="opt.id"
31
31
  >
32
32
  <input
@@ -40,7 +40,7 @@ const selectedOption = defineModel('modelValue')
40
40
  <div class="flex w-100 gap-1 flex-wrap m_gap-05 m_gap-row-025">
41
41
  <img
42
42
  v-if="opt.imgSrc"
43
- class="bg-popup shadow-light py-025 rounded m_w40"
43
+ class="bg-popup shadow-light py-025 radius-05 m_w40"
44
44
  width="60"
45
45
  :src="opt.imgSrc"
46
46
  :alt="opt.imgAlt"
@@ -67,7 +67,7 @@ function handleSelectChange(selectedOption: string) {
67
67
  thin
68
68
  flat
69
69
  :class="action.class"
70
- class="rounded"
70
+ class="radius-05"
71
71
  :aria-label="action.name"
72
72
  :icon="action.icon"
73
73
  @click="emit('action', action.name)"
@@ -134,10 +134,10 @@ function handleKeyDown(event: KeyboardEvent) {
134
134
  </script>
135
135
 
136
136
  <template>
137
- <div class="rich-text-editor round pt-05 px-1 pb-1">
137
+ <div class="rich-text-editor rounded pt-05 px-1 pb-1">
138
138
  <Toolbar :config @action="handleToolbarAction" />
139
139
  <div class="editor-container flex flex-stretch gap-1 m_column">
140
- <div class="content-area rounded p-1 shadow-light w-100 grid">
140
+ <div class="content-area radius-05 p-1 shadow-light w-100 grid">
141
141
  <textarea v-if="isCodeView" v-model="contentHtml" @input="updateContent" />
142
142
  <div
143
143
  v-else
@@ -151,7 +151,7 @@ function handleKeyDown(event: KeyboardEvent) {
151
151
  @keydown="handleKeyDown"
152
152
  />
153
153
  </div>
154
- <code v-if="isSplitView" class="preview-area w-100 rounded p-1">{{ contentHtml }}</code>
154
+ <code v-if="isSplitView" class="preview-area w-100 radius-05 p-1">{{ contentHtml }}</code>
155
155
  </div>
156
156
  </div>
157
157
  </template>
@@ -26,7 +26,7 @@ onMounted(() => {
26
26
  :class="{ small }"
27
27
  >
28
28
  <div class="switch" :class="{ checked }">
29
- <span class="slider round" />
29
+ <span class="slider rounded" />
30
30
  </div>
31
31
 
32
32
  <input
@@ -14,7 +14,7 @@ const inputVal = defineModel('modelValue', {
14
14
  <div class="bagel-input checkbox" :title="label">
15
15
  <label class="switch">
16
16
  <input :id="id" v-model="inputVal" type="checkbox">
17
- <span class="slider round" />
17
+ <span class="slider rounded" />
18
18
  </label>
19
19
  </div>
20
20
  </template>
@@ -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'
@@ -32,7 +32,7 @@ function toggleMenu() {
32
32
  @click="toggleMenu"
33
33
  />
34
34
  <Card
35
- class="py-1 px-05 h-100 flex column gap-05 round relative bg-primary font-light overflow-y "
35
+ class="py-1 px-05 h-100 flex column gap-05 rounded relative bg-primary font-light overflow-y "
36
36
  >
37
37
  <slot v-if="!isOpen || !slots['brand-open']" name="brand" />
38
38
  <slot v-if="isOpen" name="brand-open" />
@@ -8,6 +8,7 @@ import { useTabs } from './tabsManager'
8
8
  const props = defineProps<{
9
9
  tabs: Tab[]
10
10
  modelValue?: string
11
+ flat?: boolean
11
12
  }>()
12
13
 
13
14
  const emit = defineEmits(['update:modelValue'])
@@ -19,13 +20,15 @@ const tabValue = (tab: Tab) => (typeof tab === 'string' ? tab : tab.id)
19
20
 
20
21
  const slctTab = $computed({
21
22
  get: () => props.modelValue || tabValue(props.tabs[0]),
22
- set: (value) => { emit('update:modelValue', value) },
23
+ set: value => {
24
+ emit('update:modelValue', value)
25
+ },
23
26
  })
24
27
 
25
28
  const tabComponent = defineComponent({
26
29
  render() {
27
30
  const currentTabIndex = props.tabs.findIndex(
28
- tab => tabValue(tab) === currentTab.value,
31
+ tab => tabValue(tab) === currentTab.value
29
32
  )
30
33
  if (currentTabIndex === -1) return null
31
34
  const slotChildren = slots.default?.()[1]?.children
@@ -37,7 +40,7 @@ const tabComponent = defineComponent({
37
40
 
38
41
  z
39
42
  <template>
40
- <TabsNav v-model="slctTab" :tabs :group class="mb-05" />
43
+ <TabsNav v-model="slctTab" :flat :tabs :group class="mb-05" />
41
44
  <div v-if="currentTab">
42
45
  <template v-if="slots[currentTab]">
43
46
  <slot :name="currentTab" />
@@ -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="close"
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
- <Btn flat class="fixed top-1 start-1 color-white" icon="close" @click="close" />
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
- <img :src="currentItem?.src" alt="Preview" class="vw90 lightbox-image">
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
@@ -131,7 +155,7 @@ defineExpose({ open, close })
131
155
  <img
132
156
  v-if="item.type === 'image'"
133
157
  class="thumbnail object-fit-cover hover
134
- opacity-5 round flex bg-popup justify-content-center align-items-center flex-shrink-0"
158
+ opacity-5 rounded flex bg-popup justify-content-center align-items-center flex-shrink-0"
135
159
  :src="item.src"
136
160
  alt=""
137
161
  :class="{ active: currentIndex === index }"
@@ -140,7 +164,7 @@ defineExpose({ open, close })
140
164
  <Icon
141
165
  v-else
142
166
  class="thumbnail object-fit-cover hover
143
- opacity-5 round flex bg-popup justify-content-center align-items-center flex-shrink-0"
167
+ opacity-5 ed flex bg-popup justify-content-center align-items-center flex-shrink-0"
144
168
  icon="description"
145
169
  :class="{ active: currentIndex === index }"
146
170
  @click="selectItem(index)"
@@ -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/
@@ -4,4 +4,6 @@ export interface LightboxItem {
4
4
  name: string
5
5
  thumbnail?: string
6
6
  group?: string
7
+ enableZoom?: boolean
8
+ openFile?: boolean
7
9
  }
@@ -11,18 +11,34 @@
11
11
  height: 100vh;
12
12
  }
13
13
 
14
+
14
15
  .round {
16
+ border-radius: 1000px;
17
+ }
18
+
19
+ .rounded,
20
+ .radius,
21
+ .radius-1 {
15
22
  border-radius: var(--btn-border-radius) !important;
16
23
  }
17
24
 
18
- .rounded {
25
+ .radius-05 {
19
26
  border-radius: calc(var(--btn-border-radius) / 2) !important;
20
27
  }
21
28
 
22
- .round-extra {
29
+ .radius-2 {
23
30
  border-radius: calc(var(--btn-border-radius) * 2) !important;
24
31
  }
25
32
 
33
+ .radius-3 {
34
+ border-radius: calc(var(--btn-border-radius) * 3) !important;
35
+ }
36
+
37
+ .radius-4 {
38
+ border-radius: calc(var(--btn-border-radius) * 4) !important;
39
+ }
40
+
41
+
26
42
  .round-none {
27
43
  border-radius: 0;
28
44
  }
@@ -35,17 +35,31 @@
35
35
  }
36
36
 
37
37
  .m_round {
38
+ border-radius: 1000px;
39
+ }
40
+
41
+ .m_rounded,
42
+ .m_radius,
43
+ .m_radius-1 {
38
44
  border-radius: var(--btn-border-radius) !important;
39
45
  }
40
46
 
41
- .m_rounded {
47
+ .m_radius-05 {
42
48
  border-radius: calc(var(--btn-border-radius) / 2) !important;
43
49
  }
44
50
 
45
- .m_round-extra {
51
+ .m_radius-2 {
46
52
  border-radius: calc(var(--btn-border-radius) * 2) !important;
47
53
  }
48
54
 
55
+ .m_radius-3 {
56
+ border-radius: calc(var(--btn-border-radius) * 3) !important;
57
+ }
58
+
59
+ .m_radius-4 {
60
+ border-radius: calc(var(--btn-border-radius) * 4) !important;
61
+ }
62
+
49
63
  .m_round-none {
50
64
  border-radius: 0 !important;
51
65
  }
@@ -160,7 +160,7 @@ export function numField(
160
160
  export function frmRow(...children: Field[]) {
161
161
  return {
162
162
  $el: 'div',
163
- class: 'flex gap-1 m_block',
163
+ class: 'flex gap-1 m_block align-items-end',
164
164
  children,
165
165
  }
166
166
  }