Fix share-link serialization and tooltip positioning
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user