| |
| |
| |
| |
|
|
| |
| |
| |
| const getBreakpointValue = (variableName: string): number => { |
| const root = document.documentElement; |
| const value = getComputedStyle(root).getPropertyValue(variableName).trim(); |
|
|
| if (!value) { |
| throw new Error(`CSS variable ${variableName} is not defined`); |
| } |
|
|
| const parsed = parseInt(value, 10); |
| if (isNaN(parsed)) { |
| throw new Error(`CSS variable ${variableName} is not a valid number: ${value}`); |
| } |
|
|
| return parsed; |
| }; |
|
|
| |
| let mobileBreakpoint: number | null = null; |
| let mobileMediaQuery: MediaQueryList | null = null; |
|
|
| |
| |
| |
| const getMobileMediaQuery = (): MediaQueryList => { |
| if (mobileMediaQuery === null) { |
| if (mobileBreakpoint === null) { |
| mobileBreakpoint = getBreakpointValue('--breakpoint-mobile'); |
| } |
| mobileMediaQuery = window.matchMedia(`(max-width: ${mobileBreakpoint}px)`); |
| } |
| return mobileMediaQuery; |
| }; |
|
|
| |
| export const FORCE_NARROW_STORAGE_KEY = 'info_radar_force_narrow'; |
|
|
| |
| export const FORCE_NARROW_CHANGE_EVENT = 'force-narrow-change'; |
|
|
| export const getForceNarrowScreen = (): boolean => |
| localStorage.getItem(FORCE_NARROW_STORAGE_KEY) === '1'; |
|
|
| export const syncForceNarrowAttribute = (): void => { |
| const root = document.documentElement; |
| if (getForceNarrowScreen()) root.setAttribute('data-force-narrow', ''); |
| else root.removeAttribute('data-force-narrow'); |
| }; |
|
|
| let forceNarrowStorageListenerAttached = false; |
|
|
| |
| export const initForceNarrowFromStorage = (): void => { |
| syncForceNarrowAttribute(); |
| if (forceNarrowStorageListenerAttached) return; |
| forceNarrowStorageListenerAttached = true; |
| window.addEventListener('storage', (e: StorageEvent) => { |
| if (e.key !== FORCE_NARROW_STORAGE_KEY) return; |
| syncForceNarrowAttribute(); |
| window.dispatchEvent(new Event(FORCE_NARROW_CHANGE_EVENT)); |
| window.dispatchEvent(new Event('resize')); |
| }); |
| }; |
|
|
| export const setForceNarrowScreen = (enabled: boolean): void => { |
| if (enabled) localStorage.setItem(FORCE_NARROW_STORAGE_KEY, '1'); |
| else localStorage.removeItem(FORCE_NARROW_STORAGE_KEY); |
| syncForceNarrowAttribute(); |
| window.dispatchEvent(new Event(FORCE_NARROW_CHANGE_EVENT)); |
| window.dispatchEvent(new Event('resize')); |
| }; |
|
|
| |
| export const isNarrowScreen = (): boolean => |
| getMobileMediaQuery().matches || getForceNarrowScreen(); |
|
|
| |
| |
| |
| |
| export const isMobileDevice = (): boolean => { |
| |
| const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0; |
| |
| |
| const hasMouse = window.matchMedia('(pointer: fine)').matches; |
| |
| |
| const hasHover = window.matchMedia('(hover: hover)').matches; |
| |
| |
| return hasTouch && (!hasMouse || !hasHover); |
| }; |
|
|
| |
| |
| |
| |
| |
| export const getVerticalScrollbarWidth = (): number => { |
| |
| |
| const width = window.innerWidth - document.documentElement.clientWidth; |
| return width > 0 ? width : 0; |
| }; |
|
|
| |
| |
| |
| |
| |
| export const isTraditionalScrollbar = (): boolean => getVerticalScrollbarWidth() > 0; |