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.
@@ -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