Class: Dommy::ShadowRoot
Overview
ShadowRoot — a DocumentFragment-shaped subtree attached to a
host Element via attachShadow. Lives in its own Nokogiri
fragment that's invisible to the outer document's tree walks
(querySelector, getElementById, children, etc.), which is the
core "encapsulation" the spec promises.
Tree manipulation works the same as a normal Element/Fragment;
the boundary is enforced only on outer queries and event
composition. CSS scoping (:host / ::slotted / ::part, and this
tree's <style> rules) is handled by the cascade — see
internal/css/rule_index.rb and css-cascade.md.
Constant Summary
Constants included
from Node
Node::ATTRIBUTE_NODE, Node::CDATA_SECTION_NODE, Node::COMMENT_NODE, Node::DOCUMENT_FRAGMENT_NODE, Node::DOCUMENT_NODE, Node::DOCUMENT_POSITION_CONTAINED_BY, Node::DOCUMENT_POSITION_CONTAINS, Node::DOCUMENT_POSITION_DISCONNECTED, Node::DOCUMENT_POSITION_FOLLOWING, Node::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, Node::DOCUMENT_POSITION_PRECEDING, Node::DOCUMENT_TYPE_NODE, Node::ELEMENT_NODE, Node::HTML_NAMESPACE, Node::PROCESSING_INSTRUCTION_NODE, Node::TEXT_NODE, Node::XMLNS_NAMESPACE, Node::XML_NAMESPACE
Instance Attribute Summary collapse
Instance Method Summary
collapse
-
#[](key) ⇒ Object
[] accessor mirrors the bracket convention used elsewhere.
-
#[]=(k, v) ⇒ Object
-
#__dommy_backend_node__ ⇒ Object
-
#__internal_event_parent__ ⇒ Object
Event bubbling stops at the ShadowRoot unless event has composed: true.
-
#__js_call__(method, args) ⇒ Object
-
#__js_get__(key) ⇒ Object
-
#__js_set__(key, value) ⇒ Object
-
#active_element ⇒ Object
shadowRoot.activeElement — the focused element, retargeted to this shadow tree: the document's focused element when it is inside this (connected) shadow root, else null.
-
#child_element_count ⇒ Object
-
#child_nodes ⇒ Object
-
#children ⇒ Object
-
#connected? ⇒ Boolean
Whether this shadow root participates in the document (its host is connected).
-
#contains?(other) ⇒ Boolean
-
#first_child ⇒ Object
-
#first_element_child ⇒ Object
-
#get_element_by_id(id) ⇒ Object
-
#get_root_node(options = nil) ⇒ Object
getRootNode() returns the ShadowRoot itself; getRootNode({composed: true}) crosses the shadow boundary and returns the root of the host's tree (the document, or an outer shadow root for nested shadows).
-
#initialize(host, mode:, delegates_focus: false, slot_assignment: "named") ⇒ ShadowRoot
constructor
A new instance of ShadowRoot.
-
#inner_html ⇒ Object
---- Public Ruby API (ParentNode + DocumentFragment mixin) ----.
-
#inner_html=(html) ⇒ Object
-
#insert_before(node, ref) ⇒ Object
Node-level child mutations the spec exposes on a DocumentFragment (and so on a ShadowRoot).
-
#last_child ⇒ Object
-
#last_element_child ⇒ Object
-
#query_selector(selector) ⇒ Object
-
#query_selector_all(selector) ⇒ Object
-
#remove_child(node) ⇒ Object
-
#replace_child(new_child, old_child) ⇒ Object
-
#style_sheets ⇒ Object
shadowRoot.styleSheets — the CSSStyleSheets of the <style> / <link> elements within this (connected) shadow tree (mirrors Document#style_sheets); empty for a disconnected shadow root.
-
#text_content ⇒ Object
-
#text_content=(value) ⇒ Object
included
#append, #append_child, #normalize, #prepend, #replace_children
#child_node_after, #child_node_before, #child_node_replace_with, #nullable_dom_string, #validate_insert_before_ref!
Methods included from Node
#compare_document_position, #is_default_namespace, #is_equal_node, #is_same_node, #lookup_namespace_uri, #lookup_prefix
#__dommy_dump_event_failure__, #__internal_deliver_event__, #__internal_process_event_handler_return__, #add_event_listener, capture_flag, #deliver_at, #dispatch_event, #event_name_from_on, #invoke_listener_isolated, js_truthy?, #on_handler, #remove_event_listener, #set_on_handler
Constructor Details
#initialize(host, mode:, delegates_focus: false, slot_assignment: "named") ⇒ ShadowRoot
Returns a new instance of ShadowRoot.
24
25
26
27
28
29
30
31
32
|
# File 'lib/dommy/shadow_root.rb', line 24
def initialize(host, mode:, delegates_focus: false, slot_assignment: "named")
@host = host
@mode = mode.to_s
@delegates_focus = !!delegates_focus
@slot_assignment = slot_assignment.to_s
@document = host.document
@__node__ = @document.backend_doc.fragment("")
@document.__internal_register_shadow_fragment__(@__node__, self)
end
|
Instance Attribute Details
#delegates_focus ⇒ Object
Returns the value of attribute delegates_focus.
20
21
22
|
# File 'lib/dommy/shadow_root.rb', line 20
def delegates_focus
@delegates_focus
end
|
#document ⇒ Object
Returns the value of attribute document.
20
21
22
|
# File 'lib/dommy/shadow_root.rb', line 20
def document
@document
end
|
#host ⇒ Object
Returns the value of attribute host.
20
21
22
|
# File 'lib/dommy/shadow_root.rb', line 20
def host
@host
end
|
#mode ⇒ Object
Returns the value of attribute mode.
20
21
22
|
# File 'lib/dommy/shadow_root.rb', line 20
def mode
@mode
end
|
#slot_assignment ⇒ Object
Returns the value of attribute slot_assignment.
20
21
22
|
# File 'lib/dommy/shadow_root.rb', line 20
def slot_assignment
@slot_assignment
end
|
Instance Method Details
#[](key) ⇒ Object
[] accessor mirrors the bracket convention used elsewhere.
197
198
199
|
# File 'lib/dommy/shadow_root.rb', line 197
def [](key)
__js_get__(key.to_s)
end
|
#[]=(k, v) ⇒ Object
201
202
203
|
# File 'lib/dommy/shadow_root.rb', line 201
def []=(k, v)
__js_set__(k.to_s, v)
end
|
#__dommy_backend_node__ ⇒ Object
22
|
# File 'lib/dommy/shadow_root.rb', line 22
def __dommy_backend_node__ = @__node__
|
#__internal_event_parent__ ⇒ Object
Event bubbling stops at the ShadowRoot unless event has
composed: true. The host is the bubble-path successor when
composition crosses the boundary (handled in Event dispatch).
317
318
319
|
# File 'lib/dommy/shadow_root.rb', line 317
def __internal_event_parent__
nil
end
|
#__js_call__(method, args) ⇒ Object
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
|
# File 'lib/dommy/shadow_root.rb', line 269
def __js_call__(method, args)
case method
when "querySelector"
query_selector(Internal.css_query_arg!(args))
when "querySelectorAll"
query_selector_all(Internal.css_query_arg!(args))
when "getElementById"
get_element_by_id(args[0])
when "isEqualNode"
is_equal_node(args[0])
when "isSameNode"
is_same_node(args[0])
when "compareDocumentPosition"
compare_document_position(args[0])
when "hasChildNodes"
@__node__.children.any?
when "normalize"
nil
when "append"
append(*args)
when "prepend"
prepend(*args)
when "replaceChildren"
replace_children(*args)
when "appendChild"
append_child(args[0])
when "insertBefore"
insert_before(args[0], args[1])
when "removeChild"
remove_child(args[0])
when "replaceChild"
replace_child(args[0], args[1])
when "getRootNode"
get_root_node(args[0])
when "contains"
contains?(args[0])
when "addEventListener"
add_event_listener(args[0], args[1], args[2])
when "removeEventListener"
remove_event_listener(args[0], args[1], args[2])
when "dispatchEvent"
dispatch_event(args[0])
end
end
|
#__js_get__(key) ⇒ Object
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
|
# File 'lib/dommy/shadow_root.rb', line 205
def __js_get__(key)
case key
when "host"
@host
when "mode"
@mode
when "delegatesFocus"
@delegates_focus
when "slotAssignment"
@slot_assignment
when "activeElement"
active_element
when "styleSheets"
style_sheets
when "innerHTML"
inner_html
when "textContent"
text_content
when "children"
children
when "childNodes"
child_nodes
when "childElementCount"
child_element_count
when "firstChild"
first_child
when "lastChild"
last_child
when "firstElementChild"
first_element_child
when "lastElementChild"
last_element_child
when "nodeType"
11
when "nodeName"
"#document-fragment"
else
Bridge::ABSENT
end
end
|
#__js_set__(key, value) ⇒ Object
249
250
251
252
253
254
255
256
257
258
259
260
|
# File 'lib/dommy/shadow_root.rb', line 249
def __js_set__(key, value)
case key
when "innerHTML"
self.inner_html = value
when "textContent"
self.text_content = value
else
return Bridge::UNHANDLED
end
nil
end
|
#active_element ⇒ Object
shadowRoot.activeElement — the focused element, retargeted to this shadow
tree: the document's focused element when it is inside this (connected)
shadow root, else null.
97
98
99
100
101
102
|
# File 'lib/dommy/shadow_root.rb', line 97
def active_element
return nil unless connected?
focused = @document.__internal_focused_element__
focused && contains?(focused) ? focused : nil
end
|
#child_element_count ⇒ Object
67
68
69
|
# File 'lib/dommy/shadow_root.rb', line 67
def child_element_count
@__node__.element_children.size
end
|
#child_nodes ⇒ Object
63
64
65
|
# File 'lib/dommy/shadow_root.rb', line 63
def child_nodes
@__node__.children.map { |n| @document.wrap_node(n) }.compact
end
|
#children ⇒ Object
59
60
61
|
# File 'lib/dommy/shadow_root.rb', line 59
def children
@__node__.element_children.map { |n| @document.wrap_node(n) }.compact
end
|
#connected? ⇒ Boolean
Whether this shadow root participates in the document (its host is
connected). A disconnected shadow tree has no active element and no live
style sheets.
90
91
92
|
# File 'lib/dommy/shadow_root.rb', line 90
def connected?
@host.respond_to?(:is_connected?) && @host.is_connected?
end
|
#contains?(other) ⇒ Boolean
187
188
189
190
191
192
193
194
|
# File 'lib/dommy/shadow_root.rb', line 187
def contains?(other)
return false unless other.respond_to?(:__dommy_backend_node__)
other_node = other.__dommy_backend_node__
return true if other_node == @__node__
Internal::NodeTraversal.ancestor_of?(@__node__, other_node)
end
|
#first_child ⇒ Object
71
72
73
|
# File 'lib/dommy/shadow_root.rb', line 71
def first_child
@document.wrap_node(@__node__.children.first)
end
|
#first_element_child ⇒ Object
79
80
81
|
# File 'lib/dommy/shadow_root.rb', line 79
def first_element_child
@document.wrap_node(@__node__.element_children.first)
end
|
#get_element_by_id(id) ⇒ Object
130
131
132
133
134
135
136
137
|
# File 'lib/dommy/shadow_root.rb', line 130
def get_element_by_id(id)
return nil if id.nil? || id.to_s.empty?
@document.wrap_node(@__node__.at_css("##{Dommy::CSSNamespace.escape(id)}"))
end
|
#get_root_node(options = nil) ⇒ Object
getRootNode() returns the ShadowRoot itself; getRootNode({composed: true}) crosses the shadow boundary and returns the root of the host's
tree (the document, or an outer shadow root for nested shadows).
178
179
180
181
182
183
184
185
|
# File 'lib/dommy/shadow_root.rb', line 178
def get_root_node(options = nil)
composed = options.is_a?(Hash) &&
EventTarget.js_truthy?(options.key?("composed") ? options["composed"] : options[:composed])
return self unless composed
return @host.root_node({"composed" => true}) if @host.respond_to?(:root_node)
self
end
|
#inner_html ⇒ Object
---- Public Ruby API (ParentNode + DocumentFragment mixin) ----
36
37
38
|
# File 'lib/dommy/shadow_root.rb', line 36
def inner_html
@__node__.children.map(&:to_html).join
end
|
#inner_html=(html) ⇒ Object
40
41
42
43
44
45
46
47
48
|
# File 'lib/dommy/shadow_root.rb', line 40
def inner_html=(html)
removed = @__node__.children.to_a
removed.each(&:unlink)
fragment = Parser.fragment(html.to_s, owner_doc: @document.backend_doc)
added = fragment.children.to_a
added.each { |n| @__node__.add_child(n) }
notify_child_list(added: added, removed: removed)
nil
end
|
#insert_before(node, ref) ⇒ Object
Node-level child mutations the spec exposes on a DocumentFragment (and so
on a ShadowRoot). lit-html drives rendering through insertBefore /
removeChild / replaceChild against the render root, so a shadow root
must support them — not just appendChild.
143
144
145
146
147
148
149
150
151
152
153
|
# File 'lib/dommy/shadow_root.rb', line 143
def insert_before(node, ref)
nodes = detach_dom_nodes(node)
ref_bn = ref.respond_to?(:__dommy_backend_node__) ? ref.__dommy_backend_node__ : nil
if ref_bn && ref_bn.parent == @__node__
nodes.each { |n| ref_bn.add_previous_sibling(n) }
else
nodes.each { |n| @__node__.add_child(n) }
end
notify_child_list(added: nodes)
node
end
|
#last_child ⇒ Object
75
76
77
|
# File 'lib/dommy/shadow_root.rb', line 75
def last_child
@document.wrap_node(@__node__.children.last)
end
|
#last_element_child ⇒ Object
83
84
85
|
# File 'lib/dommy/shadow_root.rb', line 83
def last_element_child
@document.wrap_node(@__node__.element_children.last)
end
|
#query_selector(selector) ⇒ Object
#query_selector_all(selector) ⇒ Object
#remove_child(node) ⇒ Object
155
156
157
158
159
160
161
162
|
# File 'lib/dommy/shadow_root.rb', line 155
def remove_child(node)
bn = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__ : nil
raise DOMException::NotFoundError, "node is not a child of this shadow root" unless bn && bn.parent == @__node__
bn.unlink
notify_child_list(removed: [bn])
node
end
|
#replace_child(new_child, old_child) ⇒ Object
164
165
166
167
168
169
170
171
172
173
|
# File 'lib/dommy/shadow_root.rb', line 164
def replace_child(new_child, old_child)
old_bn = old_child.respond_to?(:__dommy_backend_node__) ? old_child.__dommy_backend_node__ : nil
raise DOMException::NotFoundError, "node is not a child of this shadow root" unless old_bn && old_bn.parent == @__node__
added = detach_dom_nodes(new_child)
added.each { |n| old_bn.add_previous_sibling(n) }
old_bn.unlink
notify_child_list(added: added, removed: [old_bn])
old_child
end
|
#style_sheets ⇒ Object
shadowRoot.styleSheets — the CSSStyleSheets of the <style> / <link>
elements within this (connected) shadow tree (mirrors Document#style_sheets);
empty for a disconnected shadow root.
107
108
109
110
111
112
113
114
|
# File 'lib/dommy/shadow_root.rb', line 107
def style_sheets
return NodeList.new unless connected?
sheets = query_selector_all("style, link").filter_map do |element|
element.sheet if element.respond_to?(:sheet)
end
NodeList.new(sheets)
end
|
#text_content ⇒ Object
50
51
52
|
# File 'lib/dommy/shadow_root.rb', line 50
def text_content
@__node__.text
end
|
#text_content=(value) ⇒ Object
54
55
56
57
|
# File 'lib/dommy/shadow_root.rb', line 54
def text_content=(value)
@__node__.children.each(&:unlink)
@__node__.add_child(Backend.create_text(value.to_s, @document.backend_doc))
end
|