@llui/vike 0.11.3 → 0.11.5
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 +74 -8
- package/dist/__llui_deps.json +13 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/nav-progress.d.ts +92 -0
- package/dist/nav-progress.d.ts.map +1 -0
- package/dist/nav-progress.js +52 -0
- package/dist/nav-progress.js.map +1 -0
- package/dist/on-render-client.d.ts +14 -0
- package/dist/on-render-client.d.ts.map +1 -1
- package/dist/on-render-client.js +6 -0
- package/dist/on-render-client.js.map +1 -1
- package/dist/page-slot.d.ts +16 -0
- package/dist/page-slot.d.ts.map +1 -1
- package/dist/page-slot.js +16 -0
- package/dist/page-slot.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -105,6 +105,20 @@ export const onRenderHtml = createOnRenderHtml({
|
|
|
105
105
|
|
|
106
106
|
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.
|
|
107
107
|
|
|
108
|
+
You can place your **own siblings** next to `pageSlot()` — before it or after it — in the same parent element (e.g. a navigation-loading bar beside the page inside `<main>`). The slot owns only the region between its anchor and a synthesized end sentinel, so on both SSR and every client navigation it inserts/disposes that region without touching your siblings. The `display: contents` wrapper that older guidance used to isolate the slot is **no longer needed** — drop it.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { div, main, text } from '@llui/dom'
|
|
112
|
+
import { pageSlot } from '@llui/vike/client'
|
|
113
|
+
|
|
114
|
+
const navBar = () => div({ class: 'nav-loading' }, [text('…')])
|
|
115
|
+
|
|
116
|
+
main([
|
|
117
|
+
navBar(), // ← your sibling survives navigation…
|
|
118
|
+
pageSlot(), // …only this region swaps
|
|
119
|
+
])
|
|
120
|
+
```
|
|
121
|
+
|
|
108
122
|
#### Nested layouts
|
|
109
123
|
|
|
110
124
|
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`.
|
|
@@ -251,6 +265,8 @@ export const onRenderClient = createOnRenderClient({
|
|
|
251
265
|
|
|
252
266
|
`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.
|
|
253
267
|
|
|
268
|
+
> **`onLeave`/`onEnter` are not loading hooks.** They bracket the DOM **swap**, which runs _after_ Vike has already fetched the new page's `+data` — Vike only invokes `onRenderClient` once the incoming pageContext is populated. Nothing in this cycle covers the during-fetch latency a user perceives as lag. For a loader that appears the moment a navigation starts (on the click, before the round-trip), use [Navigation Progress](#navigation-progress) below.
|
|
269
|
+
|
|
254
270
|
For raw animations without `@llui/transitions`, write the hooks yourself:
|
|
255
271
|
|
|
256
272
|
```ts
|
|
@@ -260,6 +276,55 @@ export const onRenderClient = createOnRenderClient({
|
|
|
260
276
|
})
|
|
261
277
|
```
|
|
262
278
|
|
|
279
|
+
### Navigation Progress
|
|
280
|
+
|
|
281
|
+
To show a loader _while_ a client navigation is in flight — the latency between the click and the new page appearing — you need a signal at navigation **start**, before the `+data` round-trip. None of the `onRenderClient` hooks fire there (see the note above). Vike's native `onPageTransitionStart` / `onPageTransitionEnd` hooks do, and `createNavigationProgress()` wraps them into a reactive boolean the layout binds:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
// nav-progress.ts — your module, created once
|
|
285
|
+
import { createNavigationProgress } from '@llui/vike/client'
|
|
286
|
+
|
|
287
|
+
// `delay` debounces the reveal: navigations that resolve faster than 120ms
|
|
288
|
+
// (e.g. served from a hover prefetch) never flash the indicator.
|
|
289
|
+
export const navProgress = createNavigationProgress({ delay: 120 })
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
`@llui/vike` can't register Vike's `+onPageTransition*` hooks for you — Vike discovers them by the `+` filename convention — so re-export the handle's hook functions from the two convention files:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
// pages/+onPageTransitionStart.ts
|
|
296
|
+
export { onPageTransitionStart } from '../nav-progress'
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
// pages/+onPageTransitionEnd.ts
|
|
301
|
+
export { onPageTransitionEnd } from '../nav-progress'
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Then bind `navProgress.pending` in the layout. It's a `LiveSignal<boolean>`: `peek()` for a one-shot read, `bind(cb)` for a reactive subscription that fires immediately with the current value and on every change. The zero-message path is an `onMount` that toggles a class — `bind` returns its unsubscribe, which doubles as the `onMount` cleanup:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
// pages/Layout.ts
|
|
308
|
+
import { component, div, header, main, onMount } from '@llui/dom'
|
|
309
|
+
import { pageSlot } from '@llui/vike/client'
|
|
310
|
+
import { navProgress } from '../nav-progress'
|
|
311
|
+
|
|
312
|
+
export const AppLayout = component<LayoutState, LayoutMsg>({
|
|
313
|
+
name: 'AppLayout',
|
|
314
|
+
init: () => [{ session: null }, []],
|
|
315
|
+
update: layoutUpdate,
|
|
316
|
+
view: () => [
|
|
317
|
+
div({ class: 'app-shell' }, [
|
|
318
|
+
onMount((root) => navProgress.pending.bind((p) => root.classList.toggle('nav-pending', p))),
|
|
319
|
+
header([]),
|
|
320
|
+
main([pageSlot()]),
|
|
321
|
+
]),
|
|
322
|
+
],
|
|
323
|
+
})
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
This replaces the module-singleton + layout-handle capture + hand-rolled `nav/pending` message + reducer case each app would otherwise re-derive. If you'd rather drive the indicator from layout state instead of a class toggle, `bind` straight into a `send({ type: 'nav/pending', pending })` — but the class toggle needs no message at all.
|
|
327
|
+
|
|
263
328
|
### Client Navigation Lifecycle
|
|
264
329
|
|
|
265
330
|
When Vike fires a client-side navigation, `@llui/vike` runs this sequence inside `onRenderClient`:
|
|
@@ -337,13 +402,14 @@ Hydrates the server-rendered HTML on the client. Attaches event listeners and re
|
|
|
337
402
|
|
|
338
403
|
## API
|
|
339
404
|
|
|
340
|
-
| Export
|
|
341
|
-
|
|
|
342
|
-
| `onRenderHtml`
|
|
343
|
-
| `createOnRenderHtml`
|
|
344
|
-
| `onRenderClient`
|
|
345
|
-
| `createOnRenderClient`
|
|
346
|
-
| `pageSlot`
|
|
347
|
-
| `fromTransition`
|
|
405
|
+
| Export | Sub-path | Description |
|
|
406
|
+
| -------------------------- | ------------------- | -------------------------------------------------------------------------------- |
|
|
407
|
+
| `onRenderHtml` | `@llui/vike/server` | Default server hook — minimal HTML template |
|
|
408
|
+
| `createOnRenderHtml` | `@llui/vike/server` | Factory for custom document templates + persistent layouts |
|
|
409
|
+
| `onRenderClient` | `@llui/vike/client` | Default client hook — hydrate or mount |
|
|
410
|
+
| `createOnRenderClient` | `@llui/vike/client` | Factory for custom container + layouts + transition hooks |
|
|
411
|
+
| `pageSlot` | `@llui/vike/client` | Structural primitive — declares where a layout renders its page |
|
|
412
|
+
| `fromTransition` | `@llui/vike/client` | Adapter: `TransitionOptions` → `{ onLeave, onEnter }` hook pair |
|
|
413
|
+
| `createNavigationProgress` | `@llui/vike/client` | Reactive `pending` signal + `+onPageTransition*` hooks for a during-fetch loader |
|
|
348
414
|
|
|
349
415
|
The barrel export (`@llui/vike`) re-exports everything, but prefer sub-path imports to avoid bundling jsdom into the client.
|
package/dist/__llui_deps.json
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
"compilerVersion": "0.3.0",
|
|
3
3
|
"components": {},
|
|
4
4
|
"helpers": {
|
|
5
|
+
"nav-progress#createNavigationProgress": {
|
|
6
|
+
"helperLocalPaths": [],
|
|
7
|
+
"kind": "view-helper",
|
|
8
|
+
"viaParams": [
|
|
9
|
+
{
|
|
10
|
+
"index": 0,
|
|
11
|
+
"reads": [
|
|
12
|
+
"delay"
|
|
13
|
+
],
|
|
14
|
+
"shape": "state-value"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
},
|
|
5
18
|
"on-render-client#_mountChainSuffix": {
|
|
6
19
|
"helperLocalPaths": [],
|
|
7
20
|
"kind": "view-helper",
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,6 @@ export type { PageContext, ServerLayoutResolverContext, DocumentContext, RenderH
|
|
|
3
3
|
export { onRenderClient, createOnRenderClient, fromTransition, getLayoutChain, } from './on-render-client.js';
|
|
4
4
|
export type { ClientPageContext, LayoutResolverContext, RenderClientOptions, LayerHandle, } from './on-render-client.js';
|
|
5
5
|
export { pageSlot } from './page-slot.js';
|
|
6
|
+
export { createNavigationProgress } from './nav-progress.js';
|
|
7
|
+
export type { NavigationProgress, NavigationProgressOptions } from './nav-progress.js';
|
|
6
8
|
//# 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,EACV,WAAW,EACX,2BAA2B,EAC3B,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,QAAQ,GACT,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,cAAc,GACf,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA"}
|
|
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,2BAA2B,EAC3B,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,QAAQ,GACT,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,cAAc,GACf,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAC5D,YAAY,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { onRenderHtml, createOnRenderHtml } from './on-render-html.js';
|
|
2
2
|
export { onRenderClient, createOnRenderClient, fromTransition, getLayoutChain, } from './on-render-client.js';
|
|
3
3
|
export { pageSlot } from './page-slot.js';
|
|
4
|
+
export { createNavigationProgress } from './nav-progress.js';
|
|
4
5
|
//# 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;AAUtE,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,cAAc,GACf,MAAM,uBAAuB,CAAA;AAQ9B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA","sourcesContent":["export { onRenderHtml, createOnRenderHtml } from './on-render-html.js'\nexport type {\n PageContext,\n ServerLayoutResolverContext,\n DocumentContext,\n RenderHtmlResult,\n RenderHtmlOptions,\n AnyLayer,\n} from './on-render-html.js'\n\nexport {\n onRenderClient,\n createOnRenderClient,\n fromTransition,\n getLayoutChain,\n} from './on-render-client.js'\nexport type {\n ClientPageContext,\n LayoutResolverContext,\n RenderClientOptions,\n LayerHandle,\n} from './on-render-client.js'\n\nexport { pageSlot } from './page-slot.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAUtE,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,cAAc,GACf,MAAM,uBAAuB,CAAA;AAQ9B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA","sourcesContent":["export { onRenderHtml, createOnRenderHtml } from './on-render-html.js'\nexport type {\n PageContext,\n ServerLayoutResolverContext,\n DocumentContext,\n RenderHtmlResult,\n RenderHtmlOptions,\n AnyLayer,\n} from './on-render-html.js'\n\nexport {\n onRenderClient,\n createOnRenderClient,\n fromTransition,\n getLayoutChain,\n} from './on-render-client.js'\nexport type {\n ClientPageContext,\n LayoutResolverContext,\n RenderClientOptions,\n LayerHandle,\n} from './on-render-client.js'\n\nexport { pageSlot } from './page-slot.js'\n\nexport { createNavigationProgress } from './nav-progress.js'\nexport type { NavigationProgress, NavigationProgressOptions } from './nav-progress.js'\n"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { LiveSignal } from '@llui/dom';
|
|
2
|
+
/**
|
|
3
|
+
* A navigation-progress handle: the first-class answer to "show a loader while a
|
|
4
|
+
* client navigation is in flight."
|
|
5
|
+
*
|
|
6
|
+
* **Why this exists.** None of `createOnRenderClient`'s lifecycle hooks fire
|
|
7
|
+
* during the latency window a user perceives as lag. `onLeave`/`onEnter` bracket
|
|
8
|
+
* the DOM *swap*, and Vike only invokes `onRenderClient` *after* it has already
|
|
9
|
+
* fetched the new page's `+data` — so by the time any of those run, the wait is
|
|
10
|
+
* over. The only signals that fire at navigation *start* (on the click, before
|
|
11
|
+
* the server round-trip) are Vike's native `onPageTransitionStart` /
|
|
12
|
+
* `onPageTransitionEnd` hooks. This helper wraps that pair into a reactive
|
|
13
|
+
* boolean the layout binds, removing the module-singleton + layout-handle capture
|
|
14
|
+
* + hand-rolled `nav/pending` message + reducer case every app would otherwise
|
|
15
|
+
* re-derive.
|
|
16
|
+
*
|
|
17
|
+
* **Wiring (three small files, no per-app glue logic).** `@llui/vike` cannot
|
|
18
|
+
* register Vike's `+onPageTransition*` hooks for you — Vike discovers them by the
|
|
19
|
+
* `+` filename convention — so create the handle once in your own module and
|
|
20
|
+
* re-export its hook functions from the convention files:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* // nav-progress.ts — your module, created once
|
|
24
|
+
* import { createNavigationProgress } from '@llui/vike/client'
|
|
25
|
+
* export const navProgress = createNavigationProgress({ delay: 120 })
|
|
26
|
+
* ```
|
|
27
|
+
* ```ts
|
|
28
|
+
* // pages/+onPageTransitionStart.ts
|
|
29
|
+
* export { onPageTransitionStart } from '../nav-progress' // (re-export by name)
|
|
30
|
+
* // pages/+onPageTransitionEnd.ts
|
|
31
|
+
* export { onPageTransitionEnd } from '../nav-progress'
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Then bind `pending` in the layout. It is a {@link LiveSignal}: `peek()` in
|
|
35
|
+
* handlers, `bind()` for a reactive subscription. The zero-message path is to
|
|
36
|
+
* place an `onMount` in the layout view and `bind()` there — `bind` returns its
|
|
37
|
+
* unsubscribe, which doubles as the `onMount` cleanup, so it auto-disposes:
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { onMount, div } from '@llui/dom'
|
|
41
|
+
* import { navProgress } from '../nav-progress'
|
|
42
|
+
*
|
|
43
|
+
* view: () => [
|
|
44
|
+
* div({ class: 'app-shell' }, [
|
|
45
|
+
* onMount((root) =>
|
|
46
|
+
* navProgress.pending.bind((p) => root.classList.toggle('nav-pending', p)),
|
|
47
|
+
* ),
|
|
48
|
+
* // …header, main([pageSlot()]), etc.
|
|
49
|
+
* ]),
|
|
50
|
+
* ]
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export interface NavigationProgress {
|
|
54
|
+
/**
|
|
55
|
+
* Vike `+onPageTransitionStart` hook. Fires on the navigation click, before
|
|
56
|
+
* Vike fetches the new page's `+data`. Re-export it from
|
|
57
|
+
* `pages/+onPageTransitionStart.ts`. Bound to the handle — safe to detach.
|
|
58
|
+
*/
|
|
59
|
+
readonly onPageTransitionStart: () => void;
|
|
60
|
+
/**
|
|
61
|
+
* Vike `+onPageTransitionEnd` hook. Fires once the new page is rendered.
|
|
62
|
+
* Re-export it from `pages/+onPageTransitionEnd.ts`. Bound to the handle —
|
|
63
|
+
* safe to detach.
|
|
64
|
+
*/
|
|
65
|
+
readonly onPageTransitionEnd: () => void;
|
|
66
|
+
/**
|
|
67
|
+
* `true` while a client navigation is in flight, `false` otherwise. A
|
|
68
|
+
* {@link LiveSignal}: `peek()` for a one-shot read; `bind(cb)` fires `cb`
|
|
69
|
+
* immediately with the current value, then on every change, returning an
|
|
70
|
+
* unsubscribe. When a `delay` is configured the value only flips to `true`
|
|
71
|
+
* after the navigation has been pending that long (the debounce that prevents
|
|
72
|
+
* prefetch-fast navigations from flashing the indicator).
|
|
73
|
+
*/
|
|
74
|
+
readonly pending: LiveSignal<boolean>;
|
|
75
|
+
}
|
|
76
|
+
export interface NavigationProgressOptions {
|
|
77
|
+
/**
|
|
78
|
+
* Anti-flash debounce, in milliseconds. The `pending` signal only becomes
|
|
79
|
+
* `true` once a navigation has been in flight for `delay` ms, so navigations
|
|
80
|
+
* that resolve faster than that (e.g. served from a hover prefetch) never
|
|
81
|
+
* reveal the indicator. The end transition always settles `pending` to `false`
|
|
82
|
+
* immediately, cancelling any not-yet-fired reveal. Default `0` (reveal
|
|
83
|
+
* immediately on navigation start).
|
|
84
|
+
*/
|
|
85
|
+
delay?: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Create a {@link NavigationProgress} handle. See the interface docs for the
|
|
89
|
+
* three-file wiring and binding patterns.
|
|
90
|
+
*/
|
|
91
|
+
export declare function createNavigationProgress(options?: NavigationProgressOptions): NavigationProgress;
|
|
92
|
+
//# sourceMappingURL=nav-progress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-progress.d.ts","sourceRoot":"","sources":["../src/nav-progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,EAAE,MAAM,IAAI,CAAA;IAE1C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,EAAE,MAAM,IAAI,CAAA;IAExC;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,yBAAyB,GAAG,kBAAkB,CAkDhG"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a {@link NavigationProgress} handle. See the interface docs for the
|
|
3
|
+
* three-file wiring and binding patterns.
|
|
4
|
+
*/
|
|
5
|
+
export function createNavigationProgress(options) {
|
|
6
|
+
const delay = options?.delay ?? 0;
|
|
7
|
+
const subs = new Set();
|
|
8
|
+
let published = false;
|
|
9
|
+
let revealTimer = null;
|
|
10
|
+
function publish(next) {
|
|
11
|
+
if (next === published)
|
|
12
|
+
return;
|
|
13
|
+
published = next;
|
|
14
|
+
for (const cb of subs)
|
|
15
|
+
cb(next);
|
|
16
|
+
}
|
|
17
|
+
function clearReveal() {
|
|
18
|
+
if (revealTimer !== null) {
|
|
19
|
+
clearTimeout(revealTimer);
|
|
20
|
+
revealTimer = null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const pending = {
|
|
24
|
+
peek: () => published,
|
|
25
|
+
bind: (cb) => {
|
|
26
|
+
subs.add(cb);
|
|
27
|
+
cb(published); // immediate fire with current value (LiveSignal contract)
|
|
28
|
+
return () => {
|
|
29
|
+
subs.delete(cb);
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
// Arrow functions so destructuring/re-export keeps them bound to this handle.
|
|
34
|
+
const onPageTransitionStart = () => {
|
|
35
|
+
if (delay > 0) {
|
|
36
|
+
clearReveal();
|
|
37
|
+
revealTimer = setTimeout(() => {
|
|
38
|
+
revealTimer = null;
|
|
39
|
+
publish(true);
|
|
40
|
+
}, delay);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
publish(true);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const onPageTransitionEnd = () => {
|
|
47
|
+
clearReveal();
|
|
48
|
+
publish(false);
|
|
49
|
+
};
|
|
50
|
+
return { onPageTransitionStart, onPageTransitionEnd, pending };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=nav-progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-progress.js","sourceRoot":"","sources":["../src/nav-progress.ts"],"names":[],"mappings":"AA2FA;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAmC;IAC1E,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,CAAC,CAAA;IAEjC,MAAM,IAAI,GAAG,IAAI,GAAG,EAA8B,CAAA;IAClD,IAAI,SAAS,GAAG,KAAK,CAAA;IACrB,IAAI,WAAW,GAAyC,IAAI,CAAA;IAE5D,SAAS,OAAO,CAAC,IAAa;QAC5B,IAAI,IAAI,KAAK,SAAS;YAAE,OAAM;QAC9B,SAAS,GAAG,IAAI,CAAA;QAChB,KAAK,MAAM,EAAE,IAAI,IAAI;YAAE,EAAE,CAAC,IAAI,CAAC,CAAA;IACjC,CAAC;IAED,SAAS,WAAW;QAClB,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACzB,YAAY,CAAC,WAAW,CAAC,CAAA;YACzB,WAAW,GAAG,IAAI,CAAA;QACpB,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAwB;QACnC,IAAI,EAAE,GAAG,EAAE,CAAC,SAAS;QACrB,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE;YACX,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACZ,EAAE,CAAC,SAAS,CAAC,CAAA,CAAC,0DAA0D;YACxE,OAAO,GAAG,EAAE;gBACV,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YACjB,CAAC,CAAA;QACH,CAAC;KACF,CAAA;IAED,8EAA8E;IAC9E,MAAM,qBAAqB,GAAG,GAAS,EAAE;QACvC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,WAAW,EAAE,CAAA;YACb,WAAW,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,WAAW,GAAG,IAAI,CAAA;gBAClB,OAAO,CAAC,IAAI,CAAC,CAAA;YACf,CAAC,EAAE,KAAK,CAAC,CAAA;QACX,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,CAAA;QACf,CAAC;IACH,CAAC,CAAA;IAED,MAAM,mBAAmB,GAAG,GAAS,EAAE;QACrC,WAAW,EAAE,CAAA;QACb,OAAO,CAAC,KAAK,CAAC,CAAA;IAChB,CAAC,CAAA;IAED,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,OAAO,EAAE,CAAA;AAChE,CAAC","sourcesContent":["import type { LiveSignal } from '@llui/dom'\n\n/**\n * A navigation-progress handle: the first-class answer to \"show a loader while a\n * client navigation is in flight.\"\n *\n * **Why this exists.** None of `createOnRenderClient`'s lifecycle hooks fire\n * during the latency window a user perceives as lag. `onLeave`/`onEnter` bracket\n * the DOM *swap*, and Vike only invokes `onRenderClient` *after* it has already\n * fetched the new page's `+data` — so by the time any of those run, the wait is\n * over. The only signals that fire at navigation *start* (on the click, before\n * the server round-trip) are Vike's native `onPageTransitionStart` /\n * `onPageTransitionEnd` hooks. This helper wraps that pair into a reactive\n * boolean the layout binds, removing the module-singleton + layout-handle capture\n * + hand-rolled `nav/pending` message + reducer case every app would otherwise\n * re-derive.\n *\n * **Wiring (three small files, no per-app glue logic).** `@llui/vike` cannot\n * register Vike's `+onPageTransition*` hooks for you — Vike discovers them by the\n * `+` filename convention — so create the handle once in your own module and\n * re-export its hook functions from the convention files:\n *\n * ```ts\n * // nav-progress.ts — your module, created once\n * import { createNavigationProgress } from '@llui/vike/client'\n * export const navProgress = createNavigationProgress({ delay: 120 })\n * ```\n * ```ts\n * // pages/+onPageTransitionStart.ts\n * export { onPageTransitionStart } from '../nav-progress' // (re-export by name)\n * // pages/+onPageTransitionEnd.ts\n * export { onPageTransitionEnd } from '../nav-progress'\n * ```\n *\n * Then bind `pending` in the layout. It is a {@link LiveSignal}: `peek()` in\n * handlers, `bind()` for a reactive subscription. The zero-message path is to\n * place an `onMount` in the layout view and `bind()` there — `bind` returns its\n * unsubscribe, which doubles as the `onMount` cleanup, so it auto-disposes:\n *\n * ```ts\n * import { onMount, div } from '@llui/dom'\n * import { navProgress } from '../nav-progress'\n *\n * view: () => [\n * div({ class: 'app-shell' }, [\n * onMount((root) =>\n * navProgress.pending.bind((p) => root.classList.toggle('nav-pending', p)),\n * ),\n * // …header, main([pageSlot()]), etc.\n * ]),\n * ]\n * ```\n */\nexport interface NavigationProgress {\n /**\n * Vike `+onPageTransitionStart` hook. Fires on the navigation click, before\n * Vike fetches the new page's `+data`. Re-export it from\n * `pages/+onPageTransitionStart.ts`. Bound to the handle — safe to detach.\n */\n readonly onPageTransitionStart: () => void\n\n /**\n * Vike `+onPageTransitionEnd` hook. Fires once the new page is rendered.\n * Re-export it from `pages/+onPageTransitionEnd.ts`. Bound to the handle —\n * safe to detach.\n */\n readonly onPageTransitionEnd: () => void\n\n /**\n * `true` while a client navigation is in flight, `false` otherwise. A\n * {@link LiveSignal}: `peek()` for a one-shot read; `bind(cb)` fires `cb`\n * immediately with the current value, then on every change, returning an\n * unsubscribe. When a `delay` is configured the value only flips to `true`\n * after the navigation has been pending that long (the debounce that prevents\n * prefetch-fast navigations from flashing the indicator).\n */\n readonly pending: LiveSignal<boolean>\n}\n\nexport interface NavigationProgressOptions {\n /**\n * Anti-flash debounce, in milliseconds. The `pending` signal only becomes\n * `true` once a navigation has been in flight for `delay` ms, so navigations\n * that resolve faster than that (e.g. served from a hover prefetch) never\n * reveal the indicator. The end transition always settles `pending` to `false`\n * immediately, cancelling any not-yet-fired reveal. Default `0` (reveal\n * immediately on navigation start).\n */\n delay?: number\n}\n\n/**\n * Create a {@link NavigationProgress} handle. See the interface docs for the\n * three-file wiring and binding patterns.\n */\nexport function createNavigationProgress(options?: NavigationProgressOptions): NavigationProgress {\n const delay = options?.delay ?? 0\n\n const subs = new Set<(pending: boolean) => void>()\n let published = false\n let revealTimer: ReturnType<typeof setTimeout> | null = null\n\n function publish(next: boolean): void {\n if (next === published) return\n published = next\n for (const cb of subs) cb(next)\n }\n\n function clearReveal(): void {\n if (revealTimer !== null) {\n clearTimeout(revealTimer)\n revealTimer = null\n }\n }\n\n const pending: LiveSignal<boolean> = {\n peek: () => published,\n bind: (cb) => {\n subs.add(cb)\n cb(published) // immediate fire with current value (LiveSignal contract)\n return () => {\n subs.delete(cb)\n }\n },\n }\n\n // Arrow functions so destructuring/re-export keeps them bound to this handle.\n const onPageTransitionStart = (): void => {\n if (delay > 0) {\n clearReveal()\n revealTimer = setTimeout(() => {\n revealTimer = null\n publish(true)\n }, delay)\n } else {\n publish(true)\n }\n }\n\n const onPageTransitionEnd = (): void => {\n clearReveal()\n publish(false)\n }\n\n return { onPageTransitionStart, onPageTransitionEnd, pending }\n}\n"]}
|
|
@@ -2,6 +2,8 @@ import type { SignalComponentHandle } from '@llui/dom';
|
|
|
2
2
|
import type { TransitionOptions, Renderable } from '@llui/dom';
|
|
3
3
|
import type { VikePageContextData } from './vike-namespace.js';
|
|
4
4
|
export { pageSlot } from './page-slot.js';
|
|
5
|
+
export { createNavigationProgress } from './nav-progress.js';
|
|
6
|
+
export type { NavigationProgress, NavigationProgressOptions } from './nav-progress.js';
|
|
5
7
|
/** A type-erased signal component as the adapter handles it (type params unused
|
|
6
8
|
* at runtime). Method syntax + a single `unknown` view-bag param so any concrete
|
|
7
9
|
* `SignalComponentDef<S,M,E>` assigns in (see on-render-html's AnyLayer note —
|
|
@@ -115,6 +117,13 @@ export interface RenderClientOptions {
|
|
|
115
117
|
*
|
|
116
118
|
* For a plain no-layout setup, the slot element is the root container.
|
|
117
119
|
* Not called on the initial hydration render.
|
|
120
|
+
*
|
|
121
|
+
* **Not a loading hook.** `onLeave` fires after Vike has already fetched the
|
|
122
|
+
* new page's `+data` — Vike only invokes `onRenderClient` once the incoming
|
|
123
|
+
* pageContext is populated. So `onLeave`/`onEnter` bracket the DOM *swap*, not
|
|
124
|
+
* the network *wait*; nothing here covers the during-fetch latency the user
|
|
125
|
+
* perceives as lag. For a navigation-start signal (fires on the click, before
|
|
126
|
+
* the round-trip) use {@link createNavigationProgress}.
|
|
118
127
|
*/
|
|
119
128
|
onLeave?: (el: HTMLElement) => void | Promise<void>;
|
|
120
129
|
/**
|
|
@@ -174,6 +183,11 @@ export interface RenderClientOptions {
|
|
|
174
183
|
* The transition operates on the slot element — in a no-layout setup,
|
|
175
184
|
* the root container; in a layout setup, the innermost surviving layer's
|
|
176
185
|
* `pageSlot()` element.
|
|
186
|
+
*
|
|
187
|
+
* Like the underlying {@link RenderClientOptions.onLeave}/`onEnter`, the
|
|
188
|
+
* transition brackets the DOM *swap*, which runs after Vike has fetched the new
|
|
189
|
+
* page's `+data` — it does not animate over the network wait. For a
|
|
190
|
+
* during-fetch loading indicator, pair it with {@link createNavigationProgress}.
|
|
177
191
|
*/
|
|
178
192
|
export declare function fromTransition(t: TransitionOptions): Pick<RenderClientOptions, 'onLeave' | 'onEnter'>;
|
|
179
193
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"on-render-client.d.ts","sourceRoot":"","sources":["../src/on-render-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAe,MAAM,WAAW,CAAA;AACnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAI9D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"on-render-client.d.ts","sourceRoot":"","sources":["../src/on-render-client.ts"],"names":[],"mappings":"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"}
|
package/dist/on-render-client.js
CHANGED
|
@@ -3,6 +3,7 @@ import { _consumePendingSlot, _resetPendingSlot } from './page-slot.js';
|
|
|
3
3
|
// Re-exported so `@llui/vike/client` is a one-stop-shop for everything
|
|
4
4
|
// a pages/+onRenderClient.ts / Layout.ts file needs.
|
|
5
5
|
export { pageSlot } from './page-slot.js';
|
|
6
|
+
export { createNavigationProgress } from './nav-progress.js';
|
|
6
7
|
/**
|
|
7
8
|
* Resolves the layout chain for a given pageContext. A single layout
|
|
8
9
|
* becomes a one-element chain; a function resolver gives callers full
|
|
@@ -44,6 +45,11 @@ function seedFor(data) {
|
|
|
44
45
|
* The transition operates on the slot element — in a no-layout setup,
|
|
45
46
|
* the root container; in a layout setup, the innermost surviving layer's
|
|
46
47
|
* `pageSlot()` element.
|
|
48
|
+
*
|
|
49
|
+
* Like the underlying {@link RenderClientOptions.onLeave}/`onEnter`, the
|
|
50
|
+
* transition brackets the DOM *swap*, which runs after Vike has fetched the new
|
|
51
|
+
* page's `+data` — it does not animate over the network wait. For a
|
|
52
|
+
* during-fetch loading indicator, pair it with {@link createNavigationProgress}.
|
|
47
53
|
*/
|
|
48
54
|
export function fromTransition(t) {
|
|
49
55
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"on-render-client.js","sourceRoot":"","sources":["../src/on-render-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { mountSignalComponent, hydrateSignalApp } from '@llui/dom'\nimport type { SignalComponentHandle, MountTarget } from '@llui/dom'\nimport type { TransitionOptions, Renderable } from '@llui/dom'\nimport { _consumePendingSlot, _resetPendingSlot } from './page-slot.js'\nimport type { VikePageContextData } from './vike-namespace.js'\n\n// Re-exported so `@llui/vike/client` is a one-stop-shop for everything\n// a pages/+onRenderClient.ts / Layout.ts file needs.\nexport { pageSlot } from './page-slot.js'\n\n/** A type-erased signal component as the adapter handles it (type params unused\n * at runtime). Method syntax + a single `unknown` view-bag param so any concrete\n * `SignalComponentDef<S,M,E>` assigns in (see on-render-html's AnyLayer note —\n * ComponentBag's state/send variance rules out the `<unknown,…>` erasure). */\nexport interface AnyLayer {\n readonly name?: string\n init(): unknown\n update(state: unknown, msg: unknown): unknown\n view(bag: unknown): Renderable\n onEffect?(effect: unknown, api: unknown): void | (() => void)\n}\n/** The live handle a mounted/hydrated layer exposes (send/getState/subscribe). */\nexport type LayerHandle = SignalComponentHandle<unknown, unknown>\n\ndeclare global {\n interface Window {\n __LLUI_STATE__?: unknown\n }\n}\n\n/**\n * Page context shape as seen by `@llui/vike`'s client-side hooks. The\n * `Page` and `data` fields come from whichever `+Page.ts` and `+data.ts`\n * Vike resolved for the current route.\n *\n * `data` is derived from the global `Vike.PageContext` namespace — the\n * convention users already know from Vike. Consumer augmentations flow\n * through to every callback here without a cast; unaugmented projects\n * fall back to `unknown`.\n *\n * In the signal runtime a component's `init()` takes no data argument, so\n * each layer's `data` slice is used directly as that layer's seed STATE\n * when present; when absent, the layer's own `init()` provides the seed.\n *\n * `lluiLayoutData` is optional and carries per-layer data for the layout\n * chain configured via `createOnRenderClient({ Layout })`. It's indexed\n * outermost-to-innermost, one entry per layout layer.\n */\nexport interface ClientPageContext {\n Page: AnyLayer\n data?: VikePageContextData\n\n /**\n * Vike's resolved pathname for the current navigation (origin-, query- and\n * hash-stripped, e.g. `/docs/getting-started`). Vike always populates this on\n * the live pageContext; it's optional here only because not every test/SSR\n * construction site supplies it. Inside a `Layout` resolver it is guaranteed\n * present — see {@link LayoutResolverContext}.\n */\n urlPathname?: string\n\n /**\n * Vike's route params for the current route (e.g. `{ slug: 'intro' }` for a\n * `/docs/@slug` route). Empty object when the matched route has no params.\n * Guaranteed present inside a `Layout` resolver — see\n * {@link LayoutResolverContext}.\n */\n routeParams?: Record<string, string>\n\n lluiLayoutData?: readonly unknown[]\n isHydration?: boolean\n}\n\n/**\n * The pageContext a `Layout` **resolver function** receives. Identical to\n * {@link ClientPageContext} except Vike's routing fields (`urlPathname`,\n * `routeParams`) are guaranteed present — the resolver only ever runs against a\n * live Vike navigation, which always populates them. Typing them as required\n * here lets a route-scoped resolver read the route directly, with no cast:\n *\n * ```ts\n * Layout: (pageContext) =>\n * pageContext.urlPathname.startsWith('/docs')\n * ? [AppLayout, DocsLayout] // docs section keeps its sidebar mounted…\n * : [AppLayout], // …only the article re-mounts on docs→docs nav\n * ```\n *\n * The chain diff keeps every layer shared (by def reference) between the old\n * and new chain mounted, disposing only the divergent suffix — so navigating\n * `/docs/a → /docs/b` re-mounts just the page while `DocsLayout` (and its\n * sidebar, scroll position, focus, and effects) survives.\n */\nexport type LayoutResolverContext = ClientPageContext &\n Required<Pick<ClientPageContext, 'urlPathname' | 'routeParams'>>\n\ntype LayoutChain = ReadonlyArray<AnyLayer>\n\n/**\n * Resolves the layout chain for a given pageContext. A single layout\n * becomes a one-element chain; a function resolver gives callers full\n * control to return different chains for different routes.\n */\nfunction resolveLayoutChain(\n layoutOption: RenderClientOptions['Layout'],\n pageContext: ClientPageContext,\n): LayoutChain {\n if (!layoutOption) return []\n if (typeof layoutOption === 'function') {\n // The resolver only ever runs against a live Vike navigation, which always\n // populates `urlPathname`/`routeParams`. Our base type marks them optional\n // (test/SSR construction sites needn't supply them), so narrow to the\n // resolver's required view at this single boundary.\n return layoutOption(pageContext as LayoutResolverContext) ?? []\n }\n if (Array.isArray(layoutOption)) return layoutOption\n return [layoutOption as AnyLayer]\n}\n\n/** Resolve a layer's seed state — a present data slice IS the seed state\n * (signal init() takes no data); an absent slice falls back to init(). */\nfunction seedFor(data: unknown): unknown | undefined {\n return data === undefined ? undefined : data\n}\n\n/**\n * Page-lifecycle hooks that fire around the dispose → mount cycle on\n * client navigation. With persistent layouts in play the cycle only\n * tears down the *divergent* suffix of the layout chain — any layers\n * shared between the old and new routes stay mounted.\n *\n * On the initial hydration render, `onLeave` and `onEnter` are NOT\n * called — there's no outgoing page to leave and no animation to enter.\n * Use `onMount` for code that should run on every render including the\n * initial one.\n */\nexport interface RenderClientOptions {\n /** CSS selector for the mount container. Default: `'#app'`. */\n container?: string\n\n /**\n * Persistent layout chain. One of:\n *\n * - A single `SignalComponentDef` — becomes a one-layout chain.\n * - An array of `SignalComponentDef`s — outermost layout first,\n * innermost layout last. Every layer except the innermost must call\n * `pageSlot()` in its view to declare where nested content renders.\n * - A function that returns a chain from the current `pageContext`.\n *\n * Layers shared between the previous and next navigation stay mounted.\n * Only the divergent suffix is disposed and re-mounted.\n */\n Layout?: AnyLayer | LayoutChain | ((pageContext: LayoutResolverContext) => LayoutChain)\n\n /**\n * Called on the slot element whose contents are about to be replaced,\n * BEFORE the divergent suffix is disposed and re-mounted. The slot's\n * current DOM is still attached when this runs. Return a promise to\n * defer the swap until the animation completes.\n *\n * For a plain no-layout setup, the slot element is the root container.\n * Not called on the initial hydration render.\n */\n onLeave?: (el: HTMLElement) => void | Promise<void>\n\n /**\n * Called after the new divergent suffix is mounted, on the same slot\n * element that was passed to `onLeave`. Fire-and-forget. Not called on\n * the initial hydration render.\n */\n onEnter?: (el: HTMLElement) => void\n\n /**\n * Called after mount or hydration completes. Fires on every render\n * including the initial hydration. Receives the live layout chain —\n * `[...layouts, page]`, outermost first — as `LayerHandle`s.\n */\n onMount?: (chain: readonly LayerHandle[]) => void\n\n /**\n * Called for each surviving layout layer whose `lluiLayoutData[i]`\n * slice changed across a client navigation. Surviving layers stay\n * mounted but need a fresh injection of nav-driven data. You decide how\n * to translate the new data into a message and dispatch it through\n * `handle.send(msg)`.\n *\n * Not called for unchanged slices, not on the initial hydration render,\n * and not for the page layer (it always disposes and remounts, so its\n * `init`/seed receives the fresh data directly).\n */\n onLayerDataChange?: (ctx: {\n def: AnyLayer\n handle: LayerHandle\n newData: unknown\n prevData: unknown\n }) => void\n\n /**\n * Forwarded to the signal hydrate path for every layer on initial\n * hydration. When `true`, effects returned by each component's `init()`\n * are dispatched post-swap on the client. When `false` (default), they\n * are skipped — the SSR pass already ran them.\n *\n * Subsequent client-side navigation always uses a fresh mount, which\n * always fires init effects regardless of this flag.\n */\n runInitEffectsOnHydrate?: boolean\n}\n\n/**\n * Adapt a `TransitionOptions` object into the `onLeave` / `onEnter` pair\n * expected by `createOnRenderClient`.\n *\n * ```ts\n * import { createOnRenderClient, fromTransition } from '@llui/vike/client'\n * import { routeTransition } from '@llui/transitions'\n *\n * export const onRenderClient = createOnRenderClient({\n * Layout: AppLayout,\n * ...fromTransition(routeTransition({ duration: 200 })),\n * })\n * ```\n *\n * The transition operates on the slot element — in a no-layout setup,\n * the root container; in a layout setup, the innermost surviving layer's\n * `pageSlot()` element.\n */\nexport function fromTransition(\n t: TransitionOptions,\n): Pick<RenderClientOptions, 'onLeave' | 'onEnter'> {\n return {\n onLeave: t.leave\n ? (el): void | Promise<void> => {\n const result = t.leave!([el])\n return result && typeof (result as Promise<void>).then === 'function'\n ? (result as Promise<void>)\n : undefined\n }\n : undefined,\n onEnter: t.enter\n ? (el): void => {\n t.enter!([el])\n }\n : undefined,\n }\n}\n\n/**\n * One element of the live chain the adapter keeps between navs.\n * `handle` is the SignalComponentHandle returned by mount/hydrate for\n * this layer. `slotAnchor` is set when the layer called `pageSlot()`\n * during its view pass; null for the innermost layer (the page, which\n * has no slot). `slotContexts` snapshots the context values in scope at\n * the slot, replayed into the nested layer's build.\n */\ninterface ChainEntry {\n def: AnyLayer\n handle: LayerHandle\n slotAnchor: Comment | null\n slotContexts: ReadonlyMap<symbol, unknown> | null\n /**\n * The data slice this layer was most recently mounted or updated with.\n * Compared shallow-key against the next nav's `lluiLayoutData[i]` to\n * decide whether a surviving layer needs `onLayerDataChange` to fire.\n */\n data: unknown\n}\n\n/**\n * Live chain of mounted layers — module-level singleton. Vike runs one\n * client-side adapter per browser tab; within one tab a single\n * `chainHandles` array holds the handle for every active layer, indexed\n * `[outermostLayout, ..., innerLayout, page]`. It mutates in place across\n * navigations: shared layout layers stay live, divergent suffix layers\n * dispose, new layers append.\n *\n * Module-level scope is correct for the browser (one consumer per page\n * load); a multi-tenant Node SSR worker importing the client adapter\n * would clobber it — that usage isn't supported.\n */\nlet chainHandles: ChainEntry[] = []\n\n/**\n * @internal — test helper. Disposes every layer in the current chain and\n * clears the module state so subsequent calls behave as a first mount.\n */\nexport function _resetChainForTest(): void {\n // Dispose innermost-first to match the normal teardown path.\n for (let i = chainHandles.length - 1; i >= 0; i--) {\n chainHandles[i]!.handle.dispose()\n }\n chainHandles = []\n _resetPendingSlot()\n}\n\n/**\n * Back-compat alias for the pre-layout test helper name.\n * @internal\n * @deprecated — use `_resetChainForTest` instead.\n */\nexport function _resetCurrentHandleForTest(): void {\n _resetChainForTest()\n}\n\n/**\n * Default onRenderClient hook — no layout, no animation hooks. Hydrates\n * on first load, mounts fresh on subsequent navs.\n */\nexport async function onRenderClient(pageContext: ClientPageContext): Promise<void> {\n await renderClient(pageContext, {})\n}\n\n/**\n * Factory to create a customized onRenderClient hook. See\n * `RenderClientOptions` for the full option surface.\n *\n * **Do not name your layout file `+Layout.ts`.** Vike reserves the `+`\n * prefix for its own framework config conventions. Name the file\n * `Layout.ts`, `app-layout.ts`, or anywhere outside `/pages` that Vike\n * won't scan, and import it here by path.\n */\nexport function createOnRenderClient(\n options: RenderClientOptions,\n): (pageContext: ClientPageContext) => Promise<void> {\n return (pageContext) => renderClient(pageContext, options)\n}\n\nasync function renderClient(\n pageContext: ClientPageContext,\n options: RenderClientOptions,\n): Promise<void> {\n const selector = options.container ?? '#app'\n const container = document.querySelector(selector)\n if (!container) {\n throw new Error(`@llui/vike: container \"${selector}\" not found in DOM`)\n }\n const rootEl = container as HTMLElement\n\n // Resolve the chain for this render. The page is always the innermost entry.\n const layoutChain = resolveLayoutChain(options.Layout, pageContext)\n const layoutData = pageContext.lluiLayoutData ?? []\n const newChain: LayoutChain = [...layoutChain, pageContext.Page]\n const newChainData: readonly unknown[] = [...layoutData, pageContext.data]\n\n if (pageContext.isHydration) {\n // First load — hydrate every layer against server-rendered HTML.\n mountChainSuffix(newChain, newChainData, 0, rootEl, undefined, {\n mode: 'hydrate',\n serverStateEnvelope: window.__LLUI_STATE__,\n runInitEffectsOnHydrate: options.runInitEffectsOnHydrate,\n })\n options.onMount?.(snapshotLayoutChain())\n return\n }\n\n // Subsequent nav — diff the layout chain to find the divergent suffix.\n //\n // The page (innermost entry) is NEVER a surviving layer: every client\n // navigation disposes the current page and mounts fresh, even when the\n // incoming Page resolves to the same def reference. The persistent-layout\n // feature is about keeping app chrome alive; the page is the thing that\n // changes per route.\n let firstMismatch = 0\n const prevLayoutLen = chainHandles.length === 0 ? 0 : chainHandles.length - 1\n const minLen = Math.min(prevLayoutLen, layoutChain.length)\n while (firstMismatch < minLen && chainHandles[firstMismatch]!.def === newChain[firstMismatch]) {\n firstMismatch++\n }\n\n // Push fresh data into surviving layers (the shared prefix). The\n // user-supplied `onLayerDataChange` receives the layer def, its handle,\n // and the new + previous data slices; it typically dispatches a message\n // through `handle.send`. Unchanged slices are skipped. Layouts-only by\n // construction (firstMismatch is bounded by layoutChain.length).\n for (let i = 0; i < firstMismatch; i++) {\n const entry = chainHandles[i]!\n const newData = newChainData[i]\n if (!hasDataChanged(entry.data, newData)) continue\n const prevData = entry.data\n entry.data = newData\n if (options.onLayerDataChange) {\n options.onLayerDataChange({ def: entry.def, handle: entry.handle, newData, prevData })\n }\n }\n\n // `firstMismatch === 0` means a root swap (no layouts, or all diverging);\n // otherwise the surviving layer at firstMismatch-1 owns the slot we mount into.\n const isRootSwap = firstMismatch === 0\n\n // onLeave runs BEFORE any teardown — outgoing DOM still mounted. Skip on the\n // very first mount (no outgoing page to leave).\n const isFirstMount = chainHandles.length === 0\n if (options.onLeave && !isFirstMount) {\n const leaveTargetEl = isRootSwap\n ? rootEl\n : (chainHandles[firstMismatch - 1]!.slotAnchor?.parentElement ?? rootEl)\n await options.onLeave(leaveTargetEl)\n }\n\n // Dispose the divergent suffix, innermost first. Each handle.dispose() runs\n // the layer's teardowns; anchor-mounted layers also remove their owned DOM\n // region (anchor → end sentinel). For a root swap the container is cleared\n // explicitly below since a container mount's dispose doesn't remove DOM.\n for (let i = chainHandles.length - 1; i >= firstMismatch; i--) {\n chainHandles[i]!.handle.dispose()\n }\n chainHandles = chainHandles.slice(0, firstMismatch)\n if (isRootSwap && !isFirstMount) rootEl.replaceChildren()\n\n // Mount the new suffix starting at firstMismatch.\n const mountTarget: HTMLElement | Comment =\n firstMismatch === 0 ? rootEl : chainHandles[firstMismatch - 1]!.slotAnchor!\n const mountContexts =\n firstMismatch === 0 ? undefined : (chainHandles[firstMismatch - 1]!.slotContexts ?? undefined)\n mountChainSuffix(newChain, newChainData, firstMismatch, mountTarget, mountContexts, {\n mode: 'mount',\n })\n\n // onEnter fires after the new suffix is in place. Fire-and-forget.\n if (options.onEnter) {\n const enterTargetEl = isRootSwap\n ? rootEl\n : (chainHandles[firstMismatch - 1]!.slotAnchor?.parentElement ?? rootEl)\n options.onEnter(enterTargetEl)\n }\n options.onMount?.(snapshotLayoutChain())\n}\n\n/**\n * Public read of the current layout chain — live `LayerHandle`s for\n * `[...layouts, page]`, outermost first. Empty before the first mount.\n */\nexport function getLayoutChain(): readonly LayerHandle[] {\n return snapshotLayoutChain()\n}\n\nfunction snapshotLayoutChain(): readonly LayerHandle[] {\n return chainHandles.map((entry) => entry.handle)\n}\n\ninterface MountOpts {\n mode: 'mount' | 'hydrate'\n /** For hydration: the full `window.__LLUI_STATE__` envelope. */\n serverStateEnvelope?: unknown\n /** Forwarded to the signal hydrate path. Mount mode ignores. */\n runInitEffectsOnHydrate?: boolean\n}\n\n/**\n * Mount (or hydrate) `chain[startAt..end]` into `initialTarget`, replaying\n * `initialContexts` into the first layer's build. Threads each layer's slot\n * (anchor + captured contexts) into the next layer's target + contexts.\n *\n * `initialTarget` is an `HTMLElement` for the outermost layer (container mount/\n * hydrate) and a `Comment` for inner layers mounting relative to a `pageSlot()`\n * anchor.\n *\n * Fails loudly if a non-innermost layer forgot to call `pageSlot()`, or if the\n * innermost layer called `pageSlot()` unnecessarily.\n *\n * @internal — test helper. Exported so `client-page-slot.test.ts` can exercise\n * anchor-mount/dispose contracts directly with hand-built DOM.\n */\nexport function _mountChainSuffix(\n chain: LayoutChain,\n chainData: readonly unknown[],\n startAt: number,\n initialTarget: HTMLElement | Comment,\n initialContexts: ReadonlyMap<symbol, unknown> | undefined,\n opts: MountOpts,\n): void {\n let mountTarget: HTMLElement | Comment = initialTarget\n let contexts: ReadonlyMap<symbol, unknown> | undefined = initialContexts\n\n for (let i = startAt; i < chain.length; i++) {\n const def = chain[i]!\n const layerData = chainData[i]\n const isInnermost = i === chain.length - 1\n\n // Defensive: clear any stale slot from a prior failed mount.\n _resetPendingSlot()\n\n const isContainer = mountTarget.nodeType === 1\n const target: Element | MountTarget = isContainer\n ? (mountTarget as HTMLElement)\n : { anchor: mountTarget as Comment, mode: opts.mode === 'hydrate' ? 'replace' : 'append' }\n\n let handle: LayerHandle\n if (opts.mode === 'hydrate') {\n // Each layer pulls its own state slice from the envelope, matched by name\n // so a server/client mismatch throws clearly instead of binding wrong state.\n const layerState = extractHydrationState(opts.serverStateEnvelope, i, chain.length, def)\n handle = hydrateSignalApp(target, def, layerState, {\n runInitEffects: opts.runInitEffectsOnHydrate,\n contexts,\n })\n } else {\n handle = mountSignalComponent(target, def, {\n initialState: seedFor(layerData),\n contexts,\n })\n }\n\n const slot = _consumePendingSlot()\n\n if (isInnermost && slot !== null) {\n handle.dispose()\n throw new Error(\n `[llui/vike] <${def.name}> is the innermost component in the chain ` +\n `but called pageSlot(). pageSlot() only belongs in layout components ` +\n `that wrap a nested page or layout — not in the page itself.`,\n )\n }\n if (!isInnermost && slot === null) {\n handle.dispose()\n throw new Error(\n `[llui/vike] <${def.name}> is a layout layer at depth ${i} but did not ` +\n `call pageSlot() in its view(). There are ${chain.length - i - 1} more ` +\n `layer(s) to mount and no slot to mount them into. Add pageSlot() from ` +\n `@llui/vike/client to the view at the position where nested content renders.`,\n )\n }\n\n chainHandles.push({\n def,\n handle,\n slotAnchor: slot?.anchor ?? null,\n slotContexts: slot?.contexts ?? null,\n data: layerData,\n })\n\n if (slot !== null) {\n // Next layer mounts relative to this slot's anchor, replaying the\n // contexts captured at the slot so providers above it stay reachable.\n mountTarget = slot.anchor\n contexts = slot.contexts\n }\n }\n}\n\n// Internal alias used by renderClient. The public-named export above carries\n// the @internal doc.\nconst mountChainSuffix = _mountChainSuffix\n\n/**\n * Shallow-key data diff for the surviving-layer prop-update path. Returns true\n * when `next` differs from `prev` enough to warrant dispatching the user's\n * `onLayerDataChange` hook.\n */\nfunction hasDataChanged(prev: unknown, next: unknown): boolean {\n if (Object.is(prev, next)) return false\n if (\n prev === null ||\n next === null ||\n typeof prev !== 'object' ||\n typeof next !== 'object' ||\n Array.isArray(prev) ||\n Array.isArray(next)\n ) {\n return true\n }\n const prevRec = prev as Record<string, unknown>\n const nextRec = next as Record<string, unknown>\n const seen = new Set<string>()\n for (const k of Object.keys(prevRec)) {\n seen.add(k)\n if (!Object.is(prevRec[k], nextRec[k])) return true\n }\n for (const k of Object.keys(nextRec)) {\n if (!seen.has(k)) return true\n }\n return false\n}\n\n/**\n * Pull the per-layer state from the hydration envelope. Supports the chain-aware\n * shape (`{ layouts: [...], page: {...} }`) and the legacy flat shape (the state\n * object itself) for a single-layer page-only render.\n *\n * Throws on envelope shape mismatch — missing entries, wrong component name at a\n * given index — so server/client drift fails loud.\n */\nfunction extractHydrationState(\n envelope: unknown,\n layerIndex: number,\n chainLength: number,\n def: AnyLayer,\n): unknown {\n const isLegacyFlat =\n envelope !== null &&\n typeof envelope === 'object' &&\n !('layouts' in (envelope as object)) &&\n !('page' in (envelope as object))\n\n if (isLegacyFlat) {\n if (chainLength !== 1) {\n throw new Error(\n `[llui/vike] Hydration envelope is in the legacy flat shape but the ` +\n `current render has ${chainLength} chain layers. The server must emit ` +\n `the chain-aware shape ({ layouts, page }) when rendering with a layout.`,\n )\n }\n return envelope\n }\n\n const chainEnvelope = envelope as\n | { layouts?: Array<{ name: string; state: unknown }>; page?: { name: string; state: unknown } }\n | undefined\n if (!chainEnvelope) {\n throw new Error(\n `[llui/vike] Hydration envelope is missing. Server-side onRenderHtml must ` +\n `populate window.__LLUI_STATE__ with the full chain before client hydration.`,\n )\n }\n\n const isPageLayer = layerIndex === chainLength - 1\n const layoutEntries = chainEnvelope.layouts ?? []\n const expected = isPageLayer ? chainEnvelope.page : layoutEntries[layerIndex]\n\n if (!expected) {\n throw new Error(\n `[llui/vike] Hydration envelope has no entry for chain layer ${layerIndex} ` +\n `(<${def.name}>). Server rendered ${layoutEntries.length} layouts + ${\n chainEnvelope.page ? 'a page' : 'no page'\n }, client expected ${chainLength} total entries.`,\n )\n }\n\n if (expected.name !== def.name) {\n throw new Error(\n `[llui/vike] Hydration mismatch at chain layer ${layerIndex}: server ` +\n `rendered <${expected.name}> but client is trying to hydrate <${def.name}>. ` +\n `This usually means the layout chain resolver returns different layouts ` +\n `on the server and the client for the same route.`,\n )\n }\n\n return expected.state\n}\n"]}
|
|
1
|
+
{"version":3,"file":"on-render-client.js","sourceRoot":"","sources":["../src/on-render-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { mountSignalComponent, hydrateSignalApp } from '@llui/dom'\nimport type { SignalComponentHandle, MountTarget } from '@llui/dom'\nimport type { TransitionOptions, Renderable } from '@llui/dom'\nimport { _consumePendingSlot, _resetPendingSlot } from './page-slot.js'\nimport type { VikePageContextData } from './vike-namespace.js'\n\n// Re-exported so `@llui/vike/client` is a one-stop-shop for everything\n// a pages/+onRenderClient.ts / Layout.ts file needs.\nexport { pageSlot } from './page-slot.js'\nexport { createNavigationProgress } from './nav-progress.js'\nexport type { NavigationProgress, NavigationProgressOptions } from './nav-progress.js'\n\n/** A type-erased signal component as the adapter handles it (type params unused\n * at runtime). Method syntax + a single `unknown` view-bag param so any concrete\n * `SignalComponentDef<S,M,E>` assigns in (see on-render-html's AnyLayer note —\n * ComponentBag's state/send variance rules out the `<unknown,…>` erasure). */\nexport interface AnyLayer {\n readonly name?: string\n init(): unknown\n update(state: unknown, msg: unknown): unknown\n view(bag: unknown): Renderable\n onEffect?(effect: unknown, api: unknown): void | (() => void)\n}\n/** The live handle a mounted/hydrated layer exposes (send/getState/subscribe). */\nexport type LayerHandle = SignalComponentHandle<unknown, unknown>\n\ndeclare global {\n interface Window {\n __LLUI_STATE__?: unknown\n }\n}\n\n/**\n * Page context shape as seen by `@llui/vike`'s client-side hooks. The\n * `Page` and `data` fields come from whichever `+Page.ts` and `+data.ts`\n * Vike resolved for the current route.\n *\n * `data` is derived from the global `Vike.PageContext` namespace — the\n * convention users already know from Vike. Consumer augmentations flow\n * through to every callback here without a cast; unaugmented projects\n * fall back to `unknown`.\n *\n * In the signal runtime a component's `init()` takes no data argument, so\n * each layer's `data` slice is used directly as that layer's seed STATE\n * when present; when absent, the layer's own `init()` provides the seed.\n *\n * `lluiLayoutData` is optional and carries per-layer data for the layout\n * chain configured via `createOnRenderClient({ Layout })`. It's indexed\n * outermost-to-innermost, one entry per layout layer.\n */\nexport interface ClientPageContext {\n Page: AnyLayer\n data?: VikePageContextData\n\n /**\n * Vike's resolved pathname for the current navigation (origin-, query- and\n * hash-stripped, e.g. `/docs/getting-started`). Vike always populates this on\n * the live pageContext; it's optional here only because not every test/SSR\n * construction site supplies it. Inside a `Layout` resolver it is guaranteed\n * present — see {@link LayoutResolverContext}.\n */\n urlPathname?: string\n\n /**\n * Vike's route params for the current route (e.g. `{ slug: 'intro' }` for a\n * `/docs/@slug` route). Empty object when the matched route has no params.\n * Guaranteed present inside a `Layout` resolver — see\n * {@link LayoutResolverContext}.\n */\n routeParams?: Record<string, string>\n\n lluiLayoutData?: readonly unknown[]\n isHydration?: boolean\n}\n\n/**\n * The pageContext a `Layout` **resolver function** receives. Identical to\n * {@link ClientPageContext} except Vike's routing fields (`urlPathname`,\n * `routeParams`) are guaranteed present — the resolver only ever runs against a\n * live Vike navigation, which always populates them. Typing them as required\n * here lets a route-scoped resolver read the route directly, with no cast:\n *\n * ```ts\n * Layout: (pageContext) =>\n * pageContext.urlPathname.startsWith('/docs')\n * ? [AppLayout, DocsLayout] // docs section keeps its sidebar mounted…\n * : [AppLayout], // …only the article re-mounts on docs→docs nav\n * ```\n *\n * The chain diff keeps every layer shared (by def reference) between the old\n * and new chain mounted, disposing only the divergent suffix — so navigating\n * `/docs/a → /docs/b` re-mounts just the page while `DocsLayout` (and its\n * sidebar, scroll position, focus, and effects) survives.\n */\nexport type LayoutResolverContext = ClientPageContext &\n Required<Pick<ClientPageContext, 'urlPathname' | 'routeParams'>>\n\ntype LayoutChain = ReadonlyArray<AnyLayer>\n\n/**\n * Resolves the layout chain for a given pageContext. A single layout\n * becomes a one-element chain; a function resolver gives callers full\n * control to return different chains for different routes.\n */\nfunction resolveLayoutChain(\n layoutOption: RenderClientOptions['Layout'],\n pageContext: ClientPageContext,\n): LayoutChain {\n if (!layoutOption) return []\n if (typeof layoutOption === 'function') {\n // The resolver only ever runs against a live Vike navigation, which always\n // populates `urlPathname`/`routeParams`. Our base type marks them optional\n // (test/SSR construction sites needn't supply them), so narrow to the\n // resolver's required view at this single boundary.\n return layoutOption(pageContext as LayoutResolverContext) ?? []\n }\n if (Array.isArray(layoutOption)) return layoutOption\n return [layoutOption as AnyLayer]\n}\n\n/** Resolve a layer's seed state — a present data slice IS the seed state\n * (signal init() takes no data); an absent slice falls back to init(). */\nfunction seedFor(data: unknown): unknown | undefined {\n return data === undefined ? undefined : data\n}\n\n/**\n * Page-lifecycle hooks that fire around the dispose → mount cycle on\n * client navigation. With persistent layouts in play the cycle only\n * tears down the *divergent* suffix of the layout chain — any layers\n * shared between the old and new routes stay mounted.\n *\n * On the initial hydration render, `onLeave` and `onEnter` are NOT\n * called — there's no outgoing page to leave and no animation to enter.\n * Use `onMount` for code that should run on every render including the\n * initial one.\n */\nexport interface RenderClientOptions {\n /** CSS selector for the mount container. Default: `'#app'`. */\n container?: string\n\n /**\n * Persistent layout chain. One of:\n *\n * - A single `SignalComponentDef` — becomes a one-layout chain.\n * - An array of `SignalComponentDef`s — outermost layout first,\n * innermost layout last. Every layer except the innermost must call\n * `pageSlot()` in its view to declare where nested content renders.\n * - A function that returns a chain from the current `pageContext`.\n *\n * Layers shared between the previous and next navigation stay mounted.\n * Only the divergent suffix is disposed and re-mounted.\n */\n Layout?: AnyLayer | LayoutChain | ((pageContext: LayoutResolverContext) => LayoutChain)\n\n /**\n * Called on the slot element whose contents are about to be replaced,\n * BEFORE the divergent suffix is disposed and re-mounted. The slot's\n * current DOM is still attached when this runs. Return a promise to\n * defer the swap until the animation completes.\n *\n * For a plain no-layout setup, the slot element is the root container.\n * Not called on the initial hydration render.\n *\n * **Not a loading hook.** `onLeave` fires after Vike has already fetched the\n * new page's `+data` — Vike only invokes `onRenderClient` once the incoming\n * pageContext is populated. So `onLeave`/`onEnter` bracket the DOM *swap*, not\n * the network *wait*; nothing here covers the during-fetch latency the user\n * perceives as lag. For a navigation-start signal (fires on the click, before\n * the round-trip) use {@link createNavigationProgress}.\n */\n onLeave?: (el: HTMLElement) => void | Promise<void>\n\n /**\n * Called after the new divergent suffix is mounted, on the same slot\n * element that was passed to `onLeave`. Fire-and-forget. Not called on\n * the initial hydration render.\n */\n onEnter?: (el: HTMLElement) => void\n\n /**\n * Called after mount or hydration completes. Fires on every render\n * including the initial hydration. Receives the live layout chain —\n * `[...layouts, page]`, outermost first — as `LayerHandle`s.\n */\n onMount?: (chain: readonly LayerHandle[]) => void\n\n /**\n * Called for each surviving layout layer whose `lluiLayoutData[i]`\n * slice changed across a client navigation. Surviving layers stay\n * mounted but need a fresh injection of nav-driven data. You decide how\n * to translate the new data into a message and dispatch it through\n * `handle.send(msg)`.\n *\n * Not called for unchanged slices, not on the initial hydration render,\n * and not for the page layer (it always disposes and remounts, so its\n * `init`/seed receives the fresh data directly).\n */\n onLayerDataChange?: (ctx: {\n def: AnyLayer\n handle: LayerHandle\n newData: unknown\n prevData: unknown\n }) => void\n\n /**\n * Forwarded to the signal hydrate path for every layer on initial\n * hydration. When `true`, effects returned by each component's `init()`\n * are dispatched post-swap on the client. When `false` (default), they\n * are skipped — the SSR pass already ran them.\n *\n * Subsequent client-side navigation always uses a fresh mount, which\n * always fires init effects regardless of this flag.\n */\n runInitEffectsOnHydrate?: boolean\n}\n\n/**\n * Adapt a `TransitionOptions` object into the `onLeave` / `onEnter` pair\n * expected by `createOnRenderClient`.\n *\n * ```ts\n * import { createOnRenderClient, fromTransition } from '@llui/vike/client'\n * import { routeTransition } from '@llui/transitions'\n *\n * export const onRenderClient = createOnRenderClient({\n * Layout: AppLayout,\n * ...fromTransition(routeTransition({ duration: 200 })),\n * })\n * ```\n *\n * The transition operates on the slot element — in a no-layout setup,\n * the root container; in a layout setup, the innermost surviving layer's\n * `pageSlot()` element.\n *\n * Like the underlying {@link RenderClientOptions.onLeave}/`onEnter`, the\n * transition brackets the DOM *swap*, which runs after Vike has fetched the new\n * page's `+data` — it does not animate over the network wait. For a\n * during-fetch loading indicator, pair it with {@link createNavigationProgress}.\n */\nexport function fromTransition(\n t: TransitionOptions,\n): Pick<RenderClientOptions, 'onLeave' | 'onEnter'> {\n return {\n onLeave: t.leave\n ? (el): void | Promise<void> => {\n const result = t.leave!([el])\n return result && typeof (result as Promise<void>).then === 'function'\n ? (result as Promise<void>)\n : undefined\n }\n : undefined,\n onEnter: t.enter\n ? (el): void => {\n t.enter!([el])\n }\n : undefined,\n }\n}\n\n/**\n * One element of the live chain the adapter keeps between navs.\n * `handle` is the SignalComponentHandle returned by mount/hydrate for\n * this layer. `slotAnchor` is set when the layer called `pageSlot()`\n * during its view pass; null for the innermost layer (the page, which\n * has no slot). `slotContexts` snapshots the context values in scope at\n * the slot, replayed into the nested layer's build.\n */\ninterface ChainEntry {\n def: AnyLayer\n handle: LayerHandle\n slotAnchor: Comment | null\n slotContexts: ReadonlyMap<symbol, unknown> | null\n /**\n * The data slice this layer was most recently mounted or updated with.\n * Compared shallow-key against the next nav's `lluiLayoutData[i]` to\n * decide whether a surviving layer needs `onLayerDataChange` to fire.\n */\n data: unknown\n}\n\n/**\n * Live chain of mounted layers — module-level singleton. Vike runs one\n * client-side adapter per browser tab; within one tab a single\n * `chainHandles` array holds the handle for every active layer, indexed\n * `[outermostLayout, ..., innerLayout, page]`. It mutates in place across\n * navigations: shared layout layers stay live, divergent suffix layers\n * dispose, new layers append.\n *\n * Module-level scope is correct for the browser (one consumer per page\n * load); a multi-tenant Node SSR worker importing the client adapter\n * would clobber it — that usage isn't supported.\n */\nlet chainHandles: ChainEntry[] = []\n\n/**\n * @internal — test helper. Disposes every layer in the current chain and\n * clears the module state so subsequent calls behave as a first mount.\n */\nexport function _resetChainForTest(): void {\n // Dispose innermost-first to match the normal teardown path.\n for (let i = chainHandles.length - 1; i >= 0; i--) {\n chainHandles[i]!.handle.dispose()\n }\n chainHandles = []\n _resetPendingSlot()\n}\n\n/**\n * Back-compat alias for the pre-layout test helper name.\n * @internal\n * @deprecated — use `_resetChainForTest` instead.\n */\nexport function _resetCurrentHandleForTest(): void {\n _resetChainForTest()\n}\n\n/**\n * Default onRenderClient hook — no layout, no animation hooks. Hydrates\n * on first load, mounts fresh on subsequent navs.\n */\nexport async function onRenderClient(pageContext: ClientPageContext): Promise<void> {\n await renderClient(pageContext, {})\n}\n\n/**\n * Factory to create a customized onRenderClient hook. See\n * `RenderClientOptions` for the full option surface.\n *\n * **Do not name your layout file `+Layout.ts`.** Vike reserves the `+`\n * prefix for its own framework config conventions. Name the file\n * `Layout.ts`, `app-layout.ts`, or anywhere outside `/pages` that Vike\n * won't scan, and import it here by path.\n */\nexport function createOnRenderClient(\n options: RenderClientOptions,\n): (pageContext: ClientPageContext) => Promise<void> {\n return (pageContext) => renderClient(pageContext, options)\n}\n\nasync function renderClient(\n pageContext: ClientPageContext,\n options: RenderClientOptions,\n): Promise<void> {\n const selector = options.container ?? '#app'\n const container = document.querySelector(selector)\n if (!container) {\n throw new Error(`@llui/vike: container \"${selector}\" not found in DOM`)\n }\n const rootEl = container as HTMLElement\n\n // Resolve the chain for this render. The page is always the innermost entry.\n const layoutChain = resolveLayoutChain(options.Layout, pageContext)\n const layoutData = pageContext.lluiLayoutData ?? []\n const newChain: LayoutChain = [...layoutChain, pageContext.Page]\n const newChainData: readonly unknown[] = [...layoutData, pageContext.data]\n\n if (pageContext.isHydration) {\n // First load — hydrate every layer against server-rendered HTML.\n mountChainSuffix(newChain, newChainData, 0, rootEl, undefined, {\n mode: 'hydrate',\n serverStateEnvelope: window.__LLUI_STATE__,\n runInitEffectsOnHydrate: options.runInitEffectsOnHydrate,\n })\n options.onMount?.(snapshotLayoutChain())\n return\n }\n\n // Subsequent nav — diff the layout chain to find the divergent suffix.\n //\n // The page (innermost entry) is NEVER a surviving layer: every client\n // navigation disposes the current page and mounts fresh, even when the\n // incoming Page resolves to the same def reference. The persistent-layout\n // feature is about keeping app chrome alive; the page is the thing that\n // changes per route.\n let firstMismatch = 0\n const prevLayoutLen = chainHandles.length === 0 ? 0 : chainHandles.length - 1\n const minLen = Math.min(prevLayoutLen, layoutChain.length)\n while (firstMismatch < minLen && chainHandles[firstMismatch]!.def === newChain[firstMismatch]) {\n firstMismatch++\n }\n\n // Push fresh data into surviving layers (the shared prefix). The\n // user-supplied `onLayerDataChange` receives the layer def, its handle,\n // and the new + previous data slices; it typically dispatches a message\n // through `handle.send`. Unchanged slices are skipped. Layouts-only by\n // construction (firstMismatch is bounded by layoutChain.length).\n for (let i = 0; i < firstMismatch; i++) {\n const entry = chainHandles[i]!\n const newData = newChainData[i]\n if (!hasDataChanged(entry.data, newData)) continue\n const prevData = entry.data\n entry.data = newData\n if (options.onLayerDataChange) {\n options.onLayerDataChange({ def: entry.def, handle: entry.handle, newData, prevData })\n }\n }\n\n // `firstMismatch === 0` means a root swap (no layouts, or all diverging);\n // otherwise the surviving layer at firstMismatch-1 owns the slot we mount into.\n const isRootSwap = firstMismatch === 0\n\n // onLeave runs BEFORE any teardown — outgoing DOM still mounted. Skip on the\n // very first mount (no outgoing page to leave).\n const isFirstMount = chainHandles.length === 0\n if (options.onLeave && !isFirstMount) {\n const leaveTargetEl = isRootSwap\n ? rootEl\n : (chainHandles[firstMismatch - 1]!.slotAnchor?.parentElement ?? rootEl)\n await options.onLeave(leaveTargetEl)\n }\n\n // Dispose the divergent suffix, innermost first. Each handle.dispose() runs\n // the layer's teardowns; anchor-mounted layers also remove their owned DOM\n // region (anchor → end sentinel). For a root swap the container is cleared\n // explicitly below since a container mount's dispose doesn't remove DOM.\n for (let i = chainHandles.length - 1; i >= firstMismatch; i--) {\n chainHandles[i]!.handle.dispose()\n }\n chainHandles = chainHandles.slice(0, firstMismatch)\n if (isRootSwap && !isFirstMount) rootEl.replaceChildren()\n\n // Mount the new suffix starting at firstMismatch.\n const mountTarget: HTMLElement | Comment =\n firstMismatch === 0 ? rootEl : chainHandles[firstMismatch - 1]!.slotAnchor!\n const mountContexts =\n firstMismatch === 0 ? undefined : (chainHandles[firstMismatch - 1]!.slotContexts ?? undefined)\n mountChainSuffix(newChain, newChainData, firstMismatch, mountTarget, mountContexts, {\n mode: 'mount',\n })\n\n // onEnter fires after the new suffix is in place. Fire-and-forget.\n if (options.onEnter) {\n const enterTargetEl = isRootSwap\n ? rootEl\n : (chainHandles[firstMismatch - 1]!.slotAnchor?.parentElement ?? rootEl)\n options.onEnter(enterTargetEl)\n }\n options.onMount?.(snapshotLayoutChain())\n}\n\n/**\n * Public read of the current layout chain — live `LayerHandle`s for\n * `[...layouts, page]`, outermost first. Empty before the first mount.\n */\nexport function getLayoutChain(): readonly LayerHandle[] {\n return snapshotLayoutChain()\n}\n\nfunction snapshotLayoutChain(): readonly LayerHandle[] {\n return chainHandles.map((entry) => entry.handle)\n}\n\ninterface MountOpts {\n mode: 'mount' | 'hydrate'\n /** For hydration: the full `window.__LLUI_STATE__` envelope. */\n serverStateEnvelope?: unknown\n /** Forwarded to the signal hydrate path. Mount mode ignores. */\n runInitEffectsOnHydrate?: boolean\n}\n\n/**\n * Mount (or hydrate) `chain[startAt..end]` into `initialTarget`, replaying\n * `initialContexts` into the first layer's build. Threads each layer's slot\n * (anchor + captured contexts) into the next layer's target + contexts.\n *\n * `initialTarget` is an `HTMLElement` for the outermost layer (container mount/\n * hydrate) and a `Comment` for inner layers mounting relative to a `pageSlot()`\n * anchor.\n *\n * Fails loudly if a non-innermost layer forgot to call `pageSlot()`, or if the\n * innermost layer called `pageSlot()` unnecessarily.\n *\n * @internal — test helper. Exported so `client-page-slot.test.ts` can exercise\n * anchor-mount/dispose contracts directly with hand-built DOM.\n */\nexport function _mountChainSuffix(\n chain: LayoutChain,\n chainData: readonly unknown[],\n startAt: number,\n initialTarget: HTMLElement | Comment,\n initialContexts: ReadonlyMap<symbol, unknown> | undefined,\n opts: MountOpts,\n): void {\n let mountTarget: HTMLElement | Comment = initialTarget\n let contexts: ReadonlyMap<symbol, unknown> | undefined = initialContexts\n\n for (let i = startAt; i < chain.length; i++) {\n const def = chain[i]!\n const layerData = chainData[i]\n const isInnermost = i === chain.length - 1\n\n // Defensive: clear any stale slot from a prior failed mount.\n _resetPendingSlot()\n\n const isContainer = mountTarget.nodeType === 1\n const target: Element | MountTarget = isContainer\n ? (mountTarget as HTMLElement)\n : { anchor: mountTarget as Comment, mode: opts.mode === 'hydrate' ? 'replace' : 'append' }\n\n let handle: LayerHandle\n if (opts.mode === 'hydrate') {\n // Each layer pulls its own state slice from the envelope, matched by name\n // so a server/client mismatch throws clearly instead of binding wrong state.\n const layerState = extractHydrationState(opts.serverStateEnvelope, i, chain.length, def)\n handle = hydrateSignalApp(target, def, layerState, {\n runInitEffects: opts.runInitEffectsOnHydrate,\n contexts,\n })\n } else {\n handle = mountSignalComponent(target, def, {\n initialState: seedFor(layerData),\n contexts,\n })\n }\n\n const slot = _consumePendingSlot()\n\n if (isInnermost && slot !== null) {\n handle.dispose()\n throw new Error(\n `[llui/vike] <${def.name}> is the innermost component in the chain ` +\n `but called pageSlot(). pageSlot() only belongs in layout components ` +\n `that wrap a nested page or layout — not in the page itself.`,\n )\n }\n if (!isInnermost && slot === null) {\n handle.dispose()\n throw new Error(\n `[llui/vike] <${def.name}> is a layout layer at depth ${i} but did not ` +\n `call pageSlot() in its view(). There are ${chain.length - i - 1} more ` +\n `layer(s) to mount and no slot to mount them into. Add pageSlot() from ` +\n `@llui/vike/client to the view at the position where nested content renders.`,\n )\n }\n\n chainHandles.push({\n def,\n handle,\n slotAnchor: slot?.anchor ?? null,\n slotContexts: slot?.contexts ?? null,\n data: layerData,\n })\n\n if (slot !== null) {\n // Next layer mounts relative to this slot's anchor, replaying the\n // contexts captured at the slot so providers above it stay reachable.\n mountTarget = slot.anchor\n contexts = slot.contexts\n }\n }\n}\n\n// Internal alias used by renderClient. The public-named export above carries\n// the @internal doc.\nconst mountChainSuffix = _mountChainSuffix\n\n/**\n * Shallow-key data diff for the surviving-layer prop-update path. Returns true\n * when `next` differs from `prev` enough to warrant dispatching the user's\n * `onLayerDataChange` hook.\n */\nfunction hasDataChanged(prev: unknown, next: unknown): boolean {\n if (Object.is(prev, next)) return false\n if (\n prev === null ||\n next === null ||\n typeof prev !== 'object' ||\n typeof next !== 'object' ||\n Array.isArray(prev) ||\n Array.isArray(next)\n ) {\n return true\n }\n const prevRec = prev as Record<string, unknown>\n const nextRec = next as Record<string, unknown>\n const seen = new Set<string>()\n for (const k of Object.keys(prevRec)) {\n seen.add(k)\n if (!Object.is(prevRec[k], nextRec[k])) return true\n }\n for (const k of Object.keys(nextRec)) {\n if (!seen.has(k)) return true\n }\n return false\n}\n\n/**\n * Pull the per-layer state from the hydration envelope. Supports the chain-aware\n * shape (`{ layouts: [...], page: {...} }`) and the legacy flat shape (the state\n * object itself) for a single-layer page-only render.\n *\n * Throws on envelope shape mismatch — missing entries, wrong component name at a\n * given index — so server/client drift fails loud.\n */\nfunction extractHydrationState(\n envelope: unknown,\n layerIndex: number,\n chainLength: number,\n def: AnyLayer,\n): unknown {\n const isLegacyFlat =\n envelope !== null &&\n typeof envelope === 'object' &&\n !('layouts' in (envelope as object)) &&\n !('page' in (envelope as object))\n\n if (isLegacyFlat) {\n if (chainLength !== 1) {\n throw new Error(\n `[llui/vike] Hydration envelope is in the legacy flat shape but the ` +\n `current render has ${chainLength} chain layers. The server must emit ` +\n `the chain-aware shape ({ layouts, page }) when rendering with a layout.`,\n )\n }\n return envelope\n }\n\n const chainEnvelope = envelope as\n | { layouts?: Array<{ name: string; state: unknown }>; page?: { name: string; state: unknown } }\n | undefined\n if (!chainEnvelope) {\n throw new Error(\n `[llui/vike] Hydration envelope is missing. Server-side onRenderHtml must ` +\n `populate window.__LLUI_STATE__ with the full chain before client hydration.`,\n )\n }\n\n const isPageLayer = layerIndex === chainLength - 1\n const layoutEntries = chainEnvelope.layouts ?? []\n const expected = isPageLayer ? chainEnvelope.page : layoutEntries[layerIndex]\n\n if (!expected) {\n throw new Error(\n `[llui/vike] Hydration envelope has no entry for chain layer ${layerIndex} ` +\n `(<${def.name}>). Server rendered ${layoutEntries.length} layouts + ${\n chainEnvelope.page ? 'a page' : 'no page'\n }, client expected ${chainLength} total entries.`,\n )\n }\n\n if (expected.name !== def.name) {\n throw new Error(\n `[llui/vike] Hydration mismatch at chain layer ${layerIndex}: server ` +\n `rendered <${expected.name}> but client is trying to hydrate <${def.name}>. ` +\n `This usually means the layout chain resolver returns different layouts ` +\n `on the server and the client for the same route.`,\n )\n }\n\n return expected.state\n}\n"]}
|
package/dist/page-slot.d.ts
CHANGED
|
@@ -29,6 +29,22 @@ interface PendingSlot {
|
|
|
29
29
|
* within the layout's own parent element; a synthesized end sentinel
|
|
30
30
|
* (`<!-- llui-mount-end -->`) brackets the owned region.
|
|
31
31
|
*
|
|
32
|
+
* **You may place your own siblings next to `pageSlot()`** — before it, or
|
|
33
|
+
* after it — in the same parent element (e.g. a navigation-loading bar beside
|
|
34
|
+
* the page inside `<main>`). The slot owns only the region between its anchor
|
|
35
|
+
* and the end sentinel, so on both SSR and every client navigation it
|
|
36
|
+
* inserts/disposes that region without touching your siblings. The
|
|
37
|
+
* `display: contents` wrapper that older guidance used to isolate the slot is
|
|
38
|
+
* **no longer needed** — drop it (regression-tested in
|
|
39
|
+
* `client-page-slot.test.ts` and `ssr-page-slot.test.ts`).
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* main([
|
|
43
|
+
* navBar(state.at('nav'), send), // ← your sibling survives navigation…
|
|
44
|
+
* pageSlot(), // …only this region swaps
|
|
45
|
+
* ])
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
32
48
|
* Contexts provided by the layout (via `provide()`) ABOVE the slot are
|
|
33
49
|
* reachable from inside the nested page: `pageSlot()` snapshots the
|
|
34
50
|
* in-scope context values and the adapter replays them into the nested
|
package/dist/page-slot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-slot.d.ts","sourceRoot":"","sources":["../src/page-slot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAS1C;;;;;;GAMG;AACH,UAAU,WAAW;IACnB,wEAAwE;IACxE,MAAM,EAAE,OAAO,CAAA;IACf;;;;;;OAMG;IACH,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACvC;AAID
|
|
1
|
+
{"version":3,"file":"page-slot.d.ts","sourceRoot":"","sources":["../src/page-slot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAS1C;;;;;;GAMG;AACH,UAAU,WAAW;IACnB,wEAAwE;IACxE,MAAM,EAAE,OAAO,CAAA;IACf;;;;;;OAMG;IACH,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACvC;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,wBAAgB,QAAQ,IAAI,SAAS,CAyBpC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,WAAW,GAAG,IAAI,CAIxD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
package/dist/page-slot.js
CHANGED
|
@@ -11,6 +11,22 @@ let pendingSlot = null;
|
|
|
11
11
|
* within the layout's own parent element; a synthesized end sentinel
|
|
12
12
|
* (`<!-- llui-mount-end -->`) brackets the owned region.
|
|
13
13
|
*
|
|
14
|
+
* **You may place your own siblings next to `pageSlot()`** — before it, or
|
|
15
|
+
* after it — in the same parent element (e.g. a navigation-loading bar beside
|
|
16
|
+
* the page inside `<main>`). The slot owns only the region between its anchor
|
|
17
|
+
* and the end sentinel, so on both SSR and every client navigation it
|
|
18
|
+
* inserts/disposes that region without touching your siblings. The
|
|
19
|
+
* `display: contents` wrapper that older guidance used to isolate the slot is
|
|
20
|
+
* **no longer needed** — drop it (regression-tested in
|
|
21
|
+
* `client-page-slot.test.ts` and `ssr-page-slot.test.ts`).
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* main([
|
|
25
|
+
* navBar(state.at('nav'), send), // ← your sibling survives navigation…
|
|
26
|
+
* pageSlot(), // …only this region swaps
|
|
27
|
+
* ])
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
14
30
|
* Contexts provided by the layout (via `provide()`) ABOVE the slot are
|
|
15
31
|
* reachable from inside the nested page: `pageSlot()` snapshots the
|
|
16
32
|
* in-scope context values and the adapter replays them into the nested
|
package/dist/page-slot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-slot.js","sourceRoot":"","sources":["../src/page-slot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AA8BzD,IAAI,WAAW,GAAuB,IAAI,CAAA;AAE1C
|
|
1
|
+
{"version":3,"file":"page-slot.js","sourceRoot":"","sources":["../src/page-slot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AA8BzD,IAAI,WAAW,GAAuB,IAAI,CAAA;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,MAAM,UAAU,QAAQ;IACtB,6EAA6E;IAC7E,8EAA8E;IAC9E,gFAAgF;IAChF,2BAA2B;IAC3B,OAAO,SAAS,CAAC,GAAG,EAAE;QACpB,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,uEAAuE;gBACrE,6EAA6E;gBAC7E,2EAA2E;gBAC3E,oEAAoE,CACvE,CAAA;QACH,CAAC;QACD,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAA;QACjC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CACb,+EAA+E;gBAC7E,6DAA6D,CAChE,CAAA;QACH,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,gBAAgB,CAAY,CAAA;QAClE,WAAW,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAA;QACjD,OAAO,MAAM,CAAA;IACf,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,GAAG,WAAW,CAAA;IACxB,WAAW,GAAG,IAAI,CAAA;IAClB,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,WAAW,GAAG,IAAI,CAAA;AACpB,CAAC","sourcesContent":["import { __currentBuildInfo, mountable } from '@llui/dom'\nimport type { Mountable } from '@llui/dom'\n\n// `@llui/dom`'s `__currentBuildInfo()` is the adapter-layer hook a\n// framework adapter like `@llui/vike` uses to participate in the signal build:\n// it exposes the in-progress build's `doc` (to create anchor nodes that belong\n// to the same document) plus a SNAPSHOT of the context values in scope at the\n// call site (so contexts provided ABOVE the slot reach the nested page's\n// separate build/mount pass). Not part of the public app-author API.\n\n/**\n * Transient handoff between a layout layer's render pass and the vike\n * adapter that's mounting the chain. `pageSlot()` populates this during\n * the layout's view() call; `_consumePendingSlot()` reads and clears it\n * immediately after the mount returns. One slot per mount pass — calling\n * `pageSlot()` twice in the same layout is a bug the primitive reports.\n */\ninterface PendingSlot {\n /** the slot's insertion anchor (a `<!-- llui-page-slot -->` comment) */\n anchor: Comment\n /**\n * Snapshot of the context values in scope at the `pageSlot()` call site.\n * The adapter replays these into the NESTED layer's build (via the signal\n * `contexts` mount/render option) so a layout-provided context — e.g. a\n * toast dispatcher provided ABOVE the slot — is reachable from inside the\n * nested page, even though the page builds in a separate pass.\n */\n contexts: ReadonlyMap<symbol, unknown>\n}\n\nlet pendingSlot: PendingSlot | null = null\n\n/**\n * Declare where a persistent layout renders its nested content — either\n * a nested layout or the route's page component. The vike adapter's\n * client and server render paths walk the layout chain, and each layer's\n * `pageSlot()` call records the position where the next layer mounts.\n *\n * Emits a single `<!-- llui-page-slot -->` comment as an insertion\n * anchor. The nested layer's DOM lives as siblings of this comment\n * within the layout's own parent element; a synthesized end sentinel\n * (`<!-- llui-mount-end -->`) brackets the owned region.\n *\n * **You may place your own siblings next to `pageSlot()`** — before it, or\n * after it — in the same parent element (e.g. a navigation-loading bar beside\n * the page inside `<main>`). The slot owns only the region between its anchor\n * and the end sentinel, so on both SSR and every client navigation it\n * inserts/disposes that region without touching your siblings. The\n * `display: contents` wrapper that older guidance used to isolate the slot is\n * **no longer needed** — drop it (regression-tested in\n * `client-page-slot.test.ts` and `ssr-page-slot.test.ts`).\n *\n * ```ts\n * main([\n * navBar(state.at('nav'), send), // ← your sibling survives navigation…\n * pageSlot(), // …only this region swaps\n * ])\n * ```\n *\n * Contexts provided by the layout (via `provide()`) ABOVE the slot are\n * reachable from inside the nested page: `pageSlot()` snapshots the\n * in-scope context values and the adapter replays them into the nested\n * layer's build. That's how patterns like a layout-owned toast\n * dispatcher work — the page does `useContext(ToastContext)` and reads\n * the value the layout provided above the slot.\n *\n * Do NOT name the file `+Layout.ts` — Vike reserves the `+` prefix for\n * its own framework config conventions. Use `Layout.ts`, `app-layout.ts`,\n * or anywhere outside `/pages` that Vike won't scan.\n *\n * ```ts\n * // pages/Layout.ts ← not +Layout.ts\n * import { component, div, main, header } from '@llui/dom'\n * import { pageSlot } from '@llui/vike/client'\n *\n * export const AppLayout = component<LayoutState, LayoutMsg>({\n * name: 'AppLayout',\n * init: () => ({ ... }),\n * update: layoutUpdate,\n * view: ({ send }) => [\n * div({ class: 'app-shell' }, [\n * header([...]),\n * main([pageSlot()]), // ← here the page goes (no wrapper div)\n * ]),\n * ],\n * })\n * ```\n *\n * Returns a `Mountable` (the slot's anchor comment) — drop it straight into\n * a children array (`main([pageSlot()])`); no spread needed.\n *\n * Call exactly once per layout. Calling more than once in a single\n * view throws (when both are placed).\n */\nexport function pageSlot(): Mountable {\n // Deferred to placement (like every authoring helper): the anchor is created\n // and the pending-slot handoff registered when the Mountable is materialized,\n // which still happens during the layout's view build — before the adapter reads\n // `_consumePendingSlot()`.\n return mountable(() => {\n if (pendingSlot !== null) {\n throw new Error(\n '[llui/vike] pageSlot() was called more than once in the same layout. ' +\n 'A layout has exactly one nested-content slot — if you need two independent ' +\n 'regions that swap on navigation, build them as sibling nested layouts in ' +\n 'the Vike routing tree and use context to share state between them.',\n )\n }\n const info = __currentBuildInfo()\n if (!info) {\n throw new Error(\n '[llui/vike] pageSlot() was called outside a signal build. It must run inside ' +\n 'a layout component view rendered by the @llui/vike adapter.',\n )\n }\n const anchor = info.doc.createComment('llui-page-slot') as Comment\n pendingSlot = { anchor, contexts: info.contexts }\n return anchor\n })\n}\n\n/**\n * @internal — vike adapter only. Read and clear the slot registered by\n * the most recent `pageSlot()` call. Returns null if the layer being\n * mounted didn't call `pageSlot()` (meaning it's the innermost layer\n * and owns no nested content).\n */\nexport function _consumePendingSlot(): PendingSlot | null {\n const slot = pendingSlot\n pendingSlot = null\n return slot\n}\n\n/**\n * @internal — vike adapter only. Reset the pending slot without reading\n * it. Used defensively in error paths to avoid leaking a pending slot\n * registration into a subsequent mount attempt.\n */\nexport function _resetPendingSlot(): void {\n pendingSlot = null\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llui/vike",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dist"
|
|
22
22
|
],
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@llui/dom": "^0.11.
|
|
24
|
+
"@llui/dom": "^0.11.4",
|
|
25
25
|
"jsdom": "*"
|
|
26
26
|
},
|
|
27
27
|
"peerDependenciesMeta": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@llui/dom": "0.11.
|
|
33
|
+
"@llui/dom": "0.11.4"
|
|
34
34
|
},
|
|
35
35
|
"description": "LLui Vike SSR adapter — onRenderHtml, onRenderClient hooks",
|
|
36
36
|
"keywords": [
|