@vielzeug/wayfinder 3.1.0 → 26.10.0

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 (71) hide show
  1. package/dist/_dev.cjs +1 -1
  2. package/dist/_dev.cjs.map +1 -1
  3. package/dist/_dev.js +1 -6
  4. package/dist/_dev.js.map +1 -1
  5. package/dist/_navigation.cjs +1 -1
  6. package/dist/_navigation.cjs.map +1 -1
  7. package/dist/_navigation.js +1 -20
  8. package/dist/_navigation.js.map +1 -1
  9. package/dist/compile.cjs +1 -1
  10. package/dist/compile.cjs.map +1 -1
  11. package/dist/compile.js +1 -46
  12. package/dist/compile.js.map +1 -1
  13. package/dist/context.cjs +1 -1
  14. package/dist/context.cjs.map +1 -1
  15. package/dist/context.js +1 -59
  16. package/dist/context.js.map +1 -1
  17. package/dist/errors.cjs +1 -1
  18. package/dist/errors.cjs.map +1 -1
  19. package/dist/errors.d.ts +4 -1
  20. package/dist/errors.d.ts.map +1 -1
  21. package/dist/errors.js +1 -17
  22. package/dist/errors.js.map +1 -1
  23. package/dist/guards.cjs +1 -1
  24. package/dist/guards.cjs.map +1 -1
  25. package/dist/guards.js +1 -9
  26. package/dist/guards.js.map +1 -1
  27. package/dist/history.cjs +1 -1
  28. package/dist/history.cjs.map +1 -1
  29. package/dist/history.d.ts +7 -0
  30. package/dist/history.d.ts.map +1 -1
  31. package/dist/history.js +1 -99
  32. package/dist/history.js.map +1 -1
  33. package/dist/index.cjs +1 -1
  34. package/dist/index.d.ts +6 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -5
  37. package/dist/middleware.cjs +1 -1
  38. package/dist/middleware.cjs.map +1 -1
  39. package/dist/middleware.js +1 -9
  40. package/dist/middleware.js.map +1 -1
  41. package/dist/path.cjs +1 -1
  42. package/dist/path.cjs.map +1 -1
  43. package/dist/path.js +1 -132
  44. package/dist/path.js.map +1 -1
  45. package/dist/phase-mirror.cjs +2 -0
  46. package/dist/phase-mirror.cjs.map +1 -0
  47. package/dist/phase-mirror.d.ts +51 -0
  48. package/dist/phase-mirror.d.ts.map +1 -0
  49. package/dist/phase-mirror.js +2 -0
  50. package/dist/phase-mirror.js.map +1 -0
  51. package/dist/route-signals.cjs +2 -0
  52. package/dist/route-signals.cjs.map +1 -0
  53. package/dist/route-signals.d.ts +23 -0
  54. package/dist/route-signals.d.ts.map +1 -0
  55. package/dist/route-signals.js +2 -0
  56. package/dist/route-signals.js.map +1 -0
  57. package/dist/router.cjs +1 -1
  58. package/dist/router.cjs.map +1 -1
  59. package/dist/router.d.ts +7 -1
  60. package/dist/router.d.ts.map +1 -1
  61. package/dist/router.js +1 -475
  62. package/dist/router.js.map +1 -1
  63. package/dist/types.d.ts +7 -0
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/wayfinder.cjs +1 -1
  66. package/dist/wayfinder.cjs.map +1 -1
  67. package/dist/wayfinder.iife.js +1 -1
  68. package/dist/wayfinder.iife.js.map +1 -1
  69. package/dist/wayfinder.js +1 -1
  70. package/dist/wayfinder.js.map +1 -1
  71. package/package.json +8 -5
package/dist/history.js CHANGED
@@ -1,100 +1,2 @@
1
- import { joinPaths as e, normalizePath as t, stripBase as n } from "./path.js";
2
- //#region src/history.ts
3
- function r() {
4
- return {
5
- back() {
6
- window.history.back();
7
- },
8
- get location() {
9
- return {
10
- hash: window.location.hash,
11
- pathname: window.location.pathname,
12
- search: window.location.search,
13
- state: window.history.state
14
- };
15
- },
16
- onPopstate(e) {
17
- return window.addEventListener("popstate", e), () => window.removeEventListener("popstate", e);
18
- },
19
- push(e, t) {
20
- window.history.pushState(t, "", e);
21
- },
22
- replace(e, t) {
23
- window.history.replaceState(t, "", e);
24
- }
25
- };
26
- }
27
- function i(r = {}) {
28
- let i = t(r.base ?? "/"), a = i === "/" ? "/" : `${i}/`, o = () => {
29
- let t = new URL(window.location.hash.slice(1) || "/", "http://localhost");
30
- return {
31
- hash: t.hash,
32
- pathname: e(i, t.pathname),
33
- search: t.search,
34
- state: window.history.state
35
- };
36
- }, s = (e) => {
37
- let t = new URL(e, "http://localhost"), r = n(t.pathname, i);
38
- return `${a}#${r}${t.search}${t.hash}`;
39
- };
40
- return {
41
- back() {
42
- window.history.back();
43
- },
44
- get location() {
45
- return o();
46
- },
47
- onPopstate(e) {
48
- return window.addEventListener("popstate", e), () => window.removeEventListener("popstate", e);
49
- },
50
- push(e, t) {
51
- window.history.pushState(t, "", s(e));
52
- },
53
- replace(e, t) {
54
- window.history.replaceState(t, "", s(e));
55
- }
56
- };
57
- }
58
- function a(e = "/") {
59
- let t = new URL(e, "http://localhost"), n = [{
60
- hash: t.hash,
61
- pathname: t.pathname,
62
- search: t.search,
63
- state: null
64
- }], r = 0, i = /* @__PURE__ */ new Set();
65
- return {
66
- back() {
67
- r <= 0 || (--r, i.forEach((e) => {
68
- e();
69
- }));
70
- },
71
- get location() {
72
- return n[r];
73
- },
74
- onPopstate(e) {
75
- return i.add(e), () => i.delete(e);
76
- },
77
- push(e, t = null) {
78
- let i = new URL(e, "http://localhost");
79
- n.splice(r + 1), n.push({
80
- hash: i.hash,
81
- pathname: i.pathname,
82
- search: i.search,
83
- state: t
84
- }), r = n.length - 1;
85
- },
86
- replace(e, t = null) {
87
- let i = new URL(e, "http://localhost");
88
- n[r] = {
89
- hash: i.hash,
90
- pathname: i.pathname,
91
- search: i.search,
92
- state: t
93
- };
94
- }
95
- };
96
- }
97
- //#endregion
98
- export { r as createBrowserHistory, i as createHashHistory, a as createMemoryHistory };
99
-
1
+ import{joinPaths as e,normalizePath as t,stripBase as n}from"./path.js";function createBrowserHistory(){return{back(){window.history.back()},get location(){return{hash:window.location.hash,pathname:window.location.pathname,search:window.location.search,state:window.history.state}},onPopstate(e){return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)},push(e,t){window.history.pushState(t,``,e)},replace(e,t){window.history.replaceState(t,``,e)}}}function createHashHistory(r={}){let i=t(r.base??`/`),a=i===`/`?`/`:`${i}/`,location=()=>{let t=new URL(window.location.hash.slice(1)||`/`,`http://localhost`);return{hash:t.hash,pathname:e(i,t.pathname),search:t.search,state:window.history.state}},destination=e=>{let t=new URL(e,`http://localhost`),r=n(t.pathname,i);return`${a}#${r}${t.search}${t.hash}`};return{back(){window.history.back()},href:destination,get location(){return location()},onPopstate(e){return window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)},push(e,t){window.history.pushState(t,``,destination(e))},replace(e,t){window.history.replaceState(t,``,destination(e))}}}function createHistoryForBase(e){return e===`/`?createBrowserHistory():createHashHistory({base:e})}function createMemoryHistory(e=`/`){let t=new URL(e,`http://localhost`),n=[{hash:t.hash,pathname:t.pathname,search:t.search,state:null}],r=0,i=new Set;return{back(){r<=0||(--r,i.forEach(e=>{e()}))},get location(){return n[r]},onPopstate(e){return i.add(e),()=>i.delete(e)},push(e,t=null){let i=new URL(e,`http://localhost`);n.splice(r+1),n.push({hash:i.hash,pathname:i.pathname,search:i.search,state:t}),r=n.length-1},replace(e,t=null){let i=new URL(e,`http://localhost`);n[r]={hash:i.hash,pathname:i.pathname,search:i.search,state:t}}}}export{createBrowserHistory,createHashHistory,createHistoryForBase,createMemoryHistory};
100
2
  //# sourceMappingURL=history.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"history.js","names":[],"sources":["../src/history.ts"],"sourcesContent":["import { joinPaths, normalizePath, stripBase } from './path';\nimport type { HistoryDriver } from './types';\n\n/** Creates a history driver backed by the browser History API. */\nexport function createBrowserHistory(): HistoryDriver {\n return {\n back() {\n window.history.back();\n },\n get location() {\n return {\n hash: window.location.hash,\n pathname: window.location.pathname,\n search: window.location.search,\n // State lives on window.history.state, not window.location.\n state: window.history.state,\n };\n },\n onPopstate(listener) {\n window.addEventListener('popstate', listener);\n\n return () => window.removeEventListener('popstate', listener);\n },\n push(url, state) {\n window.history.pushState(state, '', url);\n },\n replace(url, state) {\n window.history.replaceState(state, '', url);\n },\n };\n}\n\nexport type HashHistoryOptions = { base?: string };\n\nexport function createHashHistory(options: HashHistoryOptions = {}): HistoryDriver {\n const base = normalizePath(options.base ?? '/');\n const baseRoot = base === '/' ? '/' : `${base}/`;\n const location = () => {\n const parsed = new URL(window.location.hash.slice(1) || '/', 'http://localhost');\n\n return {\n hash: parsed.hash,\n pathname: joinPaths(base, parsed.pathname),\n search: parsed.search,\n state: window.history.state,\n };\n };\n const destination = (url: string) => {\n const parsed = new URL(url, 'http://localhost');\n const pathname = stripBase(parsed.pathname, base);\n\n return `${baseRoot}#${pathname}${parsed.search}${parsed.hash}`;\n };\n\n return {\n back() {\n window.history.back();\n },\n get location() {\n return location();\n },\n onPopstate(listener) {\n window.addEventListener('popstate', listener);\n\n return () => window.removeEventListener('popstate', listener);\n },\n push(url, state) {\n window.history.pushState(state, '', destination(url));\n },\n replace(url, state) {\n window.history.replaceState(state, '', destination(url));\n },\n };\n}\n\ntype MemoryLocation = { hash: string; pathname: string; search: string; state: unknown };\n\n/**\n * Creates an in-memory history driver. Suitable for SSR, tests, and non-browser environments.\n *\n * Semantics mirror the browser History API:\n * - `push()` and `replace()` update the location silently (no listener notification),\n * matching `pushState`/`replaceState` which do not fire `popstate`.\n * - `back()` moves one entry back and notifies subscribers, matching a browser back-button press.\n */\nexport function createMemoryHistory(initialPath = '/'): HistoryDriver {\n const parsed = new URL(initialPath, 'http://localhost');\n const stack: MemoryLocation[] = [\n { hash: parsed.hash, pathname: parsed.pathname, search: parsed.search, state: null },\n ];\n let cursor = 0;\n const listeners = new Set<() => void>();\n\n return {\n back() {\n if (cursor <= 0) return;\n\n cursor -= 1;\n listeners.forEach((l) => {\n l();\n });\n },\n get location(): MemoryLocation {\n return stack[cursor]!;\n },\n onPopstate(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n push(url, state = null) {\n const p = new URL(url, 'http://localhost');\n\n stack.splice(cursor + 1);\n stack.push({ hash: p.hash, pathname: p.pathname, search: p.search, state });\n cursor = stack.length - 1;\n // Silent — mirrors browser pushState semantics. Router drives navigation via #handleRoute directly.\n },\n replace(url, state = null) {\n const p = new URL(url, 'http://localhost');\n\n stack[cursor] = { hash: p.hash, pathname: p.pathname, search: p.search, state };\n // Silent — mirrors browser replaceState semantics.\n },\n };\n}\n"],"mappings":";;AAIA,SAAgB,IAAsC;CACpD,OAAO;EACL,OAAO;GACL,OAAO,QAAQ,KAAK;EACtB;EACA,IAAI,WAAW;GACb,OAAO;IACL,MAAM,OAAO,SAAS;IACtB,UAAU,OAAO,SAAS;IAC1B,QAAQ,OAAO,SAAS;IAExB,OAAO,OAAO,QAAQ;GACxB;EACF;EACA,WAAW,GAAU;GAGnB,OAFA,OAAO,iBAAiB,YAAY,CAAQ,SAE/B,OAAO,oBAAoB,YAAY,CAAQ;EAC9D;EACA,KAAK,GAAK,GAAO;GACf,OAAO,QAAQ,UAAU,GAAO,IAAI,CAAG;EACzC;EACA,QAAQ,GAAK,GAAO;GAClB,OAAO,QAAQ,aAAa,GAAO,IAAI,CAAG;EAC5C;CACF;AACF;AAIA,SAAgB,EAAkB,IAA8B,CAAC,GAAkB;CACjF,IAAM,IAAO,EAAc,EAAQ,QAAQ,GAAG,GACxC,IAAW,MAAS,MAAM,MAAM,GAAG,EAAK,IACxC,UAAiB;EACrB,IAAM,IAAS,IAAI,IAAI,OAAO,SAAS,KAAK,MAAM,CAAC,KAAK,KAAK,kBAAkB;EAE/E,OAAO;GACL,MAAM,EAAO;GACb,UAAU,EAAU,GAAM,EAAO,QAAQ;GACzC,QAAQ,EAAO;GACf,OAAO,OAAO,QAAQ;EACxB;CACF,GACM,KAAe,MAAgB;EACnC,IAAM,IAAS,IAAI,IAAI,GAAK,kBAAkB,GACxC,IAAW,EAAU,EAAO,UAAU,CAAI;EAEhD,OAAO,GAAG,EAAS,GAAG,IAAW,EAAO,SAAS,EAAO;CAC1D;CAEA,OAAO;EACL,OAAO;GACL,OAAO,QAAQ,KAAK;EACtB;EACA,IAAI,WAAW;GACb,OAAO,EAAS;EAClB;EACA,WAAW,GAAU;GAGnB,OAFA,OAAO,iBAAiB,YAAY,CAAQ,SAE/B,OAAO,oBAAoB,YAAY,CAAQ;EAC9D;EACA,KAAK,GAAK,GAAO;GACf,OAAO,QAAQ,UAAU,GAAO,IAAI,EAAY,CAAG,CAAC;EACtD;EACA,QAAQ,GAAK,GAAO;GAClB,OAAO,QAAQ,aAAa,GAAO,IAAI,EAAY,CAAG,CAAC;EACzD;CACF;AACF;AAYA,SAAgB,EAAoB,IAAc,KAAoB;CACpE,IAAM,IAAS,IAAI,IAAI,GAAa,kBAAkB,GAChD,IAA0B,CAC9B;EAAE,MAAM,EAAO;EAAM,UAAU,EAAO;EAAU,QAAQ,EAAO;EAAQ,OAAO;CAAK,CACrF,GACI,IAAS,GACP,oBAAY,IAAI,IAAgB;CAEtC,OAAO;EACL,OAAO;GACD,KAAU,MAEd,KACA,EAAU,SAAS,MAAM;IACvB,EAAE;GACJ,CAAC;EACH;EACA,IAAI,WAA2B;GAC7B,OAAO,EAAM;EACf;EACA,WAAW,GAAU;GAGnB,OAFA,EAAU,IAAI,CAAQ,SAET,EAAU,OAAO,CAAQ;EACxC;EACA,KAAK,GAAK,IAAQ,MAAM;GACtB,IAAM,IAAI,IAAI,IAAI,GAAK,kBAAkB;GAIzC,AAFA,EAAM,OAAO,IAAS,CAAC,GACvB,EAAM,KAAK;IAAE,MAAM,EAAE;IAAM,UAAU,EAAE;IAAU,QAAQ,EAAE;IAAQ;GAAM,CAAC,GAC1E,IAAS,EAAM,SAAS;EAE1B;EACA,QAAQ,GAAK,IAAQ,MAAM;GACzB,IAAM,IAAI,IAAI,IAAI,GAAK,kBAAkB;GAEzC,EAAM,KAAU;IAAE,MAAM,EAAE;IAAM,UAAU,EAAE;IAAU,QAAQ,EAAE;IAAQ;GAAM;EAEhF;CACF;AACF"}
