@tamagui/select 2.7.6 → 3.0.0-beta.637.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Select.cjs +409 -467
- package/dist/cjs/Select.native.cjs +495 -0
- package/dist/cjs/Select.native.js +445 -492
- package/dist/cjs/Select.native.js.map +1 -1
- package/dist/cjs/SelectContent.cjs +80 -79
- package/dist/cjs/SelectContent.native.cjs +30 -0
- package/dist/cjs/SelectContent.native.js +18 -22
- package/dist/cjs/SelectContent.native.js.map +1 -1
- package/dist/cjs/SelectImpl.cjs +299 -319
- package/dist/cjs/SelectImpl.native.cjs +30 -0
- package/dist/cjs/SelectImpl.native.js +17 -24
- package/dist/cjs/SelectImpl.native.js.map +1 -1
- package/dist/cjs/SelectItem.cjs +268 -205
- package/dist/cjs/SelectItem.native.cjs +317 -0
- package/dist/cjs/SelectItem.native.js +291 -211
- package/dist/cjs/SelectItem.native.js.map +1 -1
- package/dist/cjs/SelectItemText.cjs +49 -79
- package/dist/cjs/SelectItemText.native.cjs +74 -0
- package/dist/cjs/SelectItemText.native.js +50 -79
- package/dist/cjs/SelectItemText.native.js.map +1 -1
- package/dist/cjs/SelectScrollButton.cjs +111 -130
- package/dist/cjs/SelectScrollButton.native.cjs +38 -0
- package/dist/cjs/SelectScrollButton.native.js +25 -22
- package/dist/cjs/SelectScrollButton.native.js.map +1 -1
- package/dist/cjs/SelectTrigger.cjs +69 -84
- package/dist/cjs/SelectTrigger.native.cjs +91 -0
- package/dist/cjs/SelectTrigger.native.js +71 -66
- package/dist/cjs/SelectTrigger.native.js.map +1 -1
- package/dist/cjs/SelectViewport.cjs +119 -142
- package/dist/cjs/SelectViewport.native.cjs +73 -0
- package/dist/cjs/SelectViewport.native.js +54 -50
- package/dist/cjs/SelectViewport.native.js.map +1 -1
- package/dist/cjs/constants.cjs +21 -22
- package/dist/cjs/constants.native.cjs +41 -0
- package/dist/cjs/constants.native.js +21 -21
- package/dist/cjs/constants.native.js.map +1 -1
- package/dist/cjs/context.cjs +38 -48
- package/dist/cjs/context.native.cjs +58 -0
- package/dist/cjs/context.native.js +40 -49
- package/dist/cjs/context.native.js.map +1 -1
- package/dist/cjs/index.cjs +15 -11
- package/dist/cjs/index.native.cjs +27 -0
- package/dist/cjs/index.native.js +15 -10
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/selectionController.cjs +334 -0
- package/dist/cjs/selectionController.native.cjs +403 -0
- package/dist/cjs/selectionController.native.js +405 -0
- package/dist/cjs/selectionController.native.js.map +1 -0
- package/dist/cjs/types.cjs +10 -11
- package/dist/cjs/types.native.cjs +19 -0
- package/dist/cjs/types.native.js +10 -10
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/esm/Select.mjs +382 -443
- package/dist/esm/Select.mjs.map +1 -1
- package/dist/esm/Select.native.js +418 -469
- package/dist/esm/Select.native.js.map +1 -1
- package/dist/esm/SelectContent.mjs +67 -61
- package/dist/esm/SelectContent.mjs.map +1 -1
- package/dist/esm/SelectContent.native.js +4 -5
- package/dist/esm/SelectContent.native.js.map +1 -1
- package/dist/esm/SelectImpl.mjs +284 -294
- package/dist/esm/SelectImpl.mjs.map +1 -1
- package/dist/esm/SelectImpl.native.js +5 -8
- package/dist/esm/SelectImpl.native.js.map +1 -1
- package/dist/esm/SelectItem.mjs +252 -182
- package/dist/esm/SelectItem.mjs.map +1 -1
- package/dist/esm/SelectItem.native.js +276 -190
- package/dist/esm/SelectItem.native.js.map +1 -1
- package/dist/esm/SelectItemText.mjs +33 -55
- package/dist/esm/SelectItemText.mjs.map +1 -1
- package/dist/esm/SelectItemText.native.js +34 -56
- package/dist/esm/SelectItemText.native.js.map +1 -1
- package/dist/esm/SelectScrollButton.mjs +95 -107
- package/dist/esm/SelectScrollButton.mjs.map +1 -1
- package/dist/esm/SelectScrollButton.native.js +11 -7
- package/dist/esm/SelectScrollButton.native.js.map +1 -1
- package/dist/esm/SelectTrigger.mjs +56 -58
- package/dist/esm/SelectTrigger.mjs.map +1 -1
- package/dist/esm/SelectTrigger.native.js +54 -44
- package/dist/esm/SelectTrigger.native.js.map +1 -1
- package/dist/esm/SelectViewport.mjs +104 -119
- package/dist/esm/SelectViewport.mjs.map +1 -1
- package/dist/esm/SelectViewport.native.js +44 -39
- package/dist/esm/SelectViewport.native.js.map +1 -1
- package/dist/esm/constants.mjs +1 -0
- package/dist/esm/constants.mjs.map +1 -1
- package/dist/esm/constants.native.js +1 -0
- package/dist/esm/constants.native.js.map +1 -1
- package/dist/esm/context.mjs +20 -28
- package/dist/esm/context.mjs.map +1 -1
- package/dist/esm/context.native.js +23 -31
- package/dist/esm/context.native.js.map +1 -1
- package/dist/esm/index.js +13 -3
- package/dist/esm/index.mjs +13 -3
- package/dist/esm/index.native.js +13 -3
- package/dist/esm/selectionController.mjs +305 -0
- package/dist/esm/selectionController.mjs.map +1 -0
- package/dist/esm/selectionController.native.js +372 -0
- package/dist/esm/selectionController.native.js.map +1 -0
- package/dist/esm/types.mjs +0 -2
- package/dist/esm/types.native.js +0 -2
- package/dist/jsx/Select.mjs +382 -443
- package/dist/jsx/Select.mjs.map +1 -1
- package/dist/jsx/Select.native.cjs +495 -0
- package/dist/jsx/Select.native.js +445 -492
- package/dist/jsx/Select.native.js.map +1 -1
- package/dist/jsx/SelectContent.mjs +67 -61
- package/dist/jsx/SelectContent.mjs.map +1 -1
- package/dist/jsx/SelectContent.native.cjs +30 -0
- package/dist/jsx/SelectContent.native.js +18 -22
- package/dist/jsx/SelectContent.native.js.map +1 -1
- package/dist/jsx/SelectImpl.mjs +284 -294
- package/dist/jsx/SelectImpl.mjs.map +1 -1
- package/dist/jsx/SelectImpl.native.cjs +30 -0
- package/dist/jsx/SelectImpl.native.js +17 -24
- package/dist/jsx/SelectImpl.native.js.map +1 -1
- package/dist/jsx/SelectItem.mjs +252 -182
- package/dist/jsx/SelectItem.mjs.map +1 -1
- package/dist/jsx/SelectItem.native.cjs +317 -0
- package/dist/jsx/SelectItem.native.js +291 -211
- package/dist/jsx/SelectItem.native.js.map +1 -1
- package/dist/jsx/SelectItemText.mjs +33 -55
- package/dist/jsx/SelectItemText.mjs.map +1 -1
- package/dist/jsx/SelectItemText.native.cjs +74 -0
- package/dist/jsx/SelectItemText.native.js +50 -79
- package/dist/jsx/SelectItemText.native.js.map +1 -1
- package/dist/jsx/SelectScrollButton.mjs +95 -107
- package/dist/jsx/SelectScrollButton.mjs.map +1 -1
- package/dist/jsx/SelectScrollButton.native.cjs +38 -0
- package/dist/jsx/SelectScrollButton.native.js +25 -22
- package/dist/jsx/SelectScrollButton.native.js.map +1 -1
- package/dist/jsx/SelectTrigger.mjs +56 -58
- package/dist/jsx/SelectTrigger.mjs.map +1 -1
- package/dist/jsx/SelectTrigger.native.cjs +91 -0
- package/dist/jsx/SelectTrigger.native.js +71 -66
- package/dist/jsx/SelectTrigger.native.js.map +1 -1
- package/dist/jsx/SelectViewport.mjs +104 -119
- package/dist/jsx/SelectViewport.mjs.map +1 -1
- package/dist/jsx/SelectViewport.native.cjs +73 -0
- package/dist/jsx/SelectViewport.native.js +54 -50
- package/dist/jsx/SelectViewport.native.js.map +1 -1
- package/dist/jsx/constants.mjs +1 -0
- package/dist/jsx/constants.mjs.map +1 -1
- package/dist/jsx/constants.native.cjs +41 -0
- package/dist/jsx/constants.native.js +21 -21
- package/dist/jsx/constants.native.js.map +1 -1
- package/dist/jsx/context.mjs +20 -28
- package/dist/jsx/context.mjs.map +1 -1
- package/dist/jsx/context.native.cjs +58 -0
- package/dist/jsx/context.native.js +40 -49
- package/dist/jsx/context.native.js.map +1 -1
- package/dist/jsx/index.js +13 -3
- package/dist/jsx/index.mjs +13 -3
- package/dist/jsx/index.native.cjs +27 -0
- package/dist/jsx/index.native.js +15 -10
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/selectionController.mjs +305 -0
- package/dist/jsx/selectionController.mjs.map +1 -0
- package/dist/jsx/selectionController.native.cjs +403 -0
- package/dist/jsx/selectionController.native.js +405 -0
- package/dist/jsx/selectionController.native.js.map +1 -0
- package/dist/jsx/types.mjs +0 -2
- package/dist/jsx/types.native.cjs +19 -0
- package/dist/jsx/types.native.js +10 -10
- package/dist/jsx/types.native.js.map +1 -1
- package/package.json +28 -28
- package/src/Select.tsx +457 -357
- package/src/SelectContent.tsx +46 -16
- package/src/SelectImpl.native.tsx +0 -8
- package/src/SelectImpl.tsx +87 -100
- package/src/SelectItem.tsx +282 -136
- package/src/SelectItemText.tsx +16 -33
- package/src/SelectScrollButton.native.tsx +13 -4
- package/src/SelectScrollButton.tsx +56 -41
- package/src/SelectTrigger.tsx +77 -53
- package/src/SelectViewport.native.tsx +42 -25
- package/src/SelectViewport.tsx +52 -51
- package/src/context.tsx +6 -2
- package/src/index.tsx +5 -2
- package/src/selectionController.ts +389 -0
- package/src/types.tsx +88 -45
- package/types/Select.d.ts +139 -201
- package/types/Select.d.ts.map +1 -1
- package/types/SelectContent.d.ts +1 -1
- package/types/SelectContent.d.ts.map +1 -1
- package/types/SelectContent.native.d.ts.map +1 -1
- package/types/SelectImpl.d.ts +2 -1
- package/types/SelectImpl.d.ts.map +1 -1
- package/types/SelectImpl.native.d.ts +1 -5
- package/types/SelectImpl.native.d.ts.map +1 -1
- package/types/SelectItem.d.ts +13 -16
- package/types/SelectItem.d.ts.map +1 -1
- package/types/SelectItemText.d.ts +9 -13
- package/types/SelectItemText.d.ts.map +1 -1
- package/types/SelectScrollButton.d.ts +15 -2
- package/types/SelectScrollButton.d.ts.map +1 -1
- package/types/SelectScrollButton.native.d.ts +16 -3
- package/types/SelectScrollButton.native.d.ts.map +1 -1
- package/types/SelectTrigger.d.ts +11 -23
- package/types/SelectTrigger.d.ts.map +1 -1
- package/types/SelectViewport.d.ts +11 -38
- package/types/SelectViewport.d.ts.map +1 -1
- package/types/SelectViewport.native.d.ts +15 -4
- package/types/SelectViewport.native.d.ts.map +1 -1
- package/types/context.d.ts +2 -0
- package/types/context.d.ts.map +1 -1
- package/types/index.d.ts +5 -2
- package/types/index.d.ts.map +1 -1
- package/types/selectionController.d.ts +88 -0
- package/types/selectionController.d.ts.map +1 -0
- package/types/types.d.ts +47 -37
- package/types/types.d.ts.map +1 -1
- package/dist/cjs/BubbleSelect.cjs +0 -50
- package/dist/cjs/BubbleSelect.native.js +0 -30
- package/dist/cjs/BubbleSelect.native.js.map +0 -1
- package/dist/cjs/useSelectBreakpointActive.cjs +0 -32
- package/dist/cjs/useSelectBreakpointActive.native.js +0 -35
- package/dist/cjs/useSelectBreakpointActive.native.js.map +0 -1
- package/dist/esm/BubbleSelect.mjs +0 -27
- package/dist/esm/BubbleSelect.mjs.map +0 -1
- package/dist/esm/BubbleSelect.native.js +0 -4
- package/dist/esm/BubbleSelect.native.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/index.mjs.map +0 -1
- package/dist/esm/index.native.js.map +0 -1
- package/dist/esm/types.mjs.map +0 -1
- package/dist/esm/types.native.js.map +0 -1
- package/dist/esm/useSelectBreakpointActive.mjs +0 -7
- package/dist/esm/useSelectBreakpointActive.mjs.map +0 -1
- package/dist/esm/useSelectBreakpointActive.native.js +0 -7
- package/dist/esm/useSelectBreakpointActive.native.js.map +0 -1
- package/dist/jsx/BubbleSelect.mjs +0 -27
- package/dist/jsx/BubbleSelect.mjs.map +0 -1
- package/dist/jsx/BubbleSelect.native.js +0 -30
- package/dist/jsx/BubbleSelect.native.js.map +0 -1
- package/dist/jsx/index.js.map +0 -1
- package/dist/jsx/index.mjs.map +0 -1
- package/dist/jsx/types.mjs.map +0 -1
- package/dist/jsx/useSelectBreakpointActive.mjs +0 -7
- package/dist/jsx/useSelectBreakpointActive.mjs.map +0 -1
- package/dist/jsx/useSelectBreakpointActive.native.js +0 -35
- package/dist/jsx/useSelectBreakpointActive.native.js.map +0 -1
- package/src/BubbleSelect.tsx +0 -50
- package/src/useSelectBreakpointActive.tsx +0 -7
- package/types/BubbleSelect.d.ts +0 -2
- package/types/BubbleSelect.d.ts.map +0 -1
- package/types/useSelectBreakpointActive.d.ts +0 -3
- package/types/useSelectBreakpointActive.d.ts.map +0 -1
package/src/Select.tsx
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
import { Adapt, AdaptParent, useAdaptIsActive } from '@tamagui/adapt'
|
|
2
2
|
import { useComposedRefs } from '@tamagui/compose-refs'
|
|
3
3
|
import { isWeb, useIsomorphicLayoutEffect } from '@tamagui/constants'
|
|
4
|
-
import type {
|
|
4
|
+
import type { GetProps } from '@tamagui/core'
|
|
5
5
|
import {
|
|
6
|
+
createChangeEventDetails,
|
|
7
|
+
createStyledHOC,
|
|
6
8
|
createStyledContext,
|
|
7
|
-
getVariableValue,
|
|
8
9
|
styled,
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
Text,
|
|
11
|
+
View,
|
|
11
12
|
} from '@tamagui/core'
|
|
12
13
|
import { FocusScopeController } from '@tamagui/focus-scope'
|
|
13
14
|
import { registerFocusable } from '@tamagui/focusable'
|
|
14
|
-
import { getSpace } from '@tamagui/get-token'
|
|
15
15
|
import { withStaticProperties } from '@tamagui/helpers'
|
|
16
|
-
import { Separator } from '@tamagui/separator'
|
|
17
|
-
import { SheetController } from '@tamagui/sheet/controller'
|
|
18
|
-
import { XStack, YStack } from '@tamagui/stacks'
|
|
19
|
-
import { Paragraph, SizableText } from '@tamagui/text'
|
|
20
16
|
import { useControllableState } from '@tamagui/use-controllable-state'
|
|
17
|
+
import type { ControllableStateSetter } from '@tamagui/use-controllable-state'
|
|
21
18
|
import * as React from 'react'
|
|
22
19
|
import {
|
|
23
20
|
SelectItemParentProvider,
|
|
21
|
+
SelectNativeComponentContext,
|
|
24
22
|
SelectProvider,
|
|
25
23
|
SelectZIndexContext,
|
|
26
24
|
useSelectContext,
|
|
@@ -29,12 +27,31 @@ import {
|
|
|
29
27
|
import { SelectContent } from './SelectContent'
|
|
30
28
|
import { SelectInlineImpl } from './SelectImpl'
|
|
31
29
|
import { SelectItem, useSelectItemContext } from './SelectItem'
|
|
32
|
-
import {
|
|
30
|
+
import { SelectItemText } from './SelectItemText'
|
|
33
31
|
import { SelectScrollDownButton, SelectScrollUpButton } from './SelectScrollButton'
|
|
34
32
|
import { SelectTrigger } from './SelectTrigger'
|
|
35
33
|
import { SelectViewport } from './SelectViewport'
|
|
36
|
-
import
|
|
37
|
-
|
|
34
|
+
import {
|
|
35
|
+
createSelectItemRegistry,
|
|
36
|
+
createSelectSelectionController,
|
|
37
|
+
selectedValuesFromSelection,
|
|
38
|
+
type SelectMode,
|
|
39
|
+
type SelectSelection,
|
|
40
|
+
} from './selectionController'
|
|
41
|
+
import type {
|
|
42
|
+
SelectActiveChangeDetails,
|
|
43
|
+
SelectImplProps,
|
|
44
|
+
SelectOpenChangeDetails,
|
|
45
|
+
SelectProps,
|
|
46
|
+
SelectScopedProps,
|
|
47
|
+
SelectValueForMode,
|
|
48
|
+
SelectValueChangeDetails,
|
|
49
|
+
} from './types'
|
|
50
|
+
|
|
51
|
+
export type SelectValue<
|
|
52
|
+
Value extends string = string,
|
|
53
|
+
Multiple extends boolean | undefined = false,
|
|
54
|
+
> = SelectValueForMode<Value, Multiple>
|
|
38
55
|
|
|
39
56
|
/* -------------------------------------------------------------------------------------------------
|
|
40
57
|
* SelectValue
|
|
@@ -42,9 +59,8 @@ import { useShowSelectSheet } from './useSelectBreakpointActive'
|
|
|
42
59
|
|
|
43
60
|
const VALUE_NAME = 'SelectValue'
|
|
44
61
|
|
|
45
|
-
const SelectValueFrame = styled(
|
|
46
|
-
|
|
47
|
-
userSelect: 'none',
|
|
62
|
+
export const SelectValueFrame = styled(Text, {
|
|
63
|
+
displayName: VALUE_NAME,
|
|
48
64
|
})
|
|
49
65
|
|
|
50
66
|
export type SelectValueExtraProps = SelectScopedProps<{
|
|
@@ -53,9 +69,10 @@ export type SelectValueExtraProps = SelectScopedProps<{
|
|
|
53
69
|
|
|
54
70
|
export type SelectValueProps = GetProps<typeof SelectValueFrame> & SelectValueExtraProps
|
|
55
71
|
|
|
56
|
-
const SelectValue =
|
|
72
|
+
export const SelectValue = createStyledHOC(
|
|
73
|
+
SelectValueFrame,
|
|
57
74
|
function SelectValue(
|
|
58
|
-
{ scope, children: childrenProp, placeholder, ...props },
|
|
75
|
+
{ scope, children: childrenProp, placeholder, ...props }: SelectValueProps,
|
|
59
76
|
forwardedRef
|
|
60
77
|
) {
|
|
61
78
|
// We ignore `className` and `style` as this part shouldn't be styled.
|
|
@@ -63,76 +80,69 @@ const SelectValue = SelectValueFrame.styleable<SelectValueExtraProps>(
|
|
|
63
80
|
const itemParentContext = useSelectItemParentContext(scope)
|
|
64
81
|
|
|
65
82
|
const composedRefs = useComposedRefs(
|
|
66
|
-
// @ts-ignore
|
|
83
|
+
// @ts-ignore react 19 ref type mismatch
|
|
67
84
|
forwardedRef,
|
|
68
85
|
context.onValueNodeChange as any
|
|
69
86
|
)
|
|
70
|
-
const isEmptyValue = context.
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
const isEmptyValue = context.selectedValues.length === 0
|
|
88
|
+
|
|
89
|
+
// renderValue is synchronous for ssr and lazy item mounting
|
|
90
|
+
const registeredValue =
|
|
91
|
+
context.mode === 'multiple'
|
|
92
|
+
? context.selectedValues.map((value, index) => (
|
|
93
|
+
<React.Fragment key={value}>
|
|
94
|
+
{index > 0 ? ', ' : null}
|
|
95
|
+
{itemParentContext.registry.getItem(value)?.label ?? value}
|
|
96
|
+
</React.Fragment>
|
|
97
|
+
))
|
|
98
|
+
: (itemParentContext.registry.getItem(context.selectedValues[0])?.label ??
|
|
99
|
+
context.value)
|
|
75
100
|
const children =
|
|
76
|
-
childrenProp
|
|
101
|
+
childrenProp !== undefined
|
|
102
|
+
? childrenProp
|
|
103
|
+
: context.renderValue
|
|
104
|
+
? context.renderValue(context.value)
|
|
105
|
+
: registeredValue
|
|
77
106
|
const selectValueChildren = isEmptyValue ? (placeholder ?? children) : children
|
|
78
107
|
|
|
79
108
|
return (
|
|
80
|
-
<SelectValueFrame
|
|
81
|
-
{
|
|
82
|
-
size: itemParentContext.size as any,
|
|
83
|
-
ellipsis: true,
|
|
84
|
-
// we don't want events from the portalled `SelectValue` children to bubble
|
|
85
|
-
// through the item they came from
|
|
86
|
-
pointerEvents: 'none',
|
|
87
|
-
})}
|
|
88
|
-
ref={composedRefs}
|
|
89
|
-
{...props}
|
|
90
|
-
>
|
|
91
|
-
{unwrapSelectItem(selectValueChildren)}
|
|
109
|
+
<SelectValueFrame pointerEvents="none" ref={composedRefs} {...props}>
|
|
110
|
+
{selectValueChildren}
|
|
92
111
|
</SelectValueFrame>
|
|
93
112
|
)
|
|
94
113
|
}
|
|
95
114
|
)
|
|
96
115
|
|
|
97
|
-
function unwrapSelectItem(selectValueChildren: any) {
|
|
98
|
-
return React.Children.map(selectValueChildren, (child) => {
|
|
99
|
-
if (child) {
|
|
100
|
-
if (child.type?.staticConfig?.componentName === ITEM_TEXT_NAME) {
|
|
101
|
-
return child.props.children
|
|
102
|
-
}
|
|
103
|
-
if (child.props?.children) {
|
|
104
|
-
return unwrapSelectItem(child.props.children)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return child
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
|
|
111
116
|
/* -------------------------------------------------------------------------------------------------
|
|
112
117
|
* SelectIcon
|
|
113
118
|
* -----------------------------------------------------------------------------------------------*/
|
|
114
119
|
|
|
115
|
-
export const SelectIcon = styled(
|
|
116
|
-
|
|
120
|
+
export const SelectIcon = styled(View, {
|
|
121
|
+
displayName: 'SelectIcon',
|
|
117
122
|
// @ts-ignore
|
|
118
123
|
'aria-hidden': true,
|
|
119
|
-
children: <Paragraph>▼</Paragraph>,
|
|
120
124
|
})
|
|
121
125
|
|
|
122
126
|
/* -------------------------------------------------------------------------------------------------
|
|
123
127
|
* SelectItemIndicator
|
|
124
128
|
* -----------------------------------------------------------------------------------------------*/
|
|
125
129
|
|
|
126
|
-
const SelectItemIndicatorFrame = styled(
|
|
127
|
-
|
|
130
|
+
export const SelectItemIndicatorFrame = styled(View, {
|
|
131
|
+
displayName: 'SelectItemIndicator',
|
|
128
132
|
})
|
|
129
133
|
|
|
130
|
-
type SelectItemIndicatorProps = SelectScopedProps<
|
|
134
|
+
export type SelectItemIndicatorProps = SelectScopedProps<
|
|
131
135
|
GetProps<typeof SelectItemIndicatorFrame>
|
|
132
136
|
>
|
|
133
137
|
|
|
134
|
-
const SelectItemIndicator =
|
|
135
|
-
|
|
138
|
+
export const SelectItemIndicator = createStyledHOC(
|
|
139
|
+
SelectItemIndicatorFrame,
|
|
140
|
+
function SelectItemIndicator(
|
|
141
|
+
props: {
|
|
142
|
+
scope?: string
|
|
143
|
+
},
|
|
144
|
+
forwardedRef
|
|
145
|
+
) {
|
|
136
146
|
const { scope, ...itemIndicatorProps } = props
|
|
137
147
|
const context = useSelectItemParentContext(scope)
|
|
138
148
|
const itemContext = useSelectItemContext(scope)
|
|
@@ -151,77 +161,68 @@ const SelectItemIndicator = React.forwardRef<TamaguiElement, SelectItemIndicator
|
|
|
151
161
|
* SelectIndicator
|
|
152
162
|
* -----------------------------------------------------------------------------------------------*/
|
|
153
163
|
|
|
154
|
-
const SelectIndicatorFrame = styled(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
top: 0,
|
|
162
|
-
left: 0,
|
|
163
|
-
pointerEvents: 'none',
|
|
164
|
-
zIndex: 10,
|
|
165
|
-
backgroundColor: '$background',
|
|
166
|
-
borderRadius: 0,
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
} as const,
|
|
170
|
-
|
|
171
|
-
defaultVariants: {
|
|
172
|
-
unstyled: process.env.TAMAGUI_HEADLESS === '1',
|
|
173
|
-
},
|
|
164
|
+
export const SelectIndicatorFrame = styled(View, {
|
|
165
|
+
displayName: 'SelectIndicator',
|
|
166
|
+
position: 'absolute',
|
|
167
|
+
top: 0,
|
|
168
|
+
left: 0,
|
|
169
|
+
pointerEvents: 'none',
|
|
170
|
+
zIndex: 10,
|
|
174
171
|
})
|
|
175
172
|
|
|
176
173
|
export type SelectIndicatorProps = GetProps<typeof SelectIndicatorFrame>
|
|
177
174
|
|
|
178
|
-
const SelectIndicator =
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
175
|
+
export const SelectIndicator = createStyledHOC(
|
|
176
|
+
SelectIndicatorFrame,
|
|
177
|
+
function SelectIndicator(
|
|
178
|
+
{ scope, ...props }: SelectScopedProps<SelectIndicatorProps>,
|
|
179
|
+
forwardedRef
|
|
180
|
+
) {
|
|
181
|
+
const itemContext = useSelectItemParentContext(scope)
|
|
182
|
+
const context = useSelectContext(scope)
|
|
183
|
+
const [layout, setLayout] = React.useState<any>(null)
|
|
184
|
+
|
|
185
|
+
const rafRef = React.useRef<any>(0)
|
|
186
|
+
|
|
187
|
+
React.useLayoutEffect(() => {
|
|
188
|
+
const update = (index: number | null) => {
|
|
189
|
+
if (typeof index !== 'number') return
|
|
190
|
+
cancelAnimationFrame(rafRef.current)
|
|
191
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
192
|
+
const node = itemContext.listRef?.current?.[index]
|
|
193
|
+
if (node) {
|
|
194
|
+
setLayout({
|
|
195
|
+
width: Math.round(node.offsetWidth),
|
|
196
|
+
height: Math.round(node.offsetHeight),
|
|
197
|
+
x: Math.round(node.offsetLeft),
|
|
198
|
+
y: Math.round(node.offsetTop),
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
// use ref for initial read to avoid depending on state
|
|
205
|
+
if (context.open && context.activeIndexRef.current !== null) {
|
|
206
|
+
update(context.activeIndexRef.current)
|
|
207
|
+
}
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
return itemContext.activeIndexSubscribe(update)
|
|
210
|
+
}, [context.open, itemContext.listRef])
|
|
211
211
|
|
|
212
|
-
|
|
212
|
+
if (!layout) return null
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
214
|
+
return (
|
|
215
|
+
<SelectIndicatorFrame
|
|
216
|
+
ref={forwardedRef}
|
|
217
|
+
{...props}
|
|
218
|
+
width={layout.width}
|
|
219
|
+
height={layout.height}
|
|
220
|
+
x={layout.x}
|
|
221
|
+
y={layout.y}
|
|
222
|
+
/>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
)
|
|
225
226
|
|
|
226
227
|
/* -------------------------------------------------------------------------------------------------
|
|
227
228
|
* SelectGroup
|
|
@@ -234,96 +235,61 @@ type SelectGroupContextValue = { id: string }
|
|
|
234
235
|
const { Provider: SelectGroupContextProvider, useStyledContext: useSelectGroupContext } =
|
|
235
236
|
createStyledContext<SelectGroupContextValue>({ id: '' }, 'SelectGroup')
|
|
236
237
|
|
|
237
|
-
export const SelectGroupFrame = styled(
|
|
238
|
-
|
|
239
|
-
width: '100%',
|
|
238
|
+
export const SelectGroupFrame = styled(View, {
|
|
239
|
+
displayName: GROUP_NAME,
|
|
240
240
|
})
|
|
241
241
|
|
|
242
|
-
const
|
|
242
|
+
const NativeSelectFrame = styled(Text, {
|
|
243
|
+
displayName: 'NativeSelect',
|
|
243
244
|
render: 'select',
|
|
244
|
-
backgroundColor: '$background',
|
|
245
|
-
borderColor: '$borderColor',
|
|
246
|
-
hoverStyle: {
|
|
247
|
-
backgroundColor: '$backgroundHover',
|
|
248
|
-
},
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
const NativeSelectFrame = styled(YStack, {
|
|
252
|
-
name: 'NativeSelect',
|
|
253
|
-
|
|
254
|
-
variants: {
|
|
255
|
-
size: {
|
|
256
|
-
'...size': (val, extras) => {
|
|
257
|
-
const { tokens } = extras
|
|
258
|
-
const paddingHorizontal = getVariableValue(tokens.space[val])
|
|
259
|
-
|
|
260
|
-
return {
|
|
261
|
-
borderRadius: tokens.radius[val] ?? val,
|
|
262
|
-
minHeight: tokens.size[val],
|
|
263
|
-
paddingRight: paddingHorizontal + 20,
|
|
264
|
-
paddingLeft: paddingHorizontal,
|
|
265
|
-
paddingVertical: getSpace(val, {
|
|
266
|
-
shift: -3,
|
|
267
|
-
}),
|
|
268
|
-
}
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
|
|
272
|
-
unstyled: {
|
|
273
|
-
false: {
|
|
274
|
-
borderWidth: 1,
|
|
275
|
-
borderColor: '$borderColor',
|
|
276
|
-
userSelect: 'none',
|
|
277
|
-
outlineWidth: 0,
|
|
278
|
-
paddingRight: 10,
|
|
279
|
-
},
|
|
280
|
-
},
|
|
281
|
-
} as const,
|
|
282
|
-
|
|
283
|
-
defaultVariants: {
|
|
284
|
-
size: '$2',
|
|
285
|
-
unstyled: process.env.TAMAGUI_HEADLESS === '1' ? true : false,
|
|
286
|
-
},
|
|
287
245
|
})
|
|
246
|
+
const NativeSelect = NativeSelectFrame as any
|
|
288
247
|
|
|
289
248
|
type SelectGroupProps = SelectScopedProps<GetProps<typeof SelectGroupFrame>>
|
|
290
249
|
|
|
291
|
-
const SelectGroup =
|
|
292
|
-
|
|
250
|
+
export const SelectGroup = createStyledHOC(
|
|
251
|
+
SelectGroupFrame,
|
|
252
|
+
(props: SelectGroupProps, forwardedRef) => {
|
|
293
253
|
const { scope, ...groupProps } = props
|
|
294
254
|
const groupId = React.useId()
|
|
295
255
|
|
|
296
256
|
const context = useSelectContext(scope)
|
|
297
257
|
const itemParentContext = useSelectItemParentContext(scope)
|
|
298
|
-
const
|
|
258
|
+
const StyledNativeSelect = React.useContext(SelectNativeComponentContext)
|
|
299
259
|
const nativeSelectRef = React.useRef<HTMLSelectElement>(null)
|
|
300
260
|
|
|
301
261
|
const content = (() => {
|
|
302
262
|
if (itemParentContext.shouldRenderWebNative) {
|
|
303
263
|
return (
|
|
304
|
-
<
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
264
|
+
<NativeSelect
|
|
265
|
+
{...(StyledNativeSelect && {
|
|
266
|
+
render: StyledNativeSelect,
|
|
267
|
+
size: itemParentContext.size,
|
|
268
|
+
style: { appearance: 'none' },
|
|
269
|
+
})}
|
|
270
|
+
{...groupProps}
|
|
271
|
+
// @ts-ignore it's ok since render="select"
|
|
308
272
|
value={context.value}
|
|
273
|
+
multiple={context.mode === 'multiple'}
|
|
274
|
+
name={itemParentContext.name}
|
|
275
|
+
form={itemParentContext.form}
|
|
309
276
|
id={itemParentContext.id}
|
|
277
|
+
onChange={
|
|
278
|
+
((event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
279
|
+
const value =
|
|
280
|
+
context.mode === 'multiple'
|
|
281
|
+
? Array.from(
|
|
282
|
+
event.currentTarget.selectedOptions,
|
|
283
|
+
(option) => option.value
|
|
284
|
+
)
|
|
285
|
+
: event.currentTarget.value
|
|
286
|
+
itemParentContext.changeNativeValue(value, event.nativeEvent)
|
|
287
|
+
}) as any
|
|
288
|
+
}
|
|
289
|
+
ref={nativeSelectRef as any}
|
|
310
290
|
>
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
314
|
-
itemParentContext.onChange(event.currentTarget.value)
|
|
315
|
-
}}
|
|
316
|
-
size={size as FontSizeTokens}
|
|
317
|
-
ref={nativeSelectRef as any}
|
|
318
|
-
style={{
|
|
319
|
-
color: 'var(--color)',
|
|
320
|
-
// @ts-ignore
|
|
321
|
-
appearance: 'none',
|
|
322
|
-
}}
|
|
323
|
-
>
|
|
324
|
-
{props.children}
|
|
325
|
-
</NativeSelectTextFrame>
|
|
326
|
-
</NativeSelectFrame>
|
|
291
|
+
{props.children}
|
|
292
|
+
</NativeSelect>
|
|
327
293
|
)
|
|
328
294
|
}
|
|
329
295
|
return (
|
|
@@ -353,39 +319,14 @@ SelectGroup.displayName = GROUP_NAME
|
|
|
353
319
|
|
|
354
320
|
const LABEL_NAME = 'SelectLabel'
|
|
355
321
|
|
|
356
|
-
const SelectLabelFrame = styled(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
variants: {
|
|
360
|
-
unstyled: {
|
|
361
|
-
false: {
|
|
362
|
-
size: '$true',
|
|
363
|
-
ellipsis: true,
|
|
364
|
-
maxWidth: '100%',
|
|
365
|
-
cursor: 'default',
|
|
366
|
-
},
|
|
367
|
-
},
|
|
368
|
-
|
|
369
|
-
size: {
|
|
370
|
-
'...size': (val: SizeTokens, { tokens }) => {
|
|
371
|
-
return {
|
|
372
|
-
paddingHorizontal: tokens.space[val],
|
|
373
|
-
paddingVertical: getSpace(tokens.space[val], {
|
|
374
|
-
shift: -4,
|
|
375
|
-
}),
|
|
376
|
-
}
|
|
377
|
-
},
|
|
378
|
-
},
|
|
379
|
-
} as const,
|
|
380
|
-
|
|
381
|
-
defaultVariants: {
|
|
382
|
-
unstyled: process.env.TAMAGUI_HEADLESS === '1',
|
|
383
|
-
},
|
|
322
|
+
export const SelectLabelFrame = styled(Text, {
|
|
323
|
+
displayName: LABEL_NAME,
|
|
384
324
|
})
|
|
385
325
|
|
|
386
326
|
export type SelectLabelProps = SelectScopedProps<GetProps<typeof SelectLabelFrame>>
|
|
387
327
|
|
|
388
|
-
const SelectLabel =
|
|
328
|
+
export const SelectLabel = createStyledHOC(
|
|
329
|
+
SelectLabelFrame,
|
|
389
330
|
(props: SelectLabelProps, forwardedRef) => {
|
|
390
331
|
const { scope, ...labelProps } = props
|
|
391
332
|
const context = useSelectItemParentContext(scope)
|
|
@@ -399,7 +340,6 @@ const SelectLabel = SelectLabelFrame.styleable<{ scope?: any }>(
|
|
|
399
340
|
<SelectLabelFrame
|
|
400
341
|
render="div"
|
|
401
342
|
id={groupContext.id}
|
|
402
|
-
size={context.size}
|
|
403
343
|
{...labelProps}
|
|
404
344
|
ref={forwardedRef}
|
|
405
345
|
/>
|
|
@@ -411,36 +351,10 @@ const SelectLabel = SelectLabelFrame.styleable<{ scope?: any }>(
|
|
|
411
351
|
* SelectSeparator
|
|
412
352
|
* -----------------------------------------------------------------------------------------------*/
|
|
413
353
|
|
|
414
|
-
export const SelectSeparator = styled(
|
|
415
|
-
|
|
354
|
+
export const SelectSeparator = styled(View, {
|
|
355
|
+
displayName: 'SelectSeparator',
|
|
416
356
|
})
|
|
417
357
|
|
|
418
|
-
const SelectSheetController = (
|
|
419
|
-
props: SelectScopedProps<{
|
|
420
|
-
children: React.ReactNode
|
|
421
|
-
onOpenChange: React.Dispatch<React.SetStateAction<boolean>>
|
|
422
|
-
}>
|
|
423
|
-
) => {
|
|
424
|
-
const context = useSelectContext(props.scope)
|
|
425
|
-
const showSheet = useShowSelectSheet(context)
|
|
426
|
-
const isAdapted = useAdaptIsActive(context.adaptScope)
|
|
427
|
-
const getShowSheet = useGet(showSheet)
|
|
428
|
-
|
|
429
|
-
return (
|
|
430
|
-
<SheetController
|
|
431
|
-
onOpenChange={(val) => {
|
|
432
|
-
if (getShowSheet()) {
|
|
433
|
-
props.onOpenChange(val)
|
|
434
|
-
}
|
|
435
|
-
}}
|
|
436
|
-
open={context.open}
|
|
437
|
-
hidden={!isAdapted}
|
|
438
|
-
>
|
|
439
|
-
{props.children}
|
|
440
|
-
</SheetController>
|
|
441
|
-
)
|
|
442
|
-
}
|
|
443
|
-
|
|
444
358
|
const SelectSheetImpl = (props: SelectImplProps) => {
|
|
445
359
|
return <>{props.children}</>
|
|
446
360
|
}
|
|
@@ -449,36 +363,67 @@ const SelectSheetImpl = (props: SelectImplProps) => {
|
|
|
449
363
|
* Select
|
|
450
364
|
* -----------------------------------------------------------------------------------------------*/
|
|
451
365
|
|
|
452
|
-
export
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
366
|
+
export function SelectRoot<
|
|
367
|
+
Value extends string = string,
|
|
368
|
+
Multiple extends boolean | undefined = false,
|
|
369
|
+
>(props: SelectScopedProps<SelectProps<Value, Multiple>>) {
|
|
370
|
+
const adaptScope = `AdaptSelect${props.scope || ''}`
|
|
371
|
+
|
|
372
|
+
// open state lives here (above AdaptParent) so the Adapt handoff can drive
|
|
373
|
+
// the adapted Sheet's open/close and unmount timing directly, mirroring Dialog
|
|
374
|
+
const [open, requestOpenChange] = useControllableState<
|
|
375
|
+
boolean,
|
|
376
|
+
SelectOpenChangeDetails
|
|
377
|
+
>({
|
|
378
|
+
prop: props.open,
|
|
379
|
+
defaultProp: props.defaultOpen || false,
|
|
380
|
+
onChange: props.onOpenChange as any,
|
|
381
|
+
})
|
|
457
382
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
383
|
+
const handleAdaptOpenChange = React.useCallback(
|
|
384
|
+
(nextOpen: boolean) => {
|
|
385
|
+
requestOpenChange(
|
|
386
|
+
nextOpen,
|
|
387
|
+
createChangeEventDetails(
|
|
388
|
+
nextOpen ? 'trigger-press' : 'outside-press'
|
|
389
|
+
) as SelectOpenChangeDetails
|
|
390
|
+
)
|
|
391
|
+
},
|
|
392
|
+
[requestOpenChange]
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<AdaptParent scope={adaptScope} open={open} onOpenChange={handleAdaptOpenChange}>
|
|
397
|
+
<SelectInner
|
|
398
|
+
{...(props as SelectScopedProps<SelectProps<string, boolean>>)}
|
|
399
|
+
scope={props.scope}
|
|
400
|
+
adaptScope={adaptScope}
|
|
401
|
+
open={open}
|
|
402
|
+
requestOpenChange={requestOpenChange}
|
|
403
|
+
/>
|
|
404
|
+
</AdaptParent>
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export const Select = withStaticProperties(SelectRoot, {
|
|
409
|
+
Root: SelectRoot,
|
|
410
|
+
Adapt: Adapt,
|
|
411
|
+
Content: SelectContent,
|
|
412
|
+
Group: SelectGroup,
|
|
413
|
+
Icon: SelectIcon,
|
|
414
|
+
Item: SelectItem,
|
|
415
|
+
ItemIndicator: SelectItemIndicator,
|
|
416
|
+
ItemText: SelectItemText,
|
|
417
|
+
Label: SelectLabel,
|
|
418
|
+
ScrollDownButton: SelectScrollDownButton,
|
|
419
|
+
ScrollUpButton: SelectScrollUpButton,
|
|
420
|
+
Separator: SelectSeparator,
|
|
421
|
+
Trigger: SelectTrigger,
|
|
422
|
+
Value: SelectValue,
|
|
423
|
+
Viewport: SelectViewport,
|
|
424
|
+
Indicator: SelectIndicator,
|
|
425
|
+
FocusScope: FocusScopeController,
|
|
426
|
+
})
|
|
482
427
|
|
|
483
428
|
function useEmitter<A>() {
|
|
484
429
|
const listenersRef = React.useRef<Set<Function>>(new Set())
|
|
@@ -494,20 +439,30 @@ function useEmitter<A>() {
|
|
|
494
439
|
return [emit, subscribe] as const
|
|
495
440
|
}
|
|
496
441
|
|
|
497
|
-
function SelectInner(
|
|
442
|
+
function SelectInner(
|
|
443
|
+
props: SelectScopedProps<SelectProps<string, boolean>> & {
|
|
444
|
+
adaptScope: string
|
|
445
|
+
open: boolean
|
|
446
|
+
requestOpenChange: ControllableStateSetter<boolean, SelectOpenChangeDetails>
|
|
447
|
+
}
|
|
448
|
+
) {
|
|
498
449
|
const {
|
|
499
450
|
scope = '',
|
|
500
451
|
adaptScope,
|
|
501
452
|
native,
|
|
502
453
|
children,
|
|
503
|
-
open
|
|
504
|
-
|
|
505
|
-
|
|
454
|
+
open,
|
|
455
|
+
requestOpenChange,
|
|
456
|
+
defaultOpen: _defaultOpen,
|
|
457
|
+
onOpenChange: _onOpenChange,
|
|
506
458
|
value: valueProp,
|
|
507
459
|
defaultValue,
|
|
508
460
|
onValueChange,
|
|
461
|
+
multiple = false,
|
|
462
|
+
name,
|
|
463
|
+
form,
|
|
509
464
|
disablePreventBodyScroll,
|
|
510
|
-
size: sizeProp =
|
|
465
|
+
size: sizeProp = true,
|
|
511
466
|
onActiveChange,
|
|
512
467
|
dir,
|
|
513
468
|
id,
|
|
@@ -517,45 +472,69 @@ function SelectInner(props: SelectScopedProps<SelectProps> & { adaptScope: strin
|
|
|
517
472
|
} = props
|
|
518
473
|
|
|
519
474
|
const isAdapted = useAdaptIsActive(adaptScope)
|
|
520
|
-
const SelectImpl = isAdapted
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const [value, setValue] = useControllableState({
|
|
531
|
-
prop: valueProp,
|
|
532
|
-
defaultProp: defaultValue || '',
|
|
533
|
-
onChange: onValueChange,
|
|
475
|
+
const SelectImpl = isAdapted ? SelectSheetImpl : SelectInlineImpl
|
|
476
|
+
const mode: SelectMode = multiple ? 'multiple' : 'single'
|
|
477
|
+
|
|
478
|
+
const [valueState, requestValueChange] = useControllableState<
|
|
479
|
+
SelectSelection,
|
|
480
|
+
SelectValueChangeDetails
|
|
481
|
+
>({
|
|
482
|
+
prop: valueProp as SelectSelection | undefined,
|
|
483
|
+
defaultProp: (defaultValue ?? (mode === 'multiple' ? [] : '')) as SelectSelection,
|
|
484
|
+
onChange: onValueChange as any,
|
|
534
485
|
transition: true,
|
|
535
486
|
})
|
|
536
487
|
|
|
537
|
-
React.
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
488
|
+
const value = React.useMemo(
|
|
489
|
+
() =>
|
|
490
|
+
mode === 'multiple'
|
|
491
|
+
? selectedValuesFromSelection(mode, valueState)
|
|
492
|
+
: typeof valueState === 'string'
|
|
493
|
+
? valueState
|
|
494
|
+
: '',
|
|
495
|
+
[mode, valueState]
|
|
496
|
+
)
|
|
497
|
+
const selectedValues = React.useMemo(
|
|
498
|
+
() => selectedValuesFromSelection(mode, value),
|
|
499
|
+
[mode, value]
|
|
500
|
+
)
|
|
542
501
|
|
|
543
|
-
React.
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (
|
|
548
|
-
|
|
549
|
-
if (!props.id) return
|
|
550
|
-
|
|
551
|
-
return registerFocusable(props.id, {
|
|
552
|
-
focusAndSelect: () => {
|
|
553
|
-
setOpen?.((value) => !value)
|
|
554
|
-
},
|
|
555
|
-
focus: () => {},
|
|
556
|
-
})
|
|
557
|
-
}, [props.id])
|
|
502
|
+
const [, rerenderRegistry] = React.useReducer((version) => version + 1, 0)
|
|
503
|
+
const registryRef = React.useRef<ReturnType<typeof createSelectItemRegistry> | null>(
|
|
504
|
+
null
|
|
505
|
+
)
|
|
506
|
+
if (!registryRef.current) {
|
|
507
|
+
registryRef.current = createSelectItemRegistry(rerenderRegistry)
|
|
558
508
|
}
|
|
509
|
+
const registry = registryRef.current
|
|
510
|
+
|
|
511
|
+
const controllerRef = React.useRef<ReturnType<
|
|
512
|
+
typeof createSelectSelectionController
|
|
513
|
+
> | null>(null)
|
|
514
|
+
if (!controllerRef.current) {
|
|
515
|
+
controllerRef.current = createSelectSelectionController({
|
|
516
|
+
mode,
|
|
517
|
+
value,
|
|
518
|
+
registry,
|
|
519
|
+
})
|
|
520
|
+
}
|
|
521
|
+
const controller = controllerRef.current
|
|
522
|
+
controller.setMode(mode)
|
|
523
|
+
controller.setValue(value)
|
|
524
|
+
|
|
525
|
+
React.useEffect(() => {
|
|
526
|
+
if (process.env.TAMAGUI_TARGET !== 'native' || !props.id) return
|
|
527
|
+
|
|
528
|
+
return registerFocusable(props.id, {
|
|
529
|
+
focusAndSelect: () => {
|
|
530
|
+
requestOpenChange(
|
|
531
|
+
(current) => !current,
|
|
532
|
+
createChangeEventDetails('trigger-press') as SelectOpenChangeDetails
|
|
533
|
+
)
|
|
534
|
+
},
|
|
535
|
+
focus: () => {},
|
|
536
|
+
})
|
|
537
|
+
}, [props.id, requestOpenChange])
|
|
559
538
|
|
|
560
539
|
// activeIndex is stored in a ref to avoid re-renders on every hover
|
|
561
540
|
// we have two setters:
|
|
@@ -565,14 +544,17 @@ function SelectInner(props: SelectScopedProps<SelectProps> & { adaptScope: strin
|
|
|
565
544
|
const activeIndexRef = React.useRef<number | null>(null)
|
|
566
545
|
const [activeIndex, setActiveIndexState] = React.useState<number | null>(null)
|
|
567
546
|
|
|
568
|
-
const [emitValue, valueSubscribe] = useEmitter<any>()
|
|
569
547
|
const [emitActiveIndex, activeIndexSubscribe] = useEmitter<number>()
|
|
570
548
|
|
|
571
549
|
const selectedIndexRef = React.useRef<number | null>(null)
|
|
572
550
|
const listContentRef = React.useRef<string[]>([])
|
|
573
|
-
const
|
|
551
|
+
const listRef = React.useRef<Array<HTMLElement | null>>([])
|
|
574
552
|
const [valueNode, setValueNode] = React.useState<HTMLElement | null>(null)
|
|
553
|
+
const selectedIndex = Math.max(0, controller.selectionAnchorIndex())
|
|
554
|
+
listContentRef.current = registry.getTypeaheadLabels()
|
|
555
|
+
listRef.current.length = registry.getItems().length
|
|
575
556
|
|
|
557
|
+
// Intentionally dependency-less: selectedIndexRef mirrors every render for non-reactive reads.
|
|
576
558
|
useIsomorphicLayoutEffect(() => {
|
|
577
559
|
selectedIndexRef.current = selectedIndex
|
|
578
560
|
})
|
|
@@ -586,54 +568,158 @@ function SelectInner(props: SelectScopedProps<SelectProps> & { adaptScope: strin
|
|
|
586
568
|
// fast setter: updates ref + emits to subscribers without causing re-renders
|
|
587
569
|
// use this for mouse hover / keyboard navigation where we don't need parent re-renders
|
|
588
570
|
const setActiveIndexFast = React.useCallback(
|
|
589
|
-
(index: number | null) => {
|
|
571
|
+
(index: number | null, details?: SelectActiveChangeDetails) => {
|
|
590
572
|
if (activeIndexRef.current !== index) {
|
|
591
573
|
activeIndexRef.current = index
|
|
574
|
+
controller.setActiveIndex(index)
|
|
592
575
|
if (typeof index === 'number') {
|
|
593
576
|
emitActiveIndex(index)
|
|
577
|
+
const item = registry.getItems()[index]
|
|
578
|
+
if (item && details) {
|
|
579
|
+
onActiveChange?.(item.value, details)
|
|
580
|
+
}
|
|
594
581
|
}
|
|
595
582
|
}
|
|
596
583
|
},
|
|
597
|
-
[emitActiveIndex]
|
|
584
|
+
[controller, emitActiveIndex, onActiveChange, registry]
|
|
598
585
|
)
|
|
599
586
|
|
|
600
587
|
// slow setter: also triggers a re-render for components that need the state value
|
|
601
588
|
// use this sparingly, e.g., when controlled scrolling needs to scroll item into view
|
|
602
589
|
const setActiveIndex = React.useCallback(
|
|
603
|
-
(index: number | null) => {
|
|
604
|
-
setActiveIndexFast(index)
|
|
590
|
+
(index: number | null, details?: SelectActiveChangeDetails) => {
|
|
591
|
+
setActiveIndexFast(index, details)
|
|
605
592
|
setActiveIndexState(index)
|
|
606
593
|
},
|
|
607
594
|
[setActiveIndexFast]
|
|
608
595
|
)
|
|
609
596
|
|
|
597
|
+
const activeDetails = React.useCallback(
|
|
598
|
+
(
|
|
599
|
+
reason: SelectActiveChangeDetails['reason'],
|
|
600
|
+
index: number,
|
|
601
|
+
event?: unknown
|
|
602
|
+
): SelectActiveChangeDetails => ({
|
|
603
|
+
reason,
|
|
604
|
+
event,
|
|
605
|
+
trigger: undefined,
|
|
606
|
+
index,
|
|
607
|
+
}),
|
|
608
|
+
[]
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
const moveActive = React.useCallback(
|
|
612
|
+
(direction: 1 | -1, event?: unknown) => {
|
|
613
|
+
const nextIndex = registry.nextEnabledIndex(activeIndexRef.current, direction)
|
|
614
|
+
if (nextIndex >= 0) {
|
|
615
|
+
setActiveIndex(nextIndex, activeDetails('keyboard', nextIndex, event))
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
[activeDetails, registry, setActiveIndex]
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
const typeaheadRef = React.useRef({ text: '', timeout: null as any })
|
|
622
|
+
const search = React.useCallback(
|
|
623
|
+
(text: string, event?: unknown) => {
|
|
624
|
+
clearTimeout(typeaheadRef.current.timeout)
|
|
625
|
+
typeaheadRef.current.text += text
|
|
626
|
+
let nextIndex = registry.findTypeaheadIndex(
|
|
627
|
+
typeaheadRef.current.text,
|
|
628
|
+
activeIndexRef.current
|
|
629
|
+
)
|
|
630
|
+
if (nextIndex < 0 && typeaheadRef.current.text.length > 1) {
|
|
631
|
+
typeaheadRef.current.text = text
|
|
632
|
+
nextIndex = registry.findTypeaheadIndex(text, activeIndexRef.current)
|
|
633
|
+
}
|
|
634
|
+
typeaheadRef.current.timeout = setTimeout(() => {
|
|
635
|
+
typeaheadRef.current.text = ''
|
|
636
|
+
}, 750)
|
|
637
|
+
if (nextIndex >= 0) {
|
|
638
|
+
setActiveIndex(nextIndex, activeDetails('keyboard', nextIndex, event))
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
[activeDetails, registry, setActiveIndex]
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
React.useEffect(() => {
|
|
645
|
+
return () => clearTimeout(typeaheadRef.current.timeout)
|
|
646
|
+
}, [])
|
|
647
|
+
|
|
648
|
+
React.useEffect(() => {
|
|
649
|
+
if (open) {
|
|
650
|
+
const nextIndex = controller.initialActiveIndex()
|
|
651
|
+
if (nextIndex >= 0) {
|
|
652
|
+
setActiveIndex(nextIndex, activeDetails('list-navigation', nextIndex))
|
|
653
|
+
}
|
|
654
|
+
} else {
|
|
655
|
+
setActiveIndexFast(null)
|
|
656
|
+
setActiveIndexState(null)
|
|
657
|
+
}
|
|
658
|
+
}, [open, selectedIndex, registry.getItems().length])
|
|
659
|
+
|
|
660
|
+
const selectValue = React.useCallback(
|
|
661
|
+
(nextItemValue: string, details: SelectValueChangeDetails) => {
|
|
662
|
+
const item = registry.getItem(nextItemValue)
|
|
663
|
+
if (!item || item.disabled) return
|
|
664
|
+
controller.setMode(mode)
|
|
665
|
+
controller.setValue(value)
|
|
666
|
+
const previousValue = controller.value
|
|
667
|
+
const nextValue = controller.toggle(nextItemValue)
|
|
668
|
+
requestValueChange(nextValue, details)
|
|
669
|
+
if (details.isCanceled) {
|
|
670
|
+
controller.setValue(previousValue)
|
|
671
|
+
return
|
|
672
|
+
}
|
|
673
|
+
if (controller.shouldCloseOnSelect) {
|
|
674
|
+
requestOpenChange(
|
|
675
|
+
false,
|
|
676
|
+
createChangeEventDetails(
|
|
677
|
+
details.reason === 'keyboard' ? 'keyboard' : 'item-press',
|
|
678
|
+
details.event,
|
|
679
|
+
details.trigger
|
|
680
|
+
) as SelectOpenChangeDetails
|
|
681
|
+
)
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
[controller, mode, registry, requestOpenChange, requestValueChange, value]
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
const changeNativeValue = React.useCallback(
|
|
688
|
+
(nextValue: SelectSelection, event: unknown) => {
|
|
689
|
+
const details = createChangeEventDetails(
|
|
690
|
+
'native-change',
|
|
691
|
+
event
|
|
692
|
+
) as SelectValueChangeDetails
|
|
693
|
+
requestValueChange(nextValue, details)
|
|
694
|
+
if (!details.isCanceled) {
|
|
695
|
+
controller.setValue(nextValue)
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
[controller, requestValueChange]
|
|
699
|
+
)
|
|
700
|
+
|
|
610
701
|
const content = (
|
|
611
702
|
<SelectItemParentProvider
|
|
612
703
|
scopeName={scope}
|
|
613
704
|
scope={scope}
|
|
614
705
|
adaptScope={adaptScope}
|
|
615
|
-
|
|
706
|
+
mode={mode}
|
|
707
|
+
selectedValues={selectedValues}
|
|
708
|
+
registry={registry}
|
|
616
709
|
size={sizeProp}
|
|
617
710
|
activeIndexSubscribe={activeIndexSubscribe}
|
|
618
711
|
activeIndexRef={activeIndexRef}
|
|
619
|
-
|
|
620
|
-
setOpen={setOpen}
|
|
712
|
+
requestOpenChange={requestOpenChange}
|
|
621
713
|
id={id}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
}
|
|
626
|
-
onActiveChange={useEvent((value, index) => {
|
|
627
|
-
onActiveChange?.(value, index)
|
|
628
|
-
})}
|
|
629
|
-
setSelectedIndex={setSelectedIndex}
|
|
630
|
-
setValueAtIndex={React.useCallback((index, value) => {
|
|
631
|
-
listContentRef.current[index] = value
|
|
632
|
-
}, [])}
|
|
714
|
+
name={name}
|
|
715
|
+
form={form}
|
|
716
|
+
selectValue={selectValue}
|
|
717
|
+
changeNativeValue={changeNativeValue}
|
|
633
718
|
shouldRenderWebNative={shouldRenderWebNative}
|
|
634
719
|
setActiveIndexFast={setActiveIndexFast}
|
|
635
|
-
|
|
636
|
-
|
|
720
|
+
listRef={listRef}
|
|
721
|
+
moveActive={moveActive}
|
|
722
|
+
search={search}
|
|
637
723
|
>
|
|
638
724
|
<SelectProvider
|
|
639
725
|
scope={scope}
|
|
@@ -643,7 +729,6 @@ function SelectInner(props: SelectScopedProps<SelectProps> & { adaptScope: strin
|
|
|
643
729
|
dir={dir}
|
|
644
730
|
blockSelection={false}
|
|
645
731
|
fallback={false}
|
|
646
|
-
forceUpdate={forceUpdate}
|
|
647
732
|
valueNode={valueNode}
|
|
648
733
|
onValueNodeChange={setValueNode}
|
|
649
734
|
activeIndex={activeIndex}
|
|
@@ -651,29 +736,44 @@ function SelectInner(props: SelectScopedProps<SelectProps> & { adaptScope: strin
|
|
|
651
736
|
selectedIndex={selectedIndex}
|
|
652
737
|
setActiveIndex={setActiveIndex}
|
|
653
738
|
value={value}
|
|
739
|
+
mode={mode}
|
|
740
|
+
selectedValues={selectedValues}
|
|
741
|
+
activeItem={
|
|
742
|
+
activeIndex == null ? undefined : registry.getItems()[activeIndex]?.value
|
|
743
|
+
}
|
|
744
|
+
selectionAnchor={controller.selectionAnchor()?.value}
|
|
654
745
|
open={open}
|
|
655
746
|
native={native}
|
|
656
747
|
renderValue={renderValue}
|
|
657
748
|
lazyMount={lazyMount}
|
|
658
749
|
>
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
)}
|
|
675
|
-
</SelectSheetController>
|
|
750
|
+
{shouldRenderWebNative ? (
|
|
751
|
+
children
|
|
752
|
+
) : (
|
|
753
|
+
<SelectImpl
|
|
754
|
+
activeIndexRef={activeIndexRef}
|
|
755
|
+
listContentRef={listContentRef}
|
|
756
|
+
selectedIndexRef={selectedIndexRef}
|
|
757
|
+
setActiveIndexFast={setActiveIndexFast}
|
|
758
|
+
{...props}
|
|
759
|
+
open={open}
|
|
760
|
+
value={value}
|
|
761
|
+
>
|
|
762
|
+
{children}
|
|
763
|
+
</SelectImpl>
|
|
764
|
+
)}
|
|
676
765
|
</SelectProvider>
|
|
766
|
+
{isWeb && !shouldRenderWebNative && name
|
|
767
|
+
? (mode === 'multiple' ? selectedValues : [value as string]).map((inputValue) => (
|
|
768
|
+
<input
|
|
769
|
+
key={inputValue}
|
|
770
|
+
type="hidden"
|
|
771
|
+
name={name}
|
|
772
|
+
form={form}
|
|
773
|
+
value={inputValue}
|
|
774
|
+
/>
|
|
775
|
+
))
|
|
776
|
+
: null}
|
|
677
777
|
</SelectItemParentProvider>
|
|
678
778
|
)
|
|
679
779
|
|