@tamagui/select 2.0.0-rc.3 → 2.0.0-rc.30

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 (163) hide show
  1. package/dist/cjs/Select.cjs +63 -55
  2. package/dist/cjs/Select.native.js +65 -57
  3. package/dist/cjs/Select.native.js.map +1 -1
  4. package/dist/cjs/SelectContent.cjs +41 -47
  5. package/dist/cjs/SelectImpl.cjs +42 -24
  6. package/dist/cjs/SelectItem.cjs +10 -3
  7. package/dist/cjs/SelectItem.native.js +9 -7
  8. package/dist/cjs/SelectItem.native.js.map +1 -1
  9. package/dist/cjs/SelectScrollButton.cjs +4 -4
  10. package/dist/cjs/SelectTrigger.cjs +1 -1
  11. package/dist/cjs/SelectViewport.cjs +44 -29
  12. package/dist/cjs/context.cjs +4 -1
  13. package/dist/cjs/context.native.js +3 -0
  14. package/dist/cjs/context.native.js.map +1 -1
  15. package/dist/cjs/index.js +12 -9
  16. package/dist/esm/Select.mjs +64 -56
  17. package/dist/esm/Select.mjs.map +1 -1
  18. package/dist/esm/Select.native.js +66 -58
  19. package/dist/esm/Select.native.js.map +1 -1
  20. package/dist/esm/SelectContent.mjs +38 -33
  21. package/dist/esm/SelectContent.mjs.map +1 -1
  22. package/dist/esm/SelectImpl.mjs +38 -20
  23. package/dist/esm/SelectImpl.mjs.map +1 -1
  24. package/dist/esm/SelectItem.mjs +10 -3
  25. package/dist/esm/SelectItem.mjs.map +1 -1
  26. package/dist/esm/SelectItem.native.js +9 -7
  27. package/dist/esm/SelectItem.native.js.map +1 -1
  28. package/dist/esm/SelectScrollButton.mjs +1 -1
  29. package/dist/esm/SelectScrollButton.mjs.map +1 -1
  30. package/dist/esm/SelectTrigger.mjs +1 -1
  31. package/dist/esm/SelectTrigger.mjs.map +1 -1
  32. package/dist/esm/SelectViewport.mjs +28 -24
  33. package/dist/esm/SelectViewport.mjs.map +1 -1
  34. package/dist/esm/context.mjs +4 -2
  35. package/dist/esm/context.mjs.map +1 -1
  36. package/dist/esm/context.native.js +4 -2
  37. package/dist/esm/context.native.js.map +1 -1
  38. package/dist/esm/index.js +2 -2
  39. package/dist/esm/index.js.map +1 -6
  40. package/dist/jsx/Select.mjs +64 -56
  41. package/dist/jsx/Select.mjs.map +1 -1
  42. package/dist/jsx/Select.native.js +65 -57
  43. package/dist/jsx/Select.native.js.map +1 -1
  44. package/dist/jsx/SelectContent.mjs +38 -33
  45. package/dist/jsx/SelectContent.mjs.map +1 -1
  46. package/dist/jsx/SelectImpl.mjs +38 -20
  47. package/dist/jsx/SelectImpl.mjs.map +1 -1
  48. package/dist/jsx/SelectItem.mjs +10 -3
  49. package/dist/jsx/SelectItem.mjs.map +1 -1
  50. package/dist/jsx/SelectItem.native.js +9 -7
  51. package/dist/jsx/SelectItem.native.js.map +1 -1
  52. package/dist/jsx/SelectScrollButton.mjs +1 -1
  53. package/dist/jsx/SelectScrollButton.mjs.map +1 -1
  54. package/dist/jsx/SelectTrigger.mjs +1 -1
  55. package/dist/jsx/SelectTrigger.mjs.map +1 -1
  56. package/dist/jsx/SelectViewport.mjs +28 -24
  57. package/dist/jsx/SelectViewport.mjs.map +1 -1
  58. package/dist/jsx/context.mjs +4 -2
  59. package/dist/jsx/context.mjs.map +1 -1
  60. package/dist/jsx/context.native.js +3 -0
  61. package/dist/jsx/context.native.js.map +1 -1
  62. package/dist/jsx/index.js +2 -2
  63. package/dist/jsx/index.js.map +1 -6
  64. package/package.json +31 -35
  65. package/src/Select.tsx +16 -1
  66. package/src/SelectContent.tsx +47 -36
  67. package/src/SelectImpl.tsx +51 -49
  68. package/src/SelectItem.tsx +17 -7
  69. package/src/SelectScrollButton.tsx +1 -1
  70. package/src/SelectTrigger.tsx +1 -1
  71. package/src/SelectViewport.tsx +42 -22
  72. package/src/context.tsx +4 -0
  73. package/src/types.tsx +44 -4
  74. package/types/Select.d.ts +2 -2
  75. package/types/Select.d.ts.map +1 -1
  76. package/types/SelectContent.d.ts +1 -1
  77. package/types/SelectContent.d.ts.map +1 -1
  78. package/types/SelectImpl.d.ts.map +1 -1
  79. package/types/SelectItem.d.ts.map +1 -1
  80. package/types/SelectTrigger.d.ts +1 -1
  81. package/types/SelectViewport.d.ts.map +1 -1
  82. package/types/context.d.ts +1 -0
  83. package/types/context.d.ts.map +1 -1
  84. package/types/types.d.ts +33 -3
  85. package/types/types.d.ts.map +1 -1
  86. package/dist/cjs/BubbleSelect.js +0 -34
  87. package/dist/cjs/BubbleSelect.js.map +0 -6
  88. package/dist/cjs/Select.js +0 -402
  89. package/dist/cjs/Select.js.map +0 -6
  90. package/dist/cjs/SelectContent.js +0 -57
  91. package/dist/cjs/SelectContent.js.map +0 -6
  92. package/dist/cjs/SelectImpl.js +0 -222
  93. package/dist/cjs/SelectImpl.js.map +0 -6
  94. package/dist/cjs/SelectItem.js +0 -166
  95. package/dist/cjs/SelectItem.js.map +0 -6
  96. package/dist/cjs/SelectItemText.js +0 -64
  97. package/dist/cjs/SelectItemText.js.map +0 -6
  98. package/dist/cjs/SelectScrollButton.js +0 -105
  99. package/dist/cjs/SelectScrollButton.js.map +0 -6
  100. package/dist/cjs/SelectTrigger.js +0 -81
  101. package/dist/cjs/SelectTrigger.js.map +0 -6
  102. package/dist/cjs/SelectViewport.js +0 -114
  103. package/dist/cjs/SelectViewport.js.map +0 -6
  104. package/dist/cjs/constants.js +0 -27
  105. package/dist/cjs/constants.js.map +0 -6
  106. package/dist/cjs/context.js +0 -33
  107. package/dist/cjs/context.js.map +0 -6
  108. package/dist/cjs/types.js +0 -14
  109. package/dist/cjs/types.js.map +0 -6
  110. package/dist/cjs/useSelectBreakpointActive.js +0 -25
  111. package/dist/cjs/useSelectBreakpointActive.js.map +0 -6
  112. package/dist/esm/BubbleSelect.js +0 -17
  113. package/dist/esm/BubbleSelect.js.map +0 -6
  114. package/dist/esm/Select.js +0 -412
  115. package/dist/esm/Select.js.map +0 -6
  116. package/dist/esm/SelectContent.js +0 -39
  117. package/dist/esm/SelectContent.js.map +0 -6
  118. package/dist/esm/SelectImpl.js +0 -223
  119. package/dist/esm/SelectImpl.js.map +0 -6
  120. package/dist/esm/SelectItem.js +0 -148
  121. package/dist/esm/SelectItem.js.map +0 -6
  122. package/dist/esm/SelectItemText.js +0 -46
  123. package/dist/esm/SelectItemText.js.map +0 -6
  124. package/dist/esm/SelectScrollButton.js +0 -87
  125. package/dist/esm/SelectScrollButton.js.map +0 -6
  126. package/dist/esm/SelectTrigger.js +0 -61
  127. package/dist/esm/SelectTrigger.js.map +0 -6
  128. package/dist/esm/SelectViewport.js +0 -112
  129. package/dist/esm/SelectViewport.js.map +0 -6
  130. package/dist/esm/constants.js +0 -11
  131. package/dist/esm/constants.js.map +0 -6
  132. package/dist/esm/context.js +0 -19
  133. package/dist/esm/context.js.map +0 -6
  134. package/dist/esm/types.js +0 -1
  135. package/dist/esm/types.js.map +0 -6
  136. package/dist/esm/useSelectBreakpointActive.js +0 -9
  137. package/dist/esm/useSelectBreakpointActive.js.map +0 -6
  138. package/dist/jsx/BubbleSelect.js +0 -17
  139. package/dist/jsx/BubbleSelect.js.map +0 -6
  140. package/dist/jsx/Select.js +0 -412
  141. package/dist/jsx/Select.js.map +0 -6
  142. package/dist/jsx/SelectContent.js +0 -39
  143. package/dist/jsx/SelectContent.js.map +0 -6
  144. package/dist/jsx/SelectImpl.js +0 -223
  145. package/dist/jsx/SelectImpl.js.map +0 -6
  146. package/dist/jsx/SelectItem.js +0 -148
  147. package/dist/jsx/SelectItem.js.map +0 -6
  148. package/dist/jsx/SelectItemText.js +0 -46
  149. package/dist/jsx/SelectItemText.js.map +0 -6
  150. package/dist/jsx/SelectScrollButton.js +0 -87
  151. package/dist/jsx/SelectScrollButton.js.map +0 -6
  152. package/dist/jsx/SelectTrigger.js +0 -61
  153. package/dist/jsx/SelectTrigger.js.map +0 -6
  154. package/dist/jsx/SelectViewport.js +0 -112
  155. package/dist/jsx/SelectViewport.js.map +0 -6
  156. package/dist/jsx/constants.js +0 -11
  157. package/dist/jsx/constants.js.map +0 -6
  158. package/dist/jsx/context.js +0 -19
  159. package/dist/jsx/context.js.map +0 -6
  160. package/dist/jsx/types.js +0 -1
  161. package/dist/jsx/types.js.map +0 -6
  162. package/dist/jsx/useSelectBreakpointActive.js +0 -9
  163. package/dist/jsx/useSelectBreakpointActive.js.map +0 -6
