@sanity/ui 2.8.13 → 2.9.0-canary.1
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/getTheme_v2.js +84 -28
- package/dist/_chunks-cjs/getTheme_v2.js.map +1 -1
- package/dist/_chunks-es/getTheme_v2.mjs +84 -28
- package/dist/_chunks-es/getTheme_v2.mjs.map +1 -1
- package/dist/_legacy/getTheme_v2.esm.js +84 -28
- package/dist/_legacy/getTheme_v2.esm.js.map +1 -1
- package/dist/index.esm.js +3398 -3050
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +3399 -3052
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3398 -3050
- package/dist/index.mjs.map +1 -1
- package/dist/theme.esm.js +1197 -448
- package/dist/theme.esm.js.map +1 -1
- package/dist/theme.js +1197 -448
- package/dist/theme.js.map +1 -1
- package/dist/theme.mjs +1197 -448
- package/dist/theme.mjs.map +1 -1
- package/package.json +27 -34
- package/src/core/components/autocomplete/autocomplete.tsx +42 -31
- package/src/core/components/dialog/dialog.tsx +4 -2
- package/src/core/components/menu/menu.tsx +3 -1
- package/src/core/components/menu/menuGroup.tsx +2 -1
- package/src/core/components/menu/menuItem.tsx +4 -2
- package/src/core/components/menu/useMenuController.ts +18 -21
- package/src/core/components/tree/treeItem.tsx +13 -13
- package/src/core/hooks/useArrayProp.ts +14 -9
- package/src/core/hooks/useMatchMedia.ts +12 -22
- package/src/core/primitives/avatar/avatarStack.tsx +2 -5
- package/src/core/primitives/popover/popover.tsx +22 -26
- package/src/core/primitives/tooltip/tooltip.tsx +7 -4
- package/src/core/theme/themeProvider.tsx +4 -6
- package/src/core/utils/elementQuery/elementQuery.tsx +2 -1
- package/src/core/utils/portal/portalProvider.tsx +5 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0-canary.1",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"sanity",
|
|
6
6
|
"ui",
|
|
@@ -51,32 +51,6 @@
|
|
|
51
51
|
"src",
|
|
52
52
|
"theme.js"
|
|
53
53
|
],
|
|
54
|
-
"scripts": {
|
|
55
|
-
"build": "run-s clean pkg:build pkg:check figma:pkg:build",
|
|
56
|
-
"clean": "rimraf .workshop dist",
|
|
57
|
-
"commit": "cz",
|
|
58
|
-
"cypress:dev": "run-p dev cypress:open",
|
|
59
|
-
"cypress:open": "cypress open",
|
|
60
|
-
"cypress:run": "cypress run",
|
|
61
|
-
"dev": "run-p storybook:dev workshop:dev",
|
|
62
|
-
"figma:pkg:build": "cd figma && pnpm build",
|
|
63
|
-
"format": "prettier --write --cache --ignore-unknown .",
|
|
64
|
-
"lint": "eslint . --ext .cjs,.js,.jsx,.mjs,.ts,.tsx",
|
|
65
|
-
"pkg:build": "pkg build --strict",
|
|
66
|
-
"pkg:check": "pkg --strict",
|
|
67
|
-
"prepare": "husky install",
|
|
68
|
-
"prepack": "pnpm build",
|
|
69
|
-
"release": "semantic-release",
|
|
70
|
-
"storybook:build": "storybook build",
|
|
71
|
-
"storybook:dev": "storybook dev -p 6006",
|
|
72
|
-
"test": "jest",
|
|
73
|
-
"test:browser": "start-server-and-test 'run-s workshop:build workshop:start' http://localhost:1337 'run-s cypress:run'",
|
|
74
|
-
"ts:check": "tsc",
|
|
75
|
-
"watch": "pkg watch --strict",
|
|
76
|
-
"workshop:build": "workshop build",
|
|
77
|
-
"workshop:dev": "workshop dev",
|
|
78
|
-
"workshop:start": "http-server -a localhost -c-0 -p 1337 -s -P http://localhost:1337/index.html? dist"
|
|
79
|
-
},
|
|
80
54
|
"commitlint": {
|
|
81
55
|
"extends": [
|
|
82
56
|
"@commitlint/config-conventional"
|
|
@@ -110,6 +84,7 @@
|
|
|
110
84
|
"@sanity/icons": "^3.4.0",
|
|
111
85
|
"csstype": "^3.1.3",
|
|
112
86
|
"framer-motion": "11.0.8",
|
|
87
|
+
"react-compiler-runtime": "19.0.0-beta-6fc168f-20241025",
|
|
113
88
|
"react-refractor": "^2.2.0",
|
|
114
89
|
"use-effect-event": "^1.0.2"
|
|
115
90
|
},
|
|
@@ -198,19 +173,37 @@
|
|
|
198
173
|
"react-is": "^18",
|
|
199
174
|
"styled-components": "^5.2 || ^6"
|
|
200
175
|
},
|
|
201
|
-
"packageManager": "pnpm@9.12.3",
|
|
202
176
|
"engines": {
|
|
203
177
|
"node": ">=14.0.0"
|
|
204
178
|
},
|
|
205
179
|
"publishConfig": {
|
|
206
180
|
"access": "public"
|
|
207
181
|
},
|
|
208
|
-
"pnpm": {
|
|
209
|
-
"overrides": {
|
|
210
|
-
"conventional-changelog-conventionalcommits": ">= 8.0.0"
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
182
|
"esm.sh": {
|
|
214
183
|
"bundle": false
|
|
184
|
+
},
|
|
185
|
+
"scripts": {
|
|
186
|
+
"build": "run-s clean pkg:build pkg:check figma:pkg:build",
|
|
187
|
+
"clean": "rimraf .workshop dist",
|
|
188
|
+
"commit": "cz",
|
|
189
|
+
"cypress:dev": "run-p dev cypress:open",
|
|
190
|
+
"cypress:open": "cypress open",
|
|
191
|
+
"cypress:run": "cypress run",
|
|
192
|
+
"dev": "run-p storybook:dev workshop:dev",
|
|
193
|
+
"figma:pkg:build": "cd figma && pnpm build",
|
|
194
|
+
"format": "prettier --write --cache --ignore-unknown .",
|
|
195
|
+
"lint": "eslint . --ext .cjs,.js,.jsx,.mjs,.ts,.tsx",
|
|
196
|
+
"pkg:build": "pkg build --strict",
|
|
197
|
+
"pkg:check": "pkg --strict",
|
|
198
|
+
"release": "semantic-release",
|
|
199
|
+
"storybook:build": "storybook build",
|
|
200
|
+
"storybook:dev": "storybook dev -p 6006",
|
|
201
|
+
"test": "jest",
|
|
202
|
+
"test:browser": "start-server-and-test 'run-s workshop:build workshop:start' http://localhost:1337 'run-s cypress:run'",
|
|
203
|
+
"ts:check": "tsc",
|
|
204
|
+
"watch": "pkg watch --strict",
|
|
205
|
+
"workshop:build": "workshop build",
|
|
206
|
+
"workshop:dev": "workshop dev",
|
|
207
|
+
"workshop:start": "http-server -a localhost -c-0 -p 1337 -s -P http://localhost:1337/index.html? dist"
|
|
215
208
|
}
|
|
216
|
-
}
|
|
209
|
+
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
useMemo,
|
|
18
18
|
useReducer,
|
|
19
19
|
useRef,
|
|
20
|
+
useState,
|
|
20
21
|
} from 'react'
|
|
21
22
|
import {EMPTY_ARRAY, EMPTY_RECORD} from '../../constants'
|
|
22
23
|
import {_hasFocus, _raf, focusFirstDescendant} from '../../helpers'
|
|
@@ -184,21 +185,22 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
184
185
|
typeof filterOptionProp === 'function' ? filterOptionProp : DEFAULT_FILTER_OPTION
|
|
185
186
|
|
|
186
187
|
// Element refs
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
const
|
|
188
|
+
const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
|
|
189
|
+
const [resultsPopoverElement, setResultsPopoverElement] = useState<HTMLDivElement | null>(null)
|
|
190
|
+
const [inputElement, setInputElement] = useState<HTMLInputElement | null>(null)
|
|
190
191
|
const listBoxElementRef = useRef<HTMLDivElement | null>(null)
|
|
191
192
|
|
|
192
193
|
// Value refs
|
|
193
194
|
const listFocusedRef = useRef(false)
|
|
194
195
|
const valueRef = useRef(value)
|
|
195
196
|
const valuePropRef = useRef(valueProp)
|
|
196
|
-
const
|
|
197
|
+
const [popoverMouseWithin, setPopoverMouseWithin] = useState(false)
|
|
197
198
|
|
|
198
199
|
// Forward ref to parent
|
|
199
200
|
useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
|
|
200
201
|
forwardedRef,
|
|
201
|
-
() =>
|
|
202
|
+
() => inputElement,
|
|
203
|
+
[inputElement],
|
|
202
204
|
)
|
|
203
205
|
|
|
204
206
|
const listBoxId = `${id}-listbox`
|
|
@@ -222,13 +224,13 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
222
224
|
// NOTE: This is a workaround for a bug that may happen in Chrome (clicking the scrollbar
|
|
223
225
|
// closes the results in certain situations):
|
|
224
226
|
// - Do not handle blur if the mouse is within the popover
|
|
225
|
-
if (
|
|
227
|
+
if (popoverMouseWithin) {
|
|
226
228
|
return
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
const elements: HTMLElement[] = (relatedElements || []).concat(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
+
rootElement ? [rootElement] : [],
|
|
233
|
+
resultsPopoverElement ? [resultsPopoverElement] : [],
|
|
232
234
|
)
|
|
233
235
|
|
|
234
236
|
let focusInside = false
|
|
@@ -244,13 +246,20 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
244
246
|
|
|
245
247
|
if (focusInside === false) {
|
|
246
248
|
dispatch({type: 'root/blur'})
|
|
247
|
-
|
|
249
|
+
setPopoverMouseWithin(false)
|
|
248
250
|
if (onQueryChange) onQueryChange(null)
|
|
249
251
|
if (onBlur) onBlur(event)
|
|
250
252
|
}
|
|
251
253
|
}, 0)
|
|
252
254
|
},
|
|
253
|
-
[
|
|
255
|
+
[
|
|
256
|
+
onBlur,
|
|
257
|
+
onQueryChange,
|
|
258
|
+
popoverMouseWithin,
|
|
259
|
+
relatedElements,
|
|
260
|
+
resultsPopoverElement,
|
|
261
|
+
rootElement,
|
|
262
|
+
],
|
|
254
263
|
)
|
|
255
264
|
|
|
256
265
|
const handleRootFocus = useCallback((event: FocusEvent<HTMLDivElement>) => {
|
|
@@ -269,7 +278,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
269
278
|
(v: string) => {
|
|
270
279
|
dispatch({type: 'value/change', value: v})
|
|
271
280
|
|
|
272
|
-
|
|
281
|
+
setPopoverMouseWithin(false)
|
|
273
282
|
|
|
274
283
|
if (onSelect) onSelect(v)
|
|
275
284
|
|
|
@@ -278,9 +287,9 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
278
287
|
if (onChange) onChange(v)
|
|
279
288
|
if (onQueryChange) onQueryChange(null)
|
|
280
289
|
|
|
281
|
-
|
|
290
|
+
inputElement?.focus()
|
|
282
291
|
},
|
|
283
|
-
[onChange,
|
|
292
|
+
[onSelect, onChange, onQueryChange, inputElement],
|
|
284
293
|
)
|
|
285
294
|
|
|
286
295
|
const handleRootKeyDown = useCallback(
|
|
@@ -324,9 +333,9 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
324
333
|
|
|
325
334
|
if (event.key === 'Escape') {
|
|
326
335
|
dispatch({type: 'root/escape'})
|
|
327
|
-
|
|
336
|
+
setPopoverMouseWithin(false)
|
|
328
337
|
if (onQueryChange) onQueryChange(null)
|
|
329
|
-
|
|
338
|
+
inputElement?.focus()
|
|
330
339
|
|
|
331
340
|
return
|
|
332
341
|
}
|
|
@@ -338,12 +347,12 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
338
347
|
(listEl === target || listEl?.contains(target)) &&
|
|
339
348
|
!AUTOCOMPLETE_LISTBOX_IGNORE_KEYS.includes(event.key)
|
|
340
349
|
) {
|
|
341
|
-
|
|
350
|
+
inputElement?.focus()
|
|
342
351
|
|
|
343
352
|
return
|
|
344
353
|
}
|
|
345
354
|
},
|
|
346
|
-
[activeValue, filteredOptions, filteredOptionsLen, onQueryChange],
|
|
355
|
+
[activeValue, filteredOptions, filteredOptionsLen, inputElement, onQueryChange],
|
|
347
356
|
)
|
|
348
357
|
|
|
349
358
|
const handleInputChange = useCallback(
|
|
@@ -377,11 +386,11 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
377
386
|
)
|
|
378
387
|
|
|
379
388
|
const handlePopoverMouseEnter = useCallback(() => {
|
|
380
|
-
|
|
389
|
+
setPopoverMouseWithin(true)
|
|
381
390
|
}, [])
|
|
382
391
|
|
|
383
392
|
const handlePopoverMouseLeave = useCallback(() => {
|
|
384
|
-
|
|
393
|
+
setPopoverMouseWithin(false)
|
|
385
394
|
}, [])
|
|
386
395
|
|
|
387
396
|
const handleClearButtonClick = useCallback(() => {
|
|
@@ -389,8 +398,8 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
389
398
|
valueRef.current = ''
|
|
390
399
|
if (onChange) onChange('')
|
|
391
400
|
if (onQueryChange) onQueryChange(null)
|
|
392
|
-
|
|
393
|
-
}, [onChange, onQueryChange])
|
|
401
|
+
inputElement?.focus()
|
|
402
|
+
}, [inputElement, onChange, onQueryChange])
|
|
394
403
|
|
|
395
404
|
const handleClearButtonFocus = useCallback(() => {
|
|
396
405
|
dispatch({type: 'input/focus'})
|
|
@@ -482,9 +491,9 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
482
491
|
|
|
483
492
|
if (openButtonProps.onClick) openButtonProps.onClick(event)
|
|
484
493
|
|
|
485
|
-
_raf(() =>
|
|
494
|
+
_raf(() => inputElement?.focus())
|
|
486
495
|
},
|
|
487
|
-
[openButtonProps,
|
|
496
|
+
[dispatchOpen, openButtonProps, inputElement],
|
|
488
497
|
)
|
|
489
498
|
|
|
490
499
|
const openButtonNode = useMemo(
|
|
@@ -554,7 +563,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
554
563
|
prefix={prefix}
|
|
555
564
|
radius={radius}
|
|
556
565
|
readOnly={readOnly}
|
|
557
|
-
ref={
|
|
566
|
+
ref={setInputElement}
|
|
558
567
|
role="combobox"
|
|
559
568
|
spellCheck={false}
|
|
560
569
|
suffix={suffix || openButtonNode}
|
|
@@ -566,10 +575,10 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
566
575
|
(event: KeyboardEvent<HTMLDivElement>) => {
|
|
567
576
|
// If the focus is currently in the list, move focus to the input element
|
|
568
577
|
if (event.key === 'Tab') {
|
|
569
|
-
if (listFocused)
|
|
578
|
+
if (listFocused) inputElement?.focus()
|
|
570
579
|
}
|
|
571
580
|
},
|
|
572
|
-
[listFocused],
|
|
581
|
+
[inputElement, listFocused],
|
|
573
582
|
)
|
|
574
583
|
|
|
575
584
|
const content = useMemo(() => {
|
|
@@ -635,11 +644,11 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
635
644
|
{
|
|
636
645
|
content,
|
|
637
646
|
hidden: !expanded,
|
|
638
|
-
inputElement
|
|
647
|
+
inputElement,
|
|
639
648
|
onMouseEnter: handlePopoverMouseEnter,
|
|
640
649
|
onMouseLeave: handlePopoverMouseLeave,
|
|
641
650
|
},
|
|
642
|
-
|
|
651
|
+
{current: resultsPopoverElement},
|
|
643
652
|
)
|
|
644
653
|
}
|
|
645
654
|
|
|
@@ -661,8 +670,8 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
661
670
|
placement={AUTOCOMPLETE_POPOVER_PLACEMENT}
|
|
662
671
|
portal
|
|
663
672
|
radius={radius}
|
|
664
|
-
ref={
|
|
665
|
-
referenceElement={
|
|
673
|
+
ref={setResultsPopoverElement}
|
|
674
|
+
referenceElement={inputElement}
|
|
666
675
|
{...popover}
|
|
667
676
|
/>
|
|
668
677
|
)
|
|
@@ -672,9 +681,11 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
672
681
|
filteredOptionsLen,
|
|
673
682
|
handlePopoverMouseEnter,
|
|
674
683
|
handlePopoverMouseLeave,
|
|
684
|
+
inputElement,
|
|
675
685
|
popover,
|
|
676
686
|
radius,
|
|
677
687
|
renderPopover,
|
|
688
|
+
resultsPopoverElement,
|
|
678
689
|
])
|
|
679
690
|
|
|
680
691
|
return (
|
|
@@ -683,7 +694,7 @@ const InnerAutocomplete = forwardRef(function InnerAutocomplete<
|
|
|
683
694
|
onBlur={handleRootBlur}
|
|
684
695
|
onFocus={handleRootFocus}
|
|
685
696
|
onKeyDown={handleRootKeyDown}
|
|
686
|
-
ref={
|
|
697
|
+
ref={setRootElement}
|
|
687
698
|
>
|
|
688
699
|
{input}
|
|
689
700
|
{results}
|
|
@@ -305,13 +305,15 @@ export const Dialog = forwardRef(function Dialog(
|
|
|
305
305
|
onFocus,
|
|
306
306
|
padding: paddingProp = 3,
|
|
307
307
|
portal: portalProp,
|
|
308
|
-
position:
|
|
308
|
+
position: _positionProp,
|
|
309
309
|
scheme,
|
|
310
310
|
width: widthProp = 0,
|
|
311
|
-
zOffset:
|
|
311
|
+
zOffset: _zOffsetProp,
|
|
312
312
|
animate: _animate = false,
|
|
313
313
|
...restProps
|
|
314
314
|
} = props
|
|
315
|
+
const positionProp = _positionProp ?? (dialog.position || 'fixed')
|
|
316
|
+
const zOffsetProp = _zOffsetProp ?? (dialog.zOffset || layer.dialog.zOffset)
|
|
315
317
|
const prefersReducedMotion = usePrefersReducedMotion()
|
|
316
318
|
const animate = prefersReducedMotion ? false : _animate
|
|
317
319
|
const portal = usePortal()
|
|
@@ -57,10 +57,12 @@ export const Menu = forwardRef(function Menu(
|
|
|
57
57
|
originElement,
|
|
58
58
|
padding = 1,
|
|
59
59
|
registerElement,
|
|
60
|
-
shouldFocus
|
|
60
|
+
shouldFocus: _shouldFocus,
|
|
61
61
|
space = 1,
|
|
62
62
|
...restProps
|
|
63
63
|
} = props
|
|
64
|
+
const shouldFocus =
|
|
65
|
+
_shouldFocus ?? ((props.focusFirst && 'first') || (props.focusLast && 'last') || null)
|
|
64
66
|
|
|
65
67
|
const ref = useRef<HTMLDivElement | null>(null)
|
|
66
68
|
|
|
@@ -53,9 +53,10 @@ export function MenuGroup(
|
|
|
53
53
|
onClickOutside,
|
|
54
54
|
onEscape,
|
|
55
55
|
onItemClick,
|
|
56
|
-
onItemMouseEnter
|
|
56
|
+
onItemMouseEnter: _onItemMouseEnter,
|
|
57
57
|
registerElement,
|
|
58
58
|
} = menu
|
|
59
|
+
const onItemMouseEnter = _onItemMouseEnter ?? menu.onMouseEnter
|
|
59
60
|
const [rootElement, setRootElement] = useState<HTMLButtonElement | HTMLDivElement | null>(null)
|
|
60
61
|
const [open, setOpen] = useState(false)
|
|
61
62
|
const [shouldFocus, setShouldFocus] = useState<'first' | 'last' | null>(null)
|
|
@@ -72,9 +72,11 @@ export const MenuItem = forwardRef(function MenuItem(
|
|
|
72
72
|
activeElement,
|
|
73
73
|
mount,
|
|
74
74
|
onItemClick,
|
|
75
|
-
onItemMouseEnter
|
|
76
|
-
onItemMouseLeave
|
|
75
|
+
onItemMouseEnter: _onItemMouseEnter,
|
|
76
|
+
onItemMouseLeave: _onItemMouseLeave,
|
|
77
77
|
} = menu
|
|
78
|
+
const onItemMouseEnter = _onItemMouseEnter ?? menu.onMouseEnter
|
|
79
|
+
const onItemMouseLeave = _onItemMouseLeave ?? menu.onMouseLeave
|
|
78
80
|
const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null)
|
|
79
81
|
const active = Boolean(activeElement) && activeElement === rootElement
|
|
80
82
|
const ref = useRef<HTMLDivElement | null>(null)
|
|
@@ -36,31 +36,28 @@ export function useMenuController(props: {
|
|
|
36
36
|
activeIndexRef.current = nextActiveIndex
|
|
37
37
|
}, [])
|
|
38
38
|
|
|
39
|
-
const mount =
|
|
40
|
-
(element
|
|
41
|
-
if (!element) return () => undefined
|
|
39
|
+
const mount = (element: HTMLElement | null, selected?: boolean): (() => void) => {
|
|
40
|
+
if (!element) return () => undefined
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
if (elementsRef.current.indexOf(element) === -1) {
|
|
43
|
+
elementsRef.current.push(element)
|
|
44
|
+
_sortElements(rootElementRef.current, elementsRef.current)
|
|
45
|
+
}
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
if (selected) {
|
|
48
|
+
const selectedIndex = elementsRef.current.indexOf(element)
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
setActiveIndex(selectedIndex)
|
|
51
|
+
}
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
return () => {
|
|
54
|
+
const idx = elementsRef.current.indexOf(element)
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
56
|
+
if (idx > -1) {
|
|
57
|
+
elementsRef.current.splice(idx, 1)
|
|
60
58
|
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
64
61
|
|
|
65
62
|
const handleKeyDown = useCallback(
|
|
66
63
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
@@ -170,7 +167,7 @@ export function useMenuController(props: {
|
|
|
170
167
|
[setActiveIndex],
|
|
171
168
|
)
|
|
172
169
|
|
|
173
|
-
const handleItemMouseLeave =
|
|
170
|
+
const handleItemMouseLeave = () => {
|
|
174
171
|
// Set the active index to -2 to deactivate all menu items
|
|
175
172
|
// when the user moves the mouse away from the menu item.
|
|
176
173
|
// We avoid using -1 because it would focus the first menu item,
|
|
@@ -178,7 +175,7 @@ export function useMenuController(props: {
|
|
|
178
175
|
// between two menu items or a menu divider.
|
|
179
176
|
setActiveIndex(-2)
|
|
180
177
|
rootElementRef.current?.focus()
|
|
181
|
-
}
|
|
178
|
+
}
|
|
182
179
|
|
|
183
180
|
// Set focus on the currently active element
|
|
184
181
|
useEffect(() => {
|
|
@@ -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
|
import {Box, BoxProps, Flex, Text} from '../../primitives'
|
|
6
6
|
import {
|
|
@@ -64,7 +64,7 @@ export const TreeItem = memo(function TreeItem(
|
|
|
64
64
|
weight,
|
|
65
65
|
...restProps
|
|
66
66
|
} = props
|
|
67
|
-
const
|
|
67
|
+
const [rootElement, setRootElement] = useState<HTMLLIElement | null>(null)
|
|
68
68
|
const treeitemRef = useRef<HTMLDivElement | null>(null)
|
|
69
69
|
const tree = useTree()
|
|
70
70
|
const {path, registerItem, setExpanded, setFocusedElement} = tree
|
|
@@ -73,9 +73,9 @@ export const TreeItem = memo(function TreeItem(
|
|
|
73
73
|
const itemPath = useMemo(() => path.concat([id || '']), [id, path])
|
|
74
74
|
const itemKey = itemPath.join('/')
|
|
75
75
|
const itemState = tree.state[itemKey]
|
|
76
|
-
const focused = tree.focusedElement ===
|
|
76
|
+
const focused = tree.focusedElement === rootElement
|
|
77
77
|
const expanded = itemState?.expanded === undefined ? expandedProp : itemState?.expanded || false
|
|
78
|
-
const tabIndex = tree.focusedElement && tree.focusedElement ===
|
|
78
|
+
const tabIndex = tree.focusedElement && tree.focusedElement === rootElement ? 0 : -1
|
|
79
79
|
const contextValue = useMemo(
|
|
80
80
|
() => ({...tree, level: tree.level + 1, path: itemPath}),
|
|
81
81
|
[itemPath, tree],
|
|
@@ -94,28 +94,28 @@ export const TreeItem = memo(function TreeItem(
|
|
|
94
94
|
) {
|
|
95
95
|
event.stopPropagation()
|
|
96
96
|
setExpanded(itemKey, !expanded)
|
|
97
|
-
setFocusedElement(
|
|
97
|
+
setFocusedElement(rootElement)
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
|
-
[expanded, itemKey, onClick, setExpanded, setFocusedElement],
|
|
100
|
+
[expanded, itemKey, onClick, rootElement, setExpanded, setFocusedElement],
|
|
101
101
|
)
|
|
102
102
|
|
|
103
103
|
const handleKeyDown = useCallback(
|
|
104
104
|
(event: React.KeyboardEvent<HTMLElement>) => {
|
|
105
105
|
if (focused && event.key === 'Enter') {
|
|
106
|
-
const el = treeitemRef.current ||
|
|
106
|
+
const el = treeitemRef.current || rootElement
|
|
107
107
|
|
|
108
108
|
el?.click()
|
|
109
109
|
}
|
|
110
110
|
},
|
|
111
|
-
[focused],
|
|
111
|
+
[focused, rootElement],
|
|
112
112
|
)
|
|
113
113
|
|
|
114
114
|
useEffect(() => {
|
|
115
|
-
if (!
|
|
115
|
+
if (!rootElement) return
|
|
116
116
|
|
|
117
|
-
return registerItem(
|
|
118
|
-
}, [expanded, itemPath, registerItem, selected])
|
|
117
|
+
return registerItem(rootElement, itemPath.join('/'), expanded, selected)
|
|
118
|
+
}, [expanded, itemPath, registerItem, rootElement, selected])
|
|
119
119
|
|
|
120
120
|
const content = (
|
|
121
121
|
<Flex padding={padding}>
|
|
@@ -154,7 +154,7 @@ export const TreeItem = memo(function TreeItem(
|
|
|
154
154
|
data-ui="TreeItem"
|
|
155
155
|
{...restProps}
|
|
156
156
|
onClick={handleClick}
|
|
157
|
-
ref={
|
|
157
|
+
ref={setRootElement}
|
|
158
158
|
role="none"
|
|
159
159
|
>
|
|
160
160
|
<TreeItemBox
|
|
@@ -187,7 +187,7 @@ export const TreeItem = memo(function TreeItem(
|
|
|
187
187
|
aria-expanded={expanded}
|
|
188
188
|
onClick={handleClick}
|
|
189
189
|
onKeyDown={handleKeyDown}
|
|
190
|
-
ref={
|
|
190
|
+
ref={setRootElement}
|
|
191
191
|
role="treeitem"
|
|
192
192
|
tabIndex={tabIndex}
|
|
193
193
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {useState} from 'react'
|
|
2
2
|
import {_getArrayProp} from '../styles'
|
|
3
3
|
|
|
4
4
|
/** @beta */
|
|
@@ -12,14 +12,19 @@ export function useArrayProp<T extends ArrayPropPrimitive = ArrayPropPrimitive>(
|
|
|
12
12
|
val: T | T[] | undefined,
|
|
13
13
|
defaultVal?: T[],
|
|
14
14
|
): T[] {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const [[cachedVal, cachedHash], setCache] = useState<[T[], string]>(() => [
|
|
16
|
+
_getArrayProp(val, defaultVal),
|
|
17
|
+
JSON.stringify(val ?? defaultVal),
|
|
18
|
+
])
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
() => _getArrayProp(val, defaultVal),
|
|
20
|
+
const hash = JSON.stringify(val ?? defaultVal)
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
if (hash !== cachedHash) {
|
|
23
|
+
// If the cached hash has changed, update the cache right away.
|
|
24
|
+
// Calling setState during render is fine in this case, and preferred over a useEffect loop
|
|
25
|
+
// https://19.react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
|
|
26
|
+
setCache([_getArrayProp(val, defaultVal), hash])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return cachedVal
|
|
25
30
|
}
|
|
@@ -11,34 +11,24 @@ export function useMatchMedia(
|
|
|
11
11
|
mediaQueryString: `(${string})`,
|
|
12
12
|
getServerSnapshot?: () => boolean,
|
|
13
13
|
): boolean {
|
|
14
|
+
/**
|
|
15
|
+
* `subscribe` and `getSnapshot` are only called on the client and both need access to the same `matchMedia` instance
|
|
16
|
+
* we don't want to eagerly instantiate it to ensure it's only created when actually used
|
|
17
|
+
*/
|
|
18
|
+
const cachedMatchMedia = useMemo(
|
|
19
|
+
() => (typeof window === 'undefined' ? null : window.matchMedia(mediaQueryString)),
|
|
20
|
+
[mediaQueryString],
|
|
21
|
+
)
|
|
14
22
|
const {subscribe, getSnapshot} = useMemo(() => {
|
|
15
|
-
/**
|
|
16
|
-
* `subscribe` and `getSnapshot` are only called on the client and both need access to the same `matchMedia` instance
|
|
17
|
-
* we don't want to eagerly instantiate it to ensure it's only created when actually used
|
|
18
|
-
*/
|
|
19
|
-
let MEDIA_QUERY_CACHE: MediaQueryList | undefined
|
|
20
|
-
|
|
21
|
-
const getMatchMedia = (): MediaQueryList => {
|
|
22
|
-
if (!MEDIA_QUERY_CACHE) {
|
|
23
|
-
// As this function is only called during `subscribe` and `getSnapshot`, we can assume that the
|
|
24
|
-
// the `window` global is available and we're in a browser environment
|
|
25
|
-
MEDIA_QUERY_CACHE = window.matchMedia(mediaQueryString)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return MEDIA_QUERY_CACHE
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
return {
|
|
32
24
|
subscribe: (onStoreChange: () => void): (() => void) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
matchMedia.addEventListener('change', onStoreChange)
|
|
25
|
+
cachedMatchMedia!.addEventListener('change', onStoreChange)
|
|
36
26
|
|
|
37
|
-
return () =>
|
|
27
|
+
return () => cachedMatchMedia!.removeEventListener('change', onStoreChange)
|
|
38
28
|
},
|
|
39
|
-
getSnapshot: () =>
|
|
29
|
+
getSnapshot: () => cachedMatchMedia!.matches,
|
|
40
30
|
}
|
|
41
|
-
}, [
|
|
31
|
+
}, [cachedMatchMedia])
|
|
42
32
|
|
|
43
33
|
useDebugValue(mediaQueryString)
|
|
44
34
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {getTheme_v2} from '@sanity/ui/theme'
|
|
2
|
-
import {Children, cloneElement, forwardRef, isValidElement
|
|
2
|
+
import {Children, cloneElement, forwardRef, isValidElement} from 'react'
|
|
3
3
|
import {styled, css} from 'styled-components'
|
|
4
4
|
import {EMPTY_RECORD} from '../../constants'
|
|
5
5
|
import {useArrayProp} from '../../hooks'
|
|
@@ -65,10 +65,7 @@ export const AvatarStack = forwardRef(function AvatarStack(
|
|
|
65
65
|
size: sizeProp = 1,
|
|
66
66
|
...restProps
|
|
67
67
|
} = props
|
|
68
|
-
const children
|
|
69
|
-
() => Children.toArray(childrenProp).filter(isValidElement),
|
|
70
|
-
[childrenProp],
|
|
71
|
-
)
|
|
68
|
+
const children: React.ReactElement[] = Children.toArray(childrenProp).filter(isValidElement)
|
|
72
69
|
const maxLength = Math.max(maxLengthProp, 0)
|
|
73
70
|
const size = useArrayProp(sizeProp)
|
|
74
71
|
|