ratio-lock
Version:
A TypeScript library for managing n numbers with locked ratios. When the ratio is locked, changing one value automatically adjusts all other values to maintain their proportional relationships.
119 lines • 3.35 kB
JavaScript
import { RatioLockError } from './ratio-lock-error.js';
/**
* A class for managing n numbers with locked ratios.
* When the ratio is locked, changing one value automatically adjusts
* all other values to maintain their proportional relationships.
*/
export class RatioLock {
_values;
_ratios;
_locked;
_precision;
/**
* Creates a new RatioLock instance
* @param initialValues - Initial values for the ratio lock
* @param options - Configuration options
*/
constructor(initialValues, options) {
if (initialValues.length < 2) {
throw new RatioLockError('RatioLock requires at least 2 values');
}
this._values = [...initialValues];
this._locked = options.locked === true;
this._precision = options.precision;
this._ratios = this.calculateRatios(initialValues);
}
calculateRatios(values) {
const baseValue = values[0];
if (baseValue === 0) {
return values.map(() => 1);
}
return values.map(v => v / baseValue);
}
applyPrecision(value) {
if (this._precision === undefined) {
return value;
}
return Number(value.toFixed(this._precision));
}
/**
* Lock the current ratio
*/
lock() {
this._ratios = this.calculateRatios(this._values);
this._locked = true;
}
/**
* Unlock the ratio
*/
unlock() {
this._locked = false;
}
/**
* Toggle lock state
*/
toggle() {
if (this._locked) {
this.unlock();
}
else {
this.lock();
}
}
/**
* Set a value at the given index
* @param index - Index of the value to set
* @param value - New value
*/
setValue(index, value) {
if (index < 0 || index >= this._values.length) {
throw new RatioLockError(`Index ${index} is out of bounds`);
}
if (this._locked) {
const ratio = this._ratios[index];
const baseValue = ratio !== 0 ? value / ratio : 0;
this._values = this._ratios.map(r => this.applyPrecision(baseValue * r));
}
else {
const newValues = [...this._values];
newValues[index] = value;
this._values = newValues;
}
}
/**
* Set all values (recalculates ratio if locked, unless all values are zero)
* @param values - New values
*/
setValues(values) {
if (values.length !== this._values.length) {
throw new RatioLockError(`Expected ${this._values.length} values, got ${values.length}`);
}
this._values = [...values];
if (this._locked) {
// Don't recalculate ratios if all values are zero - preserve existing ratios
const allZero = values.every(v => v === 0);
if (!allZero) {
this._ratios = this.calculateRatios(values);
}
}
}
/**
* Get current values
*/
getValues() {
return [...this._values];
}
/**
* Get current ratios
*/
getRatios() {
return [...this._ratios];
}
/**
* Check if ratio is locked
*/
isLocked() {
return this._locked;
}
}
//# sourceMappingURL=ratio-lock.js.map