rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1/* frame.js: the first script in every frame document the gallery
2 renders, and no part of the design system. Three jobs, all done
3 before the document's own scripts run.
4
5 The scheme. A frame does not inherit its parent's colour scheme,
6 and the gallery's reader may have chosen Light or Dark before this
7 frame loaded. The parent is same-origin, so its <html> is readable:
8 whatever data-theme it carries is copied here before the first
9 paint, and gallery.js writes it again on every change after.
10
11 Opened on its own, outside a frame, there is no parent: a shell
12 demo or the demo application in a tab of its own, or a document a
13 reader followed a link into. Every frame document says
14 data-rst-scheme-store="local", so fiesta.js would restore the stored
15 choice, but fiesta.js is deferred here and restores only when it is
16 in the head without defer; after the first paint is too late. So the
17 restore is this script's: localStorage["fiesta-scheme"] is read at
18 parse time and data-theme set for light or dark, and on pageshow
19 from the back/forward cache storage is read again and the attribute
20 set or removed. fiesta.js skips its own restore, the marking of the
21 scheme menu included, when the root already carries data-theme, so
22 once the body is parsed, and again after a cache restore, every
23 [rst-scheme] copy the document holds is marked here: aria-current on
24 the stored scheme's button, on System when nothing is stored, the
25 same marking fiesta.js does. Framed, the parent's scheme is the one
26 truth and nothing is read from storage.
27
28 Submits. Every href in a frame's body is "#", so a click goes
29 nowhere; a form would still request its action, "#" included, which
30 is the page again with its fields in the query. Cancelling every
31 submit at the window, in the capture phase, stops the request
32 before any other listener runs, and runs them all the same:
33 fiesta.js's scheme switch sees its submit in its own capture
34 listener on the document and applies the scheme as usual.
35
36 Autofocus. The page around a same-origin frame grants its autofocus:
37 a sign-in field would take the reader's focus as its lazy frame
38 loaded, and the gallery would scroll down to it. A browser grants
39 autofocus early in a rendering update, to the first field that asked
40 and can take focus, and runs animation frame callbacks after. So in
41 a frame each field that asks is inert from the parser's insertion (a
42 microtask, before any update) to the first animation frame after
43 load, and its request is refused. Removing the attribute does not
44 work (the request is made on insertion), nor does hiding or inerting
45 the frame (Chromium focuses inside it anyway; measured, in the
46 gallery this came from). Opened in a tab of its own, the document
47 keeps its autofocus. */
48(function () {
49 "use strict";
50
51 var root = document.documentElement, KEY = "fiesta-scheme", SCHEME = "[rst-scheme],.rst-scheme";
52
53 var host = null;
54 try {
55 host = window.frameElement;
56 } catch (e) {
57 /* framed by another origin, whose page is not the gallery's */
58 }
59
60 function stored() {
61 var s = null;
62 try { s = localStorage.getItem(KEY); } catch (e) { /* storage off: nothing is stored */ }
63 return s === "light" || s === "dark" ? s : "system";
64 }
65 function apply(s) {
66 if (s === "system") root.removeAttribute("data-theme"); else root.setAttribute("data-theme", s);
67 }
68 // Not toggleAttribute: aria-current="" reads as false.
69 function mark(s) {
70 var menus = document.querySelectorAll(SCHEME);
71 for (var m = 0; m < menus.length; m++) {
72 var buttons = menus[m].querySelectorAll("button");
73 for (var i = 0; i < buttons.length; i++) {
74 var f = buttons[i].form, v = f && f.elements.scheme && f.elements.scheme.value;
75 if (v === s) buttons[i].setAttribute("aria-current", "true"); else buttons[i].removeAttribute("aria-current");
76 }
77 }
78 }
79 function ready(fn) {
80 if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", fn); else fn();
81 }
82
83 if (host) {
84 var parent = host.ownerDocument.documentElement, s = parent.getAttribute("data-theme");
85 if (s === "light" || s === "dark") root.setAttribute("data-theme", s);
86 else root.removeAttribute("data-theme");
87 } else if (!root.hasAttribute("data-theme")) {
88 var kept = stored();
89 if (kept !== "system") apply(kept);
90 ready(function () { mark(kept); });
91 addEventListener("pageshow", function (event) {
92 if (!event.persisted) return;
93 var now = stored();
94 apply(now);
95 mark(now);
96 });
97 }
98
99 addEventListener("submit", function (event) {
100 event.preventDefault();
101 }, true);
102
103 if (host && host.classList.contains("ds-view__frame")) {
104 var asked = [];
105 var hold = function (el) {
106 if (el.hasAttribute("autofocus") && !el.inert) {
107 el.inert = true;
108 asked.push(el);
109 }
110 };
111 var inserted = new MutationObserver(function (records) {
112 for (var r = 0; r < records.length; r++) {
113 var added = records[r].addedNodes;
114 for (var n = 0; n < added.length; n++) {
115 if (added[n].nodeType !== 1) continue;
116 hold(added[n]);
117 var inner = added[n].querySelectorAll("[autofocus]");
118 for (var i = 0; i < inner.length; i++) hold(inner[i]);
119 }
120 }
121 });
122 inserted.observe(document.documentElement, { childList: true, subtree: true });
123 addEventListener("load", function () {
124 inserted.disconnect();
125 requestAnimationFrame(function () {
126 for (var i = 0; i < asked.length; i++) asked[i].inert = false;
127 });
128 });
129 }
130})();
131