1
+ {"version":3,"file":"history.js","names":[],"sources":["../src/history.ts"],"sourcesContent":["import { joinPaths, normalizePath, stripBase } from './path';\nimport type { HistoryDriver } from './types';\n\n/** Creates a history driver backed by the browser History API. */\nexport function createBrowserHistory(): HistoryDriver {\n return {\n back() {\n window.history.back();\n },\n get location() {\n return {\n hash: window.location.hash,\n pathname: window.location.pathname,\n search: window.location.search,\n // State lives on window.history.state, not window.location.\n state: window.history.state,\n };\n },\n onPopstate(listener) {\n window.addEventListener('popstate', listener);\n\n return () => window.removeEventListener('popstate', listener);\n },\n push(url, state) {\n window.history.pushState(state, '', url);\n },\n replace(url, state) {\n window.history.replaceState(state, '', url);\n },\n };\n}\n\nexport type HashHistoryOptions = { base?: string };\n\nexport function createHashHistory(options: HashHistoryOptions = {}): HistoryDriver {\n const base = normalizePath(options.base ?? '/');\n const baseRoot = base === '/' ? '/' : `${base}/`;\n const location = () => {\n const parsed = new URL(window.location.hash.slice(1) || '/', 'http://localhost');\n\n return {\n hash: parsed.hash,\n pathname: joinPaths(base, parsed.pathname),\n search: parsed.search,\n state: window.history.state,\n };\n };\n const destination = (url: string) => {\n const parsed = new URL(url, 'http://localhost');\n const pathname = stripBase(parsed.pathname, base);\n\n return `${baseRoot}#${pathname}${parsed.search}${parsed.hash}`;\n };\n\n return {\n back() {\n window.history.back();\n },\n /** Anchor-ready form of an internal URL: the hash driver stores routes behind `#`. */\n href: destination,\n get location() {\n return location();\n },\n onPopstate(listener) {\n window.addEventListener('popstate', listener);\n\n return () => window.removeEventListener('popstate', listener);\n },\n push(url, state) {\n window.history.pushState(state, '', destination(url));\n },\n replace(url, state) {\n window.history.replaceState(state, '', destination(url));\n },\n };\n}\n\n/**\n * Picks the history driver for a deployment base path: a site at the origin root\n * (`'/'`) gets real History-API paths; an app deployed under a sub-path\n * (`import.meta.env.BASE_URL` like `'/demos/primal/'`) gets the hash driver, so\n * deep links survive a static host that cannot rewrite the base path to the app.\n */\nexport function createHistoryForBase(base: string): HistoryDriver {\n return base === '/' ? createBrowserHistory() : createHashHistory({ base });\n}\n\ntype MemoryLocation = { hash: string; pathname: string; search: string; state: unknown };\n\n/**\n * Creates an in-memory history driver. Suitable for SSR, tests, and non-browser environments.\n *\n * Semantics mirror the browser History API:\n * - `push()` and `replace()` update the location silently (no listener notification),\n * matching `pushState`/`replaceState` which do not fire `popstate`.\n * - `back()` moves one entry back and notifies subscribers, matching a browser back-button press.\n */\nexport function createMemoryHistory(initialPath = '/'): HistoryDriver {\n const parsed = new URL(initialPath, 'http://localhost');\n const stack: MemoryLocation[] = [\n { hash: parsed.hash, pathname: parsed.pathname, search: parsed.search, state: null },\n ];\n let cursor = 0;\n const listeners = new Set<() => void>();\n\n return {\n back() {\n if (cursor <= 0) return;\n\n cursor -= 1;\n listeners.forEach((l) => {\n l();\n });\n },\n get location(): MemoryLocation {\n return stack[cursor]!;\n },\n onPopstate(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n push(url, state = null) {\n const p = new URL(url, 'http://localhost');\n\n stack.splice(cursor + 1);\n stack.push({ hash: p.hash, pathname: p.pathname, search: p.search, state });\n cursor = stack.length - 1;\n // Silent — mirrors browser pushState semantics. Router drives navigation via #handleRoute directly.\n },\n replace(url, state = null) {\n const p = new URL(url, 'http://localhost');\n\n stack[cursor] = { hash: p.hash, pathname: p.pathname, search: p.search, state };\n // Silent — mirrors browser replaceState semantics.\n },\n };\n}\n"],"mappings":"wEAIA,SAAgB,sBAAsC,CACpD,MAAO,CACL,MAAO,CACL,OAAO,QAAQ,KAAK,CACtB,EACA,IAAI,UAAW,CACb,MAAO,CACL,KAAM,OAAO,SAAS,KACtB,SAAU,OAAO,SAAS,SAC1B,OAAQ,OAAO,SAAS,OAExB,MAAO,OAAO,QAAQ,KACxB,CACF,EACA,WAAW,EAAU,CAGnB,OAFA,OAAO,iBAAiB,WAAY,CAAQ,MAE/B,OAAO,oBAAoB,WAAY,CAAQ,CAC9D,EACA,KAAK,EAAK,EAAO,CACf,OAAO,QAAQ,UAAU,EAAO,GAAI,CAAG,CACzC,EACA,QAAQ,EAAK,EAAO,CAClB,OAAO,QAAQ,aAAa,EAAO,GAAI,CAAG,CAC5C,CACF,CACF,CAIA,SAAgB,kBAAkB,EAA8B,CAAC,EAAkB,CACjF,IAAM,EAAO,EAAc,EAAQ,MAAQ,GAAG,EACxC,EAAW,IAAS,IAAM,IAAM,GAAG,EAAK,GACxC,aAAiB,CACrB,IAAM,EAAS,IAAI,IAAI,OAAO,SAAS,KAAK,MAAM,CAAC,GAAK,IAAK,kBAAkB,EAE/E,MAAO,CACL,KAAM,EAAO,KACb,SAAU,EAAU,EAAM,EAAO,QAAQ,EACzC,OAAQ,EAAO,OACf,MAAO,OAAO,QAAQ,KACxB,CACF,EACM,YAAe,GAAgB,CACnC,IAAM,EAAS,IAAI,IAAI,EAAK,kBAAkB,EACxC,EAAW,EAAU,EAAO,SAAU,CAAI,EAEhD,MAAO,GAAG,EAAS,GAAG,IAAW,EAAO,SAAS,EAAO,MAC1D,EAEA,MAAO,CACL,MAAO,CACL,OAAO,QAAQ,KAAK,CACtB,EAEA,KAAM,YACN,IAAI,UAAW,CACb,OAAO,SAAS,CAClB,EACA,WAAW,EAAU,CAGnB,OAFA,OAAO,iBAAiB,WAAY,CAAQ,MAE/B,OAAO,oBAAoB,WAAY,CAAQ,CAC9D,EACA,KAAK,EAAK,EAAO,CACf,OAAO,QAAQ,UAAU,EAAO,GAAI,YAAY,CAAG,CAAC,CACtD,EACA,QAAQ,EAAK,EAAO,CAClB,OAAO,QAAQ,aAAa,EAAO,GAAI,YAAY,CAAG,CAAC,CACzD,CACF,CACF,CAQA,SAAgB,qBAAqB,EAA6B,CAChE,OAAO,IAAS,IAAM,qBAAqB,EAAI,kBAAkB,CAAE,MAAK,CAAC,CAC3E,CAYA,SAAgB,oBAAoB,EAAc,IAAoB,CACpE,IAAM,EAAS,IAAI,IAAI,EAAa,kBAAkB,EAChD,EAA0B,CAC9B,CAAE,KAAM,EAAO,KAAM,SAAU,EAAO,SAAU,OAAQ,EAAO,OAAQ,MAAO,IAAK,CACrF,EACI,EAAS,EACP,EAAY,IAAI,IAEtB,MAAO,CACL,MAAO,CACD,GAAU,IAEd,IACA,EAAU,QAAS,GAAM,CACvB,EAAE,CACJ,CAAC,EACH,EACA,IAAI,UAA2B,CAC7B,OAAO,EAAM,EACf,EACA,WAAW,EAAU,CAGnB,OAFA,EAAU,IAAI,CAAQ,MAET,EAAU,OAAO,CAAQ,CACxC,EACA,KAAK,EAAK,EAAQ,KAAM,CACtB,IAAM,EAAI,IAAI,IAAI,EAAK,kBAAkB,EAEzC,EAAM,OAAO,EAAS,CAAC,EACvB,EAAM,KAAK,CAAE,KAAM,EAAE,KAAM,SAAU,EAAE,SAAU,OAAQ,EAAE,OAAQ,OAAM,CAAC,EAC1E,EAAS,EAAM,OAAS,CAE1B,EACA,QAAQ,EAAK,EAAQ,KAAM,CACzB,IAAM,EAAI,IAAI,IAAI,EAAK,kBAAkB,EAEzC,EAAM,GAAU,CAAE,KAAM,EAAE,KAAM,SAAU,EAAE,SAAU,OAAQ,EAAE,OAAQ,OAAM,CAEhF,CACF,CACF"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./history.cjs"),n=require("./middleware.cjs"),r=require("./router.cjs");exports.WayfinderApiError=e.WayfinderApiError,exports.WayfinderDisposedError=e.WayfinderDisposedError,exports.WayfinderError=e.WayfinderError,exports.WayfinderRedirectLoopError=e.WayfinderRedirectLoopError,exports.WayfinderRouteError=e.WayfinderRouteError,exports.createBrowserHistory=t.createBrowserHistory,exports.createHashHistory=t.createHashHistory,exports.createMemoryHistory=t.createMemoryHistory,exports.createRouter=r.createRouter,exports.redirectTo=n.redirectTo;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./history.cjs"),n=require("./middleware.cjs"),r=require("./route-signals.cjs"),i=require("./phase-mirror.cjs"),a=require("./router.cjs");exports.WayfinderApiError=e.WayfinderApiError,exports.WayfinderConfigError=e.WayfinderConfigError,exports.WayfinderDisposedError=e.WayfinderDisposedError,exports.WayfinderError=e.WayfinderError,exports.WayfinderRedirectLoopError=e.WayfinderRedirectLoopError,exports.WayfinderRouteError=e.WayfinderRouteError,exports.createBrowserHistory=t.createBrowserHistory,exports.createHashHistory=t.createHashHistory,exports.createHistoryForBase=t.createHistoryForBase,exports.createMemoryHistory=t.createMemoryHistory,exports.createPhaseMirror=i.createPhaseMirror,exports.createRouteSignals=r.createRouteSignals,exports.createRouter=a.createRouter,exports.redirectTo=n.redirectTo;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,10 @@
1
- export { WayfinderApiError, WayfinderDisposedError, WayfinderError, WayfinderRedirectLoopError, WayfinderRouteError, } from './errors';
2
- export { createBrowserHistory, createHashHistory, createMemoryHistory, type HashHistoryOptions } from './history';
1
+ export { WayfinderApiError, WayfinderConfigError, WayfinderDisposedError, WayfinderError, WayfinderRedirectLoopError, WayfinderRouteError, } from './errors';
2
+ export { createBrowserHistory, createHashHistory, createHistoryForBase, createMemoryHistory, type HashHistoryOptions, } from './history';
3
3
  export { redirectTo } from './middleware';
4
+ export type { PhaseMirror, PhaseMirrorOptions } from './phase-mirror';
5
+ export { createPhaseMirror } from './phase-mirror';
6
+ export type { RouteSignals } from './route-signals';
7
+ export { createRouteSignals } from './route-signals';
4
8
  export type { Router } from './router';
5
9
  export { createRouter } from './router';
6
10
  export type { BeforeLeaveBlocker, BeforeLeaveOptions, CoerceSearchFn, DataContext, DataFn, HistoryDriver, IsActiveOptions, MaybePromise, Middleware, NamedNavigationTarget, NavigateOptions, NavigationDestination, NavigationStatus, NavigationTarget, PathParams, QueryParams, RawNavigationTarget, ResolvedQueryParams, ResolvedQueryValue, RouteContext, RouteDefinition, RouteLocation, RouteMatch, RouteMatchBranch, RouteMiddleware, RouteName, RouteParams, RoutePathByName, RouterErrorContext, RouterOptions, RouteState, RouteTable, RouteViewMap, RouteViewName, RouteViewRegistry, ScrollDecision, ScrollPosition, Unsubscribe, UntypedNamedNavigationTarget, } from './types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,cAAc,EACd,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAClH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,MAAM,EACN,aAAa,EACb,eAAe,EACf,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,UAAU,EACV,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,WAAW,EACX,4BAA4B,GAC7B,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,EACd,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,YAAY,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,MAAM,EACN,aAAa,EACb,eAAe,EACf,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,UAAU,EACV,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,WAAW,EACX,4BAA4B,GAC7B,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1 @@
