clearwater 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: