@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,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
+ }
@@ -0,0 +1,54 @@
1
+ import { h, render, type AppContext, type DirectiveBinding, type ObjectDirective } from 'vue'
2
+ import TooltipDirectiveRoot from './TooltipDirectiveRoot'
3
+ import { directiveContext } from './utils/directiveContext'
4
+ import type { TooltipDirectiveOptions, TooltipDirectiveValue } from './types'
5
+
6
+ interface TooltipMount {
7
+ container: HTMLElement
8
+ context: AppContext
9
+ revision: number
10
+ }
11
+
12
+ const mounts = new WeakMap<HTMLElement, TooltipMount>()
13
+ let nextId = 0
14
+
15
+ function options(value: TooltipDirectiveValue): TooltipDirectiveOptions {
16
+ return typeof value === 'string' ? { content: value } : value ? { ...value } : { content: '' }
17
+ }
18
+
19
+ function update(target: HTMLElement, binding: DirectiveBinding<TooltipDirectiveValue>) {
20
+ const mount = mounts.get(target)
21
+ if (!mount) return
22
+ const vnode = h(TooltipDirectiveRoot, {
23
+ target,
24
+ options: options(binding.value),
25
+ revision: ++mount.revision,
26
+ })
27
+ vnode.appContext = mount.context
28
+ render(vnode, mount.container)
29
+ }
30
+
31
+ export const vTooltip: ObjectDirective<HTMLElement, TooltipDirectiveValue> = {
32
+ deep: true,
33
+ mounted(target, binding) {
34
+ const owner = binding.instance?.$
35
+ if (!owner) return
36
+ const context = directiveContext(owner, target)
37
+ context.config = Object.create(context.config) as AppContext['config']
38
+ context.config.idPrefix = 'hn-tooltip-' + ++nextId
39
+ mounts.set(target, {
40
+ container: target.ownerDocument.createElement('div'),
41
+ context,
42
+ revision: 0,
43
+ })
44
+ update(target, binding)
45
+ },
46
+ updated: update,
47
+ beforeUnmount(target) {
48
+ const mount = mounts.get(target)
49
+ if (!mount) return
50
+ render(null, mount.container)
51
+ mounts.delete(target)
52
+ },
53
+ getSSRProps: () => ({}),
54
+ }
@@ -0,0 +1,15 @@
1
+ export interface TooltipProps {
2
+ content?: string
3
+ side?: 'top' | 'right' | 'bottom' | 'left'
4
+ align?: 'start' | 'center' | 'end'
5
+ sideOffset?: number
6
+ open?: boolean
7
+ disabled?: boolean
8
+ class?: string
9
+ }
10
+
11
+ export type TooltipDirectiveOptions = Omit<TooltipProps, 'open' | 'content'> & {
12
+ content: string
13
+ }
14
+
15
+ export type TooltipDirectiveValue = string | TooltipDirectiveOptions | null | undefined | false
@@ -0,0 +1,32 @@
1
+ import { isVNode, type AppContext, type ComponentInternalInstance, type VNode } from 'vue'
2
+
3
+ type ContextOwner = ComponentInternalInstance & { provides: AppContext['provides'] }
4
+
5
+ function findOwner(
6
+ node: VNode,
7
+ target: HTMLElement,
8
+ owner: ComponentInternalInstance,
9
+ ): ComponentInternalInstance | undefined {
10
+ if (node.component) {
11
+ return findOwner(node.component.subTree, target, node.component)
12
+ }
13
+ if (node.suspense) {
14
+ for (const branch of [node.suspense.activeBranch, node.suspense.pendingBranch]) {
15
+ const match = branch && findOwner(branch, target, owner)
16
+ if (match) return match
17
+ }
18
+ }
19
+ if (node.el === target) return owner
20
+ if (Array.isArray(node.children)) {
21
+ for (const child of node.children) {
22
+ if (!isVNode(child)) continue
23
+ const match = findOwner(child, target, owner)
24
+ if (match) return match
25
+ }
26
+ }
27
+ }
28
+
29
+ export function directiveContext(owner: ComponentInternalInstance, target: HTMLElement) {
30
+ const parent = (findOwner(owner.subTree, target, owner) ?? owner) as ContextOwner
31
+ return { ...parent.appContext, provides: parent.provides }
32
+ }
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref, watch } from 'vue'
2
+ import { useId } from 'vue'
3
3
  import { Check } from '@lucide/vue'
