opal-browser 0.2.0 → 0.3.3

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