@sanity/ui 2.8.20 → 2.8.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ui",
3
- "version": "2.8.20",
3
+ "version": "2.8.21",
4
4
  "keywords": [
5
5
  "sanity",
6
6
  "ui",
@@ -122,24 +122,24 @@
122
122
  "@commitlint/cli": "^19.4.0",
123
123
  "@commitlint/config-conventional": "^19.2.2",
124
124
  "@juggle/resize-observer": "^3.4.0",
125
- "@sanity/pkg-utils": "^6.11.8",
125
+ "@sanity/pkg-utils": "^6.11.9",
126
126
  "@sanity/prettier-config": "^1.0.2",
127
127
  "@sanity/semantic-release-preset": "^5.0.0",
128
128
  "@sanity/ui-workshop": "^2.0.16",
129
- "@storybook/addon-a11y": "^8.4.1",
130
- "@storybook/addon-docs": "^8.4.1",
131
- "@storybook/addon-essentials": "^8.4.1",
132
- "@storybook/addon-interactions": "^8.4.1",
133
- "@storybook/addon-links": "^8.4.1",
134
- "@storybook/addon-mdx-gfm": "^8.4.1",
135
- "@storybook/addon-storysource": "^8.4.1",
136
- "@storybook/addon-themes": "^8.4.1",
137
- "@storybook/blocks": "^8.4.1",
138
- "@storybook/manager-api": "^8.4.1",
139
- "@storybook/react": "^8.4.1",
140
- "@storybook/react-vite": "^8.4.1",
141
- "@storybook/test": "^8.4.1",
142
- "@storybook/theming": "^8.4.1",
129
+ "@storybook/addon-a11y": "^8.4.2",
130
+ "@storybook/addon-docs": "^8.4.2",
131
+ "@storybook/addon-essentials": "^8.4.2",
132
+ "@storybook/addon-interactions": "^8.4.2",
133
+ "@storybook/addon-links": "^8.4.2",
134
+ "@storybook/addon-mdx-gfm": "^8.4.2",
135
+ "@storybook/addon-storysource": "^8.4.2",
136
+ "@storybook/addon-themes": "^8.4.2",
137
+ "@storybook/blocks": "^8.4.2",
138
+ "@storybook/manager-api": "^8.4.2",
139
+ "@storybook/react": "^8.4.2",
140
+ "@storybook/react-vite": "^8.4.2",
141
+ "@storybook/test": "^8.4.2",
142
+ "@storybook/theming": "^8.4.2",
143
143
  "@testing-library/dom": "^10.4.0",
144
144
  "@testing-library/jest-dom": "^6.6.2",
145
145
  "@testing-library/react": "^16.0.1",
@@ -168,7 +168,7 @@
168
168
  "eslint-plugin-react": "^7.37.2",
169
169
  "eslint-plugin-react-compiler": "19.0.0-beta-63b359f-20241101",
170
170
  "eslint-plugin-react-hooks": "^5.0.0",
171
- "eslint-plugin-storybook": "^0.10.2",
171
+ "eslint-plugin-storybook": "^0.11.0",
172
172
  "http-server": "^14.1.1",
173
173
  "husky": "^8.0.3",
174
174
  "jest": "^29.7.0",
@@ -185,7 +185,7 @@
185
185
  "rimraf": "^5.0.5",
186
186
  "semantic-release": "^24.0.0",
187
187
  "start-server-and-test": "^2.0.5",
188
- "storybook": "^8.4.1",
188
+ "storybook": "^8.4.2",
189
189
  "styled-components": "^6.1.13",
190
190
  "tsconfig-paths": "^4.2.0",
191
191
  "typescript": "5.6.3",
@@ -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 rootElementRef = useRef<HTMLDivElement | null>(null)
188
- const resultsPopoverElementRef = useRef<HTMLDivElement | null>(null)
189
- const inputElementRef = useRef<HTMLInputElement | null>(null)
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 popoverMouseWithinRef = useRef(false)
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
- () => inputElementRef.current,
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 (popoverMouseWithinRef.current) {
227
+ if (popoverMouseWithin) {
226
228
  return
227
229
  }
228
230
 
229
231
  const elements: HTMLElement[] = (relatedElements || []).concat(
230
- rootElementRef.current ? [rootElementRef.current] : [],
231
- resultsPopoverElementRef.current ? [resultsPopoverElementRef.current] : [],
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
- popoverMouseWithinRef.current = false
249
+ setPopoverMouseWithin(false)
248
250
  if (onQueryChange) onQueryChange(null)
249
251
  if (onBlur) onBlur(event)
250
252
  }
251
253
  }, 0)
