From f229bb978e2407de69ebe3f212be6bc24de6f200 Mon Sep 17 00:00:00 2001 From: Timothy Farrell Date: Fri, 15 May 2026 21:19:55 +0000 Subject: [PATCH] Autofocus more forms --- dist/index.html | 63 +++++++++++++++++++++++--------- src/components/EntryForm.svelte | 3 +- src/components/LockScreen.svelte | 3 +- src/components/Sidebar.svelte | 3 +- src/lib/autofocus.js | 9 +++++ 5 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 src/lib/autofocus.js diff --git a/dist/index.html b/dist/index.html index bb3c38a..14c0c6e 100644 --- a/dist/index.html +++ b/dist/index.html @@ -2326,20 +2326,6 @@ function merge_text_nodes(text) { //#endregion //#region node_modules/svelte/src/internal/client/dom/elements/misc.js /** -* @param {HTMLElement} dom -* @param {boolean} value -* @returns {void} -*/ -function autofocus(dom, value) { - if (value) { - const body = document.body; - dom.autofocus = true; - queue_micro_task(() => { - if (document.activeElement === body) dom.focus(); - }); - } -} -/** * The child of a textarea actually corresponds to the defaultValue property, so we need * to remove it upon hydration to avoid a bug when someone resets the form value. * @param {HTMLTextAreaElement} dom @@ -4192,6 +4178,36 @@ function head(hash, render_fn) { } } //#endregion +//#region node_modules/svelte/src/internal/client/dom/elements/actions.js +/** @import { ActionPayload } from '#client' */ +/** +* @template P +* @param {Element} dom +* @param {(dom: Element, value?: P) => ActionPayload

} action +* @param {() => P} [get_value] +* @returns {void} +*/ +function action(dom, action, get_value) { + effect(() => { + var payload = untrack(() => action(dom, get_value?.()) || {}); + if (get_value && payload?.update) { + var inited = false; + /** @type {P} */ + var prev = {}; + render_effect(() => { + var value = get_value(); + deep_read_state(value); + if (inited && safe_not_equal(prev, value)) { + prev = value; + /** @type {Function} */ payload.update(value); + } + }); + inited = true; + } + if (payload?.destroy) return () => payload.destroy(); + }); +} +//#endregion //#region node_modules/svelte/src/internal/shared/attributes.js var whitespace = [..." \n\r\f\xA0\v"]; /** @@ -5615,6 +5631,15 @@ async function importAll(data, mode = "merge", sourcePassword = "", targetKey = }; } //#endregion +//#region src/lib/autofocus.js +/** +* Action that autofocuses an element after mount when the condition is truthy. +* Uses a microtask to ensure the element is fully rendered. +*/ +function autofocus(node, condition = true) { + if (condition) queueMicrotask(() => node.focus()); +} +//#endregion //#region src/components/LockScreen.svelte var root_1$8 = /* @__PURE__ */ from_html(`

