@llui/router 0.0.13 → 0.0.15

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/connect.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Router } from './index';
1
+ import type { Router } from './index.js';
2
2
  export interface RouterEffect {
3
3
  type: '__router';
4
4
  action: 'push' | 'replace' | 'back' | 'forward' | 'scroll';
@@ -1 +1 @@
1
- {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAKrC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAA;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;CACX;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,CAAA;IACzD;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,OAAO,CAAA;CAC1C;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,4CAA4C;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,YAAY,CAAA;IAC5B,4CAA4C;IAC5C,OAAO,CAAC,KAAK,EAAE,CAAC,GAAG,YAAY,CAAA;IAC/B,sBAAsB;IACtB,IAAI,IAAI,YAAY,CAAA;IACpB,yBAAyB;IACzB,OAAO,IAAI,YAAY,CAAA;IACvB,iCAAiC;IACjC,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,YAAY,CAAA;IAE1C,8DAA8D;IAC9D,YAAY,EAAE,CAAC,GAAG,EAAE;QAAE,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAA;IAEhG;;;OAGG;IACH,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,CAAA;IAEzE;;;OAGG;IACH,IAAI,CAAC,CAAC,EACJ,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EACtB,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,QAAQ,EAAE,IAAI,EAAE,EAChB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAC3B,WAAW,CAAA;IAEd;;;OAGG;IACH,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE;QAC7B,mDAAmD;QACnD,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,iCAAiC;QACjC,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAA;QACvB,oCAAoC;QACpC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAA;QACjC,8CAA8C;QAC9C,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;KAC7C,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,CAAA;CAC1C;AAED,wBAAgB,aAAa,CAAC,CAAC,EAC7B,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EACjB,OAAO,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,GAC1B,eAAe,CAAC,CAAC,CAAC,CAyJpB"}
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAKxC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAA;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;CACX;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,CAAA;IACzD;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,OAAO,CAAA;CAC1C;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,4CAA4C;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,YAAY,CAAA;IAC5B,4CAA4C;IAC5C,OAAO,CAAC,KAAK,EAAE,CAAC,GAAG,YAAY,CAAA;IAC/B,sBAAsB;IACtB,IAAI,IAAI,YAAY,CAAA;IACpB,yBAAyB;IACzB,OAAO,IAAI,YAAY,CAAA;IACvB,iCAAiC;IACjC,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,YAAY,CAAA;IAE1C,8DAA8D;IAC9D,YAAY,EAAE,CAAC,GAAG,EAAE;QAAE,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAA;IAEhG;;;OAGG;IACH,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,CAAA;IAEzE;;;OAGG;IACH,IAAI,CAAC,CAAC,EACJ,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EACtB,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,QAAQ,EAAE,IAAI,EAAE,EAChB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAC3B,WAAW,CAAA;IAEd;;;OAGG;IACH,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE;QAC7B,mDAAmD;QACnD,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,iCAAiC;QACjC,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAA;QACvB,oCAAoC;QACpC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAA;QACjC,8CAA8C;QAC9C,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;KAC7C,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,CAAA;CAC1C;AAED,wBAAgB,aAAa,CAAC,CAAC,EAC7B,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EACjB,OAAO,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,GAC1B,eAAe,CAAC,CAAC,CAAC,CAyJpB"}
package/dist/connect.js CHANGED
@@ -143,3 +143,4 @@ export function connectRouter(router, options) {
143
143
  },
144
144
  };
145
145
  }
146
+ //# sourceMappingURL=connect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.js","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Router } from './index.js'\nimport { a, onMount } from '@llui/dom'\n\n// ── Router Effects ───────────────────────────────────────────────\n\nexport interface RouterEffect {\n type: '__router'\n action: 'push' | 'replace' | 'back' | 'forward' | 'scroll'\n path?: string\n x?: number\n y?: number\n}\n\nexport interface ConnectOptions<R> {\n /**\n * Called before entering a new route. Return:\n * - `void` / `undefined` → allow navigation\n * - `false` → block navigation (stay on current route)\n * - a different `Route` → redirect to that route\n */\n beforeEnter?: (to: R, from: R | null) => R | false | void\n /**\n * Called before leaving the current route. Return:\n * - `true` → allow navigation\n * - `false` → block (e.g. unsaved changes prompt)\n */\n beforeLeave?: (from: R, to: R) => boolean\n}\n\nexport interface ConnectedRouter<R> {\n /** Effect: push a new route onto history */\n push(route: R): RouterEffect\n /** Effect: replace current history entry */\n replace(route: R): RouterEffect\n /** Effect: go back */\n back(): RouterEffect\n /** Effect: go forward */\n forward(): RouterEffect\n /** Effect: scroll to position */\n scroll(x: number, y: number): RouterEffect\n\n /** Plugin for handleEffects().use() — handles RouterEffect */\n handleEffect: (ctx: { effect: { type: string }; send: unknown; signal: AbortSignal }) => boolean\n\n /**\n * View helper: attach URL change listener via onMount.\n * Returns an empty comment node. Sends { type: 'navigate', route } on URL change.\n */\n listener<M>(send: (msg: M) => void, msgFactory?: (route: R) => M): Node[]\n\n /**\n * View helper: render a navigation link.\n * Generates <a> with proper href and click handler that sends navigate message.\n */\n link<M>(\n send: (msg: M) => void,\n route: R,\n attrs: Record<string, unknown>,\n children: Node[],\n msgFactory?: (route: R) => M,\n ): HTMLElement\n\n /**\n * Create an update handler for mergeHandlers.\n * Returns [newState, Effect[]] for navigate messages, null for others.\n */\n createHandler<S, M, E>(config: {\n /** Message type to handle (default: 'navigate') */\n message?: string\n /** Extract route from message */\n getRoute: (msg: M) => R\n /** Optional guard — can redirect */\n guard?: (route: R, state: S) => R\n /** Build new state + effects for the route */\n onNavigate: (state: S, route: R) => [S, E[]]\n }): (state: S, msg: M) => [S, E[]] | null\n}\n\nexport function connectRouter<R>(\n router: Router<R>,\n options?: ConnectOptions<R>,\n): ConnectedRouter<R> {\n let currentRoute: R | null = null\n /**\n * Run guards for a navigation to `newRoute`. Returns the final route\n * to navigate to, or `null` if navigation should be blocked.\n */\n function runGuards(newRoute: R): R | null {\n if (options?.beforeLeave && currentRoute !== null) {\n if (!options.beforeLeave(currentRoute, newRoute)) return null\n }\n if (options?.beforeEnter) {\n const result = options.beforeEnter(newRoute, currentRoute)\n if (result === false) return null\n if (result !== undefined && result !== null && typeof result === 'object') {\n return result as R\n }\n }\n return newRoute\n }\n\n function applyEffect(effect: RouterEffect): void {\n switch (effect.action) {\n case 'push': {\n const target = router.match(effect.path!)\n const finalRoute = runGuards(target)\n if (finalRoute === null) return\n const finalPath = router.href(finalRoute)\n if (router.mode === 'hash') {\n location.hash = finalPath\n } else {\n history.pushState(null, '', finalPath)\n }\n currentRoute = finalRoute\n break\n }\n case 'replace': {\n const target = router.match(effect.path!)\n const finalRoute = runGuards(target)\n if (finalRoute === null) return\n const finalPath = router.href(finalRoute)\n if (router.mode === 'hash') {\n location.replace(finalPath)\n } else {\n history.replaceState(null, '', finalPath)\n }\n currentRoute = finalRoute\n break\n }\n case 'back':\n history.back()\n break\n case 'forward':\n history.forward()\n break\n case 'scroll':\n window.scrollTo(effect.x!, effect.y!)\n break\n }\n }\n\n return {\n push(route) {\n return { type: '__router', action: 'push', path: router.href(route) }\n },\n replace(route) {\n return { type: '__router', action: 'replace', path: router.href(route) }\n },\n back() {\n return { type: '__router', action: 'back' }\n },\n forward() {\n return { type: '__router', action: 'forward' }\n },\n scroll(x, y) {\n return { type: '__router', action: 'scroll', x, y }\n },\n\n handleEffect({ effect }) {\n if (effect.type !== '__router') return false\n applyEffect(effect as RouterEffect)\n return true\n },\n\n listener<M>(send: (msg: M) => void, msgFactory?: (route: R) => M): Node[] {\n const factory = msgFactory ?? ((r: R) => ({ type: 'navigate', route: r }) as M)\n onMount(() => {\n const event = router.mode === 'hash' ? 'hashchange' : 'popstate'\n const handler = () => {\n const input = router.mode === 'hash' ? location.hash : location.pathname + location.search\n const route = router.match(input)\n const finalRoute = runGuards(route)\n if (finalRoute === null) {\n // Guard blocked — restore previous URL\n if (currentRoute !== null) {\n const restorePath = router.href(currentRoute)\n history.pushState(null, '', restorePath)\n }\n return\n }\n currentRoute = finalRoute\n send(factory(finalRoute))\n }\n window.addEventListener(event, handler)\n return () => window.removeEventListener(event, handler)\n })\n return [document.createComment('router')]\n },\n\n link<M>(\n send: (msg: M) => void,\n route: R,\n attrs: Record<string, unknown>,\n children: Node[],\n msgFactory?: (route: R) => M,\n ): HTMLElement {\n const factory = msgFactory ?? ((r: R) => ({ type: 'navigate', route: r }) as M)\n return a(\n {\n ...attrs,\n href: router.href(route),\n onClick: (e: Event) => {\n const me = e as MouseEvent\n if (me.ctrlKey || me.metaKey || me.shiftKey || me.altKey || me.button !== 0) return\n e.preventDefault()\n // Push history — pushState doesn't fire popstate, so no double-nav\n if (router.mode === 'hash') {\n // hashchange will fire the listener, which sends the navigate message\n location.hash = router.href(route)\n return\n }\n history.pushState(null, '', router.href(route))\n send(factory(route))\n },\n },\n children,\n )\n },\n\n createHandler<S, M, E>(config: {\n message?: string\n getRoute: (msg: M) => R\n guard?: (route: R, state: S) => R\n onNavigate: (state: S, route: R) => [S, E[]]\n }): (state: S, msg: M) => [S, E[]] | null {\n const msgType = config.message ?? 'navigate'\n return (state: S, msg: M) => {\n if ((msg as Record<string, unknown>).type !== msgType) return null\n let route = config.getRoute(msg)\n if (config.guard) route = config.guard(route, state)\n return config.onNavigate(state, route)\n }\n },\n }\n}\n"]}
package/dist/index.js CHANGED
@@ -218,3 +218,4 @@ function deepEqual(a, b) {
218
218
  }
219
219
  return true;
220
220
  }
221
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["// ── Path Segment Types ───────────────────────────────────────────\n\ninterface ParamSegment {\n __kind: 'param'\n name: string\n}\n\ninterface RestSegment {\n __kind: 'rest'\n name: string\n}\n\nexport type Segment = string | ParamSegment | RestSegment\n\n/** Named path parameter: matches one segment */\nexport function param(name: string): ParamSegment {\n return { __kind: 'param', name }\n}\n\n/** Rest parameter: matches remaining segments */\nexport function rest(name: string): RestSegment {\n return { __kind: 'rest', name }\n}\n\n// ── Route Definition ─────────────────────────────────────────────\n\ninterface RouteDefOptions {\n query?: string[]\n}\n\nexport interface RouteDef<R> {\n segments: Segment[]\n build: (params: Record<string, string>) => R\n queryKeys: string[]\n /** Optional manual toPath override */\n toPath?: (route: R) => string\n}\n\n/**\n * Define a route with structured path segments.\n *\n * @example\n * route(['article', param('slug')], ({ slug }) => ({ page: 'article', slug }))\n * route(['search'], { query: ['q'] }, ({ q }) => ({ page: 'search', q: q ?? '' }))\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function route<R = any>(\n segments: Segment[],\n buildOrOpts: ((params: Record<string, string>) => R) | RouteDefOptions,\n buildOrToPath?: ((params: Record<string, string>) => R) | { toPath: (route: R) => string },\n): RouteDef<R> {\n if (typeof buildOrOpts === 'function') {\n const tp = buildOrToPath && typeof buildOrToPath === 'object' ? buildOrToPath.toPath : undefined\n return { segments, build: buildOrOpts, queryKeys: [], toPath: tp }\n }\n const opts = buildOrOpts\n const build = buildOrToPath as (params: Record<string, string>) => R\n return { segments, build, queryKeys: opts.query ?? [] }\n}\n\n// ── Router ───────────────────────────────────────────────────────\n\nexport interface RouterConfig<R> {\n mode?: 'hash' | 'history'\n fallback?: R\n}\n\nexport interface Router<R> {\n /** Match a pathname to a Route. Returns fallback if no match. */\n match(pathname: string): R\n /** Format a Route back to a pathname (without hash/history prefix). */\n toPath(route: R): string\n /** Format a Route to a full href (with # prefix in hash mode). */\n href(route: R): string\n /** The configured mode */\n mode: 'hash' | 'history'\n /** All route definitions (for iteration) */\n routes: ReadonlyArray<RouteDef<R>>\n /** The fallback route */\n fallback: R\n}\n\nexport function createRouter<R>(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n defs: RouteDef<any>[],\n config?: RouterConfig<R>,\n): Router<R> {\n const mode = config?.mode ?? 'hash'\n const fallback = config?.fallback ?? defs[0]!.build({})\n\n function matchPathname(pathname: string): R {\n // Separate path from query string\n let queryParams: Record<string, string> = {}\n const qIdx = pathname.indexOf('?')\n const rawPath = qIdx !== -1 ? pathname.slice(0, qIdx) : pathname\n if (qIdx !== -1) {\n queryParams = parseQuery(pathname.slice(qIdx + 1))\n }\n\n const path = rawPath.replace(/^\\/+|\\/+$/g, '')\n const pathSegments = path === '' ? [] : path.split('/')\n\n // Try each route definition\n for (const def of defs) {\n const params = matchDef(def, pathSegments)\n if (params !== null) {\n // Merge query params\n for (const key of def.queryKeys) {\n if (queryParams[key] !== undefined) params[key] = queryParams[key]\n }\n return def.build(params)\n }\n }\n\n return fallback\n }\n\n function formatPath(r: R): string {\n // Try each route definition in reverse order (most specific first)\n for (let i = defs.length - 1; i >= 0; i--) {\n const def = defs[i]!\n\n // If route has a manual toPath, use it\n if (def.toPath) return def.toPath(r)\n\n // Try to extract params from the Route and build the path\n const path = tryFormat(def, r)\n if (path !== null) {\n // Round-trip check: parse the formatted path and verify URL-relevant\n // fields match. Ignore extra fields (like runtime `data`) that aren't\n // part of the URL — they would break the comparison since the route\n // builder produces default values that differ from the actual state.\n const roundTrip = matchPathname(path)\n const urlKeys = getUrlKeys(def)\n if (\n partialEqual(roundTrip as Record<string, unknown>, r as Record<string, unknown>, urlKeys)\n )\n return path\n }\n }\n\n // Last resort: try forward order\n for (const def of defs) {\n if (def.toPath) return def.toPath(r)\n const path = tryFormat(def, r)\n if (path !== null) return path\n }\n\n return '/'\n }\n\n return {\n match(input: string) {\n // Strip hash prefix, preserve query string\n const pathname = mode === 'hash' ? input.replace(/^#\\/?/, '/') : input\n return matchPathname(pathname)\n },\n toPath: formatPath,\n href(r: R) {\n const path = formatPath(r)\n return mode === 'hash' ? `#${path}` : path\n },\n mode,\n routes: defs,\n fallback,\n }\n}\n\n// ── Matching ─────────────────────────────────────────────────────\n\nfunction matchDef<R>(def: RouteDef<R>, pathSegments: string[]): Record<string, string> | null {\n const params: Record<string, string> = {}\n let si = 0\n\n for (let di = 0; di < def.segments.length; di++) {\n const seg = def.segments[di]!\n\n if (typeof seg === 'string') {\n if (si >= pathSegments.length || pathSegments[si] !== seg) return null\n si++\n } else if (seg.__kind === 'param') {\n if (si >= pathSegments.length) return null\n params[seg.name] = decodeURIComponent(pathSegments[si]!)\n si++\n } else if (seg.__kind === 'rest') {\n params[seg.name] = pathSegments.slice(si).map(decodeURIComponent).join('/')\n si = pathSegments.length\n }\n }\n\n // All path segments must be consumed\n if (si !== pathSegments.length) return null\n\n return params\n}\n\nfunction tryFormat<R>(def: RouteDef<R>, r: R): string | null {\n const routeObj = r as Record<string, unknown>\n const parts: string[] = []\n\n for (const seg of def.segments) {\n if (typeof seg === 'string') {\n parts.push(seg)\n } else if (seg.__kind === 'param') {\n const value = routeObj[seg.name]\n if (value === undefined || value === null) return null\n parts.push(encodeURIComponent(String(value)))\n } else if (seg.__kind === 'rest') {\n const value = routeObj[seg.name]\n if (value === undefined || value === null) return null\n parts.push(String(value))\n }\n }\n\n let path = '/' + parts.join('/')\n\n // Append query params if defined\n if (def.queryKeys.length > 0) {\n const qParts: string[] = []\n for (const key of def.queryKeys) {\n const value = routeObj[key]\n if (value !== undefined && value !== null && value !== '') {\n qParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)\n }\n }\n if (qParts.length > 0) path += '?' + qParts.join('&')\n }\n\n return path\n}\n\n// ── Utilities ────────────────────────────────────────────────────\n\nfunction parseQuery(qs: string): Record<string, string> {\n const params: Record<string, string> = {}\n for (const pair of qs.split('&')) {\n const [key, val] = pair.split('=')\n if (key) params[decodeURIComponent(key)] = decodeURIComponent(val ?? '')\n }\n return params\n}\n\n/** Extract URL-relevant field names from a route definition */\nfunction getUrlKeys<R>(def: RouteDef<R>): Set<string> {\n const keys = new Set<string>()\n for (const seg of def.segments) {\n if (typeof seg === 'string') continue\n keys.add(seg.name)\n }\n for (const key of def.queryKeys) {\n keys.add(key)\n }\n // Also include 'page' / 'tab' or any fixed field from the builder\n // by running the builder with empty params and collecting its keys\n const sample = def.build({}) as Record<string, unknown>\n for (const key of Object.keys(sample)) {\n // Include all keys from the builder EXCEPT those with object/array values\n // (which are likely runtime state like `data`)\n const val = sample[key]\n if (val === null || val === undefined || typeof val !== 'object') {\n keys.add(key)\n }\n }\n return keys\n}\n\n/** Compare two objects only on the specified keys */\nfunction partialEqual(\n a: Record<string, unknown>,\n b: Record<string, unknown>,\n keys: Set<string>,\n): boolean {\n for (const key of keys) {\n if (!deepEqual(a[key], b[key])) return false\n }\n return true\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false\n const ka = Object.keys(a as Record<string, unknown>)\n const kb = Object.keys(b as Record<string, unknown>)\n if (ka.length !== kb.length) return false\n for (const key of ka) {\n if (!deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]))\n return false\n }\n return true\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llui/router",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,19 +14,13 @@
14
14
  "types": "./dist/connect.d.ts"
15
15
  }
