@hina-ui/vue 1.3.1 → 1.5.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 (215) hide show
  1. package/dist/index.js +4336 -2180
  2. package/dist/index.js.map +1 -1
  3. package/dist/types/components/dialog/Dialog.vue.d.ts +3 -1
  4. package/dist/types/components/dialog/Dialog.vue.d.ts.map +1 -1
  5. package/dist/types/components/drawer/Drawer.vue.d.ts +21 -14
  6. package/dist/types/components/drawer/Drawer.vue.d.ts.map +1 -1
  7. package/dist/types/components/drawer/drawer.variants.d.ts +10 -1
  8. package/dist/types/components/drawer/drawer.variants.d.ts.map +1 -1
  9. package/dist/types/components/dropdown-menu/DropdownMenu.vue.d.ts +4 -1
  10. package/dist/types/components/dropdown-menu/DropdownMenu.vue.d.ts.map +1 -1
  11. package/dist/types/components/hover-card/HoverCard.vue.d.ts +10 -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 +29 -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 +22 -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 +3 -1
  18. package/dist/types/components/image/Image.vue.d.ts.map +1 -1
  19. package/dist/types/components/image/composables/useImageGroupState.d.ts.map +1 -1
  20. package/dist/types/components/image/image.variants.d.ts +1 -1
  21. package/dist/types/components/lightbox/Lightbox.vue.d.ts.map +1 -1
  22. package/dist/types/components/lightbox/LightboxChrome.vue.d.ts +4 -0
  23. package/dist/types/components/lightbox/LightboxChrome.vue.d.ts.map +1 -1
  24. package/dist/types/components/lightbox/LightboxToolbar.vue.d.ts +4 -0
  25. package/dist/types/components/lightbox/LightboxToolbar.vue.d.ts.map +1 -1
  26. package/dist/types/components/lightbox/composables/useLightboxFrames.d.ts +4 -1
  27. package/dist/types/components/lightbox/composables/useLightboxFrames.d.ts.map +1 -1
  28. package/dist/types/components/lightbox/composables/useLightboxLarge.d.ts +1 -0
  29. package/dist/types/components/lightbox/composables/useLightboxLarge.d.ts.map +1 -1
  30. package/dist/types/components/lightbox/composables/useLightboxLayout.d.ts +17 -0
  31. package/dist/types/components/lightbox/composables/useLightboxLayout.d.ts.map +1 -0
  32. package/dist/types/components/lightbox/composables/useLightboxPhase.d.ts +1 -1
  33. package/dist/types/components/lightbox/composables/useLightboxPhase.d.ts.map +1 -1
  34. package/dist/types/components/lightbox/composables/useLightboxPose.d.ts.map +1 -1
  35. package/dist/types/components/lightbox/composables/useLightboxRotation.d.ts +5 -1
  36. package/dist/types/components/lightbox/composables/useLightboxRotation.d.ts.map +1 -1
  37. package/dist/types/components/lightbox/composables/useLightboxZoom.d.ts +10 -1
  38. package/dist/types/components/lightbox/composables/useLightboxZoom.d.ts.map +1 -1
  39. package/dist/types/components/lightbox/lightbox.variants.d.ts +2 -0
  40. package/dist/types/components/lightbox/lightbox.variants.d.ts.map +1 -0
  41. package/dist/types/components/lightbox/types.d.ts +7 -1
  42. package/dist/types/components/lightbox/types.d.ts.map +1 -1
  43. package/dist/types/components/lightbox/utils/pose.d.ts +2 -2
  44. package/dist/types/components/lightbox/utils/pose.d.ts.map +1 -1
  45. package/dist/types/components/lightbox/utils/source.d.ts +4 -0
  46. package/dist/types/components/lightbox/utils/source.d.ts.map +1 -0
  47. package/dist/types/components/lightbox/utils/zoom.d.ts +8 -4
  48. package/dist/types/components/lightbox/utils/zoom.d.ts.map +1 -1
  49. package/dist/types/components/pagination/Pagination.vue.d.ts +59 -0
  50. package/dist/types/components/pagination/Pagination.vue.d.ts.map +1 -0
  51. package/dist/types/components/pagination/PaginationContent.vue.d.ts +23 -0
  52. package/dist/types/components/pagination/PaginationContent.vue.d.ts.map +1 -0
  53. package/dist/types/components/pagination/PaginationControl.vue.d.ts +10 -0
  54. package/dist/types/components/pagination/PaginationControl.vue.d.ts.map +1 -0
  55. package/dist/types/components/pagination/PaginationEllipsis.vue.d.ts +23 -0
  56. package/dist/types/components/pagination/PaginationEllipsis.vue.d.ts.map +1 -0
  57. package/dist/types/components/pagination/PaginationInfo.vue.d.ts +24 -0
  58. package/dist/types/components/pagination/PaginationInfo.vue.d.ts.map +1 -0
  59. package/dist/types/components/pagination/PaginationJump.vue.d.ts +7 -0
  60. package/dist/types/components/pagination/PaginationJump.vue.d.ts.map +1 -0
  61. package/dist/types/components/pagination/PaginationPage.vue.d.ts +20 -0
  62. package/dist/types/components/pagination/PaginationPage.vue.d.ts.map +1 -0
  63. package/dist/types/components/pagination/PaginationPages.vue.d.ts +27 -0
  64. package/dist/types/components/pagination/PaginationPages.vue.d.ts.map +1 -0
  65. package/dist/types/components/pagination/PaginationPopup.vue.d.ts +8 -0
  66. package/dist/types/components/pagination/PaginationPopup.vue.d.ts.map +1 -0
  67. package/dist/types/components/pagination/PaginationSize.vue.d.ts +7 -0
  68. package/dist/types/components/pagination/PaginationSize.vue.d.ts.map +1 -0
  69. package/dist/types/components/pagination/composables/usePagination.d.ts +25 -0
  70. package/dist/types/components/pagination/composables/usePagination.d.ts.map +1 -0
  71. package/dist/types/components/pagination/composables/usePaginationEllipsis.d.ts +34 -0
  72. package/dist/types/components/pagination/composables/usePaginationEllipsis.d.ts.map +1 -0
  73. package/dist/types/components/pagination/composables/usePaginationJump.d.ts +8 -0
  74. package/dist/types/components/pagination/composables/usePaginationJump.d.ts.map +1 -0
  75. package/dist/types/components/pagination/composables/usePaginationPageTooltip.d.ts +3 -0
  76. package/dist/types/components/pagination/composables/usePaginationPageTooltip.d.ts.map +1 -0
  77. package/dist/types/components/pagination/composables/usePaginationWindow.d.ts +188 -0
  78. package/dist/types/components/pagination/composables/usePaginationWindow.d.ts.map +1 -0
  79. package/dist/types/components/pagination/context.d.ts +19 -0
  80. package/dist/types/components/pagination/context.d.ts.map +1 -0
  81. package/dist/types/components/pagination/pagination.variants.d.ts +68 -0
  82. package/dist/types/components/pagination/pagination.variants.d.ts.map +1 -0
  83. package/dist/types/components/pagination/types.d.ts +23 -0
  84. package/dist/types/components/pagination/types.d.ts.map +1 -0
  85. package/dist/types/components/pagination/utils/ranges.d.ts +3 -0
  86. package/dist/types/components/pagination/utils/ranges.d.ts.map +1 -0
  87. package/dist/types/components/popover/Popover.vue.d.ts +4 -1
  88. package/dist/types/components/popover/Popover.vue.d.ts.map +1 -1
  89. package/dist/types/components/range-slider/RangeSlider.vue.d.ts +1 -0
  90. package/dist/types/components/range-slider/RangeSlider.vue.d.ts.map +1 -1
  91. package/dist/types/components/sheet/Sheet.vue.d.ts +21 -14
  92. package/dist/types/components/sheet/Sheet.vue.d.ts.map +1 -1
  93. package/dist/types/components/sheet/sheet.variants.d.ts +54 -3
  94. package/dist/types/components/sheet/sheet.variants.d.ts.map +1 -1
  95. package/dist/types/components/slider/Slider.vue.d.ts +1 -0
  96. package/dist/types/components/slider/Slider.vue.d.ts.map +1 -1
  97. package/dist/types/components/slider/slider.variants.d.ts.map +1 -1
  98. package/dist/types/components/tooltip/Tooltip.vue.d.ts +4 -15
  99. package/dist/types/components/tooltip/Tooltip.vue.d.ts.map +1 -1
  100. package/dist/types/components/tooltip/TooltipBubble.vue.d.ts +20 -0
  101. package/dist/types/components/tooltip/TooltipBubble.vue.d.ts.map +1 -0
  102. package/dist/types/components/tooltip/TooltipDirectiveRoot.d.ts +33 -0
  103. package/dist/types/components/tooltip/TooltipDirectiveRoot.d.ts.map +1 -0
  104. package/dist/types/components/tooltip/composables/useTooltipTarget.d.ts +2 -0
  105. package/dist/types/components/tooltip/composables/useTooltipTarget.d.ts.map +1 -0
  106. package/dist/types/components/tooltip/directive.d.ts +4 -0
  107. package/dist/types/components/tooltip/directive.d.ts.map +1 -0
  108. package/dist/types/components/tooltip/types.d.ts +14 -0
  109. package/dist/types/components/tooltip/types.d.ts.map +1 -0
  110. package/dist/types/components/tooltip/utils/directiveContext.d.ts +10 -0
  111. package/dist/types/components/tooltip/utils/directiveContext.d.ts.map +1 -0
  112. package/dist/types/components/tree-select/TreeSelect.vue.d.ts +6 -1
  113. package/dist/types/components/tree-select/TreeSelect.vue.d.ts.map +1 -1
  114. package/dist/types/components/tree-select/TreeSelectSearch.vue.d.ts +22 -0
  115. package/dist/types/components/tree-select/TreeSelectSearch.vue.d.ts.map +1 -0
  116. package/dist/types/components/tree-select/composables/useTreeSelect.d.ts +35 -0
  117. package/dist/types/components/tree-select/composables/useTreeSelect.d.ts.map +1 -0
  118. package/dist/types/components/tree-select/tree-select.variants.d.ts +1 -1
  119. package/dist/types/components/tree-select/tree-select.variants.d.ts.map +1 -1
  120. package/dist/types/components/tree-select/utils/filterTree.d.ts +7 -0
  121. package/dist/types/components/tree-select/utils/filterTree.d.ts.map +1 -0
  122. package/dist/types/index.d.ts +10 -1
  123. package/dist/types/index.d.ts.map +1 -1
  124. package/dist/types/lib/anchored-overlay.d.ts +12 -2
  125. package/dist/types/lib/anchored-overlay.d.ts.map +1 -1
  126. package/dist/types/lib/overlay-anchor.d.ts +16 -0
  127. package/dist/types/lib/overlay-anchor.d.ts.map +1 -0
  128. package/dist/types/lib/overlay-positioner.d.ts +3 -0
  129. package/dist/types/lib/overlay-positioner.d.ts.map +1 -0
  130. package/dist/types/lib/pointer-corridor.d.ts +7 -0
  131. package/dist/types/lib/pointer-corridor.d.ts.map +1 -0
  132. package/dist/types/lib/scroll-viewport.d.ts +9 -0
  133. package/dist/types/lib/scroll-viewport.d.ts.map +1 -0
  134. package/dist/types/lib/useDirection.d.ts +7 -0
  135. package/dist/types/lib/useDirection.d.ts.map +1 -0
  136. package/dist/types/locale/en-US.d.ts.map +1 -1
  137. package/dist/types/locale/index.d.ts.map +1 -1
  138. package/dist/types/locale/types.d.ts +13 -0
  139. package/dist/types/locale/types.d.ts.map +1 -1
  140. package/dist/types/locale/zh-CN.d.ts.map +1 -1
  141. package/package.json +1 -1
  142. package/src/components/badge/badge.variants.ts +1 -1
  143. package/src/components/dialog/Dialog.vue +5 -1
  144. package/src/components/drawer/Drawer.vue +69 -32
  145. package/src/components/drawer/drawer.variants.ts +6 -2
  146. package/src/components/dropdown-menu/DropdownMenu.vue +18 -5
  147. package/src/components/hover-card/HoverCard.vue +39 -16
  148. package/src/components/hover-card/HoverCardAnchor.vue +18 -0
  149. package/src/components/hover-card/composables/useHoverCardAnchor.ts +151 -0
  150. package/src/components/image/Image.vue +2 -0
  151. package/src/components/image/composables/useImageGroupState.ts +9 -1
  152. package/src/components/image/image.variants.ts +1 -1
  153. package/src/components/lightbox/Lightbox.vue +23 -17
  154. package/src/components/lightbox/LightboxChrome.vue +7 -1
  155. package/src/components/lightbox/LightboxToolbar.vue +16 -2
  156. package/src/components/lightbox/composables/useLightboxFrames.ts +108 -12
  157. package/src/components/lightbox/composables/useLightboxLarge.ts +4 -1
  158. package/src/components/lightbox/composables/useLightboxLayout.ts +44 -0
  159. package/src/components/lightbox/composables/useLightboxPhase.ts +12 -4
  160. package/src/components/lightbox/composables/useLightboxPose.ts +17 -19
  161. package/src/components/lightbox/composables/useLightboxRotation.ts +14 -4
  162. package/src/components/lightbox/composables/useLightboxZoom.ts +51 -9
  163. package/src/components/lightbox/lightbox.variants.ts +5 -0
  164. package/src/components/lightbox/types.ts +5 -1
  165. package/src/components/lightbox/utils/pose.ts +13 -6
  166. package/src/components/lightbox/utils/source.ts +17 -0
  167. package/src/components/lightbox/utils/zoom.ts +21 -7
  168. package/src/components/pagination/Pagination.vue +118 -0
  169. package/src/components/pagination/PaginationContent.vue +29 -0
  170. package/src/components/pagination/PaginationControl.vue +42 -0
  171. package/src/components/pagination/PaginationEllipsis.vue +67 -0
  172. package/src/components/pagination/PaginationInfo.vue +18 -0
  173. package/src/components/pagination/PaginationJump.vue +32 -0
  174. package/src/components/pagination/PaginationPage.vue +34 -0
  175. package/src/components/pagination/PaginationPages.vue +43 -0
  176. package/src/components/pagination/PaginationPopup.vue +92 -0
  177. package/src/components/pagination/PaginationSize.vue +30 -0
  178. package/src/components/pagination/composables/usePagination.ts +76 -0
  179. package/src/components/pagination/composables/usePaginationEllipsis.ts +269 -0
  180. package/src/components/pagination/composables/usePaginationJump.ts +23 -0
  181. package/src/components/pagination/composables/usePaginationPageTooltip.ts +23 -0
  182. package/src/components/pagination/composables/usePaginationWindow.ts +116 -0
  183. package/src/components/pagination/context.ts +23 -0
  184. package/src/components/pagination/pagination.variants.ts +32 -0
  185. package/src/components/pagination/types.ts +19 -0
  186. package/src/components/pagination/utils/ranges.ts +12 -0
  187. package/src/components/popover/Popover.vue +17 -7
  188. package/src/components/range-slider/RangeSlider.vue +6 -0
  189. package/src/components/sheet/Sheet.vue +67 -24
  190. package/src/components/sheet/sheet.variants.ts +17 -4
  191. package/src/components/slider/Slider.vue +6 -0
  192. package/src/components/slider/slider.variants.ts +1 -0
  193. package/src/components/tooltip/Tooltip.vue +10 -33
  194. package/src/components/tooltip/TooltipBubble.vue +36 -0
  195. package/src/components/tooltip/TooltipDirectiveRoot.ts +61 -0
  196. package/src/components/tooltip/composables/useTooltipTarget.ts +56 -0
  197. package/src/components/tooltip/directive.ts +54 -0
  198. package/src/components/tooltip/types.ts +15 -0
  199. package/src/components/tooltip/utils/directiveContext.ts +32 -0
  200. package/src/components/tree-select/TreeSelect.vue +90 -61
  201. package/src/components/tree-select/TreeSelectSearch.vue +48 -0
  202. package/src/components/tree-select/composables/useTreeSelect.ts +130 -0
  203. package/src/components/tree-select/tree-select.variants.ts +2 -2
  204. package/src/components/tree-select/utils/filterTree.ts +19 -0
  205. package/src/index.ts +10 -1
  206. package/src/lib/anchored-overlay.ts +28 -19
  207. package/src/lib/overlay-anchor.ts +65 -0
  208. package/src/lib/overlay-positioner.ts +25 -0
  209. package/src/lib/pointer-corridor.ts +26 -0
  210. package/src/lib/scroll-viewport.ts +8 -0
  211. package/src/lib/useDirection.ts +31 -0
  212. package/src/locale/en-US.ts +15 -1
  213. package/src/locale/index.ts +1 -0
  214. package/src/locale/types.ts +13 -0
  215. package/src/locale/zh-CN.ts +15 -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
