tangled
alpha
login
or
join now
yippee.fun
/
morphlex
0
fork
atom
Precise DOM morphing
morphing
typescript
dom
0
fork
atom
overview
issues
pulls
pipelines
Refactor
joel.drapper.me
4 months ago
3db719d3
1aed15eb
+89
-160
1 changed file
expand all
collapse all
unified
split
src
morphlex.ts
+89
-160
src/morphlex.ts
···
1
1
-
// Type declaration for moveBefore API (not yet in TypeScript DOM types)
2
2
-
declare global {
3
3
-
interface Element {
4
4
-
moveBefore?(node: Node, child: Node | null): Node
5
5
-
}
6
6
-
}
7
7
-
8
1
type IdSet = Set<string>
9
9
-
type IdMap = WeakMap<ReadonlyNode<Node>, IdSet>
10
10
-
11
11
-
// Maps to a type that can only read properties
12
12
-
type StrongReadonly<T> = { readonly [K in keyof T as T[K] extends Function ? never : K]: T[K] }
2
2
+
type IdMap = WeakMap<Node, IdSet>
13
3
14
4
declare const brand: unique symbol
15
5
type Branded<T, B extends string> = T & { [brand]: B }
16
6
17
17
-
type NodeReferencePair<N extends Node> = Readonly<[N, ReadonlyNode<N>]>
18
18
-
type MatchingElementReferencePair<E extends Element> = Branded<NodeReferencePair<E>, "MatchingElementPair">
19
19
-
20
20
-
// Maps a Node to a type limited to read-only properties and methods for that Node
21
21
-
type ReadonlyNode<N extends Node> =
22
22
-
| N
23
23
-
| (StrongReadonly<N> & {
24
24
-
readonly cloneNode: (deep: true) => Node
25
25
-
readonly childNodes: ReadonlyNodeList<ChildNode>
26
26
-
readonly querySelectorAll: (query: string) => ReadonlyNodeList<Element>
27
27
-
readonly parentElement: ReadonlyNode<Element> | null
28
28
-
readonly hasAttribute: (name: string) => boolean
29
29
-
readonly hasAttributes: () => boolean
30
30
-
readonly hasChildNodes: () => boolean
31
31
-
readonly children: ReadonlyNodeList<Element>
32
32
-
})
33
33
-
34
34
-
// Maps a node to a read-only node list of nodes of that type
35
35
-
type ReadonlyNodeList<N extends Node> =
36
36
-
| NodeListOf<N>
37
37
-
| {
38
38
-
[Symbol.iterator](): IterableIterator<ReadonlyNode<N>>
39
39
-
readonly [index: number]: ReadonlyNode<N>
40
40
-
readonly length: NodeListOf<N>["length"]
41
41
-
}
7
7
+
type PairOfNodes<N extends Node> = [N, N]
8
8
+
type PairOfMatchingElements<E extends Element> = Branded<PairOfNodes<E>, "MatchingElementPair">
42
9
43
10
interface Options {
44
11
ignoreActiveValue?: boolean
···
73
40
}
74
41
75
42
function parseChildNodeFromString(string: string): ChildNode {
76
76
-
const parser = new DOMParser()
77
77
-
const doc = parser.parseFromString(string, "text/html")
43
43
+
const template = document.createElement("template")
44
44
+
template.innerHTML = string
78
45
79
79
-
const firstChild = doc.body.firstChild
46
46
+
const firstChild = template.content.firstChild
80
47
if (firstChild) return firstChild
81
48
else throw new Error("[Morphlex] The string was not a valid HTML node.")
82
49
}
83
50
84
51
// Feature detection for moveBefore support (cached for performance)
85
85
-
const supportsMoveBefore = typeof Element !== "undefined" && typeof Element.prototype.moveBefore === "function"
86
52
87
53
class Morph {
88
88
-
readonly #idMap: IdMap
89
89
-
90
90
-
readonly #ignoreActiveValue: boolean
91
91
-
readonly #preserveModifiedValues: boolean
92
92
-
readonly #beforeNodeMorphed?: (node: Node, referenceNode: Node) => boolean
93
93
-
readonly #afterNodeMorphed?: (node: Node, referenceNode: Node) => void
94
94
-
readonly #beforeNodeAdded?: (node: Node) => boolean
95
95
-
readonly #afterNodeAdded?: (node: Node) => void
96
96
-
readonly #beforeNodeRemoved?: (node: Node) => boolean
97
97
-
readonly #afterNodeRemoved?: (node: Node) => void
98
98
-
readonly #beforeAttributeUpdated?: (element: Element, attributeName: string, newValue: string | null) => boolean
99
99
-
readonly #afterAttributeUpdated?: (element: Element, attributeName: string, previousValue: string | null) => void
100
100
-
readonly #beforePropertyUpdated?: (node: Node, propertyName: PropertyKey, newValue: unknown) => boolean
101
101
-
readonly #afterPropertyUpdated?: (node: Node, propertyName: PropertyKey, previousValue: unknown) => void
54
54
+
readonly idMap: IdMap
55
55
+
readonly options: Options
102
56
103
57
constructor(options: Options = {}) {
104
104
-
this.#idMap = new WeakMap()
105
105
-
106
106
-
this.#ignoreActiveValue = options.ignoreActiveValue || false
107
107
-
this.#preserveModifiedValues = options.preserveModifiedValues || false
108
108
-
this.#beforeNodeMorphed = options.beforeNodeMorphed
109
109
-
this.#afterNodeMorphed = options.afterNodeMorphed
110
110
-
this.#beforeNodeAdded = options.beforeNodeAdded
111
111
-
this.#afterNodeAdded = options.afterNodeAdded
112
112
-
this.#beforeNodeRemoved = options.beforeNodeRemoved
113
113
-
this.#afterNodeRemoved = options.afterNodeRemoved
114
114
-
this.#beforeAttributeUpdated = options.beforeAttributeUpdated
115
115
-
this.#afterAttributeUpdated = options.afterAttributeUpdated
116
116
-
this.#beforePropertyUpdated = options.beforePropertyUpdated
117
117
-
this.#afterPropertyUpdated = options.afterPropertyUpdated
58
58
+
this.idMap = new WeakMap()
59
59
+
this.options = options
118
60
}
119
61
120
120
-
morph(pair: NodeReferencePair<ChildNode>): void {
62
62
+
morph(pair: PairOfNodes<ChildNode>): void {
121
63
this.#withAriaBusy(pair[0], () => {
122
64
if (isParentNodePair(pair)) this.#buildMaps(pair)
123
65
this.#morphNode(pair)
124
66
})
125
67
}
126
68
127
127
-
morphInner(pair: NodeReferencePair<Element>): void {
69
69
+
morphInner(pair: PairOfNodes<Element>): void {
128
70
this.#withAriaBusy(pair[0], () => {
129
71
if (isMatchingElementPair(pair)) {
130
72
this.#buildMaps(pair)
···
144
86
} else block()
145
87
}
146
88
147
147
-
#buildMaps([node, reference]: NodeReferencePair<ParentNode>): void {
89
89
+
#buildMaps([node, reference]: PairOfNodes<ParentNode>): void {
148
90
this.#mapIdSets(node)
149
91
this.#mapIdSets(reference)
150
92
}
151
93
152
94
// For each node with an ID, push that ID into the IdSet on the IdMap, for each of its parent elements.
153
153
-
#mapIdSets(node: ReadonlyNode<ParentNode>): void {
95
95
+
#mapIdSets(node: ParentNode): void {
154
96
const elementsWithIds = node.querySelectorAll("[id]")
155
97
156
98
const elementsWithIdsLength = elementsWithIds.length
···
161
103
// Ignore empty IDs
162
104
if (id === "") continue
163
105
164
164
-
let current: ReadonlyNode<Element> | null = elementWithId
106
106
+
let current: Element | null = elementWithId
165
107
166
108
while (current) {
167
167
-
const idSet: IdSet | undefined = this.#idMap.get(current)
109
109
+
const idSet: IdSet | undefined = this.idMap.get(current)
168
110
if (idSet) idSet.add(id)
169
169
-
else this.#idMap.set(current, new Set([id]))
111
111
+
else this.idMap.set(current, new Set([id]))
170
112
if (current === node) break
171
113
current = current.parentElement
172
114
}
···
174
116
}
175
117
176
118
// This is where we actually morph the nodes. The `morph` function (above) exists only to set up the `idMap`.
177
177
-
#morphNode(pair: NodeReferencePair<ChildNode>): void {
119
119
+
#morphNode(pair: PairOfNodes<ChildNode>): void {
178
120
const [node, reference] = pair
179
121
180
180
-
if (node.nodeType === 3 && reference.nodeType === 3) {
122
122
+
if (isTextNode(node) && isTextNode(reference)) {
181
123
if (node.textContent === reference.textContent) return
182
124
}
183
125
···
185
127
else this.#morphOtherNode(pair)
186
128
}
187
129
188
188
-
#morphMatchingElementNode(pair: MatchingElementReferencePair<Element>): void {
130
130
+
#morphMatchingElementNode(pair: PairOfMatchingElements<Element>): void {
189
131
const [node, reference] = pair
190
132
191
191
-
if (!(this.#beforeNodeMorphed?.(node, writableNode(reference)) ?? true)) return
133
133
+
if (!(this.options.beforeNodeMorphed?.(node, reference) ?? true)) return
192
134
193
135
if (node.hasAttributes() || reference.hasAttributes()) this.#morphAttributes(pair)
194
136
195
137
// TODO: Should use a branded pair here.
196
138
this.#morphMatchingElementContent(pair)
197
139
198
198
-
this.#afterNodeMorphed?.(node, writableNode(reference))
140
140
+
this.options.afterNodeMorphed?.(node, reference)
199
141
}
200
142
201
201
-
#morphOtherNode([node, reference]: NodeReferencePair<ChildNode>): void {
202
202
-
if (!(this.#beforeNodeMorphed?.(node, writableNode(reference)) ?? true)) return
143
143
+
#morphOtherNode([node, reference]: PairOfNodes<ChildNode>): void {
144
144
+
if (!(this.options.beforeNodeMorphed?.(node, reference) ?? true)) return
203
145
204
146
if (node.nodeType === reference.nodeType && node.nodeValue !== null && reference.nodeValue !== null) {
205
147
// Handle text nodes, comments, and CDATA sections.
206
148
this.#updateProperty(node, "nodeValue", reference.nodeValue)
207
207
-
} else this.#replaceNode(node, reference.cloneNode(true))
149
149
+
} else this.replaceNode(node, reference.cloneNode(true))
208
150
209
209
-
this.#afterNodeMorphed?.(node, writableNode(reference))
151
151
+
this.options.afterNodeMorphed?.(node, reference)
210
152
}
211
153
212
212
-
#morphMatchingElementContent(pair: MatchingElementReferencePair<Element>): void {
154
154
+
#morphMatchingElementContent(pair: PairOfMatchingElements<Element>): void {
213
155
const [node, reference] = pair
214
156
215
215
-
if (isHead(node)) {
157
157
+
if (isHeadElement(node)) {
216
158
// We can pass the reference as a head here becuase we know it's the same as the node.
217
217
-
this.#morphHeadContents(pair as MatchingElementReferencePair<HTMLHeadElement>)
159
159
+
this.#morphHeadContents(pair as PairOfMatchingElements<HTMLHeadElement>)
218
160
} else if (node.hasChildNodes() || reference.hasChildNodes()) this.#morphChildNodes(pair)
219
161
}
220
162
221
221
-
#morphHeadContents([node, reference]: MatchingElementReferencePair<HTMLHeadElement>): void {
222
222
-
const refChildNodesMap: Map<string, ReadonlyNode<Element>> = new Map()
163
163
+
#morphHeadContents([node, reference]: PairOfMatchingElements<HTMLHeadElement>): void {
164
164
+
const refChildNodesMap: Map<string, Element> = new Map()
223
165
224
166
// Generate a map of the reference head element’s child nodes, keyed by their outerHTML.
225
167
const referenceChildrenLength = reference.children.length
···
237
179
// If the child is in the reference map already, we don't need to add it later.
238
180
// If it's not in the map, we need to remove it from the node.
239
181
if (refChild) refChildNodesMap.delete(key)
240
240
-
else this.#removeNode(child)
182
182
+
else this.removeNode(child)
241
183
}
242
184
243
185
// Any remaining nodes in the map should be appended to the head.
244
244
-
for (const refChild of refChildNodesMap.values()) this.#appendChild(node, refChild.cloneNode(true))
186
186
+
for (const refChild of refChildNodesMap.values()) this.appendChild(node, refChild.cloneNode(true))
245
187
}
246
188
247
247
-
#morphAttributes([element, reference]: MatchingElementReferencePair<Element>): void {
189
189
+
#morphAttributes([element, reference]: PairOfMatchingElements<Element>): void {
248
190
// Remove any excess attributes from the element that aren’t present in the reference.
249
191
for (const { name, value } of element.attributes) {
250
250
-
if (!reference.hasAttribute(name) && (this.#beforeAttributeUpdated?.(element, name, null) ?? true)) {
192
192
+
if (!reference.hasAttribute(name) && (this.options.beforeAttributeUpdated?.(element, name, null) ?? true)) {
251
193
element.removeAttribute(name)
252
252
-
this.#afterAttributeUpdated?.(element, name, value)
194
194
+
this.options.afterAttributeUpdated?.(element, name, value)
253
195
}
254
196
}
255
197
256
198
// Copy attributes from the reference to the element, if they don’t already match.
257
199
for (const { name, value } of reference.attributes) {
258
200
const previousValue = element.getAttribute(name)
259
259
-
if (previousValue !== value && (this.#beforeAttributeUpdated?.(element, name, value) ?? true)) {
201
201
+
if (previousValue !== value && (this.options.beforeAttributeUpdated?.(element, name, value) ?? true)) {
260
202
element.setAttribute(name, value)
261
261
-
this.#afterAttributeUpdated?.(element, name, previousValue)
203
203
+
this.options.afterAttributeUpdated?.(element, name, previousValue)
262
204
}
263
205
}
264
206
265
207
// For certain types of elements, we need to do some extra work to ensure
266
208
// the element’s state matches the reference elements’ state.
267
267
-
if (isInput(element) && isInput(reference)) {
209
209
+
if (isInputElement(element) && isInputElement(reference)) {
268
210
this.#updateProperty(element, "checked", reference.checked)
269
211
this.#updateProperty(element, "disabled", reference.disabled)
270
212
this.#updateProperty(element, "indeterminate", reference.indeterminate)
271
213
if (
272
214
element.type !== "file" &&
273
273
-
!(this.#ignoreActiveValue && document.activeElement === element) &&
274
274
-
!(this.#preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
215
215
+
!(this.options.ignoreActiveValue && document.activeElement === element) &&
216
216
+
!(this.options.preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
275
217
) {
276
218
this.#updateProperty(element, "value", reference.value)
277
219
}
278
278
-
} else if (isOption(element) && isOption(reference)) {
220
220
+
} else if (isOptionElement(element) && isOptionElement(reference)) {
279
221
this.#updateProperty(element, "selected", reference.selected)
280
222
} else if (
281
281
-
isTextArea(element) &&
282
282
-
isTextArea(reference) &&
283
283
-
!(this.#ignoreActiveValue && document.activeElement === element) &&
284
284
-
!(this.#preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
223
223
+
isTextAreaElement(element) &&
224
224
+
isTextAreaElement(reference) &&
225
225
+
!(this.options.ignoreActiveValue && document.activeElement === element) &&
226
226
+
!(this.options.preserveModifiedValues && element.name === reference.name && element.value !== element.defaultValue)
285
227
) {
286
228
this.#updateProperty(element, "value", reference.value)
287
229
···
291
233
}
292
234
293
235
// Iterates over the child nodes of the reference element, morphing the main element’s child nodes to match.
294
294
-
#morphChildNodes(pair: MatchingElementReferencePair<Element>): void {
236
236
+
#morphChildNodes(pair: PairOfMatchingElements<Element>): void {
295
237
const [element, reference] = pair
296
238
297
239
const childNodes = element.childNodes
···
299
241
300
242
for (let i = 0; i < refChildNodes.length; i++) {
301
243
const child = childNodes[i] as ChildNode | null
302
302
-
const refChild = refChildNodes[i] as ReadonlyNode<ChildNode> | null
244
244
+
const refChild = refChildNodes[i] as ChildNode | null
303
245
304
246
if (child && refChild) {
305
305
-
const pair: NodeReferencePair<ChildNode> = [child, refChild]
247
247
+
const pair: PairOfNodes<ChildNode> = [child, refChild]
306
248
307
249
if (isMatchingElementPair(pair)) {
308
308
-
if (isHead(pair[0])) {
309
309
-
this.#morphHeadContents(pair as MatchingElementReferencePair<HTMLHeadElement>)
250
250
+
if (isHeadElement(pair[0])) {
251
251
+
this.#morphHeadContents(pair as PairOfMatchingElements<HTMLHeadElement>)
310
252
} else {
311
253
this.#morphChildElement(pair, element)
312
254
}
313
255
} else this.#morphOtherNode(pair)
314
256
} else if (refChild) {
315
315
-
this.#appendChild(element, refChild.cloneNode(true))
257
257
+
this.appendChild(element, refChild.cloneNode(true))
316
258
}
317
259
}
318
260
319
261
// Clean up any excess nodes that may be left over
320
262
while (childNodes.length > refChildNodes.length) {
321
263
const child = element.lastChild
322
322
-
if (child) this.#removeNode(child)
264
264
+
if (child) this.removeNode(child)
323
265
}
324
266
}
325
267
326
326
-
#morphChildElement([child, reference]: MatchingElementReferencePair<Element>, parent: Element): void {
327
327
-
if (!(this.#beforeNodeMorphed?.(child, writableNode(reference)) ?? true)) return
268
268
+
#morphChildElement([child, reference]: PairOfMatchingElements<Element>, parent: Element): void {
269
269
+
if (!(this.options.beforeNodeMorphed?.(child, reference) ?? true)) return
328
270
329
329
-
const refIdSet = this.#idMap.get(reference)
271
271
+
const refIdSet = this.idMap.get(reference)
330
272
331
273
// Generate the array in advance of the loop
332
274
const refSetArray = refIdSet ? [...refIdSet] : []
···
345
287
346
288
if (id !== "") {
347
289
if (id === reference.id) {
348
348
-
this.#insertBefore(parent, currentNode, child)
290
290
+
this.moveBefore(parent, currentNode, child)
349
291
return this.#morphNode([currentNode, reference])
350
292
} else {
351
351
-
const currentIdSet = this.#idMap.get(currentNode)
293
293
+
const currentIdSet = this.idMap.get(currentNode)
352
294
353
295
if (currentIdSet && refSetArray.some((it) => currentIdSet.has(it))) {
354
354
-
this.#insertBefore(parent, currentNode, child)
296
296
+
this.moveBefore(parent, currentNode, child)
355
297
return this.#morphNode([currentNode, reference])
356
298
}
357
299
}
···
362
304
}
363
305
364
306
// nextMatchByTagName is always set (at minimum to child itself since they have matching tag names)
365
365
-
this.#insertBefore(parent, nextMatchByTagName!, child)
307
307
+
this.moveBefore(parent, nextMatchByTagName!, child)
366
308
this.#morphNode([nextMatchByTagName!, reference])
367
309
368
368
-
this.#afterNodeMorphed?.(child, writableNode(reference))
310
310
+
this.options.afterNodeMorphed?.(child, reference)
369
311
}
370
312
371
313
#updateProperty<N extends Node, P extends keyof N>(node: N, propertyName: P, newValue: N[P]): void {
372
314
const previousValue = node[propertyName]
373
315
374
374
-
if (previousValue !== newValue && (this.#beforePropertyUpdated?.(node, propertyName, newValue) ?? true)) {
316
316
+
if (previousValue !== newValue && (this.options.beforePropertyUpdated?.(node, propertyName, newValue) ?? true)) {
375
317
node[propertyName] = newValue
376
376
-
this.#afterPropertyUpdated?.(node, propertyName, previousValue)
318
318
+
this.options.afterPropertyUpdated?.(node, propertyName, previousValue)
377
319
}
378
320
}
379
321
380
380
-
#replaceNode(node: ChildNode, newNode: Node): void {
381
381
-
if ((this.#beforeNodeRemoved?.(node) ?? true) && (this.#beforeNodeAdded?.(newNode) ?? true)) {
322
322
+
private replaceNode(node: ChildNode, newNode: Node): void {
323
323
+
if ((this.options.beforeNodeRemoved?.(node) ?? true) && (this.options.beforeNodeAdded?.(newNode) ?? true)) {
382
324
node.replaceWith(newNode)
383
383
-
this.#afterNodeAdded?.(newNode)
384
384
-
this.#afterNodeRemoved?.(node)
325
325
+
this.options.afterNodeAdded?.(newNode)
326
326
+
this.options.afterNodeRemoved?.(node)
385
327
}
386
328
}
387
329
388
388
-
#insertBefore(parent: ParentNode, node: Node, insertionPoint: ChildNode): void {
330
330
+
private moveBefore(parent: ParentNode, node: Node, insertionPoint: ChildNode): void {
389
331
if (node === insertionPoint) return
390
332
391
391
-
// Use moveBefore when available (more efficient for moving existing nodes)
392
392
-
if (supportsMoveBefore) {
393
393
-
;(parent as any).moveBefore(node, insertionPoint)
333
333
+
if ("moveBefore" in parent && typeof parent.moveBefore === "function") {
334
334
+
parent.moveBefore(node, insertionPoint)
394
335
} else {
395
336
parent.insertBefore(node, insertionPoint)
396
337
}
397
338
}
398
339
399
399
-
#appendChild(node: ParentNode, newNode: Node): void {
400
400
-
if (this.#beforeNodeAdded?.(newNode) ?? true) {
340
340
+
private appendChild(node: ParentNode, newNode: Node): void {
341
341
+
if (this.options.beforeNodeAdded?.(newNode) ?? true) {
401
342
node.appendChild(newNode)
402
402
-
this.#afterNodeAdded?.(newNode)
343
343
+
this.options.afterNodeAdded?.(newNode)
403
344
}
404
345
}
405
346
406
406
-
#removeNode(node: ChildNode): void {
407
407
-
if (this.#beforeNodeRemoved?.(node) ?? true) {
347
347
+
private removeNode(node: ChildNode): void {
348
348
+
if (this.options.beforeNodeRemoved?.(node) ?? true) {
408
349
node.remove()
409
409
-
this.#afterNodeRemoved?.(node)
350
350
+
this.options.afterNodeRemoved?.(node)
410
351
}
411
352
}
412
353
}
413
354
414
414
-
function writableNode<N extends Node>(node: ReadonlyNode<N>): N {
415
415
-
return node as N
416
416
-
}
355
355
+
const parentNodeTypes = new Set([1, 9, 11])
417
356
418
418
-
function isMatchingElementPair(pair: NodeReferencePair<Node>): pair is MatchingElementReferencePair<Element> {
357
357
+
function isMatchingElementPair(pair: PairOfNodes<Node>): pair is PairOfMatchingElements<Element> {
419
358
const [a, b] = pair
420
359
return isElement(a) && isElement(b) && a.localName === b.localName
421
360
}
422
361
423
423
-
function isParentNodePair(pair: NodeReferencePair<Node>): pair is NodeReferencePair<ParentNode> {
362
362
+
function isParentNodePair(pair: PairOfNodes<Node>): pair is PairOfNodes<ParentNode> {
424
363
return isParentNode(pair[0]) && isParentNode(pair[1])
425
364
}
426
365
427
427
-
function isElement(node: Node): node is Element
428
428
-
function isElement(node: ReadonlyNode<Node>): node is ReadonlyNode<Element>
429
429
-
function isElement(node: Node | ReadonlyNode<Node>): boolean {
366
366
+
function isElement(node: Node): node is Element {
430
367
return node.nodeType === 1
431
368
}
432
369
433
433
-
function isInput(element: Element): element is HTMLInputElement
434
434
-
function isInput(element: ReadonlyNode<Element>): element is ReadonlyNode<HTMLInputElement>
435
435
-
function isInput(element: Element | ReadonlyNode<Element>): boolean {
370
370
+
function isInputElement(element: Element): element is HTMLInputElement {
436
371
return element.localName === "input"
437
372
}
438
373
439
439
-
function isOption(element: Element): element is HTMLOptionElement
440
440
-
function isOption(element: ReadonlyNode<Element>): element is ReadonlyNode<HTMLOptionElement>
441
441
-
function isOption(element: Element | ReadonlyNode<Element>): boolean {
374
374
+
function isOptionElement(element: Element): element is HTMLOptionElement {
442
375
return element.localName === "option"
443
376
}
444
377
445
445
-
function isTextArea(element: Element): element is HTMLTextAreaElement
446
446
-
function isTextArea(element: ReadonlyNode<Element>): element is ReadonlyNode<HTMLTextAreaElement>
447
447
-
function isTextArea(element: Element | ReadonlyNode<Element>): boolean {
378
378
+
function isTextAreaElement(element: Element): element is HTMLTextAreaElement {
448
379
return element.localName === "textarea"
449
380
}
450
381
451
451
-
function isHead(element: Element): element is HTMLHeadElement
452
452
-
function isHead(element: ReadonlyNode<Element>): element is ReadonlyNode<HTMLHeadElement>
453
453
-
function isHead(element: Element | ReadonlyNode<Element>): boolean {
382
382
+
function isHeadElement(element: Element): element is HTMLHeadElement {
454
383
return element.localName === "head"
455
384
}
456
385
457
457
-
const parentNodeTypes = new Set([1, 9, 11])
386
386
+
function isParentNode(node: Node): node is ParentNode {
387
387
+
return parentNodeTypes.has(node.nodeType)
388
388
+
}
458
389
459
459
-
function isParentNode(node: Node): node is ParentNode
460
460
-
function isParentNode(node: ReadonlyNode<Node>): node is ReadonlyNode<ParentNode>
461
461
-
function isParentNode(node: Node | ReadonlyNode<Node>): boolean {
462
462
-
return parentNodeTypes.has(node.nodeType)
390
390
+
function isTextNode(node: Node): node is Text {
391
391
+
return node.nodeType === 3
463
392
}