opal-browser 0.1.0.beta1 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/build.yml +95 -0
- data/.gitignore +3 -0
- data/.yardopts +1 -1
- data/Gemfile +22 -3
- data/LICENSE +20 -0
- data/README.md +200 -20
- data/Rakefile +29 -1
- data/config.ru +20 -2
- data/docs/polyfills.md +24 -0
- data/examples/2048/Gemfile +6 -0
- data/examples/2048/README.md +13 -0
- data/examples/2048/app/application.rb +169 -0
- data/examples/2048/config.ru +9 -0
- data/examples/canvas/Gemfile +6 -0
- data/examples/canvas/README.md +9 -0
- data/examples/canvas/app/application.rb +55 -0
- data/examples/canvas/config.ru +9 -0
- data/examples/component/Gemfile +6 -0
- data/examples/component/README.md +10 -0
- data/examples/component/app/application.rb +66 -0
- data/examples/component/config.ru +9 -0
- data/examples/integrations/README.md +24 -0
- data/examples/integrations/dynamic-rack-opal-sprockets-server/Gemfile +6 -0
- data/examples/integrations/dynamic-rack-opal-sprockets-server/README.md +16 -0
- data/examples/integrations/dynamic-rack-opal-sprockets-server/app/application.rb +6 -0
- data/examples/integrations/dynamic-rack-opal-sprockets-server/config.ru +9 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/.gitignore +1 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/Gemfile +7 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/README.md +22 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/Rakefile +4 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/app/application.rb +6 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/app.rb +32 -0
- data/examples/integrations/dynamic-roda-roda-sprockets/config.ru +3 -0
- data/examples/integrations/dynamic-roda-tilt/.gitignore +1 -0
- data/examples/integrations/dynamic-roda-tilt/Gemfile +8 -0
- data/examples/integrations/dynamic-roda-tilt/README.md +17 -0
- data/examples/integrations/dynamic-roda-tilt/Rakefile +6 -0
- data/examples/integrations/dynamic-roda-tilt/app/application.rb +6 -0
- data/examples/integrations/dynamic-roda-tilt/app.rb +50 -0
- data/examples/integrations/dynamic-roda-tilt/config.ru +3 -0
- data/examples/integrations/dynamic-sinatra-opal-sprockets-server/Gemfile +7 -0
- data/examples/integrations/dynamic-sinatra-opal-sprockets-server/README.md +16 -0
- data/examples/integrations/dynamic-sinatra-opal-sprockets-server/app/application.rb +6 -0
- data/examples/integrations/dynamic-sinatra-opal-sprockets-server/config.ru +29 -0
- data/examples/integrations/static-bash/.gitignore +2 -0
- data/examples/integrations/static-bash/Gemfile +3 -0
- data/examples/integrations/static-bash/README.md +8 -0
- data/examples/integrations/static-bash/app/application.rb +6 -0
- data/examples/integrations/static-bash/build.sh +4 -0
- data/examples/integrations/static-bash/index.html +10 -0
- data/examples/integrations/static-bash-opal-parser/.gitignore +3 -0
- data/examples/integrations/static-bash-opal-parser/Gemfile +3 -0
- data/examples/integrations/static-bash-opal-parser/README.md +10 -0
- data/examples/integrations/static-bash-opal-parser/build.sh +4 -0
- data/examples/integrations/static-bash-opal-parser/index.html +19 -0
- data/examples/integrations/static-rake/.gitignore +1 -0
- data/examples/integrations/static-rake/Gemfile +4 -0
- data/examples/integrations/static-rake/README.md +7 -0
- data/examples/integrations/static-rake/Rakefile +10 -0
- data/examples/integrations/static-rake/app/application.rb +6 -0
- data/examples/integrations/static-rake/index.html +9 -0
- data/examples/integrations/static-rake-guard/.gitignore +1 -0
- data/examples/integrations/static-rake-guard/Gemfile +6 -0
- data/examples/integrations/static-rake-guard/Guardfile +3 -0
- data/examples/integrations/static-rake-guard/README.md +10 -0
- data/examples/integrations/static-rake-guard/Rakefile +10 -0
- data/examples/integrations/static-rake-guard/app/application.rb +6 -0
- data/examples/integrations/static-rake-guard/index.html +9 -0
- data/examples/svg/.gitignore +1 -0
- data/examples/svg/Gemfile +4 -0
- data/examples/svg/README.md +7 -0
- data/examples/svg/Rakefile +10 -0
- data/examples/svg/app/application.rb +11 -0
- data/examples/svg/index.html +17 -0
- data/examples/svg/index.svg +6 -0
- data/index.html.erb +24 -0
- data/lib/opal-browser.rb +1 -0
- data/opal/browser/animation_frame.rb +92 -10
- data/opal/browser/audio/node.rb +121 -0
- data/opal/browser/audio/param_schedule.rb +43 -0
- data/opal/browser/audio.rb +66 -0
- data/opal/browser/blob.rb +94 -0
- data/opal/browser/canvas/data.rb +2 -12
- data/opal/browser/canvas/gradient.rb +1 -11
- data/opal/browser/canvas/style.rb +3 -11
- data/opal/browser/canvas/text.rb +1 -11
- data/opal/browser/canvas.rb +86 -28
- data/opal/browser/console.rb +6 -38
- data/opal/browser/cookies.rb +90 -27
- data/opal/browser/crypto.rb +79 -0
- data/opal/browser/css/declaration.rb +1 -6
- data/opal/browser/css/rule.rb +1 -1
- data/opal/browser/css/style_sheet.rb +2 -2
- data/opal/browser/css.rb +23 -7
- data/opal/browser/database/sql.rb +193 -0
- data/opal/browser/delay.rb +94 -0
- data/opal/browser/dom/attribute.rb +16 -9
- data/opal/browser/dom/builder.rb +35 -25
- data/opal/browser/dom/character_data.rb +43 -7
- data/opal/browser/dom/document.rb +171 -37
- data/opal/browser/dom/document_fragment.rb +18 -0
- data/opal/browser/dom/document_or_shadow_root.rb +19 -0
- data/opal/browser/dom/element/attributes.rb +111 -0
- data/opal/browser/dom/element/button.rb +31 -0
- data/opal/browser/dom/element/custom.rb +177 -0
- data/opal/browser/dom/element/data.rb +82 -0
- data/opal/browser/dom/element/editable.rb +47 -0
- data/opal/browser/dom/element/form.rb +38 -0
- data/opal/browser/dom/element/iframe.rb +37 -0
- data/opal/browser/dom/element/image.rb +25 -0
- data/opal/browser/dom/element/input.rb +48 -1
- data/opal/browser/dom/element/media.rb +17 -0
- data/opal/browser/dom/element/offset.rb +32 -10
- data/opal/browser/dom/element/position.rb +11 -2
- data/opal/browser/dom/element/scroll.rb +139 -20
- data/opal/browser/dom/element/select.rb +42 -0
- data/opal/browser/dom/element/size.rb +46 -0
- data/opal/browser/dom/element/template.rb +11 -0
- data/opal/browser/dom/element/textarea.rb +26 -0
- data/opal/browser/dom/element.rb +496 -168
- data/opal/browser/dom/mutation_observer.rb +69 -9
- data/opal/browser/dom/node.rb +270 -83
- data/opal/browser/dom/node_set.rb +74 -41
- data/opal/browser/dom/shadow_root.rb +12 -0
- data/opal/browser/dom/text.rb +18 -3
- data/opal/browser/dom.rb +40 -18
- data/opal/browser/effects.rb +180 -3
- data/opal/browser/event/all.rb +26 -0
- data/opal/browser/event/animation.rb +40 -0
- data/opal/browser/{dom/event → event}/audio_processing.rb +10 -6
- data/opal/browser/event/base.rb +461 -0
- data/opal/browser/event/before_unload.rb +17 -0
- data/opal/browser/event/clipboard.rb +37 -0
- data/opal/browser/event/close.rb +49 -0
- data/opal/browser/event/composition.rb +52 -0
- data/opal/browser/event/custom.rb +65 -0
- data/opal/browser/event/data_transfer.rb +95 -0
- data/opal/browser/event/device_light.rb +25 -0
- data/opal/browser/{dom/event → event}/device_motion.rb +21 -6
- data/opal/browser/event/device_orientation.rb +50 -0
- data/opal/browser/{dom/event → event}/device_proximity.rb +10 -6
- data/opal/browser/event/drag.rb +123 -0
- data/opal/browser/event/focus.rb +41 -0
- data/opal/browser/event/gamepad.rb +62 -0
- data/opal/browser/{dom/event → event}/hash_change.rb +10 -6
- data/opal/browser/event/keyboard.rb +128 -0
- data/opal/browser/event/message.rb +72 -0
- data/opal/browser/{dom/event → event}/mouse.rb +37 -32
- data/opal/browser/event/page_transition.rb +25 -0
- data/opal/browser/event/pop_state.rb +35 -0
- data/opal/browser/event/progress.rb +45 -0
- data/opal/browser/event/sensor.rb +17 -0
- data/opal/browser/{dom/event → event}/storage.rb +10 -6
- data/opal/browser/{dom/event → event}/touch.rb +14 -21
- data/opal/browser/event/ui.rb +38 -0
- data/opal/browser/{dom/event → event}/wheel.rb +6 -4
- data/opal/browser/event.rb +163 -0
- data/opal/browser/event_source.rb +7 -4
- data/opal/browser/form_data.rb +225 -0
- data/opal/browser/history.rb +53 -21
- data/opal/browser/http/binary.rb +1 -0
- data/opal/browser/http/headers.rb +21 -2
- data/opal/browser/http/request.rb +83 -55
- data/opal/browser/http/response.rb +5 -1
- data/opal/browser/http.rb +47 -9
- data/opal/browser/immediate.rb +128 -10
- data/opal/browser/interval.rb +41 -23
- data/opal/browser/location.rb +20 -4
- data/opal/browser/navigator.rb +136 -13
- data/opal/browser/polyfill/visual_viewport.rb +216 -0
- data/opal/browser/screen.rb +34 -8
- data/opal/browser/setup/base.rb +6 -0
- data/opal/browser/setup/full.rb +13 -0
- data/opal/browser/setup/large.rb +17 -0
- data/opal/browser/setup/mini.rb +8 -0
- data/opal/browser/setup/traditional.rb +10 -0
- data/opal/browser/socket.rb +16 -8
- data/opal/browser/storage.rb +155 -52
- data/opal/browser/support.rb +299 -0
- data/opal/browser/utils.rb +116 -18
- data/opal/browser/version.rb +1 -1
- data/opal/browser/visual_viewport.rb +39 -0
- data/opal/browser/window/size.rb +47 -9
- data/opal/browser/window/view.rb +37 -4
- data/opal/browser/window.rb +46 -26
- data/opal/browser.rb +1 -10
- data/opal/opal-browser.rb +1 -0
- data/opal-browser.gemspec +10 -12
- data/spec/database/sql_spec.rb +139 -0
- data/spec/delay_spec.rb +41 -0
- data/spec/dom/attribute_spec.rb +49 -0
- data/spec/dom/builder_spec.rb +36 -19
- data/spec/dom/document_spec.rb +28 -6
- data/spec/dom/element/attributes_spec.rb +52 -0
- data/spec/dom/element/custom_spec.rb +106 -0
- data/spec/dom/element/subclass_spec.rb +144 -0
- data/spec/dom/element_spec.rb +184 -7
- data/spec/dom/mutation_observer_spec.rb +13 -9
- data/spec/dom/node_set_spec.rb +44 -0
- data/spec/dom/node_spec.rb +87 -27
- data/spec/dom_spec.rb +19 -9
- data/spec/event_source_spec.rb +18 -15
- data/spec/{dom/event_spec.rb → event_spec.rb} +55 -26
- data/spec/history_spec.rb +32 -19
- data/spec/http_spec.rb +25 -36
- data/spec/immediate_spec.rb +10 -7
- data/spec/interval_spec.rb +59 -0
- data/spec/native_cached_wrapper_spec.rb +46 -0
- data/spec/runner.rb +107 -0
- data/spec/socket_spec.rb +18 -14
- data/spec/spec_helper.rb +2 -4
- data/spec/spec_helper_promise.rb.erb +25 -0
- data/spec/storage_spec.rb +7 -7
- data/spec/wgxpath.install.js +49 -0
- data/spec/window_spec.rb +2 -2
- metadata +181 -93
- data/opal/browser/compatibility/animation_frame.rb +0 -93
- data/opal/browser/compatibility/dom/document/window.rb +0 -15
- data/opal/browser/compatibility/dom/element/css.rb +0 -15
- data/opal/browser/compatibility/dom/element/matches.rb +0 -31
- data/opal/browser/compatibility/dom/element/offset.rb +0 -20
- data/opal/browser/compatibility/dom/element/scroll.rb +0 -25
- data/opal/browser/compatibility/dom/element/style.rb +0 -15
- data/opal/browser/compatibility/dom/mutation_observer.rb +0 -47
- data/opal/browser/compatibility/http/request.rb +0 -15
- data/opal/browser/compatibility/immediate.rb +0 -107
- data/opal/browser/compatibility/window/scroll.rb +0 -27
- data/opal/browser/compatibility/window/size.rb +0 -13
- data/opal/browser/compatibility/window/view.rb +0 -13
- data/opal/browser/compatibility.rb +0 -59
- data/opal/browser/dom/compatibility.rb +0 -8
- data/opal/browser/dom/event/animation.rb +0 -26
- data/opal/browser/dom/event/base.rb +0 -207
- data/opal/browser/dom/event/before_unload.rb +0 -13
- data/opal/browser/dom/event/clipboard.rb +0 -26
- data/opal/browser/dom/event/close.rb +0 -35
- data/opal/browser/dom/event/composition.rb +0 -38
- data/opal/browser/dom/event/custom.rb +0 -30
- data/opal/browser/dom/event/device_light.rb +0 -21
- data/opal/browser/dom/event/device_orientation.rb +0 -36
- data/opal/browser/dom/event/drag.rb +0 -113
- data/opal/browser/dom/event/focus.rb +0 -23
- data/opal/browser/dom/event/gamepad.rb +0 -47
- data/opal/browser/dom/event/keyboard.rb +0 -93
- data/opal/browser/dom/event/message.rb +0 -50
- data/opal/browser/dom/event/page_transition.rb +0 -21
- data/opal/browser/dom/event/pop_state.rb +0 -21
- data/opal/browser/dom/event/progress.rb +0 -31
- data/opal/browser/dom/event/sensor.rb +0 -13
- data/opal/browser/dom/event/ui.rb +0 -22
- data/opal/browser/dom/event.rb +0 -240
- data/opal/browser/http/compatibility.rb +0 -1
- data/opal/browser/http/parameters.rb +0 -8
- data/opal/browser/timeout.rb +0 -60
- data/opal/browser/window/compatibility.rb +0 -3
- data/opal/browser/window/scroll.rb +0 -49
@@ -1,24 +1,13 @@
|
|
1
|
-
require 'browser/location'
|
2
|
-
|
3
1
|
module Browser; module DOM
|
4
2
|
|
5
3
|
class Document < Element
|
6
|
-
|
7
|
-
if ns = options[:namespace]
|
8
|
-
DOM(`#@native.createElementNS(#{ns}, #{name})`)
|
9
|
-
else
|
10
|
-
DOM(`#@native.createElement(name)`)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def window
|
15
|
-
Window.new(`#@native.defaultView`)
|
16
|
-
end
|
17
|
-
|
18
|
-
def create_text(content)
|
19
|
-
DOM(`#@native.createTextNode(#{content})`)
|
20
|
-
end
|
4
|
+
include DocumentOrShadowRoot
|
21
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
|
22
11
|
def [](what)
|
23
12
|
%x{
|
24
13
|
var result = #@native.getElementById(what);
|
@@ -28,55 +17,200 @@ class Document < Element
|
|
28
17
|
}
|
29
18
|
}
|
30
19
|
|
31
|
-
|
20
|
+
css(what).first || xpath(what).first
|
32
21
|
end
|
33
22
|
|
34
23
|
alias at []
|
35
24
|
|
36
|
-
|
37
|
-
|
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})`)
|
38
108
|
end
|
39
109
|
|
40
110
|
def document
|
41
111
|
self
|
42
112
|
end
|
43
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
|
+
|
44
120
|
def inspect
|
45
|
-
"#<DOM::Document
|
121
|
+
"#<DOM::Document>"
|
46
122
|
end
|
47
123
|
|
48
|
-
|
49
|
-
|
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
|
50
155
|
end
|
51
156
|
|
52
|
-
|
53
|
-
|
157
|
+
# Check if the document is ready.
|
158
|
+
def ready?
|
159
|
+
`#@native.readyState === "complete" || #@native.readyState === "interactive"`
|
54
160
|
end
|
55
161
|
|
56
|
-
|
57
|
-
|
162
|
+
# @!attribute referrer
|
163
|
+
# @return [String] the referring document, or empty string if direct access
|
164
|
+
def referrer
|
165
|
+
`#@native.referrer`
|
58
166
|
end
|
59
167
|
|
168
|
+
# @!attribute root
|
169
|
+
# @return [Element?] the root element of the document
|
60
170
|
def root
|
61
171
|
DOM(`#@native.documentElement`)
|
62
172
|
end
|
63
173
|
|
64
|
-
def
|
65
|
-
|
174
|
+
def root=(element)
|
175
|
+
`#@native.documentElement = #{Native.convert(element)}`
|
66
176
|
end
|
67
177
|
|
68
|
-
|
69
|
-
|
178
|
+
# @!attribute title
|
179
|
+
# @return [String] the document title
|
180
|
+
def title
|
181
|
+
`#@native.title`
|
70
182
|
end
|
71
183
|
|
72
|
-
def
|
73
|
-
|
74
|
-
CSS::StyleSheet.new(e)
|
75
|
-
}
|
184
|
+
def title=(value)
|
185
|
+
`#@native.title = value`
|
76
186
|
end
|
77
187
|
|
78
|
-
|
79
|
-
|
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
|
80
214
|
end
|
81
215
|
end
|
82
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
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Browser; module DOM; class Element < Node
|
2
|
+
|
3
|
+
class Attributes
|
4
|
+
attr_reader :namespace
|
5
|
+
|
6
|
+
def initialize(element, options)
|
7
|
+
@element = element
|
8
|
+
@native = element.to_n
|
9
|
+
@namespace = options[:namespace]
|
10
|
+
end
|
11
|
+
|
12
|
+
if Browser.supports?('Element.className') || Browser.supports?('Element.htmlFor')
|
13
|
+
def [](name, options = {})
|
14
|
+
if name == :class && Browser.supports?('Element.className')
|
15
|
+
name = :className
|
16
|
+
elsif name == :for && Browser.supports?('Element.htmlFor')
|
17
|
+
name = :htmlFor
|
18
|
+
end
|
19
|
+
|
20
|
+
if namespace = options[:namespace] || @namespace
|
21
|
+
`#@native.getAttributeNS(#{namespace.to_s}, #{name.to_s}) || nil`
|
22
|
+
else
|
23
|
+
`#@native.getAttribute(#{name.to_s}) || nil`
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def []=(name, value, options = {})
|
28
|
+
if name == :class && Browser.supports?('Element.className')
|
29
|
+
name = :className
|
30
|
+
elsif name == :for && Browser.supports?('Element.htmlFor')
|
31
|
+
name = :htmlFor
|
32
|
+
end
|
33
|
+
|
34
|
+
if namespace = options[:namespace] || @namespace
|
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
|
40
|
+
else
|
41
|
+
if value
|
42
|
+
`#@native.setAttribute(#{name.to_s}, #{value.to_s})`
|
43
|
+
else
|
44
|
+
`#@native.removeAttribute(#{name.to_s})`
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
else
|
49
|
+
def [](name, options = {})
|
50
|
+
if namespace = options[:namespace] || @namespace
|
51
|
+
`#@native.getAttributeNS(#{namespace.to_s}, #{name.to_s}) || nil`
|
52
|
+
else
|
53
|
+
`#@native.getAttribute(#{name.to_s}) || nil`
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def []=(name, value, options = {})
|
58
|
+
if namespace = options[:namespace] || @namespace
|
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
|
64
|
+
else
|
65
|
+
if value
|
66
|
+
`#@native.setAttribute(#{name.to_s}, #{value.to_s})`
|
67
|
+
else
|
68
|
+
`#@native.removeAttribute(#{name.to_s})`
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
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
|
+
|
82
|
+
include Enumerable
|
83
|
+
|
84
|
+
def each(&block)
|
85
|
+
return enum_for :each unless block_given?
|
86
|
+
|
87
|
+
@element.attribute_nodes.each {|attr|
|
88
|
+
yield attr.name, attr.value
|
89
|
+
}
|
90
|
+
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
alias get []
|
95
|
+
|
96
|
+
def has_key?(name)
|
97
|
+
!!self[name]
|
98
|
+
end
|
99
|
+
|
100
|
+
def merge!(hash)
|
101
|
+
hash.each {|name, value|
|
102
|
+
self[name] = value
|
103
|
+
}
|
104
|
+
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
alias set []=
|
109
|
+
end
|
110
|
+
|
111
|
+
end; end; end
|
@@ -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
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Browser; module DOM; class Element < Node
|
2
|
+
|
3
|
+
class Data
|
4
|
+
attr_reader :element
|
5
|
+
|
6
|
+
def initialize(element)
|
7
|
+
@element = element
|
8
|
+
@native = element.to_n
|
9
|
+
|
10
|
+
unless defined?(`#@native.$data`)
|
11
|
+
`#@native.$data = {}`
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
def each(&block)
|
18
|
+
return enum_for :each unless block
|
19
|
+
|
20
|
+
%x{
|
21
|
+
var data = #@native.$data;
|
22
|
+
|
23
|
+
for (var key in data) {
|
24
|
+
#{block.call `key`, `data[key]`};
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
@element.attributes.each {|name, value|
|
29
|
+
if name =~ /^data-(.*)$/
|
30
|
+
block.call $1, value
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def assign(data)
|
38
|
+
data.each {|name, value|
|
39
|
+
self[name] = value
|
40
|
+
}
|
41
|
+
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def [](name)
|
46
|
+
if data = @element["data-#{name}"]
|
47
|
+
return data
|
48
|
+
end
|
49
|
+
|
50
|
+
%x{
|
51
|
+
var value = #@native.$data[name];
|
52
|
+
|
53
|
+
if (value === undefined) {
|
54
|
+
return nil;
|
55
|
+
}
|
56
|
+
else {
|
57
|
+
return value;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def []=(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
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end; end; end
|