UNPKG

claritykit-svelte

Version:

A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility

295 lines (294 loc) 8.41 kB
import { onMount, onDestroy } from 'svelte'; import { writable } from 'svelte/store'; import { isBrowser, safelyAccessDOM } from '../../../utils/environment'; import { debounce, throttle } from './utils'; /** * Hook to handle window resize events */ export function useResize(callback, options = {}) { let resizeObserver = null; const handleResize = (entries) => { if (!entries.length) return; const { width, height } = entries[0].contentRect; callback(width, height); }; // Apply debounce or throttle if specified let resizeHandler = handleResize; if (options.debounceTime) { resizeHandler = debounce(handleResize, options.debounceTime); } else if (options.throttleTime) { resizeHandler = throttle(handleResize, options.throttleTime); } const observe = (node) => { if (isBrowser && typeof ResizeObserver !== 'undefined') { resizeObserver = new ResizeObserver(resizeHandler); resizeObserver.observe(node); } return { destroy() { if (resizeObserver) { resizeObserver.disconnect(); resizeObserver = null; } } }; }; return { observe }; } /** * Hook to handle mouse move events with throttling */ export function useMouseMove(callback, options = {}) { let handler = (event) => { callback(event); }; if (options.throttleTime) { handler = throttle(handler, options.throttleTime); } onMount(() => { if (isBrowser) { window.addEventListener('mousemove', handler); return () => { window.removeEventListener('mousemove', handler); }; } }); } /** * Hook to handle mouse leave events */ export function useMouseLeave(element, callback) { onMount(() => { if (!element || !isBrowser) return; const handleLeave = (event) => { callback(event); }; element.addEventListener('mouseleave', handleLeave); return () => { element.removeEventListener('mouseleave', handleLeave); }; }); } /** * Hook to handle click outside an element */ export function useClickOutside(element, callback) { onMount(() => { if (!element || !isBrowser) return; const handleClick = (event) => { if (element && !element.contains(event.target)) { callback(event); } }; safelyAccessDOM(() => { document.addEventListener('mousedown', handleClick); }); return () => { safelyAccessDOM(() => { document.removeEventListener('mousedown', handleClick); }); }; }); } /** * Hook to handle keyboard events */ export function useKeyPress(targetKey, callback, options = {}) { onMount(() => { if (!isBrowser) return; const { target = window } = options; const handleKeyPress = (event) => { if (event.key === targetKey) { callback(event); } }; target.addEventListener('keydown', handleKeyPress); return () => { target.removeEventListener('keydown', handleKeyPress); }; }); } /** * Hook to manage tooltip state */ export function useTooltip() { const isOpen = writable(false); const content = writable(''); const position = writable({ x: 0, y: 0 }); const targetElement = writable(null); function show(event, tooltipContent, target) { content.set(tooltipContent); position.set({ x: event.clientX, y: event.clientY }); if (target) targetElement.set(target); isOpen.set(true); } function hide() { isOpen.set(false); content.set(''); targetElement.set(null); } function updatePosition(event) { position.set({ x: event.clientX, y: event.clientY }); } return { isOpen: { subscribe: isOpen.subscribe }, content: { subscribe: content.subscribe }, position: { subscribe: position.subscribe }, targetElement: { subscribe: targetElement.subscribe }, show, hide, updatePosition }; } /** * Hook to manage chart hover state */ export function useHoverState() { const hoveredIndex = writable(null); const hoveredPoint = writable(null); const isHovering = writable(false); function handleMouseEnter(index, point) { hoveredIndex.set(index); hoveredPoint.set(point); isHovering.set(true); } function handleMouseLeave() { isHovering.set(false); // Don't clear immediately to allow for tooltip fade-out if (isBrowser) { setTimeout(() => { if (!get(isHovering)) { hoveredIndex.set(null); hoveredPoint.set(null); } }, 100); } else { // Immediate clear on server hoveredIndex.set(null); hoveredPoint.set(null); } } return { hoveredIndex: { subscribe: hoveredIndex.subscribe }, hoveredPoint: { subscribe: hoveredPoint.subscribe }, isHovering: { subscribe: isHovering.subscribe }, handleMouseEnter, handleMouseLeave }; } /** * Hook to manage chart zoom state */ export function useZoom(initialScale = 1, options = {}) { const { minScale = 0.5, maxScale = 5 } = options; const scale = writable(initialScale); const position = writable({ x: 0, y: 0 }); function zoom(factor, focalPoint = { x: 0, y: 0 }) { scale.update(currentScale => { let newScale = currentScale * factor; newScale = Math.max(minScale, Math.min(maxScale, newScale)); // Adjust position to zoom toward focal point if (newScale !== currentScale) { position.update(pos => ({ x: focalPoint.x - (focalPoint.x - pos.x) * (newScale / currentScale), y: focalPoint.y - (focalPoint.y - pos.y) * (newScale / currentScale) })); } return newScale; }); } function zoomIn(factor = 1.2, focalPoint = { x: 0, y: 0 }) { zoom(factor, focalPoint); } function zoomOut(factor = 1.2, focalPoint = { x: 0, y: 0 }) { zoom(1 / factor, focalPoint); } function reset() { scale.set(initialScale); position.set({ x: 0, y: 0 }); } function pan(dx, dy) { position.update(pos => ({ x: pos.x + dx, y: pos.y + dy })); } return { scale: { subscribe: scale.subscribe }, position: { subscribe: position.subscribe }, zoomIn, zoomOut, reset, pan }; } /** * Hook to manage chart tooltip visibility with a delay */ export function useDelayedTooltip(delay = 300) { const isVisible = writable(false); let showTimeout = null; function show() { if (showTimeout) clearTimeout(showTimeout); if (isBrowser) { showTimeout = setTimeout(() => { isVisible.set(true); }, delay); } else { // Immediate show on server isVisible.set(true); } } function hide(immediate = false) { if (showTimeout) { clearTimeout(showTimeout); showTimeout = null; } if (immediate) { isVisible.set(false); } else if (isBrowser) { // Small delay to allow for mouse movement between elements setTimeout(() => { isVisible.set(false); }, 100); } else { // Immediate hide on server isVisible.set(false); } } onDestroy(() => { if (showTimeout) { clearTimeout(showTimeout); } }); return { isVisible: { subscribe: isVisible.subscribe }, show, hide }; } /** * Helper function to get a store's value */ function get(store) { let value; store.subscribe(v => { value = v; })(); return value; }