252
254
  },
253
- [onBlur, onQueryChange, relatedElements],
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
- popoverMouseWithinRef.current = false
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
- inputElementRef.current?.focus()
290
+ inputElement?.focus()
282
291
  },
283
- [onChange, onSelect, onQueryChange],
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
- popoverMouseWithinRef.current = false
336
+ setPopoverMouseWithin(false)
328
337
  if (onQueryChange) onQueryChange(null)
329
- inputElementRef.current?.focus()
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
- inputElementRef.current?.focus()
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
- popoverMouseWithinRef.current = true
389
+ setPopoverMouseWithin(true)
381
390
  }, [])
382
391
 
383
392
  const handlePopoverMouseLeave = useCallback(() => {
384
- popoverMouseWithinRef.current = false
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
- inputElementRef.current?.focus()
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(() => inputElementRef.current?.focus())
494
+ _raf(() => inputElement?.focus())
486
495
  },
487
- [openButtonProps, dispatchOpen],
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={inputElementRef}
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) inputElementRef.current?.focus()
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: inputElementRef.current,
647
+ inputElement,
639
648
  onMouseEnter: handlePopoverMouseEnter,
640
649
  onMouseLeave: handlePopoverMouseLeave,
641
650
  },
642
- resultsPopoverElementRef,
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={resultsPopoverElementRef}
665
- referenceElement={inputElementRef.current}
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={rootElementRef}
697
+ ref={setRootElement}
687
698
  >
688
699
  {input}
689
700
  {results}
@@ -14,19 +14,21 @@ export interface MenuProps extends ResponsivePaddingProps {
14
14
  /**
15
15
  * @deprecated Use `shouldFocus="first"` instead.
16
16
  */
17
- focusFirst?: boolean
17
+ 'focusFirst'?: boolean
18
18
  /**
19
19
  * @deprecated Use `shouldFocus="last"` instead.
20
20
  */
21
- focusLast?: boolean
22
- onClickOutside?: (event: MouseEvent) => void
23
- onEscape?: () => void
24
- onItemClick?: () => void
25
- onItemSelect?: (index: number) => void
26
- originElement?: HTMLElement | null
27
- registerElement?: (el: HTMLElement) => () => void
28
- shouldFocus?: 'first' | 'last' | null
29
- space?: number | number[]
21
+ 'focusLast'?: boolean
22
+ 'onClickOutside'?: (event: MouseEvent) => void
23
+ 'onEscape'?: () => void
24
+ 'onItemClick'?: () => void
25
+ 'onItemSelect'?: (index: number) => void
26
+ 'originElement'?: HTMLElement | null
27
+ 'registerElement'?: (el: HTMLElement) => () => void
28
+ 'shouldFocus'?: 'first' | 'last' | null
29
+ 'space'?: number | number[]
30
+ 'aria-labelledby'?: string
31
+ 'onBlurCapture'?: (event: FocusEvent) => void
30
32
  }
31
33
 
