import { type Fiber, MemoComponentTag, SimpleMemoComponentTag, SuspenseComponentTag, getDisplayName, hasMemoCache, } from 'bippy'; import { type ClassValue, clsx } from 'clsx'; import { IS_CLIENT } from './constants'; import { twMerge } from 'tailwind-merge'; export const cn = (...inputs: Array): string => { return twMerge(clsx(inputs)); }; export const isFirefox = /* @__PURE__ */ typeof navigator !== 'undefined' && navigator.userAgent.includes('Firefox'); export const onIdle = (callback: () => void) => { if ('scheduler' in globalThis) { return globalThis.scheduler.postTask(callback, { priority: 'background', }); } if ('requestIdleCallback' in window) { return requestIdleCallback(callback); } return setTimeout(callback, 0); }; export const throttle = ( callback: (e?: E) => void, delay: number, ): ((e?: E) => void) => { let lastCall = 0; return (e?: E) => { const now = Date.now(); if (now - lastCall >= delay) { lastCall = now; return callback(e); } return undefined; }; }; export const tryOrElse = (fn: () => T, defaultValue: T): T => { try { return fn(); } catch { return defaultValue; } }; export const readLocalStorage = (storageKey: string): T | null => { if (!IS_CLIENT) return null; try { const stored = localStorage.getItem(storageKey); return stored ? JSON.parse(stored) : null; } catch { return null; } }; export const saveLocalStorage = (storageKey: string, state: T): void => { if (!IS_CLIENT) return; try { window.localStorage.setItem(storageKey, JSON.stringify(state)); } catch {} }; export const removeLocalStorage = (storageKey: string): void => { if (!IS_CLIENT) return; try { window.localStorage.removeItem(storageKey); } catch {} }; interface WrapperBadge { type: 'memo' | 'forwardRef' | 'lazy' | 'suspense' | 'profiler' | 'strict'; title: string; compiler?: boolean; } export interface ExtendedDisplayName { name: string | null; wrappers: Array; wrapperTypes: Array; } // React internal tags not exported by bippy const LazyComponentTag = 24; const ProfilerTag = 12; export const getExtendedDisplayName = (fiber: Fiber): ExtendedDisplayName => { if (!fiber) { return { name: 'Unknown', wrappers: [], wrapperTypes: [], }; } const { tag, type, elementType } = fiber; let name = getDisplayName(type); const wrappers: Array = []; const wrapperTypes: Array = []; if ( hasMemoCache(fiber) || tag === SimpleMemoComponentTag || tag === MemoComponentTag || (type as { $$typeof?: symbol })?.$$typeof === Symbol.for('react.memo') || (elementType as { $$typeof?: symbol })?.$$typeof === Symbol.for('react.memo') ) { const compiler = hasMemoCache(fiber); wrapperTypes.push({ type: 'memo', title: compiler ? 'This component has been auto-memoized by the React Compiler.' : 'Memoized component that skips re-renders if props are the same', compiler, }); } if (tag === LazyComponentTag) { wrapperTypes.push({ type: 'lazy', title: 'Lazily loaded component that supports code splitting', }); } if (tag === SuspenseComponentTag) { wrapperTypes.push({ type: 'suspense', title: 'Component that can suspend while content is loading', }); } if (tag === ProfilerTag) { wrapperTypes.push({ type: 'profiler', title: 'Component that measures rendering performance', }); } if (typeof name === 'string') { const wrapperRegex = /^(\w+)\((.*)\)$/; let currentName = name; while (wrapperRegex.test(currentName)) { const match = currentName.match(wrapperRegex); if (match?.[1] && match?.[2]) { wrappers.unshift(match[1]); currentName = match[2]; } else { break; } } name = currentName; } return { name: name || 'Unknown', wrappers, wrapperTypes, }; };