@zenithbuild/router 0.5.0-beta.2.3

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.
@@ -0,0 +1,437 @@
1
+ /**
2
+ * ZenLink Runtime Module
3
+ *
4
+ * Provides programmatic navigation and ZenLink utilities.
5
+ * This module can be imported in `.zen` files or TypeScript.
6
+ *
7
+ * @package @zenithbuild/router
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { navigate, zenLink, isActive } from '@zenithbuild/router'
12
+ *
13
+ * // Programmatic navigation
14
+ * navigate('/about')
15
+ *
16
+ * // Check active state
17
+ * if (isActive('/blog')) {
18
+ * console.log('On blog section')
19
+ * }
20
+ * ```
21
+ */
22
+ // ============================================
23
+ // Internal State
24
+ // ============================================
25
+ /** Prefetched routes cache */
26
+ const prefetchedRoutes = new Set();
27
+ /** Global transition handler (set at layout level) */
28
+ let globalTransitionHandler = null;
29
+ /** Navigation in progress flag */
30
+ let isNavigating = false;
31
+ // ============================================
32
+ // Utilities
33
+ // ============================================
34
+ /**
35
+ * Check if URL is external (different origin)
36
+ */
37
+ export function isExternalUrl(url) {
38
+ if (!url)
39
+ return false;
40
+ // Protocol-relative or absolute URLs with different origin
41
+ if (url.startsWith('//') || url.startsWith('http://') || url.startsWith('https://')) {
42
+ try {
43
+ const linkUrl = new URL(url, window.location.origin);
44
+ return linkUrl.origin !== window.location.origin;
45
+ }
46
+ catch {
47
+ return true;
48
+ }
49
+ }
50
+ // mailto:, tel:, javascript:, etc.
51
+ if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)) {
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+ /**
57
+ * Check if link should use SPA navigation
58
+ */
59
+ export function shouldUseSPANavigation(href, target) {
60
+ // Don't use SPA for external links
61
+ if (isExternalUrl(href))
62
+ return false;
63
+ // Don't use SPA if target is set (except _self)
64
+ if (target && target !== '_self')
65
+ return false;
66
+ // Don't use SPA for hash-only links on same page
67
+ if (href.startsWith('#'))
68
+ return false;
69
+ // Don't use SPA for download links or special protocols
70
+ if (href.startsWith('mailto:') || href.startsWith('tel:') || href.startsWith('javascript:')) {
71
+ return false;
72
+ }
73
+ return true;
74
+ }
75
+ /**
76
+ * Normalize a path
77
+ */
78
+ export function normalizePath(path) {
79
+ // Ensure path starts with /
80
+ if (!path.startsWith('/')) {
81
+ const currentDir = window.location.pathname.split('/').slice(0, -1).join('/');
82
+ path = currentDir + '/' + path;
83
+ }
84
+ // Remove trailing slash (except for root)
85
+ if (path !== '/' && path.endsWith('/')) {
86
+ path = path.slice(0, -1);
87
+ }
88
+ return path;
89
+ }
90
+ // ============================================
91
+ // Navigation API
92
+ // ============================================
93
+ /**
94
+ * Navigate to a new URL (SPA navigation)
95
+ *
96
+ * This is the primary API for programmatic navigation.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * // Simple navigation
101
+ * navigate('/about')
102
+ *
103
+ * // With options
104
+ * navigate('/dashboard', { replace: true })
105
+ *
106
+ * // With transition
107
+ * navigate('/gallery', {
108
+ * onTransition: async (ctx) => {
109
+ * await animateOut(ctx.currentPage)
110
+ * await animateIn(ctx.nextPage)
111
+ * }
112
+ * })
113
+ * ```
114
+ */
115
+ export async function zenNavigate(to, options = {}) {
116
+ // Prevent concurrent navigations
117
+ if (isNavigating) {
118
+ console.warn('[ZenLink] Navigation already in progress');
119
+ return;
120
+ }
121
+ isNavigating = true;
122
+ try {
123
+ // Access global router
124
+ const router = window.__zenith_router;
125
+ if (router && router.navigate) {
126
+ // Use router's navigate function
127
+ await router.navigate(to, options);
128
+ }
129
+ else {
130
+ // Fallback: use History API directly
131
+ const normalizedPath = normalizePath(to);
132
+ if (options.replace) {
133
+ window.history.replaceState(options.state || null, '', normalizedPath);
134
+ }
135
+ else {
136
+ window.history.pushState(options.state || null, '', normalizedPath);
137
+ }
138
+ // Dispatch popstate to trigger route resolution
139
+ window.dispatchEvent(new PopStateEvent('popstate'));
140
+ }
141
+ // Scroll to top if requested (default: true)
142
+ if (options.scrollToTop !== false) {
143
+ window.scrollTo({ top: 0, behavior: 'smooth' });
144
+ }
145
+ }
146
+ finally {
147
+ isNavigating = false;
148
+ }
149
+ }
150
+ // Clean alias
151
+ export const navigate = zenNavigate;
152
+ /**
153
+ * Navigate back in history
154
+ */
155
+ export function zenBack() {
156
+ window.history.back();
157
+ }
158
+ export const back = zenBack;
159
+ /**
160
+ * Navigate forward in history
161
+ */
162
+ export function zenForward() {
163
+ window.history.forward();
164
+ }
165
+ export const forward = zenForward;
166
+ /**
167
+ * Navigate to a specific history index
168
+ */
169
+ export function zenGo(delta) {
170
+ window.history.go(delta);
171
+ }
172
+ export const go = zenGo;
173
+ // ============================================
174
+ // Active State
175
+ // ============================================
176
+ /**
177
+ * Check if a path is currently active
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * // Check if on blog section
182
+ * if (isActive('/blog')) {
183
+ * addClass(link, 'active')
184
+ * }
185
+ *
186
+ * // Exact match only
187
+ * if (isActive('/blog', true)) {
188
+ * addClass(link, 'active-exact')
189
+ * }
190
+ * ```
191
+ */
192
+ export function zenIsActive(path, exact = false) {
193
+ const router = window.__zenith_router;
194
+ if (router && router.isActive) {
195
+ return router.isActive(path, exact);
196
+ }
197
+ // Fallback: compare with current pathname
198
+ const currentPath = window.location.pathname;
199
+ const normalizedPath = normalizePath(path);
200
+ if (exact) {
201
+ return currentPath === normalizedPath;
202
+ }
203
+ // Root path special case
204
+ if (normalizedPath === '/') {
205
+ return currentPath === '/';
206
+ }
207
+ return currentPath.startsWith(normalizedPath);
208
+ }
209
+ export const isActive = zenIsActive;
210
+ // ============================================
211
+ // Prefetching
212
+ // ============================================
213
+ /**
214
+ * Prefetch a route for faster navigation
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * // Prefetch on hover
219
+ * element.addEventListener('mouseenter', () => {
220
+ * prefetch('/about')
221
+ * })
222
+ * ```
223
+ */
224
+ export async function zenPrefetch(path) {
225
+ // Normalize path
226
+ const normalizedPath = normalizePath(path);
227
+ // Don't prefetch if already done
228
+ if (prefetchedRoutes.has(normalizedPath)) {
229
+ return;
230
+ }
231
+ // Mark as prefetched
232
+ prefetchedRoutes.add(normalizedPath);
233
+ // Try router prefetch first
234
+ const router = window.__zenith_router;
235
+ if (router && router.prefetch) {
236
+ try {
237
+ await router.prefetch(normalizedPath);
238
+ }
239
+ catch {
240
+ // Silently ignore prefetch errors
241
+ }
242
+ return;
243
+ }
244
+ // Fallback: use link preload hint
245
+ try {
246
+ const link = document.createElement('link');
247
+ link.rel = 'prefetch';
248
+ link.href = normalizedPath;
249
+ link.as = 'document';
250
+ document.head.appendChild(link);
251
+ }
252
+ catch {
253
+ // Ignore errors
254
+ }
255
+ }
256
+ export const prefetch = zenPrefetch;
257
+ /**
258
+ * Check if a route has been prefetched
259
+ */
260
+ export function zenIsPrefetched(path) {
261
+ return prefetchedRoutes.has(normalizePath(path));
262
+ }
263
+ export const isPrefetched = zenIsPrefetched;
264
+ // ============================================
265
+ // Transitions API
266
+ // ============================================
267
+ /**
268
+ * Set global transition handler
269
+ *
270
+ * This allows setting a layout-level transition that applies to all navigations.
271
+ *
272
+ * @example
273
+ * ```ts
274
+ * // In layout component
275
+ * setGlobalTransition(async (ctx) => {
276
+ * ctx.currentPage?.classList.add('fade-out')
277
+ * await delay(300)
278
+ * ctx.nextPage?.classList.add('fade-in')
279
+ * })
280
+ * ```
281
+ */
282
+ export function setGlobalTransition(handler) {
283
+ globalTransitionHandler = handler;
284
+ }
285
+ /**
286
+ * Get current global transition handler
287
+ */
288
+ export function getGlobalTransition() {
289
+ return globalTransitionHandler;
290
+ }
291
+ /**
292
+ * Create a transition context
293
+ */
294
+ export function createTransitionContext(fromPath, toPath, direction = 'forward') {
295
+ return {
296
+ currentPage: document.querySelector('[data-zen-page]'),
297
+ nextPage: null,
298
+ betweenPage: null,
299
+ direction,
300
+ fromPath,
301
+ toPath,
302
+ params: {},
303
+ query: {}
304
+ };
305
+ }
306
+ // ============================================
307
+ // Route State
308
+ // ============================================
309
+ /**
310
+ * Get current route state
311
+ */
312
+ export function zenGetRoute() {
313
+ const router = window.__zenith_router;
314
+ if (router && router.getRoute) {
315
+ return router.getRoute();
316
+ }
317
+ // Fallback
318
+ const query = {};
319
+ const params = new URLSearchParams(window.location.search);
320
+ params.forEach((value, key) => {
321
+ query[key] = value;
322
+ });
323
+ return {
324
+ path: window.location.pathname,
325
+ params: {},
326
+ query
327
+ };
328
+ }
329
+ export const getRoute = zenGetRoute;
330
+ /**
331
+ * Get a route parameter
332
+ */
333
+ export function zenGetParam(name) {
334
+ return zenGetRoute().params[name];
335
+ }
336
+ export const getParam = zenGetParam;
337
+ /**
338
+ * Get a query parameter
339
+ */
340
+ export function zenGetQuery(name) {
341
+ return zenGetRoute().query[name];
342
+ }
343
+ export const getQuery = zenGetQuery;
344
+ // ============================================
345
+ // ZenLink Factory (for programmatic creation)
346
+ // ============================================
347
+ /**
348
+ * Create a ZenLink element programmatically
349
+ *
350
+ * @example
351
+ * ```ts
352
+ * const link = createZenLink({
353
+ * href: '/about',
354
+ * class: 'nav-link',
355
+ * children: 'About Us'
356
+ * })
357
+ * container.appendChild(link)
358
+ * ```
359
+ */
360
+ export function createZenLink(props) {
361
+ const link = document.createElement('a');
362
+ // Set href
363
+ link.href = props.href;
364
+ // Set class
365
+ const classes = ['zen-link'];
366
+ if (props.class)
367
+ classes.push(props.class);
368
+ if (zenIsActive(props.href, props.exact))
369
+ classes.push('zen-link-active');
370
+ if (isExternalUrl(props.href))
371
+ classes.push('zen-link-external');
372
+ link.className = classes.join(' ');
373
+ // Set target
374
+ if (props.target) {
375
+ link.target = props.target;
376
+ }
377
+ else if (isExternalUrl(props.href)) {
378
+ link.target = '_blank';
379
+ }
380
+ // Set rel for security
381
+ if (isExternalUrl(props.href) || props.target === '_blank') {
382
+ link.rel = 'noopener noreferrer';
383
+ }
384
+ // Set aria-label
385
+ if (props.ariaLabel) {
386
+ link.setAttribute('aria-label', props.ariaLabel);
387
+ }
388
+ // Set content
389
+ if (props.children) {
390
+ if (typeof props.children === 'string') {
391
+ link.textContent = props.children;
392
+ }
393
+ else if (Array.isArray(props.children)) {
394
+ props.children.forEach(child => link.appendChild(child));
395
+ }
396
+ else {
397
+ link.appendChild(props.children);
398
+ }
399
+ }
400
+ // Click handler
401
+ link.addEventListener('click', (event) => {
402
+ // Allow modifier keys for native behavior
403
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
404
+ return;
405
+ }
406
+ // Check if we should use SPA navigation
407
+ if (!shouldUseSPANavigation(props.href, props.target)) {
408
+ return;
409
+ }
410
+ // Prevent default
411
+ event.preventDefault();
412
+ // Call user's onClick handler
413
+ if (props.onClick) {
414
+ const result = props.onClick(event);
415
+ if (result === false)
416
+ return;
417
+ }
418
+ // Navigate
419
+ zenNavigate(props.href, {
420
+ replace: props.replace,
421
+ onTransition: props.onTransition,
422
+ noTransition: props.noTransition
423
+ });
424
+ });
425
+ // Preload on hover
426
+ if (props.preload) {
427
+ link.addEventListener('mouseenter', () => {
428
+ if (shouldUseSPANavigation(props.href, props.target)) {
429
+ zenPrefetch(props.href);
430
+ }
431
+ });
432
+ }
433
+ return link;
434
+ }
435
+ // Alias
436
+ export const zenLink = createZenLink;
437
+ //# sourceMappingURL=zen-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zen-link.js","sourceRoot":"","sources":["../../src/navigation/zen-link.ts"],"names":[],"mappings":"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"}
package/dist/router.js ADDED
@@ -0,0 +1,111 @@
1
+ // ---------------------------------------------------------------------------
2
+ // router.js — Zenith Router V0
3
+ // ---------------------------------------------------------------------------
4
+ // Router assembly: wires match engine + history + runtime mount/unmount.
5
+ //
6
+ // Usage:
7
+ // const router = createRouter({ routes, container });
8
+ // router.start();
9
+ //
10
+ // - Explicit start() — no side effects on import or creation
11
+ // - Mount/unmount delegated to @zenithbuild/runtime
12
+ // - Deterministic first-match-wins
13
+ // ---------------------------------------------------------------------------
14
+
15
+ import { matchRoute } from './match.js';
16
+ import { listen, current } from './history.js';
17
+ import { _dispatchRouteChange } from './events.js';
18
+ import { _setNavigationResolver } from './navigate.js';
19
+
20
+ /**
21
+ * Create a router instance.
22
+ *
23
+ * @param {{ routes: Array<{ path: string, load: Function }>, container: HTMLElement, mount?: Function, cleanup?: Function }} config
24
+ * @returns {{ start: () => Promise<void>, destroy: () => void }}
25
+ */
26
+ export function createRouter(config) {
27
+ const { routes, container } = config;
28
+
29
+ // Allow injecting mount/cleanup for testing without importing runtime
30
+ const mountFn = config.mount || null;
31
+ const cleanupFn = config.cleanup || null;
32
+
33
+ if (!container || !(container instanceof HTMLElement)) {
34
+ throw new Error('[Zenith Router] createRouter() requires an HTMLElement container');
35
+ }
36
+
37
+ if (!Array.isArray(routes) || routes.length === 0) {
38
+ throw new Error('[Zenith Router] createRouter() requires a non-empty routes array');
39
+ }
40
+
41
+ let _unlisten = null;
42
+ let _started = false;
43
+ let _hasMounted = false;
44
+
45
+ /**
46
+ * Resolve a path: match → load → mount.
47
+ *
48
+ * @param {string} path
49
+ */
50
+ async function _resolve(path) {
51
+ const result = matchRoute(routes, path);
52
+
53
+ if (!result) {
54
+ _dispatchRouteChange({ path, matched: false });
55
+ return;
56
+ }
57
+
58
+ // Teardown previous page (only if we've mounted before)
59
+ if (_hasMounted && cleanupFn) {
60
+ cleanupFn();
61
+ }
62
+
63
+ // Load module
64
+ const pageModule = await result.route.load(result.params);
65
+
66
+ // Mount new page
67
+ if (mountFn) {
68
+ mountFn(container, pageModule);
69
+ _hasMounted = true;
70
+ }
71
+
72
+ _dispatchRouteChange({
73
+ path,
74
+ params: result.params,
75
+ matched: true
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Start the router — match initial route and begin listening.
81
+ */
82
+ async function start() {
83
+ if (_started) return;
84
+ _started = true;
85
+
86
+ // Wire navigate() to use our resolver
87
+ _setNavigationResolver(_resolve);
88
+
89
+ // Listen for popstate (back/forward)
90
+ _unlisten = listen((path) => {
91
+ _resolve(path);
92
+ });
93
+
94
+ // Initial route
95
+ await _resolve(current());
96
+ }
97
+
98
+ /**
99
+ * Tear down the router — remove listeners, clear state.
100
+ */
101
+ function destroy() {
102
+ if (_unlisten) {
103
+ _unlisten();
104
+ _unlisten = null;
105
+ }
106
+ _setNavigationResolver(null);
107
+ _started = false;
108
+ }
109
+
110
+ return { start, destroy };
111
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Zenith Runtime Router (Native Bridge)
3
+ *
4
+ * SPA-style client-side router with SSR support via native resolution.
5
+ */
6
+ import type { RouteState, NavigateOptions, RuntimeRouteRecord, RouteListener } from "./types";
7
+ export declare function initRouter(manifest: RuntimeRouteRecord[], outlet?: HTMLElement | string): void;
8
+ /**
9
+ * Resolve route - uses native implementation if available (Node.js/SSR)
10
+ * or falls back to JS implementation for the browser.
11
+ */
12
+ export declare function resolveRoute(pathname: string): {
13
+ record: RuntimeRouteRecord;
14
+ params: Record<string, string>;
15
+ } | null;
16
+ export declare function navigate(to: string, options?: NavigateOptions): Promise<void>;
17
+ export declare function getRoute(): RouteState;
18
+ /**
19
+ * zenRoute() - Zenith Law: Environment Resolution
20
+ *
21
+ * Canonical primitive for resolving the current route environment.
22
+ * Must be resolved once and execute before state initialization.
23
+ */
24
+ export declare function zenRoute(): {
25
+ path: string;
26
+ slugs: string[];
27
+ };
28
+ export declare function onRouteChange(listener: RouteListener): () => void;
29
+ export declare function isActive(path: string, exact?: boolean): boolean;
30
+ export declare function prefetch(path: string): Promise<void>;
31
+ export declare function isPrefetched(_path: string): boolean;
32
+ export declare function generateRuntimeRouterCode(): string;
33
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EAGf,kBAAkB,EAClB,aAAa,EACd,MAAM,SAAS,CAAA;AAgBhB,wBAAgB,UAAU,CACxB,QAAQ,EAAE,kBAAkB,EAAE,EAC9B,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,GAC5B,IAAI,CAON;AAcD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,GACf;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,CA6BvE;AA8CD,wBAAsB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CASvF;AAGD,wBAAgB,QAAQ,IAAI,UAAU,CAAwB;AAE9D;;;;;GAKG;AACH,wBAAgB,QAAQ,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAM5D;AAMD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI,CAGjE;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,OAAe,GAAG,OAAO,CAEtE;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK1D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAiB;AAErE,wBAAgB,yBAAyB,IAAI,MAAM,CAElD"}