@opensite/hooks 2.0.8 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/browser/opensite-hooks.umd.cjs +1 -1
- package/dist/browser/opensite-hooks.umd.js +1 -1
- package/dist/browser/opensite-hooks.umd.js.map +1 -1
- package/dist/core/index.cjs +2 -0
- package/dist/core/index.d.ts +5 -1
- package/dist/core/index.js +2 -0
- package/dist/core/useIsTouchDevice.cjs +151 -0
- package/dist/core/useIsTouchDevice.d.ts +104 -0
- package/dist/core/useIsTouchDevice.js +151 -0
- package/dist/core/useMediaQuery.cjs +6 -8
- package/dist/core/useMediaQuery.js +6 -8
- package/dist/core/useScreen.cjs +205 -0
- package/dist/core/useScreen.d.ts +155 -0
- package/dist/core/useScreen.js +205 -0
- package/package.json +11 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opensite-hooks.umd.js","sources":["../../src/core/useIsomorphicLayoutEffect.ts","../../src/core/useDebounceCallback.ts","../../src/core/useDebounceValue.ts","../../src/core/useOnClickOutside.ts","../../src/core/usePlatformFromUrl.ts","../../src/core/useEventListener.ts","../../src/core/useIsClient.ts","../../src/core/useMap.ts","../../src/core/websiteExtractorTypes.ts","../../src/core/websiteExtractorService.ts","../../src/core/useWebsiteExtractorBase.ts","../../src/core/useOpenGraphExtractor.ts","../../src/core/useBoolean.ts","../../src/core/useCopyToClipboard.ts","../../src/core/useHover.ts","../../src/core/useLocalStorage.ts","../../src/core/useMediaQuery.ts","../../src/core/usePrevious.ts","../../src/core/useResizeObserver.ts","../../src/core/useSessionStorage.ts","../../src/core/useThrottle.ts","../../src/core/useWebsiteLinksExtractor.ts","../../src/core/useWebsiteMetaExtractor.ts","../../src/core/useWebsiteRssExtractor.ts","../../src/core/useWebsiteSchemaExtractor.ts"],"sourcesContent":["import { useEffect, useLayoutEffect } from \"react\";\n\nexport const useIsomorphicLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n","import { useCallback, useEffect, useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\nexport interface DebounceOptions {\n leading?: boolean;\n trailing?: boolean;\n maxWait?: number;\n}\n\nexport interface DebouncedCallback<T extends (...args: any[]) => void> {\n debouncedCallback: (...args: Parameters<T>) => void;\n cancel: () => void;\n flush: () => void;\n}\n\nexport function useDebounceCallback<T extends (...args: any[]) => void>(\n callback: T,\n delay: number,\n options: DebounceOptions = {}\n): DebouncedCallback<T> {\n const callbackRef = useRef(callback);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const maxTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastArgsRef = useRef<Parameters<T> | null>(null);\n\n const leading = options.leading ?? false;\n const trailing = options.trailing ?? true;\n const maxWait = options.maxWait;\n const wait = Math.max(0, delay);\n\n useIsomorphicLayoutEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n const clearTimers = useCallback(() => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n if (maxTimeoutRef.current) {\n clearTimeout(maxTimeoutRef.current);\n maxTimeoutRef.current = null;\n }\n }, []);\n\n const invoke = useCallback(() => {\n if (!lastArgsRef.current) {\n return;\n }\n const args = lastArgsRef.current;\n lastArgsRef.current = null;\n callbackRef.current(...args);\n }, []);\n\n const debouncedCallback = useCallback(\n (...args: Parameters<T>) => {\n lastArgsRef.current = args;\n\n const shouldInvokeLeading =\n leading && timeoutRef.current === null && maxTimeoutRef.current === null;\n if (shouldInvokeLeading) {\n invoke();\n }\n\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n if (trailing) {\n timeoutRef.current = setTimeout(() => {\n timeoutRef.current = null;\n if (lastArgsRef.current) {\n invoke();\n }\n if (maxTimeoutRef.current) {\n clearTimeout(maxTimeoutRef.current);\n maxTimeoutRef.current = null;\n }\n }, wait);\n }\n\n if (maxWait !== undefined && maxWait !== null && trailing) {\n if (!maxTimeoutRef.current) {\n const maxDelay = Math.max(0, maxWait);\n maxTimeoutRef.current = setTimeout(() => {\n maxTimeoutRef.current = null;\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n if (lastArgsRef.current) {\n invoke();\n }\n }, maxDelay);\n }\n }\n },\n [invoke, leading, trailing, maxWait, wait]\n );\n\n const cancel = useCallback(() => {\n clearTimers();\n lastArgsRef.current = null;\n }, [clearTimers]);\n\n const flush = useCallback(() => {\n if (!lastArgsRef.current) {\n return;\n }\n clearTimers();\n invoke();\n }, [clearTimers, invoke]);\n\n useEffect(() => () => cancel(), [cancel]);\n\n return { debouncedCallback, cancel, flush };\n}\n","import { useEffect, useState } from \"react\";\nimport { DebounceOptions, useDebounceCallback } from \"./useDebounceCallback.js\";\n\nexport function useDebounceValue<T>(\n value: T,\n delay: number,\n options?: DebounceOptions\n): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n const { debouncedCallback, cancel } = useDebounceCallback(\n (next: T) => {\n setDebouncedValue(next);\n },\n delay,\n options\n );\n\n useEffect(() => {\n debouncedCallback(value);\n }, [debouncedCallback, value]);\n\n useEffect(() => () => cancel(), [cancel]);\n\n return debouncedValue;\n}\n","import { useEffect, useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\ntype PossibleRef<T extends HTMLElement> = React.RefObject<T>;\n\n// Debug mode - set to true to enable console logging\nconst DEBUG_CLICK_OUTSIDE = false;\n\n/**\n * Get the owner document for a given element.\n * This is crucial for iframe support - returns the iframe's document if the element is inside one.\n */\nfunction getOwnerDocument(element: HTMLElement | null): Document {\n return element?.ownerDocument ?? document;\n}\n\n/**\n * Detect if an element is inside an iframe by checking if its document is different from the top document\n */\nfunction isInIframe(element: HTMLElement | null): boolean {\n if (!element) return false;\n const doc = getOwnerDocument(element);\n return doc !== document;\n}\n\nexport function useOnClickOutside<T extends HTMLElement>(\n ref: PossibleRef<T> | Array<PossibleRef<T>>,\n handler: (event: MouseEvent | TouchEvent | PointerEvent) => void,\n eventType?: \"mousedown\" | \"mouseup\" | \"click\" | \"touchstart\" | \"pointerdown\",\n options?: AddEventListenerOptions | boolean\n): void {\n const handlerRef = useRef(handler);\n\n // Use useIsomorphicLayoutEffect to update handler ref synchronously\n // This prevents stale closures in rapid click scenarios where the event\n // listener (registered below) might fire before the ref is updated\n useIsomorphicLayoutEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") {\n return;\n }\n\n const supportsPointerEvents =\n typeof window !== \"undefined\" &&\n typeof window.PointerEvent !== \"undefined\";\n const resolvedEventType =\n eventType ?? (supportsPointerEvents ? \"pointerdown\" : \"mousedown\");\n\n const refs = Array.isArray(ref) ? ref : [ref];\n\n // Detect the correct document to attach listeners to\n // If any ref is inside an iframe, use that iframe's document\n let targetDocument: Document = document;\n let inIframe = false;\n\n for (const currentRef of refs) {\n if (currentRef.current) {\n const refDocument = getOwnerDocument(currentRef.current);\n if (refDocument !== document) {\n targetDocument = refDocument;\n inIframe = true;\n break;\n }\n }\n }\n\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Setup:\", {\n eventType: resolvedEventType,\n inIframe,\n documentLocation: inIframe ? \"iframe\" : \"parent\",\n refsCount: refs.length,\n refsWithNodes: refs.filter((r) => r.current).length,\n });\n }\n\n const listener = (event: MouseEvent | TouchEvent | PointerEvent) => {\n const target = event.target;\n\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Event fired:\", {\n eventType: event.type,\n targetTag: target instanceof Element ? target.tagName : \"unknown\",\n targetInIframe: target instanceof Node ? isInIframe(target as HTMLElement) : false,\n });\n }\n\n if (typeof Node === \"undefined\" || !(target instanceof Node)) {\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Early return: target not a Node\");\n }\n return;\n }\n\n const clickedInside = refs.some((currentRef) => {\n const node = currentRef.current;\n const contains = node ? node.contains(target) : false;\n\n if (DEBUG_CLICK_OUTSIDE && node) {\n console.log(\"[useOnClickOutside] Checking ref:\", {\n refTag: node.tagName,\n contains,\n nodeInIframe: isInIframe(node),\n });\n }\n\n return contains;\n });\n\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Click result:\", {\n clickedInside,\n willCallHandler: !clickedInside,\n });\n }\n\n if (!clickedInside) {\n handlerRef.current(event);\n }\n };\n\n targetDocument.addEventListener(resolvedEventType, listener, options);\n\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Listener attached to:\", {\n documentType: inIframe ? \"iframe document\" : \"parent document\",\n eventType: resolvedEventType,\n });\n }\n\n return () => {\n targetDocument.removeEventListener(resolvedEventType, listener, options);\n\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Listener removed from:\", {\n documentType: inIframe ? \"iframe document\" : \"parent document\",\n });\n }\n };\n }, [eventType, options, ref]);\n}\n","import { useMemo } from \"react\";\n\n/**\n * Supported social platform names\n */\nexport type SocialPlatformName =\n | \"instagram\"\n | \"linkedin\"\n | \"google\"\n | \"facebook\"\n | \"tiktok\"\n | \"youtube\"\n | \"yelp\"\n | \"spotify\"\n | \"apple\"\n | \"x\"\n | \"github\"\n | \"discord\"\n | \"snapchat\"\n | \"dev\"\n | \"substack\"\n | \"reddit\"\n | \"pinterest\"\n | \"threads\"\n | \"twitch\"\n | \"whatsapp\"\n | \"telegram\"\n | \"medium\"\n | \"patreon\"\n | \"onlyfans\"\n | \"eventbrite\"\n | \"npmjs\"\n | \"crates\"\n | \"rubygems\"\n | \"behance\"\n | \"dribbble\"\n | \"unknown\";\n\n/**\n * Platform hostname mappings for O(1) lookup performance.\n * Includes standard domains and known URL variants/shorteners.\n */\nconst PLATFORM_HOSTNAME_MAP = new Map<string, SocialPlatformName>([\n // Instagram\n [\"instagram.com\", \"instagram\"],\n [\"www.instagram.com\", \"instagram\"],\n [\"instagr.am\", \"instagram\"],\n [\"www.instagr.am\", \"instagram\"],\n\n // LinkedIn\n [\"linkedin.com\", \"linkedin\"],\n [\"www.linkedin.com\", \"linkedin\"],\n [\"ca.linkedin.com\", \"linkedin\"],\n [\"uk.linkedin.com\", \"linkedin\"],\n [\"in.linkedin.com\", \"linkedin\"],\n [\"lnkd.in\", \"linkedin\"],\n\n // Google (including Maps variants)\n [\"google.com\", \"google\"],\n [\"www.google.com\", \"google\"],\n [\"maps.google.com\", \"google\"],\n [\"goo.gl\", \"google\"],\n [\"maps.app.goo.gl\", \"google\"],\n [\"g.co\", \"google\"],\n\n // Facebook\n [\"facebook.com\", \"facebook\"],\n [\"www.facebook.com\", \"facebook\"],\n [\"m.facebook.com\", \"facebook\"],\n [\"fb.com\", \"facebook\"],\n [\"fb.me\", \"facebook\"],\n [\"on.fb.me\", \"facebook\"],\n\n // TikTok\n [\"tiktok.com\", \"tiktok\"],\n [\"www.tiktok.com\", \"tiktok\"],\n [\"m.tiktok.com\", \"tiktok\"],\n [\"vm.tiktok.com\", \"tiktok\"],\n [\"vt.tiktok.com\", \"tiktok\"],\n\n // YouTube\n [\"youtube.com\", \"youtube\"],\n [\"www.youtube.com\", \"youtube\"],\n [\"m.youtube.com\", \"youtube\"],\n [\"youtu.be\", \"youtube\"],\n\n // Yelp\n [\"yelp.com\", \"yelp\"],\n [\"www.yelp.com\", \"yelp\"],\n [\"m.yelp.com\", \"yelp\"],\n\n // Spotify\n [\"spotify.com\", \"spotify\"],\n [\"www.spotify.com\", \"spotify\"],\n [\"open.spotify.com\", \"spotify\"],\n [\"play.spotify.com\", \"spotify\"],\n [\"spoti.fi\", \"spotify\"],\n [\"spotify.link\", \"spotify\"],\n\n // Apple\n [\"apple.com\", \"apple\"],\n [\"www.apple.com\", \"apple\"],\n [\"music.apple.com\", \"apple\"],\n [\"podcasts.apple.com\", \"apple\"],\n [\"apps.apple.com\", \"apple\"],\n [\"itunes.apple.com\", \"apple\"],\n\n // X (formerly Twitter)\n [\"x.com\", \"x\"],\n [\"www.x.com\", \"x\"],\n [\"twitter.com\", \"x\"],\n [\"www.twitter.com\", \"x\"],\n [\"t.co\", \"x\"],\n\n // GitHub\n [\"github.com\", \"github\"],\n [\"www.github.com\", \"github\"],\n [\"gist.github.com\", \"github\"],\n [\"raw.githubusercontent.com\", \"github\"],\n [\"github.io\", \"github\"],\n\n // Discord\n [\"discord.com\", \"discord\"],\n [\"www.discord.com\", \"discord\"],\n [\"discord.gg\", \"discord\"],\n [\"discordapp.com\", \"discord\"],\n [\"discordapp.net\", \"discord\"],\n [\"discord.new\", \"discord\"],\n [\"discord.gift\", \"discord\"],\n [\"discord.gifts\", \"discord\"],\n [\"dis.gd\", \"discord\"],\n\n // Snapchat\n [\"snapchat.com\", \"snapchat\"],\n [\"www.snapchat.com\", \"snapchat\"],\n [\"snap.com\", \"snapchat\"],\n [\"www.snap.com\", \"snapchat\"],\n [\"story.snapchat.com\", \"snapchat\"],\n [\"web.snapchat.com\", \"snapchat\"],\n\n // Dev (Dev.to)\n [\"dev.to\", \"dev\"],\n [\"www.dev.to\", \"dev\"],\n\n // Substack\n [\"substack.com\", \"substack\"],\n [\"www.substack.com\", \"substack\"],\n\n // Reddit\n [\"reddit.com\", \"reddit\"],\n [\"www.reddit.com\", \"reddit\"],\n [\"old.reddit.com\", \"reddit\"],\n [\"new.reddit.com\", \"reddit\"],\n [\"i.redd.it\", \"reddit\"],\n [\"v.redd.it\", \"reddit\"],\n [\"redd.it\", \"reddit\"],\n [\"preview.redd.it\", \"reddit\"],\n\n // Pinterest\n [\"pinterest.com\", \"pinterest\"],\n [\"www.pinterest.com\", \"pinterest\"],\n [\"pin.it\", \"pinterest\"],\n [\"in.pinterest.com\", \"pinterest\"],\n [\"br.pinterest.com\", \"pinterest\"],\n [\"uk.pinterest.com\", \"pinterest\"],\n\n // Threads (Meta)\n [\"threads.net\", \"threads\"],\n [\"www.threads.net\", \"threads\"],\n [\"threads.com\", \"threads\"],\n [\"www.threads.com\", \"threads\"],\n\n // Twitch\n [\"twitch.tv\", \"twitch\"],\n [\"www.twitch.tv\", \"twitch\"],\n [\"m.twitch.tv\", \"twitch\"],\n\n // WhatsApp\n [\"whatsapp.com\", \"whatsapp\"],\n [\"www.whatsapp.com\", \"whatsapp\"],\n [\"wa.me\", \"whatsapp\"],\n [\"web.whatsapp.com\", \"whatsapp\"],\n\n // Telegram\n [\"telegram.org\", \"telegram\"],\n [\"www.telegram.org\", \"telegram\"],\n [\"t.me\", \"telegram\"],\n [\"telegram.me\", \"telegram\"],\n [\"telegram.dog\", \"telegram\"],\n\n // Medium\n [\"medium.com\", \"medium\"],\n [\"www.medium.com\", \"medium\"],\n // Note: Custom Medium domains would need additional logic\n\n // Patreon\n [\"patreon.com\", \"patreon\"],\n [\"www.patreon.com\", \"patreon\"],\n\n // OnlyFans\n [\"onlyfans.com\", \"onlyfans\"],\n [\"www.onlyfans.com\", \"onlyfans\"],\n\n // Eventbrite\n [\"eventbrite.com\", \"eventbrite\"],\n [\"www.eventbrite.com\", \"eventbrite\"],\n [\"eventbrite.co.uk\", \"eventbrite\"],\n [\"eventbrite.com.au\", \"eventbrite\"],\n [\"eventbrite.ca\", \"eventbrite\"],\n [\"eventbrite.de\", \"eventbrite\"],\n [\"eventbrite.fr\", \"eventbrite\"],\n [\"eventbrite.es\", \"eventbrite\"],\n [\"eventbrite.it\", \"eventbrite\"],\n [\"eventbrite.ie\", \"eventbrite\"],\n [\"eventbrite.nl\", \"eventbrite\"],\n [\"eventbrite.co.nz\", \"eventbrite\"],\n [\"eventbriteapi.com\", \"eventbrite\"],\n [\"evbuc.com\", \"eventbrite\"],\n\n // npm\n [\"npmjs.com\", \"npmjs\"],\n [\"www.npmjs.com\", \"npmjs\"],\n [\"npmjs.org\", \"npmjs\"],\n [\"www.npmjs.org\", \"npmjs\"],\n [\"registry.npmjs.org\", \"npmjs\"],\n [\"registry.npmjs.com\", \"npmjs\"],\n [\"replicate.npmjs.com\", \"npmjs\"],\n\n // Crates.io (Rust)\n [\"crates.io\", \"crates\"],\n [\"www.crates.io\", \"crates\"],\n\n // RubyGems\n [\"rubygems.org\", \"rubygems\"],\n [\"www.rubygems.org\", \"rubygems\"],\n\n // Behance\n [\"behance.net\", \"behance\"],\n [\"www.behance.net\", \"behance\"],\n // Optional: catch common subdomains if you see them in your data\n [\"portfolio.behance.net\", \"behance\"],\n [\"mir-s3-cdn-cf.behance.net\", \"behance\"],\n\n // Dribbble\n [\"dribbble.com\", \"dribbble\"],\n [\"www.dribbble.com\", \"dribbble\"],\n [\"drbl.in\", \"dribbble\"],\n]);\n\n/**\n * Extracts the social platform name from a URL string.\n * Uses the native URL API for validation and hostname extraction,\n * then performs O(1) Map lookup for platform identification.\n *\n * @param url - The URL string to analyze\n * @returns The identified platform name or \"unknown\" if not recognized\n *\n * @example\n * ```tsx\n * const platform = usePlatformFromUrl(\"https://www.youtube.com/@iamthedelo\");\n * // Returns: \"youtube\"\n *\n * const platform = usePlatformFromUrl(\"https://maps.app.goo.gl/XDuog3V5fTuPcWCH7\");\n * // Returns: \"google\"\n *\n * const platform = usePlatformFromUrl(\"https://twitter.com/jordanhudgens\");\n * // Returns: \"x\"\n *\n * const platform = usePlatformFromUrl(\"not-a-url\");\n * // Returns: \"unknown\"\n * ```\n */\nexport function usePlatformFromUrl(url: string): SocialPlatformName {\n return useMemo(() => {\n if (!url || typeof url !== \"string\") {\n return \"unknown\";\n }\n\n const trimmedUrl = url.trim();\n if (!trimmedUrl) {\n return \"unknown\";\n }\n\n try {\n const urlObj = new URL(trimmedUrl);\n const hostname = urlObj.hostname.toLowerCase();\n\n // O(1) Map lookup first (fastest)\n const platform = PLATFORM_HOSTNAME_MAP.get(hostname);\n\n if (platform) {\n return platform;\n }\n\n // Fallback pattern matching for custom domains (slower but more comprehensive)\n if (hostname.endsWith(\".substack.com\")) {\n return \"substack\";\n }\n if (hostname.endsWith(\".github.io\")) {\n return \"github\";\n }\n if (hostname.includes(\"pinterest.com\")) {\n return \"pinterest\";\n }\n if (hostname.includes(\"eventbrite.\")) {\n return \"eventbrite\";\n }\n if (hostname.endsWith(\".medium.com\")) {\n return \"medium\";\n }\n if (hostname.endsWith(\".behance.net\")) {\n return \"behance\";\n }\n\n return \"unknown\";\n } catch {\n return \"unknown\";\n }\n }, [url]);\n}\n","import { useEffect, useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\ntype EventTargetLike = Window | Document | HTMLElement | null;\ntype ElementRef = React.RefObject<HTMLElement>;\n\nconst isRefObject = (value: unknown): value is ElementRef =>\n !!value && typeof value === \"object\" && \"current\" in value;\n\nexport function useEventListener<K extends keyof WindowEventMap>(\n eventName: K,\n handler: (event: WindowEventMap[K]) => void,\n element?: Window,\n options?: AddEventListenerOptions | boolean\n): void;\nexport function useEventListener<K extends keyof DocumentEventMap>(\n eventName: K,\n handler: (event: DocumentEventMap[K]) => void,\n element: Document,\n options?: AddEventListenerOptions | boolean\n): void;\nexport function useEventListener<K extends keyof HTMLElementEventMap>(\n eventName: K,\n handler: (event: HTMLElementEventMap[K]) => void,\n element: ElementRef,\n options?: AddEventListenerOptions | boolean\n): void;\nexport function useEventListener<K extends keyof HTMLElementEventMap>(\n eventName: K,\n handler: (event: HTMLElementEventMap[K]) => void,\n element: HTMLElement,\n options?: AddEventListenerOptions | boolean\n): void;\nexport function useEventListener(\n eventName: string,\n handler: EventListenerOrEventListenerObject,\n element?: EventTargetLike | ElementRef,\n options?: AddEventListenerOptions | boolean\n): void {\n const savedHandler = useRef(handler);\n\n useIsomorphicLayoutEffect(() => {\n savedHandler.current = handler;\n }, [handler]);\n\n useEffect(() => {\n const isWindow =\n typeof Window !== \"undefined\" && element instanceof Window;\n const isDocument =\n typeof Document !== \"undefined\" && element instanceof Document;\n\n const target: EventTargetLike | null =\n element === undefined\n ? typeof window !== \"undefined\"\n ? window\n : null\n : isWindow || isDocument\n ? element\n : typeof HTMLElement !== \"undefined\" && element instanceof HTMLElement\n ? element\n : isRefObject(element)\n ? element.current\n : null;\n\n if (!target?.addEventListener) {\n return;\n }\n\n const listener = (event: Event) => {\n const currentHandler = savedHandler.current;\n if (typeof currentHandler === \"function\") {\n currentHandler(event);\n } else {\n currentHandler.handleEvent(event);\n }\n };\n\n target.addEventListener(eventName, listener, options);\n\n return () => {\n target.removeEventListener(eventName, listener, options);\n };\n }, [eventName, element, options]);\n}\n","import { useEffect, useState } from \"react\";\n\nexport function useIsClient(): boolean {\n const [isClient, setIsClient] = useState(false);\n\n useEffect(() => {\n setIsClient(true);\n }, []);\n\n return isClient;\n}\n","import { useMemo, useRef, useState } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\nexport interface MapActions<K, V> {\n set: (key: K, value: V) => void;\n setAll: (entries: Map<K, V> | [K, V][]) => void;\n remove: (key: K) => void;\n clear: () => void;\n get: (key: K) => V | undefined;\n has: (key: K) => boolean;\n}\n\nexport function useMap<K, V>(\n initialState?: Map<K, V> | [K, V][]\n): [Map<K, V>, MapActions<K, V>] {\n const [map, setMap] = useState<Map<K, V>>(() => {\n if (initialState instanceof Map) {\n return new Map(initialState);\n }\n if (Array.isArray(initialState)) {\n return new Map(initialState);\n }\n return new Map();\n });\n\n const mapRef = useRef(map);\n\n // Use useIsomorphicLayoutEffect to update mapRef synchronously with state\n // This ensures get() and has() methods always read the current map state\n // without a timing window where they could return stale values\n useIsomorphicLayoutEffect(() => {\n mapRef.current = map;\n }, [map]);\n\n const actions = useMemo<MapActions<K, V>>(\n () => ({\n set: (key: K, value: V) => {\n setMap((prev) => {\n const next = new Map(prev);\n next.set(key, value);\n return next;\n });\n },\n setAll: (entries: Map<K, V> | [K, V][]) => {\n setMap(entries instanceof Map ? new Map(entries) : new Map(entries));\n },\n remove: (key: K) => {\n setMap((prev) => {\n const next = new Map(prev);\n next.delete(key);\n return next;\n });\n },\n clear: () => setMap(new Map()),\n get: (key: K) => mapRef.current.get(key),\n has: (key: K) => mapRef.current.has(key),\n }),\n []\n );\n\n return [map, actions];\n}\n","export interface WebsiteExtractCacheMeta {\n hit: boolean;\n ageSeconds: number;\n ttlSeconds: number;\n staleWhileRevalidateSeconds: number;\n}\n\nexport interface WebsiteExtractMeta {\n requestedUrl: string;\n finalUrl: string;\n url: string;\n normalizedUrl: string;\n status: number;\n contentType: string;\n fetchedAt: string;\n bodyBytes: number;\n bodyTruncated: boolean;\n maxBodyBytes: number;\n cache: WebsiteExtractCacheMeta;\n}\n\nexport interface WebsiteExtractorError {\n message: string;\n status?: number;\n raw?: unknown;\n}\n\nexport interface WebsiteExtractorOptions {\n url?: string;\n apiKey?: string;\n baseUrl?: string;\n debounceMs?: number;\n refreshDebounceMs?: number;\n enabled?: boolean;\n cache?: boolean;\n}\n\nexport interface WebsiteExtractorState<TData, TRaw = TData> {\n loading: boolean;\n data?: TData;\n raw?: TRaw;\n meta?: WebsiteExtractMeta;\n error?: WebsiteExtractorError;\n}\n\nexport interface WebsiteExtractorResult<TData, TRaw = TData>\n extends WebsiteExtractorState<TData, TRaw> {\n refresh: () => void;\n}\n\nexport type WebsiteExtractorResponse<TPayload> = WebsiteExtractMeta & TPayload;\n\nexport interface WebsiteExtractorRequest {\n endpoint: string;\n url: string;\n apiKey?: string;\n baseUrl?: string;\n signal?: AbortSignal;\n}\n\nexport type WebsiteExtractorClientResult<T> =\n | { ok: true; response: T }\n | { ok: false; error: WebsiteExtractorError };\n\nexport const DEFAULT_WEBSITE_EXTRACTOR_BASE_URL = \"https://octane.buzz\";\n\nexport const DEFAULT_EXTRACTOR_DEBOUNCE_MS = 250;\n\nexport const DEFAULT_EXTRACTOR_REFRESH_DEBOUNCE_MS = 150;\n\nexport const DEFAULT_EXTRACTOR_ENABLED = true;\n\nexport const DEFAULT_EXTRACTOR_CACHE = true;\n\nexport function extractWebsiteMeta(response: WebsiteExtractMeta): WebsiteExtractMeta {\n const {\n requestedUrl,\n finalUrl,\n url,\n normalizedUrl,\n status,\n contentType,\n fetchedAt,\n bodyBytes,\n bodyTruncated,\n maxBodyBytes,\n cache,\n } = response;\n\n return {\n requestedUrl,\n finalUrl,\n url,\n normalizedUrl,\n status,\n contentType,\n fetchedAt,\n bodyBytes,\n bodyTruncated,\n maxBodyBytes,\n cache,\n };\n}\n\nexport function stripWebsiteMeta<TResponse extends WebsiteExtractMeta>(\n response: TResponse\n): Omit<TResponse, keyof WebsiteExtractMeta> {\n const {\n requestedUrl: _requestedUrl,\n finalUrl: _finalUrl,\n url: _url,\n normalizedUrl: _normalizedUrl,\n status: _status,\n contentType: _contentType,\n fetchedAt: _fetchedAt,\n bodyBytes: _bodyBytes,\n bodyTruncated: _bodyTruncated,\n maxBodyBytes: _maxBodyBytes,\n cache: _cache,\n ...payload\n } = response;\n\n return payload;\n}\n","import {\n DEFAULT_WEBSITE_EXTRACTOR_BASE_URL,\n type WebsiteExtractorClientResult,\n type WebsiteExtractorError,\n type WebsiteExtractorRequest,\n} from \"./websiteExtractorTypes.js\";\n\nconst EXTRACTOR_PATH = \"/api/v1/extract\";\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, \"\");\n}\n\nexport function buildWebsiteExtractorUrl(request: WebsiteExtractorRequest): string {\n const baseUrl = normalizeBaseUrl(\n request.baseUrl ?? DEFAULT_WEBSITE_EXTRACTOR_BASE_URL\n );\n const params = new URLSearchParams();\n\n if (request.apiKey) {\n params.set(\"api_key\", request.apiKey);\n }\n\n params.set(\"url\", request.url);\n\n return `${baseUrl}${EXTRACTOR_PATH}/${request.endpoint}?${params.toString()}`;\n}\n\nexport async function fetchWebsiteExtractor<TResponse>(\n request: WebsiteExtractorRequest\n): Promise<WebsiteExtractorClientResult<TResponse>> {\n if (!request.url || request.url.trim().length === 0) {\n return {\n ok: false,\n error: {\n message: \"URL is required.\",\n },\n };\n }\n\n const endpoint = buildWebsiteExtractorUrl(request);\n\n try {\n const response = await fetch(endpoint, {\n method: \"GET\",\n signal: request.signal,\n });\n\n let payload: unknown = null;\n try {\n payload = await response.json();\n } catch {\n payload = null;\n }\n\n if (!response.ok) {\n const errorPayload = payload as { error?: string; status?: number } | null;\n const error: WebsiteExtractorError = {\n message:\n errorPayload?.error ??\n `Request failed with status ${response.status}.`,\n status: errorPayload?.status ?? response.status,\n raw: payload,\n };\n\n return { ok: false, error };\n }\n\n return { ok: true, response: payload as TResponse };\n } catch (error) {\n if (request.signal?.aborted) {\n return {\n ok: false,\n error: {\n message: \"Request aborted.\",\n },\n };\n }\n\n return {\n ok: false,\n error: {\n message:\n error instanceof Error ? error.message : \"Request failed unexpectedly.\",\n raw: error,\n },\n };\n }\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useDebounceCallback } from \"./useDebounceCallback.js\";\nimport { useDebounceValue } from \"./useDebounceValue.js\";\nimport { useIsClient } from \"./useIsClient.js\";\nimport { useMap } from \"./useMap.js\";\nimport { fetchWebsiteExtractor } from \"./websiteExtractorService.js\";\nimport {\n DEFAULT_EXTRACTOR_CACHE,\n DEFAULT_EXTRACTOR_DEBOUNCE_MS,\n DEFAULT_EXTRACTOR_ENABLED,\n DEFAULT_EXTRACTOR_REFRESH_DEBOUNCE_MS,\n DEFAULT_WEBSITE_EXTRACTOR_BASE_URL,\n extractWebsiteMeta,\n stripWebsiteMeta,\n type WebsiteExtractorOptions,\n type WebsiteExtractorResult,\n type WebsiteExtractorState,\n type WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\ninterface CachedExtractorEntry<TData, TRaw extends WebsiteExtractMeta> {\n data: TData;\n raw: TRaw;\n meta: WebsiteExtractMeta;\n}\n\ninterface UseWebsiteExtractorBaseConfig<\n TResponse extends WebsiteExtractMeta,\n TData,\n> {\n endpoint: string;\n options: WebsiteExtractorOptions;\n selectData: (\n payload: Omit<TResponse, keyof WebsiteExtractMeta>,\n raw: TResponse,\n meta: WebsiteExtractMeta\n ) => TData;\n shouldSkip?: (url: string) => boolean;\n}\n\nexport function useWebsiteExtractorBase<\n TResponse extends WebsiteExtractMeta,\n TData,\n>(config: UseWebsiteExtractorBaseConfig<TResponse, TData>): WebsiteExtractorResult<\n TData,\n TResponse\n> {\n const { endpoint, options, selectData, shouldSkip } = config;\n const isClient = useIsClient();\n const [state, setState] = useState<WebsiteExtractorState<TData, TResponse>>({\n loading: false,\n });\n\n const [, cacheActions] = useMap<\n string,\n CachedExtractorEntry<TData, TResponse>\n >();\n const cacheEnabled = options.cache ?? DEFAULT_EXTRACTOR_CACHE;\n const enabled = options.enabled ?? DEFAULT_EXTRACTOR_ENABLED;\n const debounceMs = options.debounceMs ?? DEFAULT_EXTRACTOR_DEBOUNCE_MS;\n const refreshDebounceMs =\n options.refreshDebounceMs ?? DEFAULT_EXTRACTOR_REFRESH_DEBOUNCE_MS;\n\n const normalizedUrl = useMemo(() => {\n return options.url?.trim() ?? \"\";\n }, [options.url]);\n\n const debouncedUrl = useDebounceValue(normalizedUrl, debounceMs);\n\n const refreshCounterRef = useRef(0);\n const lastRefreshHandledRef = useRef(0);\n const [refreshToken, setRefreshToken] = useState(0);\n\n const { debouncedCallback: scheduleRefresh, cancel: cancelRefresh } =\n useDebounceCallback(() => {\n refreshCounterRef.current += 1;\n setRefreshToken(refreshCounterRef.current);\n }, refreshDebounceMs);\n\n const refresh = useCallback(() => {\n scheduleRefresh();\n }, [scheduleRefresh]);\n\n useEffect(() => () => cancelRefresh(), [cancelRefresh]);\n\n const requestKey = useMemo(() => {\n if (!debouncedUrl) {\n return \"\";\n }\n\n const baseUrl = options.baseUrl ?? DEFAULT_WEBSITE_EXTRACTOR_BASE_URL;\n const apiKey = options.apiKey ?? \"\";\n return `${endpoint}:${baseUrl}:${apiKey}:${debouncedUrl}`;\n }, [\n endpoint,\n options.apiKey,\n options.baseUrl,\n debouncedUrl,\n ]);\n\n const inFlightControllerRef = useRef<AbortController | null>(null);\n\n useEffect(() => {\n if (!isClient) {\n return;\n }\n\n if (!enabled || !debouncedUrl) {\n setState({ loading: false });\n return;\n }\n\n if (shouldSkip?.(debouncedUrl)) {\n setState({ loading: false });\n return;\n }\n\n const forceRefresh = refreshToken !== lastRefreshHandledRef.current;\n if (forceRefresh) {\n lastRefreshHandledRef.current = refreshToken;\n }\n\n if (cacheEnabled && !forceRefresh) {\n const cached = cacheActions.get(requestKey);\n if (cached) {\n setState({\n loading: false,\n data: cached.data,\n raw: cached.raw,\n meta: cached.meta,\n });\n return;\n }\n }\n\n inFlightControllerRef.current?.abort();\n const controller = new AbortController();\n inFlightControllerRef.current = controller;\n\n setState((prev) => ({\n ...prev,\n loading: true,\n error: undefined,\n }));\n\n fetchWebsiteExtractor<TResponse>({\n endpoint,\n url: debouncedUrl,\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n signal: controller.signal,\n })\n .then((result) => {\n if (controller.signal.aborted) {\n return;\n }\n\n if (!result.ok) {\n setState((prev) => ({\n ...prev,\n loading: false,\n error: result.error,\n }));\n return;\n }\n\n const raw = result.response;\n const meta = extractWebsiteMeta(raw);\n const payload = stripWebsiteMeta(raw);\n const data = selectData(payload, raw, meta);\n\n if (cacheEnabled) {\n cacheActions.set(requestKey, { data, raw, meta });\n }\n\n setState({\n loading: false,\n data,\n raw,\n meta,\n });\n })\n .catch((error) => {\n if (controller.signal.aborted) {\n return;\n }\n\n setState((prev) => ({\n ...prev,\n loading: false,\n error: {\n message:\n error instanceof Error\n ? error.message\n : \"Request failed unexpectedly.\",\n raw: error,\n },\n }));\n });\n\n return () => {\n controller.abort();\n };\n }, [\n cacheActions,\n cacheEnabled,\n debouncedUrl,\n enabled,\n endpoint,\n isClient,\n options.apiKey,\n options.baseUrl,\n requestKey,\n refreshToken,\n selectData,\n shouldSkip,\n ]);\n\n return useMemo(\n () => ({\n ...state,\n refresh,\n }),\n [refresh, state]\n );\n}\n","import { useCallback, useMemo } from \"react\";\nimport { useWebsiteExtractorBase } from \"./useWebsiteExtractorBase.js\";\nimport type {\n WebsiteExtractorOptions,\n WebsiteExtractorResponse,\n WebsiteExtractorResult,\n WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\nexport interface OpenGraphImage {\n url?: string | null;\n height?: string | null;\n width?: string | null;\n}\n\nexport interface OpenGraphVideo {\n url?: string | null;\n height?: string | null;\n width?: string | null;\n}\n\nexport interface OpenGraphData {\n description?: string | null;\n title?: string | null;\n site_name?: string | null;\n image?: OpenGraphImage | null;\n video?: OpenGraphVideo | null;\n url?: string | null;\n ogType?: string | null;\n}\n\nexport interface OpenGraphHtmlInferredData {\n description?: string | null;\n title?: string | null;\n type?: string | null;\n videoType?: string | null;\n url?: string | null;\n favicon?: string | null;\n images?: string[] | null;\n image?: string | null;\n site_name?: string | null;\n}\n\nexport interface OpenGraphHybridData {\n description?: string | null;\n title?: string | null;\n type?: string | null;\n image?: string | null;\n video?: string | null;\n videoType?: string | null;\n favicon?: string | null;\n site_name?: string | null;\n url?: string | null;\n videoWidth?: number | null;\n videoHeight?: number | null;\n}\n\nexport type OpenGraphResponse = WebsiteExtractorResponse<{\n openGraph: OpenGraphData;\n htmlInferred: OpenGraphHtmlInferredData;\n hybridGraph: OpenGraphHybridData;\n}>;\n\nexport interface OpenGraphSummary {\n description?: string;\n favicon?: string;\n image?: string;\n video?: string;\n videoType?: string;\n siteName?: string;\n title?: string;\n url: string;\n siteHost?: string;\n}\n\nexport interface OpenGraphExtractorOptions extends WebsiteExtractorOptions {\n skipPatterns?: RegExp[];\n}\n\nconst DEFAULT_SKIP_PATTERNS = [\n /search\\.google\\.com\\/local\\/reviews/i,\n /google\\.com\\/maps\\/place/i,\n /maps\\.google\\.com/i,\n /opentable\\.com/i,\n];\n\nconst pickFirstString = (\n ...values: Array<string | null | undefined>\n): string | undefined => {\n for (const value of values) {\n if (typeof value === \"string\" && value.trim().length > 0) {\n return value;\n }\n }\n return undefined;\n};\n\nconst safeHost = (value: string | undefined): string | undefined => {\n if (!value) {\n return undefined;\n }\n\n try {\n return new URL(value).hostname;\n } catch {\n return undefined;\n }\n};\n\nexport function useOpenGraphExtractor(\n options: OpenGraphExtractorOptions\n): WebsiteExtractorResult<OpenGraphSummary, OpenGraphResponse> {\n const skipPatterns = useMemo(\n () => options.skipPatterns ?? DEFAULT_SKIP_PATTERNS,\n [options.skipPatterns]\n );\n\n const shouldSkip = useCallback(\n (url: string) => skipPatterns.some((pattern) => pattern.test(url)),\n [skipPatterns]\n );\n\n const selectData = useCallback(\n (\n payload: Omit<OpenGraphResponse, keyof WebsiteExtractMeta>,\n _raw: OpenGraphResponse,\n meta: WebsiteExtractMeta\n ): OpenGraphSummary => {\n const { openGraph, htmlInferred, hybridGraph } = payload;\n\n const description = pickFirstString(\n openGraph?.description,\n hybridGraph?.description,\n htmlInferred?.description\n );\n const title = pickFirstString(\n openGraph?.title,\n hybridGraph?.title,\n htmlInferred?.title\n );\n const siteName = pickFirstString(\n openGraph?.site_name,\n hybridGraph?.site_name,\n htmlInferred?.site_name\n );\n const favicon = pickFirstString(\n hybridGraph?.favicon,\n htmlInferred?.favicon\n );\n const image = pickFirstString(\n openGraph?.image?.url ?? undefined,\n hybridGraph?.image ?? undefined,\n htmlInferred?.image ?? undefined,\n htmlInferred?.images?.[0]\n );\n const video = pickFirstString(\n openGraph?.video?.url ?? undefined,\n hybridGraph?.video ?? undefined\n );\n const videoType = pickFirstString(\n hybridGraph?.videoType,\n htmlInferred?.videoType\n );\n const resolvedUrl =\n pickFirstString(\n meta.url,\n openGraph?.url ?? undefined,\n hybridGraph?.url ?? undefined,\n htmlInferred?.url ?? undefined,\n meta.finalUrl,\n meta.normalizedUrl,\n meta.requestedUrl\n ) ?? \"\";\n\n return {\n description,\n favicon,\n image,\n video,\n videoType,\n siteName,\n title,\n url: resolvedUrl,\n siteHost: safeHost(resolvedUrl),\n };\n },\n []\n );\n\n return useWebsiteExtractorBase<OpenGraphResponse, OpenGraphSummary>({\n endpoint: \"open-graph\",\n options,\n selectData,\n shouldSkip,\n });\n}\n","import { useCallback, useMemo, useState } from \"react\";\n\nexport interface UseBooleanResult {\n value: boolean;\n setValue: React.Dispatch<React.SetStateAction<boolean>>;\n setTrue: () => void;\n setFalse: () => void;\n toggle: () => void;\n}\n\nexport function useBoolean(defaultValue = false): UseBooleanResult {\n const [value, setValue] = useState<boolean>(defaultValue);\n\n const setTrue = useCallback(() => setValue(true), []);\n const setFalse = useCallback(() => setValue(false), []);\n const toggle = useCallback(() => setValue((current) => !current), []);\n\n // Memoize the return object to prevent unnecessary re-renders in consumers\n // that use the result object in dependency arrays\n return useMemo(\n () => ({ value, setValue, setTrue, setFalse, toggle }),\n [value, setValue, setTrue, setFalse, toggle]\n );\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nexport interface UseCopyToClipboardOptions {\n resetDelay?: number;\n}\n\nexport interface CopyToClipboardResult {\n copy: (text: string) => Promise<boolean>;\n copiedText: string | null;\n isSupported: boolean;\n}\n\nexport function useCopyToClipboard(\n options: UseCopyToClipboardOptions = {}\n): CopyToClipboardResult {\n const resetDelay = options.resetDelay ?? 2000;\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const [copiedText, setCopiedText] = useState<string | null>(null);\n\n const isSupported = useMemo(() => {\n if (typeof navigator !== \"undefined\" && navigator.clipboard) {\n return true;\n }\n if (typeof document === \"undefined\") {\n return false;\n }\n if (typeof document.queryCommandSupported !== \"function\") {\n return false;\n }\n return document.queryCommandSupported(\"copy\");\n }, []);\n\n const resetCopied = useCallback(() => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n timeoutRef.current = setTimeout(() => {\n setCopiedText(null);\n }, resetDelay);\n }, [resetDelay]);\n\n const copy = useCallback(\n async (text: string) => {\n if (!isSupported) {\n return false;\n }\n\n const shouldUseClipboardApi =\n typeof navigator !== \"undefined\" && !!navigator.clipboard;\n\n try {\n if (shouldUseClipboardApi) {\n await navigator.clipboard.writeText(text);\n } else if (typeof document !== \"undefined\") {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.setAttribute(\"readonly\", \"\");\n textarea.style.position = \"fixed\";\n textarea.style.left = \"-9999px\";\n textarea.style.top = \"0\";\n document.body.appendChild(textarea);\n textarea.focus();\n textarea.select();\n const success = document.execCommand(\"copy\");\n document.body.removeChild(textarea);\n if (!success) {\n return false;\n }\n }\n\n setCopiedText(text);\n resetCopied();\n return true;\n } catch {\n return false;\n }\n },\n [isSupported, resetCopied]\n );\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return { copy, copiedText, isSupported };\n}\n","import { useCallback, useState } from \"react\";\nimport { useEventListener } from \"./useEventListener.js\";\n\nexport function useHover<T extends HTMLElement>(\n ref: React.RefObject<T>\n): boolean {\n const [isHovered, setIsHovered] = useState(false);\n\n const handleEnter = useCallback(() => {\n setIsHovered(true);\n }, []);\n\n const handleLeave = useCallback(() => {\n setIsHovered(false);\n }, []);\n\n useEventListener(\"pointerenter\", handleEnter, ref);\n useEventListener(\"pointerleave\", handleLeave, ref);\n\n return isHovered;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface StorageOptions<T> {\n initializeWithValue?: boolean;\n serialize?: (value: T) => string;\n deserialize?: (value: string) => T;\n listenToStorageChanges?: boolean;\n}\n\ntype StoredSetter<T> = (value: T | ((current: T) => T)) => void;\n\nexport function useLocalStorage<T>(\n key: string,\n initialValue: T,\n options: StorageOptions<T> = {}\n): [T, StoredSetter<T>] {\n const {\n initializeWithValue = true,\n serialize = JSON.stringify,\n deserialize = JSON.parse as (value: string) => T,\n listenToStorageChanges = true,\n } = options;\n\n const initialValueRef = useRef(initialValue);\n\n const readValue = useCallback(() => {\n if (typeof window === \"undefined\") {\n return initialValueRef.current;\n }\n if (!initializeWithValue) {\n return initialValueRef.current;\n }\n try {\n const item = window.localStorage.getItem(key);\n return item ? deserialize(item) : initialValueRef.current;\n } catch {\n return initialValueRef.current;\n }\n }, [deserialize, initializeWithValue, key]);\n\n const [storedValue, setStoredValue] = useState<T>(() => readValue());\n\n const setValue: StoredSetter<T> = useCallback(\n (value) => {\n setStoredValue((current) => {\n const valueToStore =\n typeof value === \"function\"\n ? (value as (current: T) => T)(current)\n : value;\n if (typeof window !== \"undefined\") {\n try {\n window.localStorage.setItem(key, serialize(valueToStore));\n } catch {\n // Ignore write errors (quota/security)\n }\n }\n return valueToStore;\n });\n },\n [key, serialize]\n );\n\n useEffect(() => {\n setStoredValue(readValue());\n }, [readValue]);\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !listenToStorageChanges) {\n return;\n }\n\n const handleStorageChange = (event: StorageEvent) => {\n if (event.key !== key) {\n return;\n }\n if (event.newValue === null) {\n setStoredValue(initialValueRef.current);\n return;\n }\n try {\n setStoredValue(deserialize(event.newValue));\n } catch {\n setStoredValue(initialValueRef.current);\n }\n };\n\n window.addEventListener(\"storage\", handleStorageChange);\n return () => window.removeEventListener(\"storage\", handleStorageChange);\n }, [deserialize, key, listenToStorageChanges]);\n\n return [storedValue, setValue];\n}\n","import { useEffect, useState } from \"react\";\n\nexport interface UseMediaQueryOptions {\n defaultValue?: boolean;\n}\n\nexport function useMediaQuery(\n query: string,\n options: UseMediaQueryOptions = {}\n): boolean {\n const [matches, setMatches] = useState<boolean>(() => {\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") {\n return options.defaultValue ?? false;\n }\n return window.matchMedia(query).matches;\n });\n\n useEffect(() => {\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") {\n return;\n }\n\n const mediaQueryList = window.matchMedia(query);\n const handler = (event: MediaQueryListEvent) => {\n setMatches(event.matches);\n };\n\n setMatches(mediaQueryList.matches);\n\n if (mediaQueryList.addEventListener) {\n mediaQueryList.addEventListener(\"change\", handler);\n return () => mediaQueryList.removeEventListener(\"change\", handler);\n }\n\n mediaQueryList.addListener(handler);\n return () => mediaQueryList.removeListener(handler);\n }, [query]);\n\n return matches;\n}\n","import { useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\nexport function usePrevious<T>(value: T): T | undefined {\n const ref = useRef<T>();\n\n // Use useIsomorphicLayoutEffect to capture the previous value synchronously\n // BEFORE paint. This ensures that during render, ref.current holds the actual\n // previous value (from the last render), not the current value.\n // Using useEffect would update AFTER paint, making comparisons incorrect.\n useIsomorphicLayoutEffect(() => {\n ref.current = value;\n }, [value]);\n\n return ref.current;\n}\n","import { useEffect, useRef, useState } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\ntype TargetElement<T extends Element> = React.RefObject<T> | T | null;\n\nconst isRefObject = <T extends Element>(\n value: unknown\n): value is React.RefObject<T> => !!value && typeof value === \"object\" && \"current\" in value;\n\nexport function useResizeObserver<T extends Element>(\n target: TargetElement<T>,\n onResize?: (entry: ResizeObserverEntry) => void,\n options?: ResizeObserverOptions\n): ResizeObserverEntry | null {\n const callbackRef = useRef(onResize);\n const entryRef = useRef<ResizeObserverEntry | null>(null);\n const [entry, setEntry] = useState<ResizeObserverEntry | null>(null);\n\n useIsomorphicLayoutEffect(() => {\n callbackRef.current = onResize;\n }, [onResize]);\n\n useEffect(() => {\n if (typeof ResizeObserver === \"undefined\") {\n return;\n }\n\n const element =\n typeof Element !== \"undefined\" && target instanceof Element\n ? target\n : isRefObject<T>(target)\n ? target.current\n : null;\n if (!element) {\n return;\n }\n\n const observer = new ResizeObserver((entries) => {\n const firstEntry = entries[0];\n entryRef.current = firstEntry;\n if (callbackRef.current) {\n callbackRef.current(firstEntry);\n } else {\n setEntry(firstEntry);\n }\n });\n\n observer.observe(element, options);\n return () => observer.disconnect();\n }, [options, target]);\n\n return callbackRef.current ? entryRef.current : entry;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface SessionStorageOptions<T> {\n initializeWithValue?: boolean;\n serialize?: (value: T) => string;\n deserialize?: (value: string) => T;\n listenToStorageChanges?: boolean;\n}\n\ntype StoredSetter<T> = (value: T | ((current: T) => T)) => void;\n\nexport function useSessionStorage<T>(\n key: string,\n initialValue: T,\n options: SessionStorageOptions<T> = {}\n): [T, StoredSetter<T>] {\n const {\n initializeWithValue = true,\n serialize = JSON.stringify,\n deserialize = JSON.parse as (value: string) => T,\n listenToStorageChanges = false,\n } = options;\n\n const initialValueRef = useRef(initialValue);\n\n const readValue = useCallback(() => {\n if (typeof window === \"undefined\") {\n return initialValueRef.current;\n }\n if (!initializeWithValue) {\n return initialValueRef.current;\n }\n try {\n const item = window.sessionStorage.getItem(key);\n return item ? deserialize(item) : initialValueRef.current;\n } catch {\n return initialValueRef.current;\n }\n }, [deserialize, initializeWithValue, key]);\n\n const [storedValue, setStoredValue] = useState<T>(() => readValue());\n\n const setValue: StoredSetter<T> = useCallback(\n (value) => {\n setStoredValue((current) => {\n const valueToStore =\n typeof value === \"function\"\n ? (value as (current: T) => T)(current)\n : value;\n if (typeof window !== \"undefined\") {\n try {\n window.sessionStorage.setItem(key, serialize(valueToStore));\n } catch {\n // Ignore write errors (quota/security)\n }\n }\n return valueToStore;\n });\n },\n [key, serialize]\n );\n\n useEffect(() => {\n setStoredValue(readValue());\n }, [readValue]);\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !listenToStorageChanges) {\n return;\n }\n\n const handleStorageChange = (event: StorageEvent) => {\n if (event.key !== key) {\n return;\n }\n if (event.newValue === null) {\n setStoredValue(initialValueRef.current);\n return;\n }\n try {\n setStoredValue(deserialize(event.newValue));\n } catch {\n setStoredValue(initialValueRef.current);\n }\n };\n\n window.addEventListener(\"storage\", handleStorageChange);\n return () => window.removeEventListener(\"storage\", handleStorageChange);\n }, [deserialize, key, listenToStorageChanges]);\n\n return [storedValue, setValue];\n}\n","import { useEffect, useRef, useState } from \"react\";\n\nexport interface ThrottleOptions {\n leading?: boolean;\n trailing?: boolean;\n}\n\nexport function useThrottle<T>(\n value: T,\n interval: number,\n options: ThrottleOptions = {}\n): T {\n const leading = options.leading ?? true;\n const trailing = options.trailing ?? true;\n const wait = Math.max(0, interval);\n\n const [throttledValue, setThrottledValue] = useState<T>(value);\n const lastExecutedRef = useRef<number>(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingValueRef = useRef<T | null>(null);\n\n useEffect(() => {\n if (wait === 0) {\n setThrottledValue(value);\n return;\n }\n\n const now = Date.now();\n\n if (lastExecutedRef.current === 0) {\n lastExecutedRef.current = now;\n if (leading) {\n setThrottledValue(value);\n return;\n }\n if (trailing && !timeoutRef.current) {\n pendingValueRef.current = value;\n timeoutRef.current = setTimeout(() => {\n timeoutRef.current = null;\n if (pendingValueRef.current !== null) {\n setThrottledValue(pendingValueRef.current);\n pendingValueRef.current = null;\n lastExecutedRef.current = Date.now();\n }\n }, wait);\n }\n return;\n }\n\n const elapsed = now - lastExecutedRef.current;\n\n if (elapsed >= wait && leading) {\n setThrottledValue(value);\n lastExecutedRef.current = now;\n pendingValueRef.current = null;\n return;\n }\n\n if (trailing) {\n pendingValueRef.current = value;\n if (!timeoutRef.current) {\n const remaining = Math.max(wait - elapsed, 0);\n timeoutRef.current = setTimeout(() => {\n timeoutRef.current = null;\n if (pendingValueRef.current !== null) {\n setThrottledValue(pendingValueRef.current);\n pendingValueRef.current = null;\n lastExecutedRef.current = Date.now();\n }\n }, remaining);\n }\n }\n }, [leading, trailing, value, wait]);\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return throttledValue;\n}\n","import { useCallback } from \"react\";\nimport { useWebsiteExtractorBase } from \"./useWebsiteExtractorBase.js\";\nimport type {\n WebsiteExtractorOptions,\n WebsiteExtractorResponse,\n WebsiteExtractorResult,\n WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\nexport interface WebsiteLinkRecord {\n url: string;\n text: string;\n isExternal: boolean;\n domain?: string | null;\n}\n\nexport interface WebsiteLinksPayload {\n totalLinks: number;\n uniqueDomains: number;\n links: WebsiteLinkRecord[];\n}\n\nexport type WebsiteLinksResponse = WebsiteExtractorResponse<WebsiteLinksPayload>;\n\nexport function useWebsiteLinksExtractor(\n options: WebsiteExtractorOptions\n): WebsiteExtractorResult<WebsiteLinksPayload, WebsiteLinksResponse> {\n const selectData = useCallback(\n (\n payload: Omit<WebsiteLinksResponse, keyof WebsiteExtractMeta>\n ): WebsiteLinksPayload => {\n return {\n totalLinks: payload.totalLinks ?? 0,\n uniqueDomains: payload.uniqueDomains ?? 0,\n links: payload.links ?? [],\n };\n },\n []\n );\n\n return useWebsiteExtractorBase<WebsiteLinksResponse, WebsiteLinksPayload>({\n endpoint: \"links\",\n options,\n selectData,\n });\n}\n","import { useCallback } from \"react\";\nimport { useWebsiteExtractorBase } from \"./useWebsiteExtractorBase.js\";\nimport type {\n WebsiteExtractorOptions,\n WebsiteExtractorResponse,\n WebsiteExtractorResult,\n WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\nexport interface WebsiteMetaPayload {\n title?: string | null;\n description?: string | null;\n language?: string | null;\n canonicalUrl?: string | null;\n feedUrl?: string | null;\n textContentLength?: number | null;\n metaTags: Record<string, string>;\n}\n\nexport type WebsiteMetaResponse = WebsiteExtractorResponse<WebsiteMetaPayload>;\n\nexport function useWebsiteMetaExtractor(\n options: WebsiteExtractorOptions\n): WebsiteExtractorResult<WebsiteMetaPayload, WebsiteMetaResponse> {\n const selectData = useCallback(\n (\n payload: Omit<WebsiteMetaResponse, keyof WebsiteExtractMeta>\n ): WebsiteMetaPayload => {\n return {\n title: payload.title ?? undefined,\n description: payload.description ?? undefined,\n language: payload.language ?? undefined,\n canonicalUrl: payload.canonicalUrl ?? undefined,\n feedUrl: payload.feedUrl ?? null,\n textContentLength: payload.textContentLength ?? undefined,\n metaTags: payload.metaTags ?? {},\n };\n },\n []\n );\n\n return useWebsiteExtractorBase<WebsiteMetaResponse, WebsiteMetaPayload>({\n endpoint: \"meta\",\n options,\n selectData,\n });\n}\n","import { useCallback } from \"react\";\nimport { useWebsiteExtractorBase } from \"./useWebsiteExtractorBase.js\";\nimport type {\n WebsiteExtractorOptions,\n WebsiteExtractorResponse,\n WebsiteExtractorResult,\n WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\nexport interface WebsiteRssFeed {\n url: string;\n feedType: string;\n title?: string | null;\n}\n\nexport interface WebsiteRssPayload {\n feedUrl?: string | null;\n feeds: WebsiteRssFeed[];\n}\n\nexport type WebsiteRssResponse = WebsiteExtractorResponse<WebsiteRssPayload>;\n\nexport function useWebsiteRssExtractor(\n options: WebsiteExtractorOptions\n): WebsiteExtractorResult<WebsiteRssPayload, WebsiteRssResponse> {\n const selectData = useCallback(\n (\n payload: Omit<WebsiteRssResponse, keyof WebsiteExtractMeta>\n ): WebsiteRssPayload => {\n return {\n feedUrl: payload.feedUrl ?? null,\n feeds: payload.feeds ?? [],\n };\n },\n []\n );\n\n return useWebsiteExtractorBase<WebsiteRssResponse, WebsiteRssPayload>({\n endpoint: \"rss\",\n options,\n selectData,\n });\n}\n","import { useCallback } from \"react\";\nimport { useWebsiteExtractorBase } from \"./useWebsiteExtractorBase.js\";\nimport type {\n WebsiteExtractorOptions,\n WebsiteExtractorResponse,\n WebsiteExtractorResult,\n WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\nexport interface WebsiteSchemaRecord {\n schema_type: string;\n value: Record<string, unknown>;\n}\n\nexport interface WebsiteSchemaPayload {\n schema: WebsiteSchemaRecord[];\n schemaTypes: string[];\n}\n\nexport type WebsiteSchemaResponse = WebsiteExtractorResponse<WebsiteSchemaPayload>;\n\nexport function useWebsiteSchemaExtractor(\n options: WebsiteExtractorOptions\n): WebsiteExtractorResult<WebsiteSchemaPayload, WebsiteSchemaResponse> {\n const selectData = useCallback(\n (\n payload: Omit<WebsiteSchemaResponse, keyof WebsiteExtractMeta>\n ): WebsiteSchemaPayload => {\n return {\n schema: payload.schema ?? [],\n schemaTypes: payload.schemaTypes ?? [],\n };\n },\n []\n );\n\n return useWebsiteExtractorBase<WebsiteSchemaResponse, WebsiteSchemaPayload>({\n endpoint: \"schema\",\n options,\n selectData,\n });\n}\n"],"names":["useIsomorphicLayoutEffect","window","useLayoutEffect","useEffect","useDebounceCallback","callback","delay","options","callbackRef","useRef","timeoutRef","maxTimeoutRef","lastArgsRef","leading","trailing","maxWait","wait","Math","max","current","clearTimers","useCallback","clearTimeout","invoke","args","debouncedCallback","setTimeout","maxDelay","cancel","flush","useDebounceValue","value","debouncedValue","setDebouncedValue","useState","next","getOwnerDocument","element","ownerDocument","document","PLATFORM_HOSTNAME_MAP","Map","useEventListener","eventName","handler","savedHandler","isWindow","Window","isDocument","Document","target","HTMLElement","addEventListener","listener","event","currentHandler","handleEvent","removeEventListener","useIsClient","isClient","setIsClient","useMap","initialState","map","setMap","Array","isArray","mapRef","actions","useMemo","set","key","prev","setAll","entries","remove","delete","clear","get","has","DEFAULT_WEBSITE_EXTRACTOR_BASE_URL","buildWebsiteExtractorUrl","request","baseUrl","replace","normalizeBaseUrl","params","URLSearchParams","apiKey","url","endpoint","toString","async","fetchWebsiteExtractor","trim","length","ok","error","message","response","fetch","method","signal","payload","json","errorPayload","status","raw","_a","aborted","Error","useWebsiteExtractorBase","config","selectData","shouldSkip","state","setState","loading","cacheActions","cacheEnabled","cache","enabled","debounceMs","refreshDebounceMs","debouncedUrl","refreshCounterRef","lastRefreshHandledRef","refreshToken","setRefreshToken","scheduleRefresh","cancelRefresh","refresh","requestKey","inFlightControllerRef","forceRefresh","cached","data","meta","abort","controller","AbortController","then","result","requestedUrl","finalUrl","normalizedUrl","contentType","fetchedAt","bodyBytes","bodyTruncated","maxBodyBytes","extractWebsiteMeta","_requestedUrl","_finalUrl","_url","_normalizedUrl","_status","_contentType","_fetchedAt","_bodyBytes","_bodyTruncated","_maxBodyBytes","_cache","stripWebsiteMeta","catch","DEFAULT_SKIP_PATTERNS","pickFirstString","values","safeHost","URL","hostname","defaultValue","setValue","setTrue","setFalse","toggle","resetDelay","copiedText","setCopiedText","isSupported","navigator","clipboard","queryCommandSupported","resetCopied","copy","text","shouldUseClipboardApi","writeText","textarea","createElement","setAttribute","style","position","left","top","body","appendChild","focus","select","success","execCommand","removeChild","ref","isHovered","setIsHovered","handleEnter","handleLeave","initialValue","initializeWithValue","serialize","JSON","stringify","deserialize","parse","listenToStorageChanges","initialValueRef","readValue","item","localStorage","getItem","storedValue","setStoredValue","valueToStore","setItem","handleStorageChange","newValue","query","matches","setMatches","matchMedia","mediaQueryList","addListener","removeListener","eventType","handlerRef","supportsPointerEvents","PointerEvent","resolvedEventType","refs","targetDocument","currentRef","refDocument","Node","some","node","contains","skipPatterns","pattern","test","_raw","openGraph","htmlInferred","hybridGraph","description","title","siteName","site_name","favicon","image","_b","images","video","_c","videoType","resolvedUrl","siteHost","trimmedUrl","toLowerCase","platform","endsWith","includes","onResize","entryRef","entry","setEntry","ResizeObserver","Element","observer","firstEntry","observe","disconnect","sessionStorage","interval","throttledValue","setThrottledValue","lastExecutedRef","pendingValueRef","now","Date","elapsed","remaining","totalLinks","uniqueDomains","links","language","canonicalUrl","feedUrl","textContentLength","metaTags","feeds","schema","schemaTypes"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"opensite-hooks.umd.js","sources":["../../src/core/useIsomorphicLayoutEffect.ts","../../src/core/useDebounceCallback.ts","../../src/core/useDebounceValue.ts","../../src/core/useOnClickOutside.ts","../../src/core/useMediaQuery.ts","../../src/core/usePlatformFromUrl.ts","../../src/core/useEventListener.ts","../../src/core/useIsClient.ts","../../src/core/useMap.ts","../../src/core/websiteExtractorTypes.ts","../../src/core/websiteExtractorService.ts","../../src/core/useWebsiteExtractorBase.ts","../../src/core/useOpenGraphExtractor.ts","../../src/core/useIsTouchDevice.ts","../../src/core/useScreen.ts","../../src/core/useBoolean.ts","../../src/core/useCopyToClipboard.ts","../../src/core/useHover.ts","../../src/core/useLocalStorage.ts","../../src/core/usePrevious.ts","../../src/core/useResizeObserver.ts","../../src/core/useSessionStorage.ts","../../src/core/useThrottle.ts","../../src/core/useWebsiteLinksExtractor.ts","../../src/core/useWebsiteMetaExtractor.ts","../../src/core/useWebsiteRssExtractor.ts","../../src/core/useWebsiteSchemaExtractor.ts"],"sourcesContent":["import { useEffect, useLayoutEffect } from \"react\";\n\nexport const useIsomorphicLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n","import { useCallback, useEffect, useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\nexport interface DebounceOptions {\n leading?: boolean;\n trailing?: boolean;\n maxWait?: number;\n}\n\nexport interface DebouncedCallback<T extends (...args: any[]) => void> {\n debouncedCallback: (...args: Parameters<T>) => void;\n cancel: () => void;\n flush: () => void;\n}\n\nexport function useDebounceCallback<T extends (...args: any[]) => void>(\n callback: T,\n delay: number,\n options: DebounceOptions = {}\n): DebouncedCallback<T> {\n const callbackRef = useRef(callback);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const maxTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastArgsRef = useRef<Parameters<T> | null>(null);\n\n const leading = options.leading ?? false;\n const trailing = options.trailing ?? true;\n const maxWait = options.maxWait;\n const wait = Math.max(0, delay);\n\n useIsomorphicLayoutEffect(() => {\n callbackRef.current = callback;\n }, [callback]);\n\n const clearTimers = useCallback(() => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n if (maxTimeoutRef.current) {\n clearTimeout(maxTimeoutRef.current);\n maxTimeoutRef.current = null;\n }\n }, []);\n\n const invoke = useCallback(() => {\n if (!lastArgsRef.current) {\n return;\n }\n const args = lastArgsRef.current;\n lastArgsRef.current = null;\n callbackRef.current(...args);\n }, []);\n\n const debouncedCallback = useCallback(\n (...args: Parameters<T>) => {\n lastArgsRef.current = args;\n\n const shouldInvokeLeading =\n leading && timeoutRef.current === null && maxTimeoutRef.current === null;\n if (shouldInvokeLeading) {\n invoke();\n }\n\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n if (trailing) {\n timeoutRef.current = setTimeout(() => {\n timeoutRef.current = null;\n if (lastArgsRef.current) {\n invoke();\n }\n if (maxTimeoutRef.current) {\n clearTimeout(maxTimeoutRef.current);\n maxTimeoutRef.current = null;\n }\n }, wait);\n }\n\n if (maxWait !== undefined && maxWait !== null && trailing) {\n if (!maxTimeoutRef.current) {\n const maxDelay = Math.max(0, maxWait);\n maxTimeoutRef.current = setTimeout(() => {\n maxTimeoutRef.current = null;\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n if (lastArgsRef.current) {\n invoke();\n }\n }, maxDelay);\n }\n }\n },\n [invoke, leading, trailing, maxWait, wait]\n );\n\n const cancel = useCallback(() => {\n clearTimers();\n lastArgsRef.current = null;\n }, [clearTimers]);\n\n const flush = useCallback(() => {\n if (!lastArgsRef.current) {\n return;\n }\n clearTimers();\n invoke();\n }, [clearTimers, invoke]);\n\n useEffect(() => () => cancel(), [cancel]);\n\n return { debouncedCallback, cancel, flush };\n}\n","import { useEffect, useState } from \"react\";\nimport { DebounceOptions, useDebounceCallback } from \"./useDebounceCallback.js\";\n\nexport function useDebounceValue<T>(\n value: T,\n delay: number,\n options?: DebounceOptions\n): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n const { debouncedCallback, cancel } = useDebounceCallback(\n (next: T) => {\n setDebouncedValue(next);\n },\n delay,\n options\n );\n\n useEffect(() => {\n debouncedCallback(value);\n }, [debouncedCallback, value]);\n\n useEffect(() => () => cancel(), [cancel]);\n\n return debouncedValue;\n}\n","import { useEffect, useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\ntype PossibleRef<T extends HTMLElement> = React.RefObject<T>;\n\n// Debug mode - set to true to enable console logging\nconst DEBUG_CLICK_OUTSIDE = false;\n\n/**\n * Get the owner document for a given element.\n * This is crucial for iframe support - returns the iframe's document if the element is inside one.\n */\nfunction getOwnerDocument(element: HTMLElement | null): Document {\n return element?.ownerDocument ?? document;\n}\n\n/**\n * Detect if an element is inside an iframe by checking if its document is different from the top document\n */\nfunction isInIframe(element: HTMLElement | null): boolean {\n if (!element) return false;\n const doc = getOwnerDocument(element);\n return doc !== document;\n}\n\nexport function useOnClickOutside<T extends HTMLElement>(\n ref: PossibleRef<T> | Array<PossibleRef<T>>,\n handler: (event: MouseEvent | TouchEvent | PointerEvent) => void,\n eventType?: \"mousedown\" | \"mouseup\" | \"click\" | \"touchstart\" | \"pointerdown\",\n options?: AddEventListenerOptions | boolean\n): void {\n const handlerRef = useRef(handler);\n\n // Use useIsomorphicLayoutEffect to update handler ref synchronously\n // This prevents stale closures in rapid click scenarios where the event\n // listener (registered below) might fire before the ref is updated\n useIsomorphicLayoutEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") {\n return;\n }\n\n const supportsPointerEvents =\n typeof window !== \"undefined\" &&\n typeof window.PointerEvent !== \"undefined\";\n const resolvedEventType =\n eventType ?? (supportsPointerEvents ? \"pointerdown\" : \"mousedown\");\n\n const refs = Array.isArray(ref) ? ref : [ref];\n\n // Detect the correct document to attach listeners to\n // If any ref is inside an iframe, use that iframe's document\n let targetDocument: Document = document;\n let inIframe = false;\n\n for (const currentRef of refs) {\n if (currentRef.current) {\n const refDocument = getOwnerDocument(currentRef.current);\n if (refDocument !== document) {\n targetDocument = refDocument;\n inIframe = true;\n break;\n }\n }\n }\n\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Setup:\", {\n eventType: resolvedEventType,\n inIframe,\n documentLocation: inIframe ? \"iframe\" : \"parent\",\n refsCount: refs.length,\n refsWithNodes: refs.filter((r) => r.current).length,\n });\n }\n\n const listener = (event: MouseEvent | TouchEvent | PointerEvent) => {\n const target = event.target;\n\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Event fired:\", {\n eventType: event.type,\n targetTag: target instanceof Element ? target.tagName : \"unknown\",\n targetInIframe: target instanceof Node ? isInIframe(target as HTMLElement) : false,\n });\n }\n\n if (typeof Node === \"undefined\" || !(target instanceof Node)) {\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Early return: target not a Node\");\n }\n return;\n }\n\n const clickedInside = refs.some((currentRef) => {\n const node = currentRef.current;\n const contains = node ? node.contains(target) : false;\n\n if (DEBUG_CLICK_OUTSIDE && node) {\n console.log(\"[useOnClickOutside] Checking ref:\", {\n refTag: node.tagName,\n contains,\n nodeInIframe: isInIframe(node),\n });\n }\n\n return contains;\n });\n\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Click result:\", {\n clickedInside,\n willCallHandler: !clickedInside,\n });\n }\n\n if (!clickedInside) {\n handlerRef.current(event);\n }\n };\n\n targetDocument.addEventListener(resolvedEventType, listener, options);\n\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Listener attached to:\", {\n documentType: inIframe ? \"iframe document\" : \"parent document\",\n eventType: resolvedEventType,\n });\n }\n\n return () => {\n targetDocument.removeEventListener(resolvedEventType, listener, options);\n\n if (DEBUG_CLICK_OUTSIDE) {\n console.log(\"[useOnClickOutside] Listener removed from:\", {\n documentType: inIframe ? \"iframe document\" : \"parent document\",\n });\n }\n };\n }, [eventType, options, ref]);\n}\n","import { useEffect, useState } from \"react\";\n\nexport interface UseMediaQueryOptions {\n defaultValue?: boolean;\n}\n\nexport function useMediaQuery(\n query: string,\n options: UseMediaQueryOptions = {},\n): boolean {\n const [matches, setMatches] = useState<boolean>(() => {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return options.defaultValue ?? false;\n }\n return window.matchMedia(query).matches;\n });\n\n useEffect(() => {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return;\n }\n\n const mediaQueryList = window.matchMedia(query);\n const handler = (event: MediaQueryListEvent) => {\n setMatches(event.matches);\n };\n\n setMatches(mediaQueryList.matches);\n\n mediaQueryList.addEventListener(\"change\", handler);\n return () => mediaQueryList.removeEventListener(\"change\", handler);\n }, [query]);\n\n return matches;\n}\n","import { useMemo } from \"react\";\n\n/**\n * Supported social platform names\n */\nexport type SocialPlatformName =\n | \"instagram\"\n | \"linkedin\"\n | \"google\"\n | \"facebook\"\n | \"tiktok\"\n | \"youtube\"\n | \"yelp\"\n | \"spotify\"\n | \"apple\"\n | \"x\"\n | \"github\"\n | \"discord\"\n | \"snapchat\"\n | \"dev\"\n | \"substack\"\n | \"reddit\"\n | \"pinterest\"\n | \"threads\"\n | \"twitch\"\n | \"whatsapp\"\n | \"telegram\"\n | \"medium\"\n | \"patreon\"\n | \"onlyfans\"\n | \"eventbrite\"\n | \"npmjs\"\n | \"crates\"\n | \"rubygems\"\n | \"behance\"\n | \"dribbble\"\n | \"unknown\";\n\n/**\n * Platform hostname mappings for O(1) lookup performance.\n * Includes standard domains and known URL variants/shorteners.\n */\nconst PLATFORM_HOSTNAME_MAP = new Map<string, SocialPlatformName>([\n // Instagram\n [\"instagram.com\", \"instagram\"],\n [\"www.instagram.com\", \"instagram\"],\n [\"instagr.am\", \"instagram\"],\n [\"www.instagr.am\", \"instagram\"],\n\n // LinkedIn\n [\"linkedin.com\", \"linkedin\"],\n [\"www.linkedin.com\", \"linkedin\"],\n [\"ca.linkedin.com\", \"linkedin\"],\n [\"uk.linkedin.com\", \"linkedin\"],\n [\"in.linkedin.com\", \"linkedin\"],\n [\"lnkd.in\", \"linkedin\"],\n\n // Google (including Maps variants)\n [\"google.com\", \"google\"],\n [\"www.google.com\", \"google\"],\n [\"maps.google.com\", \"google\"],\n [\"goo.gl\", \"google\"],\n [\"maps.app.goo.gl\", \"google\"],\n [\"g.co\", \"google\"],\n\n // Facebook\n [\"facebook.com\", \"facebook\"],\n [\"www.facebook.com\", \"facebook\"],\n [\"m.facebook.com\", \"facebook\"],\n [\"fb.com\", \"facebook\"],\n [\"fb.me\", \"facebook\"],\n [\"on.fb.me\", \"facebook\"],\n\n // TikTok\n [\"tiktok.com\", \"tiktok\"],\n [\"www.tiktok.com\", \"tiktok\"],\n [\"m.tiktok.com\", \"tiktok\"],\n [\"vm.tiktok.com\", \"tiktok\"],\n [\"vt.tiktok.com\", \"tiktok\"],\n\n // YouTube\n [\"youtube.com\", \"youtube\"],\n [\"www.youtube.com\", \"youtube\"],\n [\"m.youtube.com\", \"youtube\"],\n [\"youtu.be\", \"youtube\"],\n\n // Yelp\n [\"yelp.com\", \"yelp\"],\n [\"www.yelp.com\", \"yelp\"],\n [\"m.yelp.com\", \"yelp\"],\n\n // Spotify\n [\"spotify.com\", \"spotify\"],\n [\"www.spotify.com\", \"spotify\"],\n [\"open.spotify.com\", \"spotify\"],\n [\"play.spotify.com\", \"spotify\"],\n [\"spoti.fi\", \"spotify\"],\n [\"spotify.link\", \"spotify\"],\n\n // Apple\n [\"apple.com\", \"apple\"],\n [\"www.apple.com\", \"apple\"],\n [\"music.apple.com\", \"apple\"],\n [\"podcasts.apple.com\", \"apple\"],\n [\"apps.apple.com\", \"apple\"],\n [\"itunes.apple.com\", \"apple\"],\n\n // X (formerly Twitter)\n [\"x.com\", \"x\"],\n [\"www.x.com\", \"x\"],\n [\"twitter.com\", \"x\"],\n [\"www.twitter.com\", \"x\"],\n [\"t.co\", \"x\"],\n\n // GitHub\n [\"github.com\", \"github\"],\n [\"www.github.com\", \"github\"],\n [\"gist.github.com\", \"github\"],\n [\"raw.githubusercontent.com\", \"github\"],\n [\"github.io\", \"github\"],\n\n // Discord\n [\"discord.com\", \"discord\"],\n [\"www.discord.com\", \"discord\"],\n [\"discord.gg\", \"discord\"],\n [\"discordapp.com\", \"discord\"],\n [\"discordapp.net\", \"discord\"],\n [\"discord.new\", \"discord\"],\n [\"discord.gift\", \"discord\"],\n [\"discord.gifts\", \"discord\"],\n [\"dis.gd\", \"discord\"],\n\n // Snapchat\n [\"snapchat.com\", \"snapchat\"],\n [\"www.snapchat.com\", \"snapchat\"],\n [\"snap.com\", \"snapchat\"],\n [\"www.snap.com\", \"snapchat\"],\n [\"story.snapchat.com\", \"snapchat\"],\n [\"web.snapchat.com\", \"snapchat\"],\n\n // Dev (Dev.to)\n [\"dev.to\", \"dev\"],\n [\"www.dev.to\", \"dev\"],\n\n // Substack\n [\"substack.com\", \"substack\"],\n [\"www.substack.com\", \"substack\"],\n\n // Reddit\n [\"reddit.com\", \"reddit\"],\n [\"www.reddit.com\", \"reddit\"],\n [\"old.reddit.com\", \"reddit\"],\n [\"new.reddit.com\", \"reddit\"],\n [\"i.redd.it\", \"reddit\"],\n [\"v.redd.it\", \"reddit\"],\n [\"redd.it\", \"reddit\"],\n [\"preview.redd.it\", \"reddit\"],\n\n // Pinterest\n [\"pinterest.com\", \"pinterest\"],\n [\"www.pinterest.com\", \"pinterest\"],\n [\"pin.it\", \"pinterest\"],\n [\"in.pinterest.com\", \"pinterest\"],\n [\"br.pinterest.com\", \"pinterest\"],\n [\"uk.pinterest.com\", \"pinterest\"],\n\n // Threads (Meta)\n [\"threads.net\", \"threads\"],\n [\"www.threads.net\", \"threads\"],\n [\"threads.com\", \"threads\"],\n [\"www.threads.com\", \"threads\"],\n\n // Twitch\n [\"twitch.tv\", \"twitch\"],\n [\"www.twitch.tv\", \"twitch\"],\n [\"m.twitch.tv\", \"twitch\"],\n\n // WhatsApp\n [\"whatsapp.com\", \"whatsapp\"],\n [\"www.whatsapp.com\", \"whatsapp\"],\n [\"wa.me\", \"whatsapp\"],\n [\"web.whatsapp.com\", \"whatsapp\"],\n\n // Telegram\n [\"telegram.org\", \"telegram\"],\n [\"www.telegram.org\", \"telegram\"],\n [\"t.me\", \"telegram\"],\n [\"telegram.me\", \"telegram\"],\n [\"telegram.dog\", \"telegram\"],\n\n // Medium\n [\"medium.com\", \"medium\"],\n [\"www.medium.com\", \"medium\"],\n // Note: Custom Medium domains would need additional logic\n\n // Patreon\n [\"patreon.com\", \"patreon\"],\n [\"www.patreon.com\", \"patreon\"],\n\n // OnlyFans\n [\"onlyfans.com\", \"onlyfans\"],\n [\"www.onlyfans.com\", \"onlyfans\"],\n\n // Eventbrite\n [\"eventbrite.com\", \"eventbrite\"],\n [\"www.eventbrite.com\", \"eventbrite\"],\n [\"eventbrite.co.uk\", \"eventbrite\"],\n [\"eventbrite.com.au\", \"eventbrite\"],\n [\"eventbrite.ca\", \"eventbrite\"],\n [\"eventbrite.de\", \"eventbrite\"],\n [\"eventbrite.fr\", \"eventbrite\"],\n [\"eventbrite.es\", \"eventbrite\"],\n [\"eventbrite.it\", \"eventbrite\"],\n [\"eventbrite.ie\", \"eventbrite\"],\n [\"eventbrite.nl\", \"eventbrite\"],\n [\"eventbrite.co.nz\", \"eventbrite\"],\n [\"eventbriteapi.com\", \"eventbrite\"],\n [\"evbuc.com\", \"eventbrite\"],\n\n // npm\n [\"npmjs.com\", \"npmjs\"],\n [\"www.npmjs.com\", \"npmjs\"],\n [\"npmjs.org\", \"npmjs\"],\n [\"www.npmjs.org\", \"npmjs\"],\n [\"registry.npmjs.org\", \"npmjs\"],\n [\"registry.npmjs.com\", \"npmjs\"],\n [\"replicate.npmjs.com\", \"npmjs\"],\n\n // Crates.io (Rust)\n [\"crates.io\", \"crates\"],\n [\"www.crates.io\", \"crates\"],\n\n // RubyGems\n [\"rubygems.org\", \"rubygems\"],\n [\"www.rubygems.org\", \"rubygems\"],\n\n // Behance\n [\"behance.net\", \"behance\"],\n [\"www.behance.net\", \"behance\"],\n // Optional: catch common subdomains if you see them in your data\n [\"portfolio.behance.net\", \"behance\"],\n [\"mir-s3-cdn-cf.behance.net\", \"behance\"],\n\n // Dribbble\n [\"dribbble.com\", \"dribbble\"],\n [\"www.dribbble.com\", \"dribbble\"],\n [\"drbl.in\", \"dribbble\"],\n]);\n\n/**\n * Extracts the social platform name from a URL string.\n * Uses the native URL API for validation and hostname extraction,\n * then performs O(1) Map lookup for platform identification.\n *\n * @param url - The URL string to analyze\n * @returns The identified platform name or \"unknown\" if not recognized\n *\n * @example\n * ```tsx\n * const platform = usePlatformFromUrl(\"https://www.youtube.com/@iamthedelo\");\n * // Returns: \"youtube\"\n *\n * const platform = usePlatformFromUrl(\"https://maps.app.goo.gl/XDuog3V5fTuPcWCH7\");\n * // Returns: \"google\"\n *\n * const platform = usePlatformFromUrl(\"https://twitter.com/jordanhudgens\");\n * // Returns: \"x\"\n *\n * const platform = usePlatformFromUrl(\"not-a-url\");\n * // Returns: \"unknown\"\n * ```\n */\nexport function usePlatformFromUrl(url: string): SocialPlatformName {\n return useMemo(() => {\n if (!url || typeof url !== \"string\") {\n return \"unknown\";\n }\n\n const trimmedUrl = url.trim();\n if (!trimmedUrl) {\n return \"unknown\";\n }\n\n try {\n const urlObj = new URL(trimmedUrl);\n const hostname = urlObj.hostname.toLowerCase();\n\n // O(1) Map lookup first (fastest)\n const platform = PLATFORM_HOSTNAME_MAP.get(hostname);\n\n if (platform) {\n return platform;\n }\n\n // Fallback pattern matching for custom domains (slower but more comprehensive)\n if (hostname.endsWith(\".substack.com\")) {\n return \"substack\";\n }\n if (hostname.endsWith(\".github.io\")) {\n return \"github\";\n }\n if (hostname.includes(\"pinterest.com\")) {\n return \"pinterest\";\n }\n if (hostname.includes(\"eventbrite.\")) {\n return \"eventbrite\";\n }\n if (hostname.endsWith(\".medium.com\")) {\n return \"medium\";\n }\n if (hostname.endsWith(\".behance.net\")) {\n return \"behance\";\n }\n\n return \"unknown\";\n } catch {\n return \"unknown\";\n }\n }, [url]);\n}\n","import { useEffect, useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\ntype EventTargetLike = Window | Document | HTMLElement | null;\ntype ElementRef = React.RefObject<HTMLElement>;\n\nconst isRefObject = (value: unknown): value is ElementRef =>\n !!value && typeof value === \"object\" && \"current\" in value;\n\nexport function useEventListener<K extends keyof WindowEventMap>(\n eventName: K,\n handler: (event: WindowEventMap[K]) => void,\n element?: Window,\n options?: AddEventListenerOptions | boolean\n): void;\nexport function useEventListener<K extends keyof DocumentEventMap>(\n eventName: K,\n handler: (event: DocumentEventMap[K]) => void,\n element: Document,\n options?: AddEventListenerOptions | boolean\n): void;\nexport function useEventListener<K extends keyof HTMLElementEventMap>(\n eventName: K,\n handler: (event: HTMLElementEventMap[K]) => void,\n element: ElementRef,\n options?: AddEventListenerOptions | boolean\n): void;\nexport function useEventListener<K extends keyof HTMLElementEventMap>(\n eventName: K,\n handler: (event: HTMLElementEventMap[K]) => void,\n element: HTMLElement,\n options?: AddEventListenerOptions | boolean\n): void;\nexport function useEventListener(\n eventName: string,\n handler: EventListenerOrEventListenerObject,\n element?: EventTargetLike | ElementRef,\n options?: AddEventListenerOptions | boolean\n): void {\n const savedHandler = useRef(handler);\n\n useIsomorphicLayoutEffect(() => {\n savedHandler.current = handler;\n }, [handler]);\n\n useEffect(() => {\n const isWindow =\n typeof Window !== \"undefined\" && element instanceof Window;\n const isDocument =\n typeof Document !== \"undefined\" && element instanceof Document;\n\n const target: EventTargetLike | null =\n element === undefined\n ? typeof window !== \"undefined\"\n ? window\n : null\n : isWindow || isDocument\n ? element\n : typeof HTMLElement !== \"undefined\" && element instanceof HTMLElement\n ? element\n : isRefObject(element)\n ? element.current\n : null;\n\n if (!target?.addEventListener) {\n return;\n }\n\n const listener = (event: Event) => {\n const currentHandler = savedHandler.current;\n if (typeof currentHandler === \"function\") {\n currentHandler(event);\n } else {\n currentHandler.handleEvent(event);\n }\n };\n\n target.addEventListener(eventName, listener, options);\n\n return () => {\n target.removeEventListener(eventName, listener, options);\n };\n }, [eventName, element, options]);\n}\n","import { useEffect, useState } from \"react\";\n\nexport function useIsClient(): boolean {\n const [isClient, setIsClient] = useState(false);\n\n useEffect(() => {\n setIsClient(true);\n }, []);\n\n return isClient;\n}\n","import { useMemo, useRef, useState } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\nexport interface MapActions<K, V> {\n set: (key: K, value: V) => void;\n setAll: (entries: Map<K, V> | [K, V][]) => void;\n remove: (key: K) => void;\n clear: () => void;\n get: (key: K) => V | undefined;\n has: (key: K) => boolean;\n}\n\nexport function useMap<K, V>(\n initialState?: Map<K, V> | [K, V][]\n): [Map<K, V>, MapActions<K, V>] {\n const [map, setMap] = useState<Map<K, V>>(() => {\n if (initialState instanceof Map) {\n return new Map(initialState);\n }\n if (Array.isArray(initialState)) {\n return new Map(initialState);\n }\n return new Map();\n });\n\n const mapRef = useRef(map);\n\n // Use useIsomorphicLayoutEffect to update mapRef synchronously with state\n // This ensures get() and has() methods always read the current map state\n // without a timing window where they could return stale values\n useIsomorphicLayoutEffect(() => {\n mapRef.current = map;\n }, [map]);\n\n const actions = useMemo<MapActions<K, V>>(\n () => ({\n set: (key: K, value: V) => {\n setMap((prev) => {\n const next = new Map(prev);\n next.set(key, value);\n return next;\n });\n },\n setAll: (entries: Map<K, V> | [K, V][]) => {\n setMap(entries instanceof Map ? new Map(entries) : new Map(entries));\n },\n remove: (key: K) => {\n setMap((prev) => {\n const next = new Map(prev);\n next.delete(key);\n return next;\n });\n },\n clear: () => setMap(new Map()),\n get: (key: K) => mapRef.current.get(key),\n has: (key: K) => mapRef.current.has(key),\n }),\n []\n );\n\n return [map, actions];\n}\n","export interface WebsiteExtractCacheMeta {\n hit: boolean;\n ageSeconds: number;\n ttlSeconds: number;\n staleWhileRevalidateSeconds: number;\n}\n\nexport interface WebsiteExtractMeta {\n requestedUrl: string;\n finalUrl: string;\n url: string;\n normalizedUrl: string;\n status: number;\n contentType: string;\n fetchedAt: string;\n bodyBytes: number;\n bodyTruncated: boolean;\n maxBodyBytes: number;\n cache: WebsiteExtractCacheMeta;\n}\n\nexport interface WebsiteExtractorError {\n message: string;\n status?: number;\n raw?: unknown;\n}\n\nexport interface WebsiteExtractorOptions {\n url?: string;\n apiKey?: string;\n baseUrl?: string;\n debounceMs?: number;\n refreshDebounceMs?: number;\n enabled?: boolean;\n cache?: boolean;\n}\n\nexport interface WebsiteExtractorState<TData, TRaw = TData> {\n loading: boolean;\n data?: TData;\n raw?: TRaw;\n meta?: WebsiteExtractMeta;\n error?: WebsiteExtractorError;\n}\n\nexport interface WebsiteExtractorResult<TData, TRaw = TData>\n extends WebsiteExtractorState<TData, TRaw> {\n refresh: () => void;\n}\n\nexport type WebsiteExtractorResponse<TPayload> = WebsiteExtractMeta & TPayload;\n\nexport interface WebsiteExtractorRequest {\n endpoint: string;\n url: string;\n apiKey?: string;\n baseUrl?: string;\n signal?: AbortSignal;\n}\n\nexport type WebsiteExtractorClientResult<T> =\n | { ok: true; response: T }\n | { ok: false; error: WebsiteExtractorError };\n\nexport const DEFAULT_WEBSITE_EXTRACTOR_BASE_URL = \"https://octane.buzz\";\n\nexport const DEFAULT_EXTRACTOR_DEBOUNCE_MS = 250;\n\nexport const DEFAULT_EXTRACTOR_REFRESH_DEBOUNCE_MS = 150;\n\nexport const DEFAULT_EXTRACTOR_ENABLED = true;\n\nexport const DEFAULT_EXTRACTOR_CACHE = true;\n\nexport function extractWebsiteMeta(response: WebsiteExtractMeta): WebsiteExtractMeta {\n const {\n requestedUrl,\n finalUrl,\n url,\n normalizedUrl,\n status,\n contentType,\n fetchedAt,\n bodyBytes,\n bodyTruncated,\n maxBodyBytes,\n cache,\n } = response;\n\n return {\n requestedUrl,\n finalUrl,\n url,\n normalizedUrl,\n status,\n contentType,\n fetchedAt,\n bodyBytes,\n bodyTruncated,\n maxBodyBytes,\n cache,\n };\n}\n\nexport function stripWebsiteMeta<TResponse extends WebsiteExtractMeta>(\n response: TResponse\n): Omit<TResponse, keyof WebsiteExtractMeta> {\n const {\n requestedUrl: _requestedUrl,\n finalUrl: _finalUrl,\n url: _url,\n normalizedUrl: _normalizedUrl,\n status: _status,\n contentType: _contentType,\n fetchedAt: _fetchedAt,\n bodyBytes: _bodyBytes,\n bodyTruncated: _bodyTruncated,\n maxBodyBytes: _maxBodyBytes,\n cache: _cache,\n ...payload\n } = response;\n\n return payload;\n}\n","import {\n DEFAULT_WEBSITE_EXTRACTOR_BASE_URL,\n type WebsiteExtractorClientResult,\n type WebsiteExtractorError,\n type WebsiteExtractorRequest,\n} from \"./websiteExtractorTypes.js\";\n\nconst EXTRACTOR_PATH = \"/api/v1/extract\";\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, \"\");\n}\n\nexport function buildWebsiteExtractorUrl(request: WebsiteExtractorRequest): string {\n const baseUrl = normalizeBaseUrl(\n request.baseUrl ?? DEFAULT_WEBSITE_EXTRACTOR_BASE_URL\n );\n const params = new URLSearchParams();\n\n if (request.apiKey) {\n params.set(\"api_key\", request.apiKey);\n }\n\n params.set(\"url\", request.url);\n\n return `${baseUrl}${EXTRACTOR_PATH}/${request.endpoint}?${params.toString()}`;\n}\n\nexport async function fetchWebsiteExtractor<TResponse>(\n request: WebsiteExtractorRequest\n): Promise<WebsiteExtractorClientResult<TResponse>> {\n if (!request.url || request.url.trim().length === 0) {\n return {\n ok: false,\n error: {\n message: \"URL is required.\",\n },\n };\n }\n\n const endpoint = buildWebsiteExtractorUrl(request);\n\n try {\n const response = await fetch(endpoint, {\n method: \"GET\",\n signal: request.signal,\n });\n\n let payload: unknown = null;\n try {\n payload = await response.json();\n } catch {\n payload = null;\n }\n\n if (!response.ok) {\n const errorPayload = payload as { error?: string; status?: number } | null;\n const error: WebsiteExtractorError = {\n message:\n errorPayload?.error ??\n `Request failed with status ${response.status}.`,\n status: errorPayload?.status ?? response.status,\n raw: payload,\n };\n\n return { ok: false, error };\n }\n\n return { ok: true, response: payload as TResponse };\n } catch (error) {\n if (request.signal?.aborted) {\n return {\n ok: false,\n error: {\n message: \"Request aborted.\",\n },\n };\n }\n\n return {\n ok: false,\n error: {\n message:\n error instanceof Error ? error.message : \"Request failed unexpectedly.\",\n raw: error,\n },\n };\n }\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useDebounceCallback } from \"./useDebounceCallback.js\";\nimport { useDebounceValue } from \"./useDebounceValue.js\";\nimport { useIsClient } from \"./useIsClient.js\";\nimport { useMap } from \"./useMap.js\";\nimport { fetchWebsiteExtractor } from \"./websiteExtractorService.js\";\nimport {\n DEFAULT_EXTRACTOR_CACHE,\n DEFAULT_EXTRACTOR_DEBOUNCE_MS,\n DEFAULT_EXTRACTOR_ENABLED,\n DEFAULT_EXTRACTOR_REFRESH_DEBOUNCE_MS,\n DEFAULT_WEBSITE_EXTRACTOR_BASE_URL,\n extractWebsiteMeta,\n stripWebsiteMeta,\n type WebsiteExtractorOptions,\n type WebsiteExtractorResult,\n type WebsiteExtractorState,\n type WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\ninterface CachedExtractorEntry<TData, TRaw extends WebsiteExtractMeta> {\n data: TData;\n raw: TRaw;\n meta: WebsiteExtractMeta;\n}\n\ninterface UseWebsiteExtractorBaseConfig<\n TResponse extends WebsiteExtractMeta,\n TData,\n> {\n endpoint: string;\n options: WebsiteExtractorOptions;\n selectData: (\n payload: Omit<TResponse, keyof WebsiteExtractMeta>,\n raw: TResponse,\n meta: WebsiteExtractMeta\n ) => TData;\n shouldSkip?: (url: string) => boolean;\n}\n\nexport function useWebsiteExtractorBase<\n TResponse extends WebsiteExtractMeta,\n TData,\n>(config: UseWebsiteExtractorBaseConfig<TResponse, TData>): WebsiteExtractorResult<\n TData,\n TResponse\n> {\n const { endpoint, options, selectData, shouldSkip } = config;\n const isClient = useIsClient();\n const [state, setState] = useState<WebsiteExtractorState<TData, TResponse>>({\n loading: false,\n });\n\n const [, cacheActions] = useMap<\n string,\n CachedExtractorEntry<TData, TResponse>\n >();\n const cacheEnabled = options.cache ?? DEFAULT_EXTRACTOR_CACHE;\n const enabled = options.enabled ?? DEFAULT_EXTRACTOR_ENABLED;\n const debounceMs = options.debounceMs ?? DEFAULT_EXTRACTOR_DEBOUNCE_MS;\n const refreshDebounceMs =\n options.refreshDebounceMs ?? DEFAULT_EXTRACTOR_REFRESH_DEBOUNCE_MS;\n\n const normalizedUrl = useMemo(() => {\n return options.url?.trim() ?? \"\";\n }, [options.url]);\n\n const debouncedUrl = useDebounceValue(normalizedUrl, debounceMs);\n\n const refreshCounterRef = useRef(0);\n const lastRefreshHandledRef = useRef(0);\n const [refreshToken, setRefreshToken] = useState(0);\n\n const { debouncedCallback: scheduleRefresh, cancel: cancelRefresh } =\n useDebounceCallback(() => {\n refreshCounterRef.current += 1;\n setRefreshToken(refreshCounterRef.current);\n }, refreshDebounceMs);\n\n const refresh = useCallback(() => {\n scheduleRefresh();\n }, [scheduleRefresh]);\n\n useEffect(() => () => cancelRefresh(), [cancelRefresh]);\n\n const requestKey = useMemo(() => {\n if (!debouncedUrl) {\n return \"\";\n }\n\n const baseUrl = options.baseUrl ?? DEFAULT_WEBSITE_EXTRACTOR_BASE_URL;\n const apiKey = options.apiKey ?? \"\";\n return `${endpoint}:${baseUrl}:${apiKey}:${debouncedUrl}`;\n }, [\n endpoint,\n options.apiKey,\n options.baseUrl,\n debouncedUrl,\n ]);\n\n const inFlightControllerRef = useRef<AbortController | null>(null);\n\n useEffect(() => {\n if (!isClient) {\n return;\n }\n\n if (!enabled || !debouncedUrl) {\n setState({ loading: false });\n return;\n }\n\n if (shouldSkip?.(debouncedUrl)) {\n setState({ loading: false });\n return;\n }\n\n const forceRefresh = refreshToken !== lastRefreshHandledRef.current;\n if (forceRefresh) {\n lastRefreshHandledRef.current = refreshToken;\n }\n\n if (cacheEnabled && !forceRefresh) {\n const cached = cacheActions.get(requestKey);\n if (cached) {\n setState({\n loading: false,\n data: cached.data,\n raw: cached.raw,\n meta: cached.meta,\n });\n return;\n }\n }\n\n inFlightControllerRef.current?.abort();\n const controller = new AbortController();\n inFlightControllerRef.current = controller;\n\n setState((prev) => ({\n ...prev,\n loading: true,\n error: undefined,\n }));\n\n fetchWebsiteExtractor<TResponse>({\n endpoint,\n url: debouncedUrl,\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n signal: controller.signal,\n })\n .then((result) => {\n if (controller.signal.aborted) {\n return;\n }\n\n if (!result.ok) {\n setState((prev) => ({\n ...prev,\n loading: false,\n error: result.error,\n }));\n return;\n }\n\n const raw = result.response;\n const meta = extractWebsiteMeta(raw);\n const payload = stripWebsiteMeta(raw);\n const data = selectData(payload, raw, meta);\n\n if (cacheEnabled) {\n cacheActions.set(requestKey, { data, raw, meta });\n }\n\n setState({\n loading: false,\n data,\n raw,\n meta,\n });\n })\n .catch((error) => {\n if (controller.signal.aborted) {\n return;\n }\n\n setState((prev) => ({\n ...prev,\n loading: false,\n error: {\n message:\n error instanceof Error\n ? error.message\n : \"Request failed unexpectedly.\",\n raw: error,\n },\n }));\n });\n\n return () => {\n controller.abort();\n };\n }, [\n cacheActions,\n cacheEnabled,\n debouncedUrl,\n enabled,\n endpoint,\n isClient,\n options.apiKey,\n options.baseUrl,\n requestKey,\n refreshToken,\n selectData,\n shouldSkip,\n ]);\n\n return useMemo(\n () => ({\n ...state,\n refresh,\n }),\n [refresh, state]\n );\n}\n","import { useCallback, useMemo } from \"react\";\nimport { useWebsiteExtractorBase } from \"./useWebsiteExtractorBase.js\";\nimport type {\n WebsiteExtractorOptions,\n WebsiteExtractorResponse,\n WebsiteExtractorResult,\n WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\nexport interface OpenGraphImage {\n url?: string | null;\n height?: string | null;\n width?: string | null;\n}\n\nexport interface OpenGraphVideo {\n url?: string | null;\n height?: string | null;\n width?: string | null;\n}\n\nexport interface OpenGraphData {\n description?: string | null;\n title?: string | null;\n site_name?: string | null;\n image?: OpenGraphImage | null;\n video?: OpenGraphVideo | null;\n url?: string | null;\n ogType?: string | null;\n}\n\nexport interface OpenGraphHtmlInferredData {\n description?: string | null;\n title?: string | null;\n type?: string | null;\n videoType?: string | null;\n url?: string | null;\n favicon?: string | null;\n images?: string[] | null;\n image?: string | null;\n site_name?: string | null;\n}\n\nexport interface OpenGraphHybridData {\n description?: string | null;\n title?: string | null;\n type?: string | null;\n image?: string | null;\n video?: string | null;\n videoType?: string | null;\n favicon?: string | null;\n site_name?: string | null;\n url?: string | null;\n videoWidth?: number | null;\n videoHeight?: number | null;\n}\n\nexport type OpenGraphResponse = WebsiteExtractorResponse<{\n openGraph: OpenGraphData;\n htmlInferred: OpenGraphHtmlInferredData;\n hybridGraph: OpenGraphHybridData;\n}>;\n\nexport interface OpenGraphSummary {\n description?: string;\n favicon?: string;\n image?: string;\n video?: string;\n videoType?: string;\n siteName?: string;\n title?: string;\n url: string;\n siteHost?: string;\n}\n\nexport interface OpenGraphExtractorOptions extends WebsiteExtractorOptions {\n skipPatterns?: RegExp[];\n}\n\nconst DEFAULT_SKIP_PATTERNS = [\n /search\\.google\\.com\\/local\\/reviews/i,\n /google\\.com\\/maps\\/place/i,\n /maps\\.google\\.com/i,\n /opentable\\.com/i,\n];\n\nconst pickFirstString = (\n ...values: Array<string | null | undefined>\n): string | undefined => {\n for (const value of values) {\n if (typeof value === \"string\" && value.trim().length > 0) {\n return value;\n }\n }\n return undefined;\n};\n\nconst safeHost = (value: string | undefined): string | undefined => {\n if (!value) {\n return undefined;\n }\n\n try {\n return new URL(value).hostname;\n } catch {\n return undefined;\n }\n};\n\nexport function useOpenGraphExtractor(\n options: OpenGraphExtractorOptions\n): WebsiteExtractorResult<OpenGraphSummary, OpenGraphResponse> {\n const skipPatterns = useMemo(\n () => options.skipPatterns ?? DEFAULT_SKIP_PATTERNS,\n [options.skipPatterns]\n );\n\n const shouldSkip = useCallback(\n (url: string) => skipPatterns.some((pattern) => pattern.test(url)),\n [skipPatterns]\n );\n\n const selectData = useCallback(\n (\n payload: Omit<OpenGraphResponse, keyof WebsiteExtractMeta>,\n _raw: OpenGraphResponse,\n meta: WebsiteExtractMeta\n ): OpenGraphSummary => {\n const { openGraph, htmlInferred, hybridGraph } = payload;\n\n const description = pickFirstString(\n openGraph?.description,\n hybridGraph?.description,\n htmlInferred?.description\n );\n const title = pickFirstString(\n openGraph?.title,\n hybridGraph?.title,\n htmlInferred?.title\n );\n const siteName = pickFirstString(\n openGraph?.site_name,\n hybridGraph?.site_name,\n htmlInferred?.site_name\n );\n const favicon = pickFirstString(\n hybridGraph?.favicon,\n htmlInferred?.favicon\n );\n const image = pickFirstString(\n openGraph?.image?.url ?? undefined,\n hybridGraph?.image ?? undefined,\n htmlInferred?.image ?? undefined,\n htmlInferred?.images?.[0]\n );\n const video = pickFirstString(\n openGraph?.video?.url ?? undefined,\n hybridGraph?.video ?? undefined\n );\n const videoType = pickFirstString(\n hybridGraph?.videoType,\n htmlInferred?.videoType\n );\n const resolvedUrl =\n pickFirstString(\n meta.url,\n openGraph?.url ?? undefined,\n hybridGraph?.url ?? undefined,\n htmlInferred?.url ?? undefined,\n meta.finalUrl,\n meta.normalizedUrl,\n meta.requestedUrl\n ) ?? \"\";\n\n return {\n description,\n favicon,\n image,\n video,\n videoType,\n siteName,\n title,\n url: resolvedUrl,\n siteHost: safeHost(resolvedUrl),\n };\n },\n []\n );\n\n return useWebsiteExtractorBase<OpenGraphResponse, OpenGraphSummary>({\n endpoint: \"open-graph\",\n options,\n selectData,\n shouldSkip,\n });\n}\n","import { useCallback, useEffect, useMemo, useState } from \"react\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Granular classification of the detected primary input device.\n *\n * - `\"unknown\"` – returned during SSR and before the first client-side\n * evaluation completes. Consumers should treat this as \"not yet determined\"\n * and render a safe, non-committal default (e.g. show both touch and pointer\n * affordances, or a loading skeleton).\n * - `\"touch\"` – the primary input is a coarse pointer with no hover capability\n * (phones, most tablets in touch mode).\n * - `\"desktop\"` – the primary input is a fine pointer with hover support\n * (mouse, trackpad, stylus with hover).\n */\nexport type DeviceType = \"unknown\" | \"touch\" | \"desktop\";\n\n/**\n * Configuration options for {@link useIsTouchDevice}.\n */\nexport interface UseIsTouchDeviceOptions {\n /**\n * Value returned during SSR and before the first client-side detection.\n *\n * Defaults to `\"unknown\"`. Override with `\"touch\"` or `\"desktop\"` when you\n * have server-side hints (e.g. User-Agent parsing via middleware) and want to\n * avoid a visible layout shift after hydration.\n */\n defaultDeviceType?: DeviceType;\n}\n\n/**\n * Shape returned by {@link useIsTouchDevice}.\n *\n * The object reference is memoized with `useMemo` so it remains referentially\n * stable across re-renders when `deviceType` has not changed — safe to include\n * in downstream dependency arrays without triggering spurious effects.\n */\nexport interface UseIsTouchDeviceResult {\n /** Granular device classification (`\"unknown\"` | `\"touch\"` | `\"desktop\"`). */\n deviceType: DeviceType;\n\n /** Convenience boolean — `true` when `deviceType === \"touch\"`. */\n isTouchDevice: boolean;\n\n /**\n * Re-run touch detection imperatively.\n *\n * Useful after external events that the hook cannot observe automatically\n * (e.g. a WebHID device connect, or a custom \"mode switch\" UI toggle).\n * The callback reference is stable across the lifetime of the hook.\n */\n recheck: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Detection internals\n// ---------------------------------------------------------------------------\n\n/**\n * CSS media query targeting devices whose *primary* pointing device is coarse\n * and lacks hover — the W3C Interaction Media Features (Level 4) definition of\n * a touch-first device.\n *\n * Evaluated via `matchMedia` and subscribed to with a `change` listener so\n * convertible laptops, detachable tablets, and foldables react to input-mode\n * switches in real time.\n *\n * @see https://www.w3.org/TR/mediaqueries-4/#mf-interaction\n */\nconst TOUCH_MEDIA_QUERY = \"(hover: none) and (pointer: coarse)\";\n\n/**\n * Synchronously evaluate touch capability using a two-tier strategy.\n *\n * **Tier 1 — Interaction Media Features** (`pointer: coarse` + `hover: none`).\n * Most accurate on modern browsers (Chrome 41+, Firefox 64+, Safari 9+) and\n * correctly classifies hybrid devices by inspecting only the *primary* input.\n *\n * **Tier 2 — Legacy touch-API probe** (`ontouchstart` on window,\n * `navigator.maxTouchPoints`). Covers older Android WebView and pre-Chromium\n * Edge. This probe can produce false positives on some desktop touchscreens,\n * but it is only reached when Tier 1 is inconclusive.\n *\n * The function must only be called in a browser context — callers are\n * responsible for guarding with `typeof window !== \"undefined\"`.\n */\nfunction detectTouchCapability(): DeviceType {\n // Tier 1: Interaction Media Features (wide support since ~2018).\n if (typeof window.matchMedia === \"function\") {\n const mql = window.matchMedia(TOUCH_MEDIA_QUERY);\n\n // `mql.media` is set to `\"not all\"` when the browser does not understand\n // the query at all — in that case we fall through to the legacy probe.\n if (mql.media !== \"not all\") {\n return mql.matches ? \"touch\" : \"desktop\";\n }\n }\n\n // Tier 2: legacy touch-API probe.\n if (\n \"ontouchstart\" in window ||\n (typeof navigator !== \"undefined\" &&\n typeof navigator.maxTouchPoints === \"number\" &&\n navigator.maxTouchPoints > 0)\n ) {\n return \"touch\";\n }\n\n return \"desktop\";\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Detect whether the device's primary input mechanism is touch-based.\n *\n * Uses a layered detection strategy combining CSS Interaction Media Features\n * (`pointer: coarse`, `hover: none`) with legacy touch-API probing\n * (`ontouchstart`, `maxTouchPoints`) for maximum browser coverage.\n *\n * ### SSR safety\n *\n * The hook returns `\"unknown\"` (or a caller-supplied `defaultDeviceType`) on\n * the server **and** during the first client render, so the server and client\n * always produce identical markup — no hydration mismatch. After the commit\n * phase, a `useEffect` evaluates synchronously and subscribes to `matchMedia`\n * change events.\n *\n * ### Dynamic updates\n *\n * On browsers that support Interaction Media Features, the hook listens for\n * `change` events on the `(hover: none) and (pointer: coarse)` query. This\n * means convertible laptops, detachable tablets, and foldables will\n * automatically re-classify when the user switches input modes — no polling\n * required.\n *\n * ### Performance\n *\n * - Detection runs once on mount (one `matchMedia` call) — no per-render work.\n * - The `change` listener is passive and only fires on actual input-mode\n * transitions (rare).\n * - The returned object is memoized; `deviceType` must change for the\n * reference to update.\n * - The `recheck` callback is `useCallback`-stable for the hook's lifetime.\n *\n * @param options - Optional configuration. See {@link UseIsTouchDeviceOptions}.\n * @returns A memoized {@link UseIsTouchDeviceResult} object.\n *\n * @example\n * ```tsx\n * import { useIsTouchDevice } from \"@opensite/hooks/useIsTouchDevice\";\n *\n * function Toolbar() {\n * const { isTouchDevice } = useIsTouchDevice();\n * return isTouchDevice ? <TouchToolbar /> : <DesktopToolbar />;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With a server-side hint to eliminate layout shift\n * function Page({ ssrDeviceType }: { ssrDeviceType: DeviceType }) {\n * const { deviceType } = useIsTouchDevice({\n * defaultDeviceType: ssrDeviceType,\n * });\n * // ...\n * }\n * ```\n */\nexport function useIsTouchDevice(\n options: UseIsTouchDeviceOptions = {},\n): UseIsTouchDeviceResult {\n const { defaultDeviceType = \"unknown\" } = options;\n\n // SSR-safe initial state — never accesses browser APIs at initialization.\n const [deviceType, setDeviceType] = useState<DeviceType>(defaultDeviceType);\n\n // Stable imperative re-evaluation callback. Guarded so it is a safe no-op\n // if accidentally called during SSR (e.g. in a shared util).\n const recheck = useCallback(() => {\n if (typeof window === \"undefined\") return;\n setDeviceType(detectTouchCapability());\n }, []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n // Synchronous first evaluation — updates state in the same commit so\n // consumers see the real value on the very first paint after hydration.\n setDeviceType(detectTouchCapability());\n\n // Subscribe to media-query changes so hybrid/convertible devices update\n // when the user switches between touch and pointer input modes.\n if (typeof window.matchMedia !== \"function\") return;\n\n const mql = window.matchMedia(TOUCH_MEDIA_QUERY);\n\n // If the browser doesn't understand the query there's nothing to listen\n // for — the legacy probe result from above is our final answer.\n if (mql.media === \"not all\") return;\n\n const onChange = (event: MediaQueryListEvent) => {\n setDeviceType(event.matches ? \"touch\" : \"desktop\");\n };\n\n mql.addEventListener(\"change\", onChange);\n\n return () => {\n mql.removeEventListener(\"change\", onChange);\n };\n }, []);\n\n // Memoize the return object so consumers that destructure or pass the whole\n // result into dependency arrays don't trigger spurious re-renders.\n return useMemo<UseIsTouchDeviceResult>(\n () => ({\n deviceType,\n isTouchDevice: deviceType === \"touch\",\n recheck,\n }),\n [deviceType, recheck],\n );\n}\n","import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useMediaQuery } from \"./useMediaQuery.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Tailwind CSS breakpoint identifiers.\n *\n * - `\"default\"` – viewport width below the smallest defined breakpoint (< 640px)\n * - `\"sm\"` – small devices (>= 640px)\n * - `\"md\"` – medium devices (>= 768px)\n * - `\"lg\"` – large devices (>= 1024px)\n * - `\"xl\"` – extra large devices (>= 1280px)\n * - `\"2xl\"` – 2x extra large devices (>= 1536px)\n */\nexport type TailwindSize = \"default\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\";\n\n/**\n * Semantic screen type classification for layout decisions.\n *\n * - `\"MOBILE\"` – phone-sized viewports (default + sm breakpoints)\n * - `\"TABLET\"` – tablet-sized viewports (md breakpoint)\n * - `\"DESKTOP\"` – desktop-sized viewports (lg, xl, 2xl breakpoints)\n * - `\"UNKNOWN\"` – returned during SSR before client-side detection\n */\nexport type ScreenType = \"UNKNOWN\" | \"MOBILE\" | \"TABLET\" | \"DESKTOP\";\n\n/**\n * Breakpoint configuration with pixel thresholds.\n *\n * Values represent the minimum width (in pixels) for each breakpoint.\n * Follows Tailwind CSS v4 defaults.\n */\nexport interface ScreenBreakpoints {\n /** Minimum width for `sm` breakpoint. Default: 640 */\n sm: number;\n /** Minimum width for `md` breakpoint. Default: 768 */\n md: number;\n /** Minimum width for `lg` breakpoint. Default: 1024 */\n lg: number;\n /** Minimum width for `xl` breakpoint. Default: 1280 */\n xl: number;\n /** Minimum width for `2xl` breakpoint. Default: 1536 */\n \"2xl\": number;\n}\n\n/**\n * Mapping of Tailwind breakpoints to semantic screen types.\n */\nexport interface ScreenTypeMapping {\n /** Screen type for default (< sm) viewport. Default: \"MOBILE\" */\n default: ScreenType;\n /** Screen type for sm viewport. Default: \"MOBILE\" */\n sm: ScreenType;\n /** Screen type for md viewport. Default: \"TABLET\" */\n md: ScreenType;\n /** Screen type for lg viewport. Default: \"DESKTOP\" */\n lg: ScreenType;\n /** Screen type for xl viewport. Default: \"DESKTOP\" */\n xl: ScreenType;\n /** Screen type for 2xl viewport. Default: \"DESKTOP\" */\n \"2xl\": ScreenType;\n}\n\n/**\n * Configuration options for {@link useScreen}.\n */\nexport interface UseScreenOptions {\n /**\n * Custom breakpoint pixel thresholds.\n * Partial object that merges with Tailwind v4 defaults.\n */\n breakpoints?: Partial<ScreenBreakpoints>;\n\n /**\n * Custom mapping of breakpoints to screen types.\n * Partial object that merges with defaults.\n */\n screenTypeMapping?: Partial<ScreenTypeMapping>;\n\n /**\n * Default screen type returned during SSR and before detection.\n * Defaults to `\"UNKNOWN\"`.\n */\n defaultScreenType?: ScreenType;\n\n /**\n * Default Tailwind size returned during SSR and before detection.\n * Defaults to `\"default\"`.\n */\n defaultTailwindSize?: TailwindSize;\n}\n\n/**\n * Shape returned by {@link useScreen}.\n *\n * The object reference is memoized with `useMemo` so it remains referentially\n * stable across re-renders when values have not changed.\n */\nexport interface UseScreenResult {\n /** Current viewport width in pixels. `0` during SSR. */\n width: number;\n\n /** Current viewport height in pixels. `0` during SSR. */\n height: number;\n\n /**\n * Current Tailwind CSS breakpoint identifier.\n * Determined by the largest matching `min-width` breakpoint.\n */\n tailwindSize: TailwindSize;\n\n /**\n * Semantic screen type for layout decisions.\n * Maps `tailwindSize` to a simplified classification.\n */\n screenType: ScreenType;\n\n /** Re-measure viewport dimensions on demand. */\n refresh: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/**\n * Tailwind CSS v4 default breakpoints (min-width values in pixels).\n * @see https://tailwindcss.com/docs/responsive-design\n */\nconst DEFAULT_BREAKPOINTS: ScreenBreakpoints = {\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n \"2xl\": 1536,\n};\n\n/**\n * Default mapping of Tailwind breakpoints to semantic screen types.\n *\n * - default, sm → MOBILE\n * - md → TABLET\n * - lg, xl, 2xl → DESKTOP\n */\nconst DEFAULT_SCREEN_TYPE_MAPPING: ScreenTypeMapping = {\n default: \"MOBILE\",\n sm: \"MOBILE\",\n md: \"TABLET\",\n lg: \"DESKTOP\",\n xl: \"DESKTOP\",\n \"2xl\": \"DESKTOP\",\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get current viewport dimensions.\n * Returns `{ width: 0, height: 0 }` during SSR.\n */\nfunction getViewportDimensions(): { width: number; height: number } {\n if (typeof window === \"undefined\") {\n return { width: 0, height: 0 };\n }\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n };\n}\n\n/**\n * Determine the current Tailwind breakpoint based on viewport width.\n * Uses mobile-first logic: returns the largest breakpoint that matches.\n */\nfunction calculateTailwindSize(\n width: number,\n breakpoints: ScreenBreakpoints,\n): TailwindSize {\n // Check breakpoints from largest to smallest (mobile-first)\n if (width >= breakpoints[\"2xl\"]) return \"2xl\";\n if (width >= breakpoints.xl) return \"xl\";\n if (width >= breakpoints.lg) return \"lg\";\n if (width >= breakpoints.md) return \"md\";\n if (width >= breakpoints.sm) return \"sm\";\n return \"default\";\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Track viewport dimensions and compute Tailwind breakpoint / screen type.\n *\n * Provides real-time viewport width and height, along with derived values for\n * the current Tailwind CSS breakpoint (`tailwindSize`) and a semantic screen\n * type classification (`screenType`) for layout decisions.\n *\n * ### SSR Safety\n *\n * The hook returns safe defaults during SSR (`width: 0`, `height: 0`,\n * `tailwindSize: \"default\"`, `screenType: \"UNKNOWN\"`) to prevent hydration\n * mismatches. After mount, values update to reflect the actual viewport.\n *\n * ### Performance\n *\n * - Uses `useMediaQuery` internally for efficient breakpoint detection via\n * CSS media queries (no polling).\n * - Viewport dimensions update on window `resize` events.\n * - The returned object is memoized; values must change for the reference\n * to update.\n *\n * @param options - Optional configuration. See {@link UseScreenOptions}.\n * @returns A memoized {@link UseScreenResult} object.\n *\n * @example\n * ```tsx\n * import { useScreen } from \"@opensite/hooks/useScreen\";\n *\n * function ResponsiveLayout() {\n * const { screenType, tailwindSize, width } = useScreen();\n *\n * return (\n * <div>\n * <p>Viewport: {width}px ({tailwindSize})</p>\n * {screenType === \"MOBILE\" && <MobileNav />}\n * {screenType === \"TABLET\" && <TabletNav />}\n * {screenType === \"DESKTOP\" && <DesktopNav />}\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With custom breakpoints\n * const { screenType } = useScreen({\n * breakpoints: { sm: 600, md: 900, lg: 1200, xl: 1400, \"2xl\": 1800 },\n * });\n * ```\n */\nexport function useScreen(options: UseScreenOptions = {}): UseScreenResult {\n const {\n breakpoints: customBreakpoints,\n screenTypeMapping: customMapping,\n defaultScreenType = \"UNKNOWN\",\n defaultTailwindSize = \"default\",\n } = options;\n\n // Merge custom config with defaults\n const breakpoints = useMemo<ScreenBreakpoints>(\n () => ({ ...DEFAULT_BREAKPOINTS, ...customBreakpoints }),\n [customBreakpoints],\n );\n\n const screenTypeMapping = useMemo<ScreenTypeMapping>(\n () => ({ ...DEFAULT_SCREEN_TYPE_MAPPING, ...customMapping }),\n [customMapping],\n );\n\n // Track viewport dimensions\n const [dimensions, setDimensions] = useState(() => getViewportDimensions());\n\n // Use media queries for breakpoint detection (more reliable than width alone\n // for edge cases and provides instant updates via matchMedia)\n const isSm = useMediaQuery(`(min-width: ${breakpoints.sm}px)`);\n const isMd = useMediaQuery(`(min-width: ${breakpoints.md}px)`);\n const isLg = useMediaQuery(`(min-width: ${breakpoints.lg}px)`);\n const isXl = useMediaQuery(`(min-width: ${breakpoints.xl}px)`);\n const is2xl = useMediaQuery(`(min-width: ${breakpoints[\"2xl\"]}px)`);\n\n // Stable refresh callback\n const refresh = useCallback(() => {\n if (typeof window === \"undefined\") return;\n setDimensions(getViewportDimensions());\n }, []);\n\n // Subscribe to resize events for dimension tracking\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n // Initial measurement\n setDimensions(getViewportDimensions());\n\n const handleResize = () => {\n setDimensions(getViewportDimensions());\n };\n\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n // Compute tailwindSize from media query results (most reliable)\n const tailwindSize = useMemo<TailwindSize>(() => {\n // During SSR, all media queries return false\n if (!isSm && !isMd && !isLg && !isXl && !is2xl) {\n // Check if we're on client with actual dimensions\n if (dimensions.width > 0) {\n return calculateTailwindSize(dimensions.width, breakpoints);\n }\n return defaultTailwindSize;\n }\n\n // Determine from media queries (largest matching breakpoint wins)\n if (is2xl) return \"2xl\";\n if (isXl) return \"xl\";\n if (isLg) return \"lg\";\n if (isMd) return \"md\";\n if (isSm) return \"sm\";\n return \"default\";\n }, [\n isSm,\n isMd,\n isLg,\n isXl,\n is2xl,\n dimensions.width,\n breakpoints,\n defaultTailwindSize,\n ]);\n\n // Derive screen type from tailwind size\n const screenType = useMemo<ScreenType>(() => {\n // During SSR before detection\n if (tailwindSize === defaultTailwindSize && dimensions.width === 0) {\n return defaultScreenType;\n }\n return screenTypeMapping[tailwindSize];\n }, [\n tailwindSize,\n screenTypeMapping,\n dimensions.width,\n defaultScreenType,\n defaultTailwindSize,\n ]);\n\n // Memoize the return object for stable references\n return useMemo<UseScreenResult>(\n () => ({\n width: dimensions.width,\n height: dimensions.height,\n tailwindSize,\n screenType,\n refresh,\n }),\n [dimensions.width, dimensions.height, tailwindSize, screenType, refresh],\n );\n}\n","import { useCallback, useMemo, useState } from \"react\";\n\nexport interface UseBooleanResult {\n value: boolean;\n setValue: React.Dispatch<React.SetStateAction<boolean>>;\n setTrue: () => void;\n setFalse: () => void;\n toggle: () => void;\n}\n\nexport function useBoolean(defaultValue = false): UseBooleanResult {\n const [value, setValue] = useState<boolean>(defaultValue);\n\n const setTrue = useCallback(() => setValue(true), []);\n const setFalse = useCallback(() => setValue(false), []);\n const toggle = useCallback(() => setValue((current) => !current), []);\n\n // Memoize the return object to prevent unnecessary re-renders in consumers\n // that use the result object in dependency arrays\n return useMemo(\n () => ({ value, setValue, setTrue, setFalse, toggle }),\n [value, setValue, setTrue, setFalse, toggle]\n );\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nexport interface UseCopyToClipboardOptions {\n resetDelay?: number;\n}\n\nexport interface CopyToClipboardResult {\n copy: (text: string) => Promise<boolean>;\n copiedText: string | null;\n isSupported: boolean;\n}\n\nexport function useCopyToClipboard(\n options: UseCopyToClipboardOptions = {}\n): CopyToClipboardResult {\n const resetDelay = options.resetDelay ?? 2000;\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const [copiedText, setCopiedText] = useState<string | null>(null);\n\n const isSupported = useMemo(() => {\n if (typeof navigator !== \"undefined\" && navigator.clipboard) {\n return true;\n }\n if (typeof document === \"undefined\") {\n return false;\n }\n if (typeof document.queryCommandSupported !== \"function\") {\n return false;\n }\n return document.queryCommandSupported(\"copy\");\n }, []);\n\n const resetCopied = useCallback(() => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n timeoutRef.current = setTimeout(() => {\n setCopiedText(null);\n }, resetDelay);\n }, [resetDelay]);\n\n const copy = useCallback(\n async (text: string) => {\n if (!isSupported) {\n return false;\n }\n\n const shouldUseClipboardApi =\n typeof navigator !== \"undefined\" && !!navigator.clipboard;\n\n try {\n if (shouldUseClipboardApi) {\n await navigator.clipboard.writeText(text);\n } else if (typeof document !== \"undefined\") {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.setAttribute(\"readonly\", \"\");\n textarea.style.position = \"fixed\";\n textarea.style.left = \"-9999px\";\n textarea.style.top = \"0\";\n document.body.appendChild(textarea);\n textarea.focus();\n textarea.select();\n const success = document.execCommand(\"copy\");\n document.body.removeChild(textarea);\n if (!success) {\n return false;\n }\n }\n\n setCopiedText(text);\n resetCopied();\n return true;\n } catch {\n return false;\n }\n },\n [isSupported, resetCopied]\n );\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return { copy, copiedText, isSupported };\n}\n","import { useCallback, useState } from \"react\";\nimport { useEventListener } from \"./useEventListener.js\";\n\nexport function useHover<T extends HTMLElement>(\n ref: React.RefObject<T>\n): boolean {\n const [isHovered, setIsHovered] = useState(false);\n\n const handleEnter = useCallback(() => {\n setIsHovered(true);\n }, []);\n\n const handleLeave = useCallback(() => {\n setIsHovered(false);\n }, []);\n\n useEventListener(\"pointerenter\", handleEnter, ref);\n useEventListener(\"pointerleave\", handleLeave, ref);\n\n return isHovered;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface StorageOptions<T> {\n initializeWithValue?: boolean;\n serialize?: (value: T) => string;\n deserialize?: (value: string) => T;\n listenToStorageChanges?: boolean;\n}\n\ntype StoredSetter<T> = (value: T | ((current: T) => T)) => void;\n\nexport function useLocalStorage<T>(\n key: string,\n initialValue: T,\n options: StorageOptions<T> = {}\n): [T, StoredSetter<T>] {\n const {\n initializeWithValue = true,\n serialize = JSON.stringify,\n deserialize = JSON.parse as (value: string) => T,\n listenToStorageChanges = true,\n } = options;\n\n const initialValueRef = useRef(initialValue);\n\n const readValue = useCallback(() => {\n if (typeof window === \"undefined\") {\n return initialValueRef.current;\n }\n if (!initializeWithValue) {\n return initialValueRef.current;\n }\n try {\n const item = window.localStorage.getItem(key);\n return item ? deserialize(item) : initialValueRef.current;\n } catch {\n return initialValueRef.current;\n }\n }, [deserialize, initializeWithValue, key]);\n\n const [storedValue, setStoredValue] = useState<T>(() => readValue());\n\n const setValue: StoredSetter<T> = useCallback(\n (value) => {\n setStoredValue((current) => {\n const valueToStore =\n typeof value === \"function\"\n ? (value as (current: T) => T)(current)\n : value;\n if (typeof window !== \"undefined\") {\n try {\n window.localStorage.setItem(key, serialize(valueToStore));\n } catch {\n // Ignore write errors (quota/security)\n }\n }\n return valueToStore;\n });\n },\n [key, serialize]\n );\n\n useEffect(() => {\n setStoredValue(readValue());\n }, [readValue]);\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !listenToStorageChanges) {\n return;\n }\n\n const handleStorageChange = (event: StorageEvent) => {\n if (event.key !== key) {\n return;\n }\n if (event.newValue === null) {\n setStoredValue(initialValueRef.current);\n return;\n }\n try {\n setStoredValue(deserialize(event.newValue));\n } catch {\n setStoredValue(initialValueRef.current);\n }\n };\n\n window.addEventListener(\"storage\", handleStorageChange);\n return () => window.removeEventListener(\"storage\", handleStorageChange);\n }, [deserialize, key, listenToStorageChanges]);\n\n return [storedValue, setValue];\n}\n","import { useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\nexport function usePrevious<T>(value: T): T | undefined {\n const ref = useRef<T>();\n\n // Use useIsomorphicLayoutEffect to capture the previous value synchronously\n // BEFORE paint. This ensures that during render, ref.current holds the actual\n // previous value (from the last render), not the current value.\n // Using useEffect would update AFTER paint, making comparisons incorrect.\n useIsomorphicLayoutEffect(() => {\n ref.current = value;\n }, [value]);\n\n return ref.current;\n}\n","import { useEffect, useRef, useState } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\n\ntype TargetElement<T extends Element> = React.RefObject<T> | T | null;\n\nconst isRefObject = <T extends Element>(\n value: unknown\n): value is React.RefObject<T> => !!value && typeof value === \"object\" && \"current\" in value;\n\nexport function useResizeObserver<T extends Element>(\n target: TargetElement<T>,\n onResize?: (entry: ResizeObserverEntry) => void,\n options?: ResizeObserverOptions\n): ResizeObserverEntry | null {\n const callbackRef = useRef(onResize);\n const entryRef = useRef<ResizeObserverEntry | null>(null);\n const [entry, setEntry] = useState<ResizeObserverEntry | null>(null);\n\n useIsomorphicLayoutEffect(() => {\n callbackRef.current = onResize;\n }, [onResize]);\n\n useEffect(() => {\n if (typeof ResizeObserver === \"undefined\") {\n return;\n }\n\n const element =\n typeof Element !== \"undefined\" && target instanceof Element\n ? target\n : isRefObject<T>(target)\n ? target.current\n : null;\n if (!element) {\n return;\n }\n\n const observer = new ResizeObserver((entries) => {\n const firstEntry = entries[0];\n entryRef.current = firstEntry;\n if (callbackRef.current) {\n callbackRef.current(firstEntry);\n } else {\n setEntry(firstEntry);\n }\n });\n\n observer.observe(element, options);\n return () => observer.disconnect();\n }, [options, target]);\n\n return callbackRef.current ? entryRef.current : entry;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface SessionStorageOptions<T> {\n initializeWithValue?: boolean;\n serialize?: (value: T) => string;\n deserialize?: (value: string) => T;\n listenToStorageChanges?: boolean;\n}\n\ntype StoredSetter<T> = (value: T | ((current: T) => T)) => void;\n\nexport function useSessionStorage<T>(\n key: string,\n initialValue: T,\n options: SessionStorageOptions<T> = {}\n): [T, StoredSetter<T>] {\n const {\n initializeWithValue = true,\n serialize = JSON.stringify,\n deserialize = JSON.parse as (value: string) => T,\n listenToStorageChanges = false,\n } = options;\n\n const initialValueRef = useRef(initialValue);\n\n const readValue = useCallback(() => {\n if (typeof window === \"undefined\") {\n return initialValueRef.current;\n }\n if (!initializeWithValue) {\n return initialValueRef.current;\n }\n try {\n const item = window.sessionStorage.getItem(key);\n return item ? deserialize(item) : initialValueRef.current;\n } catch {\n return initialValueRef.current;\n }\n }, [deserialize, initializeWithValue, key]);\n\n const [storedValue, setStoredValue] = useState<T>(() => readValue());\n\n const setValue: StoredSetter<T> = useCallback(\n (value) => {\n setStoredValue((current) => {\n const valueToStore =\n typeof value === \"function\"\n ? (value as (current: T) => T)(current)\n : value;\n if (typeof window !== \"undefined\") {\n try {\n window.sessionStorage.setItem(key, serialize(valueToStore));\n } catch {\n // Ignore write errors (quota/security)\n }\n }\n return valueToStore;\n });\n },\n [key, serialize]\n );\n\n useEffect(() => {\n setStoredValue(readValue());\n }, [readValue]);\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !listenToStorageChanges) {\n return;\n }\n\n const handleStorageChange = (event: StorageEvent) => {\n if (event.key !== key) {\n return;\n }\n if (event.newValue === null) {\n setStoredValue(initialValueRef.current);\n return;\n }\n try {\n setStoredValue(deserialize(event.newValue));\n } catch {\n setStoredValue(initialValueRef.current);\n }\n };\n\n window.addEventListener(\"storage\", handleStorageChange);\n return () => window.removeEventListener(\"storage\", handleStorageChange);\n }, [deserialize, key, listenToStorageChanges]);\n\n return [storedValue, setValue];\n}\n","import { useEffect, useRef, useState } from \"react\";\n\nexport interface ThrottleOptions {\n leading?: boolean;\n trailing?: boolean;\n}\n\nexport function useThrottle<T>(\n value: T,\n interval: number,\n options: ThrottleOptions = {}\n): T {\n const leading = options.leading ?? true;\n const trailing = options.trailing ?? true;\n const wait = Math.max(0, interval);\n\n const [throttledValue, setThrottledValue] = useState<T>(value);\n const lastExecutedRef = useRef<number>(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingValueRef = useRef<T | null>(null);\n\n useEffect(() => {\n if (wait === 0) {\n setThrottledValue(value);\n return;\n }\n\n const now = Date.now();\n\n if (lastExecutedRef.current === 0) {\n lastExecutedRef.current = now;\n if (leading) {\n setThrottledValue(value);\n return;\n }\n if (trailing && !timeoutRef.current) {\n pendingValueRef.current = value;\n timeoutRef.current = setTimeout(() => {\n timeoutRef.current = null;\n if (pendingValueRef.current !== null) {\n setThrottledValue(pendingValueRef.current);\n pendingValueRef.current = null;\n lastExecutedRef.current = Date.now();\n }\n }, wait);\n }\n return;\n }\n\n const elapsed = now - lastExecutedRef.current;\n\n if (elapsed >= wait && leading) {\n setThrottledValue(value);\n lastExecutedRef.current = now;\n pendingValueRef.current = null;\n return;\n }\n\n if (trailing) {\n pendingValueRef.current = value;\n if (!timeoutRef.current) {\n const remaining = Math.max(wait - elapsed, 0);\n timeoutRef.current = setTimeout(() => {\n timeoutRef.current = null;\n if (pendingValueRef.current !== null) {\n setThrottledValue(pendingValueRef.current);\n pendingValueRef.current = null;\n lastExecutedRef.current = Date.now();\n }\n }, remaining);\n }\n }\n }, [leading, trailing, value, wait]);\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n return throttledValue;\n}\n","import { useCallback } from \"react\";\nimport { useWebsiteExtractorBase } from \"./useWebsiteExtractorBase.js\";\nimport type {\n WebsiteExtractorOptions,\n WebsiteExtractorResponse,\n WebsiteExtractorResult,\n WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\nexport interface WebsiteLinkRecord {\n url: string;\n text: string;\n isExternal: boolean;\n domain?: string | null;\n}\n\nexport interface WebsiteLinksPayload {\n totalLinks: number;\n uniqueDomains: number;\n links: WebsiteLinkRecord[];\n}\n\nexport type WebsiteLinksResponse = WebsiteExtractorResponse<WebsiteLinksPayload>;\n\nexport function useWebsiteLinksExtractor(\n options: WebsiteExtractorOptions\n): WebsiteExtractorResult<WebsiteLinksPayload, WebsiteLinksResponse> {\n const selectData = useCallback(\n (\n payload: Omit<WebsiteLinksResponse, keyof WebsiteExtractMeta>\n ): WebsiteLinksPayload => {\n return {\n totalLinks: payload.totalLinks ?? 0,\n uniqueDomains: payload.uniqueDomains ?? 0,\n links: payload.links ?? [],\n };\n },\n []\n );\n\n return useWebsiteExtractorBase<WebsiteLinksResponse, WebsiteLinksPayload>({\n endpoint: \"links\",\n options,\n selectData,\n });\n}\n","import { useCallback } from \"react\";\nimport { useWebsiteExtractorBase } from \"./useWebsiteExtractorBase.js\";\nimport type {\n WebsiteExtractorOptions,\n WebsiteExtractorResponse,\n WebsiteExtractorResult,\n WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\nexport interface WebsiteMetaPayload {\n title?: string | null;\n description?: string | null;\n language?: string | null;\n canonicalUrl?: string | null;\n feedUrl?: string | null;\n textContentLength?: number | null;\n metaTags: Record<string, string>;\n}\n\nexport type WebsiteMetaResponse = WebsiteExtractorResponse<WebsiteMetaPayload>;\n\nexport function useWebsiteMetaExtractor(\n options: WebsiteExtractorOptions\n): WebsiteExtractorResult<WebsiteMetaPayload, WebsiteMetaResponse> {\n const selectData = useCallback(\n (\n payload: Omit<WebsiteMetaResponse, keyof WebsiteExtractMeta>\n ): WebsiteMetaPayload => {\n return {\n title: payload.title ?? undefined,\n description: payload.description ?? undefined,\n language: payload.language ?? undefined,\n canonicalUrl: payload.canonicalUrl ?? undefined,\n feedUrl: payload.feedUrl ?? null,\n textContentLength: payload.textContentLength ?? undefined,\n metaTags: payload.metaTags ?? {},\n };\n },\n []\n );\n\n return useWebsiteExtractorBase<WebsiteMetaResponse, WebsiteMetaPayload>({\n endpoint: \"meta\",\n options,\n selectData,\n });\n}\n","import { useCallback } from \"react\";\nimport { useWebsiteExtractorBase } from \"./useWebsiteExtractorBase.js\";\nimport type {\n WebsiteExtractorOptions,\n WebsiteExtractorResponse,\n WebsiteExtractorResult,\n WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\nexport interface WebsiteRssFeed {\n url: string;\n feedType: string;\n title?: string | null;\n}\n\nexport interface WebsiteRssPayload {\n feedUrl?: string | null;\n feeds: WebsiteRssFeed[];\n}\n\nexport type WebsiteRssResponse = WebsiteExtractorResponse<WebsiteRssPayload>;\n\nexport function useWebsiteRssExtractor(\n options: WebsiteExtractorOptions\n): WebsiteExtractorResult<WebsiteRssPayload, WebsiteRssResponse> {\n const selectData = useCallback(\n (\n payload: Omit<WebsiteRssResponse, keyof WebsiteExtractMeta>\n ): WebsiteRssPayload => {\n return {\n feedUrl: payload.feedUrl ?? null,\n feeds: payload.feeds ?? [],\n };\n },\n []\n );\n\n return useWebsiteExtractorBase<WebsiteRssResponse, WebsiteRssPayload>({\n endpoint: \"rss\",\n options,\n selectData,\n });\n}\n","import { useCallback } from \"react\";\nimport { useWebsiteExtractorBase } from \"./useWebsiteExtractorBase.js\";\nimport type {\n WebsiteExtractorOptions,\n WebsiteExtractorResponse,\n WebsiteExtractorResult,\n WebsiteExtractMeta,\n} from \"./websiteExtractorTypes.js\";\n\nexport interface WebsiteSchemaRecord {\n schema_type: string;\n value: Record<string, unknown>;\n}\n\nexport interface WebsiteSchemaPayload {\n schema: WebsiteSchemaRecord[];\n schemaTypes: string[];\n}\n\nexport type WebsiteSchemaResponse = WebsiteExtractorResponse<WebsiteSchemaPayload>;\n\nexport function useWebsiteSchemaExtractor(\n options: WebsiteExtractorOptions\n): WebsiteExtractorResult<WebsiteSchemaPayload, WebsiteSchemaResponse> {\n const selectData = useCallback(\n (\n payload: Omit<WebsiteSchemaResponse, keyof WebsiteExtractMeta>\n ): WebsiteSchemaPayload => {\n return {\n schema: payload.schema ?? [],\n schemaTypes: payload.schemaTypes ?? [],\n };\n },\n []\n );\n\n return useWebsiteExtractorBase<WebsiteSchemaResponse, WebsiteSchemaPayload>({\n endpoint: \"schema\",\n options,\n selectData,\n });\n}\n"],"names":["useIsomorphicLayoutEffect","window","useLayoutEffect","useEffect","useDebounceCallback","callback","delay","options","callbackRef","useRef","timeoutRef","maxTimeoutRef","lastArgsRef","leading","trailing","maxWait","wait","Math","max","current","clearTimers","useCallback","clearTimeout","invoke","args","debouncedCallback","setTimeout","maxDelay","cancel","flush","useDebounceValue","value","debouncedValue","setDebouncedValue","useState","next","getOwnerDocument","element","ownerDocument","document","useMediaQuery","query","matches","setMatches","matchMedia","defaultValue","mediaQueryList","handler","event","addEventListener","removeEventListener","PLATFORM_HOSTNAME_MAP","Map","useEventListener","eventName","savedHandler","isWindow","Window","isDocument","Document","target","HTMLElement","listener","currentHandler","handleEvent","useIsClient","isClient","setIsClient","useMap","initialState","map","setMap","Array","isArray","mapRef","actions","useMemo","set","key","prev","setAll","entries","remove","delete","clear","get","has","DEFAULT_WEBSITE_EXTRACTOR_BASE_URL","buildWebsiteExtractorUrl","request","baseUrl","replace","normalizeBaseUrl","params","URLSearchParams","apiKey","url","endpoint","toString","async","fetchWebsiteExtractor","trim","length","ok","error","message","response","fetch","method","signal","payload","json","errorPayload","status","raw","_a","aborted","Error","useWebsiteExtractorBase","config","selectData","shouldSkip","state","setState","loading","cacheActions","cacheEnabled","cache","enabled","debounceMs","refreshDebounceMs","debouncedUrl","refreshCounterRef","lastRefreshHandledRef","refreshToken","setRefreshToken","scheduleRefresh","cancelRefresh","refresh","requestKey","inFlightControllerRef","forceRefresh","cached","data","meta","abort","controller","AbortController","then","result","requestedUrl","finalUrl","normalizedUrl","contentType","fetchedAt","bodyBytes","bodyTruncated","maxBodyBytes","extractWebsiteMeta","_requestedUrl","_finalUrl","_url","_normalizedUrl","_status","_contentType","_fetchedAt","_bodyBytes","_bodyTruncated","_maxBodyBytes","_cache","stripWebsiteMeta","catch","DEFAULT_SKIP_PATTERNS","pickFirstString","values","safeHost","URL","hostname","TOUCH_MEDIA_QUERY","detectTouchCapability","mql","media","navigator","maxTouchPoints","DEFAULT_BREAKPOINTS","sm","md","lg","xl","DEFAULT_SCREEN_TYPE_MAPPING","default","getViewportDimensions","width","height","innerWidth","innerHeight","setValue","setTrue","setFalse","toggle","resetDelay","copiedText","setCopiedText","isSupported","clipboard","queryCommandSupported","resetCopied","copy","text","shouldUseClipboardApi","writeText","textarea","createElement","setAttribute","style","position","left","top","body","appendChild","focus","select","success","execCommand","removeChild","ref","isHovered","setIsHovered","handleEnter","handleLeave","defaultDeviceType","deviceType","setDeviceType","recheck","onChange","isTouchDevice","initialValue","initializeWithValue","serialize","JSON","stringify","deserialize","parse","listenToStorageChanges","initialValueRef","readValue","item","localStorage","getItem","storedValue","setStoredValue","valueToStore","setItem","handleStorageChange","newValue","eventType","handlerRef","supportsPointerEvents","PointerEvent","resolvedEventType","refs","targetDocument","currentRef","refDocument","Node","some","node","contains","skipPatterns","pattern","test","_raw","openGraph","htmlInferred","hybridGraph","description","title","siteName","site_name","favicon","image","_b","images","video","_c","videoType","resolvedUrl","siteHost","trimmedUrl","toLowerCase","platform","endsWith","includes","onResize","entryRef","entry","setEntry","ResizeObserver","Element","observer","firstEntry","observe","disconnect","breakpoints","customBreakpoints","screenTypeMapping","customMapping","defaultScreenType","defaultTailwindSize","dimensions","setDimensions","isSm","isMd","isLg","isXl","is2xl","handleResize","tailwindSize","calculateTailwindSize","screenType","sessionStorage","interval","throttledValue","setThrottledValue","lastExecutedRef","pendingValueRef","now","Date","elapsed","remaining","totalLinks","uniqueDomains","links","language","canonicalUrl","feedUrl","textContentLength","metaTags","feeds","schema","schemaTypes"],"mappings":"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"}
|
package/dist/core/index.cjs
CHANGED
|
@@ -20,5 +20,7 @@ export { useWebsiteSchemaExtractor } from "./useWebsiteSchemaExtractor.js";
|
|
|
20
20
|
export { useWebsiteLinksExtractor } from "./useWebsiteLinksExtractor.js";
|
|
21
21
|
export { useWebsiteMetaExtractor } from "./useWebsiteMetaExtractor.js";
|
|
22
22
|
export { useWebsiteRssExtractor } from "./useWebsiteRssExtractor.js";
|
|
23
|
+
export { useIsTouchDevice } from "./useIsTouchDevice.js";
|
|
24
|
+
export { useScreen } from "./useScreen.js";
|
|
23
25
|
export { fetchWebsiteExtractor } from "./websiteExtractorService.js";
|
|
24
26
|
export { buildWebsiteExtractorUrl } from "./websiteExtractorService.js";
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { useBoolean } from "./useBoolean.js";
|
|
2
2
|
export type { UseBooleanResult } from "./useBoolean.js";
|
|
3
3
|
export { useDebounceCallback } from "./useDebounceCallback.js";
|
|
4
|
-
export type { DebounceOptions, DebouncedCallback } from "./useDebounceCallback.js";
|
|
4
|
+
export type { DebounceOptions, DebouncedCallback, } from "./useDebounceCallback.js";
|
|
5
5
|
export { useDebounceValue } from "./useDebounceValue.js";
|
|
6
6
|
export { useLocalStorage } from "./useLocalStorage.js";
|
|
7
7
|
export type { StorageOptions } from "./useLocalStorage.js";
|
|
@@ -34,6 +34,10 @@ export { useWebsiteMetaExtractor } from "./useWebsiteMetaExtractor.js";
|
|
|
34
34
|
export type { WebsiteMetaPayload, WebsiteMetaResponse, } from "./useWebsiteMetaExtractor.js";
|
|
35
35
|
export { useWebsiteRssExtractor } from "./useWebsiteRssExtractor.js";
|
|
36
36
|
export type { WebsiteRssFeed, WebsiteRssPayload, WebsiteRssResponse, } from "./useWebsiteRssExtractor.js";
|
|
37
|
+
export { useIsTouchDevice } from "./useIsTouchDevice.js";
|
|
38
|
+
export type { DeviceType, UseIsTouchDeviceOptions, UseIsTouchDeviceResult, } from "./useIsTouchDevice.js";
|
|
39
|
+
export { useScreen } from "./useScreen.js";
|
|
40
|
+
export type { ScreenBreakpoints, ScreenType, ScreenTypeMapping, TailwindSize, UseScreenOptions, UseScreenResult, } from "./useScreen.js";
|
|
37
41
|
export { fetchWebsiteExtractor } from "./websiteExtractorService.js";
|
|
38
42
|
export { buildWebsiteExtractorUrl } from "./websiteExtractorService.js";
|
|
39
43
|
export type { WebsiteExtractCacheMeta, WebsiteExtractMeta, WebsiteExtractorError, WebsiteExtractorOptions, WebsiteExtractorRequest, WebsiteExtractorResponse, WebsiteExtractorResult, WebsiteExtractorState, } from "./websiteExtractorTypes.js";
|
package/dist/core/index.js
CHANGED
|
@@ -20,5 +20,7 @@ export { useWebsiteSchemaExtractor } from "./useWebsiteSchemaExtractor.js";
|
|
|
20
20
|
export { useWebsiteLinksExtractor } from "./useWebsiteLinksExtractor.js";
|
|
21
21
|
export { useWebsiteMetaExtractor } from "./useWebsiteMetaExtractor.js";
|
|
22
22
|
export { useWebsiteRssExtractor } from "./useWebsiteRssExtractor.js";
|
|
23
|
+
export { useIsTouchDevice } from "./useIsTouchDevice.js";
|
|
24
|
+
export { useScreen } from "./useScreen.js";
|
|
23
25
|
export { fetchWebsiteExtractor } from "./websiteExtractorService.js";
|
|
24
26
|
export { buildWebsiteExtractorUrl } from "./websiteExtractorService.js";
|