485 lines
18 KiB
JavaScript
485 lines
18 KiB
JavaScript
"use client";
|
|
const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
|
|
const require_utils = require("./utils.cjs");
|
|
const require_ClientOnly = require("./ClientOnly.cjs");
|
|
const require_useRouter = require("./useRouter.cjs");
|
|
let _tanstack_router_core = require("@tanstack/router-core");
|
|
let react = require("react");
|
|
react = require_runtime.__toESM(react);
|
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
let _tanstack_react_store = require("@tanstack/react-store");
|
|
let _tanstack_router_core_isServer = require("@tanstack/router-core/isServer");
|
|
let react_dom = require("react-dom");
|
|
//#region src/link.tsx
|
|
/**
|
|
* Build anchor-like props for declarative navigation and preloading.
|
|
*
|
|
* Returns stable `href`, event handlers and accessibility props derived from
|
|
* router options and active state. Used internally by `Link` and custom links.
|
|
*
|
|
* Options cover `to`, `params`, `search`, `hash`, `state`, `preload`,
|
|
* `activeProps`, `inactiveProps`, and more.
|
|
*
|
|
* @returns React anchor props suitable for `<a>` or custom components.
|
|
* @link https://tanstack.com/router/latest/docs/framework/react/api/router/useLinkPropsHook
|
|
*/
|
|
function useLinkProps(options, forwardedRef) {
|
|
const router = require_useRouter.useRouter();
|
|
const innerRef = require_utils.useForwardedRef(forwardedRef);
|
|
const _isServer = _tanstack_router_core_isServer.isServer ?? router.isServer;
|
|
const { activeProps, inactiveProps, activeOptions, to, preload: userPreload, preloadDelay: userPreloadDelay, preloadIntentProximity: _preloadIntentProximity, hashScrollIntoView, replace, startTransition, resetScroll, viewTransition, children, target, disabled, style, className, onClick, onBlur, onFocus, onMouseEnter, onMouseLeave, onTouchStart, ignoreBlocker, params: _params, search: _search, hash: _hash, state: _state, mask: _mask, reloadDocument: _reloadDocument, unsafeRelative: _unsafeRelative, from: _from, _fromLocation, ...propsSafeToSpread } = options;
|
|
if (_isServer) {
|
|
const safeInternal = isSafeInternal(to);
|
|
if (typeof to === "string" && !safeInternal && to.indexOf(":") > -1) try {
|
|
new URL(to);
|
|
if ((0, _tanstack_router_core.isDangerousProtocol)(to, router.protocolAllowlist)) {
|
|
if (process.env.NODE_ENV !== "production") console.warn(`Blocked Link with dangerous protocol: ${to}`);
|
|
return {
|
|
...propsSafeToSpread,
|
|
ref: innerRef,
|
|
href: void 0,
|
|
...children && { children },
|
|
...target && { target },
|
|
...disabled && { disabled },
|
|
...style && { style },
|
|
...className && { className }
|
|
};
|
|
}
|
|
return {
|
|
...propsSafeToSpread,
|
|
ref: innerRef,
|
|
href: to,
|
|
...children && { children },
|
|
...target && { target },
|
|
...disabled && { disabled },
|
|
...style && { style },
|
|
...className && { className }
|
|
};
|
|
} catch {}
|
|
const next = router.buildLocation({
|
|
...options,
|
|
from: options.from
|
|
});
|
|
const hrefOption = getHrefOption(next.maskedLocation ? next.maskedLocation.publicHref : next.publicHref, next.maskedLocation ? next.maskedLocation.external : next.external, router.history, disabled);
|
|
const externalLink = (() => {
|
|
if (hrefOption?.external) {
|
|
if ((0, _tanstack_router_core.isDangerousProtocol)(hrefOption.href, router.protocolAllowlist)) {
|
|
if (process.env.NODE_ENV !== "production") console.warn(`Blocked Link with dangerous protocol: ${hrefOption.href}`);
|
|
return;
|
|
}
|
|
return hrefOption.href;
|
|
}
|
|
if (safeInternal) return void 0;
|
|
if (typeof to === "string" && to.indexOf(":") > -1) try {
|
|
new URL(to);
|
|
if ((0, _tanstack_router_core.isDangerousProtocol)(to, router.protocolAllowlist)) {
|
|
if (process.env.NODE_ENV !== "production") console.warn(`Blocked Link with dangerous protocol: ${to}`);
|
|
return;
|
|
}
|
|
return to;
|
|
} catch {}
|
|
})();
|
|
const isActive = (() => {
|
|
if (externalLink) return false;
|
|
const currentLocation = router.stores.location.get();
|
|
const exact = activeOptions?.exact ?? false;
|
|
if (exact) {
|
|
if (!(0, _tanstack_router_core.exactPathTest)(currentLocation.pathname, next.pathname, router.basepath)) return false;
|
|
} else {
|
|
const currentPathSplit = (0, _tanstack_router_core.removeTrailingSlash)(currentLocation.pathname, router.basepath);
|
|
const nextPathSplit = (0, _tanstack_router_core.removeTrailingSlash)(next.pathname, router.basepath);
|
|
if (!(currentPathSplit.startsWith(nextPathSplit) && (currentPathSplit.length === nextPathSplit.length || currentPathSplit[nextPathSplit.length] === "/"))) return false;
|
|
}
|
|
if (activeOptions?.includeSearch ?? true) {
|
|
if (currentLocation.search !== next.search) {
|
|
const currentSearchEmpty = !currentLocation.search || typeof currentLocation.search === "object" && Object.keys(currentLocation.search).length === 0;
|
|
const nextSearchEmpty = !next.search || typeof next.search === "object" && Object.keys(next.search).length === 0;
|
|
if (!(currentSearchEmpty && nextSearchEmpty)) {
|
|
if (!(0, _tanstack_router_core.deepEqual)(currentLocation.search, next.search, {
|
|
partial: !exact,
|
|
ignoreUndefined: !activeOptions?.explicitUndefined
|
|
})) return false;
|
|
}
|
|
}
|
|
}
|
|
if (activeOptions?.includeHash) return false;
|
|
return true;
|
|
})();
|
|
if (externalLink) return {
|
|
...propsSafeToSpread,
|
|
ref: innerRef,
|
|
href: externalLink,
|
|
...children && { children },
|
|
...target && { target },
|
|
...disabled && { disabled },
|
|
...style && { style },
|
|
...className && { className }
|
|
};
|
|
const resolvedActiveProps = isActive ? (0, _tanstack_router_core.functionalUpdate)(activeProps, {}) ?? STATIC_ACTIVE_OBJECT : STATIC_EMPTY_OBJECT;
|
|
const resolvedInactiveProps = isActive ? STATIC_EMPTY_OBJECT : (0, _tanstack_router_core.functionalUpdate)(inactiveProps, {}) ?? STATIC_EMPTY_OBJECT;
|
|
const resolvedStyle = (() => {
|
|
const baseStyle = style;
|
|
const activeStyle = resolvedActiveProps.style;
|
|
const inactiveStyle = resolvedInactiveProps.style;
|
|
if (!baseStyle && !activeStyle && !inactiveStyle) return;
|
|
if (baseStyle && !activeStyle && !inactiveStyle) return baseStyle;
|
|
if (!baseStyle && activeStyle && !inactiveStyle) return activeStyle;
|
|
if (!baseStyle && !activeStyle && inactiveStyle) return inactiveStyle;
|
|
return {
|
|
...baseStyle,
|
|
...activeStyle,
|
|
...inactiveStyle
|
|
};
|
|
})();
|
|
const resolvedClassName = (() => {
|
|
const baseClassName = className;
|
|
const activeClassName = resolvedActiveProps.className;
|
|
const inactiveClassName = resolvedInactiveProps.className;
|
|
if (!baseClassName && !activeClassName && !inactiveClassName) return "";
|
|
let out = "";
|
|
if (baseClassName) out = baseClassName;
|
|
if (activeClassName) out = out ? `${out} ${activeClassName}` : activeClassName;
|
|
if (inactiveClassName) out = out ? `${out} ${inactiveClassName}` : inactiveClassName;
|
|
return out;
|
|
})();
|
|
return {
|
|
...propsSafeToSpread,
|
|
...resolvedActiveProps,
|
|
...resolvedInactiveProps,
|
|
href: hrefOption?.href,
|
|
ref: innerRef,
|
|
disabled: !!disabled,
|
|
target,
|
|
...resolvedStyle && { style: resolvedStyle },
|
|
...resolvedClassName && { className: resolvedClassName },
|
|
...disabled && STATIC_DISABLED_PROPS,
|
|
...isActive && STATIC_ACTIVE_PROPS
|
|
};
|
|
}
|
|
const isHydrated = require_ClientOnly.useHydrated();
|
|
const _options = react.useMemo(() => options, [
|
|
router,
|
|
options.from,
|
|
options._fromLocation,
|
|
options.hash,
|
|
options.to,
|
|
options.search,
|
|
options.params,
|
|
options.state,
|
|
options.mask,
|
|
options.unsafeRelative
|
|
]);
|
|
const currentLocation = (0, _tanstack_react_store.useStore)(router.stores.location, (l) => l, (prev, next) => prev.href === next.href);
|
|
const next = react.useMemo(() => {
|
|
const opts = {
|
|
_fromLocation: currentLocation,
|
|
..._options
|
|
};
|
|
return router.buildLocation(opts);
|
|
}, [
|
|
router,
|
|
currentLocation,
|
|
_options
|
|
]);
|
|
const hrefOptionPublicHref = next.maskedLocation ? next.maskedLocation.publicHref : next.publicHref;
|
|
const hrefOptionExternal = next.maskedLocation ? next.maskedLocation.external : next.external;
|
|
const hrefOption = react.useMemo(() => getHrefOption(hrefOptionPublicHref, hrefOptionExternal, router.history, disabled), [
|
|
disabled,
|
|
hrefOptionExternal,
|
|
hrefOptionPublicHref,
|
|
router.history
|
|
]);
|
|
const externalLink = react.useMemo(() => {
|
|
if (hrefOption?.external) {
|
|
if ((0, _tanstack_router_core.isDangerousProtocol)(hrefOption.href, router.protocolAllowlist)) {
|
|
if (process.env.NODE_ENV !== "production") console.warn(`Blocked Link with dangerous protocol: ${hrefOption.href}`);
|
|
return;
|
|
}
|
|
return hrefOption.href;
|
|
}
|
|
if (isSafeInternal(to)) return void 0;
|
|
if (typeof to !== "string" || to.indexOf(":") === -1) return void 0;
|
|
try {
|
|
new URL(to);
|
|
if ((0, _tanstack_router_core.isDangerousProtocol)(to, router.protocolAllowlist)) {
|
|
if (process.env.NODE_ENV !== "production") console.warn(`Blocked Link with dangerous protocol: ${to}`);
|
|
return;
|
|
}
|
|
return to;
|
|
} catch {}
|
|
}, [
|
|
to,
|
|
hrefOption,
|
|
router.protocolAllowlist
|
|
]);
|
|
const isActive = react.useMemo(() => {
|
|
if (externalLink) return false;
|
|
if (activeOptions?.exact) {
|
|
if (!(0, _tanstack_router_core.exactPathTest)(currentLocation.pathname, next.pathname, router.basepath)) return false;
|
|
} else {
|
|
const currentPathSplit = (0, _tanstack_router_core.removeTrailingSlash)(currentLocation.pathname, router.basepath);
|
|
const nextPathSplit = (0, _tanstack_router_core.removeTrailingSlash)(next.pathname, router.basepath);
|
|
if (!(currentPathSplit.startsWith(nextPathSplit) && (currentPathSplit.length === nextPathSplit.length || currentPathSplit[nextPathSplit.length] === "/"))) return false;
|
|
}
|
|
if (activeOptions?.includeSearch ?? true) {
|
|
if (!(0, _tanstack_router_core.deepEqual)(currentLocation.search, next.search, {
|
|
partial: !activeOptions?.exact,
|
|
ignoreUndefined: !activeOptions?.explicitUndefined
|
|
})) return false;
|
|
}
|
|
if (activeOptions?.includeHash) return isHydrated && currentLocation.hash === next.hash;
|
|
return true;
|
|
}, [
|
|
activeOptions?.exact,
|
|
activeOptions?.explicitUndefined,
|
|
activeOptions?.includeHash,
|
|
activeOptions?.includeSearch,
|
|
currentLocation,
|
|
externalLink,
|
|
isHydrated,
|
|
next.hash,
|
|
next.pathname,
|
|
next.search,
|
|
router.basepath
|
|
]);
|
|
const resolvedActiveProps = isActive ? (0, _tanstack_router_core.functionalUpdate)(activeProps, {}) ?? STATIC_ACTIVE_OBJECT : STATIC_EMPTY_OBJECT;
|
|
const resolvedInactiveProps = isActive ? STATIC_EMPTY_OBJECT : (0, _tanstack_router_core.functionalUpdate)(inactiveProps, {}) ?? STATIC_EMPTY_OBJECT;
|
|
const resolvedClassName = [
|
|
className,
|
|
resolvedActiveProps.className,
|
|
resolvedInactiveProps.className
|
|
].filter(Boolean).join(" ");
|
|
const resolvedStyle = (style || resolvedActiveProps.style || resolvedInactiveProps.style) && {
|
|
...style,
|
|
...resolvedActiveProps.style,
|
|
...resolvedInactiveProps.style
|
|
};
|
|
const [isTransitioning, setIsTransitioning] = react.useState(false);
|
|
const hasRenderFetched = react.useRef(false);
|
|
const preload = options.reloadDocument || externalLink ? false : userPreload ?? router.options.defaultPreload;
|
|
const preloadDelay = userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0;
|
|
const doPreload = react.useCallback(() => {
|
|
router.preloadRoute({
|
|
..._options,
|
|
_builtLocation: next
|
|
}).catch((err) => {
|
|
console.warn(err);
|
|
console.warn(_tanstack_router_core.preloadWarning);
|
|
});
|
|
}, [
|
|
router,
|
|
_options,
|
|
next
|
|
]);
|
|
require_utils.useIntersectionObserver(innerRef, react.useCallback((entry) => {
|
|
if (entry?.isIntersecting) doPreload();
|
|
}, [doPreload]), intersectionObserverOptions, { disabled: !!disabled || !(preload === "viewport") });
|
|
react.useEffect(() => {
|
|
if (hasRenderFetched.current) return;
|
|
if (!disabled && preload === "render") {
|
|
doPreload();
|
|
hasRenderFetched.current = true;
|
|
}
|
|
}, [
|
|
disabled,
|
|
doPreload,
|
|
preload
|
|
]);
|
|
const handleClick = (e) => {
|
|
const elementTarget = e.currentTarget.getAttribute("target");
|
|
const effectiveTarget = target !== void 0 ? target : elementTarget;
|
|
if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!effectiveTarget || effectiveTarget === "_self") && e.button === 0) {
|
|
e.preventDefault();
|
|
(0, react_dom.flushSync)(() => {
|
|
setIsTransitioning(true);
|
|
});
|
|
const unsub = router.subscribe("onResolved", () => {
|
|
unsub();
|
|
setIsTransitioning(false);
|
|
});
|
|
router.navigate({
|
|
..._options,
|
|
replace,
|
|
resetScroll,
|
|
hashScrollIntoView,
|
|
startTransition,
|
|
viewTransition,
|
|
ignoreBlocker
|
|
});
|
|
}
|
|
};
|
|
if (externalLink) return {
|
|
...propsSafeToSpread,
|
|
ref: innerRef,
|
|
href: externalLink,
|
|
...children && { children },
|
|
...target && { target },
|
|
...disabled && { disabled },
|
|
...style && { style },
|
|
...className && { className },
|
|
...onClick && { onClick },
|
|
...onBlur && { onBlur },
|
|
...onFocus && { onFocus },
|
|
...onMouseEnter && { onMouseEnter },
|
|
...onMouseLeave && { onMouseLeave },
|
|
...onTouchStart && { onTouchStart }
|
|
};
|
|
const enqueueIntentPreload = (e) => {
|
|
if (disabled || preload !== "intent") return;
|
|
if (!preloadDelay) {
|
|
doPreload();
|
|
return;
|
|
}
|
|
const eventTarget = e.currentTarget;
|
|
if (timeoutMap.has(eventTarget)) return;
|
|
const id = setTimeout(() => {
|
|
timeoutMap.delete(eventTarget);
|
|
doPreload();
|
|
}, preloadDelay);
|
|
timeoutMap.set(eventTarget, id);
|
|
};
|
|
const handleTouchStart = (_) => {
|
|
if (disabled || preload !== "intent") return;
|
|
doPreload();
|
|
};
|
|
const handleLeave = (e) => {
|
|
if (disabled || !preload || !preloadDelay) return;
|
|
const eventTarget = e.currentTarget;
|
|
const id = timeoutMap.get(eventTarget);
|
|
if (id) {
|
|
clearTimeout(id);
|
|
timeoutMap.delete(eventTarget);
|
|
}
|
|
};
|
|
return {
|
|
...propsSafeToSpread,
|
|
...resolvedActiveProps,
|
|
...resolvedInactiveProps,
|
|
href: hrefOption?.href,
|
|
ref: innerRef,
|
|
onClick: composeHandlers([onClick, handleClick]),
|
|
onBlur: composeHandlers([onBlur, handleLeave]),
|
|
onFocus: composeHandlers([onFocus, enqueueIntentPreload]),
|
|
onMouseEnter: composeHandlers([onMouseEnter, enqueueIntentPreload]),
|
|
onMouseLeave: composeHandlers([onMouseLeave, handleLeave]),
|
|
onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
|
|
disabled: !!disabled,
|
|
target,
|
|
...resolvedStyle && { style: resolvedStyle },
|
|
...resolvedClassName && { className: resolvedClassName },
|
|
...disabled && STATIC_DISABLED_PROPS,
|
|
...isActive && STATIC_ACTIVE_PROPS,
|
|
...isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS
|
|
};
|
|
}
|
|
var STATIC_EMPTY_OBJECT = {};
|
|
var STATIC_ACTIVE_OBJECT = { className: "active" };
|
|
var STATIC_DISABLED_PROPS = {
|
|
role: "link",
|
|
"aria-disabled": true
|
|
};
|
|
var STATIC_ACTIVE_PROPS = {
|
|
"data-status": "active",
|
|
"aria-current": "page"
|
|
};
|
|
var STATIC_TRANSITIONING_PROPS = { "data-transitioning": "transitioning" };
|
|
var timeoutMap = /* @__PURE__ */ new WeakMap();
|
|
var intersectionObserverOptions = { rootMargin: "100px" };
|
|
var composeHandlers = (handlers) => (e) => {
|
|
for (const handler of handlers) {
|
|
if (!handler) continue;
|
|
if (e.defaultPrevented) return;
|
|
handler(e);
|
|
}
|
|
};
|
|
function getHrefOption(publicHref, external, history, disabled) {
|
|
if (disabled) return void 0;
|
|
if (external) return {
|
|
href: publicHref,
|
|
external: true
|
|
};
|
|
return {
|
|
href: history.createHref(publicHref) || "/",
|
|
external: false
|
|
};
|
|
}
|
|
function isSafeInternal(to) {
|
|
if (typeof to !== "string") return false;
|
|
const zero = to.charCodeAt(0);
|
|
if (zero === 47) return to.charCodeAt(1) !== 47;
|
|
return zero === 46;
|
|
}
|
|
/**
|
|
* Creates a typed Link-like component that preserves TanStack Router's
|
|
* navigation semantics and type-safety while delegating rendering to the
|
|
* provided host component.
|
|
*
|
|
* Useful for integrating design system anchors/buttons while keeping
|
|
* router-aware props (eg. `to`, `params`, `search`, `preload`).
|
|
*
|
|
* @param Comp The host component to render (eg. a design-system Link/Button)
|
|
* @returns A router-aware component with the same API as `Link`.
|
|
* @link https://tanstack.com/router/latest/docs/framework/react/guide/custom-link
|
|
*/
|
|
function createLink(Comp) {
|
|
return react.forwardRef(function CreatedLink(props, ref) {
|
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Link, {
|
|
...props,
|
|
_asChild: Comp,
|
|
ref
|
|
});
|
|
});
|
|
}
|
|
/**
|
|
* A strongly-typed anchor component for declarative navigation.
|
|
* Handles path, search, hash and state updates with optional route preloading
|
|
* and active-state styling.
|
|
*
|
|
* Props:
|
|
* - `preload`: Controls route preloading (eg. 'intent', 'render', 'viewport', true/false)
|
|
* - `preloadDelay`: Delay in ms before preloading on hover
|
|
* - `activeProps`/`inactiveProps`: Additional props merged when link is active/inactive
|
|
* - `resetScroll`/`hashScrollIntoView`: Control scroll behavior on navigation
|
|
* - `viewTransition`/`startTransition`: Use View Transitions/React transitions for navigation
|
|
* - `ignoreBlocker`: Bypass registered blockers
|
|
*
|
|
* @returns An anchor-like element that navigates without full page reloads.
|
|
* @link https://tanstack.com/router/latest/docs/framework/react/api/router/linkComponent
|
|
*/
|
|
var Link = react.forwardRef((props, ref) => {
|
|
const { _asChild, ...rest } = props;
|
|
const { type: _type, ...linkProps } = useLinkProps(rest, ref);
|
|
const children = typeof rest.children === "function" ? rest.children({ isActive: linkProps["data-status"] === "active" }) : rest.children;
|
|
if (!_asChild) {
|
|
const { disabled: _, ...rest } = linkProps;
|
|
return react.createElement("a", rest, children);
|
|
}
|
|
return react.createElement(_asChild, linkProps, children);
|
|
});
|
|
function isCtrlEvent(e) {
|
|
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
|
|
}
|
|
/**
|
|
* Validate and reuse navigation options for `Link`, `navigate` or `redirect`.
|
|
* Accepts a literal options object and returns it typed for later spreading.
|
|
* @example
|
|
* const opts = linkOptions({ to: '/dashboard', search: { tab: 'home' } })
|
|
* @link https://tanstack.com/router/latest/docs/framework/react/api/router/linkOptions
|
|
*/
|
|
var linkOptions = (options) => {
|
|
return options;
|
|
};
|
|
/**
|
|
* Type-check a literal object for use with `Link`, `navigate` or `redirect`.
|
|
* Use to validate and reuse navigation options across your app.
|
|
* @example
|
|
* const opts = linkOptions({ to: '/dashboard', search: { tab: 'home' } })
|
|
* @link https://tanstack.com/router/latest/docs/framework/react/api/router/linkOptions
|
|
*/
|
|
//#endregion
|
|
exports.Link = Link;
|
|
exports.createLink = createLink;
|
|
exports.linkOptions = linkOptions;
|
|
exports.useLinkProps = useLinkProps;
|
|
|
|
//# sourceMappingURL=link.cjs.map
|