Module: Dommy::EventTarget

Overview

Note: ‘Callback` and `Constructor` live in `Dommy::Bridge::*` —they’re bridge-adapter classes, not part of the public DOM surface.

Defined Under Namespace

Classes: Listener

Instance Method Summary collapse

Instance Method Details

#__deliver_event__(event) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/dommy/event.rb', line 74

def __deliver_event__(event)
  listeners = listeners_for(event.type).dup
  listeners.each do |entry|
    invoke_listener(entry.listener, event)
    if entry.once?
      listeners_for(event.type).reject! { |candidate| candidate.listener.equal?(entry.listener) }
    end

    break if event.immediate_propagation_stopped?
  end

  nil
end

#add_event_listener(type, listener = nil, options = nil, &block) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/dommy/event.rb', line 9

def add_event_listener(type, listener = nil, options = nil, &block)
  cb = listener || block
  return nil if type.nil? || cb.nil?

  list = listeners_for(type.to_s)
  # Per spec, the same listener (by identity) registered on the
  # same type is silently deduplicated.
  return nil if list.any? { |entry| entry.listener.equal?(cb) }

  list << Listener.new(cb, options)

  # `{ signal: AbortSignal }` — when the signal aborts, auto-
  # remove the listener. Per spec, if the signal is already aborted
  # the listener must not be registered at all.
  signal = options.is_a?(Hash) ? (options["signal"] || options[:signal]) : nil
  if signal.respond_to?(:__js_get__)
    if signal.__js_get__("aborted")
      remove_event_listener(type, cb)
    else
      target = self
      signal.__js_call__(
        "addEventListener",
        [
          "abort",
          proc {
            target.remove_event_listener(type, cb)
          }
        ]
      )
    end
  end

  nil
end

#dispatch_event(event) ⇒ Object

Raises:

  • (TypeError)


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/dommy/event.rb', line 51

def dispatch_event(event)
  return true if event.nil?

  # Per spec, dispatchEvent must receive an Event instance.
  raise TypeError, "dispatchEvent requires an Event, got #{event.class}" unless event.is_a?(Event)

  event.__prepare_for_dispatch__(self)
  path = if event.bubbles?
    event.__js_get__("composed") ? composed_bubble_path(event) : event_bubble_path
  else
    [self]
  end

  event.__record_path__(path) if event.respond_to?(:__record_path__)
  path.each do |target|
    event.__set_current_target__(target)
    target.__deliver_event__(event)
    break if event.propagation_stopped?
  end

  !event.default_prevented?
end

#invoke_listener(listener, event) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/dommy/event.rb', line 161

def invoke_listener(listener, event)
  # DOM spec: a listener can be (a) a function, or (b) an object
  # with a `handleEvent` method. Both Ruby and JS-bridged callables
  # are supported.
  if listener.respond_to?(:handle_event)
    listener.handle_event(event)
  elsif listener.respond_to?(:call) && !listener.is_a?(Module)
    listener.call(event)
  elsif listener.respond_to?(:__js_call__)
    # Prefer handleEvent if the bridge object advertises it; fall
    # back to call. We can't introspect on the JS side, so we just
    # try call (the common case for JS.callback {}).
    listener.__js_call__("call", [event])
  end
end

#remove_event_listener(type, listener) ⇒ Object



44
45
46
47
48
49
# File 'lib/dommy/event.rb', line 44

def remove_event_listener(type, listener)
  return nil if type.nil? || listener.nil?

  listeners_for(type.to_s).reject! { |entry| entry.listener.equal?(listener) }
  nil
end