@hina-ui/vue 1.4.0 → 1.6.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 (107) hide show
  1. package/dist/index.js +1222 -599
  2. package/dist/index.js.map +1 -1
  3. package/dist/types/components/app-shell/AppShell.vue.d.ts +1 -0
  4. package/dist/types/components/app-shell/AppShell.vue.d.ts.map +1 -1
  5. package/dist/types/components/combobox/Combobox.vue.d.ts +2 -1
  6. package/dist/types/components/combobox/Combobox.vue.d.ts.map +1 -1
  7. package/dist/types/components/combobox/composables/useCombobox.d.ts +25 -0
  8. package/dist/types/components/combobox/composables/useCombobox.d.ts.map +1 -0
  9. package/dist/types/components/combobox/composables/useOptionLabels.d.ts +3 -0
  10. package/dist/types/components/combobox/composables/useOptionLabels.d.ts.map +1 -0
  11. package/dist/types/components/dialog/Dialog.vue.d.ts +3 -1
  12. package/dist/types/components/dialog/Dialog.vue.d.ts.map +1 -1
  13. package/dist/types/components/drawer/Drawer.vue.d.ts +21 -14
  14. package/dist/types/components/drawer/Drawer.vue.d.ts.map +1 -1
  15. package/dist/types/components/drawer/drawer.variants.d.ts +10 -1
  16. package/dist/types/components/drawer/drawer.variants.d.ts.map +1 -1
  17. package/dist/types/components/dropdown-menu/DropdownMenu.vue.d.ts +4 -1
  18. package/dist/types/components/dropdown-menu/DropdownMenu.vue.d.ts.map +1 -1
  19. package/dist/types/components/hover-card/HoverCard.vue.d.ts +4 -1
  20. package/dist/types/components/hover-card/HoverCard.vue.d.ts.map +1 -1
  21. package/dist/types/components/hover-card/HoverCardAnchor.vue.d.ts +6 -4
  22. package/dist/types/components/hover-card/HoverCardAnchor.vue.d.ts.map +1 -1
  23. package/dist/types/components/hover-card/composables/useHoverCardAnchor.d.ts +9 -4
  24. package/dist/types/components/hover-card/composables/useHoverCardAnchor.d.ts.map +1 -1
  25. package/dist/types/components/multi-combobox/MultiCombobox.vue.d.ts +1 -0
  26. package/dist/types/components/multi-combobox/MultiCombobox.vue.d.ts.map +1 -1
  27. package/dist/types/components/multi-combobox/composables/useMultiCombobox.d.ts +2 -1
  28. package/dist/types/components/multi-combobox/composables/useMultiCombobox.d.ts.map +1 -1
  29. package/dist/types/components/nav-link/NavLink.vue.d.ts.map +1 -1
  30. package/dist/types/components/nav-link/nav-link.variants.d.ts +1 -18
  31. package/dist/types/components/nav-link/nav-link.variants.d.ts.map +1 -1
  32. package/dist/types/components/popover/Popover.vue.d.ts +4 -1
  33. package/dist/types/components/popover/Popover.vue.d.ts.map +1 -1
  34. package/dist/types/components/sheet/Sheet.vue.d.ts +21 -14
  35. package/dist/types/components/sheet/Sheet.vue.d.ts.map +1 -1
  36. package/dist/types/components/sheet/sheet.variants.d.ts +54 -3
  37. package/dist/types/components/sheet/sheet.variants.d.ts.map +1 -1
  38. package/dist/types/components/sidebar/Sidebar.vue.d.ts +15 -11
  39. package/dist/types/components/sidebar/Sidebar.vue.d.ts.map +1 -1
  40. package/dist/types/components/sidebar/SidebarGroup.vue.d.ts.map +1 -1
  41. package/dist/types/components/sidebar/SidebarLabel.vue.d.ts +20 -0
  42. package/dist/types/components/sidebar/SidebarLabel.vue.d.ts.map +1 -0
  43. package/dist/types/components/sidebar/sidebar.variants.d.ts +46 -0
  44. package/dist/types/components/sidebar/sidebar.variants.d.ts.map +1 -0
  45. package/dist/types/components/tree/Tree.vue.d.ts +37 -0
  46. package/dist/types/components/tree/Tree.vue.d.ts.map +1 -0
  47. package/dist/types/components/tree/TreeCheck.vue.d.ts +8 -0
  48. package/dist/types/components/tree/TreeCheck.vue.d.ts.map +1 -0
  49. package/dist/types/components/tree/composables/useTree.d.ts +23 -0
  50. package/dist/types/components/tree/composables/useTree.d.ts.map +1 -0
  51. package/dist/types/components/tree/tree.variants.d.ts +5 -0
  52. package/dist/types/components/tree/tree.variants.d.ts.map +1 -0
  53. package/dist/types/components/tree/types.d.ts +18 -0
  54. package/dist/types/components/tree/types.d.ts.map +1 -0
  55. package/dist/types/components/tree/utils/selection.d.ts +16 -0
  56. package/dist/types/components/tree/utils/selection.d.ts.map +1 -0
  57. package/dist/types/index.d.ts +4 -0
  58. package/dist/types/index.d.ts.map +1 -1
  59. package/dist/types/lib/anchored-overlay.d.ts +12 -2
  60. package/dist/types/lib/anchored-overlay.d.ts.map +1 -1
  61. package/dist/types/lib/overlay-anchor.d.ts +16 -0
  62. package/dist/types/lib/overlay-anchor.d.ts.map +1 -0
  63. package/dist/types/lib/scroll-viewport.d.ts +9 -0
  64. package/dist/types/lib/scroll-viewport.d.ts.map +1 -0
  65. package/dist/types/locale/en-US.d.ts.map +1 -1
  66. package/dist/types/locale/index.d.ts.map +1 -1
  67. package/dist/types/locale/types.d.ts +3 -0
  68. package/dist/types/locale/types.d.ts.map +1 -1
  69. package/dist/types/locale/zh-CN.d.ts.map +1 -1
  70. package/package.json +1 -1
  71. package/src/components/app-shell/AppShell.vue +2 -1
  72. package/src/components/combobox/Combobox.vue +14 -23
  73. package/src/components/combobox/composables/useCombobox.ts +63 -0
  74. package/src/components/combobox/composables/useOptionLabels.ts +20 -0
  75. package/src/components/dialog/Dialog.vue +5 -1
  76. package/src/components/drawer/Drawer.vue +69 -32
  77. package/src/components/drawer/drawer.variants.ts +6 -2
  78. package/src/components/dropdown-menu/DropdownMenu.vue +18 -5
  79. package/src/components/hover-card/HoverCard.vue +6 -1
  80. package/src/components/hover-card/HoverCardAnchor.vue +3 -1
  81. package/src/components/hover-card/composables/useHoverCardAnchor.ts +33 -42
  82. package/src/components/multi-combobox/MultiCombobox.vue +2 -0
  83. package/src/components/multi-combobox/composables/useMultiCombobox.ts +5 -10
  84. package/src/components/nav-link/NavLink.vue +7 -1
  85. package/src/components/nav-link/nav-link.variants.ts +1 -10
  86. package/src/components/popover/Popover.vue +17 -7
  87. package/src/components/sheet/Sheet.vue +67 -24
  88. package/src/components/sheet/sheet.variants.ts +17 -4
  89. package/src/components/sidebar/Sidebar.vue +43 -33
  90. package/src/components/sidebar/SidebarGroup.vue +12 -11
  91. package/src/components/sidebar/SidebarLabel.vue +24 -0
  92. package/src/components/sidebar/sidebar.variants.ts +36 -0
  93. package/src/components/tree/Tree.vue +132 -0
  94. package/src/components/tree/TreeCheck.vue +33 -0
  95. package/src/components/tree/composables/useTree.ts +81 -0
  96. package/src/components/tree/tree.variants.ts +22 -0
  97. package/src/components/tree/types.ts +20 -0
  98. package/src/components/tree/utils/selection.ts +78 -0
  99. package/src/index.ts +4 -0
  100. package/src/lib/anchored-overlay.ts +28 -19
  101. package/src/lib/overlay-anchor.ts +65 -0
  102. package/src/lib/scroll-viewport.ts +8 -0
  103. package/src/locale/en-US.ts +1 -0
  104. package/src/locale/index.ts +1 -0
  105. package/src/locale/types.ts +3 -0
  106. package/src/locale/zh-CN.ts +1 -0
  107. package/src/styles/motion.css +40 -0
