eddev 2.3.12 → 2.3.13

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 (95) hide show
  1. package/dist/app/entry/MetaTags.d.ts.map +1 -1
  2. package/dist/app/entry/MetaTags.js +2 -0
  3. package/dist/app/lib/blocks/block-utils.d.ts +2 -2
  4. package/dist/app/lib/blocks/block-utils.d.ts.map +1 -1
  5. package/dist/app/lib/blocks/block-utils.js +10 -3
  6. package/dist/app/lib/blocks/editor/root-blocks.d.ts.map +1 -1
  7. package/dist/app/lib/blocks/editor/root-blocks.js +2 -2
  8. package/dist/app/lib/devtools/hooks/useTailwind.d.ts +45 -45
  9. package/dist/app/lib/hooks/query-hooks.d.ts +8 -8
  10. package/dist/app/lib/hooks/query-hooks.d.ts.map +1 -1
  11. package/dist/app/lib/hooks/query-hooks.js +2 -1
  12. package/dist/app/lib/runtime/apiConfig.d.ts +15 -0
  13. package/dist/app/lib/runtime/apiConfig.d.ts.map +1 -1
  14. package/dist/app/server/proxy-wp-admin.d.ts.map +1 -1
  15. package/dist/app/server/proxy-wp-admin.js +1 -0
  16. package/dist/app/server/server-context.d.ts +2 -2
  17. package/dist/app/server/server-context.d.ts.map +1 -1
  18. package/dist/app/utils/trpc-client.d.ts.map +1 -1
  19. package/dist/app/utils/trpc-client.js +19 -3
  20. package/dist/node/cli/version.d.ts +1 -1
  21. package/dist/node/cli/version.js +1 -1
  22. package/dist/node/compiler/dev-server.js +1 -1
  23. package/dist/node/compiler/get-vite-config.d.ts.map +1 -1
  24. package/dist/node/compiler/get-vite-config.js +1 -0
  25. package/dist/node/compiler/vinxi-app.d.ts +1 -1
  26. package/dist/node/compiler/vinxi-app.d.ts.map +1 -1
  27. package/dist/node/utils/fetch-wp.d.ts.map +1 -1
  28. package/dist/node/utils/fetch-wp.js +2 -1
  29. package/dist/node/utils/fs.d.ts +22 -19
  30. package/dist/node/utils/fs.d.ts.map +1 -1
  31. package/package.json +2 -2
  32. package/skills/eddev/SKILL.md +156 -0
  33. package/skills/eddev/docs/acf/admin-panel-widgets.mdx +99 -0
  34. package/skills/eddev/docs/acf/custom-enums.mdx +75 -0
  35. package/skills/eddev/docs/acf/custom-fields.mdx +131 -0
  36. package/skills/eddev/docs/acf.mdx +31 -0
  37. package/skills/eddev/docs/blocks/block-definition.mdx +189 -0
  38. package/skills/eddev/docs/blocks/core-blocks.mdx +86 -0
  39. package/skills/eddev/docs/blocks/data-and-editing.mdx +219 -0
  40. package/skills/eddev/docs/blocks/editor-config.mdx +157 -0
  41. package/skills/eddev/docs/blocks/nested-blocks.mdx +129 -0
  42. package/skills/eddev/docs/blocks/overview.mdx +58 -0
  43. package/skills/eddev/docs/blocks/template-parts.mdx +131 -0
  44. package/skills/eddev/docs/config.mdx +200 -0
  45. package/skills/eddev/docs/design/color.mdx +185 -0
  46. package/skills/eddev/docs/design/favicons.mdx +103 -0
  47. package/skills/eddev/docs/design/grid.mdx +120 -0
  48. package/skills/eddev/docs/design/icons.mdx +197 -0
  49. package/skills/eddev/docs/design/responsive-scaling.mdx +312 -0
  50. package/skills/eddev/docs/design/type.mdx +125 -0
  51. package/skills/eddev/docs/devtool/cli.mdx +201 -0
  52. package/skills/eddev/docs/devtool/overlay.mdx +5 -0
  53. package/skills/eddev/docs/getting-started.mdx +53 -0
  54. package/skills/eddev/docs/graphql/extending.mdx +186 -0
  55. package/skills/eddev/docs/graphql/fragments.mdx +107 -0
  56. package/skills/eddev/docs/graphql/global-data.mdx +47 -0
  57. package/skills/eddev/docs/graphql/infinite-queries.mdx +95 -0
  58. package/skills/eddev/docs/graphql/mutation-hooks.mdx +111 -0
  59. package/skills/eddev/docs/graphql/query-hooks.mdx +122 -0
  60. package/skills/eddev/docs/graphql/tooling.mdx +50 -0
  61. package/skills/eddev/docs/graphql.mdx +97 -0
  62. package/skills/eddev/docs/guides/color-schemes.mdx +204 -0
  63. package/skills/eddev/docs/guides/integrations.mdx +3 -0
  64. package/skills/eddev/docs/guides/page-transitions.mdx +5 -0
  65. package/skills/eddev/docs/guides/seo.mdx +5 -0
  66. package/skills/eddev/docs/guides/state-management.mdx +5 -0
  67. package/skills/eddev/docs/infra/caching.mdx +9 -0
  68. package/skills/eddev/docs/infra/deployment.mdx +13 -0
  69. package/skills/eddev/docs/infra/local.mdx +5 -0
  70. package/skills/eddev/docs/infra/security.mdx +11 -0
  71. package/skills/eddev/docs/routing/api.mdx +731 -0
  72. package/skills/eddev/docs/routing/custom.mdx +123 -0
  73. package/skills/eddev/docs/routing/full-details.mdx +37 -0
  74. package/skills/eddev/docs/routing/wordpress.mdx +70 -0
  75. package/skills/eddev/docs/routing.mdx +18 -0
  76. package/skills/eddev/docs/serverless/functions.mdx +436 -0
  77. package/skills/eddev/docs/serverless.mdx +202 -0
  78. package/skills/eddev/docs/snippets/automated-block-layouts.mdx +97 -0
  79. package/skills/eddev/docs/snippets/custom-routes-and-urls.mdx +91 -0
  80. package/skills/eddev/docs/snippets/multiple-editable-zones.mdx +87 -0
  81. package/skills/eddev/docs/snippets/querying-specific-blocks.mdx +164 -0
  82. package/skills/eddev/docs/snippets/submitting-forms-to-rpc.mdx +91 -0
  83. package/skills/eddev/docs/snippets/svgs.mdx +38 -0
  84. package/skills/eddev/docs/snippets/type-safe-acf-dropdowns.mdx +72 -0
  85. package/skills/eddev/docs/snippets.mdx +19 -0
  86. package/skills/eddev/docs/software.mdx +19 -0
  87. package/skills/eddev/docs/stack/how-it-works.mdx +50 -0
  88. package/skills/eddev/docs/stack/overview.mdx +56 -0
  89. package/skills/eddev/docs/stack/spa-vs-ssr.mdx +52 -0
  90. package/skills/eddev/docs/views/app-view.mdx +97 -0
  91. package/skills/eddev/docs/views/page-templates.mdx +82 -0
  92. package/skills/eddev/docs/views/queries.mdx +116 -0
  93. package/skills/eddev/docs/views.mdx +63 -0
  94. package/skills/eddev/index.mdx +79 -0
  95. package/tsconfig.app.json +2 -2
