Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@opensite/hooks

Package Overview
Dependencies
Maintainers
2
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@opensite/hooks - npm Package Compare versions

Comparing version
2.0.8
to
2.1.0
+151
dist/core/useIsTouchDevice.cjs
import { useCallback, useEffect, useMemo, useState } from "react";
// ---------------------------------------------------------------------------
// Detection internals
// ---------------------------------------------------------------------------
/**
* CSS media query targeting devices whose *primary* pointing device is coarse
* and lacks hover — the W3C Interaction Media Features (Level 4) definition of
* a touch-first device.
*
* Evaluated via `matchMedia` and subscribed to with a `change` listener so
* convertible laptops, detachable tablets, and foldables react to input-mode
* switches in real time.
*
* @see https://www.w3.org/TR/mediaqueries-4/#mf-interaction
*/
const TOUCH_MEDIA_QUERY = "(hover: none) and (pointer: coarse)";
/**
* Synchronously evaluate touch capability using a two-tier strategy.
*
* **Tier 1 — Interaction Media Features** (`pointer: coarse` + `hover: none`).
* Most accurate on modern browsers (Chrome 41+, Firefox 64+, Safari 9+) and
* correctly classifies hybrid devices by inspecting only the *primary* input.
*
* **Tier 2 — Legacy touch-API probe** (`ontouchstart` on window,
* `navigator.maxTouchPoints`). Covers older Android WebView and pre-Chromium
* Edge. This probe can produce false positives on some desktop touchscreens,
* but it is only reached when Tier 1 is inconclusive.
*
* The function must only be called in a browser context — callers are
* responsible for guarding with `typeof window !== "undefined"`.
*/
function detectTouchCapability() {
// Tier 1: Interaction Media Features (wide support since ~2018).
if (typeof window.matchMedia === "function") {
const mql = window.matchMedia(TOUCH_MEDIA_QUERY);
// `mql.media` is set to `"not all"` when the browser does not understand
// the query at all — in that case we fall through to the legacy probe.
if (mql.media !== "not all") {
return mql.matches ? "touch" : "desktop";
}
}
// Tier 2: legacy touch-API probe.
if ("ontouchstart" in window ||
(typeof navigator !== "undefined" &&
typeof navigator.maxTouchPoints === "number" &&
navigator.maxTouchPoints > 0)) {
return "touch";
}
return "desktop";
}
// ---------------------------------------------------------------------------
// Hook
// ---------------------------------------------------------------------------
/**
* Detect whether the device's primary input mechanism is touch-based.
*
* Uses a layered detection strategy combining CSS Interaction Media Features
* (`pointer: coarse`, `hover: none`) with legacy touch-API probing
* (`ontouchstart`, `maxTouchPoints`) for maximum browser coverage.
*
* ### SSR safety
*
* The hook returns `"unknown"` (or a caller-supplied `defaultDeviceType`) on
* the server **and** during the first client render, so the server and client
* always produce identical markup — no hydration mismatch. After the commit
* phase, a `useEffect` evaluates synchronously and subscribes to `matchMedia`
* change events.
*
* ### Dynamic updates
*
* On browsers that support Interaction Media Features, the hook listens for
* `change` events on the `(hover: none) and (pointer: coarse)` query. This
* means convertible laptops, detachable tablets, and foldables will
* automatically re-classify when the user switches input modes — no polling
* required.
*
* ### Performance
*
* - Detection runs once on mount (one `matchMedia` call) — no per-render work.
* - The `change` listener is passive and only fires on actual input-mode
* transitions (rare).
* - The returned object is memoized; `deviceType` must change for the
* reference to update.
* - The `recheck` callback is `useCallback`-stable for the hook's lifetime.
*
* @param options - Optional configuration. See {@link UseIsTouchDeviceOptions}.
* @returns A memoized {@link UseIsTouchDeviceResult} object.
*
* @example
* ```tsx
* import { useIsTouchDevice } from "@opensite/hooks/useIsTouchDevice";
*
* function Toolbar() {
* const { isTouchDevice } = useIsTouchDevice();
* return isTouchDevice ? <TouchToolbar /> : <DesktopToolbar />;
* }
* ```
*
* @example
* ```tsx
* // With a server-side hint to eliminate layout shift
* function Page({ ssrDeviceType }: { ssrDeviceType: DeviceType }) {
* const { deviceType } = useIsTouchDevice({
* defaultDeviceType: ssrDeviceType,
* });
* // ...
* }
* ```
*/
export function useIsTouchDevice(options = {}) {
const { defaultDeviceType = "unknown" } = options;
// SSR-safe initial state — never accesses browser APIs at initialization.
const [deviceType, setDeviceType] = useState(defaultDeviceType);
// Stable imperative re-evaluation callback. Guarded so it is a safe no-op
// if accidentally called during SSR (e.g. in a shared util).
const recheck = useCallback(() => {
if (typeof window === "undefined")
return;
setDeviceType(detectTouchCapability());
}, []);
useEffect(() => {
if (typeof window === "undefined")
return;
// Synchronous first evaluation — updates state in the same commit so
// consumers see the real value on the very first paint after hydration.
setDeviceType(detectTouchCapability());
// Subscribe to media-query changes so hybrid/convertible devices update
// when the user switches between touch and pointer input modes.
if (typeof window.matchMedia !== "function")
return;
const mql = window.matchMedia(TOUCH_MEDIA_QUERY);
// If the browser doesn't understand the query there's nothing to listen
// for — the legacy probe result from above is our final answer.
if (mql.media === "not all")
return;
const onChange = (event) => {
setDeviceType(event.matches ? "touch" : "desktop");
};
mql.addEventListener("change", onChange);
return () => {
mql.removeEventListener("change", onChange);
};
}, []);
// Memoize the return object so consumers that destructure or pass the whole
// result into dependency arrays don't trigger spurious re-renders.
return useMemo(() => ({
deviceType,
isTouchDevice: deviceType === "touch",
recheck,
}), [deviceType, recheck]);
}
/**
* Granular classification of the detected primary input device.
*
* - `"unknown"` – returned during SSR and before the first client-side
* evaluation completes. Consumers should treat this as "not yet determined"
* and render a safe, non-committal default (e.g. show both touch and pointer
* affordances, or a loading skeleton).
* - `"touch"` – the primary input is a coarse pointer with no hover capability
* (phones, most tablets in touch mode).
* - `"desktop"` – the primary input is a fine pointer with hover support
* (mouse, trackpad, stylus with hover).
*/
export type DeviceType = "unknown" | "touch" | "desktop";
/**
* Configuration options for {@link useIsTouchDevice}.
*/
export interface UseIsTouchDeviceOptions {
/**
* Value returned during SSR and before the first client-side detection.
*
* Defaults to `"unknown"`. Override with `"touch"` or `"desktop"` when you
* have server-side hints (e.g. User-Agent parsing via middleware) and want to
* avoid a visible layout shift after hydration.
*/
defaultDeviceType?: DeviceType;
}
/**
* Shape returned by {@link useIsTouchDevice}.
*
* The object reference is memoized with `useMemo` so it remains referentially
* stable across re-renders when `deviceType` has not changed — safe to include
* in downstream dependency arrays without triggering spurious effects.
*/
export interface UseIsTouchDeviceResult {
/** Granular device classification (`"unknown"` | `"touch"` | `"desktop"`). */
deviceType: DeviceType;
/** Convenience boolean — `true` when `deviceType === "touch"`. */
isTouchDevice: boolean;
/**
* Re-run touch detection imperatively.
*
* Useful after external events that the hook cannot observe automatically
* (e.g. a WebHID device connect, or a custom "mode switch" UI toggle).
* The callback reference is stable across the lifetime of the hook.
*/
recheck: () => void;
}
/**
* Detect whether the device's primary input mechanism is touch-based.
*
* Uses a layered detection strategy combining CSS Interaction Media Features
* (`pointer: coarse`, `hover: none`) with legacy touch-API probing
* (`ontouchstart`, `maxTouchPoints`) for maximum browser coverage.
*
* ### SSR safety
*
* The hook returns `"unknown"` (or a caller-supplied `defaultDeviceType`) on
* the server **and** during the first client render, so the server and client
* always produce identical markup — no hydration mismatch. After the commit
* phase, a `useEffect` evaluates synchronously and subscribes to `matchMedia`
* change events.
*
* ### Dynamic updates
*
* On browsers that support Interaction Media Features, the hook listens for
* `change` events on the `(hover: none) and (pointer: coarse)` query. This
* means convertible laptops, detachable tablets, and foldables will
* automatically re-classify when the user switches input modes — no polling
* required.
*
* ### Performance
*
* - Detection runs once on mount (one `matchMedia` call) — no per-render work.
* - The `change` listener is passive and only fires on actual input-mode
* transitions (rare).
* - The returned object is memoized; `deviceType` must change for the
* reference to update.
* - The `recheck` callback is `useCallback`-stable for the hook's lifetime.
*
* @param options - Optional configuration. See {@link UseIsTouchDeviceOptions}.
* @returns A memoized {@link UseIsTouchDeviceResult} object.
*
* @example
* ```tsx
* import { useIsTouchDevice } from "@opensite/hooks/useIsTouchDevice";
*
* function Toolbar() {
* const { isTouchDevice } = useIsTouchDevice();
* return isTouchDevice ? <TouchToolbar /> : <DesktopToolbar />;
* }
* ```
*
* @example
* ```tsx
* // With a server-side hint to eliminate layout shift
* function Page({ ssrDeviceType }: { ssrDeviceType: DeviceType }) {
* const { deviceType } = useIsTouchDevice({
* defaultDeviceType: ssrDeviceType,
* });
* // ...
* }
* ```
*/
export declare function useIsTouchDevice(options?: UseIsTouchDeviceOptions): UseIsTouchDeviceResult;
import { useCallback, useEffect, useMemo, useState } from "react";
// ---------------------------------------------------------------------------
// Detection internals
// ---------------------------------------------------------------------------
/**
* CSS media query targeting devices whose *primary* pointing device is coarse
* and lacks hover — the W3C Interaction Media Features (Level 4) definition of
* a touch-first device.
*
* Evaluated via `matchMedia` and subscribed to with a `change` listener so
* convertible laptops, detachable tablets, and foldables react to input-mode
* switches in real time.
*
* @see https://www.w3.org/TR/mediaqueries-4/#mf-interaction
*/
const TOUCH_MEDIA_QUERY = "(hover: none) and (pointer: coarse)";
/**
* Synchronously evaluate touch capability using a two-tier strategy.
*
* **Tier 1 — Interaction Media Features** (`pointer: coarse` + `hover: none`).
* Most accurate on modern browsers (Chrome 41+, Firefox 64+, Safari 9+) and
* correctly classifies hybrid devices by inspecting only the *primary* input.
*
* **Tier 2 — Legacy touch-API probe** (`ontouchstart` on window,
* `navigator.maxTouchPoints`). Covers older Android WebView and pre-Chromium
* Edge. This probe can produce false positives on some desktop touchscreens,
* but it is only reached when Tier 1 is inconclusive.
*
* The function must only be called in a browser context — callers are
* responsible for guarding with `typeof window !== "undefined"`.
*/
function detectTouchCapability() {
// Tier 1: Interaction Media Features (wide support since ~2018).
if (typeof window.matchMedia === "function") {
const mql = window.matchMedia(TOUCH_MEDIA_QUERY);
// `mql.media` is set to `"not all"` when the browser does not understand
// the query at all — in that case we fall through to the legacy probe.
if (mql.media !== "not all") {
return mql.matches ? "touch" : "desktop";
}
}
// Tier 2: legacy touch-API probe.
if ("ontouchstart" in window ||
(typeof navigator !== "undefined" &&
typeof navigator.maxTouchPoints === "number" &&
navigator.maxTouchPoints > 0)) {
return "touch";
}
return "desktop";
}
// ---------------------------------------------------------------------------
// Hook
// ---------------------------------------------------------------------------
/**
* Detect whether the device's primary input mechanism is touch-based.
*
* Uses a layered detection strategy combining CSS Interaction Media Features
* (`pointer: coarse`, `hover: none`) with legacy touch-API probing
* (`ontouchstart`, `maxTouchPoints`) for maximum browser coverage.
*
* ### SSR safety
*
* The hook returns `"unknown"` (or a caller-supplied `defaultDeviceType`) on
* the server **and** during the first client render, so the server and client
* always produce identical markup — no hydration mismatch. After the commit
* phase, a `useEffect` evaluates synchronously and subscribes to `matchMedia`
* change events.
*
* ### Dynamic updates
*
* On browsers that support Interaction Media Features, the hook listens for
* `change` events on the `(hover: none) and (pointer: coarse)` query. This
* means convertible laptops, detachable tablets, and foldables will
* automatically re-classify when the user switches input modes — no polling
* required.
*
* ### Performance
*
* - Detection runs once on mount (one `matchMedia` call) — no per-render work.
* - The `change` listener is passive and only fires on actual input-mode
* transitions (rare).
* - The returned object is memoized; `deviceType` must change for the
* reference to update.
* - The `recheck` callback is `useCallback`-stable for the hook's lifetime.
*
* @param options - Optional configuration. See {@link UseIsTouchDeviceOptions}.
* @returns A memoized {@link UseIsTouchDeviceResult} object.
*
* @example
* ```tsx
* import { useIsTouchDevice } from "@opensite/hooks/useIsTouchDevice";
*
* function Toolbar() {
* const { isTouchDevice } = useIsTouchDevice();
* return isTouchDevice ? <TouchToolbar /> : <DesktopToolbar />;
* }
* ```
*
* @example
* ```tsx
* // With a server-side hint to eliminate layout shift
* function Page({ ssrDeviceType }: { ssrDeviceType: DeviceType }) {
* const { deviceType } = useIsTouchDevice({
* defaultDeviceType: ssrDeviceType,
* });
* // ...
* }
* ```
*/
export function useIsTouchDevice(options = {}) {
const { defaultDeviceType = "unknown" } = options;
// SSR-safe initial state — never accesses browser APIs at initialization.
const [deviceType, setDeviceType] = useState(defaultDeviceType);
// Stable imperative re-evaluation callback. Guarded so it is a safe no-op
// if accidentally called during SSR (e.g. in a shared util).
const recheck = useCallback(() => {
if (typeof window === "undefined")
return;
setDeviceType(detectTouchCapability());
}, []);
useEffect(() => {
if (typeof window === "undefined")
return;
// Synchronous first evaluation — updates state in the same commit so
// consumers see the real value on the very first paint after hydration.
setDeviceType(detectTouchCapability());
// Subscribe to media-query changes so hybrid/convertible devices update
// when the user switches between touch and pointer input modes.
if (typeof window.matchMedia !== "function")
return;
const mql = window.matchMedia(TOUCH_MEDIA_QUERY);
// If the browser doesn't understand the query there's nothing to listen
// for — the legacy probe result from above is our final answer.
if (mql.media === "not all")
return;
const onChange = (event) => {
setDeviceType(event.matches ? "touch" : "desktop");
};
mql.addEventListener("change", onChange);
return () => {
mql.removeEventListener("change", onChange);
};
}, []);
// Memoize the return object so consumers that destructure or pass the whole
// result into dependency arrays don't trigger spurious re-renders.
return useMemo(() => ({
deviceType,
isTouchDevice: deviceType === "touch",
recheck,
}), [deviceType, recheck]);
}
import { useCallback, useEffect, useMemo, useState } from "react";
import { useMediaQuery } from "./useMediaQuery.js";
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/**
* Tailwind CSS v4 default breakpoints (min-width values in pixels).
* @see https://tailwindcss.com/docs/responsive-design
*/
const DEFAULT_BREAKPOINTS = {
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
"2xl": 1536,
};
/**
* Default mapping of Tailwind breakpoints to semantic screen types.
*
* - default, sm → MOBILE
* - md → TABLET
* - lg, xl, 2xl → DESKTOP
*/
const DEFAULT_SCREEN_TYPE_MAPPING = {
default: "MOBILE",
sm: "MOBILE",
md: "TABLET",
lg: "DESKTOP",
xl: "DESKTOP",
"2xl": "DESKTOP",
};
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
* Get current viewport dimensions.
* Returns `{ width: 0, height: 0 }` during SSR.
*/
function getViewportDimensions() {
if (typeof window === "undefined") {
return { width: 0, height: 0 };
}
return {
width: window.innerWidth,
height: window.innerHeight,
};
}
/**
* Determine the current Tailwind breakpoint based on viewport width.
* Uses mobile-first logic: returns the largest breakpoint that matches.
*/
function calculateTailwindSize(width, breakpoints) {
// Check breakpoints from largest to smallest (mobile-first)
if (width >= breakpoints["2xl"])
return "2xl";
if (width >= breakpoints.xl)
return "xl";
if (width >= breakpoints.lg)
return "lg";
if (width >= breakpoints.md)
return "md";
if (width >= breakpoints.sm)
return "sm";
return "default";
}
// ---------------------------------------------------------------------------
// Hook
// ---------------------------------------------------------------------------
/**
* Track viewport dimensions and compute Tailwind breakpoint / screen type.
*
* Provides real-time viewport width and height, along with derived values for
* the current Tailwind CSS breakpoint (`tailwindSize`) and a semantic screen
* type classification (`screenType`) for layout decisions.
*
* ### SSR Safety
*
* The hook returns safe defaults during SSR (`width: 0`, `height: 0`,
* `tailwindSize: "default"`, `screenType: "UNKNOWN"`) to prevent hydration
* mismatches. After mount, values update to reflect the actual viewport.
*
* ### Performance
*
* - Uses `useMediaQuery` internally for efficient breakpoint detection via
* CSS media queries (no polling).
* - Viewport dimensions update on window `resize` events.
* - The returned object is memoized; values must change for the reference
* to update.
*
* @param options - Optional configuration. See {@link UseScreenOptions}.
* @returns A memoized {@link UseScreenResult} object.
*
* @example
* ```tsx
* import { useScreen } from "@opensite/hooks/useScreen";
*
* function ResponsiveLayout() {
* const { screenType, tailwindSize, width } = useScreen();
*
* return (
* <div>
* <p>Viewport: {width}px ({tailwindSize})</p>
* {screenType === "MOBILE" && <MobileNav />}
* {screenType === "TABLET" && <TabletNav />}
* {screenType === "DESKTOP" && <DesktopNav />}
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // With custom breakpoints
* const { screenType } = useScreen({
* breakpoints: { sm: 600, md: 900, lg: 1200, xl: 1400, "2xl": 1800 },
* });
* ```
*/
export function useScreen(options = {}) {
const { breakpoints: customBreakpoints, screenTypeMapping: customMapping, defaultScreenType = "UNKNOWN", defaultTailwindSize = "default", } = options;
// Merge custom config with defaults
const breakpoints = useMemo(() => ({ ...DEFAULT_BREAKPOINTS, ...customBreakpoints }), [customBreakpoints]);
const screenTypeMapping = useMemo(() => ({ ...DEFAULT_SCREEN_TYPE_MAPPING, ...customMapping }), [customMapping]);
// Track viewport dimensions
const [dimensions, setDimensions] = useState(() => getViewportDimensions());
// Use media queries for breakpoint detection (more reliable than width alone
// for edge cases and provides instant updates via matchMedia)
const isSm = useMediaQuery(`(min-width: ${breakpoints.sm}px)`);
const isMd = useMediaQuery(`(min-width: ${breakpoints.md}px)`);
const isLg = useMediaQuery(`(min-width: ${breakpoints.lg}px)`);
const isXl = useMediaQuery(`(min-width: ${breakpoints.xl}px)`);
const is2xl = useMediaQuery(`(min-width: ${breakpoints["2xl"]}px)`);
// Stable refresh callback
const refresh = useCallback(() => {
if (typeof window === "undefined")
return;
setDimensions(getViewportDimensions());
}, []);
// Subscribe to resize events for dimension tracking
useEffect(() => {
if (typeof window === "undefined")
return;
// Initial measurement
setDimensions(getViewportDimensions());
const handleResize = () => {
setDimensions(getViewportDimensions());
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
// Compute tailwindSize from media query results (most reliable)
const tailwindSize = useMemo(() => {
// During SSR, all media queries return false
if (!isSm && !isMd && !isLg && !isXl && !is2xl) {
// Check if we're on client with actual dimensions
if (dimensions.width > 0) {
return calculateTailwindSize(dimensions.width, breakpoints);
}
return defaultTailwindSize;
}
// Determine from media queries (largest matching breakpoint wins)
if (is2xl)
return "2xl";
if (isXl)
return "xl";
if (isLg)
return "lg";
if (isMd)
return "md";
if (isSm)
return "sm";
return "default";
}, [
isSm,
isMd,
isLg,
isXl,
is2xl,
dimensions.width,
breakpoints,
defaultTailwindSize,
]);
// Derive screen type from tailwind size
const screenType = useMemo(() => {
// During SSR before detection
if (tailwindSize === defaultTailwindSize && dimensions.width === 0) {
return defaultScreenType;
}
return screenTypeMapping[tailwindSize];
}, [
tailwindSize,
screenTypeMapping,
dimensions.width,
defaultScreenType,
defaultTailwindSize,
]);
// Memoize the return object for stable references
return useMemo(() => ({
width: dimensions.width,
height: dimensions.height,
tailwindSize,
screenType,
refresh,
}), [dimensions.width, dimensions.height, tailwindSize, screenType, refresh]);
}
/**
* Tailwind CSS breakpoint identifiers.
*
* - `"default"` – viewport width below the smallest defined breakpoint (< 640px)
* - `"sm"` – small devices (>= 640px)
* - `"md"` – medium devices (>= 768px)
* - `"lg"` – large devices (>= 1024px)
* - `"xl"` – extra large devices (>= 1280px)
* - `"2xl"` – 2x extra large devices (>= 1536px)
*/
export type TailwindSize = "default" | "sm" | "md" | "lg" | "xl" | "2xl";
/**
* Semantic screen type classification for layout decisions.
*
* - `"MOBILE"` – phone-sized viewports (default + sm breakpoints)
* - `"TABLET"` – tablet-sized viewports (md breakpoint)
* - `"DESKTOP"` – desktop-sized viewports (lg, xl, 2xl breakpoints)
* - `"UNKNOWN"` – returned during SSR before client-side detection
*/
export type ScreenType = "UNKNOWN" | "MOBILE" | "TABLET" | "DESKTOP";
/**
* Breakpoint configuration with pixel thresholds.
*
* Values represent the minimum width (in pixels) for each breakpoint.
* Follows Tailwind CSS v4 defaults.
*/
export interface ScreenBreakpoints {
/** Minimum width for `sm` breakpoint. Default: 640 */
sm: number;
/** Minimum width for `md` breakpoint. Default: 768 */
md: number;
/** Minimum width for `lg` breakpoint. Default: 1024 */
lg: number;
/** Minimum width for `xl` breakpoint. Default: 1280 */
xl: number;
/** Minimum width for `2xl` breakpoint. Default: 1536 */
"2xl": number;
}
/**
* Mapping of Tailwind breakpoints to semantic screen types.
*/
export interface ScreenTypeMapping {
/** Screen type for default (< sm) viewport. Default: "MOBILE" */
default: ScreenType;
/** Screen type for sm viewport. Default: "MOBILE" */
sm: ScreenType;
/** Screen type for md viewport. Default: "TABLET" */
md: ScreenType;
/** Screen type for lg viewport. Default: "DESKTOP" */
lg: ScreenType;
/** Screen type for xl viewport. Default: "DESKTOP" */
xl: ScreenType;
/** Screen type for 2xl viewport. Default: "DESKTOP" */
"2xl": ScreenType;
}
/**
* Configuration options for {@link useScreen}.
*/
export interface UseScreenOptions {
/**
* Custom breakpoint pixel thresholds.
* Partial object that merges with Tailwind v4 defaults.
*/
breakpoints?: Partial<ScreenBreakpoints>;
/**
* Custom mapping of breakpoints to screen types.
* Partial object that merges with defaults.
*/
screenTypeMapping?: Partial<ScreenTypeMapping>;
/**
* Default screen type returned during SSR and before detection.
* Defaults to `"UNKNOWN"`.
*/
defaultScreenType?: ScreenType;
/**
* Default Tailwind size returned during SSR and before detection.
* Defaults to `"default"`.
*/
defaultTailwindSize?: TailwindSize;
}
/**
* Shape returned by {@link useScreen}.
*
* The object reference is memoized with `useMemo` so it remains referentially
* stable across re-renders when values have not changed.
*/
export interface UseScreenResult {
/** Current viewport width in pixels. `0` during SSR. */
width: number;
/** Current viewport height in pixels. `0` during SSR. */
height: number;
/**
* Current Tailwind CSS breakpoint identifier.
* Determined by the largest matching `min-width` breakpoint.
*/
tailwindSize: TailwindSize;
/**
* Semantic screen type for layout decisions.
* Maps `tailwindSize` to a simplified classification.
*/
screenType: ScreenType;
/** Re-measure viewport dimensions on demand. */
refresh: () => void;
}
/**
* Track viewport dimensions and compute Tailwind breakpoint / screen type.
*
* Provides real-time viewport width and height, along with derived values for
* the current Tailwind CSS breakpoint (`tailwindSize`) and a semantic screen
* type classification (`screenType`) for layout decisions.
*
* ### SSR Safety
*
* The hook returns safe defaults during SSR (`width: 0`, `height: 0`,
* `tailwindSize: "default"`, `screenType: "UNKNOWN"`) to prevent hydration
* mismatches. After mount, values update to reflect the actual viewport.
*
* ### Performance
*
* - Uses `useMediaQuery` internally for efficient breakpoint detection via
* CSS media queries (no polling).
* - Viewport dimensions update on window `resize` events.
* - The returned object is memoized; values must change for the reference
* to update.
*
* @param options - Optional configuration. See {@link UseScreenOptions}.
* @returns A memoized {@link UseScreenResult} object.
*
* @example
* ```tsx
* import { useScreen } from "@opensite/hooks/useScreen";
*
* function ResponsiveLayout() {
* const { screenType, tailwindSize, width } = useScreen();
*
* return (
* <div>
* <p>Viewport: {width}px ({tailwindSize})</p>
* {screenType === "MOBILE" && <MobileNav />}
* {screenType === "TABLET" && <TabletNav />}
* {screenType === "DESKTOP" && <DesktopNav />}
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // With custom breakpoints
* const { screenType } = useScreen({
* breakpoints: { sm: 600, md: 900, lg: 1200, xl: 1400, "2xl": 1800 },
* });
* ```
*/
export declare function useScreen(options?: UseScreenOptions): UseScreenResult;
import { useCallback, useEffect, useMemo, useState } from "react";
import { useMediaQuery } from "./useMediaQuery.js";
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/**
* Tailwind CSS v4 default breakpoints (min-width values in pixels).
* @see https://tailwindcss.com/docs/responsive-design
*/
const DEFAULT_BREAKPOINTS = {
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
"2xl": 1536,
};
/**
* Default mapping of Tailwind breakpoints to semantic screen types.
*
* - default, sm → MOBILE
* - md → TABLET
* - lg, xl, 2xl → DESKTOP
*/
const DEFAULT_SCREEN_TYPE_MAPPING = {
default: "MOBILE",
sm: "MOBILE",
md: "TABLET",
lg: "DESKTOP",
xl: "DESKTOP",
"2xl": "DESKTOP",
};
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
* Get current viewport dimensions.
* Returns `{ width: 0, height: 0 }` during SSR.
*/
function getViewportDimensions() {
if (typeof window === "undefined") {
return { width: 0, height: 0 };
}
return {
width: window.innerWidth,
height: window.innerHeight,
};
}
/**
* Determine the current Tailwind breakpoint based on viewport width.
* Uses mobile-first logic: returns the largest breakpoint that matches.
*/
function calculateTailwindSize(width, breakpoints) {
// Check breakpoints from largest to smallest (mobile-first)
if (width >= breakpoints["2xl"])
return "2xl";
if (width >= breakpoints.xl)
return "xl";
if (width >= breakpoints.lg)
return "lg";
if (width >= breakpoints.md)
return "md";
if (width >= breakpoints.sm)
return "sm";
return "default";
}
// ---------------------------------------------------------------------------
// Hook
// ---------------------------------------------------------------------------
/**
* Track viewport dimensions and compute Tailwind breakpoint / screen type.
*
* Provides real-time viewport width and height, along with derived values for
* the current Tailwind CSS breakpoint (`tailwindSize`) and a semantic screen
* type classification (`screenType`) for layout decisions.
*
* ### SSR Safety
*
* The hook returns safe defaults during SSR (`width: 0`, `height: 0`,
* `tailwindSize: "default"`, `screenType: "UNKNOWN"`) to prevent hydration
* mismatches. After mount, values update to reflect the actual viewport.
*
* ### Performance
*
* - Uses `useMediaQuery` internally for efficient breakpoint detection via
* CSS media queries (no polling).
* - Viewport dimensions update on window `resize` events.
* - The returned object is memoized; values must change for the reference
* to update.
*
* @param options - Optional configuration. See {@link UseScreenOptions}.
* @returns A memoized {@link UseScreenResult} object.
*
* @example
* ```tsx
* import { useScreen } from "@opensite/hooks/useScreen";
*
* function ResponsiveLayout() {
* const { screenType, tailwindSize, width } = useScreen();
*
* return (
* <div>
* <p>Viewport: {width}px ({tailwindSize})</p>
* {screenType === "MOBILE" && <MobileNav />}
* {screenType === "TABLET" && <TabletNav />}
* {screenType === "DESKTOP" && <DesktopNav />}
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // With custom breakpoints
* const { screenType } = useScreen({
* breakpoints: { sm: 600, md: 900, lg: 1200, xl: 1400, "2xl": 1800 },
* });
* ```
*/
export function useScreen(options = {}) {
const { breakpoints: customBreakpoints, screenTypeMapping: customMapping, defaultScreenType = "UNKNOWN", defaultTailwindSize = "default", } = options;
// Merge custom config with defaults
const breakpoints = useMemo(() => ({ ...DEFAULT_BREAKPOINTS, ...customBreakpoints }), [customBreakpoints]);
const screenTypeMapping = useMemo(() => ({ ...DEFAULT_SCREEN_TYPE_MAPPING, ...customMapping }), [customMapping]);
// Track viewport dimensions
const [dimensions, setDimensions] = useState(() => getViewportDimensions());
// Use media queries for breakpoint detection (more reliable than width alone
// for edge cases and provides instant updates via matchMedia)
const isSm = useMediaQuery(`(min-width: ${breakpoints.sm}px)`);
const isMd = useMediaQuery(`(min-width: ${breakpoints.md}px)`);
const isLg = useMediaQuery(`(min-width: ${breakpoints.lg}px)`);
const isXl = useMediaQuery(`(min-width: ${breakpoints.xl}px)`);
const is2xl = useMediaQuery(`(min-width: ${breakpoints["2xl"]}px)`);
// Stable refresh callback
const refresh = useCallback(() => {
if (typeof window === "undefined")
return;
setDimensions(getViewportDimensions());
}, []);
// Subscribe to resize events for dimension tracking
useEffect(() => {
if (typeof window === "undefined")
return;
// Initial measurement
setDimensions(getViewportDimensions());
const handleResize = () => {
setDimensions(getViewportDimensions());
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
// Compute tailwindSize from media query results (most reliable)
const tailwindSize = useMemo(() => {
// During SSR, all media queries return false
if (!isSm && !isMd && !isLg && !isXl && !is2xl) {
// Check if we're on client with actual dimensions
if (dimensions.width > 0) {
return calculateTailwindSize(dimensions.width, breakpoints);
}
return defaultTailwindSize;
}
// Determine from media queries (largest matching breakpoint wins)
if (is2xl)
return "2xl";
if (isXl)
return "xl";
if (isLg)
return "lg";
if (isMd)
return "md";
if (isSm)
return "sm";
return "default";
}, [
isSm,
isMd,
isLg,
isXl,
is2xl,
dimensions.width,
breakpoints,
defaultTailwindSize,
]);
// Derive screen type from tailwind size
const screenType = useMemo(() => {
// During SSR before detection
if (tailwindSize === defaultTailwindSize && dimensions.width === 0) {
return defaultScreenType;
}
return screenTypeMapping[tailwindSize];
}, [
tailwindSize,
screenTypeMapping,
dimensions.width,
defaultScreenType,
defaultTailwindSize,
]);
// Memoize the return object for stable references
return useMemo(() => ({
width: dimensions.width,
height: dimensions.height,
tailwindSize,
screenType,
refresh,
}), [dimensions.width, dimensions.height, tailwindSize, screenType, refresh]);
}
+1
-1

@@ -1,2 +0,2 @@

!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).OpensiteHooks={},e.React)}(this,function(e,t){"use strict";const n="undefined"!=typeof window?t.useLayoutEffect:t.useEffect;function r(e,r,o={}){const i=t.useRef(e),u=t.useRef(null),c=t.useRef(null),s=t.useRef(null),a=o.leading??!1,l=o.trailing??!0,d=o.maxWait,m=Math.max(0,r);n(()=>{i.current=e},[e]);const f=t.useCallback(()=>{u.current&&(clearTimeout(u.current),u.current=null),c.current&&(clearTimeout(c.current),c.current=null)},[]),p=t.useCallback(()=>{if(!s.current)return;const e=s.current;s.current=null,i.current(...e)},[]),w=t.useCallback((...e)=>{s.current=e;if(a&&null===u.current&&null===c.current&&p(),u.current&&clearTimeout(u.current),l&&(u.current=setTimeout(()=>{u.current=null,s.current&&p(),c.current&&(clearTimeout(c.current),c.current=null)},m)),null!=d&&l&&!c.current){const e=Math.max(0,d);c.current=setTimeout(()=>{c.current=null,u.current&&(clearTimeout(u.current),u.current=null),s.current&&p()},e)}},[p,a,l,d,m]),b=t.useCallback(()=>{f(),s.current=null},[f]),g=t.useCallback(()=>{s.current&&(f(),p())},[f,p]);return t.useEffect(()=>()=>b(),[b]),{debouncedCallback:w,cancel:b,flush:g}}function o(e,n,o){const[i,u]=t.useState(e),{debouncedCallback:c,cancel:s}=r(e=>{u(e)},n,o);return t.useEffect(()=>{c(e)},[c,e]),t.useEffect(()=>()=>s(),[s]),i}function i(e){return(null==e?void 0:e.ownerDocument)??document}const u=new Map([["instagram.com","instagram"],["www.instagram.com","instagram"],["instagr.am","instagram"],["www.instagr.am","instagram"],["linkedin.com","linkedin"],["www.linkedin.com","linkedin"],["ca.linkedin.com","linkedin"],["uk.linkedin.com","linkedin"],["in.linkedin.com","linkedin"],["lnkd.in","linkedin"],["google.com","google"],["www.google.com","google"],["maps.google.com","google"],["goo.gl","google"],["maps.app.goo.gl","google"],["g.co","google"],["facebook.com","facebook"],["www.facebook.com","facebook"],["m.facebook.com","facebook"],["fb.com","facebook"],["fb.me","facebook"],["on.fb.me","facebook"],["tiktok.com","tiktok"],["www.tiktok.com","tiktok"],["m.tiktok.com","tiktok"],["vm.tiktok.com","tiktok"],["vt.tiktok.com","tiktok"],["youtube.com","youtube"],["www.youtube.com","youtube"],["m.youtube.com","youtube"],["youtu.be","youtube"],["yelp.com","yelp"],["www.yelp.com","yelp"],["m.yelp.com","yelp"],["spotify.com","spotify"],["www.spotify.com","spotify"],["open.spotify.com","spotify"],["play.spotify.com","spotify"],["spoti.fi","spotify"],["spotify.link","spotify"],["apple.com","apple"],["www.apple.com","apple"],["music.apple.com","apple"],["podcasts.apple.com","apple"],["apps.apple.com","apple"],["itunes.apple.com","apple"],["x.com","x"],["www.x.com","x"],["twitter.com","x"],["www.twitter.com","x"],["t.co","x"],["github.com","github"],["www.github.com","github"],["gist.github.com","github"],["raw.githubusercontent.com","github"],["github.io","github"],["discord.com","discord"],["www.discord.com","discord"],["discord.gg","discord"],["discordapp.com","discord"],["discordapp.net","discord"],["discord.new","discord"],["discord.gift","discord"],["discord.gifts","discord"],["dis.gd","discord"],["snapchat.com","snapchat"],["www.snapchat.com","snapchat"],["snap.com","snapchat"],["www.snap.com","snapchat"],["story.snapchat.com","snapchat"],["web.snapchat.com","snapchat"],["dev.to","dev"],["www.dev.to","dev"],["substack.com","substack"],["www.substack.com","substack"],["reddit.com","reddit"],["www.reddit.com","reddit"],["old.reddit.com","reddit"],["new.reddit.com","reddit"],["i.redd.it","reddit"],["v.redd.it","reddit"],["redd.it","reddit"],["preview.redd.it","reddit"],["pinterest.com","pinterest"],["www.pinterest.com","pinterest"],["pin.it","pinterest"],["in.pinterest.com","pinterest"],["br.pinterest.com","pinterest"],["uk.pinterest.com","pinterest"],["threads.net","threads"],["www.threads.net","threads"],["threads.com","threads"],["www.threads.com","threads"],["twitch.tv","twitch"],["www.twitch.tv","twitch"],["m.twitch.tv","twitch"],["whatsapp.com","whatsapp"],["www.whatsapp.com","whatsapp"],["wa.me","whatsapp"],["web.whatsapp.com","whatsapp"],["telegram.org","telegram"],["www.telegram.org","telegram"],["t.me","telegram"],["telegram.me","telegram"],["telegram.dog","telegram"],["medium.com","medium"],["www.medium.com","medium"],["patreon.com","patreon"],["www.patreon.com","patreon"],["onlyfans.com","onlyfans"],["www.onlyfans.com","onlyfans"],["eventbrite.com","eventbrite"],["www.eventbrite.com","eventbrite"],["eventbrite.co.uk","eventbrite"],["eventbrite.com.au","eventbrite"],["eventbrite.ca","eventbrite"],["eventbrite.de","eventbrite"],["eventbrite.fr","eventbrite"],["eventbrite.es","eventbrite"],["eventbrite.it","eventbrite"],["eventbrite.ie","eventbrite"],["eventbrite.nl","eventbrite"],["eventbrite.co.nz","eventbrite"],["eventbriteapi.com","eventbrite"],["evbuc.com","eventbrite"],["npmjs.com","npmjs"],["www.npmjs.com","npmjs"],["npmjs.org","npmjs"],["www.npmjs.org","npmjs"],["registry.npmjs.org","npmjs"],["registry.npmjs.com","npmjs"],["replicate.npmjs.com","npmjs"],["crates.io","crates"],["www.crates.io","crates"],["rubygems.org","rubygems"],["www.rubygems.org","rubygems"],["behance.net","behance"],["www.behance.net","behance"],["portfolio.behance.net","behance"],["mir-s3-cdn-cf.behance.net","behance"],["dribbble.com","dribbble"],["www.dribbble.com","dribbble"],["drbl.in","dribbble"]]);function c(e,r,o,i){const u=t.useRef(r);n(()=>{u.current=r},[r]),t.useEffect(()=>{const t="undefined"!=typeof Window&&o instanceof Window,n="undefined"!=typeof Document&&o instanceof Document,r=void 0===o?"undefined"!=typeof window?window:null:t||n||"undefined"!=typeof HTMLElement&&o instanceof HTMLElement?o:(c=o)&&"object"==typeof c&&"current"in c?o.current:null;var c;if(!(null==r?void 0:r.addEventListener))return;const s=e=>{const t=u.current;"function"==typeof t?t(e):t.handleEvent(e)};return r.addEventListener(e,s,i),()=>{r.removeEventListener(e,s,i)}},[e,o,i])}function s(){const[e,n]=t.useState(!1);return t.useEffect(()=>{n(!0)},[]),e}function a(e){const[r,o]=t.useState(()=>e instanceof Map||Array.isArray(e)?new Map(e):new Map),i=t.useRef(r);n(()=>{i.current=r},[r]);const u=t.useMemo(()=>({set:(e,t)=>{o(n=>{const r=new Map(n);return r.set(e,t),r})},setAll:e=>{o((Map,new Map(e)))},remove:e=>{o(t=>{const n=new Map(t);return n.delete(e),n})},clear:()=>o(new Map),get:e=>i.current.get(e),has:e=>i.current.has(e)}),[]);return[r,u]}const l="https://octane.buzz";function d(e){const t=function(e){return e.replace(/\/+$/,"")}(e.baseUrl??l),n=new URLSearchParams;return e.apiKey&&n.set("api_key",e.apiKey),n.set("url",e.url),`${t}/api/v1/extract/${e.endpoint}?${n.toString()}`}async function m(e){var t;if(!e.url||0===e.url.trim().length)return{ok:!1,error:{message:"URL is required."}};const n=d(e);try{const t=await fetch(n,{method:"GET",signal:e.signal});let r=null;try{r=await t.json()}catch{r=null}if(!t.ok){const e=r;return{ok:!1,error:{message:(null==e?void 0:e.error)??`Request failed with status ${t.status}.`,status:(null==e?void 0:e.status)??t.status,raw:r}}}return{ok:!0,response:r}}catch(r){return(null==(t=e.signal)?void 0:t.aborted)?{ok:!1,error:{message:"Request aborted."}}:{ok:!1,error:{message:r instanceof Error?r.message:"Request failed unexpectedly.",raw:r}}}}function f(e){const{endpoint:n,options:i,selectData:u,shouldSkip:c}=e,d=s(),[f,p]=t.useState({loading:!1}),[,w]=a(),b=i.cache??true,g=i.enabled??true,v=i.debounceMs??250,y=i.refreshDebounceMs??150,h=o(t.useMemo(()=>{var e;return(null==(e=i.url)?void 0:e.trim())??""},[i.url]),v),k=t.useRef(0),E=t.useRef(0),[C,T]=t.useState(0),{debouncedCallback:x,cancel:S}=r(()=>{k.current+=1,T(k.current)},y),M=t.useCallback(()=>{x()},[x]);t.useEffect(()=>()=>S(),[S]);const R=t.useMemo(()=>{if(!h)return"";const e=i.baseUrl??l,t=i.apiKey??"";return`${n}:${e}:${t}:${h}`},[n,i.apiKey,i.baseUrl,h]),L=t.useRef(null);return t.useEffect(()=>{var e;if(!d)return;if(!g||!h)return void p({loading:!1});if(null==c?void 0:c(h))return void p({loading:!1});const t=C!==E.current;if(t&&(E.current=C),b&&!t){const e=w.get(R);if(e)return void p({loading:!1,data:e.data,raw:e.raw,meta:e.meta})}null==(e=L.current)||e.abort();const r=new AbortController;return L.current=r,p(e=>({...e,loading:!0,error:void 0})),m({endpoint:n,url:h,apiKey:i.apiKey,baseUrl:i.baseUrl,signal:r.signal}).then(e=>{if(r.signal.aborted)return;if(!e.ok)return void p(t=>({...t,loading:!1,error:e.error}));const t=e.response,n=function(e){const{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d}=e;return{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d}}(t),o=function(e){const{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d,...m}=e;return m}(t),i=u(o,t,n);b&&w.set(R,{data:i,raw:t,meta:n}),p({loading:!1,data:i,raw:t,meta:n})}).catch(e=>{r.signal.aborted||p(t=>({...t,loading:!1,error:{message:e instanceof Error?e.message:"Request failed unexpectedly.",raw:e}}))}),()=>{r.abort()}},[w,b,h,g,n,d,i.apiKey,i.baseUrl,R,C,u,c]),t.useMemo(()=>({...f,refresh:M}),[M,f])}const p=[/search\.google\.com\/local\/reviews/i,/google\.com\/maps\/place/i,/maps\.google\.com/i,/opentable\.com/i],w=(...e)=>{for(const t of e)if("string"==typeof t&&t.trim().length>0)return t},b=e=>{if(e)try{return new URL(e).hostname}catch{return}};e.buildWebsiteExtractorUrl=d,e.fetchWebsiteExtractor=m,e.useBoolean=function(e=!1){const[n,r]=t.useState(e),o=t.useCallback(()=>r(!0),[]),i=t.useCallback(()=>r(!1),[]),u=t.useCallback(()=>r(e=>!e),[]);return t.useMemo(()=>({value:n,setValue:r,setTrue:o,setFalse:i,toggle:u}),[n,r,o,i,u])},e.useCopyToClipboard=function(e={}){const n=e.resetDelay??2e3,r=t.useRef(null),[o,i]=t.useState(null),u=t.useMemo(()=>!("undefined"==typeof navigator||!navigator.clipboard)||"undefined"!=typeof document&&("function"==typeof document.queryCommandSupported&&document.queryCommandSupported("copy")),[]),c=t.useCallback(()=>{r.current&&clearTimeout(r.current),r.current=setTimeout(()=>{i(null)},n)},[n]),s=t.useCallback(async e=>{if(!u)return!1;const t="undefined"!=typeof navigator&&!!navigator.clipboard;try{if(t)await navigator.clipboard.writeText(e);else if("undefined"!=typeof document){const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="fixed",t.style.left="-9999px",t.style.top="0",document.body.appendChild(t),t.focus(),t.select();const n=document.execCommand("copy");if(document.body.removeChild(t),!n)return!1}return i(e),c(),!0}catch{return!1}},[u,c]);return t.useEffect(()=>()=>{r.current&&clearTimeout(r.current)},[]),{copy:s,copiedText:o,isSupported:u}},e.useDebounceCallback=r,e.useDebounceValue=o,e.useEventListener=c,e.useHover=function(e){const[n,r]=t.useState(!1),o=t.useCallback(()=>{r(!0)},[]),i=t.useCallback(()=>{r(!1)},[]);return c("pointerenter",o,e),c("pointerleave",i,e),n},e.useIsClient=s,e.useIsomorphicLayoutEffect=n,e.useLocalStorage=function(e,n,r={}){const{initializeWithValue:o=!0,serialize:i=JSON.stringify,deserialize:u=JSON.parse,listenToStorageChanges:c=!0}=r,s=t.useRef(n),a=t.useCallback(()=>{if("undefined"==typeof window)return s.current;if(!o)return s.current;try{const t=window.localStorage.getItem(e);return t?u(t):s.current}catch{return s.current}},[u,o,e]),[l,d]=t.useState(()=>a()),m=t.useCallback(t=>{d(n=>{const r="function"==typeof t?t(n):t;if("undefined"!=typeof window)try{window.localStorage.setItem(e,i(r))}catch{}return r})},[e,i]);return t.useEffect(()=>{d(a())},[a]),t.useEffect(()=>{if("undefined"==typeof window||!c)return;const t=t=>{if(t.key===e)if(null!==t.newValue)try{d(u(t.newValue))}catch{d(s.current)}else d(s.current)};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)},[u,e,c]),[l,m]},e.useMap=a,e.useMediaQuery=function(e,n={}){const[r,o]=t.useState(()=>"undefined"==typeof window||"function"!=typeof window.matchMedia?n.defaultValue??!1:window.matchMedia(e).matches);return t.useEffect(()=>{if("undefined"==typeof window||"function"!=typeof window.matchMedia)return;const t=window.matchMedia(e),n=e=>{o(e.matches)};return o(t.matches),t.addEventListener?(t.addEventListener("change",n),()=>t.removeEventListener("change",n)):(t.addListener(n),()=>t.removeListener(n))},[e]),r},e.useOnClickOutside=function(e,r,o,u){const c=t.useRef(r);n(()=>{c.current=r},[r]),t.useEffect(()=>{if("undefined"==typeof document)return;const t="undefined"!=typeof window&&void 0!==window.PointerEvent,n=o??(t?"pointerdown":"mousedown"),r=Array.isArray(e)?e:[e];let s=document;for(const e of r)if(e.current){const t=i(e.current);if(t!==document){s=t;break}}const a=e=>{const t=e.target;if("undefined"==typeof Node||!(t instanceof Node))return;r.some(e=>{const n=e.current;return!!n&&n.contains(t)})||c.current(e)};return s.addEventListener(n,a,u),()=>{s.removeEventListener(n,a,u)}},[o,u,e])},e.useOpenGraphExtractor=function(e){const n=t.useMemo(()=>e.skipPatterns??p,[e.skipPatterns]),r=t.useCallback(e=>n.some(t=>t.test(e)),[n]),o=t.useCallback((e,t,n)=>{var r,o,i;const{openGraph:u,htmlInferred:c,hybridGraph:s}=e,a=w(null==u?void 0:u.description,null==s?void 0:s.description,null==c?void 0:c.description),l=w(null==u?void 0:u.title,null==s?void 0:s.title,null==c?void 0:c.title),d=w(null==u?void 0:u.site_name,null==s?void 0:s.site_name,null==c?void 0:c.site_name),m=w(null==s?void 0:s.favicon,null==c?void 0:c.favicon),f=w((null==(r=null==u?void 0:u.image)?void 0:r.url)??void 0,(null==s?void 0:s.image)??void 0,(null==c?void 0:c.image)??void 0,null==(o=null==c?void 0:c.images)?void 0:o[0]),p=w((null==(i=null==u?void 0:u.video)?void 0:i.url)??void 0,(null==s?void 0:s.video)??void 0),g=w(null==s?void 0:s.videoType,null==c?void 0:c.videoType),v=w(n.url,(null==u?void 0:u.url)??void 0,(null==s?void 0:s.url)??void 0,(null==c?void 0:c.url)??void 0,n.finalUrl,n.normalizedUrl,n.requestedUrl)??"";return{description:a,favicon:m,image:f,video:p,videoType:g,siteName:d,title:l,url:v,siteHost:b(v)}},[]);return f({endpoint:"open-graph",options:e,selectData:o,shouldSkip:r})},e.usePlatformFromUrl=function(e){return t.useMemo(()=>{if(!e||"string"!=typeof e)return"unknown";const t=e.trim();if(!t)return"unknown";try{const e=new URL(t).hostname.toLowerCase(),n=u.get(e);return n||(e.endsWith(".substack.com")?"substack":e.endsWith(".github.io")?"github":e.includes("pinterest.com")?"pinterest":e.includes("eventbrite.")?"eventbrite":e.endsWith(".medium.com")?"medium":e.endsWith(".behance.net")?"behance":"unknown")}catch{return"unknown"}},[e])},e.usePrevious=function(e){const r=t.useRef();return n(()=>{r.current=e},[e]),r.current},e.useResizeObserver=function(e,r,o){const i=t.useRef(r),u=t.useRef(null),[c,s]=t.useState(null);return n(()=>{i.current=r},[r]),t.useEffect(()=>{if("undefined"==typeof ResizeObserver)return;const t="undefined"!=typeof Element&&e instanceof Element?e:(n=e)&&"object"==typeof n&&"current"in n?e.current:null;var n;if(!t)return;const r=new ResizeObserver(e=>{const t=e[0];u.current=t,i.current?i.current(t):s(t)});return r.observe(t,o),()=>r.disconnect()},[o,e]),i.current?u.current:c},e.useSessionStorage=function(e,n,r={}){const{initializeWithValue:o=!0,serialize:i=JSON.stringify,deserialize:u=JSON.parse,listenToStorageChanges:c=!1}=r,s=t.useRef(n),a=t.useCallback(()=>{if("undefined"==typeof window)return s.current;if(!o)return s.current;try{const t=window.sessionStorage.getItem(e);return t?u(t):s.current}catch{return s.current}},[u,o,e]),[l,d]=t.useState(()=>a()),m=t.useCallback(t=>{d(n=>{const r="function"==typeof t?t(n):t;if("undefined"!=typeof window)try{window.sessionStorage.setItem(e,i(r))}catch{}return r})},[e,i]);return t.useEffect(()=>{d(a())},[a]),t.useEffect(()=>{if("undefined"==typeof window||!c)return;const t=t=>{if(t.key===e)if(null!==t.newValue)try{d(u(t.newValue))}catch{d(s.current)}else d(s.current)};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)},[u,e,c]),[l,m]},e.useThrottle=function(e,n,r={}){const o=r.leading??!0,i=r.trailing??!0,u=Math.max(0,n),[c,s]=t.useState(e),a=t.useRef(0),l=t.useRef(null),d=t.useRef(null);return t.useEffect(()=>{if(0===u)return void s(e);const t=Date.now();if(0===a.current)return a.current=t,o?void s(e):void(i&&!l.current&&(d.current=e,l.current=setTimeout(()=>{l.current=null,null!==d.current&&(s(d.current),d.current=null,a.current=Date.now())},u)));const n=t-a.current;if(n>=u&&o)return s(e),a.current=t,void(d.current=null);if(i&&(d.current=e,!l.current)){const e=Math.max(u-n,0);l.current=setTimeout(()=>{l.current=null,null!==d.current&&(s(d.current),d.current=null,a.current=Date.now())},e)}},[o,i,e,u]),t.useEffect(()=>()=>{l.current&&clearTimeout(l.current)},[]),c},e.useWebsiteLinksExtractor=function(e){return f({endpoint:"links",options:e,selectData:t.useCallback(e=>({totalLinks:e.totalLinks??0,uniqueDomains:e.uniqueDomains??0,links:e.links??[]}),[])})},e.useWebsiteMetaExtractor=function(e){return f({endpoint:"meta",options:e,selectData:t.useCallback(e=>({title:e.title??void 0,description:e.description??void 0,language:e.language??void 0,canonicalUrl:e.canonicalUrl??void 0,feedUrl:e.feedUrl??null,textContentLength:e.textContentLength??void 0,metaTags:e.metaTags??{}}),[])})},e.useWebsiteRssExtractor=function(e){return f({endpoint:"rss",options:e,selectData:t.useCallback(e=>({feedUrl:e.feedUrl??null,feeds:e.feeds??[]}),[])})},e.useWebsiteSchemaExtractor=function(e){return f({endpoint:"schema",options:e,selectData:t.useCallback(e=>({schema:e.schema??[],schemaTypes:e.schemaTypes??[]}),[])})},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).OpensiteHooks={},e.React)}(this,function(e,t){"use strict";const n="undefined"!=typeof window?t.useLayoutEffect:t.useEffect;function r(e,r,o={}){const i=t.useRef(e),u=t.useRef(null),c=t.useRef(null),s=t.useRef(null),a=o.leading??!1,l=o.trailing??!0,d=o.maxWait,f=Math.max(0,r);n(()=>{i.current=e},[e]);const m=t.useCallback(()=>{u.current&&(clearTimeout(u.current),u.current=null),c.current&&(clearTimeout(c.current),c.current=null)},[]),p=t.useCallback(()=>{if(!s.current)return;const e=s.current;s.current=null,i.current(...e)},[]),w=t.useCallback((...e)=>{s.current=e;if(a&&null===u.current&&null===c.current&&p(),u.current&&clearTimeout(u.current),l&&(u.current=setTimeout(()=>{u.current=null,s.current&&p(),c.current&&(clearTimeout(c.current),c.current=null)},f)),null!=d&&l&&!c.current){const e=Math.max(0,d);c.current=setTimeout(()=>{c.current=null,u.current&&(clearTimeout(u.current),u.current=null),s.current&&p()},e)}},[p,a,l,d,f]),b=t.useCallback(()=>{m(),s.current=null},[m]),h=t.useCallback(()=>{s.current&&(m(),p())},[m,p]);return t.useEffect(()=>()=>b(),[b]),{debouncedCallback:w,cancel:b,flush:h}}function o(e,n,o){const[i,u]=t.useState(e),{debouncedCallback:c,cancel:s}=r(e=>{u(e)},n,o);return t.useEffect(()=>{c(e)},[c,e]),t.useEffect(()=>()=>s(),[s]),i}function i(e){return(null==e?void 0:e.ownerDocument)??document}function u(e,n={}){const[r,o]=t.useState(()=>"undefined"==typeof window||"function"!=typeof window.matchMedia?n.defaultValue??!1:window.matchMedia(e).matches);return t.useEffect(()=>{if("undefined"==typeof window||"function"!=typeof window.matchMedia)return;const t=window.matchMedia(e),n=e=>{o(e.matches)};return o(t.matches),t.addEventListener("change",n),()=>t.removeEventListener("change",n)},[e]),r}const c=new Map([["instagram.com","instagram"],["www.instagram.com","instagram"],["instagr.am","instagram"],["www.instagr.am","instagram"],["linkedin.com","linkedin"],["www.linkedin.com","linkedin"],["ca.linkedin.com","linkedin"],["uk.linkedin.com","linkedin"],["in.linkedin.com","linkedin"],["lnkd.in","linkedin"],["google.com","google"],["www.google.com","google"],["maps.google.com","google"],["goo.gl","google"],["maps.app.goo.gl","google"],["g.co","google"],["facebook.com","facebook"],["www.facebook.com","facebook"],["m.facebook.com","facebook"],["fb.com","facebook"],["fb.me","facebook"],["on.fb.me","facebook"],["tiktok.com","tiktok"],["www.tiktok.com","tiktok"],["m.tiktok.com","tiktok"],["vm.tiktok.com","tiktok"],["vt.tiktok.com","tiktok"],["youtube.com","youtube"],["www.youtube.com","youtube"],["m.youtube.com","youtube"],["youtu.be","youtube"],["yelp.com","yelp"],["www.yelp.com","yelp"],["m.yelp.com","yelp"],["spotify.com","spotify"],["www.spotify.com","spotify"],["open.spotify.com","spotify"],["play.spotify.com","spotify"],["spoti.fi","spotify"],["spotify.link","spotify"],["apple.com","apple"],["www.apple.com","apple"],["music.apple.com","apple"],["podcasts.apple.com","apple"],["apps.apple.com","apple"],["itunes.apple.com","apple"],["x.com","x"],["www.x.com","x"],["twitter.com","x"],["www.twitter.com","x"],["t.co","x"],["github.com","github"],["www.github.com","github"],["gist.github.com","github"],["raw.githubusercontent.com","github"],["github.io","github"],["discord.com","discord"],["www.discord.com","discord"],["discord.gg","discord"],["discordapp.com","discord"],["discordapp.net","discord"],["discord.new","discord"],["discord.gift","discord"],["discord.gifts","discord"],["dis.gd","discord"],["snapchat.com","snapchat"],["www.snapchat.com","snapchat"],["snap.com","snapchat"],["www.snap.com","snapchat"],["story.snapchat.com","snapchat"],["web.snapchat.com","snapchat"],["dev.to","dev"],["www.dev.to","dev"],["substack.com","substack"],["www.substack.com","substack"],["reddit.com","reddit"],["www.reddit.com","reddit"],["old.reddit.com","reddit"],["new.reddit.com","reddit"],["i.redd.it","reddit"],["v.redd.it","reddit"],["redd.it","reddit"],["preview.redd.it","reddit"],["pinterest.com","pinterest"],["www.pinterest.com","pinterest"],["pin.it","pinterest"],["in.pinterest.com","pinterest"],["br.pinterest.com","pinterest"],["uk.pinterest.com","pinterest"],["threads.net","threads"],["www.threads.net","threads"],["threads.com","threads"],["www.threads.com","threads"],["twitch.tv","twitch"],["www.twitch.tv","twitch"],["m.twitch.tv","twitch"],["whatsapp.com","whatsapp"],["www.whatsapp.com","whatsapp"],["wa.me","whatsapp"],["web.whatsapp.com","whatsapp"],["telegram.org","telegram"],["www.telegram.org","telegram"],["t.me","telegram"],["telegram.me","telegram"],["telegram.dog","telegram"],["medium.com","medium"],["www.medium.com","medium"],["patreon.com","patreon"],["www.patreon.com","patreon"],["onlyfans.com","onlyfans"],["www.onlyfans.com","onlyfans"],["eventbrite.com","eventbrite"],["www.eventbrite.com","eventbrite"],["eventbrite.co.uk","eventbrite"],["eventbrite.com.au","eventbrite"],["eventbrite.ca","eventbrite"],["eventbrite.de","eventbrite"],["eventbrite.fr","eventbrite"],["eventbrite.es","eventbrite"],["eventbrite.it","eventbrite"],["eventbrite.ie","eventbrite"],["eventbrite.nl","eventbrite"],["eventbrite.co.nz","eventbrite"],["eventbriteapi.com","eventbrite"],["evbuc.com","eventbrite"],["npmjs.com","npmjs"],["www.npmjs.com","npmjs"],["npmjs.org","npmjs"],["www.npmjs.org","npmjs"],["registry.npmjs.org","npmjs"],["registry.npmjs.com","npmjs"],["replicate.npmjs.com","npmjs"],["crates.io","crates"],["www.crates.io","crates"],["rubygems.org","rubygems"],["www.rubygems.org","rubygems"],["behance.net","behance"],["www.behance.net","behance"],["portfolio.behance.net","behance"],["mir-s3-cdn-cf.behance.net","behance"],["dribbble.com","dribbble"],["www.dribbble.com","dribbble"],["drbl.in","dribbble"]]);function s(e,r,o,i){const u=t.useRef(r);n(()=>{u.current=r},[r]),t.useEffect(()=>{const t="undefined"!=typeof Window&&o instanceof Window,n="undefined"!=typeof Document&&o instanceof Document,r=void 0===o?"undefined"!=typeof window?window:null:t||n||"undefined"!=typeof HTMLElement&&o instanceof HTMLElement?o:(c=o)&&"object"==typeof c&&"current"in c?o.current:null;var c;if(!(null==r?void 0:r.addEventListener))return;const s=e=>{const t=u.current;"function"==typeof t?t(e):t.handleEvent(e)};return r.addEventListener(e,s,i),()=>{r.removeEventListener(e,s,i)}},[e,o,i])}function a(){const[e,n]=t.useState(!1);return t.useEffect(()=>{n(!0)},[]),e}function l(e){const[r,o]=t.useState(()=>e instanceof Map||Array.isArray(e)?new Map(e):new Map),i=t.useRef(r);n(()=>{i.current=r},[r]);const u=t.useMemo(()=>({set:(e,t)=>{o(n=>{const r=new Map(n);return r.set(e,t),r})},setAll:e=>{o((Map,new Map(e)))},remove:e=>{o(t=>{const n=new Map(t);return n.delete(e),n})},clear:()=>o(new Map),get:e=>i.current.get(e),has:e=>i.current.has(e)}),[]);return[r,u]}const d="https://octane.buzz";function f(e){const t=function(e){return e.replace(/\/+$/,"")}(e.baseUrl??d),n=new URLSearchParams;return e.apiKey&&n.set("api_key",e.apiKey),n.set("url",e.url),`${t}/api/v1/extract/${e.endpoint}?${n.toString()}`}async function m(e){var t;if(!e.url||0===e.url.trim().length)return{ok:!1,error:{message:"URL is required."}};const n=f(e);try{const t=await fetch(n,{method:"GET",signal:e.signal});let r=null;try{r=await t.json()}catch{r=null}if(!t.ok){const e=r;return{ok:!1,error:{message:(null==e?void 0:e.error)??`Request failed with status ${t.status}.`,status:(null==e?void 0:e.status)??t.status,raw:r}}}return{ok:!0,response:r}}catch(r){return(null==(t=e.signal)?void 0:t.aborted)?{ok:!1,error:{message:"Request aborted."}}:{ok:!1,error:{message:r instanceof Error?r.message:"Request failed unexpectedly.",raw:r}}}}function p(e){const{endpoint:n,options:i,selectData:u,shouldSkip:c}=e,s=a(),[f,p]=t.useState({loading:!1}),[,w]=l(),b=i.cache??true,h=i.enabled??true,g=i.debounceMs??250,v=i.refreshDebounceMs??150,y=o(t.useMemo(()=>{var e;return(null==(e=i.url)?void 0:e.trim())??""},[i.url]),g),k=t.useRef(0),E=t.useRef(0),[x,T]=t.useState(0),{debouncedCallback:M,cancel:S}=r(()=>{k.current+=1,T(k.current)},v),C=t.useCallback(()=>{M()},[M]);t.useEffect(()=>()=>S(),[S]);const L=t.useMemo(()=>{if(!y)return"";const e=i.baseUrl??d,t=i.apiKey??"";return`${n}:${e}:${t}:${y}`},[n,i.apiKey,i.baseUrl,y]),R=t.useRef(null);return t.useEffect(()=>{var e;if(!s)return;if(!h||!y)return void p({loading:!1});if(null==c?void 0:c(y))return void p({loading:!1});const t=x!==E.current;if(t&&(E.current=x),b&&!t){const e=w.get(L);if(e)return void p({loading:!1,data:e.data,raw:e.raw,meta:e.meta})}null==(e=R.current)||e.abort();const r=new AbortController;return R.current=r,p(e=>({...e,loading:!0,error:void 0})),m({endpoint:n,url:y,apiKey:i.apiKey,baseUrl:i.baseUrl,signal:r.signal}).then(e=>{if(r.signal.aborted)return;if(!e.ok)return void p(t=>({...t,loading:!1,error:e.error}));const t=e.response,n=function(e){const{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d}=e;return{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d}}(t),o=function(e){const{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d,...f}=e;return f}(t),i=u(o,t,n);b&&w.set(L,{data:i,raw:t,meta:n}),p({loading:!1,data:i,raw:t,meta:n})}).catch(e=>{r.signal.aborted||p(t=>({...t,loading:!1,error:{message:e instanceof Error?e.message:"Request failed unexpectedly.",raw:e}}))}),()=>{r.abort()}},[w,b,y,h,n,s,i.apiKey,i.baseUrl,L,x,u,c]),t.useMemo(()=>({...f,refresh:C}),[C,f])}const w=[/search\.google\.com\/local\/reviews/i,/google\.com\/maps\/place/i,/maps\.google\.com/i,/opentable\.com/i],b=(...e)=>{for(const t of e)if("string"==typeof t&&t.trim().length>0)return t},h=e=>{if(e)try{return new URL(e).hostname}catch{return}};const g="(hover: none) and (pointer: coarse)";function v(){if("function"==typeof window.matchMedia){const e=window.matchMedia(g);if("not all"!==e.media)return e.matches?"touch":"desktop"}return"ontouchstart"in window||"undefined"!=typeof navigator&&"number"==typeof navigator.maxTouchPoints&&navigator.maxTouchPoints>0?"touch":"desktop"}const y={sm:640,md:768,lg:1024,xl:1280,"2xl":1536},k={default:"MOBILE",sm:"MOBILE",md:"TABLET",lg:"DESKTOP",xl:"DESKTOP","2xl":"DESKTOP"};function E(){return"undefined"==typeof window?{width:0,height:0}:{width:window.innerWidth,height:window.innerHeight}}e.buildWebsiteExtractorUrl=f,e.fetchWebsiteExtractor=m,e.useBoolean=function(e=!1){const[n,r]=t.useState(e),o=t.useCallback(()=>r(!0),[]),i=t.useCallback(()=>r(!1),[]),u=t.useCallback(()=>r(e=>!e),[]);return t.useMemo(()=>({value:n,setValue:r,setTrue:o,setFalse:i,toggle:u}),[n,r,o,i,u])},e.useCopyToClipboard=function(e={}){const n=e.resetDelay??2e3,r=t.useRef(null),[o,i]=t.useState(null),u=t.useMemo(()=>!("undefined"==typeof navigator||!navigator.clipboard)||"undefined"!=typeof document&&("function"==typeof document.queryCommandSupported&&document.queryCommandSupported("copy")),[]),c=t.useCallback(()=>{r.current&&clearTimeout(r.current),r.current=setTimeout(()=>{i(null)},n)},[n]),s=t.useCallback(async e=>{if(!u)return!1;const t="undefined"!=typeof navigator&&!!navigator.clipboard;try{if(t)await navigator.clipboard.writeText(e);else if("undefined"!=typeof document){const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="fixed",t.style.left="-9999px",t.style.top="0",document.body.appendChild(t),t.focus(),t.select();const n=document.execCommand("copy");if(document.body.removeChild(t),!n)return!1}return i(e),c(),!0}catch{return!1}},[u,c]);return t.useEffect(()=>()=>{r.current&&clearTimeout(r.current)},[]),{copy:s,copiedText:o,isSupported:u}},e.useDebounceCallback=r,e.useDebounceValue=o,e.useEventListener=s,e.useHover=function(e){const[n,r]=t.useState(!1),o=t.useCallback(()=>{r(!0)},[]),i=t.useCallback(()=>{r(!1)},[]);return s("pointerenter",o,e),s("pointerleave",i,e),n},e.useIsClient=a,e.useIsTouchDevice=function(e={}){const{defaultDeviceType:n="unknown"}=e,[r,o]=t.useState(n),i=t.useCallback(()=>{"undefined"!=typeof window&&o(v())},[]);return t.useEffect(()=>{if("undefined"==typeof window)return;if(o(v()),"function"!=typeof window.matchMedia)return;const e=window.matchMedia(g);if("not all"===e.media)return;const t=e=>{o(e.matches?"touch":"desktop")};return e.addEventListener("change",t),()=>{e.removeEventListener("change",t)}},[]),t.useMemo(()=>({deviceType:r,isTouchDevice:"touch"===r,recheck:i}),[r,i])},e.useIsomorphicLayoutEffect=n,e.useLocalStorage=function(e,n,r={}){const{initializeWithValue:o=!0,serialize:i=JSON.stringify,deserialize:u=JSON.parse,listenToStorageChanges:c=!0}=r,s=t.useRef(n),a=t.useCallback(()=>{if("undefined"==typeof window)return s.current;if(!o)return s.current;try{const t=window.localStorage.getItem(e);return t?u(t):s.current}catch{return s.current}},[u,o,e]),[l,d]=t.useState(()=>a()),f=t.useCallback(t=>{d(n=>{const r="function"==typeof t?t(n):t;if("undefined"!=typeof window)try{window.localStorage.setItem(e,i(r))}catch{}return r})},[e,i]);return t.useEffect(()=>{d(a())},[a]),t.useEffect(()=>{if("undefined"==typeof window||!c)return;const t=t=>{if(t.key===e)if(null!==t.newValue)try{d(u(t.newValue))}catch{d(s.current)}else d(s.current)};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)},[u,e,c]),[l,f]},e.useMap=l,e.useMediaQuery=u,e.useOnClickOutside=function(e,r,o,u){const c=t.useRef(r);n(()=>{c.current=r},[r]),t.useEffect(()=>{if("undefined"==typeof document)return;const t="undefined"!=typeof window&&void 0!==window.PointerEvent,n=o??(t?"pointerdown":"mousedown"),r=Array.isArray(e)?e:[e];let s=document;for(const e of r)if(e.current){const t=i(e.current);if(t!==document){s=t;break}}const a=e=>{const t=e.target;if("undefined"==typeof Node||!(t instanceof Node))return;r.some(e=>{const n=e.current;return!!n&&n.contains(t)})||c.current(e)};return s.addEventListener(n,a,u),()=>{s.removeEventListener(n,a,u)}},[o,u,e])},e.useOpenGraphExtractor=function(e){const n=t.useMemo(()=>e.skipPatterns??w,[e.skipPatterns]),r=t.useCallback(e=>n.some(t=>t.test(e)),[n]),o=t.useCallback((e,t,n)=>{var r,o,i;const{openGraph:u,htmlInferred:c,hybridGraph:s}=e,a=b(null==u?void 0:u.description,null==s?void 0:s.description,null==c?void 0:c.description),l=b(null==u?void 0:u.title,null==s?void 0:s.title,null==c?void 0:c.title),d=b(null==u?void 0:u.site_name,null==s?void 0:s.site_name,null==c?void 0:c.site_name),f=b(null==s?void 0:s.favicon,null==c?void 0:c.favicon),m=b((null==(r=null==u?void 0:u.image)?void 0:r.url)??void 0,(null==s?void 0:s.image)??void 0,(null==c?void 0:c.image)??void 0,null==(o=null==c?void 0:c.images)?void 0:o[0]),p=b((null==(i=null==u?void 0:u.video)?void 0:i.url)??void 0,(null==s?void 0:s.video)??void 0),w=b(null==s?void 0:s.videoType,null==c?void 0:c.videoType),g=b(n.url,(null==u?void 0:u.url)??void 0,(null==s?void 0:s.url)??void 0,(null==c?void 0:c.url)??void 0,n.finalUrl,n.normalizedUrl,n.requestedUrl)??"";return{description:a,favicon:f,image:m,video:p,videoType:w,siteName:d,title:l,url:g,siteHost:h(g)}},[]);return p({endpoint:"open-graph",options:e,selectData:o,shouldSkip:r})},e.usePlatformFromUrl=function(e){return t.useMemo(()=>{if(!e||"string"!=typeof e)return"unknown";const t=e.trim();if(!t)return"unknown";try{const e=new URL(t).hostname.toLowerCase(),n=c.get(e);return n||(e.endsWith(".substack.com")?"substack":e.endsWith(".github.io")?"github":e.includes("pinterest.com")?"pinterest":e.includes("eventbrite.")?"eventbrite":e.endsWith(".medium.com")?"medium":e.endsWith(".behance.net")?"behance":"unknown")}catch{return"unknown"}},[e])},e.usePrevious=function(e){const r=t.useRef();return n(()=>{r.current=e},[e]),r.current},e.useResizeObserver=function(e,r,o){const i=t.useRef(r),u=t.useRef(null),[c,s]=t.useState(null);return n(()=>{i.current=r},[r]),t.useEffect(()=>{if("undefined"==typeof ResizeObserver)return;const t="undefined"!=typeof Element&&e instanceof Element?e:(n=e)&&"object"==typeof n&&"current"in n?e.current:null;var n;if(!t)return;const r=new ResizeObserver(e=>{const t=e[0];u.current=t,i.current?i.current(t):s(t)});return r.observe(t,o),()=>r.disconnect()},[o,e]),i.current?u.current:c},e.useScreen=function(e={}){const{breakpoints:n,screenTypeMapping:r,defaultScreenType:o="UNKNOWN",defaultTailwindSize:i="default"}=e,c=t.useMemo(()=>({...y,...n}),[n]),s=t.useMemo(()=>({...k,...r}),[r]),[a,l]=t.useState(()=>E()),d=u(`(min-width: ${c.sm}px)`),f=u(`(min-width: ${c.md}px)`),m=u(`(min-width: ${c.lg}px)`),p=u(`(min-width: ${c.xl}px)`),w=u(`(min-width: ${c["2xl"]}px)`),b=t.useCallback(()=>{"undefined"!=typeof window&&l(E())},[]);t.useEffect(()=>{if("undefined"==typeof window)return;l(E());const e=()=>{l(E())};return window.addEventListener("resize",e),()=>window.removeEventListener("resize",e)},[]);const h=t.useMemo(()=>d||f||m||p||w?w?"2xl":p?"xl":m?"lg":f?"md":d?"sm":"default":a.width>0?function(e,t){return e>=t["2xl"]?"2xl":e>=t.xl?"xl":e>=t.lg?"lg":e>=t.md?"md":e>=t.sm?"sm":"default"}(a.width,c):i,[d,f,m,p,w,a.width,c,i]),g=t.useMemo(()=>h===i&&0===a.width?o:s[h],[h,s,a.width,o,i]);return t.useMemo(()=>({width:a.width,height:a.height,tailwindSize:h,screenType:g,refresh:b}),[a.width,a.height,h,g,b])},e.useSessionStorage=function(e,n,r={}){const{initializeWithValue:o=!0,serialize:i=JSON.stringify,deserialize:u=JSON.parse,listenToStorageChanges:c=!1}=r,s=t.useRef(n),a=t.useCallback(()=>{if("undefined"==typeof window)return s.current;if(!o)return s.current;try{const t=window.sessionStorage.getItem(e);return t?u(t):s.current}catch{return s.current}},[u,o,e]),[l,d]=t.useState(()=>a()),f=t.useCallback(t=>{d(n=>{const r="function"==typeof t?t(n):t;if("undefined"!=typeof window)try{window.sessionStorage.setItem(e,i(r))}catch{}return r})},[e,i]);return t.useEffect(()=>{d(a())},[a]),t.useEffect(()=>{if("undefined"==typeof window||!c)return;const t=t=>{if(t.key===e)if(null!==t.newValue)try{d(u(t.newValue))}catch{d(s.current)}else d(s.current)};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)},[u,e,c]),[l,f]},e.useThrottle=function(e,n,r={}){const o=r.leading??!0,i=r.trailing??!0,u=Math.max(0,n),[c,s]=t.useState(e),a=t.useRef(0),l=t.useRef(null),d=t.useRef(null);return t.useEffect(()=>{if(0===u)return void s(e);const t=Date.now();if(0===a.current)return a.current=t,o?void s(e):void(i&&!l.current&&(d.current=e,l.current=setTimeout(()=>{l.current=null,null!==d.current&&(s(d.current),d.current=null,a.current=Date.now())},u)));const n=t-a.current;if(n>=u&&o)return s(e),a.current=t,void(d.current=null);if(i&&(d.current=e,!l.current)){const e=Math.max(u-n,0);l.current=setTimeout(()=>{l.current=null,null!==d.current&&(s(d.current),d.current=null,a.current=Date.now())},e)}},[o,i,e,u]),t.useEffect(()=>()=>{l.current&&clearTimeout(l.current)},[]),c},e.useWebsiteLinksExtractor=function(e){return p({endpoint:"links",options:e,selectData:t.useCallback(e=>({totalLinks:e.totalLinks??0,uniqueDomains:e.uniqueDomains??0,links:e.links??[]}),[])})},e.useWebsiteMetaExtractor=function(e){return p({endpoint:"meta",options:e,selectData:t.useCallback(e=>({title:e.title??void 0,description:e.description??void 0,language:e.language??void 0,canonicalUrl:e.canonicalUrl??void 0,feedUrl:e.feedUrl??null,textContentLength:e.textContentLength??void 0,metaTags:e.metaTags??{}}),[])})},e.useWebsiteRssExtractor=function(e){return p({endpoint:"rss",options:e,selectData:t.useCallback(e=>({feedUrl:e.feedUrl??null,feeds:e.feeds??[]}),[])})},e.useWebsiteSchemaExtractor=function(e){return p({endpoint:"schema",options:e,selectData:t.useCallback(e=>({schema:e.schema??[],schemaTypes:e.schemaTypes??[]}),[])})},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
//# sourceMappingURL=opensite-hooks.umd.js.map

