refactor(helpers): modernize reactive helpers and add tests
This commit is contained in:
@@ -1,46 +1,98 @@
|
||||
/**
|
||||
* Numeric control with bounded values and step precision
|
||||
*
|
||||
* Creates a reactive control for numeric values that enforces min/max bounds
|
||||
* and rounds to a specific step increment. Commonly used for typography controls
|
||||
* like font size, line height, and letter spacing.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const fontSize = createTypographyControl({
|
||||
* value: 16,
|
||||
* min: 12,
|
||||
* max: 72,
|
||||
* step: 1
|
||||
* });
|
||||
*
|
||||
* // Access current value
|
||||
* fontSize.value; // 16
|
||||
* fontSize.isAtMin; // false
|
||||
*
|
||||
* // Modify value (automatically clamped and rounded)
|
||||
* fontSize.increase();
|
||||
* fontSize.value = 100; // Will be clamped to max (72)
|
||||
* ```
|
||||
*/
|
||||
|
||||
import {
|
||||
clampNumber,
|
||||
roundToStepPrecision,
|
||||
} from '$shared/lib/utils';
|
||||
|
||||
/**
|
||||
* Core numeric control configuration
|
||||
* Defines the bounds and stepping behavior for a control
|
||||
*/
|
||||
export interface ControlDataModel {
|
||||
/**
|
||||
* Control value
|
||||
*/
|
||||
/** Current numeric value */
|
||||
value: number;
|
||||
/**
|
||||
* Minimal possible value
|
||||
*/
|
||||
/** Minimum allowed value (inclusive) */
|
||||
min: number;
|
||||
/**
|
||||
* Maximal possible value
|
||||
*/
|
||||
/** Maximum allowed value (inclusive) */
|
||||
max: number;
|
||||
/**
|
||||
* Step size for increase/decrease
|
||||
*/
|
||||
/** Step size for increment/decrement operations */
|
||||
step: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full control model including accessibility labels
|
||||
*
|
||||
* @template T - Type for the control identifier
|
||||
*/
|
||||
export interface ControlModel<T extends string = string> extends ControlDataModel {
|
||||
/**
|
||||
* Control identifier
|
||||
*/
|
||||
/** Unique identifier for the control */
|
||||
id: T;
|
||||
/**
|
||||
* Area label for increase button
|
||||
*/
|
||||
/** ARIA label for the increase button */
|
||||
increaseLabel?: string;
|
||||
/**
|
||||
* Area label for decrease button
|
||||
*/
|
||||
/** ARIA label for the decrease button */
|
||||
decreaseLabel?: string;
|
||||
/**
|
||||
* Control area label
|
||||
*/
|
||||
/** ARIA label for the control area */
|
||||
controlLabel?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reactive numeric control with bounds and stepping
|
||||
*
|
||||
* The control automatically:
|
||||
* - Clamps values to the min/max range
|
||||
* - Rounds values to the step precision
|
||||
* - Tracks whether at min/max bounds
|
||||
*
|
||||
* @param initialState - Initial value, bounds, and step configuration
|
||||
* @returns Typography control instance with reactive state and methods
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Font size control: 12-72px in 1px increments
|
||||
* const fontSize = createTypographyControl({
|
||||
* value: 16,
|
||||
* min: 12,
|
||||
* max: 72,
|
||||
* step: 1
|
||||
* });
|
||||
*
|
||||
* // Line height control: 1.0-2.0 in 0.1 increments
|
||||
* const lineHeight = createTypographyControl({
|
||||
* value: 1.5,
|
||||
* min: 1.0,
|
||||
* max: 2.0,
|
||||
* step: 0.1
|
||||
* });
|
||||
*
|
||||
* // Direct assignment (auto-clamped)
|
||||
* fontSize.value = 100; // Becomes 72 (max)
|
||||
* ```
|
||||
*/
|
||||
export function createTypographyControl<T extends ControlDataModel>(
|
||||
initialState: T,
|
||||
) {
|
||||
@@ -49,12 +101,17 @@ export function createTypographyControl<T extends ControlDataModel>(
|
||||
let min = $state(initialState.min);
|
||||
let step = $state(initialState.step);
|
||||
|
||||
// Derived state for boundary detection
|
||||
const { isAtMax, isAtMin } = $derived({
|
||||
isAtMax: value >= max,
|
||||
isAtMin: value <= min,
|
||||
});
|
||||
|
||||
return {
|
||||
/**
|
||||
* Current control value (getter/setter)
|
||||
* Setting automatically clamps to bounds and rounds to step precision
|
||||
*/
|
||||
get value() {
|
||||
return value;
|
||||
},
|
||||
@@ -64,27 +121,45 @@ export function createTypographyControl<T extends ControlDataModel>(
|
||||
value = rounded;
|
||||
}
|
||||
},
|
||||
|
||||
/** Maximum allowed value */
|
||||
get max() {
|
||||
return max;
|
||||
},
|
||||
|
||||
/** Minimum allowed value */
|
||||
get min() {
|
||||
return min;
|
||||
},
|
||||
|
||||
/** Step increment size */
|
||||
get step() {
|
||||
return step;
|
||||
},
|
||||
|
||||
/** Whether the value is at or exceeds the maximum */
|
||||
get isAtMax() {
|
||||
return isAtMax;
|
||||
},
|
||||
|
||||
/** Whether the value is at or below the minimum */
|
||||
get isAtMin() {
|
||||
return isAtMin;
|
||||
},
|
||||
|
||||
/**
|
||||
* Increase value by one step (clamped to max)
|
||||
*/
|
||||
increase() {
|
||||
value = roundToStepPrecision(
|
||||
clampNumber(value + step, min, max),
|
||||
step,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Decrease value by one step (clamped to min)
|
||||
*/
|
||||
decrease() {
|
||||
value = roundToStepPrecision(
|
||||
clampNumber(value - step, min, max),
|
||||
@@ -94,4 +169,7 @@ export function createTypographyControl<T extends ControlDataModel>(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Type representing a typography control instance
|
||||
*/
|
||||
export type TypographyControl = ReturnType<typeof createTypographyControl>;
|
||||
|
||||
Reference in New Issue
Block a user