@glitchr/transparent 1.0.66 → 1.0.71

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
@@ -1,2 +1 @@
1
1
  # TransparentJS
2
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glitchr/transparent",
3
- "version": "1.0.66",
3
+ "version": "1.0.71",
4
4
  "description": "Transparent SPA Application",
5
5
  "main": "src/index.js",
6
6
  "access": "public",
@@ -1,5 +1,3 @@
1
- import $ from 'jquery';
2
-
3
1
  // Modern browser: use passive event listeners where appropriate for better performance
4
2
  jQuery.event.special.touchstart = { setup: function( _, ns, handle ) { this.addEventListener("touchstart", handle, { passive: !ns.includes("noPreventDefault") }); } };
5
3
  jQuery.event.special.touchmove = { setup: function( _, ns, handle ) { this.addEventListener("touchmove", handle, { passive: !ns.includes("noPreventDefault") }); } };
@@ -14,7 +12,6 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
14
12
  } else if (typeof exports === 'object') {
15
13
  module.exports = factory();
16
14
  } else {
17
- root = window;
18
15
  root.Transparent = factory();
19
16
  }
20
17
 
@@ -184,14 +181,14 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
184
181
  "smoothscroll_easing" : "swing",
185
182
  "exceptions": [],
186
183
  // headlock: list of url substrings/regex to preserve in <head> across page transitions
187
- // (e.g. third-party widgets like Brevo that inject <style>/<link> dynamically).
184
+ // (e.g. third-party widgets that inject <style>/<link> dynamically).
188
185
  // In addition, head nodes injected dynamically AFTER initial DOMContentLoaded are
189
186
  // preserved automatically. Use data-headlock="false" on a head element to opt-out.
190
187
  "headlock": []
191
188
  };
192
189
 
193
190
  // Set of <head> children present on initial load. Anything added after is treated
194
- // as dynamically injected (e.g. Brevo widget) and preserved across transitions.
191
+ // as dynamically injected and preserved across transitions.
195
192
  var originalHeadNodes = new WeakSet();
196
193
  function snapshotHeadNodes() {
197
194
  var head = document.head;
@@ -199,10 +196,13 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
199
196
  for(var i = 0; i < head.children.length; i++)
200
197
  originalHeadNodes.add(head.children[i]);
201
198
  }
202
- if(document.readyState === "loading")
199
+ // Snapshot synchronously at module-eval time (scripts at end of <body> run before any
200
+ // async script can inject <style> tags, so the snapshot is clean).
201
+ // A DOMContentLoaded fallback is kept for the rare case where document.head is null
202
+ // (e.g. script loaded inside <head> before it finishes parsing).
203
+ snapshotHeadNodes();
204
+ if(!document.head)
203
205
  document.addEventListener("DOMContentLoaded", snapshotHeadNodes, { once: true });
204
- else
205
- snapshotHeadNodes();
206
206
 
207
207
  Transparent.isHeadlocked = function(el) {
208
208
  if(!el || el.nodeType !== 1) return false;
@@ -213,10 +213,12 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
213
213
  if(attr !== null && attr !== undefined) return true;
214
214
  // Dynamically injected after initial load
215
215
  if(!originalHeadNodes.has(el)) return true;
216
- // URL pattern match
216
+ // URL pattern match (src/href attributes)
217
217
  var patterns = Settings["headlock"] || [];
218
218
  if(!patterns.length) return false;
219
219
  var url = el.getAttribute && (el.getAttribute("src") || el.getAttribute("href"));
220
+ // <style> elements have no src/href — match against CSS textContent instead
221
+ if(!url && el.tagName === 'STYLE') url = el.textContent || '';
220
222
  if(!url) return false;
221
223
  for(var i = 0; i < patterns.length; i++) {
222
224
  var p = patterns[i];
@@ -224,7 +226,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
224
226
  else if(typeof p === "string" && p.length && url.indexOf(p) !== -1) return true;
225
227
  }
226
228
  return false;
227
- };
229
+ }
228
230
 
229
231
  const State = Transparent.state = {
230
232
 
@@ -244,9 +246,9 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
244
246
  CLICK : "click",
245
247
 
246
248
  PREACTIVE : "pre-active",
247
- ACTIVEIN : "active-in",
249
+ FADEIN : "fade-in",
248
250
  ACTIVE : "active",
249
- ACTIVEOUT : "active-out",
251
+ FADEOUT : "fade-out",
250
252
  POSTACTIVE : "post-active",
251
253
 
252
254
  NOTIFICATION: "notification"
@@ -323,6 +325,37 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
323
325
  }
