@syntrologie/adapt-nav 2.8.0-canary.7 → 2.8.0-canary.71

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.
Files changed (34) hide show
  1. package/dist/NavWidget.d.ts +1 -1
  2. package/dist/NavWidget.d.ts.map +1 -1
  3. package/dist/NavWidget.js +98 -27
  4. package/dist/cdn.d.ts +1 -1
  5. package/dist/cdn.d.ts.map +1 -1
  6. package/dist/runtime.d.ts +11 -1
  7. package/dist/runtime.d.ts.map +1 -1
  8. package/dist/runtime.js +61 -3
  9. package/dist/schema.d.ts +402 -188
  10. package/dist/schema.d.ts.map +1 -1
  11. package/dist/schema.js +5 -3
  12. package/dist/types.d.ts +8 -1
  13. package/dist/types.d.ts.map +1 -1
  14. package/node_modules/@syntro/design-system/dist/tailwind-preset.d.ts.map +1 -1
  15. package/node_modules/@syntro/design-system/dist/tailwind-preset.js +4 -2
  16. package/node_modules/@syntro/design-system/dist/tokens/colors.css +1 -1
  17. package/node_modules/@syntro/design-system/dist/tokens/colors.d.ts +2 -2
  18. package/node_modules/@syntro/design-system/dist/tokens/colors.js +1 -1
  19. package/node_modules/@syntro/design-system/dist/tokens/effects.d.ts +54 -0
  20. package/node_modules/@syntro/design-system/dist/tokens/effects.d.ts.map +1 -1
  21. package/node_modules/@syntro/design-system/dist/tokens/effects.js +44 -0
  22. package/node_modules/@syntro/design-system/package.json +2 -2
  23. package/node_modules/@syntrologie/sdk-contracts/dist/index.d.ts +1 -1
  24. package/node_modules/@syntrologie/sdk-contracts/dist/index.js +5 -3
  25. package/node_modules/@syntrologie/sdk-contracts/dist/schemas.d.ts +150 -79
  26. package/node_modules/@syntrologie/sdk-contracts/dist/schemas.js +266 -67
  27. package/node_modules/@syntrologie/sdk-contracts/package.json +2 -2
  28. package/node_modules/@syntrologie/shared-editor-ui/dist/components/AnchorPicker.d.ts.map +1 -1
  29. package/node_modules/@syntrologie/shared-editor-ui/dist/components/AnchorPicker.js +8 -7
  30. package/node_modules/@syntrologie/shared-editor-ui/dist/components/ElementHighlight.js +2 -4
  31. package/node_modules/@syntrologie/shared-editor-ui/dist/hooks/useTriggerWhenStatus.d.ts.map +1 -1
  32. package/node_modules/@syntrologie/shared-editor-ui/dist/hooks/useTriggerWhenStatus.js +0 -1
  33. package/node_modules/@syntrologie/shared-editor-ui/package.json +11 -9
  34. package/package.json +9 -9
@@ -25,7 +25,7 @@ export declare const NavMountableWidget: {
25
25
  mount(container: HTMLElement, config?: NavConfig & {
26
26
  runtime?: NavWidgetRuntime;
27
27
  instanceId?: string;
28
- }): () => void;
28
+ }): (() => void) | undefined;
29
29
  };
30
30
  export default NavWidget;
31
31
  //# sourceMappingURL=NavWidget.d.ts.map
@@ -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;AAkVzF;;;;;;;;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;CA2B3E,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
  // ============================================================================
@@ -127,7 +145,8 @@ const themeStyles = {
127
145
  color: 'var(--sc-content-text-secondary-color)',
128
146
  },
129
147
  linkButton: {
130
- backgroundColor: 'var(--sc-color-primary, #6366f1)',
148
+ // purple[4] = #6a59ce — design system primary purple, used as fallback when --sc-color-primary is not set
149
+ backgroundColor: `var(--sc-color-primary, ${purple[4]})`,
131
150
  color: '#ffffff',
132
151
  },
