Thomas G. Lopes
IndexedDb & Structured output (#82)
1778c9e unverified
import type { MaybeGetter } from "$lib/types.js";
import { isFunction } from "$lib/utils/is.js";
import { extract } from "./extract.svelte";
type SyncedArgs<T> =
| {
value: MaybeGetter<T>;
onChange?: (value: T) => void;
}
| {
value: MaybeGetter<T | undefined>;
onChange?: (value: T) => void;
defaultValue: T;
};
/**
* Setting `current` calls the `onChange` callback with the new value.
*
* If the value arg is static, it will be used as the default value,
* and subsequent sets will set an internal state that gets read as `current`.
*
* Otherwise, if it is a getter, it will be called every time `current` is read,
* and no internal state is used.
*/
export class Synced<T> {
#internalValue = $state<T>() as T;
#valueArg: SyncedArgs<T>["value"];
#onChange?: SyncedArgs<T>["onChange"];
#defaultValue?: T;
constructor({ value, onChange, ...args }: SyncedArgs<T>) {
this.#valueArg = value;
this.#onChange = onChange;
this.#defaultValue = "defaultValue" in args ? args?.defaultValue : undefined;
this.#internalValue = extract(value, this.#defaultValue) as T;
}
get current() {
return isFunction(this.#valueArg)
? (this.#valueArg() ?? this.#defaultValue ?? this.#internalValue)
: this.#internalValue;
}
set current(value: T) {
if (this.current === value) return;
if (isFunction(this.#valueArg)) {
this.#onChange?.(value);
return;
}
this.#internalValue = value;
this.#onChange?.(value);
}
}