File size: 1,466 Bytes
1778c9e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
51
52
53
54
55
56
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);
	}
}