clear-react-router 1.8.3 → 1.8.5
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 +33 -6
- package/dist/components/Link.d.ts +14 -6
- package/dist/hooks/useIsRoutePending.d.ts +1 -0
- package/dist/index.js +70 -71
- package/dist/types.d.ts +2 -4
- package/dist/utils/findRoute.d.ts +1 -1
- package/dist/utils/utils.d.ts +2 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ It provides first-class support for:
|
|
|
70
70
|
|
|
71
71
|
### `createRouter(routes)`
|
|
72
72
|
|
|
73
|
-
Normalizes route configuration.
|
|
73
|
+
Normalizes route configuration. Extracts dynamic params, builds nested paths.
|
|
74
74
|
|
|
75
75
|
| Property | Type | Description |
|
|
76
76
|
|----------|------|-------------|
|
|
@@ -88,14 +88,26 @@ Normalizes route configuration. Handles wildcard `*` routes, extracts dynamic pa
|
|
|
88
88
|
|
|
89
89
|
### `Link`
|
|
90
90
|
|
|
91
|
-
Component for client-side navigation with prefetch support.
|
|
91
|
+
Component for client-side navigation with prefetch support, active state detection, and pending state styling.
|
|
92
92
|
|
|
93
|
-
| Prop | Type | Default |
|
|
94
|
-
|
|
95
|
-
| `to` | `string` | required |
|
|
93
|
+
| Prop | Type | Default | Description |
|
|
94
|
+
|------|------|---------|-------------|
|
|
95
|
+
| `to` | `string` | required | Target path |
|
|
96
96
|
| `prefetch` | `'hover' \| 'render' \| 'viewport' \| 'none'` | `Router` config | Override the global prefetch strategy |
|
|
97
97
|
| `hoverPrefetchDelay` | `number` | `Router` config | Override the global hover delay |
|
|
98
|
-
| `children` | `
|
|
98
|
+
| `children` | `ReactNode` | required | Content to render inside the link |
|
|
99
|
+
| `className` | `string \| ({ isActive, isPending }) => string` | `undefined` | CSS class name(s). Can be a function for dynamic styling |
|
|
100
|
+
| `style` | `CSSProperties \| ({ isActive, isPending }) => CSSProperties` | `undefined` | Inline styles. Can be a function for dynamic styling |
|
|
101
|
+
| `activeClassName` | `string` optional | `'active-link'` | Class name applied when the link matches the current URL |
|
|
102
|
+
| `pendingClassName` | `string` optional | `'pending-link'` | Class name applied when the link's target is loading |
|
|
103
|
+
| `onClick` | `() => void` | `undefined` | Callback fired before navigation |
|
|
104
|
+
|
|
105
|
+
**State values:**
|
|
106
|
+
|
|
107
|
+
| State | Type | Description |
|
|
108
|
+
|-------|------|-------------|
|
|
109
|
+
| `isActive` | `boolean` | `true` when the link's `to` matches the current URL |
|
|
110
|
+
| `isPending` | `boolean` | `true` when the target route is currently loading (loader is running) |
|
|
99
111
|
|
|
100
112
|
### Prefetch Strategies
|
|
101
113
|
|
|
@@ -123,6 +135,21 @@ import { Router, Link } from 'clear-react-router';
|
|
|
123
135
|
<Link to="/admin" prefetch="none">
|
|
124
136
|
Admin Panel
|
|
125
137
|
</Link>
|
|
138
|
+
|
|
139
|
+
// With custom active/pending classes
|
|
140
|
+
<Link to="/settings" activeClassName="active-nav-link" pendingClassName="loading-nav-link">
|
|
141
|
+
Settings
|
|
142
|
+
</Link>
|
|
143
|
+
|
|
144
|
+
// With dynamic className
|
|
145
|
+
<Link to="/dashboard" className={({ isActive, isPending }) => isActive ? 'text-blue-600' : isPending ? 'text-gray-400' : 'text-gray-600'}>
|
|
146
|
+
Dashboard
|
|
147
|
+
</Link>
|
|
148
|
+
|
|
149
|
+
// With dynamic style
|
|
150
|
+
<Link to="/profile" style={({ isActive }) => ({ fontWeight: isActive ? 'bold' : 'normal' })}>
|
|
151
|
+
Profile
|
|
152
|
+
</Link>
|
|
126
153
|
```
|
|
127
154
|
**Important**: prefetch="render" should be used sparingly, as it preloads data immediately when the link is rendered, which may cause unnecessary network requests.
|
|
128
155
|
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type CSSProperties, ReactNode } from 'react';
|
|
2
2
|
import { RouterProps } from '../types';
|
|
3
3
|
type LinkProps = {
|
|
4
4
|
to: string;
|
|
5
|
-
children:
|
|
6
|
-
onClick: (e: MouseEvent) => void;
|
|
7
|
-
style: CSSProperties;
|
|
8
|
-
}>;
|
|
5
|
+
children: ReactNode;
|
|
9
6
|
prefetch?: RouterProps['prefetch'];
|
|
10
7
|
hoverPrefetchDelay?: number;
|
|
8
|
+
style?: CSSProperties | (({ isActive }: {
|
|
9
|
+
isActive: boolean;
|
|
10
|
+
isPending: boolean;
|
|
11
|
+
}) => CSSProperties);
|
|
12
|
+
className?: string | (({ isActive }: {
|
|
13
|
+
isActive: boolean;
|
|
14
|
+
isPending: boolean;
|
|
15
|
+
}) => string);
|
|
16
|
+
activeClassName?: string;
|
|
17
|
+
pendingClassName?: string;
|
|
18
|
+
onClick?(): void;
|
|
11
19
|
};
|
|
12
|
-
export declare const Link: ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }: LinkProps) => import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export declare const Link: ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, className, style, onClick, activeClassName, pendingClassName, }: LinkProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
21
|
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useIsRoutePending: (routePath: string) => boolean;
|
package/dist/index.js
CHANGED
|
@@ -174,62 +174,45 @@ var createLazyComponent = (importFn, fallback) => {
|
|
|
174
174
|
//#endregion
|
|
175
175
|
//#region utils/utils.ts
|
|
176
176
|
var isLazy = (el) => typeof el.element === "function" && el.element.toString().includes("import(");
|
|
177
|
-
var parseClientRouteItem = (el,
|
|
178
|
-
const
|
|
179
|
-
const staticSegments = [];
|
|
180
|
-
const currentParams = [...parentParams];
|
|
181
|
-
let lastStaticSegment = "";
|
|
182
|
-
for (const segment of segments) if (segment.startsWith(":")) {
|
|
183
|
-
if (!lastStaticSegment) throw new Error(`Route "${el.path}" cannot start with a parameter.`);
|
|
184
|
-
currentParams.push({
|
|
185
|
-
key: lastStaticSegment,
|
|
186
|
-
value: segment.slice(1)
|
|
187
|
-
});
|
|
188
|
-
} else {
|
|
189
|
-
lastStaticSegment = segment;
|
|
190
|
-
staticSegments.push(segment);
|
|
191
|
-
}
|
|
192
|
-
const path = `${parentPath}/${staticSegments.join("/")}`.replace(/\/+/g, "/");
|
|
177
|
+
var parseClientRouteItem = (el, parentPattern = "") => {
|
|
178
|
+
const pattern = `${parentPattern}/${el.path}`.replace(/\/+/g, "/");
|
|
193
179
|
const resolvedElement = isLazy(el) ? createLazyComponent(el.element, el.fallback) : el.element;
|
|
194
180
|
return [{
|
|
195
181
|
...el,
|
|
196
|
-
|
|
197
|
-
params: currentParams,
|
|
182
|
+
pattern,
|
|
198
183
|
element: resolvedElement
|
|
199
|
-
}, ...el.children?.flatMap((child) => parseClientRouteItem(child,
|
|
184
|
+
}, ...el.children?.flatMap((child) => parseClientRouteItem(child, pattern)) ?? []];
|
|
200
185
|
};
|
|
201
|
-
var createRouter = (clientList) => clientList.flatMap((el) => parseClientRouteItem(el
|
|
202
|
-
var getParamsObject = (
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
186
|
+
var createRouter = (clientList) => clientList.flatMap((el) => parseClientRouteItem(el));
|
|
187
|
+
var getParamsObject = (nextItem, nextPathname) => {
|
|
188
|
+
const { routeItem: stateItem, location: { pathname: statePathname } } = router.state.routeItemDataState.getState();
|
|
189
|
+
const routeItem = nextItem || stateItem;
|
|
190
|
+
const pathname = nextPathname || statePathname;
|
|
191
|
+
if (!routeItem) return {};
|
|
192
|
+
const pathnameSegments = pathname.split("/");
|
|
193
|
+
return routeItem.pattern.split("/").reduce((acc, segment, index) => {
|
|
194
|
+
if (segment.startsWith(":")) acc[segment.slice(1)] = pathnameSegments[index];
|
|
195
|
+
return acc;
|
|
196
|
+
}, {});
|
|
212
197
|
};
|
|
213
198
|
var parseWindowLocation = (location) => ({
|
|
214
199
|
pathname: location.pathname,
|
|
215
200
|
search: location.search
|
|
216
201
|
});
|
|
217
|
-
var comparePaths = (
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
const splitPathname = pathname.split("/").filter(Boolean);
|
|
222
|
-
return splitElementPath.every((item, index) => item === splitPathname[2 * index]) && splitPathname.length === splitElementPath.length + paramsLength;
|
|
202
|
+
var comparePaths = (route, pathname) => {
|
|
203
|
+
const pattern = route.pattern.split("/").filter(Boolean);
|
|
204
|
+
const current = pathname.split("/").filter(Boolean);
|
|
205
|
+
return pattern.length === current.length ? pattern.every((segment, index) => segment.startsWith(":") || segment === current[index]) : false;
|
|
223
206
|
};
|
|
224
207
|
var findRoute = (pathname, includeAll) => {
|
|
225
|
-
if (includeAll) return routerConfig.routes.find((el) => el.path === "
|
|
208
|
+
if (includeAll) return routerConfig.routes.find((el) => el.path === "*" || comparePaths(el, pathname));
|
|
226
209
|
return routerConfig.routes.find((el) => comparePaths(el, pathname));
|
|
227
210
|
};
|
|
228
211
|
//#endregion
|
|
229
212
|
//#region runtime/navigate.ts
|
|
230
213
|
var navigationSeq = 0;
|
|
231
214
|
var createNavigate = (routerState, revalidateCache) => {
|
|
232
|
-
const { loaderStateRef, scrollMapState, prevPathnameRef, loaderFallbackState, isLoadingState, contextState, timestampMap } = routerState;
|
|
215
|
+
const { loaderStateRef, scrollMapState, prevPathnameRef, loaderFallbackState, isLoadingState, contextState, timestampMap, pendingPathRef } = routerState;
|
|
233
216
|
const commitNavigation = createCommitNavigation(createCommitState(routerState), prevPathnameRef);
|
|
234
217
|
const isCacheItemFresh = createIsCacheItemFresh(timestampMap);
|
|
235
218
|
const getContext = () => ({
|
|
@@ -241,10 +224,7 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
241
224
|
const nextItem = findRoute(location.pathname, true);
|
|
242
225
|
return {
|
|
243
226
|
nextItem,
|
|
244
|
-
params: getParamsObject(
|
|
245
|
-
params: nextItem?.params,
|
|
246
|
-
pathname: location.pathname
|
|
247
|
-
})
|
|
227
|
+
params: getParamsObject(nextItem, location.pathname)
|
|
248
228
|
};
|
|
249
229
|
};
|
|
250
230
|
const beforeLoad = async (routeItem, params) => {
|
|
@@ -289,10 +269,12 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
289
269
|
const loader = async (routeItem, location) => {
|
|
290
270
|
if (!routeItem?.loader) return;
|
|
291
271
|
isLoadingState.setState(true);
|
|
272
|
+
pendingPathRef.set(location.pathname);
|
|
292
273
|
await revalidateCache({
|
|
293
274
|
routeItem,
|
|
294
275
|
pathname: location.pathname
|
|
295
276
|
});
|
|
277
|
+
pendingPathRef.set("");
|
|
296
278
|
};
|
|
297
279
|
const afterLoad = async (routeItem, params) => {
|
|
298
280
|
const { afterLoad } = routerConfig;
|
|
@@ -326,10 +308,7 @@ var createInvalidate = ({ routeItemDataState, loaderStateRef, timestampMap, curr
|
|
|
326
308
|
const invalidatePath = async (routeItem, pathname, options) => {
|
|
327
309
|
const routePathname = routeItemDataState.getState().location.pathname;
|
|
328
310
|
timestampMap.delete(pathname);
|
|
329
|
-
const params = getParamsObject(
|
|
330
|
-
params: routeItem?.params,
|
|
331
|
-
pathname
|
|
332
|
-
});
|
|
311
|
+
const params = getParamsObject();
|
|
333
312
|
try {
|
|
334
313
|
if (routeItem?.beforeLoad && options?.withBeforeLoad) {
|
|
335
314
|
const context = contextState.getState();
|
|
@@ -406,10 +385,10 @@ var getRetry = (routeItem) => {
|
|
|
406
385
|
};
|
|
407
386
|
var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
408
387
|
var createRevalidateCache = (routerState) => {
|
|
388
|
+
const { loaderStateRef, timestampMap, contextState } = routerState;
|
|
409
389
|
const revalidateCache = async ({ routeItem, pathname }, retried = 0) => {
|
|
410
390
|
if (!routeItem?.loader) return;
|
|
411
|
-
const isCacheItemFresh = createIsCacheItemFresh(
|
|
412
|
-
const { loaderStateRef, timestampMap, contextState } = routerState;
|
|
391
|
+
const isCacheItemFresh = createIsCacheItemFresh(timestampMap);
|
|
413
392
|
if (loadingPromises.has(pathname)) return loadingPromises.get(pathname);
|
|
414
393
|
if (isCacheItemFresh({
|
|
415
394
|
routeItem,
|
|
@@ -423,10 +402,7 @@ var createRevalidateCache = (routerState) => {
|
|
|
423
402
|
try {
|
|
424
403
|
const context = contextState.getState();
|
|
425
404
|
const setContext = contextState.setState;
|
|
426
|
-
const params = getParamsObject(
|
|
427
|
-
params: routeItem.params,
|
|
428
|
-
pathname
|
|
429
|
-
});
|
|
405
|
+
const params = getParamsObject(routeItem, pathname);
|
|
430
406
|
const result = await routeItem?.loader({
|
|
431
407
|
params,
|
|
432
408
|
context,
|
|
@@ -497,6 +473,7 @@ var createRouterInstance = () => {
|
|
|
497
473
|
}),
|
|
498
474
|
loaderStateRef: new Cell(emptyLoaderState),
|
|
499
475
|
prevPathnameRef: new Cell(""),
|
|
476
|
+
pendingPathRef: new Cell(""),
|
|
500
477
|
timestampMap: /* @__PURE__ */ new Map()
|
|
501
478
|
};
|
|
502
479
|
const revalidateCache = createRevalidateCache(routerState);
|
|
@@ -504,13 +481,10 @@ var createRouterInstance = () => {
|
|
|
504
481
|
const invalidate = createInvalidate(routerState, revalidateCache);
|
|
505
482
|
const prefetch = createPrefetch(revalidateCache);
|
|
506
483
|
const useGetAction = (actionKey) => {
|
|
507
|
-
const { routeItem
|
|
484
|
+
const { routeItem } = routerState.routeItemDataState.getState();
|
|
508
485
|
const context = routerState.contextState.getState();
|
|
509
486
|
const setContext = routerState.contextState.setState;
|
|
510
|
-
const params = getParamsObject(
|
|
511
|
-
params: routeItem?.params,
|
|
512
|
-
pathname: location.pathname
|
|
513
|
-
});
|
|
487
|
+
const params = getParamsObject();
|
|
514
488
|
if (!routeItem) throw new Error("Route not found");
|
|
515
489
|
if (!routeItem.actions) throw new Error("Route action creator not found");
|
|
516
490
|
const action = routeItem.actions({
|
|
@@ -534,7 +508,8 @@ var createRouterInstance = () => {
|
|
|
534
508
|
scrollMapState: routerState.scrollMapState,
|
|
535
509
|
contextState: routerState.contextState,
|
|
536
510
|
blockedRouteState: routerState.blockedRouteState,
|
|
537
|
-
prevPathnameRef: routerState.prevPathnameRef
|
|
511
|
+
prevPathnameRef: routerState.prevPathnameRef,
|
|
512
|
+
pendingPathRef: routerState.pendingPathRef
|
|
538
513
|
},
|
|
539
514
|
runtime: {
|
|
540
515
|
navigate,
|
|
@@ -549,13 +524,7 @@ var createRouterInstance = () => {
|
|
|
549
524
|
useCurrentLoaderState: () => useGlobalState(routerState.currentLoaderState),
|
|
550
525
|
useScrollMap: () => useGlobalState(routerState.scrollMapState),
|
|
551
526
|
useContextState: () => useGlobalState(routerState.contextState),
|
|
552
|
-
useParams: () =>
|
|
553
|
-
const routeItemData = routerState.routeItemDataState.getState();
|
|
554
|
-
return getParamsObject({
|
|
555
|
-
params: routeItemData.routeItem?.params,
|
|
556
|
-
pathname: routeItemData.location.pathname
|
|
557
|
-
});
|
|
558
|
-
},
|
|
527
|
+
useParams: () => getParamsObject(),
|
|
559
528
|
useNavigate: () => {
|
|
560
529
|
const { blockedRouteState } = routerState;
|
|
561
530
|
const { location } = routerState.routeItemDataState.getState();
|
|
@@ -746,17 +715,26 @@ var Router = ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated = f
|
|
|
746
715
|
});
|
|
747
716
|
};
|
|
748
717
|
//#endregion
|
|
718
|
+
//#region hooks/useIsRoutePending.ts
|
|
719
|
+
var useIsRoutePending = (routePath) => {
|
|
720
|
+
const { hooks: { useIsLoading }, state: { pendingPathRef } } = router;
|
|
721
|
+
const [isPending] = useIsLoading();
|
|
722
|
+
return isPending && pendingPathRef.value === routePath;
|
|
723
|
+
};
|
|
724
|
+
//#endregion
|
|
749
725
|
//#region hooks/useNavigate.ts
|
|
750
726
|
var useNavigate = router.hooks.useNavigate;
|
|
751
727
|
//#endregion
|
|
752
728
|
//#region components/Link.tsx
|
|
753
|
-
var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
|
|
754
|
-
const
|
|
755
|
-
const
|
|
756
|
-
const prefetchDelay = hoverPrefetchDelay ?? configPrefetchDelay;
|
|
729
|
+
var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, className, style, onClick, activeClassName = "active-link", pendingClassName = "pending-link" }) => {
|
|
730
|
+
const isPending = useIsRoutePending(to);
|
|
731
|
+
const { pathname } = useLocation();
|
|
757
732
|
const navigate = useNavigate();
|
|
758
733
|
const timeout = useRef(0);
|
|
759
734
|
const ref = useRef(null);
|
|
735
|
+
const { prefetch: configPrefetch, hoverPrefetchDelay: configPrefetchDelay } = routerConfig;
|
|
736
|
+
const prefetch = prefetchLink || configPrefetch;
|
|
737
|
+
const prefetchDelay = hoverPrefetchDelay ?? configPrefetchDelay;
|
|
760
738
|
const onMouseEnter = useCallback(() => {
|
|
761
739
|
if (prefetch !== "hover" || !prefetchDelay) return;
|
|
762
740
|
if (timeout.current) clearTimeout(timeout.current);
|
|
@@ -791,10 +769,31 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
|
|
|
791
769
|
if (element) observer.disconnect();
|
|
792
770
|
};
|
|
793
771
|
}, [prefetch, to]);
|
|
772
|
+
const isActive = to === pathname;
|
|
773
|
+
const normalizedClassName = typeof className === "function" ? className({
|
|
774
|
+
isActive,
|
|
775
|
+
isPending
|
|
776
|
+
}) : className;
|
|
777
|
+
const normalizedStyle = typeof style === "function" ? style({
|
|
778
|
+
isActive,
|
|
779
|
+
isPending
|
|
780
|
+
}) : style;
|
|
781
|
+
const resultClassName = [
|
|
782
|
+
isActive && activeClassName,
|
|
783
|
+
isPending && pendingClassName,
|
|
784
|
+
normalizedClassName
|
|
785
|
+
].filter(Boolean).join(" ");
|
|
786
|
+
const clickHandler = async (event) => {
|
|
787
|
+
event.preventDefault();
|
|
788
|
+
onClick?.();
|
|
789
|
+
await navigate(to);
|
|
790
|
+
};
|
|
794
791
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
|
|
792
|
+
href: to,
|
|
795
793
|
ref,
|
|
796
|
-
style:
|
|
797
|
-
|
|
794
|
+
style: normalizedStyle,
|
|
795
|
+
className: resultClassName,
|
|
796
|
+
onClick: clickHandler,
|
|
798
797
|
onMouseEnter,
|
|
799
798
|
onMouseLeave,
|
|
800
799
|
children
|
package/dist/types.d.ts
CHANGED
|
@@ -40,10 +40,7 @@ export type ClientRouteItem = {
|
|
|
40
40
|
};
|
|
41
41
|
export type RouteItem = ClientRouteItem & {
|
|
42
42
|
element: RenderElement;
|
|
43
|
-
|
|
44
|
-
key: string;
|
|
45
|
-
value: string;
|
|
46
|
-
}[];
|
|
43
|
+
pattern: string;
|
|
47
44
|
cacheTimestamp?: number;
|
|
48
45
|
};
|
|
49
46
|
export type Location = {
|
|
@@ -106,6 +103,7 @@ export type RouterState = {
|
|
|
106
103
|
}>;
|
|
107
104
|
loaderStateRef: Cell<LoaderState>;
|
|
108
105
|
prevPathnameRef: Cell<string>;
|
|
106
|
+
pendingPathRef: Cell<string>;
|
|
109
107
|
timestampMap: Map<string, number>;
|
|
110
108
|
};
|
|
111
109
|
export type RouterType = {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const NOT_FOUND = "*";
|
|
2
2
|
export declare const findRoute: (pathname: string, includeAll?: boolean) => import("..").RouteItem | undefined;
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { ClientRouteItem, Location, RouteItem } from '../types';
|
|
2
2
|
export declare const createRouter: (clientList: ClientRouteItem[]) => RouteItem[];
|
|
3
|
-
export declare const getParamsObject: (
|
|
4
|
-
params?: RouteItem["params"];
|
|
5
|
-
pathname: string;
|
|
6
|
-
}) => {};
|
|
3
|
+
export declare const getParamsObject: (nextItem?: RouteItem, nextPathname?: string) => Record<string, string>;
|
|
7
4
|
export declare const parseWindowLocation: (location: typeof window.location) => Location;
|
|
8
|
-
export declare const comparePaths: (
|
|
5
|
+
export declare const comparePaths: (route: RouteItem, pathname: string) => boolean;
|