From e4dffb962d711ad84988f34319de06c96cbde93b Mon Sep 17 00:00:00 2001 From: viet-nv Date: Sun, 14 Sep 2025 23:10:03 +0700 Subject: [PATCH 01/87] tmp --- .../src/assets/svg/smart_exit.svg | 5 ++ .../src/pages/Earns/PositionDetail/Header.tsx | 25 ++++++--- .../Earns/UserPositions/DropdownAction.tsx | 14 +++++ .../Earns/UserPositions/TableContent.tsx | 9 ++++ .../Earns/components/SmartExit/index.tsx | 53 +++++++++++++++++++ 5 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 apps/kyberswap-interface/src/assets/svg/smart_exit.svg create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx diff --git a/apps/kyberswap-interface/src/assets/svg/smart_exit.svg b/apps/kyberswap-interface/src/assets/svg/smart_exit.svg new file mode 100644 index 0000000000..58ead3adba --- /dev/null +++ b/apps/kyberswap-interface/src/assets/svg/smart_exit.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/kyberswap-interface/src/pages/Earns/PositionDetail/Header.tsx b/apps/kyberswap-interface/src/pages/Earns/PositionDetail/Header.tsx index 4197c0cfca..95b4709ac0 100644 --- a/apps/kyberswap-interface/src/pages/Earns/PositionDetail/Header.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/PositionDetail/Header.tsx @@ -26,10 +26,16 @@ const PositionDetailHeader = ({ position, isLoading, initialLoading, + rightComponent, + showBackIcon = true, + style = {}, }: { position?: ParsedPosition isLoading: boolean initialLoading: boolean + rightComponent?: React.ReactNode + showBackIcon?: boolean + style?: React.CSSProperties }) => { const theme = useTheme() const navigate = useNavigate() @@ -98,10 +104,11 @@ const PositionDetailHeader = ({ alignItems="center" justifyContent="space-between" marginBottom={1} + style={style} > - navigate(hadForceLoading ? -2 : -1)} /> + {showBackIcon && navigate(hadForceLoading ? -2 : -1)} />} {initialLoading ? ( @@ -182,12 +189,16 @@ const PositionDetailHeader = ({ {isLoading && !initialLoading && } - } - text={t`My Positions`} - to={APP_PATHS.EARN_POSITIONS} - /> + {rightComponent ? ( + rightComponent + ) : ( + } + text={t`My Positions`} + to={APP_PATHS.EARN_POSITIONS} + /> + )} ) } diff --git a/apps/kyberswap-interface/src/pages/Earns/UserPositions/DropdownAction.tsx b/apps/kyberswap-interface/src/pages/Earns/UserPositions/DropdownAction.tsx index f6d98d09cb..7de5f3f21b 100644 --- a/apps/kyberswap-interface/src/pages/Earns/UserPositions/DropdownAction.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/UserPositions/DropdownAction.tsx @@ -8,6 +8,7 @@ import styled from 'styled-components' import { ReactComponent as IconClaimRewards } from 'assets/svg/earn/ic_claim.svg' import { ReactComponent as IconClaimFees } from 'assets/svg/earn/ic_earn_claim_fees.svg' +import { ReactComponent as IconSmartExit } from 'assets/svg/smart_exit.svg' import Loader from 'components/Loader' import useTheme from 'hooks/useTheme' import { ParsedPosition, PositionStatus } from 'pages/Earns/types' @@ -112,12 +113,14 @@ const DropdownAction = ({ position, onOpenIncreaseLiquidityWidget, onOpenZapOut, + onOpenSmartExit, claimFees: { onClaimFee, feesClaimDisabled, feesClaiming, positionThatClaimingFees }, claimRewards: { onClaimRewards, rewardsClaimDisabled, rewardsClaiming, positionThatClaimingRewards }, }: { position: ParsedPosition onOpenIncreaseLiquidityWidget: (e: React.MouseEvent, position: ParsedPosition) => void onOpenZapOut: (e: React.MouseEvent, position: ParsedPosition) => void + onOpenSmartExit: (e: React.MouseEvent, position: ParsedPosition) => void claimFees: { onClaimFee: (e: React.MouseEvent, position: ParsedPosition) => void feesClaimDisabled: boolean @@ -239,6 +242,17 @@ const DropdownAction = ({ )} {t`Claim Rewards`} + { + e.stopPropagation() + if (position.status === PositionStatus.CLOSED) return + handleAction(e, onOpenSmartExit) + }} + > + + {t`Smart Exit`} + ) diff --git a/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx b/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx index e0400aef33..ef76e4fe42 100644 --- a/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx @@ -58,6 +58,8 @@ import { useWalletModalToggle } from 'state/application/hooks' import { MEDIA_WIDTHS } from 'theme' import { formatDisplayNumber } from 'utils/numbers' +import { SmartExit } from '../components/SmartExit' + export interface FeeInfoFromRpc extends FeeInfo { id: string timeRemaining: number @@ -87,6 +89,7 @@ export default function TableContent({ const [positionThatClaimingFees, setPositionThatClaimingFees] = useState(null) const [positionThatClaimingRewards, setPositionThatClaimingRewards] = useState(null) const [positionToMigrate, setPositionToMigrate] = useState(null) + const [smartExitPosition, setSmartExitPosition] = useState(null) const { claimModal: claimFeesModal, @@ -275,6 +278,9 @@ export default function TableContent({ {claimRewardsModal} {zapMigrationWidget} {migrationModal} + {smartExitPosition && ( + setSmartExitPosition(null)} /> + )} {account && positions && positions.length > 0 @@ -307,6 +313,9 @@ export default function TableContent({ position={position} onOpenIncreaseLiquidityWidget={handleOpenIncreaseLiquidityWidget} onOpenZapOut={handleOpenZapOut} + onOpenSmartExit={(_e: React.MouseEvent, position: ParsedPosition) => { + setSmartExitPosition(position) + }} claimFees={{ onClaimFee: handleClaimFees, feesClaimDisabled, diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx new file mode 100644 index 0000000000..483fb02fb2 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx @@ -0,0 +1,53 @@ +import { shortenAddress } from '@kyber/utils/dist/crypto' +import { Trans } from '@lingui/macro' +import { X } from 'react-feather' +import { Flex, Text } from 'rebass' +import styled from 'styled-components' + +import CopyHelper from 'components/Copy' +import { InfoHelperWithDelay } from 'components/InfoHelper' +import Modal from 'components/Modal' +import TokenLogo from 'components/TokenLogo' +import useTheme from 'hooks/useTheme' +import PositionDetailHeader from 'pages/Earns/PositionDetail/Header' +import { Badge, BadgeType, ChainImage, ImageContainer } from 'pages/Earns/UserPositions/styles' +import { Wrapper } from 'pages/Earns/components/ClaimModal/styles' +import { ParsedPosition } from 'pages/Earns/types' + +const Content = styled.div` + display: flex; + flex-direction: row; +` + +export const SmartExit = ({ + isOpen, + onDismiss, + position, +}: { + isOpen: boolean + onDismiss: () => void + position: ParsedPosition +}) => { + const theme = useTheme() + return ( + + + + + Set Up Smart Exit + + + + + menu} + /> + + + ) +} From 139368279d64d1b75f13c34136def7947feeeee6 Mon Sep 17 00:00:00 2001 From: viet-nv Date: Wed, 17 Sep 2025 22:55:20 +0700 Subject: [PATCH 02/87] feat: create smart exit order --- .../swapv2/LimitOrder/ExpirePicker.tsx | 8 +- .../src/hooks/usePermitNft.ts | 195 ++++++++ .../components/SmartExit/Confirmation.tsx | 231 +++++++++ .../Earns/components/SmartExit/Metrics.tsx | 458 ++++++++++++++++++ .../Earns/components/SmartExit/index.tsx | 215 +++++++- .../components/SmartExit/useSmartExit.ts | 238 +++++++++ 6 files changed, 1322 insertions(+), 23 deletions(-) create mode 100644 apps/kyberswap-interface/src/hooks/usePermitNft.ts create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts diff --git a/apps/kyberswap-interface/src/components/swapv2/LimitOrder/ExpirePicker.tsx b/apps/kyberswap-interface/src/components/swapv2/LimitOrder/ExpirePicker.tsx index c96838f218..7c474aa78b 100644 --- a/apps/kyberswap-interface/src/components/swapv2/LimitOrder/ExpirePicker.tsx +++ b/apps/kyberswap-interface/src/components/swapv2/LimitOrder/ExpirePicker.tsx @@ -1,7 +1,7 @@ import { Trans } from '@lingui/macro' import dayjs from 'dayjs' import { rgba } from 'polished' -import { useCallback, useEffect, useMemo, useState } from 'react' +import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react' import { Calendar, X } from 'react-feather' import { Flex, Text } from 'rebass' import styled, { CSSProperties } from 'styled-components' @@ -69,12 +69,14 @@ export default function DateTimePicker({ onSetDate, expire, defaultDate, + title, }: { isOpen: boolean onDismiss: () => void onSetDate: (val: Date | number) => void expire: number defaultDate?: Date + title?: ReactNode }) { const today = new Date() const minDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()) @@ -158,9 +160,7 @@ export default function DateTimePicker({ - - Customize the Expiry Time - + {title || Customize the Expiry Time} diff --git a/apps/kyberswap-interface/src/hooks/usePermitNft.ts b/apps/kyberswap-interface/src/hooks/usePermitNft.ts new file mode 100644 index 0000000000..5327aa8921 --- /dev/null +++ b/apps/kyberswap-interface/src/hooks/usePermitNft.ts @@ -0,0 +1,195 @@ +import { BigNumber } from '@ethersproject/bignumber' +import { t } from '@lingui/macro' +import { defaultAbiCoder } from 'ethers/lib/utils' +import { useCallback, useMemo, useState } from 'react' + +import { NotificationType } from 'components/Announcement/type' +import { useActiveWeb3React, useWeb3React } from 'hooks' +import { useNotify } from 'state/application/hooks' +import { useSingleCallResult } from 'state/multicall/hooks' +import { friendlyError } from 'utils/errorMessage' + +import { useReadingContract } from './useContract' + +export enum PermitNftState { + NOT_APPLICABLE = 'not_applicable', + READY_TO_SIGN = 'ready_to_sign', + SIGNING = 'signing', + SIGNED = 'signed', + ERROR = 'error', +} + +export interface PermitNftParams { + contractAddress: string + tokenId: string + spender: string + deadline?: number +} + +export interface PermitNftResult { + deadline: number + nonce: BigNumber + signature: string + permitData: string +} + +// NFT Position Manager ABI for permit functionality +const NFT_PERMIT_ABI = [ + 'function name() view returns (string)', + 'function nonces(address owner, uint256 word) view returns (uint256 bitmap)', + 'function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes signature) payable', +] + +// 30 days validity buffer +const PERMIT_NFT_VALIDITY_BUFFER = 30 * 24 * 60 * 60 + +export const usePermitNft = ({ contractAddress, tokenId, spender, deadline }: PermitNftParams) => { + const { account, chainId } = useActiveWeb3React() + const { library } = useWeb3React() + const notify = useNotify() + const [isSigningInProgress, setIsSigningInProgress] = useState(false) + const [permitData, setPermitData] = useState(null) + + const nftContract = useReadingContract(contractAddress, NFT_PERMIT_ABI) + + // Get nonces bitmap for word 0 + const noncesState = useSingleCallResult(nftContract, 'nonces', [account, 0]) + const nameState = useSingleCallResult(nftContract, 'name', []) + + const permitState = useMemo(() => { + if (!account || !contractAddress || !tokenId || !spender) { + return PermitNftState.NOT_APPLICABLE + } + if (isSigningInProgress) { + return PermitNftState.SIGNING + } + if (permitData) { + return PermitNftState.SIGNED + } + return PermitNftState.READY_TO_SIGN + }, [account, contractAddress, tokenId, spender, isSigningInProgress, permitData]) + + const findFreeNonce = useCallback((bitmap: BigNumber, word = 0): BigNumber => { + // Find a free bit in the bitmap (unordered nonce) + for (let i = 0; i < 256; i++) { + if (bitmap.shr(i).and(1).isZero()) { + return BigNumber.from(word).shl(8).add(i) + } + } + throw new Error('No free nonce in word 0; pick a different word.') + }, []) + + const signPermitNft = useCallback(async (): Promise => { + if (!library || !account || !chainId || !noncesState?.result?.[0] || !nameState?.result?.[0]) { + console.error('Missing required data for NFT permit') + return null + } + + if (permitState !== PermitNftState.READY_TO_SIGN) { + console.error('NFT permit not ready to sign') + return null + } + + setIsSigningInProgress(true) + + try { + const contractName = nameState.result[0] + const bitmap = noncesState.result[0] + const nonce = findFreeNonce(bitmap, 0) + const permitDeadline = deadline || Math.floor(Date.now() / 1000) + PERMIT_NFT_VALIDITY_BUFFER + + // EIP-712 domain and types for NFT permit + const domain = { + name: contractName, + chainId, + verifyingContract: contractAddress, + } + + const types = { + Permit: [ + { name: 'spender', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + } + + const message = { + spender, + tokenId, + nonce: nonce.toString(), + deadline: permitDeadline, + } + + const typedData = JSON.stringify({ + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + ...types, + }, + domain, + primaryType: 'Permit', + message, + }) + + console.log('Signing NFT permit with data:', typedData) + + const signature = await library.send('eth_signTypedData_v4', [account.toLowerCase(), typedData]) + + // Encode permit data for contract call + const permitData = defaultAbiCoder.encode(['uint256', 'uint256', 'bytes'], [permitDeadline, nonce, signature]) + + notify({ + type: NotificationType.SUCCESS, + title: t`NFT Permit Signed`, + summary: t`Successfully signed permit for NFT #${tokenId}`, + }) + + const result = { + deadline: permitDeadline, + nonce, + signature, + permitData, + } + + setPermitData(result) + return result + } catch (error) { + const message = friendlyError(error) + console.error('NFT Permit error:', { message, error }) + + notify({ + title: t`NFT Permit Error`, + summary: message, + type: NotificationType.ERROR, + }) + + return null + } finally { + setIsSigningInProgress(false) + } + }, [ + account, + chainId, + library, + contractAddress, + tokenId, + spender, + deadline, + permitState, + noncesState?.result, + nameState?.result, + findFreeNonce, + notify, + ]) + + return { + permitState, + signPermitNft, + permitData, + isReady: permitState === PermitNftState.READY_TO_SIGN && !!noncesState?.result && !!nameState?.result, + } +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx new file mode 100644 index 0000000000..d611f939fe --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx @@ -0,0 +1,231 @@ +import { Trans, t } from '@lingui/macro' +import dayjs from 'dayjs' +import { X } from 'react-feather' +import { Box, Flex, Text } from 'rebass' + +import { ButtonOutlined, ButtonPrimary } from 'components/Button' +import TokenLogo from 'components/TokenLogo' +import { MouseoverTooltip, TextDashed } from 'components/Tooltip' +import { TIMES_IN_SECS } from 'constants/index' +import { PermitNftState, usePermitNft } from 'hooks/usePermitNft' +import useTheme from 'hooks/useTheme' +import { Badge, ChainImage, ImageContainer } from 'pages/Earns/UserPositions/styles' +import { ParsedPosition } from 'pages/Earns/types' + +import { Metric } from './Metrics' +import { useSmartExit } from './useSmartExit' + +export const Confirmation = ({ + selectedMetrics, + pos, + onDismiss, + feeYieldCondition, + priceCondition, + timeCondition, + expireTime, + conditionType, +}: { + selectedMetrics: Metric[] + pos: ParsedPosition + onDismiss: () => void + conditionType: 'and' | 'or' + expireTime: number + feeYieldCondition: string + priceCondition: { lte: string; gte: string } + timeCondition: { time: number | null; condition: 'after' | 'before' } +}) => { + const theme = useTheme() + + const today = new Date() + today.setUTCHours(0, 0, 0, 0) + const time = [7 * TIMES_IN_SECS.ONE_DAY, 30 * TIMES_IN_SECS.ONE_DAY, 90 * TIMES_IN_SECS.ONE_DAY].includes(expireTime) + ? today.getTime() + expireTime * 1000 + : expireTime + + const { permitState, signPermitNft, permitData } = usePermitNft({ + contractAddress: pos.id.split('-')[0], + tokenId: pos.tokenId, + // TODO + spender: '0xCa611DEb2914056D392bF77e13aCD544334dD957', + deadline: time, + }) + + const { createSmartExitOrder, isCreating, isSuccess } = useSmartExit({ + position: pos, + selectedMetrics, + conditionType, + feeYieldCondition, + priceCondition, + timeCondition, + expireTime: time, + permitData: permitData?.permitData, + signature: permitData?.signature, + }) + + const displayTime = dayjs(time).format('DD/MM/YYYY HH:mm:ss') + + const [condition0, condition1] = selectedMetrics + + return ( + <> + + + Confirmation + + + + + + Exit + + + + + + + + {pos.token0.symbol}/{pos.token1.symbol} + + Fee {pos?.pool.fee}% + + When + + + + {condition0 === Metric.FeeYield && The fee yield ≥ {feeYieldCondition}%} + {condition0 === Metric.Time && ( + <> + {timeCondition.condition.charAt(0).toUpperCase() + timeCondition.condition.slice(1)} + {dayjs(timeCondition.time).format('DD/MM/YYYY HH:mm:ss')} + + )} + {condition0 === Metric.PoolPrice && ( + <> + + Pool price is between + + + {priceCondition.gte} and {priceCondition.lte} {pos.token0.symbol}/{pos.token1.symbol} + + + )} + {condition1 && ( + <> + + + {conditionType === 'and' ? And : Or} + + + + {condition1 === Metric.FeeYield && The fee yield ≥ {feeYieldCondition}%} + {condition1 === Metric.Time && ( + <> + {timeCondition.condition.charAt(0).toUpperCase() + timeCondition.condition.slice(1)} + {dayjs(timeCondition.time).format('DD/MM/YYYY HH:mm:ss')} + + )} + {condition1 === Metric.PoolPrice && ( + <> + + Pool price is between + + + {priceCondition.gte} and {priceCondition.lte} {pos.token0.symbol}/{pos.token1.symbol} + + + )} + + )} + + + + + + Expires in + + + + {displayTime} + + + + + + The information is intended solely for your reference at the time you are viewing. It is your responsibility + to verify all information before making decisions + + + + {isSuccess ? ( + + { + // TODO: Navigate to orders page or open order details + console.log('View order clicked') + }} + flex={1} + > + View Order + + + Close + + + ) : ( + { + if (permitState === PermitNftState.SIGNED && permitData) { + // Create smart exit order + await createSmartExitOrder() + return + } + if (permitState === PermitNftState.READY_TO_SIGN) { + await signPermitNft() + } + }} + > + {isCreating ? ( + Creating Order... + ) : permitState === PermitNftState.SIGNED ? ( + Confirm Smart Exit + ) : permitState === PermitNftState.SIGNING ? ( + Signing... + ) : ( + Permit NFT + )} + + )} + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx new file mode 100644 index 0000000000..baf8eb3b44 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx @@ -0,0 +1,458 @@ +import { Label, RadioGroup, RadioGroupItem } from '@kyber/ui' +import { Trans, t } from '@lingui/macro' +import dayjs from 'dayjs' +import { useState } from 'react' +import { Calendar } from 'react-feather' +import { Box, Flex, Text } from 'rebass' + +import Divider from 'components/Divider' +import Input from 'components/Input' +import Select from 'components/Select' +import { DefaultSlippageOption } from 'components/SlippageControl' +import { MouseoverTooltip, TextDashed } from 'components/Tooltip' +import DateTimePicker from 'components/swapv2/LimitOrder/ExpirePicker' +import { TIMES_IN_SECS } from 'constants/index' +import useTheme from 'hooks/useTheme' +import { ParsedPosition } from 'pages/Earns/types' +import { ButtonText } from 'theme' + +export enum Metric { + FeeYield = 'fee_yield', + PoolPrice = 'pool_price', + Time = 'time', +} + +export const Metrics = ({ + position, + selectedMetrics, + setSelectedMetrics, + conditionType, + setConditionType, + expireTime, + setExpireTime, + feeYieldCondition, + setFeeYieldCondition, + priceCondition, + setPriceCondition, + timeCondition, + setTimeCondition, +}: { + position: ParsedPosition + selectedMetrics: [Metric, Metric | null] + setSelectedMetrics: (value: [Metric, Metric | null]) => void + conditionType: 'and' | 'or' + setConditionType: (v: 'and' | 'or') => void + expireTime: number + setExpireTime: (v: number) => void + feeYieldCondition: string + setFeeYieldCondition: (v: string) => void + priceCondition: { lte: string; gte: string } + setPriceCondition: (v: { lte: string; gte: string }) => void + timeCondition: { time: number | null; condition: 'after' | 'before' } + setTimeCondition: (v: { time: number | null; condition: 'after' | 'before' }) => void +}) => { + const theme = useTheme() + const [metric1, metric2] = selectedMetrics + + const displayTime = + expireTime % TIMES_IN_SECS.ONE_DAY === 0 + ? `${expireTime / TIMES_IN_SECS.ONE_DAY}D` + : dayjs(expireTime).format('DD/MM/YYYY HH:mm:ss') + + const [openDatePicker, setOpenDatePicker] = useState(false) + + return ( + + { + setSelectedMetrics([value, metric2]) + }} + selectedMetric={metric2} + position={position} + feeYieldCondition={feeYieldCondition} + setFeeYieldCondition={setFeeYieldCondition} + priceCondition={priceCondition} + setPriceCondition={setPriceCondition} + timeCondition={timeCondition} + setTimeCondition={setTimeCondition} + /> + + {metric2 ? ( + <> + setConditionType(v as 'and' | 'or')}> + + + + + + + + + + { + setSelectedMetrics([metric1, v]) + }} + selectedMetric={metric1} + position={position} + feeYieldCondition={feeYieldCondition} + setFeeYieldCondition={setFeeYieldCondition} + priceCondition={priceCondition} + setPriceCondition={setPriceCondition} + timeCondition={timeCondition} + setTimeCondition={setTimeCondition} + /> + + ) : ( + { + setSelectedMetrics([ + metric1, + [Metric.FeeYield, Metric.PoolPrice, Metric.Time].filter(item => item !== metric1)[0], + ]) + }} + > + + Add Condition 2 + + )} + + + Time Setup} + isOpen={openDatePicker} + onDismiss={() => { + setOpenDatePicker(false) + }} + onSetDate={(val: Date | number) => { + setExpireTime(typeof val === 'number' ? val : val.getTime()) + }} + expire={expireTime} + /> + + + + + Expires in + + + + + + {displayTime} + + + + + + + {[ + { label: '7D', value: TIMES_IN_SECS.ONE_DAY * 7 }, + { label: '30D', value: TIMES_IN_SECS.ONE_DAY * 30 }, + { label: '90D', value: TIMES_IN_SECS.ONE_DAY * 90 }, + { + label: 'Custom', + onSelect: () => { + setOpenDatePicker(true) + }, + }, + ].map((item: any) => { + return ( + { + if (item.label === 'Custom') item.onSelect() + else setExpireTime(item.value) + }} + data-active={ + item.label === 'Custom' ? expireTime % TIMES_IN_SECS.ONE_DAY != 0 : item.value === expireTime + } + > + {item.label} + + ) + })} + + + + ) +} + +const MetricSelect = ({ + metric, + setMetric, + selectedMetric, + position, + feeYieldCondition, + setFeeYieldCondition, + priceCondition, + setPriceCondition, + timeCondition, + setTimeCondition, +}: { + metric: Metric + setMetric: (value: Metric) => void + selectedMetric: Metric | null + position: ParsedPosition + feeYieldCondition: string + setFeeYieldCondition: (v: string) => void + priceCondition: { lte: string; gte: string } + setPriceCondition: (v: { lte: string; gte: string }) => void + timeCondition: { time: number | null; condition: 'after' | 'before' } + setTimeCondition: (v: { time: number | null; condition: 'after' | 'before' }) => void +}) => { + const theme = useTheme() + + const inputStyle = { + border: 'none', + padding: '8px 16px', + borderRadius: '12px', + fontSize: '16px', + color: theme.text, + flex: 1, + } + + const [openDatePicker, setOpenDatePicker] = useState(false) + + return ( + <> + + + Select Metric + + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + const numValue = parseFloat(value) + // Limit to 1-100% + if (value === '' || numValue > 0) { + setFeeYieldCondition(value) + } + } + }} + placeholder="0" + /> + + % + + + + + {[5, 10, 15, 20].map(item => { + const isSelected = feeYieldCondition === item.toString() + return ( + setFeeYieldCondition(item.toString())} + sx={{ + borderRadius: '999px', + border: `1px solid ${isSelected ? theme.primary : theme.border}`, + backgroundColor: isSelected ? theme.primary + '20' : 'transparent', + padding: '4px 12px', + color: isSelected ? theme.primary : theme.subText, + fontSize: '12px', + fontWeight: '500', + cursor: 'pointer', + '&:hover': { + backgroundColor: theme.primary + '10', + }, + }} + > + {item}% + + ) + })} + + + )} + + {metric === Metric.PoolPrice && ( + <> + + Exit when the pool price is between + + + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + setPriceCondition({ ...priceCondition, gte: value }) + } + }} + /> + - + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + setPriceCondition({ ...priceCondition, lte: value }) + } + }} + /> + + {position.token0.symbol}/{position.token1.symbol} + + + + )} + + {metric === Metric.Time && ( + <> + + + Exit this position + + setCustomGasUsd(v)} + placeholder={t`Custom`} + style={{ + width: '100%', + background: 'transparent', + fontSize: '12px', + }} + /> + + + + + {t`Current est. gas`} = ${(feeInfo?.gas.usd || 0).toFixed(2)} + + + + The buffer amount is recommended. The order will{' '} + + not execute + {' '} + if the actual cost exceeds this. + + + + The actual gas cost will be deducted from your outputs when the order executes. + + + + + @@ -212,13 +409,13 @@ export const SmartExit = ({ { - if (!disableBtn) setShowConfirm(true) + if (!disableBtn && feeInfo) setShowConfirm(true) }} > - Preview + {feeLoading ? t`Estimating fee...` : t`Preview`} @@ -236,8 +433,15 @@ export const SmartExit = ({ feeYieldCondition={feeYieldCondition} onDismiss={() => setShowConfirm(false)} conditionType={conditionType} - expireTime={expireTime} + deadline={deadline} pos={position} + feeSettings={{ + protocolFee: feeInfo?.protocol.percentage || 0, + maxFeesPercentage: + (feeInfo?.gas.percentage || 0) * + (customGasUsd ? parseFloat(customGasUsd) / (feeInfo?.gas.usd ?? 0) : multiplier) + + (feeInfo?.protocol.percentage || 0), + }} /> ) : ( setupContent diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts index 242f606465..e14b96b268 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts @@ -1,6 +1,11 @@ import { t } from '@lingui/macro' import { useCallback, useState } from 'react' -import { useGetSmartExitSignMessageMutation } from 'services/smartExit' +import { + SmartExitFeeParams, + SmartExitFeeResponse, + useEstimateSmartExitFeeMutation, + useGetSmartExitSignMessageMutation, +} from 'services/smartExit' import { NotificationType } from 'components/Announcement/type' import { SMART_EXIT_API_URL } from 'constants/env' @@ -13,32 +18,6 @@ import { ParsedPosition } from 'pages/Earns/types' import { useNotify } from 'state/application/hooks' import { friendlyError } from 'utils/errorMessage' -export interface SmartExitCondition { - logical: { - op: 'and' | 'or' - conditions: Array<{ - field: { - type: 'time' | 'pool_price' | 'fee_yield' - value: any - } - }> - } -} - -export interface SmartExitOrderParams { - chainId: number - userWallet: string - dexType: string - poolId: string - positionId: string - removeLiquidity: string - unwrap: boolean - permitData: string - condition: SmartExitCondition - signature: string - deadline: number -} - export interface UseSmartExitParams { position: ParsedPosition selectedMetrics: Metric[] @@ -46,7 +25,7 @@ export interface UseSmartExitParams { feeYieldCondition: string priceCondition: { gte: string; lte: string } timeCondition: { time: number | null; condition: 'after' | 'before' } - expireTime: number + deadline: number permitData?: string signature?: string } @@ -80,7 +59,7 @@ export const useSmartExit = ({ feeYieldCondition, priceCondition, timeCondition, - expireTime, + deadline, permitData, signature, }: UseSmartExitParams) => { @@ -90,6 +69,7 @@ export const useSmartExit = ({ const [state, setState] = useState(SmartExitState.IDLE) const [orderId, setOrderId] = useState(null) const [getSignMessage] = useGetSmartExitSignMessageMutation() + const [estimateFeeMutation] = useEstimateSmartExitFeeMutation() const getDexType = useCallback((dexId: string) => { // Map dex IDs to API format @@ -175,11 +155,138 @@ export const useSmartExit = ({ } }, [selectedMetrics, conditionType, feeYieldCondition, priceCondition, timeCondition]) - const createSmartExitOrder = useCallback(async (): Promise => { - if (!account || !chainId || !permitData || !signature || !library || !positionContract) { - console.error('Missing required data for smart exit order') - return false - } + const createSmartExitOrder = useCallback( + async (opts: { maxFeesPercentage: number[] }): Promise => { + if (!account || !chainId || !permitData || !signature || !library || !positionContract) { + console.error('Missing required data for smart exit order') + return false + } + let liquidity = '' + if ([DexType.DexTypeUniswapV3, DexType.DexTypePancakeV3].includes(dexType)) { + const res = await positionContract.positions(position.tokenId) + liquidity = res.liquidity.toString() + } else { + const res = await positionContract.getPositionLiquidity(position.tokenId) + liquidity = res.toString() + } + + if (!liquidity) { + console.log("Can't get liquidity of position") + return false + } + + setState(SmartExitState.CREATING) + + try { + // Step 1: Get sign message from API + const signMessageParams = { + chainId, + userWallet: account, + dexType: getDexType(position.dex.id), + poolId: position.pool.address, + positionId: position.id, + removeLiquidity: liquidity, + unwrap: position.token0.isNative || position.token1.isNative, + permitData, + condition: buildConditions(), + deadline, + maxFeesPercentage: opts.maxFeesPercentage, + } + + console.log('Getting sign message with params:', signMessageParams) + + const signMessageResult = await getSignMessage(signMessageParams).unwrap() + const typedData = signMessageResult.message + + if (!typedData || !typedData.domain || !typedData.types || !typedData.message) { + throw new Error('Failed to get valid typed data from API') + } + + // Step 2: Sign the typed data + console.log('Signing typed data:', typedData) + const orderSignature = await library.send('eth_signTypedData_v4', [account, JSON.stringify(typedData)]) + + // Step 3: Create the order with both signatures + const orderParams: SmartExitFeeParams & { signature: string; maxFeesPercentage: number[] } = { + chainId, + userWallet: account, + dexType: dexType, + poolId: position.pool.address, + positionId: position.id, + removeLiquidity: liquidity, + unwrap: false, + permitData, + condition: buildConditions(), + signature: orderSignature, + deadline, + maxFeesPercentage: opts.maxFeesPercentage, + } + + console.log('Creating smart exit order with params:', orderParams) + + const response = await fetch(`${SMART_EXIT_API_URL}/v1/orders/smart-exit`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(orderParams), + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.message || `HTTP error! status: ${response.status}`) + } + + const result = await response.json() + setOrderId(result.orderId || result.id) + setState(SmartExitState.SUCCESS) + + notify({ + type: NotificationType.SUCCESS, + title: t`Smart Exit Order Created`, + summary: t`Your smart exit order has been successfully created and is now active.`, + }) + + return true + } catch (error) { + const message = friendlyError(error) + console.error('Smart exit order creation error:', { message, error }) + + setState(SmartExitState.ERROR) + notify({ + title: t`Smart Exit Order Error`, + summary: message, + type: NotificationType.ERROR, + }) + + return false + } + }, + [ + account, + chainId, + permitData, + signature, + position, + buildConditions, + notify, + deadline, + library, + getSignMessage, + getDexType, + dexType, + positionContract, + ], + ) + + const reset = useCallback(() => { + setState(SmartExitState.IDLE) + setOrderId(null) + }, []) + + const estimateFee = useCallback(async (): Promise => { + if (!account || !chainId || !positionContract) return null + let liquidity = '' if ([DexType.DexTypeUniswapV3, DexType.DexTypePancakeV3].includes(dexType)) { const res = await positionContract.positions(position.tokenId) @@ -188,121 +295,50 @@ export const useSmartExit = ({ const res = await positionContract.getPositionLiquidity(position.tokenId) liquidity = res.toString() } - - if (!liquidity) { - console.log("Can't get liquidity of position") - return false + if (!liquidity) return null + + const payload = { + chainId, + userWallet: account, + dexType: getDexType(position.dex.id), + poolId: position.pool.address, + positionId: position.id, + removeLiquidity: liquidity, + unwrap: position.token0.isNative || position.token1.isNative, + permitData: '0x', + condition: buildConditions(), + deadline, } - setState(SmartExitState.CREATING) - try { - // Step 1: Get sign message from API - const signMessageParams = { - chainId, - userWallet: account, - dexType: getDexType(position.dex.id), - poolId: position.pool.address, - positionId: position.id, - removeLiquidity: liquidity, - unwrap: position.token0.isNative || position.token1.isNative, - permitData, - condition: buildConditions(), - deadline: expireTime, - } - - console.log('Getting sign message with params:', signMessageParams) - - const signMessageResult = await getSignMessage(signMessageParams).unwrap() - const typedData = signMessageResult.message - - if (!typedData || !typedData.domain || !typedData.types || !typedData.message) { - throw new Error('Failed to get valid typed data from API') - } - - // Step 2: Sign the typed data - console.log('Signing typed data:', typedData) - const orderSignature = await library.send('eth_signTypedData_v4', [account, JSON.stringify(typedData)]) - - // Step 3: Create the order with both signatures - const orderParams: SmartExitOrderParams = { - chainId, - userWallet: account, - dexType: dexType, - poolId: position.pool.address, - positionId: position.id, - removeLiquidity: liquidity, - unwrap: false, - permitData, - condition: buildConditions(), - signature: orderSignature, - deadline: expireTime, - } - - console.log('Creating smart exit order with params:', orderParams) - - const response = await fetch(`${SMART_EXIT_API_URL}/v1/orders/smart-exit`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(orderParams), - }) - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) - throw new Error(errorData.message || `HTTP error! status: ${response.status}`) - } - - const result = await response.json() - setOrderId(result.orderId || result.id) - setState(SmartExitState.SUCCESS) - - notify({ - type: NotificationType.SUCCESS, - title: t`Smart Exit Order Created`, - summary: t`Your smart exit order has been successfully created and is now active.`, - }) - - return true - } catch (error) { - const message = friendlyError(error) - console.error('Smart exit order creation error:', { message, error }) - - setState(SmartExitState.ERROR) - notify({ - title: t`Smart Exit Order Error`, - summary: message, - type: NotificationType.ERROR, - }) - - return false + const res = await estimateFeeMutation(payload).unwrap() + const isValid = + res && + typeof (res as any).gas === 'object' && + typeof (res as any).gas.usd === 'number' && + Number.isFinite((res as any).gas.usd) + if (!isValid) return null + return res + } catch (e) { + return null } }, [ account, chainId, - permitData, - signature, + positionContract, + dexType, position, - buildConditions, - notify, - expireTime, - library, - getSignMessage, getDexType, - dexType, - positionContract, + buildConditions, + estimateFeeMutation, + deadline, ]) - const reset = useCallback(() => { - setState(SmartExitState.IDLE) - setOrderId(null) - }, []) - return { state, orderId, createSmartExitOrder, + estimateFee, reset, isCreating: state === SmartExitState.CREATING, isSuccess: state === SmartExitState.SUCCESS, diff --git a/apps/kyberswap-interface/src/services/smartExit.ts b/apps/kyberswap-interface/src/services/smartExit.ts index 80e749d16a..66a3926c92 100644 --- a/apps/kyberswap-interface/src/services/smartExit.ts +++ b/apps/kyberswap-interface/src/services/smartExit.ts @@ -5,6 +5,18 @@ import { SMART_EXIT_API_URL } from 'constants/env' import { RTK_QUERY_TAGS } from 'constants/index' import { OrderStatus } from 'pages/Earns/SmartExit/useSmartExitFilter' +interface SmartExitCondition { + logical: { + op: 'and' | 'or' + conditions: Array<{ + field: { + type: 'time' | 'pool_price' | 'fee_yield' + value: any + } + }> + } +} + export interface SmartExitOrder { id: string chainId: number @@ -14,46 +26,55 @@ export interface SmartExitOrder { positionId: string removeLiquidity: string unwrap: boolean - condition: { - logical: { - op: 'and' | 'or' - conditions: Array<{ - field: { - type: 'time' | 'pool_price' | 'fee_yield' - value: any - } - }> - } - } + condition: SmartExitCondition status: OrderStatus createdAt: number updatedAt: number deadline: number } +interface SmartExitOrderResponse { + orders: SmartExitOrder[] + totalItems: number + totalPages: number + currentPage: number + pageSize: number +} + +interface SmartExitOrderParams { + chainIds?: string + userWallet: string + status?: string + dexTypes?: string + page?: number + pageSize?: number + positionIds?: string[] +} + +export interface SmartExitFeeResponse { + protocol: { percentage: number; category: string } + gas: { percentage: number; usd: number; wei: string } +} + +export interface SmartExitFeeParams { + chainId: number + userWallet: string + dexType: string + poolId: string + positionId: string + removeLiquidity: string + unwrap: boolean + permitData: string + condition: SmartExitCondition + deadline: number +} + const smartExitApi = createApi({ reducerPath: 'smartExitApi', baseQuery: fetchBaseQuery({ baseUrl: SMART_EXIT_API_URL }), tagTypes: [RTK_QUERY_TAGS.GET_SMART_EXIT_ORDERS], endpoints: builder => ({ - getSmartExitOrders: builder.query< - { - orders: SmartExitOrder[] - totalItems: number - totalPages: number - currentPage: number - pageSize: number - }, - { - chainIds?: string - userWallet: string - status?: string - dexTypes?: string - page?: number - pageSize?: number - positionIds?: string[] - } - >({ + getSmartExitOrders: builder.query({ query: ({ chainIds, dexTypes, userWallet, status, page = 1, pageSize = 10, positionIds }) => { const params = new URLSearchParams({ userWallet, @@ -96,29 +117,7 @@ const smartExitApi = createApi({ createSmartExitOrder: builder.mutation< { id: string; orderId: string }, - { - chainId: number - userWallet: string - dexType: string - poolId: string - positionId: string - removeLiquidity: string - unwrap: boolean - permitData: string - condition: { - logical: { - op: 'and' | 'or' - conditions: Array<{ - field: { - type: 'time' | 'pool_price' | 'fee_yield' - value: any - } - }> - } - } - signature: string - deadline: number - } + SmartExitFeeParams & { signature: string; maxFeesPercentage?: number[] } >({ query: body => ({ url: '/v1/orders/smart-exit', @@ -135,6 +134,21 @@ const smartExitApi = createApi({ invalidatesTags: [RTK_QUERY_TAGS.GET_SMART_EXIT_ORDERS], }), + estimateSmartExitFee: builder.mutation({ + query: body => ({ + url: '/v1/orders/smart-exit/estimate-fee', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body, + }), + transformResponse: (data: any) => ({ + protocol: data?.data?.protocol ?? {}, + gas: data?.data?.gas ?? {}, + }), + }), + getSmartExitSignMessage: builder.mutation< { message: { @@ -144,27 +158,7 @@ const smartExitApi = createApi({ primaryType: string } }, - { - chainId: number - userWallet: string - dexType: string - poolId: string - positionId: string - removeLiquidity: string - unwrap: boolean - permitData: string - condition: { - logical: { - op: 'and' | 'or' - conditions: Array<{ - field: { - type: 'time' | 'pool_price' | 'fee_yield' - value: any - } - }> - } - } - } + SmartExitFeeParams & { maxFeesPercentage?: number[] } >({ query: body => ({ url: '/v1/orders/smart-exit/sign-message', @@ -236,6 +230,7 @@ export const { useGetSmartExitSignMessageMutation, useGetSmartExitCancelSignMessageMutation, useCancelSmartExitOrderMutation, + useEstimateSmartExitFeeMutation, } = smartExitApi export default smartExitApi diff --git a/apps/kyberswap-interface/src/theme/color.ts b/apps/kyberswap-interface/src/theme/color.ts index dd912bc990..397b3d4eae 100644 --- a/apps/kyberswap-interface/src/theme/color.ts +++ b/apps/kyberswap-interface/src/theme/color.ts @@ -5,6 +5,7 @@ export function colors() { return { // base white, + white2: '#fafafa', black, // text From a4edd9347a08efbfbe0e286a7d6bdea2d55ec457 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 3 Nov 2025 15:31:59 +0700 Subject: [PATCH 35/87] fix --- .../Earns/components/SmartExit/index.tsx | 5 ++- .../components/SmartExit/useSmartExit.ts | 37 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx index 70a503a470..ea9ffbbdbb 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx @@ -125,7 +125,7 @@ export const SmartExit = ({ const res = await estimateFee() setFeeInfo(res) } catch { - setFeeInfo(null) + if (feeInfo) setFeeInfo(null) } finally { setFeeLoading(false) } @@ -143,7 +143,8 @@ export const SmartExit = ({ intervalRef.current = null } } - }, [disableBtn, estimateFee, feeLoading, feeInfo]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [disableBtn]) const setupContent = ( <> diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts index e14b96b268..80c6400de7 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts @@ -10,13 +10,13 @@ import { import { NotificationType } from 'components/Announcement/type' import { SMART_EXIT_API_URL } from 'constants/env' import { useActiveWeb3React, useWeb3React } from 'hooks' -import { useReadingContract } from 'hooks/useContract' import { DexType } from 'pages/Earns/SmartExit/useSmartExitFilter' import { Metric } from 'pages/Earns/components/SmartExit/Metrics' import { Exchange } from 'pages/Earns/constants' import { ParsedPosition } from 'pages/Earns/types' import { useNotify } from 'state/application/hooks' import { friendlyError } from 'utils/errorMessage' +import { getReadingContractWithCustomChain } from 'utils/getContract' export interface UseSmartExitParams { position: ParsedPosition @@ -79,9 +79,6 @@ export const useSmartExit = ({ // Detect version from dex type const dexType = getDexType(position.dex.id) - // Contract setup for liquidity fetching - const positionContract = useReadingContract(position.id.split('-')[0], POSITION_MANAGER_ABI) - const buildConditions = useCallback(() => { const conditions: Array<{ field: { @@ -157,10 +154,19 @@ export const useSmartExit = ({ const createSmartExitOrder = useCallback( async (opts: { maxFeesPercentage: number[] }): Promise => { - if (!account || !chainId || !permitData || !signature || !library || !positionContract) { + if (!account || !chainId || !permitData || !signature || !library) { console.error('Missing required data for smart exit order') return false } + const positionContract = getReadingContractWithCustomChain( + position.id.split('-')[0], + POSITION_MANAGER_ABI, + position.chain.id, + ) + if (!positionContract) { + console.error('Failed to get position contract') + return false + } let liquidity = '' if ([DexType.DexTypeUniswapV3, DexType.DexTypePancakeV3].includes(dexType)) { const res = await positionContract.positions(position.tokenId) @@ -169,7 +175,6 @@ export const useSmartExit = ({ const res = await positionContract.getPositionLiquidity(position.tokenId) liquidity = res.toString() } - if (!liquidity) { console.log("Can't get liquidity of position") return false @@ -275,7 +280,6 @@ export const useSmartExit = ({ getSignMessage, getDexType, dexType, - positionContract, ], ) @@ -285,6 +289,11 @@ export const useSmartExit = ({ }, []) const estimateFee = useCallback(async (): Promise => { + const positionContract = getReadingContractWithCustomChain( + position.id.split('-')[0], + POSITION_MANAGER_ABI, + position.chain.id, + ) if (!account || !chainId || !positionContract) return null let liquidity = '' @@ -295,6 +304,8 @@ export const useSmartExit = ({ const res = await positionContract.getPositionLiquidity(position.tokenId) liquidity = res.toString() } + console.log('liquidity', liquidity) + if (!liquidity) return null const payload = { @@ -322,17 +333,7 @@ export const useSmartExit = ({ } catch (e) { return null } - }, [ - account, - chainId, - positionContract, - dexType, - position, - getDexType, - buildConditions, - estimateFeeMutation, - deadline, - ]) + }, [account, chainId, dexType, position, getDexType, buildConditions, estimateFeeMutation, deadline]) return { state, From 7ff8853424e6ab3c48497d69a014153371579190 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 3 Nov 2025 15:37:00 +0700 Subject: [PATCH 36/87] fix --- .../Earns/components/SmartExit/useSmartExit.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts index 80c6400de7..de6f50a3de 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts @@ -63,7 +63,7 @@ export const useSmartExit = ({ permitData, signature, }: UseSmartExitParams) => { - const { account, chainId } = useActiveWeb3React() + const { account } = useActiveWeb3React() const { library } = useWeb3React() const notify = useNotify() const [state, setState] = useState(SmartExitState.IDLE) @@ -154,7 +154,7 @@ export const useSmartExit = ({ const createSmartExitOrder = useCallback( async (opts: { maxFeesPercentage: number[] }): Promise => { - if (!account || !chainId || !permitData || !signature || !library) { + if (!account || !permitData || !signature || !library) { console.error('Missing required data for smart exit order') return false } @@ -185,7 +185,7 @@ export const useSmartExit = ({ try { // Step 1: Get sign message from API const signMessageParams = { - chainId, + chainId: position.chain.id, userWallet: account, dexType: getDexType(position.dex.id), poolId: position.pool.address, @@ -213,7 +213,7 @@ export const useSmartExit = ({ // Step 3: Create the order with both signatures const orderParams: SmartExitFeeParams & { signature: string; maxFeesPercentage: number[] } = { - chainId, + chainId: position.chain.id, userWallet: account, dexType: dexType, poolId: position.pool.address, @@ -269,7 +269,6 @@ export const useSmartExit = ({ }, [ account, - chainId, permitData, signature, position, @@ -294,7 +293,7 @@ export const useSmartExit = ({ POSITION_MANAGER_ABI, position.chain.id, ) - if (!account || !chainId || !positionContract) return null + if (!account || !positionContract) return null let liquidity = '' if ([DexType.DexTypeUniswapV3, DexType.DexTypePancakeV3].includes(dexType)) { @@ -309,7 +308,7 @@ export const useSmartExit = ({ if (!liquidity) return null const payload = { - chainId, + chainId: position.chain.id, userWallet: account, dexType: getDexType(position.dex.id), poolId: position.pool.address, @@ -333,7 +332,7 @@ export const useSmartExit = ({ } catch (e) { return null } - }, [account, chainId, dexType, position, getDexType, buildConditions, estimateFeeMutation, deadline]) + }, [account, dexType, position, getDexType, buildConditions, estimateFeeMutation, deadline]) return { state, From 204643ae3ba3ae2261f2cfe5fea600f7ca8d2940 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 4 Nov 2025 11:43:30 +0700 Subject: [PATCH 37/87] fix: nonce using timestamp --- apps/kyberswap-interface/src/hooks/usePermitNft.ts | 5 ++--- .../src/pages/Earns/components/SmartExit/Confirmation.tsx | 3 +-- apps/kyberswap-interface/src/pages/Earns/constants/index.ts | 2 ++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/kyberswap-interface/src/hooks/usePermitNft.ts b/apps/kyberswap-interface/src/hooks/usePermitNft.ts index a0179073c1..cab99d4860 100644 --- a/apps/kyberswap-interface/src/hooks/usePermitNft.ts +++ b/apps/kyberswap-interface/src/hooks/usePermitNft.ts @@ -5,12 +5,11 @@ import { useCallback, useMemo, useState } from 'react' import { NotificationType } from 'components/Announcement/type' import { useActiveWeb3React, useWeb3React } from 'hooks' +import { useReadingContract } from 'hooks/useContract' import { useNotify } from 'state/application/hooks' import { useSingleCallResult } from 'state/multicall/hooks' import { friendlyError } from 'utils/errorMessage' -import { useReadingContract } from './useContract' - export enum PermitNftState { NOT_APPLICABLE = 'not_applicable', READY_TO_SIGN = 'ready_to_sign', @@ -148,7 +147,7 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline, vers setIsSigningInProgress(true) try { - const nonce = getNonce() + const nonce = actualVersion === 'v3' ? getNonce() : BigNumber.from(Date.now()) if (!nonce) { throw new Error(`Failed to get nonce for ${actualVersion}`) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx index f0a820ab89..ed8bd15a6d 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx @@ -16,10 +16,9 @@ import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' import { Badge, ImageContainer } from 'pages/Earns/UserPositions/styles' import { Metric } from 'pages/Earns/components/SmartExit/Metrics' import { useSmartExit } from 'pages/Earns/components/SmartExit/useSmartExit' +import { SMART_EXIT_ADDRESS } from 'pages/Earns/constants' import { ParsedPosition } from 'pages/Earns/types' -const SMART_EXIT_ADDRESS = '0x52ee3c8dd099ccb542c6227855d68c79e3e956f9' - export const Confirmation = ({ selectedMetrics, pos, diff --git a/apps/kyberswap-interface/src/pages/Earns/constants/index.ts b/apps/kyberswap-interface/src/pages/Earns/constants/index.ts index 42d644620d..fb76d6312f 100644 --- a/apps/kyberswap-interface/src/pages/Earns/constants/index.ts +++ b/apps/kyberswap-interface/src/pages/Earns/constants/index.ts @@ -131,3 +131,5 @@ export const LIMIT_TEXT_STYLES = { overflow: 'hidden', whiteSpace: 'nowrap', } + +export const SMART_EXIT_ADDRESS = '0x52ee3c8dd099ccb542c6227855d68c79e3e956f9' From 7055097fd9b5cf27e0437858a7825193da3e5f8f Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 4 Nov 2025 14:10:48 +0700 Subject: [PATCH 38/87] fix: small gas --- .../src/pages/Earns/components/SmartExit/index.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx index ea9ffbbdbb..ababfb6450 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx @@ -282,15 +282,14 @@ export const SmartExit = ({ {!feeInfo ? ( -- ) : ( - + setFeeSettingExpanded(e => !e)} style={{ cursor: 'default' }}> - ${customGasUsd ? customGasUsd : (feeInfo.gas.usd * multiplier).toFixed(2)} + ${customGasUsd ? customGasUsd : (feeInfo.gas.usd * multiplier).toPrecision(2)} setFeeSettingExpanded(e => !e)} > - ${(item * (feeInfo?.gas.usd || 0)).toFixed(2)} + ${formatDisplayNumber(item * (feeInfo?.gas.usd || 0), { significantDigits: 2 })} ) })} @@ -383,7 +382,7 @@ export const SmartExit = ({ - {t`Current est. gas`} = ${(feeInfo?.gas.usd || 0).toFixed(2)} + {t`Current est. gas`} = ${(feeInfo?.gas.usd || 0).toPrecision(2)} From 7179ca8921b1248c6d2150e60b51115ee1dc01ee Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 4 Nov 2025 15:51:29 +0700 Subject: [PATCH 39/87] refactor --- .../Earns/UserPositions/TableContent.tsx | 4 +- .../Earns/components/SmartExit/Actions.tsx | 31 ++ .../Earns/components/SmartExit/GasSetting.tsx | 155 +++++++ .../Earns/components/SmartExit/Metrics.tsx | 2 +- .../Earns/components/SmartExit/PoolPrice.tsx | 75 ++++ .../SmartExit/PositionLiquidity.tsx | 54 +++ .../Earns/components/SmartExit/index.tsx | 409 +++--------------- .../Earns/components/SmartExit/styles.tsx | 21 + 8 files changed, 409 insertions(+), 342 deletions(-) create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Actions.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PositionLiquidity.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx diff --git a/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx b/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx index 3d4e2e5abc..c092619ab0 100644 --- a/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx @@ -299,9 +299,7 @@ export default function TableContent({ {claimRewardsModal} {zapMigrationWidget} {migrationModal} - {smartExitPosition && ( - setSmartExitPosition(null)} /> - )} + {smartExitPosition && setSmartExitPosition(null)} />}
{account && positions && positions.length > 0 diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Actions.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Actions.tsx new file mode 100644 index 0000000000..438e215ce8 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Actions.tsx @@ -0,0 +1,31 @@ +import { Trans } from '@lingui/macro' +import { Flex, Text } from 'rebass' + +import { ButtonOutlined, ButtonPrimary } from 'components/Button' + +export default function Actions({ + onDismiss, + onPreview, + disabled, + feeLoading, +}: { + onDismiss: () => void + onPreview: () => void + disabled: boolean + feeLoading: boolean +}) { + return ( + + + + Cancel + + + + + {feeLoading ? Estimating fee... : Preview} + + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx new file mode 100644 index 0000000000..ba35331441 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx @@ -0,0 +1,155 @@ +import { Trans, t } from '@lingui/macro' +import { rgba } from 'polished' +import { useState } from 'react' +import { Box, Flex, Text } from 'rebass' +import { SmartExitFeeResponse } from 'services/smartExit' + +import Input from 'components/NumericalInput' +import { DropdownIcon } from 'components/SwapForm/SlippageSetting' +import useTheme from 'hooks/useTheme' +import { formatDisplayNumber } from 'utils/numbers' + +export default function GasSetting({ + feeInfo, + multiplier, + setMultiplier, + customGasUsd, + setCustomGasUsd, +}: { + feeInfo: SmartExitFeeResponse | null + multiplier: number + setMultiplier: (value: number) => void + customGasUsd: string + setCustomGasUsd: (value: string) => void +}) { + const theme = useTheme() + const [feeSettingExpanded, setFeeSettingExpanded] = useState(false) + + const isWarningGas = feeInfo && customGasUsd && parseFloat(customGasUsd) < (feeInfo.gas.usd || 0) + const isHighlightGas = + feeInfo && + !feeSettingExpanded && + (customGasUsd ? parseFloat(customGasUsd) > feeInfo.gas.usd : feeInfo.gas.usd * multiplier > feeInfo.gas.usd) + + return ( + <> + + {t`Max Execution Gas`}: + {!feeInfo ? ( + -- + ) : ( + setFeeSettingExpanded(e => !e)} style={{ cursor: 'default' }}> + + ${customGasUsd ? customGasUsd : (feeInfo.gas.usd * multiplier).toPrecision(2)} + + + + + + + + )} + + + + {[1, 1.5, 2, 3].map(item => { + const isSelected = !customGasUsd && multiplier === item + return ( + { + setCustomGasUsd('') + setMultiplier(item) + }} + sx={{ + borderRadius: '999px', + border: `1px solid ${isSelected ? theme.primary : theme.border}`, + backgroundColor: isSelected ? theme.primary + '20' : 'transparent', + padding: '6px 4px', + color: isSelected ? theme.primary : theme.subText, + fontSize: '12px', + fontWeight: '500', + cursor: 'pointer', + textAlign: 'center', + flex: 1, + '&:hover': { + backgroundColor: theme.primary + '10', + }, + }} + > + ${formatDisplayNumber(item * (feeInfo?.gas.usd || 0), { significantDigits: 2 })} + + ) + })} + + {/* Custom option */} + + + $ + + setCustomGasUsd(v)} + placeholder={t`Custom`} + style={{ + width: '100%', + background: 'transparent', + fontSize: '12px', + }} + /> + + + + + {t`Current est. gas`} = ${(feeInfo?.gas.usd || 0).toPrecision(2)} + + + + The buffer amount is recommended. The order will{' '} + + not execute + {' '} + if the actual cost exceeds this. + + + + The actual gas cost will be deducted from your outputs when the order executes. + + + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx index c33f4fd3d3..fb3cab861a 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx @@ -303,7 +303,7 @@ const MetricSelect = ({ label: 'Time', value: Metric.Time, }, - ].filter(item => item.value !== selectedMetric)} + ].filter(item => item.value === Metric.PoolPrice || item.value !== selectedMetric)} onChange={value => { setMetric(value) }} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx new file mode 100644 index 0000000000..868c42a952 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx @@ -0,0 +1,75 @@ +import { Trans } from '@lingui/macro' +import { useState } from 'react' +import { Flex, Text } from 'rebass' + +import { ReactComponent as RevertPriceIcon } from 'assets/svg/earn/ic_revert_price.svg' +import Divider from 'components/Divider' +import InfoHelper from 'components/InfoHelper' +import useTheme from 'hooks/useTheme' +import { RevertIconWrapper } from 'pages/Earns/PositionDetail/styles' +import PriceRange from 'pages/Earns/UserPositions/PriceRange' +import { PositionValueWrapper } from 'pages/Earns/UserPositions/styles' +import { CustomBox } from 'pages/Earns/components/SmartExit/styles' +import { ParsedPosition } from 'pages/Earns/types' +import { ExternalLink } from 'theme' +import { formatDisplayNumber } from 'utils/numbers' + +export default function PoolPrice({ position }: { position: ParsedPosition }) { + const theme = useTheme() + const [revertPrice, setRevertPrice] = useState(false) + + return ( + + + + Current Price + + + 1 {revertPrice ? position.token1.symbol : position.token0.symbol} ={' '} + {formatDisplayNumber(revertPrice ? 1 / position.priceRange.current : position.priceRange.current, { + significantDigits: 6, + })}{' '} + {revertPrice ? position.token0.symbol : position.token1.symbol} + + setRevertPrice(!revertPrice)}> + + + + + + + + + + + + + Earning Fee Yield{' '} + + + Based on the amount of fee tokens your position has earned compared with your initial deposit. + + + + Details + + + + } + />{' '} + + {position.earningFeeYield.toFixed(2)}% + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PositionLiquidity.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PositionLiquidity.tsx new file mode 100644 index 0000000000..ed522934aa --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PositionLiquidity.tsx @@ -0,0 +1,54 @@ +import { Trans } from '@lingui/macro' +import { Flex, Text } from 'rebass' + +import TokenLogo from 'components/TokenLogo' +import useTheme from 'hooks/useTheme' +import { CustomBox } from 'pages/Earns/components/SmartExit/styles' +import { ParsedPosition } from 'pages/Earns/types' +import { formatDisplayNumber } from 'utils/numbers' + +export default function PositionLiquidity({ position }: { position: ParsedPosition }) { + const theme = useTheme() + + return ( + + + + Your Position Liquidity + + {formatDisplayNumber(position.totalValue, { style: 'currency', significantDigits: 4 })} + + + + + {position.token0.symbol} + + + {formatDisplayNumber(position.token0.totalProvide, { significantDigits: 6 })} + + {formatDisplayNumber(position.token0.price * position.token0.totalProvide, { + style: 'currency', + significantDigits: 6, + })} + + + + + + + {position.token1.symbol} + + + + {formatDisplayNumber(position.token1.totalProvide, { significantDigits: 6 })} + + {formatDisplayNumber(position.token1.price * position.token1.totalProvide, { + style: 'currency', + significantDigits: 6, + })} + + + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx index ababfb6450..856b414050 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx @@ -1,63 +1,24 @@ -import { Trans, t } from '@lingui/macro' -import { rgba } from 'polished' +import { Trans } from '@lingui/macro' import { useEffect, useMemo, useRef, useState } from 'react' import { X } from 'react-feather' -import { Box, Flex, Text } from 'rebass' +import { Flex, Text } from 'rebass' import { SmartExitFeeResponse } from 'services/smartExit' -import styled from 'styled-components' -import { ReactComponent as RevertPriceIcon } from 'assets/svg/earn/ic_revert_price.svg' -import { ButtonOutlined, ButtonPrimary } from 'components/Button' import Divider from 'components/Divider' -import InfoHelper from 'components/InfoHelper' import Modal from 'components/Modal' -import Input from 'components/NumericalInput' -import { DropdownIcon } from 'components/SwapForm/SlippageSetting' -import TokenLogo from 'components/TokenLogo' import { TIMES_IN_SECS } from 'constants/index' -import useTheme from 'hooks/useTheme' import PositionDetailHeader from 'pages/Earns/PositionDetail/Header' -import { RevertIconWrapper } from 'pages/Earns/PositionDetail/styles' -import PriceRange from 'pages/Earns/UserPositions/PriceRange' -import { PositionValueWrapper } from 'pages/Earns/UserPositions/styles' +import Actions from 'pages/Earns/components/SmartExit/Actions' import { Confirmation } from 'pages/Earns/components/SmartExit/Confirmation' +import GasSetting from 'pages/Earns/components/SmartExit/GasSetting' import { Metric, Metrics } from 'pages/Earns/components/SmartExit/Metrics' +import PoolPrice from 'pages/Earns/components/SmartExit/PoolPrice' +import PositionLiquidity from 'pages/Earns/components/SmartExit/PositionLiquidity' +import { ContentWrapper } from 'pages/Earns/components/SmartExit/styles' import { useSmartExit } from 'pages/Earns/components/SmartExit/useSmartExit' import { ParsedPosition } from 'pages/Earns/types' -import { ExternalLink } from 'theme' -import { formatDisplayNumber } from 'utils/numbers' -const Content = styled.div` - margin-top: 20px; - display: flex; - flex-direction: row; - gap: 20px; - - ${({ theme }) => theme.mediaWidth.upToSmall` - flex-direction: column; - `} -` - -const CustomBox = styled.div` - border-radius: 1rem; - border: 1px solid ${({ theme }) => theme.border}; - padding: 12px; - flex-direction: column; - gap: 0.5rem; - display: flex; -` - -export const SmartExit = ({ - isOpen, - onDismiss, - position, -}: { - isOpen: boolean - onDismiss: () => void - position: ParsedPosition -}) => { - const theme = useTheme() - const [revertPrice, setRevertPrice] = useState(false) +export const SmartExit = ({ position, onDismiss }: { position: ParsedPosition; onDismiss: () => void }) => { const [selectedMetrics, setSelectedMetrics] = useState<[Metric, Metric | null]>([Metric.FeeYield, null]) const [conditionType, setConditionType] = useState<'and' | 'or'>('and') @@ -87,22 +48,14 @@ export const SmartExit = ({ (!priceCondition.gte || !priceCondition.lte || Number(priceCondition.gte) > Number(priceCondition.lte)) const invalidTime = selectedMetrics.includes(Metric.Time) && !timeCondition.time - const disableBtn = invalidYield || invalidPriceCondition || invalidTime - const [showConfirm, setShowConfirm] = useState(false) // Gas estimation + selection state const [feeInfo, setFeeInfo] = useState(null) const [feeLoading, setFeeLoading] = useState(false) const [multiplier, setMultiplier] = useState(1.5) - const [feeSettingExpanded, setFeeSettingExpanded] = useState(false) - const intervalRef = useRef(null) const [customGasUsd, setCustomGasUsd] = useState('') - const isWarningGas = feeInfo && customGasUsd && parseFloat(customGasUsd) < (feeInfo.gas.usd || 0) - const isHighlightGas = - feeInfo && - !feeSettingExpanded && - (customGasUsd ? parseFloat(customGasUsd) > feeInfo.gas.usd : feeInfo.gas.usd * multiplier > feeInfo.gas.usd) + const intervalRef = useRef(null) const { estimateFee } = useSmartExit({ position, @@ -114,9 +67,11 @@ export const SmartExit = ({ deadline, }) + const disabled = invalidYield || invalidPriceCondition || invalidTime || !feeInfo + // Auto-estimate when metrics are valid useEffect(() => { - if (disableBtn) return + if (invalidYield || invalidPriceCondition || invalidTime) return const call = async () => { if (feeLoading || feeInfo) return @@ -131,10 +86,8 @@ export const SmartExit = ({ } } - // immediate call call() - // poll every 15s intervalRef.current = window.setInterval(call, 15000) return () => { @@ -144,286 +97,10 @@ export const SmartExit = ({ } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [disableBtn]) - - const setupContent = ( - <> - - - Set Up Smart Exit - - - - -
} - /> - - - - - - - Your Position Liquidity - - {formatDisplayNumber(position.totalValue, { style: 'currency', significantDigits: 4 })} - - - - - {position.token0.symbol} - - - {formatDisplayNumber(position.token0.totalProvide, { significantDigits: 6 })} - - {formatDisplayNumber(position.token0.price * position.token0.totalProvide, { - style: 'currency', - significantDigits: 6, - })} - - - - - - - {position.token1.symbol} - - - - {formatDisplayNumber(position.token1.totalProvide, { significantDigits: 6 })} - - {formatDisplayNumber(position.token1.price * position.token1.totalProvide, { - style: 'currency', - significantDigits: 6, - })} - - - - - - - - - Current Price - - - 1 {revertPrice ? position.token1.symbol : position.token0.symbol} ={' '} - {formatDisplayNumber(revertPrice ? 1 / position.priceRange.current : position.priceRange.current, { - significantDigits: 6, - })}{' '} - {revertPrice ? position.token0.symbol : position.token1.symbol} - - setRevertPrice(!revertPrice)}> - - - - - - - - - - - - - Earning Fee Yield{' '} - - - Based on the amount of fee tokens your position has earned compared with your initial deposit. - - - - Details - - - - } - />{' '} -
- {position.earningFeeYield.toFixed(2)}% -
- -
- - - - - - {t`Max Execution Gas`}: - {!feeInfo ? ( - -- - ) : ( - setFeeSettingExpanded(e => !e)} style={{ cursor: 'default' }}> - - ${customGasUsd ? customGasUsd : (feeInfo.gas.usd * multiplier).toPrecision(2)} - - - - - - - - )} - - - - {[1, 1.5, 2, 3].map(item => { - const isSelected = !customGasUsd && multiplier === item - return ( - { - setCustomGasUsd('') - setMultiplier(item) - }} - sx={{ - borderRadius: '999px', - border: `1px solid ${isSelected ? theme.primary : theme.border}`, - backgroundColor: isSelected ? theme.primary + '20' : 'transparent', - padding: '6px 4px', - color: isSelected ? theme.primary : theme.subText, - fontSize: '12px', - fontWeight: '500', - cursor: 'pointer', - textAlign: 'center', - flex: 1, - '&:hover': { - backgroundColor: theme.primary + '10', - }, - }} - > - ${formatDisplayNumber(item * (feeInfo?.gas.usd || 0), { significantDigits: 2 })} - - ) - })} - - {/* Custom option */} - - - $ - - setCustomGasUsd(v)} - placeholder={t`Custom`} - style={{ - width: '100%', - background: 'transparent', - fontSize: '12px', - }} - /> - - - - - {t`Current est. gas`} = ${(feeInfo?.gas.usd || 0).toPrecision(2)} - - - - The buffer amount is recommended. The order will{' '} - - not execute - {' '} - if the actual cost exceeds this. - - - - The actual gas cost will be deducted from your outputs when the order executes. - - - - - - - - - - Cancel - - - { - if (!disableBtn && feeInfo) setShowConfirm(true) - }} - > - - {feeLoading ? t`Estimating fee...` : t`Preview`} - - - - - ) + }, [invalidYield || invalidPriceCondition || invalidTime]) return ( - + {showConfirm ? ( ) : ( - setupContent + <> + + + Set Up Smart Exit + + + + + } + /> + + + + + + + + + + + + + + + !disabled && setShowConfirm(true)} + disabled={disabled} + feeLoading={feeLoading} + /> + )} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx new file mode 100644 index 0000000000..3c5e0f5a0a --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx @@ -0,0 +1,21 @@ +import styled from 'styled-components' + +export const ContentWrapper = styled.div` + margin-top: 20px; + display: flex; + flex-direction: row; + gap: 20px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + flex-direction: column; + `} +` + +export const CustomBox = styled.div` + border-radius: 1rem; + border: 1px solid ${({ theme }) => theme.border}; + padding: 12px; + flex-direction: column; + gap: 0.5rem; + display: flex; +` From f63f71b83ab5fab433846c7cdbdb6964adb11374 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 4 Nov 2025 17:34:28 +0700 Subject: [PATCH 40/87] rename smart exit -> smart exit orders --- apps/kyberswap-interface/src/pages/App.tsx | 2 +- .../src/pages/Earns/{SmartExit => SmartExitOrders}/Filter.tsx | 2 +- .../src/pages/Earns/{SmartExit => SmartExitOrders}/index.tsx | 4 ++-- .../{SmartExit => SmartExitOrders}/useSmartExitFilter.ts | 0 .../src/pages/Earns/UserPositions/TableContent.tsx | 2 +- .../src/pages/Earns/components/SmartExit/useSmartExit.ts | 2 +- apps/kyberswap-interface/src/services/smartExit.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) rename apps/kyberswap-interface/src/pages/Earns/{SmartExit => SmartExitOrders}/Filter.tsx (97%) rename apps/kyberswap-interface/src/pages/Earns/{SmartExit => SmartExitOrders}/index.tsx (99%) rename apps/kyberswap-interface/src/pages/Earns/{SmartExit => SmartExitOrders}/useSmartExitFilter.ts (100%) diff --git a/apps/kyberswap-interface/src/pages/App.tsx b/apps/kyberswap-interface/src/pages/App.tsx index 245640f8d2..0bec1966e5 100644 --- a/apps/kyberswap-interface/src/pages/App.tsx +++ b/apps/kyberswap-interface/src/pages/App.tsx @@ -67,7 +67,7 @@ const Earns = lazy(() => import('pages/Earns/Landing')) const EarnPoolExplorer = lazy(() => import('pages/Earns/PoolExplorer')) const EarnUserPositions = lazy(() => import('pages/Earns/UserPositions')) const EarnPositionDetail = lazy(() => import('pages/Earns/PositionDetail')) -const SmartExit = lazy(() => import('pages/Earns/SmartExit')) +const SmartExit = lazy(() => import('pages/Earns/SmartExitOrders')) const AppWrapper = styled.div` display: flex; diff --git a/apps/kyberswap-interface/src/pages/Earns/SmartExit/Filter.tsx b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/Filter.tsx similarity index 97% rename from apps/kyberswap-interface/src/pages/Earns/SmartExit/Filter.tsx rename to apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/Filter.tsx index bff02b7e2c..4057eb34b9 100644 --- a/apps/kyberswap-interface/src/pages/Earns/SmartExit/Filter.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/Filter.tsx @@ -4,7 +4,7 @@ import { useMedia } from 'react-use' import { Flex } from 'rebass' import { NETWORKS_INFO } from 'hooks/useChainsConfig' -import { DexType, OrderStatus } from 'pages/Earns/SmartExit/useSmartExitFilter' +import { DexType, OrderStatus } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' import DropdownMenu from 'pages/Earns/components/DropdownMenu' import { AllChainsOption } from 'pages/Earns/hooks/useSupportedDexesAndChains' import { SmartExitFilter } from 'pages/Earns/types' diff --git a/apps/kyberswap-interface/src/pages/Earns/SmartExit/index.tsx b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/index.tsx similarity index 99% rename from apps/kyberswap-interface/src/pages/Earns/SmartExit/index.tsx rename to apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/index.tsx index 218b6ddc1a..cb391c5f00 100644 --- a/apps/kyberswap-interface/src/pages/Earns/SmartExit/index.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/index.tsx @@ -25,8 +25,8 @@ import useTheme from 'hooks/useTheme' import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' import { PoolPageWrapper, TableWrapper } from 'pages/Earns/PoolExplorer/styles' import { IconArrowLeft } from 'pages/Earns/PositionDetail/styles' -import Filter from 'pages/Earns/SmartExit/Filter' -import useSmartExitFilter, { OrderStatus } from 'pages/Earns/SmartExit/useSmartExitFilter' +import Filter from 'pages/Earns/SmartExitOrders/Filter' +import useSmartExitFilter, { OrderStatus } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' import { Badge, BadgeType, ImageContainer } from 'pages/Earns/UserPositions/styles' import { EarnChain, Exchange } from 'pages/Earns/constants' import { PositionStatus } from 'pages/Earns/types' diff --git a/apps/kyberswap-interface/src/pages/Earns/SmartExit/useSmartExitFilter.ts b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/useSmartExitFilter.ts similarity index 100% rename from apps/kyberswap-interface/src/pages/Earns/SmartExit/useSmartExitFilter.ts rename to apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/useSmartExitFilter.ts diff --git a/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx b/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx index c092619ab0..a5399cea82 100644 --- a/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx @@ -18,7 +18,7 @@ import { APP_PATHS, PAIR_CATEGORY } from 'constants/index' import { useActiveWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' import { PositionAction as PositionActionBtn } from 'pages/Earns/PositionDetail/styles' -import { OrderStatus } from 'pages/Earns/SmartExit/useSmartExitFilter' +import { OrderStatus } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' import DropdownAction from 'pages/Earns/UserPositions/DropdownAction' import MigrationModal from 'pages/Earns/UserPositions/MigrationModal' import PriceRange from 'pages/Earns/UserPositions/PriceRange' diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts index de6f50a3de..e175645011 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts @@ -10,7 +10,7 @@ import { import { NotificationType } from 'components/Announcement/type' import { SMART_EXIT_API_URL } from 'constants/env' import { useActiveWeb3React, useWeb3React } from 'hooks' -import { DexType } from 'pages/Earns/SmartExit/useSmartExitFilter' +import { DexType } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' import { Metric } from 'pages/Earns/components/SmartExit/Metrics' import { Exchange } from 'pages/Earns/constants' import { ParsedPosition } from 'pages/Earns/types' diff --git a/apps/kyberswap-interface/src/services/smartExit.ts b/apps/kyberswap-interface/src/services/smartExit.ts index 66a3926c92..5d4e4400e3 100644 --- a/apps/kyberswap-interface/src/services/smartExit.ts +++ b/apps/kyberswap-interface/src/services/smartExit.ts @@ -3,7 +3,7 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { SMART_EXIT_API_URL } from 'constants/env' import { RTK_QUERY_TAGS } from 'constants/index' -import { OrderStatus } from 'pages/Earns/SmartExit/useSmartExitFilter' +import { OrderStatus } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' interface SmartExitCondition { logical: { From 2d91b4a54b8e81b4f1e7585a16ddb17dd7e28773 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 4 Nov 2025 17:36:10 +0700 Subject: [PATCH 41/87] fix --- .../src/pages/Earns/components/SmartExit/PoolPrice.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx index 868c42a952..3859c48122 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx @@ -20,9 +20,9 @@ export default function PoolPrice({ position }: { position: ParsedPosition }) { return ( - + - Current Price + Current Price 1 {revertPrice ? position.token1.symbol : position.token0.symbol} ={' '} From d9647012f8d11944071881742df9319d28167151 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 5 Nov 2025 14:31:18 +0700 Subject: [PATCH 42/87] refactor type --- .../pages/Earns/SmartExitOrders/Filter.tsx | 4 +- .../src/pages/Earns/SmartExitOrders/index.tsx | 5 +- .../SmartExitOrders/useSmartExitFilter.ts | 16 -- .../Earns/UserPositions/TableContent.tsx | 3 +- .../components/SmartExit/Confirmation.tsx | 7 +- .../Earns/components/SmartExit/GasSetting.tsx | 11 +- .../Earns/components/SmartExit/Metrics.tsx | 22 +- .../Earns/components/SmartExit/index.tsx | 9 +- .../components/SmartExit/useSmartExit.ts | 18 +- .../src/pages/Earns/types/index.ts | 22 ++ .../src/pages/Earns/types/pool.ts | 62 +++++ .../Earns/{types.ts => types/position.ts} | 259 ++++-------------- .../src/pages/Earns/types/reward.ts | 58 ++++ .../src/pages/Earns/types/smartExit.ts | 57 ++++ .../src/services/smartExit.ts | 37 +-- 15 files changed, 294 insertions(+), 296 deletions(-) create mode 100644 apps/kyberswap-interface/src/pages/Earns/types/index.ts create mode 100644 apps/kyberswap-interface/src/pages/Earns/types/pool.ts rename apps/kyberswap-interface/src/pages/Earns/{types.ts => types/position.ts} (66%) create mode 100644 apps/kyberswap-interface/src/pages/Earns/types/reward.ts create mode 100644 apps/kyberswap-interface/src/pages/Earns/types/smartExit.ts diff --git a/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/Filter.tsx b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/Filter.tsx index 4057eb34b9..db9a23fefd 100644 --- a/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/Filter.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/Filter.tsx @@ -4,10 +4,10 @@ import { useMedia } from 'react-use' import { Flex } from 'rebass' import { NETWORKS_INFO } from 'hooks/useChainsConfig' -import { DexType, OrderStatus } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' +import { DexType } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' import DropdownMenu from 'pages/Earns/components/DropdownMenu' import { AllChainsOption } from 'pages/Earns/hooks/useSupportedDexesAndChains' -import { SmartExitFilter } from 'pages/Earns/types' +import { OrderStatus, SmartExitFilter } from 'pages/Earns/types' import { MEDIA_WIDTHS } from 'theme' const ORDER_STATUS = [ diff --git a/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/index.tsx b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/index.tsx index cb391c5f00..6d25894335 100644 --- a/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/index.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/index.tsx @@ -6,7 +6,6 @@ import { useNavigate } from 'react-router' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' import { - SmartExitOrder, useCancelSmartExitOrderMutation, useGetSmartExitCancelSignMessageMutation, useGetSmartExitOrdersQuery, @@ -26,10 +25,10 @@ import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' import { PoolPageWrapper, TableWrapper } from 'pages/Earns/PoolExplorer/styles' import { IconArrowLeft } from 'pages/Earns/PositionDetail/styles' import Filter from 'pages/Earns/SmartExitOrders/Filter' -import useSmartExitFilter, { OrderStatus } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' +import useSmartExitFilter from 'pages/Earns/SmartExitOrders/useSmartExitFilter' import { Badge, BadgeType, ImageContainer } from 'pages/Earns/UserPositions/styles' import { EarnChain, Exchange } from 'pages/Earns/constants' -import { PositionStatus } from 'pages/Earns/types' +import { OrderStatus, PositionStatus, SmartExitOrder } from 'pages/Earns/types' import { useNotify } from 'state/application/hooks' import { MEDIA_WIDTHS } from 'theme' import { enumToArrayOfValues } from 'utils' diff --git a/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/useSmartExitFilter.ts b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/useSmartExitFilter.ts index 8d94be0aad..2646c53e41 100644 --- a/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/useSmartExitFilter.ts +++ b/apps/kyberswap-interface/src/pages/Earns/SmartExitOrders/useSmartExitFilter.ts @@ -3,15 +3,6 @@ import { useSearchParams } from 'react-router-dom' import { SmartExitFilter } from 'pages/Earns/types' -// import { Direction } from 'pages/MarketOverview/SortIcon' - -// export enum SortBy { -// VALUE = 'value', -// APR = 'apr', -// UNCLAIMED_FEE = 'unclaimed_fees', -// UNCLAIMED_REWARDS = 'unclaimed_rewards', -// } - export enum DexType { DexTypeUniswapV3 = 'DexTypeUniswapV3', DexTypeUniswapV4 = 'DexTypeUniswapV4', @@ -21,13 +12,6 @@ export enum DexType { DexTypePancakeInfinityCLFairFlow = 'DexTypePancakeInfinityCLFairFlow', } -export enum OrderStatus { - OrderStatusOpen = 'OrderStatusOpen', - OrderStatusDone = 'OrderStatusDone', - OrderStatusCancelled = 'OrderStatusCancelled', - OrderStatusExpired = 'OrderStatusExpired', -} - export default function useSmartExitFilter() { const [searchParams, setSearchParams] = useSearchParams() diff --git a/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx b/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx index a5399cea82..9300b7ffe4 100644 --- a/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/UserPositions/TableContent.tsx @@ -18,7 +18,6 @@ import { APP_PATHS, PAIR_CATEGORY } from 'constants/index' import { useActiveWeb3React } from 'hooks' import useTheme from 'hooks/useTheme' import { PositionAction as PositionActionBtn } from 'pages/Earns/PositionDetail/styles' -import { OrderStatus } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' import DropdownAction from 'pages/Earns/UserPositions/DropdownAction' import MigrationModal from 'pages/Earns/UserPositions/MigrationModal' import PriceRange from 'pages/Earns/UserPositions/PriceRange' @@ -46,7 +45,7 @@ import useKemRewards from 'pages/Earns/hooks/useKemRewards' import { ZapInInfo } from 'pages/Earns/hooks/useZapInWidget' import useZapMigrationWidget from 'pages/Earns/hooks/useZapMigrationWidget' import { ZapOutInfo } from 'pages/Earns/hooks/useZapOutWidget' -import { FeeInfo, ParsedPosition, PositionStatus, SuggestedPool } from 'pages/Earns/types' +import { FeeInfo, OrderStatus, ParsedPosition, PositionStatus, SuggestedPool } from 'pages/Earns/types' import { getUnclaimedFeesInfo } from 'pages/Earns/utils/fees' import { checkEarlyPosition } from 'pages/Earns/utils/position' import { useWalletModalToggle } from 'state/application/hooks' diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx index ed8bd15a6d..35842c0813 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx @@ -14,10 +14,9 @@ import { PermitNftState, usePermitNft } from 'hooks/usePermitNft' import useTheme from 'hooks/useTheme' import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' import { Badge, ImageContainer } from 'pages/Earns/UserPositions/styles' -import { Metric } from 'pages/Earns/components/SmartExit/Metrics' import { useSmartExit } from 'pages/Earns/components/SmartExit/useSmartExit' import { SMART_EXIT_ADDRESS } from 'pages/Earns/constants' -import { ParsedPosition } from 'pages/Earns/types' +import { ConditionType, Metric, ParsedPosition } from 'pages/Earns/types' export const Confirmation = ({ selectedMetrics, @@ -33,7 +32,7 @@ export const Confirmation = ({ selectedMetrics: Metric[] pos: ParsedPosition onDismiss: () => void - conditionType: 'and' | 'or' + conditionType: ConditionType deadline: number feeYieldCondition: string priceCondition: { lte: string; gte: string } @@ -166,7 +165,7 @@ export const Confirmation = ({ flex: 1, }} /> - {conditionType === 'and' ? And : Or} + {conditionType === ConditionType.And ? And : Or} void customGasUsd: string @@ -40,7 +40,10 @@ export default function GasSetting({ ) : ( setFeeSettingExpanded(e => !e)} style={{ cursor: 'default' }}> - ${customGasUsd ? customGasUsd : (feeInfo.gas.usd * multiplier).toPrecision(2)} + $ + {customGasUsd + ? customGasUsd + : formatDisplayNumber(feeInfo.gas.usd * multiplier, { significantDigits: 2 })} @@ -134,7 +137,7 @@ export default function GasSetting({ - {t`Current est. gas`} = ${(feeInfo?.gas.usd || 0).toPrecision(2)} + {t`Current est. gas`} = ${formatDisplayNumber(feeInfo?.gas.usd || 0, { significantDigits: 2 })} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx index fb3cab861a..876ea452a1 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx @@ -13,16 +13,10 @@ import { MouseoverTooltip, TextDashed } from 'components/Tooltip' import DateTimePicker from 'components/swapv2/LimitOrder/ExpirePicker' import { TIMES_IN_SECS } from 'constants/index' import useTheme from 'hooks/useTheme' -import { ParsedPosition } from 'pages/Earns/types' +import { ConditionType, Metric, ParsedPosition } from 'pages/Earns/types' import { ButtonText } from 'theme' import { formatTimeDuration } from 'utils/time' -export enum Metric { - FeeYield = 'fee_yield', - PoolPrice = 'pool_price', - Time = 'time', -} - const defaultOptions = [ 5 * TIMES_IN_SECS.ONE_MIN, 10 * TIMES_IN_SECS.ONE_MIN, @@ -51,8 +45,8 @@ export const Metrics = ({ position: ParsedPosition selectedMetrics: [Metric, Metric | null] setSelectedMetrics: (value: [Metric, Metric | null]) => void - conditionType: 'and' | 'or' - setConditionType: (v: 'and' | 'or') => void + conditionType: ConditionType + setConditionType: (v: ConditionType) => void expireTime: number setExpireTime: (v: number) => void feeYieldCondition: string @@ -92,13 +86,13 @@ export const Metrics = ({ {metric2 ? ( <> - setConditionType(v as 'and' | 'or')}> + setConditionType(v as ConditionType)}> - - + + - - + + void }) => { const [selectedMetrics, setSelectedMetrics] = useState<[Metric, Metric | null]>([Metric.FeeYield, null]) - const [conditionType, setConditionType] = useState<'and' | 'or'>('and') + const [conditionType, setConditionType] = useState(ConditionType.And) const [expireTime, setExpireTime] = useState(TIMES_IN_SECS.ONE_DAY * 30) const deadline = useMemo(() => { @@ -51,7 +50,7 @@ export const SmartExit = ({ position, onDismiss }: { position: ParsedPosition; o const [showConfirm, setShowConfirm] = useState(false) // Gas estimation + selection state - const [feeInfo, setFeeInfo] = useState(null) + const [feeInfo, setFeeInfo] = useState(null) const [feeLoading, setFeeLoading] = useState(false) const [multiplier, setMultiplier] = useState(1.5) const [customGasUsd, setCustomGasUsd] = useState('') diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts index e175645011..91cd376538 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts @@ -2,7 +2,6 @@ import { t } from '@lingui/macro' import { useCallback, useState } from 'react' import { SmartExitFeeParams, - SmartExitFeeResponse, useEstimateSmartExitFeeMutation, useGetSmartExitSignMessageMutation, } from 'services/smartExit' @@ -11,9 +10,8 @@ import { NotificationType } from 'components/Announcement/type' import { SMART_EXIT_API_URL } from 'constants/env' import { useActiveWeb3React, useWeb3React } from 'hooks' import { DexType } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' -import { Metric } from 'pages/Earns/components/SmartExit/Metrics' import { Exchange } from 'pages/Earns/constants' -import { ParsedPosition } from 'pages/Earns/types' +import { ConditionType, Metric, ParsedPosition, SmartExitFee } from 'pages/Earns/types' import { useNotify } from 'state/application/hooks' import { friendlyError } from 'utils/errorMessage' import { getReadingContractWithCustomChain } from 'utils/getContract' @@ -21,7 +19,7 @@ import { getReadingContractWithCustomChain } from 'utils/getContract' export interface UseSmartExitParams { position: ParsedPosition selectedMetrics: Metric[] - conditionType: 'and' | 'or' + conditionType: ConditionType feeYieldCondition: string priceCondition: { gte: string; lte: string } timeCondition: { time: number | null; condition: 'after' | 'before' } @@ -82,7 +80,7 @@ export const useSmartExit = ({ const buildConditions = useCallback(() => { const conditions: Array<{ field: { - type: 'time' | 'pool_price' | 'fee_yield' + type: Metric value: any } }> = [] @@ -94,7 +92,7 @@ export const useSmartExit = ({ if (feeYieldCondition) { conditions.push({ field: { - type: 'fee_yield', + type: Metric.FeeYield, value: { gte: parseFloat(feeYieldCondition), }, @@ -107,7 +105,7 @@ export const useSmartExit = ({ if (priceCondition.gte && priceCondition.lte) { conditions.push({ field: { - type: 'pool_price', + type: Metric.PoolPrice, value: { gte: parseFloat(priceCondition.gte), lte: parseFloat(priceCondition.lte), @@ -123,7 +121,7 @@ export const useSmartExit = ({ if (timeCondition.condition === 'before') { conditions.push({ field: { - type: 'time', + type: Metric.Time, value: { lte: timeValue, }, @@ -132,7 +130,7 @@ export const useSmartExit = ({ } else { conditions.push({ field: { - type: 'time', + type: Metric.Time, value: { gte: timeValue, }, @@ -287,7 +285,7 @@ export const useSmartExit = ({ setOrderId(null) }, []) - const estimateFee = useCallback(async (): Promise => { + const estimateFee = useCallback(async (): Promise => { const positionContract = getReadingContractWithCustomChain( position.id.split('-')[0], POSITION_MANAGER_ABI, diff --git a/apps/kyberswap-interface/src/pages/Earns/types/index.ts b/apps/kyberswap-interface/src/pages/Earns/types/index.ts new file mode 100644 index 0000000000..bb5bb67844 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/types/index.ts @@ -0,0 +1,22 @@ +export * from 'pages/Earns/types/smartExit' +export * from 'pages/Earns/types/position' +export * from 'pages/Earns/types/pool' +export * from 'pages/Earns/types/reward' + +export interface FeeInfo { + balance0: string | number + balance1: string | number + amount0: string | number + amount1: string | number + value0: number + value1: number + totalValue: number +} + +export interface TokenInfo { + address: string + symbol: string + logo: string + decimals: number + chainId: number +} diff --git a/apps/kyberswap-interface/src/pages/Earns/types/pool.ts b/apps/kyberswap-interface/src/pages/Earns/types/pool.ts new file mode 100644 index 0000000000..5cbd092c93 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/types/pool.ts @@ -0,0 +1,62 @@ +import { Exchange } from 'pages/Earns/constants' + +export enum ProgramType { + EG = 'eg', + LM = 'lm', +} + +export enum PAIR_CATEGORY { + STABLE = 'stablePair', + CORRELATED = 'correlatedPair', + EXOTIC = 'exoticPair', + HIGH_VOLATILITY = 'highVolatilityPair', + DEFAULT_EMPTY = '', // For Krystal data +} + +export interface EarnPool { + address: string + earnFee: number + exchange: Exchange + type: string + feeTier: number + volume: number + apr: number + kemEGApr: number + kemLMApr: number + liquidity: number + tvl: number + chainId?: number + favorite?: { + chainId: number + isFavorite: boolean + } + category?: PAIR_CATEGORY + programs?: Array + tokens: Array<{ + address: string + logoURI: string + symbol: string + decimals: number + }> + maxAprInfo?: { + tickLower: number + tickUpper: number + minPrice: string + maxPrice: string + apr: string + kemEGApr: string + kemLMApr: string + } +} + +export interface ParsedEarnPool extends EarnPool { + dexLogo: string + dexName: string + feeApr: number +} + +export interface PoolAprInterval { + '7d': number + '24h': number + all: number +} diff --git a/apps/kyberswap-interface/src/pages/Earns/types.ts b/apps/kyberswap-interface/src/pages/Earns/types/position.ts similarity index 66% rename from apps/kyberswap-interface/src/pages/Earns/types.ts rename to apps/kyberswap-interface/src/pages/Earns/types/position.ts index 4551b330e2..b7e2d60679 100644 --- a/apps/kyberswap-interface/src/pages/Earns/types.ts +++ b/apps/kyberswap-interface/src/pages/Earns/types/position.ts @@ -1,27 +1,7 @@ import { NativeToken } from 'constants/networks/type' import { Exchange } from 'pages/Earns/constants' - -export enum PositionStatus { - IN_RANGE = 'IN_RANGE', - OUT_RANGE = 'OUT_RANGE', - CLOSED = 'CLOSED', -} - -export enum PositionHistoryType { - DEPOSIT = 'DEPOSIT', -} - -export enum ProgramType { - EG = 'eg', - LM = 'lm', -} - -export interface SmartExitFilter { - chainIds?: string - status?: string - dexTypes?: string - page: number -} +import { PAIR_CATEGORY, PoolAprInterval, ProgramType } from 'pages/Earns/types/pool' +import { TokenRewardInfo } from 'pages/Earns/types/reward' export interface PositionFilter { chainIds?: string @@ -34,104 +14,10 @@ export interface PositionFilter { page: number } -export interface EarnPool { - address: string - earnFee: number - exchange: Exchange - type: string - feeTier: number - volume: number - apr: number - kemEGApr: number - kemLMApr: number - liquidity: number - tvl: number - chainId?: number - favorite?: { - chainId: number - isFavorite: boolean - } - category?: PAIR_CATEGORY - programs?: Array - tokens: Array<{ - address: string - logoURI: string - symbol: string - decimals: number - }> - maxAprInfo?: { - tickLower: number - tickUpper: number - minPrice: string - maxPrice: string - apr: string - kemEGApr: string - kemLMApr: string - } -} - -export interface ParsedEarnPool extends EarnPool { - dexLogo: string - dexName: string - feeApr: number -} - -export interface EarnPosition { - [x: string]: any - chainName: 'eth' - chainId: number - chainLogo: string - id: string - tokenAddress: string - tokenId: string - minPrice: number - maxPrice: number - currentAmounts: Array - providedAmounts: Array - feePending: Array - feesClaimed: Array - createdTime: number - stats: { - apr: PoolAprInterval - kemLMApr: PoolAprInterval - kemEGApr: PoolAprInterval - earning: PoolAprInterval - } - /** @deprecated */ - apr: number - /** @deprecated */ - kemEGApr: number - /** @deprecated */ - kemLMApr: number - /** @deprecated */ - earning24h: number - /** @deprecated */ - earning7d: number - currentPositionValue: number - status: PositionStatus - pool: { - id: string - poolAddress: string - price: number - tokenAmounts: Array - fees: Array - tickSpacing: number - exchange: Exchange - projectLogo: string - category: PAIR_CATEGORY - programs?: Array - } - suggestionPool: SuggestedPool | null - latestBlock: number - createdAtBlock: number -} - -export enum PAIR_CATEGORY { - STABLE = 'stablePair', - CORRELATED = 'correlatedPair', - EXOTIC = 'exoticPair', - HIGH_VOLATILITY = 'highVolatilityPair', - DEFAULT_EMPTY = '', // For Krystal data +export enum PositionStatus { + IN_RANGE = 'IN_RANGE', + OUT_RANGE = 'OUT_RANGE', + CLOSED = 'CLOSED', } export const DEFAULT_PARSED_POSITION: ParsedPosition = { @@ -307,6 +193,56 @@ export interface ParsedPosition { txHash?: string } +export interface EarnPosition { + [x: string]: any + chainName: 'eth' + chainId: number + chainLogo: string + id: string + tokenAddress: string + tokenId: string + minPrice: number + maxPrice: number + currentAmounts: Array + providedAmounts: Array + feePending: Array + feesClaimed: Array + createdTime: number + stats: { + apr: PoolAprInterval + kemLMApr: PoolAprInterval + kemEGApr: PoolAprInterval + earning: PoolAprInterval + } + /** @deprecated */ + apr: number + /** @deprecated */ + kemEGApr: number + /** @deprecated */ + kemLMApr: number + /** @deprecated */ + earning24h: number + /** @deprecated */ + earning7d: number + currentPositionValue: number + status: PositionStatus + pool: { + id: string + poolAddress: string + price: number + tokenAmounts: Array + fees: Array + tickSpacing: number + exchange: Exchange + projectLogo: string + category: PAIR_CATEGORY + programs?: Array + } + suggestionPool: SuggestedPool | null + latestBlock: number + createdAtBlock: number +} + export interface SuggestedPool { address: string // chainId: number @@ -320,12 +256,6 @@ export interface SuggestedPool { } } -export interface PoolAprInterval { - '7d': number - '24h': number - all: number -} - interface Token { address: string symbol: string @@ -357,79 +287,6 @@ interface PositionAmount { } } -export interface FeeInfo { - balance0: string | number - balance1: string | number - amount0: string | number - amount1: string | number - value0: number - value1: number - totalValue: number -} - -export interface RewardInfo { - totalUsdValue: number - totalLmUsdValue: number - totalEgUsdValue: number - claimableUsdValue: number - claimedUsdValue: number - inProgressUsdValue: number - pendingUsdValue: number - vestingUsdValue: number - waitingUsdValue: number - nfts: Array - chains: Array - tokens: Array - egTokens: Array - lmTokens: Array -} - -export interface ChainRewardInfo { - chainId: number - chainName: string - chainLogo: string - claimableUsdValue: number - tokens: Array -} - -export interface NftRewardInfo { - nftId: string - chainId: number - totalUsdValue: number - totalLmUsdValue: number - totalEgUsdValue: number - claimedUsdValue: number - inProgressUsdValue: number - pendingUsdValue: number - vestingUsdValue: number - waitingUsdValue: number - claimableUsdValue: number - unclaimedUsdValue: number - - tokens: Array - egTokens: Array - lmTokens: Array -} - -export interface TokenRewardInfo { - symbol: string - logo: string - address: string - chainId: number - - totalAmount: number - claimableAmount: number - unclaimedAmount: number - pendingAmount: number - vestingAmount: number - waitingAmount: number - claimableUsdValue: number -} - -export interface TokenInfo { - address: string - symbol: string - logo: string - decimals: number - chainId: number +export enum PositionHistoryType { + DEPOSIT = 'DEPOSIT', } diff --git a/apps/kyberswap-interface/src/pages/Earns/types/reward.ts b/apps/kyberswap-interface/src/pages/Earns/types/reward.ts new file mode 100644 index 0000000000..ae35aded7c --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/types/reward.ts @@ -0,0 +1,58 @@ +export interface RewardInfo { + totalUsdValue: number + totalLmUsdValue: number + totalEgUsdValue: number + claimableUsdValue: number + claimedUsdValue: number + inProgressUsdValue: number + pendingUsdValue: number + vestingUsdValue: number + waitingUsdValue: number + nfts: Array + chains: Array + tokens: Array + egTokens: Array + lmTokens: Array +} + +export interface ChainRewardInfo { + chainId: number + chainName: string + chainLogo: string + claimableUsdValue: number + tokens: Array +} + +export interface NftRewardInfo { + nftId: string + chainId: number + totalUsdValue: number + totalLmUsdValue: number + totalEgUsdValue: number + claimedUsdValue: number + inProgressUsdValue: number + pendingUsdValue: number + vestingUsdValue: number + waitingUsdValue: number + claimableUsdValue: number + unclaimedUsdValue: number + + tokens: Array + egTokens: Array + lmTokens: Array +} + +export interface TokenRewardInfo { + symbol: string + logo: string + address: string + chainId: number + + totalAmount: number + claimableAmount: number + unclaimedAmount: number + pendingAmount: number + vestingAmount: number + waitingAmount: number + claimableUsdValue: number +} diff --git a/apps/kyberswap-interface/src/pages/Earns/types/smartExit.ts b/apps/kyberswap-interface/src/pages/Earns/types/smartExit.ts new file mode 100644 index 0000000000..65ae9ec4e0 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/types/smartExit.ts @@ -0,0 +1,57 @@ +export enum Metric { + FeeYield = 'fee_yield', + PoolPrice = 'pool_price', + Time = 'time', +} + +export enum ConditionType { + And = 'and', + Or = 'or', +} + +export enum OrderStatus { + OrderStatusOpen = 'OrderStatusOpen', + OrderStatusDone = 'OrderStatusDone', + OrderStatusCancelled = 'OrderStatusCancelled', + OrderStatusExpired = 'OrderStatusExpired', +} + +export interface SmartExitFilter { + chainIds?: string + status?: string + dexTypes?: string + page: number +} + +export interface SmartExitFee { + protocol: { percentage: number; category: string } + gas: { percentage: number; usd: number; wei: string } +} + +export interface SmartExitCondition { + logical: { + op: ConditionType + conditions: Array<{ + field: { + type: Metric + value: any + } + }> + } +} + +export interface SmartExitOrder { + id: string + chainId: number + userWallet: string + dexType: string + poolId: string + positionId: string + removeLiquidity: string + unwrap: boolean + condition: SmartExitCondition + status: OrderStatus + createdAt: number + updatedAt: number + deadline: number +} diff --git a/apps/kyberswap-interface/src/services/smartExit.ts b/apps/kyberswap-interface/src/services/smartExit.ts index 5d4e4400e3..c65b7595c6 100644 --- a/apps/kyberswap-interface/src/services/smartExit.ts +++ b/apps/kyberswap-interface/src/services/smartExit.ts @@ -3,35 +3,7 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { SMART_EXIT_API_URL } from 'constants/env' import { RTK_QUERY_TAGS } from 'constants/index' -import { OrderStatus } from 'pages/Earns/SmartExitOrders/useSmartExitFilter' - -interface SmartExitCondition { - logical: { - op: 'and' | 'or' - conditions: Array<{ - field: { - type: 'time' | 'pool_price' | 'fee_yield' - value: any - } - }> - } -} - -export interface SmartExitOrder { - id: string - chainId: number - userWallet: string - dexType: string - poolId: string - positionId: string - removeLiquidity: string - unwrap: boolean - condition: SmartExitCondition - status: OrderStatus - createdAt: number - updatedAt: number - deadline: number -} +import { SmartExitCondition, SmartExitFee, SmartExitOrder } from 'pages/Earns/types' interface SmartExitOrderResponse { orders: SmartExitOrder[] @@ -51,11 +23,6 @@ interface SmartExitOrderParams { positionIds?: string[] } -export interface SmartExitFeeResponse { - protocol: { percentage: number; category: string } - gas: { percentage: number; usd: number; wei: string } -} - export interface SmartExitFeeParams { chainId: number userWallet: string @@ -134,7 +101,7 @@ const smartExitApi = createApi({ invalidatesTags: [RTK_QUERY_TAGS.GET_SMART_EXIT_ORDERS], }), - estimateSmartExitFee: builder.mutation({ + estimateSmartExitFee: builder.mutation({ query: body => ({ url: '/v1/orders/smart-exit/estimate-fee', method: 'POST', From 71cb35b0f054b393878cd919fcc7352587115b32 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 6 Nov 2025 13:43:01 +0700 Subject: [PATCH 43/87] fix permit --- .../src/hooks/usePermitNft.ts | 24 ++++--------------- .../components/SmartExit/useSmartExit.ts | 1 - 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/apps/kyberswap-interface/src/hooks/usePermitNft.ts b/apps/kyberswap-interface/src/hooks/usePermitNft.ts index cab99d4860..596cf10d6b 100644 --- a/apps/kyberswap-interface/src/hooks/usePermitNft.ts +++ b/apps/kyberswap-interface/src/hooks/usePermitNft.ts @@ -94,31 +94,17 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline, vers return PermitNftState.READY_TO_SIGN }, [account, contractAddress, tokenId, spender, isSigningInProgress, permitData]) - const findFreeNonce = useCallback((bitmap: BigNumber, word = 0): BigNumber => { - // Find a free bit in the bitmap (unordered nonce) - for (let i = 0; i < 256; i++) { - if (bitmap.shr(i).and(1).isZero()) { - return BigNumber.from(word).shl(8).add(i) - } - } - throw new Error('No free nonce in word 0; pick a different word.') - }, []) - // Get nonce based on version const getNonce = useCallback((): BigNumber | null => { if (actualVersion === 'v3') { // Use ordered nonce from positions function if (positionsState?.result?.[0] !== undefined) { - return BigNumber.from(positionsState.result[0]).add(1) // Next nonce is current + 1 + return BigNumber.from(positionsState.result[0]) // Next nonce is current + 1 } - } else { - // Use unordered nonce from bitmap (V4) - if (noncesState?.result?.[0]) { - return findFreeNonce(noncesState.result[0], 0) - } - } + } else if (actualVersion === 'v4') return BigNumber.from(Math.floor(Date.now() / 1000)) + return null - }, [actualVersion, positionsState?.result, noncesState?.result, findFreeNonce]) + }, [actualVersion, positionsState?.result]) const signPermitNft = useCallback(async (): Promise => { if (!library || !account || !chainId || !nameState?.result?.[0]) { @@ -147,7 +133,7 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline, vers setIsSigningInProgress(true) try { - const nonce = actualVersion === 'v3' ? getNonce() : BigNumber.from(Date.now()) + const nonce = getNonce() if (!nonce) { throw new Error(`Failed to get nonce for ${actualVersion}`) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts index 91cd376538..29aa143de4 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/useSmartExit.ts @@ -301,7 +301,6 @@ export const useSmartExit = ({ const res = await positionContract.getPositionLiquidity(position.tokenId) liquidity = res.toString() } - console.log('liquidity', liquidity) if (!liquidity) return null From 52c7342b7138c9756449c289b11128d084553d4e Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 6 Nov 2025 13:56:26 +0700 Subject: [PATCH 44/87] test --- apps/kyberswap-interface/src/hooks/usePermitNft.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/kyberswap-interface/src/hooks/usePermitNft.ts b/apps/kyberswap-interface/src/hooks/usePermitNft.ts index 596cf10d6b..9ffb89e543 100644 --- a/apps/kyberswap-interface/src/hooks/usePermitNft.ts +++ b/apps/kyberswap-interface/src/hooks/usePermitNft.ts @@ -151,6 +151,7 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline, vers // V3 domain structure (simpler than V4) const domain = { name: contractName, + version: '1', chainId, verifyingContract: contractAddress, } @@ -175,6 +176,7 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline, vers types: { EIP712Domain: [ { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' }, ], From 2bab51b0b557ef5913ae2e445c6d7bef23f96207 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 6 Nov 2025 14:30:00 +0700 Subject: [PATCH 45/87] test --- apps/kyberswap-interface/src/hooks/usePermitNft.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/kyberswap-interface/src/hooks/usePermitNft.ts b/apps/kyberswap-interface/src/hooks/usePermitNft.ts index 9ffb89e543..fbc1c4f524 100644 --- a/apps/kyberswap-interface/src/hooks/usePermitNft.ts +++ b/apps/kyberswap-interface/src/hooks/usePermitNft.ts @@ -99,7 +99,7 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline, vers if (actualVersion === 'v3') { // Use ordered nonce from positions function if (positionsState?.result?.[0] !== undefined) { - return BigNumber.from(positionsState.result[0]) // Next nonce is current + 1 + return BigNumber.from(positionsState.result[0]).add(1) // Next nonce is current + 1 } } else if (actualVersion === 'v4') return BigNumber.from(Math.floor(Date.now() / 1000)) From 3cdb9bc17c4385ed9bfb37daaa0e8a69c9f37208 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 6 Nov 2025 14:47:10 +0700 Subject: [PATCH 46/87] test --- apps/kyberswap-interface/src/hooks/usePermitNft.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/kyberswap-interface/src/hooks/usePermitNft.ts b/apps/kyberswap-interface/src/hooks/usePermitNft.ts index fbc1c4f524..93ba7e5e90 100644 --- a/apps/kyberswap-interface/src/hooks/usePermitNft.ts +++ b/apps/kyberswap-interface/src/hooks/usePermitNft.ts @@ -99,7 +99,7 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline, vers if (actualVersion === 'v3') { // Use ordered nonce from positions function if (positionsState?.result?.[0] !== undefined) { - return BigNumber.from(positionsState.result[0]).add(1) // Next nonce is current + 1 + return BigNumber.from(positionsState.result[0]) } } else if (actualVersion === 'v4') return BigNumber.from(Math.floor(Date.now() / 1000)) From 42902fe90697f4f9fde0bc051f58e9f783395b59 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 6 Nov 2025 18:13:22 +0700 Subject: [PATCH 47/87] feat: refactor selectedMetric & 2 price conditions --- .../components/SmartExit/Confirmation.tsx | 90 ++--- .../components/SmartExit/ExpireSetting.tsx | 136 +++++++ .../Earns/components/SmartExit/Metrics.tsx | 360 +++++------------- .../Earns/components/SmartExit/index.tsx | 78 ++-- .../Earns/components/SmartExit/styles.tsx | 20 + .../components/SmartExit/useSmartExit.ts | 27 +- .../pages/Earns/components/SmartExit/utils.ts | 18 + .../src/pages/Earns/types/smartExit.ts | 17 + 8 files changed, 402 insertions(+), 344 deletions(-) create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/ExpireSetting.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/utils.ts diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx index 35842c0813..b0de40f0af 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx @@ -16,58 +16,62 @@ import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' import { Badge, ImageContainer } from 'pages/Earns/UserPositions/styles' import { useSmartExit } from 'pages/Earns/components/SmartExit/useSmartExit' import { SMART_EXIT_ADDRESS } from 'pages/Earns/constants' -import { ConditionType, Metric, ParsedPosition } from 'pages/Earns/types' +import { + ConditionType, + FeeYieldCondition, + Metric, + ParsedPosition, + PriceCondition, + SelectedMetric, + TimeCondition, +} from 'pages/Earns/types' export const Confirmation = ({ selectedMetrics, - pos, - onDismiss, - feeYieldCondition, - priceCondition, - timeCondition, + position, deadline, conditionType, feeSettings: { protocolFee, maxFeesPercentage }, + onDismiss, }: { - selectedMetrics: Metric[] - pos: ParsedPosition - onDismiss: () => void + selectedMetrics: SelectedMetric[] + position: ParsedPosition conditionType: ConditionType deadline: number - feeYieldCondition: string - priceCondition: { lte: string; gte: string } - timeCondition: { time: number | null; condition: 'after' | 'before' } feeSettings: { protocolFee: number; maxFeesPercentage: number } + onDismiss: () => void }) => { const theme = useTheme() - + const navigate = useNavigate() const { chainId } = useActiveWeb3React() const { changeNetwork } = useChangeNetwork() const { permitState, signPermitNft, permitData } = usePermitNft({ - contractAddress: pos.id.split('-')[0], - tokenId: pos.tokenId, + contractAddress: position.id.split('-')[0], + tokenId: position.tokenId, spender: SMART_EXIT_ADDRESS, deadline, }) const { createSmartExitOrder, isCreating, isSuccess } = useSmartExit({ - position: pos, + position, selectedMetrics, conditionType, - feeYieldCondition, - priceCondition, - timeCondition, deadline, permitData: permitData?.permitData, signature: permitData?.signature, }) - const displayTime = dayjs(deadline * 1000).format('DD/MM/YYYY HH:mm:ss') + const [metric1, metric2] = selectedMetrics - const [condition0, condition1] = selectedMetrics + const feeYieldCondition1 = metric1.condition as FeeYieldCondition + const priceCondition1 = metric1.condition as PriceCondition + const timeCondition1 = metric1.condition as TimeCondition + const feeYieldCondition2 = metric2?.condition as FeeYieldCondition + const priceCondition2 = metric2?.condition as PriceCondition + const timeCondition2 = metric2?.condition as TimeCondition - const navigate = useNavigate() + const displayTime = dayjs(deadline * 1000).format('DD/MM/YYYY HH:mm:ss') if (isSuccess) return ( @@ -118,14 +122,14 @@ export const Confirmation = ({ Exit - - - + + + - {pos.token0.symbol}/{pos.token1.symbol} + {position.token0.symbol}/{position.token1.symbol} - Fee {pos?.pool.fee}% + Fee {position?.pool.fee}% When @@ -138,24 +142,24 @@ export const Confirmation = ({ marginTop: '1rem', }} > - {condition0 === Metric.FeeYield && The fee yield ≥ {feeYieldCondition}%} - {condition0 === Metric.Time && ( + {metric1.metric === Metric.FeeYield && The fee yield ≥ {feeYieldCondition1}%} + {metric1.metric === Metric.Time && ( <> - {timeCondition.condition.charAt(0).toUpperCase() + timeCondition.condition.slice(1)} - {dayjs(timeCondition.time).format('DD/MM/YYYY HH:mm:ss')} + {timeCondition1.condition.charAt(0).toUpperCase() + timeCondition1.condition.slice(1)} + {dayjs(timeCondition1.time).format('DD/MM/YYYY HH:mm:ss')} )} - {condition0 === Metric.PoolPrice && ( + {metric1.metric === Metric.PoolPrice && ( <> Pool price is between - {priceCondition.gte} and {priceCondition.lte} {pos.token0.symbol}/{pos.token1.symbol} + {priceCondition1.gte} and {priceCondition1.lte} {position.token0.symbol}/{position.token1.symbol} )} - {condition1 && ( + {metric2 && ( <> - {condition1 === Metric.FeeYield && The fee yield ≥ {feeYieldCondition}%} - {condition1 === Metric.Time && ( + {metric2.metric === Metric.FeeYield && The fee yield ≥ {feeYieldCondition2}%} + {metric2.metric === Metric.Time && ( <> - {timeCondition.condition.charAt(0).toUpperCase() + timeCondition.condition.slice(1)} - {dayjs(timeCondition.time).format('DD/MM/YYYY HH:mm:ss')} + {timeCondition2.condition.charAt(0).toUpperCase() + timeCondition2.condition.slice(1)} + {dayjs(timeCondition2.time).format('DD/MM/YYYY HH:mm:ss')} )} - {condition1 === Metric.PoolPrice && ( + {metric2.metric === Metric.PoolPrice && ( <> Pool price is between - {priceCondition.gte} and {priceCondition.lte} {pos.token0.symbol}/{pos.token1.symbol} + {priceCondition2.gte} and {priceCondition2.lte} {position.token0.symbol}/{position.token1.symbol} )} @@ -238,8 +242,8 @@ export const Confirmation = ({ disabled={permitState === PermitNftState.SIGNING || isCreating || !maxFeesPercentage} onClick={async () => { if (!maxFeesPercentage) return - if (chainId !== pos.chain.id) { - changeNetwork(pos.chain.id) + if (chainId !== position.chain.id) { + changeNetwork(position.chain.id) return } @@ -253,7 +257,7 @@ export const Confirmation = ({ } }} > - {chainId !== pos.chain.id ? ( + {chainId !== position.chain.id ? ( Switch Network ) : isCreating ? ( Creating Order... diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/ExpireSetting.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/ExpireSetting.tsx new file mode 100644 index 0000000000..2d3e3547a3 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/ExpireSetting.tsx @@ -0,0 +1,136 @@ +import { Trans, t } from '@lingui/macro' +import dayjs from 'dayjs' +import { useMemo, useState } from 'react' +import { Flex, Text } from 'rebass' + +import { DefaultSlippageOption } from 'components/SlippageControl' +import { MouseoverTooltip, TextDashed } from 'components/Tooltip' +import DateTimePicker from 'components/swapv2/LimitOrder/ExpirePicker' +import { TIMES_IN_SECS } from 'constants/index' +import useTheme from 'hooks/useTheme' +import { formatTimeDuration } from 'utils/time' + +export const DEFAULT_TIME_OPTIONS = [ + 5 * TIMES_IN_SECS.ONE_MIN, + 10 * TIMES_IN_SECS.ONE_MIN, + TIMES_IN_SECS.ONE_HOUR, + TIMES_IN_SECS.ONE_DAY, + 3 * TIMES_IN_SECS.ONE_DAY, + 7 * TIMES_IN_SECS.ONE_DAY, + 30 * TIMES_IN_SECS.ONE_DAY, +].map(e => ({ value: e, label: formatTimeDuration(e) })) + +export default function ExpireSetting({ + expireTime, + setExpireTime, +}: { + expireTime: number + setExpireTime: (v: number) => void +}) { + const theme = useTheme() + const [openDatePicker, setOpenDatePicker] = useState(false) + + const displayTime = useMemo( + () => + expireTime % TIMES_IN_SECS.ONE_DAY === 0 + ? `${expireTime / TIMES_IN_SECS.ONE_DAY}D` + : dayjs(expireTime).format('DD/MM/YYYY HH:mm:ss'), + [expireTime], + ) + + return ( + <> + setOpenDatePicker(false)} + onSetDate={(val: Date | number) => setExpireTime(typeof val === 'number' ? val : val.getTime())} + expire={expireTime} + /> + + + + + Expires in + + + + + + {displayTime} + + + + + + + + {[ + { label: '7D', value: TIMES_IN_SECS.ONE_DAY * 7 }, + { label: '30D', value: TIMES_IN_SECS.ONE_DAY * 30 }, + { label: '90D', value: TIMES_IN_SECS.ONE_DAY * 90 }, + { + label: 'Custom', + onSelect: () => { + setOpenDatePicker(true) + }, + }, + ].map((item: any) => { + return ( + { + if (item.label === 'Custom') item.onSelect() + else setExpireTime(item.value) + }} + data-active={ + item.label === 'Custom' ? expireTime % TIMES_IN_SECS.ONE_DAY != 0 : item.value === expireTime + } + > + {item.label} + + ) + })} + + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx index 876ea452a1..00a5476b80 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx @@ -1,31 +1,28 @@ import { Label, RadioGroup, RadioGroupItem } from '@kyber/ui' import { Trans, t } from '@lingui/macro' import dayjs from 'dayjs' -import { useState } from 'react' +import { useMemo, useState } from 'react' import { Calendar } from 'react-feather' import { Box, Flex, Text } from 'rebass' import Divider from 'components/Divider' -import Input from 'components/Input' -import Select from 'components/Select' -import { DefaultSlippageOption } from 'components/SlippageControl' -import { MouseoverTooltip, TextDashed } from 'components/Tooltip' import DateTimePicker from 'components/swapv2/LimitOrder/ExpirePicker' -import { TIMES_IN_SECS } from 'constants/index' import useTheme from 'hooks/useTheme' -import { ConditionType, Metric, ParsedPosition } from 'pages/Earns/types' +import { DEFAULT_TIME_OPTIONS } from 'pages/Earns/components/SmartExit/ExpireSetting' +import { CustomInput, CustomSelect } from 'pages/Earns/components/SmartExit/styles' +import { getDefaultCondition } from 'pages/Earns/components/SmartExit/utils' +import { + ConditionType, + FeeYieldCondition, + Metric, + ParsedPosition, + PriceCondition, + SelectedMetric, + TimeCondition, +} from 'pages/Earns/types' import { ButtonText } from 'theme' -import { formatTimeDuration } from 'utils/time' -const defaultOptions = [ - 5 * TIMES_IN_SECS.ONE_MIN, - 10 * TIMES_IN_SECS.ONE_MIN, - TIMES_IN_SECS.ONE_HOUR, - TIMES_IN_SECS.ONE_DAY, - 3 * TIMES_IN_SECS.ONE_DAY, - 7 * TIMES_IN_SECS.ONE_DAY, - 30 * TIMES_IN_SECS.ONE_DAY, -].map(e => ({ value: e, label: formatTimeDuration(e) })) +const supportedMetrics = [Metric.FeeYield, Metric.PoolPrice, Metric.Time] export const Metrics = ({ position, @@ -33,55 +30,29 @@ export const Metrics = ({ setSelectedMetrics, conditionType, setConditionType, - expireTime, - setExpireTime, - feeYieldCondition, - setFeeYieldCondition, - priceCondition, - setPriceCondition, - timeCondition, - setTimeCondition, }: { position: ParsedPosition - selectedMetrics: [Metric, Metric | null] - setSelectedMetrics: (value: [Metric, Metric | null]) => void + selectedMetrics: SelectedMetric[] + setSelectedMetrics: (value: SelectedMetric[]) => void conditionType: ConditionType setConditionType: (v: ConditionType) => void - expireTime: number - setExpireTime: (v: number) => void - feeYieldCondition: string - setFeeYieldCondition: (v: string) => void - priceCondition: { lte: string; gte: string } - setPriceCondition: (v: { lte: string; gte: string }) => void - timeCondition: { time: number | null; condition: 'after' | 'before' } - setTimeCondition: (v: { time: number | null; condition: 'after' | 'before' }) => void }) => { const theme = useTheme() const [metric1, metric2] = selectedMetrics - const displayTime = - expireTime % TIMES_IN_SECS.ONE_DAY === 0 - ? `${expireTime / TIMES_IN_SECS.ONE_DAY}D` - : dayjs(expireTime).format('DD/MM/YYYY HH:mm:ss') - - const [openDatePicker, setOpenDatePicker] = useState(false) + const onChangeMetric1 = (value: SelectedMetric) => setSelectedMetrics(metric2 ? [value, metric2] : [value]) + const onChangeMetric2 = (value: SelectedMetric) => setSelectedMetrics([metric1, value]) + const onRemoveMetric2 = () => setSelectedMetrics([metric1]) + const onAddMetric2 = () => { + const newMetric = supportedMetrics.filter(item => item === Metric.PoolPrice || item !== metric1.metric)[0] + const newCondition = getDefaultCondition(newMetric) + if (!newCondition) return + setSelectedMetrics([metric1, { metric: newMetric, condition: newCondition }]) + } return ( - { - setSelectedMetrics([value, metric2]) - }} - selectedMetric={metric2} - position={position} - feeYieldCondition={feeYieldCondition} - setFeeYieldCondition={setFeeYieldCondition} - priceCondition={priceCondition} - setPriceCondition={setPriceCondition} - timeCondition={timeCondition} - setTimeCondition={setTimeCondition} - /> + {metric2 ? ( <> @@ -89,10 +60,14 @@ export const Metrics = ({ setConditionType(v as ConditionType)}> - + - + { - setSelectedMetrics([metric1, null]) - }} + onClick={onRemoveMetric2} > Remove Condition - { - setSelectedMetrics([metric1, v]) - }} - selectedMetric={metric1} - position={position} - feeYieldCondition={feeYieldCondition} - setFeeYieldCondition={setFeeYieldCondition} - priceCondition={priceCondition} - setPriceCondition={setPriceCondition} - timeCondition={timeCondition} - setTimeCondition={setTimeCondition} - /> + ) : ( { - setSelectedMetrics([ - metric1, - [Metric.FeeYield, Metric.PoolPrice, Metric.Time].filter(item => item !== metric1)[0], - ]) - }} + onClick={onAddMetric2} > - + Add Condition 2 + + Add Condition 2 )} - - - { - setOpenDatePicker(false) - }} - onSetDate={(val: Date | number) => { - setExpireTime(typeof val === 'number' ? val : val.getTime()) - }} - expire={expireTime} - /> - - - - - Expires in - - - - - - {displayTime} - - - - - - - {[ - { label: '7D', value: TIMES_IN_SECS.ONE_DAY * 7 }, - { label: '30D', value: TIMES_IN_SECS.ONE_DAY * 30 }, - { label: '90D', value: TIMES_IN_SECS.ONE_DAY * 90 }, - { - label: 'Custom', - onSelect: () => { - setOpenDatePicker(true) - }, - }, - ].map((item: any) => { - return ( - { - if (item.label === 'Custom') item.onSelect() - else setExpireTime(item.value) - }} - data-active={ - item.label === 'Custom' ? expireTime % TIMES_IN_SECS.ONE_DAY != 0 : item.value === expireTime - } - > - {item.label} - - ) - })} - - ) } @@ -245,36 +104,51 @@ const MetricSelect = ({ setMetric, selectedMetric, position, - feeYieldCondition, - setFeeYieldCondition, - priceCondition, - setPriceCondition, - timeCondition, - setTimeCondition, }: { - metric: Metric - setMetric: (value: Metric) => void - selectedMetric: Metric | null + metric: SelectedMetric + setMetric: (value: SelectedMetric) => void + selectedMetric?: SelectedMetric position: ParsedPosition - feeYieldCondition: string - setFeeYieldCondition: (v: string) => void - priceCondition: { lte: string; gte: string } - setPriceCondition: (v: { lte: string; gte: string }) => void - timeCondition: { time: number | null; condition: 'after' | 'before' } - setTimeCondition: (v: { time: number | null; condition: 'after' | 'before' }) => void }) => { const theme = useTheme() + const [openDatePicker, setOpenDatePicker] = useState(false) - const inputStyle = { - border: 'none', - padding: '8px 16px', - borderRadius: '12px', - fontSize: '16px', - color: theme.text, - flex: 1, - } + const metricOptions = useMemo( + () => + [ + { + label: t`Fee Yield`, + value: Metric.FeeYield, + }, + { + label: t`Pool Price`, + value: Metric.PoolPrice, + }, + { + label: t`Time`, + value: Metric.Time, + }, + ].filter(item => item.value === Metric.PoolPrice || item.value !== selectedMetric?.metric), + [selectedMetric], + ) - const [openDatePicker, setOpenDatePicker] = useState(false) + const timeOptions = useMemo( + () => [ + { + label: t`Before`, + value: 'before', + }, + { + label: t`After`, + value: 'after', + }, + ], + [], + ) + + const feeYieldCondition = metric.condition as FeeYieldCondition + const priceCondition = metric.condition as PriceCondition + const timeCondition = metric.condition as TimeCondition return ( <> @@ -282,38 +156,28 @@ const MetricSelect = ({ Select Metric - { const value = e.target.value @@ -322,7 +186,7 @@ const MetricSelect = ({ const numValue = parseFloat(value) // Limit to 1-100% if (value === '' || numValue > 0) { - setFeeYieldCondition(value) + setMetric({ ...metric, condition: value }) } } }} @@ -341,11 +205,12 @@ const MetricSelect = ({ {[5, 10, 15, 20].map(item => { - const isSelected = feeYieldCondition === item.toString() + const isSelected = metric.condition === item.toString() + return ( setFeeYieldCondition(item.toString())} + onClick={() => setMetric({ ...metric, condition: item.toString() })} sx={{ borderRadius: '999px', border: `1px solid ${isSelected ? theme.primary : theme.border}`, @@ -368,34 +233,32 @@ const MetricSelect = ({ )} - {metric === Metric.PoolPrice && ( + {metric.metric === Metric.PoolPrice && ( <> Exit when the pool price is between - { const value = e.target.value // Only allow numbers and decimal point if (/^\d*\.?\d*$/.test(value)) { - setPriceCondition({ ...priceCondition, gte: value }) + setMetric({ ...metric, condition: { ...priceCondition, gte: value } }) } }} /> - - { const value = e.target.value // Only allow numbers and decimal point if (/^\d*\.?\d*$/.test(value)) { - setPriceCondition({ ...priceCondition, lte: value }) + setMetric({ ...metric, condition: { ...priceCondition, lte: value } }) } }} /> @@ -406,29 +269,16 @@ const MetricSelect = ({ )} - {metric === Metric.Time && ( + {metric.metric === Metric.Time && ( <> Exit this position - setCustomGasUsd(v)} + value={customGasPercent} + onUserInput={v => setCustomGasPercent(v)} placeholder={t`Custom`} style={{ width: '100%', @@ -133,11 +139,14 @@ export default function GasSetting({ fontSize: '12px', }} /> + + % + - {t`Current est. gas`} = ${formatDisplayNumber(feeInfo?.gas.usd || 0, { significantDigits: 2 })} + {t`Current est. gas`} = {formatDisplayNumber(feeInfo?.gas.percentage || 0, { significantDigits: 2 })}% diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx index 4926c7e87e..82485356b2 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx @@ -50,7 +50,7 @@ export const SmartExit = ({ position, onDismiss }: { position: ParsedPosition; o const invalidYieldCondition = useMemo(() => { const feeYieldMetric = selectedMetrics.find(metric => metric.metric === Metric.FeeYield) const feeYieldCondition = feeYieldMetric?.condition as FeeYieldCondition - return feeYieldMetric && !feeYieldCondition + return feeYieldMetric && (!feeYieldCondition || parseFloat(feeYieldCondition) === 0) }, [selectedMetrics]) const invalidPriceCondition = useMemo(() => { @@ -73,8 +73,8 @@ export const SmartExit = ({ position, onDismiss }: { position: ParsedPosition; o // Gas estimation + selection state const [feeInfo, setFeeInfo] = useState(null) const [feeLoading, setFeeLoading] = useState(false) - const [multiplier, setMultiplier] = useState(1.5) - const [customGasUsd, setCustomGasUsd] = useState('') + const [multiplier, setMultiplier] = useState(2) + const [customGasPercent, setCustomGasPercent] = useState('') const intervalRef = useRef(null) const { estimateFee } = useSmartExit({ @@ -128,8 +128,7 @@ export const SmartExit = ({ position, onDismiss }: { position: ParsedPosition; o feeSettings={{ protocolFee: feeInfo?.protocol.percentage || 0, maxFeesPercentage: - (feeInfo?.gas.percentage || 0) * - (customGasUsd ? parseFloat(customGasUsd) / (feeInfo?.gas.usd ?? 0) : multiplier) + + (customGasPercent ? parseFloat(customGasPercent) : (feeInfo?.gas.percentage || 0) * multiplier) + (feeInfo?.protocol.percentage || 0), }} /> @@ -172,8 +171,8 @@ export const SmartExit = ({ position, onDismiss }: { position: ParsedPosition; o feeInfo={feeInfo} multiplier={multiplier} setMultiplier={setMultiplier} - customGasUsd={customGasUsd} - setCustomGasUsd={setCustomGasUsd} + customGasPercent={customGasPercent} + setCustomGasPercent={setCustomGasPercent} /> From 0cff94ba9a4e2d0f4a5f399ae0aac3f2bf019df8 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 1 Dec 2025 08:36:16 +0700 Subject: [PATCH 49/87] feat: price slider --- .../UniswapPriceSlider/PriceAxis.tsx | 156 +++++++ .../components/UniswapPriceSlider/hooks.ts | 295 ++++++++++++++ .../components/UniswapPriceSlider/index.tsx | 379 ++++++++++++++++++ .../components/UniswapPriceSlider/styles.ts | 276 +++++++++++++ .../components/UniswapPriceSlider/types.ts | 44 ++ .../components/UniswapPriceSlider/utils.ts | 158 ++++++++ 6 files changed, 1308 insertions(+) create mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx create mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts create mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx create mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts create mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/types.ts create mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx new file mode 100644 index 0000000000..cb20a516ca --- /dev/null +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx @@ -0,0 +1,156 @@ +import { tickToPrice } from '@kyber/utils/dist/uniswapv3' +import React, { useMemo } from 'react' + +import type { PriceAxisProps } from 'components/UniswapPriceSlider/types' +import { formatAxisPrice } from 'components/UniswapPriceSlider/utils' + +import { PriceAxisContainer, PriceAxisLabel, PriceAxisLine, PriceAxisTick } from './styles' + +const MAX_TICK_COUNT = 11 // More ticks for small ranges +const MIN_TICK_COUNT = 2 // Just first and last for extreme ranges + +/** + * Calculate the optimal number of ticks and minimum gap based on price range + * More ticks for small ranges, fewer for large ranges + */ +const getOptimalTickConfig = (minPrice: number, maxPrice: number): { tickCount: number; minGapPercent: number } => { + if (minPrice <= 0 || maxPrice <= 0) { + return { tickCount: 7, minGapPercent: 15 } + } + + // Calculate how many orders of magnitude the prices span + const priceRatio = maxPrice / minPrice + const ordersOfMagnitude = Math.log10(priceRatio) + + // Very small range (< 0.5 orders): many ticks, small gap + if (ordersOfMagnitude <= 0.5) { + return { tickCount: MAX_TICK_COUNT, minGapPercent: 12 } + } + // Small range (0.5 - 1 order): good amount of ticks + if (ordersOfMagnitude <= 1) { + return { tickCount: 9, minGapPercent: 14 } + } + // Medium range (1 - 2 orders) + if (ordersOfMagnitude <= 2) { + return { tickCount: 7, minGapPercent: 16 } + } + // Large range (2 - 4 orders) + if (ordersOfMagnitude <= 4) { + return { tickCount: 5, minGapPercent: 20 } + } + // Very large range (4 - 8 orders) + if (ordersOfMagnitude <= 8) { + return { tickCount: 3, minGapPercent: 30 } + } + // Extreme range (> 8 orders): just first and last + return { tickCount: MIN_TICK_COUNT, minGapPercent: 40 } +} + +/** + * Calculate tick positions for the axis + * Uses tick-space for even distribution (matching the slider) + */ +const calculateAxisTicks = ( + viewRange: { min: number; max: number }, + token0Decimals: number, + token1Decimals: number, + count: number, + invertPrice?: boolean, +): Array<{ tick: number; price: number; position: number }> => { + const tickRange = viewRange.max - viewRange.min + if (tickRange <= 0) return [] + + const step = tickRange / (count - 1) + const ticks: Array<{ tick: number; price: number; position: number }> = [] + + for (let i = 0; i < count; i++) { + const tick = Math.round(viewRange.min + step * i) + const price = +tickToPrice(tick, token0Decimals, token1Decimals, invertPrice) + const position = ((tick - viewRange.min) / tickRange) * 100 + + ticks.push({ tick, price, position }) + } + + return ticks +} + +/** + * Filter ticks to ensure minimum spacing between labels + * Only shows labels that have sufficient gap from previous label + */ +const filterOverlappingTicks = ( + ticks: Array<{ tick: number; price: number; position: number }>, + minGapPercent: number, +): Array<{ tick: number; price: number; position: number; showLabel: boolean }> => { + if (ticks.length === 0) return [] + + const result: Array<{ tick: number; price: number; position: number; showLabel: boolean }> = [] + let lastLabelPosition = -Infinity + + for (let i = 0; i < ticks.length; i++) { + const tick = ticks[i] + const isFirst = i === 0 + const isLast = i === ticks.length - 1 + const gap = tick.position - lastLabelPosition + + // First tick always shows label + if (isFirst) { + lastLabelPosition = tick.position + result.push({ ...tick, showLabel: true }) + continue + } + + // Last tick: only show if enough gap, otherwise hide + if (isLast) { + const showLabel = gap >= minGapPercent + if (showLabel) lastLabelPosition = tick.position + result.push({ ...tick, showLabel }) + continue + } + + // Middle ticks: show if enough gap from previous label + const showLabel = gap >= minGapPercent + if (showLabel) lastLabelPosition = tick.position + result.push({ ...tick, showLabel }) + } + + return result +} + +/** + * Price axis component that displays price scale below the slider + * Uses tick-based positioning to match the slider exactly + * Dynamically reduces tick count when price range is very large + */ +function PriceAxis({ viewRange, token0Decimals, token1Decimals, invertPrice }: PriceAxisProps) { + const axisTicks = useMemo(() => { + // Get min and max prices to determine optimal tick config + const minPrice = +tickToPrice(Math.round(viewRange.min), token0Decimals, token1Decimals, invertPrice) + const maxPrice = +tickToPrice(Math.round(viewRange.max), token0Decimals, token1Decimals, invertPrice) + const { tickCount, minGapPercent } = getOptimalTickConfig( + Math.min(minPrice, maxPrice), + Math.max(minPrice, maxPrice), + ) + + const ticks = calculateAxisTicks(viewRange, token0Decimals, token1Decimals, tickCount, invertPrice) + return filterOverlappingTicks(ticks, minGapPercent) + }, [viewRange, token0Decimals, token1Decimals, invertPrice]) + + return ( + + + {axisTicks.map(({ price, position, showLabel }, index) => { + // Only render if within visible range + if (position < -2 || position > 102) return null + return ( + + + {showLabel && {formatAxisPrice(price)}} + + ) + })} + + ) +} + +export default React.memo(PriceAxis) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts new file mode 100644 index 0000000000..0541f7defa --- /dev/null +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts @@ -0,0 +1,295 @@ +import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react' + +import type { ViewRange } from 'components/UniswapPriceSlider/types' + +const DEBOUNCE_DELAY = 150 // ms +const ZOOM_LERP_FACTOR = 0.08 // Smooth interpolation factor for zoom +const HANDLE_LERP_MIN = 0.15 // Min lerp factor when far from target +const HANDLE_LERP_MAX = 0.4 // Max lerp factor when close to target +const MAX_TICK_SPEED = 2000 // Maximum ticks per frame - increased for smoother tracking + +/** + * Hook for smooth zoom animation using requestAnimationFrame + */ +export const useSmoothZoom = ( + viewRange: ViewRange | null, + setViewRange: Dispatch>, +) => { + const zoomAnimationRef = useRef(null) + const targetViewRangeRef = useRef(null) + + const animateZoom = useCallback(() => { + if (!targetViewRangeRef.current || !viewRange) { + zoomAnimationRef.current = null + return + } + + const target = targetViewRangeRef.current + + setViewRange(prev => { + if (!prev) return prev + + const newMin = prev.min + (target.min - prev.min) * ZOOM_LERP_FACTOR + const newMax = prev.max + (target.max - prev.max) * ZOOM_LERP_FACTOR + + // Check if we're close enough to target + const minDiff = Math.abs(target.min - newMin) + const maxDiff = Math.abs(target.max - newMax) + const threshold = Math.abs(prev.max - prev.min) * 0.001 + + if (minDiff < threshold && maxDiff < threshold) { + targetViewRangeRef.current = null + zoomAnimationRef.current = null + return target + } + + // Continue animation + zoomAnimationRef.current = requestAnimationFrame(animateZoom) + return { min: newMin, max: newMax } + }) + }, [viewRange, setViewRange]) + + const startSmoothZoom = useCallback( + (targetMin: number, targetMax: number) => { + targetViewRangeRef.current = { min: targetMin, max: targetMax } + + if (!zoomAnimationRef.current) { + zoomAnimationRef.current = requestAnimationFrame(animateZoom) + } + }, + [animateZoom], + ) + + // Cleanup animation on unmount + useEffect(() => { + return () => { + if (zoomAnimationRef.current) { + cancelAnimationFrame(zoomAnimationRef.current) + } + } + }, []) + + return { startSmoothZoom } +} + +/** + * Hook for smooth tick updates with animation and debouncing + * Handles move slowly/smoothly towards target position + */ +export const useDebouncedTicks = ( + lowerTick: number | undefined, + upperTick: number | undefined, + setLowerTick: (tick: number) => void, + setUpperTick: (tick: number) => void, + isDragging: boolean, +) => { + const debounceTimerRef = useRef(null) + const animationRef = useRef(null) + + // Target ticks (where user wants to go) + const targetLowerTickRef = useRef(lowerTick) + const targetUpperTickRef = useRef(upperTick) + + // Current internal tick values (tracked via refs for animation loop) + const internalLowerRef = useRef(lowerTick) + const internalUpperRef = useRef(upperTick) + + // Internal tick state for React rendering + const [internalLowerTick, setInternalLowerTick] = useState(lowerTick) + const [internalUpperTick, setInternalUpperTick] = useState(upperTick) + + // Helper: calculate dynamic lerp factor based on distance + const getDynamicLerp = useCallback((diff: number): number => { + const absDiff = Math.abs(diff) + if (absDiff > 5000) return HANDLE_LERP_MIN + if (absDiff < 100) return HANDLE_LERP_MAX + const t = (absDiff - 100) / (5000 - 100) + return HANDLE_LERP_MAX - t * (HANDLE_LERP_MAX - HANDLE_LERP_MIN) + }, []) + + // Animation function for smooth handle movement + const animateHandles = useCallback(() => { + let needsAnimation = false + + // Update lower tick + if (internalLowerRef.current !== undefined && targetLowerTickRef.current !== undefined) { + const current = internalLowerRef.current + const target = targetLowerTickRef.current + const diff = target - current + + if (Math.abs(diff) >= 1) { + needsAnimation = true + const lerpFactor = getDynamicLerp(diff) + const lerpMovement = diff * lerpFactor + const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED) + const newValue = current + cappedMovement + internalLowerRef.current = newValue + setInternalLowerTick(newValue) + } else if (current !== target) { + internalLowerRef.current = target + setInternalLowerTick(target) + } + } + + // Update upper tick + if (internalUpperRef.current !== undefined && targetUpperTickRef.current !== undefined) { + const current = internalUpperRef.current + const target = targetUpperTickRef.current + const diff = target - current + + if (Math.abs(diff) >= 1) { + needsAnimation = true + const lerpFactor = getDynamicLerp(diff) + const lerpMovement = diff * lerpFactor + const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED) + const newValue = current + cappedMovement + internalUpperRef.current = newValue + setInternalUpperTick(newValue) + } else if (current !== target) { + internalUpperRef.current = target + setInternalUpperTick(target) + } + } + + if (needsAnimation) { + animationRef.current = requestAnimationFrame(animateHandles) + } else { + animationRef.current = null + } + }, [getDynamicLerp]) + + // Start animation if not already running + const startAnimation = useCallback(() => { + if (!animationRef.current) { + animationRef.current = requestAnimationFrame(animateHandles) + } + }, [animateHandles]) + + // Sync internal state with props when not dragging + useEffect(() => { + if (!isDragging) { + targetLowerTickRef.current = lowerTick + targetUpperTickRef.current = upperTick + internalLowerRef.current = lowerTick + internalUpperRef.current = upperTick + setInternalLowerTick(lowerTick) + setInternalUpperTick(upperTick) + } + }, [lowerTick, upperTick, isDragging]) + + // Smooth update functions - set target and start animation + const debouncedSetLowerTick = useCallback( + (tick: number) => { + targetLowerTickRef.current = tick + startAnimation() + + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current) + } + debounceTimerRef.current = setTimeout(() => { + setLowerTick(tick) + }, DEBOUNCE_DELAY) + }, + [setLowerTick, startAnimation], + ) + + const debouncedSetUpperTick = useCallback( + (tick: number) => { + targetUpperTickRef.current = tick + startAnimation() + + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current) + } + debounceTimerRef.current = setTimeout(() => { + setUpperTick(tick) + }, DEBOUNCE_DELAY) + }, + [setUpperTick, startAnimation], + ) + + // Flush debounced values immediately + const flushDebouncedValues = useCallback(() => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current) + debounceTimerRef.current = null + } + + // Set final values from targets + const finalLower = targetLowerTickRef.current + const finalUpper = targetUpperTickRef.current + + if (finalLower !== undefined && finalLower !== lowerTick) { + setLowerTick(finalLower) + internalLowerRef.current = finalLower + setInternalLowerTick(finalLower) + } + if (finalUpper !== undefined && finalUpper !== upperTick) { + setUpperTick(finalUpper) + internalUpperRef.current = finalUpper + setInternalUpperTick(finalUpper) + } + + // Stop animation + if (animationRef.current) { + cancelAnimationFrame(animationRef.current) + animationRef.current = null + } + }, [lowerTick, upperTick, setLowerTick, setUpperTick]) + + // Cleanup on unmount + useEffect(() => { + return () => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current) + } + if (animationRef.current) { + cancelAnimationFrame(animationRef.current) + } + } + }, []) + + // Get target ticks (what user actually wants, not the animated value) + const getTargetTicks = useCallback(() => { + return { + lowerTick: targetLowerTickRef.current, + upperTick: targetUpperTickRef.current, + } + }, []) + + return { + internalLowerTick, + internalUpperTick, + debouncedSetLowerTick, + debouncedSetUpperTick, + flushDebouncedValues, + getTargetTicks, + } +} + +/** + * Hook for converting between tick and position + */ +export const useTickPositionConverter = (viewRange: ViewRange | null, tickSpacing: number) => { + const getPositionFromTick = useCallback( + (tick: number): number => { + if (!viewRange) return 50 + const { min, max } = viewRange + return ((tick - min) / (max - min)) * 100 + }, + [viewRange], + ) + + const getTickFromPosition = useCallback( + (position: number): number => { + if (!viewRange) return 0 + const { min, max } = viewRange + const { nearestUsableTick } = require('@kyber/utils/dist/uniswapv3') + const tick = min + (position / 100) * (max - min) + return nearestUsableTick(Math.round(tick), tickSpacing) + }, + [viewRange, tickSpacing], + ) + + return { getPositionFromTick, getTickFromPosition } +} diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx new file mode 100644 index 0000000000..547bc2ca9d --- /dev/null +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx @@ -0,0 +1,379 @@ +import { MAX_TICK, MIN_TICK, nearestUsableTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' +import React, { useCallback, useEffect, useRef, useState } from 'react' + +import PriceAxis from 'components/UniswapPriceSlider/PriceAxis' +import { useDebouncedTicks, useSmoothZoom } from 'components/UniswapPriceSlider/hooks' +import { formatDisplayNumber } from 'utils/numbers' + +import { + CurrentPriceMarker, + Handle, + PriceLabel, + SkeletonAxisContainer, + SkeletonAxisLabel, + SkeletonAxisLine, + SkeletonAxisTick, + SkeletonCurrentPrice, + SkeletonHandle, + SkeletonPriceLabel, + SkeletonRange, + SkeletonSliderArea, + SkeletonTrack, + SkeletonWrapper, + SliderContainer, + SliderRange, + SliderTrack, + SliderWrapper, +} from './styles' +import type { HandleType, UniswapPriceSliderProps, ViewRange } from './types' +import { brushHandlePath, getEdgeIntensity } from './utils' + +// ============================================ +// Skeleton Component +// ============================================ + +const SKELETON_AXIS_POSITIONS = [0, 16.6, 33.3, 50, 66.6, 83.3, 100] + +function PriceSliderSkeleton() { + return ( + + + + + + + + + + + + + {SKELETON_AXIS_POSITIONS.map(pos => ( + + + + + ))} + + + ) +} + +// ============================================ +// Constants +// ============================================ + +const EDGE_THRESHOLD = 18 // % from edge for zoom out (ensures price labels ~6 chars visible) +const AUTO_CENTER_PADDING = 25 // % padding on each side when auto-centering after drag + +// ============================================ +// Main Component +// ============================================ + +function UniswapPriceSlider({ + pool, + invertPrice, + lowerTick, + upperTick, + setLowerTick, + setUpperTick, +}: UniswapPriceSliderProps) { + const { tickSpacing, token0Decimals, token1Decimals, currentTick } = pool + + // ============================================ + // State + // ============================================ + + const [viewRange, setViewRange] = useState(null) + const [isDragging, setIsDragging] = useState(null) + + // ============================================ + // Refs + // ============================================ + + const sliderRef = useRef(null) + const isInitialized = useRef(false) + const viewRangeRef = useRef(viewRange) + + // Keep viewRangeRef in sync with viewRange state + useEffect(() => { + viewRangeRef.current = viewRange + }, [viewRange]) + + // ============================================ + // Custom Hooks + // ============================================ + + const { startSmoothZoom } = useSmoothZoom(viewRange, setViewRange) + + const { + internalLowerTick, + internalUpperTick, + debouncedSetLowerTick, + debouncedSetUpperTick, + flushDebouncedValues, + getTargetTicks, + } = useDebouncedTicks(lowerTick, upperTick, setLowerTick, setUpperTick, isDragging !== null) + + // ============================================ + // Derived Values + // ============================================ + + const ticksReady = lowerTick !== undefined && upperTick !== undefined + + // ============================================ + // Tick/Position Converters + // ============================================ + + const getPositionFromTick = useCallback( + (tick: number): number => { + if (!viewRange) return 50 + const { min, max } = viewRange + return ((tick - min) / (max - min)) * 100 + }, + [viewRange], + ) + + const getTickFromPosition = useCallback( + (position: number): number => { + if (!viewRange) return 0 + const { min, max } = viewRange + const tick = min + (position / 100) * (max - min) + return nearestUsableTick(Math.round(tick), tickSpacing) + }, + [viewRange, tickSpacing], + ) + + // ============================================ + // Initialize View Range + // ============================================ + + useEffect(() => { + if (isInitialized.current || !ticksReady) return + + const tickRange = Math.abs(upperTick - lowerTick) + const padding = Math.max(tickRange * 0.5, tickSpacing * 50) + + const minTick = Math.min(lowerTick, upperTick, currentTick) + const maxTick = Math.max(lowerTick, upperTick, currentTick) + + setViewRange({ + min: Math.max(MIN_TICK, minTick - padding), + max: Math.min(MAX_TICK, maxTick + padding), + }) + isInitialized.current = true + }, [lowerTick, upperTick, currentTick, tickSpacing, ticksReady]) + + // ============================================ + // Event Handlers + // ============================================ + + const handleMouseDown = useCallback( + (handle: 'lower' | 'upper') => (e: React.MouseEvent) => { + e.preventDefault() + setIsDragging(handle) + }, + [], + ) + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + if (!isDragging || !sliderRef.current || !viewRange || lowerTick === undefined || upperTick === undefined) return + + const rect = sliderRef.current.getBoundingClientRect() + const x = e.clientX - rect.left + const position = Math.max(0, Math.min(100, (x / rect.width) * 100)) + const newTick = getTickFromPosition(position) + + const currentRange = viewRange.max - viewRange.min + + // Check if near edges for zoom out + const isNearLeftEdge = position < EDGE_THRESHOLD + const isNearRightEdge = position > 100 - EDGE_THRESHOLD + const edgeIntensity = getEdgeIntensity(position, EDGE_THRESHOLD) + + // Zoom out when near edges (zoom-in is handled by auto-center on mouse up) + if (isNearLeftEdge || isNearRightEdge) { + const baseExpansion = currentRange * 0.25 + const expansion = baseExpansion * edgeIntensity + + let targetMin = viewRange.min + let targetMax = viewRange.max + + if (isNearLeftEdge && viewRange.min > MIN_TICK) { + targetMin = Math.max(MIN_TICK, viewRange.min - expansion) + } + if (isNearRightEdge && viewRange.max < MAX_TICK) { + targetMax = Math.min(MAX_TICK, viewRange.max + expansion) + } + + startSmoothZoom(targetMin, targetMax) + } + + // Update tick values + if (isDragging === 'lower') { + const maxLower = (internalUpperTick ?? upperTick ?? 0) - tickSpacing * 10 + debouncedSetLowerTick(Math.min(newTick, maxLower)) + } else { + const minUpper = (internalLowerTick ?? lowerTick ?? 0) + tickSpacing * 10 + debouncedSetUpperTick(Math.max(newTick, minUpper)) + } + }, + [ + debouncedSetLowerTick, + debouncedSetUpperTick, + getTickFromPosition, + internalLowerTick, + internalUpperTick, + isDragging, + lowerTick, + startSmoothZoom, + tickSpacing, + upperTick, + viewRange, + ], + ) + + const handleMouseUp = useCallback(() => { + // Get the TARGET tick values (what user intended), not the animated values + const { lowerTick: targetLower, upperTick: targetUpper } = getTargetTicks() + + // Flush to apply target values immediately + flushDebouncedValues() + setIsDragging(null) + + // Use target ticks for auto-center calculation + const finalLowerTick = targetLower ?? lowerTick + const finalUpperTick = targetUpper ?? upperTick + + if (finalLowerTick === undefined || finalUpperTick === undefined) return + + // Use setTimeout to ensure state has updated before calculating positions + setTimeout(() => { + // Use ref to get the LATEST viewRange (not stale closure value) + const currentViewRange = viewRangeRef.current + if (!currentViewRange) return + + const tickDistance = Math.abs(finalUpperTick - finalLowerTick) + const handleCenter = (finalLowerTick + finalUpperTick) / 2 + + // Calculate ideal padding (25% on each side = handles take up 50% of view) + const idealPadding = tickDistance * (AUTO_CENTER_PADDING / (100 - 2 * AUTO_CENTER_PADDING)) + const minPadding = Math.max(tickDistance * 0.3, tickSpacing * 20) + const padding = Math.max(idealPadding, minPadding) + + const targetMin = Math.max(MIN_TICK, handleCenter - tickDistance / 2 - padding) + const targetMax = Math.min(MAX_TICK, handleCenter + tickDistance / 2 + padding) + + // Calculate current positions using LATEST viewRange from ref + const currentRange = currentViewRange.max - currentViewRange.min + const currentLowerPos = ((finalLowerTick - currentViewRange.min) / currentRange) * 100 + const currentUpperPos = ((finalUpperTick - currentViewRange.min) / currentRange) * 100 + const leftPadding = currentLowerPos + const rightPadding = 100 - currentUpperPos + const handleSpan = currentUpperPos - currentLowerPos // % of view that handles span + + // Ideal handle span is 50% (100 - 2 * AUTO_CENTER_PADDING) + const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING + const handlesTooClose = handleSpan < idealHandleSpan * 0.6 // Less than 60% of ideal = too zoomed out + const handlesTooFar = handleSpan > idealHandleSpan * 1.5 // More than 150% of ideal = too zoomed in + + // Check if rebalancing is needed + const needsRebalance = + leftPadding < EDGE_THRESHOLD + 5 || // Near left edge + rightPadding < EDGE_THRESHOLD + 5 || // Near right edge + leftPadding < 0 || // Handle outside left + rightPadding < 0 || // Handle outside right + handlesTooClose || // Handles too close together (need zoom in) + handlesTooFar || // Handles too far apart (need zoom out) + (leftPadding > 5 && rightPadding > 5 && (leftPadding / rightPadding > 2.5 || rightPadding / leftPadding > 2.5)) // Imbalanced + + if (needsRebalance) { + startSmoothZoom(targetMin, targetMax) + } + }, 50) + }, [flushDebouncedValues, getTargetTicks, lowerTick, startSmoothZoom, tickSpacing, upperTick]) + + // ============================================ + // Mouse Event Listeners + // ============================================ + + useEffect(() => { + if (!isDragging) return + + // Set grabbing cursor on body to persist while dragging outside handle + document.body.style.cursor = 'grabbing' + document.body.style.userSelect = 'none' + + document.addEventListener('mousemove', handleMouseMove) + document.addEventListener('mouseup', handleMouseUp) + + return () => { + // Reset cursor when dragging ends + document.body.style.cursor = '' + document.body.style.userSelect = '' + + document.removeEventListener('mousemove', handleMouseMove) + document.removeEventListener('mouseup', handleMouseUp) + } + }, [isDragging, handleMouseMove, handleMouseUp]) + + // ============================================ + // Render + // ============================================ + + if (!ticksReady || !viewRange) { + return + } + + // Use internal ticks for smooth visual updates during dragging + const displayLowerTick = internalLowerTick ?? lowerTick + const displayUpperTick = internalUpperTick ?? upperTick + + const lowerPrice = tickToPrice(Math.round(displayLowerTick), token0Decimals, token1Decimals, invertPrice) + const upperPrice = tickToPrice(Math.round(displayUpperTick), token0Decimals, token1Decimals, invertPrice) + + const lowerPosition = getPositionFromTick(displayLowerTick) + const upperPosition = getPositionFromTick(displayUpperTick) + const currentPosition = getPositionFromTick(currentTick) + + return ( + + + + + + + + + {formatDisplayNumber(lowerPrice, { significantDigits: 6 })} + + + + {formatDisplayNumber(upperPrice, { significantDigits: 6 })} + + + + + + + + + + + + + + + + + + ) +} + +export default UniswapPriceSlider diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts new file mode 100644 index 0000000000..bac088bcda --- /dev/null +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts @@ -0,0 +1,276 @@ +import styled, { keyframes } from 'styled-components' + +// ============================================ +// Skeleton Animation +// ============================================ + +const shimmer = keyframes` + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +` + +const SkeletonBase = styled.div` + background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%); + background-size: 200% 100%; + animation: ${shimmer} 1.5s ease-in-out infinite; + border-radius: 2px; +` + +// ============================================ +// Skeleton Components +// ============================================ + +export const SkeletonWrapper = styled.div` + width: 100%; + overflow: hidden; +` + +export const SkeletonSliderArea = styled.div` + position: relative; + width: 100%; + height: 60px; + margin: 20px 0 0 0; +` + +export const SkeletonTrack = styled(SkeletonBase)` + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 4px; + transform: translateY(-50%); +` + +export const SkeletonRange = styled(SkeletonBase)` + position: absolute; + top: 50%; + left: 25%; + width: 50%; + height: 4px; + transform: translateY(-50%); +` + +export const SkeletonHandle = styled(SkeletonBase)<{ $isLower: boolean }>` + position: absolute; + top: 50%; + left: ${props => (props.$isLower ? '25%' : '75%')}; + transform: translate(-50%, -50%); + width: 8px; + height: 35px; + border-radius: 6px; +` + +export const SkeletonPriceLabel = styled(SkeletonBase)<{ $isLower: boolean }>` + position: absolute; + top: 4px; + left: ${props => (props.$isLower ? '25%' : '75%')}; + transform: ${props => (props.$isLower ? 'translateX(calc(-100% - 12px))' : 'translateX(12px)')}; + width: 60px; + height: 14px; +` + +export const SkeletonCurrentPrice = styled(SkeletonBase)` + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 2px; + height: 15px; +` + +export const SkeletonAxisContainer = styled.div` + position: relative; + width: 100%; + height: 24px; + margin-top: -8px; +` + +export const SkeletonAxisLine = styled(SkeletonBase)` + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; +` + +export const SkeletonAxisTick = styled(SkeletonBase)<{ $position: number }>` + position: absolute; + top: 0; + left: ${props => props.$position}%; + transform: translateX(-50%); + width: 1px; + height: 6px; +` + +export const SkeletonAxisLabel = styled(SkeletonBase)<{ $position: number }>` + position: absolute; + top: 8px; + left: ${props => props.$position}%; + transform: translateX(-50%); + width: 35px; + height: 10px; +` + +// ============================================ +// Main Slider Styles +// ============================================ + +export const SliderContainer = styled.div` + width: 100%; + overflow: hidden; +` + +export const SliderWrapper = styled.div` + position: relative; + width: 100%; + height: 60px; + margin: 20px 0 0 0; + overflow: hidden; +` + +export const SliderTrack = styled.div` + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 4px; + background: #3a3a3a; + transform: translateY(-50%); + border-radius: 2px; +` + +export const SliderRange = styled.div.attrs<{ $left: number; $width: number }>(props => ({ + style: { + left: `${props.$left}%`, + width: `${props.$width}%`, + }, +}))<{ $left: number; $width: number }>` + position: absolute; + top: 50%; + height: 4px; + background: linear-gradient(90deg, #31cb9e 0%, #7289da 100%); + transform: translateY(-50%); + border-radius: 2px; +` + +// ============================================ +// Handle Styles +// ============================================ + +export const Handle = styled.div.attrs<{ $position: number }>(props => ({ + style: { + left: `${props.$position}%`, + }, +}))<{ $position: number; $isLower: boolean }>` + position: absolute; + top: 0; + transform: translate(-50%, 1%); + cursor: grab; + z-index: 10; + + &:active { + cursor: grabbing; + } + + svg { + display: block; + } +` + +export const PriceLabel = styled.div.attrs<{ $position: number; $isLower: boolean }>(props => ({ + style: { + left: `${props.$position}%`, + transform: props.$isLower ? 'translateX(calc(-100% - 12px))' : 'translateX(12px)', + }, +}))<{ $position: number; $isLower: boolean }>` + position: absolute; + top: 4px; + color: #fff; + font-size: 12px; + font-weight: 500; + white-space: nowrap; + pointer-events: none; +` + +// ============================================ +// Current Price Marker +// ============================================ + +export const CurrentPriceMarker = styled.div.attrs<{ $position: number }>(props => ({ + style: { + left: `${props.$position}%`, + }, +}))<{ $position: number }>` + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + width: 2px; + height: 15px; + background: #888; + border-radius: 2px; + z-index: 5; + + &::after { + content: ''; + position: absolute; + top: -5px; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #888; + } +` + +// ============================================ +// Price Axis Styles +// ============================================ + +export const PriceAxisContainer = styled.div` + position: relative; + width: 100%; + height: 24px; + margin-top: -8px; +` + +export const PriceAxisLine = styled.div` + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: #3a3a3a; +` + +export const PriceAxisTick = styled.div.attrs<{ $position: number }>(props => ({ + style: { + left: `${props.$position}%`, + }, +}))<{ $position: number }>` + position: absolute; + top: 0; + transform: translateX(-50%); + width: 1px; + height: 6px; + background: #555; +` + +export const PriceAxisLabel = styled.div.attrs<{ $position: number }>(props => ({ + style: { + left: `${props.$position}%`, + }, +}))<{ $position: number }>` + position: absolute; + top: 8px; + transform: translateX(-50%); + color: #888; + font-size: 10px; + white-space: nowrap; + user-select: none; +` diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/types.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/types.ts new file mode 100644 index 0000000000..a7854bc375 --- /dev/null +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/types.ts @@ -0,0 +1,44 @@ +/** + * View range representing the visible tick range on the slider + */ +export interface ViewRange { + min: number + max: number +} + +/** + * Pool information required for price calculations + */ +export interface PoolInfo { + tickSpacing: number + token0Decimals: number + token1Decimals: number + currentTick: number +} + +/** + * Props for the UniswapPriceSlider component + */ +export interface UniswapPriceSliderProps { + pool: PoolInfo + invertPrice?: boolean + lowerTick?: number + upperTick?: number + setLowerTick: (tick: number) => void + setUpperTick: (tick: number) => void +} + +/** + * Props for the PriceAxis component + */ +export interface PriceAxisProps { + viewRange: ViewRange + token0Decimals: number + token1Decimals: number + invertPrice?: boolean +} + +/** + * Handle type for dragging state + */ +export type HandleType = 'lower' | 'upper' | null diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts new file mode 100644 index 0000000000..2760a3975d --- /dev/null +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts @@ -0,0 +1,158 @@ +/** + * Generate SVG path for brush handle (oval shape with rounded ends) + */ +export const brushHandlePath = (height: number): string => { + return [ + `M 0.5 0`, + `Q 0 0 0 1.5`, // Rounded top-left corner + `v 3.5`, + `C -5 5 -5 17 0 17`, // Oval + `v ${height - 19}`, + `Q 0 ${height} 0.5 ${height}`, // Rounded bottom-left corner + `Q 1 ${height} 1 ${height - 1.5}`, // Rounded bottom-right corner + `V 17`, + `C 6 17 6 5 1 5`, + `V 1.5`, + `Q 1 0 0.5 0`, // Rounded top-right corner + ].join(' ') +} + +/** + * Calculate nice tick values for the price axis + * Returns an array of prices that are evenly spaced and use "nice" numbers + */ +export const calculatePriceAxisTicks = (minPrice: number, maxPrice: number, targetTickCount = 6): number[] => { + const range = maxPrice - minPrice + if (range <= 0) return [] + + // Calculate the rough step size + const roughStep = range / targetTickCount + + // Find the magnitude of the step + const magnitude = Math.pow(10, Math.floor(Math.log10(roughStep))) + + // Choose a nice step value (1, 2, 2.5, 5, 10 times the magnitude) + const niceSteps = [1, 2, 2.5, 5, 10] + let niceStep = magnitude + for (const step of niceSteps) { + if (step * magnitude >= roughStep) { + niceStep = step * magnitude + break + } + } + + // Calculate the start and end values + const start = Math.ceil(minPrice / niceStep) * niceStep + const ticks: number[] = [] + + for (let tick = start; tick <= maxPrice; tick += niceStep) { + // Avoid floating point precision issues + const roundedTick = Math.round(tick * 1e10) / 1e10 + if (roundedTick >= minPrice && roundedTick <= maxPrice) { + ticks.push(roundedTick) + } + } + + return ticks +} + +/** + * Format number with comma separators + */ +const formatWithCommas = (num: number, decimals = 0): string => { + const fixed = num.toFixed(decimals) + const parts = fixed.split('.') + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',') + return parts.join('.') +} + +/** + * Format price for axis display - user-friendly format + * Shows more detail for smaller ranges, uses abbreviations for large numbers + */ +export const formatAxisPrice = (price: number): string => { + if (price === 0) return '0' + if (!isFinite(price)) return '∞' + + const absPrice = Math.abs(price) + const sign = price < 0 ? '-' : '' + + // For astronomically large numbers, show a capped display + if (absPrice >= 1e18) { + return sign + '>999Q' + } + // Quadrillions (10^15) + if (absPrice >= 1e15) { + const val = absPrice / 1e15 + return sign + (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'Q' + } + // Trillions (10^12) + if (absPrice >= 1e12) { + const val = absPrice / 1e12 + return sign + (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'T' + } + // Billions (10^9) + if (absPrice >= 1e9) { + const val = absPrice / 1e9 + return sign + (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'B' + } + // Millions (10^6) + if (absPrice >= 1e6) { + const val = absPrice / 1e6 + return sign + (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'M' + } + // 100K - 999K: use K suffix + if (absPrice >= 100000) { + const val = absPrice / 1000 + return sign + Math.round(val) + 'K' + } + // 10K - 99.9K: show as "12.5K" with more precision + if (absPrice >= 10000) { + const val = absPrice / 1000 + return sign + val.toFixed(1) + 'K' + } + // 1K - 9.9K: show full number with comma (like "2,500" or "3,750") + if (absPrice >= 1000) { + // Round to nearest 10 for cleaner display + const rounded = Math.round(absPrice / 10) * 10 + return sign + formatWithCommas(rounded) + } + // 100 - 999: show full number + if (absPrice >= 100) { + return sign + Math.round(absPrice).toString() + } + // 10 - 99.99: show with 1 decimal + if (absPrice >= 10) { + return price.toFixed(1) + } + // 1 - 9.99: show with 2 decimals + if (absPrice >= 1) { + return price.toFixed(2) + } + // Small decimals + if (absPrice >= 0.01) { + return price.toFixed(4) + } + if (absPrice >= 0.0001) { + return price.toFixed(5) + } + // For extremely small numbers, show a floor display + if (absPrice < 1e-8) { + return sign + '<0.00001' + } + return price.toPrecision(3) +} + +/** + * Calculate edge intensity for zoom behavior + * Returns 0-1 based on how close position is to edge + */ +export const getEdgeIntensity = (position: number, edgeThreshold: number): number => { + if (position < edgeThreshold) { + return 1 - position / edgeThreshold + } + if (position > 100 - edgeThreshold) { + return (position - (100 - edgeThreshold)) / edgeThreshold + } + return 0 +} From c1ab7975c42011953bea64eccc04354d6e0ddeed Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 1 Dec 2025 08:49:57 +0700 Subject: [PATCH 50/87] integrate with price slider --- .../Earns/components/SmartExit/Metrics.tsx | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx index 00a5476b80..99213320c4 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx @@ -1,11 +1,13 @@ import { Label, RadioGroup, RadioGroupItem } from '@kyber/ui' +import { nearestUsableTick, priceToClosestTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' import { Trans, t } from '@lingui/macro' import dayjs from 'dayjs' -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { Calendar } from 'react-feather' import { Box, Flex, Text } from 'rebass' import Divider from 'components/Divider' +import UniswapPriceSlider from 'components/UniswapPriceSlider' import DateTimePicker from 'components/swapv2/LimitOrder/ExpirePicker' import useTheme from 'hooks/useTheme' import { DEFAULT_TIME_OPTIONS } from 'pages/Earns/components/SmartExit/ExpireSetting' @@ -150,6 +152,59 @@ const MetricSelect = ({ const priceCondition = metric.condition as PriceCondition const timeCondition = metric.condition as TimeCondition + const [lowerTick, setLowerTick] = useState() + const [upperTick, setUpperTick] = useState() + const currentTick = useMemo( + () => + nearestUsableTick( + priceToClosestTick( + position.priceRange.current.toString(), + position.token0.decimals, + position.token1.decimals, + ) || 0, + position.pool.tickSpacing, + ), + [position.pool.tickSpacing, position.priceRange, position.token0.decimals, position.token1.decimals], + ) + + useEffect(() => { + if (priceCondition?.gte) { + setLowerTick( + nearestUsableTick( + priceToClosestTick(priceCondition.gte, position.token0.decimals, position.token1.decimals) || 0, + position.pool.tickSpacing, + ), + ) + } + }, [position.pool.tickSpacing, position.token0.decimals, position.token1.decimals, priceCondition.gte]) + + useEffect(() => { + if (priceCondition?.lte) { + setUpperTick( + nearestUsableTick( + priceToClosestTick(priceCondition.lte, position.token0.decimals, position.token1.decimals) || 0, + position.pool.tickSpacing, + ), + ) + } + }, [position.pool.tickSpacing, position.token0.decimals, position.token1.decimals, priceCondition.lte]) + + useEffect(() => { + if (lowerTick !== undefined) { + const lowerPrice = tickToPrice(lowerTick, position.token0.decimals, position.token1.decimals) + setMetric({ ...metric, condition: { ...priceCondition, gte: lowerPrice } }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [lowerTick]) + + useEffect(() => { + if (upperTick !== undefined) { + const upperPrice = tickToPrice(upperTick, position.token0.decimals, position.token1.decimals) + setMetric({ ...metric, condition: { ...priceCondition, lte: upperPrice } }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [upperTick]) + return ( <> @@ -266,6 +321,18 @@ const MetricSelect = ({ {position.token0.symbol}/{position.token1.symbol} + )} From 50e87a80118a34fa473e3051be7aac4d606e772a Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 1 Dec 2025 18:13:39 +0700 Subject: [PATCH 51/87] fix(price-slider): debounce, circular update, refactor metrics --- .../Earns/components/SmartExit/Metrics.tsx | 541 +++++++++++------- .../Earns/components/SmartExit/index.tsx | 2 +- 2 files changed, 321 insertions(+), 222 deletions(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx index 99213320c4..d0fa46e229 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx @@ -2,7 +2,7 @@ import { Label, RadioGroup, RadioGroupItem } from '@kyber/ui' import { nearestUsableTick, priceToClosestTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' import { Trans, t } from '@lingui/macro' import dayjs from 'dayjs' -import { useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Calendar } from 'react-feather' import { Box, Flex, Text } from 'rebass' @@ -112,9 +112,6 @@ const MetricSelect = ({ selectedMetric?: SelectedMetric position: ParsedPosition }) => { - const theme = useTheme() - const [openDatePicker, setOpenDatePicker] = useState(false) - const metricOptions = useMemo( () => [ @@ -134,26 +131,166 @@ const MetricSelect = ({ [selectedMetric], ) - const timeOptions = useMemo( - () => [ - { - label: t`Before`, - value: 'before', - }, - { - label: t`After`, - value: 'after', - }, - ], - [], + return ( + <> + + + Select Metric + + { + if (value === metric.metric) return + const newMetric = value as Metric + const condition = getDefaultCondition(newMetric) + if (!condition) return + setMetric({ metric: newMetric, condition }) + }} + value={metric.metric} + menuStyle={{ width: '250px' }} + /> + + + {metric.metric === Metric.FeeYield && } + + {metric.metric === Metric.PoolPrice && } + + {metric.metric === Metric.Time && } + ) +} +const FeeYieldInput = ({ + metric, + setMetric, +}: { + metric: SelectedMetric + setMetric: (value: SelectedMetric) => void +}) => { + const theme = useTheme() const feeYieldCondition = metric.condition as FeeYieldCondition + + return ( + <> + + + Exit when fee yield ≥ + + + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + const numValue = parseFloat(value) + // Limit to 1-100% + if (value === '' || numValue > 0) { + setMetric({ ...metric, condition: value }) + } + } + }} + placeholder="0" + /> + + % + + + + + {[5, 10, 15, 20].map(item => { + const isSelected = metric.condition === item.toString() + + return ( + setMetric({ ...metric, condition: item.toString() })} + sx={{ + borderRadius: '999px', + border: `1px solid ${isSelected ? theme.primary : theme.border}`, + backgroundColor: isSelected ? theme.primary + '20' : 'transparent', + padding: '4px 12px', + color: isSelected ? theme.primary : theme.subText, + fontSize: '12px', + fontWeight: '500', + cursor: 'pointer', + '&:hover': { + backgroundColor: theme.primary + '10', + }, + }} + > + {item}% + + ) + })} + + + ) +} + +const PriceInput = ({ + metric, + setMetric, + position, +}: { + metric: SelectedMetric + setMetric: (value: SelectedMetric) => void + position: ParsedPosition +}) => { const priceCondition = metric.condition as PriceCondition - const timeCondition = metric.condition as TimeCondition - const [lowerTick, setLowerTick] = useState() - const [upperTick, setUpperTick] = useState() + const [lowerTick, setLowerTickState] = useState() + const [upperTick, setUpperTickState] = useState() + + // Local input state for debouncing + const [inputMinPrice, setInputMinPrice] = useState(priceCondition?.gte ?? '') + const [inputMaxPrice, setInputMaxPrice] = useState(priceCondition?.lte ?? '') + + // Track change source to prevent circular updates + const changeSourceRef = useRef<'input' | 'slider' | null>(null) + + // Sync local input with external price changes (from slider) + useEffect(() => { + if (changeSourceRef.current === 'slider' && priceCondition?.gte) { + setInputMinPrice(priceCondition.gte) + } + }, [priceCondition?.gte]) + + useEffect(() => { + if (changeSourceRef.current === 'slider' && priceCondition?.lte) { + setInputMaxPrice(priceCondition.lte) + } + }, [priceCondition?.lte]) + + // Debounce input price updates + useEffect(() => { + if (changeSourceRef.current === 'slider' || metric.metric !== Metric.PoolPrice) return + const timer = setTimeout(() => { + if (inputMinPrice !== priceCondition?.gte) { + setMetric({ ...metric, condition: { ...priceCondition, gte: inputMinPrice } }) + } + }, 300) + return () => clearTimeout(timer) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inputMinPrice]) + + useEffect(() => { + if (changeSourceRef.current === 'slider' || metric.metric !== Metric.PoolPrice) return + const timer = setTimeout(() => { + if (inputMaxPrice !== priceCondition?.lte) { + setMetric({ ...metric, condition: { ...priceCondition, lte: inputMaxPrice } }) + } + }, 300) + return () => clearTimeout(timer) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inputMaxPrice]) + const currentTick = useMemo( () => nearestUsableTick( @@ -167,229 +304,191 @@ const MetricSelect = ({ [position.pool.tickSpacing, position.priceRange, position.token0.decimals, position.token1.decimals], ) + // Convert price to tick (called from input change) + const priceToTick = useCallback( + (price: string) => { + if (!price) return undefined + return nearestUsableTick( + priceToClosestTick(price, position.token0.decimals, position.token1.decimals) || 0, + position.pool.tickSpacing, + ) + }, + [position.pool.tickSpacing, position.token0.decimals, position.token1.decimals], + ) + + // Wrapper to set tick from slider (updates price) + const setLowerTick = useCallback( + (tick: number | undefined) => { + changeSourceRef.current = 'slider' + setLowerTickState(tick) + if (tick !== undefined) { + const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals) + setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) + } + // Reset source after React batch update + setTimeout(() => { + changeSourceRef.current = null + }, 0) + }, + [metric, priceCondition, position.token0.decimals, position.token1.decimals, setMetric], + ) + + const setUpperTick = useCallback( + (tick: number | undefined) => { + changeSourceRef.current = 'slider' + setUpperTickState(tick) + if (tick !== undefined) { + const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals) + setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) + } + setTimeout(() => { + changeSourceRef.current = null + }, 0) + }, + [metric, priceCondition, position.token0.decimals, position.token1.decimals, setMetric], + ) + + // Sync tick from price input (only when source is input, not slider) useEffect(() => { + if (changeSourceRef.current === 'slider') return if (priceCondition?.gte) { - setLowerTick( - nearestUsableTick( - priceToClosestTick(priceCondition.gte, position.token0.decimals, position.token1.decimals) || 0, - position.pool.tickSpacing, - ), - ) + const tick = priceToTick(priceCondition.gte) + if (tick !== undefined && tick !== lowerTick) { + setLowerTickState(tick) + } } - }, [position.pool.tickSpacing, position.token0.decimals, position.token1.decimals, priceCondition.gte]) + }, [priceCondition?.gte, priceToTick, lowerTick]) useEffect(() => { + if (changeSourceRef.current === 'slider') return if (priceCondition?.lte) { - setUpperTick( - nearestUsableTick( - priceToClosestTick(priceCondition.lte, position.token0.decimals, position.token1.decimals) || 0, - position.pool.tickSpacing, - ), - ) + const tick = priceToTick(priceCondition.lte) + if (tick !== undefined && tick !== upperTick) { + setUpperTickState(tick) + } } - }, [position.pool.tickSpacing, position.token0.decimals, position.token1.decimals, priceCondition.lte]) + }, [priceCondition?.lte, priceToTick, upperTick]) - useEffect(() => { - if (lowerTick !== undefined) { - const lowerPrice = tickToPrice(lowerTick, position.token0.decimals, position.token1.decimals) - setMetric({ ...metric, condition: { ...priceCondition, gte: lowerPrice } }) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [lowerTick]) + return ( + <> + + Exit when the pool price is between + + + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + setInputMinPrice(value) + } + }} + /> + - + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + setInputMaxPrice(value) + } + }} + /> + + {position.token0.symbol}/{position.token1.symbol} + + + + + ) +} - useEffect(() => { - if (upperTick !== undefined) { - const upperPrice = tickToPrice(upperTick, position.token0.decimals, position.token1.decimals) - setMetric({ ...metric, condition: { ...priceCondition, lte: upperPrice } }) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [upperTick]) +const TimeInput = ({ metric, setMetric }: { metric: SelectedMetric; setMetric: (value: SelectedMetric) => void }) => { + const theme = useTheme() + const [openDatePicker, setOpenDatePicker] = useState(false) + + const timeOptions = useMemo( + () => [ + { + label: t`Before`, + value: 'before', + }, + { + label: t`After`, + value: 'after', + }, + ], + [], + ) + const timeCondition = metric.condition as TimeCondition return ( <> - + - Select Metric + Exit this position { - if (value === metric.metric) return - const newMetric = value as Metric - const condition = getDefaultCondition(newMetric) - if (!condition) return - setMetric({ metric: newMetric, condition }) + setMetric({ ...metric, condition: { ...timeCondition, condition: value } }) }} - value={metric.metric} + value={timeCondition.condition} menuStyle={{ width: '250px' }} /> + + Set Schedule + + setOpenDatePicker(true)} + > + + {timeCondition.time === null ? 'DD/MM/YYYY' : dayjs(timeCondition.time).format('DD/MM/YYYY HH:mm:ss')} + + + - {metric.metric === Metric.FeeYield && ( - <> - - - Exit when fee yield ≥ - - - { - const value = e.target.value - // Only allow numbers and decimal point - if (/^\d*\.?\d*$/.test(value)) { - const numValue = parseFloat(value) - // Limit to 1-100% - if (value === '' || numValue > 0) { - setMetric({ ...metric, condition: value }) - } - } - }} - placeholder="0" - /> - - % - - - - - {[5, 10, 15, 20].map(item => { - const isSelected = metric.condition === item.toString() - - return ( - setMetric({ ...metric, condition: item.toString() })} - sx={{ - borderRadius: '999px', - border: `1px solid ${isSelected ? theme.primary : theme.border}`, - backgroundColor: isSelected ? theme.primary + '20' : 'transparent', - padding: '4px 12px', - color: isSelected ? theme.primary : theme.subText, - fontSize: '12px', - fontWeight: '500', - cursor: 'pointer', - '&:hover': { - backgroundColor: theme.primary + '10', - }, - }} - > - {item}% - - ) - })} - - - )} - - {metric.metric === Metric.PoolPrice && ( - <> - - Exit when the pool price is between - - - { - const value = e.target.value - // Only allow numbers and decimal point - if (/^\d*\.?\d*$/.test(value)) { - setMetric({ ...metric, condition: { ...priceCondition, gte: value } }) - } - }} - /> - - - { - const value = e.target.value - // Only allow numbers and decimal point - if (/^\d*\.?\d*$/.test(value)) { - setMetric({ ...metric, condition: { ...priceCondition, lte: value } }) - } - }} - /> - - {position.token0.symbol}/{position.token1.symbol} - - - - - )} - - {metric.metric === Metric.Time && ( - <> - - - Exit this position - - { - setMetric({ ...metric, condition: { ...timeCondition, condition: value } }) - }} - value={timeCondition.condition} - menuStyle={{ width: '250px' }} - /> - - - Set Schedule - - setOpenDatePicker(true)} - > - - {timeCondition.time === null ? 'DD/MM/YYYY' : dayjs(timeCondition.time).format('DD/MM/YYYY HH:mm:ss')} - - - - - Time Setup} - isOpen={openDatePicker} - onDismiss={() => setOpenDatePicker(false)} - onSetDate={(val: Date | number) => { - setMetric({ - ...metric, - condition: { ...timeCondition, time: typeof val === 'number' ? val : val.getTime() }, - }) - }} - expire={ - timeCondition.time || 5 * 60 // 5min - } - defaultOptions={DEFAULT_TIME_OPTIONS} - /> - - )} + Time Setup} + isOpen={openDatePicker} + onDismiss={() => setOpenDatePicker(false)} + onSetDate={(val: Date | number) => { + setMetric({ + ...metric, + condition: { ...timeCondition, time: typeof val === 'number' ? val : val.getTime() }, + }) + }} + expire={ + timeCondition.time || 5 * 60 // 5min + } + defaultOptions={DEFAULT_TIME_OPTIONS} + /> ) } diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx index 82485356b2..f79f2a6bf3 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx @@ -84,7 +84,7 @@ export const SmartExit = ({ position, onDismiss }: { position: ParsedPosition; o deadline, }) - const disabled = invalidYieldCondition || invalidPriceCondition || invalidTimeCondition || !feeInfo + const disabled = invalidYieldCondition || invalidPriceCondition || invalidTimeCondition || !feeInfo || feeLoading // Auto-estimate when metrics are valid useEffect(() => { From efc317bb85a8dfe00a71e9bce470395375d13907 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 15:45:50 +0700 Subject: [PATCH 52/87] update(price-slider): implement for invert price --- .../UniswapPriceSlider/PriceAxis.tsx | 12 +- .../components/UniswapPriceSlider/hooks.ts | 20 ++- .../components/UniswapPriceSlider/index.tsx | 161 ++++++------------ .../components/UniswapPriceSlider/styles.ts | 2 +- .../components/UniswapPriceSlider/utils.ts | 102 +++-------- .../Earns/components/SmartExit/Metrics.tsx | 5 +- 6 files changed, 109 insertions(+), 193 deletions(-) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx index cb20a516ca..89ea252075 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx @@ -1,11 +1,10 @@ import { tickToPrice } from '@kyber/utils/dist/uniswapv3' import React, { useMemo } from 'react' +import { PriceAxisContainer, PriceAxisLabel, PriceAxisLine, PriceAxisTick } from 'components/UniswapPriceSlider/styles' import type { PriceAxisProps } from 'components/UniswapPriceSlider/types' import { formatAxisPrice } from 'components/UniswapPriceSlider/utils' -import { PriceAxisContainer, PriceAxisLabel, PriceAxisLine, PriceAxisTick } from './styles' - const MAX_TICK_COUNT = 11 // More ticks for small ranges const MIN_TICK_COUNT = 2 // Just first and last for extreme ranges @@ -49,6 +48,7 @@ const getOptimalTickConfig = (minPrice: number, maxPrice: number): { tickCount: /** * Calculate tick positions for the axis * Uses tick-space for even distribution (matching the slider) + * When invertPrice, positions are flipped so lower inverted price is on left */ const calculateAxisTicks = ( viewRange: { min: number; max: number }, @@ -66,7 +66,9 @@ const calculateAxisTicks = ( for (let i = 0; i < count; i++) { const tick = Math.round(viewRange.min + step * i) const price = +tickToPrice(tick, token0Decimals, token1Decimals, invertPrice) - const position = ((tick - viewRange.min) / tickRange) * 100 + const normalPosition = ((tick - viewRange.min) / tickRange) * 100 + // When invertPrice, flip position so lower inverted price (from higher tick) is on left + const position = invertPrice ? 100 - normalPosition : normalPosition ticks.push({ tick, price, position }) } @@ -133,7 +135,9 @@ function PriceAxis({ viewRange, token0Decimals, token1Decimals, invertPrice }: P ) const ticks = calculateAxisTicks(viewRange, token0Decimals, token1Decimals, tickCount, invertPrice) - return filterOverlappingTicks(ticks, minGapPercent) + // Sort by position ascending for proper overlap filtering + const sortedTicks = [...ticks].sort((a, b) => a.position - b.position) + return filterOverlappingTicks(sortedTicks, minGapPercent) }, [viewRange, token0Decimals, token1Decimals, invertPrice]) return ( diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts index 0541f7defa..cb8f2d9390 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts @@ -1,3 +1,4 @@ +import { nearestUsableTick } from '@kyber/utils/dist/uniswapv3' import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react' import type { ViewRange } from 'components/UniswapPriceSlider/types' @@ -269,26 +270,33 @@ export const useDebouncedTicks = ( /** * Hook for converting between tick and position + * When invertPrice = true, the entire visual is flipped: + * - Lower tick (higher inverted price) appears on the RIGHT + * - Upper tick (lower inverted price) appears on the LEFT + * - Axis shows inverted prices from low (left) to high (right) */ -export const useTickPositionConverter = (viewRange: ViewRange | null, tickSpacing: number) => { +export const useTickPositionConverter = (viewRange: ViewRange | null, tickSpacing: number, invertPrice?: boolean) => { const getPositionFromTick = useCallback( (tick: number): number => { if (!viewRange) return 50 const { min, max } = viewRange - return ((tick - min) / (max - min)) * 100 + const normalPosition = ((tick - min) / (max - min)) * 100 + // When invertPrice, flip the position so higher inverted price is on the right + return invertPrice ? 100 - normalPosition : normalPosition }, - [viewRange], + [viewRange, invertPrice], ) const getTickFromPosition = useCallback( (position: number): number => { if (!viewRange) return 0 const { min, max } = viewRange - const { nearestUsableTick } = require('@kyber/utils/dist/uniswapv3') - const tick = min + (position / 100) * (max - min) + // When invertPrice, flip the position first + const actualPosition = invertPrice ? 100 - position : position + const tick = min + (actualPosition / 100) * (max - min) return nearestUsableTick(Math.round(tick), tickSpacing) }, - [viewRange, tickSpacing], + [viewRange, tickSpacing, invertPrice], ) return { getPositionFromTick, getTickFromPosition } diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx index 547bc2ca9d..930d868f5c 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx @@ -1,10 +1,8 @@ -import { MAX_TICK, MIN_TICK, nearestUsableTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' +import { MAX_TICK, MIN_TICK, tickToPrice } from '@kyber/utils/dist/uniswapv3' import React, { useCallback, useEffect, useRef, useState } from 'react' import PriceAxis from 'components/UniswapPriceSlider/PriceAxis' -import { useDebouncedTicks, useSmoothZoom } from 'components/UniswapPriceSlider/hooks' -import { formatDisplayNumber } from 'utils/numbers' - +import { useDebouncedTicks, useSmoothZoom, useTickPositionConverter } from 'components/UniswapPriceSlider/hooks' import { CurrentPriceMarker, Handle, @@ -24,52 +22,39 @@ import { SliderRange, SliderTrack, SliderWrapper, -} from './styles' -import type { HandleType, UniswapPriceSliderProps, ViewRange } from './types' -import { brushHandlePath, getEdgeIntensity } from './utils' - -// ============================================ -// Skeleton Component -// ============================================ +} from 'components/UniswapPriceSlider/styles' +import type { HandleType, UniswapPriceSliderProps, ViewRange } from 'components/UniswapPriceSlider/types' +import { brushHandlePath, getEdgeIntensity } from 'components/UniswapPriceSlider/utils' +import { formatDisplayNumber } from 'utils/numbers' const SKELETON_AXIS_POSITIONS = [0, 16.6, 33.3, 50, 66.6, 83.3, 100] -function PriceSliderSkeleton() { - return ( - - - - - - - - - - - - - {SKELETON_AXIS_POSITIONS.map(pos => ( - - - - - ))} - - - ) -} - -// ============================================ -// Constants -// ============================================ +const PriceSliderSkeleton = () => ( + + + + + + + + + + + + + {SKELETON_AXIS_POSITIONS.map(pos => ( + + + + + ))} + + +) const EDGE_THRESHOLD = 18 // % from edge for zoom out (ensures price labels ~6 chars visible) const AUTO_CENTER_PADDING = 25 // % padding on each side when auto-centering after drag -// ============================================ -// Main Component -// ============================================ - function UniswapPriceSlider({ pool, invertPrice, @@ -80,17 +65,9 @@ function UniswapPriceSlider({ }: UniswapPriceSliderProps) { const { tickSpacing, token0Decimals, token1Decimals, currentTick } = pool - // ============================================ - // State - // ============================================ - const [viewRange, setViewRange] = useState(null) const [isDragging, setIsDragging] = useState(null) - // ============================================ - // Refs - // ============================================ - const sliderRef = useRef(null) const isInitialized = useRef(false) const viewRangeRef = useRef(viewRange) @@ -100,10 +77,6 @@ function UniswapPriceSlider({ viewRangeRef.current = viewRange }, [viewRange]) - // ============================================ - // Custom Hooks - // ============================================ - const { startSmoothZoom } = useSmoothZoom(viewRange, setViewRange) const { @@ -115,39 +88,11 @@ function UniswapPriceSlider({ getTargetTicks, } = useDebouncedTicks(lowerTick, upperTick, setLowerTick, setUpperTick, isDragging !== null) - // ============================================ - // Derived Values - // ============================================ + const { getPositionFromTick, getTickFromPosition } = useTickPositionConverter(viewRange, tickSpacing, invertPrice) const ticksReady = lowerTick !== undefined && upperTick !== undefined - // ============================================ - // Tick/Position Converters - // ============================================ - - const getPositionFromTick = useCallback( - (tick: number): number => { - if (!viewRange) return 50 - const { min, max } = viewRange - return ((tick - min) / (max - min)) * 100 - }, - [viewRange], - ) - - const getTickFromPosition = useCallback( - (position: number): number => { - if (!viewRange) return 0 - const { min, max } = viewRange - const tick = min + (position / 100) * (max - min) - return nearestUsableTick(Math.round(tick), tickSpacing) - }, - [viewRange, tickSpacing], - ) - - // ============================================ // Initialize View Range - // ============================================ - useEffect(() => { if (isInitialized.current || !ticksReady) return @@ -164,10 +109,6 @@ function UniswapPriceSlider({ isInitialized.current = true }, [lowerTick, upperTick, currentTick, tickSpacing, ticksReady]) - // ============================================ - // Event Handlers - // ============================================ - const handleMouseDown = useCallback( (handle: 'lower' | 'upper') => (e: React.MouseEvent) => { e.preventDefault() @@ -294,10 +235,6 @@ function UniswapPriceSlider({ }, 50) }, [flushDebouncedValues, getTargetTicks, lowerTick, startSmoothZoom, tickSpacing, upperTick]) - // ============================================ - // Mouse Event Listeners - // ============================================ - useEffect(() => { if (!isDragging) return @@ -318,10 +255,6 @@ function UniswapPriceSlider({ } }, [isDragging, handleMouseMove, handleMouseUp]) - // ============================================ - // Render - // ============================================ - if (!ticksReady || !viewRange) { return } @@ -330,36 +263,54 @@ function UniswapPriceSlider({ const displayLowerTick = internalLowerTick ?? lowerTick const displayUpperTick = internalUpperTick ?? upperTick - const lowerPrice = tickToPrice(Math.round(displayLowerTick), token0Decimals, token1Decimals, invertPrice) - const upperPrice = tickToPrice(Math.round(displayUpperTick), token0Decimals, token1Decimals, invertPrice) + // Calculate prices (with invertPrice applied) + const lowerTickPrice = tickToPrice(Math.round(displayLowerTick), token0Decimals, token1Decimals, invertPrice) + const upperTickPrice = tickToPrice(Math.round(displayUpperTick), token0Decimals, token1Decimals, invertPrice) + // Calculate positions (flipped when invertPrice=true by the hook) const lowerPosition = getPositionFromTick(displayLowerTick) const upperPosition = getPositionFromTick(displayUpperTick) const currentPosition = getPositionFromTick(currentTick) + // When invertPrice, positions are flipped so: + // - lowerTick (higher inverted price) is on the RIGHT + // - upperTick (lower inverted price) is on the LEFT + // This means left position = min of the two, right position = max of the two + const leftPosition = Math.min(lowerPosition, upperPosition) + const rightPosition = Math.max(lowerPosition, upperPosition) + + // Determine which tick is at which visual position + const isLowerOnLeft = lowerPosition <= upperPosition + const leftPrice = isLowerOnLeft ? lowerTickPrice : upperTickPrice + const rightPrice = isLowerOnLeft ? upperTickPrice : lowerTickPrice + const leftHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'lower' : 'upper' + const rightHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'upper' : 'lower' + return ( - + - - {formatDisplayNumber(lowerPrice, { significantDigits: 6 })} + {/* Left handle (green) - always shows lower price visually */} + + {formatDisplayNumber(leftPrice, { significantDigits: 6 })} - - {formatDisplayNumber(upperPrice, { significantDigits: 6 })} + {/* Right handle (blue) - always shows higher price visually */} + + {formatDisplayNumber(rightPrice, { significantDigits: 6 })} - + - + diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts index bac088bcda..9d1b41e943 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts @@ -165,7 +165,7 @@ export const Handle = styled.div.attrs<{ $position: number }>(props => ({ style: { left: `${props.$position}%`, }, -}))<{ $position: number; $isLower: boolean }>` +}))<{ $position: number }>` position: absolute; top: 0; transform: translate(-50%, 1%); diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts index 2760a3975d..eb1682d9b4 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts @@ -17,45 +17,6 @@ export const brushHandlePath = (height: number): string => { ].join(' ') } -/** - * Calculate nice tick values for the price axis - * Returns an array of prices that are evenly spaced and use "nice" numbers - */ -export const calculatePriceAxisTicks = (minPrice: number, maxPrice: number, targetTickCount = 6): number[] => { - const range = maxPrice - minPrice - if (range <= 0) return [] - - // Calculate the rough step size - const roughStep = range / targetTickCount - - // Find the magnitude of the step - const magnitude = Math.pow(10, Math.floor(Math.log10(roughStep))) - - // Choose a nice step value (1, 2, 2.5, 5, 10 times the magnitude) - const niceSteps = [1, 2, 2.5, 5, 10] - let niceStep = magnitude - for (const step of niceSteps) { - if (step * magnitude >= roughStep) { - niceStep = step * magnitude - break - } - } - - // Calculate the start and end values - const start = Math.ceil(minPrice / niceStep) * niceStep - const ticks: number[] = [] - - for (let tick = start; tick <= maxPrice; tick += niceStep) { - // Avoid floating point precision issues - const roundedTick = Math.round(tick * 1e10) / 1e10 - if (roundedTick >= minPrice && roundedTick <= maxPrice) { - ticks.push(roundedTick) - } - } - - return ticks -} - /** * Format number with comma separators */ @@ -69,76 +30,69 @@ const formatWithCommas = (num: number, decimals = 0): string => { /** * Format price for axis display - user-friendly format * Shows more detail for smaller ranges, uses abbreviations for large numbers + * Note: Prices in Uniswap context are always positive */ export const formatAxisPrice = (price: number): string => { if (price === 0) return '0' if (!isFinite(price)) return '∞' - const absPrice = Math.abs(price) - const sign = price < 0 ? '-' : '' - // For astronomically large numbers, show a capped display - if (absPrice >= 1e18) { - return sign + '>999Q' - } + if (price >= 1e18) return '>999Q' // Quadrillions (10^15) - if (absPrice >= 1e15) { - const val = absPrice / 1e15 - return sign + (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'Q' + if (price >= 1e15) { + const val = price / 1e15 + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'Q' } // Trillions (10^12) - if (absPrice >= 1e12) { - const val = absPrice / 1e12 - return sign + (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'T' + if (price >= 1e12) { + const val = price / 1e12 + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'T' } // Billions (10^9) - if (absPrice >= 1e9) { - const val = absPrice / 1e9 - return sign + (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'B' + if (price >= 1e9) { + const val = price / 1e9 + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'B' } // Millions (10^6) - if (absPrice >= 1e6) { - const val = absPrice / 1e6 - return sign + (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'M' + if (price >= 1e6) { + const val = price / 1e6 + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'M' } // 100K - 999K: use K suffix - if (absPrice >= 100000) { - const val = absPrice / 1000 - return sign + Math.round(val) + 'K' + if (price >= 100000) { + return Math.round(price / 1000) + 'K' } // 10K - 99.9K: show as "12.5K" with more precision - if (absPrice >= 10000) { - const val = absPrice / 1000 - return sign + val.toFixed(1) + 'K' + if (price >= 10000) { + return (price / 1000).toFixed(1) + 'K' } // 1K - 9.9K: show full number with comma (like "2,500" or "3,750") - if (absPrice >= 1000) { + if (price >= 1000) { // Round to nearest 10 for cleaner display - const rounded = Math.round(absPrice / 10) * 10 - return sign + formatWithCommas(rounded) + return formatWithCommas(Math.round(price / 10) * 10) } // 100 - 999: show full number - if (absPrice >= 100) { - return sign + Math.round(absPrice).toString() + if (price >= 100) { + return Math.round(price).toString() } // 10 - 99.99: show with 1 decimal - if (absPrice >= 10) { + if (price >= 10) { return price.toFixed(1) } // 1 - 9.99: show with 2 decimals - if (absPrice >= 1) { + if (price >= 1) { return price.toFixed(2) } // Small decimals - if (absPrice >= 0.01) { + if (price >= 0.01) { return price.toFixed(4) } - if (absPrice >= 0.0001) { + if (price >= 0.0001) { return price.toFixed(5) } // For extremely small numbers, show a floor display - if (absPrice < 1e-8) { - return sign + '<0.00001' + if (price < 1e-8) { + return '<0.00001' } return price.toPrecision(3) } diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx index d0fa46e229..6cc5353dd3 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx @@ -304,7 +304,6 @@ const PriceInput = ({ [position.pool.tickSpacing, position.priceRange, position.token0.decimals, position.token1.decimals], ) - // Convert price to tick (called from input change) const priceToTick = useCallback( (price: string) => { if (!price) return undefined @@ -322,7 +321,7 @@ const PriceInput = ({ changeSourceRef.current = 'slider' setLowerTickState(tick) if (tick !== undefined) { - const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals) + const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals, false) setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) } // Reset source after React batch update @@ -338,7 +337,7 @@ const PriceInput = ({ changeSourceRef.current = 'slider' setUpperTickState(tick) if (tick !== undefined) { - const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals) + const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals, false) setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) } setTimeout(() => { From a8036f2eb92ec710965994bba9a35832ad8daa7a Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 15:57:16 +0700 Subject: [PATCH 53/87] update(price-slider): implement touch for mobile --- .../components/UniswapPriceSlider/index.tsx | 57 +++++++++++++++++-- .../components/UniswapPriceSlider/styles.ts | 1 + 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx index 930d868f5c..e80009e97a 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx @@ -117,12 +117,21 @@ function UniswapPriceSlider({ [], ) - const handleMouseMove = useCallback( - (e: MouseEvent) => { + const handleTouchStart = useCallback( + (handle: 'lower' | 'upper') => (e: React.TouchEvent) => { + e.preventDefault() + setIsDragging(handle) + }, + [], + ) + + // Shared logic for handling drag movement (mouse or touch) + const handleDragMove = useCallback( + (clientX: number) => { if (!isDragging || !sliderRef.current || !viewRange || lowerTick === undefined || upperTick === undefined) return const rect = sliderRef.current.getBoundingClientRect() - const x = e.clientX - rect.left + const x = clientX - rect.left const position = Math.max(0, Math.min(100, (x / rect.width) * 100)) const newTick = getTickFromPosition(position) @@ -175,6 +184,22 @@ function UniswapPriceSlider({ ], ) + const handleMouseMove = useCallback( + (e: MouseEvent) => { + handleDragMove(e.clientX) + }, + [handleDragMove], + ) + + const handleTouchMove = useCallback( + (e: TouchEvent) => { + if (e.touches.length > 0) { + handleDragMove(e.touches[0].clientX) + } + }, + [handleDragMove], + ) + const handleMouseUp = useCallback(() => { // Get the TARGET tick values (what user intended), not the animated values const { lowerTick: targetLower, upperTick: targetUpper } = getTargetTicks() @@ -242,18 +267,30 @@ function UniswapPriceSlider({ document.body.style.cursor = 'grabbing' document.body.style.userSelect = 'none' + // Mouse events document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mouseup', handleMouseUp) + // Touch events + document.addEventListener('touchmove', handleTouchMove, { passive: false }) + document.addEventListener('touchend', handleMouseUp) + document.addEventListener('touchcancel', handleMouseUp) + return () => { // Reset cursor when dragging ends document.body.style.cursor = '' document.body.style.userSelect = '' + // Remove mouse events document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) + + // Remove touch events + document.removeEventListener('touchmove', handleTouchMove) + document.removeEventListener('touchend', handleMouseUp) + document.removeEventListener('touchcancel', handleMouseUp) } - }, [isDragging, handleMouseMove, handleMouseUp]) + }, [isDragging, handleMouseMove, handleMouseUp, handleTouchMove]) if (!ticksReady || !viewRange) { return @@ -304,13 +341,21 @@ function UniswapPriceSlider({ {formatDisplayNumber(rightPrice, { significantDigits: 6 })} - + - + diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts index 9d1b41e943..7736960129 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts @@ -171,6 +171,7 @@ export const Handle = styled.div.attrs<{ $position: number }>(props => ({ transform: translate(-50%, 1%); cursor: grab; z-index: 10; + touch-action: none; /* Prevent scroll while dragging on touch devices */ &:active { cursor: grabbing; From 2077793c8080037dc439d3c39d9c39f836a0a60f Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 16:08:28 +0700 Subject: [PATCH 54/87] update(price-slider): add will-change --- .../src/components/UniswapPriceSlider/styles.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts index 7736960129..e656cee32c 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts @@ -155,6 +155,7 @@ export const SliderRange = styled.div.attrs<{ $left: number; $width: number }>(p background: linear-gradient(90deg, #31cb9e 0%, #7289da 100%); transform: translateY(-50%); border-radius: 2px; + will-change: left, width; ` // ============================================ @@ -172,6 +173,7 @@ export const Handle = styled.div.attrs<{ $position: number }>(props => ({ cursor: grab; z-index: 10; touch-action: none; /* Prevent scroll while dragging on touch devices */ + will-change: left; /* Hint GPU to optimize frequent updates */ &:active { cursor: grabbing; @@ -195,6 +197,7 @@ export const PriceLabel = styled.div.attrs<{ $position: number; $isLower: boolea font-weight: 500; white-space: nowrap; pointer-events: none; + will-change: left, transform; ` // ============================================ @@ -214,6 +217,7 @@ export const CurrentPriceMarker = styled.div.attrs<{ $position: number }>(props background: #888; border-radius: 2px; z-index: 5; + will-change: left; &::after { content: ''; @@ -260,6 +264,7 @@ export const PriceAxisTick = styled.div.attrs<{ $position: number }>(props => ({ width: 1px; height: 6px; background: #555; + will-change: left; ` export const PriceAxisLabel = styled.div.attrs<{ $position: number }>(props => ({ @@ -274,4 +279,5 @@ export const PriceAxisLabel = styled.div.attrs<{ $position: number }>(props => ( font-size: 10px; white-space: nowrap; user-select: none; + will-change: left; ` From 7382d03040ea2827efd0da02004ee38c18349897 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 16:19:02 +0700 Subject: [PATCH 55/87] update(price-slider): add ease-out instead of lerp for zoom --- .../components/UniswapPriceSlider/hooks.ts | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts index cb8f2d9390..5cd89370ca 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts @@ -4,13 +4,17 @@ import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } fr import type { ViewRange } from 'components/UniswapPriceSlider/types' const DEBOUNCE_DELAY = 150 // ms -const ZOOM_LERP_FACTOR = 0.08 // Smooth interpolation factor for zoom +const ZOOM_DURATION = 400 // ms - duration for zoom/auto-center animation const HANDLE_LERP_MIN = 0.15 // Min lerp factor when far from target const HANDLE_LERP_MAX = 0.4 // Max lerp factor when close to target const MAX_TICK_SPEED = 2000 // Maximum ticks per frame - increased for smoother tracking +// Easing function: ease-out cubic for smooth deceleration +const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3) + /** - * Hook for smooth zoom animation using requestAnimationFrame + * Hook for smooth zoom animation using easing function + * Uses ease-out for natural deceleration feel */ export const useSmoothZoom = ( viewRange: ViewRange | null, @@ -18,47 +22,57 @@ export const useSmoothZoom = ( ) => { const zoomAnimationRef = useRef(null) const targetViewRangeRef = useRef(null) + const startViewRangeRef = useRef(null) + const startTimeRef = useRef(0) const animateZoom = useCallback(() => { - if (!targetViewRangeRef.current || !viewRange) { + if (!targetViewRangeRef.current || !startViewRangeRef.current) { zoomAnimationRef.current = null return } - const target = targetViewRangeRef.current - - setViewRange(prev => { - if (!prev) return prev + const now = performance.now() + const elapsed = now - startTimeRef.current + const progress = Math.min(elapsed / ZOOM_DURATION, 1) + const easedProgress = easeOutCubic(progress) - const newMin = prev.min + (target.min - prev.min) * ZOOM_LERP_FACTOR - const newMax = prev.max + (target.max - prev.max) * ZOOM_LERP_FACTOR + const start = startViewRangeRef.current + const target = targetViewRangeRef.current - // Check if we're close enough to target - const minDiff = Math.abs(target.min - newMin) - const maxDiff = Math.abs(target.max - newMax) - const threshold = Math.abs(prev.max - prev.min) * 0.001 + const newMin = start.min + (target.min - start.min) * easedProgress + const newMax = start.max + (target.max - start.max) * easedProgress - if (minDiff < threshold && maxDiff < threshold) { - targetViewRangeRef.current = null - zoomAnimationRef.current = null - return target - } + setViewRange({ min: newMin, max: newMax }) + if (progress < 1) { // Continue animation zoomAnimationRef.current = requestAnimationFrame(animateZoom) - return { min: newMin, max: newMax } - }) - }, [viewRange, setViewRange]) + } else { + // Animation complete - set exact target values + setViewRange(target) + targetViewRangeRef.current = null + startViewRangeRef.current = null + zoomAnimationRef.current = null + } + }, [setViewRange]) const startSmoothZoom = useCallback( (targetMin: number, targetMax: number) => { + // If already animating, use current position as new start + if (zoomAnimationRef.current && viewRange) { + startViewRangeRef.current = viewRange + } else if (viewRange) { + startViewRangeRef.current = viewRange + } + targetViewRangeRef.current = { min: targetMin, max: targetMax } + startTimeRef.current = performance.now() if (!zoomAnimationRef.current) { zoomAnimationRef.current = requestAnimationFrame(animateZoom) } }, - [animateZoom], + [animateZoom, viewRange], ) // Cleanup animation on unmount From 53466d7b5618e9b48c0dba843ce90f8ccfe59515 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 19:01:34 +0700 Subject: [PATCH 56/87] update(price-slider): refactor --- .../UniswapPriceSlider/PriceAxis.tsx | 8 ++-- .../UniswapPriceSlider/Skeleton.tsx | 43 +++++++++++++++++++ .../UniswapPriceSlider/constants.ts | 33 ++++++++++++++ .../components/UniswapPriceSlider/hooks.ts | 15 ++++--- .../components/UniswapPriceSlider/index.tsx | 41 +----------------- 5 files changed, 89 insertions(+), 51 deletions(-) create mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/Skeleton.tsx create mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx index 89ea252075..93e0b52468 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx @@ -1,13 +1,11 @@ import { tickToPrice } from '@kyber/utils/dist/uniswapv3' import React, { useMemo } from 'react' +import { MAX_AXIS_TICK_COUNT, MIN_AXIS_TICK_COUNT } from 'components/UniswapPriceSlider/constants' import { PriceAxisContainer, PriceAxisLabel, PriceAxisLine, PriceAxisTick } from 'components/UniswapPriceSlider/styles' import type { PriceAxisProps } from 'components/UniswapPriceSlider/types' import { formatAxisPrice } from 'components/UniswapPriceSlider/utils' -const MAX_TICK_COUNT = 11 // More ticks for small ranges -const MIN_TICK_COUNT = 2 // Just first and last for extreme ranges - /** * Calculate the optimal number of ticks and minimum gap based on price range * More ticks for small ranges, fewer for large ranges @@ -23,7 +21,7 @@ const getOptimalTickConfig = (minPrice: number, maxPrice: number): { tickCount: // Very small range (< 0.5 orders): many ticks, small gap if (ordersOfMagnitude <= 0.5) { - return { tickCount: MAX_TICK_COUNT, minGapPercent: 12 } + return { tickCount: MAX_AXIS_TICK_COUNT, minGapPercent: 12 } } // Small range (0.5 - 1 order): good amount of ticks if (ordersOfMagnitude <= 1) { @@ -42,7 +40,7 @@ const getOptimalTickConfig = (minPrice: number, maxPrice: number): { tickCount: return { tickCount: 3, minGapPercent: 30 } } // Extreme range (> 8 orders): just first and last - return { tickCount: MIN_TICK_COUNT, minGapPercent: 40 } + return { tickCount: MIN_AXIS_TICK_COUNT, minGapPercent: 40 } } /** diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/Skeleton.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/Skeleton.tsx new file mode 100644 index 0000000000..69e2f260d5 --- /dev/null +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/Skeleton.tsx @@ -0,0 +1,43 @@ +import React from 'react' + +import { SKELETON_AXIS_POSITIONS } from 'components/UniswapPriceSlider/constants' +import { + SkeletonAxisContainer, + SkeletonAxisLabel, + SkeletonAxisLine, + SkeletonAxisTick, + SkeletonCurrentPrice, + SkeletonHandle, + SkeletonPriceLabel, + SkeletonRange, + SkeletonSliderArea, + SkeletonTrack, + SkeletonWrapper, +} from 'components/UniswapPriceSlider/styles' + +function PriceSliderSkeleton() { + return ( + + + + + + + + + + + + + {SKELETON_AXIS_POSITIONS.map(pos => ( + + + + + ))} + + + ) +} + +export default React.memo(PriceSliderSkeleton) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts new file mode 100644 index 0000000000..b7fe9a1d0d --- /dev/null +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts @@ -0,0 +1,33 @@ +// Animation Constants +/** Duration for zoom/auto-center animation in milliseconds */ +export const ZOOM_DURATION = 400 + +/** Delay before committing tick changes to parent in milliseconds */ +export const DEBOUNCE_DELAY = 150 + +/** Minimum LERP factor when handle is far from target (slower movement) */ +export const HANDLE_LERP_MIN = 0.15 + +/** Maximum LERP factor when handle is close to target (faster movement) */ +export const HANDLE_LERP_MAX = 0.4 + +/** Maximum ticks per frame to prevent jumpy handle movement */ +export const MAX_TICK_SPEED = 2000 + +// Slider Behavior Constants +/** Percentage from edge that triggers zoom out (ensures price labels visible) */ +export const EDGE_THRESHOLD = 18 + +/** Percentage padding on each side when auto-centering after drag */ +export const AUTO_CENTER_PADDING = 25 + +// Price Axis Constants +/** Maximum number of ticks on price axis for small ranges */ +export const MAX_AXIS_TICK_COUNT = 11 + +/** Minimum number of ticks on price axis for extreme ranges */ +export const MIN_AXIS_TICK_COUNT = 2 + +// Skeleton Constants +/** Positions for skeleton axis ticks (percentage) */ +export const SKELETON_AXIS_POSITIONS = [0, 16.6, 33.3, 50, 66.6, 83.3, 100] diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts index 5cd89370ca..6bd1a2b4fb 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts @@ -1,15 +1,16 @@ import { nearestUsableTick } from '@kyber/utils/dist/uniswapv3' import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react' +import { + DEBOUNCE_DELAY, + HANDLE_LERP_MAX, + HANDLE_LERP_MIN, + MAX_TICK_SPEED, + ZOOM_DURATION, +} from 'components/UniswapPriceSlider/constants' import type { ViewRange } from 'components/UniswapPriceSlider/types' -const DEBOUNCE_DELAY = 150 // ms -const ZOOM_DURATION = 400 // ms - duration for zoom/auto-center animation -const HANDLE_LERP_MIN = 0.15 // Min lerp factor when far from target -const HANDLE_LERP_MAX = 0.4 // Max lerp factor when close to target -const MAX_TICK_SPEED = 2000 // Maximum ticks per frame - increased for smoother tracking - -// Easing function: ease-out cubic for smooth deceleration +/** Easing function: ease-out cubic for smooth deceleration */ const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3) /** diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx index e80009e97a..3cf21f4e8f 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx @@ -2,22 +2,13 @@ import { MAX_TICK, MIN_TICK, tickToPrice } from '@kyber/utils/dist/uniswapv3' import React, { useCallback, useEffect, useRef, useState } from 'react' import PriceAxis from 'components/UniswapPriceSlider/PriceAxis' +import PriceSliderSkeleton from 'components/UniswapPriceSlider/Skeleton' +import { AUTO_CENTER_PADDING, EDGE_THRESHOLD } from 'components/UniswapPriceSlider/constants' import { useDebouncedTicks, useSmoothZoom, useTickPositionConverter } from 'components/UniswapPriceSlider/hooks' import { CurrentPriceMarker, Handle, PriceLabel, - SkeletonAxisContainer, - SkeletonAxisLabel, - SkeletonAxisLine, - SkeletonAxisTick, - SkeletonCurrentPrice, - SkeletonHandle, - SkeletonPriceLabel, - SkeletonRange, - SkeletonSliderArea, - SkeletonTrack, - SkeletonWrapper, SliderContainer, SliderRange, SliderTrack, @@ -27,34 +18,6 @@ import type { HandleType, UniswapPriceSliderProps, ViewRange } from 'components/ import { brushHandlePath, getEdgeIntensity } from 'components/UniswapPriceSlider/utils' import { formatDisplayNumber } from 'utils/numbers' -const SKELETON_AXIS_POSITIONS = [0, 16.6, 33.3, 50, 66.6, 83.3, 100] - -const PriceSliderSkeleton = () => ( - - - - - - - - - - - - - {SKELETON_AXIS_POSITIONS.map(pos => ( - - - - - ))} - - -) - -const EDGE_THRESHOLD = 18 // % from edge for zoom out (ensures price labels ~6 chars visible) -const AUTO_CENTER_PADDING = 25 // % padding on each side when auto-centering after drag - function UniswapPriceSlider({ pool, invertPrice, From 666304d2765f519994f45e86463382919266b11a Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 19:09:27 +0700 Subject: [PATCH 57/87] update(price-slider): refactor2 --- .../UniswapPriceSlider/PriceAxis.tsx | 4 +-- .../UniswapPriceSlider/constants.ts | 10 +++++++ .../components/UniswapPriceSlider/hooks.ts | 8 +++-- .../components/UniswapPriceSlider/index.tsx | 30 ++++++++++++------- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx index 93e0b52468..b3b2b90faa 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx @@ -141,11 +141,11 @@ function PriceAxis({ viewRange, token0Decimals, token1Decimals, invertPrice }: P return ( - {axisTicks.map(({ price, position, showLabel }, index) => { + {axisTicks.map(({ tick, price, position, showLabel }) => { // Only render if within visible range if (position < -2 || position > 102) return null return ( - + {showLabel && {formatAxisPrice(price)}} diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts index b7fe9a1d0d..2e194241e8 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts @@ -21,6 +21,16 @@ export const EDGE_THRESHOLD = 18 /** Percentage padding on each side when auto-centering after drag */ export const AUTO_CENTER_PADDING = 25 +/** Minimum tick spacings between handles to prevent overlap */ +export const MIN_HANDLE_DISTANCE_MULTIPLIER = 10 + +// Dynamic LERP Constants +/** Distance threshold (in ticks) for minimum lerp factor */ +export const LERP_FAR_THRESHOLD = 5000 + +/** Distance threshold (in ticks) for maximum lerp factor */ +export const LERP_CLOSE_THRESHOLD = 100 + // Price Axis Constants /** Maximum number of ticks on price axis for small ranges */ export const MAX_AXIS_TICK_COUNT = 11 diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts index 6bd1a2b4fb..e8a1ae7ac5 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts @@ -5,6 +5,8 @@ import { DEBOUNCE_DELAY, HANDLE_LERP_MAX, HANDLE_LERP_MIN, + LERP_CLOSE_THRESHOLD, + LERP_FAR_THRESHOLD, MAX_TICK_SPEED, ZOOM_DURATION, } from 'components/UniswapPriceSlider/constants' @@ -117,9 +119,9 @@ export const useDebouncedTicks = ( // Helper: calculate dynamic lerp factor based on distance const getDynamicLerp = useCallback((diff: number): number => { const absDiff = Math.abs(diff) - if (absDiff > 5000) return HANDLE_LERP_MIN - if (absDiff < 100) return HANDLE_LERP_MAX - const t = (absDiff - 100) / (5000 - 100) + if (absDiff > LERP_FAR_THRESHOLD) return HANDLE_LERP_MIN + if (absDiff < LERP_CLOSE_THRESHOLD) return HANDLE_LERP_MAX + const t = (absDiff - LERP_CLOSE_THRESHOLD) / (LERP_FAR_THRESHOLD - LERP_CLOSE_THRESHOLD) return HANDLE_LERP_MAX - t * (HANDLE_LERP_MAX - HANDLE_LERP_MIN) }, []) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx index 3cf21f4e8f..6c3c12f6cf 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx @@ -3,7 +3,11 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import PriceAxis from 'components/UniswapPriceSlider/PriceAxis' import PriceSliderSkeleton from 'components/UniswapPriceSlider/Skeleton' -import { AUTO_CENTER_PADDING, EDGE_THRESHOLD } from 'components/UniswapPriceSlider/constants' +import { + AUTO_CENTER_PADDING, + EDGE_THRESHOLD, + MIN_HANDLE_DISTANCE_MULTIPLIER, +} from 'components/UniswapPriceSlider/constants' import { useDebouncedTicks, useSmoothZoom, useTickPositionConverter } from 'components/UniswapPriceSlider/hooks' import { CurrentPriceMarker, @@ -123,12 +127,12 @@ function UniswapPriceSlider({ startSmoothZoom(targetMin, targetMax) } - // Update tick values + // Update tick values (with minimum distance between handles) if (isDragging === 'lower') { - const maxLower = (internalUpperTick ?? upperTick ?? 0) - tickSpacing * 10 + const maxLower = (internalUpperTick ?? upperTick ?? 0) - tickSpacing * MIN_HANDLE_DISTANCE_MULTIPLIER debouncedSetLowerTick(Math.min(newTick, maxLower)) } else { - const minUpper = (internalLowerTick ?? lowerTick ?? 0) + tickSpacing * 10 + const minUpper = (internalLowerTick ?? lowerTick ?? 0) + tickSpacing * MIN_HANDLE_DISTANCE_MULTIPLIER debouncedSetUpperTick(Math.max(newTick, minUpper)) } }, @@ -196,11 +200,17 @@ function UniswapPriceSlider({ // Calculate current positions using LATEST viewRange from ref const currentRange = currentViewRange.max - currentViewRange.min - const currentLowerPos = ((finalLowerTick - currentViewRange.min) / currentRange) * 100 - const currentUpperPos = ((finalUpperTick - currentViewRange.min) / currentRange) * 100 - const leftPadding = currentLowerPos - const rightPadding = 100 - currentUpperPos - const handleSpan = currentUpperPos - currentLowerPos // % of view that handles span + const rawLowerPos = ((finalLowerTick - currentViewRange.min) / currentRange) * 100 + const rawUpperPos = ((finalUpperTick - currentViewRange.min) / currentRange) * 100 + + // Account for invertPrice: when inverted, positions are flipped + const currentLowerPos = invertPrice ? 100 - rawLowerPos : rawLowerPos + const currentUpperPos = invertPrice ? 100 - rawUpperPos : rawUpperPos + + // Left/right padding based on visual positions (not tick order) + const leftPadding = Math.min(currentLowerPos, currentUpperPos) + const rightPadding = 100 - Math.max(currentLowerPos, currentUpperPos) + const handleSpan = Math.abs(currentUpperPos - currentLowerPos) // % of view that handles span // Ideal handle span is 50% (100 - 2 * AUTO_CENTER_PADDING) const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING @@ -221,7 +231,7 @@ function UniswapPriceSlider({ startSmoothZoom(targetMin, targetMax) } }, 50) - }, [flushDebouncedValues, getTargetTicks, lowerTick, startSmoothZoom, tickSpacing, upperTick]) + }, [flushDebouncedValues, getTargetTicks, invertPrice, lowerTick, startSmoothZoom, tickSpacing, upperTick]) useEffect(() => { if (!isDragging) return From 4b6616bee3064302412ce503b90c69eaadfc59df Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 19:16:59 +0700 Subject: [PATCH 58/87] fix(price-slider): adjust view range if props changed --- .../components/UniswapPriceSlider/index.tsx | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx index 6c3c12f6cf..e808f5b877 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx @@ -38,6 +38,7 @@ function UniswapPriceSlider({ const sliderRef = useRef(null) const isInitialized = useRef(false) const viewRangeRef = useRef(viewRange) + const lastAdjustedTicksRef = useRef<{ lower: number; upper: number } | null>(null) // Keep viewRangeRef in sync with viewRange state useEffect(() => { @@ -76,6 +77,45 @@ function UniswapPriceSlider({ isInitialized.current = true }, [lowerTick, upperTick, currentTick, tickSpacing, ticksReady]) + // Auto-adjust viewRange when ticks change from outside (e.g., input fields) + useEffect(() => { + // Skip if not initialized, dragging, or ticks not ready + if (!isInitialized.current || isDragging || !ticksReady || !viewRange) return + + // Skip if already adjusted for these exact tick values + const lastAdjusted = lastAdjustedTicksRef.current + if (lastAdjusted && lastAdjusted.lower === lowerTick && lastAdjusted.upper === upperTick) return + + const currentRange = viewRange.max - viewRange.min + const lowerPos = ((lowerTick - viewRange.min) / currentRange) * 100 + const upperPos = ((upperTick - viewRange.min) / currentRange) * 100 + + // Check if handles are outside visible area or positioned poorly + const handleOutsideLeft = lowerPos < -5 || upperPos < -5 + const handleOutsideRight = lowerPos > 105 || upperPos > 105 + const handleSpan = Math.abs(upperPos - lowerPos) + const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING + const handlesTooClose = handleSpan < idealHandleSpan * 0.4 // Much smaller than ideal + const handlesTooFar = handleSpan > idealHandleSpan * 2 // Much larger than ideal + + // If adjustment needed, calculate new viewRange + if (handleOutsideLeft || handleOutsideRight || handlesTooClose || handlesTooFar) { + const tickDistance = Math.abs(upperTick - lowerTick) + const handleCenter = (lowerTick + upperTick) / 2 + + const idealPadding = tickDistance * (AUTO_CENTER_PADDING / (100 - 2 * AUTO_CENTER_PADDING)) + const minPadding = Math.max(tickDistance * 0.3, tickSpacing * 20) + const padding = Math.max(idealPadding, minPadding) + + const targetMin = Math.max(MIN_TICK, handleCenter - tickDistance / 2 - padding) + const targetMax = Math.min(MAX_TICK, handleCenter + tickDistance / 2 + padding) + + // Mark these ticks as adjusted to prevent re-triggering + lastAdjustedTicksRef.current = { lower: lowerTick, upper: upperTick } + startSmoothZoom(targetMin, targetMax) + } + }, [lowerTick, upperTick, isDragging, ticksReady, viewRange, tickSpacing, startSmoothZoom]) + const handleMouseDown = useCallback( (handle: 'lower' | 'upper') => (e: React.MouseEvent) => { e.preventDefault() From ba5f4917f36f80df0f6ab4b34dddbb29b4362d3d Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 19:19:41 +0700 Subject: [PATCH 59/87] fix(price-slider): lower tick < upper tick --- .../src/components/UniswapPriceSlider/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx index e808f5b877..138baf0589 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx @@ -58,7 +58,7 @@ function UniswapPriceSlider({ const { getPositionFromTick, getTickFromPosition } = useTickPositionConverter(viewRange, tickSpacing, invertPrice) - const ticksReady = lowerTick !== undefined && upperTick !== undefined + const ticksReady = lowerTick !== undefined && upperTick !== undefined && upperTick > lowerTick // Initialize View Range useEffect(() => { From 6ee63250f0c976bdcc25b75a0a3f1698aa13b1c2 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 19:27:45 +0700 Subject: [PATCH 60/87] fix(price-slider): min handle tick --- .../src/components/UniswapPriceSlider/constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts index 2e194241e8..9e7b43c897 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts @@ -21,8 +21,8 @@ export const EDGE_THRESHOLD = 18 /** Percentage padding on each side when auto-centering after drag */ export const AUTO_CENTER_PADDING = 25 -/** Minimum tick spacings between handles to prevent overlap */ -export const MIN_HANDLE_DISTANCE_MULTIPLIER = 10 +/** Minimum tick spacings between handles to prevent overlap (keep small for large tickSpacing pools) */ +export const MIN_HANDLE_DISTANCE_MULTIPLIER = 1 // Dynamic LERP Constants /** Distance threshold (in ticks) for minimum lerp factor */ From e66b1c50b143392311011c9904940ff58c301145 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 19:42:00 +0700 Subject: [PATCH 61/87] fix(price-slider): display price label for current price --- .../components/UniswapPriceSlider/index.tsx | 8 +++- .../components/UniswapPriceSlider/styles.ts | 45 ++++++++++++++----- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx index 138baf0589..bd46348033 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx @@ -11,6 +11,8 @@ import { import { useDebouncedTicks, useSmoothZoom, useTickPositionConverter } from 'components/UniswapPriceSlider/hooks' import { CurrentPriceMarker, + CurrentPriceMarkerWrapper, + CurrentPriceTooltip, Handle, PriceLabel, SliderContainer, @@ -316,6 +318,7 @@ function UniswapPriceSlider({ // Calculate prices (with invertPrice applied) const lowerTickPrice = tickToPrice(Math.round(displayLowerTick), token0Decimals, token1Decimals, invertPrice) const upperTickPrice = tickToPrice(Math.round(displayUpperTick), token0Decimals, token1Decimals, invertPrice) + const currentPrice = tickToPrice(currentTick, token0Decimals, token1Decimals, invertPrice) // Calculate positions (flipped when invertPrice=true by the hook) const lowerPosition = getPositionFromTick(displayLowerTick) @@ -342,7 +345,10 @@ function UniswapPriceSlider({ - + + + {formatDisplayNumber(currentPrice, { significantDigits: 6 })} + {/* Left handle (green) - always shows lower price visually */} diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts index e656cee32c..be8a957f23 100644 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts +++ b/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts @@ -204,20 +204,12 @@ export const PriceLabel = styled.div.attrs<{ $position: number; $isLower: boolea // Current Price Marker // ============================================ -export const CurrentPriceMarker = styled.div.attrs<{ $position: number }>(props => ({ - style: { - left: `${props.$position}%`, - }, -}))<{ $position: number }>` - position: absolute; - top: 50%; - transform: translate(-50%, -50%); +export const CurrentPriceMarker = styled.div` width: 2px; height: 15px; background: #888; border-radius: 2px; - z-index: 5; - will-change: left; + cursor: pointer; &::after { content: ''; @@ -233,6 +225,39 @@ export const CurrentPriceMarker = styled.div.attrs<{ $position: number }>(props } ` +export const CurrentPriceTooltip = styled.div` + position: absolute; + top: -24px; + left: 50%; + transform: translateX(-50%); + white-space: nowrap; + font-size: 12px; + font-weight: 500; + color: #fff; + opacity: 0; + visibility: hidden; + transition: opacity 0.15s ease, visibility 0.15s ease; + pointer-events: none; + z-index: 100; +` + +export const CurrentPriceMarkerWrapper = styled.div.attrs<{ $position: number }>(props => ({ + style: { + left: `${props.$position}%`, + }, +}))<{ $position: number }>` + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + z-index: 5; + will-change: left; + + &:hover ${CurrentPriceTooltip} { + opacity: 1; + visibility: visible; + } +` + // ============================================ // Price Axis Styles // ============================================ From 5f4340df129d42baff9bdea43d6d66c215c09df3 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 22:46:56 +0700 Subject: [PATCH 62/87] update: migrate to package --- packages/price-slider/.eslintrc.cjs | 4 + packages/price-slider/.prettierrc.cjs | 1 + packages/price-slider/package.json | 57 +++ packages/price-slider/postcss.config.js | 19 + .../price-slider/src/components/PriceAxis.tsx | 179 ++++++++ .../price-slider/src/components/Skeleton.tsx | 84 ++++ .../src/components/UniswapPriceSlider.tsx | 416 ++++++++++++++++++ packages/price-slider/src/constants/index.ts | 43 ++ packages/price-slider/src/hooks/index.ts | 321 ++++++++++++++ packages/price-slider/src/index.ts | 36 ++ packages/price-slider/src/styles.css | 3 + packages/price-slider/src/types/index.ts | 46 ++ packages/price-slider/src/utils/index.ts | 128 ++++++ packages/price-slider/tailwind.config.ts | 10 + packages/price-slider/tsconfig.json | 14 + packages/price-slider/tsup.config.ts | 29 ++ 16 files changed, 1390 insertions(+) create mode 100644 packages/price-slider/.eslintrc.cjs create mode 100644 packages/price-slider/.prettierrc.cjs create mode 100644 packages/price-slider/package.json create mode 100644 packages/price-slider/postcss.config.js create mode 100644 packages/price-slider/src/components/PriceAxis.tsx create mode 100644 packages/price-slider/src/components/Skeleton.tsx create mode 100644 packages/price-slider/src/components/UniswapPriceSlider.tsx create mode 100644 packages/price-slider/src/constants/index.ts create mode 100644 packages/price-slider/src/hooks/index.ts create mode 100644 packages/price-slider/src/index.ts create mode 100644 packages/price-slider/src/styles.css create mode 100644 packages/price-slider/src/types/index.ts create mode 100644 packages/price-slider/src/utils/index.ts create mode 100644 packages/price-slider/tailwind.config.ts create mode 100644 packages/price-slider/tsconfig.json create mode 100644 packages/price-slider/tsup.config.ts diff --git a/packages/price-slider/.eslintrc.cjs b/packages/price-slider/.eslintrc.cjs new file mode 100644 index 0000000000..f934cce541 --- /dev/null +++ b/packages/price-slider/.eslintrc.cjs @@ -0,0 +1,4 @@ +/* eslint-env node */ +module.exports = { + extends: ['@kyber/eslint-config/index'], +}; diff --git a/packages/price-slider/.prettierrc.cjs b/packages/price-slider/.prettierrc.cjs new file mode 100644 index 0000000000..6e86a44df2 --- /dev/null +++ b/packages/price-slider/.prettierrc.cjs @@ -0,0 +1 @@ +module.exports = require('@kyber/eslint-config/prettier'); diff --git a/packages/price-slider/package.json b/packages/price-slider/package.json new file mode 100644 index 0000000000..aaacba39b4 --- /dev/null +++ b/packages/price-slider/package.json @@ -0,0 +1,57 @@ +{ + "name": "@kyberswap/price-slider", + "version": "1.0.0", + "license": "MIT", + "type": "module", + "exports": { + ".": { + "import": "./dist/price-slider.js", + "require": "./dist/price-slider.cjs" + }, + "./style.css": "./dist/price-slider.css" + }, + "main": "./dist/price-slider.cjs", + "module": "./dist/price-slider.js", + "types": "./dist/price-slider.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "lint": "eslint --ext .ts,.tsx src", + "format": "prettier --write src", + "prepublishOnly": "tsc && tsup", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "@kyber/ui": "workspace:^" + }, + "devDependencies": { + "@kyber/eslint-config": "workspace:*", + "@kyber/tailwind-config": "workspace:^", + "@kyber/typescript-config": "workspace:*", + "@kyber/utils": "workspace:^", + "@types/eslint": "^8.56.5", + "@types/node": "^20.11.24", + "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", + "autoprefixer": "^10.4.20", + "eslint": "^8.57.0", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.13", + "tsup": "^8.3.0", + "typescript": "5.3.2", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.4.0", + "prettier": "^3.5.3", + "@trivago/prettier-plugin-sort-imports": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + } +} diff --git a/packages/price-slider/postcss.config.js b/packages/price-slider/postcss.config.js new file mode 100644 index 0000000000..6b83666f1d --- /dev/null +++ b/packages/price-slider/postcss.config.js @@ -0,0 +1,19 @@ +const prefixOverrideList = ['html', 'body', "[role='portal']"]; + +export default { + plugins: { + 'postcss-import': {}, + 'tailwindcss/nesting': {}, + tailwindcss: { config: 'tailwind.config.ts' }, + 'postcss-prefix-selector': { + prefix: '.ks-ps-style', + transform(prefix, selector, prefixedSelector) { + if (prefixOverrideList.includes(selector)) { + return prefix; + } + return prefixedSelector; + }, + }, + autoprefixer: {}, + }, +}; diff --git a/packages/price-slider/src/components/PriceAxis.tsx b/packages/price-slider/src/components/PriceAxis.tsx new file mode 100644 index 0000000000..5771b982f5 --- /dev/null +++ b/packages/price-slider/src/components/PriceAxis.tsx @@ -0,0 +1,179 @@ +import React, { useMemo } from 'react'; + +import { tickToPrice } from '@kyber/utils/dist/uniswapv3'; + +import { MAX_AXIS_TICK_COUNT, MIN_AXIS_TICK_COUNT } from '@/constants'; +import type { PriceAxisProps } from '@/types'; +import { formatAxisPrice } from '@/utils'; + +/** + * Calculate the optimal number of ticks and minimum gap based on price range + * More ticks for small ranges, fewer for large ranges + */ +const getOptimalTickConfig = (minPrice: number, maxPrice: number): { tickCount: number; minGapPercent: number } => { + if (minPrice <= 0 || maxPrice <= 0) { + return { tickCount: 7, minGapPercent: 15 }; + } + + // Calculate how many orders of magnitude the prices span + const priceRatio = maxPrice / minPrice; + const ordersOfMagnitude = Math.log10(priceRatio); + + // Very small range (< 0.5 orders): many ticks, small gap + if (ordersOfMagnitude <= 0.5) { + return { tickCount: MAX_AXIS_TICK_COUNT, minGapPercent: 12 }; + } + // Small range (0.5 - 1 order): good amount of ticks + if (ordersOfMagnitude <= 1) { + return { tickCount: 9, minGapPercent: 14 }; + } + // Medium range (1 - 2 orders) + if (ordersOfMagnitude <= 2) { + return { tickCount: 7, minGapPercent: 16 }; + } + // Large range (2 - 4 orders) + if (ordersOfMagnitude <= 4) { + return { tickCount: 5, minGapPercent: 20 }; + } + // Very large range (4 - 8 orders) + if (ordersOfMagnitude <= 8) { + return { tickCount: 3, minGapPercent: 30 }; + } + // Extreme range (> 8 orders): just first and last + return { tickCount: MIN_AXIS_TICK_COUNT, minGapPercent: 40 }; +}; + +/** + * Calculate tick positions for the axis + * Uses tick-space for even distribution (matching the slider) + * When invertPrice, positions are flipped so lower inverted price is on left + */ +const calculateAxisTicks = ( + viewRange: { min: number; max: number }, + token0Decimals: number, + token1Decimals: number, + count: number, + invertPrice?: boolean, +): Array<{ tick: number; price: number; position: number }> => { + const tickRange = viewRange.max - viewRange.min; + if (tickRange <= 0) return []; + + const step = tickRange / (count - 1); + const ticks: Array<{ tick: number; price: number; position: number }> = []; + + for (let i = 0; i < count; i++) { + const tick = Math.round(viewRange.min + step * i); + const price = +tickToPrice(tick, token0Decimals, token1Decimals, invertPrice); + const normalPosition = ((tick - viewRange.min) / tickRange) * 100; + // When invertPrice, flip position so lower inverted price (from higher tick) is on left + const position = invertPrice ? 100 - normalPosition : normalPosition; + + ticks.push({ tick, price, position }); + } + + return ticks; +}; + +/** + * Filter ticks to ensure minimum spacing between labels + * Only shows labels that have sufficient gap from previous label + */ +const filterOverlappingTicks = ( + ticks: Array<{ tick: number; price: number; position: number }>, + minGapPercent: number, +): Array<{ tick: number; price: number; position: number; showLabel: boolean }> => { + if (ticks.length === 0) return []; + + const result: Array<{ tick: number; price: number; position: number; showLabel: boolean }> = []; + let lastLabelPosition = -Infinity; + + for (let i = 0; i < ticks.length; i++) { + const tick = ticks[i]; + const isFirst = i === 0; + const isLast = i === ticks.length - 1; + const gap = tick.position - lastLabelPosition; + + // First tick always shows label + if (isFirst) { + lastLabelPosition = tick.position; + result.push({ ...tick, showLabel: true }); + continue; + } + + // Last tick: only show if enough gap, otherwise hide + if (isLast) { + const showLabel = gap >= minGapPercent; + if (showLabel) lastLabelPosition = tick.position; + result.push({ ...tick, showLabel }); + continue; + } + + // Middle ticks: show if enough gap from previous label + const showLabel = gap >= minGapPercent; + if (showLabel) lastLabelPosition = tick.position; + result.push({ ...tick, showLabel }); + } + + return result; +}; + +/** + * Price axis component that displays price scale below the slider + * Uses tick-based positioning to match the slider exactly + * Dynamically reduces tick count when price range is very large + */ +function PriceAxis({ viewRange, token0Decimals, token1Decimals, invertPrice }: PriceAxisProps) { + const axisTicks = useMemo(() => { + // Get min and max prices to determine optimal tick config + const minPrice = +tickToPrice(Math.round(viewRange.min), token0Decimals, token1Decimals, invertPrice); + const maxPrice = +tickToPrice(Math.round(viewRange.max), token0Decimals, token1Decimals, invertPrice); + const { tickCount, minGapPercent } = getOptimalTickConfig( + Math.min(minPrice, maxPrice), + Math.max(minPrice, maxPrice), + ); + + const ticks = calculateAxisTicks(viewRange, token0Decimals, token1Decimals, tickCount, invertPrice); + // Sort by position ascending for proper overlap filtering + const sortedTicks = [...ticks].sort((a, b) => a.position - b.position); + return filterOverlappingTicks(sortedTicks, minGapPercent); + }, [viewRange, token0Decimals, token1Decimals, invertPrice]); + + return ( +
+ {/* Axis Line */} +
+ + {/* Ticks and Labels */} + {axisTicks.map(({ tick, price, position, showLabel }, index) => { + // Only render if within visible range + if (position < -2 || position > 102) return null; + + // Determine alignment: first label align left, last label align right, others center + const isFirst = index === 0; + const isLast = index === axisTicks.length - 1; + const alignClass = isFirst ? 'left-0' : isLast ? 'right-0' : '-translate-x-1/2'; + + return ( + + {/* Tick Mark */} +
+ {/* Label */} + {showLabel && ( +
+ {formatAxisPrice(price)} +
+ )} + + ); + })} +
+ ); +} + +export default React.memo(PriceAxis); diff --git a/packages/price-slider/src/components/Skeleton.tsx b/packages/price-slider/src/components/Skeleton.tsx new file mode 100644 index 0000000000..d842256fd6 --- /dev/null +++ b/packages/price-slider/src/components/Skeleton.tsx @@ -0,0 +1,84 @@ +import React from 'react'; + +import { Skeleton } from '@kyber/ui'; + +import { SKELETON_AXIS_POSITIONS } from '@/constants'; + +/** + * Skeleton loading state for the price slider + * Uses Skeleton component from @kyber/ui for shimmer animation + */ +function PriceSliderSkeleton() { + return ( +
+ {/* Slider Area */} +
+ {/* Track */} + + + {/* Range */} + + + {/* Current Price Marker */} + + + {/* Lower Price Label */} + + + {/* Upper Price Label */} + + + {/* Lower Handle */} + + + {/* Upper Handle */} + +
+ + {/* Axis Container */} +
+ {/* Axis Line */} + + + {/* Axis Ticks and Labels */} + {SKELETON_AXIS_POSITIONS.map((pos, index) => { + const isFirst = index === 0; + const isLast = index === SKELETON_AXIS_POSITIONS.length - 1; + const alignClass = isFirst ? 'left-0' : isLast ? 'right-0' : '-translate-x-1/2'; + + return ( + + {/* Tick */} + + {/* Label */} + + + ); + })} +
+
+ ); +} + +export default React.memo(PriceSliderSkeleton); diff --git a/packages/price-slider/src/components/UniswapPriceSlider.tsx b/packages/price-slider/src/components/UniswapPriceSlider.tsx new file mode 100644 index 0000000000..f0137ac4a7 --- /dev/null +++ b/packages/price-slider/src/components/UniswapPriceSlider.tsx @@ -0,0 +1,416 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; + +import { MAX_TICK, MIN_TICK, tickToPrice } from '@kyber/utils/dist/uniswapv3'; + +import PriceAxis from '@/components/PriceAxis'; +import PriceSliderSkeleton from '@/components/Skeleton'; +import { AUTO_CENTER_PADDING, EDGE_THRESHOLD, MIN_HANDLE_DISTANCE_MULTIPLIER } from '@/constants'; +import { useDebouncedTicks, useSmoothZoom, useTickPositionConverter } from '@/hooks'; +import type { HandleType, UniswapPriceSliderProps, ViewRange } from '@/types'; +import { brushHandlePath, formatDisplayNumber, getEdgeIntensity } from '@/utils'; + +function UniswapPriceSlider({ + pool, + invertPrice, + lowerTick, + upperTick, + setLowerTick, + setUpperTick, + className, +}: UniswapPriceSliderProps) { + const { tickSpacing, token0Decimals, token1Decimals, currentTick } = pool; + + const [viewRange, setViewRange] = useState(null); + const [isDragging, setIsDragging] = useState(null); + + const sliderRef = useRef(null); + const isInitialized = useRef(false); + const viewRangeRef = useRef(viewRange); + const lastAdjustedTicksRef = useRef<{ lower: number; upper: number } | null>(null); + + // Keep viewRangeRef in sync with viewRange state + useEffect(() => { + viewRangeRef.current = viewRange; + }, [viewRange]); + + const { startSmoothZoom } = useSmoothZoom(viewRange, setViewRange); + + const { + internalLowerTick, + internalUpperTick, + debouncedSetLowerTick, + debouncedSetUpperTick, + flushDebouncedValues, + getTargetTicks, + } = useDebouncedTicks(lowerTick, upperTick, setLowerTick, setUpperTick, isDragging !== null); + + const { getPositionFromTick, getTickFromPosition } = useTickPositionConverter(viewRange, tickSpacing, invertPrice); + + const ticksReady = lowerTick !== undefined && upperTick !== undefined && upperTick > lowerTick; + + // Initialize View Range + useEffect(() => { + if (isInitialized.current || !ticksReady) return; + + const tickRange = Math.abs(upperTick - lowerTick); + const padding = Math.max(tickRange * 0.5, tickSpacing * 50); + + const minTick = Math.min(lowerTick, upperTick, currentTick); + const maxTick = Math.max(lowerTick, upperTick, currentTick); + + setViewRange({ + min: Math.max(MIN_TICK, minTick - padding), + max: Math.min(MAX_TICK, maxTick + padding), + }); + isInitialized.current = true; + }, [lowerTick, upperTick, currentTick, tickSpacing, ticksReady]); + + // Auto-adjust viewRange when ticks change from outside (e.g., input fields) + useEffect(() => { + // Skip if not initialized, dragging, or ticks not ready + if (!isInitialized.current || isDragging || !ticksReady || !viewRange) return; + + // Skip if already adjusted for these exact tick values + const lastAdjusted = lastAdjustedTicksRef.current; + if (lastAdjusted && lastAdjusted.lower === lowerTick && lastAdjusted.upper === upperTick) return; + + const currentRange = viewRange.max - viewRange.min; + const lowerPos = ((lowerTick - viewRange.min) / currentRange) * 100; + const upperPos = ((upperTick - viewRange.min) / currentRange) * 100; + + // Check if handles are outside visible area or positioned poorly + const handleOutsideLeft = lowerPos < -5 || upperPos < -5; + const handleOutsideRight = lowerPos > 105 || upperPos > 105; + const handleSpan = Math.abs(upperPos - lowerPos); + const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING; + const handlesTooClose = handleSpan < idealHandleSpan * 0.4; // Much smaller than ideal + const handlesTooFar = handleSpan > idealHandleSpan * 2; // Much larger than ideal + + // If adjustment needed, calculate new viewRange + if (handleOutsideLeft || handleOutsideRight || handlesTooClose || handlesTooFar) { + const tickDistance = Math.abs(upperTick - lowerTick); + const handleCenter = (lowerTick + upperTick) / 2; + + const idealPadding = tickDistance * (AUTO_CENTER_PADDING / (100 - 2 * AUTO_CENTER_PADDING)); + const minPadding = Math.max(tickDistance * 0.3, tickSpacing * 20); + const padding = Math.max(idealPadding, minPadding); + + const targetMin = Math.max(MIN_TICK, handleCenter - tickDistance / 2 - padding); + const targetMax = Math.min(MAX_TICK, handleCenter + tickDistance / 2 + padding); + + // Mark these ticks as adjusted to prevent re-triggering + lastAdjustedTicksRef.current = { lower: lowerTick, upper: upperTick }; + startSmoothZoom(targetMin, targetMax); + } + }, [lowerTick, upperTick, isDragging, ticksReady, viewRange, tickSpacing, startSmoothZoom]); + + const handleMouseDown = useCallback( + (handle: 'lower' | 'upper') => (e: React.MouseEvent) => { + e.preventDefault(); + setIsDragging(handle); + }, + [], + ); + + const handleTouchStart = useCallback( + (handle: 'lower' | 'upper') => (e: React.TouchEvent) => { + e.preventDefault(); + setIsDragging(handle); + }, + [], + ); + + // Shared logic for handling drag movement (mouse or touch) + const handleDragMove = useCallback( + (clientX: number) => { + if (!isDragging || !sliderRef.current || !viewRange || lowerTick === undefined || upperTick === undefined) return; + + const rect = sliderRef.current.getBoundingClientRect(); + const x = clientX - rect.left; + const position = Math.max(0, Math.min(100, (x / rect.width) * 100)); + const newTick = getTickFromPosition(position); + + const currentRange = viewRange.max - viewRange.min; + + // Check if near edges for zoom out + const isNearLeftEdge = position < EDGE_THRESHOLD; + const isNearRightEdge = position > 100 - EDGE_THRESHOLD; + const edgeIntensity = getEdgeIntensity(position, EDGE_THRESHOLD); + + // Zoom out when near edges (zoom-in is handled by auto-center on mouse up) + if (isNearLeftEdge || isNearRightEdge) { + const baseExpansion = currentRange * 0.25; + const expansion = baseExpansion * edgeIntensity; + + let targetMin = viewRange.min; + let targetMax = viewRange.max; + + if (isNearLeftEdge && viewRange.min > MIN_TICK) { + targetMin = Math.max(MIN_TICK, viewRange.min - expansion); + } + if (isNearRightEdge && viewRange.max < MAX_TICK) { + targetMax = Math.min(MAX_TICK, viewRange.max + expansion); + } + + startSmoothZoom(targetMin, targetMax); + } + + // Update tick values (with minimum distance between handles) + if (isDragging === 'lower') { + const maxLower = (internalUpperTick ?? upperTick ?? 0) - tickSpacing * MIN_HANDLE_DISTANCE_MULTIPLIER; + debouncedSetLowerTick(Math.min(newTick, maxLower)); + } else { + const minUpper = (internalLowerTick ?? lowerTick ?? 0) + tickSpacing * MIN_HANDLE_DISTANCE_MULTIPLIER; + debouncedSetUpperTick(Math.max(newTick, minUpper)); + } + }, + [ + debouncedSetLowerTick, + debouncedSetUpperTick, + getTickFromPosition, + internalLowerTick, + internalUpperTick, + isDragging, + lowerTick, + startSmoothZoom, + tickSpacing, + upperTick, + viewRange, + ], + ); + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + handleDragMove(e.clientX); + }, + [handleDragMove], + ); + + const handleTouchMove = useCallback( + (e: TouchEvent) => { + if (e.touches.length > 0) { + handleDragMove(e.touches[0].clientX); + } + }, + [handleDragMove], + ); + + const handleMouseUp = useCallback(() => { + // Get the TARGET tick values (what user intended), not the animated values + const { lowerTick: targetLower, upperTick: targetUpper } = getTargetTicks(); + + // Flush to apply target values immediately + flushDebouncedValues(); + setIsDragging(null); + + // Use target ticks for auto-center calculation + const finalLowerTick = targetLower ?? lowerTick; + const finalUpperTick = targetUpper ?? upperTick; + + if (finalLowerTick === undefined || finalUpperTick === undefined) return; + + // Use setTimeout to ensure state has updated before calculating positions + setTimeout(() => { + // Use ref to get the LATEST viewRange (not stale closure value) + const currentViewRange = viewRangeRef.current; + if (!currentViewRange) return; + + const tickDistance = Math.abs(finalUpperTick - finalLowerTick); + const handleCenter = (finalLowerTick + finalUpperTick) / 2; + + // Calculate ideal padding (25% on each side = handles take up 50% of view) + const idealPadding = tickDistance * (AUTO_CENTER_PADDING / (100 - 2 * AUTO_CENTER_PADDING)); + const minPadding = Math.max(tickDistance * 0.3, tickSpacing * 20); + const padding = Math.max(idealPadding, minPadding); + + const targetMin = Math.max(MIN_TICK, handleCenter - tickDistance / 2 - padding); + const targetMax = Math.min(MAX_TICK, handleCenter + tickDistance / 2 + padding); + + // Calculate current positions using LATEST viewRange from ref + const currentRange = currentViewRange.max - currentViewRange.min; + const rawLowerPos = ((finalLowerTick - currentViewRange.min) / currentRange) * 100; + const rawUpperPos = ((finalUpperTick - currentViewRange.min) / currentRange) * 100; + + // Account for invertPrice: when inverted, positions are flipped + const currentLowerPos = invertPrice ? 100 - rawLowerPos : rawLowerPos; + const currentUpperPos = invertPrice ? 100 - rawUpperPos : rawUpperPos; + + // Left/right padding based on visual positions (not tick order) + const leftPadding = Math.min(currentLowerPos, currentUpperPos); + const rightPadding = 100 - Math.max(currentLowerPos, currentUpperPos); + const handleSpan = Math.abs(currentUpperPos - currentLowerPos); // % of view that handles span + + // Ideal handle span is 50% (100 - 2 * AUTO_CENTER_PADDING) + const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING; + const handlesTooClose = handleSpan < idealHandleSpan * 0.6; // Less than 60% of ideal = too zoomed out + const handlesTooFar = handleSpan > idealHandleSpan * 1.5; // More than 150% of ideal = too zoomed in + + // Check if rebalancing is needed + const needsRebalance = + leftPadding < EDGE_THRESHOLD + 5 || // Near left edge + rightPadding < EDGE_THRESHOLD + 5 || // Near right edge + leftPadding < 0 || // Handle outside left + rightPadding < 0 || // Handle outside right + handlesTooClose || // Handles too close together (need zoom in) + handlesTooFar || // Handles too far apart (need zoom out) + (leftPadding > 5 && rightPadding > 5 && (leftPadding / rightPadding > 2.5 || rightPadding / leftPadding > 2.5)); // Imbalanced + + if (needsRebalance) { + startSmoothZoom(targetMin, targetMax); + } + }, 50); + }, [flushDebouncedValues, getTargetTicks, invertPrice, lowerTick, startSmoothZoom, tickSpacing, upperTick]); + + useEffect(() => { + if (!isDragging) return; + + // Set grabbing cursor on body to persist while dragging outside handle + document.body.style.cursor = 'grabbing'; + document.body.style.userSelect = 'none'; + + // Mouse events + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + + // Touch events + document.addEventListener('touchmove', handleTouchMove, { passive: false }); + document.addEventListener('touchend', handleMouseUp); + document.addEventListener('touchcancel', handleMouseUp); + + return () => { + // Reset cursor when dragging ends + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + + // Remove mouse events + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + + // Remove touch events + document.removeEventListener('touchmove', handleTouchMove); + document.removeEventListener('touchend', handleMouseUp); + document.removeEventListener('touchcancel', handleMouseUp); + }; + }, [isDragging, handleMouseMove, handleMouseUp, handleTouchMove]); + + if (!ticksReady || !viewRange) { + return ; + } + + // Use internal ticks for smooth visual updates during dragging + const displayLowerTick = internalLowerTick ?? lowerTick; + const displayUpperTick = internalUpperTick ?? upperTick; + + // Calculate prices (with invertPrice applied) + const lowerTickPrice = tickToPrice(Math.round(displayLowerTick), token0Decimals, token1Decimals, invertPrice); + const upperTickPrice = tickToPrice(Math.round(displayUpperTick), token0Decimals, token1Decimals, invertPrice); + const currentPrice = tickToPrice(currentTick, token0Decimals, token1Decimals, invertPrice); + + // Calculate positions (flipped when invertPrice=true by the hook) + const lowerPosition = getPositionFromTick(displayLowerTick); + const upperPosition = getPositionFromTick(displayUpperTick); + const currentPosition = getPositionFromTick(currentTick); + + // When invertPrice, positions are flipped so: + // - lowerTick (higher inverted price) is on the RIGHT + // - upperTick (lower inverted price) is on the LEFT + // This means left position = min of the two, right position = max of the two + const leftPosition = Math.min(lowerPosition, upperPosition); + const rightPosition = Math.max(lowerPosition, upperPosition); + + // Determine which tick is at which visual position + const isLowerOnLeft = lowerPosition <= upperPosition; + const leftPrice = isLowerOnLeft ? lowerTickPrice : upperTickPrice; + const rightPrice = isLowerOnLeft ? upperTickPrice : lowerTickPrice; + const leftHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'lower' : 'upper'; + const rightHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'upper' : 'lower'; + + return ( +
+ {/* Slider Wrapper */} +
+ {/* Track */} +
+ + {/* Range */} +
+ + {/* Current Price Marker */} +
+
+ {/* Arrow indicator */} +
+
+ {/* Tooltip */} +
+ {formatDisplayNumber(currentPrice, { significantDigits: 6 })} +
+
+ + {/* Left Price Label */} +
+ {formatDisplayNumber(leftPrice, { significantDigits: 6 })} +
+ + {/* Right Price Label */} +
+ {formatDisplayNumber(rightPrice, { significantDigits: 6 })} +
+ + {/* Left Handle (green) */} +
+ + + +
+ + {/* Right Handle (blue) */} +
+ + + +
+
+ + +
+ ); +} + +export default UniswapPriceSlider; diff --git a/packages/price-slider/src/constants/index.ts b/packages/price-slider/src/constants/index.ts new file mode 100644 index 0000000000..6dd258b782 --- /dev/null +++ b/packages/price-slider/src/constants/index.ts @@ -0,0 +1,43 @@ +// Animation Constants +/** Duration for zoom/auto-center animation in milliseconds */ +export const ZOOM_DURATION = 400; + +/** Delay before committing tick changes to parent in milliseconds */ +export const DEBOUNCE_DELAY = 150; + +/** Minimum LERP factor when handle is far from target (slower movement) */ +export const HANDLE_LERP_MIN = 0.15; + +/** Maximum LERP factor when handle is close to target (faster movement) */ +export const HANDLE_LERP_MAX = 0.4; + +/** Maximum ticks per frame to prevent jumpy handle movement */ +export const MAX_TICK_SPEED = 2000; + +// Slider Behavior Constants +/** Percentage from edge that triggers zoom out (ensures price labels visible) */ +export const EDGE_THRESHOLD = 18; + +/** Percentage padding on each side when auto-centering after drag */ +export const AUTO_CENTER_PADDING = 25; + +/** Minimum tick spacings between handles to prevent overlap (keep small for large tickSpacing pools) */ +export const MIN_HANDLE_DISTANCE_MULTIPLIER = 1; + +// Dynamic LERP Constants +/** Distance threshold (in ticks) for minimum lerp factor */ +export const LERP_FAR_THRESHOLD = 5000; + +/** Distance threshold (in ticks) for maximum lerp factor */ +export const LERP_CLOSE_THRESHOLD = 100; + +// Price Axis Constants +/** Maximum number of ticks on price axis for small ranges */ +export const MAX_AXIS_TICK_COUNT = 11; + +/** Minimum number of ticks on price axis for extreme ranges */ +export const MIN_AXIS_TICK_COUNT = 2; + +// Skeleton Constants +/** Positions for skeleton axis ticks (percentage) */ +export const SKELETON_AXIS_POSITIONS = [0, 16.6, 33.3, 50, 66.6, 83.3, 100]; diff --git a/packages/price-slider/src/hooks/index.ts b/packages/price-slider/src/hooks/index.ts new file mode 100644 index 0000000000..ac320e413c --- /dev/null +++ b/packages/price-slider/src/hooks/index.ts @@ -0,0 +1,321 @@ +import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; + +import { nearestUsableTick } from '@kyber/utils/dist/uniswapv3'; + +import { + DEBOUNCE_DELAY, + HANDLE_LERP_MAX, + HANDLE_LERP_MIN, + LERP_CLOSE_THRESHOLD, + LERP_FAR_THRESHOLD, + MAX_TICK_SPEED, + ZOOM_DURATION, +} from '@/constants'; +import type { ViewRange } from '@/types'; + +/** Easing function: ease-out cubic for smooth deceleration */ +const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3); + +/** + * Hook for smooth zoom animation using easing function + * Uses ease-out for natural deceleration feel + */ +export const useSmoothZoom = ( + viewRange: ViewRange | null, + setViewRange: Dispatch>, +) => { + const zoomAnimationRef = useRef(null); + const targetViewRangeRef = useRef(null); + const startViewRangeRef = useRef(null); + const startTimeRef = useRef(0); + + const animateZoom = useCallback(() => { + if (!targetViewRangeRef.current || !startViewRangeRef.current) { + zoomAnimationRef.current = null; + return; + } + + const now = performance.now(); + const elapsed = now - startTimeRef.current; + const progress = Math.min(elapsed / ZOOM_DURATION, 1); + const easedProgress = easeOutCubic(progress); + + const start = startViewRangeRef.current; + const target = targetViewRangeRef.current; + + const newMin = start.min + (target.min - start.min) * easedProgress; + const newMax = start.max + (target.max - start.max) * easedProgress; + + setViewRange({ min: newMin, max: newMax }); + + if (progress < 1) { + // Continue animation + zoomAnimationRef.current = requestAnimationFrame(animateZoom); + } else { + // Animation complete - set exact target values + setViewRange(target); + targetViewRangeRef.current = null; + startViewRangeRef.current = null; + zoomAnimationRef.current = null; + } + }, [setViewRange]); + + const startSmoothZoom = useCallback( + (targetMin: number, targetMax: number) => { + // If already animating, use current position as new start + if (zoomAnimationRef.current && viewRange) { + startViewRangeRef.current = viewRange; + } else if (viewRange) { + startViewRangeRef.current = viewRange; + } + + targetViewRangeRef.current = { min: targetMin, max: targetMax }; + startTimeRef.current = performance.now(); + + if (!zoomAnimationRef.current) { + zoomAnimationRef.current = requestAnimationFrame(animateZoom); + } + }, + [animateZoom, viewRange], + ); + + // Cleanup animation on unmount + useEffect(() => { + return () => { + if (zoomAnimationRef.current) { + cancelAnimationFrame(zoomAnimationRef.current); + } + }; + }, []); + + return { startSmoothZoom }; +}; + +/** + * Hook for smooth tick updates with animation and debouncing + * Handles move slowly/smoothly towards target position + */ +export const useDebouncedTicks = ( + lowerTick: number | undefined, + upperTick: number | undefined, + setLowerTick: (tick: number) => void, + setUpperTick: (tick: number) => void, + isDragging: boolean, +) => { + const debounceTimerRef = useRef(null); + const animationRef = useRef(null); + + // Target ticks (where user wants to go) + const targetLowerTickRef = useRef(lowerTick); + const targetUpperTickRef = useRef(upperTick); + + // Current internal tick values (tracked via refs for animation loop) + const internalLowerRef = useRef(lowerTick); + const internalUpperRef = useRef(upperTick); + + // Internal tick state for React rendering + const [internalLowerTick, setInternalLowerTick] = useState(lowerTick); + const [internalUpperTick, setInternalUpperTick] = useState(upperTick); + + // Helper: calculate dynamic lerp factor based on distance + const getDynamicLerp = useCallback((diff: number): number => { + const absDiff = Math.abs(diff); + if (absDiff > LERP_FAR_THRESHOLD) return HANDLE_LERP_MIN; + if (absDiff < LERP_CLOSE_THRESHOLD) return HANDLE_LERP_MAX; + const t = (absDiff - LERP_CLOSE_THRESHOLD) / (LERP_FAR_THRESHOLD - LERP_CLOSE_THRESHOLD); + return HANDLE_LERP_MAX - t * (HANDLE_LERP_MAX - HANDLE_LERP_MIN); + }, []); + + // Animation function for smooth handle movement + const animateHandles = useCallback(() => { + let needsAnimation = false; + + // Update lower tick + if (internalLowerRef.current !== undefined && targetLowerTickRef.current !== undefined) { + const current = internalLowerRef.current; + const target = targetLowerTickRef.current; + const diff = target - current; + + if (Math.abs(diff) >= 1) { + needsAnimation = true; + const lerpFactor = getDynamicLerp(diff); + const lerpMovement = diff * lerpFactor; + const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED); + const newValue = current + cappedMovement; + internalLowerRef.current = newValue; + setInternalLowerTick(newValue); + } else if (current !== target) { + internalLowerRef.current = target; + setInternalLowerTick(target); + } + } + + // Update upper tick + if (internalUpperRef.current !== undefined && targetUpperTickRef.current !== undefined) { + const current = internalUpperRef.current; + const target = targetUpperTickRef.current; + const diff = target - current; + + if (Math.abs(diff) >= 1) { + needsAnimation = true; + const lerpFactor = getDynamicLerp(diff); + const lerpMovement = diff * lerpFactor; + const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED); + const newValue = current + cappedMovement; + internalUpperRef.current = newValue; + setInternalUpperTick(newValue); + } else if (current !== target) { + internalUpperRef.current = target; + setInternalUpperTick(target); + } + } + + if (needsAnimation) { + animationRef.current = requestAnimationFrame(animateHandles); + } else { + animationRef.current = null; + } + }, [getDynamicLerp]); + + // Start animation if not already running + const startAnimation = useCallback(() => { + if (!animationRef.current) { + animationRef.current = requestAnimationFrame(animateHandles); + } + }, [animateHandles]); + + // Sync internal state with props when not dragging + useEffect(() => { + if (!isDragging) { + targetLowerTickRef.current = lowerTick; + targetUpperTickRef.current = upperTick; + internalLowerRef.current = lowerTick; + internalUpperRef.current = upperTick; + setInternalLowerTick(lowerTick); + setInternalUpperTick(upperTick); + } + }, [lowerTick, upperTick, isDragging]); + + // Smooth update functions - set target and start animation + const debouncedSetLowerTick = useCallback( + (tick: number) => { + targetLowerTickRef.current = tick; + startAnimation(); + + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + debounceTimerRef.current = setTimeout(() => { + setLowerTick(tick); + }, DEBOUNCE_DELAY); + }, + [setLowerTick, startAnimation], + ); + + const debouncedSetUpperTick = useCallback( + (tick: number) => { + targetUpperTickRef.current = tick; + startAnimation(); + + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + debounceTimerRef.current = setTimeout(() => { + setUpperTick(tick); + }, DEBOUNCE_DELAY); + }, + [setUpperTick, startAnimation], + ); + + // Flush debounced values immediately + const flushDebouncedValues = useCallback(() => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + debounceTimerRef.current = null; + } + + // Set final values from targets + const finalLower = targetLowerTickRef.current; + const finalUpper = targetUpperTickRef.current; + + if (finalLower !== undefined && finalLower !== lowerTick) { + setLowerTick(finalLower); + internalLowerRef.current = finalLower; + setInternalLowerTick(finalLower); + } + if (finalUpper !== undefined && finalUpper !== upperTick) { + setUpperTick(finalUpper); + internalUpperRef.current = finalUpper; + setInternalUpperTick(finalUpper); + } + + // Stop animation + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + animationRef.current = null; + } + }, [lowerTick, upperTick, setLowerTick, setUpperTick]); + + // Cleanup on unmount + useEffect(() => { + return () => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + }; + }, []); + + // Get target ticks (what user actually wants, not the animated value) + const getTargetTicks = useCallback(() => { + return { + lowerTick: targetLowerTickRef.current, + upperTick: targetUpperTickRef.current, + }; + }, []); + + return { + internalLowerTick, + internalUpperTick, + debouncedSetLowerTick, + debouncedSetUpperTick, + flushDebouncedValues, + getTargetTicks, + }; +}; + +/** + * Hook for converting between tick and position + * When invertPrice = true, the entire visual is flipped: + * - Lower tick (higher inverted price) appears on the RIGHT + * - Upper tick (lower inverted price) appears on the LEFT + * - Axis shows inverted prices from low (left) to high (right) + */ +export const useTickPositionConverter = (viewRange: ViewRange | null, tickSpacing: number, invertPrice?: boolean) => { + const getPositionFromTick = useCallback( + (tick: number): number => { + if (!viewRange) return 50; + const { min, max } = viewRange; + const normalPosition = ((tick - min) / (max - min)) * 100; + // When invertPrice, flip the position so higher inverted price is on the right + return invertPrice ? 100 - normalPosition : normalPosition; + }, + [viewRange, invertPrice], + ); + + const getTickFromPosition = useCallback( + (position: number): number => { + if (!viewRange) return 0; + const { min, max } = viewRange; + // When invertPrice, flip the position first + const actualPosition = invertPrice ? 100 - position : position; + const tick = min + (actualPosition / 100) * (max - min); + return nearestUsableTick(Math.round(tick), tickSpacing); + }, + [viewRange, tickSpacing, invertPrice], + ); + + return { getPositionFromTick, getTickFromPosition }; +}; diff --git a/packages/price-slider/src/index.ts b/packages/price-slider/src/index.ts new file mode 100644 index 0000000000..4a6deda1b8 --- /dev/null +++ b/packages/price-slider/src/index.ts @@ -0,0 +1,36 @@ +// Import styles for CSS bundling +import '@/styles.css'; + +// Main component - export both default and named for flexibility +export { default } from '@/components/UniswapPriceSlider'; +export { default as UniswapPriceSlider } from '@/components/UniswapPriceSlider'; + +// Sub-components export +export { default as PriceAxis } from '@/components/PriceAxis'; +export { default as PriceSliderSkeleton } from '@/components/Skeleton'; + +// Types export +export type { HandleType, PoolInfo, PriceAxisProps, UniswapPriceSliderProps, ViewRange } from '@/types'; + +// Hooks export +export { useDebouncedTicks, useSmoothZoom, useTickPositionConverter } from '@/hooks'; + +// Utils export +export { brushHandlePath, formatAxisPrice, formatDisplayNumber, getEdgeIntensity } from '@/utils'; + +// Constants export +export { + AUTO_CENTER_PADDING, + DEBOUNCE_DELAY, + EDGE_THRESHOLD, + HANDLE_LERP_MAX, + HANDLE_LERP_MIN, + LERP_CLOSE_THRESHOLD, + LERP_FAR_THRESHOLD, + MAX_AXIS_TICK_COUNT, + MAX_TICK_SPEED, + MIN_AXIS_TICK_COUNT, + MIN_HANDLE_DISTANCE_MULTIPLIER, + SKELETON_AXIS_POSITIONS, + ZOOM_DURATION, +} from '@/constants'; diff --git a/packages/price-slider/src/styles.css b/packages/price-slider/src/styles.css new file mode 100644 index 0000000000..b5c61c9567 --- /dev/null +++ b/packages/price-slider/src/styles.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/price-slider/src/types/index.ts b/packages/price-slider/src/types/index.ts new file mode 100644 index 0000000000..ee183d8fe1 --- /dev/null +++ b/packages/price-slider/src/types/index.ts @@ -0,0 +1,46 @@ +/** + * View range representing the visible tick range on the slider + */ +export interface ViewRange { + min: number; + max: number; +} + +/** + * Pool information required for price calculations + */ +export interface PoolInfo { + tickSpacing: number; + token0Decimals: number; + token1Decimals: number; + currentTick: number; +} + +/** + * Props for the UniswapPriceSlider component + */ +export interface UniswapPriceSliderProps { + pool: PoolInfo; + invertPrice?: boolean; + lowerTick?: number; + upperTick?: number; + setLowerTick: (tick: number) => void; + setUpperTick: (tick: number) => void; + /** Optional class name for custom styling */ + className?: string; +} + +/** + * Props for the PriceAxis component + */ +export interface PriceAxisProps { + viewRange: ViewRange; + token0Decimals: number; + token1Decimals: number; + invertPrice?: boolean; +} + +/** + * Handle type for dragging state + */ +export type HandleType = 'lower' | 'upper' | null; diff --git a/packages/price-slider/src/utils/index.ts b/packages/price-slider/src/utils/index.ts new file mode 100644 index 0000000000..b178e0d54e --- /dev/null +++ b/packages/price-slider/src/utils/index.ts @@ -0,0 +1,128 @@ +/** + * Generate SVG path for brush handle (oval shape with rounded ends) + */ +export const brushHandlePath = (height: number): string => { + return [ + `M 0.5 0`, + `Q 0 0 0 1.5`, // Rounded top-left corner + `v 3.5`, + `C -5 5 -5 17 0 17`, // Oval + `v ${height - 19}`, + `Q 0 ${height} 0.5 ${height}`, // Rounded bottom-left corner + `Q 1 ${height} 1 ${height - 1.5}`, // Rounded bottom-right corner + `V 17`, + `C 6 17 6 5 1 5`, + `V 1.5`, + `Q 1 0 0.5 0`, // Rounded top-right corner + ].join(' '); +}; + +/** + * Format number with comma separators + */ +const formatWithCommas = (num: number, decimals = 0): string => { + const fixed = num.toFixed(decimals); + const parts = fixed.split('.'); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); + return parts.join('.'); +}; + +/** + * Format price for axis display - user-friendly format + * Shows more detail for smaller ranges, uses abbreviations for large numbers + * Note: Prices in Uniswap context are always positive + */ +export const formatAxisPrice = (price: number): string => { + if (price === 0) return '0'; + if (!isFinite(price)) return '∞'; + + // For astronomically large numbers, show a capped display + if (price >= 1e18) return '>999Q'; + // Quadrillions (10^15) + if (price >= 1e15) { + const val = price / 1e15; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'Q'; + } + // Trillions (10^12) + if (price >= 1e12) { + const val = price / 1e12; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'T'; + } + // Billions (10^9) + if (price >= 1e9) { + const val = price / 1e9; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'B'; + } + // Millions (10^6) + if (price >= 1e6) { + const val = price / 1e6; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'M'; + } + // 100K - 999K: use K suffix + if (price >= 100000) { + return Math.round(price / 1000) + 'K'; + } + // 10K - 99.9K: show as "12.5K" with more precision + if (price >= 10000) { + return (price / 1000).toFixed(1) + 'K'; + } + // 1K - 9.9K: show full number with comma (like "2,500" or "3,750") + if (price >= 1000) { + // Round to nearest 10 for cleaner display + return formatWithCommas(Math.round(price / 10) * 10); + } + // 100 - 999: show full number + if (price >= 100) { + return Math.round(price).toString(); + } + // 10 - 99.99: show with 1 decimal + if (price >= 10) { + return price.toFixed(1); + } + // 1 - 9.99: show with 2 decimals + if (price >= 1) { + return price.toFixed(2); + } + // Small decimals + if (price >= 0.01) { + return price.toFixed(4); + } + if (price >= 0.0001) { + return price.toFixed(5); + } + // For extremely small numbers, show a floor display + if (price < 1e-8) { + return '<0.00001'; + } + return price.toPrecision(3); +}; + +/** + * Calculate edge intensity for zoom behavior + * Returns 0-1 based on how close position is to edge + */ +export const getEdgeIntensity = (position: number, edgeThreshold: number): number => { + if (position < edgeThreshold) { + return 1 - position / edgeThreshold; + } + if (position > 100 - edgeThreshold) { + return (position - (100 - edgeThreshold)) / edgeThreshold; + } + return 0; +}; + +/** + * Format display number with significant digits + */ +export const formatDisplayNumber = (value: number | string, options?: { significantDigits?: number }): string => { + const num = typeof value === 'string' ? parseFloat(value) : value; + if (isNaN(num) || !isFinite(num)) return '0'; + + const significantDigits = options?.significantDigits ?? 6; + + if (num === 0) return '0'; + if (Math.abs(num) < 1e-10) return '<0.0000001'; + if (Math.abs(num) >= 1e15) return num.toExponential(2); + + return num.toPrecision(significantDigits).replace(/\.?0+$/, ''); +}; diff --git a/packages/price-slider/tailwind.config.ts b/packages/price-slider/tailwind.config.ts new file mode 100644 index 0000000000..92425727e9 --- /dev/null +++ b/packages/price-slider/tailwind.config.ts @@ -0,0 +1,10 @@ +import type { Config } from 'tailwindcss'; + +import sharedConfig from '@kyber/tailwind-config'; + +const config: Pick = { + content: ['./src/**/*.tsx'], + presets: [sharedConfig], +}; + +export default config; diff --git a/packages/price-slider/tsconfig.json b/packages/price-slider/tsconfig.json new file mode 100644 index 0000000000..052970bf20 --- /dev/null +++ b/packages/price-slider/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@kyber/typescript-config/react-library.json", + "compilerOptions": { + "target": "ES2020", + "outDir": "dist", + "jsx": "react-jsx", + "paths": { + "@/*": ["./src/*"], + "react": ["./node_modules/@types/react"] + } + }, + "include": ["src", ".eslintrc.cjs"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/price-slider/tsup.config.ts b/packages/price-slider/tsup.config.ts new file mode 100644 index 0000000000..a43cf2302b --- /dev/null +++ b/packages/price-slider/tsup.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { 'price-slider': 'src/index.ts' }, + format: ['esm', 'cjs'], + outDir: 'dist', + target: 'esnext', + clean: true, + dts: true, + minify: false, + sourcemap: true, + onSuccess: 'tsc --noEmit', + external: ['react', 'react-dom'], + noExternal: ['@kyber/utils', '@kyber/eslint-config', '@kyber/tailwind-config'], + esbuildOptions(options) { + options.globalName = 'PriceSlider'; + options.define = { + global: 'globalThis', + }; + options.supported = { + bigint: true, + }; + }, + banner: { + js: ` + // eslint-disable + `, + }, +}); From 049558e9f36a581696ce4c9f2824b90243e69cb3 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 22:47:39 +0700 Subject: [PATCH 63/87] fix: using package --- apps/kyberswap-interface/package.json | 1 + .../Earns/components/SmartExit/Metrics.tsx | 3 +- pnpm-lock.yaml | 2231 ++++++++++------- 3 files changed, 1353 insertions(+), 882 deletions(-) diff --git a/apps/kyberswap-interface/package.json b/apps/kyberswap-interface/package.json index 498bfc3208..9efd72687f 100644 --- a/apps/kyberswap-interface/package.json +++ b/apps/kyberswap-interface/package.json @@ -60,6 +60,7 @@ "@kyberswap/ks-sdk-core": "1.1.14", "@kyberswap/ks-sdk-elastic": "^1.1.2", "@kyberswap/liquidity-chart": "workspace:*", + "@kyberswap/price-slider": "workspace:*", "@kyberswap/liquidity-widgets": "workspace:*", "@kyberswap/oauth2": "1.0.2", "@kyberswap/zap-migration-widgets": "workspace:*", diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx index 6cc5353dd3..4abb293f76 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx @@ -1,5 +1,7 @@ import { Label, RadioGroup, RadioGroupItem } from '@kyber/ui' import { nearestUsableTick, priceToClosestTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' +import UniswapPriceSlider from '@kyberswap/price-slider' +import '@kyberswap/price-slider/style.css' import { Trans, t } from '@lingui/macro' import dayjs from 'dayjs' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' @@ -7,7 +9,6 @@ import { Calendar } from 'react-feather' import { Box, Flex, Text } from 'rebass' import Divider from 'components/Divider' -import UniswapPriceSlider from 'components/UniswapPriceSlider' import DateTimePicker from 'components/swapv2/LimitOrder/ExpirePicker' import useTheme from 'hooks/useTheme' import { DEFAULT_TIME_OPTIONS } from 'pages/Earns/components/SmartExit/ExpireSetting' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1979a8fa0..6ed4ddb7b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,7 +40,7 @@ importers: version: 0.1.2 '@esbuild-plugins/node-globals-polyfill': specifier: ^0.2.3 - version: 0.2.3(esbuild@0.27.0) + version: 0.2.3(esbuild@0.24.0) '@ethersproject/abi': specifier: ^5.8.0 version: 5.8.0 @@ -98,6 +98,9 @@ importers: '@kyberswap/oauth2': specifier: 1.0.2 version: 1.0.2 + '@kyberswap/price-slider': + specifier: workspace:* + version: link:../../packages/price-slider '@kyberswap/zap-migration-widgets': specifier: workspace:* version: link:../../packages/zap-migration-widgets @@ -725,7 +728,7 @@ importers: dependencies: '@esbuild-plugins/node-globals-polyfill': specifier: ^0.1.1 - version: 0.1.1(esbuild@0.27.0) + version: 0.1.1(esbuild@0.24.0) '@kyberswap/widgets': specifier: workspace:* version: link:../../packages/swap-widgets @@ -941,7 +944,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -989,10 +992,10 @@ importers: devDependencies: '@vercel/style-guide': specifier: ^5.2.0 - version: 5.2.0(eslint@8.57.1)(prettier@3.5.3)(typescript@5.9.3) + version: 5.2.0(eslint@8.57.0)(prettier@3.5.3)(typescript@5.1.6) eslint-config-turbo: specifier: ^1.12.4 - version: 1.13.4(eslint@8.57.1) + version: 1.13.4(eslint@8.57.0) packages/config-tailwind: devDependencies: @@ -1134,7 +1137,7 @@ importers: version: 10.4.20(postcss@8.4.49) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -1243,7 +1246,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -1343,7 +1346,7 @@ importers: version: 10.4.20(postcss@8.4.49) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -1372,6 +1375,82 @@ importers: specifier: 5.3.2 version: 5.3.2 + packages/price-slider: + dependencies: + '@kyber/ui': + specifier: workspace:^ + version: link:../ui + react: + specifier: '>=16.8.0' + version: 18.3.1 + devDependencies: + '@kyber/eslint-config': + specifier: workspace:* + version: link:../config-eslint + '@kyber/tailwind-config': + specifier: workspace:^ + version: link:../config-tailwind + '@kyber/typescript-config': + specifier: workspace:* + version: link:../config-typescript + '@kyber/utils': + specifier: workspace:^ + version: link:../utils + '@trivago/prettier-plugin-sort-imports': + specifier: ^3.3.1 + version: 3.4.0(prettier@3.5.3) + '@types/eslint': + specifier: ^8.56.5 + version: 8.56.12 + '@types/node': + specifier: ^20.11.24 + version: 20.19.25 + '@types/react': + specifier: ^18.0.17 + version: 18.3.12 + '@types/react-dom': + specifier: ^18.0.6 + version: 18.3.1 + '@typescript-eslint/eslint-plugin': + specifier: ^6.14.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.3.2) + '@typescript-eslint/parser': + specifier: ^6.14.0 + version: 6.21.0(eslint@8.57.0)(typescript@5.3.2) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.4.49) + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.0) + eslint-plugin-prettier: + specifier: ^5.4.0 + version: 5.4.0(@types/eslint@8.56.12)(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.5.3) + eslint-plugin-react: + specifier: ^7.33.2 + version: 7.37.2(eslint@8.57.0) + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.2(eslint@8.57.0) + postcss: + specifier: ^8.4.47 + version: 8.4.49 + prettier: + specifier: ^3.5.3 + version: 3.5.3 + tailwindcss: + specifier: ^3.4.13 + version: 3.4.14 + tsup: + specifier: ^8.3.0 + version: 8.3.5(postcss@8.4.49)(typescript@5.3.2) + typescript: + specifier: 5.3.2 + version: 5.3.2 + packages/schema: dependencies: react: @@ -1871,7 +1950,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -1995,7 +2074,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -2150,7 +2229,7 @@ packages: '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 convert-source-map: 1.9.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -2195,7 +2274,7 @@ packages: '@babel/types': 7.28.5 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -2216,20 +2295,6 @@ packages: semver: 6.3.1 dev: true - /@babel/eslint-parser@7.25.9(@babel/core@7.26.0)(eslint@8.57.1): - resolution: {integrity: sha512-5UXfgpK0j0Xr/xIdgdLEhOFxaDZ0bRPWJJchRpqOSur/3rZoPbqqki5mm0p4NE2cs28krBEiSM2MB7//afRSQQ==} - engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} - peerDependencies: - '@babel/core': ^7.11.0 - eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 - dependencies: - '@babel/core': 7.26.0 - '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 - eslint: 8.57.1 - eslint-visitor-keys: 2.1.0 - semver: 6.3.1 - dev: true - /@babel/generator@7.17.7: resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==} engines: {node: '>=6.9.0'} @@ -2276,8 +2341,8 @@ packages: resolution: {integrity: sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9(supports-color@5.5.0) - '@babel/types': 7.26.0 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2320,6 +2385,24 @@ packages: - supports-color dev: true + /@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.28.5) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} engines: {node: '>=6.9.0'} @@ -2350,6 +2433,18 @@ packages: semver: 6.3.1 dev: true + /@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + regexpu-core: 6.1.1 + semver: 6.3.1 + dev: true + /@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} engines: {node: '>=6.9.0'} @@ -2368,11 +2463,26 @@ packages: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 dependencies: '@babel/core': 7.26.0 - '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.25.9 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) lodash.debounce: 4.0.8 - resolve: 1.22.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.28.5): + resolution: {integrity: sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.25.9 + debug: 4.4.3(supports-color@5.5.0) + lodash.debounce: 4.0.8 + resolve: 1.22.11 transitivePeerDependencies: - supports-color dev: true @@ -2385,7 +2495,7 @@ packages: '@babel/core': 7.26.0 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.11 transitivePeerDependencies: @@ -2422,8 +2532,8 @@ packages: resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9(supports-color@5.5.0) - '@babel/types': 7.26.0 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2464,8 +2574,8 @@ packages: dependencies: '@babel/core': 7.17.8 '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) - '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2483,6 +2593,20 @@ packages: transitivePeerDependencies: - supports-color + /@babel/helper-module-transforms@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-module-transforms@7.28.3(@babel/core@7.26.0): resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} @@ -2514,7 +2638,7 @@ packages: resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.26.0 + '@babel/types': 7.28.5 dev: true /@babel/helper-optimise-call-expression@7.27.1: @@ -2541,7 +2665,21 @@ packages: '@babel/core': 7.26.0 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-wrap-function': 7.25.9 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2569,7 +2707,21 @@ packages: '@babel/core': 7.26.0 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-replace-supers@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2653,9 +2805,9 @@ packages: resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) - '@babel/types': 7.26.0 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2715,7 +2867,20 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2743,6 +2908,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} engines: {node: '>=6.9.0'} @@ -2763,6 +2938,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} engines: {node: '>=6.9.0'} @@ -2787,6 +2972,20 @@ packages: - supports-color dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} engines: {node: '>=6.9.0'} @@ -2809,7 +3008,20 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2917,6 +3129,15 @@ packages: '@babel/core': 7.26.0 dev: true + /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5): + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + dev: true + /@babel/plugin-proposal-private-property-in-object@7.21.11(@babel/core@7.26.0): resolution: {integrity: sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==} engines: {node: '>=6.9.0'} @@ -3027,6 +3248,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-flow@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.5): resolution: {integrity: sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==} engines: {node: '>=6.9.0'} @@ -3047,6 +3278,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==} engines: {node: '>=6.9.0'} @@ -3067,6 +3308,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} engines: {node: '>=6.9.0'} @@ -3139,7 +3390,6 @@ packages: dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.25.9 - dev: false /@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5): resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} @@ -3305,6 +3555,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0): resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} @@ -3316,6 +3576,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.5): + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0): resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} engines: {node: '>=6.9.0'} @@ -3326,6 +3597,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} engines: {node: '>=6.9.0'} @@ -3345,7 +3626,21 @@ packages: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.28.5) + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -3378,6 +3673,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==} engines: {node: '>=6.9.0'} @@ -3402,6 +3711,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-block-scoped-functions@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} engines: {node: '>=6.9.0'} @@ -3422,6 +3741,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==} engines: {node: '>=6.9.0'} @@ -3445,6 +3774,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==} engines: {node: '>=6.9.0'} @@ -3471,6 +3813,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.26.0): resolution: {integrity: sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==} engines: {node: '>=6.9.0'} @@ -3495,7 +3850,24 @@ packages: '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/traverse': 7.28.5 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-classes@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.28.5) + '@babel/traverse': 7.28.5 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3529,6 +3901,17 @@ packages: '@babel/template': 7.25.9 dev: true + /@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/template': 7.25.9 + dev: true + /@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==} engines: {node: '>=6.9.0'} @@ -3550,6 +3933,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} engines: {node: '>=6.9.0'} @@ -3574,6 +3967,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==} engines: {node: '>=6.9.0'} @@ -3595,6 +3999,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} engines: {node: '>=6.9.0'} @@ -3616,6 +4030,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==} engines: {node: '>=6.9.0'} @@ -3637,6 +4062,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} engines: {node: '>=6.9.0'} @@ -3673,6 +4108,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-exponentiation-operator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-exponentiation-operator@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==} engines: {node: '>=6.9.0'} @@ -3693,6 +4141,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} engines: {node: '>=6.9.0'} @@ -3714,6 +4172,17 @@ packages: '@babel/plugin-syntax-flow': 7.26.0(@babel/core@7.26.0) dev: true + /@babel/plugin-transform-flow-strip-types@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-/VVukELzPDdci7UUsWQaSkhgnjIWXnIyRpM02ldxaVoFK96c41So8JcKT3m0gYjyv7j5FNPGS5vfELrWalkbDA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-flow': 7.26.0(@babel/core@7.28.5) + dev: true + /@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.0): resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==} engines: {node: '>=6.9.0'} @@ -3727,6 +4196,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-for-of@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-for-of@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} engines: {node: '>=6.9.0'} @@ -3749,7 +4231,21 @@ packages: '@babel/core': 7.26.0 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-function-name@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -3778,6 +4274,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==} engines: {node: '>=6.9.0'} @@ -3798,6 +4304,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} engines: {node: '>=6.9.0'} @@ -3818,6 +4334,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==} engines: {node: '>=6.9.0'} @@ -3838,6 +4364,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} engines: {node: '>=6.9.0'} @@ -3861,6 +4397,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} engines: {node: '>=6.9.0'} @@ -3888,6 +4437,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-simple-access': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} engines: {node: '>=6.9.0'} @@ -3911,7 +4474,22 @@ packages: '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -3944,6 +4522,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} engines: {node: '>=6.9.0'} @@ -3968,6 +4559,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} engines: {node: '>=6.9.0'} @@ -3989,6 +4591,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-new-target@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-new-target@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} engines: {node: '>=6.9.0'} @@ -4009,6 +4621,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==} engines: {node: '>=6.9.0'} @@ -4029,6 +4651,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==} engines: {node: '>=6.9.0'} @@ -4051,6 +4683,18 @@ packages: '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) dev: true + /@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.28.5) + dev: true + /@babel/plugin-transform-object-rest-spread@7.28.4(@babel/core@7.26.0): resolution: {integrity: sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==} engines: {node: '>=6.9.0'} @@ -4080,6 +4724,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-object-super@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-object-super@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} engines: {node: '>=6.9.0'} @@ -4103,6 +4760,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==} engines: {node: '>=6.9.0'} @@ -4126,6 +4793,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==} engines: {node: '>=6.9.0'} @@ -4149,6 +4829,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-parameters@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-parameters@7.27.7(@babel/core@7.26.0): resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} engines: {node: '>=6.9.0'} @@ -4172,6 +4862,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==} engines: {node: '>=6.9.0'} @@ -4199,6 +4902,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==} engines: {node: '>=6.9.0'} @@ -4223,6 +4940,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} engines: {node: '>=6.9.0'} @@ -4339,6 +5066,17 @@ packages: regenerator-transform: 0.15.2 dev: true + /@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + regenerator-transform: 0.15.2 + dev: true + /@babel/plugin-transform-regenerator@7.28.4(@babel/core@7.26.0): resolution: {integrity: sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==} engines: {node: '>=6.9.0'} @@ -4360,6 +5098,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==} engines: {node: '>=6.9.0'} @@ -4381,6 +5130,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} engines: {node: '>=6.9.0'} @@ -4418,6 +5177,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} engines: {node: '>=6.9.0'} @@ -4441,6 +5210,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-spread@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-spread@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==} engines: {node: '>=6.9.0'} @@ -4464,6 +5246,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} engines: {node: '>=6.9.0'} @@ -4484,6 +5276,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} engines: {node: '>=6.9.0'} @@ -4504,6 +5306,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} engines: {node: '>=6.9.0'} @@ -4530,6 +5342,22 @@ packages: - supports-color dev: true + /@babel/plugin-transform-typescript@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.0): resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} engines: {node: '>=6.9.0'} @@ -4540,6 +5368,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} engines: {node: '>=6.9.0'} @@ -4561,6 +5399,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==} engines: {node: '>=6.9.0'} @@ -4583,6 +5432,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} engines: {node: '>=6.9.0'} @@ -4605,6 +5465,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==} engines: {node: '>=6.9.0'} @@ -4696,6 +5567,86 @@ packages: - supports-color dev: true + /@babel/preset-env@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.5) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-block-scoped-functions': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-exponentiation-operator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.28.5) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.5) + babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.28.5) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.28.5) + babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.28.5) + core-js-compat: 3.39.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/preset-env@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==} engines: {node: '>=6.9.0'} @@ -4777,16 +5728,16 @@ packages: - supports-color dev: true - /@babel/preset-flow@7.25.9(@babel/core@7.26.0): + /@babel/preset-flow@7.25.9(@babel/core@7.28.5): resolution: {integrity: sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-transform-flow-strip-types': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-flow-strip-types': 7.25.9(@babel/core@7.28.5) dev: true /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0): @@ -4796,7 +5747,18 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/types': 7.26.0 + '@babel/types': 7.28.5 + esutils: 2.0.3 + dev: true + + /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.5): + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/types': 7.28.5 esutils: 2.0.3 dev: true @@ -4833,13 +5795,29 @@ packages: - supports-color dev: true - /@babel/register@7.25.9(@babel/core@7.26.0): + /@babel/preset-typescript@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/register@7.25.9(@babel/core@7.28.5): resolution: {integrity: sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 @@ -4900,7 +5878,7 @@ packages: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.26.2 '@babel/types': 7.26.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -4918,7 +5896,7 @@ packages: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.26.2 '@babel/types': 7.26.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -4928,12 +5906,12 @@ packages: resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.2 - '@babel/parser': 7.26.2 - '@babel/template': 7.25.9 - '@babel/types': 7.26.0 - debug: 4.4.0(supports-color@5.5.0) + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -4948,7 +5926,7 @@ packages: '@babel/parser': 7.28.5 '@babel/template': 7.27.2 '@babel/types': 7.28.5 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -5071,13 +6049,13 @@ packages: chalk: 4.1.2 cypress: 12.17.3 dayjs: 1.11.13 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) execa: 4.1.0 globby: 11.1.0 istanbul-lib-coverage: 3.2.2 js-yaml: 4.1.0 nyc: 15.1.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) transitivePeerDependencies: - supports-color dev: true @@ -5179,9 +6157,9 @@ packages: '@babel/preset-env': 7.28.5(@babel/core@7.26.0) babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.103.0) bluebird: 3.7.1 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) lodash: 4.17.21 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) transitivePeerDependencies: - supports-color dev: true @@ -5367,20 +6345,20 @@ packages: resolution: {integrity: sha512-Bz1zLGEqBQ0BVkqt1OgMxdBOE3BdUWUd7Ly9Ecr/aUwkA8AV1w1XzBMe4xblmJHnB1XXNlPH4SraXCvO+q0Mig==} dev: false - /@esbuild-plugins/node-globals-polyfill@0.1.1(esbuild@0.27.0): + /@esbuild-plugins/node-globals-polyfill@0.1.1(esbuild@0.24.0): resolution: {integrity: sha512-MR0oAA+mlnJWrt1RQVQ+4VYuRJW/P2YmRTv1AsplObyvuBMnPHiizUF95HHYiSsMGLhyGtWufaq2XQg6+iurBg==} peerDependencies: esbuild: '*' dependencies: - esbuild: 0.27.0 + esbuild: 0.24.0 dev: false - /@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.27.0): + /@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.24.0): resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} peerDependencies: esbuild: '*' dependencies: - esbuild: 0.27.0 + esbuild: 0.24.0 dev: false /@esbuild/aix-ppc64@0.19.12: @@ -5409,14 +6387,6 @@ packages: requiresBuild: true optional: true - /@esbuild/aix-ppc64@0.27.0: - resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - requiresBuild: true - optional: true - /@esbuild/android-arm64@0.17.19: resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} engines: {node: '>=12'} @@ -5461,14 +6431,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm64@0.27.0: - resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - requiresBuild: true - optional: true - /@esbuild/android-arm@0.15.18: resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} engines: {node: '>=12'} @@ -5522,14 +6484,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm@0.27.0: - resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - requiresBuild: true - optional: true - /@esbuild/android-x64@0.17.19: resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} engines: {node: '>=12'} @@ -5574,14 +6528,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-x64@0.27.0: - resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - requiresBuild: true - optional: true - /@esbuild/darwin-arm64@0.17.19: resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} engines: {node: '>=12'} @@ -5626,14 +6572,6 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-arm64@0.27.0: - resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - optional: true - /@esbuild/darwin-x64@0.17.19: resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} engines: {node: '>=12'} @@ -5678,14 +6616,6 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-x64@0.27.0: - resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - requiresBuild: true - optional: true - /@esbuild/freebsd-arm64@0.17.19: resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} engines: {node: '>=12'} @@ -5730,14 +6660,6 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-arm64@0.27.0: - resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - optional: true - /@esbuild/freebsd-x64@0.17.19: resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} engines: {node: '>=12'} @@ -5782,14 +6704,6 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-x64@0.27.0: - resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - optional: true - /@esbuild/linux-arm64@0.17.19: resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} engines: {node: '>=12'} @@ -5834,14 +6748,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm64@0.27.0: - resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-arm@0.17.19: resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} engines: {node: '>=12'} @@ -5886,14 +6792,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm@0.27.0: - resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-ia32@0.17.19: resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} engines: {node: '>=12'} @@ -5938,14 +6836,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ia32@0.27.0: - resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-loong64@0.15.18: resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} engines: {node: '>=12'} @@ -5999,14 +6889,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-loong64@0.27.0: - resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-mips64el@0.17.19: resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} engines: {node: '>=12'} @@ -6051,14 +6933,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-mips64el@0.27.0: - resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-ppc64@0.17.19: resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} engines: {node: '>=12'} @@ -6103,14 +6977,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ppc64@0.27.0: - resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-riscv64@0.17.19: resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} engines: {node: '>=12'} @@ -6155,14 +7021,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-riscv64@0.27.0: - resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-s390x@0.17.19: resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} engines: {node: '>=12'} @@ -6207,14 +7065,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-s390x@0.27.0: - resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-x64@0.17.19: resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} engines: {node: '>=12'} @@ -6259,22 +7109,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-x64@0.27.0: - resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/netbsd-arm64@0.27.0: - resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - requiresBuild: true - optional: true - /@esbuild/netbsd-x64@0.17.19: resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} engines: {node: '>=12'} @@ -6319,14 +7153,6 @@ packages: requiresBuild: true optional: true - /@esbuild/netbsd-x64@0.27.0: - resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - optional: true - /@esbuild/openbsd-arm64@0.24.0: resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} engines: {node: '>=18'} @@ -6335,14 +7161,6 @@ packages: requiresBuild: true optional: true - /@esbuild/openbsd-arm64@0.27.0: - resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - requiresBuild: true - optional: true - /@esbuild/openbsd-x64@0.17.19: resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} engines: {node: '>=12'} @@ -6387,22 +7205,6 @@ packages: requiresBuild: true optional: true - /@esbuild/openbsd-x64@0.27.0: - resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - optional: true - - /@esbuild/openharmony-arm64@0.27.0: - resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - requiresBuild: true - optional: true - /@esbuild/sunos-x64@0.17.19: resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} engines: {node: '>=12'} @@ -6447,14 +7249,6 @@ packages: requiresBuild: true optional: true - /@esbuild/sunos-x64@0.27.0: - resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - requiresBuild: true - optional: true - /@esbuild/win32-arm64@0.17.19: resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} engines: {node: '>=12'} @@ -6499,14 +7293,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-arm64@0.27.0: - resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - requiresBuild: true - optional: true - /@esbuild/win32-ia32@0.17.19: resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} engines: {node: '>=12'} @@ -6551,14 +7337,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-ia32@0.27.0: - resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - requiresBuild: true - optional: true - /@esbuild/win32-x64@0.17.19: resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} engines: {node: '>=12'} @@ -6603,14 +7381,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-x64@0.27.0: - resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - requiresBuild: true - optional: true - /@eslint-community/eslint-utils@4.4.1(eslint@8.57.0): resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6620,33 +7390,33 @@ packages: eslint: 8.57.0 eslint-visitor-keys: 3.4.3 - /@eslint-community/eslint-utils@4.4.1(eslint@8.57.1): + /@eslint-community/eslint-utils@4.4.1(eslint@9.14.0): resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.57.1 + eslint: 9.14.0 eslint-visitor-keys: 3.4.3 dev: true - /@eslint-community/eslint-utils@4.4.1(eslint@9.14.0): - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + /@eslint-community/eslint-utils@4.9.0(eslint@8.57.0): + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 9.14.0 + eslint: 8.57.0 eslint-visitor-keys: 3.4.3 dev: true - /@eslint-community/eslint-utils@4.9.0(eslint@8.57.1): + /@eslint-community/eslint-utils@4.9.0(eslint@9.14.0): resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.57.1 + eslint: 9.14.0 eslint-visitor-keys: 3.4.3 dev: true @@ -6664,7 +7434,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -6680,7 +7450,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -6696,12 +7466,12 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: ajv: 6.12.6 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -6712,11 +7482,6 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /@eslint/js@8.57.1: - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - /@eslint/js@9.14.0: resolution: {integrity: sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8137,22 +8902,10 @@ packages: deprecated: Use @eslint/config-array instead dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.0(supports-color@5.5.0) - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - /@humanwhocodes/config-array@0.13.0: - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color - dev: true /@humanwhocodes/module-importer@1.0.1: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -8216,7 +8969,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-mock: 29.7.0 dev: false @@ -8226,7 +8979,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -8609,7 +9362,7 @@ packages: '@ledgerhq/errors': 6.19.1 '@ledgerhq/logs': 6.12.0 rxjs: 7.8.1 - semver: 7.6.3 + semver: 7.7.3 dev: false /@ledgerhq/devices@8.4.5: @@ -8627,7 +9380,7 @@ packages: '@ledgerhq/errors': 6.21.0 '@ledgerhq/logs': 6.13.0 rxjs: 7.8.1 - semver: 7.6.3 + semver: 7.7.3 dev: false /@ledgerhq/errors@6.19.1: @@ -9254,7 +10007,7 @@ packages: bufferutil: 4.0.9 cross-fetch: 4.0.0 date-fns: 2.30.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) eciesjs: 0.3.21 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -9277,7 +10030,7 @@ packages: bufferutil: 4.0.9 cross-fetch: 4.0.0 date-fns: 2.30.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) eciesjs: 0.4.14 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -9336,7 +10089,7 @@ packages: '@types/dom-screen-wake-lock': 1.0.3 bowser: 2.11.0 cross-fetch: 4.0.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) eciesjs: 0.3.21 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -9374,7 +10127,7 @@ packages: '@paulmillr/qr': 0.2.1 bowser: 2.11.0 cross-fetch: 4.0.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) eciesjs: 0.4.14 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -9403,7 +10156,7 @@ packages: dependencies: '@ethereumjs/tx': 4.2.0 '@types/debug': 4.1.12 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@5.5.0) semver: 7.7.3 superstruct: 1.0.4 transitivePeerDependencies: @@ -9419,9 +10172,9 @@ packages: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) pony-cause: 2.1.11 - semver: 7.6.3 + semver: 7.7.3 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -9436,9 +10189,9 @@ packages: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) pony-cause: 2.1.11 - semver: 7.6.3 + semver: 7.7.3 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -12716,7 +13469,7 @@ packages: optional: true dependencies: '@react-native/dev-middleware': 0.82.1 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@5.5.0) invariant: 2.2.4 metro: 0.83.3 metro-config: 0.83.3 @@ -12751,7 +13504,7 @@ packages: chrome-launcher: 0.15.2 chromium-edge-launcher: 0.2.0 connect: 3.7.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@5.5.0) invariant: 2.2.4 nullthrows: 1.1.1 open: 7.4.2 @@ -13075,7 +13828,7 @@ packages: rollup: 3.29.5 dev: false - /@rollup/pluginutils@5.1.3(rollup@3.29.5): + /@rollup/pluginutils@5.1.3: resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} engines: {node: '>=14.0.0'} peerDependencies: @@ -13087,6 +13840,20 @@ packages: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 4.0.2 + dev: true + + /@rollup/pluginutils@5.1.3(rollup@3.29.5): + resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.2 rollup: 3.29.5 /@rollup/rollup-android-arm-eabi@4.25.0: @@ -13887,7 +14654,7 @@ packages: peerDependencies: typescript: '>=5' dependencies: - chalk: 5.4.1 + chalk: 5.6.2 commander: 12.1.0 typescript: 5.3.3 dev: false @@ -13899,7 +14666,7 @@ packages: peerDependencies: typescript: '>=5.3.3' dependencies: - chalk: 5.4.1 + chalk: 5.6.2 commander: 13.1.0 typescript: 5.3.3 dev: false @@ -14244,7 +15011,7 @@ packages: '@solana/rpc-spec': 2.1.1(typescript@5.3.3) '@solana/rpc-spec-types': 2.1.1(typescript@5.3.3) typescript: 5.3.3 - undici-types: 7.10.0 + undici-types: 7.16.0 dev: false /@solana/rpc-types@2.1.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.3.3): @@ -15671,15 +16438,15 @@ packages: /@storybook/codemod@7.6.20: resolution: {integrity: sha512-8vmSsksO4XukNw0TmqylPmk7PxnfNfE21YsxFa7mnEBmEKQcZCQsNil4ZgWfG0IzdhTfhglAN4r++Ew0WE+PYA==} dependencies: - '@babel/core': 7.26.0 - '@babel/preset-env': 7.26.0(@babel/core@7.26.0) - '@babel/types': 7.26.0 + '@babel/core': 7.28.5 + '@babel/preset-env': 7.26.0(@babel/core@7.28.5) + '@babel/types': 7.28.5 '@storybook/csf': 0.1.13 '@storybook/csf-tools': 7.6.20 '@storybook/node-logger': 7.6.20 '@storybook/types': 7.6.20 '@types/cross-spawn': 6.0.6 - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 globby: 11.1.0 jscodeshift: 0.15.2(@babel/preset-env@7.26.0) lodash: 4.17.21 @@ -15791,7 +16558,7 @@ packages: pretty-hrtime: 1.0.3 prompts: 2.4.2 read-pkg-up: 7.0.1 - semver: 7.6.3 + semver: 7.7.3 telejson: 7.2.0 tiny-invariant: 1.3.3 ts-dedent: 2.2.0 @@ -15958,7 +16725,7 @@ packages: vite: ^3.0.0 || ^4.0.0 || ^5.0.0 dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.3)(vite@4.5.9) - '@rollup/pluginutils': 5.1.3(rollup@3.29.5) + '@rollup/pluginutils': 5.1.3 '@storybook/builder-vite': 7.6.20(typescript@5.3.3)(vite@4.5.9) '@storybook/react': 7.6.20(react-dom@18.3.1)(react@18.3.1)(typescript@5.3.3) '@vitejs/plugin-react': 3.1.0(vite@4.5.9) @@ -16360,7 +17127,7 @@ packages: resolution: {integrity: sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==} engines: {node: '>=10'} dependencies: - '@babel/types': 7.26.0 + '@babel/types': 7.28.5 entities: 4.5.0 dev: true @@ -17346,13 +18113,13 @@ packages: /@types/babel__generator@7.6.8: resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} dependencies: - '@babel/types': 7.26.0 + '@babel/types': 7.28.5 /@types/babel__template@7.4.4: resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} dependencies: - '@babel/parser': 7.26.2 - '@babel/types': 7.26.0 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 /@types/babel__traverse@7.20.6: resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} @@ -17635,7 +18402,7 @@ packages: /@types/eslint-scope@3.7.7: resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: - '@types/eslint': 9.6.1 + '@types/eslint': 8.56.12 '@types/estree': 1.0.8 dev: true @@ -17646,13 +18413,6 @@ packages: '@types/json-schema': 7.0.15 dev: true - /@types/eslint@9.6.1: - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - dependencies: - '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 - dev: true - /@types/estree@0.0.51: resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} dev: true @@ -17662,7 +18422,6 @@ packages: /@types/estree@1.0.8: resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - dev: true /@types/express-serve-static-core@4.19.6: resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} @@ -17867,6 +18626,7 @@ packages: resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} dependencies: undici-types: 6.21.0 + dev: true /@types/node@22.10.2: resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} @@ -17884,12 +18644,6 @@ packages: dependencies: undici-types: 6.19.8 - /@types/node@24.10.1: - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} - dependencies: - undici-types: 7.16.0 - dev: false - /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true @@ -18166,7 +18920,7 @@ packages: /@types/webpack-sources@3.2.3: resolution: {integrity: sha512-4nZOdMwSPHZ4pTEZzSp0AsTM4K7Qmu40UKW4tJDiOVs20UzYF9l+qUe4s0ftfN0pin06n+5cWWDJXH+sbhAiDw==} dependencies: - '@types/node': 20.17.6 + '@types/node': 22.10.7 '@types/source-list-map': 0.1.6 source-map: 0.7.6 dev: true @@ -18174,7 +18928,7 @@ packages: /@types/webpack@4.41.40: resolution: {integrity: sha512-u6kMFSBM9HcoTpUXnL6mt2HSzftqb3JgYV6oxIgL2dl6sX6aCa5k6SOkzv5DuZjBTPUE/dJltKtwwuqrkZHpfw==} dependencies: - '@types/node': 20.17.6 + '@types/node': 22.10.7 '@types/tapable': 1.0.12 '@types/uglify-js': 3.17.5 '@types/webpack-sources': 3.2.3 @@ -18217,7 +18971,7 @@ packages: resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} requiresBuild: true dependencies: - '@types/node': 20.17.6 + '@types/node': 22.10.7 dev: true optional: true @@ -18232,17 +18986,17 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.12.1 + '@eslint-community/regexpp': 4.12.2 '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.3.2) '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.0)(typescript@5.3.2) '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.3.2) - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare-lite: 1.4.0 - semver: 7.6.3 + semver: 7.7.3 tsutils: 3.21.0(typescript@5.3.2) typescript: 5.3.2 transitivePeerDependencies: @@ -18294,7 +19048,7 @@ packages: '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.2 @@ -18323,7 +19077,7 @@ packages: '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.3.2) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.3.2) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.2 @@ -18335,35 +19089,6 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) - eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/eslint-plugin@8.14.0(@typescript-eslint/parser@8.14.0)(eslint@9.14.0)(typescript@5.3.2): resolution: {integrity: sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18375,7 +19100,7 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.12.1 + '@eslint-community/regexpp': 4.12.2 '@typescript-eslint/parser': 8.14.0(eslint@9.14.0)(typescript@5.3.2) '@typescript-eslint/scope-manager': 8.14.0 '@typescript-eslint/type-utils': 8.14.0(eslint@9.14.0)(typescript@5.3.2) @@ -18417,7 +19142,7 @@ packages: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.2) - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) eslint: 8.57.0 typescript: 5.3.2 transitivePeerDependencies: @@ -18457,7 +19182,7 @@ packages: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.0 typescript: 5.1.6 transitivePeerDependencies: @@ -18478,34 +19203,13 @@ packages: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.2) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.0 typescript: 5.3.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) - eslint: 8.57.1 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/parser@8.14.0(eslint@9.14.0)(typescript@5.3.2): resolution: {integrity: sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18520,7 +19224,7 @@ packages: '@typescript-eslint/types': 8.14.0 '@typescript-eslint/typescript-estree': 8.14.0(typescript@5.3.2) '@typescript-eslint/visitor-keys': 8.14.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) eslint: 9.14.0 typescript: 5.3.2 transitivePeerDependencies: @@ -18562,7 +19266,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.2) '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.3.2) - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.0 tsutils: 3.21.0(typescript@5.3.2) typescript: 5.3.2 @@ -18582,7 +19286,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.3.3) - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.0 tsutils: 3.21.0(typescript@5.3.3) typescript: 5.3.3 @@ -18602,7 +19306,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) eslint: 8.57.0 ts-api-utils: 1.4.0(typescript@5.1.6) typescript: 5.1.6 @@ -18622,7 +19326,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.2) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.3.2) - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) eslint: 8.57.0 ts-api-utils: 1.4.0(typescript@5.3.2) typescript: 5.3.2 @@ -18630,26 +19334,6 @@ packages: - supports-color dev: true - /@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - debug: 4.4.0(supports-color@5.5.0) - eslint: 8.57.1 - ts-api-utils: 1.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/type-utils@8.14.0(eslint@9.14.0)(typescript@5.3.2): resolution: {integrity: sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18661,7 +19345,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 8.14.0(typescript@5.3.2) '@typescript-eslint/utils': 8.14.0(eslint@9.14.0)(typescript@5.3.2) - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) ts-api-utils: 1.4.0(typescript@5.3.2) typescript: 5.3.2 transitivePeerDependencies: @@ -18683,7 +19367,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.2): + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.1.6): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18694,16 +19378,17 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 - tsutils: 3.21.0(typescript@5.3.2) - typescript: 5.3.2 + semver: 7.7.3 + tsutils: 3.21.0(typescript@5.1.6) + typescript: 5.1.6 transitivePeerDependencies: - supports-color + dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.3): + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.2): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18714,17 +19399,16 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 - tsutils: 3.21.0(typescript@5.3.3) - typescript: 5.3.3 + semver: 7.7.3 + tsutils: 3.21.0(typescript@5.3.2) + typescript: 5.3.2 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.3): + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.3): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18735,12 +19419,12 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 - tsutils: 3.21.0(typescript@5.9.3) - typescript: 5.9.3 + semver: 7.7.3 + tsutils: 3.21.0(typescript@5.3.3) + typescript: 5.3.3 transitivePeerDependencies: - supports-color dev: true @@ -18756,11 +19440,11 @@ packages: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.3 + semver: 7.7.3 ts-api-utils: 1.4.0(typescript@5.1.6) typescript: 5.1.6 transitivePeerDependencies: @@ -18778,39 +19462,17 @@ packages: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.3 + semver: 7.7.3 ts-api-utils: 1.4.0(typescript@5.3.2) typescript: 5.3.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3): - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/typescript-estree@8.14.0(typescript@5.3.2): resolution: {integrity: sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18822,72 +19484,72 @@ packages: dependencies: '@typescript-eslint/types': 8.14.0 '@typescript-eslint/visitor-keys': 8.14.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 + semver: 7.7.3 ts-api-utils: 1.4.0(typescript@5.3.2) typescript: 5.3.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.2): + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.1.6): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.2) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.1.6) eslint: 8.57.0 eslint-scope: 5.1.1 - semver: 7.6.3 + semver: 7.7.3 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.3): + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.2): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.2) eslint: 8.57.0 eslint-scope: 5.1.1 - semver: 7.6.3 + semver: 7.7.3 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.9.3): + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) + eslint: 8.57.0 eslint-scope: 5.1.1 - semver: 7.6.3 + semver: 7.7.3 transitivePeerDependencies: - supports-color - typescript @@ -18899,14 +19561,14 @@ packages: peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6) eslint: 8.57.0 - semver: 7.6.3 + semver: 7.7.3 transitivePeerDependencies: - supports-color - typescript @@ -18918,33 +19580,14 @@ packages: peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.0) + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.2) eslint: 8.57.0 - semver: 7.6.3 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - - /@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - eslint: 8.57.1 - semver: 7.6.3 + semver: 7.7.3 transitivePeerDependencies: - supports-color - typescript @@ -18956,7 +19599,7 @@ packages: peerDependencies: eslint: ^8.57.0 || ^9.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.14.0) '@typescript-eslint/scope-manager': 8.14.0 '@typescript-eslint/types': 8.14.0 '@typescript-eslint/typescript-estree': 8.14.0(typescript@5.3.2) @@ -18992,10 +19635,6 @@ packages: /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - /@ungap/structured-clone@1.3.0: - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - dev: true - /@uniswap/lib@4.0.1-alpha: resolution: {integrity: sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==} engines: {node: '>=10'} @@ -19069,7 +19708,7 @@ packages: '@vanilla-extract/css': 1.14.0 dev: false - /@vercel/style-guide@5.2.0(eslint@8.57.1)(prettier@3.5.3)(typescript@5.9.3): + /@vercel/style-guide@5.2.0(eslint@8.57.0)(prettier@3.5.3)(typescript@5.1.6): resolution: {integrity: sha512-fNSKEaZvSkiBoF6XEefs8CcgAV9K9e+MbcsDZjUsktHycKdA0jvjAzQi1W/FzLS+Nr5zZ6oejCwq/97dHUKe0g==} engines: {node: '>=16'} peerDependencies: @@ -19088,27 +19727,27 @@ packages: optional: true dependencies: '@babel/core': 7.26.0 - '@babel/eslint-parser': 7.25.9(@babel/core@7.26.0)(eslint@8.57.1) + '@babel/eslint-parser': 7.25.9(@babel/core@7.26.0)(eslint@8.57.0) '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 - eslint-config-prettier: 9.1.0(eslint@8.57.1) + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.1.6) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) + eslint: 8.57.0 + eslint-config-prettier: 9.1.0(eslint@8.57.0) eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0) - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.1)(typescript@5.9.3) - eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) - eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.1) - eslint-plugin-react: 7.37.2(eslint@8.57.1) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) - eslint-plugin-testing-library: 6.4.0(eslint@8.57.1)(typescript@5.9.3) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(typescript@5.1.6) + eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) + eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.0) + eslint-plugin-react: 7.37.2(eslint@8.57.0) + eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) + eslint-plugin-testing-library: 6.4.0(eslint@8.57.0)(typescript@5.1.6) eslint-plugin-tsdoc: 0.2.17 - eslint-plugin-unicorn: 48.0.1(eslint@8.57.1) + eslint-plugin-unicorn: 48.0.1(eslint@8.57.0) prettier: 3.5.3 prettier-plugin-packagejson: 2.5.3(prettier@3.5.3) - typescript: 5.9.3 + typescript: 5.1.6 transitivePeerDependencies: - eslint-import-resolver-node - eslint-import-resolver-webpack @@ -19479,7 +20118,7 @@ packages: engines: {node: '>=16'} hasBin: true dependencies: - chalk: 5.4.1 + chalk: 5.6.2 commander: 13.1.0 dev: false @@ -21743,7 +22382,7 @@ packages: array-buffer-byte-length: 1.0.1 call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.4 @@ -21756,7 +22395,7 @@ packages: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 @@ -21856,8 +22495,8 @@ packages: peerDependencies: postcss: ^8.1.0 dependencies: - browserslist: 4.24.2 - caniuse-lite: 1.0.30001680 + browserslist: 4.28.0 + caniuse-lite: 1.0.30001757 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -21926,12 +22565,12 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} - /babel-core@7.0.0-bridge.0(@babel/core@7.26.0): + /babel-core@7.0.0-bridge.0(@babel/core@7.28.5): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 dev: true /babel-jest@29.7.0(@babel/core@7.26.0): @@ -21980,7 +22619,7 @@ packages: '@babel/core': 7.26.0 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /babel-plugin-emotion@10.2.2: @@ -22039,7 +22678,7 @@ packages: dependencies: '@babel/runtime': 7.27.0 cosmiconfig: 6.0.0 - resolve: 1.22.8 + resolve: 1.22.11 dev: false /babel-plugin-macros@3.1.0: @@ -22063,6 +22702,19 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.28.5): + resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.28.5) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.26.0): resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} peerDependencies: @@ -22088,6 +22740,18 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.28.5): + resolution: {integrity: sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.28.5) + core-js-compat: 3.39.0 + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.26.0): resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} peerDependencies: @@ -22111,6 +22775,17 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-regenerator@0.6.3(@babel/core@7.28.5): + resolution: {integrity: sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.26.0): resolution: {integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==} peerDependencies: @@ -22802,7 +23477,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001680 + caniuse-lite: 1.0.30001757 electron-to-chromium: 1.5.57 node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) @@ -23048,6 +23723,7 @@ packages: /caniuse-lite@1.0.30001680: resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} + dev: false /caniuse-lite@1.0.30001757: resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} @@ -23106,11 +23782,6 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 - /chalk@5.4.1: - resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: false - /chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -23202,7 +23873,7 @@ packages: engines: {node: '>=12.13.0'} hasBin: true dependencies: - '@types/node': 24.10.1 + '@types/node': 22.10.7 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -23218,7 +23889,7 @@ packages: /chromium-edge-launcher@0.2.0: resolution: {integrity: sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==} dependencies: - '@types/node': 20.19.25 + '@types/node': 22.10.7 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -23657,7 +24328,7 @@ packages: /core-js-compat@3.39.0: resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==} dependencies: - browserslist: 4.24.2 + browserslist: 4.28.0 dev: true /core-js-compat@3.47.0: @@ -23720,7 +24391,7 @@ packages: optional: true dependencies: import-fresh: 3.3.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 typescript: 5.1.6 @@ -23736,7 +24407,7 @@ packages: optional: true dependencies: import-fresh: 3.3.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 typescript: 5.3.2 @@ -23751,7 +24422,7 @@ packages: optional: true dependencies: import-fresh: 3.3.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 typescript: 5.3.3 @@ -23766,7 +24437,7 @@ packages: optional: true dependencies: import-fresh: 3.3.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 typescript: 5.6.3 @@ -24470,7 +25141,7 @@ packages: dependencies: ms: 2.1.3 - /debug@4.4.0(supports-color@5.5.0): + /debug@4.4.0(supports-color@8.1.1): resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} peerDependencies: @@ -24480,10 +25151,10 @@ packages: optional: true dependencies: ms: 2.1.3 - supports-color: 5.5.0 + supports-color: 8.1.1 - /debug@4.4.0(supports-color@8.1.1): - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + /debug@4.4.3(supports-color@5.5.0): + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -24492,8 +25163,7 @@ packages: optional: true dependencies: ms: 2.1.3 - supports-color: 8.1.1 - dev: true + supports-color: 5.5.0 /debug@4.4.3(supports-color@8.1.1): resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} @@ -24506,6 +25176,7 @@ packages: dependencies: ms: 2.1.3 supports-color: 8.1.1 + dev: true /decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} @@ -24796,7 +25467,7 @@ packages: hasBin: true dependencies: address: 1.2.2 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -24961,7 +25632,7 @@ packages: resolution: {integrity: sha512-/Tezlx6xpDqR6zKg1V4vLCeQtHWiELhWoBz5A/E0+A1lXN9iIkNbbfc4THSymS0LQUo8F1PMiIwVG8ai/HrnSA==} engines: {node: '>= 8.3.0'} dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) is-string-and-not-blank: 0.0.2 transitivePeerDependencies: - supports-color @@ -25389,7 +26060,6 @@ packages: typed-array-length: 1.0.7 unbox-primitive: 1.1.0 which-typed-array: 1.1.19 - dev: true /es-array-method-boxes-properly@1.0.0: resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} @@ -25696,19 +26366,19 @@ packages: peerDependencies: esbuild: '>=0.12 <1' dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) esbuild: 0.18.20 transitivePeerDependencies: - supports-color dev: true - /esbuild-sass-plugin@3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3): + /esbuild-sass-plugin@3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3): resolution: {integrity: sha512-SnO1ls+d52n6j8gRRpjexXI8MsHEaumS0IdDHaYM29Y6gakzZYMls6i9ql9+AWMSQk/eryndmUpXEgT34QrX1A==} peerDependencies: esbuild: '>=0.20.1' sass-embedded: ^1.71.1 dependencies: - esbuild: 0.27.0 + esbuild: 0.24.0 resolve: 1.22.8 safe-identifier: 0.4.2 sass: 1.80.7 @@ -25934,39 +26604,6 @@ packages: '@esbuild/win32-ia32': 0.24.0 '@esbuild/win32-x64': 0.24.0 - /esbuild@0.27.0: - resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} - engines: {node: '>=18'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.0 - '@esbuild/android-arm': 0.27.0 - '@esbuild/android-arm64': 0.27.0 - '@esbuild/android-x64': 0.27.0 - '@esbuild/darwin-arm64': 0.27.0 - '@esbuild/darwin-x64': 0.27.0 - '@esbuild/freebsd-arm64': 0.27.0 - '@esbuild/freebsd-x64': 0.27.0 - '@esbuild/linux-arm': 0.27.0 - '@esbuild/linux-arm64': 0.27.0 - '@esbuild/linux-ia32': 0.27.0 - '@esbuild/linux-loong64': 0.27.0 - '@esbuild/linux-mips64el': 0.27.0 - '@esbuild/linux-ppc64': 0.27.0 - '@esbuild/linux-riscv64': 0.27.0 - '@esbuild/linux-s390x': 0.27.0 - '@esbuild/linux-x64': 0.27.0 - '@esbuild/netbsd-arm64': 0.27.0 - '@esbuild/netbsd-x64': 0.27.0 - '@esbuild/openbsd-arm64': 0.27.0 - '@esbuild/openbsd-x64': 0.27.0 - '@esbuild/openharmony-arm64': 0.27.0 - '@esbuild/sunos-x64': 0.27.0 - '@esbuild/win32-arm64': 0.27.0 - '@esbuild/win32-ia32': 0.27.0 - '@esbuild/win32-x64': 0.27.0 - /escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -26048,15 +26685,6 @@ packages: eslint: 8.57.0 dev: true - /eslint-config-prettier@9.1.0(eslint@8.57.1): - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - dependencies: - eslint: 8.57.1 - dev: true - /eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.27.1)(@babel/plugin-transform-react-jsx@7.27.1)(eslint@8.57.0)(typescript@5.3.2): resolution: {integrity: sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==} engines: {node: '>=14.0.0'} @@ -26101,22 +26729,13 @@ packages: eslint-plugin-turbo: 1.13.4(eslint@8.57.0) dev: true - /eslint-config-turbo@1.13.4(eslint@8.57.1): - resolution: {integrity: sha512-+we4eWdZlmlEn7LnhXHCIPX/wtujbHCS7XjQM/TN09BHNEl2fZ8id4rHfdfUKIYTSKyy8U/nNyJ0DNoZj5Q8bw==} - peerDependencies: - eslint: '>6.6.0' - dependencies: - eslint: 8.57.1 - eslint-plugin-turbo: 1.13.4(eslint@8.57.1) - dev: true - /eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0): resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==} engines: {node: '>= 4'} peerDependencies: eslint-plugin-import: '>=1.4.0' dependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) dev: true /eslint-import-resolver-node@0.3.9: @@ -26142,7 +26761,7 @@ packages: optional: true dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.0 eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) @@ -26157,7 +26776,7 @@ packages: - eslint-import-resolver-webpack - supports-color - /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.1): + /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.0): resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -26171,11 +26790,11 @@ packages: optional: true dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) enhanced-resolve: 5.17.1 - eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint: 8.57.0 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -26216,7 +26835,7 @@ packages: transitivePeerDependencies: - supports-color - /eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + /eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} engines: {node: '>=4'} peerDependencies: @@ -26237,11 +26856,11 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) debug: 3.2.7(supports-color@8.1.1) - eslint: 8.57.1 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color dev: true @@ -26253,14 +26872,14 @@ packages: postcss: 7.0.39 requireindex: 1.1.0 - /eslint-plugin-eslint-comments@3.2.0(eslint@8.57.1): + /eslint-plugin-eslint-comments@3.2.0(eslint@8.57.0): resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} engines: {node: '>=6.5.0'} peerDependencies: eslint: '>=4.19.1' dependencies: escape-string-regexp: 1.0.5 - eslint: 8.57.1 + eslint: 8.57.0 ignore: 5.3.2 dev: true @@ -26315,7 +26934,7 @@ packages: - eslint-import-resolver-webpack - supports-color - /eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + /eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} engines: {node: '>=4'} peerDependencies: @@ -26326,16 +26945,16 @@ packages: optional: true dependencies: '@rtsao/scc': 1.1.0 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 - eslint: 8.57.1 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -26373,7 +26992,7 @@ packages: - typescript dev: true - /eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.1)(typescript@5.9.3): + /eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(typescript@5.1.6): resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -26386,9 +27005,9 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.1.6) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.1.6) + eslint: 8.57.0 transitivePeerDependencies: - supports-color - typescript @@ -26417,30 +27036,6 @@ packages: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.1 - /eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1): - resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - dependencies: - aria-query: 5.3.2 - array-includes: 3.1.8 - array.prototype.flatmap: 1.3.2 - ast-types-flow: 0.0.8 - axe-core: 4.10.2 - axobject-query: 4.1.0 - damerau-levenshtein: 1.0.8 - emoji-regex: 9.2.2 - eslint: 8.57.1 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - language-tags: 1.0.9 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - safe-regex-test: 1.0.3 - string.prototype.includes: 2.0.1 - dev: true - /eslint-plugin-lingui@0.2.2(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-orWu6o/IkCckLD0owLt+8fKy6SsIqIR9kz2L36KbCuAIfWg3dLRWC+XvuqQWEgJ4raRt+eoomTMunUJiBeogDQ==} engines: {node: '>=16.0.0'} @@ -26452,7 +27047,7 @@ packages: - typescript dev: true - /eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.1): + /eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.0): resolution: {integrity: sha512-DcHpF0SLbNeh9MT4pMzUGuUSnJ7q5MWbP8sSEFIMS6j7Ggnduq8ghNlfhURgty4c1YFny7Ge9xYTO1FSAoV2Vw==} peerDependencies: eslint: '>=7' @@ -26461,8 +27056,8 @@ packages: eslint-plugin-jest: optional: true dependencies: - eslint: 8.57.1 - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.1)(typescript@5.9.3) + eslint: 8.57.0 + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(typescript@5.1.6) dev: true /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0)(eslint@8.57.0)(prettier@2.8.8): @@ -26512,15 +27107,6 @@ packages: dependencies: eslint: 8.57.0 - /eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): - resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - dependencies: - eslint: 8.57.1 - dev: true - /eslint-plugin-react-hooks@5.1.0-rc-fb9a90fa48-20240614(eslint@9.14.0): resolution: {integrity: sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w==} engines: {node: '>=10'} @@ -26572,33 +27158,6 @@ packages: string.prototype.matchall: 4.0.11 string.prototype.repeat: 1.0.0 - /eslint-plugin-react@7.37.2(eslint@8.57.1): - resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - dependencies: - array-includes: 3.1.8 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.2 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.2.0 - eslint: 8.57.1 - estraverse: 5.3.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 - object.entries: 1.1.8 - object.fromentries: 2.0.8 - object.values: 1.2.0 - prop-types: 15.8.1 - resolve: 2.0.0-next.5 - semver: 6.3.1 - string.prototype.matchall: 4.0.11 - string.prototype.repeat: 1.0.0 - dev: true - /eslint-plugin-storybook@0.6.15(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-lAGqVAJGob47Griu29KXYowI4G7KwMoJDOkEip8ujikuDLxU+oWJ1l0WL6F2oDO4QiyUFXvtDkEkISMOPzo+7w==} engines: {node: 12.x || 14.x || >= 16} @@ -26628,14 +27187,14 @@ packages: - typescript dev: true - /eslint-plugin-testing-library@6.4.0(eslint@8.57.1)(typescript@5.9.3): + /eslint-plugin-testing-library@6.4.0(eslint@8.57.0)(typescript@5.1.6): resolution: {integrity: sha512-yeWF+YgCgvNyPNI9UKnG0FjeE2sk93N/3lsKqcmR8dSfeXJwFT5irnWo7NjLf152HkRzfoFjh3LsBUrhvFz4eA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} peerDependencies: eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.1.6) + eslint: 8.57.0 transitivePeerDependencies: - supports-color - typescript @@ -26657,26 +27216,17 @@ packages: eslint: 8.57.0 dev: true - /eslint-plugin-turbo@1.13.4(eslint@8.57.1): - resolution: {integrity: sha512-82GfMzrewI/DJB92Bbch239GWbGx4j1zvjk1lqb06lxIlMPnVwUHVwPbAnLfyLG3JuhLv9whxGkO/q1CL18JTg==} - peerDependencies: - eslint: '>6.6.0' - dependencies: - dotenv: 16.0.3 - eslint: 8.57.1 - dev: true - - /eslint-plugin-unicorn@48.0.1(eslint@8.57.1): + /eslint-plugin-unicorn@48.0.1(eslint@8.57.0): resolution: {integrity: sha512-FW+4r20myG/DqFcCSzoumaddKBicIPeFnTrifon2mWIzlfyvzwyqZjqVP7m4Cqr/ZYisS2aiLghkUWaPg6vtCw==} engines: {node: '>=16'} peerDependencies: eslint: '>=8.44.0' dependencies: '@babel/helper-validator-identifier': 7.25.9 - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.0) ci-info: 3.9.0 clean-regexp: 1.0.0 - eslint: 8.57.1 + eslint: 8.57.0 esquery: 1.6.0 indent-string: 4.0.0 is-builtin-module: 3.2.1 @@ -26686,7 +27236,7 @@ packages: read-pkg-up: 7.0.1 regexp-tree: 0.1.27 regjsparser: 0.10.0 - semver: 7.6.3 + semver: 7.7.3 strip-indent: 3.0.0 dev: true @@ -26809,54 +27359,6 @@ packages: transitivePeerDependencies: - supports-color - /eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.3.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@8.1.1) - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.1 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true - /eslint@9.14.0: resolution: {integrity: sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -26882,7 +27384,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.5 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint-scope: 8.2.0 eslint-visitor-keys: 4.2.0 @@ -26970,7 +27472,7 @@ packages: /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 dev: true /esutils@2.0.3: @@ -27190,7 +27692,7 @@ packages: resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} engines: {node: '>=10'} dependencies: - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 get-stream: 5.2.0 human-signals: 1.1.1 is-stream: 2.0.1 @@ -27205,7 +27707,7 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} dependencies: - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -27220,7 +27722,7 @@ packages: resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} dependencies: - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 4.3.1 is-stream: 3.0.0 @@ -27366,7 +27868,7 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -27828,13 +28330,12 @@ packages: engines: {node: '>= 0.4'} dependencies: is-callable: 1.2.7 - dev: true /foreground-child@2.0.0: resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} engines: {node: '>=8.0.0'} dependencies: - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 signal-exit: 3.0.7 dev: true @@ -27842,7 +28343,7 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} dependencies: - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 signal-exit: 4.1.0 dev: true @@ -27999,7 +28500,7 @@ packages: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 functions-have-names: 1.2.3 /function.prototype.name@1.1.8: @@ -28679,7 +29180,7 @@ packages: pretty-error: 2.1.2 tapable: 1.1.3 util.promisify: 1.0.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /html-webpack-plugin@5.6.5(webpack@5.103.0): @@ -28699,7 +29200,7 @@ packages: lodash: 4.17.21 pretty-error: 4.0.0 tapable: 2.3.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /html2canvas@1.4.1: @@ -28823,7 +29324,7 @@ packages: engines: {node: '>= 6.0.0'} dependencies: agent-base: 5.1.1 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -28833,7 +29334,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -29149,7 +29650,7 @@ packages: /is-bun-module@1.2.1: resolution: {integrity: sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==} dependencies: - semver: 7.6.3 + semver: 7.7.3 /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} @@ -29173,7 +29674,6 @@ packages: engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 - dev: true /is-data-view@1.0.1: resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} @@ -29483,7 +29983,7 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} dependencies: - which-typed-array: 1.1.18 + which-typed-array: 1.1.19 /is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} @@ -29622,7 +30122,7 @@ packages: resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -29647,7 +30147,7 @@ packages: engines: {node: '>=8'} dependencies: archy: 1.0.0 - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 istanbul-lib-coverage: 3.2.2 p-map: 3.0.0 rimraf: 3.0.2 @@ -29667,7 +30167,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -29752,7 +30252,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-mock: 29.7.0 jest-util: 29.7.0 dev: false @@ -29807,7 +30307,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-util: 29.7.0 dev: false @@ -29841,7 +30341,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.19.25 + '@types/node': 22.10.7 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -29949,7 +30449,6 @@ packages: hasBin: true dependencies: argparse: 2.0.1 - dev: true /jsbi@3.2.5: resolution: {integrity: sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ==} @@ -29976,18 +30475,18 @@ packages: '@babel/preset-env': optional: true dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.2 - '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.28.5) '@babel/preset-env': 7.26.0(@babel/core@7.26.0) - '@babel/preset-flow': 7.25.9(@babel/core@7.26.0) - '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) - '@babel/register': 7.25.9(@babel/core@7.26.0) - babel-core: 7.0.0-bridge.0(@babel/core@7.26.0) + '@babel/preset-flow': 7.25.9(@babel/core@7.28.5) + '@babel/preset-typescript': 7.26.0(@babel/core@7.28.5) + '@babel/register': 7.25.9(@babel/core@7.28.5) + babel-core: 7.0.0-bridge.0(@babel/core@7.28.5) chalk: 4.1.2 flow-parser: 0.252.0 graceful-fs: 4.2.11 @@ -30629,7 +31128,7 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} dependencies: - semver: 7.6.3 + semver: 7.7.3 dev: true /make-error-cause@2.3.0: @@ -30971,7 +31470,7 @@ packages: resolution: {integrity: sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==} engines: {node: '>=20.19.4'} dependencies: - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@5.5.0) fb-watchman: 2.0.2 flow-enums-runtime: 0.0.6 graceful-fs: 4.2.11 @@ -31093,7 +31592,7 @@ packages: chalk: 4.1.2 ci-info: 2.0.0 connect: 3.7.0 - debug: 4.4.3(supports-color@8.1.1) + debug: 4.4.3(supports-color@5.5.0) error-stack-parser: 2.1.4 flow-enums-runtime: 0.0.6 graceful-fs: 4.2.11 @@ -31359,7 +31858,7 @@ packages: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} dependencies: '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -31947,7 +32446,7 @@ packages: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.8 + resolve: 1.22.11 semver: 5.7.2 validate-npm-package-license: 3.0.4 dev: true @@ -32077,7 +32576,6 @@ packages: /object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} - dev: true /object-is@1.1.6: resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} @@ -32998,7 +33496,7 @@ packages: postcss: 8.4.49 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.8 + resolve: 1.22.11 dev: true /postcss-js@4.0.1(postcss@8.4.49): @@ -33398,7 +33896,7 @@ packages: engines: {node: '>=8.16.0'} dependencies: '@types/mime-types': 2.1.4 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 @@ -34664,7 +35162,6 @@ packages: get-proto: 1.0.1 gopd: 1.2.0 set-function-name: 2.0.2 - dev: true /regexpu-core@6.1.1: resolution: {integrity: sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==} @@ -34843,7 +35340,7 @@ packages: /resolve@1.19.0: resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} dependencies: - is-core-module: 2.15.1 + is-core-module: 2.16.1 path-parse: 1.0.7 dev: true @@ -34855,7 +35352,6 @@ packages: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} @@ -34981,7 +35477,7 @@ packages: dependencies: open: 8.4.2 picomatch: 2.3.1 - source-map: 0.7.4 + source-map: 0.7.6 yargs: 17.7.2 dev: false @@ -35504,7 +36000,6 @@ packages: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true - dev: false /send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} @@ -35827,7 +36322,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) socks: 2.8.5 transitivePeerDependencies: - supports-color @@ -35890,7 +36385,7 @@ packages: git-hooks-list: 3.1.0 globby: 13.2.2 is-plain-obj: 4.1.0 - semver: 7.6.3 + semver: 7.7.3 sort-object-keys: 1.1.3 dev: true @@ -35917,15 +36412,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - /source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - dev: false - /source-map@0.7.6: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} - dev: true /source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} @@ -35980,7 +36469,7 @@ packages: /spdy-transport@3.0.0: resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -35994,7 +36483,7 @@ packages: resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} engines: {node: '>=6.0.0'} dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) handle-thing: 2.0.1 http-deceiver: 1.2.7 select-hose: 2.0.0 @@ -36010,7 +36499,7 @@ packages: webpack: ^1 || ^2 || ^3 || ^4 || ^5 dependencies: chalk: 4.1.2 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /split-on-first@1.1.0: @@ -36110,7 +36599,6 @@ packages: dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 - dev: true /store2@2.14.4: resolution: {integrity: sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==} @@ -36218,7 +36706,7 @@ packages: call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 @@ -36228,7 +36716,7 @@ packages: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.24.0 es-object-atoms: 1.1.1 /string.prototype.trimend@1.0.8: @@ -36448,7 +36936,7 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.13 commander: 4.1.1 glob: 10.4.5 lines-and-columns: 1.2.4 @@ -36717,7 +37205,7 @@ packages: unique-string: 2.0.0 dev: true - /terser-webpack-plugin@5.3.14(esbuild@0.27.0)(webpack@5.103.0): + /terser-webpack-plugin@5.3.14(esbuild@0.24.0)(webpack@5.103.0): resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -36734,12 +37222,12 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.31 - esbuild: 0.27.0 + esbuild: 0.24.0 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 terser: 5.44.1 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /terser@4.8.1: @@ -37059,15 +37547,6 @@ packages: typescript: 5.3.2 dev: true - /ts-api-utils@1.4.0(typescript@5.9.3): - resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - dependencies: - typescript: 5.9.3 - dev: true - /ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} @@ -37185,7 +37664,7 @@ packages: cac: 6.7.14 chokidar: 4.0.1 consola: 3.4.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) esbuild: 0.24.0 joycon: 3.1.1 picocolors: 1.1.1 @@ -37229,7 +37708,7 @@ packages: cac: 6.7.14 chokidar: 4.0.1 consola: 3.4.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) esbuild: 0.24.0 joycon: 3.1.1 picocolors: 1.1.1 @@ -37250,33 +37729,33 @@ packages: - yaml dev: true - /tsutils@3.21.0(typescript@5.3.2): + /tsutils@3.21.0(typescript@5.1.6): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.3.2 + typescript: 5.1.6 + dev: true - /tsutils@3.21.0(typescript@5.3.3): + /tsutils@3.21.0(typescript@5.3.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.3.3 - dev: true + typescript: 5.3.2 - /tsutils@3.21.0(typescript@5.9.3): + /tsutils@3.21.0(typescript@5.3.3): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.9.3 + typescript: 5.3.3 dev: true /tunnel-agent@0.6.0: @@ -37450,7 +37929,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.8 - for-each: 0.3.3 + for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 @@ -37559,12 +38038,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - /typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - dev: true - /ua-is-frozen@0.1.2: resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} dev: false @@ -37659,10 +38132,7 @@ packages: /undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - /undici-types@7.10.0: - resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} - dev: false + dev: true /undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -37806,7 +38276,7 @@ packages: resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} engines: {node: '>=14.0.0'} dependencies: - acorn: 8.14.0 + acorn: 8.15.0 webpack-virtual-modules: 0.6.2 dev: true @@ -38561,7 +39031,7 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.1.1 vite: 5.4.11(@types/node@20.17.6) @@ -38638,7 +39108,7 @@ packages: peerDependencies: vite: ^2.6.0 || 3 || 4 dependencies: - '@rollup/pluginutils': 5.1.3(rollup@3.29.5) + '@rollup/pluginutils': 5.1.3 '@svgr/core': 6.5.1 vite: 4.5.9(@types/node@20.17.6) transitivePeerDependencies: @@ -38651,7 +39121,7 @@ packages: peerDependencies: vite: '>=2.6.0' dependencies: - '@rollup/pluginutils': 5.1.3(rollup@3.29.5) + '@rollup/pluginutils': 5.1.3 '@svgr/core': 8.1.0(typescript@5.3.2) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0) vite: 5.4.11(@types/node@20.17.6) @@ -38819,7 +39289,7 @@ packages: '@vitest/utils': 1.6.1 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.1 magic-string: 0.30.12 @@ -39082,7 +39552,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.3.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /webpack-dev-server@4.15.2(debug@4.3.7)(webpack@5.103.0): @@ -39126,7 +39596,7 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) webpack-dev-middleware: 5.3.4(webpack@5.103.0) ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: @@ -39154,7 +39624,7 @@ packages: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} dev: true - /webpack@5.103.0(esbuild@0.27.0): + /webpack@5.103.0(esbuild@0.24.0): resolution: {integrity: sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==} engines: {node: '>=10.13.0'} hasBin: true @@ -39186,7 +39656,7 @@ packages: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.14(esbuild@0.27.0)(webpack@5.103.0) + terser-webpack-plugin: 5.3.14(esbuild@0.24.0)(webpack@5.103.0) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -39314,7 +39784,6 @@ packages: get-proto: 1.0.1 gopd: 1.2.0 has-tostringtag: 1.0.2 - dev: true /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} From c07b50e27974bf2050e65baf19f413661ab6eb61 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 22:52:08 +0700 Subject: [PATCH 64/87] fix: remove component from app --- .../UniswapPriceSlider/PriceAxis.tsx | 158 ------- .../UniswapPriceSlider/Skeleton.tsx | 43 -- .../UniswapPriceSlider/constants.ts | 43 -- .../components/UniswapPriceSlider/hooks.ts | 320 -------------- .../components/UniswapPriceSlider/index.tsx | 394 ------------------ .../components/UniswapPriceSlider/styles.ts | 308 -------------- .../components/UniswapPriceSlider/types.ts | 44 -- .../components/UniswapPriceSlider/utils.ts | 112 ----- 8 files changed, 1422 deletions(-) delete mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx delete mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/Skeleton.tsx delete mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts delete mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts delete mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx delete mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts delete mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/types.ts delete mode 100644 apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx deleted file mode 100644 index b3b2b90faa..0000000000 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/PriceAxis.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { tickToPrice } from '@kyber/utils/dist/uniswapv3' -import React, { useMemo } from 'react' - -import { MAX_AXIS_TICK_COUNT, MIN_AXIS_TICK_COUNT } from 'components/UniswapPriceSlider/constants' -import { PriceAxisContainer, PriceAxisLabel, PriceAxisLine, PriceAxisTick } from 'components/UniswapPriceSlider/styles' -import type { PriceAxisProps } from 'components/UniswapPriceSlider/types' -import { formatAxisPrice } from 'components/UniswapPriceSlider/utils' - -/** - * Calculate the optimal number of ticks and minimum gap based on price range - * More ticks for small ranges, fewer for large ranges - */ -const getOptimalTickConfig = (minPrice: number, maxPrice: number): { tickCount: number; minGapPercent: number } => { - if (minPrice <= 0 || maxPrice <= 0) { - return { tickCount: 7, minGapPercent: 15 } - } - - // Calculate how many orders of magnitude the prices span - const priceRatio = maxPrice / minPrice - const ordersOfMagnitude = Math.log10(priceRatio) - - // Very small range (< 0.5 orders): many ticks, small gap - if (ordersOfMagnitude <= 0.5) { - return { tickCount: MAX_AXIS_TICK_COUNT, minGapPercent: 12 } - } - // Small range (0.5 - 1 order): good amount of ticks - if (ordersOfMagnitude <= 1) { - return { tickCount: 9, minGapPercent: 14 } - } - // Medium range (1 - 2 orders) - if (ordersOfMagnitude <= 2) { - return { tickCount: 7, minGapPercent: 16 } - } - // Large range (2 - 4 orders) - if (ordersOfMagnitude <= 4) { - return { tickCount: 5, minGapPercent: 20 } - } - // Very large range (4 - 8 orders) - if (ordersOfMagnitude <= 8) { - return { tickCount: 3, minGapPercent: 30 } - } - // Extreme range (> 8 orders): just first and last - return { tickCount: MIN_AXIS_TICK_COUNT, minGapPercent: 40 } -} - -/** - * Calculate tick positions for the axis - * Uses tick-space for even distribution (matching the slider) - * When invertPrice, positions are flipped so lower inverted price is on left - */ -const calculateAxisTicks = ( - viewRange: { min: number; max: number }, - token0Decimals: number, - token1Decimals: number, - count: number, - invertPrice?: boolean, -): Array<{ tick: number; price: number; position: number }> => { - const tickRange = viewRange.max - viewRange.min - if (tickRange <= 0) return [] - - const step = tickRange / (count - 1) - const ticks: Array<{ tick: number; price: number; position: number }> = [] - - for (let i = 0; i < count; i++) { - const tick = Math.round(viewRange.min + step * i) - const price = +tickToPrice(tick, token0Decimals, token1Decimals, invertPrice) - const normalPosition = ((tick - viewRange.min) / tickRange) * 100 - // When invertPrice, flip position so lower inverted price (from higher tick) is on left - const position = invertPrice ? 100 - normalPosition : normalPosition - - ticks.push({ tick, price, position }) - } - - return ticks -} - -/** - * Filter ticks to ensure minimum spacing between labels - * Only shows labels that have sufficient gap from previous label - */ -const filterOverlappingTicks = ( - ticks: Array<{ tick: number; price: number; position: number }>, - minGapPercent: number, -): Array<{ tick: number; price: number; position: number; showLabel: boolean }> => { - if (ticks.length === 0) return [] - - const result: Array<{ tick: number; price: number; position: number; showLabel: boolean }> = [] - let lastLabelPosition = -Infinity - - for (let i = 0; i < ticks.length; i++) { - const tick = ticks[i] - const isFirst = i === 0 - const isLast = i === ticks.length - 1 - const gap = tick.position - lastLabelPosition - - // First tick always shows label - if (isFirst) { - lastLabelPosition = tick.position - result.push({ ...tick, showLabel: true }) - continue - } - - // Last tick: only show if enough gap, otherwise hide - if (isLast) { - const showLabel = gap >= minGapPercent - if (showLabel) lastLabelPosition = tick.position - result.push({ ...tick, showLabel }) - continue - } - - // Middle ticks: show if enough gap from previous label - const showLabel = gap >= minGapPercent - if (showLabel) lastLabelPosition = tick.position - result.push({ ...tick, showLabel }) - } - - return result -} - -/** - * Price axis component that displays price scale below the slider - * Uses tick-based positioning to match the slider exactly - * Dynamically reduces tick count when price range is very large - */ -function PriceAxis({ viewRange, token0Decimals, token1Decimals, invertPrice }: PriceAxisProps) { - const axisTicks = useMemo(() => { - // Get min and max prices to determine optimal tick config - const minPrice = +tickToPrice(Math.round(viewRange.min), token0Decimals, token1Decimals, invertPrice) - const maxPrice = +tickToPrice(Math.round(viewRange.max), token0Decimals, token1Decimals, invertPrice) - const { tickCount, minGapPercent } = getOptimalTickConfig( - Math.min(minPrice, maxPrice), - Math.max(minPrice, maxPrice), - ) - - const ticks = calculateAxisTicks(viewRange, token0Decimals, token1Decimals, tickCount, invertPrice) - // Sort by position ascending for proper overlap filtering - const sortedTicks = [...ticks].sort((a, b) => a.position - b.position) - return filterOverlappingTicks(sortedTicks, minGapPercent) - }, [viewRange, token0Decimals, token1Decimals, invertPrice]) - - return ( - - - {axisTicks.map(({ tick, price, position, showLabel }) => { - // Only render if within visible range - if (position < -2 || position > 102) return null - return ( - - - {showLabel && {formatAxisPrice(price)}} - - ) - })} - - ) -} - -export default React.memo(PriceAxis) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/Skeleton.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/Skeleton.tsx deleted file mode 100644 index 69e2f260d5..0000000000 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/Skeleton.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react' - -import { SKELETON_AXIS_POSITIONS } from 'components/UniswapPriceSlider/constants' -import { - SkeletonAxisContainer, - SkeletonAxisLabel, - SkeletonAxisLine, - SkeletonAxisTick, - SkeletonCurrentPrice, - SkeletonHandle, - SkeletonPriceLabel, - SkeletonRange, - SkeletonSliderArea, - SkeletonTrack, - SkeletonWrapper, -} from 'components/UniswapPriceSlider/styles' - -function PriceSliderSkeleton() { - return ( - - - - - - - - - - - - - {SKELETON_AXIS_POSITIONS.map(pos => ( - - - - - ))} - - - ) -} - -export default React.memo(PriceSliderSkeleton) diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts deleted file mode 100644 index 9e7b43c897..0000000000 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/constants.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Animation Constants -/** Duration for zoom/auto-center animation in milliseconds */ -export const ZOOM_DURATION = 400 - -/** Delay before committing tick changes to parent in milliseconds */ -export const DEBOUNCE_DELAY = 150 - -/** Minimum LERP factor when handle is far from target (slower movement) */ -export const HANDLE_LERP_MIN = 0.15 - -/** Maximum LERP factor when handle is close to target (faster movement) */ -export const HANDLE_LERP_MAX = 0.4 - -/** Maximum ticks per frame to prevent jumpy handle movement */ -export const MAX_TICK_SPEED = 2000 - -// Slider Behavior Constants -/** Percentage from edge that triggers zoom out (ensures price labels visible) */ -export const EDGE_THRESHOLD = 18 - -/** Percentage padding on each side when auto-centering after drag */ -export const AUTO_CENTER_PADDING = 25 - -/** Minimum tick spacings between handles to prevent overlap (keep small for large tickSpacing pools) */ -export const MIN_HANDLE_DISTANCE_MULTIPLIER = 1 - -// Dynamic LERP Constants -/** Distance threshold (in ticks) for minimum lerp factor */ -export const LERP_FAR_THRESHOLD = 5000 - -/** Distance threshold (in ticks) for maximum lerp factor */ -export const LERP_CLOSE_THRESHOLD = 100 - -// Price Axis Constants -/** Maximum number of ticks on price axis for small ranges */ -export const MAX_AXIS_TICK_COUNT = 11 - -/** Minimum number of ticks on price axis for extreme ranges */ -export const MIN_AXIS_TICK_COUNT = 2 - -// Skeleton Constants -/** Positions for skeleton axis ticks (percentage) */ -export const SKELETON_AXIS_POSITIONS = [0, 16.6, 33.3, 50, 66.6, 83.3, 100] diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts deleted file mode 100644 index e8a1ae7ac5..0000000000 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/hooks.ts +++ /dev/null @@ -1,320 +0,0 @@ -import { nearestUsableTick } from '@kyber/utils/dist/uniswapv3' -import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react' - -import { - DEBOUNCE_DELAY, - HANDLE_LERP_MAX, - HANDLE_LERP_MIN, - LERP_CLOSE_THRESHOLD, - LERP_FAR_THRESHOLD, - MAX_TICK_SPEED, - ZOOM_DURATION, -} from 'components/UniswapPriceSlider/constants' -import type { ViewRange } from 'components/UniswapPriceSlider/types' - -/** Easing function: ease-out cubic for smooth deceleration */ -const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3) - -/** - * Hook for smooth zoom animation using easing function - * Uses ease-out for natural deceleration feel - */ -export const useSmoothZoom = ( - viewRange: ViewRange | null, - setViewRange: Dispatch>, -) => { - const zoomAnimationRef = useRef(null) - const targetViewRangeRef = useRef(null) - const startViewRangeRef = useRef(null) - const startTimeRef = useRef(0) - - const animateZoom = useCallback(() => { - if (!targetViewRangeRef.current || !startViewRangeRef.current) { - zoomAnimationRef.current = null - return - } - - const now = performance.now() - const elapsed = now - startTimeRef.current - const progress = Math.min(elapsed / ZOOM_DURATION, 1) - const easedProgress = easeOutCubic(progress) - - const start = startViewRangeRef.current - const target = targetViewRangeRef.current - - const newMin = start.min + (target.min - start.min) * easedProgress - const newMax = start.max + (target.max - start.max) * easedProgress - - setViewRange({ min: newMin, max: newMax }) - - if (progress < 1) { - // Continue animation - zoomAnimationRef.current = requestAnimationFrame(animateZoom) - } else { - // Animation complete - set exact target values - setViewRange(target) - targetViewRangeRef.current = null - startViewRangeRef.current = null - zoomAnimationRef.current = null - } - }, [setViewRange]) - - const startSmoothZoom = useCallback( - (targetMin: number, targetMax: number) => { - // If already animating, use current position as new start - if (zoomAnimationRef.current && viewRange) { - startViewRangeRef.current = viewRange - } else if (viewRange) { - startViewRangeRef.current = viewRange - } - - targetViewRangeRef.current = { min: targetMin, max: targetMax } - startTimeRef.current = performance.now() - - if (!zoomAnimationRef.current) { - zoomAnimationRef.current = requestAnimationFrame(animateZoom) - } - }, - [animateZoom, viewRange], - ) - - // Cleanup animation on unmount - useEffect(() => { - return () => { - if (zoomAnimationRef.current) { - cancelAnimationFrame(zoomAnimationRef.current) - } - } - }, []) - - return { startSmoothZoom } -} - -/** - * Hook for smooth tick updates with animation and debouncing - * Handles move slowly/smoothly towards target position - */ -export const useDebouncedTicks = ( - lowerTick: number | undefined, - upperTick: number | undefined, - setLowerTick: (tick: number) => void, - setUpperTick: (tick: number) => void, - isDragging: boolean, -) => { - const debounceTimerRef = useRef(null) - const animationRef = useRef(null) - - // Target ticks (where user wants to go) - const targetLowerTickRef = useRef(lowerTick) - const targetUpperTickRef = useRef(upperTick) - - // Current internal tick values (tracked via refs for animation loop) - const internalLowerRef = useRef(lowerTick) - const internalUpperRef = useRef(upperTick) - - // Internal tick state for React rendering - const [internalLowerTick, setInternalLowerTick] = useState(lowerTick) - const [internalUpperTick, setInternalUpperTick] = useState(upperTick) - - // Helper: calculate dynamic lerp factor based on distance - const getDynamicLerp = useCallback((diff: number): number => { - const absDiff = Math.abs(diff) - if (absDiff > LERP_FAR_THRESHOLD) return HANDLE_LERP_MIN - if (absDiff < LERP_CLOSE_THRESHOLD) return HANDLE_LERP_MAX - const t = (absDiff - LERP_CLOSE_THRESHOLD) / (LERP_FAR_THRESHOLD - LERP_CLOSE_THRESHOLD) - return HANDLE_LERP_MAX - t * (HANDLE_LERP_MAX - HANDLE_LERP_MIN) - }, []) - - // Animation function for smooth handle movement - const animateHandles = useCallback(() => { - let needsAnimation = false - - // Update lower tick - if (internalLowerRef.current !== undefined && targetLowerTickRef.current !== undefined) { - const current = internalLowerRef.current - const target = targetLowerTickRef.current - const diff = target - current - - if (Math.abs(diff) >= 1) { - needsAnimation = true - const lerpFactor = getDynamicLerp(diff) - const lerpMovement = diff * lerpFactor - const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED) - const newValue = current + cappedMovement - internalLowerRef.current = newValue - setInternalLowerTick(newValue) - } else if (current !== target) { - internalLowerRef.current = target - setInternalLowerTick(target) - } - } - - // Update upper tick - if (internalUpperRef.current !== undefined && targetUpperTickRef.current !== undefined) { - const current = internalUpperRef.current - const target = targetUpperTickRef.current - const diff = target - current - - if (Math.abs(diff) >= 1) { - needsAnimation = true - const lerpFactor = getDynamicLerp(diff) - const lerpMovement = diff * lerpFactor - const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED) - const newValue = current + cappedMovement - internalUpperRef.current = newValue - setInternalUpperTick(newValue) - } else if (current !== target) { - internalUpperRef.current = target - setInternalUpperTick(target) - } - } - - if (needsAnimation) { - animationRef.current = requestAnimationFrame(animateHandles) - } else { - animationRef.current = null - } - }, [getDynamicLerp]) - - // Start animation if not already running - const startAnimation = useCallback(() => { - if (!animationRef.current) { - animationRef.current = requestAnimationFrame(animateHandles) - } - }, [animateHandles]) - - // Sync internal state with props when not dragging - useEffect(() => { - if (!isDragging) { - targetLowerTickRef.current = lowerTick - targetUpperTickRef.current = upperTick - internalLowerRef.current = lowerTick - internalUpperRef.current = upperTick - setInternalLowerTick(lowerTick) - setInternalUpperTick(upperTick) - } - }, [lowerTick, upperTick, isDragging]) - - // Smooth update functions - set target and start animation - const debouncedSetLowerTick = useCallback( - (tick: number) => { - targetLowerTickRef.current = tick - startAnimation() - - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current) - } - debounceTimerRef.current = setTimeout(() => { - setLowerTick(tick) - }, DEBOUNCE_DELAY) - }, - [setLowerTick, startAnimation], - ) - - const debouncedSetUpperTick = useCallback( - (tick: number) => { - targetUpperTickRef.current = tick - startAnimation() - - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current) - } - debounceTimerRef.current = setTimeout(() => { - setUpperTick(tick) - }, DEBOUNCE_DELAY) - }, - [setUpperTick, startAnimation], - ) - - // Flush debounced values immediately - const flushDebouncedValues = useCallback(() => { - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current) - debounceTimerRef.current = null - } - - // Set final values from targets - const finalLower = targetLowerTickRef.current - const finalUpper = targetUpperTickRef.current - - if (finalLower !== undefined && finalLower !== lowerTick) { - setLowerTick(finalLower) - internalLowerRef.current = finalLower - setInternalLowerTick(finalLower) - } - if (finalUpper !== undefined && finalUpper !== upperTick) { - setUpperTick(finalUpper) - internalUpperRef.current = finalUpper - setInternalUpperTick(finalUpper) - } - - // Stop animation - if (animationRef.current) { - cancelAnimationFrame(animationRef.current) - animationRef.current = null - } - }, [lowerTick, upperTick, setLowerTick, setUpperTick]) - - // Cleanup on unmount - useEffect(() => { - return () => { - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current) - } - if (animationRef.current) { - cancelAnimationFrame(animationRef.current) - } - } - }, []) - - // Get target ticks (what user actually wants, not the animated value) - const getTargetTicks = useCallback(() => { - return { - lowerTick: targetLowerTickRef.current, - upperTick: targetUpperTickRef.current, - } - }, []) - - return { - internalLowerTick, - internalUpperTick, - debouncedSetLowerTick, - debouncedSetUpperTick, - flushDebouncedValues, - getTargetTicks, - } -} - -/** - * Hook for converting between tick and position - * When invertPrice = true, the entire visual is flipped: - * - Lower tick (higher inverted price) appears on the RIGHT - * - Upper tick (lower inverted price) appears on the LEFT - * - Axis shows inverted prices from low (left) to high (right) - */ -export const useTickPositionConverter = (viewRange: ViewRange | null, tickSpacing: number, invertPrice?: boolean) => { - const getPositionFromTick = useCallback( - (tick: number): number => { - if (!viewRange) return 50 - const { min, max } = viewRange - const normalPosition = ((tick - min) / (max - min)) * 100 - // When invertPrice, flip the position so higher inverted price is on the right - return invertPrice ? 100 - normalPosition : normalPosition - }, - [viewRange, invertPrice], - ) - - const getTickFromPosition = useCallback( - (position: number): number => { - if (!viewRange) return 0 - const { min, max } = viewRange - // When invertPrice, flip the position first - const actualPosition = invertPrice ? 100 - position : position - const tick = min + (actualPosition / 100) * (max - min) - return nearestUsableTick(Math.round(tick), tickSpacing) - }, - [viewRange, tickSpacing, invertPrice], - ) - - return { getPositionFromTick, getTickFromPosition } -} diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx b/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx deleted file mode 100644 index bd46348033..0000000000 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/index.tsx +++ /dev/null @@ -1,394 +0,0 @@ -import { MAX_TICK, MIN_TICK, tickToPrice } from '@kyber/utils/dist/uniswapv3' -import React, { useCallback, useEffect, useRef, useState } from 'react' - -import PriceAxis from 'components/UniswapPriceSlider/PriceAxis' -import PriceSliderSkeleton from 'components/UniswapPriceSlider/Skeleton' -import { - AUTO_CENTER_PADDING, - EDGE_THRESHOLD, - MIN_HANDLE_DISTANCE_MULTIPLIER, -} from 'components/UniswapPriceSlider/constants' -import { useDebouncedTicks, useSmoothZoom, useTickPositionConverter } from 'components/UniswapPriceSlider/hooks' -import { - CurrentPriceMarker, - CurrentPriceMarkerWrapper, - CurrentPriceTooltip, - Handle, - PriceLabel, - SliderContainer, - SliderRange, - SliderTrack, - SliderWrapper, -} from 'components/UniswapPriceSlider/styles' -import type { HandleType, UniswapPriceSliderProps, ViewRange } from 'components/UniswapPriceSlider/types' -import { brushHandlePath, getEdgeIntensity } from 'components/UniswapPriceSlider/utils' -import { formatDisplayNumber } from 'utils/numbers' - -function UniswapPriceSlider({ - pool, - invertPrice, - lowerTick, - upperTick, - setLowerTick, - setUpperTick, -}: UniswapPriceSliderProps) { - const { tickSpacing, token0Decimals, token1Decimals, currentTick } = pool - - const [viewRange, setViewRange] = useState(null) - const [isDragging, setIsDragging] = useState(null) - - const sliderRef = useRef(null) - const isInitialized = useRef(false) - const viewRangeRef = useRef(viewRange) - const lastAdjustedTicksRef = useRef<{ lower: number; upper: number } | null>(null) - - // Keep viewRangeRef in sync with viewRange state - useEffect(() => { - viewRangeRef.current = viewRange - }, [viewRange]) - - const { startSmoothZoom } = useSmoothZoom(viewRange, setViewRange) - - const { - internalLowerTick, - internalUpperTick, - debouncedSetLowerTick, - debouncedSetUpperTick, - flushDebouncedValues, - getTargetTicks, - } = useDebouncedTicks(lowerTick, upperTick, setLowerTick, setUpperTick, isDragging !== null) - - const { getPositionFromTick, getTickFromPosition } = useTickPositionConverter(viewRange, tickSpacing, invertPrice) - - const ticksReady = lowerTick !== undefined && upperTick !== undefined && upperTick > lowerTick - - // Initialize View Range - useEffect(() => { - if (isInitialized.current || !ticksReady) return - - const tickRange = Math.abs(upperTick - lowerTick) - const padding = Math.max(tickRange * 0.5, tickSpacing * 50) - - const minTick = Math.min(lowerTick, upperTick, currentTick) - const maxTick = Math.max(lowerTick, upperTick, currentTick) - - setViewRange({ - min: Math.max(MIN_TICK, minTick - padding), - max: Math.min(MAX_TICK, maxTick + padding), - }) - isInitialized.current = true - }, [lowerTick, upperTick, currentTick, tickSpacing, ticksReady]) - - // Auto-adjust viewRange when ticks change from outside (e.g., input fields) - useEffect(() => { - // Skip if not initialized, dragging, or ticks not ready - if (!isInitialized.current || isDragging || !ticksReady || !viewRange) return - - // Skip if already adjusted for these exact tick values - const lastAdjusted = lastAdjustedTicksRef.current - if (lastAdjusted && lastAdjusted.lower === lowerTick && lastAdjusted.upper === upperTick) return - - const currentRange = viewRange.max - viewRange.min - const lowerPos = ((lowerTick - viewRange.min) / currentRange) * 100 - const upperPos = ((upperTick - viewRange.min) / currentRange) * 100 - - // Check if handles are outside visible area or positioned poorly - const handleOutsideLeft = lowerPos < -5 || upperPos < -5 - const handleOutsideRight = lowerPos > 105 || upperPos > 105 - const handleSpan = Math.abs(upperPos - lowerPos) - const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING - const handlesTooClose = handleSpan < idealHandleSpan * 0.4 // Much smaller than ideal - const handlesTooFar = handleSpan > idealHandleSpan * 2 // Much larger than ideal - - // If adjustment needed, calculate new viewRange - if (handleOutsideLeft || handleOutsideRight || handlesTooClose || handlesTooFar) { - const tickDistance = Math.abs(upperTick - lowerTick) - const handleCenter = (lowerTick + upperTick) / 2 - - const idealPadding = tickDistance * (AUTO_CENTER_PADDING / (100 - 2 * AUTO_CENTER_PADDING)) - const minPadding = Math.max(tickDistance * 0.3, tickSpacing * 20) - const padding = Math.max(idealPadding, minPadding) - - const targetMin = Math.max(MIN_TICK, handleCenter - tickDistance / 2 - padding) - const targetMax = Math.min(MAX_TICK, handleCenter + tickDistance / 2 + padding) - - // Mark these ticks as adjusted to prevent re-triggering - lastAdjustedTicksRef.current = { lower: lowerTick, upper: upperTick } - startSmoothZoom(targetMin, targetMax) - } - }, [lowerTick, upperTick, isDragging, ticksReady, viewRange, tickSpacing, startSmoothZoom]) - - const handleMouseDown = useCallback( - (handle: 'lower' | 'upper') => (e: React.MouseEvent) => { - e.preventDefault() - setIsDragging(handle) - }, - [], - ) - - const handleTouchStart = useCallback( - (handle: 'lower' | 'upper') => (e: React.TouchEvent) => { - e.preventDefault() - setIsDragging(handle) - }, - [], - ) - - // Shared logic for handling drag movement (mouse or touch) - const handleDragMove = useCallback( - (clientX: number) => { - if (!isDragging || !sliderRef.current || !viewRange || lowerTick === undefined || upperTick === undefined) return - - const rect = sliderRef.current.getBoundingClientRect() - const x = clientX - rect.left - const position = Math.max(0, Math.min(100, (x / rect.width) * 100)) - const newTick = getTickFromPosition(position) - - const currentRange = viewRange.max - viewRange.min - - // Check if near edges for zoom out - const isNearLeftEdge = position < EDGE_THRESHOLD - const isNearRightEdge = position > 100 - EDGE_THRESHOLD - const edgeIntensity = getEdgeIntensity(position, EDGE_THRESHOLD) - - // Zoom out when near edges (zoom-in is handled by auto-center on mouse up) - if (isNearLeftEdge || isNearRightEdge) { - const baseExpansion = currentRange * 0.25 - const expansion = baseExpansion * edgeIntensity - - let targetMin = viewRange.min - let targetMax = viewRange.max - - if (isNearLeftEdge && viewRange.min > MIN_TICK) { - targetMin = Math.max(MIN_TICK, viewRange.min - expansion) - } - if (isNearRightEdge && viewRange.max < MAX_TICK) { - targetMax = Math.min(MAX_TICK, viewRange.max + expansion) - } - - startSmoothZoom(targetMin, targetMax) - } - - // Update tick values (with minimum distance between handles) - if (isDragging === 'lower') { - const maxLower = (internalUpperTick ?? upperTick ?? 0) - tickSpacing * MIN_HANDLE_DISTANCE_MULTIPLIER - debouncedSetLowerTick(Math.min(newTick, maxLower)) - } else { - const minUpper = (internalLowerTick ?? lowerTick ?? 0) + tickSpacing * MIN_HANDLE_DISTANCE_MULTIPLIER - debouncedSetUpperTick(Math.max(newTick, minUpper)) - } - }, - [ - debouncedSetLowerTick, - debouncedSetUpperTick, - getTickFromPosition, - internalLowerTick, - internalUpperTick, - isDragging, - lowerTick, - startSmoothZoom, - tickSpacing, - upperTick, - viewRange, - ], - ) - - const handleMouseMove = useCallback( - (e: MouseEvent) => { - handleDragMove(e.clientX) - }, - [handleDragMove], - ) - - const handleTouchMove = useCallback( - (e: TouchEvent) => { - if (e.touches.length > 0) { - handleDragMove(e.touches[0].clientX) - } - }, - [handleDragMove], - ) - - const handleMouseUp = useCallback(() => { - // Get the TARGET tick values (what user intended), not the animated values - const { lowerTick: targetLower, upperTick: targetUpper } = getTargetTicks() - - // Flush to apply target values immediately - flushDebouncedValues() - setIsDragging(null) - - // Use target ticks for auto-center calculation - const finalLowerTick = targetLower ?? lowerTick - const finalUpperTick = targetUpper ?? upperTick - - if (finalLowerTick === undefined || finalUpperTick === undefined) return - - // Use setTimeout to ensure state has updated before calculating positions - setTimeout(() => { - // Use ref to get the LATEST viewRange (not stale closure value) - const currentViewRange = viewRangeRef.current - if (!currentViewRange) return - - const tickDistance = Math.abs(finalUpperTick - finalLowerTick) - const handleCenter = (finalLowerTick + finalUpperTick) / 2 - - // Calculate ideal padding (25% on each side = handles take up 50% of view) - const idealPadding = tickDistance * (AUTO_CENTER_PADDING / (100 - 2 * AUTO_CENTER_PADDING)) - const minPadding = Math.max(tickDistance * 0.3, tickSpacing * 20) - const padding = Math.max(idealPadding, minPadding) - - const targetMin = Math.max(MIN_TICK, handleCenter - tickDistance / 2 - padding) - const targetMax = Math.min(MAX_TICK, handleCenter + tickDistance / 2 + padding) - - // Calculate current positions using LATEST viewRange from ref - const currentRange = currentViewRange.max - currentViewRange.min - const rawLowerPos = ((finalLowerTick - currentViewRange.min) / currentRange) * 100 - const rawUpperPos = ((finalUpperTick - currentViewRange.min) / currentRange) * 100 - - // Account for invertPrice: when inverted, positions are flipped - const currentLowerPos = invertPrice ? 100 - rawLowerPos : rawLowerPos - const currentUpperPos = invertPrice ? 100 - rawUpperPos : rawUpperPos - - // Left/right padding based on visual positions (not tick order) - const leftPadding = Math.min(currentLowerPos, currentUpperPos) - const rightPadding = 100 - Math.max(currentLowerPos, currentUpperPos) - const handleSpan = Math.abs(currentUpperPos - currentLowerPos) // % of view that handles span - - // Ideal handle span is 50% (100 - 2 * AUTO_CENTER_PADDING) - const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING - const handlesTooClose = handleSpan < idealHandleSpan * 0.6 // Less than 60% of ideal = too zoomed out - const handlesTooFar = handleSpan > idealHandleSpan * 1.5 // More than 150% of ideal = too zoomed in - - // Check if rebalancing is needed - const needsRebalance = - leftPadding < EDGE_THRESHOLD + 5 || // Near left edge - rightPadding < EDGE_THRESHOLD + 5 || // Near right edge - leftPadding < 0 || // Handle outside left - rightPadding < 0 || // Handle outside right - handlesTooClose || // Handles too close together (need zoom in) - handlesTooFar || // Handles too far apart (need zoom out) - (leftPadding > 5 && rightPadding > 5 && (leftPadding / rightPadding > 2.5 || rightPadding / leftPadding > 2.5)) // Imbalanced - - if (needsRebalance) { - startSmoothZoom(targetMin, targetMax) - } - }, 50) - }, [flushDebouncedValues, getTargetTicks, invertPrice, lowerTick, startSmoothZoom, tickSpacing, upperTick]) - - useEffect(() => { - if (!isDragging) return - - // Set grabbing cursor on body to persist while dragging outside handle - document.body.style.cursor = 'grabbing' - document.body.style.userSelect = 'none' - - // Mouse events - document.addEventListener('mousemove', handleMouseMove) - document.addEventListener('mouseup', handleMouseUp) - - // Touch events - document.addEventListener('touchmove', handleTouchMove, { passive: false }) - document.addEventListener('touchend', handleMouseUp) - document.addEventListener('touchcancel', handleMouseUp) - - return () => { - // Reset cursor when dragging ends - document.body.style.cursor = '' - document.body.style.userSelect = '' - - // Remove mouse events - document.removeEventListener('mousemove', handleMouseMove) - document.removeEventListener('mouseup', handleMouseUp) - - // Remove touch events - document.removeEventListener('touchmove', handleTouchMove) - document.removeEventListener('touchend', handleMouseUp) - document.removeEventListener('touchcancel', handleMouseUp) - } - }, [isDragging, handleMouseMove, handleMouseUp, handleTouchMove]) - - if (!ticksReady || !viewRange) { - return - } - - // Use internal ticks for smooth visual updates during dragging - const displayLowerTick = internalLowerTick ?? lowerTick - const displayUpperTick = internalUpperTick ?? upperTick - - // Calculate prices (with invertPrice applied) - const lowerTickPrice = tickToPrice(Math.round(displayLowerTick), token0Decimals, token1Decimals, invertPrice) - const upperTickPrice = tickToPrice(Math.round(displayUpperTick), token0Decimals, token1Decimals, invertPrice) - const currentPrice = tickToPrice(currentTick, token0Decimals, token1Decimals, invertPrice) - - // Calculate positions (flipped when invertPrice=true by the hook) - const lowerPosition = getPositionFromTick(displayLowerTick) - const upperPosition = getPositionFromTick(displayUpperTick) - const currentPosition = getPositionFromTick(currentTick) - - // When invertPrice, positions are flipped so: - // - lowerTick (higher inverted price) is on the RIGHT - // - upperTick (lower inverted price) is on the LEFT - // This means left position = min of the two, right position = max of the two - const leftPosition = Math.min(lowerPosition, upperPosition) - const rightPosition = Math.max(lowerPosition, upperPosition) - - // Determine which tick is at which visual position - const isLowerOnLeft = lowerPosition <= upperPosition - const leftPrice = isLowerOnLeft ? lowerTickPrice : upperTickPrice - const rightPrice = isLowerOnLeft ? upperTickPrice : lowerTickPrice - const leftHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'lower' : 'upper' - const rightHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'upper' : 'lower' - - return ( - - - - - - - - {formatDisplayNumber(currentPrice, { significantDigits: 6 })} - - - {/* Left handle (green) - always shows lower price visually */} - - {formatDisplayNumber(leftPrice, { significantDigits: 6 })} - - - {/* Right handle (blue) - always shows higher price visually */} - - {formatDisplayNumber(rightPrice, { significantDigits: 6 })} - - - - - - - - - - - - - - - - - - ) -} - -export default UniswapPriceSlider diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts deleted file mode 100644 index be8a957f23..0000000000 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/styles.ts +++ /dev/null @@ -1,308 +0,0 @@ -import styled, { keyframes } from 'styled-components' - -// ============================================ -// Skeleton Animation -// ============================================ - -const shimmer = keyframes` - 0% { - background-position: -200% 0; - } - 100% { - background-position: 200% 0; - } -` - -const SkeletonBase = styled.div` - background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%); - background-size: 200% 100%; - animation: ${shimmer} 1.5s ease-in-out infinite; - border-radius: 2px; -` - -// ============================================ -// Skeleton Components -// ============================================ - -export const SkeletonWrapper = styled.div` - width: 100%; - overflow: hidden; -` - -export const SkeletonSliderArea = styled.div` - position: relative; - width: 100%; - height: 60px; - margin: 20px 0 0 0; -` - -export const SkeletonTrack = styled(SkeletonBase)` - position: absolute; - top: 50%; - left: 0; - right: 0; - height: 4px; - transform: translateY(-50%); -` - -export const SkeletonRange = styled(SkeletonBase)` - position: absolute; - top: 50%; - left: 25%; - width: 50%; - height: 4px; - transform: translateY(-50%); -` - -export const SkeletonHandle = styled(SkeletonBase)<{ $isLower: boolean }>` - position: absolute; - top: 50%; - left: ${props => (props.$isLower ? '25%' : '75%')}; - transform: translate(-50%, -50%); - width: 8px; - height: 35px; - border-radius: 6px; -` - -export const SkeletonPriceLabel = styled(SkeletonBase)<{ $isLower: boolean }>` - position: absolute; - top: 4px; - left: ${props => (props.$isLower ? '25%' : '75%')}; - transform: ${props => (props.$isLower ? 'translateX(calc(-100% - 12px))' : 'translateX(12px)')}; - width: 60px; - height: 14px; -` - -export const SkeletonCurrentPrice = styled(SkeletonBase)` - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 2px; - height: 15px; -` - -export const SkeletonAxisContainer = styled.div` - position: relative; - width: 100%; - height: 24px; - margin-top: -8px; -` - -export const SkeletonAxisLine = styled(SkeletonBase)` - position: absolute; - top: 0; - left: 0; - right: 0; - height: 1px; -` - -export const SkeletonAxisTick = styled(SkeletonBase)<{ $position: number }>` - position: absolute; - top: 0; - left: ${props => props.$position}%; - transform: translateX(-50%); - width: 1px; - height: 6px; -` - -export const SkeletonAxisLabel = styled(SkeletonBase)<{ $position: number }>` - position: absolute; - top: 8px; - left: ${props => props.$position}%; - transform: translateX(-50%); - width: 35px; - height: 10px; -` - -// ============================================ -// Main Slider Styles -// ============================================ - -export const SliderContainer = styled.div` - width: 100%; - overflow: hidden; -` - -export const SliderWrapper = styled.div` - position: relative; - width: 100%; - height: 60px; - margin: 20px 0 0 0; - overflow: hidden; -` - -export const SliderTrack = styled.div` - position: absolute; - top: 50%; - left: 0; - right: 0; - height: 4px; - background: #3a3a3a; - transform: translateY(-50%); - border-radius: 2px; -` - -export const SliderRange = styled.div.attrs<{ $left: number; $width: number }>(props => ({ - style: { - left: `${props.$left}%`, - width: `${props.$width}%`, - }, -}))<{ $left: number; $width: number }>` - position: absolute; - top: 50%; - height: 4px; - background: linear-gradient(90deg, #31cb9e 0%, #7289da 100%); - transform: translateY(-50%); - border-radius: 2px; - will-change: left, width; -` - -// ============================================ -// Handle Styles -// ============================================ - -export const Handle = styled.div.attrs<{ $position: number }>(props => ({ - style: { - left: `${props.$position}%`, - }, -}))<{ $position: number }>` - position: absolute; - top: 0; - transform: translate(-50%, 1%); - cursor: grab; - z-index: 10; - touch-action: none; /* Prevent scroll while dragging on touch devices */ - will-change: left; /* Hint GPU to optimize frequent updates */ - - &:active { - cursor: grabbing; - } - - svg { - display: block; - } -` - -export const PriceLabel = styled.div.attrs<{ $position: number; $isLower: boolean }>(props => ({ - style: { - left: `${props.$position}%`, - transform: props.$isLower ? 'translateX(calc(-100% - 12px))' : 'translateX(12px)', - }, -}))<{ $position: number; $isLower: boolean }>` - position: absolute; - top: 4px; - color: #fff; - font-size: 12px; - font-weight: 500; - white-space: nowrap; - pointer-events: none; - will-change: left, transform; -` - -// ============================================ -// Current Price Marker -// ============================================ - -export const CurrentPriceMarker = styled.div` - width: 2px; - height: 15px; - background: #888; - border-radius: 2px; - cursor: pointer; - - &::after { - content: ''; - position: absolute; - top: -5px; - left: 50%; - transform: translateX(-50%); - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 5px solid #888; - } -` - -export const CurrentPriceTooltip = styled.div` - position: absolute; - top: -24px; - left: 50%; - transform: translateX(-50%); - white-space: nowrap; - font-size: 12px; - font-weight: 500; - color: #fff; - opacity: 0; - visibility: hidden; - transition: opacity 0.15s ease, visibility 0.15s ease; - pointer-events: none; - z-index: 100; -` - -export const CurrentPriceMarkerWrapper = styled.div.attrs<{ $position: number }>(props => ({ - style: { - left: `${props.$position}%`, - }, -}))<{ $position: number }>` - position: absolute; - top: 50%; - transform: translate(-50%, -50%); - z-index: 5; - will-change: left; - - &:hover ${CurrentPriceTooltip} { - opacity: 1; - visibility: visible; - } -` - -// ============================================ -// Price Axis Styles -// ============================================ - -export const PriceAxisContainer = styled.div` - position: relative; - width: 100%; - height: 24px; - margin-top: -8px; -` - -export const PriceAxisLine = styled.div` - position: absolute; - top: 0; - left: 0; - right: 0; - height: 1px; - background: #3a3a3a; -` - -export const PriceAxisTick = styled.div.attrs<{ $position: number }>(props => ({ - style: { - left: `${props.$position}%`, - }, -}))<{ $position: number }>` - position: absolute; - top: 0; - transform: translateX(-50%); - width: 1px; - height: 6px; - background: #555; - will-change: left; -` - -export const PriceAxisLabel = styled.div.attrs<{ $position: number }>(props => ({ - style: { - left: `${props.$position}%`, - }, -}))<{ $position: number }>` - position: absolute; - top: 8px; - transform: translateX(-50%); - color: #888; - font-size: 10px; - white-space: nowrap; - user-select: none; - will-change: left; -` diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/types.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/types.ts deleted file mode 100644 index a7854bc375..0000000000 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/types.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * View range representing the visible tick range on the slider - */ -export interface ViewRange { - min: number - max: number -} - -/** - * Pool information required for price calculations - */ -export interface PoolInfo { - tickSpacing: number - token0Decimals: number - token1Decimals: number - currentTick: number -} - -/** - * Props for the UniswapPriceSlider component - */ -export interface UniswapPriceSliderProps { - pool: PoolInfo - invertPrice?: boolean - lowerTick?: number - upperTick?: number - setLowerTick: (tick: number) => void - setUpperTick: (tick: number) => void -} - -/** - * Props for the PriceAxis component - */ -export interface PriceAxisProps { - viewRange: ViewRange - token0Decimals: number - token1Decimals: number - invertPrice?: boolean -} - -/** - * Handle type for dragging state - */ -export type HandleType = 'lower' | 'upper' | null diff --git a/apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts b/apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts deleted file mode 100644 index eb1682d9b4..0000000000 --- a/apps/kyberswap-interface/src/components/UniswapPriceSlider/utils.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Generate SVG path for brush handle (oval shape with rounded ends) - */ -export const brushHandlePath = (height: number): string => { - return [ - `M 0.5 0`, - `Q 0 0 0 1.5`, // Rounded top-left corner - `v 3.5`, - `C -5 5 -5 17 0 17`, // Oval - `v ${height - 19}`, - `Q 0 ${height} 0.5 ${height}`, // Rounded bottom-left corner - `Q 1 ${height} 1 ${height - 1.5}`, // Rounded bottom-right corner - `V 17`, - `C 6 17 6 5 1 5`, - `V 1.5`, - `Q 1 0 0.5 0`, // Rounded top-right corner - ].join(' ') -} - -/** - * Format number with comma separators - */ -const formatWithCommas = (num: number, decimals = 0): string => { - const fixed = num.toFixed(decimals) - const parts = fixed.split('.') - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',') - return parts.join('.') -} - -/** - * Format price for axis display - user-friendly format - * Shows more detail for smaller ranges, uses abbreviations for large numbers - * Note: Prices in Uniswap context are always positive - */ -export const formatAxisPrice = (price: number): string => { - if (price === 0) return '0' - if (!isFinite(price)) return '∞' - - // For astronomically large numbers, show a capped display - if (price >= 1e18) return '>999Q' - // Quadrillions (10^15) - if (price >= 1e15) { - const val = price / 1e15 - return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'Q' - } - // Trillions (10^12) - if (price >= 1e12) { - const val = price / 1e12 - return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'T' - } - // Billions (10^9) - if (price >= 1e9) { - const val = price / 1e9 - return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'B' - } - // Millions (10^6) - if (price >= 1e6) { - const val = price / 1e6 - return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'M' - } - // 100K - 999K: use K suffix - if (price >= 100000) { - return Math.round(price / 1000) + 'K' - } - // 10K - 99.9K: show as "12.5K" with more precision - if (price >= 10000) { - return (price / 1000).toFixed(1) + 'K' - } - // 1K - 9.9K: show full number with comma (like "2,500" or "3,750") - if (price >= 1000) { - // Round to nearest 10 for cleaner display - return formatWithCommas(Math.round(price / 10) * 10) - } - // 100 - 999: show full number - if (price >= 100) { - return Math.round(price).toString() - } - // 10 - 99.99: show with 1 decimal - if (price >= 10) { - return price.toFixed(1) - } - // 1 - 9.99: show with 2 decimals - if (price >= 1) { - return price.toFixed(2) - } - // Small decimals - if (price >= 0.01) { - return price.toFixed(4) - } - if (price >= 0.0001) { - return price.toFixed(5) - } - // For extremely small numbers, show a floor display - if (price < 1e-8) { - return '<0.00001' - } - return price.toPrecision(3) -} - -/** - * Calculate edge intensity for zoom behavior - * Returns 0-1 based on how close position is to edge - */ -export const getEdgeIntensity = (position: number, edgeThreshold: number): number => { - if (position < edgeThreshold) { - return 1 - position / edgeThreshold - } - if (position > 100 - edgeThreshold) { - return (position - (100 - edgeThreshold)) / edgeThreshold - } - return 0 -} From 86e6f2055eb33232a27a149b32da05f134e5348d Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 22:46:56 +0700 Subject: [PATCH 65/87] update: migrate to package --- packages/price-slider/.eslintrc.cjs | 4 + packages/price-slider/.prettierrc.cjs | 1 + packages/price-slider/package.json | 57 +++ packages/price-slider/postcss.config.js | 19 + .../price-slider/src/components/PriceAxis.tsx | 179 ++++++++ .../price-slider/src/components/Skeleton.tsx | 84 ++++ .../src/components/UniswapPriceSlider.tsx | 416 ++++++++++++++++++ packages/price-slider/src/constants/index.ts | 43 ++ packages/price-slider/src/hooks/index.ts | 321 ++++++++++++++ packages/price-slider/src/index.ts | 36 ++ packages/price-slider/src/styles.css | 3 + packages/price-slider/src/types/index.ts | 46 ++ packages/price-slider/src/utils/index.ts | 128 ++++++ packages/price-slider/tailwind.config.ts | 10 + packages/price-slider/tsconfig.json | 14 + packages/price-slider/tsup.config.ts | 29 ++ 16 files changed, 1390 insertions(+) create mode 100644 packages/price-slider/.eslintrc.cjs create mode 100644 packages/price-slider/.prettierrc.cjs create mode 100644 packages/price-slider/package.json create mode 100644 packages/price-slider/postcss.config.js create mode 100644 packages/price-slider/src/components/PriceAxis.tsx create mode 100644 packages/price-slider/src/components/Skeleton.tsx create mode 100644 packages/price-slider/src/components/UniswapPriceSlider.tsx create mode 100644 packages/price-slider/src/constants/index.ts create mode 100644 packages/price-slider/src/hooks/index.ts create mode 100644 packages/price-slider/src/index.ts create mode 100644 packages/price-slider/src/styles.css create mode 100644 packages/price-slider/src/types/index.ts create mode 100644 packages/price-slider/src/utils/index.ts create mode 100644 packages/price-slider/tailwind.config.ts create mode 100644 packages/price-slider/tsconfig.json create mode 100644 packages/price-slider/tsup.config.ts diff --git a/packages/price-slider/.eslintrc.cjs b/packages/price-slider/.eslintrc.cjs new file mode 100644 index 0000000000..f934cce541 --- /dev/null +++ b/packages/price-slider/.eslintrc.cjs @@ -0,0 +1,4 @@ +/* eslint-env node */ +module.exports = { + extends: ['@kyber/eslint-config/index'], +}; diff --git a/packages/price-slider/.prettierrc.cjs b/packages/price-slider/.prettierrc.cjs new file mode 100644 index 0000000000..6e86a44df2 --- /dev/null +++ b/packages/price-slider/.prettierrc.cjs @@ -0,0 +1 @@ +module.exports = require('@kyber/eslint-config/prettier'); diff --git a/packages/price-slider/package.json b/packages/price-slider/package.json new file mode 100644 index 0000000000..aaacba39b4 --- /dev/null +++ b/packages/price-slider/package.json @@ -0,0 +1,57 @@ +{ + "name": "@kyberswap/price-slider", + "version": "1.0.0", + "license": "MIT", + "type": "module", + "exports": { + ".": { + "import": "./dist/price-slider.js", + "require": "./dist/price-slider.cjs" + }, + "./style.css": "./dist/price-slider.css" + }, + "main": "./dist/price-slider.cjs", + "module": "./dist/price-slider.js", + "types": "./dist/price-slider.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "lint": "eslint --ext .ts,.tsx src", + "format": "prettier --write src", + "prepublishOnly": "tsc && tsup", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "@kyber/ui": "workspace:^" + }, + "devDependencies": { + "@kyber/eslint-config": "workspace:*", + "@kyber/tailwind-config": "workspace:^", + "@kyber/typescript-config": "workspace:*", + "@kyber/utils": "workspace:^", + "@types/eslint": "^8.56.5", + "@types/node": "^20.11.24", + "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", + "autoprefixer": "^10.4.20", + "eslint": "^8.57.0", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.13", + "tsup": "^8.3.0", + "typescript": "5.3.2", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.4.0", + "prettier": "^3.5.3", + "@trivago/prettier-plugin-sort-imports": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + } +} diff --git a/packages/price-slider/postcss.config.js b/packages/price-slider/postcss.config.js new file mode 100644 index 0000000000..6b83666f1d --- /dev/null +++ b/packages/price-slider/postcss.config.js @@ -0,0 +1,19 @@ +const prefixOverrideList = ['html', 'body', "[role='portal']"]; + +export default { + plugins: { + 'postcss-import': {}, + 'tailwindcss/nesting': {}, + tailwindcss: { config: 'tailwind.config.ts' }, + 'postcss-prefix-selector': { + prefix: '.ks-ps-style', + transform(prefix, selector, prefixedSelector) { + if (prefixOverrideList.includes(selector)) { + return prefix; + } + return prefixedSelector; + }, + }, + autoprefixer: {}, + }, +}; diff --git a/packages/price-slider/src/components/PriceAxis.tsx b/packages/price-slider/src/components/PriceAxis.tsx new file mode 100644 index 0000000000..5771b982f5 --- /dev/null +++ b/packages/price-slider/src/components/PriceAxis.tsx @@ -0,0 +1,179 @@ +import React, { useMemo } from 'react'; + +import { tickToPrice } from '@kyber/utils/dist/uniswapv3'; + +import { MAX_AXIS_TICK_COUNT, MIN_AXIS_TICK_COUNT } from '@/constants'; +import type { PriceAxisProps } from '@/types'; +import { formatAxisPrice } from '@/utils'; + +/** + * Calculate the optimal number of ticks and minimum gap based on price range + * More ticks for small ranges, fewer for large ranges + */ +const getOptimalTickConfig = (minPrice: number, maxPrice: number): { tickCount: number; minGapPercent: number } => { + if (minPrice <= 0 || maxPrice <= 0) { + return { tickCount: 7, minGapPercent: 15 }; + } + + // Calculate how many orders of magnitude the prices span + const priceRatio = maxPrice / minPrice; + const ordersOfMagnitude = Math.log10(priceRatio); + + // Very small range (< 0.5 orders): many ticks, small gap + if (ordersOfMagnitude <= 0.5) { + return { tickCount: MAX_AXIS_TICK_COUNT, minGapPercent: 12 }; + } + // Small range (0.5 - 1 order): good amount of ticks + if (ordersOfMagnitude <= 1) { + return { tickCount: 9, minGapPercent: 14 }; + } + // Medium range (1 - 2 orders) + if (ordersOfMagnitude <= 2) { + return { tickCount: 7, minGapPercent: 16 }; + } + // Large range (2 - 4 orders) + if (ordersOfMagnitude <= 4) { + return { tickCount: 5, minGapPercent: 20 }; + } + // Very large range (4 - 8 orders) + if (ordersOfMagnitude <= 8) { + return { tickCount: 3, minGapPercent: 30 }; + } + // Extreme range (> 8 orders): just first and last + return { tickCount: MIN_AXIS_TICK_COUNT, minGapPercent: 40 }; +}; + +/** + * Calculate tick positions for the axis + * Uses tick-space for even distribution (matching the slider) + * When invertPrice, positions are flipped so lower inverted price is on left + */ +const calculateAxisTicks = ( + viewRange: { min: number; max: number }, + token0Decimals: number, + token1Decimals: number, + count: number, + invertPrice?: boolean, +): Array<{ tick: number; price: number; position: number }> => { + const tickRange = viewRange.max - viewRange.min; + if (tickRange <= 0) return []; + + const step = tickRange / (count - 1); + const ticks: Array<{ tick: number; price: number; position: number }> = []; + + for (let i = 0; i < count; i++) { + const tick = Math.round(viewRange.min + step * i); + const price = +tickToPrice(tick, token0Decimals, token1Decimals, invertPrice); + const normalPosition = ((tick - viewRange.min) / tickRange) * 100; + // When invertPrice, flip position so lower inverted price (from higher tick) is on left + const position = invertPrice ? 100 - normalPosition : normalPosition; + + ticks.push({ tick, price, position }); + } + + return ticks; +}; + +/** + * Filter ticks to ensure minimum spacing between labels + * Only shows labels that have sufficient gap from previous label + */ +const filterOverlappingTicks = ( + ticks: Array<{ tick: number; price: number; position: number }>, + minGapPercent: number, +): Array<{ tick: number; price: number; position: number; showLabel: boolean }> => { + if (ticks.length === 0) return []; + + const result: Array<{ tick: number; price: number; position: number; showLabel: boolean }> = []; + let lastLabelPosition = -Infinity; + + for (let i = 0; i < ticks.length; i++) { + const tick = ticks[i]; + const isFirst = i === 0; + const isLast = i === ticks.length - 1; + const gap = tick.position - lastLabelPosition; + + // First tick always shows label + if (isFirst) { + lastLabelPosition = tick.position; + result.push({ ...tick, showLabel: true }); + continue; + } + + // Last tick: only show if enough gap, otherwise hide + if (isLast) { + const showLabel = gap >= minGapPercent; + if (showLabel) lastLabelPosition = tick.position; + result.push({ ...tick, showLabel }); + continue; + } + + // Middle ticks: show if enough gap from previous label + const showLabel = gap >= minGapPercent; + if (showLabel) lastLabelPosition = tick.position; + result.push({ ...tick, showLabel }); + } + + return result; +}; + +/** + * Price axis component that displays price scale below the slider + * Uses tick-based positioning to match the slider exactly + * Dynamically reduces tick count when price range is very large + */ +function PriceAxis({ viewRange, token0Decimals, token1Decimals, invertPrice }: PriceAxisProps) { + const axisTicks = useMemo(() => { + // Get min and max prices to determine optimal tick config + const minPrice = +tickToPrice(Math.round(viewRange.min), token0Decimals, token1Decimals, invertPrice); + const maxPrice = +tickToPrice(Math.round(viewRange.max), token0Decimals, token1Decimals, invertPrice); + const { tickCount, minGapPercent } = getOptimalTickConfig( + Math.min(minPrice, maxPrice), + Math.max(minPrice, maxPrice), + ); + + const ticks = calculateAxisTicks(viewRange, token0Decimals, token1Decimals, tickCount, invertPrice); + // Sort by position ascending for proper overlap filtering + const sortedTicks = [...ticks].sort((a, b) => a.position - b.position); + return filterOverlappingTicks(sortedTicks, minGapPercent); + }, [viewRange, token0Decimals, token1Decimals, invertPrice]); + + return ( +
+ {/* Axis Line */} +
+ + {/* Ticks and Labels */} + {axisTicks.map(({ tick, price, position, showLabel }, index) => { + // Only render if within visible range + if (position < -2 || position > 102) return null; + + // Determine alignment: first label align left, last label align right, others center + const isFirst = index === 0; + const isLast = index === axisTicks.length - 1; + const alignClass = isFirst ? 'left-0' : isLast ? 'right-0' : '-translate-x-1/2'; + + return ( + + {/* Tick Mark */} +
+ {/* Label */} + {showLabel && ( +
+ {formatAxisPrice(price)} +
+ )} + + ); + })} +
+ ); +} + +export default React.memo(PriceAxis); diff --git a/packages/price-slider/src/components/Skeleton.tsx b/packages/price-slider/src/components/Skeleton.tsx new file mode 100644 index 0000000000..d842256fd6 --- /dev/null +++ b/packages/price-slider/src/components/Skeleton.tsx @@ -0,0 +1,84 @@ +import React from 'react'; + +import { Skeleton } from '@kyber/ui'; + +import { SKELETON_AXIS_POSITIONS } from '@/constants'; + +/** + * Skeleton loading state for the price slider + * Uses Skeleton component from @kyber/ui for shimmer animation + */ +function PriceSliderSkeleton() { + return ( +
+ {/* Slider Area */} +
+ {/* Track */} + + + {/* Range */} + + + {/* Current Price Marker */} + + + {/* Lower Price Label */} + + + {/* Upper Price Label */} + + + {/* Lower Handle */} + + + {/* Upper Handle */} + +
+ + {/* Axis Container */} +
+ {/* Axis Line */} + + + {/* Axis Ticks and Labels */} + {SKELETON_AXIS_POSITIONS.map((pos, index) => { + const isFirst = index === 0; + const isLast = index === SKELETON_AXIS_POSITIONS.length - 1; + const alignClass = isFirst ? 'left-0' : isLast ? 'right-0' : '-translate-x-1/2'; + + return ( + + {/* Tick */} + + {/* Label */} + + + ); + })} +
+
+ ); +} + +export default React.memo(PriceSliderSkeleton); diff --git a/packages/price-slider/src/components/UniswapPriceSlider.tsx b/packages/price-slider/src/components/UniswapPriceSlider.tsx new file mode 100644 index 0000000000..f0137ac4a7 --- /dev/null +++ b/packages/price-slider/src/components/UniswapPriceSlider.tsx @@ -0,0 +1,416 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; + +import { MAX_TICK, MIN_TICK, tickToPrice } from '@kyber/utils/dist/uniswapv3'; + +import PriceAxis from '@/components/PriceAxis'; +import PriceSliderSkeleton from '@/components/Skeleton'; +import { AUTO_CENTER_PADDING, EDGE_THRESHOLD, MIN_HANDLE_DISTANCE_MULTIPLIER } from '@/constants'; +import { useDebouncedTicks, useSmoothZoom, useTickPositionConverter } from '@/hooks'; +import type { HandleType, UniswapPriceSliderProps, ViewRange } from '@/types'; +import { brushHandlePath, formatDisplayNumber, getEdgeIntensity } from '@/utils'; + +function UniswapPriceSlider({ + pool, + invertPrice, + lowerTick, + upperTick, + setLowerTick, + setUpperTick, + className, +}: UniswapPriceSliderProps) { + const { tickSpacing, token0Decimals, token1Decimals, currentTick } = pool; + + const [viewRange, setViewRange] = useState(null); + const [isDragging, setIsDragging] = useState(null); + + const sliderRef = useRef(null); + const isInitialized = useRef(false); + const viewRangeRef = useRef(viewRange); + const lastAdjustedTicksRef = useRef<{ lower: number; upper: number } | null>(null); + + // Keep viewRangeRef in sync with viewRange state + useEffect(() => { + viewRangeRef.current = viewRange; + }, [viewRange]); + + const { startSmoothZoom } = useSmoothZoom(viewRange, setViewRange); + + const { + internalLowerTick, + internalUpperTick, + debouncedSetLowerTick, + debouncedSetUpperTick, + flushDebouncedValues, + getTargetTicks, + } = useDebouncedTicks(lowerTick, upperTick, setLowerTick, setUpperTick, isDragging !== null); + + const { getPositionFromTick, getTickFromPosition } = useTickPositionConverter(viewRange, tickSpacing, invertPrice); + + const ticksReady = lowerTick !== undefined && upperTick !== undefined && upperTick > lowerTick; + + // Initialize View Range + useEffect(() => { + if (isInitialized.current || !ticksReady) return; + + const tickRange = Math.abs(upperTick - lowerTick); + const padding = Math.max(tickRange * 0.5, tickSpacing * 50); + + const minTick = Math.min(lowerTick, upperTick, currentTick); + const maxTick = Math.max(lowerTick, upperTick, currentTick); + + setViewRange({ + min: Math.max(MIN_TICK, minTick - padding), + max: Math.min(MAX_TICK, maxTick + padding), + }); + isInitialized.current = true; + }, [lowerTick, upperTick, currentTick, tickSpacing, ticksReady]); + + // Auto-adjust viewRange when ticks change from outside (e.g., input fields) + useEffect(() => { + // Skip if not initialized, dragging, or ticks not ready + if (!isInitialized.current || isDragging || !ticksReady || !viewRange) return; + + // Skip if already adjusted for these exact tick values + const lastAdjusted = lastAdjustedTicksRef.current; + if (lastAdjusted && lastAdjusted.lower === lowerTick && lastAdjusted.upper === upperTick) return; + + const currentRange = viewRange.max - viewRange.min; + const lowerPos = ((lowerTick - viewRange.min) / currentRange) * 100; + const upperPos = ((upperTick - viewRange.min) / currentRange) * 100; + + // Check if handles are outside visible area or positioned poorly + const handleOutsideLeft = lowerPos < -5 || upperPos < -5; + const handleOutsideRight = lowerPos > 105 || upperPos > 105; + const handleSpan = Math.abs(upperPos - lowerPos); + const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING; + const handlesTooClose = handleSpan < idealHandleSpan * 0.4; // Much smaller than ideal + const handlesTooFar = handleSpan > idealHandleSpan * 2; // Much larger than ideal + + // If adjustment needed, calculate new viewRange + if (handleOutsideLeft || handleOutsideRight || handlesTooClose || handlesTooFar) { + const tickDistance = Math.abs(upperTick - lowerTick); + const handleCenter = (lowerTick + upperTick) / 2; + + const idealPadding = tickDistance * (AUTO_CENTER_PADDING / (100 - 2 * AUTO_CENTER_PADDING)); + const minPadding = Math.max(tickDistance * 0.3, tickSpacing * 20); + const padding = Math.max(idealPadding, minPadding); + + const targetMin = Math.max(MIN_TICK, handleCenter - tickDistance / 2 - padding); + const targetMax = Math.min(MAX_TICK, handleCenter + tickDistance / 2 + padding); + + // Mark these ticks as adjusted to prevent re-triggering + lastAdjustedTicksRef.current = { lower: lowerTick, upper: upperTick }; + startSmoothZoom(targetMin, targetMax); + } + }, [lowerTick, upperTick, isDragging, ticksReady, viewRange, tickSpacing, startSmoothZoom]); + + const handleMouseDown = useCallback( + (handle: 'lower' | 'upper') => (e: React.MouseEvent) => { + e.preventDefault(); + setIsDragging(handle); + }, + [], + ); + + const handleTouchStart = useCallback( + (handle: 'lower' | 'upper') => (e: React.TouchEvent) => { + e.preventDefault(); + setIsDragging(handle); + }, + [], + ); + + // Shared logic for handling drag movement (mouse or touch) + const handleDragMove = useCallback( + (clientX: number) => { + if (!isDragging || !sliderRef.current || !viewRange || lowerTick === undefined || upperTick === undefined) return; + + const rect = sliderRef.current.getBoundingClientRect(); + const x = clientX - rect.left; + const position = Math.max(0, Math.min(100, (x / rect.width) * 100)); + const newTick = getTickFromPosition(position); + + const currentRange = viewRange.max - viewRange.min; + + // Check if near edges for zoom out + const isNearLeftEdge = position < EDGE_THRESHOLD; + const isNearRightEdge = position > 100 - EDGE_THRESHOLD; + const edgeIntensity = getEdgeIntensity(position, EDGE_THRESHOLD); + + // Zoom out when near edges (zoom-in is handled by auto-center on mouse up) + if (isNearLeftEdge || isNearRightEdge) { + const baseExpansion = currentRange * 0.25; + const expansion = baseExpansion * edgeIntensity; + + let targetMin = viewRange.min; + let targetMax = viewRange.max; + + if (isNearLeftEdge && viewRange.min > MIN_TICK) { + targetMin = Math.max(MIN_TICK, viewRange.min - expansion); + } + if (isNearRightEdge && viewRange.max < MAX_TICK) { + targetMax = Math.min(MAX_TICK, viewRange.max + expansion); + } + + startSmoothZoom(targetMin, targetMax); + } + + // Update tick values (with minimum distance between handles) + if (isDragging === 'lower') { + const maxLower = (internalUpperTick ?? upperTick ?? 0) - tickSpacing * MIN_HANDLE_DISTANCE_MULTIPLIER; + debouncedSetLowerTick(Math.min(newTick, maxLower)); + } else { + const minUpper = (internalLowerTick ?? lowerTick ?? 0) + tickSpacing * MIN_HANDLE_DISTANCE_MULTIPLIER; + debouncedSetUpperTick(Math.max(newTick, minUpper)); + } + }, + [ + debouncedSetLowerTick, + debouncedSetUpperTick, + getTickFromPosition, + internalLowerTick, + internalUpperTick, + isDragging, + lowerTick, + startSmoothZoom, + tickSpacing, + upperTick, + viewRange, + ], + ); + + const handleMouseMove = useCallback( + (e: MouseEvent) => { + handleDragMove(e.clientX); + }, + [handleDragMove], + ); + + const handleTouchMove = useCallback( + (e: TouchEvent) => { + if (e.touches.length > 0) { + handleDragMove(e.touches[0].clientX); + } + }, + [handleDragMove], + ); + + const handleMouseUp = useCallback(() => { + // Get the TARGET tick values (what user intended), not the animated values + const { lowerTick: targetLower, upperTick: targetUpper } = getTargetTicks(); + + // Flush to apply target values immediately + flushDebouncedValues(); + setIsDragging(null); + + // Use target ticks for auto-center calculation + const finalLowerTick = targetLower ?? lowerTick; + const finalUpperTick = targetUpper ?? upperTick; + + if (finalLowerTick === undefined || finalUpperTick === undefined) return; + + // Use setTimeout to ensure state has updated before calculating positions + setTimeout(() => { + // Use ref to get the LATEST viewRange (not stale closure value) + const currentViewRange = viewRangeRef.current; + if (!currentViewRange) return; + + const tickDistance = Math.abs(finalUpperTick - finalLowerTick); + const handleCenter = (finalLowerTick + finalUpperTick) / 2; + + // Calculate ideal padding (25% on each side = handles take up 50% of view) + const idealPadding = tickDistance * (AUTO_CENTER_PADDING / (100 - 2 * AUTO_CENTER_PADDING)); + const minPadding = Math.max(tickDistance * 0.3, tickSpacing * 20); + const padding = Math.max(idealPadding, minPadding); + + const targetMin = Math.max(MIN_TICK, handleCenter - tickDistance / 2 - padding); + const targetMax = Math.min(MAX_TICK, handleCenter + tickDistance / 2 + padding); + + // Calculate current positions using LATEST viewRange from ref + const currentRange = currentViewRange.max - currentViewRange.min; + const rawLowerPos = ((finalLowerTick - currentViewRange.min) / currentRange) * 100; + const rawUpperPos = ((finalUpperTick - currentViewRange.min) / currentRange) * 100; + + // Account for invertPrice: when inverted, positions are flipped + const currentLowerPos = invertPrice ? 100 - rawLowerPos : rawLowerPos; + const currentUpperPos = invertPrice ? 100 - rawUpperPos : rawUpperPos; + + // Left/right padding based on visual positions (not tick order) + const leftPadding = Math.min(currentLowerPos, currentUpperPos); + const rightPadding = 100 - Math.max(currentLowerPos, currentUpperPos); + const handleSpan = Math.abs(currentUpperPos - currentLowerPos); // % of view that handles span + + // Ideal handle span is 50% (100 - 2 * AUTO_CENTER_PADDING) + const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING; + const handlesTooClose = handleSpan < idealHandleSpan * 0.6; // Less than 60% of ideal = too zoomed out + const handlesTooFar = handleSpan > idealHandleSpan * 1.5; // More than 150% of ideal = too zoomed in + + // Check if rebalancing is needed + const needsRebalance = + leftPadding < EDGE_THRESHOLD + 5 || // Near left edge + rightPadding < EDGE_THRESHOLD + 5 || // Near right edge + leftPadding < 0 || // Handle outside left + rightPadding < 0 || // Handle outside right + handlesTooClose || // Handles too close together (need zoom in) + handlesTooFar || // Handles too far apart (need zoom out) + (leftPadding > 5 && rightPadding > 5 && (leftPadding / rightPadding > 2.5 || rightPadding / leftPadding > 2.5)); // Imbalanced + + if (needsRebalance) { + startSmoothZoom(targetMin, targetMax); + } + }, 50); + }, [flushDebouncedValues, getTargetTicks, invertPrice, lowerTick, startSmoothZoom, tickSpacing, upperTick]); + + useEffect(() => { + if (!isDragging) return; + + // Set grabbing cursor on body to persist while dragging outside handle + document.body.style.cursor = 'grabbing'; + document.body.style.userSelect = 'none'; + + // Mouse events + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + + // Touch events + document.addEventListener('touchmove', handleTouchMove, { passive: false }); + document.addEventListener('touchend', handleMouseUp); + document.addEventListener('touchcancel', handleMouseUp); + + return () => { + // Reset cursor when dragging ends + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + + // Remove mouse events + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + + // Remove touch events + document.removeEventListener('touchmove', handleTouchMove); + document.removeEventListener('touchend', handleMouseUp); + document.removeEventListener('touchcancel', handleMouseUp); + }; + }, [isDragging, handleMouseMove, handleMouseUp, handleTouchMove]); + + if (!ticksReady || !viewRange) { + return ; + } + + // Use internal ticks for smooth visual updates during dragging + const displayLowerTick = internalLowerTick ?? lowerTick; + const displayUpperTick = internalUpperTick ?? upperTick; + + // Calculate prices (with invertPrice applied) + const lowerTickPrice = tickToPrice(Math.round(displayLowerTick), token0Decimals, token1Decimals, invertPrice); + const upperTickPrice = tickToPrice(Math.round(displayUpperTick), token0Decimals, token1Decimals, invertPrice); + const currentPrice = tickToPrice(currentTick, token0Decimals, token1Decimals, invertPrice); + + // Calculate positions (flipped when invertPrice=true by the hook) + const lowerPosition = getPositionFromTick(displayLowerTick); + const upperPosition = getPositionFromTick(displayUpperTick); + const currentPosition = getPositionFromTick(currentTick); + + // When invertPrice, positions are flipped so: + // - lowerTick (higher inverted price) is on the RIGHT + // - upperTick (lower inverted price) is on the LEFT + // This means left position = min of the two, right position = max of the two + const leftPosition = Math.min(lowerPosition, upperPosition); + const rightPosition = Math.max(lowerPosition, upperPosition); + + // Determine which tick is at which visual position + const isLowerOnLeft = lowerPosition <= upperPosition; + const leftPrice = isLowerOnLeft ? lowerTickPrice : upperTickPrice; + const rightPrice = isLowerOnLeft ? upperTickPrice : lowerTickPrice; + const leftHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'lower' : 'upper'; + const rightHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'upper' : 'lower'; + + return ( +
+ {/* Slider Wrapper */} +
+ {/* Track */} +
+ + {/* Range */} +
+ + {/* Current Price Marker */} +
+
+ {/* Arrow indicator */} +
+
+ {/* Tooltip */} +
+ {formatDisplayNumber(currentPrice, { significantDigits: 6 })} +
+
+ + {/* Left Price Label */} +
+ {formatDisplayNumber(leftPrice, { significantDigits: 6 })} +
+ + {/* Right Price Label */} +
+ {formatDisplayNumber(rightPrice, { significantDigits: 6 })} +
+ + {/* Left Handle (green) */} +
+ + + +
+ + {/* Right Handle (blue) */} +
+ + + +
+
+ + +
+ ); +} + +export default UniswapPriceSlider; diff --git a/packages/price-slider/src/constants/index.ts b/packages/price-slider/src/constants/index.ts new file mode 100644 index 0000000000..6dd258b782 --- /dev/null +++ b/packages/price-slider/src/constants/index.ts @@ -0,0 +1,43 @@ +// Animation Constants +/** Duration for zoom/auto-center animation in milliseconds */ +export const ZOOM_DURATION = 400; + +/** Delay before committing tick changes to parent in milliseconds */ +export const DEBOUNCE_DELAY = 150; + +/** Minimum LERP factor when handle is far from target (slower movement) */ +export const HANDLE_LERP_MIN = 0.15; + +/** Maximum LERP factor when handle is close to target (faster movement) */ +export const HANDLE_LERP_MAX = 0.4; + +/** Maximum ticks per frame to prevent jumpy handle movement */ +export const MAX_TICK_SPEED = 2000; + +// Slider Behavior Constants +/** Percentage from edge that triggers zoom out (ensures price labels visible) */ +export const EDGE_THRESHOLD = 18; + +/** Percentage padding on each side when auto-centering after drag */ +export const AUTO_CENTER_PADDING = 25; + +/** Minimum tick spacings between handles to prevent overlap (keep small for large tickSpacing pools) */ +export const MIN_HANDLE_DISTANCE_MULTIPLIER = 1; + +// Dynamic LERP Constants +/** Distance threshold (in ticks) for minimum lerp factor */ +export const LERP_FAR_THRESHOLD = 5000; + +/** Distance threshold (in ticks) for maximum lerp factor */ +export const LERP_CLOSE_THRESHOLD = 100; + +// Price Axis Constants +/** Maximum number of ticks on price axis for small ranges */ +export const MAX_AXIS_TICK_COUNT = 11; + +/** Minimum number of ticks on price axis for extreme ranges */ +export const MIN_AXIS_TICK_COUNT = 2; + +// Skeleton Constants +/** Positions for skeleton axis ticks (percentage) */ +export const SKELETON_AXIS_POSITIONS = [0, 16.6, 33.3, 50, 66.6, 83.3, 100]; diff --git a/packages/price-slider/src/hooks/index.ts b/packages/price-slider/src/hooks/index.ts new file mode 100644 index 0000000000..ac320e413c --- /dev/null +++ b/packages/price-slider/src/hooks/index.ts @@ -0,0 +1,321 @@ +import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; + +import { nearestUsableTick } from '@kyber/utils/dist/uniswapv3'; + +import { + DEBOUNCE_DELAY, + HANDLE_LERP_MAX, + HANDLE_LERP_MIN, + LERP_CLOSE_THRESHOLD, + LERP_FAR_THRESHOLD, + MAX_TICK_SPEED, + ZOOM_DURATION, +} from '@/constants'; +import type { ViewRange } from '@/types'; + +/** Easing function: ease-out cubic for smooth deceleration */ +const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3); + +/** + * Hook for smooth zoom animation using easing function + * Uses ease-out for natural deceleration feel + */ +export const useSmoothZoom = ( + viewRange: ViewRange | null, + setViewRange: Dispatch>, +) => { + const zoomAnimationRef = useRef(null); + const targetViewRangeRef = useRef(null); + const startViewRangeRef = useRef(null); + const startTimeRef = useRef(0); + + const animateZoom = useCallback(() => { + if (!targetViewRangeRef.current || !startViewRangeRef.current) { + zoomAnimationRef.current = null; + return; + } + + const now = performance.now(); + const elapsed = now - startTimeRef.current; + const progress = Math.min(elapsed / ZOOM_DURATION, 1); + const easedProgress = easeOutCubic(progress); + + const start = startViewRangeRef.current; + const target = targetViewRangeRef.current; + + const newMin = start.min + (target.min - start.min) * easedProgress; + const newMax = start.max + (target.max - start.max) * easedProgress; + + setViewRange({ min: newMin, max: newMax }); + + if (progress < 1) { + // Continue animation + zoomAnimationRef.current = requestAnimationFrame(animateZoom); + } else { + // Animation complete - set exact target values + setViewRange(target); + targetViewRangeRef.current = null; + startViewRangeRef.current = null; + zoomAnimationRef.current = null; + } + }, [setViewRange]); + + const startSmoothZoom = useCallback( + (targetMin: number, targetMax: number) => { + // If already animating, use current position as new start + if (zoomAnimationRef.current && viewRange) { + startViewRangeRef.current = viewRange; + } else if (viewRange) { + startViewRangeRef.current = viewRange; + } + + targetViewRangeRef.current = { min: targetMin, max: targetMax }; + startTimeRef.current = performance.now(); + + if (!zoomAnimationRef.current) { + zoomAnimationRef.current = requestAnimationFrame(animateZoom); + } + }, + [animateZoom, viewRange], + ); + + // Cleanup animation on unmount + useEffect(() => { + return () => { + if (zoomAnimationRef.current) { + cancelAnimationFrame(zoomAnimationRef.current); + } + }; + }, []); + + return { startSmoothZoom }; +}; + +/** + * Hook for smooth tick updates with animation and debouncing + * Handles move slowly/smoothly towards target position + */ +export const useDebouncedTicks = ( + lowerTick: number | undefined, + upperTick: number | undefined, + setLowerTick: (tick: number) => void, + setUpperTick: (tick: number) => void, + isDragging: boolean, +) => { + const debounceTimerRef = useRef(null); + const animationRef = useRef(null); + + // Target ticks (where user wants to go) + const targetLowerTickRef = useRef(lowerTick); + const targetUpperTickRef = useRef(upperTick); + + // Current internal tick values (tracked via refs for animation loop) + const internalLowerRef = useRef(lowerTick); + const internalUpperRef = useRef(upperTick); + + // Internal tick state for React rendering + const [internalLowerTick, setInternalLowerTick] = useState(lowerTick); + const [internalUpperTick, setInternalUpperTick] = useState(upperTick); + + // Helper: calculate dynamic lerp factor based on distance + const getDynamicLerp = useCallback((diff: number): number => { + const absDiff = Math.abs(diff); + if (absDiff > LERP_FAR_THRESHOLD) return HANDLE_LERP_MIN; + if (absDiff < LERP_CLOSE_THRESHOLD) return HANDLE_LERP_MAX; + const t = (absDiff - LERP_CLOSE_THRESHOLD) / (LERP_FAR_THRESHOLD - LERP_CLOSE_THRESHOLD); + return HANDLE_LERP_MAX - t * (HANDLE_LERP_MAX - HANDLE_LERP_MIN); + }, []); + + // Animation function for smooth handle movement + const animateHandles = useCallback(() => { + let needsAnimation = false; + + // Update lower tick + if (internalLowerRef.current !== undefined && targetLowerTickRef.current !== undefined) { + const current = internalLowerRef.current; + const target = targetLowerTickRef.current; + const diff = target - current; + + if (Math.abs(diff) >= 1) { + needsAnimation = true; + const lerpFactor = getDynamicLerp(diff); + const lerpMovement = diff * lerpFactor; + const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED); + const newValue = current + cappedMovement; + internalLowerRef.current = newValue; + setInternalLowerTick(newValue); + } else if (current !== target) { + internalLowerRef.current = target; + setInternalLowerTick(target); + } + } + + // Update upper tick + if (internalUpperRef.current !== undefined && targetUpperTickRef.current !== undefined) { + const current = internalUpperRef.current; + const target = targetUpperTickRef.current; + const diff = target - current; + + if (Math.abs(diff) >= 1) { + needsAnimation = true; + const lerpFactor = getDynamicLerp(diff); + const lerpMovement = diff * lerpFactor; + const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED); + const newValue = current + cappedMovement; + internalUpperRef.current = newValue; + setInternalUpperTick(newValue); + } else if (current !== target) { + internalUpperRef.current = target; + setInternalUpperTick(target); + } + } + + if (needsAnimation) { + animationRef.current = requestAnimationFrame(animateHandles); + } else { + animationRef.current = null; + } + }, [getDynamicLerp]); + + // Start animation if not already running + const startAnimation = useCallback(() => { + if (!animationRef.current) { + animationRef.current = requestAnimationFrame(animateHandles); + } + }, [animateHandles]); + + // Sync internal state with props when not dragging + useEffect(() => { + if (!isDragging) { + targetLowerTickRef.current = lowerTick; + targetUpperTickRef.current = upperTick; + internalLowerRef.current = lowerTick; + internalUpperRef.current = upperTick; + setInternalLowerTick(lowerTick); + setInternalUpperTick(upperTick); + } + }, [lowerTick, upperTick, isDragging]); + + // Smooth update functions - set target and start animation + const debouncedSetLowerTick = useCallback( + (tick: number) => { + targetLowerTickRef.current = tick; + startAnimation(); + + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + debounceTimerRef.current = setTimeout(() => { + setLowerTick(tick); + }, DEBOUNCE_DELAY); + }, + [setLowerTick, startAnimation], + ); + + const debouncedSetUpperTick = useCallback( + (tick: number) => { + targetUpperTickRef.current = tick; + startAnimation(); + + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + debounceTimerRef.current = setTimeout(() => { + setUpperTick(tick); + }, DEBOUNCE_DELAY); + }, + [setUpperTick, startAnimation], + ); + + // Flush debounced values immediately + const flushDebouncedValues = useCallback(() => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + debounceTimerRef.current = null; + } + + // Set final values from targets + const finalLower = targetLowerTickRef.current; + const finalUpper = targetUpperTickRef.current; + + if (finalLower !== undefined && finalLower !== lowerTick) { + setLowerTick(finalLower); + internalLowerRef.current = finalLower; + setInternalLowerTick(finalLower); + } + if (finalUpper !== undefined && finalUpper !== upperTick) { + setUpperTick(finalUpper); + internalUpperRef.current = finalUpper; + setInternalUpperTick(finalUpper); + } + + // Stop animation + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + animationRef.current = null; + } + }, [lowerTick, upperTick, setLowerTick, setUpperTick]); + + // Cleanup on unmount + useEffect(() => { + return () => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + }; + }, []); + + // Get target ticks (what user actually wants, not the animated value) + const getTargetTicks = useCallback(() => { + return { + lowerTick: targetLowerTickRef.current, + upperTick: targetUpperTickRef.current, + }; + }, []); + + return { + internalLowerTick, + internalUpperTick, + debouncedSetLowerTick, + debouncedSetUpperTick, + flushDebouncedValues, + getTargetTicks, + }; +}; + +/** + * Hook for converting between tick and position + * When invertPrice = true, the entire visual is flipped: + * - Lower tick (higher inverted price) appears on the RIGHT + * - Upper tick (lower inverted price) appears on the LEFT + * - Axis shows inverted prices from low (left) to high (right) + */ +export const useTickPositionConverter = (viewRange: ViewRange | null, tickSpacing: number, invertPrice?: boolean) => { + const getPositionFromTick = useCallback( + (tick: number): number => { + if (!viewRange) return 50; + const { min, max } = viewRange; + const normalPosition = ((tick - min) / (max - min)) * 100; + // When invertPrice, flip the position so higher inverted price is on the right + return invertPrice ? 100 - normalPosition : normalPosition; + }, + [viewRange, invertPrice], + ); + + const getTickFromPosition = useCallback( + (position: number): number => { + if (!viewRange) return 0; + const { min, max } = viewRange; + // When invertPrice, flip the position first + const actualPosition = invertPrice ? 100 - position : position; + const tick = min + (actualPosition / 100) * (max - min); + return nearestUsableTick(Math.round(tick), tickSpacing); + }, + [viewRange, tickSpacing, invertPrice], + ); + + return { getPositionFromTick, getTickFromPosition }; +}; diff --git a/packages/price-slider/src/index.ts b/packages/price-slider/src/index.ts new file mode 100644 index 0000000000..4a6deda1b8 --- /dev/null +++ b/packages/price-slider/src/index.ts @@ -0,0 +1,36 @@ +// Import styles for CSS bundling +import '@/styles.css'; + +// Main component - export both default and named for flexibility +export { default } from '@/components/UniswapPriceSlider'; +export { default as UniswapPriceSlider } from '@/components/UniswapPriceSlider'; + +// Sub-components export +export { default as PriceAxis } from '@/components/PriceAxis'; +export { default as PriceSliderSkeleton } from '@/components/Skeleton'; + +// Types export +export type { HandleType, PoolInfo, PriceAxisProps, UniswapPriceSliderProps, ViewRange } from '@/types'; + +// Hooks export +export { useDebouncedTicks, useSmoothZoom, useTickPositionConverter } from '@/hooks'; + +// Utils export +export { brushHandlePath, formatAxisPrice, formatDisplayNumber, getEdgeIntensity } from '@/utils'; + +// Constants export +export { + AUTO_CENTER_PADDING, + DEBOUNCE_DELAY, + EDGE_THRESHOLD, + HANDLE_LERP_MAX, + HANDLE_LERP_MIN, + LERP_CLOSE_THRESHOLD, + LERP_FAR_THRESHOLD, + MAX_AXIS_TICK_COUNT, + MAX_TICK_SPEED, + MIN_AXIS_TICK_COUNT, + MIN_HANDLE_DISTANCE_MULTIPLIER, + SKELETON_AXIS_POSITIONS, + ZOOM_DURATION, +} from '@/constants'; diff --git a/packages/price-slider/src/styles.css b/packages/price-slider/src/styles.css new file mode 100644 index 0000000000..b5c61c9567 --- /dev/null +++ b/packages/price-slider/src/styles.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/price-slider/src/types/index.ts b/packages/price-slider/src/types/index.ts new file mode 100644 index 0000000000..ee183d8fe1 --- /dev/null +++ b/packages/price-slider/src/types/index.ts @@ -0,0 +1,46 @@ +/** + * View range representing the visible tick range on the slider + */ +export interface ViewRange { + min: number; + max: number; +} + +/** + * Pool information required for price calculations + */ +export interface PoolInfo { + tickSpacing: number; + token0Decimals: number; + token1Decimals: number; + currentTick: number; +} + +/** + * Props for the UniswapPriceSlider component + */ +export interface UniswapPriceSliderProps { + pool: PoolInfo; + invertPrice?: boolean; + lowerTick?: number; + upperTick?: number; + setLowerTick: (tick: number) => void; + setUpperTick: (tick: number) => void; + /** Optional class name for custom styling */ + className?: string; +} + +/** + * Props for the PriceAxis component + */ +export interface PriceAxisProps { + viewRange: ViewRange; + token0Decimals: number; + token1Decimals: number; + invertPrice?: boolean; +} + +/** + * Handle type for dragging state + */ +export type HandleType = 'lower' | 'upper' | null; diff --git a/packages/price-slider/src/utils/index.ts b/packages/price-slider/src/utils/index.ts new file mode 100644 index 0000000000..b178e0d54e --- /dev/null +++ b/packages/price-slider/src/utils/index.ts @@ -0,0 +1,128 @@ +/** + * Generate SVG path for brush handle (oval shape with rounded ends) + */ +export const brushHandlePath = (height: number): string => { + return [ + `M 0.5 0`, + `Q 0 0 0 1.5`, // Rounded top-left corner + `v 3.5`, + `C -5 5 -5 17 0 17`, // Oval + `v ${height - 19}`, + `Q 0 ${height} 0.5 ${height}`, // Rounded bottom-left corner + `Q 1 ${height} 1 ${height - 1.5}`, // Rounded bottom-right corner + `V 17`, + `C 6 17 6 5 1 5`, + `V 1.5`, + `Q 1 0 0.5 0`, // Rounded top-right corner + ].join(' '); +}; + +/** + * Format number with comma separators + */ +const formatWithCommas = (num: number, decimals = 0): string => { + const fixed = num.toFixed(decimals); + const parts = fixed.split('.'); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); + return parts.join('.'); +}; + +/** + * Format price for axis display - user-friendly format + * Shows more detail for smaller ranges, uses abbreviations for large numbers + * Note: Prices in Uniswap context are always positive + */ +export const formatAxisPrice = (price: number): string => { + if (price === 0) return '0'; + if (!isFinite(price)) return '∞'; + + // For astronomically large numbers, show a capped display + if (price >= 1e18) return '>999Q'; + // Quadrillions (10^15) + if (price >= 1e15) { + const val = price / 1e15; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'Q'; + } + // Trillions (10^12) + if (price >= 1e12) { + const val = price / 1e12; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'T'; + } + // Billions (10^9) + if (price >= 1e9) { + const val = price / 1e9; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'B'; + } + // Millions (10^6) + if (price >= 1e6) { + const val = price / 1e6; + return (val >= 100 ? Math.round(val) : val.toFixed(1)) + 'M'; + } + // 100K - 999K: use K suffix + if (price >= 100000) { + return Math.round(price / 1000) + 'K'; + } + // 10K - 99.9K: show as "12.5K" with more precision + if (price >= 10000) { + return (price / 1000).toFixed(1) + 'K'; + } + // 1K - 9.9K: show full number with comma (like "2,500" or "3,750") + if (price >= 1000) { + // Round to nearest 10 for cleaner display + return formatWithCommas(Math.round(price / 10) * 10); + } + // 100 - 999: show full number + if (price >= 100) { + return Math.round(price).toString(); + } + // 10 - 99.99: show with 1 decimal + if (price >= 10) { + return price.toFixed(1); + } + // 1 - 9.99: show with 2 decimals + if (price >= 1) { + return price.toFixed(2); + } + // Small decimals + if (price >= 0.01) { + return price.toFixed(4); + } + if (price >= 0.0001) { + return price.toFixed(5); + } + // For extremely small numbers, show a floor display + if (price < 1e-8) { + return '<0.00001'; + } + return price.toPrecision(3); +}; + +/** + * Calculate edge intensity for zoom behavior + * Returns 0-1 based on how close position is to edge + */ +export const getEdgeIntensity = (position: number, edgeThreshold: number): number => { + if (position < edgeThreshold) { + return 1 - position / edgeThreshold; + } + if (position > 100 - edgeThreshold) { + return (position - (100 - edgeThreshold)) / edgeThreshold; + } + return 0; +}; + +/** + * Format display number with significant digits + */ +export const formatDisplayNumber = (value: number | string, options?: { significantDigits?: number }): string => { + const num = typeof value === 'string' ? parseFloat(value) : value; + if (isNaN(num) || !isFinite(num)) return '0'; + + const significantDigits = options?.significantDigits ?? 6; + + if (num === 0) return '0'; + if (Math.abs(num) < 1e-10) return '<0.0000001'; + if (Math.abs(num) >= 1e15) return num.toExponential(2); + + return num.toPrecision(significantDigits).replace(/\.?0+$/, ''); +}; diff --git a/packages/price-slider/tailwind.config.ts b/packages/price-slider/tailwind.config.ts new file mode 100644 index 0000000000..92425727e9 --- /dev/null +++ b/packages/price-slider/tailwind.config.ts @@ -0,0 +1,10 @@ +import type { Config } from 'tailwindcss'; + +import sharedConfig from '@kyber/tailwind-config'; + +const config: Pick = { + content: ['./src/**/*.tsx'], + presets: [sharedConfig], +}; + +export default config; diff --git a/packages/price-slider/tsconfig.json b/packages/price-slider/tsconfig.json new file mode 100644 index 0000000000..052970bf20 --- /dev/null +++ b/packages/price-slider/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@kyber/typescript-config/react-library.json", + "compilerOptions": { + "target": "ES2020", + "outDir": "dist", + "jsx": "react-jsx", + "paths": { + "@/*": ["./src/*"], + "react": ["./node_modules/@types/react"] + } + }, + "include": ["src", ".eslintrc.cjs"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/price-slider/tsup.config.ts b/packages/price-slider/tsup.config.ts new file mode 100644 index 0000000000..a43cf2302b --- /dev/null +++ b/packages/price-slider/tsup.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { 'price-slider': 'src/index.ts' }, + format: ['esm', 'cjs'], + outDir: 'dist', + target: 'esnext', + clean: true, + dts: true, + minify: false, + sourcemap: true, + onSuccess: 'tsc --noEmit', + external: ['react', 'react-dom'], + noExternal: ['@kyber/utils', '@kyber/eslint-config', '@kyber/tailwind-config'], + esbuildOptions(options) { + options.globalName = 'PriceSlider'; + options.define = { + global: 'globalThis', + }; + options.supported = { + bigint: true, + }; + }, + banner: { + js: ` + // eslint-disable + `, + }, +}); From bfdd4fcae9be9bb200a2a61c94e974eb817a55b5 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 23:01:20 +0700 Subject: [PATCH 66/87] fix: install --- pnpm-lock.yaml | 2031 +++++++++++++++++++++++++++++------------------- 1 file changed, 1241 insertions(+), 790 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1979a8fa0..3240e904ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,7 +40,7 @@ importers: version: 0.1.2 '@esbuild-plugins/node-globals-polyfill': specifier: ^0.2.3 - version: 0.2.3(esbuild@0.27.0) + version: 0.2.3(esbuild@0.24.0) '@ethersproject/abi': specifier: ^5.8.0 version: 5.8.0 @@ -725,7 +725,7 @@ importers: dependencies: '@esbuild-plugins/node-globals-polyfill': specifier: ^0.1.1 - version: 0.1.1(esbuild@0.27.0) + version: 0.1.1(esbuild@0.24.0) '@kyberswap/widgets': specifier: workspace:* version: link:../../packages/swap-widgets @@ -941,7 +941,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -989,10 +989,10 @@ importers: devDependencies: '@vercel/style-guide': specifier: ^5.2.0 - version: 5.2.0(eslint@8.57.1)(prettier@3.5.3)(typescript@5.9.3) + version: 5.2.0(eslint@8.57.0)(prettier@3.5.3)(typescript@5.1.6) eslint-config-turbo: specifier: ^1.12.4 - version: 1.13.4(eslint@8.57.1) + version: 1.13.4(eslint@8.57.0) packages/config-tailwind: devDependencies: @@ -1134,7 +1134,7 @@ importers: version: 10.4.20(postcss@8.4.49) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -1243,7 +1243,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -1343,7 +1343,7 @@ importers: version: 10.4.20(postcss@8.4.49) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -1372,6 +1372,82 @@ importers: specifier: 5.3.2 version: 5.3.2 + packages/price-slider: + dependencies: + '@kyber/ui': + specifier: workspace:^ + version: link:../ui + react: + specifier: '>=16.8.0' + version: 18.3.1 + devDependencies: + '@kyber/eslint-config': + specifier: workspace:* + version: link:../config-eslint + '@kyber/tailwind-config': + specifier: workspace:^ + version: link:../config-tailwind + '@kyber/typescript-config': + specifier: workspace:* + version: link:../config-typescript + '@kyber/utils': + specifier: workspace:^ + version: link:../utils + '@trivago/prettier-plugin-sort-imports': + specifier: ^3.3.1 + version: 3.4.0(prettier@3.5.3) + '@types/eslint': + specifier: ^8.56.5 + version: 8.56.12 + '@types/node': + specifier: ^20.11.24 + version: 20.19.25 + '@types/react': + specifier: ^18.0.17 + version: 18.3.12 + '@types/react-dom': + specifier: ^18.0.6 + version: 18.3.1 + '@typescript-eslint/eslint-plugin': + specifier: ^6.14.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.3.2) + '@typescript-eslint/parser': + specifier: ^6.14.0 + version: 6.21.0(eslint@8.57.0)(typescript@5.3.2) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.4.49) + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.0) + eslint-plugin-prettier: + specifier: ^5.4.0 + version: 5.4.0(@types/eslint@8.56.12)(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.5.3) + eslint-plugin-react: + specifier: ^7.33.2 + version: 7.37.2(eslint@8.57.0) + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.2(eslint@8.57.0) + postcss: + specifier: ^8.4.47 + version: 8.4.49 + prettier: + specifier: ^3.5.3 + version: 3.5.3 + tailwindcss: + specifier: ^3.4.13 + version: 3.4.14 + tsup: + specifier: ^8.3.0 + version: 8.3.5(postcss@8.4.49)(typescript@5.3.2) + typescript: + specifier: 5.3.2 + version: 5.3.2 + packages/schema: dependencies: react: @@ -1871,7 +1947,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -1995,7 +2071,7 @@ importers: version: 0.2.3(@babel/core@7.26.0) esbuild-sass-plugin: specifier: ^3.3.1 - version: 3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3) + version: 3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -2216,20 +2292,6 @@ packages: semver: 6.3.1 dev: true - /@babel/eslint-parser@7.25.9(@babel/core@7.26.0)(eslint@8.57.1): - resolution: {integrity: sha512-5UXfgpK0j0Xr/xIdgdLEhOFxaDZ0bRPWJJchRpqOSur/3rZoPbqqki5mm0p4NE2cs28krBEiSM2MB7//afRSQQ==} - engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} - peerDependencies: - '@babel/core': ^7.11.0 - eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 - dependencies: - '@babel/core': 7.26.0 - '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 - eslint: 8.57.1 - eslint-visitor-keys: 2.1.0 - semver: 6.3.1 - dev: true - /@babel/generator@7.17.7: resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==} engines: {node: '>=6.9.0'} @@ -2276,8 +2338,8 @@ packages: resolution: {integrity: sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9(supports-color@5.5.0) - '@babel/types': 7.26.0 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2320,6 +2382,24 @@ packages: - supports-color dev: true + /@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.28.5) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} engines: {node: '>=6.9.0'} @@ -2350,6 +2430,18 @@ packages: semver: 6.3.1 dev: true + /@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + regexpu-core: 6.1.1 + semver: 6.3.1 + dev: true + /@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} engines: {node: '>=6.9.0'} @@ -2370,9 +2462,24 @@ packages: '@babel/core': 7.26.0 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) lodash.debounce: 4.0.8 - resolve: 1.22.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.28.5): + resolution: {integrity: sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + debug: 4.4.3(supports-color@8.1.1) + lodash.debounce: 4.0.8 + resolve: 1.22.11 transitivePeerDependencies: - supports-color dev: true @@ -2483,6 +2590,20 @@ packages: transitivePeerDependencies: - supports-color + /@babel/helper-module-transforms@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-module-transforms@7.28.3(@babel/core@7.26.0): resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} @@ -2541,7 +2662,21 @@ packages: '@babel/core': 7.26.0 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-wrap-function': 7.25.9 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2574,6 +2709,20 @@ packages: - supports-color dev: true + /@babel/helper-replace-supers@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-replace-supers@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} engines: {node: '>=6.9.0'} @@ -2654,8 +2803,8 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9(supports-color@5.5.0) - '@babel/types': 7.26.0 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color dev: true @@ -2720,6 +2869,19 @@ packages: - supports-color dev: true + /@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} @@ -2743,6 +2905,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} engines: {node: '>=6.9.0'} @@ -2763,6 +2935,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} engines: {node: '>=6.9.0'} @@ -2787,6 +2969,20 @@ packages: - supports-color dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} engines: {node: '>=6.9.0'} @@ -2814,6 +3010,19 @@ packages: - supports-color dev: true + /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3(@babel/core@7.26.0): resolution: {integrity: sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==} engines: {node: '>=6.9.0'} @@ -2917,6 +3126,15 @@ packages: '@babel/core': 7.26.0 dev: true + /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5): + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + dev: true + /@babel/plugin-proposal-private-property-in-object@7.21.11(@babel/core@7.26.0): resolution: {integrity: sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==} engines: {node: '>=6.9.0'} @@ -3027,6 +3245,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-flow@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.5): resolution: {integrity: sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==} engines: {node: '>=6.9.0'} @@ -3047,6 +3275,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==} engines: {node: '>=6.9.0'} @@ -3067,6 +3305,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} engines: {node: '>=6.9.0'} @@ -3139,7 +3387,6 @@ packages: dependencies: '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.25.9 - dev: false /@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5): resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} @@ -3305,6 +3552,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0): resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} engines: {node: '>=6.9.0'} @@ -3316,6 +3573,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.5): + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0): resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} engines: {node: '>=6.9.0'} @@ -3326,6 +3594,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} engines: {node: '>=6.9.0'} @@ -3350,6 +3628,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.28.5) + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.26.0): resolution: {integrity: sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==} engines: {node: '>=6.9.0'} @@ -3378,6 +3670,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==} engines: {node: '>=6.9.0'} @@ -3402,6 +3708,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-block-scoped-functions@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} engines: {node: '>=6.9.0'} @@ -3422,6 +3738,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==} engines: {node: '>=6.9.0'} @@ -3445,6 +3771,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==} engines: {node: '>=6.9.0'} @@ -3471,6 +3810,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.26.0): resolution: {integrity: sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==} engines: {node: '>=6.9.0'} @@ -3501,6 +3853,23 @@ packages: - supports-color dev: true + /@babel/plugin-transform-classes@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.28.5) + '@babel/traverse': 7.28.5 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-classes@7.28.4(@babel/core@7.26.0): resolution: {integrity: sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==} engines: {node: '>=6.9.0'} @@ -3529,6 +3898,17 @@ packages: '@babel/template': 7.25.9 dev: true + /@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/template': 7.25.9 + dev: true + /@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==} engines: {node: '>=6.9.0'} @@ -3550,6 +3930,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} engines: {node: '>=6.9.0'} @@ -3574,6 +3964,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==} engines: {node: '>=6.9.0'} @@ -3595,6 +3996,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} engines: {node: '>=6.9.0'} @@ -3616,6 +4027,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==} engines: {node: '>=6.9.0'} @@ -3637,6 +4059,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} engines: {node: '>=6.9.0'} @@ -3673,6 +4105,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-exponentiation-operator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-exponentiation-operator@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==} engines: {node: '>=6.9.0'} @@ -3693,6 +4138,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} engines: {node: '>=6.9.0'} @@ -3714,6 +4169,17 @@ packages: '@babel/plugin-syntax-flow': 7.26.0(@babel/core@7.26.0) dev: true + /@babel/plugin-transform-flow-strip-types@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-/VVukELzPDdci7UUsWQaSkhgnjIWXnIyRpM02ldxaVoFK96c41So8JcKT3m0gYjyv7j5FNPGS5vfELrWalkbDA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-flow': 7.26.0(@babel/core@7.28.5) + dev: true + /@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.0): resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==} engines: {node: '>=6.9.0'} @@ -3727,6 +4193,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-for-of@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-for-of@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} engines: {node: '>=6.9.0'} @@ -3754,6 +4233,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-function-name@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-function-name@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} engines: {node: '>=6.9.0'} @@ -3778,6 +4271,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==} engines: {node: '>=6.9.0'} @@ -3798,6 +4301,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} engines: {node: '>=6.9.0'} @@ -3818,6 +4331,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==} engines: {node: '>=6.9.0'} @@ -3838,6 +4361,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} engines: {node: '>=6.9.0'} @@ -3861,6 +4394,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} engines: {node: '>=6.9.0'} @@ -3888,6 +4434,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-simple-access': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} engines: {node: '>=6.9.0'} @@ -3916,6 +4476,21 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==} engines: {node: '>=6.9.0'} @@ -3944,6 +4519,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} engines: {node: '>=6.9.0'} @@ -3968,6 +4556,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} engines: {node: '>=6.9.0'} @@ -3989,6 +4588,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-new-target@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-new-target@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} engines: {node: '>=6.9.0'} @@ -4009,6 +4618,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==} engines: {node: '>=6.9.0'} @@ -4029,6 +4648,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==} engines: {node: '>=6.9.0'} @@ -4051,6 +4680,18 @@ packages: '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) dev: true + /@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.28.5) + dev: true + /@babel/plugin-transform-object-rest-spread@7.28.4(@babel/core@7.26.0): resolution: {integrity: sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==} engines: {node: '>=6.9.0'} @@ -4080,6 +4721,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-object-super@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-object-super@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} engines: {node: '>=6.9.0'} @@ -4103,6 +4757,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==} engines: {node: '>=6.9.0'} @@ -4126,6 +4790,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==} engines: {node: '>=6.9.0'} @@ -4149,6 +4826,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-parameters@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-parameters@7.27.7(@babel/core@7.26.0): resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} engines: {node: '>=6.9.0'} @@ -4172,6 +4859,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==} engines: {node: '>=6.9.0'} @@ -4199,6 +4899,20 @@ packages: - supports-color dev: true + /@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==} engines: {node: '>=6.9.0'} @@ -4223,6 +4937,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} engines: {node: '>=6.9.0'} @@ -4339,6 +5063,17 @@ packages: regenerator-transform: 0.15.2 dev: true + /@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + regenerator-transform: 0.15.2 + dev: true + /@babel/plugin-transform-regenerator@7.28.4(@babel/core@7.26.0): resolution: {integrity: sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==} engines: {node: '>=6.9.0'} @@ -4360,6 +5095,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==} engines: {node: '>=6.9.0'} @@ -4381,6 +5127,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} engines: {node: '>=6.9.0'} @@ -4418,6 +5174,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} engines: {node: '>=6.9.0'} @@ -4441,6 +5207,19 @@ packages: - supports-color dev: true + /@babel/plugin-transform-spread@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-spread@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==} engines: {node: '>=6.9.0'} @@ -4464,6 +5243,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} engines: {node: '>=6.9.0'} @@ -4484,6 +5273,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} engines: {node: '>=6.9.0'} @@ -4504,6 +5303,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} engines: {node: '>=6.9.0'} @@ -4530,6 +5339,22 @@ packages: - supports-color dev: true + /@babel/plugin-transform-typescript@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.0): resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} engines: {node: '>=6.9.0'} @@ -4540,6 +5365,16 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} engines: {node: '>=6.9.0'} @@ -4561,6 +5396,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==} engines: {node: '>=6.9.0'} @@ -4583,6 +5429,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} engines: {node: '>=6.9.0'} @@ -4605,6 +5462,17 @@ packages: '@babel/helper-plugin-utils': 7.25.9 dev: true + /@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.28.5): + resolution: {integrity: sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.25.9 + dev: true + /@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.26.0): resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==} engines: {node: '>=6.9.0'} @@ -4696,6 +5564,86 @@ packages: - supports-color dev: true + /@babel/preset-env@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.5) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-block-scoped-functions': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-exponentiation-operator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.28.5) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.28.5) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.5) + babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.28.5) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.28.5) + babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.28.5) + core-js-compat: 3.39.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/preset-env@7.28.5(@babel/core@7.26.0): resolution: {integrity: sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==} engines: {node: '>=6.9.0'} @@ -4777,16 +5725,16 @@ packages: - supports-color dev: true - /@babel/preset-flow@7.25.9(@babel/core@7.26.0): + /@babel/preset-flow@7.25.9(@babel/core@7.28.5): resolution: {integrity: sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-transform-flow-strip-types': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-flow-strip-types': 7.25.9(@babel/core@7.28.5) dev: true /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0): @@ -4796,7 +5744,18 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/types': 7.26.0 + '@babel/types': 7.28.5 + esutils: 2.0.3 + dev: true + + /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.5): + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/types': 7.28.5 esutils: 2.0.3 dev: true @@ -4833,13 +5792,29 @@ packages: - supports-color dev: true - /@babel/register@7.25.9(@babel/core@7.26.0): + /@babel/preset-typescript@7.26.0(@babel/core@7.28.5): + resolution: {integrity: sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/register@7.25.9(@babel/core@7.28.5): resolution: {integrity: sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 @@ -5077,7 +6052,7 @@ packages: istanbul-lib-coverage: 3.2.2 js-yaml: 4.1.0 nyc: 15.1.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) transitivePeerDependencies: - supports-color dev: true @@ -5181,7 +6156,7 @@ packages: bluebird: 3.7.1 debug: 4.4.0(supports-color@5.5.0) lodash: 4.17.21 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) transitivePeerDependencies: - supports-color dev: true @@ -5367,20 +6342,20 @@ packages: resolution: {integrity: sha512-Bz1zLGEqBQ0BVkqt1OgMxdBOE3BdUWUd7Ly9Ecr/aUwkA8AV1w1XzBMe4xblmJHnB1XXNlPH4SraXCvO+q0Mig==} dev: false - /@esbuild-plugins/node-globals-polyfill@0.1.1(esbuild@0.27.0): + /@esbuild-plugins/node-globals-polyfill@0.1.1(esbuild@0.24.0): resolution: {integrity: sha512-MR0oAA+mlnJWrt1RQVQ+4VYuRJW/P2YmRTv1AsplObyvuBMnPHiizUF95HHYiSsMGLhyGtWufaq2XQg6+iurBg==} peerDependencies: esbuild: '*' dependencies: - esbuild: 0.27.0 + esbuild: 0.24.0 dev: false - /@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.27.0): + /@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.24.0): resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} peerDependencies: esbuild: '*' dependencies: - esbuild: 0.27.0 + esbuild: 0.24.0 dev: false /@esbuild/aix-ppc64@0.19.12: @@ -5409,14 +6384,6 @@ packages: requiresBuild: true optional: true - /@esbuild/aix-ppc64@0.27.0: - resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - requiresBuild: true - optional: true - /@esbuild/android-arm64@0.17.19: resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} engines: {node: '>=12'} @@ -5461,14 +6428,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm64@0.27.0: - resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - requiresBuild: true - optional: true - /@esbuild/android-arm@0.15.18: resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} engines: {node: '>=12'} @@ -5522,14 +6481,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-arm@0.27.0: - resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - requiresBuild: true - optional: true - /@esbuild/android-x64@0.17.19: resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} engines: {node: '>=12'} @@ -5574,14 +6525,6 @@ packages: requiresBuild: true optional: true - /@esbuild/android-x64@0.27.0: - resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - requiresBuild: true - optional: true - /@esbuild/darwin-arm64@0.17.19: resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} engines: {node: '>=12'} @@ -5626,14 +6569,6 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-arm64@0.27.0: - resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - optional: true - /@esbuild/darwin-x64@0.17.19: resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} engines: {node: '>=12'} @@ -5678,14 +6613,6 @@ packages: requiresBuild: true optional: true - /@esbuild/darwin-x64@0.27.0: - resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - requiresBuild: true - optional: true - /@esbuild/freebsd-arm64@0.17.19: resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} engines: {node: '>=12'} @@ -5730,14 +6657,6 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-arm64@0.27.0: - resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - optional: true - /@esbuild/freebsd-x64@0.17.19: resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} engines: {node: '>=12'} @@ -5782,14 +6701,6 @@ packages: requiresBuild: true optional: true - /@esbuild/freebsd-x64@0.27.0: - resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - optional: true - /@esbuild/linux-arm64@0.17.19: resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} engines: {node: '>=12'} @@ -5834,14 +6745,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm64@0.27.0: - resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-arm@0.17.19: resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} engines: {node: '>=12'} @@ -5886,14 +6789,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-arm@0.27.0: - resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-ia32@0.17.19: resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} engines: {node: '>=12'} @@ -5938,14 +6833,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ia32@0.27.0: - resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-loong64@0.15.18: resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} engines: {node: '>=12'} @@ -5999,14 +6886,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-loong64@0.27.0: - resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-mips64el@0.17.19: resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} engines: {node: '>=12'} @@ -6051,14 +6930,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-mips64el@0.27.0: - resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-ppc64@0.17.19: resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} engines: {node: '>=12'} @@ -6103,14 +6974,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-ppc64@0.27.0: - resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-riscv64@0.17.19: resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} engines: {node: '>=12'} @@ -6155,14 +7018,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-riscv64@0.27.0: - resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-s390x@0.17.19: resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} engines: {node: '>=12'} @@ -6207,14 +7062,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-s390x@0.27.0: - resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - requiresBuild: true - optional: true - /@esbuild/linux-x64@0.17.19: resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} engines: {node: '>=12'} @@ -6259,22 +7106,6 @@ packages: requiresBuild: true optional: true - /@esbuild/linux-x64@0.27.0: - resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/netbsd-arm64@0.27.0: - resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - requiresBuild: true - optional: true - /@esbuild/netbsd-x64@0.17.19: resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} engines: {node: '>=12'} @@ -6319,14 +7150,6 @@ packages: requiresBuild: true optional: true - /@esbuild/netbsd-x64@0.27.0: - resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - optional: true - /@esbuild/openbsd-arm64@0.24.0: resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} engines: {node: '>=18'} @@ -6335,14 +7158,6 @@ packages: requiresBuild: true optional: true - /@esbuild/openbsd-arm64@0.27.0: - resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - requiresBuild: true - optional: true - /@esbuild/openbsd-x64@0.17.19: resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} engines: {node: '>=12'} @@ -6387,22 +7202,6 @@ packages: requiresBuild: true optional: true - /@esbuild/openbsd-x64@0.27.0: - resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - optional: true - - /@esbuild/openharmony-arm64@0.27.0: - resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - requiresBuild: true - optional: true - /@esbuild/sunos-x64@0.17.19: resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} engines: {node: '>=12'} @@ -6447,14 +7246,6 @@ packages: requiresBuild: true optional: true - /@esbuild/sunos-x64@0.27.0: - resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - requiresBuild: true - optional: true - /@esbuild/win32-arm64@0.17.19: resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} engines: {node: '>=12'} @@ -6499,14 +7290,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-arm64@0.27.0: - resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - requiresBuild: true - optional: true - /@esbuild/win32-ia32@0.17.19: resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} engines: {node: '>=12'} @@ -6551,14 +7334,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-ia32@0.27.0: - resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - requiresBuild: true - optional: true - /@esbuild/win32-x64@0.17.19: resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} engines: {node: '>=12'} @@ -6603,14 +7378,6 @@ packages: requiresBuild: true optional: true - /@esbuild/win32-x64@0.27.0: - resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - requiresBuild: true - optional: true - /@eslint-community/eslint-utils@4.4.1(eslint@8.57.0): resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6620,16 +7387,6 @@ packages: eslint: 8.57.0 eslint-visitor-keys: 3.4.3 - /@eslint-community/eslint-utils@4.4.1(eslint@8.57.1): - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/eslint-utils@4.4.1(eslint@9.14.0): resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6640,16 +7397,6 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@eslint-community/eslint-utils@4.9.0(eslint@8.57.1): - resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/regexpp@4.12.1: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -6712,11 +7459,6 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /@eslint/js@8.57.1: - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - /@eslint/js@9.14.0: resolution: {integrity: sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8142,18 +8884,6 @@ packages: transitivePeerDependencies: - supports-color - /@humanwhocodes/config-array@0.13.0: - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3(supports-color@8.1.1) - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - /@humanwhocodes/module-importer@1.0.1: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} @@ -8216,7 +8946,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-mock: 29.7.0 dev: false @@ -8226,7 +8956,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -8609,7 +9339,7 @@ packages: '@ledgerhq/errors': 6.19.1 '@ledgerhq/logs': 6.12.0 rxjs: 7.8.1 - semver: 7.6.3 + semver: 7.7.3 dev: false /@ledgerhq/devices@8.4.5: @@ -8627,7 +9357,7 @@ packages: '@ledgerhq/errors': 6.21.0 '@ledgerhq/logs': 6.13.0 rxjs: 7.8.1 - semver: 7.6.3 + semver: 7.7.3 dev: false /@ledgerhq/errors@6.19.1: @@ -9254,7 +9984,7 @@ packages: bufferutil: 4.0.9 cross-fetch: 4.0.0 date-fns: 2.30.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) eciesjs: 0.3.21 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -9277,7 +10007,7 @@ packages: bufferutil: 4.0.9 cross-fetch: 4.0.0 date-fns: 2.30.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) eciesjs: 0.4.14 eventemitter2: 6.4.9 readable-stream: 3.6.2 @@ -9336,7 +10066,7 @@ packages: '@types/dom-screen-wake-lock': 1.0.3 bowser: 2.11.0 cross-fetch: 4.0.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) eciesjs: 0.3.21 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -9374,7 +10104,7 @@ packages: '@paulmillr/qr': 0.2.1 bowser: 2.11.0 cross-fetch: 4.0.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) eciesjs: 0.4.14 eth-rpc-errors: 4.0.3 eventemitter2: 6.4.9 @@ -9419,9 +10149,9 @@ packages: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) pony-cause: 2.1.11 - semver: 7.6.3 + semver: 7.7.3 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -9436,9 +10166,9 @@ packages: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) pony-cause: 2.1.11 - semver: 7.6.3 + semver: 7.7.3 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -13075,7 +13805,7 @@ packages: rollup: 3.29.5 dev: false - /@rollup/pluginutils@5.1.3(rollup@3.29.5): + /@rollup/pluginutils@5.1.3: resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} engines: {node: '>=14.0.0'} peerDependencies: @@ -13087,6 +13817,20 @@ packages: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 4.0.2 + dev: true + + /@rollup/pluginutils@5.1.3(rollup@3.29.5): + resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.2 rollup: 3.29.5 /@rollup/rollup-android-arm-eabi@4.25.0: @@ -13887,7 +14631,7 @@ packages: peerDependencies: typescript: '>=5' dependencies: - chalk: 5.4.1 + chalk: 5.6.2 commander: 12.1.0 typescript: 5.3.3 dev: false @@ -14244,7 +14988,7 @@ packages: '@solana/rpc-spec': 2.1.1(typescript@5.3.3) '@solana/rpc-spec-types': 2.1.1(typescript@5.3.3) typescript: 5.3.3 - undici-types: 7.10.0 + undici-types: 7.16.0 dev: false /@solana/rpc-types@2.1.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.3.3): @@ -15671,15 +16415,15 @@ packages: /@storybook/codemod@7.6.20: resolution: {integrity: sha512-8vmSsksO4XukNw0TmqylPmk7PxnfNfE21YsxFa7mnEBmEKQcZCQsNil4ZgWfG0IzdhTfhglAN4r++Ew0WE+PYA==} dependencies: - '@babel/core': 7.26.0 - '@babel/preset-env': 7.26.0(@babel/core@7.26.0) - '@babel/types': 7.26.0 + '@babel/core': 7.28.5 + '@babel/preset-env': 7.26.0(@babel/core@7.28.5) + '@babel/types': 7.28.5 '@storybook/csf': 0.1.13 '@storybook/csf-tools': 7.6.20 '@storybook/node-logger': 7.6.20 '@storybook/types': 7.6.20 '@types/cross-spawn': 6.0.6 - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 globby: 11.1.0 jscodeshift: 0.15.2(@babel/preset-env@7.26.0) lodash: 4.17.21 @@ -15791,7 +16535,7 @@ packages: pretty-hrtime: 1.0.3 prompts: 2.4.2 read-pkg-up: 7.0.1 - semver: 7.6.3 + semver: 7.7.3 telejson: 7.2.0 tiny-invariant: 1.3.3 ts-dedent: 2.2.0 @@ -15958,7 +16702,7 @@ packages: vite: ^3.0.0 || ^4.0.0 || ^5.0.0 dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.3)(vite@4.5.9) - '@rollup/pluginutils': 5.1.3(rollup@3.29.5) + '@rollup/pluginutils': 5.1.3 '@storybook/builder-vite': 7.6.20(typescript@5.3.3)(vite@4.5.9) '@storybook/react': 7.6.20(react-dom@18.3.1)(react@18.3.1)(typescript@5.3.3) '@vitejs/plugin-react': 3.1.0(vite@4.5.9) @@ -16360,7 +17104,7 @@ packages: resolution: {integrity: sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==} engines: {node: '>=10'} dependencies: - '@babel/types': 7.26.0 + '@babel/types': 7.28.5 entities: 4.5.0 dev: true @@ -17635,7 +18379,7 @@ packages: /@types/eslint-scope@3.7.7: resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: - '@types/eslint': 9.6.1 + '@types/eslint': 8.56.12 '@types/estree': 1.0.8 dev: true @@ -17646,13 +18390,6 @@ packages: '@types/json-schema': 7.0.15 dev: true - /@types/eslint@9.6.1: - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - dependencies: - '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 - dev: true - /@types/estree@0.0.51: resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} dev: true @@ -17662,7 +18399,6 @@ packages: /@types/estree@1.0.8: resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - dev: true /@types/express-serve-static-core@4.19.6: resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} @@ -17867,6 +18603,7 @@ packages: resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} dependencies: undici-types: 6.21.0 + dev: true /@types/node@22.10.2: resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} @@ -17884,12 +18621,6 @@ packages: dependencies: undici-types: 6.19.8 - /@types/node@24.10.1: - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} - dependencies: - undici-types: 7.16.0 - dev: false - /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true @@ -18166,7 +18897,7 @@ packages: /@types/webpack-sources@3.2.3: resolution: {integrity: sha512-4nZOdMwSPHZ4pTEZzSp0AsTM4K7Qmu40UKW4tJDiOVs20UzYF9l+qUe4s0ftfN0pin06n+5cWWDJXH+sbhAiDw==} dependencies: - '@types/node': 20.17.6 + '@types/node': 22.10.7 '@types/source-list-map': 0.1.6 source-map: 0.7.6 dev: true @@ -18174,7 +18905,7 @@ packages: /@types/webpack@4.41.40: resolution: {integrity: sha512-u6kMFSBM9HcoTpUXnL6mt2HSzftqb3JgYV6oxIgL2dl6sX6aCa5k6SOkzv5DuZjBTPUE/dJltKtwwuqrkZHpfw==} dependencies: - '@types/node': 20.17.6 + '@types/node': 22.10.7 '@types/tapable': 1.0.12 '@types/uglify-js': 3.17.5 '@types/webpack-sources': 3.2.3 @@ -18217,7 +18948,7 @@ packages: resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} requiresBuild: true dependencies: - '@types/node': 20.17.6 + '@types/node': 22.10.7 dev: true optional: true @@ -18232,17 +18963,17 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.12.1 + '@eslint-community/regexpp': 4.12.2 '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.3.2) '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.0)(typescript@5.3.2) '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.3.2) - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare-lite: 1.4.0 - semver: 7.6.3 + semver: 7.7.3 tsutils: 3.21.0(typescript@5.3.2) typescript: 5.3.2 transitivePeerDependencies: @@ -18335,35 +19066,6 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) - eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/eslint-plugin@8.14.0(@typescript-eslint/parser@8.14.0)(eslint@9.14.0)(typescript@5.3.2): resolution: {integrity: sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18485,27 +19187,6 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) - eslint: 8.57.1 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/parser@8.14.0(eslint@9.14.0)(typescript@5.3.2): resolution: {integrity: sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18630,26 +19311,6 @@ packages: - supports-color dev: true - /@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - debug: 4.4.0(supports-color@5.5.0) - eslint: 8.57.1 - ts-api-utils: 1.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/type-utils@8.14.0(eslint@9.14.0)(typescript@5.3.2): resolution: {integrity: sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18661,7 +19322,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 8.14.0(typescript@5.3.2) '@typescript-eslint/utils': 8.14.0(eslint@9.14.0)(typescript@5.3.2) - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) ts-api-utils: 1.4.0(typescript@5.3.2) typescript: 5.3.2 transitivePeerDependencies: @@ -18683,7 +19344,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.2): + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.1.6): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18694,16 +19355,17 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 - tsutils: 3.21.0(typescript@5.3.2) - typescript: 5.3.2 + semver: 7.7.3 + tsutils: 3.21.0(typescript@5.1.6) + typescript: 5.1.6 transitivePeerDependencies: - supports-color + dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.3): + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.2): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18714,17 +19376,16 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 - tsutils: 3.21.0(typescript@5.3.3) - typescript: 5.3.3 + semver: 7.7.3 + tsutils: 3.21.0(typescript@5.3.2) + typescript: 5.3.2 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.3): + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.3): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18735,12 +19396,12 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 - tsutils: 3.21.0(typescript@5.9.3) - typescript: 5.9.3 + semver: 7.7.3 + tsutils: 3.21.0(typescript@5.3.3) + typescript: 5.3.3 transitivePeerDependencies: - supports-color dev: true @@ -18789,28 +19450,6 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3): - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@5.5.0) - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/typescript-estree@8.14.0(typescript@5.3.2): resolution: {integrity: sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18822,18 +19461,18 @@ packages: dependencies: '@typescript-eslint/types': 8.14.0 '@typescript-eslint/visitor-keys': 8.14.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.3 + semver: 7.7.3 ts-api-utils: 1.4.0(typescript@5.3.2) typescript: 5.3.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.2): + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.1.6): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18844,7 +19483,7 @@ packages: '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.2) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.1.6) eslint: 8.57.0 eslint-scope: 5.1.1 semver: 7.6.3 @@ -18853,7 +19492,7 @@ packages: - typescript dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.3): + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.2): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -18864,7 +19503,7 @@ packages: '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.2) eslint: 8.57.0 eslint-scope: 5.1.1 semver: 7.6.3 @@ -18873,19 +19512,19 @@ packages: - typescript dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.9.3): + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) + eslint: 8.57.0 eslint-scope: 5.1.1 semver: 7.6.3 transitivePeerDependencies: @@ -18931,25 +19570,6 @@ packages: - typescript dev: true - /@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3): - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - eslint: 8.57.1 - semver: 7.6.3 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /@typescript-eslint/utils@8.14.0(eslint@9.14.0)(typescript@5.3.2): resolution: {integrity: sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18992,10 +19612,6 @@ packages: /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - /@ungap/structured-clone@1.3.0: - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - dev: true - /@uniswap/lib@4.0.1-alpha: resolution: {integrity: sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==} engines: {node: '>=10'} @@ -19069,7 +19685,7 @@ packages: '@vanilla-extract/css': 1.14.0 dev: false - /@vercel/style-guide@5.2.0(eslint@8.57.1)(prettier@3.5.3)(typescript@5.9.3): + /@vercel/style-guide@5.2.0(eslint@8.57.0)(prettier@3.5.3)(typescript@5.1.6): resolution: {integrity: sha512-fNSKEaZvSkiBoF6XEefs8CcgAV9K9e+MbcsDZjUsktHycKdA0jvjAzQi1W/FzLS+Nr5zZ6oejCwq/97dHUKe0g==} engines: {node: '>=16'} peerDependencies: @@ -19088,27 +19704,27 @@ packages: optional: true dependencies: '@babel/core': 7.26.0 - '@babel/eslint-parser': 7.25.9(@babel/core@7.26.0)(eslint@8.57.1) + '@babel/eslint-parser': 7.25.9(@babel/core@7.26.0)(eslint@8.57.0) '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 - eslint-config-prettier: 9.1.0(eslint@8.57.1) + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.1.6) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) + eslint: 8.57.0 + eslint-config-prettier: 9.1.0(eslint@8.57.0) eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0) - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.1)(typescript@5.9.3) - eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) - eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.1) - eslint-plugin-react: 7.37.2(eslint@8.57.1) - eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) - eslint-plugin-testing-library: 6.4.0(eslint@8.57.1)(typescript@5.9.3) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.0) + eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(typescript@5.1.6) + eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0) + eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.0) + eslint-plugin-react: 7.37.2(eslint@8.57.0) + eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) + eslint-plugin-testing-library: 6.4.0(eslint@8.57.0)(typescript@5.1.6) eslint-plugin-tsdoc: 0.2.17 - eslint-plugin-unicorn: 48.0.1(eslint@8.57.1) + eslint-plugin-unicorn: 48.0.1(eslint@8.57.0) prettier: 3.5.3 prettier-plugin-packagejson: 2.5.3(prettier@3.5.3) - typescript: 5.9.3 + typescript: 5.1.6 transitivePeerDependencies: - eslint-import-resolver-node - eslint-import-resolver-webpack @@ -19479,7 +20095,7 @@ packages: engines: {node: '>=16'} hasBin: true dependencies: - chalk: 5.4.1 + chalk: 5.6.2 commander: 13.1.0 dev: false @@ -21856,8 +22472,8 @@ packages: peerDependencies: postcss: ^8.1.0 dependencies: - browserslist: 4.24.2 - caniuse-lite: 1.0.30001680 + browserslist: 4.28.0 + caniuse-lite: 1.0.30001757 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -21926,12 +22542,12 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} - /babel-core@7.0.0-bridge.0(@babel/core@7.26.0): + /babel-core@7.0.0-bridge.0(@babel/core@7.28.5): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 dev: true /babel-jest@29.7.0(@babel/core@7.26.0): @@ -21980,7 +22596,7 @@ packages: '@babel/core': 7.26.0 find-cache-dir: 4.0.0 schema-utils: 4.3.3 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /babel-plugin-emotion@10.2.2: @@ -22039,7 +22655,7 @@ packages: dependencies: '@babel/runtime': 7.27.0 cosmiconfig: 6.0.0 - resolve: 1.22.8 + resolve: 1.22.11 dev: false /babel-plugin-macros@3.1.0: @@ -22063,6 +22679,19 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.28.5): + resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.28.5) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.26.0): resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} peerDependencies: @@ -22088,6 +22717,18 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.28.5): + resolution: {integrity: sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.28.5) + core-js-compat: 3.39.0 + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.26.0): resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} peerDependencies: @@ -22111,6 +22752,17 @@ packages: - supports-color dev: true + /babel-plugin-polyfill-regenerator@0.6.3(@babel/core@7.28.5): + resolution: {integrity: sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + dev: true + /babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.26.0): resolution: {integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==} peerDependencies: @@ -22802,7 +23454,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001680 + caniuse-lite: 1.0.30001757 electron-to-chromium: 1.5.57 node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) @@ -23048,6 +23700,7 @@ packages: /caniuse-lite@1.0.30001680: resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} + dev: false /caniuse-lite@1.0.30001757: resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} @@ -23202,7 +23855,7 @@ packages: engines: {node: '>=12.13.0'} hasBin: true dependencies: - '@types/node': 24.10.1 + '@types/node': 22.10.7 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -23218,7 +23871,7 @@ packages: /chromium-edge-launcher@0.2.0: resolution: {integrity: sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==} dependencies: - '@types/node': 20.19.25 + '@types/node': 22.10.7 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 1.4.2 @@ -23657,7 +24310,7 @@ packages: /core-js-compat@3.39.0: resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==} dependencies: - browserslist: 4.24.2 + browserslist: 4.28.0 dev: true /core-js-compat@3.47.0: @@ -24796,7 +25449,7 @@ packages: hasBin: true dependencies: address: 1.2.2 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: true @@ -25696,19 +26349,19 @@ packages: peerDependencies: esbuild: '>=0.12 <1' dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) esbuild: 0.18.20 transitivePeerDependencies: - supports-color dev: true - /esbuild-sass-plugin@3.3.1(esbuild@0.27.0)(sass-embedded@1.93.3): + /esbuild-sass-plugin@3.3.1(esbuild@0.24.0)(sass-embedded@1.93.3): resolution: {integrity: sha512-SnO1ls+d52n6j8gRRpjexXI8MsHEaumS0IdDHaYM29Y6gakzZYMls6i9ql9+AWMSQk/eryndmUpXEgT34QrX1A==} peerDependencies: esbuild: '>=0.20.1' sass-embedded: ^1.71.1 dependencies: - esbuild: 0.27.0 + esbuild: 0.24.0 resolve: 1.22.8 safe-identifier: 0.4.2 sass: 1.80.7 @@ -25934,39 +26587,6 @@ packages: '@esbuild/win32-ia32': 0.24.0 '@esbuild/win32-x64': 0.24.0 - /esbuild@0.27.0: - resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} - engines: {node: '>=18'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.0 - '@esbuild/android-arm': 0.27.0 - '@esbuild/android-arm64': 0.27.0 - '@esbuild/android-x64': 0.27.0 - '@esbuild/darwin-arm64': 0.27.0 - '@esbuild/darwin-x64': 0.27.0 - '@esbuild/freebsd-arm64': 0.27.0 - '@esbuild/freebsd-x64': 0.27.0 - '@esbuild/linux-arm': 0.27.0 - '@esbuild/linux-arm64': 0.27.0 - '@esbuild/linux-ia32': 0.27.0 - '@esbuild/linux-loong64': 0.27.0 - '@esbuild/linux-mips64el': 0.27.0 - '@esbuild/linux-ppc64': 0.27.0 - '@esbuild/linux-riscv64': 0.27.0 - '@esbuild/linux-s390x': 0.27.0 - '@esbuild/linux-x64': 0.27.0 - '@esbuild/netbsd-arm64': 0.27.0 - '@esbuild/netbsd-x64': 0.27.0 - '@esbuild/openbsd-arm64': 0.27.0 - '@esbuild/openbsd-x64': 0.27.0 - '@esbuild/openharmony-arm64': 0.27.0 - '@esbuild/sunos-x64': 0.27.0 - '@esbuild/win32-arm64': 0.27.0 - '@esbuild/win32-ia32': 0.27.0 - '@esbuild/win32-x64': 0.27.0 - /escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -26048,15 +26668,6 @@ packages: eslint: 8.57.0 dev: true - /eslint-config-prettier@9.1.0(eslint@8.57.1): - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - dependencies: - eslint: 8.57.1 - dev: true - /eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.27.1)(@babel/plugin-transform-react-jsx@7.27.1)(eslint@8.57.0)(typescript@5.3.2): resolution: {integrity: sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==} engines: {node: '>=14.0.0'} @@ -26101,22 +26712,13 @@ packages: eslint-plugin-turbo: 1.13.4(eslint@8.57.0) dev: true - /eslint-config-turbo@1.13.4(eslint@8.57.1): - resolution: {integrity: sha512-+we4eWdZlmlEn7LnhXHCIPX/wtujbHCS7XjQM/TN09BHNEl2fZ8id4rHfdfUKIYTSKyy8U/nNyJ0DNoZj5Q8bw==} - peerDependencies: - eslint: '>6.6.0' - dependencies: - eslint: 8.57.1 - eslint-plugin-turbo: 1.13.4(eslint@8.57.1) - dev: true - /eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0): resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==} engines: {node: '>= 4'} peerDependencies: eslint-plugin-import: '>=1.4.0' dependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) dev: true /eslint-import-resolver-node@0.3.9: @@ -26157,7 +26759,7 @@ packages: - eslint-import-resolver-webpack - supports-color - /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.1): + /eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.0): resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -26173,9 +26775,9 @@ packages: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@5.5.0) enhanced-resolve: 5.17.1 - eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint: 8.57.0 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -26216,7 +26818,7 @@ packages: transitivePeerDependencies: - supports-color - /eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + /eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} engines: {node: '>=4'} peerDependencies: @@ -26237,11 +26839,11 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) debug: 3.2.7(supports-color@8.1.1) - eslint: 8.57.1 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0)(eslint-plugin-import@2.31.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color dev: true @@ -26253,14 +26855,14 @@ packages: postcss: 7.0.39 requireindex: 1.1.0 - /eslint-plugin-eslint-comments@3.2.0(eslint@8.57.1): + /eslint-plugin-eslint-comments@3.2.0(eslint@8.57.0): resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} engines: {node: '>=6.5.0'} peerDependencies: eslint: '>=4.19.1' dependencies: escape-string-regexp: 1.0.5 - eslint: 8.57.1 + eslint: 8.57.0 ignore: 5.3.2 dev: true @@ -26315,7 +26917,7 @@ packages: - eslint-import-resolver-webpack - supports-color - /eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + /eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} engines: {node: '>=4'} peerDependencies: @@ -26326,16 +26928,16 @@ packages: optional: true dependencies: '@rtsao/scc': 1.1.0 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 - eslint: 8.57.1 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -26373,7 +26975,7 @@ packages: - typescript dev: true - /eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.1)(typescript@5.9.3): + /eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(typescript@5.1.6): resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -26386,9 +26988,9 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.1.6) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.1.6) + eslint: 8.57.0 transitivePeerDependencies: - supports-color - typescript @@ -26417,30 +27019,6 @@ packages: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.1 - /eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1): - resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - dependencies: - aria-query: 5.3.2 - array-includes: 3.1.8 - array.prototype.flatmap: 1.3.2 - ast-types-flow: 0.0.8 - axe-core: 4.10.2 - axobject-query: 4.1.0 - damerau-levenshtein: 1.0.8 - emoji-regex: 9.2.2 - eslint: 8.57.1 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - language-tags: 1.0.9 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - safe-regex-test: 1.0.3 - string.prototype.includes: 2.0.1 - dev: true - /eslint-plugin-lingui@0.2.2(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-orWu6o/IkCckLD0owLt+8fKy6SsIqIR9kz2L36KbCuAIfWg3dLRWC+XvuqQWEgJ4raRt+eoomTMunUJiBeogDQ==} engines: {node: '>=16.0.0'} @@ -26452,7 +27030,7 @@ packages: - typescript dev: true - /eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.1): + /eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0)(eslint@8.57.0): resolution: {integrity: sha512-DcHpF0SLbNeh9MT4pMzUGuUSnJ7q5MWbP8sSEFIMS6j7Ggnduq8ghNlfhURgty4c1YFny7Ge9xYTO1FSAoV2Vw==} peerDependencies: eslint: '>=7' @@ -26461,8 +27039,8 @@ packages: eslint-plugin-jest: optional: true dependencies: - eslint: 8.57.1 - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.1)(typescript@5.9.3) + eslint: 8.57.0 + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(typescript@5.1.6) dev: true /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0)(eslint@8.57.0)(prettier@2.8.8): @@ -26512,15 +27090,6 @@ packages: dependencies: eslint: 8.57.0 - /eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): - resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - dependencies: - eslint: 8.57.1 - dev: true - /eslint-plugin-react-hooks@5.1.0-rc-fb9a90fa48-20240614(eslint@9.14.0): resolution: {integrity: sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w==} engines: {node: '>=10'} @@ -26572,33 +27141,6 @@ packages: string.prototype.matchall: 4.0.11 string.prototype.repeat: 1.0.0 - /eslint-plugin-react@7.37.2(eslint@8.57.1): - resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - dependencies: - array-includes: 3.1.8 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.2 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.2.0 - eslint: 8.57.1 - estraverse: 5.3.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 - object.entries: 1.1.8 - object.fromentries: 2.0.8 - object.values: 1.2.0 - prop-types: 15.8.1 - resolve: 2.0.0-next.5 - semver: 6.3.1 - string.prototype.matchall: 4.0.11 - string.prototype.repeat: 1.0.0 - dev: true - /eslint-plugin-storybook@0.6.15(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-lAGqVAJGob47Griu29KXYowI4G7KwMoJDOkEip8ujikuDLxU+oWJ1l0WL6F2oDO4QiyUFXvtDkEkISMOPzo+7w==} engines: {node: 12.x || 14.x || >= 16} @@ -26628,14 +27170,14 @@ packages: - typescript dev: true - /eslint-plugin-testing-library@6.4.0(eslint@8.57.1)(typescript@5.9.3): + /eslint-plugin-testing-library@6.4.0(eslint@8.57.0)(typescript@5.1.6): resolution: {integrity: sha512-yeWF+YgCgvNyPNI9UKnG0FjeE2sk93N/3lsKqcmR8dSfeXJwFT5irnWo7NjLf152HkRzfoFjh3LsBUrhvFz4eA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} peerDependencies: eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.1.6) + eslint: 8.57.0 transitivePeerDependencies: - supports-color - typescript @@ -26657,26 +27199,17 @@ packages: eslint: 8.57.0 dev: true - /eslint-plugin-turbo@1.13.4(eslint@8.57.1): - resolution: {integrity: sha512-82GfMzrewI/DJB92Bbch239GWbGx4j1zvjk1lqb06lxIlMPnVwUHVwPbAnLfyLG3JuhLv9whxGkO/q1CL18JTg==} - peerDependencies: - eslint: '>6.6.0' - dependencies: - dotenv: 16.0.3 - eslint: 8.57.1 - dev: true - - /eslint-plugin-unicorn@48.0.1(eslint@8.57.1): + /eslint-plugin-unicorn@48.0.1(eslint@8.57.0): resolution: {integrity: sha512-FW+4r20myG/DqFcCSzoumaddKBicIPeFnTrifon2mWIzlfyvzwyqZjqVP7m4Cqr/ZYisS2aiLghkUWaPg6vtCw==} engines: {node: '>=16'} peerDependencies: eslint: '>=8.44.0' dependencies: '@babel/helper-validator-identifier': 7.25.9 - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.0) ci-info: 3.9.0 clean-regexp: 1.0.0 - eslint: 8.57.1 + eslint: 8.57.0 esquery: 1.6.0 indent-string: 4.0.0 is-builtin-module: 3.2.1 @@ -26809,54 +27342,6 @@ packages: transitivePeerDependencies: - supports-color - /eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.3.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@8.1.1) - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.1 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true - /eslint@9.14.0: resolution: {integrity: sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -26970,7 +27455,7 @@ packages: /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 dev: true /esutils@2.0.3: @@ -27205,7 +27690,7 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} dependencies: - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -27366,7 +27851,7 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -27834,7 +28319,7 @@ packages: resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} engines: {node: '>=8.0.0'} dependencies: - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 signal-exit: 3.0.7 dev: true @@ -27842,7 +28327,7 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} dependencies: - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 signal-exit: 4.1.0 dev: true @@ -28679,7 +29164,7 @@ packages: pretty-error: 2.1.2 tapable: 1.1.3 util.promisify: 1.0.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /html-webpack-plugin@5.6.5(webpack@5.103.0): @@ -28699,7 +29184,7 @@ packages: lodash: 4.17.21 pretty-error: 4.0.0 tapable: 2.3.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /html2canvas@1.4.1: @@ -28823,7 +29308,7 @@ packages: engines: {node: '>= 6.0.0'} dependencies: agent-base: 5.1.1 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color dev: true @@ -29149,7 +29634,7 @@ packages: /is-bun-module@1.2.1: resolution: {integrity: sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==} dependencies: - semver: 7.6.3 + semver: 7.7.3 /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} @@ -29173,7 +29658,6 @@ packages: engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 - dev: true /is-data-view@1.0.1: resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} @@ -29622,7 +30106,7 @@ packages: resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -29647,7 +30131,7 @@ packages: engines: {node: '>=8'} dependencies: archy: 1.0.0 - cross-spawn: 7.0.5 + cross-spawn: 7.0.6 istanbul-lib-coverage: 3.2.2 p-map: 3.0.0 rimraf: 3.0.2 @@ -29667,7 +30151,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -29752,7 +30236,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-mock: 29.7.0 jest-util: 29.7.0 dev: false @@ -29807,7 +30291,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.19.25 + '@types/node': 22.10.7 jest-util: 29.7.0 dev: false @@ -29841,7 +30325,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.19.25 + '@types/node': 22.10.7 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -29944,13 +30428,6 @@ packages: dependencies: argparse: 2.0.1 - /js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - /jsbi@3.2.5: resolution: {integrity: sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ==} dev: false @@ -29976,18 +30453,18 @@ packages: '@babel/preset-env': optional: true dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.2 - '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.28.5) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.28.5) '@babel/preset-env': 7.26.0(@babel/core@7.26.0) - '@babel/preset-flow': 7.25.9(@babel/core@7.26.0) - '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) - '@babel/register': 7.25.9(@babel/core@7.26.0) - babel-core: 7.0.0-bridge.0(@babel/core@7.26.0) + '@babel/preset-flow': 7.25.9(@babel/core@7.28.5) + '@babel/preset-typescript': 7.26.0(@babel/core@7.28.5) + '@babel/register': 7.25.9(@babel/core@7.28.5) + babel-core: 7.0.0-bridge.0(@babel/core@7.28.5) chalk: 4.1.2 flow-parser: 0.252.0 graceful-fs: 4.2.11 @@ -30629,7 +31106,7 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} dependencies: - semver: 7.6.3 + semver: 7.7.3 dev: true /make-error-cause@2.3.0: @@ -31359,7 +31836,7 @@ packages: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} dependencies: '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -31947,7 +32424,7 @@ packages: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.8 + resolve: 1.22.11 semver: 5.7.2 validate-npm-package-license: 3.0.4 dev: true @@ -33398,7 +33875,7 @@ packages: engines: {node: '>=8.16.0'} dependencies: '@types/mime-types': 2.1.4 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 @@ -34843,7 +35320,7 @@ packages: /resolve@1.19.0: resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} dependencies: - is-core-module: 2.15.1 + is-core-module: 2.16.1 path-parse: 1.0.7 dev: true @@ -34855,7 +35332,6 @@ packages: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} @@ -34981,7 +35457,7 @@ packages: dependencies: open: 8.4.2 picomatch: 2.3.1 - source-map: 0.7.4 + source-map: 0.7.6 yargs: 17.7.2 dev: false @@ -35504,7 +35980,6 @@ packages: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true - dev: false /send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} @@ -35827,7 +36302,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) socks: 2.8.5 transitivePeerDependencies: - supports-color @@ -35890,7 +36365,7 @@ packages: git-hooks-list: 3.1.0 globby: 13.2.2 is-plain-obj: 4.1.0 - semver: 7.6.3 + semver: 7.7.3 sort-object-keys: 1.1.3 dev: true @@ -35917,15 +36392,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - /source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - dev: false - /source-map@0.7.6: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} - dev: true /source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} @@ -35980,7 +36449,7 @@ packages: /spdy-transport@3.0.0: resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -35994,7 +36463,7 @@ packages: resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} engines: {node: '>=6.0.0'} dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3(supports-color@8.1.1) handle-thing: 2.0.1 http-deceiver: 1.2.7 select-hose: 2.0.0 @@ -36010,7 +36479,7 @@ packages: webpack: ^1 || ^2 || ^3 || ^4 || ^5 dependencies: chalk: 4.1.2 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /split-on-first@1.1.0: @@ -36717,7 +37186,7 @@ packages: unique-string: 2.0.0 dev: true - /terser-webpack-plugin@5.3.14(esbuild@0.27.0)(webpack@5.103.0): + /terser-webpack-plugin@5.3.14(esbuild@0.24.0)(webpack@5.103.0): resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -36734,12 +37203,12 @@ packages: optional: true dependencies: '@jridgewell/trace-mapping': 0.3.31 - esbuild: 0.27.0 + esbuild: 0.24.0 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 terser: 5.44.1 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /terser@4.8.1: @@ -37059,15 +37528,6 @@ packages: typescript: 5.3.2 dev: true - /ts-api-utils@1.4.0(typescript@5.9.3): - resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - dependencies: - typescript: 5.9.3 - dev: true - /ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} @@ -37250,33 +37710,33 @@ packages: - yaml dev: true - /tsutils@3.21.0(typescript@5.3.2): + /tsutils@3.21.0(typescript@5.1.6): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.3.2 + typescript: 5.1.6 + dev: true - /tsutils@3.21.0(typescript@5.3.3): + /tsutils@3.21.0(typescript@5.3.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.3.3 - dev: true + typescript: 5.3.2 - /tsutils@3.21.0(typescript@5.9.3): + /tsutils@3.21.0(typescript@5.3.3): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 5.9.3 + typescript: 5.3.3 dev: true /tunnel-agent@0.6.0: @@ -37559,12 +38019,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - /typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - dev: true - /ua-is-frozen@0.1.2: resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} dev: false @@ -37659,10 +38113,7 @@ packages: /undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - /undici-types@7.10.0: - resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} - dev: false + dev: true /undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -38638,7 +39089,7 @@ packages: peerDependencies: vite: ^2.6.0 || 3 || 4 dependencies: - '@rollup/pluginutils': 5.1.3(rollup@3.29.5) + '@rollup/pluginutils': 5.1.3 '@svgr/core': 6.5.1 vite: 4.5.9(@types/node@20.17.6) transitivePeerDependencies: @@ -38651,7 +39102,7 @@ packages: peerDependencies: vite: '>=2.6.0' dependencies: - '@rollup/pluginutils': 5.1.3(rollup@3.29.5) + '@rollup/pluginutils': 5.1.3 '@svgr/core': 8.1.0(typescript@5.3.2) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0) vite: 5.4.11(@types/node@20.17.6) @@ -39082,7 +39533,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.3.0 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) dev: true /webpack-dev-server@4.15.2(debug@4.3.7)(webpack@5.103.0): @@ -39126,7 +39577,7 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.103.0(esbuild@0.27.0) + webpack: 5.103.0(esbuild@0.24.0) webpack-dev-middleware: 5.3.4(webpack@5.103.0) ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: @@ -39154,7 +39605,7 @@ packages: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} dev: true - /webpack@5.103.0(esbuild@0.27.0): + /webpack@5.103.0(esbuild@0.24.0): resolution: {integrity: sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==} engines: {node: '>=10.13.0'} hasBin: true @@ -39186,7 +39637,7 @@ packages: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.14(esbuild@0.27.0)(webpack@5.103.0) + terser-webpack-plugin: 5.3.14(esbuild@0.24.0)(webpack@5.103.0) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: From 2e2a3b5a7d6df256b50c1057170db7f7106c0d87 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 23:02:25 +0700 Subject: [PATCH 67/87] fix: update build-package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9891eb68f8..d3edfb5436 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ }, "scripts": { "build": "turbo build", - "build-package": "pnpm --parallel -r --filter \"@kyber/svgr-esbuild-plugin\" --filter \"@kyber/schema\" build && pnpm --parallel -r --filter \"@kyber/utils\" --filter \"@kyber/ui\" build && pnpm --filter \"@kyberswap/liquidity-chart\" build && pnpm --parallel --filter \"@kyberswap/liquidity-widgets\" --filter \"@kyberswap/zap-migration-widgets\" --filter \"@kyberswap/zap-out-widgets\" --filter \"@kyberswap/pancake-liquidity-widgets\" --filter \"@kyberswap/compounding-widget\" build", + "build-package": "pnpm --parallel -r --filter \"@kyber/svgr-esbuild-plugin\" --filter \"@kyber/schema\" build && pnpm --parallel -r --filter \"@kyber/utils\" --filter \"@kyber/ui\" build && pnpm --filter \"@kyberswap/price-slider\" build && pnpm --filter \"@kyberswap/liquidity-chart\" build && pnpm --parallel --filter \"@kyberswap/liquidity-widgets\" --filter \"@kyberswap/zap-migration-widgets\" --filter \"@kyberswap/zap-out-widgets\" --filter \"@kyberswap/pancake-liquidity-widgets\" --filter \"@kyberswap/compounding-widget\" build", "lint": "turbo lint", "dev": "turbo dev", "type-check": "turbo type-check" From 118ea39f5701d79f5afe6bc42b305bf4dacacbb8 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 23:12:55 +0700 Subject: [PATCH 68/87] fix: remove export --- packages/price-slider/src/index.ts | 32 ------------------------------ 1 file changed, 32 deletions(-) diff --git a/packages/price-slider/src/index.ts b/packages/price-slider/src/index.ts index 4a6deda1b8..0c764f4fc0 100644 --- a/packages/price-slider/src/index.ts +++ b/packages/price-slider/src/index.ts @@ -1,36 +1,4 @@ -// Import styles for CSS bundling import '@/styles.css'; -// Main component - export both default and named for flexibility export { default } from '@/components/UniswapPriceSlider'; export { default as UniswapPriceSlider } from '@/components/UniswapPriceSlider'; - -// Sub-components export -export { default as PriceAxis } from '@/components/PriceAxis'; -export { default as PriceSliderSkeleton } from '@/components/Skeleton'; - -// Types export -export type { HandleType, PoolInfo, PriceAxisProps, UniswapPriceSliderProps, ViewRange } from '@/types'; - -// Hooks export -export { useDebouncedTicks, useSmoothZoom, useTickPositionConverter } from '@/hooks'; - -// Utils export -export { brushHandlePath, formatAxisPrice, formatDisplayNumber, getEdgeIntensity } from '@/utils'; - -// Constants export -export { - AUTO_CENTER_PADDING, - DEBOUNCE_DELAY, - EDGE_THRESHOLD, - HANDLE_LERP_MAX, - HANDLE_LERP_MIN, - LERP_CLOSE_THRESHOLD, - LERP_FAR_THRESHOLD, - MAX_AXIS_TICK_COUNT, - MAX_TICK_SPEED, - MIN_AXIS_TICK_COUNT, - MIN_HANDLE_DISTANCE_MULTIPLIER, - SKELETON_AXIS_POSITIONS, - ZOOM_DURATION, -} from '@/constants'; From b9252f12b284ccfade06cb76b607548180b8d673 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Tue, 2 Dec 2025 23:25:43 +0700 Subject: [PATCH 69/87] fix: hooks file --- packages/price-slider/src/hooks/index.ts | 324 +----------------- .../src/hooks/useDebouncedTicks.ts | 205 +++++++++++ .../price-slider/src/hooks/useSmoothZoom.ts | 82 +++++ .../src/hooks/useTickPositionConverter.ts | 39 +++ 4 files changed, 329 insertions(+), 321 deletions(-) create mode 100644 packages/price-slider/src/hooks/useDebouncedTicks.ts create mode 100644 packages/price-slider/src/hooks/useSmoothZoom.ts create mode 100644 packages/price-slider/src/hooks/useTickPositionConverter.ts diff --git a/packages/price-slider/src/hooks/index.ts b/packages/price-slider/src/hooks/index.ts index ac320e413c..b08011d1e1 100644 --- a/packages/price-slider/src/hooks/index.ts +++ b/packages/price-slider/src/hooks/index.ts @@ -1,321 +1,3 @@ -import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; - -import { nearestUsableTick } from '@kyber/utils/dist/uniswapv3'; - -import { - DEBOUNCE_DELAY, - HANDLE_LERP_MAX, - HANDLE_LERP_MIN, - LERP_CLOSE_THRESHOLD, - LERP_FAR_THRESHOLD, - MAX_TICK_SPEED, - ZOOM_DURATION, -} from '@/constants'; -import type { ViewRange } from '@/types'; - -/** Easing function: ease-out cubic for smooth deceleration */ -const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3); - -/** - * Hook for smooth zoom animation using easing function - * Uses ease-out for natural deceleration feel - */ -export const useSmoothZoom = ( - viewRange: ViewRange | null, - setViewRange: Dispatch>, -) => { - const zoomAnimationRef = useRef(null); - const targetViewRangeRef = useRef(null); - const startViewRangeRef = useRef(null); - const startTimeRef = useRef(0); - - const animateZoom = useCallback(() => { - if (!targetViewRangeRef.current || !startViewRangeRef.current) { - zoomAnimationRef.current = null; - return; - } - - const now = performance.now(); - const elapsed = now - startTimeRef.current; - const progress = Math.min(elapsed / ZOOM_DURATION, 1); - const easedProgress = easeOutCubic(progress); - - const start = startViewRangeRef.current; - const target = targetViewRangeRef.current; - - const newMin = start.min + (target.min - start.min) * easedProgress; - const newMax = start.max + (target.max - start.max) * easedProgress; - - setViewRange({ min: newMin, max: newMax }); - - if (progress < 1) { - // Continue animation - zoomAnimationRef.current = requestAnimationFrame(animateZoom); - } else { - // Animation complete - set exact target values - setViewRange(target); - targetViewRangeRef.current = null; - startViewRangeRef.current = null; - zoomAnimationRef.current = null; - } - }, [setViewRange]); - - const startSmoothZoom = useCallback( - (targetMin: number, targetMax: number) => { - // If already animating, use current position as new start - if (zoomAnimationRef.current && viewRange) { - startViewRangeRef.current = viewRange; - } else if (viewRange) { - startViewRangeRef.current = viewRange; - } - - targetViewRangeRef.current = { min: targetMin, max: targetMax }; - startTimeRef.current = performance.now(); - - if (!zoomAnimationRef.current) { - zoomAnimationRef.current = requestAnimationFrame(animateZoom); - } - }, - [animateZoom, viewRange], - ); - - // Cleanup animation on unmount - useEffect(() => { - return () => { - if (zoomAnimationRef.current) { - cancelAnimationFrame(zoomAnimationRef.current); - } - }; - }, []); - - return { startSmoothZoom }; -}; - -/** - * Hook for smooth tick updates with animation and debouncing - * Handles move slowly/smoothly towards target position - */ -export const useDebouncedTicks = ( - lowerTick: number | undefined, - upperTick: number | undefined, - setLowerTick: (tick: number) => void, - setUpperTick: (tick: number) => void, - isDragging: boolean, -) => { - const debounceTimerRef = useRef(null); - const animationRef = useRef(null); - - // Target ticks (where user wants to go) - const targetLowerTickRef = useRef(lowerTick); - const targetUpperTickRef = useRef(upperTick); - - // Current internal tick values (tracked via refs for animation loop) - const internalLowerRef = useRef(lowerTick); - const internalUpperRef = useRef(upperTick); - - // Internal tick state for React rendering - const [internalLowerTick, setInternalLowerTick] = useState(lowerTick); - const [internalUpperTick, setInternalUpperTick] = useState(upperTick); - - // Helper: calculate dynamic lerp factor based on distance - const getDynamicLerp = useCallback((diff: number): number => { - const absDiff = Math.abs(diff); - if (absDiff > LERP_FAR_THRESHOLD) return HANDLE_LERP_MIN; - if (absDiff < LERP_CLOSE_THRESHOLD) return HANDLE_LERP_MAX; - const t = (absDiff - LERP_CLOSE_THRESHOLD) / (LERP_FAR_THRESHOLD - LERP_CLOSE_THRESHOLD); - return HANDLE_LERP_MAX - t * (HANDLE_LERP_MAX - HANDLE_LERP_MIN); - }, []); - - // Animation function for smooth handle movement - const animateHandles = useCallback(() => { - let needsAnimation = false; - - // Update lower tick - if (internalLowerRef.current !== undefined && targetLowerTickRef.current !== undefined) { - const current = internalLowerRef.current; - const target = targetLowerTickRef.current; - const diff = target - current; - - if (Math.abs(diff) >= 1) { - needsAnimation = true; - const lerpFactor = getDynamicLerp(diff); - const lerpMovement = diff * lerpFactor; - const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED); - const newValue = current + cappedMovement; - internalLowerRef.current = newValue; - setInternalLowerTick(newValue); - } else if (current !== target) { - internalLowerRef.current = target; - setInternalLowerTick(target); - } - } - - // Update upper tick - if (internalUpperRef.current !== undefined && targetUpperTickRef.current !== undefined) { - const current = internalUpperRef.current; - const target = targetUpperTickRef.current; - const diff = target - current; - - if (Math.abs(diff) >= 1) { - needsAnimation = true; - const lerpFactor = getDynamicLerp(diff); - const lerpMovement = diff * lerpFactor; - const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED); - const newValue = current + cappedMovement; - internalUpperRef.current = newValue; - setInternalUpperTick(newValue); - } else if (current !== target) { - internalUpperRef.current = target; - setInternalUpperTick(target); - } - } - - if (needsAnimation) { - animationRef.current = requestAnimationFrame(animateHandles); - } else { - animationRef.current = null; - } - }, [getDynamicLerp]); - - // Start animation if not already running - const startAnimation = useCallback(() => { - if (!animationRef.current) { - animationRef.current = requestAnimationFrame(animateHandles); - } - }, [animateHandles]); - - // Sync internal state with props when not dragging - useEffect(() => { - if (!isDragging) { - targetLowerTickRef.current = lowerTick; - targetUpperTickRef.current = upperTick; - internalLowerRef.current = lowerTick; - internalUpperRef.current = upperTick; - setInternalLowerTick(lowerTick); - setInternalUpperTick(upperTick); - } - }, [lowerTick, upperTick, isDragging]); - - // Smooth update functions - set target and start animation - const debouncedSetLowerTick = useCallback( - (tick: number) => { - targetLowerTickRef.current = tick; - startAnimation(); - - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current); - } - debounceTimerRef.current = setTimeout(() => { - setLowerTick(tick); - }, DEBOUNCE_DELAY); - }, - [setLowerTick, startAnimation], - ); - - const debouncedSetUpperTick = useCallback( - (tick: number) => { - targetUpperTickRef.current = tick; - startAnimation(); - - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current); - } - debounceTimerRef.current = setTimeout(() => { - setUpperTick(tick); - }, DEBOUNCE_DELAY); - }, - [setUpperTick, startAnimation], - ); - - // Flush debounced values immediately - const flushDebouncedValues = useCallback(() => { - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current); - debounceTimerRef.current = null; - } - - // Set final values from targets - const finalLower = targetLowerTickRef.current; - const finalUpper = targetUpperTickRef.current; - - if (finalLower !== undefined && finalLower !== lowerTick) { - setLowerTick(finalLower); - internalLowerRef.current = finalLower; - setInternalLowerTick(finalLower); - } - if (finalUpper !== undefined && finalUpper !== upperTick) { - setUpperTick(finalUpper); - internalUpperRef.current = finalUpper; - setInternalUpperTick(finalUpper); - } - - // Stop animation - if (animationRef.current) { - cancelAnimationFrame(animationRef.current); - animationRef.current = null; - } - }, [lowerTick, upperTick, setLowerTick, setUpperTick]); - - // Cleanup on unmount - useEffect(() => { - return () => { - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current); - } - if (animationRef.current) { - cancelAnimationFrame(animationRef.current); - } - }; - }, []); - - // Get target ticks (what user actually wants, not the animated value) - const getTargetTicks = useCallback(() => { - return { - lowerTick: targetLowerTickRef.current, - upperTick: targetUpperTickRef.current, - }; - }, []); - - return { - internalLowerTick, - internalUpperTick, - debouncedSetLowerTick, - debouncedSetUpperTick, - flushDebouncedValues, - getTargetTicks, - }; -}; - -/** - * Hook for converting between tick and position - * When invertPrice = true, the entire visual is flipped: - * - Lower tick (higher inverted price) appears on the RIGHT - * - Upper tick (lower inverted price) appears on the LEFT - * - Axis shows inverted prices from low (left) to high (right) - */ -export const useTickPositionConverter = (viewRange: ViewRange | null, tickSpacing: number, invertPrice?: boolean) => { - const getPositionFromTick = useCallback( - (tick: number): number => { - if (!viewRange) return 50; - const { min, max } = viewRange; - const normalPosition = ((tick - min) / (max - min)) * 100; - // When invertPrice, flip the position so higher inverted price is on the right - return invertPrice ? 100 - normalPosition : normalPosition; - }, - [viewRange, invertPrice], - ); - - const getTickFromPosition = useCallback( - (position: number): number => { - if (!viewRange) return 0; - const { min, max } = viewRange; - // When invertPrice, flip the position first - const actualPosition = invertPrice ? 100 - position : position; - const tick = min + (actualPosition / 100) * (max - min); - return nearestUsableTick(Math.round(tick), tickSpacing); - }, - [viewRange, tickSpacing, invertPrice], - ); - - return { getPositionFromTick, getTickFromPosition }; -}; +export { useSmoothZoom } from '@/hooks/useSmoothZoom'; +export { useDebouncedTicks } from '@/hooks/useDebouncedTicks'; +export { useTickPositionConverter } from '@/hooks/useTickPositionConverter'; diff --git a/packages/price-slider/src/hooks/useDebouncedTicks.ts b/packages/price-slider/src/hooks/useDebouncedTicks.ts new file mode 100644 index 0000000000..2d8010a5a7 --- /dev/null +++ b/packages/price-slider/src/hooks/useDebouncedTicks.ts @@ -0,0 +1,205 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { + DEBOUNCE_DELAY, + HANDLE_LERP_MAX, + HANDLE_LERP_MIN, + LERP_CLOSE_THRESHOLD, + LERP_FAR_THRESHOLD, + MAX_TICK_SPEED, +} from '@/constants'; + +/** + * Hook for smooth tick updates with animation and debouncing + * Handles move slowly/smoothly towards target position + */ +export const useDebouncedTicks = ( + lowerTick: number | undefined, + upperTick: number | undefined, + setLowerTick: (tick: number) => void, + setUpperTick: (tick: number) => void, + isDragging: boolean, +) => { + const debounceTimerRef = useRef(null); + const animationRef = useRef(null); + + // Target ticks (where user wants to go) + const targetLowerTickRef = useRef(lowerTick); + const targetUpperTickRef = useRef(upperTick); + + // Current internal tick values (tracked via refs for animation loop) + const internalLowerRef = useRef(lowerTick); + const internalUpperRef = useRef(upperTick); + + // Internal tick state for React rendering + const [internalLowerTick, setInternalLowerTick] = useState(lowerTick); + const [internalUpperTick, setInternalUpperTick] = useState(upperTick); + + // Helper: calculate dynamic lerp factor based on distance + const getDynamicLerp = useCallback((diff: number): number => { + const absDiff = Math.abs(diff); + if (absDiff > LERP_FAR_THRESHOLD) return HANDLE_LERP_MIN; + if (absDiff < LERP_CLOSE_THRESHOLD) return HANDLE_LERP_MAX; + const t = (absDiff - LERP_CLOSE_THRESHOLD) / (LERP_FAR_THRESHOLD - LERP_CLOSE_THRESHOLD); + return HANDLE_LERP_MAX - t * (HANDLE_LERP_MAX - HANDLE_LERP_MIN); + }, []); + + // Animation function for smooth handle movement + const animateHandles = useCallback(() => { + let needsAnimation = false; + + // Update lower tick + if (internalLowerRef.current !== undefined && targetLowerTickRef.current !== undefined) { + const current = internalLowerRef.current; + const target = targetLowerTickRef.current; + const diff = target - current; + + if (Math.abs(diff) >= 1) { + needsAnimation = true; + const lerpFactor = getDynamicLerp(diff); + const lerpMovement = diff * lerpFactor; + const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED); + const newValue = current + cappedMovement; + internalLowerRef.current = newValue; + setInternalLowerTick(newValue); + } else if (current !== target) { + internalLowerRef.current = target; + setInternalLowerTick(target); + } + } + + // Update upper tick + if (internalUpperRef.current !== undefined && targetUpperTickRef.current !== undefined) { + const current = internalUpperRef.current; + const target = targetUpperTickRef.current; + const diff = target - current; + + if (Math.abs(diff) >= 1) { + needsAnimation = true; + const lerpFactor = getDynamicLerp(diff); + const lerpMovement = diff * lerpFactor; + const cappedMovement = Math.sign(lerpMovement) * Math.min(Math.abs(lerpMovement), MAX_TICK_SPEED); + const newValue = current + cappedMovement; + internalUpperRef.current = newValue; + setInternalUpperTick(newValue); + } else if (current !== target) { + internalUpperRef.current = target; + setInternalUpperTick(target); + } + } + + if (needsAnimation) { + animationRef.current = requestAnimationFrame(animateHandles); + } else { + animationRef.current = null; + } + }, [getDynamicLerp]); + + // Start animation if not already running + const startAnimation = useCallback(() => { + if (!animationRef.current) { + animationRef.current = requestAnimationFrame(animateHandles); + } + }, [animateHandles]); + + // Sync internal state with props when not dragging + useEffect(() => { + if (!isDragging) { + targetLowerTickRef.current = lowerTick; + targetUpperTickRef.current = upperTick; + internalLowerRef.current = lowerTick; + internalUpperRef.current = upperTick; + setInternalLowerTick(lowerTick); + setInternalUpperTick(upperTick); + } + }, [lowerTick, upperTick, isDragging]); + + // Smooth update functions - set target and start animation + const debouncedSetLowerTick = useCallback( + (tick: number) => { + targetLowerTickRef.current = tick; + startAnimation(); + + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + debounceTimerRef.current = setTimeout(() => { + setLowerTick(tick); + }, DEBOUNCE_DELAY); + }, + [setLowerTick, startAnimation], + ); + + const debouncedSetUpperTick = useCallback( + (tick: number) => { + targetUpperTickRef.current = tick; + startAnimation(); + + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + debounceTimerRef.current = setTimeout(() => { + setUpperTick(tick); + }, DEBOUNCE_DELAY); + }, + [setUpperTick, startAnimation], + ); + + // Flush debounced values immediately + const flushDebouncedValues = useCallback(() => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + debounceTimerRef.current = null; + } + + // Set final values from targets + const finalLower = targetLowerTickRef.current; + const finalUpper = targetUpperTickRef.current; + + if (finalLower !== undefined && finalLower !== lowerTick) { + setLowerTick(finalLower); + internalLowerRef.current = finalLower; + setInternalLowerTick(finalLower); + } + if (finalUpper !== undefined && finalUpper !== upperTick) { + setUpperTick(finalUpper); + internalUpperRef.current = finalUpper; + setInternalUpperTick(finalUpper); + } + + // Stop animation + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + animationRef.current = null; + } + }, [lowerTick, upperTick, setLowerTick, setUpperTick]); + + // Cleanup on unmount + useEffect(() => { + return () => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + }; + }, []); + + // Get target ticks (what user actually wants, not the animated value) + const getTargetTicks = useCallback(() => { + return { + lowerTick: targetLowerTickRef.current, + upperTick: targetUpperTickRef.current, + }; + }, []); + + return { + internalLowerTick, + internalUpperTick, + debouncedSetLowerTick, + debouncedSetUpperTick, + flushDebouncedValues, + getTargetTicks, + }; +}; diff --git a/packages/price-slider/src/hooks/useSmoothZoom.ts b/packages/price-slider/src/hooks/useSmoothZoom.ts new file mode 100644 index 0000000000..971995d61f --- /dev/null +++ b/packages/price-slider/src/hooks/useSmoothZoom.ts @@ -0,0 +1,82 @@ +import { Dispatch, SetStateAction, useCallback, useEffect, useRef } from 'react'; + +import { ZOOM_DURATION } from '@/constants'; +import type { ViewRange } from '@/types'; + +/** Easing function: ease-out cubic for smooth deceleration */ +const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3); + +/** + * Hook for smooth zoom animation using easing function + * Uses ease-out for natural deceleration feel + */ +export const useSmoothZoom = ( + viewRange: ViewRange | null, + setViewRange: Dispatch>, +) => { + const zoomAnimationRef = useRef(null); + const targetViewRangeRef = useRef(null); + const startViewRangeRef = useRef(null); + const startTimeRef = useRef(0); + + const animateZoom = useCallback(() => { + if (!targetViewRangeRef.current || !startViewRangeRef.current) { + zoomAnimationRef.current = null; + return; + } + + const now = performance.now(); + const elapsed = now - startTimeRef.current; + const progress = Math.min(elapsed / ZOOM_DURATION, 1); + const easedProgress = easeOutCubic(progress); + + const start = startViewRangeRef.current; + const target = targetViewRangeRef.current; + + const newMin = start.min + (target.min - start.min) * easedProgress; + const newMax = start.max + (target.max - start.max) * easedProgress; + + setViewRange({ min: newMin, max: newMax }); + + if (progress < 1) { + // Continue animation + zoomAnimationRef.current = requestAnimationFrame(animateZoom); + } else { + // Animation complete - set exact target values + setViewRange(target); + targetViewRangeRef.current = null; + startViewRangeRef.current = null; + zoomAnimationRef.current = null; + } + }, [setViewRange]); + + const startSmoothZoom = useCallback( + (targetMin: number, targetMax: number) => { + // If already animating, use current position as new start + if (zoomAnimationRef.current && viewRange) { + startViewRangeRef.current = viewRange; + } else if (viewRange) { + startViewRangeRef.current = viewRange; + } + + targetViewRangeRef.current = { min: targetMin, max: targetMax }; + startTimeRef.current = performance.now(); + + if (!zoomAnimationRef.current) { + zoomAnimationRef.current = requestAnimationFrame(animateZoom); + } + }, + [animateZoom, viewRange], + ); + + // Cleanup animation on unmount + useEffect(() => { + return () => { + if (zoomAnimationRef.current) { + cancelAnimationFrame(zoomAnimationRef.current); + } + }; + }, []); + + return { startSmoothZoom }; +}; diff --git a/packages/price-slider/src/hooks/useTickPositionConverter.ts b/packages/price-slider/src/hooks/useTickPositionConverter.ts new file mode 100644 index 0000000000..dd0bf082f6 --- /dev/null +++ b/packages/price-slider/src/hooks/useTickPositionConverter.ts @@ -0,0 +1,39 @@ +import { useCallback } from 'react'; + +import { nearestUsableTick } from '@kyber/utils/dist/uniswapv3'; + +import type { ViewRange } from '@/types'; + +/** + * Hook for converting between tick and position + * When invertPrice = true, the entire visual is flipped: + * - Lower tick (higher inverted price) appears on the RIGHT + * - Upper tick (lower inverted price) appears on the LEFT + * - Axis shows inverted prices from low (left) to high (right) + */ +export const useTickPositionConverter = (viewRange: ViewRange | null, tickSpacing: number, invertPrice?: boolean) => { + const getPositionFromTick = useCallback( + (tick: number): number => { + if (!viewRange) return 50; + const { min, max } = viewRange; + const normalPosition = ((tick - min) / (max - min)) * 100; + // When invertPrice, flip the position so higher inverted price is on the right + return invertPrice ? 100 - normalPosition : normalPosition; + }, + [viewRange, invertPrice], + ); + + const getTickFromPosition = useCallback( + (position: number): number => { + if (!viewRange) return 0; + const { min, max } = viewRange; + // When invertPrice, flip the position first + const actualPosition = invertPrice ? 100 - position : position; + const tick = min + (actualPosition / 100) * (max - min); + return nearestUsableTick(Math.round(tick), tickSpacing); + }, + [viewRange, tickSpacing, invertPrice], + ); + + return { getPositionFromTick, getTickFromPosition }; +}; From da78adbb380a865a2236245deeacb0a8b0499960 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 3 Dec 2025 13:54:35 +0700 Subject: [PATCH 70/87] fix: auto-zoom threshold --- packages/price-slider/src/components/UniswapPriceSlider.tsx | 4 ++-- packages/zap-create-widgets/src/locales/en-US.mjs | 1 + packages/zap-create-widgets/src/locales/zh-CN.mjs | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 packages/zap-create-widgets/src/locales/en-US.mjs create mode 100644 packages/zap-create-widgets/src/locales/zh-CN.mjs diff --git a/packages/price-slider/src/components/UniswapPriceSlider.tsx b/packages/price-slider/src/components/UniswapPriceSlider.tsx index f0137ac4a7..fbc9554ac4 100644 --- a/packages/price-slider/src/components/UniswapPriceSlider.tsx +++ b/packages/price-slider/src/components/UniswapPriceSlider.tsx @@ -83,8 +83,8 @@ function UniswapPriceSlider({ const handleOutsideRight = lowerPos > 105 || upperPos > 105; const handleSpan = Math.abs(upperPos - lowerPos); const idealHandleSpan = 100 - 2 * AUTO_CENTER_PADDING; - const handlesTooClose = handleSpan < idealHandleSpan * 0.4; // Much smaller than ideal - const handlesTooFar = handleSpan > idealHandleSpan * 2; // Much larger than ideal + const handlesTooClose = handleSpan < idealHandleSpan * 0.6; // Much smaller than ideal + const handlesTooFar = handleSpan > idealHandleSpan * 1.5; // Much larger than ideal // If adjustment needed, calculate new viewRange if (handleOutsideLeft || handleOutsideRight || handlesTooClose || handlesTooFar) { diff --git a/packages/zap-create-widgets/src/locales/en-US.mjs b/packages/zap-create-widgets/src/locales/en-US.mjs new file mode 100644 index 0000000000..7ce897e85e --- /dev/null +++ b/packages/zap-create-widgets/src/locales/en-US.mjs @@ -0,0 +1 @@ +/*eslint-disable*/export const messages=JSON.parse("{\"+BzDT+\":\"Please type the word <0>Confirm below to enable Degen Mode\",\"+Xo+IP\":\"Zap Impact\",\"/TDU3i\":\"No, Go back\",\"00h/ne\":\"Est. Liquidity Value\",\"26ACSh\":[\"Confirm this transaction in your wallet - Zapping \",[\"dexName\"],\" \",[\"0\"],\"/\",[\"1\"],\" \",[\"2\"],\"%\"],\"2eisoZ\":\"<0/><1>per<2/>\",\"2l3xuC\":\"Degen Mode\",\"2v2+8S\":\"Est. Gas Fee\",\"3131gY\":\"mins\",\"32tKpD\":\"Transaction Time Limit\",\"3gOdOq\":\"Select token in\",\"5etEUX\":\"Max price\",\"5wj2xK\":\"Est. Pooled\",\"6foA8n\":\"Are you sure?\",\"7VpPHA\":\"Confirm\",\"89wfR9\":\"Full Range\",\"8Tg/JR\":\"Custom\",\"9nG3ze\":[\"Fee \",[\"0\"],\"%\"],\"AANvU4\":\"Create New Pool\",\"CAyITr\":\"Turn this on to make trades with very high price impact or to set very high slippage tolerance. This can result in bad rates and loss of funds. Be cautious.\",\"CK1KXz\":\"Max\",\"CXmWpg\":\"Estimated network fee for your transaction.\",\"DMtNzZ\":\"Transaction will revert if it is pending for longer than the indicated time.\",\"EJxZzK\":\"Dynamic entry based on trading pair.\",\"EQs1sJ\":\"Min price\",\"Eq83tw\":\"Max Price\",\"FrFdej\":\"Zap Summary\",\"GN35ia\":\"Based on your price range settings, a portion of your liquidity will be automatically zapped into the pool, while the remaining amount will stay in your wallet.\",\"I+mmd/\":[\"Build LP using \",[\"0\"],\" \",[\"symbol0\"],\" and \",[\"1\"],\" \",[\"symbol1\"],\" on <0>\",[\"dexName\"],\"\"],\"IVZ5wG\":[[\"0\"],\"% Protocol Fee + \",[\"1\"],\"% Fee for \",[\"source\"]],\"J3/IOp\":\"Enter amount\",\"K/hY2w\":\"Failed to load pool\",\"KSxz3k\":\"Close & Refresh\",\"LAy9Nl\":\"+ Add Token(s)\",\"N2/A6I\":\"Zap Fee\",\"NByPet\":[\"Claim fee\",[\"0\"],[\"1\"]],\"NywJ/9\":\"Market Rate\",\"OBdohg\":\"Add Liquidity\",\"PWu4bU\":[\"Fee \",[\"fee\"],\"%\"],\"Q9e+Yn\":\"Use Suggested Slippage\",\"RGcOeX\":\"Failed to build zap route\",\"TdHXFE\":\"Enter a valid slippage percentage\",\"V94cpQ\":\"Zap-in Amount\",\"V9XJXH\":\"Native token\",\"VHOVEJ\":\"Connect wallet\",\"Vx5mrL\":\"To ensure you dont lose funds due to very high price impact, swap has been disabled for this trade. If you still wish to continue, you can turn on Degen Mode from Settings.\",\"W7uELf\":[\"Swap \",[\"0\"],\" \",[\"1\"],\" for \",[\"2\"],\" \",[\"3\"],\" via \",[\"4\"]],\"YledUl\":\"Suggestion\",\"YyaKjU\":\"Fetching Route\",\"ZI+bSz\":\"You have successfully created your Pool.\",\"ZWizC1\":\"Enter max price\",\"aRPqbz\":\"Switch network\",\"bJc+l6\":\"Set the initiate pool price\",\"dEgA5A\":\"Cancel\",\"dn6AJz\":\"Zap anyway\",\"dwrkgX\":\"Set Custom Slippage\",\"eRk8om\":\"Pool Address:\",\"fgGids\":[\"Approve \",[\"0\"]],\"hMmliu\":\"Checking Allowance\",\"j2Uisd\":\"Approving\",\"jjuKOj\":\"Min Price\",\"kNeEXg\":\"Current Price\",\"kjyqzM\":\"Degen Mode is turned on!\",\"kv9KGL\":\"Invalid price range\",\"lJNjbZ\":[[\"0\"],\"% remains unused and will be returned to your wallet. Refresh or change your amount to get updated routes.\"],\"lPAFve\":\"Unable to get the market price. Please be cautious!\",\"lbJLoa\":\"The actual Zap Routes could be adjusted with on-chain states\",\"lozjmb\":\"Your slippage is set lower than usual, increasing the risk of transaction failure.\",\"lqTDfd\":\"Slippage Tolerance\",\"mhiJjR\":\"The difference between input and estimated liquidity received (including remaining amount). Be careful with high value!\",\"o8g8uf\":\"Advanced Setting\",\"q/DsBd\":\"Est. Remaining Value\",\"qQ5mQt\":\"Remaining Amount\",\"qWaeTT\":\"You have turned on Degen Mode from settings. Trades with very high price impact can be executed\",\"rVthCd\":\"The information is intended solely for your reference at the time you are viewing. It is your responsibility to verify all information before making decisions\",\"rdUucN\":\"Preview\",\"rgbZAH\":\"Enter min price\",\"rhO8zp\":\"View position\",\"sK8oeS\":\"Estimating Gas\",\"sWlMe7\":\"Half\",\"se2Whk\":\"Fees charged for automatically zapping into a liquidity pool. You still have to pay the standard gas fees. <0>More details.\",\"t7YiQu\":\"Applied to each zap step. Setting a high slippage tolerance can help transactions succeed, but you may not get such a good price. Please use with caution!\",\"uur6fF\":\"Create Pool with Zap\",\"vpqiZ6\":\"Your slippage is set higher than usual, which may cause unexpected losses.\",\"wFXyWj\":\"Invalid input amount\",\"wUh/Ia\":\"Use Market Rate\",\"x6GJ4a\":\"Max Slippage\",\"ykSios\":\"Enter a smaller slippage percentage\",\"yz7wBu\":\"Close\",\"znqB4T\":\"Insufficient balance\"}"); \ No newline at end of file diff --git a/packages/zap-create-widgets/src/locales/zh-CN.mjs b/packages/zap-create-widgets/src/locales/zh-CN.mjs new file mode 100644 index 0000000000..ed565182c1 --- /dev/null +++ b/packages/zap-create-widgets/src/locales/zh-CN.mjs @@ -0,0 +1 @@ +/*eslint-disable*/export const messages=JSON.parse("{\"+BzDT+\":\"请在下方输入 <0>Confirm 以启用 Degen 模式\",\"+Xo+IP\":\"Zap 影响\",\"/TDU3i\":\"不,返回\",\"00h/ne\":\"预计流动性价值\",\"26ACSh\":[\"在钱包中确认此笔交易 - 正在对 \",[\"dexName\"],\" \",[\"0\"],\"/\",[\"1\"],\" \",[\"2\"],\"% 进行 Zap\"],\"2eisoZ\":\"<0/><1>每<2/>\",\"2l3xuC\":\"Degen 模式\",\"2v2+8S\":\"预计 Gas 费用\",\"3131gY\":\"分钟\",\"32tKpD\":\"交易时间限制\",\"3gOdOq\":\"选择输入代币\",\"5etEUX\":\"最高价格\",\"5wj2xK\":\"预计注入\",\"6foA8n\":\"确定吗?\",\"7VpPHA\":\"确认\",\"89wfR9\":\"全范围\",\"8Tg/JR\":\"自定义\",\"9nG3ze\":[\"手续费 \",[\"0\"],\"%\"],\"AANvU4\":\"创建新池子\",\"CAyITr\":\"开启此选项可在极高价格影响或滑点下完成交易,可能导致糟糕的价格和资金损失,请谨慎操作。\",\"CK1KXz\":\"最大\",\"CXmWpg\":\"你这笔交易的预计网络费用。\",\"DMtNzZ\":\"如果交易等待时间超过设定时限,将会回滚。\",\"EJxZzK\":\"根据交易对动态设定。\",\"EQs1sJ\":\"最低价格\",\"Eq83tw\":\"最高价格\",\"FrFdej\":\"Zap 摘要\",\"GN35ia\":\"根据你设置的价格区间,部分流动性会自动 Zap 入池,其余金额将保留在你的钱包中。\",\"I+mmd/\":[\"在 <0>\",[\"dexName\"],\" 上使用 \",[\"0\"],\" \",[\"symbol0\"],\" 和 \",[\"1\"],\" \",[\"symbol1\"],\" 构建 LP\"],\"IVZ5wG\":[[\"0\"],\"% 协议费用 + \",[\"1\"],\"% \",[\"source\"],\" 费用\"],\"J3/IOp\":\"输入金额\",\"K/hY2w\":\"加载池失败\",\"KSxz3k\":\"关闭并刷新\",\"LAy9Nl\":\"+ 添加代币\",\"N2/A6I\":\"Zap 费用\",\"NByPet\":[\"领取手续费\",[\"0\"],[\"1\"]],\"NywJ/9\":\"市场价格\",\"OBdohg\":\"添加流动性\",\"PWu4bU\":[\"手续费 \",[\"fee\"],\"%\"],\"Q9e+Yn\":\"使用建议滑点\",\"RGcOeX\":\"构建 Zap 路线失败\",\"TdHXFE\":\"请输入有效的滑点百分比\",\"V94cpQ\":\"Zap 金额\",\"V9XJXH\":\"原生代币\",\"VHOVEJ\":\"连接钱包\",\"Vx5mrL\":\"为确保你不会因为极高价格影响而损失资金,此笔交易的兑换已被禁用。如仍希望继续,可在设置中开启 Degen 模式。\",\"W7uELf\":[\"通过 \",[\"4\"],\" 将 \",[\"0\"],\" \",[\"1\"],\" 兑换为 \",[\"2\"],\" \",[\"3\"]],\"YledUl\":\"建议\",\"YyaKjU\":\"获取路线\",\"ZI+bSz\":\"你已成功创建池子。\",\"ZWizC1\":\"输入最高价格\",\"aRPqbz\":\"切换网络\",\"bJc+l6\":\"设置池子初始价格\",\"dEgA5A\":\"取消\",\"dn6AJz\":\"仍然 Zap\",\"dwrkgX\":\"设置自定义滑点\",\"eRk8om\":\"池地址:\",\"fgGids\":[\"批准 \",[\"0\"]],\"hMmliu\":\"检查授权\",\"j2Uisd\":\"正在批准\",\"jjuKOj\":\"最低价格\",\"kNeEXg\":\"当前价格\",\"kjyqzM\":\"Degen 模式已开启!\",\"kv9KGL\":\"无效的价格区间\",\"lJNjbZ\":[[\"0\"],\"% 将不会使用并会退回你的钱包。刷新或调整金额以获取最新路线。\"],\"lPAFve\":\"无法获取市场价格,请务必谨慎!\",\"lbJLoa\":\"实际 Zap 路线可能会根据链上状态调整\",\"lozjmb\":\"你的滑点设置低于常规,增加了交易失败的风险。\",\"lqTDfd\":\"滑点容差\",\"mhiJjR\":\"输入金额与预计获得的流动性(含剩余金额)之间的差额。数值过高时请谨慎!\",\"o8g8uf\":\"高级设置\",\"q/DsBd\":\"预计剩余价值\",\"qQ5mQt\":\"剩余金额\",\"qWaeTT\":\"你已在设置中开启 Degen 模式,可执行高价格影响的交易\",\"rVthCd\":\"此信息仅供你查看时参考。做出决策前请自行核实所有信息。\",\"rdUucN\":\"预览\",\"rgbZAH\":\"输入最低价格\",\"rhO8zp\":\"查看头寸\",\"sK8oeS\":\"估算 Gas\",\"sWlMe7\":\"一半\",\"se2Whk\":\"自动 Zap 进池所收取的费用,你仍需支付标准 Gas 费。<0>了解更多。\",\"t7YiQu\":\"应用于每一步 Zap。较高的滑点容差有助于交易成功,但可能带来较差价格,请谨慎使用!\",\"uur6fF\":\"使用 Zap 创建池子\",\"vpqiZ6\":\"你的滑点设置高于常规,可能导致意外损失。\",\"wFXyWj\":\"无效的输入金额\",\"wUh/Ia\":\"使用市场价格\",\"x6GJ4a\":\"最大滑点\",\"ykSios\":\"请输入更低的滑点百分比\",\"yz7wBu\":\"关闭\",\"znqB4T\":\"余额不足\"}"); \ No newline at end of file From 317605820a42b6be0acf9b0225feec71218561e0 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 3 Dec 2025 13:56:08 +0700 Subject: [PATCH 71/87] fix: remove margin top --- packages/price-slider/src/components/UniswapPriceSlider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/price-slider/src/components/UniswapPriceSlider.tsx b/packages/price-slider/src/components/UniswapPriceSlider.tsx index fbc9554ac4..505b980deb 100644 --- a/packages/price-slider/src/components/UniswapPriceSlider.tsx +++ b/packages/price-slider/src/components/UniswapPriceSlider.tsx @@ -328,7 +328,7 @@ function UniswapPriceSlider({ return (
{/* Slider Wrapper */} -
+
{/* Track */}
From ea75d4ec448ba4adda6b1130fe9f5183eec8d148 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 3 Dec 2025 13:56:25 +0700 Subject: [PATCH 72/87] fix: remove stuff --- packages/zap-create-widgets/src/locales/en-US.mjs | 1 - packages/zap-create-widgets/src/locales/zh-CN.mjs | 1 - 2 files changed, 2 deletions(-) delete mode 100644 packages/zap-create-widgets/src/locales/en-US.mjs delete mode 100644 packages/zap-create-widgets/src/locales/zh-CN.mjs diff --git a/packages/zap-create-widgets/src/locales/en-US.mjs b/packages/zap-create-widgets/src/locales/en-US.mjs deleted file mode 100644 index 7ce897e85e..0000000000 --- a/packages/zap-create-widgets/src/locales/en-US.mjs +++ /dev/null @@ -1 +0,0 @@ -/*eslint-disable*/export const messages=JSON.parse("{\"+BzDT+\":\"Please type the word <0>Confirm below to enable Degen Mode\",\"+Xo+IP\":\"Zap Impact\",\"/TDU3i\":\"No, Go back\",\"00h/ne\":\"Est. Liquidity Value\",\"26ACSh\":[\"Confirm this transaction in your wallet - Zapping \",[\"dexName\"],\" \",[\"0\"],\"/\",[\"1\"],\" \",[\"2\"],\"%\"],\"2eisoZ\":\"<0/><1>per<2/>\",\"2l3xuC\":\"Degen Mode\",\"2v2+8S\":\"Est. Gas Fee\",\"3131gY\":\"mins\",\"32tKpD\":\"Transaction Time Limit\",\"3gOdOq\":\"Select token in\",\"5etEUX\":\"Max price\",\"5wj2xK\":\"Est. Pooled\",\"6foA8n\":\"Are you sure?\",\"7VpPHA\":\"Confirm\",\"89wfR9\":\"Full Range\",\"8Tg/JR\":\"Custom\",\"9nG3ze\":[\"Fee \",[\"0\"],\"%\"],\"AANvU4\":\"Create New Pool\",\"CAyITr\":\"Turn this on to make trades with very high price impact or to set very high slippage tolerance. This can result in bad rates and loss of funds. Be cautious.\",\"CK1KXz\":\"Max\",\"CXmWpg\":\"Estimated network fee for your transaction.\",\"DMtNzZ\":\"Transaction will revert if it is pending for longer than the indicated time.\",\"EJxZzK\":\"Dynamic entry based on trading pair.\",\"EQs1sJ\":\"Min price\",\"Eq83tw\":\"Max Price\",\"FrFdej\":\"Zap Summary\",\"GN35ia\":\"Based on your price range settings, a portion of your liquidity will be automatically zapped into the pool, while the remaining amount will stay in your wallet.\",\"I+mmd/\":[\"Build LP using \",[\"0\"],\" \",[\"symbol0\"],\" and \",[\"1\"],\" \",[\"symbol1\"],\" on <0>\",[\"dexName\"],\"\"],\"IVZ5wG\":[[\"0\"],\"% Protocol Fee + \",[\"1\"],\"% Fee for \",[\"source\"]],\"J3/IOp\":\"Enter amount\",\"K/hY2w\":\"Failed to load pool\",\"KSxz3k\":\"Close & Refresh\",\"LAy9Nl\":\"+ Add Token(s)\",\"N2/A6I\":\"Zap Fee\",\"NByPet\":[\"Claim fee\",[\"0\"],[\"1\"]],\"NywJ/9\":\"Market Rate\",\"OBdohg\":\"Add Liquidity\",\"PWu4bU\":[\"Fee \",[\"fee\"],\"%\"],\"Q9e+Yn\":\"Use Suggested Slippage\",\"RGcOeX\":\"Failed to build zap route\",\"TdHXFE\":\"Enter a valid slippage percentage\",\"V94cpQ\":\"Zap-in Amount\",\"V9XJXH\":\"Native token\",\"VHOVEJ\":\"Connect wallet\",\"Vx5mrL\":\"To ensure you dont lose funds due to very high price impact, swap has been disabled for this trade. If you still wish to continue, you can turn on Degen Mode from Settings.\",\"W7uELf\":[\"Swap \",[\"0\"],\" \",[\"1\"],\" for \",[\"2\"],\" \",[\"3\"],\" via \",[\"4\"]],\"YledUl\":\"Suggestion\",\"YyaKjU\":\"Fetching Route\",\"ZI+bSz\":\"You have successfully created your Pool.\",\"ZWizC1\":\"Enter max price\",\"aRPqbz\":\"Switch network\",\"bJc+l6\":\"Set the initiate pool price\",\"dEgA5A\":\"Cancel\",\"dn6AJz\":\"Zap anyway\",\"dwrkgX\":\"Set Custom Slippage\",\"eRk8om\":\"Pool Address:\",\"fgGids\":[\"Approve \",[\"0\"]],\"hMmliu\":\"Checking Allowance\",\"j2Uisd\":\"Approving\",\"jjuKOj\":\"Min Price\",\"kNeEXg\":\"Current Price\",\"kjyqzM\":\"Degen Mode is turned on!\",\"kv9KGL\":\"Invalid price range\",\"lJNjbZ\":[[\"0\"],\"% remains unused and will be returned to your wallet. Refresh or change your amount to get updated routes.\"],\"lPAFve\":\"Unable to get the market price. Please be cautious!\",\"lbJLoa\":\"The actual Zap Routes could be adjusted with on-chain states\",\"lozjmb\":\"Your slippage is set lower than usual, increasing the risk of transaction failure.\",\"lqTDfd\":\"Slippage Tolerance\",\"mhiJjR\":\"The difference between input and estimated liquidity received (including remaining amount). Be careful with high value!\",\"o8g8uf\":\"Advanced Setting\",\"q/DsBd\":\"Est. Remaining Value\",\"qQ5mQt\":\"Remaining Amount\",\"qWaeTT\":\"You have turned on Degen Mode from settings. Trades with very high price impact can be executed\",\"rVthCd\":\"The information is intended solely for your reference at the time you are viewing. It is your responsibility to verify all information before making decisions\",\"rdUucN\":\"Preview\",\"rgbZAH\":\"Enter min price\",\"rhO8zp\":\"View position\",\"sK8oeS\":\"Estimating Gas\",\"sWlMe7\":\"Half\",\"se2Whk\":\"Fees charged for automatically zapping into a liquidity pool. You still have to pay the standard gas fees. <0>More details.\",\"t7YiQu\":\"Applied to each zap step. Setting a high slippage tolerance can help transactions succeed, but you may not get such a good price. Please use with caution!\",\"uur6fF\":\"Create Pool with Zap\",\"vpqiZ6\":\"Your slippage is set higher than usual, which may cause unexpected losses.\",\"wFXyWj\":\"Invalid input amount\",\"wUh/Ia\":\"Use Market Rate\",\"x6GJ4a\":\"Max Slippage\",\"ykSios\":\"Enter a smaller slippage percentage\",\"yz7wBu\":\"Close\",\"znqB4T\":\"Insufficient balance\"}"); \ No newline at end of file diff --git a/packages/zap-create-widgets/src/locales/zh-CN.mjs b/packages/zap-create-widgets/src/locales/zh-CN.mjs deleted file mode 100644 index ed565182c1..0000000000 --- a/packages/zap-create-widgets/src/locales/zh-CN.mjs +++ /dev/null @@ -1 +0,0 @@ -/*eslint-disable*/export const messages=JSON.parse("{\"+BzDT+\":\"请在下方输入 <0>Confirm 以启用 Degen 模式\",\"+Xo+IP\":\"Zap 影响\",\"/TDU3i\":\"不,返回\",\"00h/ne\":\"预计流动性价值\",\"26ACSh\":[\"在钱包中确认此笔交易 - 正在对 \",[\"dexName\"],\" \",[\"0\"],\"/\",[\"1\"],\" \",[\"2\"],\"% 进行 Zap\"],\"2eisoZ\":\"<0/><1>每<2/>\",\"2l3xuC\":\"Degen 模式\",\"2v2+8S\":\"预计 Gas 费用\",\"3131gY\":\"分钟\",\"32tKpD\":\"交易时间限制\",\"3gOdOq\":\"选择输入代币\",\"5etEUX\":\"最高价格\",\"5wj2xK\":\"预计注入\",\"6foA8n\":\"确定吗?\",\"7VpPHA\":\"确认\",\"89wfR9\":\"全范围\",\"8Tg/JR\":\"自定义\",\"9nG3ze\":[\"手续费 \",[\"0\"],\"%\"],\"AANvU4\":\"创建新池子\",\"CAyITr\":\"开启此选项可在极高价格影响或滑点下完成交易,可能导致糟糕的价格和资金损失,请谨慎操作。\",\"CK1KXz\":\"最大\",\"CXmWpg\":\"你这笔交易的预计网络费用。\",\"DMtNzZ\":\"如果交易等待时间超过设定时限,将会回滚。\",\"EJxZzK\":\"根据交易对动态设定。\",\"EQs1sJ\":\"最低价格\",\"Eq83tw\":\"最高价格\",\"FrFdej\":\"Zap 摘要\",\"GN35ia\":\"根据你设置的价格区间,部分流动性会自动 Zap 入池,其余金额将保留在你的钱包中。\",\"I+mmd/\":[\"在 <0>\",[\"dexName\"],\" 上使用 \",[\"0\"],\" \",[\"symbol0\"],\" 和 \",[\"1\"],\" \",[\"symbol1\"],\" 构建 LP\"],\"IVZ5wG\":[[\"0\"],\"% 协议费用 + \",[\"1\"],\"% \",[\"source\"],\" 费用\"],\"J3/IOp\":\"输入金额\",\"K/hY2w\":\"加载池失败\",\"KSxz3k\":\"关闭并刷新\",\"LAy9Nl\":\"+ 添加代币\",\"N2/A6I\":\"Zap 费用\",\"NByPet\":[\"领取手续费\",[\"0\"],[\"1\"]],\"NywJ/9\":\"市场价格\",\"OBdohg\":\"添加流动性\",\"PWu4bU\":[\"手续费 \",[\"fee\"],\"%\"],\"Q9e+Yn\":\"使用建议滑点\",\"RGcOeX\":\"构建 Zap 路线失败\",\"TdHXFE\":\"请输入有效的滑点百分比\",\"V94cpQ\":\"Zap 金额\",\"V9XJXH\":\"原生代币\",\"VHOVEJ\":\"连接钱包\",\"Vx5mrL\":\"为确保你不会因为极高价格影响而损失资金,此笔交易的兑换已被禁用。如仍希望继续,可在设置中开启 Degen 模式。\",\"W7uELf\":[\"通过 \",[\"4\"],\" 将 \",[\"0\"],\" \",[\"1\"],\" 兑换为 \",[\"2\"],\" \",[\"3\"]],\"YledUl\":\"建议\",\"YyaKjU\":\"获取路线\",\"ZI+bSz\":\"你已成功创建池子。\",\"ZWizC1\":\"输入最高价格\",\"aRPqbz\":\"切换网络\",\"bJc+l6\":\"设置池子初始价格\",\"dEgA5A\":\"取消\",\"dn6AJz\":\"仍然 Zap\",\"dwrkgX\":\"设置自定义滑点\",\"eRk8om\":\"池地址:\",\"fgGids\":[\"批准 \",[\"0\"]],\"hMmliu\":\"检查授权\",\"j2Uisd\":\"正在批准\",\"jjuKOj\":\"最低价格\",\"kNeEXg\":\"当前价格\",\"kjyqzM\":\"Degen 模式已开启!\",\"kv9KGL\":\"无效的价格区间\",\"lJNjbZ\":[[\"0\"],\"% 将不会使用并会退回你的钱包。刷新或调整金额以获取最新路线。\"],\"lPAFve\":\"无法获取市场价格,请务必谨慎!\",\"lbJLoa\":\"实际 Zap 路线可能会根据链上状态调整\",\"lozjmb\":\"你的滑点设置低于常规,增加了交易失败的风险。\",\"lqTDfd\":\"滑点容差\",\"mhiJjR\":\"输入金额与预计获得的流动性(含剩余金额)之间的差额。数值过高时请谨慎!\",\"o8g8uf\":\"高级设置\",\"q/DsBd\":\"预计剩余价值\",\"qQ5mQt\":\"剩余金额\",\"qWaeTT\":\"你已在设置中开启 Degen 模式,可执行高价格影响的交易\",\"rVthCd\":\"此信息仅供你查看时参考。做出决策前请自行核实所有信息。\",\"rdUucN\":\"预览\",\"rgbZAH\":\"输入最低价格\",\"rhO8zp\":\"查看头寸\",\"sK8oeS\":\"估算 Gas\",\"sWlMe7\":\"一半\",\"se2Whk\":\"自动 Zap 进池所收取的费用,你仍需支付标准 Gas 费。<0>了解更多。\",\"t7YiQu\":\"应用于每一步 Zap。较高的滑点容差有助于交易成功,但可能带来较差价格,请谨慎使用!\",\"uur6fF\":\"使用 Zap 创建池子\",\"vpqiZ6\":\"你的滑点设置高于常规,可能导致意外损失。\",\"wFXyWj\":\"无效的输入金额\",\"wUh/Ia\":\"使用市场价格\",\"x6GJ4a\":\"最大滑点\",\"ykSios\":\"请输入更低的滑点百分比\",\"yz7wBu\":\"关闭\",\"znqB4T\":\"余额不足\"}"); \ No newline at end of file From ab18e2b0ae09fb3ad9014a59522a4a8892519679 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 3 Dec 2025 14:36:35 +0700 Subject: [PATCH 73/87] fix: remove className --- packages/price-slider/src/components/UniswapPriceSlider.tsx | 3 +-- packages/price-slider/src/types/index.ts | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/price-slider/src/components/UniswapPriceSlider.tsx b/packages/price-slider/src/components/UniswapPriceSlider.tsx index 505b980deb..e5e3fa8dcf 100644 --- a/packages/price-slider/src/components/UniswapPriceSlider.tsx +++ b/packages/price-slider/src/components/UniswapPriceSlider.tsx @@ -16,7 +16,6 @@ function UniswapPriceSlider({ upperTick, setLowerTick, setUpperTick, - className, }: UniswapPriceSliderProps) { const { tickSpacing, token0Decimals, token1Decimals, currentTick } = pool; @@ -326,7 +325,7 @@ function UniswapPriceSlider({ const rightHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'upper' : 'lower'; return ( -
+
{/* Slider Wrapper */}
{/* Track */} diff --git a/packages/price-slider/src/types/index.ts b/packages/price-slider/src/types/index.ts index ee183d8fe1..b8064e6bc5 100644 --- a/packages/price-slider/src/types/index.ts +++ b/packages/price-slider/src/types/index.ts @@ -26,8 +26,6 @@ export interface UniswapPriceSliderProps { upperTick?: number; setLowerTick: (tick: number) => void; setUpperTick: (tick: number) => void; - /** Optional class name for custom styling */ - className?: string; } /** From 70112cfad9c86c80831ccff1752d6793592362ce Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 3 Dec 2025 15:06:25 +0700 Subject: [PATCH 74/87] fix: styles --- packages/price-slider/src/components/Skeleton.tsx | 2 +- packages/price-slider/src/components/UniswapPriceSlider.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/price-slider/src/components/Skeleton.tsx b/packages/price-slider/src/components/Skeleton.tsx index d842256fd6..fea9470ef4 100644 --- a/packages/price-slider/src/components/Skeleton.tsx +++ b/packages/price-slider/src/components/Skeleton.tsx @@ -10,7 +10,7 @@ import { SKELETON_AXIS_POSITIONS } from '@/constants'; */ function PriceSliderSkeleton() { return ( -
+
{/* Slider Area */}
{/* Track */} diff --git a/packages/price-slider/src/components/UniswapPriceSlider.tsx b/packages/price-slider/src/components/UniswapPriceSlider.tsx index e5e3fa8dcf..fe8fc372f9 100644 --- a/packages/price-slider/src/components/UniswapPriceSlider.tsx +++ b/packages/price-slider/src/components/UniswapPriceSlider.tsx @@ -325,9 +325,9 @@ function UniswapPriceSlider({ const rightHandleType: 'lower' | 'upper' = isLowerOnLeft ? 'upper' : 'lower'; return ( -
+
{/* Slider Wrapper */} -
+
{/* Track */}
From 7e3177b198ba658e61ce01e487f80608c1c52943 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 3 Dec 2025 15:09:00 +0700 Subject: [PATCH 75/87] fix: styles --- packages/price-slider/src/components/Skeleton.tsx | 2 +- packages/price-slider/src/components/UniswapPriceSlider.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/price-slider/src/components/Skeleton.tsx b/packages/price-slider/src/components/Skeleton.tsx index fea9470ef4..4a2ad9b3e4 100644 --- a/packages/price-slider/src/components/Skeleton.tsx +++ b/packages/price-slider/src/components/Skeleton.tsx @@ -12,7 +12,7 @@ function PriceSliderSkeleton() { return (
{/* Slider Area */} -
+
{/* Track */} diff --git a/packages/price-slider/src/components/UniswapPriceSlider.tsx b/packages/price-slider/src/components/UniswapPriceSlider.tsx index fe8fc372f9..fbf5dda14d 100644 --- a/packages/price-slider/src/components/UniswapPriceSlider.tsx +++ b/packages/price-slider/src/components/UniswapPriceSlider.tsx @@ -327,7 +327,7 @@ function UniswapPriceSlider({ return (
{/* Slider Wrapper */} -
+
{/* Track */}
From 80c6fdffb6480f06453b2926f72544e3408292a7 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 3 Dec 2025 23:04:03 +0700 Subject: [PATCH 76/87] fix: mt --- packages/price-slider/src/components/Skeleton.tsx | 2 +- packages/price-slider/src/components/UniswapPriceSlider.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/price-slider/src/components/Skeleton.tsx b/packages/price-slider/src/components/Skeleton.tsx index 4a2ad9b3e4..06d56f28af 100644 --- a/packages/price-slider/src/components/Skeleton.tsx +++ b/packages/price-slider/src/components/Skeleton.tsx @@ -12,7 +12,7 @@ function PriceSliderSkeleton() { return (
{/* Slider Area */} -
+
{/* Track */} diff --git a/packages/price-slider/src/components/UniswapPriceSlider.tsx b/packages/price-slider/src/components/UniswapPriceSlider.tsx index fbf5dda14d..9ff37d1064 100644 --- a/packages/price-slider/src/components/UniswapPriceSlider.tsx +++ b/packages/price-slider/src/components/UniswapPriceSlider.tsx @@ -327,7 +327,7 @@ function UniswapPriceSlider({ return (
{/* Slider Wrapper */} -
+
{/* Track */}
From f00c4fe9ee8ea11962806404b54b72729f65754c Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 3 Dec 2025 23:06:51 +0700 Subject: [PATCH 77/87] fix: mt --- .../src/pages/Earns/components/SmartExit/Metrics.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx index 4abb293f76..f1e9f28213 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx @@ -374,7 +374,7 @@ const PriceInput = ({ Exit when the pool price is between - + Date: Thu, 4 Dec 2025 11:45:52 +0700 Subject: [PATCH 78/87] feat: expected amount --- .../components/SmartExit/Confirmation.tsx | 91 ++++++++ .../SmartExit/calculateExpectedAmounts.ts | 205 ++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/calculateExpectedAmounts.ts diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx index b0de40f0af..80c3bde0e2 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx @@ -1,5 +1,6 @@ import { Trans, t } from '@lingui/macro' import dayjs from 'dayjs' +import { useMemo } from 'react' import { X } from 'react-feather' import { useNavigate } from 'react-router' import { Box, Flex, Text } from 'rebass' @@ -14,6 +15,7 @@ import { PermitNftState, usePermitNft } from 'hooks/usePermitNft' import useTheme from 'hooks/useTheme' import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' import { Badge, ImageContainer } from 'pages/Earns/UserPositions/styles' +import { calculateExpectedAmounts } from 'pages/Earns/components/SmartExit/calculateExpectedAmounts' import { useSmartExit } from 'pages/Earns/components/SmartExit/useSmartExit' import { SMART_EXIT_ADDRESS } from 'pages/Earns/constants' import { @@ -25,6 +27,7 @@ import { SelectedMetric, TimeCondition, } from 'pages/Earns/types' +import { formatDisplayNumber } from 'utils/numbers' export const Confirmation = ({ selectedMetrics, @@ -73,6 +76,41 @@ export const Confirmation = ({ const displayTime = dayjs(deadline * 1000).format('DD/MM/YYYY HH:mm:ss') + // Calculate expected amounts for pool price conditions + const expectedAmounts = useMemo(() => { + // Check if any metric is pool price + const poolPriceMetrics = selectedMetrics.filter(m => m.metric === Metric.PoolPrice) + if (poolPriceMetrics.length === 0) return null + + // If there are multiple pool price conditions, merge them + let mergedCondition: PriceCondition | null = null + + if (poolPriceMetrics.length === 1) { + mergedCondition = poolPriceMetrics[0].condition as PriceCondition + } else if (poolPriceMetrics.length === 2) { + // When using AND: take the intersection (more restrictive range) + // When using OR: take the union (wider range) + const condition1 = poolPriceMetrics[0].condition as PriceCondition + const condition2 = poolPriceMetrics[1].condition as PriceCondition + + if (conditionType === ConditionType.And) { + // Intersection: higher min, lower max + mergedCondition = { + gte: Math.max(parseFloat(condition1.gte), parseFloat(condition2.gte)).toString(), + lte: Math.min(parseFloat(condition1.lte), parseFloat(condition2.lte)).toString(), + } + } else { + // Union: lower min, higher max + mergedCondition = { + gte: Math.min(parseFloat(condition1.gte), parseFloat(condition2.gte)).toString(), + lte: Math.max(parseFloat(condition1.lte), parseFloat(condition2.lte)).toString(), + } + } + } + + return mergedCondition ? calculateExpectedAmounts(position, mergedCondition) : null + }, [position, selectedMetrics, conditionType]) + if (isSuccess) return ( <> @@ -200,6 +238,59 @@ export const Confirmation = ({ )} + {expectedAmounts && ( + + + Estimated balance when exit + + + + + + Min + + + + + {formatDisplayNumber(expectedAmounts.minAmount0, { significantDigits: 6 })} {position.token0.symbol} + + + + + + {formatDisplayNumber(expectedAmounts.minAmount1, { significantDigits: 6 })} {position.token1.symbol} + + + + + + + Max + + + + + {formatDisplayNumber(expectedAmounts.maxAmount0, { significantDigits: 6 })} {position.token0.symbol} + + + + + + {formatDisplayNumber(expectedAmounts.maxAmount1, { significantDigits: 6 })} {position.token1.symbol} + + + + + + )} + Platform Fee diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/calculateExpectedAmounts.ts b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/calculateExpectedAmounts.ts new file mode 100644 index 0000000000..1598c13c1f --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/calculateExpectedAmounts.ts @@ -0,0 +1,205 @@ +import { ParsedPosition, PriceCondition } from 'pages/Earns/types' + +interface ExpectedAmounts { + minAmount0: number + maxAmount0: number + minAmount1: number + maxAmount1: number +} + +/** + * Calculate expected token amounts when removing liquidity at specific price conditions + * Based on Uniswap V3 formulas: + * - If price < priceLower: all liquidity is in token0 + * - If price > priceUpper: all liquidity is in token1 + * - If priceLower <= price <= priceUpper: liquidity is split between both tokens + * + * Correct Uniswap V3 formulas: + * When in range: + * - amount0 = liquidity * (1/sqrt(price) - 1/sqrt(priceUpper)) + * - amount1 = liquidity * (sqrt(price) - sqrt(priceLower)) + * When price < priceLower (all in token0): + * - amount0 = liquidity * (1/sqrt(priceLower) - 1/sqrt(priceUpper)) + * - amount1 = 0 + * When price > priceUpper (all in token1): + * - amount0 = 0 + * - amount1 = liquidity * (sqrt(priceUpper) - sqrt(priceLower)) + */ +export function calculateExpectedAmounts( + position: ParsedPosition, + priceCondition?: PriceCondition, +): ExpectedAmounts | null { + if (!priceCondition?.gte || !priceCondition?.lte) { + return null + } + + const minExitPrice = parseFloat(priceCondition.gte) + const maxExitPrice = parseFloat(priceCondition.lte) + + if (!minExitPrice || !maxExitPrice || minExitPrice > maxExitPrice || minExitPrice <= 0 || maxExitPrice <= 0) { + return null + } + + // Get position's price range + const positionPriceLower = position.priceRange.min + const positionPriceUpper = position.priceRange.max + const currentPrice = position.priceRange.current + + // Validate price range + if (positionPriceLower <= 0 || positionPriceUpper <= 0 || currentPrice <= 0) { + return null + } + + if (positionPriceLower >= positionPriceUpper) { + return null + } + + // Get current amounts in position (including fees) + const currentAmount0 = position.token0.totalProvide + position.token0.unclaimedAmount + const currentAmount1 = position.token1.totalProvide + position.token1.unclaimedAmount + + // If position is out of range, handle special cases + const isOutOfRange = currentPrice < positionPriceLower || currentPrice > positionPriceUpper + + // Calculate liquidity from current amounts and price + // Using the relationship: L = sqrt(amount0 * amount1 * P) + const liquidity = estimateLiquidity( + currentAmount0, + currentAmount1, + currentPrice, + positionPriceLower, + positionPriceUpper, + isOutOfRange, + ) + + // Calculate expected amounts at min exit price + const { amount0: minPriceAmount0, amount1: minPriceAmount1 } = calculateAmountsAtPrice( + minExitPrice, + positionPriceLower, + positionPriceUpper, + liquidity, + ) + + // Calculate expected amounts at max exit price + const { amount0: maxPriceAmount0, amount1: maxPriceAmount1 } = calculateAmountsAtPrice( + maxExitPrice, + positionPriceLower, + positionPriceUpper, + liquidity, + ) + + // Return min/max for each token across both price points + return { + minAmount0: Math.min(minPriceAmount0, maxPriceAmount0), + maxAmount0: Math.max(minPriceAmount0, maxPriceAmount0), + minAmount1: Math.min(minPriceAmount1, maxPriceAmount1), + maxAmount1: Math.max(minPriceAmount1, maxPriceAmount1), + } +} + +/** + * Estimate liquidity from current amounts and prices + * Using correct Uniswap V3 formulas: + * L = amount0 / (1/sqrt(P) - 1/sqrt(Pb)) where P is current price, Pb is upper bound + * L = amount1 / (sqrt(P) - sqrt(Pa)) where Pa is lower bound + */ +function estimateLiquidity( + amount0: number, + amount1: number, + currentPrice: number, + priceLower: number, + priceUpper: number, + isOutOfRange: boolean, +): number { + const sqrtPrice = Math.sqrt(currentPrice) + const sqrtPriceLower = Math.sqrt(priceLower) + const sqrtPriceUpper = Math.sqrt(priceUpper) + + if (isOutOfRange) { + if (currentPrice < priceLower) { + // All in token0: L = amount0 / (1/sqrt(Pa) - 1/sqrt(Pb)) + return amount0 / (1 / sqrtPriceLower - 1 / sqrtPriceUpper) + } else { + // All in token1: L = amount1 / (sqrt(Pb) - sqrt(Pa)) + return amount1 / (sqrtPriceUpper - sqrtPriceLower) + } + } + + // In range - calculate from both and take average for stability + let L0 = 0 + let L1 = 0 + + if (amount0 > 0) { + // L = amount0 / (1/sqrt(P) - 1/sqrt(Pb)) + const denominator0 = 1 / sqrtPrice - 1 / sqrtPriceUpper + if (Math.abs(denominator0) > 0.000001) { + // Avoid division by very small number + L0 = amount0 / denominator0 + } + } + + if (amount1 > 0) { + // L = amount1 / (sqrt(P) - sqrt(Pa)) + const denominator1 = sqrtPrice - sqrtPriceLower + if (Math.abs(denominator1) > 0.000001) { + // Avoid division by very small number + L1 = amount1 / denominator1 + } + } + + // Return average if both are calculated, otherwise return the non-zero one + if (L0 > 0 && L1 > 0) { + return (L0 + L1) / 2 + } + return L0 > 0 ? L0 : L1 +} + +/** + * Calculate token amounts at a specific price using correct Uniswap V3 formulas + */ +function calculateAmountsAtPrice( + price: number, + priceLower: number, + priceUpper: number, + liquidity: number, +): { amount0: number; amount1: number } { + // If no liquidity, return 0 + if (liquidity === 0) { + return { amount0: 0, amount1: 0 } + } + + const sqrtPrice = Math.sqrt(price) + const sqrtPriceLower = Math.sqrt(priceLower) + const sqrtPriceUpper = Math.sqrt(priceUpper) + + // Case 1: Price below range - all in token0 + if (price <= priceLower) { + // amount0 = L * (1/sqrt(Pa) - 1/sqrt(Pb)) + const amount0 = liquidity * (1 / sqrtPriceLower - 1 / sqrtPriceUpper) + return { + amount0, + amount1: 0, + } + } + + // Case 2: Price above range - all in token1 + if (price >= priceUpper) { + // amount1 = L * (sqrt(Pb) - sqrt(Pa)) + const amount1 = liquidity * (sqrtPriceUpper - sqrtPriceLower) + return { + amount0: 0, + amount1, + } + } + + // Case 3: Price in range - split between both tokens + // amount0 = L * (1/sqrt(P) - 1/sqrt(Pb)) + // amount1 = L * (sqrt(P) - sqrt(Pa)) + const amount0 = liquidity * (1 / sqrtPrice - 1 / sqrtPriceUpper) + const amount1 = liquidity * (sqrtPrice - sqrtPriceLower) + + return { + amount0, + amount1, + } +} From 9fa74fd25894eacf724abb4e6cfd3a0b4bd3a17c Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 4 Dec 2025 14:27:27 +0700 Subject: [PATCH 79/87] feat: refactor file --- .../components/SmartExit/Confirmation.tsx | 365 ------------- .../SmartExit/Confirmation/Condition.tsx | 123 +++++ .../SmartExit/Confirmation/ExpectedAmount.tsx | 110 ++++ .../SmartExit/Confirmation/MoreInfo.tsx | 46 ++ .../SmartExit/Confirmation/Success.tsx | 49 ++ .../calculateExpectedAmounts.ts | 0 .../SmartExit/Confirmation/index.tsx | 108 ++++ .../Earns/components/SmartExit/Metrics.tsx | 494 ------------------ .../SmartExit/Metrics/FeeYieldInput.tsx | 80 +++ .../SmartExit/Metrics/MetricSelect.tsx | 69 +++ .../SmartExit/Metrics/PriceInput.tsx | 192 +++++++ .../SmartExit/Metrics/TimeInput.tsx | 92 ++++ .../components/SmartExit/Metrics/index.tsx | 87 +++ .../Earns/components/SmartExit/index.tsx | 4 +- 14 files changed, 958 insertions(+), 861 deletions(-) delete mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Condition.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/MoreInfo.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Success.tsx rename apps/kyberswap-interface/src/pages/Earns/components/SmartExit/{ => Confirmation}/calculateExpectedAmounts.ts (100%) create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/index.tsx delete mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/FeeYieldInput.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/MetricSelect.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/TimeInput.tsx create mode 100644 apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/index.tsx diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx deleted file mode 100644 index 80c3bde0e2..0000000000 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation.tsx +++ /dev/null @@ -1,365 +0,0 @@ -import { Trans, t } from '@lingui/macro' -import dayjs from 'dayjs' -import { useMemo } from 'react' -import { X } from 'react-feather' -import { useNavigate } from 'react-router' -import { Box, Flex, Text } from 'rebass' - -import { ButtonOutlined, ButtonPrimary } from 'components/Button' -import { CheckCircle } from 'components/Icons' -import TokenLogo from 'components/TokenLogo' -import { MouseoverTooltip, TextDashed } from 'components/Tooltip' -import { APP_PATHS } from 'constants/index' -import { useActiveWeb3React } from 'hooks' -import { PermitNftState, usePermitNft } from 'hooks/usePermitNft' -import useTheme from 'hooks/useTheme' -import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' -import { Badge, ImageContainer } from 'pages/Earns/UserPositions/styles' -import { calculateExpectedAmounts } from 'pages/Earns/components/SmartExit/calculateExpectedAmounts' -import { useSmartExit } from 'pages/Earns/components/SmartExit/useSmartExit' -import { SMART_EXIT_ADDRESS } from 'pages/Earns/constants' -import { - ConditionType, - FeeYieldCondition, - Metric, - ParsedPosition, - PriceCondition, - SelectedMetric, - TimeCondition, -} from 'pages/Earns/types' -import { formatDisplayNumber } from 'utils/numbers' - -export const Confirmation = ({ - selectedMetrics, - position, - deadline, - conditionType, - feeSettings: { protocolFee, maxFeesPercentage }, - onDismiss, -}: { - selectedMetrics: SelectedMetric[] - position: ParsedPosition - conditionType: ConditionType - deadline: number - feeSettings: { protocolFee: number; maxFeesPercentage: number } - onDismiss: () => void -}) => { - const theme = useTheme() - const navigate = useNavigate() - const { chainId } = useActiveWeb3React() - const { changeNetwork } = useChangeNetwork() - - const { permitState, signPermitNft, permitData } = usePermitNft({ - contractAddress: position.id.split('-')[0], - tokenId: position.tokenId, - spender: SMART_EXIT_ADDRESS, - deadline, - }) - - const { createSmartExitOrder, isCreating, isSuccess } = useSmartExit({ - position, - selectedMetrics, - conditionType, - deadline, - permitData: permitData?.permitData, - signature: permitData?.signature, - }) - - const [metric1, metric2] = selectedMetrics - - const feeYieldCondition1 = metric1.condition as FeeYieldCondition - const priceCondition1 = metric1.condition as PriceCondition - const timeCondition1 = metric1.condition as TimeCondition - const feeYieldCondition2 = metric2?.condition as FeeYieldCondition - const priceCondition2 = metric2?.condition as PriceCondition - const timeCondition2 = metric2?.condition as TimeCondition - - const displayTime = dayjs(deadline * 1000).format('DD/MM/YYYY HH:mm:ss') - - // Calculate expected amounts for pool price conditions - const expectedAmounts = useMemo(() => { - // Check if any metric is pool price - const poolPriceMetrics = selectedMetrics.filter(m => m.metric === Metric.PoolPrice) - if (poolPriceMetrics.length === 0) return null - - // If there are multiple pool price conditions, merge them - let mergedCondition: PriceCondition | null = null - - if (poolPriceMetrics.length === 1) { - mergedCondition = poolPriceMetrics[0].condition as PriceCondition - } else if (poolPriceMetrics.length === 2) { - // When using AND: take the intersection (more restrictive range) - // When using OR: take the union (wider range) - const condition1 = poolPriceMetrics[0].condition as PriceCondition - const condition2 = poolPriceMetrics[1].condition as PriceCondition - - if (conditionType === ConditionType.And) { - // Intersection: higher min, lower max - mergedCondition = { - gte: Math.max(parseFloat(condition1.gte), parseFloat(condition2.gte)).toString(), - lte: Math.min(parseFloat(condition1.lte), parseFloat(condition2.lte)).toString(), - } - } else { - // Union: lower min, higher max - mergedCondition = { - gte: Math.min(parseFloat(condition1.gte), parseFloat(condition2.gte)).toString(), - lte: Math.max(parseFloat(condition1.lte), parseFloat(condition2.lte)).toString(), - } - } - } - - return mergedCondition ? calculateExpectedAmounts(position, mergedCondition) : null - }, [position, selectedMetrics, conditionType]) - - if (isSuccess) - return ( - <> - -
- -
- - - - - - Condition saved - - - - - Your Smart Exit condition has been created successfully. - - - - - Cancel - - { - navigate(APP_PATHS.EARN_SMART_EXIT) - }} - > - View All Condition(s) - - - - ) - - return ( - <> - - - Confirmation - - - - - - Exit - - - - - - - - {position.token0.symbol}/{position.token1.symbol} - - Fee {position?.pool.fee}% - - When - - - - {metric1.metric === Metric.FeeYield && The fee yield ≥ {feeYieldCondition1}%} - {metric1.metric === Metric.Time && ( - <> - {timeCondition1.condition.charAt(0).toUpperCase() + timeCondition1.condition.slice(1)} - {dayjs(timeCondition1.time).format('DD/MM/YYYY HH:mm:ss')} - - )} - {metric1.metric === Metric.PoolPrice && ( - <> - - Pool price is between - - - {priceCondition1.gte} and {priceCondition1.lte} {position.token0.symbol}/{position.token1.symbol} - - - )} - {metric2 && ( - <> - - - {conditionType === ConditionType.And ? And : Or} - - - - {metric2.metric === Metric.FeeYield && The fee yield ≥ {feeYieldCondition2}%} - {metric2.metric === Metric.Time && ( - <> - {timeCondition2.condition.charAt(0).toUpperCase() + timeCondition2.condition.slice(1)} - {dayjs(timeCondition2.time).format('DD/MM/YYYY HH:mm:ss')} - - )} - {metric2.metric === Metric.PoolPrice && ( - <> - - Pool price is between - - - {priceCondition2.gte} and {priceCondition2.lte} {position.token0.symbol}/{position.token1.symbol} - - - )} - - )} - - - {expectedAmounts && ( - - - Estimated balance when exit - - - - - - Min - - - - - {formatDisplayNumber(expectedAmounts.minAmount0, { significantDigits: 6 })} {position.token0.symbol} - - - - - - {formatDisplayNumber(expectedAmounts.minAmount1, { significantDigits: 6 })} {position.token1.symbol} - - - - - - - Max - - - - - {formatDisplayNumber(expectedAmounts.maxAmount0, { significantDigits: 6 })} {position.token0.symbol} - - - - - - {formatDisplayNumber(expectedAmounts.maxAmount1, { significantDigits: 6 })} {position.token1.symbol} - - - - - - )} - - - - Platform Fee - - - {protocolFee}% - - - - - - - Expires in - - - - {displayTime} - - - - - - The information is intended solely for your reference at the time you are viewing. It is your responsibility - to verify all information before making decisions - - - - { - if (!maxFeesPercentage) return - if (chainId !== position.chain.id) { - changeNetwork(position.chain.id) - return - } - - if (permitState === PermitNftState.SIGNED && permitData) { - // Create smart exit order - await createSmartExitOrder({ maxFeesPercentage: [maxFeesPercentage, maxFeesPercentage] }) - return - } - if (permitState === PermitNftState.READY_TO_SIGN) { - await signPermitNft() - } - }} - > - {chainId !== position.chain.id ? ( - Switch Network - ) : isCreating ? ( - Creating Order... - ) : permitState === PermitNftState.SIGNED ? ( - Confirm Smart Exit - ) : permitState === PermitNftState.SIGNING ? ( - Signing... - ) : ( - Permit NFT - )} - - - ) -} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Condition.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Condition.tsx new file mode 100644 index 0000000000..672d73e1ee --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Condition.tsx @@ -0,0 +1,123 @@ +import { Trans } from '@lingui/macro' +import dayjs from 'dayjs' +import { Box, Flex, Text } from 'rebass' + +import TokenLogo from 'components/TokenLogo' +import useTheme from 'hooks/useTheme' +import { Badge, ImageContainer } from 'pages/Earns/UserPositions/styles' +import { + ConditionType, + FeeYieldCondition, + Metric, + ParsedPosition, + PriceCondition, + SelectedMetric, + TimeCondition, +} from 'pages/Earns/types' + +export default function Condition({ + position, + selectedMetrics, + conditionType, +}: { + position: ParsedPosition + selectedMetrics: SelectedMetric[] + conditionType: ConditionType +}) { + const theme = useTheme() + + const [metric1, metric2] = selectedMetrics + + const feeYieldCondition1 = metric1.condition as FeeYieldCondition + const priceCondition1 = metric1.condition as PriceCondition + const timeCondition1 = metric1.condition as TimeCondition + const feeYieldCondition2 = metric2?.condition as FeeYieldCondition + const priceCondition2 = metric2?.condition as PriceCondition + const timeCondition2 = metric2?.condition as TimeCondition + + return ( + <> + + Exit + + + + + + + + {position.token0.symbol}/{position.token1.symbol} + + Fee {position?.pool.fee}% + + When + + + + {metric1.metric === Metric.FeeYield && The fee yield ≥ {feeYieldCondition1}%} + {metric1.metric === Metric.Time && ( + <> + {timeCondition1.condition.charAt(0).toUpperCase() + timeCondition1.condition.slice(1)} + {dayjs(timeCondition1.time).format('DD/MM/YYYY HH:mm:ss')} + + )} + {metric1.metric === Metric.PoolPrice && ( + <> + + Pool price is between + + + {priceCondition1.gte} and {priceCondition1.lte} {position.token0.symbol}/{position.token1.symbol} + + + )} + {metric2 && ( + <> + + + {conditionType === ConditionType.And ? And : Or} + + + + {metric2.metric === Metric.FeeYield && The fee yield ≥ {feeYieldCondition2}%} + {metric2.metric === Metric.Time && ( + <> + {timeCondition2.condition.charAt(0).toUpperCase() + timeCondition2.condition.slice(1)} + {dayjs(timeCondition2.time).format('DD/MM/YYYY HH:mm:ss')} + + )} + {metric2.metric === Metric.PoolPrice && ( + <> + + Pool price is between + + + {priceCondition2.gte} and {priceCondition2.lte} {position.token0.symbol}/{position.token1.symbol} + + + )} + + )} + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx new file mode 100644 index 0000000000..384d5d14ac --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx @@ -0,0 +1,110 @@ +import { Trans } from '@lingui/macro' +import { useMemo } from 'react' +import { Box, Flex, Text } from 'rebass' + +import TokenLogo from 'components/TokenLogo' +import useTheme from 'hooks/useTheme' +import { calculateExpectedAmounts } from 'pages/Earns/components/SmartExit/Confirmation/calculateExpectedAmounts' +import { ConditionType, Metric, ParsedPosition, PriceCondition, SelectedMetric } from 'pages/Earns/types' +import { formatDisplayNumber } from 'utils/numbers' + +export default function ExpectedAmount({ + selectedMetrics, + position, + conditionType, +}: { + selectedMetrics: SelectedMetric[] + position: ParsedPosition + conditionType: ConditionType +}) { + const theme = useTheme() + + const expectedAmounts = useMemo(() => { + // Check if any metric is pool price + const poolPriceMetrics = selectedMetrics.filter(m => m.metric === Metric.PoolPrice) + if (poolPriceMetrics.length === 0) return null + + // If there are multiple pool price conditions, merge them + let mergedCondition: PriceCondition | null = null + + if (poolPriceMetrics.length === 1) { + mergedCondition = poolPriceMetrics[0].condition as PriceCondition + } else if (poolPriceMetrics.length === 2) { + // When using AND: take the intersection (more restrictive range) + // When using OR: take the union (wider range) + const condition1 = poolPriceMetrics[0].condition as PriceCondition + const condition2 = poolPriceMetrics[1].condition as PriceCondition + + if (conditionType === ConditionType.And) { + // Intersection: higher min, lower max + mergedCondition = { + gte: Math.max(parseFloat(condition1.gte), parseFloat(condition2.gte)).toString(), + lte: Math.min(parseFloat(condition1.lte), parseFloat(condition2.lte)).toString(), + } + } else { + // Union: lower min, higher max + mergedCondition = { + gte: Math.min(parseFloat(condition1.gte), parseFloat(condition2.gte)).toString(), + lte: Math.max(parseFloat(condition1.lte), parseFloat(condition2.lte)).toString(), + } + } + } + + return mergedCondition ? calculateExpectedAmounts(position, mergedCondition) : null + }, [position, selectedMetrics, conditionType]) + + return ( + expectedAmounts && ( + + + Estimated balance when exit + + + + + + Min + + + + + {formatDisplayNumber(expectedAmounts.minAmount0, { significantDigits: 6 })} {position.token0.symbol} + + + + + + {formatDisplayNumber(expectedAmounts.minAmount1, { significantDigits: 6 })} {position.token1.symbol} + + + + + + + Max + + + + + {formatDisplayNumber(expectedAmounts.maxAmount0, { significantDigits: 6 })} {position.token0.symbol} + + + + + + {formatDisplayNumber(expectedAmounts.maxAmount1, { significantDigits: 6 })} {position.token1.symbol} + + + + + + ) + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/MoreInfo.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/MoreInfo.tsx new file mode 100644 index 0000000000..1d533c3ae4 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/MoreInfo.tsx @@ -0,0 +1,46 @@ +import { Trans, t } from '@lingui/macro' +import dayjs from 'dayjs' +import { Flex, Text } from 'rebass' + +import { MouseoverTooltip, TextDashed } from 'components/Tooltip' +import useTheme from 'hooks/useTheme' + +export default function MoreInfo({ deadline, protocolFee }: { deadline: number; protocolFee: number }) { + const theme = useTheme() + const displayTime = dayjs(deadline * 1000).format('DD/MM/YYYY HH:mm:ss') + + return ( + <> + + + Platform Fee + + + {protocolFee}% + + + + + + + Expires in + + + + {displayTime} + + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Success.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Success.tsx new file mode 100644 index 0000000000..d45430272c --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Success.tsx @@ -0,0 +1,49 @@ +import { Trans } from '@lingui/macro' +import { X } from 'react-feather' +import { useNavigate } from 'react-router' +import { Flex, Text } from 'rebass' + +import { ButtonOutlined, ButtonPrimary } from 'components/Button' +import { CheckCircle } from 'components/Icons' +import { APP_PATHS } from 'constants/index' +import useTheme from 'hooks/useTheme' + +export default function Success({ onDismiss }: { onDismiss: () => void }) { + const theme = useTheme() + const navigate = useNavigate() + + return ( + <> + +
+ +
+ + + + + + Condition saved + + + + + Your Smart Exit condition has been created successfully. + + + + + Cancel + + { + navigate(APP_PATHS.EARN_SMART_EXIT) + }} + > + View All Condition(s) + + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/calculateExpectedAmounts.ts b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/calculateExpectedAmounts.ts similarity index 100% rename from apps/kyberswap-interface/src/pages/Earns/components/SmartExit/calculateExpectedAmounts.ts rename to apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/calculateExpectedAmounts.ts diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/index.tsx new file mode 100644 index 0000000000..c56fd53ec0 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/index.tsx @@ -0,0 +1,108 @@ +import { Trans } from '@lingui/macro' +import { X } from 'react-feather' +import { Flex, Text } from 'rebass' + +import { ButtonPrimary } from 'components/Button' +import { useActiveWeb3React } from 'hooks' +import { PermitNftState, usePermitNft } from 'hooks/usePermitNft' +import useTheme from 'hooks/useTheme' +import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' +import Condition from 'pages/Earns/components/SmartExit/Confirmation/Condition' +import ExpectedAmount from 'pages/Earns/components/SmartExit/Confirmation/ExpectedAmount' +import MoreInfo from 'pages/Earns/components/SmartExit/Confirmation/MoreInfo' +import Success from 'pages/Earns/components/SmartExit/Confirmation/Success' +import { useSmartExit } from 'pages/Earns/components/SmartExit/useSmartExit' +import { SMART_EXIT_ADDRESS } from 'pages/Earns/constants' +import { ConditionType, ParsedPosition, SelectedMetric } from 'pages/Earns/types' + +export default function Confirmation({ + selectedMetrics, + position, + deadline, + conditionType, + feeSettings: { protocolFee, maxFeesPercentage }, + onDismiss, +}: { + selectedMetrics: SelectedMetric[] + position: ParsedPosition + conditionType: ConditionType + deadline: number + feeSettings: { protocolFee: number; maxFeesPercentage: number } + onDismiss: () => void +}) { + const theme = useTheme() + const { chainId } = useActiveWeb3React() + const { changeNetwork } = useChangeNetwork() + + const { permitState, signPermitNft, permitData } = usePermitNft({ + contractAddress: position.id.split('-')[0], + tokenId: position.tokenId, + spender: SMART_EXIT_ADDRESS, + deadline, + }) + + const { createSmartExitOrder, isCreating, isSuccess } = useSmartExit({ + position, + selectedMetrics, + conditionType, + deadline, + permitData: permitData?.permitData, + signature: permitData?.signature, + }) + + if (isSuccess) return + + return ( + <> + + + Confirmation + + + + + + + + + + + The information is intended solely for your reference at the time you are viewing. It is your responsibility + to verify all information before making decisions + + + + { + if (!maxFeesPercentage) return + if (chainId !== position.chain.id) { + changeNetwork(position.chain.id) + return + } + + if (permitState === PermitNftState.SIGNED && permitData) { + // Create smart exit order + await createSmartExitOrder({ maxFeesPercentage: [maxFeesPercentage, maxFeesPercentage] }) + return + } + if (permitState === PermitNftState.READY_TO_SIGN) { + await signPermitNft() + } + }} + > + {chainId !== position.chain.id ? ( + Switch Network + ) : isCreating ? ( + Creating Order... + ) : permitState === PermitNftState.SIGNED ? ( + Confirm Smart Exit + ) : permitState === PermitNftState.SIGNING ? ( + Signing... + ) : ( + Permit NFT + )} + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx deleted file mode 100644 index f1e9f28213..0000000000 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics.tsx +++ /dev/null @@ -1,494 +0,0 @@ -import { Label, RadioGroup, RadioGroupItem } from '@kyber/ui' -import { nearestUsableTick, priceToClosestTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' -import UniswapPriceSlider from '@kyberswap/price-slider' -import '@kyberswap/price-slider/style.css' -import { Trans, t } from '@lingui/macro' -import dayjs from 'dayjs' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Calendar } from 'react-feather' -import { Box, Flex, Text } from 'rebass' - -import Divider from 'components/Divider' -import DateTimePicker from 'components/swapv2/LimitOrder/ExpirePicker' -import useTheme from 'hooks/useTheme' -import { DEFAULT_TIME_OPTIONS } from 'pages/Earns/components/SmartExit/ExpireSetting' -import { CustomInput, CustomSelect } from 'pages/Earns/components/SmartExit/styles' -import { getDefaultCondition } from 'pages/Earns/components/SmartExit/utils' -import { - ConditionType, - FeeYieldCondition, - Metric, - ParsedPosition, - PriceCondition, - SelectedMetric, - TimeCondition, -} from 'pages/Earns/types' -import { ButtonText } from 'theme' - -const supportedMetrics = [Metric.FeeYield, Metric.PoolPrice, Metric.Time] - -export const Metrics = ({ - position, - selectedMetrics, - setSelectedMetrics, - conditionType, - setConditionType, -}: { - position: ParsedPosition - selectedMetrics: SelectedMetric[] - setSelectedMetrics: (value: SelectedMetric[]) => void - conditionType: ConditionType - setConditionType: (v: ConditionType) => void -}) => { - const theme = useTheme() - const [metric1, metric2] = selectedMetrics - - const onChangeMetric1 = (value: SelectedMetric) => setSelectedMetrics(metric2 ? [value, metric2] : [value]) - const onChangeMetric2 = (value: SelectedMetric) => setSelectedMetrics([metric1, value]) - const onRemoveMetric2 = () => setSelectedMetrics([metric1]) - const onAddMetric2 = () => { - const newMetric = supportedMetrics.filter(item => item === Metric.PoolPrice || item !== metric1.metric)[0] - const newCondition = getDefaultCondition(newMetric) - if (!newCondition) return - setSelectedMetrics([metric1, { metric: newMetric, condition: newCondition }]) - } - - return ( - - - - {metric2 ? ( - <> - - setConditionType(v as ConditionType)}> - - - - - - - - - - Remove Condition - - - - - - ) : ( - - + Add Condition 2 - - )} - - ) -} - -const MetricSelect = ({ - metric, - setMetric, - selectedMetric, - position, -}: { - metric: SelectedMetric - setMetric: (value: SelectedMetric) => void - selectedMetric?: SelectedMetric - position: ParsedPosition -}) => { - const metricOptions = useMemo( - () => - [ - { - label: t`Fee Yield`, - value: Metric.FeeYield, - }, - { - label: t`Pool Price`, - value: Metric.PoolPrice, - }, - { - label: t`Time`, - value: Metric.Time, - }, - ].filter(item => item.value === Metric.PoolPrice || item.value !== selectedMetric?.metric), - [selectedMetric], - ) - - return ( - <> - - - Select Metric - - { - if (value === metric.metric) return - const newMetric = value as Metric - const condition = getDefaultCondition(newMetric) - if (!condition) return - setMetric({ metric: newMetric, condition }) - }} - value={metric.metric} - menuStyle={{ width: '250px' }} - /> - - - {metric.metric === Metric.FeeYield && } - - {metric.metric === Metric.PoolPrice && } - - {metric.metric === Metric.Time && } - - ) -} - -const FeeYieldInput = ({ - metric, - setMetric, -}: { - metric: SelectedMetric - setMetric: (value: SelectedMetric) => void -}) => { - const theme = useTheme() - const feeYieldCondition = metric.condition as FeeYieldCondition - - return ( - <> - - - Exit when fee yield ≥ - - - { - const value = e.target.value - // Only allow numbers and decimal point - if (/^\d*\.?\d*$/.test(value)) { - const numValue = parseFloat(value) - // Limit to 1-100% - if (value === '' || numValue > 0) { - setMetric({ ...metric, condition: value }) - } - } - }} - placeholder="0" - /> - - % - - - - - {[5, 10, 15, 20].map(item => { - const isSelected = metric.condition === item.toString() - - return ( - setMetric({ ...metric, condition: item.toString() })} - sx={{ - borderRadius: '999px', - border: `1px solid ${isSelected ? theme.primary : theme.border}`, - backgroundColor: isSelected ? theme.primary + '20' : 'transparent', - padding: '4px 12px', - color: isSelected ? theme.primary : theme.subText, - fontSize: '12px', - fontWeight: '500', - cursor: 'pointer', - '&:hover': { - backgroundColor: theme.primary + '10', - }, - }} - > - {item}% - - ) - })} - - - ) -} - -const PriceInput = ({ - metric, - setMetric, - position, -}: { - metric: SelectedMetric - setMetric: (value: SelectedMetric) => void - position: ParsedPosition -}) => { - const priceCondition = metric.condition as PriceCondition - - const [lowerTick, setLowerTickState] = useState() - const [upperTick, setUpperTickState] = useState() - - // Local input state for debouncing - const [inputMinPrice, setInputMinPrice] = useState(priceCondition?.gte ?? '') - const [inputMaxPrice, setInputMaxPrice] = useState(priceCondition?.lte ?? '') - - // Track change source to prevent circular updates - const changeSourceRef = useRef<'input' | 'slider' | null>(null) - - // Sync local input with external price changes (from slider) - useEffect(() => { - if (changeSourceRef.current === 'slider' && priceCondition?.gte) { - setInputMinPrice(priceCondition.gte) - } - }, [priceCondition?.gte]) - - useEffect(() => { - if (changeSourceRef.current === 'slider' && priceCondition?.lte) { - setInputMaxPrice(priceCondition.lte) - } - }, [priceCondition?.lte]) - - // Debounce input price updates - useEffect(() => { - if (changeSourceRef.current === 'slider' || metric.metric !== Metric.PoolPrice) return - const timer = setTimeout(() => { - if (inputMinPrice !== priceCondition?.gte) { - setMetric({ ...metric, condition: { ...priceCondition, gte: inputMinPrice } }) - } - }, 300) - return () => clearTimeout(timer) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [inputMinPrice]) - - useEffect(() => { - if (changeSourceRef.current === 'slider' || metric.metric !== Metric.PoolPrice) return - const timer = setTimeout(() => { - if (inputMaxPrice !== priceCondition?.lte) { - setMetric({ ...metric, condition: { ...priceCondition, lte: inputMaxPrice } }) - } - }, 300) - return () => clearTimeout(timer) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [inputMaxPrice]) - - const currentTick = useMemo( - () => - nearestUsableTick( - priceToClosestTick( - position.priceRange.current.toString(), - position.token0.decimals, - position.token1.decimals, - ) || 0, - position.pool.tickSpacing, - ), - [position.pool.tickSpacing, position.priceRange, position.token0.decimals, position.token1.decimals], - ) - - const priceToTick = useCallback( - (price: string) => { - if (!price) return undefined - return nearestUsableTick( - priceToClosestTick(price, position.token0.decimals, position.token1.decimals) || 0, - position.pool.tickSpacing, - ) - }, - [position.pool.tickSpacing, position.token0.decimals, position.token1.decimals], - ) - - // Wrapper to set tick from slider (updates price) - const setLowerTick = useCallback( - (tick: number | undefined) => { - changeSourceRef.current = 'slider' - setLowerTickState(tick) - if (tick !== undefined) { - const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals, false) - setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) - } - // Reset source after React batch update - setTimeout(() => { - changeSourceRef.current = null - }, 0) - }, - [metric, priceCondition, position.token0.decimals, position.token1.decimals, setMetric], - ) - - const setUpperTick = useCallback( - (tick: number | undefined) => { - changeSourceRef.current = 'slider' - setUpperTickState(tick) - if (tick !== undefined) { - const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals, false) - setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) - } - setTimeout(() => { - changeSourceRef.current = null - }, 0) - }, - [metric, priceCondition, position.token0.decimals, position.token1.decimals, setMetric], - ) - - // Sync tick from price input (only when source is input, not slider) - useEffect(() => { - if (changeSourceRef.current === 'slider') return - if (priceCondition?.gte) { - const tick = priceToTick(priceCondition.gte) - if (tick !== undefined && tick !== lowerTick) { - setLowerTickState(tick) - } - } - }, [priceCondition?.gte, priceToTick, lowerTick]) - - useEffect(() => { - if (changeSourceRef.current === 'slider') return - if (priceCondition?.lte) { - const tick = priceToTick(priceCondition.lte) - if (tick !== undefined && tick !== upperTick) { - setUpperTickState(tick) - } - } - }, [priceCondition?.lte, priceToTick, upperTick]) - - return ( - <> - - Exit when the pool price is between - - - { - const value = e.target.value - // Only allow numbers and decimal point - if (/^\d*\.?\d*$/.test(value)) { - setInputMinPrice(value) - } - }} - /> - - - { - const value = e.target.value - // Only allow numbers and decimal point - if (/^\d*\.?\d*$/.test(value)) { - setInputMaxPrice(value) - } - }} - /> - - {position.token0.symbol}/{position.token1.symbol} - - - - - ) -} - -const TimeInput = ({ metric, setMetric }: { metric: SelectedMetric; setMetric: (value: SelectedMetric) => void }) => { - const theme = useTheme() - const [openDatePicker, setOpenDatePicker] = useState(false) - - const timeOptions = useMemo( - () => [ - { - label: t`Before`, - value: 'before', - }, - { - label: t`After`, - value: 'after', - }, - ], - [], - ) - const timeCondition = metric.condition as TimeCondition - - return ( - <> - - - Exit this position - - { - setMetric({ ...metric, condition: { ...timeCondition, condition: value } }) - }} - value={timeCondition.condition} - menuStyle={{ width: '250px' }} - /> - - - Set Schedule - - setOpenDatePicker(true)} - > - - {timeCondition.time === null ? 'DD/MM/YYYY' : dayjs(timeCondition.time).format('DD/MM/YYYY HH:mm:ss')} - - - - - Time Setup} - isOpen={openDatePicker} - onDismiss={() => setOpenDatePicker(false)} - onSetDate={(val: Date | number) => { - setMetric({ - ...metric, - condition: { ...timeCondition, time: typeof val === 'number' ? val : val.getTime() }, - }) - }} - expire={ - timeCondition.time || 5 * 60 // 5min - } - defaultOptions={DEFAULT_TIME_OPTIONS} - /> - - ) -} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/FeeYieldInput.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/FeeYieldInput.tsx new file mode 100644 index 0000000000..74b6b3c07b --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/FeeYieldInput.tsx @@ -0,0 +1,80 @@ +import { Trans } from '@lingui/macro' +import { Box, Flex, Text } from 'rebass' + +import useTheme from 'hooks/useTheme' +import { CustomInput } from 'pages/Earns/components/SmartExit/styles' +import { FeeYieldCondition, SelectedMetric } from 'pages/Earns/types' + +export default function FeeYieldInput({ + metric, + setMetric, +}: { + metric: SelectedMetric + setMetric: (value: SelectedMetric) => void +}) { + const theme = useTheme() + const feeYieldCondition = metric.condition as FeeYieldCondition + + return ( + <> + + + Exit when fee yield ≥ + + + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + const numValue = parseFloat(value) + // Limit to 1-100% + if (value === '' || numValue > 0) { + setMetric({ ...metric, condition: value }) + } + } + }} + placeholder="0" + /> + + % + + + + + {[5, 10, 15, 20].map(item => { + const isSelected = metric.condition === item.toString() + + return ( + setMetric({ ...metric, condition: item.toString() })} + sx={{ + borderRadius: '999px', + border: `1px solid ${isSelected ? theme.primary : theme.border}`, + backgroundColor: isSelected ? theme.primary + '20' : 'transparent', + padding: '4px 12px', + color: isSelected ? theme.primary : theme.subText, + fontSize: '12px', + fontWeight: '500', + cursor: 'pointer', + '&:hover': { + backgroundColor: theme.primary + '10', + }, + }} + > + {item}% + + ) + })} + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/MetricSelect.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/MetricSelect.tsx new file mode 100644 index 0000000000..89968d9c9b --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/MetricSelect.tsx @@ -0,0 +1,69 @@ +import { Trans, t } from '@lingui/macro' +import { useMemo } from 'react' +import { Flex, Text } from 'rebass' + +import FeeYieldInput from 'pages/Earns/components/SmartExit/Metrics/FeeYieldInput' +import PriceInput from 'pages/Earns/components/SmartExit/Metrics/PriceInput' +import TimeInput from 'pages/Earns/components/SmartExit/Metrics/TimeInput' +import { CustomSelect } from 'pages/Earns/components/SmartExit/styles' +import { getDefaultCondition } from 'pages/Earns/components/SmartExit/utils' +import { Metric, ParsedPosition, SelectedMetric } from 'pages/Earns/types' + +export default function MetricSelect({ + metric, + setMetric, + selectedMetric, + position, +}: { + metric: SelectedMetric + setMetric: (value: SelectedMetric) => void + selectedMetric?: SelectedMetric + position: ParsedPosition +}) { + const metricOptions = useMemo( + () => + [ + { + label: t`Fee Yield`, + value: Metric.FeeYield, + }, + { + label: t`Pool Price`, + value: Metric.PoolPrice, + }, + { + label: t`Time`, + value: Metric.Time, + }, + ].filter(item => item.value === Metric.PoolPrice || item.value !== selectedMetric?.metric), + [selectedMetric], + ) + + return ( + <> + + + Select Metric + + { + if (value === metric.metric) return + const newMetric = value as Metric + const condition = getDefaultCondition(newMetric) + if (!condition) return + setMetric({ metric: newMetric, condition }) + }} + value={metric.metric} + menuStyle={{ width: '250px' }} + /> + + + {metric.metric === Metric.FeeYield && } + + {metric.metric === Metric.PoolPrice && } + + {metric.metric === Metric.Time && } + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx new file mode 100644 index 0000000000..58e69a42e0 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx @@ -0,0 +1,192 @@ +import { nearestUsableTick, priceToClosestTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' +import UniswapPriceSlider from '@kyberswap/price-slider' +import '@kyberswap/price-slider/style.css' +import { Trans } from '@lingui/macro' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Flex, Text } from 'rebass' + +import { CustomInput } from 'pages/Earns/components/SmartExit/styles' +import { Metric, ParsedPosition, PriceCondition, SelectedMetric } from 'pages/Earns/types' + +export default function PriceInput({ + metric, + setMetric, + position, +}: { + metric: SelectedMetric + setMetric: (value: SelectedMetric) => void + position: ParsedPosition +}) { + const priceCondition = metric.condition as PriceCondition + + const [lowerTick, setLowerTickState] = useState() + const [upperTick, setUpperTickState] = useState() + + // Local input state for debouncing + const [inputMinPrice, setInputMinPrice] = useState(priceCondition?.gte ?? '') + const [inputMaxPrice, setInputMaxPrice] = useState(priceCondition?.lte ?? '') + + // Track change source to prevent circular updates + const changeSourceRef = useRef<'input' | 'slider' | null>(null) + + // Sync local input with external price changes (from slider) + useEffect(() => { + if (changeSourceRef.current === 'slider' && priceCondition?.gte) { + setInputMinPrice(priceCondition.gte) + } + }, [priceCondition?.gte]) + + useEffect(() => { + if (changeSourceRef.current === 'slider' && priceCondition?.lte) { + setInputMaxPrice(priceCondition.lte) + } + }, [priceCondition?.lte]) + + // Debounce input price updates + useEffect(() => { + if (changeSourceRef.current === 'slider' || metric.metric !== Metric.PoolPrice) return + const timer = setTimeout(() => { + if (inputMinPrice !== priceCondition?.gte) { + setMetric({ ...metric, condition: { ...priceCondition, gte: inputMinPrice } }) + } + }, 300) + return () => clearTimeout(timer) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inputMinPrice]) + + useEffect(() => { + if (changeSourceRef.current === 'slider' || metric.metric !== Metric.PoolPrice) return + const timer = setTimeout(() => { + if (inputMaxPrice !== priceCondition?.lte) { + setMetric({ ...metric, condition: { ...priceCondition, lte: inputMaxPrice } }) + } + }, 300) + return () => clearTimeout(timer) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inputMaxPrice]) + + const currentTick = useMemo( + () => + nearestUsableTick( + priceToClosestTick( + position.priceRange.current.toString(), + position.token0.decimals, + position.token1.decimals, + ) || 0, + position.pool.tickSpacing, + ), + [position.pool.tickSpacing, position.priceRange, position.token0.decimals, position.token1.decimals], + ) + + const priceToTick = useCallback( + (price: string) => { + if (!price) return undefined + return nearestUsableTick( + priceToClosestTick(price, position.token0.decimals, position.token1.decimals) || 0, + position.pool.tickSpacing, + ) + }, + [position.pool.tickSpacing, position.token0.decimals, position.token1.decimals], + ) + + // Wrapper to set tick from slider (updates price) + const setLowerTick = useCallback( + (tick: number | undefined) => { + changeSourceRef.current = 'slider' + setLowerTickState(tick) + if (tick !== undefined) { + const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals, false) + setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) + } + // Reset source after React batch update + setTimeout(() => { + changeSourceRef.current = null + }, 0) + }, + [metric, priceCondition, position.token0.decimals, position.token1.decimals, setMetric], + ) + + const setUpperTick = useCallback( + (tick: number | undefined) => { + changeSourceRef.current = 'slider' + setUpperTickState(tick) + if (tick !== undefined) { + const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals, false) + setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) + } + setTimeout(() => { + changeSourceRef.current = null + }, 0) + }, + [metric, priceCondition, position.token0.decimals, position.token1.decimals, setMetric], + ) + + // Sync tick from price input (only when source is input, not slider) + useEffect(() => { + if (changeSourceRef.current === 'slider') return + if (priceCondition?.gte) { + const tick = priceToTick(priceCondition.gte) + if (tick !== undefined && tick !== lowerTick) { + setLowerTickState(tick) + } + } + }, [priceCondition?.gte, priceToTick, lowerTick]) + + useEffect(() => { + if (changeSourceRef.current === 'slider') return + if (priceCondition?.lte) { + const tick = priceToTick(priceCondition.lte) + if (tick !== undefined && tick !== upperTick) { + setUpperTickState(tick) + } + } + }, [priceCondition?.lte, priceToTick, upperTick]) + + return ( + <> + + Exit when the pool price is between + + + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + setInputMinPrice(value) + } + }} + /> + - + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + setInputMaxPrice(value) + } + }} + /> + + {position.token0.symbol}/{position.token1.symbol} + + + + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/TimeInput.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/TimeInput.tsx new file mode 100644 index 0000000000..cfc2c63b74 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/TimeInput.tsx @@ -0,0 +1,92 @@ +import { Trans, t } from '@lingui/macro' +import dayjs from 'dayjs' +import { useMemo, useState } from 'react' +import { Calendar } from 'react-feather' +import { Flex, Text } from 'rebass' + +import DateTimePicker from 'components/swapv2/LimitOrder/ExpirePicker' +import useTheme from 'hooks/useTheme' +import { DEFAULT_TIME_OPTIONS } from 'pages/Earns/components/SmartExit/ExpireSetting' +import { CustomSelect } from 'pages/Earns/components/SmartExit/styles' +import { SelectedMetric, TimeCondition } from 'pages/Earns/types' + +export default function TimeInput({ + metric, + setMetric, +}: { + metric: SelectedMetric + setMetric: (value: SelectedMetric) => void +}) { + const theme = useTheme() + const [openDatePicker, setOpenDatePicker] = useState(false) + + const timeOptions = useMemo( + () => [ + { + label: t`Before`, + value: 'before', + }, + { + label: t`After`, + value: 'after', + }, + ], + [], + ) + const timeCondition = metric.condition as TimeCondition + + return ( + <> + + + Exit this position + + { + setMetric({ ...metric, condition: { ...timeCondition, condition: value } }) + }} + value={timeCondition.condition} + menuStyle={{ width: '250px' }} + /> + + + Set Schedule + + setOpenDatePicker(true)} + > + + {timeCondition.time === null ? 'DD/MM/YYYY' : dayjs(timeCondition.time).format('DD/MM/YYYY HH:mm:ss')} + + + + + Time Setup} + isOpen={openDatePicker} + onDismiss={() => setOpenDatePicker(false)} + onSetDate={(val: Date | number) => { + setMetric({ + ...metric, + condition: { ...timeCondition, time: typeof val === 'number' ? val : val.getTime() }, + }) + }} + expire={ + timeCondition.time || 5 * 60 // 5min + } + defaultOptions={DEFAULT_TIME_OPTIONS} + /> + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/index.tsx new file mode 100644 index 0000000000..dbc7f4de83 --- /dev/null +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/index.tsx @@ -0,0 +1,87 @@ +import { Label, RadioGroup, RadioGroupItem } from '@kyber/ui' +import { Trans } from '@lingui/macro' +import { Flex } from 'rebass' + +import Divider from 'components/Divider' +import useTheme from 'hooks/useTheme' +import MetricSelect from 'pages/Earns/components/SmartExit/Metrics/MetricSelect' +import { getDefaultCondition } from 'pages/Earns/components/SmartExit/utils' +import { ConditionType, Metric, ParsedPosition, SelectedMetric } from 'pages/Earns/types' +import { ButtonText } from 'theme' + +const supportedMetrics = [Metric.FeeYield, Metric.PoolPrice, Metric.Time] + +export default function Metrics({ + position, + selectedMetrics, + setSelectedMetrics, + conditionType, + setConditionType, +}: { + position: ParsedPosition + selectedMetrics: SelectedMetric[] + setSelectedMetrics: (value: SelectedMetric[]) => void + conditionType: ConditionType + setConditionType: (v: ConditionType) => void +}) { + const theme = useTheme() + const [metric1, metric2] = selectedMetrics + + const onChangeMetric1 = (value: SelectedMetric) => setSelectedMetrics(metric2 ? [value, metric2] : [value]) + const onChangeMetric2 = (value: SelectedMetric) => setSelectedMetrics([metric1, value]) + const onRemoveMetric2 = () => setSelectedMetrics([metric1]) + const onAddMetric2 = () => { + const newMetric = supportedMetrics.filter(item => item === Metric.PoolPrice || item !== metric1.metric)[0] + const newCondition = getDefaultCondition(newMetric) + if (!newCondition) return + setSelectedMetrics([metric1, { metric: newMetric, condition: newCondition }]) + } + + return ( + + + + {metric2 ? ( + <> + + setConditionType(v as ConditionType)}> + + + + + + + + + + Remove Condition + + + + + + ) : ( + + + Add Condition 2 + + )} + + ) +} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx index f79f2a6bf3..066b53a98e 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx @@ -8,10 +8,10 @@ import Modal from 'components/Modal' import { TIMES_IN_SECS } from 'constants/index' import PositionDetailHeader from 'pages/Earns/PositionDetail/Header' import Actions from 'pages/Earns/components/SmartExit/Actions' -import { Confirmation } from 'pages/Earns/components/SmartExit/Confirmation' +import Confirmation from 'pages/Earns/components/SmartExit/Confirmation' import ExpireSetting from 'pages/Earns/components/SmartExit/ExpireSetting' import GasSetting from 'pages/Earns/components/SmartExit/GasSetting' -import { Metrics } from 'pages/Earns/components/SmartExit/Metrics' +import Metrics from 'pages/Earns/components/SmartExit/Metrics' import PoolPrice from 'pages/Earns/components/SmartExit/PoolPrice' import PositionLiquidity from 'pages/Earns/components/SmartExit/PositionLiquidity' import { ContentWrapper } from 'pages/Earns/components/SmartExit/styles' From f60ab87256bd520c9804968ec132cbaf7009cac9 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 4 Dec 2025 14:58:47 +0700 Subject: [PATCH 80/87] fix: expected amount --- .../Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx | 3 ++- .../SmartExit/Confirmation/calculateExpectedAmounts.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx index 384d5d14ac..78a2a24ace 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx @@ -22,7 +22,8 @@ export default function ExpectedAmount({ const expectedAmounts = useMemo(() => { // Check if any metric is pool price const poolPriceMetrics = selectedMetrics.filter(m => m.metric === Metric.PoolPrice) - if (poolPriceMetrics.length === 0) return null + const otherMetrics = selectedMetrics.filter(m => m.metric !== Metric.PoolPrice) + if (poolPriceMetrics.length === 0 || otherMetrics.length > 0) return null // If there are multiple pool price conditions, merge them let mergedCondition: PriceCondition | null = null diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/calculateExpectedAmounts.ts b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/calculateExpectedAmounts.ts index 1598c13c1f..c76bbb8d7c 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/calculateExpectedAmounts.ts +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/calculateExpectedAmounts.ts @@ -14,7 +14,7 @@ interface ExpectedAmounts { * - If price > priceUpper: all liquidity is in token1 * - If priceLower <= price <= priceUpper: liquidity is split between both tokens * - * Correct Uniswap V3 formulas: + * Correct Uniswap V3/V4 formulas: * When in range: * - amount0 = liquidity * (1/sqrt(price) - 1/sqrt(priceUpper)) * - amount1 = liquidity * (sqrt(price) - sqrt(priceLower)) From b1903098229d7830119391bc6c82ea2e6f616737 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 4 Dec 2025 17:27:11 +0700 Subject: [PATCH 81/87] fix UI --- .../src/components/ArrowRotate.tsx | 5 +- .../src/components/Select/index.tsx | 9 ++- .../components/SwapForm/SlippageSetting.tsx | 6 +- .../components/SmartExit/ExpireSetting.tsx | 63 ++++++------------- .../Earns/components/SmartExit/GasSetting.tsx | 15 ++--- .../SmartExit/Metrics/FeeYieldInput.tsx | 24 ++----- .../SmartExit/Metrics/MetricSelect.tsx | 7 ++- .../SmartExit/Metrics/TimeInput.tsx | 12 ++-- .../components/SmartExit/Metrics/index.tsx | 6 +- .../Earns/components/SmartExit/PoolPrice.tsx | 3 +- .../Earns/components/SmartExit/index.tsx | 7 ++- .../Earns/components/SmartExit/styles.tsx | 29 ++++++++- packages/ui/src/components/ui/radio-group.tsx | 2 +- 13 files changed, 96 insertions(+), 92 deletions(-) diff --git a/apps/kyberswap-interface/src/components/ArrowRotate.tsx b/apps/kyberswap-interface/src/components/ArrowRotate.tsx index 894fcb8c88..23772fe1fc 100644 --- a/apps/kyberswap-interface/src/components/ArrowRotate.tsx +++ b/apps/kyberswap-interface/src/components/ArrowRotate.tsx @@ -1,3 +1,4 @@ +import { ChevronDown } from 'react-feather' import styled, { CSSProperties, css } from 'styled-components' import { ReactComponent as DropdownSVG } from 'assets/svg/down.svg' @@ -65,14 +66,16 @@ export const DropdownArrowIcon = ({ rotate, size = 24, color, + arrow = 'arrow', }: { rotate?: boolean size?: number color?: string + arrow?: 'chevron' | 'arrow' }) => { return ( - + {arrow === 'chevron' ? : } ) } diff --git a/apps/kyberswap-interface/src/components/Select/index.tsx b/apps/kyberswap-interface/src/components/Select/index.tsx index 48f2ea195e..5f44ef66e9 100644 --- a/apps/kyberswap-interface/src/components/Select/index.tsx +++ b/apps/kyberswap-interface/src/components/Select/index.tsx @@ -6,12 +6,11 @@ import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react' import { usePopper } from 'react-popper' import styled from 'styled-components' +import { DropdownArrowIcon } from 'components/ArrowRotate' import Icon from 'components/Icons/Icon' import { Z_INDEXS } from 'constants/styles' import { useOnClickOutside } from 'hooks/useOnClickOutside' -import { DropdownArrowIcon } from '../ArrowRotate' - const SelectWrapper = styled.div` cursor: pointer; border-radius: 12px; @@ -110,7 +109,9 @@ export type SelectProps = { optionStyle?: CSSProperties onChange?: (value: any) => void forceMenuPlacementTop?: boolean + arrow?: 'chevron' | 'arrow' arrowColor?: string + arrowSize?: number placement?: Placement withSearch?: boolean onHideMenu?: () => void // hide without changes @@ -127,7 +128,9 @@ function Select({ value: selectedValue, className, forceMenuPlacementTop = false, + arrow = 'arrow', arrowColor, + arrowSize = 24, dropdownRender, onHideMenu, withSearch, @@ -203,7 +206,7 @@ function Select({ className={className} > {activeRender ? activeRender(selectedInfo) : getOptionLabel(selectedInfo)} - + {showMenu && ( diff --git a/apps/kyberswap-interface/src/components/SwapForm/SlippageSetting.tsx b/apps/kyberswap-interface/src/components/SwapForm/SlippageSetting.tsx index b91225d240..a298a35686 100644 --- a/apps/kyberswap-interface/src/components/SwapForm/SlippageSetting.tsx +++ b/apps/kyberswap-interface/src/components/SwapForm/SlippageSetting.tsx @@ -31,11 +31,11 @@ const highlight = keyframes` ` //transition: transform 300ms; -export const DropdownIcon = styled.div` +export const DropdownIcon = styled.div<{ size?: number }>` margin-left: 6px; border-radius: 50%; - width: 12px; - height: 12px; + width: ${({ size }) => size || 12}px; + height: ${({ size }) => size || 12}px; display: flex; justify-content: center; align-items: center; diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/ExpireSetting.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/ExpireSetting.tsx index 2d3e3547a3..f1ad2c8043 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/ExpireSetting.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/ExpireSetting.tsx @@ -3,11 +3,11 @@ import dayjs from 'dayjs' import { useMemo, useState } from 'react' import { Flex, Text } from 'rebass' -import { DefaultSlippageOption } from 'components/SlippageControl' import { MouseoverTooltip, TextDashed } from 'components/Tooltip' import DateTimePicker from 'components/swapv2/LimitOrder/ExpirePicker' import { TIMES_IN_SECS } from 'constants/index' import useTheme from 'hooks/useTheme' +import { CustomOption } from 'pages/Earns/components/SmartExit/styles' import { formatTimeDuration } from 'utils/time' export const DEFAULT_TIME_OPTIONS = [ @@ -63,46 +63,8 @@ export default function ExpireSetting({ Expires in - - - - {displayTime} - - - -
- - + {[ { label: '7D', value: TIMES_IN_SECS.ONE_DAY * 7 }, { label: '30D', value: TIMES_IN_SECS.ONE_DAY * 30 }, @@ -115,22 +77,37 @@ export default function ExpireSetting({ }, ].map((item: any) => { return ( - { if (item.label === 'Custom') item.onSelect() else setExpireTime(item.value) }} - data-active={ + isSelected={ item.label === 'Custom' ? expireTime % TIMES_IN_SECS.ONE_DAY != 0 : item.value === expireTime } > {item.label} - + ) })} + + + + + {displayTime} + + + ) } diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx index 397f840502..0ba9ccfa73 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx @@ -1,6 +1,7 @@ import { Trans, t } from '@lingui/macro' import { rgba } from 'polished' import { useState } from 'react' +import { ChevronDown } from 'react-feather' import { Box, Flex, Text } from 'rebass' import Input from 'components/NumericalInput' @@ -54,13 +55,13 @@ export default function GasSetting({ )} ) - - - - + + )} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/FeeYieldInput.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/FeeYieldInput.tsx index 74b6b3c07b..d15848a775 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/FeeYieldInput.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/FeeYieldInput.tsx @@ -1,8 +1,7 @@ import { Trans } from '@lingui/macro' -import { Box, Flex, Text } from 'rebass' +import { Flex, Text } from 'rebass' -import useTheme from 'hooks/useTheme' -import { CustomInput } from 'pages/Earns/components/SmartExit/styles' +import { CustomInput, CustomOption } from 'pages/Earns/components/SmartExit/styles' import { FeeYieldCondition, SelectedMetric } from 'pages/Earns/types' export default function FeeYieldInput({ @@ -12,7 +11,6 @@ export default function FeeYieldInput({ metric: SelectedMetric setMetric: (value: SelectedMetric) => void }) { - const theme = useTheme() const feeYieldCondition = metric.condition as FeeYieldCondition return ( @@ -53,25 +51,13 @@ export default function FeeYieldInput({ const isSelected = metric.condition === item.toString() return ( - setMetric({ ...metric, condition: item.toString() })} - sx={{ - borderRadius: '999px', - border: `1px solid ${isSelected ? theme.primary : theme.border}`, - backgroundColor: isSelected ? theme.primary + '20' : 'transparent', - padding: '4px 12px', - color: isSelected ? theme.primary : theme.subText, - fontSize: '12px', - fontWeight: '500', - cursor: 'pointer', - '&:hover': { - backgroundColor: theme.primary + '10', - }, - }} + isSelected={isSelected} > {item}% - + ) })}
diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/MetricSelect.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/MetricSelect.tsx index 89968d9c9b..599cf08242 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/MetricSelect.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/MetricSelect.tsx @@ -2,6 +2,7 @@ import { Trans, t } from '@lingui/macro' import { useMemo } from 'react' import { Flex, Text } from 'rebass' +import useTheme from 'hooks/useTheme' import FeeYieldInput from 'pages/Earns/components/SmartExit/Metrics/FeeYieldInput' import PriceInput from 'pages/Earns/components/SmartExit/Metrics/PriceInput' import TimeInput from 'pages/Earns/components/SmartExit/Metrics/TimeInput' @@ -20,6 +21,7 @@ export default function MetricSelect({ selectedMetric?: SelectedMetric position: ParsedPosition }) { + const theme = useTheme() const metricOptions = useMemo( () => [ @@ -55,7 +57,10 @@ export default function MetricSelect({ setMetric({ metric: newMetric, condition }) }} value={metric.metric} - menuStyle={{ width: '250px' }} + menuStyle={{ width: '250px', marginTop: '2px' }} + arrow="chevron" + arrowSize={16} + arrowColor={theme.border} />
diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/TimeInput.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/TimeInput.tsx index cfc2c63b74..e580af31d0 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/TimeInput.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/TimeInput.tsx @@ -1,5 +1,6 @@ import { Trans, t } from '@lingui/macro' import dayjs from 'dayjs' +import { rgba } from 'polished' import { useMemo, useState } from 'react' import { Calendar } from 'react-feather' import { Flex, Text } from 'rebass' @@ -47,7 +48,10 @@ export default function TimeInput({ setMetric({ ...metric, condition: { ...timeCondition, condition: value } }) }} value={timeCondition.condition} - menuStyle={{ width: '250px' }} + menuStyle={{ width: '220px', marginTop: '2px' }} + arrow="chevron" + arrowSize={16} + arrowColor={theme.border} /> @@ -56,7 +60,7 @@ export default function TimeInput({ setOpenDatePicker(true)} > - - {timeCondition.time === null ? 'DD/MM/YYYY' : dayjs(timeCondition.time).format('DD/MM/YYYY HH:mm:ss')} + + {timeCondition.time === null ? t`Pickup time` : dayjs(timeCondition.time).format('DD/MM/YYYY HH:mm:ss')} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/index.tsx index dbc7f4de83..4bb00dea9b 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/index.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/index.tsx @@ -1,10 +1,11 @@ import { Label, RadioGroup, RadioGroupItem } from '@kyber/ui' import { Trans } from '@lingui/macro' +import { Trash2 } from 'react-feather' import { Flex } from 'rebass' -import Divider from 'components/Divider' import useTheme from 'hooks/useTheme' import MetricSelect from 'pages/Earns/components/SmartExit/Metrics/MetricSelect' +import { Divider } from 'pages/Earns/components/SmartExit/styles' import { getDefaultCondition } from 'pages/Earns/components/SmartExit/utils' import { ConditionType, Metric, ParsedPosition, SelectedMetric } from 'pages/Earns/types' import { ButtonText } from 'theme' @@ -62,10 +63,11 @@ export default function Metrics({ width: 'fit-content', color: theme.red, fontSize: '14px', + marginRight: '2px', }} onClick={onRemoveMetric2} > - Remove Condition + diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx index 3859c48122..bb46eaacb9 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/PoolPrice.tsx @@ -3,13 +3,12 @@ import { useState } from 'react' import { Flex, Text } from 'rebass' import { ReactComponent as RevertPriceIcon } from 'assets/svg/earn/ic_revert_price.svg' -import Divider from 'components/Divider' import InfoHelper from 'components/InfoHelper' import useTheme from 'hooks/useTheme' import { RevertIconWrapper } from 'pages/Earns/PositionDetail/styles' import PriceRange from 'pages/Earns/UserPositions/PriceRange' import { PositionValueWrapper } from 'pages/Earns/UserPositions/styles' -import { CustomBox } from 'pages/Earns/components/SmartExit/styles' +import { CustomBox, Divider } from 'pages/Earns/components/SmartExit/styles' import { ParsedPosition } from 'pages/Earns/types' import { ExternalLink } from 'theme' import { formatDisplayNumber } from 'utils/numbers' diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx index 066b53a98e..a52131caf8 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/index.tsx @@ -3,9 +3,9 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { X } from 'react-feather' import { Flex, Text } from 'rebass' -import Divider from 'components/Divider' import Modal from 'components/Modal' import { TIMES_IN_SECS } from 'constants/index' +import useTheme from 'hooks/useTheme' import PositionDetailHeader from 'pages/Earns/PositionDetail/Header' import Actions from 'pages/Earns/components/SmartExit/Actions' import Confirmation from 'pages/Earns/components/SmartExit/Confirmation' @@ -14,7 +14,7 @@ import GasSetting from 'pages/Earns/components/SmartExit/GasSetting' import Metrics from 'pages/Earns/components/SmartExit/Metrics' import PoolPrice from 'pages/Earns/components/SmartExit/PoolPrice' import PositionLiquidity from 'pages/Earns/components/SmartExit/PositionLiquidity' -import { ContentWrapper } from 'pages/Earns/components/SmartExit/styles' +import { ContentWrapper, Divider } from 'pages/Earns/components/SmartExit/styles' import { useSmartExit } from 'pages/Earns/components/SmartExit/useSmartExit' import { defaultFeeYieldCondition } from 'pages/Earns/components/SmartExit/utils' import { @@ -29,6 +29,7 @@ import { } from 'pages/Earns/types' export const SmartExit = ({ position, onDismiss }: { position: ParsedPosition; onDismiss: () => void }) => { + const theme = useTheme() const [selectedMetrics, setSelectedMetrics] = useState([ { metric: Metric.FeeYield, condition: defaultFeeYieldCondition }, ]) @@ -117,7 +118,7 @@ export const SmartExit = ({ position, onDismiss }: { position: ParsedPosition; o return ( - + {showConfirm ? ( theme.border}; + border: 1px solid ${({ theme }) => theme.tableHeader}; padding: 12px; flex-direction: column; - gap: 0.5rem; + gap: 0.75rem; display: flex; ` @@ -29,13 +31,34 @@ export const CustomInput = styled(Input)` border-radius: 12px; font-size: 16px; color: ${({ theme }) => theme.text}; + background-color: ${({ theme }) => rgba(theme.text, 0.04)}; flex: 1; ` export const CustomSelect = styled(Select)` width: 100%; - padding: 4px 16px; + padding: 10px 16px; font-size: 14px; color: ${({ theme }) => theme.text}; + background-color: ${({ theme }) => rgba(theme.text, 0.04)}; flex: 1; ` + +export const Divider = styled(Box)` + height: 1px; + background-color: ${({ theme }) => theme.tableHeader}; +` + +export const CustomOption = styled(Box)<{ isSelected?: boolean }>` + border-radius: 24px; + border: 1px solid ${({ theme, isSelected }) => (isSelected ? rgba(theme.primary, 0.2) : rgba(theme.text, 0.08))}; + background-color: ${({ theme, isSelected }) => (isSelected ? rgba(theme.primary, 0.2) : 'transparent')}; + padding: 4px 12px; + color: ${({ theme, isSelected }) => (isSelected ? theme.white2 : '#737373')}; + font-size: 12px; + font-weight: 500; + cursor: pointer; + &:hover { + background-color: ${({ theme }) => rgba(theme.primary, 0.1)}; + } +` diff --git a/packages/ui/src/components/ui/radio-group.tsx b/packages/ui/src/components/ui/radio-group.tsx index 569282739f..ddd0a48247 100644 --- a/packages/ui/src/components/ui/radio-group.tsx +++ b/packages/ui/src/components/ui/radio-group.tsx @@ -29,7 +29,7 @@ const RadioGroupItem = React.forwardRef< {...props} > - + ); From 2130368691d46337d978d1058271f5e4fc8b6cf1 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 4 Dec 2025 17:33:12 +0700 Subject: [PATCH 82/87] fix gas setting ui --- .../Earns/components/SmartExit/GasSetting.tsx | 41 ++++--------------- 1 file changed, 8 insertions(+), 33 deletions(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx index 0ba9ccfa73..6504709f0d 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/GasSetting.tsx @@ -2,11 +2,12 @@ import { Trans, t } from '@lingui/macro' import { rgba } from 'polished' import { useState } from 'react' import { ChevronDown } from 'react-feather' -import { Box, Flex, Text } from 'rebass' +import { Flex, Text } from 'rebass' import Input from 'components/NumericalInput' import { DropdownIcon } from 'components/SwapForm/SlippageSetting' import useTheme from 'hooks/useTheme' +import { CustomOption } from 'pages/Earns/components/SmartExit/styles' import { SmartExitFee } from 'pages/Earns/types' import { formatDisplayNumber } from 'utils/numbers' @@ -80,54 +81,28 @@ export default function GasSetting({ {[1, 1.5, 2, 3].map(item => { const isSelected = !customGasPercent && multiplier === item return ( - { setCustomGasPercent('') setMultiplier(item) }} - sx={{ - borderRadius: '999px', - border: `1px solid ${isSelected ? theme.primary : theme.border}`, - backgroundColor: isSelected ? theme.primary + '20' : 'transparent', - padding: '6px 4px', - color: isSelected ? theme.primary : theme.subText, - fontSize: '12px', - fontWeight: '500', - cursor: 'pointer', - textAlign: 'center', - flex: 1, - '&:hover': { - backgroundColor: theme.primary + '10', - }, - }} + isSelected={isSelected} > {formatDisplayNumber(item * (feeInfo?.gas.percentage || 0), { significantDigits: 2 })}% - + ) })} {/* Custom option */} - % - + From 6860380efc7e743ccf12f880502cd397d8774677 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 4 Dec 2025 18:16:44 +0700 Subject: [PATCH 83/87] fix price input --- .../SmartExit/Metrics/PriceInput.tsx | 204 ++++++++++++++---- .../Earns/components/SmartExit/styles.tsx | 32 +++ 2 files changed, 197 insertions(+), 39 deletions(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx index 58e69a42e0..a733faed54 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx @@ -1,11 +1,14 @@ -import { nearestUsableTick, priceToClosestTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' +import { MAX_TICK, MIN_TICK, nearestUsableTick, priceToClosestTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' import UniswapPriceSlider from '@kyberswap/price-slider' import '@kyberswap/price-slider/style.css' import { Trans } from '@lingui/macro' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { ChevronDown, Minus, Plus } from 'react-feather' import { Flex, Text } from 'rebass' -import { CustomInput } from 'pages/Earns/components/SmartExit/styles' +import { DropdownIcon } from 'components/SwapForm/SlippageSetting' +import useTheme from 'hooks/useTheme' +import { CustomPriceInput, PriceInputIcon, PriceInputWrapper } from 'pages/Earns/components/SmartExit/styles' import { Metric, ParsedPosition, PriceCondition, SelectedMetric } from 'pages/Earns/types' export default function PriceInput({ @@ -17,8 +20,10 @@ export default function PriceInput({ setMetric: (value: SelectedMetric) => void position: ParsedPosition }) { + const theme = useTheme() const priceCondition = metric.condition as PriceCondition + const [priceSliderExpanded, setPriceSliderExpanded] = useState(false) const [lowerTick, setLowerTickState] = useState() const [upperTick, setUpperTickState] = useState() @@ -142,51 +147,172 @@ export default function PriceInput({ } }, [priceCondition?.lte, priceToTick, upperTick]) + const onDecreaseMinPrice = useCallback(() => { + if (lowerTick === undefined) return + const newLowerTick = lowerTick - position.pool.tickSpacing + if (newLowerTick < MIN_TICK) return + const price = tickToPrice(newLowerTick, position.token0.decimals, position.token1.decimals, false) + setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) + setInputMinPrice(price) + }, [ + lowerTick, + metric, + position.pool.tickSpacing, + position.token0.decimals, + position.token1.decimals, + priceCondition, + setMetric, + ]) + + const onIncreaseMinPrice = useCallback(() => { + if (lowerTick === undefined) return + const newLowerTick = lowerTick + position.pool.tickSpacing + if (newLowerTick > MAX_TICK) return + const price = tickToPrice(newLowerTick, position.token0.decimals, position.token1.decimals, false) + setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) + setInputMinPrice(price) + }, [ + lowerTick, + metric, + position.pool.tickSpacing, + position.token0.decimals, + position.token1.decimals, + priceCondition, + setMetric, + ]) + + const onDecreaseMaxPrice = useCallback(() => { + if (upperTick === undefined) return + const newUpperTick = upperTick - position.pool.tickSpacing + if (newUpperTick < MIN_TICK) return + const price = tickToPrice(newUpperTick, position.token0.decimals, position.token1.decimals, false) + setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) + setInputMaxPrice(price) + }, [ + upperTick, + metric, + position.pool.tickSpacing, + position.token0.decimals, + position.token1.decimals, + priceCondition, + setMetric, + ]) + + const onIncreaseMaxPrice = useCallback(() => { + if (upperTick === undefined) return + const newUpperTick = upperTick + position.pool.tickSpacing + if (newUpperTick > MAX_TICK) return + const price = tickToPrice(newUpperTick, position.token0.decimals, position.token1.decimals, false) + setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) + setInputMaxPrice(price) + }, [ + upperTick, + metric, + position.pool.tickSpacing, + position.token0.decimals, + position.token1.decimals, + priceCondition, + setMetric, + ]) + + const wrappedCorrectPrice = (value: string, type: 'lower' | 'upper') => { + const tick = priceToClosestTick(value, position.token0.decimals, position.token1.decimals, false) + if (tick !== undefined) { + const correctedTick = + tick % position.pool.tickSpacing === 0 ? tick : nearestUsableTick(tick, position.pool.tickSpacing) + const correctedPrice = tickToPrice(correctedTick, position.token0.decimals, position.token1.decimals, false) + if (type === 'lower') { + setInputMinPrice(correctedPrice) + } else { + setInputMaxPrice(correctedPrice) + } + } + } + return ( <> Exit when the pool price is between - - { - const value = e.target.value - // Only allow numbers and decimal point - if (/^\d*\.?\d*$/.test(value)) { - setInputMinPrice(value) - } - }} - /> - - - { - const value = e.target.value - // Only allow numbers and decimal point - if (/^\d*\.?\d*$/.test(value)) { - setInputMaxPrice(value) - } - }} - /> - - {position.token0.symbol}/{position.token1.symbol} + + + + + + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + setInputMinPrice(value) + } + }} + onBlur={e => wrappedCorrectPrice(e.target.value, 'lower')} + /> + + + + + + + + + + { + const value = e.target.value + // Only allow numbers and decimal point + if (/^\d*\.?\d*$/.test(value)) { + setInputMaxPrice(value) + } + }} + onBlur={e => wrappedCorrectPrice(e.target.value, 'upper')} + /> + + + + + + + setPriceSliderExpanded(e => !e)} + marginTop={2} + style={{ cursor: 'default', position: 'relative' }} + > + + {position.token1.symbol} per {position.token0.symbol} + + + - + > + + ) } diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx index ad1dda271d..5bdf282fce 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx @@ -62,3 +62,35 @@ export const CustomOption = styled(Box)<{ isSelected?: boolean }>` background-color: ${({ theme }) => rgba(theme.primary, 0.1)}; } ` + +export const PriceInputWrapper = styled.div` + display: flex; + align-items: center; + justify-content: space-around; + padding: 8px; + gap: 10px; + border-radius: 12px; + background-color: ${({ theme }) => rgba(theme.text, 0.04)}; + cursor: pointer; +` + +export const PriceInputIcon = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 4px; + background-color: ${({ theme }) => rgba(theme.text, 0.08)}; +` + +export const CustomPriceInput = styled(Input)` + border: none; + padding: 0; + border-radius: 0; + font-size: 16px; + color: ${({ theme }) => theme.text}; + background-color: transparent; + flex: 1; + text-align: center; +` From 1920732fc3879164830f3267861660b54d016120 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 4 Dec 2025 18:24:25 +0700 Subject: [PATCH 84/87] fix: format price number --- .../SmartExit/Metrics/PriceInput.tsx | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx index a733faed54..85a1344308 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx @@ -1,3 +1,4 @@ +import { formatNumber } from '@kyber/utils/dist/number' import { MAX_TICK, MIN_TICK, nearestUsableTick, priceToClosestTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' import UniswapPriceSlider from '@kyberswap/price-slider' import '@kyberswap/price-slider/style.css' @@ -100,7 +101,10 @@ export default function PriceInput({ changeSourceRef.current = 'slider' setLowerTickState(tick) if (tick !== undefined) { - const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals, false) + const price = formatNumber( + Number(tickToPrice(tick, position.token0.decimals, position.token1.decimals, false)), + 6, + ) setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) } // Reset source after React batch update @@ -116,7 +120,10 @@ export default function PriceInput({ changeSourceRef.current = 'slider' setUpperTickState(tick) if (tick !== undefined) { - const price = tickToPrice(tick, position.token0.decimals, position.token1.decimals, false) + const price = formatNumber( + Number(tickToPrice(tick, position.token0.decimals, position.token1.decimals, false)), + 6, + ) setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) } setTimeout(() => { @@ -151,7 +158,10 @@ export default function PriceInput({ if (lowerTick === undefined) return const newLowerTick = lowerTick - position.pool.tickSpacing if (newLowerTick < MIN_TICK) return - const price = tickToPrice(newLowerTick, position.token0.decimals, position.token1.decimals, false) + const price = formatNumber( + Number(tickToPrice(newLowerTick, position.token0.decimals, position.token1.decimals, false)), + 6, + ) setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) setInputMinPrice(price) }, [ @@ -168,7 +178,10 @@ export default function PriceInput({ if (lowerTick === undefined) return const newLowerTick = lowerTick + position.pool.tickSpacing if (newLowerTick > MAX_TICK) return - const price = tickToPrice(newLowerTick, position.token0.decimals, position.token1.decimals, false) + const price = formatNumber( + Number(tickToPrice(newLowerTick, position.token0.decimals, position.token1.decimals, false)), + 6, + ) setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) setInputMinPrice(price) }, [ @@ -185,7 +198,10 @@ export default function PriceInput({ if (upperTick === undefined) return const newUpperTick = upperTick - position.pool.tickSpacing if (newUpperTick < MIN_TICK) return - const price = tickToPrice(newUpperTick, position.token0.decimals, position.token1.decimals, false) + const price = formatNumber( + Number(tickToPrice(newUpperTick, position.token0.decimals, position.token1.decimals, false)), + 6, + ) setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) setInputMaxPrice(price) }, [ @@ -202,7 +218,10 @@ export default function PriceInput({ if (upperTick === undefined) return const newUpperTick = upperTick + position.pool.tickSpacing if (newUpperTick > MAX_TICK) return - const price = tickToPrice(newUpperTick, position.token0.decimals, position.token1.decimals, false) + const price = formatNumber( + Number(tickToPrice(newUpperTick, position.token0.decimals, position.token1.decimals, false)), + 6, + ) setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) setInputMaxPrice(price) }, [ @@ -222,9 +241,9 @@ export default function PriceInput({ tick % position.pool.tickSpacing === 0 ? tick : nearestUsableTick(tick, position.pool.tickSpacing) const correctedPrice = tickToPrice(correctedTick, position.token0.decimals, position.token1.decimals, false) if (type === 'lower') { - setInputMinPrice(correctedPrice) + setInputMinPrice(formatNumber(Number(correctedPrice), 6)) } else { - setInputMaxPrice(correctedPrice) + setInputMaxPrice(formatNumber(Number(correctedPrice), 6)) } } } From d207bf80d0d016d46d68789ff68c8ddea6e5020d Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 4 Dec 2025 22:25:52 +0700 Subject: [PATCH 85/87] fix: format price --- .../SmartExit/Metrics/PriceInput.tsx | 52 +++++------ .../Earns/components/SmartExit/styles.tsx | 2 + packages/utils/src/index.ts | 1 + packages/utils/src/number.ts | 90 +++++++++++++++++++ 4 files changed, 119 insertions(+), 26 deletions(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx index 85a1344308..636202ed01 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Metrics/PriceInput.tsx @@ -1,4 +1,4 @@ -import { formatNumber } from '@kyber/utils/dist/number' +import { formatNumberBySignificantDigits } from '@kyber/utils/dist/number' import { MAX_TICK, MIN_TICK, nearestUsableTick, priceToClosestTick, tickToPrice } from '@kyber/utils/dist/uniswapv3' import UniswapPriceSlider from '@kyberswap/price-slider' import '@kyberswap/price-slider/style.css' @@ -101,11 +101,11 @@ export default function PriceInput({ changeSourceRef.current = 'slider' setLowerTickState(tick) if (tick !== undefined) { - const price = formatNumber( - Number(tickToPrice(tick, position.token0.decimals, position.token1.decimals, false)), + const price = formatNumberBySignificantDigits( + tickToPrice(tick, position.token0.decimals, position.token1.decimals, false), 6, ) - setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) + setMetric({ ...metric, condition: { ...priceCondition, gte: price.toString() } }) } // Reset source after React batch update setTimeout(() => { @@ -120,11 +120,11 @@ export default function PriceInput({ changeSourceRef.current = 'slider' setUpperTickState(tick) if (tick !== undefined) { - const price = formatNumber( - Number(tickToPrice(tick, position.token0.decimals, position.token1.decimals, false)), + const price = formatNumberBySignificantDigits( + tickToPrice(tick, position.token0.decimals, position.token1.decimals, false), 6, ) - setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) + setMetric({ ...metric, condition: { ...priceCondition, lte: price.toString() } }) } setTimeout(() => { changeSourceRef.current = null @@ -158,12 +158,12 @@ export default function PriceInput({ if (lowerTick === undefined) return const newLowerTick = lowerTick - position.pool.tickSpacing if (newLowerTick < MIN_TICK) return - const price = formatNumber( - Number(tickToPrice(newLowerTick, position.token0.decimals, position.token1.decimals, false)), + const price = formatNumberBySignificantDigits( + tickToPrice(newLowerTick, position.token0.decimals, position.token1.decimals, false), 6, ) - setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) - setInputMinPrice(price) + setMetric({ ...metric, condition: { ...priceCondition, gte: price.toString() } }) + setInputMinPrice(price.toString()) }, [ lowerTick, metric, @@ -178,12 +178,12 @@ export default function PriceInput({ if (lowerTick === undefined) return const newLowerTick = lowerTick + position.pool.tickSpacing if (newLowerTick > MAX_TICK) return - const price = formatNumber( - Number(tickToPrice(newLowerTick, position.token0.decimals, position.token1.decimals, false)), + const price = formatNumberBySignificantDigits( + tickToPrice(newLowerTick, position.token0.decimals, position.token1.decimals, false), 6, ) - setMetric({ ...metric, condition: { ...priceCondition, gte: price } }) - setInputMinPrice(price) + setMetric({ ...metric, condition: { ...priceCondition, gte: price.toString() } }) + setInputMinPrice(price.toString()) }, [ lowerTick, metric, @@ -198,12 +198,12 @@ export default function PriceInput({ if (upperTick === undefined) return const newUpperTick = upperTick - position.pool.tickSpacing if (newUpperTick < MIN_TICK) return - const price = formatNumber( - Number(tickToPrice(newUpperTick, position.token0.decimals, position.token1.decimals, false)), + const price = formatNumberBySignificantDigits( + tickToPrice(newUpperTick, position.token0.decimals, position.token1.decimals, false), 6, ) - setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) - setInputMaxPrice(price) + setMetric({ ...metric, condition: { ...priceCondition, lte: price.toString() } }) + setInputMaxPrice(price.toString()) }, [ upperTick, metric, @@ -218,12 +218,12 @@ export default function PriceInput({ if (upperTick === undefined) return const newUpperTick = upperTick + position.pool.tickSpacing if (newUpperTick > MAX_TICK) return - const price = formatNumber( - Number(tickToPrice(newUpperTick, position.token0.decimals, position.token1.decimals, false)), + const price = formatNumberBySignificantDigits( + tickToPrice(newUpperTick, position.token0.decimals, position.token1.decimals, false), 6, ) - setMetric({ ...metric, condition: { ...priceCondition, lte: price } }) - setInputMaxPrice(price) + setMetric({ ...metric, condition: { ...priceCondition, lte: price.toString() } }) + setInputMaxPrice(price.toString()) }, [ upperTick, metric, @@ -241,9 +241,9 @@ export default function PriceInput({ tick % position.pool.tickSpacing === 0 ? tick : nearestUsableTick(tick, position.pool.tickSpacing) const correctedPrice = tickToPrice(correctedTick, position.token0.decimals, position.token1.decimals, false) if (type === 'lower') { - setInputMinPrice(formatNumber(Number(correctedPrice), 6)) + setInputMinPrice(formatNumberBySignificantDigits(correctedPrice, 6).toString()) } else { - setInputMaxPrice(formatNumber(Number(correctedPrice), 6)) + setInputMaxPrice(formatNumberBySignificantDigits(correctedPrice, 6).toString()) } } } @@ -301,7 +301,7 @@ export default function PriceInput({ justifyContent="center" onClick={() => setPriceSliderExpanded(e => !e)} marginTop={2} - style={{ cursor: 'default', position: 'relative' }} + style={{ cursor: 'default', position: 'relative', userSelect: 'none' }} > {position.token1.symbol} per {position.token0.symbol} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx index 5bdf282fce..2512d4a31b 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/styles.tsx @@ -72,6 +72,7 @@ export const PriceInputWrapper = styled.div` border-radius: 12px; background-color: ${({ theme }) => rgba(theme.text, 0.04)}; cursor: pointer; + user-select: none; ` export const PriceInputIcon = styled.div` @@ -82,6 +83,7 @@ export const PriceInputIcon = styled.div` height: 20px; border-radius: 4px; background-color: ${({ theme }) => rgba(theme.text, 0.08)}; + user-select: none; ` export const CustomPriceInput = styled(Input)` diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index cbc09fac7c..f17383ed55 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -39,3 +39,4 @@ export * from './liquidity/price-impact'; export * from './liquidity/zap'; export * from './services'; export * from './error'; +export * from './number'; diff --git a/packages/utils/src/number.ts b/packages/utils/src/number.ts index 6ff216eabb..73d5abbdfb 100644 --- a/packages/utils/src/number.ts +++ b/packages/utils/src/number.ts @@ -219,3 +219,93 @@ export const formatWei = (value?: string, decimals?: number) => { return '--'; }; + +/** + * Format a number based on significant digits (with truncation) + * @param value - The number or string to format + * @param significantDigits - Number of significant digits to display + * @returns Formatted number with the specified number of significant digits + */ +export function formatNumberBySignificantDigits(value: string | number, significantDigits: number): number { + // Convert input to number + const num = typeof value === 'string' ? parseFloat(value) : value; + + // Handle invalid numbers + if (isNaN(num) || !isFinite(num)) { + return 0; + } + + // Handle zero + if (num === 0) { + return 0; + } + + // Handle negative numbers + const isNegative = num < 0; + const absNum = Math.abs(num); + + // Convert to string to work with significant digits + const numStr = absNum.toString(); + + // Handle scientific notation + let plainStr: string; + if (numStr.includes('e')) { + plainStr = absNum.toFixed(20); + } else { + plainStr = numStr; + } + + // Split into parts + const parts = plainStr.split('.'); + const integerPart = parts[0]; + const decimalPart = parts[1] || ''; + + let result = ''; + let sigDigitsCount = 0; + + // Process integer part + let foundNonZero = false; + for (let i = 0; i < integerPart.length; i++) { + const digit = integerPart[i]; + result += digit; + + // Count significant digits (skip leading zeros) + if (digit !== '0' || foundNonZero) { + foundNonZero = true; + sigDigitsCount++; + } + + if (sigDigitsCount >= significantDigits) { + // Convert to number and return + return isNegative ? -parseFloat(result) : parseFloat(result); + } + } + + // If we have decimal part and still need more significant digits + if (decimalPart && sigDigitsCount < significantDigits) { + result += '.'; + + for (let i = 0; i < decimalPart.length; i++) { + const digit = decimalPart[i]; + + // Count significant digits (after decimal, zeros count if we found non-zero) + if (digit !== '0' || foundNonZero) { + foundNonZero = true; + sigDigitsCount++; + } + + result += digit; + + if (sigDigitsCount >= significantDigits) { + break; + } + } + } + + // Remove trailing zeros and decimal point if necessary + if (result.includes('.')) { + result = result.replace(/\.?0+$/, ''); + } + + return isNegative ? -parseFloat(result) : parseFloat(result); +} From a54fc1eb2e36530da11b88c0f0d41bec57395750 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 4 Dec 2025 22:36:54 +0700 Subject: [PATCH 86/87] fix: confirmation ui --- .../SmartExit/Confirmation/Condition.tsx | 11 ++-- .../SmartExit/Confirmation/ExpectedAmount.tsx | 51 ++++++++++++------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Condition.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Condition.tsx index 672d73e1ee..3aac5860fa 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Condition.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/Condition.tsx @@ -1,5 +1,6 @@ import { Trans } from '@lingui/macro' import dayjs from 'dayjs' +import { rgba } from 'polished' import { Box, Flex, Text } from 'rebass' import TokenLogo from 'components/TokenLogo' @@ -38,14 +39,18 @@ export default function Condition({ return ( <> - Exit - + Remove + - + {position.token0.symbol}/{position.token1.symbol} Fee {position?.pool.fee}% diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx index 78a2a24ace..96e808f9cf 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx @@ -1,4 +1,5 @@ import { Trans } from '@lingui/macro' +import { rgba } from 'polished' import { useMemo } from 'react' import { Box, Flex, Text } from 'rebass' @@ -59,47 +60,59 @@ export default function ExpectedAmount({ Estimated balance when exit - - - + + + Min - - - + + + {formatDisplayNumber(expectedAmounts.minAmount0, { significantDigits: 6 })} {position.token0.symbol} - - - + + + {formatDisplayNumber(expectedAmounts.minAmount1, { significantDigits: 6 })} {position.token1.symbol} - - + + Max - - - + + + {formatDisplayNumber(expectedAmounts.maxAmount0, { significantDigits: 6 })} {position.token0.symbol} - - - + + + {formatDisplayNumber(expectedAmounts.maxAmount1, { significantDigits: 6 })} {position.token1.symbol} From f4a81d9b2b8d7418460cc8c243b53107779cce7f Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 4 Dec 2025 22:46:33 +0700 Subject: [PATCH 87/87] fix: responsive --- .../src/components/Modal/index.tsx | 5 ++++- .../src/pages/Earns/components/SmartExit/Actions.tsx | 6 +++++- .../SmartExit/Confirmation/ExpectedAmount.tsx | 9 ++++++++- .../src/pages/Earns/components/SmartExit/index.tsx | 12 ++++++++++-- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/apps/kyberswap-interface/src/components/Modal/index.tsx b/apps/kyberswap-interface/src/components/Modal/index.tsx index 8a825c2468..9d25ae3916 100644 --- a/apps/kyberswap-interface/src/components/Modal/index.tsx +++ b/apps/kyberswap-interface/src/components/Modal/index.tsx @@ -45,7 +45,7 @@ const StyledDialogContent = styled( margin: ${({ margin }) => margin || '0 0 2rem 0'}; background-color: ${({ theme, bgColor }) => bgColor || theme.tableHeader}; box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadow1)}; - padding: 0; + padding: ${({ padding }) => padding || '0'}; width: ${({ width }) => width || '50vw'}; height: ${({ height }) => height || 'auto'}; overflow-y: scroll; @@ -104,6 +104,7 @@ export interface ModalProps { bgColor?: string zindex?: number | string margin?: string + padding?: string enableInitialFocusInput?: boolean className?: string children?: React.ReactNode @@ -121,6 +122,7 @@ export default function Modal({ }, minHeight = false, margin = '', + padding = '', maxHeight = 90, maxWidth = 420, width, @@ -173,6 +175,7 @@ export default function Modal({ maxHeight={maxHeight} maxWidth={maxWidth} margin={margin} + padding={padding} width={width} height={height} bgColor={bgColor} diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Actions.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Actions.tsx index 438e215ce8..b6d3f3978f 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Actions.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Actions.tsx @@ -1,7 +1,9 @@ import { Trans } from '@lingui/macro' +import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' import { ButtonOutlined, ButtonPrimary } from 'components/Button' +import { MEDIA_WIDTHS } from 'theme' export default function Actions({ onDismiss, @@ -14,8 +16,10 @@ export default function Actions({ disabled: boolean feeLoading: boolean }) { + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) + return ( - + Cancel diff --git a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx index 96e808f9cf..c875684391 100644 --- a/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx +++ b/apps/kyberswap-interface/src/pages/Earns/components/SmartExit/Confirmation/ExpectedAmount.tsx @@ -1,12 +1,14 @@ import { Trans } from '@lingui/macro' import { rgba } from 'polished' import { useMemo } from 'react' +import { useMedia } from 'react-use' import { Box, Flex, Text } from 'rebass' import TokenLogo from 'components/TokenLogo' import useTheme from 'hooks/useTheme' import { calculateExpectedAmounts } from 'pages/Earns/components/SmartExit/Confirmation/calculateExpectedAmounts' import { ConditionType, Metric, ParsedPosition, PriceCondition, SelectedMetric } from 'pages/Earns/types' +import { MEDIA_WIDTHS } from 'theme' import { formatDisplayNumber } from 'utils/numbers' export default function ExpectedAmount({ @@ -19,6 +21,7 @@ export default function ExpectedAmount({ conditionType: ConditionType }) { const theme = useTheme() + const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) const expectedAmounts = useMemo(() => { // Check if any metric is pool price @@ -69,7 +72,11 @@ export default function ExpectedAmount({ Estimated balance when exit - + - + + {showConfirm ? (