@tamagui/select 2.7.7 → 3.0.0-beta.1091.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 +406 -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 +79 -79
  6. package/dist/cjs/SelectContent.native.cjs +29 -0
  7. package/dist/cjs/SelectContent.native.js +17 -22
  8. package/dist/cjs/SelectContent.native.js.map +1 -1
  9. package/dist/cjs/SelectImpl.cjs +305 -320
  10. package/dist/cjs/SelectImpl.native.cjs +29 -0
  11. package/dist/cjs/SelectImpl.native.js +16 -24
  12. package/dist/cjs/SelectImpl.native.js.map +1 -1
  13. package/dist/cjs/SelectItem.cjs +256 -205
  14. package/dist/cjs/SelectItem.native.cjs +288 -0
  15. package/dist/cjs/SelectItem.native.js +264 -213
  16. package/dist/cjs/SelectItem.native.js.map +1 -1
  17. package/dist/cjs/SelectItemText.cjs +48 -79
  18. package/dist/cjs/SelectItemText.native.cjs +73 -0
  19. package/dist/cjs/SelectItemText.native.js +49 -79
  20. package/dist/cjs/SelectItemText.native.js.map +1 -1
  21. package/dist/cjs/SelectScrollButton.cjs +110 -130
  22. package/dist/cjs/SelectScrollButton.native.cjs +37 -0
  23. package/dist/cjs/SelectScrollButton.native.js +24 -22
  24. package/dist/cjs/SelectScrollButton.native.js.map +1 -1
  25. package/dist/cjs/SelectTrigger.cjs +68 -84
  26. package/dist/cjs/SelectTrigger.native.cjs +90 -0
  27. package/dist/cjs/SelectTrigger.native.js +70 -66
  28. package/dist/cjs/SelectTrigger.native.js.map +1 -1
  29. package/dist/cjs/SelectViewport.cjs +121 -142
  30. package/dist/cjs/SelectViewport.native.cjs +72 -0
  31. package/dist/cjs/SelectViewport.native.js +53 -50
  32. package/dist/cjs/SelectViewport.native.js.map +1 -1
  33. package/dist/cjs/constants.cjs +20 -22
  34. package/dist/cjs/constants.native.cjs +40 -0
  35. package/dist/cjs/constants.native.js +20 -21
  36. package/dist/cjs/constants.native.js.map +1 -1
  37. package/dist/cjs/context.cjs +37 -48
  38. package/dist/cjs/context.native.cjs +57 -0
  39. package/dist/cjs/context.native.js +39 -49
  40. package/dist/cjs/context.native.js.map +1 -1
  41. package/dist/cjs/index.cjs +14 -11
  42. package/dist/cjs/index.native.cjs +26 -0
  43. package/dist/cjs/index.native.js +14 -10
  44. package/dist/cjs/index.native.js.map +1 -1
  45. package/dist/cjs/selectionController.cjs +343 -0
  46. package/dist/cjs/selectionController.native.cjs +407 -0
  47. package/dist/cjs/selectionController.native.js +409 -0
  48. package/dist/cjs/selectionController.native.js.map +1 -0
  49. package/dist/cjs/types.cjs +9 -11
  50. package/dist/cjs/types.native.cjs +18 -0
  51. package/dist/cjs/types.native.js +9 -10
  52. package/dist/cjs/types.native.js.map +1 -1
  53. package/dist/esm/Select.mjs +381 -443
  54. package/dist/esm/Select.mjs.map +1 -1
  55. package/dist/esm/Select.native.js +420 -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 +291 -295
  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 +240 -181
  66. package/dist/esm/SelectItem.mjs.map +1 -1
  67. package/dist/esm/SelectItem.native.js +249 -191
  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 +107 -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 +315 -0
  97. package/dist/esm/selectionController.mjs.map +1 -0
  98. package/dist/esm/selectionController.native.js +377 -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 +381 -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 +29 -0
  110. package/dist/jsx/SelectContent.native.js +17 -22
  111. package/dist/jsx/SelectContent.native.js.map +1 -1
  112. package/dist/jsx/SelectImpl.mjs +291 -295
  113. package/dist/jsx/SelectImpl.mjs.map +1 -1
  114. package/dist/jsx/SelectImpl.native.cjs +29 -0
  115. package/dist/jsx/SelectImpl.native.js +16 -24
  116. package/dist/jsx/SelectImpl.native.js.map +1 -1
  117. package/dist/jsx/SelectItem.mjs +240 -181
  118. package/dist/jsx/SelectItem.mjs.map +1 -1
  119. package/dist/jsx/SelectItem.native.cjs +288 -0
  120. package/dist/jsx/SelectItem.native.js +264 -213
  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 +73 -0
  125. package/dist/jsx/SelectItemText.native.js +49 -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 +37 -0
  130. package/dist/jsx/SelectScrollButton.native.js +24 -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 +90 -0
  135. package/dist/jsx/SelectTrigger.native.js +70 -66
  136. package/dist/jsx/SelectTrigger.native.js.map +1 -1
  137. package/dist/jsx/SelectViewport.mjs +107 -119
  138. package/dist/jsx/SelectViewport.mjs.map +1 -1
  139. package/dist/jsx/SelectViewport.native.cjs +72 -0
  140. package/dist/jsx/SelectViewport.native.js +53 -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 +40 -0
  145. package/dist/jsx/constants.native.js +20 -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 +57 -0
  150. package/dist/jsx/context.native.js +39 -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 +26 -0
  155. package/dist/jsx/index.native.js +14 -10
  156. package/dist/jsx/index.native.js.map +1 -1
  157. package/dist/jsx/selectionController.mjs +315 -0
  158. package/dist/jsx/selectionController.mjs.map +1 -0
  159. package/dist/jsx/selectionController.native.cjs +407 -0
  160. package/dist/jsx/selectionController.native.js +409 -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 +18 -0
  164. package/dist/jsx/types.native.js +9 -10
  165. package/dist/jsx/types.native.js.map +1 -1
  166. package/package.json +31 -30
  167. package/src/Select.tsx +478 -378
  168. package/src/SelectContent.tsx +46 -16
  169. package/src/SelectImpl.native.tsx +0 -8
  170. package/src/SelectImpl.tsx +149 -160
  171. package/src/SelectItem.tsx +280 -159
  172. package/src/SelectItemText.tsx +16 -33
  173. package/src/SelectScrollButton.native.tsx +13 -4
  174. package/src/SelectScrollButton.tsx +57 -42
  175. package/src/SelectTrigger.tsx +77 -53
  176. package/src/SelectViewport.native.tsx +42 -25
  177. package/src/SelectViewport.tsx +60 -60
  178. package/src/context.tsx +6 -2
  179. package/src/index.tsx +5 -2
  180. package/src/selectionController.ts +404 -0
  181. package/src/types.tsx +105 -59
  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 +90 -0
  210. package/types/selectionController.d.ts.map +1 -0
  211. package/types/types.d.ts +66 -50
  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