1
- import { WayfinderApiError as e, WayfinderDisposedError as t, WayfinderError as n, WayfinderRedirectLoopError as r, WayfinderRouteError as i } from "./errors.js";
2
- import { createBrowserHistory as a, createHashHistory as o, createMemoryHistory as s } from "./history.js";
3
- import { redirectTo as c } from "./middleware.js";
4
- import { createRouter as l } from "./router.js";
5
- export { e as WayfinderApiError, t as WayfinderDisposedError, n as WayfinderError, r as WayfinderRedirectLoopError, i as WayfinderRouteError, a as createBrowserHistory, o as createHashHistory, s as createMemoryHistory, l as createRouter, c as redirectTo };
1
+ import{WayfinderApiError as e,WayfinderConfigError as t,WayfinderDisposedError as n,WayfinderError as r,WayfinderRedirectLoopError as i,WayfinderRouteError as a}from"./errors.js";import{createBrowserHistory as o,createHashHistory as s,createHistoryForBase as c,createMemoryHistory as l}from"./history.js";import{redirectTo as u}from"./middleware.js";import{createRouteSignals as d}from"./route-signals.js";import{createPhaseMirror as f}from"./phase-mirror.js";import{createRouter as p}from"./router.js";export{e as WayfinderApiError,t as WayfinderConfigError,n as WayfinderDisposedError,r as WayfinderError,i as WayfinderRedirectLoopError,a as WayfinderRouteError,o as createBrowserHistory,s as createHashHistory,c as createHistoryForBase,l as createMemoryHistory,f as createPhaseMirror,d as createRouteSignals,p as createRouter,u as redirectTo};
@@ -1,2 +1,2 @@
1
- function e(e,t){return async n=>{await n.navigate(e,t)}}exports.redirectTo=e;
1
+ function redirectTo(e,t){return async n=>{await n.navigate(e,t)}}exports.redirectTo=redirectTo;
2
2
  //# sourceMappingURL=middleware.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.cjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["import type { Middleware, NamedNavigationTarget, NavigateOptions, RawNavigationTarget, RouteTable } from './types';\n\n/**\n * Middleware helper that redirects navigation to another route.\n * Attach to a route's `middleware` array for declarative redirect patterns.\n *\n * Note: `next()` is intentionally not called — this middleware blocks the chain\n * and immediately navigates to the target.\n *\n * @example\n * // Redirect /old → /new\n * routes: {\n * old: { middleware: [redirectTo({ path: '/new' })], path: '/old' },\n * new: { path: '/new' },\n * }\n */\nexport function redirectTo<TRoutes extends RouteTable = RouteTable>(\n target: NamedNavigationTarget<TRoutes> | RawNavigationTarget | string,\n options?: NavigateOptions,\n): Middleware<TRoutes> {\n return async (context) => {\n await context.navigate(target, options);\n };\n}\n"],"mappings":"AAgBA,SAAgB,EACd,EACA,EACqB,CACrB,OAAO,KAAO,IAAY,CACxB,MAAM,EAAQ,SAAS,EAAQ,CAAO,CACxC,CACF"}
