@sanity/ui 2.6.4-canary.2 → 2.6.5

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.
Files changed (37) hide show
  1. package/dist/index.d.mts +6 -42
  2. package/dist/index.d.ts +6 -42
  3. package/dist/index.esm.js +164 -97
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.js +162 -95
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +164 -97
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +34 -26
  10. package/src/core/components/breadcrumbs/breadcrumbs.tsx +6 -15
  11. package/src/core/components/dialog/dialog.tsx +21 -12
  12. package/src/core/components/menu/menu.tsx +18 -11
  13. package/src/core/components/menu/menuButton.tsx +1 -1
  14. package/src/core/components/menu/menuContext.ts +1 -1
  15. package/src/core/components/menu/useMenuController.ts +17 -11
  16. package/src/core/components/toast/styles.ts +1 -2
  17. package/src/core/components/toast/useToast.ts +0 -1
  18. package/src/core/components/tree/tree.tsx +0 -1
  19. package/src/core/components/tree/treeItem.tsx +0 -1
  20. package/src/core/helpers/focus.ts +0 -1
  21. package/src/core/helpers/scroll.ts +0 -1
  22. package/src/core/hooks/_internal/index.ts +1 -0
  23. package/src/core/hooks/_internal/useUnique.ts +34 -0
  24. package/src/core/hooks/index.ts +2 -3
  25. package/src/core/hooks/useClickOutside.ts +72 -38
  26. package/src/core/hooks/useElementSize.ts +0 -1
  27. package/src/core/hooks/useMediaIndex/useMediaIndex.ts +10 -2
  28. package/src/core/hooks/usePrefersDark.ts +55 -10
  29. package/src/core/hooks/usePrefersReducedMotion.ts +56 -10
  30. package/src/core/primitives/popover/__workshop__/AlignedStory.tsx +7 -9
  31. package/src/core/primitives/tooltip/__workshop__/customPortal.tsx +6 -8
  32. package/src/core/primitives/tooltip/tooltip.tsx +29 -31
  33. package/src/core/primitives/tooltip/tooltipDelayGroup/tooltipDelayGroupProvider.tsx +0 -1
  34. package/src/core/utils/layer/layerProvider.tsx +0 -1
  35. package/src/core/utils/portal/__workshop__/named.tsx +8 -10
  36. package/src/core/utils/portal/portalProvider.tsx +3 -2
  37. package/src/core/hooks/useMatchMedia.ts +0 -46
@@ -1,16 +1,61 @@
1
- import {useMatchMedia} from './useMatchMedia'
1
+ import {useSyncExternalStore} from 'react'
2
+
3
+ let MEDIA_QUERY_CACHE: MediaQueryList | undefined
2
4
 