@@ -1,2 +0,2 @@

!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).OpensiteHooks={},e.React)}(this,function(e,t){"use strict";const n="undefined"!=typeof window?t.useLayoutEffect:t.useEffect;function r(e,r,o={}){const i=t.useRef(e),u=t.useRef(null),c=t.useRef(null),s=t.useRef(null),a=o.leading??!1,l=o.trailing??!0,d=o.maxWait,m=Math.max(0,r);n(()=>{i.current=e},[e]);const f=t.useCallback(()=>{u.current&&(clearTimeout(u.current),u.current=null),c.current&&(clearTimeout(c.current),c.current=null)},[]),p=t.useCallback(()=>{if(!s.current)return;const e=s.current;s.current=null,i.current(...e)},[]),w=t.useCallback((...e)=>{s.current=e;if(a&&null===u.current&&null===c.current&&p(),u.current&&clearTimeout(u.current),l&&(u.current=setTimeout(()=>{u.current=null,s.current&&p(),c.current&&(clearTimeout(c.current),c.current=null)},m)),null!=d&&l&&!c.current){const e=Math.max(0,d);c.current=setTimeout(()=>{c.current=null,u.current&&(clearTimeout(u.current),u.current=null),s.current&&p()},e)}},[p,a,l,d,m]),b=t.useCallback(()=>{f(),s.current=null},[f]),g=t.useCallback(()=>{s.current&&(f(),p())},[f,p]);return t.useEffect(()=>()=>b(),[b]),{debouncedCallback:w,cancel:b,flush:g}}function o(e,n,o){const[i,u]=t.useState(e),{debouncedCallback:c,cancel:s}=r(e=>{u(e)},n,o);return t.useEffect(()=>{c(e)},[c,e]),t.useEffect(()=>()=>s(),[s]),i}function i(e){return(null==e?void 0:e.ownerDocument)??document}const u=new Map([["instagram.com","instagram"],["www.instagram.com","instagram"],["instagr.am","instagram"],["www.instagr.am","instagram"],["linkedin.com","linkedin"],["www.linkedin.com","linkedin"],["ca.linkedin.com","linkedin"],["uk.linkedin.com","linkedin"],["in.linkedin.com","linkedin"],["lnkd.in","linkedin"],["google.com","google"],["www.google.com","google"],["maps.google.com","google"],["goo.gl","google"],["maps.app.goo.gl","google"],["g.co","google"],["facebook.com","facebook"],["www.facebook.com","facebook"],["m.facebook.com","facebook"],["fb.com","facebook"],["fb.me","facebook"],["on.fb.me","facebook"],["tiktok.com","tiktok"],["www.tiktok.com","tiktok"],["m.tiktok.com","tiktok"],["vm.tiktok.com","tiktok"],["vt.tiktok.com","tiktok"],["youtube.com","youtube"],["www.youtube.com","youtube"],["m.youtube.com","youtube"],["youtu.be","youtube"],["yelp.com","yelp"],["www.yelp.com","yelp"],["m.yelp.com","yelp"],["spotify.com","spotify"],["www.spotify.com","spotify"],["open.spotify.com","spotify"],["play.spotify.com","spotify"],["spoti.fi","spotify"],["spotify.link","spotify"],["apple.com","apple"],["www.apple.com","apple"],["music.apple.com","apple"],["podcasts.apple.com","apple"],["apps.apple.com","apple"],["itunes.apple.com","apple"],["x.com","x"],["www.x.com","x"],["twitter.com","x"],["www.twitter.com","x"],["t.co","x"],["github.com","github"],["www.github.com","github"],["gist.github.com","github"],["raw.githubusercontent.com","github"],["github.io","github"],["discord.com","discord"],["www.discord.com","discord"],["discord.gg","discord"],["discordapp.com","discord"],["discordapp.net","discord"],["discord.new","discord"],["discord.gift","discord"],["discord.gifts","discord"],["dis.gd","discord"],["snapchat.com","snapchat"],["www.snapchat.com","snapchat"],["snap.com","snapchat"],["www.snap.com","snapchat"],["story.snapchat.com","snapchat"],["web.snapchat.com","snapchat"],["dev.to","dev"],["www.dev.to","dev"],["substack.com","substack"],["www.substack.com","substack"],["reddit.com","reddit"],["www.reddit.com","reddit"],["old.reddit.com","reddit"],["new.reddit.com","reddit"],["i.redd.it","reddit"],["v.redd.it","reddit"],["redd.it","reddit"],["preview.redd.it","reddit"],["pinterest.com","pinterest"],["www.pinterest.com","pinterest"],["pin.it","pinterest"],["in.pinterest.com","pinterest"],["br.pinterest.com","pinterest"],["uk.pinterest.com","pinterest"],["threads.net","threads"],["www.threads.net","threads"],["threads.com","threads"],["www.threads.com","threads"],["twitch.tv","twitch"],["www.twitch.tv","twitch"],["m.twitch.tv","twitch"],["whatsapp.com","whatsapp"],["www.whatsapp.com","whatsapp"],["wa.me","whatsapp"],["web.whatsapp.com","whatsapp"],["telegram.org","telegram"],["www.telegram.org","telegram"],["t.me","telegram"],["telegram.me","telegram"],["telegram.dog","telegram"],["medium.com","medium"],["www.medium.com","medium"],["patreon.com","patreon"],["www.patreon.com","patreon"],["onlyfans.com","onlyfans"],["www.onlyfans.com","onlyfans"],["eventbrite.com","eventbrite"],["www.eventbrite.com","eventbrite"],["eventbrite.co.uk","eventbrite"],["eventbrite.com.au","eventbrite"],["eventbrite.ca","eventbrite"],["eventbrite.de","eventbrite"],["eventbrite.fr","eventbrite"],["eventbrite.es","eventbrite"],["eventbrite.it","eventbrite"],["eventbrite.ie","eventbrite"],["eventbrite.nl","eventbrite"],["eventbrite.co.nz","eventbrite"],["eventbriteapi.com","eventbrite"],["evbuc.com","eventbrite"],["npmjs.com","npmjs"],["www.npmjs.com","npmjs"],["npmjs.org","npmjs"],["www.npmjs.org","npmjs"],["registry.npmjs.org","npmjs"],["registry.npmjs.com","npmjs"],["replicate.npmjs.com","npmjs"],["crates.io","crates"],["www.crates.io","crates"],["rubygems.org","rubygems"],["www.rubygems.org","rubygems"],["behance.net","behance"],["www.behance.net","behance"],["portfolio.behance.net","behance"],["mir-s3-cdn-cf.behance.net","behance"],["dribbble.com","dribbble"],["www.dribbble.com","dribbble"],["drbl.in","dribbble"]]);function c(e,r,o,i){const u=t.useRef(r);n(()=>{u.current=r},[r]),t.useEffect(()=>{const t="undefined"!=typeof Window&&o instanceof Window,n="undefined"!=typeof Document&&o instanceof Document,r=void 0===o?"undefined"!=typeof window?window:null:t||n||"undefined"!=typeof HTMLElement&&o instanceof HTMLElement?o:(c=o)&&"object"==typeof c&&"current"in c?o.current:null;var c;if(!(null==r?void 0:r.addEventListener))return;const s=e=>{const t=u.current;"function"==typeof t?t(e):t.handleEvent(e)};return r.addEventListener(e,s,i),()=>{r.removeEventListener(e,s,i)}},[e,o,i])}function s(){const[e,n]=t.useState(!1);return t.useEffect(()=>{n(!0)},[]),e}function a(e){const[r,o]=t.useState(()=>e instanceof Map||Array.isArray(e)?new Map(e):new Map),i=t.useRef(r);n(()=>{i.current=r},[r]);const u=t.useMemo(()=>({set:(e,t)=>{o(n=>{const r=new Map(n);return r.set(e,t),r})},setAll:e=>{o((Map,new Map(e)))},remove:e=>{o(t=>{const n=new Map(t);return n.delete(e),n})},clear:()=>o(new Map),get:e=>i.current.get(e),has:e=>i.current.has(e)}),[]);return[r,u]}const l="https://octane.buzz";function d(e){const t=function(e){return e.replace(/\/+$/,"")}(e.baseUrl??l),n=new URLSearchParams;return e.apiKey&&n.set("api_key",e.apiKey),n.set("url",e.url),`${t}/api/v1/extract/${e.endpoint}?${n.toString()}`}async function m(e){var t;if(!e.url||0===e.url.trim().length)return{ok:!1,error:{message:"URL is required."}};const n=d(e);try{const t=await fetch(n,{method:"GET",signal:e.signal});let r=null;try{r=await t.json()}catch{r=null}if(!t.ok){const e=r;return{ok:!1,error:{message:(null==e?void 0:e.error)??`Request failed with status ${t.status}.`,status:(null==e?void 0:e.status)??t.status,raw:r}}}return{ok:!0,response:r}}catch(r){return(null==(t=e.signal)?void 0:t.aborted)?{ok:!1,error:{message:"Request aborted."}}:{ok:!1,error:{message:r instanceof Error?r.message:"Request failed unexpectedly.",raw:r}}}}function f(e){const{endpoint:n,options:i,selectData:u,shouldSkip:c}=e,d=s(),[f,p]=t.useState({loading:!1}),[,w]=a(),b=i.cache??true,g=i.enabled??true,v=i.debounceMs??250,y=i.refreshDebounceMs??150,h=o(t.useMemo(()=>{var e;return(null==(e=i.url)?void 0:e.trim())??""},[i.url]),v),k=t.useRef(0),E=t.useRef(0),[C,T]=t.useState(0),{debouncedCallback:x,cancel:S}=r(()=>{k.current+=1,T(k.current)},y),M=t.useCallback(()=>{x()},[x]);t.useEffect(()=>()=>S(),[S]);const R=t.useMemo(()=>{if(!h)return"";const e=i.baseUrl??l,t=i.apiKey??"";return`${n}:${e}:${t}:${h}`},[n,i.apiKey,i.baseUrl,h]),L=t.useRef(null);return t.useEffect(()=>{var e;if(!d)return;if(!g||!h)return void p({loading:!1});if(null==c?void 0:c(h))return void p({loading:!1});const t=C!==E.current;if(t&&(E.current=C),b&&!t){const e=w.get(R);if(e)return void p({loading:!1,data:e.data,raw:e.raw,meta:e.meta})}null==(e=L.current)||e.abort();const r=new AbortController;return L.current=r,p(e=>({...e,loading:!0,error:void 0})),m({endpoint:n,url:h,apiKey:i.apiKey,baseUrl:i.baseUrl,signal:r.signal}).then(e=>{if(r.signal.aborted)return;if(!e.ok)return void p(t=>({...t,loading:!1,error:e.error}));const t=e.response,n=function(e){const{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d}=e;return{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d}}(t),o=function(e){const{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d,...m}=e;return m}(t),i=u(o,t,n);b&&w.set(R,{data:i,raw:t,meta:n}),p({loading:!1,data:i,raw:t,meta:n})}).catch(e=>{r.signal.aborted||p(t=>({...t,loading:!1,error:{message:e instanceof Error?e.message:"Request failed unexpectedly.",raw:e}}))}),()=>{r.abort()}},[w,b,h,g,n,d,i.apiKey,i.baseUrl,R,C,u,c]),t.useMemo(()=>({...f,refresh:M}),[M,f])}const p=[/search\.google\.com\/local\/reviews/i,/google\.com\/maps\/place/i,/maps\.google\.com/i,/opentable\.com/i],w=(...e)=>{for(const t of e)if("string"==typeof t&&t.trim().length>0)return t},b=e=>{if(e)try{return new URL(e).hostname}catch{return}};e.buildWebsiteExtractorUrl=d,e.fetchWebsiteExtractor=m,e.useBoolean=function(e=!1){const[n,r]=t.useState(e),o=t.useCallback(()=>r(!0),[]),i=t.useCallback(()=>r(!1),[]),u=t.useCallback(()=>r(e=>!e),[]);return t.useMemo(()=>({value:n,setValue:r,setTrue:o,setFalse:i,toggle:u}),[n,r,o,i,u])},e.useCopyToClipboard=function(e={}){const n=e.resetDelay??2e3,r=t.useRef(null),[o,i]=t.useState(null),u=t.useMemo(()=>!("undefined"==typeof navigator||!navigator.clipboard)||"undefined"!=typeof document&&("function"==typeof document.queryCommandSupported&&document.queryCommandSupported("copy")),[]),c=t.useCallback(()=>{r.current&&clearTimeout(r.current),r.current=setTimeout(()=>{i(null)},n)},[n]),s=t.useCallback(async e=>{if(!u)return!1;const t="undefined"!=typeof navigator&&!!navigator.clipboard;try{if(t)await navigator.clipboard.writeText(e);else if("undefined"!=typeof document){const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="fixed",t.style.left="-9999px",t.style.top="0",document.body.appendChild(t),t.focus(),t.select();const n=document.execCommand("copy");if(document.body.removeChild(t),!n)return!1}return i(e),c(),!0}catch{return!1}},[u,c]);return t.useEffect(()=>()=>{r.current&&clearTimeout(r.current)},[]),{copy:s,copiedText:o,isSupported:u}},e.useDebounceCallback=r,e.useDebounceValue=o,e.useEventListener=c,e.useHover=function(e){const[n,r]=t.useState(!1),o=t.useCallback(()=>{r(!0)},[]),i=t.useCallback(()=>{r(!1)},[]);return c("pointerenter",o,e),c("pointerleave",i,e),n},e.useIsClient=s,e.useIsomorphicLayoutEffect=n,e.useLocalStorage=function(e,n,r={}){const{initializeWithValue:o=!0,serialize:i=JSON.stringify,deserialize:u=JSON.parse,listenToStorageChanges:c=!0}=r,s=t.useRef(n),a=t.useCallback(()=>{if("undefined"==typeof window)return s.current;if(!o)return s.current;try{const t=window.localStorage.getItem(e);return t?u(t):s.current}catch{return s.current}},[u,o,e]),[l,d]=t.useState(()=>a()),m=t.useCallback(t=>{d(n=>{const r="function"==typeof t?t(n):t;if("undefined"!=typeof window)try{window.localStorage.setItem(e,i(r))}catch{}return r})},[e,i]);return t.useEffect(()=>{d(a())},[a]),t.useEffect(()=>{if("undefined"==typeof window||!c)return;const t=t=>{if(t.key===e)if(null!==t.newValue)try{d(u(t.newValue))}catch{d(s.current)}else d(s.current)};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)},[u,e,c]),[l,m]},e.useMap=a,e.useMediaQuery=function(e,n={}){const[r,o]=t.useState(()=>"undefined"==typeof window||"function"!=typeof window.matchMedia?n.defaultValue??!1:window.matchMedia(e).matches);return t.useEffect(()=>{if("undefined"==typeof window||"function"!=typeof window.matchMedia)return;const t=window.matchMedia(e),n=e=>{o(e.matches)};return o(t.matches),t.addEventListener?(t.addEventListener("change",n),()=>t.removeEventListener("change",n)):(t.addListener(n),()=>t.removeListener(n))},[e]),r},e.useOnClickOutside=function(e,r,o,u){const c=t.useRef(r);n(()=>{c.current=r},[r]),t.useEffect(()=>{if("undefined"==typeof document)return;const t="undefined"!=typeof window&&void 0!==window.PointerEvent,n=o??(t?"pointerdown":"mousedown"),r=Array.isArray(e)?e:[e];let s=document;for(const e of r)if(e.current){const t=i(e.current);if(t!==document){s=t;break}}const a=e=>{const t=e.target;if("undefined"==typeof Node||!(t instanceof Node))return;r.some(e=>{const n=e.current;return!!n&&n.contains(t)})||c.current(e)};return s.addEventListener(n,a,u),()=>{s.removeEventListener(n,a,u)}},[o,u,e])},e.useOpenGraphExtractor=function(e){const n=t.useMemo(()=>e.skipPatterns??p,[e.skipPatterns]),r=t.useCallback(e=>n.some(t=>t.test(e)),[n]),o=t.useCallback((e,t,n)=>{var r,o,i;const{openGraph:u,htmlInferred:c,hybridGraph:s}=e,a=w(null==u?void 0:u.description,null==s?void 0:s.description,null==c?void 0:c.description),l=w(null==u?void 0:u.title,null==s?void 0:s.title,null==c?void 0:c.title),d=w(null==u?void 0:u.site_name,null==s?void 0:s.site_name,null==c?void 0:c.site_name),m=w(null==s?void 0:s.favicon,null==c?void 0:c.favicon),f=w((null==(r=null==u?void 0:u.image)?void 0:r.url)??void 0,(null==s?void 0:s.image)??void 0,(null==c?void 0:c.image)??void 0,null==(o=null==c?void 0:c.images)?void 0:o[0]),p=w((null==(i=null==u?void 0:u.video)?void 0:i.url)??void 0,(null==s?void 0:s.video)??void 0),g=w(null==s?void 0:s.videoType,null==c?void 0:c.videoType),v=w(n.url,(null==u?void 0:u.url)??void 0,(null==s?void 0:s.url)??void 0,(null==c?void 0:c.url)??void 0,n.finalUrl,n.normalizedUrl,n.requestedUrl)??"";return{description:a,favicon:m,image:f,video:p,videoType:g,siteName:d,title:l,url:v,siteHost:b(v)}},[]);return f({endpoint:"open-graph",options:e,selectData:o,shouldSkip:r})},e.usePlatformFromUrl=function(e){return t.useMemo(()=>{if(!e||"string"!=typeof e)return"unknown";const t=e.trim();if(!t)return"unknown";try{const e=new URL(t).hostname.toLowerCase(),n=u.get(e);return n||(e.endsWith(".substack.com")?"substack":e.endsWith(".github.io")?"github":e.includes("pinterest.com")?"pinterest":e.includes("eventbrite.")?"eventbrite":e.endsWith(".medium.com")?"medium":e.endsWith(".behance.net")?"behance":"unknown")}catch{return"unknown"}},[e])},e.usePrevious=function(e){const r=t.useRef();return n(()=>{r.current=e},[e]),r.current},e.useResizeObserver=function(e,r,o){const i=t.useRef(r),u=t.useRef(null),[c,s]=t.useState(null);return n(()=>{i.current=r},[r]),t.useEffect(()=>{if("undefined"==typeof ResizeObserver)return;const t="undefined"!=typeof Element&&e instanceof Element?e:(n=e)&&"object"==typeof n&&"current"in n?e.current:null;var n;if(!t)return;const r=new ResizeObserver(e=>{const t=e[0];u.current=t,i.current?i.current(t):s(t)});return r.observe(t,o),()=>r.disconnect()},[o,e]),i.current?u.current:c},e.useSessionStorage=function(e,n,r={}){const{initializeWithValue:o=!0,serialize:i=JSON.stringify,deserialize:u=JSON.parse,listenToStorageChanges:c=!1}=r,s=t.useRef(n),a=t.useCallback(()=>{if("undefined"==typeof window)return s.current;if(!o)return s.current;try{const t=window.sessionStorage.getItem(e);return t?u(t):s.current}catch{return s.current}},[u,o,e]),[l,d]=t.useState(()=>a()),m=t.useCallback(t=>{d(n=>{const r="function"==typeof t?t(n):t;if("undefined"!=typeof window)try{window.sessionStorage.setItem(e,i(r))}catch{}return r})},[e,i]);return t.useEffect(()=>{d(a())},[a]),t.useEffect(()=>{if("undefined"==typeof window||!c)return;const t=t=>{if(t.key===e)if(null!==t.newValue)try{d(u(t.newValue))}catch{d(s.current)}else d(s.current)};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)},[u,e,c]),[l,m]},e.useThrottle=function(e,n,r={}){const o=r.leading??!0,i=r.trailing??!0,u=Math.max(0,n),[c,s]=t.useState(e),a=t.useRef(0),l=t.useRef(null),d=t.useRef(null);return t.useEffect(()=>{if(0===u)return void s(e);const t=Date.now();if(0===a.current)return a.current=t,o?void s(e):void(i&&!l.current&&(d.current=e,l.current=setTimeout(()=>{l.current=null,null!==d.current&&(s(d.current),d.current=null,a.current=Date.now())},u)));const n=t-a.current;if(n>=u&&o)return s(e),a.current=t,void(d.current=null);if(i&&(d.current=e,!l.current)){const e=Math.max(u-n,0);l.current=setTimeout(()=>{l.current=null,null!==d.current&&(s(d.current),d.current=null,a.current=Date.now())},e)}},[o,i,e,u]),t.useEffect(()=>()=>{l.current&&clearTimeout(l.current)},[]),c},e.useWebsiteLinksExtractor=function(e){return f({endpoint:"links",options:e,selectData:t.useCallback(e=>({totalLinks:e.totalLinks??0,uniqueDomains:e.uniqueDomains??0,links:e.links??[]}),[])})},e.useWebsiteMetaExtractor=function(e){return f({endpoint:"meta",options:e,selectData:t.useCallback(e=>({title:e.title??void 0,description:e.description??void 0,language:e.language??void 0,canonicalUrl:e.canonicalUrl??void 0,feedUrl:e.feedUrl??null,textContentLength:e.textContentLength??void 0,metaTags:e.metaTags??{}}),[])})},e.useWebsiteRssExtractor=function(e){return f({endpoint:"rss",options:e,selectData:t.useCallback(e=>({feedUrl:e.feedUrl??null,feeds:e.feeds??[]}),[])})},e.useWebsiteSchemaExtractor=function(e){return f({endpoint:"schema",options:e,selectData:t.useCallback(e=>({schema:e.schema??[],schemaTypes:e.schemaTypes??[]}),[])})},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).OpensiteHooks={},e.React)}(this,function(e,t){"use strict";const n="undefined"!=typeof window?t.useLayoutEffect:t.useEffect;function r(e,r,o={}){const i=t.useRef(e),u=t.useRef(null),c=t.useRef(null),s=t.useRef(null),a=o.leading??!1,l=o.trailing??!0,d=o.maxWait,f=Math.max(0,r);n(()=>{i.current=e},[e]);const m=t.useCallback(()=>{u.current&&(clearTimeout(u.current),u.current=null),c.current&&(clearTimeout(c.current),c.current=null)},[]),p=t.useCallback(()=>{if(!s.current)return;const e=s.current;s.current=null,i.current(...e)},[]),w=t.useCallback((...e)=>{s.current=e;if(a&&null===u.current&&null===c.current&&p(),u.current&&clearTimeout(u.current),l&&(u.current=setTimeout(()=>{u.current=null,s.current&&p(),c.current&&(clearTimeout(c.current),c.current=null)},f)),null!=d&&l&&!c.current){const e=Math.max(0,d);c.current=setTimeout(()=>{c.current=null,u.current&&(clearTimeout(u.current),u.current=null),s.current&&p()},e)}},[p,a,l,d,f]),b=t.useCallback(()=>{m(),s.current=null},[m]),h=t.useCallback(()=>{s.current&&(m(),p())},[m,p]);return t.useEffect(()=>()=>b(),[b]),{debouncedCallback:w,cancel:b,flush:h}}function o(e,n,o){const[i,u]=t.useState(e),{debouncedCallback:c,cancel:s}=r(e=>{u(e)},n,o);return t.useEffect(()=>{c(e)},[c,e]),t.useEffect(()=>()=>s(),[s]),i}function i(e){return(null==e?void 0:e.ownerDocument)??document}function u(e,n={}){const[r,o]=t.useState(()=>"undefined"==typeof window||"function"!=typeof window.matchMedia?n.defaultValue??!1:window.matchMedia(e).matches);return t.useEffect(()=>{if("undefined"==typeof window||"function"!=typeof window.matchMedia)return;const t=window.matchMedia(e),n=e=>{o(e.matches)};return o(t.matches),t.addEventListener("change",n),()=>t.removeEventListener("change",n)},[e]),r}const c=new Map([["instagram.com","instagram"],["www.instagram.com","instagram"],["instagr.am","instagram"],["www.instagr.am","instagram"],["linkedin.com","linkedin"],["www.linkedin.com","linkedin"],["ca.linkedin.com","linkedin"],["uk.linkedin.com","linkedin"],["in.linkedin.com","linkedin"],["lnkd.in","linkedin"],["google.com","google"],["www.google.com","google"],["maps.google.com","google"],["goo.gl","google"],["maps.app.goo.gl","google"],["g.co","google"],["facebook.com","facebook"],["www.facebook.com","facebook"],["m.facebook.com","facebook"],["fb.com","facebook"],["fb.me","facebook"],["on.fb.me","facebook"],["tiktok.com","tiktok"],["www.tiktok.com","tiktok"],["m.tiktok.com","tiktok"],["vm.tiktok.com","tiktok"],["vt.tiktok.com","tiktok"],["youtube.com","youtube"],["www.youtube.com","youtube"],["m.youtube.com","youtube"],["youtu.be","youtube"],["yelp.com","yelp"],["www.yelp.com","yelp"],["m.yelp.com","yelp"],["spotify.com","spotify"],["www.spotify.com","spotify"],["open.spotify.com","spotify"],["play.spotify.com","spotify"],["spoti.fi","spotify"],["spotify.link","spotify"],["apple.com","apple"],["www.apple.com","apple"],["music.apple.com","apple"],["podcasts.apple.com","apple"],["apps.apple.com","apple"],["itunes.apple.com","apple"],["x.com","x"],["www.x.com","x"],["twitter.com","x"],["www.twitter.com","x"],["t.co","x"],["github.com","github"],["www.github.com","github"],["gist.github.com","github"],["raw.githubusercontent.com","github"],["github.io","github"],["discord.com","discord"],["www.discord.com","discord"],["discord.gg","discord"],["discordapp.com","discord"],["discordapp.net","discord"],["discord.new","discord"],["discord.gift","discord"],["discord.gifts","discord"],["dis.gd","discord"],["snapchat.com","snapchat"],["www.snapchat.com","snapchat"],["snap.com","snapchat"],["www.snap.com","snapchat"],["story.snapchat.com","snapchat"],["web.snapchat.com","snapchat"],["dev.to","dev"],["www.dev.to","dev"],["substack.com","substack"],["www.substack.com","substack"],["reddit.com","reddit"],["www.reddit.com","reddit"],["old.reddit.com","reddit"],["new.reddit.com","reddit"],["i.redd.it","reddit"],["v.redd.it","reddit"],["redd.it","reddit"],["preview.redd.it","reddit"],["pinterest.com","pinterest"],["www.pinterest.com","pinterest"],["pin.it","pinterest"],["in.pinterest.com","pinterest"],["br.pinterest.com","pinterest"],["uk.pinterest.com","pinterest"],["threads.net","threads"],["www.threads.net","threads"],["threads.com","threads"],["www.threads.com","threads"],["twitch.tv","twitch"],["www.twitch.tv","twitch"],["m.twitch.tv","twitch"],["whatsapp.com","whatsapp"],["www.whatsapp.com","whatsapp"],["wa.me","whatsapp"],["web.whatsapp.com","whatsapp"],["telegram.org","telegram"],["www.telegram.org","telegram"],["t.me","telegram"],["telegram.me","telegram"],["telegram.dog","telegram"],["medium.com","medium"],["www.medium.com","medium"],["patreon.com","patreon"],["www.patreon.com","patreon"],["onlyfans.com","onlyfans"],["www.onlyfans.com","onlyfans"],["eventbrite.com","eventbrite"],["www.eventbrite.com","eventbrite"],["eventbrite.co.uk","eventbrite"],["eventbrite.com.au","eventbrite"],["eventbrite.ca","eventbrite"],["eventbrite.de","eventbrite"],["eventbrite.fr","eventbrite"],["eventbrite.es","eventbrite"],["eventbrite.it","eventbrite"],["eventbrite.ie","eventbrite"],["eventbrite.nl","eventbrite"],["eventbrite.co.nz","eventbrite"],["eventbriteapi.com","eventbrite"],["evbuc.com","eventbrite"],["npmjs.com","npmjs"],["www.npmjs.com","npmjs"],["npmjs.org","npmjs"],["www.npmjs.org","npmjs"],["registry.npmjs.org","npmjs"],["registry.npmjs.com","npmjs"],["replicate.npmjs.com","npmjs"],["crates.io","crates"],["www.crates.io","crates"],["rubygems.org","rubygems"],["www.rubygems.org","rubygems"],["behance.net","behance"],["www.behance.net","behance"],["portfolio.behance.net","behance"],["mir-s3-cdn-cf.behance.net","behance"],["dribbble.com","dribbble"],["www.dribbble.com","dribbble"],["drbl.in","dribbble"]]);function s(e,r,o,i){const u=t.useRef(r);n(()=>{u.current=r},[r]),t.useEffect(()=>{const t="undefined"!=typeof Window&&o instanceof Window,n="undefined"!=typeof Document&&o instanceof Document,r=void 0===o?"undefined"!=typeof window?window:null:t||n||"undefined"!=typeof HTMLElement&&o instanceof HTMLElement?o:(c=o)&&"object"==typeof c&&"current"in c?o.current:null;var c;if(!(null==r?void 0:r.addEventListener))return;const s=e=>{const t=u.current;"function"==typeof t?t(e):t.handleEvent(e)};return r.addEventListener(e,s,i),()=>{r.removeEventListener(e,s,i)}},[e,o,i])}function a(){const[e,n]=t.useState(!1);return t.useEffect(()=>{n(!0)},[]),e}function l(e){const[r,o]=t.useState(()=>e instanceof Map||Array.isArray(e)?new Map(e):new Map),i=t.useRef(r);n(()=>{i.current=r},[r]);const u=t.useMemo(()=>({set:(e,t)=>{o(n=>{const r=new Map(n);return r.set(e,t),r})},setAll:e=>{o((Map,new Map(e)))},remove:e=>{o(t=>{const n=new Map(t);return n.delete(e),n})},clear:()=>o(new Map),get:e=>i.current.get(e),has:e=>i.current.has(e)}),[]);return[r,u]}const d="https://octane.buzz";function f(e){const t=function(e){return e.replace(/\/+$/,"")}(e.baseUrl??d),n=new URLSearchParams;return e.apiKey&&n.set("api_key",e.apiKey),n.set("url",e.url),`${t}/api/v1/extract/${e.endpoint}?${n.toString()}`}async function m(e){var t;if(!e.url||0===e.url.trim().length)return{ok:!1,error:{message:"URL is required."}};const n=f(e);try{const t=await fetch(n,{method:"GET",signal:e.signal});let r=null;try{r=await t.json()}catch{r=null}if(!t.ok){const e=r;return{ok:!1,error:{message:(null==e?void 0:e.error)??`Request failed with status ${t.status}.`,status:(null==e?void 0:e.status)??t.status,raw:r}}}return{ok:!0,response:r}}catch(r){return(null==(t=e.signal)?void 0:t.aborted)?{ok:!1,error:{message:"Request aborted."}}:{ok:!1,error:{message:r instanceof Error?r.message:"Request failed unexpectedly.",raw:r}}}}function p(e){const{endpoint:n,options:i,selectData:u,shouldSkip:c}=e,s=a(),[f,p]=t.useState({loading:!1}),[,w]=l(),b=i.cache??true,h=i.enabled??true,g=i.debounceMs??250,v=i.refreshDebounceMs??150,y=o(t.useMemo(()=>{var e;return(null==(e=i.url)?void 0:e.trim())??""},[i.url]),g),k=t.useRef(0),E=t.useRef(0),[x,T]=t.useState(0),{debouncedCallback:M,cancel:S}=r(()=>{k.current+=1,T(k.current)},v),C=t.useCallback(()=>{M()},[M]);t.useEffect(()=>()=>S(),[S]);const L=t.useMemo(()=>{if(!y)return"";const e=i.baseUrl??d,t=i.apiKey??"";return`${n}:${e}:${t}:${y}`},[n,i.apiKey,i.baseUrl,y]),R=t.useRef(null);return t.useEffect(()=>{var e;if(!s)return;if(!h||!y)return void p({loading:!1});if(null==c?void 0:c(y))return void p({loading:!1});const t=x!==E.current;if(t&&(E.current=x),b&&!t){const e=w.get(L);if(e)return void p({loading:!1,data:e.data,raw:e.raw,meta:e.meta})}null==(e=R.current)||e.abort();const r=new AbortController;return R.current=r,p(e=>({...e,loading:!0,error:void 0})),m({endpoint:n,url:y,apiKey:i.apiKey,baseUrl:i.baseUrl,signal:r.signal}).then(e=>{if(r.signal.aborted)return;if(!e.ok)return void p(t=>({...t,loading:!1,error:e.error}));const t=e.response,n=function(e){const{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d}=e;return{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d}}(t),o=function(e){const{requestedUrl:t,finalUrl:n,url:r,normalizedUrl:o,status:i,contentType:u,fetchedAt:c,bodyBytes:s,bodyTruncated:a,maxBodyBytes:l,cache:d,...f}=e;return f}(t),i=u(o,t,n);b&&w.set(L,{data:i,raw:t,meta:n}),p({loading:!1,data:i,raw:t,meta:n})}).catch(e=>{r.signal.aborted||p(t=>({...t,loading:!1,error:{message:e instanceof Error?e.message:"Request failed unexpectedly.",raw:e}}))}),()=>{r.abort()}},[w,b,y,h,n,s,i.apiKey,i.baseUrl,L,x,u,c]),t.useMemo(()=>({...f,refresh:C}),[C,f])}const w=[/search\.google\.com\/local\/reviews/i,/google\.com\/maps\/place/i,/maps\.google\.com/i,/opentable\.com/i],b=(...e)=>{for(const t of e)if("string"==typeof t&&t.trim().length>0)return t},h=e=>{if(e)try{return new URL(e).hostname}catch{return}};const g="(hover: none) and (pointer: coarse)";function v(){if("function"==typeof window.matchMedia){const e=window.matchMedia(g);if("not all"!==e.media)return e.matches?"touch":"desktop"}return"ontouchstart"in window||"undefined"!=typeof navigator&&"number"==typeof navigator.maxTouchPoints&&navigator.maxTouchPoints>0?"touch":"desktop"}const y={sm:640,md:768,lg:1024,xl:1280,"2xl":1536},k={default:"MOBILE",sm:"MOBILE",md:"TABLET",lg:"DESKTOP",xl:"DESKTOP","2xl":"DESKTOP"};function E(){return"undefined"==typeof window?{width:0,height:0}:{width:window.innerWidth,height:window.innerHeight}}e.buildWebsiteExtractorUrl=f,e.fetchWebsiteExtractor=m,e.useBoolean=function(e=!1){const[n,r]=t.useState(e),o=t.useCallback(()=>r(!0),[]),i=t.useCallback(()=>r(!1),[]),u=t.useCallback(()=>r(e=>!e),[]);return t.useMemo(()=>({value:n,setValue:r,setTrue:o,setFalse:i,toggle:u}),[n,r,o,i,u])},e.useCopyToClipboard=function(e={}){const n=e.resetDelay??2e3,r=t.useRef(null),[o,i]=t.useState(null),u=t.useMemo(()=>!("undefined"==typeof navigator||!navigator.clipboard)||"undefined"!=typeof document&&("function"==typeof document.queryCommandSupported&&document.queryCommandSupported("copy")),[]),c=t.useCallback(()=>{r.current&&clearTimeout(r.current),r.current=setTimeout(()=>{i(null)},n)},[n]),s=t.useCallback(async e=>{if(!u)return!1;const t="undefined"!=typeof navigator&&!!navigator.clipboard;try{if(t)await navigator.clipboard.writeText(e);else if("undefined"!=typeof document){const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="fixed",t.style.left="-9999px",t.style.top="0",document.body.appendChild(t),t.focus(),t.select();const n=document.execCommand("copy");if(document.body.removeChild(t),!n)return!1}return i(e),c(),!0}catch{return!1}},[u,c]);return t.useEffect(()=>()=>{r.current&&clearTimeout(r.current)},[]),{copy:s,copiedText:o,isSupported:u}},e.useDebounceCallback=r,e.useDebounceValue=o,e.useEventListener=s,e.useHover=function(e){const[n,r]=t.useState(!1),o=t.useCallback(()=>{r(!0)},[]),i=t.useCallback(()=>{r(!1)},[]);return s("pointerenter",o,e),s("pointerleave",i,e),n},e.useIsClient=a,e.useIsTouchDevice=function(e={}){const{defaultDeviceType:n="unknown"}=e,[r,o]=t.useState(n),i=t.useCallback(()=>{"undefined"!=typeof window&&o(v())},[]);return t.useEffect(()=>{if("undefined"==typeof window)return;if(o(v()),"function"!=typeof window.matchMedia)return;const e=window.matchMedia(g);if("not all"===e.media)return;const t=e=>{o(e.matches?"touch":"desktop")};return e.addEventListener("change",t),()=>{e.removeEventListener("change",t)}},[]),t.useMemo(()=>({deviceType:r,isTouchDevice:"touch"===r,recheck:i}),[r,i])},e.useIsomorphicLayoutEffect=n,e.useLocalStorage=function(e,n,r={}){const{initializeWithValue:o=!0,serialize:i=JSON.stringify,deserialize:u=JSON.parse,listenToStorageChanges:c=!0}=r,s=t.useRef(n),a=t.useCallback(()=>{if("undefined"==typeof window)return s.current;if(!o)return s.current;try{const t=window.localStorage.getItem(e);return t?u(t):s.current}catch{return s.current}},[u,o,e]),[l,d]=t.useState(()=>a()),f=t.useCallback(t=>{d(n=>{const r="function"==typeof t?t(n):t;if("undefined"!=typeof window)try{window.localStorage.setItem(e,i(r))}catch{}return r})},[e,i]);return t.useEffect(()=>{d(a())},[a]),t.useEffect(()=>{if("undefined"==typeof window||!c)return;const t=t=>{if(t.key===e)if(null!==t.newValue)try{d(u(t.newValue))}catch{d(s.current)}else d(s.current)};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)},[u,e,c]),[l,f]},e.useMap=l,e.useMediaQuery=u,e.useOnClickOutside=function(e,r,o,u){const c=t.useRef(r);n(()=>{c.current=r},[r]),t.useEffect(()=>{if("undefined"==typeof document)return;const t="undefined"!=typeof window&&void 0!==window.PointerEvent,n=o??(t?"pointerdown":"mousedown"),r=Array.isArray(e)?e:[e];let s=document;for(const e of r)if(e.current){const t=i(e.current);if(t!==document){s=t;break}}const a=e=>{const t=e.target;if("undefined"==typeof Node||!(t instanceof Node))return;r.some(e=>{const n=e.current;return!!n&&n.contains(t)})||c.current(e)};return s.addEventListener(n,a,u),()=>{s.removeEventListener(n,a,u)}},[o,u,e])},e.useOpenGraphExtractor=function(e){const n=t.useMemo(()=>e.skipPatterns??w,[e.skipPatterns]),r=t.useCallback(e=>n.some(t=>t.test(e)),[n]),o=t.useCallback((e,t,n)=>{var r,o,i;const{openGraph:u,htmlInferred:c,hybridGraph:s}=e,a=b(null==u?void 0:u.description,null==s?void 0:s.description,null==c?void 0:c.description),l=b(null==u?void 0:u.title,null==s?void 0:s.title,null==c?void 0:c.title),d=b(null==u?void 0:u.site_name,null==s?void 0:s.site_name,null==c?void 0:c.site_name),f=b(null==s?void 0:s.favicon,null==c?void 0:c.favicon),m=b((null==(r=null==u?void 0:u.image)?void 0:r.url)??void 0,(null==s?void 0:s.image)??void 0,(null==c?void 0:c.image)??void 0,null==(o=null==c?void 0:c.images)?void 0:o[0]),p=b((null==(i=null==u?void 0:u.video)?void 0:i.url)??void 0,(null==s?void 0:s.video)??void 0),w=b(null==s?void 0:s.videoType,null==c?void 0:c.videoType),g=b(n.url,(null==u?void 0:u.url)??void 0,(null==s?void 0:s.url)??void 0,(null==c?void 0:c.url)??void 0,n.finalUrl,n.normalizedUrl,n.requestedUrl)??"";return{description:a,favicon:f,image:m,video:p,videoType:w,siteName:d,title:l,url:g,siteHost:h(g)}},[]);return p({endpoint:"open-graph",options:e,selectData:o,shouldSkip:r})},e.usePlatformFromUrl=function(e){return t.useMemo(()=>{if(!e||"string"!=typeof e)return"unknown";const t=e.trim();if(!t)return"unknown";try{const e=new URL(t).hostname.toLowerCase(),n=c.get(e);return n||(e.endsWith(".substack.com")?"substack":e.endsWith(".github.io")?"github":e.includes("pinterest.com")?"pinterest":e.includes("eventbrite.")?"eventbrite":e.endsWith(".medium.com")?"medium":e.endsWith(".behance.net")?"behance":"unknown")}catch{return"unknown"}},[e])},e.usePrevious=function(e){const r=t.useRef();return n(()=>{r.current=e},[e]),r.current},e.useResizeObserver=function(e,r,o){const i=t.useRef(r),u=t.useRef(null),[c,s]=t.useState(null);return n(()=>{i.current=r},[r]),t.useEffect(()=>{if("undefined"==typeof ResizeObserver)return;const t="undefined"!=typeof Element&&e instanceof Element?e:(n=e)&&"object"==typeof n&&"current"in n?e.current:null;var n;if(!t)return;const r=new ResizeObserver(e=>{const t=e[0];u.current=t,i.current?i.current(t):s(t)});return r.observe(t,o),()=>r.disconnect()},[o,e]),i.current?u.current:c},e.useScreen=function(e={}){const{breakpoints:n,screenTypeMapping:r,defaultScreenType:o="UNKNOWN",defaultTailwindSize:i="default"}=e,c=t.useMemo(()=>({...y,...n}),[n]),s=t.useMemo(()=>({...k,...r}),[r]),[a,l]=t.useState(()=>E()),d=u(`(min-width: ${c.sm}px)`),f=u(`(min-width: ${c.md}px)`),m=u(`(min-width: ${c.lg}px)`),p=u(`(min-width: ${c.xl}px)`),w=u(`(min-width: ${c["2xl"]}px)`),b=t.useCallback(()=>{"undefined"!=typeof window&&l(E())},[]);t.useEffect(()=>{if("undefined"==typeof window)return;l(E());const e=()=>{l(E())};return window.addEventListener("resize",e),()=>window.removeEventListener("resize",e)},[]);const h=t.useMemo(()=>d||f||m||p||w?w?"2xl":p?"xl":m?"lg":f?"md":d?"sm":"default":a.width>0?function(e,t){return e>=t["2xl"]?"2xl":e>=t.xl?"xl":e>=t.lg?"lg":e>=t.md?"md":e>=t.sm?"sm":"default"}(a.width,c):i,[d,f,m,p,w,a.width,c,i]),g=t.useMemo(()=>h===i&&0===a.width?o:s[h],[h,s,a.width,o,i]);return t.useMemo(()=>({width:a.width,height:a.height,tailwindSize:h,screenType:g,refresh:b}),[a.width,a.height,h,g,b])},e.useSessionStorage=function(e,n,r={}){const{initializeWithValue:o=!0,serialize:i=JSON.stringify,deserialize:u=JSON.parse,listenToStorageChanges:c=!1}=r,s=t.useRef(n),a=t.useCallback(()=>{if("undefined"==typeof window)return s.current;if(!o)return s.current;try{const t=window.sessionStorage.getItem(e);return t?u(t):s.current}catch{return s.current}},[u,o,e]),[l,d]=t.useState(()=>a()),f=t.useCallback(t=>{d(n=>{const r="function"==typeof t?t(n):t;if("undefined"!=typeof window)try{window.sessionStorage.setItem(e,i(r))}catch{}return r})},[e,i]);return t.useEffect(()=>{d(a())},[a]),t.useEffect(()=>{if("undefined"==typeof window||!c)return;const t=t=>{if(t.key===e)if(null!==t.newValue)try{d(u(t.newValue))}catch{d(s.current)}else d(s.current)};return window.addEventListener("storage",t),()=>window.removeEventListener("storage",t)},[u,e,c]),[l,f]},e.useThrottle=function(e,n,r={}){const o=r.leading??!0,i=r.trailing??!0,u=Math.max(0,n),[c,s]=t.useState(e),a=t.useRef(0),l=t.useRef(null),d=t.useRef(null);return t.useEffect(()=>{if(0===u)return void s(e);const t=Date.now();if(0===a.current)return a.current=t,o?void s(e):void(i&&!l.current&&(d.current=e,l.current=setTimeout(()=>{l.current=null,null!==d.current&&(s(d.current),d.current=null,a.current=Date.now())},u)));const n=t-a.current;if(n>=u&&o)return s(e),a.current=t,void(d.current=null);if(i&&(d.current=e,!l.current)){const e=Math.max(u-n,0);l.current=setTimeout(()=>{l.current=null,null!==d.current&&(s(d.current),d.current=null,a.current=Date.now())},e)}},[o,i,e,u]),t.useEffect(()=>()=>{l.current&&clearTimeout(l.current)},[]),c},e.useWebsiteLinksExtractor=function(e){return p({endpoint:"links",options:e,selectData:t.useCallback(e=>({totalLinks:e.totalLinks??0,uniqueDomains:e.uniqueDomains??0,links:e.links??[]}),[])})},e.useWebsiteMetaExtractor=function(e){return p({endpoint:"meta",options:e,selectData:t.useCallback(e=>({title:e.title??void 0,description:e.description??void 0,language:e.language??void 0,canonicalUrl:e.canonicalUrl??void 0,feedUrl:e.feedUrl??null,textContentLength:e.textContentLength??void 0,metaTags:e.metaTags??{}}),[])})},e.useWebsiteRssExtractor=function(e){return p({endpoint:"rss",options:e,selectData:t.useCallback(e=>({feedUrl:e.feedUrl??null,feeds:e.feeds??[]}),[])})},e.useWebsiteSchemaExtractor=function(e){return p({endpoint:"schema",options:e,selectData:t.useCallback(e=>({schema:e.schema??[],schemaTypes:e.schemaTypes??[]}),[])})},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
//# sourceMappingURL=opensite-hooks.umd.js.map

@@ -23,3 +23,5 @@ export { useBoolean } from "./useBoolean.js";

export { useWebsiteRssExtractor } from "./useWebsiteRssExtractor.js";
export { useIsTouchDevice } from "./useIsTouchDevice.js";
export { useScreen } from "./useScreen.js";
export { fetchWebsiteExtractor } from "./websiteExtractorService.js";
export { buildWebsiteExtractorUrl } from "./websiteExtractorService.js";
export { useBoolean } from "./useBoolean.js";
export type { UseBooleanResult } from "./useBoolean.js";
export { useDebounceCallback } from "./useDebounceCallback.js";
export type { DebounceOptions, DebouncedCallback } from "./useDebounceCallback.js";
export type { DebounceOptions, DebouncedCallback, } from "./useDebounceCallback.js";
export { useDebounceValue } from "./useDebounceValue.js";

@@ -37,4 +37,8 @@ export { useLocalStorage } from "./useLocalStorage.js";

export type { WebsiteRssFeed, WebsiteRssPayload, WebsiteRssResponse, } from "./useWebsiteRssExtractor.js";
export { useIsTouchDevice } from "./useIsTouchDevice.js";
export type { DeviceType, UseIsTouchDeviceOptions, UseIsTouchDeviceResult, } from "./useIsTouchDevice.js";
export { useScreen } from "./useScreen.js";
export type { ScreenBreakpoints, ScreenType, ScreenTypeMapping, TailwindSize, UseScreenOptions, UseScreenResult, } from "./useScreen.js";
export { fetchWebsiteExtractor } from "./websiteExtractorService.js";
export { buildWebsiteExtractorUrl } from "./websiteExtractorService.js";
export type { WebsiteExtractCacheMeta, WebsiteExtractMeta, WebsiteExtractorError, WebsiteExtractorOptions, WebsiteExtractorRequest, WebsiteExtractorResponse, WebsiteExtractorResult, WebsiteExtractorState, } from "./websiteExtractorTypes.js";

@@ -23,3 +23,5 @@ export { useBoolean } from "./useBoolean.js";

export { useWebsiteRssExtractor } from "./useWebsiteRssExtractor.js";
export { useIsTouchDevice } from "./useIsTouchDevice.js";
export { useScreen } from "./useScreen.js";
export { fetchWebsiteExtractor } from "./websiteExtractorService.js";
export { buildWebsiteExtractorUrl } from "./websiteExtractorService.js";
import { useEffect, useState } from "react";
export function useMediaQuery(query, options = {}) {
const [matches, setMatches] = useState(() => {
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
if (typeof window === "undefined" ||
typeof window.matchMedia !== "function") {
return options.defaultValue ?? false;

@@ -10,3 +11,4 @@ }

useEffect(() => {
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
if (typeof window === "undefined" ||
typeof window.matchMedia !== "function") {
return;

@@ -19,10 +21,6 @@ }

setMatches(mediaQueryList.matches);
if (mediaQueryList.addEventListener) {
mediaQueryList.addEventListener("change", handler);
return () => mediaQueryList.removeEventListener("change", handler);
}
mediaQueryList.addListener(handler);
return () => mediaQueryList.removeListener(handler);
mediaQueryList.addEventListener("change", handler);
return () => mediaQueryList.removeEventListener("change", handler);
}, [query]);
return matches;
}
import { useEffect, useState } from "react";
export function useMediaQuery(query, options = {}) {
const [matches, setMatches] = useState(() => {
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
if (typeof window === "undefined" ||
typeof window.matchMedia !== "function") {
return options.defaultValue ?? false;

@@ -10,3 +11,4 @@ }

useEffect(() => {
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
if (typeof window === "undefined" ||
typeof window.matchMedia !== "function") {
return;

@@ -19,10 +21,6 @@ }

setMatches(mediaQueryList.matches);
if (mediaQueryList.addEventListener) {
mediaQueryList.addEventListener("change", handler);
return () => mediaQueryList.removeEventListener("change", handler);
}
mediaQueryList.addListener(handler);
return () => mediaQueryList.removeListener(handler);
mediaQueryList.addEventListener("change", handler);
return () => mediaQueryList.removeEventListener("change", handler);
}, [query]);
return matches;
}
{
"name": "@opensite/hooks",
"version": "2.0.8",
"version": "2.1.0",
"description": "Performance-first React hooks for UI state, storage, events, and responsive behavior with tree-shakable exports.",

@@ -76,2 +76,7 @@ "keywords": [

},
"./useIsTouchDevice": {
"import": "./dist/core/useIsTouchDevice.js",
"require": "./dist/core/useIsTouchDevice.cjs",
"types": "./dist/core/useIsTouchDevice.d.ts"
},
"./useIsomorphicLayoutEffect": {

@@ -122,2 +127,7 @@ "import": "./dist/core/useIsomorphicLayoutEffect.js",

},
"./useScreen": {
"import": "./dist/core/useScreen.js",
"require": "./dist/core/useScreen.cjs",
"types": "./dist/core/useScreen.d.ts"
},
"./useSessionStorage": {

@@ -124,0 +134,0 @@ "import": "./dist/core/useSessionStorage.js",

@@ -95,2 +95,3 @@ ![Opensite AI Utility Hooks](https://octane.cdn.ing/api/v1/images/transform?url=https://cdn.ing/assets/i/r/285728/knsbi168qz1imlat2aq042c10rw8/opensite-react-hooks.png&q=90&f=webp)

| [`useMediaQuery`](./docs/useMediaQuery.md) | Reactive CSS media query matching | [View](./docs/useMediaQuery.md) |
| [`useScreen`](./docs/useScreen.md) | Viewport dimensions with Tailwind breakpoint and screen type detection | [View](./docs/useScreen.md) |
| **Utilities** | | |

@@ -100,2 +101,3 @@ | [`useCopyToClipboard`](./docs/useCopyToClipboard.md) | Copy text to clipboard with feedback state | [View](./docs/useCopyToClipboard.md) |

| [`useIsClient`](./docs/useIsClient.md) | Detect client-side vs server-side rendering | [View](./docs/useIsClient.md) |
| [`useIsTouchDevice`](./docs/useIsTouchDevice.md) | Detect touch-primary input with dynamic hybrid device updates | [View](./docs/useIsTouchDevice.md) |
| [`useIsomorphicLayoutEffect`](./docs/useIsomorphicLayoutEffect.md) | SSR-safe useLayoutEffect | [View](./docs/useIsomorphicLayoutEffect.md) |

@@ -102,0 +104,0 @@ | **Website Extractors** | | |

Sorry, the diff of this file is too big to display