@ramstack/alpinegear-router 1.1.0-preview.5 → 1.1.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.
package/README.md CHANGED
@@ -56,7 +56,7 @@ Alpine.start();
56
56
 
57
57
  <nav>
58
58
  <a x-router:link href="/">Home</a>
59
- <a x-router:link href="/about">About</a>
59
+ <a x-router:link.replace href="/about">About</a>
60
60
  </nav>
61
61
 
62
62
  <!-- Render the matching route -->
@@ -172,6 +172,27 @@ In this case:
172
172
  * `x-router:link` will locate the nested `<a>` element within the parent and use its `href` for routing.
173
173
  * The `$active` state will correctly reflect whether the nested link's `href` matches the current route.
174
174
 
175
+ ### Modifier `replace`
176
+
177
+ The `replace` modifier changes the default navigation behavior of the router. When a link with this modifier is clicked,
178
+ it triggers `$router.navigate(href, /* replace */ true)`.
179
+
180
+ Unlike the default behavior, which adds a new entry to the browser's history stack, this option replaces
181
+ the current history entry with the new URL. As a result, the user's navigation history remains unchanged,
182
+ and pressing the **"Back"** button will skip over the replaced entry.
183
+
184
+ ```html
185
+ <div x-router:hash>
186
+ ...
187
+ <nav>
188
+ <a x-router:link href="/">Home</a>
189
+ <!-- The "replace" modifier ensures that clicking this link
190
+ replaces the current history entry instead of adding a new one -->
191
+ <a x-router:link.replace href="/about">About</a>
192
+ </nav>
193
+ </div>
194
+ ```
195
+
175
196
  ## Inline and External templates
176
197
  Routes can be defined using either **inline templates** or **external templates**:
177
198
 
@@ -212,8 +233,8 @@ Returns `true` or `false`, indicating whether a `x-router:link` corresponds to t
212
233
 
213
234
  ```html
214
235
  <nav>
215
- <a x-router:link href="/" class="{ link__active: $active }">Home</a>
216
- <a x-router:link href="/about" class="{ link__active: $active }">About</a>
236
+ <a x-router:link href="/" class="{ active: $active }">Home</a>
237
+ <a x-router:link href="/about" class="{ active: $active }">About</a>
217
238
  </nav>
218
239
  ```
219
240
 
@@ -4,6 +4,7 @@ const is_nullish = value => value === null || value === undefined;
4
4
  const is_template = el => el instanceof HTMLTemplateElement;
5
5
  const is_function = value => typeof value === "function";
6
6
  const as_array = value => is_array(value) ? value : [value];
7
+ const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
7
8
 
