@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.
Files changed (223) hide show
  1. package/dist/index.js +4326 -2130
  2. package/dist/index.js.map +1 -1
  3. package/dist/types/components/alert-dialog/AlertDialog.vue.d.ts +2 -2
  4. package/dist/types/components/alert-dialog/AlertDialog.vue.d.ts.map +1 -1
  5. package/dist/types/components/dialog/Dialog.vue.d.ts.map +1 -1
  6. package/dist/types/components/dialog/ModalContent.vue.d.ts +21 -0
  7. package/dist/types/components/dialog/ModalContent.vue.d.ts.map +1 -0
  8. package/dist/types/components/dropdown-menu/DropdownMenu.vue.d.ts.map +1 -1
  9. package/dist/types/components/file-upload/FileUpload.vue.d.ts +1 -1
  10. package/dist/types/components/file-upload/FileUpload.vue.d.ts.map +1 -1
  11. package/dist/types/components/hover-card/HoverCard.vue.d.ts +7 -7
  12. package/dist/types/components/hover-card/HoverCard.vue.d.ts.map +1 -1
  13. package/dist/types/components/hover-card/HoverCardAnchor.vue.d.ts +27 -0
  14. package/dist/types/components/hover-card/HoverCardAnchor.vue.d.ts.map +1 -0
  15. package/dist/types/components/hover-card/composables/useHoverCardAnchor.d.ts +17 -0
  16. package/dist/types/components/hover-card/composables/useHoverCardAnchor.d.ts.map +1 -0
  17. package/dist/types/components/image/Image.vue.d.ts +8 -3
  18. package/dist/types/components/image/Image.vue.d.ts.map +1 -1
  19. package/dist/types/components/image/composables/useImage.d.ts.map +1 -1
  20. package/dist/types/components/image/composables/useImageGroupState.d.ts.map +1 -1
  21. package/dist/types/components/image/image.variants.d.ts +23 -1
  22. package/dist/types/components/image/image.variants.d.ts.map +1 -1
  23. package/dist/types/components/lightbox/Lightbox.vue.d.ts.map +1 -1
  24. package/dist/types/components/lightbox/LightboxChrome.vue.d.ts +4 -0
  25. package/dist/types/components/lightbox/LightboxChrome.vue.d.ts.map +1 -1
  26. package/dist/types/components/lightbox/LightboxToolbar.vue.d.ts +4 -0
  27. package/dist/types/components/lightbox/LightboxToolbar.vue.d.ts.map +1 -1
  28. package/dist/types/components/lightbox/composables/useLightboxFrames.d.ts +4 -1
  29. package/dist/types/components/lightbox/composables/useLightboxFrames.d.ts.map +1 -1
  30. package/dist/types/components/lightbox/composables/useLightboxLarge.d.ts +1 -0
  31. package/dist/types/components/lightbox/composables/useLightboxLarge.d.ts.map +1 -1
  32. package/dist/types/components/lightbox/composables/useLightboxLayout.d.ts +17 -0
  33. package/dist/types/components/lightbox/composables/useLightboxLayout.d.ts.map +1 -0
  34. package/dist/types/components/lightbox/composables/useLightboxMotion.d.ts.map +1 -1
  35. package/dist/types/components/lightbox/composables/useLightboxPhase.d.ts +1 -1
  36. package/dist/types/components/lightbox/composables/useLightboxPhase.d.ts.map +1 -1
  37. package/dist/types/components/lightbox/composables/useLightboxPose.d.ts.map +1 -1
  38. package/dist/types/components/lightbox/composables/useLightboxRotation.d.ts +5 -1
  39. package/dist/types/components/lightbox/composables/useLightboxRotation.d.ts.map +1 -1
  40. package/dist/types/components/lightbox/composables/useLightboxZoom.d.ts +10 -1
  41. package/dist/types/components/lightbox/composables/useLightboxZoom.d.ts.map +1 -1
  42. package/dist/types/components/lightbox/lightbox.variants.d.ts +2 -0
  43. package/dist/types/components/lightbox/lightbox.variants.d.ts.map +1 -0
  44. package/dist/types/components/lightbox/types.d.ts +7 -1
  45. package/dist/types/components/lightbox/types.d.ts.map +1 -1
  46. package/dist/types/components/lightbox/utils/clip.d.ts +16 -0
  47. package/dist/types/components/lightbox/utils/clip.d.ts.map +1 -0
  48. package/dist/types/components/lightbox/utils/pose.d.ts +4 -4
  49. package/dist/types/components/lightbox/utils/pose.d.ts.map +1 -1
  50. package/dist/types/components/lightbox/utils/source.d.ts +4 -0
  51. package/dist/types/components/lightbox/utils/source.d.ts.map +1 -0
  52. package/dist/types/components/lightbox/utils/zoom.d.ts +8 -4
  53. package/dist/types/components/lightbox/utils/zoom.d.ts.map +1 -1
  54. package/dist/types/components/pagination/Pagination.vue.d.ts +59 -0
  55. package/dist/types/components/pagination/Pagination.vue.d.ts.map +1 -0
  56. package/dist/types/components/pagination/PaginationContent.vue.d.ts +23 -0
  57. package/dist/types/components/pagination/PaginationContent.vue.d.ts.map +1 -0
  58. package/dist/types/components/pagination/PaginationControl.vue.d.ts +10 -0
  59. package/dist/types/components/pagination/PaginationControl.vue.d.ts.map +1 -0
  60. package/dist/types/components/pagination/PaginationEllipsis.vue.d.ts +23 -0
  61. package/dist/types/components/pagination/PaginationEllipsis.vue.d.ts.map +1 -0
  62. package/dist/types/components/pagination/PaginationInfo.vue.d.ts +24 -0
  63. package/dist/types/components/pagination/PaginationInfo.vue.d.ts.map +1 -0
  64. package/dist/types/components/pagination/PaginationJump.vue.d.ts +7 -0
  65. package/dist/types/components/pagination/PaginationJump.vue.d.ts.map +1 -0
  66. package/dist/types/components/pagination/PaginationPage.vue.d.ts +20 -0
  67. package/dist/types/components/pagination/PaginationPage.vue.d.ts.map +1 -0
  68. package/dist/types/components/pagination/PaginationPages.vue.d.ts +27 -0
  69. package/dist/types/components/pagination/PaginationPages.vue.d.ts.map +1 -0
  70. package/dist/types/components/pagination/PaginationPopup.vue.d.ts +8 -0
  71. package/dist/types/components/pagination/PaginationPopup.vue.d.ts.map +1 -0
  72. package/dist/types/components/pagination/PaginationSize.vue.d.ts +7 -0
  73. package/dist/types/components/pagination/PaginationSize.vue.d.ts.map +1 -0
  74. package/dist/types/components/pagination/composables/usePagination.d.ts +25 -0
  75. package/dist/types/components/pagination/composables/usePagination.d.ts.map +1 -0
  76. package/dist/types/components/pagination/composables/usePaginationEllipsis.d.ts +34 -0
  77. package/dist/types/components/pagination/composables/usePaginationEllipsis.d.ts.map +1 -0
  78. package/dist/types/components/pagination/composables/usePaginationJump.d.ts +8 -0
  79. package/dist/types/components/pagination/composables/usePaginationJump.d.ts.map +1 -0
  80. package/dist/types/components/pagination/composables/usePaginationPageTooltip.d.ts +3 -0
  81. package/dist/types/components/pagination/composables/usePaginationPageTooltip.d.ts.map +1 -0
  82. package/dist/types/components/pagination/composables/usePaginationWindow.d.ts +188 -0
  83. package/dist/types/components/pagination/composables/usePaginationWindow.d.ts.map +1 -0
  84. package/dist/types/components/pagination/context.d.ts +19 -0
  85. package/dist/types/components/pagination/context.d.ts.map +1 -0
  86. package/dist/types/components/pagination/pagination.variants.d.ts +68 -0
  87. package/dist/types/components/pagination/pagination.variants.d.ts.map +1 -0
  88. package/dist/types/components/pagination/types.d.ts +23 -0
  89. package/dist/types/components/pagination/types.d.ts.map +1 -0
  90. package/dist/types/components/pagination/utils/ranges.d.ts +3 -0
  91. package/dist/types/components/pagination/utils/ranges.d.ts.map +1 -0
  92. package/dist/types/components/popover/Popover.vue.d.ts.map +1 -1
  93. package/dist/types/components/range-slider/RangeSlider.vue.d.ts +1 -0
  94. package/dist/types/components/range-slider/RangeSlider.vue.d.ts.map +1 -1
  95. package/dist/types/components/scroll-area/composables/useLayerLock.d.ts.map +1 -1
  96. package/dist/types/components/slider/Slider.vue.d.ts +1 -0
  97. package/dist/types/components/slider/Slider.vue.d.ts.map +1 -1
  98. package/dist/types/components/slider/slider.variants.d.ts.map +1 -1
  99. package/dist/types/components/tooltip/Tooltip.vue.d.ts +4 -15
  100. package/dist/types/components/tooltip/Tooltip.vue.d.ts.map +1 -1
  101. package/dist/types/components/tooltip/TooltipBubble.vue.d.ts +20 -0
  102. package/dist/types/components/tooltip/TooltipBubble.vue.d.ts.map +1 -0
  103. package/dist/types/components/tooltip/TooltipDirectiveRoot.d.ts +33 -0
  104. package/dist/types/components/tooltip/TooltipDirectiveRoot.d.ts.map +1 -0
  105. package/dist/types/components/tooltip/composables/useTooltipTarget.d.ts +2 -0
  106. package/dist/types/components/tooltip/composables/useTooltipTarget.d.ts.map +1 -0
  107. package/dist/types/components/tooltip/directive.d.ts +4 -0
  108. package/dist/types/components/tooltip/directive.d.ts.map +1 -0
  109. package/dist/types/components/tooltip/types.d.ts +14 -0
  110. package/dist/types/components/tooltip/types.d.ts.map +1 -0
  111. package/dist/types/components/tooltip/utils/directiveContext.d.ts +10 -0
  112. package/dist/types/components/tooltip/utils/directiveContext.d.ts.map +1 -0
  113. package/dist/types/components/tree-select/TreeSelect.vue.d.ts +6 -1
  114. package/dist/types/components/tree-select/TreeSelect.vue.d.ts.map +1 -1
  115. package/dist/types/components/tree-select/TreeSelectSearch.vue.d.ts +22 -0
  116. package/dist/types/components/tree-select/TreeSelectSearch.vue.d.ts.map +1 -0
  117. package/dist/types/components/tree-select/composables/useTreeSelect.d.ts +35 -0
  118. package/dist/types/components/tree-select/composables/useTreeSelect.d.ts.map +1 -0
  119. package/dist/types/components/tree-select/tree-select.variants.d.ts +1 -1
  120. package/dist/types/components/tree-select/tree-select.variants.d.ts.map +1 -1
  121. package/dist/types/components/tree-select/utils/filterTree.d.ts +7 -0
  122. package/dist/types/components/tree-select/utils/filterTree.d.ts.map +1 -0
  123. package/dist/types/index.d.ts +10 -0
  124. package/dist/types/index.d.ts.map +1 -1
  125. package/dist/types/lib/anchored-overlay.d.ts +3 -7
  126. package/dist/types/lib/anchored-overlay.d.ts.map +1 -1
  127. package/dist/types/lib/dialog-focus.d.ts +2 -0
  128. package/dist/types/lib/dialog-focus.d.ts.map +1 -0
  129. package/dist/types/lib/overlay-portal.d.ts +6 -0
  130. package/dist/types/lib/overlay-portal.d.ts.map +1 -0
  131. package/dist/types/lib/overlay-positioner.d.ts +3 -0
  132. package/dist/types/lib/overlay-positioner.d.ts.map +1 -0
  133. package/dist/types/lib/pointer-corridor.d.ts +7 -0
  134. package/dist/types/lib/pointer-corridor.d.ts.map +1 -0
  135. package/dist/types/lib/useDirection.d.ts +7 -0
  136. package/dist/types/lib/useDirection.d.ts.map +1 -0
  137. package/dist/types/locale/en-US.d.ts.map +1 -1
  138. package/dist/types/locale/index.d.ts.map +1 -1
  139. package/dist/types/locale/types.d.ts +13 -0
  140. package/dist/types/locale/types.d.ts.map +1 -1
  141. package/dist/types/locale/zh-CN.d.ts.map +1 -1
  142. package/package.json +1 -1
  143. package/src/components/alert-dialog/AlertDialog.vue +7 -4
  144. package/src/components/badge/badge.variants.ts +1 -1
  145. package/src/components/dialog/Dialog.vue +7 -4
  146. package/src/components/dialog/ModalContent.vue +19 -0
  147. package/src/components/dropdown-menu/DropdownMenu.vue +1 -1
  148. package/src/components/hover-card/HoverCard.vue +34 -16
  149. package/src/components/hover-card/HoverCardAnchor.vue +16 -0
  150. package/src/components/hover-card/composables/useHoverCardAnchor.ts +160 -0
  151. package/src/components/image/Image.vue +11 -12
  152. package/src/components/image/composables/useImage.ts +6 -3
  153. package/src/components/image/composables/useImageGroupState.ts +9 -1
  154. package/src/components/image/image.variants.ts +13 -1
  155. package/src/components/lightbox/Lightbox.vue +23 -17
  156. package/src/components/lightbox/LightboxChrome.vue +7 -1
  157. package/src/components/lightbox/LightboxToolbar.vue +16 -2
  158. package/src/components/lightbox/composables/useLightboxFrames.ts +108 -12
  159. package/src/components/lightbox/composables/useLightboxLarge.ts +4 -1
  160. package/src/components/lightbox/composables/useLightboxLayout.ts +44 -0
  161. package/src/components/lightbox/composables/useLightboxMotion.ts +3 -1
  162. package/src/components/lightbox/composables/useLightboxPhase.ts +12 -4
  163. package/src/components/lightbox/composables/useLightboxPose.ts +20 -19
  164. package/src/components/lightbox/composables/useLightboxRotation.ts +14 -4
  165. package/src/components/lightbox/composables/useLightboxZoom.ts +51 -9
  166. package/src/components/lightbox/lightbox.variants.ts +5 -0
  167. package/src/components/lightbox/types.ts +5 -1
  168. package/src/components/lightbox/utils/clip.ts +117 -0
  169. package/src/components/lightbox/utils/pose.ts +36 -19
  170. package/src/components/lightbox/utils/source.ts +17 -0
  171. package/src/components/lightbox/utils/zoom.ts +21 -7
  172. package/src/components/pagination/Pagination.vue +118 -0
  173. package/src/components/pagination/PaginationContent.vue +29 -0
  174. package/src/components/pagination/PaginationControl.vue +42 -0
  175. package/src/components/pagination/PaginationEllipsis.vue +67 -0
  176. package/src/components/pagination/PaginationInfo.vue +18 -0
  177. package/src/components/pagination/PaginationJump.vue +32 -0
  178. package/src/components/pagination/PaginationPage.vue +34 -0
  179. package/src/components/pagination/PaginationPages.vue +43 -0
  180. package/src/components/pagination/PaginationPopup.vue +92 -0
  181. package/src/components/pagination/PaginationSize.vue +30 -0
  182. package/src/components/pagination/composables/usePagination.ts +76 -0
  183. package/src/components/pagination/composables/usePaginationEllipsis.ts +269 -0
  184. package/src/components/pagination/composables/usePaginationJump.ts +23 -0
  185. package/src/components/pagination/composables/usePaginationPageTooltip.ts +23 -0
  186. package/src/components/pagination/composables/usePaginationWindow.ts +116 -0
  187. package/src/components/pagination/context.ts +23 -0
  188. package/src/components/pagination/pagination.variants.ts +32 -0
  189. package/src/components/pagination/types.ts +19 -0
  190. package/src/components/pagination/utils/ranges.ts +12 -0
  191. package/src/components/popover/Popover.vue +6 -2
  192. package/src/components/range-slider/RangeSlider.vue +6 -0
  193. package/src/components/scroll-area/composables/useLayerLock.ts +8 -9
  194. package/src/components/scroll-area/composables/useWheelRedirect.ts +1 -1
  195. package/src/components/skeleton/LICENSE +24 -0
  196. package/src/components/slider/Slider.vue +6 -0
  197. package/src/components/slider/slider.variants.ts +1 -0
  198. package/src/components/tooltip/Tooltip.vue +10 -33
  199. package/src/components/tooltip/TooltipBubble.vue +36 -0
  200. package/src/components/tooltip/TooltipDirectiveRoot.ts +61 -0
  201. package/src/components/tooltip/composables/useTooltipTarget.ts +56 -0
  202. package/src/components/tooltip/directive.ts +54 -0
  203. package/src/components/tooltip/types.ts +15 -0
  204. package/src/components/tooltip/utils/directiveContext.ts +32 -0
  205. package/src/components/tree-select/TreeSelect.vue +90 -61
  206. package/src/components/tree-select/TreeSelectSearch.vue +48 -0
  207. package/src/components/tree-select/composables/useTreeSelect.ts +130 -0
  208. package/src/components/tree-select/tree-select.variants.ts +2 -2
  209. package/src/components/tree-select/utils/filterTree.ts +19 -0
  210. package/src/index.ts +10 -0
  211. package/src/lib/anchored-overlay.ts +2 -5
  212. package/src/lib/dialog-focus.ts +13 -0
  213. package/src/lib/overlay-portal.ts +8 -0
  214. package/src/lib/overlay-positioner.ts +25 -0
  215. package/src/lib/pointer-corridor.ts +26 -0
  216. package/src/lib/useDirection.ts +31 -0
  217. package/src/locale/en-US.ts +15 -1
  218. package/src/locale/index.ts +1 -0
  219. package/src/locale/types.ts +13 -0
  220. package/src/locale/zh-CN.ts +15 -1
  221. package/src/styles/interaction.css +26 -3
  222. package/src/styles/motion.css +14 -2
  223. 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 || getComputedStyle(el).pointerEvents !== 'none') return
29
- el.addEventListener('wheel', block, { passive: false })
30
- el.addEventListener('touchmove', block, { passive: false })
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, TooltipPortal, TooltipContent, TooltipArrow } from 'reka-ui'
3
- import { cn } from '../../lib/cn'
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
- defineProps<{
9
- content?: string
10
- side?: 'top' | 'right' | 'bottom' | 'left'
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
- <TooltipPortal>
27
- <TooltipContent
28
- :side="props.side"
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
+ }