@sanity/ui 0.36.17 → 0.37.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/lib/sanity-ui.js +222 -197
- package/lib/sanity-ui.js.map +1 -1
- package/lib/sanity-ui.modern.js +286 -277
- package/lib/sanity-ui.modern.js.map +1 -1
- package/lib/sanity-ui.module.js +222 -197
- package/lib/sanity-ui.module.js.map +1 -1
- package/lib/src/components/menu/menu.d.ts +1 -1
- package/lib/src/components/menu/menu.d.ts.map +1 -1
- package/lib/src/components/menu/menuContext.d.ts +2 -2
- package/lib/src/components/menu/menuContext.d.ts.map +1 -1
- package/lib/src/components/menu/menuGroup.d.ts +2 -0
- package/lib/src/components/menu/menuGroup.d.ts.map +1 -1
- package/lib/src/components/menu/useMenuController.d.ts +25 -0
- package/lib/src/components/menu/useMenuController.d.ts.map +1 -0
- package/package.json +6 -6
- package/src/components/menu/__workshop__/index.ts +5 -0
- package/src/components/menu/__workshop__/menuGroupRight.tsx +59 -0
- package/src/components/menu/menu.test.tsx +16 -12
- package/src/components/menu/menu.tsx +26 -210
- package/src/components/menu/menuContext.ts +2 -2
- package/src/components/menu/menuGroup.tsx +54 -77
- package/src/components/menu/menuItem.tsx +5 -5
- package/src/components/menu/useMenuController.ts +239 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {ChevronRightIcon} from '@sanity/icons'
|
|
2
|
-
import React, {useCallback, useEffect, useRef, useState} from 'react'
|
|
3
|
-
import
|
|
4
|
-
import {Box, Popover, PopoverProps, Text} from '../../primitives'
|
|
2
|
+
import React, {createElement, isValidElement, useCallback, useEffect, useRef, useState} from 'react'
|
|
3
|
+
import {isValidElementType} from 'react-is'
|
|
4
|
+
import {Box, Flex, Popover, PopoverProps, Text} from '../../primitives'
|
|
5
5
|
import {Selectable} from '../../primitives/_selectable'
|
|
6
6
|
import {SelectableTone} from '../../types/selectable'
|
|
7
7
|
import {Menu} from './menu'
|
|
@@ -13,26 +13,15 @@ import {useMenu} from './useMenu'
|
|
|
13
13
|
export interface MenuGroupProps {
|
|
14
14
|
as?: React.ElementType | keyof JSX.IntrinsicElements
|
|
15
15
|
fontSize?: number | number[]
|
|
16
|
+
icon?: React.ComponentType | React.ReactNode
|
|
16
17
|
padding?: number | number[]
|
|
17
18
|
popover?: Omit<PopoverProps, 'content' | 'open'>
|
|
18
19
|
radius?: number | number[]
|
|
20
|
+
space?: number | number[]
|
|
19
21
|
text: React.ReactNode
|
|
20
22
|
tone?: SelectableTone
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
const MOUSE_LEAVE_TIMEOUT = 1000
|
|
24
|
-
|
|
25
|
-
const TextContainer = styled.span`
|
|
26
|
-
&:not([hidden]) {
|
|
27
|
-
display: flex;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
& > div:first-child {
|
|
31
|
-
flex: 1;
|
|
32
|
-
min-width: 0;
|
|
33
|
-
}
|
|
34
|
-
`
|
|
35
|
-
|
|
36
25
|
/**
|
|
37
26
|
* @public
|
|
38
27
|
*/
|
|
@@ -44,56 +33,38 @@ export function MenuGroup(
|
|
|
44
33
|
as = 'button',
|
|
45
34
|
children,
|
|
46
35
|
fontSize,
|
|
36
|
+
icon,
|
|
47
37
|
onClick,
|
|
48
38
|
padding = 3,
|
|
49
39
|
popover = {},
|
|
50
40
|
radius = 2,
|
|
41
|
+
space = 3,
|
|
51
42
|
text,
|
|
52
43
|
tone = 'default',
|
|
53
44
|
...restProps
|
|
54
45
|
} = props
|
|
55
|
-
const [open, setOpen] = useState(false)
|
|
56
46
|
const {
|
|
57
47
|
activeElement,
|
|
58
48
|
mount,
|
|
59
49
|
onClickOutside,
|
|
60
50
|
onEscape,
|
|
61
51
|
onItemClick,
|
|
62
|
-
|
|
63
|
-
onMouseLeave,
|
|
52
|
+
onItemMouseEnter,
|
|
64
53
|
registerElement,
|
|
65
54
|
} = useMenu()
|
|
66
55
|
const [rootElement, setRootElement] = useState<HTMLButtonElement | null>(null)
|
|
67
|
-
const
|
|
56
|
+
const [open, setOpen] = useState(false)
|
|
68
57
|
const shouldFocusRef = useRef<'first' | 'last' | null>(null)
|
|
69
58
|
const active = Boolean(activeElement) && activeElement === rootElement
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// Register the element
|
|
73
|
-
useEffect(() => mount(rootElement), [mount, rootElement])
|
|
59
|
+
const [withinMenu, setWithinMenu] = useState(false)
|
|
74
60
|
|
|
75
61
|
const handleMouseEnter = useCallback(
|
|
76
62
|
(event: React.MouseEvent<HTMLElement>) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
mouseLeaveTimeoutRef.current = null
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
onMouseEnter(event)
|
|
63
|
+
setWithinMenu(false)
|
|
64
|
+
onItemMouseEnter(event)
|
|
83
65
|
setOpen(true)
|
|
84
66
|
},
|
|
85
|
-
[
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
const handleMouseLeave = useCallback(
|
|
89
|
-
(event: React.MouseEvent<HTMLElement>) => {
|
|
90
|
-
onMouseLeave(event)
|
|
91
|
-
|
|
92
|
-
mouseLeaveTimeoutRef.current = setTimeout(() => {
|
|
93
|
-
setOpen(false)
|
|
94
|
-
}, MOUSE_LEAVE_TIMEOUT)
|
|
95
|
-
},
|
|
96
|
-
[onMouseLeave]
|
|
67
|
+
[onItemMouseEnter]
|
|
97
68
|
)
|
|
98
69
|
|
|
99
70
|
const handleMenuKeyDown = useCallback(
|
|
@@ -111,19 +82,6 @@ export function MenuGroup(
|
|
|
111
82
|
[rootElement]
|
|
112
83
|
)
|
|
113
84
|
|
|
114
|
-
const handleMenuMouseEnter = useCallback(() => {
|
|
115
|
-
if (mouseLeaveTimeoutRef.current) {
|
|
116
|
-
clearTimeout(mouseLeaveTimeoutRef.current)
|
|
117
|
-
mouseLeaveTimeoutRef.current = null
|
|
118
|
-
}
|
|
119
|
-
}, [])
|
|
120
|
-
|
|
121
|
-
const handleMenuMouseLeave = useCallback(() => {
|
|
122
|
-
mouseLeaveTimeoutRef.current = setTimeout(() => {
|
|
123
|
-
setOpen(false)
|
|
124
|
-
}, MOUSE_LEAVE_TIMEOUT)
|
|
125
|
-
}, [])
|
|
126
|
-
|
|
127
85
|
const handleClick = useCallback(
|
|
128
86
|
(event: React.MouseEvent<HTMLDivElement>) => {
|
|
129
87
|
if (onClick) onClick(event)
|
|
@@ -139,28 +97,33 @@ export function MenuGroup(
|
|
|
139
97
|
[onClick]
|
|
140
98
|
)
|
|
141
99
|
|
|
142
|
-
const
|
|
100
|
+
const handleChildItemClick = useCallback(() => {
|
|
143
101
|
setOpen(false)
|
|
144
102
|
if (onItemClick) onItemClick()
|
|
145
103
|
}, [onItemClick])
|
|
146
104
|
|
|
105
|
+
const handleMenuMouseEnter = useCallback(() => setWithinMenu(true), [])
|
|
106
|
+
|
|
107
|
+
// Register the menu item element
|
|
108
|
+
useEffect(() => mount(rootElement), [mount, rootElement])
|
|
109
|
+
|
|
110
|
+
// Close child menu when a sibling item becomes active
|
|
147
111
|
useEffect(() => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}, [])
|
|
112
|
+
if (!active) setOpen(false)
|
|
113
|
+
}, [active])
|
|
114
|
+
|
|
115
|
+
// Update state when child menu is no longer open
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!open) setWithinMenu(false)
|
|
118
|
+
}, [open])
|
|
155
119
|
|
|
156
|
-
const
|
|
120
|
+
const childMenu = (
|
|
157
121
|
<Menu
|
|
158
122
|
onClickOutside={onClickOutside}
|
|
159
123
|
onEscape={onEscape}
|
|
160
|
-
onItemClick={
|
|
124
|
+
onItemClick={handleChildItemClick}
|
|
161
125
|
onKeyDown={handleMenuKeyDown}
|
|
162
126
|
onMouseEnter={handleMenuMouseEnter}
|
|
163
|
-
onMouseLeave={handleMenuMouseLeave}
|
|
164
127
|
registerElement={registerElement}
|
|
165
128
|
shouldFocus={shouldFocusRef.current}
|
|
166
129
|
>
|
|
@@ -179,6 +142,7 @@ export function MenuGroup(
|
|
|
179
142
|
shouldFocusRef.current = 'first'
|
|
180
143
|
|
|
181
144
|
setOpen(true)
|
|
145
|
+
setWithinMenu(true)
|
|
182
146
|
|
|
183
147
|
requestAnimationFrame(() => {
|
|
184
148
|
shouldFocusRef.current = null
|
|
@@ -189,32 +153,45 @@ export function MenuGroup(
|
|
|
189
153
|
}, [])
|
|
190
154
|
|
|
191
155
|
return (
|
|
192
|
-
<Popover {...popover} content={
|
|
156
|
+
<Popover {...popover} content={childMenu} data-ui="MenuGroup__popover" open={open}>
|
|
193
157
|
<Selectable
|
|
194
158
|
data-as={as}
|
|
195
159
|
data-ui="MenuGroup"
|
|
196
160
|
forwardedAs={as}
|
|
197
161
|
{...restProps}
|
|
198
|
-
aria-pressed={as === 'button' ?
|
|
199
|
-
data-pressed={as !== 'button' ?
|
|
200
|
-
data-selected={active ? '' : undefined}
|
|
162
|
+
aria-pressed={as === 'button' ? withinMenu : undefined}
|
|
163
|
+
data-pressed={as !== 'button' ? withinMenu : undefined}
|
|
164
|
+
data-selected={!withinMenu && active ? '' : undefined}
|
|
201
165
|
$radius={radius}
|
|
202
166
|
$tone={tone}
|
|
203
167
|
onClick={handleClick}
|
|
204
168
|
onKeyDown={handleKeyDown}
|
|
205
169
|
onMouseEnter={handleMouseEnter}
|
|
206
|
-
onMouseLeave={handleMouseLeave}
|
|
207
170
|
ref={setRootElement}
|
|
208
171
|
tabIndex={-1}
|
|
209
172
|
type={as === 'button' ? 'button' : undefined}
|
|
210
173
|
>
|
|
211
174
|
<Box padding={padding}>
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
175
|
+
<Flex>
|
|
176
|
+
{icon && (
|
|
177
|
+
<Text size={fontSize}>
|
|
178
|
+
{isValidElement(icon) && icon}
|
|
179
|
+
{isValidElementType(icon) && createElement(icon)}
|
|
180
|
+
</Text>
|
|
181
|
+
)}
|
|
182
|
+
|
|
183
|
+
<Box flex={1} marginLeft={icon ? space : undefined}>
|
|
184
|
+
<Text size={fontSize} textOverflow="ellipsis">
|
|
185
|
+
{text}
|
|
186
|
+
</Text>
|
|
187
|
+
</Box>
|
|
188
|
+
|
|
189
|
+
<Box marginLeft={space}>
|
|
190
|
+
<Text size={fontSize}>
|
|
191
|
+
<ChevronRightIcon />
|
|
192
|
+
</Text>
|
|
193
|
+
</Box>
|
|
194
|
+
</Flex>
|
|
218
195
|
</Box>
|
|
219
196
|
</Selectable>
|
|
220
197
|
</Popover>
|
|
@@ -64,9 +64,9 @@ export const MenuItem = forwardRef(function MenuItem(
|
|
|
64
64
|
tone = 'default',
|
|
65
65
|
...restProps
|
|
66
66
|
} = props
|
|
67
|
-
const {activeElement, mount, onItemClick,
|
|
67
|
+
const {activeElement, mount, onItemClick, onItemMouseEnter, onItemMouseLeave} = useMenu()
|
|
68
68
|
const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
|
|
69
|
-
const
|
|
69
|
+
const active = Boolean(activeElement) && activeElement === rootElement
|
|
70
70
|
|
|
71
71
|
useEffect(() => mount(rootElement, selectedProp), [mount, rootElement, selectedProp])
|
|
72
72
|
|
|
@@ -110,14 +110,14 @@ export const MenuItem = forwardRef(function MenuItem(
|
|
|
110
110
|
{...restProps}
|
|
111
111
|
aria-pressed={as === 'button' && pressed}
|
|
112
112
|
data-pressed={as !== 'button' && pressed ? '' : undefined}
|
|
113
|
-
data-selected={
|
|
113
|
+
data-selected={active ? '' : undefined}
|
|
114
114
|
data-disabled={disabled ? '' : undefined}
|
|
115
115
|
$radius={radius}
|
|
116
116
|
$tone={tone}
|
|
117
117
|
disabled={disabled}
|
|
118
118
|
onClick={handleClick}
|
|
119
|
-
onMouseEnter={
|
|
120
|
-
onMouseLeave={
|
|
119
|
+
onMouseEnter={onItemMouseEnter}
|
|
120
|
+
onMouseLeave={onItemMouseLeave}
|
|
121
121
|
padding={0}
|
|
122
122
|
ref={setRef}
|
|
123
123
|
role="menuitem"
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import {useCallback, useEffect, useRef, useState} from 'react'
|
|
2
|
+
import {_getFocusableElements, _sortElements} from './helpers'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export interface MenuController {
|
|
8
|
+
activeElement: HTMLElement | null
|
|
9
|
+
activeIndex: number
|
|
10
|
+
handleItemMouseEnter: (event: React.MouseEvent<HTMLElement>) => void
|
|
11
|
+
handleItemMouseLeave: () => void
|
|
12
|
+
handleKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void
|
|
13
|
+
mount: (element: HTMLElement | null, selected?: boolean) => () => void
|
|
14
|
+
rootElement: HTMLDivElement | null
|
|
15
|
+
setRootElement: (el: HTMLDivElement | null) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* This controller is responsible for controlling UI menu state.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export function useMenuController(props: {
|
|
24
|
+
onKeyDown?: React.KeyboardEventHandler
|
|
25
|
+
originElement?: HTMLElement | null
|
|
26
|
+
shouldFocus: 'first' | 'last' | null
|
|
27
|
+
}): MenuController {
|
|
28
|
+
const {onKeyDown, originElement, shouldFocus} = props
|
|
29
|
+
|
|
30
|
+
const elementsRef = useRef<HTMLElement[]>([])
|
|
31
|
+
|
|
32
|
+
const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
|
|
33
|
+
const [activeIndex, _setActiveIndex] = useState(-1)
|
|
34
|
+
|
|
35
|
+
const activeElement = elementsRef.current[activeIndex] || null
|
|
36
|
+
const activeIndexRef = useRef(activeIndex)
|
|
37
|
+
|
|
38
|
+
const mounted = Boolean(rootElement)
|
|
39
|
+
|
|
40
|
+
const setActiveIndex = useCallback((nextActiveIndex) => {
|
|
41
|
+
_setActiveIndex(nextActiveIndex)
|
|
42
|
+
activeIndexRef.current = nextActiveIndex
|
|
43
|
+
}, [])
|
|
44
|
+
|
|
45
|
+
const mount = useCallback(
|
|
46
|
+
(element: HTMLElement | null, selected?: boolean): (() => void) => {
|
|
47
|
+
if (!element) return () => undefined
|
|
48
|
+
|
|
49
|
+
if (elementsRef.current.indexOf(element) === -1) {
|
|
50
|
+
elementsRef.current.push(element)
|
|
51
|
+
_sortElements(rootElement, elementsRef.current)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (selected) {
|
|
55
|
+
const selectedIndex = elementsRef.current.indexOf(element)
|
|
56
|
+
|
|
57
|
+
setActiveIndex(selectedIndex)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
const idx = elementsRef.current.indexOf(element)
|
|
62
|
+
|
|
63
|
+
if (idx > -1) {
|
|
64
|
+
elementsRef.current.splice(idx, 1)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
[rootElement, setActiveIndex]
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
const handleKeyDown = useCallback(
|
|
72
|
+
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
73
|
+
// Move focus to the element that opened the menu before handling the `Tab` press
|
|
74
|
+
if (event.key === 'Tab') {
|
|
75
|
+
if (originElement) {
|
|
76
|
+
originElement.focus()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Move focus to the first focusable menuitem
|
|
83
|
+
if (event.key === 'Home') {
|
|
84
|
+
event.preventDefault()
|
|
85
|
+
event.stopPropagation()
|
|
86
|
+
|
|
87
|
+
const focusableElements = _getFocusableElements(elementsRef.current)
|
|
88
|
+
const el = focusableElements[0]
|
|
89
|
+
|
|
90
|
+
if (!el) return
|
|
91
|
+
|
|
92
|
+
const currentIndex = elementsRef.current.indexOf(el)
|
|
93
|
+
|
|
94
|
+
setActiveIndex(currentIndex)
|
|
95
|
+
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Move focus to the last focusable menuitem
|
|
100
|
+
if (event.key === 'End') {
|
|
101
|
+
event.preventDefault()
|
|
102
|
+
event.stopPropagation()
|
|
103
|
+
|
|
104
|
+
const focusableElements = _getFocusableElements(elementsRef.current)
|
|
105
|
+
const el = focusableElements[focusableElements.length - 1]
|
|
106
|
+
|
|
107
|
+
if (!el) return
|
|
108
|
+
|
|
109
|
+
const currentIndex = elementsRef.current.indexOf(el)
|
|
110
|
+
|
|
111
|
+
setActiveIndex(currentIndex)
|
|
112
|
+
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (event.key === 'ArrowUp') {
|
|
117
|
+
event.preventDefault()
|
|
118
|
+
event.stopPropagation()
|
|
119
|
+
|
|
120
|
+
const focusableElements = _getFocusableElements(elementsRef.current)
|
|
121
|
+
const focusableLen = focusableElements.length
|
|
122
|
+
|
|
123
|
+
if (focusableLen === 0) return
|
|
124
|
+
|
|
125
|
+
const focusedElement = elementsRef.current[activeIndex]
|
|
126
|
+
|
|
127
|
+
let focusedIndex = focusableElements.indexOf(focusedElement)
|
|
128
|
+
|
|
129
|
+
focusedIndex = (focusedIndex - 1 + focusableLen) % focusableLen
|
|
130
|
+
|
|
131
|
+
const el = focusableElements[focusedIndex]
|
|
132
|
+
const currentIndex = elementsRef.current.indexOf(el)
|
|
133
|
+
|
|
134
|
+
setActiveIndex(currentIndex)
|
|
135
|
+
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (event.key === 'ArrowDown') {
|
|
140
|
+
event.preventDefault()
|
|
141
|
+
event.stopPropagation()
|
|
142
|
+
|
|
143
|
+
const focusableElements = _getFocusableElements(elementsRef.current)
|
|
144
|
+
const focusableLen = focusableElements.length
|
|
145
|
+
|
|
146
|
+
if (focusableLen === 0) return
|
|
147
|
+
|
|
148
|
+
const focusedElement = elementsRef.current[activeIndex]
|
|
149
|
+
|
|
150
|
+
let focusedIndex = focusableElements.indexOf(focusedElement)
|
|
151
|
+
|
|
152
|
+
focusedIndex = (focusedIndex + 1) % focusableLen
|
|
153
|
+
|
|
154
|
+
const el = focusableElements[focusedIndex]
|
|
155
|
+
const currentIndex = elementsRef.current.indexOf(el)
|
|
156
|
+
|
|
157
|
+
setActiveIndex(currentIndex)
|
|
158
|
+
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (onKeyDown) {
|
|
163
|
+
onKeyDown(event)
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
[activeIndex, onKeyDown, originElement, setActiveIndex]
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
const handleItemMouseEnter = useCallback(
|
|
170
|
+
(event: React.MouseEvent<HTMLElement>) => {
|
|
171
|
+
const element = event.currentTarget
|
|
172
|
+
const currentIndex = elementsRef.current.indexOf(element)
|
|
173
|
+
|
|
174
|
+
setActiveIndex(currentIndex)
|
|
175
|
+
},
|
|
176
|
+
[setActiveIndex]
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
const handleItemMouseLeave = useCallback(() => {
|
|
180
|
+
rootElement?.focus()
|
|
181
|
+
setActiveIndex(-1)
|
|
182
|
+
}, [rootElement, setActiveIndex])
|
|
183
|
+
|
|
184
|
+
// Set focus on the currently active element
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (!mounted) return
|
|
187
|
+
|
|
188
|
+
const rafId = window.requestAnimationFrame(() => {
|
|
189
|
+
const _activeIndex = activeIndexRef.current
|
|
190
|
+
|
|
191
|
+
if (_activeIndex === -1) {
|
|
192
|
+
if (shouldFocus === 'first') {
|
|
193
|
+
const focusableElements = _getFocusableElements(elementsRef.current)
|
|
194
|
+
const el = focusableElements[0]
|
|
195
|
+
|
|
196
|
+
if (el) {
|
|
197
|
+
const currentIndex = elementsRef.current.indexOf(el)
|
|
198
|
+
|
|
199
|
+
setActiveIndex(currentIndex)
|
|
200
|
+
activeIndexRef.current = currentIndex
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (shouldFocus === 'last') {
|
|
205
|
+
const focusableElements = _getFocusableElements(elementsRef.current)
|
|
206
|
+
const el = focusableElements[focusableElements.length - 1]
|
|
207
|
+
|
|
208
|
+
if (el) {
|
|
209
|
+
const currentIndex = elementsRef.current.indexOf(el)
|
|
210
|
+
|
|
211
|
+
setActiveIndex(currentIndex)
|
|
212
|
+
activeIndexRef.current = currentIndex
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const element = elementsRef.current[_activeIndex] || null
|
|
220
|
+
|
|
221
|
+
element?.focus()
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
return () => {
|
|
225
|
+
window.cancelAnimationFrame(rafId)
|
|
226
|
+
}
|
|
227
|
+
}, [activeIndex, mounted, setActiveIndex, shouldFocus])
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
activeElement,
|
|
231
|
+
activeIndex,
|
|
232
|
+
handleItemMouseEnter,
|
|
233
|
+
handleItemMouseLeave,
|
|
234
|
+
handleKeyDown,
|
|
235
|
+
mount,
|
|
236
|
+
rootElement,
|
|
237
|
+
setRootElement,
|
|
238
|
+
}
|
|
239
|
+
}
|