3
5
  /**
4
- * Returns true if a dark color scheme is preferred, false if a light color scheme is preferred or the preference is not known.
5
- *
6
- * @param getServerSnapshot - Only called during server-side rendering, and hydration if using hydrateRoot. Since the server environment doesn't have access to the DOM, we can't determine the current value of the media query and we assume `(prefers-color-scheme: light)` since it's the most common scheme (https://react.dev/reference/react/useSyncExternalStore#adding-support-for-server-rendering)
7
- *
8
- * If you persist the detected preference in a cookie or a header then you may implement your own server snapshot to read it.
9
- * Chrome supports reading the `prefers-color-scheme` media query from a header if the server response: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Color-Scheme
10
- * @example https://gist.github.com/stipsan/13c0cccf8dfc34f4b44bb1b984baf7df
6
+ * Lazy init the matchMedia instance
7
+ */
8
+ function getMatchMedia(): MediaQueryList {
9
+ if (!MEDIA_QUERY_CACHE) {
10
+ // As this function is only called during `subscribe` and `getSnapshot`, we can assume that the
11
+ // the `window` global is available and we're in a browser environment
12
+ MEDIA_QUERY_CACHE = window.matchMedia('(prefers-color-scheme: dark)')
13
+ }
14
+
15
+ return MEDIA_QUERY_CACHE
16
+ }
17
+
18
+ /**
19
+ * As the query is the same for all instances of this hook, we can cache the matchMedia instance
20
+ * and have cheap `change` event listeners, while getSnapshot always reads from the same
21
+ * matchMedia instance and we don't get any tearing.
22
+ * Tearing in this context means the bad edge case in React concurrent render mdoe
23
+ * where you sometimes would end up with some components doing render while seeing `usePrefersDark() === true` while others would see `usePrefersDark() === false`
24
+ * during the same render.
25
+ * By using `useSyncExternalStore` every component only sees the same value during the same render, and always re-render when it changes no matter
26
+ * what React.memo boundaries there might be between the layers..
27
+ */
28
+ function subscribe(onStoreChange: () => void): () => void {
29
+ const matchMedia = getMatchMedia()
30
+
31
+ matchMedia.addEventListener('change', onStoreChange)
32
+
33
+ return () => matchMedia.removeEventListener('change', onStoreChange)
34
+ }
35
+
36
+ /**
37
+ * Only called client-side, when using createRoot, or after hydration is complete when using hydrateRoot.
38
+ * It's important that this function does not create new objects or arrays when called:
39
+ * https://beta.reactjs.org/apis/react/useSyncExternalStore#im-getting-an-error-the-result-of-getsnapshot-should-be-cached
40
+ */
41
+ function getSnapshot() {
42
+ return getMatchMedia().matches
43
+ }
44
+
45
+ /**
46
+ * Only called during server-side rendering, and hydration if using hydrateRoot
47
+ * Since the server environment doesn't have access to the DOM, we can't determine the current value of the media query
48
+ * and we assume `(prefers-color-scheme: light)` since it's the most common scheme
11
49
  *
50
+ * @link https://beta.reactjs.org/apis/react/useSyncExternalStore#adding-support-for-server-rendering
51
+ */
52
+ function getServerSnapshot() {
53
+ return false
54
+ }
55
+
56
+ /**
12
57
  * @public
13
58
  */
