Commit 4fe35ea4 authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

fix: max on WebKit (#3349)

* chore: add walletconnect to cosmos

* fix: onClickMax for TokenInput

* chore: add setImmediate
parent 0d852b61
...@@ -71,6 +71,8 @@ ...@@ -71,6 +71,8 @@
"@uniswap/v2-periphery": "^1.1.0-beta.0", "@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v3-core": "1.0.0", "@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1", "@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-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7",
"web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9", "web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9",
"web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7", "web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7",
...@@ -219,13 +221,13 @@ ...@@ -219,13 +221,13 @@
"react-window": "^1.8.5", "react-window": "^1.8.5",
"rebass": "^4.0.7", "rebass": "^4.0.7",
"redux": "^4.1.2", "redux": "^4.1.2",
"setimmediate": "^1.0.5",
"styled-components": "^5.3.0", "styled-components": "^5.3.0",
"tiny-invariant": "^1.2.0", "tiny-invariant": "^1.2.0",
"wcag-contrast": "^3.0.0", "wcag-contrast": "^3.0.0",
"@web3-react/core": "8.0.16-alpha.0", "@web3-react/core": "8.0.16-alpha.0",
"@web3-react/eip1193": "8.0.16-alpha.0", "@web3-react/eip1193": "8.0.16-alpha.0",
"@web3-react/empty": "8.0.17-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/types": "8.0.16-alpha.0",
"@web3-react/url": "8.0.17-alpha.0", "@web3-react/url": "8.0.17-alpha.0",
"wicg-inert": "^3.1.1" "wicg-inert": "^3.1.1"
......
...@@ -69,14 +69,10 @@ export default function Input({ disabled, focused }: InputProps) { ...@@ -69,14 +69,10 @@ export default function Input({ disabled, focused }: InputProps) {
const mockApproved = true const mockApproved = true
// account for gas needed if using max on native token // account for gas needed if using max on native token
const maxAmount = useMemo(() => maxAmountSpend(balance), [balance]) const max = useMemo(() => {
const maxAmount = maxAmountSpend(balance)
const onMax = useMemo(() => { return maxAmount?.greaterThan(0) ? maxAmount.toExact() : undefined
if (maxAmount?.greaterThan(0)) { }, [balance])
return () => updateSwapInputAmount(maxAmount.toExact())
}
return
}, [maxAmount, updateSwapInputAmount])
const balanceColor = useMemo(() => { const balanceColor = useMemo(() => {
const insufficientBalance = const insufficientBalance =
...@@ -90,8 +86,8 @@ export default function Input({ disabled, focused }: InputProps) { ...@@ -90,8 +86,8 @@ export default function Input({ disabled, focused }: InputProps) {
<TokenInput <TokenInput
currency={swapInputCurrency} currency={swapInputCurrency}
amount={(swapInputAmount !== undefined ? swapInputAmount : swapInputCurrencyAmount?.toSignificant(6)) ?? ''} amount={(swapInputAmount !== undefined ? swapInputAmount : swapInputCurrencyAmount?.toSignificant(6)) ?? ''}
max={max}
disabled={disabled} disabled={disabled}
onMax={onMax}
onChangeInput={updateSwapInputAmount} onChangeInput={updateSwapInputAmount}
onChangeCurrency={updateSwapInputCurrency} onChangeCurrency={updateSwapInputCurrency}
loading={isLoading} loading={isLoading}
......
import 'setimmediate'
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import { loadingOpacityCss } from 'lib/css/loading' import { loadingOpacityCss } from 'lib/css/loading'
import styled, { keyframes, ThemedText } from 'lib/theme' import styled, { keyframes, ThemedText } from 'lib/theme'
import { FocusEvent, ReactNode, useCallback, useRef, useState } from 'react' import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import Button from '../Button' import Button from '../Button'
import Column from '../Column' import Column from '../Column'
...@@ -50,8 +52,8 @@ const MaxButton = styled(Button)` ...@@ -50,8 +52,8 @@ const MaxButton = styled(Button)`
interface TokenInputProps { interface TokenInputProps {
currency?: Currency currency?: Currency
amount: string amount: string
max?: string
disabled?: boolean disabled?: boolean
onMax?: () => void
onChangeInput: (input: string) => void onChangeInput: (input: string) => void
onChangeCurrency: (currency: Currency) => void onChangeCurrency: (currency: Currency) => void
loading?: boolean loading?: boolean
...@@ -61,38 +63,49 @@ interface TokenInputProps { ...@@ -61,38 +63,49 @@ interface TokenInputProps {
export default function TokenInput({ export default function TokenInput({
currency, currency,
amount, amount,
max,
disabled, disabled,
onMax,
onChangeInput, onChangeInput,
onChangeCurrency, onChangeCurrency,
loading, loading,
children, children,
}: TokenInputProps) { }: TokenInputProps) {
const max = useRef<HTMLButtonElement>(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<HTMLInputElement>(null) const input = useRef<HTMLInputElement>(null)
const onSelect = useCallback( const onSelect = useCallback(
(currency: Currency) => { (currency: Currency) => {
onChangeCurrency(currency) onChangeCurrency(currency)
setTimeout(() => input.current?.focus(), 0) setImmediate(() => input.current?.focus())
}, },
[onChangeCurrency] [onChangeCurrency]
) )
const maxButton = useRef<HTMLButtonElement>(null)
const hasMax = useMemo(() => Boolean(max && max !== amount), [max, amount])
const [showMax, setShowMax] = useState<boolean>(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 ( return (
<Column gap={0.25}> <Column gap={0.25}>
<TokenInputRow gap={0.5} onBlur={onBlur}> <TokenInputRow gap={0.5} onBlur={onBlur}>
<ThemedText.H2> <ThemedText.H2>
<ValueInput <ValueInput
value={amount} value={amount}
onFocus={onFocus} onFocus={() => setShowMax(hasMax)}
onChange={onChangeInput} onChange={onChangeInput}
disabled={disabled || !currency} disabled={disabled || !currency}
$loading={Boolean(loading)} $loading={Boolean(loading)}
...@@ -100,8 +113,9 @@ export default function TokenInput({ ...@@ -100,8 +113,9 @@ export default function TokenInput({
></ValueInput> ></ValueInput>
</ThemedText.H2> </ThemedText.H2>
{showMax && ( {showMax && (
<MaxButton onClick={onMax} ref={max}> <MaxButton onClick={onClickMax} ref={maxButton}>
<ThemedText.ButtonMedium> {/* Without a tab index, Safari would not populate the FocusEvent.relatedTarget needed by onBlur. */}
<ThemedText.ButtonMedium tabIndex={-1}>
<Trans>Max</Trans> <Trans>Max</Trans>
</ThemedText.ButtonMedium> </ThemedText.ButtonMedium>
</MaxButton> </MaxButton>
......
import { initializeConnector } from '@web3-react/core' import { initializeConnector } from '@web3-react/core'
import { MetaMask } from '@web3-react/metamask' import { MetaMask } from '@web3-react/metamask'
import { Connector } from '@web3-react/types'
import { WalletConnect } from '@web3-react/walletconnect'
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import { INFURA_NETWORK_URLS } from 'constants/infura' import { INFURA_NETWORK_URLS } from 'constants/infura'
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from 'constants/locales' import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from 'constants/locales'
import Widget from 'lib/components/Widget' import Widget from 'lib/components/Widget'
import { darkTheme, defaultTheme, lightTheme } from 'lib/theme' 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' import { useSelect, useValue } from 'react-cosmos/fixture'
export const [metaMask] = initializeConnector<MetaMask>((actions) => new MetaMask(actions)) const [metaMask] = initializeConnector<MetaMask>((actions) => new MetaMask(actions))
const [walletConnect] = initializeConnector<WalletConnect>(
(actions) => new WalletConnect(actions, { rpc: INFURA_NETWORK_URLS })
)
export default function Wrapper({ children }: { children: ReactNode }) { export default function Wrapper({ children }: { children: ReactNode }) {
const [width] = useValue('width', { defaultValue: 360 }) const [width] = useValue('width', { defaultValue: 360 })
...@@ -27,21 +32,40 @@ export default function Wrapper({ children }: { children: ReactNode }) { ...@@ -27,21 +32,40 @@ export default function Wrapper({ children }: { children: ReactNode }) {
options: [NO_JSON_RPC, ...Object.values(INFURA_NETWORK_URLS).sort()], options: [NO_JSON_RPC, ...Object.values(INFURA_NETWORK_URLS).sort()],
}) })
const NO_PROVIDER = 'None' const NO_CONNECTOR = 'None'
const META_MASK = 'MetaMask' const META_MASK = 'MetaMask'
const [providerType] = useSelect('Provider', { const WALLET_CONNECT = 'WalletConnect'
defaultValue: NO_PROVIDER, const [connectorType] = useSelect('Provider', {
options: [NO_PROVIDER, META_MASK], defaultValue: NO_CONNECTOR,
options: [NO_CONNECTOR, META_MASK, WALLET_CONNECT],
}) })
const provider = useMemo(() => { const [connector, setConnector] = useState<Connector>()
switch (providerType) { useEffect(() => {
case META_MASK: let stale = false
metaMask.activate() activateConnector(connectorType)
return metaMask.provider return () => {
default: stale = true
return undefined }
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 ( return (
<Widget <Widget
...@@ -49,7 +73,7 @@ export default function Wrapper({ children }: { children: ReactNode }) { ...@@ -49,7 +73,7 @@ export default function Wrapper({ children }: { children: ReactNode }) {
theme={theme} theme={theme}
locale={locale} locale={locale}
jsonRpcEndpoint={jsonRpcEndpoint === NO_JSON_RPC ? undefined : jsonRpcEndpoint} jsonRpcEndpoint={jsonRpcEndpoint === NO_JSON_RPC ? undefined : jsonRpcEndpoint}
provider={provider} provider={connector?.provider}
> >
{children} {children}
</Widget> </Widget>
......
...@@ -5648,6 +5648,13 @@ ...@@ -5648,6 +5648,13 @@
"@ethersproject/providers" "^5.4.5" "@ethersproject/providers" "^5.4.5"
"@web3-react/types" "^8.0.16-alpha.0" "@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": "@webassemblyjs/ast@1.9.0":
version "1.9.0" version "1.9.0"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment