opal-browser 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.
Files changed (201) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/build.yml +95 -0
  3. data/.gitignore +2 -0
  4. data/Gemfile +17 -3
  5. data/LICENSE +2 -1
  6. data/README.md +116 -54
  7. data/Rakefile +29 -1
  8. data/config.ru +20 -3
  9. data/docs/polyfills.md +24 -0
  10. data/examples/2048/Gemfile +7 -0
  11. data/examples/2048/README.md +13 -0
  12. data/examples/2048/app/application.rb +169 -0
  13. data/examples/2048/config.ru +9 -0
  14. data/examples/canvas/Gemfile +7 -0
  15. data/examples/canvas/README.md +9 -0
  16. data/examples/canvas/app/application.rb +55 -0
  17. data/examples/canvas/config.ru +9 -0
  18. data/examples/component/Gemfile +7 -0
  19. data/examples/component/README.md +10 -0
  20. data/examples/component/app/application.rb +66 -0
  21. data/examples/component/config.ru +9 -0
  22. data/examples/integrations/README.md +24 -0
  23. data/examples/integrations/dynamic-rack-opal-sprockets-server/Gemfile +7 -0
  24. data/examples/integrations/dynamic-rack-opal-sprockets-server/README.md +16 -0
  25. data/examples/integrations/dynamic-rack-opal-sprockets-server/app/application.rb +6 -0
  26. data/examples/integrations/dynamic-rack-opal-sprockets-server/config.ru +9 -0
  27. data/examples/integrations/dynamic-roda-roda-sprockets/.gitignore +1 -0
  28. data/examples/integrations/dynamic-roda-roda-sprockets/Gemfile +8 -0
  29. data/examples/integrations/dynamic-roda-roda-sprockets/README.md +22 -0
  30. data/examples/integrations/dynamic-roda-roda-sprockets/Rakefile +4 -0
  31. data/examples/integrations/dynamic-roda-roda-sprockets/app/application.rb +6 -0
  32. data/examples/integrations/dynamic-roda-roda-sprockets/app.rb +32 -0
  33. data/examples/integrations/dynamic-roda-roda-sprockets/config.ru +3 -0
  34. data/examples/integrations/dynamic-roda-tilt/.gitignore +1 -0
  35. data/examples/integrations/dynamic-roda-tilt/Gemfile +9 -0
  36. data/examples/integrations/dynamic-roda-tilt/README.md +17 -0
  37. data/examples/integrations/dynamic-roda-tilt/Rakefile +6 -0
  38. data/examples/integrations/dynamic-roda-tilt/app/application.rb +6 -0
  39. data/examples/integrations/dynamic-roda-tilt/app.rb +50 -0
  40. data/examples/integrations/dynamic-roda-tilt/config.ru +3 -0
  41. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/Gemfile +8 -0
  42. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/README.md +16 -0
  43. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/app/application.rb +6 -0
  44. data/examples/integrations/dynamic-sinatra-opal-sprockets-server/config.ru +29 -0
  45. data/examples/integrations/static-bash/.gitignore +2 -0
  46. data/examples/integrations/static-bash/Gemfile +4 -0
  47. data/examples/integrations/static-bash/README.md +8 -0
  48. data/examples/integrations/static-bash/app/application.rb +6 -0
  49. data/examples/integrations/static-bash/build.sh +4 -0
  50. data/examples/integrations/static-bash/index.html +10 -0
  51. data/examples/integrations/static-bash-opal-parser/.gitignore +3 -0
  52. data/examples/integrations/static-bash-opal-parser/Gemfile +4 -0
  53. data/examples/integrations/static-bash-opal-parser/README.md +10 -0
  54. data/examples/integrations/static-bash-opal-parser/build.sh +4 -0
  55. data/examples/integrations/static-bash-opal-parser/index.html +19 -0
  56. data/examples/integrations/static-rake/.gitignore +1 -0
  57. data/examples/integrations/static-rake/Gemfile +5 -0
  58. data/examples/integrations/static-rake/README.md +7 -0
  59. data/examples/integrations/static-rake/Rakefile +10 -0
  60. data/examples/integrations/static-rake/app/application.rb +6 -0
  61. data/examples/integrations/static-rake/index.html +9 -0
  62. data/examples/integrations/static-rake-guard/.gitignore +1 -0
  63. data/examples/integrations/static-rake-guard/Gemfile +7 -0
  64. data/examples/integrations/static-rake-guard/Guardfile +3 -0
  65. data/examples/integrations/static-rake-guard/README.md +10 -0
  66. data/examples/integrations/static-rake-guard/Rakefile +10 -0
  67. data/examples/integrations/static-rake-guard/app/application.rb +6 -0
  68. data/examples/integrations/static-rake-guard/index.html +9 -0
  69. data/examples/svg/.gitignore +1 -0
  70. data/examples/svg/Gemfile +5 -0
  71. data/examples/svg/README.md +7 -0
  72. data/examples/svg/Rakefile +10 -0
  73. data/examples/svg/app/application.rb +11 -0
  74. data/examples/svg/index.html +17 -0
  75. data/examples/svg/index.svg +6 -0
  76. data/index.html.erb +2 -3
  77. data/opal/browser/audio/node.rb +121 -0
  78. data/opal/browser/audio/param_schedule.rb +43 -0
  79. data/opal/browser/audio.rb +66 -0
  80. data/opal/browser/blob.rb +94 -0
  81. data/opal/browser/canvas/data.rb +1 -1
  82. data/opal/browser/canvas/gradient.rb +1 -1
  83. data/opal/browser/canvas/style.rb +3 -1
  84. data/opal/browser/canvas/text.rb +1 -1
  85. data/opal/browser/canvas.rb +17 -3
  86. data/opal/browser/console.rb +3 -1
  87. data/opal/browser/cookies.rb +16 -7
  88. data/opal/browser/crypto.rb +79 -0
  89. data/opal/browser/css/declaration.rb +1 -1
  90. data/opal/browser/css/rule.rb +1 -1
  91. data/opal/browser/css/style_sheet.rb +2 -2
  92. data/opal/browser/css.rb +23 -7
  93. data/opal/browser/database/sql.rb +7 -8
  94. data/opal/browser/delay.rb +16 -0
  95. data/opal/browser/dom/attribute.rb +1 -1
  96. data/opal/browser/dom/builder.rb +29 -10
  97. data/opal/browser/dom/document.rb +81 -13
  98. data/opal/browser/dom/document_fragment.rb +18 -0
  99. data/opal/browser/dom/document_or_shadow_root.rb +19 -0
  100. data/opal/browser/dom/element/attributes.rb +28 -4
  101. data/opal/browser/dom/element/button.rb +31 -0
  102. data/opal/browser/dom/element/custom.rb +177 -0
  103. data/opal/browser/dom/element/data.rb +17 -2
  104. data/opal/browser/dom/element/editable.rb +47 -0
  105. data/opal/browser/dom/element/form.rb +38 -0
  106. data/opal/browser/dom/element/iframe.rb +37 -0
  107. data/opal/browser/dom/element/image.rb +2 -0
  108. data/opal/browser/dom/element/input.rb +36 -0
  109. data/opal/browser/dom/element/media.rb +17 -0
  110. data/opal/browser/dom/element/scroll.rb +106 -74
  111. data/opal/browser/dom/element/select.rb +6 -0
  112. data/opal/browser/dom/element/size.rb +12 -0
  113. data/opal/browser/dom/element/template.rb +2 -0
  114. data/opal/browser/dom/element/textarea.rb +2 -0
  115. data/opal/browser/dom/element.rb +193 -48
  116. data/opal/browser/dom/mutation_observer.rb +2 -2
  117. data/opal/browser/dom/node.rb +53 -13
  118. data/opal/browser/dom/node_set.rb +11 -2
  119. data/opal/browser/dom/shadow_root.rb +12 -0
  120. data/opal/browser/dom/text.rb +2 -2
  121. data/opal/browser/dom.rb +38 -5
  122. data/opal/browser/effects.rb +170 -4
  123. data/opal/browser/event/all.rb +26 -0
  124. data/opal/browser/event/animation.rb +2 -0
  125. data/opal/browser/event/audio_processing.rb +2 -0
  126. data/opal/browser/event/base.rb +35 -4
  127. data/opal/browser/event/before_unload.rb +2 -0
  128. data/opal/browser/event/clipboard.rb +9 -0
  129. data/opal/browser/event/close.rb +2 -0
  130. data/opal/browser/event/composition.rb +2 -0
  131. data/opal/browser/event/custom.rb +1 -1
  132. data/opal/browser/event/data_transfer.rb +95 -0
  133. data/opal/browser/event/device_light.rb +2 -0
  134. data/opal/browser/event/device_motion.rb +2 -0
  135. data/opal/browser/event/device_orientation.rb +2 -0
  136. data/opal/browser/event/device_proximity.rb +2 -0
  137. data/opal/browser/event/drag.rb +9 -5
  138. data/opal/browser/event/focus.rb +2 -0
  139. data/opal/browser/event/gamepad.rb +3 -1
  140. data/opal/browser/event/hash_change.rb +2 -0
  141. data/opal/browser/event/keyboard.rb +14 -1
  142. data/opal/browser/event/message.rb +2 -0
  143. data/opal/browser/event/mouse.rb +10 -6
  144. data/opal/browser/event/page_transition.rb +2 -0
  145. data/opal/browser/event/pop_state.rb +2 -0
  146. data/opal/browser/event/progress.rb +2 -0
  147. data/opal/browser/event/sensor.rb +2 -0
  148. data/opal/browser/event/storage.rb +2 -0
  149. data/opal/browser/event/touch.rb +2 -0
  150. data/opal/browser/event/wheel.rb +2 -0
  151. data/opal/browser/event.rb +26 -116
  152. data/opal/browser/event_source.rb +1 -1
  153. data/opal/browser/form_data.rb +225 -0
  154. data/opal/browser/history.rb +4 -8
  155. data/opal/browser/http/request.rb +32 -10
  156. data/opal/browser/http/response.rb +5 -1
  157. data/opal/browser/http.rb +0 -2
  158. data/opal/browser/immediate.rb +0 -2
  159. data/opal/browser/location.rb +7 -1
  160. data/opal/browser/navigator.rb +105 -4
  161. data/opal/browser/polyfill/visual_viewport.rb +216 -0
  162. data/opal/browser/screen.rb +2 -2
  163. data/opal/browser/setup/base.rb +6 -0
  164. data/opal/browser/setup/full.rb +13 -0
  165. data/opal/browser/setup/large.rb +17 -0
  166. data/opal/browser/setup/mini.rb +8 -0
  167. data/opal/browser/setup/traditional.rb +10 -0
  168. data/opal/browser/socket.rb +3 -3
  169. data/opal/browser/storage.rb +2 -2
  170. data/opal/browser/support.rb +13 -1
  171. data/opal/browser/utils.rb +94 -14
  172. data/opal/browser/version.rb +1 -1
  173. data/opal/browser/visual_viewport.rb +39 -0
  174. data/opal/browser/window/size.rb +14 -0
  175. data/opal/browser/window/view.rb +15 -0
  176. data/opal/browser/window.rb +29 -16
  177. data/opal/browser.rb +1 -11
  178. data/opal-browser.gemspec +3 -3
  179. data/spec/database/sql_spec.rb +43 -35
  180. data/spec/delay_spec.rb +15 -12
  181. data/spec/dom/document_spec.rb +10 -8
  182. data/spec/dom/element/custom_spec.rb +106 -0
  183. data/spec/dom/element/subclass_spec.rb +144 -0
  184. data/spec/dom/element_spec.rb +42 -0
  185. data/spec/dom/mutation_observer_spec.rb +12 -8
  186. data/spec/dom/node_spec.rb +48 -0
  187. data/spec/dom_spec.rb +8 -0
  188. data/spec/event_source_spec.rb +15 -12
  189. data/spec/{dom/event_spec.rb → event_spec.rb} +44 -15
  190. data/spec/history_spec.rb +23 -19
  191. data/spec/http_spec.rb +19 -31
  192. data/spec/immediate_spec.rb +5 -4
  193. data/spec/interval_spec.rb +18 -9
  194. data/spec/native_cached_wrapper_spec.rb +46 -0
  195. data/spec/runner.rb +37 -62
  196. data/spec/socket_spec.rb +15 -12
  197. data/spec/spec_helper.rb +2 -1
  198. data/spec/spec_helper_promise.rb.erb +25 -0
  199. metadata +119 -16
  200. data/.travis.yml +0 -74
  201. 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
