@syntrologie/adapt-nav 2.8.0-canary.4 → 2.8.0-canary.41
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/dist/NavWidget.d.ts.map +1 -1
- package/dist/NavWidget.js +95 -8
- package/dist/cdn.d.ts.map +1 -1
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +61 -3
- package/dist/schema.d.ts +270 -56
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +5 -3
- package/dist/types.d.ts +8 -1
- package/dist/types.d.ts.map +1 -1
- package/node_modules/@syntro/design-system/package.json +2 -2
- package/node_modules/@syntrologie/sdk-contracts/package.json +2 -2
- package/node_modules/@syntrologie/shared-editor-ui/dist/components/AnchorPicker.js +2 -4
- package/node_modules/@syntrologie/shared-editor-ui/dist/components/ElementHighlight.js +2 -4
- package/node_modules/@syntrologie/shared-editor-ui/dist/hooks/useTriggerWhenStatus.d.ts.map +1 -1
- package/node_modules/@syntrologie/shared-editor-ui/dist/hooks/useTriggerWhenStatus.js +0 -1
- package/node_modules/@syntrologie/shared-editor-ui/package.json +11 -9
- package/package.json +9 -9
package/dist/NavWidget.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavWidget.d.ts","sourceRoot":"","sources":["../src/NavWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"NavWidget.d.ts","sourceRoot":"","sources":["../src/NavWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,OAAO,KAAK,EAAE,SAAS,EAAgB,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAgVzF;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,cAAc,2CA8NxE;AAMD;;GAEG;AACH,eAAO,MAAM,kBAAkB;qBAEhB,WAAW,WACb,SAAS,GAAG;QAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;CAiD3E,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
package/dist/NavWidget.js
CHANGED
|
@@ -11,6 +11,24 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
11
|
import { purple, slateGrey } from '@syntro/design-system/tokens';
|
|
12
12
|
import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
|
|
13
13
|
import { createRoot } from 'react-dom/client';
|
|
14
|
+
import { navigateWithFrameworkRouter } from './runtime';
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Emoji → Lucide SVG inline mapping (no lucide-react dependency)
|
|
17
|
+
// ============================================================================
|
|
18
|
+
const EMOJI_SVG_MAP = {
|
|
19
|
+
'💵': '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="12" x="2" y="6" rx="2"/><circle cx="12" cy="12" r="2"/><path d="M6 12h.01M18 12h.01"/></svg>',
|
|
20
|
+
'🏛️': '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" x2="21" y1="22" y2="22"/><line x1="6" x2="6" y1="18" y2="11"/><line x1="10" x2="10" y1="18" y2="11"/><line x1="14" x2="14" y1="18" y2="11"/><line x1="18" x2="18" y1="18" y2="11"/><polygon points="12 2 20 7 4 7"/></svg>',
|
|
21
|
+
'⏭️': '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 4 15 12 5 20 5 4"/><line x1="19" x2="19" y1="5" y2="19"/></svg>',
|
|
22
|
+
'➡️': '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>',
|
|
23
|
+
'💡': '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/></svg>',
|
|
24
|
+
'💰': '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="12" x="2" y="6" rx="2"/><circle cx="12" cy="12" r="2"/><path d="M6 12h.01M18 12h.01"/></svg>',
|
|
25
|
+
'📋': '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/></svg>',
|
|
26
|
+
'✅': '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg>',
|
|
27
|
+
'⚠️': '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
|
|
28
|
+
};
|
|
29
|
+
function renderIcon(emoji) {
|
|
30
|
+
return EMOJI_SVG_MAP[emoji] ?? escapeHtml(emoji);
|
|
31
|
+
}
|
|
14
32
|
// ============================================================================
|
|
15
33
|
// Sanitization
|
|
16
34
|
// ============================================================================
|
|
@@ -171,10 +189,36 @@ const themeStyles = {
|
|
|
171
189
|
},
|
|
172
190
|
},
|
|
173
191
|
};
|
|
174
|
-
|
|
192
|
+
// ============================================================================
|
|
193
|
+
// Helpers
|
|
194
|
+
// ============================================================================
|
|
195
|
+
/** Check if any of the given routes match the current page pathname */
|
|
196
|
+
function routeMatchesCurrent(routes) {
|
|
197
|
+
if (typeof window === 'undefined')
|
|
198
|
+
return false;
|
|
199
|
+
const current = window.location.pathname;
|
|
200
|
+
return routes.some((route) => {
|
|
201
|
+
// Strip query/hash from route if present
|
|
202
|
+
const routePath = route.split('?')[0].split('#')[0];
|
|
203
|
+
// Exact match or glob pattern (** suffix)
|
|
204
|
+
if (routePath.endsWith('/**')) {
|
|
205
|
+
return current.startsWith(routePath.slice(0, -3));
|
|
206
|
+
}
|
|
207
|
+
return current === routePath;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/** Apply a brief pulse animation to an element, then remove it */
|
|
211
|
+
function pulseElement(el) {
|
|
212
|
+
const keyframes = [
|
|
213
|
+
{ boxShadow: '0 0 0 0 rgba(13, 148, 136, 0.5)' },
|
|
214
|
+
{ boxShadow: '0 0 0 8px rgba(13, 148, 136, 0)' },
|
|
215
|
+
];
|
|
216
|
+
el.animate(keyframes, { duration: 600, iterations: 3, easing: 'ease-out' });
|
|
217
|
+
}
|
|
218
|
+
function NavTipItem({ item, isExpanded, isLast, onToggle, onNavigate, onFocusAnchor, theme, }) {
|
|
175
219
|
const [isHovered, setIsHovered] = useState(false);
|
|
176
220
|
const colors = themeStyles[theme];
|
|
177
|
-
const { title, description, href, icon, external } = item.config;
|
|
221
|
+
const { title, description, href, icon, external, anchor } = item.config;
|
|
178
222
|
const itemStyle = {
|
|
179
223
|
...baseStyles.item,
|
|
180
224
|
...colors.item,
|
|
@@ -196,14 +240,36 @@ function NavTipItem({ item, isExpanded, isLast, onToggle, onNavigate, theme }) {
|
|
|
196
240
|
maxHeight: isExpanded ? '500px' : '0',
|
|
197
241
|
paddingBottom: isExpanded ? '16px' : '0',
|
|
198
242
|
};
|
|
243
|
+
// Determine the effective navigation target from anchor or legacy href
|
|
244
|
+
const effectiveHref = anchor
|
|
245
|
+
? Array.isArray(anchor.route)
|
|
246
|
+
? anchor.route[0]
|
|
247
|
+
: anchor.route
|
|
248
|
+
: href;
|
|
249
|
+
// Same-page check: anchor exists, selector is meaningful, and route matches current page
|
|
250
|
+
const isSamePage = anchor
|
|
251
|
+
? routeMatchesCurrent(Array.isArray(anchor.route) ? anchor.route : [anchor.route])
|
|
252
|
+
: effectiveHref
|
|
253
|
+
? routeMatchesCurrent([effectiveHref])
|
|
254
|
+
: false;
|
|
255
|
+
const hasSelector = anchor?.selector && anchor.selector !== '*';
|
|
256
|
+
const isFocusAction = isSamePage && hasSelector;
|
|
257
|
+
const hasAction = !!effectiveHref || isFocusAction;
|
|
199
258
|
const handleLinkClick = (e) => {
|
|
200
259
|
e.preventDefault();
|
|
201
260
|
e.stopPropagation();
|
|
202
|
-
if (
|
|
203
|
-
|
|
261
|
+
if (isFocusAction && anchor) {
|
|
262
|
+
onFocusAnchor(anchor);
|
|
263
|
+
}
|
|
264
|
+
else if (effectiveHref) {
|
|
265
|
+
onNavigate(effectiveHref, external ?? false);
|
|
204
266
|
}
|
|
205
267
|
};
|
|
206
|
-
|
|
268
|
+
// CTA label
|
|
269
|
+
const ctaLabel = isFocusAction ? `Focus \u2192` : external ? `Go \u2197` : `Go \u2192`;
|
|
270
|
+
return (_jsxs("div", { style: itemStyle, "data-nav-tip-id": item.config.id, children: [_jsxs("button", { type: "button", style: headerStyle, onClick: onToggle, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), "aria-expanded": isExpanded, children: [icon && (
|
|
271
|
+
// biome-ignore lint/security/noDangerouslySetInnerHtml: renderIcon returns sanitized SVG from EMOJI_SVG_MAP or escapeHtml
|
|
272
|
+
_jsx("span", { style: baseStyles.icon, dangerouslySetInnerHTML: { __html: renderIcon(icon) } })), _jsx("span", { children: title }), _jsx("span", { style: chevronStyle, children: '\u203A' })] }), _jsxs("div", { style: bodyStyle, "aria-hidden": !isExpanded, children: [_jsx("p", { style: baseStyles.description, children: description }), hasAction && (_jsx("a", { href: effectiveHref || '#', onClick: handleLinkClick, style: { ...baseStyles.linkButton, ...colors.linkButton }, target: external ? '_blank' : undefined, rel: external ? 'noopener noreferrer' : undefined, children: ctaLabel }))] })] }));
|
|
207
273
|
}
|
|
208
274
|
// ============================================================================
|
|
209
275
|
// NavWidget Component
|
|
@@ -329,11 +395,32 @@ export function NavWidget({ config, runtime, instanceId }) {
|
|
|
329
395
|
window.open(href, '_blank', 'noopener,noreferrer');
|
|
330
396
|
}
|
|
331
397
|
else {
|
|
398
|
+
// Try the host framework's native router first (Next.js, Nuxt, etc.)
|
|
399
|
+
// Falls back to pushState for vanilla SPAs.
|
|
332
400
|
const url = new URL(href, window.location.origin);
|
|
333
401
|
url.search = window.location.search;
|
|
334
|
-
|
|
402
|
+
if (!navigateWithFrameworkRouter(url.toString())) {
|
|
403
|
+
window.history.pushState(null, '', url.toString());
|
|
404
|
+
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
405
|
+
}
|
|
335
406
|
}
|
|
336
407
|
}, [runtime.events, instanceId]);
|
|
408
|
+
// Handle same-page anchor focus: scroll + pulse + focus
|
|
409
|
+
const handleFocusAnchor = useCallback((anchor) => {
|
|
410
|
+
const el = document.querySelector(anchor.selector);
|
|
411
|
+
if (!(el instanceof HTMLElement))
|
|
412
|
+
return;
|
|
413
|
+
runtime.events.publish('nav:tip_focused', {
|
|
414
|
+
instanceId,
|
|
415
|
+
selector: anchor.selector,
|
|
416
|
+
route: anchor.route,
|
|
417
|
+
timestamp: Date.now(),
|
|
418
|
+
});
|
|
419
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
420
|
+
pulseElement(el);
|
|
421
|
+
// Focus after scroll completes
|
|
422
|
+
setTimeout(() => el.focus(), 400);
|
|
423
|
+
}, [runtime.events, instanceId]);
|
|
337
424
|
// Compute container styles
|
|
338
425
|
const containerStyle = {
|
|
339
426
|
...baseStyles.container,
|
|
@@ -348,7 +435,7 @@ export function NavWidget({ config, runtime, instanceId }) {
|
|
|
348
435
|
...themeStyles[resolvedTheme].emptyState,
|
|
349
436
|
};
|
|
350
437
|
// Render a list of nav tip items
|
|
351
|
-
const renderItems = (items) => items.map((tip, index) => (_jsx(NavTipItem, { item: tip, isExpanded: expandedIds.has(tip.config.id), isLast: index === items.length - 1, onToggle: () => handleToggle(tip.config.id), onNavigate: handleNavigate, theme: resolvedTheme }, tip.config.id)));
|
|
438
|
+
const renderItems = (items) => items.map((tip, index) => (_jsx(NavTipItem, { item: tip, isExpanded: expandedIds.has(tip.config.id), isLast: index === items.length - 1, onToggle: () => handleToggle(tip.config.id), onNavigate: handleNavigate, onFocusAnchor: handleFocusAnchor, theme: resolvedTheme }, tip.config.id)));
|
|
352
439
|
// Empty state
|
|
353
440
|
if (visibleTips.length === 0) {
|
|
354
441
|
return (_jsx("div", { style: containerStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-nav", children: _jsx("div", { style: emptyStateStyle, children: "You're all set for now! We'll share helpful tips here when they're relevant to what you're doing." }) }));
|
|
@@ -389,7 +476,7 @@ export const NavMountableWidget = {
|
|
|
389
476
|
${tips
|
|
390
477
|
.map((tip) => `
|
|
391
478
|
<div style="margin-bottom: 4px; padding: 12px 16px; background: ${slateGrey[12]}; border-radius: 8px;">
|
|
392
|
-
${tip.config.icon ? `<span>${
|
|
479
|
+
${tip.config.icon ? `<span>${renderIcon(tip.config.icon)}</span> ` : ''}<strong>${escapeHtml(tip.config.title)}</strong>
|
|
393
480
|
<p style="margin-top: 8px; color: ${slateGrey[6]}; font-size: 13px;">${escapeHtml(tip.config.description)}</p>
|
|
394
481
|
${tip.config.href ? `<a href="${escapeHtml(tip.config.href)}" style="color: ${purple[2]}; font-size: 13px;">Go →</a>` : ''}
|
|
395
482
|
</div>
|
package/dist/cdn.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,SAA0B,MAAM,UAAU,CAAC;AAGlD;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,SAA0B,MAAM,UAAU,CAAC;AAGlD;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;2BAsCujmB,CAAC;8BAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAtBjlmB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;CAQjD,CAAC;AAaF,eAAe,QAAQ,CAAC"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -10,6 +10,16 @@ import type { ActionExecutor, NavigateAction, ScrollToAction } from './types';
|
|
|
10
10
|
* Execute a scrollTo action
|
|
11
11
|
*/
|
|
12
12
|
export declare const executeScrollTo: ActionExecutor<ScrollToAction>;
|
|
13
|
+
/**
|
|
14
|
+
* Try to navigate using the host framework's native router.
|
|
15
|
+
* Returns true if a framework router handled the navigation,
|
|
16
|
+
* false if no framework was detected (caller should use pushState or location.href).
|
|
17
|
+
*
|
|
18
|
+
* Using the native router preserves SPA behavior (no full reload),
|
|
19
|
+
* layout state, scroll position, and framework-specific features
|
|
20
|
+
* (RSC streaming, view transitions, etc.).
|
|
21
|
+
*/
|
|
22
|
+
export declare function navigateWithFrameworkRouter(url: string): boolean;
|
|
13
23
|
/**
|
|
14
24
|
* Execute a navigate action
|
|
15
25
|
*/
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EACV,cAAc,EAEd,cAAc,EAEd,cAAc,EACf,MAAM,SAAS,CAAC;AAMjB;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,cAAc,CA+B1D,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EACV,cAAc,EAEd,cAAc,EAEd,cAAc,EACf,MAAM,SAAS,CAAC;AAMjB;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,cAAc,CA+B1D,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAgDhE;AAgBD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,cAAc,CAuC1D,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,SAAS;;;;;;EAGZ,CAAC;AAMX;;;;;;GAMG;AACH,eAAO,MAAM,OAAO;;;;;IAMlB;;OAEG;;;;;;;;IAGH;;OAEG;;;;;uBAqCi3a,CAAC;0BAA8B,CAAC;;;;;;;;;IAxBp5a;;;;OAIG;0BACmB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;CAgB9C,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
package/dist/runtime.js
CHANGED
|
@@ -36,6 +36,61 @@ export const executeScrollTo = async (action, context) => {
|
|
|
36
36
|
},
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Try to navigate using the host framework's native router.
|
|
41
|
+
* Returns true if a framework router handled the navigation,
|
|
42
|
+
* false if no framework was detected (caller should use pushState or location.href).
|
|
43
|
+
*
|
|
44
|
+
* Using the native router preserves SPA behavior (no full reload),
|
|
45
|
+
* layout state, scroll position, and framework-specific features
|
|
46
|
+
* (RSC streaming, view transitions, etc.).
|
|
47
|
+
*/
|
|
48
|
+
export function navigateWithFrameworkRouter(url) {
|
|
49
|
+
if (typeof window === 'undefined')
|
|
50
|
+
return false;
|
|
51
|
+
const w = window;
|
|
52
|
+
// Next.js �� window.next.router.push() triggers App Router navigation
|
|
53
|
+
// with RSC fetch, layout preservation, and loading states
|
|
54
|
+
try {
|
|
55
|
+
const nextRouter = w.next?.router;
|
|
56
|
+
if (nextRouter?.push) {
|
|
57
|
+
nextRouter.push(url);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* fall through */
|
|
63
|
+
}
|
|
64
|
+
// Nuxt 3 — useRouter() isn't accessible from outside Vue, but
|
|
65
|
+
// $nuxt.$router (Nuxt 2) or window.__NUXT__?.hooks (Nuxt 3) + navigateTo
|
|
66
|
+
// aren't reliably exposed. Nuxt 2 exposes $nuxt.$router.push().
|
|
67
|
+
try {
|
|
68
|
+
if (w.$nuxt?.$router?.push) {
|
|
69
|
+
w.$nuxt.$router.push(url);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* fall through */
|
|
75
|
+
}
|
|
76
|
+
// Angular — Angular Router isn't exposed on window by default.
|
|
77
|
+
// The most reliable approach is to detect Angular and use location.href.
|
|
78
|
+
if (w.ng || w.getAllAngularRootElements || document.querySelector('[ng-version]')) {
|
|
79
|
+
window.location.href = url;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
// SvelteKit — goto() isn't on window, but __SVELTEKIT_DATA__ confirms the framework.
|
|
83
|
+
if (w.__SVELTEKIT_DATA__ || document.body?.hasAttribute('data-sveltekit')) {
|
|
84
|
+
window.location.href = url;
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
// Astro (View Transitions) — no client-side router API exposed
|
|
88
|
+
if (document.querySelector('[data-astro-transition-fallback]')) {
|
|
89
|
+
window.location.href = url;
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
39
94
|
/**
|
|
40
95
|
* Check if a URL is same-origin as the current page.
|
|
41
96
|
* Relative URLs (e.g. "/dashboard") are always same-origin.
|
|
@@ -71,9 +126,12 @@ export const executeNavigate = async (action, context) => {
|
|
|
71
126
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
72
127
|
}
|
|
73
128
|
else if (!action.forceFullNavigation && isSameOrigin(url)) {
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
129
|
+
// Try the host framework's native router first (Next.js, Nuxt, Angular, etc.)
|
|
130
|
+
// Falls back to pushState for vanilla SPAs, or location.href as last resort.
|
|
131
|
+
if (!navigateWithFrameworkRouter(url)) {
|
|
132
|
+
window.history.pushState(null, '', url);
|
|
133
|
+
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
134
|
+
}
|
|
77
135
|
}
|
|
78
136
|
else {
|
|
79
137
|
// Full navigation for cross-origin URLs or when explicitly requested
|