324
326
 
325
327
  Transparent.setResponse = function(uuid, responseText, scrollableXY = [], exceptionRaised = false)
328
+ {
329
+ if(isDomEntity(responseText))
330
+ responseText = responseText.outerHTML;
331
+
332
+ var array = JSON.parse(sessionStorage.getItem('transparent')) || [];
333
+ array.push(uuid);
334
+
335
+ while(array.length > Settings["response_limit"])
336
+ sessionStorage.removeItem('transparent['+array.shift()+']');
337
+
338
+ try {
339
+
340
+ if(isLocalStorageNameSupported()) {
341
+
342
+ sessionStorage.setItem('transparent', JSON.stringify(array));
343
+ sessionStorage.setItem('transparent[response]['+uuid+']', responseText);
344
+ sessionStorage.setItem('transparent[position]['+uuid+']', JSON.stringify(scrollableXY));
345
+ }
346
+
347
+ } catch(e) {
348
+
349
+ if (e.name === 'QuotaExceededError')
350
+ sessionStorage.clear();
351
+
352
+ return exceptionRaised === false ? Transparent.setResponse(uuid, responseText, scrollableXY, true) : this;
353
+ }
354
+
355
+ return this;
356
+ }
357
+
358
+ Transparent.setResponse = function(uuid, responseText, scrollableXY, exceptionRaised = false)
326
359
  {
327
360
  if(isDomEntity(responseText))
328
361
  responseText = responseText.outerHTML;
@@ -462,7 +495,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
462
495
 
463
496
  if($(Transparent.html).hasClass(Transparent.state.FIRST)) {
464
497
  Transparent.scrollToHash(location.hash, {}, function() {
465
- Transparent.activeOut(() => Transparent.html.removeClass(Transparent.state.FIRST));
498
+ Transparent.fadeOut(() => Transparent.html.removeClass(Transparent.state.FIRST));
466
499
  });
467
500
  }
468
501
 
@@ -588,7 +621,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
588
621
  }
589
622
  }
590
623
 
591
- var closestEl = $(el).closest("a");
624
+ closestEl = $(el).closest("a");
592
625
  if(!closestEl.length) closestEl = $(el).closest("button");
593
626
  if(!closestEl.length) closestEl = $(el).closest("input");
594
627
  if (closestEl.length) el = closestEl[0];
@@ -803,9 +836,10 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
803
836
 
804
837
  return {delay:delay, duration:duration};
805
838
  }