1
+ {"version":3,"file":"middleware.cjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["import type { Middleware, NamedNavigationTarget, NavigateOptions, RawNavigationTarget, RouteTable } from './types';\n\n/**\n * Middleware helper that redirects navigation to another route.\n * Attach to a route's `middleware` array for declarative redirect patterns.\n *\n * Note: `next()` is intentionally not called — this middleware blocks the chain\n * and immediately navigates to the target.\n *\n * @example\n * // Redirect /old → /new\n * routes: {\n * old: { middleware: [redirectTo({ path: '/new' })], path: '/old' },\n * new: { path: '/new' },\n * }\n */\nexport function redirectTo<TRoutes extends RouteTable = RouteTable>(\n target: NamedNavigationTarget<TRoutes> | RawNavigationTarget | string,\n options?: NavigateOptions,\n): Middleware<TRoutes> {\n return async (context) => {\n await context.navigate(target, options);\n };\n}\n"],"mappings":"AAgBA,SAAgB,WACd,EACA,EACqB,CACrB,OAAO,KAAO,IAAY,CACxB,MAAM,EAAQ,SAAS,EAAQ,CAAO,CACxC,CACF"}
@@ -1,10 +1,2 @@
1
- //#region src/middleware.ts
2
- function e(e, t) {
3
- return async (n) => {
4
- await n.navigate(e, t);
5
- };
6
- }
7
- //#endregion
8
- export { e as redirectTo };
9
-
1
+ function redirectTo(e,t){return async n=>{await n.navigate(e,t)}}export{redirectTo};
10
2
  //# sourceMappingURL=middleware.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["import type { Middleware, NamedNavigationTarget, NavigateOptions, RawNavigationTarget, RouteTable } from './types';\n\n/**\n * Middleware helper that redirects navigation to another route.\n * Attach to a route's `middleware` array for declarative redirect patterns.\n *\n * Note: `next()` is intentionally not called — this middleware blocks the chain\n * and immediately navigates to the target.\n *\n * @example\n * // Redirect /old → /new\n * routes: {\n * old: { middleware: [redirectTo({ path: '/new' })], path: '/old' },\n * new: { path: '/new' },\n * }\n */\nexport function redirectTo<TRoutes extends RouteTable = RouteTable>(\n target: NamedNavigationTarget<TRoutes> | RawNavigationTarget | string,\n options?: NavigateOptions,\n): Middleware<TRoutes> {\n return async (context) => {\n await context.navigate(target, options);\n };\n}\n"],"mappings":";AAgBA,SAAgB,EACd,GACA,GACqB;CACrB,OAAO,OAAO,MAAY;EACxB,MAAM,EAAQ,SAAS,GAAQ,CAAO;CACxC;AACF"}
1
+ {"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["import type { Middleware, NamedNavigationTarget, NavigateOptions, RawNavigationTarget, RouteTable } from './types';\n\n/**\n * Middleware helper that redirects navigation to another route.\n * Attach to a route's `middleware` array for declarative redirect patterns.\n *\n * Note: `next()` is intentionally not called — this middleware blocks the chain\n * and immediately navigates to the target.\n *\n * @example\n * // Redirect /old → /new\n * routes: {\n * old: { middleware: [redirectTo({ path: '/new' })], path: '/old' },\n * new: { path: '/new' },\n * }\n */\nexport function redirectTo<TRoutes extends RouteTable = RouteTable>(\n target: NamedNavigationTarget<TRoutes> | RawNavigationTarget | string,\n options?: NavigateOptions,\n): Middleware<TRoutes> {\n return async (context) => {\n await context.navigate(target, options);\n };\n}\n"],"mappings":"AAgBA,SAAgB,WACd,EACA,EACqB,CACrB,OAAO,KAAO,IAAY,CACxB,MAAM,EAAQ,SAAS,EAAQ,CAAO,CACxC,CACF"}
package/dist/path.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs");function t(e){let t=`/${e}`.replace(/\/+/g,`/`);return t!==`/`&&t.endsWith(`/`)?t.slice(0,-1):t}function n(e,n){return t(`${e}/${n}`)}function r(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function i(n){let i=t(n);if(i===`/`)return{paramNames:[],pattern:/^\/$/,prefixPattern:/^\/$/};let a=[],o=i.slice(1).split(`/`),s=[`^`],c=[`^`];for(let t=0;t<o.length;t+=1){let i=o[t];if(i===`*`){if(t!==o.length-1)throw new e.WayfinderRouteError(`Wildcard "*" must be the final segment in path: ${n}`);s.push(`(?:/.*)?`);break}if(i.startsWith(`:`)&&i.endsWith(`*`)){if(t!==o.length-1)throw new e.WayfinderRouteError(`Wildcard param must be final segment in path: ${n}`);let r=i.slice(1,-1);if(!/^\w+$/.test(r))throw new e.WayfinderRouteError(`Invalid param name ":${r}" in path "${n}". Param names must only contain word characters (a–z, A–Z, 0–9, _).`);a.push(r),s.push(t===o.length-1?`(?:/(.*))?`:`/(.*)`),c.push(`/.*`);break}if(i.startsWith(`:`)){let t=i.slice(1);if(!/^\w+$/.test(t))throw new e.WayfinderRouteError(`Invalid param name ":${t}" in path "${n}". Param names must only contain word characters (a–z, A–Z, 0–9, _).`);a.push(t),s.push(`/([^/]+)`),c.push(`/[^/]+`);continue}let l=r(i);s.push(`/${l}`),c.push(`/${l}`)}return s.push(`$`),c.push(`(?:/.*)?$`),{paramNames:a,pattern:new RegExp(s.join(``)),prefixPattern:new RegExp(c.join(``))}}function a(e,t){let n=t.matcher.pattern.exec(e);if(!n)return null;let r={};return t.matcher.paramNames.forEach((e,t)=>{let i=n[t+1]??``;try{r[e]=decodeURIComponent(i)}catch{r[e]=i}}),r}function o(e,t){for(let n of t){let t=a(e,n);if(t)return{params:t,record:n}}return{params:{}}}function s(e,t){return t.matcher.prefixPattern.test(e)}function c(e){let t=new URLSearchParams(e),n={},r=(e,t)=>{Object.defineProperty(n,e,{configurable:!0,enumerable:!0,value:t,writable:!0})};for(let[e,i]of t.entries()){let t=Object.hasOwn(n,e)?n[e]:void 0;if(t===void 0){r(e,i);continue}if(Array.isArray(t)){t.push(i);continue}r(e,[t,i])}return n}function l(t,r,i={},a){let o=r.replace(/:(\w+)(\*)?/g,(t,n,r)=>{let a=i[n];if(a==null)throw new e.WayfinderRouteError(`Missing path param: ${n}`);return r?a.split(`/`).map(e=>encodeURIComponent(e)).join(`/`):encodeURIComponent(a)});if(a&&Object.keys(a).length){let e=new URLSearchParams;Object.entries(a).forEach(([t,n])=>{Array.isArray(n)?n.forEach(n=>{e.append(t,String(n))}):e.set(t,String(n))}),o+=`?${e.toString()}`}return n(t,o)}function u(e,n=`/`){let r=t(e),i=t(n);if(i===`/`)return r;if(r===i)return`/`;let a=`${i}/`;return r.startsWith(a)?t(r.slice(i.length)):r}function d(e,t){return{hash:t.location.hash.replace(/^#/,``),historyState:t.location.state,pathname:u(t.location.pathname||`/`,e),query:c(t.location.search||``)}}exports.buildUrl=l,exports.compilePathMatcher=i,exports.joinPaths=n,exports.matchRecord=a,exports.matchRouteFor=o,exports.matchesPrefix=s,exports.normalizePath=t,exports.parseQuery=c,exports.readLocation=d,exports.stripBase=u;
1
+ const e=require("./errors.cjs");function normalizePath(e){let t=`/${e}`.replace(/\/+/g,`/`);return t!==`/`&&t.endsWith(`/`)?t.slice(0,-1):t}function joinPaths(e,t){return normalizePath(`${e}/${t}`)}function escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function compilePathMatcher(t){let n=normalizePath(t);if(n===`/`)return{paramNames:[],pattern:/^\/$/,prefixPattern:/^\/$/};let r=[],i=n.slice(1).split(`/`),a=[`^`],o=[`^`];for(let n=0;n<i.length;n+=1){let s=i[n];if(s===`*`){if(n!==i.length-1)throw new e.WayfinderConfigError(`Wildcard "*" must be the final segment in path: ${t}`);a.push(`(?:/.*)?`);break}if(s.startsWith(`:`)&&s.endsWith(`*`)){if(n!==i.length-1)throw new e.WayfinderConfigError(`Wildcard param must be final segment in path: ${t}`);let c=s.slice(1,-1);if(!/^\w+$/.test(c))throw new e.WayfinderConfigError(`Invalid param name ":${c}" in path "${t}". Param names must only contain word characters (a–z, A–Z, 0–9, _).`);r.push(c),a.push(n===i.length-1?`(?:/(.*))?`:`/(.*)`),o.push(`/.*`);break}if(s.startsWith(`:`)){let n=s.slice(1);if(!/^\w+$/.test(n))throw new e.WayfinderConfigError(`Invalid param name ":${n}" in path "${t}". Param names must only contain word characters (a–z, A–Z, 0–9, _).`);r.push(n),a.push(`/([^/]+)`),o.push(`/[^/]+`);continue}let c=escapeRegex(s);a.push(`/${c}`),o.push(`/${c}`)}return a.push(`$`),o.push(`(?:/.*)?$`),{paramNames:r,pattern:new RegExp(a.join(``)),prefixPattern:new RegExp(o.join(``))}}function matchRecord(e,t){let n=t.matcher.pattern.exec(e);if(!n)return null;let r={};return t.matcher.paramNames.forEach((e,t)=>{let i=n[t+1]??``;try{r[e]=decodeURIComponent(i)}catch{r[e]=i}}),r}function matchRouteFor(e,t){for(let n of t){let t=matchRecord(e,n);if(t)return{params:t,record:n}}return{params:{}}}function matchesPrefix(e,t){return t.matcher.prefixPattern.test(e)}function parseQuery(e){let t=new URLSearchParams(e),n={},setQueryValue=(e,t)=>{Object.defineProperty(n,e,{configurable:!0,enumerable:!0,value:t,writable:!0})};for(let[e,r]of t.entries()){let t=Object.hasOwn(n,e)?n[e]:void 0;if(t===void 0){setQueryValue(e,r);continue}if(Array.isArray(t)){t.push(r);continue}setQueryValue(e,[t,r])}return n}function buildUrl(t,n,r={},i){let a=n.replace(/:(\w+)(\*)?/g,(t,n,i)=>{let a=r[n];if(a==null)throw new e.WayfinderRouteError(`Missing path param: ${n}`);return i?a.split(`/`).map(e=>encodeURIComponent(e)).join(`/`):encodeURIComponent(a)});if(i&&Object.keys(i).length){let e=new URLSearchParams;Object.entries(i).forEach(([t,n])=>{Array.isArray(n)?n.forEach(n=>{e.append(t,String(n))}):e.set(t,String(n))}),a+=`?${e.toString()}`}return joinPaths(t,a)}function stripBase(e,t=`/`){let n=normalizePath(e),r=normalizePath(t);if(r===`/`)return n;if(n===r)return`/`;let i=`${r}/`;return n.startsWith(i)?normalizePath(n.slice(r.length)):n}function readLocation(e,t){return{hash:t.location.hash.replace(/^#/,``),historyState:t.location.state,pathname:stripBase(t.location.pathname||`/`,e),query:parseQuery(t.location.search||``)}}exports.buildUrl=buildUrl,exports.compilePathMatcher=compilePathMatcher,exports.joinPaths=joinPaths,exports.matchRecord=matchRecord,exports.matchRouteFor=matchRouteFor,exports.matchesPrefix=matchesPrefix,exports.normalizePath=normalizePath,exports.parseQuery=parseQuery,exports.readLocation=readLocation,exports.stripBase=stripBase;
2
2
  //# sourceMappingURL=path.cjs.map
package/dist/path.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"path.cjs","names":[],"sources":["../src/path.ts"],"sourcesContent":["import { WayfinderRouteError } from './errors';\nimport type { QueryParams, ResolvedQueryParams, RouteLocation, RouteMatcher, RouteParams, RouteRecord } from './types';\n\n/** Ensure leading slash, collapse duplicate slashes, preserve root. */\nexport function normalizePath(path: string): string {\n const normalized = `/${path}`.replace(/\\/+/g, '/');\n\n return normalized !== '/' && normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;\n}\n\nexport function joinPaths(base: string, path: string): string {\n return normalizePath(`${base}/${path}`);\n}\n\nfunction escapeRegex(input: string): string {\n return input.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport function compilePathMatcher(path: string): RouteMatcher {\n const normalized = normalizePath(path);\n\n if (normalized === '/') {\n return {\n paramNames: [],\n pattern: /^\\/$/,\n prefixPattern: /^\\/$/,\n };\n }\n\n const paramNames: string[] = [];\n const segments = normalized.slice(1).split('/');\n const regexParts: string[] = ['^'];\n const prefixParts: string[] = ['^'];\n\n for (let i = 0; i < segments.length; i += 1) {\n const segment = segments[i]!;\n\n if (segment === '*') {\n if (i !== segments.length - 1) {\n throw new WayfinderRouteError(`Wildcard \"*\" must be the final segment in path: ${path}`);\n }\n\n regexParts.push('(?:/.*)?');\n break;\n }\n\n if (segment.startsWith(':') && segment.endsWith('*')) {\n if (i !== segments.length - 1) {\n throw new WayfinderRouteError(`Wildcard param must be final segment in path: ${path}`);\n }\n\n const name = segment.slice(1, -1);\n\n if (!/^\\w+$/.test(name)) {\n throw new WayfinderRouteError(\n `Invalid param name \":${name}\" in path \"${path}\". Param names must only contain word characters (a–z, A–Z, 0–9, _).`,\n );\n }\n\n paramNames.push(name);\n regexParts.push(i === segments.length - 1 ? '(?:/(.*))?' : '/(.*)');\n prefixParts.push('/.*');\n break;\n }\n\n if (segment.startsWith(':')) {\n const name = segment.slice(1);\n\n if (!/^\\w+$/.test(name)) {\n throw new WayfinderRouteError(\n `Invalid param name \":${name}\" in path \"${path}\". Param names must only contain word characters (a–z, A–Z, 0–9, _).`,\n );\n }\n\n paramNames.push(name);\n regexParts.push('/([^/]+)');\n prefixParts.push('/[^/]+');\n continue;\n }\n\n const escaped = escapeRegex(segment);\n\n regexParts.push(`/${escaped}`);\n prefixParts.push(`/${escaped}`);\n }\n\n regexParts.push('$');\n prefixParts.push('(?:/.*)?$');\n\n return {\n paramNames,\n pattern: new RegExp(regexParts.join('')),\n prefixPattern: new RegExp(prefixParts.join('')),\n };\n}\n\nexport function matchRecord(pathname: string, record: RouteRecord): RouteParams | null {\n const match = record.matcher.pattern.exec(pathname);\n\n if (!match) return null;\n\n const params: RouteParams = {};\n\n record.matcher.paramNames.forEach((name: string, index: number) => {\n const value = match[index + 1] ?? '';\n\n try {\n params[name] = decodeURIComponent(value);\n } catch {\n params[name] = value;\n }\n });\n\n return params;\n}\n\nexport function matchRouteFor(\n pathname: string,\n records: readonly RouteRecord[],\n): { params: RouteParams; record?: RouteRecord } {\n for (const record of records) {\n const params = matchRecord(pathname, record);\n\n if (params) return { params, record };\n }\n\n return { params: {} };\n}\n\nexport function matchesPrefix(pathname: string, record: RouteRecord): boolean {\n return record.matcher.prefixPattern.test(pathname);\n}\n\n/**\n * Parse `?foo=a&foo=b&bar=c` into `{ foo: ['a', 'b'], bar: 'c' }`.\n *\n * @security Query keys come directly from the (attacker-controlled) URL. Plain bracket\n * assignment (`out[key] = value`) would let a key like `__proto__` reach the inherited\n * `Object.prototype.__proto__` setter and silently rewrite `out`'s own prototype instead of\n * storing a value — `setQueryValue` uses `Object.defineProperty` so every key, including\n * `__proto__`/`constructor`/`prototype`, is always stored as a plain own data property.\n */\nexport function parseQuery(queryString: string): QueryParams {\n const search = new URLSearchParams(queryString);\n const out: QueryParams = {};\n\n const setQueryValue = (key: string, value: QueryParams[string]): void => {\n Object.defineProperty(out, key, { configurable: true, enumerable: true, value, writable: true });\n };\n\n for (const [key, value] of search.entries()) {\n const existing = Object.hasOwn(out, key) ? out[key] : undefined;\n\n if (existing === undefined) {\n setQueryValue(key, value);\n continue;\n }\n\n if (Array.isArray(existing)) {\n existing.push(value);\n continue;\n }\n\n setQueryValue(key, [existing, value]);\n }\n\n return out;\n}\n\n/** Build a URL from a path pattern, params, and query, respecting the router base. */\nexport function buildUrl(base: string, pattern: string, params: RouteParams = {}, query?: ResolvedQueryParams): string {\n let path = pattern.replace(/:(\\w+)(\\*)?/g, (_match, key: string, isWildcard: string) => {\n const value = params[key];\n\n if (value == null) throw new WayfinderRouteError(`Missing path param: ${key}`);\n\n if (isWildcard) {\n return value\n .split('/')\n .map((segment) => encodeURIComponent(segment))\n .join('/');\n }\n\n return encodeURIComponent(value);\n });\n\n if (query && Object.keys(query).length) {\n const search = new URLSearchParams();\n\n Object.entries(query).forEach(([key, value]) => {\n if (Array.isArray(value))\n value.forEach((item) => {\n search.append(key, String(item));\n });\n else search.set(key, String(value));\n });\n\n path += `?${search.toString()}`;\n }\n\n return joinPaths(base, path);\n}\n\n// ─── Location helpers (formerly in resolve.ts) ────────────────────────────────\n\nexport function stripBase(pathname: string, base = '/'): string {\n const normalizedPath = normalizePath(pathname);\n const normalizedBase = normalizePath(base);\n\n if (normalizedBase === '/') return normalizedPath;\n\n if (normalizedPath === normalizedBase) return '/';\n\n const prefix = `${normalizedBase}/`;\n\n return normalizedPath.startsWith(prefix)\n ? normalizePath(normalizedPath.slice(normalizedBase.length))\n : normalizedPath;\n}\n\nexport function readLocation(\n base: string,\n history: { location: { hash: string; pathname: string; search: string; state: unknown } },\n): RouteLocation {\n return {\n hash: history.location.hash.replace(/^#/, ''),\n historyState: history.location.state,\n pathname: stripBase(history.location.pathname || '/', base),\n query: parseQuery(history.location.search || ''),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"path.cjs","names":[],"sources":["../src/path.ts"],"sourcesContent":["import { WayfinderConfigError, WayfinderRouteError } from './errors';\nimport type { QueryParams, ResolvedQueryParams, RouteLocation, RouteMatcher, RouteParams, RouteRecord } from './types';\n\n/** Ensure leading slash, collapse duplicate slashes, preserve root. */\nexport function normalizePath(path: string): string {\n const normalized = `/${path}`.replace(/\\/+/g, '/');\n\n return normalized !== '/' && normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;\n}\n\nexport function joinPaths(base: string, path: string): string {\n return normalizePath(`${base}/${path}`);\n}\n\nfunction escapeRegex(input: string): string {\n return input.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport function compilePathMatcher(path: string): RouteMatcher {\n const normalized = normalizePath(path);\n\n if (normalized === '/') {\n return {\n paramNames: [],\n pattern: /^\\/$/,\n prefixPattern: /^\\/$/,\n };\n }\n\n const paramNames: string[] = [];\n const segments = normalized.slice(1).split('/');\n const regexParts: string[] = ['^'];\n const prefixParts: string[] = ['^'];\n\n for (let i = 0; i < segments.length; i += 1) {\n const segment = segments[i]!;\n\n if (segment === '*') {\n if (i !== segments.length - 1) {\n throw new WayfinderConfigError(`Wildcard \"*\" must be the final segment in path: ${path}`);\n }\n\n regexParts.push('(?:/.*)?');\n break;\n }\n\n if (segment.startsWith(':') && segment.endsWith('*')) {\n if (i !== segments.length - 1) {\n throw new WayfinderConfigError(`Wildcard param must be final segment in path: ${path}`);\n }\n\n const name = segment.slice(1, -1);\n\n if (!/^\\w+$/.test(name)) {\n throw new WayfinderConfigError(\n `Invalid param name \":${name}\" in path \"${path}\". Param names must only contain word characters (a–z, A–Z, 0–9, _).`,\n );\n }\n\n paramNames.push(name);\n regexParts.push(i === segments.length - 1 ? '(?:/(.*))?' : '/(.*)');\n prefixParts.push('/.*');\n break;\n }\n\n if (segment.startsWith(':')) {\n const name = segment.slice(1);\n\n if (!/^\\w+$/.test(name)) {\n throw new WayfinderConfigError(\n `Invalid param name \":${name}\" in path \"${path}\". Param names must only contain word characters (a–z, A–Z, 0–9, _).`,\n );\n }\n\n paramNames.push(name);\n regexParts.push('/([^/]+)');\n prefixParts.push('/[^/]+');\n continue;\n }\n\n const escaped = escapeRegex(segment);\n\n regexParts.push(`/${escaped}`);\n prefixParts.push(`/${escaped}`);\n }\n\n regexParts.push('$');\n prefixParts.push('(?:/.*)?$');\n\n return {\n paramNames,\n pattern: new RegExp(regexParts.join('')),\n prefixPattern: new RegExp(prefixParts.join('')),\n };\n}\n\nexport function matchRecord(pathname: string, record: RouteRecord): RouteParams | null {\n const match = record.matcher.pattern.exec(pathname);\n\n if (!match) return null;\n\n const params: RouteParams = {};\n\n record.matcher.paramNames.forEach((name: string, index: number) => {\n const value = match[index + 1] ?? '';\n\n try {\n params[name] = decodeURIComponent(value);\n } catch {\n params[name] = value;\n }\n });\n\n return params;\n}\n\nexport function matchRouteFor(\n pathname: string,\n records: readonly RouteRecord[],\n): { params: RouteParams; record?: RouteRecord } {\n for (const record of records) {\n const params = matchRecord(pathname, record);\n\n if (params) return { params, record };\n }\n\n return { params: {} };\n}\n\nexport function matchesPrefix(pathname: string, record: RouteRecord): boolean {\n return record.matcher.prefixPattern.test(pathname);\n}\n\n/**\n * Parse `?foo=a&foo=b&bar=c` into `{ foo: ['a', 'b'], bar: 'c' }`.\n *\n * @security Query keys come directly from the (attacker-controlled) URL. Plain bracket\n * assignment (`out[key] = value`) would let a key like `__proto__` reach the inherited\n * `Object.prototype.__proto__` setter and silently rewrite `out`'s own prototype instead of\n * storing a value — `setQueryValue` uses `Object.defineProperty` so every key, including\n * `__proto__`/`constructor`/`prototype`, is always stored as a plain own data property.\n */\nexport function parseQuery(queryString: string): QueryParams {\n const search = new URLSearchParams(queryString);\n const out: QueryParams = {};\n\n const setQueryValue = (key: string, value: QueryParams[string]): void => {\n Object.defineProperty(out, key, { configurable: true, enumerable: true, value, writable: true });\n };\n\n for (const [key, value] of search.entries()) {\n const existing = Object.hasOwn(out, key) ? out[key] : undefined;\n\n if (existing === undefined) {\n setQueryValue(key, value);\n continue;\n }\n\n if (Array.isArray(existing)) {\n existing.push(value);\n continue;\n }\n\n setQueryValue(key, [existing, value]);\n }\n\n return out;\n}\n\n/** Build a URL from a path pattern, params, and query, respecting the router base. */\nexport function buildUrl(base: string, pattern: string, params: RouteParams = {}, query?: ResolvedQueryParams): string {\n let path = pattern.replace(/:(\\w+)(\\*)?/g, (_match, key: string, isWildcard: string) => {\n const value = params[key];\n\n if (value == null) throw new WayfinderRouteError(`Missing path param: ${key}`);\n\n if (isWildcard) {\n return value\n .split('/')\n .map((segment) => encodeURIComponent(segment))\n .join('/');\n }\n\n return encodeURIComponent(value);\n });\n\n if (query && Object.keys(query).length) {\n const search = new URLSearchParams();\n\n Object.entries(query).forEach(([key, value]) => {\n if (Array.isArray(value))\n value.forEach((item) => {\n search.append(key, String(item));\n });\n else search.set(key, String(value));\n });\n\n path += `?${search.toString()}`;\n }\n\n return joinPaths(base, path);\n}\n\n// ─── Location helpers (formerly in resolve.ts) ────────────────────────────────\n\nexport function stripBase(pathname: string, base = '/'): string {\n const normalizedPath = normalizePath(pathname);\n const normalizedBase = normalizePath(base);\n\n if (normalizedBase === '/') return normalizedPath;\n\n if (normalizedPath === normalizedBase) return '/';\n\n const prefix = `${normalizedBase}/`;\n\n return normalizedPath.startsWith(prefix)\n ? normalizePath(normalizedPath.slice(normalizedBase.length))\n : normalizedPath;\n}\n\nexport function readLocation(\n base: string,\n history: { location: { hash: string; pathname: string; search: string; state: unknown } },\n): RouteLocation {\n return {\n hash: history.location.hash.replace(/^#/, ''),\n historyState: history.location.state,\n pathname: stripBase(history.location.pathname || '/', base),\n query: parseQuery(history.location.search || ''),\n };\n}\n"],"mappings":"gCAIA,SAAgB,cAAc,EAAsB,CAClD,IAAM,EAAa,IAAI,IAAO,QAAQ,OAAQ,GAAG,EAEjD,OAAO,IAAe,KAAO,EAAW,SAAS,GAAG,EAAI,EAAW,MAAM,EAAG,EAAE,EAAI,CACpF,CAEA,SAAgB,UAAU,EAAc,EAAsB,CAC5D,OAAO,cAAc,GAAG,EAAK,GAAG,GAAM,CACxC,CAEA,SAAS,YAAY,EAAuB,CAC1C,OAAO,EAAM,QAAQ,sBAAuB,MAAM,CACpD,CAEA,SAAgB,mBAAmB,EAA4B,CAC7D,IAAM,EAAa,cAAc,CAAI,EAErC,GAAI,IAAe,IACjB,MAAO,CACL,WAAY,CAAC,EACb,QAAS,OACT,cAAe,MACjB,EAGF,IAAM,EAAuB,CAAC,EACxB,EAAW,EAAW,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,EACxC,EAAuB,CAAC,GAAG,EAC3B,EAAwB,CAAC,GAAG,EAElC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAS,OAAQ,GAAK,EAAG,CAC3C,IAAM,EAAU,EAAS,GAEzB,GAAI,IAAY,IAAK,CACnB,GAAI,IAAM,EAAS,OAAS,EAC1B,MAAM,IAAI,EAAA,qBAAqB,mDAAmD,GAAM,EAG1F,EAAW,KAAK,UAAU,EAC1B,KACF,CAEA,GAAI,EAAQ,WAAW,GAAG,GAAK,EAAQ,SAAS,GAAG,EAAG,CACpD,GAAI,IAAM,EAAS,OAAS,EAC1B,MAAM,IAAI,EAAA,qBAAqB,iDAAiD,GAAM,EAGxF,IAAM,EAAO,EAAQ,MAAM,EAAG,EAAE,EAEhC,GAAI,CAAC,QAAQ,KAAK,CAAI,EACpB,MAAM,IAAI,EAAA,qBACR,wBAAwB,EAAK,aAAa,EAAK,qEACjD,EAGF,EAAW,KAAK,CAAI,EACpB,EAAW,KAAK,IAAM,EAAS,OAAS,EAAI,aAAe,OAAO,EAClE,EAAY,KAAK,KAAK,EACtB,KACF,CAEA,GAAI,EAAQ,WAAW,GAAG,EAAG,CAC3B,IAAM,EAAO,EAAQ,MAAM,CAAC,EAE5B,GAAI,CAAC,QAAQ,KAAK,CAAI,EACpB,MAAM,IAAI,EAAA,qBACR,wBAAwB,EAAK,aAAa,EAAK,qEACjD,EAGF,EAAW,KAAK,CAAI,EACpB,EAAW,KAAK,UAAU,EAC1B,EAAY,KAAK,QAAQ,EACzB,QACF,CAEA,IAAM,EAAU,YAAY,CAAO,EAEnC,EAAW,KAAK,IAAI,GAAS,EAC7B,EAAY,KAAK,IAAI,GAAS,CAChC,CAKA,OAHA,EAAW,KAAK,GAAG,EACnB,EAAY,KAAK,WAAW,EAErB,CACL,aACA,QAAS,IAAI,OAAO,EAAW,KAAK,EAAE,CAAC,EACvC,cAAe,IAAI,OAAO,EAAY,KAAK,EAAE,CAAC,CAChD,CACF,CAEA,SAAgB,YAAY,EAAkB,EAAyC,CACrF,IAAM,EAAQ,EAAO,QAAQ,QAAQ,KAAK,CAAQ,EAElD,GAAI,CAAC,EAAO,OAAO,KAEnB,IAAM,EAAsB,CAAC,EAY7B,OAVA,EAAO,QAAQ,WAAW,SAAS,EAAc,IAAkB,CACjE,IAAM,EAAQ,EAAM,EAAQ,IAAM,GAElC,GAAI,CACF,EAAO,GAAQ,mBAAmB,CAAK,CACzC,MAAQ,CACN,EAAO,GAAQ,CACjB,CACF,CAAC,EAEM,CACT,CAEA,SAAgB,cACd,EACA,EAC+C,CAC/C,IAAK,IAAM,KAAU,EAAS,CAC5B,IAAM,EAAS,YAAY,EAAU,CAAM,EAE3C,GAAI,EAAQ,MAAO,CAAE,SAAQ,QAAO,CACtC,CAEA,MAAO,CAAE,OAAQ,CAAC,CAAE,CACtB,CAEA,SAAgB,cAAc,EAAkB,EAA8B,CAC5E,OAAO,EAAO,QAAQ,cAAc,KAAK,CAAQ,CACnD,CAWA,SAAgB,WAAW,EAAkC,CAC3D,IAAM,EAAS,IAAI,gBAAgB,CAAW,EACxC,EAAmB,CAAC,EAEpB,eAAiB,EAAa,IAAqC,CACvE,OAAO,eAAe,EAAK,EAAK,CAAE,aAAc,GAAM,WAAY,GAAM,QAAO,SAAU,EAAK,CAAC,CACjG,EAEA,IAAK,GAAM,CAAC,EAAK,KAAU,EAAO,QAAQ,EAAG,CAC3C,IAAM,EAAW,OAAO,OAAO,EAAK,CAAG,EAAI,EAAI,GAAO,IAAA,GAEtD,GAAI,IAAa,IAAA,GAAW,CAC1B,cAAc,EAAK,CAAK,EACxB,QACF,CAEA,GAAI,MAAM,QAAQ,CAAQ,EAAG,CAC3B,EAAS,KAAK,CAAK,EACnB,QACF,CAEA,cAAc,EAAK,CAAC,EAAU,CAAK,CAAC,CACtC,CAEA,OAAO,CACT,CAGA,SAAgB,SAAS,EAAc,EAAiB,EAAsB,CAAC,EAAG,EAAqC,CACrH,IAAI,EAAO,EAAQ,QAAQ,gBAAiB,EAAQ,EAAa,IAAuB,CACtF,IAAM,EAAQ,EAAO,GAErB,GAAI,GAAS,KAAM,MAAM,IAAI,EAAA,oBAAoB,uBAAuB,GAAK,EAS7E,OAPI,EACK,EACJ,MAAM,GAAG,CAAC,CACV,IAAK,GAAY,mBAAmB,CAAO,CAAC,CAAC,CAC7C,KAAK,GAAG,EAGN,mBAAmB,CAAK,CACjC,CAAC,EAED,GAAI,GAAS,OAAO,KAAK,CAAK,CAAC,CAAC,OAAQ,CACtC,IAAM,EAAS,IAAI,gBAEnB,OAAO,QAAQ,CAAK,CAAC,CAAC,SAAS,CAAC,EAAK,KAAW,CAC1C,MAAM,QAAQ,CAAK,EACrB,EAAM,QAAS,GAAS,CACtB,EAAO,OAAO,EAAK,OAAO,CAAI,CAAC,CACjC,CAAC,EACE,EAAO,IAAI,EAAK,OAAO,CAAK,CAAC,CACpC,CAAC,EAED,GAAQ,IAAI,EAAO,SAAS,GAC9B,CAEA,OAAO,UAAU,EAAM,CAAI,CAC7B,CAIA,SAAgB,UAAU,EAAkB,EAAO,IAAa,CAC9D,IAAM,EAAiB,cAAc,CAAQ,EACvC,EAAiB,cAAc,CAAI,EAEzC,GAAI,IAAmB,IAAK,OAAO,EAEnC,GAAI,IAAmB,EAAgB,MAAO,IAE9C,IAAM,EAAS,GAAG,EAAe,GAEjC,OAAO,EAAe,WAAW,CAAM,EACnC,cAAc,EAAe,MAAM,EAAe,MAAM,CAAC,EACzD,CACN,CAEA,SAAgB,aACd,EACA,EACe,CACf,MAAO,CACL,KAAM,EAAQ,SAAS,KAAK,QAAQ,KAAM,EAAE,EAC5C,aAAc,EAAQ,SAAS,MAC/B,SAAU,UAAU,EAAQ,SAAS,UAAY,IAAK,CAAI,EAC1D,MAAO,WAAW,EAAQ,SAAS,QAAU,EAAE,CACjD,CACF"}
package/dist/path.js CHANGED
@@ -1,133 +1,2 @@
1
- import { WayfinderRouteError as e } from "./errors.js";
2
- //#region src/path.ts
3
- function t(e) {
4
- let t = `/${e}`.replace(/\/+/g, "/");
5
- return t !== "/" && t.endsWith("/") ? t.slice(0, -1) : t;
6
- }
7
- function n(e, n) {
8
- return t(`${e}/${n}`);
9
- }
10
- function r(e) {
11
- return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12
- }
13
- function i(n) {
14
- let i = t(n);
15
- if (i === "/") return {
16
- paramNames: [],
17
- pattern: /^\/$/,
18
- prefixPattern: /^\/$/
19
- };
20
- let a = [], o = i.slice(1).split("/"), s = ["^"], c = ["^"];
21
- for (let t = 0; t < o.length; t += 1) {
22
- let i = o[t];
23
- if (i === "*") {
24
- if (t !== o.length - 1) throw new e(`Wildcard "*" must be the final segment in path: ${n}`);
25
- s.push("(?:/.*)?");
26
- break;
27
- }
28
- if (i.startsWith(":") && i.endsWith("*")) {
29
- if (t !== o.length - 1) throw new e(`Wildcard param must be final segment in path: ${n}`);
30
- let r = i.slice(1, -1);
31
- if (!/^\w+$/.test(r)) throw new e(`Invalid param name ":${r}" in path "${n}". Param names must only contain word characters (a–z, A–Z, 0–9, _).`);
32
- a.push(r), s.push(t === o.length - 1 ? "(?:/(.*))?" : "/(.*)"), c.push("/.*");
33
- break;
34
- }
35
- if (i.startsWith(":")) {
36
- let t = i.slice(1);
37
- if (!/^\w+$/.test(t)) throw new e(`Invalid param name ":${t}" in path "${n}". Param names must only contain word characters (a–z, A–Z, 0–9, _).`);
38
- a.push(t), s.push("/([^/]+)"), c.push("/[^/]+");
39
- continue;
40
- }
41
- let l = r(i);
42
- s.push(`/${l}`), c.push(`/${l}`);
43
- }
44
- return s.push("$"), c.push("(?:/.*)?$"), {
45
- paramNames: a,
46
- pattern: new RegExp(s.join("")),
47
- prefixPattern: new RegExp(c.join(""))
48
- };
49
- }
50
- function a(e, t) {
51
- let n = t.matcher.pattern.exec(e);
52
- if (!n) return null;
53
- let r = {};
54
- return t.matcher.paramNames.forEach((e, t) => {
55
- let i = n[t + 1] ?? "";
56
- try {
57
- r[e] = decodeURIComponent(i);
58
- } catch {
59
- r[e] = i;
60
- }
61
- }), r;
62
- }
63
- function o(e, t) {
64
- for (let n of t) {
65
- let t = a(e, n);
66
- if (t) return {
67
- params: t,
68
- record: n
69
- };
70
- }
71
- return { params: {} };
72
- }
73
- function s(e, t) {
74
- return t.matcher.prefixPattern.test(e);
75
- }
76
- function c(e) {
77
- let t = new URLSearchParams(e), n = {}, r = (e, t) => {
78
- Object.defineProperty(n, e, {
79
- configurable: !0,
80
- enumerable: !0,
81
- value: t,
82
- writable: !0
83
- });
84
- };
85
- for (let [e, i] of t.entries()) {
86
- let t = Object.hasOwn(n, e) ? n[e] : void 0;
87
- if (t === void 0) {
88
- r(e, i);
89
- continue;
90
- }
91
- if (Array.isArray(t)) {
92
- t.push(i);
93
- continue;
94
- }
95
- r(e, [t, i]);
96
- }
97
- return n;
98
- }
99
- function l(t, r, i = {}, a) {
100
- let o = r.replace(/:(\w+)(\*)?/g, (t, n, r) => {
101
- let a = i[n];
102
- if (a == null) throw new e(`Missing path param: ${n}`);
103
- return r ? a.split("/").map((e) => encodeURIComponent(e)).join("/") : encodeURIComponent(a);
104
- });
105
- if (a && Object.keys(a).length) {
106
- let e = new URLSearchParams();
107
- Object.entries(a).forEach(([t, n]) => {
108
- Array.isArray(n) ? n.forEach((n) => {
109
- e.append(t, String(n));
110
- }) : e.set(t, String(n));
111
- }), o += `?${e.toString()}`;
112
- }
113
- return n(t, o);
114
- }
115
- function u(e, n = "/") {
116
- let r = t(e), i = t(n);
117
- if (i === "/") return r;
118
- if (r === i) return "/";
119
- let a = `${i}/`;
120
- return r.startsWith(a) ? t(r.slice(i.length)) : r;
121
- }
122
- function d(e, t) {
123
- return {
124
- hash: t.location.hash.replace(/^#/, ""),
125
- historyState: t.location.state,
126
- pathname: u(t.location.pathname || "/", e),
127
- query: c(t.location.search || "")
128
- };
129
- }
130
- //#endregion
131
- export { l as buildUrl, i as compilePathMatcher, n as joinPaths, a as matchRecord, o as matchRouteFor, s as matchesPrefix, t as normalizePath, c as parseQuery, d as readLocation, u as stripBase };
132
-
1
+ import{WayfinderConfigError as e,WayfinderRouteError as t}from"./errors.js";function normalizePath(e){let t=`/${e}`.replace(/\/+/g,`/`);return t!==`/`&&t.endsWith(`/`)?t.slice(0,-1):t}function joinPaths(e,t){return normalizePath(`${e}/${t}`)}function escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function compilePathMatcher(t){let n=normalizePath(t);if(n===`/`)return{paramNames:[],pattern:/^\/$/,prefixPattern:/^\/$/};let r=[],i=n.slice(1).split(`/`),a=[`^`],o=[`^`];for(let n=0;n<i.length;n+=1){let s=i[n];if(s===`*`){if(n!==i.length-1)throw new e(`Wildcard "*" must be the final segment in path: ${t}`);a.push(`(?:/.*)?`);break}if(s.startsWith(`:`)&&s.endsWith(`*`)){if(n!==i.length-1)throw new e(`Wildcard param must be final segment in path: ${t}`);let c=s.slice(1,-1);if(!/^\w+$/.test(c))throw new e(`Invalid param name ":${c}" in path "${t}". Param names must only contain word characters (a–z, A–Z, 0–9, _).`);r.push(c),a.push(n===i.length-1?`(?:/(.*))?`:`/(.*)`),o.push(`/.*`);break}if(s.startsWith(`:`)){let n=s.slice(1);if(!/^\w+$/.test(n))throw new e(`Invalid param name ":${n}" in path "${t}". Param names must only contain word characters (a–z, A–Z, 0–9, _).`);r.push(n),a.push(`/([^/]+)`),o.push(`/[^/]+`);continue}let c=escapeRegex(s);a.push(`/${c}`),o.push(`/${c}`)}return a.push(`$`),o.push(`(?:/.*)?$`),{paramNames:r,pattern:new RegExp(a.join(``)),prefixPattern:new RegExp(o.join(``))}}function matchRecord(e,t){let n=t.matcher.pattern.exec(e);if(!n)return null;let r={};return t.matcher.paramNames.forEach((e,t)=>{let i=n[t+1]??``;try{r[e]=decodeURIComponent(i)}catch{r[e]=i}}),r}function matchRouteFor(e,t){for(let n of t){let t=matchRecord(e,n);if(t)return{params:t,record:n}}return{params:{}}}function matchesPrefix(e,t){return t.matcher.prefixPattern.test(e)}function parseQuery(e){let t=new URLSearchParams(e),n={},setQueryValue=(e,t)=>{Object.defineProperty(n,e,{configurable:!0,enumerable:!0,value:t,writable:!0})};for(let[e,r]of t.entries()){let t=Object.hasOwn(n,e)?n[e]:void 0;if(t===void 0){setQueryValue(e,r);continue}if(Array.isArray(t)){t.push(r);continue}setQueryValue(e,[t,r])}return n}function buildUrl(e,n,r={},i){let a=n.replace(/:(\w+)(\*)?/g,(e,n,i)=>{let a=r[n];if(a==null)throw new t(`Missing path param: ${n}`);return i?a.split(`/`).map(e=>encodeURIComponent(e)).join(`/`):encodeURIComponent(a)});if(i&&Object.keys(i).length){let e=new URLSearchParams;Object.entries(i).forEach(([t,n])=>{Array.isArray(n)?n.forEach(n=>{e.append(t,String(n))}):e.set(t,String(n))}),a+=`?${e.toString()}`}return joinPaths(e,a)}function stripBase(e,t=`/`){let n=normalizePath(e),r=normalizePath(t);if(r===`/`)return n;if(n===r)return`/`;let i=`${r}/`;return n.startsWith(i)?normalizePath(n.slice(r.length)):n}function readLocation(e,t){return{hash:t.location.hash.replace(/^#/,``),historyState:t.location.state,pathname:stripBase(t.location.pathname||`/`,e),query:parseQuery(t.location.search||``)}}export{buildUrl,compilePathMatcher,joinPaths,matchRecord,matchRouteFor,matchesPrefix,normalizePath,parseQuery,readLocation,stripBase};
133
2
  //# sourceMappingURL=path.js.map
package/dist/path.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"path.js","names":[],"sources":["../src/path.ts"],"sourcesContent":["import { WayfinderRouteError } from './errors';\nimport type { QueryParams, ResolvedQueryParams, RouteLocation, RouteMatcher, RouteParams, RouteRecord } from './types';\n\n/** Ensure leading slash, collapse duplicate slashes, preserve root. */\nexport function normalizePath(path: string): string {\n const normalized = `/${path}`.replace(/\\/+/g, '/');\n\n return normalized !== '/' && normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;\n}\n\nexport function joinPaths(base: string, path: string): string {\n return normalizePath(`${base}/${path}`);\n}\n\nfunction escapeRegex(input: string): string {\n return input.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport function compilePathMatcher(path: string): RouteMatcher {\n const normalized = normalizePath(path);\n\n if (normalized === '/') {\n return {\n paramNames: [],\n pattern: /^\\/$/,\n prefixPattern: /^\\/$/,\n };\n }\n\n const paramNames: string[] = [];\n const segments = normalized.slice(1).split('/');\n const regexParts: string[] = ['^'];\n const prefixParts: string[] = ['^'];\n\n for (let i = 0; i < segments.length; i += 1) {\n const segment = segments[i]!;\n\n if (segment === '*') {\n if (i !== segments.length - 1) {\n throw new WayfinderRouteError(`Wildcard \"*\" must be the final segment in path: ${path}`);\n }\n\n regexParts.push('(?:/.*)?');\n break;\n }\n\n if (segment.startsWith(':') && segment.endsWith('*')) {\n if (i !== segments.length - 1) {\n throw new WayfinderRouteError(`Wildcard param must be final segment in path: ${path}`);\n }\n\n const name = segment.slice(1, -1);\n\n if (!/^\\w+$/.test(name)) {\n throw new WayfinderRouteError(\n `Invalid param name \":${name}\" in path \"${path}\". Param names must only contain word characters (a–z, A–Z, 0–9, _).`,\n );\n }\n\n paramNames.push(name);\n regexParts.push(i === segments.length - 1 ? '(?:/(.*))?' : '/(.*)');\n prefixParts.push('/.*');\n break;\n }\n\n if (segment.startsWith(':')) {\n const name = segment.slice(1);\n\n if (!/^\\w+$/.test(name)) {\n throw new WayfinderRouteError(\n `Invalid param name \":${name}\" in path \"${path}\". Param names must only contain word characters (a–z, A–Z, 0–9, _).`,\n );\n }\n\n paramNames.push(name);\n regexParts.push('/([^/]+)');\n prefixParts.push('/[^/]+');\n continue;\n }\n\n const escaped = escapeRegex(segment);\n\n regexParts.push(`/${escaped}`);\n prefixParts.push(`/${escaped}`);\n }\n\n regexParts.push('$');\n prefixParts.push('(?:/.*)?$');\n\n return {\n paramNames,\n pattern: new RegExp(regexParts.join('')),\n prefixPattern: new RegExp(prefixParts.join('')),\n };\n}\n\nexport function matchRecord(pathname: string, record: RouteRecord): RouteParams | null {\n const match = record.matcher.pattern.exec(pathname);\n\n if (!match) return null;\n\n const params: RouteParams = {};\n\n record.matcher.paramNames.forEach((name: string, index: number) => {\n const value = match[index + 1] ?? '';\n\n try {\n params[name] = decodeURIComponent(value);\n } catch {\n params[name] = value;\n }\n });\n\n return params;\n}\n\nexport function matchRouteFor(\n pathname: string,\n records: readonly RouteRecord[],\n): { params: RouteParams; record?: RouteRecord } {\n for (const record of records) {\n const params = matchRecord(pathname, record);\n\n if (params) return { params, record };\n }\n\n return { params: {} };\n}\n\nexport function matchesPrefix(pathname: string, record: RouteRecord): boolean {\n return record.matcher.prefixPattern.test(pathname);\n}\n\n/**\n * Parse `?foo=a&foo=b&bar=c` into `{ foo: ['a', 'b'], bar: 'c' }`.\n *\n * @security Query keys come directly from the (attacker-controlled) URL. Plain bracket\n * assignment (`out[key] = value`) would let a key like `__proto__` reach the inherited\n * `Object.prototype.__proto__` setter and silently rewrite `out`'s own prototype instead of\n * storing a value — `setQueryValue` uses `Object.defineProperty` so every key, including\n * `__proto__`/`constructor`/`prototype`, is always stored as a plain own data property.\n */\nexport function parseQuery(queryString: string): QueryParams {\n const search = new URLSearchParams(queryString);\n const out: QueryParams = {};\n\n const setQueryValue = (key: string, value: QueryParams[string]): void => {\n Object.defineProperty(out, key, { configurable: true, enumerable: true, value, writable: true });\n };\n\n for (const [key, value] of search.entries()) {\n const existing = Object.hasOwn(out, key) ? out[key] : undefined;\n\n if (existing === undefined) {\n setQueryValue(key, value);\n continue;\n }\n\n if (Array.isArray(existing)) {\n existing.push(value);\n continue;\n }\n\n setQueryValue(key, [existing, value]);\n }\n\n return out;\n}\n\n/** Build a URL from a path pattern, params, and query, respecting the router base. */\nexport function buildUrl(base: string, pattern: string, params: RouteParams = {}, query?: ResolvedQueryParams): string {\n let path = pattern.replace(/:(\\w+)(\\*)?/g, (_match, key: string, isWildcard: string) => {\n const value = params[key];\n\n if (value == null) throw new WayfinderRouteError(`Missing path param: ${key}`);\n\n if (isWildcard) {\n return value\n .split('/')\n .map((segment) => encodeURIComponent(segment))\n .join('/');\n }\n\n return encodeURIComponent(value);\n });\n\n if (query && Object.keys(query).length) {\n const search = new URLSearchParams();\n\n Object.entries(query).forEach(([key, value]) => {\n if (Array.isArray(value))\n value.forEach((item) => {\n search.append(key, String(item));\n });\n else search.set(key, String(value));\n });\n\n path += `?${search.toString()}`;\n }\n\n return joinPaths(base, path);\n}\n\n// ─── Location helpers (formerly in resolve.ts) ────────────────────────────────\n\nexport function stripBase(pathname: string, base = '/'): string {\n const normalizedPath = normalizePath(pathname);\n const normalizedBase = normalizePath(base);\n\n if (normalizedBase === '/') return normalizedPath;\n\n if (normalizedPath === normalizedBase) return '/';\n\n const prefix = `${normalizedBase}/`;\n\n return normalizedPath.startsWith(prefix)\n ? normalizePath(normalizedPath.slice(normalizedBase.length))\n : normalizedPath;\n}\n\nexport function readLocation(\n base: string,\n history: { location: { hash: string; pathname: string; search: string; state: unknown } },\n): RouteLocation {\n return {\n hash: history.location.hash.replace(/^#/, ''),\n historyState: history.location.state,\n pathname: stripBase(history.location.pathname || '/', base),\n query: parseQuery(history.location.search || ''),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"path.js","names":[],"sources":["../src/path.ts"],"sourcesContent":["import { WayfinderConfigError, WayfinderRouteError } from './errors';\nimport type { QueryParams, ResolvedQueryParams, RouteLocation, RouteMatcher, RouteParams, RouteRecord } from './types';\n\n/** Ensure leading slash, collapse duplicate slashes, preserve root. */\nexport function normalizePath(path: string): string {\n const normalized = `/${path}`.replace(/\\/+/g, '/');\n\n return normalized !== '/' && normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;\n}\n\nexport function joinPaths(base: string, path: string): string {\n return normalizePath(`${base}/${path}`);\n}\n\nfunction escapeRegex(input: string): string {\n return input.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport function compilePathMatcher(path: string): RouteMatcher {\n const normalized = normalizePath(path);\n\n if (normalized === '/') {\n return {\n paramNames: [],\n pattern: /^\\/$/,\n prefixPattern: /^\\/$/,\n };\n }\n\n const paramNames: string[] = [];\n const segments = normalized.slice(1).split('/');\n const regexParts: string[] = ['^'];\n const prefixParts: string[] = ['^'];\n\n for (let i = 0; i < segments.length; i += 1) {\n const segment = segments[i]!;\n\n if (segment === '*') {\n if (i !== segments.length - 1) {\n throw new WayfinderConfigError(`Wildcard \"*\" must be the final segment in path: ${path}`);\n }\n\n regexParts.push('(?:/.*)?');\n break;\n }\n\n if (segment.startsWith(':') && segment.endsWith('*')) {\n if (i !== segments.length - 1) {\n throw new WayfinderConfigError(`Wildcard param must be final segment in path: ${path}`);\n }\n\n const name = segment.slice(1, -1);\n\n if (!/^\\w+$/.test(name)) {\n throw new WayfinderConfigError(\n `Invalid param name \":${name}\" in path \"${path}\". Param names must only contain word characters (a–z, A–Z, 0–9, _).`,\n );\n }\n\n paramNames.push(name);\n regexParts.push(i === segments.length - 1 ? '(?:/(.*))?' : '/(.*)');\n prefixParts.push('/.*');\n break;\n }\n\n if (segment.startsWith(':')) {\n const name = segment.slice(1);\n\n if (!/^\\w+$/.test(name)) {\n throw new WayfinderConfigError(\n `Invalid param name \":${name}\" in path \"${path}\". Param names must only contain word characters (a–z, A–Z, 0–9, _).`,\n );\n }\n\n paramNames.push(name);\n regexParts.push('/([^/]+)');\n prefixParts.push('/[^/]+');\n continue;\n }\n\n const escaped = escapeRegex(segment);\n\n regexParts.push(`/${escaped}`);\n prefixParts.push(`/${escaped}`);\n }\n\n regexParts.push('$');\n prefixParts.push('(?:/.*)?$');\n\n return {\n paramNames,\n pattern: new RegExp(regexParts.join('')),\n prefixPattern: new RegExp(prefixParts.join('')),\n };\n}\n\nexport function matchRecord(pathname: string, record: RouteRecord): RouteParams | null {\n const match = record.matcher.pattern.exec(pathname);\n\n if (!match) return null;\n\n const params: RouteParams = {};\n\n record.matcher.paramNames.forEach((name: string, index: number) => {\n const value = match[index + 1] ?? '';\n\n try {\n params[name] = decodeURIComponent(value);\n } catch {\n params[name] = value;\n }\n });\n\n return params;\n}\n\nexport function matchRouteFor(\n pathname: string,\n records: readonly RouteRecord[],\n): { params: RouteParams; record?: RouteRecord } {\n for (const record of records) {\n const params = matchRecord(pathname, record);\n\n if (params) return { params, record };\n }\n\n return { params: {} };\n}\n\nexport function matchesPrefix(pathname: string, record: RouteRecord): boolean {\n return record.matcher.prefixPattern.test(pathname);\n}\n\n/**\n * Parse `?foo=a&foo=b&bar=c` into `{ foo: ['a', 'b'], bar: 'c' }`.\n *\n * @security Query keys come directly from the (attacker-controlled) URL. Plain bracket\n * assignment (`out[key] = value`) would let a key like `__proto__` reach the inherited\n * `Object.prototype.__proto__` setter and silently rewrite `out`'s own prototype instead of\n * storing a value — `setQueryValue` uses `Object.defineProperty` so every key, including\n * `__proto__`/`constructor`/`prototype`, is always stored as a plain own data property.\n */\nexport function parseQuery(queryString: string): QueryParams {\n const search = new URLSearchParams(queryString);\n const out: QueryParams = {};\n\n const setQueryValue = (key: string, value: QueryParams[string]): void => {\n Object.defineProperty(out, key, { configurable: true, enumerable: true, value, writable: true });\n };\n\n for (const [key, value] of search.entries()) {\n const existing = Object.hasOwn(out, key) ? out[key] : undefined;\n\n if (existing === undefined) {\n setQueryValue(key, value);\n continue;\n }\n\n if (Array.isArray(existing)) {\n existing.push(value);\n continue;\n }\n\n setQueryValue(key, [existing, value]);\n }\n\n return out;\n}\n\n/** Build a URL from a path pattern, params, and query, respecting the router base. */\nexport function buildUrl(base: string, pattern: string, params: RouteParams = {}, query?: ResolvedQueryParams): string {\n let path = pattern.replace(/:(\\w+)(\\*)?/g, (_match, key: string, isWildcard: string) => {\n const value = params[key];\n\n if (value == null) throw new WayfinderRouteError(`Missing path param: ${key}`);\n\n if (isWildcard) {\n return value\n .split('/')\n .map((segment) => encodeURIComponent(segment))\n .join('/');\n }\n\n return encodeURIComponent(value);\n });\n\n if (query && Object.keys(query).length) {\n const search = new URLSearchParams();\n\n Object.entries(query).forEach(([key, value]) => {\n if (Array.isArray(value))\n value.forEach((item) => {\n search.append(key, String(item));\n });\n else search.set(key, String(value));\n });\n\n path += `?${search.toString()}`;\n }\n\n return joinPaths(base, path);\n}\n\n// ─── Location helpers (formerly in resolve.ts) ────────────────────────────────\n\nexport function stripBase(pathname: string, base = '/'): string {\n const normalizedPath = normalizePath(pathname);\n const normalizedBase = normalizePath(base);\n\n if (normalizedBase === '/') return normalizedPath;\n\n if (normalizedPath === normalizedBase) return '/';\n\n const prefix = `${normalizedBase}/`;\n\n return normalizedPath.startsWith(prefix)\n ? normalizePath(normalizedPath.slice(normalizedBase.length))\n : normalizedPath;\n}\n\nexport function readLocation(\n base: string,\n history: { location: { hash: string; pathname: string; search: string; state: unknown } },\n): RouteLocation {\n return {\n hash: history.location.hash.replace(/^#/, ''),\n historyState: history.location.state,\n pathname: stripBase(history.location.pathname || '/', base),\n query: parseQuery(history.location.search || ''),\n };\n}\n"],"mappings":"4EAIA,SAAgB,cAAc,EAAsB,CAClD,IAAM,EAAa,IAAI,IAAO,QAAQ,OAAQ,GAAG,EAEjD,OAAO,IAAe,KAAO,EAAW,SAAS,GAAG,EAAI,EAAW,MAAM,EAAG,EAAE,EAAI,CACpF,CAEA,SAAgB,UAAU,EAAc,EAAsB,CAC5D,OAAO,cAAc,GAAG,EAAK,GAAG,GAAM,CACxC,CAEA,SAAS,YAAY,EAAuB,CAC1C,OAAO,EAAM,QAAQ,sBAAuB,MAAM,CACpD,CAEA,SAAgB,mBAAmB,EAA4B,CAC7D,IAAM,EAAa,cAAc,CAAI,EAErC,GAAI,IAAe,IACjB,MAAO,CACL,WAAY,CAAC,EACb,QAAS,OACT,cAAe,MACjB,EAGF,IAAM,EAAuB,CAAC,EACxB,EAAW,EAAW,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,EACxC,EAAuB,CAAC,GAAG,EAC3B,EAAwB,CAAC,GAAG,EAElC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAS,OAAQ,GAAK,EAAG,CAC3C,IAAM,EAAU,EAAS,GAEzB,GAAI,IAAY,IAAK,CACnB,GAAI,IAAM,EAAS,OAAS,EAC1B,MAAM,IAAI,EAAqB,mDAAmD,GAAM,EAG1F,EAAW,KAAK,UAAU,EAC1B,KACF,CAEA,GAAI,EAAQ,WAAW,GAAG,GAAK,EAAQ,SAAS,GAAG,EAAG,CACpD,GAAI,IAAM,EAAS,OAAS,EAC1B,MAAM,IAAI,EAAqB,iDAAiD,GAAM,EAGxF,IAAM,EAAO,EAAQ,MAAM,EAAG,EAAE,EAEhC,GAAI,CAAC,QAAQ,KAAK,CAAI,EACpB,MAAM,IAAI,EACR,wBAAwB,EAAK,aAAa,EAAK,qEACjD,EAGF,EAAW,KAAK,CAAI,EACpB,EAAW,KAAK,IAAM,EAAS,OAAS,EAAI,aAAe,OAAO,EAClE,EAAY,KAAK,KAAK,EACtB,KACF,CAEA,GAAI,EAAQ,WAAW,GAAG,EAAG,CAC3B,IAAM,EAAO,EAAQ,MAAM,CAAC,EAE5B,GAAI,CAAC,QAAQ,KAAK,CAAI,EACpB,MAAM,IAAI,EACR,wBAAwB,EAAK,aAAa,EAAK,qEACjD,EAGF,EAAW,KAAK,CAAI,EACpB,EAAW,KAAK,UAAU,EAC1B,EAAY,KAAK,QAAQ,EACzB,QACF,CAEA,IAAM,EAAU,YAAY,CAAO,EAEnC,EAAW,KAAK,IAAI,GAAS,EAC7B,EAAY,KAAK,IAAI,GAAS,CAChC,CAKA,OAHA,EAAW,KAAK,GAAG,EACnB,EAAY,KAAK,WAAW,EAErB,CACL,aACA,QAAS,IAAI,OAAO,EAAW,KAAK,EAAE,CAAC,EACvC,cAAe,IAAI,OAAO,EAAY,KAAK,EAAE,CAAC,CAChD,CACF,CAEA,SAAgB,YAAY,EAAkB,EAAyC,CACrF,IAAM,EAAQ,EAAO,QAAQ,QAAQ,KAAK,CAAQ,EAElD,GAAI,CAAC,EAAO,OAAO,KAEnB,IAAM,EAAsB,CAAC,EAY7B,OAVA,EAAO,QAAQ,WAAW,SAAS,EAAc,IAAkB,CACjE,IAAM,EAAQ,EAAM,EAAQ,IAAM,GAElC,GAAI,CACF,EAAO,GAAQ,mBAAmB,CAAK,CACzC,MAAQ,CACN,EAAO,GAAQ,CACjB,CACF,CAAC,EAEM,CACT,CAEA,SAAgB,cACd,EACA,EAC+C,CAC/C,IAAK,IAAM,KAAU,EAAS,CAC5B,IAAM,EAAS,YAAY,EAAU,CAAM,EAE3C,GAAI,EAAQ,MAAO,CAAE,SAAQ,QAAO,CACtC,CAEA,MAAO,CAAE,OAAQ,CAAC,CAAE,CACtB,CAEA,SAAgB,cAAc,EAAkB,EAA8B,CAC5E,OAAO,EAAO,QAAQ,cAAc,KAAK,CAAQ,CACnD,CAWA,SAAgB,WAAW,EAAkC,CAC3D,IAAM,EAAS,IAAI,gBAAgB,CAAW,EACxC,EAAmB,CAAC,EAEpB,eAAiB,EAAa,IAAqC,CACvE,OAAO,eAAe,EAAK,EAAK,CAAE,aAAc,GAAM,WAAY,GAAM,QAAO,SAAU,EAAK,CAAC,CACjG,EAEA,IAAK,GAAM,CAAC,EAAK,KAAU,EAAO,QAAQ,EAAG,CAC3C,IAAM,EAAW,OAAO,OAAO,EAAK,CAAG,EAAI,EAAI,GAAO,IAAA,GAEtD,GAAI,IAAa,IAAA,GAAW,CAC1B,cAAc,EAAK,CAAK,EACxB,QACF,CAEA,GAAI,MAAM,QAAQ,CAAQ,EAAG,CAC3B,EAAS,KAAK,CAAK,EACnB,QACF,CAEA,cAAc,EAAK,CAAC,EAAU,CAAK,CAAC,CACtC,CAEA,OAAO,CACT,CAGA,SAAgB,SAAS,EAAc,EAAiB,EAAsB,CAAC,EAAG,EAAqC,CACrH,IAAI,EAAO,EAAQ,QAAQ,gBAAiB,EAAQ,EAAa,IAAuB,CACtF,IAAM,EAAQ,EAAO,GAErB,GAAI,GAAS,KAAM,MAAM,IAAI,EAAoB,uBAAuB,GAAK,EAS7E,OAPI,EACK,EACJ,MAAM,GAAG,CAAC,CACV,IAAK,GAAY,mBAAmB,CAAO,CAAC,CAAC,CAC7C,KAAK,GAAG,EAGN,mBAAmB,CAAK,CACjC,CAAC,EAED,GAAI,GAAS,OAAO,KAAK,CAAK,CAAC,CAAC,OAAQ,CACtC,IAAM,EAAS,IAAI,gBAEnB,OAAO,QAAQ,CAAK,CAAC,CAAC,SAAS,CAAC,EAAK,KAAW,CAC1C,MAAM,QAAQ,CAAK,EACrB,EAAM,QAAS,GAAS,CACtB,EAAO,OAAO,EAAK,OAAO,CAAI,CAAC,CACjC,CAAC,EACE,EAAO,IAAI,EAAK,OAAO,CAAK,CAAC,CACpC,CAAC,EAED,GAAQ,IAAI,EAAO,SAAS,GAC9B,CAEA,OAAO,UAAU,EAAM,CAAI,CAC7B,CAIA,SAAgB,UAAU,EAAkB,EAAO,IAAa,CAC9D,IAAM,EAAiB,cAAc,CAAQ,EACvC,EAAiB,cAAc,CAAI,EAEzC,GAAI,IAAmB,IAAK,OAAO,EAEnC,GAAI,IAAmB,EAAgB,MAAO,IAE9C,IAAM,EAAS,GAAG,EAAe,GAEjC,OAAO,EAAe,WAAW,CAAM,EACnC,cAAc,EAAe,MAAM,EAAe,MAAM,CAAC,EACzD,CACN,CAEA,SAAgB,aACd,EACA,EACe,CACf,MAAO,CACL,KAAM,EAAQ,SAAS,KAAK,QAAQ,KAAM,EAAE,EAC5C,aAAc,EAAQ,SAAS,MAC/B,SAAU,UAAU,EAAQ,SAAS,UAAY,IAAK,CAAI,EAC1D,MAAO,WAAW,EAAQ,SAAS,QAAU,EAAE,CACjD,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("./route-signals.cjs");let t=require("@vielzeug/ripple");function createPhaseMirror(n){let{name:r,params:i}=e.createRouteSignals(n.router),a=(0,t.computed)(()=>{let e=i.value.phase;return e!==void 0&&n.phases.includes(e)?e:null}),o=(0,t.computed)(()=>{let e=n.currentPhase.value;return e?n.phases.filter(t=>n.canRevisit(e,t)):[]}),goToPhase=e=>{let t=n.subjectId.value;t&&n.router.navigate({name:n.phaseRoute,params:{id:t,phase:e}},{replace:!0})},s=[(0,t.watch)(()=>`${n.subjectId.value??``}|${r.value??``}`,()=>{let e=n.subjectId.value,t=n.currentPhase.value,a=i.value.id;(typeof a!=`string`||a===e)&&r.value===n.detailRoute&&e&&t&&goToPhase(t)},{immediate:!0}),(0,t.watch)(n.currentPhase,(e,t)=>{e&&e!==t&&a.value!==e&&goToPhase(e)}),(0,t.watch)(()=>[a.value,n.currentPhase.value],([e,t],a)=>{if(r.value!==n.phaseRoute&&r.value!==n.detailRoute)return;let o=i.value.id;if(typeof o==`string`&&o!==n.subjectId.value)return;let s=a?.[1]??null;if((t===s||s===null)&&n.subjectId.value&&t&&e!==t){if(e&&n.canRevisit(t,e)){n.requestRevisit(e);return}goToPhase(t)}},{immediate:!0})];return{bounceIfMismatched:()=>{let e=n.currentPhase.value;n.subjectId.value&&e&&a.value!==null&&a.value!==e&&goToPhase(e)},dispose:()=>{for(let e of s.splice(0))e.dispose()},followPhase:goToPhase,phaseHref:e=>{let t=n.subjectId.value;return t?n.router.href(n.phaseRoute,{id:t,phase:e}):`#`},revisitPhases:o,routePhase:a}}exports.createPhaseMirror=createPhaseMirror;
2
+ //# sourceMappingURL=phase-mirror.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phase-mirror.cjs","names":[],"sources":["../src/phase-mirror.ts"],"sourcesContent":["import { computed, type Readable, watch } from '@vielzeug/ripple';\n\nimport { createRouteSignals } from './route-signals';\nimport type { Router } from './router';\n\n/**\n * Mirrors a domain state machine's phase into the URL as `/:subject/:id/:phase`.\n *\n * The mirror never writes history: every phase transition REPLACES the route, so the browser\n * back button leaves the flow rather than stepping between phases. Arriving at a mismatched\n * phase route is a revisit request (the consumer owns its confirm dialog) or an impossible\n * jump, which bounces back to the actual phase. The bare detail URL canonicalizes onto the\n * current phase's route, so every arrival is shareable and reload-safe. The state machine\n * stays the sole source of truth — `canRevisit` decides which mismatched routes may ask and\n * which bounce.\n */\nexport interface PhaseMirrorOptions<Phase extends string> {\n /** Whether the domain may revisit `target` from `current`. */\n readonly canRevisit: (current: Phase, target: Phase) => boolean;\n /** Reads the domain's current phase; `null` while the subject is missing. */\n readonly currentPhase: Readable<Phase | null>;\n /** Bare detail route name that canonicalizes onto the phase route. */\n readonly detailRoute: string;\n /** Route name carrying the `:phase` segment. */\n readonly phaseRoute: string;\n /** Valid phase values; the route segment is validated against this list. */\n readonly phases: readonly Phase[];\n /** Runs the revisit the URL asked for — the consumer owns its confirm dialog and command. */\n readonly requestRevisit: (phase: Phase) => void;\n /**\n * Any wayfinder router instance. The mirror only navigates by route name with runtime-shaped\n * params, so it deliberately erases the route-table generic instead of propagating it.\n */\n readonly router: Router<any>;\n /** Reads the subject id; `null` while the subject is missing. */\n readonly subjectId: Readable<string | null>;\n}\n\nexport interface PhaseMirror<Phase extends string> {\n /** Bounces a mismatched phase route back to the domain's actual phase — the dialog cancel path. */\n readonly bounceIfMismatched: () => void;\n /** Stops mirroring; the URL keeps whatever the last navigation left behind. */\n readonly dispose: () => void;\n /** Follows a tracker revisit link without writing history. */\n readonly followPhase: (phase: string) => void;\n /** Anchor href for one phase step; `#` while the subject is missing. */\n readonly phaseHref: (phase: string) => string;\n /** The phases the current phase may legally revisit — the tracker links exactly these. */\n readonly revisitPhases: Readable<readonly Phase[]>;\n /** The phase segment of the current route, when it names a valid phase. */\n readonly routePhase: Readable<Phase | null>;\n}\n\n/** Creates the two-way phase mirror between a domain state machine and a route segment. */\nexport function createPhaseMirror<Phase extends string>(options: PhaseMirrorOptions<Phase>): PhaseMirror<Phase> {\n const { name: routeName, params: routeParams } = createRouteSignals(options.router);\n\n const routePhase = computed<Phase | null>(() => {\n const segment = routeParams.value.phase as Phase | undefined;\n return segment !== undefined && options.phases.includes(segment) ? segment : null;\n });\n const revisitPhases = computed<readonly Phase[]>(() => {\n const phase = options.currentPhase.value;\n return phase ? options.phases.filter((entry) => options.canRevisit(phase, entry)) : [];\n });\n\n /** Lands on the phase's route, replacing so back exits the flow. */\n const goToPhase = (phase: string): void => {\n const id = options.subjectId.value;\n if (!id) return;\n void options.router.navigate({ name: options.phaseRoute, params: { id, phase } } as never, { replace: true });\n };\n\n const handles = [\n // The bare detail URL canonicalizes to the current phase's route.\n watch(\n () => `${options.subjectId.value ?? ''}|${routeName.value ?? ''}`,\n () => {\n const id = options.subjectId.value;\n const phase = options.currentPhase.value;\n // The URL names a different subject of the same flow — a cross-instance navigation.\n // Canonicalizing it here would stomp the arrival onto this mirror's own subject;\n // the arriving view's own mirror takes over from here.\n const urlSubjectId = routeParams.value.id;\n if (typeof urlSubjectId === 'string' && urlSubjectId !== id) return;\n if (routeName.value === options.detailRoute && id && phase) goToPhase(phase);\n },\n { immediate: true },\n ),\n // Domain → URL: every phase change lands on the phase's route.\n watch(options.currentPhase, (phase, previous) => {\n if (!phase || phase === previous) return;\n if (routePhase.value === phase) return;\n goToPhase(phase);\n }),\n // URL → domain: a mismatched or invalid phase route revisits (through the consumer's\n // dialog) or bounces. Reacts to the URL itself and to the domain loading (null → value,\n // which re-checks the mismatch a cold deep link brought in) — never to domain-led changes:\n // an advance or a confirmed revisit swaps the domain before the URL catches up, and reading\n // the not-yet-updated URL as a revisit request would open the dialog on every advance.\n watch(\n () => [routePhase.value, options.currentPhase.value] as const,\n ([phase, current], previous) => {\n // The user left the stepped flow entirely — the mirror stops following. Ripple\n // watchers fire synchronously on route change (before the component unmounts), so\n // a departure reads as \"phase vanished\" and would bounce back without this guard.\n if (routeName.value !== options.phaseRoute && routeName.value !== options.detailRoute) return;\n // The URL names a different subject of the same flow — a cross-instance navigation,\n // not a phase mismatch. The old mirror's component unmounts right after this.\n const urlSubjectId = routeParams.value.id;\n if (typeof urlSubjectId === 'string' && urlSubjectId !== options.subjectId.value) return;\n const previousCurrent = previous?.[1] ?? null;\n // A domain-led change (value → value) belongs to the domain → URL watcher above.\n if (current !== previousCurrent && previousCurrent !== null) return;\n const id = options.subjectId.value;\n if (!id || !current) return;\n if (phase === current) return;\n if (phase && options.canRevisit(current, phase)) {\n options.requestRevisit(phase);\n return;\n }\n goToPhase(current);\n },\n { immediate: true },\n ),\n ];\n\n return {\n bounceIfMismatched: () => {\n const current = options.currentPhase.value;\n if (options.subjectId.value && current && routePhase.value !== null && routePhase.value !== current) {\n goToPhase(current);\n }\n },\n dispose: () => {\n for (const handle of handles.splice(0)) handle.dispose();\n },\n followPhase: goToPhase,\n phaseHref: (phase: string): string => {\n const id = options.subjectId.value;\n return id ? options.router.href(options.phaseRoute as never, { id, phase } as never) : '#';\n },\n revisitPhases,\n routePhase,\n };\n}\n"],"mappings":"yEAsDA,SAAgB,kBAAwC,EAAwD,CAC9G,GAAM,CAAE,KAAM,EAAW,OAAQ,GAAgB,EAAA,mBAAmB,EAAQ,MAAM,EAE5E,GAAA,EAAa,EAAA,SAAA,KAA6B,CAC9C,IAAM,EAAU,EAAY,MAAM,MAClC,OAAO,IAAY,IAAA,IAAa,EAAQ,OAAO,SAAS,CAAO,EAAI,EAAU,IAC/E,CAAC,EACK,GAAA,EAAgB,EAAA,SAAA,KAAiC,CACrD,IAAM,EAAQ,EAAQ,aAAa,MACnC,OAAO,EAAQ,EAAQ,OAAO,OAAQ,GAAU,EAAQ,WAAW,EAAO,CAAK,CAAC,EAAI,CAAC,CACvF,CAAC,EAGK,UAAa,GAAwB,CACzC,IAAM,EAAK,EAAQ,UAAU,MACxB,GACL,EAAa,OAAO,SAAS,CAAE,KAAM,EAAQ,WAAY,OAAQ,CAAE,KAAI,OAAM,CAAE,EAAY,CAAE,QAAS,EAAK,CAAC,CAC9G,EAEM,EAAU,EAEd,EAAA,EAAA,MAAA,KACQ,GAAG,EAAQ,UAAU,OAAS,GAAG,GAAG,EAAU,OAAS,SACvD,CACJ,IAAM,EAAK,EAAQ,UAAU,MACvB,EAAQ,EAAQ,aAAa,MAI7B,EAAe,EAAY,MAAM,IACnC,OAAO,GAAiB,UAAY,IAAiB,IACrD,EAAU,QAAU,EAAQ,aAAe,GAAM,GAAO,UAAU,CAAK,CAC7E,EACA,CAAE,UAAW,EAAK,CACpB,GAEA,EAAA,EAAA,MAAA,CAAM,EAAQ,cAAe,EAAO,IAAa,CAC1C,GAAS,IAAU,GACpB,EAAW,QAAU,GACzB,UAAU,CAAK,CACjB,CAAC,GAMD,EAAA,EAAA,MAAA,KACQ,CAAC,EAAW,MAAO,EAAQ,aAAa,KAAK,GAClD,CAAC,EAAO,GAAU,IAAa,CAI9B,GAAI,EAAU,QAAU,EAAQ,YAAc,EAAU,QAAU,EAAQ,YAAa,OAGvF,IAAM,EAAe,EAAY,MAAM,GACvC,GAAI,OAAO,GAAiB,UAAY,IAAiB,EAAQ,UAAU,MAAO,OAClF,IAAM,EAAkB,IAAW,IAAM,KAErC,QAAY,GAAmB,IAAoB,OAC5C,EAAQ,UAAU,OACjB,GACR,IAAU,EACd,IAAI,GAAS,EAAQ,WAAW,EAAS,CAAK,EAAG,CAC/C,EAAQ,eAAe,CAAK,EAC5B,MACF,CACA,UAAU,CAAO,CADjB,CAEF,EACA,CAAE,UAAW,EAAK,CACpB,CACF,EAEA,MAAO,CACL,uBAA0B,CACxB,IAAM,EAAU,EAAQ,aAAa,MACjC,EAAQ,UAAU,OAAS,GAAW,EAAW,QAAU,MAAQ,EAAW,QAAU,GAC1F,UAAU,CAAO,CAErB,EACA,YAAe,CACb,IAAK,IAAM,KAAU,EAAQ,OAAO,CAAC,EAAG,EAAO,QAAQ,CACzD,EACA,YAAa,UACb,UAAY,GAA0B,CACpC,IAAM,EAAK,EAAQ,UAAU,MAC7B,OAAO,EAAK,EAAQ,OAAO,KAAK,EAAQ,WAAqB,CAAE,KAAI,OAAM,CAAU,EAAI,GACzF,EACA,gBACA,YACF,CACF"}
@@ -0,0 +1,51 @@
1
+ import { type Readable } from '@vielzeug/ripple';
2
+ import type { Router } from './router';
3
+ /**
4
+ * Mirrors a domain state machine's phase into the URL as `/:subject/:id/:phase`.
5
+ *
6
+ * The mirror never writes history: every phase transition REPLACES the route, so the browser
7
+ * back button leaves the flow rather than stepping between phases. Arriving at a mismatched
8
+ * phase route is a revisit request (the consumer owns its confirm dialog) or an impossible
9
+ * jump, which bounces back to the actual phase. The bare detail URL canonicalizes onto the
10
+ * current phase's route, so every arrival is shareable and reload-safe. The state machine
11
+ * stays the sole source of truth — `canRevisit` decides which mismatched routes may ask and
12
+ * which bounce.
13
+ */
14
+ export interface PhaseMirrorOptions<Phase extends string> {
15
+ /** Whether the domain may revisit `target` from `current`. */
16
+ readonly canRevisit: (current: Phase, target: Phase) => boolean;
17
+ /** Reads the domain's current phase; `null` while the subject is missing. */
18
+ readonly currentPhase: Readable<Phase | null>;
19
+ /** Bare detail route name that canonicalizes onto the phase route. */
20
+ readonly detailRoute: string;
21
+ /** Route name carrying the `:phase` segment. */
22
+ readonly phaseRoute: string;
23
+ /** Valid phase values; the route segment is validated against this list. */
24
+ readonly phases: readonly Phase[];
25
+ /** Runs the revisit the URL asked for — the consumer owns its confirm dialog and command. */
26
+ readonly requestRevisit: (phase: Phase) => void;
27
+ /**
28
+ * Any wayfinder router instance. The mirror only navigates by route name with runtime-shaped
29
+ * params, so it deliberately erases the route-table generic instead of propagating it.
30
+ */
31
+ readonly router: Router<any>;
32
+ /** Reads the subject id; `null` while the subject is missing. */
33
+ readonly subjectId: Readable<string | null>;
34
+ }
35
+ export interface PhaseMirror<Phase extends string> {
36
+ /** Bounces a mismatched phase route back to the domain's actual phase — the dialog cancel path. */
37
+ readonly bounceIfMismatched: () => void;
38
+ /** Stops mirroring; the URL keeps whatever the last navigation left behind. */
39
+ readonly dispose: () => void;
40
+ /** Follows a tracker revisit link without writing history. */
41
+ readonly followPhase: (phase: string) => void;
42
+ /** Anchor href for one phase step; `#` while the subject is missing. */
43
+ readonly phaseHref: (phase: string) => string;
44
+ /** The phases the current phase may legally revisit — the tracker links exactly these. */
45
+ readonly revisitPhases: Readable<readonly Phase[]>;
46
+ /** The phase segment of the current route, when it names a valid phase. */
47
+ readonly routePhase: Readable<Phase | null>;
48
+ }
49
+ /** Creates the two-way phase mirror between a domain state machine and a route segment. */
50
+ export declare function createPhaseMirror<Phase extends string>(options: PhaseMirrorOptions<Phase>): PhaseMirror<Phase>;
51
+ //# sourceMappingURL=phase-mirror.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phase-mirror.d.ts","sourceRoot":"","sources":["../src/phase-mirror.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAS,MAAM,kBAAkB,CAAC;AAGlE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB,CAAC,KAAK,SAAS,MAAM;IACtD,8DAA8D;IAC9D,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,KAAK,OAAO,CAAC;IAChE,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC9C,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,gDAAgD;IAChD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAClC,6FAA6F;IAC7F,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAChD;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,WAAW,CAAC,KAAK,SAAS,MAAM;IAC/C,mGAAmG;IACnG,QAAQ,CAAC,kBAAkB,EAAE,MAAM,IAAI,CAAC;IACxC,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9C,0FAA0F;IAC1F,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;IACnD,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;CAC7C;AAED,2FAA2F;AAC3F,wBAAgB,iBAAiB,CAAC,KAAK,SAAS,MAAM,EAAE,OAAO,EAAE,kBAAkB,CAAC,KAAK,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CA2F9G"}
@@ -0,0 +1,2 @@
1
+ import{createRouteSignals as e}from"./route-signals.js";import{computed as t,watch as n}from"@vielzeug/ripple";function createPhaseMirror(r){let{name:i,params:a}=e(r.router),o=t(()=>{let e=a.value.phase;return e!==void 0&&r.phases.includes(e)?e:null}),s=t(()=>{let e=r.currentPhase.value;return e?r.phases.filter(t=>r.canRevisit(e,t)):[]}),goToPhase=e=>{let t=r.subjectId.value;t&&r.router.navigate({name:r.phaseRoute,params:{id:t,phase:e}},{replace:!0})},c=[n(()=>`${r.subjectId.value??``}|${i.value??``}`,()=>{let e=r.subjectId.value,t=r.currentPhase.value,n=a.value.id;(typeof n!=`string`||n===e)&&i.value===r.detailRoute&&e&&t&&goToPhase(t)},{immediate:!0}),n(r.currentPhase,(e,t)=>{e&&e!==t&&o.value!==e&&goToPhase(e)}),n(()=>[o.value,r.currentPhase.value],([e,t],n)=>{if(i.value!==r.phaseRoute&&i.value!==r.detailRoute)return;let o=a.value.id;if(typeof o==`string`&&o!==r.subjectId.value)return;let s=n?.[1]??null;if((t===s||s===null)&&r.subjectId.value&&t&&e!==t){if(e&&r.canRevisit(t,e)){r.requestRevisit(e);return}goToPhase(t)}},{immediate:!0})];return{bounceIfMismatched:()=>{let e=r.currentPhase.value;r.subjectId.value&&e&&o.value!==null&&o.value!==e&&goToPhase(e)},dispose:()=>{for(let e of c.splice(0))e.dispose()},followPhase:goToPhase,phaseHref:e=>{let t=r.subjectId.value;return t?r.router.href(r.phaseRoute,{id:t,phase:e}):`#`},revisitPhases:s,routePhase:o}}export{createPhaseMirror};
2
+ //# sourceMappingURL=phase-mirror.js.map