8
9
  function assert(value, message) {
9
10
  if (!value) {
@@ -468,16 +469,19 @@ async function load_template(path) {
468
469
  return fragment;
469
470
  }
470
471
 
471
- function route({ directive, magic, $data }) {
472
+ function route({ directive, $data }) {
472
473
  directive("route", (el, { expression, value, modifiers }, { cleanup, evaluate }) => {
473
474
  if (!is_template(el)) {
474
475
  warn("x-route can only be used on a 'template' tag");
475
476
  return;
476
477
  }
477
478
 
478
- const route = closest(el, n => n._r_route)?._r_route;
479
+
480
+
481
+
482
+ const route = el._r_route;
479
483
 
480
- if (is_nullish(route) && (value === "view" || value === "handler")) {
484
+ if (!route && (value === "view" || value === "handler")) {
481
485
  warn(`no x-route directive found`);
482
486
  return;
483
487
  }
@@ -497,20 +501,18 @@ function route({ directive, magic, $data }) {
497
501
  }
498
502
 
499
503
  function process_route() {
500
- const router = closest(el, n => n._r_router)?._r_router;
501
- if (is_nullish(router)) {
502
- warn(`no x-router directive found`);
503
- return;
504
- }
504
+ const router = $data(el)?.$router;
505
+ if (router) {
506
+ const view = () => new Promise(resolve => resolve(el.content));
505
507
 
506
- const view = () => new Promise(resolve => resolve(el.content));
508
+ el._r_route = Object.assign(new RoutePattern(expression), { el, view, handler: () => Promise.resolve() });
509
+ router.routes.push(el._r_route);
507
510
 
508
- el._r_route = Object.assign(new RoutePattern(expression), { el, view, handler: () => Promise.resolve() });
509
- router.routes.push(el._r_route);
510
-
511
- cleanup(() => {
512
- router.routes = router.routes.filter(r => r !== el._r_route);
513
- });
511
+ cleanup(() => router.routes = router.routes.filter(r => r !== el._r_route));
512
+ }
513
+ else {
514
+ warn(`no x-router directive found`);
515
+ }
514
516
  }
515
517
 
516
518
  function process_handler() {
@@ -540,8 +542,6 @@ function route({ directive, magic, $data }) {
540
542
  });
541
543
  }
542
544
  });
543
-
544
- magic("route", el => closest(el, n => n._r_router)?._r_router.values);
545
545
  }
546
546
 
547
547
  let data;
@@ -674,13 +674,13 @@ function watch(get_value, callback, options = null) {
674
674
  return () => release(handle);
675
675
  }
676
676
 
677
- function router({ directive, magic, reactive }) {
678
- directive("router", (el, { expression, value }, { cleanup, evaluate }) => {
677
+ function router({ $data, addScopeToNode, directive, magic, reactive }) {
678
+ directive("router", (el, { modifiers, value }, { cleanup }) => {
679
679
  value || (value = "html5");
680
680
 
681
- const router = closest(el, node => node._r_router)?._r_router;
681
+ const router = $data(el).$router;
682
682
 
683
- if (is_nullish(router) && (value === "outlet" || value === "link")) {
683
+ if (!router && (value === "outlet" || value === "link")) {
684
684
  warn(`no x-router directive found`);
685
685
  return;
686
686
  }
@@ -708,12 +708,10 @@ function router({ directive, magic, reactive }) {
708
708
  const values = reactive({
709
709
  pattern: "",
710
710
  path: "",
711
- params: ""
711
+ params: {}
712
712
  });
713
713
 
714
- const api = is_nullish(value) && expression
715
- ? evaluate(expression)
716
- : create_history(value);
714
+ const api = create_history(value);
717
715
 
718
716
  const router = {
719
717
  routes: [],
@@ -738,7 +736,7 @@ function router({ directive, magic, reactive }) {
738
736
  }
739
737
  };
740
738
 
741
- el._r_router = router;
739
+ addScopeToNode(el, { $route: values, $router: router });
742
740
 
743
741
  function activate(route, path, params) {
744
742
  if (route.nodes?.length && values.path === path) {
@@ -749,7 +747,7 @@ function router({ directive, magic, reactive }) {
749
747
 
750
748
  values.path = path;
751
749
  values.pattern = route.template;
752
- values.params = params;
750
+ values.params = params ?? {};
753
751
 
754
752
  router.active = route;
755
753
 
@@ -775,6 +773,7 @@ function router({ directive, magic, reactive }) {
775
773
  for (let n of router.active.nodes ?? []) {
776
774
  n.remove();
777
775
  }
776
+
778
777
  router.active.nodes = null;
779
778
  router.active = null;
780
779
  }
@@ -816,7 +815,7 @@ function router({ directive, magic, reactive }) {
816
815
 
817
816
  e.preventDefault();
818
817
 
819
- router.navigate(`${ link.pathname }${ link.search }${ link.hash }`);
818
+ router.navigate(`${ link.pathname }${ link.search }${ link.hash }`, has_modifier(modifiers, "replace"));
820
819
  });
821
820
 
822
821
  cleanup(unsubscribe);
@@ -839,11 +838,9 @@ function router({ directive, magic, reactive }) {
839
838
  }
840
839
  });
841
840
 
842
- magic("router", el => closest(el, n => n._r_router)?._r_router);
843
-
844
841
  magic("active", el => {
845
- const router = closest(el, node => node._r_router)?._r_router;
846
- if (is_nullish(router)) {
842
+ const router = $data(el).$router;
843
+ if (!router) {
847
844
  warn("No x-router directive found");
848
845
  return false;
849
846
  }
@@ -1 +1 @@
1
- const e=(...e)=>console.warn("alpine-gear.js:",...e),t=Array.isArray,n=e=>null==e,r=e=>e instanceof HTMLTemplateElement,a=e=>"function"==typeof e,o=e=>t(e)?e:[e],i=e=>a(e)&&"AsyncFunction"===e.constructor?.name?e:function(...t){const n=e.apply(this,t);return a(n?.then)?n:Promise.resolve(n)},l=(e,t,n,r)=>(e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)),s=(e,t)=>{for(;e&&!t(e);)e=(e._x_teleportBack??e).parentElement;return e},u=Object.freeze({regex(e){const t=new RegExp(e);return{test(e){return t.test(e)}}},bool:()=>({test(e){return/^(?:true|false)$/i.test(e)},transform(e){return 4===e.length}}),int:()=>({test(e){return/^-?\d+$/.test(e)},transform(e){return+e}}),number:()=>({test(e){return/^[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$/.test(e)&&isFinite(parseFloat(e))},transform(e){return parseFloat(e)}}),alpha:()=>({test(e){return/^[a-z]+$/i.test(e)}}),min:e=>({test(t){return t>=+e}}),max:e=>({test(t){return+e>=t}}),range(e){let[t,n]=e.split(",",2).map((e=>e.trim()));return{test(e){return e>=+t&&+n>=e}}},length(e){let t=e.split(",").map((e=>e.trim()));return{test:2==t.length?e=>e.length>=+t[0]&&+t[1]>=e.length:e=>e.length===+t[0]}},minlength:e=>({test(t){return t.length>=+e}}),maxlength:e=>({test(t){return+e>=t.length}})});class c{#e;#t;#n;#r;#a;get template(){return this.#t}get regex(){return this.#e}get constraints(){return this.#a}constructor(e,t=null){this.#t=e,this.#e=function(e,t,r,a){t.push(...function(e,t){return function(e){e.find((e=>e.parts.length>1&&e.parts.every((e=>e.optional))))&&s("Using all segment parameters as optional is not permitted");const r=new Map;return e.flatMap((e=>e.parts)).forEach(((e,a,o)=>{if("literal"!==e.kind||0>e.value.indexOf("?")||s("Literal segments cannot contain the '?' character"),"parameter"===e.kind){e.catch_all&&a!==o.length-1&&s("A catch-all parameter can only appear as the last segment"),r.has(e.name)&&s(`The route parameter name '${e.name}' appears more than one time`),"*"===e.quantifier&&n(e.default)&&(e.default=""),""===e.default&&"*"!==e.quantifier&&(e.default=null),r.set(e.name,!0);for(let r of e.constraints){const e=t?.[r.name]??u[r.name];n(e)&&s(`Unknown constraint '${r.name}'`),Object.assign(r,e(r.argument))}}})),e}(function(){const t=[];for(let n=0;e.length>n;){const e=r(n);e.template&&t.push(e),n+=e.template.length+1}return t}());function r(t){let n=[],r=t;for(;e.length>r&&"/"!==e[r];){const e=o(r)||a(r);n.push(e),r+=e.template.length}return{template:e.slice(t,r),parts:n}}function a(t){if("{"!==e[t])return null;const r=i(t),a=e.slice(t,t+r.length+2),o=function(e){const t=e.match(/^(?<name>[a-z_$][a-z0-9_$-]*?)(?:[:?+*]|$)/i)?.groups?.name;return n(t)&&s("Invalid parameter name"),t}(r),u=/[*+?]/.exec(r[o.length])?.[0]??"",c=function(e,t){const r=[];for(let a=t;e.length>a;){":"!==e[a]&&s();const t=l(e.slice(a+1));a+=t.length+1;const o="("===e[a]?i(a,e):null;n(o)||(a+=o.length+2),t||o||s(),r.push({name:"="===t?"default":t||"regex",argument:o??""})}return r}(r,o.length+u.length);return{name:o,kind:"parameter",template:a,quantifier:u,constraints:c.filter((e=>"default"!==e.name)),default:c.find((e=>"default"===e.name))?.argument,required:"+"===u||""===u,optional:"?"===u||"*"===u,catch_all:"+"===u||"*"===u}}function o(t){for(let n=t;;n++)if(n>=e.length||"/"===e[n]||"{"===e[n]){if(n===t)return null;const r=e.slice(t,n);return{kind:"literal",template:r,value:r}}}function i(t,n){n??=e;const r=[];e:for(let e=t;n.length>e;e++){switch(n[e]){case"{":r.push("}");break;case"(":r.push(")");break;case"}":case")":if(r.pop()!==n[e])break e}if(0===r.length)return n.slice(t+1,e)}s()}function l(e){const t=e.match(/^(?<name>=|[a-z0-9_$]*)(?=[/:(]|$)/i)?.groups?.name;return n(t)&&s("Invalid constraint name"),t}function s(t="Invalid pattern"){throw new Error(`${t}: ${e}`)}}(e,a));let o=t.map((e=>e.parts.map(((t,n)=>{if("literal"===t.kind)return n?t.value:`/${t.value}`;if(r.set(t.name,t),1===e.parts.length&&"?"===t.quantifier)return`(?:/(?<${t.name}>[^/]+?))?`;if(t.catch_all){let e=`(?<${t.name}>.${t.quantifier})`;return n||(e=`(?:/${e})`),"*"===t.quantifier&&(e+="?"),"*"===t.quantifier?e+"?":e}{const e=`(?<${t.name}>[^/]+?)${t.quantifier}`;return n?e:`/${e}`}})).join(""))).join("")||"/";return"/"!==o&&(o+="/?"),new RegExp(`^${o}$`)}(e,this.#n=[],this.#r=new Map,this.#a=t??{})}match(e){let t=this.#e.exec(e);if(n(t))return null;t=t.groups??{};for(let[e,r]of this.#r.entries()){let a=t[e];if(n(a)&&n(r.default))continue;a||n(r.default)||(a=r.default);const o=r.catch_all?a.split("/").filter((e=>e.length)):[a];for(let e=0;o.length>e;e++)for(let t of r.constraints){if(t.test&&!t.test(o[e]))return null;t.transform&&(o[e]=t.transform(o[e]))}t[e]=r.catch_all?o:o[0]}return t}resolve(e){e=new Map(Object.entries(e));const t=[];for(let r of this.#n){const a=[];for(let t of r.parts)if("literal"===t.kind)a.push(t.value);else{let r=e.get(t.name);if(e.delete(t.name),(n(r)||""===r)&&(r=this.#r.get(t.name)?.default,t.catch_all&&r&&(r=r.split("/"))),n(r)||""===r){if(t.required)return null;if(t.optional&&t.default===r)continue}t.catch_all?(r=o(r),a.push(...r.map((e=>encodeURIComponent(e))).join("/"))):a.push(encodeURIComponent(r))}a.length&&t.push(a.join(""))}let r=[...e.entries()].map((([e,t])=>encodeURIComponent(e)+"="+encodeURIComponent(t))).join("&");r&&(r="?"+r);const a=t.join("/")+r;return"/"!==a[0]?"/"+a:a}}function f({directive:t,magic:a,$data:o}){t("route",((t,{expression:a,value:l},{cleanup:u,evaluate:f})=>{if(!r(t))return void e("x-route can only be used on a 'template' tag");const h=s(t,(e=>e._r_route))?._r_route;if(!n(h)||"view"!==l&&"handler"!==l)switch(l){case"view":h.view=()=>async function(t){let n;try{n=await fetch(t)}catch{}if(!n?.ok)return e(`Failed to load template from ${t}`),new DocumentFragment;const r=new DocumentFragment,a=(new DOMParser).parseFromString(await n.text(),"text/html");return r.append(...a.body.childNodes),r}(a),u((()=>{h.view=()=>new Promise((e=>e(new DocumentFragment)))}));break;case"handler":!function(){a||(a="[]"),a.startsWith("[")||(a=`[${a}]`);const e=f(a).map(i),r=o(t);h.handler=async t=>{for(let a of e){const e=await a.call(r,t);if(!n(e))return e}},u((()=>h.handler=null))}();break;default:!function(){const r=s(t,(e=>e._r_router))?._r_router;n(r)?e("no x-router directive found"):(t._r_route=Object.assign(new c(a),{el:t,view:()=>new Promise((e=>e(t.content))),handler(){return Promise.resolve()}}),r.routes.push(t._r_route),u((()=>{r.routes=r.routes.filter((e=>e!==t._r_route))})))}()}else e("no x-route directive found")})),a("route",(e=>s(e,(e=>e._r_router))?._r_router.values))}let h,p;function m(){for(let e in h)e in location&&(h[e]=location[e])}const d={get path(){return p.hash.slice(1)||"/"},get location(){return p},resolve(e){let t=new URL(e);return t.hash?t.hash.slice(1)||"/":t.pathname},navigate(e,t=!1){0>e.indexOf("#")&&(e="#"+e),v(e,t)}},g={get path(){return p.pathname},get location(){return p},resolve:e=>new URL(e).pathname,navigate(e,t=!1){v(e,t)}};function v(e,t){history[t?"replaceState":"pushState"]({},"",e),p.refresh()}const _={html5:g,hash:d};function w({directive:t,magic:a,reactive:o}){t("router",((t,{expression:a,value:i},{cleanup:u,evaluate:c})=>{i||(i="html5");const f=s(t,(e=>e._r_router))?._r_router;if(!n(f)||"outlet"!==i&&"link"!==i)switch(i){case"outlet":f.outlet?e("x-router:outlet already specified",f.outlet,t):(f.outlet=t,u((()=>f.outlet=null)));break;case"link":!function(){let n=function(t){if(x(t))return t;const n=t.querySelectorAll("a");return 1!==n.length&&e(`Expected exactly one link, but found ${n.length}`),n[0]}(t);if(n){t._r_routerlink=n;const e=(n.getAttribute("target")??"").indexOf("_blank")>=0,r=l(n,"click",(t=>{t.metaKey||t.altKey||t.ctrlKey||t.shiftKey||t.defaultPrevented||t.button>0||e||(t.preventDefault(),f.navigate(`${n.pathname}${n.search}${n.hash}`))}));u(r)}}();break;default:!function(){if(r(t))return void e("x-router cannot be used on a 'template' tag");const s=o({pattern:"",path:"",params:""}),f=n(i)&&a?c(a):function(t){p??=(h||(h=Alpine.reactive({hash:"",host:"",hostname:"",href:"",origin:"",pathname:"",port:0,protocol:"",search:"",refresh(){m()}}),m(),l(window,"hashchange",m),l(window,"popstate",m)),h),t||="html5";let n=_[t];return n||(e(`Unknown history API: ${t}`),n=g),n}(i),d={routes:[],outlet:null,active:null,history:f,values:s,async match(e){for(let t of this.routes){const n=t.match(e);if(n){const r={router:d,route:t,params:n,path:e};if(!1!==await t.handler(r))return r}}},navigate:(e,t=!1)=>(f.navigate(e,t),!0)};function v(){if(d.active){for(let e of d.active.nodes??[])e.remove();d.active.nodes=null,d.active=null}}t._r_router=d;const w=function(e,t,n=null){const{effect:r,release:a}=Alpine;let o,i,l=!1;const s=r((()=>{o=e(),l||(n?.deep&&JSON.stringify(o),i=o),(l||(n?.immediate??1))&&setTimeout((()=>{t(o,i),i=o}),0),l=!0}));return()=>a(s)}((()=>f.path),(async e=>{const t=await d.match(e);t?e===f.path&&function(e,t,n){if(e.nodes?.length&&s.path===t)return;v(),s.path=t,s.pattern=e.template,s.params=n,d.active=e;const a=d.outlet;a&&e.view().then((o=>{s.path===t&&s.pattern===e.template&&JSON.stringify(s.params)===JSON.stringify(n)&&(e.nodes=[...o.cloneNode(!0).childNodes],r(a)?e.nodes.forEach((e=>a.parentElement.insertBefore(e,a))):e.nodes.forEach((e=>a.append(e))))}))}(t.route,t.path,t.params):v()}));u(w),u(v)}()}else e("no x-router directive found")})),a("router",(e=>s(e,(e=>e._r_router))?._r_router)),a("active",(t=>{const r=s(t,(e=>e._r_router))?._r_router;if(n(r))return e("No x-router directive found"),!1;JSON.stringify(r.values);const a=x(t)?t:s(t,(e=>e._r_routerlink))?._r_routerlink;return a?r.history.resolve(a.href)===r.values.path:(t._r_routerlink_init?e("x-router:link directive not found",t):queueMicrotask((()=>{t._r_routerlink_init=!0,r.values.path=r.values.path})),!1)}))}function x(e){return"A"===e.tagName.toUpperCase()}function $(e){window.RoutePattern=c,e.plugin([w,f])}export{c as RoutePattern,$ as router};
1
+ const e=(...e)=>console.warn("alpine-gear.js:",...e),t=Array.isArray,n=e=>null==e,r=e=>e instanceof HTMLTemplateElement,a=e=>"function"==typeof e,o=e=>t(e)?e:[e],i=e=>a(e)&&"AsyncFunction"===e.constructor?.name?e:function(...t){const n=e.apply(this,t);return a(n?.then)?n:Promise.resolve(n)},l=(e,t,n,r)=>(e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)),s=Object.freeze({regex(e){const t=new RegExp(e);return{test:e=>t.test(e)}},bool(){return{test:e=>/^(?:true|false)$/i.test(e),transform:e=>4===e.length}},int(){return{test:e=>/^-?\d+$/.test(e),transform:e=>+e}},number(){return{test:e=>/^[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$/.test(e)&&isFinite(parseFloat(e)),transform:e=>parseFloat(e)}},alpha(){return{test:e=>/^[a-z]+$/i.test(e)}},min(e){return{test:t=>t>=+e}},max(e){return{test:t=>+e>=t}},range(e){let[t,n]=e.split(",",2).map((e=>e.trim()));return{test:e=>e>=+t&&+n>=e}},length(e){let t=e.split(",").map((e=>e.trim()));return{test:2==t.length?e=>e.length>=+t[0]&&+t[1]>=e.length:e=>e.length===+t[0]}},minlength(e){return{test:t=>t.length>=+e}},maxlength(e){return{test:t=>+e>=t.length}}});class u{#e;#t;#n;#r;#a;get template(){return this.#t}get regex(){return this.#e}get constraints(){return this.#a}constructor(e,t=null){this.#t=e,this.#e=function(e,t,r,a){t.push(...function(e,t){return function(e){e.find((e=>e.parts.length>1&&e.parts.every((e=>e.optional))))&&u("Using all segment parameters as optional is not permitted");const r=new Map;return e.flatMap((e=>e.parts)).forEach(((e,a,o)=>{if("literal"!==e.kind||0>e.value.indexOf("?")||u("Literal segments cannot contain the '?' character"),"parameter"===e.kind){e.catch_all&&a!==o.length-1&&u("A catch-all parameter can only appear as the last segment"),r.has(e.name)&&u(`The route parameter name '${e.name}' appears more than one time`),"*"===e.quantifier&&n(e.default)&&(e.default=""),""===e.default&&"*"!==e.quantifier&&(e.default=null),r.set(e.name,!0);for(let r of e.constraints){const e=t?.[r.name]??s[r.name];n(e)&&u(`Unknown constraint '${r.name}'`),Object.assign(r,e(r.argument))}}})),e}(function(){const t=[];for(let n=0;e.length>n;){const e=r(n);e.template&&t.push(e),n+=e.template.length+1}return t}());function r(t){let n=[],r=t;for(;e.length>r&&"/"!==e[r];){const e=o(r)||a(r);n.push(e),r+=e.template.length}return{template:e.slice(t,r),parts:n}}function a(t){if("{"!==e[t])return null;const r=i(t),a=e.slice(t,t+r.length+2),o=function(e){const t=e.match(/^(?<name>[a-z_$][a-z0-9_$-]*?)(?:[:?+*]|$)/i)?.groups?.name;return n(t)&&u("Invalid parameter name"),t}(r),s=/[*+?]/.exec(r[o.length])?.[0]??"",c=function(e,t){const r=[];for(let a=t;e.length>a;){":"!==e[a]&&u();const t=l(e.slice(a+1));a+=t.length+1;const o="("===e[a]?i(a,e):null;n(o)||(a+=o.length+2),t||o||u(),r.push({name:"="===t?"default":t||"regex",argument:o??""})}return r}(r,o.length+s.length);return{name:o,kind:"parameter",template:a,quantifier:s,constraints:c.filter((e=>"default"!==e.name)),default:c.find((e=>"default"===e.name))?.argument,required:"+"===s||""===s,optional:"?"===s||"*"===s,catch_all:"+"===s||"*"===s}}function o(t){for(let n=t;;n++)if(n>=e.length||"/"===e[n]||"{"===e[n]){if(n===t)return null;const r=e.slice(t,n);return{kind:"literal",template:r,value:r}}}function i(t,n){n??=e;const r=[];e:for(let e=t;n.length>e;e++){switch(n[e]){case"{":r.push("}");break;case"(":r.push(")");break;case"}":case")":if(r.pop()!==n[e])break e}if(0===r.length)return n.slice(t+1,e)}u()}function l(e){const t=e.match(/^(?<name>=|[a-z0-9_$]*)(?=[/:(]|$)/i)?.groups?.name;return n(t)&&u("Invalid constraint name"),t}function u(t="Invalid pattern"){throw new Error(`${t}: ${e}`)}}(e,a));let o=t.map((e=>e.parts.map(((t,n)=>{if("literal"===t.kind)return n?t.value:`/${t.value}`;if(r.set(t.name,t),1===e.parts.length&&"?"===t.quantifier)return`(?:/(?<${t.name}>[^/]+?))?`;if(t.catch_all){let e=`(?<${t.name}>.${t.quantifier})`;return n||(e=`(?:/${e})`),"*"===t.quantifier&&(e+="?"),"*"===t.quantifier?e+"?":e}{const e=`(?<${t.name}>[^/]+?)${t.quantifier}`;return n?e:`/${e}`}})).join(""))).join("")||"/";return"/"!==o&&(o+="/?"),new RegExp(`^${o}$`)}(e,this.#n=[],this.#r=new Map,this.#a=t??{})}match(e){let t=this.#e.exec(e);if(n(t))return null;t=t.groups??{};for(let[e,r]of this.#r.entries()){let a=t[e];if(n(a)&&n(r.default))continue;a||n(r.default)||(a=r.default);const o=r.catch_all?a.split("/").filter((e=>e.length)):[a];for(let e=0;o.length>e;e++)for(let t of r.constraints){if(t.test&&!t.test(o[e]))return null;t.transform&&(o[e]=t.transform(o[e]))}t[e]=r.catch_all?o:o[0]}return t}resolve(e){e=new Map(Object.entries(e));const t=[];for(let r of this.#n){const a=[];for(let t of r.parts)if("literal"===t.kind)a.push(t.value);else{let r=e.get(t.name);if(e.delete(t.name),(n(r)||""===r)&&(r=this.#r.get(t.name)?.default,t.catch_all&&r&&(r=r.split("/"))),n(r)||""===r){if(t.required)return null;if(t.optional&&t.default===r)continue}t.catch_all?(r=o(r),a.push(...r.map((e=>encodeURIComponent(e))).join("/"))):a.push(encodeURIComponent(r))}a.length&&t.push(a.join(""))}let r=[...e.entries()].map((([e,t])=>encodeURIComponent(e)+"="+encodeURIComponent(t))).join("&");r&&(r="?"+r);const a=t.join("/")+r;return"/"!==a[0]?"/"+a:a}}function c({directive:t,$data:a}){t("route",((t,{expression:o,value:l},{cleanup:s,evaluate:c})=>{if(!r(t))return void e("x-route can only be used on a 'template' tag");const f=t._r_route;if(f||"view"!==l&&"handler"!==l)switch(l){case"view":f.view=()=>async function(t){let n;try{n=await fetch(t)}catch{}if(!n?.ok)return e(`Failed to load template from ${t}`),new DocumentFragment;const r=new DocumentFragment,a=(new DOMParser).parseFromString(await n.text(),"text/html");return r.append(...a.body.childNodes),r}(o),s((()=>{f.view=()=>new Promise((e=>e(new DocumentFragment)))}));break;case"handler":!function(){o||(o="[]"),o.startsWith("[")||(o=`[${o}]`);const e=c(o).map(i),r=a(t);f.handler=async t=>{for(let a of e){const e=await a.call(r,t);if(!n(e))return e}},s((()=>f.handler=null))}();break;default:!function(){const n=a(t)?.$router;if(n){const e=()=>new Promise((e=>e(t.content)));t._r_route=Object.assign(new u(o),{el:t,view:e,handler:()=>Promise.resolve()}),n.routes.push(t._r_route),s((()=>n.routes=n.routes.filter((e=>e!==t._r_route))))}else e("no x-router directive found")}()}else e("no x-route directive found")}))}let f,h;function p(){for(let e in f)e in location&&(f[e]=location[e])}const m={get path(){return h.hash.slice(1)||"/"},get location(){return h},resolve(e){let t=new URL(e);return t.hash?t.hash.slice(1)||"/":t.pathname},navigate(e,t=!1){0>e.indexOf("#")&&(e="#"+e),g(e,t)}},d={get path(){return h.pathname},get location(){return h},resolve(e){return new URL(e).pathname},navigate(e,t=!1){g(e,t)}};function g(e,t){history[t?"replaceState":"pushState"]({},"",e),h.refresh()}const v={html5:d,hash:m};function w({$data:t,addScopeToNode:n,directive:a,magic:o,reactive:i}){a("router",((a,{modifiers:o,value:s},{cleanup:u})=>{s||(s="html5");const c=t(a).$router;if(c||"outlet"!==s&&"link"!==s)switch(s){case"outlet":c.outlet?e("x-router:outlet already specified",c.outlet,a):(c.outlet=a,u((()=>c.outlet=null)));break;case"link":!function(){let t=function(t){if($(t))return t;const n=t.querySelectorAll("a");return 1!==n.length&&e(`Expected exactly one link, but found ${n.length}`),n[0]}(a);if(t){a._r_routerlink=t;const e=(t.getAttribute("target")??"").indexOf("_blank")>=0,n=l(t,"click",(n=>{n.metaKey||n.altKey||n.ctrlKey||n.shiftKey||n.defaultPrevented||n.button>0||e||(n.preventDefault(),c.navigate(`${t.pathname}${t.search}${t.hash}`,(e=>e.includes("replace"))(o)))}));u(n)}}();break;default:!function(){if(r(a))return void e("x-router cannot be used on a 'template' tag");const t=i({pattern:"",path:"",params:{}}),o=function(t){h??=(f||(f=Alpine.reactive({hash:"",host:"",hostname:"",href:"",origin:"",pathname:"",port:0,protocol:"",search:"",refresh(){p()}}),p(),l(window,"hashchange",p),l(window,"popstate",p)),f),t||="html5";let n=v[t];return n||(e(`Unknown history API: ${t}`),n=d),n}(s),c={routes:[],outlet:null,active:null,history:o,values:t,async match(e){for(let t of this.routes){const n=t.match(e);if(n){const r={router:c,route:t,params:n,path:e};if(!1!==await t.handler(r))return r}}},navigate(e,t=!1){return o.navigate(e,t),!0}};function m(){if(c.active){for(let e of c.active.nodes??[])e.remove();c.active.nodes=null,c.active=null}}n(a,{$route:t,$router:c});const g=function(e,t,n=null){const{effect:r,release:a}=Alpine;let o,i,l=!1;const s=r((()=>{o=e(),l||(n?.deep&&JSON.stringify(o),i=o),(l||(n?.immediate??1))&&setTimeout((()=>{t(o,i),i=o}),0),l=!0}));return()=>a(s)}((()=>o.path),(async e=>{const n=await c.match(e);n?e===o.path&&function(e,n,a){if(e.nodes?.length&&t.path===n)return;m(),t.path=n,t.pattern=e.template,t.params=a??{},c.active=e;const o=c.outlet;o&&e.view().then((i=>{t.path===n&&t.pattern===e.template&&JSON.stringify(t.params)===JSON.stringify(a)&&(e.nodes=[...i.cloneNode(!0).childNodes],r(o)?e.nodes.forEach((e=>o.parentElement.insertBefore(e,o))):e.nodes.forEach((e=>o.append(e))))}))}(n.route,n.path,n.params):m()}));u(g),u(m)}()}else e("no x-router directive found")})),o("active",(n=>{const r=t(n).$router;if(!r)return e("No x-router directive found"),!1;JSON.stringify(r.values);const a=$(n)?n:(e=>{for(;e&&!e._r_routerlink;)e=(e._x_teleportBack??e).parentElement;return e})(n)?._r_routerlink;return a?r.history.resolve(a.href)===r.values.path:(n._r_routerlink_init?e("x-router:link directive not found",n):queueMicrotask((()=>{n._r_routerlink_init=!0,r.values.path=r.values.path})),!1)}))}function $(e){return"A"===e.tagName.toUpperCase()}function _(e){window.RoutePattern=u,e.plugin([w,c])}export{u as RoutePattern,_ as router};
@@ -7,6 +7,7 @@
7
7
  const is_template = el => el instanceof HTMLTemplateElement;
8
8
  const is_function = value => typeof value === "function";
9
9
  const as_array = value => is_array(value) ? value : [value];
10
+ const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
10
11
 
11
12
  function assert(value, message) {
12
13
  if (!value) {
@@ -471,16 +472,19 @@
471
472
  return fragment;
472
473
  }
473
474
 
474
- function route({ directive, magic, $data }) {
475
+ function route({ directive, $data }) {
475
476
  directive("route", (el, { expression, value, modifiers }, { cleanup, evaluate }) => {
476
477
  if (!is_template(el)) {
477
478
  warn("x-route can only be used on a 'template' tag");
478
479
  return;
479
480
  }
480
481
 
481
- const route = closest(el, n => n._r_route)?._r_route;
482
+
483
+
484
+
485
+ const route = el._r_route;
482
486
 
483
- if (is_nullish(route) && (value === "view" || value === "handler")) {
487
+ if (!route && (value === "view" || value === "handler")) {
484
488
  warn(`no x-route directive found`);
485
489
  return;
486
490
  }
@@ -500,20 +504,18 @@
500
504
  }
501
505
 
502
506
  function process_route() {
503
- const router = closest(el, n => n._r_router)?._r_router;
504
- if (is_nullish(router)) {
505
- warn(`no x-router directive found`);
506
- return;
507
- }
507
+ const router = $data(el)?.$router;
508
+ if (router) {
509
+ const view = () => new Promise(resolve => resolve(el.content));
508
510
 
509
- const view = () => new Promise(resolve => resolve(el.content));
511
+ el._r_route = Object.assign(new RoutePattern(expression), { el, view, handler: () => Promise.resolve() });
512
+ router.routes.push(el._r_route);
510
513
 
511
- el._r_route = Object.assign(new RoutePattern(expression), { el, view, handler: () => Promise.resolve() });
512
- router.routes.push(el._r_route);
513
-
514
- cleanup(() => {
515
- router.routes = router.routes.filter(r => r !== el._r_route);
516
- });
514
+ cleanup(() => router.routes = router.routes.filter(r => r !== el._r_route));
515
+ }
516
+ else {
517
+ warn(`no x-router directive found`);
518
+ }
517
519
  }
518
520
 
519
521
  function process_handler() {
@@ -543,8 +545,6 @@
543
545
  });
544
546
  }
545
547
  });
546
-
547
- magic("route", el => closest(el, n => n._r_router)?._r_router.values);
548
548
  }
549
549
 
550
550
  let data;
@@ -677,13 +677,13 @@
677
677
  return () => release(handle);
678
678
  }
679
679
 
680
- function router({ directive, magic, reactive }) {
681
- directive("router", (el, { expression, value }, { cleanup, evaluate }) => {
680
+ function router({ $data, addScopeToNode, directive, magic, reactive }) {
681
+ directive("router", (el, { modifiers, value }, { cleanup }) => {
682
682
  value || (value = "html5");
683
683
 
684
- const router = closest(el, node => node._r_router)?._r_router;
684
+ const router = $data(el).$router;
685
685
 
686
- if (is_nullish(router) && (value === "outlet" || value === "link")) {
686
+ if (!router && (value === "outlet" || value === "link")) {
687
687
  warn(`no x-router directive found`);
688
688
  return;
689
689
  }
@@ -711,12 +711,10 @@
711
711
  const values = reactive({
712
712
  pattern: "",
713
713
  path: "",
714
- params: ""
714
+ params: {}
715
715
  });
716
716
 
717
- const api = is_nullish(value) && expression
718
- ? evaluate(expression)
719
- : create_history(value);
717
+ const api = create_history(value);
720
718
 
721
719
  const router = {
722
720
  routes: [],
@@ -741,7 +739,7 @@
741
739
  }
742
740
  };
743
741
 
744
- el._r_router = router;
742
+ addScopeToNode(el, { $route: values, $router: router });
745
743
 
746
744
  function activate(route, path, params) {
747
745
  if (route.nodes?.length && values.path === path) {
@@ -752,7 +750,7 @@
752
750
 
753
751
  values.path = path;
754
752
  values.pattern = route.template;
755
- values.params = params;
753
+ values.params = params ?? {};
756
754
 
757
755
  router.active = route;
758
756
 
@@ -778,6 +776,7 @@
778
776
  for (let n of router.active.nodes ?? []) {
779
777
  n.remove();
780
778
  }
779
+
781
780
  router.active.nodes = null;
782
781
  router.active = null;
783
782
  }
@@ -819,7 +818,7 @@
819
818
 
820
819
  e.preventDefault();
821
820
 
822
- router.navigate(`${ link.pathname }${ link.search }${ link.hash }`);
821
+ router.navigate(`${ link.pathname }${ link.search }${ link.hash }`, has_modifier(modifiers, "replace"));
823
822
  });
824
823
 
825
824
  cleanup(unsubscribe);
@@ -842,11 +841,9 @@
842
841
  }
843
842
  });
844
843
 
845
- magic("router", el => closest(el, n => n._r_router)?._r_router);
846
-
847
844
  magic("active", el => {
848
- const router = closest(el, node => node._r_router)?._r_router;
849
- if (is_nullish(router)) {
845
+ const router = $data(el).$router;
846
+ if (!router) {
850
847
  warn("No x-router directive found");
851
848
  return false;
852
849
  }
@@ -1 +1 @@
1
- !function(){"use strict";const e=(...e)=>console.warn("alpine-gear.js:",...e),t=Array.isArray,n=e=>null==e,r=e=>e instanceof HTMLTemplateElement,a=e=>"function"==typeof e,o=e=>t(e)?e:[e],i=e=>a(e)&&"AsyncFunction"===e.constructor?.name?e:function(...t){const n=e.apply(this,t);return a(n?.then)?n:Promise.resolve(n)},l=(e,t,n,r)=>(e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)),s=(e,t)=>{for(;e&&!t(e);)e=(e._x_teleportBack??e).parentElement;return e},u=Object.freeze({regex(e){const t=new RegExp(e);return{test(e){return t.test(e)}}},bool:()=>({test(e){return/^(?:true|false)$/i.test(e)},transform(e){return 4===e.length}}),int:()=>({test(e){return/^-?\d+$/.test(e)},transform(e){return+e}}),number:()=>({test(e){return/^[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$/.test(e)&&isFinite(parseFloat(e))},transform(e){return parseFloat(e)}}),alpha:()=>({test(e){return/^[a-z]+$/i.test(e)}}),min:e=>({test(t){return t>=+e}}),max:e=>({test(t){return+e>=t}}),range(e){let[t,n]=e.split(",",2).map((e=>e.trim()));return{test(e){return e>=+t&&+n>=e}}},length(e){let t=e.split(",").map((e=>e.trim()));return{test:2==t.length?e=>e.length>=+t[0]&&+t[1]>=e.length:e=>e.length===+t[0]}},minlength:e=>({test(t){return t.length>=+e}}),maxlength:e=>({test(t){return+e>=t.length}})});class c{#e;#t;#n;#r;#a;get template(){return this.#t}get regex(){return this.#e}get constraints(){return this.#a}constructor(e,t=null){this.#t=e,this.#e=function(e,t,r,a){t.push(...function(e,t){return function(e){e.find((e=>e.parts.length>1&&e.parts.every((e=>e.optional))))&&s("Using all segment parameters as optional is not permitted");const r=new Map;return e.flatMap((e=>e.parts)).forEach(((e,a,o)=>{if("literal"!==e.kind||0>e.value.indexOf("?")||s("Literal segments cannot contain the '?' character"),"parameter"===e.kind){e.catch_all&&a!==o.length-1&&s("A catch-all parameter can only appear as the last segment"),r.has(e.name)&&s(`The route parameter name '${e.name}' appears more than one time`),"*"===e.quantifier&&n(e.default)&&(e.default=""),""===e.default&&"*"!==e.quantifier&&(e.default=null),r.set(e.name,!0);for(let r of e.constraints){const e=t?.[r.name]??u[r.name];n(e)&&s(`Unknown constraint '${r.name}'`),Object.assign(r,e(r.argument))}}})),e}(function(){const t=[];for(let n=0;e.length>n;){const e=r(n);e.template&&t.push(e),n+=e.template.length+1}return t}());function r(t){let n=[],r=t;for(;e.length>r&&"/"!==e[r];){const e=o(r)||a(r);n.push(e),r+=e.template.length}return{template:e.slice(t,r),parts:n}}function a(t){if("{"!==e[t])return null;const r=i(t),a=e.slice(t,t+r.length+2),o=function(e){const t=e.match(/^(?<name>[a-z_$][a-z0-9_$-]*?)(?:[:?+*]|$)/i)?.groups?.name;return n(t)&&s("Invalid parameter name"),t}(r),u=/[*+?]/.exec(r[o.length])?.[0]??"",c=function(e,t){const r=[];for(let a=t;e.length>a;){":"!==e[a]&&s();const t=l(e.slice(a+1));a+=t.length+1;const o="("===e[a]?i(a,e):null;n(o)||(a+=o.length+2),t||o||s(),r.push({name:"="===t?"default":t||"regex",argument:o??""})}return r}(r,o.length+u.length);return{name:o,kind:"parameter",template:a,quantifier:u,constraints:c.filter((e=>"default"!==e.name)),default:c.find((e=>"default"===e.name))?.argument,required:"+"===u||""===u,optional:"?"===u||"*"===u,catch_all:"+"===u||"*"===u}}function o(t){for(let n=t;;n++)if(n>=e.length||"/"===e[n]||"{"===e[n]){if(n===t)return null;const r=e.slice(t,n);return{kind:"literal",template:r,value:r}}}function i(t,n){n??=e;const r=[];e:for(let e=t;n.length>e;e++){switch(n[e]){case"{":r.push("}");break;case"(":r.push(")");break;case"}":case")":if(r.pop()!==n[e])break e}if(0===r.length)return n.slice(t+1,e)}s()}function l(e){const t=e.match(/^(?<name>=|[a-z0-9_$]*)(?=[/:(]|$)/i)?.groups?.name;return n(t)&&s("Invalid constraint name"),t}function s(t="Invalid pattern"){throw new Error(`${t}: ${e}`)}}(e,a));let o=t.map((e=>e.parts.map(((t,n)=>{if("literal"===t.kind)return n?t.value:`/${t.value}`;if(r.set(t.name,t),1===e.parts.length&&"?"===t.quantifier)return`(?:/(?<${t.name}>[^/]+?))?`;if(t.catch_all){let e=`(?<${t.name}>.${t.quantifier})`;return n||(e=`(?:/${e})`),"*"===t.quantifier&&(e+="?"),"*"===t.quantifier?e+"?":e}{const e=`(?<${t.name}>[^/]+?)${t.quantifier}`;return n?e:`/${e}`}})).join(""))).join("")||"/";return"/"!==o&&(o+="/?"),new RegExp(`^${o}$`)}(e,this.#n=[],this.#r=new Map,this.#a=t??{})}match(e){let t=this.#e.exec(e);if(n(t))return null;t=t.groups??{};for(let[e,r]of this.#r.entries()){let a=t[e];if(n(a)&&n(r.default))continue;a||n(r.default)||(a=r.default);const o=r.catch_all?a.split("/").filter((e=>e.length)):[a];for(let e=0;o.length>e;e++)for(let t of r.constraints){if(t.test&&!t.test(o[e]))return null;t.transform&&(o[e]=t.transform(o[e]))}t[e]=r.catch_all?o:o[0]}return t}resolve(e){e=new Map(Object.entries(e));const t=[];for(let r of this.#n){const a=[];for(let t of r.parts)if("literal"===t.kind)a.push(t.value);else{let r=e.get(t.name);if(e.delete(t.name),(n(r)||""===r)&&(r=this.#r.get(t.name)?.default,t.catch_all&&r&&(r=r.split("/"))),n(r)||""===r){if(t.required)return null;if(t.optional&&t.default===r)continue}t.catch_all?(r=o(r),a.push(...r.map((e=>encodeURIComponent(e))).join("/"))):a.push(encodeURIComponent(r))}a.length&&t.push(a.join(""))}let r=[...e.entries()].map((([e,t])=>encodeURIComponent(e)+"="+encodeURIComponent(t))).join("&");r&&(r="?"+r);const a=t.join("/")+r;return"/"!==a[0]?"/"+a:a}}function f({directive:t,magic:a,$data:o}){t("route",((t,{expression:a,value:l},{cleanup:u,evaluate:f})=>{if(!r(t))return void e("x-route can only be used on a 'template' tag");const h=s(t,(e=>e._r_route))?._r_route;if(!n(h)||"view"!==l&&"handler"!==l)switch(l){case"view":h.view=()=>async function(t){let n;try{n=await fetch(t)}catch{}if(!n?.ok)return e(`Failed to load template from ${t}`),new DocumentFragment;const r=new DocumentFragment,a=(new DOMParser).parseFromString(await n.text(),"text/html");return r.append(...a.body.childNodes),r}(a),u((()=>{h.view=()=>new Promise((e=>e(new DocumentFragment)))}));break;case"handler":!function(){a||(a="[]"),a.startsWith("[")||(a=`[${a}]`);const e=f(a).map(i),r=o(t);h.handler=async t=>{for(let a of e){const e=await a.call(r,t);if(!n(e))return e}},u((()=>h.handler=null))}();break;default:!function(){const r=s(t,(e=>e._r_router))?._r_router;n(r)?e("no x-router directive found"):(t._r_route=Object.assign(new c(a),{el:t,view:()=>new Promise((e=>e(t.content))),handler(){return Promise.resolve()}}),r.routes.push(t._r_route),u((()=>{r.routes=r.routes.filter((e=>e!==t._r_route))})))}()}else e("no x-route directive found")})),a("route",(e=>s(e,(e=>e._r_router))?._r_router.values))}let h,p;function m(){for(let e in h)e in location&&(h[e]=location[e])}const d={get path(){return p.hash.slice(1)||"/"},get location(){return p},resolve(e){let t=new URL(e);return t.hash?t.hash.slice(1)||"/":t.pathname},navigate(e,t=!1){0>e.indexOf("#")&&(e="#"+e),v(e,t)}},g={get path(){return p.pathname},get location(){return p},resolve:e=>new URL(e).pathname,navigate(e,t=!1){v(e,t)}};function v(e,t){history[t?"replaceState":"pushState"]({},"",e),p.refresh()}const _={html5:g,hash:d};function w({directive:t,magic:a,reactive:o}){t("router",((t,{expression:a,value:i},{cleanup:u,evaluate:c})=>{i||(i="html5");const f=s(t,(e=>e._r_router))?._r_router;if(!n(f)||"outlet"!==i&&"link"!==i)switch(i){case"outlet":f.outlet?e("x-router:outlet already specified",f.outlet,t):(f.outlet=t,u((()=>f.outlet=null)));break;case"link":!function(){let n=function(t){if(x(t))return t;const n=t.querySelectorAll("a");return 1!==n.length&&e(`Expected exactly one link, but found ${n.length}`),n[0]}(t);if(n){t._r_routerlink=n;const e=(n.getAttribute("target")??"").indexOf("_blank")>=0,r=l(n,"click",(t=>{t.metaKey||t.altKey||t.ctrlKey||t.shiftKey||t.defaultPrevented||t.button>0||e||(t.preventDefault(),f.navigate(`${n.pathname}${n.search}${n.hash}`))}));u(r)}}();break;default:!function(){if(r(t))return void e("x-router cannot be used on a 'template' tag");const s=o({pattern:"",path:"",params:""}),f=n(i)&&a?c(a):function(t){p??=(h||(h=Alpine.reactive({hash:"",host:"",hostname:"",href:"",origin:"",pathname:"",port:0,protocol:"",search:"",refresh(){m()}}),m(),l(window,"hashchange",m),l(window,"popstate",m)),h),t||="html5";let n=_[t];return n||(e(`Unknown history API: ${t}`),n=g),n}(i),d={routes:[],outlet:null,active:null,history:f,values:s,async match(e){for(let t of this.routes){const n=t.match(e);if(n){const r={router:d,route:t,params:n,path:e};if(!1!==await t.handler(r))return r}}},navigate:(e,t=!1)=>(f.navigate(e,t),!0)};function v(){if(d.active){for(let e of d.active.nodes??[])e.remove();d.active.nodes=null,d.active=null}}t._r_router=d;const w=function(e,t,n=null){const{effect:r,release:a}=Alpine;let o,i,l=!1;const s=r((()=>{o=e(),l||(n?.deep&&JSON.stringify(o),i=o),(l||(n?.immediate??1))&&setTimeout((()=>{t(o,i),i=o}),0),l=!0}));return()=>a(s)}((()=>f.path),(async e=>{const t=await d.match(e);t?e===f.path&&function(e,t,n){if(e.nodes?.length&&s.path===t)return;v(),s.path=t,s.pattern=e.template,s.params=n,d.active=e;const a=d.outlet;a&&e.view().then((o=>{s.path===t&&s.pattern===e.template&&JSON.stringify(s.params)===JSON.stringify(n)&&(e.nodes=[...o.cloneNode(!0).childNodes],r(a)?e.nodes.forEach((e=>a.parentElement.insertBefore(e,a))):e.nodes.forEach((e=>a.append(e))))}))}(t.route,t.path,t.params):v()}));u(w),u(v)}()}else e("no x-router directive found")})),a("router",(e=>s(e,(e=>e._r_router))?._r_router)),a("active",(t=>{const r=s(t,(e=>e._r_router))?._r_router;if(n(r))return e("No x-router directive found"),!1;JSON.stringify(r.values);const a=x(t)?t:s(t,(e=>e._r_routerlink))?._r_routerlink;return a?r.history.resolve(a.href)===r.values.path:(t._r_routerlink_init?e("x-router:link directive not found",t):queueMicrotask((()=>{t._r_routerlink_init=!0,r.values.path=r.values.path})),!1)}))}function x(e){return"A"===e.tagName.toUpperCase()}function $(e){window.RoutePattern=c,e.plugin([w,f])}document.addEventListener("alpine:init",(()=>{Alpine.plugin($)}))}();
1
+ !function(){"use strict";const e=(...e)=>console.warn("alpine-gear.js:",...e),t=Array.isArray,n=e=>null==e,r=e=>e instanceof HTMLTemplateElement,a=e=>"function"==typeof e,o=e=>t(e)?e:[e],i=e=>a(e)&&"AsyncFunction"===e.constructor?.name?e:function(...t){const n=e.apply(this,t);return a(n?.then)?n:Promise.resolve(n)},l=(e,t,n,r)=>(e.addEventListener(t,n,r),()=>e.removeEventListener(t,n,r)),s=Object.freeze({regex(e){const t=new RegExp(e);return{test:e=>t.test(e)}},bool(){return{test:e=>/^(?:true|false)$/i.test(e),transform:e=>4===e.length}},int(){return{test:e=>/^-?\d+$/.test(e),transform:e=>+e}},number(){return{test:e=>/^[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$/.test(e)&&isFinite(parseFloat(e)),transform:e=>parseFloat(e)}},alpha(){return{test:e=>/^[a-z]+$/i.test(e)}},min(e){return{test:t=>t>=+e}},max(e){return{test:t=>+e>=t}},range(e){let[t,n]=e.split(",",2).map((e=>e.trim()));return{test:e=>e>=+t&&+n>=e}},length(e){let t=e.split(",").map((e=>e.trim()));return{test:2==t.length?e=>e.length>=+t[0]&&+t[1]>=e.length:e=>e.length===+t[0]}},minlength(e){return{test:t=>t.length>=+e}},maxlength(e){return{test:t=>+e>=t.length}}});class u{#e;#t;#n;#r;#a;get template(){return this.#t}get regex(){return this.#e}get constraints(){return this.#a}constructor(e,t=null){this.#t=e,this.#e=function(e,t,r,a){t.push(...function(e,t){return function(e){e.find((e=>e.parts.length>1&&e.parts.every((e=>e.optional))))&&u("Using all segment parameters as optional is not permitted");const r=new Map;return e.flatMap((e=>e.parts)).forEach(((e,a,o)=>{if("literal"!==e.kind||0>e.value.indexOf("?")||u("Literal segments cannot contain the '?' character"),"parameter"===e.kind){e.catch_all&&a!==o.length-1&&u("A catch-all parameter can only appear as the last segment"),r.has(e.name)&&u(`The route parameter name '${e.name}' appears more than one time`),"*"===e.quantifier&&n(e.default)&&(e.default=""),""===e.default&&"*"!==e.quantifier&&(e.default=null),r.set(e.name,!0);for(let r of e.constraints){const e=t?.[r.name]??s[r.name];n(e)&&u(`Unknown constraint '${r.name}'`),Object.assign(r,e(r.argument))}}})),e}(function(){const t=[];for(let n=0;e.length>n;){const e=r(n);e.template&&t.push(e),n+=e.template.length+1}return t}());function r(t){let n=[],r=t;for(;e.length>r&&"/"!==e[r];){const e=o(r)||a(r);n.push(e),r+=e.template.length}return{template:e.slice(t,r),parts:n}}function a(t){if("{"!==e[t])return null;const r=i(t),a=e.slice(t,t+r.length+2),o=function(e){const t=e.match(/^(?<name>[a-z_$][a-z0-9_$-]*?)(?:[:?+*]|$)/i)?.groups?.name;return n(t)&&u("Invalid parameter name"),t}(r),s=/[*+?]/.exec(r[o.length])?.[0]??"",c=function(e,t){const r=[];for(let a=t;e.length>a;){":"!==e[a]&&u();const t=l(e.slice(a+1));a+=t.length+1;const o="("===e[a]?i(a,e):null;n(o)||(a+=o.length+2),t||o||u(),r.push({name:"="===t?"default":t||"regex",argument:o??""})}return r}(r,o.length+s.length);return{name:o,kind:"parameter",template:a,quantifier:s,constraints:c.filter((e=>"default"!==e.name)),default:c.find((e=>"default"===e.name))?.argument,required:"+"===s||""===s,optional:"?"===s||"*"===s,catch_all:"+"===s||"*"===s}}function o(t){for(let n=t;;n++)if(n>=e.length||"/"===e[n]||"{"===e[n]){if(n===t)return null;const r=e.slice(t,n);return{kind:"literal",template:r,value:r}}}function i(t,n){n??=e;const r=[];e:for(let e=t;n.length>e;e++){switch(n[e]){case"{":r.push("}");break;case"(":r.push(")");break;case"}":case")":if(r.pop()!==n[e])break e}if(0===r.length)return n.slice(t+1,e)}u()}function l(e){const t=e.match(/^(?<name>=|[a-z0-9_$]*)(?=[/:(]|$)/i)?.groups?.name;return n(t)&&u("Invalid constraint name"),t}function u(t="Invalid pattern"){throw new Error(`${t}: ${e}`)}}(e,a));let o=t.map((e=>e.parts.map(((t,n)=>{if("literal"===t.kind)return n?t.value:`/${t.value}`;if(r.set(t.name,t),1===e.parts.length&&"?"===t.quantifier)return`(?:/(?<${t.name}>[^/]+?))?`;if(t.catch_all){let e=`(?<${t.name}>.${t.quantifier})`;return n||(e=`(?:/${e})`),"*"===t.quantifier&&(e+="?"),"*"===t.quantifier?e+"?":e}{const e=`(?<${t.name}>[^/]+?)${t.quantifier}`;return n?e:`/${e}`}})).join(""))).join("")||"/";return"/"!==o&&(o+="/?"),new RegExp(`^${o}$`)}(e,this.#n=[],this.#r=new Map,this.#a=t??{})}match(e){let t=this.#e.exec(e);if(n(t))return null;t=t.groups??{};for(let[e,r]of this.#r.entries()){let a=t[e];if(n(a)&&n(r.default))continue;a||n(r.default)||(a=r.default);const o=r.catch_all?a.split("/").filter((e=>e.length)):[a];for(let e=0;o.length>e;e++)for(let t of r.constraints){if(t.test&&!t.test(o[e]))return null;t.transform&&(o[e]=t.transform(o[e]))}t[e]=r.catch_all?o:o[0]}return t}resolve(e){e=new Map(Object.entries(e));const t=[];for(let r of this.#n){const a=[];for(let t of r.parts)if("literal"===t.kind)a.push(t.value);else{let r=e.get(t.name);if(e.delete(t.name),(n(r)||""===r)&&(r=this.#r.get(t.name)?.default,t.catch_all&&r&&(r=r.split("/"))),n(r)||""===r){if(t.required)return null;if(t.optional&&t.default===r)continue}t.catch_all?(r=o(r),a.push(...r.map((e=>encodeURIComponent(e))).join("/"))):a.push(encodeURIComponent(r))}a.length&&t.push(a.join(""))}let r=[...e.entries()].map((([e,t])=>encodeURIComponent(e)+"="+encodeURIComponent(t))).join("&");r&&(r="?"+r);const a=t.join("/")+r;return"/"!==a[0]?"/"+a:a}}function c({directive:t,$data:a}){t("route",((t,{expression:o,value:l},{cleanup:s,evaluate:c})=>{if(!r(t))return void e("x-route can only be used on a 'template' tag");const f=t._r_route;if(f||"view"!==l&&"handler"!==l)switch(l){case"view":f.view=()=>async function(t){let n;try{n=await fetch(t)}catch{}if(!n?.ok)return e(`Failed to load template from ${t}`),new DocumentFragment;const r=new DocumentFragment,a=(new DOMParser).parseFromString(await n.text(),"text/html");return r.append(...a.body.childNodes),r}(o),s((()=>{f.view=()=>new Promise((e=>e(new DocumentFragment)))}));break;case"handler":!function(){o||(o="[]"),o.startsWith("[")||(o=`[${o}]`);const e=c(o).map(i),r=a(t);f.handler=async t=>{for(let a of e){const e=await a.call(r,t);if(!n(e))return e}},s((()=>f.handler=null))}();break;default:!function(){const n=a(t)?.$router;if(n){const e=()=>new Promise((e=>e(t.content)));t._r_route=Object.assign(new u(o),{el:t,view:e,handler:()=>Promise.resolve()}),n.routes.push(t._r_route),s((()=>n.routes=n.routes.filter((e=>e!==t._r_route))))}else e("no x-router directive found")}()}else e("no x-route directive found")}))}let f,h;function p(){for(let e in f)e in location&&(f[e]=location[e])}const m={get path(){return h.hash.slice(1)||"/"},get location(){return h},resolve(e){let t=new URL(e);return t.hash?t.hash.slice(1)||"/":t.pathname},navigate(e,t=!1){0>e.indexOf("#")&&(e="#"+e),g(e,t)}},d={get path(){return h.pathname},get location(){return h},resolve(e){return new URL(e).pathname},navigate(e,t=!1){g(e,t)}};function g(e,t){history[t?"replaceState":"pushState"]({},"",e),h.refresh()}const v={html5:d,hash:m};function w({$data:t,addScopeToNode:n,directive:a,magic:o,reactive:i}){a("router",((a,{modifiers:o,value:s},{cleanup:u})=>{s||(s="html5");const c=t(a).$router;if(c||"outlet"!==s&&"link"!==s)switch(s){case"outlet":c.outlet?e("x-router:outlet already specified",c.outlet,a):(c.outlet=a,u((()=>c.outlet=null)));break;case"link":!function(){let t=function(t){if($(t))return t;const n=t.querySelectorAll("a");return 1!==n.length&&e(`Expected exactly one link, but found ${n.length}`),n[0]}(a);if(t){a._r_routerlink=t;const e=(t.getAttribute("target")??"").indexOf("_blank")>=0,n=l(t,"click",(n=>{n.metaKey||n.altKey||n.ctrlKey||n.shiftKey||n.defaultPrevented||n.button>0||e||(n.preventDefault(),c.navigate(`${t.pathname}${t.search}${t.hash}`,(e=>e.includes("replace"))(o)))}));u(n)}}();break;default:!function(){if(r(a))return void e("x-router cannot be used on a 'template' tag");const t=i({pattern:"",path:"",params:{}}),o=function(t){h??=(f||(f=Alpine.reactive({hash:"",host:"",hostname:"",href:"",origin:"",pathname:"",port:0,protocol:"",search:"",refresh(){p()}}),p(),l(window,"hashchange",p),l(window,"popstate",p)),f),t||="html5";let n=v[t];return n||(e(`Unknown history API: ${t}`),n=d),n}(s),c={routes:[],outlet:null,active:null,history:o,values:t,async match(e){for(let t of this.routes){const n=t.match(e);if(n){const r={router:c,route:t,params:n,path:e};if(!1!==await t.handler(r))return r}}},navigate(e,t=!1){return o.navigate(e,t),!0}};function m(){if(c.active){for(let e of c.active.nodes??[])e.remove();c.active.nodes=null,c.active=null}}n(a,{$route:t,$router:c});const g=function(e,t,n=null){const{effect:r,release:a}=Alpine;let o,i,l=!1;const s=r((()=>{o=e(),l||(n?.deep&&JSON.stringify(o),i=o),(l||(n?.immediate??1))&&setTimeout((()=>{t(o,i),i=o}),0),l=!0}));return()=>a(s)}((()=>o.path),(async e=>{const n=await c.match(e);n?e===o.path&&function(e,n,a){if(e.nodes?.length&&t.path===n)return;m(),t.path=n,t.pattern=e.template,t.params=a??{},c.active=e;const o=c.outlet;o&&e.view().then((i=>{t.path===n&&t.pattern===e.template&&JSON.stringify(t.params)===JSON.stringify(a)&&(e.nodes=[...i.cloneNode(!0).childNodes],r(o)?e.nodes.forEach((e=>o.parentElement.insertBefore(e,o))):e.nodes.forEach((e=>o.append(e))))}))}(n.route,n.path,n.params):m()}));u(g),u(m)}()}else e("no x-router directive found")})),o("active",(n=>{const r=t(n).$router;if(!r)return e("No x-router directive found"),!1;JSON.stringify(r.values);const a=$(n)?n:(e=>{for(;e&&!e._r_routerlink;)e=(e._x_teleportBack??e).parentElement;return e})(n)?._r_routerlink;return a?r.history.resolve(a.href)===r.values.path:(n._r_routerlink_init?e("x-router:link directive not found",n):queueMicrotask((()=>{n._r_routerlink_init=!0,r.values.path=r.values.path})),!1)}))}function $(e){return"A"===e.tagName.toUpperCase()}function _(e){window.RoutePattern=u,e.plugin([w,c])}document.addEventListener("alpine:init",(()=>{Alpine.plugin(_)}))}();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramstack/alpinegear-router",
3
- "version": "1.1.0-preview.5",
3
+ "version": "1.1.0",
4
4
  "description": "@ramstack/alpinegear-router provides routing-related directives for Alpine.js, enabling client-side navigation and routing functionality.",
5
5
  "author": "Rameel Burhan",
6
6
  "license": "MIT",