@sanity/ui 3.0.10 → 3.0.11
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/_chunks-cjs/_visual-editing.js +77 -63
- package/dist/_chunks-cjs/_visual-editing.js.map +1 -1
- package/dist/_chunks-es/_visual-editing.mjs +77 -63
- package/dist/_chunks-es/_visual-editing.mjs.map +1 -1
- package/dist/index.js +308 -98
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +310 -100
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/core/components/autocomplete/autocomplete.tsx +79 -47
- package/src/core/components/menu/menu.test.tsx +11 -14
- package/src/core/components/menu/menu.tsx +13 -18
- package/src/core/components/menu/menuContext.ts +3 -14
- package/src/core/components/menu/menuGroup.tsx +1 -1
- package/src/core/components/menu/menuItem.tsx +2 -2
- package/src/core/components/menu/useMenu.ts +2 -2
- package/src/core/components/menu/useMenuController.ts +5 -5
- package/src/core/components/tree/treeItem.tsx +17 -15
- package/src/core/utils/portal/portalProvider.tsx +2 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/ui",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.11",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"sanity",
|
|
6
6
|
"ui",
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
"@testing-library/user-event": "^14.6.1",
|
|
157
157
|
"@types/jest": "^30.0.0",
|
|
158
158
|
"@types/jest-axe": "^3.5.9",
|
|
159
|
-
"@types/node": "^22.18.
|
|
159
|
+
"@types/node": "^22.18.1",
|
|
160
160
|
"@types/react": "^19.1.11",
|
|
161
161
|
"@types/react-dom": "^19.1.8",
|
|
162
162
|
"@types/react-is": "^19.0.0",
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
useMemo,
|
|
17
17
|
useReducer,
|
|
18
18
|
useRef,
|
|
19
|
+
useState,
|
|
19
20
|
} from 'react'
|
|
20
21
|
|
|
21
22
|
import {EMPTY_ARRAY, EMPTY_RECORD} from '../../constants'
|
|
@@ -189,6 +190,8 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
189
190
|
const resultsPopoverElementRef = useRef<HTMLDivElement | null>(null)
|
|
190
191
|
const inputElementRef = useRef<HTMLInputElement | null>(null)
|
|
191
192
|
const listBoxElementRef = useRef<HTMLDivElement | null>(null)
|
|
193
|
+
// Element refs that need to be accessed during render
|
|
194
|
+
const [inputElement, setInputElement] = useState<HTMLInputElement | null>(null)
|
|
192
195
|
|
|
193
196
|
// Value refs
|
|
194
197
|
const listFocusedRef = useRef(false)
|
|
@@ -196,10 +199,17 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
196
199
|
const valuePropRef = useRef(valueProp)
|
|
197
200
|
const popoverMouseWithinRef = useRef(false)
|
|
198
201
|
|
|
199
|
-
// Forward
|
|
202
|
+
// Forward inputElement state to inputElementRef
|
|
203
|
+
useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
|
|
204
|
+
inputElementRef,
|
|
205
|
+
() => inputElement,
|
|
206
|
+
[inputElement],
|
|
207
|
+
)
|
|
208
|
+
// Forward inputElement to parent
|
|
200
209
|
useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
|
|
201
210
|
forwardedRef,
|
|
202
|
-
() =>
|
|
211
|
+
() => inputElement,
|
|
212
|
+
[inputElement],
|
|
203
213
|
)
|
|
204
214
|
|
|
205
215
|
const listBoxId = `${id}-listbox`
|
|
@@ -529,40 +539,6 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
529
539
|
return query
|
|
530
540
|
}, [currentOption, query, renderValue, value])
|
|
531
541
|
|
|
532
|
-
const input = (
|
|
533
|
-
<TextInput
|
|
534
|
-
{...restProps}
|
|
535
|
-
aria-activedescendant={activeItemId}
|
|
536
|
-
aria-autocomplete="list"
|
|
537
|
-
aria-expanded={expanded}
|
|
538
|
-
aria-owns={listBoxId}
|
|
539
|
-
autoCapitalize="off"
|
|
540
|
-
autoComplete="off"
|
|
541
|
-
autoCorrect="off"
|
|
542
|
-
border={border}
|
|
543
|
-
clearButton={clearButton}
|
|
544
|
-
customValidity={customValidity}
|
|
545
|
-
disabled={disabled}
|
|
546
|
-
fontSize={fontSize}
|
|
547
|
-
icon={icon}
|
|
548
|
-
iconRight={loading && AnimatedSpinnerIcon}
|
|
549
|
-
id={id}
|
|
550
|
-
inputMode="search"
|
|
551
|
-
onChange={handleInputChange}
|
|
552
|
-
onClear={handleClearButtonClick}
|
|
553
|
-
onFocus={handleInputFocus}
|
|
554
|
-
padding={padding}
|
|
555
|
-
prefix={prefix}
|
|
556
|
-
radius={radius}
|
|
557
|
-
readOnly={readOnly}
|
|
558
|
-
ref={inputElementRef}
|
|
559
|
-
role="combobox"
|
|
560
|
-
spellCheck={false}
|
|
561
|
-
suffix={suffix || openButtonNode}
|
|
562
|
-
value={inputValue}
|
|
563
|
-
/>
|
|
564
|
-
)
|
|
565
|
-
|
|
566
542
|
const handleListBoxKeyDown = useCallback(
|
|
567
543
|
(event: KeyboardEvent<HTMLDivElement>) => {
|
|
568
544
|
// If the focus is currently in the list, move focus to the input element
|
|
@@ -632,15 +608,16 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
632
608
|
|
|
633
609
|
const results = useMemo(() => {
|
|
634
610
|
if (renderPopover) {
|
|
635
|
-
return
|
|
636
|
-
|
|
637
|
-
content
|
|
638
|
-
hidden
|
|
639
|
-
inputElement
|
|
640
|
-
onMouseEnter
|
|
641
|
-
onMouseLeave
|
|
642
|
-
|
|
643
|
-
|
|
611
|
+
return (
|
|
612
|
+
<RenderPopover
|
|
613
|
+
content={content}
|
|
614
|
+
hidden={!expanded}
|
|
615
|
+
inputElement={inputElement}
|
|
616
|
+
onMouseEnter={handlePopoverMouseEnter}
|
|
617
|
+
onMouseLeave={handlePopoverMouseLeave}
|
|
618
|
+
resultsPopoverElementRef={resultsPopoverElementRef}
|
|
619
|
+
renderPopover={renderPopover}
|
|
620
|
+
/>
|
|
644
621
|
)
|
|
645
622
|
}
|
|
646
623
|
|
|
@@ -663,7 +640,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
663
640
|
portal
|
|
664
641
|
radius={radius}
|
|
665
642
|
ref={resultsPopoverElementRef}
|
|
666
|
-
referenceElement={
|
|
643
|
+
referenceElement={inputElement}
|
|
667
644
|
{...popover}
|
|
668
645
|
/>
|
|
669
646
|
)
|
|
@@ -673,6 +650,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
673
650
|
filteredOptionsLen,
|
|
674
651
|
handlePopoverMouseEnter,
|
|
675
652
|
handlePopoverMouseLeave,
|
|
653
|
+
inputElement,
|
|
676
654
|
popover,
|
|
677
655
|
radius,
|
|
678
656
|
renderPopover,
|
|
@@ -686,12 +664,66 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
686
664
|
onKeyDown={handleRootKeyDown}
|
|
687
665
|
ref={rootElementRef}
|
|
688
666
|
>
|
|
689
|
-
|
|
667
|
+
<TextInput
|
|
668
|
+
{...restProps}
|
|
669
|
+
aria-activedescendant={activeItemId}
|
|
670
|
+
aria-autocomplete="list"
|
|
671
|
+
aria-expanded={expanded}
|
|
672
|
+
aria-owns={listBoxId}
|
|
673
|
+
autoCapitalize="off"
|
|
674
|
+
autoComplete="off"
|
|
675
|
+
autoCorrect="off"
|
|
676
|
+
border={border}
|
|
677
|
+
clearButton={clearButton}
|
|
678
|
+
customValidity={customValidity}
|
|
679
|
+
disabled={disabled}
|
|
680
|
+
fontSize={fontSize}
|
|
681
|
+
icon={icon}
|
|
682
|
+
iconRight={loading && AnimatedSpinnerIcon}
|
|
683
|
+
id={id}
|
|
684
|
+
inputMode="search"
|
|
685
|
+
onChange={handleInputChange}
|
|
686
|
+
onClear={handleClearButtonClick}
|
|
687
|
+
onFocus={handleInputFocus}
|
|
688
|
+
padding={padding}
|
|
689
|
+
prefix={prefix}
|
|
690
|
+
radius={radius}
|
|
691
|
+
readOnly={readOnly}
|
|
692
|
+
ref={setInputElement}
|
|
693
|
+
role="combobox"
|
|
694
|
+
spellCheck={false}
|
|
695
|
+
suffix={suffix || openButtonNode}
|
|
696
|
+
value={inputValue}
|
|
697
|
+
/>
|
|
690
698
|
{results}
|
|
691
699
|
</StyledAutocomplete>
|
|
692
700
|
)
|
|
693
701
|
})
|
|
694
702
|
|
|
703
|
+
function RenderPopover({
|
|
704
|
+
renderPopover,
|
|
705
|
+
content,
|
|
706
|
+
hidden,
|
|
707
|
+
inputElement,
|
|
708
|
+
onMouseEnter,
|
|
709
|
+
onMouseLeave,
|
|
710
|
+
resultsPopoverElementRef,
|
|
711
|
+
}: {
|
|
712
|
+
renderPopover: Exclude<AutocompleteProps['renderPopover'], undefined>
|
|
713
|
+
resultsPopoverElementRef: Parameters<Exclude<AutocompleteProps['renderPopover'], undefined>>[1]
|
|
714
|
+
} & Parameters<Exclude<AutocompleteProps['renderPopover'], undefined>>[0]) {
|
|
715
|
+
return renderPopover(
|
|
716
|
+
{
|
|
717
|
+
content,
|
|
718
|
+
hidden,
|
|
719
|
+
inputElement,
|
|
720
|
+
onMouseEnter,
|
|
721
|
+
onMouseLeave,
|
|
722
|
+
},
|
|
723
|
+
resultsPopoverElementRef,
|
|
724
|
+
)
|
|
725
|
+
}
|
|
726
|
+
|
|
695
727
|
InnerAutocomplete.displayName = 'ForwardRef(Autocomplete)'
|
|
696
728
|
|
|
697
729
|
/**
|
|
@@ -32,18 +32,16 @@ describe('components/menu', () => {
|
|
|
32
32
|
[],
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
-
const value
|
|
36
|
-
() =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
onMouseLeave: handleItemMouseLeave,
|
|
46
|
-
}),
|
|
35
|
+
const value = useMemo(
|
|
36
|
+
() =>
|
|
37
|
+
({
|
|
38
|
+
version: 2,
|
|
39
|
+
activeElement: null,
|
|
40
|
+
mount: (element: HTMLElement | null) => () => console.log(element),
|
|
41
|
+
onItemClick: () => undefined,
|
|
42
|
+
onItemMouseEnter: handleItemMouseEnter,
|
|
43
|
+
onItemMouseLeave: handleItemMouseLeave,
|
|
44
|
+
}) satisfies MenuContextValue,
|
|
47
45
|
[handleItemMouseEnter, handleItemMouseLeave],
|
|
48
46
|
)
|
|
49
47
|
|
|
@@ -58,8 +56,7 @@ describe('components/menu', () => {
|
|
|
58
56
|
|
|
59
57
|
const contextValue = log.mock.calls[0][0]
|
|
60
58
|
|
|
61
|
-
expect(contextValue.version).toBe(
|
|
62
|
-
expect(contextValue.activeIndex).toBe(0)
|
|
59
|
+
expect(contextValue.version).toBe(2)
|
|
63
60
|
expect(typeof contextValue.mount).toBe('function')
|
|
64
61
|
expect(typeof contextValue.onItemClick).toBe('function')
|
|
65
62
|
expect(typeof contextValue.onItemMouseEnter).toBe('function')
|
|
@@ -128,26 +128,21 @@ export const Menu = forwardRef(function Menu(
|
|
|
128
128
|
),
|
|
129
129
|
)
|
|
130
130
|
|
|
131
|
-
const value
|
|
132
|
-
() =>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// deprecated
|
|
145
|
-
onMouseEnter: handleItemMouseEnter,
|
|
146
|
-
onMouseLeave: handleItemMouseLeave,
|
|
147
|
-
}),
|
|
131
|
+
const value = useMemo(
|
|
132
|
+
() =>
|
|
133
|
+
({
|
|
134
|
+
version: 2,
|
|
135
|
+
activeElement,
|
|
136
|
+
mount,
|
|
137
|
+
onClickOutside,
|
|
138
|
+
onEscape,
|
|
139
|
+
onItemClick,
|
|
140
|
+
onItemMouseEnter: handleItemMouseEnter,
|
|
141
|
+
onItemMouseLeave: handleItemMouseLeave,
|
|
142
|
+
registerElement,
|
|
143
|
+
}) satisfies MenuContextValue,
|
|
148
144
|
[
|
|
149
145
|
activeElement,
|
|
150
|
-
activeIndex,
|
|
151
146
|
mount,
|
|
152
147
|
handleItemMouseEnter,
|
|
153
148
|
handleItemMouseLeave,
|
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
import {createGlobalScopedContext} from '../../lib/createGlobalScopedContext'
|
|
2
2
|
|
|
3
3
|
export interface MenuContextValue {
|
|
4
|
-
version:
|
|
4
|
+
version: 2
|
|
5
5
|
activeElement: HTMLElement | null
|
|
6
|
-
activeIndex: number
|
|
7
6
|
mount: (element: HTMLElement | null, selected?: boolean) => () => void
|
|
8
7
|
onClickOutside?: (event: MouseEvent) => void
|
|
9
8
|
onEscape?: () => void
|
|
10
9
|
onItemClick?: () => void
|
|
11
|
-
onItemMouseEnter
|
|
12
|
-
onItemMouseLeave
|
|
10
|
+
onItemMouseEnter: (event: React.MouseEvent<HTMLElement>) => void
|
|
11
|
+
onItemMouseLeave: (event: React.MouseEvent<HTMLElement>) => void
|
|
13
12
|
registerElement?: (el: HTMLElement) => () => void
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @deprecated Use `onItemMouseEnter` instead
|
|
17
|
-
*/
|
|
18
|
-
onMouseEnter: (event: React.MouseEvent<HTMLElement>) => void
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @deprecated Use `onItemMouseLeave` instead
|
|
22
|
-
*/
|
|
23
|
-
onMouseLeave: (event: React.MouseEvent<HTMLElement>) => void
|
|
24
13
|
}
|
|
25
14
|
|
|
26
15
|
export const MenuContext = createGlobalScopedContext<MenuContextValue | null>(
|
|
@@ -69,7 +69,7 @@ export function MenuGroup(
|
|
|
69
69
|
onItemMouseEnter: _onItemMouseEnter,
|
|
70
70
|
registerElement,
|
|
71
71
|
} = menu
|
|
72
|
-
const onItemMouseEnter = _onItemMouseEnter ?? menu.
|
|
72
|
+
const onItemMouseEnter = _onItemMouseEnter ?? menu.onItemMouseEnter
|
|
73
73
|
const [rootElement, setRootElement] = useState<HTMLButtonElement | HTMLDivElement | null>(null)
|
|
74
74
|
const [open, setOpen] = useState(false)
|
|
75
75
|
const [shouldFocus, setShouldFocus] = useState<'first' | 'last' | null>(null)
|
|
@@ -76,8 +76,8 @@ export const MenuItem = forwardRef(function MenuItem(
|
|
|
76
76
|
onItemMouseEnter: _onItemMouseEnter,
|
|
77
77
|
onItemMouseLeave: _onItemMouseLeave,
|
|
78
78
|
} = menu
|
|
79
|
-
const onItemMouseEnter = _onItemMouseEnter ?? menu.
|
|
80
|
-
const onItemMouseLeave = _onItemMouseLeave ?? menu.
|
|
79
|
+
const onItemMouseEnter = _onItemMouseEnter ?? menu.onItemMouseEnter
|
|
80
|
+
const onItemMouseLeave = _onItemMouseLeave ?? menu.onItemMouseLeave
|
|
81
81
|
const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
|
|
82
82
|
const active = Boolean(activeElement) && activeElement === rootElement
|
|
83
83
|
const ref = useRef<HTMLDivElement | null>(null)
|
|
@@ -12,8 +12,8 @@ export function useMenu(): MenuContextValue {
|
|
|
12
12
|
|
|
13
13
|
// NOTE: This check is for future-compatiblity
|
|
14
14
|
// - If the value is not an object, it’s not compatible with the current version
|
|
15
|
-
// - If the value is an object, but doesn’t have `version:
|
|
16
|
-
if (!isRecord(value) || value.version !==
|
|
15
|
+
// - If the value is an object, but doesn’t have `version: 2`, it’s not compatible with the current version
|
|
16
|
+
if (!isRecord(value) || value.version !== 2) {
|
|
17
17
|
throw new Error('useMenu(): the context value is not compatible')
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {useCallback, useEffect,
|
|
1
|
+
import {useCallback, useEffect, useRef, useState} from 'react'
|
|
2
2
|
|
|
3
3
|
import {_getFocusableElements, _sortElements} from './helpers'
|
|
4
4
|
|
|
@@ -29,12 +29,12 @@ export function useMenuController(props: {
|
|
|
29
29
|
const elementsRef = useRef<HTMLElement[]>([])
|
|
30
30
|
const [activeIndex, _setActiveIndex] = useState(-1)
|
|
31
31
|
const activeIndexRef = useRef(activeIndex)
|
|
32
|
-
const activeElement =
|
|
33
|
-
const mounted = Boolean(rootElementRef.current)
|
|
32
|
+
const [activeElement, setActiveElement] = useState<HTMLElement | null>(null)
|
|
34
33
|
|
|
35
34
|
const setActiveIndex = useCallback((nextActiveIndex: number) => {
|
|
36
35
|
_setActiveIndex(nextActiveIndex)
|
|
37
36
|
activeIndexRef.current = nextActiveIndex
|
|
37
|
+
setActiveElement(elementsRef.current[nextActiveIndex] || null)
|
|
38
38
|
}, [])
|
|
39
39
|
|
|
40
40
|
const mount = useCallback(
|
|
@@ -183,7 +183,7 @@ export function useMenuController(props: {
|
|
|
183
183
|
|
|
184
184
|
// Set focus on the currently active element
|
|
185
185
|
useEffect(() => {
|
|
186
|
-
if (!
|
|
186
|
+
if (!rootElementRef.current) return
|
|
187
187
|
|
|
188
188
|
const rafId = requestAnimationFrame(() => {
|
|
189
189
|
if (activeIndex === -1) {
|
|
@@ -218,7 +218,7 @@ export function useMenuController(props: {
|
|
|
218
218
|
})
|
|
219
219
|
|
|
220
220
|
return () => cancelAnimationFrame(rafId)
|
|
221
|
-
}, [activeIndex,
|
|
221
|
+
}, [activeIndex, rootElementRef, setActiveIndex, shouldFocus])
|
|
222
222
|
|
|
223
223
|
return {
|
|
224
224
|
activeElement,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {ToggleArrowRightIcon} from '@sanity/icons'
|
|
2
2
|
import {ThemeFontWeightKey} from '@sanity/ui/theme'
|
|
3
|
-
import {memo, useCallback, useEffect, useId, useMemo, useRef} from 'react'
|
|
3
|
+
import {memo, useCallback, useEffect, useId, useMemo, useRef, useState} from 'react'
|
|
4
4
|
import {styled} from 'styled-components'
|
|
5
5
|
|
|
6
6
|
import {Box, BoxProps, Flex, Text} from '../../primitives'
|
|
@@ -65,18 +65,20 @@ export const TreeItem = memo(function TreeItem(
|
|
|
65
65
|
weight,
|
|
66
66
|
...restProps
|
|
67
67
|
} = props
|
|
68
|
-
const
|
|
68
|
+
const [rootElement, setRootElement] = useState<HTMLLIElement | null>(null)
|
|
69
69
|
const treeitemRef = useRef<HTMLDivElement | null>(null)
|
|
70
70
|
const tree = useTree()
|
|
71
71
|
const {path, registerItem, setExpanded, setFocusedElement} = tree
|
|
72
72
|
const _id = useId()
|
|
73
73
|
const id = idProp || _id
|
|
74
|
-
const itemPath = useMemo(() =>
|
|
75
|
-
|
|
74
|
+
const [itemPath, itemKey] = useMemo(() => {
|
|
75
|
+
const itemPath = path.concat([id || ''])
|
|
76
|
+
return [itemPath, itemPath.join('/')]
|
|
77
|
+
}, [id, path])
|
|
76
78
|
const itemState = tree.state[itemKey]
|
|
77
|
-
const focused = tree.focusedElement ===
|
|
79
|
+
const focused = tree.focusedElement === rootElement
|
|
78
80
|
const expanded = itemState?.expanded === undefined ? expandedProp : itemState?.expanded || false
|
|
79
|
-
const tabIndex = tree.focusedElement && tree.focusedElement ===
|
|
81
|
+
const tabIndex = tree.focusedElement && tree.focusedElement === rootElement ? 0 : -1
|
|
80
82
|
const contextValue = useMemo(
|
|
81
83
|
() => ({...tree, level: tree.level + 1, path: itemPath}),
|
|
82
84
|
[itemPath, tree],
|
|
@@ -95,28 +97,28 @@ export const TreeItem = memo(function TreeItem(
|
|
|
95
97
|
) {
|
|
96
98
|
event.stopPropagation()
|
|
97
99
|
setExpanded(itemKey, !expanded)
|
|
98
|
-
setFocusedElement(
|
|
100
|
+
setFocusedElement(rootElement)
|
|
99
101
|
}
|
|
100
102
|
},
|
|
101
|
-
[expanded, itemKey, onClick, setExpanded, setFocusedElement],
|
|
103
|
+
[expanded, itemKey, onClick, rootElement, setExpanded, setFocusedElement],
|
|
102
104
|
)
|
|
103
105
|
|
|
104
106
|
const handleKeyDown = useCallback(
|
|
105
107
|
(event: React.KeyboardEvent<HTMLElement>) => {
|
|
106
108
|
if (focused && event.key === 'Enter') {
|
|
107
|
-
const el = treeitemRef.current ||
|
|
109
|
+
const el = treeitemRef.current || rootElement
|
|
108
110
|
|
|
109
111
|
el?.click()
|
|
110
112
|
}
|
|
111
113
|
},
|
|
112
|
-
[focused],
|
|
114
|
+
[focused, rootElement],
|
|
113
115
|
)
|
|
114
116
|
|
|
115
117
|
useEffect(() => {
|
|
116
|
-
if (!
|
|
118
|
+
if (!rootElement) return
|
|
117
119
|
|
|
118
|
-
return registerItem(
|
|
119
|
-
}, [expanded,
|
|
120
|
+
return registerItem(rootElement, itemKey, expanded, selected)
|
|
121
|
+
}, [expanded, itemKey, registerItem, rootElement, selected])
|
|
120
122
|
|
|
121
123
|
const content = (
|
|
122
124
|
<Flex padding={padding}>
|
|
@@ -155,7 +157,7 @@ export const TreeItem = memo(function TreeItem(
|
|
|
155
157
|
data-ui="TreeItem"
|
|
156
158
|
{...restProps}
|
|
157
159
|
onClick={handleClick}
|
|
158
|
-
ref={
|
|
160
|
+
ref={setRootElement}
|
|
159
161
|
role="none"
|
|
160
162
|
>
|
|
161
163
|
<TreeItemBox
|
|
@@ -188,7 +190,7 @@ export const TreeItem = memo(function TreeItem(
|
|
|
188
190
|
aria-expanded={expanded}
|
|
189
191
|
onClick={handleClick}
|
|
190
192
|
onKeyDown={handleKeyDown}
|
|
191
|
-
ref={
|
|
193
|
+
ref={setRootElement}
|
|
192
194
|
role="treeitem"
|
|
193
195
|
tabIndex={tabIndex}
|
|
194
196
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {useMemo,
|
|
1
|
+
import {useMemo, useSyncExternalStore} from 'react'
|
|
2
2
|
|
|
3
3
|
import {PortalContext} from './portalContext'
|
|
4
4
|
import {PortalContextValue} from './types'
|
|
@@ -23,8 +23,7 @@ export interface PortalProviderProps {
|
|
|
23
23
|
* @public
|
|
24
24
|
*/
|
|
25
25
|
export function PortalProvider(props: PortalProviderProps): React.JSX.Element {
|
|
26
|
-
const {boundaryElement, children, element, __unstable_elements:
|
|
27
|
-
const elements = useUnique(elementsProp)
|
|
26
|
+
const {boundaryElement, children, element, __unstable_elements: elements} = props
|
|
28
27
|
const fallbackElement = useSyncExternalStore(
|
|
29
28
|
emptySubscribe,
|
|
30
29
|
() => document.body,
|
|
@@ -46,34 +45,3 @@ export function PortalProvider(props: PortalProviderProps): React.JSX.Element {
|
|
|
46
45
|
PortalProvider.displayName = 'PortalProvider'
|
|
47
46
|
|
|
48
47
|
const emptySubscribe = () => () => {}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* This is a React hook to make sure that a record identity is the same on every render. Uses strict
|
|
52
|
-
* equality comparison (eg by identity), and only goes one level deep.
|
|
53
|
-
*/
|
|
54
|
-
function useUnique<ValueType extends Comparable = Comparable>(value: ValueType): ValueType {
|
|
55
|
-
const valueRef = useRef<ValueType>(value)
|
|
56
|
-
|
|
57
|
-
if (!_isEqual(valueRef.current, value)) {
|
|
58
|
-
valueRef.current = value
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return valueRef.current
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function _isEqual(objA: Comparable, objB: Comparable): boolean {
|
|
65
|
-
if (!objA || !objB) {
|
|
66
|
-
return objA === objB
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const keysA = Object.keys(objA)
|
|
70
|
-
const keysB = Object.keys(objB)
|
|
71
|
-
|
|
72
|
-
if (keysA.length !== keysB.length) {
|
|
73
|
-
return false
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return keysA.every((key) => objA[key] === objB[key])
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
type Comparable = Record<string | number | symbol, unknown> | undefined | null
|