opal-browser 0.2.0 → 0.3.3

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.
Files changed (202) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/build.yml +78 -0
  3. data/.gitignore +3 -0
  4. data/CHANGELOG.md +11 -0
  5. data/Gemfile +17 -3
  6. data/LICENSE +2 -1
  7. data/README.md +131 -54
  8. data/Rakefile +29 -1
  9. data/config.ru +20 -3
  10. data/docs/polyfills.md +24 -0
  11. data/examples/2048/Gemfile +6 -0
  12. data/examples/2048/README.md +13 -0
  13. data/examples/2048/app/application.rb +169 -0
  14. data/examples/2048/config.ru +9 -0
  15. data/examples/canvas/Gemfile +6 -0
  16. data/examples/canvas/README.md +9 -0
  17. data/examples/canvas/app/application.rb +55 -0
  18. data/examples/canvas/config.ru +9 -0
  19. data/examples/component/Gemfile +6 -0
  20. data/examples/component/README.md +10 -0
  21. data/examples/component/app/application.rb +66 -0
  22. data/examples/component/config.ru +9 -0
  23. data/examples/integrations/README.md +24 -0
  24. data/examples/integrations/dynamic-rack-opal-sprockets-server/Gemfile +6 -0
  25. data/examples/integrations/dynamic-rack-opal-sprockets-server/README.md +16 -0
  26. data/examples/integrations/dynamic-rack-opal-sprockets-server/app/application.rb +6 -0
  27. data/examples/integrations/dynamic-rack-opal-sprockets-server/config.ru +9 -0
  28. data/examples/integrations/dynamic-roda-roda-sprockets/.gitignore +1 -0
  29. data/examples/integrations/dynamic-roda-roda-sprockets/Gemfile +7 -0
  30. data/examples/integrations/dynamic-roda-roda-sprockets/README.md +22 -0
  31. data/examples/integrations/dynamic-roda-roda-sprockets/Rakefile +4 -0
  32. data/examples/integrations/dynamic-roda-roda-sprockets/app/application.rb +6 -0
  33. data/examples/integrations/dynamic-roda-roda-sprockets/app.rb +32 -0
  34. data/examples/integrations/dynamic-roda-roda-sprockets/config.ru +3 -0
  35. data/examples/integrations/dynamic-roda-tilt/.gitignore +1 -0
  36. data/examples/integrations/dynamic-roda-tilt/Gemfile +8 -0
  37. data/examples/integrations/dynamic-roda-tilt/README.md +17 -0
  38. data/examples/integrations/dynamic-roda-tilt/Rakefile +6 -0
  39. data/examples/integrations/dynamic-roda-tilt/app/application.rb +6 -0
  40. data/examples/integrations/dynamic-roda-tilt/app.rb +50 -0
  41. data/examples/integrations/dynamic-roda-tilt/config.ru +3 -0
  42. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/Gemfile +7 -0
  43. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/README.md +16 -0
  44. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/app/application.rb +6 -0
  45. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/config.ru +29 -0
  46. data/examples/integrations/static-bash/.gitignore +2 -0
  47. data/examples/integrations/static-bash/Gemfile +3 -0
  48. data/examples/integrations/static-bash/README.md +8 -0
  49. data/examples/integrations/static-bash/app/application.rb +6 -0
  50. data/examples/integrations/static-bash/build.sh +4 -0
  51. data/examples/integrations/static-bash/index.html +10 -0
  52. data/examples/integrations/static-bash-opal-parser/.gitignore +3 -0
  53. data/examples/integrations/static-bash-opal-parser/Gemfile +3 -0
  54. data/examples/integrations/static-bash-opal-parser/README.md +10 -0
  55. data/examples/integrations/static-bash-opal-parser/build.sh +4 -0
  56. data/examples/integrations/static-bash-opal-parser/index.html +19 -0
  57. data/examples/integrations/static-rake/.gitignore +1 -0
  58. data/examples/integrations/static-rake/Gemfile +4 -0
  59. data/examples/integrations/static-rake/README.md +7 -0
  60. data/examples/integrations/static-rake/Rakefile +10 -0
  61. data/examples/integrations/static-rake/app/application.rb +6 -0
  62. data/examples/integrations/static-rake/index.html +9 -0
  63. data/examples/integrations/static-rake-guard/.gitignore +1 -0
  64. data/examples/integrations/static-rake-guard/Gemfile +6 -0
  65. data/examples/integrations/static-rake-guard/Guardfile +3 -0
  66. data/examples/integrations/static-rake-guard/README.md +10 -0
  67. data/examples/integrations/static-rake-guard/Rakefile +10 -0
  68. data/examples/integrations/static-rake-guard/app/application.rb +6 -0
  69. data/examples/integrations/static-rake-guard/index.html +9 -0
  70. data/examples/svg/.gitignore +1 -0
  71. data/examples/svg/Gemfile +4 -0
  72. data/examples/svg/README.md +7 -0
  73. data/examples/svg/Rakefile +10 -0
  74. data/examples/svg/app/application.rb +11 -0
  75. data/examples/svg/index.html +17 -0
  76. data/examples/svg/index.svg +6 -0
  77. data/index.html.erb +2 -3
  78. data/opal/browser/audio/node.rb +121 -0
  79. data/opal/browser/audio/param_schedule.rb +43 -0
  80. data/opal/browser/audio.rb +66 -0
  81. data/opal/browser/blob.rb +94 -0
  82. data/opal/browser/canvas/data.rb +1 -1
  83. data/opal/browser/canvas/gradient.rb +1 -1
  84. data/opal/browser/canvas/style.rb +3 -1
  85. data/opal/browser/canvas/text.rb +1 -1
  86. data/opal/browser/canvas.rb +17 -3
  87. data/opal/browser/console.rb +3 -1
  88. data/opal/browser/cookies.rb +72 -34
  89. data/opal/browser/crypto.rb +79 -0
  90. data/opal/browser/css/declaration.rb +1 -1
  91. data/opal/browser/css/rule.rb +1 -1
  92. data/opal/browser/css/style_sheet.rb +2 -2
  93. data/opal/browser/css.rb +23 -7
  94. data/opal/browser/database/sql.rb +7 -8
  95. data/opal/browser/delay.rb +16 -0
  96. data/opal/browser/dom/attribute.rb +1 -1
  97. data/opal/browser/dom/builder.rb +29 -10
  98. data/opal/browser/dom/document.rb +81 -13
  99. data/opal/browser/dom/document_fragment.rb +18 -0
  100. data/opal/browser/dom/document_or_shadow_root.rb +19 -0
  101. data/opal/browser/dom/element/attributes.rb +28 -4
  102. data/opal/browser/dom/element/button.rb +31 -0
  103. data/opal/browser/dom/element/custom.rb +177 -0
  104. data/opal/browser/dom/element/data.rb +17 -2
  105. data/opal/browser/dom/element/editable.rb +47 -0
  106. data/opal/browser/dom/element/form.rb +38 -0
  107. data/opal/browser/dom/element/iframe.rb +37 -0
  108. data/opal/browser/dom/element/image.rb +2 -0
  109. data/opal/browser/dom/element/input.rb +36 -0
  110. data/opal/browser/dom/element/media.rb +17 -0
  111. data/opal/browser/dom/element/scroll.rb +106 -74
  112. data/opal/browser/dom/element/select.rb +6 -0
  113. data/opal/browser/dom/element/size.rb +12 -0
  114. data/opal/browser/dom/element/template.rb +2 -0
  115. data/opal/browser/dom/element/textarea.rb +2 -0
  116. data/opal/browser/dom/element.rb +194 -50
  117. data/opal/browser/dom/mutation_observer.rb +2 -2
  118. data/opal/browser/dom/node.rb +53 -13
  119. data/opal/browser/dom/node_set.rb +13 -2
  120. data/opal/browser/dom/shadow_root.rb +12 -0
  121. data/opal/browser/dom/text.rb +2 -2
  122. data/opal/browser/dom.rb +38 -5
  123. data/opal/browser/effects.rb +170 -4
  124. data/opal/browser/event/all.rb +26 -0
  125. data/opal/browser/event/animation.rb +2 -0
  126. data/opal/browser/event/audio_processing.rb +2 -0
  127. data/opal/browser/event/base.rb +35 -4
  128. data/opal/browser/event/before_unload.rb +2 -0
  129. data/opal/browser/event/clipboard.rb +9 -0
  130. data/opal/browser/event/close.rb +2 -0
  131. data/opal/browser/event/composition.rb +2 -0
  132. data/opal/browser/event/custom.rb +1 -1
  133. data/opal/browser/event/data_transfer.rb +95 -0
  134. data/opal/browser/event/device_light.rb +2 -0
  135. data/opal/browser/event/device_motion.rb +2 -0
  136. data/opal/browser/event/device_orientation.rb +2 -0
  137. data/opal/browser/event/device_proximity.rb +2 -0
  138. data/opal/browser/event/drag.rb +9 -5
  139. data/opal/browser/event/focus.rb +2 -0
  140. data/opal/browser/event/gamepad.rb +3 -1
  141. data/opal/browser/event/hash_change.rb +2 -0
  142. data/opal/browser/event/keyboard.rb +14 -1
  143. data/opal/browser/event/message.rb +2 -0
  144. data/opal/browser/event/mouse.rb +10 -6
  145. data/opal/browser/event/page_transition.rb +2 -0
  146. data/opal/browser/event/pop_state.rb +2 -0
  147. data/opal/browser/event/progress.rb +2 -0
  148. data/opal/browser/event/sensor.rb +2 -0
  149. data/opal/browser/event/storage.rb +2 -0
  150. data/opal/browser/event/touch.rb +2 -0
  151. data/opal/browser/event/wheel.rb +2 -0
  152. data/opal/browser/event.rb +26 -116
  153. data/opal/browser/event_source.rb +1 -1
  154. data/opal/browser/form_data.rb +225 -0
  155. data/opal/browser/history.rb +4 -8
  156. data/opal/browser/http/request.rb +32 -10
  157. data/opal/browser/http/response.rb +5 -1
  158. data/opal/browser/http.rb +0 -2
  159. data/opal/browser/immediate.rb +0 -2
  160. data/opal/browser/location.rb +7 -1
  161. data/opal/browser/navigator.rb +105 -4
  162. data/opal/browser/polyfill/visual_viewport.rb +216 -0
  163. data/opal/browser/screen.rb +2 -2
  164. data/opal/browser/setup/base.rb +6 -0
  165. data/opal/browser/setup/full.rb +13 -0
  166. data/opal/browser/setup/large.rb +17 -0
  167. data/opal/browser/setup/mini.rb +8 -0
  168. data/opal/browser/setup/traditional.rb +10 -0
  169. data/opal/browser/socket.rb +3 -3
  170. data/opal/browser/storage.rb +2 -2
  171. data/opal/browser/support.rb +46 -22
  172. data/opal/browser/utils.rb +94 -14
  173. data/opal/browser/version.rb +1 -1
  174. data/opal/browser/visual_viewport.rb +39 -0
  175. data/opal/browser/window/size.rb +14 -0
  176. data/opal/browser/window/view.rb +15 -0
  177. data/opal/browser/window.rb +29 -16
  178. data/opal/browser.rb +1 -11
  179. data/opal-browser.gemspec +3 -3
  180. data/spec/database/sql_spec.rb +43 -35
  181. data/spec/delay_spec.rb +15 -12
  182. data/spec/dom/document_spec.rb +10 -8
  183. data/spec/dom/element/custom_spec.rb +106 -0
  184. data/spec/dom/element/subclass_spec.rb +144 -0
  185. data/spec/dom/element_spec.rb +42 -0
  186. data/spec/dom/mutation_observer_spec.rb +12 -8
  187. data/spec/dom/node_spec.rb +48 -0
  188. data/spec/dom_spec.rb +8 -0
  189. data/spec/event_source_spec.rb +15 -12
  190. data/spec/{dom/event_spec.rb → event_spec.rb} +44 -15
  191. data/spec/history_spec.rb +23 -19
  192. data/spec/http_spec.rb +19 -31
  193. data/spec/immediate_spec.rb +5 -4
  194. data/spec/interval_spec.rb +18 -9
  195. data/spec/native_cached_wrapper_spec.rb +46 -0
  196. data/spec/runner.rb +37 -62
  197. data/spec/socket_spec.rb +15 -12
  198. data/spec/spec_helper.rb +2 -1
  199. data/spec/spec_helper_promise.rb.erb +25 -0
  200. metadata +120 -16
  201. data/.travis.yml +0 -74
  202. data/opal/browser/window/scroll.rb +0 -59
