| 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 | |