32
34
  const Root = styled(Box)`
@@ -169,7 +169,7 @@ export const MenuButton = forwardRef(function MenuButton(
169
169
  }, [buttonElement, disableRestoreFocusOnClose])
170
170
 
171
171
  const handleBlur = useCallback(
172
- (event: React.FocusEvent<HTMLButtonElement>) => {
172
+ (event: FocusEvent) => {
173
173
  const target = event.relatedTarget
174
174
 
175
175
  if (!(target instanceof Node)) {
@@ -199,32 +199,19 @@ export const MenuButton = forwardRef(function MenuButton(
199
199
  return () => setChildMenuElements((els) => els.filter((_el) => _el !== el))
200
200
  }, [])
201
201
 
202
- const menuProps: MenuProps = useMemo(
203
- () => ({
204
- 'aria-labelledby': id,
205
- 'onBlurCapture': handleBlur,
206
- 'onClickOutside': handleMenuClickOutside,
207
- 'onEscape': handleMenuEscape,
208
- 'onItemClick': handleItemClick,
209
- 'originElement': buttonElement,
210
- registerElement,
211
- shouldFocus,
212
- }),
213
- [
214
- buttonElement,
215
- handleMenuClickOutside,
216
- handleMenuEscape,
217
- handleItemClick,
218
- id,
219
- handleBlur,
220
- registerElement,
221
- shouldFocus,
222
- ],
223
- )
202
+ const menuProps: MenuProps = {
203
+ 'aria-labelledby': id,
204
+ 'onBlurCapture': handleBlur,
205
+ 'onClickOutside': handleMenuClickOutside,
206
+ 'onEscape': handleMenuEscape,
207
+ 'onItemClick': handleItemClick,
208
+ 'originElement': buttonElement,
209
+ registerElement,
210
+ shouldFocus,
211
+ }
224
212
 
225
213
  const menu = menuProp && cloneElement(menuProp, menuProps)
226
214
 
227
- const ref = useRef<HTMLButtonElement | null>(null)
228
215
  const button = useMemo(
229
216
  () =>
230
217
  buttonProp &&
@@ -236,7 +223,7 @@ export const MenuButton = forwardRef(function MenuButton(
236
223
  'onMouseDown': handleMouseDown,
237
224
  'aria-haspopup': true,
238
225
  'aria-expanded': open,
239
- 'ref': ref,
226
+ 'ref': setButtonElement,
240
227
  'selected': buttonProp.props.selected ?? open,
241
228
  }),
242
229
  [buttonProp, handleButtonClick, handleButtonKeyDown, handleMouseDown, id, open],
@@ -245,19 +232,10 @@ export const MenuButton = forwardRef(function MenuButton(
245
232
  // Forward button ref to parent
246
233
  useImperativeHandle<HTMLButtonElement | null, HTMLButtonElement | null>(
247
234
  forwardedRef,
248
- () => ref.current,
235
+ () => buttonElement,
236
+ [buttonElement],
249
237
  )
250
238
 
251
- // If there's a button then we need to set the reference element to the cloned button ref
252
- // and if button changes we make sure to update or remove the reference element.
253
- useEffect(() => {
254
- if (!button) return undefined
255
-
256
- setButtonElement(ref.current)
257
-
258
- return () => setButtonElement(null)
259
- }, [button])
260
-
261
239
  const popoverProps: MenuButtonProps['popover'] = useMemo(
262
240
  () => ({
263
241
  boundaryElement: deprecated_boundaryElement,
@@ -8,6 +8,7 @@ import {Box} from '../../primitives'
8
8
  import {Layer} from '../../utils'
9
9
  import {Toast} from './toast'
10
10
  import {ToastContext} from './toastContext'
11
+ import {generateToastId} from './toastState'
11
12
  import {ToastContextValue, ToastParams} from './types'
12
13
 
13
14
  type ToastState = {
@@ -45,8 +46,6 @@ const ToastContainer = styled.div`
45
46
  width: 100%;