@@ -1,5 +1,7 @@
1
1
  module Browser; module DOM; class Element < Node
2
2
 
3
+ # @todo Consider using the new interfaces which allow for optional
4
+ # smooth transitions.
3
5
  class Scroll
4
6
  attr_reader :element
5
7
 
@@ -7,57 +9,114 @@ class Scroll
7
9
  def initialize(element)
8
10
  @element = element
9
11
  @native = element.to_n
10
- end
11
12
 
12
- if Browser.supports? 'Element.scroll'
13
- def to(*args)
14
- if Hash === args.first
15
- x = args.first[:x] || self.x
16
- y = args.first[:y] || self.y
17
- else
18
- x, y = args
13
+ # Portable support for Window#scroll and Document#scroll
14
+ @scrolling_native = @native
15
+ if [Document, Window].include?(@element.class)
16
+ # If we are a window, let's become a document first.
17
+ if defined? `#@scrolling_native.document`
18
+ @scrolling_native = `#@scrolling_native.document`
19
+ end
20
+ # There were slight disagreements in the past which element
21
+ # should we handle.
22
+ if defined? `#@scrolling_native.documentElement.scrollTop`
23
+ @scrolling_native = `#@scrolling_native.documentElement`
24
+ elsif defined? `#@scrolling_native.body.scrollTop`
25
+ @scrolling_native = `#@scrolling_native.body`
19
26
  end
