stx-router 0.2.456 → 0.2.457
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.js +76 -18
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -253,10 +253,34 @@ function routerSource() {
|
|
|
253
253
|
// started the navigation, so programmatic and history navigations keep it.
|
|
254
254
|
var instantNext=false;
|
|
255
255
|
var instantNav=false;
|
|
256
|
+
// The one element a transition names stx-content: the routed container.
|
|
257
|
+
// The stylesheet used to name every main, #app-content and
|
|
258
|
+
// [data-stx-content], and a View Transition with two elements of one name
|
|
259
|
+
// aborts ("Unexpected duplicate view-transition-name: stx-content"). HQ's
|
|
260
|
+
// public profile has a <main> inside its layout's <main>, so every
|
|
261
|
+
// navigation away from it skipped its transition with that error, and a
|
|
262
|
+
// layout pairing a [data-stx-content] with a <main> did the same. So the
|
|
263
|
+
// name goes on the container alone, before the old page is captured and
|
|
264
|
+
// again once the swap has written the new one.
|
|
265
|
+
var VT_NAME='data-stx-vt-content';
|
|
266
|
+
function nameTransitionContainer(){
|
|
267
|
+
var named=getContainer();
|
|
268
|
+
qsa('['+VT_NAME+']').forEach(function(el){if(el!==named)el.removeAttribute(VT_NAME)});
|
|
269
|
+
if(named&&!named.hasAttribute(VT_NAME))named.setAttribute(VT_NAME,'');
|
|
270
|
+
}
|
|
271
|
+
function namedTransition(callback){
|
|
272
|
+
nameTransitionContainer();
|
|
273
|
+
return document.startViewTransition(function(){
|
|
274
|
+
var out=callback();
|
|
275
|
+
nameTransitionContainer();
|
|
276
|
+
if(out&&typeof out.then==='function')return out.then(function(v){nameTransitionContainer();return v});
|
|
277
|
+
return out;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
256
280
|
function runViewTransition(callback){
|
|
257
281
|
if(instantNav||!o.viewTransitions||!document.startViewTransition)return false;
|
|
258
282
|
try{
|
|
259
|
-
var transition=
|
|
283
|
+
var transition=namedTransition(callback);
|
|
260
284
|
var onAbort=function(err){log('[router] view transition aborted:',err&&err.message?err.message:err)};
|
|
261
285
|
if(transition&&transition.ready&&transition.ready.catch)transition.ready.catch(onAbort);
|
|
262
286
|
if(transition&&transition.finished&&transition.finished.catch)transition.finished.catch(onAbort);
|
|
@@ -326,7 +350,7 @@ function routerSource() {
|
|
|
326
350
|
root.classList.add('stx-instant');
|
|
327
351
|
var clear=function(){root.classList.remove('stx-instant')};
|
|
328
352
|
try{
|
|
329
|
-
var transition=
|
|
353
|
+
var transition=namedTransition(function(){
|
|
330
354
|
scriptsPending=0;
|
|
331
355
|
// The runtime binds the page on stx:load after a short debounce; run
|
|
332
356
|
// it now, so the frame this reveals is bound rather than showing its
|
|
@@ -1341,15 +1365,10 @@ else {
|
|
|
1341
1365
|
return el;
|
|
1342
1366
|
}
|
|
1343
1367
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
// The heading names the destination better than the title, which often
|
|
1349
|
-
// carries a site-name suffix. Falls back to the title, then gives up
|
|
1350
|
-
// rather than announcing an empty string.
|
|
1351
|
-
var label=(h&&(h.textContent||'').trim())||document.title||'';
|
|
1352
|
-
if(!label)return;
|
|
1368
|
+
// Text the runtime has not bound yet: an interpolation or an @{ } block as
|
|
1369
|
+
// the server sent it. Never worth saying aloud.
|
|
1370
|
+
function unrendered(text){return text.indexOf('{{')!==-1||text.indexOf('}}')!==-1||text.indexOf('@{')!==-1}
|
|
1371
|
+
function speakRoute(label){
|
|
1353
1372
|
var el=routeAnnouncer();
|
|
1354
1373
|
// Cleared first: several screen readers ignore a live-region update whose
|
|
1355
1374
|
// text is identical to what is already there, so navigating between two
|
|
@@ -1357,6 +1376,42 @@ else {
|
|
|
1357
1376
|
el.textContent='';
|
|
1358
1377
|
el.textContent=label;
|
|
1359
1378
|
}
|
|
1379
|
+
// The heading names the destination better than the title, which often
|
|
1380
|
+
// carries a site-name suffix. It is read once the page has bound: read at
|
|
1381
|
+
// the swap, a heading built from the page's state was still the server's
|
|
1382
|
+
// template, and screen readers said "{{ greeting }}, {{ firstName }}" while
|
|
1383
|
+
// the screen showed "Good morning, Chris". So a swap that binds its page
|
|
1384
|
+
// (awaitBind) waits for stx:load and the runtime's bind after it, and a
|
|
1385
|
+
// heading still holding template text is given until ANNOUNCE_WAIT to fill
|
|
1386
|
+
// in. Past that, or with no heading, the title; neither when it too is
|
|
1387
|
+
// unrendered, rather than announcing an expression or an empty string.
|
|
1388
|
+
var ANNOUNCE_WAIT=1500;
|
|
1389
|
+
var announceSeq=0;
|
|
1390
|
+
function announceRoute(awaitBind){
|
|
1391
|
+
if(o.announceRoute===false)return;
|
|
1392
|
+
var seq=++announceSeq,started=Date.now(),bound=!awaitBind,timer=null;
|
|
1393
|
+
function onLoad(){bound=true;schedule(20)}
|
|
1394
|
+
function stop(){if(timer)clearTimeout(timer);timer=null;window.removeEventListener('stx:load',onLoad)}
|
|
1395
|
+
function schedule(ms){if(timer)clearTimeout(timer);timer=setTimeout(attempt,ms)}
|
|
1396
|
+
function attempt(){
|
|
1397
|
+
timer=null;
|
|
1398
|
+
if(seq!==announceSeq){stop();return}
|
|
1399
|
+
var expired=Date.now()-started>=ANNOUNCE_WAIT;
|
|
1400
|
+
if(!bound&&!expired){schedule(50);return}
|
|
1401
|
+
var c=activeRoot();
|
|
1402
|
+
var h=c&&c.querySelector?c.querySelector('h1'):null;
|
|
1403
|
+
var heading=h?(h.textContent||'').trim():'';
|
|
1404
|
+
if(heading&&!unrendered(heading)){stop();speakRoute(heading);return}
|
|
1405
|
+
if(heading&&!expired){schedule(50);return}
|
|
1406
|
+
stop();
|
|
1407
|
+
var title=(document.title||'').trim();
|
|
1408
|
+
if(title&&!unrendered(title))speakRoute(title);
|
|
1409
|
+
}
|
|
1410
|
+
if(awaitBind)window.addEventListener('stx:load',onLoad);
|
|
1411
|
+
// Deferred a tick regardless: the title is applied by the caller after
|
|
1412
|
+
// the swap resolves, and the announcement should read the new one.
|
|
1413
|
+
schedule(0);
|
|
1414
|
+
}
|
|
1360
1415
|
|
|
1361
1416
|
function focusAfterNavigation(hash){
|
|
1362
1417
|
if(o.routeFocus===false)return;
|
|
@@ -1628,9 +1683,8 @@ else {
|
|
|
1628
1683
|
// Before page scripts run, so a page that focuses its own control on
|
|
1629
1684
|
// mount still wins \u2014 its script executes after this.
|
|
1630
1685
|
focusAfterNavigation(hash);
|
|
1631
|
-
//
|
|
1632
|
-
|
|
1633
|
-
setTimeout(announceRoute,0);
|
|
1686
|
+
// Read once the page's scripts have run and the runtime has bound it.
|
|
1687
|
+
announceRoute(true);
|
|
1634
1688
|
// Execute page scripts FIRST \u2014 they define setup functions and set _latestSetup
|
|
1635
1689
|
log('[router] frag scripts:', fragScripts.length);
|
|
1636
1690
|
qsa('script[data-stx-page]').forEach(function(s){s.remove()});
|
|
@@ -2136,6 +2190,8 @@ else {
|
|
|
2136
2190
|
}
|
|
2137
2191
|
|
|
2138
2192
|
window.dispatchEvent(new CustomEvent('stx:navigate',{detail:{url:url,direction:ctx.direction}}));
|
|
2193
|
+
// A layout change is a navigation too, and said nothing at all.
|
|
2194
|
+
announceRoute(true);
|
|
2139
2195
|
|
|
2140
2196
|
// Execute page scripts FIRST \u2014 they define setup functions and set _latestSetup
|
|
2141
2197
|
function execScripts(){
|
|
@@ -2532,7 +2588,8 @@ else {
|
|
|
2532
2588
|
refreshCurrentLinks();
|
|
2533
2589
|
window.dispatchEvent(new CustomEvent('stx:navigate',{detail:{url:to.url,direction:dir,retained:true,params:to.params||{}}}));
|
|
2534
2590
|
focusAfterNavigation('');
|
|
2535
|
-
|
|
2591
|
+
// A retained screen is bound already.
|
|
2592
|
+
announceRoute(false);
|
|
2536
2593
|
window.dispatchEvent(new CustomEvent('stx:screen-shown',{detail:{url:to.url,direction:dir}}));
|
|
2537
2594
|
revalidate(to.key,to.url);
|
|
2538
2595
|
var keep=dir==='push'||dir==='tab';
|
|
@@ -3561,13 +3618,14 @@ else {
|
|
|
3561
3618
|
// One View Transitions block. There used to be two, and the second
|
|
3562
3619
|
// (injected after this one) silently overrode the first: the page stays
|
|
3563
3620
|
// put and only the routed content fades, which is what this now says
|
|
3564
|
-
// once.
|
|
3565
|
-
//
|
|
3621
|
+
// once. Only the routed container carries the name, set by
|
|
3622
|
+
// nameTransitionContainer as a transition starts: never a retained
|
|
3623
|
+
// screen inside it, a nested <main>, or a second content element.
|
|
3566
3624
|
if(o.viewTransitions&&'startViewTransition' in document){
|
|
3567
3625
|
var dur=(o.viewTransitionDuration||220)+'ms';
|
|
3568
3626
|
var ease=o.viewTransitionEasing||'cubic-bezier(0.16, 1, 0.3, 1)';
|
|
3569
3627
|
css+='::view-transition-old(root),::view-transition-new(root){animation:none}::view-transition{background:transparent}';
|
|
3570
|
-
css+='
|
|
3628
|
+
css+='[data-stx-vt-content]{view-transition-name:stx-content}::view-transition-group(stx-content){overflow:hidden}';
|
|
3571
3629
|
css+='::view-transition-old(stx-content){animation:stx-r-fade-out '+dur+' '+ease+' both}::view-transition-new(stx-content){animation:stx-r-fade-in '+dur+' '+ease+' both}';
|
|
3572
3630
|
css+='@keyframes stx-r-fade-out{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-4px)}}';
|
|
3573
3631
|
css+='@keyframes stx-r-fade-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}';
|
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.457",
|
|
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",
|