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,6 +1,8 @@
1
1
  module Browser; module DOM
2
2
 
3
3
  class Document < Element
4
+ include DocumentOrShadowRoot
5
+
4
6
  # Get the first element matching the given ID, CSS selector or XPath.
5
7
  #
6
8
  # @param what [String] ID, CSS selector or XPath
@@ -24,20 +26,67 @@ class Document < Element
24
26
  # @return [Element?] the body element of the document
25
27
  def body
26
28
  DOM(`#@native.body`)
29
+ rescue ArgumentError
30
+ raise '$document.body is not defined; try to wrap your code in $document.ready{}'
27
31
  end
28
32
 
29
33
  # Create a new element for the document.
30
34
  #
31
35
  # @param name [String] the node name
32
- # @param options [Hash] optional `:namespace` name
36
+ # @param builder [Browser::DOM::Builder] optional builder to append element to
37
+ # @param options [String] :namespace optional namespace name
38
+ # @param options [String] :is optional WebComponents is parameter
39
+ # @param options [String] :id optional id to set
40
+ # @param options [Array<String>] :classes optional classes to set
41
+ # @param options [Hash] :attrs optional attributes to set
33
42
  #
34
43
  # @return [Element]
35
- def create_element(name, options = {})
44
+ def create_element(name, builder=nil, **options, &block)
45
+ opts = {}
46
+
47
+ if options[:is] ||= (options.dig(:attrs, :is))
48
+ opts[:is] = options[:is]
49
+ end
50
+
36
51
  if ns = options[:namespace]
37
- DOM(`#@native.createElementNS(#{ns}, #{name})`)
52
+ elem = `#@native.createElementNS(#{ns}, #{name}, #{opts.to_n})`
38
53
  else
39
- DOM(`#@native.createElement(name)`)
54
+ elem = `#@native.createElement(name, #{opts.to_n})`
40
55
  end
56
+
57
+ if options[:classes]
58
+ `#{elem}.className = #{Array(options[:classes]).join(" ")}`
59
+ end
60
+
61
+ if options[:id]
62
+ `#{elem}.id = #{options[:id]}`
63
+ end
64
+
65
+ if options[:attrs]
66
+ options[:attrs].each do |k,v|
67
+ next unless v
68
+ `#{elem}.setAttribute(#{k}, #{v})`
69
+ end
70
+ end
71
+
72
+ dom = DOM(elem)
73
+
74
+ if block_given?
75
+ dom.inner_dom(builder, &block)
76
+ end
77
+
78
+ if builder
79
+ builder << dom
80
+ end
81
+
82
+ dom
83
+ end
84
+
85
+ # Create a new document fragment.
86
+ #
87
+ # @return [DocumentFragment]
88
+ def create_document_fragment
89
+ DOM(`#@native.createDocumentFragment()`)
41
90
  end
42
91
 
43
92
  # Create a new text node for the document.
@@ -49,6 +98,15 @@ class Document < Element
49
98
  DOM(`#@native.createTextNode(#{content})`)
50
99
  end
51
100
 
101
+ # Create a new comment node for the document.
102
+ #
103
+ # @param content [String] the comment content
104
+ #
105
+ # @return [Comment]
106
+ def create_comment(content)
107
+ DOM(`#@native.createComment(#{content})`)
108
+ end
109
+
52
110
  def document
53
111
  self
54
112
  end
@@ -98,7 +156,13 @@ class Document < Element
98
156
 
99
157
  # Check if the document is ready.
100
158
  def ready?
101
- `#@native.readyState === "complete"`
159
+ `#@native.readyState === "complete" || #@native.readyState === "interactive"`
160
+ end
161
+
162
+ # @!attribute referrer
163
+ # @return [String] the referring document, or empty string if direct access
164
+ def referrer
165
+ `#@native.referrer`
102
166
  end
103
167
 
104
168
  # @!attribute root
@@ -111,14 +175,6 @@ class Document < Element
111
175
  `#@native.documentElement = #{Native.convert(element)}`
112
176
  end
113
177
 
114
- # @!attribute [r] style_sheets
115
- # @return [Array<CSS::StyleSheet>] the style sheets for the document
116
- def style_sheets
117
- Native::Array.new(`#@native.styleSheets`) {|e|
118
- CSS::StyleSheet.new(e)
119
- }
120
- end
121
-
122
178
  # @!attribute title
123
179
  # @return [String] the document title
124
180
  def title
@@ -129,6 +185,18 @@ class Document < Element
129
185
  `#@native.title = value`
