Fix layout shift from async USD prices and token balances

Reserve vertical space with min-height and   placeholders for all
elements populated by async data: per-address USD totals, ETH price
display, token balance containers, and total value sub-line.  Prevents
buttons and click targets from moving when price API responds.
This commit is contained in:
2026-02-27 16:05:49 +07:00
parent 9e45c75d29
commit b9250dab2e
4 changed files with 26 additions and 14 deletions

View File

@@ -274,11 +274,15 @@
</div> </div>
<div <div
id="address-usd-total" id="address-usd-total"
class="text-xs text-muted mb-3 text-right" class="text-xs text-muted mb-3 text-right min-h-[1rem]"
></div> >
&nbsp;
</div>
<!-- balances --> <!-- balances -->
<div class="border-b border-border-light pb-2 mb-2"> <div class="border-b border-border-light pb-2 mb-2">
<div id="address-balances"></div> <div id="address-balances" class="min-h-[1.25rem]">
&nbsp;
</div>
</div> </div>
<!-- actions --> <!-- actions -->
@@ -343,11 +347,15 @@
</div> </div>
<div <div
id="address-token-usd-total" id="address-token-usd-total"
class="text-xs text-muted mb-3 text-right" class="text-xs text-muted mb-3 text-right min-h-[1rem]"
></div> >
&nbsp;
</div>
<!-- single token balance --> <!-- single token balance -->
<div class="border-b border-border-light pb-2 mb-2"> <div class="border-b border-border-light pb-2 mb-2">
<div id="address-token-balance"></div> <div id="address-token-balance" class="min-h-[1.25rem]">
&nbsp;
</div>
</div> </div>
<!-- actions --> <!-- actions -->

View File

@@ -54,7 +54,8 @@ function show() {
const addrLink = etherscanAddressLink(addr.address); const addrLink = etherscanAddressLink(addr.address);
$("address-etherscan-link").innerHTML = $("address-etherscan-link").innerHTML =
`<a href="${addrLink}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`; `<a href="${addrLink}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
$("address-usd-total").textContent = formatUsd(getAddressValueUsd(addr)); const usdTotal = formatUsd(getAddressValueUsd(addr));
$("address-usd-total").innerHTML = usdTotal || "&nbsp;";
const ensEl = $("address-ens"); const ensEl = $("address-ens");
if (addr.ensName) { if (addr.ensName) {
ensEl.innerHTML = ensEl.innerHTML =

View File

@@ -124,7 +124,8 @@ function show() {
// USD total for this token only // USD total for this token only
const usdVal = price ? amount * price : 0; const usdVal = price ? amount * price : 0;
$("address-token-usd-total").textContent = formatUsd(usdVal); const usdStr = formatUsd(usdVal);
$("address-token-usd-total").innerHTML = usdStr || "&nbsp;";
// Single token balance line (no tokenId — not clickable here) // Single token balance line (no tokenId — not clickable here)
$("address-token-balance").innerHTML = balanceLine(symbol, amount, price); $("address-token-balance").innerHTML = balanceLine(symbol, amount, price);

View File

@@ -38,13 +38,15 @@ function renderTotalValue() {
const ethPrice = getPrice("ETH"); const ethPrice = getPrice("ETH");
if (priceEl) { if (priceEl) {
priceEl.textContent = ethPrice ? formatUsd(ethPrice) + " USD/ETH" : ""; priceEl.innerHTML = ethPrice
? formatUsd(ethPrice) + " USD/ETH"
: "&nbsp;";
} }
const addr = findActiveAddr(); const addr = findActiveAddr();
if (!addr) { if (!addr) {
el.textContent = ""; el.innerHTML = "&nbsp;";
if (subEl) subEl.textContent = ""; if (subEl) subEl.innerHTML = "&nbsp;";
return; return;
} }
const ethBal = parseFloat(addr.balance || "0"); const ethBal = parseFloat(addr.balance || "0");
@@ -54,8 +56,8 @@ function renderTotalValue() {
if (subEl) { if (subEl) {
const totalUsd = getAddressValueUsd(addr); const totalUsd = getAddressValueUsd(addr);
subEl.textContent = subEl.innerHTML =
totalUsd !== null ? "Total: " + formatUsd(totalUsd) : ""; totalUsd !== null ? "Total: " + formatUsd(totalUsd) : "&nbsp;";
} }
} }
@@ -276,7 +278,7 @@ function render(ctx) {
html += `<span class="flex-shrink-0 ml-1">${infoBtn}</span>`; html += `<span class="flex-shrink-0 ml-1">${infoBtn}</span>`;
html += `</div>`; html += `</div>`;
const addrUsd = formatUsd(getAddressValueUsd(addr)); const addrUsd = formatUsd(getAddressValueUsd(addr));
html += `<div class="text-xs text-muted text-right">${addrUsd}</div>`; html += `<div class="text-xs text-muted text-right min-h-[1rem]">${addrUsd || "&nbsp;"}</div>`;
html += balanceLinesForAddress( html += balanceLinesForAddress(
addr, addr,
state.trackedTokens, state.trackedTokens,