@tamagui/select 2.7.6 → 3.0.0-beta.637.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.
Files changed (248) hide show
  1. package/dist/cjs/Select.cjs +409 -467
  2. package/dist/cjs/Select.native.cjs +495 -0
  3. package/dist/cjs/Select.native.js +445 -492
  4. package/dist/cjs/Select.native.js.map +1 -1
  5. package/dist/cjs/SelectContent.cjs +80 -79
  6. package/dist/cjs/SelectContent.native.cjs +30 -0
  7. package/dist/cjs/SelectContent.native.js +18 -22
  8. package/dist/cjs/SelectContent.native.js.map +1 -1
  9. package/dist/cjs/SelectImpl.cjs +299 -319
  10. package/dist/cjs/SelectImpl.native.cjs +30 -0
  11. package/dist/cjs/SelectImpl.native.js +17 -24
  12. package/dist/cjs/SelectImpl.native.js.map +1 -1
  13. package/dist/cjs/SelectItem.cjs +268 -205
  14. package/dist/cjs/SelectItem.native.cjs +317 -0
  15. package/dist/cjs/SelectItem.native.js +291 -211
  16. package/dist/cjs/SelectItem.native.js.map +1 -1
  17. package/dist/cjs/SelectItemText.cjs +49 -79
  18. package/dist/cjs/SelectItemText.native.cjs +74 -0
  19. package/dist/cjs/SelectItemText.native.js +50 -79
  20. package/dist/cjs/SelectItemText.native.js.map +1 -1
  21. package/dist/cjs/SelectScrollButton.cjs +111 -130
  22. package/dist/cjs/SelectScrollButton.native.cjs +38 -0
  23. package/dist/cjs/SelectScrollButton.native.js +25 -22
  24. package/dist/cjs/SelectScrollButton.native.js.map +1 -1
  25. package/dist/cjs/SelectTrigger.cjs +69 -84
  26. package/dist/cjs/SelectTrigger.native.cjs +91 -0
  27. package/dist/cjs/SelectTrigger.native.js +71 -66
  28. package/dist/cjs/SelectTrigger.native.js.map +1 -1
  29. package/dist/cjs/SelectViewport.cjs +119 -142
  30. package/dist/cjs/SelectViewport.native.cjs +73 -0
  31. package/dist/cjs/SelectViewport.native.js +54 -50
  32. package/dist/cjs/SelectViewport.native.js.map +1 -1
  33. package/dist/cjs/constants.cjs +21 -22
  34. package/dist/cjs/constants.native.cjs +41 -0
  35. package/dist/cjs/constants.native.js +21 -21
  36. package/dist/cjs/constants.native.js.map +1 -1
  37. package/dist/cjs/context.cjs +38 -48
  38. package/dist/cjs/context.native.cjs +58 -0
  39. package/dist/cjs/context.native.js +40 -49
  40. package/dist/cjs/context.native.js.map +1 -1
  41. package/dist/cjs/index.cjs +15 -11
  42. package/dist/cjs/index.native.cjs +27 -0
  43. package/dist/cjs/index.native.js +15 -10
  44. package/dist/cjs/index.native.js.map +1 -1
  45. package/dist/cjs/selectionController.cjs +334 -0
  46. package/dist/cjs/selectionController.native.cjs +403 -0
  47. package/dist/cjs/selectionController.native.js +405 -0
  48. package/dist/cjs/selectionController.native.js.map +1 -0
  49. package/dist/cjs/types.cjs +10 -11
  50. package/dist/cjs/types.native.cjs +19 -0
  51. package/dist/cjs/types.native.js +10 -10
  52. package/dist/cjs/types.native.js.map +1 -1
  53. package/dist/esm/Select.mjs +382 -443
  54. package/dist/esm/Select.mjs.map +1 -1
  55. package/dist/esm/Select.native.js +418 -469
  56. package/dist/esm/Select.native.js.map +1 -1
  57. package/dist/esm/SelectContent.mjs +67 -61
  58. package/dist/esm/SelectContent.mjs.map +1 -1
  59. package/dist/esm/SelectContent.native.js +4 -5
  60. package/dist/esm/SelectContent.native.js.map +1 -1
  61. package/dist/esm/SelectImpl.mjs +284 -294
  62. package/dist/esm/SelectImpl.mjs.map +1 -1
  63. package/dist/esm/SelectImpl.native.js +5 -8
  64. package/dist/esm/SelectImpl.native.js.map +1 -1
  65. package/dist/esm/SelectItem.mjs +252 -182
  66. package/dist/esm/SelectItem.mjs.map +1 -1
  67. package/dist/esm/SelectItem.native.js +276 -190
  68. package/dist/esm/SelectItem.native.js.map +1 -1
  69. package/dist/esm/SelectItemText.mjs +33 -55
  70. package/dist/esm/SelectItemText.mjs.map +1 -1
  71. package/dist/esm/SelectItemText.native.js +34 -56
  72. package/dist/esm/SelectItemText.native.js.map +1 -1
  73. package/dist/esm/SelectScrollButton.mjs +95 -107
  74. package/dist/esm/SelectScrollButton.mjs.map +1 -1
  75. package/dist/esm/SelectScrollButton.native.js +11 -7
  76. package/dist/esm/SelectScrollButton.native.js.map +1 -1
  77. package/dist/esm/SelectTrigger.mjs +56 -58
  78. package/dist/esm/SelectTrigger.mjs.map +1 -1
  79. package/dist/esm/SelectTrigger.native.js +54 -44
  80. package/dist/esm/SelectTrigger.native.js.map +1 -1
  81. package/dist/esm/SelectViewport.mjs +104 -119
  82. package/dist/esm/SelectViewport.mjs.map +1 -1
  83. package/dist/esm/SelectViewport.native.js +44 -39
  84. package/dist/esm/SelectViewport.native.js.map +1 -1
  85. package/dist/esm/constants.mjs +1 -0
  86. package/dist/esm/constants.mjs.map +1 -1
  87. package/dist/esm/constants.native.js +1 -0
  88. package/dist/esm/constants.native.js.map +1 -1
  89. package/dist/esm/context.mjs +20 -28
  90. package/dist/esm/context.mjs.map +1 -1
  91. package/dist/esm/context.native.js +23 -31
  92. package/dist/esm/context.native.js.map +1 -1
  93. package/dist/esm/index.js +13 -3
  94. package/dist/esm/index.mjs +13 -3
  95. package/dist/esm/index.native.js +13 -3
  96. package/dist/esm/selectionController.mjs +305 -0
  97. package/dist/esm/selectionController.mjs.map +1 -0
  98. package/dist/esm/selectionController.native.js +372 -0
  99. package/dist/esm/selectionController.native.js.map +1 -0
  100. package/dist/esm/types.mjs +0 -2
  101. package/dist/esm/types.native.js +0 -2
  102. package/dist/jsx/Select.mjs +382 -443
  103. package/dist/jsx/Select.mjs.map +1 -1
  104. package/dist/jsx/Select.native.cjs +495 -0
  105. package/dist/jsx/Select.native.js +445 -492
  106. package/dist/jsx/Select.native.js.map +1 -1
  107. package/dist/jsx/SelectContent.mjs +67 -61
  108. package/dist/jsx/SelectContent.mjs.map +1 -1
  109. package/dist/jsx/SelectContent.native.cjs +30 -0
  110. package/dist/jsx/SelectContent.native.js +18 -22
  111. package/dist/jsx/SelectContent.native.js.map +1 -1
  112. package/dist/jsx/SelectImpl.mjs +284 -294
  113. package/dist/jsx/SelectImpl.mjs.map +1 -1
  114. package/dist/jsx/SelectImpl.native.cjs +30 -0
  115. package/dist/jsx/SelectImpl.native.js +17 -24
  116. package/dist/jsx/SelectImpl.native.js.map +1 -1
  117. package/dist/jsx/SelectItem.mjs +252 -182
  118. package/dist/jsx/SelectItem.mjs.map +1 -1
  119. package/dist/jsx/SelectItem.native.cjs +317 -0
  120. package/dist/jsx/SelectItem.native.js +291 -211
  121. package/dist/jsx/SelectItem.native.js.map +1 -1
  122. package/dist/jsx/SelectItemText.mjs +33 -55
  123. package/dist/jsx/SelectItemText.mjs.map +1 -1
  124. package/dist/jsx/SelectItemText.native.cjs +74 -0
  125. package/dist/jsx/SelectItemText.native.js +50 -79
  126. package/dist/jsx/SelectItemText.native.js.map +1 -1
  127. package/dist/jsx/SelectScrollButton.mjs +95 -107
  128. package/dist/jsx/SelectScrollButton.mjs.map +1 -1
  129. package/dist/jsx/SelectScrollButton.native.cjs +38 -0
  130. package/dist/jsx/SelectScrollButton.native.js +25 -22
  131. package/dist/jsx/SelectScrollButton.native.js.map +1 -1
  132. package/dist/jsx/SelectTrigger.mjs +56 -58
  133. package/dist/jsx/SelectTrigger.mjs.map +1 -1
  134. package/dist/jsx/SelectTrigger.native.cjs +91 -0
  135. package/dist/jsx/SelectTrigger.native.js +71 -66
  136. package/dist/jsx/SelectTrigger.native.js.map +1 -1
  137. package/dist/jsx/SelectViewport.mjs +104 -119
  138. package/dist/jsx/SelectViewport.mjs.map +1 -1
  139. package/dist/jsx/SelectViewport.native.cjs +73 -0
  140. package/dist/jsx/SelectViewport.native.js +54 -50
  141. package/dist/jsx/SelectViewport.native.js.map +1 -1
  142. package/dist/jsx/constants.mjs +1 -0
  143. package/dist/jsx/constants.mjs.map +1 -1
  144. package/dist/jsx/constants.native.cjs +41 -0
  145. package/dist/jsx/constants.native.js +21 -21
  146. package/dist/jsx/constants.native.js.map +1 -1
  147. package/dist/jsx/context.mjs +20 -28
  148. package/dist/jsx/context.mjs.map +1 -1
  149. package/dist/jsx/context.native.cjs +58 -0
  150. package/dist/jsx/context.native.js +40 -49
  151. package/dist/jsx/context.native.js.map +1 -1
  152. package/dist/jsx/index.js +13 -3
  153. package/dist/jsx/index.mjs +13 -3
  154. package/dist/jsx/index.native.cjs +27 -0
  155. package/dist/jsx/index.native.js +15 -10
  156. package/dist/jsx/index.native.js.map +1 -1
  157. package/dist/jsx/selectionController.mjs +305 -0
  158. package/dist/jsx/selectionController.mjs.map +1 -0
  159. package/dist/jsx/selectionController.native.cjs +403 -0
  160. package/dist/jsx/selectionController.native.js +405 -0
  161. package/dist/jsx/selectionController.native.js.map +1 -0
  162. package/dist/jsx/types.mjs +0 -2
  163. package/dist/jsx/types.native.cjs +19 -0
  164. package/dist/jsx/types.native.js +10 -10
  165. package/dist/jsx/types.native.js.map +1 -1
  166. package/package.json +28 -28
  167. package/src/Select.tsx +457 -357
  168. package/src/SelectContent.tsx +46 -16
  169. package/src/SelectImpl.native.tsx +0 -8
  170. package/src/SelectImpl.tsx +87 -100
  171. package/src/SelectItem.tsx +282 -136
  172. package/src/SelectItemText.tsx +16 -33
  173. package/src/SelectScrollButton.native.tsx +13 -4
  174. package/src/SelectScrollButton.tsx +56 -41
  175. package/src/SelectTrigger.tsx +77 -53
  176. package/src/SelectViewport.native.tsx +42 -25
  177. package/src/SelectViewport.tsx +52 -51
  178. package/src/context.tsx +6 -2
  179. package/src/index.tsx +5 -2
  180. package/src/selectionController.ts +389 -0
  181. package/src/types.tsx +88 -45
  182. package/types/Select.d.ts +139 -201
  183. package/types/Select.d.ts.map +1 -1
  184. package/types/SelectContent.d.ts +1 -1
  185. package/types/SelectContent.d.ts.map +1 -1
  186. package/types/SelectContent.native.d.ts.map +1 -1
  187. package/types/SelectImpl.d.ts +2 -1
  188. package/types/SelectImpl.d.ts.map +1 -1
  189. package/types/SelectImpl.native.d.ts +1 -5
  190. package/types/SelectImpl.native.d.ts.map +1 -1
  191. package/types/SelectItem.d.ts +13 -16
  192. package/types/SelectItem.d.ts.map +1 -1
  193. package/types/SelectItemText.d.ts +9 -13
  194. package/types/SelectItemText.d.ts.map +1 -1
  195. package/types/SelectScrollButton.d.ts +15 -2
  196. package/types/SelectScrollButton.d.ts.map +1 -1
  197. package/types/SelectScrollButton.native.d.ts +16 -3
  198. package/types/SelectScrollButton.native.d.ts.map +1 -1
  199. package/types/SelectTrigger.d.ts +11 -23
  200. package/types/SelectTrigger.d.ts.map +1 -1
  201. package/types/SelectViewport.d.ts +11 -38
  202. package/types/SelectViewport.d.ts.map +1 -1
  203. package/types/SelectViewport.native.d.ts +15 -4
  204. package/types/SelectViewport.native.d.ts.map +1 -1
  205. package/types/context.d.ts +2 -0
  206. package/types/context.d.ts.map +1 -1
  207. package/types/index.d.ts +5 -2
  208. package/types/index.d.ts.map +1 -1
  209. package/types/selectionController.d.ts +88 -0
  210. package/types/selectionController.d.ts.map +1 -0
  211. package/types/types.d.ts +47 -37
  212. package/types/types.d.ts.map +1 -1
  213. package/dist/cjs/BubbleSelect.cjs +0 -50
  214. package/dist/cjs/BubbleSelect.native.js +0 -30
  215. package/dist/cjs/BubbleSelect.native.js.map +0 -1
  216. package/dist/cjs/useSelectBreakpointActive.cjs +0 -32
  217. package/dist/cjs/useSelectBreakpointActive.native.js +0 -35
  218. package/dist/cjs/useSelectBreakpointActive.native.js.map +0 -1
  219. package/dist/esm/BubbleSelect.mjs +0 -27
  220. package/dist/esm/BubbleSelect.mjs.map +0 -1
  221. package/dist/esm/BubbleSelect.native.js +0 -4
  222. package/dist/esm/BubbleSelect.native.js.map +0 -1
  223. package/dist/esm/index.js.map +0 -1
  224. package/dist/esm/index.mjs.map +0 -1
  225. package/dist/esm/index.native.js.map +0 -1
  226. package/dist/esm/types.mjs.map +0 -1
  227. package/dist/esm/types.native.js.map +0 -1
  228. package/dist/esm/useSelectBreakpointActive.mjs +0 -7
  229. package/dist/esm/useSelectBreakpointActive.mjs.map +0 -1
  230. package/dist/esm/useSelectBreakpointActive.native.js +0 -7
  231. package/dist/esm/useSelectBreakpointActive.native.js.map +0 -1
  232. package/dist/jsx/BubbleSelect.mjs +0 -27
  233. package/dist/jsx/BubbleSelect.mjs.map +0 -1
  234. package/dist/jsx/BubbleSelect.native.js +0 -30
  235. package/dist/jsx/BubbleSelect.native.js.map +0 -1
  236. package/dist/jsx/index.js.map +0 -1
  237. package/dist/jsx/index.mjs.map +0 -1
  238. package/dist/jsx/types.mjs.map +0 -1
  239. package/dist/jsx/useSelectBreakpointActive.mjs +0 -7
  240. package/dist/jsx/useSelectBreakpointActive.mjs.map +0 -1
  241. package/dist/jsx/useSelectBreakpointActive.native.js +0 -35
  242. package/dist/jsx/useSelectBreakpointActive.native.js.map +0 -1
  243. package/src/BubbleSelect.tsx +0 -50
  244. package/src/useSelectBreakpointActive.tsx +0 -7
  245. package/types/BubbleSelect.d.ts +0 -2
  246. package/types/BubbleSelect.d.ts.map +0 -1
  247. package/types/useSelectBreakpointActive.d.ts +0 -3
  248. package/types/useSelectBreakpointActive.d.ts.map +0 -1
