@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.
Files changed (204) hide show
  1. package/dist/esm/AppDefault.js +14 -0
  2. package/dist/esm/AppDefault.js.map +1 -1
  3. package/dist/esm/components/AmountInput/AmountInput.js +2 -1
  4. package/dist/esm/components/AmountInput/AmountInput.js.map +1 -1
  5. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +4 -2
  6. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
  7. package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js +1 -1
  8. package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js.map +1 -1
  9. package/dist/esm/components/AmountInput/PriceFormHelperText.js +4 -2
  10. package/dist/esm/components/AmountInput/PriceFormHelperText.js.map +1 -1
  11. package/dist/esm/components/AmountInputCard/AmountInputCard.style.js +98 -0
  12. package/dist/esm/components/AmountInputCard/AmountInputCard.style.js.map +1 -0
  13. package/dist/esm/components/AmountInputCard/AmountInputCardPair.js +25 -0
  14. package/dist/esm/components/AmountInputCard/AmountInputCardPair.js.map +1 -0
  15. package/dist/esm/components/AmountInputCard/BalanceDisplay.js +35 -0
  16. package/dist/esm/components/AmountInputCard/BalanceDisplay.js.map +1 -0
  17. package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js +11 -0
  18. package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js.map +1 -0
  19. package/dist/esm/components/AmountInputCard/FiatValueToggle.js +41 -0
  20. package/dist/esm/components/AmountInputCard/FiatValueToggle.js.map +1 -0
  21. package/dist/esm/components/AmountInputCard/PercentageChips.js +80 -0
  22. package/dist/esm/components/AmountInputCard/PercentageChips.js.map +1 -0
  23. package/dist/esm/components/AmountInputCard/PercentageChips.style.js +43 -0
  24. package/dist/esm/components/AmountInputCard/PercentageChips.style.js.map +1 -0
  25. package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js +137 -0
  26. package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js.map +1 -0
  27. package/dist/esm/components/AmountInputCard/SendAmountCard.js +102 -0
  28. package/dist/esm/components/AmountInputCard/SendAmountCard.js.map +1 -0
  29. package/dist/esm/components/Avatar/Avatar.js +12 -3
  30. package/dist/esm/components/Avatar/Avatar.js.map +1 -1
  31. package/dist/esm/components/Chains/VirtualizedChainList.js +2 -1
  32. package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
  33. package/dist/esm/components/Header/HeaderNavigationTabs.js +14 -1
  34. package/dist/esm/components/Header/HeaderNavigationTabs.js.map +1 -1
  35. package/dist/esm/components/IconCircle/IconCircle.style.js +1 -1
  36. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js +99 -0
  37. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js.map +1 -0
  38. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js +52 -0
  39. package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js.map +1 -0
  40. package/dist/esm/components/LimitPriceCard/LimitPriceCard.js +115 -0
  41. package/dist/esm/components/LimitPriceCard/LimitPriceCard.js.map +1 -0
  42. package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js +89 -0
  43. package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js.map +1 -0
  44. package/dist/esm/components/LimitPriceCard/LimitPricePresets.js +91 -0
  45. package/dist/esm/components/LimitPriceCard/LimitPricePresets.js.map +1 -0
  46. package/dist/esm/components/RouteCard/RouteProviderCard.js +118 -0
  47. package/dist/esm/components/RouteCard/RouteProviderCard.js.map +1 -0
  48. package/dist/esm/components/Routes/LimitOrderRoutes.js +34 -0
  49. package/dist/esm/components/Routes/LimitOrderRoutes.js.map +1 -0
  50. package/dist/esm/components/Routes/Routes.js +2 -2
  51. package/dist/esm/components/Routes/Routes.js.map +1 -1
  52. package/dist/esm/components/SettingsPanel/SettingsPanel.js +51 -0
  53. package/dist/esm/components/SettingsPanel/SettingsPanel.js.map +1 -0
  54. package/dist/esm/components/SettingsPanel/SettingsPanel.style.js +33 -0
  55. package/dist/esm/components/SettingsPanel/SettingsPanel.style.js.map +1 -0
  56. package/dist/esm/components/SettingsPanel/utils.js +14 -0
  57. package/dist/esm/components/SettingsPanel/utils.js.map +1 -0
  58. package/dist/esm/components/SwapButton/SwapButton.js +50 -0
  59. package/dist/esm/components/SwapButton/SwapButton.js.map +1 -0
  60. package/dist/esm/components/SwapButton/SwapButton.style.js +24 -0
  61. package/dist/esm/components/SwapButton/SwapButton.style.js.map +1 -0
  62. package/dist/esm/components/TokenPillButton/TokenPillButton.js +52 -0
  63. package/dist/esm/components/TokenPillButton/TokenPillButton.js.map +1 -0
  64. package/dist/esm/components/TokenPillButton/TokenPillButton.style.js +39 -0
  65. package/dist/esm/components/TokenPillButton/TokenPillButton.style.js.map +1 -0
  66. package/dist/esm/config/version.d.ts +1 -1
  67. package/dist/esm/config/version.js +1 -1
  68. package/dist/esm/config/version.js.map +1 -1
  69. package/dist/esm/hooks/useChainSearch.js +2 -1
  70. package/dist/esm/hooks/useChainSearch.js.map +1 -1
  71. package/dist/esm/hooks/useLimitMarketRate.js +24 -0
  72. package/dist/esm/hooks/useLimitMarketRate.js.map +1 -0
  73. package/dist/esm/hooks/useLinkedLimitFields.js +49 -0
  74. package/dist/esm/hooks/useLinkedLimitFields.js.map +1 -0
  75. package/dist/esm/hooks/useRoutes.js +23 -6
  76. package/dist/esm/hooks/useRoutes.js.map +1 -1
  77. package/dist/esm/hooks/useTokens.js +16 -14
  78. package/dist/esm/hooks/useTokens.js.map +1 -1
  79. package/dist/esm/hooks/useWideVariant.js +5 -2
  80. package/dist/esm/hooks/useWideVariant.js.map +1 -1
  81. package/dist/esm/i18n/en.json +22 -0
  82. package/dist/esm/index.d.ts +2 -2
  83. package/dist/esm/pages/MainPage/LimitPageContent.js +23 -0
  84. package/dist/esm/pages/MainPage/LimitPageContent.js.map +1 -0
  85. package/dist/esm/pages/MainPage/MainPage.js +19 -22
  86. package/dist/esm/pages/MainPage/MainPage.js.map +1 -1
  87. package/dist/esm/pages/MainPage/MainPageActions.js +28 -0
  88. package/dist/esm/pages/MainPage/MainPageActions.js.map +1 -0
  89. package/dist/esm/pages/MainPage/ReviewButton.js +9 -2
  90. package/dist/esm/pages/MainPage/ReviewButton.js.map +1 -1
  91. package/dist/esm/pages/RoutePriorityPage.js +48 -0
  92. package/dist/esm/pages/RoutePriorityPage.js.map +1 -0
  93. package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js +2 -2
  94. package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js.map +1 -1
  95. package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js +12 -27
  96. package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js.map +1 -1
  97. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js +133 -0
  98. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js.map +1 -0
  99. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js +14 -72
  100. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js.map +1 -1
  101. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js +26 -14
  102. package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js.map +1 -1
  103. package/dist/esm/pages/SettingsPage/ThemeSettings.js +4 -0
  104. package/dist/esm/pages/SettingsPage/ThemeSettings.js.map +1 -1
  105. package/dist/esm/pages/TransactionPage/TransactionReview.js +4 -1
  106. package/dist/esm/pages/TransactionPage/TransactionReview.js.map +1 -1
  107. package/dist/esm/providers/I18nProvider/I18nProvider.js +1 -1
  108. package/dist/esm/providers/I18nProvider/enResource.d.ts +1 -1
  109. package/dist/esm/providers/I18nProvider/enResource.js +1 -1
  110. package/dist/esm/providers/I18nProvider/types.d.ts +2 -2
  111. package/dist/esm/providers/SDKClientProvider.js +25 -24
  112. package/dist/esm/providers/SDKClientProvider.js.map +1 -1
  113. package/dist/esm/providers/WidgetProvider/WidgetProvider.js +22 -21
  114. package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
  115. package/dist/esm/stores/form/URLSearchParamsBuilder.js +14 -3
  116. package/dist/esm/stores/form/URLSearchParamsBuilder.js.map +1 -1
  117. package/dist/esm/stores/form/createFormStore.js +6 -2
  118. package/dist/esm/stores/form/createFormStore.js.map +1 -1
  119. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js +3 -0
  120. package/dist/esm/stores/form/getDefaultValuesFromQueryString.js.map +1 -1
  121. package/dist/esm/stores/form/types.d.ts +9 -1
  122. package/dist/esm/stores/form/types.d.ts.map +1 -1
  123. package/dist/esm/stores/form/types.js.map +1 -1
  124. package/dist/esm/stores/form/useClearAmountFields.js +1 -0
  125. package/dist/esm/stores/form/useClearAmountFields.js.map +1 -1
  126. package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js +4 -2
  127. package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js.map +1 -1
  128. package/dist/esm/stores/navigationTabs/utils.js +38 -8
  129. package/dist/esm/stores/navigationTabs/utils.js.map +1 -1
  130. package/dist/esm/types/events.d.ts +10 -1
  131. package/dist/esm/types/events.d.ts.map +1 -1
  132. package/dist/esm/types/events.js +2 -0
  133. package/dist/esm/types/events.js.map +1 -1
  134. package/dist/esm/types/widget.d.ts +9 -0
  135. package/dist/esm/types/widget.d.ts.map +1 -1
  136. package/dist/esm/types/widget.js.map +1 -1
  137. package/dist/esm/utils/format.d.ts +1 -1
  138. package/dist/esm/utils/format.d.ts.map +1 -1
  139. package/dist/esm/utils/format.js +2 -2
  140. package/dist/esm/utils/format.js.map +1 -1
  141. package/dist/esm/utils/limitOrder.js +61 -0
  142. package/dist/esm/utils/limitOrder.js.map +1 -0
  143. package/dist/esm/utils/navigationRoutes.d.ts +2 -0
  144. package/dist/esm/utils/navigationRoutes.d.ts.map +1 -1
  145. package/dist/esm/utils/navigationRoutes.js +6 -0
  146. package/dist/esm/utils/navigationRoutes.js.map +1 -1
  147. package/package.json +11 -11
  148. package/src/AppDefault.tsx +16 -0
  149. package/src/components/AmountInput/AmountInputEndAdornment.style.ts +1 -1
  150. package/src/components/AmountInput/AmountInputEndAdornment.tsx +5 -2
  151. package/src/components/AmountInputCard/AmountInputCard.style.tsx +157 -0
  152. package/src/components/AmountInputCard/AmountInputCardPair.tsx +25 -0
  153. package/src/components/AmountInputCard/BalanceDisplay.style.tsx +9 -0
  154. package/src/components/AmountInputCard/BalanceDisplay.tsx +46 -0
  155. package/src/components/AmountInputCard/FiatValueToggle.tsx +55 -0
  156. package/src/components/AmountInputCard/PercentageChips.style.tsx +60 -0
  157. package/src/components/AmountInputCard/PercentageChips.tsx +100 -0
  158. package/src/components/AmountInputCard/ReceiveAmountCard.tsx +219 -0
  159. package/src/components/AmountInputCard/SendAmountCard.tsx +179 -0
  160. package/src/components/Avatar/Avatar.tsx +8 -3
  161. package/src/components/Header/HeaderNavigationTabs.tsx +19 -2
  162. package/src/components/LimitOrderSettings/LimitOrderSettings.style.tsx +69 -0
  163. package/src/components/LimitOrderSettings/LimitOrderSettings.tsx +124 -0
  164. package/src/components/LimitPriceCard/LimitPriceCard.style.tsx +103 -0
  165. package/src/components/LimitPriceCard/LimitPriceCard.tsx +153 -0
  166. package/src/components/LimitPriceCard/LimitPricePresets.tsx +139 -0
  167. package/src/components/RouteCard/RouteProviderCard.tsx +113 -0
  168. package/src/components/Routes/LimitOrderRoutes.tsx +64 -0
  169. package/src/components/Routes/Routes.tsx +3 -2
  170. package/src/components/SettingsPanel/SettingsPanel.style.tsx +41 -0
  171. package/src/components/SettingsPanel/SettingsPanel.tsx +73 -0
  172. package/src/components/SettingsPanel/utils.ts +27 -0
  173. package/src/components/SwapButton/SwapButton.style.tsx +28 -0
  174. package/src/components/SwapButton/SwapButton.tsx +71 -0
  175. package/src/components/TokenPillButton/TokenPillButton.style.tsx +50 -0
  176. package/src/components/TokenPillButton/TokenPillButton.tsx +81 -0
  177. package/src/config/version.ts +1 -1
  178. package/src/hooks/useLimitMarketRate.ts +39 -0
  179. package/src/hooks/useLinkedLimitFields.ts +74 -0
  180. package/src/hooks/useRoutes.ts +45 -4
  181. package/src/hooks/useWideVariant.ts +8 -2
  182. package/src/pages/MainPage/LimitPageContent.tsx +18 -0
  183. package/src/pages/MainPage/MainPage.tsx +43 -30
  184. package/src/pages/MainPage/MainPageActions.tsx +35 -0
  185. package/src/pages/MainPage/ReviewButton.tsx +13 -1
  186. package/src/pages/RoutePriorityPage.tsx +55 -0
  187. package/src/pages/SettingsPage/RoutePrioritySettings.tsx +14 -38
  188. package/src/pages/SettingsPage/SlippageSettings/SlippagePage.tsx +176 -0
  189. package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.style.tsx +32 -25
  190. package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.tsx +14 -128
  191. package/src/pages/SettingsPage/ThemeSettings.tsx +4 -0
  192. package/src/stores/form/URLSearchParamsBuilder.tsx +25 -3
  193. package/src/stores/form/createFormStore.ts +7 -3
  194. package/src/stores/form/getDefaultValuesFromQueryString.ts +11 -0
  195. package/src/stores/form/types.ts +14 -1
  196. package/src/stores/form/useClearAmountFields.ts +1 -0
  197. package/src/stores/navigationTabs/types.ts +6 -0
  198. package/src/stores/navigationTabs/useNavigationTabsStore.tsx +8 -3
  199. package/src/stores/navigationTabs/utils.ts +67 -5
  200. package/src/types/events.ts +12 -0
  201. package/src/types/widget.ts +9 -0
  202. package/src/utils/format.ts +6 -2
  203. package/src/utils/limitOrder.ts +88 -0
  204. package/src/utils/navigationRoutes.ts +6 -0
