@tamagui/select 2.7.7 → 3.0.0-beta.1091.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Select.cjs +406 -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 +79 -79
- package/dist/cjs/SelectContent.native.cjs +29 -0
- package/dist/cjs/SelectContent.native.js +17 -22
- package/dist/cjs/SelectContent.native.js.map +1 -1
- package/dist/cjs/SelectImpl.cjs +305 -320
- package/dist/cjs/SelectImpl.native.cjs +29 -0
- package/dist/cjs/SelectImpl.native.js +16 -24
- package/dist/cjs/SelectImpl.native.js.map +1 -1
- package/dist/cjs/SelectItem.cjs +256 -205
- package/dist/cjs/SelectItem.native.cjs +288 -0
- package/dist/cjs/SelectItem.native.js +264 -213
- package/dist/cjs/SelectItem.native.js.map +1 -1
- package/dist/cjs/SelectItemText.cjs +48 -79
- package/dist/cjs/SelectItemText.native.cjs +73 -0
- package/dist/cjs/SelectItemText.native.js +49 -79
- package/dist/cjs/SelectItemText.native.js.map +1 -1
- package/dist/cjs/SelectScrollButton.cjs +110 -130
- package/dist/cjs/SelectScrollButton.native.cjs +37 -0
- package/dist/cjs/SelectScrollButton.native.js +24 -22
- package/dist/cjs/SelectScrollButton.native.js.map +1 -1
- package/dist/cjs/SelectTrigger.cjs +68 -84
- package/dist/cjs/SelectTrigger.native.cjs +90 -0
- package/dist/cjs/SelectTrigger.native.js +70 -66
- package/dist/cjs/SelectTrigger.native.js.map +1 -1
- package/dist/cjs/SelectViewport.cjs +121 -142
- package/dist/cjs/SelectViewport.native.cjs +72 -0
- package/dist/cjs/SelectViewport.native.js +53 -50
- package/dist/cjs/SelectViewport.native.js.map +1 -1
- package/dist/cjs/constants.cjs +20 -22
- package/dist/cjs/constants.native.cjs +40 -0
- package/dist/cjs/constants.native.js +20 -21
- package/dist/cjs/constants.native.js.map +1 -1
- package/dist/cjs/context.cjs +37 -48
- package/dist/cjs/context.native.cjs +57 -0
- package/dist/cjs/context.native.js +39 -49
- package/dist/cjs/context.native.js.map +1 -1
- package/dist/cjs/index.cjs +14 -11
- package/dist/cjs/index.native.cjs +26 -0
- package/dist/cjs/index.native.js +14 -10
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/selectionController.cjs +343 -0
- package/dist/cjs/selectionController.native.cjs +407 -0
- package/dist/cjs/selectionController.native.js +409 -0
- package/dist/cjs/selectionController.native.js.map +1 -0
- package/dist/cjs/types.cjs +9 -11
- package/dist/cjs/types.native.cjs +18 -0
- package/dist/cjs/types.native.js +9 -10
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/esm/Select.mjs +381 -443
- package/dist/esm/Select.mjs.map +1 -1
- package/dist/esm/Select.native.js +420 -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 +291 -295
- 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 +240 -181
- package/dist/esm/SelectItem.mjs.map +1 -1
- package/dist/esm/SelectItem.native.js +249 -191
- 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 +107 -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 +315 -0
- package/dist/esm/selectionController.mjs.map +1 -0
- package/dist/esm/selectionController.native.js +377 -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 +381 -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 +29 -0
- package/dist/jsx/SelectContent.native.js +17 -22
- package/dist/jsx/SelectContent.native.js.map +1 -1
- package/dist/jsx/SelectImpl.mjs +291 -295
- package/dist/jsx/SelectImpl.mjs.map +1 -1
- package/dist/jsx/SelectImpl.native.cjs +29 -0
- package/dist/jsx/SelectImpl.native.js +16 -24
- package/dist/jsx/SelectImpl.native.js.map +1 -1
- package/dist/jsx/SelectItem.mjs +240 -181
- package/dist/jsx/SelectItem.mjs.map +1 -1
- package/dist/jsx/SelectItem.native.cjs +288 -0
- package/dist/jsx/SelectItem.native.js +264 -213
- 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 +73 -0
- package/dist/jsx/SelectItemText.native.js +49 -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 +37 -0
- package/dist/jsx/SelectScrollButton.native.js +24 -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 +90 -0
- package/dist/jsx/SelectTrigger.native.js +70 -66
- package/dist/jsx/SelectTrigger.native.js.map +1 -1
- package/dist/jsx/SelectViewport.mjs +107 -119
- package/dist/jsx/SelectViewport.mjs.map +1 -1
- package/dist/jsx/SelectViewport.native.cjs +72 -0
- package/dist/jsx/SelectViewport.native.js +53 -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 +40 -0
- package/dist/jsx/constants.native.js +20 -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 +57 -0
- package/dist/jsx/context.native.js +39 -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 +26 -0
- package/dist/jsx/index.native.js +14 -10
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/selectionController.mjs +315 -0
- package/dist/jsx/selectionController.mjs.map +1 -0
- package/dist/jsx/selectionController.native.cjs +407 -0
- package/dist/jsx/selectionController.native.js +409 -0
- package/dist/jsx/selectionController.native.js.map +1 -0
- package/dist/jsx/types.mjs +0 -2
- package/dist/jsx/types.native.cjs +18 -0
- package/dist/jsx/types.native.js +9 -10
- package/dist/jsx/types.native.js.map +1 -1
- package/package.json +31 -30
- package/src/Select.tsx +478 -378
- package/src/SelectContent.tsx +46 -16
- package/src/SelectImpl.native.tsx +0 -8
- package/src/SelectImpl.tsx +149 -160
- package/src/SelectItem.tsx +280 -159
- package/src/SelectItemText.tsx +16 -33
- package/src/SelectScrollButton.native.tsx +13 -4
- package/src/SelectScrollButton.tsx +57 -42
- package/src/SelectTrigger.tsx +77 -53
- package/src/SelectViewport.native.tsx +42 -25
- package/src/SelectViewport.tsx +60 -60
- package/src/context.tsx +6 -2
- package/src/index.tsx +5 -2
- package/src/selectionController.ts +404 -0
- package/src/types.tsx +105 -59
- 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 +90 -0
- package/types/selectionController.d.ts.map +1 -0
- package/types/types.d.ts +66 -50
- 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,77 @@ 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.
|
|
87
|
+
const isEmptyValue = context.selectedValues.length === 0
|
|
88
|
+
|
|
89
|
+
// the label comes from the registry, which items fill in after mount and
|
|
90
|
+
// outside any context value, so this is the one registry subscriber
|
|
91
|
+
const [, rerender] = React.useReducer((version: number) => version + 1, 0)
|
|
92
|
+
useIsomorphicLayoutEffect(
|
|
93
|
+
() => itemParentContext.registry.subscribe(rerender),
|
|
94
|
+
[itemParentContext.registry]
|
|
95
|
+
)
|
|
71
96
|
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
97
|
+
// renderValue is synchronous for ssr and lazy item mounting
|
|
98
|
+
const registeredValue =
|
|
99
|
+
context.mode === 'multiple'
|
|
100
|
+
? context.selectedValues.map((value, index) => (
|
|
101
|
+
<React.Fragment key={value}>
|
|
102
|
+
{index > 0 ? ', ' : null}
|
|
103
|
+
{itemParentContext.registry.getItem(value)?.label ?? value}
|
|
104
|
+
</React.Fragment>
|
|
105
|
+
))
|
|
106
|
+
: (itemParentContext.registry.getItem(context.selectedValues[0])?.label ??
|
|
107
|
+
context.value)
|
|
75
108
|
const children =
|
|
76
|
-
childrenProp
|
|
109
|
+
childrenProp !== undefined
|
|
110
|
+
? childrenProp
|
|
111
|
+
: context.renderValue
|
|
112
|
+
? context.renderValue(context.value)
|
|
113
|
+
: registeredValue
|
|
77
114
|
const selectValueChildren = isEmptyValue ? (placeholder ?? children) : children
|
|
78
115
|
|
|
79
116
|
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)}
|
|
117
|
+
<SelectValueFrame pointerEvents="none" ref={composedRefs} {...props}>
|
|
118
|
+
{selectValueChildren}
|
|
92
119
|
</SelectValueFrame>
|
|
93
120
|
)
|
|
94
121
|
}
|
|
95
122
|
)
|
|
96
123
|
|
|
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
124
|
/* -------------------------------------------------------------------------------------------------
|
|
112
125
|
* SelectIcon
|
|
113
126
|
* -----------------------------------------------------------------------------------------------*/
|
|
114
127
|
|
|
115
|
-
export const SelectIcon = styled(
|
|
116
|
-
|
|
128
|
+
export const SelectIcon = styled(View, {
|
|
129
|
+
displayName: 'SelectIcon',
|
|
117
130
|
// @ts-ignore
|
|
118
131
|
'aria-hidden': true,
|
|
119
|
-
children: <Paragraph>▼</Paragraph>,
|
|
120
132
|
})
|
|
121
133
|
|
|
122
134
|
/* -------------------------------------------------------------------------------------------------
|
|
123
135
|
* SelectItemIndicator
|
|
124
136
|
* -----------------------------------------------------------------------------------------------*/
|
|
125
137
|
|
|
126
|
-
const SelectItemIndicatorFrame = styled(
|
|
127
|
-
|
|
138
|
+
export const SelectItemIndicatorFrame = styled(View, {
|
|
139
|
+
displayName: 'SelectItemIndicator',
|
|
128
140
|
})
|
|
129
141
|
|
|
130
|
-
type SelectItemIndicatorProps = SelectScopedProps<
|
|
142
|
+
export type SelectItemIndicatorProps = SelectScopedProps<
|
|
131
143
|
GetProps<typeof SelectItemIndicatorFrame>
|
|
132
144
|
>
|
|
133
145
|
|
|
134
|
-
const SelectItemIndicator =
|
|
135
|
-
|
|
146
|
+
export const SelectItemIndicator = createStyledHOC(
|
|
147
|
+
SelectItemIndicatorFrame,
|
|
148
|
+
function SelectItemIndicator(
|
|
149
|
+
props: {
|
|
150
|
+
scope?: string
|
|
151
|
+
},
|
|
152
|
+
forwardedRef
|
|
153
|
+
) {
|
|
136
154
|
const { scope, ...itemIndicatorProps } = props
|
|
137
155
|
const context = useSelectItemParentContext(scope)
|
|
138
156
|
const itemContext = useSelectItemContext(scope)
|
|
@@ -151,77 +169,68 @@ const SelectItemIndicator = React.forwardRef<TamaguiElement, SelectItemIndicator
|
|
|
151
169
|
* SelectIndicator
|
|
152
170
|
* -----------------------------------------------------------------------------------------------*/
|
|
153
171
|
|
|
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
|
-
},
|
|
172
|
+
export const SelectIndicatorFrame = styled(View, {
|
|
173
|
+
displayName: 'SelectIndicator',
|
|
174
|
+
position: 'absolute',
|
|
175
|
+
top: 0,
|
|
176
|
+
left: 0,
|
|
177
|
+
pointerEvents: 'none',
|
|
178
|
+
zIndex: 10,
|
|
174
179
|
})
|
|
175
180
|
|
|
176
181
|
export type SelectIndicatorProps = GetProps<typeof SelectIndicatorFrame>
|
|
177
182
|
|
|
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
|
-
|
|
183
|
+
export const SelectIndicator = createStyledHOC(
|
|
184
|
+
SelectIndicatorFrame,
|
|
185
|
+
function SelectIndicator(
|
|
186
|
+
{ scope, ...props }: SelectScopedProps<SelectIndicatorProps>,
|
|
187
|
+
forwardedRef
|
|
188
|
+
) {
|
|
189
|
+
const itemContext = useSelectItemParentContext(scope)
|
|
190
|
+
const context = useSelectContext(scope)
|
|
191
|
+
const [layout, setLayout] = React.useState<any>(null)
|
|
192
|
+
|
|
193
|
+
const rafRef = React.useRef<any>(0)
|
|
194
|
+
|
|
195
|
+
useIsomorphicLayoutEffect(() => {
|
|
196
|
+
const update = (index: number | null) => {
|
|
197
|
+
if (typeof index !== 'number') return
|
|
198
|
+
cancelAnimationFrame(rafRef.current)
|
|
199
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
200
|
+
const node = itemContext.listRef?.current?.[index]
|
|
201
|
+
if (node) {
|
|
202
|
+
setLayout({
|
|
203
|
+
width: Math.round(node.offsetWidth),
|
|
204
|
+
height: Math.round(node.offsetHeight),
|
|
205
|
+
x: Math.round(node.offsetLeft),
|
|
206
|
+
y: Math.round(node.offsetTop),
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
}
|
|
203
211
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
212
|
+
// use ref for initial read to avoid depending on state
|
|
213
|
+
if (context.open && context.activeIndexRef.current !== null) {
|
|
214
|
+
update(context.activeIndexRef.current)
|
|
215
|
+
}
|
|
208
216
|
|
|
209
|
-
|
|
210
|
-
|
|
217
|
+
return itemContext.activeIndexSubscribe(update)
|
|
218
|
+
}, [context.open, itemContext.listRef])
|
|
211
219
|
|
|
212
|
-
|
|
220
|
+
if (!layout) return null
|
|
213
221
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
222
|
+
return (
|
|
223
|
+
<SelectIndicatorFrame
|
|
224
|
+
ref={forwardedRef}
|
|
225
|
+
{...props}
|
|
226
|
+
width={layout.width}
|
|
227
|
+
height={layout.height}
|
|
228
|
+
x={layout.x}
|
|
229
|
+
y={layout.y}
|
|
230
|
+
/>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
)
|
|
225
234
|
|
|
226
235
|
/* -------------------------------------------------------------------------------------------------
|
|
227
236
|
* SelectGroup
|
|
@@ -234,96 +243,61 @@ type SelectGroupContextValue = { id: string }
|
|
|
234
243
|
const { Provider: SelectGroupContextProvider, useStyledContext: useSelectGroupContext } =
|
|
235
244
|
createStyledContext<SelectGroupContextValue>({ id: '' }, 'SelectGroup')
|
|
236
245
|
|
|
237
|
-
export const SelectGroupFrame = styled(
|
|
238
|
-
|
|
239
|
-
width: '100%',
|
|
246
|
+
export const SelectGroupFrame = styled(View, {
|
|
247
|
+
displayName: GROUP_NAME,
|
|
240
248
|
})
|
|
241
249
|
|
|
242
|
-
const
|
|
250
|
+
const NativeSelectFrame = styled(Text, {
|
|
251
|
+
displayName: 'NativeSelect',
|
|
243
252
|
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
253
|
})
|
|
254
|
+
const NativeSelect = NativeSelectFrame as any
|
|
288
255
|
|
|
289
256
|
type SelectGroupProps = SelectScopedProps<GetProps<typeof SelectGroupFrame>>
|
|
290
257
|
|
|
291
|
-
const SelectGroup =
|
|
292
|
-
|
|
258
|
+
export const SelectGroup = createStyledHOC(
|
|
259
|
+
SelectGroupFrame,
|
|
260
|
+
(props: SelectGroupProps, forwardedRef) => {
|
|
293
261
|
const { scope, ...groupProps } = props
|
|
294
262
|
const groupId = React.useId()
|
|
295
263
|
|
|
296
264
|
const context = useSelectContext(scope)
|
|
297
265
|
const itemParentContext = useSelectItemParentContext(scope)
|
|
298
|
-
const
|
|
266
|
+
const StyledNativeSelect = React.useContext(SelectNativeComponentContext)
|
|
299
267
|
const nativeSelectRef = React.useRef<HTMLSelectElement>(null)
|
|
300
268
|
|
|
301
269
|
const content = (() => {
|
|
302
270
|
if (itemParentContext.shouldRenderWebNative) {
|
|
303
271
|
return (
|
|
304
|
-
<
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
272
|
+
<NativeSelect
|
|
273
|
+
{...(StyledNativeSelect && {
|
|
274
|
+
render: StyledNativeSelect,
|
|
275
|
+
size: itemParentContext.size,
|
|
276
|
+
style: { appearance: 'none' },
|
|
277
|
+
})}
|
|
278
|
+
{...groupProps}
|
|
279
|
+
// @ts-ignore it's ok since render="select"
|
|
308
280
|
value={context.value}
|
|
281
|
+
multiple={context.mode === 'multiple'}
|
|
282
|
+
name={itemParentContext.name}
|
|
283
|
+
form={itemParentContext.form}
|
|
309
284
|
id={itemParentContext.id}
|
|
285
|
+
onChange={
|
|
286
|
+
((event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
287
|
+
const value =
|
|
288
|
+
context.mode === 'multiple'
|
|
289
|
+
? Array.from(
|
|
290
|
+
event.currentTarget.selectedOptions,
|
|
291
|
+
(option) => option.value
|
|
292
|
+
)
|
|
293
|
+
: event.currentTarget.value
|
|
294
|
+
itemParentContext.changeNativeValue(value, event.nativeEvent)
|
|
295
|
+
}) as any
|
|
296
|
+
}
|
|
297
|
+
ref={nativeSelectRef as any}
|
|
310
298
|
>
|
|
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>
|
|
299
|
+
{props.children}
|
|
300
|
+
</NativeSelect>
|
|
327
301
|
)
|
|
328
302
|
}
|
|
329
303
|
return (
|
|
@@ -353,39 +327,14 @@ SelectGroup.displayName = GROUP_NAME
|
|
|
353
327
|
|
|
354
328
|
const LABEL_NAME = 'SelectLabel'
|
|
355
329
|
|
|
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
|
-
},
|
|
330
|
+
export const SelectLabelFrame = styled(Text, {
|
|
331
|
+
displayName: LABEL_NAME,
|
|
384
332
|
})
|
|
385
333
|
|
|
386
334
|
export type SelectLabelProps = SelectScopedProps<GetProps<typeof SelectLabelFrame>>
|
|
387
335
|
|
|
388
|
-
const SelectLabel =
|
|
336
|
+
export const SelectLabel = createStyledHOC(
|
|
337
|
+
SelectLabelFrame,
|
|
389
338
|
(props: SelectLabelProps, forwardedRef) => {
|
|
390
339
|
const { scope, ...labelProps } = props
|
|
391
340
|
const context = useSelectItemParentContext(scope)
|
|
@@ -399,7 +348,6 @@ const SelectLabel = SelectLabelFrame.styleable<{ scope?: any }>(
|
|
|
399
348
|
<SelectLabelFrame
|
|
400
349
|
render="div"
|
|
401
350
|
id={groupContext.id}
|
|
402
|
-
size={context.size}
|
|
403
351
|
{...labelProps}
|
|
404
352
|
ref={forwardedRef}
|
|
405
353
|
/>
|
|
@@ -411,36 +359,10 @@ const SelectLabel = SelectLabelFrame.styleable<{ scope?: any }>(
|
|
|
411
359
|
* SelectSeparator
|
|
412
360
|
* -----------------------------------------------------------------------------------------------*/
|
|
413
361
|
|
|
414
|
-
export const SelectSeparator = styled(
|
|
415
|
-
|
|
362
|
+
export const SelectSeparator = styled(View, {
|
|
363
|
+
displayName: 'SelectSeparator',
|
|
416
364
|
})
|
|
417
365
|
|
|
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
366
|
const SelectSheetImpl = (props: SelectImplProps) => {
|
|
445
367
|
return <>{props.children}</>
|
|
446
368
|
}
|
|
@@ -449,36 +371,72 @@ const SelectSheetImpl = (props: SelectImplProps) => {
|
|
|
449
371
|
* Select
|
|
450
372
|
* -----------------------------------------------------------------------------------------------*/
|
|
451
373
|
|
|
452
|
-
export
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
374
|
+
export function SelectRoot<
|
|
375
|
+
Value extends string = string,
|
|
376
|
+
Multiple extends boolean | undefined = false,
|
|
377
|
+
>(props: SelectScopedProps<SelectProps<Value, Multiple>>) {
|
|
378
|
+
const adaptScope = `AdaptSelect${props.scope || ''}`
|
|
379
|
+
|
|
380
|
+
// open state lives here (above AdaptParent) so the Adapt handoff can drive
|
|
381
|
+
// the adapted Sheet's open/close and unmount timing directly, mirroring Dialog
|
|
382
|
+
const [open, requestOpenChange] = useControllableState<
|
|
383
|
+
boolean,
|
|
384
|
+
SelectOpenChangeDetails
|
|
385
|
+
>({
|
|
386
|
+
prop: props.open,
|
|
387
|
+
defaultProp: props.defaultOpen || false,
|
|
388
|
+
onChange: props.onOpenChange as any,
|
|
389
|
+
})
|
|
457
390
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
391
|
+
const handleAdaptOpenChange = React.useCallback(
|
|
392
|
+
(nextOpen: boolean) => {
|
|
393
|
+
requestOpenChange(
|
|
394
|
+
nextOpen,
|
|
395
|
+
createChangeEventDetails(
|
|
396
|
+
nextOpen ? 'trigger-press' : 'outside-press'
|
|
397
|
+
) as SelectOpenChangeDetails
|
|
398
|
+
)
|
|
399
|
+
},
|
|
400
|
+
[requestOpenChange]
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<AdaptParent
|
|
405
|
+
scope={adaptScope}
|
|
406
|
+
adaptChildren={props.children}
|
|
407
|
+
open={open}
|
|
408
|
+
onOpenChange={handleAdaptOpenChange}
|
|
409
|
+
>
|
|
410
|
+
<SelectInner
|
|
411
|
+
{...(props as SelectScopedProps<SelectProps<string, boolean>>)}
|
|
412
|
+
scope={props.scope}
|
|
413
|
+
adaptScope={adaptScope}
|
|
414
|
+
open={open}
|
|
415
|
+
requestOpenChange={requestOpenChange}
|
|
416
|
+
/>
|
|
417
|
+
</AdaptParent>
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export const Select = withStaticProperties(SelectRoot, {
|
|
422
|
+
Root: SelectRoot,
|
|
423
|
+
Adapt: Adapt,
|
|
424
|
+
Content: SelectContent,
|
|
425
|
+
Group: SelectGroup,
|
|
426
|
+
Icon: SelectIcon,
|
|
427
|
+
Item: SelectItem,
|
|
428
|
+
ItemIndicator: SelectItemIndicator,
|
|
429
|
+
ItemText: SelectItemText,
|
|
430
|
+
Label: SelectLabel,
|
|
431
|
+
ScrollDownButton: SelectScrollDownButton,
|
|
432
|
+
ScrollUpButton: SelectScrollUpButton,
|
|
433
|
+
Separator: SelectSeparator,
|
|
434
|
+
Trigger: SelectTrigger,
|
|
435
|
+
Value: SelectValue,
|
|
436
|
+
Viewport: SelectViewport,
|
|
437
|
+
Indicator: SelectIndicator,
|
|
438
|
+
FocusScope: FocusScopeController,
|
|
439
|
+
})
|
|
482
440
|
|
|
483
441
|
function useEmitter<A>() {
|
|
484
442
|
const listenersRef = React.useRef<Set<Function>>(new Set())
|
|
@@ -494,20 +452,30 @@ function useEmitter<A>() {
|
|
|
494
452
|
return [emit, subscribe] as const
|
|
495
453
|
}
|
|
496
454
|
|
|
497
|
-
function SelectInner(
|
|
455
|
+
function SelectInner(
|
|
456
|
+
props: SelectScopedProps<SelectProps<string, boolean>> & {
|
|
457
|
+
adaptScope: string
|
|
458
|
+
open: boolean
|
|
459
|
+
requestOpenChange: ControllableStateSetter<boolean, SelectOpenChangeDetails>
|
|
460
|
+
}
|
|
461
|
+
) {
|
|
498
462
|
const {
|
|
499
463
|
scope = '',
|
|
500
464
|
adaptScope,
|
|
501
465
|
native,
|
|
502
466
|
children,
|
|
503
|
-
open
|
|
504
|
-
|
|
505
|
-
|
|
467
|
+
open,
|
|
468
|
+
requestOpenChange,
|
|
469
|
+
defaultOpen: _defaultOpen,
|
|
470
|
+
onOpenChange: _onOpenChange,
|
|
506
471
|
value: valueProp,
|
|
507
472
|
defaultValue,
|
|
508
473
|
onValueChange,
|
|
474
|
+
multiple = false,
|
|
475
|
+
name,
|
|
476
|
+
form,
|
|
509
477
|
disablePreventBodyScroll,
|
|
510
|
-
size: sizeProp =
|
|
478
|
+
size: sizeProp = true,
|
|
511
479
|
onActiveChange,
|
|
512
480
|
dir,
|
|
513
481
|
id,
|
|
@@ -517,62 +485,94 @@ function SelectInner(props: SelectScopedProps<SelectProps> & { adaptScope: strin
|
|
|
517
485
|
} = props
|
|
518
486
|
|
|
519
487
|
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,
|
|
488
|
+
const SelectImpl = isAdapted ? SelectSheetImpl : SelectInlineImpl
|
|
489
|
+
const mode: SelectMode = multiple ? 'multiple' : 'single'
|
|
490
|
+
|
|
491
|
+
const [valueState, requestValueChange] = useControllableState<
|
|
492
|
+
SelectSelection,
|
|
493
|
+
SelectValueChangeDetails
|
|
494
|
+
>({
|
|
495
|
+
prop: valueProp as SelectSelection | undefined,
|
|
496
|
+
defaultProp: (defaultValue ?? (mode === 'multiple' ? [] : '')) as SelectSelection,
|
|
497
|
+
onChange: onValueChange as any,
|
|
534
498
|
transition: true,
|
|
535
499
|
})
|
|
536
500
|
|
|
537
|
-
React.
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
501
|
+
const value = React.useMemo(
|
|
502
|
+
() =>
|
|
503
|
+
mode === 'multiple'
|
|
504
|
+
? selectedValuesFromSelection(mode, valueState)
|
|
505
|
+
: typeof valueState === 'string'
|
|
506
|
+
? valueState
|
|
507
|
+
: '',
|
|
508
|
+
[mode, valueState]
|
|
509
|
+
)
|
|
510
|
+
const selectedValues = React.useMemo(
|
|
511
|
+
() => selectedValuesFromSelection(mode, value),
|
|
512
|
+
[mode, value]
|
|
513
|
+
)
|
|
542
514
|
|
|
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])
|
|
515
|
+
const [, rerenderRegistry] = React.useReducer((version) => version + 1, 0)
|
|
516
|
+
const registryRef = React.useRef<ReturnType<typeof createSelectItemRegistry> | null>(
|
|
517
|
+
null
|
|
518
|
+
)
|
|
519
|
+
if (!registryRef.current) {
|
|
520
|
+
registryRef.current = createSelectItemRegistry(rerenderRegistry)
|
|
558
521
|
}
|
|
522
|
+
const registry = registryRef.current
|
|
523
|
+
|
|
524
|
+
const controllerRef = React.useRef<ReturnType<
|
|
525
|
+
typeof createSelectSelectionController
|
|
526
|
+
> | null>(null)
|
|
527
|
+
if (!controllerRef.current) {
|
|
528
|
+
controllerRef.current = createSelectSelectionController({
|
|
529
|
+
mode,
|
|
530
|
+
value,
|
|
531
|
+
registry,
|
|
532
|
+
})
|
|
533
|
+
}
|
|
534
|
+
const controller = controllerRef.current
|
|
535
|
+
controller.setMode(mode)
|
|
536
|
+
controller.setValue(value)
|
|
559
537
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
538
|
+
React.useEffect(() => {
|
|
539
|
+
if (process.env.TAMAGUI_TARGET !== 'native' || !props.id) return
|
|
540
|
+
|
|
541
|
+
return registerFocusable(props.id, {
|
|
542
|
+
focusAndSelect: () => {
|
|
543
|
+
requestOpenChange(
|
|
544
|
+
(current) => !current,
|
|
545
|
+
createChangeEventDetails('trigger-press') as SelectOpenChangeDetails
|
|
546
|
+
)
|
|
547
|
+
},
|
|
548
|
+
focus: () => {},
|
|
549
|
+
})
|
|
550
|
+
}, [props.id, requestOpenChange])
|
|
551
|
+
|
|
552
|
+
// the active index is a ref plus an emitter, never React state: hover and
|
|
553
|
+
// arrow keys must not re-render the list. an item that has to react (focus,
|
|
554
|
+
// the roving tabindex) subscribes and updates only itself.
|
|
555
|
+
// null until first open so floating-ui starts from selectedIndex
|
|
565
556
|
const activeIndexRef = React.useRef<number | null>(null)
|
|
566
|
-
const [activeIndex, setActiveIndexState] = React.useState<number | null>(null)
|
|
567
557
|
|
|
568
|
-
const [
|
|
569
|
-
const [emitActiveIndex, activeIndexSubscribe] = useEmitter<number>()
|
|
558
|
+
const [emitActiveIndex, activeIndexSubscribe] = useEmitter<number | null>()
|
|
570
559
|
|
|
571
560
|
const selectedIndexRef = React.useRef<number | null>(null)
|
|
561
|
+
// a focus-driven scroll makes the browser re-dispatch mousemove under a
|
|
562
|
+
// pointer that never moved; items compare against this to ignore it
|
|
563
|
+
const lastPointerRef = React.useRef({ x: -1, y: -1 })
|
|
572
564
|
const listContentRef = React.useRef<string[]>([])
|
|
573
|
-
const
|
|
565
|
+
const listRef = React.useRef<Array<HTMLElement | null>>([])
|
|
574
566
|
const [valueNode, setValueNode] = React.useState<HTMLElement | null>(null)
|
|
575
|
-
|
|
567
|
+
const selectedIndex = Math.max(0, controller.selectionAnchorIndex())
|
|
568
|
+
listContentRef.current = registry.getTypeaheadLabels()
|
|
569
|
+
// the registry re-renders this on every registration and node change, so
|
|
570
|
+
// the list ref (floating-ui's focus target array) mirrors it here
|
|
571
|
+
const itemNodes = registry.getNodes()
|
|
572
|
+
listRef.current.length = itemNodes.length
|
|
573
|
+
for (let i = 0; i < itemNodes.length; i++) listRef.current[i] = itemNodes[i]
|
|
574
|
+
|
|
575
|
+
// Intentionally dependency-less: selectedIndexRef mirrors every render for non-reactive reads.
|
|
576
576
|
useIsomorphicLayoutEffect(() => {
|
|
577
577
|
selectedIndexRef.current = selectedIndex
|
|
578
578
|
})
|
|
@@ -583,28 +583,123 @@ function SelectInner(props: SelectScopedProps<SelectProps> & { adaptScope: strin
|
|
|
583
583
|
native === 'web' ||
|
|
584
584
|
(Array.isArray(native) && native.includes('web')))
|
|
585
585
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
586
|
+
const setActiveIndex = React.useCallback(
|
|
587
|
+
(index: number | null, details?: SelectActiveChangeDetails) => {
|
|
588
|
+
if (activeIndexRef.current === index) return
|
|
589
|
+
activeIndexRef.current = index
|
|
590
|
+
controller.setActiveIndex(index)
|
|
591
|
+
emitActiveIndex(index)
|
|
592
|
+
if (typeof index === 'number') {
|
|
593
|
+
const item = registry.getItems()[index]
|
|
594
|
+
if (item && details) {
|
|
595
|
+
onActiveChange?.(item.value, details)
|
|
594
596
|
}
|
|
595
597
|
}
|
|
596
598
|
},
|
|
597
|
-
[emitActiveIndex]
|
|
599
|
+
[controller, emitActiveIndex, onActiveChange, registry]
|
|
598
600
|
)
|
|
599
601
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
602
|
+
const activeDetails = React.useCallback(
|
|
603
|
+
(
|
|
604
|
+
reason: SelectActiveChangeDetails['reason'],
|
|
605
|
+
index: number,
|
|
606
|
+
event?: unknown
|
|
607
|
+
): SelectActiveChangeDetails => ({
|
|
608
|
+
reason,
|
|
609
|
+
event,
|
|
610
|
+
trigger: undefined,
|
|
611
|
+
index,
|
|
612
|
+
}),
|
|
613
|
+
[]
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
const moveActive = React.useCallback(
|
|
617
|
+
(direction: 1 | -1, event?: unknown) => {
|
|
618
|
+
const nextIndex = registry.nextEnabledIndex(activeIndexRef.current, direction)
|
|
619
|
+
if (nextIndex >= 0) {
|
|
620
|
+
setActiveIndex(nextIndex, activeDetails('keyboard', nextIndex, event))
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
[activeDetails, registry, setActiveIndex]
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
const typeaheadRef = React.useRef({ text: '', timeout: null as any })
|
|
627
|
+
const search = React.useCallback(
|
|
628
|
+
(text: string, event?: unknown) => {
|
|
629
|
+
clearTimeout(typeaheadRef.current.timeout)
|
|
630
|
+
typeaheadRef.current.text += text
|
|
631
|
+
let nextIndex = registry.findTypeaheadIndex(
|
|
632
|
+
typeaheadRef.current.text,
|
|
633
|
+
activeIndexRef.current
|
|
634
|
+
)
|
|
635
|
+
if (nextIndex < 0 && typeaheadRef.current.text.length > 1) {
|
|
636
|
+
typeaheadRef.current.text = text
|
|
637
|
+
nextIndex = registry.findTypeaheadIndex(text, activeIndexRef.current)
|
|
638
|
+
}
|
|
639
|
+
typeaheadRef.current.timeout = setTimeout(() => {
|
|
640
|
+
typeaheadRef.current.text = ''
|
|
641
|
+
}, 750)
|
|
642
|
+
if (nextIndex >= 0) {
|
|
643
|
+
setActiveIndex(nextIndex, activeDetails('keyboard', nextIndex, event))
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
[activeDetails, registry, setActiveIndex]
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
React.useEffect(() => {
|
|
650
|
+
return () => clearTimeout(typeaheadRef.current.timeout)
|
|
651
|
+
}, [])
|
|
652
|
+
|
|
653
|
+
React.useEffect(() => {
|
|
654
|
+
if (open) {
|
|
655
|
+
const nextIndex = controller.initialActiveIndex()
|
|
656
|
+
if (nextIndex >= 0) {
|
|
657
|
+
setActiveIndex(nextIndex, activeDetails('list-navigation', nextIndex))
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
setActiveIndex(null)
|
|
661
|
+
}
|
|
662
|
+
}, [open, selectedIndex, registry.getItems().length])
|
|
663
|
+
|
|
664
|
+
const selectValue = React.useCallback(
|
|
665
|
+
(nextItemValue: string, details: SelectValueChangeDetails) => {
|
|
666
|
+
const item = registry.getItem(nextItemValue)
|
|
667
|
+
if (!item || item.disabled) return
|
|
668
|
+
controller.setMode(mode)
|
|
669
|
+
controller.setValue(value)
|
|
670
|
+
const previousValue = controller.value
|
|
671
|
+
const nextValue = controller.toggle(nextItemValue)
|
|
672
|
+
requestValueChange(nextValue, details)
|
|
673
|
+
if (details.isCanceled) {
|
|
674
|
+
controller.setValue(previousValue)
|
|
675
|
+
return
|
|
676
|
+
}
|
|
677
|
+
if (controller.shouldCloseOnSelect) {
|
|
678
|
+
requestOpenChange(
|
|
679
|
+
false,
|
|
680
|
+
createChangeEventDetails(
|
|
681
|
+
details.reason === 'keyboard' ? 'keyboard' : 'item-press',
|
|
682
|
+
details.event,
|
|
683
|
+
details.trigger
|
|
684
|
+
) as SelectOpenChangeDetails
|
|
685
|
+
)
|
|
686
|
+
}
|
|
606
687
|
},
|
|
607
|
-
[
|
|
688
|
+
[controller, mode, registry, requestOpenChange, requestValueChange, value]
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
const changeNativeValue = React.useCallback(
|
|
692
|
+
(nextValue: SelectSelection, event: unknown) => {
|
|
693
|
+
const details = createChangeEventDetails(
|
|
694
|
+
'native-change',
|
|
695
|
+
event
|
|
696
|
+
) as SelectValueChangeDetails
|
|
697
|
+
requestValueChange(nextValue, details)
|
|
698
|
+
if (!details.isCanceled) {
|
|
699
|
+
controller.setValue(nextValue)
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
[controller, requestValueChange]
|
|
608
703
|
)
|
|
609
704
|
|
|
610
705
|
const content = (
|
|
@@ -612,28 +707,25 @@ function SelectInner(props: SelectScopedProps<SelectProps> & { adaptScope: strin
|
|
|
612
707
|
scopeName={scope}
|
|
613
708
|
scope={scope}
|
|
614
709
|
adaptScope={adaptScope}
|
|
615
|
-
|
|
710
|
+
mode={mode}
|
|
711
|
+
selectedValues={selectedValues}
|
|
712
|
+
registry={registry}
|
|
616
713
|
size={sizeProp}
|
|
617
714
|
activeIndexSubscribe={activeIndexSubscribe}
|
|
618
715
|
activeIndexRef={activeIndexRef}
|
|
619
|
-
|
|
620
|
-
setOpen={setOpen}
|
|
716
|
+
requestOpenChange={requestOpenChange}
|
|
621
717
|
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
|
-
}, [])}
|
|
718
|
+
name={name}
|
|
719
|
+
form={form}
|
|
720
|
+
selectValue={selectValue}
|
|
721
|
+
changeNativeValue={changeNativeValue}
|
|
633
722
|
shouldRenderWebNative={shouldRenderWebNative}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
723
|
+
setActiveIndex={setActiveIndex}
|
|
724
|
+
selectedIndex={selectedIndex}
|
|
725
|
+
lastPointerRef={lastPointerRef}
|
|
726
|
+
listRef={listRef}
|
|
727
|
+
moveActive={moveActive}
|
|
728
|
+
search={search}
|
|
637
729
|
>
|
|
638
730
|
<SelectProvider
|
|
639
731
|
scope={scope}
|
|
@@ -643,37 +735,45 @@ function SelectInner(props: SelectScopedProps<SelectProps> & { adaptScope: strin
|
|
|
643
735
|
dir={dir}
|
|
644
736
|
blockSelection={false}
|
|
645
737
|
fallback={false}
|
|
646
|
-
forceUpdate={forceUpdate}
|
|
647
738
|
valueNode={valueNode}
|
|
648
739
|
onValueNodeChange={setValueNode}
|
|
649
|
-
activeIndex={activeIndex}
|
|
650
740
|
activeIndexRef={activeIndexRef}
|
|
651
741
|
selectedIndex={selectedIndex}
|
|
652
742
|
setActiveIndex={setActiveIndex}
|
|
653
743
|
value={value}
|
|
744
|
+
mode={mode}
|
|
745
|
+
selectedValues={selectedValues}
|
|
654
746
|
open={open}
|
|
655
747
|
native={native}
|
|
656
748
|
renderValue={renderValue}
|
|
657
749
|
lazyMount={lazyMount}
|
|
658
750
|
>
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
</SelectImpl>
|
|
674
|
-
)}
|
|
675
|
-
</SelectSheetController>
|
|
751
|
+
{shouldRenderWebNative ? (
|
|
752
|
+
children
|
|
753
|
+
) : (
|
|
754
|
+
<SelectImpl
|
|
755
|
+
activeIndexRef={activeIndexRef}
|
|
756
|
+
listContentRef={listContentRef}
|
|
757
|
+
selectedIndexRef={selectedIndexRef}
|
|
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
|
|