@senka-ai/ui
Version:
A modern, type-safe Svelte 5 UI component library with full theme support, accessibility standards, and robust state management patterns
141 lines (140 loc) • 5.02 kB
JavaScript
/**
* Event handler utilities for consistent event handling across components
*/
/**
* Creates a standardized keyboard event handler
*/
export function createKeyboardHandler(handler, config = { keys: ['Enter', ' '], preventDefault: true }) {
return (event) => {
if (config.disabled)
return;
if (config.keys.includes(event.key)) {
if (config.preventDefault)
event.preventDefault();
if (config.stopPropagation)
event.stopPropagation();
handler();
}
};
}
/**
* Creates a click handler with disabled state support
*/
export function createClickHandler(handler, disabled, stopPropagation) {
return (event) => {
if (disabled)
return;
if (stopPropagation)
event?.stopPropagation();
handler();
};
}
/**
* Creates a safe click handler that prevents action on interactive elements
* Useful for Card components and other containers with clickable children
*/
export function createSafeClickHandler(handler, disabled) {
return (event) => {
if (disabled)
return;
// Prevent action if clicked on interactive elements
const target = event.target;
if (isInteractiveElement(target)) {
return;
}
handler();
};
}
/**
* Common keyboard key sets for different component types
*/
export const KeySets = {
ACTIVATION: ['Enter', ' '], // Enter and Space for buttons, toggles, etc.
NAVIGATION: ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'],
HORIZONTAL_NAVIGATION: ['ArrowLeft', 'ArrowRight'],
VERTICAL_NAVIGATION: ['ArrowUp', 'ArrowDown'],
ESCAPE: ['Escape'],
TAB: ['Tab'],
FORM_SUBMISSION: ['Enter'],
SPACE_ONLY: [' '], // For checkboxes, radio buttons
ENTER_ONLY: ['Enter'], // For form submission
};
/**
* Navigation direction enum for better type safety
*/
export var NavigationDirection;
(function (NavigationDirection) {
NavigationDirection["UP"] = "up";
NavigationDirection["DOWN"] = "down";
NavigationDirection["LEFT"] = "left";
NavigationDirection["RIGHT"] = "right";
})(NavigationDirection || (NavigationDirection = {}));
/**
* Creates a navigation handler for arrow key navigation
* Useful for TabBar, dropdown menus, and other navigable components
*/
export function createNavigationHandler(onNavigate, config = { direction: 'horizontal', wrap: true, preventDefault: true }) {
return (event) => {
if (config.disabled)
return;
const { direction, preventDefault = true } = config;
let handled = false;
// Handle navigation keys based on direction
if (direction === 'horizontal' || direction === 'both') {
if (event.key === 'ArrowLeft') {
onNavigate(NavigationDirection.LEFT);
handled = true;
}
else if (event.key === 'ArrowRight') {
onNavigate(NavigationDirection.RIGHT);
handled = true;
}
}
if (direction === 'vertical' || direction === 'both') {
if (event.key === 'ArrowUp') {
onNavigate(NavigationDirection.UP);
handled = true;
}
else if (event.key === 'ArrowDown') {
onNavigate(NavigationDirection.DOWN);
handled = true;
}
}
if (handled && preventDefault) {
event.preventDefault();
}
};
}
/**
* Creates a combined activation and navigation handler
* Perfect for components like TabBar that need both activation and navigation
*/
export function createActivationNavigationHandler(onActivate, onNavigate, config = {}) {
const { navigationConfig = { direction: 'horizontal', wrap: true, preventDefault: true }, activationConfig = { keys: [...KeySets.ACTIVATION], preventDefault: true }, disabled = false, } = config;
const navigationHandler = createNavigationHandler(onNavigate, { ...navigationConfig, disabled });
const activationHandler = createKeyboardHandler(onActivate, {
keys: activationConfig.keys || [...KeySets.ACTIVATION],
preventDefault: activationConfig.preventDefault,
disabled,
});
return (event) => {
// Try navigation first
navigationHandler(event);
// Then try activation
activationHandler(event);
};
}
/**
* Check if an event target is an interactive element
*/
export function isInteractiveElement(element) {
if (!element || !(element instanceof HTMLElement))
return false;
const tagName = element.tagName.toLowerCase();
const interactiveTags = ['button', 'a', 'input', 'select', 'textarea'];
return (interactiveTags.includes(tagName) ||
element.hasAttribute('onclick') ||
element.hasAttribute('onkeydown') ||
element.getAttribute('role') === 'button' ||
element.closest('button, a, [role="button"]') !== null);
}