Class: Dommy::ShadowRoot

Inherits:
Object
  • Object
show all
Includes:
EventTarget, Node
Defined in:
lib/dommy/shadow_root.rb

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`) is out of scope.

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::PROCESSING_INSTRUCTION_NODE, Node::TEXT_NODE

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from EventTarget

#__deliver_event__, #add_event_listener, #dispatch_event, #invoke_listener, #remove_event_listener

Constructor Details

#initialize(host, mode:, delegates_focus: false, slot_assignment: "named") ⇒ ShadowRoot

Returns a new instance of ShadowRoot.



19
20
21
22
23
24
25
26
27
# File 'lib/dommy/shadow_root.rb', line 19

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.nokogiri_doc.fragment("")
  @document.__register_shadow_fragment__(@__node__, self)
end

Instance Attribute Details

#__node__Object (readonly)

Returns the value of attribute __node__.



17
18
19
# File 'lib/dommy/shadow_root.rb', line 17

def __node__
  @__node__
end

#delegates_focusObject (readonly)

Returns the value of attribute delegates_focus.



17
18
19
# File 'lib/dommy/shadow_root.rb', line 17

def delegates_focus
  @delegates_focus
end

#documentObject (readonly)

Returns the value of attribute document.



17
18
19
# File 'lib/dommy/shadow_root.rb', line 17

def document
  @document
end

#hostObject (readonly)

Returns the value of attribute host.



17
18
19
# File 'lib/dommy/shadow_root.rb', line 17

def host
  @host
end

#modeObject (readonly)

Returns the value of attribute mode.



17
18
19
# File 'lib/dommy/shadow_root.rb', line 17

def mode
  @mode
end

#slot_assignmentObject (readonly)

Returns the value of attribute slot_assignment.



17
18
19
# File 'lib/dommy/shadow_root.rb', line 17

def slot_assignment
  @slot_assignment
end

Instance Method Details

#[](key) ⇒ Object

‘[]` accessor mirrors the bracket convention used elsewhere.



152
153
154
# File 'lib/dommy/shadow_root.rb', line 152

def [](key)
  __js_get__(key.to_s)
end

#[]=(k, v) ⇒ Object



156
157
158
# File 'lib/dommy/shadow_root.rb', line 156

def []=(k, v)
  __js_set__(k.to_s, v)
end

#__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).



236
237
238
# File 'lib/dommy/shadow_root.rb', line 236

def __event_parent__
  nil
end

#__js_call__(method, args) ⇒ Object



204
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
# File 'lib/dommy/shadow_root.rb', line 204

def __js_call__(method, args)
  case method
  when "querySelector"
    query_selector(args[0])
  when "querySelectorAll"
    query_selector_all(args[0])
  when "getElementById"
    get_element_by_id(args[0])
  when "append"
    append(*args)
  when "prepend"
    prepend(*args)
  when "replaceChildren"
    replace_children(*args)
  when "appendChild"
    append_child(args[0])
  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])
  when "dispatchEvent"
    dispatch_event(args[0])
  end
end

#__js_get__(key) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/dommy/shadow_root.rb', line 160

def __js_get__(key)
  case key
  when "host"
    @host
  when "mode"
    @mode
  when "delegatesFocus"
    @delegates_focus
  when "slotAssignment"
    @slot_assignment
  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
  end
end

#__js_set__(key, value) ⇒ Object



193
194
195
196
197
198
199
200
201
202
# File 'lib/dommy/shadow_root.rb', line 193

def __js_set__(key, value)
  case key
  when "innerHTML"
    self.inner_html = value
  when "textContent"
    self.text_content = value
  end

  nil
end

#append(*args) ⇒ Object



89
90
91
92
93
94
# File 'lib/dommy/shadow_root.rb', line 89

def append(*args)
  nodes = args.flat_map { |a| detach_dom_nodes(a) }
  nodes.each { |n| @__node__.add_child(n) }
  @document.notify_child_list_mutation(target_node: @__node__, added_nodes: nodes, removed_nodes: [])
  nil
end

#append_child(child) ⇒ Object



82
83
84
85
86
87
# File 'lib/dommy/shadow_root.rb', line 82

def append_child(child)
  nodes = detach_dom_nodes(child)
  nodes.each { |n| @__node__.add_child(n) }
  @document.notify_child_list_mutation(target_node: @__node__, added_nodes: nodes, removed_nodes: [])
  child
end

#child_element_countObject



62
63
64
# File 'lib/dommy/shadow_root.rb', line 62

def child_element_count
  @__node__.element_children.size
end

#child_nodesObject



58
59
60
# File 'lib/dommy/shadow_root.rb', line 58

def child_nodes
  @__node__.children.map { |n| @document.wrap_node(n) }.compact
