@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
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { computed, ref, type Ref } from 'vue'
|
|
2
|
+
import { useEventListener, useMutationObserver, useResizeObserver } from '@vueuse/core'
|
|
3
|
+
|
|
4
|
+
export function usePaginationPageTooltip(
|
|
5
|
+
label: Readonly<Ref<HTMLElement | null>>,
|
|
6
|
+
blocked: Readonly<Ref<boolean>>,
|
|
7
|
+
) {
|
|
8
|
+
const content = ref('')
|
|
9
|
+
|
|
10
|
+
function measure() {
|
|
11
|
+
const element = label.value
|
|
12
|
+
content.value =
|
|
13
|
+
element && element.scrollWidth > element.clientWidth
|
|
14
|
+
? (element.textContent?.trim() ?? '')
|
|
15
|
+
: ''
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
useResizeObserver(label, measure)
|
|
19
|
+
useMutationObserver(label, measure, { childList: true, characterData: true, subtree: true })
|
|
20
|
+
useEventListener(() => label.value?.ownerDocument.fonts, 'loadingdone', measure)
|
|
21
|
+
|
|
22
|
+
return computed(() => (blocked.value ? false : content.value))
|
|
23
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { computed, nextTick, ref, shallowRef, watch } from 'vue'
|
|
2
|
+
import { useEventListener, useResizeObserver } from '@vueuse/core'
|
|
3
|
+
import type ScrollArea from '../../scroll-area/ScrollArea.vue'
|
|
4
|
+
import type { PaginationEllipsisController } from './usePaginationEllipsis'
|
|
5
|
+
|
|
6
|
+
export function usePaginationWindow(controller: PaginationEllipsisController) {
|
|
7
|
+
const scroll = shallowRef<InstanceType<typeof ScrollArea>>()
|
|
8
|
+
const rowsHost = shallowRef<HTMLElement>()
|
|
9
|
+
const viewport = computed(() => scroll.value?.viewport)
|
|
10
|
+
const top = ref(0)
|
|
11
|
+
const height = ref(224)
|
|
12
|
+
const rowHeight = ref(36)
|
|
13
|
+
const rowGap = ref(0)
|
|
14
|
+
const rowStep = computed(() => rowHeight.value + rowGap.value)
|
|
15
|
+
const activePage = ref(1)
|
|
16
|
+
const range = controller.displayRange
|
|
17
|
+
const count = computed(() => (range.value ? range.value.to - range.value.from + 1 : 0))
|
|
18
|
+
const extent = computed(() => Math.max(0, count.value * rowStep.value - rowGap.value))
|
|
19
|
+
const rows = computed(() => {
|
|
20
|
+
if (!range.value) return []
|
|
21
|
+
const start = Math.max(0, Math.floor(top.value / rowStep.value) - 3)
|
|
22
|
+
const end = Math.min(count.value, start + Math.ceil(height.value / rowStep.value) + 7)
|
|
23
|
+
const values = Array.from(
|
|
24
|
+
{ length: Math.max(0, end - start) },
|
|
25
|
+
(_, i) => start + i + range.value!.from,
|
|
26
|
+
)
|
|
27
|
+
if (
|
|
28
|
+
activePage.value >= range.value.from &&
|
|
29
|
+
activePage.value <= range.value.to &&
|
|
30
|
+
!values.includes(activePage.value)
|
|
31
|
+
)
|
|
32
|
+
values.push(activePage.value)
|
|
33
|
+
return values.map(page => ({ page, top: (page - range.value!.from) * rowStep.value }))
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
function measure() {
|
|
37
|
+
if (viewport.value) height.value = viewport.value.clientHeight || 224
|
|
38
|
+
const button = rowsHost.value?.querySelector<HTMLElement>('[data-hn-pagination-choice]')
|
|
39
|
+
if (button) {
|
|
40
|
+
rowHeight.value = button.offsetHeight
|
|
41
|
+
rowGap.value = Number.parseFloat(getComputedStyle(rowsHost.value!).rowGap) || 0
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function focusPage(page: number) {
|
|
46
|
+
if (!range.value || !controller.open.value) return
|
|
47
|
+
activePage.value = Math.min(range.value.to, Math.max(range.value.from, page))
|
|
48
|
+
const element = viewport.value
|
|
49
|
+
if (element) {
|
|
50
|
+
const offset = (activePage.value - range.value.from) * rowStep.value
|
|
51
|
+
if (offset < element.scrollTop) element.scrollTop = offset
|
|
52
|
+
else if (offset + rowHeight.value > element.scrollTop + element.clientHeight)
|
|
53
|
+
element.scrollTop = offset + rowHeight.value - element.clientHeight
|
|
54
|
+
top.value = element.scrollTop
|
|
55
|
+
}
|
|
56
|
+
await nextTick()
|
|
57
|
+
rowsHost.value
|
|
58
|
+
?.querySelector<HTMLElement>('[data-hn-pagination-choice="' + activePage.value + '"]')
|
|
59
|
+
?.focus({ preventScroll: true })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function keydown(event: KeyboardEvent) {
|
|
63
|
+
if (!range.value) return
|
|
64
|
+
const step = Math.max(1, Math.floor(height.value / rowStep.value))
|
|
65
|
+
const targets: Record<string, number> = {
|
|
66
|
+
ArrowDown: activePage.value + 1,
|
|
67
|
+
ArrowUp: activePage.value - 1,
|
|
68
|
+
Home: range.value.from,
|
|
69
|
+
End: range.value.to,
|
|
70
|
+
PageDown: activePage.value + step,
|
|
71
|
+
PageUp: activePage.value - step,
|
|
72
|
+
}
|
|
73
|
+
const target = targets[event.key]
|
|
74
|
+
if (target === undefined) return
|
|
75
|
+
event.preventDefault()
|
|
76
|
+
event.stopPropagation()
|
|
77
|
+
void focusPage(target)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
watch(
|
|
81
|
+
[range, controller.open],
|
|
82
|
+
() => {
|
|
83
|
+
if (!controller.open.value || !range.value) return
|
|
84
|
+
activePage.value = range.value.from
|
|
85
|
+
top.value = 0
|
|
86
|
+
if (viewport.value) viewport.value.scrollTop = 0
|
|
87
|
+
},
|
|
88
|
+
{ immediate: true },
|
|
89
|
+
)
|
|
90
|
+
watch(
|
|
91
|
+
[controller.focusRequest, viewport],
|
|
92
|
+
async ([request, element]) => {
|
|
93
|
+
if (!request || !element || !range.value) return
|
|
94
|
+
await nextTick()
|
|
95
|
+
measure()
|
|
96
|
+
await focusPage(request.edge === 'first' ? range.value.from : range.value.to)
|
|
97
|
+
controller.focusRequest.value = undefined
|
|
98
|
+
},
|
|
99
|
+
{ flush: 'post' },
|
|
100
|
+
)
|
|
101
|
+
useResizeObserver(
|
|
102
|
+
() => rowsHost.value?.querySelector<HTMLElement>('[data-hn-pagination-choice]'),
|
|
103
|
+
measure,
|
|
104
|
+
)
|
|
105
|
+
useResizeObserver(rowsHost, measure)
|
|
106
|
+
useResizeObserver(viewport, measure)
|
|
107
|
+
useEventListener(
|
|
108
|
+
viewport,
|
|
109
|
+
'scroll',
|
|
110
|
+
() => {
|
|
111
|
+
top.value = viewport.value?.scrollTop ?? 0
|
|
112
|
+
},
|
|
113
|
+
{ passive: true },
|
|
114
|
+
)
|
|
115
|
+
return { scroll, rowsHost, activePage, extent, rows, keydown }
|
|
116
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { inject, provide, type InjectionKey, type Ref } from 'vue'
|
|
2
|
+
import type { PaginationState } from './types'
|
|
3
|
+
|
|
4
|
+
export interface PaginationContext {
|
|
5
|
+
state: Readonly<Ref<PaginationState>>
|
|
6
|
+
blocked: Readonly<Ref<boolean>>
|
|
7
|
+
size: Readonly<Ref<'sm' | 'md' | 'lg'>>
|
|
8
|
+
direction: Readonly<Ref<'ltr' | 'rtl'>>
|
|
9
|
+
siblingCount: Readonly<Ref<number>>
|
|
10
|
+
showFirstLast: Readonly<Ref<boolean>>
|
|
11
|
+
options: Readonly<Ref<Array<{ value: number; label: string }>>>
|
|
12
|
+
update: (page: number) => void
|
|
13
|
+
resize: (pageSize: number) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const key: InjectionKey<PaginationContext> = Symbol('HnPagination')
|
|
17
|
+
|
|
18
|
+
export const providePagination = (context: PaginationContext) => provide(key, context)
|
|
19
|
+
export function usePaginationContext() {
|
|
20
|
+
const context = inject(key)
|
|
21
|
+
if (!context) throw new Error('Pagination parts require a Pagination parent')
|
|
22
|
+
return context
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { tv } from '../../lib/tv'
|
|
2
|
+
|
|
3
|
+
export const pagination = tv({
|
|
4
|
+
base: 'flex w-full shrink-0 flex-wrap items-center gap-x-4 gap-y-3',
|
|
5
|
+
variants: {
|
|
6
|
+
align: {
|
|
7
|
+
start: 'justify-start',
|
|
8
|
+
center: 'justify-center',
|
|
9
|
+
end: 'justify-end',
|
|
10
|
+
between: 'justify-between',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export const paginationList = tv({
|
|
16
|
+
base: 'relative isolate flex max-w-full flex-wrap items-center gap-1',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export const paginationItem = tv({
|
|
20
|
+
base: 'min-w-0 px-0 tabular-nums [&>span]:min-w-0',
|
|
21
|
+
variants: {
|
|
22
|
+
size: {
|
|
23
|
+
sm: 'size-[var(--hn-control-h-sm)]',
|
|
24
|
+
md: 'size-[var(--hn-control-h-md)]',
|
|
25
|
+
lg: 'size-[var(--hn-control-h-lg)]',
|
|
26
|
+
},
|
|
27
|
+
selected: { true: 'bg-accent text-accent-on' },
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: { size: 'md' },
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export const paginationArrow = tv({ variants: { dir: { ltr: '', rtl: 'rotate-180' } } })
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface PaginationChange {
|
|
2
|
+
page: number
|
|
3
|
+
pageSize: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface PaginationState extends PaginationChange {
|
|
7
|
+
total: number
|
|
8
|
+
pageCount: number
|
|
9
|
+
from: number
|
|
10
|
+
to: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type PaginationEntry = { type: 'page'; value: number } | { type: 'ellipsis' }
|
|
14
|
+
export type PaginationSide = 'prev' | 'next'
|
|
15
|
+
export interface PaginationRange {
|
|
16
|
+
side: PaginationSide
|
|
17
|
+
from: number
|
|
18
|
+
to: number
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PaginationEntry, PaginationRange } from '../types'
|
|
2
|
+
|
|
3
|
+
export function paginationRanges(items: PaginationEntry[]): PaginationRange[] {
|
|
4
|
+
return items.flatMap((item, index) => {
|
|
5
|
+
if (item.type !== 'ellipsis') return []
|
|
6
|
+
const previous = items[index - 1]
|
|
7
|
+
const next = items[index + 1]
|
|
8
|
+
if (previous?.type !== 'page' || next?.type !== 'page' || next.value <= previous.value + 1)
|
|
9
|
+
return []
|
|
10
|
+
return [{ side: index === 1 ? 'prev' : 'next', from: previous.value + 1, to: next.value - 1 }]
|
|
11
|
+
})
|
|
12
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import PopoverContent from './PopoverContent.vue'
|
|
5
5
|
import { useAnchoredOverlay } from '../../lib/anchored-overlay'
|
|
6
6
|
import Card from '../card/Card.vue'
|
|
7
|
+
import { useOverlayPortal } from '../../lib/overlay-portal'
|
|
7
8
|
import { cn } from '../../lib/cn'
|
|
8
9
|
|
|
9
10
|
defineOptions({ name: 'HnPopover', inheritAttrs: false })
|
|
@@ -28,6 +29,8 @@
|
|
|
28
29
|
emit('openAutoFocus', event),
|
|
29
30
|
)
|
|
30
31
|
|
|
32
|
+
const { content, present } = useOverlayPortal(visible)
|
|
33
|
+
|
|
31
34
|
const pressOrigin = computed(() => {
|
|
32
35
|
if (props.side === 'left') return 'right'
|
|
33
36
|
if (props.side === 'right') return 'left'
|
|
@@ -41,13 +44,13 @@
|
|
|
41
44
|
<PopoverRoot v-model:open="visible" :modal="props.modal">
|
|
42
45
|
<PopoverTrigger
|
|
43
46
|
v-if="$slots.default"
|
|
44
|
-
ref="trigger"
|
|
47
|
+
:ref="trigger"
|
|
45
48
|
as-child
|
|
46
49
|
:style="{ transformOrigin: pressOrigin }"
|
|
47
50
|
>
|
|
48
51
|
<slot />
|
|
49
52
|
</PopoverTrigger>
|
|
50
|
-
<PopoverPortal>
|
|
53
|
+
<PopoverPortal v-if="present">
|
|
51
54
|
<PopoverContent
|
|
52
55
|
v-bind="{ ...$attrs, ...events }"
|
|
53
56
|
:reference="reference"
|
|
@@ -57,6 +60,7 @@
|
|
|
57
60
|
:side-offset="props.sideOffset"
|
|
58
61
|
>
|
|
59
62
|
<Card
|
|
63
|
+
ref="content"
|
|
60
64
|
:padded="props.padded"
|
|
61
65
|
:class="cn('hn-anim-pop z-(--hn-z-overlay) max-w-sm shadow-md outline-none', props.class)"
|
|
62
66
|
>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { useFieldControl } from '../form-field/context'
|
|
6
6
|
import { useUiLocale } from '../../locale'
|
|
7
7
|
import { useSliderChrome } from '../slider/composables/useSliderChrome'
|
|
8
|
+
import { useDirection } from '../../lib/useDirection'
|
|
8
9
|
import SliderHandle from '../slider/SliderHandle.vue'
|
|
9
10
|
import SliderMarks from '../slider/SliderMarks.vue'
|
|
10
11
|
import {
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
min?: number
|
|
23
24
|
max?: number
|
|
24
25
|
step?: number
|
|
26
|
+
dir?: 'ltr' | 'rtl'
|
|
25
27
|
minSteps?: number
|
|
26
28
|
marks?: Array<{ value: number; label?: string }>
|
|
27
29
|
label?: 'auto' | 'always' | 'none'
|
|
@@ -37,6 +39,7 @@
|
|
|
37
39
|
|
|
38
40
|
const model = defineModel<[number, number]>()
|
|
39
41
|
|
|
42
|
+
const { root, direction, rootDirection } = useDirection(() => props.dir)
|
|
40
43
|
const t = useUiLocale()
|
|
41
44
|
const { labelledBy, invalid, disabled, describedBy } = useFieldControl({
|
|
42
45
|
disabled: () => props.disabled,
|
|
@@ -66,6 +69,7 @@
|
|
|
66
69
|
|
|
67
70
|
<template>
|
|
68
71
|
<span
|
|
72
|
+
ref="root"
|
|
69
73
|
data-hn-range-slider
|
|
70
74
|
data-hn-state-group
|
|
71
75
|
role="group"
|
|
@@ -73,6 +77,7 @@
|
|
|
73
77
|
:aria-labelledby="labelledBy"
|
|
74
78
|
:aria-describedby="describedBy"
|
|
75
79
|
:aria-invalid="invalid || undefined"
|
|
80
|
+
:dir="rootDirection"
|
|
76
81
|
:data-disabled="disabled ? '' : undefined"
|
|
77
82
|
:data-dragging="dragging ? '' : undefined"
|
|
78
83
|
:style="position"
|
|
@@ -81,6 +86,7 @@
|
|
|
81
86
|
v-on="listeners"
|
|
82
87
|
>
|
|
83
88
|
<SliderRoot
|
|
89
|
+
:dir="direction"
|
|
84
90
|
:model-value="values"
|
|
85
91
|
:min="props.min"
|
|
86
92
|
:max="props.max"
|
|
@@ -13,10 +13,6 @@ const useBodyPointerLock = createSharedComposable(() => {
|
|
|
13
13
|
return locked
|
|
14
14
|
})
|
|
15
15
|
|
|
16
|
-
function block(event: Event) {
|
|
17
|
-
event.preventDefault()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
16
|
export function useLayerLock(viewport: ShallowRef<HTMLElement | undefined>) {
|
|
21
17
|
const locked = useBodyPointerLock()
|
|
22
18
|
let release: (() => void) | undefined
|
|
@@ -25,12 +21,15 @@ export function useLayerLock(viewport: ShallowRef<HTMLElement | undefined>) {
|
|
|
25
21
|
release?.()
|
|
26
22
|
release = undefined
|
|
27
23
|
const el = viewport.value
|
|
28
|
-
if (!el || !locked.value
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
if (!el || !locked.value) return
|
|
25
|
+
const block = (event: Event) => {
|
|
26
|
+
if (getComputedStyle(el).pointerEvents === 'none') event.preventDefault()
|
|
27
|
+
}
|
|
28
|
+
el.addEventListener('wheel', block, { passive: false, capture: true })
|
|
29
|
+
el.addEventListener('touchmove', block, { passive: false, capture: true })
|
|
31
30
|
release = () => {
|
|
32
|
-
el.removeEventListener('wheel', block)
|
|
33
|
-
el.removeEventListener('touchmove', block)
|
|
31
|
+
el.removeEventListener('wheel', block, true)
|
|
32
|
+
el.removeEventListener('touchmove', block, true)
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
|
|
@@ -5,7 +5,7 @@ export function useWheelRedirect(
|
|
|
5
5
|
enabled: () => boolean,
|
|
6
6
|
) {
|
|
7
7
|
function onWheel(event: WheelEvent) {
|
|
8
|
-
if (!enabled()) return
|
|
8
|
+
if (event.defaultPrevented || !enabled()) return
|
|
9
9
|
const el = viewport.value
|
|
10
10
|
if (!el) return
|
|
11
11
|
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) return
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
The skeleton shimmer styles and light/dark opacity values are adapted from PrimeVue / PrimeUIX
|
|
2
|
+
(https://github.com/primefaces/primeuix), using Hina design tokens.
|
|
3
|
+
|
|
4
|
+
MIT License
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2025 PrimeTek
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { useFieldControl } from '../form-field/context'
|
|
6
6
|
import { useUiLocale } from '../../locale'
|
|
7
7
|
import { useSliderChrome } from './composables/useSliderChrome'
|
|
8
|
+
import { useDirection } from '../../lib/useDirection'
|
|
8
9
|
import SliderHandle from './SliderHandle.vue'
|
|
9
10
|
import SliderMarks from './SliderMarks.vue'
|
|
10
11
|
import {
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
min?: number
|
|
23
24
|
max?: number
|
|
24
25
|
step?: number
|
|
26
|
+
dir?: 'ltr' | 'rtl'
|
|
25
27
|
marks?: Array<{ value: number; label?: string }>
|
|
26
28
|
label?: 'auto' | 'always' | 'none'
|
|
27
29
|
format?: (value: number) => string
|
|
@@ -36,6 +38,7 @@
|
|
|
36
38
|
|
|
37
39
|
const model = defineModel<number>()
|
|
38
40
|
|
|
41
|
+
const { root, direction, rootDirection } = useDirection(() => props.dir)
|
|
39
42
|
const t = useUiLocale()
|
|
40
43
|
const { labelledBy, invalid, disabled, describedBy } = useFieldControl({
|
|
41
44
|
disabled: () => props.disabled,
|
|
@@ -60,8 +63,10 @@
|
|
|
60
63
|
|
|
61
64
|
<template>
|
|
62
65
|
<span
|
|
66
|
+
ref="root"
|
|
63
67
|
data-hn-slider
|
|
64
68
|
data-hn-state-group
|
|
69
|
+
:dir="rootDirection"
|
|
65
70
|
:data-disabled="disabled ? '' : undefined"
|
|
66
71
|
:data-dragging="dragging ? '' : undefined"
|
|
67
72
|
:style="position"
|
|
@@ -70,6 +75,7 @@
|
|
|
70
75
|
v-on="listeners"
|
|
71
76
|
>
|
|
72
77
|
<SliderRoot
|
|
78
|
+
:dir="direction"
|
|
73
79
|
:model-value="values"
|
|
74
80
|
:min="props.min"
|
|
75
81
|
:max="props.max"
|
|
@@ -18,6 +18,7 @@ export const sliderRoot = tv({
|
|
|
18
18
|
base: [
|
|
19
19
|
'bg-line-strong relative flex h-[var(--hn-slider-h)] w-full cursor-pointer touch-none rounded-full select-none',
|
|
20
20
|
'data-disabled:pointer-events-none',
|
|
21
|
+
'[&[dir=rtl]>[role=slider]]:[--reka-slider-thumb-transform:translateX(50%)]',
|
|
21
22
|
],
|
|
22
23
|
})
|
|
23
24
|
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { TooltipRoot, TooltipTrigger
|
|
3
|
-
import
|
|
2
|
+
import { TooltipRoot, TooltipTrigger } from 'reka-ui'
|
|
3
|
+
import TooltipBubble from './TooltipBubble.vue'
|
|
4
|
+
import type { TooltipProps } from './types'
|
|
4
5
|
|
|
5
6
|
defineOptions({ name: 'HnTooltip' })
|
|
6
7
|
|
|
7
|
-
const props = withDefaults(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
align?: 'start' | 'center' | 'end'
|
|
12
|
-
sideOffset?: number
|
|
13
|
-
open?: boolean
|
|
14
|
-
disabled?: boolean
|
|
15
|
-
class?: string
|
|
16
|
-
}>(),
|
|
17
|
-
{ side: 'top', align: 'center', sideOffset: 8, open: undefined, disabled: false },
|
|
18
|
-
)
|
|
8
|
+
const props = withDefaults(defineProps<TooltipProps>(), {
|
|
9
|
+
open: undefined,
|
|
10
|
+
disabled: false,
|
|
11
|
+
})
|
|
19
12
|
</script>
|
|
20
13
|
|
|
21
14
|
<template>
|
|
@@ -23,24 +16,8 @@
|
|
|
23
16
|
<TooltipTrigger as-child>
|
|
24
17
|
<slot />
|
|
25
18
|
</TooltipTrigger>
|
|
26
|
-
<
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
:align="props.align"
|
|
30
|
-
:side-offset="props.sideOffset"
|
|
31
|
-
:aria-label="props.content"
|
|
32
|
-
:update-position-strategy="props.open === undefined ? 'optimized' : 'always'"
|
|
33
|
-
:class="
|
|
34
|
-
cn(
|
|
35
|
-
'hn-anim-pop [--hn-pop-in:var(--hn-duration-fast)] bg-neutral-solid text-neutral-solid-on z-(--hn-z-overlay)',
|
|
36
|
-
'max-w-xs rounded-md px-2.5 py-1 text-xs shadow-md',
|
|
37
|
-
props.class,
|
|
38
|
-
)
|
|
39
|
-
"
|
|
40
|
-
>
|
|
41
|
-
<slot name="content">{{ props.content }}</slot>
|
|
42
|
-
<TooltipArrow :width="10" :height="5" class="fill-neutral-solid" />
|
|
43
|
-
</TooltipContent>
|
|
44
|
-
</TooltipPortal>
|
|
19
|
+
<TooltipBubble v-bind="props">
|
|
20
|
+
<slot name="content">{{ props.content }}</slot>
|
|
21
|
+
</TooltipBubble>
|
|
45
22
|
</TooltipRoot>
|
|
46
23
|
</template>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { TooltipPortal, TooltipContent, TooltipArrow } from 'reka-ui'
|
|
3
|
+
import { cn } from '../../lib/cn'
|
|
4
|
+
import type { TooltipProps } from './types'
|
|
5
|
+
|
|
6
|
+
defineOptions({ name: 'HnTooltipBubble' })
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(defineProps<TooltipProps>(), {
|
|
9
|
+
side: 'top',
|
|
10
|
+
align: 'center',
|
|
11
|
+
sideOffset: 8,
|
|
12
|
+
open: undefined,
|
|
13
|
+
})
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<TooltipPortal>
|
|
18
|
+
<TooltipContent
|
|
19
|
+
:side="props.side"
|
|
20
|
+
:align="props.align"
|
|
21
|
+
:side-offset="props.sideOffset"
|
|
22
|
+
:aria-label="props.content"
|
|
23
|
+
:update-position-strategy="props.open === undefined ? 'optimized' : 'always'"
|
|
24
|
+
:class="
|
|
25
|
+
cn(
|
|
26
|
+
'hn-anim-pop [--hn-pop-in:var(--hn-duration-fast)] bg-neutral-solid text-neutral-solid-on z-(--hn-z-overlay)',
|
|
27
|
+
'max-w-xs rounded-md px-2.5 py-1 text-xs shadow-md',
|
|
28
|
+
props.class,
|
|
29
|
+
)
|
|
30
|
+
"
|
|
31
|
+
>
|
|
32
|
+
<slot>{{ props.content }}</slot>
|
|
33
|
+
<TooltipArrow :width="10" :height="5" class="fill-neutral-solid" />
|
|
34
|
+
</TooltipContent>
|
|
35
|
+
</TooltipPortal>
|
|
36
|
+
</template>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { computed, defineComponent, h, type PropType } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
TooltipTrigger,
|
|
4
|
+
TooltipRoot,
|
|
5
|
+
injectTooltipProviderContext,
|
|
6
|
+
injectTooltipRootContext,
|
|
7
|
+
} from 'reka-ui'
|
|
8
|
+
import TooltipBubble from './TooltipBubble.vue'
|
|
9
|
+
import { useTooltipTarget } from './composables/useTooltipTarget'
|
|
10
|
+
import type { TooltipDirectiveOptions } from './types'
|
|
11
|
+
|
|
12
|
+
const TooltipTarget = defineComponent({
|
|
13
|
+
name: 'HnTooltipTarget',
|
|
14
|
+
props: {
|
|
15
|
+
target: { type: Object as PropType<HTMLElement>, required: true },
|
|
16
|
+
options: { type: Object as PropType<TooltipDirectiveOptions>, required: true },
|
|
17
|
+
revision: { type: Number, required: true },
|
|
18
|
+
},
|
|
19
|
+
setup(props) {
|
|
20
|
+
const root = injectTooltipRootContext()
|
|
21
|
+
const ExternalTarget = defineComponent({
|
|
22
|
+
name: 'HnTooltipElement',
|
|
23
|
+
inheritAttrs: false,
|
|
24
|
+
setup(_, { attrs }) {
|
|
25
|
+
useTooltipTarget(props.target, attrs, () => props.revision)
|
|
26
|
+
return () => null
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
return () => [
|
|
30
|
+
h(TooltipTrigger, {
|
|
31
|
+
key: root.disabled.value ? 'disabled' : 'enabled',
|
|
32
|
+
reference: props.target,
|
|
33
|
+
as: ExternalTarget,
|
|
34
|
+
}),
|
|
35
|
+
h(TooltipBubble, props.options),
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
export default defineComponent({
|
|
41
|
+
name: 'HnTooltipDirectiveRoot',
|
|
42
|
+
props: {
|
|
43
|
+
target: { type: Object as PropType<HTMLElement>, required: true },
|
|
44
|
+
options: { type: Object as PropType<TooltipDirectiveOptions>, required: true },
|
|
45
|
+
revision: { type: Number, required: true },
|
|
46
|
+
},
|
|
47
|
+
setup(props) {
|
|
48
|
+
const provider = injectTooltipProviderContext(null)
|
|
49
|
+
const disabled = computed(() => !!props.options.disabled || !props.options.content.trim())
|
|
50
|
+
return () =>
|
|
51
|
+
provider
|
|
52
|
+
? h(
|
|
53
|
+
TooltipRoot,
|
|
54
|
+
{ disabled: disabled.value, ignoreNonKeyboardFocus: true },
|
|
55
|
+
{
|
|
56
|
+
default: () => h(TooltipTarget, props),
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
: null
|
|
60
|
+
},
|
|
61
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { onScopeDispose, watch, watchPostEffect } from 'vue'
|
|
2
|
+
import { useEventListener } from '@vueuse/core'
|
|
3
|
+
import { injectTooltipProviderContext, injectTooltipRootContext } from 'reka-ui'
|
|
4
|
+
|
|
5
|
+
type Handler = (event: Event) => void
|
|
6
|
+
|
|
7
|
+
export function useTooltipTarget(
|
|
8
|
+
target: HTMLElement,
|
|
9
|
+
attrs: Record<string, unknown>,
|
|
10
|
+
revision: () => number,
|
|
11
|
+
) {
|
|
12
|
+
const root = injectTooltipRootContext()
|
|
13
|
+
const provider = injectTooltipProviderContext()
|
|
14
|
+
|
|
15
|
+
watchPostEffect(() => {
|
|
16
|
+
if (root.trigger.value !== target) root.onTriggerChange(target)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const contentId = root.contentId
|
|
20
|
+
const graceAttribute = 'data-grace-area-trigger'
|
|
21
|
+
const previousGrace = target.getAttribute(graceAttribute)
|
|
22
|
+
target.setAttribute(graceAttribute, '')
|
|
23
|
+
|
|
24
|
+
function describe(open: boolean) {
|
|
25
|
+
const ids = new Set(
|
|
26
|
+
(target.getAttribute('aria-describedby') ?? '').split(/\s+/).filter(Boolean),
|
|
27
|
+
)
|
|
28
|
+
ids.delete(contentId)
|
|
29
|
+
if (open) ids.add(contentId)
|
|
30
|
+
if (ids.size) target.setAttribute('aria-describedby', [...ids].join(' '))
|
|
31
|
+
else target.removeAttribute('aria-describedby')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const event of ['pointermove', 'pointerleave', 'pointerdown', 'focus', 'blur', 'click']) {
|
|
35
|
+
const prop = 'on' + event[0]!.toUpperCase() + event.slice(1)
|
|
36
|
+
useEventListener(target, event, (event: Event) => {
|
|
37
|
+
const handlers = attrs[prop] as Handler | Handler[] | undefined
|
|
38
|
+
for (const handler of Array.isArray(handlers) ? handlers : handlers ? [handlers] : []) {
|
|
39
|
+
handler(event)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
watch([root.open, revision], ([open]) => describe(open), { immediate: true, flush: 'post' })
|
|
45
|
+
watch(root.disabled, disabled => {
|
|
46
|
+
if (disabled) root.onClose()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
onScopeDispose(() => {
|
|
50
|
+
if (root.open.value) provider.onClose()
|
|
51
|
+
root.onClose()
|
|
52
|
+
describe(false)
|
|
53
|
+
if (previousGrace === null) target.removeAttribute(graceAttribute)
|
|
54
|
+
else target.setAttribute(graceAttribute, previousGrace)
|
|
55
|
+
})
|
|
56
|
+
}
|