opal-browser 0.2.0.beta1 → 0.3.2

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 (218) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/build.yml +95 -0
  3. data/.gitignore +3 -0
  4. data/CHANGELOG.md +8 -0
  5. data/Gemfile +17 -3
  6. data/LICENSE +2 -1
  7. data/README.md +183 -52
  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 +8 -6
  78. data/lib/opal-browser.rb +1 -0
  79. data/opal/browser/animation_frame.rb +26 -1
  80. data/opal/browser/audio/node.rb +121 -0
  81. data/opal/browser/audio/param_schedule.rb +43 -0
  82. data/opal/browser/audio.rb +66 -0
  83. data/opal/browser/blob.rb +94 -0
  84. data/opal/browser/canvas/data.rb +1 -11
  85. data/opal/browser/canvas/gradient.rb +1 -11
  86. data/opal/browser/canvas/style.rb +3 -11
  87. data/opal/browser/canvas/text.rb +1 -11
  88. data/opal/browser/canvas.rb +17 -13
  89. data/opal/browser/console.rb +3 -1
  90. data/opal/browser/cookies.rb +78 -42
  91. data/opal/browser/crypto.rb +79 -0
  92. data/opal/browser/css/declaration.rb +1 -1
  93. data/opal/browser/css/rule.rb +1 -1
  94. data/opal/browser/css/style_sheet.rb +2 -2
  95. data/opal/browser/css.rb +23 -7
  96. data/opal/browser/database/sql.rb +193 -0
  97. data/opal/browser/delay.rb +41 -7
  98. data/opal/browser/dom/attribute.rb +13 -12
  99. data/opal/browser/dom/builder.rb +31 -17
  100. data/opal/browser/dom/document.rb +174 -42
  101. data/opal/browser/dom/document_fragment.rb +18 -0
  102. data/opal/browser/dom/document_or_shadow_root.rb +19 -0
  103. data/opal/browser/dom/element/attributes.rb +111 -0
  104. data/opal/browser/dom/element/button.rb +31 -0
  105. data/opal/browser/dom/element/custom.rb +177 -0
  106. data/opal/browser/dom/element/data.rb +82 -0
  107. data/opal/browser/dom/element/editable.rb +47 -0
  108. data/opal/browser/dom/element/form.rb +38 -0
  109. data/opal/browser/dom/element/iframe.rb +37 -0
  110. data/opal/browser/dom/element/image.rb +2 -0
  111. data/opal/browser/dom/element/input.rb +48 -1
  112. data/opal/browser/dom/element/media.rb +17 -0
  113. data/opal/browser/dom/element/offset.rb +5 -0
  114. data/opal/browser/dom/element/position.rb +11 -2
  115. data/opal/browser/dom/element/scroll.rb +123 -24
  116. data/opal/browser/dom/element/select.rb +42 -0
  117. data/opal/browser/dom/element/size.rb +17 -0
  118. data/opal/browser/dom/element/template.rb +11 -0
  119. data/opal/browser/dom/element/textarea.rb +26 -0
  120. data/opal/browser/dom/element.rb +468 -238
  121. data/opal/browser/dom/mutation_observer.rb +4 -4
  122. data/opal/browser/dom/node.rb +142 -60
  123. data/opal/browser/dom/node_set.rb +73 -44
  124. data/opal/browser/dom/shadow_root.rb +12 -0
  125. data/opal/browser/dom/text.rb +2 -2
  126. data/opal/browser/dom.rb +40 -16
  127. data/opal/browser/effects.rb +180 -3
  128. data/opal/browser/event/all.rb +26 -0
  129. data/opal/browser/{dom/event → event}/animation.rb +4 -2
  130. data/opal/browser/{dom/event → event}/audio_processing.rb +4 -2
  131. data/opal/browser/{dom/event → event}/base.rb +98 -9
  132. data/opal/browser/{dom/event → event}/before_unload.rb +4 -2
  133. data/opal/browser/{dom/event → event}/clipboard.rb +11 -2
  134. data/opal/browser/{dom/event → event}/close.rb +4 -2
  135. data/opal/browser/{dom/event → event}/composition.rb +4 -2
  136. data/opal/browser/{dom/event → event}/custom.rb +3 -3
  137. data/opal/browser/event/data_transfer.rb +95 -0
  138. data/opal/browser/{dom/event → event}/device_light.rb +4 -2
  139. data/opal/browser/{dom/event → event}/device_motion.rb +4 -2
  140. data/opal/browser/{dom/event → event}/device_orientation.rb +4 -2
  141. data/opal/browser/{dom/event → event}/device_proximity.rb +4 -2
  142. data/opal/browser/{dom/event → event}/drag.rb +11 -7
  143. data/opal/browser/{dom/event → event}/focus.rb +4 -2
  144. data/opal/browser/{dom/event → event}/gamepad.rb +5 -3
  145. data/opal/browser/{dom/event → event}/hash_change.rb +4 -2
  146. data/opal/browser/{dom/event → event}/keyboard.rb +16 -3
  147. data/opal/browser/{dom/event → event}/message.rb +4 -2
  148. data/opal/browser/{dom/event → event}/mouse.rb +12 -8
  149. data/opal/browser/{dom/event → event}/page_transition.rb +4 -2
  150. data/opal/browser/{dom/event → event}/pop_state.rb +4 -2
  151. data/opal/browser/{dom/event → event}/progress.rb +4 -2
  152. data/opal/browser/{dom/event → event}/sensor.rb +4 -2
  153. data/opal/browser/{dom/event → event}/storage.rb +4 -2
  154. data/opal/browser/{dom/event → event}/touch.rb +4 -2
  155. data/opal/browser/{dom/event → event}/ui.rb +2 -2
  156. data/opal/browser/{dom/event → event}/wheel.rb +4 -2
  157. data/opal/browser/event.rb +163 -0
  158. data/opal/browser/event_source.rb +2 -2
  159. data/opal/browser/form_data.rb +225 -0
  160. data/opal/browser/history.rb +4 -8
  161. data/opal/browser/http/binary.rb +1 -0
  162. data/opal/browser/http/headers.rb +16 -2
  163. data/opal/browser/http/request.rb +46 -48
  164. data/opal/browser/http/response.rb +5 -1
  165. data/opal/browser/http.rb +25 -2
  166. data/opal/browser/immediate.rb +9 -5
  167. data/opal/browser/interval.rb +34 -11
  168. data/opal/browser/location.rb +7 -1
  169. data/opal/browser/navigator.rb +127 -7
  170. data/opal/browser/polyfill/visual_viewport.rb +216 -0
  171. data/opal/browser/screen.rb +3 -3
  172. data/opal/browser/setup/base.rb +6 -0
  173. data/opal/browser/setup/full.rb +13 -0
  174. data/opal/browser/setup/large.rb +17 -0
  175. data/opal/browser/setup/mini.rb +8 -0
  176. data/opal/browser/setup/traditional.rb +10 -0
  177. data/opal/browser/socket.rb +8 -4
  178. data/opal/browser/storage.rb +53 -35
  179. data/opal/browser/support.rb +72 -5
  180. data/opal/browser/utils.rb +94 -14
  181. data/opal/browser/version.rb +1 -1
  182. data/opal/browser/visual_viewport.rb +39 -0
  183. data/opal/browser/window/size.rb +31 -3
  184. data/opal/browser/window/view.rb +15 -0
  185. data/opal/browser/window.rb +46 -25
  186. data/opal/browser.rb +1 -10
  187. data/opal/opal-browser.rb +1 -0
  188. data/opal-browser.gemspec +3 -3
  189. data/spec/database/sql_spec.rb +139 -0
  190. data/spec/delay_spec.rb +41 -0
  191. data/spec/dom/attribute_spec.rb +49 -0
  192. data/spec/dom/builder_spec.rb +25 -8
  193. data/spec/dom/document_spec.rb +22 -0
  194. data/spec/dom/element/attributes_spec.rb +52 -0
  195. data/spec/dom/element/custom_spec.rb +106 -0
  196. data/spec/dom/element/subclass_spec.rb +144 -0
  197. data/spec/dom/element_spec.rb +181 -4
  198. data/spec/dom/mutation_observer_spec.rb +12 -8
  199. data/spec/dom/node_set_spec.rb +44 -0
  200. data/spec/dom/node_spec.rb +48 -0
  201. data/spec/dom_spec.rb +8 -0
  202. data/spec/event_source_spec.rb +15 -12
  203. data/spec/{dom/event_spec.rb → event_spec.rb} +44 -15
  204. data/spec/history_spec.rb +23 -19
  205. data/spec/http_spec.rb +19 -31
  206. data/spec/immediate_spec.rb +5 -4
  207. data/spec/interval_spec.rb +59 -0
  208. data/spec/native_cached_wrapper_spec.rb +46 -0
  209. data/spec/runner.rb +62 -69
  210. data/spec/socket_spec.rb +16 -12
  211. data/spec/spec_helper.rb +2 -5
  212. data/spec/spec_helper_promise.rb.erb +25 -0
  213. data/spec/storage_spec.rb +1 -1
  214. metadata +172 -50
  215. data/.travis.yml +0 -60
  216. data/opal/browser/dom/event.rb +0 -253
  217. data/opal/browser/http/parameters.rb +0 -8
  218. data/opal/browser/window/scroll.rb +0 -59
