@hina-ui/vue 1.3.1 → 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 (185) hide show
  1. package/dist/index.js +3990 -2016
  2. package/dist/index.js.map +1 -1
  3. package/dist/types/components/hover-card/HoverCard.vue.d.ts +7 -7
  4. package/dist/types/components/hover-card/HoverCard.vue.d.ts.map +1 -1
  5. package/dist/types/components/hover-card/HoverCardAnchor.vue.d.ts +27 -0
  6. package/dist/types/components/hover-card/HoverCardAnchor.vue.d.ts.map +1 -0
  7. package/dist/types/components/hover-card/composables/useHoverCardAnchor.d.ts +17 -0
  8. package/dist/types/components/hover-card/composables/useHoverCardAnchor.d.ts.map +1 -0
  9. package/dist/types/components/image/Image.vue.d.ts +3 -1
  10. package/dist/types/components/image/Image.vue.d.ts.map +1 -1
  11. package/dist/types/components/image/composables/useImageGroupState.d.ts.map +1 -1
  12. package/dist/types/components/image/image.variants.d.ts +1 -1
  13. package/dist/types/components/lightbox/Lightbox.vue.d.ts.map +1 -1
  14. package/dist/types/components/lightbox/LightboxChrome.vue.d.ts +4 -0
  15. package/dist/types/components/lightbox/LightboxChrome.vue.d.ts.map +1 -1
  16. package/dist/types/components/lightbox/LightboxToolbar.vue.d.ts +4 -0
  17. package/dist/types/components/lightbox/LightboxToolbar.vue.d.ts.map +1 -1
  18. package/dist/types/components/lightbox/composables/useLightboxFrames.d.ts +4 -1
  19. package/dist/types/components/lightbox/composables/useLightboxFrames.d.ts.map +1 -1
  20. package/dist/types/components/lightbox/composables/useLightboxLarge.d.ts +1 -0
  21. package/dist/types/components/lightbox/composables/useLightboxLarge.d.ts.map +1 -1
  22. package/dist/types/components/lightbox/composables/useLightboxLayout.d.ts +17 -0
  23. package/dist/types/components/lightbox/composables/useLightboxLayout.d.ts.map +1 -0
  24. package/dist/types/components/lightbox/composables/useLightboxPhase.d.ts +1 -1
  25. package/dist/types/components/lightbox/composables/useLightboxPhase.d.ts.map +1 -1
  26. package/dist/types/components/lightbox/composables/useLightboxPose.d.ts.map +1 -1
  27. package/dist/types/components/lightbox/composables/useLightboxRotation.d.ts +5 -1
  28. package/dist/types/components/lightbox/composables/useLightboxRotation.d.ts.map +1 -1
  29. package/dist/types/components/lightbox/composables/useLightboxZoom.d.ts +10 -1
  30. package/dist/types/components/lightbox/composables/useLightboxZoom.d.ts.map +1 -1
  31. package/dist/types/components/lightbox/lightbox.variants.d.ts +2 -0
  32. package/dist/types/components/lightbox/lightbox.variants.d.ts.map +1 -0
  33. package/dist/types/components/lightbox/types.d.ts +7 -1
  34. package/dist/types/components/lightbox/types.d.ts.map +1 -1
  35. package/dist/types/components/lightbox/utils/pose.d.ts +2 -2
  36. package/dist/types/components/lightbox/utils/pose.d.ts.map +1 -1
  37. package/dist/types/components/lightbox/utils/source.d.ts +4 -0
  38. package/dist/types/components/lightbox/utils/source.d.ts.map +1 -0
  39. package/dist/types/components/lightbox/utils/zoom.d.ts +8 -4
  40. package/dist/types/components/lightbox/utils/zoom.d.ts.map +1 -1
  41. package/dist/types/components/pagination/Pagination.vue.d.ts +59 -0
  42. package/dist/types/components/pagination/Pagination.vue.d.ts.map +1 -0
  43. package/dist/types/components/pagination/PaginationContent.vue.d.ts +23 -0
  44. package/dist/types/components/pagination/PaginationContent.vue.d.ts.map +1 -0
  45. package/dist/types/components/pagination/PaginationControl.vue.d.ts +10 -0
  46. package/dist/types/components/pagination/PaginationControl.vue.d.ts.map +1 -0
  47. package/dist/types/components/pagination/PaginationEllipsis.vue.d.ts +23 -0
  48. package/dist/types/components/pagination/PaginationEllipsis.vue.d.ts.map +1 -0
  49. package/dist/types/components/pagination/PaginationInfo.vue.d.ts +24 -0
  50. package/dist/types/components/pagination/PaginationInfo.vue.d.ts.map +1 -0
  51. package/dist/types/components/pagination/PaginationJump.vue.d.ts +7 -0
  52. package/dist/types/components/pagination/PaginationJump.vue.d.ts.map +1 -0
  53. package/dist/types/components/pagination/PaginationPage.vue.d.ts +20 -0
  54. package/dist/types/components/pagination/PaginationPage.vue.d.ts.map +1 -0
  55. package/dist/types/components/pagination/PaginationPages.vue.d.ts +27 -0
  56. package/dist/types/components/pagination/PaginationPages.vue.d.ts.map +1 -0
  57. package/dist/types/components/pagination/PaginationPopup.vue.d.ts +8 -0
  58. package/dist/types/components/pagination/PaginationPopup.vue.d.ts.map +1 -0
  59. package/dist/types/components/pagination/PaginationSize.vue.d.ts +7 -0
  60. package/dist/types/components/pagination/PaginationSize.vue.d.ts.map +1 -0
  61. package/dist/types/components/pagination/composables/usePagination.d.ts +25 -0
  62. package/dist/types/components/pagination/composables/usePagination.d.ts.map +1 -0
  63. package/dist/types/components/pagination/composables/usePaginationEllipsis.d.ts +34 -0
  64. package/dist/types/components/pagination/composables/usePaginationEllipsis.d.ts.map +1 -0
  65. package/dist/types/components/pagination/composables/usePaginationJump.d.ts +8 -0
  66. package/dist/types/components/pagination/composables/usePaginationJump.d.ts.map +1 -0
  67. package/dist/types/components/pagination/composables/usePaginationPageTooltip.d.ts +3 -0
  68. package/dist/types/components/pagination/composables/usePaginationPageTooltip.d.ts.map +1 -0
  69. package/dist/types/components/pagination/composables/usePaginationWindow.d.ts +188 -0
  70. package/dist/types/components/pagination/composables/usePaginationWindow.d.ts.map +1 -0
  71. package/dist/types/components/pagination/context.d.ts +19 -0
  72. package/dist/types/components/pagination/context.d.ts.map +1 -0
  73. package/dist/types/components/pagination/pagination.variants.d.ts +68 -0
  74. package/dist/types/components/pagination/pagination.variants.d.ts.map +1 -0
  75. package/dist/types/components/pagination/types.d.ts +23 -0
  76. package/dist/types/components/pagination/types.d.ts.map +1 -0
  77. package/dist/types/components/pagination/utils/ranges.d.ts +3 -0
  78. package/dist/types/components/pagination/utils/ranges.d.ts.map +1 -0
  79. package/dist/types/components/range-slider/RangeSlider.vue.d.ts +1 -0
  80. package/dist/types/components/range-slider/RangeSlider.vue.d.ts.map +1 -1
  81. package/dist/types/components/slider/Slider.vue.d.ts +1 -0
  82. package/dist/types/components/slider/Slider.vue.d.ts.map +1 -1
  83. package/dist/types/components/slider/slider.variants.d.ts.map +1 -1
  84. package/dist/types/components/tooltip/Tooltip.vue.d.ts +4 -15
  85. package/dist/types/components/tooltip/Tooltip.vue.d.ts.map +1 -1
  86. package/dist/types/components/tooltip/TooltipBubble.vue.d.ts +20 -0
  87. package/dist/types/components/tooltip/TooltipBubble.vue.d.ts.map +1 -0
  88. package/dist/types/components/tooltip/TooltipDirectiveRoot.d.ts +33 -0
  89. package/dist/types/components/tooltip/TooltipDirectiveRoot.d.ts.map +1 -0
  90. package/dist/types/components/tooltip/composables/useTooltipTarget.d.ts +2 -0
  91. package/dist/types/components/tooltip/composables/useTooltipTarget.d.ts.map +1 -0
  92. package/dist/types/components/tooltip/directive.d.ts +4 -0
  93. package/dist/types/components/tooltip/directive.d.ts.map +1 -0
  94. package/dist/types/components/tooltip/types.d.ts +14 -0
  95. package/dist/types/components/tooltip/types.d.ts.map +1 -0
  96. package/dist/types/components/tooltip/utils/directiveContext.d.ts +10 -0
  97. package/dist/types/components/tooltip/utils/directiveContext.d.ts.map +1 -0
  98. package/dist/types/components/tree-select/TreeSelect.vue.d.ts +6 -1
  99. package/dist/types/components/tree-select/TreeSelect.vue.d.ts.map +1 -1
  100. package/dist/types/components/tree-select/TreeSelectSearch.vue.d.ts +22 -0
  101. package/dist/types/components/tree-select/TreeSelectSearch.vue.d.ts.map +1 -0
  102. package/dist/types/components/tree-select/composables/useTreeSelect.d.ts +35 -0
  103. package/dist/types/components/tree-select/composables/useTreeSelect.d.ts.map +1 -0
  104. package/dist/types/components/tree-select/tree-select.variants.d.ts +1 -1
  105. package/dist/types/components/tree-select/tree-select.variants.d.ts.map +1 -1
  106. package/dist/types/components/tree-select/utils/filterTree.d.ts +7 -0
  107. package/dist/types/components/tree-select/utils/filterTree.d.ts.map +1 -0
  108. package/dist/types/index.d.ts +9 -1
  109. package/dist/types/index.d.ts.map +1 -1
  110. package/dist/types/lib/overlay-positioner.d.ts +3 -0
  111. package/dist/types/lib/overlay-positioner.d.ts.map +1 -0
  112. package/dist/types/lib/pointer-corridor.d.ts +7 -0
  113. package/dist/types/lib/pointer-corridor.d.ts.map +1 -0
  114. package/dist/types/lib/useDirection.d.ts +7 -0
  115. package/dist/types/lib/useDirection.d.ts.map +1 -0
  116. package/dist/types/locale/en-US.d.ts.map +1 -1
  117. package/dist/types/locale/index.d.ts.map +1 -1
  118. package/dist/types/locale/types.d.ts +13 -0
  119. package/dist/types/locale/types.d.ts.map +1 -1
  120. package/dist/types/locale/zh-CN.d.ts.map +1 -1
  121. package/package.json +1 -1
  122. package/src/components/badge/badge.variants.ts +1 -1
  123. package/src/components/hover-card/HoverCard.vue +34 -16
  124. package/src/components/hover-card/HoverCardAnchor.vue +16 -0
  125. package/src/components/hover-card/composables/useHoverCardAnchor.ts +160 -0
  126. package/src/components/image/Image.vue +2 -0
  127. package/src/components/image/composables/useImageGroupState.ts +9 -1
  128. package/src/components/image/image.variants.ts +1 -1
  129. package/src/components/lightbox/Lightbox.vue +23 -17
  130. package/src/components/lightbox/LightboxChrome.vue +7 -1
  131. package/src/components/lightbox/LightboxToolbar.vue +16 -2
  132. package/src/components/lightbox/composables/useLightboxFrames.ts +108 -12
  133. package/src/components/lightbox/composables/useLightboxLarge.ts +4 -1
  134. package/src/components/lightbox/composables/useLightboxLayout.ts +44 -0
  135. package/src/components/lightbox/composables/useLightboxPhase.ts +12 -4
  136. package/src/components/lightbox/composables/useLightboxPose.ts +17 -19
  137. package/src/components/lightbox/composables/useLightboxRotation.ts +14 -4
  138. package/src/components/lightbox/composables/useLightboxZoom.ts +51 -9
  139. package/src/components/lightbox/lightbox.variants.ts +5 -0
  140. package/src/components/lightbox/types.ts +5 -1
  141. package/src/components/lightbox/utils/pose.ts +13 -6
  142. package/src/components/lightbox/utils/source.ts +17 -0
  143. package/src/components/lightbox/utils/zoom.ts +21 -7
  144. package/src/components/pagination/Pagination.vue +118 -0
  145. package/src/components/pagination/PaginationContent.vue +29 -0
  146. package/src/components/pagination/PaginationControl.vue +42 -0
  147. package/src/components/pagination/PaginationEllipsis.vue +67 -0
  148. package/src/components/pagination/PaginationInfo.vue +18 -0
  149. package/src/components/pagination/PaginationJump.vue +32 -0
  150. package/src/components/pagination/PaginationPage.vue +34 -0
  151. package/src/components/pagination/PaginationPages.vue +43 -0
  152. package/src/components/pagination/PaginationPopup.vue +92 -0
  153. package/src/components/pagination/PaginationSize.vue +30 -0
  154. package/src/components/pagination/composables/usePagination.ts +76 -0
  155. package/src/components/pagination/composables/usePaginationEllipsis.ts +269 -0
  156. package/src/components/pagination/composables/usePaginationJump.ts +23 -0
  157. package/src/components/pagination/composables/usePaginationPageTooltip.ts +23 -0
  158. package/src/components/pagination/composables/usePaginationWindow.ts +116 -0
  159. package/src/components/pagination/context.ts +23 -0
  160. package/src/components/pagination/pagination.variants.ts +32 -0
  161. package/src/components/pagination/types.ts +19 -0
  162. package/src/components/pagination/utils/ranges.ts +12 -0
  163. package/src/components/range-slider/RangeSlider.vue +6 -0
  164. package/src/components/slider/Slider.vue +6 -0
  165. package/src/components/slider/slider.variants.ts +1 -0
  166. package/src/components/tooltip/Tooltip.vue +10 -33
  167. package/src/components/tooltip/TooltipBubble.vue +36 -0
  168. package/src/components/tooltip/TooltipDirectiveRoot.ts +61 -0
  169. package/src/components/tooltip/composables/useTooltipTarget.ts +56 -0
  170. package/src/components/tooltip/directive.ts +54 -0
  171. package/src/components/tooltip/types.ts +15 -0
  172. package/src/components/tooltip/utils/directiveContext.ts +32 -0
  173. package/src/components/tree-select/TreeSelect.vue +90 -61
  174. package/src/components/tree-select/TreeSelectSearch.vue +48 -0
  175. package/src/components/tree-select/composables/useTreeSelect.ts +130 -0
  176. package/src/components/tree-select/tree-select.variants.ts +2 -2
  177. package/src/components/tree-select/utils/filterTree.ts +19 -0
  178. package/src/index.ts +9 -1
  179. package/src/lib/overlay-positioner.ts +25 -0
  180. package/src/lib/pointer-corridor.ts +26 -0
  181. package/src/lib/useDirection.ts +31 -0
  182. package/src/locale/en-US.ts +15 -1
  183. package/src/locale/index.ts +1 -0
  184. package/src/locale/types.ts +13 -0
  185. package/src/locale/zh-CN.ts +15 -1