@@ -0,0 +1,20 @@
1
+ export type TreeValue = string | number
2
+
3
+ export interface TreeNode {
4
+ value: TreeValue
5
+ label: string
6
+ description?: string
7
+ disabled?: boolean
8
+ children?: TreeNode[]
9
+ }
10
+
11
+ export interface TreeNodeState {
12
+ selected: boolean
13
+ indeterminate: boolean
14
+ disabled: boolean
15
+ }
16
+
17
+ export interface TreeNodeSlot extends TreeNodeState {
18
+ node: TreeNode
19
+ expanded: boolean
20
+ }
@@ -0,0 +1,78 @@
1
+ import type { TreeNode, TreeNodeState, TreeValue } from '../types'
2
+
3
+ interface Entry {
4
+ node: TreeNode
5
+ parent?: TreeValue
6
+ children: TreeValue[]
7
+ disabled: boolean
8
+ }
9
+
10
+ export interface TreeIndex {
11
+ roots: TreeValue[]
12
+ entries: Map<TreeValue, Entry>
13
+ }
14
+
15
+ export function indexTree(nodes: TreeNode[]): TreeIndex {
16
+ const entries = new Map<TreeValue, Entry>()
17
+ function visit(node: TreeNode, parent?: TreeValue, disabled = false) {
18
+ const entry = {
19
+ node,
20
+ parent,
21
+ disabled: disabled || !!node.disabled,
22
+ children: node.children?.map(child => child.value) ?? [],
23
+ }
24
+ entries.set(node.value, entry)
25
+ node.children?.forEach(child => visit(child, node.value, entry.disabled))
26
+ }
27
+ nodes.forEach(node => visit(node))
28
+ return { roots: nodes.map(node => node.value), entries }
29
+ }
30
+
31
+ export function checkedState(tree: TreeIndex, values: TreeValue[]) {
32
+ const checked = new Set(values)
33
+ const states = new Map<TreeValue, TreeNodeState>()
34
+ function visit(value: TreeValue, inherited = false): TreeNodeState {
35
+ const entry = tree.entries.get(value)!
36
+ const own = checked.has(value) || (!entry.disabled && inherited)
37
+ const children = entry.children.map(child => visit(child, !entry.disabled && own))
38
+ const enabled = children.filter(child => !child.disabled)
39
+ const selected =
40
+ entry.disabled || enabled.length === 0 ? own : enabled.every(child => child.selected)
41
+ const indeterminate =
42
+ !entry.disabled && !selected && enabled.some(child => child.selected || child.indeterminate)
43
+ const state = { selected, indeterminate, disabled: entry.disabled }
44
+ states.set(value, state)
45
+ return state
46
+ }
47
+ tree.roots.forEach(value => visit(value))
48
+ return states
49
+ }
50
+
51
+ export function toggleChecked(tree: TreeIndex, values: TreeValue[], value: TreeValue): TreeValue[] {
52
+ const entry = tree.entries.get(value)
53
+ if (!entry || entry.disabled) return values
54
+ const states = checkedState(tree, values)
55
+ const checked = new Set(values.filter(value => !tree.entries.has(value)))
56
+ states.forEach((state, value) => {
57
+ if (state.selected) checked.add(value)
58
+ })
59
+ const selected = !states.get(value)!.selected
60
+ function visit(value: TreeValue) {
61
+ const entry = tree.entries.get(value)!
62
+ if (entry.disabled) return
63
+ if (selected) checked.add(value)
64
+ else checked.delete(value)
65
+ entry.children.forEach(visit)
66
+ }
67
+ visit(value)
68
+ let parent = entry.parent
69
+ while (parent !== undefined) {
70
+ checked.delete(parent)
71
+ parent = tree.entries.get(parent)!.parent
72
+ }
73
+ const next = checkedState(tree, [...checked])
74
+ return [
75
+ ...[...checked].filter(value => !tree.entries.has(value)),
76
+ ...[...tree.entries.keys()].filter(value => next.get(value)!.selected),
77
+ ]
78
+ }
package/src/index.ts CHANGED
@@ -62,6 +62,8 @@ export { default as CommandPalette } from './components/command-palette/CommandP
62
62
  export type { CommandItem, CommandGroup, CommandItems } from './components/command-palette/types'