27
+ end
28
+ end
20
29
 
21
- `#@native.scrollTop = #{y}`
22
- `#@native.scrollLeft = #{x}`
30
+ # @overload to(x, y)
31
+ #
32
+ # Scroll to the given x and y.
33
+ #
34
+ # @param x [Integer] scroll to x on the x axis
35
+ # @param y [Integer] scroll to y on the y axis
36
+ #
37
+ # @overload to(hash)
38
+ #
39
+ # Scroll to the given x and y.
40
+ #
41
+ # @param hash [Hash] the descriptor
42
+ #
43
+ # @option hash [Integer] :x scroll to x on the x axis
44
+ # @option hash [Integer] :y scroll to y on the y axis
45
+ #
46
+ # @overload to(symbol)
47
+ #
48
+ # Scroll to :top or to :bottom
49
+ #
50
+ # @param symbol [Symbol] either :top or :bottom
51
+ def to(*args)
52
+ x, y = nil, nil
53
+ case args.first
54
+ when Hash
55
+ x = args.first[:x]
56
+ y = args.first[:y]
57
+ when :top
58
+ y = 0
59
+ when :bottom
60
+ y = 99999999
61
+ else
62
+ x, y = args
23
63
  end
24
64
 
25
- def position
26
- Browser::Position.new(`#@native.scrollLeft`, `#@native.scrollTop`)
65
+ set(x, y) if x || y
66
+
67
+ self
68
+ end
69
+
70
+ # @overload by(x, y)
71
+ #
72
+ # Scroll by the given x and y.
73
+ #
74
+ # @param x [Integer] scroll by x on the x axis
75
+ # @param y [Integer] scroll by y on the y axis
76
+ #
77
+ # @overload by(hash)
78
+ #
79
+ # Scroll by the given x and y.
80
+ #
81
+ # @param hash [Hash] the descriptor
82
+ #
83
+ # @option hash [Integer] :x scroll by x on the x axis
84
+ # @option hash [Integer] :y scroll by y on the y axis
85
+ def by(*args)
86
+ case args.first
87
+ when Hash
88
+ x = args.first[:x] || 0
89
+ y = args.first[:y] || 0
90
+ else
91
+ x, y = args
27
92
  end