806
- var activeInTime = 0;
807
- var activeInRemainingTime = 0;
808
- Transparent.activeIn = function(activeCallback = function() {}) {
839
+
840
+ var fadeInTime = 0;
841
+ var fadeInRemainingTime = 0;
842
+ Transparent.fadeIn = function(activeCallback = function() {}) {
809
843
 
810
844
  if(!Transparent.html.hasClass(Transparent.state.PREACTIVE)) {
811
845
  Transparent.html.addClass(Transparent.state.PREACTIVE);
@@ -813,18 +847,18 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
813
847
  }
814
848
 
815
849
  var active = Transparent.activeTime();
816
- activeInTime = Date.now();
817
- activeInRemainingTime = active.delay+active.duration;
850
+ fadeInTime = Date.now();
851
+ fadeInRemainingTime = active.delay+active.duration;
818
852
 
819
853
  Transparent.html.removeClass(Transparent.state.PREACTIVE);
820
- if(!Transparent.html.hasClass(Transparent.state.ACTIVEIN)) {
821
- Transparent.html.addClass(Transparent.state.ACTIVEIN);
822
- dispatchEvent(new Event('transparent:'+Transparent.state.ACTIVEIN));
854
+ if(!Transparent.html.hasClass(Transparent.state.FADEIN)) {
855
+ Transparent.html.addClass(Transparent.state.FADEIN);
856
+ dispatchEvent(new Event('transparent:'+Transparent.state.FADEIN));
823
857
  }
824
858
 
825
859
  Transparent.callback(function() {
826
860
 
827
- Transparent.html.removeClass(Transparent.state.ACTIVEIN);
861
+ Transparent.html.removeClass(Transparent.state.FADEIN);
828
862
  if(!Transparent.html.hasClass(Transparent.state.ACTIVE)) {
829
863
  Transparent.html.addClass(Transparent.state.ACTIVE);
830
864
  dispatchEvent(new Event('transparent:'+Transparent.state.ACTIVE));
@@ -834,23 +868,23 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
834
868
  Transparent.callback(function() {
835
869
 
836
870
  activeCallback();
837
- activeInRemainingTime = 0;
871
+ fadeInRemainingTime = 0;
838
872
 
839
873
  }.bind(this), active.duration);
840
874
 
841
875
  }.bind(this), active.delay);
842
876
  }
843
877
 
844
- Transparent.activeOut = function(activeCallback = function() {}) {
878
+ Transparent.fadeOut = function(activeCallback = function() {}) {
845
879
 
846
880
  if(!Transparent.html.hasClass(Transparent.state.ACTIVE)) {
847
881
  Transparent.html.addClass(Transparent.state.ACTIVE);
848
882
  dispatchEvent(new Event('transparent:'+Transparent.state.ACTIVE));
849
883
  }
850
884
 
851
- if(!Transparent.html.hasClass(Transparent.state.ACTIVEOUT)) {
852
- Transparent.html.addClass(Transparent.state.ACTIVEOUT);
853
- dispatchEvent(new Event('transparent:'+Transparent.state.ACTIVEOUT));
885
+ if(!Transparent.html.hasClass(Transparent.state.FADEOUT)) {
886
+ Transparent.html.addClass(Transparent.state.FADEOUT);
887
+ dispatchEvent(new Event('transparent:'+Transparent.state.FADEOUT));
854
888
  }
855
889
 
856
890
  var active = Transparent.activeTime();
@@ -862,7 +896,9 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
862
896
  var active = Transparent.activeTime();
863
897
  Transparent.callback(function() {
864
898
 
865
- Transparent.html.removeClass(Transparent.state.ACTIVEOUT);
899
+ Transparent.html.removeClass(Transparent.state.FADEOUT);
900
+ ajaxSemaphore = false;
901
+
866
902
  if(Transparent.html.hasClass(Transparent.state.LOADING)) {
867
903
 
868
904
  dispatchEvent(new Event('transparent:'+Transparent.state.LOADING));
@@ -870,7 +906,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
870
906
  Object.values(Transparent.state).forEach(e => Transparent.html.removeClass(e));
871
907
  Transparent.html.addClass(Transparent.state.ROOT + " " + Transparent.state.READY);
872
908
  }
873
-
909
+
874
910
  Transparent.html.addClass(Transparent.state.POSTACTIVE);
875
911
 
876
912
  var active = Transparent.activeTime();
@@ -903,7 +939,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
903
939
  } else {
904
940
 
905
941
  if(dom === undefined)
906
- console.error("Response missing..");
942
+ console.alert("Response missing..");
907
943
 
908
944
  var parent = Transparent.findElementFromParents(dom, $(this).parents(), 3);
909
945
  if (parent === undefined) {
@@ -953,7 +989,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
953
989
  var head = $(dom).find("head").html();
954
990
  var body = $(dom).find("body").html();
955
991
 
956
- if(head == undefined || body == undefined) {
992
+ if(head == undefined || body == "undefined") {
957
993
 
958
994
  $(Settings.identifier).html("<div class='error'></div>");
959
995
 
@@ -967,8 +1003,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
967
1003
  Transparent.evalScript($("body")[0]);
968
1004
  }
969
1005
 
970
- Transparent.scrollTo({top:0, left:0, duration:0});
971
- Transparent.activeOut();
1006
+ Transparent.fadeOut();
972
1007
  }
973
1008
 
974
1009
  Transparent.userScroll = function(el = undefined) { return $(el === undefined ? document.documentElement : el).closestScrollable().prop("user-scroll") ?? true; }
@@ -987,19 +1022,18 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
987
1022
  var maxScrollY = $(el).prop("scrollHeight") - Math.round($(el).prop("clientHeight"));
988
1023
  if (maxScrollY == 0) maxScrollY = Math.round($(el).prop("clientHeight"));
989
1024
 
990
- var scrollTop = Math.max(0, Math.min(dict["top"] ?? $(el).prop("scrollTop"), maxScrollY));
991
- var scrollLeft = Math.max(0, Math.min(dict["left"] ?? $(el).prop("scrollLeft"), maxScrollX));
1025
+ scrollTop = Math.max(0, Math.min(dict["top"] ?? $(el).prop("scrollTop"), maxScrollY));
1026
+ scrollLeft = Math.max(0, Math.min(dict["left"] ?? $(el).prop("scrollLeft"), maxScrollX));
992
1027
 
993
- var speed = parseFloat(dict["speed"] ?? 0);
994
- var easing = dict["easing"] ?? "swing";
995
- var debounce = dict["debounce"] ?? 0;
1028
+ speed = parseFloat(dict["speed"] ?? 0);
1029
+ easing = dict["easing"] ?? "swing";
1030
+ debounce = dict["debounce"] ?? 0;
996
1031
 
997
- var duration = 1000*Transparent.parseDuration(dict["duration"] ?? 0);
998
- var durationX = 1000*Transparent.parseDuration(dict["duration-x"] ?? dict["duration"] ?? 0);
999
- var durationY = 1000*Transparent.parseDuration(dict["duration-y"] ?? dict["duration"] ?? 0);
1032
+ duration = 1000*Transparent.parseDuration(dict["duration"] ?? 0);
1033
+ durationX = 1000*Transparent.parseDuration(dict["duration-x"] ?? dict["duration"] ?? 0);
1034
+ durationY = 1000*Transparent.parseDuration(dict["duration-y"] ?? dict["duration"] ?? 0);
1000
1035
 
1001
1036
  if(speed) {
1002
- var distanceX = 0, distanceY = 0;
1003
1037
 
1004
1038
  var currentScrollX = $(el)[0].scrollLeft;
1005
1039
  if(currentScrollX < scrollLeft || scrollLeft == 0) // Going to the right
@@ -1236,7 +1270,6 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1236
1270
  $(this).stop();
1237
1271
  });
1238
1272
 
1239
- activeInRemainingTime = activeInRemainingTime - (Date.now() - activeInTime);
1240
1273
  setTimeout(function() {
1241
1274
 
1242
1275
  // Transfert attributes
@@ -1244,6 +1277,15 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1244
1277
 
1245
1278
  // Replace head..
1246
1279
  var head = $(dom).find("head");
1280
+
1281
+ // Snapshot hrefs of already-loaded stylesheets so we can detect new ones
1282
+ // added by the head merge and wait for them to finish loading before
1283
+ // making #page visible (prevents FOUC on cold-cache layout transitions).
1284
+ var _existingStyleHrefs = {};
1285
+ $("head").children("link[rel='stylesheet']").each(function() {
1286
+ var h = this.getAttribute("href"); if(h) _existingStyleHrefs[h] = true;
1287
+ });
1288
+
1247
1289
  $("head").children().each(function() {
1248
1290
 
1249
1291
  var el = this;
@@ -1252,12 +1294,17 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1252
1294
  head.children().each(function() {
1253
1295
 
1254
1296
  found = this.isEqualNode(el);
1297
+ // Also match identical <style> tags by content
1298
+ if(!found && el.tagName === 'STYLE' && this.tagName === 'STYLE' &&
1299
+ el.textContent && this.textContent &&
1300
+ el.textContent.length > 100 && this.textContent.length === el.textContent.length) {
1301
+ found = this.textContent === el.textContent;
1302
+ }
1255
1303
  return !found;
1256
1304
  });
1257
1305
 
1258
1306
  // Preserve headlocked nodes (dynamically injected widgets, url-matched, etc.)
1259
1307
  if(!found && Transparent.isHeadlocked(el)) found = true;
1260
-
1261
1308
  if(!found) this.remove();
1262
1309
  });
1263
1310
 
@@ -1269,18 +1316,45 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1269
1316
  $("head").children().each(function() { found |= this.isEqualNode(el); });
1270
1317
  if(!found) {
1271
1318
 
1272
-
1273
- if(this.tagName != "SCRIPT" || Settings["global_code"] == true) {
1274
-
1275
- $("head").append(this.cloneNode(true));
1319
+ if(this.tagName == "SCRIPT" && Settings["global_code"] != true) {
1320
+
1321
+ // For inline scripts (without src), create and execute
1322
+ if(!this.src || this.src === '') {
1323
+ var script = document.createElement("script");
1324
+ script.text = this.innerHTML;
1325
+ var i = -1, attrs = this.attributes, attr;
1326
+ var N = attrs.length;
1327
+ while ( ++i < N ) {
1328
+ if(attrs[i].name !== 'src') {
1329
+ script.setAttribute( attrs[i].name, attrs[i].value );
1330
+ }
1331
+ }
1332
+ $("head").append(script);
1333
+ originalHeadNodes.add(script);
1334
+ } else {
1335
+ $("head").append(this);
1336
+ originalHeadNodes.add(this);
1337
+ }
1276
1338
 
1277
1339
  } else {
1278
1340
 
1279
- $("head").append(this);
1341
+ var clonedEl = this.cloneNode(true);
1342
+ $("head").append(clonedEl);
1343
+ // Register as an "original" node so it falls through to URL-pattern
1344
+ // matching on future transitions — prevents layout CSS added by
1345
+ // Transparent itself from being auto-headlocked as third-party content.
1346
+ originalHeadNodes.add(clonedEl);
1280
1347
  }
1281
1348
  }
1282
1349
  });
1283
1350
 
1351
+ // Collect link[rel="stylesheet"] elements inserted by the head merge above
1352
+ var _newStyleLinks = [];
1353
+ $("head").children("link[rel='stylesheet']").each(function() {
1354
+ var h = this.getAttribute("href");
1355
+ if(h && !_existingStyleHrefs[h]) _newStyleLinks.push(this);
1356
+ });
1357
+
1284
1358
  var bodyScript = $(dom).find("body > script");
1285
1359
  bodyScript.each(function() {
1286
1360
 
@@ -1290,10 +1364,27 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1290
1364
  $("body").children().each(function() { found |= this.isEqualNode(el); });
1291
1365
  if(!found) {
1292
1366
 
1293
- if(this.tagName != "SCRIPT" || Settings["global_code"] == true) {
1294
- $("body").append(this.cloneNode(true));
1367
+ if(this.tagName == "SCRIPT" && Settings["global_code"] != true) {
1368
+
1369
+ // For inline scripts (without src), create and execute
1370
+ if(!this.src || this.src === '') {
1371
+ var script = document.createElement("script");
1372
+ script.text = this.innerHTML;
1373
+ var i = -1, attrs = this.attributes, attr;
1374
+ var N = attrs.length;
1375
+ while ( ++i < N ) {
1376
+ if(attrs[i].name !== 'src') {
1377
+ script.setAttribute( attrs[i].name, attrs[i].value );
1378
+ }
1379
+ }
1380
+ $("body").append(script);
1381
+ } else {
1382
+ $("body").append(this);
1383
+ }
1384
+
1295
1385
  } else {
1296
- $("body").append(this);
1386
+
1387
+ $("body").append(this.cloneNode(true));
1297
1388
  }
1298
1389
  }
1299
1390
  });
@@ -1336,7 +1427,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1336
1427
  var scrollableElements = Transparent.getScrollableElement();
1337
1428
  var scrollableElementsXY = Transparent.getResponsePosition(uuid);
1338
1429
 
1339
- for(var i = 0; i < scrollableElements.length; i++) {
1430
+ for(i = 0; i < scrollableElements.length; i++) {
1340
1431
 
1341
1432
  var el = scrollableElements[i];
1342
1433
  var positionXY = undefined;
@@ -1358,20 +1449,43 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1358
1449
  }
1359
1450
  }
1360
1451
 
1361
- $('head').append(function() {
1362
-
1363
- $(Settings.identifier).append(function() {
1364
-
1365
- // Callback if needed, or any other actions
1366
- callback();
1367
-
1368
- // Trigger onload event
1369
- dispatchEvent(new Event('transparent:load'));
1370
- dispatchEvent(new Event('load'));
1371
- });
1372
- });
1452
+ // Wait for any newly added layout stylesheets to finish loading before
1453
+ // calling callback() / fadeOut() — otherwise #page becomes visible while
1454
+ // the new CSS is still being parsed, causing a flash of unstyled content.
1455
+ (function() {
1456
+ function doCallback() {
1457
+ $('head').append(function() {
1458
+ $(Settings.identifier).append(function() {
1459
+ callback();
1460
+ dispatchEvent(new Event('transparent:load'));
1461
+ dispatchEvent(new Event('load'));
1462
+ });
1463
+ });
1464
+ }
1465
+ if(_newStyleLinks.length === 0) {
1466
+ doCallback();
1467
+ } else {
1468
+ var remaining = _newStyleLinks.length;
1469
+ var fired = false;
1470
+ // Safety valve: if a stylesheet fails or stalls, don't block forever.
1471
+ var guard = setTimeout(function() {
1472
+ if(!fired) { fired = true; doCallback(); }
1473
+ }, 3000);
1474
+ _newStyleLinks.forEach(function(link) {
1475
+ function onDone() {
1476
+ if(--remaining <= 0 && !fired) {
1477
+ fired = true;
1478
+ clearTimeout(guard);
1479
+ doCallback();
1480
+ }
1481
+ }
1482
+ link.addEventListener('load', onDone, {once:true});
1483
+ link.addEventListener('error', onDone, {once:true});
1484
+ });
1485
+ }
1486
+ })();
1373
1487
 
1374
- }.bind(this), activeInRemainingTime > 0 ? activeInRemainingTime : 1);
1488
+ }.bind(this), 1);
1375
1489
  }
1376
1490
 
1377
1491
  function uuidv4() {
@@ -1474,7 +1588,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1474
1588
  var elementsXY = [];
1475
1589
  var elements = Transparent.getScrollableElement();
1476
1590
 
1477
- for(var i = 0; i < elements.length; i++)
1591
+ for(i = 0; i < elements.length; i++)
1478
1592
  elementsXY.push([$(elements[i]).scrollTop(), $(elements[i]).scrollLeft()]);
1479
1593
 
1480
1594
  return elementsXY;
@@ -1494,7 +1608,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1494
1608
  return;
1495
1609
  }
1496
1610
 
1497
- dispatchEvent(new CustomEvent('transparent:link', {detail: {link: link}}));
1611
+ dispatchEvent(new CustomEvent('transparent:link', {link:link}));
1498
1612
 
1499
1613
  const uuid = uuidv4();
1500
1614
  const type = link[0];
@@ -1506,10 +1620,8 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1506
1620
  // Wait for transparent window event to be triggered
1507
1621
  if (!isReady) return;
1508
1622
 
1509
- const $ctx = (e.type === Transparent.state.SUBMIT) ? $(document) : $(this);
1510
- if (e.type !== Transparent.state.POPSTATE &&
1511
- e.type !== Transparent.state.HASHCHANGE &&
1512
- !$ctx.find(Settings.identifier).length) return;
1623
+ if (e.type != Transparent.state.POPSTATE &&
1624
+ e.type != Transparent.state.HASHCHANGE && !$(this).find(Settings.identifier).length) return;
1513
1625
 
1514
1626
  var form = target != undefined && target.tagName == "FORM" ? target : undefined;
1515
1627
  var formTrigger = undefined;
@@ -1602,41 +1714,59 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1602
1714
  $(Transparent.html).stop();
1603
1715
 
1604
1716
  Transparent.html.addClass(Transparent.state.LOADING);
1605
- Transparent.activeIn();
1717
+
1718
+ var fadeInDone = false;
1719
+ var pendingResponseArgs = null;
1720
+
1721
+ function tryDispatch(args) {
1722
+ if (args !== undefined) pendingResponseArgs = args;
1723
+ if (fadeInDone && pendingResponseArgs !== null)
1724
+ handleResponse(...pendingResponseArgs);
1725
+ }
1726
+
1727
+ // Lock navigation for the full transition (fadeIn + page swap + fadeOut).
1728
+ // Released inside Transparent.fadeOut's final cleanup, covering both the
1729
+ // AJAX path and the popstate/cached-response path.
1730
+ ajaxSemaphore = true;
1731
+
1732
+ Transparent.fadeIn(function() {
1733
+ fadeInDone = true;
1734
+ tryDispatch();
1735
+ });
1606
1736
 
1607
1737
  function isJsonResponse(str) {
1608
1738
  try { JSON.parse(str); return true; }
1609
1739
  catch (e) { return false; }
1610
1740
  }
1611
1741
 
1612
- function handleResponse(uuid, status = 200, method = null, data = null, responseURL = null, contentType = null, fetchedResponseText = null) {
1742
+ function handleResponse(uuid, status = 200, method = null, data = null, xhr = null, request = null) {
1613
1743
 
1614
- ajaxSemaphore = false;
1615
1744
 
1616
- responseURL = responseURL ?? url.href;
1745
+ var responseURL;
1746
+ responseURL = xhr !== null ? xhr.responseURL : url.href;
1617
1747
 
1618
- var responseText = Transparent.getResponseText(uuid);
1748
+ responseText = Transparent.getResponseText(uuid);
1619
1749
 
1620
- const fragmentPosResp = responseURL.indexOf("#");
1621
- var strippedResponseUrl = (fragmentPosResp < 0 ? responseURL : responseURL.substring(0, fragmentPosResp)).trimEnd("/");
1750
+ var fragmentPos = responseURL.indexOf("#");
1751
+ var strippedResponseUrl = (fragmentPos < 0 ? responseURL : responseURL.substring(0, fragmentPos)).trimEnd("/");
1622
1752
 
1623
- const fragmentPosReq = url.href.indexOf("#");
1624
- var strippedUrlHref = (fragmentPosReq < 0 ? url.href : url.href.substring(0, fragmentPosReq)).trimEnd("/");
1753
+ var fragmentPos = url.href.indexOf("#");
1754
+ var strippedUrlHref = (fragmentPos < 0 ? url.href : url.href.substring(0, fragmentPos)).trimEnd("/");
1625
1755
  if( strippedUrlHref == strippedResponseUrl )
1626
- responseURL = url.href; // NB: fetch response.url strips away #fragments
1756
+ responseURL = url.href; // NB: xhr.responseURL strips away #fragments
1627
1757
 
1628
1758
  if(!responseText) {
1629
1759
 
1630
- if(!fetchedResponseText && responseText === null) {
1760
+ if(!request && responseText === null) {
1631
1761
 
1632
1762
  setTimeout(function() { window.location.href = responseURL; }, Settings["throttle"]);
1633
1763
  return;
1634
1764
  }
1635
1765
 
1636
- responseText = fetchedResponseText;
1766
+ responseText = request.responseText;
1637
1767
  if(status >= 500) {
1638
1768
 
1639
- console.error("Unexpected response from "+uuid+": error code "+status);
1769
+ console.error("Unexpected XHR response from "+uuid+": error code "+request.status);
1640
1770
  console.error(sessionStorage);
1641
1771
  }
1642
1772
 
@@ -1645,7 +1775,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1645
1775
  }
1646
1776
 
1647
1777
  var dom = new DOMParser().parseFromString(responseText, "text/html");
1648
- if(contentType && contentType.includes("application/json")) {
1778
+ if(request && request.getResponseHeader("Content-Type") == "application/json") {
1649
1779
 
1650
1780
  if(!isJsonResponse(responseText)) {
1651
1781
  console.error("Invalid response received for "+ responseURL);
@@ -1667,11 +1797,12 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1667
1797
  else location.reload();
1668
1798
  }
1669
1799
 
1800
+ ajaxSemaphore = false;
1670
1801
  return dispatchEvent(new Event('load'));
1671
1802
  }
1672
1803
 
1673
1804
  // Invalid html page returned
1674
- if(contentType && contentType.includes("text/html")) {
1805
+ if(request && request.getResponseHeader("Content-Type") == "text/html") {
1675
1806
 
1676
1807
  if (!responseText.includes("<html") && !responseText.includes("<body") && !responseText.includes("<head"))
1677
1808
  return Transparent.rescue(dom);
@@ -1690,7 +1821,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1690
1821
 
1691
1822
  // From here the page is valid..
1692
1823
  // so the new page is added to history..
1693
- if(fetchedResponseText !== null)
1824
+ if(xhr)
1694
1825
  history.pushState({uuid: uuid, status:status, method: method, data: {}, href: responseURL}, '', responseURL);
1695
1826
 
1696
1827
  // Page not recognized.. just go fetch by yourself.. no POST information transmitted..
@@ -1734,9 +1865,28 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1734
1865
  if($(dom).find("html").hasClass(Transparent.state.RELOAD) || $(dom).find("html").hasClass(Transparent.state.DISABLE))
1735
1866
  return window.location.reload();
1736
1867
 
1868
+ // Kick off preloads for stylesheets the new page needs but aren't yet in <head>.
1869
+ // They download in parallel during the fadeIn animation so onLoad() finds them
1870
+ // already cached — eliminating FOUC on cold-cache layout transitions.
1871
+ (function() {
1872
+ var loaded = {};
1873
+ $("head").children("link[rel='stylesheet']").each(function() {
1874
+ var h = this.getAttribute("href"); if(h) loaded[h] = true;
1875
+ });
1876
+
1877
+ $(dom).find("head").children("link[rel='stylesheet']").each(function() {
1878
+ var h = this.getAttribute("href");
1879
+ if(!h || loaded[h]) return;
1880
+ if($("head").find("link[rel='preload'][href='" + h.replace(/'/g, "\\'") + "']").length) return;
1881
+ var pl = document.createElement("link");
1882
+ pl.rel = "preload"; pl.as = "style"; pl.href = h;
1883
+ document.head.appendChild(pl);
1884
+ });
1885
+ })();
1886
+
1737
1887
  return Transparent.onLoad(uuid, dom, function() {
1738
1888
 
1739
- Transparent.activeOut(function() {
1889
+ Transparent.fadeOut(function() {
1740
1890
 
1741
1891
  Transparent.html
1742
1892
  .removeClass(switchLayout)
@@ -1759,27 +1909,26 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1759
1909
  if(history.state)
1760
1910
  Transparent.setResponse(history.state.uuid, Transparent.html[0], Transparent.getScrollableElementXY());
1761
1911
 
1762
- $(Transparent.html).prop("user-scroll", false); // make sure to avoid page jump during transition (cancelled in activeIn callback)
1912
+ $(Transparent.html).prop("user-scroll", false); // make sure to avoid page jump during transition (cancelled in fadeIn callback)
1763
1913
 
1764
1914
  // Submit ajax request..
1765
- ajaxSemaphore = true; // Raise before dispatching synthetic submit to prevent double-submission
1766
1915
  if(form) form.dispatchEvent(new SubmitEvent("submit", { submitter: formTrigger }));
1767
-
1768
- return fetch(url.href, {
1769
- method: type,
1770
- body: type === "GET" ? undefined : data,
1771
- headers: Settings["headers"] || {}
1772
- })
1773
- .then(async (response) => {
1774
- const responseText = await response.text();
1775
- return handleResponse(uuid, response.status, type, data, response.url, response.headers.get("Content-Type"), responseText);
1776
- })
1777
- .catch(() => {
1778
- handleResponse(uuid, 500, type, data, url.href, null, null);
1916
+ var xhr = new XMLHttpRequest();
1917
+
1918
+ return jQuery.ajax({
1919
+ url: url.href,
1920
+ type: type,
1921
+ data: data,
1922
+ contentType: false,
1923
+ processData: false,
1924
+ headers: Settings["headers"] || {},
1925
+ xhr: function () { return xhr; },
1926
+ success: function (html, status, request) { return tryDispatch([uuid, request.status, type, data, xhr, request]); },
1927
+ error: function (request, ajaxOptions, thrownError) { return tryDispatch([uuid, request.status, type, data, xhr, request]); }
1779
1928
  });
1780
1929
  }
1781
1930
 
1782
- return handleResponse(history.state.uuid, history.state.status, history.state.method, history.state.data);
1931
+ return tryDispatch([history.state.uuid, history.state.status, history.state.method, history.state.data]);
1783
1932
  }
1784
1933
 
1785
1934
  // Update history if not refreshing page or different page (avoid double pushState)
@@ -1795,8 +1944,8 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1795
1944
 
1796
1945
  if(Settings.debug) console.debug("Transparent is disabled..");
1797
1946
 
1798
- const statesSet = new Set(Object.values(Transparent.state));
1799
- const htmlClass = ($("html").attr("class") || "").split(" ").filter(x => !statesSet.has(x));
1947
+ var states = Object.values(Transparent.state);
1948
+ var htmlClass = Array.from(($("html").attr("class") || "").split(" ")).filter(x => !states.includes(x));
1800
1949
  Transparent.html.removeClass(states).addClass(htmlClass.join(" ")+" "+Transparent.state.ROOT+" "+Transparent.state.READY+" "+Transparent.state.DISABLE);
1801
1950
 
1802
1951
  } else {
@@ -1882,7 +2031,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1882
2031
  var fieldValueBefore = formDataBefore[fieldName];
1883
2032
  if(fieldValueBefore instanceof File) {
1884
2033
 
1885
- if(!(fieldValueAfter instanceof File)) preventDefault = true;
2034
+ if(!fieldValueAfter instanceof File) preventDefault = true;
1886
2035
  else if (fieldValueBefore.size != fieldValueAfter.size) preventDefault = true;
1887
2036
 
1888
2037
  } else if(fieldValueBefore != fieldValueAfter) {
@@ -1894,7 +2043,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1894
2043
  if(Settings.debug || preventDefault) {
1895
2044
 
1896
2045
  if(preventDefault) Transparent.html.addClass(Transparent.state.READY);
1897
- if(preventDefault) Transparent.activeOut();
2046
+ if(preventDefault) Transparent.fadeOut();
1898
2047
  if(preventDefault) dispatchEvent(new Event('load'));
1899
2048
 
1900
2049
  return "Dude, are you sure you want to leave? Think of the kittens!";
@@ -1903,7 +2052,7 @@ jQuery.event.special.mousewheel = { setup: function( _, ns, handle ) { this.addE
1903
2052
 
1904
2053
  document.addEventListener('click', __main__, false);
1905
2054
 
1906
- $(document).on("submit", "form", __main__);
2055
+ $("form").on("submit", __main__);
1907
2056
  }
1908
2057
 
1909
2058