@llui/vike 0.0.14 → 0.0.16
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 +203 -6
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/on-render-client.d.ts +145 -10
- package/dist/on-render-client.d.ts.map +1 -1
- package/dist/on-render-client.js +270 -23
- package/dist/on-render-client.js.map +1 -1
- package/dist/on-render-html.d.ts +45 -15
- package/dist/on-render-html.d.ts.map +1 -1
- package/dist/on-render-html.js +119 -20
- package/dist/on-render-html.js.map +1 -1
- package/dist/page-slot.d.ts +63 -0
- package/dist/page-slot.d.ts.map +1 -0
- package/dist/page-slot.js +77 -0
- package/dist/page-slot.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -57,6 +57,201 @@ export const onRenderClient = createOnRenderClient({
|
|
|
57
57
|
})
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
### Persistent Layouts
|
|
61
|
+
|
|
62
|
+
Declare app chrome (header, sidebar, dialogs, session state) as a `Layout` component that stays mounted across client navigation. The route-scoped `Page` swaps in and out at the layout's `pageSlot()` position while the surrounding layout subtree — and every DOM node, focus trap, portal, and effect subscription inside it — is untouched.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// pages/+Layout.ts
|
|
66
|
+
import { component, div, header, main } from '@llui/dom'
|
|
67
|
+
import { pageSlot } from '@llui/vike/client'
|
|
68
|
+
|
|
69
|
+
export const AppLayout = component<LayoutState, LayoutMsg>({
|
|
70
|
+
name: 'AppLayout',
|
|
71
|
+
init: () => [{ session: null }, []],
|
|
72
|
+
update: layoutUpdate,
|
|
73
|
+
view: ({ send }) => [
|
|
74
|
+
div({ class: 'app-shell' }, [
|
|
75
|
+
header([
|
|
76
|
+
/* persistent chrome */
|
|
77
|
+
]),
|
|
78
|
+
main([pageSlot()]), // ← where the route's Page renders
|
|
79
|
+
]),
|
|
80
|
+
],
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// pages/+onRenderClient.ts
|
|
86
|
+
import { createOnRenderClient } from '@llui/vike/client'
|
|
87
|
+
import { AppLayout } from './+Layout'
|
|
88
|
+
|
|
89
|
+
export const onRenderClient = createOnRenderClient({
|
|
90
|
+
Layout: AppLayout,
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// pages/+onRenderHtml.ts — server renders layout + page as one tree
|
|
96
|
+
import { createOnRenderHtml } from '@llui/vike/server'
|
|
97
|
+
import { AppLayout } from './+Layout'
|
|
98
|
+
|
|
99
|
+
export const onRenderHtml = createOnRenderHtml({
|
|
100
|
+
Layout: AppLayout,
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Call `pageSlot()` exactly once in each layout's view, at the position where nested content should render. It's an ordinary structural primitive — composes naturally inside `show()`, `branch()`, `provide()`, and any other view tree.
|
|
105
|
+
|
|
106
|
+
#### Nested layouts
|
|
107
|
+
|
|
108
|
+
Pass an array to stack layouts outer-to-inner. Each layout except the innermost calls its own `pageSlot()`. The innermost layer is always the route's `Page`.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
createOnRenderClient({
|
|
112
|
+
Layout: [AppLayout, DashboardLayout],
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
For per-route chains — e.g. `/dashboard/*` routes use `[AppLayout, DashboardLayout]` while `/settings` uses `[AppLayout]` — pass a resolver function instead:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
createOnRenderClient({
|
|
120
|
+
Layout: (pageContext) =>
|
|
121
|
+
pageContext.urlPathname.startsWith('/dashboard') ? [AppLayout, DashboardLayout] : [AppLayout],
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The chain diff on each nav walks old and new chains in parallel and finds the first mismatch. Every layer before that mismatch stays mounted; every layer at or after it is torn down innermost-first and re-mounted outermost-first. Navigating from `/dashboard/reports` to `/dashboard/overview` only disposes the `Page` — `AppLayout` and `DashboardLayout` stay alive. Navigating to `/settings` disposes `DashboardLayout` and the `Page`, keeping only `AppLayout`.
|
|
126
|
+
|
|
127
|
+
#### Layout ↔ Page communication
|
|
128
|
+
|
|
129
|
+
Layouts and pages are independent component instances with their own state, update, and `send`. They share state and expose cross-cutting operations via **context**, not via direct messaging.
|
|
130
|
+
|
|
131
|
+
The scope-tree integration makes this natural: `pageSlot()` creates its slot as a child of the layout's render scope, and the page's `rootScope` is parented inside that slot. `useContext` from within the page walks up through the slot and finds any providers the layout installed above it.
|
|
132
|
+
|
|
133
|
+
Common pattern — a layout-owned toast system:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
// pages/+Layout.ts
|
|
137
|
+
import { component, div, main, provide, createContext } from '@llui/dom'
|
|
138
|
+
import { pageSlot } from '@llui/vike/client'
|
|
139
|
+
|
|
140
|
+
interface ToastDispatchers {
|
|
141
|
+
show: (msg: string) => void
|
|
142
|
+
dismiss: (id: string) => void
|
|
143
|
+
}
|
|
144
|
+
export const ToastContext = createContext<ToastDispatchers>(undefined, 'Toast')
|
|
145
|
+
|
|
146
|
+
export const AppLayout = component<LayoutState, LayoutMsg>({
|
|
147
|
+
name: 'AppLayout',
|
|
148
|
+
init: () => [{ toasts: [] }, []],
|
|
149
|
+
update: layoutUpdate,
|
|
150
|
+
view: ({ send }) => [
|
|
151
|
+
div({ class: 'app-shell' }, [
|
|
152
|
+
ToastStack(), // reads from layout state
|
|
153
|
+
...provide(
|
|
154
|
+
ToastContext,
|
|
155
|
+
() => ({
|
|
156
|
+
show: (msg) => send({ type: 'toast/show', msg }),
|
|
157
|
+
dismiss: (id) => send({ type: 'toast/dismiss', id }),
|
|
158
|
+
}),
|
|
159
|
+
() => [main([pageSlot()])],
|
|
160
|
+
),
|
|
161
|
+
]),
|
|
162
|
+
],
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// Any page below the layout can now use the toast dispatcher.
|
|
168
|
+
// pages/studio/+Page.ts
|
|
169
|
+
import { component, button, text, useContext } from '@llui/dom'
|
|
170
|
+
import { ToastContext } from '../+Layout'
|
|
171
|
+
|
|
172
|
+
export const StudioPage = component<StudioState, StudioMsg>({
|
|
173
|
+
name: 'StudioPage',
|
|
174
|
+
init: () => [{ saved: false }, []],
|
|
175
|
+
update: (s, m) => {
|
|
176
|
+
if (m.type === 'saveSucceeded') {
|
|
177
|
+
// ...
|
|
178
|
+
}
|
|
179
|
+
return [s, []]
|
|
180
|
+
},
|
|
181
|
+
view: ({ send }) => {
|
|
182
|
+
const toast = useContext(ToastContext)
|
|
183
|
+
return [button({ onClick: () => toast({} as LayoutState).show('Saved') }, [text('Save')])]
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Toast state machines, global progress indicators, breadcrumb/title bars, modal-takeover chrome toggles, and session-expired banners all fall out of this pattern naturally — the layout owns the state, provides a dispatcher via context, and any page can trigger layout operations without touching the layout's internals.
|
|
189
|
+
|
|
190
|
+
For the rarer case where a layout needs to **probe a page** (e.g. "is your form dirty? can we navigate away?"), use **addressed effects** — the page registers an address on mount, the layout dispatches a targeted effect to it.
|
|
191
|
+
|
|
192
|
+
#### Layout data
|
|
193
|
+
|
|
194
|
+
Layouts can have their own server-fetched data alongside per-page `+data.ts` by populating `pageContext.lluiLayoutData` as an array matching the layout chain (outermost first). Each layout's `init(layoutData)` receives its slice.
|
|
195
|
+
|
|
196
|
+
Wire this from Vike's config mechanism however you like — the adapter just reads `pageContext.lluiLayoutData` when present.
|
|
197
|
+
|
|
198
|
+
#### Hydration envelope
|
|
199
|
+
|
|
200
|
+
With a `Layout` configured, `window.__LLUI_STATE__` is chain-aware:
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
window.__LLUI_STATE__ = {
|
|
204
|
+
layouts: [
|
|
205
|
+
{ name: 'AppLayout', state: { session: 'alice' } },
|
|
206
|
+
{ name: 'DashboardLayout', state: { active: 'reports' } },
|
|
207
|
+
],
|
|
208
|
+
page: { name: 'ReportsPage', state: { view: 'summary' } },
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The client matches each layer by component `name` when hydrating — server/client chain mismatches throw with a clear error instead of silently binding the wrong state to the wrong instance. Pages written against the pre-layout flat envelope shape continue to hydrate correctly when no `Layout` is configured.
|
|
213
|
+
|
|
214
|
+
### Page Transitions
|
|
215
|
+
|
|
216
|
+
`createOnRenderClient` accepts `onLeave` and `onEnter` hooks that fire around the dispose-and-remount cycle on client navigation. `onLeave` is awaited — return a promise to defer the swap until a leave animation finishes:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { createOnRenderClient, fromTransition } from '@llui/vike/client'
|
|
220
|
+
import { routeTransition } from '@llui/transitions'
|
|
221
|
+
|
|
222
|
+
export const onRenderClient = createOnRenderClient({
|
|
223
|
+
...fromTransition(routeTransition({ duration: 200 })),
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`fromTransition` adapts any `TransitionOptions` (the shape returned by `routeTransition`, `fade`, `slide`, etc.) into the hook pair. The transition operates on the container element — its opacity / transform fades out the outgoing page, then the new page fades in after mount.
|
|
228
|
+
|
|
229
|
+
For raw animations without `@llui/transitions`, write the hooks yourself:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
export const onRenderClient = createOnRenderClient({
|
|
233
|
+
onLeave: (el) => el.animate({ opacity: [1, 0] }, 200).finished,
|
|
234
|
+
onEnter: (el) => el.animate({ opacity: [0, 1] }, 200),
|
|
235
|
+
})
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Client Navigation Lifecycle
|
|
239
|
+
|
|
240
|
+
When Vike fires a client-side navigation, `@llui/vike` runs this sequence inside `onRenderClient`:
|
|
241
|
+
|
|
242
|
+
1. **`onLeave(el)`** — awaited. The outgoing page's DOM is still mounted; this is the only moment where a leave animation can read/write it.
|
|
243
|
+
2. **`currentHandle.dispose()`** — tears down the outgoing component's scope tree. All `onMount` cleanups run here, portals are removed from their targets, focus traps are popped, body scroll locks release, sibling `aria-hidden` is restored. The regression test in `@llui/components/test/components/dialog-dispose.test.ts` covers this path explicitly.
|
|
244
|
+
3. **`el.textContent = ''`** — the outgoing DOM is cleared from the container.
|
|
245
|
+
4. **`mountApp(el, Page, data)`** — the new page mounts.
|
|
246
|
+
5. **`onEnter(el)`** — synchronous; fire-and-forget. Promises are ignored here.
|
|
247
|
+
6. **`onMount()`** — legacy hook, fires last on every render (including the initial hydration).
|
|
248
|
+
|
|
249
|
+
On the initial hydration render, `onLeave` and `onEnter` are both skipped — there's no outgoing page to leave, and hydration doesn't insert new DOM that needs an enter animation.
|
|
250
|
+
|
|
251
|
+
**AbortSignal semantics for in-flight effects.** When a component is disposed, its `AbortController` fires and `inst.signal.aborted` becomes `true`. Effect handlers should guard their `send()` calls against `signal.aborted` — the base package already does this in `@llui/effects`. Network requests that have already been accepted by the server are NOT cancelled by navigation; cancellation only applies to future `send()` dispatches into the now-aborted instance. This is intentional: cancelling a successful signup POST just because the user clicked a nav link would lose data.
|
|
252
|
+
|
|
253
|
+
**Scroll position is the host's problem.** Vike controls scroll-to-top behavior via `scrollToTop` in `+config.ts`. `@llui/vike` doesn't touch scroll — if you need custom scroll handling, configure it on the Vike side.
|
|
254
|
+
|
|
60
255
|
## How It Works
|
|
61
256
|
|
|
62
257
|
### Server (`onRenderHtml`)
|
|
@@ -69,11 +264,13 @@ Hydrates the server-rendered HTML on the client. Attaches event listeners and re
|
|
|
69
264
|
|
|
70
265
|
## API
|
|
71
266
|
|
|
72
|
-
| Export | Sub-path | Description
|
|
73
|
-
| ---------------------- | ------------------- |
|
|
74
|
-
| `onRenderHtml` | `@llui/vike/server` | Default server hook — minimal HTML template
|
|
75
|
-
| `createOnRenderHtml` | `@llui/vike/server` | Factory for custom document templates
|
|
76
|
-
| `onRenderClient` | `@llui/vike/client` | Default client hook — hydrate or mount
|
|
77
|
-
| `createOnRenderClient` | `@llui/vike/client` | Factory for custom container
|
|
267
|
+
| Export | Sub-path | Description |
|
|
268
|
+
| ---------------------- | ------------------- | --------------------------------------------------------------- |
|
|
269
|
+
| `onRenderHtml` | `@llui/vike/server` | Default server hook — minimal HTML template |
|
|
270
|
+
| `createOnRenderHtml` | `@llui/vike/server` | Factory for custom document templates + persistent layouts |
|
|
271
|
+
| `onRenderClient` | `@llui/vike/client` | Default client hook — hydrate or mount |
|
|
272
|
+
| `createOnRenderClient` | `@llui/vike/client` | Factory for custom container + layouts + transition hooks |
|
|
273
|
+
| `pageSlot` | `@llui/vike/client` | Structural primitive — declares where a layout renders its page |
|
|
274
|
+
| `fromTransition` | `@llui/vike/client` | Adapter: `TransitionOptions` → `{ onLeave, onEnter }` hook pair |
|
|
78
275
|
|
|
79
276
|
The barrel export (`@llui/vike`) re-exports everything, but prefer sub-path imports to avoid bundling jsdom into the client.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { onRenderHtml, createOnRenderHtml } from './on-render-html.js';
|
|
2
|
-
export type { PageContext, DocumentContext, RenderHtmlResult } from './on-render-html.js';
|
|
3
|
-
export { onRenderClient, createOnRenderClient } from './on-render-client.js';
|
|
2
|
+
export type { PageContext, DocumentContext, RenderHtmlResult, RenderHtmlOptions, } from './on-render-html.js';
|
|
3
|
+
export { onRenderClient, createOnRenderClient, fromTransition } from './on-render-client.js';
|
|
4
4
|
export type { ClientPageContext, RenderClientOptions } from './on-render-client.js';
|
|
5
|
+
export { pageSlot } from './page-slot.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACtE,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACtE,YAAY,EACV,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC5F,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAEnF,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { onRenderHtml, createOnRenderHtml } from './on-render-html.js';
|
|
2
|
-
export { onRenderClient, createOnRenderClient } from './on-render-client.js';
|
|
2
|
+
export { onRenderClient, createOnRenderClient, fromTransition } from './on-render-client.js';
|
|
3
|
+
export { pageSlot } from './page-slot.js';
|
|
3
4
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAQtE,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAG5F,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA","sourcesContent":["export { onRenderHtml, createOnRenderHtml } from './on-render-html.js'\nexport type {\n PageContext,\n DocumentContext,\n RenderHtmlResult,\n RenderHtmlOptions,\n} from './on-render-html.js'\n\nexport { onRenderClient, createOnRenderClient, fromTransition } from './on-render-client.js'\nexport type { ClientPageContext, RenderClientOptions } from './on-render-client.js'\n\nexport { pageSlot } from './page-slot.js'\n"]}
|
|
@@ -1,35 +1,170 @@
|
|
|
1
|
-
import type { ComponentDef } from '@llui/dom';
|
|
1
|
+
import type { ComponentDef, TransitionOptions } from '@llui/dom';
|
|
2
|
+
export { pageSlot } from './page-slot.js';
|
|
2
3
|
declare global {
|
|
3
4
|
interface Window {
|
|
4
5
|
__LLUI_STATE__?: unknown;
|
|
5
6
|
}
|
|
6
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Page context shape as seen by `@llui/vike`'s client-side hooks. The
|
|
10
|
+
* `Page` and `data` fields come from whichever `+Page.ts` and `+data.ts`
|
|
11
|
+
* Vike resolved for the current route.
|
|
12
|
+
*
|
|
13
|
+
* `lluiLayoutData` is optional and carries per-layer data for the layout
|
|
14
|
+
* chain configured via `createOnRenderClient({ Layout })`. It's indexed
|
|
15
|
+
* outermost-to-innermost, one entry per layout layer. Absent entries
|
|
16
|
+
* mean the corresponding layout's `init()` receives `undefined`. Users
|
|
17
|
+
* wire this from their Vike `+data.ts` files by merging layout-owned
|
|
18
|
+
* data under the `lluiLayoutData` key.
|
|
19
|
+
*/
|
|
7
20
|
export interface ClientPageContext {
|
|
8
21
|
Page: ComponentDef<unknown, unknown, unknown, unknown>;
|
|
9
22
|
data?: unknown;
|
|
23
|
+
lluiLayoutData?: readonly unknown[];
|
|
10
24
|
isHydration?: boolean;
|
|
11
25
|
}
|
|
26
|
+
type AnyComponentDef = ComponentDef<unknown, unknown, unknown, unknown>;
|
|
27
|
+
type LayoutChain = ReadonlyArray<AnyComponentDef>;
|
|
28
|
+
/**
|
|
29
|
+
* Page-lifecycle hooks that fire around the dispose → mount cycle on
|
|
30
|
+
* client navigation. With persistent layouts in play the cycle only
|
|
31
|
+
* tears down the *divergent* suffix of the layout chain — any layers
|
|
32
|
+
* shared between the old and new routes stay mounted.
|
|
33
|
+
*
|
|
34
|
+
* Navigation sequence for an already-mounted app:
|
|
35
|
+
*
|
|
36
|
+
* ```
|
|
37
|
+
* client nav triggered
|
|
38
|
+
* │
|
|
39
|
+
* ▼
|
|
40
|
+
* compare old chain to new chain → find first mismatch index K
|
|
41
|
+
* │
|
|
42
|
+
* ▼
|
|
43
|
+
* onLeave(leaveTarget) ← awaited; leaveTarget is the slot element
|
|
44
|
+
* │ at depth K-1 (or the root container if K=0)
|
|
45
|
+
* │ whose contents are about to be replaced
|
|
46
|
+
* ▼
|
|
47
|
+
* dispose chainHandles[K..end] innermost first
|
|
48
|
+
* │
|
|
49
|
+
* ▼
|
|
50
|
+
* leaveTarget.textContent = ''
|
|
51
|
+
* │
|
|
52
|
+
* ▼
|
|
53
|
+
* mount newChain[K..end] into leaveTarget, outermost first
|
|
54
|
+
* │
|
|
55
|
+
* ▼
|
|
56
|
+
* onEnter(leaveTarget) ← fire-and-forget; fresh DOM in place
|
|
57
|
+
* │
|
|
58
|
+
* ▼
|
|
59
|
+
* onMount()
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* On the initial hydration render, `onLeave` and `onEnter` are NOT
|
|
63
|
+
* called — there's no outgoing page to leave and no animation to enter.
|
|
64
|
+
* Use `onMount` for code that should run on every render including the
|
|
65
|
+
* initial one.
|
|
66
|
+
*/
|
|
12
67
|
export interface RenderClientOptions {
|
|
13
|
-
/** CSS selector for the mount container. Default: '#app' */
|
|
68
|
+
/** CSS selector for the mount container. Default: `'#app'`. */
|
|
14
69
|
container?: string;
|
|
15
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Persistent layout chain. One of:
|
|
72
|
+
*
|
|
73
|
+
* - A single `ComponentDef` — becomes a one-layout chain.
|
|
74
|
+
* - An array of `ComponentDef`s — outermost layout first, innermost
|
|
75
|
+
* layout last. Every layer except the innermost must call
|
|
76
|
+
* `pageSlot()` in its view to declare where nested content renders.
|
|
77
|
+
* - A function that returns a chain from the current `pageContext` —
|
|
78
|
+
* lets different routes use different chains, e.g. by reading
|
|
79
|
+
* Vike's `pageContext.urlPathname` or `pageContext.config.Layout`.
|
|
80
|
+
*
|
|
81
|
+
* Layers that are shared between the previous and next navigation
|
|
82
|
+
* stay mounted. Only the divergent suffix is disposed and re-mounted.
|
|
83
|
+
* Dialogs, focus traps, and effect subscriptions rooted in a surviving
|
|
84
|
+
* layer are unaffected by the nav.
|
|
85
|
+
*/
|
|
86
|
+
Layout?: AnyComponentDef | LayoutChain | ((pageContext: ClientPageContext) => LayoutChain);
|
|
87
|
+
/**
|
|
88
|
+
* Called on the slot element whose contents are about to be replaced,
|
|
89
|
+
* BEFORE the divergent suffix is disposed and re-mounted. The slot's
|
|
90
|
+
* current DOM is still attached when this runs — the only moment a
|
|
91
|
+
* leave animation can read/write it. Return a promise to defer the
|
|
92
|
+
* swap until the animation completes.
|
|
93
|
+
*
|
|
94
|
+
* For a plain no-layout setup, the slot element is the root container.
|
|
95
|
+
* Not called on the initial hydration render.
|
|
96
|
+
*/
|
|
97
|
+
onLeave?: (el: HTMLElement) => void | Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Called after the new divergent suffix is mounted, on the same slot
|
|
100
|
+
* element that was passed to `onLeave`. Use this to kick off an enter
|
|
101
|
+
* animation. Fire-and-forget — promise returns are ignored.
|
|
102
|
+
*
|
|
103
|
+
* Not called on the initial hydration render.
|
|
104
|
+
*/
|
|
105
|
+
onEnter?: (el: HTMLElement) => void;
|
|
106
|
+
/**
|
|
107
|
+
* Called after mount or hydration completes. Fires on every render
|
|
108
|
+
* including the initial hydration. Use for per-render side effects
|
|
109
|
+
* that don't fit the animation hooks.
|
|
110
|
+
*/
|
|
16
111
|
onMount?: () => void;
|
|
17
112
|
}
|
|
18
113
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
114
|
+
* Adapt a `TransitionOptions` object (e.g. the output of
|
|
115
|
+
* `routeTransition()` from `@llui/transitions`, or a preset like `fade`
|
|
116
|
+
* / `slide`) into the `onLeave` / `onEnter` pair expected by
|
|
117
|
+
* `createOnRenderClient`.
|
|
118
|
+
*
|
|
119
|
+
* ```ts
|
|
120
|
+
* import { createOnRenderClient, fromTransition } from '@llui/vike/client'
|
|
121
|
+
* import { routeTransition } from '@llui/transitions'
|
|
122
|
+
*
|
|
123
|
+
* export const onRenderClient = createOnRenderClient({
|
|
124
|
+
* Layout: AppLayout,
|
|
125
|
+
* ...fromTransition(routeTransition({ duration: 200 })),
|
|
126
|
+
* })
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* The transition operates on the slot element — in a no-layout setup,
|
|
130
|
+
* the root container; in a layout setup, the innermost surviving
|
|
131
|
+
* layer's `pageSlot()` element. Opacity / transform fades apply to the
|
|
132
|
+
* outgoing page content, then the new page fades in.
|
|
133
|
+
*/
|
|
134
|
+
export declare function fromTransition(t: TransitionOptions): Pick<RenderClientOptions, 'onLeave' | 'onEnter'>;
|
|
135
|
+
/**
|
|
136
|
+
* @internal — test helper. Disposes every layer in the current chain
|
|
137
|
+
* and clears the module state so subsequent calls behave as a first
|
|
138
|
+
* mount. Not part of the public API; subject to change without notice.
|
|
139
|
+
*/
|
|
140
|
+
export declare function _resetChainForTest(): void;
|
|
141
|
+
/**
|
|
142
|
+
* Back-compat alias for the pre-layout test helper name.
|
|
143
|
+
* @internal
|
|
144
|
+
* @deprecated — use `_resetChainForTest` instead.
|
|
145
|
+
*/
|
|
146
|
+
export declare function _resetCurrentHandleForTest(): void;
|
|
147
|
+
/**
|
|
148
|
+
* Default onRenderClient hook — no layout, no animation hooks. Hydrates
|
|
149
|
+
* on first load, mounts fresh on subsequent navs. Use `createOnRenderClient`
|
|
150
|
+
* for the customizable factory form.
|
|
21
151
|
*/
|
|
22
152
|
export declare function onRenderClient(pageContext: ClientPageContext): Promise<void>;
|
|
23
153
|
/**
|
|
24
|
-
* Factory to create a customized onRenderClient hook.
|
|
154
|
+
* Factory to create a customized onRenderClient hook. See `RenderClientOptions`
|
|
155
|
+
* for the full option surface — this is the entry point for persistent
|
|
156
|
+
* layouts, route transitions, and lifecycle hooks.
|
|
25
157
|
*
|
|
26
|
-
* ```
|
|
158
|
+
* ```ts
|
|
27
159
|
* // pages/+onRenderClient.ts
|
|
28
|
-
* import { createOnRenderClient } from '@llui/vike/client'
|
|
160
|
+
* import { createOnRenderClient, fromTransition } from '@llui/vike/client'
|
|
161
|
+
* import { routeTransition } from '@llui/transitions'
|
|
162
|
+
* import { AppLayout } from './+Layout'
|
|
29
163
|
*
|
|
30
164
|
* export const onRenderClient = createOnRenderClient({
|
|
31
|
-
*
|
|
32
|
-
*
|
|
165
|
+
* Layout: AppLayout,
|
|
166
|
+
* ...fromTransition(routeTransition({ duration: 200 })),
|
|
167
|
+
* onMount: () => console.log('page rendered'),
|
|
33
168
|
* })
|
|
34
169
|
* ```
|
|
35
170
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"on-render-client.d.ts","sourceRoot":"","sources":["../src/on-render-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAa,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"on-render-client.d.ts","sourceRoot":"","sources":["../src/on-render-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAa,iBAAiB,EAAS,MAAM,WAAW,CAAA;AAKlF,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,cAAc,CAAC,EAAE,OAAO,CAAA;KACzB;CACF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACtD,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,cAAc,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;IACnC,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,KAAK,eAAe,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;AACvE,KAAK,WAAW,GAAG,aAAa,CAAC,eAAe,CAAC,CAAA;AAoBjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,eAAe,GAAG,WAAW,GAAG,CAAC,CAAC,WAAW,EAAE,iBAAiB,KAAK,WAAW,CAAC,CAAA;IAE1F;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEnD;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,IAAI,CAAA;IAEnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EAAE,iBAAiB,GACnB,IAAI,CAAC,mBAAmB,EAAE,SAAS,GAAG,SAAS,CAAC,CAgBlD;AAoBD;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAOzC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,mBAAmB,GAC3B,CAAC,WAAW,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAEnD"}
|