stx-router 0.2.531 → 0.2.533

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 +61 -10
  2. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -188,7 +188,13 @@ function routerSource() {
188
188
  releaseScrollHold();
189
189
  var reached=function(){return Math.abs((window.pageYOffset||window.scrollY||0)-y)<2};
190
190
  if(typeof ResizeObserver!=='function')return;
191
- var h={stop:releaseScrollHold,ro:null,timer:0,onScroll:null,anchor:null,win:window,root:document.documentElement};
191
+ var h={stop:releaseScrollHold,ro:null,timer:0,onScroll:null,anchor:null,win:window,root:document.documentElement,seen:{}};
192
+ // Every place the page was at while the hold kept it: where the hold put
193
+ // it, and where the page's own height clamped it as it grew and shrank.
194
+ // Their scroll events arrive a frame later, when the height they were
195
+ // clamped to may be gone, so they are recognised by place, not by height.
196
+ var note=function(){h.seen[Math.round(window.pageYOffset||window.scrollY||0)]=true};
197
+ note();
192
198
  // Scroll anchoring off while held: content filling in above the place
193
199
  // would otherwise carry the window down with it (Chrome keeps what is
194
200
  // on screen in view), and Back landed thousands of pixels past it.
@@ -198,8 +204,11 @@ function routerSource() {
198
204
  // Held for the whole window even once reached: a page that is tall
199
205
  // enough when it is put back is often still drawing, above the place
200
206
  // as much as below it.
201
- if(scrollHold!==h||reached())return;
207
+ if(scrollHold!==h)return;
208
+ note();
209
+ if(reached())return;
202
210
  window.scrollTo({left:x,top:y,behavior:'instant'});
211
+ note();
203
212
  });
204
213
  h.ro.observe(document.body||document.documentElement);
205
214
  h.timer=setTimeout(releaseScrollHold,4000);
