summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts
blob: 0e7ae6a7790ce7ec512e28c4e2819be073fec132 (plain) (blame)
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import { useReducer, useRef } from "react";
import { evaluateMathExpression } from "@/utils/math";

function looksLikeExpression({ input }: { input: string }): boolean {
	const trimmed = input.trim();
	if (!trimmed) return false;
	if (/[+*/]/.test(input)) return true;
	const minusIndex = trimmed.indexOf("-");
	return minusIndex > 0;
}

export function usePropertyDraft<T>({
	displayValue: sourceDisplay,
	parse,
	onPreview,
	onCommit,
	supportsExpressions = true,
}: {
	displayValue: string;
	parse: (input: string) => T | null;
	onPreview: (value: T) => void;
	onCommit: () => void;
	supportsExpressions?: boolean;
}) {
	const [, forceRender] = useReducer(
		(renderVersion: number) => renderVersion + 1,
		0,
	);
	const isEditing = useRef(false);
	const draft = useRef("");

	return {
		displayValue: isEditing.current ? draft.current : sourceDisplay,
		scrubTo: (value: number) => {
			const parsed = parse(String(value));
			if (parsed !== null) onPreview(parsed);
		},
		commitScrub: onCommit,
		onFocus: () => {
			isEditing.current = true;
			draft.current = sourceDisplay;
			forceRender();
		},
		onChange: (
			event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
		) => {
			draft.current = event.target.value;
			forceRender();

			const parsed = parse(event.target.value);
			if (parsed !== null) {
				onPreview(parsed);
			}
		},
		onBlur: () => {
			if (
				supportsExpressions &&
				looksLikeExpression({ input: draft.current })
			) {
				const evaluated = evaluateMathExpression({ input: draft.current });
				if (evaluated !== null) {
					const parsed = parse(String(evaluated));
					if (parsed !== null) onPreview(parsed);
				}
			}
			onCommit();
			isEditing.current = false;
			draft.current = "";
			forceRender();
		},
	};
}