28
- elsif Browser.supports? 'Element.pageOffset'
29
- def to(*args)
30
- if Hash === args.first
31
- x = args.first[:x] || self.x
32
- y = args.first[:y] || self.y
33
- else
34
- x, y = args
35
- end
36
93
 
37
- `#@native.pageYOffset = #{y}`
38
- `#@native.pageXOffset = #{x}`
94
+ set_by(x, y)
95
+
96
+ self
97
+ end
98
+
99
+ if Browser.supports? 'Element.scrollBy'
100
+ private def set_by(x, y)
101
+ `#@scrolling_native.scrollBy(#{x}, #{y})`
102
+ end
103
+ else
104
+ private def set_by(x, y)
105
+ set(self.x + x, self.y + y)
106
+ end
107
+ end
108
+
109
+ if Browser.supports? 'Element.scroll'
110
+ private def set(x=nil, y=nil)
111
+ `#@scrolling_native.scrollTop = #{y}` if y
112
+ `#@scrolling_native.scrollLeft = #{x}` if x
39
113
  end
40
114
 
41
115
  def position
42
- Position.new(`#@native.pageXOffset`, `#@native.pageYOffset`)
116
+ Browser::Position.new(`#@scrolling_native.scrollLeft`, `#@scrolling_native.scrollTop`)
43
117
  end
44
118
  else
