diff --git a/core/lib/services/writables.spec.ts b/core/lib/services/writables.spec.ts index c91bd798e3..746d91e983 100644 --- a/core/lib/services/writables.spec.ts +++ b/core/lib/services/writables.spec.ts @@ -43,6 +43,46 @@ describe(`Writables service`, () => { expect(normalizeValueFn(2)).toBe(2); expect(normalizeValueFn(3)).toBe(3); expect(normalizeValueFn(4)).toBe(3); - expect(normalizeValueFn(+'a')).toBe(INVALID_VALUE); + expect(normalizeValueFn(NaN)).toBe(INVALID_VALUE); + expect(normalizeValueFn('a' as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn(true as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn({} as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn(null as any)).toBe(INVALID_VALUE); + + const normalizeStrictFn = typeNumberInRangeFactory(1, 3, {strict: true}).normalizeValue!; + expect(normalizeStrictFn(0)).toBe(INVALID_VALUE); + expect(normalizeStrictFn(1)).toBe(INVALID_VALUE); + expect(normalizeStrictFn(2)).toBe(2); + expect(normalizeStrictFn(3)).toBe(INVALID_VALUE); + expect(normalizeStrictFn(4)).toBe(INVALID_VALUE); + expect(normalizeValueFn(NaN)).toBe(INVALID_VALUE); + expect(normalizeValueFn('a' as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn(true as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn({} as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn(null as any)).toBe(INVALID_VALUE); + + const normalizeNoClampFn = typeNumberInRangeFactory(1, 3, {useClamp: false}).normalizeValue!; + expect(normalizeNoClampFn(0)).toBe(INVALID_VALUE); + expect(normalizeNoClampFn(1)).toBe(1); + expect(normalizeNoClampFn(2)).toBe(2); + expect(normalizeNoClampFn(3)).toBe(3); + expect(normalizeNoClampFn(4)).toBe(INVALID_VALUE); + expect(normalizeValueFn(NaN)).toBe(INVALID_VALUE); + expect(normalizeValueFn('a' as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn(true as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn({} as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn(null as any)).toBe(INVALID_VALUE); + + const normalizeCombineFn = typeNumberInRangeFactory(1, 3, {strict: true, useClamp: false}).normalizeValue!; + expect(normalizeCombineFn(0)).toBe(INVALID_VALUE); + expect(normalizeCombineFn(1)).toBe(INVALID_VALUE); + expect(normalizeCombineFn(2)).toBe(2); + expect(normalizeCombineFn(3)).toBe(INVALID_VALUE); + expect(normalizeCombineFn(4)).toBe(INVALID_VALUE); + expect(normalizeValueFn(NaN)).toBe(INVALID_VALUE); + expect(normalizeValueFn('a' as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn(true as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn({} as any)).toBe(INVALID_VALUE); + expect(normalizeValueFn(null as any)).toBe(INVALID_VALUE); }); }); diff --git a/core/lib/services/writables.ts b/core/lib/services/writables.ts index 95825f969d..1904928ddc 100644 --- a/core/lib/services/writables.ts +++ b/core/lib/services/writables.ts @@ -13,18 +13,39 @@ export const typeNumber: WritableWithDefaultOptions = { normalizeValue: numberNormalizeFn, }; +export interface TypeNumberInRangeOptions { + /** If `true`, the range checking will be strict, excluding the minimum and maximum values. Default is `false`. */ + strict?: boolean; + + /** If `true`, values outside the range will be clamped to the minimum or maximum. Default is `true`. */ + useClamp?: boolean; +} + /** - * A factory function that creates a type guard function to check and rectify a value is within a specified range. + * Factory function for creating a type constraint for numbers within a specified range. * - * @param min - The minimum value allowed. - * @param max - The maximum value allowed. - * @returns A type guard function that returns the clamp value if the value is a value number, and INVALID_VALUE otherwise. + * @param min - The minimum value. + * @param max - The maximum value. + * @param options - Additional options to customize the behavior. + * + * @returns A type guard function that returns the clamp value or INVALID_VALUE depending on the provided options. */ -export function typeNumberInRangeFactory(min: number, max: number) { +export function typeNumberInRangeFactory(min: number, max: number, options: TypeNumberInRangeOptions = {}) { + const {strict = false, useClamp = true} = options; return >{ normalizeValue: (value) => { - const normalizedNumber = numberNormalizeFn(value); - return normalizedNumber === INVALID_VALUE ? INVALID_VALUE : clamp(normalizedNumber, max, min); + let normalizedNumber = numberNormalizeFn(value); + if (normalizedNumber !== INVALID_VALUE) { + if (!strict && useClamp) { + normalizedNumber = clamp(normalizedNumber, max, min); + } + if (normalizedNumber >= min && normalizedNumber <= max) { + if (!strict || (normalizedNumber !== min && normalizedNumber !== max)) { + return normalizedNumber; + } + } + } + return INVALID_VALUE; }, }; } diff --git a/core/lib/slider.spec.ts b/core/lib/slider.spec.ts index 5f9f62ad69..b047ebcaa3 100644 --- a/core/lib/slider.spec.ts +++ b/core/lib/slider.spec.ts @@ -263,12 +263,12 @@ describe(`Slider basic`, () => { expect(state).toStrictEqual(defaultStateValues); }); - test(`should set the step as 0 if the provided value less than 0`, () => { + test(`shouldn't accept 0 as a valid value`, () => { slider.patch({ - stepSize: -1, + stepSize: 0, }); - expect(state).toStrictEqual({...defaultStateValues, stepSize: 0}); + expect(state).toStrictEqual(defaultStateValues); }); test(`should snap the value to the valid step`, () => { diff --git a/core/lib/slider.ts b/core/lib/slider.ts index b65f182f25..7ff9949507 100644 --- a/core/lib/slider.ts +++ b/core/lib/slider.ts @@ -204,7 +204,7 @@ export function getSliderDefaultConfig() { const configValidator: ConfigValidator = { min: typeNumber, max: typeNumber, - stepSize: typeNumberInRangeFactory(0, +Infinity), + stepSize: typeNumberInRangeFactory(0, +Infinity, {strict: true}), readonly: typeBoolean, disabled: typeBoolean, vertical: typeBoolean,