mithril-lynx 0.0.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.omo/plans/m-request-fetch-lynx.md +306 -0
  2. package/.omo/plans/m-route-en-memoria.md +397 -0
  3. package/.omo/plans/mithril-lynx-v2-desde-cero.md +548 -0
  4. package/FETCH_INVESTIGATION.md +307 -0
  5. package/README.md +32 -302
  6. package/REQUEST.md +71 -0
  7. package/ROUTE.md +71 -0
  8. package/package.json +24 -80
  9. package/plugin.d.ts +4 -33
  10. package/plugin.js +108 -438
  11. package/rstest.config.ts +27 -0
  12. package/src/apply-patch.js +179 -0
  13. package/src/backends/virtual-backend.js +80 -0
  14. package/src/background.d.ts +11 -0
  15. package/src/background.js +79 -0
  16. package/src/channel.js +41 -0
  17. package/src/commit.js +67 -0
  18. package/src/dev-reload-client.js +171 -187
  19. package/src/dev-transport-noop.js +10 -0
  20. package/src/fake-dom.js +374 -0
  21. package/src/main-thread.d.ts +1 -0
  22. package/src/main-thread.js +68 -0
  23. package/src/mount-redraw.js +67 -0
  24. package/src/patch-protocol.js +40 -0
  25. package/src/reload/version.js +28 -0
  26. package/src/request.d.ts +37 -0
  27. package/src/request.js +181 -0
  28. package/src/route.d.ts +33 -0
  29. package/src/route.js +207 -0
  30. package/test/end-to-end.test.ts +86 -0
  31. package/test/reload-version.test.ts +17 -0
  32. package/test/request.test.ts +182 -0
  33. package/test/route-hot-reload.test.ts +40 -0
  34. package/test/route.test.ts +152 -0
  35. package/test/setup.ts +25 -0
  36. package/test/structural-reload.test.ts +95 -0
  37. package/CONTRACT.md +0 -151
  38. package/LICENSE +0 -21
  39. package/background.d.ts +0 -54
  40. package/background.js +0 -169
  41. package/element.d.ts +0 -34
  42. package/element.js +0 -83
  43. package/gesture.d.ts +0 -40
  44. package/gesture.js +0 -117
  45. package/internal/constants.js +0 -26
  46. package/internal/virtual-node.js +0 -388
  47. package/list.d.ts +0 -31
  48. package/list.js +0 -185
  49. package/main-thread.d.ts +0 -43
  50. package/main-thread.js +0 -165
  51. package/navigation.d.ts +0 -35
  52. package/navigation.js +0 -76
  53. package/renderer/background.d.ts +0 -21
  54. package/renderer/background.js +0 -84
  55. package/renderer/main-thread.d.ts +0 -12
  56. package/renderer/main-thread.js +0 -175
  57. package/src/lynx-mithril-shim.d.ts +0 -16
  58. package/src/lynx-mithril-shim.js +0 -1505
  59. package/src/worklet-runtime.js +0 -82
  60. package/testing.d.ts +0 -10
  61. package/testing.js +0 -91
