feat: the password fields have an eye that shows what was typed

This commit is contained in:
Steffen Becker committed 2026-09-04 19:30:04 +02:00
1 parent b3b8e58bd4
commit cdecbee234
3 files changed
+59 -18

No files matched your search

+55 -18
View File
@@ -16,6 +16,7 @@
let firstInput = $state<HTMLInputElement | null>(null);
let password = $state('');
let repeat = $state('');
let show = $state(false);
const mismatch = $derived(mode === 'set' && repeat !== '' && password !== repeat);
const canApply = $derived(password !== '' && (mode === 'ask' || password === repeat));
@@ -31,6 +32,7 @@
if (!open) return;
password = '';
repeat = '';
show = false;
queueMicrotask(() => firstInput?.focus());
});
@@ -45,6 +47,23 @@
}
</script>
{#snippet peek()}
<button
class="peek"
type="button"
aria-label={show ? t().password.hide : t().password.show}
title={show ? t().password.hide : t().password.show}
onclick={() => (show = !show)}
>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M1.5 8S4 4 8 4s6.5 4 6.5 4-2.5 4-6.5 4-6.5-4-6.5-4z" />
<path d="M8 9.75a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5z" />
{#if !show}<path d="M3 13 13 3" />{/if}
</svg>
</button>
{/snippet}
<dialog
bind:this={dialogEl}
onclose={cancel}
@@ -56,29 +75,35 @@
{#if wrong}<p class="wrong">{t().password.wrong}</p>{/if}
<label class="row">
<span>{t().password.password}</span>
<input
bind:this={firstInput}
type="password"
bind:value={password}
autocomplete="off"
spellcheck="false"
onkeydown={(e) => { if (e.key === 'Enter' && canApply) apply(password); }}
/>
</label>
{#if mode === 'set'}
<label class="row">
<span>{t().password.repeat}</span>
<div class="row">
<label>
<span>{t().password.password}</span>
<input
type="password"
bind:value={repeat}
bind:this={firstInput}
type={show ? 'text' : 'password'}
bind:value={password}
autocomplete="off"
spellcheck="false"
onkeydown={(e) => { if (e.key === 'Enter' && canApply) apply(password); }}
/>
</label>
{@render peek()}
</div>
{#if mode === 'set'}
<div class="row">
<label>
<span>{t().password.repeat}</span>
<input
type={show ? 'text' : 'password'}
bind:value={repeat}
autocomplete="off"
spellcheck="false"
onkeydown={(e) => { if (e.key === 'Enter' && canApply) apply(password); }}
/>
</label>
{@render peek()}
</div>
{#if mismatch}<p class="wrong">{t().password.mismatch}</p>{/if}
<p class="note">{t().password.note}</p>
{/if}
@@ -124,7 +149,8 @@
h2 { font-size: 1rem; margin-bottom: 4px; }
.row { display: flex; align-items: center; gap: 8px; }
.row > span { flex: 1; color: var(--color-text-muted); }
.row label { display: contents; }
.row span { flex: 1; color: var(--color-text-muted); }
input {
height: 26px;
@@ -137,6 +163,17 @@
font: inherit;
}
.peek {
display: flex;
border: none;
background: none;
color: var(--color-text-muted);
padding: 2px;
cursor: pointer;
}
.peek:hover { color: var(--color-text); }
.peek svg { width: 16px; height: 16px; }
.note { color: var(--color-text-muted); line-height: 1.35; }
.wrong { color: #c62828; }
+2
View File
@@ -1114,6 +1114,8 @@ const de: Messages = {
mismatch: 'Die beiden Eingaben unterscheiden sich.',
wrong: 'Falsches Passwort.',
note: 'Schützt die gespeicherte Datei. Die Kopie, die der Editor im Browser hält, bleibt unverschlüsselt.',
show: 'Passwort anzeigen',
hide: 'Passwort verbergen',
remove: 'Schutz entfernen',
},
importError: {
+2
View File
@@ -1122,6 +1122,8 @@ const en = {
mismatch: 'The two entries differ.',
wrong: 'Wrong password.',
note: 'Protects the saved file. The copy this editor keeps in the browser stays unencrypted.',
show: 'Show password',
hide: 'Hide password',
remove: 'Remove protection',
},
importError: {