@hina-ui/vue 1.3.0 → 1.4.0
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/index.js +4326 -2130
- package/dist/index.js.map +1 -1
- package/dist/types/components/alert-dialog/AlertDialog.vue.d.ts +2 -2
- package/dist/types/components/alert-dialog/AlertDialog.vue.d.ts.map +1 -1
- package/dist/types/components/dialog/Dialog.vue.d.ts.map +1 -1
- package/dist/types/components/dialog/ModalContent.vue.d.ts +21 -0
- package/dist/types/components/dialog/ModalContent.vue.d.ts.map +1 -0
- package/dist/types/components/dropdown-menu/DropdownMenu.vue.d.ts.map +1 -1
- package/dist/types/components/file-upload/FileUpload.vue.d.ts +1 -1
- package/dist/types/components/file-upload/FileUpload.vue.d.ts.map +1 -1
- package/dist/types/components/hover-card/HoverCard.vue.d.ts +7 -7
- package/dist/types/components/hover-card/HoverCard.vue.d.ts.map +1 -1
- package/dist/types/components/hover-card/HoverCardAnchor.vue.d.ts +27 -0
- package/dist/types/components/hover-card/HoverCardAnchor.vue.d.ts.map +1 -0
- package/dist/types/components/hover-card/composables/useHoverCardAnchor.d.ts +17 -0
- package/dist/types/components/hover-card/composables/useHoverCardAnchor.d.ts.map +1 -0
- package/dist/types/components/image/Image.vue.d.ts +8 -3
- package/dist/types/components/image/Image.vue.d.ts.map +1 -1
- package/dist/types/components/image/composables/useImage.d.ts.map +1 -1
- package/dist/types/components/image/composables/useImageGroupState.d.ts.map +1 -1
- package/dist/types/components/image/image.variants.d.ts +23 -1
- package/dist/types/components/image/image.variants.d.ts.map +1 -1
- package/dist/types/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/types/components/lightbox/LightboxChrome.vue.d.ts +4 -0
- package/dist/types/components/lightbox/LightboxChrome.vue.d.ts.map +1 -1
- package/dist/types/components/lightbox/LightboxToolbar.vue.d.ts +4 -0
- package/dist/types/components/lightbox/LightboxToolbar.vue.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxFrames.d.ts +4 -1
- package/dist/types/components/lightbox/composables/useLightboxFrames.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxLarge.d.ts +1 -0
- package/dist/types/components/lightbox/composables/useLightboxLarge.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxLayout.d.ts +17 -0
- package/dist/types/components/lightbox/composables/useLightboxLayout.d.ts.map +1 -0
- package/dist/types/components/lightbox/composables/useLightboxMotion.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxPhase.d.ts +1 -1
- package/dist/types/components/lightbox/composables/useLightboxPhase.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxPose.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxRotation.d.ts +5 -1
- package/dist/types/components/lightbox/composables/useLightboxRotation.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxZoom.d.ts +10 -1
- package/dist/types/components/lightbox/composables/useLightboxZoom.d.ts.map +1 -1
- package/dist/types/components/lightbox/lightbox.variants.d.ts +2 -0
- package/dist/types/components/lightbox/lightbox.variants.d.ts.map +1 -0
- package/dist/types/components/lightbox/types.d.ts +7 -1
- package/dist/types/components/lightbox/types.d.ts.map +1 -1
- package/dist/types/components/lightbox/utils/clip.d.ts +16 -0
- package/dist/types/components/lightbox/utils/clip.d.ts.map +1 -0
- package/dist/types/components/lightbox/utils/pose.d.ts +4 -4
- package/dist/types/components/lightbox/utils/pose.d.ts.map +1 -1
- package/dist/types/components/lightbox/utils/source.d.ts +4 -0
- package/dist/types/components/lightbox/utils/source.d.ts.map +1 -0
- package/dist/types/components/lightbox/utils/zoom.d.ts +8 -4
- package/dist/types/components/lightbox/utils/zoom.d.ts.map +1 -1
- package/dist/types/components/pagination/Pagination.vue.d.ts +59 -0
- package/dist/types/components/pagination/Pagination.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationContent.vue.d.ts +23 -0
- package/dist/types/components/pagination/PaginationContent.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationControl.vue.d.ts +10 -0
- package/dist/types/components/pagination/PaginationControl.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationEllipsis.vue.d.ts +23 -0
- package/dist/types/components/pagination/PaginationEllipsis.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationInfo.vue.d.ts +24 -0
- package/dist/types/components/pagination/PaginationInfo.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationJump.vue.d.ts +7 -0
- package/dist/types/components/pagination/PaginationJump.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationPage.vue.d.ts +20 -0
- package/dist/types/components/pagination/PaginationPage.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationPages.vue.d.ts +27 -0
- package/dist/types/components/pagination/PaginationPages.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationPopup.vue.d.ts +8 -0
- package/dist/types/components/pagination/PaginationPopup.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationSize.vue.d.ts +7 -0
- package/dist/types/components/pagination/PaginationSize.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/composables/usePagination.d.ts +25 -0
- package/dist/types/components/pagination/composables/usePagination.d.ts.map +1 -0
- package/dist/types/components/pagination/composables/usePaginationEllipsis.d.ts +34 -0
- package/dist/types/components/pagination/composables/usePaginationEllipsis.d.ts.map +1 -0
- package/dist/types/components/pagination/composables/usePaginationJump.d.ts +8 -0
- package/dist/types/components/pagination/composables/usePaginationJump.d.ts.map +1 -0
- package/dist/types/components/pagination/composables/usePaginationPageTooltip.d.ts +3 -0
- package/dist/types/components/pagination/composables/usePaginationPageTooltip.d.ts.map +1 -0
- package/dist/types/components/pagination/composables/usePaginationWindow.d.ts +188 -0
- package/dist/types/components/pagination/composables/usePaginationWindow.d.ts.map +1 -0
- package/dist/types/components/pagination/context.d.ts +19 -0
- package/dist/types/components/pagination/context.d.ts.map +1 -0
- package/dist/types/components/pagination/pagination.variants.d.ts +68 -0
- package/dist/types/components/pagination/pagination.variants.d.ts.map +1 -0
- package/dist/types/components/pagination/types.d.ts +23 -0
- package/dist/types/components/pagination/types.d.ts.map +1 -0
- package/dist/types/components/pagination/utils/ranges.d.ts +3 -0
- package/dist/types/components/pagination/utils/ranges.d.ts.map +1 -0
- package/dist/types/components/popover/Popover.vue.d.ts.map +1 -1
- package/dist/types/components/range-slider/RangeSlider.vue.d.ts +1 -0
- package/dist/types/components/range-slider/RangeSlider.vue.d.ts.map +1 -1
- package/dist/types/components/scroll-area/composables/useLayerLock.d.ts.map +1 -1
- package/dist/types/components/slider/Slider.vue.d.ts +1 -0
- package/dist/types/components/slider/Slider.vue.d.ts.map +1 -1
- package/dist/types/components/slider/slider.variants.d.ts.map +1 -1
- package/dist/types/components/tooltip/Tooltip.vue.d.ts +4 -15
- package/dist/types/components/tooltip/Tooltip.vue.d.ts.map +1 -1
- package/dist/types/components/tooltip/TooltipBubble.vue.d.ts +20 -0
- package/dist/types/components/tooltip/TooltipBubble.vue.d.ts.map +1 -0
- package/dist/types/components/tooltip/TooltipDirectiveRoot.d.ts +33 -0
- package/dist/types/components/tooltip/TooltipDirectiveRoot.d.ts.map +1 -0
- package/dist/types/components/tooltip/composables/useTooltipTarget.d.ts +2 -0
- package/dist/types/components/tooltip/composables/useTooltipTarget.d.ts.map +1 -0
- package/dist/types/components/tooltip/directive.d.ts +4 -0
- package/dist/types/components/tooltip/directive.d.ts.map +1 -0
- package/dist/types/components/tooltip/types.d.ts +14 -0
- package/dist/types/components/tooltip/types.d.ts.map +1 -0
- package/dist/types/components/tooltip/utils/directiveContext.d.ts +10 -0
- package/dist/types/components/tooltip/utils/directiveContext.d.ts.map +1 -0
- package/dist/types/components/tree-select/TreeSelect.vue.d.ts +6 -1
- package/dist/types/components/tree-select/TreeSelect.vue.d.ts.map +1 -1
- package/dist/types/components/tree-select/TreeSelectSearch.vue.d.ts +22 -0
- package/dist/types/components/tree-select/TreeSelectSearch.vue.d.ts.map +1 -0
- package/dist/types/components/tree-select/composables/useTreeSelect.d.ts +35 -0
- package/dist/types/components/tree-select/composables/useTreeSelect.d.ts.map +1 -0
- package/dist/types/components/tree-select/tree-select.variants.d.ts +1 -1
- package/dist/types/components/tree-select/tree-select.variants.d.ts.map +1 -1
- package/dist/types/components/tree-select/utils/filterTree.d.ts +7 -0
- package/dist/types/components/tree-select/utils/filterTree.d.ts.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/anchored-overlay.d.ts +3 -7
- package/dist/types/lib/anchored-overlay.d.ts.map +1 -1
- package/dist/types/lib/dialog-focus.d.ts +2 -0
- package/dist/types/lib/dialog-focus.d.ts.map +1 -0
- package/dist/types/lib/overlay-portal.d.ts +6 -0
- package/dist/types/lib/overlay-portal.d.ts.map +1 -0
- package/dist/types/lib/overlay-positioner.d.ts +3 -0
- package/dist/types/lib/overlay-positioner.d.ts.map +1 -0
- package/dist/types/lib/pointer-corridor.d.ts +7 -0
- package/dist/types/lib/pointer-corridor.d.ts.map +1 -0
- package/dist/types/lib/useDirection.d.ts +7 -0
- package/dist/types/lib/useDirection.d.ts.map +1 -0
- package/dist/types/locale/en-US.d.ts.map +1 -1
- package/dist/types/locale/index.d.ts.map +1 -1
- package/dist/types/locale/types.d.ts +13 -0
- package/dist/types/locale/types.d.ts.map +1 -1
- package/dist/types/locale/zh-CN.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/alert-dialog/AlertDialog.vue +7 -4
- package/src/components/badge/badge.variants.ts +1 -1
- package/src/components/dialog/Dialog.vue +7 -4
- package/src/components/dialog/ModalContent.vue +19 -0
- package/src/components/dropdown-menu/DropdownMenu.vue +1 -1
- package/src/components/hover-card/HoverCard.vue +34 -16
- package/src/components/hover-card/HoverCardAnchor.vue +16 -0
- package/src/components/hover-card/composables/useHoverCardAnchor.ts +160 -0
- package/src/components/image/Image.vue +11 -12
- package/src/components/image/composables/useImage.ts +6 -3
- package/src/components/image/composables/useImageGroupState.ts +9 -1
- package/src/components/image/image.variants.ts +13 -1
- package/src/components/lightbox/Lightbox.vue +23 -17
- package/src/components/lightbox/LightboxChrome.vue +7 -1
- package/src/components/lightbox/LightboxToolbar.vue +16 -2
- package/src/components/lightbox/composables/useLightboxFrames.ts +108 -12
- package/src/components/lightbox/composables/useLightboxLarge.ts +4 -1
- package/src/components/lightbox/composables/useLightboxLayout.ts +44 -0
- package/src/components/lightbox/composables/useLightboxMotion.ts +3 -1
- package/src/components/lightbox/composables/useLightboxPhase.ts +12 -4
- package/src/components/lightbox/composables/useLightboxPose.ts +20 -19
- package/src/components/lightbox/composables/useLightboxRotation.ts +14 -4
- package/src/components/lightbox/composables/useLightboxZoom.ts +51 -9
- package/src/components/lightbox/lightbox.variants.ts +5 -0
- package/src/components/lightbox/types.ts +5 -1
- package/src/components/lightbox/utils/clip.ts +117 -0
- package/src/components/lightbox/utils/pose.ts +36 -19
- package/src/components/lightbox/utils/source.ts +17 -0
- package/src/components/lightbox/utils/zoom.ts +21 -7
- package/src/components/pagination/Pagination.vue +118 -0
- package/src/components/pagination/PaginationContent.vue +29 -0
- package/src/components/pagination/PaginationControl.vue +42 -0
- package/src/components/pagination/PaginationEllipsis.vue +67 -0
- package/src/components/pagination/PaginationInfo.vue +18 -0
- package/src/components/pagination/PaginationJump.vue +32 -0
- package/src/components/pagination/PaginationPage.vue +34 -0
- package/src/components/pagination/PaginationPages.vue +43 -0
- package/src/components/pagination/PaginationPopup.vue +92 -0
- package/src/components/pagination/PaginationSize.vue +30 -0
- package/src/components/pagination/composables/usePagination.ts +76 -0
- package/src/components/pagination/composables/usePaginationEllipsis.ts +269 -0
- package/src/components/pagination/composables/usePaginationJump.ts +23 -0
- package/src/components/pagination/composables/usePaginationPageTooltip.ts +23 -0
- package/src/components/pagination/composables/usePaginationWindow.ts +116 -0
- package/src/components/pagination/context.ts +23 -0
- package/src/components/pagination/pagination.variants.ts +32 -0
- package/src/components/pagination/types.ts +19 -0
- package/src/components/pagination/utils/ranges.ts +12 -0
- package/src/components/popover/Popover.vue +6 -2
- package/src/components/range-slider/RangeSlider.vue +6 -0
- package/src/components/scroll-area/composables/useLayerLock.ts +8 -9
- package/src/components/scroll-area/composables/useWheelRedirect.ts +1 -1
- package/src/components/skeleton/LICENSE +24 -0
- package/src/components/slider/Slider.vue +6 -0
- package/src/components/slider/slider.variants.ts +1 -0
- package/src/components/tooltip/Tooltip.vue +10 -33
- package/src/components/tooltip/TooltipBubble.vue +36 -0
- package/src/components/tooltip/TooltipDirectiveRoot.ts +61 -0
- package/src/components/tooltip/composables/useTooltipTarget.ts +56 -0
- package/src/components/tooltip/directive.ts +54 -0
- package/src/components/tooltip/types.ts +15 -0
- package/src/components/tooltip/utils/directiveContext.ts +32 -0
- package/src/components/tree-select/TreeSelect.vue +90 -61
- package/src/components/tree-select/TreeSelectSearch.vue +48 -0
- package/src/components/tree-select/composables/useTreeSelect.ts +130 -0
- package/src/components/tree-select/tree-select.variants.ts +2 -2
- package/src/components/tree-select/utils/filterTree.ts +19 -0
- package/src/index.ts +10 -0
- package/src/lib/anchored-overlay.ts +2 -5
- package/src/lib/dialog-focus.ts +13 -0
- package/src/lib/overlay-portal.ts +8 -0
- package/src/lib/overlay-positioner.ts +25 -0
- package/src/lib/pointer-corridor.ts +26 -0
- package/src/lib/useDirection.ts +31 -0
- package/src/locale/en-US.ts +15 -1
- package/src/locale/index.ts +1 -0
- package/src/locale/types.ts +13 -0
- package/src/locale/zh-CN.ts +15 -1
- package/src/styles/interaction.css +26 -3
- package/src/styles/motion.css +14 -2
- package/src/styles/semantic.css +5 -1
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { computed, onMounted, shallowRef, watch } from 'vue'
|
|
3
3
|
import { DialogRoot, DialogPortal, DialogContent, DialogTitle, useBodyScrollLock } from 'reka-ui'
|
|
4
4
|
import { Motion } from 'motion-v'
|
|
5
|
-
import { useEventListener } from '@vueuse/core'
|
|
6
5
|
import { cn } from '../../lib/cn'
|
|
7
6
|
import VisuallyHidden from '../visually-hidden/VisuallyHidden.vue'
|
|
8
7
|
import LightboxStrip from './LightboxStrip.vue'
|
|
@@ -17,6 +16,8 @@
|
|
|
17
16
|
import { useLightboxPhase } from './composables/useLightboxPhase'
|
|
18
17
|
import { useLightboxHint } from './composables/useLightboxHint'
|
|
19
18
|
import { useLightboxSlides } from './composables/useLightboxSlides'
|
|
19
|
+
import { lightboxStage } from './lightbox.variants'
|
|
20
|
+
import { useLightboxLayout } from './composables/useLightboxLayout'
|
|
20
21
|
import { useLightboxRotation } from './composables/useLightboxRotation'
|
|
21
22
|
import { clampIndex } from './utils/paging'
|
|
22
23
|
import { saveImage } from './utils/download'
|
|
@@ -57,9 +58,10 @@
|
|
|
57
58
|
loop: () => !!props.loop,
|
|
58
59
|
})
|
|
59
60
|
|
|
60
|
-
function measure() {
|
|
61
|
+
function measure(force = false) {
|
|
62
|
+
const width = frames.stage.value.width
|
|
61
63
|
frames.measure(stageEl.value)
|
|
62
|
-
paging.sync()
|
|
64
|
+
if (force || width !== frames.stage.value.width) paging.sync()
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
const phase = useLightboxPhase({
|
|
@@ -70,9 +72,9 @@
|
|
|
70
72
|
pose: pose.fromSource,
|
|
71
73
|
prepare: () => {
|
|
72
74
|
rotation.clear()
|
|
73
|
-
frames.read()
|
|
75
|
+
return frames.read(() => current.value)
|
|
74
76
|
},
|
|
75
|
-
layout: measure,
|
|
77
|
+
layout: () => measure(true),
|
|
76
78
|
})
|
|
77
79
|
|
|
78
80
|
const close = phase.close
|
|
@@ -94,6 +96,16 @@
|
|
|
94
96
|
() => mounted.value,
|
|
95
97
|
)
|
|
96
98
|
|
|
99
|
+
const layout = useLightboxLayout({
|
|
100
|
+
stage: () => stageEl.value,
|
|
101
|
+
current: () => current.value,
|
|
102
|
+
frames,
|
|
103
|
+
large,
|
|
104
|
+
phase,
|
|
105
|
+
zoom,
|
|
106
|
+
measure,
|
|
107
|
+
})
|
|
108
|
+
|
|
97
109
|
const hint = useLightboxHint({ motion, frames, paging, current: () => current.value })
|
|
98
110
|
|
|
99
111
|
const looping = computed(() => !!props.loop && count.value > 1)
|
|
@@ -137,10 +149,6 @@
|
|
|
137
149
|
if (open.value) void phase.show()
|
|
138
150
|
})
|
|
139
151
|
|
|
140
|
-
useEventListener('resize', () => {
|
|
141
|
-
if (mounted.value) measure()
|
|
142
|
-
})
|
|
143
|
-
|
|
144
152
|
function onEscape(event: Event) {
|
|
145
153
|
event.preventDefault()
|
|
146
154
|
void close()
|
|
@@ -173,7 +181,7 @@
|
|
|
173
181
|
const currentClass = computed(() =>
|
|
174
182
|
cn(
|
|
175
183
|
'absolute will-change-transform',
|
|
176
|
-
!zoom.zoomed.value && 'cursor-zoom-in',
|
|
184
|
+
!zoom.zoomed.value && (zoom.canToggle.value ? 'cursor-zoom-in' : 'cursor-default'),
|
|
177
185
|
zoom.zoomed.value && (gesture.mode.value === 'pan' ? 'cursor-grabbing' : 'cursor-grab'),
|
|
178
186
|
),
|
|
179
187
|
)
|
|
@@ -191,12 +199,7 @@
|
|
|
191
199
|
<div
|
|
192
200
|
ref="stageEl"
|
|
193
201
|
:data-hn-phase="phase.current()"
|
|
194
|
-
:class="
|
|
195
|
-
cn(
|
|
196
|
-
'dark fixed inset-0 z-(--hn-z-overlay) touch-none outline-none select-none',
|
|
197
|
-
props.class,
|
|
198
|
-
)
|
|
199
|
-
"
|
|
202
|
+
:class="cn(lightboxStage(), props.class)"
|
|
200
203
|
@pointerdown="gesture.onPointerdown"
|
|
201
204
|
@pointermove="gesture.onPointermove"
|
|
202
205
|
@pointerup="gesture.onPointerup"
|
|
@@ -220,7 +223,7 @@
|
|
|
220
223
|
:transition="motion.transition('base')"
|
|
221
224
|
:large-src="large.src.value"
|
|
222
225
|
:large-ready="large.ready.value"
|
|
223
|
-
@learn="
|
|
226
|
+
@learn="layout.learn"
|
|
224
227
|
/>
|
|
225
228
|
<DialogTitle as-child>
|
|
226
229
|
<VisuallyHidden>{{ current?.alt }}</VisuallyHidden>
|
|
@@ -229,6 +232,8 @@
|
|
|
229
232
|
:items="props.items"
|
|
230
233
|
:index="index"
|
|
231
234
|
:zoomed="zoom.zoomed.value"
|
|
235
|
+
:can-zoom-in="zoom.canZoomIn.value"
|
|
236
|
+
:at-original="zoom.atOriginal.value"
|
|
232
237
|
:waiting="large.waiting.value"
|
|
233
238
|
:loop="looping"
|
|
234
239
|
:hint="hint"
|
|
@@ -239,6 +244,7 @@
|
|
|
239
244
|
@zoom-in="zoom.step(1)"
|
|
240
245
|
@zoom-out="zoom.step(-1)"
|
|
241
246
|
@reset="zoom.reset()"
|
|
247
|
+
@original="zoom.original()"
|
|
242
248
|
@rotate="rotate"
|
|
243
249
|
@download="download"
|
|
244
250
|
/>
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
items: LightboxItem[]
|
|
17
17
|
index: number
|
|
18
18
|
zoomed: boolean
|
|
19
|
+
canZoomIn: boolean
|
|
20
|
+
atOriginal: boolean
|
|
19
21
|
waiting: boolean
|
|
20
22
|
loop: boolean
|
|
21
23
|
presence: MotionValue<number>
|
|
@@ -29,6 +31,7 @@
|
|
|
29
31
|
zoomIn: []
|
|
30
32
|
zoomOut: []
|
|
31
33
|
reset: []
|
|
34
|
+
original: []
|
|
32
35
|
rotate: []
|
|
33
36
|
download: []
|
|
34
37
|
}>()
|
|
@@ -88,7 +91,7 @@
|
|
|
88
91
|
<ChevronRight />
|
|
89
92
|
</IconButton>
|
|
90
93
|
</Motion>
|
|
91
|
-
<Motion class="absolute end-4 top-4" :style="{ opacity: props.presence }">
|
|
94
|
+
<Motion data-hn-close class="absolute end-4 top-4" :style="{ opacity: props.presence }">
|
|
92
95
|
<DialogClose as-child>
|
|
93
96
|
<CloseButton size="md" />
|
|
94
97
|
</DialogClose>
|
|
@@ -102,10 +105,13 @@
|
|
|
102
105
|
:items="props.items"
|
|
103
106
|
:index="props.index"
|
|
104
107
|
:zoomed="props.zoomed"
|
|
108
|
+
:can-zoom-in="props.canZoomIn"
|
|
109
|
+
:at-original="props.atOriginal"
|
|
105
110
|
@select="emit('select', $event)"
|
|
106
111
|
@zoom-in="emit('zoomIn')"
|
|
107
112
|
@zoom-out="emit('zoomOut')"
|
|
108
113
|
@reset="emit('reset')"
|
|
114
|
+
@original="emit('original')"
|
|
109
115
|
@rotate="emit('rotate')"
|
|
110
116
|
@download="emit('download')"
|
|
111
117
|
/>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { Download, RotateCw, Shrink, ZoomIn, ZoomOut } from '@lucide/vue'
|
|
2
|
+
import { Download, RotateCw, Scan, Shrink, ZoomIn, ZoomOut } from '@lucide/vue'
|
|
3
3
|
import { useUiLocale } from '../../locale'
|
|
4
4
|
import IconButton from '../icon-button/IconButton.vue'
|
|
5
5
|
import LightboxThumbs from './LightboxThumbs.vue'
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
items: LightboxItem[]
|
|
12
12
|
index: number
|
|
13
13
|
zoomed: boolean
|
|
14
|
+
canZoomIn: boolean
|
|
15
|
+
atOriginal: boolean
|
|
14
16
|
}>()
|
|
15
17
|
|
|
16
18
|
const emit = defineEmits<{
|
|
@@ -18,6 +20,7 @@
|
|
|
18
20
|
zoomIn: []
|
|
19
21
|
zoomOut: []
|
|
20
22
|
reset: []
|
|
23
|
+
original: []
|
|
21
24
|
rotate: []
|
|
22
25
|
download: []
|
|
23
26
|
}>()
|
|
@@ -26,7 +29,7 @@
|
|
|
26
29
|
</script>
|
|
27
30
|
|
|
28
31
|
<template>
|
|
29
|
-
<div class="flex flex-col items-center gap-1 pb-3">
|
|
32
|
+
<div class="flex flex-col items-center gap-1 pb-[max(--spacing(3),env(safe-area-inset-bottom))]">
|
|
30
33
|
<LightboxThumbs
|
|
31
34
|
v-if="props.items.length > 1"
|
|
32
35
|
:items="props.items"
|
|
@@ -36,6 +39,7 @@
|
|
|
36
39
|
<div class="flex items-center gap-1">
|
|
37
40
|
<IconButton
|
|
38
41
|
:label="t.lightbox.zoomOut"
|
|
42
|
+
:disabled="!props.zoomed"
|
|
39
43
|
variant="soft"
|
|
40
44
|
pill
|
|
41
45
|
class="hidden pointer-fine:inline-flex"
|
|
@@ -45,6 +49,7 @@
|
|
|
45
49
|
</IconButton>
|
|
46
50
|
<IconButton
|
|
47
51
|
:label="t.lightbox.zoomIn"
|
|
52
|
+
:disabled="!props.canZoomIn"
|
|
48
53
|
variant="soft"
|
|
49
54
|
pill
|
|
50
55
|
class="hidden pointer-fine:inline-flex"
|
|
@@ -52,6 +57,15 @@
|
|
|
52
57
|
>
|
|
53
58
|
<ZoomIn />
|
|
54
59
|
</IconButton>
|
|
60
|
+
<IconButton
|
|
61
|
+
:label="t.lightbox.actualSize"
|
|
62
|
+
variant="soft"
|
|
63
|
+
pill
|
|
64
|
+
:disabled="props.atOriginal"
|
|
65
|
+
@click="emit('original')"
|
|
66
|
+
>
|
|
67
|
+
<Scan />
|
|
68
|
+
</IconButton>
|
|
55
69
|
<IconButton
|
|
56
70
|
:label="t.lightbox.resetZoom"
|
|
57
71
|
variant="soft"
|
|
@@ -1,38 +1,134 @@
|
|
|
1
|
-
import { reactive, shallowRef } from 'vue'
|
|
1
|
+
import { onScopeDispose, reactive, shallowRef } from 'vue'
|
|
2
2
|
import type { LightboxItem } from '../types'
|
|
3
3
|
import { fitRect, type Rect } from '../utils/pose'
|
|
4
4
|
import type { Size } from '../utils/zoom'
|
|
5
5
|
|
|
6
|
+
function previewSizeOf(item: LightboxItem | undefined): Size | undefined {
|
|
7
|
+
const size = item?.previewSize
|
|
8
|
+
if (
|
|
9
|
+
size &&
|
|
10
|
+
Number.isFinite(size.width) &&
|
|
11
|
+
size.width > 0 &&
|
|
12
|
+
Number.isFinite(size.height) &&
|
|
13
|
+
size.height > 0
|
|
14
|
+
)
|
|
15
|
+
return size
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
export function useLightboxFrames(items: () => LightboxItem[]) {
|
|
7
19
|
const stage = shallowRef<Size>({ width: 0, height: 0 })
|
|
8
|
-
const naturals = reactive(new Map<string, Size>())
|
|
20
|
+
const naturals = reactive(new Map<string, Size & { src: string }>())
|
|
21
|
+
const previews = reactive(new Map<string, Size & { src: string; preview: string }>())
|
|
22
|
+
const area = shallowRef<Rect>({ x: 0, y: 0, width: 0, height: 0 })
|
|
23
|
+
let loader: HTMLImageElement | undefined
|
|
9
24
|
|
|
10
25
|
function measure(el: HTMLElement | undefined) {
|
|
11
|
-
|
|
26
|
+
if (!el) return
|
|
27
|
+
const width = el.clientWidth
|
|
28
|
+
const height = el.clientHeight
|
|
29
|
+
const style = getComputedStyle(el)
|
|
30
|
+
const rect = el.getBoundingClientRect()
|
|
31
|
+
const chrome = el.querySelector('[data-hn-chrome]')?.getBoundingClientRect()
|
|
32
|
+
const close = el.querySelector('[data-hn-close]')?.getBoundingClientRect()
|
|
33
|
+
const x = Math.min(
|
|
34
|
+
width / 2,
|
|
35
|
+
Math.max(parseFloat(style.paddingLeft), parseFloat(style.paddingRight)),
|
|
36
|
+
)
|
|
37
|
+
const y = Math.min(
|
|
38
|
+
height / 2,
|
|
39
|
+
Math.max(
|
|
40
|
+
parseFloat(style.paddingTop) + (close ? close.bottom - rect.top : 0),
|
|
41
|
+
parseFloat(style.paddingBottom) + (chrome ? rect.bottom - chrome.top : 0),
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
stage.value = { width, height }
|
|
45
|
+
area.value = { x, y, width: width - x * 2, height: height - y * 2 }
|
|
12
46
|
}
|
|
13
47
|
|
|
14
|
-
function read() {
|
|
48
|
+
function read(current?: () => LightboxItem | undefined): Promise<void> | undefined {
|
|
49
|
+
loader?.removeAttribute('src')
|
|
50
|
+
loader = undefined
|
|
15
51
|
for (const item of items()) {
|
|
16
52
|
const el = item.source?.()
|
|
17
|
-
if (
|
|
18
|
-
|
|
53
|
+
if (
|
|
54
|
+
el &&
|
|
55
|
+
'naturalWidth' in el &&
|
|
56
|
+
el.naturalWidth &&
|
|
57
|
+
el.naturalHeight &&
|
|
58
|
+
!el.srcset &&
|
|
59
|
+
(el.currentSrc || el.src) === new URL(item.src, el.ownerDocument.baseURI).href
|
|
60
|
+
) {
|
|
61
|
+
naturals.set(item.id, { src: item.src, width: el.naturalWidth, height: el.naturalHeight })
|
|
19
62
|
}
|
|
20
63
|
}
|
|
64
|
+
const item = current?.()
|
|
65
|
+
if (!item || previewSizeOf(item) || naturalOf(item)) return
|
|
66
|
+
const img = new Image()
|
|
67
|
+
loader = img
|
|
68
|
+
const { id, src } = item
|
|
69
|
+
img.src = src
|
|
70
|
+
return img
|
|
71
|
+
.decode()
|
|
72
|
+
.catch(() => {})
|
|
73
|
+
.then(() => {
|
|
74
|
+
if (loader !== img) return
|
|
75
|
+
learn(id, img)
|
|
76
|
+
loader = undefined
|
|
77
|
+
const latest = current?.()
|
|
78
|
+
if (latest?.id !== id || latest?.src !== src) return read(current)
|
|
79
|
+
})
|
|
21
80
|
}
|
|
22
81
|
|
|
23
82
|
function learn(id: string, img: HTMLImageElement) {
|
|
24
|
-
|
|
25
|
-
|
|
83
|
+
const item = items().find(item => item.id === id)
|
|
84
|
+
if (
|
|
85
|
+
!item ||
|
|
86
|
+
naturalOf(item) ||
|
|
87
|
+
!img.naturalWidth ||
|
|
88
|
+
!img.naturalHeight ||
|
|
89
|
+
img.src !== new URL(item.src, img.ownerDocument.baseURI).href
|
|
90
|
+
) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
naturals.set(id, { src: item.src, width: img.naturalWidth, height: img.naturalHeight })
|
|
26
94
|
}
|
|
27
95
|
|
|
28
96
|
function naturalOf(item: LightboxItem | undefined): Size | undefined {
|
|
29
|
-
|
|
97
|
+
if (!item) return
|
|
98
|
+
const natural = naturals.get(item.id)
|
|
99
|
+
return natural?.src === item.src ? natural : undefined
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function learnPreview(item: LightboxItem | undefined, img: HTMLImageElement | undefined) {
|
|
103
|
+
if (!item?.preview || !img?.naturalWidth || !img.naturalHeight) return
|
|
104
|
+
if (img.src !== new URL(item.preview, img.ownerDocument.baseURI).href) return
|
|
105
|
+
const previous = previews.get(item.id)
|
|
106
|
+
if (previous?.src === item.src && previous.preview === item.preview) return
|
|
107
|
+
previews.set(item.id, {
|
|
108
|
+
src: item.src,
|
|
109
|
+
preview: item.preview,
|
|
110
|
+
width: img.naturalWidth,
|
|
111
|
+
height: img.naturalHeight,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function displayOf(item: LightboxItem | undefined): Size | undefined {
|
|
116
|
+
if (!item) return
|
|
117
|
+
const size = previewSizeOf(item)
|
|
118
|
+
if (size) return size
|
|
119
|
+
const preview = previews.get(item.id)
|
|
120
|
+
return preview?.src === item.src && preview.preview === item.preview ? preview : naturalOf(item)
|
|
30
121
|
}
|
|
31
122
|
|
|
32
123
|
function frameOf(item: LightboxItem | undefined): Rect {
|
|
33
|
-
const natural =
|
|
34
|
-
return natural ? fitRect(natural,
|
|
124
|
+
const natural = displayOf(item)
|
|
125
|
+
return natural ? fitRect(natural, area.value) : area.value
|
|
35
126
|
}
|
|
36
127
|
|
|
37
|
-
|
|
128
|
+
onScopeDispose(() => {
|
|
129
|
+
loader?.removeAttribute('src')
|
|
130
|
+
loader = undefined
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return { stage, area, measure, read, learn, learnPreview, naturalOf, displayOf, frameOf }
|
|
38
134
|
}
|
|
@@ -6,6 +6,7 @@ export const LARGE_HINT_DELAY_MS = 800
|
|
|
6
6
|
export function useLightboxLarge(item: () => LightboxItem | undefined, active: () => boolean) {
|
|
7
7
|
const src = shallowRef<string>()
|
|
8
8
|
const ready = shallowRef(false)
|
|
9
|
+
const image = shallowRef<HTMLImageElement>()
|
|
9
10
|
const waiting = shallowRef(false)
|
|
10
11
|
let loader: HTMLImageElement | null = null
|
|
11
12
|
let timer: ReturnType<typeof setTimeout> | null = null
|
|
@@ -17,6 +18,7 @@ export function useLightboxLarge(item: () => LightboxItem | undefined, active: (
|
|
|
17
18
|
timer = null
|
|
18
19
|
src.value = undefined
|
|
19
20
|
ready.value = false
|
|
21
|
+
image.value = undefined
|
|
20
22
|
waiting.value = false
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -36,6 +38,7 @@ export function useLightboxLarge(item: () => LightboxItem | undefined, active: (
|
|
|
36
38
|
if (timer) clearTimeout(timer)
|
|
37
39
|
timer = null
|
|
38
40
|
waiting.value = false
|
|
41
|
+
image.value = img
|
|
39
42
|
ready.value = true
|
|
40
43
|
}
|
|
41
44
|
const decode = typeof img.decode === 'function' ? img.decode() : Promise.resolve()
|
|
@@ -58,5 +61,5 @@ export function useLightboxLarge(item: () => LightboxItem | undefined, active: (
|
|
|
58
61
|
|
|
59
62
|
onBeforeUnmount(reset)
|
|
60
63
|
|
|
61
|
-
return { src, ready, waiting }
|
|
64
|
+
return { src, ready, waiting, image }
|
|
62
65
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { watch } from 'vue'
|
|
2
|
+
import { useResizeObserver } from '@vueuse/core'
|
|
3
|
+
import type { LightboxItem } from '../types'
|
|
4
|
+
import type { useLightboxFrames } from './useLightboxFrames'
|
|
5
|
+
import type { useLightboxLarge } from './useLightboxLarge'
|
|
6
|
+
import type { useLightboxPhase } from './useLightboxPhase'
|
|
7
|
+
import type { useLightboxZoom } from './useLightboxZoom'
|
|
8
|
+
|
|
9
|
+
export function useLightboxLayout(options: {
|
|
10
|
+
stage: () => HTMLElement | undefined
|
|
11
|
+
current: () => LightboxItem | undefined
|
|
12
|
+
frames: ReturnType<typeof useLightboxFrames>
|
|
13
|
+
large: ReturnType<typeof useLightboxLarge>
|
|
14
|
+
phase: ReturnType<typeof useLightboxPhase>
|
|
15
|
+
zoom: ReturnType<typeof useLightboxZoom>
|
|
16
|
+
measure: () => void
|
|
17
|
+
}) {
|
|
18
|
+
function sync() {
|
|
19
|
+
if (options.phase.current() !== 'open') return
|
|
20
|
+
options.zoom.reflow(() => {
|
|
21
|
+
options.measure()
|
|
22
|
+
options.frames.learnPreview(options.current(), options.large.image.value)
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function learn(id: string, img: HTMLImageElement) {
|
|
27
|
+
const update = () => options.frames.learn(id, img)
|
|
28
|
+
if (options.phase.current() === 'open' && options.current()?.id === id)
|
|
29
|
+
options.zoom.reflow(update)
|
|
30
|
+
else update()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
watch(() => [options.phase.current(), options.current(), options.large.image.value], sync, {
|
|
34
|
+
flush: 'post',
|
|
35
|
+
})
|
|
36
|
+
useResizeObserver(() => {
|
|
37
|
+
const stage = options.stage()
|
|
38
|
+
if (!stage) return []
|
|
39
|
+
const chrome = stage.querySelector<HTMLElement>('[data-hn-chrome]')
|
|
40
|
+
return chrome ? [stage, chrome] : [stage]
|
|
41
|
+
}, sync)
|
|
42
|
+
|
|
43
|
+
return { learn }
|
|
44
|
+
}
|
|
@@ -84,7 +84,9 @@ export function useLightboxMotion(stageHeight: () => number, restScale: () => nu
|
|
|
84
84
|
function leave(to: Pose | null): Promise<void> {
|
|
85
85
|
stop()
|
|
86
86
|
const t = to ? timing(RETURN_SPRING) : transition('exit')
|
|
87
|
-
const travel = to
|
|
87
|
+
const travel = to
|
|
88
|
+
? poseTo(to, t)
|
|
89
|
+
: [animate(scale, Math.min(scale.get(), FADE_POSE.scale), t), animate(opacity, 0, t)]
|
|
88
90
|
return settle([...travel, animate(presence, 0, t)])
|
|
89
91
|
}
|
|
90
92
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nextTick, shallowRef, type Ref } from 'vue'
|
|
1
|
+
import { nextTick, onScopeDispose, shallowRef, type Ref } from 'vue'
|
|
2
2
|
import type { useLightboxMotion } from './useLightboxMotion'
|
|
3
3
|
import type { Pose } from '../utils/pose'
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ export function useLightboxPhase(options: {
|
|
|
10
10
|
locked: Ref<boolean>
|
|
11
11
|
motion: ReturnType<typeof useLightboxMotion>
|
|
12
12
|
pose: () => Pose | null
|
|
13
|
-
prepare: () => void
|
|
13
|
+
prepare: () => void | Promise<void>
|
|
14
14
|
layout: () => void
|
|
15
15
|
}) {
|
|
16
16
|
const { motion } = options
|
|
@@ -21,10 +21,13 @@ export function useLightboxPhase(options: {
|
|
|
21
21
|
if (phase.value !== 'closed') return
|
|
22
22
|
phase.value = 'entering'
|
|
23
23
|
const run = ++generation
|
|
24
|
-
options.prepare()
|
|
24
|
+
const preparing = options.prepare()
|
|
25
|
+
if (preparing) await preparing
|
|
26
|
+
if (run !== generation) return
|
|
25
27
|
options.mounted.value = true
|
|
26
28
|
options.locked.value = true
|
|
27
29
|
await nextTick()
|
|
30
|
+
if (run !== generation) return
|
|
28
31
|
options.layout()
|
|
29
32
|
await motion.enter(options.pose())
|
|
30
33
|
if (run === generation) phase.value = 'open'
|
|
@@ -42,7 +45,7 @@ export function useLightboxPhase(options: {
|
|
|
42
45
|
if (phase.value === 'closed' || phase.value === 'closing') return
|
|
43
46
|
phase.value = 'closing'
|
|
44
47
|
const run = ++generation
|
|
45
|
-
await motion.leave(options.pose())
|
|
48
|
+
if (options.mounted.value) await motion.leave(options.pose())
|
|
46
49
|
if (run !== generation) return
|
|
47
50
|
phase.value = 'closed'
|
|
48
51
|
options.mounted.value = false
|
|
@@ -55,5 +58,10 @@ export function useLightboxPhase(options: {
|
|
|
55
58
|
else if (phase.value === 'closing') void reopen()
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
onScopeDispose(() => {
|
|
62
|
+
generation++
|
|
63
|
+
motion.stop()
|
|
64
|
+
})
|
|
65
|
+
|
|
58
66
|
return { current: () => phase.value, show, close, reopen, interrupt }
|
|
59
67
|
}
|
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
import type { useLightboxFrames } from './useLightboxFrames'
|
|
2
|
-
import { openPose,
|
|
2
|
+
import { openPose, type Pose } from '../utils/pose'
|
|
3
|
+
import { clipOf } from '../utils/clip'
|
|
4
|
+
import { sourceRect } from '../utils/source'
|
|
3
5
|
import type { LightboxItem } from '../types'
|
|
4
6
|
|
|
5
7
|
export function useLightboxPose(
|
|
6
8
|
frames: ReturnType<typeof useLightboxFrames>,
|
|
7
9
|
current: () => LightboxItem | undefined,
|
|
8
10
|
) {
|
|
9
|
-
function
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
11
|
+
function fromSource(): Pose | null {
|
|
12
|
+
const item = current()
|
|
13
|
+
const source = item?.source?.()
|
|
14
|
+
const box = sourceRect(source)
|
|
15
|
+
const el = source && 'getBoundingClientRect' in source ? source : undefined
|
|
16
|
+
const natural =
|
|
17
|
+
el?.naturalWidth && el.naturalHeight
|
|
18
|
+
? { width: el.naturalWidth, height: el.naturalHeight }
|
|
19
|
+
: (frames.naturalOf(item) ?? frames.displayOf(item))
|
|
20
|
+
if (!box || !natural) return null
|
|
14
21
|
const stage = frames.stage.value
|
|
15
22
|
if (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
box.y + box.height <= 0 ||
|
|
24
|
+
box.x + box.width <= 0 ||
|
|
25
|
+
box.y >= stage.height ||
|
|
26
|
+
box.x >= stage.width
|
|
20
27
|
) {
|
|
21
28
|
return null
|
|
22
29
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
function fromSource(): Pose | null {
|
|
27
|
-
const item = current()
|
|
28
|
-
const box = sourceBox()
|
|
29
|
-
const natural = frames.naturalOf(item)
|
|
30
|
-
if (!box || !natural) return null
|
|
31
|
-
return openPose(frames.frameOf(item), box, natural, item?.fit, radiusOf(item?.source?.()))
|
|
30
|
+
const clip = clipOf(el, box)
|
|
31
|
+
if (clip.rect.width <= 0 || clip.rect.height <= 0) return null
|
|
32
|
+
return openPose(frames.frameOf(item), box, natural, item?.fit, clip.corners, clip.rect)
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
return { fromSource }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { reactive } from 'vue'
|
|
2
2
|
import type { useLightboxFrames } from './useLightboxFrames'
|
|
3
3
|
import { baseScale } from '../utils/pose'
|
|
4
|
-
import { rotatedSize } from '../utils/zoom'
|
|
4
|
+
import { rotatedSize, zoomLevels } from '../utils/zoom'
|
|
5
5
|
import type { LightboxItem } from '../types'
|
|
6
6
|
|
|
7
7
|
export function useLightboxRotation(
|
|
@@ -15,17 +15,27 @@ export function useLightboxRotation(
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function baseOf(item: LightboxItem | undefined): number {
|
|
18
|
-
|
|
18
|
+
const frame = frames.frameOf(item)
|
|
19
|
+
return baseScale(
|
|
20
|
+
frame,
|
|
21
|
+
frames.area.value,
|
|
22
|
+
rotationOf(item) % 360,
|
|
23
|
+
frames.displayOf(item) ?? frame,
|
|
24
|
+
)
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
function geometry() {
|
|
22
28
|
const item = current()
|
|
23
29
|
const base = baseOf(item)
|
|
24
30
|
const visual = rotatedSize(frames.frameOf(item), rotationOf(item) % 360)
|
|
31
|
+
const fit = { width: visual.width * base, height: visual.height * base }
|
|
32
|
+
const natural = rotatedSize(frames.displayOf(item) ?? fit, rotationOf(item) % 360)
|
|
25
33
|
return {
|
|
26
|
-
fit
|
|
27
|
-
|
|
34
|
+
fit,
|
|
35
|
+
frame: frames.frameOf(item),
|
|
36
|
+
stage: frames.area.value,
|
|
28
37
|
base,
|
|
38
|
+
...zoomLevels(natural, fit, frames.area.value),
|
|
29
39
|
}
|
|
30
40
|
}
|
|
31
41
|
|