@@ -1,1505 +0,0 @@
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
- // "" (not undefined) when clearing: real hardware's FiberSetClasses
357
- // rejects a non-string argument outright ("FiberSetClasses param 1
358
- // should be String"), confirmed on device — a genuinely native-only
359
- // validation this shim's own jsdom-backed test mock never catches,
360
- // since ElementPAPI's __SetClasses(e, cls) is just `e.className = cls`
361
- // and happily accepts undefined. Found via mithril-lynx-ui's FeedList
362
- // (a native-list cell recycled between content that has a "class"
363
- // attr and content that doesn't).
364
- __SetClasses(this._handle, value == null ? "" : String(value))
365
- } else if (key === "id") {
366
- __SetID(this._handle, value == null ? null : String(value))
367
- } else if (key === "checked") {
368
- __SetAttribute(this._handle, key, value == null ? null : value)
369
- } else {
370
- __SetAttribute(this._handle, key, value == null ? null : String(value))
371
- }
372
- }
373
-
374
- LynxNodeWrapper.prototype.setAttribute = function (key, value) {
375
- if (key === "class") {
376
- // See _setDirectProp's own comment above: "" not undefined.
377
- __SetClasses(this._handle, value == null ? "" : String(value))
378
- } else if (key === "id") {
379
- __SetID(this._handle, value == null ? null : String(value))
380
- } else if (key.slice(0, 5) === "data-") {
381
- __AddDataset(this._handle, key.slice(5), value)
382
- } else {
383
- __SetAttribute(this._handle, key, value == null ? null : String(value))
384
- }
385
- }
386
-
387
- LynxNodeWrapper.prototype.removeAttribute = function (key) {
388
- if (key === "class") {
389
- // See _setDirectProp's own comment above: "" not undefined.
390
- __SetClasses(this._handle, "")
391
- } else if (key === "id") {
392
- __SetID(this._handle, null)
393
- } else if (key.slice(0, 5) === "data-") {
394
- __AddDataset(this._handle, key.slice(5), null)
395
- } else {
396
- __SetAttribute(this._handle, key, null)
397
- }
398
- }
399
-
400
- LynxNodeWrapper.prototype.setAttributeNS = function (ns, key, value) {
401
- this.setAttribute(key, value)
402
- }
403
-
404
- LynxNodeWrapper.prototype.appendChild = function (child) {
405
- if (child == null) return child
406
- if (child.nodeType === 11) return child // inert fragment: children already placed by the engine
407
- __AppendElement(this._handle, child._handle)
408
- maybeFlush()
409
- return child
410
- }
411
-
412
- LynxNodeWrapper.prototype.insertBefore = function (child, ref) {
413
- if (child == null) return child
414
- if (child.nodeType === 11) return child
415
- if (ref == null) {
416
- __AppendElement(this._handle, child._handle)
417
- } else {
418
- __InsertElementBefore(this._handle, child._handle, ref._handle)
419
- }
420
- maybeFlush()
421
- return child
422
- }
423
-
424
- LynxNodeWrapper.prototype.removeChild = function (child) {
425
- if (child == null) return child
426
- if (child.nodeType === 11) return child
427
- __RemoveElement(this._handle, child._handle)
428
- maybeFlush()
429
- return child
430
- }
431
-
432
- LynxNodeWrapper.prototype.contains = function (other) {
433
- if (other == null) return false
434
- if (other === this) return true
435
- if (other._handle == null) return false
436
- var p = __GetParent(other._handle)
437
- while (p != null) {
438
- if (p === this._handle) return true
439
- p = __GetParent(p)
440
- }
441
- return false
442
- }
443
-
444
- LynxNodeWrapper.prototype.focus = function () {}
445
-
446
- // Events: per-type registry of {listener, wrapped}. The wrapped handler is the
447
- // real JS function passed to __AddEventListener; it normalizes the Lynx event
448
- // and dispatches to the mithril EventDict (or a plain function).
449
- LynxNodeWrapper.prototype.addEventListener = function (type, listener, opts) {
450
- var entry = this._listeners[type]
451
- if (entry != null && entry.listener === listener) return
452
- if (entry != null) {
453
- __RemoveEventListener(this._handle, type, entry.wrapped, entry.opts || {})
454
- }
455
- var self = this
456
- var wrapped = function (rawEv) {
457
- var ev = normalizeEvent(rawEv, self)
458
- if (typeof listener === "function") listener.call(ev.currentTarget, ev)
459
- else if (listener != null && typeof listener.handleEvent === "function") listener.handleEvent(ev)
460
- }
461
- this._listeners[type] = { listener: listener, wrapped: wrapped, opts: opts || {} }
462
- __AddEventListener(this._handle, type, wrapped, opts || {})
463
- }
464
-
465
- LynxNodeWrapper.prototype.removeEventListener = function (type, listener, opts) {
466
- var entry = this._listeners[type]
467
- if (entry == null) return
468
- if (listener != null && entry.listener !== listener) return
469
- delete this._listeners[type]
470
- __RemoveEventListener(this._handle, type, entry.wrapped, entry.opts || {})
471
- }
472
-
473
- // Normalize a Lynx event into a DOM-like event object. `redraw: false` disables
474
- // mithril's automatic redraw-on-event; the demo must call shim.redraw() (or
475
- // m.redraw()) explicitly.
476
- function normalizeEvent(rawEv, node) {
477
- var ev = {
478
- type: rawEv != null && rawEv.type != null ? rawEv.type : "tap",
479
- currentTarget: node,
480
- redraw: false,
481
- preventDefault: function () {},
482
- stopPropagation: function () {}
483
- }
484
- if (rawEv != null) {
485
- for (var k in rawEv) {
486
- if (ev[k] === undefined) ev[k] = rawEv[k]
487
- }
488
- }
489
- return ev
490
- }
491
-
492
- // ============================================================
493
- // 6. fakeDocument + fragment
494
- // ============================================================
495
-
496
- var defaultDocument = null
497
-
498
- function getDefaultDocument() {
499
- if (defaultDocument == null) defaultDocument = createFakeDocument(0)
500
- return defaultDocument
501
- }
502
-
503
- function createRawTextNode(value) {
504
- var handle = __CreateRawText(String(value))
505
- var wrapper = wrapperFor(handle)
506
- wrapper._isRawText = true
507
- wrapper._text = String(value)
508
- return wrapper
509
- }
510
-
511
- function createElementWrapper(tag, pageId) {
512
- var handle
513
- if (tag === "view") handle = __CreateView(pageId)
514
- else if (tag === "text") handle = __CreateText(pageId)
515
- else handle = __CreateElement(tag, pageId, {})
516
- var wrapper = wrapperFor(handle)
517
- wrapper._tag = tag
518
- return wrapper
519
- }
520
-
521
- function createFakeDocument(pageId) {
522
- var document = {
523
- _pageId: pageId,
524
- createElement: function (tag, opts) {
525
- var wrapper = createElementWrapper(tag, pageId)
526
- wrapper._document = document
527
- return wrapper
528
- },
529
- createElementNS: function (ns, tag, opts) {
530
- return document.createElement(tag, opts)
531
- },
532
- createTextNode: function (value) {
533
- var wrapper = createRawTextNode(value)
534
- wrapper._document = document
535
- return wrapper
536
- },
537
- createDocumentFragment: function () {
538
- return createFragment(document)
539
- }
540
- }
541
- Object.defineProperty(document, "activeElement", {
542
- get: function () { return null }
543
- })
544
- return document
545
- }
546
-
547
- // Inert fragment: never __AppendElement'd. Children are created directly in the
548
- // real parent by the engine (see createFragment below).
549
- function createFragment(document) {
550
- return {
551
- nodeType: 11,
552
- ownerDocument: document,
553
- _parent: null,
554
- appendChild: function () {},
555
- insertBefore: function () {},
556
- removeChild: function () {}
557
- }
558
- }
559
-
560
- function getPreviousSibling(parentHandle, nodeHandle) {
561
- var child = __FirstElement(parentHandle)
562
- while (child != null) {
563
- var next = __NextElement(child)
564
- if (next === nodeHandle) return child
565
- child = next
566
- }
567
- return null
568
- }
569
-
570
- function getLastChild(parentHandle) {
571
- var child = __FirstElement(parentHandle)
572
- var last = null
573
- while (child != null) {
574
- last = child
575
- child = __NextElement(child)
576
- }
577
- return last
578
- }
579
-
580
- // ============================================================
581
- // 7. Render engine (port of mithril/render/render.js@2.3.8)
582
- // ============================================================
583
-
584
- function factory() {
585
- var nameSpace = {
586
- svg: "http://www.w3.org/2000/svg",
587
- math: "http://www.w3.org/1998/Math/MathML"
588
- }
589
-
590
- var currentRedraw
591
- var currentRender
592
- var currentDOM
593
-
594
- function getDocument(dom) {
595
- return dom.ownerDocument
596
- }
597
-
598
- function getNameSpace(vnode) {
599
- return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag]
600
- }
601
-
602
- //sanity check to discourage people from doing `vnode.state = ...`
603
- function checkState(vnode, original) {
604
- if (vnode.state !== original) throw new Error("'vnode.state' must not be modified.")
605
- }
606
-
607
- //Note: the hook is passed as the `this` argument to allow proxying the
608
- //arguments without requiring a full array allocation to do so. It also
609
- //takes advantage of the fact the current `vnode` is the first argument in
610
- //all lifecycle methods.
611
- function callHook(vnode) {
612
- var original = vnode.state
613
- try {
614
- return this.apply(original, arguments)
615
- } finally {
616
- checkState(vnode, original)
617
- }
618
- }
619
-
620
- function activeElement(dom) {
621
- try {
622
- return getDocument(dom).activeElement
623
- } catch (e) {
624
- return null
625
- }
626
- }
627
-
628
- //create
629
- function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
630
- for (var i = start; i < end; i++) {
631
- var vnode = vnodes[i]
632
- if (vnode != null) {
633
- createNode(parent, vnode, hooks, ns, nextSibling)
634
- }
635
- }
636
- }
637
-
638
- function createNode(parent, vnode, hooks, ns, nextSibling) {
639
- var tag = vnode.tag
640
- if (typeof tag === "string") {
641
- vnode.state = {}
642
- if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
643
- switch (tag) {
644
- case "#": createText(parent, vnode, nextSibling); break
645
- case "<": createHTML(parent, vnode, ns, nextSibling); break
646
- case "[": createFragment(parent, vnode, hooks, ns, nextSibling); break
647
- default: createElement(parent, vnode, hooks, ns, nextSibling)
648
- }
649
- }
650
- else createComponent(parent, vnode, hooks, ns, nextSibling)
651
- }
652
-
653
- function createText(parent, vnode, nextSibling) {
654
- vnode.dom = getDocument(parent).createTextNode(vnode.children)
655
- insertDOM(parent, vnode.dom, nextSibling)
656
- }
657
-
658
- function createHTML(parent, vnode, ns, nextSibling) {
659
- throw new Error("m.trust / innerHTML is not supported by the Lynx shim (v1).")
660
- }
661
-
662
- function createFragment(parent, vnode, hooks, ns, nextSibling) {
663
- // Inert fragment: children are created directly in the real parent.
664
- // The fragment itself is never __AppendElement'd.
665
- if (vnode.children != null) {
666
- var children = vnode.children
667
- var before = nextSibling != null
668
- ? getPreviousSibling(parent._handle, nextSibling._handle)
669
- : getLastChild(parent._handle)
670
- createNodes(parent, children, 0, children.length, hooks, nextSibling, ns)
671
- var firstHandle = before != null ? __NextElement(before) : __FirstElement(parent._handle)
672
- vnode.dom = firstHandle != null ? wrapperFor(firstHandle) : null
673
- vnode.domSize = children.length
674
- } else {
675
- vnode.dom = null
676
- vnode.domSize = 0
677
- }
678
- }
679
-
680
- function createElement(parent, vnode, hooks, ns, nextSibling) {
681
- var tag = vnode.tag
682
- var attrs = vnode.attrs
683
- var is = vnode.is
684
-
685
- ns = getNameSpace(vnode) || ns
686
-
687
- var element = ns ?
688
- is ? getDocument(parent).createElementNS(ns, tag, {is: is}) : getDocument(parent).createElementNS(ns, tag) :
689
- is ? getDocument(parent).createElement(tag, {is: is}) : getDocument(parent).createElement(tag)
690
- vnode.dom = element
691
-
692
- if (attrs != null) {
693
- setAttrs(vnode, attrs, ns)
694
- }
695
-
696
- insertDOM(parent, element, nextSibling)
697
-
698
- if (!maybeSetContentEditable(vnode)) {
699
- if (vnode.children != null) {
700
- var children = vnode.children
701
- createNodes(element, children, 0, children.length, hooks, null, ns)
702
- if (vnode.tag === "select" && attrs != null) setLateSelectAttrs(vnode, attrs)
703
- }
704
- }
705
- }
706
-
707
- function initComponent(vnode, hooks) {
708
- var sentinel
709
- var classStyle = typeof vnode.tag.view !== "function"
710
- if (!classStyle) {
711
- vnode.state = Object.create(vnode.tag)
712
- sentinel = vnode.state.view
713
- } else {
714
- sentinel = vnode.tag
715
- }
716
- if (sentinel.$$reentrantLock$$ != null) return
717
- sentinel.$$reentrantLock$$ = true
718
- // The lock above is stored on `sentinel` — the component's OWN shared
719
- // view function (or, for class-style components, the tag itself), NOT
720
- // on this vnode instance — so it's shared by EVERY use of this
721
- // component anywhere in the app. Without the try/finally below, a
722
- // view() (or, for a class-style component, its CONSTRUCTOR) that
723
- // throws on a component's first render (e.g. a fail-loudly validation
724
- // error) skips the `sentinel.$$reentrantLock$$ = null` reset entirely,
725
- // leaving that lock stuck at `true` forever. Every later attempt to
726
- // create that SAME component type — anywhere, not just the one
727
- // instance that threw — then silently no-ops right here
728
- // (`if (sentinel.$$reentrantLock$$ != null) return`): no render, no
729
- // error, nothing. Found via mithril-lynx-ui's own test suite: a
730
- // component whose view() deliberately throws to validate a prop,
731
- // exercised in one test, made every SUBSEQUENT test mounting that
732
- // same component silently render nothing, no matter how unrelated.
733
- try {
734
- if (classStyle) {
735
- vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode)
736
- }
737
- initLifecycle(vnode.state, vnode, hooks)
738
- if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
739
- vnode.instance = Vnode.normalize(callHook.call(vnode.state.view, vnode))
740
- if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
741
- } finally {
742
- sentinel.$$reentrantLock$$ = null
743
- }
744
- }
745
-
746
- function createComponent(parent, vnode, hooks, ns, nextSibling) {
747
- initComponent(vnode, hooks)
748
- if (vnode.instance != null) {
749
- createNode(parent, vnode.instance, hooks, ns, nextSibling)
750
- vnode.dom = vnode.instance.dom
751
- vnode.domSize = vnode.instance.domSize
752
- }
753
- else {
754
- vnode.domSize = 0
755
- }
756
- }
757
-
758
- //update
759
- function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
760
- var o, v
761
- if (old === vnodes || old == null && vnodes == null) return
762
- else if (old == null || old.length === 0) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns)
763
- else if (vnodes == null || vnodes.length === 0) removeNodes(parent, old, 0, old.length)
764
- else {
765
- var isOldKeyed = old[0] != null && old[0].key != null
766
- var isKeyed = vnodes[0] != null && vnodes[0].key != null
767
- var start = 0, oldStart = 0
768
- if (!isOldKeyed) while (oldStart < old.length && old[oldStart] == null) oldStart++
769
- if (!isKeyed) while (start < vnodes.length && vnodes[start] == null) start++
770
- if (isOldKeyed !== isKeyed) {
771
- removeNodes(parent, old, oldStart, old.length)
772
- createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
773
- } else if (!isKeyed) {
774
- // Don't index past the end of either list (causes deopts).
775
- var commonLength = old.length < vnodes.length ? old.length : vnodes.length
776
- // Rewind if necessary to the first non-null index on either side.
777
- start = start < oldStart ? start : oldStart
778
- for (; start < commonLength; start++) {
779
- o = old[start]
780
- v = vnodes[start]
781
- if (o === v || o == null && v == null) continue
782
- else if (o == null) createNode(parent, v, hooks, ns, getNextSibling(old, start + 1, nextSibling))
783
- else if (v == null) removeNode(parent, o)
784
- else updateNode(parent, o, v, hooks, getNextSibling(old, start + 1, nextSibling), ns)
785
- }
786
- if (old.length > commonLength) removeNodes(parent, old, start, old.length)
787
- if (vnodes.length > commonLength) createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
788
- } else {
789
- // keyed diff
790
- var oldEnd = old.length - 1, end = vnodes.length - 1, map, oe, ve, topSibling
791
-
792
- // bottom-up
793
- while (oldEnd >= oldStart && end >= start) {
794
- oe = old[oldEnd]
795
- ve = vnodes[end]
796
- if (oe.key !== ve.key) break
797
- if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns)
798
- if (ve.dom != null) nextSibling = ve.dom
799
- oldEnd--, end--
800
- }
801
- // top-down
802
- while (oldEnd >= oldStart && end >= start) {
803
- o = old[oldStart]
804
- v = vnodes[start]
805
- if (o.key !== v.key) break
806
- oldStart++, start++
807
- if (o !== v) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns)
808
- }
809
- // swaps and list reversals
810
- while (oldEnd >= oldStart && end >= start) {
811
- if (start === end) break
812
- if (o.key !== ve.key || oe.key !== v.key) break
813
- topSibling = getNextSibling(old, oldStart, nextSibling)
814
- moveDOM(parent, oe, topSibling)
815
- if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns)
816
- if (++start <= --end) moveDOM(parent, o, nextSibling)
817
- if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns)
818
- if (ve.dom != null) nextSibling = ve.dom
819
- oldStart++; oldEnd--
820
- oe = old[oldEnd]
821
- ve = vnodes[end]
822
- o = old[oldStart]
823
- v = vnodes[start]
824
- }
825
- // bottom up once again
826
- while (oldEnd >= oldStart && end >= start) {
827
- if (oe.key !== ve.key) break
828
- if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns)
829
- if (ve.dom != null) nextSibling = ve.dom
830
- oldEnd--, end--
831
- oe = old[oldEnd]
832
- ve = vnodes[end]
833
- }
834
- if (start > end) removeNodes(parent, old, oldStart, oldEnd + 1)
835
- else if (oldStart > oldEnd) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
836
- else {
837
- // inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul
838
- var originalNextSibling = nextSibling, vnodesLength = end - start + 1, oldIndices = new Array(vnodesLength), li = 0, i = 0, pos = 2147483647, matched = 0, lisIndices
839
- for (i = 0; i < vnodesLength; i++) oldIndices[i] = -1
840
- for (i = end; i >= start; i--) {
841
- if (map == null) map = getKeyMap(old, oldStart, oldEnd + 1)
842
- ve = vnodes[i]
843
- var oldIndex = map[ve.key]
844
- if (oldIndex != null) {
845
- pos = (oldIndex < pos) ? oldIndex : -1 // becomes -1 if nodes were re-ordered
846
- oldIndices[i - start] = oldIndex
847
- oe = old[oldIndex]
848
- old[oldIndex] = null
849
- if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns)
850
- if (ve.dom != null) nextSibling = ve.dom
851
- matched++
852
- }
853
- }
854
- nextSibling = originalNextSibling
855
- if (matched !== oldEnd - oldStart + 1) removeNodes(parent, old, oldStart, oldEnd + 1)
856
- if (matched === 0) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
857
- else {
858
- if (pos === -1) {
859
- // the indices of the indices of the items that are part of the
860
- // longest increasing subsequence in the oldIndices list
861
- lisIndices = makeLisIndices(oldIndices)
862
- li = lisIndices.length - 1
863
- for (i = end; i >= start; i--) {
864
- v = vnodes[i]
865
- if (oldIndices[i - start] === -1) createNode(parent, v, hooks, ns, nextSibling)
866
- else {
867
- if (lisIndices[li] === i - start) li--
868
- else moveDOM(parent, v, nextSibling)
869
- }
870
- if (v.dom != null) nextSibling = vnodes[i].dom
871
- }
872
- } else {
873
- for (i = end; i >= start; i--) {
874
- v = vnodes[i]
875
- if (oldIndices[i - start] === -1) createNode(parent, v, hooks, ns, nextSibling)
876
- if (v.dom != null) nextSibling = vnodes[i].dom
877
- }
878
- }
879
- }
880
- }
881
- }
882
- }
883
- }
884
-
885
- function updateNode(parent, old, vnode, hooks, nextSibling, ns) {
886
- var oldTag = old.tag, tag = vnode.tag
887
- if (oldTag === tag && old.is === vnode.is) {
888
- vnode.state = old.state
889
- vnode.events = old.events
890
- if (shouldNotUpdate(vnode, old)) return
891
- if (typeof oldTag === "string") {
892
- if (vnode.attrs != null) {
893
- updateLifecycle(vnode.attrs, vnode, hooks)
894
- }
895
- switch (oldTag) {
896
- case "#": updateText(old, vnode); break
897
- case "<": updateHTML(parent, old, vnode, ns, nextSibling); break
898
- case "[": updateFragment(parent, old, vnode, hooks, nextSibling, ns); break
899
- default: updateElement(old, vnode, hooks, ns)
900
- }
901
- }
902
- else updateComponent(parent, old, vnode, hooks, nextSibling, ns)
903
- }
904
- else {
905
- removeNode(parent, old)
906
- createNode(parent, vnode, hooks, ns, nextSibling)
907
- }
908
- }
909
-
910
- function updateText(old, vnode) {
911
- if (old.children.toString() !== vnode.children.toString()) {
912
- old.dom.nodeValue = vnode.children
913
- }
914
- vnode.dom = old.dom
915
- }
916
-
917
- function updateHTML(parent, old, vnode, ns, nextSibling) {
918
- if (old.children !== vnode.children) {
919
- removeDOM(parent, old)
920
- createHTML(parent, vnode, ns, nextSibling)
921
- }
922
- else {
923
- vnode.dom = old.dom
924
- vnode.domSize = old.domSize
925
- }
926
- }
927
-
928
- function updateFragment(parent, old, vnode, hooks, nextSibling, ns) {
929
- updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns)
930
- var domSize = 0, children = vnode.children
931
- vnode.dom = null
932
- if (children != null) {
933
- for (var i = 0; i < children.length; i++) {
934
- var child = children[i]
935
- if (child != null && child.dom != null) {
936
- if (vnode.dom == null) vnode.dom = child.dom
937
- domSize += child.domSize || 1
938
- }
939
- }
940
- }
941
- vnode.domSize = domSize
942
- }
943
-
944
- function updateElement(old, vnode, hooks, ns) {
945
- var element = vnode.dom = old.dom
946
- ns = getNameSpace(vnode) || ns
947
-
948
- if (old.attrs != vnode.attrs || (vnode.attrs != null && !cachedAttrsIsStaticMap.get(vnode.attrs))) {
949
- updateAttrs(vnode, old.attrs, vnode.attrs, ns)
950
- }
951
- if (!maybeSetContentEditable(vnode)) {
952
- updateNodes(element, old.children, vnode.children, hooks, null, ns)
953
- }
954
- }
955
-
956
- function updateComponent(parent, old, vnode, hooks, nextSibling, ns) {
957
- vnode.instance = Vnode.normalize(callHook.call(vnode.state.view, vnode))
958
- if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
959
- updateLifecycle(vnode.state, vnode, hooks)
960
- if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks)
961
- if (vnode.instance != null) {
962
- if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling)
963
- else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, ns)
964
- vnode.dom = vnode.instance.dom
965
- vnode.domSize = vnode.instance.domSize
966
- }
967
- else {
968
- if (old.instance != null) removeNode(parent, old.instance)
969
- vnode.domSize = 0
970
- }
971
- }
972
-
973
- function getKeyMap(vnodes, start, end) {
974
- var map = Object.create(null)
975
- for (; start < end; start++) {
976
- var vnode = vnodes[start]
977
- if (vnode != null) {
978
- var key = vnode.key
979
- if (key != null) map[key] = start
980
- }
981
- }
982
- return map
983
- }
984
-
985
- // Lifted from ivi https://github.com/ivijs/ivi/
986
- // takes a list of unique numbers (-1 is special and can
987
- // occur multiple times) and returns an array with the indices
988
- // of the items that are part of the longest increasing
989
- // subsequence
990
- var lisTemp = []
991
- function makeLisIndices(a) {
992
- var result = [0]
993
- var u = 0, v = 0, i = 0
994
- var il = lisTemp.length = a.length
995
- for (var i = 0; i < il; i++) lisTemp[i] = a[i]
996
- for (var i = 0; i < il; ++i) {
997
- if (a[i] === -1) continue
998
- var j = result[result.length - 1]
999
- if (a[j] < a[i]) {
1000
- lisTemp[i] = j
1001
- result.push(i)
1002
- continue
1003
- }
1004
- u = 0
1005
- v = result.length - 1
1006
- while (u < v) {
1007
- // Fast integer average without overflow.
1008
- // eslint-disable-next-line no-bitwise
1009
- var c = (u >>> 1) + (v >>> 1) + (u & v & 1)
1010
- if (a[result[c]] < a[i]) {
1011
- u = c + 1
1012
- }
1013
- else {
1014
- v = c
1015
- }
1016
- }
1017
- if (a[i] < a[result[u]]) {
1018
- if (u > 0) lisTemp[i] = result[u - 1]
1019
- result[u] = i
1020
- }
1021
- }
1022
- u = result.length
1023
- v = result[u - 1]
1024
- while (u-- > 0) {
1025
- result[u] = v
1026
- v = lisTemp[v]
1027
- }
1028
- lisTemp.length = 0
1029
- return result
1030
- }
1031
-
1032
- function getNextSibling(vnodes, i, nextSibling) {
1033
- for (; i < vnodes.length; i++) {
1034
- if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom
1035
- }
1036
- return nextSibling
1037
- }
1038
-
1039
- // This handles fragments with zombie children (removed from vdom, but persisted in DOM through onbeforeremove)
1040
- function moveDOM(parent, vnode, nextSibling) {
1041
- if (vnode.dom != null) {
1042
- if (vnode.domSize == null || vnode.domSize === 1) {
1043
- // don't allocate for the common case
1044
- insertDOM(parent, vnode.dom, nextSibling)
1045
- } else {
1046
- var doms = domFor(vnode)
1047
- for (var i = 0; i < doms.length; i++) insertDOM(parent, doms[i], nextSibling)
1048
- }
1049
- maybeFlush()
1050
- }
1051
- }
1052
-
1053
- function insertDOM(parent, dom, nextSibling) {
1054
- if (nextSibling != null) parent.insertBefore(dom, nextSibling)
1055
- else parent.appendChild(dom)
1056
- }
1057
-
1058
- function maybeSetContentEditable(vnode) {
1059
- if (vnode.attrs == null || (
1060
- vnode.attrs.contenteditable == null && // attribute
1061
- vnode.attrs.contentEditable == null // property
1062
- )) return false
1063
- var children = vnode.children
1064
- if (children != null && children.length === 1 && children[0].tag === "<") {
1065
- var content = children[0].children
1066
- if (vnode.dom.innerHTML !== content) vnode.dom.innerHTML = content
1067
- }
1068
- else if (children != null && children.length !== 0) throw new Error("Child node of a contenteditable must be trusted.")
1069
- return true
1070
- }
1071
-
1072
- //remove
1073
- function removeNodes(parent, vnodes, start, end) {
1074
- for (var i = start; i < end; i++) {
1075
- var vnode = vnodes[i]
1076
- if (vnode != null) removeNode(parent, vnode)
1077
- }
1078
- }
1079
-
1080
- function tryBlockRemove(parent, vnode, source, counter) {
1081
- var original = vnode.state
1082
- var result = callHook.call(source.onbeforeremove, vnode)
1083
- if (result == null) return
1084
-
1085
- var generation = currentRender
1086
- var doms = domFor(vnode)
1087
- for (var i = 0; i < doms.length; i++) delayedRemoval.set(doms[i], generation)
1088
- counter.v++
1089
-
1090
- Promise.resolve(result).finally(function () {
1091
- checkState(vnode, original)
1092
- tryResumeRemove(parent, vnode, counter)
1093
- })
1094
- }
1095
-
1096
- function tryResumeRemove(parent, vnode, counter) {
1097
- if (--counter.v === 0) {
1098
- onremove(vnode)
1099
- removeDOM(parent, vnode)
1100
- }
1101
- }
1102
-
1103
- function removeNode(parent, vnode) {
1104
- var counter = {v: 1}
1105
- if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeremove === "function") tryBlockRemove(parent, vnode, vnode.state, counter)
1106
- if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") tryBlockRemove(parent, vnode, vnode.attrs, counter)
1107
- tryResumeRemove(parent, vnode, counter)
1108
- }
1109
-
1110
- function removeDOM(parent, vnode) {
1111
- if (vnode.dom == null) return
1112
- if (vnode.domSize == null || vnode.domSize === 1) {
1113
- parent.removeChild(vnode.dom)
1114
- } else {
1115
- var doms = domFor(vnode)
1116
- for (var i = 0; i < doms.length; i++) parent.removeChild(doms[i])
1117
- }
1118
- }
1119
-
1120
- function domFor(vnode) {
1121
- var doms = []
1122
- var dom = vnode.dom
1123
- var domSize = vnode.domSize
1124
- var generation = delayedRemoval.get(dom)
1125
- if (dom != null) {
1126
- do {
1127
- var nextSibling = dom.nextSibling
1128
- if (delayedRemoval.get(dom) === generation) {
1129
- doms.push(dom)
1130
- domSize--
1131
- }
1132
- dom = nextSibling
1133
- } while (domSize)
1134
- }
1135
- return doms
1136
- }
1137
-
1138
- function onremove(vnode) {
1139
- if (typeof vnode.tag !== "string" && typeof vnode.state.onremove === "function") callHook.call(vnode.state.onremove, vnode)
1140
- if (vnode.attrs && typeof vnode.attrs.onremove === "function") callHook.call(vnode.attrs.onremove, vnode)
1141
- if (typeof vnode.tag !== "string") {
1142
- if (vnode.instance != null) onremove(vnode.instance)
1143
- } else {
1144
- if (vnode.events != null) vnode.events._ = null
1145
- var children = vnode.children
1146
- if (Array.isArray(children)) {
1147
- for (var i = 0; i < children.length; i++) {
1148
- var child = children[i]
1149
- if (child != null) onremove(child)
1150
- }
1151
- }
1152
- }
1153
- }
1154
-
1155
- //attrs
1156
- function setAttrs(vnode, attrs, ns) {
1157
- for (var key in attrs) {
1158
- setAttr(vnode, key, null, attrs[key], ns)
1159
- }
1160
- }
1161
-
1162
- function setAttr(vnode, key, old, value, ns) {
1163
- if (key === "key" || value == null || isLifecycleMethod(key) || (old === value && !isFormAttribute(vnode, key)) && typeof value !== "object") return
1164
- if (key[0] === "o" && key[1] === "n") return updateEvent(vnode, key, value)
1165
- if (key.slice(0, 6) === "xlink:") vnode.dom.setAttributeNS("http://www.w3.org/1999/xlink", key.slice(6), value)
1166
- else if (key === "style") updateStyle(vnode.dom, old, value)
1167
- else if (hasPropertyKey(vnode, key, ns)) {
1168
- if (key === "value") {
1169
- // Only do the coercion if we're actually going to check the value.
1170
- /* eslint-disable no-implicit-coercion */
1171
- //setting input[value] to same value by typing on focused element moves cursor to end in Chrome
1172
- //setting input[type=file][value] to same value causes an error to be generated if it's non-empty
1173
- //minlength/maxlength validation isn't performed on script-set values(#2256)
1174
- if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === "" + value) return
1175
- //setting select[value] to same value while having select open blinks select dropdown in Chrome
1176
- if (vnode.tag === "select" && old !== null && vnode.dom.value === "" + value) return
1177
- //setting option[value] to same value while having select open blinks select dropdown in Chrome
1178
- if (vnode.tag === "option" && old !== null && vnode.dom.value === "" + value) return
1179
- //setting input[type=file][value] to different value is an error if it's non-empty
1180
- // Not ideal, but it at least works around the most common source of uncaught exceptions for now.
1181
- if (vnode.tag === "input" && vnode.attrs.type === "file" && "" + value !== "") { console.error("`value` is read-only on file inputs!"); return }
1182
- /* eslint-enable no-implicit-coercion */
1183
- }
1184
- // If you assign an input type that is not supported by IE 11 with an assignment expression, an error will occur.
1185
- if (vnode.tag === "input" && key === "type") vnode.dom.setAttribute(key, value)
1186
- else vnode.dom[key] = value
1187
- } else {
1188
- if (typeof value === "boolean") {
1189
- if (value) vnode.dom.setAttribute(key, "")
1190
- else vnode.dom.removeAttribute(key)
1191
- }
1192
- else vnode.dom.setAttribute(key === "className" ? "class" : key, value)
1193
- }
1194
- }
1195
-
1196
- function removeAttr(vnode, key, old, ns) {
1197
- if (key === "key" || old == null || isLifecycleMethod(key)) return
1198
- if (key[0] === "o" && key[1] === "n") updateEvent(vnode, key, undefined)
1199
- else if (key === "style") updateStyle(vnode.dom, old, null)
1200
- else if (
1201
- hasPropertyKey(vnode, key, ns)
1202
- && key !== "className"
1203
- && key !== "title" // creates "null" as title
1204
- && !(key === "value" && (
1205
- vnode.tag === "option"
1206
- || vnode.tag === "select" && vnode.dom.selectedIndex === -1 && vnode.dom === activeElement(vnode.dom)
1207
- ))
1208
- && !(vnode.tag === "input" && key === "type")
1209
- ) {
1210
- vnode.dom[key] = null
1211
- } else {
1212
- var nsLastIndex = key.indexOf(":")
1213
- if (nsLastIndex !== -1) key = key.slice(nsLastIndex + 1)
1214
- if (old !== false) vnode.dom.removeAttribute(key === "className" ? "class" : key)
1215
- }
1216
- }
1217
-
1218
- function setLateSelectAttrs(vnode, attrs) {
1219
- if ("value" in attrs) {
1220
- if (attrs.value === null) {
1221
- if (vnode.dom.selectedIndex !== -1) vnode.dom.value = null
1222
- } else {
1223
- var normalized = "" + attrs.value // eslint-disable-line no-implicit-coercion
1224
- if (vnode.dom.value !== normalized || vnode.dom.selectedIndex === -1) {
1225
- vnode.dom.value = normalized
1226
- }
1227
- }
1228
- }
1229
- if ("selectedIndex" in attrs) setAttr(vnode, "selectedIndex", null, attrs.selectedIndex, undefined)
1230
- }
1231
-
1232
- function updateAttrs(vnode, old, attrs, ns) {
1233
- // Some attributes may NOT be case-sensitive (e.g. data-***),
1234
- // so removal should be done first to prevent accidental removal for newly setting values.
1235
- var val
1236
- if (old != null) {
1237
- if (old === attrs && !cachedAttrsIsStaticMap.has(attrs)) {
1238
- console.warn("Don't reuse attrs object, use new object for every redraw, this will throw in next major")
1239
- }
1240
- for (var key in old) {
1241
- if (((val = old[key]) != null) && (attrs == null || attrs[key] == null)) {
1242
- removeAttr(vnode, key, val, ns)
1243
- }
1244
- }
1245
- }
1246
- if (attrs != null) {
1247
- for (var key in attrs) {
1248
- setAttr(vnode, key, old && old[key], attrs[key], ns)
1249
- }
1250
- }
1251
- }
1252
-
1253
- function isFormAttribute(vnode, attr) {
1254
- return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && (vnode.dom === activeElement(vnode.dom) || vnode.tag === "option" && vnode.dom.parentNode === activeElement(vnode.dom))
1255
- }
1256
-
1257
- function hasPropertyKey(vnode, key, ns) {
1258
- // Filter out namespaced keys
1259
- return ns === undefined && (
1260
- // If it's a custom element, just keep it.
1261
- vnode.tag.indexOf("-") > -1 || vnode.is ||
1262
- // If it's a normal element, let's try to avoid a few browser bugs.
1263
- key !== "href" && key !== "list" && key !== "form" && key !== "width" && key !== "height"// && key !== "type"
1264
- // Defer the property check until *after* we check everything.
1265
- ) && key in vnode.dom
1266
- }
1267
-
1268
- //style
1269
- function updateStyle(element, old, style) {
1270
- if (old === style) {
1271
- // Styles are equivalent, do nothing.
1272
- } else if (style == null) {
1273
- // New style is missing, just clear it.
1274
- element.style = ""
1275
- } else if (typeof style !== "object") {
1276
- // New style is a string, let engine deal with patching.
1277
- element.style = style
1278
- } else if (old == null || typeof old !== "object") {
1279
- // `old` is missing or a string, `style` is an object.
1280
- element.style = ""
1281
- // Add new style properties
1282
- for (var key in style) {
1283
- var value = style[key]
1284
- if (value != null) {
1285
- if (key.includes("-")) element.style.setProperty(key, String(value))
1286
- else element.style[key] = String(value)
1287
- }
1288
- }
1289
- } else {
1290
- // Both old & new are (different) objects.
1291
- // Remove style properties that no longer exist
1292
- // Style properties may have two cases(dash-case and camelCase),
1293
- // so removal should be done first to prevent accidental removal for newly setting values.
1294
- for (var key in old) {
1295
- if (old[key] != null && style[key] == null) {
1296
- if (key.includes("-")) element.style.removeProperty(key)
1297
- else element.style[key] = ""
1298
- }
1299
- }
1300
- // Update style properties that have changed
1301
- for (var key in style) {
1302
- var value = style[key]
1303
- if (value != null && (value = String(value)) !== String(old[key])) {
1304
- if (key.includes("-")) element.style.setProperty(key, value)
1305
- else element.style[key] = value
1306
- }
1307
- }
1308
- }
1309
- }
1310
-
1311
- // Here's an explanation of how this works:
1312
- // 1. The event names are always (by design) prefixed by `on`.
1313
- // 2. The EventListener interface accepts either a function or an object
1314
- // with a `handleEvent` method.
1315
- // 3. The object does not inherit from `Object.prototype`, to avoid
1316
- // any potential interference with that (e.g. setters).
1317
- // 4. The event name is remapped to the handler before calling it.
1318
- // 5. In function-based event handlers, `ev.target === this`. We replicate
1319
- // that below.
1320
- // 6. In function-based event handlers, `return false` prevents the default
1321
- // action and stops event propagation. We replicate that below.
1322
- function EventDict() {
1323
- // Save this, so the current redraw is correctly tracked.
1324
- this._ = currentRedraw
1325
- }
1326
- EventDict.prototype = Object.create(null)
1327
- EventDict.prototype.handleEvent = function (ev) {
1328
- var handler = this["on" + ev.type]
1329
- var result
1330
- if (typeof handler === "function") result = handler.call(ev.currentTarget, ev)
1331
- else if (typeof handler.handleEvent === "function") handler.handleEvent(ev)
1332
- var self = this
1333
- if (self._ != null) {
1334
- if (ev.redraw !== false) (0, self._)()
1335
- if (result != null && typeof result.then === "function") {
1336
- Promise.resolve(result).then(function () {
1337
- if (self._ != null && ev.redraw !== false) (0, self._)()
1338
- })
1339
- }
1340
- }
1341
- if (result === false) {
1342
- ev.preventDefault()
1343
- ev.stopPropagation()
1344
- }
1345
- }
1346
-
1347
- //event
1348
- function updateEvent(vnode, key, value) {
1349
- if (vnode.events != null) {
1350
- vnode.events._ = currentRedraw
1351
- if (vnode.events[key] === value) return
1352
- if (value != null && (typeof value === "function" || typeof value === "object")) {
1353
- if (vnode.events[key] == null) vnode.dom.addEventListener(key.slice(2), vnode.events, false)
1354
- vnode.events[key] = value
1355
- } else {
1356
- if (vnode.events[key] != null) vnode.dom.removeEventListener(key.slice(2), vnode.events, false)
1357
- vnode.events[key] = undefined
1358
- }
1359
- } else if (value != null && (typeof value === "function" || typeof value === "object")) {
1360
- vnode.events = new EventDict()
1361
- vnode.dom.addEventListener(key.slice(2), vnode.events, false)
1362
- vnode.events[key] = value
1363
- }
1364
- }
1365
-
1366
- //lifecycle
1367
- function initLifecycle(source, vnode, hooks) {
1368
- if (typeof source.oninit === "function") callHook.call(source.oninit, vnode)
1369
- if (typeof source.oncreate === "function") hooks.push(callHook.bind(source.oncreate, vnode))
1370
- }
1371
-
1372
- function updateLifecycle(source, vnode, hooks) {
1373
- if (typeof source.onupdate === "function") hooks.push(callHook.bind(source.onupdate, vnode))
1374
- }
1375
-
1376
- function shouldNotUpdate(vnode, old) {
1377
- do {
1378
- if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") {
1379
- var force = callHook.call(vnode.attrs.onbeforeupdate, vnode, old)
1380
- if (force !== undefined && !force) break
1381
- }
1382
- if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeupdate === "function") {
1383
- var force = callHook.call(vnode.state.onbeforeupdate, vnode, old)
1384
- if (force !== undefined && !force) break
1385
- }
1386
- return false
1387
- } while (false); // eslint-disable-line no-constant-condition
1388
- vnode.dom = old.dom
1389
- vnode.domSize = old.domSize
1390
- vnode.instance = old.instance
1391
- vnode.attrs = old.attrs
1392
- vnode.children = old.children
1393
- vnode.text = old.text
1394
- return true
1395
- }
1396
-
1397
- return function (dom, vnodes, redraw) {
1398
- if (!dom) throw new TypeError("DOM element being rendered to does not exist.")
1399
- if (currentDOM != null && dom.contains(currentDOM)) {
1400
- throw new TypeError("Node is currently being rendered to and thus is locked.")
1401
- }
1402
- var prevRedraw = currentRedraw
1403
- var prevDOM = currentDOM
1404
- var hooks = []
1405
- var active = activeElement(dom)
1406
- var namespace = dom.namespaceURI
1407
-
1408
- currentDOM = dom
1409
- currentRedraw = typeof redraw === "function" ? redraw : undefined
1410
- currentRender = {}
1411
- renderDepth++
1412
- try {
1413
- // First time rendering into a node clears it out
1414
- if (dom.vnodes == null) dom.textContent = ""
1415
- vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes])
1416
- updateNodes(dom, dom.vnodes, vnodes, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace)
1417
- dom.vnodes = vnodes
1418
- // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement
1419
- if (active != null && activeElement(dom) !== active && typeof active.focus === "function") active.focus()
1420
- for (var i = 0; i < hooks.length; i++) hooks[i]()
1421
- } finally {
1422
- renderDepth--
1423
- currentRedraw = prevRedraw
1424
- currentDOM = prevDOM
1425
- }
1426
- // Flush hook: after each mithril redraw pass, push the tree to Lynx.
1427
- flushTree()
1428
- }
1429
- }
1430
-
1431
- // ============================================================
1432
- // 8. Convenience API
1433
- // ============================================================
1434
-
1435
- var mithrilRender = factory()
1436
- var rootWrapper = null
1437
- var rootComponent = null
1438
- var currentVnode = null
1439
- var runRender = null
1440
- var redraw = null
1441
-
1442
- function flush() {
1443
- if (runRender != null && rootWrapper != null) {
1444
- if (rootComponent != null) currentVnode = Vnode(rootComponent)
1445
- runRender(rootWrapper, currentVnode, redraw)
1446
- } else {
1447
- flushTree()
1448
- }
1449
- }
1450
-
1451
- // Initial render. Re-render ONLY via mithril's own render/redraw
1452
- // (shim.redraw() / m.redraw()), never by calling this again manually.
1453
- function render(rootWrapperArg, vnode) {
1454
- rootWrapper = rootWrapperArg
1455
- rootComponent = vnode != null && typeof vnode.tag !== "string" ? vnode.tag : null
1456
- currentVnode = vnode
1457
- redraw = function () { flush() }
1458
- // runRender must hold the render FUNCTION itself (mithrilRender), not the
1459
- // result of calling it (which is undefined) — flush() re-invokes it on redraw.
1460
- runRender = mithrilRender
1461
- runRender(rootWrapper, currentVnode, redraw)
1462
- }
1463
-
1464
- function redrawNow() {
1465
- if (redraw != null) redraw()
1466
- }
1467
-
1468
- function createPageWrapper(pageElement) {
1469
- var pageId = __GetElementUniqueID(pageElement)
1470
- var document = createFakeDocument(pageId)
1471
- var wrapper = wrapperFor(pageElement)
1472
- wrapper._document = document
1473
- wrapper._pageId = pageId
1474
- wrapper._tag = "page"
1475
- return wrapper
1476
- }
1477
-
1478
- function renderToPage(pageElement, vnode) {
1479
- var wrapper = createPageWrapper(pageElement)
1480
- render(wrapper, vnode)
1481
- return wrapper
1482
- }
1483
-
1484
- // Legacy compatibility with the old shim's API surface.
1485
- function createLynxWindow(pageElement) {
1486
- var wrapper = pageElement != null ? createPageWrapper(pageElement) : null
1487
- return {
1488
- document: wrapper != null ? wrapper.ownerDocument : getDefaultDocument(),
1489
- __root: wrapper
1490
- }
1491
- }
1492
-
1493
- // ============================================================
1494
- // 9. Exports
1495
- // ============================================================
1496
-
1497
- module.exports = factory
1498
- module.exports.render = render
1499
- module.exports.redraw = redrawNow
1500
- module.exports.createPageWrapper = createPageWrapper
1501
- module.exports.renderToPage = renderToPage
1502
- module.exports.createLynxWindow = createLynxWindow
1503
- module.exports.LynxNodeWrapper = LynxNodeWrapper
1504
- module.exports.LynxStyleProxy = LynxStyleProxy
1505
- module.exports.normalizeEvent = normalizeEvent