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
JavaScript
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;
}