@tradejs/app 1.0.9 → 1.0.11
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/README.md +16 -3
- package/bin/tradejs-app.mjs +146 -2
- package/next.config.mjs +0 -14
- package/package.json +35 -19
- package/src/app/actions/backtest.ts +54 -0
- package/src/app/actions/kline.ts +10 -5
- package/src/app/actions/scanner.ts +8 -3
- package/src/app/actions/strategies.ts +39 -0
- package/src/app/api/ai/route.ts +5 -4
- package/src/app/api/auth/[...nextauth]/route.ts +1 -1
- package/src/app/api/backtest/configs/route.ts +25 -0
- package/src/app/api/backtest/files/route.ts +5 -2
- package/src/app/api/backtest/order-log/[strategy]/[name]/route.ts +26 -2
- package/src/app/api/backtest/result/[strategy]/[name]/route.ts +27 -2
- package/src/app/api/backtest/runs/[jobId]/route.ts +124 -0
- package/src/app/api/backtest/runs/route.ts +45 -0
- package/src/app/api/backtest/test/[strategy]/[name]/route.ts +19 -2
- package/src/app/api/derivatives/summary/route.ts +8 -1
- package/src/app/api/install/route.ts +51 -0
- package/src/app/api/kline/[provider]/[universe]/[symbol]/[interval]/route.ts +1 -0
- package/src/app/api/kline/{[provider]/[symbol]/[interval]/route.ts → handler.ts} +61 -39
- package/src/app/api/scanner/[provider]/[universe]/route.ts +1 -0
- package/src/app/api/scanner/[provider]/route.ts +30 -12
- package/src/app/api/scanner/route.ts +3 -3
- package/src/app/api/signal/[symbol]/[signalId]/route.ts +1 -1
- package/src/app/api/strategies/ai/[cardId]/route.ts +22 -0
- package/src/app/api/strategies/ai/route.ts +139 -0
- package/src/app/api/strategies/replay/[cardId]/route.ts +18 -0
- package/src/app/api/strategies/replay/route.ts +44 -0
- package/src/app/api/strategies/runtime/route.ts +831 -0
- package/src/app/api/user/runtime-deployments/[deploymentId]/route.ts +25 -0
- package/src/app/api/user/runtime-deployments/route.ts +94 -0
- package/src/app/api/user/runtime-strategy-configs/route.ts +241 -0
- package/src/app/api/user/settings/route.ts +18 -1
- package/src/app/api/user/trading-accounts/[accountId]/route.ts +35 -0
- package/src/app/api/user/trading-accounts/route.ts +134 -0
- package/src/app/auth.ts +1 -19
- package/src/app/components/Backtest/CompareList/index.tsx +1 -1
- package/src/app/components/Backtest/ResultsPageClient.tsx +376 -0
- package/src/app/components/Backtest/TestCard/ActionsMenu/index.tsx +123 -0
- package/src/app/components/Backtest/TestCard/Chart/index.tsx +1 -1
- package/src/app/components/Backtest/TestCard/CompareButton/index.tsx +1 -1
- package/src/app/components/Backtest/TestCard/ConfigDrawer/index.tsx +28 -12
- package/src/app/components/Backtest/TestCard/DeleteButton/index.tsx +33 -17
- package/src/app/components/Backtest/TestCard/FavoriteIndicator/index.tsx +2 -2
- package/src/app/components/Backtest/TestCard/OpenDashboardButton/index.tsx +10 -1
- package/src/app/components/Backtest/TestCard/OrdersDrawer/index.tsx +128 -0
- package/src/app/components/Backtest/TestCard/Root/index.tsx +18 -4
- package/src/app/components/Backtest/TestCard/Stat/index.tsx +2 -0
- package/src/app/components/Backtest/TestCard/StatDrawer/index.tsx +66 -0
- package/src/app/components/Backtest/TestCard/index.ts +6 -2
- package/src/app/components/Backtest/TestList/index.tsx +4 -9
- package/src/app/components/Dashboard/AiDrawer/index.tsx +1 -1
- package/src/app/components/Dashboard/KlineChart/hooks/useBacktest.ts +1 -1
- package/src/app/components/Dashboard/KlineChart/hooks/useBbIndicator.ts +10 -5
- package/src/app/components/Dashboard/KlineChart/hooks/useBtcCorrelation.ts +1 -1
- package/src/app/components/Dashboard/KlineChart/hooks/useBtcIndicator.ts +4 -1
- package/src/app/components/Dashboard/KlineChart/hooks/useEmaIndicator.ts +3 -1
- package/src/app/components/Dashboard/KlineChart/hooks/usePluginIndicators.ts +1 -1
- package/src/app/components/Dashboard/KlineChart/hooks/useSetup.ts +1 -1
- package/src/app/components/Dashboard/KlineChart/hooks/useSignal.ts +1 -1
- package/src/app/components/Dashboard/KlineChart/hooks/useSpreadIndicator.ts +1 -1
- package/src/app/components/Dashboard/KlineChart/hooks/useTrendLine.ts +1 -1
- package/src/app/components/Dashboard/KlineChart/hooks/useWmaIndicator.ts +3 -1
- package/src/app/components/Dashboard/KlineChart/index.tsx +21 -7
- package/src/app/components/Dashboard/MainChart/index.tsx +3 -22
- package/src/app/components/Shared/AppShell.tsx +2 -2
- package/src/app/components/Shared/BulkSelection/index.tsx +140 -0
- package/src/app/components/Shared/BulkSelection/useBulkSelection.ts +97 -0
- package/src/app/components/Shared/BulkSelection/utils.ts +126 -0
- package/src/app/components/Shared/Filters/Backtest/index.tsx +51 -51
- package/src/app/components/Shared/Filters/FavoriteIndicator/index.tsx +5 -3
- package/src/app/components/Shared/Filters/Indicators/index.tsx +2 -2
- package/src/app/components/Shared/Filters/Interval/index.tsx +1 -1
- package/src/app/components/Shared/Filters/Provider/index.tsx +8 -2
- package/src/app/components/Shared/Filters/Symbol/index.tsx +4 -3
- package/src/app/components/Shared/Filters/Universe/index.tsx +36 -0
- package/src/app/components/Shared/Filters/index.ts +2 -0
- package/src/app/components/Shared/OrdersDrawer.tsx +629 -0
- package/src/app/components/Shared/Sidebar/AccountSettingsDrawer.tsx +55 -28
- package/src/app/components/Shared/Sidebar/TradingAccountsPanel.tsx +445 -0
- package/src/app/components/Shared/Sidebar/index.tsx +17 -3
- package/src/app/components/Strategies/AdvancedMetricsPanel.tsx +727 -0
- package/src/app/components/Strategies/RuntimeStrategyCard.tsx +2204 -0
- package/src/app/components/Strategies/RuntimeStrategyCardSkeleton.tsx +40 -0
- package/src/app/components/Strategies/RuntimeStrategyChart.tsx +121 -0
- package/src/app/components/Strategies/RuntimeStrategyConfigDrawer.tsx +458 -0
- package/src/app/components/Strategies/StrategySnapshotCard.tsx +2890 -0
- package/src/app/components/Strategies/StrategySnapshotChart.tsx +94 -0
- package/src/app/components/Strategies/StrategySnapshotList.tsx +82 -0
- package/src/app/components/UI/Segment/index.tsx +8 -1
- package/src/app/components/UI/Select/index.tsx +24 -13
- package/src/app/components/UI/SelectWithSearch/index.tsx +46 -5
- package/src/app/layout.tsx +1 -1
- package/src/app/lib/backtestJobs.ts +925 -0
- package/src/app/lib/cardPageLayout.ts +1 -0
- package/src/app/lib/connectorCreator.ts +28 -0
- package/src/app/lib/currentUser.ts +1 -1
- package/src/app/lib/errorMessage.ts +34 -0
- package/src/app/lib/installation.ts +83 -0
- package/src/app/lib/marketDefaults.ts +12 -0
- package/src/app/lib/marketRoutes.ts +58 -0
- package/src/app/lib/runtimeStrategies.ts +1376 -0
- package/src/app/lib/runtimeStrategyConfigForm.ts +86 -0
- package/src/app/provider.tsx +1 -1
- package/src/app/routes/backtest/page.tsx +1100 -302
- package/src/app/routes/dashboard/{[provider]/[symbol]/[interval]/page.tsx → Dashboard.tsx} +76 -35
- package/src/app/routes/dashboard/[provider]/[universe]/[symbol]/[interval]/page.tsx +1 -0
- package/src/app/routes/dashboard/page.tsx +15 -3
- package/src/app/routes/derivatives/page.tsx +1222 -165
- package/src/app/routes/install/page.tsx +175 -0
- package/src/app/routes/signin/page.tsx +7 -0
- package/src/app/routes/strategies/StrategiesPageClient.tsx +593 -0
- package/src/app/routes/strategies/[mode]/page.tsx +20 -0
- package/src/app/routes/strategies/page.tsx +7 -0
- package/src/app/store/ai.ts +1 -1
- package/src/app/store/data.ts +73 -17
- package/src/app/store/filters.ts +1 -0
- package/src/app/store/marketKlineStream.ts +87 -0
- package/src/app/store/tests.ts +83 -45
- package/src/app/store/tickers.ts +59 -22
- package/src/proxy.ts +4 -0
- package/tsconfig.json +17 -12
- package/src/app/components/Backtest/TestCard/OpenReportButton/index.tsx +0 -24
- package/src/app/routes/backtest/[test]/page.tsx +0 -33
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { Box, Text } from '@chakra-ui/react';
|
|
5
|
+
import { Chart, useChart } from '@chakra-ui/charts';
|
|
6
|
+
import {
|
|
7
|
+
CartesianGrid,
|
|
8
|
+
Line,
|
|
9
|
+
LineChart,
|
|
10
|
+
ResponsiveContainer,
|
|
11
|
+
Tooltip,
|
|
12
|
+
XAxis,
|
|
13
|
+
YAxis,
|
|
14
|
+
} from 'recharts';
|
|
15
|
+
import { format } from 'date-fns';
|
|
16
|
+
import type { SimpleOrderLogData } from '@tradejs/types';
|
|
17
|
+
|
|
18
|
+
export const StrategySnapshotChart = ({
|
|
19
|
+
orderLog,
|
|
20
|
+
height = '350px',
|
|
21
|
+
emptyText = 'No chart data for the selected run.',
|
|
22
|
+
}: {
|
|
23
|
+
orderLog: SimpleOrderLogData;
|
|
24
|
+
height?: string | number;
|
|
25
|
+
emptyText?: string;
|
|
26
|
+
}) => {
|
|
27
|
+
const chartData = useMemo(
|
|
28
|
+
() => ({
|
|
29
|
+
data: orderLog.map(([timestamp, amount]) => ({
|
|
30
|
+
timestamp: format(timestamp, 'dd.MM'),
|
|
31
|
+
equity: amount,
|
|
32
|
+
})),
|
|
33
|
+
series: [
|
|
34
|
+
{
|
|
35
|
+
name: 'equity',
|
|
36
|
+
color: 'teal.solid',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
}),
|
|
40
|
+
[orderLog],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const chart = useChart(chartData as any);
|
|
44
|
+
|
|
45
|
+
if (!orderLog.length) {
|
|
46
|
+
return (
|
|
47
|
+
<Box
|
|
48
|
+
w="100%"
|
|
49
|
+
minW="600px"
|
|
50
|
+
h={height}
|
|
51
|
+
display="flex"
|
|
52
|
+
alignItems="center"
|
|
53
|
+
justifyContent="center"
|
|
54
|
+
>
|
|
55
|
+
<Text color="gray.500">{emptyText}</Text>
|
|
56
|
+
</Box>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const values = orderLog.map(([, amount]) => amount);
|
|
61
|
+
const minValue = Math.min(...values);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Box w="100%" minW="600px" h={height} pr={2}>
|
|
65
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
66
|
+
<Chart.Root maxH="md" chart={chart}>
|
|
67
|
+
<LineChart data={chart.data}>
|
|
68
|
+
<CartesianGrid stroke={chart.color('border')} vertical={false} />
|
|
69
|
+
<XAxis dataKey="timestamp" />
|
|
70
|
+
<YAxis
|
|
71
|
+
tickCount={10}
|
|
72
|
+
domain={[Math.min(minValue - 10, 0), 'auto']}
|
|
73
|
+
/>
|
|
74
|
+
<Tooltip
|
|
75
|
+
animationDuration={100}
|
|
76
|
+
cursor={false}
|
|
77
|
+
content={<Chart.Tooltip />}
|
|
78
|
+
/>
|
|
79
|
+
{chart.series.map((item) => (
|
|
80
|
+
<Line
|
|
81
|
+
key={item.name as string}
|
|
82
|
+
isAnimationActive={false}
|
|
83
|
+
dataKey={chart.key(item.name) as string}
|
|
84
|
+
stroke={chart.color(item.color)}
|
|
85
|
+
strokeWidth={2}
|
|
86
|
+
dot={false}
|
|
87
|
+
/>
|
|
88
|
+
))}
|
|
89
|
+
</LineChart>
|
|
90
|
+
</Chart.Root>
|
|
91
|
+
</ResponsiveContainer>
|
|
92
|
+
</Box>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import AutoSizer from 'react-virtualized-auto-sizer';
|
|
5
|
+
import { FixedSizeList, type ListChildComponentProps } from 'react-window';
|
|
6
|
+
import { Box } from '@chakra-ui/react';
|
|
7
|
+
import type { StrategyChartSnapshot } from '@tradejs/types';
|
|
8
|
+
import { StrategySnapshotCard } from './StrategySnapshotCard';
|
|
9
|
+
|
|
10
|
+
const SNAPSHOT_CARD_ROW_HEIGHT = 620;
|
|
11
|
+
|
|
12
|
+
interface StrategySnapshotListProps {
|
|
13
|
+
strategies: StrategyChartSnapshot[];
|
|
14
|
+
mode: 'replay' | 'ai';
|
|
15
|
+
selectedCardIds: ReadonlySet<string>;
|
|
16
|
+
emptyText: string;
|
|
17
|
+
onDeleted: (cardId: string) => void;
|
|
18
|
+
onToggleSelection: (cardId: string, checked: boolean) => void;
|
|
19
|
+
overscan?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const StrategySnapshotList = ({
|
|
23
|
+
strategies,
|
|
24
|
+
mode,
|
|
25
|
+
selectedCardIds,
|
|
26
|
+
emptyText,
|
|
27
|
+
onDeleted,
|
|
28
|
+
onToggleSelection,
|
|
29
|
+
overscan = 1,
|
|
30
|
+
}: StrategySnapshotListProps) => {
|
|
31
|
+
const itemKey = useCallback(
|
|
32
|
+
(index: number) => strategies[index]?.cardId ?? String(index),
|
|
33
|
+
[strategies],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const Row = useCallback(
|
|
37
|
+
({ index, style }: ListChildComponentProps) => {
|
|
38
|
+
const strategy = strategies[index];
|
|
39
|
+
if (!strategy) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Box style={style}>
|
|
45
|
+
<StrategySnapshotCard
|
|
46
|
+
snapshot={strategy}
|
|
47
|
+
mode={mode}
|
|
48
|
+
onDeleted={onDeleted}
|
|
49
|
+
selected={selectedCardIds.has(strategy.cardId)}
|
|
50
|
+
onToggleSelection={onToggleSelection}
|
|
51
|
+
emptyText={emptyText}
|
|
52
|
+
/>
|
|
53
|
+
</Box>
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
[
|
|
57
|
+
emptyText,
|
|
58
|
+
mode,
|
|
59
|
+
onDeleted,
|
|
60
|
+
onToggleSelection,
|
|
61
|
+
selectedCardIds,
|
|
62
|
+
strategies,
|
|
63
|
+
],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<AutoSizer>
|
|
68
|
+
{({ height, width }) => (
|
|
69
|
+
<FixedSizeList
|
|
70
|
+
height={height}
|
|
71
|
+
width={width}
|
|
72
|
+
itemCount={strategies.length}
|
|
73
|
+
itemSize={SNAPSHOT_CARD_ROW_HEIGHT}
|
|
74
|
+
overscanCount={overscan}
|
|
75
|
+
itemKey={itemKey}
|
|
76
|
+
>
|
|
77
|
+
{Row}
|
|
78
|
+
</FixedSizeList>
|
|
79
|
+
)}
|
|
80
|
+
</AutoSizer>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
@@ -5,14 +5,21 @@ import { Items } from '@tradejs/types';
|
|
|
5
5
|
|
|
6
6
|
interface SegmentProps {
|
|
7
7
|
defaultValue: string;
|
|
8
|
+
value?: string;
|
|
8
9
|
items: Items;
|
|
9
10
|
onChange?: (value: string | null) => void;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export const Segment = ({
|
|
13
|
+
export const Segment = ({
|
|
14
|
+
defaultValue,
|
|
15
|
+
value,
|
|
16
|
+
items,
|
|
17
|
+
onChange,
|
|
18
|
+
}: SegmentProps) => {
|
|
13
19
|
return (
|
|
14
20
|
<SegmentGroup.Root
|
|
15
21
|
size="md"
|
|
22
|
+
value={value}
|
|
16
23
|
defaultValue={defaultValue}
|
|
17
24
|
onValueChange={(e) => onChange?.(e.value)}
|
|
18
25
|
>
|
|
@@ -15,8 +15,10 @@ interface SelectProps {
|
|
|
15
15
|
value?: string[];
|
|
16
16
|
items: Items;
|
|
17
17
|
placeholder?: string;
|
|
18
|
+
emptyState?: string;
|
|
18
19
|
width?: string | number;
|
|
19
20
|
multiple?: boolean;
|
|
21
|
+
disabled?: boolean;
|
|
20
22
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
21
23
|
onChange?: (value: string[]) => void;
|
|
22
24
|
onOpenChange?: (open: boolean) => void;
|
|
@@ -28,7 +30,9 @@ export const Select = ({
|
|
|
28
30
|
items,
|
|
29
31
|
multiple = false,
|
|
30
32
|
placeholder = 'Select',
|
|
33
|
+
emptyState = 'No items found',
|
|
31
34
|
width = '320px',
|
|
35
|
+
disabled = false,
|
|
32
36
|
size = 'sm',
|
|
33
37
|
onChange,
|
|
34
38
|
onOpenChange,
|
|
@@ -50,6 +54,7 @@ export const Select = ({
|
|
|
50
54
|
size={size}
|
|
51
55
|
multiple={multiple}
|
|
52
56
|
width={width}
|
|
57
|
+
disabled={disabled}
|
|
53
58
|
>
|
|
54
59
|
<UISelect.HiddenSelect />
|
|
55
60
|
<UISelect.Control>
|
|
@@ -63,19 +68,25 @@ export const Select = ({
|
|
|
63
68
|
<Portal>
|
|
64
69
|
<UISelect.Positioner>
|
|
65
70
|
<UISelect.Content>
|
|
66
|
-
{collection.items.
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
{collection.items.length === 0 ? (
|
|
72
|
+
<Span color="fg.muted" px="3" py="2" textStyle="sm">
|
|
73
|
+
{emptyState}
|
|
74
|
+
</Span>
|
|
75
|
+
) : (
|
|
76
|
+
collection.items.map((item) => (
|
|
77
|
+
<UISelect.Item item={item} key={item.value}>
|
|
78
|
+
<Stack gap="0">
|
|
79
|
+
<UISelect.ItemText>{item.label}</UISelect.ItemText>
|
|
80
|
+
{item.description && (
|
|
81
|
+
<Span color="fg.muted" textStyle="xs">
|
|
82
|
+
{item.description}
|
|
83
|
+
</Span>
|
|
84
|
+
)}
|
|
85
|
+
</Stack>
|
|
86
|
+
<UISelect.ItemIndicator />
|
|
87
|
+
</UISelect.Item>
|
|
88
|
+
))
|
|
89
|
+
)}
|
|
79
90
|
</UISelect.Content>
|
|
80
91
|
</UISelect.Positioner>
|
|
81
92
|
</Portal>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import _ from 'lodash';
|
|
4
|
-
import { useEffect, useState } from 'react';
|
|
4
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
5
5
|
import {
|
|
6
6
|
Portal,
|
|
7
7
|
Stack,
|
|
@@ -15,12 +15,14 @@ import { Items } from '@tradejs/types';
|
|
|
15
15
|
|
|
16
16
|
interface SelectWithSearchProps {
|
|
17
17
|
defaultValue: string[];
|
|
18
|
+
value?: string[];
|
|
18
19
|
defaultInputValue?: string;
|
|
19
20
|
items: Items;
|
|
20
21
|
placeholder?: string;
|
|
21
22
|
emptyState?: string;
|
|
22
23
|
width?: string | number;
|
|
23
24
|
multiple?: boolean;
|
|
25
|
+
disabled?: boolean;
|
|
24
26
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
25
27
|
onChange?: (value: string[]) => void;
|
|
26
28
|
onOpenChange?: (open: boolean) => void;
|
|
@@ -28,9 +30,11 @@ interface SelectWithSearchProps {
|
|
|
28
30
|
|
|
29
31
|
export const SelectWithSearch = ({
|
|
30
32
|
defaultValue,
|
|
33
|
+
value,
|
|
31
34
|
defaultInputValue,
|
|
32
35
|
items,
|
|
33
36
|
multiple = false,
|
|
37
|
+
disabled = false,
|
|
34
38
|
placeholder = 'Select',
|
|
35
39
|
emptyState = 'No items found',
|
|
36
40
|
width = '320px',
|
|
@@ -39,8 +43,29 @@ export const SelectWithSearch = ({
|
|
|
39
43
|
onOpenChange,
|
|
40
44
|
}: SelectWithSearchProps) => {
|
|
41
45
|
const { contains } = useFilter({ sensitivity: 'base' });
|
|
42
|
-
const
|
|
43
|
-
|
|
46
|
+
const defaultSelectedValue = defaultValue[0];
|
|
47
|
+
const [selectedValue, setSelectedValue] = useState(defaultValue);
|
|
48
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
49
|
+
const currentValue = value ?? selectedValue;
|
|
50
|
+
const getSelectedInputValue = useCallback(
|
|
51
|
+
(selected: string[]) => {
|
|
52
|
+
if (multiple) return '';
|
|
53
|
+
|
|
54
|
+
const selectedItem = selected[0];
|
|
55
|
+
if (!selectedItem) return '';
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
items.find((item) => item.value === selectedItem)?.label ??
|
|
59
|
+
(selectedItem === defaultSelectedValue
|
|
60
|
+
? defaultInputValue
|
|
61
|
+
: undefined) ??
|
|
62
|
+
selectedItem
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
[defaultInputValue, defaultSelectedValue, items, multiple],
|
|
66
|
+
);
|
|
67
|
+
const [inputValue, setInputValue] = useState(() =>
|
|
68
|
+
getSelectedInputValue(currentValue),
|
|
44
69
|
);
|
|
45
70
|
|
|
46
71
|
const { collection, filter, set } = useListCollection({
|
|
@@ -52,26 +77,42 @@ export const SelectWithSearch = ({
|
|
|
52
77
|
set(items);
|
|
53
78
|
}, [items, set]);
|
|
54
79
|
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!isOpen) {
|
|
82
|
+
setInputValue(getSelectedInputValue(currentValue));
|
|
83
|
+
}
|
|
84
|
+
}, [currentValue, getSelectedInputValue, isOpen]);
|
|
85
|
+
|
|
55
86
|
return (
|
|
56
87
|
<Combobox.Root
|
|
57
88
|
collection={collection}
|
|
58
|
-
|
|
89
|
+
{...(value ? { value } : { defaultValue })}
|
|
59
90
|
inputValue={inputValue}
|
|
60
|
-
onValueChange={(details) =>
|
|
91
|
+
onValueChange={(details) => {
|
|
92
|
+
if (!value) {
|
|
93
|
+
setSelectedValue(details.value);
|
|
94
|
+
}
|
|
95
|
+
setInputValue(getSelectedInputValue(details.value));
|
|
96
|
+
onChange?.(details.value);
|
|
97
|
+
}}
|
|
61
98
|
onInputValueChange={(e) => {
|
|
62
99
|
filter(e.inputValue);
|
|
63
100
|
setInputValue(e.inputValue);
|
|
64
101
|
}}
|
|
65
102
|
onOpenChange={(details) => {
|
|
103
|
+
setIsOpen(details.open);
|
|
66
104
|
onOpenChange?.(details.open);
|
|
67
105
|
if (details.open) {
|
|
68
106
|
filter('');
|
|
69
107
|
setInputValue('');
|
|
108
|
+
} else {
|
|
109
|
+
setInputValue(getSelectedInputValue(currentValue));
|
|
70
110
|
}
|
|
71
111
|
}}
|
|
72
112
|
width={width}
|
|
73
113
|
multiple={multiple}
|
|
74
114
|
size={size}
|
|
115
|
+
disabled={disabled}
|
|
75
116
|
openOnClick
|
|
76
117
|
>
|
|
77
118
|
<Combobox.Control>
|
package/src/app/layout.tsx
CHANGED