@@ -0,0 +1,193 @@
1
+ require 'ostruct'
2
+
3
+ module Browser; module Database
4
+
5
+ class SQL
6
+ # Check if the browser supports WebSQL.
7
+ def self.supported?
8
+ Browser.supports? 'WebSQL'
9
+ end
10
+
11
+ class Error < StandardError
12
+ def self.new(error)
13
+ return super if self != Error
14
+
15
+ [Unknown, Database, Version, TooLarge, Quota, Syntax, Constraint, Timeout] \
16
+ [`error.code`].new(`error.message`)
17
+ end
18
+
19
+ Unknown = Class.new(self)
20
+ Database = Class.new(self)
21
+ Version = Class.new(self)
22
+ TooLarge = Class.new(self)
23
+ Quota = Class.new(self)
24
+ Syntax = Class.new(self)
25
+ Constraint = Class.new(self)
26
+ Timeout = Class.new(self)
27
+ end
28
+
29
+ include Native::Wrapper
30
+
31
+ # @return [String] the name of the database
32
+ attr_reader :name
33
+
34
+ # @return [String] the description for the database
35
+ attr_reader :description
36
+
37
+ # @return [Integer] the size constraint in bytes
38
+ attr_reader :size
39
+
40
+ # Open a database with the given name and options.
41
+ #
42
+ # @param name [String] the name for the database
43
+ # @param options [Hash] options to open the database
44
+ #
45
+ # @option options [String] :description the description for the database
46
+ # @option options [String] :version ('') the expected version of the database
47
+ # @option options [Integer] :size (5 * 1024 * 1024) the size constraint in bytes
48
+ def initialize(name, options = {})
49
+ @name = name
50
+ @description = options[:description] || name
51
+ @version = options[:version] || ''
52
+ @size = options[:size] || 2 * 1024 * 1024
53
+
54
+ super(`window.openDatabase(#{name}, #{@version}, #{@description}, #{@size})`)
55
+ end
56
+
57
+ # @overload version()
58
+ #
59
+ # Get the version of the database.
60
+ #
61
+ # @return [String]
62
+ #
63
+ # @overload version(from, to, &block)
64
+ #
65
+ # Migrate the database to a new version.
66
+ #
67
+ # @param from [String] the version you're migrating from
68
+ # @param to [String] the version you're migrating to
69
+ #
70
+ # @yieldparam transaction [Transaction] the transaction to work with
71
+ def version(from = nil, to = nil, &block)
72
+ return `#@native.version` unless block
73
+
74
+ `#@native.changeVersion(#{from}, #{to},
75
+ #{->(t) { block.call(Transaction.new(self, t)) }})`
76
+ end
77
+
78
+ # Start a transaction on the database.
79
+ #
80
+ # @yieldparam transaction [Transaction] the transaction to work on
81
+ def transaction(&block)
82
+ raise ArgumentError, 'no block given' unless block
83
+
84
+ `#@native.transaction(#{->(t) { block.call(Transaction.new(self, t)) }})`
85
+ end
86
+
87
+ # Allows you to make changes to the database or read data from it.
88
+ class Transaction
89
+ include Native::Wrapper
90
+
91
+ # @return [Database] the database the transaction has been created from
92
+ attr_reader :database
93
+
94
+ # @private
95
+ def initialize(database, transaction)
96
+ @database = database
97
+
98
+ super(transaction)
99
+ end
100
+
101
+ # Query the database.
102
+ #
103
+ # @param query [String] the SQL query to send
104
+ # @param parameters [Array] optional bind parameters for the query
105
+ #
106
+ # @return [Promise]
107
+ def query(query, *parameters)
108
+ promise = Promise.new
109
+
110
+ `#@native.executeSql(#{query}, #{parameters},
111
+ #{->(_, r) { promise.resolve(Result.new(self, r)) }},
112
+ #{->(_, e) { promise.reject(Error.new(e)) }})`
113
+
114
+ promise
115
+ end
116
+ end
117
+
118
+ # Represents a row.
119
+ class Row < OpenStruct
120
+ # @private
121
+ def initialize(row)
122
+ super(Hash.new(row))
123
+ end
124
+
125
+ def inspect
126
+ "#<SQL::Row: #{Hash.new(@native).inspect}>"
127
+ end
128
+ end
129
+
130
+ class Result
131
+ include Native::Wrapper
132
+
133
+ # @return [Transaction] the transaction the result came from
134
+ attr_reader :transaction
135
+
136
+ # @return [SQL] the database the result came from
137
+ attr_reader :database
138
+
139
+ # @private
140
+ def initialize(transaction, result)
141
+ @transaction = transaction
142
+ @database = transaction.database
143
+
144
+ super(result)
145
+ end
146
+
147
+ include Enumerable
148
+
149
+ # Get a row from the result.
150
+ #
151
+ # @param index [Integer] the index for the row
152
+ #
153
+ # @return [Row]
154
+ def [](index)
155
+ if index < 0
156
+ index += length
157
+ end
158
+
159
+ unless index < 0 || index >= length
160
+ Row.new(`#@native.rows.item(index)`)
161
+ end
162
+ end
163
+
164
+ # Enumerate over the rows.
165
+ #
166
+ # @yieldparam row [Row]
167
+ #
168
+ # @return [self]
169
+ def each(&block)
170
+ return enum_for :each unless block
171
+
172
+ %x{
173
+ for (var i = 0, length = #@native.rows.length; i < length; i++) {
174
+ #{block.call(self[`i`])};
175
+ }
176
+ }
177
+
178
+ self
179
+ end
180
+
181
+ # @!attribute [r] length
182
+ # @return [Integer] number of rows in the result
183
+ def length
184
+ `#@native.rows.length`
185
+ end
186
+
187
+ # @!attribute [r] affected
188
+ # @return [Integer] number of affected rows
189
+ alias_native :affected, :rowsAffected
190
+ end
191
+ end
192
+
193
+ end; end
@@ -17,8 +17,6 @@ class Delay
17
17
  @window = Native.convert(window)