14
  import { useSelectItemParentContext } from './context'
8
- import type { SelectScopedProps } from './types'
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,228 +39,336 @@ 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
 
70
+ // items read only the item parent context: the select context carries
71
+ // viewport state (position, scroll arrows) that must not re-render the list
48
72
  const context = useSelectItemParentContext(scope)
73
+ const isAdapted = useAdaptIsActive(context.adaptScope)
49
74
 
50
75
  const {
51
- setSelectedIndex,
52
- 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
- interactions,
85
+ getItemProps,
64
86
  shouldRenderWebNative,
65
- size,
66
- onActiveChange,
67
- initialValue,
68
- setActiveIndexFast,
87
+ setActiveIndex,
88
+ lastPointerRef,
89
+ moveActive,
90
+ search,
69
91
  } = context
70
92
 
71
- const [isSelected, setSelected] = React.useState(initialValue === value)
93
+ const isSelected = selectedValues.includes(value)
94
+ const pendingMouseUpSelectionRef = React.useRef(false)
95
+ const itemNodeRef = React.useRef<any>(null)
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
+ })
72
104
 
73
- // set initial selectedIndex when this item matches the initial value
105
+ // the item never subscribes to the registry: its index is read at event
106
+ // time, and the owner mirrors the registry's nodes into the list ref
74
107
  useIsomorphicLayoutEffect(() => {
75
- if (initialValue === value) {
76
- setSelectedIndex(index)
108
+ const registration = registry.registerItem(initialRegistration.current)
109
+ registrationRef.current = registration
110
+ registration.setNode(itemNodeRef.current)
111
+ return () => {
112
+ registrationRef.current = null
113
+ registration.unregister()
77
114
  }
78
- }, [])
115
+ }, [registry])
116
+
117
+ useIsomorphicLayoutEffect(() => {
118
+ registrationRef.current?.update({ value, disabled, textValue: textValueProp })
119
+ }, [disabled, textValueProp, value])
120
+
121
+ useIsomorphicLayoutEffect(() => {
122
+ registrationRef.current?.setNode(itemNodeRef.current)
123
+ })
124
+
125
+ // the only per-item state: this item is the active one. the active index
126
+ // itself is a ref plus an emitter, so a hover re-renders two items, not the list
127
+ const [isActive, setIsActive] = React.useState(false)
128
+ // every item hears every change; only the two whose state flips may set
129
+ // it, since a same-value set still renders once when updates are queued
130
+ const isActiveRef = React.useRef(false)
79
131
 
