@uniweb/runtime 0.8.15 → 0.8.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/runtime",
3
- "version": "0.8.15",
3
+ "version": "0.8.17",
4
4
  "description": "Minimal runtime for loading Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -35,14 +35,15 @@
35
35
  "node": ">=20.19"
36
36
  },
37
37
  "dependencies": {
38
- "@uniweb/core": "0.7.12",
39
- "@uniweb/theming": "0.1.3"
38
+ "@uniweb/theming": "0.1.3",
39
+ "@uniweb/core": "0.7.12"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@vitejs/plugin-react": "^4.5.2",
43
+ "esbuild": "^0.21.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.27.0",
43
44
  "vite": "^7.3.1",
44
45
  "vitest": "^4.1.7",
45
- "@uniweb/build": "0.14.5"
46
+ "@uniweb/build": "0.14.11"
46
47
  },
47
48
  "peerDependencies": {
48
49
  "react": "^18.0.0 || ^19.0.0",
@@ -53,6 +54,7 @@
53
54
  "build:ssr": "vite build --config vite.config.ssr.js",
54
55
  "build:app": "vite build --config vite.config.app.js",
55
56
  "build": "npm run build:ssr && npm run build:app",
57
+ "build:worker": "node build-worker.js",
56
58
  "test": "vitest run",
57
59
  "test:watch": "vitest"
58
60
  }
@@ -32,6 +32,7 @@
32
32
  */
33
33
 
34
34
  import Blocks from './Blocks.jsx'
35
+ import { resolveLayoutTransitions } from '../view-transitions.js'
35
36
 
