@tamagui/select 1.0.1-beta.113 → 1.0.1-beta.116
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 +61 -567
- 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 +1 -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 +44 -574
- 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 +1 -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 +43 -516
- 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 +1 -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 +69 -890
- 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 +83 -241
- 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
|
* -----------------------------------------------------------------------------------------------*/
|
|
@@ -131,6 +35,17 @@ const TRIGGER_NAME = 'SelectTrigger'
|
|
|
131
35
|
|
|
132
36
|
export type SelectTriggerProps = ListItemProps
|
|
133
37
|
|
|
38
|
+
const SelectTriggerFrame = styled(ListItem, {
|
|
39
|
+
componentName: TRIGGER_NAME,
|
|
40
|
+
backgrounded: true,
|
|
41
|
+
radiused: true,
|
|
42
|
+
hoverTheme: true,
|
|
43
|
+
pressTheme: true,
|
|
44
|
+
focusTheme: true,
|
|
45
|
+
focusable: false,
|
|
46
|
+
borderWidth: 1,
|
|
47
|
+
})
|
|
48
|
+
|
|
134
49
|
export const SelectTrigger = React.forwardRef<TamaguiElement, SelectTriggerProps>(
|
|
135
50
|
(props: ScopedProps<SelectTriggerProps>, forwardedRef) => {
|
|
136
51
|
const {
|
|
@@ -147,15 +62,7 @@ export const SelectTrigger = React.forwardRef<TamaguiElement, SelectTriggerProps
|
|
|
147
62
|
const labelledBy = ariaLabelledby // || labelId
|
|
148
63
|
|
|
149
64
|
return (
|
|
150
|
-
<
|
|
151
|
-
backgrounded
|
|
152
|
-
radiused
|
|
153
|
-
hoverTheme
|
|
154
|
-
pressTheme
|
|
155
|
-
focusTheme
|
|
156
|
-
focusable={false}
|
|
157
|
-
borderWidth={1}
|
|
158
|
-
componentName={TRIGGER_NAME}
|
|
65
|
+
<SelectTriggerFrame
|
|
159
66
|
size={context.size}
|
|
160
67
|
// aria-controls={context.contentId}
|
|
161
68
|
aria-expanded={context.open}
|
|
@@ -197,17 +104,22 @@ type SelectValueProps = GetProps<typeof SelectValueFrame> & {
|
|
|
197
104
|
|
|
198
105
|
const SelectValue = SelectValueFrame.extractable(
|
|
199
106
|
React.forwardRef<TamaguiElement, SelectValueProps>(
|
|
200
|
-
(
|
|
107
|
+
(
|
|
108
|
+
{ __scopeSelect, children: childrenProp, placeholder }: ScopedProps<SelectValueProps>,
|
|
109
|
+
forwardedRef
|
|
110
|
+
) => {
|
|
201
111
|
// We ignore `className` and `style` as this part shouldn't be styled.
|
|
202
112
|
const context = useSelectContext(VALUE_NAME, __scopeSelect)
|
|
203
113
|
const { onValueNodeHasChildrenChange } = context
|
|
204
|
-
const hasChildren =
|
|
114
|
+
const hasChildren = childrenProp !== undefined
|
|
205
115
|
const composedRefs = useComposedRefs(forwardedRef, context.onValueNodeChange)
|
|
206
116
|
|
|
207
117
|
React.useLayoutEffect(() => {
|
|
208
118
|
onValueNodeHasChildrenChange(hasChildren)
|
|
209
119
|
}, [onValueNodeHasChildrenChange, hasChildren])
|
|
210
120
|
|
|
121
|
+
const children = childrenProp ?? context.selectedItem
|
|
122
|
+
|
|
211
123
|
return (
|
|
212
124
|
<SelectValueFrame
|
|
213
125
|
size={context.size}
|
|
@@ -236,115 +148,6 @@ export const SelectIcon = styled(XStack, {
|
|
|
236
148
|
children: <Paragraph>▼</Paragraph>,
|
|
237
149
|
})
|
|
238
150
|
|
|
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
151
|
/* -------------------------------------------------------------------------------------------------
|
|
349
152
|
* SelectItem
|
|
350
153
|
* -----------------------------------------------------------------------------------------------*/
|
|
@@ -368,8 +171,6 @@ export interface SelectItemProps extends YStackProps {
|
|
|
368
171
|
textValue?: string
|
|
369
172
|
}
|
|
370
173
|
|
|
371
|
-
// TODO styled(ListItem, { name: 'SelectItem' })
|
|
372
|
-
|
|
373
174
|
export const SelectItem = React.forwardRef<TamaguiElement, SelectItemProps>(
|
|
374
175
|
(props: ScopedProps<SelectItemProps>, forwardedRef) => {
|
|
375
176
|
const {
|
|
@@ -402,6 +203,7 @@ export const SelectItem = React.forwardRef<TamaguiElement, SelectItemProps>(
|
|
|
402
203
|
const composedRefs = useComposedRefs(
|
|
403
204
|
forwardedRef,
|
|
404
205
|
(node) => {
|
|
206
|
+
if (!isWeb) return
|
|
405
207
|
if (node instanceof HTMLElement) {
|
|
406
208
|
if (listRef) {
|
|
407
209
|
listRef.current[index] = node
|
|
@@ -511,20 +313,35 @@ const SelectItemText = React.forwardRef<TamaguiElement, SelectItemTextProps>(
|
|
|
511
313
|
const itemContext = useSelectItemContext(ITEM_TEXT_NAME, __scopeSelect)
|
|
512
314
|
const ref = React.useRef<TamaguiElement | null>(null)
|
|
513
315
|
const composedRefs = useComposedRefs(forwardedRef, ref, itemContext.onItemTextChange)
|
|
316
|
+
const isSelected = itemContext.isSelected && context.valueNode && !context.valueNodeHasChildren
|
|
317
|
+
|
|
318
|
+
const contents = (
|
|
319
|
+
<SelectItemTextFrame
|
|
320
|
+
className={className}
|
|
321
|
+
size={context.size}
|
|
322
|
+
id={itemContext.textId}
|
|
323
|
+
{...itemTextProps}
|
|
324
|
+
ref={composedRefs}
|
|
325
|
+
/>
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
// until portals work in sub-trees on RN, use this just for native:
|
|
329
|
+
if (!isWeb) {
|
|
330
|
+
React.useEffect(() => {
|
|
331
|
+
if (isSelected) {
|
|
332
|
+
context.setSelectedItem(contents)
|
|
333
|
+
}
|
|
334
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
335
|
+
}, [isSelected])
|
|
336
|
+
}
|
|
514
337
|
|
|
515
338
|
return (
|
|
516
339
|
<>
|
|
517
|
-
|
|
518
|
-
className={className}
|
|
519
|
-
size={context.size}
|
|
520
|
-
id={itemContext.textId}
|
|
521
|
-
{...itemTextProps}
|
|
522
|
-
ref={composedRefs}
|
|
523
|
-
/>
|
|
340
|
+
{contents}
|
|
524
341
|
|
|
525
342
|
{/* Portal the select item text into the trigger value node */}
|
|
526
|
-
{
|
|
527
|
-
? ReactDOM.createPortal(itemTextProps.children, context.valueNode)
|
|
343
|
+
{isWeb && isSelected
|
|
344
|
+
? ReactDOM.createPortal(itemTextProps.children, context.valueNode!)
|
|
528
345
|
: null}
|
|
529
346
|
|
|
530
347
|
{/* Portal an option in the bubble select */}
|
|
@@ -633,154 +450,6 @@ const SelectLabel = React.forwardRef<TamaguiElement, SelectLabelProps>(
|
|
|
633
450
|
|
|
634
451
|
SelectLabel.displayName = LABEL_NAME
|
|
635
452
|
|
|
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
453
|
/* -------------------------------------------------------------------------------------------------
|
|
785
454
|
* SelectSeparator
|
|
786
455
|
* -----------------------------------------------------------------------------------------------*/
|
|
@@ -789,12 +458,14 @@ export const SelectSeparator = styled(Separator, {
|
|
|
789
458
|
name: 'SelectSeparator',
|
|
790
459
|
})
|
|
791
460
|
|
|
792
|
-
const useSelectBreakpointActive = (
|
|
461
|
+
export const useSelectBreakpointActive = (
|
|
462
|
+
sheetBreakpoint: SelectContextValue['sheetBreakpoint']
|
|
463
|
+
) => {
|
|
793
464
|
const media = useMedia()
|
|
794
465
|
return sheetBreakpoint ? media[sheetBreakpoint] : false
|
|
795
466
|
}
|
|
796
467
|
|
|
797
|
-
const useShowSelectSheet = (context: SelectContextValue) => {
|
|
468
|
+
export const useShowSelectSheet = (context: SelectContextValue) => {
|
|
798
469
|
const breakpointActive = useSelectBreakpointActive(context.sheetBreakpoint)
|
|
799
470
|
return context.open === false ? false : breakpointActive
|
|
800
471
|
}
|
|
@@ -838,507 +509,14 @@ export const SelectSheetContents = ({ __scopeSelect }: ScopedProps<{}>) => {
|
|
|
838
509
|
|
|
839
510
|
SelectSheetContents.displayName = SHEET_CONTENTS_NAME
|
|
840
511
|
|
|
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
512
|
const SelectSheetImpl = (props: SelectImplProps) => {
|
|
1339
513
|
return <>{props.children}</>
|
|
1340
514
|
}
|
|
1341
515
|
|
|
516
|
+
/* -------------------------------------------------------------------------------------------------
|
|
517
|
+
* Select
|
|
518
|
+
* -----------------------------------------------------------------------------------------------*/
|
|
519
|
+
|
|
1342
520
|
export const Select = withStaticProperties(
|
|
1343
521
|
(props: ScopedProps<SelectProps>) => {
|
|
1344
522
|
const {
|
|
@@ -1350,7 +528,7 @@ export const Select = withStaticProperties(
|
|
|
1350
528
|
value: valueProp,
|
|
1351
529
|
defaultValue,
|
|
1352
530
|
onValueChange,
|
|
1353
|
-
size: sizeProp,
|
|
531
|
+
size: sizeProp = '$4',
|
|
1354
532
|
sheetBreakpoint = false,
|
|
1355
533
|
dir,
|
|
1356
534
|
} = props
|
|
@@ -1358,6 +536,7 @@ export const Select = withStaticProperties(
|
|
|
1358
536
|
const isSheet = useSelectBreakpointActive(sheetBreakpoint)
|
|
1359
537
|
const SelectImpl = isSheet ? SelectSheetImpl : SelectInlineImpl
|
|
1360
538
|
const forceUpdate = React.useReducer(() => ({}), {})[1]
|
|
539
|
+
const [selectedItem, setSelectedItem] = React.useState<React.ReactNode>(null)
|
|
1361
540
|
|
|
1362
541
|
const [open, setOpen] = useControllableState({
|
|
1363
542
|
prop: openProp,
|
|
@@ -1393,6 +572,8 @@ export const Select = withStaticProperties(
|
|
|
1393
572
|
<SelectProvider
|
|
1394
573
|
dir={dir}
|
|
1395
574
|
size={sizeProp}
|
|
575
|
+
selectedItem={selectedItem}
|
|
576
|
+
setSelectedItem={setSelectedItem}
|
|
1396
577
|
forceUpdate={forceUpdate}
|
|
1397
578
|
valueNode={valueNode}
|
|
1398
579
|
onValueNodeChange={setValueNode}
|
|
@@ -1448,5 +629,3 @@ export const Select = withStaticProperties(
|
|
|
1448
629
|
|
|
1449
630
|
// @ts-ignore
|
|
1450
631
|
Select.displayName = SELECT_NAME
|
|
1451
|
-
|
|
1452
|
-
export { createSelectScope }
|