Class: Dommy::ShadowRoot

Inherits:
Object
  • Object
show all
Includes:
Bridge::Methods, EventTarget, Internal::ParentNode, 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::HTML_NAMESPACE, Node::PROCESSING_INSTRUCTION_NODE, Node::TEXT_NODE

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Bridge::Methods

included

Methods included from Internal::ParentNode

#append, #append_child, #prepend, #replace_children

Methods included from Node

#compare_document_position, #is_default_namespace, #is_equal_node, #is_same_node, #lookup_namespace_uri, #lookup_prefix

Methods included from EventTarget

#__internal_deliver_event__, #add_event_listener, capture_flag, #deliver_at, #dispatch_event, js_truthy?, #remove_event_listener

Constructor Details

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

Returns a new instance of ShadowRoot.



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

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.



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

def delegates_focus
  @delegates_focus
end

#documentObject (readonly)

Returns the value of attribute document.



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

def document
  @document
end

#hostObject (readonly)

Returns the value of attribute host.



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

def host
  @host
end

#modeObject (readonly)

Returns the value of attribute mode.



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

def mode
  @mode
end

#slot_assignmentObject (readonly)

Returns the value of attribute slot_assignment.



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

def slot_assignment
  @slot_assignment
end

Instance Method Details

#[](key) ⇒ Object

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



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

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

#[]=(k, v) ⇒ Object



123
124
125
# File 'lib/dommy/shadow_root.rb', line 123

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

#__dommy_backend_node__Object



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

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



221
222
223
# File 'lib/dommy/shadow_root.rb', line 221

def __internal_event_parent__
  nil
end

#__js_call__(method, args) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/dommy/shadow_root.rb', line 179

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 "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



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/dommy/shadow_root.rb', line 127

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



160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/dommy/shadow_root.rb', line 160

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

#child_element_countObject



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

def child_element_count
  @__node__.element_children.size
end

#child_nodesObject



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

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

#childrenObject



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

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

#contains?(other) ⇒ Boolean

Returns:

  • (Boolean)


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

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



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

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

#first_element_childObject



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

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

#get_element_by_id(id) ⇒ Object



97
98
99
100
101
# File 'lib/dommy/shadow_root.rb', line 97

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



105
106
107
# File 'lib/dommy/shadow_root.rb', line 105

def get_root_node(_options = nil)
  self
end

#inner_htmlObject

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



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

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

#inner_html=(html) ⇒ Object



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

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) }
  notify_child_list(added: added, removed: removed)
  nil
end

#last_childObject



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

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

#last_element_childObject



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

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

#query_selector(selector) ⇒ Object



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

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



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

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

#text_contentObject



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

def text_content
  @__node__.text
end

#text_content=(value) ⇒ Object



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

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