deku 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +5 -0
- data/LICENSE +25 -0
- data/README.md +28 -0
- data/Rakefile +1 -0
- data/deku.gemspec +23 -0
- data/deps/node_modules/deku.js +2 -0
- data/deps/node_modules/deku/.editorconfig +16 -0
- data/deps/node_modules/deku/.zuul.yml +15 -0
- data/deps/node_modules/deku/History.md +290 -0
- data/deps/node_modules/deku/LICENSE.md +7 -0
- data/deps/node_modules/deku/Makefile +91 -0
- data/deps/node_modules/deku/README.md +293 -0
- data/deps/node_modules/deku/index.js +4072 -0
- data/deps/node_modules/deku/lib/application.js +85 -0
- data/deps/node_modules/deku/lib/index.js +28 -0
- data/deps/node_modules/deku/lib/render.js +1300 -0
- data/deps/node_modules/deku/lib/stringify.js +105 -0
- data/deps/node_modules/deku/lib/svg.js +107 -0
- data/deps/node_modules/deku/lib/utils.js +18 -0
- data/deps/node_modules/deku/lib/virtual.js +247 -0
- data/deps/node_modules/deku/node_modules/array-flatten/LICENSE +21 -0
- data/deps/node_modules/deku/node_modules/array-flatten/README.md +43 -0
- data/deps/node_modules/deku/node_modules/array-flatten/array-flatten.js +57 -0
- data/deps/node_modules/deku/node_modules/array-flatten/package.json +62 -0
- data/deps/node_modules/deku/node_modules/component-emitter/History.md +63 -0
- data/deps/node_modules/deku/node_modules/component-emitter/LICENSE +24 -0
- data/deps/node_modules/deku/node_modules/component-emitter/Readme.md +74 -0
- data/deps/node_modules/deku/node_modules/component-emitter/index.js +161 -0
- data/deps/node_modules/deku/node_modules/component-emitter/package.json +174 -0
- data/deps/node_modules/deku/node_modules/component-raf/.npmignore +2 -0
- data/deps/node_modules/deku/node_modules/component-raf/History.md +26 -0
- data/deps/node_modules/deku/node_modules/component-raf/Makefile +11 -0
- data/deps/node_modules/deku/node_modules/component-raf/Readme.md +46 -0
- data/deps/node_modules/deku/node_modules/component-raf/component.json +16 -0
- data/deps/node_modules/deku/node_modules/component-raf/example.html +43 -0
- data/deps/node_modules/deku/node_modules/component-raf/index.js +34 -0
- data/deps/node_modules/deku/node_modules/component-raf/package.json +164 -0
- data/deps/node_modules/deku/node_modules/component-type/.npmignore +3 -0
- data/deps/node_modules/deku/node_modules/component-type/Makefile +14 -0
- data/deps/node_modules/deku/node_modules/component-type/Readme.md +37 -0
- data/deps/node_modules/deku/node_modules/component-type/component.json +13 -0
- data/deps/node_modules/deku/node_modules/component-type/index.js +34 -0
- data/deps/node_modules/deku/node_modules/component-type/package.json +120 -0
- data/deps/node_modules/deku/node_modules/component-type/test/index.html +17 -0
- data/deps/node_modules/deku/node_modules/component-type/test/mocha.css +231 -0
- data/deps/node_modules/deku/node_modules/component-type/test/mocha.js +5340 -0
- data/deps/node_modules/deku/node_modules/component-type/test/tests.js +72 -0
- data/deps/node_modules/deku/node_modules/dom-pool/.npmignore +1 -0
- data/deps/node_modules/deku/node_modules/dom-pool/Pool.js +52 -0
- data/deps/node_modules/deku/node_modules/dom-pool/README.md +42 -0
- data/deps/node_modules/deku/node_modules/dom-pool/authors.txt +4 -0
- data/deps/node_modules/deku/node_modules/dom-pool/bower.json +26 -0
- data/deps/node_modules/deku/node_modules/dom-pool/package.json +46 -0
- data/deps/node_modules/deku/node_modules/dom-pool/tests.html +16 -0
- data/deps/node_modules/deku/node_modules/dom-pool/tests.js +102 -0
- data/deps/node_modules/deku/node_modules/dom-walk/.npmignore +3 -0
- data/deps/node_modules/deku/node_modules/dom-walk/LICENCE +19 -0
- data/deps/node_modules/deku/node_modules/dom-walk/Makefile +2 -0
- data/deps/node_modules/deku/node_modules/dom-walk/README.md +23 -0
- data/deps/node_modules/deku/node_modules/dom-walk/example/index.js +5 -0
- data/deps/node_modules/deku/node_modules/dom-walk/example/static/bundle.js +211 -0
- data/deps/node_modules/deku/node_modules/dom-walk/example/static/index.html +16 -0
- data/deps/node_modules/deku/node_modules/dom-walk/index.js +24 -0
- data/deps/node_modules/deku/node_modules/dom-walk/package.json +57 -0
- data/deps/node_modules/deku/node_modules/fast.js/.jshintignore +7 -0
- data/deps/node_modules/deku/node_modules/fast.js/.jshintrc +80 -0
- data/deps/node_modules/deku/node_modules/fast.js/.npmignore +6 -0
- data/deps/node_modules/deku/node_modules/fast.js/.travis.yml +3 -0
- data/deps/node_modules/deku/node_modules/fast.js/LICENSE.md +21 -0
- data/deps/node_modules/deku/node_modules/fast.js/README.md +552 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/clone.js +21 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/concat.js +32 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/every.js +25 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/fill.js +29 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/filter.js +26 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/forEach.js +21 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/index.js +15 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/indexOf.js +33 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/lastIndexOf.js +29 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/map.js +24 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/pluck.js +24 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/reduce.js +35 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/reduceRight.js +35 -0
- data/deps/node_modules/deku/node_modules/fast.js/array/some.js +25 -0
- data/deps/node_modules/deku/node_modules/fast.js/bower.json +28 -0
- data/deps/node_modules/deku/node_modules/fast.js/clone.js +27 -0
- data/deps/node_modules/deku/node_modules/fast.js/dist/bench.html +15 -0
- data/deps/node_modules/deku/node_modules/fast.js/dist/bench.js +19900 -0
- data/deps/node_modules/deku/node_modules/fast.js/dist/fast.js +1450 -0
- data/deps/node_modules/deku/node_modules/fast.js/dist/fast.min.js +1 -0
- data/deps/node_modules/deku/node_modules/fast.js/filter.js +23 -0
- data/deps/node_modules/deku/node_modules/fast.js/forEach.js +22 -0
- data/deps/node_modules/deku/node_modules/fast.js/function/apply.js +19 -0
- data/deps/node_modules/deku/node_modules/fast.js/function/applyNoContext.js +29 -0
- data/deps/node_modules/deku/node_modules/fast.js/function/applyWithContext.js +29 -0
- data/deps/node_modules/deku/node_modules/fast.js/function/bind.js +71 -0
- data/deps/node_modules/deku/node_modules/fast.js/function/bindInternal3.js +11 -0
- data/deps/node_modules/deku/node_modules/fast.js/function/bindInternal4.js +11 -0
- data/deps/node_modules/deku/node_modules/fast.js/function/index.js +7 -0
- data/deps/node_modules/deku/node_modules/fast.js/function/partial.js +42 -0
- data/deps/node_modules/deku/node_modules/fast.js/function/partialConstructor.js +45 -0
- data/deps/node_modules/deku/node_modules/fast.js/function/try.js +35 -0
- data/deps/node_modules/deku/node_modules/fast.js/index.js +241 -0
- data/deps/node_modules/deku/node_modules/fast.js/map.js +23 -0
- data/deps/node_modules/deku/node_modules/fast.js/object/assign.js +34 -0
- data/deps/node_modules/deku/node_modules/fast.js/object/clone.js +25 -0
- data/deps/node_modules/deku/node_modules/fast.js/object/filter.js +28 -0
- data/deps/node_modules/deku/node_modules/fast.js/object/forEach.js +23 -0
- data/deps/node_modules/deku/node_modules/fast.js/object/index.js +11 -0
- data/deps/node_modules/deku/node_modules/fast.js/object/keys.js +17 -0
- data/deps/node_modules/deku/node_modules/fast.js/object/map.js +26 -0
- data/deps/node_modules/deku/node_modules/fast.js/object/reduce.js +37 -0
- data/deps/node_modules/deku/node_modules/fast.js/object/reduceRight.js +37 -0
- data/deps/node_modules/deku/node_modules/fast.js/object/values.js +20 -0
- data/deps/node_modules/deku/node_modules/fast.js/package.json +73 -0
- data/deps/node_modules/deku/node_modules/fast.js/reduce.js +24 -0
- data/deps/node_modules/deku/node_modules/fast.js/reduceRight.js +24 -0
- data/deps/node_modules/deku/node_modules/fast.js/string/index.js +3 -0
- data/deps/node_modules/deku/node_modules/fast.js/string/intern.js +56 -0
- data/deps/node_modules/deku/node_modules/get-uid/README.md +44 -0
- data/deps/node_modules/deku/node_modules/get-uid/index.js +6 -0
- data/deps/node_modules/deku/node_modules/get-uid/package.json +56 -0
- data/deps/node_modules/deku/node_modules/is-dom/HISTORY.md +2 -0
- data/deps/node_modules/deku/node_modules/is-dom/LICENSE +21 -0
- data/deps/node_modules/deku/node_modules/is-dom/README.md +32 -0
- data/deps/node_modules/deku/node_modules/is-dom/index.js +15 -0
- data/deps/node_modules/deku/node_modules/is-dom/package.json +62 -0
- data/deps/node_modules/deku/node_modules/object-path/.npmignore +7 -0
- data/deps/node_modules/deku/node_modules/object-path/.travis.yml +6 -0
- data/deps/node_modules/deku/node_modules/object-path/LICENSE +21 -0
- data/deps/node_modules/deku/node_modules/object-path/README.md +96 -0
- data/deps/node_modules/deku/node_modules/object-path/bower.json +17 -0
- data/deps/node_modules/deku/node_modules/object-path/component.json +22 -0
- data/deps/node_modules/deku/node_modules/object-path/index.js +269 -0
- data/deps/node_modules/deku/node_modules/object-path/package.json +89 -0
- data/deps/node_modules/deku/node_modules/object-path/test.js +510 -0
- data/deps/node_modules/deku/node_modules/per-frame/.npmignore +68 -0
- data/deps/node_modules/deku/node_modules/per-frame/History.md +32 -0
- data/deps/node_modules/deku/node_modules/per-frame/README.md +44 -0
- data/deps/node_modules/deku/node_modules/per-frame/component.json +13 -0
- data/deps/node_modules/deku/node_modules/per-frame/index.js +37 -0
- data/deps/node_modules/deku/node_modules/per-frame/package.json +143 -0
- data/deps/node_modules/deku/node_modules/per-frame/test/test.js +94 -0
- data/deps/node_modules/deku/node_modules/sliced/.npmignore +2 -0
- data/deps/node_modules/deku/node_modules/sliced/.travis.yml +4 -0
- data/deps/node_modules/deku/node_modules/sliced/History.md +30 -0
- data/deps/node_modules/deku/node_modules/sliced/LICENSE +22 -0
- data/deps/node_modules/deku/node_modules/sliced/Makefile +5 -0
- data/deps/node_modules/deku/node_modules/sliced/README.md +62 -0
- data/deps/node_modules/deku/node_modules/sliced/bench.js +95 -0
- data/deps/node_modules/deku/node_modules/sliced/component.json +14 -0
- data/deps/node_modules/deku/node_modules/sliced/index.js +1 -0
- data/deps/node_modules/deku/node_modules/sliced/lib/sliced.js +33 -0
- data/deps/node_modules/deku/node_modules/sliced/package.json +52 -0
- data/deps/node_modules/deku/node_modules/sliced/test/index.js +80 -0
- data/deps/node_modules/deku/package.json +67 -0
- data/lib/deku.rb +11 -0
- data/lib/deku/application.rb +16 -0
- data/lib/deku/component.rb +36 -0
- data/lib/deku/context.rb +38 -0
- data/lib/deku/element_node.rb +17 -0
- data/lib/deku/version.rb +4 -0
- metadata +278 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module dependencies.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
var Emitter = require('component-emitter')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Expose `scene`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
module.exports = Application
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a new `Application`.
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} element Optional initial element
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
function Application (element) {
|
|
20
|
+
if (!(this instanceof Application)) return new Application(element)
|
|
21
|
+
this.options = {}
|
|
22
|
+
this.sources = {}
|
|
23
|
+
this.element = element
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Mixin `Emitter`.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
Emitter(Application.prototype)
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Add a plugin
|
|
34
|
+
*
|
|
35
|
+
* @param {Function} plugin
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
Application.prototype.use = function (plugin) {
|
|
39
|
+
plugin(this)
|
|
40
|
+
return this
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Set an option
|
|
45
|
+
*
|
|
46
|
+
* @param {String} name
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
Application.prototype.option = function (name, val) {
|
|
50
|
+
this.options[name] = val
|
|
51
|
+
return this
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Set value used somewhere in the IO network.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
Application.prototype.set = function (name, data) {
|
|
59
|
+
this.sources[name] = data
|
|
60
|
+
this.emit('source', name, data)
|
|
61
|
+
return this
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Mount a virtual element.
|
|
66
|
+
*
|
|
67
|
+
* @param {VirtualElement} element
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
Application.prototype.mount = function (element) {
|
|
71
|
+
this.element = element
|
|
72
|
+
this.emit('mount', element)
|
|
73
|
+
return this
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Remove the world. Unmount everything.
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
Application.prototype.unmount = function () {
|
|
81
|
+
if (!this.element) return
|
|
82
|
+
this.element = null
|
|
83
|
+
this.emit('unmount')
|
|
84
|
+
return this
|
|
85
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create the application.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
exports.tree =
|
|
6
|
+
exports.scene =
|
|
7
|
+
exports.deku = require('./application')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Render scenes to the DOM.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
if (typeof document !== 'undefined') {
|
|
14
|
+
exports.render = require('./render')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Render scenes to a string
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
exports.renderString = require('./stringify')
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create virtual elements.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
exports.element =
|
|
28
|
+
exports.dom = require('./virtual')
|
|
@@ -0,0 +1,1300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependencies.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
var raf = require('component-raf')
|
|
6
|
+
var Pool = require('dom-pool')
|
|
7
|
+
var walk = require('dom-walk')
|
|
8
|
+
var isDom = require('is-dom')
|
|
9
|
+
var uid = require('get-uid')
|
|
10
|
+
var throttle = require('per-frame')
|
|
11
|
+
var keypath = require('object-path')
|
|
12
|
+
var type = require('component-type')
|
|
13
|
+
var fast = require('fast.js')
|
|
14
|
+
var utils = require('./utils')
|
|
15
|
+
var svg = require('./svg')
|
|
16
|
+
var defaults = utils.defaults
|
|
17
|
+
var forEach = fast.forEach
|
|
18
|
+
var assign = fast.assign
|
|
19
|
+
var reduce = fast.reduce
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* All of the events can bind to
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
var events = {
|
|
26
|
+
onBlur: 'blur',
|
|
27
|
+
onChange: 'change',
|
|
28
|
+
onClick: 'click',
|
|
29
|
+
onContextMenu: 'contextmenu',
|
|
30
|
+
onCopy: 'copy',
|
|
31
|
+
onCut: 'cut',
|
|
32
|
+
onDoubleClick: 'dblclick',
|
|
33
|
+
onDrag: 'drag',
|
|
34
|
+
onDragEnd: 'dragend',
|
|
35
|
+
onDragEnter: 'dragenter',
|
|
36
|
+
onDragExit: 'dragexit',
|
|
37
|
+
onDragLeave: 'dragleave',
|
|
38
|
+
onDragOver: 'dragover',
|
|
39
|
+
onDragStart: 'dragstart',
|
|
40
|
+
onDrop: 'drop',
|
|
41
|
+
onFocus: 'focus',
|
|
42
|
+
onInput: 'input',
|
|
43
|
+
onKeyDown: 'keydown',
|
|
44
|
+
onKeyUp: 'keyup',
|
|
45
|
+
onMouseDown: 'mousedown',
|
|
46
|
+
onMouseMove: 'mousemove',
|
|
47
|
+
onMouseOut: 'mouseout',
|
|
48
|
+
onMouseOver: 'mouseover',
|
|
49
|
+
onMouseUp: 'mouseup',
|
|
50
|
+
onPaste: 'paste',
|
|
51
|
+
onScroll: 'scroll',
|
|
52
|
+
onSubmit: 'submit',
|
|
53
|
+
onTouchCancel: 'touchcancel',
|
|
54
|
+
onTouchEnd: 'touchend',
|
|
55
|
+
onTouchMove: 'touchmove',
|
|
56
|
+
onTouchStart: 'touchstart'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* These elements won't be pooled
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
var avoidPooling = ['input', 'textarea'];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Expose `dom`.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
module.exports = render
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Render an app to the DOM
|
|
73
|
+
*
|
|
74
|
+
* @param {Application} app
|
|
75
|
+
* @param {HTMLElement} container
|
|
76
|
+
* @param {Object} opts
|
|
77
|
+
*
|
|
78
|
+
* @return {Object}
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
function render (app, container, opts) {
|
|
82
|
+
var frameId
|
|
83
|
+
var isRendering
|
|
84
|
+
var rootId = 'root'
|
|
85
|
+
var currentElement
|
|
86
|
+
var currentNativeElement
|
|
87
|
+
var connections = {}
|
|
88
|
+
var entities = {}
|
|
89
|
+
var pools = {}
|
|
90
|
+
var handlers = {}
|
|
91
|
+
var children = {}
|
|
92
|
+
children[rootId] = {}
|
|
93
|
+
|
|
94
|
+
if (!isDom(container)) {
|
|
95
|
+
throw new Error('Container element must be a DOM element')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Rendering options. Batching is only ever really disabled
|
|
100
|
+
* when running tests, and pooling can be disabled if the user
|
|
101
|
+
* is doing something stupid with the DOM in their components.
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
var options = defaults(assign({}, app.options || {}, opts || {}), {
|
|
105
|
+
pooling: true,
|
|
106
|
+
batching: true,
|
|
107
|
+
validateProps: false
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Listen to DOM events
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
addNativeEventListeners()
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Watch for changes to the app so that we can update
|
|
118
|
+
* the DOM as needed.
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
app.on('unmount', onunmount)
|
|
122
|
+
app.on('mount', onmount)
|
|
123
|
+
app.on('source', onupdate)
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* If the app has already mounted an element, we can just
|
|
127
|
+
* render that straight away.
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
if (app.element) render()
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Teardown the DOM rendering so that it stops
|
|
134
|
+
* rendering and everything can be garbage collected.
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
function teardown () {
|
|
138
|
+
removeNativeEventListeners()
|
|
139
|
+
removeNativeElement()
|
|
140
|
+
app.off('unmount', onunmount)
|
|
141
|
+
app.off('mount', onmount)
|
|
142
|
+
app.off('source', onupdate)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Swap the current rendered node with a new one that is rendered
|
|
147
|
+
* from the new virtual element mounted on the app.
|
|
148
|
+
*
|
|
149
|
+
* @param {VirtualElement} element
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
function onmount () {
|
|
153
|
+
invalidate()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* If the app unmounts an element, we should clear out the current
|
|
158
|
+
* rendered element. This will remove all the entities.
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
function onunmount () {
|
|
162
|
+
removeNativeElement()
|
|
163
|
+
currentElement = null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Update all components that are bound to the source
|
|
168
|
+
*
|
|
169
|
+
* @param {String} name
|
|
170
|
+
* @param {*} data
|
|
171
|
+
*/
|
|
172
|
+
|
|
173
|
+
function onupdate (name, data) {
|
|
174
|
+
connections[name](data)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Render and mount a component to the native dom.
|
|
179
|
+
*
|
|
180
|
+
* @param {Entity} entity
|
|
181
|
+
* @return {HTMLElement}
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
function mountEntity (entity) {
|
|
185
|
+
register(entity)
|
|
186
|
+
setSources(entity)
|
|
187
|
+
children[entity.id] = {}
|
|
188
|
+
entities[entity.id] = entity
|
|
189
|
+
|
|
190
|
+
// commit initial state and props.
|
|
191
|
+
commit(entity)
|
|
192
|
+
|
|
193
|
+
// callback before mounting.
|
|
194
|
+
trigger('beforeMount', entity, [entity.context])
|
|
195
|
+
trigger('beforeRender', entity, [entity.context])
|
|
196
|
+
|
|
197
|
+
// render virtual element.
|
|
198
|
+
var virtualElement = renderEntity(entity)
|
|
199
|
+
// create native element.
|
|
200
|
+
var nativeElement = toNative(entity.id, '0', virtualElement)
|
|
201
|
+
|
|
202
|
+
entity.virtualElement = virtualElement
|
|
203
|
+
entity.nativeElement = nativeElement
|
|
204
|
+
|
|
205
|
+
// callback after mounting.
|
|
206
|
+
trigger('afterRender', entity, [entity.context, nativeElement])
|
|
207
|
+
trigger('afterMount', entity, [entity.context, nativeElement, setState(entity)])
|
|
208
|
+
|
|
209
|
+
return nativeElement
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Remove a component from the native dom.
|
|
214
|
+
*
|
|
215
|
+
* @param {Entity} entity
|
|
216
|
+
*/
|
|
217
|
+
|
|
218
|
+
function unmountEntity (entityId) {
|
|
219
|
+
var entity = entities[entityId]
|
|
220
|
+
if (!entity) return
|
|
221
|
+
trigger('beforeUnmount', entity, [entity.context, entity.nativeElement])
|
|
222
|
+
unmountChildren(entityId)
|
|
223
|
+
removeAllEvents(entityId)
|
|
224
|
+
delete entities[entityId]
|
|
225
|
+
delete children[entityId]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Render the entity and make sure it returns a node
|
|
230
|
+
*
|
|
231
|
+
* @param {Entity} entity
|
|
232
|
+
*
|
|
233
|
+
* @return {VirtualTree}
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
function renderEntity (entity) {
|
|
237
|
+
var component = entity.component
|
|
238
|
+
if (!component.render) throw new Error('Component needs a render function')
|
|
239
|
+
var result = component.render(entity.context, setState(entity))
|
|
240
|
+
if (!result) throw new Error('Render function must return an element.')
|
|
241
|
+
return result
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Whenever setState or setProps is called, we mark the entity
|
|
246
|
+
* as dirty in the renderer. This lets us optimize the re-rendering
|
|
247
|
+
* and skip components that definitely haven't changed.
|
|
248
|
+
*
|
|
249
|
+
* @param {Entity} entity
|
|
250
|
+
*
|
|
251
|
+
* @return {Function} A curried function for updating the state of an entity
|
|
252
|
+
*/
|
|
253
|
+
|
|
254
|
+
function setState (entity) {
|
|
255
|
+
return function (nextState) {
|
|
256
|
+
updateEntityState(entity, nextState)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Tell the app it's dirty and needs to re-render. If batching is disabled
|
|
262
|
+
* we can just trigger a render immediately, otherwise we'll wait until
|
|
263
|
+
* the next available frame.
|
|
264
|
+
*/
|
|
265
|
+
|
|
266
|
+
function invalidate () {
|
|
267
|
+
if (!options.batching) {
|
|
268
|
+
if (!isRendering) render()
|
|
269
|
+
} else {
|
|
270
|
+
if (!frameId) frameId = raf(render)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Update the DOM. If the update fails we stop the loop
|
|
276
|
+
* so we don't get errors on every frame.
|
|
277
|
+
*
|
|
278
|
+
* @api public
|
|
279
|
+
*/
|
|
280
|
+
|
|
281
|
+
function render () {
|
|
282
|
+
// If this is called synchronously we need to
|
|
283
|
+
// cancel any pending future updates
|
|
284
|
+
clearFrame()
|
|
285
|
+
|
|
286
|
+
// If the rendering from the previous frame is still going,
|
|
287
|
+
// we'll just wait until the next frame. Ideally renders should
|
|
288
|
+
// not take over 16ms to stay within a single frame, but this should
|
|
289
|
+
// catch it if it does.
|
|
290
|
+
if (isRendering) {
|
|
291
|
+
frameId = raf(render)
|
|
292
|
+
return
|
|
293
|
+
} else {
|
|
294
|
+
isRendering = true
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// 1. If there isn't a native element rendered for the current mounted element
|
|
298
|
+
// then we need to create it from scratch.
|
|
299
|
+
// 2. If a new element has been mounted, we should diff them.
|
|
300
|
+
// 3. We should update check all child components for changes.
|
|
301
|
+
if (!currentNativeElement) {
|
|
302
|
+
currentElement = app.element
|
|
303
|
+
currentNativeElement = toNative(rootId, '0', currentElement)
|
|
304
|
+
container.appendChild(currentNativeElement)
|
|
305
|
+
} else if (currentElement !== app.element) {
|
|
306
|
+
currentNativeElement = patch(rootId, currentElement, app.element, currentNativeElement)
|
|
307
|
+
currentElement = app.element
|
|
308
|
+
updateChildren(rootId)
|
|
309
|
+
} else {
|
|
310
|
+
updateChildren(rootId)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Allow rendering again.
|
|
314
|
+
isRendering = false
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Clear the current scheduled frame
|
|
319
|
+
*/
|
|
320
|
+
|
|
321
|
+
function clearFrame () {
|
|
322
|
+
if (!frameId) return
|
|
323
|
+
raf.cancel(frameId)
|
|
324
|
+
frameId = 0
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Update a component.
|
|
329
|
+
*
|
|
330
|
+
* The entity is just the data object for a component instance.
|
|
331
|
+
*
|
|
332
|
+
* @param {String} id Component instance id.
|
|
333
|
+
*/
|
|
334
|
+
|
|
335
|
+
function updateEntity (entityId) {
|
|
336
|
+
var entity = entities[entityId]
|
|
337
|
+
setSources(entity)
|
|
338
|
+
|
|
339
|
+
if (!shouldUpdate(entity)) return updateChildren(entityId)
|
|
340
|
+
|
|
341
|
+
var currentTree = entity.virtualElement
|
|
342
|
+
var nextProps = entity.pendingProps
|
|
343
|
+
var nextState = entity.pendingState
|
|
344
|
+
var previousState = entity.context.state
|
|
345
|
+
var previousProps = entity.context.props
|
|
346
|
+
|
|
347
|
+
// hook before rendering. could modify state just before the render occurs.
|
|
348
|
+
trigger('beforeUpdate', entity, [entity.context, nextProps, nextState])
|
|
349
|
+
trigger('beforeRender', entity, [entity.context])
|
|
350
|
+
|
|
351
|
+
// commit state and props.
|
|
352
|
+
commit(entity)
|
|
353
|
+
|
|
354
|
+
// re-render.
|
|
355
|
+
var nextTree = renderEntity(entity)
|
|
356
|
+
|
|
357
|
+
// if the tree is the same we can just skip this component
|
|
358
|
+
// but we should still check the children to see if they're dirty.
|
|
359
|
+
// This allows us to memoize the render function of components.
|
|
360
|
+
if (nextTree === currentTree) return updateChildren(entityId)
|
|
361
|
+
|
|
362
|
+
// apply new virtual tree to native dom.
|
|
363
|
+
entity.nativeElement = patch(entityId, currentTree, nextTree, entity.nativeElement)
|
|
364
|
+
entity.virtualElement = nextTree
|
|
365
|
+
updateChildren(entityId)
|
|
366
|
+
|
|
367
|
+
// trigger render hook
|
|
368
|
+
trigger('afterRender', entity, [entity.context, entity.nativeElement])
|
|
369
|
+
|
|
370
|
+
// trigger afterUpdate after all children have updated.
|
|
371
|
+
trigger('afterUpdate', entity, [entity.context, previousProps, previousState, setState(entity)])
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Update all the children of an entity.
|
|
376
|
+
*
|
|
377
|
+
* @param {String} id Component instance id.
|
|
378
|
+
*/
|
|
379
|
+
|
|
380
|
+
function updateChildren (entityId) {
|
|
381
|
+
forEach(children[entityId], function (childId) {
|
|
382
|
+
updateEntity(childId)
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Remove all of the child entities of an entity
|
|
388
|
+
*
|
|
389
|
+
* @param {Entity} entity
|
|
390
|
+
*/
|
|
391
|
+
|
|
392
|
+
function unmountChildren (entityId) {
|
|
393
|
+
forEach(children[entityId], function (childId) {
|
|
394
|
+
unmountEntity(childId)
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Remove the root element. If this is called synchronously we need to
|
|
400
|
+
* cancel any pending future updates.
|
|
401
|
+
*/
|
|
402
|
+
|
|
403
|
+
function removeNativeElement () {
|
|
404
|
+
clearFrame()
|
|
405
|
+
removeElement(rootId, '0', currentNativeElement)
|
|
406
|
+
currentNativeElement = null
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Create a native element from a virtual element.
|
|
411
|
+
*
|
|
412
|
+
* @param {String} entityId
|
|
413
|
+
* @param {String} path
|
|
414
|
+
* @param {Object} vnode
|
|
415
|
+
*
|
|
416
|
+
* @return {HTMLDocumentFragment}
|
|
417
|
+
*/
|
|
418
|
+
|
|
419
|
+
function toNative (entityId, path, vnode) {
|
|
420
|
+
switch (vnode.type) {
|
|
421
|
+
case 'text': return toNativeText(vnode)
|
|
422
|
+
case 'element': return toNativeElement(entityId, path, vnode)
|
|
423
|
+
case 'component': return toNativeComponent(entityId, path, vnode)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Create a native text element from a virtual element.
|
|
429
|
+
*
|
|
430
|
+
* @param {Object} vnode
|
|
431
|
+
*/
|
|
432
|
+
|
|
433
|
+
function toNativeText (vnode) {
|
|
434
|
+
return document.createTextNode(vnode.data)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Create a native element from a virtual element.
|
|
439
|
+
*/
|
|
440
|
+
|
|
441
|
+
function toNativeElement (entityId, path, vnode) {
|
|
442
|
+
var attributes = vnode.attributes
|
|
443
|
+
var children = vnode.children
|
|
444
|
+
var tagName = vnode.tagName
|
|
445
|
+
var el
|
|
446
|
+
|
|
447
|
+
// create element either from pool or fresh.
|
|
448
|
+
if (!options.pooling || !canPool(tagName)) {
|
|
449
|
+
if (svg.isElement(tagName)) {
|
|
450
|
+
el = document.createElementNS(svg.namespace, tagName)
|
|
451
|
+
} else {
|
|
452
|
+
el = document.createElement(tagName)
|
|
453
|
+
}
|
|
454
|
+
} else {
|
|
455
|
+
var pool = getPool(tagName)
|
|
456
|
+
el = cleanup(pool.pop())
|
|
457
|
+
if (el.parentNode) el.parentNode.removeChild(el)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// set attributes.
|
|
461
|
+
forEach(attributes, function (value, name) {
|
|
462
|
+
setAttribute(entityId, path, el, name, value)
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
// store keys on the native element for fast event handling.
|
|
466
|
+
el.__entity__ = entityId
|
|
467
|
+
el.__path__ = path
|
|
468
|
+
|
|
469
|
+
// add children.
|
|
470
|
+
forEach(children, function (child, i) {
|
|
471
|
+
var childEl = toNative(entityId, path + '.' + i, child)
|
|
472
|
+
if (!childEl.parentNode) el.appendChild(childEl)
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
return el
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Create a native element from a component.
|
|
480
|
+
*/
|
|
481
|
+
|
|
482
|
+
function toNativeComponent (entityId, path, vnode) {
|
|
483
|
+
var child = new Entity(vnode.component, vnode.props)
|
|
484
|
+
children[entityId][path] = child.id
|
|
485
|
+
return mountEntity(child)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Patch an element with the diff from two trees.
|
|
490
|
+
*/
|
|
491
|
+
|
|
492
|
+
function patch (entityId, prev, next, el) {
|
|
493
|
+
return diffNode('0', entityId, prev, next, el)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Create a diff between two tress of nodes.
|
|
498
|
+
*/
|
|
499
|
+
|
|
500
|
+
function diffNode (path, entityId, prev, next, el) {
|
|
501
|
+
// Type changed. This could be from element->text, text->ComponentA,
|
|
502
|
+
// ComponentA->ComponentB etc. But NOT div->span. These are the same type
|
|
503
|
+
// (ElementNode) but different tag name.
|
|
504
|
+
if (prev.type !== next.type) return replaceElement(entityId, path, el, next)
|
|
505
|
+
|
|
506
|
+
switch (next.type) {
|
|
507
|
+
case 'text': return diffText(prev, next, el)
|
|
508
|
+
case 'element': return diffElement(path, entityId, prev, next, el)
|
|
509
|
+
case 'component': return diffComponent(path, entityId, prev, next, el)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Diff two text nodes and update the element.
|
|
515
|
+
*/
|
|
516
|
+
|
|
517
|
+
function diffText (previous, current, el) {
|
|
518
|
+
if (current.data !== previous.data) el.data = current.data
|
|
519
|
+
return el
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Diff the children of an ElementNode.
|
|
524
|
+
*/
|
|
525
|
+
|
|
526
|
+
function diffChildren (path, entityId, prev, next, el) {
|
|
527
|
+
var positions = []
|
|
528
|
+
var hasKeys = false
|
|
529
|
+
var childNodes = Array.prototype.slice.apply(el.childNodes)
|
|
530
|
+
var leftKeys = reduce(prev.children, keyMapReducer, {})
|
|
531
|
+
var rightKeys = reduce(next.children, keyMapReducer, {})
|
|
532
|
+
var currentChildren = assign({}, children[entityId])
|
|
533
|
+
|
|
534
|
+
function keyMapReducer (acc, child) {
|
|
535
|
+
if (child.key != null) {
|
|
536
|
+
acc[child.key] = child
|
|
537
|
+
hasKeys = true
|
|
538
|
+
}
|
|
539
|
+
return acc
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Diff all of the nodes that have keys. This lets us re-used elements
|
|
543
|
+
// instead of overriding them and lets us move them around.
|
|
544
|
+
if (hasKeys) {
|
|
545
|
+
|
|
546
|
+
// Removals
|
|
547
|
+
forEach(leftKeys, function (leftNode, key) {
|
|
548
|
+
if (rightKeys[key] == null) {
|
|
549
|
+
var leftPath = path + '.' + leftNode.index
|
|
550
|
+
removeElement(
|
|
551
|
+
entityId,
|
|
552
|
+
leftPath,
|
|
553
|
+
childNodes[leftNode.index]
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
// Update nodes
|
|
559
|
+
forEach(rightKeys, function (rightNode, key) {
|
|
560
|
+
var leftNode = leftKeys[key]
|
|
561
|
+
|
|
562
|
+
// We only want updates for now
|
|
563
|
+
if (leftNode == null) return
|
|
564
|
+
|
|
565
|
+
var leftPath = path + '.' + leftNode.index
|
|
566
|
+
|
|
567
|
+
// Updated
|
|
568
|
+
positions[rightNode.index] = diffNode(
|
|
569
|
+
leftPath,
|
|
570
|
+
entityId,
|
|
571
|
+
leftNode,
|
|
572
|
+
rightNode,
|
|
573
|
+
childNodes[leftNode.index]
|
|
574
|
+
)
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
// Update the positions of all child components and event handlers
|
|
578
|
+
forEach(rightKeys, function (rightNode, key) {
|
|
579
|
+
var leftNode = leftKeys[key]
|
|
580
|
+
|
|
581
|
+
// We just want elements that have moved around
|
|
582
|
+
if (leftNode == null || leftNode.index === rightNode.index) return
|
|
583
|
+
|
|
584
|
+
var rightPath = path + '.' + rightNode.index
|
|
585
|
+
var leftPath = path + '.' + leftNode.index
|
|
586
|
+
|
|
587
|
+
// Update all the child component path positions to match
|
|
588
|
+
// the latest positions if they've changed. This is a bit hacky.
|
|
589
|
+
forEach(currentChildren, function (childId, childPath) {
|
|
590
|
+
if (leftPath === childPath) {
|
|
591
|
+
delete children[entityId][childPath]
|
|
592
|
+
children[entityId][rightPath] = childId
|
|
593
|
+
}
|
|
594
|
+
})
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
// Now add all of the new nodes last in case their path
|
|
598
|
+
// would have conflicted with one of the previous paths.
|
|
599
|
+
forEach(rightKeys, function (rightNode, key) {
|
|
600
|
+
var rightPath = path + '.' + rightNode.index
|
|
601
|
+
if (leftKeys[key] == null) {
|
|
602
|
+
positions[rightNode.index] = toNative(
|
|
603
|
+
entityId,
|
|
604
|
+
rightPath,
|
|
605
|
+
rightNode
|
|
606
|
+
)
|
|
607
|
+
}
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
} else {
|
|
611
|
+
var maxLength = Math.max(prev.children.length, next.children.length)
|
|
612
|
+
|
|
613
|
+
// Now diff all of the nodes that don't have keys
|
|
614
|
+
for (var i = 0; i < maxLength; i++) {
|
|
615
|
+
var leftNode = prev.children[i]
|
|
616
|
+
var rightNode = next.children[i]
|
|
617
|
+
|
|
618
|
+
// Removals
|
|
619
|
+
if (rightNode == null) {
|
|
620
|
+
removeElement(
|
|
621
|
+
entityId,
|
|
622
|
+
path + '.' + leftNode.index,
|
|
623
|
+
childNodes[leftNode.index]
|
|
624
|
+
)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// New Node
|
|
628
|
+
if (leftNode == null) {
|
|
629
|
+
positions[rightNode.index] = toNative(
|
|
630
|
+
entityId,
|
|
631
|
+
path + '.' + rightNode.index,
|
|
632
|
+
rightNode
|
|
633
|
+
)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Updated
|
|
637
|
+
if (leftNode && rightNode) {
|
|
638
|
+
positions[leftNode.index] = diffNode(
|
|
639
|
+
path + '.' + leftNode.index,
|
|
640
|
+
entityId,
|
|
641
|
+
leftNode,
|
|
642
|
+
rightNode,
|
|
643
|
+
childNodes[leftNode.index]
|
|
644
|
+
)
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Reposition all the elements
|
|
650
|
+
forEach(positions, function (childEl, newPosition) {
|
|
651
|
+
var target = el.childNodes[newPosition]
|
|
652
|
+
if (childEl !== target) {
|
|
653
|
+
if (target) {
|
|
654
|
+
el.insertBefore(childEl, target)
|
|
655
|
+
} else {
|
|
656
|
+
el.appendChild(childEl)
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
})
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Diff the attributes and add/remove them.
|
|
664
|
+
*/
|
|
665
|
+
|
|
666
|
+
function diffAttributes (prev, next, el, entityId, path) {
|
|
667
|
+
var nextAttrs = next.attributes
|
|
668
|
+
var prevAttrs = prev.attributes
|
|
669
|
+
|
|
670
|
+
// add new attrs
|
|
671
|
+
forEach(nextAttrs, function (value, name) {
|
|
672
|
+
if (events[name] || !(name in prevAttrs) || prevAttrs[name] !== value) {
|
|
673
|
+
setAttribute(entityId, path, el, name, value)
|
|
674
|
+
}
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
// remove old attrs
|
|
678
|
+
forEach(prevAttrs, function (value, name) {
|
|
679
|
+
if (!(name in nextAttrs)) {
|
|
680
|
+
removeAttribute(entityId, path, el, name)
|
|
681
|
+
}
|
|
682
|
+
})
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Update a component with the props from the next node. If
|
|
687
|
+
* the component type has changed, we'll just remove the old one
|
|
688
|
+
* and replace it with the new component.
|
|
689
|
+
*/
|
|
690
|
+
|
|
691
|
+
function diffComponent (path, entityId, prev, next, el) {
|
|
692
|
+
if (next.component !== prev.component) {
|
|
693
|
+
return replaceElement(entityId, path, el, next)
|
|
694
|
+
} else {
|
|
695
|
+
var targetId = children[entityId][path]
|
|
696
|
+
|
|
697
|
+
// This is a hack for now
|
|
698
|
+
if (targetId) {
|
|
699
|
+
updateEntityProps(targetId, next.props)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return el
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Diff two element nodes.
|
|
708
|
+
*/
|
|
709
|
+
|
|
710
|
+
function diffElement (path, entityId, prev, next, el) {
|
|
711
|
+
if (next.tagName !== prev.tagName) return replaceElement(entityId, path, el, next)
|
|
712
|
+
diffAttributes(prev, next, el, entityId, path)
|
|
713
|
+
diffChildren(path, entityId, prev, next, el)
|
|
714
|
+
return el
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Removes an element from the DOM and unmounts and components
|
|
719
|
+
* that are within that branch
|
|
720
|
+
*
|
|
721
|
+
* side effects:
|
|
722
|
+
* - removes element from the DOM
|
|
723
|
+
* - removes internal references
|
|
724
|
+
*
|
|
725
|
+
* @param {String} entityId
|
|
726
|
+
* @param {String} path
|
|
727
|
+
* @param {HTMLElement} el
|
|
728
|
+
*/
|
|
729
|
+
|
|
730
|
+
function removeElement (entityId, path, el) {
|
|
731
|
+
var childrenByPath = children[entityId]
|
|
732
|
+
var childId = childrenByPath[path]
|
|
733
|
+
var entityHandlers = handlers[entityId] || {}
|
|
734
|
+
var removals = []
|
|
735
|
+
|
|
736
|
+
// If the path points to a component we should use that
|
|
737
|
+
// components element instead, because it might have moved it.
|
|
738
|
+
if (childId) {
|
|
739
|
+
var child = entities[childId]
|
|
740
|
+
el = child.nativeElement
|
|
741
|
+
unmountEntity(childId)
|
|
742
|
+
removals.push(path)
|
|
743
|
+
} else {
|
|
744
|
+
|
|
745
|
+
// Just remove the text node
|
|
746
|
+
if (!isElement(el)) return el.parentNode.removeChild(el)
|
|
747
|
+
|
|
748
|
+
// Then we need to find any components within this
|
|
749
|
+
// branch and unmount them.
|
|
750
|
+
forEach(childrenByPath, function (childId, childPath) {
|
|
751
|
+
if (childPath === path || isWithinPath(path, childPath)) {
|
|
752
|
+
unmountEntity(childId)
|
|
753
|
+
removals.push(childPath)
|
|
754
|
+
}
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
// Remove all events at this path or below it
|
|
758
|
+
forEach(entityHandlers, function (fn, handlerPath) {
|
|
759
|
+
if (handlerPath === path || isWithinPath(path, handlerPath)) {
|
|
760
|
+
removeEvent(entityId, handlerPath)
|
|
761
|
+
}
|
|
762
|
+
})
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Remove the paths from the object without touching the
|
|
766
|
+
// old object. This keeps the object using fast properties.
|
|
767
|
+
forEach(removals, function (path) {
|
|
768
|
+
delete children[entityId][path]
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
// Remove it from the DOM
|
|
772
|
+
el.parentNode.removeChild(el)
|
|
773
|
+
|
|
774
|
+
// Return all of the elements in this node tree to the pool
|
|
775
|
+
// so that the elements can be re-used.
|
|
776
|
+
if (options.pooling) {
|
|
777
|
+
walk(el, function (node) {
|
|
778
|
+
if (!isElement(node) || !canPool(node.tagName)) return
|
|
779
|
+
getPool(node.tagName.toLowerCase()).push(node)
|
|
780
|
+
})
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Replace an element in the DOM. Removing all components
|
|
786
|
+
* within that element and re-rendering the new virtual node.
|
|
787
|
+
*
|
|
788
|
+
* @param {Entity} entity
|
|
789
|
+
* @param {String} path
|
|
790
|
+
* @param {HTMLElement} el
|
|
791
|
+
* @param {Object} vnode
|
|
792
|
+
*
|
|
793
|
+
* @return {void}
|
|
794
|
+
*/
|
|
795
|
+
|
|
796
|
+
function replaceElement (entityId, path, el, vnode) {
|
|
797
|
+
var parent = el.parentNode
|
|
798
|
+
var index = Array.prototype.indexOf.call(parent.childNodes, el)
|
|
799
|
+
|
|
800
|
+
// remove the previous element and all nested components. This
|
|
801
|
+
// needs to happen before we create the new element so we don't
|
|
802
|
+
// get clashes on the component paths.
|
|
803
|
+
removeElement(entityId, path, el)
|
|
804
|
+
|
|
805
|
+
// then add the new element in there
|
|
806
|
+
var newEl = toNative(entityId, path, vnode)
|
|
807
|
+
var target = parent.childNodes[index]
|
|
808
|
+
|
|
809
|
+
if (target) {
|
|
810
|
+
parent.insertBefore(newEl, target)
|
|
811
|
+
} else {
|
|
812
|
+
parent.appendChild(newEl)
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// update all `entity.nativeElement` references.
|
|
816
|
+
forEach(entities, function (entity) {
|
|
817
|
+
if (entity.nativeElement === el) {
|
|
818
|
+
entity.nativeElement = newEl
|
|
819
|
+
}
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
return newEl
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Set the attribute of an element, performing additional transformations
|
|
827
|
+
* dependning on the attribute name
|
|
828
|
+
*
|
|
829
|
+
* @param {HTMLElement} el
|
|
830
|
+
* @param {String} name
|
|
831
|
+
* @param {String} value
|
|
832
|
+
*/
|
|
833
|
+
|
|
834
|
+
function setAttribute (entityId, path, el, name, value) {
|
|
835
|
+
if (events[name]) {
|
|
836
|
+
addEvent(entityId, path, events[name], value)
|
|
837
|
+
return
|
|
838
|
+
}
|
|
839
|
+
switch (name) {
|
|
840
|
+
case 'value':
|
|
841
|
+
el.value = value
|
|
842
|
+
break
|
|
843
|
+
case 'innerHTML':
|
|
844
|
+
el.innerHTML = value
|
|
845
|
+
break
|
|
846
|
+
case svg.isAttribute(name):
|
|
847
|
+
el.setAttributeNS(svg.namespace, name, value)
|
|
848
|
+
break
|
|
849
|
+
default:
|
|
850
|
+
el.setAttribute(name, value)
|
|
851
|
+
break
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Remove an attribute, performing additional transformations
|
|
857
|
+
* dependning on the attribute name
|
|
858
|
+
*
|
|
859
|
+
* @param {HTMLElement} el
|
|
860
|
+
* @param {String} name
|
|
861
|
+
*/
|
|
862
|
+
|
|
863
|
+
function removeAttribute (entityId, path, el, name) {
|
|
864
|
+
if (events[name]) {
|
|
865
|
+
removeEvent(entityId, path, events[name])
|
|
866
|
+
return
|
|
867
|
+
}
|
|
868
|
+
el.removeAttribute(name)
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Checks to see if one tree path is within
|
|
873
|
+
* another tree path. Example:
|
|
874
|
+
*
|
|
875
|
+
* 0.1 vs 0.1.1 = true
|
|
876
|
+
* 0.2 vs 0.3.5 = false
|
|
877
|
+
*
|
|
878
|
+
* @param {String} target
|
|
879
|
+
* @param {String} path
|
|
880
|
+
*
|
|
881
|
+
* @return {Boolean}
|
|
882
|
+
*/
|
|
883
|
+
|
|
884
|
+
function isWithinPath (target, path) {
|
|
885
|
+
return path.indexOf(target + '.') === 0
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Is the DOM node an element node
|
|
890
|
+
*
|
|
891
|
+
* @param {HTMLElement} el
|
|
892
|
+
*
|
|
893
|
+
* @return {Boolean}
|
|
894
|
+
*/
|
|
895
|
+
|
|
896
|
+
function isElement (el) {
|
|
897
|
+
return !!el.tagName
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Get the pool for a tagName, creating it if it
|
|
902
|
+
* doesn't exist.
|
|
903
|
+
*
|
|
904
|
+
* @param {String} tagName
|
|
905
|
+
*
|
|
906
|
+
* @return {Pool}
|
|
907
|
+
*/
|
|
908
|
+
|
|
909
|
+
function getPool (tagName) {
|
|
910
|
+
var pool = pools[tagName]
|
|
911
|
+
if (!pool) {
|
|
912
|
+
var poolOpts = svg.isElement(tagName) ?
|
|
913
|
+
{ namespace: svg.namespace, tagName: tagName } :
|
|
914
|
+
{ tagName: tagName }
|
|
915
|
+
pool = pools[tagName] = new Pool(poolOpts)
|
|
916
|
+
}
|
|
917
|
+
return pool
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Clean up previously used native element for reuse.
|
|
922
|
+
*
|
|
923
|
+
* @param {HTMLElement} el
|
|
924
|
+
*/
|
|
925
|
+
|
|
926
|
+
function cleanup (el) {
|
|
927
|
+
removeAllChildren(el)
|
|
928
|
+
removeAllAttributes(el)
|
|
929
|
+
return el
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Remove all the attributes from a node
|
|
934
|
+
*
|
|
935
|
+
* @param {HTMLElement} el
|
|
936
|
+
*/
|
|
937
|
+
|
|
938
|
+
function removeAllAttributes (el) {
|
|
939
|
+
for (var i = el.attributes.length - 1; i >= 0; i--) {
|
|
940
|
+
var name = el.attributes[i].name
|
|
941
|
+
el.removeAttribute(name)
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Remove all the child nodes from an element
|
|
947
|
+
*
|
|
948
|
+
* @param {HTMLElement} el
|
|
949
|
+
*/
|
|
950
|
+
|
|
951
|
+
function removeAllChildren (el) {
|
|
952
|
+
while (el.firstChild) el.removeChild(el.firstChild)
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Trigger a hook on a component.
|
|
957
|
+
*
|
|
958
|
+
* @param {String} name Name of hook.
|
|
959
|
+
* @param {Entity} entity The component instance.
|
|
960
|
+
* @param {Array} args To pass along to hook.
|
|
961
|
+
*/
|
|
962
|
+
|
|
963
|
+
function trigger (name, entity, args) {
|
|
964
|
+
if (typeof entity.component[name] !== 'function') return
|
|
965
|
+
entity.component[name].apply(null, args)
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Update an entity to match the latest rendered vode. We always
|
|
970
|
+
* replace the props on the component when composing them. This
|
|
971
|
+
* will trigger a re-render on all children below this point.
|
|
972
|
+
*
|
|
973
|
+
* @param {Entity} entity
|
|
974
|
+
* @param {String} path
|
|
975
|
+
* @param {Object} vnode
|
|
976
|
+
*
|
|
977
|
+
* @return {void}
|
|
978
|
+
*/
|
|
979
|
+
|
|
980
|
+
function updateEntityProps (entityId, nextProps) {
|
|
981
|
+
var entity = entities[entityId]
|
|
982
|
+
entity.pendingProps = nextProps
|
|
983
|
+
entity.dirty = true
|
|
984
|
+
invalidate()
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Update component instance state.
|
|
989
|
+
*/
|
|
990
|
+
|
|
991
|
+
function updateEntityState (entity, nextState) {
|
|
992
|
+
entity.pendingState = assign(entity.pendingState, nextState)
|
|
993
|
+
entity.dirty = true
|
|
994
|
+
invalidate()
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Commit props and state changes to an entity.
|
|
999
|
+
*/
|
|
1000
|
+
|
|
1001
|
+
function commit (entity) {
|
|
1002
|
+
entity.context = {
|
|
1003
|
+
state: entity.pendingState,
|
|
1004
|
+
props: entity.pendingProps,
|
|
1005
|
+
id: entity.id
|
|
1006
|
+
}
|
|
1007
|
+
entity.pendingState = assign({}, entity.context.state)
|
|
1008
|
+
entity.pendingProps = assign({}, entity.context.props)
|
|
1009
|
+
validateProps(entity.context.props, entity.propTypes)
|
|
1010
|
+
entity.dirty = false
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* Try to avoid creating new virtual dom if possible.
|
|
1015
|
+
*
|
|
1016
|
+
* Later we may expose this so you can override, but not there yet.
|
|
1017
|
+
*/
|
|
1018
|
+
|
|
1019
|
+
function shouldUpdate (entity) {
|
|
1020
|
+
if (!entity.dirty) return false
|
|
1021
|
+
if (!entity.component.shouldUpdate) return true
|
|
1022
|
+
var nextProps = entity.pendingProps
|
|
1023
|
+
var nextState = entity.pendingState
|
|
1024
|
+
var bool = entity.component.shouldUpdate(entity.context, nextProps, nextState)
|
|
1025
|
+
return bool
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Register an entity.
|
|
1030
|
+
*
|
|
1031
|
+
* This is mostly to pre-preprocess component properties and values chains.
|
|
1032
|
+
*
|
|
1033
|
+
* The end result is for every component that gets mounted,
|
|
1034
|
+
* you create a set of IO nodes in the network from the `value` definitions.
|
|
1035
|
+
*
|
|
1036
|
+
* @param {Component} component
|
|
1037
|
+
*/
|
|
1038
|
+
|
|
1039
|
+
function register (entity) {
|
|
1040
|
+
var component = entity.component
|
|
1041
|
+
// all entities for this component type.
|
|
1042
|
+
var entities = component.entities = component.entities || {}
|
|
1043
|
+
// add entity to component list
|
|
1044
|
+
entities[entity.id] = entity
|
|
1045
|
+
|
|
1046
|
+
// get 'class-level' sources.
|
|
1047
|
+
var sources = component.sources
|
|
1048
|
+
if (sources) return
|
|
1049
|
+
|
|
1050
|
+
var map = component.sourceToPropertyName = {}
|
|
1051
|
+
component.sources = sources = []
|
|
1052
|
+
var propTypes = component.propTypes
|
|
1053
|
+
for (var name in propTypes) {
|
|
1054
|
+
var data = propTypes[name]
|
|
1055
|
+
if (!data) continue
|
|
1056
|
+
if (!data.source) continue
|
|
1057
|
+
sources.push(data.source)
|
|
1058
|
+
map[data.source] = name
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// send value updates to all component instances.
|
|
1062
|
+
sources.forEach(function (source) {
|
|
1063
|
+
connections[source] = update
|
|
1064
|
+
|
|
1065
|
+
function update (data) {
|
|
1066
|
+
var prop = map[source]
|
|
1067
|
+
for (var entityId in entities) {
|
|
1068
|
+
var entity = entities[entityId]
|
|
1069
|
+
var changes = {}
|
|
1070
|
+
changes[prop] = data
|
|
1071
|
+
updateEntityProps(entityId, assign(entity.pendingProps, changes))
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
})
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Set the initial source value on the entity
|
|
1079
|
+
*
|
|
1080
|
+
* @param {Entity} entity
|
|
1081
|
+
*/
|
|
1082
|
+
|
|
1083
|
+
function setSources (entity) {
|
|
1084
|
+
var component = entity.component
|
|
1085
|
+
var map = component.sourceToPropertyName
|
|
1086
|
+
var sources = component.sources
|
|
1087
|
+
sources.forEach(function (source) {
|
|
1088
|
+
var name = map[source]
|
|
1089
|
+
if (entity.pendingProps[name] != null) return
|
|
1090
|
+
entity.pendingProps[name] = app.sources[source] // get latest value plugged into global store
|
|
1091
|
+
})
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Add all of the DOM event listeners
|
|
1096
|
+
*/
|
|
1097
|
+
|
|
1098
|
+
function addNativeEventListeners () {
|
|
1099
|
+
forEach(events, function (eventType) {
|
|
1100
|
+
document.body.addEventListener(eventType, handleEvent, true)
|
|
1101
|
+
})
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Add all of the DOM event listeners
|
|
1106
|
+
*/
|
|
1107
|
+
|
|
1108
|
+
function removeNativeEventListeners () {
|
|
1109
|
+
forEach(events, function (eventType) {
|
|
1110
|
+
document.body.removeEventListener(eventType, handleEvent, true)
|
|
1111
|
+
})
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Handle an event that has occured within the container
|
|
1116
|
+
*
|
|
1117
|
+
* @param {Event} event
|
|
1118
|
+
*/
|
|
1119
|
+
|
|
1120
|
+
function handleEvent (event) {
|
|
1121
|
+
var target = event.target
|
|
1122
|
+
var entityId = target.__entity__
|
|
1123
|
+
var eventType = event.type
|
|
1124
|
+
|
|
1125
|
+
// Walk up the DOM tree and see if there is a handler
|
|
1126
|
+
// for this event type higher up.
|
|
1127
|
+
while (target && target.__entity__ === entityId) {
|
|
1128
|
+
var fn = keypath.get(handlers, [entityId, target.__path__, eventType])
|
|
1129
|
+
if (fn) {
|
|
1130
|
+
event.delegateTarget = target
|
|
1131
|
+
fn(event)
|
|
1132
|
+
break
|
|
1133
|
+
}
|
|
1134
|
+
target = target.parentNode
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Bind events for an element, and all it's rendered child elements.
|
|
1140
|
+
*
|
|
1141
|
+
* @param {String} path
|
|
1142
|
+
* @param {String} event
|
|
1143
|
+
* @param {Function} fn
|
|
1144
|
+
*/
|
|
1145
|
+
|
|
1146
|
+
function addEvent (entityId, path, eventType, fn) {
|
|
1147
|
+
keypath.set(handlers, [entityId, path, eventType], throttle(function (e) {
|
|
1148
|
+
var entity = entities[entityId]
|
|
1149
|
+
if (entity) {
|
|
1150
|
+
fn.call(null, e, entity.context, setState(entity))
|
|
1151
|
+
} else {
|
|
1152
|
+
fn.call(null, e)
|
|
1153
|
+
}
|
|
1154
|
+
}))
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Unbind events for a entityId
|
|
1159
|
+
*
|
|
1160
|
+
* @param {String} entityId
|
|
1161
|
+
*/
|
|
1162
|
+
|
|
1163
|
+
function removeEvent (entityId, path, eventType) {
|
|
1164
|
+
var args = [entityId]
|
|
1165
|
+
if (path) args.push(path)
|
|
1166
|
+
if (eventType) args.push(eventType)
|
|
1167
|
+
keypath.del(handlers, args)
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Unbind all events from an entity
|
|
1172
|
+
*
|
|
1173
|
+
* @param {Entity} entity
|
|
1174
|
+
*/
|
|
1175
|
+
|
|
1176
|
+
function removeAllEvents (entityId) {
|
|
1177
|
+
keypath.del(handlers, [entityId])
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* Validate the current properties. These simple validations
|
|
1182
|
+
* make it easier to ensure the correct props are passed in.
|
|
1183
|
+
*
|
|
1184
|
+
* Available rules include:
|
|
1185
|
+
*
|
|
1186
|
+
* type: string | array | object | boolean | number | date | function
|
|
1187
|
+
* expects: [] An array of values this prop could equal
|
|
1188
|
+
* optional: Boolean
|
|
1189
|
+
*/
|
|
1190
|
+
|
|
1191
|
+
function validateProps (props, rules) {
|
|
1192
|
+
if (!options.validateProps) return
|
|
1193
|
+
|
|
1194
|
+
// TODO: Only validate in dev mode
|
|
1195
|
+
forEach(rules, function (options, name) {
|
|
1196
|
+
if (name === 'children') return
|
|
1197
|
+
var value = props[name]
|
|
1198
|
+
var optional = (options.optional === true)
|
|
1199
|
+
if (optional && value == null) {
|
|
1200
|
+
return
|
|
1201
|
+
}
|
|
1202
|
+
if (!optional && value == null) {
|
|
1203
|
+
throw new Error('Missing prop named: ' + name)
|
|
1204
|
+
}
|
|
1205
|
+
if (options.type && type(value) !== options.type) {
|
|
1206
|
+
throw new Error('Invalid type for prop named: ' + name)
|
|
1207
|
+
}
|
|
1208
|
+
if (options.expects && options.expects.indexOf(value) < 0) {
|
|
1209
|
+
throw new Error('Invalid value for prop named: ' + name + '. Must be one of ' + options.expects.toString())
|
|
1210
|
+
}
|
|
1211
|
+
})
|
|
1212
|
+
|
|
1213
|
+
// Now check for props that haven't been defined
|
|
1214
|
+
forEach(props, function (value, key) {
|
|
1215
|
+
if (key === 'children') return
|
|
1216
|
+
if (!rules[key]) throw new Error('Unexpected prop named: ' + key)
|
|
1217
|
+
})
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Used for debugging to inspect the current state without
|
|
1222
|
+
* us needing to explicitly manage storing/updating references.
|
|
1223
|
+
*
|
|
1224
|
+
* @return {Object}
|
|
1225
|
+
*/
|
|
1226
|
+
|
|
1227
|
+
function inspect () {
|
|
1228
|
+
return {
|
|
1229
|
+
entities: entities,
|
|
1230
|
+
pools: pools,
|
|
1231
|
+
handlers: handlers,
|
|
1232
|
+
connections: connections,
|
|
1233
|
+
currentElement: currentElement,
|
|
1234
|
+
options: options,
|
|
1235
|
+
app: app,
|
|
1236
|
+
container: container,
|
|
1237
|
+
children: children
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Return an object that lets us completely remove the automatic
|
|
1243
|
+
* DOM rendering and export debugging tools.
|
|
1244
|
+
*/
|
|
1245
|
+
|
|
1246
|
+
return {
|
|
1247
|
+
remove: teardown,
|
|
1248
|
+
inspect: inspect
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* A rendered component instance.
|
|
1254
|
+
*
|
|
1255
|
+
* This manages the lifecycle, props and state of the component.
|
|
1256
|
+
* It's basically just a data object for more straightfoward lookup.
|
|
1257
|
+
*
|
|
1258
|
+
* @param {Component} component
|
|
1259
|
+
* @param {Object} props
|
|
1260
|
+
*/
|
|
1261
|
+
|
|
1262
|
+
function Entity (component, props) {
|
|
1263
|
+
this.id = uid()
|
|
1264
|
+
this.component = component
|
|
1265
|
+
this.propTypes = component.propTypes || {}
|
|
1266
|
+
this.context = {}
|
|
1267
|
+
this.context.id = this.id;
|
|
1268
|
+
this.context.props = defaults(props || {}, component.defaultProps || {})
|
|
1269
|
+
this.context.state = this.component.initialState ? this.component.initialState() : {}
|
|
1270
|
+
this.pendingProps = assign({}, this.context.props)
|
|
1271
|
+
this.pendingState = assign({}, this.context.state)
|
|
1272
|
+
this.dirty = false
|
|
1273
|
+
this.virtualElement = null
|
|
1274
|
+
this.nativeElement = null
|
|
1275
|
+
this.displayName = component.name || 'Component'
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* Should we pool an element?
|
|
1280
|
+
*/
|
|
1281
|
+
|
|
1282
|
+
function canPool(tagName) {
|
|
1283
|
+
return avoidPooling.indexOf(tagName) < 0
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* Get a nested node using a path
|
|
1288
|
+
*
|
|
1289
|
+
* @param {HTMLElement} el The root node '0'
|
|
1290
|
+
* @param {String} path The path string eg. '0.2.43'
|
|
1291
|
+
*/
|
|
1292
|
+
|
|
1293
|
+
function getNodeAtPath(el, path) {
|
|
1294
|
+
var parts = path.split('.')
|
|
1295
|
+
parts.shift()
|
|
1296
|
+
while (parts.length) {
|
|
1297
|
+
el = el.childNodes[parts.pop()]
|
|
1298
|
+
}
|
|
1299
|
+
return el
|
|
1300
|
+
}
|