@@ -1,7 +1,8 @@
1
+ import { createStyledHOC, styled, View } from '@tamagui/core'
1
2
  import { autoUpdate, offset, useFloatingRaw as useFloating } from '@tamagui/floating'
2
3
  import { useComposedRefs } from '@tamagui/compose-refs'
3
4
  import type { TamaguiElement } from '@tamagui/core'
4
- import { YStack } from '@tamagui/stacks'
5
+ import { composeEventHandlers } from '@tamagui/helpers'
5
6
  import * as React from 'react'
6
7
  import { flushSync } from 'react-dom'
7
8
 
@@ -18,20 +19,24 @@ import type {
18
19
 
19
20
  const SCROLL_UP_BUTTON_NAME = 'SelectScrollUpButton'
20
21
 
21
- export const SelectScrollUpButton = React.forwardRef<
22
- TamaguiElement,
23
- SelectScrollButtonProps
24
- >((props: SelectScopedProps<SelectScrollButtonProps>, forwardedRef) => {
25
- return (
26
- <SelectScrollButtonImpl
27
- componentName={SCROLL_UP_BUTTON_NAME}
28
- {...props}
29
- dir="up"
30
- ref={forwardedRef}
31
- />
32
- )
22
+ export const SelectScrollButtonFrame = styled(View, {
23
+ displayName: 'SelectScrollButton',
33
24
  })
34
25
 
26
+ export const SelectScrollUpButton = createStyledHOC(
27
+ SelectScrollButtonFrame,
28
+ (props: SelectScopedProps<SelectScrollButtonProps>, forwardedRef) => {
29
+ return (
30
+ <SelectScrollButtonImpl
31
+ partClassName={`is_${SCROLL_UP_BUTTON_NAME}`}
32
+ {...props}
33
+ dir="up"
34
+ ref={forwardedRef}
35
+ />
36
+ )
37
+ }
38
+ )
39
+
35
40
  SelectScrollUpButton.displayName = SCROLL_UP_BUTTON_NAME
36
41
 
37
42
  /* -------------------------------------------------------------------------------------------------
@@ -40,29 +45,30 @@ SelectScrollUpButton.displayName = SCROLL_UP_BUTTON_NAME
40
45
 
41
46
  const SCROLL_DOWN_BUTTON_NAME = 'SelectScrollDownButton'
42
47
 
43
- export const SelectScrollDownButton = React.forwardRef<
44
- TamaguiElement,
45
- SelectScrollButtonProps
46
- >((props: SelectScopedProps<SelectScrollButtonProps>, forwardedRef) => {
47
- return (
48
- <SelectScrollButtonImpl
49
- componentName={SCROLL_DOWN_BUTTON_NAME}
50
- {...props}
51
- dir="down"
52
- ref={forwardedRef}
53
- />
54
- )
55
- })
48
+ export const SelectScrollDownButton = createStyledHOC(
49
+ SelectScrollButtonFrame,
50
+ (props: SelectScopedProps<SelectScrollButtonProps>, forwardedRef) => {
51
+ return (
52
+ <SelectScrollButtonImpl
53
+ partClassName={`is_${SCROLL_DOWN_BUTTON_NAME}`}
54
+ {...props}
55
+ dir="down"
56
+ ref={forwardedRef}
57
+ />
58
+ )
59
+ }
60
+ )
56
61
 
57
62
  SelectScrollDownButton.displayName = SCROLL_DOWN_BUTTON_NAME
58
63
 
59
- type SelectScrollButtonImplElement = TamaguiElement
60
-
61
64
  const SelectScrollButtonImpl = React.memo(
62
- React.forwardRef<SelectScrollButtonImplElement, SelectScrollButtonImplProps>(
63
- (props, forwardedRef) => {
64
- const { scope, dir, componentName, ...scrollIndicatorProps } = props
65
- const { forceUpdate, open, fallback, setScrollTop, setInnerOffset, ...context } =
65
+ createStyledHOC(
66
+ SelectScrollButtonFrame,
67
+ (props: SelectScrollButtonImplProps, forwardedRef) => {
68
+ const { scope, dir, partClassName, ...scrollIndicatorProps } = props
69
+ const { className, onPointerEnter, onPointerLeave, ...frameProps } =
70
+ scrollIndicatorProps
71
+ const { open, fallback, setScrollTop, setInnerOffset, ...context } =
66
72
  useSelectContext(scope)
67
73
  const floatingRef = context.floatingContext?.refs.floating
68
74
 
@@ -81,8 +87,17 @@ const SelectScrollButtonImpl = React.memo(
81
87
  whileElementsMounted: (...args) => autoUpdate(...args, { animationFrame: true }),
82
88
  })
83
89
 
84
- // @ts-ignore TODO react 19 type needs fix
85
- const composedRef = useComposedRefs(forwardedRef, refs.setFloating)
90
+ const setFloatingRef = React.useCallback(
91
+ (node: TamaguiElement | null) => {
92
+ refs.setFloating(node as HTMLElement | null)
93
+ },
94
+ [refs.setFloating]
95
+ )
96
+ const composedRef = useComposedRefs(forwardedRef, setFloatingRef)
97
+
98
+ React.useEffect(() => {
99
+ return () => cancelAnimationFrame(frameRef.current)
100
+ }, [])
86
101
 
87
102
  if (!isVisible) {
88
103
  return null
@@ -102,17 +117,17 @@ const SelectScrollButtonImpl = React.memo(
102
117
  }
103
118
 
104
119
  return (
105
- <YStack
120
+ <SelectScrollButtonFrame
106
121
  ref={composedRef}
107
- componentName={componentName}
122
+ className={`${partClassName} ${className || ''}`.trim()}
108
123
  aria-hidden
109
- {...scrollIndicatorProps}
124
+ {...frameProps}
110
125
  zIndex={1000}
111
126
  position={strategy}
112
127
  left={x || 0}
113
128
  top={y || 0}
114
129
  width={`calc(${(floatingRef?.current?.offsetWidth ?? 0) - 2}px)`}
115
- onPointerEnter={() => {
130
+ onPointerEnter={composeEventHandlers(onPointerEnter as any, () => {
116
131
  statusRef.current = 'active'
117
132
  let prevNow = Date.now()
118
133
 
@@ -150,11 +165,11 @@ const SelectScrollButtonImpl = React.memo(
150
165
 
151
166
  cancelAnimationFrame(frameRef.current)
152
167
  frameRef.current = requestAnimationFrame(frame)
153
- }}
154
- onPointerLeave={() => {
168
+ })}
169
+ onPointerLeave={composeEventHandlers(onPointerLeave as any, () => {
155
170
  statusRef.current = 'idle'
156
171
  cancelAnimationFrame(frameRef.current)
157
- }}
172
+ })}
158
173
  />
159
174
  )
160
175
  }
@@ -1,87 +1,111 @@
1
1
  import { useComposedRefs } from '@tamagui/compose-refs'
2
- import type { TamaguiElement } from '@tamagui/core'
3
- import type { ListItemProps } from '@tamagui/list-item'
4
- import { ListItem } from '@tamagui/list-item'
2
+ import { isWeb } from '@tamagui/constants'
3
+ import {
4
+ createChangeEventDetails,
5
+ createStyledHOC,
6
+ styled,
7
+ View,
8
+ type GetProps,
9
+ } from '@tamagui/core'
10
+ import { composeEventHandlers } from '@tamagui/helpers'
5
11
  import * as React from 'react'
6
12
 
7
13
  import { useSelectContext, useSelectItemParentContext } from './context'
8
- import type { SelectScopedProps } from './types'
14
+ import type { SelectOpenChangeDetails, SelectScopedProps } from './types'
9
15
 
10
- /* -------------------------------------------------------------------------------------------------
11
- * SelectTrigger
12
- * -----------------------------------------------------------------------------------------------*/
13
16
  const TRIGGER_NAME = 'SelectTrigger'
14
17
 
15
- export type SelectTriggerProps = SelectScopedProps<ListItemProps>
18
+ export const SelectTriggerFrame = styled(View, {
19
+ displayName: TRIGGER_NAME,
20
+ alignItems: 'center',
21
+ flexDirection: 'row',
22
+ render: <button type="button" />,
23
+ })
16
24
 
17
- const isPointerCoarse =
18
- typeof window !== 'undefined' && process.env.TAMAGUI_TARGET === 'web'
19
- ? window.matchMedia('(pointer:coarse)').matches
20
- : true
25
+ export type SelectTriggerProps = SelectScopedProps<GetProps<typeof SelectTriggerFrame>>
21
26
 
22
- export const SelectTrigger = React.forwardRef<TamaguiElement, SelectTriggerProps>(
27
+ export const SelectTrigger = createStyledHOC(
28
+ SelectTriggerFrame,
23
29
  function SelectTrigger(props: SelectTriggerProps, forwardedRef) {
24
- const { scope, disabled = false, unstyled = false, ...triggerProps } = props
25
-
30
+ const { scope, disabled = false, ...triggerProps } = props
26
31
  const context = useSelectContext(scope)
27
32
  const itemParentContext = useSelectItemParentContext(scope)
28
33
  const composedRefs = useComposedRefs(
29
34
  forwardedRef,
30
35
  context.floatingContext?.refs.setReference as any
31
36
  )
32
- // const getItems = useCollection(__scopeSelect)
33
- // const labelId = useLabelContext(context.trigger)
34
- // const labelledBy = ariaLabelledby || labelId
37
+
35
38
  if (itemParentContext.shouldRenderWebNative) {
36
39
  return null
37
40
  }
38
41
 
42
+ const toggleOpen = (
43
+ event?: any,
44
+ reason: 'trigger-press' | 'keyboard' = 'trigger-press'
45
+ ) => {
46
+ if (!disabled) {
47
+ itemParentContext.requestOpenChange(
48
+ !context.open,
49
+ createChangeEventDetails(
50
+ reason,
51
+ event?.nativeEvent || event,
52
+ event?.currentTarget
53
+ ) as SelectOpenChangeDetails
54
+ )
55
+ }
56
+ }
57
+ const interactionProps =
58
+ process.env.TAMAGUI_TARGET === 'web' && itemParentContext.interactions
59
+ ? itemParentContext.interactions.getReferenceProps({
60
+ ...triggerProps,
61
+ onMouseDown: composeEventHandlers(triggerProps.onMouseDown as any, () =>
62
+ context.floatingContext?.update?.()
63
+ ),
64
+ } as any)
65
+ : {
66
+ ...triggerProps,
67
+ ...(isWeb
68
+ ? {
69
+ onMouseDown: composeEventHandlers(
70
+ triggerProps.onMouseDown as any,
71
+ (event: any) => {
72
+ event.preventDefault()
73
+ toggleOpen(event)
74
+ }
75
+ ),
76
+ }
77
+ : {
78
+ onPress: composeEventHandlers(triggerProps.onPress as any, toggleOpen),
79
+ }),
80
+ onKeyDown: composeEventHandlers(
81
+ triggerProps.onKeyDown as any,
82
+ (event: any) => {
83
+ if (event.key === 'Enter' || event.key === ' ') {
84
+ event.preventDefault()
85
+ toggleOpen(event, 'keyboard')
86
+ }
87
+ }
88
+ ),
89
+ }
90
+
39
91
  return (
40
- <ListItem
41
- componentName={TRIGGER_NAME}
42
- unstyled={unstyled}
43
- render="button"
92
+ <SelectTriggerFrame
44
93
  type="button"
45
94
  id={itemParentContext.id}
46
- {...(!unstyled && {
47
- focusVisibleStyle: {
48
- outlineStyle: 'solid',
49
- outlineWidth: 2,
50
- outlineColor: '$outlineColor',
51
- },
52
- borderWidth: 1,
53
- size: itemParentContext.size,
54
- })}
55
95
  role="combobox"
56
96
  aria-haspopup="listbox"
57
97
  aria-expanded={context.open}
98
+ {...(process.env.TAMAGUI_TARGET === 'web' && {
99
+ 'data-state': context.open ? 'open' : 'closed',
100
+ })}
58
101
  aria-autocomplete="none"
102
+ accessibilityRole={isWeb ? undefined : 'button'}
103
+ accessibilityState={isWeb ? undefined : { expanded: context.open, disabled }}
59
104
  dir={context.dir}
60
105
  disabled={disabled}
61
106
  data-disabled={disabled ? '' : undefined}
62
- {...triggerProps}
107
+ {...interactionProps}
63
108
  ref={composedRefs}
64
- {...(process.env.TAMAGUI_TARGET === 'web' && itemParentContext.interactions
65
- ? {
66
- ...itemParentContext.interactions.getReferenceProps(),
67
- ...(isPointerCoarse
68
- ? {
69
- onPress() {
70
- itemParentContext.setOpen(!context.open)
71
- },
72
- }
73
- : {
74
- onMouseDown() {
75
- context.floatingContext?.update?.()
76
- itemParentContext.setOpen(!context.open)
77
- },
78
- }),
79
- }
80
- : {
81
- onPress() {
82
- itemParentContext.setOpen(!context.open)
83
- },
84
- })}
85
109
  />
86
110
  )
87
111
  }
@@ -1,6 +1,10 @@
1
- import { AdaptContext, AdaptPortalContents, useAdaptContext } from '@tamagui/adapt'
2
- import { Theme, useThemeName } from '@tamagui/core'
3
- import { YStack } from '@tamagui/stacks'
1
+ import {
2
+ AdaptContext,
3
+ AdaptPortalContents,
4
+ useAdaptContext,
5
+ useAdaptIsActive,
6
+ } from '@tamagui/adapt'
7
+ import { createStyledHOC, styled, Theme, useThemeName, View } from '@tamagui/core'
4
8
 
5
9
  import { VIEWPORT_NAME } from './constants'
6
10
  import {
@@ -8,33 +12,46 @@ import {
8
12
  useSelectContext,
9
13
  useSelectItemParentContext,
10
14
  } from './context'
11
- import type { SelectViewportProps } from './types'
15
+ import type { SelectViewportExtraProps, SelectViewportProps } from './types'
16
+
17
+ export const SelectViewportFrame = styled(View, {
18
+ displayName: VIEWPORT_NAME,
19
+ position: 'relative',
20
+ })
12
21
 
13
- export const SelectViewport = (props: SelectViewportProps) => {
14
- const { scope, children } = props
15
- const context = useSelectContext(scope)
16
- const itemParentContext = useSelectItemParentContext(scope)
17
- const themeName = useThemeName()
22
+ export const SelectViewport = createStyledHOC(
23
+ SelectViewportFrame,
24
+ function SelectViewport(props: SelectViewportProps, forwardedRef) {
25
+ const { scope, children, disableScroll: _disableScroll, ...viewportProps } = props
26
+ const context = useSelectContext(scope)
27
+ const itemParentContext = useSelectItemParentContext(scope)
28
+ const themeName = useThemeName()
29
+ const adaptContext = useAdaptContext()
30
+ const isAdapted = useAdaptIsActive(context.adaptScope)
18
31
 
19
- // re-propagate context
20
- const adaptContext = useAdaptContext()
21
- const contents = (
22
- <Theme name={themeName}>
23
- <ForwardSelectContext itemContext={itemParentContext} context={context}>
24
- <AdaptContext.Provider {...adaptContext}>{children}</AdaptContext.Provider>
25
- </ForwardSelectContext>
26
- </Theme>
27
- )
32
+ const contents = (
33
+ <Theme name={themeName}>
34
+ <ForwardSelectContext itemContext={itemParentContext} context={context}>
35
+ <AdaptContext.Provider {...adaptContext}>
36
+ <SelectViewportFrame {...viewportProps} ref={forwardedRef}>
37
+ {children}
38
+ </SelectViewportFrame>
39
+ </AdaptContext.Provider>
40
+ </ForwardSelectContext>
41
+ </Theme>
42
+ )
28
43
 
29
- if (!context.open) {
30
- if (context.lazyMount && context.renderValue) {
31
- return null
44
+ if (!context.open) {
45
+ if (context.lazyMount && context.renderValue) return null
46
+ return <SelectViewportFrame display="none">{contents}</SelectViewportFrame>
32
47
  }
33
48
 
34
- return <YStack display="none">{contents}</YStack>
49
+ return isAdapted ? (
50
+ <AdaptPortalContents scope={context.adaptScope}>{contents}</AdaptPortalContents>
51
+ ) : (
52
+ contents
53
+ )
35
54
  }
36
-
37
- return <AdaptPortalContents scope={context.adaptScope}>{contents}</AdaptPortalContents>
38
- }
55
+ )
39
56
 
40
57
  SelectViewport.displayName = VIEWPORT_NAME
@@ -2,9 +2,9 @@ import { AdaptPortalContents, useAdaptIsActive } from '@tamagui/adapt'
2
2
  import { AnimatePresence } from '@tamagui/animate-presence'
3
3
  import { useComposedRefs } from '@tamagui/compose-refs'
4
4
  import { isWeb, useIsomorphicLayoutEffect } from '@tamagui/constants'
5
- import { styled } from '@tamagui/core'
5
+ import { createStyledHOC, styled, View } from '@tamagui/core'
6
6
  import { needsPortalRepropagation } from '@tamagui/portal'
7
- import { ThemeableStack, YStack } from '@tamagui/stacks'
7
+ import { YStack } from '@tamagui/stacks'
8
8
  import { startTransition } from '@tamagui/start-transition'
9
9
  import * as React from 'react'
10
10
  import { VIEWPORT_NAME } from './constants'
@@ -13,51 +13,29 @@ import {
13
13
  useSelectContext,
14
14
  useSelectItemParentContext,
15
15
  } from './context'
16
- import type { SelectViewportExtraProps } from './types'
16
+ import type { SelectViewportProps } from './types'
17
+ import { getSelectListboxProps } from './selectionController'
17
18
 
18
19
  /* -------------------------------------------------------------------------------------------------
19
20
  * SelectViewport
20
21
  * -----------------------------------------------------------------------------------------------*/
21
22
 
22
- // must extend ThemeableStack (not YStack) so the `elevate` and `bordered`
23
- // variants used below resolve via stacks/variants instead of leaking to DOM.
24
- export const SelectViewportFrame = styled(ThemeableStack, {
25
- name: VIEWPORT_NAME,
26
-
27
- variants: {
28
- unstyled: {
29
- false: {
30
- size: '$2',
31
- backgroundColor: '$background',
32
- elevate: true,
33
- bordered: true,
34
- userSelect: 'none',
35
- outlineWidth: 0,
36
- },
37
- },
38
-
39
- size: {
40
- '...size': (val, { tokens }) => {
41
- return {
42
- borderRadius: tokens.radius[val] ?? val,
43
- }
44
- },
45
- },
46
- } as const,
47
-
48
- defaultVariants: {
49
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
50
- },
23
+ export const SelectViewportFrame = styled(View, {
24
+ displayName: VIEWPORT_NAME,
25
+ position: 'relative',
51
26
  })
52
27
 
53
28
  const needsRepropagation = needsPortalRepropagation()
54
29
 
55
- export const SelectViewport = SelectViewportFrame.styleable<SelectViewportExtraProps>(
56
- function SelectViewport(props, forwardedRef) {
30
+ export const SelectViewport = createStyledHOC(
31
+ SelectViewportFrame,
32
+ function SelectViewport(props: SelectViewportProps, forwardedRef) {
57
33
  const { scope, children, disableScroll, ...viewportProps } = props
58
34
  const context = useSelectContext(scope)
59
35
  const itemContext = useSelectItemParentContext(scope)
60
36
  const isAdapted = useAdaptIsActive(context.adaptScope)
37
+ const viewportRef = React.useRef<any>(null)
38
+ const registeredItemCount = itemContext.registry.getItems().length
61
39
 
62
40
  // lazy mount: defer mounting children until first open using startTransition
63
41
  const [lazyMounted, setLazyMounted] = React.useState(context.lazyMount ? false : true)
@@ -71,9 +49,25 @@ export const SelectViewport = SelectViewportFrame.styleable<SelectViewportExtraP
71
49
  })
72
50
  }, [context.lazyMount, context.open, lazyMounted])
73
51
 
52
+ React.useEffect(() => {
53
+ if (!isWeb || !isAdapted || !context.open) return
54
+ const frame = requestAnimationFrame(() => {
55
+ const index =
56
+ context.activeIndexRef.current ?? itemContext.registry.firstEnabledIndex()
57
+ const activeItem =
58
+ (viewportRef.current?.querySelector(
59
+ '[role="option"][tabindex="0"]'
60
+ ) as HTMLElement | null) ??
61
+ (index >= 0 ? itemContext.listRef?.current[index] : null)
62
+ activeItem?.focus()
63
+ })
64
+ return () => cancelAnimationFrame(frame)
65
+ }, [context.activeIndex, context.open, isAdapted, registeredItemCount])
66
+
74
67
  const composedRefs = useComposedRefs(
75
- // @ts-ignore TODO react 19 type needs fix
68
+ // @ts-ignore react 19 ref type mismatch
76
69
  forwardedRef,
70
+ viewportRef,
77
71
  context.floatingContext?.refs.setFloating as any
78
72
  )
79
73
 
@@ -85,18 +79,28 @@ export const SelectViewport = SelectViewportFrame.styleable<SelectViewportExtraP
85
79
 
86
80
  // after lazy children mount, force floating-ui to recompute so inner middleware
87
81
  // can position using the now-present list items
88
- useIsomorphicLayoutEffect(() => {
82
+ React.useEffect(() => {
89
83
  if (context.lazyMount && lazyMounted && context.open && context.update) {
90
- context.update()
84
+ const frame = requestAnimationFrame(context.update)
85
+ return () => cancelAnimationFrame(frame)
91
86
  }
92
- }, [lazyMounted])
87
+ }, [lazyMounted, registeredItemCount])
93
88
 
94
89
  if (itemContext.shouldRenderWebNative) {
95
90
  return <YStack position="relative">{children}</YStack>
96
91
  }
97
92
 
98
93
  if (isAdapted || !isWeb) {
99
- let content = children
94
+ let content = (
95
+ <SelectViewportFrame
96
+ {...viewportProps}
97
+ {...(isWeb ? (getSelectListboxProps(itemContext.mode) as any) : {})}
98
+ data-select-viewport=""
99
+ ref={composedRefs}
100
+ >
101
+ {lazyMounted ? children : null}
102
+ </SelectViewportFrame>
103
+ )
100
104
 
101
105
  if (needsRepropagation) {
102
106
  content = (
@@ -112,10 +116,6 @@ export const SelectViewport = SelectViewportFrame.styleable<SelectViewportExtraP
112
116
  }
113
117
 
114
118
  if (!itemContext.interactions) {
115
- if (process.env.NODE_ENV === 'development') {
116
- console.warn(`No interactions provided to Select, potentially missing Adapt`)
117
- }
118
-
119
119
  return null
120
120
  }
121
121
 
@@ -130,7 +130,7 @@ export const SelectViewport = SelectViewportFrame.styleable<SelectViewportExtraP
130
130
  // that handles focus trapping and auto-focus
131
131
  return (
132
132
  <>
133
- {!disableScroll && !props.unstyled && (
133
+ {!disableScroll && (
134
134
  <style
135
135
  dangerouslySetInnerHTML={{
136
136
  __html: selectViewportCSS,
@@ -141,14 +141,15 @@ export const SelectViewport = SelectViewportFrame.styleable<SelectViewportExtraP
141
141
  {context.open ? (
142
142
  <SelectViewportFrame
143
143
  key="select-viewport"
144
- size={itemContext.size}
145
- role="presentation"
144
+ data-select-viewport=""
145
+ {...(isWeb && {
146
+ 'data-state': context.open ? 'open' : 'closed',
147
+ })}
146
148
  {...viewportProps}
147
149
  {...style}
148
150
  {...floatingProps}
149
- {...(!props.unstyled && {
150
- overflowY: disableScroll ? undefined : (style.overflow ?? 'auto'),
151
- })}
151
+ {...getSelectListboxProps(itemContext.mode)}
152
+ overflowY={disableScroll ? undefined : (style.overflow ?? 'auto')}
152
153
  ref={composedRefs}
153
154
  >
154
155
  {lazyMounted ? children : null}
@@ -167,13 +168,13 @@ export const SelectViewport = SelectViewportFrame.styleable<SelectViewportExtraP
167
168
  )
168
169
 
169
170
  const selectViewportCSS = `
170
- .is_SelectViewport {
171
+ [data-select-viewport] {
171
172
  scrollbar-width: none;
172
173
  -webkit-overflow-scrolling: touch;
173
174
  overscroll-behavior: contain;
174
175
  }
175
176
 
176
- .is_SelectViewport::-webkit-scrollbar{
177
+ [data-select-viewport]::-webkit-scrollbar{
177
178
  display:none
178
179
  }
179
180
  `
package/src/context.tsx CHANGED
@@ -1,11 +1,15 @@
1
1
  import { createStyledContext } from '@tamagui/core'
2
2
  import { getPortal } from '@tamagui/native'
3
- import { createContext } from 'react'
3
+ import { createContext, type ComponentType } from 'react'
4
4
  import type { SelectContextValue, SelectItemParentContextValue } from './types'
5
5
 
6
6
  // zIndex flows from root Select prop to SelectContent portal
7
7
  export const SelectZIndexContext = createContext<number | undefined>(undefined)
8
8
 
9
+ // the behavior surface renders a raw native select. the styled tamagui surface
10
+ // supplies its native-select skin through this slot.
11
+ export const SelectNativeComponentContext = createContext<ComponentType<any> | null>(null)
12
+
9
13
  export const { Provider: SelectProvider, useStyledContext: useSelectContext } =
10
14
  createStyledContext<SelectContextValue>(null as any, 'Select')
11
15
 
@@ -34,7 +38,7 @@ export const ForwardSelectContext = ({
34
38
 
35
39
  // fall back to context forwarding for legacy portal implementations
36
40
  return (
37
- <SelectProvider isInSheet scope={context.scopeName} {...context}>
41
+ <SelectProvider scope={context.scopeName} {...context}>
38
42
  <SelectItemParentProvider scope={context.scopeName} {...itemContext}>
39
43
  {children}
40
44
  </SelectItemParentProvider>
package/src/index.tsx CHANGED
@@ -1,5 +1,8 @@
1
1
  export * from './Select'
2
+ export * from './SelectItem'
3
+ export * from './SelectItemText'
4
+ export * from './SelectScrollButton'
5
+ export * from './SelectTrigger'
6
+ export * from './SelectViewport'
2
7
  export type * from './types'
3
8
  export * from './context'
4
- export type { SelectItemProps, SelectItemExtraProps } from './SelectItem'
5
- export type { SelectTriggerProps } from './SelectTrigger'