19
36
  end
20
37
 
21
- def self.new(node)
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
61
+ end
62
+
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,10 @@ 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)
356
+ def inner_dom(builder=nil, &block)
275
357
  clear
276
358
 
277
- # FIXME: when block passing is fixed
278
- doc = document
279
-
280
- self << Builder.new(doc, self, &block).to_a
359
+ self << Builder.new(document, builder, &block).to_a
281
360
  end
282
361
 
283
362
  # Set the inner DOM with the given node.
@@ -289,6 +368,16 @@ class Element < Node
289
368
  self << node
290
369
  end
291
370
 
371
+ # @!attribute inner_html
372
+ # @return [String] the inner HTML of the element
373
+ def inner_html
374
+ `#@native.innerHTML`
375
+ end
376
+
377
+ def inner_html=(value)
378
+ `#@native.innerHTML = #{value}`
379
+ end
380
+
292
381
  def inspect
293
382
  inspect = name.downcase
294
383
 
@@ -300,7 +389,7 @@ class Element < Node
300
389
  inspect += '.' + class_names.join('.')
301
390
  end
302
391
 
303
- "#<DOM::Element: #{inspect}>"
392
+ "#<#{self.class.name.gsub("Browser::","")}: #{inspect}>"
304
393
  end
305
394
 
