@adea-ai/ui 0.89.1 → 0.93.2
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/README.md +22 -5
- package/dist/NOTICE +19 -0
- package/dist/components/composites/list-row/index.d.ts +1 -0
- package/dist/components/composites/list-row/index.d.ts.map +1 -1
- package/dist/components/composites/list-row/index.js +2 -1
- package/dist/components/composites/list-row/list-row-control.d.ts +20 -0
- package/dist/components/composites/list-row/list-row-control.d.ts.map +1 -0
- package/dist/components/composites/list-row/list-row-control.js +96 -0
- package/dist/components/composites/list-row/list-row-control.js.map +1 -0
- package/dist/components/composites/list-row/list-row.d.ts +15 -35
- package/dist/components/composites/list-row/list-row.d.ts.map +1 -1
- package/dist/components/composites/list-row/list-row.js +88 -135
- package/dist/components/composites/list-row/list-row.js.map +1 -1
- package/dist/components/composites/tree/index.d.ts +4 -0
- package/dist/components/composites/tree/index.d.ts.map +1 -0
- package/dist/components/composites/tree/index.js +2 -0
- package/dist/components/composites/tree/tree-navigation.d.ts +46 -0
- package/dist/components/composites/tree/tree-navigation.d.ts.map +1 -0
- package/dist/components/composites/tree/tree-navigation.js +95 -0
- package/dist/components/composites/tree/tree-navigation.js.map +1 -0
- package/dist/components/composites/tree/tree.d.ts +64 -0
- package/dist/components/composites/tree/tree.d.ts.map +1 -0
- package/dist/components/composites/tree/tree.js +311 -0
- package/dist/components/composites/tree/tree.js.map +1 -0
- package/dist/components/conversation/message-composer.d.ts +18 -3
- package/dist/components/conversation/message-composer.d.ts.map +1 -1
- package/dist/components/conversation/message-composer.js +93 -27
- package/dist/components/conversation/message-composer.js.map +1 -1
- package/dist/components/layout/sidebar-nav/index.d.ts +1 -1
- package/dist/components/layout/sidebar-nav/index.d.ts.map +1 -1
- package/dist/components/layout/sidebar-nav/index.js +2 -2
- package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts +19 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav.d.ts.map +1 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav.js +83 -17
- package/dist/components/layout/sidebar-nav/sidebar-nav.js.map +1 -1
- package/dist/components/layout/top-bar/top-bar.d.ts +6 -5
- package/dist/components/layout/top-bar/top-bar.d.ts.map +1 -1
- package/dist/components/layout/top-bar/top-bar.js +30 -7
- package/dist/components/layout/top-bar/top-bar.js.map +1 -1
- package/dist/components/ui/dialog/dialog.d.ts +2 -0
- package/dist/components/ui/dialog/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog/dialog.js +4 -3
- package/dist/components/ui/dialog/dialog.js.map +1 -1
- package/dist/components/ui/modal-dialog/modal-dialog.d.ts +5 -2
- package/dist/components/ui/modal-dialog/modal-dialog.d.ts.map +1 -1
- package/dist/components/ui/modal-dialog/modal-dialog.js +33 -24
- package/dist/components/ui/modal-dialog/modal-dialog.js.map +1 -1
- package/dist/components/ui/textarea/index.d.ts +1 -1
- package/dist/components/ui/textarea/index.d.ts.map +1 -1
- package/dist/components/ui/textarea/index.js +2 -2
- package/dist/components/ui/textarea/textarea.d.ts +15 -2
- package/dist/components/ui/textarea/textarea.d.ts.map +1 -1
- package/dist/components/ui/textarea/textarea.js +47 -3
- package/dist/components/ui/textarea/textarea.js.map +1 -1
- package/dist/components/ui/tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/ui/tooltip/tooltip.js +239 -17
- package/dist/components/ui/tooltip/tooltip.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/lib/variants.js +2 -2
- package/dist/r/conversation.json +1 -0
- package/dist/r/list-row.json +5 -0
- package/dist/r/registry.json +41 -2
- package/dist/r/src/components/composites/list-row/index.ts +1 -0
- package/dist/r/src/components/composites/list-row/list-row-control.tsx +108 -0
- package/dist/r/src/components/composites/list-row/list-row.tsx +60 -127
- package/dist/r/src/components/composites/tree/index.ts +3 -0
- package/dist/r/src/components/composites/tree/tree-navigation.ts +114 -0
- package/dist/r/src/components/composites/tree/tree.tsx +405 -0
- package/dist/r/src/components/conversation/message-composer.tsx +103 -22
- package/dist/r/src/components/layout/sidebar-nav/index.ts +3 -0
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +75 -6
- package/dist/r/src/components/layout/top-bar/top-bar.tsx +34 -11
- package/dist/r/src/components/ui/dialog/dialog.tsx +4 -2
- package/dist/r/src/components/ui/modal-dialog/modal-dialog.tsx +20 -4
- package/dist/r/src/components/ui/textarea/index.ts +1 -1
- package/dist/r/src/components/ui/textarea/textarea.tsx +52 -10
- package/dist/r/src/components/ui/tooltip/tooltip.tsx +305 -10
- package/dist/r/src/styles/base.css +40 -0
- package/dist/r/textarea.json +3 -1
- package/dist/r/top-bar.json +3 -1
- package/dist/r/tree.json +29 -0
- package/package.json +4 -1
- package/registry.json +41 -2
- package/src/components/composites/list-row/index.ts +1 -0
- package/src/components/composites/list-row/list-row-control.tsx +108 -0
- package/src/components/composites/list-row/list-row.tsx +60 -127
- package/src/components/composites/tree/index.ts +3 -0
- package/src/components/composites/tree/tree-navigation.ts +114 -0
- package/src/components/composites/tree/tree.tsx +405 -0
- package/src/components/conversation/message-composer.tsx +103 -22
- package/src/components/layout/sidebar-nav/index.ts +3 -0
- package/src/components/layout/sidebar-nav/sidebar-nav.tsx +75 -6
- package/src/components/layout/top-bar/top-bar.tsx +34 -11
- package/src/components/ui/dialog/dialog.tsx +4 -2
- package/src/components/ui/modal-dialog/modal-dialog.tsx +20 -4
- package/src/components/ui/textarea/index.ts +1 -1
- package/src/components/ui/textarea/textarea.tsx +52 -10
- package/src/components/ui/tooltip/tooltip.tsx +305 -10
- package/src/index.ts +1 -0
- package/src/styles/base.css +40 -0
|
@@ -1,161 +1,93 @@
|
|
|
1
1
|
import type { PolymorphicProps } from '@kobalte/core/polymorphic'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import { Show, splitProps } from 'solid-js'
|
|
2
|
+
import type { ComponentProps, ValidComponent } from 'solid-js'
|
|
3
|
+
import { createSignal, createUniqueId, Show, splitProps } from 'solid-js'
|
|
5
4
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip'
|
|
6
5
|
import { cn } from '#lib/utils'
|
|
6
|
+
import { ListRowControl, type ListRowControlProps } from './list-row-control'
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* ListRow.
|
|
10
|
-
*
|
|
11
|
-
* One row in a list of like things: a project, a session, a file, a person. It
|
|
12
|
-
* is the highest-frequency component in an application of this shape, and the
|
|
13
|
-
* one where a half-pixel of drift is most visible — twenty rows of a sidebar
|
|
14
|
-
* make any inconsistency in height or padding obvious.
|
|
15
|
-
*
|
|
16
|
-
* The structure is fixed at four slots — `leading`, `children`, `meta`,
|
|
17
|
-
* `trailing` — so rows that show an avatar, an icon, a status dot, a timestamp
|
|
18
|
-
* and an overflow menu all sit at the same height without the caller doing
|
|
19
|
-
* arithmetic.
|
|
20
|
-
*
|
|
21
|
-
* A row is only interactive when it is given an `as` that can be activated
|
|
22
|
-
* (an anchor, a button) or an `onClick`. Click handlers default to a native
|
|
23
|
-
* button; a row without either remains a `div` with no hover treatment.
|
|
24
|
-
*/
|
|
25
8
|
export type ListRowProps<T extends ValidComponent = 'div'> = PolymorphicProps<
|
|
26
9
|
T,
|
|
27
|
-
{
|
|
28
|
-
class?: string
|
|
29
|
-
/** Marks the row as current. Sets `aria-current` for a link row. */
|
|
30
|
-
selected?: boolean
|
|
31
|
-
/** Draw the row at the compact height. */
|
|
32
|
-
dense?: boolean
|
|
33
|
-
/** Leading slot: an avatar, an icon, a checkbox. */
|
|
34
|
-
leading?: JSX.Element
|
|
35
|
-
/** Trailing slot: a count, a menu trigger, a timestamp. */
|
|
36
|
-
trailing?: JSX.Element
|
|
37
|
-
/** A dimmed second line under the main content. */
|
|
38
|
-
description?: string
|
|
10
|
+
ListRowControlProps<T> & {
|
|
39
11
|
/** A short explanation shown on pointer hover and keyboard focus. */
|
|
40
12
|
tooltip?: string
|
|
41
13
|
/** Native button type; defaults to `button` for button rows. */
|
|
42
14
|
type?: ComponentProps<'button'>['type']
|
|
15
|
+
/** Additional accessible description preserved on plain and rich rows. */
|
|
16
|
+
'aria-describedby'?: string
|
|
43
17
|
}
|
|
44
18
|
>
|
|
45
19
|
|
|
46
|
-
type
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
function ListRowBase<T extends ValidComponent = 'div'>(props: ListRowBaseProps<T>) {
|
|
52
|
-
const [local, rest] = splitProps(props as ListRowProps, [
|
|
53
|
-
'as',
|
|
54
|
-
'class',
|
|
55
|
-
'selected',
|
|
56
|
-
'dense',
|
|
57
|
-
'leading',
|
|
58
|
-
'trailing',
|
|
59
|
-
'description',
|
|
60
|
-
'tooltip',
|
|
61
|
-
'type',
|
|
62
|
-
'tabIndex',
|
|
63
|
-
'children',
|
|
64
|
-
])
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<Polymorphic
|
|
68
|
-
as={(local.as ?? 'div') as T}
|
|
69
|
-
type={local.as === 'button' ? (local.type ?? 'button') : local.type}
|
|
70
|
-
tabIndex={local.tabIndex ?? (local.as === 'button' || local.as === 'a' ? 0 : undefined)}
|
|
71
|
-
aria-current={local.selected ? 'true' : undefined}
|
|
72
|
-
data-selected={local.selected ? '' : undefined}
|
|
73
|
-
class={cn(
|
|
74
|
-
'group/row flex min-w-0 items-center gap-2.5 rounded-md px-2 text-sm',
|
|
75
|
-
'transition-colors ease-out outline-none',
|
|
76
|
-
'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',
|
|
77
|
-
{
|
|
78
|
-
'h-row-sm': local.dense && !local.description,
|
|
79
|
-
'h-row-md': !local.dense && !local.description,
|
|
80
|
-
'min-h-row-sm py-1': local.dense && local.description,
|
|
81
|
-
'min-h-row-md py-1.5': !local.dense && local.description,
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
'bg-primary-subtle text-foreground': local.selected,
|
|
85
|
-
'hover:bg-surface-hover': !local.selected,
|
|
86
|
-
},
|
|
87
|
-
local.class
|
|
88
|
-
)}
|
|
89
|
-
{...(rest as ComponentProps<'div'>)}
|
|
90
|
-
>
|
|
91
|
-
<Show when={local.leading}>
|
|
92
|
-
<span class="flex size-4 shrink-0 items-center justify-center [&_svg]:size-4">
|
|
93
|
-
{local.leading}
|
|
94
|
-
</span>
|
|
95
|
-
</Show>
|
|
96
|
-
<span class="flex min-w-0 flex-1 flex-col">
|
|
97
|
-
<span class="truncate">{local.children}</span>
|
|
98
|
-
<Show when={local.description}>
|
|
99
|
-
<span
|
|
100
|
-
data-slot="list-row-description"
|
|
101
|
-
class={cn(
|
|
102
|
-
'truncate text-xs',
|
|
103
|
-
local.selected ? 'text-foreground' : 'text-muted-foreground'
|
|
104
|
-
)}
|
|
105
|
-
>
|
|
106
|
-
{local.description}
|
|
107
|
-
</span>
|
|
108
|
-
</Show>
|
|
109
|
-
</span>
|
|
110
|
-
<Show when={local.trailing}>
|
|
111
|
-
<span
|
|
112
|
-
data-slot="list-row-trailing"
|
|
113
|
-
class={cn(
|
|
114
|
-
'ms-auto flex shrink-0 items-center gap-1',
|
|
115
|
-
local.selected ? 'text-foreground' : 'text-muted-foreground'
|
|
116
|
-
)}
|
|
117
|
-
>
|
|
118
|
-
{local.trailing}
|
|
119
|
-
</span>
|
|
120
|
-
</Show>
|
|
121
|
-
</Polymorphic>
|
|
122
|
-
)
|
|
20
|
+
type ListRowTooltipTargetProps<T extends ValidComponent = 'div'> = Omit<ListRowProps<T>, 'as'> & {
|
|
21
|
+
rowAs?: T
|
|
22
|
+
externalDescribedBy?: string
|
|
23
|
+
tooltipId?: string
|
|
24
|
+
tooltipOpen?: () => boolean
|
|
123
25
|
}
|
|
124
26
|
|
|
125
|
-
type ListRowTooltipTargetProps<T extends ValidComponent = 'div'> = Omit<
|
|
126
|
-
ListRowBaseProps<T>,
|
|
127
|
-
'as'
|
|
128
|
-
> & { rowAs?: T }
|
|
129
|
-
|
|
130
27
|
/** Adapts TooltipTrigger's polymorphic target to ListRow's semantic element. */
|
|
131
28
|
function ListRowTooltipTarget<T extends ValidComponent = 'div'>(
|
|
132
29
|
props: ListRowTooltipTargetProps<T>
|
|
133
30
|
) {
|
|
134
|
-
const [local, rest] = splitProps(props as ListRowTooltipTargetProps, [
|
|
135
|
-
|
|
31
|
+
const [local, rest] = splitProps(props as ListRowTooltipTargetProps, [
|
|
32
|
+
'rowAs',
|
|
33
|
+
'externalDescribedBy',
|
|
34
|
+
'tooltipId',
|
|
35
|
+
'tooltipOpen',
|
|
36
|
+
])
|
|
37
|
+
const describedBy = () => {
|
|
38
|
+
const ids = [
|
|
39
|
+
rest['aria-describedby'],
|
|
40
|
+
local.externalDescribedBy,
|
|
41
|
+
local.tooltipOpen?.() ? local.tooltipId : undefined,
|
|
42
|
+
]
|
|
43
|
+
.flatMap((value) => value?.split(/\s+/) ?? [])
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
return [...new Set(ids)].join(' ') || undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<ListRowControl
|
|
50
|
+
as={local.rowAs}
|
|
51
|
+
{...(rest as unknown as ListRowControlProps<T>)}
|
|
52
|
+
aria-describedby={describedBy()}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
136
55
|
}
|
|
137
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Rich row composition with optional hover and keyboard tooltip behavior.
|
|
59
|
+
* Import ListRowControl when the tooltip API is not needed and the consumer
|
|
60
|
+
* wants the plain renderer to tree-shake independently.
|
|
61
|
+
*/
|
|
138
62
|
export function ListRow<T extends ValidComponent = 'div'>(props: ListRowProps<T>) {
|
|
139
|
-
const [local, rest] = splitProps(props as ListRowProps
|
|
63
|
+
const [local, rest] = splitProps(props as ListRowProps<T>, ['as', 'tooltip', 'aria-describedby'])
|
|
64
|
+
const [tooltipOpen, setTooltipOpen] = createSignal(false)
|
|
65
|
+
const tooltipId = `list-row-tooltip-${createUniqueId()}`
|
|
140
66
|
const hasClickHandler = () => Boolean(rest.onClick || rest['on:click'])
|
|
141
67
|
const rowAs = () => (local.as ?? (hasClickHandler() ? 'button' : 'div')) as T
|
|
142
68
|
const rowType = () => (rowAs() === 'button' ? (rest.type ?? 'button') : rest.type)
|
|
69
|
+
const rowProps = () =>
|
|
70
|
+
({
|
|
71
|
+
...rest,
|
|
72
|
+
type: rowType(),
|
|
73
|
+
'aria-describedby': local['aria-describedby'],
|
|
74
|
+
}) as unknown as ListRowControlProps<T>
|
|
143
75
|
|
|
144
76
|
return (
|
|
145
|
-
<Show
|
|
146
|
-
|
|
147
|
-
fallback={<ListRowBase as={rowAs()} type={rowType()} {...(rest as ListRowBaseProps<T>)} />}
|
|
148
|
-
>
|
|
149
|
-
<Tooltip>
|
|
77
|
+
<Show when={local.tooltip} fallback={<ListRowControl as={rowAs()} {...rowProps()} />}>
|
|
78
|
+
<Tooltip open={tooltipOpen()} onOpenChange={setTooltipOpen}>
|
|
150
79
|
<TooltipTrigger
|
|
151
80
|
as={ListRowTooltipTarget}
|
|
152
81
|
rowAs={rowAs()}
|
|
153
82
|
type={rowType()}
|
|
83
|
+
externalDescribedBy={local['aria-describedby']}
|
|
84
|
+
tooltipId={tooltipId}
|
|
85
|
+
tooltipOpen={tooltipOpen}
|
|
154
86
|
{...(rest as ListRowTooltipTargetProps<T>)}
|
|
155
87
|
>
|
|
156
88
|
{rest.children}
|
|
157
89
|
</TooltipTrigger>
|
|
158
|
-
<TooltipContent>{local.tooltip}</TooltipContent>
|
|
90
|
+
<TooltipContent id={tooltipId}>{local.tooltip}</TooltipContent>
|
|
159
91
|
</Tooltip>
|
|
160
92
|
</Show>
|
|
161
93
|
)
|
|
@@ -163,12 +95,13 @@ export function ListRow<T extends ValidComponent = 'div'>(props: ListRowProps<T>
|
|
|
163
95
|
|
|
164
96
|
/**
|
|
165
97
|
* A group of rows with an optional heading and a scrolling body. The heading is
|
|
166
|
-
* a plain label rather than a control: grouping is for reading, and a
|
|
167
|
-
*
|
|
98
|
+
* a plain label rather than a control: grouping is for reading, and a disclosure
|
|
99
|
+
* on a list of five rows costs more attention than it saves.
|
|
168
100
|
*/
|
|
169
|
-
export function ListGroup(
|
|
101
|
+
export function ListGroup(
|
|
102
|
+
props: ComponentProps<'div'> & { label?: string; action?: import('solid-js').JSX.Element }
|
|
103
|
+
) {
|
|
170
104
|
const [local, rest] = splitProps(props, ['class', 'label', 'action', 'children'])
|
|
171
|
-
|
|
172
105
|
return (
|
|
173
106
|
<div data-slot="list-group" class={cn('flex flex-col gap-0.5', local.class)} {...rest}>
|
|
174
107
|
<Show when={local.label}>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/** A visible tree row's host-owned projection metadata. */
|
|
2
|
+
export type TreeItemDescriptor = {
|
|
3
|
+
/** Stable identity shared with the host's domain item. */
|
|
4
|
+
id: string
|
|
5
|
+
/** Stable parent identity, or null for a root item. */
|
|
6
|
+
parentId: string | null
|
|
7
|
+
/** One-based depth exposed to assistive technology. */
|
|
8
|
+
level: number
|
|
9
|
+
/** Whether this item can be expanded. */
|
|
10
|
+
expandable: boolean
|
|
11
|
+
/** Whether this item is currently expanded. */
|
|
12
|
+
expanded: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type TreeNavigationIndex = {
|
|
16
|
+
ids: readonly string[]
|
|
17
|
+
byId: ReadonlyMap<string, TreeItemDescriptor>
|
|
18
|
+
indexById: ReadonlyMap<string, number>
|
|
19
|
+
firstChildByParent: ReadonlyMap<string, string>
|
|
20
|
+
positionById: ReadonlyMap<string, { position: number; setSize: number }>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type TreeNavigationAction =
|
|
24
|
+
| { type: 'focus'; id: string }
|
|
25
|
+
| { type: 'expand'; id: string; expanded: boolean }
|
|
26
|
+
| { type: 'activate'; id: string }
|
|
27
|
+
| { type: 'select'; id: string }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Indexes the complete expanded projection once per host update. The DOM may
|
|
31
|
+
* contain only a window of these rows; this index keeps keyboard movement and
|
|
32
|
+
* aria-setsize independent of the mounted window.
|
|
33
|
+
*/
|
|
34
|
+
export function indexTreeItems(items: readonly TreeItemDescriptor[]): TreeNavigationIndex {
|
|
35
|
+
const byId = new Map<string, TreeItemDescriptor>()
|
|
36
|
+
const indexById = new Map<string, number>()
|
|
37
|
+
const firstChildByParent = new Map<string, string>()
|
|
38
|
+
const siblingsByParent = new Map<string | null, string[]>()
|
|
39
|
+
|
|
40
|
+
items.forEach((item, index) => {
|
|
41
|
+
if (byId.has(item.id)) {
|
|
42
|
+
throw new Error(`[adea-ui]: Tree visibleItems contains duplicate id "${item.id}".`)
|
|
43
|
+
}
|
|
44
|
+
byId.set(item.id, item)
|
|
45
|
+
indexById.set(item.id, index)
|
|
46
|
+
|
|
47
|
+
const siblings = siblingsByParent.get(item.parentId) ?? []
|
|
48
|
+
siblings.push(item.id)
|
|
49
|
+
siblingsByParent.set(item.parentId, siblings)
|
|
50
|
+
|
|
51
|
+
if (item.parentId !== null && !firstChildByParent.has(item.parentId)) {
|
|
52
|
+
firstChildByParent.set(item.parentId, item.id)
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const positionById = new Map<string, { position: number; setSize: number }>()
|
|
57
|
+
for (const siblings of siblingsByParent.values()) {
|
|
58
|
+
siblings.forEach((id, index) => {
|
|
59
|
+
positionById.set(id, { position: index + 1, setSize: siblings.length })
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
ids: items.map((item) => item.id),
|
|
65
|
+
byId,
|
|
66
|
+
indexById,
|
|
67
|
+
firstChildByParent,
|
|
68
|
+
positionById,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Resolves one keyboard event without owning tree data or host actions. */
|
|
73
|
+
export function treeNavigationAction(
|
|
74
|
+
index: TreeNavigationIndex,
|
|
75
|
+
activeId: string | null,
|
|
76
|
+
key: string
|
|
77
|
+
): TreeNavigationAction | null {
|
|
78
|
+
const { ids, byId, indexById, firstChildByParent } = index
|
|
79
|
+
if (ids.length === 0) return null
|
|
80
|
+
|
|
81
|
+
const activeIndex = activeId === null ? undefined : indexById.get(activeId)
|
|
82
|
+
const active = activeId === null ? undefined : byId.get(activeId)
|
|
83
|
+
|
|
84
|
+
if (key === 'ArrowDown' || key === 'ArrowUp') {
|
|
85
|
+
const step = key === 'ArrowDown' ? 1 : -1
|
|
86
|
+
const start = activeIndex ?? (step > 0 ? -1 : 0)
|
|
87
|
+
const nextIndex = (start + step + ids.length) % ids.length
|
|
88
|
+
const id = ids[nextIndex]
|
|
89
|
+
return id === undefined ? null : { type: 'focus', id }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (key === 'Home') return { type: 'focus', id: ids[0]! }
|
|
93
|
+
if (key === 'End') return { type: 'focus', id: ids[ids.length - 1]! }
|
|
94
|
+
if (!active) return null
|
|
95
|
+
|
|
96
|
+
if (key === 'ArrowRight' && active.expandable) {
|
|
97
|
+
if (!active.expanded) return { type: 'expand', id: active.id, expanded: true }
|
|
98
|
+
const childId = firstChildByParent.get(active.id)
|
|
99
|
+
return childId ? { type: 'focus', id: childId } : null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (key === 'ArrowLeft') {
|
|
103
|
+
if (active.expandable && active.expanded) {
|
|
104
|
+
return { type: 'expand', id: active.id, expanded: false }
|
|
105
|
+
}
|
|
106
|
+
if (active.parentId !== null && byId.has(active.parentId)) {
|
|
107
|
+
return { type: 'focus', id: active.parentId }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (key === 'Enter') return { type: 'activate', id: active.id }
|
|
112
|
+
if (key === ' ' || key === 'Spacebar') return { type: 'select', id: active.id }
|
|
113
|
+
return null
|
|
114
|
+
}
|