@@ -1,4 +1,3 @@
1
- import type { SideObject } from '@floating-ui/react'
2
1
  import {
3
2
  autoUpdate,
4
3
  flip,
@@ -7,14 +6,15 @@ import {
7
6
  shift,
8
7
  size,
9
8
  useClick,
10
- useDismiss,
11
- useFloating,
12
- useInnerOffset,
9
+ useFloatingRaw as useFloatingDom,
13
10
  useInteractions,
11
+ useInnerOffset,
14
12
  useListNavigation,
15
13
  useRole,
16
14
  useTypeahead,
17
- } from '@floating-ui/react'
15
+ type FloatingInteractionContext,
16
+ type SideObject,
17
+ } from '@tamagui/floating'
18
18
  import { useIsomorphicLayoutEffect } from '@tamagui/constants'
19
19
  import { useEvent, useIsTouchDevice } from '@tamagui/core'
20
20
  import * as React from 'react'
@@ -60,13 +60,10 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
60
60
  const floatingStyle = React.useRef({})
61
61
 
62
62
  // sync activeIndex on open/close
63
- // use useEffect (not layout effect) so this runs AFTER SelectItem children have subscribed
64
63
  React.useEffect(() => {
65
64
  if (open) {
66
- // use fast setter for initial focus - doesn't trigger re-render
67
65
  setActiveIndexFast(selectedIndex ?? 0)
68
66
  } else {
69
- // reset state when closed
70
67
  setScrollTop(0)
71
68
  setFallback(false)
72
69
  setActiveIndexFast(null)
@@ -90,9 +87,15 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
90
87
  }, [open])
91
88
  }