306
395
  # @!attribute offset
@@ -319,16 +408,22 @@ class Element < Node
319
408
  offset.set(*value)
320
409
  end
321
410
 
411
+ # @!attribute outer_html
412
+ # @return [String] the outer HTML of the element
413
+ def outer_html
414
+ `#@native.outerHTML`
415
+ end
416
+
322
417
  # @!attribute [r] position
323
418
  # @return [Position] the position of the element
324
419
  def position
325
- Position.new(self)
420
+ @position ||= Position.new(self)
326
421
  end
327
422
 
328
423
  # @!attribute [r] scroll
329
424
  # @return [Scroll] the scrolling for the element
330
425
  def scroll
331
- Scroll.new(self)
426
+ @scroll ||= Scroll.new(self)
332
427
  end
333
428
 
334
429
  # Search for all the children matching the given XPaths or CSS selectors.
@@ -346,6 +441,27 @@ class Element < Node
346
441
 
347
442
  alias set_attribute []=
348
443
 
444
+ # Creates or accesses the shadow root of this element
445
+ #
446
+ # @param open [Boolean] set to false if you want to create a closed
447
+ # shadow root
448
+ #
449
+ # @return [ShadowRoot]
450
+ def shadow (open = true)
451
+ if root = `#@native.shadowRoot`
452
+ DOM(root)
453
+ else
454
+ DOM(`#@native.attachShadow({mode: #{open ? "open" : "closed"}})`)
455
+ end
456
+ end
457
+
458
+ # Checks for a presence of a shadow root of this element
459
+ #
460
+ # @return [Boolean]
461
+ def shadow?
462
+ `!!#@native.shadowRoot`
463
+ end
464
+
349
465
  # @overload style()
