@lifi/widget 4.1.0 → 4.2.0
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/esm/AppDefault.js +14 -0
- package/dist/esm/AppDefault.js.map +1 -1
- package/dist/esm/components/AmountInput/AmountInput.js +2 -1
- package/dist/esm/components/AmountInput/AmountInput.js.map +1 -1
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +4 -2
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js +1 -1
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js.map +1 -1
- package/dist/esm/components/AmountInput/PriceFormHelperText.js +4 -2
- package/dist/esm/components/AmountInput/PriceFormHelperText.js.map +1 -1
- package/dist/esm/components/AmountInputCard/AmountInputCard.style.js +98 -0
- package/dist/esm/components/AmountInputCard/AmountInputCard.style.js.map +1 -0
- package/dist/esm/components/AmountInputCard/AmountInputCardPair.js +25 -0
- package/dist/esm/components/AmountInputCard/AmountInputCardPair.js.map +1 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.js +35 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.js.map +1 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js +11 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js.map +1 -0
- package/dist/esm/components/AmountInputCard/FiatValueToggle.js +41 -0
- package/dist/esm/components/AmountInputCard/FiatValueToggle.js.map +1 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.js +80 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.js.map +1 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.style.js +43 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.style.js.map +1 -0
- package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js +137 -0
- package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js.map +1 -0
- package/dist/esm/components/AmountInputCard/SendAmountCard.js +102 -0
- package/dist/esm/components/AmountInputCard/SendAmountCard.js.map +1 -0
- package/dist/esm/components/Avatar/Avatar.js +12 -3
- package/dist/esm/components/Avatar/Avatar.js.map +1 -1
- package/dist/esm/components/Chains/VirtualizedChainList.js +2 -1
- package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
- package/dist/esm/components/Header/HeaderNavigationTabs.js +14 -1
- package/dist/esm/components/Header/HeaderNavigationTabs.js.map +1 -1
- package/dist/esm/components/IconCircle/IconCircle.style.js +1 -1
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js +99 -0
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js.map +1 -0
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js +52 -0
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js.map +1 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.js +115 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.js.map +1 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js +89 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js.map +1 -0
- package/dist/esm/components/LimitPriceCard/LimitPricePresets.js +91 -0
- package/dist/esm/components/LimitPriceCard/LimitPricePresets.js.map +1 -0
- package/dist/esm/components/RouteCard/RouteProviderCard.js +118 -0
- package/dist/esm/components/RouteCard/RouteProviderCard.js.map +1 -0
- package/dist/esm/components/Routes/LimitOrderRoutes.js +34 -0
- package/dist/esm/components/Routes/LimitOrderRoutes.js.map +1 -0
- package/dist/esm/components/Routes/Routes.js +2 -2
- package/dist/esm/components/Routes/Routes.js.map +1 -1
- package/dist/esm/components/SettingsPanel/SettingsPanel.js +51 -0
- package/dist/esm/components/SettingsPanel/SettingsPanel.js.map +1 -0
- package/dist/esm/components/SettingsPanel/SettingsPanel.style.js +33 -0
- package/dist/esm/components/SettingsPanel/SettingsPanel.style.js.map +1 -0
- package/dist/esm/components/SettingsPanel/utils.js +14 -0
- package/dist/esm/components/SettingsPanel/utils.js.map +1 -0
- package/dist/esm/components/SwapButton/SwapButton.js +50 -0
- package/dist/esm/components/SwapButton/SwapButton.js.map +1 -0
- package/dist/esm/components/SwapButton/SwapButton.style.js +24 -0
- package/dist/esm/components/SwapButton/SwapButton.style.js.map +1 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.js +52 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.js.map +1 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.style.js +39 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.style.js.map +1 -0
- package/dist/esm/config/version.d.ts +1 -1
- package/dist/esm/config/version.js +1 -1
- package/dist/esm/config/version.js.map +1 -1
- package/dist/esm/hooks/useChainSearch.js +2 -1
- package/dist/esm/hooks/useChainSearch.js.map +1 -1
- package/dist/esm/hooks/useLimitMarketRate.js +24 -0
- package/dist/esm/hooks/useLimitMarketRate.js.map +1 -0
- package/dist/esm/hooks/useLinkedLimitFields.js +49 -0
- package/dist/esm/hooks/useLinkedLimitFields.js.map +1 -0
- package/dist/esm/hooks/useRoutes.js +23 -6
- package/dist/esm/hooks/useRoutes.js.map +1 -1
- package/dist/esm/hooks/useTokens.js +16 -14
- package/dist/esm/hooks/useTokens.js.map +1 -1
- package/dist/esm/hooks/useWideVariant.js +5 -2
- package/dist/esm/hooks/useWideVariant.js.map +1 -1
- package/dist/esm/i18n/en.json +22 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/pages/MainPage/LimitPageContent.js +23 -0
- package/dist/esm/pages/MainPage/LimitPageContent.js.map +1 -0
- package/dist/esm/pages/MainPage/MainPage.js +19 -22
- package/dist/esm/pages/MainPage/MainPage.js.map +1 -1
- package/dist/esm/pages/MainPage/MainPageActions.js +28 -0
- package/dist/esm/pages/MainPage/MainPageActions.js.map +1 -0
- package/dist/esm/pages/MainPage/ReviewButton.js +9 -2
- package/dist/esm/pages/MainPage/ReviewButton.js.map +1 -1
- package/dist/esm/pages/RoutePriorityPage.js +48 -0
- package/dist/esm/pages/RoutePriorityPage.js.map +1 -0
- package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js +2 -2
- package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js.map +1 -1
- package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js +12 -27
- package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js.map +1 -1
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js +133 -0
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js.map +1 -0
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js +14 -72
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js.map +1 -1
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js +26 -14
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js.map +1 -1
- package/dist/esm/pages/SettingsPage/ThemeSettings.js +4 -0
- package/dist/esm/pages/SettingsPage/ThemeSettings.js.map +1 -1
- package/dist/esm/pages/TransactionPage/TransactionReview.js +4 -1
- package/dist/esm/pages/TransactionPage/TransactionReview.js.map +1 -1
- package/dist/esm/providers/I18nProvider/I18nProvider.js +1 -1
- package/dist/esm/providers/I18nProvider/enResource.d.ts +1 -1
- package/dist/esm/providers/I18nProvider/enResource.js +1 -1
- package/dist/esm/providers/I18nProvider/types.d.ts +2 -2
- package/dist/esm/providers/SDKClientProvider.js +25 -24
- package/dist/esm/providers/SDKClientProvider.js.map +1 -1
- package/dist/esm/providers/WidgetProvider/WidgetProvider.js +22 -21
- package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
- package/dist/esm/stores/form/URLSearchParamsBuilder.js +14 -3
- package/dist/esm/stores/form/URLSearchParamsBuilder.js.map +1 -1
- package/dist/esm/stores/form/createFormStore.js +6 -2
- package/dist/esm/stores/form/createFormStore.js.map +1 -1
- package/dist/esm/stores/form/getDefaultValuesFromQueryString.js +3 -0
- package/dist/esm/stores/form/getDefaultValuesFromQueryString.js.map +1 -1
- package/dist/esm/stores/form/types.d.ts +9 -1
- package/dist/esm/stores/form/types.d.ts.map +1 -1
- package/dist/esm/stores/form/types.js.map +1 -1
- package/dist/esm/stores/form/useClearAmountFields.js +1 -0
- package/dist/esm/stores/form/useClearAmountFields.js.map +1 -1
- package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js +4 -2
- package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js.map +1 -1
- package/dist/esm/stores/navigationTabs/utils.js +38 -8
- package/dist/esm/stores/navigationTabs/utils.js.map +1 -1
- package/dist/esm/types/events.d.ts +10 -1
- package/dist/esm/types/events.d.ts.map +1 -1
- package/dist/esm/types/events.js +2 -0
- package/dist/esm/types/events.js.map +1 -1
- package/dist/esm/types/widget.d.ts +9 -0
- package/dist/esm/types/widget.d.ts.map +1 -1
- package/dist/esm/types/widget.js.map +1 -1
- package/dist/esm/utils/format.d.ts +1 -1
- package/dist/esm/utils/format.d.ts.map +1 -1
- package/dist/esm/utils/format.js +2 -2
- package/dist/esm/utils/format.js.map +1 -1
- package/dist/esm/utils/limitOrder.js +61 -0
- package/dist/esm/utils/limitOrder.js.map +1 -0
- package/dist/esm/utils/navigationRoutes.d.ts +2 -0
- package/dist/esm/utils/navigationRoutes.d.ts.map +1 -1
- package/dist/esm/utils/navigationRoutes.js +6 -0
- package/dist/esm/utils/navigationRoutes.js.map +1 -1
- package/package.json +11 -11
- package/src/AppDefault.tsx +16 -0
- package/src/components/AmountInput/AmountInputEndAdornment.style.ts +1 -1
- package/src/components/AmountInput/AmountInputEndAdornment.tsx +5 -2
- package/src/components/AmountInputCard/AmountInputCard.style.tsx +157 -0
- package/src/components/AmountInputCard/AmountInputCardPair.tsx +25 -0
- package/src/components/AmountInputCard/BalanceDisplay.style.tsx +9 -0
- package/src/components/AmountInputCard/BalanceDisplay.tsx +46 -0
- package/src/components/AmountInputCard/FiatValueToggle.tsx +55 -0
- package/src/components/AmountInputCard/PercentageChips.style.tsx +60 -0
- package/src/components/AmountInputCard/PercentageChips.tsx +100 -0
- package/src/components/AmountInputCard/ReceiveAmountCard.tsx +219 -0
- package/src/components/AmountInputCard/SendAmountCard.tsx +179 -0
- package/src/components/Avatar/Avatar.tsx +8 -3
- package/src/components/Header/HeaderNavigationTabs.tsx +19 -2
- package/src/components/LimitOrderSettings/LimitOrderSettings.style.tsx +69 -0
- package/src/components/LimitOrderSettings/LimitOrderSettings.tsx +124 -0
- package/src/components/LimitPriceCard/LimitPriceCard.style.tsx +103 -0
- package/src/components/LimitPriceCard/LimitPriceCard.tsx +153 -0
- package/src/components/LimitPriceCard/LimitPricePresets.tsx +139 -0
- package/src/components/RouteCard/RouteProviderCard.tsx +113 -0
- package/src/components/Routes/LimitOrderRoutes.tsx +64 -0
- package/src/components/Routes/Routes.tsx +3 -2
- package/src/components/SettingsPanel/SettingsPanel.style.tsx +41 -0
- package/src/components/SettingsPanel/SettingsPanel.tsx +73 -0
- package/src/components/SettingsPanel/utils.ts +27 -0
- package/src/components/SwapButton/SwapButton.style.tsx +28 -0
- package/src/components/SwapButton/SwapButton.tsx +71 -0
- package/src/components/TokenPillButton/TokenPillButton.style.tsx +50 -0
- package/src/components/TokenPillButton/TokenPillButton.tsx +81 -0
- package/src/config/version.ts +1 -1
- package/src/hooks/useLimitMarketRate.ts +39 -0
- package/src/hooks/useLinkedLimitFields.ts +74 -0
- package/src/hooks/useRoutes.ts +45 -4
- package/src/hooks/useWideVariant.ts +8 -2
- package/src/pages/MainPage/LimitPageContent.tsx +18 -0
- package/src/pages/MainPage/MainPage.tsx +43 -30
- package/src/pages/MainPage/MainPageActions.tsx +35 -0
- package/src/pages/MainPage/ReviewButton.tsx +13 -1
- package/src/pages/RoutePriorityPage.tsx +55 -0
- package/src/pages/SettingsPage/RoutePrioritySettings.tsx +14 -38
- package/src/pages/SettingsPage/SlippageSettings/SlippagePage.tsx +176 -0
- package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.style.tsx +32 -25
- package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.tsx +14 -128
- package/src/pages/SettingsPage/ThemeSettings.tsx +4 -0
- package/src/stores/form/URLSearchParamsBuilder.tsx +25 -3
- package/src/stores/form/createFormStore.ts +7 -3
- package/src/stores/form/getDefaultValuesFromQueryString.ts +11 -0
- package/src/stores/form/types.ts +14 -1
- package/src/stores/form/useClearAmountFields.ts +1 -0
- package/src/stores/navigationTabs/types.ts +6 -0
- package/src/stores/navigationTabs/useNavigationTabsStore.tsx +8 -3
- package/src/stores/navigationTabs/utils.ts +67 -5
- package/src/types/events.ts +12 -0
- package/src/types/widget.ts +9 -0
- package/src/utils/format.ts +6 -2
- package/src/utils/limitOrder.ts +88 -0
- package/src/utils/navigationRoutes.ts +6 -0
|
@@ -1,82 +1,17 @@
|
|
|
1
1
|
import Percent from '@mui/icons-material/Percent'
|
|
2
|
-
import
|
|
3
|
-
import { Box, Typography } from '@mui/material'
|
|
4
|
-
import type { ChangeEventHandler, FocusEventHandler } from 'react'
|
|
5
|
-
import { useRef, useState } from 'react'
|
|
2
|
+
import { useNavigate } from '@tanstack/react-router'
|
|
6
3
|
import { useTranslation } from 'react-i18next'
|
|
4
|
+
import { CardButton } from '../../../components/Card/CardButton.js'
|
|
7
5
|
import { useSettingMonitor } from '../../../hooks/useSettingMonitor.js'
|
|
8
|
-
import { defaultSlippage } from '../../../stores/settings/createSettingsStore.js'
|
|
9
6
|
import { useSettings } from '../../../stores/settings/useSettings.js'
|
|
10
|
-
import {
|
|
11
|
-
import { formatInputAmount, formatSlippage } from '../../../utils/format.js'
|
|
7
|
+
import { navigationRoutes } from '../../../utils/navigationRoutes.js'
|
|
12
8
|
import { BadgedValue } from '../SettingsCard/BadgedValue.js'
|
|
13
|
-
import { SettingCardExpandable } from '../SettingsCard/SettingCardExpandable.js'
|
|
14
|
-
import {
|
|
15
|
-
SettingsCustomInput,
|
|
16
|
-
SettingsDefaultButton,
|
|
17
|
-
SettingsFieldSet,
|
|
18
|
-
SlippageLimitsWarningContainer,
|
|
19
|
-
} from './SlippageSettings.style.js'
|
|
20
|
-
|
|
21
|
-
const defaultSlippageInputValue = '0.5'
|
|
22
|
-
const maxFractionDigits = 5
|
|
23
9
|
|
|
24
10
|
export const SlippageSettings: React.FC = () => {
|
|
25
11
|
const { t } = useTranslation()
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
isSlippageUnderRecommendedLimits,
|
|
29
|
-
isSlippageOutsideRecommendedLimits,
|
|
30
|
-
isSlippageChanged,
|
|
31
|
-
} = useSettingMonitor()
|
|
12
|
+
const navigate = useNavigate()
|
|
13
|
+
const { isSlippageNotRecommended, isSlippageChanged } = useSettingMonitor()
|
|
32
14
|
const { slippage } = useSettings(['slippage'])
|
|
33
|
-
const { setValue } = useSettingsActions()
|
|
34
|
-
const defaultValue = useRef(slippage)
|
|
35
|
-
const [focused, setFocused] = useState<'input' | 'button'>()
|
|
36
|
-
|
|
37
|
-
const customInputValue =
|
|
38
|
-
!slippage || slippage === defaultSlippage
|
|
39
|
-
? defaultSlippageInputValue
|
|
40
|
-
: slippage
|
|
41
|
-
|
|
42
|
-
const [inputValue, setInputValue] = useState(customInputValue)
|
|
43
|
-
|
|
44
|
-
const handleDefaultClick = () => {
|
|
45
|
-
setValue('slippage', defaultSlippage)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const formatAndSetSlippage = (value: string, returnInitial = false) => {
|
|
49
|
-
const formattedSlippage = formatSlippage(
|
|
50
|
-
value,
|
|
51
|
-
defaultValue.current,
|
|
52
|
-
returnInitial
|
|
53
|
-
)
|
|
54
|
-
const formattedValue =
|
|
55
|
-
Number(formattedSlippage) === 0 && !returnInitial
|
|
56
|
-
? '0'
|
|
57
|
-
: formatInputAmount(formattedSlippage, maxFractionDigits, returnInitial)
|
|
58
|
-
const maxLength =
|
|
59
|
-
Number(formattedValue) < 10
|
|
60
|
-
? maxFractionDigits + 2
|
|
61
|
-
: maxFractionDigits + 3
|
|
62
|
-
const slicedValue = formattedValue.slice(0, maxLength)
|
|
63
|
-
setInputValue(slicedValue)
|
|
64
|
-
setValue('slippage', slicedValue.length ? slicedValue : defaultSlippage)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const handleInputUpdate: ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
68
|
-
formatAndSetSlippage(event.target.value, true)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const handleInputFocus: FocusEventHandler<HTMLInputElement> = (event) => {
|
|
72
|
-
setFocused('input')
|
|
73
|
-
formatAndSetSlippage(event.target.value)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const handleInputBlur: FocusEventHandler<HTMLInputElement> = (event) => {
|
|
77
|
-
setFocused(undefined)
|
|
78
|
-
formatAndSetSlippage(event.target.value)
|
|
79
|
-
}
|
|
80
15
|
|
|
81
16
|
const badgeColor = isSlippageNotRecommended
|
|
82
17
|
? 'warning'
|
|
@@ -84,68 +19,19 @@ export const SlippageSettings: React.FC = () => {
|
|
|
84
19
|
? 'info'
|
|
85
20
|
: undefined
|
|
86
21
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
? t('warning.message.slippageUnderRecommendedLimits')
|
|
91
|
-
: ''
|
|
22
|
+
const handleClick = () => {
|
|
23
|
+
navigate({ to: navigationRoutes.slippage })
|
|
24
|
+
}
|
|
92
25
|
|
|
93
26
|
return (
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
<BadgedValue badgeColor={badgeColor} showBadge={!!badgeColor}>
|
|
97
|
-
{slippage ? `${slippage}%` : t('button.auto')}
|
|
98
|
-
</BadgedValue>
|
|
99
|
-
}
|
|
27
|
+
<CardButton
|
|
28
|
+
onClick={handleClick}
|
|
100
29
|
icon={<Percent />}
|
|
101
30
|
title={t('settings.slippage')}
|
|
102
31
|
>
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
>
|
|
108
|
-
<SettingsFieldSet>
|
|
109
|
-
<SettingsDefaultButton
|
|
110
|
-
selected={defaultSlippage === slippage && focused !== 'input'}
|
|
111
|
-
onFocus={() => {
|
|
112
|
-
setFocused('button')
|
|
113
|
-
}}
|
|
114
|
-
onBlur={() => {
|
|
115
|
-
setFocused(undefined)
|
|
116
|
-
}}
|
|
117
|
-
onClick={handleDefaultClick}
|
|
118
|
-
disableRipple
|
|
119
|
-
>
|
|
120
|
-
{t('button.auto')}
|
|
121
|
-
</SettingsDefaultButton>
|
|
122
|
-
<SettingsCustomInput
|
|
123
|
-
selected={defaultSlippage !== slippage && focused !== 'button'}
|
|
124
|
-
placeholder={focused === 'input' ? '' : t('settings.custom')}
|
|
125
|
-
inputProps={{
|
|
126
|
-
inputMode: 'decimal',
|
|
127
|
-
}}
|
|
128
|
-
onChange={handleInputUpdate}
|
|
129
|
-
onFocus={handleInputFocus}
|
|
130
|
-
value={inputValue}
|
|
131
|
-
autoComplete="off"
|
|
132
|
-
onBlur={handleInputBlur}
|
|
133
|
-
/>
|
|
134
|
-
</SettingsFieldSet>
|
|
135
|
-
{isSlippageNotRecommended && (
|
|
136
|
-
<SlippageLimitsWarningContainer>
|
|
137
|
-
<WarningRounded color="warning" />
|
|
138
|
-
<Typography
|
|
139
|
-
sx={{
|
|
140
|
-
fontSize: 13,
|
|
141
|
-
fontWeight: 400,
|
|
142
|
-
}}
|
|
143
|
-
>
|
|
144
|
-
{slippageWarningText}
|
|
145
|
-
</Typography>
|
|
146
|
-
</SlippageLimitsWarningContainer>
|
|
147
|
-
)}
|
|
148
|
-
</Box>
|
|
149
|
-
</SettingCardExpandable>
|
|
32
|
+
<BadgedValue badgeColor={badgeColor} showBadge={!!badgeColor}>
|
|
33
|
+
{slippage ? `${slippage}%` : t('button.auto')}
|
|
34
|
+
</BadgedValue>
|
|
35
|
+
</CardButton>
|
|
150
36
|
)
|
|
151
37
|
}
|
|
@@ -5,7 +5,9 @@ import { Tooltip, useColorScheme } from '@mui/material'
|
|
|
5
5
|
import { useTranslation } from 'react-i18next'
|
|
6
6
|
import { CardValue } from '../../components/Card/CardButton.style.js'
|
|
7
7
|
import { CardTabs, Tab } from '../../components/Tabs/Tabs.style.js'
|
|
8
|
+
import { useWidgetEvents } from '../../hooks/useWidgetEvents.js'
|
|
8
9
|
import { useWidgetConfig } from '../../providers/WidgetProvider/WidgetProvider.js'
|
|
10
|
+
import { WidgetEvent } from '../../types/events.js'
|
|
9
11
|
import type { Appearance } from '../../types/widget.js'
|
|
10
12
|
import { SettingCardExpandable } from './SettingsCard/SettingCardExpandable.js'
|
|
11
13
|
|
|
@@ -37,6 +39,7 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
|
|
|
37
39
|
export const ThemeSettings: React.FC = () => {
|
|
38
40
|
const { t } = useTranslation()
|
|
39
41
|
const { mode, setMode } = useColorScheme()
|
|
42
|
+
const widgetEvents = useWidgetEvents()
|
|
40
43
|
const { hiddenUI } = useWidgetConfig()
|
|
41
44
|
|
|
42
45
|
if (hiddenUI?.appearance) {
|
|
@@ -52,6 +55,7 @@ export const ThemeSettings: React.FC = () => {
|
|
|
52
55
|
appearance: Appearance
|
|
53
56
|
) => {
|
|
54
57
|
setMode(appearance)
|
|
58
|
+
widgetEvents.emit(WidgetEvent.AppearanceChanged, appearance)
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
return (
|
|
@@ -18,13 +18,23 @@ const formValueKeys: FormFieldNames[] = [
|
|
|
18
18
|
'toToken',
|
|
19
19
|
]
|
|
20
20
|
|
|
21
|
+
// Limit-order params are only mirrored to the URL while the limit tab is
|
|
22
|
+
// active, and stripped when the user leaves it. Unlike the base keys these are
|
|
23
|
+
// presence-gated (not touched-gated) so the URL fully reproduces the limit
|
|
24
|
+
// order — including the boolean/number fields, which are never "falsy-empty".
|
|
25
|
+
const limitFormValueKeys: FormFieldNames[] = [
|
|
26
|
+
'toAmount',
|
|
27
|
+
'validUntil',
|
|
28
|
+
'partiallyFillable',
|
|
29
|
+
]
|
|
30
|
+
|
|
21
31
|
export const URLSearchParamsBuilder = () => {
|
|
22
32
|
const { pathname } = useLocation()
|
|
23
33
|
const touchedFields = useTouchedFields()
|
|
24
|
-
const values = useFieldValues(...formValueKeys)
|
|
34
|
+
const values = useFieldValues(...formValueKeys, ...limitFormValueKeys)
|
|
25
35
|
const { setSelectedBookmark, addRecentWallet } = useBookmarkActions()
|
|
26
36
|
const { validateAddress } = useAddressValidation()
|
|
27
|
-
const { buildUrl } = useWidgetConfig()
|
|
37
|
+
const { buildUrl, mode } = useWidgetConfig()
|
|
28
38
|
// Using these methods as trying to use the touchedFields and values above
|
|
29
39
|
// often has a lag that can effect the widgets initialisation sequence
|
|
30
40
|
// and accidentally cause values to be wiped from the query string
|
|
@@ -85,9 +95,21 @@ export const URLSearchParamsBuilder = () => {
|
|
|
85
95
|
url.searchParams.delete(key)
|
|
86
96
|
}
|
|
87
97
|
})
|
|
98
|
+
// Limit params live in the URL only while the limit tab is active.
|
|
99
|
+
limitFormValueKeys.forEach((key) => {
|
|
100
|
+
const value = getFieldValues(key)[0]
|
|
101
|
+
// Booleans are always "set" (false is meaningful); strings/numbers use
|
|
102
|
+
// truthiness so an empty toAmount is treated as absent.
|
|
103
|
+
const hasValue = typeof value === 'boolean' ? true : Boolean(value)
|
|
104
|
+
if (mode === 'limit' && hasValue) {
|
|
105
|
+
url.searchParams.set(key, String(value))
|
|
106
|
+
} else {
|
|
107
|
+
url.searchParams.delete(key)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
88
110
|
url.searchParams.sort()
|
|
89
111
|
window.history.replaceState(window.history.state, '', url)
|
|
90
|
-
}, [pathname, touchedFields, values, isTouched, getFieldValues])
|
|
112
|
+
}, [pathname, touchedFields, values, isTouched, getFieldValues, mode])
|
|
91
113
|
|
|
92
114
|
return null
|
|
93
115
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StoreApi, UseBoundStore } from 'zustand'
|
|
2
2
|
import { create } from 'zustand'
|
|
3
|
-
|
|
3
|
+
import { DEFAULT_VALID_UNTIL_SECONDS } from '../../utils/limitOrder.js'
|
|
4
4
|
import type {
|
|
5
5
|
DefaultValues,
|
|
6
6
|
FormFieldArray,
|
|
@@ -14,6 +14,9 @@ export const formDefaultValues: DefaultValues = {
|
|
|
14
14
|
fromAmount: '',
|
|
15
15
|
toAmount: '',
|
|
16
16
|
tokenSearchFilter: '',
|
|
17
|
+
priceInverted: false,
|
|
18
|
+
validUntil: DEFAULT_VALID_UNTIL_SECONDS,
|
|
19
|
+
partiallyFillable: true,
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
const defaultValueToFormValue = <T>(value: T): FormValueControl<T> => ({
|
|
@@ -32,7 +35,8 @@ const valuesToFormValues = (defaultValues: DefaultValues): FormValues => {
|
|
|
32
35
|
)
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
const isString = (str:
|
|
38
|
+
const isString = (str: unknown): str is string =>
|
|
39
|
+
typeof str === 'string' || str instanceof String
|
|
36
40
|
|
|
37
41
|
const getUpdatedTouchedFields = (userValues: FormValues) => {
|
|
38
42
|
return (Object.keys(userValues) as FormFieldNames[]).reduce(
|
|
@@ -58,7 +62,7 @@ const mergeDefaultFormValues = (
|
|
|
58
62
|
),
|
|
59
63
|
isDirty: !!(userValues[key]?.isDirty || defaultValues[key]?.isTouched),
|
|
60
64
|
value:
|
|
61
|
-
userValues[key]?.value
|
|
65
|
+
userValues[key]?.value !== undefined && userValues[key]?.value !== ''
|
|
62
66
|
? userValues[key]?.value
|
|
63
67
|
: defaultValues[key]?.value,
|
|
64
68
|
}
|
|
@@ -41,6 +41,17 @@ export const getDefaultValuesFromQueryString = ({
|
|
|
41
41
|
...(Number.isFinite(Number.parseFloat(searchParams.fromAmount))
|
|
42
42
|
? { fromAmount: formatInputAmount(searchParams.fromAmount) }
|
|
43
43
|
: {}),
|
|
44
|
+
...(Number.isFinite(Number.parseFloat(searchParams.toAmount))
|
|
45
|
+
? { toAmount: formatInputAmount(searchParams.toAmount) }
|
|
46
|
+
: {}),
|
|
47
|
+
// Limit-order params (written only while on the limit tab).
|
|
48
|
+
...(Number.isFinite(Number.parseInt(searchParams.validUntil, 10))
|
|
49
|
+
? { validUntil: Number.parseInt(searchParams.validUntil, 10) }
|
|
50
|
+
: {}),
|
|
51
|
+
...(searchParams.partiallyFillable === 'true' ||
|
|
52
|
+
searchParams.partiallyFillable === 'false'
|
|
53
|
+
? { partiallyFillable: searchParams.partiallyFillable === 'true' }
|
|
54
|
+
: {}),
|
|
44
55
|
...(includeToAddress && searchParams.toAddress
|
|
45
56
|
? { toAddress: searchParams.toAddress }
|
|
46
57
|
: {}),
|
package/src/stores/form/types.ts
CHANGED
|
@@ -14,9 +14,22 @@ export interface DefaultFieldValues {
|
|
|
14
14
|
export interface DefaultValues extends DefaultFieldValues {
|
|
15
15
|
contractCalls?: ContractCall[]
|
|
16
16
|
tokenSearchFilter: string
|
|
17
|
+
/** Limit-order: when true, the price is displayed/entered inverted. */
|
|
18
|
+
priceInverted: boolean
|
|
19
|
+
/** Limit-order: order validity as a duration in seconds. */
|
|
20
|
+
validUntil: number
|
|
21
|
+
/** Limit-order: whether the order may be partially filled. */
|
|
22
|
+
partiallyFillable: boolean
|
|
23
|
+
/** Limit-order: id of the route the user picked. */
|
|
24
|
+
selectedRouteId?: string
|
|
17
25
|
}
|
|
18
26
|
|
|
19
|
-
export type GenericFormValue =
|
|
27
|
+
export type GenericFormValue =
|
|
28
|
+
| string
|
|
29
|
+
| number
|
|
30
|
+
| boolean
|
|
31
|
+
| ContractCall[]
|
|
32
|
+
| undefined
|
|
20
33
|
export interface FormValueControl<T> {
|
|
21
34
|
isTouched: boolean
|
|
22
35
|
isDirty: boolean
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { StoreApi, UseBoundStore } from 'zustand'
|
|
2
2
|
import type {
|
|
3
|
+
DefaultUI,
|
|
3
4
|
ModeOptions,
|
|
4
5
|
NavigationTabKey,
|
|
6
|
+
RequiredUIConfig,
|
|
5
7
|
WidgetMode,
|
|
6
8
|
WidgetVariant,
|
|
7
9
|
} from '../../types/widget.js'
|
|
@@ -14,6 +16,10 @@ export interface NavigationTab {
|
|
|
14
16
|
/** When omitted, the tab inherits `config.mode`. */
|
|
15
17
|
mode?: WidgetMode
|
|
16
18
|
modeOptions?: ModeOptions
|
|
19
|
+
/** Layered over `config.defaultUI` while the tab is active. */
|
|
20
|
+
defaultUI?: DefaultUI
|
|
21
|
+
/** Layered over `config.requiredUI` while the tab is active. */
|
|
22
|
+
requiredUI?: RequiredUIConfig
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
export interface NavigationTabsState {
|
|
@@ -21,8 +21,10 @@ import type { NavigationTabsState, NavigationTabsStore } from './types.js'
|
|
|
21
21
|
import {
|
|
22
22
|
getInitialActiveTab,
|
|
23
23
|
getNavigationTabKeys,
|
|
24
|
+
getTabDefaultUI,
|
|
24
25
|
getTabMode,
|
|
25
26
|
getTabModeOptions,
|
|
27
|
+
getTabRequiredUI,
|
|
26
28
|
getTabSplitMode,
|
|
27
29
|
getTabVariant,
|
|
28
30
|
} from './utils.js'
|
|
@@ -49,9 +51,10 @@ export function NavigationTabsStoreProvider({
|
|
|
49
51
|
signatureRef.current = signature
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
// Override the widget config with the active tab's mode, variant
|
|
53
|
-
// modeOptions (each falling back to the config), so
|
|
54
|
-
// reads tab-driven config transparently. No-op when
|
|
54
|
+
// Override the widget config with the active tab's mode, variant,
|
|
55
|
+
// modeOptions, defaultUI and requiredUI (each falling back to the config), so
|
|
56
|
+
// the rest of the widget reads tab-driven config transparently. No-op when
|
|
57
|
+
// there is no active tab.
|
|
55
58
|
const widgetConfig = useWidgetConfig()
|
|
56
59
|
const activeTab = store((state) => state.activeTab)
|
|
57
60
|
const tabConfig = useMemo(() => {
|
|
@@ -63,6 +66,8 @@ export function NavigationTabsStoreProvider({
|
|
|
63
66
|
mode: getTabMode(widgetConfig, activeTab),
|
|
64
67
|
variant: getTabVariant(widgetConfig, activeTab),
|
|
65
68
|
modeOptions: getTabModeOptions(widgetConfig, activeTab),
|
|
69
|
+
defaultUI: getTabDefaultUI(widgetConfig, activeTab),
|
|
70
|
+
requiredUI: getTabRequiredUI(widgetConfig, activeTab),
|
|
66
71
|
}
|
|
67
72
|
}, [widgetConfig, activeTab])
|
|
68
73
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
DefaultUI,
|
|
2
3
|
InternalNavigationTabKey,
|
|
3
4
|
ModeOptions,
|
|
4
5
|
NavigationTabKey,
|
|
6
|
+
RequiredUIConfig,
|
|
5
7
|
SplitMode,
|
|
6
8
|
SplitNavigationTabKey,
|
|
7
9
|
WidgetConfig,
|
|
@@ -13,26 +15,56 @@ import type { NavigationTab } from './types.js'
|
|
|
13
15
|
|
|
14
16
|
/** Configured navigation tabs by key. Labels are resolved from the key by a hook. */
|
|
15
17
|
const navigationTabsByKey: Record<InternalNavigationTabKey, NavigationTab> = {
|
|
16
|
-
default: {
|
|
18
|
+
default: {
|
|
19
|
+
key: 'default',
|
|
20
|
+
variant: 'wide',
|
|
21
|
+
mode: 'default',
|
|
22
|
+
defaultUI: {
|
|
23
|
+
layout: 'cards',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
17
26
|
private: {
|
|
18
27
|
key: 'private',
|
|
19
28
|
variant: 'compact',
|
|
20
|
-
mode: '
|
|
21
|
-
|
|
29
|
+
mode: 'default',
|
|
30
|
+
defaultUI: {
|
|
31
|
+
layout: 'cards',
|
|
32
|
+
},
|
|
33
|
+
requiredUI: { toAddress: true },
|
|
34
|
+
},
|
|
35
|
+
refuel: {
|
|
36
|
+
key: 'refuel',
|
|
37
|
+
variant: 'wide',
|
|
38
|
+
mode: 'refuel',
|
|
39
|
+
defaultUI: {
|
|
40
|
+
layout: 'cards',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
limit: {
|
|
44
|
+
key: 'limit',
|
|
45
|
+
variant: 'compact',
|
|
46
|
+
mode: 'limit',
|
|
47
|
+
defaultUI: {
|
|
48
|
+
layout: 'cards',
|
|
49
|
+
},
|
|
22
50
|
},
|
|
23
|
-
refuel: { key: 'refuel', variant: 'wide', mode: 'refuel' },
|
|
24
|
-
limit: { key: 'limit', variant: 'compact', mode: 'limit' },
|
|
25
51
|
'swap-advanced': {
|
|
26
52
|
key: 'swap-advanced',
|
|
27
53
|
variant: 'wide',
|
|
28
54
|
mode: 'split',
|
|
29
55
|
modeOptions: { split: 'swap' },
|
|
56
|
+
defaultUI: {
|
|
57
|
+
layout: 'cards',
|
|
58
|
+
},
|
|
30
59
|
},
|
|
31
60
|
'bridge-advanced': {
|
|
32
61
|
key: 'bridge-advanced',
|
|
33
62
|
variant: 'wide',
|
|
34
63
|
mode: 'split',
|
|
35
64
|
modeOptions: { split: 'bridge' },
|
|
65
|
+
defaultUI: {
|
|
66
|
+
layout: 'cards',
|
|
67
|
+
},
|
|
36
68
|
},
|
|
37
69
|
}
|
|
38
70
|
|
|
@@ -132,3 +164,33 @@ export const getTabModeOptions = (
|
|
|
132
164
|
const tab = key ? tabByKey[key] : undefined
|
|
133
165
|
return tab?.modeOptions ?? config.modeOptions
|
|
134
166
|
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Default UI for a tab, layered over the widget config's `defaultUI` so the
|
|
170
|
+
* tab overrides only the fields it sets and inherits the rest.
|
|
171
|
+
*/
|
|
172
|
+
export const getTabDefaultUI = (
|
|
173
|
+
config: WidgetConfig,
|
|
174
|
+
key?: NavigationTabKey
|
|
175
|
+
): DefaultUI | undefined => {
|
|
176
|
+
const tab = key ? tabByKey[key] : undefined
|
|
177
|
+
if (!tab?.defaultUI) {
|
|
178
|
+
return config.defaultUI
|
|
179
|
+
}
|
|
180
|
+
return { ...config.defaultUI, ...tab.defaultUI }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Required UI for a tab, layered over the widget config's `requiredUI` so the
|
|
185
|
+
* tab overrides only the fields it sets and inherits the rest.
|
|
186
|
+
*/
|
|
187
|
+
export const getTabRequiredUI = (
|
|
188
|
+
config: WidgetConfig,
|
|
189
|
+
key?: NavigationTabKey
|
|
190
|
+
): RequiredUIConfig | undefined => {
|
|
191
|
+
const tab = key ? tabByKey[key] : undefined
|
|
192
|
+
if (!tab?.requiredUI) {
|
|
193
|
+
return config.requiredUI
|
|
194
|
+
}
|
|
195
|
+
return { ...config.requiredUI, ...tab.requiredUI }
|
|
196
|
+
}
|
package/src/types/events.ts
CHANGED
|
@@ -3,14 +3,17 @@ import type { DefaultValues } from '../stores/form/types.js'
|
|
|
3
3
|
import type { SettingsProps } from '../stores/settings/types.js'
|
|
4
4
|
import type { NavigationRouteType } from '../utils/navigationRoutes.js'
|
|
5
5
|
import type { TokenAmount } from './token.js'
|
|
6
|
+
import type { Appearance, NavigationTabKey } from './widget.js'
|
|
6
7
|
|
|
7
8
|
export enum WidgetEvent {
|
|
9
|
+
AppearanceChanged = 'appearanceChanged',
|
|
8
10
|
AvailableRoutes = 'availableRoutes',
|
|
9
11
|
ChainPinned = 'chainPinned',
|
|
10
12
|
ContactSupport = 'contactSupport',
|
|
11
13
|
DestinationChainTokenSelected = 'destinationChainTokenSelected',
|
|
12
14
|
FormFieldChanged = 'formFieldChanged',
|
|
13
15
|
LowAddressActivityConfirmed = 'lowAddressActivityConfirmed',
|
|
16
|
+
NavigationTabChanged = 'navigationTabChanged',
|
|
14
17
|
PageEntered = 'pageEntered',
|
|
15
18
|
RouteExecutionCompleted = 'routeExecutionCompleted',
|
|
16
19
|
RouteExecutionFailed = 'routeExecutionFailed',
|
|
@@ -26,12 +29,14 @@ export enum WidgetEvent {
|
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export type WidgetEvents = {
|
|
32
|
+
appearanceChanged: (data: Appearance) => void
|
|
29
33
|
availableRoutes: (data: Route[]) => void
|
|
30
34
|
chainPinned: (data: ChainPinned) => void
|
|
31
35
|
contactSupport: (data: ContactSupport) => void
|
|
32
36
|
destinationChainTokenSelected: (data: ChainTokenSelected) => void
|
|
33
37
|
formFieldChanged: (data: FormFieldChanged) => void
|
|
34
38
|
lowAddressActivityConfirmed: (data: LowAddressActivityConfirmed) => void
|
|
39
|
+
navigationTabChanged: (data: NavigationTabChanged) => void
|
|
35
40
|
pageEntered: (data: NavigationRouteType) => void
|
|
36
41
|
routeExecutionCompleted: (data: Route) => void
|
|
37
42
|
routeExecutionFailed: (data: RouteExecutionUpdate) => void
|
|
@@ -112,3 +117,10 @@ export type LowAddressActivityConfirmed = {
|
|
|
112
117
|
address: string
|
|
113
118
|
chainId: number
|
|
114
119
|
}
|
|
120
|
+
|
|
121
|
+
export type NavigationTabChanged = {
|
|
122
|
+
/** Key of the now-active tab. */
|
|
123
|
+
tab: NavigationTabKey
|
|
124
|
+
/** Key of the previously active tab, or `undefined` on first render. */
|
|
125
|
+
previousTab?: NavigationTabKey
|
|
126
|
+
}
|
package/src/types/widget.ts
CHANGED
|
@@ -173,6 +173,15 @@ export interface RequiredUIConfig {
|
|
|
173
173
|
export type DefaultUI = {
|
|
174
174
|
transactionDetailsExpanded?: boolean
|
|
175
175
|
navigationHeaderTitleNoWrap?: boolean
|
|
176
|
+
/**
|
|
177
|
+
* Layout for the main form.
|
|
178
|
+
* - `'default'`: the chain/token selectors plus single amount input.
|
|
179
|
+
* - `'cards'`: the redesigned card-based layout — stacked Send/Receive amount
|
|
180
|
+
* cards (`AmountInputCardPair`) with inline token pill selectors and a swap
|
|
181
|
+
* button.
|
|
182
|
+
* @default 'default'
|
|
183
|
+
*/
|
|
184
|
+
layout?: 'default' | 'cards'
|
|
176
185
|
}
|
|
177
186
|
|
|
178
187
|
export interface WidgetWalletConfig {
|
package/src/utils/format.ts
CHANGED
|
@@ -175,7 +175,11 @@ const units = [
|
|
|
175
175
|
['second', 1],
|
|
176
176
|
] as const
|
|
177
177
|
|
|
178
|
-
export function formatDuration(
|
|
178
|
+
export function formatDuration(
|
|
179
|
+
seconds: number,
|
|
180
|
+
locale: string,
|
|
181
|
+
unitDisplay: Intl.NumberFormatOptions['unitDisplay'] = 'narrow'
|
|
182
|
+
): string {
|
|
179
183
|
const match = units.find(([, v]) => seconds >= v)
|
|
180
184
|
const amount = match ? Math.floor(seconds / match[1]) : 0
|
|
181
185
|
const unit = match?.[0] ?? 'second'
|
|
@@ -183,7 +187,7 @@ export function formatDuration(seconds: number, locale: string): string {
|
|
|
183
187
|
return amount.toLocaleString(locale, {
|
|
184
188
|
style: 'unit',
|
|
185
189
|
unit,
|
|
186
|
-
unitDisplay
|
|
190
|
+
unitDisplay,
|
|
187
191
|
})
|
|
188
192
|
}
|
|
189
193
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { formatInputAmount } from './format.js'
|
|
2
|
+
|
|
3
|
+
/** Default order validity: 1 day, expressed as a duration in seconds. */
|
|
4
|
+
export const DEFAULT_VALID_UNTIL_SECONDS: number = 24 * 60 * 60
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* High-precision formatter for derived limit-order values. Mirrors the
|
|
8
|
+
* approach used by `priceToTokenAmount` — these are display-level derivations;
|
|
9
|
+
* the route request re-parses amounts via `parseUnits`.
|
|
10
|
+
*/
|
|
11
|
+
const preciseFormatter = new Intl.NumberFormat('en', {
|
|
12
|
+
notation: 'standard',
|
|
13
|
+
roundingPriority: 'morePrecision',
|
|
14
|
+
maximumSignificantDigits: 20,
|
|
15
|
+
maximumFractionDigits: 20,
|
|
16
|
+
useGrouping: false,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
function parsePositive(value?: string): number | undefined {
|
|
20
|
+
if (!value) {
|
|
21
|
+
return undefined
|
|
22
|
+
}
|
|
23
|
+
const parsed = Number.parseFloat(value)
|
|
24
|
+
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
25
|
+
return undefined
|
|
26
|
+
}
|
|
27
|
+
return parsed
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Derive the receive amount: `receiveAmount = sendAmount × limitPrice`.
|
|
32
|
+
* Returns an empty string when either input is missing or invalid.
|
|
33
|
+
*/
|
|
34
|
+
export function deriveReceiveAmount(
|
|
35
|
+
sendAmount?: string,
|
|
36
|
+
limitPrice?: string,
|
|
37
|
+
toDecimals?: number
|
|
38
|
+
): string {
|
|
39
|
+
const send = parsePositive(sendAmount)
|
|
40
|
+
const price = parsePositive(limitPrice)
|
|
41
|
+
if (send === undefined || price === undefined) {
|
|
42
|
+
return ''
|
|
43
|
+
}
|
|
44
|
+
return formatInputAmount(
|
|
45
|
+
preciseFormatter.format(send * price),
|
|
46
|
+
toDecimals ?? null
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Derive the limit price: `limitPrice = receiveAmount ÷ sendAmount`.
|
|
52
|
+
* Returns an empty string when either input is missing or invalid.
|
|
53
|
+
*/
|
|
54
|
+
export function deriveLimitPrice(
|
|
55
|
+
sendAmount?: string,
|
|
56
|
+
receiveAmount?: string
|
|
57
|
+
): string {
|
|
58
|
+
const send = parsePositive(sendAmount)
|
|
59
|
+
const receive = parsePositive(receiveAmount)
|
|
60
|
+
if (send === undefined || receive === undefined) {
|
|
61
|
+
return ''
|
|
62
|
+
}
|
|
63
|
+
return preciseFormatter.format(receive / send)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Invert a price for display flipping: `1 ÷ price`. */
|
|
67
|
+
export function invertPrice(price?: string): string {
|
|
68
|
+
const parsed = parsePositive(price)
|
|
69
|
+
if (parsed === undefined) {
|
|
70
|
+
return ''
|
|
71
|
+
}
|
|
72
|
+
return preciseFormatter.format(1 / parsed)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Apply a percentage offset to a price, e.g. `percent = 2` → `price × 1.02`.
|
|
77
|
+
* Used by the quick-set buttons (+2%, +5%).
|
|
78
|
+
*/
|
|
79
|
+
export function applyPriceOffset(
|
|
80
|
+
price: string | undefined,
|
|
81
|
+
percent: number
|
|
82
|
+
): string {
|
|
83
|
+
const parsed = parsePositive(price)
|
|
84
|
+
if (parsed === undefined) {
|
|
85
|
+
return ''
|
|
86
|
+
}
|
|
87
|
+
return preciseFormatter.format(parsed * (1 + percent / 100))
|
|
88
|
+
}
|