From 4fe35ea42e2cdb412209e09c769e81868f0bf622 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 23 Feb 2022 12:50:22 -0800 Subject: [PATCH] fix: max on WebKit (#3349) * chore: add walletconnect to cosmos * fix: onClickMax for TokenInput * chore: add setImmediate --- package.json | 4 +- src/lib/components/Swap/Input.tsx | 14 +++---- src/lib/components/Swap/TokenInput.tsx | 46 ++++++++++++++-------- src/lib/cosmos/components/Widget.tsx | 54 +++++++++++++++++++------- yarn.lock | 7 ++++ 5 files changed, 84 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index fbf12ac29e..8a7f2317d1 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,8 @@ "@uniswap/v2-periphery": "^1.1.0-beta.0", "@uniswap/v3-core": "1.0.0", "@uniswap/v3-periphery": "^1.1.1", + "@web3-react/metamask": "8.0.16-alpha.0", + "@web3-react/walletconnect": "8.0.16-alpha.0", "web3-react-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7", "web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9", "web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7", @@ -219,13 +221,13 @@ "react-window": "^1.8.5", "rebass": "^4.0.7", "redux": "^4.1.2", + "setimmediate": "^1.0.5", "styled-components": "^5.3.0", "tiny-invariant": "^1.2.0", "wcag-contrast": "^3.0.0", "@web3-react/core": "8.0.16-alpha.0", "@web3-react/eip1193": "8.0.16-alpha.0", "@web3-react/empty": "8.0.17-alpha.0", - "@web3-react/metamask": "8.0.16-alpha.0", "@web3-react/types": "8.0.16-alpha.0", "@web3-react/url": "8.0.17-alpha.0", "wicg-inert": "^3.1.1" diff --git a/src/lib/components/Swap/Input.tsx b/src/lib/components/Swap/Input.tsx index 422a139720..bf9488deb1 100644 --- a/src/lib/components/Swap/Input.tsx +++ b/src/lib/components/Swap/Input.tsx @@ -69,14 +69,10 @@ export default function Input({ disabled, focused }: InputProps) { const mockApproved = true // account for gas needed if using max on native token - const maxAmount = useMemo(() => maxAmountSpend(balance), [balance]) - - const onMax = useMemo(() => { - if (maxAmount?.greaterThan(0)) { - return () => updateSwapInputAmount(maxAmount.toExact()) - } - return - }, [maxAmount, updateSwapInputAmount]) + const max = useMemo(() => { + const maxAmount = maxAmountSpend(balance) + return maxAmount?.greaterThan(0) ? maxAmount.toExact() : undefined + }, [balance]) const balanceColor = useMemo(() => { const insufficientBalance = @@ -90,8 +86,8 @@ export default function Input({ disabled, focused }: InputProps) { void onChangeInput: (input: string) => void onChangeCurrency: (currency: Currency) => void loading?: boolean @@ -61,38 +63,49 @@ interface TokenInputProps { export default function TokenInput({ currency, amount, + max, disabled, - onMax, onChangeInput, onChangeCurrency, loading, children, }: TokenInputProps) { - const max = useRef(null) - const [showMax, setShowMax] = useState(false) - const onFocus = useCallback(() => setShowMax(Boolean(onMax)), [onMax]) - const onBlur = useCallback((e: FocusEvent) => { - if (e.relatedTarget !== max.current && e.relatedTarget !== input.current) { - setShowMax(false) - } - }, []) - const input = useRef(null) const onSelect = useCallback( (currency: Currency) => { onChangeCurrency(currency) - setTimeout(() => input.current?.focus(), 0) + setImmediate(() => input.current?.focus()) }, [onChangeCurrency] ) + const maxButton = useRef(null) + const hasMax = useMemo(() => Boolean(max && max !== amount), [max, amount]) + const [showMax, setShowMax] = useState(hasMax) + useEffect(() => setShowMax((hasMax && input.current?.contains(document.activeElement)) ?? false), [hasMax]) + const onBlur = useCallback((e) => { + // Filters out clicks on input or maxButton, because onBlur fires before onClickMax. + if (!input.current?.contains(e.relatedTarget) && !maxButton.current?.contains(e.relatedTarget)) { + setShowMax(false) + } + }, []) + const onClickMax = useCallback(() => { + onChangeInput(max || '') + setShowMax(false) + setImmediate(() => { + input.current?.focus() + // Brings the start of the input into view. NB: This only works for clicks, not eg keyboard interactions. + input.current?.setSelectionRange(0, null) + }) + }, [max, onChangeInput]) + return ( setShowMax(hasMax)} onChange={onChangeInput} disabled={disabled || !currency} $loading={Boolean(loading)} @@ -100,8 +113,9 @@ export default function TokenInput({ > {showMax && ( - - + + {/* Without a tab index, Safari would not populate the FocusEvent.relatedTarget needed by onBlur. */} + Max diff --git a/src/lib/cosmos/components/Widget.tsx b/src/lib/cosmos/components/Widget.tsx index 203ce43d4a..aae19df236 100644 --- a/src/lib/cosmos/components/Widget.tsx +++ b/src/lib/cosmos/components/Widget.tsx @@ -1,14 +1,19 @@ import { initializeConnector } from '@web3-react/core' import { MetaMask } from '@web3-react/metamask' +import { Connector } from '@web3-react/types' +import { WalletConnect } from '@web3-react/walletconnect' import { SupportedChainId } from 'constants/chains' import { INFURA_NETWORK_URLS } from 'constants/infura' import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from 'constants/locales' import Widget from 'lib/components/Widget' import { darkTheme, defaultTheme, lightTheme } from 'lib/theme' -import { ReactNode, useEffect, useMemo } from 'react' +import { ReactNode, useEffect, useState } from 'react' import { useSelect, useValue } from 'react-cosmos/fixture' -export const [metaMask] = initializeConnector((actions) => new MetaMask(actions)) +const [metaMask] = initializeConnector((actions) => new MetaMask(actions)) +const [walletConnect] = initializeConnector( + (actions) => new WalletConnect(actions, { rpc: INFURA_NETWORK_URLS }) +) export default function Wrapper({ children }: { children: ReactNode }) { const [width] = useValue('width', { defaultValue: 360 }) @@ -27,21 +32,40 @@ export default function Wrapper({ children }: { children: ReactNode }) { options: [NO_JSON_RPC, ...Object.values(INFURA_NETWORK_URLS).sort()], }) - const NO_PROVIDER = 'None' + const NO_CONNECTOR = 'None' const META_MASK = 'MetaMask' - const [providerType] = useSelect('Provider', { - defaultValue: NO_PROVIDER, - options: [NO_PROVIDER, META_MASK], + const WALLET_CONNECT = 'WalletConnect' + const [connectorType] = useSelect('Provider', { + defaultValue: NO_CONNECTOR, + options: [NO_CONNECTOR, META_MASK, WALLET_CONNECT], }) - const provider = useMemo(() => { - switch (providerType) { - case META_MASK: - metaMask.activate() - return metaMask.provider - default: - return undefined + const [connector, setConnector] = useState() + useEffect(() => { + let stale = false + activateConnector(connectorType) + return () => { + stale = true + } + + async function activateConnector(connectorType: 'None' | 'MetaMask' | 'WalletConnect') { + let connector: Connector + switch (connectorType) { + case META_MASK: + await metaMask.activate() + connector = metaMask + break + case WALLET_CONNECT: + await walletConnect.activate() + connector = walletConnect + } + if (!stale) { + setConnector((oldConnector) => { + oldConnector?.deactivate?.() + return connector + }) + } } - }, [providerType]) + }, [connectorType]) return ( {children} diff --git a/yarn.lock b/yarn.lock index 1839fa4e5c..25937c07a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5648,6 +5648,13 @@ "@ethersproject/providers" "^5.4.5" "@web3-react/types" "^8.0.16-alpha.0" +"@web3-react/walletconnect@8.0.16-alpha.0": + version "8.0.16-alpha.0" + resolved "https://registry.yarnpkg.com/@web3-react/walletconnect/-/walletconnect-8.0.16-alpha.0.tgz#63e69261ec0029db362bc740db90c7ed5c31c144" + integrity sha512-ZE1fuPw+rAirw4dTz+/bhgoZWv9opw+eu3XZuDeyee+IWd80U2GYHZtztyF+0RRTRm7A+dRfXx9I2tsnmoF1sw== + dependencies: + "@web3-react/types" "^8.0.16-alpha.0" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"