end

#childrenObject



54
55
56
# File 'lib/dommy/shadow_root.rb', line 54

def children
  @__node__.element_children.map { |n| @document.wrap_node(n) }.compact
end

#contains?(other) ⇒ Boolean

Returns:

  • (Boolean)


142
143
144
145
146
147
148
149
# File 'lib/dommy/shadow_root.rb', line 142

def contains?(other)
  return false unless other.respond_to?(:__node__)

  other_node = other.__node__
  return true if other_node == @__node__

  Internal::NodeTraversal.ancestor_of?(@__node__, other_node)
end

#first_childObject



66
67
68
# File 'lib/dommy/shadow_root.rb', line 66

def first_child
  @document.wrap_node(@__node__.children.first)
end

#first_element_childObject



74
75
76
# File 'lib/dommy/shadow_root.rb', line 74

def first_element_child
  @document.wrap_node(@__node__.element_children.first)
end

#get_element_by_id(id) ⇒ Object



130
131
132
133
134
# File 'lib/dommy/shadow_root.rb', line 130

def get_element_by_id(id)
  return nil if id.nil?

  @document.wrap_node(@__node__.at_css("##{id}"))
end

#get_root_node(_options = nil) ⇒ Object

‘getRootNode()` returns the ShadowRoot itself (closed-shadow semantics; `composed: true` callers go through the Event path).



138
139
140
# File 'lib/dommy/shadow_root.rb', line 138

def get_root_node(_options = nil)
  self
end

#inner_htmlObject

—- Public Ruby API (ParentNode + DocumentFragment mixin) —-



31
32
33
# File 'lib/dommy/shadow_root.rb', line 31

def inner_html
  @__node__.children.map(&:to_html).join
end

#inner_html=(html) ⇒ Object



35
36
37
38
39
40
41
42
43
# File 'lib/dommy/shadow_root.rb', line 35

def inner_html=(html)
  removed = @__node__.children.to_a
  removed.each(&:unlink)
  fragment = Parser.fragment(html.to_s, owner_doc: @document.nokogiri_doc)
  added = fragment.children.to_a
  added.each { |n| @__node__.add_child(n) }
  @document.notify_child_list_mutation(target_node: @__node__, added_nodes: added, removed_nodes: removed)
  nil
end

#last_childObject



70
71
72
# File 'lib/dommy/shadow_root.rb', line 70

def last_child
  @document.wrap_node(@__node__.children.last)
end

#last_element_childObject



78
79
80
# File 'lib/dommy/shadow_root.rb', line 78

def last_element_child
  @document.wrap_node(@__node__.element_children.last)
end

#prepend(*args) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/dommy/shadow_root.rb', line 96

def prepend(*args)
  nodes = args.flat_map { |a| detach_dom_nodes(a) }
  anchor = @__node__.children.first
  if anchor
    nodes.reverse_each { |n| anchor.add_previous_sibling(n) }
  else
    nodes.each { |n| @__node__.add_child(n) }
  end

  @document.notify_child_list_mutation(target_node: @__node__, added_nodes: nodes, removed_nodes: [])
  nil
end

#query_selector(selector) ⇒ Object



118
119
120
121
122
# File 'lib/dommy/shadow_root.rb', line 118

def query_selector(selector)
  return nil if selector.nil? || selector.to_s.empty?

  @document.wrap_node(@__node__.at_css(selector.to_s))
end

#query_selector_all(selector) ⇒ Object



124
125
126
127
128
# File 'lib/dommy/shadow_root.rb', line 124

def query_selector_all(selector)
  return NodeList.new if selector.nil? || selector.to_s.empty?

  NodeList.new(@__node__.css(selector.to_s).map { |n| @document.wrap_node(n) }.compact)
end

#replace_children(*args) ⇒ Object



109
110
111
112
113
114
115
116
# File 'lib/dommy/shadow_root.rb', line 109

def replace_children(*args)
  removed = @__node__.children.to_a
  removed.each(&:unlink)
  nodes = args.flat_map { |a| detach_dom_nodes(a) }
  nodes.each { |n| @__node__.add_child(n) }
  @document.notify_child_list_mutation(target_node: @__node__, added_nodes: nodes, removed_nodes: removed)
  nil
end

#text_contentObject



45
46
47
# File 'lib/dommy/shadow_root.rb', line 45

def text_content
  @__node__.text
end

#text_content=(value) ⇒ Object



49
50
51
52
# File 'lib/dommy/shadow_root.rb', line 49

def text_content=(value)
  @__node__.children.each(&:unlink)
  @__node__.add_child(Nokogiri::XML::Text.new(value.to_s, @document.nokogiri_doc))
end