46
47
  `
47
48
 
48
- let toastId = 0
49
-
50
49
  /**
51
50
  * @public
52
51
  */
@@ -60,20 +59,20 @@ export function ToastProvider(props: ToastProviderProps): React.ReactElement {
60
59
  () => ({
61
60
  initial: {
62
61
  opacity: 0,
63
- [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: 0,
62
+ [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: 0,
64
63
  y: 32,
65
64
  scale: 0.25,
66
65
  willChange: 'transform',
67
66
  },
68
67
  animate: {
69
68
  opacity: [0, 1, 1],
70
- [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: [0, 0, 1],
69
+ [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: [0, 0, 1],
71
70
  y: 0,
72
71
  scale: 1,
73
72
  },
74
73
  exit: {
75
74
  opacity: [1, 1, 0],
76
- [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY as string]: [1, 0, 0],
75
+ [POPOVER_MOTION_CONTENT_OPACITY_PROPERTY]: [1, 0, 0],
77
76
  scale: 0.5,
78
77
  transition: prefersReducedMotion ? {duration: 0} : {duration: 0.2},
79
78
  },
@@ -86,7 +85,7 @@ export function ToastProvider(props: ToastProviderProps): React.ReactElement {
86
85
  // Wrap setState in startTransition to allow React to give input state updates higher priority
87
86
  const setState: typeof _setState = (state) => startTransition(() => _setState(state))
88
87
 
89
- const id = params.id || String(toastId++)
88
+ const id = params.id || generateToastId()
90
89
  const duration = params.duration || 5000
91
90
 
92
91
  const dismiss = () => {
@@ -0,0 +1,6 @@
1
+ let toastId = 0
2
+
3
+ /** @internal */
4
+ export function generateToastId(): string {
5
+ return String(toastId++)
6
+ }
@@ -13,7 +13,7 @@ export const EMPTY_RECORD: Record<string, never> = {}
13
13
  /**
14
14
  * @internal
15
15
  */
16
- export const POPOVER_MOTION_CONTENT_OPACITY_PROPERTY = '--motion-content-opacity'
16
+ export const POPOVER_MOTION_CONTENT_OPACITY_PROPERTY = '--motion-content-opacity' as string
17
17
 
18
18
  /**
19
19
  * Shared `framer-motion` variants used by `Popover` and `Tooltip` components.
@@ -1,4 +1,4 @@
1
- import {useMemo} from 'react'
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
- // JSON.stringify is fast, but it's not faster than useMemo's referencial equality check
16
- const __perf_hash__ = useMemo(() => JSON.stringify(val ?? defaultVal), [defaultVal, val])
15
+ const [[cachedVal, cachedHash], setCache] = useState<[T[], string]>(() => [
16
+ _getArrayProp(val, defaultVal),
17
+ JSON.stringify(val ?? defaultVal),
18
+ ])
17
19
 
18
- return useMemo(
19
- () => _getArrayProp(val, defaultVal),
20
+ const hash = JSON.stringify(val ?? defaultVal)
20
21
 
21
- // Improve performance: Keep object identify for a given hash of the value
22
- // eslint-disable-next-line react-hooks/exhaustive-deps
23
- [__perf_hash__],
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
  }
@@ -1,5 +1,5 @@
1
1
  import {getTheme_v2} from '@sanity/ui/theme'
2
- import {Children, cloneElement, forwardRef, isValidElement, useMemo} from 'react'
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 = useMemo<React.ReactElement[]>(
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
 
@@ -1,4 +1,4 @@
1
- import {forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from 'react'
1
+ import {forwardRef, useImperativeHandle, useMemo, useState} from 'react'
2
2
  import {useElementSize} from '../../hooks'
3
3
  import {useTheme_v2} from '../../theme'
4
4
  import {findMaxBreakpoints, findMinBreakpoints} from './helpers'
@@ -21,31 +21,30 @@ export const ElementQuery = forwardRef(function ElementQuery(
21
21
  forwardedRef: React.ForwardedRef<HTMLDivElement>,
22
22
  ) {
23
23
  const theme = useTheme_v2()
24
- const {children, media = theme.media, ...restProps} = props
24
+ const {children, media: _media, ...restProps} = props
25
+ const media = _media ?? theme.media
25
26
 
26
- const ref = useRef<HTMLDivElement | null>(null)
27
27
  const [element, setElement] = useState<HTMLDivElement | null>(null)
28
28
  const elementSize = useElementSize(element)
29
29
  const width = useMemo(() => elementSize?.border.width ?? window.innerWidth, [elementSize])
30
30
 
31
- const max = useMemo(() => findMaxBreakpoints(media, width), [media, width])
32
- const min = useMemo(() => findMinBreakpoints(media, width), [media, width])
31
+ const max = useMemo(() => {
32
+ const eq = findMaxBreakpoints(media, width)
33
33
 
34
- useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(forwardedRef, () => ref.current)
34
+ return eq.length ? eq.join(' ') : undefined
35
+ }, [media, width])
36
+ const min = useMemo(() => {
37
+ const eq = findMinBreakpoints(media, width)
35
38
 
36
- const setRef = useCallback((el: HTMLDivElement | null) => {
37
- ref.current = el
38
- setElement(el)
39
- }, [])
39
+ return eq.length ? eq.join(' ') : undefined
40
+ }, [media, width])
41
+
42
+ useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(forwardedRef, () => element, [
43
+ element,
44
+ ])
40
45
 
41
46
  return (
42
- <div
43
- data-ui="ElementQuery"
44
- {...restProps}
45
- data-eq-max={max.length ? max.join(' ') : undefined}
46
- data-eq-min={min.length ? min.join(' ') : undefined}
47
- ref={setRef}
48
- >
47
+ <div data-ui="ElementQuery" {...restProps} data-eq-max={max} data-eq-min={min} ref={setElement}>
49
48
  {children}
50
49
  </div>
51
50
  )
@@ -85,7 +85,7 @@ export function LayerProvider(props: LayerProviderProps): React.ReactElement {
85
85
  parentDispose?.()
86
86
  }
87
87
  },
88
- [parentRegisterChild],
88
+ [parentRegisterChild, setSize, setChildLayers],
89
89
  )
90
90
 
91
91
  // Register this layer on mount