4
4
  import {
5
5
  PopoverContent,
@@ -29,7 +29,9 @@
29
29
  treeSelectRow,
30
30
  treeSelectToggle,
31
31
  } from './tree-select.variants'
32
- import { findNode, pathTo, type TreeSelectNode } from './types'
32
+ import type { TreeSelectNode } from './types'
33
+ import TreeSelectSearch from './TreeSelectSearch.vue'
34
+ import { useTreeSelect } from './composables/useTreeSelect'
33
35
 
34
36
  defineOptions({ name: 'HnTreeSelect', inheritAttrs: false })
35
37
 
@@ -37,6 +39,8 @@
37
39
  defineProps<{
38
40
  items: TreeSelectNode[]
39
41
  placeholder?: string
42
+ searchable?: boolean
43
+ searchPlaceholder?: string
40
44
  defaultExpanded?: Array<string | number>
41
45
  variant?: InputVariants['variant']
42
46
  size?: InputVariants['size']
@@ -49,6 +53,9 @@
49
53
 
50
54
  const model = defineModel<string | number | null>()
51
55
  const open = defineModel<boolean>('open', { default: false })
56
+ const search = defineModel<string>('search', { default: '' })
57
+ const treeId = useId()
58
+ const triggerId = useId()
52
59
 
53
60
  defineSlots<{ node(props: { node: TreeSelectNode }): unknown }>()
54
61
 
@@ -60,82 +67,102 @@
60
67
  invalid,
61
68
  disabled,
62
69
  describedBy,
70
+ labelledBy,
63
71
  } = useFieldControl({
64
72
  invalid: () => props.invalid || !!group?.invalid.value,
65
73
  disabled: () => props.disabled || !!group?.disabled.value,
66
74
  })
67
- const selected = computed(() => findNode(props.items, model.value))
68
- const expanded = ref<string[]>([])
69
-
70
- watch(
71
- open,
72
- isOpen => {
73
- if (!isOpen) return
74
- const path = pathTo(props.items, model.value) ?? []
75
- expanded.value = Array.from(
76
- new Set([...props.defaultExpanded, ...path].map(value => String(value))),
77
- )
78
- },
79
- { immediate: true },
80
- )
81
-
82
- function key(node: TreeSelectNode) {
83
- return String(node.value)
84
- }
85
-
86
- function choose(node: TreeSelectNode | undefined) {
87
- if (!node || node.disabled) return
88
- model.value = node.value
89
- open.value = false
90
- }
91
-
92
- function keepRowClick(event: CustomEvent<{ originalEvent: Event }>) {
93
- if (event.detail.originalEvent.type === 'click') event.preventDefault()
94
- }
75
+ const {
76
+ selected,
77
+ items,
78
+ expanded,
79
+ input,
80
+ tree,
81
+ getChildren,
82
+ key,
83
+ choose,
84
+ keepRowClick,
85
+ focusSearch,
86
+ clearSearch,
87
+ onEscape,
88
+ onSearchKeydown,
89
+ onTreeKeydown,
90
+ } = useTreeSelect(props, model, open, search)
95
91
  </script>
96
92
 
97
93
  <template>
98
94
  <PopoverRoot v-model:open="open" modal>