+ }
@@ -1,17 +1,18 @@
1
1
  <script setup lang="ts">
2
+ import type { OverlayAnchor, OverlayPositionStrategy } from '../../lib/overlay-anchor'
2
3
  import { computed } from 'vue'
3
4
  import { PopoverRoot, PopoverTrigger, PopoverPortal, type PopoverContentEmits } from 'reka-ui'
4
5
  import PopoverContent from './PopoverContent.vue'
5
6
  import { useAnchoredOverlay } from '../../lib/anchored-overlay'
6
7
  import Card from '../card/Card.vue'
7
- import { useOverlayPortal } from '../../lib/overlay-portal'
8
8
  import { cn } from '../../lib/cn'
9
9
 
10
10
  defineOptions({ name: 'HnPopover', inheritAttrs: false })
11
11
 
12
12
  const props = withDefaults(
13
13
  defineProps<{
14
- anchor?: HTMLElement | null
14
+ anchor?: OverlayAnchor | null
15
+ updatePositionStrategy?: OverlayPositionStrategy
15
16
  modal?: boolean
16
17
  side?: 'top' | 'right' | 'bottom' | 'left'
17
18
  align?: 'start' | 'center' | 'end'
@@ -19,18 +20,26 @@
19
20
  padded?: boolean
20
21
  class?: string
21
22
  }>(),
22
- { side: 'bottom', align: 'center', sideOffset: 8, padded: true, modal: true },
23
+ {
24
+ updatePositionStrategy: 'optimized',
25
+ side: 'bottom',
26
+ align: 'center',
27
+ sideOffset: 8,
28
+ padded: true,
29
+ modal: true,
30
+ },
23
31
  )
