@loafmarkets/ui 0.1.415 → 0.1.416
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +87 -2
- package/dist/index.d.ts +87 -2
- package/dist/index.js +1818 -578
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1591 -355
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import
|
|
1
|
+
import * as React9 from 'react';
|
|
2
|
+
import React9__default, { createContext, useState, useRef, useMemo, useCallback, useEffect, useLayoutEffect, useContext } from 'react';
|
|
3
3
|
import { Slot } from '@radix-ui/react-slot';
|
|
4
4
|
import { cva } from 'class-variance-authority';
|
|
5
5
|
import { clsx } from 'clsx';
|
|
6
6
|
import { twMerge } from 'tailwind-merge';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
-
import styled10, { keyframes, css } from 'styled-components';
|
|
9
8
|
import * as ReactDOM from 'react-dom';
|
|
10
9
|
import { createPortal } from 'react-dom';
|
|
10
|
+
import styled10, { keyframes, css } from 'styled-components';
|
|
11
11
|
import { Newspaper } from 'lucide-react';
|
|
12
12
|
import * as LightweightCharts from 'lightweight-charts';
|
|
13
13
|
import { MdKingBed, MdBathtub, MdDirectionsCar, MdPool, MdYard, MdFitnessCenter } from 'react-icons/md';
|
|
@@ -54,7 +54,7 @@ var buttonVariants = cva(
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
);
|
|
57
|
-
var Button =
|
|
57
|
+
var Button = React9.forwardRef(
|
|
58
58
|
({ className, variant, size, radius, asChild = false, ...props }, ref) => {
|
|
59
59
|
const Comp = asChild ? Slot : "button";
|
|
60
60
|
const coercedRadius = radius ?? (variant === "onboarding" || variant === "onboardingOutline" ? "square" : void 0);
|
|
@@ -93,11 +93,11 @@ var badgeVariants = cva(
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
);
|
|
96
|
-
var Badge =
|
|
96
|
+
var Badge = React9.forwardRef(
|
|
97
97
|
({ className, variant, size, ...props }, ref) => /* @__PURE__ */ jsx("span", { ref, className: cn(badgeVariants({ variant, size }), className), ...props })
|
|
98
98
|
);
|
|
99
99
|
Badge.displayName = "Badge";
|
|
100
|
-
var Card =
|
|
100
|
+
var Card = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
101
101
|
"div",
|
|
102
102
|
{
|
|
103
103
|
ref,
|
|
@@ -109,11 +109,11 @@ var Card = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ j
|
|
|
109
109
|
}
|
|
110
110
|
));
|
|
111
111
|
Card.displayName = "Card";
|
|
112
|
-
var CardHeader =
|
|
112
|
+
var CardHeader = React9.forwardRef(
|
|
113
113
|
({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex flex-col space-y-1.5 p-6", className), ...props })
|
|
114
114
|
);
|
|
115
115
|
CardHeader.displayName = "CardHeader";
|
|
116
|
-
var CardTitle =
|
|
116
|
+
var CardTitle = React9.forwardRef(
|
|
117
117
|
({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
118
118
|
"h3",
|
|
119
119
|
{
|
|
@@ -124,15 +124,15 @@ var CardTitle = React5.forwardRef(
|
|
|
124
124
|
)
|
|
125
125
|
);
|
|
126
126
|
CardTitle.displayName = "CardTitle";
|
|
127
|
-
var CardDescription =
|
|
127
|
+
var CardDescription = React9.forwardRef(
|
|
128
128
|
({ className, ...props }, ref) => /* @__PURE__ */ jsx("p", { ref, className: cn("text-sm text-slate-500", className), ...props })
|
|
129
129
|
);
|
|
130
130
|
CardDescription.displayName = "CardDescription";
|
|
131
|
-
var CardContent =
|
|
131
|
+
var CardContent = React9.forwardRef(
|
|
132
132
|
({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("px-6 pb-6 pt-2 text-sm text-slate-600", className), ...props })
|
|
133
133
|
);
|
|
134
134
|
CardContent.displayName = "CardContent";
|
|
135
|
-
var CardFooter =
|
|
135
|
+
var CardFooter = React9.forwardRef(
|
|
136
136
|
({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex items-center border-t border-slate-100 px-6 py-4", className), ...props })
|
|
137
137
|
);
|
|
138
138
|
CardFooter.displayName = "CardFooter";
|
|
@@ -150,7 +150,7 @@ var defaultFormatSignedCurrency = (value) => {
|
|
|
150
150
|
const sign = value >= 0 ? "+" : "-";
|
|
151
151
|
return `${sign}${defaultFormatCurrency(Math.abs(value))}`;
|
|
152
152
|
};
|
|
153
|
-
var PortfolioSummary =
|
|
153
|
+
var PortfolioSummary = React9.forwardRef(
|
|
154
154
|
({
|
|
155
155
|
availableCash,
|
|
156
156
|
portfolioValue,
|
|
@@ -300,23 +300,23 @@ function HousePositionSlider({
|
|
|
300
300
|
className,
|
|
301
301
|
...props
|
|
302
302
|
}) {
|
|
303
|
-
const [orderMode, setOrderMode] =
|
|
304
|
-
const [buyTrackingMode, setBuyTrackingMode] =
|
|
305
|
-
const [deltaDollars, setDeltaDollars] =
|
|
306
|
-
const [deltaTokensBuy, setDeltaTokensBuy] =
|
|
307
|
-
const [deltaTokensSell, setDeltaTokensSell] =
|
|
308
|
-
const [_isDragging, setIsDragging] =
|
|
309
|
-
const [visualTargetPct, setVisualTargetPct] =
|
|
310
|
-
const [orderType, setOrderType] =
|
|
311
|
-
const [limitPrice, setLimitPrice] =
|
|
312
|
-
const [limitPriceInput, setLimitPriceInput] =
|
|
313
|
-
const [limitPriceDirty, setLimitPriceDirty] =
|
|
314
|
-
const [ownershipInput, setOwnershipInput] =
|
|
315
|
-
const [tokenAmountInput, setTokenAmountInput] =
|
|
316
|
-
const houseRef =
|
|
303
|
+
const [orderMode, setOrderMode] = React9.useState("none");
|
|
304
|
+
const [buyTrackingMode, setBuyTrackingMode] = React9.useState("dollars");
|
|
305
|
+
const [deltaDollars, setDeltaDollars] = React9.useState(0);
|
|
306
|
+
const [deltaTokensBuy, setDeltaTokensBuy] = React9.useState(0);
|
|
307
|
+
const [deltaTokensSell, setDeltaTokensSell] = React9.useState(0);
|
|
308
|
+
const [_isDragging, setIsDragging] = React9.useState(false);
|
|
309
|
+
const [visualTargetPct, setVisualTargetPct] = React9.useState(null);
|
|
310
|
+
const [orderType, setOrderType] = React9.useState(defaultOrderType);
|
|
311
|
+
const [limitPrice, setLimitPrice] = React9.useState(currentPrice);
|
|
312
|
+
const [limitPriceInput, setLimitPriceInput] = React9.useState(currentPrice.toFixed(2));
|
|
313
|
+
const [limitPriceDirty, setLimitPriceDirty] = React9.useState(false);
|
|
314
|
+
const [ownershipInput, setOwnershipInput] = React9.useState("");
|
|
315
|
+
const [tokenAmountInput, setTokenAmountInput] = React9.useState("");
|
|
316
|
+
const houseRef = React9.useRef(null);
|
|
317
317
|
const asks = orderbook?.asks ?? [];
|
|
318
318
|
const bids = orderbook?.bids ?? [];
|
|
319
|
-
|
|
319
|
+
React9.useEffect(() => {
|
|
320
320
|
if (orderType !== "limit") return;
|
|
321
321
|
if (limitPriceDirty) return;
|
|
322
322
|
setLimitPrice(currentPrice);
|
|
@@ -391,7 +391,7 @@ function HousePositionSlider({
|
|
|
391
391
|
const impliedDisplayTargetOwnership = clamp(targetOwnership + ownershipShift, 0, 100);
|
|
392
392
|
const displayTargetOwnership = visualTargetPct ?? impliedDisplayTargetOwnership;
|
|
393
393
|
const estFeeTokens = Math.abs(deltaValue) * 5e-3 / (effectivePrice || 1);
|
|
394
|
-
const resetOrder =
|
|
394
|
+
const resetOrder = React9.useCallback(() => {
|
|
395
395
|
setOrderMode("none");
|
|
396
396
|
setBuyTrackingMode("dollars");
|
|
397
397
|
setDeltaDollars(0);
|
|
@@ -399,7 +399,7 @@ function HousePositionSlider({
|
|
|
399
399
|
setDeltaTokensSell(0);
|
|
400
400
|
setVisualTargetPct(null);
|
|
401
401
|
}, []);
|
|
402
|
-
const updateOrderFromTargetValue =
|
|
402
|
+
const updateOrderFromTargetValue = React9.useCallback(
|
|
403
403
|
(newTargetValue) => {
|
|
404
404
|
const newDeltaValue = newTargetValue - holdingsValue;
|
|
405
405
|
if (newDeltaValue > 0) {
|
|
@@ -423,7 +423,7 @@ function HousePositionSlider({
|
|
|
423
423
|
},
|
|
424
424
|
[effectiveAvailableCash, effectivePrice, effectiveTokensHeld, holdingsValue, resetOrder, tokensHeld]
|
|
425
425
|
);
|
|
426
|
-
const updateOrderFromOwnership =
|
|
426
|
+
const updateOrderFromOwnership = React9.useCallback(
|
|
427
427
|
(newOwnershipPercent) => {
|
|
428
428
|
const nextOwnership = clamp(newOwnershipPercent, 0, 100);
|
|
429
429
|
const newTargetTokens = nextOwnership / 100 * totalTokens;
|
|
@@ -432,7 +432,7 @@ function HousePositionSlider({
|
|
|
432
432
|
},
|
|
433
433
|
[effectivePrice, totalTokens, updateOrderFromTargetValue]
|
|
434
434
|
);
|
|
435
|
-
const updateOrderFromTokenAmount =
|
|
435
|
+
const updateOrderFromTokenAmount = React9.useCallback(
|
|
436
436
|
(tokenAmountSigned) => {
|
|
437
437
|
if (tokenAmountSigned > 0) {
|
|
438
438
|
const maxTokens = effectiveAvailableCash / (effectivePrice || 1);
|
|
@@ -455,7 +455,7 @@ function HousePositionSlider({
|
|
|
455
455
|
},
|
|
456
456
|
[effectiveAvailableCash, effectivePrice, effectiveTokensHeld, resetOrder]
|
|
457
457
|
);
|
|
458
|
-
const updateOrderFromSlider =
|
|
458
|
+
const updateOrderFromSlider = React9.useCallback(
|
|
459
459
|
(pct) => {
|
|
460
460
|
const normalized = (pct - 50) / 50;
|
|
461
461
|
const magnitude = Math.min(Math.abs(normalized), 1);
|
|
@@ -499,7 +499,7 @@ function HousePositionSlider({
|
|
|
499
499
|
},
|
|
500
500
|
[effectiveAvailableCash, effectiveTokensHeld, resetOrder]
|
|
501
501
|
);
|
|
502
|
-
const handleDragAtClientY =
|
|
502
|
+
const handleDragAtClientY = React9.useCallback(
|
|
503
503
|
(clientY) => {
|
|
504
504
|
if (!houseRef.current) return;
|
|
505
505
|
const rect = houseRef.current.getBoundingClientRect();
|
|
@@ -1015,18 +1015,18 @@ function HousePositionSliderMobile({
|
|
|
1015
1015
|
className,
|
|
1016
1016
|
...props
|
|
1017
1017
|
}) {
|
|
1018
|
-
const [orderMode, setOrderMode] =
|
|
1019
|
-
const [deltaDollars, setDeltaDollars] =
|
|
1020
|
-
const [deltaTokensSell, setDeltaTokensSell] =
|
|
1021
|
-
const [deltaTokensBuy, setDeltaTokensBuy] =
|
|
1022
|
-
const [buyTrackingMode, setBuyTrackingMode] =
|
|
1023
|
-
const [orderType, setOrderType] =
|
|
1024
|
-
const [limitPrice, setLimitPrice] =
|
|
1025
|
-
const [limitPriceInput, setLimitPriceInput] =
|
|
1026
|
-
const [limitPriceDirty, setLimitPriceDirty] =
|
|
1027
|
-
const [tokenAmountInput, setTokenAmountInput] =
|
|
1028
|
-
const houseRef =
|
|
1029
|
-
|
|
1018
|
+
const [orderMode, setOrderMode] = React9.useState("none");
|
|
1019
|
+
const [deltaDollars, setDeltaDollars] = React9.useState(0);
|
|
1020
|
+
const [deltaTokensSell, setDeltaTokensSell] = React9.useState(0);
|
|
1021
|
+
const [deltaTokensBuy, setDeltaTokensBuy] = React9.useState(0);
|
|
1022
|
+
const [buyTrackingMode, setBuyTrackingMode] = React9.useState("dollars");
|
|
1023
|
+
const [orderType, setOrderType] = React9.useState(defaultOrderType);
|
|
1024
|
+
const [limitPrice, setLimitPrice] = React9.useState(currentPrice);
|
|
1025
|
+
const [limitPriceInput, setLimitPriceInput] = React9.useState(currentPrice.toFixed(2));
|
|
1026
|
+
const [limitPriceDirty, setLimitPriceDirty] = React9.useState(false);
|
|
1027
|
+
const [tokenAmountInput, setTokenAmountInput] = React9.useState("");
|
|
1028
|
+
const houseRef = React9.useRef(null);
|
|
1029
|
+
React9.useEffect(() => {
|
|
1030
1030
|
if (orderType !== "limit") return;
|
|
1031
1031
|
if (limitPriceDirty) return;
|
|
1032
1032
|
setLimitPrice(currentPrice);
|
|
@@ -1068,7 +1068,7 @@ function HousePositionSliderMobile({
|
|
|
1068
1068
|
const hasChange = orderMode !== "none" && (Math.abs(deltaTokens) > 1e-3 || Math.abs(deltaValue) > 0.01);
|
|
1069
1069
|
const targetOwnership = totalTokens > 0 ? targetTokens / totalTokens * 100 : 0;
|
|
1070
1070
|
const estFeeTokens = effectivePrice > 0 ? Math.abs(deltaValue) * 5e-3 / effectivePrice : 0;
|
|
1071
|
-
const updateOrderFromTargetValue =
|
|
1071
|
+
const updateOrderFromTargetValue = React9.useCallback(
|
|
1072
1072
|
(newTargetValue) => {
|
|
1073
1073
|
const newDeltaValue = newTargetValue - holdingsValue;
|
|
1074
1074
|
if (newDeltaValue > 0.01) {
|
|
@@ -1096,7 +1096,7 @@ function HousePositionSliderMobile({
|
|
|
1096
1096
|
},
|
|
1097
1097
|
[holdingsValue, availableCash, effectivePrice, tokensHeld]
|
|
1098
1098
|
);
|
|
1099
|
-
const updateOrderFromTokenAmount =
|
|
1099
|
+
const updateOrderFromTokenAmount = React9.useCallback(
|
|
1100
1100
|
(tokenAmount) => {
|
|
1101
1101
|
if (tokenAmount > 0) {
|
|
1102
1102
|
const maxTokens = effectivePrice > 0 ? availableCash / effectivePrice : 0;
|
|
@@ -1123,14 +1123,14 @@ function HousePositionSliderMobile({
|
|
|
1123
1123
|
},
|
|
1124
1124
|
[effectivePrice, availableCash, tokensHeld]
|
|
1125
1125
|
);
|
|
1126
|
-
const handleMarkerClick =
|
|
1126
|
+
const handleMarkerClick = React9.useCallback(
|
|
1127
1127
|
(pct) => {
|
|
1128
1128
|
const newTargetValue = pct / 100 * totalCapacity;
|
|
1129
1129
|
updateOrderFromTargetValue(newTargetValue);
|
|
1130
1130
|
},
|
|
1131
1131
|
[totalCapacity, updateOrderFromTargetValue]
|
|
1132
1132
|
);
|
|
1133
|
-
const onMouseDown =
|
|
1133
|
+
const onMouseDown = React9.useCallback(
|
|
1134
1134
|
(e) => {
|
|
1135
1135
|
e.preventDefault();
|
|
1136
1136
|
const move = (ev) => {
|
|
@@ -1150,7 +1150,7 @@ function HousePositionSliderMobile({
|
|
|
1150
1150
|
},
|
|
1151
1151
|
[totalCapacity, updateOrderFromTargetValue]
|
|
1152
1152
|
);
|
|
1153
|
-
const onTouchStart =
|
|
1153
|
+
const onTouchStart = React9.useCallback(
|
|
1154
1154
|
(e) => {
|
|
1155
1155
|
e.preventDefault();
|
|
1156
1156
|
e.stopPropagation();
|
|
@@ -1630,7 +1630,7 @@ var StatusDot = styled10.span`
|
|
|
1630
1630
|
background: rgba(255, 255, 255, 0.3);
|
|
1631
1631
|
`}
|
|
1632
1632
|
`;
|
|
1633
|
-
var LoafLiquidityBadge =
|
|
1633
|
+
var LoafLiquidityBadge = React9.forwardRef(
|
|
1634
1634
|
({ className, isGlowing, status = "unknown", onClick, children, ...props }, ref) => {
|
|
1635
1635
|
return /* @__PURE__ */ jsxs(
|
|
1636
1636
|
LogoContainer,
|
|
@@ -1654,7 +1654,7 @@ var LoafLiquidityBadge = React5.forwardRef(
|
|
|
1654
1654
|
);
|
|
1655
1655
|
LoafLiquidityBadge.displayName = "LoafLiquidityBadge";
|
|
1656
1656
|
var toSize = (v, fallback) => v == null ? fallback : typeof v === "number" ? `${v}px` : v;
|
|
1657
|
-
var Skeleton =
|
|
1657
|
+
var Skeleton = React9.forwardRef(
|
|
1658
1658
|
({ width, height, radius, className, style, ...props }, ref) => {
|
|
1659
1659
|
return /* @__PURE__ */ jsx(
|
|
1660
1660
|
"div",
|
|
@@ -1678,9 +1678,17 @@ var Skeleton = React5.forwardRef(
|
|
|
1678
1678
|
}
|
|
1679
1679
|
);
|
|
1680
1680
|
Skeleton.displayName = "Skeleton";
|
|
1681
|
+
var DEFAULT_PRICE_LABEL = "Price (USD)";
|
|
1682
|
+
var PRICE_UNIT_MIN_WIDTH_PX = 320;
|
|
1683
|
+
function priceHeaderLabel(label, panelWidth) {
|
|
1684
|
+
if (label === DEFAULT_PRICE_LABEL && panelWidth != null && panelWidth > 0 && panelWidth < PRICE_UNIT_MIN_WIDTH_PX) {
|
|
1685
|
+
return "Price";
|
|
1686
|
+
}
|
|
1687
|
+
return label;
|
|
1688
|
+
}
|
|
1681
1689
|
function useViewportCompact(breakpoint) {
|
|
1682
|
-
const [isCompact, setIsCompact] =
|
|
1683
|
-
|
|
1690
|
+
const [isCompact, setIsCompact] = React9.useState(false);
|
|
1691
|
+
React9.useEffect(() => {
|
|
1684
1692
|
if (typeof window === "undefined") return;
|
|
1685
1693
|
const check = () => setIsCompact(window.innerWidth <= breakpoint);
|
|
1686
1694
|
check();
|
|
@@ -1695,9 +1703,14 @@ var formatTradeTime = (time) => {
|
|
|
1695
1703
|
if (Number.isNaN(d.getTime())) return time;
|
|
1696
1704
|
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false });
|
|
1697
1705
|
};
|
|
1698
|
-
var FLASH_DURATION_MS =
|
|
1699
|
-
var FLASH_UP_COLOR = "rgba(14, 203, 129, 0.
|
|
1700
|
-
var FLASH_DOWN_COLOR = "rgba(246, 70, 93, 0.
|
|
1706
|
+
var FLASH_DURATION_MS = 900;
|
|
1707
|
+
var FLASH_UP_COLOR = "rgba(14, 203, 129, 0.22)";
|
|
1708
|
+
var FLASH_DOWN_COLOR = "rgba(246, 70, 93, 0.22)";
|
|
1709
|
+
var FLASH_KEYFRAMES = (color) => [
|
|
1710
|
+
{ backgroundColor: "rgba(0,0,0,0)" },
|
|
1711
|
+
{ backgroundColor: color, offset: 0.3 },
|
|
1712
|
+
{ backgroundColor: "rgba(0,0,0,0)" }
|
|
1713
|
+
];
|
|
1701
1714
|
function getTradeKey(trade, fallbackIndex) {
|
|
1702
1715
|
if (trade.tradeId != null) return `id-${trade.tradeId}`;
|
|
1703
1716
|
if (trade.time != null) return `t-${trade.time}-${trade.type}-${trade.price}-${trade.amount}`;
|
|
@@ -1706,14 +1719,11 @@ function getTradeKey(trade, fallbackIndex) {
|
|
|
1706
1719
|
function flashElement(node, side) {
|
|
1707
1720
|
if (typeof node.animate !== "function") return;
|
|
1708
1721
|
const color = side === "bid" ? FLASH_UP_COLOR : FLASH_DOWN_COLOR;
|
|
1709
|
-
node.animate(
|
|
1710
|
-
[{ backgroundColor: color }, { backgroundColor: "transparent" }],
|
|
1711
|
-
{ duration: FLASH_DURATION_MS, easing: "ease-out" }
|
|
1712
|
-
);
|
|
1722
|
+
node.animate(FLASH_KEYFRAMES(color), { duration: FLASH_DURATION_MS, easing: "ease-in-out" });
|
|
1713
1723
|
}
|
|
1714
1724
|
function useAmountChangeFlash(ref, amount, side, flashOnMount) {
|
|
1715
|
-
const prevAmountRef =
|
|
1716
|
-
|
|
1725
|
+
const prevAmountRef = React9.useRef(flashOnMount ? null : amount);
|
|
1726
|
+
React9.useEffect(() => {
|
|
1717
1727
|
const prev = prevAmountRef.current;
|
|
1718
1728
|
const node = ref.current;
|
|
1719
1729
|
if (node && prev !== amount) {
|
|
@@ -1734,18 +1744,15 @@ function TradeRow({
|
|
|
1734
1744
|
compact,
|
|
1735
1745
|
explorerUrl
|
|
1736
1746
|
}) {
|
|
1737
|
-
const rowRef =
|
|
1747
|
+
const rowRef = React9.useRef(null);
|
|
1738
1748
|
const { type } = trade;
|
|
1739
|
-
|
|
1749
|
+
React9.useEffect(() => {
|
|
1740
1750
|
if (seenTradeKeysRef.current.has(tradeKey)) return;
|
|
1741
1751
|
seenTradeKeysRef.current.add(tradeKey);
|
|
1742
1752
|
const node = rowRef.current;
|
|
1743
1753
|
if (!node || typeof node.animate !== "function") return;
|
|
1744
1754
|
const color = type === "buy" ? FLASH_UP_COLOR : FLASH_DOWN_COLOR;
|
|
1745
|
-
node.animate(
|
|
1746
|
-
[{ backgroundColor: color }, { backgroundColor: "transparent" }],
|
|
1747
|
-
{ duration: FLASH_DURATION_MS, easing: "ease-out" }
|
|
1748
|
-
);
|
|
1755
|
+
node.animate(FLASH_KEYFRAMES(color), { duration: FLASH_DURATION_MS, easing: "ease-in-out" });
|
|
1749
1756
|
}, [tradeKey, seenTradeKeysRef, type]);
|
|
1750
1757
|
if (compact) {
|
|
1751
1758
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1812,6 +1819,27 @@ function TradeRow({
|
|
|
1812
1819
|
}
|
|
1813
1820
|
);
|
|
1814
1821
|
}
|
|
1822
|
+
function BreadSliceGlyph() {
|
|
1823
|
+
return /* @__PURE__ */ jsx(
|
|
1824
|
+
"svg",
|
|
1825
|
+
{
|
|
1826
|
+
width: "10",
|
|
1827
|
+
height: "10",
|
|
1828
|
+
viewBox: "0 0 10 10",
|
|
1829
|
+
"aria-hidden": "true",
|
|
1830
|
+
style: { display: "block", filter: "drop-shadow(0 0 4px rgba(201,162,39,0.6))" },
|
|
1831
|
+
children: /* @__PURE__ */ jsx(
|
|
1832
|
+
"path",
|
|
1833
|
+
{
|
|
1834
|
+
d: "M1 4.2 C1 1.9 2.7 1 5 1 C7.3 1 9 1.9 9 4.2 L9 8.2 C9 8.6 8.6 9 8.2 9 L1.8 9 C1.4 9 1 8.6 1 8.2 Z",
|
|
1835
|
+
fill: "none",
|
|
1836
|
+
stroke: "#C9A227",
|
|
1837
|
+
strokeWidth: "1.3"
|
|
1838
|
+
}
|
|
1839
|
+
)
|
|
1840
|
+
}
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1815
1843
|
function DepthRow({
|
|
1816
1844
|
side,
|
|
1817
1845
|
price,
|
|
@@ -1826,12 +1854,14 @@ function DepthRow({
|
|
|
1826
1854
|
fillHeight
|
|
1827
1855
|
}) {
|
|
1828
1856
|
const isAsk = side === "ask";
|
|
1829
|
-
const rowRef =
|
|
1857
|
+
const rowRef = React9.useRef(null);
|
|
1830
1858
|
useAmountChangeFlash(rowRef, amount, side, flashOnMount);
|
|
1831
1859
|
return /* @__PURE__ */ jsxs(
|
|
1832
1860
|
"div",
|
|
1833
1861
|
{
|
|
1834
1862
|
ref: rowRef,
|
|
1863
|
+
"data-depth-row": true,
|
|
1864
|
+
"data-price": `$${formatNumber(price, precision)}`,
|
|
1835
1865
|
className: "relative grid grid-cols-3 items-center gap-3 px-3 text-[0.8rem]",
|
|
1836
1866
|
style: fillHeight ? { flex: "1 1 0", minHeight: 0, maxHeight: `${DEPTH_ROW_HEIGHT_PX}px`, cursor: onPriceClick ? "pointer" : void 0 } : { height: `${DEPTH_ROW_HEIGHT_PX}px`, flexShrink: 0, cursor: onPriceClick ? "pointer" : void 0 },
|
|
1837
1867
|
onClick: onPriceClick ? () => onPriceClick(price) : void 0,
|
|
@@ -1839,14 +1869,20 @@ function DepthRow({
|
|
|
1839
1869
|
/* @__PURE__ */ jsx(
|
|
1840
1870
|
"div",
|
|
1841
1871
|
{
|
|
1872
|
+
"data-depth-bar": true,
|
|
1842
1873
|
className: cn(isAsk ? "bg-rose-900/30" : "bg-emerald-900/30"),
|
|
1843
1874
|
style: {
|
|
1844
1875
|
position: "absolute",
|
|
1845
|
-
top:
|
|
1846
|
-
bottom:
|
|
1876
|
+
top: 1,
|
|
1877
|
+
bottom: 1,
|
|
1847
1878
|
left: 0,
|
|
1848
1879
|
width: `${clamp3(depthPct, 0, 100)}%`,
|
|
1849
|
-
transition: "width 300ms ease-out"
|
|
1880
|
+
transition: "width 300ms ease-out",
|
|
1881
|
+
borderRadius: "0 4px 4px 0",
|
|
1882
|
+
// backgroundImage LONGHAND, not the `background` shorthand — jsdom's
|
|
1883
|
+
// cssstyle drops gradients from the shorthand (tests assert this).
|
|
1884
|
+
backgroundColor: "transparent",
|
|
1885
|
+
backgroundImage: isAsk ? "linear-gradient(90deg, rgba(246,70,93,0.11), rgba(246,70,93,0.15))" : "linear-gradient(90deg, rgba(14,203,129,0.09), rgba(14,203,129,0.13))"
|
|
1850
1886
|
}
|
|
1851
1887
|
}
|
|
1852
1888
|
),
|
|
@@ -1854,33 +1890,35 @@ function DepthRow({
|
|
|
1854
1890
|
"span",
|
|
1855
1891
|
{
|
|
1856
1892
|
"aria-label": "Your order at this price",
|
|
1857
|
-
className: "absolute left-0 top-1/2 -translate-y-1/2 z-[2]
|
|
1893
|
+
className: "absolute left-0 top-1/2 -translate-y-1/2 z-[2]",
|
|
1894
|
+
children: /* @__PURE__ */ jsx(BreadSliceGlyph, {})
|
|
1858
1895
|
}
|
|
1859
1896
|
) : null,
|
|
1860
|
-
/* @__PURE__ */ jsxs("div", { className: cn("relative z-[1] tabular-nums", isAsk ? "text-[#f6465d]" : "text-[#0ecb81]"), children: [
|
|
1897
|
+
/* @__PURE__ */ jsxs("div", { "data-cell": "price", className: cn("relative z-[1] tabular-nums", isAsk ? "text-[#f6465d]" : "text-[#0ecb81]"), children: [
|
|
1861
1898
|
"$",
|
|
1862
1899
|
formatNumber(price, precision)
|
|
1863
1900
|
] }),
|
|
1864
|
-
/* @__PURE__ */ jsx("div", { className: "relative z-[1] text-right tabular-nums text-white/90", children: formatNumber(amount, amountPrecision) }),
|
|
1865
|
-
/* @__PURE__ */ jsx("div", { className: "relative z-[1] text-right tabular-nums text-white/50", children: formatNumber(cumDepth, amountPrecision) })
|
|
1901
|
+
/* @__PURE__ */ jsx("div", { "data-cell": "amt", className: "relative z-[1] text-right tabular-nums text-white/90", children: formatNumber(amount, amountPrecision) }),
|
|
1902
|
+
/* @__PURE__ */ jsx("div", { "data-cell": "tot", className: "relative z-[1] text-right tabular-nums text-white/50", children: formatNumber(cumDepth, amountPrecision) })
|
|
1866
1903
|
]
|
|
1867
1904
|
}
|
|
1868
1905
|
);
|
|
1869
1906
|
}
|
|
1870
1907
|
var clamp3 = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
1908
|
+
var fitLevelCount = (availableHeightPx, max) => clamp3(Math.floor(availableHeightPx / DEPTH_ROW_HEIGHT_PX), 1, max);
|
|
1871
1909
|
var LEVEL_ROWS_VISIBLE = 8;
|
|
1872
1910
|
var DEPTH_ROW_HEIGHT_PX = 28;
|
|
1873
1911
|
var COMPACT_ROWS_VISIBLE = 5;
|
|
1874
1912
|
var COMPACT_ROW_HEIGHT_PX = 30;
|
|
1875
1913
|
var COMPACT_BREAKPOINT_PX = 1024;
|
|
1876
|
-
var Orderbook =
|
|
1914
|
+
var Orderbook = React9.forwardRef(
|
|
1877
1915
|
({
|
|
1878
1916
|
asks,
|
|
1879
1917
|
bids,
|
|
1880
1918
|
midPrice,
|
|
1881
1919
|
midChangePercent,
|
|
1882
1920
|
trades = [],
|
|
1883
|
-
priceLabel =
|
|
1921
|
+
priceLabel = DEFAULT_PRICE_LABEL,
|
|
1884
1922
|
amountLabel = "Amount",
|
|
1885
1923
|
precision = 2,
|
|
1886
1924
|
amountPrecision = 2,
|
|
@@ -1908,12 +1946,12 @@ var Orderbook = React5.forwardRef(
|
|
|
1908
1946
|
status: liquidityStatus
|
|
1909
1947
|
}
|
|
1910
1948
|
);
|
|
1911
|
-
const [tab, setTabState] =
|
|
1949
|
+
const [tab, setTabState] = React9.useState(defaultTab);
|
|
1912
1950
|
const setTab = setTabState;
|
|
1913
1951
|
const effectiveTab = showTradesTab ? tab : "orderbook";
|
|
1914
|
-
const [tradeFilter, setTradeFilter] =
|
|
1952
|
+
const [tradeFilter, setTradeFilter] = React9.useState("all");
|
|
1915
1953
|
const viewportCompact = useViewportCompact(COMPACT_BREAKPOINT_PX);
|
|
1916
|
-
const { userAskPrices, userBidPrices } =
|
|
1954
|
+
const { userAskPrices, userBidPrices } = React9.useMemo(() => {
|
|
1917
1955
|
const ask = /* @__PURE__ */ new Set();
|
|
1918
1956
|
const bid = /* @__PURE__ */ new Set();
|
|
1919
1957
|
if (!userOrderPrices) return { userAskPrices: ask, userBidPrices: bid };
|
|
@@ -1923,15 +1961,15 @@ var Orderbook = React5.forwardRef(
|
|
|
1923
1961
|
}
|
|
1924
1962
|
return { userAskPrices: ask, userBidPrices: bid };
|
|
1925
1963
|
}, [userOrderPrices]);
|
|
1926
|
-
const seenTradeKeysRef =
|
|
1927
|
-
const initializedRef =
|
|
1964
|
+
const seenTradeKeysRef = React9.useRef(/* @__PURE__ */ new Set());
|
|
1965
|
+
const initializedRef = React9.useRef(false);
|
|
1928
1966
|
if (!initializedRef.current && trades.length > 0) {
|
|
1929
1967
|
for (let i = 0; i < trades.length; i++) {
|
|
1930
1968
|
seenTradeKeysRef.current.add(getTradeKey(trades[i], i));
|
|
1931
1969
|
}
|
|
1932
1970
|
initializedRef.current = true;
|
|
1933
1971
|
}
|
|
1934
|
-
|
|
1972
|
+
React9.useEffect(() => {
|
|
1935
1973
|
if (!initializedRef.current) return;
|
|
1936
1974
|
const live = /* @__PURE__ */ new Set();
|
|
1937
1975
|
for (let i = 0; i < trades.length; i++) {
|
|
@@ -1942,7 +1980,7 @@ var Orderbook = React5.forwardRef(
|
|
|
1942
1980
|
if (!live.has(key)) seen.delete(key);
|
|
1943
1981
|
}
|
|
1944
1982
|
}, [trades]);
|
|
1945
|
-
|
|
1983
|
+
React9.useEffect(() => {
|
|
1946
1984
|
setTab(defaultTab);
|
|
1947
1985
|
}, [defaultTab]);
|
|
1948
1986
|
const handleTab = (next) => {
|
|
@@ -1950,10 +1988,49 @@ var Orderbook = React5.forwardRef(
|
|
|
1950
1988
|
onTabChange?.(next);
|
|
1951
1989
|
};
|
|
1952
1990
|
const isCompact = variant === "compact" || variant === "auto" && viewportCompact;
|
|
1991
|
+
const rootRef = React9.useRef(null);
|
|
1992
|
+
const setRootRef = React9.useCallback(
|
|
1993
|
+
(node) => {
|
|
1994
|
+
rootRef.current = node;
|
|
1995
|
+
if (typeof ref === "function") ref(node);
|
|
1996
|
+
else if (ref) ref.current = node;
|
|
1997
|
+
},
|
|
1998
|
+
[ref]
|
|
1999
|
+
);
|
|
2000
|
+
const [panelWidth, setPanelWidth] = React9.useState(null);
|
|
2001
|
+
React9.useEffect(() => {
|
|
2002
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
2003
|
+
const el = rootRef.current;
|
|
2004
|
+
if (!el) return;
|
|
2005
|
+
const ro = new ResizeObserver((entries) => {
|
|
2006
|
+
const w = entries[0]?.contentRect?.width;
|
|
2007
|
+
if (typeof w === "number") setPanelWidth((prev) => prev === w ? prev : w);
|
|
2008
|
+
});
|
|
2009
|
+
ro.observe(el);
|
|
2010
|
+
return () => ro.disconnect();
|
|
2011
|
+
}, []);
|
|
2012
|
+
const effectivePriceLabel = priceHeaderLabel(priceLabel, panelWidth);
|
|
1953
2013
|
const effectiveLevels = maxLevels ?? LEVEL_ROWS_VISIBLE;
|
|
1954
2014
|
const effectiveCompactLevels = maxLevels ?? COMPACT_ROWS_VISIBLE;
|
|
1955
2015
|
const sectionHeight = isCompact ? effectiveCompactLevels * COMPACT_ROW_HEIGHT_PX : effectiveLevels * DEPTH_ROW_HEIGHT_PX;
|
|
1956
|
-
const
|
|
2016
|
+
const asksSectionRef = React9.useRef(null);
|
|
2017
|
+
const [depthSectionH, setDepthSectionH] = React9.useState(null);
|
|
2018
|
+
React9.useEffect(() => {
|
|
2019
|
+
if (!fillHeight || isCompact || effectiveTab !== "orderbook") return;
|
|
2020
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
2021
|
+
const el = asksSectionRef.current;
|
|
2022
|
+
if (!el) return;
|
|
2023
|
+
const ro = new ResizeObserver((entries) => {
|
|
2024
|
+
const h = entries[0]?.contentRect?.height;
|
|
2025
|
+
if (typeof h === "number") {
|
|
2026
|
+
setDepthSectionH((prev) => prev === h ? prev : h);
|
|
2027
|
+
}
|
|
2028
|
+
});
|
|
2029
|
+
ro.observe(el);
|
|
2030
|
+
return () => ro.disconnect();
|
|
2031
|
+
}, [fillHeight, isCompact, effectiveTab]);
|
|
2032
|
+
const fitRows = fillHeight && !isCompact && depthSectionH != null ? fitLevelCount(depthSectionH, effectiveLevels) : null;
|
|
2033
|
+
const rowCount = isCompact ? effectiveCompactLevels : fitRows ?? effectiveLevels;
|
|
1957
2034
|
const askVisibleLevels = asks.slice(-rowCount);
|
|
1958
2035
|
const bidVisibleLevels = bids.slice(0, rowCount);
|
|
1959
2036
|
const askCumDepths = new Array(askVisibleLevels.length);
|
|
@@ -1974,9 +2051,9 @@ var Orderbook = React5.forwardRef(
|
|
|
1974
2051
|
}
|
|
1975
2052
|
const combinedMaxCumDepth = Math.max(1, ...askCumDepths, ...bidCumDepths);
|
|
1976
2053
|
const askMaxCumDepth = combinedMaxCumDepth;
|
|
1977
|
-
const prevAskPricesRef =
|
|
1978
|
-
const prevBidPricesRef =
|
|
1979
|
-
const newAskPrices =
|
|
2054
|
+
const prevAskPricesRef = React9.useRef(null);
|
|
2055
|
+
const prevBidPricesRef = React9.useRef(null);
|
|
2056
|
+
const newAskPrices = React9.useMemo(() => {
|
|
1980
2057
|
const current = new Set(askVisibleLevels.map((l) => l.price));
|
|
1981
2058
|
const prev = prevAskPricesRef.current;
|
|
1982
2059
|
if (!prev) return /* @__PURE__ */ new Set();
|
|
@@ -1986,7 +2063,7 @@ var Orderbook = React5.forwardRef(
|
|
|
1986
2063
|
}
|
|
1987
2064
|
return added;
|
|
1988
2065
|
}, [askVisibleLevels]);
|
|
1989
|
-
const newBidPrices =
|
|
2066
|
+
const newBidPrices = React9.useMemo(() => {
|
|
1990
2067
|
const current = new Set(bidVisibleLevels.map((l) => l.price));
|
|
1991
2068
|
const prev = prevBidPricesRef.current;
|
|
1992
2069
|
if (!prev) return /* @__PURE__ */ new Set();
|
|
@@ -1996,12 +2073,16 @@ var Orderbook = React5.forwardRef(
|
|
|
1996
2073
|
}
|
|
1997
2074
|
return added;
|
|
1998
2075
|
}, [bidVisibleLevels]);
|
|
1999
|
-
|
|
2076
|
+
React9.useEffect(() => {
|
|
2000
2077
|
prevAskPricesRef.current = new Set(askVisibleLevels.map((l) => l.price));
|
|
2001
2078
|
prevBidPricesRef.current = new Set(bidVisibleLevels.map((l) => l.price));
|
|
2002
2079
|
}, [askVisibleLevels, bidVisibleLevels]);
|
|
2003
2080
|
const bidMaxCumDepth = combinedMaxCumDepth;
|
|
2004
2081
|
const midClass = midChangePercent == null ? "text-white" : midChangePercent >= 0 ? "text-[#0ecb81]" : "text-[#f6465d]";
|
|
2082
|
+
const bestAskPrice = asks.length > 0 ? asks[asks.length - 1].price : null;
|
|
2083
|
+
const bestBidPrice = bids.length > 0 ? bids[0].price : null;
|
|
2084
|
+
const spread = bestAskPrice != null && bestBidPrice != null ? bestAskPrice - bestBidPrice : null;
|
|
2085
|
+
const spreadPct = spread != null && midPrice > 0 ? spread / midPrice * 100 : null;
|
|
2005
2086
|
const tradeFiltered = trades.filter((t) => tradeFilter === "all" || t.type === tradeFilter);
|
|
2006
2087
|
const layoutProps = {
|
|
2007
2088
|
tab: effectiveTab,
|
|
@@ -2010,7 +2091,7 @@ var Orderbook = React5.forwardRef(
|
|
|
2010
2091
|
tradeFilter,
|
|
2011
2092
|
setTradeFilter,
|
|
2012
2093
|
rightHeader: resolvedRightHeader,
|
|
2013
|
-
priceLabel,
|
|
2094
|
+
priceLabel: effectivePriceLabel,
|
|
2014
2095
|
amountLabel,
|
|
2015
2096
|
tradeFiltered,
|
|
2016
2097
|
precision,
|
|
@@ -2037,12 +2118,15 @@ var Orderbook = React5.forwardRef(
|
|
|
2037
2118
|
compactLevelCount: effectiveCompactLevels,
|
|
2038
2119
|
fillHeight,
|
|
2039
2120
|
newAskPrices,
|
|
2040
|
-
newBidPrices
|
|
2121
|
+
newBidPrices,
|
|
2122
|
+
spread,
|
|
2123
|
+
spreadPct,
|
|
2124
|
+
asksSectionRef
|
|
2041
2125
|
};
|
|
2042
2126
|
return /* @__PURE__ */ jsx(
|
|
2043
2127
|
Card,
|
|
2044
2128
|
{
|
|
2045
|
-
ref,
|
|
2129
|
+
ref: setRootRef,
|
|
2046
2130
|
className: cn(
|
|
2047
2131
|
"w-full max-w-[520px] overflow-hidden rounded-[12px] border border-white/10 bg-[#111111] text-white shadow-md flex flex-col",
|
|
2048
2132
|
isCompact && "max-w-none",
|
|
@@ -2056,6 +2140,170 @@ var Orderbook = React5.forwardRef(
|
|
|
2056
2140
|
}
|
|
2057
2141
|
);
|
|
2058
2142
|
Orderbook.displayName = "Orderbook";
|
|
2143
|
+
var MID_ROW_GOLD_STYLE = {
|
|
2144
|
+
backgroundImage: "linear-gradient(90deg, rgba(230,200,126,0.07), rgba(230,200,126,0.015))",
|
|
2145
|
+
borderTop: "1px solid rgba(230,200,126,0.22)",
|
|
2146
|
+
borderBottom: "1px solid rgba(230,200,126,0.22)"
|
|
2147
|
+
};
|
|
2148
|
+
function SpreadReadout({
|
|
2149
|
+
spread,
|
|
2150
|
+
spreadPct,
|
|
2151
|
+
precision: _precision,
|
|
2152
|
+
compact
|
|
2153
|
+
}) {
|
|
2154
|
+
if (spread == null || spreadPct == null) return null;
|
|
2155
|
+
return /* @__PURE__ */ jsxs(
|
|
2156
|
+
"div",
|
|
2157
|
+
{
|
|
2158
|
+
style: {
|
|
2159
|
+
display: "flex",
|
|
2160
|
+
alignItems: "baseline",
|
|
2161
|
+
gap: 5,
|
|
2162
|
+
whiteSpace: "nowrap",
|
|
2163
|
+
lineHeight: 1
|
|
2164
|
+
},
|
|
2165
|
+
children: [
|
|
2166
|
+
/* @__PURE__ */ jsx(
|
|
2167
|
+
"span",
|
|
2168
|
+
{
|
|
2169
|
+
style: {
|
|
2170
|
+
fontSize: compact ? "0.48rem" : "0.52rem",
|
|
2171
|
+
letterSpacing: "0.14em",
|
|
2172
|
+
textTransform: "uppercase",
|
|
2173
|
+
color: "rgba(255,255,255,0.45)",
|
|
2174
|
+
fontWeight: 600
|
|
2175
|
+
},
|
|
2176
|
+
children: "Spread"
|
|
2177
|
+
}
|
|
2178
|
+
),
|
|
2179
|
+
/* @__PURE__ */ jsxs(
|
|
2180
|
+
"span",
|
|
2181
|
+
{
|
|
2182
|
+
className: "tabular-nums",
|
|
2183
|
+
style: { fontSize: compact ? "0.56rem" : "0.62rem", color: "rgba(255,255,255,0.45)" },
|
|
2184
|
+
children: [
|
|
2185
|
+
spreadPct.toFixed(2),
|
|
2186
|
+
"%"
|
|
2187
|
+
]
|
|
2188
|
+
}
|
|
2189
|
+
)
|
|
2190
|
+
]
|
|
2191
|
+
}
|
|
2192
|
+
);
|
|
2193
|
+
}
|
|
2194
|
+
function useGlidePill() {
|
|
2195
|
+
const sectionRef = React9.useRef(null);
|
|
2196
|
+
const pillRef = React9.useRef(null);
|
|
2197
|
+
const priceSpanRef = React9.useRef(null);
|
|
2198
|
+
const boldedCellRef = React9.useRef(null);
|
|
2199
|
+
const [pillPrice, setPillPrice] = React9.useState(null);
|
|
2200
|
+
const motion = React9.useRef({ x: 0, targetX: 0, y: 0, targetY: 0, amp: 0, on: false, raf: 0 });
|
|
2201
|
+
const reducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
2202
|
+
const unbold = () => {
|
|
2203
|
+
if (boldedCellRef.current) {
|
|
2204
|
+
boldedCellRef.current.style.fontWeight = "";
|
|
2205
|
+
boldedCellRef.current = null;
|
|
2206
|
+
}
|
|
2207
|
+
};
|
|
2208
|
+
const frame = React9.useCallback(() => {
|
|
2209
|
+
const m = motion.current;
|
|
2210
|
+
const section = sectionRef.current;
|
|
2211
|
+
const pill = pillRef.current;
|
|
2212
|
+
m.raf = 0;
|
|
2213
|
+
if (!section || !pill) return;
|
|
2214
|
+
const k = reducedMotion ? 1 : 0.12;
|
|
2215
|
+
m.x += (m.targetX - m.x) * k;
|
|
2216
|
+
m.y += (m.targetY - m.y) * k;
|
|
2217
|
+
pill.style.transform = `translate(${m.x.toFixed(1)}px, ${m.y.toFixed(1)}px)`;
|
|
2218
|
+
const ampTarget = m.on && !reducedMotion ? 1 : 0;
|
|
2219
|
+
m.amp += (ampTarget - m.amp) * 0.14;
|
|
2220
|
+
const crest = m.y + pill.offsetHeight / 2;
|
|
2221
|
+
const rows = section.querySelectorAll("[data-depth-row]");
|
|
2222
|
+
for (const row of rows) {
|
|
2223
|
+
const rect = row.getBoundingClientRect();
|
|
2224
|
+
const center = rect.top + rect.height / 2;
|
|
2225
|
+
const d = center - crest;
|
|
2226
|
+
const inf = Math.exp(-(d * d) / (2 * 42 * 42)) * m.amp;
|
|
2227
|
+
const bar = row.querySelector("[data-depth-bar]");
|
|
2228
|
+
const priceCell = row.querySelector('[data-cell="price"]');
|
|
2229
|
+
if (inf < 0.01) {
|
|
2230
|
+
if (row.dataset.waved) {
|
|
2231
|
+
delete row.dataset.waved;
|
|
2232
|
+
row.style.zIndex = "";
|
|
2233
|
+
row.style.background = "";
|
|
2234
|
+
row.style.boxShadow = "";
|
|
2235
|
+
if (bar) bar.style.filter = "";
|
|
2236
|
+
if (priceCell) priceCell.style.transform = "";
|
|
2237
|
+
}
|
|
2238
|
+
continue;
|
|
2239
|
+
}
|
|
2240
|
+
row.dataset.waved = "1";
|
|
2241
|
+
row.style.zIndex = String(3 + Math.round(inf * 10));
|
|
2242
|
+
row.style.background = `rgba(19,20,24,${(inf * 0.92).toFixed(2)})`;
|
|
2243
|
+
row.style.boxShadow = `0 ${(inf * 3).toFixed(1)}px ${(inf * 12).toFixed(1)}px rgba(0,0,0,${(inf * 0.45).toFixed(2)})`;
|
|
2244
|
+
if (bar) bar.style.filter = `brightness(${(1 + inf * 0.45).toFixed(2)})`;
|
|
2245
|
+
if (priceCell) {
|
|
2246
|
+
priceCell.style.transformOrigin = "left center";
|
|
2247
|
+
priceCell.style.transform = `scale(${(1 + inf * 0.09).toFixed(3)})`;
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
if (m.on || Math.abs(m.targetX - m.x) + Math.abs(m.targetY - m.y) > 0.5 || m.amp > 5e-3) {
|
|
2251
|
+
m.raf = requestAnimationFrame(frame);
|
|
2252
|
+
}
|
|
2253
|
+
}, [reducedMotion]);
|
|
2254
|
+
const onMouseMove = React9.useCallback(
|
|
2255
|
+
(e) => {
|
|
2256
|
+
const target = e.target;
|
|
2257
|
+
const row = target?.closest?.("[data-depth-row]");
|
|
2258
|
+
const m = motion.current;
|
|
2259
|
+
if (!row || !row.dataset.price) {
|
|
2260
|
+
m.on = false;
|
|
2261
|
+
unbold();
|
|
2262
|
+
setPillPrice(null);
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
setPillPrice(row.dataset.price);
|
|
2266
|
+
const priceCell = row.querySelector('[data-cell="price"]');
|
|
2267
|
+
if (priceCell !== boldedCellRef.current) {
|
|
2268
|
+
unbold();
|
|
2269
|
+
if (priceCell) {
|
|
2270
|
+
priceCell.style.fontWeight = "600";
|
|
2271
|
+
boldedCellRef.current = priceCell;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
const pill = pillRef.current;
|
|
2275
|
+
if (pill && priceCell) {
|
|
2276
|
+
const rowRect = row.getBoundingClientRect();
|
|
2277
|
+
const cellRect = priceCell.getBoundingClientRect();
|
|
2278
|
+
const priceOffset = priceSpanRef.current?.offsetLeft || 48;
|
|
2279
|
+
const nextX = cellRect.left - priceOffset;
|
|
2280
|
+
const nextY = rowRect.top + (rowRect.height || 28) / 2 - pill.offsetHeight / 2;
|
|
2281
|
+
if (!m.on) {
|
|
2282
|
+
m.x = nextX;
|
|
2283
|
+
m.y = nextY;
|
|
2284
|
+
}
|
|
2285
|
+
m.targetX = nextX;
|
|
2286
|
+
m.targetY = nextY;
|
|
2287
|
+
}
|
|
2288
|
+
m.on = true;
|
|
2289
|
+
if (!m.raf && typeof requestAnimationFrame === "function") {
|
|
2290
|
+
m.raf = requestAnimationFrame(frame);
|
|
2291
|
+
}
|
|
2292
|
+
},
|
|
2293
|
+
[frame]
|
|
2294
|
+
);
|
|
2295
|
+
const onMouseLeave = React9.useCallback(() => {
|
|
2296
|
+
motion.current.on = false;
|
|
2297
|
+
unbold();
|
|
2298
|
+
setPillPrice(null);
|
|
2299
|
+
}, []);
|
|
2300
|
+
React9.useEffect(() => {
|
|
2301
|
+
return () => {
|
|
2302
|
+
if (motion.current.raf) cancelAnimationFrame(motion.current.raf);
|
|
2303
|
+
};
|
|
2304
|
+
}, []);
|
|
2305
|
+
return { sectionRef, pillRef, priceSpanRef, pillPrice, onMouseMove, onMouseLeave };
|
|
2306
|
+
}
|
|
2059
2307
|
function SkeletonRow({ compact }) {
|
|
2060
2308
|
if (compact) {
|
|
2061
2309
|
return /* @__PURE__ */ jsxs(
|
|
@@ -2112,8 +2360,12 @@ function DesktopOrderbookLayout({
|
|
|
2112
2360
|
compactLevelCount: _compactLevelCount,
|
|
2113
2361
|
fillHeight,
|
|
2114
2362
|
newAskPrices,
|
|
2115
|
-
newBidPrices
|
|
2363
|
+
newBidPrices,
|
|
2364
|
+
spread,
|
|
2365
|
+
spreadPct,
|
|
2366
|
+
asksSectionRef
|
|
2116
2367
|
}) {
|
|
2368
|
+
const glide = useGlidePill();
|
|
2117
2369
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2118
2370
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 pt-4", children: [
|
|
2119
2371
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
|
|
@@ -2123,8 +2375,8 @@ function DesktopOrderbookLayout({
|
|
|
2123
2375
|
type: "button",
|
|
2124
2376
|
onClick: () => handleTab("orderbook"),
|
|
2125
2377
|
className: cn(
|
|
2126
|
-
"relative pb-2 text-
|
|
2127
|
-
tab === "orderbook" ? "text-white" : "text-white/60"
|
|
2378
|
+
"relative pb-2 text-[0.8rem] font-medium transition-colors",
|
|
2379
|
+
tab === "orderbook" ? "text-white" : "text-white/60 hover:text-white/85"
|
|
2128
2380
|
),
|
|
2129
2381
|
children: [
|
|
2130
2382
|
"Orderbook",
|
|
@@ -2138,8 +2390,8 @@ function DesktopOrderbookLayout({
|
|
|
2138
2390
|
type: "button",
|
|
2139
2391
|
onClick: () => handleTab("trades"),
|
|
2140
2392
|
className: cn(
|
|
2141
|
-
"relative pb-2 text-
|
|
2142
|
-
tab === "trades" ? "text-white" : "text-white/60"
|
|
2393
|
+
"relative pb-2 text-[0.8rem] font-medium transition-colors",
|
|
2394
|
+
tab === "trades" ? "text-white" : "text-white/60 hover:text-white/85"
|
|
2143
2395
|
),
|
|
2144
2396
|
children: [
|
|
2145
2397
|
"Trades",
|
|
@@ -2187,100 +2439,170 @@ function DesktopOrderbookLayout({
|
|
|
2187
2439
|
)
|
|
2188
2440
|
] }) : /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: rightHeader })
|
|
2189
2441
|
] }),
|
|
2190
|
-
/* @__PURE__ */ jsxs(
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
const tradeKey = getTradeKey(trade, i);
|
|
2203
|
-
const explorerUrl = trade.txHash && tradeExplorerUrl ? tradeExplorerUrl(trade.txHash) : trade.txHash ? `https://sepolia.etherscan.io/tx/${trade.txHash}` : void 0;
|
|
2204
|
-
return /* @__PURE__ */ jsx(
|
|
2205
|
-
TradeRow,
|
|
2442
|
+
/* @__PURE__ */ jsxs(
|
|
2443
|
+
"div",
|
|
2444
|
+
{
|
|
2445
|
+
ref: glide.sectionRef,
|
|
2446
|
+
"data-glide-section": true,
|
|
2447
|
+
className: "relative flex flex-1 flex-col min-h-0 px-4 pt-2",
|
|
2448
|
+
onMouseMove: glide.onMouseMove,
|
|
2449
|
+
onMouseLeave: glide.onMouseLeave,
|
|
2450
|
+
children: [
|
|
2451
|
+
typeof document !== "undefined" ? createPortal(
|
|
2452
|
+
/* @__PURE__ */ jsxs(
|
|
2453
|
+
"div",
|
|
2206
2454
|
{
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2455
|
+
ref: glide.pillRef,
|
|
2456
|
+
"data-glide-pill": true,
|
|
2457
|
+
"data-visible": glide.pillPrice != null ? "true" : "false",
|
|
2458
|
+
style: {
|
|
2459
|
+
position: "fixed",
|
|
2460
|
+
left: 0,
|
|
2461
|
+
top: 0,
|
|
2462
|
+
zIndex: 60,
|
|
2463
|
+
pointerEvents: "none",
|
|
2464
|
+
display: "flex",
|
|
2465
|
+
alignItems: "center",
|
|
2466
|
+
gap: 8,
|
|
2467
|
+
whiteSpace: "nowrap",
|
|
2468
|
+
background: "#1b1d22",
|
|
2469
|
+
border: "1px solid rgba(255,255,255,0.14)",
|
|
2470
|
+
borderRadius: 999,
|
|
2471
|
+
padding: "4px 13px",
|
|
2472
|
+
boxShadow: "0 6px 20px rgba(0,0,0,0.55)",
|
|
2473
|
+
fontVariantNumeric: "tabular-nums",
|
|
2474
|
+
opacity: glide.pillPrice != null ? 1 : 0,
|
|
2475
|
+
transition: "opacity 160ms ease"
|
|
2476
|
+
},
|
|
2477
|
+
children: [
|
|
2478
|
+
/* @__PURE__ */ jsx(
|
|
2479
|
+
"span",
|
|
2480
|
+
{
|
|
2481
|
+
style: {
|
|
2482
|
+
fontSize: "0.6rem",
|
|
2483
|
+
fontWeight: 600,
|
|
2484
|
+
letterSpacing: "0.08em",
|
|
2485
|
+
textTransform: "uppercase",
|
|
2486
|
+
color: "#E6C87E",
|
|
2487
|
+
lineHeight: 1
|
|
2488
|
+
},
|
|
2489
|
+
children: "Limit"
|
|
2490
|
+
}
|
|
2491
|
+
),
|
|
2492
|
+
/* @__PURE__ */ jsx(
|
|
2493
|
+
"span",
|
|
2494
|
+
{
|
|
2495
|
+
ref: glide.priceSpanRef,
|
|
2496
|
+
className: "tabular-nums",
|
|
2497
|
+
style: { fontSize: "0.8rem", fontWeight: 600, color: "#fff" },
|
|
2498
|
+
children: glide.pillPrice ?? ""
|
|
2499
|
+
}
|
|
2500
|
+
)
|
|
2501
|
+
]
|
|
2502
|
+
}
|
|
2503
|
+
),
|
|
2504
|
+
document.body
|
|
2505
|
+
) : null,
|
|
2506
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-3 px-3 pb-1.5 pt-1 text-xs text-white/60", children: [
|
|
2507
|
+
/* @__PURE__ */ jsx("div", { children: priceLabel }),
|
|
2508
|
+
/* @__PURE__ */ jsx("div", { className: "text-right", children: amountLabel }),
|
|
2509
|
+
/* @__PURE__ */ jsx("div", { className: "text-right", children: tab === "orderbook" ? "Total" : "Time" })
|
|
2510
|
+
] }),
|
|
2511
|
+
tab === "trades" ? /* @__PURE__ */ jsx(
|
|
2512
|
+
"div",
|
|
2513
|
+
{
|
|
2514
|
+
className: "max-h-[380px] overflow-y-auto overflow-x-hidden",
|
|
2515
|
+
style: { scrollbarGutter: "stable" },
|
|
2516
|
+
children: isLoading && tradeFiltered.length === 0 ? /* @__PURE__ */ jsx("div", { children: Array.from({ length: effectiveLevels }).map((_, i) => /* @__PURE__ */ jsx(SkeletonRow, {}, `trade-skel-${i}`)) }) : tradeFiltered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-10 text-center text-sm text-white/50", children: "No trades" }) : /* @__PURE__ */ jsx("div", { children: tradeFiltered.map((trade, i) => {
|
|
2517
|
+
const tradeKey = getTradeKey(trade, i);
|
|
2518
|
+
const explorerUrl = trade.txHash && tradeExplorerUrl ? tradeExplorerUrl(trade.txHash) : trade.txHash ? `https://sepolia.etherscan.io/tx/${trade.txHash}` : void 0;
|
|
2519
|
+
return /* @__PURE__ */ jsx(
|
|
2520
|
+
TradeRow,
|
|
2521
|
+
{
|
|
2522
|
+
trade,
|
|
2523
|
+
tradeKey,
|
|
2524
|
+
precision,
|
|
2525
|
+
amountPrecision,
|
|
2526
|
+
seenTradeKeysRef,
|
|
2527
|
+
explorerUrl
|
|
2528
|
+
},
|
|
2529
|
+
tradeKey
|
|
2530
|
+
);
|
|
2531
|
+
}) })
|
|
2532
|
+
}
|
|
2533
|
+
) : /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col min-h-0 overflow-hidden", fillHeight && "flex-1"), children: [
|
|
2534
|
+
/* @__PURE__ */ jsx(
|
|
2535
|
+
"div",
|
|
2226
2536
|
{
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
midChangePercent >= 0 ? "+" : "",
|
|
2252
|
-
midChangePercent.toFixed(2),
|
|
2253
|
-
"%"
|
|
2254
|
-
] })
|
|
2255
|
-
] }) })
|
|
2256
|
-
}
|
|
2257
|
-
),
|
|
2258
|
-
/* @__PURE__ */ jsx(
|
|
2259
|
-
"div",
|
|
2260
|
-
{
|
|
2261
|
-
className: cn("flex flex-col overflow-hidden", !fillHeight && "flex-shrink-0"),
|
|
2262
|
-
style: fillHeight ? { flex: 1, minHeight: 0 } : { height: `${sectionHeight}px` },
|
|
2263
|
-
children: isLoading ? Array.from({ length: effectiveLevels }).map((_, i) => /* @__PURE__ */ jsx(SkeletonRow, {}, `bid-skel-${i}`)) : bidVisibleLevels.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center px-3 text-xs text-white/40", children: "No bids" }) : bidVisibleLevels.map((l, i) => /* @__PURE__ */ jsx(
|
|
2264
|
-
DepthRow,
|
|
2537
|
+
ref: asksSectionRef,
|
|
2538
|
+
className: cn("flex flex-col justify-end overflow-hidden", !fillHeight && "flex-shrink-0"),
|
|
2539
|
+
style: fillHeight ? { flex: 1, minHeight: 0 } : { height: `${sectionHeight}px` },
|
|
2540
|
+
children: isLoading ? Array.from({ length: effectiveLevels }).map((_, i) => /* @__PURE__ */ jsx(SkeletonRow, {}, `ask-skel-${i}`)) : askVisibleLevels.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center px-3 text-xs text-white/40", children: "No asks" }) : askVisibleLevels.map((l, i) => /* @__PURE__ */ jsx(
|
|
2541
|
+
DepthRow,
|
|
2542
|
+
{
|
|
2543
|
+
side: "ask",
|
|
2544
|
+
price: l.price,
|
|
2545
|
+
amount: l.amount,
|
|
2546
|
+
cumDepth: askCumDepths[i],
|
|
2547
|
+
depthPct: askCumDepths[i] / askMaxCumDepth * 100,
|
|
2548
|
+
precision,
|
|
2549
|
+
amountPrecision,
|
|
2550
|
+
hasUserOrder: userAskPrices.has(l.price),
|
|
2551
|
+
onPriceClick,
|
|
2552
|
+
flashOnMount: newAskPrices.has(l.price),
|
|
2553
|
+
fillHeight
|
|
2554
|
+
},
|
|
2555
|
+
`ask-${l.price}`
|
|
2556
|
+
))
|
|
2557
|
+
}
|
|
2558
|
+
),
|
|
2559
|
+
/* @__PURE__ */ jsxs(
|
|
2560
|
+
"div",
|
|
2265
2561
|
{
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2562
|
+
className: "flex items-center justify-between gap-2 px-3 py-2 flex-shrink-0 overflow-hidden orderbook-mid-row",
|
|
2563
|
+
style: { ...MID_ROW_GOLD_STYLE, alignItems: "baseline" },
|
|
2564
|
+
children: [
|
|
2565
|
+
/* @__PURE__ */ jsx("div", { className: cn("text-lg font-semibold tabular-nums whitespace-nowrap orderbook-mid-price", midClass), children: isLoading ? /* @__PURE__ */ jsx(Skeleton, { width: 110, height: 20 }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2566
|
+
"$",
|
|
2567
|
+
formatNumber(midPrice, precision),
|
|
2568
|
+
midChangePercent == null ? null : /* @__PURE__ */ jsxs("span", { className: cn("ml-2 text-sm font-semibold tabular-nums", midClass), children: [
|
|
2569
|
+
midChangePercent >= 0 ? "+" : "",
|
|
2570
|
+
midChangePercent.toFixed(2),
|
|
2571
|
+
"%"
|
|
2572
|
+
] })
|
|
2573
|
+
] }) }),
|
|
2574
|
+
!isLoading && /* @__PURE__ */ jsx(SpreadReadout, { spread, spreadPct, precision })
|
|
2575
|
+
]
|
|
2576
|
+
}
|
|
2577
|
+
),
|
|
2578
|
+
/* @__PURE__ */ jsx(
|
|
2579
|
+
"div",
|
|
2580
|
+
{
|
|
2581
|
+
className: cn("flex flex-col overflow-hidden", !fillHeight && "flex-shrink-0"),
|
|
2582
|
+
style: fillHeight ? { flex: 1, minHeight: 0 } : { height: `${sectionHeight}px` },
|
|
2583
|
+
children: isLoading ? Array.from({ length: effectiveLevels }).map((_, i) => /* @__PURE__ */ jsx(SkeletonRow, {}, `bid-skel-${i}`)) : bidVisibleLevels.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center px-3 text-xs text-white/40", children: "No bids" }) : bidVisibleLevels.map((l, i) => /* @__PURE__ */ jsx(
|
|
2584
|
+
DepthRow,
|
|
2585
|
+
{
|
|
2586
|
+
side: "bid",
|
|
2587
|
+
price: l.price,
|
|
2588
|
+
amount: l.amount,
|
|
2589
|
+
cumDepth: bidCumDepths[i],
|
|
2590
|
+
depthPct: bidCumDepths[i] / bidMaxCumDepth * 100,
|
|
2591
|
+
precision,
|
|
2592
|
+
amountPrecision,
|
|
2593
|
+
hasUserOrder: userBidPrices.has(l.price),
|
|
2594
|
+
onPriceClick,
|
|
2595
|
+
flashOnMount: newBidPrices.has(l.price),
|
|
2596
|
+
fillHeight
|
|
2597
|
+
},
|
|
2598
|
+
`bid-${l.price}`
|
|
2599
|
+
))
|
|
2600
|
+
}
|
|
2601
|
+
)
|
|
2602
|
+
] })
|
|
2603
|
+
]
|
|
2604
|
+
}
|
|
2605
|
+
)
|
|
2284
2606
|
] });
|
|
2285
2607
|
}
|
|
2286
2608
|
function MobileOrderbookLayout({
|
|
@@ -2407,8 +2729,7 @@ function MobileOrderbookLayout({
|
|
|
2407
2729
|
gridTemplateColumns: "1.2fr 0.8fr",
|
|
2408
2730
|
padding: "0.3rem 0",
|
|
2409
2731
|
fontSize: "0.8rem",
|
|
2410
|
-
|
|
2411
|
-
borderBottom: "1px solid rgba(255,255,255,0.1)"
|
|
2732
|
+
...MID_ROW_GOLD_STYLE
|
|
2412
2733
|
},
|
|
2413
2734
|
children: [
|
|
2414
2735
|
/* @__PURE__ */ jsx(
|
|
@@ -2490,7 +2811,7 @@ function MobileDepthRow({
|
|
|
2490
2811
|
flashOnMount
|
|
2491
2812
|
}) {
|
|
2492
2813
|
const isAsk = side === "ask";
|
|
2493
|
-
const rowRef =
|
|
2814
|
+
const rowRef = React9.useRef(null);
|
|
2494
2815
|
useAmountChangeFlash(rowRef, amount, side, flashOnMount);
|
|
2495
2816
|
return /* @__PURE__ */ jsxs(
|
|
2496
2817
|
"div",
|
|
@@ -2516,16 +2837,12 @@ function MobileDepthRow({
|
|
|
2516
2837
|
left: "2px",
|
|
2517
2838
|
top: "50%",
|
|
2518
2839
|
transform: "translateY(-50%)",
|
|
2519
|
-
width: "6px",
|
|
2520
|
-
height: "6px",
|
|
2521
|
-
borderRadius: "50%",
|
|
2522
|
-
backgroundColor: "#C9A227",
|
|
2523
|
-
boxShadow: "0 0 6px rgba(201,162,39,0.7)",
|
|
2524
2840
|
zIndex: 2
|
|
2525
|
-
}
|
|
2841
|
+
},
|
|
2842
|
+
children: /* @__PURE__ */ jsx(BreadSliceGlyph, {})
|
|
2526
2843
|
}
|
|
2527
2844
|
) : null,
|
|
2528
|
-
/* @__PURE__ */ jsxs("div", { style: { position: "relative", zIndex: 1, color: isAsk ? "#f6465d" : "#0ecb81", paddingLeft: hasUserOrder ? "
|
|
2845
|
+
/* @__PURE__ */ jsxs("div", { style: { position: "relative", zIndex: 1, color: isAsk ? "#f6465d" : "#0ecb81", paddingLeft: hasUserOrder ? "14px" : void 0 }, children: [
|
|
2529
2846
|
"$",
|
|
2530
2847
|
formatNumber(price, precision)
|
|
2531
2848
|
] }),
|
|
@@ -2535,12 +2852,12 @@ function MobileDepthRow({
|
|
|
2535
2852
|
{
|
|
2536
2853
|
style: {
|
|
2537
2854
|
position: "absolute",
|
|
2538
|
-
top:
|
|
2855
|
+
top: 1,
|
|
2539
2856
|
left: 0,
|
|
2540
|
-
bottom:
|
|
2857
|
+
bottom: 1,
|
|
2541
2858
|
width: `${clamp3(depthPct, 0, 100)}%`,
|
|
2542
|
-
|
|
2543
|
-
|
|
2859
|
+
backgroundImage: isAsk ? "linear-gradient(90deg, rgba(246,70,93,0.11), rgba(246,70,93,0.15))" : "linear-gradient(90deg, rgba(14,203,129,0.09), rgba(14,203,129,0.13))",
|
|
2860
|
+
borderRadius: "0 4px 4px 0",
|
|
2544
2861
|
zIndex: 0,
|
|
2545
2862
|
transition: "width 300ms ease-out"
|
|
2546
2863
|
}
|
|
@@ -2553,20 +2870,20 @@ function MobileDepthRow({
|
|
|
2553
2870
|
var isHlsManifest = (url) => /\.m3u8(\?|$)/i.test(url);
|
|
2554
2871
|
var HLS_MIME = "application/vnd.apple.mpegurl";
|
|
2555
2872
|
var MANIFEST_FAILURES = /* @__PURE__ */ new Set(["manifestLoadError", "manifestLoadTimeOut", "manifestParsingError"]);
|
|
2556
|
-
var HlsVideo =
|
|
2873
|
+
var HlsVideo = React9.forwardRef(
|
|
2557
2874
|
({ src, capLevelToPlayerSize = true, onError, ...videoProps }, ref) => {
|
|
2558
|
-
const videoRef =
|
|
2559
|
-
|
|
2875
|
+
const videoRef = React9.useRef(null);
|
|
2876
|
+
React9.useImperativeHandle(ref, () => videoRef.current, []);
|
|
2560
2877
|
const primary = typeof src === "string" ? src : src.src;
|
|
2561
2878
|
const fallbackSrc = typeof src === "string" ? void 0 : src.fallbackSrc;
|
|
2562
|
-
const [failedSrc, setFailedSrc] =
|
|
2879
|
+
const [failedSrc, setFailedSrc] = React9.useState(null);
|
|
2563
2880
|
const usingFallback = fallbackSrc != null && failedSrc === primary;
|
|
2564
2881
|
const activeSrc = usingFallback ? fallbackSrc : primary;
|
|
2565
|
-
const failPrimary =
|
|
2882
|
+
const failPrimary = React9.useCallback(() => {
|
|
2566
2883
|
if (fallbackSrc == null) return;
|
|
2567
2884
|
setFailedSrc(primary);
|
|
2568
2885
|
}, [fallbackSrc, primary]);
|
|
2569
|
-
|
|
2886
|
+
React9.useEffect(() => {
|
|
2570
2887
|
const video = videoRef.current;
|
|
2571
2888
|
if (!video || !activeSrc) return;
|
|
2572
2889
|
if (!isHlsManifest(activeSrc) || video.canPlayType(HLS_MIME)) {
|
|
@@ -2599,7 +2916,7 @@ var HlsVideo = React5.forwardRef(
|
|
|
2599
2916
|
hls?.destroy();
|
|
2600
2917
|
};
|
|
2601
2918
|
}, [activeSrc, capLevelToPlayerSize, failPrimary]);
|
|
2602
|
-
const handleError =
|
|
2919
|
+
const handleError = React9.useCallback(
|
|
2603
2920
|
(e) => {
|
|
2604
2921
|
if (!usingFallback) failPrimary();
|
|
2605
2922
|
onError?.(e);
|
|
@@ -2610,7 +2927,7 @@ var HlsVideo = React5.forwardRef(
|
|
|
2610
2927
|
}
|
|
2611
2928
|
);
|
|
2612
2929
|
HlsVideo.displayName = "HlsVideo";
|
|
2613
|
-
var PropertyTour =
|
|
2930
|
+
var PropertyTour = React9.forwardRef(
|
|
2614
2931
|
({
|
|
2615
2932
|
className,
|
|
2616
2933
|
title,
|
|
@@ -2623,8 +2940,8 @@ var PropertyTour = React5.forwardRef(
|
|
|
2623
2940
|
playsInline = true,
|
|
2624
2941
|
...props
|
|
2625
2942
|
}, ref) => {
|
|
2626
|
-
const videoRef =
|
|
2627
|
-
|
|
2943
|
+
const videoRef = React9.useRef(null);
|
|
2944
|
+
React9.useEffect(() => {
|
|
2628
2945
|
const video = videoRef.current;
|
|
2629
2946
|
if (!video) return;
|
|
2630
2947
|
const handleFullscreenChange = () => {
|
|
@@ -2732,8 +3049,50 @@ function isSafeUrl(url) {
|
|
|
2732
3049
|
return false;
|
|
2733
3050
|
}
|
|
2734
3051
|
}
|
|
3052
|
+
var BREAKING_WINDOW_HOURS = 6;
|
|
3053
|
+
var MAX_PINNED_BREAKING = 2;
|
|
3054
|
+
var BREAKING_IMPACT_FLOOR = 8;
|
|
3055
|
+
var breakingImpact = (it) => typeof it.impactScore === "number" && Number.isFinite(it.impactScore) ? it.impactScore : BREAKING_IMPACT_FLOOR;
|
|
3056
|
+
var dismissedBreakingKeys = /* @__PURE__ */ new Set();
|
|
3057
|
+
var __resetDismissedBreaking = () => dismissedBreakingKeys.clear();
|
|
3058
|
+
var breakingKey = (it) => String(it.id ?? it.title);
|
|
3059
|
+
var BREAKING_SWIPE_MIN_PX = 80;
|
|
2735
3060
|
var ITEMS_PER_PAGE = 5;
|
|
2736
3061
|
var ITEMS_PER_PAGE_MOBILE = 5;
|
|
3062
|
+
function formatAge(publishedAt, now) {
|
|
3063
|
+
const elapsed = Math.max(0, now - new Date(publishedAt).getTime());
|
|
3064
|
+
const secs = Math.floor(elapsed / 1e3);
|
|
3065
|
+
if (secs < 60) return `${secs}s ago`;
|
|
3066
|
+
const mins = Math.floor(secs / 60);
|
|
3067
|
+
if (mins < 60) return `${mins}m ago`;
|
|
3068
|
+
return `${Math.floor(mins / 60)}h ago`;
|
|
3069
|
+
}
|
|
3070
|
+
function rowAgeLabel(publishedAt, now) {
|
|
3071
|
+
if (!publishedAt) return null;
|
|
3072
|
+
const t = new Date(publishedAt).getTime();
|
|
3073
|
+
if (!Number.isFinite(t)) return null;
|
|
3074
|
+
const ageMs = Math.max(0, now - t);
|
|
3075
|
+
if (ageMs >= 24 * 36e5) return null;
|
|
3076
|
+
return { label: formatAge(publishedAt, now), fresh: ageMs < 36e5 };
|
|
3077
|
+
}
|
|
3078
|
+
function useTickingNow(active, youngestPublishedAt) {
|
|
3079
|
+
const [now, setNow] = React9.useState(() => Date.now());
|
|
3080
|
+
React9.useEffect(() => {
|
|
3081
|
+
if (!active || !youngestPublishedAt) return;
|
|
3082
|
+
let timer;
|
|
3083
|
+
const schedule = () => {
|
|
3084
|
+
const ageSecs = (Date.now() - new Date(youngestPublishedAt).getTime()) / 1e3;
|
|
3085
|
+
const delay = ageSecs < 60 ? 1e3 : ageSecs < 3600 ? 15e3 : 6e4;
|
|
3086
|
+
timer = setTimeout(() => {
|
|
3087
|
+
setNow(Date.now());
|
|
3088
|
+
schedule();
|
|
3089
|
+
}, delay);
|
|
3090
|
+
};
|
|
3091
|
+
schedule();
|
|
3092
|
+
return () => clearTimeout(timer);
|
|
3093
|
+
}, [active, youngestPublishedAt]);
|
|
3094
|
+
return now;
|
|
3095
|
+
}
|
|
2737
3096
|
var ensureAnimationsInjected = () => {
|
|
2738
3097
|
if (typeof document === "undefined") return;
|
|
2739
3098
|
if (document.getElementById("property-news-updates-animations")) return;
|
|
@@ -2756,6 +3115,110 @@ var ensureAnimationsInjected = () => {
|
|
|
2756
3115
|
from { opacity: 0; transform: translateY(12px) scale(0.98); }
|
|
2757
3116
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
2758
3117
|
}
|
|
3118
|
+
/* The list scrolls but never shows a scrollbar (user call 2026-08-22: the
|
|
3119
|
+
grey track crowded the rows). Same treatment as the rest of the app. */
|
|
3120
|
+
.news-list-scroll { scrollbar-width: none; -ms-overflow-style: none; }
|
|
3121
|
+
.news-list-scroll::-webkit-scrollbar { display: none; width: 0; height: 0; }
|
|
3122
|
+
.property-news-breaking-dismiss {
|
|
3123
|
+
opacity: 0;
|
|
3124
|
+
pointer-events: none;
|
|
3125
|
+
transform: translateY(3px);
|
|
3126
|
+
transition: opacity 0.16s ease, transform 0.2s cubic-bezier(0.22, 1, 0.36, 1), color 0.15s ease;
|
|
3127
|
+
}
|
|
3128
|
+
.property-news-breaking-item:hover .property-news-breaking-dismiss,
|
|
3129
|
+
.property-news-breaking-item:focus-within .property-news-breaking-dismiss {
|
|
3130
|
+
opacity: 1;
|
|
3131
|
+
pointer-events: auto;
|
|
3132
|
+
transform: translateY(0);
|
|
3133
|
+
}
|
|
3134
|
+
.property-news-breaking-dismiss:hover,
|
|
3135
|
+
.property-news-breaking-dismiss:focus-visible {
|
|
3136
|
+
color: #fff !important;
|
|
3137
|
+
text-decoration: underline;
|
|
3138
|
+
text-underline-offset: 2px;
|
|
3139
|
+
outline: none;
|
|
3140
|
+
}
|
|
3141
|
+
.property-news-breaking-item[data-dismissing="true"] {
|
|
3142
|
+
pointer-events: none;
|
|
3143
|
+
animation: propertyNewsBreakingDismiss 0.22s cubic-bezier(0.4, 0, 1, 1) forwards;
|
|
3144
|
+
}
|
|
3145
|
+
.property-news-breaking-item[data-new-breaking="true"] {
|
|
3146
|
+
position: relative;
|
|
3147
|
+
overflow: hidden;
|
|
3148
|
+
animation: propertyNewsBreakingArrive 1.18s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
3149
|
+
}
|
|
3150
|
+
.property-news-breaking-item[data-new-breaking="true"]::after {
|
|
3151
|
+
content: "";
|
|
3152
|
+
position: absolute;
|
|
3153
|
+
inset: 0;
|
|
3154
|
+
z-index: 0;
|
|
3155
|
+
pointer-events: none;
|
|
3156
|
+
background: linear-gradient(100deg, transparent 8%, rgba(var(--color-accent-rgb, 230, 200, 126), 0.42) 48%, transparent 82%);
|
|
3157
|
+
transform: translateX(-120%);
|
|
3158
|
+
animation: propertyNewsBreakingSheen 1.18s 0.2s cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
3159
|
+
}
|
|
3160
|
+
.property-news-breaking-item[data-new-breaking="true"] > span {
|
|
3161
|
+
position: relative;
|
|
3162
|
+
z-index: 1;
|
|
3163
|
+
}
|
|
3164
|
+
.property-news-breaking-item[data-new-breaking="true"][data-dismissing="true"] {
|
|
3165
|
+
animation: propertyNewsBreakingDismiss 0.22s cubic-bezier(0.4, 0, 1, 1) forwards;
|
|
3166
|
+
}
|
|
3167
|
+
@keyframes propertyNewsBreakingArrive {
|
|
3168
|
+
0% { opacity: 0; transform: translateY(-14px) scale(0.985); box-shadow: 0 0 0 rgba(var(--color-accent-rgb, 230, 200, 126), 0); filter: brightness(1); }
|
|
3169
|
+
24% { opacity: 1; transform: translateY(0) scale(1); box-shadow: 0 0 26px rgba(var(--color-accent-rgb, 230, 200, 126), 0.38); filter: brightness(1.16); }
|
|
3170
|
+
58% { box-shadow: 0 0 16px rgba(var(--color-accent-rgb, 230, 200, 126), 0.22); filter: brightness(1.06); }
|
|
3171
|
+
100% { opacity: 1; transform: translateY(0) scale(1); box-shadow: 0 0 0 rgba(var(--color-accent-rgb, 230, 200, 126), 0); }
|
|
3172
|
+
}
|
|
3173
|
+
@keyframes propertyNewsBreakingSheen {
|
|
3174
|
+
from { transform: translateX(-120%); }
|
|
3175
|
+
to { transform: translateX(120%); }
|
|
3176
|
+
}
|
|
3177
|
+
@keyframes propertyNewsBreakingDismiss {
|
|
3178
|
+
from { opacity: 1; transform: translateX(0); max-height: 96px; }
|
|
3179
|
+
to { opacity: 0; transform: translateX(24px); max-height: 0; padding-top: 0; padding-bottom: 0; }
|
|
3180
|
+
}
|
|
3181
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3182
|
+
.property-news-breaking-dismiss { transition: none; }
|
|
3183
|
+
.property-news-breaking-item[data-new-breaking="true"],
|
|
3184
|
+
.property-news-breaking-item[data-new-breaking="true"]::after { animation-duration: 1ms; animation-delay: 0ms; }
|
|
3185
|
+
.property-news-breaking-item[data-dismissing="true"] { animation-duration: 1ms; }
|
|
3186
|
+
}
|
|
3187
|
+
/* Asset badge: the wrapper hosts the INSTANT CSS tooltip (native title is
|
|
3188
|
+
too slow) and the hover affordance that says "the icon itself is the
|
|
3189
|
+
target" \u2014 the box scales up with a gold ring while the tooltip shows
|
|
3190
|
+
"Name (TICKER)" immediately. */
|
|
3191
|
+
.news-badge-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
|
|
3192
|
+
.news-badge-wrap > .news-badge-box { transition: transform 0.15s ease, box-shadow 0.15s ease; }
|
|
3193
|
+
.news-badge-wrap:hover > .news-badge-box {
|
|
3194
|
+
transform: scale(1.08);
|
|
3195
|
+
box-shadow: 0 0 0 1px rgba(var(--color-accent-rgb, 230, 200, 126), 0.7);
|
|
3196
|
+
}
|
|
3197
|
+
/* Instant element tooltip (an ::after can't carry a coloured % change).
|
|
3198
|
+
PORTALED to document.body with position:fixed \u2014 as an absolute child it
|
|
3199
|
+
was clipped by the scrolling news list at the top/right edges. Anchored
|
|
3200
|
+
above the badge by default (translateY(-100%)), flipped below via
|
|
3201
|
+
data-tip-below when the badge sits too close to the viewport top. */
|
|
3202
|
+
.news-badge-tipbox {
|
|
3203
|
+
position: fixed;
|
|
3204
|
+
display: flex;
|
|
3205
|
+
transform: translate(-50%, -100%);
|
|
3206
|
+
flex-direction: column;
|
|
3207
|
+
align-items: center;
|
|
3208
|
+
gap: 2px;
|
|
3209
|
+
background: #1a1d24;
|
|
3210
|
+
color: #fff;
|
|
3211
|
+
font-size: 0.62rem;
|
|
3212
|
+
font-weight: 600;
|
|
3213
|
+
line-height: 1.2;
|
|
3214
|
+
padding: 0.32rem 0.55rem;
|
|
3215
|
+
border-radius: 5px;
|
|
3216
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
3217
|
+
white-space: nowrap;
|
|
3218
|
+
z-index: 10060;
|
|
3219
|
+
pointer-events: none;
|
|
3220
|
+
}
|
|
3221
|
+
.news-badge-tipbox[data-tip-below="true"] { transform: translate(-50%, 0); }
|
|
2759
3222
|
`;
|
|
2760
3223
|
document.head.appendChild(style);
|
|
2761
3224
|
};
|
|
@@ -2792,7 +3255,8 @@ var formatDateShort = (value) => {
|
|
|
2792
3255
|
if (diffS < 3600) return `${Math.floor(diffS / 60)}m ago`;
|
|
2793
3256
|
if (diffS < 86400) return `${Math.floor(diffS / 3600)}h ago`;
|
|
2794
3257
|
const time = d.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit", hour12: true });
|
|
2795
|
-
|
|
3258
|
+
const date = d.toLocaleDateString(void 0, { month: "short", day: "numeric" });
|
|
3259
|
+
return `${date} \xB7 ${time}`;
|
|
2796
3260
|
};
|
|
2797
3261
|
var formatTimeAgo = (timestamp) => {
|
|
2798
3262
|
const diff = (Date.now() - new Date(timestamp).getTime()) / 1e3;
|
|
@@ -2806,10 +3270,66 @@ function getSentimentInfo(score) {
|
|
|
2806
3270
|
if (score < -0.15) return { arrow: "\u25BC", label: "Bearish", color: "#f6465d" };
|
|
2807
3271
|
return { arrow: "", label: "Neutral", color: "#848e9c" };
|
|
2808
3272
|
}
|
|
3273
|
+
function SentimentBar({ score, color }) {
|
|
3274
|
+
const clamped = Math.max(-1, Math.min(1, score));
|
|
3275
|
+
const widthPct = Math.abs(clamped) * 50;
|
|
3276
|
+
const direction = clamped > 0 ? "positive" : clamped < 0 ? "negative" : "neutral";
|
|
3277
|
+
const word = clamped > 0.15 ? "bullish" : clamped < -0.15 ? "bearish" : "neutral";
|
|
3278
|
+
const signed = `${clamped >= 0 ? "+" : "\u2212"}${Math.abs(clamped).toFixed(2)}`;
|
|
3279
|
+
return /* @__PURE__ */ jsxs(
|
|
3280
|
+
"span",
|
|
3281
|
+
{
|
|
3282
|
+
"data-testid": "sentiment-bar",
|
|
3283
|
+
"data-score": String(score),
|
|
3284
|
+
role: "img",
|
|
3285
|
+
"aria-label": `Sentiment: ${word}, ${signed} of +1`,
|
|
3286
|
+
style: {
|
|
3287
|
+
position: "relative",
|
|
3288
|
+
display: "inline-block",
|
|
3289
|
+
width: "36px",
|
|
3290
|
+
height: "3px",
|
|
3291
|
+
borderRadius: "2px",
|
|
3292
|
+
background: "rgba(255,255,255,0.10)",
|
|
3293
|
+
flexShrink: 0
|
|
3294
|
+
},
|
|
3295
|
+
children: [
|
|
3296
|
+
/* @__PURE__ */ jsx(
|
|
3297
|
+
"span",
|
|
3298
|
+
{
|
|
3299
|
+
style: {
|
|
3300
|
+
position: "absolute",
|
|
3301
|
+
left: "50%",
|
|
3302
|
+
top: "-1px",
|
|
3303
|
+
width: "1px",
|
|
3304
|
+
height: "5px",
|
|
3305
|
+
background: "rgba(255,255,255,0.28)"
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
),
|
|
3309
|
+
/* @__PURE__ */ jsx(
|
|
3310
|
+
"span",
|
|
3311
|
+
{
|
|
3312
|
+
"data-testid": "sentiment-fill",
|
|
3313
|
+
"data-direction": direction,
|
|
3314
|
+
style: {
|
|
3315
|
+
position: "absolute",
|
|
3316
|
+
top: 0,
|
|
3317
|
+
height: "100%",
|
|
3318
|
+
borderRadius: "2px",
|
|
3319
|
+
background: color,
|
|
3320
|
+
width: `${widthPct}%`,
|
|
3321
|
+
left: `${clamped >= 0 ? 50 : 50 - widthPct}%`
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
)
|
|
3325
|
+
]
|
|
3326
|
+
}
|
|
3327
|
+
);
|
|
3328
|
+
}
|
|
2809
3329
|
function NewsArticleModal({ item, onClose }) {
|
|
2810
3330
|
const sentimentInfo = getSentimentInfo(item.sentimentScore);
|
|
2811
3331
|
const catStyle = categoryStyles[item.type] ?? categoryStyles.market;
|
|
2812
|
-
|
|
3332
|
+
React9.useEffect(() => {
|
|
2813
3333
|
const handler = (e) => {
|
|
2814
3334
|
if (e.key === "Escape") onClose();
|
|
2815
3335
|
};
|
|
@@ -2870,7 +3390,7 @@ function NewsArticleModal({ item, onClose }) {
|
|
|
2870
3390
|
children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) })
|
|
2871
3391
|
}
|
|
2872
3392
|
) }),
|
|
2873
|
-
sentimentInfo && /* @__PURE__ */ jsx("div", { style: { marginBottom: "0.6rem" }, children: /* @__PURE__ */
|
|
3393
|
+
sentimentInfo && /* @__PURE__ */ jsx("div", { style: { marginBottom: "0.6rem" }, children: /* @__PURE__ */ jsx(
|
|
2874
3394
|
"span",
|
|
2875
3395
|
{
|
|
2876
3396
|
style: {
|
|
@@ -2885,10 +3405,11 @@ function NewsArticleModal({ item, onClose }) {
|
|
|
2885
3405
|
background: `${sentimentInfo.color}1a`,
|
|
2886
3406
|
border: `1px solid ${sentimentInfo.color}40`
|
|
2887
3407
|
},
|
|
2888
|
-
children: [
|
|
3408
|
+
children: /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "0.3rem" }, children: [
|
|
2889
3409
|
sentimentInfo.arrow ? `${sentimentInfo.arrow} ` : "",
|
|
2890
|
-
sentimentInfo.label
|
|
2891
|
-
|
|
3410
|
+
sentimentInfo.label,
|
|
3411
|
+
item.sentimentScore != null && /* @__PURE__ */ jsx(SentimentBar, { score: item.sentimentScore, color: sentimentInfo.color })
|
|
3412
|
+
] })
|
|
2892
3413
|
}
|
|
2893
3414
|
) }),
|
|
2894
3415
|
/* @__PURE__ */ jsx("h2", { style: { fontSize: "1rem", fontWeight: 600, color: "#fff", lineHeight: 1.5, margin: "0 0 1rem" }, children: item.title }),
|
|
@@ -2903,7 +3424,13 @@ function NewsArticleModal({ item, onClose }) {
|
|
|
2903
3424
|
fontSize: "0.65rem",
|
|
2904
3425
|
fontWeight: 500,
|
|
2905
3426
|
background: "rgba(255,255,255,0.07)",
|
|
2906
|
-
color: "#848e9c"
|
|
3427
|
+
color: "#848e9c",
|
|
3428
|
+
// Truncation guard moved here with the source itself — a long
|
|
3429
|
+
// publication name would otherwise stretch the meta row.
|
|
3430
|
+
maxWidth: "120px",
|
|
3431
|
+
overflow: "hidden",
|
|
3432
|
+
textOverflow: "ellipsis",
|
|
3433
|
+
whiteSpace: "nowrap"
|
|
2907
3434
|
},
|
|
2908
3435
|
children: item.source
|
|
2909
3436
|
}
|
|
@@ -2965,7 +3492,7 @@ function NewsArticleModal({ item, onClose }) {
|
|
|
2965
3492
|
}
|
|
2966
3493
|
return modal;
|
|
2967
3494
|
}
|
|
2968
|
-
var PropertyNewsUpdates =
|
|
3495
|
+
var PropertyNewsUpdates = React9.forwardRef(
|
|
2969
3496
|
({
|
|
2970
3497
|
className,
|
|
2971
3498
|
heading,
|
|
@@ -2979,49 +3506,120 @@ var PropertyNewsUpdates = React5.forwardRef(
|
|
|
2979
3506
|
viewAllLabel = "View All News",
|
|
2980
3507
|
connectionStatus = "live",
|
|
2981
3508
|
disablePagination = false,
|
|
3509
|
+
hideHeading = false,
|
|
3510
|
+
hideCategoryTabs = false,
|
|
3511
|
+
category,
|
|
3512
|
+
onCategoryChange,
|
|
3513
|
+
pinBreaking = false,
|
|
3514
|
+
onAssetOpen,
|
|
3515
|
+
showAssetBadges = true,
|
|
2982
3516
|
...props
|
|
2983
3517
|
}, ref) => {
|
|
2984
3518
|
const isPurchaseVariant = variant === "purchases";
|
|
2985
3519
|
const isHomeVariant = variant === "home";
|
|
2986
3520
|
const resolvedHeading = heading ?? (isPurchaseVariant ? "Live Purchases" : "Property News & Headlines");
|
|
2987
|
-
const
|
|
2988
|
-
const [
|
|
3521
|
+
const headerTrailing = Boolean(isHomeVariant ? viewAllHref : !isPurchaseVariant);
|
|
3522
|
+
const [homeTabInternal, setHomeTabInternal] = React9.useState("all");
|
|
3523
|
+
const homeTab = category ?? homeTabInternal;
|
|
3524
|
+
const setHomeTab = React9.useCallback(
|
|
3525
|
+
(next) => {
|
|
3526
|
+
setHomeTabInternal(next);
|
|
3527
|
+
onCategoryChange?.(next);
|
|
3528
|
+
},
|
|
3529
|
+
[onCategoryChange]
|
|
3530
|
+
);
|
|
3531
|
+
const [homePage, setHomePage] = React9.useState(0);
|
|
2989
3532
|
const purchaseItems = purchasesProp ?? [];
|
|
2990
|
-
const [page, setPage] =
|
|
2991
|
-
const [selectedItem, setSelectedItem] =
|
|
2992
|
-
const [isMobile, setIsMobile] =
|
|
3533
|
+
const [page, setPage] = React9.useState(0);
|
|
3534
|
+
const [selectedItem, setSelectedItem] = React9.useState(null);
|
|
3535
|
+
const [isMobile, setIsMobile] = React9.useState(
|
|
2993
3536
|
() => typeof window !== "undefined" && window.innerWidth <= 768
|
|
2994
3537
|
);
|
|
2995
|
-
|
|
3538
|
+
React9.useEffect(() => {
|
|
2996
3539
|
const check = () => setIsMobile(window.innerWidth <= 768);
|
|
2997
3540
|
window.addEventListener("resize", check);
|
|
2998
3541
|
return () => window.removeEventListener("resize", check);
|
|
2999
3542
|
}, []);
|
|
3000
3543
|
const homePageSize = isMobile ? ITEMS_PER_PAGE_MOBILE : ITEMS_PER_PAGE;
|
|
3001
|
-
|
|
3544
|
+
React9.useEffect(() => {
|
|
3002
3545
|
ensureAnimationsInjected();
|
|
3003
3546
|
}, []);
|
|
3004
|
-
|
|
3547
|
+
React9.useEffect(() => {
|
|
3005
3548
|
setHomePage(0);
|
|
3006
3549
|
}, [homeTab]);
|
|
3007
|
-
const
|
|
3008
|
-
|
|
3009
|
-
() =>
|
|
3010
|
-
|
|
3550
|
+
const breakingCandidates = React9.useMemo(() => {
|
|
3551
|
+
if (!pinBreaking || !Array.isArray(items)) return [];
|
|
3552
|
+
return items.filter((it) => it.breaking && it.publishedAt && Number.isFinite(new Date(it.publishedAt).getTime())).sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime());
|
|
3553
|
+
}, [pinBreaking, items]);
|
|
3554
|
+
const breakingNow = useTickingNow(
|
|
3555
|
+
breakingCandidates.length > 0,
|
|
3556
|
+
breakingCandidates[0]?.publishedAt ?? null
|
|
3557
|
+
);
|
|
3558
|
+
const breakingEligible = React9.useMemo(() => {
|
|
3559
|
+
const cutoff = breakingNow - BREAKING_WINDOW_HOURS * 36e5;
|
|
3560
|
+
return breakingCandidates.filter((it) => {
|
|
3561
|
+
if (new Date(it.publishedAt).getTime() < cutoff) return false;
|
|
3562
|
+
return homeTab === "all" || it.type === homeTab;
|
|
3563
|
+
});
|
|
3564
|
+
}, [breakingCandidates, breakingNow, homeTab]);
|
|
3565
|
+
const [dismissedVersion, setDismissedVersion] = React9.useState(0);
|
|
3566
|
+
const [dismissingBreakingKey, setDismissingBreakingKey] = React9.useState(null);
|
|
3567
|
+
const [breakingSwipe, setBreakingSwipe] = React9.useState(null);
|
|
3568
|
+
const breakingSwipeStartXRef = React9.useRef(0);
|
|
3569
|
+
const suppressBreakingClickRef = React9.useRef(false);
|
|
3570
|
+
const breakingSwipeActiveKeyRef = React9.useRef(null);
|
|
3571
|
+
const breakingSwipeMaxRef = React9.useRef(0);
|
|
3572
|
+
const [badgeTip, setBadgeTip] = React9.useState(null);
|
|
3573
|
+
const BADGE_TIP_GAP = 7;
|
|
3574
|
+
const BADGE_TIP_FLIP_PX = 56;
|
|
3575
|
+
const showBadgeTip = (el, asset) => {
|
|
3576
|
+
const rect = el.getBoundingClientRect();
|
|
3577
|
+
const below = rect.top < BADGE_TIP_FLIP_PX;
|
|
3578
|
+
setBadgeTip({
|
|
3579
|
+
asset,
|
|
3580
|
+
left: rect.left + rect.width / 2,
|
|
3581
|
+
top: below ? rect.bottom + BADGE_TIP_GAP : rect.top - BADGE_TIP_GAP,
|
|
3582
|
+
below
|
|
3583
|
+
});
|
|
3584
|
+
};
|
|
3585
|
+
const pinnedBreaking = React9.useMemo(() => {
|
|
3586
|
+
return breakingEligible.filter((it) => !dismissedBreakingKeys.has(breakingKey(it))).sort((a, b) => breakingImpact(b) - breakingImpact(a)).slice(0, MAX_PINNED_BREAKING);
|
|
3587
|
+
}, [breakingEligible, dismissedVersion]);
|
|
3588
|
+
const breakingRowKeys = React9.useMemo(
|
|
3589
|
+
() => new Set(breakingEligible.map(breakingKey)),
|
|
3590
|
+
[breakingEligible]
|
|
3591
|
+
);
|
|
3592
|
+
const listItems = React9.useMemo(() => {
|
|
3593
|
+
if (pinnedBreaking.length === 0) return items;
|
|
3594
|
+
const pinned = new Set(pinnedBreaking);
|
|
3595
|
+
return items.filter((it) => !pinned.has(it));
|
|
3596
|
+
}, [items, pinnedBreaking]);
|
|
3597
|
+
const hasItems = Array.isArray(listItems) && listItems.length > 0;
|
|
3598
|
+
const totalPages = React9.useMemo(
|
|
3599
|
+
() => hasItems ? Math.max(1, Math.ceil(listItems.length / ITEMS_PER_PAGE)) : 1,
|
|
3600
|
+
[hasItems, listItems.length]
|
|
3011
3601
|
);
|
|
3012
|
-
|
|
3602
|
+
React9.useEffect(() => {
|
|
3013
3603
|
setPage((prev) => Math.min(prev, totalPages - 1));
|
|
3014
3604
|
}, [totalPages]);
|
|
3015
|
-
const paginatedItems = hasItems ?
|
|
3016
|
-
const homeFilteredItems =
|
|
3017
|
-
() => hasItems ?
|
|
3018
|
-
[hasItems,
|
|
3605
|
+
const paginatedItems = hasItems ? listItems.slice(page * ITEMS_PER_PAGE, page * ITEMS_PER_PAGE + ITEMS_PER_PAGE) : [];
|
|
3606
|
+
const homeFilteredItems = React9.useMemo(
|
|
3607
|
+
() => hasItems ? listItems.filter((it) => homeTab === "all" || it.type === homeTab) : [],
|
|
3608
|
+
[hasItems, listItems, homeTab]
|
|
3019
3609
|
);
|
|
3020
|
-
const homeTotalPages =
|
|
3610
|
+
const homeTotalPages = React9.useMemo(
|
|
3021
3611
|
() => Math.max(1, Math.ceil(homeFilteredItems.length / homePageSize)),
|
|
3022
3612
|
[homeFilteredItems.length, homePageSize]
|
|
3023
3613
|
);
|
|
3024
3614
|
const homePaginatedItems = disablePagination ? homeFilteredItems : homeFilteredItems.slice(homePage * homePageSize, (homePage + 1) * homePageSize);
|
|
3615
|
+
const youngestRowPublishedAt = React9.useMemo(() => {
|
|
3616
|
+
let youngest = null;
|
|
3617
|
+
for (const it of homePaginatedItems) {
|
|
3618
|
+
if (it.publishedAt && (!youngest || it.publishedAt > youngest)) youngest = it.publishedAt;
|
|
3619
|
+
}
|
|
3620
|
+
return youngest;
|
|
3621
|
+
}, [homePaginatedItems]);
|
|
3622
|
+
const rowNow = useTickingNow(isHomeVariant && youngestRowPublishedAt != null, youngestRowPublishedAt);
|
|
3025
3623
|
return /* @__PURE__ */ jsxs(
|
|
3026
3624
|
"div",
|
|
3027
3625
|
{
|
|
@@ -3032,8 +3630,8 @@ var PropertyNewsUpdates = React5.forwardRef(
|
|
|
3032
3630
|
),
|
|
3033
3631
|
...props,
|
|
3034
3632
|
children: [
|
|
3035
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", style: isHomeVariant ? { marginBottom: "0.5rem", minHeight: isMobile ? "32px" : "24px", gap: isMobile ? "0.5rem" : "1rem" } : void 0, children: [
|
|
3036
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
3633
|
+
(!hideHeading || headerTrailing) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", style: isHomeVariant ? { marginBottom: "0.5rem", minHeight: isMobile ? "32px" : "24px", gap: isMobile ? "0.5rem" : "1rem", justifyContent: hideHeading ? "flex-end" : void 0 } : hideHeading ? { justifyContent: "flex-end" } : void 0, children: [
|
|
3634
|
+
!hideHeading && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
3037
3635
|
isHomeVariant && /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: isMobile ? 14 : 16, height: isMobile ? 14 : 16, viewBox: "0 0 24 24", fill: "currentColor", style: { color: "var(--color-text, #fff)", flexShrink: 0, display: "block" }, children: /* @__PURE__ */ jsx("path", { d: "M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-5 14H4v-4h11v4zm0-5H4V9h11v4zm5 5h-4V9h4v9z" }) }),
|
|
3038
3636
|
isPurchaseVariant && /* @__PURE__ */ jsx("span", { style: { display: "inline-block", width: "8px", height: "8px", borderRadius: "50%", backgroundColor: "#0ecb81", boxShadow: "0 0 8px rgba(14,203,129,0.8)", animation: "propertyNewsPulse 1.5s infinite" } }),
|
|
3039
3637
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -3052,7 +3650,7 @@ var PropertyNewsUpdates = React5.forwardRef(
|
|
|
3052
3650
|
"LIVE"
|
|
3053
3651
|
] }) : null
|
|
3054
3652
|
] }),
|
|
3055
|
-
isHomeVariant && /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: isMobile ? "1rem" : "1.5rem", marginTop: "0.25rem", marginBottom: "1rem", borderBottom: "1px solid rgba(255, 255, 255, 0.08)", paddingBottom: "0.5rem" }, children: ["all", "property", "market"].map((tab) => /* @__PURE__ */ jsx(
|
|
3653
|
+
isHomeVariant && !hideCategoryTabs && /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: isMobile ? "1rem" : "1.5rem", marginTop: "0.25rem", marginBottom: "1rem", borderBottom: "1px solid rgba(255, 255, 255, 0.08)", paddingBottom: "0.5rem" }, children: ["all", "property", "market"].map((tab) => /* @__PURE__ */ jsx(
|
|
3056
3654
|
"button",
|
|
3057
3655
|
{
|
|
3058
3656
|
type: "button",
|
|
@@ -3073,10 +3671,229 @@ var PropertyNewsUpdates = React5.forwardRef(
|
|
|
3073
3671
|
},
|
|
3074
3672
|
tab
|
|
3075
3673
|
)) }),
|
|
3674
|
+
pinnedBreaking.length > 0 && /* @__PURE__ */ jsx(
|
|
3675
|
+
"div",
|
|
3676
|
+
{
|
|
3677
|
+
"data-testid": "news-breaking",
|
|
3678
|
+
style: {
|
|
3679
|
+
display: "flex",
|
|
3680
|
+
flexDirection: "column",
|
|
3681
|
+
marginBottom: "0.5rem",
|
|
3682
|
+
// Clips the row while it's being swiped out to the right. The gold
|
|
3683
|
+
// band styling lives on each ITEM below, not here — the whole band
|
|
3684
|
+
// (rule, wash, hairlines) must travel with a swipe, not just the
|
|
3685
|
+
// text inside it (user bug report, 2026-08-20).
|
|
3686
|
+
overflow: "hidden"
|
|
3687
|
+
},
|
|
3688
|
+
children: pinnedBreaking.map((item, i) => {
|
|
3689
|
+
const sentiment = getSentimentInfo(item.sentimentScore);
|
|
3690
|
+
const k = breakingKey(item);
|
|
3691
|
+
const dx = breakingSwipe?.key === k ? breakingSwipe.dx : 0;
|
|
3692
|
+
const finishDismiss = () => {
|
|
3693
|
+
dismissedBreakingKeys.add(k);
|
|
3694
|
+
setDismissingBreakingKey(null);
|
|
3695
|
+
setDismissedVersion((v) => v + 1);
|
|
3696
|
+
};
|
|
3697
|
+
return /* @__PURE__ */ jsxs(
|
|
3698
|
+
"button",
|
|
3699
|
+
{
|
|
3700
|
+
type: "button",
|
|
3701
|
+
className: "property-news-breaking-item",
|
|
3702
|
+
"data-new-breaking": item.isNew ? "true" : void 0,
|
|
3703
|
+
"data-dismissing": dismissingBreakingKey === k ? "true" : void 0,
|
|
3704
|
+
"data-testid": "news-breaking-item",
|
|
3705
|
+
onAnimationEnd: (e) => {
|
|
3706
|
+
if (e.animationName === "propertyNewsBreakingDismiss" && dismissingBreakingKey === k) {
|
|
3707
|
+
finishDismiss();
|
|
3708
|
+
}
|
|
3709
|
+
},
|
|
3710
|
+
onClick: () => {
|
|
3711
|
+
if (suppressBreakingClickRef.current) {
|
|
3712
|
+
suppressBreakingClickRef.current = false;
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3715
|
+
setSelectedItem(item);
|
|
3716
|
+
},
|
|
3717
|
+
onPointerDown: (e) => {
|
|
3718
|
+
breakingSwipeStartXRef.current = e.clientX;
|
|
3719
|
+
breakingSwipeActiveKeyRef.current = k;
|
|
3720
|
+
breakingSwipeMaxRef.current = 0;
|
|
3721
|
+
setBreakingSwipe({ key: k, dx: 0 });
|
|
3722
|
+
try {
|
|
3723
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
3724
|
+
} catch {
|
|
3725
|
+
}
|
|
3726
|
+
},
|
|
3727
|
+
onPointerMove: (e) => {
|
|
3728
|
+
if (breakingSwipeActiveKeyRef.current !== k) return;
|
|
3729
|
+
const delta = e.clientX - breakingSwipeStartXRef.current;
|
|
3730
|
+
breakingSwipeMaxRef.current = Math.max(breakingSwipeMaxRef.current, Math.abs(delta));
|
|
3731
|
+
setBreakingSwipe({ key: k, dx: Math.max(0, delta) });
|
|
3732
|
+
},
|
|
3733
|
+
onPointerUp: (e) => {
|
|
3734
|
+
if (breakingSwipeActiveKeyRef.current !== k) return;
|
|
3735
|
+
breakingSwipeActiveKeyRef.current = null;
|
|
3736
|
+
if (breakingSwipeMaxRef.current > 6) suppressBreakingClickRef.current = true;
|
|
3737
|
+
setBreakingSwipe(null);
|
|
3738
|
+
const drag = Math.max(0, e.clientX - breakingSwipeStartXRef.current);
|
|
3739
|
+
const width = e.currentTarget.offsetWidth || 0;
|
|
3740
|
+
if (drag >= Math.max(BREAKING_SWIPE_MIN_PX, width * 0.3)) {
|
|
3741
|
+
dismissedBreakingKeys.add(k);
|
|
3742
|
+
setDismissedVersion((v) => v + 1);
|
|
3743
|
+
}
|
|
3744
|
+
},
|
|
3745
|
+
onPointerCancel: () => {
|
|
3746
|
+
breakingSwipeActiveKeyRef.current = null;
|
|
3747
|
+
setBreakingSwipe(null);
|
|
3748
|
+
},
|
|
3749
|
+
style: {
|
|
3750
|
+
display: "block",
|
|
3751
|
+
width: "100%",
|
|
3752
|
+
padding: "0.4rem 0 0.5rem 0.6rem",
|
|
3753
|
+
textAlign: "left",
|
|
3754
|
+
background: "none",
|
|
3755
|
+
border: "none",
|
|
3756
|
+
cursor: "pointer",
|
|
3757
|
+
minWidth: 0,
|
|
3758
|
+
// Highlighted on purpose (user call, 2026-08-20): BREAKING is
|
|
3759
|
+
// the one thing on this page allowed to shout. The gold rule,
|
|
3760
|
+
// hairlines and wash live HERE on the item — the whole band
|
|
3761
|
+
// travels when the item is swiped away.
|
|
3762
|
+
borderLeft: "3px solid var(--color-accent, #E6C87E)",
|
|
3763
|
+
borderTop: "1px solid rgba(var(--color-accent-rgb, 230, 200, 126), 0.28)",
|
|
3764
|
+
borderBottom: "1px solid rgba(var(--color-accent-rgb, 230, 200, 126), 0.28)",
|
|
3765
|
+
backgroundImage: "linear-gradient(90deg, rgba(var(--color-accent-rgb, 230, 200, 126), 0.16) 0%, rgba(var(--color-accent-rgb, 230, 200, 126), 0.06) 45%, rgba(var(--color-accent-rgb, 230, 200, 126), 0.02) 100%)",
|
|
3766
|
+
// Horizontal drags are ours; vertical stays with the scroll.
|
|
3767
|
+
touchAction: "pan-y",
|
|
3768
|
+
transform: dx > 0 ? `translateX(${dx}px)` : void 0,
|
|
3769
|
+
opacity: dx > 0 ? Math.max(0.3, 1 - dx / 320) : 1,
|
|
3770
|
+
transition: dx > 0 ? "none" : "transform 0.15s ease, opacity 0.15s ease"
|
|
3771
|
+
},
|
|
3772
|
+
children: [
|
|
3773
|
+
/* @__PURE__ */ jsx(
|
|
3774
|
+
"span",
|
|
3775
|
+
{
|
|
3776
|
+
"data-testid": "news-breaking-title",
|
|
3777
|
+
style: {
|
|
3778
|
+
display: "-webkit-box",
|
|
3779
|
+
WebkitBoxOrient: "vertical",
|
|
3780
|
+
WebkitLineClamp: 2,
|
|
3781
|
+
fontSize: "0.8rem",
|
|
3782
|
+
fontWeight: 600,
|
|
3783
|
+
color: "#fff",
|
|
3784
|
+
lineHeight: 1.35,
|
|
3785
|
+
overflow: "hidden",
|
|
3786
|
+
overflowWrap: "anywhere"
|
|
3787
|
+
},
|
|
3788
|
+
children: item.title
|
|
3789
|
+
}
|
|
3790
|
+
),
|
|
3791
|
+
/* @__PURE__ */ jsxs(
|
|
3792
|
+
"span",
|
|
3793
|
+
{
|
|
3794
|
+
style: {
|
|
3795
|
+
display: "flex",
|
|
3796
|
+
alignItems: "center",
|
|
3797
|
+
gap: "0.45rem",
|
|
3798
|
+
marginTop: "0.2rem"
|
|
3799
|
+
},
|
|
3800
|
+
children: [
|
|
3801
|
+
(() => {
|
|
3802
|
+
const age = rowAgeLabel(item.publishedAt, breakingNow);
|
|
3803
|
+
return age ? /* @__PURE__ */ jsx(
|
|
3804
|
+
"span",
|
|
3805
|
+
{
|
|
3806
|
+
"data-age-fresh": age.fresh ? "true" : void 0,
|
|
3807
|
+
style: {
|
|
3808
|
+
fontSize: "0.64rem",
|
|
3809
|
+
fontWeight: 600,
|
|
3810
|
+
fontVariantNumeric: "tabular-nums",
|
|
3811
|
+
color: age.fresh ? "var(--color-accent, #E6C87E)" : "rgba(255,255,255,0.65)"
|
|
3812
|
+
},
|
|
3813
|
+
children: age.label
|
|
3814
|
+
}
|
|
3815
|
+
) : null;
|
|
3816
|
+
})(),
|
|
3817
|
+
/* @__PURE__ */ jsx(
|
|
3818
|
+
"span",
|
|
3819
|
+
{
|
|
3820
|
+
"data-testid": "breaking-tag",
|
|
3821
|
+
style: {
|
|
3822
|
+
fontSize: "0.55rem",
|
|
3823
|
+
fontWeight: 700,
|
|
3824
|
+
letterSpacing: "0.09em",
|
|
3825
|
+
color: "#0a0b0e",
|
|
3826
|
+
background: "var(--color-accent, #E6C87E)",
|
|
3827
|
+
padding: "0.16rem 0.34rem 0.13rem",
|
|
3828
|
+
borderRadius: "3px",
|
|
3829
|
+
lineHeight: 1
|
|
3830
|
+
},
|
|
3831
|
+
children: "BREAKING"
|
|
3832
|
+
}
|
|
3833
|
+
),
|
|
3834
|
+
sentiment ? /* @__PURE__ */ jsxs(
|
|
3835
|
+
"span",
|
|
3836
|
+
{
|
|
3837
|
+
style: {
|
|
3838
|
+
display: "inline-flex",
|
|
3839
|
+
alignItems: "center",
|
|
3840
|
+
gap: "0.3rem",
|
|
3841
|
+
fontSize: "0.58rem",
|
|
3842
|
+
fontWeight: 600,
|
|
3843
|
+
color: sentiment.color
|
|
3844
|
+
},
|
|
3845
|
+
children: [
|
|
3846
|
+
sentiment.arrow ? `${sentiment.arrow} ` : "",
|
|
3847
|
+
sentiment.label,
|
|
3848
|
+
item.sentimentScore != null && /* @__PURE__ */ jsx(SentimentBar, { score: item.sentimentScore, color: sentiment.color })
|
|
3849
|
+
]
|
|
3850
|
+
}
|
|
3851
|
+
) : null,
|
|
3852
|
+
/* @__PURE__ */ jsx(
|
|
3853
|
+
"span",
|
|
3854
|
+
{
|
|
3855
|
+
className: "property-news-breaking-dismiss",
|
|
3856
|
+
"data-testid": "breaking-dismiss",
|
|
3857
|
+
role: "button",
|
|
3858
|
+
tabIndex: 0,
|
|
3859
|
+
"aria-label": `Dismiss breaking story: ${item.title}`,
|
|
3860
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
3861
|
+
onClick: (e) => {
|
|
3862
|
+
e.stopPropagation();
|
|
3863
|
+
setDismissingBreakingKey(k);
|
|
3864
|
+
},
|
|
3865
|
+
onKeyDown: (e) => {
|
|
3866
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
3867
|
+
e.preventDefault();
|
|
3868
|
+
e.stopPropagation();
|
|
3869
|
+
setDismissingBreakingKey(k);
|
|
3870
|
+
},
|
|
3871
|
+
style: {
|
|
3872
|
+
marginLeft: "auto",
|
|
3873
|
+
padding: "0.2rem 0.35rem 0.2rem 0.5rem",
|
|
3874
|
+
fontSize: "0.58rem",
|
|
3875
|
+
fontWeight: 600,
|
|
3876
|
+
color: "rgba(255,255,255,0.58)",
|
|
3877
|
+
cursor: "pointer",
|
|
3878
|
+
whiteSpace: "nowrap"
|
|
3879
|
+
},
|
|
3880
|
+
children: "Dismiss"
|
|
3881
|
+
}
|
|
3882
|
+
)
|
|
3883
|
+
]
|
|
3884
|
+
}
|
|
3885
|
+
)
|
|
3886
|
+
]
|
|
3887
|
+
},
|
|
3888
|
+
item.id ?? `breaking-${i}`
|
|
3889
|
+
);
|
|
3890
|
+
})
|
|
3891
|
+
}
|
|
3892
|
+
),
|
|
3076
3893
|
/* @__PURE__ */ jsx(
|
|
3077
3894
|
"div",
|
|
3078
3895
|
{
|
|
3079
|
-
className: cn("flex flex-1 flex-col overflow-y-auto overflow-x-hidden", isHomeVariant ? "gap-0" : "mt-4 gap-3"),
|
|
3896
|
+
className: cn("news-list-scroll flex flex-1 flex-col overflow-y-auto overflow-x-hidden", isHomeVariant ? "gap-0" : "mt-4 gap-3"),
|
|
3080
3897
|
style: !isPurchaseVariant && !isHomeVariant ? { minHeight: `${ITEMS_PER_PAGE * 86}px` } : void 0,
|
|
3081
3898
|
children: isPurchaseVariant ? purchaseItems.length > 0 ? purchaseItems.slice(0, 7).map((purchase, index) => {
|
|
3082
3899
|
const maxAmount = 6e4;
|
|
@@ -3107,10 +3924,12 @@ var PropertyNewsUpdates = React5.forwardRef(
|
|
|
3107
3924
|
const key = item.displayId ?? item.id ?? `${item.title}-${index}`;
|
|
3108
3925
|
const isProperty = item.type === "property";
|
|
3109
3926
|
const sentimentInfo = getSentimentInfo(item.sentimentScore);
|
|
3927
|
+
const isBreakingRow = pinBreaking && Boolean(item.breaking) && breakingRowKeys.has(breakingKey(item));
|
|
3110
3928
|
return /* @__PURE__ */ jsxs(
|
|
3111
3929
|
"button",
|
|
3112
3930
|
{
|
|
3113
3931
|
type: "button",
|
|
3932
|
+
"data-breaking-row": isBreakingRow ? "true" : void 0,
|
|
3114
3933
|
onClick: () => setSelectedItem(item),
|
|
3115
3934
|
style: {
|
|
3116
3935
|
width: "100%",
|
|
@@ -3126,6 +3945,10 @@ var PropertyNewsUpdates = React5.forwardRef(
|
|
|
3126
3945
|
gap: isMobile ? "0.25rem" : "0.5rem",
|
|
3127
3946
|
color: "inherit",
|
|
3128
3947
|
transition: "background-color 0.15s"
|
|
3948
|
+
// No gold wash or left edge on a dismissed row: once swiped
|
|
3949
|
+
// out of the pin it is back in the ordinary feed, and the
|
|
3950
|
+
// BREAKING badge below is enough to find it again. Only the
|
|
3951
|
+
// PINNED strip keeps the gold treatment.
|
|
3129
3952
|
},
|
|
3130
3953
|
onMouseEnter: (e) => {
|
|
3131
3954
|
e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.04)";
|
|
@@ -3139,15 +3962,91 @@ var PropertyNewsUpdates = React5.forwardRef(
|
|
|
3139
3962
|
},
|
|
3140
3963
|
children: [
|
|
3141
3964
|
/* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
|
|
3142
|
-
/* @__PURE__ */ jsx("h3", { style: { fontSize:
|
|
3965
|
+
/* @__PURE__ */ jsx("h3", { style: { fontSize: "0.8rem", fontWeight: 500, marginBottom: isMobile ? "0.2rem" : "0.3rem", color: "#f8f9fa", lineHeight: 1.35, transition: "color 0.15s" }, children: item.title }),
|
|
3143
3966
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: isMobile ? "0.35rem" : "0.5rem", alignItems: "center", flexWrap: "wrap" }, children: [
|
|
3144
|
-
/* @__PURE__ */ jsx(
|
|
3145
|
-
|
|
3967
|
+
isBreakingRow && /* @__PURE__ */ jsx(
|
|
3968
|
+
"span",
|
|
3969
|
+
{
|
|
3970
|
+
"data-testid": "breaking-tag",
|
|
3971
|
+
style: {
|
|
3972
|
+
fontSize: "0.55rem",
|
|
3973
|
+
fontWeight: 700,
|
|
3974
|
+
letterSpacing: "0.09em",
|
|
3975
|
+
color: "#0a0b0e",
|
|
3976
|
+
background: "var(--color-accent, #E6C87E)",
|
|
3977
|
+
padding: "0.16rem 0.34rem 0.13rem",
|
|
3978
|
+
borderRadius: "3px",
|
|
3979
|
+
lineHeight: 1
|
|
3980
|
+
},
|
|
3981
|
+
children: "BREAKING"
|
|
3982
|
+
}
|
|
3983
|
+
),
|
|
3984
|
+
(() => {
|
|
3985
|
+
const age = rowAgeLabel(item.publishedAt, rowNow);
|
|
3986
|
+
return age ? /* @__PURE__ */ jsx(
|
|
3987
|
+
"span",
|
|
3988
|
+
{
|
|
3989
|
+
"data-testid": "news-age",
|
|
3990
|
+
"data-age-fresh": age.fresh ? "true" : void 0,
|
|
3991
|
+
style: {
|
|
3992
|
+
// Sized against the 0.8rem row title (matches the
|
|
3993
|
+
// breaking strip's meta scale).
|
|
3994
|
+
fontSize: isMobile ? "0.58rem" : "0.64rem",
|
|
3995
|
+
fontWeight: 600,
|
|
3996
|
+
fontVariantNumeric: "tabular-nums",
|
|
3997
|
+
color: age.fresh ? "var(--color-accent, #E6C87E)" : "rgba(255,255,255,0.65)"
|
|
3998
|
+
},
|
|
3999
|
+
children: age.label
|
|
4000
|
+
}
|
|
4001
|
+
) : /* @__PURE__ */ jsx("span", { style: { color: "#848e9c", fontSize: isMobile ? "0.55rem" : "0.7rem" }, children: formatDateShort(item.date) });
|
|
4002
|
+
})(),
|
|
4003
|
+
showAssetBadges && isProperty && (item.assets ?? (item.asset ? [item.asset] : [])).map((badgeAsset) => /* @__PURE__ */ jsx(
|
|
4004
|
+
"span",
|
|
4005
|
+
{
|
|
4006
|
+
className: "news-badge-wrap",
|
|
4007
|
+
onClick: onAssetOpen ? (e) => {
|
|
4008
|
+
e.stopPropagation();
|
|
4009
|
+
e.preventDefault();
|
|
4010
|
+
onAssetOpen(badgeAsset);
|
|
4011
|
+
} : void 0,
|
|
4012
|
+
onMouseEnter: (e) => showBadgeTip(e.currentTarget, badgeAsset),
|
|
4013
|
+
onMouseLeave: () => setBadgeTip(null),
|
|
4014
|
+
style: { cursor: onAssetOpen ? "pointer" : void 0 },
|
|
4015
|
+
children: /* @__PURE__ */ jsx(
|
|
4016
|
+
"span",
|
|
4017
|
+
{
|
|
4018
|
+
className: "news-badge-box",
|
|
4019
|
+
"data-testid": "news-asset-badge",
|
|
4020
|
+
style: {
|
|
4021
|
+
display: "inline-flex",
|
|
4022
|
+
alignItems: "center",
|
|
4023
|
+
justifyContent: "center",
|
|
4024
|
+
width: "20px",
|
|
4025
|
+
height: "20px",
|
|
4026
|
+
borderRadius: "5px",
|
|
4027
|
+
overflow: "hidden",
|
|
4028
|
+
background: "rgba(255,255,255,0.08)",
|
|
4029
|
+
flexShrink: 0
|
|
4030
|
+
},
|
|
4031
|
+
children: badgeAsset.imageUrl ? /* @__PURE__ */ jsx(
|
|
4032
|
+
"img",
|
|
4033
|
+
{
|
|
4034
|
+
src: badgeAsset.imageUrl,
|
|
4035
|
+
alt: badgeAsset.name ?? badgeAsset.ticker,
|
|
4036
|
+
style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
|
|
4037
|
+
}
|
|
4038
|
+
) : /* @__PURE__ */ jsx("span", { style: { fontSize: "0.45rem", fontWeight: 700, letterSpacing: "0.02em", color: "rgba(255,255,255,0.75)" }, children: badgeAsset.ticker.slice(0, 4) })
|
|
4039
|
+
}
|
|
4040
|
+
)
|
|
4041
|
+
},
|
|
4042
|
+
badgeAsset.ticker
|
|
4043
|
+
)),
|
|
3146
4044
|
/* @__PURE__ */ jsx("span", { style: { padding: isMobile ? "0.1rem 0.35rem" : "0.15rem 0.4rem", borderRadius: "2px", fontSize: isMobile ? "0.52rem" : "0.65rem", fontWeight: 500, backgroundColor: isProperty ? "rgba(14,203,129,0.1)" : "rgba(240,185,11,0.1)", color: isProperty ? "#0ecb81" : "#E6C87E" }, children: isProperty ? "Property Update" : "Market News" }),
|
|
3147
|
-
sentimentInfo && /* @__PURE__ */
|
|
4045
|
+
sentimentInfo && /* @__PURE__ */ jsx("span", { style: { fontSize: isMobile ? "0.52rem" : "0.65rem", fontWeight: 600, color: sentimentInfo.color }, children: /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "0.3rem" }, children: [
|
|
3148
4046
|
sentimentInfo.arrow ? `${sentimentInfo.arrow} ` : "",
|
|
3149
|
-
sentimentInfo.label
|
|
3150
|
-
|
|
4047
|
+
sentimentInfo.label,
|
|
4048
|
+
item.sentimentScore != null && /* @__PURE__ */ jsx(SentimentBar, { score: item.sentimentScore, color: sentimentInfo.color })
|
|
4049
|
+
] }) })
|
|
3151
4050
|
] })
|
|
3152
4051
|
] }),
|
|
3153
4052
|
/* @__PURE__ */ jsx("div", { style: { color: "#848e9c", paddingTop: "2px", flexShrink: 0 }, children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z" }) }) })
|
|
@@ -3196,15 +4095,13 @@ var PropertyNewsUpdates = React5.forwardRef(
|
|
|
3196
4095
|
children: [
|
|
3197
4096
|
/* @__PURE__ */ jsx("p", { style: { fontSize: "0.9375rem", fontWeight: 500, marginBottom: "0.4rem", color: "#fff", lineHeight: 1.4 }, children: item.title }),
|
|
3198
4097
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: "0.5rem" }, children: [
|
|
3199
|
-
/* @__PURE__ */
|
|
3200
|
-
/* @__PURE__ */ jsx("span", { style: { fontSize: "0.75rem", color: isHighlighted ? "#0ecb81" : "#b5b8c5", whiteSpace: "nowrap" }, children: dateLabel }),
|
|
3201
|
-
item.source && /* @__PURE__ */ jsx("span", { style: { padding: "0.15rem 0.45rem", borderRadius: "3px", fontSize: "0.65rem", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.07)", color: "#848e9c", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: "120px" }, children: item.source })
|
|
3202
|
-
] }),
|
|
4098
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", minWidth: 0, flexWrap: "wrap" }, children: /* @__PURE__ */ jsx("span", { style: { fontSize: "0.75rem", color: isHighlighted ? "#0ecb81" : "#b5b8c5", whiteSpace: "nowrap" }, children: dateLabel }) }),
|
|
3203
4099
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: "0.3rem", flexShrink: 0 }, children: [
|
|
3204
|
-
sentimentInfo && /* @__PURE__ */
|
|
4100
|
+
sentimentInfo && /* @__PURE__ */ jsx("span", { style: { fontSize: "0.65rem", fontWeight: 700, color: sentimentInfo.color, whiteSpace: "nowrap" }, children: /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "0.3rem" }, children: [
|
|
3205
4101
|
sentimentInfo.arrow ? `${sentimentInfo.arrow} ` : "",
|
|
3206
|
-
sentimentInfo.label
|
|
3207
|
-
|
|
4102
|
+
sentimentInfo.label,
|
|
4103
|
+
item.sentimentScore != null && /* @__PURE__ */ jsx(SentimentBar, { score: item.sentimentScore, color: sentimentInfo.color })
|
|
4104
|
+
] }) }),
|
|
3208
4105
|
/* @__PURE__ */ jsx("span", { style: { padding: "0.25rem 0.6rem", borderRadius: "4px", border: `1px solid ${catStyle.borderColor}`, backgroundColor: catStyle.backgroundColor, color: catStyle.color, fontSize: "0.68rem", fontWeight: 600, textTransform: "uppercase", whiteSpace: "nowrap" }, children: item.type === "property" ? catStyle.label : "Market News" })
|
|
3209
4106
|
] })
|
|
3210
4107
|
] })
|
|
@@ -3277,7 +4174,21 @@ var PropertyNewsUpdates = React5.forwardRef(
|
|
|
3277
4174
|
}
|
|
3278
4175
|
)
|
|
3279
4176
|
] }),
|
|
3280
|
-
selectedItem && !isPurchaseVariant && /* @__PURE__ */ jsx(NewsArticleModal, { item: selectedItem, onClose: () => setSelectedItem(null) })
|
|
4177
|
+
selectedItem && !isPurchaseVariant && /* @__PURE__ */ jsx(NewsArticleModal, { item: selectedItem, onClose: () => setSelectedItem(null) }),
|
|
4178
|
+
badgeTip && ReactDOM.createPortal(
|
|
4179
|
+
/* @__PURE__ */ jsx(
|
|
4180
|
+
"span",
|
|
4181
|
+
{
|
|
4182
|
+
className: "news-badge-tipbox",
|
|
4183
|
+
"data-testid": "news-asset-tip",
|
|
4184
|
+
"data-tip-below": badgeTip.below ? "true" : void 0,
|
|
4185
|
+
"aria-hidden": true,
|
|
4186
|
+
style: { position: "fixed", left: `${badgeTip.left}px`, top: `${badgeTip.top}px` },
|
|
4187
|
+
children: /* @__PURE__ */ jsx("span", { children: badgeTip.asset.name ? `${badgeTip.asset.name} (${badgeTip.asset.ticker})` : badgeTip.asset.ticker })
|
|
4188
|
+
}
|
|
4189
|
+
),
|
|
4190
|
+
document.body
|
|
4191
|
+
)
|
|
3281
4192
|
]
|
|
3282
4193
|
}
|
|
3283
4194
|
);
|
|
@@ -3330,7 +4241,7 @@ var defaultFormat = (value) => new Intl.NumberFormat("en-US", {
|
|
|
3330
4241
|
notation: value >= 1e5 ? "compact" : "standard",
|
|
3331
4242
|
maximumFractionDigits: value >= 1e3 ? 0 : 2
|
|
3332
4243
|
}).format(value);
|
|
3333
|
-
var TradingSlider =
|
|
4244
|
+
var TradingSlider = React9.forwardRef(
|
|
3334
4245
|
({
|
|
3335
4246
|
label = "Trade size",
|
|
3336
4247
|
helperText = "Drag to pick the desired notional.",
|
|
@@ -3348,7 +4259,7 @@ var TradingSlider = React5.forwardRef(
|
|
|
3348
4259
|
...rest
|
|
3349
4260
|
}, ref) => {
|
|
3350
4261
|
const isControlled = value !== void 0;
|
|
3351
|
-
const [internalValue, setInternalValue] =
|
|
4262
|
+
const [internalValue, setInternalValue] = React9.useState(
|
|
3352
4263
|
defaultValue ?? (typeof min === "number" ? min : 0)
|
|
3353
4264
|
);
|
|
3354
4265
|
const currentValue = isControlled ? Number(value) : internalValue;
|
|
@@ -3451,7 +4362,7 @@ var MobileToggleButton = styled10.button`
|
|
|
3451
4362
|
padding: 0.6rem 0.5rem;
|
|
3452
4363
|
}
|
|
3453
4364
|
`;
|
|
3454
|
-
var MobileTradeNav =
|
|
4365
|
+
var MobileTradeNav = React9.forwardRef(
|
|
3455
4366
|
({ className, items, activeId, onChange, ...props }, ref) => {
|
|
3456
4367
|
return /* @__PURE__ */ jsx(MobileToggleContainer, { ref, className: cn(className), ...props, children: items.map((item) => /* @__PURE__ */ jsxs(
|
|
3457
4368
|
MobileToggleButton,
|
|
@@ -3977,7 +4888,7 @@ var formatPercent = (value, fractionDigits = 2) => {
|
|
|
3977
4888
|
if (value == null || Number.isNaN(value)) return "\u2014";
|
|
3978
4889
|
return `${value.toFixed(fractionDigits)}%`;
|
|
3979
4890
|
};
|
|
3980
|
-
var YourOrders =
|
|
4891
|
+
var YourOrders = React9.forwardRef(
|
|
3981
4892
|
({
|
|
3982
4893
|
className,
|
|
3983
4894
|
title,
|
|
@@ -3990,10 +4901,10 @@ var YourOrders = React5.forwardRef(
|
|
|
3990
4901
|
pageSize: pageSizeOverride,
|
|
3991
4902
|
...props
|
|
3992
4903
|
}, ref) => {
|
|
3993
|
-
const [internalActiveTab, setInternalActiveTab] =
|
|
3994
|
-
const [page, setPage] =
|
|
4904
|
+
const [internalActiveTab, setInternalActiveTab] = React9.useState(tabs?.[0]?.id ?? "portfolio");
|
|
4905
|
+
const [page, setPage] = React9.useState(0);
|
|
3995
4906
|
const effectiveActiveTabId = activeTabId ?? internalActiveTab;
|
|
3996
|
-
|
|
4907
|
+
React9.useEffect(() => {
|
|
3997
4908
|
setPage(0);
|
|
3998
4909
|
}, [effectiveActiveTabId]);
|
|
3999
4910
|
const handleTabChange = (tabId) => {
|
|
@@ -4375,7 +5286,7 @@ function createCandlestickSeries(chart, options) {
|
|
|
4375
5286
|
}
|
|
4376
5287
|
throw new Error("Candlestick series API is not available in the current lightweight-charts version.");
|
|
4377
5288
|
}
|
|
4378
|
-
var PriceChart =
|
|
5289
|
+
var PriceChart = React9.forwardRef(
|
|
4379
5290
|
({
|
|
4380
5291
|
className,
|
|
4381
5292
|
title = "Price Chart",
|
|
@@ -4389,18 +5300,18 @@ var PriceChart = React5.forwardRef(
|
|
|
4389
5300
|
height = 301.52,
|
|
4390
5301
|
...props
|
|
4391
5302
|
}, ref) => {
|
|
4392
|
-
const containerRef =
|
|
4393
|
-
const chartRef =
|
|
4394
|
-
const seriesRef =
|
|
4395
|
-
const priceLineRef =
|
|
4396
|
-
const [hoveredRange, setHoveredRange] =
|
|
4397
|
-
const [dropdownOpen, setDropdownOpen] =
|
|
4398
|
-
const dropdownRef =
|
|
4399
|
-
const isAutoScrollRef =
|
|
5303
|
+
const containerRef = React9.useRef(null);
|
|
5304
|
+
const chartRef = React9.useRef(null);
|
|
5305
|
+
const seriesRef = React9.useRef(null);
|
|
5306
|
+
const priceLineRef = React9.useRef(null);
|
|
5307
|
+
const [hoveredRange, setHoveredRange] = React9.useState(null);
|
|
5308
|
+
const [dropdownOpen, setDropdownOpen] = React9.useState(false);
|
|
5309
|
+
const dropdownRef = React9.useRef(null);
|
|
5310
|
+
const isAutoScrollRef = React9.useRef(true);
|
|
4400
5311
|
const visibleRanges = ranges.slice(0, VISIBLE_RANGE_COUNT);
|
|
4401
5312
|
const dropdownRanges = ranges.slice(VISIBLE_RANGE_COUNT);
|
|
4402
5313
|
const selectedInDropdown = dropdownRanges.includes(selectedRange);
|
|
4403
|
-
|
|
5314
|
+
React9.useEffect(() => {
|
|
4404
5315
|
const handleClickOutside = (e) => {
|
|
4405
5316
|
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
4406
5317
|
setDropdownOpen(false);
|
|
@@ -4409,25 +5320,25 @@ var PriceChart = React5.forwardRef(
|
|
|
4409
5320
|
document.addEventListener("mousedown", handleClickOutside);
|
|
4410
5321
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
4411
5322
|
}, []);
|
|
4412
|
-
const resolvedPrice =
|
|
5323
|
+
const resolvedPrice = React9.useMemo(() => {
|
|
4413
5324
|
if (price != null) return price;
|
|
4414
5325
|
const last = data.at(-1);
|
|
4415
5326
|
return last?.close;
|
|
4416
5327
|
}, [data, price]);
|
|
4417
|
-
const inferredChangePercent =
|
|
5328
|
+
const inferredChangePercent = React9.useMemo(() => {
|
|
4418
5329
|
if (changePercent != null) return changePercent;
|
|
4419
5330
|
const first = data[0]?.open;
|
|
4420
5331
|
const last = data.at(-1)?.close;
|
|
4421
5332
|
if (first == null || last == null || first === 0) return void 0;
|
|
4422
5333
|
return (last - first) / first * 100;
|
|
4423
5334
|
}, [changePercent, data]);
|
|
4424
|
-
const dollarChange =
|
|
5335
|
+
const dollarChange = React9.useMemo(() => {
|
|
4425
5336
|
const first = data[0]?.open;
|
|
4426
5337
|
const last = data.at(-1)?.close;
|
|
4427
5338
|
if (first == null || last == null) return void 0;
|
|
4428
5339
|
return last - first;
|
|
4429
5340
|
}, [data]);
|
|
4430
|
-
|
|
5341
|
+
React9.useEffect(() => {
|
|
4431
5342
|
const el = containerRef.current;
|
|
4432
5343
|
if (!el) return;
|
|
4433
5344
|
const chart = LightweightCharts.createChart(el, {
|
|
@@ -4472,7 +5383,7 @@ var PriceChart = React5.forwardRef(
|
|
|
4472
5383
|
chart.remove();
|
|
4473
5384
|
};
|
|
4474
5385
|
}, []);
|
|
4475
|
-
|
|
5386
|
+
React9.useEffect(() => {
|
|
4476
5387
|
const chart = chartRef.current;
|
|
4477
5388
|
if (!chart) return;
|
|
4478
5389
|
const effectiveRange = selectedRange ?? ranges?.[0] ?? "1D";
|
|
@@ -4480,7 +5391,7 @@ var PriceChart = React5.forwardRef(
|
|
|
4480
5391
|
timeScale: getTimeScaleOptions(effectiveRange)
|
|
4481
5392
|
});
|
|
4482
5393
|
}, [selectedRange, ranges]);
|
|
4483
|
-
|
|
5394
|
+
React9.useEffect(() => {
|
|
4484
5395
|
const chart = chartRef.current;
|
|
4485
5396
|
const series = seriesRef.current;
|
|
4486
5397
|
if (!chart || !series) return;
|
|
@@ -4695,7 +5606,7 @@ var formatPrice3 = (value, currencySymbol, compact) => {
|
|
|
4695
5606
|
maximumFractionDigits: 2
|
|
4696
5607
|
})}`;
|
|
4697
5608
|
};
|
|
4698
|
-
var PropertyHeroHeader =
|
|
5609
|
+
var PropertyHeroHeader = React9.forwardRef(
|
|
4699
5610
|
({
|
|
4700
5611
|
className,
|
|
4701
5612
|
imageUrl,
|
|
@@ -4729,12 +5640,12 @@ var PropertyHeroHeader = React5.forwardRef(
|
|
|
4729
5640
|
const isPositive = changePercent == null ? void 0 : changePercent >= 0;
|
|
4730
5641
|
const accentColor = "#e6c87e";
|
|
4731
5642
|
const tradeHoverColor = "#f5dd9a";
|
|
4732
|
-
const [isTradeInteracting, setIsTradeInteracting] =
|
|
4733
|
-
const [isOfferInteracting, setIsOfferInteracting] =
|
|
4734
|
-
const [isMobile, setIsMobile] =
|
|
5643
|
+
const [isTradeInteracting, setIsTradeInteracting] = React9.useState(false);
|
|
5644
|
+
const [isOfferInteracting, setIsOfferInteracting] = React9.useState(false);
|
|
5645
|
+
const [isMobile, setIsMobile] = React9.useState(
|
|
4735
5646
|
() => typeof window !== "undefined" && window.innerWidth <= 768
|
|
4736
5647
|
);
|
|
4737
|
-
|
|
5648
|
+
React9.useEffect(() => {
|
|
4738
5649
|
const onResize = () => setIsMobile(window.innerWidth <= 768);
|
|
4739
5650
|
window.addEventListener("resize", onResize);
|
|
4740
5651
|
return () => window.removeEventListener("resize", onResize);
|
|
@@ -5414,7 +6325,7 @@ var Header = ({
|
|
|
5414
6325
|
setShowLoginPopup(true);
|
|
5415
6326
|
}
|
|
5416
6327
|
};
|
|
5417
|
-
const handleLoginPopupClose =
|
|
6328
|
+
const handleLoginPopupClose = React9__default.useCallback(() => {
|
|
5418
6329
|
setShowLoginPopup(false);
|
|
5419
6330
|
setLoginPopupInitialView(void 0);
|
|
5420
6331
|
}, []);
|
|
@@ -6578,10 +7489,10 @@ var PBOperator = styled10.span`
|
|
|
6578
7489
|
font-size: 0.55rem;
|
|
6579
7490
|
}
|
|
6580
7491
|
`;
|
|
6581
|
-
var PropertySubheader =
|
|
7492
|
+
var PropertySubheader = React9.forwardRef(
|
|
6582
7493
|
({ className, tabs, activeTabId, onTabChange, actions, ...props }, ref) => {
|
|
6583
|
-
const tabsContainerRef =
|
|
6584
|
-
|
|
7494
|
+
const tabsContainerRef = React9.useRef(null);
|
|
7495
|
+
React9.useEffect(() => {
|
|
6585
7496
|
const container = tabsContainerRef.current;
|
|
6586
7497
|
if (!container) return;
|
|
6587
7498
|
const isMobile = window.innerWidth <= 768;
|
|
@@ -6778,9 +7689,9 @@ var LoginPopup = ({
|
|
|
6778
7689
|
const [fiatFundingLoading, setFiatFundingLoading] = useState(false);
|
|
6779
7690
|
const [fundingError, setFundingError] = useState("");
|
|
6780
7691
|
const [transakWidgetUrl, setTransakWidgetUrl] = useState(null);
|
|
6781
|
-
const suppressAutoCloseRef =
|
|
6782
|
-
const validatedCodeRef =
|
|
6783
|
-
const finishGate =
|
|
7692
|
+
const suppressAutoCloseRef = React9__default.useRef(false);
|
|
7693
|
+
const validatedCodeRef = React9__default.useRef(null);
|
|
7694
|
+
const finishGate = React9__default.useCallback(async () => {
|
|
6784
7695
|
if (!gate) return true;
|
|
6785
7696
|
const code = validatedCodeRef.current;
|
|
6786
7697
|
if (code) {
|
|
@@ -6806,7 +7717,7 @@ var LoginPopup = ({
|
|
|
6806
7717
|
);
|
|
6807
7718
|
return false;
|
|
6808
7719
|
}, [gate, onSubmitReferralCode, onCheckAccess]);
|
|
6809
|
-
const runCodeValidation =
|
|
7720
|
+
const runCodeValidation = React9__default.useCallback(async (fullCode) => {
|
|
6810
7721
|
setCodeStatus("checking");
|
|
6811
7722
|
setError("");
|
|
6812
7723
|
try {
|
|
@@ -6829,7 +7740,7 @@ var LoginPopup = ({
|
|
|
6829
7740
|
setCodeStatus("invalid");
|
|
6830
7741
|
}
|
|
6831
7742
|
}, [onValidateCode, isAuthenticated, finishGate, onClose]);
|
|
6832
|
-
const autoCodeTriedRef =
|
|
7743
|
+
const autoCodeTriedRef = React9__default.useRef(false);
|
|
6833
7744
|
useEffect(() => {
|
|
6834
7745
|
if (autoCodeTriedRef.current || !gate || !initialCode) return;
|
|
6835
7746
|
const full = normalizeAccessCode(initialCode);
|
|
@@ -7112,7 +8023,7 @@ var LoginPopup = ({
|
|
|
7112
8023
|
setLoading(false);
|
|
7113
8024
|
}
|
|
7114
8025
|
};
|
|
7115
|
-
const runGateOtpVerify =
|
|
8026
|
+
const runGateOtpVerify = React9__default.useCallback(async (code) => {
|
|
7116
8027
|
if (!onVerifyEmailCode) return;
|
|
7117
8028
|
setOtpStatus("checking");
|
|
7118
8029
|
setError("");
|
|
@@ -9336,7 +10247,7 @@ function OnboardingGuide({
|
|
|
9336
10247
|
] })
|
|
9337
10248
|
] }) });
|
|
9338
10249
|
}
|
|
9339
|
-
var PropertyCompareBar =
|
|
10250
|
+
var PropertyCompareBar = React9.forwardRef(
|
|
9340
10251
|
({
|
|
9341
10252
|
className,
|
|
9342
10253
|
addresses,
|
|
@@ -9347,7 +10258,7 @@ var PropertyCompareBar = React5.forwardRef(
|
|
|
9347
10258
|
price,
|
|
9348
10259
|
...props
|
|
9349
10260
|
}, ref) => {
|
|
9350
|
-
const normalizedAddresses =
|
|
10261
|
+
const normalizedAddresses = React9.useMemo(() => {
|
|
9351
10262
|
return addresses.map(
|
|
9352
10263
|
(option) => typeof option === "string" ? { id: option, label: option } : option
|
|
9353
10264
|
);
|
|
@@ -9355,11 +10266,11 @@ var PropertyCompareBar = React5.forwardRef(
|
|
|
9355
10266
|
const hasAddresses = normalizedAddresses.length > 0;
|
|
9356
10267
|
const firstAddressId = normalizedAddresses[0]?.id;
|
|
9357
10268
|
const isControlled = selectedAddressId !== void 0;
|
|
9358
|
-
const [internalSelectedId, setInternalSelectedId] =
|
|
10269
|
+
const [internalSelectedId, setInternalSelectedId] = React9.useState(
|
|
9359
10270
|
() => isControlled ? void 0 : firstAddressId
|
|
9360
10271
|
);
|
|
9361
10272
|
const resolvedSelectedId = isControlled ? selectedAddressId : internalSelectedId;
|
|
9362
|
-
|
|
10273
|
+
React9.useEffect(() => {
|
|
9363
10274
|
if (!isControlled) {
|
|
9364
10275
|
setInternalSelectedId((current) => {
|
|
9365
10276
|
if (current != null && normalizedAddresses.some((option) => option.id === current)) {
|
|
@@ -9370,9 +10281,9 @@ var PropertyCompareBar = React5.forwardRef(
|
|
|
9370
10281
|
}
|
|
9371
10282
|
}, [firstAddressId, isControlled, normalizedAddresses]);
|
|
9372
10283
|
const selectedOption = normalizedAddresses.find((option) => option.id === resolvedSelectedId) ?? normalizedAddresses[0];
|
|
9373
|
-
const [isDropdownOpen, setIsDropdownOpen] =
|
|
9374
|
-
const dropdownRef =
|
|
9375
|
-
|
|
10284
|
+
const [isDropdownOpen, setIsDropdownOpen] = React9.useState(false);
|
|
10285
|
+
const dropdownRef = React9.useRef(null);
|
|
10286
|
+
React9.useEffect(() => {
|
|
9376
10287
|
if (!isDropdownOpen) return;
|
|
9377
10288
|
const handleClick = (event) => {
|
|
9378
10289
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
@@ -12800,6 +13711,32 @@ var SignInButton = styled10.button`
|
|
|
12800
13711
|
background: rgba(240, 185, 11, 0.1);
|
|
12801
13712
|
}
|
|
12802
13713
|
`;
|
|
13714
|
+
var SORT_COLUMNS = [
|
|
13715
|
+
{ field: "price", label: "Price" },
|
|
13716
|
+
{ field: "change", label: "24h Change" },
|
|
13717
|
+
{ field: "volume", label: "24h Volume", hideOnPhone: true },
|
|
13718
|
+
{ field: "value", label: "Asset Value", hideOnPhone: true }
|
|
13719
|
+
];
|
|
13720
|
+
function sortValue(item, field) {
|
|
13721
|
+
switch (field) {
|
|
13722
|
+
case "volume":
|
|
13723
|
+
return item.volume24h ?? null;
|
|
13724
|
+
case "price":
|
|
13725
|
+
return item.price ?? null;
|
|
13726
|
+
case "change":
|
|
13727
|
+
return item.changePercent ?? null;
|
|
13728
|
+
case "value":
|
|
13729
|
+
return item.propertyValue ?? null;
|
|
13730
|
+
}
|
|
13731
|
+
}
|
|
13732
|
+
function fmtWholeUsd(value) {
|
|
13733
|
+
if (value == null) return null;
|
|
13734
|
+
return `$${value.toLocaleString(void 0, { maximumFractionDigits: 0 })}`;
|
|
13735
|
+
}
|
|
13736
|
+
function fmtPrice(value) {
|
|
13737
|
+
if (value == null) return null;
|
|
13738
|
+
return `$${value.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
13739
|
+
}
|
|
12803
13740
|
function displayName(item) {
|
|
12804
13741
|
if (item.name) return item.name;
|
|
12805
13742
|
return item.tokenName.charAt(0).toUpperCase() + item.tokenName.slice(1);
|
|
@@ -12813,24 +13750,79 @@ function AssetSelectorBar({
|
|
|
12813
13750
|
selectorItems,
|
|
12814
13751
|
onSelect,
|
|
12815
13752
|
trailing,
|
|
13753
|
+
afterName,
|
|
12816
13754
|
imageUrl,
|
|
12817
13755
|
badgeLabel,
|
|
12818
|
-
tickerPrimary
|
|
13756
|
+
tickerPrimary,
|
|
13757
|
+
compactName
|
|
12819
13758
|
}) {
|
|
12820
13759
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
13760
|
+
const [nameTip, setNameTip] = useState(null);
|
|
13761
|
+
const anchorRef = useRef(null);
|
|
13762
|
+
const [dropdownPos, setDropdownPos] = useState(null);
|
|
13763
|
+
useLayoutEffect(() => {
|
|
13764
|
+
if (!isDropdownOpen) return;
|
|
13765
|
+
const measure = () => {
|
|
13766
|
+
const rect = anchorRef.current?.getBoundingClientRect();
|
|
13767
|
+
if (rect) setDropdownPos({ top: rect.bottom + 6, left: rect.left });
|
|
13768
|
+
};
|
|
13769
|
+
measure();
|
|
13770
|
+
window.addEventListener("resize", measure);
|
|
13771
|
+
window.addEventListener("scroll", measure, true);
|
|
13772
|
+
return () => {
|
|
13773
|
+
window.removeEventListener("resize", measure);
|
|
13774
|
+
window.removeEventListener("scroll", measure, true);
|
|
13775
|
+
};
|
|
13776
|
+
}, [isDropdownOpen]);
|
|
13777
|
+
const [sortField, setSortField] = useState("volume");
|
|
13778
|
+
const [sortDir, setSortDir] = useState("desc");
|
|
12821
13779
|
const hasItems = selectorItems && selectorItems.length > 0;
|
|
13780
|
+
const hasMarketData = !!selectorItems?.some(
|
|
13781
|
+
(i) => i.price != null || i.changePercent != null || i.volume24h != null || i.propertyValue != null
|
|
13782
|
+
);
|
|
13783
|
+
const sortedItems = useMemo(() => {
|
|
13784
|
+
if (!selectorItems) return [];
|
|
13785
|
+
if (!hasMarketData) return [...selectorItems];
|
|
13786
|
+
const dir = sortDir === "desc" ? -1 : 1;
|
|
13787
|
+
const missing = sortDir === "desc" ? -Infinity : Infinity;
|
|
13788
|
+
return [...selectorItems].sort((a, b) => {
|
|
13789
|
+
const av = sortValue(a, sortField) ?? missing;
|
|
13790
|
+
const bv = sortValue(b, sortField) ?? missing;
|
|
13791
|
+
return (av - bv) * dir || 0;
|
|
13792
|
+
});
|
|
13793
|
+
}, [selectorItems, hasMarketData, sortField, sortDir]);
|
|
13794
|
+
const handleSortClick = (field) => {
|
|
13795
|
+
if (field === sortField) {
|
|
13796
|
+
setSortDir((d) => d === "desc" ? "asc" : "desc");
|
|
13797
|
+
} else {
|
|
13798
|
+
setSortField(field);
|
|
13799
|
+
setSortDir("desc");
|
|
13800
|
+
}
|
|
13801
|
+
};
|
|
12822
13802
|
const currentTicker = currentTokenName ? selectorItems?.find((i) => i.tokenName.toLowerCase() === currentTokenName.toLowerCase())?.ticker : void 0;
|
|
12823
13803
|
const metrics = metricsProp ?? [
|
|
12824
13804
|
...tokenPrice != null ? [{ label: "Unit Price", value: `$${tokenPrice.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`, accent: true }] : [],
|
|
12825
13805
|
...offeringValuation != null ? [{ label: "Offering Valuation", value: `$${offeringValuation.toLocaleString()}` }] : []
|
|
12826
13806
|
];
|
|
12827
|
-
return /* @__PURE__ */ jsx(AssetSelectorWrapper, { children: /* @__PURE__ */ jsxs(IPOAssetSelector, { children: [
|
|
13807
|
+
return /* @__PURE__ */ jsx(AssetSelectorWrapper, { children: /* @__PURE__ */ jsxs(IPOAssetSelector, { ref: anchorRef, children: [
|
|
12828
13808
|
imageUrl && /* @__PURE__ */ jsx(AssetThumbnail, { src: imageUrl, alt: propertyName }),
|
|
12829
|
-
/* @__PURE__ */ jsx(AssetSelectorDropdown, { $flush: tickerPrimary, onClick: () => hasItems && setIsDropdownOpen((prev) => !prev), children: /* @__PURE__ */ jsxs(AssetName, { children: [
|
|
13809
|
+
/* @__PURE__ */ jsx(AssetSelectorDropdown, { $flush: tickerPrimary, $noShrink: !!compactName && !!currentTicker && !tickerPrimary, onClick: () => hasItems && setIsDropdownOpen((prev) => !prev), children: /* @__PURE__ */ jsxs(AssetName, { children: [
|
|
12830
13810
|
tickerPrimary && currentTicker ? /* @__PURE__ */ jsxs(AssetIdentity, { children: [
|
|
12831
13811
|
/* @__PURE__ */ jsx(AssetTicker, { children: currentTicker.toUpperCase() }),
|
|
12832
13812
|
/* @__PURE__ */ jsx(AssetSubName, { children: propertyName })
|
|
12833
|
-
] }) : /* @__PURE__ */
|
|
13813
|
+
] }) : compactName && currentTicker ? /* @__PURE__ */ jsx(
|
|
13814
|
+
CompactName,
|
|
13815
|
+
{
|
|
13816
|
+
className: "asset-bar-compact-name",
|
|
13817
|
+
"aria-label": propertyName,
|
|
13818
|
+
onMouseEnter: (e) => {
|
|
13819
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
13820
|
+
setNameTip({ left: r.left + r.width / 2, top: r.bottom + 10 });
|
|
13821
|
+
},
|
|
13822
|
+
onMouseLeave: () => setNameTip(null),
|
|
13823
|
+
children: currentTicker.toUpperCase()
|
|
13824
|
+
}
|
|
13825
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12834
13826
|
propertyName,
|
|
12835
13827
|
currentTicker ? ` (${currentTicker.toUpperCase()})` : ""
|
|
12836
13828
|
] }),
|
|
@@ -12848,6 +13840,7 @@ function AssetSelectorBar({
|
|
|
12848
13840
|
}
|
|
12849
13841
|
)
|
|
12850
13842
|
] }) }),
|
|
13843
|
+
afterName != null && /* @__PURE__ */ jsx(AfterNameSlot, { className: "asset-bar-after-name", children: afterName }),
|
|
12851
13844
|
metrics.length > 0 && /* @__PURE__ */ jsx(SelectorMetrics, { children: metrics.map((m, i) => /* @__PURE__ */ jsxs("div", { style: { display: "contents" }, children: [
|
|
12852
13845
|
i > 0 && /* @__PURE__ */ jsx(Separator, { children: "|" }),
|
|
12853
13846
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
@@ -12863,40 +13856,128 @@ function AssetSelectorBar({
|
|
|
12863
13856
|
] })
|
|
12864
13857
|
] }, m.label)) }),
|
|
12865
13858
|
trailing != null && /* @__PURE__ */ jsx(TrailingWrapper, { className: "asset-bar-trailing", children: /* @__PURE__ */ jsx(TrailingInner, { children: trailing }) }),
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
const statusLabel = status === "PENDING" ? "Preparing" : status === "CLOSED" ? "Closed" : status === "CANCELLED" ? "Cancelled" : null;
|
|
12870
|
-
return /* @__PURE__ */ jsx(
|
|
12871
|
-
IPOOption,
|
|
13859
|
+
nameTip != null && typeof document !== "undefined" && createPortal(
|
|
13860
|
+
/* @__PURE__ */ jsx(
|
|
13861
|
+
NameTip,
|
|
12872
13862
|
{
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
/* @__PURE__ */
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
|
|
13863
|
+
role: "tooltip",
|
|
13864
|
+
"data-testid": "asset-name-tip",
|
|
13865
|
+
style: { position: "fixed", left: nameTip.left, top: nameTip.top, transform: "translateX(-50%)", zIndex: 10050 },
|
|
13866
|
+
children: propertyName
|
|
13867
|
+
}
|
|
13868
|
+
),
|
|
13869
|
+
document.body
|
|
13870
|
+
),
|
|
13871
|
+
isDropdownOpen && hasItems && dropdownPos != null && typeof document !== "undefined" && createPortal(
|
|
13872
|
+
/* @__PURE__ */ jsxs(
|
|
13873
|
+
IPODropdown,
|
|
13874
|
+
{
|
|
13875
|
+
$wide: hasMarketData,
|
|
13876
|
+
style: { position: "fixed", top: dropdownPos.top, left: dropdownPos.left, zIndex: 10050 },
|
|
13877
|
+
children: [
|
|
13878
|
+
hasMarketData && /* @__PURE__ */ jsxs(HeaderRow, { children: [
|
|
13879
|
+
/* @__PURE__ */ jsx(HeaderLabel, { children: "Asset" }),
|
|
13880
|
+
SORT_COLUMNS.map(({ field, label, hideOnPhone }) => /* @__PURE__ */ jsxs(
|
|
13881
|
+
SortButton,
|
|
13882
|
+
{
|
|
13883
|
+
type: "button",
|
|
13884
|
+
$active: sortField === field,
|
|
13885
|
+
$hideOnPhone: hideOnPhone,
|
|
13886
|
+
onClick: (e) => {
|
|
13887
|
+
e.stopPropagation();
|
|
13888
|
+
handleSortClick(field);
|
|
13889
|
+
},
|
|
13890
|
+
children: [
|
|
13891
|
+
label,
|
|
13892
|
+
sortField === field ? sortDir === "desc" ? " \u25BE" : " \u25B4" : ""
|
|
13893
|
+
]
|
|
13894
|
+
},
|
|
13895
|
+
field
|
|
13896
|
+
))
|
|
13897
|
+
] }),
|
|
13898
|
+
sortedItems.map((item) => {
|
|
13899
|
+
const isCurrent = item.tokenName === currentTokenName;
|
|
13900
|
+
const status = item.status?.toUpperCase();
|
|
13901
|
+
const statusLabel = status === "PENDING" ? "Preparing" : status === "CLOSED" ? "Closed" : status === "CANCELLED" ? "Cancelled" : null;
|
|
13902
|
+
const price = fmtPrice(item.price);
|
|
13903
|
+
const change = item.changePercent;
|
|
13904
|
+
const vol = fmtWholeUsd(item.volume24h);
|
|
13905
|
+
const val = fmtWholeUsd(item.propertyValue);
|
|
13906
|
+
return /* @__PURE__ */ jsx(
|
|
13907
|
+
IPOOption,
|
|
13908
|
+
{
|
|
13909
|
+
"data-selector-row": item.tokenName,
|
|
13910
|
+
onClick: () => {
|
|
13911
|
+
if (!isCurrent && onSelect) {
|
|
13912
|
+
onSelect(item.tokenName);
|
|
13913
|
+
}
|
|
13914
|
+
setIsDropdownOpen(false);
|
|
13915
|
+
},
|
|
13916
|
+
$selected: isCurrent,
|
|
13917
|
+
style: {
|
|
13918
|
+
cursor: isCurrent ? "default" : "pointer",
|
|
13919
|
+
// Arena gold for competition assets — the same restrained cue as
|
|
13920
|
+
// the home Overview row (gold left crust + pooled spotlight, no
|
|
13921
|
+
// badge). backgroundImage LONGHAND: jsdom drops gradients from
|
|
13922
|
+
// the shorthand and UI6-AS-508 asserts it.
|
|
13923
|
+
...item.isCompetition ? {
|
|
13924
|
+
// Straight crust bar — an inset box-shadow hugged the
|
|
13925
|
+
// row's rounded corners and read as a curved rim.
|
|
13926
|
+
// Left corners squared so the bar runs dead straight
|
|
13927
|
+
// like the home Overview arena row; right side keeps
|
|
13928
|
+
// the row rounding.
|
|
13929
|
+
borderLeft: "3px solid #E0B377",
|
|
13930
|
+
borderRadius: "0 7px 7px 0",
|
|
13931
|
+
backgroundImage: "radial-gradient(140% 90% at 0% 50%, rgba(224, 179, 119, 0.18) 0%, rgba(224, 179, 119, 0.06) 40%, transparent 68%)"
|
|
13932
|
+
} : {}
|
|
13933
|
+
},
|
|
13934
|
+
children: hasMarketData ? /* @__PURE__ */ jsxs(MarketRowInner, { children: [
|
|
13935
|
+
/* @__PURE__ */ jsxs(IdentityCell, { children: [
|
|
13936
|
+
item.imageUrl && /* @__PURE__ */ jsx(IPOOptionThumb, { src: item.imageUrl, alt: displayName(item) }),
|
|
13937
|
+
/* @__PURE__ */ jsxs(IPOOptionIdentity, { children: [
|
|
13938
|
+
/* @__PURE__ */ jsxs(IPOOptionName, { children: [
|
|
13939
|
+
/* @__PURE__ */ jsx(MarketTicker, { children: (item.ticker || displayName(item)).toUpperCase() }),
|
|
13940
|
+
item.typeBadge && /* @__PURE__ */ jsx(TypePill, { style: { color: item.typeBadge.color, background: item.typeBadge.background }, children: item.typeBadge.label }),
|
|
13941
|
+
item.badge && /* @__PURE__ */ jsx(CompetitionBadge, { children: item.badge })
|
|
13942
|
+
] }),
|
|
13943
|
+
/* @__PURE__ */ jsxs(IPOOptionLocation, { children: [
|
|
13944
|
+
item.streetAddress,
|
|
13945
|
+
statusLabel && /* @__PURE__ */ jsx("span", { children: statusLabel })
|
|
13946
|
+
] })
|
|
13947
|
+
] })
|
|
13948
|
+
] }),
|
|
13949
|
+
/* @__PURE__ */ jsx(PriceCell, { children: price ?? "\u2014" }),
|
|
13950
|
+
change != null ? /* @__PURE__ */ jsxs(ChangeCell, { $positive: change >= 0, children: [
|
|
13951
|
+
change >= 0 ? "+" : "",
|
|
13952
|
+
change.toFixed(2),
|
|
13953
|
+
"%"
|
|
13954
|
+
] }) : /* @__PURE__ */ jsx(MutedCell, { children: "\u2014" }),
|
|
13955
|
+
/* @__PURE__ */ jsx(MutedCell, { $hideOnPhone: true, children: vol ?? "\u2014" }),
|
|
13956
|
+
/* @__PURE__ */ jsx(MutedCell, { $hideOnPhone: true, children: val ?? "\u2014" })
|
|
13957
|
+
] }) : /* @__PURE__ */ jsxs(IPOOptionInner, { children: [
|
|
13958
|
+
item.imageUrl && /* @__PURE__ */ jsx(IPOOptionThumb, { src: item.imageUrl, alt: displayName(item) }),
|
|
13959
|
+
/* @__PURE__ */ jsxs(IPOOptionIdentity, { children: [
|
|
13960
|
+
/* @__PURE__ */ jsxs(IPOOptionName, { children: [
|
|
13961
|
+
displayName(item),
|
|
13962
|
+
item.ticker ? ` (${item.ticker})` : "",
|
|
13963
|
+
item.badge && /* @__PURE__ */ jsx(CompetitionBadge, { children: item.badge })
|
|
13964
|
+
] }),
|
|
13965
|
+
/* @__PURE__ */ jsxs(IPOOptionLocation, { children: [
|
|
13966
|
+
item.streetAddress,
|
|
13967
|
+
item.suburb ? `, ${item.suburb}` : "",
|
|
13968
|
+
statusLabel && /* @__PURE__ */ jsx("span", { children: statusLabel })
|
|
13969
|
+
] })
|
|
13970
|
+
] })
|
|
13971
|
+
] })
|
|
13972
|
+
},
|
|
13973
|
+
item.tokenName
|
|
13974
|
+
);
|
|
13975
|
+
})
|
|
13976
|
+
]
|
|
13977
|
+
}
|
|
13978
|
+
),
|
|
13979
|
+
document.body
|
|
13980
|
+
)
|
|
12900
13981
|
] }) });
|
|
12901
13982
|
}
|
|
12902
13983
|
var AssetSelectorWrapper = styled10.div`
|
|
@@ -12958,7 +14039,9 @@ var AssetSelectorDropdown = styled10.div`
|
|
|
12958
14039
|
padding: ${(p) => p.$flush ? "0.25rem 0" : "0.5rem"};
|
|
12959
14040
|
border-radius: 8px;
|
|
12960
14041
|
transition: ${(p) => p.$flush ? "none" : "all 0.2s ease"};
|
|
12961
|
-
|
|
14042
|
+
/* $noShrink (compactName): a four-letter ticker is already the floor — it must
|
|
14043
|
+
never clip to "Y" when the bar is starved; the trailing scrolls instead. */
|
|
14044
|
+
flex: ${(p) => p.$noShrink ? "0 0 auto" : "0 1 auto"};
|
|
12962
14045
|
min-width: 0;
|
|
12963
14046
|
overflow: hidden;
|
|
12964
14047
|
&:hover { background-color: ${(p) => p.$flush ? "transparent" : "rgba(255,255,255,0.05)"}; }
|
|
@@ -13015,6 +14098,35 @@ var AssetName = styled10.div`
|
|
|
13015
14098
|
svg { margin-left: 0.4rem; width: 16px; height: 16px; }
|
|
13016
14099
|
}
|
|
13017
14100
|
`;
|
|
14101
|
+
var CompactName = styled10.span`
|
|
14102
|
+
white-space: nowrap;
|
|
14103
|
+
letter-spacing: 0.01em;
|
|
14104
|
+
`;
|
|
14105
|
+
var NameTip = styled10.div`
|
|
14106
|
+
background: #1a1d24;
|
|
14107
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
14108
|
+
border-radius: 8px;
|
|
14109
|
+
padding: 0.45rem 0.7rem;
|
|
14110
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
|
14111
|
+
pointer-events: none;
|
|
14112
|
+
white-space: nowrap;
|
|
14113
|
+
color: #fff;
|
|
14114
|
+
font-size: 0.78rem;
|
|
14115
|
+
font-weight: 600;
|
|
14116
|
+
letter-spacing: -0.01em;
|
|
14117
|
+
&::before {
|
|
14118
|
+
content: '';
|
|
14119
|
+
position: absolute;
|
|
14120
|
+
top: -5px;
|
|
14121
|
+
left: 50%;
|
|
14122
|
+
transform: translateX(-50%) rotate(45deg);
|
|
14123
|
+
width: 8px;
|
|
14124
|
+
height: 8px;
|
|
14125
|
+
background: #1a1d24;
|
|
14126
|
+
border-left: 1px solid rgba(255, 255, 255, 0.1);
|
|
14127
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
14128
|
+
}
|
|
14129
|
+
`;
|
|
13018
14130
|
var SelectorMetrics = styled10.div`
|
|
13019
14131
|
display: flex;
|
|
13020
14132
|
align-items: center;
|
|
@@ -13035,6 +14147,12 @@ var SelectorMetrics = styled10.div`
|
|
|
13035
14147
|
padding: 0.4rem 0.5rem;
|
|
13036
14148
|
}
|
|
13037
14149
|
`;
|
|
14150
|
+
var AfterNameSlot = styled10.div`
|
|
14151
|
+
display: flex;
|
|
14152
|
+
align-items: center;
|
|
14153
|
+
flex-shrink: 0;
|
|
14154
|
+
min-width: 0;
|
|
14155
|
+
`;
|
|
13038
14156
|
var TrailingWrapper = styled10.div`
|
|
13039
14157
|
display: flex;
|
|
13040
14158
|
align-items: center;
|
|
@@ -13106,14 +14224,16 @@ var IPODropdown = styled10.div`
|
|
|
13106
14224
|
position: absolute;
|
|
13107
14225
|
top: calc(100% + 6px);
|
|
13108
14226
|
left: 0;
|
|
13109
|
-
width: 320px;
|
|
14227
|
+
width: ${(p) => p.$wide ? "min(660px, calc(100vw - 24px))" : "320px"};
|
|
13110
14228
|
background: #0d0d0f;
|
|
13111
14229
|
border-radius: 10px;
|
|
13112
14230
|
box-shadow: 0 12px 32px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.06);
|
|
13113
14231
|
z-index: 1000;
|
|
13114
|
-
max-height:
|
|
14232
|
+
max-height: 360px;
|
|
13115
14233
|
overflow-y: auto;
|
|
13116
|
-
|
|
14234
|
+
/* Market mode: NO top padding — the sticky header must sit flush against the
|
|
14235
|
+
container's top edge so scrolling rows can never peek above/behind it. */
|
|
14236
|
+
padding: ${(p) => p.$wide ? "0 0 4px" : "4px"};
|
|
13117
14237
|
|
|
13118
14238
|
scrollbar-width: thin;
|
|
13119
14239
|
scrollbar-color: rgba(255,255,255,0.12) transparent;
|
|
@@ -13121,8 +14241,8 @@ var IPODropdown = styled10.div`
|
|
|
13121
14241
|
&::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 2px; }
|
|
13122
14242
|
|
|
13123
14243
|
@media (max-width: 480px) {
|
|
13124
|
-
width: 280px;
|
|
13125
|
-
max-height:
|
|
14244
|
+
width: ${(p) => p.$wide ? "min(360px, calc(100vw - 16px))" : "280px"};
|
|
14245
|
+
max-height: 300px;
|
|
13126
14246
|
border-radius: 8px;
|
|
13127
14247
|
}
|
|
13128
14248
|
animation: slideDown 0.2s cubic-bezier(0.16,1,0.3,1);
|
|
@@ -13131,6 +14251,99 @@ var IPODropdown = styled10.div`
|
|
|
13131
14251
|
to { opacity: 1; transform: translateY(0); }
|
|
13132
14252
|
}
|
|
13133
14253
|
`;
|
|
14254
|
+
var MARKET_GRID = "minmax(0, 1fr) 76px 92px 108px 118px";
|
|
14255
|
+
var MARKET_GRID_PHONE = "minmax(0, 1fr) 76px 92px";
|
|
14256
|
+
var HeaderRow = styled10.div`
|
|
14257
|
+
position: sticky;
|
|
14258
|
+
top: 0;
|
|
14259
|
+
z-index: 2;
|
|
14260
|
+
display: grid;
|
|
14261
|
+
grid-template-columns: ${MARKET_GRID};
|
|
14262
|
+
gap: 10px;
|
|
14263
|
+
align-items: center;
|
|
14264
|
+
padding: 0.5rem 0.85rem 0.45rem;
|
|
14265
|
+
background: #0d0d0f;
|
|
14266
|
+
border-bottom: 1px solid rgba(255,255,255,0.06);
|
|
14267
|
+
|
|
14268
|
+
@media (max-width: 480px) { grid-template-columns: ${MARKET_GRID_PHONE}; }
|
|
14269
|
+
`;
|
|
14270
|
+
var HeaderLabel = styled10.span`
|
|
14271
|
+
font-size: 0.6rem;
|
|
14272
|
+
font-weight: 600;
|
|
14273
|
+
letter-spacing: 0.08em;
|
|
14274
|
+
text-transform: uppercase;
|
|
14275
|
+
color: rgba(255,255,255,0.45);
|
|
14276
|
+
white-space: nowrap;
|
|
14277
|
+
`;
|
|
14278
|
+
var SortButton = styled10.button`
|
|
14279
|
+
background: none;
|
|
14280
|
+
border: none;
|
|
14281
|
+
padding: 0;
|
|
14282
|
+
cursor: pointer;
|
|
14283
|
+
font-family: inherit;
|
|
14284
|
+
font-size: 0.6rem;
|
|
14285
|
+
font-weight: 600;
|
|
14286
|
+
letter-spacing: 0.08em;
|
|
14287
|
+
text-transform: uppercase;
|
|
14288
|
+
white-space: nowrap;
|
|
14289
|
+
/* Left-aligned like every other column (user call, 2026-08-20). */
|
|
14290
|
+
text-align: left;
|
|
14291
|
+
justify-self: start;
|
|
14292
|
+
color: ${(p) => p.$active ? "var(--color-accent, #E6C87E)" : "rgba(255,255,255,0.45)"};
|
|
14293
|
+
transition: color 0.15s ease;
|
|
14294
|
+
&:hover { color: ${(p) => p.$active ? "var(--color-accent, #E6C87E)" : "rgba(255,255,255,0.75)"}; }
|
|
14295
|
+
|
|
14296
|
+
@media (max-width: 480px) {
|
|
14297
|
+
display: ${(p) => p.$hideOnPhone ? "none" : "inline"};
|
|
14298
|
+
}
|
|
14299
|
+
`;
|
|
14300
|
+
var IPOOptionIdentity = styled10.div`
|
|
14301
|
+
flex: 1 1 0;
|
|
14302
|
+
min-width: 0;
|
|
14303
|
+
overflow: hidden;
|
|
14304
|
+
`;
|
|
14305
|
+
var MarketRowInner = styled10.div`
|
|
14306
|
+
display: grid;
|
|
14307
|
+
grid-template-columns: ${MARKET_GRID};
|
|
14308
|
+
gap: 10px;
|
|
14309
|
+
align-items: center;
|
|
14310
|
+
|
|
14311
|
+
@media (max-width: 480px) { grid-template-columns: ${MARKET_GRID_PHONE}; }
|
|
14312
|
+
`;
|
|
14313
|
+
var IdentityCell = styled10.div`
|
|
14314
|
+
display: flex;
|
|
14315
|
+
align-items: center;
|
|
14316
|
+
gap: 0.65rem;
|
|
14317
|
+
min-width: 0;
|
|
14318
|
+
`;
|
|
14319
|
+
var PriceCell = styled10.span`
|
|
14320
|
+
font-size: 0.8rem;
|
|
14321
|
+
font-weight: 600;
|
|
14322
|
+
color: #fff;
|
|
14323
|
+
font-variant-numeric: tabular-nums;
|
|
14324
|
+
white-space: nowrap;
|
|
14325
|
+
text-align: left;
|
|
14326
|
+
`;
|
|
14327
|
+
var ChangeCell = styled10.span`
|
|
14328
|
+
font-size: 0.72rem;
|
|
14329
|
+
font-weight: 600;
|
|
14330
|
+
color: ${(p) => p.$positive ? "#0ecb81" : "#f6465d"};
|
|
14331
|
+
font-variant-numeric: tabular-nums;
|
|
14332
|
+
white-space: nowrap;
|
|
14333
|
+
text-align: left;
|
|
14334
|
+
`;
|
|
14335
|
+
var MutedCell = styled10.span`
|
|
14336
|
+
font-size: 0.72rem;
|
|
14337
|
+
/* Same ink as the price column (user call, 2026-08-20). */
|
|
14338
|
+
color: #fff;
|
|
14339
|
+
font-variant-numeric: tabular-nums;
|
|
14340
|
+
white-space: nowrap;
|
|
14341
|
+
text-align: left;
|
|
14342
|
+
|
|
14343
|
+
@media (max-width: 480px) {
|
|
14344
|
+
display: ${(p) => p.$hideOnPhone ? "none" : "inline"};
|
|
14345
|
+
}
|
|
14346
|
+
`;
|
|
13134
14347
|
var IPOOption = styled10.div`
|
|
13135
14348
|
padding: 0.6rem 0.75rem;
|
|
13136
14349
|
transition: background 0.15s ease;
|
|
@@ -13156,6 +14369,25 @@ var IPOOptionName = styled10.div`
|
|
|
13156
14369
|
font-size: 0.82rem;
|
|
13157
14370
|
color: ${"#ebebeb"};
|
|
13158
14371
|
line-height: 1.3;
|
|
14372
|
+
/* One line like the location — wrapped names made market rows uneven. */
|
|
14373
|
+
white-space: nowrap;
|
|
14374
|
+
overflow: hidden;
|
|
14375
|
+
text-overflow: ellipsis;
|
|
14376
|
+
`;
|
|
14377
|
+
var MarketTicker = styled10.span`
|
|
14378
|
+
font-weight: 700;
|
|
14379
|
+
letter-spacing: 0.01em;
|
|
14380
|
+
`;
|
|
14381
|
+
var TypePill = styled10.span`
|
|
14382
|
+
margin-left: 0.45rem;
|
|
14383
|
+
font-size: 0.55rem;
|
|
14384
|
+
font-weight: 700;
|
|
14385
|
+
letter-spacing: 0.08em;
|
|
14386
|
+
text-transform: uppercase;
|
|
14387
|
+
padding: 1px 5px;
|
|
14388
|
+
border-radius: 3px;
|
|
14389
|
+
vertical-align: middle;
|
|
14390
|
+
white-space: nowrap;
|
|
13159
14391
|
`;
|
|
13160
14392
|
var CompetitionBadge = styled10.span`
|
|
13161
14393
|
margin-left: 0.4rem;
|
|
@@ -13174,6 +14406,11 @@ var IPOOptionLocation = styled10.div`
|
|
|
13174
14406
|
font-size: 0.72rem;
|
|
13175
14407
|
color: rgba(255,255,255,0.38);
|
|
13176
14408
|
margin-top: 1px;
|
|
14409
|
+
/* One line, ellipsized — a long address must never wrap the row taller
|
|
14410
|
+
(matches the deployed dropdown's compact rows). */
|
|
14411
|
+
white-space: nowrap;
|
|
14412
|
+
overflow: hidden;
|
|
14413
|
+
text-overflow: ellipsis;
|
|
13177
14414
|
span {
|
|
13178
14415
|
margin-left: 0.4rem;
|
|
13179
14416
|
color: var(--color-accent, #E6C87E);
|
|
@@ -16338,7 +17575,7 @@ var ContentScroll = styled10.div`
|
|
|
16338
17575
|
`;
|
|
16339
17576
|
var TabContainer = styled10.div`
|
|
16340
17577
|
display: flex;
|
|
16341
|
-
gap:
|
|
17578
|
+
gap: 1rem;
|
|
16342
17579
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
16343
17580
|
margin-bottom: 0.75rem;
|
|
16344
17581
|
overflow-x: auto;
|
|
@@ -16350,12 +17587,12 @@ var TabContainer = styled10.div`
|
|
|
16350
17587
|
var Tab = styled10.button`
|
|
16351
17588
|
background: transparent;
|
|
16352
17589
|
border: none;
|
|
16353
|
-
padding: 0.
|
|
16354
|
-
font-size: 0.
|
|
17590
|
+
padding: 0 0 0.5rem;
|
|
17591
|
+
font-size: 0.8rem;
|
|
16355
17592
|
font-weight: 500;
|
|
16356
17593
|
white-space: nowrap;
|
|
16357
17594
|
flex-shrink: 0;
|
|
16358
|
-
color: ${({ $active }) => $active ? "#
|
|
17595
|
+
color: ${({ $active }) => $active ? "#fff" : "rgba(255, 255, 255, 0.6)"};
|
|
16359
17596
|
cursor: pointer;
|
|
16360
17597
|
transition: color 0.15s;
|
|
16361
17598
|
position: relative;
|
|
@@ -16368,12 +17605,11 @@ var Tab = styled10.button`
|
|
|
16368
17605
|
right: 0;
|
|
16369
17606
|
height: 2px;
|
|
16370
17607
|
background: ${({ $active }) => $active ? "#E6C87E" : "transparent"};
|
|
16371
|
-
border-radius: 1px;
|
|
16372
17608
|
transition: background 0.15s;
|
|
16373
17609
|
}
|
|
16374
17610
|
|
|
16375
17611
|
&:hover {
|
|
16376
|
-
color: ${({ $active }) => $active ? "#fff" : "rgba(255, 255, 255, 0.
|
|
17612
|
+
color: ${({ $active }) => $active ? "#fff" : "rgba(255, 255, 255, 0.85)"};
|
|
16377
17613
|
}
|
|
16378
17614
|
`;
|
|
16379
17615
|
var TabCount = styled10.span`
|
|
@@ -20602,7 +21838,7 @@ function extractSparkline(candles, maxPoints = 20) {
|
|
|
20602
21838
|
}
|
|
20603
21839
|
return points;
|
|
20604
21840
|
}
|
|
20605
|
-
var SparklineChart =
|
|
21841
|
+
var SparklineChart = React9__default.memo(function SparklineChart2({ data, width, height, positive }) {
|
|
20606
21842
|
const [hoverX, setHoverX] = useState(null);
|
|
20607
21843
|
const svgRef = useRef(null);
|
|
20608
21844
|
const { linePath, fillPath, points } = useMemo(() => {
|
|
@@ -20716,6 +21952,6 @@ var SparklineChart = React5__default.memo(function SparklineChart2({ data, width
|
|
|
20716
21952
|
);
|
|
20717
21953
|
});
|
|
20718
21954
|
|
|
20719
|
-
export { AssetSelectorBar, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ContactPopup, Header, HlsVideo, HousePositionSlider, HousePositionSliderMobile, LoafLiquidityBadge, LoafLiquidityLogo, LoginPopup, MobileTradeNav, OfferingProgressCard, OnboardingGuide, Orderbook, owner_booking_default as OwnerBooking, PaymentPopup, PortfolioActivityPanel, PortfolioSummary, PriceChart, PropertyBuy, PropertyCompareBar, PropertyDocuments, PropertyHeroHeader, PropertyHistory, PropertyInspectionTimes, PropertyMediaRow, PropertyNewsUpdates, PropertyOffers, PropertyOverview, PropertyPhotoGallery, PropertySubheader, PropertyTour, PropertyValuation, SiteFooter, Skeleton, SlideDigit, SparklineChart, ToastProvider, TradeConfirmationModal, TradingSlider, YourOrders, badgeVariants, buttonVariants, extractSparkline, hasPendingActivity, useAdaptivePolling, useToast };
|
|
21955
|
+
export { AssetSelectorBar, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ContactPopup, Header, HlsVideo, HousePositionSlider, HousePositionSliderMobile, LoafLiquidityBadge, LoafLiquidityLogo, LoginPopup, MobileTradeNav, OfferingProgressCard, OnboardingGuide, Orderbook, owner_booking_default as OwnerBooking, PaymentPopup, PortfolioActivityPanel, PortfolioSummary, PriceChart, PropertyBuy, PropertyCompareBar, PropertyDocuments, PropertyHeroHeader, PropertyHistory, PropertyInspectionTimes, PropertyMediaRow, PropertyNewsUpdates, PropertyOffers, PropertyOverview, PropertyPhotoGallery, PropertySubheader, PropertyTour, PropertyValuation, SentimentBar, SiteFooter, Skeleton, SlideDigit, SparklineChart, ToastProvider, TradeConfirmationModal, TradingSlider, YourOrders, __resetDismissedBreaking, badgeVariants, buttonVariants, extractSparkline, formatAge, hasPendingActivity, rowAgeLabel, useAdaptivePolling, useToast };
|
|
20720
21956
|
//# sourceMappingURL=index.mjs.map
|
|
20721
21957
|
//# sourceMappingURL=index.mjs.map
|