@@ -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,8 @@ 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'
209
217
  export { default as Popover } from './components/popover/Popover.vue'
210
218
  export { default as DropdownMenu } from './components/dropdown-menu/DropdownMenu.vue'
211
219
  export { default as DropdownMenuItem } from './components/dropdown-menu/DropdownMenuItem.vue'
@@ -228,7 +236,7 @@ export { default as Skeleton } from './components/skeleton/Skeleton.vue'
228
236
  export { default as Image } from './components/image/Image.vue'
229
237
  export { default as ImageGroup } from './components/image/ImageGroup.vue'
230
238
  export { default as Lightbox } from './components/lightbox/Lightbox.vue'
231
- export type { LightboxItem } from './components/lightbox/types'
239
+ export type { LightboxItem, LightboxSource } from './components/lightbox/types'
232
240
  export { provideImageResolver, useImageResolver } from './components/image/resolver'
233
241
  export type { ImageResolver } from './components/image/resolver'
234
242
  export type { ImageVariants } from './components/image/image.variants'
@@ -0,0 +1,25 @@
1
+ import {
2
+ toValue,
3
+ watchEffect,
4
+ type ComponentPublicInstance,
5
+ type MaybeRefOrGetter,
6
+ type Ref,
7
+ } from 'vue'
8
+
9
+ export function useOverlayPositionerClass(
10
+ content: Ref<ComponentPublicInstance | null>,
11
+ className: MaybeRefOrGetter<string | undefined>,
12
+ ) {
13
+ watchEffect(
14
+ onCleanup => {
15
+ const element = content.value?.$el as HTMLElement | undefined
16
+ const positioner = element?.parentElement
17
+ const classes = toValue(className)?.split(/\s+/).filter(Boolean) ?? []
18
+ if (!positioner?.hasAttribute('data-reka-popper-content-wrapper') || !classes.length) return
19
+ const added = classes.filter(name => !positioner.classList.contains(name))
20
+ positioner.classList.add(...added)
21
+ onCleanup(() => positioner.classList.remove(...added))
22
+ },
23
+ { flush: 'post' },
24
+ )
25
+ }
@@ -0,0 +1,26 @@
1
+ type Point = { x: number; y: number }
2
+ const cross = (a: Point, b: Point, c: Point) =>
3
+ (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
4
+
5
+ export function inPointerCorridor(point: Point, first: DOMRect, second: DOMRect) {
6
+ const points = [first, second]
7
+ .flatMap(rect => [
8
+ { x: rect.left - 6, y: rect.top - 6 },
9
+ { x: rect.right + 6, y: rect.top - 6 },
10
+ { x: rect.left - 6, y: rect.bottom + 6 },
11
+ { x: rect.right + 6, y: rect.bottom + 6 },
12
+ ])
13
+ .sort((a, b) => a.x - b.x || a.y - b.y)
14
+ const half = (values: Point[]) => {
15
+ const hull: Point[] = []
16
+ for (const value of values) {
17
+ while (hull.length > 1 && cross(hull[hull.length - 2]!, hull[hull.length - 1]!, value) <= 0)
18
+ hull.pop()
19
+ hull.push(value)
20
+ }
21
+ hull.pop()
22
+ return hull
23
+ }
24
+ const hull = [...half(points), ...half([...points].reverse())]
25
+ return hull.every((a, i) => cross(a, hull[(i + 1) % hull.length]!, point) >= 0)
26
+ }
@@ -0,0 +1,31 @@
1
+ import { computed, shallowRef, watchEffect } from 'vue'
2
+ import { injectConfigProviderContext, type Direction } from 'reka-ui'
3
+
4
+ export function useDirection(dir: () => Direction | undefined) {
5
+ const root = shallowRef<HTMLElement>()
6
+ const context = injectConfigProviderContext(null)
7
+ const inherited = shallowRef<Direction>('ltr')
8
+ const rootDirection = computed(() => dir() ?? context?.dir?.value)
9
+ const direction = computed(() => rootDirection.value ?? inherited.value)
10
+
11
+ watchEffect(
12
+ onCleanup => {
13
+ const element = root.value
14
+ const view = element?.ownerDocument.defaultView
15
+ if (!element || !view || rootDirection.value) return
16
+
17
+ const update = () => {
18
+ inherited.value = view.getComputedStyle(element).direction === 'rtl' ? 'rtl' : 'ltr'
19
+ }
20
+
21
+ update()
22
+ const observer = new view.MutationObserver(update)
23
+ for (let ancestor: HTMLElement | null = element; ancestor; ancestor = ancestor.parentElement)
24
+ observer.observe(ancestor, { attributes: true, attributeFilter: ['dir'] })
25
+ onCleanup(() => observer.disconnect())
26
+ },
27
+ { flush: 'post' },
28
+ )
29
+
30
+ return { root, direction, rootDirection }
31
+ }
@@ -16,6 +16,16 @@ export const enUS: UiMessages = {
16
16
  next: 'Next notice',
17
17
  },
18
18
  pagination: {
19
+ rangeLabel: (from, to, total) =>
20
+ total ? String(from) + '–' + to + ' of ' + total + ' items' : '0 items',
21
+ pageCountLabel: (page, count) => 'Page ' + page + ' of ' + count,
22
+ pageSizeLabel: 'Items per page',
23
+ pageSizeOption: n => String(n) + ' / page',
24
+ jumpLabel: 'Go to page',
25
+ previousPagesLabel: n => 'Go back ' + n + ' pages',
26
+ nextPagesLabel: n => 'Go forward ' + n + ' pages',
27
+ choosePage: 'Choose a page',
28
+ choosePageHint: 'Press ArrowDown or ArrowUp to choose a page',
19
29
  navLabel: 'Pagination',
20
30
  prev: 'Previous page',
21
31
  next: 'Next page',
@@ -78,7 +88,8 @@ export const enUS: UiMessages = {
78
88
  lightbox: {
79
89
  zoomIn: 'Zoom in',
80
90
  zoomOut: 'Zoom out',
81
- resetZoom: 'Reset zoom',
91
+ resetZoom: 'Fit to window',
92
+ actualSize: 'Actual size',
82
93
  rotate: 'Rotate',
83
94
  download: 'Download',
84
95
  loadingLarge: 'Loading HD',
@@ -94,6 +105,9 @@ export const enUS: UiMessages = {
94
105
  show: 'Show password',
95
106
  hide: 'Hide password',
96
107
  },
108
+ treeSelect: {
109
+ search: 'Search nodes',
110
+ },
97
111
  combobox: {
98
112
  placeholder: 'Type or choose',
99
113
  toggle: 'Show options',
@@ -36,6 +36,7 @@ function merge(base: UiMessages, patch: PartialUiMessages): UiMessages {
36
36
  chip: { ...base.chip, ...patch.chip },
37
37
  numberInput: { ...base.numberInput, ...patch.numberInput },
38
38
  passwordInput: { ...base.passwordInput, ...patch.passwordInput },
39
+ treeSelect: { ...base.treeSelect, ...patch.treeSelect },
39
40
  combobox: { ...base.combobox, ...patch.combobox },
40
41
  slider: { ...base.slider, ...patch.slider },
41
42
  pinInput: { ...base.pinInput, ...patch.pinInput },
@@ -14,6 +14,15 @@ export interface UiMessages {
14
14
  next: string
15
15
  }
16
16
  pagination: {
17
+ rangeLabel: (from: number, to: number, total: number) => string
18
+ pageCountLabel: (page: number, count: number) => string
19
+ pageSizeLabel: string
20
+ pageSizeOption: (n: number) => string
21
+ jumpLabel: string
22
+ previousPagesLabel: (n: number) => string
23
+ nextPagesLabel: (n: number) => string
24
+ choosePage: string
25
+ choosePageHint: string
17
26
  navLabel: string
18
27
  prev: string
19
28
  next: string
@@ -77,6 +86,7 @@ export interface UiMessages {
77
86
  zoomIn: string
78
87
  zoomOut: string
79
88
  resetZoom: string
89
+ actualSize: string
80
90
  rotate: string
81
91
  download: string
82
92
  loadingLarge: string
@@ -92,6 +102,9 @@ export interface UiMessages {
92
102
  show: string
93
103
  hide: string
94
104
  }
105
+ treeSelect: {
106
+ search: string
107
+ }
95
108
  combobox: {
96
109
  placeholder: string
97
110
  toggle: string
@@ -16,6 +16,16 @@ export const zhCN: UiMessages = {
16
16
  next: '下一条',
17
17
  },
18
18
  pagination: {
19
+ rangeLabel: (from, to, total) =>
20
+ total ? String(from) + '–' + to + ' / 共 ' + total + ' 条' : '共 0 条',
21
+ pageCountLabel: (page, count) => '第 ' + page + ' / ' + count + ' 页',
22
+ pageSizeLabel: '每页条数',
23
+ pageSizeOption: n => String(n) + ' / 页',
24
+ jumpLabel: '跳至页码',
25
+ previousPagesLabel: n => '向前跳 ' + n + ' 页',
26
+ nextPagesLabel: n => '向后跳 ' + n + ' 页',
27
+ choosePage: '选择页码',
28
+ choosePageHint: '按向下或向上箭头选择页码',
19
29
  navLabel: '翻页',
20
30
  prev: '上一页',
21
31
  next: '下一页',
@@ -78,7 +88,8 @@ export const zhCN: UiMessages = {
78
88
  lightbox: {
79
89
  zoomIn: '放大',
80
90
  zoomOut: '缩小',
81
- resetZoom: '恢复原始大小',
91
+ resetZoom: '适应窗口',
92
+ actualSize: '原始尺寸',
82
93
  rotate: '旋转',
83
94
  download: '下载',
84
95
  loadingLarge: '正在加载原图',
@@ -94,6 +105,9 @@ export const zhCN: UiMessages = {
94
105
  show: '显示密码',
95
106
  hide: '隐藏密码',
96
107
  },
108
+ treeSelect: {
109
+ search: '搜索节点',
110
+ },
97
111
  combobox: {
98
112
  placeholder: '输入或选择',
99
113
  toggle: '展开选项',