clear-react-router 1.9.3 → 1.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -107,6 +107,8 @@ Component for client-side navigation with prefetch support, active state detecti
107
107
  | Prop | Type | Default | Description |
108
108
  |------|------|---------|-------------|
109
109
  | `to` | `string` | required | Target path |
110
+ | `as` | `(props: ElementProps<T>) => ReactElement` | renders `<a>` | Render prop for using a custom element/component instead of the default `<a>`. Receives the computed isActive and isPending values, event handlers, and ref to attach to your own element |
111
+ | `exact` | `boolean` | `false` | When `false`, the link is also considered active if the current URL starts with `to` (useful for nested routes) |
110
112
  | `prefetch` | `'hover' \| 'render' \| 'viewport' \| 'none'` | `Router` config | Override the global prefetch strategy |
111
113
  | `hoverPrefetchDelay` | `number` | `Router` config | Override the global hover delay |
112
114
  | `children` | `ReactNode` | required | Content to render inside the link |
@@ -120,7 +122,7 @@ Component for client-side navigation with prefetch support, active state detecti
120
122
 
121
123
  | State | Type | Description |
122
124
  |-------|------|-------------|
123
- | `isActive` | `boolean` | `true` when the link's `to` matches the current URL |
125
+ | `isActive` | `boolean` | `true` when the link's `to` matches the current URL considering `exact` value |
124
126
  | `isPending` | `boolean` | `true` when the target route is currently loading (loader is running) |
125
127
 
126
128
  ### Prefetch Strategies
@@ -137,6 +139,22 @@ Component for client-side navigation with prefetch support, active state detecti
137
139
  ```tsx
138
140
  import { Router, Link } from 'clear-react-router';
139
141
 
142
+ // Render a custom element/component via `as`. The function receives ref, event handlers, isActive/isPending and must render them itself
143
+ import { Button } from '@mui/material';
144
+
145
+ <Link
146
+ to="/dashboard"
147
+ as={({ isActive, isPending, ...props }) => (
148
+ <Button
149
+ {...props}
150
+ variant={isActive ? 'contained' : 'outlined'}
151
+ sx={{ opacity: isPending ? 0.5 : 1 }}
152
+ />
153
+ )}
154
+ >
155
+ Dashboard
156
+ </Link>
157
+
140
158
  // Global prefetch: hover with 100ms delay
141
159
  <Router routes={routes} prefetch="hover" hoverPrefetchDelay={100} />
142
160
 
@@ -169,6 +187,12 @@ import { Router, Link } from 'clear-react-router';
169
187
  <Link to="/details" beforeNavigate={saveDashboardData}>
170
188
  Admin Panel
171
189
  </Link>
190
+
191
+ // `exact={false}` — active for nested routes too
192
+ // e.g. active when current URL is "/settings" or "/settings/profile"
193
+ <Link to="/settings" exact={false}>
194
+ Settings
195
+ </Link>
172
196
  ```
173
197
  **Important**: prefetch="render" should be used sparingly, as it preloads data immediately when the link is rendered, which may cause unnecessary network requests.
174
198
 
@@ -1,19 +1,33 @@
1
- import { type CSSProperties, ReactNode, ComponentPropsWithoutRef } from 'react';
1
+ import { type CSSProperties, ReactNode, MouseEvent, ReactElement, Ref } from 'react';
2
2
  import { RouterProps } from '../types';
3
3
  type States = {
4
4
  isActive: boolean;
5
5
  isPending: boolean;
6
6
  };
7
- type LinkProps = Omit<ComponentPropsWithoutRef<'a'>, 'href' | 'className' | 'style'> & {
7
+ type ElementProps<T extends HTMLElement = HTMLElement> = {
8
+ ref: Ref<T>;
9
+ href: string;
10
+ isActive: boolean;
11
+ isPending: boolean;
12
+ onClick(event: MouseEvent): void;
13
+ onMouseEnter(event: MouseEvent): void;
14
+ onMouseLeave(event: MouseEvent): void;
15
+ className?: string;
16
+ style?: CSSProperties;
17
+ children?: ReactNode;
18
+ };
19
+ type LinkProps<T extends HTMLElement = HTMLAnchorElement> = {
8
20
  to: string;
9
- children: ReactNode;
21
+ children?: ReactNode;
22
+ as?: (props: ElementProps<T>) => ReactElement;
10
23
  prefetch?: RouterProps['prefetch'];
11
24
  hoverPrefetchDelay?: number;
12
- style?: CSSProperties | ((arg: States) => CSSProperties);
13
25
  className?: string | ((arg: States) => string);
14
26
  activeClassName?: string;
15
27
  pendingClassName?: string;
16
28
  beforeNavigate?(): Promise<void>;
29
+ style?: CSSProperties | ((arg: States) => CSSProperties);
30
+ exact?: boolean;
17
31
  };
18
- export declare const Link: ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, className, style, beforeNavigate, activeClassName, pendingClassName, }: LinkProps) => import("react/jsx-runtime").JSX.Element;
32
+ export declare const Link: <T extends HTMLElement = HTMLAnchorElement>({ children, to, as, prefetch: prefetchLink, hoverPrefetchDelay, className, style, beforeNavigate, exact, activeClassName, pendingClassName, }: LinkProps<T>) => ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
19
33
  export {};
