@tamagui/select 1.0.1-beta.113 → 1.0.1-beta.114
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 +50 -558
- package/dist/cjs/Select.js.map +2 -2
- package/dist/cjs/SelectContent.js +53 -0
- package/dist/cjs/SelectContent.js.map +7 -0
- package/dist/cjs/SelectContent.native.js +31 -0
- package/dist/cjs/SelectContent.native.js.map +7 -0
- package/dist/cjs/SelectImpl.js +393 -0
- package/dist/cjs/SelectImpl.js.map +7 -0
- package/dist/cjs/SelectScrollButton.js +128 -0
- package/dist/cjs/SelectScrollButton.js.map +7 -0
- package/dist/cjs/SelectScrollButton.native.js +32 -0
- package/dist/cjs/SelectScrollButton.native.js.map +7 -0
- package/dist/cjs/SelectViewport.js +96 -0
- package/dist/cjs/SelectViewport.js.map +7 -0
- package/dist/cjs/SelectViewport.native.js +43 -0
- package/dist/cjs/SelectViewport.native.js.map +7 -0
- package/dist/cjs/constants.js +47 -0
- package/dist/cjs/constants.js.map +7 -0
- package/dist/cjs/context.js +38 -0
- package/dist/cjs/context.js.map +7 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/types.js +17 -0
- package/dist/cjs/types.js.map +7 -0
- package/dist/esm/Select.js +33 -565
- package/dist/esm/Select.js.map +2 -2
- package/dist/esm/SelectContent.js +26 -0
- package/dist/esm/SelectContent.js.map +7 -0
- package/dist/esm/SelectContent.native.js +7 -0
- package/dist/esm/SelectContent.native.js.map +7 -0
- package/dist/esm/SelectImpl.js +378 -0
- package/dist/esm/SelectImpl.js.map +7 -0
- package/dist/esm/SelectScrollButton.js +100 -0
- package/dist/esm/SelectScrollButton.js.map +7 -0
- package/dist/esm/SelectScrollButton.native.js +7 -0
- package/dist/esm/SelectScrollButton.native.js.map +7 -0
- package/dist/esm/SelectViewport.js +68 -0
- package/dist/esm/SelectViewport.js.map +7 -0
- package/dist/esm/SelectViewport.native.js +16 -0
- package/dist/esm/SelectViewport.native.js.map +7 -0
- package/dist/esm/constants.js +17 -0
- package/dist/esm/constants.js.map +7 -0
- package/dist/esm/context.js +11 -0
- package/dist/esm/context.js.map +7 -0
- package/dist/esm/index.js +13 -0
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/types.js +1 -0
- package/dist/esm/types.js.map +7 -0
- package/dist/jsx/Select.js +32 -515
- package/dist/jsx/Select.js.map +2 -2
- package/dist/jsx/SelectContent.js +19 -0
- package/dist/jsx/SelectContent.js.map +7 -0
- package/dist/jsx/SelectContent.native.js +7 -0
- package/dist/jsx/SelectContent.native.js.map +7 -0
- package/dist/jsx/SelectImpl.js +366 -0
- package/dist/jsx/SelectImpl.js.map +7 -0
- package/dist/jsx/SelectScrollButton.js +78 -0
- package/dist/jsx/SelectScrollButton.js.map +7 -0
- package/dist/jsx/SelectScrollButton.native.js +7 -0
- package/dist/jsx/SelectScrollButton.native.js.map +7 -0
- package/dist/jsx/SelectViewport.js +57 -0
- package/dist/jsx/SelectViewport.js.map +7 -0
- package/dist/jsx/SelectViewport.native.js +14 -0
- package/dist/jsx/SelectViewport.native.js.map +7 -0
- package/dist/jsx/constants.js +17 -0
- package/dist/jsx/constants.js.map +7 -0
- package/dist/jsx/context.js +11 -0
- package/dist/jsx/context.js.map +7 -0
- package/dist/jsx/index.js +13 -0
- package/dist/jsx/index.js.map +2 -2
- package/dist/jsx/types.js +1 -0
- package/dist/jsx/types.js.map +7 -0
- package/package.json +12 -12
- package/src/Select.tsx +57 -881
- package/src/SelectContent.native.tsx +5 -0
- package/src/SelectContent.tsx +34 -0
- package/src/SelectImpl.tsx +511 -0
- package/src/SelectScrollButton.native.tsx +7 -0
- package/src/SelectScrollButton.tsx +150 -0
- package/src/SelectViewport.native.tsx +17 -0
- package/src/SelectViewport.tsx +86 -0
- package/src/constants.tsx +7 -0
- package/src/context.tsx +9 -0
- package/src/index.tsx +7 -0
- package/src/types.tsx +83 -0
- package/types/Select.d.ts +140 -298
- package/types/Select.d.ts.map +1 -1
- package/types/SelectContent.d.ts +4 -0
- package/types/SelectContent.d.ts.map +1 -0
- package/types/SelectContent.native.d.ts +4 -0
- package/types/SelectContent.native.d.ts.map +1 -0
- package/types/SelectImpl.d.ts +9 -0
- package/types/SelectImpl.d.ts.map +1 -0
- package/types/SelectScrollButton.d.ts +6 -0
- package/types/SelectScrollButton.d.ts.map +1 -0
- package/types/SelectScrollButton.native.d.ts +4 -0
- package/types/SelectScrollButton.native.d.ts.map +1 -0
- package/types/SelectViewport.d.ts +127 -0
- package/types/SelectViewport.d.ts.map +1 -0
- package/types/SelectViewport.native.d.ts +54 -0
- package/types/SelectViewport.native.d.ts.map +1 -0
- package/types/constants.d.ts +8 -0
- package/types/constants.d.ts.map +1 -0
- package/types/context.d.ts +17 -0
- package/types/context.d.ts.map +1 -0
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -1
- package/types/types.d.ts +74 -0
- package/types/types.d.ts.map +1 -0
package/src/Select.tsx
CHANGED
|
@@ -1,128 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ContextData,
|
|
3
|
-
FloatingContext,
|
|
4
|
-
FloatingFocusManager,
|
|
5
|
-
FloatingOverlay,
|
|
6
|
-
FloatingPortal,
|
|
7
|
-
ReferenceType,
|
|
8
|
-
autoUpdate,
|
|
9
|
-
detectOverflow,
|
|
10
|
-
flip,
|
|
11
|
-
offset,
|
|
12
|
-
size,
|
|
13
|
-
useClick,
|
|
14
|
-
useDismiss,
|
|
15
|
-
useFloating,
|
|
16
|
-
useInteractions,
|
|
17
|
-
useListNavigation,
|
|
18
|
-
useRole,
|
|
19
|
-
useTypeahead,
|
|
20
|
-
} from '@floating-ui/react-dom-interactions'
|
|
21
|
-
import { usePrevious } from '@radix-ui/react-use-previous'
|
|
22
1
|
import { useComposedRefs } from '@tamagui/compose-refs'
|
|
23
|
-
import { GetProps,
|
|
24
|
-
import {
|
|
25
|
-
Theme,
|
|
26
|
-
styled,
|
|
27
|
-
useGet,
|
|
28
|
-
useIsomorphicLayoutEffect,
|
|
29
|
-
useThemeName,
|
|
30
|
-
withStaticProperties,
|
|
31
|
-
} from '@tamagui/core'
|
|
2
|
+
import { GetProps, TamaguiElement, isWeb, useMedia } from '@tamagui/core'
|
|
3
|
+
import { styled, useGet, useIsomorphicLayoutEffect, withStaticProperties } from '@tamagui/core'
|
|
32
4
|
import { useId } from '@tamagui/core'
|
|
33
|
-
import { createContextScope } from '@tamagui/create-context'
|
|
34
|
-
import type { Scope } from '@tamagui/create-context'
|
|
35
5
|
import { ListItem, ListItemProps } from '@tamagui/list-item'
|
|
36
|
-
import { PortalHost
|
|
6
|
+
import { PortalHost } from '@tamagui/portal'
|
|
37
7
|
import { Separator } from '@tamagui/separator'
|
|
38
8
|
import { Sheet, SheetController } from '@tamagui/sheet'
|
|
39
|
-
import {
|
|
9
|
+
import { XStack, YStack, YStackProps } from '@tamagui/stacks'
|
|
40
10
|
import { Paragraph } from '@tamagui/text'
|
|
41
11
|
import { useControllableState } from '@tamagui/use-controllable-state'
|
|
42
12
|
import * as React from 'react'
|
|
43
13
|
import * as ReactDOM from 'react-dom'
|
|
44
14
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
export interface SelectProps {
|
|
53
|
-
id?: string
|
|
54
|
-
children?: React.ReactNode
|
|
55
|
-
value?: string
|
|
56
|
-
defaultValue?: string
|
|
57
|
-
onValueChange?(value: string): void
|
|
58
|
-
open?: boolean
|
|
59
|
-
defaultOpen?: boolean
|
|
60
|
-
onOpenChange?(open: boolean): void
|
|
61
|
-
dir?: Direction
|
|
62
|
-
name?: string
|
|
63
|
-
autoComplete?: string
|
|
64
|
-
size?: SizeTokens
|
|
65
|
-
sheetBreakpoint?: MediaPropKeys | false
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
type NonNull<A> = Exclude<A, void | null>
|
|
69
|
-
|
|
70
|
-
interface SelectContextValue {
|
|
71
|
-
dir?: Direction
|
|
72
|
-
scopeKey: string
|
|
73
|
-
sheetBreakpoint: NonNull<SelectProps['sheetBreakpoint']>
|
|
74
|
-
size?: SizeTokens
|
|
75
|
-
value: any
|
|
76
|
-
selectedIndex: number
|
|
77
|
-
setSelectedIndex: (index: number) => void
|
|
78
|
-
activeIndex: number | null
|
|
79
|
-
setActiveIndex: (index: number | null) => void
|
|
80
|
-
setValueAtIndex: (index: number, value: string) => void
|
|
81
|
-
open: boolean
|
|
82
|
-
setOpen: (open: boolean) => void
|
|
83
|
-
onChange: (value: string) => void
|
|
84
|
-
valueNode: Element | null
|
|
85
|
-
onValueNodeChange(node: HTMLElement): void
|
|
86
|
-
valueNodeHasChildren: boolean
|
|
87
|
-
onValueNodeHasChildrenChange(hasChildren: boolean): void
|
|
88
|
-
forceUpdate: React.DispatchWithoutAction
|
|
89
|
-
|
|
90
|
-
// InlineImpl only:
|
|
91
|
-
dataRef?: React.MutableRefObject<ContextData>
|
|
92
|
-
controlledScrolling?: boolean
|
|
93
|
-
listRef?: React.MutableRefObject<Array<HTMLElement | null>>
|
|
94
|
-
floatingRef?: React.MutableRefObject<HTMLElement | null>
|
|
95
|
-
canScrollUp?: boolean
|
|
96
|
-
canScrollDown?: boolean
|
|
97
|
-
floatingContext?: FloatingContext<ReferenceType>
|
|
98
|
-
increaseHeight?: (floating: HTMLElement, amount?: any) => number | undefined
|
|
99
|
-
interactions?: {
|
|
100
|
-
getReferenceProps: (userProps?: React.HTMLProps<Element> | undefined) => any
|
|
101
|
-
getFloatingProps: (userProps?: React.HTMLProps<HTMLElement> | undefined) => any
|
|
102
|
-
getItemProps: (userProps?: React.HTMLProps<HTMLElement> | undefined) => any
|
|
103
|
-
}
|
|
104
|
-
}
|
|
15
|
+
import { SELECT_NAME } from './constants'
|
|
16
|
+
import { SelectProvider, createSelectContext, useSelectContext } from './context'
|
|
17
|
+
import { SelectContent } from './SelectContent'
|
|
18
|
+
import { SelectImplProps, SelectInlineImpl } from './SelectImpl'
|
|
19
|
+
import { SelectScrollDownButton, SelectScrollUpButton } from './SelectScrollButton'
|
|
20
|
+
import { SelectViewport } from './SelectViewport'
|
|
21
|
+
import { ScopedProps, SelectContextValue, SelectProps } from './types'
|
|
105
22
|
|
|
106
23
|
// Cross browser fixes for pinch-zooming/backdrop-filter 🙄
|
|
107
24
|
const userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || ''
|
|
108
|
-
const isFirefox = userAgent.toLowerCase().includes('firefox')
|
|
109
|
-
|
|
110
|
-
document.body.classList.add('firefox')
|
|
111
|
-
}
|
|
112
|
-
function getVisualOffsetTop() {
|
|
25
|
+
export const isFirefox = userAgent.toLowerCase().includes('firefox')
|
|
26
|
+
export function getVisualOffsetTop() {
|
|
113
27
|
return !/^((?!chrome|android).)*safari/i.test(userAgent) ? visualViewport?.offsetTop ?? 0 : 0
|
|
114
28
|
}
|
|
115
29
|
|
|
116
|
-
type ScopedProps<P> = P & { __scopeSelect?: Scope }
|
|
117
|
-
|
|
118
|
-
const [createSelectContext, createSelectScope] = createContextScope(SELECT_NAME)
|
|
119
|
-
const [SelectProvider, useSelectContext] = createSelectContext<SelectContextValue>(SELECT_NAME)
|
|
120
|
-
|
|
121
|
-
// const [SelectContentContextProvider, useSelectContentContext] =
|
|
122
|
-
// createSelectContext<SelectContentContextValue>(CONTENT_NAME);
|
|
123
|
-
|
|
124
|
-
type Direction = 'ltr' | 'rtl'
|
|
125
|
-
|
|
126
30
|
/* -------------------------------------------------------------------------------------------------
|
|
127
31
|
* SelectTrigger
|
|
128
32
|
* -----------------------------------------------------------------------------------------------*/
|
|
@@ -197,17 +101,22 @@ type SelectValueProps = GetProps<typeof SelectValueFrame> & {
|
|
|
197
101
|
|
|
198
102
|
const SelectValue = SelectValueFrame.extractable(
|
|
199
103
|
React.forwardRef<TamaguiElement, SelectValueProps>(
|
|
200
|
-
(
|
|
104
|
+
(
|
|
105
|
+
{ __scopeSelect, children: childrenProp, placeholder }: ScopedProps<SelectValueProps>,
|
|
106
|
+
forwardedRef
|
|
107
|
+
) => {
|
|
201
108
|
// We ignore `className` and `style` as this part shouldn't be styled.
|
|
202
109
|
const context = useSelectContext(VALUE_NAME, __scopeSelect)
|
|
203
110
|
const { onValueNodeHasChildrenChange } = context
|
|
204
|
-
const hasChildren =
|
|
111
|
+
const hasChildren = childrenProp !== undefined
|
|
205
112
|
const composedRefs = useComposedRefs(forwardedRef, context.onValueNodeChange)
|
|
206
113
|
|
|
207
114
|
React.useLayoutEffect(() => {
|
|
208
115
|
onValueNodeHasChildrenChange(hasChildren)
|
|
209
116
|
}, [onValueNodeHasChildrenChange, hasChildren])
|
|
210
117
|
|
|
118
|
+
const children = childrenProp ?? context.selectedItem
|
|
119
|
+
|
|
211
120
|
return (
|
|
212
121
|
<SelectValueFrame
|
|
213
122
|
size={context.size}
|
|
@@ -236,115 +145,6 @@ export const SelectIcon = styled(XStack, {
|
|
|
236
145
|
children: <Paragraph>▼</Paragraph>,
|
|
237
146
|
})
|
|
238
147
|
|
|
239
|
-
/* -------------------------------------------------------------------------------------------------
|
|
240
|
-
* SelectContent
|
|
241
|
-
* -----------------------------------------------------------------------------------------------*/
|
|
242
|
-
|
|
243
|
-
const CONTENT_NAME = 'SelectContent'
|
|
244
|
-
|
|
245
|
-
export type SelectContentProps = { children?: React.ReactNode }
|
|
246
|
-
|
|
247
|
-
const SelectContent = ({ children, __scopeSelect }: ScopedProps<SelectContentProps>) => {
|
|
248
|
-
const context = useSelectContext(CONTENT_NAME, __scopeSelect)
|
|
249
|
-
const themeName = useThemeName()
|
|
250
|
-
const showSheet = useShowSelectSheet(context)
|
|
251
|
-
const contents = <Theme name={themeName}>{children}</Theme>
|
|
252
|
-
|
|
253
|
-
if (showSheet && context.open) {
|
|
254
|
-
return contents
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return (
|
|
258
|
-
<FloatingPortal>
|
|
259
|
-
{context.open ? (
|
|
260
|
-
<FloatingOverlay lockScroll>{contents}</FloatingOverlay>
|
|
261
|
-
) : (
|
|
262
|
-
<div style={{ display: 'none' }}>{contents}</div>
|
|
263
|
-
)}
|
|
264
|
-
</FloatingPortal>
|
|
265
|
-
)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/* -------------------------------------------------------------------------------------------------
|
|
269
|
-
* SelectViewport
|
|
270
|
-
* -----------------------------------------------------------------------------------------------*/
|
|
271
|
-
|
|
272
|
-
const VIEWPORT_NAME = 'SelectViewport'
|
|
273
|
-
|
|
274
|
-
export const SelectViewportFrame = styled(ThemeableStack, {
|
|
275
|
-
name: VIEWPORT_NAME,
|
|
276
|
-
backgroundColor: '$background',
|
|
277
|
-
elevate: true,
|
|
278
|
-
bordered: true,
|
|
279
|
-
// width: '100%',
|
|
280
|
-
overflow: 'scroll',
|
|
281
|
-
userSelect: 'none',
|
|
282
|
-
|
|
283
|
-
variants: {
|
|
284
|
-
size: {
|
|
285
|
-
'...size': (val, { tokens }) => {
|
|
286
|
-
return {
|
|
287
|
-
borderRadius: tokens.radius[val] ?? val,
|
|
288
|
-
}
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
|
|
293
|
-
defaultVariants: {
|
|
294
|
-
size: '$2',
|
|
295
|
-
},
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
export type SelectViewportProps = GetProps<typeof SelectViewportFrame>
|
|
299
|
-
|
|
300
|
-
export const SelectViewport = React.forwardRef<TamaguiElement, SelectViewportProps>(
|
|
301
|
-
(props: ScopedProps<SelectViewportProps>, forwardedRef) => {
|
|
302
|
-
const { __scopeSelect, children, ...viewportProps } = props
|
|
303
|
-
const context = useSelectContext(VIEWPORT_NAME, __scopeSelect)
|
|
304
|
-
const { style, ...floatingProps } = context.interactions?.getFloatingProps() ?? {}
|
|
305
|
-
const breakpointActive = useSelectBreakpointActive(context.sheetBreakpoint)
|
|
306
|
-
|
|
307
|
-
if (breakpointActive || !isWeb) {
|
|
308
|
-
return <PortalItem hostName={`${context.scopeKey}SheetContents`}>{children}</PortalItem>
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (!context.floatingContext) {
|
|
312
|
-
return null
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// TODO
|
|
316
|
-
delete style['scrollbarWidth']
|
|
317
|
-
delete style['listStyleType']
|
|
318
|
-
|
|
319
|
-
return (
|
|
320
|
-
<>
|
|
321
|
-
{/* Hide scrollbars cross-browser and enable momentum scroll for touch devices */}
|
|
322
|
-
<style
|
|
323
|
-
dangerouslySetInnerHTML={{
|
|
324
|
-
__html: `[data-tamagui-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-tamagui-select-viewport]::-webkit-scrollbar{display:none}`,
|
|
325
|
-
}}
|
|
326
|
-
/>
|
|
327
|
-
<FloatingFocusManager context={context.floatingContext} preventTabbing>
|
|
328
|
-
<SelectViewportFrame
|
|
329
|
-
size={context.size}
|
|
330
|
-
data-tamagui-select-viewport=""
|
|
331
|
-
// @ts-ignore
|
|
332
|
-
role="presentation"
|
|
333
|
-
{...viewportProps}
|
|
334
|
-
ref={forwardedRef}
|
|
335
|
-
{...floatingProps}
|
|
336
|
-
{...style}
|
|
337
|
-
>
|
|
338
|
-
{children}
|
|
339
|
-
</SelectViewportFrame>
|
|
340
|
-
</FloatingFocusManager>
|
|
341
|
-
</>
|
|
342
|
-
)
|
|
343
|
-
}
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
SelectViewport.displayName = VIEWPORT_NAME
|
|
347
|
-
|
|
348
148
|
/* -------------------------------------------------------------------------------------------------
|
|
349
149
|
* SelectItem
|
|
350
150
|
* -----------------------------------------------------------------------------------------------*/
|
|
@@ -368,8 +168,6 @@ export interface SelectItemProps extends YStackProps {
|
|
|
368
168
|
textValue?: string
|
|
369
169
|
}
|
|
370
170
|
|
|
371
|
-
// TODO styled(ListItem, { name: 'SelectItem' })
|
|
372
|
-
|
|
373
171
|
export const SelectItem = React.forwardRef<TamaguiElement, SelectItemProps>(
|
|
374
172
|
(props: ScopedProps<SelectItemProps>, forwardedRef) => {
|
|
375
173
|
const {
|
|
@@ -402,6 +200,7 @@ export const SelectItem = React.forwardRef<TamaguiElement, SelectItemProps>(
|
|
|
402
200
|
const composedRefs = useComposedRefs(
|
|
403
201
|
forwardedRef,
|
|
404
202
|
(node) => {
|
|
203
|
+
if (!isWeb) return
|
|
405
204
|
if (node instanceof HTMLElement) {
|
|
406
205
|
if (listRef) {
|
|
407
206
|
listRef.current[index] = node
|
|
@@ -511,20 +310,35 @@ const SelectItemText = React.forwardRef<TamaguiElement, SelectItemTextProps>(
|
|
|
511
310
|
const itemContext = useSelectItemContext(ITEM_TEXT_NAME, __scopeSelect)
|
|
512
311
|
const ref = React.useRef<TamaguiElement | null>(null)
|
|
513
312
|
const composedRefs = useComposedRefs(forwardedRef, ref, itemContext.onItemTextChange)
|
|
313
|
+
const isSelected = itemContext.isSelected && context.valueNode && !context.valueNodeHasChildren
|
|
314
|
+
|
|
315
|
+
const contents = (
|
|
316
|
+
<SelectItemTextFrame
|
|
317
|
+
className={className}
|
|
318
|
+
size={context.size}
|
|
319
|
+
id={itemContext.textId}
|
|
320
|
+
{...itemTextProps}
|
|
321
|
+
ref={composedRefs}
|
|
322
|
+
/>
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
// until portals work in sub-trees on RN, use this just for native:
|
|
326
|
+
if (!isWeb) {
|
|
327
|
+
React.useEffect(() => {
|
|
328
|
+
if (isSelected) {
|
|
329
|
+
context.setSelectedItem(contents)
|
|
330
|
+
}
|
|
331
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
332
|
+
}, [isSelected])
|
|
333
|
+
}
|
|
514
334
|
|
|
515
335
|
return (
|
|
516
336
|
<>
|
|
517
|
-
|
|
518
|
-
className={className}
|
|
519
|
-
size={context.size}
|
|
520
|
-
id={itemContext.textId}
|
|
521
|
-
{...itemTextProps}
|
|
522
|
-
ref={composedRefs}
|
|
523
|
-
/>
|
|
337
|
+
{contents}
|
|
524
338
|
|
|
525
339
|
{/* Portal the select item text into the trigger value node */}
|
|
526
|
-
{
|
|
527
|
-
? ReactDOM.createPortal(itemTextProps.children, context.valueNode)
|
|
340
|
+
{isWeb && isSelected
|
|
341
|
+
? ReactDOM.createPortal(itemTextProps.children, context.valueNode!)
|
|
528
342
|
: null}
|
|
529
343
|
|
|
530
344
|
{/* Portal an option in the bubble select */}
|
|
@@ -633,154 +447,6 @@ const SelectLabel = React.forwardRef<TamaguiElement, SelectLabelProps>(
|
|
|
633
447
|
|
|
634
448
|
SelectLabel.displayName = LABEL_NAME
|
|
635
449
|
|
|
636
|
-
/* -------------------------------------------------------------------------------------------------
|
|
637
|
-
* SelectScrollUpButton
|
|
638
|
-
* -----------------------------------------------------------------------------------------------*/
|
|
639
|
-
|
|
640
|
-
const SCROLL_UP_BUTTON_NAME = 'SelectScrollUpButton'
|
|
641
|
-
|
|
642
|
-
interface SelectScrollButtonProps
|
|
643
|
-
extends Omit<SelectScrollButtonImplProps, 'dir' | 'componentName'> {}
|
|
644
|
-
|
|
645
|
-
const SelectScrollUpButton = React.forwardRef<TamaguiElement, SelectScrollButtonProps>(
|
|
646
|
-
(props: ScopedProps<SelectScrollButtonProps>, forwardedRef) => {
|
|
647
|
-
return (
|
|
648
|
-
<SelectScrollButtonImpl
|
|
649
|
-
componentName={SCROLL_UP_BUTTON_NAME}
|
|
650
|
-
{...props}
|
|
651
|
-
dir="up"
|
|
652
|
-
ref={forwardedRef}
|
|
653
|
-
/>
|
|
654
|
-
)
|
|
655
|
-
}
|
|
656
|
-
)
|
|
657
|
-
|
|
658
|
-
SelectScrollUpButton.displayName = SCROLL_UP_BUTTON_NAME
|
|
659
|
-
|
|
660
|
-
/* -------------------------------------------------------------------------------------------------
|
|
661
|
-
* SelectScrollDownButton
|
|
662
|
-
* -----------------------------------------------------------------------------------------------*/
|
|
663
|
-
|
|
664
|
-
const SCROLL_DOWN_BUTTON_NAME = 'SelectScrollDownButton'
|
|
665
|
-
|
|
666
|
-
const SelectScrollDownButton = React.forwardRef<TamaguiElement, SelectScrollButtonProps>(
|
|
667
|
-
(props: ScopedProps<SelectScrollButtonProps>, forwardedRef) => {
|
|
668
|
-
return (
|
|
669
|
-
<SelectScrollButtonImpl
|
|
670
|
-
componentName={SCROLL_DOWN_BUTTON_NAME}
|
|
671
|
-
{...props}
|
|
672
|
-
dir="down"
|
|
673
|
-
ref={forwardedRef}
|
|
674
|
-
/>
|
|
675
|
-
)
|
|
676
|
-
}
|
|
677
|
-
)
|
|
678
|
-
|
|
679
|
-
SelectScrollDownButton.displayName = SCROLL_DOWN_BUTTON_NAME
|
|
680
|
-
|
|
681
|
-
type SelectScrollButtonImplElement = TamaguiElement
|
|
682
|
-
interface SelectScrollButtonImplProps extends YStackProps {
|
|
683
|
-
dir: 'up' | 'down'
|
|
684
|
-
componentName: string
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
const SelectScrollButtonImpl = React.memo(
|
|
688
|
-
React.forwardRef<SelectScrollButtonImplElement, SelectScrollButtonImplProps>(
|
|
689
|
-
(props: ScopedProps<SelectScrollButtonImplProps>, forwardedRef) => {
|
|
690
|
-
const { __scopeSelect, dir, componentName, ...scrollIndicatorProps } = props
|
|
691
|
-
const { floatingRef, increaseHeight, forceUpdate, ...context } = useSelectContext(
|
|
692
|
-
componentName,
|
|
693
|
-
__scopeSelect
|
|
694
|
-
)
|
|
695
|
-
const intervalRef = React.useRef<any>()
|
|
696
|
-
const loopingRef = React.useRef(false)
|
|
697
|
-
const isVisible = context[dir === 'down' ? 'canScrollDown' : 'canScrollUp']
|
|
698
|
-
|
|
699
|
-
const { x, y, reference, floating, strategy, update, refs } = useFloating({
|
|
700
|
-
strategy: 'fixed',
|
|
701
|
-
placement: dir === 'up' ? 'top' : 'bottom',
|
|
702
|
-
middleware: [offset(({ rects }) => -rects.floating.height)],
|
|
703
|
-
})
|
|
704
|
-
|
|
705
|
-
const composedRefs = useComposedRefs(forwardedRef, floating)
|
|
706
|
-
|
|
707
|
-
React.useLayoutEffect(() => {
|
|
708
|
-
if (!floatingRef) return
|
|
709
|
-
reference(floatingRef.current)
|
|
710
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
711
|
-
}, [reference, floatingRef?.current])
|
|
712
|
-
|
|
713
|
-
React.useEffect(() => {
|
|
714
|
-
if (!refs.reference.current || !refs.floating.current || !isVisible) {
|
|
715
|
-
return
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
const cleanup = autoUpdate(refs.reference.current, refs.floating.current, update, {
|
|
719
|
-
animationFrame: true,
|
|
720
|
-
})
|
|
721
|
-
|
|
722
|
-
return () => {
|
|
723
|
-
clearInterval(intervalRef.current)
|
|
724
|
-
loopingRef.current = false
|
|
725
|
-
cleanup()
|
|
726
|
-
}
|
|
727
|
-
}, [isVisible, update, refs.floating, refs.reference])
|
|
728
|
-
|
|
729
|
-
if (!isVisible) {
|
|
730
|
-
return null
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
const handleScrollArrowChange = () => {
|
|
734
|
-
if (!floatingRef) return
|
|
735
|
-
const floating = floatingRef.current
|
|
736
|
-
const isUp = dir === 'up'
|
|
737
|
-
|
|
738
|
-
if (floating) {
|
|
739
|
-
const value = isUp ? -SCROLL_ARROW_VELOCITY : SCROLL_ARROW_VELOCITY
|
|
740
|
-
const multi =
|
|
741
|
-
(isUp && floating.scrollTop <= SCROLL_ARROW_THRESHOLD * 2) ||
|
|
742
|
-
(!isUp &&
|
|
743
|
-
floating.scrollTop >=
|
|
744
|
-
floating.scrollHeight - floating.clientHeight - SCROLL_ARROW_THRESHOLD * 2)
|
|
745
|
-
? 2
|
|
746
|
-
: 1
|
|
747
|
-
|
|
748
|
-
floating.scrollTop += multi * (isUp ? -SCROLL_ARROW_VELOCITY : SCROLL_ARROW_VELOCITY)
|
|
749
|
-
|
|
750
|
-
increaseHeight?.(floating, multi === 2 ? value * 2 : value)
|
|
751
|
-
// Ensure derived data (scroll arrows) is fresh
|
|
752
|
-
forceUpdate()
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
return (
|
|
757
|
-
<YStack
|
|
758
|
-
ref={composedRefs}
|
|
759
|
-
componentName={componentName}
|
|
760
|
-
aria-hidden
|
|
761
|
-
{...scrollIndicatorProps}
|
|
762
|
-
zIndex={1000}
|
|
763
|
-
// @ts-expect-error
|
|
764
|
-
position={strategy}
|
|
765
|
-
left={x || 0}
|
|
766
|
-
top={y || 0}
|
|
767
|
-
width={`calc(${(floatingRef?.current?.offsetWidth ?? 0) - 2}px)`}
|
|
768
|
-
onPointerMove={() => {
|
|
769
|
-
if (!loopingRef.current) {
|
|
770
|
-
intervalRef.current = setInterval(handleScrollArrowChange, 1000 / 60)
|
|
771
|
-
loopingRef.current = true
|
|
772
|
-
}
|
|
773
|
-
}}
|
|
774
|
-
onPointerLeave={() => {
|
|
775
|
-
loopingRef.current = false
|
|
776
|
-
clearInterval(intervalRef.current)
|
|
777
|
-
}}
|
|
778
|
-
/>
|
|
779
|
-
)
|
|
780
|
-
}
|
|
781
|
-
)
|
|
782
|
-
)
|
|
783
|
-
|
|
784
450
|
/* -------------------------------------------------------------------------------------------------
|
|
785
451
|
* SelectSeparator
|
|
786
452
|
* -----------------------------------------------------------------------------------------------*/
|
|
@@ -789,12 +455,14 @@ export const SelectSeparator = styled(Separator, {
|
|
|
789
455
|
name: 'SelectSeparator',
|
|
790
456
|
})
|
|
791
457
|
|
|
792
|
-
const useSelectBreakpointActive = (
|
|
458
|
+
export const useSelectBreakpointActive = (
|
|
459
|
+
sheetBreakpoint: SelectContextValue['sheetBreakpoint']
|
|
460
|
+
) => {
|
|
793
461
|
const media = useMedia()
|
|
794
462
|
return sheetBreakpoint ? media[sheetBreakpoint] : false
|
|
795
463
|
}
|
|
796
464
|
|
|
797
|
-
const useShowSelectSheet = (context: SelectContextValue) => {
|
|
465
|
+
export const useShowSelectSheet = (context: SelectContextValue) => {
|
|
798
466
|
const breakpointActive = useSelectBreakpointActive(context.sheetBreakpoint)
|
|
799
467
|
return context.open === false ? false : breakpointActive
|
|
800
468
|
}
|
|
@@ -838,507 +506,14 @@ export const SelectSheetContents = ({ __scopeSelect }: ScopedProps<{}>) => {
|
|
|
838
506
|
|
|
839
507
|
SelectSheetContents.displayName = SHEET_CONTENTS_NAME
|
|
840
508
|
|
|
841
|
-
/* -------------------------------------------------------------------------------------------------
|
|
842
|
-
* Select
|
|
843
|
-
* -----------------------------------------------------------------------------------------------*/
|
|
844
|
-
|
|
845
|
-
type SelectImplProps = ScopedProps<SelectProps> & {
|
|
846
|
-
activeIndexRef: any
|
|
847
|
-
selectedIndexRef: any
|
|
848
|
-
listContentRef: any
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
// TODO use id for focusing from label
|
|
852
|
-
|
|
853
|
-
const SelectInlineImpl = (props: SelectImplProps) => {
|
|
854
|
-
const {
|
|
855
|
-
__scopeSelect,
|
|
856
|
-
children,
|
|
857
|
-
open = false,
|
|
858
|
-
activeIndexRef,
|
|
859
|
-
selectedIndexRef,
|
|
860
|
-
listContentRef,
|
|
861
|
-
} = props
|
|
862
|
-
|
|
863
|
-
const selectContext = useSelectContext('SelectSheetImpl', __scopeSelect)
|
|
864
|
-
const { setActiveIndex, setOpen, setSelectedIndex, selectedIndex, activeIndex, forceUpdate } =
|
|
865
|
-
selectContext
|
|
866
|
-
const [showArrows, setShowArrows] = React.useState(false)
|
|
867
|
-
const [scrollTop, setScrollTop] = React.useState(0)
|
|
868
|
-
const prevActiveIndex = usePrevious<number | null>(activeIndex)
|
|
869
|
-
|
|
870
|
-
const listItemsRef = React.useRef<Array<HTMLElement | null>>([])
|
|
871
|
-
|
|
872
|
-
const [controlledScrolling, setControlledScrolling] = React.useState(false)
|
|
873
|
-
const [middlewareType, setMiddlewareType] = React.useState<'align' | 'fallback'>('align')
|
|
874
|
-
|
|
875
|
-
// Wait for scroll position to settle before showing arrows to prevent
|
|
876
|
-
// interference with pointer events.
|
|
877
|
-
React.useEffect(() => {
|
|
878
|
-
const frame = requestAnimationFrame(() => {
|
|
879
|
-
setShowArrows(open)
|
|
880
|
-
|
|
881
|
-
if (!open) {
|
|
882
|
-
setScrollTop(0)
|
|
883
|
-
setMiddlewareType('align')
|
|
884
|
-
setActiveIndex(null)
|
|
885
|
-
setControlledScrolling(false)
|
|
886
|
-
}
|
|
887
|
-
})
|
|
888
|
-
return () => {
|
|
889
|
-
cancelAnimationFrame(frame)
|
|
890
|
-
}
|
|
891
|
-
}, [open, setActiveIndex])
|
|
892
|
-
|
|
893
|
-
function getFloatingPadding(floating: HTMLElement | null) {
|
|
894
|
-
if (!floating) {
|
|
895
|
-
return 0
|
|
896
|
-
}
|
|
897
|
-
return Number(getComputedStyle(floating).paddingLeft?.replace('px', ''))
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
const { x, y, reference, floating, strategy, context, refs, middlewareData, update } =
|
|
901
|
-
useFloating({
|
|
902
|
-
open,
|
|
903
|
-
onOpenChange: setOpen,
|
|
904
|
-
placement: 'bottom',
|
|
905
|
-
middleware:
|
|
906
|
-
middlewareType === 'align'
|
|
907
|
-
? [
|
|
908
|
-
offset(({ rects }) => {
|
|
909
|
-
const index = activeIndexRef.current ?? selectedIndexRef.current
|
|
910
|
-
|
|
911
|
-
if (index == null) {
|
|
912
|
-
return 0
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
const item = listItemsRef.current[index]
|
|
916
|
-
|
|
917
|
-
if (item == null) {
|
|
918
|
-
return 0
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
const offsetTop = item.offsetTop
|
|
922
|
-
const itemHeight = item.offsetHeight
|
|
923
|
-
const height = rects.reference.height
|
|
924
|
-
|
|
925
|
-
return -offsetTop - height - (itemHeight - height) / 2
|
|
926
|
-
}),
|
|
927
|
-
// Custom `size` that can handle the opposite direction of the placement
|
|
928
|
-
{
|
|
929
|
-
name: 'size',
|
|
930
|
-
async fn(args) {
|
|
931
|
-
const {
|
|
932
|
-
elements: { floating },
|
|
933
|
-
rects: { reference },
|
|
934
|
-
middlewareData,
|
|
935
|
-
} = args
|
|
936
|
-
|
|
937
|
-
const overflow = await detectOverflow(args, {
|
|
938
|
-
padding: WINDOW_PADDING,
|
|
939
|
-
})
|
|
940
|
-
|
|
941
|
-
const top = Math.max(0, overflow.top)
|
|
942
|
-
const bottom = Math.max(0, overflow.bottom)
|
|
943
|
-
const nextY = args.y + top
|
|
944
|
-
|
|
945
|
-
if (middlewareData.size?.skip) {
|
|
946
|
-
return {
|
|
947
|
-
y: nextY,
|
|
948
|
-
data: {
|
|
949
|
-
y: middlewareData.size.y,
|
|
950
|
-
},
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
Object.assign(floating.style, {
|
|
955
|
-
maxHeight: `${floating.scrollHeight - Math.abs(top + bottom)}px`,
|
|
956
|
-
minWidth: `${reference.width + getFloatingPadding(floating) * 2}px`,
|
|
957
|
-
})
|
|
958
|
-
|
|
959
|
-
return {
|
|
960
|
-
y: nextY,
|
|
961
|
-
data: {
|
|
962
|
-
y: top,
|
|
963
|
-
skip: true,
|
|
964
|
-
},
|
|
965
|
-
reset: {
|
|
966
|
-
rects: true,
|
|
967
|
-
},
|
|
968
|
-
}
|
|
969
|
-
},
|
|
970
|
-
},
|
|
971
|
-
]
|
|
972
|
-
: [
|
|
973
|
-
offset(5),
|
|
974
|
-
flip(),
|
|
975
|
-
size({
|
|
976
|
-
apply({ rects, availableHeight, elements }) {
|
|
977
|
-
Object.assign(elements.floating.style, {
|
|
978
|
-
width: `${rects.reference.width}px`,
|
|
979
|
-
maxHeight: `${availableHeight}px`,
|
|
980
|
-
})
|
|
981
|
-
},
|
|
982
|
-
padding: WINDOW_PADDING,
|
|
983
|
-
}),
|
|
984
|
-
],
|
|
985
|
-
})
|
|
986
|
-
|
|
987
|
-
const floatingRef = refs.floating
|
|
988
|
-
|
|
989
|
-
const showUpArrow = showArrows && scrollTop > SCROLL_ARROW_THRESHOLD
|
|
990
|
-
const showDownArrow =
|
|
991
|
-
showArrows &&
|
|
992
|
-
floatingRef.current &&
|
|
993
|
-
scrollTop <
|
|
994
|
-
floatingRef.current.scrollHeight - floatingRef.current.clientHeight - SCROLL_ARROW_THRESHOLD
|
|
995
|
-
|
|
996
|
-
const interactions = useInteractions([
|
|
997
|
-
useClick(context, { pointerDown: true }),
|
|
998
|
-
useRole(context, { role: 'listbox' }),
|
|
999
|
-
useDismiss(context),
|
|
1000
|
-
useListNavigation(context, {
|
|
1001
|
-
listRef: listItemsRef,
|
|
1002
|
-
activeIndex,
|
|
1003
|
-
selectedIndex,
|
|
1004
|
-
onNavigate: setActiveIndex,
|
|
1005
|
-
}),
|
|
1006
|
-
useTypeahead(context, {
|
|
1007
|
-
listRef: listContentRef,
|
|
1008
|
-
onMatch: open ? setActiveIndex : setSelectedIndex,
|
|
1009
|
-
selectedIndex,
|
|
1010
|
-
activeIndex,
|
|
1011
|
-
}),
|
|
1012
|
-
])
|
|
1013
|
-
|
|
1014
|
-
const increaseHeight = React.useCallback(
|
|
1015
|
-
(floating: HTMLElement, amount = 0) => {
|
|
1016
|
-
if (middlewareType === 'fallback') {
|
|
1017
|
-
return
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
const currentMaxHeight = Number(floating.style.maxHeight.replace('px', ''))
|
|
1021
|
-
const currentTop = Number(floating.style.top.replace('px', ''))
|
|
1022
|
-
const rect = floating.getBoundingClientRect()
|
|
1023
|
-
const rectTop = rect.top
|
|
1024
|
-
const rectBottom = rect.bottom
|
|
1025
|
-
const viewportHeight = visualViewport?.height ?? 0
|
|
1026
|
-
const visualMaxHeight = viewportHeight - WINDOW_PADDING * 2
|
|
1027
|
-
|
|
1028
|
-
if (
|
|
1029
|
-
amount < 0 &&
|
|
1030
|
-
selectedIndexRef.current != null &&
|
|
1031
|
-
Math.round(rectBottom) < Math.round(viewportHeight + getVisualOffsetTop() - WINDOW_PADDING)
|
|
1032
|
-
) {
|
|
1033
|
-
floating.style.maxHeight = `${Math.min(visualMaxHeight, currentMaxHeight - amount)}px`
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
if (
|
|
1037
|
-
amount > 0 &&
|
|
1038
|
-
Math.round(rectTop) > Math.round(WINDOW_PADDING - getVisualOffsetTop()) &&
|
|
1039
|
-
floating.scrollHeight > floating.offsetHeight
|
|
1040
|
-
) {
|
|
1041
|
-
const nextTop = Math.max(WINDOW_PADDING + getVisualOffsetTop(), currentTop - amount)
|
|
1042
|
-
|
|
1043
|
-
const nextMaxHeight = Math.min(visualMaxHeight, currentMaxHeight + amount)
|
|
1044
|
-
|
|
1045
|
-
Object.assign(floating.style, {
|
|
1046
|
-
maxHeight: `${nextMaxHeight}px`,
|
|
1047
|
-
top: `${nextTop}px`,
|
|
1048
|
-
})
|
|
1049
|
-
|
|
1050
|
-
if (nextTop - WINDOW_PADDING > getVisualOffsetTop()) {
|
|
1051
|
-
floating.scrollTop -= nextMaxHeight - currentMaxHeight + getFloatingPadding(floating)
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
return currentTop - nextTop
|
|
1055
|
-
}
|
|
1056
|
-
},
|
|
1057
|
-
[middlewareType, selectedIndexRef]
|
|
1058
|
-
)
|
|
1059
|
-
|
|
1060
|
-
const touchPageYRef = React.useRef<number | null>(null)
|
|
1061
|
-
|
|
1062
|
-
const handleWheel = React.useCallback(
|
|
1063
|
-
(event: WheelEvent | TouchEvent) => {
|
|
1064
|
-
const pinching = event.ctrlKey
|
|
1065
|
-
|
|
1066
|
-
const currentTarget = event.currentTarget as HTMLElement
|
|
1067
|
-
|
|
1068
|
-
function isWheelEvent(event: any): event is WheelEvent {
|
|
1069
|
-
return typeof event.deltaY === 'number'
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
function isTouchEvent(event: any): event is TouchEvent {
|
|
1073
|
-
return event.touches != null
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
if (
|
|
1077
|
-
Math.abs(
|
|
1078
|
-
(currentTarget?.offsetHeight ?? 0) - ((visualViewport?.height ?? 0) - WINDOW_PADDING * 2)
|
|
1079
|
-
) > 1 &&
|
|
1080
|
-
!pinching
|
|
1081
|
-
) {
|
|
1082
|
-
event.preventDefault()
|
|
1083
|
-
} else if (isWheelEvent(event) && isFirefox) {
|
|
1084
|
-
// Firefox needs this to propagate scrolling
|
|
1085
|
-
// during momentum scrolling phase if the
|
|
1086
|
-
// height reached its maximum (at boundaries)
|
|
1087
|
-
currentTarget.scrollTop += event.deltaY
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
if (!pinching) {
|
|
1091
|
-
let delta = 5
|
|
1092
|
-
|
|
1093
|
-
if (isTouchEvent(event)) {
|
|
1094
|
-
const currentPageY = touchPageYRef.current
|
|
1095
|
-
const pageY = event.touches[0]?.pageY
|
|
1096
|
-
|
|
1097
|
-
if (pageY != null) {
|
|
1098
|
-
touchPageYRef.current = pageY
|
|
1099
|
-
|
|
1100
|
-
if (currentPageY != null) {
|
|
1101
|
-
delta = currentPageY - pageY
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
increaseHeight(currentTarget, isWheelEvent(event) ? event.deltaY : delta)
|
|
1107
|
-
setScrollTop(currentTarget.scrollTop)
|
|
1108
|
-
// Ensure derived data (scroll arrows) is fresh
|
|
1109
|
-
forceUpdate()
|
|
1110
|
-
}
|
|
1111
|
-
},
|
|
1112
|
-
[forceUpdate, increaseHeight]
|
|
1113
|
-
)
|
|
1114
|
-
|
|
1115
|
-
// Handle `onWheel` event in an effect to remove the `passive` option so we
|
|
1116
|
-
// can .preventDefault() it
|
|
1117
|
-
React.useEffect(() => {
|
|
1118
|
-
function onTouchEnd() {
|
|
1119
|
-
touchPageYRef.current = null
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
const floating = floatingRef.current
|
|
1123
|
-
if (open && floating && middlewareType === 'align') {
|
|
1124
|
-
floating.addEventListener('wheel', handleWheel)
|
|
1125
|
-
floating.addEventListener('touchmove', handleWheel)
|
|
1126
|
-
floating.addEventListener('touchend', onTouchEnd, { passive: true })
|
|
1127
|
-
return () => {
|
|
1128
|
-
floating.removeEventListener('wheel', handleWheel)
|
|
1129
|
-
floating.removeEventListener('touchmove', handleWheel)
|
|
1130
|
-
floating.removeEventListener('touchend', onTouchEnd)
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
}, [open, floatingRef, handleWheel, middlewareType])
|
|
1134
|
-
|
|
1135
|
-
// Ensure the menu remains attached to the reference element when resizing.
|
|
1136
|
-
React.useEffect(() => {
|
|
1137
|
-
window.addEventListener('resize', update)
|
|
1138
|
-
return () => {
|
|
1139
|
-
window.removeEventListener('resize', update)
|
|
1140
|
-
}
|
|
1141
|
-
}, [update])
|
|
1142
|
-
|
|
1143
|
-
// Scroll the active or selected item into view when in `controlledScrolling`
|
|
1144
|
-
// mode (i.e. arrow key nav).
|
|
1145
|
-
React.useLayoutEffect(() => {
|
|
1146
|
-
const floating = floatingRef.current
|
|
1147
|
-
|
|
1148
|
-
if (open && controlledScrolling && floating) {
|
|
1149
|
-
const item =
|
|
1150
|
-
activeIndex != null
|
|
1151
|
-
? listItemsRef.current[activeIndex]
|
|
1152
|
-
: selectedIndex != null
|
|
1153
|
-
? listItemsRef.current[selectedIndex]
|
|
1154
|
-
: null
|
|
1155
|
-
|
|
1156
|
-
if (item && prevActiveIndex != null) {
|
|
1157
|
-
const itemHeight = listItemsRef.current[prevActiveIndex]?.offsetHeight ?? 0
|
|
1158
|
-
|
|
1159
|
-
const floatingHeight = floating.offsetHeight
|
|
1160
|
-
const top = item.offsetTop
|
|
1161
|
-
const bottom = top + itemHeight
|
|
1162
|
-
|
|
1163
|
-
if (top < floating.scrollTop + 20) {
|
|
1164
|
-
const diff = floating.scrollTop - top + 20
|
|
1165
|
-
floating.scrollTop -= diff
|
|
1166
|
-
|
|
1167
|
-
if (activeIndex != selectedIndex && activeIndex != null) {
|
|
1168
|
-
increaseHeight(floating, -diff)
|
|
1169
|
-
}
|
|
1170
|
-
} else if (bottom > floatingHeight + floating.scrollTop - 20) {
|
|
1171
|
-
const diff = bottom - floatingHeight - floating.scrollTop + 20
|
|
1172
|
-
|
|
1173
|
-
floating.scrollTop += diff
|
|
1174
|
-
|
|
1175
|
-
if (activeIndex != selectedIndex && activeIndex != null) {
|
|
1176
|
-
floating.scrollTop -= increaseHeight(floating, diff) ?? 0
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
}, [
|
|
1182
|
-
open,
|
|
1183
|
-
controlledScrolling,
|
|
1184
|
-
prevActiveIndex,
|
|
1185
|
-
activeIndex,
|
|
1186
|
-
selectedIndex,
|
|
1187
|
-
floatingRef,
|
|
1188
|
-
increaseHeight,
|
|
1189
|
-
])
|
|
1190
|
-
|
|
1191
|
-
// Sync the height and the scrollTop values and device whether to use fallback
|
|
1192
|
-
// positioning.
|
|
1193
|
-
React.useLayoutEffect(() => {
|
|
1194
|
-
const floating = refs.floating.current
|
|
1195
|
-
const reference = refs.reference.current
|
|
1196
|
-
|
|
1197
|
-
if (open && floating && reference && floating.offsetHeight < floating.scrollHeight) {
|
|
1198
|
-
const referenceRect = reference.getBoundingClientRect()
|
|
1199
|
-
|
|
1200
|
-
if (middlewareType === 'fallback') {
|
|
1201
|
-
const item = listItemsRef.current[selectedIndex]
|
|
1202
|
-
if (item) {
|
|
1203
|
-
floating.scrollTop = item.offsetTop - floating.clientHeight + referenceRect.height
|
|
1204
|
-
}
|
|
1205
|
-
return
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
floating.scrollTop = middlewareData.size?.y
|
|
1209
|
-
|
|
1210
|
-
const closeToBottom =
|
|
1211
|
-
(visualViewport?.height ?? 0) + getVisualOffsetTop() - referenceRect.bottom <
|
|
1212
|
-
FALLBACK_THRESHOLD
|
|
1213
|
-
const closeToTop = referenceRect.top < FALLBACK_THRESHOLD
|
|
1214
|
-
|
|
1215
|
-
if (floating.offsetHeight < MIN_HEIGHT || closeToTop || closeToBottom) {
|
|
1216
|
-
setMiddlewareType('fallback')
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
}, [
|
|
1220
|
-
open,
|
|
1221
|
-
increaseHeight,
|
|
1222
|
-
selectedIndex,
|
|
1223
|
-
middlewareType,
|
|
1224
|
-
refs.floating,
|
|
1225
|
-
refs.reference,
|
|
1226
|
-
// Always re-run this effect when the position has been computed so the
|
|
1227
|
-
// .scrollTop change works with fresh sizing.
|
|
1228
|
-
middlewareData,
|
|
1229
|
-
])
|
|
1230
|
-
|
|
1231
|
-
React.useLayoutEffect(() => {
|
|
1232
|
-
if (open && selectedIndex != null) {
|
|
1233
|
-
requestAnimationFrame(() => {
|
|
1234
|
-
listItemsRef.current[selectedIndex]?.focus({ preventScroll: true })
|
|
1235
|
-
})
|
|
1236
|
-
}
|
|
1237
|
-
}, [listItemsRef, selectedIndex, open])
|
|
1238
|
-
|
|
1239
|
-
// Wait for scroll position to settle before showing arrows to prevent
|
|
1240
|
-
// interference with pointer events.
|
|
1241
|
-
React.useEffect(() => {
|
|
1242
|
-
const frame = requestAnimationFrame(() => {
|
|
1243
|
-
setShowArrows(open)
|
|
1244
|
-
|
|
1245
|
-
if (!open) {
|
|
1246
|
-
setScrollTop(0)
|
|
1247
|
-
setMiddlewareType('align')
|
|
1248
|
-
setActiveIndex(null)
|
|
1249
|
-
setControlledScrolling(false)
|
|
1250
|
-
}
|
|
1251
|
-
})
|
|
1252
|
-
return () => cancelAnimationFrame(frame)
|
|
1253
|
-
}, [open])
|
|
1254
|
-
|
|
1255
|
-
// We set this to true by default so that events bubble to forms without JS (SSR)
|
|
1256
|
-
// const isFormControl = trigger ? Boolean(trigger.closest('form')) : true
|
|
1257
|
-
// const [bubbleSelect, setBubbleSelect] = React.useState<HTMLSelectElement | null>(null)
|
|
1258
|
-
// const triggerPointerDownPosRef = React.useRef<{ x: number; y: number } | null>(null)
|
|
1259
|
-
|
|
1260
|
-
return (
|
|
1261
|
-
<SelectProvider
|
|
1262
|
-
scope={__scopeSelect}
|
|
1263
|
-
{...(selectContext as Required<typeof selectContext>)}
|
|
1264
|
-
increaseHeight={increaseHeight}
|
|
1265
|
-
floatingRef={floatingRef}
|
|
1266
|
-
setValueAtIndex={(index, value) => {
|
|
1267
|
-
listContentRef.current[index] = value
|
|
1268
|
-
}}
|
|
1269
|
-
interactions={{
|
|
1270
|
-
...interactions,
|
|
1271
|
-
getReferenceProps() {
|
|
1272
|
-
return interactions.getReferenceProps({
|
|
1273
|
-
ref: reference,
|
|
1274
|
-
className: 'SelectTrigger',
|
|
1275
|
-
onKeyDown(event) {
|
|
1276
|
-
if (event.key === 'Enter' || (event.key === ' ' && !context.dataRef.current.typing)) {
|
|
1277
|
-
event.preventDefault()
|
|
1278
|
-
setOpen(true)
|
|
1279
|
-
}
|
|
1280
|
-
},
|
|
1281
|
-
})
|
|
1282
|
-
},
|
|
1283
|
-
getFloatingProps(props) {
|
|
1284
|
-
return interactions.getFloatingProps({
|
|
1285
|
-
ref: floating,
|
|
1286
|
-
className: 'Select',
|
|
1287
|
-
...props,
|
|
1288
|
-
style: {
|
|
1289
|
-
position: strategy,
|
|
1290
|
-
top: y ?? '',
|
|
1291
|
-
left: x ?? '',
|
|
1292
|
-
outline: 0,
|
|
1293
|
-
listStyleType: 'none',
|
|
1294
|
-
scrollbarWidth: 'none',
|
|
1295
|
-
userSelect: 'none',
|
|
1296
|
-
...props?.style,
|
|
1297
|
-
},
|
|
1298
|
-
onPointerEnter() {
|
|
1299
|
-
setControlledScrolling(false)
|
|
1300
|
-
},
|
|
1301
|
-
onPointerMove() {
|
|
1302
|
-
setControlledScrolling(false)
|
|
1303
|
-
},
|
|
1304
|
-
onKeyDown() {
|
|
1305
|
-
setControlledScrolling(true)
|
|
1306
|
-
},
|
|
1307
|
-
onScroll(event) {
|
|
1308
|
-
setScrollTop(event.currentTarget.scrollTop)
|
|
1309
|
-
},
|
|
1310
|
-
})
|
|
1311
|
-
},
|
|
1312
|
-
}}
|
|
1313
|
-
floatingContext={context}
|
|
1314
|
-
activeIndex={activeIndex}
|
|
1315
|
-
canScrollDown={!!showDownArrow}
|
|
1316
|
-
canScrollUp={!!showUpArrow}
|
|
1317
|
-
controlledScrolling
|
|
1318
|
-
dataRef={context.dataRef}
|
|
1319
|
-
listRef={listItemsRef}
|
|
1320
|
-
>
|
|
1321
|
-
{children}
|
|
1322
|
-
{/* {isFormControl ? (
|
|
1323
|
-
<BubbleSelect
|
|
1324
|
-
ref={setBubbleSelect}
|
|
1325
|
-
aria-hidden
|
|
1326
|
-
tabIndex={-1}
|
|
1327
|
-
name={name}
|
|
1328
|
-
autoComplete={autoComplete}
|
|
1329
|
-
value={value}
|
|
1330
|
-
// enable form autofill
|
|
1331
|
-
onChange={(event) => setValue(event.target.value)}
|
|
1332
|
-
/>
|
|
1333
|
-
) : null} */}
|
|
1334
|
-
</SelectProvider>
|
|
1335
|
-
)
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
509
|
const SelectSheetImpl = (props: SelectImplProps) => {
|
|
1339
510
|
return <>{props.children}</>
|
|
1340
511
|
}
|
|
1341
512
|
|
|
513
|
+
/* -------------------------------------------------------------------------------------------------
|
|
514
|
+
* Select
|
|
515
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
516
|
+
|
|
1342
517
|
export const Select = withStaticProperties(
|
|
1343
518
|
(props: ScopedProps<SelectProps>) => {
|
|
1344
519
|
const {
|
|
@@ -1350,7 +525,7 @@ export const Select = withStaticProperties(
|
|
|
1350
525
|
value: valueProp,
|
|
1351
526
|
defaultValue,
|
|
1352
527
|
onValueChange,
|
|
1353
|
-
size: sizeProp,
|
|
528
|
+
size: sizeProp = '$4',
|
|
1354
529
|
sheetBreakpoint = false,
|
|
1355
530
|
dir,
|
|
1356
531
|
} = props
|
|
@@ -1358,6 +533,7 @@ export const Select = withStaticProperties(
|
|
|
1358
533
|
const isSheet = useSelectBreakpointActive(sheetBreakpoint)
|
|
1359
534
|
const SelectImpl = isSheet ? SelectSheetImpl : SelectInlineImpl
|
|
1360
535
|
const forceUpdate = React.useReducer(() => ({}), {})[1]
|
|
536
|
+
const [selectedItem, setSelectedItem] = React.useState<React.ReactNode>(null)
|
|
1361
537
|
|
|
1362
538
|
const [open, setOpen] = useControllableState({
|
|
1363
539
|
prop: openProp,
|
|
@@ -1393,6 +569,8 @@ export const Select = withStaticProperties(
|
|
|
1393
569
|
<SelectProvider
|
|
1394
570
|
dir={dir}
|
|
1395
571
|
size={sizeProp}
|
|
572
|
+
selectedItem={selectedItem}
|
|
573
|
+
setSelectedItem={setSelectedItem}
|
|
1396
574
|
forceUpdate={forceUpdate}
|
|
1397
575
|
valueNode={valueNode}
|
|
1398
576
|
onValueNodeChange={setValueNode}
|
|
@@ -1448,5 +626,3 @@ export const Select = withStaticProperties(
|
|
|
1448
626
|
|
|
1449
627
|
// @ts-ignore
|
|
1450
628
|
Select.displayName = SELECT_NAME
|
|
1451
|
-
|
|
1452
|
-
export { createSelectScope }
|