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