Files
frontend-svelte/src/shared/lib/helpers/createTypographyControl/createTypographyControl.svelte.ts
T

201 lines
4.6 KiB
TypeScript
Raw Normal View History

/**
* 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 {
2026-04-17 12:14:55 +03:00
/**
* Initial or current numeric value
*/
value: number;
2026-04-17 12:14:55 +03:00
/**
* Lower inclusive bound
*/
min: number;
2026-04-17 12:14:55 +03:00
/**
* Upper inclusive bound
*/
max: number;
2026-04-17 12:14:55 +03:00
/**
* Precision 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 {
2026-04-17 12:14:55 +03:00
/**
* Unique string identifier for the control
*/
id: T;
2026-04-17 12:14:55 +03:00
/**
* Label used by screen readers for the increase button
*/
increaseLabel?: string;
2026-04-17 12:14:55 +03:00
/**
* Label used by screen readers for the decrease button
*/
decreaseLabel?: string;
2026-04-17 12:14:55 +03:00
/**
* Overall label describing the control's purpose
*/
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,
) {
let value = $state(initialState.value);
let max = $state(initialState.max);
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 {
/**
2026-04-17 12:14:55 +03:00
* Clamped and rounded control value (reactive)
*/
get value() {
return value;
},
set value(newValue) {
const rounded = roundToStepPrecision(clampNumber(newValue, min, max), step);
if (value !== rounded) {
value = rounded;
}
},
2026-04-17 12:14:55 +03:00
/**
* Upper limit for the control value
*/
get max() {
return max;
},
2026-04-17 12:14:55 +03:00
/**
* Lower limit for the control value
*/
get min() {
return min;
},
2026-04-17 12:14:55 +03:00
/**
* Configured step increment
*/
get step() {
return step;
},
2026-04-17 12:14:55 +03:00
/**
* True if current value is equal to or greater than max
*/
get isAtMax() {
return isAtMax;
},
2026-04-17 12:14:55 +03:00
/**
* True if current value is equal to or less than min
*/
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),
step,
);
},
};
}
/**
* Type representing a typography control instance
*/
export type TypographyControl = ReturnType<typeof createTypographyControl>;