@ramstack/alpinegear-router 1.2.4 → 1.4.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
@@ -372,6 +372,11 @@ of any element, running a callback when the element is removed from the DOM.
372
372
  **[@ramstack/alpinegear-hotkey](https://www.npmjs.com/package/@ramstack/alpinegear-hotkey)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/hotkey))<br>
373
373
  Provides the `x-hotkey` directive, which allows you to easily handle keyboard shortcuts within your Alpine.js components or application.
374
374
 
375
+ **[@ramstack/alpinegear-dialog](https://www.npmjs.com/package/@ramstack/alpinegear-dialog)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/dialog))<br>
376
+ Provides a headless dialog directive for Alpine.js based on the native HTML `<dialog>` element.
377
+ It supports declarative composition, value-based close semantics, and both modal and non-modal dialogs,
378
+ with optional Promise-based imperative control.
379
+
375
380
 
376
381
  ## Contributions
377
382
  Bug reports and contributions are welcome.
@@ -1,7 +1,7 @@
1
1
  const warn = (...args) => console.warn("alpinegear.js:", ...args);
2
2
  const is_array = Array.isArray;
3
3
  const is_nullish = value => value === null || value === undefined;
4
- const is_template = el => el instanceof HTMLTemplateElement;
4
+ const is_template = el => el.matches("template");
5
5
  const is_function = value => typeof value === "function";
6
6
  const as_array = value => is_array(value) ? value : [value];
7
7
  const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
@@ -454,7 +454,7 @@ async function load_template(path) {
454
454
  result = await fetch(path);
455
455
  }
456
456
  catch {
457
-
457
+ // skip
458
458
  }
459
459
 
460
460
  if (!result?.ok) {
@@ -476,9 +476,9 @@ function route({ directive, $data }) {
476
476
  return;
477
477
  }
478
478
 
479
-
480
-
481
-
479
+ //
480
+ // x-route:view and x-route:handler must be declared on the same element as x-route
481
+ //
482
482
  const route = el._r_route;
483
483
 
484
484
  if (!route && (value === "view" || value === "handler")) {
@@ -661,7 +661,7 @@ function watch(get_value, callback, options = null) {
661
661
  }
662
662
 
663
663
  if (initialized || (options?.immediate ?? true)) {
664
-
664
+ // Prevent the watcher from detecting its own dependencies.
665
665
  setTimeout(() => {
666
666
  callback(new_value, old_value);
667
667
  old_value = new_value;
@@ -681,7 +681,7 @@ function router({ $data, addScopeToNode, directive, magic, reactive }) {
681
681
  const router = $data(el).$router;
682
682
 
683
683
  if (!router && (value === "outlet" || value === "link")) {
684
- warn(`no x-router directive found`);
684
+ warn(`x-router:${value} is missing a parent x-router`);
685
685
  return;
686
686
  }
687
687
 
@@ -821,15 +821,15 @@ function router({ $data, addScopeToNode, directive, magic, reactive }) {
821
821
  cleanup(unsubscribe);
822
822
  }
823
823
 
824
-
825
-
826
-
827
-
824
+ //
825
+ // A warning about a non-existing anchor element is printed in the get_anchor_element function.
826
+ // warn("<a> element not found")
827
+ //
828
828
  }
829
829
 
830
830
  function process_outlet() {
831
831
  if (router.outlet) {
832
- warn("x-router:outlet already specified", router.outlet, el);
832
+ warn("x-router:outlet is already present", router.outlet, el);
833
833
  }
834
834
  else {
835
835
  router.outlet = el;
@@ -845,22 +845,22 @@ function router({ $data, addScopeToNode, directive, magic, reactive }) {
845
845
  return false;
846
846
  }
847
847
 
848
-
849
-
850
-
848
+ //
849
+ // Create a dependency on router.values
850
+ //
851
851
  JSON.stringify(router.values);
852
852
 
853
853
  const link = is_anchor_element(el) ? el : closest(el, node => node._r_routerlink)?._r_routerlink;
854
854
 
855
-
856
-
857
-
858
-
859
-
860
-
861
-
862
-
863
-
855
+ //
856
+ // The issue is that the router:link directive is processed later than x-bind,
857
+ // and if $active is used in x-bind, we won’t find node._r_routerlink.
858
+ // Therefore, we delay execution and try again.
859
+ //
860
+ // <div x-router:link :class="{ active: $active }">
861
+ // ...
862
+ // </div>
863
+ //
864
864
 
865
865
  if (link) {
866
866
  return router.history.resolve(link.href) === router.values.path;
@@ -872,9 +872,9 @@ function router({ $data, addScopeToNode, directive, magic, reactive }) {
872
872
  else {
873
873
  queueMicrotask(() => {
874
874
  el._r_routerlink_init = true;
875
-
876
-
877
-
875
+ //
876
+ // Force an upate
877
+ //
878
878
  router.values.path = router.values.path;
879
879
  });
880
880
  }
@@ -902,4 +902,4 @@ function plugin(alpine) {
902
902
  alpine.plugin([router, route]);
903
903
  }
904
904
 
905
- export { RoutePattern, plugin as router };
905
+ export { RoutePattern, plugin as router };
@@ -1 +1 @@
1
- const e=(...e)=>console.warn("alpinegear.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,t)=>{for(;e&&!t(e);)e=(e._x_teleportBack??e).parentElement;return e})(n,e=>e._r_routerlink)?._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};
1
+ const e=(...e)=>console.warn("alpinegear.js:",...e),t=Array.isArray,n=e=>null==e,r=e=>e.matches("template"),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 h=t._r_route;if(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}(o),s(()=>{h.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);h.handler=async t=>{for(let a of e){const e=await a.call(r,t);if(!n(e))return e}},s(()=>h.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 h,f;function p(){for(let e in h)e in location&&(h[e]=location[e])}const m={get path(){return f.hash.slice(1)||"/"},get location(){return f},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 f.pathname},get location(){return f},resolve(e){return new URL(e).pathname},navigate(e,t=!1){g(e,t)}};function g(e,t){history[t?"replaceState":"pushState"]({},"",e),f.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 is already present",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){f??=(h||(h=Alpine.reactive({hash:"",host:"",hostname:"",href:"",origin:"",pathname:"",port:0,protocol:"",search:"",refresh(){p()}}),p(),l(window,"hashchange",p),l(window,"popstate",p)),h),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(`x-router:${s} is missing a parent x-router`)}),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,t)=>{for(;e&&!t(e);)e=(e._x_teleportBack??e).parentElement;return e})(n,e=>e._r_routerlink)?._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};
@@ -4,7 +4,7 @@
4
4
  const warn = (...args) => console.warn("alpinegear.js:", ...args);
5
5
  const is_array = Array.isArray;
6
6
  const is_nullish = value => value === null || value === undefined;
7
- const is_template = el => el instanceof HTMLTemplateElement;
7
+ const is_template = el => el.matches("template");
8
8
  const is_function = value => typeof value === "function";
9
9
  const as_array = value => is_array(value) ? value : [value];
10
10
  const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
@@ -457,7 +457,7 @@
457
457
  result = await fetch(path);
458
458
  }
459
459
  catch {
460
-
460
+ // skip
461
461
  }
462
462
 
463
463
  if (!result?.ok) {
@@ -479,9 +479,9 @@
479
479
  return;
480
480
  }
481
481
 
482
-
483
-
484
-
482
+ //
483
+ // x-route:view and x-route:handler must be declared on the same element as x-route
484
+ //
485
485
  const route = el._r_route;
486
486
 
487
487
  if (!route && (value === "view" || value === "handler")) {
@@ -664,7 +664,7 @@
664
664
  }
665
665
 
666
666
  if (initialized || (options?.immediate ?? true)) {
667
-
667
+ // Prevent the watcher from detecting its own dependencies.
668
668
  setTimeout(() => {
669
669
  callback(new_value, old_value);
670
670
  old_value = new_value;
@@ -684,7 +684,7 @@
684
684
  const router = $data(el).$router;
685
685
 
686
686
  if (!router && (value === "outlet" || value === "link")) {
687
- warn(`no x-router directive found`);
687
+ warn(`x-router:${value} is missing a parent x-router`);
688
688
  return;
689
689
  }
690
690
 
@@ -824,15 +824,15 @@
824
824
  cleanup(unsubscribe);
825
825
  }
826
826
 
827
-
828
-
829
-
830
-
827
+ //
828
+ // A warning about a non-existing anchor element is printed in the get_anchor_element function.
829
+ // warn("<a> element not found")
830
+ //
831
831
  }
832
832
 
833
833
  function process_outlet() {
834
834
  if (router.outlet) {
835
- warn("x-router:outlet already specified", router.outlet, el);
835
+ warn("x-router:outlet is already present", router.outlet, el);
836
836
  }
837
837
  else {
838
838
  router.outlet = el;
@@ -848,22 +848,22 @@
848
848
  return false;
849
849
  }
850
850
 
851
-
852
-
853
-
851
+ //
852
+ // Create a dependency on router.values
853
+ //
854
854
  JSON.stringify(router.values);
855
855
 
856
856
  const link = is_anchor_element(el) ? el : closest(el, node => node._r_routerlink)?._r_routerlink;
857
857
 
858
-
859
-
860
-
861
-
862
-
863
-
864
-
865
-
866
-
858
+ //
859
+ // The issue is that the router:link directive is processed later than x-bind,
860
+ // and if $active is used in x-bind, we won’t find node._r_routerlink.
861
+ // Therefore, we delay execution and try again.
862
+ //
863
+ // <div x-router:link :class="{ active: $active }">
864
+ // ...
865
+ // </div>
866
+ //
867
867
 
868
868
  if (link) {
869
869
  return router.history.resolve(link.href) === router.values.path;
@@ -875,9 +875,9 @@
875
875
  else {
876
876
  queueMicrotask(() => {
877
877
  el._r_routerlink_init = true;
878
-
879
-
880
-
878
+ //
879
+ // Force an upate
880
+ //
881
881
  router.values.path = router.values.path;
882
882
  });
883
883
  }
@@ -907,4 +907,4 @@
907
907
 
908
908
  document.addEventListener("alpine:init", () => { Alpine.plugin(plugin); });
909
909
 
910
- })();
910
+ })();
@@ -1 +1 @@
1
- !function(){"use strict";const e=(...e)=>console.warn("alpinegear.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,t)=>{for(;e&&!t(e);)e=(e._x_teleportBack??e).parentElement;return e})(n,e=>e._r_routerlink)?._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(_)})}();
1
+ !function(){"use strict";const e=(...e)=>console.warn("alpinegear.js:",...e),t=Array.isArray,n=e=>null==e,r=e=>e.matches("template"),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 h=t._r_route;if(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}(o),s(()=>{h.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);h.handler=async t=>{for(let a of e){const e=await a.call(r,t);if(!n(e))return e}},s(()=>h.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 h,f;function p(){for(let e in h)e in location&&(h[e]=location[e])}const m={get path(){return f.hash.slice(1)||"/"},get location(){return f},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 f.pathname},get location(){return f},resolve(e){return new URL(e).pathname},navigate(e,t=!1){g(e,t)}};function g(e,t){history[t?"replaceState":"pushState"]({},"",e),f.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 is already present",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){f??=(h||(h=Alpine.reactive({hash:"",host:"",hostname:"",href:"",origin:"",pathname:"",port:0,protocol:"",search:"",refresh(){p()}}),p(),l(window,"hashchange",p),l(window,"popstate",p)),h),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(`x-router:${s} is missing a parent x-router`)}),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,t)=>{for(;e&&!t(e);)e=(e._x_teleportBack??e).parentElement;return e})(n,e=>e._r_routerlink)?._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.2.4",
3
+ "version": "1.4.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",