@readium/navigator
Version:
Next generation SDK for publications in Web Apps
272 lines (228 loc) • 6.33 kB
text/typescript
export interface IPreference<T> {
/**
* The current value of the preference.
*/
value: T | null | undefined;
/**
* The value that will be effectively used by the Configurable object if preferences are submitted as they are.
*/
effectiveValue: T | null | undefined;
/**
* Indicates if this preference will be effectively used by the Configurable object if preferences are submitted as they are.
*/
isEffective: boolean;
/**
* Unset the preference.
* Equivalent to set(null).
*/
clear(): void;
}
export interface IBooleanPreference extends IPreference<boolean> {
/**
* Toggle the preference to its opposite value.
*/
toggle(): void;
}
export interface IEnumPreference<T> extends IPreference<T> {
/**
* The possible values for this preference.
*/
supportedValues: T[];
}
export interface IRangePreference<T> extends IPreference<T> {
/**
* The supported range [min, max] for this preference.
*/
supportedRange: [T, T];
/**
* The step value for the incrementing/decrementing into the range.
*/
step: number;
/**
* Increase the preference value.
*/
increment(): void;
/**
* Decrease the preference value.
*/
decrement(): void;
/**
* Format the preference value as a string.
*/
format(value: T): string;
}
export class Preference<T> implements Preference<T> {
protected _value?: T | null;
protected readonly _effectiveValue?: T | null;
protected readonly _isEffective: boolean;
protected _onChange: (newValue: T | null | undefined) => void;
constructor({
initialValue = null,
effectiveValue,
isEffective,
onChange
} : {
initialValue?: T | null,
effectiveValue?: T | null,
isEffective: boolean,
onChange: (newValue: T | null | undefined) => void
}) {
this._value = initialValue;
this._effectiveValue = effectiveValue;
this._isEffective = isEffective;
this._onChange = onChange;
}
set value(value: T | null | undefined) {
this._value = value;
this._onChange(this._value);
}
get value(): T | null | undefined {
return this._value;
}
get effectiveValue(): T | null | undefined {
return this._effectiveValue;
}
get isEffective(): boolean {
return this._isEffective;
}
clear(): void {
this._value = null;
}
}
export class BooleanPreference extends Preference<boolean> implements IBooleanPreference {
set value(value: boolean | null | undefined) {
this._value = value;
this._onChange(this._value);
}
get value(): boolean | null | undefined {
return this._value;
}
get effectiveValue(): boolean | null | undefined {
return this._effectiveValue;
}
get isEffective(): boolean {
return this._isEffective;
}
clear(): void {
this._value = null;
}
toggle(): void {
this._value = !this._value;
this._onChange(this._value);
}
}
export class EnumPreference<T extends string | number | symbol> extends Preference<T> implements IEnumPreference<T> {
private readonly _supportedValues: T[];
constructor({
initialValue = null,
effectiveValue,
isEffective,
onChange,
supportedValues
} : {
initialValue?: T | null,
effectiveValue?: T | null,
isEffective: boolean,
onChange: (newValue: T | null | undefined) => void,
supportedValues: T[]
}) {
super({ initialValue, effectiveValue, isEffective, onChange });
this._supportedValues = supportedValues;
}
set value(value: T | null | undefined) {
if (value && !this._supportedValues.includes(value)) {
throw new Error(`Value '${ String(value) }' is not in the supported values for this preference.`);
}
this._value = value;
this._onChange(this._value);
}
get value(): T | null | undefined {
return this._value;
}
get effectiveValue(): T | null | undefined {
return this._effectiveValue;
}
get isEffective(): boolean {
return this._isEffective;
}
get supportedValues(): T[] {
return this._supportedValues;
}
clear(): void {
this._value = null;
}
}
export class RangePreference<T extends number> extends Preference<T> implements IRangePreference<T> {
private readonly _supportedRange: [T, T];
private readonly _step: number;
private readonly _decimals: number;
constructor({
initialValue = null,
effectiveValue,
isEffective,
onChange,
supportedRange,
step
} : {
initialValue?: T | null,
effectiveValue?: T | null,
isEffective: boolean,
onChange: (newValue: T | null | undefined) => void,
supportedRange: [T, T],
step: number
}
) {
super({ initialValue, effectiveValue, isEffective, onChange });
this._supportedRange = supportedRange;
this._step = step;
this._decimals = this._step.toString().includes('.')
? this._step.toString().split('.')[1].length
: 0;
}
set value(value: T | null | undefined) {
if (value && (value < this._supportedRange[0] || value > this._supportedRange[1])) {
throw new Error(`Value '${ String(value) }' is out of the supported range for this preference.`);
}
this._value = value;
this._onChange(this._value);
}
get value(): T | null | undefined {
return this._value;
}
get effectiveValue(): T | null | undefined {
return this._effectiveValue;
}
get isEffective(): boolean {
return this._isEffective;
}
get supportedRange(): [T, T] {
return this._supportedRange;
}
get step(): number {
return this._step;
}
increment(): void {
if (this._value && this._value < this._supportedRange[1]) {
this._value = Math.min(
Math.round((this._value + this._step) * 10 ** this._decimals) / 10 ** this._decimals,
this._supportedRange[1]
) as T;
this._onChange(this._value);
}
}
decrement(): void {
if (this._value && this._value > this._supportedRange[0]) {
this._value = Math.max(
Math.round((this._value - this._step) * 10 ** this._decimals) / 10 ** this._decimals,
this._supportedRange[0]
) as T;
this._onChange(this._value);
}
}
format(value: T): string {
return value.toString();
}
clear(): void {
this._value = null;
}
}