`); var root_2$6 = /* @__PURE__ */ from_html(`
`); @@ -5697,7 +5722,8 @@ function LockScreen($$anchor, $$props) { var div_3 = child(form); var input = sibling(child(div_3), 2); remove_input_defaults(input); - autofocus(input, true); + effect(() => bind_value(input, () => get(masterPassword), ($$value) => set(masterPassword, $$value))); + action(input, ($$node) => autofocus?.($$node)); reset(div_3); var node_1 = sibling(div_3, 2); var consequent_1 = ($$anchor) => { @@ -5732,7 +5758,6 @@ function LockScreen($$anchor, $$props) { e.preventDefault(); handleSubmit(); }); - bind_value(input, () => get(masterPassword), ($$value) => set(masterPassword, $$value)); append($$anchor, div); pop(); } @@ -5913,6 +5938,8 @@ function Sidebar($$anchor, $$props) { var div_8 = sibling(node_2, 2); var input_1 = sibling(child(div_8), 2); remove_input_defaults(input_1); + effect(() => bind_value(input_1, () => get(groupName), ($$value) => set(groupName, $$value))); + action(input_1, ($$node, $$action_arg) => autofocus?.($$node, $$action_arg), () => !get(editingGroupId)); reset(div_8); var div_9 = sibling(div_8, 2); var div_10 = sibling(child(div_9), 2); @@ -5942,7 +5969,6 @@ function Sidebar($$anchor, $$props) { }); delegated("click", div_5, () => set(showGroupForm, false)); delegated("click", div_6, (e) => e.stopPropagation()); - bind_value(input_1, () => get(groupName), ($$value) => set(groupName, $$value)); delegated("click", button_6, saveGroup); delegated("click", button_7, () => set(showGroupForm, false)); append($$anchor, div_5); @@ -6494,6 +6520,8 @@ function EntryForm($$anchor, $$props) { var div_5 = sibling(node_2, 2); var input = sibling(child(div_5), 2); remove_input_defaults(input); + effect(() => bind_value(input, () => get(title), ($$value) => set(title, $$value))); + action(input, ($$node, $$action_arg) => autofocus?.($$node, $$action_arg), () => !get(isEdit)); reset(div_5); var div_6 = sibling(div_5, 2); var input_1 = sibling(child(div_6), 2); @@ -6551,7 +6579,6 @@ function EntryForm($$anchor, $$props) { e.preventDefault(); handleSubmit(); }); - bind_value(input, () => get(title), ($$value) => set(title, $$value)); bind_value(input_1, () => get(username), ($$value) => set(username, $$value)); bind_value(input_2, () => get(password), ($$value) => set(password, $$value)); delegated("click", button, () => set(passwordVisible, !get(passwordVisible))); diff --git a/src/components/EntryForm.svelte b/src/components/EntryForm.svelte index dc74872..18abc1c 100644 --- a/src/components/EntryForm.svelte +++ b/src/components/EntryForm.svelte @@ -5,6 +5,7 @@ import { generatePassword } from '../lib/crypto/crypto.js' import { app } from '../lib/stores/app.svelte.js' import PasswordGenerator from './PasswordGenerator.svelte' +import { autofocus } from '../lib/autofocus.js' let { entryId, onSave, onCancel } = $props() @@ -114,7 +115,7 @@
- +
diff --git a/src/components/LockScreen.svelte b/src/components/LockScreen.svelte index d453c94..4b4e257 100644 --- a/src/components/LockScreen.svelte +++ b/src/components/LockScreen.svelte @@ -3,6 +3,7 @@ import { deriveKey, createTestPayload, verifyPassword } from '../lib/crypto/crypto.js' import { saveVaultMeta, loadVaultMeta, isVaultInitialized } from '../lib/storage/db.js' import { startAutoLock } from '../lib/stores/security.svelte.js' + import { autofocus } from '../lib/autofocus.js' let masterPassword = $state('') let confirmPassword = $state('') @@ -95,7 +96,7 @@ bind:value={masterPassword} placeholder="Enter master password" autocomplete="current-password" - autofocus + use:autofocus disabled={loading} />
diff --git a/src/components/Sidebar.svelte b/src/components/Sidebar.svelte index afcfbaa..c82578e 100644 --- a/src/components/Sidebar.svelte +++ b/src/components/Sidebar.svelte @@ -2,6 +2,7 @@ import { getGroups, addGroup, updateGroup, deleteGroup, getEntryCountsByGroup } from '../lib/storage/db.js' import { createGroup, validateGroup } from '../lib/models/schema.js' import { search as searchStore } from '../lib/stores/search.svelte.js' + import { autofocus } from '../lib/autofocus.js' let groups = $state([]) let entryCounts = $state(new Map()) @@ -142,7 +143,7 @@
- +
diff --git a/src/lib/autofocus.js b/src/lib/autofocus.js new file mode 100644 index 0000000..b90e76d --- /dev/null +++ b/src/lib/autofocus.js @@ -0,0 +1,9 @@ +/** + * Action that autofocuses an element after mount when the condition is truthy. + * Uses a microtask to ensure the element is fully rendered. + */ +export function autofocus(node, condition = true) { + if (condition) { + queueMicrotask(() => node.focus()) + } +}