36
37
  /**
37
38
  * Default layout - renders header, body, footer in sequence
@@ -91,9 +92,13 @@ export default function Layout({ page, website }) {
91
92
  initializeAllBlocks(...allBlockGroups)
92
93
 
93
94
  // Pre-render each area as React elements.
94
- // When the foundation enables view transitions, wrap areas in thin divs
95
- // with view-transition-name so the browser can animate them independently.
96
- const transitions = website.viewTransitions ? layoutMeta?.transitions : null
95
+ // When the foundation enables view transitions, wrap areas in thin divs with
96
+ // view-transition-name so the browser can animate them independently. Names
97
+ // default to one per area + body (see resolveLayoutTransitions); the layout's
98
+ // `transitions` meta overrides or opts out.
99
+ const transitions = website.viewTransitions
100
+ ? resolveLayoutTransitions(Object.keys(areas), layoutMeta?.transitions)
101
+ : null
97
102
 
98
103
  const bodyElement = bodyBlocks ? (
99
104
  transitions?.body
package/src/setup.js CHANGED
@@ -217,9 +217,13 @@ function createIconResolver(iconConfig = {}) {
217
217
  // ─── Runtime default fetcher ────────────────────────────────────────────────
218
218
 
219
219
  function buildDefaultFetcher(content) {
220
- // BASE_URL is injected by Vite at build. For subpath deployments the default
221
- // fetcher prepends it to local absolute paths (remote URLs are never touched).
222
- const basePath = import.meta.env?.BASE_URL || ''
220
+ // Base for resolving local absolute paths (e.g. /data/*.json). Prefer the base
221
+ // the PAYLOAD carries (`content.config.base`) a backend-hosted SPA is served
222
+ // under a subpath the payload declares (e.g. /gateway/site/<uuid>/) that the
223
+ // gateway-delivered runtime bundle can't know via Vite's build-time BASE_URL.
224
+ // This is the SAME base routing uses; without it local fetches hit the origin
225
+ // root and 404. Falls back to Vite's BASE_URL for a statically built site.
226
+ const basePath = content?.config?.base || import.meta.env?.BASE_URL || ''
223
227
  // Per-site transport config from `site.yml fetcher:`. The default fetcher
224
228
  // recognizes `baseUrl` and `envelope`; foundations with their own fetchers
225
229
  // may read additional keys from the same block via ctx.website.config.fetcher.
@@ -25,6 +25,7 @@ import {
25
25
  sliceContentForLocale,
26
26
  hydrateDataStore,
27
27
  } from './wire-foundation.js'
28
+ import { resolveLayoutTransitions } from './view-transitions.js'
28
29
 
29
30
  // Re-export L2 helpers so the public `@uniweb/runtime/ssr` surface
30
31
  // carries everything an SSR consumer needs from one entry point.
@@ -343,10 +344,23 @@ export function renderLayout(page, website) {
343
344
  const bodyBlocks = page.getBodyBlocks()
344
345
  const areas = page.getLayoutAreas()
345
346
 
346
- const bodyElement = bodyBlocks ? renderBlocks(bodyBlocks) : null
347
+ // Mirror Layout.jsx: wrap body + each area in a thin div carrying its
348
+ // view-transition-name, so the prerendered HTML matches what the SPA hydrates
349
+ // and the browser can animate regions independently on client navigation.
350
+ const transitions = website.viewTransitions
351
+ ? resolveLayoutTransitions(Object.keys(areas), layoutMeta?.transitions)
352
+ : null
353
+ const wrapTransition = (name, element) => {
354
+ const vtName = transitions?.[name]
355
+ return vtName
356
+ ? React.createElement('div', { style: { viewTransitionName: vtName } }, element)
357
+ : element
358
+ }
359
+
360
+ const bodyElement = bodyBlocks ? wrapTransition('body', renderBlocks(bodyBlocks)) : null
347
361
  const areaElements = {}
348
362
  for (const [name, blocks] of Object.entries(areas)) {
349
- areaElements[name] = renderBlocks(blocks)
363
+ areaElements[name] = wrapTransition(name, renderBlocks(blocks))
350
364
  }
351
365
 
352
366
  if (RemoteLayout) {
@@ -0,0 +1,47 @@
1
+ /**
2
+ * View-transition name resolution for layout areas.
3
+ *
4
+ * When a foundation enables view transitions (the default), the runtime gives
5
+ * each layout region a `view-transition-name` so the browser animates them
6
+ * independently — persistent chrome (header, sidebar, footer) morphs in place
7
+ * while the body crossfades. Without per-region names the browser falls back to
8
+ * a single full-page crossfade, which makes the whole layout (chrome included)
9
+ * flicker on every navigation.
10
+ *
11
+ * This module is pure (no React/DOM) so both the SPA renderer
12
+ * (`components/Layout.jsx`) and the SSR renderer (`ssr-renderer.js`) derive the
13
+ * exact same names — keeping the prerendered HTML and the hydrated SPA aligned.
14
+ */
15
+
16
+ // Namespace so generated names can't collide with `view-transition-name`s a
17
+ // foundation sets inside its own component CSS. The prefix also guarantees a
18
+ // valid CSS <custom-ident> (starts with a letter).
19
+ const NS = 'uw-'
20
+
21
+ const toIdent = (name) => NS + String(name).replace(/[^a-zA-Z0-9_-]/g, '-')
22
+
23
+ /**
24
+ * Build the effective view-transition-name map for a layout.
25
+ *
26
+ * Default: every rendered area plus the implicit `body` gets a stable,
27
+ * namespaced name (`uw-<area>`, `uw-body`). Same-named areas across layouts
28
+ * therefore share a name and morph between layouts automatically.
29
+ *
30
+ * The layout's `meta.js` `transitions` value overrides this:
31
+ * - an object overrides per region (`{ left: 'sidebar' }` to group across
32
+ * layouts, or `{ left: null }` to opt one region out);
33
+ * - `false` opts the whole layout out (back to the full-page crossfade).
34
+ *
35
+ * @param {string[]} areaNames - Names of the areas rendered for this page (excludes `body`).
36
+ * @param {Object|false|null|undefined} explicit - `layoutMeta.transitions`.
37
+ * @returns {Object|null} region → view-transition-name; `null` when opted out.
38
+ * A region whose value is null/empty in the returned map gets no name.
39
+ */
40
+ export function resolveLayoutTransitions(areaNames, explicit) {
41
+ if (explicit === false) return null
42
+
43
+ const transitions = { body: toIdent('body') }
44
+ for (const name of areaNames) transitions[name] = toIdent(name)
45
+
46
+ return explicit ? { ...transitions, ...explicit } : transitions
47
+ }