import { Change } from '../internals/Change'; import { dispatchChanges } from '../internals/ChangeDispatcher'; export interface ClampOptions< Key extends string, KeyMin extends string = `${Key}Min`, KeyMax extends string = `${Key}Max`, KeyTarget extends string = `${Key}Target`, > { name: Key; min?: KeyMin; max?: KeyMax; target?: KeyTarget; } export type ClampDecorated< Key extends string, KeyMin extends string = `${Key}Min`, KeyMax extends string = `${Key}Max`, KeyTarget extends string = `${Key}Target`, > = { [key in Key | KeyMin | KeyMax | KeyTarget]: number; }; type ClampDecoratedInt = { [key in KeyInt]: number; }; export const Clamp = < Key extends string, KeyMin extends string = `${Key}Min`, KeyMax extends string = `${Key}Max`, KeyTarget extends string = `${Key}Target`, >( options: ClampOptions, ) => { const name = options.name; const min = options.min ?? (`${options.name}Min` as KeyMin); const max = options.max ?? (`${options.name}Max` as KeyMax); const target = options.target ?? (`${options.name}Target` as KeyTarget); return function ( objOrCls: ClampDecorated, propertyKey: KeyInt, ) { Object.defineProperties(objOrCls, { [name]: { get: function (this: ClampDecorated & ClampDecoratedInt): number { return this[propertyKey]; }, set: function (this: ClampDecorated & ClampDecoratedInt, value: number) { if (isNaN(value)) value = 0; value = Math.max(Math.min(Math.floor(value), this[max]), this[min]); if (this[propertyKey] !== value) { const valueOld = this[propertyKey]; Reflect.set(this, propertyKey, value); if (target && this[target] < this[propertyKey]) { const valueTargetOld = this[target]; Reflect.set(this, target, this[propertyKey]); dispatchChanges(this, { [name]: new Change(valueOld, this[propertyKey]), [target]: new Change(valueTargetOld, this[target]), }); } else { dispatchChanges(this, { [name]: new Change(valueOld, this[propertyKey]), }); } } }, }, }); }; };