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
Better handling of value attributes
joel.drapper.me
4 months ago
b22a007f
89304e25
+68
-31
1 changed file
expand all
collapse all
unified
split
src
morphlex.ts
+68
-31
src/morphlex.ts
···
1
const ParentNodeTypes = new Set([1, 9, 11])
0
0
0
0
0
0
0
0
0
0
0
0
2
3
type IdSet = Set<string>
4
type IdMap = WeakMap<Node, IdSet>
···
8
9
type PairOfNodes<N extends Node> = [N, N]
10
type PairOfMatchingElements<E extends Element> = Branded<PairOfNodes<E>, "MatchingElementPair">
0
0
0
0
0
0
0
0
0
0
0
11
12
interface Options {
13
beforeNodeVisited?: (fromNode: Node, toNode: Node) => boolean
···
148
}
149
150
private morphOtherNode([from, to]: PairOfNodes<ChildNode>): void {
151
-
// TODO: Improve this logic
152
-
// Handle text nodes, comments, and CDATA sections.
153
if (from.nodeType === to.nodeType && from.nodeValue !== null && to.nodeValue !== null) {
154
from.nodeValue = to.nodeValue
155
} else {
···
158
}
159
160
private visitAttributes([from, to]: PairOfMatchingElements<Element>): void {
161
-
const isInput = isInputElement(from) && isInputElement(to)
162
-
const isOption = isOptionElement(from) && isOptionElement(to)
163
-
164
const toAttrs = to.attributes
165
const fromAttrs = from.attributes
166
···
171
const value = attr.value
172
const oldValue = from.getAttribute(name)
173
174
-
if (isInput) {
175
-
if (name === "value" || name === "checked" || name === "indeterminate") {
176
-
continue
177
-
} else if (name === "morph-value" && (this.options.beforeAttributeUpdated?.(from, name, value) ?? true)) {
178
-
from.setAttribute(name, value)
179
-
from.value = value
180
-
this.options.afterAttributeUpdated?.(from, name, oldValue)
181
-
continue
182
-
} else if (name === "morph-checked" && (this.options.beforeAttributeUpdated?.(from, name, value) ?? true)) {
183
-
from.setAttribute(name, value)
184
-
from.checked = value === "true"
185
-
this.options.afterAttributeUpdated?.(from, name, oldValue)
186
-
continue
187
-
} else if (name === "morph-indeterminate" && (this.options.beforeAttributeUpdated?.(from, name, value) ?? true)) {
188
-
from.setAttribute(name, value)
189
-
from.indeterminate = value === "true"
190
-
this.options.afterAttributeUpdated?.(from, name, oldValue)
191
-
continue
0
0
0
0
0
0
0
0
192
}
193
-
} else if (isOption) {
194
-
if (name === "selected") {
195
-
continue
196
-
} else if (name === "morph-selected") {
197
-
from.setAttribute(name, value)
198
-
from.selected = value === "true"
199
-
this.options.afterAttributeUpdated?.(from, name, oldValue)
0
0
0
200
}
201
}
202
203
if (oldValue !== value && (this.options.beforeAttributeUpdated?.(from, name, value) ?? true)) {
204
from.setAttribute(name, value)
205
206
-
if (isInput && name === "disabled" && from.disabled !== to.disabled) {
207
from.disabled = to.disabled
208
}
209
···
421
}
422
}
423
}
0
0
0
0
0
0
0
0
424
}
425
426
function isMatchingElementPair(pair: PairOfNodes<Element>): pair is PairOfMatchingElements<Element> {
···
1
const ParentNodeTypes = new Set([1, 9, 11])
2
+
const DisablableElements = new Set(["input", "button", "select", "textarea", "option", "optgroup", "fieldset"])
3
+
const ValuableElements = new Set(["input", "select", "textarea"])
4
+
const ValueAttributes = new Set([
5
+
"value",
6
+
"selected",
7
+
"checked",
8
+
"indeterminate",
9
+
"morph-value",
10
+
"morph-selected",
11
+
"morph-checked",
12
+
"morph-indeterminate",
13
+
])
14
15
type IdSet = Set<string>
16
type IdMap = WeakMap<Node, IdSet>
···
20
21
type PairOfNodes<N extends Node> = [N, N]
22
type PairOfMatchingElements<E extends Element> = Branded<PairOfNodes<E>, "MatchingElementPair">
23
+
24
+
type DisablableElement =
25
+
| HTMLInputElement
26
+
| HTMLButtonElement
27
+
| HTMLSelectElement
28
+
| HTMLTextAreaElement
29
+
| HTMLOptionElement
30
+
| HTMLOptGroupElement
31
+
| HTMLFieldSetElement
32
+
33
+
type ValuableElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
34
35
interface Options {
36
beforeNodeVisited?: (fromNode: Node, toNode: Node) => boolean
···
171
}
172
173
private morphOtherNode([from, to]: PairOfNodes<ChildNode>): void {
0
0
174
if (from.nodeType === to.nodeType && from.nodeValue !== null && to.nodeValue !== null) {
175
from.nodeValue = to.nodeValue
176
} else {
···
179
}
180
181
private visitAttributes([from, to]: PairOfMatchingElements<Element>): void {
0
0
0
182
const toAttrs = to.attributes
183
const fromAttrs = from.attributes
184
···
189
const value = attr.value
190
const oldValue = from.getAttribute(name)
191
192
+
if (ValueAttributes.has(name)) {
193
+
if (isValuableElement(from) && isValuableElement(to)) {
194
+
if (name === "value") {
195
+
continue
196
+
} else if (name === "morph-value" && (this.options.beforeAttributeUpdated?.(from, name, value) ?? true)) {
197
+
from.setAttribute(name, value)
198
+
from.value = value
199
+
this.options.afterAttributeUpdated?.(from, name, oldValue)
200
+
continue
201
+
}
202
+
}
203
+
204
+
if (isInputElement(from) && isInputElement(to)) {
205
+
if (name === "checked" || name === "indeterminate") {
206
+
continue
207
+
} else if (name === "morph-checked" && (this.options.beforeAttributeUpdated?.(from, name, value) ?? true)) {
208
+
from.setAttribute(name, value)
209
+
from.checked = value === "true"
210
+
this.options.afterAttributeUpdated?.(from, name, oldValue)
211
+
continue
212
+
} else if (name === "morph-indeterminate" && (this.options.beforeAttributeUpdated?.(from, name, value) ?? true)) {
213
+
from.setAttribute(name, value)
214
+
from.indeterminate = value === "true"
215
+
this.options.afterAttributeUpdated?.(from, name, oldValue)
216
+
continue
217
+
}
218
}
219
+
220
+
if (isOptionElement(from) && isOptionElement(to)) {
221
+
if (name === "selected") {
222
+
continue
223
+
} else if (name === "morph-selected") {
224
+
from.setAttribute(name, value)
225
+
from.selected = value === "true"
226
+
this.options.afterAttributeUpdated?.(from, name, oldValue)
227
+
continue
228
+
}
229
}
230
}
231
232
if (oldValue !== value && (this.options.beforeAttributeUpdated?.(from, name, value) ?? true)) {
233
from.setAttribute(name, value)
234
235
+
if (name === "disabled" && isDisablableElement(from) && isDisablableElement(to) && from.disabled !== to.disabled) {
236
from.disabled = to.disabled
237
}
238
···
450
}
451
}
452
}
453
+
}
454
+
455
+
function isDisablableElement(element: Element): element is DisablableElement {
456
+
return DisablableElements.has(element.localName)
457
+
}
458
+
459
+
function isValuableElement(element: Element): element is ValuableElement {
460
+
return ValuableElements.has(element.localName)
461
}
462
463
function isMatchingElementPair(pair: PairOfNodes<Element>): pair is PairOfMatchingElements<Element> {