350
466
  #
351
467
  # Return the style for the element.
@@ -429,6 +545,18 @@ class Element < Node
429
545
  Size.new(self, *inc)
430
546
  end
431
547
 
548
+ # Toggle class names of the element.
549
+ #
550
+ # @param names [Array<String>] class names to toggle
551
+ #
552
+ # @return [self]
553
+ def toggle_class(*names)
554
+ to_remove, to_add = names.partition { |name| class_names.include? name }
555
+
556
+ add_class(*to_add)
557
+ remove_class(*to_remove)
558
+ end
559
+
432
560
  # @!attribute width
433
561
  # @return [Integer] the width of the element
434
562
  def width
@@ -456,7 +584,7 @@ class Element < Node
456
584
  #@native, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)`,
457
585
  get: :snapshotItem,
458
586
  length: :snapshotLength)]
459
- rescue
587
+ rescue StandardError, JS::Error
460
588
  NodeSet[]
461
589
  end
462
590
  else
@@ -472,3 +600,20 @@ class Element < Node
472
600
  end
473
601
 
474
602
  end; end
603
+
604
+ require 'browser/dom/element/attributes'
605
+ require 'browser/dom/element/data'
606
+ require 'browser/dom/element/position'
607
+ require 'browser/dom/element/offset'
608
+ require 'browser/dom/element/scroll'
609
+ require 'browser/dom/element/size'
610
+
611
+ require 'browser/dom/element/button'
612
+ require 'browser/dom/element/image'
613
+ require 'browser/dom/element/form'
614
+ require 'browser/dom/element/input'
615
+ require 'browser/dom/element/select'
616
+ require 'browser/dom/element/template'
617
+ require 'browser/dom/element/textarea'
618
+ require 'browser/dom/element/iframe'
619
+ 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