63
63
  export { default as Listbox } from './components/listbox/Listbox.vue'
64
64
  export type { ListboxVariants } from './components/listbox/listbox.variants'
65
+ export { default as Tree } from './components/tree/Tree.vue'
66
+ export type { TreeNode, TreeNodeSlot, TreeValue } from './components/tree/types'
65
67
  export { default as TreeSelect } from './components/tree-select/TreeSelect.vue'
66
68
  export type { TreeSelectNode } from './components/tree-select/types'
67
69
  export { default as Checkbox } from './components/checkbox/Checkbox.vue'
@@ -197,6 +199,7 @@ export { default as AccordionTrigger } from './components/accordion/AccordionTri
197
199
  export { default as AccordionContent } from './components/accordion/AccordionContent.vue'
198
200
  export { default as NavLink } from './components/nav-link/NavLink.vue'
199
201
  export { default as Sidebar } from './components/sidebar/Sidebar.vue'
202
+ export { default as SidebarLabel } from './components/sidebar/SidebarLabel.vue'
200
203
  export { default as SidebarGroup } from './components/sidebar/SidebarGroup.vue'
201
204
  export { default as AppShell } from './components/app-shell/AppShell.vue'
202
205
  export { default as Page } from './components/page/Page.vue'
@@ -214,6 +217,7 @@ export { default as TooltipProvider } from './components/tooltip/TooltipProvider
214
217
  export { default as Tooltip } from './components/tooltip/Tooltip.vue'
