@uniweb/runtime 1.1.18 → 1.1.20
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 +3 -3
- package/src/hooks/useLinkInterceptor.js +46 -26
- package/src/setup.js +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/runtime",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.20",
|
|
4
4
|
"description": "Minimal runtime for loading Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"node": ">=22.12"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@uniweb/core": "~1.4.
|
|
39
|
+
"@uniweb/core": "~1.4.4",
|
|
40
40
|
"@uniweb/theming": "~1.2.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"esbuild": "^0.21.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.27.0 || ^0.28.0",
|
|
45
45
|
"vite": "^7.3.1",
|
|
46
46
|
"vitest": "^4.1.7",
|
|
47
|
-
"@uniweb/build": "1.8.
|
|
47
|
+
"@uniweb/build": "1.8.5"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"react": "^19.0.0",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { useEffect, useCallback } from 'react'
|
|
16
16
|
import { useNavigate, useLocation } from 'react-router-dom'
|
|
17
|
+
import { localeOfPath } from '@uniweb/core/resolve-route'
|
|
17
18
|
import { prefetchContent, prefersReducedMotion } from '../setup.js'
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -74,6 +75,24 @@ function findAnchorElement(target) {
|
|
|
74
75
|
return null
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
/**
|
|
79
|
+
* The locale and page a link goes to when it leaves the active locale, or null.
|
|
80
|
+
*
|
|
81
|
+
* Which locale a path names is the hosts' rule (`localeOfPath`): a served locale's prefix,
|
|
82
|
+
* else the locale served unprefixed. The page is the route that locale shows, made canonical.
|
|
83
|
+
* It reads a path only; the click handler resolves every href to one first.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} routeHref - Where the link goes, as a path, base path removed
|
|
86
|
+
* @param {Object} website - The active Website
|
|
87
|
+
* @returns {{ locale: string, route: string } | null}
|
|
88
|
+
*/
|
|
89
|
+
export function localeSwitchOf(routeHref, website) {
|
|
90
|
+
if (!website?.hasMultipleLocales() || !routeHref.startsWith('/') || routeHref.startsWith('//')) return null
|
|
91
|
+
const { locale, path } = localeOfPath(routeHref.split(/[?#]/)[0], website.config)
|
|
92
|
+
if (locale === website.getActiveLocale()) return null
|
|
93
|
+
return { locale, route: website.reverseTranslateRoute(path, locale) }
|
|
94
|
+
}
|
|
95
|
+
|
|
77
96
|
/**
|
|
78
97
|
* Scroll to element by ID with retry logic
|
|
79
98
|
* Retries a few times to handle elements that render asynchronously
|
|
@@ -144,48 +163,49 @@ export function useLinkInterceptor(options = {}) {
|
|
|
144
163
|
// Check for download attribute
|
|
145
164
|
if (anchor.hasAttribute('download')) return
|
|
146
165
|
|
|
147
|
-
// Opt out: anchors marked for full-page reload (e.g., <Link reload>)
|
|
148
|
-
if (anchor.hasAttribute('data-reload')) return
|
|
149
|
-
|
|
150
166
|
// Check for target="_blank" or other non-self targets
|
|
151
167
|
const target = anchor.getAttribute('target')
|
|
152
168
|
if (target && target !== '_self') return
|
|
153
169
|
|
|
154
|
-
// Strip basePath from href for internal processing
|
|
155
|
-
// Links from <Link reload> include the basePath, but React Router
|
|
156
|
-
// and locale detection work with router-relative paths
|
|
157
170
|
const website = globalThis.uniweb?.activeWebsite
|
|
158
171
|
const basePath = website?.basePath || ''
|
|
159
172
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
173
|
+
// Where the browser would go, as a path: the href resolved against this page's URL, so
|
|
174
|
+
// a query (`?tab=2`) or a relative href (`about`) is judged like any other path. The
|
|
175
|
+
// base path comes off, because React Router and locale detection work with
|
|
176
|
+
// router-relative paths (a <Link reload> carries the base).
|
|
177
|
+
//
|
|
178
|
+
// ⛔ Until 2026-10-24 an href that was not a path counted as the default locale's, so
|
|
179
|
+
// on any other locale's page it was a page load: a query or a relative link reloaded
|
|
180
|
+
// the page, and left an editor's preview frame.
|
|
181
|
+
const resolved = new URL(href, window.location.href)
|
|
182
|
+
let routeHref = resolved.pathname + resolved.search + resolved.hash
|
|
183
|
+
if (basePath && routeHref.startsWith(basePath)) {
|
|
184
|
+
routeHref = routeHref.slice(basePath.length) || '/'
|
|
163
185
|
}
|
|
164
186
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (hrefLocale && localeCodes.includes(hrefLocale)) {
|
|
175
|
-
isLocaleSwitch = hrefLocale !== activeLocale
|
|
176
|
-
} else {
|
|
177
|
-
// No locale prefix = targets default locale
|
|
178
|
-
isLocaleSwitch = activeLocale !== defaultLocale
|
|
187
|
+
// A link to another locale's page is a page load, since that locale is its own document,
|
|
188
|
+
// and the browser makes it. A host that switches locale its own way (`uniweb.localeSwitch`:
|
|
189
|
+
// an editor's preview, which a page load would leave) is handed it instead, a
|
|
190
|
+
// `<Link reload>` from a language menu and a plain link to `/fr/…` alike.
|
|
191
|
+
const switchTo = localeSwitchOf(routeHref, website)
|
|
192
|
+
if (switchTo) {
|
|
193
|
+
if (globalThis.uniweb?.localeSwitch) {
|
|
194
|
+
event.preventDefault()
|
|
195
|
+
website.switchLocale(switchTo.locale, switchTo.route)
|
|
179
196
|
}
|
|
180
|
-
|
|
197
|
+
return
|
|
181
198
|
}
|
|
182
199
|
|
|
200
|
+
// Opt out: anchors marked for full-page reload (e.g., <Link reload>)
|
|
201
|
+
if (anchor.hasAttribute('data-reload')) return
|
|
202
|
+
|
|
183
203
|
// Prevent the default browser navigation
|
|
184
204
|
event.preventDefault()
|
|
185
205
|
|
|
186
206
|
// Handle hash-only links (same page scroll)
|
|
187
|
-
if (
|
|
188
|
-
const elementId =
|
|
207
|
+
if (href.startsWith('#')) {
|
|
208
|
+
const elementId = href.slice(1)
|
|
189
209
|
if (elementId) {
|
|
190
210
|
scrollToElement(elementId)
|
|
191
211
|
// Update URL hash without navigation
|
package/src/setup.js
CHANGED
|
@@ -269,9 +269,15 @@ function buildDefaultFetcher(content) {
|
|
|
269
269
|
* through this transport, bypassing foundation routes and the framework
|
|
270
270
|
* default. Used only by the editor's preview iframe — normal sites never
|
|
271
271
|
* pass this option.
|
|
272
|
+
* @param {(target: { locale: string, route: string, url: string }) => void} [options.localeSwitch] -
|
|
273
|
+
* Locale switch override: called with the locale, the page's canonical route and
|
|
274
|
+
* the URL a page load would go to, instead of the page load
|
|
275
|
+
* (`Website#switchLocale`; a click on a link to another locale's page too). For a
|
|
276
|
+
* host that hands the runtime each locale's content itself, like the editor's
|
|
277
|
+
* preview iframe, which a page load would leave.
|
|
272
278
|
* @returns {import('@uniweb/core').Uniweb}
|
|
273
279
|
*/
|
|
274
|
-
export function initUniweb({ content, foundation, extensions = [], routingComponents = DEFAULT_ROUTING_COMPONENTS, transport = null }) {
|
|
280
|
+
export function initUniweb({ content, foundation, extensions = [], routingComponents = DEFAULT_ROUTING_COMPONENTS, transport = null, localeSwitch = null }) {
|
|
275
281
|
const defaultFetcher = buildDefaultFetcher(content)
|
|
276
282
|
const dev = !!(import.meta.env && import.meta.env.DEV)
|
|
277
283
|
const uniweb = createUniweb(content, foundation, extensions, { defaultFetcher, transport, dev })
|
|
@@ -308,6 +314,7 @@ export function initUniweb({ content, foundation, extensions = [], routingCompon
|
|
|
308
314
|
}
|
|
309
315
|
|
|
310
316
|
uniweb.routingComponents = routingComponents
|
|
317
|
+
uniweb.localeSwitch = localeSwitch
|
|
311
318
|
// `config.icons`, NOT `content.icons` — the latter is the build's manifest
|
|
312
319
|
// (`{used, families, …}`), which carries no `cdnUrl`, so passing it here
|
|
313
320
|
// pinned every browser fetch to the built-in default and made `icons.cdnUrl`
|