All files / src/lib/actions autosave.ts

5.88% Statements 1/17
0% Branches 0/6
0% Functions 0/5
7.69% Lines 1/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50                                    1x                                                              
import { enhance } from '$app/forms';
import type { Action } from 'svelte/action';
 
interface AutosaveOptions {
	/** Only auto-submit while this is true (e.g. an already-logged row). Defaults to always-on. */
	enabled?: boolean;
}
 
/**
 * Immediate-save as a reusable action — the "no Save button" interaction principle.
 * Progressively enhances the form (`use:enhance`, so submits round-trip with no
 * full reload when client JS is live) AND submits it whenever an associated field
 * changes. Native Enter/submit still posts when JS is unavailable.
 *
 * Change listeners are attached to `form.elements` (not just the form) because
 * controls associated via the `form=` attribute render outside the form's DOM
 * subtree, so their `change` events never bubble to it.
 */
export const autosave: Action<HTMLFormElement, AutosaveOptions | undefined> = (form, options) => {
	// `reset: false` is load-bearing, not cosmetic. The default `use:enhance`
	// resets the <form> on a successful submission, which blanks every
	// associated input to its (empty) defaultValue — Svelte writes the `value`
	// PROPERTY, never the reset-restoring attribute. The follow-up reconciling
	// `invalidateAll()` reload only re-writes an input whose reloaded value
	// DIFFERS from the one Svelte last wrote (`set_value` short-circuits equal
	// values), so an input left unchanged by the edit (e.g. weight when only
	// reps changed) stays blank and shows the unit placeholder instead of its
	// logged value. Suppressing the reset keeps the inputs showing the athlete's
	// values; invalidateAll still reconciles them against the DB.
	const enhancement = enhance(form, () => ({ update }) => update({ reset: false }));
	let enabled = options?.enabled ?? true;
	const onChange = () => {
		if (enabled) form.requestSubmit();
	};
	const listened: Element[] = [];
	for (const el of Array.from(form.elements)) {
		el.addEventListener('change', onChange);
		listened.push(el);
	}
	return {
		update(next) {
			enabled = next?.enabled ?? true;
		},
		destroy() {
			for (const el of listened) el.removeEventListener('change', onChange);
			enhancement?.destroy?.();
		}
	};
};