18
18
  @after = time
19
19
  @block = block
20
-
21
- start
22
20
  end
23
21
 
24
22
  # Abort the timeout.
@@ -39,10 +37,48 @@ class Window
39
37
  #
40
38
  # @return [Delay] the object representing the timeout
41
39
  def after(time, &block)
40
+ Delay.new(@native, time, &block).tap(&:start)
41
+ end
42
+
43
+ # Execute a block after the given seconds, you have to call [#start] on it
44
+ # yourself.
45
+ #
46
+ # @param time [Float] the seconds after it gets called
47
+ #
48
+ # @return [Delay] the object representing the timeout
49
+ def after!(time, &block)
42
50
  Delay.new(@native, time, &block)
43
51
  end
52
+
53
+ # Returns a promise that will resolve after the given seconds.
54
+ #
55
+ # @param time [Float] the seconds after it gets called
56
+ #
57
+ # @return [Promise] the promise that will resolve after timeout happens
58
+ def resolve_after(time)
59
+ promise = Promise.new
60
+ Delay.new(@native, time) { promise.resolve }.start
61
+ promise
62
+ end
63
+ end
64
+
44
65
  end
45
66
 
67
+ module Kernel
68
+ # (see Browser::Window#after)
69
+ def after(time, &block)
70
+ $window.after(time, &block)
71
+ end
72
+
73
+ # (see Browser::Window#after!)
74
+ def after!(time, &block)
75
+ $window.after!(time, &block)
76
+ end
77
+
78
+ # (see Browser::Window#resolve_after)
79
+ def resolve_after(time)
80
+ $window.resolve_after(time)
81
+ end
46
82
  end