99
- <PopoverTrigger
100
- v-bind="$attrs"
101
- :id="fieldId"
102
- :aria-describedby="describedBy"
103
- data-hn-tree-select
104
- role="combobox"
105
- :aria-expanded="open"
106
- :disabled="disabled"
107
- :data-placeholder="selected ? undefined : ''"
108
- :data-invalid="invalid ? '' : undefined"
109
- :data-disabled="disabled ? '' : undefined"
110
- :aria-invalid="invalid || undefined"
111
- :class="
112
- cn(
113
- group ? inputEmbedded() : inputHost({ variant: props.variant, size: props.size }),
114
- selectTrigger(),
115
- props.class,
116
- )
117
- "
118
- >
119
- <span class="min-w-0 flex-1 truncate">
120
- {{ selected ? selected.label : (props.placeholder ?? t.select.placeholder) }}
121
- </span>
122
- <span :class="inputAdornment()">
123
- <DisclosureIcon />
124
- </span>
95
+ <PopoverTrigger as-child>
96
+ <button
97
+ type="button"
98
+ v-bind="$attrs"
99
+ :id="fieldId ?? triggerId"
100
+ :aria-describedby="describedBy"
101
+ data-hn-tree-select
102
+ role="combobox"
103
+ :aria-expanded="open"
104
+ :disabled="disabled"
105
+ :data-placeholder="selected ? undefined : ''"
106
+ :data-invalid="invalid ? '' : undefined"
107
+ :data-disabled="disabled ? '' : undefined"
108
+ :aria-invalid="invalid || undefined"
109
+ :class="
110
+ cn(
111
+ group ? inputEmbedded() : inputHost({ variant: props.variant, size: props.size }),
112
+ selectTrigger(),
113
+ props.class,
114
+ )
115
+ "
116
+ >
117
+ <span class="min-w-0 flex-1 truncate">
118
+ {{ selected ? selected.label : (props.placeholder ?? t.select.placeholder) }}
119
+ </span>
120
+ <span :class="inputAdornment()">
121
+ <DisclosureIcon />
122
+ </span>
123
+ </button>
125
124
  </PopoverTrigger>
126
125
  <PopoverPortal>
127
- <PopoverContent as-child align="start" :side-offset="8">
128
- <Card :padded="false" data-hn-tree-select-content :class="treeSelectContent()">
126
+ <PopoverContent
127
+ as-child
128
+ align="start"
129
+ :side-offset="8"
130
+ @open-auto-focus="focusSearch"
131
+ @escape-key-down="onEscape"
132
+ >
133
+ <Card
134
+ :padded="false"
135
+ data-hn-tree-select-content
136
+ :aria-label="$attrs['aria-label'] as string | undefined"
137
+ :aria-labelledby="
138
+ labelledBy ?? ($attrs['aria-label'] ? undefined : (fieldId ?? triggerId))
139
+ "
140
+ :class="treeSelectContent()"
141
+ >
142
+ <TreeSelectSearch
143
+ v-if="props.searchable"
144
+ ref="input"
145
+ v-model="search"
146
+ :placeholder="props.searchPlaceholder ?? t.treeSelect.search"
147
+ :controls="treeId"
148
+ @clear="clearSearch"
149
+ @keydown="onSearchKeydown"
150
+ />
129
151
  <ScrollArea :class="treeSelectList()">
130
152
  <TreeRoot
153
+ :id="treeId"
154
+ ref="tree"
131
155
  v-slot="{ flattenItems }"
132
156
  v-model:expanded="expanded"
133
- :items="props.items"
157
+ :items="items"
134
158
  :get-key="key"
135
- :get-children="node => node.children"
159
+ :get-children="getChildren"
136
160
  :model-value="selected"
161
+ :aria-label="$attrs['aria-label'] as string | undefined"
162
+ :aria-labelledby="labelledBy"
137
163
  class="flex flex-col p-1 outline-none"
138
164
  @update:model-value="choose"
165
+ @keydown.capture="onTreeKeydown"
139
166
  >
140
167
  <TreeItem
141
168
  v-for="item in flattenItems"
@@ -169,7 +196,9 @@
169
196
  <Check v-if="isSelected" />
170
197
  </span>
171
198
  </TreeItem>