@@ -0,0 +1,731 @@
1
+ # Frontend Routing APIs (/docs/routing/api)
2
+
3
+ **Use eddev routing APIs for links, preloading, and client navigation.**
4
+
5
+ ## `<Link />` [#link-]
6
+
7
+ The `<Link />` component is a drop-in replacement for `<a />` tags, and is used to enable runtime page transitions via our routing system.
8
+
9
+ ```tsx
10
+ <Link href="/about">About</Link>
11
+ ```
12
+
13
+ The Link component is only a light wrapper around the `a` tag, and provides the following:
14
+
15
+ * Preloading of page data & components when hovering, via `router.preload(props.href)`
16
+ * When a user clicks, it calls `router.handleClickEvent(e, props.href)` — which kicks off navigation, as long as the user isn't holding Command/Ctrl, ensuring that 'Open in new tab' shortcuts work as expected.
17
+ * Status attributes, `data-active`, `data-child-active` and `data-pending` via `useLinkState(props.href)`, allowing developers to style links based on their active or loading state.
18
+ * In Gutenberg, link clicks are ignored — ensuring that authors don't accidentally navigate to another page while selecting text.
19
+
20
+ You can prevent navigation by calling `e.preventDefault()` in the `onClick` prop.
21
+
22
+ It accepts the following props:
23
+
24
+ ```typescript
25
+ type LinkProps<T extends ElementType = "a"> = ComponentPropsWithRef<T> & {
26
+ /** Override the component/tag that gets rendered @default 'a' */
27
+ as?: T
28
+ /** The target URL */
29
+ href?: string | null
30
+ /** Target attribute */
31
+ target?: string | null
32
+ /** When set to 'true', the link will prefer to navigate back in history if possible — restoring scroll position */
33
+ preferBack?: boolean | "exact"
34
+ /** Additional data to attach to the route, which can be read by other components via `useRoute().linkData`, or in router events. */
35
+ linkData?: Record<string, any>
36
+ }
37
+ ```
38
+
39
+ ## `<ScrollRestoration />` [#scrollrestoration-]
40
+
41
+ Since all navigation is handled by the stack, we need to take care of scrolling to the top of the page when a user clicks a link, and restoring the previous scroll position if the user clicks the browser back button.
42
+
43
+ The started theme includes `<ScrollRestoration />` in `_app.tsx` with no props, however for sites with more complex animation/rendering/navigation, you can either remove this component entirely, or override the restoration function.
44
+
45
+ The default restore function looks something like this:
46
+
47
+ ```typescript filename="views/_app.tsx"
48
+ <ScrollRestoration
49
+ restore={(scroll) => {
50
+ // The `scroll` object has `left` and `top` values, and `direction` which is either `back` or `forward`.
51
+ document.scrollingElement?.scrollTo({
52
+ behavior: "instant",
53
+ left: scroll.left,
54
+ top: scroll.top,
55
+ })
56
+ }}
57
+ />
58
+ ```
59
+
60
+ <Callout type="info">
61
+ For more complex cases, you can build your own using [`useRestorableState()`](#userestorablestate), or by hooking into the `navigate:changed` and `capture-restorable-state` [route events](/routing/events).
62
+ </Callout>
63
+
64
+ ## `<BackButton />` [#backbutton-]
65
+
66
+ Display a back button that will navigate to the previous page in the router's history.
67
+
68
+ This will allow you to render a 'back button' on the condition that the back button will not send the user to a different website. You can also specify an optional fallback URL, which will be used when the page is opened as a direct link — again, keeping the user on the site.
69
+
70
+ When a valid history route is available, the back button will function like `history.go(-1)`. Otherwise, if a `fallbackHref` has been providing, clicking it will send the user to that page.
71
+
72
+ ```tsx
73
+ /** Always displays a back button, but will navigate to /work if the user hasn't navigated here from another page */
74
+ <BackButton
75
+ fallbackHref="/work"
76
+ render={(args) => {
77
+ return <button onClick={args.onClick}>Back</button>
78
+ }}
79
+ />
80
+ ```
81
+
82
+ The `BackButton` component has the following props:
83
+
84
+ * `filter?: (route: RouteState) => boolean`
85
+
86
+ ```tsx
87
+ type BackButtonProps = /**
88
+ * An optional matching function, which will stop the render button from appearing unless the function returns true.
89
+ *
90
+ * The below example will only render a back button if the last route was a project archive.
91
+ *
92
+ * eg. `filter={(route) => route.view === 'archive-projects'}`
93
+ *
94
+ * @param route The last route in the history stack.
95
+ */
96
+ filter?: (route: RouteState) => boolean
97
+ /**
98
+ * Whether to use the top-most "global" route, or the "context" route.
99
+ *
100
+ * The "global" route is the top-most route, reflected in the URL bar.
101
+ *
102
+ * The "context" route will be the route that is currently being rendered, which may be different to the global route when using modals/overlays/route stacks/transitions.
103
+ *
104
+ * @default "context"
105
+ */
106
+ mode?: "global" | "context"
107
+ /**
108
+ * An optional href, which will be used if no back route is found.
109
+ *
110
+ * When used, the back button will always render.
111
+ */
112
+ fallbackHref?: string
113
+ /**
114
+ * A function to render the back button, if one should be rendered.
115
+ *
116
+ * Receives a `route` prop to further inspect the route, and an `onClick` function to navigate back.
117
+ *
118
+ * @param props
119
+ * @returns
120
+ */
121
+ render: (props: { route: RouteState; onClick: (e?: any) => void }) => ReactNode
122
+ ```
123
+
124
+ ## `useRoute()` [#useroute]
125
+
126
+ This hook provides access to information about the current route. It returns an object with the following properties:
127
+
128
+ ```typescript
129
+ export type RouteState = {
130
+ /** A unique ID, representing this history item. */
131
+ id: string
132
+ /** A unique hash for this URI — useful for page transitions. */
133
+ key: string
134
+ /** The current path of the route eg. `"/about"` */
135
+ pathname: string
136
+ /** The un-parsed querystring, eg. `"?foo=bar"` */
137
+ search: string
138
+ /** The parsed querystring, parsed using the `qs` library, which supports arrays. */
139
+ query: Record<string, string | string[]>
140
+ /** The hash of the route, eg. `"#section"` */
141
+ hash: string
142
+ /** The normalised URI, composed from pathname, query and hash */
143
+ uri: string
144
+ /* An object that can hold transient state, like scroll position.
145
+ * When a user navigates back to a page, this state is included along with router events.
146
+ * By default, contains `scrollLeft` and `scrollTop`
147
+ * See `useRestorableState` for more info. */
148
+ returnState?: RouteReturnState
149
+ /** The name of the template, like `front-page` or `single-event` */
150
+ view: string
151
+ /** The view component for the current route. */
152
+ component: FunctionComponent
153
+ /** The props for this route, to be passed to the component. */
154
+ props: Props
155
+ /** Loaded metadata for this route */
156
+ meta: RouteMeta
157
+ /** Link data, provided using `<Link linkData={...} />`. Will be `undefined` if none was provided. */
158
+ linkData?: Record<string, any>
159
+ }
160
+
161
+ /** Data used for generation of <head /> tags */
162
+ type RouteMeta = {
163
+ title?: string
164
+ tags?: {
165
+ tagName: string,
166
+ attributes: Record<string, string>
167
+ inner?: string
168
+ }[]
169
+ }
170
+
171
+ ```
172
+
173
+ Note that the `view` and `props` properties are fully typed, so you can extract props directly from a route by first checking it's view property.
174
+
175
+ ```tsx
176
+ const route = useRoute()
177
+ let theme = 'default'
178
+ if (route.view === "single-project") {
179
+ theme = route.props.project.theme
180
+ }
181
+ ```
182
+
183
+ ### `useViewRoute(name)` [#useviewroutename]
184
+
185
+ Similar to `useRoute()`, there's also `useViewRoute(viewName)`. It returns the same `RouteState` object as `useRoute()`, but it'll only return the route data if the view matches. The return value will be fully typed, so you can access it's props just like you would in the template.
186
+
187
+ ```tsx
188
+ const projectRoute = useViewRoute('single-project')
189
+ if (projectRoute) {
190
+ return <div>You're viewing the {project.props.project.title} project</div>
191
+ } else {
192
+ return <div>You're not viewing a project.</div>
193
+ }
194
+ ```
195
+
196
+ ## `useRouter()` [#userouter]
197
+
198
+ This hook provides access to the routing API, so that you can directly control navigation.
199
+
200
+ ```typescript
201
+ type RouterAPI = {
202
+ /** Preload a page and it's components */
203
+ preload: (url: string) => Promise<void>
204
+
205
+ /** Prefetch the data for a page, but not it's components. */
206
+ prefetch: (url: string) => Promise<void>
207
+
208
+ /** Begin transitioning to a new page */
209
+ navigate: (url: string, args?: { linkData?: Record<string, any> }) => Promise<void>
210
+
211
+ /** Replace the browser search params value with the given data, without transitioning the route */
212
+ replaceQuery: (search: Record<string, string | string[]>) => void
213
+
214
+ /** Replace the hash */
215
+ replaceHash: (hash: string) => void
216
+
217
+ /**
218
+ * Handle a click event, potentially triggering a route change.
219
+ *
220
+ * @param e A pointer or mouse event.
221
+ * @param href An optional URL. This must be provided if no `href` property exists on the clicked element
222
+ * @param preferBack If set, clicking this link will send the user 'back', when clicking a link to the previous history item.
223
+ */
224
+ handleClickEvent(
225
+ e: PointerOrMouseEvent,
226
+ href?: string,
227
+ preferBack?: boolean | "exact",
228
+ element?: HTMLElement,
229
+ linkData?: Record<string, any>,
230
+ ): void
231
+
232
+ /** A reference to the route loader (mostly for internal use) */
233
+ loader: RouteLoader
234
+
235
+ /** Subscribe to router events */
236
+ subscribe: (subscribe: RouterSubscriber) => () => void
237
+
238
+ /** Returns the current RouterAPIState, which contains the active route, history items, and pending navigation */
239
+ getState: () => RouterAPIState | null
240
+
241
+ /** Go back to a previous route in the history stack, or push it onto the stack if it isn't currently on the stack. */
242
+ restoreRoute: (route: RouteState) => void
243
+ }
244
+ ```
245
+
246
+ So, to manually trigger a route change, you can use something like:
247
+
248
+ ```tsx
249
+ const router = useRouter()
250
+ <button onClick={() => router.navigate("/")}>Go Home</button>
251
+ ```
252
+
253
+ ## `useHydrating()` [#usehydrating]
254
+
255
+ *Formally called `useIsSSR()`*
256
+
257
+ Use this function to help with SSR edge-cases, especially when relying on querystrings, hash fragments, user preferences, screen-size, random values or the current date/time — values which either aren't available on the server, or will differ on the server.
258
+
259
+ On the server, this hook will always return `true`.
260
+
261
+ In the browser, this hook will return `true` for the first render while the page is hydrating, and then switch to `false`.
262
+
263
+ ```tsx
264
+ const isHydrating = useIsHydrating()
265
+ const hash = useRoute().hash
266
+
267
+ if (isHydrating) {
268
+ return <Spinner />
269
+ } else {
270
+ return <p>Hash is {hash}, and the time is {new Date().toString()}.</p>
271
+ }
272
+ ```
273
+
274
+ In the example above, the spinner will be rendered on the server, and for the first frame on the client, until the hash is available.
275
+
276
+ ## `useRestorableState()` [#userestorablestate]
277
+
278
+ This hook works kinda like `useState`, and is used to store transient state that should be restored when a user navigates back to a page.
279
+
280
+ This is great if you have tabs or accordions which affect the scroll height of the page, which can be jarring when going forward and then back again.
281
+
282
+ The routing system uses this system internally for scroll restoration.
283
+
284
+ You'll need to pass in an `id`, which acts as a key for the state, as well as a default value. The `id` should be unique for the current page, but can be reused across different pages.
285
+
286
+ ```tsx
287
+ /**
288
+ * The value will default to 0, but if the user changes the slider value,
289
+ * navigates away, and then comes back, the slider will be at the same value.
290
+ *
291
+ * If the user refreshes the page, the slider will reset back to 0.
292
+ */
293
+ const [sliderValue, setSliderValue] = useRestorableState('sliderValue', 0)
294
+
295
+ return <div>
296
+ <input type="range" value={sliderValue} onChange={e => setSliderValue(e.valueAsNumber)} />
297
+ <Link href="/another-page">Another page</Link>
298
+ </div>
299
+ ```
300
+
301
+ ## `useSearchParams()` [#usesearchparams]
302
+
303
+ This hook provides a nicer API for working with querystring parameters, and should be used when building search and filter interfaces.
304
+
305
+ ```tsx
306
+ const [params, api] = useSearchParams({
307
+ tags: [] as string[],
308
+ search: "",
309
+ })
310
+ ```
311
+
312
+ When calling the hook, you pass in an object of default values — which helps provide TypeScript hints, and ensures that required parameters are never `null` or `undefined`.
313
+
314
+ Simple arrays are supported, where `api.set('tags', ['a', 'b']):{js}` will result in a querystring of `?tags[]=a&tags[]=b`.
315
+
316
+ Default values are managed individually — if a parameter is not set in the querystring, then it'll be replaced with the default value.
317
+
318
+ Also, any parameters which are set back to their default value will be removed from the URL, to keep things tidy.
319
+
320
+ The hook returns a tuple of two objects:
321
+
322
+ 1. The returned `params` object contains the querystring parameters (arrays and strings)
323
+ 2. The returned `api` object provides functions for mutating the query state.
324
+
325
+ The `api` object has the following methods:
326
+
327
+ ````typescript
328
+ export type SearchParamAPI<T> = {
329
+ /**
330
+ * Set an individual parameter value by key.
331
+ * If the value is `null` or `undefined`, the parameter will be
332
+ * removed/set back to the default.
333
+ */
334
+ set<K extends keyof T>(key: K, value: T[K]): void
335
+ /**
336
+ * Set all parameters at once, replacing the entire query string.
337
+ * Any parameters not present will be replaced with their default value.
338
+ * Passing an empty object is equivalent to calling `reset()`.
339
+ */
340
+ setAll: (value: Partial<T>) => void
341
+ /**
342
+ * Returns a memoized setter function for a specific key.
343
+ * This is useful when you want to pass a setter function to a
344
+ * child component, without needing to pass the entire API object.
345
+ *
346
+ * For example:
347
+ * ```tsx
348
+ * <SearchInput value={params.q} onSearch={api.setter("q")} />
349
+ * ```
350
+ *
351
+ * @param key The search parameter name
352
+ * @returns (value: T) => void
353
+ */
354
+ setter<K extends keyof T>(key: K): (value: T[K]) => void
355
+ /**
356
+ * Reset all parameters to their default values.
357
+ */
358
+ reset(): void
359
+ /**
360
+ * Toggles an array item on or off.
361
+ *
362
+ * For example, `api.toggle("tags", "news")` will add "news" to the "tags" array if it isn't already present, or remove it if it is.
363
+ *
364
+ * Works well with `.has(key, item)`.
365
+ */
366
+ toggle<K extends keyof Extract<T, Array<any>>>(
367
+ key: K,
368
+ item: Extract<T, Array<any>>[K] extends Array<infer U> ? U : never,
369
+ state?: boolean,
370
+ ): void
371
+ /**
372
+ * Returns true/false depending on whether the item is present in the array parameter, or the value is equal.
373
+ *
374
+ * For example, `api.has("tags", "news")` will return true if "news" is present in the "tags" array.
375
+ *
376
+ * It can also be used as shorthand for api.get(key) === item, for non-array values.
377
+ *
378
+ * Works well with `.toggle(key, item)`.
379
+ */
380
+ has<K extends keyof T>(key: K, item: Extract<T, Array<any>>[K] extends Array<infer U> ? U : T[K]): boolean
381
+ /**
382
+ * Gets the current value of a parameter.
383
+ * In most cases, you should use the `params` object returned from `useSearchParams` instead of this method.
384
+ */
385
+ get<K extends keyof T>(key: K): T[K]
386
+ }
387
+ ````
388
+
389
+ Below is a more complete example, showing arrays and strings
390
+
391
+ ```tsx
392
+ const [params, api] = useSearchParams({
393
+ type: "all" as "all" | "news" | "events",
394
+ tags: [] as string[],
395
+ search: "",
396
+ })
397
+
398
+ return (
399
+ <>
400
+ {/* Search */}
401
+ <input type="search" defaultValue={params.search} onChange={(e) => api.set("search", e.currentTarget.value)} />
402
+ {/* Tags, using `has` and `toggle` */}
403
+ <ul>
404
+ {allTags.map((tag) => (
405
+ <label key={tag.value}>
406
+ <input
407
+ type="checkbox"
408
+ checked={api.has("tags", tag.value)}
409
+ onChange={(e) => api.toggle("tags", tag.value)}
410
+ />
411
+ <span>{tag.label}</span>
412
+ </label>
413
+ ))}
414
+ </ul>
415
+ {/* Type */}
416
+ <select value={params.type} onChange={(e) => api.set("type", e.currentTarget.value)}>
417
+ <option value="all">All</option>
418
+ <option value="news">News</option>
419
+ <option value="events">Events</option>
420
+ </select>
421
+ {/* Reset */}
422
+ <button onClick={() => api.reset()}>Reset</button>
423
+ </>
424
+ )
425
+ ```
426
+
427
+ <Callout>
428
+ Want to do something more advanced? Internally, this hook uses `route.query` and `router.replaceQuery()`
429
+ </Callout>
430
+
431
+ ## `useLinkState()` [#uselinkstate]
432
+
433
+ This hooks provides insights on whether a link is currently loading, active, or if it's children are active. It takes hashes and querystrings into account too.
434
+
435
+ It returns an object with the following structure:
436
+
437
+ ```typescript
438
+ type LinkState = {
439
+ active: 'path' | 'query' | 'exact' | undefined
440
+ pending: 'path' | 'query' | 'exact' | undefined
441
+ childActive: boolean
442
+ }
443
+ ```
444
+
445
+ This object is used by the `Link` component, to provide status attributes as `data-active`, `data-child-active` and `data-pending`.
446
+
447
+ Truthy values mean that at *least* the path is a match, but more specific values can help you figure out if the querystring and hash are also a match.
448
+
449
+ * `"path"` means that the path is a match, but not the query/hash
450
+ * `"query"` means the path and query matches, but not the hash
451
+ * `"exact"` means the path, query and hash all match exactly
452
+
453
+ ## Advanced [#advanced]
454
+
455
+ ### `useLinkProps()` [#uselinkprops]
456
+
457
+ This is used internally by the `Link` component, and accepts all the same props. It then returns the React props that you can add onto an `a` element — notably onMouseEnter/onPointerDown for preloading, and onClick for triggering the page navigation.
458
+
459
+ ```tsx
460
+ function FunnyLink(props: { url: string }) {
461
+ const linkProps = useLinkProps({ href: props.url })
462
+
463
+ return <a {...linkProps}>Click here</a>
464
+ }
465
+ ```
466
+
467
+ <Callout>
468
+ There's almost no reason to use this, since all functionality is exposed via `useRouter()`. The `router.handleClickEvent()` function provides additional checks such link targets, file extensions and keyboard keys being held down.
469
+ </Callout>
470
+
471
+ ### `useRouterState()` [#userouterstate]
472
+
473
+ While the `useRouter()` hook provides access to the core navigational functionality of the router, and the `useRoute()` hook provides access to information about the *current route*, the `useRouterState()` hook provides access to the state of the router.
474
+
475
+ The most common use-case for this hook is to access the 'history stack' — that is, the list of routes that can be accessed via the browsers back button. This can be used alongside the `<RouteRenderer />` component to keep past routes mounted, even while inactive.
476
+
477
+ You can also access the `activeRoute` to access the currently active route, normally accessed via `useRoute()` — however the `activeRoute` can be used to access the top-most active route, if multiple routes are being rendered.
478
+
479
+ The Router State object looks like this:
480
+
481
+ ```typescript
482
+ export type RouterAPIState = {
483
+ /** Router chain */
484
+ history: RouteState[]
485
+
486
+ /** The top-most, active route */
487
+ activeRoute: RouteState
488
+
489
+ /** The pending route */
490
+ pendingRoute?: RouteState
491
+ pendingRouteReady?: boolean
492
+
493
+ /** Transition blockers */
494
+ blockers: Promise<void>[]
495
+ }
496
+ ```
497
+
498
+ ### `<NativeLinkHandler />` [#nativelinkhandler-]
499
+
500
+ Renders a wrapper element, which handles link clicks from HTML `<a />` inside it. This can be used when rendering HTML from somewhere else, where it's expected that it may cotnain internal links.
501
+
502
+ ```tsx
503
+ <NativeLinkHandler as="div">
504
+ <div
505
+ dangerouslySetInnerHTML={{
506
+ // When the link is clicked, it'll work as though it was a <Link> element.
507
+ __html: `<a href="/about">About</a>`
508
+ }}>
509
+ </NativeLinkHandler>
510
+ ```
511
+
512
+ ### `<RouteDisplay />` [#routedisplay-]
513
+
514
+ The `RouteDisplay` component renders a route object. This is used internally by the router by default, with the result passed to your `_app.tsx` view via `props.children` — however it's exposed for additional use-cases, like rendering historic routes in the background with the *real* route overlaid on top.
515
+
516
+ The following example shows how you could use this to make a simple modal system, by updating your `_app.tsx` to render routes manually. In the example, any link which opens a team profile, or has `linkData={{openAsModal: true}}` will open a modal overlay, but will still render them as normal pages when accessed via a direct link, or upon refresh.
517
+
518
+ ```tsx
519
+ function isModalRoute(route: RouteState) {
520
+ return route.linkData.openAsModal || route.view === "single-team"
521
+ }
522
+
523
+ export default defineView('_app', () => {
524
+ const pageRoutes = useRouterState((r) => {
525
+ return r.history.length === 1 ? r.history : r.history.filter((route) => !isModalRoute(route))
526
+ })
527
+
528
+ const modalRoute = useRouterState((r) => {
529
+ if (r.history.length <= 1) return null
530
+ const top = r.history[r.history.length - 1]
531
+ return isModalRoute(top) ? top : null
532
+ })
533
+
534
+ return <>
535
+ <div style={{ pointerEvents: modalRoute ? 'none' : 'auto' }}>
536
+ <header>
537
+ <Link href="/about">About</Link>
538
+ <Link href="/contact" linkData={{openAsModal: true}}>Contact Modal</Link>
539
+ <Link href="/team/daniel">Dan's Profile</Link>
540
+ </header>
541
+ <RouteDisplay route={pageRoutes[pageRoutes.length - 1]} />
542
+ <Footer />
543
+ </div>
544
+ {modalRoute && <div className="fixed inset-0 overflow-auto">
545
+ <BackButton render={({onClick}) => <button onClick={onClick}>Close</button>} />
546
+ <RouteDisplay route={modalRoute} />
547
+ </div>}
548
+ </>
549
+ })
550
+ ```
551
+
552
+ ### `useRouterEvents()` [#userouterevents]
553
+
554
+ The `useRouterEvents()` hook allows your components to listen to a large number of events, which are emitted by the router as the user interacts with a site.
555
+
556
+ You can use this to observe clicks, loading, navigation progress, and navigation errors. Some events can also be cancelled, allowing you to stop navigation when needed.
557
+
558
+ To use it, just pass an event handler:
559
+
560
+ ```typescript
561
+ useRouterEvents((e) => {
562
+ if (e.type === 'navigate:start') {
563
+ console.log('10% loaded...')
564
+ } else if (e.type === 'navigate:data-ready') {
565
+ console.log('60% loaded...')
566
+ } else if (e.type === 'navigate:will-change') {
567
+ console.log('80% loaded...')
568
+ } else if (e.type === 'navigate:changed') {
569
+ console.log('100% loaded!')
570
+ }
571
+ })
572
+ ```
573
+
574
+ ## Events [#events]
575
+
576
+ Below are all events emitted by the router, which can be monitored using [`useRouterEvents()`](#userouterevents).
577
+
578
+ ### `"clicked"` [#clicked]
579
+
580
+ Triggered when a `Link` is clicked. It is NOT called when calling `router.navigate()`, but is called when calling `router.handleClickEvent()`.
581
+
582
+ ```typescript
583
+ {
584
+ type: "clicked"
585
+ link: RouteLink
586
+ linkData: Record<string, any> | undefined
587
+ currentRoute: RouteState
588
+ // The clicked element
589
+ target?: HTMLElement
590
+ // ignore: The event was already cancelled, navigate: Navigation will occur, native: Likely a file download or a modifier key was held
591
+ mode: "ignore" | "navigate" | "native"
592
+ href: string | undefined
593
+ isSameUrl: boolean
594
+ cancel: () => void
595
+ }
596
+ ```
597
+
598
+ ### `"capture-restorable-state"` [#capture-restorable-state]
599
+
600
+ Triggered when leaving a page. The `state` value can be mutated directly to 'shelve' data until an outgoing route, or update the `scrollLeft`/`scrollTop` properties which are automatically captured. It's used by [`useRestorableState()`](#userestorablestate).
601
+
602
+ ```typescript
603
+ {
604
+ type: "capture-restorable-state"
605
+ state: RouteReturnState<Record<string, any>>
606
+ }
607
+ ```
608
+
609
+ ### `"navigate:start"` [#navigatestart]
610
+
611
+ Triggered when the navigation process is starting — just before the page starts to load. You can call `.cancel()` to prevent the transition.
612
+
613
+ ```typescript
614
+ {
615
+ type: "navigate:start"
616
+ link: RouteLink
617
+ linkData: LinkClickData | undefined
618
+ currentRoute: RouteState
619
+ hasPreloadedData: boolean
620
+ goingBack?: boolean
621
+ cancel: () => void
622
+ }
623
+ ```
624
+
625
+ ### `"navigate:data-ready"` [#navigatedata-ready]
626
+
627
+ Triggered once the page data has been loaded, and the route object is ready. At this point, the actual React components needed by the page may not have loaded yet.
628
+
629
+ ```typescript
630
+ {
631
+ type: "navigate:data-ready"
632
+ link: RouteLink
633
+ linkData: LinkClickData | undefined
634
+ currentRoute: RouteState
635
+ loadedRoute: RouteState
636
+ }
637
+ ```
638
+
639
+ ### `"navigate:will-change"` [#navigatewill-change]
640
+
641
+ Triggered immediately before swapping to the new route. The React components still may not have loaded, but the Suspense transition is about to start.
642
+
643
+ ```typescript
644
+ {
645
+ type: "navigate:will-change"
646
+ link: RouteLink
647
+ linkData: LinkClickData | undefined
648
+ currentRoute: RouteState
649
+ loadedRoute: RouteState
650
+ }
651
+ ```
652
+
653
+ ### `"navigate:changed"` [#navigatechanged]
654
+
655
+ Triggered once the new route has been fully loaded and rendered, and the transition is complete.
656
+
657
+ ```typescript
658
+ {
659
+ type: "navigate:changed"
660
+ link: RouteLink
661
+ linkData: LinkClickData | undefined
662
+ lastRoute: RouteState
663
+ currentRoute: RouteState
664
+ }
665
+ ```
666
+
667
+ ### `"preload:start"` [#preloadstart]
668
+
669
+ Triggered when preloading a page (eg. on hover)
670
+
671
+ ```typescript
672
+ {
673
+ type: "preload:start"
674
+ link: RouteLink
675
+ currentRoute: RouteState
676
+ }
677
+ ```
678
+
679
+ ### `"preload:data-ready"` [#preloaddata-ready]
680
+
681
+ Triggered when page data has finished preloading (eg. on hover)
682
+
683
+ ```typescript
684
+ {
685
+ type: "preload:data-ready"
686
+ link: RouteLink
687
+ currentRoute: RouteState
688
+ data: RouteData
689
+ }
690
+ ```
691
+
692
+ ### `"preload:finished"` [#preloadfinished]
693
+
694
+ Triggered when page data + the required React page template have finished preloading. Block components will not be loaded until this page is actually displayed.
695
+
696
+ ```typescript
697
+ {
698
+ type: "preload:finished"
699
+ link: RouteLink
700
+ currentRoute: RouteState
701
+ data: RouteData
702
+ }
703
+ ```
704
+
705
+ ### `"error"` [#error]
706
+
707
+ Called when an error occurs during either navigation or preloading.
708
+
709
+ If `critical` is `true, then the JSON data of the page couldn't be loaded, and the `\_error\` view is about to be rendered.
710
+
711
+ ```typescript
712
+ {
713
+ type: "error"
714
+ critical: boolean
715
+ linkData: LinkClickData | undefined
716
+ url?: string
717
+ shouldReload?: boolean
718
+ error: Error
719
+ }
720
+ ```
721
+
722
+ ### `"error:component-load-failed"` [#errorcomponent-load-failed]
723
+
724
+ Triggered when the view component couldn't be loaded, which usually indicates a caching or network issue.
725
+
726
+ ```typescript
727
+ {
728
+ type: "error:component-load-failed"
729
+ error: Error
730
+ }
731
+ ```