stx-router 0.2.113 → 0.2.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/client.js +83 -35
  2. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -16,6 +16,7 @@ export function getRouterScript() {
16
16
  try{
17
17
  var transition=document.startViewTransition(callback);
18
18
  var onAbort=function(err){log('[router] view transition aborted:',err&&err.message?err.message:err)};
19
+ if(transition&&transition.ready&&transition.ready.catch)transition.ready.catch(onAbort);
19
20
  if(transition&&transition.finished&&transition.finished.catch)transition.finished.catch(onAbort);
20
21
  if(transition&&transition.updateCallbackDone&&transition.updateCallbackDone.catch)transition.updateCallbackDone.catch(onAbort);
21
22
  return true;
@@ -209,6 +210,18 @@ export function getRouterScript() {
209
210
  var h=0;for(var i=0;i<code.length;i++){h=((h<<5)-h)+code.charCodeAt(i);h|=0}
210
211
  return h;
211
212
  }
213
+ function hasStaticImport(code){
214
+ return /(?:^|\\n)[ \\t]*import(?:[ \\t]+|[{*'"])/.test(code);
215
+ }
216
+ function generatedSetupName(code){
217
+ var match=code.match(/function (__stx_setup_[A-Za-z0-9_]+)\\s*\\(/);
218
+ return match?match[1]:'';
219
+ }
220
+ function isSignalsRuntimeScript(script,code){
221
+ return !!(script&&script.hasAttribute&&script.hasAttribute('data-stx-runtime'))
222
+ ||(code.indexOf('_cleanupContainer')!==-1&&code.indexOf('signals runtime loading')!==-1)
223
+ ||code.indexOf("'use strict';var cloakStyle")!==-1;
224
+ }
212
225
  // Record scripts from the initial page load
213
226
  document.querySelectorAll('script').forEach(function(s){
214
227
  var text=s.textContent||'';
@@ -400,10 +413,15 @@ else {
400
413
  function doFragSwap(){
401
414
  // Extract scripts from fragment before injecting HTML
402
415
  var fragScripts=[];
416
+ var fragScriptId=0;
403
417
  var fragStyles=[];
404
418
  var fragCrosswindCSS=null;
405
- var cleanFrag=html.replace(new RegExp('<scr'+'ipt\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\/scr'+'ipt>','gi'),function(m,code){
406
- if(code&&code.trim())fragScripts.push(code);
419
+ var cleanFrag=html.replace(new RegExp('<scr'+'ipt\\\\b([^>]*)>([\\\\s\\\\S]*?)<\\\\/scr'+'ipt>','gi'),function(m,attrs,code){
420
+ if(code&&code.trim()&&!isSignalsRuntimeScript({hasAttribute:function(name){return name==='data-stx-runtime'&&attrs.indexOf('data-stx-runtime')!==-1}},code)){
421
+ var slot='fragment-'+(++fragScriptId);
422
+ fragScripts.push({text:code,slot:slot,setupName:generatedSetupName(code)});
423
+ return '<scr'+'ipt type="application/stx-pending" data-stx-route-script="'+slot+'"></scr'+'ipt>';
424
+ }
407
425
  return '';
408
426
  });
409
427
  cleanFrag=cleanFrag.replace(new RegExp('<sty'+'le\\\\b([^>]*)>([\\\\s\\\\S]*?)<\\\\/sty'+'le>','gi'),function(m,attrs,css){
@@ -452,7 +470,9 @@ else {
452
470
  // Execute page scripts FIRST \u2014 they define setup functions and set _latestSetup
453
471
  log('[router] frag scripts:', fragScripts.length);
454
472
  document.querySelectorAll('script[data-stx-page]').forEach(function(s){s.remove()});
455
- fragScripts.forEach(function(code){
473
+ fragScripts.sort(function(a,b){return (a.setupName?1:0)-(b.setupName?1:0)});
474
+ fragScripts.forEach(function(entry){
475
+ var code=entry.text;
456
476
  log('[router] exec script len:', code.length, 'has __stx_setup:', code.indexOf('__stx_setup')>-1);
457
477
  // Skip scripts that were already executed (layout-level partials
458
478
  // like theme.stx, stores.stx, nav.stx). Their top-level const/let
@@ -478,13 +498,19 @@ else {
478
498
  }
479
499
  executedScriptHashes[h]=1;
480
500
  var ns=document.createElement('script');
481
- var hasImport=code.indexOf('import ')!==-1;
501
+ var hasImport=hasStaticImport(code);
482
502
  if(hasImport){
483
503
  ns.type='module';
484
504
  }
485
505
  ns.textContent=(hasImport||isAlreadyScoped)?code:'{'+code+'}';
486
506
  ns.setAttribute('data-stx-page','');
487
- document.body.appendChild(ns);
507
+ var placeholder=document.querySelector('script[data-stx-route-script="'+entry.slot+'"]');
508
+ if(placeholder&&placeholder.parentNode){
509
+ ns.setAttribute('data-stx-positioned','');
510
+ placeholder.parentNode.replaceChild(ns,placeholder);
511
+ }else{
512
+ document.body.appendChild(ns);
513
+ }
488
514
  });
489
515
  log('[router] scripts done. _latestSetup:', !!window.stx._latestSetup);
490
516
  // THEN fire stx:load \u2014 now _latestSetup is set and processElement has the right scope
@@ -593,10 +619,35 @@ else {
593
619
  var newGroup=getDocLayoutGroup(doc,newLayout);
594
620
  isLayoutChange=curGroup!==newGroup||(curLayout&&newLayout&&curLayout!==newLayout);
595
621
  }
622
+ var incomingSetupName=newBody?(newBody.getAttribute('data-stx')||''):'';
623
+ // Scripts assigned through innerHTML are inert. Preserve executable
624
+ // scripts as placeholders so re-execution keeps document.currentScript
625
+ // beside the component template it owns.
626
+ var routedBodyScripts=[];
627
+ var routedBodyScriptId=0;
628
+ function prepareRoutedBodyScripts(root){
629
+ if(!root)return;
630
+ root.querySelectorAll('script').forEach(function(s){
631
+ var text=s.textContent||'';
632
+ if(s.hasAttribute('src')){s.remove();return}
633
+ var type=(s.getAttribute('type')||'').trim().toLowerCase();
634
+ var executable=!type||type==='text/javascript'||type==='application/javascript'||type==='module';
635
+ if(!executable||!text.trim())return;
636
+ if(isSignalsRuntimeScript(s,text)||text.indexOf('__stxRouter')!==-1){s.remove();return}
637
+ var slot='document-'+(++routedBodyScriptId);
638
+ var setupName=generatedSetupName(text);
639
+ if(setupName)incomingSetupName=setupName;
640
+ routedBodyScripts.push({text:text,runAlways:true,slot:slot,setupName:setupName});
641
+ s.textContent='';
642
+ s.setAttribute('type','application/stx-pending');
643
+ s.setAttribute('data-stx-route-script',slot);
644
+ });
645
+ }
646
+ prepareRoutedBodyScripts(isLayoutChange?newBody:newContent);
596
647
  if(isLayoutChange&&newBody){
597
648
  log('[router] full body swap for layout change');
598
649
  // Replace entire body content \u2014 layout chrome and all
599
- var bodyHTML=newBody.innerHTML.replace(new RegExp('<scr'+'ipt\\\\b[^>]*>[\\\\s\\\\S]*?<\\\\/scr'+'ipt\\\\s*>','gi'),'');
650
+ var bodyHTML=newBody.innerHTML;
600
651
  document.body.innerHTML=bodyHTML;
601
652
  // Copy body attributes (class, data-stx, etc.)
602
653
  Array.from(newBody.attributes).forEach(function(attr){document.body.setAttribute(attr.name,attr.value)});
@@ -618,8 +669,9 @@ else {
618
669
  var attrMap={};
619
670
  Array.prototype.forEach.call(newContent.attributes,function(a){attrMap[a.name]=a.value});
620
671
  setContainerAttrs(currentContent,attrMap);
621
- var cleanHTML=newContent.innerHTML.replace(new RegExp('<scr'+'ipt\\\\b[^>]*>[\\\\s\\\\S]*?<\\\\/scr'+'ipt\\\\s*>','gi'),'');
672
+ var cleanHTML=newContent.innerHTML;
622
673
  currentContent.innerHTML=cleanHTML;
674
+ if(incomingSetupName)document.body.setAttribute('data-stx',incomingSetupName);
623
675
  }
624
676
 
625
677
  // \u2500\u2500 Load new external <head> scripts \u2500\u2500
@@ -645,40 +697,20 @@ else {
645
697
  // Remove previously injected page scripts
646
698
  document.querySelectorAll('script[data-stx-page]').forEach(function(s){s.remove()});
647
699
 
648
- var scripts=[];
649
- function addScript(text, runAlways){
650
- scripts.push({text:text,runAlways:!!runAlways});
700
+ var scripts=routedBodyScripts.slice();
701
+ function addScript(text, runAlways, slot){
702
+ scripts.push({text:text,runAlways:!!runAlways,slot:slot||''});
651
703
  }
652
704
  if(isLayoutChange){
653
- // Layout change: collect ALL scripts from the new document body
654
- // (layout scripts, component mounts, setup functions \u2014 everything)
655
- var newBodyEl=doc.querySelector('body');
656
- if(newBodyEl){
657
- newBodyEl.querySelectorAll('script').forEach(function(s){
658
- var text=s.textContent||'';
659
- if(s.hasAttribute('src'))return;
660
- if(!text.trim())return;
661
- // Skip the signals runtime IIFE \u2014 it's already loaded
662
- if(text.indexOf("'use strict';var cloakStyle")!==-1)return;
663
- if(text.indexOf('__stxRouter')!==-1)return;
664
- addScript(text,true);
665
- });
666
- }
667
705
  // Also collect setup functions from <head>
668
706
  doc.querySelectorAll('head script').forEach(function(s){
669
707
  var text=s.textContent||'';
670
708
  if(s.hasAttribute('src'))return;
671
709
  if(!text.trim())return;
710
+ if(isSignalsRuntimeScript(s,text))return;
672
711
  if(text.indexOf('__stx_setup_')!==-1)addScript(text,true);
673
712
  });
674
713
  } else {
675
- // Same layout: collect scripts from the container
676
- newContent.querySelectorAll('script').forEach(function(s){
677
- var text=s.textContent||'';
678
- if(s.hasAttribute('src'))return;
679
- if(!text.trim())return;
680
- addScript(text,true);
681
- });
682
714
  // Collect page scripts from <head> AND <body> (outside container).
683
715
  // SSG builds place the setup function in <body> before <main>, not
684
716
  // in <head>. Without this, SPA navigation on static sites never
@@ -690,16 +722,26 @@ else {
690
722
  if(s.hasAttribute('src'))return;
691
723
  if(!text.trim())return;
692
724
  // Skip signals runtime and router (they contain __stx_setup references but aren't setup functions)
693
- if(text.indexOf("'use strict';var cloakStyle")!==-1)return;
725
+ if(isSignalsRuntimeScript(s,text))return;
694
726
  if(text.indexOf('__stxRouter')!==-1)return;
695
727
  if(newContent.contains(s))return;
696
- if(text.indexOf('__stx_setup_')===-1&&!s.hasAttribute('data-stx-scoped')&&!s.hasAttribute('data-stx-page'))return;
728
+ if(text.indexOf('__stx_setup_')===-1&&!s.hasAttribute('data-stx-scoped')&&!s.hasAttribute('data-stx-page')&&!s.hasAttribute('data-stx-route-params'))return;
697
729
  var key=text.substring(0,80);
698
730
  if(seenSetups[key])return;
699
731
  seenSetups[key]=1;
700
732
  addScript(text,true);
701
733
  });
702
734
  }
735
+ // Component setup scripts also assign window.stx._latestSetup. Run the
736
+ // destination page setup last so stx:load hydrates with the page scope,
737
+ // not whichever nested component happened to render last.
738
+ if(incomingSetupName){
739
+ scripts.sort(function(a,b){
740
+ var aPage=(a.setupName||generatedSetupName(a.text))===incomingSetupName?1:0;
741
+ var bPage=(b.setupName||generatedSetupName(b.text))===incomingSetupName?1:0;
742
+ return aPage-bPage;
743
+ });
744
+ }
703
745
 
704
746
  // Push history state (before active link updates so location.pathname is current)
705
747
  if(pushState!==false)history.pushState({},'',url+(hash||''));
@@ -738,7 +780,7 @@ else {
738
780
  // need special handling.
739
781
  var h=hashScript(text);
740
782
  var isSetup=text.indexOf('__stx_setup_')!==-1;
741
- var hasImport=text.indexOf('import ')!==-1;
783
+ var hasImport=hasStaticImport(text);
742
784
  if(!runAlways&&!isSetup&&executedScriptHashes[h])return;
743
785
  executedScriptHashes[h]=1;
744
786
  // Wrap scripts with import statements as modules (Bug 3 fix)
@@ -755,7 +797,13 @@ else {
755
797
  var alreadyScoped=text.trimStart().charAt(0)==='('||text.trimStart().charAt(0)===';'||text.indexOf('window.stx.mount')>-1;
756
798
  ns.textContent=(hasImport||alreadyScoped)?text:'{'+text+'}';
757
799
  ns.setAttribute('data-stx-page','');
758
- document.body.appendChild(ns);
800
+ var placeholder=entry.slot?document.querySelector('script[data-stx-route-script="'+entry.slot+'"]'):null;
801
+ if(placeholder&&placeholder.parentNode){
802
+ ns.setAttribute('data-stx-positioned','');
803
+ placeholder.parentNode.replaceChild(ns,placeholder);
804
+ }else{
805
+ document.body.appendChild(ns);
806
+ }
759
807
  });
760
808
  // THEN fire stx:load \u2014 now _latestSetup is set and processElement has the right scope
761
809
  window.dispatchEvent(new Event('stx:load'));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stx-router",
3
3
  "type": "module",
4
- "version": "0.2.113",
4
+ "version": "0.2.115",
5
5
  "description": "File-based router for STX — .stx template discovery, nested layouts, typed route params, middleware, and client-side SPA navigation.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",