172
- <div v-if="props.items.length === 0" :class="selectEmpty()">{{ t.select.empty }}</div>
199
+ <li v-if="items.length === 0" role="none" :class="selectEmpty()">
200
+ <span role="status">{{ t.select.empty }}</span>
201
+ </li>
173
202
  </TreeRoot>
174
203
  </ScrollArea>
175
204
  </Card>
@@ -0,0 +1,48 @@
1
+ <script setup lang="ts">
2
+ import { shallowRef } from 'vue'
3
+ import { Search, X } from '@lucide/vue'
4
+ import { useUiLocale } from '../../locale'
5
+ import InputAction from '../input/InputAction.vue'
6
+ import { inputAdornment, inputControl, textInputHost } from '../input/input.variants'
7
+
8
+ defineOptions({ name: 'HnTreeSelectSearch', inheritAttrs: false })
9
+
10
+ const props = defineProps<{ placeholder: string; controls: string }>()
11
+ const emit = defineEmits<{ clear: []; keydown: [event: KeyboardEvent] }>()
12
+ const model = defineModel<string>({ required: true })
13
+ const t = useUiLocale()
14
+ const input = shallowRef<HTMLInputElement | null>(null)
15
+
16
+ defineExpose({ focus: () => input.value?.focus() })
17
+ </script>
18
+
19
+ <template>
20
+ <div class="border-line shrink-0 border-b px-1">
21
+ <div :class="textInputHost({ variant: 'bare', size: 'sm' })">
22
+ <span :class="inputAdornment()"><Search /></span>
23
+ <input
24
+ ref="input"
25
+ v-model="model"
26
+ data-hn-tree-select-search
27
+ type="text"
28
+ role="searchbox"
29
+ autocomplete="off"
30
+ :aria-label="props.placeholder"
31
+ :aria-controls="props.controls"
32
+ :placeholder="props.placeholder"
33
+ :class="inputControl({ leading: true, trailing: !!model })"
34
+ @keydown="emit('keydown', $event)"
35
+ />
36
+ <Transition
37
+ enter-active-class="hn-transition-base"
38
+ enter-from-class="scale-90 opacity-0"
39
+ leave-active-class="hn-transition"
40
+ leave-to-class="scale-90 opacity-0"
41
+ >
42
+ <InputAction v-if="model" :label="t.common.clear" @click="emit('clear')">
43
+ <X />
44
+ </InputAction>
45
+ </Transition>
46
+ </div>
47
+ </div>
48
+ </template>
@@ -0,0 +1,130 @@
1
+ import { computed, ref, shallowRef, watch, type Ref } from 'vue'
2
+ import { useFilter } from 'reka-ui'
3
+ import { findNode, pathTo, type TreeSelectNode } from '../types'
4
+ import { filterTree } from '../utils/filterTree'
5
+
6
+ interface TreeSelectOptions {
7
+ items: TreeSelectNode[]
8
+ defaultExpanded: Array<string | number>
9
+ searchable?: boolean
10
+ }
11
+
12
+ export function useTreeSelect(
13
+ props: TreeSelectOptions,
14
+ model: Ref<string | number | null | undefined>,
15
+ open: Ref<boolean>,
16
+ search: Ref<string>,
17
+ ) {
18
+ const selected = computed(() => findNode(props.items, model.value))
19
+ const normalExpanded = ref<string[]>([])
20
+ const searchExpanded = ref<string[]>([])
21
+ const input = shallowRef<{ focus: () => void } | null>(null)
22
+ const tree = shallowRef<{ $el: HTMLElement } | null>(null)
23
+ const { contains } = useFilter({ sensitivity: 'base' })
24
+ const query = computed(() => (props.searchable ? search.value.trim() : ''))
25
+ const filtered = computed(() =>
26
+ query.value ? filterTree(props.items, node => contains(node.label, query.value)) : null,
27
+ )
28
+ const items = computed(() => filtered.value?.items ?? props.items)
29
+ const expanded = computed({
30
+ get: () => (filtered.value ? searchExpanded.value : normalExpanded.value),
31
+ set: value => {
32
+ if (filtered.value) searchExpanded.value = value
33
+ else normalExpanded.value = value
34
+ },
35
+ })
36
+
37
+ watch(
38
+ filtered,
39
+ value => {
40
+ searchExpanded.value = value?.expanded ?? []
41
+ },
42
+ { immediate: true },
43
+ )
44
+
45
+ watch(
46
+ open,
47
+ (isOpen, wasOpen) => {
48
+ if (!isOpen) {
49
+ if (wasOpen) search.value = ''
50
+ return
51
+ }
52
+ const path = pathTo(props.items, model.value) ?? []
53
+ normalExpanded.value = Array.from(new Set([...props.defaultExpanded, ...path].map(String)))
54
+ },
55
+ { immediate: true },
56
+ )
57
+
58
+ function getChildren(node: TreeSelectNode) {
59
+ return filtered.value ? filtered.value.children.get(node) : node.children
60
+ }
61
+
62
+ function key(node: TreeSelectNode) {
63
+ return String(node.value)
64
+ }
65
+
66
+ function choose(node: TreeSelectNode | undefined) {
67
+ if (!node || node.disabled) return
68
+ model.value = node.value
69
+ open.value = false
70
+ }
71
+
72
+ function keepRowClick(event: CustomEvent<{ originalEvent: Event }>) {
73
+ if (event.detail.originalEvent.type === 'click') event.preventDefault()
74
+ }
75
+
76
+ function focusSearch(event: Event) {
77
+ if (!props.searchable) return
78
+ event.preventDefault()
79
+ input.value?.focus()
80
+ }
81
+
82
+ function clearSearch() {
83
+ search.value = ''
84
+ input.value?.focus()
85
+ }
86
+
87
+ function onEscape(event: KeyboardEvent) {
88
+ if (!props.searchable || !search.value) return
89
+ event.preventDefault()
90
+ clearSearch()
91
+ }
92
+
93
+ function enabledRows() {
94
+ return Array.from(
95
+ tree.value?.$el.querySelectorAll<HTMLElement>('[role="treeitem"]:not([data-disabled])') ?? [],
96
+ )
97
+ }
98
+
99
+ function onSearchKeydown(event: KeyboardEvent) {
100
+ if (event.isComposing || !['ArrowDown', 'ArrowUp'].includes(event.key)) return
101
+ event.preventDefault()
102
+ const rows = enabledRows()
103
+ const target = event.key === 'ArrowDown' ? rows[0] : rows.at(-1)
104
+ target?.focus()
105
+ }
106
+
107
+ function onTreeKeydown(event: KeyboardEvent) {
108
+ if (!props.searchable || event.key !== 'ArrowUp' || event.target !== enabledRows()[0]) return
109
+ event.preventDefault()
110
+ event.stopPropagation()
111
+ input.value?.focus()
112
+ }
113
+
114
+ return {
115
+ selected,
116
+ items,
117
+ expanded,
118
+ input,
119
+ tree,
120
+ getChildren,
121
+ key,
122
+ choose,
123
+ keepRowClick,
124
+ focusSearch,
125
+ clearSearch,
126
+ onEscape,
127
+ onSearchKeydown,
128
+ onTreeKeydown,
129
+ }
130
+ }
@@ -2,12 +2,12 @@ import { tv } from '../../lib/tv'
2
2
 
