// $shared/lib/createResponsiveManager.svelte.ts /** * Breakpoint definitions following common device sizes * Customize these values to match your design system */ export interface Breakpoints { /** Mobile devices (portrait phones) */ mobile: number; /** Tablet portrait */ tabletPortrait: number; /** Tablet landscape */ tablet: number; /** Desktop */ desktop: number; /** Large desktop */ desktopLarge: number; } /** * Default breakpoints (matches common Tailwind-like breakpoints) */ const DEFAULT_BREAKPOINTS: Breakpoints = { mobile: 640, // sm tabletPortrait: 768, // md tablet: 1024, // lg desktop: 1280, // xl desktopLarge: 1536, // 2xl }; /** * Orientation type */ export type Orientation = 'portrait' | 'landscape'; /** * Creates a reactive responsive manager that tracks viewport size and breakpoints. * * Provides reactive getters for: * - Current breakpoint detection (isMobile, isTablet, etc.) * - Viewport dimensions (width, height) * - Device orientation (portrait/landscape) * - Custom breakpoint matching * * @param customBreakpoints - Optional custom breakpoint values * @returns Responsive manager instance with reactive properties * * @example * ```svelte * * * {#if responsive.isMobile} * * {:else if responsive.isTablet} * * {:else} * * {/if} * *

Width: {responsive.width}px

*

Orientation: {responsive.orientation}

* ``` */ export function createResponsiveManager(customBreakpoints?: Partial) { const breakpoints: Breakpoints = { ...DEFAULT_BREAKPOINTS, ...customBreakpoints, }; // Reactive state let width = $state(typeof window !== 'undefined' ? window.innerWidth : 0); let height = $state(typeof window !== 'undefined' ? window.innerHeight : 0); // Derived breakpoint states const isMobile = $derived(width < breakpoints.mobile); const isTabletPortrait = $derived( width >= breakpoints.mobile && width < breakpoints.tabletPortrait, ); const isTablet = $derived( width >= breakpoints.tabletPortrait && width < breakpoints.desktop, ); const isDesktop = $derived( width >= breakpoints.desktop && width < breakpoints.desktopLarge, ); const isDesktopLarge = $derived(width >= breakpoints.desktopLarge); // Convenience groupings const isMobileOrTablet = $derived(width < breakpoints.desktop); const isTabletOrDesktop = $derived(width >= breakpoints.tabletPortrait); // Orientation const orientation = $derived(height > width ? 'portrait' : 'landscape'); const isPortrait = $derived(orientation === 'portrait'); const isLandscape = $derived(orientation === 'landscape'); // Touch device detection (best effort) const isTouchDevice = $derived( typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0), ); /** * Initialize responsive tracking * Call this in an $effect or component mount */ function init() { if (typeof window === 'undefined') return; const handleResize = () => { width = window.innerWidth; height = window.innerHeight; }; // Use ResizeObserver for more accurate tracking const resizeObserver = new ResizeObserver(handleResize); resizeObserver.observe(document.documentElement); // Fallback to window resize event window.addEventListener('resize', handleResize, { passive: true }); // Initial measurement handleResize(); return () => { resizeObserver.disconnect(); window.removeEventListener('resize', handleResize); }; } /** * Check if current width matches a custom breakpoint * @param min - Minimum width (inclusive) * @param max - Maximum width (exclusive) */ function matches(min: number, max?: number): boolean { if (max !== undefined) { return width >= min && width < max; } return width >= min; } /** * Get the current breakpoint name */ const currentBreakpoint = $derived( (() => { if (isMobile) return 'mobile'; if (isTabletPortrait) return 'tabletPortrait'; if (isTablet) return 'tablet'; if (isDesktop) return 'desktop'; if (isDesktopLarge) return 'desktopLarge'; return 'xs'; // Fallback for very small screens })(), ); return { // Dimensions get width() { return width; }, get height() { return height; }, // Standard breakpoints get isMobile() { return isMobile; }, get isTabletPortrait() { return isTabletPortrait; }, get isTablet() { return isTablet; }, get isDesktop() { return isDesktop; }, get isDesktopLarge() { return isDesktopLarge; }, // Convenience groupings get isMobileOrTablet() { return isMobileOrTablet; }, get isTabletOrDesktop() { return isTabletOrDesktop; }, // Orientation get orientation() { return orientation; }, get isPortrait() { return isPortrait; }, get isLandscape() { return isLandscape; }, // Device capabilities get isTouchDevice() { return isTouchDevice; }, // Current breakpoint get currentBreakpoint() { return currentBreakpoint; }, // Methods init, matches, // Breakpoint values (for custom logic) breakpoints, }; } export const responsiveManager = createResponsiveManager(); if (typeof window !== 'undefined') { responsiveManager.init(); } /** * Type for the responsive manager instance */ export type ResponsiveManager = ReturnType;