@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.
@@ -1 +1 @@
1
- {"version":3,"file":"NavWidget.d.ts","sourceRoot":"","sources":["../src/NavWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAgB,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AA8PzF;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,cAAc,2CAmMxE;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"}
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
- function NavTipItem({ item, isExpanded, isLast, onToggle, onNavigate, theme }) {
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 (href) {
203
- onNavigate(href, external ?? false);
261
+ if (isFocusAction && anchor) {
262
+ onFocusAnchor(anchor);
263
+ }
264
+ else if (effectiveHref) {
265
+ onNavigate(effectiveHref, external ?? false);
204
266
  }
205
267
  };
206
- 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 && _jsx("span", { style: baseStyles.icon, children: 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 }), href && (_jsxs("a", { href: href, onClick: handleLinkClick, style: { ...baseStyles.linkButton, ...colors.linkButton }, target: external ? '_blank' : undefined, rel: external ? 'noopener noreferrer' : undefined, children: ["Go ", external ? '\u2197' : '\u2192'] }))] })] }));
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
- window.location.href = url.toString();
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>${escapeHtml(tip.config.icon)}</span> ` : ''}<strong>${escapeHtml(tip.config.title)}</strong>
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 &rarr;</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;;;;;;;;;;;;;;2BAsCgpZ,CAAC;8BAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAtB1qZ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;CAQjD,CAAC;AAaF,eAAe,QAAQ,CAAC"}
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
  */
@@ -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;AAgBF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,cAAc,CAoC1D,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,SAAS;;;;;;EAGZ,CAAC;AAMX;;;;;;GAMG;AACH,eAAO,MAAM,OAAO;;;;;IAMlB;;OAEG;;;;;;;;IAGH;;OAEG;;;;;uBAqCy8R,CAAC;0BAA8B,CAAC;;;;;;;;;IAxB5+R;;;;OAIG;0BACmB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;CAgB9C,CAAC;AAEF,eAAe,OAAO,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
- // SPA-aware: use pushState for same-origin URLs to avoid full page reload
75
- window.history.pushState(null, '', url);
76
- window.dispatchEvent(new PopStateEvent('popstate'));
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