3
3
  export const treeSelectContent = tv({
4
4
  base: [
5
- 'hn-anim-pop z-(--hn-z-overlay) flex w-[var(--reka-popover-trigger-width)] min-w-40 flex-col overflow-hidden shadow-md outline-none',
5
+ 'hn-anim-pop z-(--hn-z-overlay) flex w-[var(--reka-popover-trigger-width)] min-w-40 max-h-[var(--reka-popover-content-available-height)] flex-col overflow-hidden shadow-md outline-none',
6
6
  ],
7
7
  })
8
8
 
9
9
  export const treeSelectList = tv({
10
- base: 'max-h-[min(20rem,var(--reka-popover-content-available-height))]',
10
+ base: 'min-h-0 max-h-80',
11
11
  })
12
12
 
13
13
  export const treeSelectRow = tv({
@@ -0,0 +1,19 @@
1
+ import type { TreeSelectNode } from '../types'
2
+
3
+ export function filterTree(nodes: TreeSelectNode[], matches: (node: TreeSelectNode) => boolean) {
4
+ const children = new Map<TreeSelectNode, TreeSelectNode[]>()
5
+ const expanded: string[] = []
6
+
7
+ function visit(items: TreeSelectNode[]): TreeSelectNode[] {
8
+ return items.filter(node => {
9
+ const descendants = visit(node.children ?? [])
10
+ if (descendants.length) {
11
+ children.set(node, descendants)
12
+ expanded.push(String(node.value))
13
+ }
14
+ return descendants.length > 0 || matches(node)
15
+ })
16
+ }
17
+
18
+ return { items: visit(nodes), children, expanded }
19
+ }
package/src/index.ts CHANGED
@@ -25,6 +25,12 @@ export { default as Badge } from './components/badge/Badge.vue'
25
25
  export { default as Indicator } from './components/indicator/Indicator.vue'
26
26
  export { default as Callout } from './components/callout/Callout.vue'
27
27
  export { default as Alert } from './components/alert/Alert.vue'
28
+ export { default as Pagination } from './components/pagination/Pagination.vue'
29
+ export { default as PaginationContent } from './components/pagination/PaginationContent.vue'
30
+ export { default as PaginationInfo } from './components/pagination/PaginationInfo.vue'
31
+ export { default as PaginationSize } from './components/pagination/PaginationSize.vue'
32
+ export { default as PaginationJump } from './components/pagination/PaginationJump.vue'
33
+ export type { PaginationChange, PaginationState } from './components/pagination/types'
28
34
  export { default as PrevNext } from './components/prev-next/PrevNext.vue'
29
35
  export { default as PrevNextLink } from './components/prev-next/PrevNextLink.vue'
30
36
  export { default as Table } from './components/table/Table.vue'
@@ -206,6 +212,9 @@ export { default as BreadcrumbItem } from './components/breadcrumb/BreadcrumbIte
206
212
  export { default as BreadcrumbSeparator } from './components/breadcrumb/BreadcrumbSeparator.vue'
207
213
  export { default as TooltipProvider } from './components/tooltip/TooltipProvider.vue'
208
214
  export { default as Tooltip } from './components/tooltip/Tooltip.vue'
215
+ export { vTooltip } from './components/tooltip/directive'
216
+ export type { TooltipDirectiveOptions, TooltipDirectiveValue } from './components/tooltip/types'
217
+ export type { OverlayAnchor, OverlayPositionStrategy } from './lib/overlay-anchor'
209
218
  export { default as Popover } from './components/popover/Popover.vue'
210
219
  export { default as DropdownMenu } from './components/dropdown-menu/DropdownMenu.vue'
211
220
  export { default as DropdownMenuItem } from './components/dropdown-menu/DropdownMenuItem.vue'
@@ -228,7 +237,7 @@ export { default as Skeleton } from './components/skeleton/Skeleton.vue'
228
237
  export { default as Image } from './components/image/Image.vue'
229
238
  export { default as ImageGroup } from './components/image/ImageGroup.vue'
230
239
  export { default as Lightbox } from './components/lightbox/Lightbox.vue'
231
- export type { LightboxItem } from './components/lightbox/types'
240
+ export type { LightboxItem, LightboxSource } from './components/lightbox/types'
232
241
  export { provideImageResolver, useImageResolver } from './components/image/resolver'
233
242
  export type { ImageResolver } from './components/image/resolver'
234
243
  export type { ImageVariants } from './components/image/image.variants'