@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { onBeforeUnmount, shallowRef } from 'vue'
|
|
1
|
+
import { computed, onBeforeUnmount, shallowRef } from 'vue'
|
|
2
2
|
import { animate } from 'motion-v'
|
|
3
3
|
import { prefersReducedMotion, type TransitionName } from '../../../motion'
|
|
4
4
|
import type { useLightboxMotion } from './useLightboxMotion'
|
|
@@ -6,6 +6,7 @@ import { FLING, TRACKPAD_INTENSITY } from '../utils/gesture'
|
|
|
6
6
|
import {
|
|
7
7
|
WHEEL_INTENSITY,
|
|
8
8
|
ZOOM_MIN,
|
|
9
|
+
ZOOM_EPSILON,
|
|
9
10
|
clampOffset,
|
|
10
11
|
clampZoom,
|
|
11
12
|
doubleTapZoom,
|
|
@@ -30,16 +31,28 @@ export interface PinchStart {
|
|
|
30
31
|
|
|
31
32
|
export function useLightboxZoom(
|
|
32
33
|
motion: ReturnType<typeof useLightboxMotion>,
|
|
33
|
-
geometry: () => {
|
|
34
|
+
geometry: () => {
|
|
35
|
+
fit: Size
|
|
36
|
+
frame: Size
|
|
37
|
+
stage: Size
|
|
38
|
+
base: number
|
|
39
|
+
original: number
|
|
40
|
+
secondary: number
|
|
41
|
+
max: number
|
|
42
|
+
},
|
|
34
43
|
) {
|
|
35
44
|
const { x, y, scale } = motion
|
|
36
|
-
const
|
|
45
|
+
const raw = shallowRef(scale.get())
|
|
37
46
|
const unsubscribe = scale.on('change', value => {
|
|
38
|
-
|
|
47
|
+
raw.value = value
|
|
39
48
|
})
|
|
40
49
|
onBeforeUnmount(unsubscribe)
|
|
41
50
|
|
|
42
|
-
const zoom = () =>
|
|
51
|
+
const zoom = () => raw.value / geometry().base
|
|
52
|
+
const zoomed = computed(() => isZoomed(zoom()))
|
|
53
|
+
const canZoomIn = computed(() => zoom() < geometry().max - ZOOM_EPSILON)
|
|
54
|
+
const canToggle = computed(() => geometry().secondary > ZOOM_MIN + ZOOM_EPSILON)
|
|
55
|
+
const atOriginal = computed(() => Math.abs(zoom() - geometry().original) < ZOOM_EPSILON)
|
|
43
56
|
const offset = (): Point => ({ x: x.get(), y: y.get() })
|
|
44
57
|
|
|
45
58
|
function bounds(level = zoom()): Bounds {
|
|
@@ -57,7 +70,7 @@ export function useLightboxZoom(
|
|
|
57
70
|
}
|
|
58
71
|
|
|
59
72
|
function target(level: number, focal: Point): { level: number; offset: Point } {
|
|
60
|
-
const clamped = clampZoom(level)
|
|
73
|
+
const clamped = clampZoom(level, ZOOM_MIN, geometry().max)
|
|
61
74
|
if (!isZoomed(clamped)) return { level: ZOOM_MIN, offset: { x: 0, y: 0 } }
|
|
62
75
|
return {
|
|
63
76
|
level: clamped,
|
|
@@ -74,7 +87,7 @@ export function useLightboxZoom(
|
|
|
74
87
|
}
|
|
75
88
|
|
|
76
89
|
function step(direction: 1 | -1) {
|
|
77
|
-
zoomTo(stepZoom(zoom(), direction), { x: 0, y: 0 })
|
|
90
|
+
zoomTo(stepZoom(zoom(), direction, geometry().max), { x: 0, y: 0 })
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
function reset() {
|
|
@@ -88,11 +101,11 @@ export function useLightboxZoom(
|
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
function doubleTap(focal: Point) {
|
|
91
|
-
zoomTo(doubleTapZoom(zoom()), focal)
|
|
104
|
+
zoomTo(doubleTapZoom(zoom(), geometry().secondary), focal)
|
|
92
105
|
}
|
|
93
106
|
|
|
94
107
|
function pinch(start: PinchStart, mid: Point, ratio: number) {
|
|
95
|
-
const level = elasticZoom(start.zoom * ratio)
|
|
108
|
+
const level = elasticZoom(start.zoom * ratio, ZOOM_MIN, geometry().max)
|
|
96
109
|
const about = zoomAbout(start.offset, start.zoom, level, start.mid)
|
|
97
110
|
place(level, { x: about.x + mid.x - start.mid.x, y: about.y + mid.y - start.mid.y })
|
|
98
111
|
}
|
|
@@ -101,6 +114,30 @@ export function useLightboxZoom(
|
|
|
101
114
|
zoomTo(zoom(), focal, 'press')
|
|
102
115
|
}
|
|
103
116
|
|
|
117
|
+
function reflow(update: () => void) {
|
|
118
|
+
const before = geometry()
|
|
119
|
+
const wasZoomed = isZoomed(zoom())
|
|
120
|
+
const currentScale = scale.get()
|
|
121
|
+
update()
|
|
122
|
+
const after = geometry()
|
|
123
|
+
if (before.frame.width <= 0 || after.frame.width <= 0) return
|
|
124
|
+
if (
|
|
125
|
+
before.frame.width === after.frame.width &&
|
|
126
|
+
before.frame.height === after.frame.height &&
|
|
127
|
+
before.base === after.base &&
|
|
128
|
+
before.stage.width === after.stage.width &&
|
|
129
|
+
before.stage.height === after.stage.height
|
|
130
|
+
)
|
|
131
|
+
return
|
|
132
|
+
scale.stop()
|
|
133
|
+
scale.set((currentScale * before.frame.width) / after.frame.width)
|
|
134
|
+
zoomTo(wasZoomed ? zoom() : ZOOM_MIN, { x: 0, y: 0 })
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function original() {
|
|
138
|
+
zoomTo(geometry().original, { x: 0, y: 0 })
|
|
139
|
+
}
|
|
140
|
+
|
|
104
141
|
function pan(origin: Point, delta: Point) {
|
|
105
142
|
const next = elasticOffset(
|
|
106
143
|
{ x: origin.x + delta.x, y: origin.y + delta.y },
|
|
@@ -133,6 +170,11 @@ export function useLightboxZoom(
|
|
|
133
170
|
|
|
134
171
|
return {
|
|
135
172
|
zoomed,
|
|
173
|
+
canZoomIn,
|
|
174
|
+
canToggle,
|
|
175
|
+
atOriginal,
|
|
176
|
+
original,
|
|
177
|
+
reflow,
|
|
136
178
|
zoom,
|
|
137
179
|
isZoomed: () => isZoomed(zoom()),
|
|
138
180
|
zoomTo,
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { ImageVariants } from '../image/image.variants'
|
|
2
|
+
import type { Rect } from './utils/pose'
|
|
3
|
+
|
|
4
|
+
export type LightboxSource = HTMLImageElement | Rect
|
|
2
5
|
|
|
3
6
|
export interface LightboxItem {
|
|
4
7
|
id: string
|
|
5
8
|
src: string
|
|
6
9
|
preview?: string
|
|
10
|
+
previewSize?: { width: number; height: number }
|
|
7
11
|
alt: string
|
|
8
12
|
fit?: ImageVariants['fit']
|
|
9
|
-
source?: () =>
|
|
13
|
+
source?: () => LightboxSource | null | undefined
|
|
10
14
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Rect } from './pose'
|
|
2
|
+
|
|
3
|
+
export interface Corner {
|
|
4
|
+
x: number
|
|
5
|
+
y: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type Corners = [Corner, Corner, Corner, Corner]
|
|
9
|
+
|
|
10
|
+
function emptyCorners(): Corners {
|
|
11
|
+
return [
|
|
12
|
+
{ x: 0, y: 0 },
|
|
13
|
+
{ x: 0, y: 0 },
|
|
14
|
+
{ x: 0, y: 0 },
|
|
15
|
+
{ x: 0, y: 0 },
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function length(value: string, size: number): number {
|
|
20
|
+
const number = Number.parseFloat(value) || 0
|
|
21
|
+
return value.endsWith('%') ? (number * size) / 100 : number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function cornersOf(style: CSSStyleDeclaration, width: number, height: number): Corners {
|
|
25
|
+
const corners = [
|
|
26
|
+
style.borderTopLeftRadius,
|
|
27
|
+
style.borderTopRightRadius,
|
|
28
|
+
style.borderBottomRightRadius,
|
|
29
|
+
style.borderBottomLeftRadius,
|
|
30
|
+
].map(value => {
|
|
31
|
+
const [x = '0', y = x] = value.split(/\s+/)
|
|
32
|
+
return { x: length(x, width), y: length(y, height) }
|
|
33
|
+
}) as Corners
|
|
34
|
+
const [tl, tr, br, bl] = corners
|
|
35
|
+
const factor = Math.min(
|
|
36
|
+
1,
|
|
37
|
+
width / (tl.x + tr.x || 1),
|
|
38
|
+
width / (bl.x + br.x || 1),
|
|
39
|
+
height / (tl.y + bl.y || 1),
|
|
40
|
+
height / (tr.y + br.y || 1),
|
|
41
|
+
)
|
|
42
|
+
return corners.map(corner => ({ x: corner.x * factor, y: corner.y * factor })) as Corners
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function paddingBox(node: Element, style: CSSStyleDeclaration) {
|
|
46
|
+
const box = node.getBoundingClientRect()
|
|
47
|
+
const width = node instanceof HTMLElement ? node.offsetWidth : box.width
|
|
48
|
+
const height = node instanceof HTMLElement ? node.offsetHeight : box.height
|
|
49
|
+
const sx = width ? box.width / width : 1
|
|
50
|
+
const sy = height ? box.height / height : 1
|
|
51
|
+
const left = Number.parseFloat(style.borderLeftWidth) || 0
|
|
52
|
+
const right = Number.parseFloat(style.borderRightWidth) || 0
|
|
53
|
+
const top = Number.parseFloat(style.borderTopWidth) || 0
|
|
54
|
+
const bottom = Number.parseFloat(style.borderBottomWidth) || 0
|
|
55
|
+
const corners = cornersOf(style, width, height)
|
|
56
|
+
const borders = [
|
|
57
|
+
[left, top],
|
|
58
|
+
[right, top],
|
|
59
|
+
[right, bottom],
|
|
60
|
+
[left, bottom],
|
|
61
|
+
]
|
|
62
|
+
return {
|
|
63
|
+
rect: {
|
|
64
|
+
x: box.x + left * sx,
|
|
65
|
+
y: box.y + top * sy,
|
|
66
|
+
width: Math.max(0, box.width - (left + right) * sx),
|
|
67
|
+
height: Math.max(0, box.height - (top + bottom) * sy),
|
|
68
|
+
},
|
|
69
|
+
corners: corners.map((corner, index) => ({
|
|
70
|
+
x: Math.max(0, corner.x - borders[index]![0]!) * sx,
|
|
71
|
+
y: Math.max(0, corner.y - borders[index]![1]!) * sy,
|
|
72
|
+
})) as Corners,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function points(rect: Rect) {
|
|
77
|
+
return [
|
|
78
|
+
[rect.x, rect.y],
|
|
79
|
+
[rect.x + rect.width, rect.y],
|
|
80
|
+
[rect.x + rect.width, rect.y + rect.height],
|
|
81
|
+
[rect.x, rect.y + rect.height],
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function clipOf(el: Element | null | undefined, box: Rect) {
|
|
86
|
+
let rect = { x: box.x, y: box.y, width: box.width, height: box.height }
|
|
87
|
+
const clips: ReturnType<typeof paddingBox>[] = []
|
|
88
|
+
for (let node = el; node; node = node.parentElement) {
|
|
89
|
+
const style = getComputedStyle(node)
|
|
90
|
+
const clipX = node === el || /^(hidden|clip|scroll|auto)$/.test(style.overflowX)
|
|
91
|
+
const clipY = node === el || /^(hidden|clip|scroll|auto)$/.test(style.overflowY)
|
|
92
|
+
if (!clipX && !clipY) continue
|
|
93
|
+
const clip = paddingBox(node, style)
|
|
94
|
+
const x = clipX ? Math.max(rect.x, clip.rect.x) : rect.x
|
|
95
|
+
const y = clipY ? Math.max(rect.y, clip.rect.y) : rect.y
|
|
96
|
+
const right = clipX
|
|
97
|
+
? Math.min(rect.x + rect.width, clip.rect.x + clip.rect.width)
|
|
98
|
+
: rect.x + rect.width
|
|
99
|
+
const bottom = clipY
|
|
100
|
+
? Math.min(rect.y + rect.height, clip.rect.y + clip.rect.height)
|
|
101
|
+
: rect.y + rect.height
|
|
102
|
+
rect = { x, y, width: Math.max(0, right - x), height: Math.max(0, bottom - y) }
|
|
103
|
+
if (clipX && clipY) clips.push(clip)
|
|
104
|
+
}
|
|
105
|
+
const corners = emptyCorners()
|
|
106
|
+
const visible = points(rect)
|
|
107
|
+
for (const clip of clips) {
|
|
108
|
+
const edges = points(clip.rect)
|
|
109
|
+
for (let index = 0; index < 4; index += 1) {
|
|
110
|
+
if (visible[index]!.some((value, axis) => Math.abs(value - edges[index]![axis]!) > 0.5))
|
|
111
|
+
continue
|
|
112
|
+
corners[index]!.x = Math.max(corners[index]!.x, clip.corners[index]!.x)
|
|
113
|
+
corners[index]!.y = Math.max(corners[index]!.y, clip.corners[index]!.y)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { rect, corners }
|
|
117
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ImageVariants } from '../../image/image.variants'
|
|
2
|
+
import type { Corners } from './clip'
|
|
2
3
|
import { fitSize, rotatedSize, type Size } from './zoom'
|
|
3
4
|
|
|
4
5
|
export interface Rect {
|
|
@@ -21,7 +22,7 @@ export const REST_POSE: Pose = {
|
|
|
21
22
|
y: 0,
|
|
22
23
|
scale: 1,
|
|
23
24
|
rotate: 0,
|
|
24
|
-
clipPath: 'inset(0px 0px 0px 0px round 0px)',
|
|
25
|
+
clipPath: 'inset(0px 0px 0px 0px round 0px 0px 0px 0px / 0px 0px 0px 0px)',
|
|
25
26
|
}
|
|
26
27
|
export const TRAVEL_SPRING = { type: 'spring', visualDuration: 0.3, bounce: 0 } as const
|
|
27
28
|
export const RETURN_SPRING = { ...TRAVEL_SPRING, visualDuration: 0.22 } as const
|
|
@@ -34,11 +35,15 @@ function px(value: number): string {
|
|
|
34
35
|
return `${Math.round(value * 100) / 100}px`
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
export function fitRect(
|
|
38
|
+
export function fitRect(
|
|
39
|
+
natural: Size,
|
|
40
|
+
stage: Size & Partial<Pick<Rect, 'x' | 'y'>>,
|
|
41
|
+
rotation = 0,
|
|
42
|
+
): Rect {
|
|
38
43
|
const size = fitSize(natural, stage, rotation)
|
|
39
44
|
return {
|
|
40
|
-
x: (stage.width - size.width) / 2,
|
|
41
|
-
y: (stage.height - size.height) / 2,
|
|
45
|
+
x: (stage.x ?? 0) + (stage.width - size.width) / 2,
|
|
46
|
+
y: (stage.y ?? 0) + (stage.height - size.height) / 2,
|
|
42
47
|
width: size.width,
|
|
43
48
|
height: size.height,
|
|
44
49
|
}
|
|
@@ -79,39 +84,51 @@ export function openPose(
|
|
|
79
84
|
box: Rect,
|
|
80
85
|
natural: Size,
|
|
81
86
|
fit?: ImageVariants['fit'],
|
|
82
|
-
radius = 0,
|
|
87
|
+
radius: number | Corners = 0,
|
|
88
|
+
clipBox = box,
|
|
83
89
|
): Pose {
|
|
84
90
|
const rendered = renderedRect(box, natural, fit)
|
|
85
91
|
if (frame.width <= 0 || frame.height <= 0 || rendered.width <= 0) return REST_POSE
|
|
86
92
|
const scale = rendered.width / frame.width
|
|
87
|
-
const visible = intersect(box, rendered)
|
|
93
|
+
const visible = intersect(intersect(box, clipBox), rendered)
|
|
88
94
|
const top = (visible.y - rendered.y) / scale
|
|
89
95
|
const left = (visible.x - rendered.x) / scale
|
|
90
96
|
const right = (rendered.x + rendered.width - visible.x - visible.width) / scale
|
|
91
97
|
const bottom = (rendered.y + rendered.height - visible.y - visible.height) / scale
|
|
92
|
-
const
|
|
98
|
+
const corners =
|
|
99
|
+
typeof radius === 'number'
|
|
100
|
+
? Array.from({ length: 4 }, () => ({ x: radius, y: radius }))
|
|
101
|
+
: radius
|
|
102
|
+
const leftEdge = Math.abs(visible.x - clipBox.x) <= 0.5
|
|
103
|
+
const topEdge = Math.abs(visible.y - clipBox.y) <= 0.5
|
|
104
|
+
const rightEdge = Math.abs(visible.x + visible.width - clipBox.x - clipBox.width) <= 0.5
|
|
105
|
+
const bottomEdge = Math.abs(visible.y + visible.height - clipBox.y - clipBox.height) <= 0.5
|
|
106
|
+
const touches = [
|
|
107
|
+
leftEdge && topEdge,
|
|
108
|
+
rightEdge && topEdge,
|
|
109
|
+
rightEdge && bottomEdge,
|
|
110
|
+
leftEdge && bottomEdge,
|
|
111
|
+
]
|
|
112
|
+
const clipped = corners.map((corner, index) => (touches[index] ? corner : { x: 0, y: 0 }))
|
|
113
|
+
const horizontal = clipped.map(corner => px(Math.max(0, corner.x) / scale)).join(' ')
|
|
114
|
+
const vertical = clipped.map(corner => px(Math.max(0, corner.y) / scale)).join(' ')
|
|
93
115
|
return {
|
|
94
116
|
x: rendered.x + rendered.width / 2 - (frame.x + frame.width / 2),
|
|
95
117
|
y: rendered.y + rendered.height / 2 - (frame.y + frame.height / 2),
|
|
96
118
|
scale,
|
|
97
119
|
rotate: 0,
|
|
98
|
-
clipPath: `inset(${px(top)} ${px(right)} ${px(bottom)} ${px(left)} round ${
|
|
120
|
+
clipPath: `inset(${px(top)} ${px(right)} ${px(bottom)} ${px(left)} round ${horizontal} / ${vertical})`,
|
|
99
121
|
}
|
|
100
122
|
}
|
|
101
123
|
|
|
102
|
-
export function baseScale(frame: Size, stage: Size, rotation: number): number {
|
|
124
|
+
export function baseScale(frame: Size, stage: Size, rotation: number, natural: Size): number {
|
|
103
125
|
const visual = rotatedSize(frame, rotation)
|
|
104
126
|
if (visual.width <= 0 || visual.height <= 0) return 1
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
for (let node = el, depth = 0; node && depth < 2; node = node.parentElement, depth += 1) {
|
|
111
|
-
const value = Number.parseFloat(getComputedStyle(node).borderTopLeftRadius)
|
|
112
|
-
if (value > 0) return value
|
|
113
|
-
}
|
|
114
|
-
return 0
|
|
127
|
+
return Math.min(
|
|
128
|
+
stage.width / visual.width,
|
|
129
|
+
stage.height / visual.height,
|
|
130
|
+
natural.width / frame.width,
|
|
131
|
+
)
|
|
115
132
|
}
|
|
116
133
|
|
|
117
134
|
export function dismissProgress(offsetY: number, stageHeight: number): number {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { LightboxSource } from '../types'
|
|
2
|
+
import type { Rect } from './pose'
|
|
3
|
+
|
|
4
|
+
export function sourceRect(source: LightboxSource | null | undefined): Rect | null {
|
|
5
|
+
if (!source) return null
|
|
6
|
+
const isElement = 'getBoundingClientRect' in source
|
|
7
|
+
if (isElement && !source.isConnected) return null
|
|
8
|
+
const rect = isElement ? source.getBoundingClientRect() : source
|
|
9
|
+
if (
|
|
10
|
+
![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite) ||
|
|
11
|
+
rect.width <= 0 ||
|
|
12
|
+
rect.height <= 0
|
|
13
|
+
) {
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
|
|
17
|
+
}
|
|
@@ -16,8 +16,7 @@ export interface Bounds {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export const ZOOM_MIN = 1
|
|
19
|
-
export const ZOOM_MAX =
|
|
20
|
-
export const ZOOM_DOUBLE_TAP = 2.5
|
|
19
|
+
export const ZOOM_MAX = 2
|
|
21
20
|
export const ZOOM_STEP = 1.5
|
|
22
21
|
export const OVERSHOOT = 0.55
|
|
23
22
|
export const WHEEL_INTENSITY = 0.002
|
|
@@ -36,7 +35,7 @@ export function rotatedSize(size: Size, rotation: number): Size {
|
|
|
36
35
|
export function fitSize(natural: Size, stage: Size, rotation = 0): Size {
|
|
37
36
|
const upright = rotatedSize(natural, rotation)
|
|
38
37
|
if (upright.width <= 0 || upright.height <= 0) return { width: 0, height: 0 }
|
|
39
|
-
const scale = Math.min(stage.width / upright.width, stage.height / upright.height)
|
|
38
|
+
const scale = Math.min(1, stage.width / upright.width, stage.height / upright.height)
|
|
40
39
|
return { width: upright.width * scale, height: upright.height * scale }
|
|
41
40
|
}
|
|
42
41
|
|
|
@@ -104,10 +103,25 @@ export function wheelZoom(zoom: number, deltaY: number, intensity = WHEEL_INTENS
|
|
|
104
103
|
return zoom * Math.exp(-deltaY * intensity)
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
export function
|
|
108
|
-
|
|
106
|
+
export function zoomLevels(natural: Size, fit: Size, stage: Size) {
|
|
107
|
+
const original = fit.width > 0 ? Math.max(ZOOM_MIN, natural.width / fit.width) : ZOOM_MIN
|
|
108
|
+
const secondary =
|
|
109
|
+
natural.height > natural.width * 2
|
|
110
|
+
? Math.min(original, stage.width / fit.width)
|
|
111
|
+
: natural.width > natural.height * 2
|
|
112
|
+
? Math.min(original, stage.height / fit.height)
|
|
113
|
+
: original
|
|
114
|
+
return {
|
|
115
|
+
original,
|
|
116
|
+
secondary: Number.isFinite(secondary) ? Math.max(ZOOM_MIN, secondary) : ZOOM_MIN,
|
|
117
|
+
max: original * ZOOM_MAX,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function doubleTapZoom(zoom: number, secondary: number): number {
|
|
122
|
+
return isZoomed(zoom) ? ZOOM_MIN : secondary
|
|
109
123
|
}
|
|
110
124
|
|
|
111
|
-
export function stepZoom(zoom: number, direction: 1 | -1): number {
|
|
112
|
-
return clampZoom(zoom * ZOOM_STEP ** direction)
|
|
125
|
+
export function stepZoom(zoom: number, direction: 1 | -1, max = ZOOM_MAX): number {
|
|
126
|
+
return clampZoom(zoom * ZOOM_STEP ** direction, ZOOM_MIN, max)
|
|
113
127
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { PaginationRoot } from 'reka-ui'
|
|
4
|
+
import { cn } from '../../lib/cn'
|
|
5
|
+
import { useDirection } from '../../lib/useDirection'
|
|
6
|
+
import { useUiLocale } from '../../locale'
|
|
7
|
+
import LoadingOverlay from '../loading-overlay/LoadingOverlay.vue'
|
|
8
|
+
import PaginationContent from './PaginationContent.vue'
|
|
9
|
+
import PaginationInfo from './PaginationInfo.vue'
|
|
10
|
+
import PaginationSize from './PaginationSize.vue'
|
|
11
|
+
import PaginationJump from './PaginationJump.vue'
|
|
12
|
+
import { providePagination } from './context'
|
|
13
|
+
import { usePagination } from './composables/usePagination'
|
|
14
|
+
import { pagination } from './pagination.variants'
|
|
15
|
+
import type { PaginationChange, PaginationState } from './types'
|
|
16
|
+
|
|
17
|
+
defineOptions({ name: 'HnPagination', inheritAttrs: false })
|
|
18
|
+
const props = withDefaults(
|
|
19
|
+
defineProps<{
|
|
20
|
+
total: number
|
|
21
|
+
itemCount?: number
|
|
22
|
+
siblingCount?: number
|
|
23
|
+
showEdges?: boolean
|
|
24
|
+
showFirstLast?: boolean
|
|
25
|
+
showInfo?: boolean
|
|
26
|
+
showJump?: boolean
|
|
27
|
+
hideSinglePage?: boolean
|
|
28
|
+
pageSizeOptions?: number[]
|
|
29
|
+
pending?: boolean
|
|
30
|
+
align?: 'start' | 'center' | 'end' | 'between'
|
|
31
|
+
size?: 'sm' | 'md' | 'lg'
|
|
32
|
+
disabled?: boolean
|
|
33
|
+
dir?: 'ltr' | 'rtl'
|
|
34
|
+
label?: string
|
|
35
|
+
class?: string
|
|
36
|
+
}>(),
|
|
37
|
+
{ siblingCount: 1, showEdges: true, size: 'md', align: 'start' },
|
|
38
|
+
)
|
|
39
|
+
const model = defineModel<number>({ default: 1 })
|
|
40
|
+
const sizeModel = defineModel<number>('pageSize', { default: 10 })
|
|
41
|
+
const emit = defineEmits<{ change: [value: PaginationChange] }>()
|
|
42
|
+
defineSlots<{
|
|
43
|
+
default?(state: PaginationState): unknown
|
|
44
|
+
list?(state: PaginationState): unknown
|
|
45
|
+
page?(props: { page: number; selected: boolean }): unknown
|
|
46
|
+
ellipsis?(props: { side: 'prev' | 'next'; expanded: boolean }): unknown
|
|
47
|
+
}>()
|
|
48
|
+
const t = useUiLocale()
|
|
49
|
+
const { root, direction, rootDirection } = useDirection(() => props.dir)
|
|
50
|
+
const { total, pageSize, siblingCount, page, state, blocked, sizes, update, resize } =
|
|
51
|
+
usePagination(props, model, sizeModel, value => emit('change', value))
|
|
52
|
+
providePagination({
|
|
53
|
+
state,
|
|
54
|
+
blocked,
|
|
55
|
+
siblingCount,
|
|
56
|
+
update,
|
|
57
|
+
resize,
|
|
58
|
+
direction,
|
|
59
|
+
size: computed(() => props.size),
|
|
60
|
+
showFirstLast: computed(() => props.showFirstLast),
|
|
61
|
+
options: computed(() =>
|
|
62
|
+
sizes.value.map(value => ({ value, label: t.value.pagination.pageSizeOption(value) })),
|
|
63
|
+
),
|
|
64
|
+
})
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<div
|
|
69
|
+
ref="root"
|
|
70
|
+
v-bind="$attrs"
|
|
71
|
+
data-hn-pagination
|
|
72
|
+
:dir="rootDirection"
|
|
73
|
+
:aria-busy="props.pending || undefined"
|
|
74
|
+
:class="cn('max-w-full', $slots.list && 'flex min-h-0 flex-col gap-3', props.class)"
|
|
75
|
+
>
|
|
76
|
+
<div v-if="$slots.list" class="relative min-h-0 flex-auto">
|
|
77
|
+
<div :inert="props.pending || undefined"><slot name="list" v-bind="state" /></div>
|
|
78
|
+
<LoadingOverlay :visible="props.pending" />
|
|
79
|
+
</div>
|
|
80
|
+
<PaginationRoot
|
|
81
|
+
v-if="!props.hideSinglePage || state.pageCount > 1"
|
|
82
|
+
:page="page"
|
|
83
|
+
:total="total"
|
|
84
|
+
:items-per-page="pageSize"
|
|
85
|
+
:sibling-count="siblingCount"
|
|
86
|
+
:show-edges="props.showEdges"
|
|
87
|
+
:disabled="blocked"
|
|
88
|
+
as-child
|
|
89
|
+
@update:page="update"
|
|
90
|
+
>
|
|
91
|
+
<nav
|
|
92
|
+
:aria-label="props.label ?? t.pagination.navLabel"
|
|
93
|
+
:inert="props.pending || undefined"
|
|
94
|
+
:data-disabled="blocked ? '' : undefined"
|
|
95
|
+
:class="pagination({ align: props.align })"
|
|
96
|
+
>
|
|
97
|
+
<slot v-bind="state">
|
|
98
|
+
<PaginationInfo v-if="props.showInfo" />
|
|
99
|
+
<PaginationContent>
|
|
100
|
+
<template v-if="$slots.page" #page="slotProps">
|
|
101
|
+
<slot name="page" v-bind="slotProps" />
|
|
102
|
+
</template>
|
|
103
|
+
<template v-if="$slots.ellipsis" #ellipsis="slotProps">
|
|
104
|
+
<slot name="ellipsis" v-bind="slotProps" />
|
|
105
|
+
</template>
|
|
106
|
+
</PaginationContent>
|
|
107
|
+
<div
|
|
108
|
+
v-if="props.pageSizeOptions?.length || props.showJump"
|
|
109
|
+
class="flex max-w-full flex-wrap items-center gap-3"
|
|
110
|
+
>
|
|
111
|
+
<PaginationSize v-if="props.pageSizeOptions?.length" />
|
|
112
|
+
<PaginationJump v-if="props.showJump" />
|
|
113
|
+
</div>
|
|
114
|
+
</slot>
|
|
115
|
+
</nav>
|
|
116
|
+
</PaginationRoot>
|
|
117
|
+
</div>
|
|
118
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { shallowRef } from 'vue'
|
|
3
|
+
import { PaginationList } from 'reka-ui'
|
|
4
|
+
import { cn } from '../../lib/cn'
|
|
5
|
+
import PaginationPages from './PaginationPages.vue'
|
|
6
|
+
import { paginationList } from './pagination.variants'
|
|
7
|
+
defineOptions({ name: 'HnPaginationContent' })
|
|
8
|
+
const props = defineProps<{ class?: string }>()
|
|
9
|
+
defineSlots<{
|
|
10
|
+
page?(props: { page: number; selected: boolean }): unknown
|
|
11
|
+
ellipsis?(props: { side: 'prev' | 'next'; expanded: boolean }): unknown
|
|
12
|
+
}>()
|
|
13
|
+
const host = shallowRef<HTMLElement>()
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<PaginationList v-slot="{ items }" as-child>
|
|
18
|
+
<ul ref="host" data-hn-pagination-content :class="cn(paginationList(), props.class)">
|
|
19
|
+
<PaginationPages :items="items" :host="host">
|
|
20
|
+
<template v-if="$slots.page" #page="slotProps">
|
|
21
|
+
<slot name="page" v-bind="slotProps" />
|
|
22
|
+
</template>
|
|
23
|
+
<template v-if="$slots.ellipsis" #ellipsis="slotProps">
|
|
24
|
+
<slot name="ellipsis" v-bind="slotProps" />
|
|
25
|
+
</template>
|
|
26
|
+
</PaginationPages>
|
|
27
|
+
</ul>
|
|
28
|
+
</PaginationList>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from '@lucide/vue'
|
|
3
|
+
import { PaginationFirst, PaginationPrev, PaginationNext, PaginationLast } from 'reka-ui'
|
|
4
|
+
import { useUiLocale } from '../../locale'
|
|
5
|
+
import Button from '../button/Button.vue'
|
|
6
|
+
import type { ButtonVariants } from '../button/button.variants'
|
|
7
|
+
import { paginationArrow } from './pagination.variants'
|
|
8
|
+
|
|
9
|
+
defineOptions({ name: 'HnPaginationControl' })
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
action: 'first' | 'prev' | 'next' | 'last'
|
|
13
|
+
size?: ButtonVariants['size']
|
|
14
|
+
dir: 'ltr' | 'rtl'
|
|
15
|
+
}>()
|
|
16
|
+
const t = useUiLocale()
|
|
17
|
+
const controls = {
|
|
18
|
+
first: { component: PaginationFirst, icon: ChevronsLeft },
|
|
19
|
+
prev: { component: PaginationPrev, icon: ChevronLeft },
|
|
20
|
+
next: { component: PaginationNext, icon: ChevronRight },
|
|
21
|
+
last: { component: PaginationLast, icon: ChevronsRight },
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<component :is="controls[props.action].component" as-child>
|
|
27
|
+
<Button
|
|
28
|
+
variant="ghost"
|
|
29
|
+
tone="neutral"
|
|
30
|
+
:size="props.size"
|
|
31
|
+
icon-only
|
|
32
|
+
:aria-label="t.pagination[props.action]"
|
|
33
|
+
:data-hn-pagination-action="props.action"
|
|
34
|
+
>
|
|
35
|
+
<component
|
|
36
|
+
:is="controls[props.action].icon"
|
|
37
|
+
aria-hidden="true"
|
|
38
|
+
:class="paginationArrow({ dir: props.dir })"
|
|
39
|
+
/>
|
|
40
|
+
</Button>
|
|
41
|
+
</component>
|
|
42
|
+
</template>
|