45
- # @overload to(x, y)
46
- #
47
- # Scroll to the given x and y.
48
- #
49
- # @param x [Integer] scroll to x on the x axis
50
- # @param y [Integer] scroll to y on the y axis
51
- #
52
- # @overload to(hash)
53
- #
54
- # Scroll to the given x and y.
55
- #
56
- # @param hash [Hash] the descriptor
57
- #
58
- # @option hash [Integer] :x scroll to x on the x axis
59
- # @option hash [Integer] :y scroll to y on the y axis
60
- def to(*args)
119
+ private def set(x=nil, y=nil)
61
120
  raise NotImplementedError, 'scroll on element unsupported'
62
121
  end
63
122
 
@@ -81,55 +140,28 @@ class Scroll
81
140
  # @!attribute [r] height
82
141
  # @return [Integer] the height of the scroll
83
142
  def height
84
- `#@native.scrollHeight`
143
+ `#@scrolling_native.scrollHeight`
85
144
  end
86
145
 
87
146
  # @!attribute [r] width
88
147
  # @return [Integer] the width of the scroll
89
148
  def width
90
- `#@native.scrollWidth`
91
- end
92
-
93
- # @overload by(x, y)
94
- #
95
- # Scroll by the given x and y.
96
- #
97
- # @param x [Integer] scroll by x on the x axis
98
- # @param y [Integer] scroll by y on the y axis
99
- #
100
- # @overload by(hash)
101
- #
102
- # Scroll by the given x and y.
103
- #
104
- # @param hash [Hash] the descriptor
105
- #
106
- # @option hash [Integer] :x scroll by x on the x axis
107
- # @option hash [Integer] :y scroll by y on the y axis
108
- def by(*args)
109
- if Hash === args.first
110
- x = args.first[:x] || 0
111
- y = args.first[:y] || 0
112
- else
113
- x, y = args
114
- end
115
-
116
- `#@native.scrollBy(#{x}, #{y})`
117
-
118
- self
149
+ `#@scrolling_native.scrollWidth`
119
150
  end
120
151
 
121
152
  if Browser.supports? 'Element.scrollIntoViewIfNeeded'
122
- def to(align = true)
123
- `#@native.scrollIntoViewIfNeeded(align)`
153
+ def into_view(align = true)
154
+ `#@scrolling_native.scrollIntoViewIfNeeded(align)`
124
155
  end
125
156
  else
126
- def to(align = true)
157
+ # Non-standard. Not supported by modern Firefox. Use {#into_view!}
158
+ def into_view(align = true)
127
159
  raise NotImplementedError
128
160
  end
129
161
  end
130
162
 
131
- def to!(align = true)
132
- `#@native.scrollIntoView(align)`
163
+ def into_view!(align = true)
164
+ `#@scrolling_native.scrollIntoView(align)`
133
165
  end
134
166
  end
135
167
 
@@ -1,6 +1,8 @@
1
1
  module Browser; module DOM; class Element < Node
2
2
 
3
3
  class Select < Element
4
+ def_selector "select"
5
+
4
6
  def value