47
83
 
48
84
  class Proc
@@ -50,11 +86,9 @@ class Proc
50
86
  def after(time)
51
87
  $window.after(time, &self)
52
88
  end
53
- end
54
89
 
55
- module Kernel
56
- # (see Browser::Window#after)
57
- def after(time, &block)
58
- $window.after(time, &block)
90
+ # (see Browser::Window#after!)
91
+ def after!(time)
92
+ $window.after!(time, &self)
59
93
  end
60
94
  end
@@ -2,23 +2,24 @@ module Browser; module DOM
2
2
 
3
3
  # Encapsulates an {Element} attribute.
4
4
  class Attribute
5
- include Native
6
-
7
- # Returns true if the attribute is an id.
8
- def id?
9
- `#@native.isId`
10
- end
5
+ include Browser::NativeCachedWrapper
11
6
 
12
7
  # @!attribute [r] name
13
8
  # @return [String] the name of the attribute
14
- def name
15
- `#@native.name`
16
- end
9
+ alias_native :name
17
10
 
18
- # @!attribute [r] value
11
+ # @!attribute value
19
12
  # @return [String] the value of the attribute
20
- def value
21
- `#@native.value`
13
+ alias_native :value
14
+ alias_native :value=
15
+
16
+ # Returns true if the attribute is an id.
17
+ if Browser.supports? 'Attr.isId'
18
+ alias_native :id?, :isId
19
+ else
20
+ def id?
21
+ name == :id
22
+ end
22
23
  end
