@entropy-tamer/reynard-selection-primitives
v0.1.0
Published
Modular selection and configuration primitives for the Reynard ecosystem
Maintainers
Readme
Reynard Selection Primitives
Modular selection and configuration primitives for the Reynard ecosystem. This package provides a comprehensive set of composables for managing selection state, configuration management, and signal coordination with full TypeScript support, validation, and persistence.
Features
- Modular Architecture: Composable primitives that can be used independently or combined
- Type Safety: Full TypeScript support with generic types
- Validation: Built-in validation system with real-time feedback
- Persistence: Configurable persistence with localStorage, sessionStorage, and memory options
- Signal Coordination: Advanced signal coordination with dependency tracking and debouncing
- Accessibility: Built-in accessibility compliance checking
- Performance: Optimized for performance with minimal re-renders
Installation
pnpm add reynard-selection-primitivesCore Primitives
useSelection
Base selection primitive for managing item selection state.
import { useSelection } from "reynard-selection-primitives";
const selection = useSelection<string>({
multiple: true,
allowDeselect: true,
maxSelections: 5,
validation: item => item.length > 0,
onChange: selected => console.log("Selected:", selected),
});
// Select items
selection.select("item1");
selection.selectMultiple(["item2", "item3"]);
// Check selection state
console.log(selection.selected()); // ['item1', 'item2', 'item3']
console.log(selection.hasSelection()); // true
console.log(selection.selectionCount()); // 3useConfiguration
Configuration management primitive with validation and persistence.
import { useConfiguration, commonValidators } from "reynard-selection-primitives";
interface AppConfig {
theme: string;
language: string;
notifications: boolean;
}
const config = useConfiguration<AppConfig>({
initialConfig: {
theme: "light",
language: "en",
notifications: true,
},
validation: {
rules: [
commonValidators.required<AppConfig>("Theme is required"),
{
validate: config => ["light", "dark"].includes(config.theme),
message: "Theme must be light or dark",
severity: "error",
},
],
mode: "strict",
realTime: true,
},
persistence: {
key: "app-config",
storage: "localStorage",
serialization: "json",
},
});
// Update configuration
config.updateConfig({ theme: "dark" });
// Check validation
console.log(config.isValid()); // true
console.log(config.validationErrors()); // []useCoordination<T, U>
Signal coordination primitive for managing dependencies between signals.
import { useCoordination, createSignal } from "reynard-selection-primitives";
const [primary, setPrimary] = createSignal("initial");
const [secondary, setSecondary] = createSignal(0);
const coordination = useCoordination({
primary,
secondary,
coordination: (primaryValue, secondaryValue) => {
console.log(`Primary: ${primaryValue}, Secondary: ${secondaryValue}`);
},
dependencies: {
dependencies: [primary, secondary],
coordination: deps => {
console.log("Dependencies changed:", deps);
},
debounce: 100,
},
});
// Manually trigger coordination
coordination.coordinate("manual", 42);Extensions
useThemeAccessibility
Specialized extension for theme and accessibility selection coordination.
import { useThemeAccessibility } from "reynard-selection-primitives";
const themeAccessibility = useThemeAccessibility({
theme: "light",
colorblind: {
enabled: false,
deficiency: "normal",
severity: "moderate",
},
accessibility: {
highContrast: false,
reducedMotion: false,
fontSize: "medium",
},
});
// Theme selection
themeAccessibility.themeSelection.select("dark");
// Colorblind selection
themeAccessibility.colorblindSelection.select("protanopia");
// Configuration management
themeAccessibility.config.updateConfig({
accessibility: {
highContrast: true,
reducedMotion: true,
fontSize: "large",
},
});
// Get recommendations
const recommendations = themeAccessibility.getRecommendations();
console.log(recommendations); // ['Consider enabling high contrast mode...']Validation System
The validation system provides comprehensive validation with real-time feedback.
import { commonValidators } from "reynard-selection-primitives";
// Common validators
const validators = {
required: commonValidators.required("This field is required"),
minLength: commonValidators.minLength(3, "Minimum 3 characters"),
maxLength: commonValidators.maxLength(50, "Maximum 50 characters"),
email: commonValidators.email("Invalid email format"),
url: commonValidators.url("Invalid URL format"),
min: commonValidators.min(0, "Must be positive"),
max: commonValidators.max(100, "Must be less than 100"),
pattern: commonValidators.pattern(/^[A-Z]/, "Must start with uppercase"),
};
// Custom validator
const customValidator = {
validate: (value: string) => value.includes("@"),
message: "Must contain @ symbol",
severity: "error" as const,
};Persistence System
The persistence system supports multiple storage options with custom serialization.
// localStorage with JSON serialization
const localStorageConfig = {
key: "my-config",
storage: "localStorage" as const,
serialization: "json" as const,
};
// sessionStorage with custom serialization
const sessionStorageConfig = {
key: "session-data",
storage: "sessionStorage" as const,
serialization: "custom" as const,
serializer: (data: any) => btoa(JSON.stringify(data)),
deserializer: (data: string) => JSON.parse(atob(data)),
};
// Memory storage (no persistence)
const memoryConfig = {
key: "temp-data",
storage: "memory" as const,
serialization: "json" as const,
};Advanced Features
Signal Coordination with Dependencies
const coordination = useCoordination({
primary: () => userInput(),
secondary: () => serverResponse(),
coordination: (input, response) => {
// Update UI based on input and response
updateUI(input, response);
},
dependencies: {
dependencies: [userInput, serverResponse, networkStatus],
coordination: deps => {
// Handle dependency changes
handleDependencyChanges(deps);
},
debounce: 300,
immediate: false,
},
});Selection with Validation
const selection = useSelection<User>({
multiple: true,
maxSelections: 10,
validation: user => {
return user.isActive && user.hasPermission;
},
onChange: selectedUsers => {
// Update permissions based on selection
updatePermissions(selectedUsers);
},
});Configuration with Complex Validation
const config = useConfiguration<ComplexConfig>({
initialConfig: defaultConfig,
validation: {
rules: [
{
validate: config => config.apiKey.length >= 32,
message: "API key must be at least 32 characters",
severity: "error",
},
{
validate: config => config.timeout > 0,
message: "Timeout must be positive",
severity: "warning",
},
],
mode: "strict",
realTime: true,
},
persistence: {
key: "complex-config",
storage: "localStorage",
serialization: "json",
},
});TypeScript Support
All primitives are fully typed with TypeScript generics for maximum type safety.
// Typed selection
const stringSelection = useSelection<string>({
/* config */
});
const userSelection = useSelection<User>({
/* config */
});
// Typed configuration
const appConfig = useConfiguration<AppConfig>({
/* config */
});
const userConfig = useConfiguration<UserConfig>({
/* config */
});
// Typed coordination
const stringNumberCoordination = useCoordination<string, number>({
/* config */
});
const userResponseCoordination = useCoordination<User, Response>({
/* config */
});Performance Considerations
- Minimal Re-renders: Primitives use SolidJS signals for optimal performance
- Debounced Coordination: Signal coordination supports debouncing to prevent excessive updates
- Lazy Validation: Validation is performed only when needed
- Efficient Persistence: Persistence operations are optimized and cached
Accessibility
The primitives include built-in accessibility features:
- WCAG Compliance: Validation includes accessibility compliance checking
- Screen Reader Support: All state changes are properly announced
- Keyboard Navigation: Full keyboard support for all interactions
- High Contrast: Support for high contrast mode
- Reduced Motion: Respects user's motion preferences
Examples
Data Table Selection
import { useSelection } from "reynard-selection-primitives";
const dataTableSelection = useSelection<number>({
multiple: true,
allowDeselect: true,
onChange: selectedIndices => {
const selectedData = selectedIndices.map(i => data[i]);
onRowSelect?.(selectedData);
},
});Form Configuration
import { useConfiguration, commonValidators } from "reynard-selection-primitives";
const formConfig = useConfiguration<FormConfig>({
initialConfig: defaultFormConfig,
validation: {
rules: [
commonValidators.required<FormConfig>("Form configuration is required"),
{
validate: config => config.fields.length > 0,
message: "At least one field is required",
severity: "error",
},
],
mode: "strict",
realTime: true,
},
persistence: {
key: "form-config",
storage: "localStorage",
serialization: "json",
},
});Theme and Accessibility
import { useThemeAccessibility } from "reynard-selection-primitives";
const themeAccessibility = useThemeAccessibility({
theme: "light",
colorblind: {
enabled: false,
deficiency: "normal",
severity: "moderate",
},
accessibility: {
highContrast: false,
reducedMotion: false,
fontSize: "medium",
},
});API Reference
useSelection
| Property | Type | Description |
| ----------------- | ------------------------- | ------------------------------------- |
| selected | () => T[] | Get currently selected items |
| isSelected | (item: T) => boolean | Check if item is selected |
| select | (item: T) => void | Select an item |
| deselect | (item: T) => void | Deselect an item |
| toggle | (item: T) => void | Toggle item selection |
| selectMultiple | (items: T[]) => void | Select multiple items |
| selectAll | (items: T[]) => void | Select all items from a list |
| clear | () => void | Clear all selections |
| canSelect | (item: T) => boolean | Check if item can be selected |
| selectionCount | () => number | Get selection count |
| hasSelection | () => boolean | Check if any items are selected |
| isAtMaxCapacity | () => boolean | Check if selection is at max capacity |
| getState | () => SelectionState<T> | Get selection state |
useConfiguration
| Property | Type | Description |
| ------------------ | ------------------------------- | ---------------------------------- |
| config | () => T | Get current configuration |
| updateConfig | (updates: Partial<T>) => void | Update configuration |
| resetConfig | () => void | Reset to initial configuration |
| isValid | () => boolean | Check if configuration is valid |
| validationErrors | () => string[] | Get validation errors |
| hasChanged | () => boolean | Check if configuration has changed |
| getState | () => ConfigurationState<T> | Get configuration state |
| save | () => void | Save configuration to storage |
| load | () => void | Load configuration from storage |
useCoordination<T, U>
| Property | Type | Description |
| --------------- | ------------------------------------ | ------------------------------- |
| primary | () => T | Get primary signal value |
| secondary | () => U | Get secondary signal value |
| coordinate | (primary: T, secondary: U) => void | Manually trigger coordination |
| isCoordinated | () => boolean | Check if coordination is active |
| getState | () => CoordinationState<T, U> | Get coordination state |
| enable | () => void | Enable coordination |
| disable | () => void | Disable coordination |
| reset | () => void | Reset coordination state |
Contributing
Contributions are welcome! Please read our contributing guidelines and code of conduct.
License
MIT License - see LICENSE file for details.
