stx-router 0.2.337 → 0.2.339
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/dist/client.d.ts +15 -0
- package/dist/client.js +225 -73
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The router script as it ships: debug logging removed, then minified.
|
|
3
|
+
*
|
|
4
|
+
* Every page carries this, so the 32 `log()` call sites -- 2.3KB of the
|
|
5
|
+
* delivered bytes, printing nothing unless `debug` is set -- are not part of
|
|
6
|
+
* it. Stripping happens outside the try, as in generateSignalsRuntime: losing
|
|
7
|
+
* minification makes the script bigger, losing the strip would ship the logs.
|
|
8
|
+
*/
|
|
1
9
|
export declare function getRouterScript(): string;
|
|
10
|
+
/**
|
|
11
|
+
* The router script with its `log()` calls intact, for development.
|
|
12
|
+
*
|
|
13
|
+
* Set `debug` in the router config to see them. Minified the same way, so what
|
|
14
|
+
* a developer runs behaves exactly like what ships.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getRouterScriptDev(): string;
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,45 @@
|
|
|
1
|
-
let cachedRouterScript;
|
|
1
|
+
let cachedRouterScript, cachedRouterScriptDev;
|
|
2
|
+
function stripRouterLogs(source) {
|
|
3
|
+
const out = [], needle = "log(";
|
|
4
|
+
let i = 0;
|
|
5
|
+
while (i < source.length) {
|
|
6
|
+
const hit = source.indexOf(needle, i);
|
|
7
|
+
if (hit === -1) {
|
|
8
|
+
out.push(source.slice(i));
|
|
9
|
+
break;
|
|
10
|
+
}
|
|
11
|
+
const before = hit > 0 ? source[hit - 1] : " ", isDeclaration = /\bfunction\s+$/.test(source.slice(Math.max(0, hit - 16), hit));
|
|
12
|
+
if (/[\w$.]/.test(before) || isDeclaration) {
|
|
13
|
+
out.push(source.slice(i, hit + needle.length));
|
|
14
|
+
i = hit + needle.length;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
out.push(source.slice(i, hit));
|
|
18
|
+
let depth = 1, j = hit + needle.length;
|
|
19
|
+
while (j < source.length && depth > 0) {
|
|
20
|
+
const character = source[j];
|
|
21
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
22
|
+
const quote = character;
|
|
23
|
+
j++;
|
|
24
|
+
while (j < source.length && source[j] !== quote) {
|
|
25
|
+
if (source[j] === "\\")
|
|
26
|
+
j++;
|
|
27
|
+
j++;
|
|
28
|
+
}
|
|
29
|
+
j++;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (character === "(")
|
|
33
|
+
depth++;
|
|
34
|
+
else if (character === ")")
|
|
35
|
+
depth--;
|
|
36
|
+
j++;
|
|
37
|
+
}
|
|
38
|
+
out.push("0");
|
|
39
|
+
i = j;
|
|
40
|
+
}
|
|
41
|
+
return out.join("");
|
|
42
|
+
}
|
|
2
43
|
function minifyRouterScript(source) {
|
|
3
44
|
try {
|
|
4
45
|
return new Bun.Transpiler({ loader: "js", minifyWhitespace: !0 }).transformSync(source).replace(/\}(var |let |const |function )/g, "};$1").trim();
|
|
@@ -6,22 +47,109 @@ function minifyRouterScript(source) {
|
|
|
6
47
|
return source;
|
|
7
48
|
}
|
|
8
49
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return cachedRouterScript;
|
|
12
|
-
cachedRouterScript = minifyRouterScript(`
|
|
50
|
+
function routerSource() {
|
|
51
|
+
return `
|
|
13
52
|
;(function(){
|
|
14
53
|
'use strict';
|
|
15
54
|
var ROUTER_REV=7;
|
|
16
55
|
if(window.__stxRouter&&window.__stxRouter.__rev===ROUTER_REV)return;
|
|
17
56
|
|
|
18
57
|
// \u2500\u2500 Configuration \u2500\u2500
|
|
19
|
-
var defaults={container:'main',loadingClass:'stx-navigating',viewTransitions:true,cache:true,scrollToTop:true,prefetch:true,progress:true,progressColor:'#78dce8',progressHeight:'2px',interceptAllLinks:false,prefetchCacheMax:50,routeFocus:true,announceRoute:true,interceptForms:false,cssLoadTimeout:1500};
|
|
58
|
+
var defaults={container:'main',loadingClass:'stx-navigating',viewTransitions:true,cache:true,scrollToTop:true,prefetch:true,progress:true,progressColor:'#78dce8',progressHeight:'2px',interceptAllLinks:false,prefetchCacheMax:50,routeFocus:true,announceRoute:true,interceptForms:false,cssLoadTimeout:1500,scrollRestoration:true};
|
|
20
59
|
var o=Object.assign({},defaults,window.__stxRouterConfig||{},window.STX_ROUTER_OPTIONS||{});
|
|
21
60
|
var containerSel=o.container;
|
|
22
61
|
var debug=!!o.debug;
|
|
23
62
|
function log(){if(debug&&typeof console!=='undefined'&&console.log)console.log.apply(console,arguments)}
|
|
24
63
|
|
|
64
|
+
// Shorthands for the document-rooted queries this script makes ~33 times.
|
|
65
|
+
// Every page carries these bytes, and the budget in
|
|
66
|
+
// scripts/performance-budgets.ts is what noticed.
|
|
67
|
+
var LAYOUT_META='meta[name="stx-layout"]';
|
|
68
|
+
var LAYOUT_GROUP_META='meta[name="stx-layout-group"]';
|
|
69
|
+
var BUILD_META='meta[name="stx-build"]';
|
|
70
|
+
function qs(sel){return document.querySelector(sel)}
|
|
71
|
+
function qsa(sel){return document.querySelectorAll(sel)}
|
|
72
|
+
function ce(tag){return document.createElement(tag)}
|
|
73
|
+
function dhead(){return document.head}
|
|
74
|
+
|
|
75
|
+
// \u2500\u2500 Scroll position \u2500\u2500
|
|
76
|
+
// Forward navigation goes to the top, or to the hash. Back and forward return
|
|
77
|
+
// to where the entry was left -- the browser cannot do that for us, because
|
|
78
|
+
// by the time it would restore, the content of the entry has not been fetched
|
|
79
|
+
// yet, so it clamps to whatever height the outgoing page happened to have.
|
|
80
|
+
// history.scrollRestoration therefore goes to manual and the positions are
|
|
81
|
+
// kept here, keyed by a token written into each history entry (two entries
|
|
82
|
+
// for the same URL keep their own positions, which is the point).
|
|
83
|
+
//
|
|
84
|
+
// Manual restoration also takes the browser's reload behaviour away, so the
|
|
85
|
+
// positions are mirrored into sessionStorage: a reload, or a return to the
|
|
86
|
+
// tab, finds the entry's token already in history.state and restores from
|
|
87
|
+
// there. sessionStorage is per tab and dies with it, same as the history.
|
|
88
|
+
var SCROLL_TOKEN='__stxScroll';
|
|
89
|
+
var SCROLL_STORE='stx:scroll:';
|
|
90
|
+
var scrollSeq=0;
|
|
91
|
+
var pendingScroll=null;
|
|
92
|
+
var restoreScroll=!!o.scrollRestoration;
|
|
93
|
+
function newScrollToken(){return 's'+(++scrollSeq)+'-'+Date.now()}
|
|
94
|
+
var scrollToken=(history.state&&history.state[SCROLL_TOKEN])||newScrollToken();
|
|
95
|
+
// sessionStorage is the only store: it is synchronous, it is per tab, it dies
|
|
96
|
+
// with the tab exactly as the history does, and it is the one that survives a
|
|
97
|
+
// reload. A private window that refuses it degrades to scrolling to the top.
|
|
98
|
+
function readScroll(token){
|
|
99
|
+
try{
|
|
100
|
+
var parts=(window.sessionStorage.getItem(SCROLL_STORE+token)||'').split(',');
|
|
101
|
+
return parts.length===2?[parseFloat(parts[0])||0,parseFloat(parts[1])||0]:null;
|
|
102
|
+
}catch(e){return null}
|
|
103
|
+
}
|
|
104
|
+
// Called while the outgoing entry is still the current one, so the position
|
|
105
|
+
// read here belongs to the token being written.
|
|
106
|
+
function rememberScroll(){
|
|
107
|
+
if(!restoreScroll)return;
|
|
108
|
+
try{window.sessionStorage.setItem(SCROLL_STORE+scrollToken,(window.pageXOffset||window.scrollX||0)+','+(window.pageYOffset||window.scrollY||0))}catch(e){}
|
|
109
|
+
}
|
|
110
|
+
// One place decides where a swap leaves the viewport, so the fragment path
|
|
111
|
+
// and the whole-document path cannot drift apart.
|
|
112
|
+
function applyScroll(hash){
|
|
113
|
+
if(pendingScroll){
|
|
114
|
+
var at=pendingScroll;
|
|
115
|
+
pendingScroll=null;
|
|
116
|
+
window.scrollTo({left:at[0],top:at[1],behavior:'instant'});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if(o.scrollToTop&&!hash){window.scrollTo({top:0,behavior:'instant'});return}
|
|
120
|
+
if(hash){var el=qs(hash);if(el)el.scrollIntoView({behavior:'smooth'})}
|
|
121
|
+
}
|
|
122
|
+
// Keeps whatever another script put in the entry; only adds the token.
|
|
123
|
+
function stampScrollToken(){
|
|
124
|
+
var state={};
|
|
125
|
+
var current=history.state;
|
|
126
|
+
if(current&&typeof current==='object')for(var key in current)state[key]=current[key];
|
|
127
|
+
state[SCROLL_TOKEN]=scrollToken;
|
|
128
|
+
try{history.replaceState(state,'',location.href)}catch(e){}
|
|
129
|
+
}
|
|
130
|
+
if(restoreScroll){
|
|
131
|
+
try{history.scrollRestoration='manual'}catch(e){}
|
|
132
|
+
// A token already in the entry means this document is a reload or a
|
|
133
|
+
// restore of an entry visited before, so its position is worth asking for.
|
|
134
|
+
var known=history.state&&history.state[SCROLL_TOKEN];
|
|
135
|
+
if(known){
|
|
136
|
+
var saved=readScroll(scrollToken);
|
|
137
|
+
if(saved&&(saved[0]||saved[1])){
|
|
138
|
+
var restoreAt=function(){window.scrollTo({left:saved[0],top:saved[1],behavior:'instant'})};
|
|
139
|
+
if(document.readyState==='complete')restoreAt();
|
|
140
|
+
else window.addEventListener('load',restoreAt,{once:true});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else{
|
|
144
|
+
stampScrollToken();
|
|
145
|
+
}
|
|
146
|
+
// A reload or a link out of the app never calls writeHistory, so this is
|
|
147
|
+
// the only chance to record where the page was left. pagehide fires for
|
|
148
|
+
// both, and unlike beforeunload it does not keep the page out of the
|
|
149
|
+
// back/forward cache.
|
|
150
|
+
window.addEventListener('pagehide',rememberScroll);
|
|
151
|
+
}
|
|
152
|
+
|
|
25
153
|
// Replace, never merge: absent payloads must forget the outgoing page's data.
|
|
26
154
|
// Run at the committed swap, not during prefetch or a superseded navigation.
|
|
27
155
|
function hydrateServerData(html){
|
|
@@ -44,7 +172,7 @@ export function getRouterScript() {
|
|
|
44
172
|
// Conservative on purpose: act only when BOTH ids are known. A missing id is
|
|
45
173
|
// "no information", never "mismatch", so statically hosted output (no headers)
|
|
46
174
|
// and older servers behave exactly as before.
|
|
47
|
-
var buildMeta=
|
|
175
|
+
var buildMeta=qs(BUILD_META);
|
|
48
176
|
var loadedBuild=buildMeta?(buildMeta.getAttribute('content')||''):'';
|
|
49
177
|
window.__stxBuild=loadedBuild;
|
|
50
178
|
function isBuildSkew(incoming){
|
|
@@ -192,7 +320,7 @@ export function getRouterScript() {
|
|
|
192
320
|
function parseContainerAttrs(attrStr){
|
|
193
321
|
var map={};
|
|
194
322
|
if(!attrStr)return map;
|
|
195
|
-
var probe=
|
|
323
|
+
var probe=ce('div');
|
|
196
324
|
probe.innerHTML='<i '+attrStr+'></i>';
|
|
197
325
|
var src=probe.firstChild;
|
|
198
326
|
// getAttributeNames first: it is the same standard DOM answer and it is
|
|
@@ -281,7 +409,7 @@ export function getRouterScript() {
|
|
|
281
409
|
}
|
|
282
410
|
|
|
283
411
|
function getContainer(){
|
|
284
|
-
return
|
|
412
|
+
return qs(containerSel)||qs('[data-stx-content]')||qs('main');
|
|
285
413
|
}
|
|
286
414
|
|
|
287
415
|
// \u2500\u2500 Navigation \u2500\u2500
|
|
@@ -365,7 +493,7 @@ export function getRouterScript() {
|
|
|
365
493
|
// navigation. Scripts without an owner are unaffected.
|
|
366
494
|
function ownerStays(owner){
|
|
367
495
|
if(!owner)return false;
|
|
368
|
-
var root=
|
|
496
|
+
var root=qs('[data-stx-scope="'+owner+'"]');
|
|
369
497
|
return !root||typeof root.__stx_disposers==='function';
|
|
370
498
|
}
|
|
371
499
|
function hashScript(code){
|
|
@@ -532,7 +660,7 @@ export function getRouterScript() {
|
|
|
532
660
|
if(!key||loadedExternalScripts[key])return null;
|
|
533
661
|
loadedExternalScripts[key]=1;
|
|
534
662
|
return new Promise(function(resolve){
|
|
535
|
-
var el=
|
|
663
|
+
var el=ce('script');
|
|
536
664
|
el.src=key;
|
|
537
665
|
// Deliberately NOT data-stx-page: both swap paths clear those on every
|
|
538
666
|
// navigation, which would sweep this away and \u2014 since it is never loaded
|
|
@@ -541,12 +669,12 @@ export function getRouterScript() {
|
|
|
541
669
|
el.setAttribute('data-stx-external','');
|
|
542
670
|
el.onload=function(){resolve()};
|
|
543
671
|
el.onerror=function(){log('[router] container script failed:',key);resolve()};
|
|
544
|
-
|
|
672
|
+
dhead().appendChild(el);
|
|
545
673
|
});
|
|
546
674
|
}
|
|
547
675
|
|
|
548
676
|
// Record scripts from the initial page load
|
|
549
|
-
|
|
677
|
+
qsa('script').forEach(function(s){
|
|
550
678
|
var text=s.textContent||'';
|
|
551
679
|
if(s.hasAttribute('src'))rememberExternalScript(s.getAttribute('src'));
|
|
552
680
|
else if(text.trim())executedScriptHashes[hashScript(text)]=1;
|
|
@@ -573,17 +701,17 @@ export function getRouterScript() {
|
|
|
573
701
|
return part?part.replace(/\\.stx$/i,''):'app';
|
|
574
702
|
}
|
|
575
703
|
function getCurrentLayoutGroup(){
|
|
576
|
-
var meta=
|
|
704
|
+
var meta=qs(LAYOUT_GROUP_META);
|
|
577
705
|
if(meta&&meta.getAttribute('content'))return meta.getAttribute('content');
|
|
578
|
-
var layout=
|
|
706
|
+
var layout=qs(LAYOUT_META);
|
|
579
707
|
return defaultLayoutGroup(layout?layout.getAttribute('content'):'');
|
|
580
708
|
}
|
|
581
709
|
function getDocLayoutGroup(doc,layout){
|
|
582
|
-
var meta=doc&&doc.querySelector?doc.querySelector(
|
|
710
|
+
var meta=doc&&doc.querySelector?doc.querySelector(LAYOUT_GROUP_META):null;
|
|
583
711
|
return meta&&meta.getAttribute('content')?meta.getAttribute('content'):defaultLayoutGroup(layout||'');
|
|
584
712
|
}
|
|
585
713
|
function checkLayoutChange(newLayout,targetUrl,newGroup){
|
|
586
|
-
var currentLayout=
|
|
714
|
+
var currentLayout=qs(LAYOUT_META);
|
|
587
715
|
var curLayoutName=currentLayout?currentLayout.getAttribute('content'):'';
|
|
588
716
|
var curGroup=getCurrentLayoutGroup();
|
|
589
717
|
var nextGroup=newGroup||defaultLayoutGroup(newLayout);
|
|
@@ -612,9 +740,14 @@ export function getRouterScript() {
|
|
|
612
740
|
// through swap() to each of the three history sites would have been the
|
|
613
741
|
// alternative (#1807).
|
|
614
742
|
function writeHistory(mode,href){
|
|
615
|
-
|
|
616
|
-
|
|
743
|
+
// The position belongs to the entry being left, and scrollToken still
|
|
744
|
+
// names it here.
|
|
745
|
+
rememberScroll();
|
|
746
|
+
if(mode==='replace'){history.replaceState(tokenState(),'',href);return}
|
|
747
|
+
scrollToken=newScrollToken();
|
|
748
|
+
history.pushState(tokenState(),'',href);
|
|
617
749
|
}
|
|
750
|
+
function tokenState(){var state={};state[SCROLL_TOKEN]=scrollToken;return state}
|
|
618
751
|
|
|
619
752
|
// Second arg accepts the legacy pushState boolean OR an options object
|
|
620
753
|
// { replace }. Callers inside this file still pass the boolean.
|
|
@@ -636,7 +769,7 @@ export function getRouterScript() {
|
|
|
636
769
|
|
|
637
770
|
if(t.pathname===location.pathname&&t.hash){
|
|
638
771
|
if(pushState!==false)writeHistory(pushState,t.href);
|
|
639
|
-
var el=
|
|
772
|
+
var el=qs(t.hash);
|
|
640
773
|
if(el)el.scrollIntoView({behavior:'smooth'});
|
|
641
774
|
return Promise.resolve(true);
|
|
642
775
|
}
|
|
@@ -781,7 +914,7 @@ else {
|
|
|
781
914
|
function routeAnnouncer(){
|
|
782
915
|
var el=document.getElementById('stx-route-announcer');
|
|
783
916
|
if(el)return el;
|
|
784
|
-
el=
|
|
917
|
+
el=ce('div');
|
|
785
918
|
el.id='stx-route-announcer';
|
|
786
919
|
el.setAttribute('aria-live','polite');
|
|
787
920
|
el.setAttribute('role','status');
|
|
@@ -814,7 +947,7 @@ else {
|
|
|
814
947
|
function focusAfterNavigation(hash){
|
|
815
948
|
if(o.routeFocus===false)return;
|
|
816
949
|
var target=null;
|
|
817
|
-
if(hash){try{target=
|
|
950
|
+
if(hash){try{target=qs(hash)}catch(e){target=null}}
|
|
818
951
|
if(!target)target=getContainer();
|
|
819
952
|
if(!target||!target.focus)return;
|
|
820
953
|
// The container is not naturally focusable. tabindex="-1" makes it
|
|
@@ -837,7 +970,7 @@ else {
|
|
|
837
970
|
// be the last one, every navigation, not just the first time it is seen.
|
|
838
971
|
function findGeneratedCss(abs){
|
|
839
972
|
var found=null;
|
|
840
|
-
|
|
973
|
+
qsa('head link[data-css][href]').forEach(function(link){
|
|
841
974
|
if(new URL(link.getAttribute('href'),location.href).href!==abs)return;
|
|
842
975
|
// Collapse duplicates, so moving one copy to the end can never leave an
|
|
843
976
|
// older copy of the same sheet behind to fight it.
|
|
@@ -866,7 +999,7 @@ else {
|
|
|
866
999
|
if(link.__stxCssReady)pending.push(link.__stxCssReady);
|
|
867
1000
|
return;
|
|
868
1001
|
}
|
|
869
|
-
link=
|
|
1002
|
+
link=ce('link');
|
|
870
1003
|
link.setAttribute('data-css','generated');
|
|
871
1004
|
link.setAttribute('rel','stylesheet');
|
|
872
1005
|
link.setAttribute('href',new URL(href,location.href).href===abs?href:abs);
|
|
@@ -887,7 +1020,7 @@ else {
|
|
|
887
1020
|
link.onerror=function(){finish(true)};
|
|
888
1021
|
});
|
|
889
1022
|
pending.push(link.__stxCssReady);
|
|
890
|
-
|
|
1023
|
+
dhead().appendChild(link);
|
|
891
1024
|
});
|
|
892
1025
|
return pending.length?Promise.all(pending).then(function(results){return results.every(function(ok){return ok!==false})}):null;
|
|
893
1026
|
}
|
|
@@ -923,7 +1056,7 @@ else {
|
|
|
923
1056
|
link.removeAttribute('data-stx-css-pending');
|
|
924
1057
|
link.removeAttribute('media');
|
|
925
1058
|
}
|
|
926
|
-
|
|
1059
|
+
dhead().appendChild(link);
|
|
927
1060
|
});
|
|
928
1061
|
}
|
|
929
1062
|
|
|
@@ -1036,18 +1169,18 @@ else {
|
|
|
1036
1169
|
});
|
|
1037
1170
|
cleanFrag=cleanFrag.replace(new RegExp('<!--stx-held-script-(\\\\d+)-->','g'),function(m,i){return heldScripts[Number(i)]});
|
|
1038
1171
|
// Remove old page styles (not css \u2014 that gets merged)
|
|
1039
|
-
|
|
1172
|
+
qsa('style[data-stx-page]').forEach(function(s){s.remove()});
|
|
1040
1173
|
// Merge css CSS from fragment into existing css style
|
|
1041
1174
|
if(fragCss){
|
|
1042
|
-
var curCss=
|
|
1175
|
+
var curCss=qs('head style[data-css]');
|
|
1043
1176
|
if(curCss){
|
|
1044
1177
|
var merged=mergeCss(curCss.textContent||'',fragCss);
|
|
1045
1178
|
if(merged)curCss.textContent=merged;
|
|
1046
1179
|
}else{
|
|
1047
|
-
var cw=
|
|
1180
|
+
var cw=ce('style');
|
|
1048
1181
|
cw.setAttribute('data-css','generated');
|
|
1049
1182
|
cw.textContent=fragCss;
|
|
1050
|
-
|
|
1183
|
+
dhead().appendChild(cw);
|
|
1051
1184
|
}
|
|
1052
1185
|
}
|
|
1053
1186
|
// Already present ones are MOVED last, not skipped \u2014 see
|
|
@@ -1055,10 +1188,10 @@ else {
|
|
|
1055
1188
|
promoteGeneratedCss(fragCssHrefs,location.href);
|
|
1056
1189
|
// Add new page styles
|
|
1057
1190
|
fragStyles.forEach(function(s){
|
|
1058
|
-
var el=
|
|
1191
|
+
var el=ce('style');
|
|
1059
1192
|
el.textContent=s.css;
|
|
1060
1193
|
el.setAttribute('data-stx-page','');
|
|
1061
|
-
|
|
1194
|
+
dhead().appendChild(el);
|
|
1062
1195
|
});
|
|
1063
1196
|
// Swap content \u2014 apply the destination container's own attributes first
|
|
1064
1197
|
// so the incoming markup lands in a correctly-laid-out container instead
|
|
@@ -1066,11 +1199,10 @@ else {
|
|
|
1066
1199
|
applyContainerAttrs(currentContent,pendingContainerAttrs);
|
|
1067
1200
|
currentContent.innerHTML=cleanFrag;
|
|
1068
1201
|
// Remove old page scripts
|
|
1069
|
-
|
|
1202
|
+
qsa('script[data-stx-page]').forEach(function(s){s.remove()});
|
|
1070
1203
|
if(pushState!==false)writeHistory(pushState,url+(hash||''));
|
|
1071
1204
|
refreshCurrentLinks();
|
|
1072
|
-
|
|
1073
|
-
else if(hash){var el=document.querySelector(hash);if(el)el.scrollIntoView({behavior:'smooth'})}
|
|
1205
|
+
applyScroll(hash);
|
|
1074
1206
|
window.dispatchEvent(new CustomEvent('stx:navigate',{detail:{url:url}}));
|
|
1075
1207
|
// Before page scripts run, so a page that focuses its own control on
|
|
1076
1208
|
// mount still wins \u2014 its script executes after this.
|
|
@@ -1080,7 +1212,7 @@ else {
|
|
|
1080
1212
|
setTimeout(announceRoute,0);
|
|
1081
1213
|
// Execute page scripts FIRST \u2014 they define setup functions and set _latestSetup
|
|
1082
1214
|
log('[router] frag scripts:', fragScripts.length);
|
|
1083
|
-
|
|
1215
|
+
qsa('script[data-stx-page]').forEach(function(s){s.remove()});
|
|
1084
1216
|
// The page's module registry first (#1957), setups last. A fragment
|
|
1085
1217
|
// lists its scripts in document order, and the registry is emitted
|
|
1086
1218
|
// after the content it serves: a component that imports a package ran
|
|
@@ -1092,11 +1224,11 @@ else {
|
|
|
1092
1224
|
fragScripts.forEach(function(entry){
|
|
1093
1225
|
var code=entry.text;
|
|
1094
1226
|
log('[router] exec script len:', code.length, 'has __stx_setup:', code.indexOf('__stx_setup')>-1);
|
|
1095
|
-
var placeholder=
|
|
1227
|
+
var placeholder=qs('script[data-stx-route-script="'+entry.slot+'"]');
|
|
1096
1228
|
var nestedScopedTemplate=false;
|
|
1097
1229
|
var scopedLoopSetup=false;
|
|
1098
1230
|
if(entry.scoped&&placeholder){
|
|
1099
|
-
|
|
1231
|
+
qsa('template').forEach(function(template){
|
|
1100
1232
|
if(template.contains(placeholder)||(template.content&&template.content.contains(placeholder)))
|
|
1101
1233
|
nestedScopedTemplate=true;
|
|
1102
1234
|
});
|
|
@@ -1139,7 +1271,7 @@ else {
|
|
|
1139
1271
|
return;
|
|
1140
1272
|
}
|
|
1141
1273
|
executedScriptHashes[h]=1;
|
|
1142
|
-
var ns=
|
|
1274
|
+
var ns=ce('script');
|
|
1143
1275
|
var hasImport=hasStaticImport(code);
|
|
1144
1276
|
if(hasImport){
|
|
1145
1277
|
ns.type='module';
|
|
@@ -1202,7 +1334,7 @@ else {
|
|
|
1202
1334
|
var doc=parser.parseFromString(html,'text/html');
|
|
1203
1335
|
// Full documents carry the build id as a meta rather than a header, and
|
|
1204
1336
|
// this path also serves cached HTML, so check here too (#1772).
|
|
1205
|
-
var docBuildMeta=doc.querySelector(
|
|
1337
|
+
var docBuildMeta=doc.querySelector(BUILD_META);
|
|
1206
1338
|
var docBuild=docBuildMeta?(docBuildMeta.getAttribute('content')||''):'';
|
|
1207
1339
|
if(isBuildSkew(docBuild)){reloadForSkew(url,docBuild);return Promise.resolve(false)}
|
|
1208
1340
|
// Same hand-off as the fragment path, for the same reason (#1809, #1839).
|
|
@@ -1236,12 +1368,12 @@ else {
|
|
|
1236
1368
|
// \u2500\u2500 Swap <head> styles \u2500\u2500
|
|
1237
1369
|
// Inject new styles FIRST, then remove old to prevent unstyled flash
|
|
1238
1370
|
var keepIds={'stx-view-transitions':1,'stx-r-css':1};
|
|
1239
|
-
var curStyles=
|
|
1371
|
+
var curStyles=qsa('head style');
|
|
1240
1372
|
var newStyles=doc.querySelectorAll('head style');
|
|
1241
1373
|
|
|
1242
1374
|
// Merge css styles instead of replacing \u2014 persistent elements
|
|
1243
1375
|
// (nav, footer) outside <main> still need their utility classes
|
|
1244
|
-
var curCss=
|
|
1376
|
+
var curCss=qs('head style[data-css]');
|
|
1245
1377
|
var newCss=null;
|
|
1246
1378
|
newStyles.forEach(function(s){if(s.getAttribute('data-css'))newCss=s});
|
|
1247
1379
|
|
|
@@ -1253,20 +1385,20 @@ else {
|
|
|
1253
1385
|
var incoming=[];
|
|
1254
1386
|
newStyles.forEach(function(s){
|
|
1255
1387
|
if(!keepIds[s.id]&&!s.getAttribute('data-css')){
|
|
1256
|
-
var ns=
|
|
1388
|
+
var ns=ce('style');
|
|
1257
1389
|
ns.textContent=s.textContent;
|
|
1258
1390
|
ns.setAttribute('data-stx-incoming','');
|
|
1259
|
-
|
|
1391
|
+
dhead().appendChild(ns);
|
|
1260
1392
|
incoming.push(ns);
|
|
1261
1393
|
}
|
|
1262
1394
|
});
|
|
1263
1395
|
|
|
1264
1396
|
// If no existing css but new page has one, add it
|
|
1265
1397
|
if(!curCss&&newCss){
|
|
1266
|
-
var ns=
|
|
1398
|
+
var ns=ce('style');
|
|
1267
1399
|
ns.textContent=newCss.textContent;
|
|
1268
1400
|
ns.setAttribute('data-css',newCss.getAttribute('data-css'));
|
|
1269
|
-
|
|
1401
|
+
dhead().appendChild(ns);
|
|
1270
1402
|
}
|
|
1271
1403
|
|
|
1272
1404
|
// Remove old styles (except persistent ones, css, and incoming)
|
|
@@ -1293,7 +1425,7 @@ else {
|
|
|
1293
1425
|
try{docBase=new URL(url,location.href).href}catch(e){docBase=location.href}
|
|
1294
1426
|
var linkSel='link[rel="stylesheet"],link[rel="preconnect"]';
|
|
1295
1427
|
var curLinks={};
|
|
1296
|
-
|
|
1428
|
+
qsa('head '+linkSel).forEach(function(l){var h=l.getAttribute('href');if(h)curLinks[new URL(h,location.href).href]=1});
|
|
1297
1429
|
doc.querySelectorAll('head '+linkSel).forEach(function(l){
|
|
1298
1430
|
var href=l.getAttribute('href');
|
|
1299
1431
|
if(!href)return;
|
|
@@ -1303,9 +1435,9 @@ else {
|
|
|
1303
1435
|
var abs=new URL(href,docBase).href;
|
|
1304
1436
|
if(curLinks[abs])return;
|
|
1305
1437
|
curLinks[abs]=1;
|
|
1306
|
-
var nl=
|
|
1438
|
+
var nl=ce('link');
|
|
1307
1439
|
Array.from(l.attributes).forEach(function(a){nl.setAttribute(a.name,a.value)});
|
|
1308
|
-
|
|
1440
|
+
dhead().appendChild(nl);
|
|
1309
1441
|
});
|
|
1310
1442
|
// After every other stylesheet link, so the destination's complete
|
|
1311
1443
|
// utility sheet is last in the cascade.
|
|
@@ -1319,8 +1451,8 @@ else {
|
|
|
1319
1451
|
var declaredLayout=pendingLayoutDecl;
|
|
1320
1452
|
pendingLayoutDecl=null;
|
|
1321
1453
|
if(newBody){
|
|
1322
|
-
var newMeta=doc.querySelector(
|
|
1323
|
-
var curMeta=
|
|
1454
|
+
var newMeta=doc.querySelector(LAYOUT_META);
|
|
1455
|
+
var curMeta=qs(LAYOUT_META);
|
|
1324
1456
|
var curLayout=curMeta?curMeta.getAttribute('content'):'';
|
|
1325
1457
|
// The server's answer wins over the document's metas. We are only here
|
|
1326
1458
|
// because checkLayoutChange already said the group changed, based on
|
|
@@ -1388,16 +1520,16 @@ else {
|
|
|
1388
1520
|
// Copy body attributes (class, data-stx, etc.)
|
|
1389
1521
|
Array.from(newBody.attributes).forEach(function(attr){document.body.setAttribute(attr.name,attr.value)});
|
|
1390
1522
|
// Update layout meta tag
|
|
1391
|
-
var oldMeta=
|
|
1392
|
-
var freshMeta=doc.querySelector(
|
|
1523
|
+
var oldMeta=qs(LAYOUT_META);
|
|
1524
|
+
var freshMeta=doc.querySelector(LAYOUT_META);
|
|
1393
1525
|
if(oldMeta&&freshMeta)oldMeta.setAttribute('content',freshMeta.getAttribute('content')||'');
|
|
1394
|
-
else if(freshMeta){var m=
|
|
1395
|
-
var oldGroupMeta=
|
|
1396
|
-
var freshGroupMeta=doc.querySelector(
|
|
1526
|
+
else if(freshMeta){var m=ce('meta');m.name='stx-layout';m.content=freshMeta.getAttribute('content')||'';dhead().appendChild(m)}
|
|
1527
|
+
var oldGroupMeta=qs(LAYOUT_GROUP_META);
|
|
1528
|
+
var freshGroupMeta=doc.querySelector(LAYOUT_GROUP_META);
|
|
1397
1529
|
if(oldGroupMeta&&freshGroupMeta)oldGroupMeta.setAttribute('content',freshGroupMeta.getAttribute('content')||'');
|
|
1398
|
-
else if(freshGroupMeta){var gm=
|
|
1530
|
+
else if(freshGroupMeta){var gm=ce('meta');gm.name='stx-layout-group';gm.content=freshGroupMeta.getAttribute('content')||'';dhead().appendChild(gm)}
|
|
1399
1531
|
// Update container reference for script execution below
|
|
1400
|
-
currentContent=
|
|
1532
|
+
currentContent=qs(containerSel)||qs('main')||document.body;
|
|
1401
1533
|
} else {
|
|
1402
1534
|
// Same layout \u2014 swap only container content. Carry the destination
|
|
1403
1535
|
// container's attributes across too (same reason as the fragment path:
|
|
@@ -1416,7 +1548,7 @@ else {
|
|
|
1416
1548
|
|
|
1417
1549
|
// \u2500\u2500 Load new external <head> scripts \u2500\u2500
|
|
1418
1550
|
var loadedSrcs={};
|
|
1419
|
-
|
|
1551
|
+
qsa('head script[src]').forEach(function(s){loadedSrcs[s.src]=1});
|
|
1420
1552
|
var extPromises=[];
|
|
1421
1553
|
doc.querySelectorAll('head script[src]').forEach(function(s){
|
|
1422
1554
|
if(isSharedClientScript(s))return;
|
|
@@ -1426,11 +1558,11 @@ else {
|
|
|
1426
1558
|
if(loadedSrcs[src])return;
|
|
1427
1559
|
loadedSrcs[src]=1;
|
|
1428
1560
|
extPromises.push(new Promise(function(resolve,reject){
|
|
1429
|
-
var ns=
|
|
1561
|
+
var ns=ce('script');
|
|
1430
1562
|
ns.src=src;
|
|
1431
1563
|
ns.onload=resolve;
|
|
1432
1564
|
ns.onerror=reject;
|
|
1433
|
-
|
|
1565
|
+
dhead().appendChild(ns);
|
|
1434
1566
|
}));
|
|
1435
1567
|
});
|
|
1436
1568
|
|
|
@@ -1444,7 +1576,7 @@ else {
|
|
|
1444
1576
|
|
|
1445
1577
|
// \u2500\u2500 Script re-execution \u2500\u2500
|
|
1446
1578
|
// Remove previously injected page scripts
|
|
1447
|
-
|
|
1579
|
+
qsa('script[data-stx-page]').forEach(function(s){s.remove()});
|
|
1448
1580
|
|
|
1449
1581
|
var scripts=routedBodyScripts.slice();
|
|
1450
1582
|
function addScript(text, runAlways, slot, owner){
|
|
@@ -1528,8 +1660,7 @@ else {
|
|
|
1528
1660
|
refreshCurrentLinks();
|
|
1529
1661
|
|
|
1530
1662
|
// Scroll
|
|
1531
|
-
|
|
1532
|
-
else if(hash){var el=document.querySelector(hash);if(el)el.scrollIntoView({behavior:'smooth'})}
|
|
1663
|
+
applyScroll(hash);
|
|
1533
1664
|
|
|
1534
1665
|
// Update title
|
|
1535
1666
|
var newTitle=doc.querySelector('title');
|
|
@@ -1590,7 +1721,7 @@ else {
|
|
|
1590
1721
|
if(!runAlways&&!isSetup&&executedScriptHashes[h])return;
|
|
1591
1722
|
executedScriptHashes[h]=1;
|
|
1592
1723
|
// Wrap scripts with import statements as modules (Bug 3 fix)
|
|
1593
|
-
var ns=
|
|
1724
|
+
var ns=ce('script');
|
|
1594
1725
|
if(hasImport){
|
|
1595
1726
|
ns.type='module';
|
|
1596
1727
|
}
|
|
@@ -1603,7 +1734,7 @@ else {
|
|
|
1603
1734
|
var alreadyScoped=runsAlways(typeof entry==='string'?'':entry.run,text);
|
|
1604
1735
|
ns.textContent=(hasImport||alreadyScoped)?text:'{'+text+'}';
|
|
1605
1736
|
ns.setAttribute('data-stx-page','');
|
|
1606
|
-
var placeholder=entry.slot?
|
|
1737
|
+
var placeholder=entry.slot?qs('script[data-stx-route-script="'+entry.slot+'"]'):null;
|
|
1607
1738
|
if(placeholder&&placeholder.parentNode){
|
|
1608
1739
|
ns.setAttribute('data-stx-positioned','');
|
|
1609
1740
|
placeholder.parentNode.replaceChild(ns,placeholder);
|
|
@@ -1895,6 +2026,16 @@ else {
|
|
|
1895
2026
|
|
|
1896
2027
|
// \u2500\u2500 Back/forward \u2500\u2500
|
|
1897
2028
|
window.addEventListener('popstate',function(){
|
|
2029
|
+
// Nothing has scrolled yet (restoration is manual), so the viewport still
|
|
2030
|
+
// shows the entry being left: save it under its own token before adopting
|
|
2031
|
+
// the popped entry's, and hand the swap the position to land on.
|
|
2032
|
+
rememberScroll();
|
|
2033
|
+
if(restoreScroll){
|
|
2034
|
+
var popped=history.state&&history.state[SCROLL_TOKEN];
|
|
2035
|
+
scrollToken=popped||newScrollToken();
|
|
2036
|
+
if(!popped)stampScrollToken();
|
|
2037
|
+
pendingScroll=readScroll(scrollToken);
|
|
2038
|
+
}
|
|
1898
2039
|
navigate(location.pathname+location.search+location.hash,false);
|
|
1899
2040
|
});
|
|
1900
2041
|
|
|
@@ -1961,7 +2102,7 @@ else {
|
|
|
1961
2102
|
}
|
|
1962
2103
|
|
|
1963
2104
|
function updateNav(){
|
|
1964
|
-
|
|
2105
|
+
qsa('nav a[href], #mobileNav a[href], [data-stx-nav] a[href]').forEach(function(a){
|
|
1965
2106
|
if(!a.hasAttribute('data-stx-link'))return;
|
|
1966
2107
|
var st=linkState(a.getAttribute('href'),a.getAttribute('data-stx-active-match'));
|
|
1967
2108
|
if(!st)return;
|
|
@@ -1982,7 +2123,7 @@ else {
|
|
|
1982
2123
|
|
|
1983
2124
|
function updateActiveLinks(){
|
|
1984
2125
|
// Update active classes on <stx-link> elements (and legacy data-stx-link)
|
|
1985
|
-
var links=
|
|
2126
|
+
var links=qsa('[data-stx-link]');
|
|
1986
2127
|
links.forEach(function(link){
|
|
1987
2128
|
var st=linkState(link.getAttribute('to')||link.getAttribute('href')||'',link.getAttribute('data-stx-active-match'));
|
|
1988
2129
|
if(!st)return;
|
|
@@ -2024,7 +2165,7 @@ else {
|
|
|
2024
2165
|
// the cascade, or opt out entirely with viewTransitions:false.
|
|
2025
2166
|
function injectStyles(){
|
|
2026
2167
|
if(!document.getElementById('stx-r-css')){
|
|
2027
|
-
var s=
|
|
2168
|
+
var s=ce('style');s.id='stx-r-css';
|
|
2028
2169
|
// Strip characters that could escape our CSS block and inject new
|
|
2029
2170
|
// declarations: ; { } ( ) " ' \\ < > plus whitespace control chars.
|
|
2030
2171
|
// This keeps the value safe to concat into a stylesheet even if a
|
|
@@ -2046,10 +2187,10 @@ else {
|
|
|
2046
2187
|
css+='@media (prefers-reduced-motion: reduce){::view-transition-old(root),::view-transition-new(root){animation-duration:0s;animation-name:none}}';
|
|
2047
2188
|
}
|
|
2048
2189
|
s.textContent=css;
|
|
2049
|
-
|
|
2190
|
+
dhead().appendChild(s);
|
|
2050
2191
|
}
|
|
2051
2192
|
if(o.progress&&!document.getElementById('stx-router-progress')){
|
|
2052
|
-
var el=
|
|
2193
|
+
var el=ce('div');
|
|
2053
2194
|
el.id='stx-router-progress';
|
|
2054
2195
|
el.setAttribute('role','progressbar');
|
|
2055
2196
|
el.setAttribute('aria-hidden','true');
|
|
@@ -2066,7 +2207,7 @@ else {
|
|
|
2066
2207
|
|
|
2067
2208
|
function injectViewTransitionCSS(){
|
|
2068
2209
|
if(document.getElementById('stx-view-transitions'))return;
|
|
2069
|
-
var s=
|
|
2210
|
+
var s=ce('style');s.id='stx-view-transitions';
|
|
2070
2211
|
s.textContent='::view-transition-group(root){animation:none}::view-transition-old(root){animation:none}::view-transition-new(root){animation:none}main,#app-content,[data-stx-content]{view-transition-name:stx-content}::view-transition-old(stx-content){animation:stx-fade-out .15s ease-out both}::view-transition-new(stx-content){animation:stx-fade-in .15s ease-in .1s both}@keyframes stx-fade-out{from{opacity:1}to{opacity:0}}@keyframes stx-fade-in{from{opacity:0}to{opacity:1}}::view-transition{background:transparent}::view-transition-group(stx-content){background:inherit;overflow:hidden}';
|
|
2071
2212
|
(document.head||document.documentElement).appendChild(s);
|
|
2072
2213
|
}
|
|
@@ -2123,6 +2264,17 @@ else {
|
|
|
2123
2264
|
|
|
2124
2265
|
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init);else init();
|
|
2125
2266
|
})();
|
|
2126
|
-
|
|
2267
|
+
`;
|
|
2268
|
+
}
|
|
2269
|
+
export function getRouterScript() {
|
|
2270
|
+
if (cachedRouterScript)
|
|
2271
|
+
return cachedRouterScript;
|
|
2272
|
+
cachedRouterScript = minifyRouterScript(stripRouterLogs(routerSource()));
|
|
2127
2273
|
return cachedRouterScript;
|
|
2128
2274
|
}
|
|
2275
|
+
export function getRouterScriptDev() {
|
|
2276
|
+
if (cachedRouterScriptDev)
|
|
2277
|
+
return cachedRouterScriptDev;
|
|
2278
|
+
cachedRouterScriptDev = minifyRouterScript(routerSource());
|
|
2279
|
+
return cachedRouterScriptDev;
|
|
2280
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -52,4 +52,4 @@ export { generateRouteTypes, generateRouteManifest } from './codegen';
|
|
|
52
52
|
// Which files under a page root are public routes (#1866)
|
|
53
53
|
export { NON_PAGE_DIRS, isNonPageRoutePath, isNonPageRoutePattern } from './page-routes';
|
|
54
54
|
// Client-side SPA navigation
|
|
55
|
-
export { getRouterScript } from './client';
|
|
55
|
+
export { getRouterScript, getRouterScriptDev } from './client';
|
package/dist/index.js
CHANGED
|
@@ -22,4 +22,4 @@ export { resolveLayoutChain } from "./nested-layouts.js";
|
|
|
22
22
|
export { deriveLayoutGroup, extractLayoutMetadata } from "./layout-metadata.js";
|
|
23
23
|
export { generateRouteTypes, generateRouteManifest } from "./codegen.js";
|
|
24
24
|
export { NON_PAGE_DIRS, isNonPageRoutePath, isNonPageRoutePattern } from "./page-routes.js";
|
|
25
|
-
export { getRouterScript } from "./client.js";
|
|
25
|
+
export { getRouterScript, getRouterScriptDev } from "./client.js";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stx-router",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.339",
|
|
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",
|