80
132
  React.useEffect(() => {
81
- const handleActiveIndex = (i: number) => {
82
- if (index === i) {
83
- onActiveChange(value, index)
84
- if (isWeb) {
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
- })
90
- }
133
+ const handleActiveIndex = (i: number | null) => {
134
+ const next = i != null && registry.getIndex(value) === i
135
+ if (next !== isActiveRef.current) {
136
+ isActiveRef.current = next
137
+ setIsActive(next)
138
+ }
139
+ if (next && isWeb) {
140
+ // use rAF to focus after browser's click handling completes
141
+ // this prevents the trigger from stealing focus after we set it
142
+ requestAnimationFrame(() => {
143
+ itemNodeRef.current?.focus?.()
144
+ })
91
145
  }
92
146
  }
93
147
 
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
- }
148
+ // the parent effect may have set it before we subscribed
149
+ handleActiveIndex(activeIndexRef?.current ?? null)
99
150
 
100
151
  return activeIndexSubscribe(handleActiveIndex)
101
- }, [index])
102
-
103
- React.useEffect(() => {
104
- return valueSubscribe((val) => {
105
- setSelected(val === value)
106
- })
107
- }, [value])
152
+ }, [activeIndexRef, activeIndexSubscribe, registry, value])
108
153
 
109
154
  const textId = React.useId()
110
155
 
111
- const refCallback = React.useCallback(
112
- (node) => {
113
- if (!isWeb) return
114
- if (node instanceof HTMLElement) {
115
- if (listRef) {
116
- listRef.current[index] = node
117
- }
118
- }
119
- },
120
- [index, listRef]
121
- )
156
+ const refCallback = React.useCallback((node) => {
157
+ itemNodeRef.current = node
158
+ registrationRef.current?.setNode(node)
159
+ }, [])
122
160
 
123
161
  const composedRefs = useComposedRefs(forwardedRef, refCallback)
124
162
 
125
- useIsomorphicLayoutEffect(() => {
126
- setValueAtIndex(index, value)
127
- }, [index, setValueAtIndex, value])
163
+ const handleSelect = React.useCallback(
164
+ (event?: any, reason: 'item-press' | 'keyboard' = 'item-press') => {
165
+ if (disabled) return
166
+ const nativeEvent = event?.nativeEvent || event
167
+ selectValue(
168
+ value,
169
+ createChangeEventDetails(
170
+ reason,
171
+ nativeEvent,
172
+ event?.currentTarget
173
+ ) as SelectValueChangeDetails
174
+ )
175
+ },
176
+ [disabled, selectValue, value]
177
+ )
128
178
 
