@abide/abide 0.111.0 → 0.113.0

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.
@@ -1,214 +0,0 @@
1
- // 39.6 — ON A CLIENT NAVIGATION THE INCOMING PAGE'S KEYED CONTRIBUTIONS REPLACE THE OUTGOING
2
- // PAGE'S BY KEY. The outgoing page's were written by a server on the first load and by this
3
- // function on every one after, so they are found the same way either time: by their key, read off
4
- // the document. Nothing is added to the head to mark ownership — a marker would be a fact about
5
- // the framework in a document an author writes, and the key is already what decides.
6
- //
7
- // 39.5 — A CONTRIBUTION WITH NO KEY APPENDS. It cannot replace anything ANOTHER contributor wrote,
8
- // because there is nothing it could be said to be the same as, and dropping it instead would lose
9
- // the one kind of contribution a page makes several of. What its OWN contributor wrote last is
10
- // another matter: a head written again puts its keyless nodes where the last ones stood (39.15),
11
- // and a head withdrawn takes them with it (39.16). Found the way a key is — off the document, by
12
- // the markup the contributor last wrote — so a head the server painted is found the same as one
13
- // written here.
14
-
15
- import { headKeyOf } from '#shared/headKeyOf.ts'
16
- import { markupNodes } from './parseMarkup.ts'
17
-
18
- /**
19
- * Writes `markup`'s contributions into the document head, a key `below` holds excepted.
20
- *
21
- * 39.4 — `below` is the head of every contributor deeper than the one writing, which a head
22
- * re-applied live (39.13) must not undo: the deepest contributor wins whichever of them moved last.
23
- *
24
- * 39.15 — `last` is what the same contributor wrote before, whose keyless nodes this replaces in
25
- * place, in order, rather than adding beside them. 39.14 — a key `last` held that `markup` does not
26
- * is withdrawn to `outer`'s value for it, as a leaving contributor's is; a key list that did not
27
- * move withdraws nothing and never asks `outer`.
28
- */
29
- export function applyHead(
30
- markup: string,
31
- below = '',
32
- last = '',
33
- outer: () => string = () => '',
34
- ): void {
35
- const head = document.head
36
- if (markup === '') return
37
- const incoming = markupNodes(head, markup)
38
- const standingByKey = standingKeys(head)
39
- const deeper = below === '' ? undefined : keyedIn(head, below)
40
- const replaced: Node[] = []
41
- const withdrawn = keyedIn(head, last, replaced)
42
- let next = 0
43
- for (const node of incoming) {
44
- const key = node.nodeType === 1 ? headKey(node as Element) : undefined
45
- if (key === undefined) {
46
- const standing = replaced[next]
47
- next += 1
48
- if (standing === undefined) head.appendChild(runnable(node))
49
- // The same node left standing, as a key's is: a `<style>` put back as itself is not
50
- // re-parsed into the cascade, and a `<script>` is not run twice.
51
- else if (!standing.isEqualNode(node))
52
- head.replaceChild(runnable(node), standing)
53
- continue
54
- }
55
- const element = node as Element
56
- withdrawn.delete(key)
57
- if (deeper?.has(key) === true) continue
58
- const standing = standingByKey.get(key)
59
- if (standing === undefined) {
60
- head.appendChild(element)
61
- standingByKey.set(key, element)
62
- continue
63
- }
64
- // WHAT THE TWO PAGES SHARE IS NOT TOUCHED. `hydrate` skips `applyHead` outright because
65
- // re-applying a `<link rel=stylesheet>` refetches it and flashes the document unstyled —
66
- // and a navigation between two pages carrying the same stylesheet, viewport or icon walks
67
- // into that same cost per contribution. An identical node is the one case where the
68
- // cheaper arm is to do nothing at all.
69
- if (standing.outerHTML === element.outerHTML) continue
70
- head.replaceChild(element, standing)
71
- standingByKey.set(key, element)
72
- }
73
- for (; next < replaced.length; next += 1)
74
- (replaced[next] as Node).parentNode?.removeChild(replaced[next] as Node)
75
- if (withdrawn.size === 0) return
76
- if (deeper !== undefined)
77
- for (const key of deeper.keys()) withdrawn.delete(key)
78
- restoreKeys(head, withdrawn, outer())
79
- }
80
-
81
- /**
82
- * `node`, or — for a `<script>` — the same script made by the document rather than the parser.
83
- *
84
- * 39.17 — A PARSED SCRIPT NEVER RUNS. The fragment parser marks every `<script>` it makes "already
85
- * started", and `parseMarkup` is that parser, so a script it made and then moved into the head is
86
- * inert where the server's, made by the document's own parser, ran. One made by `createElement`
87
- * starts clear and runs on insertion. Keyless always (39.11 keys no `<script>`), so this is reached
88
- * only where a keyless contribution is actually written.
89
- */
90
- function runnable(node: Node): Node {
91
- if (node.nodeName !== 'SCRIPT') return node
92
- const parsed = node as HTMLScriptElement
93
- const script = document.createElement('script')
94
- const attributes = parsed.attributes
95
- for (let index = 0; index < attributes.length; index += 1) {
96
- const attribute = attributes[index] as Attr
97
- script.setAttribute(attribute.name, attribute.value)
98
- }
99
- script.text = parsed.text
100
- return script
101
- }
102
-
103
- /**
104
- * Puts back, or removes, what the leaving levels keyed and the arriving ones do not.
105
- *
106
- * 39.14 — THE NEXT-OUTER CONTRIBUTOR'S VALUE, OR NONE. `leaving` is the head of the levels a
107
- * navigation stopped; `around` answers `outer`, every contributor standing above them, and
108
- * `arriving`, the heads of the levels it mounted. `outer` runs outermost first — the shell's keyed
109
- * entries, then each kept layout's — so the LAST entry for a key in it is the one the server would
110
- * paint there (39.4). A component leaving its region is withdrawn the same way, its level's
111
- * contributors either side of it standing in for the two (`Level.headsAround`). What the arriving levels key they have
112
- * already written (39.6), so only the keys they are silent on are touched — and 39.16, the leaving
113
- * levels' keyless nodes leave with them.
114
- *
115
- * THE COMMON NAVIGATION WITHDRAWS NOTHING BY KEY: two pages that both key a `<title>` leave
116
- * `outer` built but never parsed, and a head keying nothing never asks `around`.
117
- */
118
- export function withdrawHead(
119
- leaving: string,
120
- around: () => [outer: string, arriving: string],
121
- ): void {
122
- if (leaving === '') return
123
- const head = document.head
124
- const keyless: Node[] = []
125
- const withdrawn = keyedIn(head, leaving, keyless)
126
- for (const node of keyless) node.parentNode?.removeChild(node)
127
- if (withdrawn.size === 0) return
128
- const [outer, arriving] = around()
129
- for (const key of keyedIn(head, arriving).keys()) withdrawn.delete(key)
130
- restoreKeys(head, withdrawn, outer)
131
- }
132
-
133
- /** 39.14 — each of `withdrawn`'s keys back to `outer`'s last value for it, or removed. */
134
- function restoreKeys(
135
- head: HTMLHeadElement,
136
- withdrawn: Map<string, Element>,
137
- outer: string,
138
- ): void {
139
- if (withdrawn.size === 0) return
140
- const restored = keyedIn(head, outer)
141
- const standingByKey = standingKeys(head)
142
- for (const key of withdrawn.keys()) {
143
- const standing = standingByKey.get(key)
144
- const next = restored.get(key)
145
- if (next === undefined) standing?.remove()
146
- else if (standing === undefined) head.appendChild(next)
147
- // The same node as `applyHead`'s: a stylesheet put back as itself is not refetched.
148
- else if (standing.outerHTML !== next.outerHTML)
149
- head.replaceChild(next, standing)
150
- }
151
- }
152
-
153
- /**
154
- * The keyed elements `markup` parses to, by key, the last holding each (39.4).
155
- *
156
- * Where `standing` is given, each keyless node is matched to the first node standing in the head
157
- * equal to it and not already matched, and the match pushed there (39.15, 39.16).
158
- */
159
- function keyedIn(
160
- head: HTMLHeadElement,
161
- markup: string,
162
- standing?: Node[],
163
- ): Map<string, Element> {
164
- const keyed = new Map<string, Element>()
165
- if (markup === '') return keyed
166
- for (const node of markupNodes(head, markup)) {
167
- const key = node.nodeType === 1 ? headKey(node as Element) : undefined
168
- if (key !== undefined) keyed.set(key, node as Element)
169
- else if (standing !== undefined)
170
- for (let at = head.firstChild; at !== null; at = at.nextSibling)
171
- if (at.isEqualNode(node) && !standing.includes(at)) {
172
- standing.push(at)
173
- break
174
- }
175
- }
176
- return keyed
177
- }
178
-
179
- /**
180
- * The head's keyed elements by key, the first holding each.
181
- *
182
- * KEYED ONCE, NOT ONCE PER CONTRIBUTION. Walking the head per incoming node asked `headKey` — a
183
- * `toLowerCase` and a fresh closure per call — for every standing element times every arriving
184
- * one. One pass builds the index and each contribution is then a lookup.
185
- */
186
- function standingKeys(head: HTMLHeadElement): Map<string, Element> {
187
- const standingByKey = new Map<string, Element>()
188
- for (
189
- let node = head.firstElementChild;
190
- node !== null;
191
- node = node.nextElementSibling
192
- ) {
193
- const key = headKey(node)
194
- // 39.12 WITHIN THE STANDING HEAD: the FIRST element holding a key is the one a
195
- // contribution replaces, which is the node the per-contribution walk returned.
196
- if (key !== undefined && !standingByKey.has(key))
197
- standingByKey.set(key, node)
198
- }
199
- return standingByKey
200
- }
201
-
202
- /**
203
- * The key a head element replaces by, or `undefined` for one that appends.
204
- *
205
- * 39.11 THROUGH THE ONE GRAMMAR (`headKeyOf`), asked with a RESOLVED attribute. The compiler
206
- * answers the same question for a STATIC one and appends where it cannot (`<meta name={x}>` has a
207
- * name only the run time knows) — here every attribute is resolved, so a contribution the compiler
208
- * had to append arrives with a key. That is the same rule reaching further, not a different one.
209
- */
210
- function headKey(element: Element): string | undefined {
211
- return headKeyOf(element.tagName.toLowerCase(), (name) =>
212
- element.getAttribute(name),
213
- )
214
- }