130
186
  end
131
187
 
188
+ # @!attribute [r] hidden?
189
+ # @return [Boolean] is the page considered hidden?
190
+ def hidden?
191
+ `#@native.hidden`
192
+ end
193
+
194
+ # @!attribute [r] visibility
195
+ # @return [String] the visibility state of the document - prerender, hidden or visible
196
+ def visibility
197
+ `#@native.visibilityState`
198
+ end
199
+
132
200
  if Browser.supports? 'Document.view'
133
201
  def window
134
202
  Window.new(`#@native.defaultView`)
@@ -1,7 +1,25 @@
1
1
  module Browser; module DOM
2
2
 
3
+ # TODO: DocumentFragment is not a subclass of Element, but
4
+ # a subclass of Node. It implements a ParentNode.
5
+ #
6
+ # @see https://github.com/opal/opal-browser/pull/46
3
7
  class DocumentFragment < Element
8
+ def self.new(node)
9
+ if self == DocumentFragment
10
+ if defined? `#{node}.mode`
11
+ ShadowRoot.new(node)
12
+ else
13
+ super
14
+ end
15
+ else
16
+ super
17
+ end
18
+ end
4
19
 
20
+ def self.create
21
+ $document.create_document_fragment
22
+ end
5
23
  end
6
24
 
7
25
  end; end
@@ -0,0 +1,19 @@
1
+ module Browser; module DOM
2
+
3
+ # Document and ShadowRoot have some methods and properties in common.
4
+ # This solution mimics how it's done in DOM.
5
+ #
6
+ # @see https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot
7
+ module DocumentOrShadowRoot
8
+ # @!attribute [r] style_sheets
9
+ # @return [Array<CSS::StyleSheet>] the style sheets for the document
10
+ def style_sheets
11
+ Native::Array.new(`#@native.styleSheets`) {|e|
12
+ CSS::StyleSheet.new(e)
13
+ }
14
+ end
15
+
16
+ alias stylesheets style_sheets
17
+ end
18
+
19
+ end; end
@@ -32,9 +32,17 @@ class Attributes
32
32
  end
33
33
 
34
34
  if namespace = options[:namespace] || @namespace
35
- `#@native.setAttributeNS(#{namespace.to_s}, #{name.to_s}, #{value})`
35
+ if value
36
+ `#@native.setAttributeNS(#{namespace.to_s}, #{name.to_s}, #{value})`
37
+ else
38
+ `#@native.removeAttributeNS(#{namespace.to_s}, #{name.to_s})`
39
+ end
36
40
  else
37
- `#@native.setAttribute(#{name.to_s}, #{value.to_s})`
41
+ if value
42
+ `#@native.setAttribute(#{name.to_s}, #{value.to_s})`
43
+ else
44
+ `#@native.removeAttribute(#{name.to_s})`
45
+ end
38
46
  end
39
47
  end
40
48
  else
@@ -48,13 +56,29 @@ class Attributes
48
56
 
49
57
  def []=(name, value, options = {})
50
58
  if namespace = options[:namespace] || @namespace
51
- `#@native.setAttributeNS(#{namespace.to_s}, #{name.to_s}, #{value})`
59
+ if value
60
+ `#@native.setAttributeNS(#{namespace.to_s}, #{name.to_s}, #{value})`
61
+ else
62
+ `#@native.removeAttributeNS(#{namespace.to_s}, #{name.to_s})`
63
+ end
52
64
  else
53
- `#@native.setAttribute(#{name.to_s}, #{value.to_s})`
65
+ if value
66
+ `#@native.setAttribute(#{name.to_s}, #{value.to_s})`
67
+ else
68
+ `#@native.removeAttribute(#{name.to_s})`
69
+ end
54
70
  end
55
71
  end
56
72
  end
57
73
 
74
+ # Deletes an attribute with a given name
75
+ # @return [String] an attribute value before deletion
76
+ def delete(name)
77
+ attr = self[name]
78
+ self[name] = nil
79
+ attr
80
+ end
81
+
58
82
  include Enumerable
59
83
 
60
84
  def each(&block)
