UNPKG

@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
/** * 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); }