package/dist/index.js CHANGED
@@ -44,7 +44,7 @@ var createCommitState = ({ routeItemDataState, pendingState }) => (nextLocation,
44
44
  //#region constants.ts
45
45
  var emptyLoaderState = {};
46
46
  //#endregion
47
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/typeof.js
47
+ //#region \0@oxc-project+runtime@0.132.0/helpers/typeof.js
48
48
  function _typeof(o) {
49
49
  "@babel/helpers - typeof";
50
50
  return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
@@ -54,7 +54,7 @@ function _typeof(o) {
54
54
  }, _typeof(o);
55
55
  }
56
56
  //#endregion
57
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPrimitive.js
57
+ //#region \0@oxc-project+runtime@0.132.0/helpers/toPrimitive.js
58
58
  function toPrimitive(t, r) {
59
59
  if ("object" != _typeof(t) || !t) return t;
60
60
  var e = t[Symbol.toPrimitive];
@@ -66,13 +66,13 @@ function toPrimitive(t, r) {
66
66
  return ("string" === r ? String : Number)(t);
67
67
  }
68
68
  //#endregion
69
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPropertyKey.js
69
+ //#region \0@oxc-project+runtime@0.132.0/helpers/toPropertyKey.js
70
70
  function toPropertyKey(t) {
71
71
  var i = toPrimitive(t, "string");
72
72
  return "symbol" == _typeof(i) ? i : i + "";
73
73
  }
74
74
  //#endregion
75
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/defineProperty.js
75
+ //#region \0@oxc-project+runtime@0.132.0/helpers/defineProperty.js
76
76
  function _defineProperty(e, r, t) {
77
77
  return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
78
78
  value: t,
@@ -756,12 +756,13 @@ var useLocation = () => {
756
756
  };
757
757
  //#endregion
758
758
  //#region components/Link.tsx
759
- var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, className, style, beforeNavigate, activeClassName = "active-link", pendingClassName = "pending-link" }) => {
759
+ var defaultAs = ({ isActive, isPending, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { ...props });
760
+ var Link = ({ children, to, as = defaultAs, prefetch: prefetchLink, hoverPrefetchDelay, className, style, beforeNavigate, exact = false, activeClassName = "active-link", pendingClassName = "pending-link" }) => {
760
761
  const isPending = useIsRoutePending(to);
761
762
  const { pathname } = useLocation();
762
763
  const navigate = useNavigate();
763
764
  const timeout = useRef(0);
764
- const ref = useRef(null);
765
+ const elementRef = useRef(null);
765
766
  const { prefetch: configPrefetch, hoverPrefetchDelay: configPrefetchDelay } = routerConfig;
766
767
  const prefetch = prefetchLink || configPrefetch;
767
768
  const prefetchDelay = hoverPrefetchDelay ?? configPrefetchDelay;
@@ -789,17 +790,19 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, classNam
789
790
  }, [prefetch, to]);
790
791
  useEffect(() => {
791
792
  if (prefetch !== "viewport") return;
792
- const element = ref.current;
793
+ const element = elementRef.current;
794
+ if (!element) return;
793
795
  const observer = new IntersectionObserver(async () => {
794
796
  await router.runtime.prefetch(to);
795
797
  observer.disconnect();
796
798
  });
797
- if (element) observer.observe(element);
798
- return () => {
799
- if (element) observer.disconnect();
800
- };
799
+ observer.observe(element);
800
+ return () => observer.disconnect();
801
801
  }, [prefetch, to]);
802
- const isActive = to === pathname;
802
+ useEffect(() => () => {
803
+ if (timeout.current) clearTimeout(timeout.current);
804
+ }, []);
805
+ const isActive = to === "/" ? pathname === "/" : exact ? pathname === to : pathname === to || pathname?.startsWith(`${to}/`);
803
806
  const normalizedClassName = typeof className === "function" ? className({
804
807
  isActive,
805
808
  isPending
@@ -814,18 +817,21 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, classNam
814
817
  normalizedClassName
815
818
  ].filter(Boolean).join(" ");
816
819
  const clickHandler = async (event) => {
820
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
817
821
  event.preventDefault();
818
822
  await beforeNavigate?.();
819
823
  await navigate(to);
820
824
  };
821
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
825
+ return as({
826
+ ref: elementRef,
822
827
  href: to,
823
- ref,
824
828
  style: normalizedStyle,
825
829
  className: resultClassName,
826
830
  onClick: clickHandler,
827
831
  onMouseEnter,
828
832
  onMouseLeave,
833
+ isActive,
834
+ isPending,
829
835
  children
830
836
  });
831
837
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-react-router",
3
- "version": "1.9.3",
3
+ "version": "1.9.4",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {