@tamagui/select 2.7.7 → 3.0.0-beta.643.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,11 +1,23 @@
1
1
  import { useComposedRefs } from '@tamagui/compose-refs'
2
+ import { useAdaptIsActive } from '@tamagui/adapt'
2
3
  import { isWeb, useIsomorphicLayoutEffect } from '@tamagui/constants'
3
- import { createStyledContext } from '@tamagui/core'
4
- import type { ListItemProps } from '@tamagui/list-item'
5
- import { ListItem } from '@tamagui/list-item'
4
+ import {
5
+ createChangeEventDetails,
6
+ createStyledHOC,
7
+ createStyledContext,
8
+ styled,
9
+ View,
10
+ } from '@tamagui/core'
11
+ import type { GetProps } from '@tamagui/core'
12
+ import { composeEventHandlers } from '@tamagui/helpers'
6
13
  import * as React from 'react'
7
- import { useSelectItemParentContext } from './context'
8
- import type { SelectScopedProps } from './types'
14
+ import { useSelectContext, useSelectItemParentContext } from './context'
15
+ import { getSelectOptionProps } from './selectionController'
16
+ import type {
17
+ SelectActiveChangeDetails,
18
+ SelectScopedProps,
19
+ SelectValueChangeDetails,
20
+ } from './types'
9
21
 
10
22
  /* -------------------------------------------------------------------------------------------------
11
23
  * SelectItem
@@ -16,6 +28,7 @@ const ITEM_NAME = 'SelectItem'
16
28
  type SelectItemContextValue = {
17
29
  value: string
18
30
  textId: string
31
+ textValue?: string
19
32
  isSelected: boolean
20
33
  }
21
34
 
@@ -26,61 +39,119 @@ export const {
26
39
 
27
40
  export interface SelectItemExtraProps {
28
41
  value: string
29
- index: number
30
42
  disabled?: boolean
31
43
  textValue?: string
32
44
  }
33
45
 
34
46
  export interface SelectItemProps
35
- extends Omit<ListItemProps, keyof SelectItemExtraProps>, SelectItemExtraProps {}
47
+ extends
48
+ Omit<GetProps<typeof SelectItemFrame>, keyof SelectItemExtraProps>,
49
+ SelectItemExtraProps {}
50
+
51
+ export const SelectItemFrame = styled(View, {
52
+ displayName: ITEM_NAME,
53
+ alignItems: 'center',
54
+ flexDirection: 'row',
55
+ })
36
56
 
37
- export const SelectItem = ListItem.Frame.styleable<SelectItemExtraProps>(
57
+ export const SelectItem = createStyledHOC(
58
+ SelectItemFrame,
38
59
  function SelectItem(props: SelectScopedProps<SelectItemProps>, forwardedRef) {
39
60
  const {
40
61
  scope,
41
62
  value,
42
- disabled = false,
63
+ disabled: disabledProp,
64
+ 'aria-disabled': ariaDisabled,
43
65
  textValue: textValueProp,
44
- index,
45
66
  ...restProps
46
67
  } = props
68
+ const disabled = disabledProp ?? ariaDisabled === true
47
69
 
48
70
  const context = useSelectItemParentContext(scope)
71
+ const selectContext = useSelectContext(scope)
72
+ const isAdapted = useAdaptIsActive(selectContext.adaptScope)
49
73
 
50
74
  const {
51
- setSelectedIndex,
52
75
  listRef,
53
- setOpen,
54
- onChange,
76
+ registry,
77
+ mode,
78
+ selectedValues,
79
+ selectValue,
55
80
  activeIndexSubscribe,
56
81
  activeIndexRef,
57
- valueSubscribe,
58
82
  allowMouseUpRef,
59
83
  allowSelectRef,
60
- setValueAtIndex,
61
84
  selectTimeoutRef,
62
- dataRef,
63
85
  interactions,
64
86
  shouldRenderWebNative,
65
- size,
66
- onActiveChange,
67
- initialValue,
68
87
  setActiveIndexFast,
88
+ moveActive,
89
+ search,
69
90
  } = context
70
91
 
71
- const [isSelected, setSelected] = React.useState(initialValue === value)
92
+ const isSelected = selectedValues.includes(value)
93
+ const pendingMouseUpSelectionRef = React.useRef(false)
94
+ const itemNodeRef = React.useRef<any>(null)
95
+ const [, rerenderRegistry] = React.useReducer((version) => version + 1, 0)
96
+ const registrationRef = React.useRef<ReturnType<typeof registry.registerItem> | null>(
97
+ null
98
+ )
99
+ const initialRegistration = React.useRef({
100
+ value,
101
+ disabled,
102
+ textValue: textValueProp,
103
+ })
104
+
105
+ useIsomorphicLayoutEffect(
106
+ () => registry.subscribe(() => rerenderRegistry()),
107
+ [registry]
108
+ )
72
109
 
73
- // set initial selectedIndex when this item matches the initial value
74
110
  useIsomorphicLayoutEffect(() => {
75
- if (initialValue === value) {
76
- setSelectedIndex(index)
111
+ const registration = registry.registerItem(initialRegistration.current)
112
+ registrationRef.current = registration
113
+ const registeredNode = itemNodeRef.current
114
+ registration.setNode(registeredNode)
115
+ const registeredIndex = registry
116
+ .getItems()
117
+ .findIndex((item) => item.id === registration.id)
118
+ if (
119
+ isWeb &&
120
+ listRef &&
121
+ registeredIndex >= 0 &&
122
+ registeredNode instanceof HTMLElement
123
+ ) {
124
+ listRef.current[registeredIndex] = registeredNode
125
+ }
126
+ return () => {
127
+ const currentIndex = registry
128
+ .getItems()
129
+ .findIndex((item) => item.id === registration.id)
130
+ if (
131
+ listRef &&
132
+ currentIndex >= 0 &&
133
+ listRef.current[currentIndex] === registeredNode
134
+ ) {
135
+ listRef.current[currentIndex] = null
136
+ }
137
+ registrationRef.current = null
138
+ registration.unregister()
77
139
  }
78
- }, [])
140
+ }, [registry])
141
+
142
+ useIsomorphicLayoutEffect(() => {
143
+ registrationRef.current?.update({ value, disabled, textValue: textValueProp })
144
+ }, [disabled, textValueProp, value])
145
+
146
+ useIsomorphicLayoutEffect(() => {
147
+ registrationRef.current?.setNode(itemNodeRef.current)
148
+ })
149
+
150
+ const index = registry.getIndex(value)
79
151
 
80
152
  React.useEffect(() => {
81
153
  const handleActiveIndex = (i: number) => {
82
154
  if (index === i) {
83
- onActiveChange(value, index)
84
155
  if (isWeb) {
85
156
  // use rAF to focus after browser's click handling completes
86
157
  // this prevents the trigger from stealing focus after we set it
@@ -98,23 +169,17 @@ export const SelectItem = ListItem.Frame.styleable<SelectItemExtraProps>(
98
169
  }
99
170
 
100
171
  return activeIndexSubscribe(handleActiveIndex)
101
- }, [index])
102
-
103
- React.useEffect(() => {
104
- return valueSubscribe((val) => {
105
- setSelected(val === value)
106
- })
107
- }, [value])
172
+ }, [activeIndexRef, activeIndexSubscribe, index, listRef])
108
173
 
109
174
  const textId = React.useId()
110
175
 
111
176
  const refCallback = React.useCallback(
112
177
  (node) => {
178
+ itemNodeRef.current = node
179
+ registrationRef.current?.setNode(node)
113
180
  if (!isWeb) return
114
- if (node instanceof HTMLElement) {
115
- if (listRef) {
116
- listRef.current[index] = node
117
- }
181
+ if (listRef && index >= 0) {
182
+ listRef.current[index] = node instanceof HTMLElement ? node : null
118
183
  }
119
184
  },
120
185
  [index, listRef]
@@ -122,132 +187,213 @@ export const SelectItem = ListItem.Frame.styleable<SelectItemExtraProps>(
122
187
 
123
188
  const composedRefs = useComposedRefs(forwardedRef, refCallback)
124
189
 
125
- useIsomorphicLayoutEffect(() => {
126
- setValueAtIndex(index, value)
127
- }, [index, setValueAtIndex, value])
190
+ const handleSelect = React.useCallback(
191
+ (event?: any, reason: 'item-press' | 'keyboard' = 'item-press') => {
192
+ if (disabled) return
193
+ const nativeEvent = event?.nativeEvent || event
194
+ selectValue(
195
+ value,
196
+ createChangeEventDetails(
197
+ reason,
198
+ nativeEvent,
199
+ event?.currentTarget
200
+ ) as SelectValueChangeDetails
201
+ )
202
+ },
203
+ [disabled, selectValue, value]
204
+ )
128
205
 
129
- function handleSelect() {
130
- setSelectedIndex(index)
131
- onChange(value)
132
- setOpen(false)
133
- }
206
+ const handleKeyDown = React.useCallback(
207
+ (event: any) => {
208
+ if (disabled) return
209
+ if (event.key === 'Enter' || event.key === ' ') {
210
+ event.preventDefault()
211
+ handleSelect(event, 'keyboard')
212
+ return
213
+ }
214
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
215
+ event.preventDefault()
216
+ event.stopPropagation()
217
+ moveActive(event.key === 'ArrowDown' ? 1 : -1, event.nativeEvent || event)
218
+ return
219
+ }
220
+ if (
221
+ !interactions &&
222
+ event.key?.length === 1 &&
223
+ !event.metaKey &&
224
+ !event.ctrlKey
225
+ ) {
226
+ search(event.key, event.nativeEvent || event)
227
+ }
228
+ if (allowSelectRef) {
229
+ allowSelectRef.current = true
230
+ }
231
+ },
232
+ [allowSelectRef, disabled, handleSelect, interactions, moveActive, search]
233
+ )
134
234
 
135
235
  const selectItemProps = React.useMemo(() => {
136
- return interactions
137
- ? interactions.getItemProps({
138
- onTouchMove() {
139
- allowSelectRef!.current = true
140
- allowMouseUpRef!.current = false
141
- },
142
- onTouchEnd() {
143
- allowSelectRef!.current = false
236
+ if (interactions) {
237
+ const {
238
+ onTouchMove,
239
+ onTouchEnd,
240
+ onKeyDown,
241
+ onClick,
242
+ onMouseUp,
243
+ onMouseMove,
244
+ onPress,
245
+ ...itemProps
246
+ } = restProps
247
+ const interactionProps = interactions.getItemProps({
248
+ ...itemProps,
249
+ onTouchMove() {
250
+ allowSelectRef!.current = true
251
+ allowMouseUpRef!.current = false
252
+ },
253
+ onTouchEnd() {
254
+ allowSelectRef!.current = false
255
+ allowMouseUpRef!.current = true
256
+ },
257
+ onMouseMove(event) {
258
+ if (disabled || index < 0) return
259
+ setActiveIndexFast?.(index, {
260
+ reason: 'item-hover',
261
+ event: event.nativeEvent || event,
262
+ trigger: event.currentTarget,
263
+ index,
264
+ } as SelectActiveChangeDetails)
265
+ },
266
+ onKeyDown: handleKeyDown,
267
+ onClick(event) {
268
+ if (disabled) return
269
+ const shouldSelect =
270
+ pendingMouseUpSelectionRef.current || allowSelectRef!.current
271
+ pendingMouseUpSelectionRef.current = false
272
+ clearTimeout(selectTimeoutRef!.current)
273
+ allowSelectRef!.current = true
274
+ if (shouldSelect) {
275
+ handleSelect(event)
276
+ }
277
+ },
278
+ onMouseUp(event) {
279
+ if (disabled) return
280
+ if (!allowMouseUpRef!.current) {
281
+ // Re-enable mouseup and selection for subsequent interactions
144
282
  allowMouseUpRef!.current = true
145
- },
146
- onKeyDown(event) {
147
- if (
148
- event.key === 'Enter' ||
149
- (event.key === ' ' && !dataRef?.current.typing)
150
- ) {
151
- event.preventDefault()
152
- handleSelect()
153
- } else if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
154
- // prevent default and stop propagation so floating-ui doesn't also handle
155
- event.preventDefault()
156
- event.stopPropagation()
157
- const itemCount = listRef?.current.length ?? 0
158
- if (itemCount === 0) return
283
+ allowSelectRef!.current = true
284
+ return
285
+ }
159
286
 
160
- let nextIndex: number
161
- if (event.key === 'ArrowDown') {
162
- nextIndex = index + 1 >= itemCount ? 0 : index + 1
163
- } else {
164
- nextIndex = index - 1 < 0 ? itemCount - 1 : index - 1
165
- }
166
- // use fast setter to avoid triggering state updates that reset activeIndex
167
- setActiveIndexFast?.(nextIndex)
168
- } else {
169
- allowSelectRef!.current = true
170
- }
171
- },
287
+ pendingMouseUpSelectionRef.current = allowSelectRef!.current
172
288
 
173
- onClick() {
174
- if (allowSelectRef!.current) {
175
- handleSelect()
289
+ // A normal click follows mouseup synchronously. Defer the drag-release
290
+ // fallback so the caller's onClick runs before selection closes the item.
291
+ clearTimeout(selectTimeoutRef!.current)
292
+ selectTimeoutRef!.current = setTimeout(() => {
293
+ allowSelectRef!.current = true
294
+ if (pendingMouseUpSelectionRef.current) {
295
+ pendingMouseUpSelectionRef.current = false
296
+ handleSelect(event)
176
297
  }
177
- },
298
+ })
299
+ },
300
+ } as any)
178
301
 
179
- onMouseUp() {
180
- if (!allowMouseUpRef!.current) {
181
- // Re-enable mouseup and selection for subsequent interactions
182
- allowMouseUpRef!.current = true
183
- allowSelectRef!.current = true
184
- return
185
- }
302
+ return {
303
+ ...interactionProps,
304
+ onTouchMove: composeEventHandlers(
305
+ onTouchMove as any,
306
+ interactionProps.onTouchMove
307
+ ),
308
+ onTouchEnd: composeEventHandlers(
309
+ onTouchEnd as any,
310
+ interactionProps.onTouchEnd
311
+ ),
312
+ onKeyDown: composeEventHandlers(onKeyDown as any, interactionProps.onKeyDown),
313
+ onClick(event: any) {
314
+ onClick?.(event)
315
+ if (event.defaultPrevented) {
316
+ pendingMouseUpSelectionRef.current = false
317
+ clearTimeout(selectTimeoutRef!.current)
318
+ allowSelectRef!.current = true
319
+ return
320
+ }
321
+ interactionProps.onClick?.(event)
322
+ },
323
+ onMouseUp: composeEventHandlers(onMouseUp as any, interactionProps.onMouseUp),
324
+ onMouseMove: composeEventHandlers(
325
+ onMouseMove as any,
326
+ interactionProps.onMouseMove
327
+ ),
328
+ onPress,
329
+ }
330
+ }
186
331
 
187
- if (allowSelectRef!.current) {
188
- handleSelect()
189
- }
332
+ return {
333
+ ...restProps,
334
+ onKeyDown: composeEventHandlers(restProps.onKeyDown as any, handleKeyDown),
335
+ onMouseMove: composeEventHandlers(restProps.onMouseMove as any, (event: any) => {
336
+ if (disabled || index < 0) return
337
+ setActiveIndexFast?.(index, {
338
+ reason: 'item-hover',
339
+ event: event.nativeEvent || event,
340
+ trigger: event.currentTarget,
341
+ index,
342
+ } as SelectActiveChangeDetails)
343
+ }),
344
+ onPress: composeEventHandlers(restProps.onPress as any, handleSelect),
345
+ }
346
+ }, [
347
+ allowMouseUpRef,
348
+ allowSelectRef,
349
+ disabled,
350
+ handleKeyDown,
351
+ handleSelect,
352
+ index,
353
+ interactions,
354
+ restProps,
355
+ selectTimeoutRef,
356
+ setActiveIndexFast,
357
+ ])
190
358
 
191
- // On touch devices, prevent the element from
192
- // immediately closing `onClick` by deferring it
193
- clearTimeout(selectTimeoutRef!.current)
194
- selectTimeoutRef!.current = setTimeout(() => {
195
- allowSelectRef!.current = true
196
- })
197
- },
198
- })
199
- : {
200
- onPress: handleSelect,
201
- }
202
- }, [handleSelect, index, listRef, setActiveIndexFast])
359
+ const accessibilityProps = getSelectOptionProps(
360
+ mode,
361
+ isSelected,
362
+ disabled,
363
+ isWeb ? 'web' : 'native'
364
+ )
203
365
 
204
366
  return (
205
367
  <SelectItemContextProvider
206
368
  scope={scope}
207
369
  value={value}
208
370
  textId={textId || ''}
371
+ textValue={textValueProp}
209
372
  isSelected={isSelected}
210
373
  >
211
374
  {shouldRenderWebNative ? (
212
- <option value={value}>{props.children}</option>
375
+ <option value={value} disabled={disabled}>
376
+ {props.children}
377
+ </option>
213
378
  ) : (
214
- <ListItem.Frame
379
+ <SelectItemFrame
215
380
  render="div"
216
- componentName={ITEM_NAME}
217
381
  ref={composedRefs}
218
- role="option"
219
382
  aria-labelledby={textId}
220
- aria-selected={isSelected}
221
383
  data-state={isSelected ? 'active' : 'inactive'}
222
- aria-disabled={disabled || undefined}
223
384
  data-disabled={disabled ? '' : undefined}
224
- tabIndex={disabled ? undefined : -1}
225
- {...(!props.unstyled && {
226
- cursor: 'default',
227
- size,
228
- outlineOffset: -0.5,
229
- zIndex: 100,
230
-
231
- hoverStyle: {
232
- backgroundColor: '$backgroundHover',
233
- },
234
-
235
- pressStyle: {
236
- backgroundColor: '$backgroundPress',
237
- },
238
-
239
- focusStyle: {
240
- backgroundColor: '$backgroundFocus',
241
- },
242
-
243
- focusVisibleStyle: {
244
- outlineColor: '$outlineColor',
245
- outlineWidth: 1,
246
- outlineStyle: 'solid',
247
- },
248
- })}
249
- {...restProps}
385
+ tabIndex={
386
+ disabled
387
+ ? undefined
388
+ : isWeb && isAdapted
389
+ ? index === (selectContext.activeIndex ?? selectContext.selectedIndex)
390
+ ? 0
391
+ : -1
392
+ : -1
393
+ }
394
+ zIndex={100}
250
395
  {...selectItemProps}
396
+ {...accessibilityProps}
251
397
  />
252
398
  )}
253
399
  </SelectItemContextProvider>
@@ -1,10 +1,10 @@
1
1
  import { useComposedRefs } from '@tamagui/compose-refs'
2
2
  import type { GetProps, TamaguiTextElement } from '@tamagui/core'
3
- import { styled, useIsomorphicLayoutEffect } from '@tamagui/core'
4
- import { SizableText } from '@tamagui/text'
3
+ import { createStyledHOC, styled, Text, useIsomorphicLayoutEffect } from '@tamagui/core'
5
4
  import * as React from 'react'
6
5
 
7
6
  import { useSelectItemParentContext } from './context'
7
+ import { getSelectLabelText } from './selectionController'
8
8
  import { useSelectItemContext } from './SelectItem'
9
9
  import type { SelectScopedProps } from './types'
10
10
 
@@ -14,30 +14,17 @@ import type { SelectScopedProps } from './types'
14
14
 
15
15
  export const ITEM_TEXT_NAME = 'SelectItemText'
16
16
 
17
- export const SelectItemTextFrame = styled(SizableText, {
18
- name: ITEM_TEXT_NAME,
19
-
20
- variants: {
21
- unstyled: {
22
- false: {
23
- userSelect: 'none',
24
- color: '$color',
25
- ellipsis: true,
26
- },
27
- },
28
- } as const,
29
-
30
- defaultVariants: {
31
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
32
- },
17
+ export const SelectItemTextFrame = styled(Text, {
18
+ displayName: ITEM_TEXT_NAME,
33
19
  })
34
20
 
35
21
  type SelectItemTextExtraProps = SelectScopedProps<{}>
36
22
  export type SelectItemTextProps = GetProps<typeof SelectItemTextFrame> &
37
23
  SelectItemTextExtraProps
38
24
 
39
- export const SelectItemText = SelectItemTextFrame.styleable<SelectItemTextExtraProps>(
40
- function SelectItemText(props, forwardedRef) {
25
+ export const SelectItemText = createStyledHOC(
26
+ SelectItemTextFrame,
27
+ function SelectItemText(props: SelectItemTextProps, forwardedRef) {
41
28
  const { scope, className, ...itemTextProps } = props
42
29
  // note: only uses itemParentContext (not selectContext) to avoid re-renders
43
30
  // when activeIndex changes on hover
@@ -46,13 +33,15 @@ export const SelectItemText = SelectItemTextFrame.styleable<SelectItemTextExtraP
46
33
  const composedRefs = useComposedRefs(forwardedRef, ref)
47
34
  const itemContext = useSelectItemContext(scope)
48
35
  const contents = React.useRef<React.ReactNode>(null)
36
+ const label = React.useRef(props.children)
37
+ label.current = props.children
38
+ const labelText = getSelectLabelText(props.children)
49
39
 
50
40
  // we portal this to the selected area, which is fine to be a bit unsafe concurrently (mostly? its not changing often)...
51
41
  // until react native supports portals this is best i think
52
42
  contents.current = (
53
43
  <SelectItemTextFrame
54
44
  className={className}
55
- size={itemParentContext.size as any}
56
45
  id={itemContext.textId}
57
46
  {...itemTextProps}
58
47
  ref={composedRefs}
@@ -60,18 +49,12 @@ export const SelectItemText = SelectItemTextFrame.styleable<SelectItemTextExtraP
60
49
  )
61
50
 
62
51
  useIsomorphicLayoutEffect(() => {
63
- if (itemParentContext.initialValue === itemContext.value) {
64
- itemParentContext.setSelectedItem(contents.current)
65
- }
66
- }, [])
67
-
68
- useIsomorphicLayoutEffect(() => {
69
- return itemParentContext.valueSubscribe((val) => {
70
- if (val === itemContext.value) {
71
- itemParentContext.setSelectedItem(contents.current)
72
- }
73
- })
74
- }, [itemContext.value])
52
+ return itemParentContext.registry.registerLabel(
53
+ itemContext.value,
54
+ label.current,
55
+ itemContext.textValue
56
+ )
57
+ }, [itemContext.textValue, itemContext.value, itemParentContext.registry, labelText])
75
58
 
76
59
  if (itemParentContext.shouldRenderWebNative) {
77
60
  return <>{props.children}</>
@@ -1,7 +1,16 @@
1
+ import { createStyledHOC, styled, View } from '@tamagui/core'
1
2
  import type { SelectScopedProps, SelectScrollButtonProps } from './types'
2
3
 
3
- export const SelectScrollUpButton = (_: SelectScopedProps<SelectScrollButtonProps>) =>
4
- null
4
+ export const SelectScrollButtonFrame = styled(View, {
5
+ displayName: 'SelectScrollButton',
6
+ })
5
7
 
6
- export const SelectScrollDownButton = (_: SelectScopedProps<SelectScrollButtonProps>) =>
7
- null
8
+ export const SelectScrollUpButton = createStyledHOC(
9
+ SelectScrollButtonFrame,
10
+ (_props: SelectScopedProps<SelectScrollButtonProps>) => null
11
+ )
12
+
13
+ export const SelectScrollDownButton = createStyledHOC(
14
+ SelectScrollButtonFrame,
15
+ (_props: SelectScopedProps<SelectScrollButtonProps>) => null
16
+ )