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

#__internal_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.



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

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.__internal_register_shadow_fragment__(@__node__, self)
end

Instance Attribute Details

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



154
155
156
# File 'lib/dommy/shadow_root.rb', line 154

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

#[]=(k, v) ⇒ Object



158
159
160
# File 'lib/dommy/shadow_root.rb', line 158

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

#__dommy_backend_node__Object



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

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



238
239
240
# File 'lib/dommy/shadow_root.rb', line 238

def __internal_event_parent__
  nil
end

#__js_call__(method, args) ⇒ Object



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

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



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

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



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

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



91
92
93
94
95
96
# File 'lib/dommy/shadow_root.rb', line 91

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



84
85
86
87
88
89
# File 'lib/dommy/shadow_root.rb', line 84

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



64
65
66
# File 'lib/dommy/shadow_root.rb', line 64

def child_element_count
  @__node__.element_children.size
end

#child_nodesObject



60
61
62
# File 'lib/dommy/shadow_root.rb', line 60

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

#childrenObject



56
57
58
# File 'lib/dommy/shadow_root.rb', line 56

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

#contains?(other) ⇒ Boolean

Returns:

  • (Boolean)


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

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_childObject



68
69
70
# File 'lib/dommy/shadow_root.rb', line 68

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

#first_element_childObject



76
77
78
# File 'lib/dommy/shadow_root.rb', line 76

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

#get_element_by_id(id) ⇒ Object



132
133
134
135
136
# File 'lib/dommy/shadow_root.rb', line 132

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



140
141
142
# File 'lib/dommy/shadow_root.rb', line 140

def get_root_node(_options = nil)
  self
end

#inner_htmlObject

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



33
34
35
# File 'lib/dommy/shadow_root.rb', line 33

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

#inner_html=(html) ⇒ Object



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

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



72
73
74
# File 'lib/dommy/shadow_root.rb', line 72

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

#last_element_childObject



80
81
82
# File 'lib/dommy/shadow_root.rb', line 80

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

#prepend(*args) ⇒ Object



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

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



120
121
122
123
124
# File 'lib/dommy/shadow_root.rb', line 120

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



126
127
128
129
130
# File 'lib/dommy/shadow_root.rb', line 126

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



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

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



47
48
49
# File 'lib/dommy/shadow_root.rb', line 47

def text_content
  @__node__.text
end

#text_content=(value) ⇒ Object



51
52
53
54
# File 'lib/dommy/shadow_root.rb', line 51

def text_content=(value)
  @__node__.children.each(&:unlink)
  @__node__.add_child(Backend.create_text(value.to_s, @document.nokogiri_doc))
end