@hina-ui/vue 1.5.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.
- package/dist/index.js +831 -390
- package/dist/index.js.map +1 -1
- package/dist/types/components/app-shell/AppShell.vue.d.ts +1 -0
- package/dist/types/components/app-shell/AppShell.vue.d.ts.map +1 -1
- package/dist/types/components/combobox/Combobox.vue.d.ts +2 -1
- package/dist/types/components/combobox/Combobox.vue.d.ts.map +1 -1
- package/dist/types/components/combobox/composables/useCombobox.d.ts +25 -0
- package/dist/types/components/combobox/composables/useCombobox.d.ts.map +1 -0
- package/dist/types/components/combobox/composables/useOptionLabels.d.ts +3 -0
- package/dist/types/components/combobox/composables/useOptionLabels.d.ts.map +1 -0
- package/dist/types/components/multi-combobox/MultiCombobox.vue.d.ts +1 -0
- package/dist/types/components/multi-combobox/MultiCombobox.vue.d.ts.map +1 -1
- package/dist/types/components/multi-combobox/composables/useMultiCombobox.d.ts +2 -1
- package/dist/types/components/multi-combobox/composables/useMultiCombobox.d.ts.map +1 -1
- package/dist/types/components/nav-link/NavLink.vue.d.ts.map +1 -1
- package/dist/types/components/nav-link/nav-link.variants.d.ts +1 -18
- package/dist/types/components/nav-link/nav-link.variants.d.ts.map +1 -1
- package/dist/types/components/sidebar/Sidebar.vue.d.ts +15 -11
- package/dist/types/components/sidebar/Sidebar.vue.d.ts.map +1 -1
- package/dist/types/components/sidebar/SidebarGroup.vue.d.ts.map +1 -1
- package/dist/types/components/sidebar/SidebarLabel.vue.d.ts +20 -0
- package/dist/types/components/sidebar/SidebarLabel.vue.d.ts.map +1 -0
- package/dist/types/components/sidebar/sidebar.variants.d.ts +46 -0
- package/dist/types/components/sidebar/sidebar.variants.d.ts.map +1 -0
- package/dist/types/components/tree/Tree.vue.d.ts +37 -0
- package/dist/types/components/tree/Tree.vue.d.ts.map +1 -0
- package/dist/types/components/tree/TreeCheck.vue.d.ts +8 -0
- package/dist/types/components/tree/TreeCheck.vue.d.ts.map +1 -0
- package/dist/types/components/tree/composables/useTree.d.ts +23 -0
- package/dist/types/components/tree/composables/useTree.d.ts.map +1 -0
- package/dist/types/components/tree/tree.variants.d.ts +5 -0
- package/dist/types/components/tree/tree.variants.d.ts.map +1 -0
- package/dist/types/components/tree/types.d.ts +18 -0
- package/dist/types/components/tree/types.d.ts.map +1 -0
- package/dist/types/components/tree/utils/selection.d.ts +16 -0
- package/dist/types/components/tree/utils/selection.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/locale/en-US.d.ts.map +1 -1
- package/dist/types/locale/index.d.ts.map +1 -1
- package/dist/types/locale/types.d.ts +3 -0
- package/dist/types/locale/types.d.ts.map +1 -1
- package/dist/types/locale/zh-CN.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/app-shell/AppShell.vue +2 -1
- package/src/components/combobox/Combobox.vue +14 -23
- package/src/components/combobox/composables/useCombobox.ts +63 -0
- package/src/components/combobox/composables/useOptionLabels.ts +20 -0
- package/src/components/multi-combobox/MultiCombobox.vue +2 -0
- package/src/components/multi-combobox/composables/useMultiCombobox.ts +5 -10
- package/src/components/nav-link/NavLink.vue +7 -1
- package/src/components/nav-link/nav-link.variants.ts +1 -10
- package/src/components/sidebar/Sidebar.vue +43 -33
- package/src/components/sidebar/SidebarGroup.vue +12 -11
- package/src/components/sidebar/SidebarLabel.vue +24 -0
- package/src/components/sidebar/sidebar.variants.ts +36 -0
- package/src/components/tree/Tree.vue +132 -0
- package/src/components/tree/TreeCheck.vue +33 -0
- package/src/components/tree/composables/useTree.ts +81 -0
- package/src/components/tree/tree.variants.ts +22 -0
- package/src/components/tree/types.ts +20 -0
- package/src/components/tree/utils/selection.ts +78 -0
- package/src/index.ts +3 -0
- package/src/locale/en-US.ts +1 -0
- package/src/locale/index.ts +1 -0
- package/src/locale/types.ts +3 -0
- package/src/locale/zh-CN.ts +1 -0
- package/src/styles/motion.css +40 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { computed, type Ref } from 'vue'
|
|
2
|
+
import type { TreeNode, TreeNodeState, TreeValue } from '../types'
|
|
3
|
+
import { checkedState, indexTree, toggleChecked } from '../utils/selection'
|
|
4
|
+
|
|
5
|
+
interface Options {
|
|
6
|
+
items: TreeNode[]
|
|
7
|
+
multiple?: boolean
|
|
8
|
+
defaultExpanded: TreeValue[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useTree(
|
|
12
|
+
props: Options,
|
|
13
|
+
model: Ref<TreeValue | TreeValue[] | null | undefined>,
|
|
14
|
+
expandedModel: Ref<TreeValue[] | undefined>,
|
|
15
|
+
disabled: Ref<boolean>,
|
|
16
|
+
) {
|
|
17
|
+
const initialExpanded = [...props.defaultExpanded]
|
|
18
|
+
const index = computed(() => indexTree(props.items))
|
|
19
|
+
const states = computed(() => {
|
|
20
|
+
if (props.multiple)
|
|
21
|
+
return checkedState(index.value, Array.isArray(model.value) ? model.value : [])
|
|
22
|
+
return new Map(
|
|
23
|
+
[...index.value.entries].map(([value, entry]) => [
|
|
24
|
+
value,
|
|
25
|
+
{
|
|
26
|
+
selected: value === model.value,
|
|
27
|
+
indeterminate: false,
|
|
28
|
+
disabled: entry.disabled,
|
|
29
|
+
},
|
|
30
|
+
]),
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
const selected = computed(() =>
|
|
34
|
+
[...index.value.entries.values()]
|
|
35
|
+
.filter(entry => states.value.get(entry.node.value)?.selected)
|
|
36
|
+
.map(entry => entry.node),
|
|
37
|
+
)
|
|
38
|
+
const keys = computed(
|
|
39
|
+
() => new Map([...index.value.entries].map(([value, entry]) => [key(entry.node), value])),
|
|
40
|
+
)
|
|
41
|
+
const expanded = computed({
|
|
42
|
+
get: () => (expandedModel.value ?? initialExpanded).map(value => key({ value })),
|
|
43
|
+
set: value => {
|
|
44
|
+
expandedModel.value = value.flatMap(key =>
|
|
45
|
+
keys.value.has(key) ? [keys.value.get(key)!] : [],
|
|
46
|
+
)
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
function key(node: Pick<TreeNode, 'value'>) {
|
|
51
|
+
return `${typeof node.value}:${node.value}`
|
|
52
|
+
}
|
|
53
|
+
function getChildren(node: TreeNode) {
|
|
54
|
+
return node.children?.length ? node.children : undefined
|
|
55
|
+
}
|
|
56
|
+
function state(node: TreeNode): TreeNodeState {
|
|
57
|
+
const value = states.value.get(node.value)!
|
|
58
|
+
return { ...value, disabled: disabled.value || value.disabled }
|
|
59
|
+
}
|
|
60
|
+
function select(event: CustomEvent<{ value?: TreeNode }>) {
|
|
61
|
+
event.preventDefault()
|
|
62
|
+
const node = event.detail.value
|
|
63
|
+
if (!node || state(node).disabled) return
|
|
64
|
+
model.value = props.multiple
|
|
65
|
+
? toggleChecked(index.value, Array.isArray(model.value) ? model.value : [], node.value)
|
|
66
|
+
: model.value === node.value
|
|
67
|
+
? null
|
|
68
|
+
: node.value
|
|
69
|
+
}
|
|
70
|
+
function toggle(node: TreeNode) {
|
|
71
|
+
if (state(node).disabled || !getChildren(node)) return
|
|
72
|
+
const value = key(node)
|
|
73
|
+
expanded.value = expanded.value.includes(value)
|
|
74
|
+
? expanded.value.filter(key => key !== value)
|
|
75
|
+
: [...expanded.value, value]
|
|
76
|
+
}
|
|
77
|
+
function keepRowClick(event: CustomEvent<{ originalEvent: Event }>) {
|
|
78
|
+
if (event.detail.originalEvent.type === 'click') event.preventDefault()
|
|
79
|
+
}
|
|
80
|
+
return { selected, expanded, key, getChildren, state, select, toggle, keepRowClick }
|
|
81
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { tv } from '../../lib/tv'
|
|
2
|
+
|
|
3
|
+
export const tree = tv({
|
|
4
|
+
base: 'flex w-full min-w-0 flex-col outline-none',
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
export const treeRow = tv({
|
|
8
|
+
base: [
|
|
9
|
+
'hn-interactive hn-state-layer hn-press-none relative isolate flex w-full cursor-pointer',
|
|
10
|
+
'items-center gap-(--hn-control-gap) rounded-md py-2 pe-2.5 text-start text-sm select-none',
|
|
11
|
+
'ps-[calc(0.375rem+var(--hn-tree-level)*1.25rem)] text-fg outline-none',
|
|
12
|
+
'data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
13
|
+
],
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export const treeToggle = tv({
|
|
17
|
+
base: 'text-muted flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm rtl:-scale-x-100 [&>span>svg]:size-3.5',
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export const treeEmpty = tv({
|
|
21
|
+
base: 'text-muted px-2.5 py-2 text-sm',
|
|
22
|
+
})
|
|
@@ -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'
|
package/src/locale/en-US.ts
CHANGED
package/src/locale/index.ts
CHANGED
|
@@ -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 },
|
package/src/locale/types.ts
CHANGED
package/src/locale/zh-CN.ts
CHANGED
package/src/styles/motion.css
CHANGED
|
@@ -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
|
+
}
|