59 lines
2.5 KiB
TypeScript
59 lines
2.5 KiB
TypeScript
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<Target, Model = PropertyValue> {
|
||
/** 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<Target, Model = PropertyValue>(host: InspectorHost, spec: PreviewedValue<Target, Model>): 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)));
|
||
},
|
||
};
|
||
}
|