Fix share-link serialization and tooltip positioning

This commit is contained in:
Codex Agent
2026-03-08 19:44:30 +00:00
parent 02b9c2411f
commit cf1114e8d8

View File

@@ -15,7 +15,7 @@
let val2 = ''; let val2 = '';
let val3 = ''; let val3 = '';
let activeField: 1 | 2 | 3 = 1; let activeField: 1 | 2 | 3 = 1;
let swapState: { originalField: 1 | 2; originalValue: string } | null = null; let swapState: { originalField: 1 | 2; originalValue: string | number | null } | null = null;
let copyStatus: 'idle' | 'copied' | 'failed' = 'idle'; let copyStatus: 'idle' | 'copied' | 'failed' = 'idle';
let statusTimeout: ReturnType<typeof setTimeout> | null = null; let statusTimeout: ReturnType<typeof setTimeout> | null = null;
let tooltipFadeTimeout: ReturnType<typeof setTimeout> | null = null; let tooltipFadeTimeout: ReturnType<typeof setTimeout> | null = null;
@@ -23,6 +23,8 @@
let showCopyTooltip = false; let showCopyTooltip = false;
let isTooltipFading = false; let isTooltipFading = false;
let showHoverTooltip = false; let showHoverTooltip = false;
let footerControlsEl: HTMLDivElement | null = null;
let tooltipX = 20;
let copyStatusMessage = ''; let copyStatusMessage = '';
$: has3 = ['3col', '3col-mul'].includes(config.type) || !!config.labels.in3; $: has3 = ['3col', '3col-mul'].includes(config.type) || !!config.labels.in3;
@@ -75,21 +77,27 @@
function buildShareUrl() { function buildShareUrl() {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (val1.trim()) { const v1 = toQueryValue(val1);
params.set('v1', val1); const v2 = toQueryValue(val2);
} const v3 = toQueryValue(val3);
if (val2.trim()) {
params.set('v2', val2); if (v1 !== null) params.set('v1', v1);
} if (v2 !== null) params.set('v2', v2);
if (val3.trim() && has3) { if (has3 && v3 !== null) params.set('v3', v3);
params.set('v3', val3);
}
const shareUrl = new URL($page.url); const shareUrl = new URL($page.url);
shareUrl.search = params.toString(); shareUrl.search = params.toString();
return shareUrl.toString(); return shareUrl.toString();
} }
function toQueryValue(value: unknown): string | null {
if (value === null || value === undefined) {
return null;
}
const stringValue = String(value);
return stringValue.trim() ? stringValue : null;
}
async function copyText(text: string) { async function copyText(text: string) {
if (navigator.clipboard?.writeText) { if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text); await navigator.clipboard.writeText(text);
@@ -125,6 +133,21 @@
}, 1300); }, 1300);
} }
function updateTooltipPosition(event: MouseEvent) {
if (!footerControlsEl) return;
const rect = footerControlsEl.getBoundingClientRect();
const x = event.clientX - rect.left;
tooltipX = Math.max(12, Math.min(rect.width - 12, x));
}
function positionTooltipFromButton(button: HTMLButtonElement) {
if (!footerControlsEl) return;
const controlsRect = footerControlsEl.getBoundingClientRect();
const buttonRect = button.getBoundingClientRect();
const centerX = buttonRect.left - controlsRect.left + buttonRect.width / 2;
tooltipX = Math.max(12, Math.min(controlsRect.width - 12, centerX));
}
async function copyLink() { async function copyLink() {
if (!browser) return; if (!browser) return;
const url = buildShareUrl(); const url = buildShareUrl();
@@ -237,17 +260,27 @@
</div> </div>
<div class="calc-footer"> <div class="calc-footer">
<div class="footer-controls"> <div class="footer-controls" bind:this={footerControlsEl}>
<button type="button" class="clear-btn" on:click={clear} aria-label="Clear calculator inputs"> <button type="button" class="clear-btn" on:click={clear} aria-label="Clear calculator inputs">
Clear Clear
</button> </button>
<button <button
type="button" type="button"
class="icon-btn" class="icon-btn"
on:click={copyLink} on:click={(event) => {
on:mouseenter={() => (showHoverTooltip = true)} positionTooltipFromButton(event.currentTarget as HTMLButtonElement);
copyLink();
}}
on:mouseenter={(event) => {
showHoverTooltip = true;
updateTooltipPosition(event);
}}
on:mousemove={updateTooltipPosition}
on:mouseleave={() => (showHoverTooltip = false)} on:mouseleave={() => (showHoverTooltip = false)}
on:focus={() => (showHoverTooltip = true)} on:focus={(event) => {
showHoverTooltip = true;
positionTooltipFromButton(event.currentTarget as HTMLButtonElement);
}}
on:blur={() => (showHoverTooltip = false)} on:blur={() => (showHoverTooltip = false)}
aria-label="Copy calculator link" aria-label="Copy calculator link"
> >
@@ -263,10 +296,10 @@
</svg> </svg>
</button> </button>
{#if showHoverTooltip && !showCopyTooltip} {#if showHoverTooltip && !showCopyTooltip}
<span class="copy-tooltip hover">Copy link</span> <span class="copy-tooltip hover" style={`left: ${tooltipX}px;`}>Copy link</span>
{/if} {/if}
{#if showCopyTooltip && copyStatus === 'copied'} {#if showCopyTooltip && copyStatus === 'copied'}
<span class="copy-tooltip" class:fading={isTooltipFading}>Link copied!</span> <span class="copy-tooltip" class:fading={isTooltipFading} style={`left: ${tooltipX}px;`}>Link copied!</span>
{/if} {/if}
<span class="sr-only" aria-live="polite"> <span class="sr-only" aria-live="polite">
{copyStatusMessage} {copyStatusMessage}
@@ -465,8 +498,7 @@
} }
.copy-tooltip { .copy-tooltip {
position: absolute; position: absolute;
top: calc(100% + 0.4rem); bottom: calc(100% + 0.4rem);
right: 0;
background: color-mix(in srgb, var(--accent) 90%, black 10%); background: color-mix(in srgb, var(--accent) 90%, black 10%);
color: #fff; color: #fff;
border-radius: 6px; border-radius: 6px;
@@ -476,7 +508,7 @@
letter-spacing: 0.02em; letter-spacing: 0.02em;
pointer-events: none; pointer-events: none;
opacity: 1; opacity: 1;
transform: translateY(0); transform: translate(-50%, 0);
transition: opacity 0.35s ease, transform 0.35s ease; transition: opacity 0.35s ease, transform 0.35s ease;
white-space: nowrap; white-space: nowrap;
z-index: 2; z-index: 2;
@@ -488,7 +520,7 @@
} }
.copy-tooltip.fading { .copy-tooltip.fading {
opacity: 0; opacity: 0;
transform: translateY(-0.2rem); transform: translate(-50%, -0.2rem);
} }
.sr-only { .sr-only {
position: absolute; position: absolute;