129
- function handleSelect() {
130
- setSelectedIndex(index)
131
- onChange(value)
132
- setOpen(false)
133
- }
179
+ const handleMouseMove = React.useCallback(
180
+ (event: any) => {
181
+ if (disabled) return
182
+ const index = registry.getIndex(value)
183
+ if (index < 0) return
184
+ const last = lastPointerRef.current
185
+ if (event.clientX === last.x && event.clientY === last.y) return
186
+ last.x = event.clientX
187
+ last.y = event.clientY
188
+ setActiveIndex(index, {
189
+ reason: 'item-hover',
190
+ event: event.nativeEvent || event,
191
+ trigger: event.currentTarget,
192
+ index,
193
+ } as SelectActiveChangeDetails)
194
+ },
195
+ [disabled, lastPointerRef, registry, setActiveIndex, value]
196
+ )
197
+
198
+ const handleKeyDown = React.useCallback(
199
+ (event: any) => {
200
+ if (disabled) return
201
+ if (event.key === 'Enter' || event.key === ' ') {
202
+ event.preventDefault()
203
+ handleSelect(event, 'keyboard')
204
+ return
205
+ }
206
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
207
+ event.preventDefault()
208
+ event.stopPropagation()
209
+ moveActive(event.key === 'ArrowDown' ? 1 : -1, event.nativeEvent || event)
210
+ return
211
+ }
212
+ if (
213
+ !getItemProps &&
214
+ event.key?.length === 1 &&
215
+ !event.metaKey &&
216
+ !event.ctrlKey
217
+ ) {
218
+ search(event.key, event.nativeEvent || event)
219
+ }
220
+ if (allowSelectRef) {
221
+ allowSelectRef.current = true
222
+ }
223
+ },
224
+ [allowSelectRef, disabled, handleSelect, getItemProps, moveActive, search]
225
+ )
134
226
 
135
227
  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
228
+ if (getItemProps) {
229
+ const {
230
+ onTouchMove,
231
+ onTouchEnd,
232
+ onKeyDown,
233
+ onClick,
234
+ onMouseUp,
235
+ onMouseMove,
236
+ onPress,
237
+ ...itemProps
238
+ } = restProps
239
+ const interactionProps = getItemProps({
240
+ ...itemProps,
241
+ onTouchMove() {
242
+ allowSelectRef!.current = true
243
+ allowMouseUpRef!.current = false
244
+ },
245
+ onTouchEnd() {
246
+ allowSelectRef!.current = false
247
+ allowMouseUpRef!.current = true
248
+ },
249
+ onMouseMove: handleMouseMove,
250
+ onKeyDown: handleKeyDown,
251
+ onClick(event) {
252
+ if (disabled) return
253
+ const shouldSelect =
254
+ pendingMouseUpSelectionRef.current || allowSelectRef!.current
255
+ pendingMouseUpSelectionRef.current = false
256
+ clearTimeout(selectTimeoutRef!.current)
257
+ allowSelectRef!.current = true
258
+ if (shouldSelect) {
259
+ handleSelect(event)
260
+ }
261
+ },
262
+ onMouseUp(event) {
263
+ if (disabled) return
264
+ if (!allowMouseUpRef!.current) {
265
+ // Re-enable mouseup and selection for subsequent interactions
144
266
  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
267
+ allowSelectRef!.current = true
268
+ return
269
+ }
159
270
 
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
- },
271
+ pendingMouseUpSelectionRef.current = allowSelectRef!.current
172
272
 
173
- onClick() {
174
- if (allowSelectRef!.current) {
175
- handleSelect()
273
+ // A normal click follows mouseup synchronously. Defer the drag-release
274
+ // fallback so the caller's onClick runs before selection closes the item.
275
+ clearTimeout(selectTimeoutRef!.current)
276
+ selectTimeoutRef!.current = setTimeout(() => {
277
+ allowSelectRef!.current = true
278
+ if (pendingMouseUpSelectionRef.current) {
279
+ pendingMouseUpSelectionRef.current = false
280
+ handleSelect(event)
176
281
  }
177
- },
282
+ })
283
+ },
284
+ } as any)
178
285
 
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
- }
286
+ return {
287
+ ...interactionProps,
288
+ onTouchMove: composeEventHandlers(
289
+ onTouchMove as any,
290
+ interactionProps.onTouchMove
291
+ ),
292
+ onTouchEnd: composeEventHandlers(
293
+ onTouchEnd as any,
294
+ interactionProps.onTouchEnd
295
+ ),
296
+ onKeyDown: composeEventHandlers(onKeyDown as any, interactionProps.onKeyDown),
297
+ onClick(event: any) {
298
+ onClick?.(event)
299
+ if (event.defaultPrevented) {
300
+ pendingMouseUpSelectionRef.current = false
301
+ clearTimeout(selectTimeoutRef!.current)
302
+ allowSelectRef!.current = true
303
+ return
304
+ }
305
+ interactionProps.onClick?.(event)
306
+ },
307
+ onMouseUp: composeEventHandlers(onMouseUp as any, interactionProps.onMouseUp),
308
+ onMouseMove: composeEventHandlers(
309
+ onMouseMove as any,
310
+ interactionProps.onMouseMove
311
+ ),
312
+ onPress,
313
+ }
314
+ }
186
315
 