24
32
 
25
33
  const open = defineModel<boolean>('open')
26
34
  const emit = defineEmits<PopoverContentEmits>()
27
35
  defineSlots<{ default?(): unknown; content?(): unknown }>()
28
- const { trigger, reference, visible, events } = useAnchoredOverlay(props, open, emit, event =>
29
- emit('openAutoFocus', event),
36
+ const { trigger, content, present, reference, visible, events } = useAnchoredOverlay(
37
+ props,
38
+ open,
39
+ emit,
40
+ event => emit('openAutoFocus', event),
30
41
  )
31
42
 
32
- const { content, present } = useOverlayPortal(visible)
33
-
34
43
  const pressOrigin = computed(() => {
35
44
  if (props.side === 'left') return 'right'
36
45
  if (props.side === 'right') return 'left'
@@ -54,6 +63,7 @@
54
63
  <PopoverContent
55
64
  v-bind="{ ...$attrs, ...events }"
56
65
  :reference="reference"
66
+ :update-position-strategy="props.updatePositionStrategy"
57
67
  as-child
58
68
  :side="props.side"
59
69
  :align="props.align"
@@ -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"
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, shallowRef } from 'vue'
2
+ import { computed } from 'vue'
3
3
  import {
4
4
  DialogClose,
5
5
  DialogContent,
@@ -10,8 +10,9 @@
10
10
  DialogTitle,
11
11
  DialogTrigger,
12
12
  } from 'reka-ui'
13
+ import { useOverlayPortal } from '../../lib/overlay-portal'
14
+ import { useScrollViewport } from '../../lib/scroll-viewport'
13
15
  import { cn } from '../../lib/cn'
14
- import { useUiLocale } from '../../locale'
15
16
  import Card from '../card/Card.vue'
16
17
  import CloseButton from '../close-button/CloseButton.vue'
17
18
  import Heading from '../heading/Heading.vue'
@@ -26,16 +27,29 @@
26
27
  defineProps<{
27
28
  title: string
28
29
  description?: string
30
+ header?: boolean
31
+ closable?: boolean
29
32
  handle?: boolean
30
33
  locked?: boolean
31
34
  class?: string
32
35
  }>(),
33
- { handle: true, locked: false },
36
+ { header: true, closable: true, handle: true, locked: false },
34
37
  )
35
38
 
39
+ const slots = defineSlots<{
40
+ default?(): unknown
41
+ icon?(): unknown
42
+ title?(): unknown
43
+ body?(props: { close: () => void }): unknown
44
+ content?(props: { close: () => void }): unknown
45
+ footer?(props: { close: () => void }): unknown
46
+ }>()
47
+
48
+ const { scrollArea, viewport } = useScrollViewport()
49
+ defineExpose({ viewport })
50
+
36
51
  const open = defineModel<boolean>('open')
37
- const t = useUiLocale()
38
- const panel = shallowRef<{ $el: HTMLElement } | null>(null)
52
+ const { content: panel, present } = useOverlayPortal(open)
39
53
 
40
54
  const { dragging, offset, onPointerDown } = useDragToDismiss(() => panel.value?.$el ?? null, {
41
55
  enabled: () => !props.locked,
@@ -63,54 +77,83 @@
63
77
  <DialogTrigger v-if="$slots.default" as-child>
64
78
  <slot />
65
79
  </DialogTrigger>
66
- <DialogPortal>
80
+ <DialogPortal v-if="present">
67
81
  <DialogOverlay class="hn-scrim" />
68
82
  <DialogContent as-child @escape-key-down="guard" @interact-outside="guard">
69
83
  <Card
70
84
  ref="panel"
85
+ v-bind="props.description ? {} : { 'aria-describedby': undefined }"
71
86
  data-hn-sheet
72
87
  :data-dragging="dragging ? '' : undefined"
73
88
  :padded="false"
74
89
  :style="style"
75
- :class="cn(sheetPanel(), props.class)"
90
+ :class="
91
+ cn(sheetPanel({ grip: props.header || props.handle, padded: !slots.body }), props.class)
92
+ "
76
93
  >
94
+ <template v-if="!props.header || slots.body">
95
+ <DialogTitle as-child>
96
+ <Heading :level="2" class="sr-only">{{ props.title }}</Heading>
97
+ </DialogTitle>
98
+ <DialogDescription v-if="props.description" class="sr-only">
99
+ {{ props.description }}
100
+ </DialogDescription>
101
+ </template>
77
102
  <div
103
+ v-if="props.handle || (props.header && !slots.body)"
78
104
  data-hn-sheet-grip
79
105
  :data-dragging="dragging ? '' : undefined"
80
- :class="sheetGrip()"
106
+ :class="sheetGrip({ standalone: !!slots.body })"
81
107
  @pointerdown="onPointerDown"
82
108
  >
83
109
  <div
84
110
  v-if="props.handle"
85
111
  aria-hidden="true"
86
112
  :data-disabled="props.locked ? '' : undefined"
87
- :class="sheetHandle()"
113
+ :class="sheetHandle({ header: props.header && !slots.body })"
88
114
  />
89
- <div class="flex items-start justify-between gap-4 px-(--hn-panel-p)">
115
+ <div
116
+ v-if="props.header && !slots.body"
117
+ class="flex items-start justify-between gap-4 px-(--hn-panel-p)"
118
+ >
90
119
  <div class="flex min-w-0 flex-col gap-1.5">
91
- <DialogTitle as-child>
92
- <Heading :level="2" size="lg">{{ props.title }}</Heading>
93
- </DialogTitle>
120
+ <div class="flex min-w-0 items-center gap-2">
121
+ <span
122
+ v-if="slots.icon"
123
+ class="text-muted flex shrink-0 [&_svg]:size-5"
124
+ aria-hidden="true"
125
+ >
126
+ <slot name="icon" />
127
+ </span>
128
+ <DialogTitle as-child>
129
+ <Heading :level="2" size="lg" class="min-w-0">
130
+ <slot name="title">{{ props.title }}</slot>
131
+ </Heading>
132
+ </DialogTitle>
133
+ </div>
94
134
  <DialogDescription v-if="props.description" as-child>
95
135
  <Text tone="muted">{{ props.description }}</Text>
96
136
  </DialogDescription>
97
137
  </div>
98
- <DialogClose v-if="!props.handle" as-child>
138
+ <DialogClose v-if="!props.handle && props.closable" as-child>
99
139
  <CloseButton :disabled="props.locked" class="-mt-1.5 -me-1.5 shrink-0" />
100
140
  </DialogClose>
101
141
  </div>
102
142
  </div>
103
- <ScrollArea v-if="$slots.content" class="min-h-0 grow">
104
- <div class="px-(--hn-panel-p) py-1">
105
- <slot name="content" :close="close" />
143
+ <slot v-if="slots.body" name="body" :close="close" />
144
+ <template v-else>
145
+ <ScrollArea v-if="$slots.content" ref="scrollArea" class="min-h-0 grow">
146
+ <div class="px-(--hn-panel-p) py-1">
147
+ <slot name="content" :close="close" />
148
+ </div>
149
+ </ScrollArea>
150
+ <div
151
+ v-if="$slots.footer"
152
+ class="flex shrink-0 justify-end gap-(--hn-inline-gap) px-(--hn-panel-p)"
153
+ >
154
+ <slot name="footer" :close="close" />
106
155
  </div>
107
- </ScrollArea>
108
- <div
109
- v-if="$slots.footer"
110
- class="flex shrink-0 justify-end gap-(--hn-inline-gap) px-(--hn-panel-p)"
111
- >
112
- <slot name="footer" :close="close" />
113
- </div>
156
+ </template>
114
157
  </Card>
115
158
  </DialogContent>
116
159
  </DialogPortal>
@@ -3,17 +3,30 @@ import { tv } from '../../lib/tv'
3
3
  export const sheetPanel = tv({
4
4
  base: [
5
5
  'hn-anim-sheet-bottom hn-transition pointer-events-auto fixed inset-x-0 bottom-0 z-(--hn-z-overlay)',
6
- 'mx-auto flex max-h-[calc(100dvh-3rem)] w-full flex-col gap-4 sm:max-w-xl',
7
- 'rounded-t-xl rounded-b-none border-b-0 pt-2 shadow-lg outline-none',
8
- 'pb-[max(var(--hn-panel-p),env(safe-area-inset-bottom))]',
6
+ 'mx-auto flex max-h-[calc(100dvh-3rem)] w-full flex-col sm:max-w-xl',
7
+ 'rounded-t-xl rounded-b-none border-b-0 shadow-lg outline-none',
9
8
  'data-dragging:transition-none',
10
9
  ],
10
+ variants: {
11
+ padded: { true: 'gap-4 pb-[max(var(--hn-panel-p),env(safe-area-inset-bottom))]' },
12
+ grip: { true: '', false: '' },
13
+ },
14
+ compoundVariants: [
15
+ { padded: true, grip: true, class: 'pt-2' },
16
+ { padded: true, grip: false, class: 'pt-(--hn-panel-p)' },
17
+ ],
18
+ defaultVariants: { grip: true, padded: true },
11
19
  })
12
20
 
13
21
  export const sheetGrip = tv({
14
22
  base: 'flex shrink-0 cursor-grab touch-none flex-col select-none data-dragging:cursor-grabbing',
23
+ variants: { standalone: { true: 'py-2' } },
15
24
  })
16
25
 
17
26
  export const sheetHandle = tv({
18
- base: 'bg-line-strong hn-transition-base mx-auto mb-3 h-1.5 w-10 rounded-full data-disabled:opacity-50',
27
+ base: 'bg-line-strong hn-transition-base mx-auto h-1.5 w-10 rounded-full data-disabled:opacity-50',
28
+ variants: {
29
+ header: { true: 'mb-3', false: '' },
30
+ },
31
+ defaultVariants: { header: true },
19
32
  })
@@ -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>