92
89
 
93
- const { x, y, strategy, context, refs, update } = useFloating({
90
+ const {
91
+ x,
92
+ y,
93
+ strategy,
94
+ refs,
95
+ update,
96
+ placement: computedPlacement,
97
+ } = useFloatingDom({
94
98
  open,
95
- onOpenChange: setOpen,
96
99
  placement: 'bottom-start',
97
100
  whileElementsMounted: autoUpdate,
98
101
  // eslint-disable-next-line no-constant-condition
@@ -145,7 +148,7 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
145
148
  }),
146
149
  offset({ crossAxis: -5 }),
147
150
  ],
148
- })
151
+ } as any)
149
152
 
150
153
  const floatingRef = refs.floating
151
154
 
@@ -174,21 +177,39 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
174
177
  return fn(index)
175
178
  })
176
179
 
180
+ // construct interaction context for our custom hooks
181
+ const dataRef = React.useRef<{ openEvent?: Event; placement?: string }>({})
182
+ dataRef.current.placement = computedPlacement
183
+ const interactionContext: FloatingInteractionContext = {
184
+ open,
185
+ onOpenChange: (val) => setOpen(val),
186
+ refs: {
187
+ reference: refs.reference as any,
188
+ floating: refs.floating,
189
+ domReference: refs.reference as any,
190
+ },
191
+ elements: {
192
+ reference: (refs.reference?.current as Element) || null,
193
+ floating: refs.floating?.current || null,
194
+ domReference: (refs.reference?.current as Element) || null,
195
+ },
196
+ dataRef,
197
+ }
198
+
177
199
  const interactionsProps = [
178
- useClick(context, { event: 'mousedown', keyboardHandlers: false }),
179
- useDismiss(context, { outsidePress: false }),
180
- useRole(context, { role: 'listbox' }),
181
- useInnerOffset(context, {
200
+ useClick(interactionContext, { event: 'mousedown', keyboardHandlers: false }),
201
+ // useDismiss removed - already handled by Dismissable in SelectContent
202
+ useRole(interactionContext, { role: 'listbox' }),
203
+ useInnerOffset(interactionContext, {
182
204
  enabled: !fallback && isScrollable,
183
205
  onChange: setInnerOffset,
184
206
  overflowRef,
185
207
  scrollRef: refs.floating,
186
208
  }),
187
- useListNavigation(context, {
209
+ useListNavigation(interactionContext, {
188
210
  listRef: listItemsRef,
189
211
  activeIndex: selectContext.activeIndex ?? 0,
190
212
  selectedIndex,
191
- // wrap onNavigate to prevent floating-ui from resetting activeIndex to null on focus loss
192
213
  onNavigate: (index) => {
193
214
  if (index !== null) {
194
215
  setActiveIndex(index)
@@ -196,7 +217,7 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
196
217
  },
197
218
  scrollItemIntoView: false,
198
219
  }),
199
- useTypeahead(context, {
220
+ useTypeahead(interactionContext, {
200
221
  listRef: listContentRef,
201
222
  onMatch,
202
223
  selectedIndex,
@@ -208,7 +229,6 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
208
229
  ]
209
230
 
210
231
  const interactions = useInteractions(
211
- // unfortunately these memos will just always break due to floating-ui context always changing :/
212
232
  React.useMemo(() => {
213
233
  return interactionsProps
214
234
  }, interactionsProps)
@@ -278,13 +298,10 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
278
298
 
279
299
  useIsomorphicLayoutEffect(() => {
280
300
  if (open) {
281
- // Prevent the initial mouseup from selecting an item
282
- // (the mouseup from the click that opened the menu)
283
301
  allowMouseUpRef.current = false
284
302
 
285
303
  selectTimeoutRef.current = setTimeout(() => {
286
304
  allowSelectRef.current = true
287
- // Re-enable mouseup after delay for click-and-hold-to-select behavior
288
305
  allowMouseUpRef.current = true
289
306
  }, 300)
290
307
 
@@ -305,8 +322,7 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
305
322
  }
306
323
  }, [open])
307
324
 
308
- // Replacement for `useDismiss` as the arrows are outside of the floating
309
- // element DOM tree.
325
+ // dismiss on outside pointer down (arrows are outside the floating DOM tree)
310
326
  useIsomorphicLayoutEffect(() => {
311
327
  function onPointerDown(e: PointerEvent) {
312
328
  const target = e.target as Node
@@ -330,8 +346,7 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
330
346
  }
331
347
  }, [open, refs, setOpen])
332
348
 
333
- // Scroll the `activeIndex` item into view only in "controlledScrolling"
334
- // (keyboard nav) mode. Use subscription to avoid re-renders on every activeIndex change.
349
+ // scroll activeIndex into view during keyboard nav
335
350
  React.useEffect(() => {
336
351
  if (!open) return
337
352
 
@@ -342,14 +357,11 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
342
357
  setScrollTop(refs.floating.current?.scrollTop ?? 0)
343
358
  }
344
359
 
345
- // initial scroll
346
360
  scrollActiveIntoView(activeIndexRef.current)
347
361
 
348
- // subscribe to future changes
349
362
  return selectItemParentContext.activeIndexSubscribe(scrollActiveIntoView)
350
363
  }, [open, refs, controlledScrolling, selectItemParentContext.activeIndexSubscribe])
351
364
 
352
- // Scroll the `selectedIndex` into view upon opening the floating element.
353
365
  React.useEffect(() => {
354
366
  if (open && fallback) {
355
367
  if (selectedIndex != null) {
@@ -358,18 +370,20 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
358
370
  }
359
371
  }, [open, fallback, selectedIndex])
360
372
 
361
- // Unset the height limiting for fallback mode. This gets executed prior to
362
- // the positioning call.
363
373
  useIsomorphicLayoutEffect(() => {
364
374
  if (refs.floating.current && fallback) {
365
375
  refs.floating.current.style.maxHeight = ''
366
376
  }
367
377
  }, [refs, fallback])
368
378
 
369
- // We set this to true by default so that events bubble to forms without JS (SSR)
370
- // const isFormControl = trigger ? Boolean(trigger.closest('form')) : true
371
- // const [bubbleSelect, setBubbleSelect] = React.useState<HTMLSelectElement | null>(null)
372
- // const triggerPointerDownPosRef = React.useRef<{ x: number; y: number } | null>(null)
379
+ // build a minimal floating context for SelectViewport/SelectScrollButton
380
+ const floatingContext = React.useMemo(
381
+ () => ({
382
+ refs,
383
+ dataRef,
384
+ }),
385
+ [refs]
386
+ )
373
387
 
374
388
  return (
375
389
  <SelectProvider
@@ -378,7 +392,7 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
378
392
  setScrollTop={setScrollTop}
379
393
  setInnerOffset={setInnerOffset}
380
394
  fallback={fallback}
381
- floatingContext={context}
395
+ floatingContext={floatingContext as any}
382
396
  canScrollDown={!!showDownArrow}
383
397
  canScrollUp={!!showUpArrow}
384
398
  controlledScrolling={controlledScrolling}
@@ -392,25 +406,13 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
392
406
  {...selectItemParentContext}
393
407
  allowMouseUpRef={allowMouseUpRef}
394
408
  allowSelectRef={allowSelectRef}
395
- dataRef={context.dataRef}
409
+ dataRef={dataRef as any}
396
410
  interactions={interactionsContext}
397
411
  listRef={listItemsRef}
398
412
  selectTimeoutRef={selectTimeoutRef}
399
413
  >
400
414
  {children}
401
415
  </SelectItemParentProvider>
402
- {/* {isFormControl ? (
403
- <BubbleSelect
404
- ref={setBubbleSelect}
405
- aria-hidden
406
- tabIndex={-1}
407
- name={name}
408
- autoComplete={autoComplete}
409
- value={value}
410
- // enable form autofill
411
- onChange={(event) => setValue(event.target.value)}
412
- />
413
- ) : null} */}
414
416
  </SelectProvider>
415
417
  )
416
418
  }
@@ -53,6 +53,7 @@ export const SelectItem = ListItem.Frame.styleable<SelectItemExtraProps>(
53
53
  setOpen,
54
54
  onChange,
55
55
  activeIndexSubscribe,
56
+ activeIndexRef,
56
57
  valueSubscribe,
57
58
  allowMouseUpRef,
58
59
  allowSelectRef,
@@ -77,17 +78,26 @@ export const SelectItem = ListItem.Frame.styleable<SelectItemExtraProps>(
77
78
  }, [])
78
79
 
79
80
  React.useEffect(() => {
80
- return activeIndexSubscribe((i) => {
81
- const isActive = index === i
82
-
83
- if (isActive) {
81
+ const handleActiveIndex = (i: number) => {
82
+ if (index === i) {
84
83
  onActiveChange(value, index)
85
-
86
84
  if (isWeb) {
87
- listRef?.current[index]?.focus()
85
+ // use rAF to focus after browser's click handling completes
86
+ // this prevents the trigger from stealing focus after we set it
87
+ requestAnimationFrame(() => {
88
+ listRef?.current[index]?.focus()
89
+ })
88
90
  }
89
91
  }
90
- })
92
+ }
93
+
94
+ // check initial value (parent effect may have set it before we subscribed)
95
+ const currentActiveIndex = activeIndexRef?.current
96
+ if (currentActiveIndex !== null && currentActiveIndex !== undefined) {
97
+ handleActiveIndex(currentActiveIndex)
98
+ }
99
+
100
+ return activeIndexSubscribe(handleActiveIndex)
91
101
  }, [index])
92
102
 
93
103
  React.useEffect(() => {
@@ -1,4 +1,4 @@
1
- import { autoUpdate, offset, useFloating } from '@floating-ui/react'
1
+ import { autoUpdate, offset, useFloatingRaw as useFloating } from '@tamagui/floating'
2
2
  import { useComposedRefs } from '@tamagui/compose-refs'
3
3
  import type { TamaguiElement } from '@tamagui/core'
4
4
  import { YStack } from '@tamagui/stacks'
@@ -72,7 +72,7 @@ export const SelectTrigger = React.forwardRef<TamaguiElement, SelectTriggerProps
72
72
  }
73
73
  : {
74
74
  onMouseDown() {
75
- context.floatingContext?.update()
75
+ context.floatingContext?.update?.()
76
76
  itemParentContext.setOpen(!context.open)
77
77
  },
78
78
  }),
@@ -1,4 +1,3 @@
1
- import { FloatingFocusManager } from '@floating-ui/react'
2
1
  import { AdaptPortalContents, useAdaptIsActive } from '@tamagui/adapt'
3
2
  import { AnimatePresence } from '@tamagui/animate-presence'
4
3
  import { useComposedRefs } from '@tamagui/compose-refs'
@@ -6,6 +5,8 @@ import { isWeb, useIsomorphicLayoutEffect } from '@tamagui/constants'
6
5
  import { styled } from '@tamagui/core'
7
6
  import { needsPortalRepropagation } from '@tamagui/portal'
8
7
  import { YStack } from '@tamagui/stacks'
8
+ import { startTransition } from '@tamagui/start-transition'
9
+ import * as React from 'react'
9
10
  import { VIEWPORT_NAME } from './constants'
10
11
  import {
11
12
  ForwardSelectContext,
@@ -56,6 +57,18 @@ export const SelectViewport = SelectViewportFrame.styleable<SelectViewportExtraP
56
57
  const itemContext = useSelectItemParentContext(scope)
57
58
  const isAdapted = useAdaptIsActive(context.adaptScope)
58
59
 
60
+ // lazy mount: defer mounting children until first open using startTransition
61
+ const [lazyMounted, setLazyMounted] = React.useState(context.lazyMount ? false : true)
62
+
63
+ React.useEffect(() => {
64
+ if (!context.lazyMount) return
65
+ if (!context.open) return
66
+ if (lazyMounted) return
67
+ startTransition(() => {
68
+ setLazyMounted(true)
69
+ })
70
+ }, [context.lazyMount, context.open, lazyMounted])
71
+
59
72
  const composedRefs = useComposedRefs(
60
73
  // @ts-ignore TODO react 19 type needs fix
61
74
  forwardedRef,
@@ -68,6 +81,14 @@ export const SelectViewport = SelectViewportFrame.styleable<SelectViewportExtraP
68
81
  }
69
82
  }, [isAdapted])
70
83
 
84
+ // after lazy children mount, force floating-ui to recompute so inner middleware
85
+ // can position using the now-present list items
86
+ useIsomorphicLayoutEffect(() => {
87
+ if (context.lazyMount && lazyMounted && context.open && context.update) {
88
+ context.update()
89
+ }
90
+ }, [lazyMounted])
91
+
71
92
  if (itemContext.shouldRenderWebNative) {
72
93
  return <YStack position="relative">{children}</YStack>
73
94
  }
@@ -103,6 +124,8 @@ export const SelectViewport = SelectViewportFrame.styleable<SelectViewportExtraP
103
124
  ...floatingProps
104
125
  } = itemContext.interactions.getFloatingProps()
105
126
 
127
+ // FloatingFocusManager removed — SelectContent already wraps with FocusScope
128
+ // that handles focus trapping and auto-focus
106
129
  return (
107
130
  <>
108
131
  {!disableScroll && !props.unstyled && (
@@ -114,31 +137,28 @@ export const SelectViewport = SelectViewportFrame.styleable<SelectViewportExtraP
114
137
  )}
115
138
  <AnimatePresence>
116
139
  {context.open ? (
117
- <FloatingFocusManager
118
- context={context.floatingContext!}
119
- modal={false}
120
- initialFocus={-1}
140
+ <SelectViewportFrame
141
+ key="select-viewport"
142
+ size={itemContext.size}
143
+ role="presentation"
144
+ {...viewportProps}
145
+ {...style}
146
+ {...floatingProps}
147
+ {...(!props.unstyled && {
148
+ overflowY: disableScroll ? undefined : (style.overflow ?? 'auto'),
149
+ })}
150
+ ref={composedRefs}
121
151
  >
122
- <SelectViewportFrame
123
- key="select-viewport"
124
- size={itemContext.size}
125
- role="presentation"
126
- {...viewportProps}
127
- {...style}
128
- {...floatingProps}
129
- {...(!props.unstyled && {
130
- overflowY: disableScroll ? undefined : (style.overflow ?? 'auto'),
131
- })}
132
- ref={composedRefs}
133
- >
134
- {children}
135
- </SelectViewportFrame>
136
- </FloatingFocusManager>
152
+ {lazyMounted ? children : null}
153
+ </SelectViewportFrame>
137
154
  ) : null}
138
155
  </AnimatePresence>
139
156
 
140
- {/* keep in dom to allow for portal to the trigger... very hacky! we should fix */}
141
- {!context.open && <div style={{ display: 'none' }}>{props.children}</div>}
157
+ {/* keep in dom to allow for portal to the trigger when renderValue isn't provided */}
158
+ {/* when lazyMount is enabled and renderValue is provided, skip this entirely for performance */}
159
+ {!context.open && !(context.lazyMount && context.renderValue) && lazyMounted && (
160
+ <div style={{ display: 'none' }}>{children}</div>
161
+ )}
142
162
  </>
143
163
  )
144
164
  }
package/src/context.tsx CHANGED
@@ -1,7 +1,11 @@
1
1
  import { createStyledContext } from '@tamagui/core'
2
2
  import { getPortal } from '@tamagui/native'
3
+ import { createContext } from 'react'
3
4
  import type { SelectContextValue, SelectItemParentContextValue } from './types'
4
5
 
6
+ // zIndex flows from root Select prop to SelectContent portal
7
+ export const SelectZIndexContext = createContext<number | undefined>(undefined)
8
+
5
9
  export const { Provider: SelectProvider, useStyledContext: useSelectContext } =
6
10
  createStyledContext<SelectContextValue>(null as any, 'Select')
7
11
 
package/src/types.tsx CHANGED
@@ -1,7 +1,28 @@
1
- import type { ContextData, FloatingContext, ReferenceType } from '@floating-ui/react'
2
1
  import type { NativeValue, SizeTokens } from '@tamagui/core'
3
- import type { ThemeableStackProps, YStackProps } from '@tamagui/stacks'
4
- import type { DispatchWithoutAction, HTMLProps, MutableRefObject, ReactNode } from 'react'
2
+ import type { YStackProps } from '@tamagui/stacks'
3
+ import type {
4
+ DispatchWithoutAction,
5
+ HTMLProps,
6
+ MutableRefObject,
7
+ ReactNode,
8
+ RefObject,
9
+ } from 'react'
10
+
11
+ // minimal types replacing @floating-ui/react imports
12
+ type ContextData = Record<string, any>
13
+ type ReferenceType = Element
14
+ type FloatingContext<RT = ReferenceType> = {
15
+ refs: {
16
+ reference: RefObject<RT | null>
17
+ floating: RefObject<HTMLElement | null>
18
+ setFloating: (el: HTMLElement | null) => void
19
+ setReference: (el: RT | null) => void
20
+ [key: string]: any
21
+ }
22
+ dataRef: RefObject<ContextData>
23
+ update?: () => void
24
+ [key: string]: any
25
+ }
5
26
 
6
27
  export type SelectDirection = 'ltr' | 'rtl'
7
28
 
@@ -59,6 +80,23 @@ export interface SelectProps<Value extends string = string> {
59
80
  * ```
60
81
  */
61
82
  renderValue?(value: Value): ReactNode
83
+
84
+ /**
85
+ * When true, defers mounting Select items until opened using startTransition.
86
+ * This significantly improves initial render performance for pages with many Selects.
87
+ *
88
+ * Should be combined with `renderValue` to display the selected value during SSR
89
+ * and before items are mounted.
90
+ *
91
+ * @default false
92
+ */
93
+ lazyMount?: boolean
94
+
95
+ /**
96
+ * z-index for the select portal. Use this when select dropdowns need to appear
97
+ * above other portaled content like dialogs or fixed headers.
98
+ */
99
+ zIndex?: number
62
100
  }
63
101
 
64
102
  type DisposeFn = () => void
@@ -75,6 +113,7 @@ export interface SelectItemParentContextValue {
75
113
  onChange: (value: string) => void
76
114
  onActiveChange: (value: string, index: number) => void
77
115
  activeIndexSubscribe: EmitterSubscriber<number>
116
+ activeIndexRef?: MutableRefObject<number | null>
78
117
  valueSubscribe: EmitterSubscriber<any>
79
118
  allowSelectRef?: MutableRefObject<boolean>
80
119
  allowMouseUpRef?: MutableRefObject<boolean>
@@ -131,6 +170,8 @@ export interface SelectContextValue {
131
170
  update?: () => void
132
171
  /** Render function for the selected value (SSR support) */
133
172
  renderValue?: (value: any) => ReactNode
173
+ /** When true, defers mounting items until opened */
174
+ lazyMount?: boolean
134
175
  }
135
176
 
136
177
  export type SelectViewportExtraProps = SelectScopedProps<{
@@ -143,7 +184,6 @@ export type SelectViewportProps = YStackProps & SelectViewportExtraProps
143
184
 
144
185
  export type SelectContentProps = SelectScopedProps<{
145
186
  children?: React.ReactNode
146
- zIndex?: number
147
187
  }>
148
188
 
149
189
  export type SelectScrollButtonImplProps = YStackProps &
package/types/Select.d.ts CHANGED
@@ -41,7 +41,7 @@ export declare const Select: (<Value extends string = string>(props: SelectScope
41
41
  shouldForwardSpace: boolean;
42
42
  };
43
43
  };
44
- Content: ({ children, scope, zIndex, ...focusScopeProps }: import("./types").SelectContentProps & import("@tamagui/focus-scope").FocusScopeProps) => import("react/jsx-runtime").JSX.Element | null;
44
+ Content: ({ children, scope, ...focusScopeProps }: import("./types").SelectContentProps & import("@tamagui/focus-scope").FocusScopeProps) => import("react/jsx-runtime").JSX.Element | null;
45
45
  Group: React.ForwardRefExoticComponent<Omit<import("@tamagui/core").RNTamaguiViewNonStyleProps, "elevation" | keyof import("@tamagui/core").StackStyleBase | "fullscreen"> & import("@tamagui/core").WithThemeValues<import("@tamagui/core").StackStyleBase> & {
46
46
  elevation?: number | SizeTokens | undefined;
47
47
  fullscreen?: boolean | undefined;
@@ -107,7 +107,7 @@ export declare const Select: (<Value extends string = string>(props: SelectScope
107
107
  }, import("@tamagui/core").StaticConfigPublic>;
108
108
  ScrollDownButton: React.ForwardRefExoticComponent<import("./types").SelectScrollButtonProps & React.RefAttributes<TamaguiElement>>;
109
109
  ScrollUpButton: React.ForwardRefExoticComponent<import("./types").SelectScrollButtonProps & React.RefAttributes<TamaguiElement>>;
110
- Trigger: React.ForwardRefExoticComponent<Omit<import("@tamagui/core").StackNonStyleProps, "disabled" | "size" | "unstyled" | keyof import("@tamagui/core").StackStyleBase | "active" | "variant"> & import("@tamagui/core").WithThemeValues<import("@tamagui/core").StackStyleBase> & {
110
+ Trigger: React.ForwardRefExoticComponent<Omit<import("@tamagui/core").StackNonStyleProps, "disabled" | "size" | "unstyled" | keyof import("@tamagui/core").StackStyleBase | "variant" | "active"> & import("@tamagui/core").WithThemeValues<import("@tamagui/core").StackStyleBase> & {
111
111
  size?: SizeTokens | undefined;
112
112
  variant?: "outlined" | undefined;
113
113
  disabled?: boolean | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../src/Select.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAiBzF,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAc9B,OAAO,KAAK,EAAmB,WAAW,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAS9E,QAAA,MAAM,gBAAgB;;;8CAGpB,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,CAAC;IACpD,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC9B,CAAC,CAAA;AAEF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,OAAO,gBAAgB,CAAC,GAAG,qBAAqB,CAAA;AA6DxF,eAAO,MAAM,UAAU;;;8CAKrB,CAAA;AAkCF,QAAA,MAAM,oBAAoB;;;;8CAoBxB,CAAA;AAEF,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC,OAAO,oBAAoB,CAAC,CAAA;AA6DxE,eAAO,MAAM,gBAAgB;;;8CAG3B,CAAA;AAoHF,QAAA,MAAM,gBAAgB;;;8CA4BpB,CAAA;AAEF,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,QAAQ,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAA;AA4BnF,eAAO,MAAM,eAAe;;;8CAE1B,CAAA;AAoCF,eAAO,MAAM,MAAM,IACD,KAAK,SAAS,MAAM,kBAC3B,iBAAiB,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;;;sBAzZhD,GAAE;qBAA2B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAuV2B,GAAG;;gBAAH,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAjV5C,KAAK,CAAC,SAAS;;;;sBAAf,KAAK,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8a9B,CAAA"}
1
+ {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../src/Select.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAiBzF,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAe9B,OAAO,KAAK,EAAmB,WAAW,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAS9E,QAAA,MAAM,gBAAgB;;;8CAGpB,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,CAAC;IACpD,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC9B,CAAC,CAAA;AAEF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,OAAO,gBAAgB,CAAC,GAAG,qBAAqB,CAAA;AA6DxF,eAAO,MAAM,UAAU;;;8CAKrB,CAAA;AAkCF,QAAA,MAAM,oBAAoB;;;;8CAoBxB,CAAA;AAEF,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC,OAAO,oBAAoB,CAAC,CAAA;AA6DxE,eAAO,MAAM,gBAAgB;;;8CAG3B,CAAA;AAoHF,QAAA,MAAM,gBAAgB;;;8CA4BpB,CAAA;AAEF,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,QAAQ,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAA;AA4BnF,eAAO,MAAM,eAAe;;;8CAE1B,CAAA;AAoCF,eAAO,MAAM,MAAM,IACD,KAAK,SAAS,MAAM,kBAC3B,iBAAiB,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;;;sBA7ZsB,GAAI;qBAE1E,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAyVyD,GAAG;;gBAAH,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAjV5C,KAAK,CAAC,SAAS;;;;sBAAf,KAAK,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8a9B,CAAA"}
@@ -1,4 +1,4 @@
1
1
  import type { FocusScopeProps } from '@tamagui/focus-scope';
2
2
  import type { SelectContentProps } from './types';
3
- export declare const SelectContent: ({ children, scope, zIndex, ...focusScopeProps }: SelectContentProps & FocusScopeProps) => import("react/jsx-runtime").JSX.Element | null;
3
+ export declare const SelectContent: ({ children, scope, ...focusScopeProps }: SelectContentProps & FocusScopeProps) => import("react/jsx-runtime").JSX.Element | null;
4
4
  //# sourceMappingURL=SelectContent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SelectContent.d.ts","sourceRoot":"","sources":["../src/SelectContent.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAK3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAOjD,eAAO,MAAM,aAAa,GAAI,iDAK3B,kBAAkB,GAAG,eAAe,mDAmDtC,CAAA"}
1
+ {"version":3,"file":"SelectContent.d.ts","sourceRoot":"","sources":["../src/SelectContent.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAW3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAOjD,eAAO,MAAM,aAAa,GAAI,yCAI3B,kBAAkB,GAAG,eAAe,mDAyDtC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"SelectImpl.d.ts","sourceRoot":"","sources":["../src/SelectImpl.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAG9C,eAAO,MAAM,gBAAgB,GAAI,OAAO,eAAe,4CAgYtD,CAAA"}
1
+ {"version":3,"file":"SelectImpl.d.ts","sourceRoot":"","sources":["../src/SelectImpl.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAG9C,eAAO,MAAM,gBAAgB,GAAI,OAAO,eAAe,4CAkYtD,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"SelectItem.d.ts","sourceRoot":"","sources":["../src/SelectItem.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAU9B,KAAK,sBAAsB,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,eAAO,MACK,yBAAyB;;;IACjB,oBAAoB,4CAC+B,CAAA;AAEvE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eACf,SAAQ,IAAI,CAAC,aAAa,EAAE,MAAM,oBAAoB,CAAC,EAAE,oBAAoB;CAAG;AAElF,eAAO,MAAM,UAAU;;;;;;;;;;;;8CAoNtB,CAAA"}
1
+ {"version":3,"file":"SelectItem.d.ts","sourceRoot":"","sources":["../src/SelectItem.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAU9B,KAAK,sBAAsB,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,eAAO,MACK,yBAAyB;;;IACjB,oBAAoB,4CAC+B,CAAA;AAEvE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eACf,SAAQ,IAAI,CAAC,aAAa,EAAE,MAAM,oBAAoB,CAAC,EAAE,oBAAoB;CAAG;AAElF,eAAO,MAAM,UAAU;;;;;;;;;;;;8CA8NtB,CAAA"}
@@ -3,7 +3,7 @@ import type { ListItemProps } from '@tamagui/list-item';
3
3
  import * as React from 'react';
4
4
  import type { SelectScopedProps } from './types';
5
5
  export type SelectTriggerProps = SelectScopedProps<ListItemProps>;
6
- export declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<import("@tamagui/core").StackNonStyleProps, "disabled" | "size" | "unstyled" | keyof import("@tamagui/core").StackStyleBase | "active" | "variant"> & import("@tamagui/core").WithThemeValues<import("@tamagui/core").StackStyleBase> & {
6
+ export declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<import("@tamagui/core").StackNonStyleProps, "disabled" | "size" | "unstyled" | keyof import("@tamagui/core").StackStyleBase | "variant" | "active"> & import("@tamagui/core").WithThemeValues<import("@tamagui/core").StackStyleBase> & {
7
7
  size?: import("@tamagui/core").SizeTokens | undefined;
8
8
  variant?: "outlined" | undefined;
9
9
  disabled?: boolean | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"SelectViewport.d.ts","sourceRoot":"","sources":["../src/SelectViewport.tsx"],"names":[],"mappings":"AAoBA,eAAO,MAAM,mBAAmB;;;;;8CA2B9B,CAAA;AAIF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;8CA6F1B,CAAA"}
1
+ {"version":3,"file":"SelectViewport.d.ts","sourceRoot":"","sources":["../src/SelectViewport.tsx"],"names":[],"mappings":"AAqBA,eAAO,MAAM,mBAAmB;;;;;8CA2B9B,CAAA;AAIF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;8CAgH1B,CAAA"}
@@ -1,4 +1,5 @@
1
1
  import type { SelectContextValue, SelectItemParentContextValue } from './types';
2
+ export declare const SelectZIndexContext: import("react").Context<number | undefined>;
2
3
  export declare const SelectProvider: import("react").Provider<SelectContextValue> & import("react").ProviderExoticComponent<Partial<SelectContextValue> & {
3
4
  children?: import("react").ReactNode;
4
5
  scope?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAA;AAE/E,eAAO,MAAkB,cAAc;;;IAAoB,gBAAgB,wCACX,CAAA;AAIhE,eAAO,MACK,wBAAwB;;;IAChB,0BAA0B,kDACkC,CAAA;AAEhF,eAAO,MAAM,oBAAoB,GAAI,qCAIlC;IACD,QAAQ,CAAC,EAAE,GAAG,CAAA;IACd,OAAO,EAAE,kBAAkB,CAAA;IAC3B,WAAW,EAAE,4BAA4B,CAAA;CAC1C,QAgBA,CAAA"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAA;AAG/E,eAAO,MAAM,mBAAmB,6CAA+C,CAAA;AAE/E,eAAO,MAAkB,cAAc;;;IAAoB,gBAAgB,wCACX,CAAA;AAIhE,eAAO,MACK,wBAAwB;;;IAChB,0BAA0B,kDACkC,CAAA;AAEhF,eAAO,MAAM,oBAAoB,GAAI,qCAIlC;IACD,QAAQ,CAAC,EAAE,GAAG,CAAA;IACd,OAAO,EAAE,kBAAkB,CAAA;IAC3B,WAAW,EAAE,4BAA4B,CAAA;CAC1C,QAgBA,CAAA"}
package/types/types.d.ts CHANGED
@@ -1,7 +1,20 @@
1
- import type { ContextData, FloatingContext, ReferenceType } from '@floating-ui/react';
2
1
  import type { NativeValue, SizeTokens } from '@tamagui/core';
3
2
  import type { YStackProps } from '@tamagui/stacks';
4
- import type { DispatchWithoutAction, HTMLProps, MutableRefObject, ReactNode } from 'react';
3
+ import type { DispatchWithoutAction, HTMLProps, MutableRefObject, ReactNode, RefObject } from 'react';
4
+ type ContextData = Record<string, any>;
5
+ type ReferenceType = Element;
6
+ type FloatingContext<RT = ReferenceType> = {
7
+ refs: {
8
+ reference: RefObject<RT | null>;
9
+ floating: RefObject<HTMLElement | null>;
10
+ setFloating: (el: HTMLElement | null) => void;
11
+ setReference: (el: RT | null) => void;
12
+ [key: string]: any;
13
+ };
14
+ dataRef: RefObject<ContextData>;
15
+ update?: () => void;
16
+ [key: string]: any;
17
+ };
5
18
  export type SelectDirection = 'ltr' | 'rtl';
6
19
  export type SelectScopes = string;
7
20
  export type SelectScopedProps<P> = P & {
@@ -53,6 +66,21 @@ export interface SelectProps<Value extends string = string> {
53
66
  * ```
54
67
  */
55
68
  renderValue?(value: Value): ReactNode;
69
+ /**
70
+ * When true, defers mounting Select items until opened using startTransition.
71
+ * This significantly improves initial render performance for pages with many Selects.
72
+ *
73
+ * Should be combined with `renderValue` to display the selected value during SSR
74
+ * and before items are mounted.
75
+ *
76
+ * @default false
77
+ */
78
+ lazyMount?: boolean;
79
+ /**
80
+ * z-index for the select portal. Use this when select dropdowns need to appear
81
+ * above other portaled content like dialogs or fixed headers.
82
+ */
83
+ zIndex?: number;
56
84
  }
57
85
  type DisposeFn = () => void;
58
86
  export type EmitterSubscriber<Val> = (cb: (val: Val) => void) => DisposeFn;
@@ -67,6 +95,7 @@ export interface SelectItemParentContextValue {
67
95
  onChange: (value: string) => void;
68
96
  onActiveChange: (value: string, index: number) => void;
69
97
  activeIndexSubscribe: EmitterSubscriber<number>;
98
+ activeIndexRef?: MutableRefObject<number | null>;
70
99
  valueSubscribe: EmitterSubscriber<any>;
71
100
  allowSelectRef?: MutableRefObject<boolean>;
72
101
  allowMouseUpRef?: MutableRefObject<boolean>;
@@ -120,6 +149,8 @@ export interface SelectContextValue {
120
149
  update?: () => void;
121
150
  /** Render function for the selected value (SSR support) */
122
151
  renderValue?: (value: any) => ReactNode;
152
+ /** When true, defers mounting items until opened */
153
+ lazyMount?: boolean;
123
154
  }
124
155
  export type SelectViewportExtraProps = SelectScopedProps<{
125
156
  size?: SizeTokens;
@@ -129,7 +160,6 @@ export type SelectViewportExtraProps = SelectScopedProps<{
129
160
  export type SelectViewportProps = YStackProps & SelectViewportExtraProps;
130
161
  export type SelectContentProps = SelectScopedProps<{
131
162
  children?: React.ReactNode;
132
- zIndex?: number;
133
163
  }>;
134
164
  export type SelectScrollButtonImplProps = YStackProps & SelectScopedProps<{
135
165
  dir: 'up' | 'down';