@@ -1,82 +1,17 @@
1
1
  import Percent from '@mui/icons-material/Percent'
2
- import WarningRounded from '@mui/icons-material/WarningRounded'
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 { useSettingsActions } from '../../../stores/settings/useSettingsActions.js'
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
- isSlippageNotRecommended,
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 slippageWarningText = isSlippageOutsideRecommendedLimits
88
- ? t('warning.message.slippageOutsideRecommendedLimits')
89
- : isSlippageUnderRecommendedLimits
90
- ? t('warning.message.slippageUnderRecommendedLimits')
91
- : ''
22
+ const handleClick = () => {
23
+ navigate({ to: navigationRoutes.slippage })
24
+ }
92
25
 
93
26
  return (
94
- <SettingCardExpandable
95
- value={
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
- <Box
104
- sx={{
105
- mt: 1.5,
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: any) => typeof str === 'string' || str instanceof String
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 || Number.isFinite(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
  : {}),
@@ -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 = string | number | ContractCall[] | undefined
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
@@ -6,5 +6,6 @@ export const useClearAmountFields = (): (() => void) => {
6
6
  return useCallback(() => {
7
7
  setFieldValue('fromAmount', '')
8
8
  setFieldValue('toAmount', '')
9
+ setFieldValue('selectedRouteId', undefined)
9
10
  }, [setFieldValue])
10
11
  }
@@ -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 and
53
- // modeOptions (each falling back to the config), so the rest of the widget
54
- // reads tab-driven config transparently. No-op when there is no active tab.
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: { key: 'default', variant: 'wide', mode: '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: 'split',
21
- modeOptions: { split: 'swap' },
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
+ }
@@ -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
+ }
@@ -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 {
@@ -175,7 +175,11 @@ const units = [
175
175
  ['second', 1],
176
176
  ] as const
177
177
 
178
- export function formatDuration(seconds: number, locale: string): string {
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: 'narrow',
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
+ }