@senka-ai/ui
Version:
A modern, type-safe Svelte 5 UI component library with full theme support, accessibility standards, and robust state management patterns
209 lines (208 loc) • 6.31 kB
JavaScript
/**
* Unified state management utilities for Svelte 5 components
* Provides consistent patterns for controlled/uncontrolled components
*/
/**
* Creates a controlled state pattern for components that can be either controlled or uncontrolled
* @param initialValue - The initial value for uncontrolled mode
* @param controlledValue - The controlled value (undefined for uncontrolled)
* @param onChange - Callback for value changes
* @returns Object with current value and change handler
*/
export function useControlledState(initialValue, controlledValue, onChange) {
let localValue = $state(initialValue);
// Reactively determine if controlled based on current controlledValue
const isControlled = $derived(controlledValue !== undefined);
const currentValue = $derived(isControlled ? controlledValue : localValue);
const setValue = (newValue) => {
if (!isControlled) {
localValue = newValue;
}
onChange?.(newValue);
};
return {
value: () => currentValue,
setValue,
isControlled: () => isControlled,
};
}
/**
* Creates a focus state manager with consistent focus/blur handling
* @param onFocus - Optional callback when element gains focus
* @param onBlur - Optional callback when element loses focus
* @param disabled - Whether focus handling is disabled
* @returns Object with focus state and handlers
*/
export function useFocusState(onFocus, onBlur, disabled) {
let focused = $state(false);
const handleFocus = (event) => {
if (disabled)
return;
focused = true;
onFocus?.(event);
};
const handleBlur = (event) => {
if (disabled)
return;
focused = false;
onBlur?.(event);
};
return {
focused: () => focused,
handleFocus,
handleBlur,
};
}
/**
* Creates a toggle state manager for boolean values
* @param initialValue - Initial boolean value
* @param controlled - Controlled value (undefined for uncontrolled)
* @param onChange - Callback for changes
* @returns Object with toggle state and handlers
*/
export function useToggleState(initialValue, controlled, onChange) {
const { value, setValue, isControlled } = useControlledState(initialValue, controlled, onChange);
const toggle = () => {
setValue(!value());
};
return {
value,
setValue,
toggle,
isControlled,
};
}
/**
* Creates a dropdown/select state manager
* @param initialValue - Initial selected value
* @param controlled - Controlled value (undefined for uncontrolled)
* @param onChange - Callback for selection changes
* @returns Object with selection state and handlers
*/
export function useSelectState(initialValue, controlled, onChange) {
const { value, setValue, isControlled } = useControlledState(initialValue, controlled, onChange);
let isOpen = $state(false);
const select = (newValue) => {
setValue(newValue);
isOpen = false;
};
const open = () => {
isOpen = true;
};
const close = () => {
isOpen = false;
};
const toggle = () => {
isOpen = !isOpen;
};
return {
value,
setValue,
select,
isOpen: () => isOpen,
open,
close,
toggle,
isControlled,
};
}
/**
* Creates a text input state manager with validation support
* @param initialValue - Initial text value
* @param controlled - Controlled value (undefined for uncontrolled)
* @param onChange - Callback for text changes
* @param validator - Optional validation function
* @returns Object with text state and handlers
*/
export function useTextState(initialValue, controlled, onChange, validator) {
const { value, setValue, isControlled } = useControlledState(initialValue, controlled, onChange);
let validationError = $state(null);
const updateValue = (newValue) => {
if (validator) {
validationError = validator(newValue);
}
setValue(newValue);
};
const clearError = () => {
validationError = null;
};
return {
value,
setValue: updateValue,
validationError: () => validationError,
clearError,
isControlled,
hasError: () => validationError !== null,
};
}
/**
* Creates a loading state manager for async operations
* @param initialLoading - Initial loading state
* @returns Object with loading state and handlers
*/
export function useLoadingState(initialLoading = false) {
let loading = $state(initialLoading);
let error = $state(null);
const setLoading = (isLoading) => {
loading = isLoading;
if (isLoading) {
error = null;
}
};
const setError = (errorMessage) => {
error = errorMessage;
loading = false;
};
const reset = () => {
loading = false;
error = null;
};
return {
loading: () => loading,
error: () => error,
setLoading,
setError,
reset,
hasError: () => error !== null,
};
}
/**
* Creates a counter state manager with bounds
* @param initialValue - Initial counter value
* @param min - Minimum allowed value
* @param max - Maximum allowed value
* @param onChange - Callback for value changes
* @returns Object with counter state and handlers
*/
export function useCounterState(initialValue, min, max, onChange) {
let value = $state(initialValue);
const setValue = (newValue) => {
let clampedValue = newValue;
if (min !== undefined && clampedValue < min) {
clampedValue = min;
}
if (max !== undefined && clampedValue > max) {
clampedValue = max;
}
value = clampedValue;
onChange?.(clampedValue);
};
const increment = () => {
setValue(value + 1);
};
const decrement = () => {
setValue(value - 1);
};
const reset = () => {
setValue(initialValue);
};
return {
value: () => value,
setValue,
increment,
decrement,
reset,
canIncrement: () => max === undefined || value < max,
canDecrement: () => min === undefined || value > min,
};
}