23
24
  end
24
25
 
@@ -29,23 +29,40 @@ class Builder
29
29
  def self.build(builder, item)
30
30
  to_h.each {|klass, block|
31
31
  if klass === item
32
- break block.call(builder, item)
32
+ return block.call(builder, item)
33
33
  end
34
34
  }
35
+
36
+ raise ArgumentError, "cannot build unknown item #{item}"
35
37
  end
36
38
 
37
39
  attr_reader :document, :element
38
40
 
39
- def initialize(document, element = nil, &block)
41
+ NEW_PAGGIO = (Paggio::HTML.instance_method(:build!) rescue false)
42
+
43
+ def initialize(document, builder=nil, &block)
40
44
  @document = document
41
- @element = element
42
- @builder = Paggio::HTML.new(&block)
43
- @roots = @builder.each.map { |e| Builder.build(self, e) }
44
-
45
- if @element
46
- @roots.each {|root|
47
- @element << root
48
- }
45
+
46
+ # Compatibility issue due to an unreleased Paggio gem.
47
+ # Let's try to support both versions. When Paggio is released,
48
+ # we may remove it.
49
+
50
+ if NEW_PAGGIO
51
+ @builder = Paggio::HTML.new(defer: true, &block)
52
+
53
+ build = proc do
54
+ @builder.build!(force_call: !!builder)
55
+ @roots = @builder.each.map { |e| Builder.build(self, e) }
56
+ end
57
+
58
+ if builder
59
+ builder.extend!(@builder, &build)
60
+ else
61
+ build.()
62
+ end
63
+ else
64
+ @builder = Paggio::HTML.new(&block)
65
+ @roots = @builder.each.map { |e| Builder.build(self, e) }
49
66
  end
50
67
  end
51
68
 
@@ -59,15 +76,12 @@ Builder.for String do |b, item|
59
76
  end
60
77
 
61
78
  Builder.for Paggio::HTML::Element do |b, item|
62
- dom = b.document.create_element(`item.name`)
79
+ options = {}
63
80
 
64
- if Hash === `item.attributes`
65
- dom.attributes.merge!(`item.attributes`)
66
- end
81
+ options[:attrs] = `item.attributes` if Hash === `item.attributes`
82
+ options[:classes] = `item.class_names`
67
83
 
68
- `item.class_names`.each {|value|
69
- dom.add_class value
70
- }
84
+ dom = b.document.create_element(`item.name`, **options)
71
85
 
72
86
  if on = `item.on || nil`