215
218
  export { vTooltip } from './components/tooltip/directive'
216
219
  export type { TooltipDirectiveOptions, TooltipDirectiveValue } from './components/tooltip/types'
220
+ export type { OverlayAnchor, OverlayPositionStrategy } from './lib/overlay-anchor'
217
221
  export { default as Popover } from './components/popover/Popover.vue'
218
222
  export { default as DropdownMenu } from './components/dropdown-menu/DropdownMenu.vue'
219
223
  export { default as DropdownMenuItem } from './components/dropdown-menu/DropdownMenuItem.vue'
@@ -1,8 +1,21 @@
1
- import { computed, nextTick, shallowRef, watch, type Ref } from 'vue'
1
+ import { computed, nextTick, watch, type Ref } from 'vue'
2
+ import { defaultDocument } from '@vueuse/core'
3
+ import { useOverlayPortal } from './overlay-portal'
4
+ import {
5
+ overlayAnchorContext,
6
+ overlayAnchorElement,
7
+ useOverlayAnchor,
8
+ type OverlayAnchor,
9
+ type OverlayPositionStrategy,
10
+ } from './overlay-anchor'
2
11
  import { useForwardExpose, type DropdownMenuContentEmits } from 'reka-ui'
3
12
 
4
13
  export function useAnchoredOverlay(
5
- props: { anchor?: HTMLElement | null; modal: boolean },
14
+ props: {
15
+ anchor?: OverlayAnchor | null
16
+ modal: boolean
17
+ updatePositionStrategy: OverlayPositionStrategy
18
+ },
6
19
  open: Ref<boolean | undefined>,
7
20
  emit: <K extends keyof DropdownMenuContentEmits>(
8
21
  event: K,
@@ -11,14 +24,6 @@ export function useAnchoredOverlay(
11
24
  openAutoFocus?: (event: Event) => void,
12
25
  ) {
13
26
  const { forwardRef: trigger, currentElement: triggerElement } = useForwardExpose()
14
- const retainedAnchor = shallowRef<HTMLElement>()
15
- const reference = computed(
16
- () =>
17
- props.anchor ??
18
- (open.value
19
- ? (triggerElement.value ?? retainedAnchor.value)
20
- : (retainedAnchor.value ?? triggerElement.value)),
21
- )
22
27
  const visible = computed({
23
28
  get: () => !!open.value && !!(props.anchor || triggerElement.value),
24
29
  set: value => {
@@ -26,12 +31,12 @@ export function useAnchoredOverlay(
26
31
  },
27
32
  })
28
33
 
29
- watch(
30
- () => props.anchor,
31
- anchor => {
32
- if (anchor) retainedAnchor.value = anchor
33
- },
34
- { immediate: true, flush: 'sync' },
34
+ const { content, present } = useOverlayPortal(visible)
35
+ const reference = useOverlayAnchor(
36
+ () => props.anchor ?? triggerElement.value,
37
+ visible,
38
+ present,
39
+ () => props.updatePositionStrategy,
35
40
  )
36
41
 
37
42
  let previousFocus: HTMLElement | null = null
@@ -50,7 +55,10 @@ export function useAnchoredOverlay(
50
55
  visible,
51
56
  active => {
52
57
  if (!active) return
53
- const document = props.anchor?.ownerDocument ?? triggerElement.value?.ownerDocument
58
+ const document =
59
+ overlayAnchorContext(props.anchor)?.ownerDocument ??
60
+ triggerElement.value?.ownerDocument ??
61
+ defaultDocument
54
62
  previousFocus = document?.activeElement as HTMLElement | null
55
63
  interactedOutside = false
56
64
  },
@@ -73,12 +81,11 @@ export function useAnchoredOverlay(
73
81
  (active === panel.ownerDocument.body || panel.contains(active))
74
82
  )
75
83
  target.focus({ preventScroll: true })
76
- if (!visible.value) retainedAnchor.value = undefined
77
84
  })
78
85
  })
79
86
 
80
87
  const onInteractOutside = once((event: DropdownMenuContentEmits['interactOutside'][0]) => {
81
- if (!triggerElement.value && props.anchor?.contains(event.target as Node))
88
+ if (!triggerElement.value && overlayAnchorElement(props.anchor)?.contains(event.target as Node))
82
89
  event.preventDefault()
83
90
  emit('interactOutside', event)
84
91
  const original = event.detail.originalEvent
@@ -89,6 +96,8 @@ export function useAnchoredOverlay(
89
96
 
90
97
  return {
91
98
  trigger,
99
+ content,
100
+ present,
92
101
  reference,
93
102
  visible,
94
103
  events: {
@@ -0,0 +1,65 @@
1
+ import { shallowRef, watch, type Ref } from 'vue'
2
+
3
+ export type OverlayAnchor =
4
+ | HTMLElement
5
+ | {
6
+ getBoundingClientRect: () => Pick<
7
+ DOMRect,
8
+ 'x' | 'y' | 'width' | 'height' | 'top' | 'right' | 'bottom' | 'left'
9
+ >
10
+ contextElement?: Element
11
+ }
12
+ export type OverlayPositionStrategy = 'optimized' | 'always'
13
+
14
+ export function overlayAnchorElement(anchor?: OverlayAnchor | null): HTMLElement | undefined {
15
+ return anchor && 'nodeType' in anchor ? anchor : undefined
16
+ }
17
+
18
+ export function overlayAnchorContext(anchor?: OverlayAnchor | null): Element | undefined {
19
+ return (
20
+ overlayAnchorElement(anchor) ??
21
+ (anchor && 'contextElement' in anchor ? anchor.contextElement : undefined)
22
+ )
23
+ }
24
+
25
+ export function useOverlayAnchor(
26
+ source: () => OverlayAnchor | null | undefined,
27
+ open: Ref<boolean | undefined>,
28
+ present: Ref<boolean>,
29
+ strategy: () => OverlayPositionStrategy,
30
+ ) {
31
+ const reference = shallowRef<Exclude<OverlayAnchor, HTMLElement>>()
32
+
33
+ let current: OverlayAnchor | undefined
34
+
35
+ watch(
36
+ [source, open, present, strategy],
37
+ ([anchor, active, mounted]) => {
38
+ if (!mounted) {
39
+ current = undefined
40
+ reference.value = undefined
41
+ return
42
+ }
43
+ if (!anchor || (!active && reference.value && anchor !== current)) return
44
+ const contextElement = overlayAnchorContext(anchor)
45
+ if (contextElement && !contextElement.isConnected) return
46
+ current = anchor
47
+ const measure = () => {
48
+ const { x, y, width, height, top, right, bottom, left } = anchor.getBoundingClientRect()
49
+ return { x, y, width, height, top, right, bottom, left }
50
+ }
51
+ let rect = measure()
52
+ reference.value = {
53
+ contextElement,
54
+ getBoundingClientRect: () => {
55
+ if (source() === anchor && (!contextElement || contextElement.isConnected))
56
+ rect = measure()
57
+ return rect
58
+ },
59
+ }
60
+ },
61
+ { immediate: true, flush: 'post' },
62
+ )
63
+
64
+ return reference
65
+ }
@@ -0,0 +1,8 @@
1
+ import { computed, shallowRef } from 'vue'
2
+
3
+ export function useScrollViewport() {
4
+ const scrollArea = shallowRef<{ viewport?: HTMLElement } | null>(null)
5
+ const viewport = computed(() => scrollArea.value?.viewport)
6
+
7
+ return { scrollArea, viewport }
8
+ }
@@ -105,6 +105,7 @@ export const enUS: UiMessages = {
105
105
  show: 'Show password',
106
106
  hide: 'Hide password',
107
107
  },
108
+ tree: { empty: 'No nodes' },
108
109
  treeSelect: {
109
110
  search: 'Search nodes',
110
111
  },
@@ -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
+ tree: { ...base.tree, ...patch.tree },
39
40
  treeSelect: { ...base.treeSelect, ...patch.treeSelect },
40
41
  combobox: { ...base.combobox, ...patch.combobox },
41
42
  slider: { ...base.slider, ...patch.slider },
@@ -102,6 +102,9 @@ export interface UiMessages {
102
102
  show: string
103
103
  hide: string
104
104
  }
105
+ tree: {
106
+ empty: string
107
+ }
105
108
  treeSelect: {
106
109
  search: string
107
110
  }
@@ -105,6 +105,7 @@ export const zhCN: UiMessages = {
105
105
  show: '显示密码',
106
106
  hide: '隐藏密码',
107
107
  },
108
+ tree: { empty: '暂无节点' },
108
109
  treeSelect: {
109
110
  search: '搜索节点',
110
111
  },
@@ -616,3 +616,43 @@
616
616
  }
617
617
  }
618
618
  }
619
+
620
+ @utility hn-sidebar {
621
+ transition:
622
+ width var(--hn-duration-base) var(--hn-ease-move),
623
+ border-color var(--hn-duration-base) var(--hn-ease-move),
624
+ visibility 0s;
625
+
626
+ &[data-state='hidden'] {
627
+ visibility: hidden;
628
+ transition:
629
+ width var(--hn-duration-base) var(--hn-ease-move),
630
+ border-color var(--hn-duration-base) var(--hn-ease-move),
631
+ visibility 0s var(--hn-duration-base);
632
+ }
633
+
634
+ @media (prefers-reduced-motion: reduce) {
635
+ transition: none;
636
+ }
637
+ }
638
+
639
+ @utility hn-sidebar-label {
640
+ opacity: 1;
641
+ visibility: visible;
642
+ transition:
643
+ opacity var(--hn-duration-fast) var(--hn-ease-enter) var(--hn-duration-fast),
644
+ visibility 0s var(--hn-duration-fast);
645
+
646
+ &[data-collapsed] {
647
+ opacity: 0;
648
+ visibility: hidden;
649
+ pointer-events: none;
650
+ transition:
651
+ opacity calc(var(--hn-duration-fast) / 2) var(--hn-ease-exit),
652
+ visibility 0s calc(var(--hn-duration-fast) / 2);
653
+ }
654
+
655
+ @media (prefers-reduced-motion: reduce) {
656
+ transition: none;
657
+ }
658
+ }