133
152
  categoryHeader: {
@@ -160,7 +179,8 @@ const themeStyles = {
160
179
  color: 'var(--sc-content-text-secondary-color)',
161
180
  },
162
181
  linkButton: {
163
- backgroundColor: 'var(--sc-color-primary, #6366f1)',
182
+ // purple[4] = #6a59ce — design system primary purple, used as fallback when --sc-color-primary is not set
183
+ backgroundColor: `var(--sc-color-primary, ${purple[4]})`,
164
184
  color: '#ffffff',
165
185
  },
166
186
  categoryHeader: {
@@ -171,10 +191,36 @@ const themeStyles = {
171
191
  },
172
192
  },
173
193
  };
174
- function NavTipItem({ item, isExpanded, isLast, onToggle, onNavigate, theme }) {
194
+ // ============================================================================
195
+ // Helpers
196
+ // ============================================================================
197
+ /** Check if any of the given routes match the current page pathname */
198
+ function routeMatchesCurrent(routes) {
199
+ if (typeof window === 'undefined')
200
+ return false;
201
+ const current = window.location.pathname;
202
+ return routes.some((route) => {
203
+ // Strip query/hash from route if present
204
+ const routePath = route.split('?')[0].split('#')[0];
205
+ // Exact match or glob pattern (** suffix)
206
+ if (routePath.endsWith('/**')) {
207
+ return current.startsWith(routePath.slice(0, -3));
208
+ }
209
+ return current === routePath;
210
+ });
211
+ }
212
+ /** Apply a brief pulse animation to an element, then remove it */
213
+ function pulseElement(el) {
214
+ const keyframes = [
215
+ { boxShadow: '0 0 0 0 rgba(13, 148, 136, 0.5)' },
216
+ { boxShadow: '0 0 0 8px rgba(13, 148, 136, 0)' },
217
+ ];
218
+ el.animate(keyframes, { duration: 600, iterations: 3, easing: 'ease-out' });
219
+ }
220
+ function NavTipItem({ item, isExpanded, isLast, onToggle, onNavigate, onFocusAnchor, theme, }) {
175
221
  const [isHovered, setIsHovered] = useState(false);
176
222
  const colors = themeStyles[theme];
177
- const { title, description, href, icon, external } = item.config;
223
+ const { title, description, href, icon, external, anchor } = item.config;
178
224
  const itemStyle = {
179
225
  ...baseStyles.item,
180
226
  ...colors.item,
@@ -196,14 +242,36 @@ function NavTipItem({ item, isExpanded, isLast, onToggle, onNavigate, theme }) {
196
242
  maxHeight: isExpanded ? '500px' : '0',
197
243
  paddingBottom: isExpanded ? '16px' : '0',
198
244
  };
245
+ // Determine the effective navigation target from anchor or legacy href
246
+ const effectiveHref = anchor
247
+ ? Array.isArray(anchor.route)
248
+ ? anchor.route[0]
249
+ : anchor.route
250
+ : href;
251
+ // Same-page check: anchor exists, selector is meaningful, and route matches current page
252
+ const isSamePage = anchor
253
+ ? routeMatchesCurrent(Array.isArray(anchor.route) ? anchor.route : [anchor.route])
254
+ : effectiveHref
255
+ ? routeMatchesCurrent([effectiveHref])
256
+ : false;
257
+ const hasSelector = anchor?.selector && anchor.selector !== '*';
258
+ const isFocusAction = isSamePage && hasSelector;
259
+ const hasAction = !!effectiveHref || isFocusAction;
199
260
  const handleLinkClick = (e) => {
200
261
  e.preventDefault();
201
262
  e.stopPropagation();
202
- if (href) {
203
- onNavigate(href, external ?? false);
263
+ if (isFocusAction && anchor) {
264
+ onFocusAnchor(anchor);
265
+ }
266
+ else if (effectiveHref) {
267
+ onNavigate(effectiveHref, external ?? false);
204
268
  }
205
269
  };
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'] }))] })] }));
270
+ // CTA label
271
+ const ctaLabel = isFocusAction ? `Focus \u2192` : external ? `Go \u2197` : `Go \u2192`;
272
+ 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 && (
273
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: renderIcon returns sanitized SVG from EMOJI_SVG_MAP or escapeHtml
274
+ _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
275
  }
208
276
  // ============================================================================
209
277
  // NavWidget Component
@@ -329,11 +397,32 @@ export function NavWidget({ config, runtime, instanceId }) {
329
397
  window.open(href, '_blank', 'noopener,noreferrer');
330
398
  }
331
399
  else {
400
+ // Try the host framework's native router first (Next.js, Nuxt, etc.)
401
+ // Falls back to pushState for vanilla SPAs.
332
402
  const url = new URL(href, window.location.origin);
333
403
  url.search = window.location.search;
334
- window.location.href = url.toString();
404
+ if (!navigateWithFrameworkRouter(url.toString())) {
405
+ window.history.pushState(null, '', url.toString());
406
+ window.dispatchEvent(new PopStateEvent('popstate'));
407
+ }
335
408
  }
336
409
  }, [runtime.events, instanceId]);
410
+ // Handle same-page anchor focus: scroll + pulse + focus
411
+ const handleFocusAnchor = useCallback((anchor) => {
412
+ const el = document.querySelector(anchor.selector);
413
+ if (!(el instanceof HTMLElement))
414
+ return;
415
+ runtime.events.publish('nav:tip_focused', {
416
+ instanceId,
417
+ selector: anchor.selector,
418
+ route: anchor.route,
419
+ timestamp: Date.now(),
420
+ });
421
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' });
422
+ pulseElement(el);
423
+ // Focus after scroll completes
424
+ setTimeout(() => el.focus(), 400);
425
+ }, [runtime.events, instanceId]);
337
426
  // Compute container styles
338
427
  const containerStyle = {
339
428
  ...baseStyles.container,
@@ -348,7 +437,7 @@ export function NavWidget({ config, runtime, instanceId }) {
348
437
  ...themeStyles[resolvedTheme].emptyState,
349
438
  };
350
439
  // 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)));
440
+ 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
441
  // Empty state
353
442
  if (visibleTips.length === 0) {
354
443
  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." }) }));
@@ -382,24 +471,6 @@ export const NavMountableWidget = {
382
471
  root.unmount();
383
472
  };
384
473
  }
385
- // HTML fallback for non-React environments
386
- const tips = navConfig.actions || [];
387
- container.innerHTML = `
388
- <div style="font-family: system-ui; max-width: 100%;">
389
- ${tips
390
- .map((tip) => `
391
- <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>
393
- <p style="margin-top: 8px; color: ${slateGrey[6]}; font-size: 13px;">${escapeHtml(tip.config.description)}</p>
394
- ${tip.config.href ? `<a href="${escapeHtml(tip.config.href)}" style="color: ${purple[2]}; font-size: 13px;">Go &rarr;</a>` : ''}
395
- </div>
396
- `)
397
- .join('')}
398
- </div>
399
- `;
400
- return () => {
401
- container.innerHTML = '';
402
- };
403
474
  },
404
475
  };
405
476
  export default NavWidget;
package/dist/cdn.d.ts CHANGED
@@ -25,7 +25,7 @@ export declare const manifest: {
25
25
  mount(container: HTMLElement, config?: import("./types").NavConfig & {
26
26
  runtime?: import("./types").NavWidgetRuntime;
27
27
  instanceId?: string;
28
- }): () => void;
28
+ }): (() => void) | undefined;
29
29
  };
30
30
  metadata: {
31
31
  name: string;
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;;;;;;;;;;;;;;2BAsCmymB,CAAC;8BAA8B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAtB7zmB,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
  */
@@ -56,7 +66,7 @@ export declare const runtime: {
56
66
  mount(container: HTMLElement, config?: import("./types").NavConfig & {
57
67
  runtime?: import("./types").NavWidgetRuntime;
58
68
  instanceId?: string;
59
- }): () => void;
69
+ }): (() => void) | undefined;
60
70
  };
61
71
  metadata: {
62
72
  name: string;
@@ -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;;;;;uBAqC6lb,CAAC;0BAA8B,CAAC;;;;;;;;;IAxBhob;;;;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