@@ -0,0 +1,31 @@
1
+ module Browser; module DOM; class Element < Node
2
+
3
+ class Button < Element
4
+ def_selector "button"
5
+
6
+ def disabled?
7
+ `#@native.disabled`
8
+ end
9
+
10
+ def disabled=(value)
11
+ `#@native.disabled = #{value}`
12
+ end
13
+
14
+ def autofocus?
15
+ `#@native.autofocus`
16
+ end
17
+
18
+ def autofocus=(value)
19
+ `#@native.autofocus = #{value}`
20
+ end
21
+
22
+ def name_
23
+ `#@native.name`
24
+ end
25
+
26
+ def name_=(value)
27
+ `#@native.name = #{value}`
28
+ end
29
+ end
30
+
31
+ end; end; end
@@ -0,0 +1,177 @@
1
+ # use_strict: true
2
+ # helpers: truthy
3
+
4
+ module Browser; module DOM; class Element < Node
5
+
6
+ # CustomElements implementation for opal-browser. See examples/custom_elements/.
7
+ #
8
+ # @see https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements
9
+ # @abstract This class should not be used directly. Please extend it and implement needed methods.
10
+ class Custom < Element
11
+ # The reason why we wrap class definition with an eval is kind of selfish. I want it to work
12
+ # with opal-optimizer which doesn't support the new class syntax. I would do it with prototypes,
13
+ # but the prototypes system is so messy I gave up.
14
+ #
15
+ # Therefore, for it to be cleaned up, one of those two must happen:
16
+ # - we raise the supported ES version in Opal and we implement those ES syntax features in
17
+ # rkelly-turbo. And then we remove the polyfill.
18
+ # - we reimplement it in terms of prototypes.
19
+ %x{
20
+ var make_custom_class = Function('self,base_class',
21
+ '"use strict"; \
22
+ var klass = class extends base_class { \
23
+ constructor() { \
24
+ super(); \
25
+ self.$_dispatch_constructor(this); \
26
+ } \
27
+ connectedCallback() { \
28
+ return this.$$opal_native_cached.$attached(); \
29
+ } \
30
+ disconnectedCallback() { \
31
+ return this.$$opal_native_cached.$detached(); \
32
+ } \
33
+ adoptedCallback() { \
34
+ return this.$$opal_native_cached.$adopted(); \
35
+ } \
36
+ attributeChangedCallback(attr, from, to) { \
37
+ if (from === null) from = Opal.nil; \
38
+ if (to === null) to = Opal.nil; \
39
+ return this.$$opal_native_cached.$attribute_changed(attr, from, to); \
40
+ } \
41
+ \
42
+ static get observedAttributes() { \
43
+ return self.$observed_attributes(); \
44
+ } \
45
+ }; \
46
+ klass.$$opal_class = self; \
47
+ return klass;'
48
+ );
49
+ } if Browser.supports? 'Custom Elements' #'
50
+
51
+ module ClassMethods
52
+ if Browser.supports? 'Custom Elements'
53
+ # Defines a new custom element. This should come as the last call
54
+ # in the class definition, because at this point the methods may
55
+ # be called!
56
+ #
57
+ # @opalopt uses:_dispatch_constructor,attached,detached,adopted,attribute_changed,observed_attributes
58
+ def def_custom(tag_name, base_class: nil, extends: nil)
59
+ if `base_class !== nil`
60
+ elsif self.superclass == Custom
61
+ base_class = `HTMLElement`
62
+ elsif self.ancestors.include? Custom
63
+ base_class = `#{self.superclass}.custom_class`
64
+ else
65
+ raise ArgumentError, "You must define base_class"
66
+ end
67
+
68
+ @custom_class = `make_custom_class(self, #{base_class})`
69
+ @observed_attributes ||= []
70
+
71
+ def_selector tag_name
72
+
73
+ %x{
74
+ if ($truthy(#{extends})) customElements.define(#{tag_name}, #{@custom_class}, {extends: #{extends}});
75
+ else customElements.define(#{tag_name}, #{@custom_class});
76
+ }
77
+ end
78
+ elsif Browser.supports? 'MutationObserver'
79
+ # Can we polyfill it?
80
+ Browser::DOM::MutationObserver.new do |obs|
81
+ obs.each do |e|
82
+ target = e.target
83
+
84
+ case e.type
85
+ when :attribute
86
+ if Custom::Mixin === target && target.class.observed_attributes.include?(e.name)
87
+ target.attribute_changed(e.name, e.old, target[e.name])
88
+ end
89
+ when :tree
90
+ e.added.each { |n| n.attached_once if Custom::Mixin === n }
91
+ e.removed.each { |n| n.detached_once if Custom::Mixin === n }
92
+ end
93
+ end
94
+ end.observe($document, tree: true, children: true, attributes: :old)
95
+ end
96
+
97
+ unless Browser.supports? 'Custom Elements'
98
+ # The polyfilled implementation. Define the selector and then
99
+ # try to upgrade the elements that are already in the document.
100
+ def def_custom(tag_name, base_class: nil, extends: nil)
101
+ def_selector tag_name
102
+
103
+ $document.body.css(tag_name).each do |elem|
104
+ _dispatch_constructor(elem.to_n)&.attached_once
105
+ end
106
+ end
107
+ end
108
+
109
+ private def _dispatch_constructor(obj)
110
+ %x{
111
+ if (typeof obj.$$opal_native_cached !== 'undefined') {
112
+ delete obj.$$opal_native_cached;
113
+ return self.$new(obj);
114
+ }
115
+ else {
116
+ self.$new(obj);
117
+ return nil;
118
+ }
119
+ }
120
+ end
121
+
122
+ # This must be defined before def_custom is called!
123
+ attr_accessor :observed_attributes
124
+
125
+ attr_reader :custom_class
126
+ end
127
+
128
+ module Mixin
129
+ def self.included(klass)
130
+ klass.extend ClassMethods
131
+ end
132
+
133
+ # @abstract
134
+ def attached
135
+ end
136
+
137
+ # @abstract
138
+ def detached
139
+ end
140
+
141
+ # @abstract
142
+ def adopted
143
+ end
144
+
145
+ # Note: for this method to fire, you will need to define
146
+ # the observed attributes.
147
+ #
148
+ # @abstract
149
+ def attribute_changed(attr, from, to)
150
+ end
151
+
152
+ # Return true if the node is a custom element.
153
+ def custom?
154
+ true
155
+ end
156
+
157
+ # Those methods keep track of the attachment status of the elements,
158
+ # so that #attached/#detached isn't called twice.
159
+ unless Browser.supports? 'Custom Elements'
160
+ # @private
161
+ def attached_once
162
+ attached unless @_polyfill_attached
163
+ @_polyfill_attached = true
164
+ end
165
+
166
+ # @private
167
+ def detached_once
168
+ detached if @_polyfill_attached
169
+ @_polyfill_attached = false
170
+ end
171
+ end
172
+ end
173
+
174
+ include Mixin
175
+ end
176
+
177
+ end; end; end
@@ -36,7 +36,7 @@ class Data
36
36
 
37
37
  def assign(data)
38
38
  data.each {|name, value|
39
- `#@native.$data[name] = value`
39
+ self[name] = value
40
40
  }
41
41
 
42
42
  self
@@ -60,7 +60,22 @@ class Data
60
60
  end
61
61
 
62
62
  def []=(name, value)
63
- `#@native.$data[name] = value`
63
+ `delete #@native.$data[name]`
64
+ if [true, false, nil].include?(value)
65
+ @element["data-#{name}"] = value
66
+ elsif value.respond_to? :to_str
67
+ @element["data-#{name}"] = value.to_str
68
+ elsif value.respond_to? :to_int
69
+ @element["data-#{name}"] = value.to_int.to_s
70
+ else
71
+ `#@native.$data[name] = value`
72
+ end
73
+ end
74
+
75
+ def delete(name)
76
+ data = self[name]
77
+ self[name] = nil
78
+ data
64
79
  end
65
80
  end
66
81
 
@@ -0,0 +1,47 @@
1
+ module Browser; module DOM
2
+
3
+ class Element < Node
4
+ # @!attribute editable
5
+ # @return [Boolean?] the value of contentEditable for this element
6
+ def editable
7
+ case `#@native.contentEditable`
8
+ when "true"
9
+ true
10
+ when "false"
11
+ false
12
+ when "inherit"
13
+ nil
14
+ end
15
+ end
16
+
17
+ def editable=(value)
18
+ value = case value
19
+ when true
20
+ "true"
21
+ when false
22
+ "false"
23
+ when nil
24
+ "inherit"
25
+ end
26
+ `#@native.contentEditable = #{value}`
27
+ end
28
+
29
+ def editable?
30
+ `#@native.isContentEditable`
31
+ end
32
+
33
+ # Execute a contentEditable command
34
+ def edit(command, value=nil)
35
+ command = command.gsub(/_./) { |i| i[1].upcase }
36
+
37
+ focus
38
+
39
+ if value
40
+ `#{document}.native.execCommand(#{command}, false, #{value})`
41
+ else
42
+ `#{document}.native.execCommand(#{command}, false)`
43
+ end
44
+ end
45
+ end
46
+
47
+ end; end
@@ -0,0 +1,38 @@
1
+ module Browser; module DOM; class Element < Node
2
+
3
+ class Form < Element
4
+ def_selector "form"
5
+
6
+ # Capture the content of this form to a new {FormData} object,
7
+ #
8
+ # @return [FormData]
9
+ def form_data
10
+ FormData.create(self)
11
+ end
12
+
13
+ # Submit a form. This will fire a submit event.
14
+ def submit
15
+ `#@native.submit()`
16
+ end
17
+
18
+ # Reset a form. This will fire a reset event.
19
+ def reset
20
+ `#@native.reset()`
21
+ end
22
+
23
+ alias_native :action
24
+ alias_native :action=
25
+ alias_native :method
26
+ alias_native :method=
27
+ alias_native :target
28
+ alias_native :target=
29
+ alias_native :encoding
30
+ alias_native :encoding=
31
+
32
+ # Return a NodeSet containing all form controls belonging to this form element.
33
+ def controls
34
+ NodeSet[Native::Array.new(`#@native.elements`)]
35
+ end
36
+ end
37
+
38
+ end; end; end
@@ -0,0 +1,37 @@
1
+ module Browser; module DOM; class Element < Node
2
+
3
+ class Iframe < Element
4
+ def_selector "iframe"
5
+
6
+ # @!attribute src
7
+ # @return [String] the URL of the page to embed
8
+ alias_native :src
9
+ alias_native :src=
10
+
11
+ # @!attribute [r] content_window
12
+ # @return [Window] window of content of this iframe
13
+ def content_window
14
+ Browser::Window.new(`#@native.contentWindow`)
15
+ end
16
+
17
+ # @!attribute [r] content_document
18
+ # @return [Document] document of content of this iframe
19
+ def content_document
20
+ DOM(`#@native.contentDocument || #@native.contentWindow.document`)
21
+ end
22
+
23
+ # Send a message to the iframe content's window.
24
+ #
25
+ # @param message [String] the message
26
+ # @param options [Hash] optional `to: target`
27
+ def send(message, options={})
28
+ content_window.send(message, options)
29
+ end
30
+ end
31
+
32
+ # Object is not an iframe, but acts the same.
33
+ class Object < Iframe
34
+ def_selector "object"
35
+ end
36
+
37
+ end; end; end
@@ -1,6 +1,8 @@
1
1
  module Browser; module DOM; class Element < Node
2
2
 
3
3
  class Image < Element
4
+ def_selector "img"
5
+
4
6
  def complete?
5
7
  `#@native.complete`
6
8
  end
@@ -1,6 +1,8 @@
1
1
  module Browser; module DOM; class Element < Node
2
2
 
3
3
  class Input < Element
4
+ def_selector "input"
5
+
4
6
  def value
5
7
  %x{
6
8
  if (#@native.value == "") {
@@ -16,13 +18,47 @@ class Input < Element
16
18
  `#@native.value = #{value}`
17
19
  end
18
20
 
21
+ def name_
22
+ `#@native.name`
23
+ end
24
+
25
+ def type
26
+ `#@native.type`
27
+ end
28
+
19
29
  def checked?
20
30
  `#@native.checked`
21
31
  end
22
32
 
33
+ def check!
34
+ `#@native.checked = 'checked'`
35
+ end
36
+
37
+ def uncheck!
38
+ `#@native.checked = ''`
39
+ end
40
+
41
+ def enabled?
42
+ `#@native.enabled`
43
+ end
44
+
45
+ def disable!
46
+ `#@native.disabled = 'disabled'`
47
+ end
48
+
49
+ def enable!
50
+ `#@native.disabled = ''`
51
+ end
52
+
23
53
  def clear
24
54
  `#@native.value = ''`
25
55
  end
56
+
57
+ # @!attribute [r] files
58
+ # @return [Array<File>] list of files attached to this {Input}
59
+ def files
60
+ Native::Array.new(`#@native.files`).map { |f| File.new(f.to_n) }
61
+ end
26
62
  end
27
63
 
28
64
  end; end; end
@@ -0,0 +1,17 @@
1
+ module Browser; module DOM; class Element < Node
2
+
3
+ class Media < Element
4
+ def play
5
+ `#@native.play()`
6
+ end
7
+ end
8
+
9
+ class Video < Media
10
+ def_selector "video"
11
+ end
12
+
13
+ class Audio < Media
14
+ def_selector "audio"
15
+ end
16
+
17
+ end; end; end