@nifrajs/web-solid 2.1.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.
Files changed (50) hide show
  1. package/README.md +3 -3
  2. package/dist/await.d.ts +2 -2
  3. package/dist/await.js +3 -3
  4. package/dist/client.d.ts +4 -4
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/client.js +19 -11
  7. package/dist/client.js.map +1 -1
  8. package/dist/compose.d.ts +4 -1
  9. package/dist/compose.d.ts.map +1 -1
  10. package/dist/compose.js +21 -3
  11. package/dist/compose.js.map +1 -1
  12. package/dist/content.d.ts +2 -2
  13. package/dist/content.js +2 -2
  14. package/dist/error.d.ts +1 -1
  15. package/dist/error.js +2 -2
  16. package/dist/fetcher.d.ts +4 -4
  17. package/dist/fetcher.js +4 -4
  18. package/dist/fn.d.ts +29 -0
  19. package/dist/fn.d.ts.map +1 -0
  20. package/dist/fn.js +28 -0
  21. package/dist/fn.js.map +1 -0
  22. package/dist/i18n.d.ts +1 -1
  23. package/dist/i18n.js +1 -1
  24. package/dist/image.d.ts +3 -3
  25. package/dist/image.js +1 -1
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.js +7 -7
  28. package/dist/mdx-runtime.js +2 -2
  29. package/dist/mdx.js +5 -5
  30. package/dist/query.d.ts +2 -2
  31. package/dist/query.js +2 -2
  32. package/dist/router.d.ts +45 -0
  33. package/dist/router.d.ts.map +1 -0
  34. package/dist/router.js +60 -0
  35. package/dist/router.js.map +1 -0
  36. package/package.json +16 -5
  37. package/src/await.ts +3 -3
  38. package/src/client.ts +22 -12
  39. package/src/compose.ts +23 -3
  40. package/src/content.ts +2 -2
  41. package/src/error.ts +2 -2
  42. package/src/fetcher.ts +4 -4
  43. package/src/fn.ts +42 -0
  44. package/src/i18n.ts +1 -1
  45. package/src/image.ts +3 -3
  46. package/src/index.ts +7 -7
  47. package/src/mdx-runtime.ts +2 -2
  48. package/src/mdx.ts +5 -5
  49. package/src/query.ts +2 -2
  50. 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
+ }