14
- export function usePrefersDark(getServerSnapshot = () => false): boolean {
15
- return useMatchMedia('(prefers-color-scheme: dark)', getServerSnapshot)
59
+ export function usePrefersDark(): boolean {
60
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
16
61
  }
@@ -1,16 +1,62 @@
1
- import {useMatchMedia} from './useMatchMedia'
1
+ import {useSyncExternalStore} from 'react'
2
+
3
+ let MEDIA_QUERY_CACHE: MediaQueryList | undefined
2
4
 
3
5
  /**
4
- * Returns true if motion should be reduced
5
- *
6
- * @param getServerSnapshot - Only called during server-side rendering, and hydration if using hydrateRoot. Since the server environment doesn't have access to the DOM, we can't determine the current value of the media query and we assume `(prefers-reduced-motion: no-preference)` since it's the most common scheme (https://react.dev/reference/react/useSyncExternalStore#adding-support-for-server-rendering)
7
- *
8
- * If you persist the detected preference in a cookie or a header then you may implement your own server snapshot to read it.
9
- * Chrome supports reading the `prefers-reduced-motion` media query from a header if the server response: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-Prefers-Reduced-Motion
10
- * @example https://gist.github.com/stipsan/0c0f839a27842249cada893e9fb7767b
6
+ * Lazy init the matchMedia instance
7
+ */
8
+ function getMatchMedia(): MediaQueryList {
9
+ if (!MEDIA_QUERY_CACHE) {
10
+ // As this function is only called during `subscribe` and `getSnapshot`, we can assume that the
11
+ // the `window` global is available and we're in a browser environment
12
+ MEDIA_QUERY_CACHE = window.matchMedia('(prefers-reduced-motion: reduce)')
13
+ }
14
+
15
+ return MEDIA_QUERY_CACHE
16
+ }
17
+
18
+ /**
19
+ * As the query is the same for all instances of this hook, we can cache the matchMedia instance
20
+ * and have cheap `change` event listeners, while getSnapshot always reads from the same
21
+ * matchMedia instance and we don't get any tearing.
22
+ * Tearing in this context means the bad edge case in React concurrent render mdoe
23
+ * where you sometimes would end up with some components doing render while seeing `usePrefersDark() === true` while others would see `usePrefersDark() === false`
24
+ * during the same render.
25
+ * By using `useSyncExternalStore` every component only sees the same value during the same render, and always re-render when it changes no matter
26
+ * what React.memo boundaries there might be between the layers..
27
+ */
28
+ function subscribe(onStoreChange: () => void): () => void {
29
+ const matchMedia = getMatchMedia()
30
+
31
+ matchMedia.addEventListener('change', onStoreChange)
32
+
33
+ return () => matchMedia.removeEventListener('change', onStoreChange)
34
+ }
35
+
36
+ /**
37
+ * Only called client-side, when using createRoot, or after hydration is complete when using hydrateRoot.
38
+ * It's important that this function does not create new objects or arrays when called:
39
+ * https://beta.reactjs.org/apis/react/useSyncExternalStore#im-getting-an-error-the-result-of-getsnapshot-should-be-cached
40
+ */
41
+ function getSnapshot() {
42
+ return getMatchMedia().matches
43
+ }
44
+
45
+ /**
46
+ * Only called during server-side rendering, and hydration if using hydrateRoot
47
+ * Since the server environment doesn't have access to the DOM, we can't determine the current value of the media query
48
+ * and we assume `(prefers-reduced-motion: no-preference)` since it's the most common scheme
11
49
  *
50
+ * @link https://beta.reactjs.org/apis/react/useSyncExternalStore#adding-support-for-server-rendering
51
+ */
52
+ function getServerSnapshot() {
53
+ return false
54
+ }
55
+
56
+ /**
57
+ * Returns true if motion should be reduced
12
58
  * @public
13
59
  */
14
- export function usePrefersReducedMotion(getServerSnapshot = () => false): boolean {
15
- return useMatchMedia('(prefers-reduced-motion: reduce)', getServerSnapshot)
60
+ export function usePrefersReducedMotion(): boolean {
61
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
16
62
  }
@@ -1,7 +1,7 @@
1
1
  import {EllipsisVerticalIcon} from '@sanity/icons'
2
2
  import {Button, Card, Flex, Popover, Text, useClickOutside} from '@sanity/ui'
3
3
  import {useBoolean, useSelect} from '@sanity/ui-workshop'
4
- import {useCallback, useRef, useState} from 'react'
4
+ import {useCallback, useState} from 'react'
5
5
  import {
6
6
  WORKSHOP_FLEX_ALIGN_OPTIONS,
7
7
  WORKSHOP_FLEX_JUSTIFY_OPTIONS,
@@ -20,8 +20,8 @@ export default function AlignedStory() {
20
20
 
21
21
  const [open, setOpen] = useState(false)
22
22
  const [boundaryElement, setBoundaryElement] = useState<HTMLDivElement | null>(null)
23
- const buttonElementRef = useRef<HTMLButtonElement | null>(null)
24
- const popoverElementRef = useRef<HTMLDivElement | null>(null)
23
+ const [buttonElement, setButtonElement] = useState<HTMLButtonElement | null>(null)
24
+ const [popoverElement, setPopoverElement] = useState<HTMLDivElement | null>(null)
25
25
 
26
26
  const content = (
27
27
  <Text>
@@ -39,11 +39,9 @@ export default function AlignedStory() {
39
39
  )
40
40
 
41
41
  const handleToggleOpen = useCallback(() => setOpen((v) => !v), [])
42
+ const handleClose = useCallback(() => setOpen(false), [])
42
43
 
43
- useClickOutside(
44
- () => setOpen(false),
45
- () => [buttonElementRef.current, popoverElementRef.current],
46
- )
44
+ useClickOutside(handleClose, [buttonElement, popoverElement])
47
45
 
48
46
  return (
49
47
  <Card height="fill" padding={[4, 5, 6]} sizing="border" tone="transparent">
@@ -58,14 +56,14 @@ export default function AlignedStory() {
58
56
  padding={3}
59
57
  portal={portal}
60
58
  placement={placement}
61
- ref={popoverElementRef}
59
+ ref={setPopoverElement}
62
60
  width={width}
63
61
  >
64
62
  <Button
65
63
  icon={EllipsisVerticalIcon}
66
64
  mode="bleed"
67
65
  onClick={handleToggleOpen}
68
- ref={buttonElementRef}
66
+ ref={setButtonElement}
69
67
  selected={open}
70
68
  />
71
69
  </Popover>
@@ -10,7 +10,7 @@ import {
10
10
  Tooltip,
11
11
  } from '@sanity/ui'
12
12
  import {useBoolean, useSelect, useText} from '@sanity/ui-workshop'
13
- import {useMemo, useState} from 'react'
13
+ import {useState} from 'react'
14
14
  import {WORKSHOP_PLACEMENT_OPTIONS} from '../../../__workshop__/constants'
15
15
 
16
16
  const PORTAL_OPTIONS = {
@@ -30,15 +30,13 @@ export default function CustomPortalStory() {
30
30
 
31
31
  const [portal1Element, setPortal1Element] = useState<HTMLDivElement | null>(null)
32
32
  const [boundaryElement, setBoundaryElement] = useState<HTMLDivElement | null>(null)
33
- const __unstable_elements = useMemo(
34
- () => ({
35
- portal1: portal1Element,
36
- }),
37
- [portal1Element],
38
- )
39
33
 
40
34
  return (
41
- <PortalProvider __unstable_elements={__unstable_elements}>
35
+ <PortalProvider
36
+ __unstable_elements={{
37
+ portal1: portal1Element,
38
+ }}
39
+ >
42
40
  <Flex align="center" height="fill" justify="center">
43
41
  <BoundaryElementProvider element={useBoundaryElement ? boundaryElement : null}>
44
42
  <Card
@@ -5,7 +5,6 @@ import {
5
5
  flip,
6
6
  offset,
7
7
  shift,
8
- size,
9
8
  useFloating,
10
9
  type Middleware,
11
10
  type RootBoundary,
@@ -22,6 +21,7 @@ import {
22
21
  useState,
23
22
  useId,
24
23
  useImperativeHandle,
24
+ useLayoutEffect,
25
25
  } from 'react'
26
26
  import {styled} from 'styled-components'
27
27
  import {useEffectEvent} from 'use-effect-event'
@@ -127,6 +127,7 @@ export const Tooltip = forwardRef(function Tooltip(
127
127
  const [referenceElement, setReferenceElement] = useState<HTMLElement | null>(null)
128
128
  const arrowRef = useRef<HTMLDivElement | null>(null)
129
129
  const rootBoundary: RootBoundary = 'viewport'
130
+ const [tooltipMaxWidth, setTooltipMaxWidth] = useState(0)
130
131
 
131
132
  useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(forwardedRef, () => ref.current)
132
133
 
@@ -137,28 +138,6 @@ export const Tooltip = forwardRef(function Tooltip(
137
138
  const middleware = useMemo(() => {
138
139
  const ret: Middleware[] = []
139
140
 
140
- // Set the max width of the tooltip based on boundaries and portals
141
- ret.push(
142
- size({
143
- apply({elements}) {
144
- // Get the maximum tooltip width (sans tooltip padding)
145
- // Tooltip width should never exceed the width of either any supplied boundary or portal element.
146
- // If both portal and boundary elements are provided, use the smaller width of the two.
147
- const availableWidths = [
148
- ...(boundaryElement ? [boundaryElement.offsetWidth] : []),
149
- portalElement?.offsetWidth || document.body.offsetWidth,
150
- ]
151
-
152
- const tooltipWidth = Math.min(...availableWidths) - DEFAULT_TOOLTIP_PADDING * 2
153
-
154
- // Set the max width directly, efficiently, without another react effect + render loop
155
- Object.assign(elements.floating.style, {
156
- maxWidth: tooltipWidth > 0 ? `${tooltipWidth}px` : undefined,
157
- })
158
- },
159
- }),
160
- )
161
-
162
141
  // Flip the floating element when leaving the boundary box
163
142
  ret.push(
164
143
  flip({
@@ -193,7 +172,7 @@ export const Tooltip = forwardRef(function Tooltip(
193
172
  }
194
173
 
195
174
  return ret
196
- }, [animate, arrowProp, boundaryElement, fallbackPlacements, portalElement])
175
+ }, [animate, arrowProp, boundaryElement, fallbackPlacements])
197
176
 
198
177
  const {floatingStyles, placement, middlewareData, refs, update} = useFloating({
199
178
  middleware,
@@ -322,6 +301,19 @@ export const Tooltip = forwardRef(function Tooltip(
322
301
  // Handle closing the tooltip when the mouse leaves the referenceElement
323
302
  useCloseOnMouseLeave({handleIsOpenChange, referenceElement, showTooltip})
324
303
 
304
+ // // Set the max width of the tooltip based on boundaries and portals
305
+ useLayoutEffect(() => {
306
+ // Get the maximum tooltip width (sans tooltip padding)
307
+ // Tooltip width should never exceed the width of either any supplied boundary or portal element.
308
+ // If both portal and boundary elements are provided, use the smaller width of the two.
309
+ const availableWidths = [
310
+ ...(boundaryElement ? [boundaryElement.offsetWidth] : []),
311
+ portalElement?.offsetWidth || document.body.offsetWidth,
312
+ ]
313
+
314
+ setTooltipMaxWidth(Math.min(...availableWidths) - DEFAULT_TOOLTIP_PADDING * 2)
315
+ }, [boundaryElement, portalElement])
316
+
325
317
  const setArrow = useCallback(
326
318
  (arrowEl: HTMLDivElement | null) => {
327
319
  arrowRef.current = arrowEl
@@ -384,7 +376,10 @@ export const Tooltip = forwardRef(function Tooltip(
384
376
  data-ui="Tooltip"
385
377
  {...restProps}
386
378
  ref={setFloating}
387
- style={floatingStyles}
379
+ style={{
380
+ ...floatingStyles,
381
+ maxWidth: tooltipMaxWidth > 0 ? `${tooltipMaxWidth}px` : undefined,
382
+ }}
388
383
  zOffset={zOffset}
389
384
  >
390
385
  <TooltipCard
@@ -451,12 +446,11 @@ function useCloseOnMouseLeave({
451
446
  // Since we don't want the `mouseevent` events to be attached and removed if the `referenceElement` is changed
452
447
  // we use a "effect event" (https://19.react.dev/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events)
453
448
  // in order to always see the latest `referenceElement` value inside the event handler itself.
454
- const handleWindowMouseMove = useEffectEvent((event: MouseEvent) => {
449
+ const onMouseMove = useEffectEvent((target: EventTarget | null) => {
455
450
  if (!referenceElement) return
456
451
 
457
452
  const isHoveringReference =
458
- referenceElement === event.target ||
459
- (event.target instanceof Node && referenceElement.contains(event.target))
453
+ referenceElement === target || (target instanceof Node && referenceElement.contains(target))
460
454
 
461
455
  if (!isHoveringReference) {
462
456
  handleIsOpenChange(false)
@@ -469,8 +463,12 @@ function useCloseOnMouseLeave({
469
463
  useEffect(() => {
470
464
  if (!showTooltip) return
471
465
 
472
- window.addEventListener('mousemove', handleWindowMouseMove)
466
+ const handleMouseMove = (event: MouseEvent) => {
467
+ onMouseMove(event.target)
468
+ }
469
+
470
+ window.addEventListener('mousemove', handleMouseMove)
473
471
 
474
- return () => window.removeEventListener('mousemove', handleWindowMouseMove)
475
- }, [handleWindowMouseMove, showTooltip])
472
+ return () => window.removeEventListener('mousemove', handleMouseMove)
473
+ }, [onMouseMove, showTooltip])
476
474
  }
@@ -36,7 +36,6 @@ export function TooltipDelayGroupProvider(
36
36
  const openDelay = typeof delay === 'number' ? delay : delay?.open || 0
37
37
  const closeDelay = typeof delay === 'number' ? delay : delay?.close || 0
38
38
 
39
- // @TODO split out into separate contexts
40
39
  const value: TooltipDelayGroupContextValue = useMemo(
41
40
  () => ({
42
41
  isGroupActive: isGroupActive,
@@ -91,7 +91,6 @@ export function LayerProvider(props: LayerProviderProps): React.ReactElement {
91
91
  // Register this layer on mount
92
92
  useEffect(() => parentRegisterChild?.(level), [level, parentRegisterChild])
93
93
 
94
- // @TODO split out into separate contexts
95
94
  const value: LayerContextValue = useMemo(
96
95
  () => ({
97
96
  version: 0.0,
@@ -1,21 +1,19 @@
1
1
  import {Card, Container, Portal, PortalProvider, Stack, Text} from '@sanity/ui'
2
- import {useMemo, useState} from 'react'
2
+ import {useState} from 'react'
3
3
 
4
4
  export default function NamedStory() {
5
5
  const [portal1Element, setPortal1Element] = useState<HTMLDivElement | null>(null)
6
6
  const [portal2Element, setPortal2Element] = useState<HTMLDivElement | null>(null)
7
7
  const [portal3Element, setPortal3Element] = useState<HTMLDivElement | null>(null)
8
- const __unstable_elements = useMemo(
9
- () => ({
10
- portal1: portal1Element,
11
- portal2: portal2Element,
12
- portal3: portal3Element,
13
- }),
14
- [portal1Element, portal2Element, portal3Element],
15
- )
16
8
 
17
9
  return (
18
- <PortalProvider __unstable_elements={__unstable_elements}>
10
+ <PortalProvider
11
+ __unstable_elements={{
12
+ portal1: portal1Element,
13
+ portal2: portal2Element,
14
+ portal3: portal3Element,
15
+ }}
16
+ >
19
17
  <Container width={1}>
20
18
  <Card height="fill" padding={4}>
21
19
  <Stack space={2}>
@@ -1,4 +1,5 @@
1
1
  import {useMemo} from 'react'
2
+ import {useUnique} from '../../hooks/_internal'
2
3
  import {useMounted} from '../../hooks/useMounted'
3
4
  import {PortalContext} from './portalContext'
4
5
  import {PortalContextValue} from './types'
@@ -23,10 +24,10 @@ export interface PortalProviderProps {
23
24
  * @public
24
25
  */
25
26
  export function PortalProvider(props: PortalProviderProps): React.ReactElement {
26
- const {boundaryElement, children, element, __unstable_elements: elements} = props
27
+ const {boundaryElement, children, element, __unstable_elements: elementsProp} = props
28
+ const elements = useUnique(elementsProp)
27
29
  const mounted = useMounted()
28
30
 
29
- // @TODO split out into separate contexts
30
31
  const value: PortalContextValue = useMemo(() => {
31
32
  return {
32
33
  version: 0.0,
@@ -1,46 +0,0 @@
1
- import {useDebugValue, useMemo, useSyncExternalStore} from 'react'
2
-
3
- /**
4
- * Efficiently subscribes to `window.matchMedia` queries
5
- *
6
- * @param getServerSnapshot - Only called during server-side rendering, and hydration if using hydrateRoot. Required if the hook is called during SSR (https://react.dev/reference/react/useSyncExternalStore#adding-support-for-server-rendering)
7
- *
8
- * @public
9
- */
10
- export function useMatchMedia(
11
- mediaQueryString: `(${string})`,
12
- getServerSnapshot?: () => boolean,
13
- ): boolean {
14
- 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
- return {
32
- subscribe: (onStoreChange: () => void): (() => void) => {
33
- const matchMedia = getMatchMedia()
34
-
35
- matchMedia.addEventListener('change', onStoreChange)
36
-
37
- return () => matchMedia.removeEventListener('change', onStoreChange)
38
- },
39
- getSnapshot: () => getMatchMedia().matches,
40
- }
41
- }, [mediaQueryString])
42
-
43
- useDebugValue(mediaQueryString)
44
-
45
- return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
46
- }