5
7
  %x{
6
8
  if (#@native.value == "") {
@@ -12,6 +14,10 @@ class Select < Element
12
14
  }
13
15
  end
14
16
 
17
+ def value= value
18
+ `#@native.value = #{value.to_n}`
19
+ end
20
+
15
21
  def labels
16
22
  NodeSet[Native::Array.new(`#@native.labels`)]
17
23
  end
@@ -29,6 +29,18 @@ class Size
29
29
  def height=(value)
30
30
  @element.style[:height] = value
31
31
  end
32
+
33
+ # @!attribute client_width
34
+ # @return [Integer] the content-box width of an element
35
+ def client_width
36
+ `#@native.clientWidth`
37
+ end
38
+
39
+ # @!attribute client_height
40
+ # @return [Integer] the content-box height of an element
41
+ def client_height
42
+ `#@native.clientHeight`
43
+ end
32
44
  end
33
45
 
34
46
  end; end; end
@@ -1,6 +1,8 @@
1
1
  module Browser; module DOM; class Element < Node
2
2
 
3
3
  class Template < Element
4
+ def_selector "template"
5
+
4
6
  def content
5
7
  DOM(`#@native.content`)
6
8
  end
@@ -1,6 +1,8 @@
1
1
  module Browser; module DOM; class Element < Node
2
2
 
3
3
  class Textarea < Element
4
+ def_selector "textarea"
5
+
4
6
  def value
5
7
  %x{
6
8
  if (#@native.value == "") {
@@ -1,76 +1,152 @@
1
- require 'browser/dom/element/attributes'
2
- require 'browser/dom/element/data'
3
- require 'browser/dom/element/position'
4
- require 'browser/dom/element/offset'
5
- require 'browser/dom/element/scroll'
6
- require 'browser/dom/element/size'
7
-
8
- require 'browser/dom/element/input'
9
- require 'browser/dom/element/select'
10
- require 'browser/dom/element/image'
11
- require 'browser/dom/element/template'
12
- require 'browser/dom/element/textarea'
1
+ # Requires are moved to the bottom of this file.
13
2
 
14
3
  module Browser; module DOM
15
4
 
16
5
  class Element < Node
17
- def self.create(*args)
18
- $document.create_element(*args)
6
+ def self.create(*args, &block)
7
+ if Document === args.first
8
+ document = args.shift
9
+ else
10
+ document = $document
11
+ end
12
+
13
+ if self == Element
14
+ document.create_element(*args, &block)
15
+ elsif @tag_name
16
+ document.create_element(@tag_name, *args, &block)
17
+ elsif @selector
18
+ # That's crude, but should cover the most basic cases.
19
+ # Just in case, you can override it safely. To reiterate:
20
+ # .create is not to be used inside libraries, those are
21
+ # expected to use the Document#create_element API.
22
+ kwargs = {}
23
+ kwargs = args.pop if Hash === args.last
24
+ custom_attrs, custom_id, custom_classes = nil, nil, nil
25
+ tag_name = (@selector.scan(/^[\w-]+/).first || "div").upcase
26
+ classes = @selector.scan(/\.([\w-]+)/).flatten
27
+ classes |= custom_classes if custom_classes = kwargs.delete(:classes)
28
+ id = @selector.scan(/#([\w-]+)/).flatten.first
29
+ id = custom_id if custom_id = kwargs.delete(:id)
30
+ attrs = @selector.scan(/\[([\w-]+)=((["'])(.*?)\3|[\w_-]*)\]/).map { |a,b,_,d| [a,d||b] }.to_h
31
+ attrs = attrs.merge(custom_attrs) if custom_attrs = kwargs.delete(:attrs)
32
+ document.create_element(tag_name, *args, classes: classes, id: id, attrs: attrs, **kwargs, &block)
33
+ else
34
+ raise NotImplementedError
35
+ end
36
+ end
37
+
38
+ def self.subclasses
39
+ @subclasses ||= []
40
+ end
41
+
42
+ # Define a selector for subclass dispatch
43
+ #
44
+ # Example:
45
+ # ```
46
+ # class CustomElement < Browser::DOM::Element
47
+ # def_selector "div.hello-world"
48
+ # end
49
+ # ```
50
+ def self.def_selector(selector)
51
+ Element.subclasses << self
52
+
53
+ @selector = selector
54
+
55
+ # A special case to speedup dispatch
56
+ @tag_name = selector.upcase unless selector =~ /[^\w-]/
57
+ end
58
+
59
+ def self.selector
60
+ @selector
19
61
  end
20
62
 
21
- def self.new(node)
63
+ def self.tag_name
64
+ @tag_name
65
+ end
66
+
67
+ def self.new(*args, &block)
68
+ if args.length == 1 && !block_given? && Opal.native?(args[0])
69
+ # Use `.new` as a wrapping method.
70
+ node = args[0]
71
+ else
72
+ # Use `.new` as an alias for `.create`.
73
+ return create(*args, &block)
74
+ end
75
+
22
76
  if self == Element
23
- name = `node.nodeName`.capitalize
77
+ subclass = Element.subclasses.select do |subclass|
78
+ Element.native_is?(node, subclass)
79
+ end.last
24
80
 
25
- if Element.constants.include?(name)
26
- Element.const_get(name).new(node)
81
+ if subclass
82
+ subclass.new(node)
27
83
  else
28
- super
84
+ super(node)
29
85
  end
30
86
  else
31
- super
87
+ super(node)
32
88
  end
33
89
  end
34
90
 
35
91
  include Event::Target
36
92
 
37
93
  target {|value|
38
- DOM(value) rescue nil
94
+ begin
95
+ DOM(value)
96
+ rescue StandardError, JS::Error
97
+ nil
98
+ end
39
99
  }
40
100
 
101
+ def self.native_is? (native, klass)
102
+ if tag_name = klass.tag_name
103
+ is = `(#{native}.getAttribute("is") || "")`
104
+ `#{tag_name} === #{is}.toUpperCase() || #{tag_name} === #{native}.nodeName`
105
+ else
106
+ Element.native_matches?(native, klass.selector)
107
+ end
108
+ end
109
+
41
110
  if Browser.supports? 'Element.matches'
42
- def =~(selector)
43
- `#@native.matches(#{selector})`
111
+ def self.native_matches? (native, selector)
112
+ `#{native}.matches(#{selector})`
44
113
  end
45
114
  elsif Browser.supports? 'Element.matches (Opera)'
46
- def =~(selector)
47
- `#@native.oMatchesSelector(#{selector})`
115
+ def self.native_matches? (native, selector)
116
+ `#{native}.oMatchesSelector(#{selector})`
48
117
  end
49
118
  elsif Browser.supports? 'Element.matches (Internet Explorer)'
50
- def =~(selector)
51
- `#@native.msMatchesSelector(#{selector})`
119
+ def self.native_matches? (native, selector)
120
+ `#{native}.msMatchesSelector(#{selector})`
52
121
  end
53
122
  elsif Browser.supports? 'Element.matches (Firefox)'
54
- def =~(selector)
55
- `#@native.mozMatchesSelector(#{selector})`
123
+ def self.native_matches? (native, selector)
124
+ `#{native}.mozMatchesSelector(#{selector})`
56
125
  end
57
126
  elsif Browser.supports? 'Element.matches (Chrome)'
58
- def =~(selector)
59
- `#@native.webkitMatchesSelector(#{selector})`
127
+ def self.native_matches? (native, selector)
128
+ `#{native}.webkitMatchesSelector(#{selector})`
60
129
  end
61
130
  elsif Browser.loaded? 'Sizzle'
62
- def =~(selector)
63
- `Sizzle.matchesSelector(#@native, #{selector})`
131
+ def self.native_matches? (native, selector)
132
+ `Sizzle.matchesSelector(#{native}, #{selector})`
64
133
  end
65
134
  else
66
- # Check whether the element matches the given selector.
67
- #
68
- # @param selector [String] the CSS selector
69
- def =~(selector)
135
+ def self.native_matches? (native, selector)
70
136
  raise NotImplementedError, 'selector matching unsupported'
71
137
  end
72
138
  end
73
139
 
140
+ # Check whether the element matches the given selector.
141
+ #
142
+ # @param selector [String] the CSS selector
143
+ def =~(selector)
144
+ Element.native_matches?(@native, selector)
145
+ end
146
+
147
+ # Allow for case expressions
148
+ alias === =~
149
+
74
150
  # Query for children with the given XPpaths.
75
151
  #
76
152
  # @param paths [Array<String>] the XPaths to look for
@@ -190,13 +266,13 @@ class Element < Node
190
266
  if Browser.supports? 'Query.css'
191
267
  def css(path)
192
268
  NodeSet[Native::Array.new(`#@native.querySelectorAll(path)`)]
193
- rescue
269
+ rescue StandardError, JS::Error
194
270
  NodeSet[]
195
271
  end
196
272
  elsif Browser.loaded? 'Sizzle'
197
273
  def css(path)
198
274
  NodeSet[`Sizzle(path, #@native)`]
199
- rescue
275
+ rescue StandardError, JS::Error
200
276
  NodeSet[]
201
277
  end
202
278
  else
@@ -210,6 +286,12 @@ class Element < Node
210
286
  end
211
287
  end
212
288
 
289
+ # Click the element. it fires the element's click event.
290
+ def click
291
+ `#@native.click()`
292
+ self
293
+ end
294
+
213
295
  # @overload data()
214
296
  #
215
297
  # Return the data for the element.
@@ -271,13 +353,9 @@ class Element < Node
271
353
  end
272
354
 
273
355
  # Set the inner DOM of the element using the {Builder}.
274
- def inner_dom(&block)
275
- clear
276
-
277
- # FIXME: when block passing is fixed
278
- doc = document
279
-
280
- self << Builder.new(doc, self, &block).to_a
356
+ def inner_dom(builder=nil, &block)
357
+ self.inner_dom = Builder.new(document, builder, &block).to_a
358
+ self
281
359
  end
282
360
 
283
361
  # Set the inner DOM with the given node.
@@ -289,6 +367,16 @@ class Element < Node
289
367
  self << node
290
368
  end
291
369
 
370
+ # @!attribute inner_html
371
+ # @return [String] the inner HTML of the element
372
+ def inner_html
373
+ `#@native.innerHTML`
374
+ end
375
+
376
+ def inner_html=(value)
377
+ `#@native.innerHTML = #{value}`
378
+ end
379
+
292
380
  def inspect
293
381
  inspect = name.downcase
294
382
 
@@ -300,7 +388,7 @@ class Element < Node
300
388
  inspect += '.' + class_names.join('.')
301
389
  end
302
390
 
303
- "#<DOM::Element: #{inspect}>"
391
+ "#<#{self.class.name.gsub("Browser::","")}: #{inspect}>"
304
392
  end
305
393
 
306
394
  # @!attribute offset
@@ -319,16 +407,22 @@ class Element < Node
319
407
  offset.set(*value)
320
408
  end
321
409
 
410
+ # @!attribute outer_html
411
+ # @return [String] the outer HTML of the element
412
+ def outer_html
413
+ `#@native.outerHTML`
414
+ end
415
+
322
416
  # @!attribute [r] position
323
417
  # @return [Position] the position of the element
324
418
  def position
325
- Position.new(self)
419
+ @position ||= Position.new(self)
326
420
  end
327
421
 
328
422
  # @!attribute [r] scroll
329
423
  # @return [Scroll] the scrolling for the element
330
424
  def scroll
331
- Scroll.new(self)
425
+ @scroll ||= Scroll.new(self)
332
426
  end
333
427
 
334
428
  # Search for all the children matching the given XPaths or CSS selectors.
@@ -346,6 +440,27 @@ class Element < Node
346
440
 
347
441
  alias set_attribute []=
348
442
 
443
+ # Creates or accesses the shadow root of this element
444
+ #
445
+ # @param open [Boolean] set to false if you want to create a closed
446
+ # shadow root
447
+ #
448
+ # @return [ShadowRoot]
449
+ def shadow (open = true)
450
+ if root = `#@native.shadowRoot`
451
+ DOM(root)
452
+ else
453
+ DOM(`#@native.attachShadow({mode: #{open ? "open" : "closed"}})`)
454
+ end
455
+ end
456
+
457
+ # Checks for a presence of a shadow root of this element
458
+ #
459
+ # @return [Boolean]
460
+ def shadow?
461
+ `!!#@native.shadowRoot`
462
+ end
463
+
349
464
  # @overload style()
350
465
  #
351
466
  # Return the style for the element.
@@ -429,6 +544,18 @@ class Element < Node
429
544
  Size.new(self, *inc)
430
545
  end
431
546
 
547
+ # Toggle class names of the element.
548
+ #
549
+ # @param names [Array<String>] class names to toggle
550
+ #
551
+ # @return [self]
552
+ def toggle_class(*names)
553
+ to_remove, to_add = names.partition { |name| class_names.include? name }
554
+
555
+ add_class(*to_add)
556
+ remove_class(*to_remove)
557
+ end
558
+
432
559
  # @!attribute width
433
560
  # @return [Integer] the width of the element
434
561
  def width
@@ -456,7 +583,7 @@ class Element < Node
456
583
  #@native, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)`,
457
584
  get: :snapshotItem,
458
585
  length: :snapshotLength)]
459
- rescue
586
+ rescue StandardError, JS::Error
460
587
  NodeSet[]
461
588
  end
462
589
  else
@@ -472,3 +599,20 @@ class Element < Node
472
599
  end
473
600
 
474
601
  end; end
602
+
603
+ require 'browser/dom/element/attributes'
604
+ require 'browser/dom/element/data'
605
+ require 'browser/dom/element/position'
606
+ require 'browser/dom/element/offset'
607
+ require 'browser/dom/element/scroll'
608
+ require 'browser/dom/element/size'
609
+
610
+ require 'browser/dom/element/button'
611
+ require 'browser/dom/element/image'
612
+ require 'browser/dom/element/form'
613
+ require 'browser/dom/element/input'
614
+ require 'browser/dom/element/select'
615
+ require 'browser/dom/element/template'
616
+ require 'browser/dom/element/textarea'
617
+ require 'browser/dom/element/iframe'
618
+ require 'browser/dom/element/media'
@@ -9,11 +9,11 @@ class MutationObserver
9
9
  Browser.supports? :MutationObserver
10
10
  end
11
11
 
12
- include Native
12
+ include Native::Wrapper
13
13
 
14
14
  # Encapsulates a recorded change.
15
15
  class Record
16
- include Native
16
+ include Browser::NativeCachedWrapper
17
17
 
18
18
  # @!attribute [r] type
19
19
  # @return [:attributes, :tree, :cdata] the type of the recorded change