@tamagui/select 1.0.1-beta.118 → 1.0.1-beta.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Select.js +36 -32
- package/dist/cjs/Select.js.map +2 -2
- package/dist/cjs/SelectContent.js +2 -1
- package/dist/cjs/SelectContent.js.map +2 -2
- package/dist/cjs/SelectImpl.js +154 -284
- package/dist/cjs/SelectImpl.js.map +2 -2
- package/dist/cjs/SelectScrollButton.js +54 -40
- package/dist/cjs/SelectScrollButton.js.map +2 -2
- package/dist/cjs/SelectViewport.js +10 -6
- package/dist/cjs/SelectViewport.js.map +2 -2
- package/dist/cjs/types.js.map +1 -1
- package/dist/esm/Select.js +36 -32
- package/dist/esm/Select.js.map +2 -2
- package/dist/esm/SelectContent.js +3 -2
- package/dist/esm/SelectContent.js.map +2 -2
- package/dist/esm/SelectImpl.js +158 -286
- package/dist/esm/SelectImpl.js.map +2 -2
- package/dist/esm/SelectScrollButton.js +54 -40
- package/dist/esm/SelectScrollButton.js.map +2 -2
- package/dist/esm/SelectViewport.js +10 -6
- package/dist/esm/SelectViewport.js.map +2 -2
- package/dist/jsx/Select.js +34 -34
- package/dist/jsx/Select.js.map +2 -2
- package/dist/jsx/SelectContent.js +3 -2
- package/dist/jsx/SelectContent.js.map +2 -2
- package/dist/jsx/SelectImpl.js +128 -266
- package/dist/jsx/SelectImpl.js.map +2 -2
- package/dist/jsx/SelectScrollButton.js +53 -39
- package/dist/jsx/SelectScrollButton.js.map +2 -2
- package/dist/jsx/SelectViewport.js +10 -6
- package/dist/jsx/SelectViewport.js.map +2 -2
- package/package.json +15 -15
- package/src/Select.tsx +47 -49
- package/src/SelectContent.tsx +4 -2
- package/src/SelectImpl.tsx +179 -369
- package/src/SelectScrollButton.tsx +72 -54
- package/src/SelectViewport.tsx +12 -6
- package/src/types.tsx +9 -1
- package/types/Select.d.ts +15 -15
- package/types/Select.d.ts.map +1 -1
- package/types/SelectContent.d.ts +0 -1
- package/types/SelectContent.d.ts.map +1 -1
- package/types/SelectImpl.d.ts.map +1 -1
- package/types/SelectViewport.d.ts +3 -3
- package/types/SelectViewport.d.ts.map +1 -1
- package/types/types.d.ts +9 -1
- package/types/types.d.ts.map +1 -1
- package/types/constants.d.ts +0 -8
- package/types/constants.d.ts.map +0 -1
package/src/SelectImpl.tsx
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SideObject,
|
|
2
3
|
detectOverflow,
|
|
3
4
|
flip,
|
|
5
|
+
inner,
|
|
4
6
|
offset,
|
|
7
|
+
shift,
|
|
5
8
|
size,
|
|
6
9
|
useClick,
|
|
7
10
|
useDismiss,
|
|
8
11
|
useFloating,
|
|
12
|
+
useInnerOffset,
|
|
9
13
|
useInteractions,
|
|
10
14
|
useListNavigation,
|
|
11
15
|
useRole,
|
|
12
16
|
useTypeahead,
|
|
13
17
|
} from '@floating-ui/react-dom-interactions'
|
|
14
18
|
import { usePrevious } from '@radix-ui/react-use-previous'
|
|
19
|
+
import { useForceUpdate, useIsTouchDevice } from '@tamagui/core'
|
|
15
20
|
import * as React from 'react'
|
|
21
|
+
import { flushSync } from 'react-dom'
|
|
16
22
|
|
|
17
23
|
import { FALLBACK_THRESHOLD, MIN_HEIGHT, SCROLL_ARROW_THRESHOLD, WINDOW_PADDING } from './constants'
|
|
18
24
|
import { SelectProvider, useSelectContext } from './context'
|
|
@@ -26,36 +32,35 @@ export type SelectImplProps = ScopedProps<SelectProps> & {
|
|
|
26
32
|
|
|
27
33
|
// TODO use id for focusing from label
|
|
28
34
|
export const SelectInlineImpl = (props: SelectImplProps) => {
|
|
29
|
-
const {
|
|
30
|
-
__scopeSelect,
|
|
31
|
-
children,
|
|
32
|
-
open = false,
|
|
33
|
-
activeIndexRef,
|
|
34
|
-
selectedIndexRef,
|
|
35
|
-
listContentRef,
|
|
36
|
-
} = props
|
|
35
|
+
const { __scopeSelect, children, open = false, selectedIndexRef, listContentRef } = props
|
|
37
36
|
|
|
38
37
|
const selectContext = useSelectContext('SelectSheetImpl', __scopeSelect)
|
|
39
38
|
const { setActiveIndex, setOpen, setSelectedIndex, selectedIndex, activeIndex, forceUpdate } =
|
|
40
39
|
selectContext
|
|
41
|
-
const [showArrows, setShowArrows] = React.useState(false)
|
|
42
40
|
const [scrollTop, setScrollTop] = React.useState(0)
|
|
43
|
-
const
|
|
41
|
+
const touch = useIsTouchDevice()
|
|
44
42
|
|
|
45
43
|
const listItemsRef = React.useRef<Array<HTMLElement | null>>([])
|
|
44
|
+
const overflowRef = React.useRef<null | SideObject>(null)
|
|
45
|
+
const upArrowRef = React.useRef<HTMLDivElement | null>(null)
|
|
46
|
+
const downArrowRef = React.useRef<HTMLDivElement | null>(null)
|
|
47
|
+
const allowSelectRef = React.useRef(false)
|
|
48
|
+
const allowMouseUpRef = React.useRef(true)
|
|
49
|
+
const selectTimeoutRef = React.useRef<any>()
|
|
46
50
|
|
|
47
51
|
const [controlledScrolling, setControlledScrolling] = React.useState(false)
|
|
48
|
-
const [
|
|
52
|
+
const [fallback, setFallback] = React.useState(false)
|
|
53
|
+
const [innerOffset, setInnerOffset] = React.useState(0)
|
|
54
|
+
const [blockSelection, setBlockSelection] = React.useState(false)
|
|
55
|
+
const floatingStyle = React.useRef({})
|
|
49
56
|
|
|
50
57
|
// Wait for scroll position to settle before showing arrows to prevent
|
|
51
58
|
// interference with pointer events.
|
|
52
59
|
React.useEffect(() => {
|
|
53
60
|
const frame = requestAnimationFrame(() => {
|
|
54
|
-
setShowArrows(open)
|
|
55
|
-
|
|
56
61
|
if (!open) {
|
|
57
62
|
setScrollTop(0)
|
|
58
|
-
|
|
63
|
+
setFallback(false)
|
|
59
64
|
setActiveIndex(null)
|
|
60
65
|
setControlledScrolling(false)
|
|
61
66
|
}
|
|
@@ -65,113 +70,68 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
|
|
|
65
70
|
}
|
|
66
71
|
}, [open, setActiveIndex])
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
? [
|
|
83
|
-
offset(({ rects }) => {
|
|
84
|
-
const index = activeIndexRef.current ?? selectedIndexRef.current
|
|
85
|
-
|
|
86
|
-
if (index == null) {
|
|
87
|
-
return 0
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const item = listItemsRef.current[index]
|
|
91
|
-
|
|
92
|
-
if (item == null) {
|
|
93
|
-
return 0
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const offsetTop = item.offsetTop
|
|
97
|
-
const itemHeight = item.offsetHeight
|
|
98
|
-
const height = rects.reference.height
|
|
99
|
-
|
|
100
|
-
return -offsetTop - height - (itemHeight - height) / 2
|
|
101
|
-
}),
|
|
102
|
-
// Custom `size` that can handle the opposite direction of the placement
|
|
103
|
-
{
|
|
104
|
-
name: 'size',
|
|
105
|
-
async fn(args) {
|
|
106
|
-
const {
|
|
107
|
-
elements: { floating },
|
|
108
|
-
rects: { reference },
|
|
109
|
-
middlewareData,
|
|
110
|
-
} = args
|
|
111
|
-
|
|
112
|
-
const overflow = await detectOverflow(args, {
|
|
113
|
-
padding: WINDOW_PADDING,
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
const top = Math.max(0, overflow.top)
|
|
117
|
-
const bottom = Math.max(0, overflow.bottom)
|
|
118
|
-
const nextY = args.y + top
|
|
119
|
-
|
|
120
|
-
if (middlewareData.size?.skip) {
|
|
121
|
-
return {
|
|
122
|
-
y: nextY,
|
|
123
|
-
data: {
|
|
124
|
-
y: middlewareData.size.y,
|
|
125
|
-
},
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
Object.assign(floating.style, {
|
|
130
|
-
maxHeight: `${floating.scrollHeight - Math.abs(top + bottom)}px`,
|
|
131
|
-
minWidth: `${reference.width + getFloatingPadding(floating) * 2}px`,
|
|
132
|
-
})
|
|
73
|
+
const updateFloatingSize = size({
|
|
74
|
+
apply({
|
|
75
|
+
availableHeight,
|
|
76
|
+
rects: {
|
|
77
|
+
reference: { width },
|
|
78
|
+
},
|
|
79
|
+
}) {
|
|
80
|
+
floatingStyle.current = {
|
|
81
|
+
width: width,
|
|
82
|
+
maxHeight: availableHeight,
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
padding: WINDOW_PADDING,
|
|
86
|
+
})
|
|
133
87
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
88
|
+
const { x, y, reference, floating, strategy, context, refs } = useFloating({
|
|
89
|
+
open,
|
|
90
|
+
onOpenChange: setOpen,
|
|
91
|
+
placement: 'bottom-start',
|
|
92
|
+
middleware: fallback
|
|
93
|
+
? [
|
|
94
|
+
offset(5),
|
|
95
|
+
...[
|
|
96
|
+
touch
|
|
97
|
+
? shift({ crossAxis: true, padding: WINDOW_PADDING })
|
|
98
|
+
: flip({ padding: WINDOW_PADDING }),
|
|
99
|
+
],
|
|
100
|
+
updateFloatingSize,
|
|
101
|
+
]
|
|
102
|
+
: [
|
|
103
|
+
inner({
|
|
104
|
+
listRef: listItemsRef,
|
|
105
|
+
overflowRef,
|
|
106
|
+
index: selectedIndex,
|
|
107
|
+
offset: innerOffset,
|
|
108
|
+
onFallbackChange: setFallback,
|
|
109
|
+
padding: 10,
|
|
110
|
+
minItemsVisible: touch ? 10 : 4,
|
|
111
|
+
referenceOverflowThreshold: 20,
|
|
112
|
+
}),
|
|
113
|
+
updateFloatingSize,
|
|
114
|
+
],
|
|
115
|
+
})
|
|
161
116
|
|
|
162
117
|
const floatingRef = refs.floating
|
|
163
118
|
|
|
164
|
-
const showUpArrow =
|
|
119
|
+
const showUpArrow = open && scrollTop > SCROLL_ARROW_THRESHOLD
|
|
165
120
|
const showDownArrow =
|
|
166
|
-
|
|
121
|
+
open &&
|
|
167
122
|
floatingRef.current &&
|
|
168
123
|
scrollTop <
|
|
169
124
|
floatingRef.current.scrollHeight - floatingRef.current.clientHeight - SCROLL_ARROW_THRESHOLD
|
|
170
125
|
|
|
171
126
|
const interactions = useInteractions([
|
|
172
127
|
useClick(context, { pointerDown: true }),
|
|
128
|
+
useDismiss(context, { outsidePointerDown: false }),
|
|
173
129
|
useRole(context, { role: 'listbox' }),
|
|
174
|
-
|
|
130
|
+
useInnerOffset(context, {
|
|
131
|
+
enabled: !fallback,
|
|
132
|
+
onChange: setInnerOffset,
|
|
133
|
+
overflowRef,
|
|
134
|
+
}),
|
|
175
135
|
useListNavigation(context, {
|
|
176
136
|
listRef: listItemsRef,
|
|
177
137
|
activeIndex,
|
|
@@ -186,304 +146,148 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
|
|
|
186
146
|
}),
|
|
187
147
|
])
|
|
188
148
|
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
amount < 0 &&
|
|
205
|
-
selectedIndexRef.current != null &&
|
|
206
|
-
Math.round(rectBottom) < Math.round(viewportHeight + getVisualOffsetTop() - WINDOW_PADDING)
|
|
207
|
-
) {
|
|
208
|
-
floating.style.maxHeight = `${Math.min(visualMaxHeight, currentMaxHeight - amount)}px`
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (
|
|
212
|
-
amount > 0 &&
|
|
213
|
-
Math.round(rectTop) > Math.round(WINDOW_PADDING - getVisualOffsetTop()) &&
|
|
214
|
-
floating.scrollHeight > floating.offsetHeight
|
|
215
|
-
) {
|
|
216
|
-
const nextTop = Math.max(WINDOW_PADDING + getVisualOffsetTop(), currentTop - amount)
|
|
217
|
-
|
|
218
|
-
const nextMaxHeight = Math.min(visualMaxHeight, currentMaxHeight + amount)
|
|
219
|
-
|
|
220
|
-
Object.assign(floating.style, {
|
|
221
|
-
maxHeight: `${nextMaxHeight}px`,
|
|
222
|
-
top: `${nextTop}px`,
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
if (nextTop - WINDOW_PADDING > getVisualOffsetTop()) {
|
|
226
|
-
floating.scrollTop -= nextMaxHeight - currentMaxHeight + getFloatingPadding(floating)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return currentTop - nextTop
|
|
230
|
-
}
|
|
149
|
+
const interactionsContext = {
|
|
150
|
+
...interactions,
|
|
151
|
+
getReferenceProps() {
|
|
152
|
+
return interactions.getReferenceProps({
|
|
153
|
+
ref: reference,
|
|
154
|
+
className: 'SelectTrigger',
|
|
155
|
+
onKeyDown(event) {
|
|
156
|
+
if (event.key === 'Enter' || (event.key === ' ' && !context.dataRef.current.typing)) {
|
|
157
|
+
event.preventDefault()
|
|
158
|
+
setOpen(true)
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
})
|
|
231
162
|
},
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
163
|
+
getFloatingProps(props) {
|
|
164
|
+
return interactions.getFloatingProps({
|
|
165
|
+
ref: floating,
|
|
166
|
+
className: 'Select',
|
|
167
|
+
...props,
|
|
168
|
+
style: {
|
|
169
|
+
position: strategy,
|
|
170
|
+
top: y ?? '',
|
|
171
|
+
left: x ?? '',
|
|
172
|
+
outline: 0,
|
|
173
|
+
listStyleType: 'none',
|
|
174
|
+
scrollbarWidth: 'none',
|
|
175
|
+
...floatingStyle.current,
|
|
176
|
+
...props?.style,
|
|
177
|
+
},
|
|
178
|
+
onPointerEnter() {
|
|
179
|
+
setControlledScrolling(false)
|
|
180
|
+
},
|
|
181
|
+
onPointerMove() {
|
|
182
|
+
setControlledScrolling(false)
|
|
183
|
+
},
|
|
184
|
+
onKeyDown() {
|
|
185
|
+
setControlledScrolling(true)
|
|
186
|
+
},
|
|
187
|
+
onContextMenu(e) {
|
|
188
|
+
e.preventDefault()
|
|
189
|
+
},
|
|
190
|
+
onScroll(event) {
|
|
191
|
+
// In React 18, the ScrollArrows need to synchronously know this value to prevent
|
|
192
|
+
// painting at the wrong time.
|
|
193
|
+
flushSync(() => setScrollTop(event.currentTarget.scrollTop))
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
},
|
|
197
|
+
}
|
|
240
198
|
|
|
241
|
-
|
|
199
|
+
// effects
|
|
242
200
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
201
|
+
React.useLayoutEffect(() => {
|
|
202
|
+
if (open) {
|
|
203
|
+
selectTimeoutRef.current = setTimeout(() => {
|
|
204
|
+
allowSelectRef.current = true
|
|
205
|
+
}, 300)
|
|
246
206
|
|
|
247
|
-
|
|
248
|
-
|
|
207
|
+
return () => {
|
|
208
|
+
clearTimeout(selectTimeoutRef.current)
|
|
249
209
|
}
|
|
210
|
+
} else {
|
|
211
|
+
allowSelectRef.current = false
|
|
212
|
+
allowMouseUpRef.current = true
|
|
213
|
+
setInnerOffset(0)
|
|
214
|
+
setFallback(false)
|
|
215
|
+
setBlockSelection(false)
|
|
216
|
+
}
|
|
217
|
+
}, [open])
|
|
250
218
|
|
|
219
|
+
// Replacement for `useDismiss` as the arrows are outside of the floating
|
|
220
|
+
// element DOM tree.
|
|
221
|
+
React.useLayoutEffect(() => {
|
|
222
|
+
function onPointerDown(e: PointerEvent) {
|
|
223
|
+
const target = e.target as Node
|
|
251
224
|
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
)
|
|
255
|
-
!pinching
|
|
225
|
+
!refs.floating.current?.contains(target) &&
|
|
226
|
+
!upArrowRef.current?.contains(target) &&
|
|
227
|
+
!downArrowRef.current?.contains(target)
|
|
256
228
|
) {
|
|
257
|
-
|
|
258
|
-
} else if (isWheelEvent(event) && isFirefox) {
|
|
259
|
-
// Firefox needs this to propagate scrolling
|
|
260
|
-
// during momentum scrolling phase if the
|
|
261
|
-
// height reached its maximum (at boundaries)
|
|
262
|
-
currentTarget.scrollTop += event.deltaY
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (!pinching) {
|
|
266
|
-
let delta = 5
|
|
267
|
-
|
|
268
|
-
if (isTouchEvent(event)) {
|
|
269
|
-
const currentPageY = touchPageYRef.current
|
|
270
|
-
const pageY = event.touches[0]?.pageY
|
|
271
|
-
|
|
272
|
-
if (pageY != null) {
|
|
273
|
-
touchPageYRef.current = pageY
|
|
274
|
-
|
|
275
|
-
if (currentPageY != null) {
|
|
276
|
-
delta = currentPageY - pageY
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
increaseHeight(currentTarget, isWheelEvent(event) ? event.deltaY : delta)
|
|
282
|
-
setScrollTop(currentTarget.scrollTop)
|
|
283
|
-
// Ensure derived data (scroll arrows) is fresh
|
|
284
|
-
forceUpdate()
|
|
229
|
+
setOpen(false)
|
|
285
230
|
}
|
|
286
|
-
},
|
|
287
|
-
[forceUpdate, increaseHeight]
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
// Handle `onWheel` event in an effect to remove the `passive` option so we
|
|
291
|
-
// can .preventDefault() it
|
|
292
|
-
React.useEffect(() => {
|
|
293
|
-
function onTouchEnd() {
|
|
294
|
-
touchPageYRef.current = null
|
|
295
231
|
}
|
|
296
232
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
floating.addEventListener('wheel', handleWheel)
|
|
300
|
-
floating.addEventListener('touchmove', handleWheel)
|
|
301
|
-
floating.addEventListener('touchend', onTouchEnd, { passive: true })
|
|
233
|
+
if (open) {
|
|
234
|
+
document.addEventListener('pointerdown', onPointerDown)
|
|
302
235
|
return () => {
|
|
303
|
-
|
|
304
|
-
floating.removeEventListener('touchmove', handleWheel)
|
|
305
|
-
floating.removeEventListener('touchend', onTouchEnd)
|
|
236
|
+
document.removeEventListener('pointerdown', onPointerDown)
|
|
306
237
|
}
|
|
307
238
|
}
|
|
308
|
-
}, [open,
|
|
239
|
+
}, [open, refs, setOpen])
|
|
309
240
|
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
window.addEventListener('resize', update)
|
|
313
|
-
return () => {
|
|
314
|
-
window.removeEventListener('resize', update)
|
|
315
|
-
}
|
|
316
|
-
}, [update])
|
|
317
|
-
|
|
318
|
-
// Scroll the active or selected item into view when in `controlledScrolling`
|
|
319
|
-
// mode (i.e. arrow key nav).
|
|
241
|
+
// Scroll the `activeIndex` item into view only in "controlledScrolling"
|
|
242
|
+
// (keyboard nav) mode.
|
|
320
243
|
React.useLayoutEffect(() => {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
activeIndex != null
|
|
326
|
-
? listItemsRef.current[activeIndex]
|
|
327
|
-
: selectedIndex != null
|
|
328
|
-
? listItemsRef.current[selectedIndex]
|
|
329
|
-
: null
|
|
330
|
-
|
|
331
|
-
if (item && prevActiveIndex != null) {
|
|
332
|
-
const itemHeight = listItemsRef.current[prevActiveIndex]?.offsetHeight ?? 0
|
|
333
|
-
|
|
334
|
-
const floatingHeight = floating.offsetHeight
|
|
335
|
-
const top = item.offsetTop
|
|
336
|
-
const bottom = top + itemHeight
|
|
337
|
-
|
|
338
|
-
if (top < floating.scrollTop + 20) {
|
|
339
|
-
const diff = floating.scrollTop - top + 20
|
|
340
|
-
floating.scrollTop -= diff
|
|
341
|
-
|
|
342
|
-
if (activeIndex != selectedIndex && activeIndex != null) {
|
|
343
|
-
increaseHeight(floating, -diff)
|
|
344
|
-
}
|
|
345
|
-
} else if (bottom > floatingHeight + floating.scrollTop - 20) {
|
|
346
|
-
const diff = bottom - floatingHeight - floating.scrollTop + 20
|
|
347
|
-
|
|
348
|
-
floating.scrollTop += diff
|
|
349
|
-
|
|
350
|
-
if (activeIndex != selectedIndex && activeIndex != null) {
|
|
351
|
-
floating.scrollTop -= increaseHeight(floating, diff) ?? 0
|
|
352
|
-
}
|
|
244
|
+
if (open && controlledScrolling) {
|
|
245
|
+
requestAnimationFrame(() => {
|
|
246
|
+
if (activeIndex != null) {
|
|
247
|
+
listItemsRef.current[activeIndex]?.scrollIntoView({ block: 'nearest' })
|
|
353
248
|
}
|
|
354
|
-
}
|
|
249
|
+
})
|
|
355
250
|
}
|
|
356
|
-
}, [
|
|
357
|
-
open,
|
|
358
|
-
controlledScrolling,
|
|
359
|
-
prevActiveIndex,
|
|
360
|
-
activeIndex,
|
|
361
|
-
selectedIndex,
|
|
362
|
-
floatingRef,
|
|
363
|
-
increaseHeight,
|
|
364
|
-
])
|
|
365
251
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
React.useLayoutEffect(() => {
|
|
369
|
-
const floating = refs.floating.current
|
|
370
|
-
const reference = refs.reference.current
|
|
371
|
-
|
|
372
|
-
if (open && floating && reference && floating.offsetHeight < floating.scrollHeight) {
|
|
373
|
-
const referenceRect = reference.getBoundingClientRect()
|
|
374
|
-
|
|
375
|
-
if (middlewareType === 'fallback') {
|
|
376
|
-
const item = listItemsRef.current[selectedIndex]
|
|
377
|
-
if (item) {
|
|
378
|
-
floating.scrollTop = item.offsetTop - floating.clientHeight + referenceRect.height
|
|
379
|
-
}
|
|
380
|
-
return
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
floating.scrollTop = middlewareData.size?.y
|
|
384
|
-
|
|
385
|
-
const closeToBottom =
|
|
386
|
-
(visualViewport?.height ?? 0) + getVisualOffsetTop() - referenceRect.bottom <
|
|
387
|
-
FALLBACK_THRESHOLD
|
|
388
|
-
const closeToTop = referenceRect.top < FALLBACK_THRESHOLD
|
|
389
|
-
|
|
390
|
-
if (floating.offsetHeight < MIN_HEIGHT || closeToTop || closeToBottom) {
|
|
391
|
-
setMiddlewareType('fallback')
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}, [
|
|
395
|
-
open,
|
|
396
|
-
increaseHeight,
|
|
397
|
-
selectedIndex,
|
|
398
|
-
middlewareType,
|
|
399
|
-
refs.floating,
|
|
400
|
-
refs.reference,
|
|
401
|
-
// Always re-run this effect when the position has been computed so the
|
|
402
|
-
// .scrollTop change works with fresh sizing.
|
|
403
|
-
middlewareData,
|
|
404
|
-
])
|
|
252
|
+
setScrollTop(refs.floating.current?.scrollTop ?? 0)
|
|
253
|
+
}, [open, refs, controlledScrolling, activeIndex])
|
|
405
254
|
|
|
255
|
+
// Scroll the `selectedIndex` into view upon opening the floating element.
|
|
406
256
|
React.useLayoutEffect(() => {
|
|
407
|
-
if (open &&
|
|
257
|
+
if (open && fallback) {
|
|
408
258
|
requestAnimationFrame(() => {
|
|
409
|
-
|
|
259
|
+
if (selectedIndex != null) {
|
|
260
|
+
listItemsRef.current[selectedIndex]?.scrollIntoView({ block: 'nearest' })
|
|
261
|
+
}
|
|
410
262
|
})
|
|
411
263
|
}
|
|
412
|
-
}, [
|
|
413
|
-
|
|
414
|
-
// Wait for scroll position to settle before showing arrows to prevent
|
|
415
|
-
// interference with pointer events.
|
|
416
|
-
React.useEffect(() => {
|
|
417
|
-
const frame = requestAnimationFrame(() => {
|
|
418
|
-
setShowArrows(open)
|
|
264
|
+
}, [open, fallback, selectedIndex])
|
|
419
265
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
return () => cancelAnimationFrame(frame)
|
|
428
|
-
}, [open, setActiveIndex])
|
|
266
|
+
// Unset the height limiting for fallback mode. This gets executed prior to
|
|
267
|
+
// the positioning call.
|
|
268
|
+
React.useLayoutEffect(() => {
|
|
269
|
+
if (refs.floating.current && fallback) {
|
|
270
|
+
refs.floating.current.style.maxHeight = ''
|
|
271
|
+
}
|
|
272
|
+
}, [refs, fallback])
|
|
429
273
|
|
|
430
274
|
// We set this to true by default so that events bubble to forms without JS (SSR)
|
|
431
275
|
// const isFormControl = trigger ? Boolean(trigger.closest('form')) : true
|
|
432
276
|
// const [bubbleSelect, setBubbleSelect] = React.useState<HTMLSelectElement | null>(null)
|
|
433
277
|
// const triggerPointerDownPosRef = React.useRef<{ x: number; y: number } | null>(null)
|
|
278
|
+
|
|
434
279
|
return (
|
|
435
280
|
<SelectProvider
|
|
436
281
|
scope={__scopeSelect}
|
|
437
282
|
{...(selectContext as Required<typeof selectContext>)}
|
|
438
|
-
|
|
283
|
+
setScrollTop={setScrollTop}
|
|
284
|
+
setInnerOffset={setInnerOffset}
|
|
439
285
|
floatingRef={floatingRef}
|
|
440
286
|
setValueAtIndex={(index, value) => {
|
|
441
287
|
listContentRef.current[index] = value
|
|
442
288
|
}}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
getReferenceProps() {
|
|
446
|
-
return interactions.getReferenceProps({
|
|
447
|
-
ref: reference,
|
|
448
|
-
className: 'SelectTrigger',
|
|
449
|
-
onKeyDown(event) {
|
|
450
|
-
if (event.key === 'Enter' || (event.key === ' ' && !context.dataRef.current.typing)) {
|
|
451
|
-
event.preventDefault()
|
|
452
|
-
setOpen(true)
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
})
|
|
456
|
-
},
|
|
457
|
-
getFloatingProps(props) {
|
|
458
|
-
return interactions.getFloatingProps({
|
|
459
|
-
ref: floating,
|
|
460
|
-
className: 'Select',
|
|
461
|
-
...props,
|
|
462
|
-
style: {
|
|
463
|
-
position: strategy,
|
|
464
|
-
top: y ?? '',
|
|
465
|
-
left: x ?? '',
|
|
466
|
-
outline: 0,
|
|
467
|
-
listStyleType: 'none',
|
|
468
|
-
scrollbarWidth: 'none',
|
|
469
|
-
userSelect: 'none',
|
|
470
|
-
...props?.style,
|
|
471
|
-
},
|
|
472
|
-
onPointerEnter() {
|
|
473
|
-
setControlledScrolling(false)
|
|
474
|
-
},
|
|
475
|
-
onPointerMove() {
|
|
476
|
-
setControlledScrolling(false)
|
|
477
|
-
},
|
|
478
|
-
onKeyDown() {
|
|
479
|
-
setControlledScrolling(true)
|
|
480
|
-
},
|
|
481
|
-
onScroll(event) {
|
|
482
|
-
setScrollTop(event.currentTarget.scrollTop)
|
|
483
|
-
},
|
|
484
|
-
})
|
|
485
|
-
},
|
|
486
|
-
}}
|
|
289
|
+
fallback={fallback}
|
|
290
|
+
interactions={interactionsContext}
|
|
487
291
|
floatingContext={context}
|
|
488
292
|
activeIndex={activeIndex}
|
|
489
293
|
canScrollDown={!!showDownArrow}
|
|
@@ -491,6 +295,12 @@ export const SelectInlineImpl = (props: SelectImplProps) => {
|
|
|
491
295
|
controlledScrolling
|
|
492
296
|
dataRef={context.dataRef}
|
|
493
297
|
listRef={listItemsRef}
|
|
298
|
+
blockSelection={blockSelection}
|
|
299
|
+
allowMouseUpRef={allowMouseUpRef}
|
|
300
|
+
upArrowRef={upArrowRef}
|
|
301
|
+
downArrowRef={downArrowRef}
|
|
302
|
+
selectTimeoutRef={selectTimeoutRef}
|
|
303
|
+
allowSelectRef={allowSelectRef}
|
|
494
304
|
>
|
|
495
305
|
{children}
|
|
496
306
|
{/* {isFormControl ? (
|