@@ -210,7 +219,7 @@ function routerSource() {
210
219
  h.onScroll=function(){
211
220
  var at=window.pageYOffset||window.scrollY||0;
212
221
  var end=Math.max(0,(document.documentElement.scrollHeight||0)-(window.innerHeight||0));
213
- if(Math.abs(at-y)<2||(at<y&&at>=end-1))return;
222
+ if(Math.abs(at-y)<2||(at<y&&at>=end-1)||h.seen[Math.round(at)])return;
214
223
  releaseScrollHold();
215
224
  };
216
225
  window.addEventListener('scroll',h.onScroll,{passive:true});
@@ -799,6 +808,35 @@ function routerSource() {
799
808
  var scopeId=root.getAttribute('data-stx-scope');
800
809
  return !!scopeId&&code.indexOf(scopeId)!==-1;
801
810
  }
811
+ // A script inside an :if (or :else) template runs when the branch is
812
+ // drawn: on a whole page the browser runs it as the runtime clones the
813
+ // template into the page, which is how a component used under :if gets its
814
+ // scope. A fragment's scripts are all turned into inert placeholders, and
815
+ // one inside template.content is out of the router's reach, so it never ran
816
+ // at all: the component's markup appeared with no setup behind it (an
817
+ // <OuraRecovery :if> on a page reached by a link stayed empty). Such a
818
+ // placeholder is made a live script again, so the branch runs it exactly as
819
+ // a whole page would. Loop templates keep theirs inert: bindFor runs those per
820
+ // row. Returns the slots it handed over, for runFragScripts to leave alone.
821
+ function armBranchScripts(root,slots){
822
+ if(!root||!root.querySelectorAll)return slots;
823
+ root.querySelectorAll('template').forEach(function(template){
824
+ if(hasLoopAttr(template)||!template.content)return;
825
+ template.content.querySelectorAll('script[type="application/stx-pending"][data-stx-route-script]').forEach(function(script){
826
+ if(script.hasAttribute('data-stx-scoped')&&loopOwnsScopedScript(script,script.textContent||''))return;
827
+ slots[script.getAttribute('data-stx-route-script')]=true;
828
+ // A new element, not the placeholder untyped: a script parsed by
829
+ // innerHTML is marked as already run, and so is every clone of it.
830
+ var live=script.ownerDocument.createElement('script');
831
+ var names=script.getAttributeNames?script.getAttributeNames():Array.prototype.map.call(script.attributes,function(a){return a.name});
832
+ names.forEach(function(name){if(name!=='type')live.setAttribute(name,script.getAttribute(name))});
833
+ live.textContent=script.textContent;
834
+ script.parentNode.replaceChild(live,script);
835
+ });
836
+ armBranchScripts(template.content,slots);
837
+ });
838
+ return slots;
839
+ }
802
840
  // A component scope script names the root it binds in data-stx-owner (#1958).
803
841
  // On navigation it runs only for a root that just ARRIVED: one swapped in
804
842
  // with the new content, which is not bound yet. A layout component that
@@ -1236,6 +1274,7 @@ function routerSource() {
1236
1274
  isNavigating=false;navAbort=null;
1237
1275
  document.body.classList.remove(o.loadingClass);finishProgress();
1238
1276
  settleDirection();
1277
+ if(openingUrl&&location.pathname+location.search!==openingUrl)keepOpeningPage();
1239
1278
  }
1240
1279
  function stale(){return id!==navSeq}
1241
1280
  // A failed fetch keeps the app: the destination shows a way to try again
@@ -1714,6 +1753,7 @@ else {
1714
1753
  return '<scr'+'ipt type="application/stx-pending" data-stx-route-script="'+slot+'"'
1715
1754
  +(scoped?' data-stx-scoped':'')
1716
1755
  +(runDecl?' data-stx-run="'+runDecl.toLowerCase()+'"':'')
1756
+ +(ownerDecl?' data-stx-owner="'+ownerDecl+'"':'')
1717
1757
  +'>'+code+'<\\/scr'+'ipt>';
1718
1758
  }
1719
1759
  return '';
@@ -1774,6 +1814,7 @@ else {
1774
1814
  if(scr)scr.cattrs=pendingContainerAttrs;
1775
1815
  applyContainerAttrs(host,pendingContainerAttrs);
1776
1816
  currentContent.innerHTML=cleanFrag;
1817
+ var branchSlots=armBranchScripts(currentContent,{});
1777
1818
  // Remove old page scripts
1778
1819
  qsa('script[data-stx-page]').forEach(function(s){s.remove()});
1779
1820
  if(pushState!==false)writeHistory(pushState,url+(hash||''));
@@ -1800,6 +1841,10 @@ else {
1800
1841
  fragScripts.forEach(function(entry){
1801
1842
  var code=entry.text;
1802
1843
  log('[router] exec script len:', code.length, 'has __stx_setup:', code.indexOf('__stx_setup')>-1);
1844
+ if(branchSlots&&branchSlots[entry.slot]){
1845
+ log('[router] leaving a branch script to its :if');
1846
+ return;
1847
+ }
1803
1848
  var placeholder=qs('script[data-stx-route-script="'+entry.slot+'"]');
1804
1849
  var nestedScopedTemplate=false;
1805
1850
  var scopedLoopSetup=false;
@@ -3584,14 +3629,22 @@ else {
3584
3629
  // The page the app was opened on came as a whole document, so it is the
3585
3630
  // one page the cache never holds: Back to it went to the server and left
3586
3631
  // the page being read on screen under the new address until it answered
3587
- // (a second or more on a distant server). Once the page is idle its
3588
- // fragment is fetched, the same request a link to it would make, so Back
3589
- // is a swap like every other.
3632
+ // (a second or more on a distant server). Once the router has taken the
3633
+ // person somewhere else its fragment is fetched, the same request a link
3634
+ // to it would make, so Back is a swap like every other.
3635
+ //
3636
+ // Not before: the fragment is answered no-store, and a document that has
3637
+ // fetched a no-store response is kept out of the back/forward cache. Done
3638
+ // on load, it cost the opening page its instant restore when the person
3639
+ // left the app and came back (a full-document Back). A router navigation
3640
+ // has already fetched one, so after it nothing more is lost.
3641
+ var openingUrl=location.pathname+location.search;
3590
3642
  function keepOpeningPage(){
3591
- if(!o.cache||!shouldUseFragmentResponse())return;
3643
+ var url=openingUrl;
3644
+ openingUrl='';
3645
+ if(!url||!o.prefetch||o.keepOpening===false||!o.cache||!shouldUseFragmentResponse())return;
3592
3646
  var c=navigator.connection;
3593
3647
  if(c&&(c.saveData||/2g/.test(c.effectiveType||'')))return;
3594
- var url=location.pathname+location.search;
3595
3648
  var key=cacheKey(url);
3596
3649
  // Late by design, so it can find the page gone (a test's document torn
3597
3650
  // down, a full navigation begun): never let that throw from a timer.
@@ -3887,8 +3940,6 @@ else {
3887
3940
  restoreKeptPages();
3888
3941
  prefetchEager();
3889
3942
  observeLinks();
3890
- // The runtime's first stx:load has usually gone by when this runs.
3891
- if(o.prefetch&&o.keepOpening!==false)keepOpeningPage();
3892
3943
  }
3893
3944
 
3894
3945
  if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init);else init();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stx-router",
3
3
  "type": "module",
4
- "version": "0.2.531",
4
+ "version": "0.2.533",
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",