@paramour-js/next 0.3.1 → 0.4.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/dist/app.js +40 -12
- package/dist/navigation-adapter.d.ts +60 -0
- package/dist/navigation-adapter.js +9 -0
- package/dist/pages.js +18 -2
- package/dist/testing.d.ts +72 -0
- package/dist/testing.js +113 -0
- package/package.json +5 -1
package/dist/app.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useParams, usePathname, useRouter, useSearchParams, } from "next/navigation";
|
|
3
3
|
import { decodeParams, decodeSearch, ParamsDecodeError, safeDecodeParams, safeDecodeSearch, SearchDecodeError, } from "paramour";
|
|
4
|
+
import { useContext } from "react";
|
|
4
5
|
import { searchWireSnapshot } from "./devtools-seam.js";
|
|
6
|
+
import { AppNavigationContext, } from "./navigation-adapter.js";
|
|
5
7
|
import { makeAppNavigate, useDevtoolsEmitter, } from "./observe.js";
|
|
6
8
|
import { paramsFingerprint, searchParamsFingerprint, useSelectedResult, useSelectedValue, useStableResult, } from "./select.js";
|
|
7
9
|
export function useRouteParams(route, options) {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
10
|
+
const nav = useAppNavigation();
|
|
11
|
+
const params = nav.useParams() ?? {};
|
|
12
|
+
const router = nav.useRouter();
|
|
13
|
+
const pathname = nav.usePathname();
|
|
11
14
|
const emitter = useDevtoolsEmitter();
|
|
12
15
|
const spec = process.env.NODE_ENV === "production"
|
|
13
16
|
? undefined
|
|
@@ -33,9 +36,10 @@ export function useRouteParams(route, options) {
|
|
|
33
36
|
return useSelectedResult(result, options);
|
|
34
37
|
}
|
|
35
38
|
export function useRouteParamsOrThrow(route, options) {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
+
const nav = useAppNavigation();
|
|
40
|
+
const params = nav.useParams() ?? {};
|
|
41
|
+
const router = nav.useRouter();
|
|
42
|
+
const pathname = nav.usePathname();
|
|
39
43
|
const emitter = useDevtoolsEmitter();
|
|
40
44
|
const spec = process.env.NODE_ENV === "production"
|
|
41
45
|
? undefined
|
|
@@ -77,9 +81,10 @@ export function useRouteParamsOrThrow(route, options) {
|
|
|
77
81
|
return useSelectedValue(value, options);
|
|
78
82
|
}
|
|
79
83
|
export function useSearch(route, options) {
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
84
|
+
const nav = useAppNavigation();
|
|
85
|
+
const searchParams = nav.useSearchParams();
|
|
86
|
+
const router = nav.useRouter();
|
|
87
|
+
const pathname = nav.usePathname();
|
|
83
88
|
const emitter = useDevtoolsEmitter();
|
|
84
89
|
const spec = process.env.NODE_ENV === "production"
|
|
85
90
|
? undefined
|
|
@@ -105,9 +110,10 @@ export function useSearch(route, options) {
|
|
|
105
110
|
return useSelectedResult(result, options);
|
|
106
111
|
}
|
|
107
112
|
export function useSearchOrThrow(route, options) {
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const
|
|
113
|
+
const nav = useAppNavigation();
|
|
114
|
+
const searchParams = nav.useSearchParams();
|
|
115
|
+
const router = nav.useRouter();
|
|
116
|
+
const pathname = nav.usePathname();
|
|
111
117
|
const emitter = useDevtoolsEmitter();
|
|
112
118
|
const spec = process.env.NODE_ENV === "production"
|
|
113
119
|
? undefined
|
|
@@ -156,3 +162,25 @@ export function useSearchOrThrow(route, options) {
|
|
|
156
162
|
}
|
|
157
163
|
return useSelectedValue(value, options);
|
|
158
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Real-Next fallback for the adapter seam (design-16 TA3): the /testing
|
|
167
|
+
* provider overrides these reads through {@link AppNavigationContext}; with
|
|
168
|
+
* no provider mounted the context's `null` default resolves here, so
|
|
169
|
+
* production behavior (and this module's `next/navigation`-only bundle
|
|
170
|
+
* graph, per dist.test.ts) is unchanged.
|
|
171
|
+
*/
|
|
172
|
+
const realAppAdapter = {
|
|
173
|
+
useParams,
|
|
174
|
+
usePathname,
|
|
175
|
+
useRouter,
|
|
176
|
+
useSearchParams,
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Every hook resolves the adapter ONCE at its top and calls the adapter's
|
|
180
|
+
* reads unconditionally, exactly where the direct Next calls previously sat
|
|
181
|
+
* — hook call order is identical across renders and across provider
|
|
182
|
+
* presence (TA4).
|
|
183
|
+
*/
|
|
184
|
+
function useAppNavigation() {
|
|
185
|
+
return useContext(AppNavigationContext) ?? realAppAdapter;
|
|
186
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ParamsSource } from "paramour";
|
|
2
|
+
/**
|
|
3
|
+
* Adapter seam for the client hooks' framework reads (design-16 TA1): each
|
|
4
|
+
* flavor's hooks resolve Next through a React context so the /testing entry
|
|
5
|
+
* can override the reads without runner-specific module mocking.
|
|
6
|
+
* Deliberately NO `"use client"` directive — app.ts (which carries one) and
|
|
7
|
+
* pages.ts (which must not, PR2) both import from here, like
|
|
8
|
+
* observe.ts/select.ts; the directive belongs on the entry modules, not a
|
|
9
|
+
* shared leaf.
|
|
10
|
+
*
|
|
11
|
+
* This module is Next-free on purpose (TA3): the contexts default to `null`
|
|
12
|
+
* and each flavor ENTRY supplies its own real-Next fallback
|
|
13
|
+
* (`useContext(ctx) ?? realAdapter`), so neither this module nor the
|
|
14
|
+
* /testing entry ever drags a `next/*` specifier into its graph. The
|
|
15
|
+
* dist.test.ts bundle-hygiene invariants (/app reaches only
|
|
16
|
+
* `next/navigation`, /pages only `next/router.js`, /testing neither) depend
|
|
17
|
+
* on exactly this split — a context whose DEFAULT VALUE were the real
|
|
18
|
+
* adapter would break all three.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* App-flavor adapter: EXACTLY the ambient view of `next/navigation` declared
|
|
22
|
+
* in `src/types/next-navigation.d.ts` — that ambient is the contract of
|
|
23
|
+
* record, and this interface must stay in lockstep with it (TA2; the
|
|
24
|
+
* `examples/next-compat` pins guard the real-Next side). `useParams()`'s
|
|
25
|
+
* `null` arm is the outside-App-Router-tree state (Next #48058 family) the
|
|
26
|
+
* hooks deliberately tolerate; `useRouter().replace`/`usePathname` are the
|
|
27
|
+
* devtools `navigate` capability's write path and resolution base (DT8).
|
|
28
|
+
*/
|
|
29
|
+
export interface AppNavigationAdapter {
|
|
30
|
+
useParams(): null | ParamsSource;
|
|
31
|
+
usePathname(): string;
|
|
32
|
+
useRouter(): {
|
|
33
|
+
replace(href: string): void;
|
|
34
|
+
};
|
|
35
|
+
useSearchParams(): URLSearchParams;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Pages-flavor adapter: EXACTLY the ambient view of `next/router.js`
|
|
39
|
+
* declared in `src/types/next-router.d.ts` — that ambient is the contract of
|
|
40
|
+
* record, and this interface must stay in lockstep with it (TA2). The
|
|
41
|
+
* ambient's throw-on-unmounted behavior under `app/` (PR5) is part of the
|
|
42
|
+
* contract: adapter implementations reproduce it by THROWING from
|
|
43
|
+
* `useRouter()`, which pages.ts translates.
|
|
44
|
+
*/
|
|
45
|
+
export interface PagesNavigationAdapter {
|
|
46
|
+
useRouter(): {
|
|
47
|
+
asPath: string;
|
|
48
|
+
isReady: boolean;
|
|
49
|
+
query: ParamsSource;
|
|
50
|
+
replace(url: string): Promise<boolean>;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The `null` default is load-bearing (TA3): with no provider mounted,
|
|
55
|
+
* app.ts falls back to its real `next/navigation` adapter — zero markup and
|
|
56
|
+
* zero behavior change in production (TA4).
|
|
57
|
+
*/
|
|
58
|
+
export declare const AppNavigationContext: import("react").Context<AppNavigationAdapter | null>;
|
|
59
|
+
/** The pages twin; `null` default load-bearing for the same TA3 reasons. */
|
|
60
|
+
export declare const PagesNavigationContext: import("react").Context<PagesNavigationAdapter | null>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The `null` default is load-bearing (TA3): with no provider mounted,
|
|
4
|
+
* app.ts falls back to its real `next/navigation` adapter — zero markup and
|
|
5
|
+
* zero behavior change in production (TA4).
|
|
6
|
+
*/
|
|
7
|
+
export const AppNavigationContext = createContext(null);
|
|
8
|
+
/** The pages twin; `null` default load-bearing for the same TA3 reasons. */
|
|
9
|
+
export const PagesNavigationContext = createContext(null);
|
package/dist/pages.js
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
// every bundler. Guarded by the examples/next-compat build-app.
|
|
7
7
|
import { useRouter } from "next/router.js";
|
|
8
8
|
import { ParamourError, safeDecodeParams, safeDecodeSearch, } from "paramour";
|
|
9
|
+
import { useContext } from "react";
|
|
9
10
|
import { recordWireSnapshot } from "./devtools-seam.js";
|
|
11
|
+
import { PagesNavigationContext, } from "./navigation-adapter.js";
|
|
10
12
|
import { makePagesNavigate, useDevtoolsEmitter, } from "./observe.js";
|
|
11
13
|
import { paramsFingerprint, PENDING_FINGERPRINT, queryFingerprint, useSelectedResult, useStableResult, } from "./select.js";
|
|
12
14
|
/** Referentially stable across every pending render. */
|
|
@@ -99,17 +101,31 @@ function omitPathParams(query, route) {
|
|
|
99
101
|
}
|
|
100
102
|
return Object.fromEntries(Object.entries(query).filter(([key]) => !names.has(key)));
|
|
101
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Real-Next fallback for the adapter seam (design-16 TA3): the /testing
|
|
106
|
+
* provider overrides the read through {@link PagesNavigationContext}; with
|
|
107
|
+
* no provider mounted the context's `null` default resolves here, so
|
|
108
|
+
* production behavior (and this module's `next/router.js`-only bundle
|
|
109
|
+
* graph, per dist.test.ts) is unchanged. The adapter is resolved via an
|
|
110
|
+
* unconditional `useContext` BEFORE the try below, exactly where the direct
|
|
111
|
+
* call previously sat, keeping hook call order identical across renders and
|
|
112
|
+
* across provider presence (TA4).
|
|
113
|
+
*/
|
|
114
|
+
const realPagesAdapter = { useRouter };
|
|
102
115
|
/**
|
|
103
116
|
* `useRouter` with the one failure the brand cannot catch translated (PR5):
|
|
104
117
|
* in a hybrid project a component rendered under `app/` can legally hold a
|
|
105
118
|
* pages-branded route, but `next/router` has no mount there and throws
|
|
106
119
|
* "NextRouter was not mounted" — a message pointing at the wrong fix
|
|
107
120
|
* (component placement is invisible to the type system). Rethrow a
|
|
108
|
-
* `ParamourError` naming the actual mistake; everything else propagates
|
|
121
|
+
* `ParamourError` naming the actual mistake; everything else propagates —
|
|
122
|
+
* and the adapter's `useRouter()` stays INSIDE the try so a testing
|
|
123
|
+
* adapter's unmounted reproduction gets the same translation.
|
|
109
124
|
*/
|
|
110
125
|
function usePagesRouter() {
|
|
126
|
+
const nav = useContext(PagesNavigationContext) ?? realPagesAdapter;
|
|
111
127
|
try {
|
|
112
|
-
return useRouter();
|
|
128
|
+
return nav.useRouter();
|
|
113
129
|
}
|
|
114
130
|
catch (error) {
|
|
115
131
|
if (error instanceof Error &&
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ParamsSource } from "paramour";
|
|
2
|
+
import type { ReactElement, ReactNode } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* `@paramour-js/next/testing` (design-16): a provider that overrides the
|
|
5
|
+
* hooks' framework reads through the adapter seam (TA1), so client
|
|
6
|
+
* components calling `useSearch`/`useRouteParams` (either flavor) can be
|
|
7
|
+
* unit-tested without runner-specific `next/*` module mocking. One provider
|
|
8
|
+
* feeds BOTH flavor contexts (TA5) — hybrid apps and pages components need
|
|
9
|
+
* no second import. Server code needs none of this: `parse`/`safeParse` and
|
|
10
|
+
* server components are pure functions over props (TA8).
|
|
11
|
+
*
|
|
12
|
+
* This module imports ONLY react and the (Next-free) adapter-seam module —
|
|
13
|
+
* no `next/*` specifier and no `@testing-library/*` (TA5; dist.test.ts pins
|
|
14
|
+
* the bundle graph, and the hermeticity check there is why these docs never
|
|
15
|
+
* spell out the two Next specifiers). It carries `"use client"` like
|
|
16
|
+
* app.ts.
|
|
17
|
+
*
|
|
18
|
+
* Stability contract (TA4): the provider holds ONE adapter pair for its
|
|
19
|
+
* lifetime, created once and closing over a latest-props ref that is
|
|
20
|
+
* reassigned every render — prop changes mutate what the stable adapters
|
|
21
|
+
* RETURN, they never mint new adapters, so the hooks' context reads stay
|
|
22
|
+
* identity-stable and mid-test URL changes are driven by ordinary rerenders
|
|
23
|
+
* with new props.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Input shape mirrors what Next hands the hooks (TA6) — no `url`
|
|
27
|
+
* reverse-matching in v1 (deferred: needs a core matcher). `params: null`
|
|
28
|
+
* and `mounted: false` are first-class because they are the two states
|
|
29
|
+
* nobody hand-rolling a mock models.
|
|
30
|
+
*/
|
|
31
|
+
export interface ParamourTestingOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Pages flavor only: `false` is the pre-hydration state of a
|
|
34
|
+
* statically-optimized page (`query` not yet populated — the PR5
|
|
35
|
+
* `pending` arm). Defaults to `true`. design-16 listed this as deferred,
|
|
36
|
+
* but TA7 migrates the whole pages suite — which pins the pending arm —
|
|
37
|
+
* to this provider, so it was promoted from the deferred list.
|
|
38
|
+
*/
|
|
39
|
+
isReady?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Pages flavor only: `false` reproduces the pages router's
|
|
42
|
+
* throw-on-unmounted state under `app/` (PR5) — pages.ts translates it to
|
|
43
|
+
* a `ParamourError` naming the actual mistake.
|
|
44
|
+
*/
|
|
45
|
+
mounted?: boolean;
|
|
46
|
+
/** Captures `replace(href)` from either flavor's router. */
|
|
47
|
+
onReplace?: (href: string) => void;
|
|
48
|
+
/**
|
|
49
|
+
* `null` is the hybrid-app `useParams()` state outside an App-Router tree
|
|
50
|
+
* (Next #48058 family) and passes through as `null`; omitted means `{}`.
|
|
51
|
+
*/
|
|
52
|
+
params?: null | ParamsSource;
|
|
53
|
+
/** Defaults to `"/"`. */
|
|
54
|
+
pathname?: string;
|
|
55
|
+
/** `"?page=2"` and `"page=2"` are both accepted. */
|
|
56
|
+
search?: string | URLSearchParams;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Renders BOTH flavor contexts' providers around `children` (TA5). Exported
|
|
60
|
+
* for people composing their own wrappers (Storybook decorators, custom
|
|
61
|
+
* render helpers); testing-library users want {@link withParamourTesting}.
|
|
62
|
+
*/
|
|
63
|
+
export declare function ParamourTestingProvider(props: ParamourTestingOptions & {
|
|
64
|
+
children?: ReactNode;
|
|
65
|
+
}): ReactElement;
|
|
66
|
+
/**
|
|
67
|
+
* Wrapper-component form for testing-library's `wrapper` option (TA5,
|
|
68
|
+
* mirroring `withNuqsTestingAdapter`).
|
|
69
|
+
*/
|
|
70
|
+
export declare function withParamourTesting(options?: ParamourTestingOptions): (props: {
|
|
71
|
+
children?: ReactNode;
|
|
72
|
+
}) => ReactElement;
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useState } from "react";
|
|
4
|
+
import { AppNavigationContext, PagesNavigationContext, } from "./navigation-adapter.js";
|
|
5
|
+
/**
|
|
6
|
+
* Renders BOTH flavor contexts' providers around `children` (TA5). Exported
|
|
7
|
+
* for people composing their own wrappers (Storybook decorators, custom
|
|
8
|
+
* render helpers); testing-library users want {@link withParamourTesting}.
|
|
9
|
+
*/
|
|
10
|
+
export function ParamourTestingProvider(props) {
|
|
11
|
+
// Latest-ref pattern (TA4): reassigned every render so the stable
|
|
12
|
+
// adapters below always read the CURRENT render's props.
|
|
13
|
+
const latest = useRef(props);
|
|
14
|
+
latest.current = props;
|
|
15
|
+
const [adapters] = useState(() => createAdapters(latest));
|
|
16
|
+
return (_jsx(AppNavigationContext.Provider, { value: adapters.app, children: _jsx(PagesNavigationContext.Provider, { value: adapters.pages, children: props.children }) }));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Wrapper-component form for testing-library's `wrapper` option (TA5,
|
|
20
|
+
* mirroring `withNuqsTestingAdapter`).
|
|
21
|
+
*/
|
|
22
|
+
export function withParamourTesting(options = {}) {
|
|
23
|
+
return function ParamourTestingWrapper({ children, }) {
|
|
24
|
+
return (_jsx(ParamourTestingProvider, { ...options, children: children }));
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The one adapter pair a provider instance ever holds (TA4). Every read
|
|
29
|
+
* defers to `latest.current`, so the adapters are stable while their
|
|
30
|
+
* answers track prop updates. Fresh `URLSearchParams` per call is fine —
|
|
31
|
+
* the hooks fingerprint the declared slice (SEL4), not the instance.
|
|
32
|
+
*/
|
|
33
|
+
function createAdapters(latest) {
|
|
34
|
+
const app = {
|
|
35
|
+
useParams() {
|
|
36
|
+
const { params } = latest.current;
|
|
37
|
+
// `null` must pass through as-is — it is the hybrid-app state the
|
|
38
|
+
// app hooks deliberately tolerate; only OMITTED means `{}`.
|
|
39
|
+
return params === undefined ? {} : params;
|
|
40
|
+
},
|
|
41
|
+
usePathname() {
|
|
42
|
+
return latest.current.pathname ?? "/";
|
|
43
|
+
},
|
|
44
|
+
useRouter() {
|
|
45
|
+
return {
|
|
46
|
+
replace(href) {
|
|
47
|
+
latest.current.onReplace?.(href);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
useSearchParams() {
|
|
52
|
+
return new URLSearchParams(normalizeSearch(latest.current.search));
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const pages = {
|
|
56
|
+
useRouter() {
|
|
57
|
+
const options = latest.current;
|
|
58
|
+
if (options.mounted === false) {
|
|
59
|
+
// Verbatim prefix of next/router's real unmounted error — pages.ts
|
|
60
|
+
// matches on the message to translate it (PR5).
|
|
61
|
+
throw new Error("NextRouter was not mounted. https://nextjs.org/docs/messages/next-router-not-mounted");
|
|
62
|
+
}
|
|
63
|
+
const search = normalizeSearch(options.search);
|
|
64
|
+
return {
|
|
65
|
+
// asPath derives from pathname + normalized search (TA6) —
|
|
66
|
+
// basePath-relative, what the devtools navigate capability resolves
|
|
67
|
+
// against (DT8).
|
|
68
|
+
asPath: (options.pathname ?? "/") + (search === "" ? "" : `?${search}`),
|
|
69
|
+
isReady: options.isReady ?? true,
|
|
70
|
+
query: mergedQuery(options),
|
|
71
|
+
replace(url) {
|
|
72
|
+
latest.current.onReplace?.(url);
|
|
73
|
+
// Real next/router resolves `true` on a completed replace (TA6).
|
|
74
|
+
return Promise.resolve(true);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
return { app, pages };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The merged `query` bag real Next hands the pages router: search entries
|
|
83
|
+
* first (single value → scalar, repeats → `string[]`), then `params`
|
|
84
|
+
* entries override — path params win, mirroring real Next's merge.
|
|
85
|
+
* Entries → fromEntries for define-semantics, so a hostile `"__proto__"`
|
|
86
|
+
* key stays an ordinary own property (omitPathParams's ethos in pages.ts).
|
|
87
|
+
*/
|
|
88
|
+
function mergedQuery(options) {
|
|
89
|
+
const entries = [];
|
|
90
|
+
const searchParams = new URLSearchParams(normalizeSearch(options.search));
|
|
91
|
+
for (const key of new Set(searchParams.keys())) {
|
|
92
|
+
const values = searchParams.getAll(key);
|
|
93
|
+
const first = values[0];
|
|
94
|
+
// Unreachable for a key yielded by keys(); satisfies
|
|
95
|
+
// noUncheckedIndexedAccess without a cast.
|
|
96
|
+
if (first === undefined)
|
|
97
|
+
continue;
|
|
98
|
+
entries.push([key, values.length === 1 ? first : values]);
|
|
99
|
+
}
|
|
100
|
+
for (const entry of Object.entries(options.params ?? {})) {
|
|
101
|
+
entries.push(entry);
|
|
102
|
+
}
|
|
103
|
+
return Object.fromEntries(entries);
|
|
104
|
+
}
|
|
105
|
+
/** `undefined` → `""`; `URLSearchParams` → its string; strip one leading `?`. */
|
|
106
|
+
function normalizeSearch(search) {
|
|
107
|
+
if (search === undefined)
|
|
108
|
+
return "";
|
|
109
|
+
if (typeof search === "string") {
|
|
110
|
+
return search.startsWith("?") ? search.slice(1) : search;
|
|
111
|
+
}
|
|
112
|
+
return search.toString();
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paramour-js/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"./pages": {
|
|
18
18
|
"types": "./dist/pages.d.ts",
|
|
19
19
|
"default": "./dist/pages.js"
|
|
20
|
+
},
|
|
21
|
+
"./testing": {
|
|
22
|
+
"types": "./dist/testing.d.ts",
|
|
23
|
+
"default": "./dist/testing.js"
|
|
20
24
|
}
|
|
21
25
|
},
|
|
22
26
|
"sideEffects": false,
|