diff --git a/packages/widget-v2/eslint.config.mjs b/packages/widget-v2/eslint.config.mjs index c84d60142..cc67ca013 100644 --- a/packages/widget-v2/eslint.config.mjs +++ b/packages/widget-v2/eslint.config.mjs @@ -13,10 +13,20 @@ export default tseslint.config( "react-hooks": fixupPluginRules(reactHooks), }, rules: { + ...reactHooks.configs.recommended.rules, "@typescript-eslint/consistent-type-definitions": ["error", "type"], + "@typescript-eslint/no-unused-vars": ["error", { + "args": "all", + "argsIgnorePattern": "^_", + "caughtErrors": "all", + "caughtErrorsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "ignoreRestSiblings": true + }], "no-console": ["error", { allow: ["warn", "error"] }], "quotes": ["error", "double", { "avoidEscape": true }], - ...reactHooks.configs.recommended.rules, + }, } ); diff --git a/packages/widget-v2/package.json b/packages/widget-v2/package.json index e3a1ece1d..7758d02d7 100644 --- a/packages/widget-v2/package.json +++ b/packages/widget-v2/package.json @@ -58,6 +58,7 @@ "@tanstack/query-core": "^5.51.21", "chain-registry": "^1.63.43", "jotai": "^2.9.1", + "jotai-effect": "^1.0.2", "jotai-tanstack-query": "^0.8.6", "lodash.debounce": "^4.0.8", "match-sorter": "^6.3.4", diff --git a/packages/widget-v2/src/components/AssetChainInput.tsx b/packages/widget-v2/src/components/AssetChainInput.tsx index 1bc2fb36a..901c03b68 100644 --- a/packages/widget-v2/src/components/AssetChainInput.tsx +++ b/packages/widget-v2/src/components/AssetChainInput.tsx @@ -9,6 +9,8 @@ import { useAtom } from "jotai"; import { skipAssetsAtom, skipChainsAtom } from "@/state/skipClient"; import { useUsdValue } from "@/utils/useUsdValue"; import { formatUSD } from "@/utils/intl"; +import { BigNumber } from "bignumber.js"; +import { formatNumberWithCommas, formatNumberWithoutCommas } from "@/utils/number"; export type AssetChainInputProps = { value?: string; @@ -19,7 +21,7 @@ export type AssetChainInputProps = { }; export const AssetChainInput = ({ - value = "0", + value, onChangeValue, selectedAssetDenom, handleChangeAsset, @@ -39,6 +41,80 @@ export const AssetChainInput = ({ const usdValue = useUsdValue({ ...selectedAsset, value }); + const handleInputChange = (e: React.ChangeEvent) => { + if (!onChangeValue) return; + let latest = e.target.value; + + if (latest.match(/^[.,]/)) latest = `0.${latest}`; // Handle first character being a period or comma + latest = latest.replace(/^[0]{2,}/, "0"); // Remove leading zeros + latest = latest.replace(/[^\d.,]/g, ""); // Remove non-numeric and non-decimal characters + latest = latest.replace(/[.]{2,}/g, "."); // Remove multiple decimals + latest = latest.replace(/[,]{2,}/g, ","); // Remove multiple commas + onChangeValue?.(formatNumberWithoutCommas(latest)); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (!onChangeValue) return; + + let value: BigNumber; + + switch (event.key) { + case "Escape": + if ( + event.currentTarget.selectionStart === + event.currentTarget.selectionEnd + ) { + event.currentTarget.select(); + } + return; + + case "ArrowUp": + event.preventDefault(); + value = new BigNumber( + formatNumberWithoutCommas(event.currentTarget.value) || "0" + ); + + if (event.shiftKey) { + value = value.plus(10); + } else if (event.altKey || event.ctrlKey || event.metaKey) { + value = value.plus(0.1); + } else { + value = value.plus(1); + } + + if (value.isNegative()) { + value = new BigNumber(0); + } + + onChangeValue(value.toString()); + break; + + case "ArrowDown": + event.preventDefault(); + value = new BigNumber( + formatNumberWithoutCommas(event.currentTarget.value) || "0" + ); + + if (event.shiftKey) { + value = value.minus(10); + } else if (event.altKey || event.ctrlKey || event.metaKey) { + value = value.minus(0.1); + } else { + value = value.minus(1); + } + + if (value.isNegative()) { + value = new BigNumber(0); + } + + onChangeValue(value.toString()); + break; + + default: + return; + } + }; + return ( onChangeValue?.(e.target.value)} + value={formatNumberWithCommas(value || "")} + placeholder="0" + inputMode="numeric" + onChange={handleInputChange} + onKeyDown={handleKeyDown} />