From 06e48290ca5e6da7866715cc26c4e3fdf3e03b91 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 23 Jun 2026 23:17:19 -0700 Subject: Show full recipient address below the input for verification The address input scrolls/truncates on mobile, hiding the ends. Render the full address as wrapping text beneath it with the first/last 6 chars emphasized, so paste/type/scan can be verified without scrolling. Updated imperatively on input so the field keeps focus. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app.js | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'src/app.js') diff --git a/src/app.js b/src/app.js index 286372e..54592a7 100644 --- a/src/app.js +++ b/src/app.js @@ -964,18 +964,42 @@ async function broadcastScanned() { render(); } +// Full address as wrapping nodes, first/last 6 chars emphasized — readable +// without horizontally scrolling the input. Returns DOM nodes for in-place +// updates (the address input doesn't re-render on every keystroke). +function addrVerifyNodes(a) { + const n = 6; + if (!a) return []; + if (a.length <= n * 2) return [document.createTextNode(a)]; + return [ + h('span', { class: 'hl' }, a.slice(0, n)), + document.createTextNode(a.slice(n, -n)), + h('span', { class: 'hl' }, a.slice(-n)), + ]; +} + // One recipient: address + amount. Max is only offered for a single recipient. function recipientRow(s, r, i) { const single = s.recipients.length === 1; const maxOn = single && s.max; - return h( + + // Updated imperatively on input (and on render) so paste, typing, and scan + // all reflect immediately without disrupting the input's focus/cursor. + const check = h('div', { class: 'addr-check' }); + const syncCheck = () => { + const a = r.address.trim(); + check.replaceChildren(...addrVerifyNodes(a)); + check.style.display = a ? '' : 'none'; + }; + + const row = h( 'div', { class: 'col gap6' }, h('div', { class: 'input-group' }, h('input', { type: 'text', class: 'mono-input grow', placeholder: 'bc1q…', autocapitalize: 'none', autocomplete: 'off', spellcheck: 'false', value: r.address, - onInput: (e) => (r.address = e.target.value), + onInput: (e) => { r.address = e.target.value; syncCheck(); }, }), i === 0 && canScan() && h('button', { type: 'button', class: 'btn-sm', title: t('scanQr'), onClick: scanIntoSend, @@ -983,6 +1007,7 @@ function recipientRow(s, r, i) { }), !single && h('button', { type: 'button', class: 'btn-sm', title: t('remove'), onClick: () => { s.recipients.splice(i, 1); render(); } }, '✕') ), + check, h('div', { class: 'input-group' }, h('input', { type: 'number', step: unit === 'sats' ? '1' : '0.00000001', min: '0', @@ -1008,6 +1033,8 @@ function recipientRow(s, r, i) { single && h('button', { type: 'button', class: s.max ? 'btn-primary' : '', onClick: () => { s.max = !s.max; render(); } }, t('max')) ) ); + syncCheck(); + return row; } function sendForm() { -- cgit v1.2.3