16
16
  },
17
- "scripts": {
18
- "build": "tsc -p tsconfig.build.json",
19
- "check": "tsc --noEmit -p tsconfig.check.json",
20
- "lint": "eslint src",
21
- "test": "vitest run"
22
- },
23
17
  "peerDependencies": {
24
- "@llui/dom": "^0.0.13"
18
+ "@llui/dom": "^0.0.15"
25
19
  },
26
20
  "devDependencies": {
27
- "@llui/dom": "workspace:*",
28
21
  "typescript": "^6.0.0",
29
- "vitest": "^4.1.2"
22
+ "vitest": "^4.1.2",
23
+ "@llui/dom": "0.0.15"
30
24
  },
31
25
  "sideEffects": false,
32
26
  "description": "LLui router — structured path matching, history/hash mode, link helper",
@@ -50,5 +44,11 @@
50
44
  "homepage": "https://github.com/fponticelli/llui/tree/main/packages/router#readme",
51
45
  "files": [
52
46
  "dist"
53
- ]
54
- }
47
+ ],
48
+ "scripts": {
49
+ "build": "tsc -p tsconfig.build.json",
50
+ "check": "tsc --noEmit -p tsconfig.check.json",
51
+ "lint": "eslint src",
52
+ "test": "vitest run"
53
+ }
54
+ }