73
87
  on.each {|args, block|
@@ -1,34 +1,13 @@
1
- require 'browser/location'
2
-
3
1
  module Browser; module DOM
4
2
 
5
3
  class Document < Element
6
- def create_element(name, options = {})
7
- if ns = options[:namespace]
8
- DOM(`#@native.createElementNS(#{ns}, #{name})`)
9
- else
10
- DOM(`#@native.createElement(name)`)
11
- end
12
- end
13
-
14
- if Browser.supports? 'Document.view'
15
- def window
16
- Window.new(`#@native.defaultView`)
17
- end
18
- elsif Browser.supports? 'Document.window'
19
- def window
20
- Window.new(`#@native.parentWindow`)
21
- end
22
- else
23
- def window
24
- raise NotImplementedError, 'window from document unsupported'
25
- end
26
- end
27
-
28
- def create_text(content)
29
- DOM(`#@native.createTextNode(#{content})`)
30
- end
4
+ include DocumentOrShadowRoot
31
5
 
6
+ # Get the first element matching the given ID, CSS selector or XPath.
7
+ #
8
+ # @param what [String] ID, CSS selector or XPath
9
+ #
10
+ # @return [Element?] the first matching element
32
11
  def [](what)
33
12
  %x{
34
13
  var result = #@native.getElementById(what);
@@ -43,42 +22,195 @@ class Document < Element
43
22
 
44
23
  alias at []
45
24
 
25
+ # @!attribute [r] body
26
+ # @return [Element?] the body element of the document
27
+ def body
28
+ DOM(`#@native.body`)
29
+ rescue ArgumentError
30
+ raise '$document.body is not defined; try to wrap your code in $document.ready{}'
31
+ end
32
+
33
+ # Create a new element for the document.
34
+ #
35
+ # @param name [String] the node 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
42
+ #
43
+ # @return [Element]
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
+
51
+ if ns = options[:namespace]
52
+ elem = `#@native.createElementNS(#{ns}, #{name}, #{opts.to_n})`
53
+ else
54
+ elem = `#@native.createElement(name, #{opts.to_n})`
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()`)
90
+ end
91
+
92
+ # Create a new text node for the document.
93
+ #
94
+ # @param content [String] the text content
95
+ #
96
+ # @return [Text]
97
+ def create_text(content)
98
+ DOM(`#@native.createTextNode(#{content})`)
99
+ end
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
+
46
110
  def document
47
111
  self
48
112
  end
49
113
 
114
+ # @!attribute [r] head
115
+ # @return [Element?] the head element of the document
116
+ def head
117
+ DOM(`#@native.getElementsByTagName("head")[0]`)
118
+ end
119
+
50
120
  def inspect
51
- "#<DOM::Document: #{children.inspect}>"
121
+ "#<DOM::Document>"
52
122
  end
53
123
 
54
- def title
55
- `#@native.title`
124
+ if Browser.supports? 'Event.addListener'
125
+ def ready(&block)
126
+ raise ArgumentError, 'no block given' unless block
127
+
128
+ return block.call if ready?
129
+
130
+ on 'dom:load' do |e|
131
+ e.off
132
+
133
+ block.call
134
+ end
135
+ end
136
+ elsif Browser.supports? 'Event.attach'
137
+ def ready(&block)
138
+ raise ArgumentError, 'no block given' unless block
139
+
140
+ return block.call if ready?
141
+
142
+ on 'ready:state:change' do |e|
143
+ if ready?
144
+ e.off
145
+
146
+ block.call
147
+ end
148
+ end
149
+ end
150
+ else
151
+ # Wait for the document to be ready and call the block.
152
+ def ready(&block)
153
+ raise NotImplementedError, 'document ready unsupported'
154
+ end
56
155
  end
57
156
 
58
- def title=(value)
59
- `#@native.title = value`
157
+ # Check if the document is ready.
158
+ def ready?
159
+ `#@native.readyState === "complete" || #@native.readyState === "interactive"`
60
160
  end
61
161
 
162
+ # @!attribute referrer
163
+ # @return [String] the referring document, or empty string if direct access
164
+ def referrer
165
+ `#@native.referrer`
166
+ end
167
+
168
+ # @!attribute root
169
+ # @return [Element?] the root element of the document
62
170
  def root
63
171
  DOM(`#@native.documentElement`)
64
172
  end
65
173
 
66
- def head
67
- DOM(`#@native.getElementsByTagName("head")[0]`)
174
+ def root=(element)
175
+ `#@native.documentElement = #{Native.convert(element)}`
68
176
  end
69
177
 
70
- def body
71
- DOM(`#@native.body`)
178
+ # @!attribute title
179
+ # @return [String] the document title
180
+ def title
181
+ `#@native.title`
72
182
  end
73
183
 
74
- def style_sheets
75
- Native::Array.new(`#@native.styleSheets`) {|e|
76
- CSS::StyleSheet.new(e)
77
- }
184
+ def title=(value)
185
+ `#@native.title = value`
78
186
  end
79
187
 
80
- def root=(element)
81
- `#@native.documentElement = #{Native.convert(element)}`
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
+
200
+ if Browser.supports? 'Document.view'
201
+ def window
202
+ Window.new(`#@native.defaultView`)
203
+ end
204
+ elsif Browser.supports? 'Document.window'
205
+ def window
206
+ Window.new(`#@native.parentWindow`)
207
+ end
208
+ else
209
+ # @!attribute [r] window
210
+ # @return [Window] the window for the document
211
+ def window
212
+ raise NotImplementedError, 'window from document unsupported'
213
+ end
82
214
  end
83
215
  end
84
216
 
@@ -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