import type { Command } from '../../core/commands/Command'; import type { PropertyValue } from '../../core/model/Properties'; import type { FieldHandlers } from './fieldFactory'; import type { InspectorHost } from './host'; /** Where an inspector row finds the value it edits, and what committing it means. */ export interface PreviewedValue { /** The model object holding the value, or undefined when the selection is gone – then the row does nothing. */ target: () => Target | undefined; /** The stored value; undefined means the key is not set on the target at all. */ read: (target: Target) => Model | undefined; write: (target: Target, value: Model) => void; /** Unsets the key again; needed wherever `read` can return undefined. */ remove?: (target: Target) => void; /** The widget value as the model stores it (default: unchanged). Called on the target as it is at that moment. */ toModel?: (input: PropertyValue, target: Target) => Model; commit: (value: Model) => Command; } /** * Row handlers that preview into the model while editing and take that preview back before * committing. * * Scrubbing has to be visible at once, so the value goes straight into the model and is only * redrawn (`preview`), without an undo entry. A command records the value it finds at execute * time, so committing right away would make the last previewed frame the undo target: the * value from before the preview is written back first – including "the key did not exist", * via `remove` – and only then is the command executed. */ export function previewingHandlers(host: InspectorHost, spec: PreviewedValue): FieldHandlers { const toModel = spec.toModel ?? ((input: PropertyValue) => input as unknown as Model); let previewed = false; let before: Model | undefined; return { onInput: (input) => { const target = spec.target(); if (!target) return; if (!previewed) { previewed = true; before = spec.read(target); } spec.write(target, toModel(input, target)); host.state.preview(); }, onChange: (input) => { const target = spec.target(); if (!target) return; if (previewed) { if (before === undefined) spec.remove?.(target); else spec.write(target, before); } previewed = false; before = undefined; // Built after the restore: `toModel` may fill the rest of a compound value from the target. host.state.execute(spec.commit(toModel(input, target))); }, }; }