clearwater 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5d75fcebb1932de023f32ad43613c30907bfed76
4
- data.tar.gz: 1b0b6fba9eef8bf9539f1c7cc454af550fb40ff2
3
+ metadata.gz: de7a25a8617f75d57327653aa98374069ea6d115
4
+ data.tar.gz: a385d64e3b234b18d281addcec1618d415e7a19f
5
5
  SHA512:
6
- metadata.gz: 70fac50714fb26e2600bf9e23d3ebaee2350b1500cfc21ec36265e0f8d30376e0abb7cabad394f92e5346f51211ec960060fc19d6257993a784c12365d5abf4c
7
- data.tar.gz: 03a1ea608623bb899094124e71cae1708fcd40f4940d52d06de73fe570f26af8279d0e33bac731f995ac0153f031ac52c0362c0ed6ffd2e23761abddd4137197
6
+ metadata.gz: db776bfa51cc49a40ab37dc55f30684af26cefd100c93fa0c603c7fceb323bf17b8b71cceea5e434cfea01193c8b633f1355635c777efbba3189173248d53bf5
7
+ data.tar.gz: b6098e4a9f2497cdf17f8533d8ef085a1c1e4bd7f92751571667ff97ff4b0e9902de0ae476d6783e13d1959d071de36842ea47f266ed8c53daf6ff02d89111ab
@@ -1,3 +1,3 @@
1
1
  module Clearwater
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,16 +1,16 @@
1
- require 'clearwater/router'
2
- require 'clearwater/application_registry'
3
1
  require 'browser'
4
2
  require 'browser/delay'
5
- require 'native'
6
3
  require 'browser/event'
7
4
  require 'browser/animation_frame'
5
+ require 'clearwater/router'
6
+ require 'clearwater/application_registry'
7
+ require 'native'
8
8
 
9
9
  module Clearwater
10
10
  class Application
11
11
  AppRegistry = ApplicationRegistry.new
12
12
 
13
- attr_reader :store, :router, :component, :api_client, :on_render
13
+ attr_reader :router, :component, :api_client, :on_render
14
14
 
15
15
  def self.render
16
16
  AppRegistry.render_all
@@ -43,8 +43,8 @@ module Clearwater
43
43
 
44
44
  def watch_url
45
45
  unless @watching_url
46
- @watching_url = true
47
46
  @window.on('popstate') { render_current_url }
47
+ @watching_url = true
48
48
  end
49
49
  end
50
50
 
@@ -59,7 +59,7 @@ module Clearwater
59
59
  @will_render = true
60
60
 
61
61
  # If the app isn't being shown, wait to render until it is.
62
- if `document.hidden`
62
+ if `!!#@document.hidden`
63
63
  @render_on_visibility_change = true
64
64
  return
65
65
  end
@@ -70,13 +70,7 @@ module Clearwater
70
70
  end
71
71
 
72
72
  def element
73
- @element ||= begin
74
- if @document.body
75
- @document.body
76
- else
77
- nil
78
- end
79
- end
73
+ @element ||= @document.body ? @document.body : nil
80
74
  end
81
75
 
82
76
  def benchmark message
@@ -104,9 +98,8 @@ module Clearwater
104
98
  raise TypeError, "Cannot render to a non-existent element. Make sure the document ready event has been triggered before invoking the application."
105
99
  end
106
100
 
107
- rendered = benchmark('Generated virtual DOM') { component.render }
101
+ rendered = benchmark('Generated virtual DOM') { Component.sanitize_content(component.render) }
108
102
  benchmark('Rendered to actual DOM') { virtual_dom.render rendered }
109
- @last_render = Time.now
110
103
  @will_render = false
111
104
  run_callbacks
112
105
  nil
@@ -1,17 +1,17 @@
1
+ require 'clearwater/component'
2
+
1
3
  module Clearwater
2
4
  module CachedRender
3
-
4
5
  def self.included base
