@nifrajs/web-solid 2.2.0 → 2.3.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 +3 -3
- package/dist/await.d.ts +2 -2
- package/dist/await.js +3 -3
- package/dist/client.d.ts +4 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +19 -11
- package/dist/client.js.map +1 -1
- package/dist/compose.d.ts +4 -1
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +16 -2
- package/dist/compose.js.map +1 -1
- package/dist/content.d.ts +2 -2
- package/dist/content.js +2 -2
- package/dist/error.d.ts +1 -1
- package/dist/error.js +2 -2
- package/dist/fetcher.d.ts +4 -4
- package/dist/fetcher.js +4 -4
- package/dist/fn.d.ts +29 -0
- package/dist/fn.d.ts.map +1 -0
- package/dist/fn.js +28 -0
- package/dist/fn.js.map +1 -0
- package/dist/i18n.d.ts +1 -1
- package/dist/i18n.js +1 -1
- package/dist/image.d.ts +3 -3
- package/dist/image.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -7
- package/dist/mdx-runtime.js +2 -2
- package/dist/mdx.js +5 -5
- package/dist/query.d.ts +2 -2
- package/dist/query.js +2 -2
- package/dist/router.d.ts +45 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +60 -0
- package/dist/router.js.map +1 -0
- package/package.json +16 -5
- package/src/await.ts +3 -3
- package/src/client.ts +22 -12
- package/src/compose.ts +18 -2
- package/src/content.ts +2 -2
- package/src/error.ts +2 -2
- package/src/fetcher.ts +4 -4
- package/src/fn.ts +42 -0
- package/src/i18n.ts +1 -1
- package/src/image.ts +3 -3
- package/src/index.ts +7 -7
- package/src/mdx-runtime.ts +2 -2
- package/src/mdx.ts +5 -5
- package/src/query.ts +2 -2
- package/src/router.ts +85 -0
package/src/router.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { InferOutput, StandardSchemaV1 } from "@nifrajs/core/server"
|
|
2
|
+
import {
|
|
3
|
+
type Blocker,
|
|
4
|
+
type BlockerFunction,
|
|
5
|
+
getBrowserNavigate,
|
|
6
|
+
IDLE_BLOCKER,
|
|
7
|
+
type NavigateFunction,
|
|
8
|
+
type NavigateOptions,
|
|
9
|
+
type NavigateTargetInput,
|
|
10
|
+
registerBlocker,
|
|
11
|
+
resolveNavigate,
|
|
12
|
+
} from "@nifrajs/web"
|
|
13
|
+
/**
|
|
14
|
+
* `@nifrajs/web-solid/router` - Solid routing bindings over the agnostic `@nifrajs/web` history layer:
|
|
15
|
+
* `useNavigate` (programmatic navigation), `useBlocker` (the unsaved-changes guard), and `useSearch`
|
|
16
|
+
* (the route's typed, validated search, as a reactive accessor). Navigation goes through `@nifrajs/web`'s
|
|
17
|
+
* DOM-free bridges (`getBrowserNavigate` / `registerBlocker`, populated by `installHistory`); `useSearch`
|
|
18
|
+
* reads the accessor `compose` provides on SSR + client mount alike. Imports only `solid-js`. No JSX.
|
|
19
|
+
*/
|
|
20
|
+
import { type Accessor, createContext, createSignal, onCleanup, useContext } from "solid-js"
|
|
21
|
+
|
|
22
|
+
export type { Blocker, BlockerFunction, BlockerState, NavigateFunction } from "@nifrajs/web"
|
|
23
|
+
|
|
24
|
+
// Frozen empty search + a stable accessor for a `useSearch` used outside a nifra route tree.
|
|
25
|
+
const EMPTY_SEARCH: Readonly<Record<string, unknown>> = Object.freeze({})
|
|
26
|
+
const EMPTY_SEARCH_ACCESSOR: Accessor<Record<string, unknown>> = () => EMPTY_SEARCH
|
|
27
|
+
|
|
28
|
+
/** The current route's validated search accessor, provided by `compose` on SSR + client mount alike
|
|
29
|
+
* (derived from the URL via the shared `searchOfChain`). Read via {@link useSearch}. */
|
|
30
|
+
export const SearchContext = createContext<Accessor<Record<string, unknown>>>()
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The route's typed, validated search params as a reactive accessor - the SAME value the loader received
|
|
34
|
+
* as `ctx.search`. SSR-correct: `compose` provides it from the URL server-side and from the identical
|
|
35
|
+
* client-mount derivation, so a value rendered from it doesn't flash on hydration. Call it to read the
|
|
36
|
+
* current search (`useSearch()().page`); it updates in place on a same-route navigation. Pass the route's
|
|
37
|
+
* `searchSchema` as the type argument for its output type.
|
|
38
|
+
*
|
|
39
|
+
* ```tsx
|
|
40
|
+
* const search = useSearch<typeof searchSchema>() // Accessor<{ page: number }>
|
|
41
|
+
* return <span>{search().page}</span>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function useSearch<Schema extends StandardSchemaV1 | undefined = undefined>(): Accessor<
|
|
45
|
+
Schema extends StandardSchemaV1 ? InferOutput<Schema> : Record<string, unknown>
|
|
46
|
+
> {
|
|
47
|
+
return (useContext(SearchContext) ?? EMPTY_SEARCH_ACCESSOR) as Accessor<
|
|
48
|
+
Schema extends StandardSchemaV1 ? InferOutput<Schema> : Record<string, unknown>
|
|
49
|
+
>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Get the {@link NavigateFunction} (a string path, a history delta, or a typed `{ to, search }` object).
|
|
53
|
+
* Resolves the browser navigate at call time, so it works as soon as `installHistory` has run and no-ops
|
|
54
|
+
* before then / on the server. */
|
|
55
|
+
export function useNavigate(): NavigateFunction {
|
|
56
|
+
return ((to: string | number | NavigateTargetInput, options?: NavigateOptions) => {
|
|
57
|
+
const navigate = getBrowserNavigate()
|
|
58
|
+
if (navigate === undefined) return
|
|
59
|
+
const resolved = resolveNavigate(to, options)
|
|
60
|
+
navigate(resolved.to, resolved.options)
|
|
61
|
+
}) as NavigateFunction
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Guard navigation away from a page with unsaved work, confirming with your OWN async UI. Mirrors
|
|
66
|
+
* react-router's `useBlocker`: pass a boolean or a `({ currentLocation, nextLocation }) => boolean`
|
|
67
|
+
* predicate, and get back a reactive {@link Blocker} accessor. When a navigation (an anchor click,
|
|
68
|
+
* `useNavigate`, or a browser back/forward) is intercepted, `blocker().state` becomes `"blocked"` and
|
|
69
|
+
* `proceed`/`reset` go live - show a dialog and call `proceed()` to continue or `reset()` to stay put.
|
|
70
|
+
* It also arms the browser's native "Leave site?" prompt on tab close / reload. Idle on the server and
|
|
71
|
+
* before hydration.
|
|
72
|
+
*
|
|
73
|
+
* The component body runs once, so to track a CHANGING flag pass a function -
|
|
74
|
+
* `useBlocker(() => isDirty())` - not a bare signal accessor's current value. A constant boolean is fine.
|
|
75
|
+
*/
|
|
76
|
+
export function useBlocker(shouldBlock: boolean | BlockerFunction): Accessor<Blocker> {
|
|
77
|
+
const [blocker, setBlocker] = createSignal<Blocker>(IDLE_BLOCKER)
|
|
78
|
+
onCleanup(
|
|
79
|
+
registerBlocker(
|
|
80
|
+
(args) => (typeof shouldBlock === "function" ? shouldBlock(args) : shouldBlock),
|
|
81
|
+
setBlocker,
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
return blocker
|
|
85
|
+
}
|