187
- if (allowSelectRef!.current) {
188
- handleSelect()
189
- }
316
+ return {
317
+ ...restProps,
318
+ onKeyDown: composeEventHandlers(restProps.onKeyDown as any, handleKeyDown),
319
+ onMouseMove: composeEventHandlers(restProps.onMouseMove as any, handleMouseMove),
320
+ onPress: composeEventHandlers(restProps.onPress as any, handleSelect),
321
+ }
322
+ }, [
323
+ allowMouseUpRef,
324
+ allowSelectRef,
325
+ disabled,
326
+ handleKeyDown,
327
+ handleMouseMove,
328
+ handleSelect,
329
+ getItemProps,
330
+ restProps,
331
+ selectTimeoutRef,
332
+ ])
190
333
 
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])
334
+ const accessibilityProps = getSelectOptionProps(
335
+ mode,
336
+ isSelected,
337
+ disabled,
338
+ isWeb ? 'web' : 'native'
339
+ )
203
340
 
204
341
  return (
205
342
  <SelectItemContextProvider
206
343
  scope={scope}
207
344
  value={value}
208
345
  textId={textId || ''}
346
+ textValue={textValueProp}
209
347
  isSelected={isSelected}
210
348
  >
211
349
  {shouldRenderWebNative ? (
212
- <option value={value}>{props.children}</option>
350
+ <option value={value} disabled={disabled}>
351
+ {props.children}
352
+ </option>
213
353
  ) : (
214
- <ListItem.Frame
354
+ <SelectItemFrame
215
355
  render="div"
216
- componentName={ITEM_NAME}
217
356
  ref={composedRefs}
218
- role="option"
219
357
  aria-labelledby={textId}
220
- aria-selected={isSelected}
221
358
  data-state={isSelected ? 'active' : 'inactive'}
222
- aria-disabled={disabled || undefined}
223
359
  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}
360
+ tabIndex={
361
+ disabled
362
+ ? undefined
363
+ : isWeb &&
364
+ isAdapted &&
365
+ (isActive || (activeIndexRef?.current == null && isSelected))
366
+ ? 0
367
+ : -1
368
+ }
369
+ zIndex={100}
250
370
  {...selectItemProps}
371
+ {...accessibilityProps}
251
372
  />
252
373
  )}
253
374
  </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
+ )