@native-router/react 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -44
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +1 -2
- package/dist/server.js +1 -2
- package/dist/ssr-BVFDJcr7.cjs +2 -0
- package/dist/ssr-BVFDJcr7.cjs.map +1 -0
- package/dist/ssr-C17nRDBM.js +8 -0
- package/dist/ssr-C17nRDBM.js.map +1 -0
- package/dist/types/components/Link.d.ts +1 -1
- package/dist/types/components/NavLink.d.ts +22 -0
- package/dist/types/components/PrefetchLink.d.ts +9 -2
- package/dist/types/components/Router.d.ts +6 -6
- package/dist/types/components/ScrollRestoration.d.ts +43 -0
- package/dist/types/components/link-behavior.d.ts +22 -0
- package/dist/types/context.d.ts +15 -4
- package/dist/types/index.d.ts +5 -2
- package/dist/types/resolve-view.d.ts +3 -3
- package/dist/types/ssr.d.ts +18 -4
- package/dist/types/types.d.ts +93 -9
- package/dist/types/use-search-params.d.ts +56 -0
- package/package.json +52 -51
- package/dist/server.cjs.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/ssr-CiHFLO3B.js +0 -6
- package/dist/ssr-CiHFLO3B.js.map +0 -1
- package/dist/ssr-CsvbhYZu.cjs +0 -2
- package/dist/ssr-CsvbhYZu.cjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr-C17nRDBM.js","names":[],"sources":["../src/context.tsx","../src/resolve-view.tsx","../src/components/Router.tsx","../src/ssr.tsx"],"sourcesContent":["import {createContext, ReactNode, useContext, useMemo} from 'react';\nimport type {Context, LoadStatus, Route} from './types';\n\nconst ViewContext = createContext<ReactNode>(null);\n\nexport function ViewProvider(props: {children: ReactNode; value: ReactNode}) {\n return <ViewContext.Provider {...props} />;\n}\n\n/**\n * @group Hooks\n * @see {@link View View Component}\n */\nexport function useView() {\n return useContext(ViewContext);\n}\n\n/**\n * Used for route component to render child route component.\n * It just render the return of {@link useView}\n * @group Components\n */\nexport function View() {\n return useView();\n}\n\nconst DataContext = createContext<[any, Record<string, any>]>([undefined, {}]);\n\nfunction useDataContext() {\n return useContext(DataContext);\n}\n\n/**\n * Get the named data map of the resolved route levels: an object keyed by\n * each ancestor's `name`, holding its resolved `data`. The current level\n * is included only when it declares a `name`.\n *\n * Give the generic the expected map shape to read values type-safely:\n * `useNamedData<{user: User}>()`.\n * @group Hooks\n */\nexport function useNamedData<T = Record<string, unknown>>() {\n return useDataContext()[1] as T;\n}\n\nexport function DataProvider({\n children,\n name,\n data\n}: {\n children: ReactNode;\n data: any;\n name?: string;\n}) {\n const namedData = useNamedData();\n const value = useMemo(\n () => [data, name ? {...namedData, [name]: data} : namedData] as [any, any],\n [data, name, namedData]\n );\n return <DataContext.Provider value={value}>{children}</DataContext.Provider>;\n}\n\nexport const MatchedContext = createContext<Context<Route> | undefined>(\n undefined\n);\n\n/**\n * @group Hooks\n */\nexport function useMatched() {\n return useContext(MatchedContext)!;\n}\n\n/**\n * Get the resolved `data` of the current route level, or the named data\n * of an ancestor level when `name` is given.\n *\n * Give the generic the expected data type to read it type-safely without\n * a cast: `useData<Article>()` → `Article | undefined`.\n * @group Hooks\n */\nexport function useData<T = unknown>(name?: string): T | undefined {\n const [data, namedData] = useDataContext();\n return (name ? namedData[name] : data) as T | undefined;\n}\n\nexport const LoadingContext = createContext<LoadStatus | undefined>(undefined);\n\n/**\n * @group Hooks\n */\nexport function useLoading() {\n return useContext(LoadingContext);\n}\n","import type {ComponentType, ReactElement} from 'react';\nimport type {Context, ResolveViewContext, Route} from '@@/types';\nimport {\n mergeMatchedParams,\n parseSearch,\n parseSearchInput\n} from '@native-router/core';\nimport type {Matched} from '@native-router/core';\nimport {DataProvider, MatchedContext, View, ViewProvider} from './context';\n\n/**\n * The default implementation of resolve view\n * @param matched the matched result\n * @param viewContext resolved view context\n * @returns the resolve view\n * @see {@link create router->create}\n */\nexport default function resolveView(\n matched: Matched<Route>[],\n ctx: ResolveViewContext<Route>\n) {\n return resolveViewBase(matched, ctx, (data, dataCtx) => data?.(dataCtx));\n}\n\nconst viewDataMap = new WeakMap<ReactElement, any[]>();\n\nexport function resolveViewServer(\n matched: Matched<Route>[],\n ctx: ResolveViewContext<Route>\n) {\n const dataResults = new Array(matched.length);\n return resolveViewBase(matched, ctx, (data, dataCtx) =>\n Promise.resolve(data?.(dataCtx)).then(\n (result) => (dataResults[dataCtx.index] = result)\n )\n ).then((view) => {\n viewDataMap.set(view, dataResults);\n return view;\n });\n}\n\nexport function createHydrateResolveView(data: any[]) {\n return (matched: Matched<Route>[], ctx: ResolveViewContext<Route>) =>\n resolveViewBase(matched, ctx, (_, dataCtx) => data[dataCtx.index]);\n}\n\nexport function getViewData(view: ReactElement) {\n return viewDataMap.get(view);\n}\n\nfunction resolveViewBase(\n matched: Matched<Route>[],\n {router, location}: ResolveViewContext<Route>,\n resolveData: (\n dataFetcher: ((ctx: Context<Route>) => any) | undefined,\n ctx: Context<Route>\n ) => any\n) {\n return Promise.all(\n matched.map(({route}, index) => {\n // `search` starts as the degraded input and is upgraded to the\n // schema output before the data fetcher runs below; schema outputs\n // are user-typed(`Route<P, S>`), so the property stays `any` here.\n const ctx: Context<Route, Record<string, string>, any> = {\n matched: matched!,\n params: mergeMatchedParams(matched, index),\n index,\n router,\n location,\n search: parseSearchInput(location.search)\n };\n function resolveComponent(): ComponentType | Promise<ComponentType> {\n if (!route.component) return View;\n const r = route.component(ctx);\n return Promise.resolve(r).then((m) => ('default' in m ? m.default : m));\n }\n\n // The level's search schema runs before its data fetcher: the parsed\n // output replaces the degraded input in `ctx.search`, and a rejected\n // validation fails the level exactly like a data error.\n const resolveDataWithSearch = () =>\n route.search\n ? parseSearch(route.search, location.search).then((search) => {\n ctx.search = search;\n return resolveData(route.data, ctx);\n })\n : resolveData(route.data, ctx);\n\n // A level that fails to resolve (search, data or component) is\n // replaced by its route-level errorComponent when configured;\n // otherwise the error bubbles up to the global errorHandler as before.\n return Promise.all([resolveDataWithSearch(), resolveComponent()]).then(\n ([data, C]) => (\n <DataProvider data={data} name={route.name}>\n <MatchedContext.Provider value={ctx}>\n <C />\n </MatchedContext.Provider>\n </DataProvider>\n ),\n (error: Error) => {\n if (!route.errorComponent) throw error;\n return (\n <DataProvider data={undefined} name={route.name}>\n <MatchedContext.Provider value={ctx}>\n <route.errorComponent error={error} ctx={ctx} />\n </MatchedContext.Provider>\n </DataProvider>\n );\n }\n );\n })\n ).then((views) =>\n views\n .reverse()\n .reduce((acc, view) => <ViewProvider value={acc}>{view}</ViewProvider>)\n );\n}\n","import {\n ReactNode,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState\n} from 'react';\nimport {\n History,\n createBrowserHistory,\n createHashHistory,\n createMemoryHistory,\n MemoryHistoryOptions\n} from 'history';\nimport type {LoadStatus, Route} from '@@/types';\nimport {LoadingContext, ViewProvider} from '@@/context';\nimport {create, getCurrentView, listen, setOptions} from '@native-router/core';\nimport type {Options, ResolveView, RouterInstance} from '@native-router/core';\nimport {splitProps, uniqId} from '@native-router/core/util';\nimport {useSyncExternalStore} from 'use-sync-external-store/shim';\nimport defaultResolve from '@@/resolve-view';\n\nconst RouterContext = createContext<RouterInstance<Route, ReactNode> | null>(\n null\n);\n\ntype Props = {\n children?: ReactNode;\n routes: Route[] | Route;\n resolveView?: typeof defaultResolve;\n} & Omit<Options<ReactNode>, 'onLoadingChange'>;\n\n/**\n * Base Router Component.\n * @group Components\n */\nexport function Router({\n router,\n children\n}: {\n children?: ReactNode;\n router: RouterInstance<Route, ReactNode>;\n}) {\n const viewRef = useRef<ReactNode>(getCurrentView(router));\n const subscribe = useCallback(\n (onStoreChange: () => void) =>\n listen(router, (view) => {\n viewRef.current = view;\n onStoreChange();\n }),\n [router]\n );\n const getSnapshot = useCallback(() => viewRef.current, []);\n // `getServerSnapshot` is required by the native implementation when the\n // Router is rendered inside server-rendered content(e.g. resolveServerView).\n const getServerSnapshot = useCallback(() => getCurrentView(router), [router]);\n const view = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n\n return (\n <RouterContext.Provider value={router}>\n {children === undefined ? (\n view\n ) : (\n <ViewProvider value={view}>{children}</ViewProvider>\n )}\n </RouterContext.Provider>\n );\n}\n\nexport function createRouter(\n routes: Route | Route[],\n history: History,\n {\n resolveView = defaultResolve,\n ...options\n }: Options<ReactNode> & {resolveView?: ResolveView<Route, ReactNode>} = {}\n): RouterInstance<Route, ReactNode> {\n return create(routes, history, resolveView, options);\n}\n\nfunction useNewRouter(\n {routes, children, ...options}: Props,\n createHistory: () => History\n) {\n const [tracked, rest] = splitProps(options, ['baseUrl', 'currentView']);\n const {baseUrl, currentView} = tracked;\n const router = useMemo(\n () => createRouter(routes, createHistory(), tracked),\n [routes, createHistory, baseUrl, currentView]\n );\n\n const [loading, setLoading] = useState<LoadStatus>();\n // Options are applied in an effect(never during render) and refreshed on\n // every commit, so `onLoadingChange` always sees the latest closure.\n useEffect(() => {\n setOptions(router, {\n ...rest,\n onLoadingChange(status) {\n setLoading(status && {key: uniqId(), status});\n }\n });\n }, [router, rest]);\n\n const r = useMemo(\n () => <Router router={router}>{children}</Router>,\n [router, children]\n );\n\n return <LoadingContext.Provider value={loading}>{r}</LoadingContext.Provider>;\n}\n\n/**\n * History mode Router Component.\n * @group Components\n */\nexport function HistoryRouter(props: Props) {\n return useNewRouter(props, createBrowserHistory);\n}\n\n/**\n * Hash mode Router Component.\n * @group Components\n */\nexport function HashRouter(props: Props) {\n return useNewRouter(props, createHashHistory);\n}\n\n/**\n * Memory mode Router Component.\n * @group Components\n */\nexport function MemoryRouter({\n initialEntries,\n initialIndex,\n ...props\n}: Props & MemoryHistoryOptions) {\n const createHistory = useMemo(\n () => () => createMemoryHistory({initialEntries, initialIndex}),\n [initialEntries, initialIndex]\n );\n return useNewRouter(props, createHistory);\n}\n\n/**\n * Get Router instance.\n * @group Hooks\n * @returns Router Instance\n */\nexport function useRouter() {\n const router = useContext(RouterContext);\n if (!router) {\n throw new Error('useRouter() must be used within a <Router> component');\n }\n return router;\n}\n","import {createBrowserHistory, createMemoryHistory, createPath} from 'history';\nimport {ReactElement, ReactNode} from 'react';\nimport {create, resolve, toLocation} from '@native-router/core';\nimport type {\n HistoryState,\n Location,\n Options,\n RouterInstance\n} from '@native-router/core';\nimport {isString} from '@native-router/core/util';\nimport {Router} from './components/Router';\nimport {\n createHydrateResolveView,\n getViewData,\n resolveViewServer\n} from './resolve-view';\nimport type {Route} from './types';\n\nconst defaultHydrateKey = '_nativeRouterReactSSRData';\n\n/**\n * Serialize the SSR payload for embedding in a script element.\n * `JSON.stringify` does not escape `<`, U+2028 and U+2029,\n * so route data like `</script>` would break out of the script element.\n * @param payload the payload to serialize\n * @returns the escaped JSON string\n */\nfunction serializePayload(payload: {\n data?: any[];\n location: Location;\n index: number;\n}) {\n return JSON.stringify(payload)\n .replace(/</g, '\\\\u003c')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n\nexport function resolveServerViewBase(\n router: RouterInstance<Route, ReactNode>,\n location: Location,\n options?: {\n scriptAttributes?: Record<string, string>;\n hydrateKey?: string;\n }\n) {\n return resolve<Route, ReactNode>(router, location).then((view) => {\n const data = getViewData(view as ReactElement);\n const index =\n (router.history.location.state as HistoryState | undefined)?.index || 0;\n return (\n <>\n <Router router={router}>{view}</Router>\n <script\n {...options?.scriptAttributes}\n suppressHydrationWarning\n // eslint-disable-next-line @eslint-react/dom-no-dangerously-set-innerhtml -- serialized state for hydration\n dangerouslySetInnerHTML={{\n __html: `window.${\n options?.hydrateKey || defaultHydrateKey\n } = ${serializePayload({data, location, index})};`\n }}\n />\n </>\n );\n });\n}\n\nexport function resolveServerView(\n routes: Route | Route[],\n location: Location | string,\n {\n scriptAttributes,\n hydrateKey,\n ...options\n }: Options<ReactElement> & {\n scriptAttributes?: Record<string, string>;\n hydrateKey?: string;\n } = {}\n) {\n const router = create(\n routes,\n createMemoryHistory({initialEntries: [location]}),\n resolveViewServer,\n options\n );\n\n return resolveServerViewBase(\n router,\n isString(location) ? toLocation(router, location) : location,\n {\n scriptAttributes,\n hydrateKey\n }\n );\n}\n\n/**\n * Hydrate the SSR result of {@link resolveServerView} on the client.\n * The router is bound to the browser history(aligned with the index\n * in the SSR payload), so navigation after hydration updates the address bar.\n * @param routes routes config, must match the server side\n * @param options options, `hydrateKey` must match the server side\n * @returns the resolved view and the router instance, for example:\n * `const {view, router} = await hydrate(routes);`\n * `hydrateRoot(root, <Router router={router}>{view}</Router>)`\n * @group Methods\n */\nexport function hydrate(\n routes: Route | Route[],\n options?: Options<ReactElement> & {\n hydrateKey?: string;\n }\n): Promise<{view: ReactNode; router: RouterInstance<Route, ReactNode>}> {\n const {\n data,\n location,\n index = 0\n } = (window as any)[options?.hydrateKey || defaultHydrateKey] as {\n data: any[];\n location: Location;\n index?: number;\n };\n const history = createBrowserHistory();\n history.replace(createPath(history.location), {index});\n const router = create(\n routes,\n history,\n createHydrateResolveView(data),\n options\n );\n return resolve<Route, ReactNode>(router, location).then((view) => ({\n view,\n router\n }));\n}\n"],"mappings":"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"}
|
|
@@ -4,4 +4,4 @@ import type { LinkProps } from '../types';
|
|
|
4
4
|
* @param props
|
|
5
5
|
* @group Components
|
|
6
6
|
*/
|
|
7
|
-
export default function Link({ to, ...rest }: LinkProps): import("react
|
|
7
|
+
export default function Link({ to, ...rest }: LinkProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { NavLinkProps } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Link that knows whether its target matches the current location.
|
|
4
|
+
*
|
|
5
|
+
* Active rules(aligned with react-router's `NavLink`):
|
|
6
|
+
*
|
|
7
|
+
* - `target` is the pathname of `toLocation(router, to)`(baseUrl prepended),
|
|
8
|
+
* `current` is `router.history.location.pathname`; both are lowercased
|
|
9
|
+
* unless `caseSensitive` is set.
|
|
10
|
+
* - `isExactActive`: `current === target`.
|
|
11
|
+
* - `isActive`: `isExactActive` when `end` is set, otherwise `current` equals
|
|
12
|
+
* `target` or starts with `target` plus a trailing `/`(so `to="/"` is active
|
|
13
|
+
* for every path).
|
|
14
|
+
*
|
|
15
|
+
* While active the anchor renders `aria-current={ariaCurrent ?? 'page'}` and
|
|
16
|
+
* `className`/`style`/`children` receive the active state when given as
|
|
17
|
+
* functions. Click behavior is delegated to {@link Link}, inheriting the
|
|
18
|
+
* modified-click guard and the double-click lock.
|
|
19
|
+
* @param props
|
|
20
|
+
* @group Components
|
|
21
|
+
*/
|
|
22
|
+
export default function NavLink({ to, end, caseSensitive, className, style, ariaCurrent, children, ...rest }: NavLinkProps): import("react").JSX.Element;
|
|
@@ -11,9 +11,16 @@ type PrefetchLinkContext = {
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function usePrefetch(): PrefetchLinkContext;
|
|
13
13
|
/**
|
|
14
|
-
* Link
|
|
14
|
+
* Link with prefetch support.
|
|
15
|
+
*
|
|
16
|
+
* The `prefetch` prop controls when the target view is resolved:
|
|
17
|
+
* - `'intent'` (default): prefetch on hover or focus.
|
|
18
|
+
* - `'render'`: prefetch as soon as the link mounts.
|
|
19
|
+
* - `'viewport'`: prefetch when the link scrolls into the viewport.
|
|
20
|
+
* - `'none'`: never prefetch; the target is resolved on click.
|
|
21
|
+
*
|
|
15
22
|
* @param props
|
|
16
23
|
* @group Components
|
|
17
24
|
*/
|
|
18
|
-
export default function PrefetchLink({ to, children, ...rest }: LinkProps): import("react
|
|
25
|
+
export default function PrefetchLink({ to, prefetch, children, ...rest }: LinkProps): import("react").JSX.Element;
|
|
19
26
|
export {};
|
|
@@ -4,7 +4,7 @@ import type { Route } from '../types';
|
|
|
4
4
|
import type { Options, ResolveView, RouterInstance } from '@native-router/core';
|
|
5
5
|
import defaultResolve from '../resolve-view';
|
|
6
6
|
type Props = {
|
|
7
|
-
children
|
|
7
|
+
children?: ReactNode;
|
|
8
8
|
routes: Route[] | Route;
|
|
9
9
|
resolveView?: typeof defaultResolve;
|
|
10
10
|
} & Omit<Options<ReactNode>, 'onLoadingChange'>;
|
|
@@ -13,9 +13,9 @@ type Props = {
|
|
|
13
13
|
* @group Components
|
|
14
14
|
*/
|
|
15
15
|
export declare function Router({ router, children }: {
|
|
16
|
-
children
|
|
16
|
+
children?: ReactNode;
|
|
17
17
|
router: RouterInstance<Route, ReactNode>;
|
|
18
|
-
}): import("react
|
|
18
|
+
}): import("react").JSX.Element;
|
|
19
19
|
export declare function createRouter(routes: Route | Route[], history: History, { resolveView, ...options }?: Options<ReactNode> & {
|
|
20
20
|
resolveView?: ResolveView<Route, ReactNode>;
|
|
21
21
|
}): RouterInstance<Route, ReactNode>;
|
|
@@ -23,17 +23,17 @@ export declare function createRouter(routes: Route | Route[], history: History,
|
|
|
23
23
|
* History mode Router Component.
|
|
24
24
|
* @group Components
|
|
25
25
|
*/
|
|
26
|
-
export declare function HistoryRouter(props: Props): import("react
|
|
26
|
+
export declare function HistoryRouter(props: Props): import("react").JSX.Element;
|
|
27
27
|
/**
|
|
28
28
|
* Hash mode Router Component.
|
|
29
29
|
* @group Components
|
|
30
30
|
*/
|
|
31
|
-
export declare function HashRouter(props: Props): import("react
|
|
31
|
+
export declare function HashRouter(props: Props): import("react").JSX.Element;
|
|
32
32
|
/**
|
|
33
33
|
* Memory mode Router Component.
|
|
34
34
|
* @group Components
|
|
35
35
|
*/
|
|
36
|
-
export declare function MemoryRouter({ initialEntries, initialIndex, ...props }: Props & MemoryHistoryOptions): import("react
|
|
36
|
+
export declare function MemoryRouter({ initialEntries, initialIndex, ...props }: Props & MemoryHistoryOptions): import("react").JSX.Element;
|
|
37
37
|
/**
|
|
38
38
|
* Get Router instance.
|
|
39
39
|
* @group Hooks
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
type ScrollRestorationProps = {
|
|
2
|
+
/**
|
|
3
|
+
* Scroll a freshly pushed(or replaced) entry back to the top, like a full
|
|
4
|
+
* page load. Set to false to keep the current offset across forward
|
|
5
|
+
* navigations(e.g. feed-style "load more" pages). POP always restores the
|
|
6
|
+
* saved offset, regardless of this flag.
|
|
7
|
+
* @default true
|
|
8
|
+
*/
|
|
9
|
+
resetOnPush?: boolean;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Restore the window scroll position across history navigations.
|
|
13
|
+
*
|
|
14
|
+
* The router restores views from the in-memory `viewStack`: on back/forward
|
|
15
|
+
* the view is reused without re-fetching its data, but the browser has
|
|
16
|
+
* already left the old document position and a remounted DOM starts at the
|
|
17
|
+
* top by default. Mount `<ScrollRestoration />` anywhere inside
|
|
18
|
+
* {@link Router}(typically in the root layout) to fill that gap:
|
|
19
|
+
*
|
|
20
|
+
* - the scroll offset of every visited entry is remembered, keyed by the
|
|
21
|
+
* absolute history index(`history.location.state.index` — the same key
|
|
22
|
+
* `viewStack` is keyed by). Like `viewStack`, the map is in-memory and
|
|
23
|
+
* session scoped: after a page reload there is nothing to restore and the
|
|
24
|
+
* browser's own restoration takes over.
|
|
25
|
+
* - `history.scrollRestoration` is taken over as `manual`(the browser's
|
|
26
|
+
* own `auto` restoration would race the restore and pre-scroll while the
|
|
27
|
+
* left entry's offset is still being read), making the component solely
|
|
28
|
+
* responsible for restoration; the takeover is session scoped and not
|
|
29
|
+
* reverted on unmount, like `viewStack`.
|
|
30
|
+
* - POP restores the saved offset of the landed entry(`0,0` when none was
|
|
31
|
+
* saved, e.g. forward-past-the-end or post-reload entries).
|
|
32
|
+
* - PUSH and REPLACE scroll a fresh entry back to the top when
|
|
33
|
+
* `resetOnPush` is set(default). Internal re-commits of the very same
|
|
34
|
+
* entry(the core listener's POP sync, `refresh`, listen bootstrap) never
|
|
35
|
+
* touch the scroll.
|
|
36
|
+
*
|
|
37
|
+
* Renders nothing. SSR safe: it only subscribes in an effect and only when
|
|
38
|
+
* `window` exists.
|
|
39
|
+
* @param props
|
|
40
|
+
* @group Components
|
|
41
|
+
*/
|
|
42
|
+
export default function ScrollRestoration({ resetOnPush }: ScrollRestorationProps): null;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks whether a click on an anchor should be intercepted and handled as an
|
|
3
|
+
* in-app navigation, following the standard link interception semantics of
|
|
4
|
+
* react-router/wouter: plain left clicks only.
|
|
5
|
+
*
|
|
6
|
+
* The browser keeps the default behavior whenever this returns `false`, so
|
|
7
|
+
* modified clicks, middle clicks and links to other browsing contexts open in
|
|
8
|
+
* a new tab/window as the user expects.
|
|
9
|
+
*
|
|
10
|
+
* @param e The click event to inspect.
|
|
11
|
+
* @param target The anchor's `target` attribute, if any.
|
|
12
|
+
* @param rel The anchor's `rel` attribute, if any.
|
|
13
|
+
* @group Components
|
|
14
|
+
*/
|
|
15
|
+
export declare function shouldNavigate(e: {
|
|
16
|
+
button: number;
|
|
17
|
+
defaultPrevented: boolean;
|
|
18
|
+
metaKey: boolean;
|
|
19
|
+
ctrlKey: boolean;
|
|
20
|
+
shiftKey: boolean;
|
|
21
|
+
altKey: boolean;
|
|
22
|
+
}, target?: string, rel?: string): boolean;
|
package/dist/types/context.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Context, LoadStatus, Route } from './types';
|
|
|
3
3
|
export declare function ViewProvider(props: {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
value: ReactNode;
|
|
6
|
-
}): import("react
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
7
|
/**
|
|
8
8
|
* @group Hooks
|
|
9
9
|
* @see {@link View View Component}
|
|
@@ -16,23 +16,34 @@ export declare function useView(): ReactNode;
|
|
|
16
16
|
*/
|
|
17
17
|
export declare function View(): ReactNode;
|
|
18
18
|
/**
|
|
19
|
+
* Get the named data map of the resolved route levels: an object keyed by
|
|
20
|
+
* each ancestor's `name`, holding its resolved `data`. The current level
|
|
21
|
+
* is included only when it declares a `name`.
|
|
22
|
+
*
|
|
23
|
+
* Give the generic the expected map shape to read values type-safely:
|
|
24
|
+
* `useNamedData<{user: User}>()`.
|
|
19
25
|
* @group Hooks
|
|
20
26
|
*/
|
|
21
|
-
export declare function useNamedData
|
|
27
|
+
export declare function useNamedData<T = Record<string, unknown>>(): T;
|
|
22
28
|
export declare function DataProvider({ children, name, data }: {
|
|
23
29
|
children: ReactNode;
|
|
24
30
|
data: any;
|
|
25
31
|
name?: string;
|
|
26
|
-
}): import("react
|
|
32
|
+
}): import("react").JSX.Element;
|
|
27
33
|
export declare const MatchedContext: import("react").Context<Context<Route> | undefined>;
|
|
28
34
|
/**
|
|
29
35
|
* @group Hooks
|
|
30
36
|
*/
|
|
31
37
|
export declare function useMatched(): Context<Route>;
|
|
32
38
|
/**
|
|
39
|
+
* Get the resolved `data` of the current route level, or the named data
|
|
40
|
+
* of an ancestor level when `name` is given.
|
|
41
|
+
*
|
|
42
|
+
* Give the generic the expected data type to read it type-safely without
|
|
43
|
+
* a cast: `useData<Article>()` → `Article | undefined`.
|
|
33
44
|
* @group Hooks
|
|
34
45
|
*/
|
|
35
|
-
export declare function useData(name?: string):
|
|
46
|
+
export declare function useData<T = unknown>(name?: string): T | undefined;
|
|
36
47
|
export declare const LoadingContext: import("react").Context<LoadStatus | undefined>;
|
|
37
48
|
/**
|
|
38
49
|
* @group Hooks
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export * from './components/Router';
|
|
2
2
|
export { default as Link } from './components/Link';
|
|
3
|
+
export { default as NavLink } from './components/NavLink';
|
|
3
4
|
export { default as PrefetchLink, usePrefetch } from './components/PrefetchLink';
|
|
4
|
-
export {
|
|
5
|
+
export { default as ScrollRestoration } from './components/ScrollRestoration';
|
|
6
|
+
export { useView, View, useData, useNamedData, useLoading, useMatched } from './context';
|
|
7
|
+
export { useSearchParams, useSearch } from './use-search-params';
|
|
5
8
|
export { default as defaultResolveView } from './resolve-view';
|
|
6
9
|
export * from './types';
|
|
7
|
-
export {
|
|
10
|
+
export { hydrate } from './ssr';
|
|
@@ -8,7 +8,7 @@ import type { Matched } from '@native-router/core';
|
|
|
8
8
|
* @returns the resolve view
|
|
9
9
|
* @see {@link create router->create}
|
|
10
10
|
*/
|
|
11
|
-
export default function resolveView(matched: Matched<Route>[], ctx: ResolveViewContext<Route>): Promise<import("react
|
|
12
|
-
export declare function resolveViewServer(matched: Matched<Route>[], ctx: ResolveViewContext<Route>): Promise<import("react
|
|
13
|
-
export declare function createHydrateResolveView(data: any[]): (matched: Matched<Route>[], ctx: ResolveViewContext<Route>) => Promise<import("react
|
|
11
|
+
export default function resolveView(matched: Matched<Route>[], ctx: ResolveViewContext<Route>): Promise<import("react").JSX.Element>;
|
|
12
|
+
export declare function resolveViewServer(matched: Matched<Route>[], ctx: ResolveViewContext<Route>): Promise<import("react").JSX.Element>;
|
|
13
|
+
export declare function createHydrateResolveView(data: any[]): (matched: Matched<Route>[], ctx: ResolveViewContext<Route>) => Promise<import("react").JSX.Element>;
|
|
14
14
|
export declare function getViewData(view: ReactElement): any[] | undefined;
|
package/dist/types/ssr.d.ts
CHANGED
|
@@ -4,11 +4,25 @@ import type { Route } from './types';
|
|
|
4
4
|
export declare function resolveServerViewBase(router: RouterInstance<Route, ReactNode>, location: Location, options?: {
|
|
5
5
|
scriptAttributes?: Record<string, string>;
|
|
6
6
|
hydrateKey?: string;
|
|
7
|
-
}): Promise<import("react
|
|
7
|
+
}): Promise<import("react").JSX.Element>;
|
|
8
8
|
export declare function resolveServerView(routes: Route | Route[], location: Location | string, { scriptAttributes, hydrateKey, ...options }?: Options<ReactElement> & {
|
|
9
9
|
scriptAttributes?: Record<string, string>;
|
|
10
10
|
hydrateKey?: string;
|
|
11
|
-
}): Promise<import("react
|
|
12
|
-
|
|
11
|
+
}): Promise<import("react").JSX.Element>;
|
|
12
|
+
/**
|
|
13
|
+
* Hydrate the SSR result of {@link resolveServerView} on the client.
|
|
14
|
+
* The router is bound to the browser history(aligned with the index
|
|
15
|
+
* in the SSR payload), so navigation after hydration updates the address bar.
|
|
16
|
+
* @param routes routes config, must match the server side
|
|
17
|
+
* @param options options, `hydrateKey` must match the server side
|
|
18
|
+
* @returns the resolved view and the router instance, for example:
|
|
19
|
+
* `const {view, router} = await hydrate(routes);`
|
|
20
|
+
* `hydrateRoot(root, <Router router={router}>{view}</Router>)`
|
|
21
|
+
* @group Methods
|
|
22
|
+
*/
|
|
23
|
+
export declare function hydrate(routes: Route | Route[], options?: Options<ReactElement> & {
|
|
13
24
|
hydrateKey?: string;
|
|
14
|
-
}): Promise<
|
|
25
|
+
}): Promise<{
|
|
26
|
+
view: ReactNode;
|
|
27
|
+
router: RouterInstance<Route, ReactNode>;
|
|
28
|
+
}>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,28 +1,112 @@
|
|
|
1
|
-
import type { AnchorHTMLAttributes, ComponentType, DetailedHTMLProps, ReactNode } from 'react';
|
|
2
|
-
import type { BaseRoute, Matched, Location, RouterInstance } from '@native-router/core';
|
|
1
|
+
import type { AnchorHTMLAttributes, ComponentType, CSSProperties, DetailedHTMLProps, ReactNode } from 'react';
|
|
2
|
+
import type { BaseRoute, ExtractPathParams, Matched, Location, RouterInstance, SearchInput } from '@native-router/core';
|
|
3
3
|
export type ResolveViewContext<R extends BaseRoute> = {
|
|
4
4
|
router: RouterInstance<R>;
|
|
5
5
|
location: Location;
|
|
6
6
|
};
|
|
7
|
-
export type Context<T extends BaseRoute> = {
|
|
7
|
+
export type Context<T extends BaseRoute, P = Record<string, string>, S = SearchInput> = {
|
|
8
8
|
matched: Matched<T>[];
|
|
9
9
|
index: number;
|
|
10
|
-
router: RouterInstance<
|
|
10
|
+
router: RouterInstance<T>;
|
|
11
11
|
location: Location;
|
|
12
|
-
params:
|
|
12
|
+
params: P;
|
|
13
|
+
/**
|
|
14
|
+
* The parsed search of the current location: with a route
|
|
15
|
+
* {@link Route.search search schema} the schema output, otherwise the
|
|
16
|
+
* raw input object of `parseSearchInput`(strings, arrays for repeated
|
|
17
|
+
* keys).
|
|
18
|
+
*/
|
|
19
|
+
search: S;
|
|
13
20
|
};
|
|
14
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Params shape of a route path. Literal patterns get a precise shape via
|
|
23
|
+
* {@link ExtractPathParams}; the default `string`(and `any`) degrade to
|
|
24
|
+
* the legacy `Record<string, string>` so untyped routes keep working.
|
|
25
|
+
* @group Types
|
|
26
|
+
* @category Route
|
|
27
|
+
*/
|
|
28
|
+
export type RouteParams<P extends string> = string extends P ? Record<string, string> : ExtractPathParams<P>;
|
|
29
|
+
/**
|
|
30
|
+
* Route with optional path-pattern and search generics. Give `path` a
|
|
31
|
+
* string literal type and the `data`/`component`/`errorComponent`
|
|
32
|
+
* contexts receive precisely-typed `params`, e.g.
|
|
33
|
+
* `Route<'/users/:id'>` → `params: {id: string}`. Give the second
|
|
34
|
+
* generic the output type of the route `search` schema and `ctx.search`
|
|
35
|
+
* is typed accordingly, e.g. `Route<'/list', {page: number}>` with
|
|
36
|
+
* `search: z.object({page: z.coerce.number()})` → `search: {page: number}`.
|
|
37
|
+
*
|
|
38
|
+
* Without the search generic an untyped `ctx.search` stays `any` — the
|
|
39
|
+
* default that keeps differently typed levels assignable to plain
|
|
40
|
+
* `Route`: schema outputs are arbitrary(coerced numbers, defaults, ...),
|
|
41
|
+
* so no single degraded shape is bivariant with all of them. At runtime
|
|
42
|
+
* it holds the raw input object of `parseSearchInput`; see `useSearch`
|
|
43
|
+
* for the typed degraded shape.
|
|
44
|
+
* `children` accepts `Route<any, any>` so levels with different patterns
|
|
45
|
+
* and search shapes nest without variance conflicts.
|
|
46
|
+
* @group Types
|
|
47
|
+
* @category Route
|
|
48
|
+
*/
|
|
49
|
+
export type Route<P extends string = string, S = any> = Omit<BaseRoute<{
|
|
15
50
|
name?: string;
|
|
16
|
-
data?(ctx: Context<Route>): any | Promise<any>;
|
|
17
|
-
component?(ctx: Context<Route>): ComponentType | Promise<ComponentType | {
|
|
51
|
+
data?(ctx: Context<Route, RouteParams<P>, S>): any | Promise<any>;
|
|
52
|
+
component?(ctx: Context<Route, RouteParams<P>, S>): ComponentType | Promise<ComponentType | {
|
|
18
53
|
default: ComponentType;
|
|
19
54
|
}>;
|
|
20
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Not parametrized by `P`: props are strictly contravariant, so a
|
|
57
|
+
* precise params type here would break assignability between
|
|
58
|
+
* `Route<'/a/:id'>` and plain `Route`.
|
|
59
|
+
*/
|
|
60
|
+
errorComponent?: ComponentType<{
|
|
61
|
+
error: Error;
|
|
62
|
+
ctx: Context<Route>;
|
|
63
|
+
}>;
|
|
64
|
+
}>, 'path' | 'children'> & {
|
|
65
|
+
/** Path pattern; params of the contexts above are inferred from it. */
|
|
66
|
+
path?: P;
|
|
67
|
+
/**
|
|
68
|
+
* `Route<any, any>` accepts levels with any path pattern and search
|
|
69
|
+
* shape, so typed levels nest without variance conflicts. The
|
|
70
|
+
* `search` field is inherited from `BaseRoute`, loosely typed — see
|
|
71
|
+
* the Route doc above.
|
|
72
|
+
*/
|
|
73
|
+
children?: Route<any, any>[];
|
|
74
|
+
};
|
|
21
75
|
export type LoadStatus = {
|
|
22
76
|
key: number;
|
|
23
77
|
status: 'pending' | 'resolved' | 'rejected';
|
|
24
78
|
};
|
|
25
79
|
export type LinkProps = {
|
|
26
80
|
to: string;
|
|
81
|
+
/**
|
|
82
|
+
* 预取策略:'intent'(默认,hover/focus 触发)、'render'(挂载即预取)、
|
|
83
|
+
* 'viewport'(进入视口)、'none'(不预取)
|
|
84
|
+
*/
|
|
85
|
+
prefetch?: 'intent' | 'render' | 'viewport' | 'none';
|
|
27
86
|
children?: ReactNode;
|
|
28
87
|
} & DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
|
|
88
|
+
export type { SearchInput, SearchOutputOf, StandardSchemaV1 } from '@native-router/core';
|
|
89
|
+
/**
|
|
90
|
+
* Active state passed to the render-prop / callback flavors of
|
|
91
|
+
* {@link NavLinkProps.className}, {@link NavLinkProps.style} and
|
|
92
|
+
* {@link NavLinkProps.children}.
|
|
93
|
+
*/
|
|
94
|
+
export type NavLinkState = {
|
|
95
|
+
/** Matches the target pathname, partially or exactly. */
|
|
96
|
+
isActive: boolean;
|
|
97
|
+
/** Matches the target pathname exactly. */
|
|
98
|
+
isExactActive: boolean;
|
|
99
|
+
};
|
|
100
|
+
export type NavLinkProps = {
|
|
101
|
+
/** Target path, same as {@link LinkProps.to}. */
|
|
102
|
+
to: string;
|
|
103
|
+
/** Only the exact pathname counts as active. @default false */
|
|
104
|
+
end?: boolean;
|
|
105
|
+
/** Compare pathnames case-sensitively. @default false */
|
|
106
|
+
caseSensitive?: boolean;
|
|
107
|
+
className?: string | ((state: NavLinkState) => string);
|
|
108
|
+
style?: CSSProperties | ((state: NavLinkState) => CSSProperties);
|
|
109
|
+
/** `aria-current` value rendered while active. @default 'page' */
|
|
110
|
+
ariaCurrent?: 'page' | 'step' | 'location' | 'date' | 'time';
|
|
111
|
+
children?: ReactNode | ((state: NavLinkState) => ReactNode);
|
|
112
|
+
} & Omit<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, 'className' | 'style' | 'children' | 'onClick'>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { SearchInput, SearchOutputOf, StandardSchemaV1 } from '@native-router/core';
|
|
2
|
+
type SetSearchParams = (next: URLSearchParams | ((prev: URLSearchParams) => URLSearchParams), opts?: {
|
|
3
|
+
replace?: boolean;
|
|
4
|
+
}) => Promise<void> | void;
|
|
5
|
+
/**
|
|
6
|
+
* Read and write the search params of the current location.
|
|
7
|
+
*
|
|
8
|
+
* The raw `location.search` string is subscribed via
|
|
9
|
+
* `useSyncExternalStore`(same source as the Router Component), so every
|
|
10
|
+
* location change(push, replace or pop) re-renders the component with the
|
|
11
|
+
* latest params; a fresh `URLSearchParams` view is derived from that string
|
|
12
|
+
* on each render.
|
|
13
|
+
*
|
|
14
|
+
* The setter navigates like any other route change: by default the new
|
|
15
|
+
* search is pushed onto the history stack(mainstream react-router
|
|
16
|
+
* semantics), pass `{replace: true}` to rewrite the current entry instead.
|
|
17
|
+
* Since the search is part of the location, every write re-resolves the
|
|
18
|
+
* matched route, so route `data` fetchers(see {@link useData}) observe the
|
|
19
|
+
* new search on the next {@link useData} read.
|
|
20
|
+
*
|
|
21
|
+
* @group Hooks
|
|
22
|
+
* @returns [searchParams, setSearchParams] - the current search params and
|
|
23
|
+
* the setter(functional updates receive the live previous params); the
|
|
24
|
+
* setter returns a `Promise<void>` that resolves once the navigation
|
|
25
|
+
* commits, so callers may optionally `await` it
|
|
26
|
+
*/
|
|
27
|
+
export declare function useSearchParams(): [URLSearchParams, SetSearchParams];
|
|
28
|
+
/**
|
|
29
|
+
* Read the parsed search params of the current location.
|
|
30
|
+
*
|
|
31
|
+
* The raw `location.search` string is subscribed via
|
|
32
|
+
* `useSyncExternalStore`(same source as {@link useSearchParams}), so every
|
|
33
|
+
* location change(push, replace or pop) re-renders the component with the
|
|
34
|
+
* latest params; the parse itself runs on each render.
|
|
35
|
+
*
|
|
36
|
+
* Without a schema the hook degrades to the raw input object of
|
|
37
|
+
* `parseSearchInput` — strings, arrays for repeated keys
|
|
38
|
+
* (`{page: '2', tag: ['a', 'b']}`). With a schema — any zod/valibot/
|
|
39
|
+
* arktype schema, see the route {@link Route.search search field} — the
|
|
40
|
+
* returned object is the schema's parsed output, so coercion and defaults
|
|
41
|
+
* apply, e.g. `useSearch(pageSchema).page` is a number.
|
|
42
|
+
*
|
|
43
|
+
* Prefer declaring the schema once on the route: its `data` loader then
|
|
44
|
+
* receives a parsed `ctx.search` during resolve, and an invalid search
|
|
45
|
+
* fails the navigation through the existing error channels instead of
|
|
46
|
+
* throwing during render.
|
|
47
|
+
*
|
|
48
|
+
* @group Hooks
|
|
49
|
+
* @param schema an optional Standard Schema validator of the search; it
|
|
50
|
+
* must validate synchronously
|
|
51
|
+
* @returns the parsed search params of the current location
|
|
52
|
+
* @throws {SearchError} when `schema` rejects the current search
|
|
53
|
+
*/
|
|
54
|
+
export declare function useSearch<S extends StandardSchemaV1>(schema: S): SearchOutputOf<S>;
|
|
55
|
+
export declare function useSearch(): SearchInput;
|
|
56
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@native-router/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"test": "vitest",
|
|
41
41
|
"test:ui": "vitest --ui",
|
|
42
42
|
"test:run": "vitest run",
|
|
43
|
-
"serve": "vite preview"
|
|
43
|
+
"serve": "vite preview",
|
|
44
|
+
"typecheck": "tsc -p tsconfig.production.json --noEmit && tsc -p tsconfig.test.json --noEmit"
|
|
44
45
|
},
|
|
45
46
|
"repository": {
|
|
46
47
|
"type": "git",
|
|
@@ -54,68 +55,68 @@
|
|
|
54
55
|
},
|
|
55
56
|
"homepage": "https://github.com/native-router/react",
|
|
56
57
|
"engines": {
|
|
57
|
-
"node": ">=
|
|
58
|
+
"node": ">=18"
|
|
58
59
|
},
|
|
59
60
|
"peerDependencies": {
|
|
60
61
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
61
62
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
62
63
|
},
|
|
63
64
|
"dependencies": {
|
|
64
|
-
"@native-router/core": "^1.
|
|
65
|
-
"history": "^5.3.0"
|
|
65
|
+
"@native-router/core": "^1.4.1",
|
|
66
|
+
"history": "^5.3.0",
|
|
67
|
+
"use-sync-external-store": "^1.6.0"
|
|
66
68
|
},
|
|
67
69
|
"devDependencies": {
|
|
68
|
-
"@babel/core": "^
|
|
69
|
-
"@babel/preset-env": "^
|
|
70
|
-
"@babel/preset-react": "^
|
|
71
|
-
"@babel/preset-typescript": "^
|
|
72
|
-
"@
|
|
73
|
-
"@
|
|
74
|
-
"@linaria/
|
|
70
|
+
"@babel/core": "^8.0.1",
|
|
71
|
+
"@babel/preset-env": "^8.0.2",
|
|
72
|
+
"@babel/preset-react": "^8.0.1",
|
|
73
|
+
"@babel/preset-typescript": "^8.0.1",
|
|
74
|
+
"@eslint-react/eslint-plugin": "^5.18.6",
|
|
75
|
+
"@eslint/js": "^10.0.1",
|
|
76
|
+
"@linaria/babel-preset": "^5.0.4",
|
|
77
|
+
"@linaria/core": "^8.2.0",
|
|
78
|
+
"@linaria/vite": "^5.0.4",
|
|
75
79
|
"@testing-library/dom": "^10.4.1",
|
|
76
|
-
"@testing-library/react": "^
|
|
77
|
-
"@types/node": "^
|
|
78
|
-
"@types/react": "^19.
|
|
79
|
-
"@types/react-dom": "^19.
|
|
80
|
-
"@
|
|
81
|
-
"@
|
|
82
|
-
"@
|
|
83
|
-
"@vitest/coverage-v8": "^4.0.18",
|
|
84
|
-
"@vitest/ui": "^4.0.18",
|
|
80
|
+
"@testing-library/react": "^16.3.2",
|
|
81
|
+
"@types/node": "^26.2.0",
|
|
82
|
+
"@types/react": "^19.2.18",
|
|
83
|
+
"@types/react-dom": "^19.2.4",
|
|
84
|
+
"@vitejs/plugin-react": "^6.1.0",
|
|
85
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
86
|
+
"@vitest/ui": "^4.1.11",
|
|
85
87
|
"babel-plugin-transform-jsx-class": "^0.1.3",
|
|
86
88
|
"babel-plugin-transform-jsx-condition": "^0.1.3",
|
|
87
89
|
"babel-runtime-jsx-plus": "^0.1.5",
|
|
88
|
-
"commitizen": "^4.3.
|
|
89
|
-
"core-js": "^3.
|
|
90
|
-
"cross-env": "^
|
|
91
|
-
"eslint": "^
|
|
92
|
-
"eslint-config-
|
|
93
|
-
"eslint-
|
|
94
|
-
"eslint-
|
|
95
|
-
"eslint-import-
|
|
96
|
-
"eslint-plugin-
|
|
97
|
-
"eslint-plugin-
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"prettier": "^3.0.3",
|
|
108
|
-
"react": "^19.0.0",
|
|
109
|
-
"react-dom": "^19.0.0",
|
|
90
|
+
"commitizen": "^4.3.2",
|
|
91
|
+
"core-js": "^3.50.0",
|
|
92
|
+
"cross-env": "^10.1.0",
|
|
93
|
+
"eslint": "^10.9.0",
|
|
94
|
+
"eslint-config-prettier": "^10.1.8",
|
|
95
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
96
|
+
"eslint-plugin-compat": "^7.0.2",
|
|
97
|
+
"eslint-plugin-import-x": "^4.17.1",
|
|
98
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
99
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
100
|
+
"gh-pages": "^6.3.0",
|
|
101
|
+
"global-jsdom": "^29.0.0",
|
|
102
|
+
"globals": "^17.11.0",
|
|
103
|
+
"husky": "^9.1.7",
|
|
104
|
+
"jsdom": "^30.0.1",
|
|
105
|
+
"lint-staged": "^17.3.0",
|
|
106
|
+
"prettier": "^3.9.6",
|
|
107
|
+
"react": "^19.2.8",
|
|
108
|
+
"react-dom": "^19.2.8",
|
|
110
109
|
"rollup-plugin-type-as-json-schema": "^0.2.6",
|
|
111
|
-
"semantic-release": "^25.0.
|
|
112
|
-
"terser": "^5.
|
|
113
|
-
"tsc-alias": "^1.
|
|
114
|
-
"typedoc": "^0.
|
|
110
|
+
"semantic-release": "^25.0.9",
|
|
111
|
+
"terser": "^5.50.0",
|
|
112
|
+
"tsc-alias": "^1.9.2",
|
|
113
|
+
"typedoc": "^0.28.20",
|
|
115
114
|
"typedoc-plugin-mark-react-functional-components": "^0.2.2",
|
|
116
|
-
"typedoc-plugin-missing-exports": "^
|
|
117
|
-
"typescript": "^
|
|
118
|
-
"
|
|
119
|
-
"
|
|
115
|
+
"typedoc-plugin-missing-exports": "^4.1.4",
|
|
116
|
+
"typescript": "^6.0.3",
|
|
117
|
+
"typescript-eslint": "^8.67.0",
|
|
118
|
+
"vite": "^8.2.2",
|
|
119
|
+
"vitest": "^4.1.11",
|
|
120
|
+
"eslint-plugin-jsx-a11y": "^6.10.2"
|
|
120
121
|
}
|
|
121
122
|
}
|
package/dist/server.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/server.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|