5
6
  %x{
6
- Opal.defn(base, 'type', 'Thunk');
7
- Opal.defn(base, 'render', function(prev) {
7
+ Opal.defn(self, 'type', 'Thunk');
8
+ Opal.defn(self, 'render', function(prev) {
8
9
  var self = this;
9
- var should_render;
10
10
 
11
11
  if(prev && prev.vnode && #{!should_render?(`prev`)}) {
12
12
  return prev.vnode;
13
13
  } else {
14
- return #{sanitize_content(render)};
14
+ return #{Component.sanitize_content(render)};
15
15
  }
16
16
  });
17
17
  }
@@ -136,8 +136,8 @@ module Clearwater
136
136
 
137
137
  VirtualDOM.node(
138
138
  tag_name,
139
- sanitize_attributes(attributes),
140
- sanitize_content(content)
139
+ Component.sanitize_attributes(attributes),
140
+ Component.sanitize_content(content)
141
141
  )
142
142
  end
143
143
 
@@ -145,7 +145,7 @@ module Clearwater
145
145
  router.params_for_path(router.current_path)
146
146
  end
147
147
 
148
- def sanitize_attributes attributes
148
+ def self.sanitize_attributes attributes
149
149
  return attributes unless attributes.is_a? Hash
150
150
 
151
151
  # Allow specifying `class` instead of `class_name`.
@@ -170,15 +170,12 @@ module Clearwater
170
170
  attributes
171
171
  end
172
172
 
173
- def sanitize_content content
173
+ def self.sanitize_content content
174
174
  %x{
175
175
  if(content && content.$$class) {
176
176
  if(content.$$class === Opal.Array) {
177
177
  return #{content.map { |c| `self.$sanitize_content(c)` }};
178
- } else if(content === Opal.nil) {
179
- return '';
180
178
  } else {
181
- var cached_render = content.$cached_render;
182
179
  var render = content.$render;
183
180
 
184
181
  if(content.type === 'Thunk' && typeof(content.render) === 'function') {
@@ -1,4 +1,5 @@
1
1
  require 'clearwater/component'
2
+ require 'browser'
2
3
  require 'browser/history'
3
4
 
4
5
  # TODO: Remove this once opal-browser supports coordinates natively
@@ -35,15 +35,15 @@ module Clearwater
35
35
  def params_for_path path
36
36
  path_parts = path.split("/").reject(&:empty?)
37
37
  canonical_parts = canonical_path_for_path(path).split("/").reject(&:empty?)
38
- params = {}
39
- canonical_parts.each_with_object(params)
40
- .each_with_index { |(part, params), index|
38
+
39
+ canonical_parts.each_with_index.reduce({}) { |params, (part, index)|
41
40
  if part.start_with? ":"
42
- param = part[1..-1].to_sym
43
- params[param] = path_parts[index]
41
+ param = part[1..-1]
42
+ params.merge! param => path_parts[index]
43
+ else
44
+ params
44
45
  end
45
46
  }
46
- params
47
47
  end
48
48
 
49
49
  def canonical_path
@@ -0,0 +1,111 @@
1
+ require 'clearwater/component'
2
+ require 'clearwater/virtual_dom'
3
+
4
+ module Clearwater
5
+ module SVGComponent
6
+ def render
7
+ end
8
+
9
+ SVG_TAGS = {
10
+ a: 'a',
11
+ alt_glyph: 'altGlyph',
12
+ alt_glyph_def: 'altGlyphDef',
13
+ alt_glyph_item: 'altGlyphItem',
14
+ animate: 'animate',
15
+ animate_color: 'animateColor',
16
+ animate_motion: 'animateMotion',
17
+ animate_transform: 'animateTransform',
18
+ circle: 'circle',
19
+ clip_path: 'clipPath',
20
+ color_profile: 'color-profile',
21
+ cursor: 'cursor',
22
+ defs: 'defs',
23
+ desc: 'desc',
24
+ ellipse: 'ellipse',
25
+ fe_blend: 'feBlend',
26
+ fe_color_matrix: 'feColorMatrix',
27
+ fe_component_transfer: 'feComponentTransfer',
28
+ fe_composite: 'feComposite',
29
+ fe_convolve_matrix: 'feConvolveMatrix',
30
+ fe_diffuse_lighting: 'feDiffuseLighting',
31
+ fe_displacement_map: 'feDisplacementMap',
32
+ fe_distant_light: 'feDistantLight',
33
+ fe_flood: 'feFlood',
34
+ fe_func_a: 'feFuncA',
35
+ fe_func_b: 'feFuncB',
36
+ fe_func_g: 'feFuncG',
37
+ fe_func_r: 'feFuncR',
38
+ fe_gaussian_blur: 'feGaussianBlur',
39
+ fe_image: 'feImage',
40
+ fe_merge: 'feMerge',
41
+ fe_merge_node: 'feMergeNode',
42
+ fe_morphology: 'feMorphology',
43
+ fe_offset: 'feOffset',
44
+ fe_point_light: 'fePointLight',
45
+ fe_specular_lighting: 'feSpecularLighting',
46
+ fe_spot_light: 'feSpotLight',
47
+ fe_tile: 'feTile',
48
+ fe_turbulence: 'feTurbulence',
49
+ filter: 'filter',
50
+ font: 'font',
51
+ font_face: 'font-face',
52
+ font_face_format: 'font-face-format',
53
+ font_face_name: 'font-face-name',
54
+ font_face_src: 'font-face-src',
55
+ font_face_uri: 'font-face-uri',
56
+ foreign_object: 'foreignObject',
57
+ g: 'g',
58
+ glyph: 'glyph',
59
+ glyph_ref: 'glyphRef',
60
+ hkern: 'hkern',
61
+ image: 'image',
62
+ line: 'line',
63
+ linear_gradient: 'linearGradient',
64
+ marker: 'marker',
65
+ mask: 'mask',
66
+ metadata: 'metadata',
67
+ missing_glyph: 'missing-glyph',
68
+ mpath: 'mpath',
69
+ path: 'path',
70
+ pattern: 'pattern',
71
+ polygon: 'polygon',
72
+ polyline: 'polyline',
73
+ radial_gradient: 'radialGradient',
74
+ rect: 'rect',
75
+ script: 'script',
76
+ set: 'set',
77
+ stop: 'stop',
78
+ style: 'style',
79
+ svg: 'svg',
80
+ switch: 'switch',
81
+ symbol: 'symbol',
82
+ text: 'text',
83
+ text_path: 'textPath',
84
+ title: 'title',
85
+ tref: 'tref',
86
+ tspan: 'tspan',
87
+ use: 'use',
88
+ view: 'view',
89
+ vkern: 'vkern',
90
+ }
91
+
92
+ SVG_TAGS.each do |method_name, tag_name|
93
+ define_method(method_name) do |attributes, content|
94
+ tag(tag_name, attributes, content)
95
+ end
96
+ end
97
+
98
+ def tag tag_name, attributes=nil, content=nil
99
+ if !(`attributes.$$is_hash || attributes === #{nil}`)
100
+ content = attributes
101
+ attributes = nil
102
+ end
103
+
104
+ VirtualDOM.svg(
105
+ tag_name,
106
+ Component.sanitize_attributes(attributes),
107
+ Component.sanitize_content(content),
108
+ )
109
+ end
110
+ end
111
+ end
@@ -3,23 +3,24 @@ var createElement = require("./vdom/create-element.js")
3
3
 
4
4
  module.exports = createElement
5
5
 
6
- },{"./vdom/create-element.js":13}],2:[function(require,module,exports){
6
+ },{"./vdom/create-element.js":14}],2:[function(require,module,exports){
7
7
  var diff = require("./vtree/diff.js")
8
8
 
9
9
  module.exports = diff
10
10
 
11
- },{"./vtree/diff.js":33}],3:[function(require,module,exports){
11
+ },{"./vtree/diff.js":37}],3:[function(require,module,exports){
12
12
  var h = require("./virtual-hyperscript/index.js")
13
13
 
14
14
  module.exports = h
15
15
 
16
- },{"./virtual-hyperscript/index.js":20}],4:[function(require,module,exports){
16
+ },{"./virtual-hyperscript/index.js":22}],4:[function(require,module,exports){
17
17
  var diff = require("./diff.js")
18
18
  var patch = require("./patch.js")
19
19
  var h = require("./h.js")
20
20
  var create = require("./create-element.js")
21
21
  var VNode = require('./vnode/vnode.js')
22
22
  var VText = require('./vnode/vtext.js')
23
+ var svg = require("./virtual-hyperscript/svg.js")
23
24
 
24
25
  module.exports = {
25
26
  diff: diff,
@@ -27,10 +28,119 @@ module.exports = {
27
28
  h: h,
28
29
  create: create,
29
30
  VNode: VNode,
30
- VText: VText
31
+ VText: VText,
32
+ svg: svg,
31
33
  }
32
34
 
33
- },{"./create-element.js":1,"./diff.js":2,"./h.js":3,"./patch.js":11,"./vnode/vnode.js":29,"./vnode/vtext.js":31}],5:[function(require,module,exports){
35
+ },{"./create-element.js":1,"./diff.js":2,"./h.js":3,"./patch.js":12,"./virtual-hyperscript/svg.js":25,"./vnode/vnode.js":33,"./vnode/vtext.js":35}],5:[function(require,module,exports){
36
+ /*!
37
+ * Cross-Browser Split 1.1.1
38
+ * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
39
+ * Available under the MIT License
40
+ * ECMAScript compliant, uniform cross-browser split method
41
+ */
42
+
43
+ /**
44
+ * Splits a string into an array of strings using a regex or string separator. Matches of the
45
+ * separator are not included in the result array. However, if `separator` is a regex that contains
46
+ * capturing groups, backreferences are spliced into the result each time `separator` is matched.
47
+ * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
48
+ * cross-browser.
49
+ * @param {String} str String to split.
50
+ * @param {RegExp|String} separator Regex or string to use for separating the string.
51
+ * @param {Number} [limit] Maximum number of items to include in the result array.
52
+ * @returns {Array} Array of substrings.
53
+ * @example
54
+ *
55
+ * // Basic use
56
+ * split('a b c d', ' ');
57
+ * // -> ['a', 'b', 'c', 'd']
58
+ *
59
+ * // With limit
60
+ * split('a b c d', ' ', 2);
61
+ * // -> ['a', 'b']
62
+ *
63
+ * // Backreferences in result array
64
+ * split('..word1 word2..', /([a-z]+)(\d+)/i);
65
+ * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
66
+ */
67
+ module.exports = (function split(undef) {
68
+
69
+ var nativeSplit = String.prototype.split,
70
+ compliantExecNpcg = /()??/.exec("")[1] === undef,
71
+ // NPCG: nonparticipating capturing group
72
+ self;
73
+
74
+ self = function(str, separator, limit) {
75
+ // If `separator` is not a regex, use `nativeSplit`
76
+ if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
77
+ return nativeSplit.call(str, separator, limit);
78
+ }
79
+ var output = [],
80
+ flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6
81
+ (separator.sticky ? "y" : ""),
82
+ // Firefox 3+
83
+ lastLastIndex = 0,
84
+ // Make `global` and avoid `lastIndex` issues by working with a copy
85
+ separator = new RegExp(separator.source, flags + "g"),
86
+ separator2, match, lastIndex, lastLength;
87
+ str += ""; // Type-convert
88
+ if (!compliantExecNpcg) {
89
+ // Doesn't need flags gy, but they don't hurt
90
+ separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
91
+ }
92
+ /* Values for `limit`, per the spec:
93
+ * If undefined: 4294967295 // Math.pow(2, 32) - 1
94
+ * If 0, Infinity, or NaN: 0
95
+ * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
96
+ * If negative number: 4294967296 - Math.floor(Math.abs(limit))
97
+ * If other: Type-convert, then use the above rules
98
+ */
99
+ limit = limit === undef ? -1 >>> 0 : // Math.pow(2, 32) - 1
100
+ limit >>> 0; // ToUint32(limit)
101
+ while (match = separator.exec(str)) {
102
+ // `separator.lastIndex` is not reliable cross-browser
103
+ lastIndex = match.index + match[0].length;
104
+ if (lastIndex > lastLastIndex) {
105
+ output.push(str.slice(lastLastIndex, match.index));
106
+ // Fix browsers whose `exec` methods don't consistently return `undefined` for
107
+ // nonparticipating capturing groups
108
+ if (!compliantExecNpcg && match.length > 1) {
109
+ match[0].replace(separator2, function() {
110
+ for (var i = 1; i < arguments.length - 2; i++) {
111
+ if (arguments[i] === undef) {
112
+ match[i] = undef;
113
+ }
114
+ }
115
+ });
116
+ }
117
+ if (match.length > 1 && match.index < str.length) {
118
+ Array.prototype.push.apply(output, match.slice(1));
119
+ }
120
+ lastLength = match[0].length;
121
+ lastLastIndex = lastIndex;
122
+ if (output.length >= limit) {
123
+ break;
124
+ }
125
+ }
126
+ if (separator.lastIndex === match.index) {
127
+ separator.lastIndex++; // Avoid an infinite loop
128
+ }
129
+ }
130
+ if (lastLastIndex === str.length) {
131
+ if (lastLength || !separator.test("")) {
132
+ output.push("");
133
+ }
134
+ } else {
135
+ output.push(str.slice(lastLastIndex));
136
+ }
137
+ return output.length > limit ? output.slice(0, limit) : output;
138
+ };
139
+
140
+ return self;
141
+ })();
142
+
143
+ },{}],6:[function(require,module,exports){
34
144
  'use strict';
35
145
 
36
146
  var OneVersionConstraint = require('individual/one-version');
@@ -52,7 +162,7 @@ function EvStore(elem) {
52
162
  return hash;
53
163
  }
54
164
 
55
- },{"individual/one-version":7}],6:[function(require,module,exports){
165
+ },{"individual/one-version":8}],7:[function(require,module,exports){
56
166
  (function (global){
57
167
  'use strict';
58
168
 
@@ -75,7 +185,7 @@ function Individual(key, value) {
75
185
  }
76
186
 
77
187
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
78
- },{}],7:[function(require,module,exports){
188
+ },{}],8:[function(require,module,exports){
79
189
  'use strict';
80
190
 
81
191
  var Individual = require('./index.js');
@@ -99,7 +209,7 @@ function OneVersion(moduleName, version, defaultValue) {
99
209
  return Individual(key, defaultValue);
100
210
  }
101
211
 
102
- },{"./index.js":6}],8:[function(require,module,exports){
212
+ },{"./index.js":7}],9:[function(require,module,exports){
103
213
  (function (global){
104
214
  var topLevel = typeof global !== 'undefined' ? global :
105
215
  typeof window !== 'undefined' ? window : {}
@@ -118,14 +228,14 @@ if (typeof document !== 'undefined') {
118
228
  }
119
229
 
120
230
  }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
121
- },{"min-document":34}],9:[function(require,module,exports){
231
+ },{"min-document":38}],10:[function(require,module,exports){
122
232
  "use strict";
123
233
 
124
234
  module.exports = function isObject(x) {
125
235
  return typeof x === "object" && x !== null;
126
236
  };
127
237
 
128
- },{}],10:[function(require,module,exports){
238
+ },{}],11:[function(require,module,exports){
129
239
  var nativeIsArray = Array.isArray
130
240
  var toString = Object.prototype.toString
131
241
 
@@ -135,12 +245,12 @@ function isArray(obj) {
135
245
  return toString.call(obj) === "[object Array]"
136
246
  }
137
247
 
138
- },{}],11:[function(require,module,exports){
248
+ },{}],12:[function(require,module,exports){
139
249
  var patch = require("./vdom/patch.js")
140
250
 
141
251
  module.exports = patch
142
252
 
143
- },{"./vdom/patch.js":16}],12:[function(require,module,exports){
253
+ },{"./vdom/patch.js":17}],13:[function(require,module,exports){
144
254
  var isObject = require("is-object")
145
255
  var isHook = require("../vnode/is-vhook.js")
146
256
 
@@ -239,7 +349,7 @@ function getPrototype(value) {
239
349
  }
240
350
  }
241
351
 
242
- },{"../vnode/is-vhook.js":24,"is-object":9}],13:[function(require,module,exports){
352
+ },{"../vnode/is-vhook.js":28,"is-object":10}],14:[function(require,module,exports){
243
353
  var document = require("global/document")
244
354
 
245
355
  var applyProperties = require("./apply-properties")
@@ -287,7 +397,7 @@ function createElement(vnode, opts) {
287
397
  return node
288
398
  }
289
399
 
290
- },{"../vnode/handle-thunk.js":22,"../vnode/is-vnode.js":25,"../vnode/is-vtext.js":26,"../vnode/is-widget.js":27,"./apply-properties":12,"global/document":8}],14:[function(require,module,exports){
400
+ },{"../vnode/handle-thunk.js":26,"../vnode/is-vnode.js":29,"../vnode/is-vtext.js":30,"../vnode/is-widget.js":31,"./apply-properties":13,"global/document":9}],15:[function(require,module,exports){
291
401
  // Maps a virtual DOM tree onto a real DOM tree in an efficient manner.
292
402
  // We don't want to read all of the DOM nodes in the tree so we use
293
403
  // the in-order tree indexing to eliminate recursion down certain branches.
@@ -374,7 +484,7 @@ function ascending(a, b) {
374
484
  return a > b ? 1 : -1
375
485
  }
376
486
 
377
- },{}],15:[function(require,module,exports){
487
+ },{}],16:[function(require,module,exports){
378
488
  var applyProperties = require("./apply-properties")
379
489
 
380
490
  var isWidget = require("../vnode/is-widget.js")
@@ -527,7 +637,7 @@ function replaceRoot(oldRoot, newRoot) {
527
637
  return newRoot;
528
638
  }
529
639
 
530
- },{"../vnode/is-widget.js":27,"../vnode/vpatch.js":30,"./apply-properties":12,"./update-widget":17}],16:[function(require,module,exports){
640
+ },{"../vnode/is-widget.js":31,"../vnode/vpatch.js":34,"./apply-properties":13,"./update-widget":18}],17:[function(require,module,exports){
531
641
  var document = require("global/document")
532
642
  var isArray = require("x-is-array")
533
643
 
@@ -538,7 +648,9 @@ module.exports = patch
538
648
 
539
649
  function patch(rootNode, patches, renderOptions) {
540
650
  renderOptions = renderOptions || {}
541
- renderOptions.patch = renderOptions.patch || patchRecursive
651
+ renderOptions.patch = renderOptions.patch && renderOptions.patch !== patch
652
+ ? renderOptions.patch
653
+ : patchRecursive
542
654
  renderOptions.render = renderOptions.render || render
543
655
 
544
656
  return renderOptions.patch(rootNode, patches, renderOptions)
@@ -607,7 +719,7 @@ function patchIndices(patches) {
607
719
  return indices
608
720
  }
609
721
 
610
- },{"./create-element":13,"./dom-index":14,"./patch-op":15,"global/document":8,"x-is-array":10}],17:[function(require,module,exports){
722
+ },{"./create-element":14,"./dom-index":15,"./patch-op":16,"global/document":9,"x-is-array":11}],18:[function(require,module,exports){
611
723
  var isWidget = require("../vnode/is-widget.js")
612
724
 
613
725
  module.exports = updateWidget
@@ -624,7 +736,44 @@ function updateWidget(a, b) {
624
736
  return false
625
737
  }
626
738
 
627
- },{"../vnode/is-widget.js":27}],18:[function(require,module,exports){
739
+ },{"../vnode/is-widget.js":31}],19:[function(require,module,exports){
740
+ 'use strict';
741
+
742
+ module.exports = AttributeHook;
743
+
744
+ function AttributeHook(namespace, value) {
745
+ if (!(this instanceof AttributeHook)) {
746
+ return new AttributeHook(namespace, value);
747
+ }
748
+
749
+ this.namespace = namespace;
750
+ this.value = value;
751
+ }
752
+
753
+ AttributeHook.prototype.hook = function (node, prop, prev) {
754
+ if (prev && prev.type === 'AttributeHook' &&
755
+ prev.value === this.value &&
756
+ prev.namespace === this.namespace) {
757
+ return;
758
+ }
759
+
760
+ node.setAttributeNS(this.namespace, prop, this.value);
761
+ };
762
+
763
+ AttributeHook.prototype.unhook = function (node, prop, next) {
764
+ if (next && next.type === 'AttributeHook' &&
765
+ next.namespace === this.namespace) {
766
+ return;
767
+ }
768
+
769
+ var colonPosition = prop.indexOf(':');
770
+ var localName = colonPosition > -1 ? prop.substr(colonPosition + 1) : prop;
771
+ node.removeAttributeNS(this.namespace, localName);
772
+ };
773
+
774
+ AttributeHook.prototype.type = 'AttributeHook';
775
+
776
+ },{}],20:[function(require,module,exports){
628
777
  'use strict';
629
778
 
630
779
  var EvStore = require('ev-store');
@@ -653,7 +802,7 @@ EvHook.prototype.unhook = function(node, propertyName) {
653
802
  es[propName] = undefined;
654
803
  };
655
804
 
656
- },{"ev-store":5}],19:[function(require,module,exports){
805
+ },{"ev-store":6}],21:[function(require,module,exports){
657
806
  'use strict';
658
807
 
659
808
  module.exports = SoftSetHook;
@@ -672,7 +821,7 @@ SoftSetHook.prototype.hook = function (node, propertyName) {
672
821
  }
673
822
  };
674
823
 
675
- },{}],20:[function(require,module,exports){
824
+ },{}],22:[function(require,module,exports){
676
825
  'use strict';
677
826
 
678
827
  var isArray = require('x-is-array');
@@ -811,9 +960,14 @@ function errorString(obj) {
811
960
  }
812
961
  }
813
962
 
814
- },{"../vnode/is-thunk":23,"../vnode/is-vhook":24,"../vnode/is-vnode":25,"../vnode/is-vtext":26,"../vnode/is-widget":27,"../vnode/vnode.js":29,"../vnode/vtext.js":31,"./hooks/ev-hook.js":18,"./hooks/soft-set-hook.js":19,"./parse-tag.js":21,"x-is-array":10}],21:[function(require,module,exports){
963
+ },{"../vnode/is-thunk":27,"../vnode/is-vhook":28,"../vnode/is-vnode":29,"../vnode/is-vtext":30,"../vnode/is-widget":31,"../vnode/vnode.js":33,"../vnode/vtext.js":35,"./hooks/ev-hook.js":20,"./hooks/soft-set-hook.js":21,"./parse-tag.js":23,"x-is-array":11}],23:[function(require,module,exports){
815
964
  'use strict';
816
965
 
966
+ var split = require('browser-split');
967
+
968
+ var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/;
969
+ var notClassId = /^\.|#/;
970
+
817
971
  module.exports = parseTag;
818
972
 
819
973
  function parseTag(tag, props) {
@@ -885,7 +1039,386 @@ function splitTag(tag) {
885
1039
  return parts;
886
1040
  }
887
1041
 
888
- },{}],22:[function(require,module,exports){
1042
+ },{"browser-split":5}],24:[function(require,module,exports){
1043
+ 'use strict';
1044
+
1045
+ var DEFAULT_NAMESPACE = null;
1046
+ var EV_NAMESPACE = 'http://www.w3.org/2001/xml-events';
1047
+ var XLINK_NAMESPACE = 'http://www.w3.org/1999/xlink';
1048
+ var XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace';
1049
+
1050
+ // http://www.w3.org/TR/SVGTiny12/attributeTable.html
1051
+ // http://www.w3.org/TR/SVG/attindex.html
1052
+ var SVG_PROPERTIES = {
1053
+ 'about': DEFAULT_NAMESPACE,
1054
+ 'accent-height': DEFAULT_NAMESPACE,
1055
+ 'accumulate': DEFAULT_NAMESPACE,
1056
+ 'additive': DEFAULT_NAMESPACE,
1057
+ 'alignment-baseline': DEFAULT_NAMESPACE,
1058
+ 'alphabetic': DEFAULT_NAMESPACE,
1059
+ 'amplitude': DEFAULT_NAMESPACE,
1060
+ 'arabic-form': DEFAULT_NAMESPACE,
1061
+ 'ascent': DEFAULT_NAMESPACE,
1062
+ 'attributeName': DEFAULT_NAMESPACE,
1063
+ 'attributeType': DEFAULT_NAMESPACE,
1064
+ 'azimuth': DEFAULT_NAMESPACE,
1065
+ 'bandwidth': DEFAULT_NAMESPACE,
1066
+ 'baseFrequency': DEFAULT_NAMESPACE,
1067
+ 'baseProfile': DEFAULT_NAMESPACE,
1068
+ 'baseline-shift': DEFAULT_NAMESPACE,
1069
+ 'bbox': DEFAULT_NAMESPACE,
1070
+ 'begin': DEFAULT_NAMESPACE,
1071
+ 'bias': DEFAULT_NAMESPACE,
1072
+ 'by': DEFAULT_NAMESPACE,
1073
+ 'calcMode': DEFAULT_NAMESPACE,
1074
+ 'cap-height': DEFAULT_NAMESPACE,
1075
+ 'class': DEFAULT_NAMESPACE,
1076
+ 'clip': DEFAULT_NAMESPACE,
1077
+ 'clip-path': DEFAULT_NAMESPACE,
1078
+ 'clip-rule': DEFAULT_NAMESPACE,
1079
+ 'clipPathUnits': DEFAULT_NAMESPACE,
1080
+ 'color': DEFAULT_NAMESPACE,
1081
+ 'color-interpolation': DEFAULT_NAMESPACE,
1082
+ 'color-interpolation-filters': DEFAULT_NAMESPACE,
1083
+ 'color-profile': DEFAULT_NAMESPACE,
1084
+ 'color-rendering': DEFAULT_NAMESPACE,
1085
+ 'content': DEFAULT_NAMESPACE,
1086
+ 'contentScriptType': DEFAULT_NAMESPACE,
1087
+ 'contentStyleType': DEFAULT_NAMESPACE,
1088
+ 'cursor': DEFAULT_NAMESPACE,
1089
+ 'cx': DEFAULT_NAMESPACE,
1090
+ 'cy': DEFAULT_NAMESPACE,
1091
+ 'd': DEFAULT_NAMESPACE,
1092
+ 'datatype': DEFAULT_NAMESPACE,
1093
+ 'defaultAction': DEFAULT_NAMESPACE,
1094
+ 'descent': DEFAULT_NAMESPACE,
1095
+ 'diffuseConstant': DEFAULT_NAMESPACE,
1096
+ 'direction': DEFAULT_NAMESPACE,
1097
+ 'display': DEFAULT_NAMESPACE,
1098
+ 'divisor': DEFAULT_NAMESPACE,
1099
+ 'dominant-baseline': DEFAULT_NAMESPACE,
1100
+ 'dur': DEFAULT_NAMESPACE,
1101
+ 'dx': DEFAULT_NAMESPACE,
1102
+ 'dy': DEFAULT_NAMESPACE,
1103
+ 'edgeMode': DEFAULT_NAMESPACE,
1104
+ 'editable': DEFAULT_NAMESPACE,
1105
+ 'elevation': DEFAULT_NAMESPACE,
1106
+ 'enable-background': DEFAULT_NAMESPACE,
1107
+ 'end': DEFAULT_NAMESPACE,
1108
+ 'ev:event': EV_NAMESPACE,
1109
+ 'event': DEFAULT_NAMESPACE,
1110
+ 'exponent': DEFAULT_NAMESPACE,
1111
+ 'externalResourcesRequired': DEFAULT_NAMESPACE,
1112
+ 'fill': DEFAULT_NAMESPACE,
1113
+ 'fill-opacity': DEFAULT_NAMESPACE,
1114
+ 'fill-rule': DEFAULT_NAMESPACE,
1115
+ 'filter': DEFAULT_NAMESPACE,
1116
+ 'filterRes': DEFAULT_NAMESPACE,
1117
+ 'filterUnits': DEFAULT_NAMESPACE,
1118
+ 'flood-color': DEFAULT_NAMESPACE,
1119
+ 'flood-opacity': DEFAULT_NAMESPACE,
1120
+ 'focusHighlight': DEFAULT_NAMESPACE,
1121
+ 'focusable': DEFAULT_NAMESPACE,
1122
+ 'font-family': DEFAULT_NAMESPACE,
1123
+ 'font-size': DEFAULT_NAMESPACE,
1124
+ 'font-size-adjust': DEFAULT_NAMESPACE,
1125
+ 'font-stretch': DEFAULT_NAMESPACE,
1126
+ 'font-style': DEFAULT_NAMESPACE,
1127
+ 'font-variant': DEFAULT_NAMESPACE,
1128
+ 'font-weight': DEFAULT_NAMESPACE,
1129
+ 'format': DEFAULT_NAMESPACE,
1130
+ 'from': DEFAULT_NAMESPACE,
1131
+ 'fx': DEFAULT_NAMESPACE,
1132
+ 'fy': DEFAULT_NAMESPACE,
1133
+ 'g1': DEFAULT_NAMESPACE,
1134
+ 'g2': DEFAULT_NAMESPACE,
1135
+ 'glyph-name': DEFAULT_NAMESPACE,
1136
+ 'glyph-orientation-horizontal': DEFAULT_NAMESPACE,
1137
+ 'glyph-orientation-vertical': DEFAULT_NAMESPACE,
1138
+ 'glyphRef': DEFAULT_NAMESPACE,
1139
+ 'gradientTransform': DEFAULT_NAMESPACE,
1140
+ 'gradientUnits': DEFAULT_NAMESPACE,
1141
+ 'handler': DEFAULT_NAMESPACE,
1142
+ 'hanging': DEFAULT_NAMESPACE,
1143
+ 'height': DEFAULT_NAMESPACE,
1144
+ 'horiz-adv-x': DEFAULT_NAMESPACE,
1145
+ 'horiz-origin-x': DEFAULT_NAMESPACE,
1146
+ 'horiz-origin-y': DEFAULT_NAMESPACE,
1147
+ 'id': DEFAULT_NAMESPACE,
1148
+ 'ideographic': DEFAULT_NAMESPACE,
1149
+ 'image-rendering': DEFAULT_NAMESPACE,
1150
+ 'in': DEFAULT_NAMESPACE,
1151
+ 'in2': DEFAULT_NAMESPACE,
1152
+ 'initialVisibility': DEFAULT_NAMESPACE,
1153
+ 'intercept': DEFAULT_NAMESPACE,
1154
+ 'k': DEFAULT_NAMESPACE,
1155
+ 'k1': DEFAULT_NAMESPACE,
1156
+ 'k2': DEFAULT_NAMESPACE,
1157
+ 'k3': DEFAULT_NAMESPACE,
1158
+ 'k4': DEFAULT_NAMESPACE,
1159
+ 'kernelMatrix': DEFAULT_NAMESPACE,
1160
+ 'kernelUnitLength': DEFAULT_NAMESPACE,
1161
+ 'kerning': DEFAULT_NAMESPACE,
1162
+ 'keyPoints': DEFAULT_NAMESPACE,
1163
+ 'keySplines': DEFAULT_NAMESPACE,
1164
+ 'keyTimes': DEFAULT_NAMESPACE,
1165
+ 'lang': DEFAULT_NAMESPACE,
1166
+ 'lengthAdjust': DEFAULT_NAMESPACE,
1167
+ 'letter-spacing': DEFAULT_NAMESPACE,
1168
+ 'lighting-color': DEFAULT_NAMESPACE,
1169
+ 'limitingConeAngle': DEFAULT_NAMESPACE,
1170
+ 'local': DEFAULT_NAMESPACE,
1171
+ 'marker-end': DEFAULT_NAMESPACE,
1172
+ 'marker-mid': DEFAULT_NAMESPACE,
1173
+ 'marker-start': DEFAULT_NAMESPACE,
1174
+ 'markerHeight': DEFAULT_NAMESPACE,
1175
+ 'markerUnits': DEFAULT_NAMESPACE,
1176
+ 'markerWidth': DEFAULT_NAMESPACE,
1177
+ 'mask': DEFAULT_NAMESPACE,
1178
+ 'maskContentUnits': DEFAULT_NAMESPACE,
1179
+ 'maskUnits': DEFAULT_NAMESPACE,
1180
+ 'mathematical': DEFAULT_NAMESPACE,
1181
+ 'max': DEFAULT_NAMESPACE,
1182
+ 'media': DEFAULT_NAMESPACE,
1183
+ 'mediaCharacterEncoding': DEFAULT_NAMESPACE,
1184
+ 'mediaContentEncodings': DEFAULT_NAMESPACE,
1185
+ 'mediaSize': DEFAULT_NAMESPACE,
1186
+ 'mediaTime': DEFAULT_NAMESPACE,
1187
+ 'method': DEFAULT_NAMESPACE,
1188
+ 'min': DEFAULT_NAMESPACE,
1189
+ 'mode': DEFAULT_NAMESPACE,
1190
+ 'name': DEFAULT_NAMESPACE,
1191
+ 'nav-down': DEFAULT_NAMESPACE,
1192
+ 'nav-down-left': DEFAULT_NAMESPACE,
1193
+ 'nav-down-right': DEFAULT_NAMESPACE,
1194
+ 'nav-left': DEFAULT_NAMESPACE,
1195
+ 'nav-next': DEFAULT_NAMESPACE,
1196
+ 'nav-prev': DEFAULT_NAMESPACE,
1197
+ 'nav-right': DEFAULT_NAMESPACE,
1198
+ 'nav-up': DEFAULT_NAMESPACE,
1199
+ 'nav-up-left': DEFAULT_NAMESPACE,
1200
+ 'nav-up-right': DEFAULT_NAMESPACE,
1201
+ 'numOctaves': DEFAULT_NAMESPACE,
1202
+ 'observer': DEFAULT_NAMESPACE,
1203
+ 'offset': DEFAULT_NAMESPACE,
1204
+ 'opacity': DEFAULT_NAMESPACE,
1205
+ 'operator': DEFAULT_NAMESPACE,
1206
+ 'order': DEFAULT_NAMESPACE,
1207
+ 'orient': DEFAULT_NAMESPACE,
1208
+ 'orientation': DEFAULT_NAMESPACE,
1209
+ 'origin': DEFAULT_NAMESPACE,
1210
+ 'overflow': DEFAULT_NAMESPACE,
1211
+ 'overlay': DEFAULT_NAMESPACE,
1212
+ 'overline-position': DEFAULT_NAMESPACE,
1213
+ 'overline-thickness': DEFAULT_NAMESPACE,
1214
+ 'panose-1': DEFAULT_NAMESPACE,
1215
+ 'path': DEFAULT_NAMESPACE,
1216
+ 'pathLength': DEFAULT_NAMESPACE,
1217
+ 'patternContentUnits': DEFAULT_NAMESPACE,
1218
+ 'patternTransform': DEFAULT_NAMESPACE,
1219
+ 'patternUnits': DEFAULT_NAMESPACE,
1220
+ 'phase': DEFAULT_NAMESPACE,
1221
+ 'playbackOrder': DEFAULT_NAMESPACE,
1222
+ 'pointer-events': DEFAULT_NAMESPACE,
1223
+ 'points': DEFAULT_NAMESPACE,
1224
+ 'pointsAtX': DEFAULT_NAMESPACE,
1225
+ 'pointsAtY': DEFAULT_NAMESPACE,
1226
+ 'pointsAtZ': DEFAULT_NAMESPACE,
1227
+ 'preserveAlpha': DEFAULT_NAMESPACE,
1228
+ 'preserveAspectRatio': DEFAULT_NAMESPACE,
1229
+ 'primitiveUnits': DEFAULT_NAMESPACE,
1230
+ 'propagate': DEFAULT_NAMESPACE,
1231
+ 'property': DEFAULT_NAMESPACE,
1232
+ 'r': DEFAULT_NAMESPACE,
1233
+ 'radius': DEFAULT_NAMESPACE,
1234
+ 'refX': DEFAULT_NAMESPACE,
1235
+ 'refY': DEFAULT_NAMESPACE,
1236
+ 'rel': DEFAULT_NAMESPACE,
1237
+ 'rendering-intent': DEFAULT_NAMESPACE,
1238
+ 'repeatCount': DEFAULT_NAMESPACE,
1239
+ 'repeatDur': DEFAULT_NAMESPACE,
1240
+ 'requiredExtensions': DEFAULT_NAMESPACE,
1241
+ 'requiredFeatures': DEFAULT_NAMESPACE,
1242
+ 'requiredFonts': DEFAULT_NAMESPACE,
1243
+ 'requiredFormats': DEFAULT_NAMESPACE,
1244
+ 'resource': DEFAULT_NAMESPACE,
1245
+ 'restart': DEFAULT_NAMESPACE,
1246
+ 'result': DEFAULT_NAMESPACE,
1247
+ 'rev': DEFAULT_NAMESPACE,
1248
+ 'role': DEFAULT_NAMESPACE,
1249
+ 'rotate': DEFAULT_NAMESPACE,
1250
+ 'rx': DEFAULT_NAMESPACE,
1251
+ 'ry': DEFAULT_NAMESPACE,
1252
+ 'scale': DEFAULT_NAMESPACE,
1253
+ 'seed': DEFAULT_NAMESPACE,
1254
+ 'shape-rendering': DEFAULT_NAMESPACE,
1255
+ 'slope': DEFAULT_NAMESPACE,
1256
+ 'snapshotTime': DEFAULT_NAMESPACE,
1257
+ 'spacing': DEFAULT_NAMESPACE,
1258
+ 'specularConstant': DEFAULT_NAMESPACE,
1259
+ 'specularExponent': DEFAULT_NAMESPACE,
1260
+ 'spreadMethod': DEFAULT_NAMESPACE,
1261
+ 'startOffset': DEFAULT_NAMESPACE,
1262
+ 'stdDeviation': DEFAULT_NAMESPACE,
1263
+ 'stemh': DEFAULT_NAMESPACE,
1264
+ 'stemv': DEFAULT_NAMESPACE,
1265
+ 'stitchTiles': DEFAULT_NAMESPACE,
1266
+ 'stop-color': DEFAULT_NAMESPACE,
1267
+ 'stop-opacity': DEFAULT_NAMESPACE,
1268
+ 'strikethrough-position': DEFAULT_NAMESPACE,
1269
+ 'strikethrough-thickness': DEFAULT_NAMESPACE,
1270
+ 'string': DEFAULT_NAMESPACE,
1271
+ 'stroke': DEFAULT_NAMESPACE,
1272
+ 'stroke-dasharray': DEFAULT_NAMESPACE,
1273
+ 'stroke-dashoffset': DEFAULT_NAMESPACE,
1274
+ 'stroke-linecap': DEFAULT_NAMESPACE,
1275
+ 'stroke-linejoin': DEFAULT_NAMESPACE,
1276
+ 'stroke-miterlimit': DEFAULT_NAMESPACE,
1277
+ 'stroke-opacity': DEFAULT_NAMESPACE,
1278
+ 'stroke-width': DEFAULT_NAMESPACE,
1279
+ 'surfaceScale': DEFAULT_NAMESPACE,
1280
+ 'syncBehavior': DEFAULT_NAMESPACE,
1281
+ 'syncBehaviorDefault': DEFAULT_NAMESPACE,
1282
+ 'syncMaster': DEFAULT_NAMESPACE,
1283
+ 'syncTolerance': DEFAULT_NAMESPACE,
1284
+ 'syncToleranceDefault': DEFAULT_NAMESPACE,
1285
+ 'systemLanguage': DEFAULT_NAMESPACE,
1286
+ 'tableValues': DEFAULT_NAMESPACE,
1287
+ 'target': DEFAULT_NAMESPACE,
1288
+ 'targetX': DEFAULT_NAMESPACE,
1289
+ 'targetY': DEFAULT_NAMESPACE,
1290
+ 'text-anchor': DEFAULT_NAMESPACE,
1291
+ 'text-decoration': DEFAULT_NAMESPACE,
1292
+ 'text-rendering': DEFAULT_NAMESPACE,
1293
+ 'textLength': DEFAULT_NAMESPACE,
1294
+ 'timelineBegin': DEFAULT_NAMESPACE,
1295
+ 'title': DEFAULT_NAMESPACE,
1296
+ 'to': DEFAULT_NAMESPACE,
1297
+ 'transform': DEFAULT_NAMESPACE,
1298
+ 'transformBehavior': DEFAULT_NAMESPACE,
1299
+ 'type': DEFAULT_NAMESPACE,
1300
+ 'typeof': DEFAULT_NAMESPACE,
1301
+ 'u1': DEFAULT_NAMESPACE,
1302
+ 'u2': DEFAULT_NAMESPACE,
1303
+ 'underline-position': DEFAULT_NAMESPACE,
1304
+ 'underline-thickness': DEFAULT_NAMESPACE,
1305
+ 'unicode': DEFAULT_NAMESPACE,
1306
+ 'unicode-bidi': DEFAULT_NAMESPACE,
1307
+ 'unicode-range': DEFAULT_NAMESPACE,
1308
+ 'units-per-em': DEFAULT_NAMESPACE,
1309
+ 'v-alphabetic': DEFAULT_NAMESPACE,
1310
+ 'v-hanging': DEFAULT_NAMESPACE,
1311
+ 'v-ideographic': DEFAULT_NAMESPACE,
1312
+ 'v-mathematical': DEFAULT_NAMESPACE,
1313
+ 'values': DEFAULT_NAMESPACE,
1314
+ 'version': DEFAULT_NAMESPACE,
1315
+ 'vert-adv-y': DEFAULT_NAMESPACE,
1316
+ 'vert-origin-x': DEFAULT_NAMESPACE,
1317
+ 'vert-origin-y': DEFAULT_NAMESPACE,
1318
+ 'viewBox': DEFAULT_NAMESPACE,
1319
+ 'viewTarget': DEFAULT_NAMESPACE,
1320
+ 'visibility': DEFAULT_NAMESPACE,
1321
+ 'width': DEFAULT_NAMESPACE,
1322
+ 'widths': DEFAULT_NAMESPACE,
1323
+ 'word-spacing': DEFAULT_NAMESPACE,
1324
+ 'writing-mode': DEFAULT_NAMESPACE,
1325
+ 'x': DEFAULT_NAMESPACE,
1326
+ 'x-height': DEFAULT_NAMESPACE,
1327
+ 'x1': DEFAULT_NAMESPACE,
1328
+ 'x2': DEFAULT_NAMESPACE,
1329
+ 'xChannelSelector': DEFAULT_NAMESPACE,
1330
+ 'xlink:actuate': XLINK_NAMESPACE,
1331
+ 'xlink:arcrole': XLINK_NAMESPACE,
1332
+ 'xlink:href': XLINK_NAMESPACE,
1333
+ 'xlink:role': XLINK_NAMESPACE,
1334
+ 'xlink:show': XLINK_NAMESPACE,
1335
+ 'xlink:title': XLINK_NAMESPACE,
1336
+ 'xlink:type': XLINK_NAMESPACE,
1337
+ 'xml:base': XML_NAMESPACE,
1338
+ 'xml:id': XML_NAMESPACE,
1339
+ 'xml:lang': XML_NAMESPACE,
1340
+ 'xml:space': XML_NAMESPACE,
1341
+ 'y': DEFAULT_NAMESPACE,
1342
+ 'y1': DEFAULT_NAMESPACE,
1343
+ 'y2': DEFAULT_NAMESPACE,
1344
+ 'yChannelSelector': DEFAULT_NAMESPACE,
1345
+ 'z': DEFAULT_NAMESPACE,
1346
+ 'zoomAndPan': DEFAULT_NAMESPACE
1347
+ };
1348
+
1349
+ module.exports = SVGAttributeNamespace;
1350
+
1351
+ function SVGAttributeNamespace(value) {
1352
+ if (SVG_PROPERTIES.hasOwnProperty(value)) {
1353
+ return SVG_PROPERTIES[value];
1354
+ }
1355
+ }
1356
+
1357
+ },{}],25:[function(require,module,exports){
1358
+ 'use strict';
1359
+
1360
+ var isArray = require('x-is-array');
1361
+
1362
+ var h = require('./index.js');
1363
+
1364
+
1365
+ var SVGAttributeNamespace = require('./svg-attribute-namespace');
1366
+ var attributeHook = require('./hooks/attribute-hook');
1367
+
1368
+ var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
1369
+
1370
+ module.exports = svg;
1371
+
1372
+ function svg(tagName, properties, children) {
1373
+ if (!children && isChildren(properties)) {
1374
+ children = properties;
1375
+ properties = {};
1376
+ }
1377
+
1378
+ properties = properties || {};
1379
+
1380
+ // set namespace for svg
1381
+ properties.namespace = SVG_NAMESPACE;
1382
+
1383
+ var attributes = properties.attributes || (properties.attributes = {});
1384
+
1385
+ for (var key in properties) {
1386
+ if (!properties.hasOwnProperty(key)) {
1387
+ continue;
1388
+ }
1389
+
1390
+ var namespace = SVGAttributeNamespace(key);
1391
+
1392
+ if (namespace === undefined) { // not a svg attribute
1393
+ continue;
1394
+ }
1395
+
1396
+ var value = properties[key];
1397
+
1398
+ if (typeof value !== 'string' &&
1399
+ typeof value !== 'number' &&
1400
+ typeof value !== 'boolean'
1401
+ ) {
1402
+ continue;
1403
+ }
1404
+
1405
+ if (namespace !== null) { // namespaced attribute
1406
+ properties[key] = attributeHook(namespace, value);
1407
+ continue;
1408
+ }
1409
+
1410
+ attributes[key] = value
1411
+ properties[key] = undefined
1412
+ }
1413
+
1414
+ return h(tagName, properties, children);
1415
+ }
1416
+
1417
+ function isChildren(x) {
1418
+ return typeof x === 'string' || isArray(x);
1419
+ }
1420
+
1421
+ },{"./hooks/attribute-hook":19,"./index.js":22,"./svg-attribute-namespace":24,"x-is-array":11}],26:[function(require,module,exports){
889
1422
  var isVNode = require("./is-vnode")
890
1423
  var isVText = require("./is-vtext")
891
1424
  var isWidget = require("./is-widget")
@@ -927,14 +1460,14 @@ function renderThunk(thunk, previous) {
927
1460
  return renderedThunk
928
1461
  }
929
1462
 
930
- },{"./is-thunk":23,"./is-vnode":25,"./is-vtext":26,"./is-widget":27}],23:[function(require,module,exports){
1463
+ },{"./is-thunk":27,"./is-vnode":29,"./is-vtext":30,"./is-widget":31}],27:[function(require,module,exports){
931
1464
  module.exports = isThunk
932
1465
 
933
1466
  function isThunk(t) {
934
1467
  return t && t.type === "Thunk"
935
1468
  }
936
1469
 
937
- },{}],24:[function(require,module,exports){
1470
+ },{}],28:[function(require,module,exports){
938
1471
  module.exports = isHook
939
1472
 
940
1473
  function isHook(hook) {
@@ -943,7 +1476,7 @@ function isHook(hook) {
943
1476
  typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook"))
944
1477
  }
945
1478
 
946
- },{}],25:[function(require,module,exports){
1479
+ },{}],29:[function(require,module,exports){
947
1480
  var version = require("./version")
948
1481
 
949
1482
  module.exports = isVirtualNode
@@ -952,7 +1485,7 @@ function isVirtualNode(x) {
952
1485
  return x && x.type === "VirtualNode" && x.version === version
953
1486
  }
954
1487
 
955
- },{"./version":28}],26:[function(require,module,exports){
1488
+ },{"./version":32}],30:[function(require,module,exports){
956
1489
  var version = require("./version")
957
1490
 
958
1491
  module.exports = isVirtualText
@@ -961,17 +1494,17 @@ function isVirtualText(x) {
961
1494
  return x && x.type === "VirtualText" && x.version === version
962
1495
  }
963
1496
 
964
- },{"./version":28}],27:[function(require,module,exports){
1497
+ },{"./version":32}],31:[function(require,module,exports){
965
1498
  module.exports = isWidget
966
1499
 
967
1500
  function isWidget(w) {
968
1501
  return w && w.type === "Widget"
969
1502
  }
970
1503
 
971
- },{}],28:[function(require,module,exports){
1504
+ },{}],32:[function(require,module,exports){
972
1505
  module.exports = "2"
973
1506
 
974
- },{}],29:[function(require,module,exports){
1507
+ },{}],33:[function(require,module,exports){
975
1508
  var version = require("./version")
976
1509
  var isVNode = require("./is-vnode")
977
1510
  var isWidget = require("./is-widget")
@@ -1045,7 +1578,7 @@ function VirtualNode(tagName, properties, children, key, namespace) {
1045
1578
  VirtualNode.prototype.version = version
1046
1579
  VirtualNode.prototype.type = "VirtualNode"
1047
1580
 
1048
- },{"./is-thunk":23,"./is-vhook":24,"./is-vnode":25,"./is-widget":27,"./version":28}],30:[function(require,module,exports){
1581
+ },{"./is-thunk":27,"./is-vhook":28,"./is-vnode":29,"./is-widget":31,"./version":32}],34:[function(require,module,exports){
1049
1582
  var version = require("./version")
1050
1583
 
1051
1584
  VirtualPatch.NONE = 0
@@ -1069,7 +1602,7 @@ function VirtualPatch(type, vNode, patch) {
1069
1602
  VirtualPatch.prototype.version = version
1070
1603
  VirtualPatch.prototype.type = "VirtualPatch"
1071
1604
 
1072
- },{"./version":28}],31:[function(require,module,exports){
1605
+ },{"./version":32}],35:[function(require,module,exports){
1073
1606
  var version = require("./version")
1074
1607
 
1075
1608
  module.exports = VirtualText
@@ -1081,7 +1614,7 @@ function VirtualText(text) {
1081
1614
  VirtualText.prototype.version = version
1082
1615
  VirtualText.prototype.type = "VirtualText"
1083
1616
 
1084
- },{"./version":28}],32:[function(require,module,exports){
1617
+ },{"./version":32}],36:[function(require,module,exports){
1085
1618
  var isObject = require("is-object")
1086
1619
  var isHook = require("../vnode/is-vhook")
1087
1620
 
@@ -1141,7 +1674,7 @@ function getPrototype(value) {
1141
1674
  }
1142
1675
  }
1143
1676
 
1144
- },{"../vnode/is-vhook":24,"is-object":9}],33:[function(require,module,exports){
1677
+ },{"../vnode/is-vhook":28,"is-object":10}],37:[function(require,module,exports){
1145
1678
  var isArray = require("x-is-array")
1146
1679
 
1147
1680
  var VPatch = require("../vnode/vpatch")
@@ -1570,7 +2103,7 @@ function appendPatch(apply, patch) {
1570
2103
  }
1571
2104
  }
1572
2105
 
1573
- },{"../vnode/handle-thunk":22,"../vnode/is-thunk":23,"../vnode/is-vnode":25,"../vnode/is-vtext":26,"../vnode/is-widget":27,"../vnode/vpatch":30,"./diff-props":32,"x-is-array":10}],34:[function(require,module,exports){
2106
+ },{"../vnode/handle-thunk":26,"../vnode/is-thunk":27,"../vnode/is-vnode":29,"../vnode/is-vtext":30,"../vnode/is-widget":31,"../vnode/vpatch":34,"./diff-props":36,"x-is-array":11}],38:[function(require,module,exports){
1574
2107
 
1575
2108
  },{}]},{},[4])(4)
1576
2109
  });
@@ -8,6 +8,16 @@ module VirtualDOM
8
8
  `virtualDom.h(tag_name, attributes, content)`
9
9
  end
10
10
 
11
+ def self.svg(tag_name, attributes=nil, content=nil)
12
+ %x{
13
+ return virtualDom.svg(
14
+ tag_name,
15
+ #{HashUtils.camelize_keys(attributes).to_n},
16
+ #{sanitize_content(content)}
17
+ );
18
+ }
19
+ end
20
+
11
21
  def self.create_element(node)
12
22
  `virtualDom.create(node)`
13
23
  end
data/opal/clearwater.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  require 'browser'
2
2
  require 'clearwater/component'
3
+ require 'clearwater/svg_component'
4
+ require 'clearwater/cached_render'
3
5
  require 'clearwater/link'
4
6
  require 'clearwater/application'
@@ -1,4 +1,5 @@
1
1
  require 'clearwater'
2
+ require 'clearwater/svg_component'
2
3
 
3
4
  module Clearwater
4
5
  RSpec.describe Application do
@@ -9,11 +10,26 @@ module Clearwater
9
10
  )
10
11
  }
11
12
  let(:component) {
13
+ $svg_component = self.svg_component
12
14
  Class.new do
13
15
  include Clearwater::Component
14
16
 
15
17
  def render
16
- h1('Hello world')
18
+ div([
19
+ p({ class_name: 'foo' }, 'Hello world'),
20
+ $svg_component,
21
+ ])
22
+ end
23
+ end.new
24
+ }
25
+ let(:svg_component) {
26
+ Class.new do
27
+ include Clearwater::SVGComponent
28
+
29
+ def render
30
+ svg({ view_box: '0 0 120 120' }, [
31
+ circle(cx: 50, cy: 50, r: 30),
32
+ ])
17
33
  end
18
34
  end.new
19
35
  }
@@ -22,7 +38,7 @@ module Clearwater
22
38
  it 'renders to the specified element' do
23
39
  app.perform_render
24
40
 
25
- expect(element.inner_html).to eq '<h1>Hello world</h1>'
41
+ expect(element.inner_html).to eq '<div><p class="foo">Hello world</p><svg viewBox="0 0 120 120"><circle cx="50" cy="50" r="30"></circle></svg></div>'
26
42
  end
27
43
 
28
44
  it 'calls queued blocks after rendering' do
@@ -30,7 +30,7 @@ module Clearwater
30
30
  end
31
31
 
32
32
  it 'sanitizes element attributes' do
33
- attributes = component.sanitize_attributes({
33
+ attributes = Component.sanitize_attributes({
34
34
  class: 'foo',
35
35
  onclick: proc { |event| expect(event).to be_a Browser::Event },
36
36
  })
@@ -45,12 +45,12 @@ module Clearwater
45
45
  describe 'sanitizing content' do
46
46
  it 'sanitizes components by calling `render`' do
47
47
  allow(component).to receive(:render) { 'foo' }
48
- expect(component.sanitize_content(component)).to eq 'foo'
48
+ expect(Component.sanitize_content(component)).to eq 'foo'
49
49
  end
50
50
 
51
51
  it 'sanitizes arrays by sanitizing each element' do
52
52
  allow(component).to receive(:render) { 'foo' }
53
- expect(component.sanitize_content([component, nil, 1])).to eq ['foo', '', 1]
53
+ expect(Component.sanitize_content([component, nil, 1])).to eq ['foo', nil, 1]
54
54
  end
55
55
  end
56
56
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clearwater
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamie Gaskins
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-22 00:00:00.000000000 Z
11
+ date: 2015-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: opal
@@ -94,34 +94,6 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0.9'
97
- - !ruby/object:Gem::Dependency
98
- name: pry-doc
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '0.6'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '0.6'
111
- - !ruby/object:Gem::Dependency
112
- name: codeclimate-test-reporter
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '0.4'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '0.4'
125
97
  description: Front-end web framework built on Opal
126
98
  email:
127
99
  - jgaskins@gmail.com
@@ -140,6 +112,7 @@ files:
140
112
  - opal/clearwater/router.rb
141
113
  - opal/clearwater/router/route.rb
142
114
  - opal/clearwater/router/route_collection.rb
115
+ - opal/clearwater/svg_component.rb
143
116
  - opal/clearwater/virtual_dom.rb
144
117
  - opal/clearwater/virtual_dom/js/virtual_dom.js
145
118
  - spec/clearwater/application_spec.rb
@@ -175,4 +148,3 @@ test_files:
175
148
  - spec/clearwater/cached_render_spec.rb
176
149
  - spec/clearwater/router_spec.rb
177
150
  - spec/component_spec.rb
178
- has_rdoc: