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.
- checksums.yaml +5 -5
- data/.github/workflows/build.yml +78 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +17 -3
- data/LICENSE +2 -1
- data/README.md +131 -54
- data/Rakefile +29 -1
- data/config.ru +20 -3
- 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 +2 -3
- 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 +1 -1
- data/opal/browser/canvas/gradient.rb +1 -1
- data/opal/browser/canvas/style.rb +3 -1
- data/opal/browser/canvas/text.rb +1 -1
- data/opal/browser/canvas.rb +17 -3
- data/opal/browser/console.rb +3 -1
- data/opal/browser/cookies.rb +72 -34
- data/opal/browser/crypto.rb +79 -0
- data/opal/browser/css/declaration.rb +1 -1
- 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 +7 -8
- data/opal/browser/delay.rb +16 -0
- data/opal/browser/dom/attribute.rb +1 -1
- data/opal/browser/dom/builder.rb +29 -10
- data/opal/browser/dom/document.rb +81 -13
- 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 +28 -4
- 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 +17 -2
- 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 +2 -0
- data/opal/browser/dom/element/input.rb +36 -0
- data/opal/browser/dom/element/media.rb +17 -0
- data/opal/browser/dom/element/scroll.rb +106 -74
- data/opal/browser/dom/element/select.rb +6 -0
- data/opal/browser/dom/element/size.rb +12 -0
- data/opal/browser/dom/element/template.rb +2 -0
- data/opal/browser/dom/element/textarea.rb +2 -0
- data/opal/browser/dom/element.rb +194 -50
- data/opal/browser/dom/mutation_observer.rb +2 -2
- data/opal/browser/dom/node.rb +53 -13
- data/opal/browser/dom/node_set.rb +13 -2
- data/opal/browser/dom/shadow_root.rb +12 -0
- data/opal/browser/dom/text.rb +2 -2
- data/opal/browser/dom.rb +38 -5
- data/opal/browser/effects.rb +170 -4
- data/opal/browser/event/all.rb +26 -0
- data/opal/browser/event/animation.rb +2 -0
- data/opal/browser/event/audio_processing.rb +2 -0
- data/opal/browser/event/base.rb +35 -4
- data/opal/browser/event/before_unload.rb +2 -0
- data/opal/browser/event/clipboard.rb +9 -0
- data/opal/browser/event/close.rb +2 -0
- data/opal/browser/event/composition.rb +2 -0
- data/opal/browser/event/custom.rb +1 -1
- data/opal/browser/event/data_transfer.rb +95 -0
- data/opal/browser/event/device_light.rb +2 -0
- data/opal/browser/event/device_motion.rb +2 -0
- data/opal/browser/event/device_orientation.rb +2 -0
- data/opal/browser/event/device_proximity.rb +2 -0
- data/opal/browser/event/drag.rb +9 -5
- data/opal/browser/event/focus.rb +2 -0
- data/opal/browser/event/gamepad.rb +3 -1
- data/opal/browser/event/hash_change.rb +2 -0
- data/opal/browser/event/keyboard.rb +14 -1
- data/opal/browser/event/message.rb +2 -0
- data/opal/browser/event/mouse.rb +10 -6
- data/opal/browser/event/page_transition.rb +2 -0
- data/opal/browser/event/pop_state.rb +2 -0
- data/opal/browser/event/progress.rb +2 -0
- data/opal/browser/event/sensor.rb +2 -0
- data/opal/browser/event/storage.rb +2 -0
- data/opal/browser/event/touch.rb +2 -0
- data/opal/browser/event/wheel.rb +2 -0
- data/opal/browser/event.rb +26 -116
- data/opal/browser/event_source.rb +1 -1
- data/opal/browser/form_data.rb +225 -0
- data/opal/browser/history.rb +4 -8
- data/opal/browser/http/request.rb +32 -10
- data/opal/browser/http/response.rb +5 -1
- data/opal/browser/http.rb +0 -2
- data/opal/browser/immediate.rb +0 -2
- data/opal/browser/location.rb +7 -1
- data/opal/browser/navigator.rb +105 -4
- data/opal/browser/polyfill/visual_viewport.rb +216 -0
- data/opal/browser/screen.rb +2 -2
- 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 +3 -3
- data/opal/browser/storage.rb +2 -2
- data/opal/browser/support.rb +46 -22
- data/opal/browser/utils.rb +94 -14
- data/opal/browser/version.rb +1 -1
- data/opal/browser/visual_viewport.rb +39 -0
- data/opal/browser/window/size.rb +14 -0
- data/opal/browser/window/view.rb +15 -0
- data/opal/browser/window.rb +29 -16
- data/opal/browser.rb +1 -11
- data/opal-browser.gemspec +3 -3
- data/spec/database/sql_spec.rb +43 -35
- data/spec/delay_spec.rb +15 -12
- data/spec/dom/document_spec.rb +10 -8
- data/spec/dom/element/custom_spec.rb +106 -0
- data/spec/dom/element/subclass_spec.rb +144 -0
- data/spec/dom/element_spec.rb +42 -0
- data/spec/dom/mutation_observer_spec.rb +12 -8
- data/spec/dom/node_spec.rb +48 -0
- data/spec/dom_spec.rb +8 -0
- data/spec/event_source_spec.rb +15 -12
- data/spec/{dom/event_spec.rb → event_spec.rb} +44 -15
- data/spec/history_spec.rb +23 -19
- data/spec/http_spec.rb +19 -31
- data/spec/immediate_spec.rb +5 -4
- data/spec/interval_spec.rb +18 -9
- data/spec/native_cached_wrapper_spec.rb +46 -0
- data/spec/runner.rb +37 -62
- data/spec/socket_spec.rb +15 -12
- data/spec/spec_helper.rb +2 -1
- data/spec/spec_helper_promise.rb.erb +25 -0
- metadata +120 -16
- data/.travis.yml +0 -74
- 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
|
|
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
|
-
|
|
52
|
+
elem = `#@native.createElementNS(#{ns}, #{name}, #{opts.to_n})`
|
|
38
53
|
else
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 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
|