mithril-lynx 0.0.1
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.
- package/CONTRACT.md +151 -0
- package/LICENSE +21 -0
- package/README.md +227 -0
- package/background.d.ts +54 -0
- package/background.js +169 -0
- package/element.d.ts +34 -0
- package/element.js +79 -0
- package/gesture.d.ts +40 -0
- package/gesture.js +117 -0
- package/internal/constants.js +26 -0
- package/internal/virtual-node.js +388 -0
- package/list.d.ts +31 -0
- package/list.js +185 -0
- package/main-thread.d.ts +43 -0
- package/main-thread.js +165 -0
- package/package.json +100 -0
- package/plugin.d.ts +17 -0
- package/plugin.js +185 -0
- package/renderer/background.d.ts +21 -0
- package/renderer/background.js +84 -0
- package/renderer/main-thread.d.ts +12 -0
- package/renderer/main-thread.js +175 -0
- package/src/lynx-mithril-shim.d.ts +16 -0
- package/src/lynx-mithril-shim.js +1477 -0
- package/src/worklet-runtime.js +82 -0
- package/testing.d.ts +10 -0
- package/testing.js +91 -0
|
@@ -0,0 +1,1477 @@
|
|
|
1
|
+
// lynx-mithril-shim.js
|
|
2
|
+
//
|
|
3
|
+
// Contract-complete reimplementation of mithril/render/render.js@2.3.8
|
|
4
|
+
// mapping the mithril render contract onto the Lynx Element PAPI (main thread).
|
|
5
|
+
//
|
|
6
|
+
// Satisfies the mapping table in CONTRACT.md (Findings A & B of the
|
|
7
|
+
// rspeedy-mithril-scaffold plan). Key mappings:
|
|
8
|
+
// - Every wrapper node exposes `ownerDocument` (a fakeDocument implementing
|
|
9
|
+
// createElement / createElementNS / createTextNode / createDocumentFragment).
|
|
10
|
+
// - Events use `__AddEventListener(node, name, fn, {})` with real JS function
|
|
11
|
+
// handlers (NO `__SetEvents`). Lynx tap events are normalized to
|
|
12
|
+
// `{type, currentTarget, redraw:false, preventDefault(){}, stopPropagation(){}}`.
|
|
13
|
+
// - Styles go through LynxStyleProxy, which accepts BOTH
|
|
14
|
+
// `setProperty("font-size", v)` and `style["fontSize"] = v`; all keys are
|
|
15
|
+
// camelized before `__SetInlineStyles(node, camelizedObject)`.
|
|
16
|
+
// - Text uses the raw-text pattern: `__CreateText(id)` + child
|
|
17
|
+
// `__CreateRawText(value)`; dynamic updates via `__SetAttribute(raw, "text", v)`
|
|
18
|
+
// + `__FlushElementTree()`. NO `__SetInnerText`.
|
|
19
|
+
// - Fragments are inert `{nodeType: 11, ownerDocument, _parent:null,
|
|
20
|
+
// appendChild(){}}` objects; their children are created directly in the real
|
|
21
|
+
// parent and the fragment itself is never `__AppendElement`d.
|
|
22
|
+
// - requestAnimationFrame/cancelAnimationFrame are polyfilled on top of
|
|
23
|
+
// queueMicrotask; the flush hook calls `__FlushElementTree()` after each
|
|
24
|
+
// mithril redraw pass, and node-mutation helpers flush opportunistically
|
|
25
|
+
// when no rAF is pending.
|
|
26
|
+
//
|
|
27
|
+
// Module format: CommonJS (`module.exports = function()` factory), matching the
|
|
28
|
+
// plan's `require("mithril")` usage in src/index.js.
|
|
29
|
+
|
|
30
|
+
"use strict"
|
|
31
|
+
|
|
32
|
+
var Vnode = require("mithril/render/vnode")
|
|
33
|
+
var cachedAttrsIsStaticMap = require("mithril/render/cachedAttrsIsStaticMap")
|
|
34
|
+
|
|
35
|
+
// ============================================================
|
|
36
|
+
// 1. Small helpers
|
|
37
|
+
// ============================================================
|
|
38
|
+
|
|
39
|
+
function camelize(str) {
|
|
40
|
+
return str.replace(/-([a-z])/g, function (m, c) { return c.toUpperCase() })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseCssText(text) {
|
|
44
|
+
var out = {}
|
|
45
|
+
if (text == null) return out
|
|
46
|
+
var parts = String(text).split(";")
|
|
47
|
+
for (var i = 0; i < parts.length; i++) {
|
|
48
|
+
var part = parts[i]
|
|
49
|
+
if (part == null) continue
|
|
50
|
+
var idx = part.indexOf(":")
|
|
51
|
+
if (idx === -1) continue
|
|
52
|
+
var key = part.slice(0, idx).trim()
|
|
53
|
+
var value = part.slice(idx + 1).trim()
|
|
54
|
+
if (key !== "") out[key] = value
|
|
55
|
+
}
|
|
56
|
+
return out
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isLifecycleMethod(attr) {
|
|
60
|
+
return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================
|
|
64
|
+
// 2. delayedRemoval (port of mithril/render/delayedRemoval.js)
|
|
65
|
+
// ============================================================
|
|
66
|
+
|
|
67
|
+
var delayedRemoval = new WeakMap()
|
|
68
|
+
|
|
69
|
+
// ============================================================
|
|
70
|
+
// 3. rAF polyfill + flush machinery
|
|
71
|
+
// ============================================================
|
|
72
|
+
|
|
73
|
+
var rafCallbacks = []
|
|
74
|
+
var rafPending = false
|
|
75
|
+
var rafIdCounter = 0
|
|
76
|
+
var enqueue = typeof queueMicrotask === "function" ? queueMicrotask : function (fn) { Promise.resolve().then(fn) }
|
|
77
|
+
|
|
78
|
+
function requestAnimationFrame(cb) {
|
|
79
|
+
rafIdCounter++
|
|
80
|
+
var id = rafIdCounter
|
|
81
|
+
rafCallbacks.push({ id: id, cb: cb })
|
|
82
|
+
if (!rafPending) {
|
|
83
|
+
rafPending = true
|
|
84
|
+
enqueue(function () {
|
|
85
|
+
rafPending = false
|
|
86
|
+
var callbacks = rafCallbacks
|
|
87
|
+
rafCallbacks = []
|
|
88
|
+
for (var i = 0; i < callbacks.length; i++) {
|
|
89
|
+
try { callbacks[i].cb() } catch (e) { /* swallow */ }
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
return id
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function cancelAnimationFrame(id) {
|
|
97
|
+
for (var i = 0; i < rafCallbacks.length; i++) {
|
|
98
|
+
if (rafCallbacks[i].id === id) {
|
|
99
|
+
rafCallbacks.splice(i, 1)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Install the polyfill BEFORE mithril is required so that mithril's
|
|
106
|
+
// mount-redraw sees requestAnimationFrame at load time.
|
|
107
|
+
var g = typeof globalThis !== "undefined" ? globalThis : (typeof self !== "undefined" ? self : this)
|
|
108
|
+
g.requestAnimationFrame = requestAnimationFrame
|
|
109
|
+
g.cancelAnimationFrame = cancelAnimationFrame
|
|
110
|
+
if (typeof g.window === "undefined") {
|
|
111
|
+
try { g.window = g } catch (e) { /* ignore */ }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Flush machinery. renderDepth is > 0 while the shim's own render pass is
|
|
115
|
+
// running, so opportunistic flushes are suppressed and batched into the
|
|
116
|
+
// end-of-pass flush hook instead.
|
|
117
|
+
var renderDepth = 0
|
|
118
|
+
var styleProxies = []
|
|
119
|
+
|
|
120
|
+
function flushTree() {
|
|
121
|
+
for (var i = 0; i < styleProxies.length; i++) {
|
|
122
|
+
try { styleProxies[i]._flush() } catch (e) { /* ignore */ }
|
|
123
|
+
}
|
|
124
|
+
// __FlushElementTree is a main-thread-only PAPI global. This same shim
|
|
125
|
+
// module is also used on the background thread in "renderer mode"
|
|
126
|
+
// (renderer/background.js), driving a VirtualNodeWrapper tree that has
|
|
127
|
+
// no real PAPI to flush — that side's own op-log dispatch is its
|
|
128
|
+
// equivalent of a flush. Found via real-device testing: the jsdom test
|
|
129
|
+
// polyfill leaves __FlushElementTree defined globally even after
|
|
130
|
+
// switching to the simulated background thread (switchToBackgroundThread
|
|
131
|
+
// only overwrites keys present in ITS OWN globals snapshot, never
|
|
132
|
+
// deletes leftover ones), so this gap never surfaced as a test failure.
|
|
133
|
+
if (typeof __FlushElementTree === "function") __FlushElementTree()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function maybeFlush() {
|
|
137
|
+
if (renderDepth === 0 && !rafPending) flushTree()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ============================================================
|
|
141
|
+
// 4. LynxStyleProxy
|
|
142
|
+
// ============================================================
|
|
143
|
+
|
|
144
|
+
// Accepts BOTH `setProperty("font-size", v)` and `style["fontSize"] = v`.
|
|
145
|
+
// All keys are camelized before being sent to `__SetInlineStyles`.
|
|
146
|
+
function LynxStyleProxy(wrapper) {
|
|
147
|
+
this._wrapper = wrapper
|
|
148
|
+
this._props = {}
|
|
149
|
+
styleProxies.push(this)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
LynxStyleProxy.prototype._flush = function () {
|
|
153
|
+
var styles = {}
|
|
154
|
+
var keys = Object.keys(this)
|
|
155
|
+
for (var i = 0; i < keys.length; i++) {
|
|
156
|
+
var key = keys[i]
|
|
157
|
+
if (key === "_wrapper" || key === "_props") continue
|
|
158
|
+
var value = this[key]
|
|
159
|
+
if (value == null) continue
|
|
160
|
+
styles[camelize(key)] = String(value)
|
|
161
|
+
}
|
|
162
|
+
__SetInlineStyles(this._wrapper._handle, styles)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
LynxStyleProxy.prototype.setProperty = function (name, value) {
|
|
166
|
+
var key = camelize(name)
|
|
167
|
+
if (value == null || value === "") {
|
|
168
|
+
this.removeProperty(name)
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
var self = this
|
|
172
|
+
if (!Object.prototype.hasOwnProperty.call(this, key)) {
|
|
173
|
+
Object.defineProperty(this, key, {
|
|
174
|
+
configurable: true,
|
|
175
|
+
enumerable: true,
|
|
176
|
+
get: function () { return self._props[key] },
|
|
177
|
+
set: function (v) {
|
|
178
|
+
if (v == null || v === "") delete self._props[key]
|
|
179
|
+
else self._props[key] = String(v)
|
|
180
|
+
self._flush()
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
this._props[key] = String(value)
|
|
185
|
+
this._flush()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
LynxStyleProxy.prototype.removeProperty = function (name) {
|
|
189
|
+
var key = camelize(name)
|
|
190
|
+
delete this._props[key]
|
|
191
|
+
if (Object.prototype.hasOwnProperty.call(this, key)) delete this[key]
|
|
192
|
+
this._flush()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
Object.defineProperty(LynxStyleProxy.prototype, "cssText", {
|
|
196
|
+
get: function () {
|
|
197
|
+
var out = []
|
|
198
|
+
for (var key in this._props) out.push(key + ":" + this._props[key])
|
|
199
|
+
return out.join(";")
|
|
200
|
+
},
|
|
201
|
+
set: function (value) {
|
|
202
|
+
var self = this
|
|
203
|
+
Object.keys(this).forEach(function (key) {
|
|
204
|
+
if (key !== "_wrapper" && key !== "_props") delete self[key]
|
|
205
|
+
})
|
|
206
|
+
this._props = {}
|
|
207
|
+
var parsed = parseCssText(value)
|
|
208
|
+
for (var key in parsed) this.setProperty(key, parsed[key])
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// ============================================================
|
|
213
|
+
// 5. LynxNodeWrapper
|
|
214
|
+
// ============================================================
|
|
215
|
+
|
|
216
|
+
var wrapperCache = new WeakMap()
|
|
217
|
+
|
|
218
|
+
function wrapperFor(handle) {
|
|
219
|
+
if (handle == null) return null
|
|
220
|
+
var wrapper = wrapperCache.get(handle)
|
|
221
|
+
if (wrapper == null) {
|
|
222
|
+
wrapper = new LynxNodeWrapper(handle)
|
|
223
|
+
wrapperCache.set(handle, wrapper)
|
|
224
|
+
}
|
|
225
|
+
return wrapper
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function LynxNodeWrapper(handle) {
|
|
229
|
+
this._handle = handle
|
|
230
|
+
this._style = new LynxStyleProxy(this)
|
|
231
|
+
this._directProps = {}
|
|
232
|
+
this._listeners = Object.create(null)
|
|
233
|
+
this._isRawText = false
|
|
234
|
+
this._text = null
|
|
235
|
+
this._document = null
|
|
236
|
+
this._pageId = 0
|
|
237
|
+
this._tag = null
|
|
238
|
+
this.vnodes = null
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
Object.defineProperty(LynxNodeWrapper.prototype, "nodeType", {
|
|
242
|
+
get: function () { return this._isRawText ? 3 : 1 }
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
Object.defineProperty(LynxNodeWrapper.prototype, "ownerDocument", {
|
|
246
|
+
get: function () {
|
|
247
|
+
if (this._document != null) return this._document
|
|
248
|
+
var p = __GetParent(this._handle)
|
|
249
|
+
if (p != null) {
|
|
250
|
+
var doc = wrapperFor(p).ownerDocument
|
|
251
|
+
if (doc != null) {
|
|
252
|
+
this._document = doc
|
|
253
|
+
return doc
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return getDefaultDocument()
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
Object.defineProperty(LynxNodeWrapper.prototype, "namespaceURI", {
|
|
261
|
+
get: function () { return undefined }
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
Object.defineProperty(LynxNodeWrapper.prototype, "parentNode", {
|
|
265
|
+
get: function () {
|
|
266
|
+
var p = __GetParent(this._handle)
|
|
267
|
+
return p != null ? wrapperFor(p) : null
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
Object.defineProperty(LynxNodeWrapper.prototype, "firstChild", {
|
|
272
|
+
get: function () {
|
|
273
|
+
var c = __FirstElement(this._handle)
|
|
274
|
+
return c != null ? wrapperFor(c) : null
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
Object.defineProperty(LynxNodeWrapper.prototype, "nextSibling", {
|
|
279
|
+
get: function () {
|
|
280
|
+
var n = __NextElement(this._handle)
|
|
281
|
+
return n != null ? wrapperFor(n) : null
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
Object.defineProperty(LynxNodeWrapper.prototype, "textContent", {
|
|
286
|
+
get: function () {
|
|
287
|
+
if (this._isRawText) return this._text
|
|
288
|
+
var out = ""
|
|
289
|
+
var child = __FirstElement(this._handle)
|
|
290
|
+
while (child != null) {
|
|
291
|
+
out += wrapperFor(child).textContent
|
|
292
|
+
child = __NextElement(child)
|
|
293
|
+
}
|
|
294
|
+
return out
|
|
295
|
+
},
|
|
296
|
+
set: function (value) {
|
|
297
|
+
if (this._isRawText) {
|
|
298
|
+
this.nodeValue = value
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
var children = __GetChildren(this._handle)
|
|
302
|
+
if (children.length > 0) __ReplaceElements(this._handle, [], children)
|
|
303
|
+
if (value != null && value !== "") {
|
|
304
|
+
var raw = createRawTextNode(String(value))
|
|
305
|
+
__AppendElement(this._handle, raw._handle)
|
|
306
|
+
}
|
|
307
|
+
maybeFlush()
|
|
308
|
+
}
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
Object.defineProperty(LynxNodeWrapper.prototype, "nodeValue", {
|
|
312
|
+
get: function () {
|
|
313
|
+
return this._isRawText ? this._text : null
|
|
314
|
+
},
|
|
315
|
+
set: function (value) {
|
|
316
|
+
if (this._isRawText) {
|
|
317
|
+
this._text = String(value)
|
|
318
|
+
__SetAttribute(this._handle, "text", this._text)
|
|
319
|
+
maybeFlush()
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
Object.defineProperty(LynxNodeWrapper.prototype, "innerHTML", {
|
|
325
|
+
get: function () { return this.textContent },
|
|
326
|
+
set: function () {
|
|
327
|
+
throw new Error("m.trust / innerHTML is not supported by the Lynx shim (v1).")
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
Object.defineProperty(LynxNodeWrapper.prototype, "style", {
|
|
332
|
+
get: function () { return this._style },
|
|
333
|
+
set: function (value) {
|
|
334
|
+
if (value == null) this._style.cssText = ""
|
|
335
|
+
else if (typeof value === "string") this._style.cssText = value
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
// Direct properties: these are the keys mithril's hasPropertyKey() will find
|
|
340
|
+
// via `key in vnode.dom`, so they must exist on the wrapper.
|
|
341
|
+
var DIRECT_PROPS = ["value", "checked", "selectedIndex", "className", "id", "type"]
|
|
342
|
+
DIRECT_PROPS.forEach(function (p) {
|
|
343
|
+
Object.defineProperty(LynxNodeWrapper.prototype, p, {
|
|
344
|
+
configurable: true,
|
|
345
|
+
enumerable: true,
|
|
346
|
+
get: function () { return this._directProps[p] },
|
|
347
|
+
set: function (v) {
|
|
348
|
+
this._directProps[p] = v
|
|
349
|
+
this._setDirectProp(p, v)
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
LynxNodeWrapper.prototype._setDirectProp = function (key, value) {
|
|
355
|
+
if (key === "className") {
|
|
356
|
+
__SetClasses(this._handle, value == null ? undefined : String(value))
|
|
357
|
+
} else if (key === "id") {
|
|
358
|
+
__SetID(this._handle, value == null ? null : String(value))
|
|
359
|
+
} else if (key === "checked") {
|
|
360
|
+
__SetAttribute(this._handle, key, value == null ? null : value)
|
|
361
|
+
} else {
|
|
362
|
+
__SetAttribute(this._handle, key, value == null ? null : String(value))
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
LynxNodeWrapper.prototype.setAttribute = function (key, value) {
|
|
367
|
+
if (key === "class") {
|
|
368
|
+
__SetClasses(this._handle, value == null ? undefined : String(value))
|
|
369
|
+
} else if (key === "id") {
|
|
370
|
+
__SetID(this._handle, value == null ? null : String(value))
|
|
371
|
+
} else if (key.slice(0, 5) === "data-") {
|
|
372
|
+
__AddDataset(this._handle, key.slice(5), value)
|
|
373
|
+
} else {
|
|
374
|
+
__SetAttribute(this._handle, key, value == null ? null : String(value))
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
LynxNodeWrapper.prototype.removeAttribute = function (key) {
|
|
379
|
+
if (key === "class") {
|
|
380
|
+
__SetClasses(this._handle, undefined)
|
|
381
|
+
} else if (key === "id") {
|
|
382
|
+
__SetID(this._handle, null)
|
|
383
|
+
} else if (key.slice(0, 5) === "data-") {
|
|
384
|
+
__AddDataset(this._handle, key.slice(5), null)
|
|
385
|
+
} else {
|
|
386
|
+
__SetAttribute(this._handle, key, null)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
LynxNodeWrapper.prototype.setAttributeNS = function (ns, key, value) {
|
|
391
|
+
this.setAttribute(key, value)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
LynxNodeWrapper.prototype.appendChild = function (child) {
|
|
395
|
+
if (child == null) return child
|
|
396
|
+
if (child.nodeType === 11) return child // inert fragment: children already placed by the engine
|
|
397
|
+
__AppendElement(this._handle, child._handle)
|
|
398
|
+
maybeFlush()
|
|
399
|
+
return child
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
LynxNodeWrapper.prototype.insertBefore = function (child, ref) {
|
|
403
|
+
if (child == null) return child
|
|
404
|
+
if (child.nodeType === 11) return child
|
|
405
|
+
if (ref == null) {
|
|
406
|
+
__AppendElement(this._handle, child._handle)
|
|
407
|
+
} else {
|
|
408
|
+
__InsertElementBefore(this._handle, child._handle, ref._handle)
|
|
409
|
+
}
|
|
410
|
+
maybeFlush()
|
|
411
|
+
return child
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
LynxNodeWrapper.prototype.removeChild = function (child) {
|
|
415
|
+
if (child == null) return child
|
|
416
|
+
if (child.nodeType === 11) return child
|
|
417
|
+
__RemoveElement(this._handle, child._handle)
|
|
418
|
+
maybeFlush()
|
|
419
|
+
return child
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
LynxNodeWrapper.prototype.contains = function (other) {
|
|
423
|
+
if (other == null) return false
|
|
424
|
+
if (other === this) return true
|
|
425
|
+
if (other._handle == null) return false
|
|
426
|
+
var p = __GetParent(other._handle)
|
|
427
|
+
while (p != null) {
|
|
428
|
+
if (p === this._handle) return true
|
|
429
|
+
p = __GetParent(p)
|
|
430
|
+
}
|
|
431
|
+
return false
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
LynxNodeWrapper.prototype.focus = function () {}
|
|
435
|
+
|
|
436
|
+
// Events: per-type registry of {listener, wrapped}. The wrapped handler is the
|
|
437
|
+
// real JS function passed to __AddEventListener; it normalizes the Lynx event
|
|
438
|
+
// and dispatches to the mithril EventDict (or a plain function).
|
|
439
|
+
LynxNodeWrapper.prototype.addEventListener = function (type, listener, opts) {
|
|
440
|
+
var entry = this._listeners[type]
|
|
441
|
+
if (entry != null && entry.listener === listener) return
|
|
442
|
+
if (entry != null) {
|
|
443
|
+
__RemoveEventListener(this._handle, type, entry.wrapped, entry.opts || {})
|
|
444
|
+
}
|
|
445
|
+
var self = this
|
|
446
|
+
var wrapped = function (rawEv) {
|
|
447
|
+
var ev = normalizeEvent(rawEv, self)
|
|
448
|
+
if (typeof listener === "function") listener.call(ev.currentTarget, ev)
|
|
449
|
+
else if (listener != null && typeof listener.handleEvent === "function") listener.handleEvent(ev)
|
|
450
|
+
}
|
|
451
|
+
this._listeners[type] = { listener: listener, wrapped: wrapped, opts: opts || {} }
|
|
452
|
+
__AddEventListener(this._handle, type, wrapped, opts || {})
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
LynxNodeWrapper.prototype.removeEventListener = function (type, listener, opts) {
|
|
456
|
+
var entry = this._listeners[type]
|
|
457
|
+
if (entry == null) return
|
|
458
|
+
if (listener != null && entry.listener !== listener) return
|
|
459
|
+
delete this._listeners[type]
|
|
460
|
+
__RemoveEventListener(this._handle, type, entry.wrapped, entry.opts || {})
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Normalize a Lynx event into a DOM-like event object. `redraw: false` disables
|
|
464
|
+
// mithril's automatic redraw-on-event; the demo must call shim.redraw() (or
|
|
465
|
+
// m.redraw()) explicitly.
|
|
466
|
+
function normalizeEvent(rawEv, node) {
|
|
467
|
+
var ev = {
|
|
468
|
+
type: rawEv != null && rawEv.type != null ? rawEv.type : "tap",
|
|
469
|
+
currentTarget: node,
|
|
470
|
+
redraw: false,
|
|
471
|
+
preventDefault: function () {},
|
|
472
|
+
stopPropagation: function () {}
|
|
473
|
+
}
|
|
474
|
+
if (rawEv != null) {
|
|
475
|
+
for (var k in rawEv) {
|
|
476
|
+
if (ev[k] === undefined) ev[k] = rawEv[k]
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return ev
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ============================================================
|
|
483
|
+
// 6. fakeDocument + fragment
|
|
484
|
+
// ============================================================
|
|
485
|
+
|
|
486
|
+
var defaultDocument = null
|
|
487
|
+
|
|
488
|
+
function getDefaultDocument() {
|
|
489
|
+
if (defaultDocument == null) defaultDocument = createFakeDocument(0)
|
|
490
|
+
return defaultDocument
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function createRawTextNode(value) {
|
|
494
|
+
var handle = __CreateRawText(String(value))
|
|
495
|
+
var wrapper = wrapperFor(handle)
|
|
496
|
+
wrapper._isRawText = true
|
|
497
|
+
wrapper._text = String(value)
|
|
498
|
+
return wrapper
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function createElementWrapper(tag, pageId) {
|
|
502
|
+
var handle
|
|
503
|
+
if (tag === "view") handle = __CreateView(pageId)
|
|
504
|
+
else if (tag === "text") handle = __CreateText(pageId)
|
|
505
|
+
else handle = __CreateElement(tag, pageId, {})
|
|
506
|
+
var wrapper = wrapperFor(handle)
|
|
507
|
+
wrapper._tag = tag
|
|
508
|
+
return wrapper
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function createFakeDocument(pageId) {
|
|
512
|
+
var document = {
|
|
513
|
+
_pageId: pageId,
|
|
514
|
+
createElement: function (tag, opts) {
|
|
515
|
+
var wrapper = createElementWrapper(tag, pageId)
|
|
516
|
+
wrapper._document = document
|
|
517
|
+
return wrapper
|
|
518
|
+
},
|
|
519
|
+
createElementNS: function (ns, tag, opts) {
|
|
520
|
+
return document.createElement(tag, opts)
|
|
521
|
+
},
|
|
522
|
+
createTextNode: function (value) {
|
|
523
|
+
var wrapper = createRawTextNode(value)
|
|
524
|
+
wrapper._document = document
|
|
525
|
+
return wrapper
|
|
526
|
+
},
|
|
527
|
+
createDocumentFragment: function () {
|
|
528
|
+
return createFragment(document)
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
Object.defineProperty(document, "activeElement", {
|
|
532
|
+
get: function () { return null }
|
|
533
|
+
})
|
|
534
|
+
return document
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Inert fragment: never __AppendElement'd. Children are created directly in the
|
|
538
|
+
// real parent by the engine (see createFragment below).
|
|
539
|
+
function createFragment(document) {
|
|
540
|
+
return {
|
|
541
|
+
nodeType: 11,
|
|
542
|
+
ownerDocument: document,
|
|
543
|
+
_parent: null,
|
|
544
|
+
appendChild: function () {},
|
|
545
|
+
insertBefore: function () {},
|
|
546
|
+
removeChild: function () {}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function getPreviousSibling(parentHandle, nodeHandle) {
|
|
551
|
+
var child = __FirstElement(parentHandle)
|
|
552
|
+
while (child != null) {
|
|
553
|
+
var next = __NextElement(child)
|
|
554
|
+
if (next === nodeHandle) return child
|
|
555
|
+
child = next
|
|
556
|
+
}
|
|
557
|
+
return null
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function getLastChild(parentHandle) {
|
|
561
|
+
var child = __FirstElement(parentHandle)
|
|
562
|
+
var last = null
|
|
563
|
+
while (child != null) {
|
|
564
|
+
last = child
|
|
565
|
+
child = __NextElement(child)
|
|
566
|
+
}
|
|
567
|
+
return last
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ============================================================
|
|
571
|
+
// 7. Render engine (port of mithril/render/render.js@2.3.8)
|
|
572
|
+
// ============================================================
|
|
573
|
+
|
|
574
|
+
function factory() {
|
|
575
|
+
var nameSpace = {
|
|
576
|
+
svg: "http://www.w3.org/2000/svg",
|
|
577
|
+
math: "http://www.w3.org/1998/Math/MathML"
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
var currentRedraw
|
|
581
|
+
var currentRender
|
|
582
|
+
var currentDOM
|
|
583
|
+
|
|
584
|
+
function getDocument(dom) {
|
|
585
|
+
return dom.ownerDocument
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function getNameSpace(vnode) {
|
|
589
|
+
return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag]
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
//sanity check to discourage people from doing `vnode.state = ...`
|
|
593
|
+
function checkState(vnode, original) {
|
|
594
|
+
if (vnode.state !== original) throw new Error("'vnode.state' must not be modified.")
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
//Note: the hook is passed as the `this` argument to allow proxying the
|
|
598
|
+
//arguments without requiring a full array allocation to do so. It also
|
|
599
|
+
//takes advantage of the fact the current `vnode` is the first argument in
|
|
600
|
+
//all lifecycle methods.
|
|
601
|
+
function callHook(vnode) {
|
|
602
|
+
var original = vnode.state
|
|
603
|
+
try {
|
|
604
|
+
return this.apply(original, arguments)
|
|
605
|
+
} finally {
|
|
606
|
+
checkState(vnode, original)
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function activeElement(dom) {
|
|
611
|
+
try {
|
|
612
|
+
return getDocument(dom).activeElement
|
|
613
|
+
} catch (e) {
|
|
614
|
+
return null
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
//create
|
|
619
|
+
function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
|
|
620
|
+
for (var i = start; i < end; i++) {
|
|
621
|
+
var vnode = vnodes[i]
|
|
622
|
+
if (vnode != null) {
|
|
623
|
+
createNode(parent, vnode, hooks, ns, nextSibling)
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function createNode(parent, vnode, hooks, ns, nextSibling) {
|
|
629
|
+
var tag = vnode.tag
|
|
630
|
+
if (typeof tag === "string") {
|
|
631
|
+
vnode.state = {}
|
|
632
|
+
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
|
|
633
|
+
switch (tag) {
|
|
634
|
+
case "#": createText(parent, vnode, nextSibling); break
|
|
635
|
+
case "<": createHTML(parent, vnode, ns, nextSibling); break
|
|
636
|
+
case "[": createFragment(parent, vnode, hooks, ns, nextSibling); break
|
|
637
|
+
default: createElement(parent, vnode, hooks, ns, nextSibling)
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
else createComponent(parent, vnode, hooks, ns, nextSibling)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function createText(parent, vnode, nextSibling) {
|
|
644
|
+
vnode.dom = getDocument(parent).createTextNode(vnode.children)
|
|
645
|
+
insertDOM(parent, vnode.dom, nextSibling)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function createHTML(parent, vnode, ns, nextSibling) {
|
|
649
|
+
throw new Error("m.trust / innerHTML is not supported by the Lynx shim (v1).")
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function createFragment(parent, vnode, hooks, ns, nextSibling) {
|
|
653
|
+
// Inert fragment: children are created directly in the real parent.
|
|
654
|
+
// The fragment itself is never __AppendElement'd.
|
|
655
|
+
if (vnode.children != null) {
|
|
656
|
+
var children = vnode.children
|
|
657
|
+
var before = nextSibling != null
|
|
658
|
+
? getPreviousSibling(parent._handle, nextSibling._handle)
|
|
659
|
+
: getLastChild(parent._handle)
|
|
660
|
+
createNodes(parent, children, 0, children.length, hooks, nextSibling, ns)
|
|
661
|
+
var firstHandle = before != null ? __NextElement(before) : __FirstElement(parent._handle)
|
|
662
|
+
vnode.dom = firstHandle != null ? wrapperFor(firstHandle) : null
|
|
663
|
+
vnode.domSize = children.length
|
|
664
|
+
} else {
|
|
665
|
+
vnode.dom = null
|
|
666
|
+
vnode.domSize = 0
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function createElement(parent, vnode, hooks, ns, nextSibling) {
|
|
671
|
+
var tag = vnode.tag
|
|
672
|
+
var attrs = vnode.attrs
|
|
673
|
+
var is = vnode.is
|
|
674
|
+
|
|
675
|
+
ns = getNameSpace(vnode) || ns
|
|
676
|
+
|
|
677
|
+
var element = ns ?
|
|
678
|
+
is ? getDocument(parent).createElementNS(ns, tag, {is: is}) : getDocument(parent).createElementNS(ns, tag) :
|
|
679
|
+
is ? getDocument(parent).createElement(tag, {is: is}) : getDocument(parent).createElement(tag)
|
|
680
|
+
vnode.dom = element
|
|
681
|
+
|
|
682
|
+
if (attrs != null) {
|
|
683
|
+
setAttrs(vnode, attrs, ns)
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
insertDOM(parent, element, nextSibling)
|
|
687
|
+
|
|
688
|
+
if (!maybeSetContentEditable(vnode)) {
|
|
689
|
+
if (vnode.children != null) {
|
|
690
|
+
var children = vnode.children
|
|
691
|
+
createNodes(element, children, 0, children.length, hooks, null, ns)
|
|
692
|
+
if (vnode.tag === "select" && attrs != null) setLateSelectAttrs(vnode, attrs)
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function initComponent(vnode, hooks) {
|
|
698
|
+
var sentinel
|
|
699
|
+
if (typeof vnode.tag.view === "function") {
|
|
700
|
+
vnode.state = Object.create(vnode.tag)
|
|
701
|
+
sentinel = vnode.state.view
|
|
702
|
+
if (sentinel.$$reentrantLock$$ != null) return
|
|
703
|
+
sentinel.$$reentrantLock$$ = true
|
|
704
|
+
} else {
|
|
705
|
+
vnode.state = void 0
|
|
706
|
+
sentinel = vnode.tag
|
|
707
|
+
if (sentinel.$$reentrantLock$$ != null) return
|
|
708
|
+
sentinel.$$reentrantLock$$ = true
|
|
709
|
+
vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode)
|
|
710
|
+
}
|
|
711
|
+
initLifecycle(vnode.state, vnode, hooks)
|
|
712
|
+
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
|
|
713
|
+
vnode.instance = Vnode.normalize(callHook.call(vnode.state.view, vnode))
|
|
714
|
+
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
|
|
715
|
+
sentinel.$$reentrantLock$$ = null
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function createComponent(parent, vnode, hooks, ns, nextSibling) {
|
|
719
|
+
initComponent(vnode, hooks)
|
|
720
|
+
if (vnode.instance != null) {
|
|
721
|
+
createNode(parent, vnode.instance, hooks, ns, nextSibling)
|
|
722
|
+
vnode.dom = vnode.instance.dom
|
|
723
|
+
vnode.domSize = vnode.instance.domSize
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
vnode.domSize = 0
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
//update
|
|
731
|
+
function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
|
|
732
|
+
var o, v
|
|
733
|
+
if (old === vnodes || old == null && vnodes == null) return
|
|
734
|
+
else if (old == null || old.length === 0) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns)
|
|
735
|
+
else if (vnodes == null || vnodes.length === 0) removeNodes(parent, old, 0, old.length)
|
|
736
|
+
else {
|
|
737
|
+
var isOldKeyed = old[0] != null && old[0].key != null
|
|
738
|
+
var isKeyed = vnodes[0] != null && vnodes[0].key != null
|
|
739
|
+
var start = 0, oldStart = 0
|
|
740
|
+
if (!isOldKeyed) while (oldStart < old.length && old[oldStart] == null) oldStart++
|
|
741
|
+
if (!isKeyed) while (start < vnodes.length && vnodes[start] == null) start++
|
|
742
|
+
if (isOldKeyed !== isKeyed) {
|
|
743
|
+
removeNodes(parent, old, oldStart, old.length)
|
|
744
|
+
createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
|
|
745
|
+
} else if (!isKeyed) {
|
|
746
|
+
// Don't index past the end of either list (causes deopts).
|
|
747
|
+
var commonLength = old.length < vnodes.length ? old.length : vnodes.length
|
|
748
|
+
// Rewind if necessary to the first non-null index on either side.
|
|
749
|
+
start = start < oldStart ? start : oldStart
|
|
750
|
+
for (; start < commonLength; start++) {
|
|
751
|
+
o = old[start]
|
|
752
|
+
v = vnodes[start]
|
|
753
|
+
if (o === v || o == null && v == null) continue
|
|
754
|
+
else if (o == null) createNode(parent, v, hooks, ns, getNextSibling(old, start + 1, nextSibling))
|
|
755
|
+
else if (v == null) removeNode(parent, o)
|
|
756
|
+
else updateNode(parent, o, v, hooks, getNextSibling(old, start + 1, nextSibling), ns)
|
|
757
|
+
}
|
|
758
|
+
if (old.length > commonLength) removeNodes(parent, old, start, old.length)
|
|
759
|
+
if (vnodes.length > commonLength) createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
|
|
760
|
+
} else {
|
|
761
|
+
// keyed diff
|
|
762
|
+
var oldEnd = old.length - 1, end = vnodes.length - 1, map, oe, ve, topSibling
|
|
763
|
+
|
|
764
|
+
// bottom-up
|
|
765
|
+
while (oldEnd >= oldStart && end >= start) {
|
|
766
|
+
oe = old[oldEnd]
|
|
767
|
+
ve = vnodes[end]
|
|
768
|
+
if (oe.key !== ve.key) break
|
|
769
|
+
if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns)
|
|
770
|
+
if (ve.dom != null) nextSibling = ve.dom
|
|
771
|
+
oldEnd--, end--
|
|
772
|
+
}
|
|
773
|
+
// top-down
|
|
774
|
+
while (oldEnd >= oldStart && end >= start) {
|
|
775
|
+
o = old[oldStart]
|
|
776
|
+
v = vnodes[start]
|
|
777
|
+
if (o.key !== v.key) break
|
|
778
|
+
oldStart++, start++
|
|
779
|
+
if (o !== v) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns)
|
|
780
|
+
}
|
|
781
|
+
// swaps and list reversals
|
|
782
|
+
while (oldEnd >= oldStart && end >= start) {
|
|
783
|
+
if (start === end) break
|
|
784
|
+
if (o.key !== ve.key || oe.key !== v.key) break
|
|
785
|
+
topSibling = getNextSibling(old, oldStart, nextSibling)
|
|
786
|
+
moveDOM(parent, oe, topSibling)
|
|
787
|
+
if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns)
|
|
788
|
+
if (++start <= --end) moveDOM(parent, o, nextSibling)
|
|
789
|
+
if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns)
|
|
790
|
+
if (ve.dom != null) nextSibling = ve.dom
|
|
791
|
+
oldStart++; oldEnd--
|
|
792
|
+
oe = old[oldEnd]
|
|
793
|
+
ve = vnodes[end]
|
|
794
|
+
o = old[oldStart]
|
|
795
|
+
v = vnodes[start]
|
|
796
|
+
}
|
|
797
|
+
// bottom up once again
|
|
798
|
+
while (oldEnd >= oldStart && end >= start) {
|
|
799
|
+
if (oe.key !== ve.key) break
|
|
800
|
+
if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns)
|
|
801
|
+
if (ve.dom != null) nextSibling = ve.dom
|
|
802
|
+
oldEnd--, end--
|
|
803
|
+
oe = old[oldEnd]
|
|
804
|
+
ve = vnodes[end]
|
|
805
|
+
}
|
|
806
|
+
if (start > end) removeNodes(parent, old, oldStart, oldEnd + 1)
|
|
807
|
+
else if (oldStart > oldEnd) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
|
|
808
|
+
else {
|
|
809
|
+
// inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul
|
|
810
|
+
var originalNextSibling = nextSibling, vnodesLength = end - start + 1, oldIndices = new Array(vnodesLength), li = 0, i = 0, pos = 2147483647, matched = 0, lisIndices
|
|
811
|
+
for (i = 0; i < vnodesLength; i++) oldIndices[i] = -1
|
|
812
|
+
for (i = end; i >= start; i--) {
|
|
813
|
+
if (map == null) map = getKeyMap(old, oldStart, oldEnd + 1)
|
|
814
|
+
ve = vnodes[i]
|
|
815
|
+
var oldIndex = map[ve.key]
|
|
816
|
+
if (oldIndex != null) {
|
|
817
|
+
pos = (oldIndex < pos) ? oldIndex : -1 // becomes -1 if nodes were re-ordered
|
|
818
|
+
oldIndices[i - start] = oldIndex
|
|
819
|
+
oe = old[oldIndex]
|
|
820
|
+
old[oldIndex] = null
|
|
821
|
+
if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns)
|
|
822
|
+
if (ve.dom != null) nextSibling = ve.dom
|
|
823
|
+
matched++
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
nextSibling = originalNextSibling
|
|
827
|
+
if (matched !== oldEnd - oldStart + 1) removeNodes(parent, old, oldStart, oldEnd + 1)
|
|
828
|
+
if (matched === 0) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
|
|
829
|
+
else {
|
|
830
|
+
if (pos === -1) {
|
|
831
|
+
// the indices of the indices of the items that are part of the
|
|
832
|
+
// longest increasing subsequence in the oldIndices list
|
|
833
|
+
lisIndices = makeLisIndices(oldIndices)
|
|
834
|
+
li = lisIndices.length - 1
|
|
835
|
+
for (i = end; i >= start; i--) {
|
|
836
|
+
v = vnodes[i]
|
|
837
|
+
if (oldIndices[i - start] === -1) createNode(parent, v, hooks, ns, nextSibling)
|
|
838
|
+
else {
|
|
839
|
+
if (lisIndices[li] === i - start) li--
|
|
840
|
+
else moveDOM(parent, v, nextSibling)
|
|
841
|
+
}
|
|
842
|
+
if (v.dom != null) nextSibling = vnodes[i].dom
|
|
843
|
+
}
|
|
844
|
+
} else {
|
|
845
|
+
for (i = end; i >= start; i--) {
|
|
846
|
+
v = vnodes[i]
|
|
847
|
+
if (oldIndices[i - start] === -1) createNode(parent, v, hooks, ns, nextSibling)
|
|
848
|
+
if (v.dom != null) nextSibling = vnodes[i].dom
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function updateNode(parent, old, vnode, hooks, nextSibling, ns) {
|
|
858
|
+
var oldTag = old.tag, tag = vnode.tag
|
|
859
|
+
if (oldTag === tag && old.is === vnode.is) {
|
|
860
|
+
vnode.state = old.state
|
|
861
|
+
vnode.events = old.events
|
|
862
|
+
if (shouldNotUpdate(vnode, old)) return
|
|
863
|
+
if (typeof oldTag === "string") {
|
|
864
|
+
if (vnode.attrs != null) {
|
|
865
|
+
updateLifecycle(vnode.attrs, vnode, hooks)
|
|
866
|
+
}
|
|
867
|
+
switch (oldTag) {
|
|
868
|
+
case "#": updateText(old, vnode); break
|
|
869
|
+
case "<": updateHTML(parent, old, vnode, ns, nextSibling); break
|
|
870
|
+
case "[": updateFragment(parent, old, vnode, hooks, nextSibling, ns); break
|
|
871
|
+
default: updateElement(old, vnode, hooks, ns)
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
else updateComponent(parent, old, vnode, hooks, nextSibling, ns)
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
removeNode(parent, old)
|
|
878
|
+
createNode(parent, vnode, hooks, ns, nextSibling)
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function updateText(old, vnode) {
|
|
883
|
+
if (old.children.toString() !== vnode.children.toString()) {
|
|
884
|
+
old.dom.nodeValue = vnode.children
|
|
885
|
+
}
|
|
886
|
+
vnode.dom = old.dom
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function updateHTML(parent, old, vnode, ns, nextSibling) {
|
|
890
|
+
if (old.children !== vnode.children) {
|
|
891
|
+
removeDOM(parent, old)
|
|
892
|
+
createHTML(parent, vnode, ns, nextSibling)
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
vnode.dom = old.dom
|
|
896
|
+
vnode.domSize = old.domSize
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function updateFragment(parent, old, vnode, hooks, nextSibling, ns) {
|
|
901
|
+
updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns)
|
|
902
|
+
var domSize = 0, children = vnode.children
|
|
903
|
+
vnode.dom = null
|
|
904
|
+
if (children != null) {
|
|
905
|
+
for (var i = 0; i < children.length; i++) {
|
|
906
|
+
var child = children[i]
|
|
907
|
+
if (child != null && child.dom != null) {
|
|
908
|
+
if (vnode.dom == null) vnode.dom = child.dom
|
|
909
|
+
domSize += child.domSize || 1
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
vnode.domSize = domSize
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function updateElement(old, vnode, hooks, ns) {
|
|
917
|
+
var element = vnode.dom = old.dom
|
|
918
|
+
ns = getNameSpace(vnode) || ns
|
|
919
|
+
|
|
920
|
+
if (old.attrs != vnode.attrs || (vnode.attrs != null && !cachedAttrsIsStaticMap.get(vnode.attrs))) {
|
|
921
|
+
updateAttrs(vnode, old.attrs, vnode.attrs, ns)
|
|
922
|
+
}
|
|
923
|
+
if (!maybeSetContentEditable(vnode)) {
|
|
924
|
+
updateNodes(element, old.children, vnode.children, hooks, null, ns)
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function updateComponent(parent, old, vnode, hooks, nextSibling, ns) {
|
|
929
|
+
vnode.instance = Vnode.normalize(callHook.call(vnode.state.view, vnode))
|
|
930
|
+
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
|
|
931
|
+
updateLifecycle(vnode.state, vnode, hooks)
|
|
932
|
+
if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks)
|
|
933
|
+
if (vnode.instance != null) {
|
|
934
|
+
if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling)
|
|
935
|
+
else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, ns)
|
|
936
|
+
vnode.dom = vnode.instance.dom
|
|
937
|
+
vnode.domSize = vnode.instance.domSize
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
if (old.instance != null) removeNode(parent, old.instance)
|
|
941
|
+
vnode.domSize = 0
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function getKeyMap(vnodes, start, end) {
|
|
946
|
+
var map = Object.create(null)
|
|
947
|
+
for (; start < end; start++) {
|
|
948
|
+
var vnode = vnodes[start]
|
|
949
|
+
if (vnode != null) {
|
|
950
|
+
var key = vnode.key
|
|
951
|
+
if (key != null) map[key] = start
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return map
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Lifted from ivi https://github.com/ivijs/ivi/
|
|
958
|
+
// takes a list of unique numbers (-1 is special and can
|
|
959
|
+
// occur multiple times) and returns an array with the indices
|
|
960
|
+
// of the items that are part of the longest increasing
|
|
961
|
+
// subsequence
|
|
962
|
+
var lisTemp = []
|
|
963
|
+
function makeLisIndices(a) {
|
|
964
|
+
var result = [0]
|
|
965
|
+
var u = 0, v = 0, i = 0
|
|
966
|
+
var il = lisTemp.length = a.length
|
|
967
|
+
for (var i = 0; i < il; i++) lisTemp[i] = a[i]
|
|
968
|
+
for (var i = 0; i < il; ++i) {
|
|
969
|
+
if (a[i] === -1) continue
|
|
970
|
+
var j = result[result.length - 1]
|
|
971
|
+
if (a[j] < a[i]) {
|
|
972
|
+
lisTemp[i] = j
|
|
973
|
+
result.push(i)
|
|
974
|
+
continue
|
|
975
|
+
}
|
|
976
|
+
u = 0
|
|
977
|
+
v = result.length - 1
|
|
978
|
+
while (u < v) {
|
|
979
|
+
// Fast integer average without overflow.
|
|
980
|
+
// eslint-disable-next-line no-bitwise
|
|
981
|
+
var c = (u >>> 1) + (v >>> 1) + (u & v & 1)
|
|
982
|
+
if (a[result[c]] < a[i]) {
|
|
983
|
+
u = c + 1
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
v = c
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
if (a[i] < a[result[u]]) {
|
|
990
|
+
if (u > 0) lisTemp[i] = result[u - 1]
|
|
991
|
+
result[u] = i
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
u = result.length
|
|
995
|
+
v = result[u - 1]
|
|
996
|
+
while (u-- > 0) {
|
|
997
|
+
result[u] = v
|
|
998
|
+
v = lisTemp[v]
|
|
999
|
+
}
|
|
1000
|
+
lisTemp.length = 0
|
|
1001
|
+
return result
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function getNextSibling(vnodes, i, nextSibling) {
|
|
1005
|
+
for (; i < vnodes.length; i++) {
|
|
1006
|
+
if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom
|
|
1007
|
+
}
|
|
1008
|
+
return nextSibling
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// This handles fragments with zombie children (removed from vdom, but persisted in DOM through onbeforeremove)
|
|
1012
|
+
function moveDOM(parent, vnode, nextSibling) {
|
|
1013
|
+
if (vnode.dom != null) {
|
|
1014
|
+
if (vnode.domSize == null || vnode.domSize === 1) {
|
|
1015
|
+
// don't allocate for the common case
|
|
1016
|
+
insertDOM(parent, vnode.dom, nextSibling)
|
|
1017
|
+
} else {
|
|
1018
|
+
var doms = domFor(vnode)
|
|
1019
|
+
for (var i = 0; i < doms.length; i++) insertDOM(parent, doms[i], nextSibling)
|
|
1020
|
+
}
|
|
1021
|
+
maybeFlush()
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function insertDOM(parent, dom, nextSibling) {
|
|
1026
|
+
if (nextSibling != null) parent.insertBefore(dom, nextSibling)
|
|
1027
|
+
else parent.appendChild(dom)
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function maybeSetContentEditable(vnode) {
|
|
1031
|
+
if (vnode.attrs == null || (
|
|
1032
|
+
vnode.attrs.contenteditable == null && // attribute
|
|
1033
|
+
vnode.attrs.contentEditable == null // property
|
|
1034
|
+
)) return false
|
|
1035
|
+
var children = vnode.children
|
|
1036
|
+
if (children != null && children.length === 1 && children[0].tag === "<") {
|
|
1037
|
+
var content = children[0].children
|
|
1038
|
+
if (vnode.dom.innerHTML !== content) vnode.dom.innerHTML = content
|
|
1039
|
+
}
|
|
1040
|
+
else if (children != null && children.length !== 0) throw new Error("Child node of a contenteditable must be trusted.")
|
|
1041
|
+
return true
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
//remove
|
|
1045
|
+
function removeNodes(parent, vnodes, start, end) {
|
|
1046
|
+
for (var i = start; i < end; i++) {
|
|
1047
|
+
var vnode = vnodes[i]
|
|
1048
|
+
if (vnode != null) removeNode(parent, vnode)
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function tryBlockRemove(parent, vnode, source, counter) {
|
|
1053
|
+
var original = vnode.state
|
|
1054
|
+
var result = callHook.call(source.onbeforeremove, vnode)
|
|
1055
|
+
if (result == null) return
|
|
1056
|
+
|
|
1057
|
+
var generation = currentRender
|
|
1058
|
+
var doms = domFor(vnode)
|
|
1059
|
+
for (var i = 0; i < doms.length; i++) delayedRemoval.set(doms[i], generation)
|
|
1060
|
+
counter.v++
|
|
1061
|
+
|
|
1062
|
+
Promise.resolve(result).finally(function () {
|
|
1063
|
+
checkState(vnode, original)
|
|
1064
|
+
tryResumeRemove(parent, vnode, counter)
|
|
1065
|
+
})
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
function tryResumeRemove(parent, vnode, counter) {
|
|
1069
|
+
if (--counter.v === 0) {
|
|
1070
|
+
onremove(vnode)
|
|
1071
|
+
removeDOM(parent, vnode)
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function removeNode(parent, vnode) {
|
|
1076
|
+
var counter = {v: 1}
|
|
1077
|
+
if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeremove === "function") tryBlockRemove(parent, vnode, vnode.state, counter)
|
|
1078
|
+
if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") tryBlockRemove(parent, vnode, vnode.attrs, counter)
|
|
1079
|
+
tryResumeRemove(parent, vnode, counter)
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
function removeDOM(parent, vnode) {
|
|
1083
|
+
if (vnode.dom == null) return
|
|
1084
|
+
if (vnode.domSize == null || vnode.domSize === 1) {
|
|
1085
|
+
parent.removeChild(vnode.dom)
|
|
1086
|
+
} else {
|
|
1087
|
+
var doms = domFor(vnode)
|
|
1088
|
+
for (var i = 0; i < doms.length; i++) parent.removeChild(doms[i])
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function domFor(vnode) {
|
|
1093
|
+
var doms = []
|
|
1094
|
+
var dom = vnode.dom
|
|
1095
|
+
var domSize = vnode.domSize
|
|
1096
|
+
var generation = delayedRemoval.get(dom)
|
|
1097
|
+
if (dom != null) {
|
|
1098
|
+
do {
|
|
1099
|
+
var nextSibling = dom.nextSibling
|
|
1100
|
+
if (delayedRemoval.get(dom) === generation) {
|
|
1101
|
+
doms.push(dom)
|
|
1102
|
+
domSize--
|
|
1103
|
+
}
|
|
1104
|
+
dom = nextSibling
|
|
1105
|
+
} while (domSize)
|
|
1106
|
+
}
|
|
1107
|
+
return doms
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function onremove(vnode) {
|
|
1111
|
+
if (typeof vnode.tag !== "string" && typeof vnode.state.onremove === "function") callHook.call(vnode.state.onremove, vnode)
|
|
1112
|
+
if (vnode.attrs && typeof vnode.attrs.onremove === "function") callHook.call(vnode.attrs.onremove, vnode)
|
|
1113
|
+
if (typeof vnode.tag !== "string") {
|
|
1114
|
+
if (vnode.instance != null) onremove(vnode.instance)
|
|
1115
|
+
} else {
|
|
1116
|
+
if (vnode.events != null) vnode.events._ = null
|
|
1117
|
+
var children = vnode.children
|
|
1118
|
+
if (Array.isArray(children)) {
|
|
1119
|
+
for (var i = 0; i < children.length; i++) {
|
|
1120
|
+
var child = children[i]
|
|
1121
|
+
if (child != null) onremove(child)
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
//attrs
|
|
1128
|
+
function setAttrs(vnode, attrs, ns) {
|
|
1129
|
+
for (var key in attrs) {
|
|
1130
|
+
setAttr(vnode, key, null, attrs[key], ns)
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function setAttr(vnode, key, old, value, ns) {
|
|
1135
|
+
if (key === "key" || value == null || isLifecycleMethod(key) || (old === value && !isFormAttribute(vnode, key)) && typeof value !== "object") return
|
|
1136
|
+
if (key[0] === "o" && key[1] === "n") return updateEvent(vnode, key, value)
|
|
1137
|
+
if (key.slice(0, 6) === "xlink:") vnode.dom.setAttributeNS("http://www.w3.org/1999/xlink", key.slice(6), value)
|
|
1138
|
+
else if (key === "style") updateStyle(vnode.dom, old, value)
|
|
1139
|
+
else if (hasPropertyKey(vnode, key, ns)) {
|
|
1140
|
+
if (key === "value") {
|
|
1141
|
+
// Only do the coercion if we're actually going to check the value.
|
|
1142
|
+
/* eslint-disable no-implicit-coercion */
|
|
1143
|
+
//setting input[value] to same value by typing on focused element moves cursor to end in Chrome
|
|
1144
|
+
//setting input[type=file][value] to same value causes an error to be generated if it's non-empty
|
|
1145
|
+
//minlength/maxlength validation isn't performed on script-set values(#2256)
|
|
1146
|
+
if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === "" + value) return
|
|
1147
|
+
//setting select[value] to same value while having select open blinks select dropdown in Chrome
|
|
1148
|
+
if (vnode.tag === "select" && old !== null && vnode.dom.value === "" + value) return
|
|
1149
|
+
//setting option[value] to same value while having select open blinks select dropdown in Chrome
|
|
1150
|
+
if (vnode.tag === "option" && old !== null && vnode.dom.value === "" + value) return
|
|
1151
|
+
//setting input[type=file][value] to different value is an error if it's non-empty
|
|
1152
|
+
// Not ideal, but it at least works around the most common source of uncaught exceptions for now.
|
|
1153
|
+
if (vnode.tag === "input" && vnode.attrs.type === "file" && "" + value !== "") { console.error("`value` is read-only on file inputs!"); return }
|
|
1154
|
+
/* eslint-enable no-implicit-coercion */
|
|
1155
|
+
}
|
|
1156
|
+
// If you assign an input type that is not supported by IE 11 with an assignment expression, an error will occur.
|
|
1157
|
+
if (vnode.tag === "input" && key === "type") vnode.dom.setAttribute(key, value)
|
|
1158
|
+
else vnode.dom[key] = value
|
|
1159
|
+
} else {
|
|
1160
|
+
if (typeof value === "boolean") {
|
|
1161
|
+
if (value) vnode.dom.setAttribute(key, "")
|
|
1162
|
+
else vnode.dom.removeAttribute(key)
|
|
1163
|
+
}
|
|
1164
|
+
else vnode.dom.setAttribute(key === "className" ? "class" : key, value)
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function removeAttr(vnode, key, old, ns) {
|
|
1169
|
+
if (key === "key" || old == null || isLifecycleMethod(key)) return
|
|
1170
|
+
if (key[0] === "o" && key[1] === "n") updateEvent(vnode, key, undefined)
|
|
1171
|
+
else if (key === "style") updateStyle(vnode.dom, old, null)
|
|
1172
|
+
else if (
|
|
1173
|
+
hasPropertyKey(vnode, key, ns)
|
|
1174
|
+
&& key !== "className"
|
|
1175
|
+
&& key !== "title" // creates "null" as title
|
|
1176
|
+
&& !(key === "value" && (
|
|
1177
|
+
vnode.tag === "option"
|
|
1178
|
+
|| vnode.tag === "select" && vnode.dom.selectedIndex === -1 && vnode.dom === activeElement(vnode.dom)
|
|
1179
|
+
))
|
|
1180
|
+
&& !(vnode.tag === "input" && key === "type")
|
|
1181
|
+
) {
|
|
1182
|
+
vnode.dom[key] = null
|
|
1183
|
+
} else {
|
|
1184
|
+
var nsLastIndex = key.indexOf(":")
|
|
1185
|
+
if (nsLastIndex !== -1) key = key.slice(nsLastIndex + 1)
|
|
1186
|
+
if (old !== false) vnode.dom.removeAttribute(key === "className" ? "class" : key)
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function setLateSelectAttrs(vnode, attrs) {
|
|
1191
|
+
if ("value" in attrs) {
|
|
1192
|
+
if (attrs.value === null) {
|
|
1193
|
+
if (vnode.dom.selectedIndex !== -1) vnode.dom.value = null
|
|
1194
|
+
} else {
|
|
1195
|
+
var normalized = "" + attrs.value // eslint-disable-line no-implicit-coercion
|
|
1196
|
+
if (vnode.dom.value !== normalized || vnode.dom.selectedIndex === -1) {
|
|
1197
|
+
vnode.dom.value = normalized
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
if ("selectedIndex" in attrs) setAttr(vnode, "selectedIndex", null, attrs.selectedIndex, undefined)
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function updateAttrs(vnode, old, attrs, ns) {
|
|
1205
|
+
// Some attributes may NOT be case-sensitive (e.g. data-***),
|
|
1206
|
+
// so removal should be done first to prevent accidental removal for newly setting values.
|
|
1207
|
+
var val
|
|
1208
|
+
if (old != null) {
|
|
1209
|
+
if (old === attrs && !cachedAttrsIsStaticMap.has(attrs)) {
|
|
1210
|
+
console.warn("Don't reuse attrs object, use new object for every redraw, this will throw in next major")
|
|
1211
|
+
}
|
|
1212
|
+
for (var key in old) {
|
|
1213
|
+
if (((val = old[key]) != null) && (attrs == null || attrs[key] == null)) {
|
|
1214
|
+
removeAttr(vnode, key, val, ns)
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (attrs != null) {
|
|
1219
|
+
for (var key in attrs) {
|
|
1220
|
+
setAttr(vnode, key, old && old[key], attrs[key], ns)
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function isFormAttribute(vnode, attr) {
|
|
1226
|
+
return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && (vnode.dom === activeElement(vnode.dom) || vnode.tag === "option" && vnode.dom.parentNode === activeElement(vnode.dom))
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function hasPropertyKey(vnode, key, ns) {
|
|
1230
|
+
// Filter out namespaced keys
|
|
1231
|
+
return ns === undefined && (
|
|
1232
|
+
// If it's a custom element, just keep it.
|
|
1233
|
+
vnode.tag.indexOf("-") > -1 || vnode.is ||
|
|
1234
|
+
// If it's a normal element, let's try to avoid a few browser bugs.
|
|
1235
|
+
key !== "href" && key !== "list" && key !== "form" && key !== "width" && key !== "height"// && key !== "type"
|
|
1236
|
+
// Defer the property check until *after* we check everything.
|
|
1237
|
+
) && key in vnode.dom
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
//style
|
|
1241
|
+
function updateStyle(element, old, style) {
|
|
1242
|
+
if (old === style) {
|
|
1243
|
+
// Styles are equivalent, do nothing.
|
|
1244
|
+
} else if (style == null) {
|
|
1245
|
+
// New style is missing, just clear it.
|
|
1246
|
+
element.style = ""
|
|
1247
|
+
} else if (typeof style !== "object") {
|
|
1248
|
+
// New style is a string, let engine deal with patching.
|
|
1249
|
+
element.style = style
|
|
1250
|
+
} else if (old == null || typeof old !== "object") {
|
|
1251
|
+
// `old` is missing or a string, `style` is an object.
|
|
1252
|
+
element.style = ""
|
|
1253
|
+
// Add new style properties
|
|
1254
|
+
for (var key in style) {
|
|
1255
|
+
var value = style[key]
|
|
1256
|
+
if (value != null) {
|
|
1257
|
+
if (key.includes("-")) element.style.setProperty(key, String(value))
|
|
1258
|
+
else element.style[key] = String(value)
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
} else {
|
|
1262
|
+
// Both old & new are (different) objects.
|
|
1263
|
+
// Remove style properties that no longer exist
|
|
1264
|
+
// Style properties may have two cases(dash-case and camelCase),
|
|
1265
|
+
// so removal should be done first to prevent accidental removal for newly setting values.
|
|
1266
|
+
for (var key in old) {
|
|
1267
|
+
if (old[key] != null && style[key] == null) {
|
|
1268
|
+
if (key.includes("-")) element.style.removeProperty(key)
|
|
1269
|
+
else element.style[key] = ""
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
// Update style properties that have changed
|
|
1273
|
+
for (var key in style) {
|
|
1274
|
+
var value = style[key]
|
|
1275
|
+
if (value != null && (value = String(value)) !== String(old[key])) {
|
|
1276
|
+
if (key.includes("-")) element.style.setProperty(key, value)
|
|
1277
|
+
else element.style[key] = value
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Here's an explanation of how this works:
|
|
1284
|
+
// 1. The event names are always (by design) prefixed by `on`.
|
|
1285
|
+
// 2. The EventListener interface accepts either a function or an object
|
|
1286
|
+
// with a `handleEvent` method.
|
|
1287
|
+
// 3. The object does not inherit from `Object.prototype`, to avoid
|
|
1288
|
+
// any potential interference with that (e.g. setters).
|
|
1289
|
+
// 4. The event name is remapped to the handler before calling it.
|
|
1290
|
+
// 5. In function-based event handlers, `ev.target === this`. We replicate
|
|
1291
|
+
// that below.
|
|
1292
|
+
// 6. In function-based event handlers, `return false` prevents the default
|
|
1293
|
+
// action and stops event propagation. We replicate that below.
|
|
1294
|
+
function EventDict() {
|
|
1295
|
+
// Save this, so the current redraw is correctly tracked.
|
|
1296
|
+
this._ = currentRedraw
|
|
1297
|
+
}
|
|
1298
|
+
EventDict.prototype = Object.create(null)
|
|
1299
|
+
EventDict.prototype.handleEvent = function (ev) {
|
|
1300
|
+
var handler = this["on" + ev.type]
|
|
1301
|
+
var result
|
|
1302
|
+
if (typeof handler === "function") result = handler.call(ev.currentTarget, ev)
|
|
1303
|
+
else if (typeof handler.handleEvent === "function") handler.handleEvent(ev)
|
|
1304
|
+
var self = this
|
|
1305
|
+
if (self._ != null) {
|
|
1306
|
+
if (ev.redraw !== false) (0, self._)()
|
|
1307
|
+
if (result != null && typeof result.then === "function") {
|
|
1308
|
+
Promise.resolve(result).then(function () {
|
|
1309
|
+
if (self._ != null && ev.redraw !== false) (0, self._)()
|
|
1310
|
+
})
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
if (result === false) {
|
|
1314
|
+
ev.preventDefault()
|
|
1315
|
+
ev.stopPropagation()
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
//event
|
|
1320
|
+
function updateEvent(vnode, key, value) {
|
|
1321
|
+
if (vnode.events != null) {
|
|
1322
|
+
vnode.events._ = currentRedraw
|
|
1323
|
+
if (vnode.events[key] === value) return
|
|
1324
|
+
if (value != null && (typeof value === "function" || typeof value === "object")) {
|
|
1325
|
+
if (vnode.events[key] == null) vnode.dom.addEventListener(key.slice(2), vnode.events, false)
|
|
1326
|
+
vnode.events[key] = value
|
|
1327
|
+
} else {
|
|
1328
|
+
if (vnode.events[key] != null) vnode.dom.removeEventListener(key.slice(2), vnode.events, false)
|
|
1329
|
+
vnode.events[key] = undefined
|
|
1330
|
+
}
|
|
1331
|
+
} else if (value != null && (typeof value === "function" || typeof value === "object")) {
|
|
1332
|
+
vnode.events = new EventDict()
|
|
1333
|
+
vnode.dom.addEventListener(key.slice(2), vnode.events, false)
|
|
1334
|
+
vnode.events[key] = value
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
//lifecycle
|
|
1339
|
+
function initLifecycle(source, vnode, hooks) {
|
|
1340
|
+
if (typeof source.oninit === "function") callHook.call(source.oninit, vnode)
|
|
1341
|
+
if (typeof source.oncreate === "function") hooks.push(callHook.bind(source.oncreate, vnode))
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function updateLifecycle(source, vnode, hooks) {
|
|
1345
|
+
if (typeof source.onupdate === "function") hooks.push(callHook.bind(source.onupdate, vnode))
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
function shouldNotUpdate(vnode, old) {
|
|
1349
|
+
do {
|
|
1350
|
+
if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") {
|
|
1351
|
+
var force = callHook.call(vnode.attrs.onbeforeupdate, vnode, old)
|
|
1352
|
+
if (force !== undefined && !force) break
|
|
1353
|
+
}
|
|
1354
|
+
if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeupdate === "function") {
|
|
1355
|
+
var force = callHook.call(vnode.state.onbeforeupdate, vnode, old)
|
|
1356
|
+
if (force !== undefined && !force) break
|
|
1357
|
+
}
|
|
1358
|
+
return false
|
|
1359
|
+
} while (false); // eslint-disable-line no-constant-condition
|
|
1360
|
+
vnode.dom = old.dom
|
|
1361
|
+
vnode.domSize = old.domSize
|
|
1362
|
+
vnode.instance = old.instance
|
|
1363
|
+
vnode.attrs = old.attrs
|
|
1364
|
+
vnode.children = old.children
|
|
1365
|
+
vnode.text = old.text
|
|
1366
|
+
return true
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
return function (dom, vnodes, redraw) {
|
|
1370
|
+
if (!dom) throw new TypeError("DOM element being rendered to does not exist.")
|
|
1371
|
+
if (currentDOM != null && dom.contains(currentDOM)) {
|
|
1372
|
+
throw new TypeError("Node is currently being rendered to and thus is locked.")
|
|
1373
|
+
}
|
|
1374
|
+
var prevRedraw = currentRedraw
|
|
1375
|
+
var prevDOM = currentDOM
|
|
1376
|
+
var hooks = []
|
|
1377
|
+
var active = activeElement(dom)
|
|
1378
|
+
var namespace = dom.namespaceURI
|
|
1379
|
+
|
|
1380
|
+
currentDOM = dom
|
|
1381
|
+
currentRedraw = typeof redraw === "function" ? redraw : undefined
|
|
1382
|
+
currentRender = {}
|
|
1383
|
+
renderDepth++
|
|
1384
|
+
try {
|
|
1385
|
+
// First time rendering into a node clears it out
|
|
1386
|
+
if (dom.vnodes == null) dom.textContent = ""
|
|
1387
|
+
vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes])
|
|
1388
|
+
updateNodes(dom, dom.vnodes, vnodes, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace)
|
|
1389
|
+
dom.vnodes = vnodes
|
|
1390
|
+
// `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement
|
|
1391
|
+
if (active != null && activeElement(dom) !== active && typeof active.focus === "function") active.focus()
|
|
1392
|
+
for (var i = 0; i < hooks.length; i++) hooks[i]()
|
|
1393
|
+
} finally {
|
|
1394
|
+
renderDepth--
|
|
1395
|
+
currentRedraw = prevRedraw
|
|
1396
|
+
currentDOM = prevDOM
|
|
1397
|
+
}
|
|
1398
|
+
// Flush hook: after each mithril redraw pass, push the tree to Lynx.
|
|
1399
|
+
flushTree()
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// ============================================================
|
|
1404
|
+
// 8. Convenience API
|
|
1405
|
+
// ============================================================
|
|
1406
|
+
|
|
1407
|
+
var mithrilRender = factory()
|
|
1408
|
+
var rootWrapper = null
|
|
1409
|
+
var rootComponent = null
|
|
1410
|
+
var currentVnode = null
|
|
1411
|
+
var runRender = null
|
|
1412
|
+
var redraw = null
|
|
1413
|
+
|
|
1414
|
+
function flush() {
|
|
1415
|
+
if (runRender != null && rootWrapper != null) {
|
|
1416
|
+
if (rootComponent != null) currentVnode = Vnode(rootComponent)
|
|
1417
|
+
runRender(rootWrapper, currentVnode, redraw)
|
|
1418
|
+
} else {
|
|
1419
|
+
flushTree()
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// Initial render. Re-render ONLY via mithril's own render/redraw
|
|
1424
|
+
// (shim.redraw() / m.redraw()), never by calling this again manually.
|
|
1425
|
+
function render(rootWrapperArg, vnode) {
|
|
1426
|
+
rootWrapper = rootWrapperArg
|
|
1427
|
+
rootComponent = vnode != null && typeof vnode.tag !== "string" ? vnode.tag : null
|
|
1428
|
+
currentVnode = vnode
|
|
1429
|
+
redraw = function () { flush() }
|
|
1430
|
+
// runRender must hold the render FUNCTION itself (mithrilRender), not the
|
|
1431
|
+
// result of calling it (which is undefined) — flush() re-invokes it on redraw.
|
|
1432
|
+
runRender = mithrilRender
|
|
1433
|
+
runRender(rootWrapper, currentVnode, redraw)
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function redrawNow() {
|
|
1437
|
+
if (redraw != null) redraw()
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
function createPageWrapper(pageElement) {
|
|
1441
|
+
var pageId = __GetElementUniqueID(pageElement)
|
|
1442
|
+
var document = createFakeDocument(pageId)
|
|
1443
|
+
var wrapper = wrapperFor(pageElement)
|
|
1444
|
+
wrapper._document = document
|
|
1445
|
+
wrapper._pageId = pageId
|
|
1446
|
+
wrapper._tag = "page"
|
|
1447
|
+
return wrapper
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
function renderToPage(pageElement, vnode) {
|
|
1451
|
+
var wrapper = createPageWrapper(pageElement)
|
|
1452
|
+
render(wrapper, vnode)
|
|
1453
|
+
return wrapper
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// Legacy compatibility with the old shim's API surface.
|
|
1457
|
+
function createLynxWindow(pageElement) {
|
|
1458
|
+
var wrapper = pageElement != null ? createPageWrapper(pageElement) : null
|
|
1459
|
+
return {
|
|
1460
|
+
document: wrapper != null ? wrapper.ownerDocument : getDefaultDocument(),
|
|
1461
|
+
__root: wrapper
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// ============================================================
|
|
1466
|
+
// 9. Exports
|
|
1467
|
+
// ============================================================
|
|
1468
|
+
|
|
1469
|
+
module.exports = factory
|
|
1470
|
+
module.exports.render = render
|
|
1471
|
+
module.exports.redraw = redrawNow
|
|
1472
|
+
module.exports.createPageWrapper = createPageWrapper
|
|
1473
|
+
module.exports.renderToPage = renderToPage
|
|
1474
|
+
module.exports.createLynxWindow = createLynxWindow
|
|
1475
|
+
module.exports.LynxNodeWrapper = LynxNodeWrapper
|
|
1476
|
+
module.exports.LynxStyleProxy = LynxStyleProxy
|
|
1477
|
+
module.exports.normalizeEvent = normalizeEvent
|