receptive 0.1.0.alpha

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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9f4b45907709281428dd8d3864bed092143f4e7542c0f1a5b9d9aed2c005b95f
4
+ data.tar.gz: 22a456c8dd79e115ba8cbe2fb3706e75b4f70b31b310ae6412067405616fe34a
5
+ SHA512:
6
+ metadata.gz: b543eb6ee8a093fd62a9b3dce5db88418b1cdfeb102e1a0a461e14cfd9284d74bb2b91c1efeda359f4bcc49def4f0d5bbe137988bedaa1864109387def270dea
7
+ data.tar.gz: d11818c1327dd29fb395304bd299ac1b4cc1d981e0cb3b03ea32d80108de0c82d382cd7fa73722c3ef1cbb4f1595e77c5ef15b18103e401464dedf9ef8cb7075
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.0
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in receptive.gemspec
6
+ gemspec
@@ -0,0 +1,43 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ receptive (0.1.0.alpha)
5
+ opal (>= 0.10.5, < 0.12)
6
+ opal-jquery (~> 0.4.2)
7
+ opal-minitest (= 0.0.5)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ concurrent-ruby (1.0.5)
13
+ hike (1.2.3)
14
+ minitest (5.11.3)
15
+ opal (0.10.5)
16
+ hike (~> 1.2)
17
+ sourcemap (~> 0.1.0)
18
+ sprockets (~> 3.1)
19
+ tilt (>= 1.4)
20
+ opal-jquery (0.4.2)
21
+ opal (>= 0.7.0, < 0.11.0)
22
+ opal-minitest (0.0.5)
23
+ opal (>= 0.8)
24
+ rake (~> 10)
25
+ rack (2.0.4)
26
+ rake (10.5.0)
27
+ sourcemap (0.1.1)
28
+ sprockets (3.7.1)
29
+ concurrent-ruby (~> 1.0)
30
+ rack (> 1, < 3)
31
+ tilt (2.0.8)
32
+
33
+ PLATFORMS
34
+ ruby
35
+
36
+ DEPENDENCIES
37
+ bundler (~> 1.16)
38
+ minitest (~> 5.0)
39
+ rake (~> 10.0)
40
+ receptive!
41
+
42
+ BUNDLED WITH
43
+ 1.16.1
@@ -0,0 +1,52 @@
1
+ # Receptive handbook
2
+
3
+ ## A basic view
4
+
5
+ The README example, annotated:
6
+
7
+ ```html
8
+ <div class="hello-world">
9
+ <input type="text">
10
+ <button>Greet</button>
11
+ <span class="output"></span>
12
+ </div>
13
+ ```
14
+
15
+ ```rb
16
+ require 'opal'
17
+ require 'receptive'
18
+
19
+ class HelloWorld
20
+ extend Receptive::View
21
+
22
+ self.selector = ".hello-world" # this will set the root-node for our view
23
+
24
+ # `on` takes an event, and optionally a selector
25
+ on(:click, 'button') do |event| # leaving the selector black will listen for events
26
+ # on the root-node
27
+
28
+ @greeting_text = find('input').text # `find` operates only on inside the root-node
29
+
30
+ render! # `render!` will execute the render method below
31
+ end # wrapped in `requestAnimationFrame()`
32
+
33
+ def self.render # where possible is advisable to separate data collection
34
+ find('.output').text = @greeting_text # from the rendering/updating of the node, this has been
35
+ end # proven almost invariably useful in managing UI interactions
36
+ end
37
+
38
+ Receptive::App.run
39
+ ```
40
+
41
+ ---
42
+
43
+ *upcoming chapters…*
44
+
45
+ ## Application lifecycle
46
+ ## Incremental DOM
47
+ ## The `render!` method
48
+ ## `window` and `document` events
49
+ ## Why still jQuery?
50
+ ## Using actions and the global status
51
+
52
+
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Elia Schito
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,76 @@
1
+ # Receptive
2
+
3
+ Receptive is a toolkit that will help you add behavior to your existing, server-generated HTML. It's not intended for single-page-application but rather about taking care of specific DOM nodes.
4
+
5
+ This is perfect for you if:
6
+
7
+ - **you already generate all your views from the server** (and don't want to throw everything away following the latest JavaScript fad)
8
+ - **you need your site to work without JavaScript** for SEO or any other reasons
9
+ - **you have a bunch of jQuery stuff around** and always wanted to organize it properly
10
+ - **you like Ruby** and don't want to get caught by all the subtle quirks of JavaScript
11
+
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'receptive'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+
26
+ ## How it works
27
+
28
+ Just keep your HTML as it is:
29
+
30
+ ```html
31
+ <div class="hello-world">
32
+ <input type="text">
33
+ <button>Greet</button>
34
+ <span class="output"></span>
35
+ </div>
36
+ ```
37
+
38
+ Then write a view with a reference to it:
39
+
40
+ ```rb
41
+ class HelloWorld
42
+ extend Receptive::View
43
+ self.selector = ".hello-world"
44
+
45
+ on(:click, 'button') do |event|
46
+ @greeting_text = find('input').text
47
+ render!
48
+ end
49
+
50
+ def self.render
51
+ find('.output').text = @greeting_text
52
+ end
53
+ end
54
+ ```
55
+
56
+ Read on in the [handbook](./HANDBOOK.md)
57
+
58
+ ## Other features
59
+
60
+ - script[async] compatible
61
+ - pjax/turbolinks compatible
62
+
63
+
64
+ ## Development
65
+
66
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
67
+
68
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
69
+
70
+ ## Contributing
71
+
72
+ Bug reports and pull requests are welcome on GitHub at https://github.com/elia/receptive.
73
+
74
+ ## License
75
+
76
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,22 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test_mri) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :test_opal do
11
+ require 'opal'
12
+ ENV['RUBYOPT'] += ' -rbundler/setup -ropal/minitest '
13
+ files = nil
14
+ cd('test-opal') do
15
+ files = Dir['**/*_test.rb'].map{|f| "-r#{f.shellescape}"}
16
+ end
17
+ sh "opal -Ilib-opal -Itest-opal #{files.join(' ')} -e ':done'"
18
+ end
19
+
20
+ task :test => [:test_mri, :test_opal]
21
+
22
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "receptive"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,13 @@
1
+ require 'console'
2
+
3
+ module Receptive
4
+ def self.silence_logs
5
+ debug = $DEBUG
6
+ $DEBUG = false
7
+ yield
8
+ $DEBUG = debug
9
+ end
10
+ end
11
+
12
+ require 'receptive/view'
13
+ require 'receptive/app'
@@ -0,0 +1,48 @@
1
+ require 'receptive/view'
2
+
3
+ module Receptive::App
4
+ extend self
5
+
6
+ def trigger(event_name, data = nil)
7
+ $console.log(
8
+ "%c event %c #{event_name} %c ",
9
+ 'background-color:#eee;color:#444;border-radius:2px 0 0 2px',
10
+ 'background-color:#94D1F5;color:#0A3EA3;border-radius:0 2px 2px 0',
11
+ 'background-color:none;border:none;font-family:monospace;',
12
+ `data != null && data.$inspect && data.$inspect()`,
13
+ self
14
+ ) if $DEBUG
15
+ Document.trigger(event_name, data)
16
+ end
17
+
18
+ def on(event_name, &block)
19
+ Document.on(event_name, &block)
20
+ end
21
+
22
+ def run
23
+ trigger('app:starting', self)
24
+ ::Receptive::View.extenders.each { |c| c.setup_persistent_events }
25
+ trigger('app:started', self)
26
+
27
+ # Managing dom:ready for sync & async script tags
28
+ app_loaded = ->*{ trigger('app:loaded') }
29
+
30
+ %x{
31
+ if (document.readyState === 'complete') {
32
+ setTimeout(#{app_loaded}, 1);
33
+ } else {
34
+ document.addEventListener('DOMContentLoaded', #{app_loaded}, false);
35
+ }
36
+ }
37
+
38
+ # Managing visibility changes
39
+ visibility_change = -> { trigger(`document`.JS[:hidden] ? 'visibility:hidden' : 'visibility:visible') }
40
+ %x{
41
+ if (!document.hidden) {
42
+ setTimeout(#{visibility_change}, 1);
43
+ } else {
44
+ document.addEventListener('visibilitychange', #{visibility_change}, false);
45
+ }
46
+ }
47
+ end
48
+ end
@@ -0,0 +1,1223 @@
1
+ // https://ajax.googleapis.com/ajax/libs/incrementaldom/0.5.1/incremental-dom.js
2
+
3
+ /**
4
+ * @license
5
+ * Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS-IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+
20
+ (function (global, factory) {
21
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
22
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
23
+ (factory((global.IncrementalDOM = {})));
24
+ }(this, function (exports) { 'use strict';
25
+
26
+ /**
27
+ * Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
28
+ *
29
+ * Licensed under the Apache License, Version 2.0 (the "License");
30
+ * you may not use this file except in compliance with the License.
31
+ * You may obtain a copy of the License at
32
+ *
33
+ * http://www.apache.org/licenses/LICENSE-2.0
34
+ *
35
+ * Unless required by applicable law or agreed to in writing, software
36
+ * distributed under the License is distributed on an "AS-IS" BASIS,
37
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
38
+ * See the License for the specific language governing permissions and
39
+ * limitations under the License.
40
+ */
41
+
42
+ /**
43
+ * A cached reference to the hasOwnProperty function.
44
+ */
45
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
46
+
47
+ /**
48
+ * A constructor function that will create blank objects.
49
+ * @constructor
50
+ */
51
+ function Blank() {}
52
+
53
+ Blank.prototype = Object.create(null);
54
+
55
+ /**
56
+ * Used to prevent property collisions between our "map" and its prototype.
57
+ * @param {!Object<string, *>} map The map to check.
58
+ * @param {string} property The property to check.
59
+ * @return {boolean} Whether map has property.
60
+ */
61
+ var has = function (map, property) {
62
+ return hasOwnProperty.call(map, property);
63
+ };
64
+
65
+ /**
66
+ * Creates an map object without a prototype.
67
+ * @return {!Object}
68
+ */
69
+ var createMap = function () {
70
+ return new Blank();
71
+ };
72
+
73
+ /**
74
+ * The property name where we store Incremental DOM data.
75
+ */
76
+ var DATA_PROP = '__incrementalDOMData';
77
+
78
+ /**
79
+ * Keeps track of information needed to perform diffs for a given DOM node.
80
+ * @param {!string} nodeName
81
+ * @param {?string=} key
82
+ * @constructor
83
+ */
84
+ function NodeData(nodeName, key) {
85
+ /**
86
+ * The attributes and their values.
87
+ * @const {!Object<string, *>}
88
+ */
89
+ this.attrs = createMap();
90
+
91
+ /**
92
+ * An array of attribute name/value pairs, used for quickly diffing the
93
+ * incomming attributes to see if the DOM node's attributes need to be
94
+ * updated.
95
+ * @const {Array<*>}
96
+ */
97
+ this.attrsArr = [];
98
+
99
+ /**
100
+ * The incoming attributes for this Node, before they are updated.
101
+ * @const {!Object<string, *>}
102
+ */
103
+ this.newAttrs = createMap();
104
+
105
+ /**
106
+ * Whether or not the statics have been applied for the node yet.
107
+ * {boolean}
108
+ */
109
+ this.staticsApplied = false;
110
+
111
+ /**
112
+ * The key used to identify this node, used to preserve DOM nodes when they
113
+ * move within their parent.
114
+ * @const
115
+ */
116
+ this.key = key;
117
+
118
+ /**
119
+ * Keeps track of children within this node by their key.
120
+ * {!Object<string, !Element>}
121
+ */
122
+ this.keyMap = createMap();
123
+
124
+ /**
125
+ * Whether or not the keyMap is currently valid.
126
+ * @type {boolean}
127
+ */
128
+ this.keyMapValid = true;
129
+
130
+ /**
131
+ * Whether or the associated node is, or contains, a focused Element.
132
+ * @type {boolean}
133
+ */
134
+ this.focused = false;
135
+
136
+ /**
137
+ * The node name for this node.
138
+ * @const {string}
139
+ */
140
+ this.nodeName = nodeName;
141
+
142
+ /**
143
+ * @type {?string}
144
+ */
145
+ this.text = null;
146
+ }
147
+
148
+ /**
149
+ * Initializes a NodeData object for a Node.
150
+ *
151
+ * @param {Node} node The node to initialize data for.
152
+ * @param {string} nodeName The node name of node.
153
+ * @param {?string=} key The key that identifies the node.
154
+ * @return {!NodeData} The newly initialized data object
155
+ */
156
+ var initData = function (node, nodeName, key) {
157
+ var data = new NodeData(nodeName, key);
158
+ node[DATA_PROP] = data;
159
+ return data;
160
+ };
161
+
162
+ /**
163
+ * Retrieves the NodeData object for a Node, creating it if necessary.
164
+ *
165
+ * @param {?Node} node The Node to retrieve the data for.
166
+ * @return {!NodeData} The NodeData for this Node.
167
+ */
168
+ var getData = function (node) {
169
+ importNode(node);
170
+ return node[DATA_PROP];
171
+ };
172
+
173
+ /**
174
+ * Imports node and its subtree, initializing caches.
175
+ *
176
+ * @param {?Node} node The Node to import.
177
+ */
178
+ var importNode = function (node) {
179
+ if (node[DATA_PROP]) {
180
+ return;
181
+ }
182
+
183
+ var isElement = node instanceof Element;
184
+ var nodeName = isElement ? node.localName : node.nodeName;
185
+ var key = isElement ? node.getAttribute('key') : null;
186
+ var data = initData(node, nodeName, key);
187
+
188
+ if (key) {
189
+ getData(node.parentNode).keyMap[key] = node;
190
+ }
191
+
192
+ if (isElement) {
193
+ var attributes = node.attributes;
194
+ var attrs = data.attrs;
195
+ var newAttrs = data.newAttrs;
196
+ var attrsArr = data.attrsArr;
197
+
198
+ for (var i = 0; i < attributes.length; i += 1) {
199
+ var attr = attributes[i];
200
+ var name = attr.name;
201
+ var value = attr.value;
202
+
203
+ attrs[name] = value;
204
+ newAttrs[name] = undefined;
205
+ attrsArr.push(name);
206
+ attrsArr.push(value);
207
+ }
208
+ }
209
+
210
+ for (var child = node.firstChild; child; child = child.nextSibling) {
211
+ importNode(child);
212
+ }
213
+ };
214
+
215
+ /**
216
+ * Gets the namespace to create an element (of a given tag) in.
217
+ * @param {string} tag The tag to get the namespace for.
218
+ * @param {?Node} parent
219
+ * @return {?string} The namespace to create the tag in.
220
+ */
221
+ var getNamespaceForTag = function (tag, parent) {
222
+ if (tag === 'svg') {
223
+ return 'http://www.w3.org/2000/svg';
224
+ }
225
+
226
+ if (getData(parent).nodeName === 'foreignObject') {
227
+ return null;
228
+ }
229
+
230
+ return parent.namespaceURI;
231
+ };
232
+
233
+ /**
234
+ * Creates an Element.
235
+ * @param {Document} doc The document with which to create the Element.
236
+ * @param {?Node} parent
237
+ * @param {string} tag The tag for the Element.
238
+ * @param {?string=} key A key to identify the Element.
239
+ * @return {!Element}
240
+ */
241
+ var createElement = function (doc, parent, tag, key) {
242
+ var namespace = getNamespaceForTag(tag, parent);
243
+ var el = undefined;
244
+
245
+ if (namespace) {
246
+ el = doc.createElementNS(namespace, tag);
247
+ } else {
248
+ el = doc.createElement(tag);
249
+ }
250
+
251
+ initData(el, tag, key);
252
+
253
+ return el;
254
+ };
255
+
256
+ /**
257
+ * Creates a Text Node.
258
+ * @param {Document} doc The document with which to create the Element.
259
+ * @return {!Text}
260
+ */
261
+ var createText = function (doc) {
262
+ var node = doc.createTextNode('');
263
+ initData(node, '#text', null);
264
+ return node;
265
+ };
266
+
267
+ /**
268
+ * Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
269
+ *
270
+ * Licensed under the Apache License, Version 2.0 (the "License");
271
+ * you may not use this file except in compliance with the License.
272
+ * You may obtain a copy of the License at
273
+ *
274
+ * http://www.apache.org/licenses/LICENSE-2.0
275
+ *
276
+ * Unless required by applicable law or agreed to in writing, software
277
+ * distributed under the License is distributed on an "AS-IS" BASIS,
278
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
279
+ * See the License for the specific language governing permissions and
280
+ * limitations under the License.
281
+ */
282
+
283
+ /** @const */
284
+ var notifications = {
285
+ /**
286
+ * Called after patch has compleated with any Nodes that have been created
287
+ * and added to the DOM.
288
+ * @type {?function(Array<!Node>)}
289
+ */
290
+ nodesCreated: null,
291
+
292
+ /**
293
+ * Called after patch has compleated with any Nodes that have been removed
294
+ * from the DOM.
295
+ * Note it's an applications responsibility to handle any childNodes.
296
+ * @type {?function(Array<!Node>)}
297
+ */
298
+ nodesDeleted: null
299
+ };
300
+
301
+ /**
302
+ * Keeps track of the state of a patch.
303
+ * @constructor
304
+ */
305
+ function Context() {
306
+ /**
307
+ * @type {(Array<!Node>|undefined)}
308
+ */
309
+ this.created = notifications.nodesCreated && [];
310
+
311
+ /**
312
+ * @type {(Array<!Node>|undefined)}
313
+ */
314
+ this.deleted = notifications.nodesDeleted && [];
315
+ }
316
+
317
+ /**
318
+ * @param {!Node} node
319
+ */
320
+ Context.prototype.markCreated = function (node) {
321
+ if (this.created) {
322
+ this.created.push(node);
323
+ }
324
+ };
325
+
326
+ /**
327
+ * @param {!Node} node
328
+ */
329
+ Context.prototype.markDeleted = function (node) {
330
+ if (this.deleted) {
331
+ this.deleted.push(node);
332
+ }
333
+ };
334
+
335
+ /**
336
+ * Notifies about nodes that were created during the patch opearation.
337
+ */
338
+ Context.prototype.notifyChanges = function () {
339
+ if (this.created && this.created.length > 0) {
340
+ notifications.nodesCreated(this.created);
341
+ }
342
+
343
+ if (this.deleted && this.deleted.length > 0) {
344
+ notifications.nodesDeleted(this.deleted);
345
+ }
346
+ };
347
+
348
+ /**
349
+ * Copyright 2016 The Incremental DOM Authors. All Rights Reserved.
350
+ *
351
+ * Licensed under the Apache License, Version 2.0 (the "License");
352
+ * you may not use this file except in compliance with the License.
353
+ * You may obtain a copy of the License at
354
+ *
355
+ * http://www.apache.org/licenses/LICENSE-2.0
356
+ *
357
+ * Unless required by applicable law or agreed to in writing, software
358
+ * distributed under the License is distributed on an "AS-IS" BASIS,
359
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
360
+ * See the License for the specific language governing permissions and
361
+ * limitations under the License.
362
+ */
363
+
364
+ /**
365
+ * @param {!Node} node
366
+ * @return {boolean} True if the node the root of a document, false otherwise.
367
+ */
368
+ var isDocumentRoot = function (node) {
369
+ // For ShadowRoots, check if they are a DocumentFragment instead of if they
370
+ // are a ShadowRoot so that this can work in 'use strict' if ShadowRoots are
371
+ // not supported.
372
+ return node instanceof Document || node instanceof DocumentFragment;
373
+ };
374
+
375
+ /**
376
+ * @param {!Node} node The node to start at, inclusive.
377
+ * @param {?Node} root The root ancestor to get until, exclusive.
378
+ * @return {!Array<!Node>} The ancestry of DOM nodes.
379
+ */
380
+ var getAncestry = function (node, root) {
381
+ var ancestry = [];
382
+ var cur = node;
383
+
384
+ while (cur !== root) {
385
+ ancestry.push(cur);
386
+ cur = cur.parentNode;
387
+ }
388
+
389
+ return ancestry;
390
+ };
391
+
392
+ /**
393
+ * @param {!Node} node
394
+ * @return {!Node} The root node of the DOM tree that contains node.
395
+ */
396
+ var getRoot = function (node) {
397
+ var cur = node;
398
+ var prev = cur;
399
+
400
+ while (cur) {
401
+ prev = cur;
402
+ cur = cur.parentNode;
403
+ }
404
+
405
+ return prev;
406
+ };
407
+
408
+ /**
409
+ * @param {!Node} node The node to get the activeElement for.
410
+ * @return {?Element} The activeElement in the Document or ShadowRoot
411
+ * corresponding to node, if present.
412
+ */
413
+ var getActiveElement = function (node) {
414
+ var root = getRoot(node);
415
+ return isDocumentRoot(root) ? root.activeElement : null;
416
+ };
417
+
418
+ /**
419
+ * Gets the path of nodes that contain the focused node in the same document as
420
+ * a reference node, up until the root.
421
+ * @param {!Node} node The reference node to get the activeElement for.
422
+ * @param {?Node} root The root to get the focused path until.
423
+ * @return {!Array<Node>}
424
+ */
425
+ var getFocusedPath = function (node, root) {
426
+ var activeElement = getActiveElement(node);
427
+
428
+ if (!activeElement || !node.contains(activeElement)) {
429
+ return [];
430
+ }
431
+
432
+ return getAncestry(activeElement, root);
433
+ };
434
+
435
+ /**
436
+ * Like insertBefore, but instead instead of moving the desired node, instead
437
+ * moves all the other nodes after.
438
+ * @param {?Node} parentNode
439
+ * @param {!Node} node
440
+ * @param {?Node} referenceNode
441
+ */
442
+ var moveBefore = function (parentNode, node, referenceNode) {
443
+ var insertReferenceNode = node.nextSibling;
444
+ var cur = referenceNode;
445
+
446
+ while (cur !== node) {
447
+ var next = cur.nextSibling;
448
+ parentNode.insertBefore(cur, insertReferenceNode);
449
+ cur = next;
450
+ }
451
+ };
452
+
453
+ /** @type {?Context} */
454
+ var context = null;
455
+
456
+ /** @type {?Node} */
457
+ var currentNode = null;
458
+
459
+ /** @type {?Node} */
460
+ var currentParent = null;
461
+
462
+ /** @type {?Document} */
463
+ var doc = null;
464
+
465
+ /**
466
+ * @param {!Array<Node>} focusPath The nodes to mark.
467
+ * @param {boolean} focused Whether or not they are focused.
468
+ */
469
+ var markFocused = function (focusPath, focused) {
470
+ for (var i = 0; i < focusPath.length; i += 1) {
471
+ getData(focusPath[i]).focused = focused;
472
+ }
473
+ };
474
+
475
+ /**
476
+ * Returns a patcher function that sets up and restores a patch context,
477
+ * running the run function with the provided data.
478
+ * @param {function((!Element|!DocumentFragment),!function(T),T=): ?Node} run
479
+ * @return {function((!Element|!DocumentFragment),!function(T),T=): ?Node}
480
+ * @template T
481
+ */
482
+ var patchFactory = function (run) {
483
+ /**
484
+ * TODO(moz): These annotations won't be necessary once we switch to Closure
485
+ * Compiler's new type inference. Remove these once the switch is done.
486
+ *
487
+ * @param {(!Element|!DocumentFragment)} node
488
+ * @param {!function(T)} fn
489
+ * @param {T=} data
490
+ * @return {?Node} node
491
+ * @template T
492
+ */
493
+ var f = function (node, fn, data) {
494
+ var prevContext = context;
495
+ var prevDoc = doc;
496
+ var prevCurrentNode = currentNode;
497
+ var prevCurrentParent = currentParent;
498
+ var previousInAttributes = false;
499
+ var previousInSkip = false;
500
+
501
+ context = new Context();
502
+ doc = node.ownerDocument;
503
+ currentParent = node.parentNode;
504
+
505
+ if ('production' !== 'production') {}
506
+
507
+ var focusPath = getFocusedPath(node, currentParent);
508
+ markFocused(focusPath, true);
509
+ var retVal = run(node, fn, data);
510
+ markFocused(focusPath, false);
511
+
512
+ if ('production' !== 'production') {}
513
+
514
+ context.notifyChanges();
515
+
516
+ context = prevContext;
517
+ doc = prevDoc;
518
+ currentNode = prevCurrentNode;
519
+ currentParent = prevCurrentParent;
520
+
521
+ return retVal;
522
+ };
523
+ return f;
524
+ };
525
+
526
+ /**
527
+ * Patches the document starting at node with the provided function. This
528
+ * function may be called during an existing patch operation.
529
+ * @param {!Element|!DocumentFragment} node The Element or Document
530
+ * to patch.
531
+ * @param {!function(T)} fn A function containing elementOpen/elementClose/etc.
532
+ * calls that describe the DOM.
533
+ * @param {T=} data An argument passed to fn to represent DOM state.
534
+ * @return {!Node} The patched node.
535
+ * @template T
536
+ */
537
+ var patchInner = patchFactory(function (node, fn, data) {
538
+ currentNode = node;
539
+
540
+ enterNode();
541
+ fn(data);
542
+ exitNode();
543
+
544
+ if ('production' !== 'production') {}
545
+
546
+ return node;
547
+ });
548
+
549
+ /**
550
+ * Patches an Element with the the provided function. Exactly one top level
551
+ * element call should be made corresponding to `node`.
552
+ * @param {!Element} node The Element where the patch should start.
553
+ * @param {!function(T)} fn A function containing elementOpen/elementClose/etc.
554
+ * calls that describe the DOM. This should have at most one top level
555
+ * element call.
556
+ * @param {T=} data An argument passed to fn to represent DOM state.
557
+ * @return {?Node} The node if it was updated, its replacedment or null if it
558
+ * was removed.
559
+ * @template T
560
+ */
561
+ var patchOuter = patchFactory(function (node, fn, data) {
562
+ var startNode = /** @type {!Element} */{ nextSibling: node };
563
+ var expectedNextNode = null;
564
+ var expectedPrevNode = null;
565
+
566
+ if ('production' !== 'production') {}
567
+
568
+ currentNode = startNode;
569
+ fn(data);
570
+
571
+ if ('production' !== 'production') {}
572
+
573
+ if (node !== currentNode && node.parentNode) {
574
+ removeChild(currentParent, node, getData(currentParent).keyMap);
575
+ }
576
+
577
+ return startNode === currentNode ? null : currentNode;
578
+ });
579
+
580
+ /**
581
+ * Checks whether or not the current node matches the specified nodeName and
582
+ * key.
583
+ *
584
+ * @param {!Node} matchNode A node to match the data to.
585
+ * @param {?string} nodeName The nodeName for this node.
586
+ * @param {?string=} key An optional key that identifies a node.
587
+ * @return {boolean} True if the node matches, false otherwise.
588
+ */
589
+ var matches = function (matchNode, nodeName, key) {
590
+ var data = getData(matchNode);
591
+
592
+ // Key check is done using double equals as we want to treat a null key the
593
+ // same as undefined. This should be okay as the only values allowed are
594
+ // strings, null and undefined so the == semantics are not too weird.
595
+ return nodeName === data.nodeName && key == data.key;
596
+ };
597
+
598
+ /**
599
+ * Aligns the virtual Element definition with the actual DOM, moving the
600
+ * corresponding DOM node to the correct location or creating it if necessary.
601
+ * @param {string} nodeName For an Element, this should be a valid tag string.
602
+ * For a Text, this should be #text.
603
+ * @param {?string=} key The key used to identify this element.
604
+ */
605
+ var alignWithDOM = function (nodeName, key) {
606
+ if (currentNode && matches(currentNode, nodeName, key)) {
607
+ return;
608
+ }
609
+
610
+ var parentData = getData(currentParent);
611
+ var currentNodeData = currentNode && getData(currentNode);
612
+ var keyMap = parentData.keyMap;
613
+ var node = undefined;
614
+
615
+ // Check to see if the node has moved within the parent.
616
+ if (key) {
617
+ var keyNode = keyMap[key];
618
+ if (keyNode) {
619
+ if (matches(keyNode, nodeName, key)) {
620
+ node = keyNode;
621
+ } else if (keyNode === currentNode) {
622
+ context.markDeleted(keyNode);
623
+ } else {
624
+ removeChild(currentParent, keyNode, keyMap);
625
+ }
626
+ }
627
+ }
628
+
629
+ // Create the node if it doesn't exist.
630
+ if (!node) {
631
+ if (nodeName === '#text') {
632
+ node = createText(doc);
633
+ } else {
634
+ node = createElement(doc, currentParent, nodeName, key);
635
+ }
636
+
637
+ if (key) {
638
+ keyMap[key] = node;
639
+ }
640
+
641
+ context.markCreated(node);
642
+ }
643
+
644
+ // Re-order the node into the right position, preserving focus if either
645
+ // node or currentNode are focused by making sure that they are not detached
646
+ // from the DOM.
647
+ if (getData(node).focused) {
648
+ // Move everything else before the node.
649
+ moveBefore(currentParent, node, currentNode);
650
+ } else if (currentNodeData && currentNodeData.key && !currentNodeData.focused) {
651
+ // Remove the currentNode, which can always be added back since we hold a
652
+ // reference through the keyMap. This prevents a large number of moves when
653
+ // a keyed item is removed or moved backwards in the DOM.
654
+ currentParent.replaceChild(node, currentNode);
655
+ parentData.keyMapValid = false;
656
+ } else {
657
+ currentParent.insertBefore(node, currentNode);
658
+ }
659
+
660
+ currentNode = node;
661
+ };
662
+
663
+ /**
664
+ * @param {?Node} node
665
+ * @param {?Node} child
666
+ * @param {?Object<string, !Element>} keyMap
667
+ */
668
+ var removeChild = function (node, child, keyMap) {
669
+ node.removeChild(child);
670
+ context.markDeleted( /** @type {!Node}*/child);
671
+
672
+ var key = getData(child).key;
673
+ if (key) {
674
+ delete keyMap[key];
675
+ }
676
+ };
677
+
678
+ /**
679
+ * Clears out any unvisited Nodes, as the corresponding virtual element
680
+ * functions were never called for them.
681
+ */
682
+ var clearUnvisitedDOM = function () {
683
+ var node = currentParent;
684
+ var data = getData(node);
685
+ var keyMap = data.keyMap;
686
+ var keyMapValid = data.keyMapValid;
687
+ var child = node.lastChild;
688
+ var key = undefined;
689
+
690
+ if (child === currentNode && keyMapValid) {
691
+ return;
692
+ }
693
+
694
+ while (child !== currentNode) {
695
+ removeChild(node, child, keyMap);
696
+ child = node.lastChild;
697
+ }
698
+
699
+ // Clean the keyMap, removing any unusued keys.
700
+ if (!keyMapValid) {
701
+ for (key in keyMap) {
702
+ child = keyMap[key];
703
+ if (child.parentNode !== node) {
704
+ context.markDeleted(child);
705
+ delete keyMap[key];
706
+ }
707
+ }
708
+
709
+ data.keyMapValid = true;
710
+ }
711
+ };
712
+
713
+ /**
714
+ * Changes to the first child of the current node.
715
+ */
716
+ var enterNode = function () {
717
+ currentParent = currentNode;
718
+ currentNode = null;
719
+ };
720
+
721
+ /**
722
+ * @return {?Node} The next Node to be patched.
723
+ */
724
+ var getNextNode = function () {
725
+ if (currentNode) {
726
+ return currentNode.nextSibling;
727
+ } else {
728
+ return currentParent.firstChild;
729
+ }
730
+ };
731
+
732
+ /**
733
+ * Changes to the next sibling of the current node.
734
+ */
735
+ var nextNode = function () {
736
+ currentNode = getNextNode();
737
+ };
738
+
739
+ /**
740
+ * Changes to the parent of the current node, removing any unvisited children.
741
+ */
742
+ var exitNode = function () {
743
+ clearUnvisitedDOM();
744
+
745
+ currentNode = currentParent;
746
+ currentParent = currentParent.parentNode;
747
+ };
748
+
749
+ /**
750
+ * Makes sure that the current node is an Element with a matching tagName and
751
+ * key.
752
+ *
753
+ * @param {string} tag The element's tag.
754
+ * @param {?string=} key The key used to identify this element. This can be an
755
+ * empty string, but performance may be better if a unique value is used
756
+ * when iterating over an array of items.
757
+ * @return {!Element} The corresponding Element.
758
+ */
759
+ var coreElementOpen = function (tag, key) {
760
+ nextNode();
761
+ alignWithDOM(tag, key);
762
+ enterNode();
763
+ return (/** @type {!Element} */currentParent
764
+ );
765
+ };
766
+
767
+ /**
768
+ * Closes the currently open Element, removing any unvisited children if
769
+ * necessary.
770
+ *
771
+ * @return {!Element} The corresponding Element.
772
+ */
773
+ var coreElementClose = function () {
774
+ if ('production' !== 'production') {}
775
+
776
+ exitNode();
777
+ return (/** @type {!Element} */currentNode
778
+ );
779
+ };
780
+
781
+ /**
782
+ * Makes sure the current node is a Text node and creates a Text node if it is
783
+ * not.
784
+ *
785
+ * @return {!Text} The corresponding Text Node.
786
+ */
787
+ var coreText = function () {
788
+ nextNode();
789
+ alignWithDOM('#text', null);
790
+ return (/** @type {!Text} */currentNode
791
+ );
792
+ };
793
+
794
+ /**
795
+ * Gets the current Element being patched.
796
+ * @return {!Element}
797
+ */
798
+ var currentElement = function () {
799
+ if ('production' !== 'production') {}
800
+ return (/** @type {!Element} */currentParent
801
+ );
802
+ };
803
+
804
+ /**
805
+ * @return {Node} The Node that will be evaluated for the next instruction.
806
+ */
807
+ var currentPointer = function () {
808
+ if ('production' !== 'production') {}
809
+ return getNextNode();
810
+ };
811
+
812
+ /**
813
+ * Skips the children in a subtree, allowing an Element to be closed without
814
+ * clearing out the children.
815
+ */
816
+ var skip = function () {
817
+ if ('production' !== 'production') {}
818
+ currentNode = currentParent.lastChild;
819
+ };
820
+
821
+ /**
822
+ * Skips the next Node to be patched, moving the pointer forward to the next
823
+ * sibling of the current pointer.
824
+ */
825
+ var skipNode = nextNode;
826
+
827
+ /**
828
+ * Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
829
+ *
830
+ * Licensed under the Apache License, Version 2.0 (the "License");
831
+ * you may not use this file except in compliance with the License.
832
+ * You may obtain a copy of the License at
833
+ *
834
+ * http://www.apache.org/licenses/LICENSE-2.0
835
+ *
836
+ * Unless required by applicable law or agreed to in writing, software
837
+ * distributed under the License is distributed on an "AS-IS" BASIS,
838
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
839
+ * See the License for the specific language governing permissions and
840
+ * limitations under the License.
841
+ */
842
+
843
+ /** @const */
844
+ var symbols = {
845
+ default: '__default'
846
+ };
847
+
848
+ /**
849
+ * @param {string} name
850
+ * @return {string|undefined} The namespace to use for the attribute.
851
+ */
852
+ var getNamespace = function (name) {
853
+ if (name.lastIndexOf('xml:', 0) === 0) {
854
+ return 'http://www.w3.org/XML/1998/namespace';
855
+ }
856
+
857
+ if (name.lastIndexOf('xlink:', 0) === 0) {
858
+ return 'http://www.w3.org/1999/xlink';
859
+ }
860
+ };
861
+
862
+ /**
863
+ * Applies an attribute or property to a given Element. If the value is null
864
+ * or undefined, it is removed from the Element. Otherwise, the value is set
865
+ * as an attribute.
866
+ * @param {!Element} el
867
+ * @param {string} name The attribute's name.
868
+ * @param {?(boolean|number|string)=} value The attribute's value.
869
+ */
870
+ var applyAttr = function (el, name, value) {
871
+ if (value == null) {
872
+ el.removeAttribute(name);
873
+ } else {
874
+ var attrNS = getNamespace(name);
875
+ if (attrNS) {
876
+ el.setAttributeNS(attrNS, name, value);
877
+ } else {
878
+ el.setAttribute(name, value);
879
+ }
880
+ }
881
+ };
882
+
883
+ /**
884
+ * Applies a property to a given Element.
885
+ * @param {!Element} el
886
+ * @param {string} name The property's name.
887
+ * @param {*} value The property's value.
888
+ */
889
+ var applyProp = function (el, name, value) {
890
+ el[name] = value;
891
+ };
892
+
893
+ /**
894
+ * Applies a value to a style declaration. Supports CSS custom properties by
895
+ * setting properties containing a dash using CSSStyleDeclaration.setProperty.
896
+ * @param {CSSStyleDeclaration} style
897
+ * @param {!string} prop
898
+ * @param {*} value
899
+ */
900
+ var setStyleValue = function (style, prop, value) {
901
+ if (prop.indexOf('-') >= 0) {
902
+ style.setProperty(prop, /** @type {string} */value);
903
+ } else {
904
+ style[prop] = value;
905
+ }
906
+ };
907
+
908
+ /**
909
+ * Applies a style to an Element. No vendor prefix expansion is done for
910
+ * property names/values.
911
+ * @param {!Element} el
912
+ * @param {string} name The attribute's name.
913
+ * @param {*} style The style to set. Either a string of css or an object
914
+ * containing property-value pairs.
915
+ */
916
+ var applyStyle = function (el, name, style) {
917
+ if (typeof style === 'string') {
918
+ el.style.cssText = style;
919
+ } else {
920
+ el.style.cssText = '';
921
+ var elStyle = el.style;
922
+ var obj = /** @type {!Object<string,string>} */style;
923
+
924
+ for (var prop in obj) {
925
+ if (has(obj, prop)) {
926
+ setStyleValue(elStyle, prop, obj[prop]);
927
+ }
928
+ }
929
+ }
930
+ };
931
+
932
+ /**
933
+ * Updates a single attribute on an Element.
934
+ * @param {!Element} el
935
+ * @param {string} name The attribute's name.
936
+ * @param {*} value The attribute's value. If the value is an object or
937
+ * function it is set on the Element, otherwise, it is set as an HTML
938
+ * attribute.
939
+ */
940
+ var applyAttributeTyped = function (el, name, value) {
941
+ var type = typeof value;
942
+
943
+ if (type === 'object' || type === 'function') {
944
+ applyProp(el, name, value);
945
+ } else {
946
+ applyAttr(el, name, /** @type {?(boolean|number|string)} */value);
947
+ }
948
+ };
949
+
950
+ /**
951
+ * Calls the appropriate attribute mutator for this attribute.
952
+ * @param {!Element} el
953
+ * @param {string} name The attribute's name.
954
+ * @param {*} value The attribute's value.
955
+ */
956
+ var updateAttribute = function (el, name, value) {
957
+ var data = getData(el);
958
+ var attrs = data.attrs;
959
+
960
+ if (attrs[name] === value) {
961
+ return;
962
+ }
963
+
964
+ var mutator = attributes[name] || attributes[symbols.default];
965
+ mutator(el, name, value);
966
+
967
+ attrs[name] = value;
968
+ };
969
+
970
+ /**
971
+ * A publicly mutable object to provide custom mutators for attributes.
972
+ * @const {!Object<string, function(!Element, string, *)>}
973
+ */
974
+ var attributes = createMap();
975
+
976
+ // Special generic mutator that's called for any attribute that does not
977
+ // have a specific mutator.
978
+ attributes[symbols.default] = applyAttributeTyped;
979
+
980
+ attributes['style'] = applyStyle;
981
+
982
+ /**
983
+ * The offset in the virtual element declaration where the attributes are
984
+ * specified.
985
+ * @const
986
+ */
987
+ var ATTRIBUTES_OFFSET = 3;
988
+
989
+ /**
990
+ * Builds an array of arguments for use with elementOpenStart, attr and
991
+ * elementOpenEnd.
992
+ * @const {Array<*>}
993
+ */
994
+ var argsBuilder = [];
995
+
996
+ /**
997
+ * @param {string} tag The element's tag.
998
+ * @param {?string=} key The key used to identify this element. This can be an
999
+ * empty string, but performance may be better if a unique value is used
1000
+ * when iterating over an array of items.
1001
+ * @param {?Array<*>=} statics An array of attribute name/value pairs of the
1002
+ * static attributes for the Element. These will only be set once when the
1003
+ * Element is created.
1004
+ * @param {...*} var_args, Attribute name/value pairs of the dynamic attributes
1005
+ * for the Element.
1006
+ * @return {!Element} The corresponding Element.
1007
+ */
1008
+ var elementOpen = function (tag, key, statics, var_args) {
1009
+ if ('production' !== 'production') {}
1010
+
1011
+ var node = coreElementOpen(tag, key);
1012
+ var data = getData(node);
1013
+
1014
+ if (!data.staticsApplied) {
1015
+ if (statics) {
1016
+ for (var _i = 0; _i < statics.length; _i += 2) {
1017
+ var name = /** @type {string} */statics[_i];
1018
+ var value = statics[_i + 1];
1019
+ updateAttribute(node, name, value);
1020
+ }
1021
+ }
1022
+ // Down the road, we may want to keep track of the statics array to use it
1023
+ // as an additional signal about whether a node matches or not. For now,
1024
+ // just use a marker so that we do not reapply statics.
1025
+ data.staticsApplied = true;
1026
+ }
1027
+
1028
+ /*
1029
+ * Checks to see if one or more attributes have changed for a given Element.
1030
+ * When no attributes have changed, this is much faster than checking each
1031
+ * individual argument. When attributes have changed, the overhead of this is
1032
+ * minimal.
1033
+ */
1034
+ var attrsArr = data.attrsArr;
1035
+ var newAttrs = data.newAttrs;
1036
+ var isNew = !attrsArr.length;
1037
+ var i = ATTRIBUTES_OFFSET;
1038
+ var j = 0;
1039
+
1040
+ for (; i < arguments.length; i += 2, j += 2) {
1041
+ var _attr = arguments[i];
1042
+ if (isNew) {
1043
+ attrsArr[j] = _attr;
1044
+ newAttrs[_attr] = undefined;
1045
+ } else if (attrsArr[j] !== _attr) {
1046
+ break;
1047
+ }
1048
+
1049
+ var value = arguments[i + 1];
1050
+ if (isNew || attrsArr[j + 1] !== value) {
1051
+ attrsArr[j + 1] = value;
1052
+ updateAttribute(node, _attr, value);
1053
+ }
1054
+ }
1055
+
1056
+ if (i < arguments.length || j < attrsArr.length) {
1057
+ for (; i < arguments.length; i += 1, j += 1) {
1058
+ attrsArr[j] = arguments[i];
1059
+ }
1060
+
1061
+ if (j < attrsArr.length) {
1062
+ attrsArr.length = j;
1063
+ }
1064
+
1065
+ /*
1066
+ * Actually perform the attribute update.
1067
+ */
1068
+ for (i = 0; i < attrsArr.length; i += 2) {
1069
+ var name = /** @type {string} */attrsArr[i];
1070
+ var value = attrsArr[i + 1];
1071
+ newAttrs[name] = value;
1072
+ }
1073
+
1074
+ for (var _attr2 in newAttrs) {
1075
+ updateAttribute(node, _attr2, newAttrs[_attr2]);
1076
+ newAttrs[_attr2] = undefined;
1077
+ }
1078
+ }
1079
+
1080
+ return node;
1081
+ };
1082
+
1083
+ /**
1084
+ * Declares a virtual Element at the current location in the document. This
1085
+ * corresponds to an opening tag and a elementClose tag is required. This is
1086
+ * like elementOpen, but the attributes are defined using the attr function
1087
+ * rather than being passed as arguments. Must be folllowed by 0 or more calls
1088
+ * to attr, then a call to elementOpenEnd.
1089
+ * @param {string} tag The element's tag.
1090
+ * @param {?string=} key The key used to identify this element. This can be an
1091
+ * empty string, but performance may be better if a unique value is used
1092
+ * when iterating over an array of items.
1093
+ * @param {?Array<*>=} statics An array of attribute name/value pairs of the
1094
+ * static attributes for the Element. These will only be set once when the
1095
+ * Element is created.
1096
+ */
1097
+ var elementOpenStart = function (tag, key, statics) {
1098
+ if ('production' !== 'production') {}
1099
+
1100
+ argsBuilder[0] = tag;
1101
+ argsBuilder[1] = key;
1102
+ argsBuilder[2] = statics;
1103
+ };
1104
+
1105
+ /***
1106
+ * Defines a virtual attribute at this point of the DOM. This is only valid
1107
+ * when called between elementOpenStart and elementOpenEnd.
1108
+ *
1109
+ * @param {string} name
1110
+ * @param {*} value
1111
+ */
1112
+ var attr = function (name, value) {
1113
+ if ('production' !== 'production') {}
1114
+
1115
+ argsBuilder.push(name);
1116
+ argsBuilder.push(value);
1117
+ };
1118
+
1119
+ /**
1120
+ * Closes an open tag started with elementOpenStart.
1121
+ * @return {!Element} The corresponding Element.
1122
+ */
1123
+ var elementOpenEnd = function () {
1124
+ if ('production' !== 'production') {}
1125
+
1126
+ var node = elementOpen.apply(null, argsBuilder);
1127
+ argsBuilder.length = 0;
1128
+ return node;
1129
+ };
1130
+
1131
+ /**
1132
+ * Closes an open virtual Element.
1133
+ *
1134
+ * @param {string} tag The element's tag.
1135
+ * @return {!Element} The corresponding Element.
1136
+ */
1137
+ var elementClose = function (tag) {
1138
+ if ('production' !== 'production') {}
1139
+
1140
+ var node = coreElementClose();
1141
+
1142
+ if ('production' !== 'production') {}
1143
+
1144
+ return node;
1145
+ };
1146
+
1147
+ /**
1148
+ * Declares a virtual Element at the current location in the document that has
1149
+ * no children.
1150
+ * @param {string} tag The element's tag.
1151
+ * @param {?string=} key The key used to identify this element. This can be an
1152
+ * empty string, but performance may be better if a unique value is used
1153
+ * when iterating over an array of items.
1154
+ * @param {?Array<*>=} statics An array of attribute name/value pairs of the
1155
+ * static attributes for the Element. These will only be set once when the
1156
+ * Element is created.
1157
+ * @param {...*} var_args Attribute name/value pairs of the dynamic attributes
1158
+ * for the Element.
1159
+ * @return {!Element} The corresponding Element.
1160
+ */
1161
+ var elementVoid = function (tag, key, statics, var_args) {
1162
+ elementOpen.apply(null, arguments);
1163
+ return elementClose(tag);
1164
+ };
1165
+
1166
+ /**
1167
+ * Declares a virtual Text at this point in the document.
1168
+ *
1169
+ * @param {string|number|boolean} value The value of the Text.
1170
+ * @param {...(function((string|number|boolean)):string)} var_args
1171
+ * Functions to format the value which are called only when the value has
1172
+ * changed.
1173
+ * @return {!Text} The corresponding text node.
1174
+ */
1175
+ var text = function (value, var_args) {
1176
+ if ('production' !== 'production') {}
1177
+
1178
+ var node = coreText();
1179
+ var data = getData(node);
1180
+
1181
+ if (data.text !== value) {
1182
+ data.text = /** @type {string} */value;
1183
+
1184
+ var formatted = value;
1185
+ for (var i = 1; i < arguments.length; i += 1) {
1186
+ /*
1187
+ * Call the formatter function directly to prevent leaking arguments.
1188
+ * https://github.com/google/incremental-dom/pull/204#issuecomment-178223574
1189
+ */
1190
+ var fn = arguments[i];
1191
+ formatted = fn(formatted);
1192
+ }
1193
+
1194
+ node.data = formatted;
1195
+ }
1196
+
1197
+ return node;
1198
+ };
1199
+
1200
+ exports.patch = patchInner;
1201
+ exports.patchInner = patchInner;
1202
+ exports.patchOuter = patchOuter;
1203
+ exports.currentElement = currentElement;
1204
+ exports.currentPointer = currentPointer;
1205
+ exports.skip = skip;
1206
+ exports.skipNode = skipNode;
1207
+ exports.elementVoid = elementVoid;
1208
+ exports.elementOpenStart = elementOpenStart;
1209
+ exports.elementOpenEnd = elementOpenEnd;
1210
+ exports.elementOpen = elementOpen;
1211
+ exports.elementClose = elementClose;
1212
+ exports.text = text;
1213
+ exports.attr = attr;
1214
+ exports.symbols = symbols;
1215
+ exports.attributes = attributes;
1216
+ exports.applyAttr = applyAttr;
1217
+ exports.applyProp = applyProp;
1218
+ exports.notifications = notifications;
1219
+ exports.importNode = importNode;
1220
+
1221
+ }));
1222
+
1223
+ //!#! sourceMappingURL=https://ajax.googleapis.com/ajax/libs/incrementaldom/0.5.1/incremental-dom.js.map