Comparar commits
10 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| fdfb88c8e5 | |||
| 0a73550213 | |||
| d88b8e854b | |||
| 3ea2746b4d | |||
| 752ba0a698 | |||
| a75eefcf55 | |||
| 6e89c07891 | |||
| 5a041721cd | |||
| 3feb5568f2 | |||
| 5e320b39d9 |
@@ -15,14 +15,18 @@ describe "CommandRegistry", ->
|
||||
document.querySelector('#jasmine-content').appendChild(parent)
|
||||
|
||||
registry = new CommandRegistry
|
||||
atom.commands.restoreDOMEventMethods()
|
||||
registry.patchDOMEventMethods()
|
||||
|
||||
afterEach ->
|
||||
registry.restoreDOMEventMethods()
|
||||
atom.commands.patchDOMEventMethods()
|
||||
registry.destroy()
|
||||
|
||||
describe "when a command event is dispatched on an element", ->
|
||||
it "invokes callbacks with selectors matching the target", ->
|
||||
called = false
|
||||
registry.add '.grandchild', 'command', (event) ->
|
||||
registry.listen '.grandchild', 'command', (event) ->
|
||||
expect(this).toBe grandchild
|
||||
expect(event.type).toBe 'command'
|
||||
expect(event.eventPhase).toBe Event.BUBBLING_PHASE
|
||||
@@ -36,13 +40,13 @@ describe "CommandRegistry", ->
|
||||
it "invokes callbacks with selectors matching ancestors of the target", ->
|
||||
calls = []
|
||||
|
||||
registry.add '.child', 'command', (event) ->
|
||||
registry.listen '.child', 'command', (event) ->
|
||||
expect(this).toBe child
|
||||
expect(event.target).toBe grandchild
|
||||
expect(event.currentTarget).toBe child
|
||||
calls.push('child')
|
||||
|
||||
registry.add '.parent', 'command', (event) ->
|
||||
registry.listen '.parent', 'command', (event) ->
|
||||
expect(this).toBe parent
|
||||
expect(event.target).toBe grandchild
|
||||
expect(event.currentTarget).toBe parent
|
||||
@@ -53,10 +57,10 @@ describe "CommandRegistry", ->
|
||||
|
||||
it "invokes inline listeners prior to listeners applied via selectors", ->
|
||||
calls = []
|
||||
registry.add '.grandchild', 'command', -> calls.push('grandchild')
|
||||
registry.add child, 'command', -> calls.push('child-inline')
|
||||
registry.add '.child', 'command', -> calls.push('child')
|
||||
registry.add '.parent', 'command', -> calls.push('parent')
|
||||
registry.listen '.grandchild', 'command', -> calls.push('grandchild')
|
||||
registry.listen child, 'command', -> calls.push('child-inline')
|
||||
registry.listen '.child', 'command', -> calls.push('child')
|
||||
registry.listen '.parent', 'command', -> calls.push('parent')
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
|
||||
expect(calls).toEqual ['grandchild', 'child-inline', 'child', 'parent']
|
||||
@@ -65,41 +69,77 @@ describe "CommandRegistry", ->
|
||||
child.classList.add('foo', 'bar')
|
||||
calls = []
|
||||
|
||||
registry.add '.foo.bar', 'command', -> calls.push('.foo.bar')
|
||||
registry.add '.foo', 'command', -> calls.push('.foo')
|
||||
registry.add '.bar', 'command', -> calls.push('.bar') # specificity ties favor commands added later, like CSS
|
||||
registry.listen '.foo.bar', 'command', -> calls.push('.foo.bar')
|
||||
registry.listen '.foo', 'command', -> calls.push('.foo')
|
||||
registry.listen '.bar', 'command', -> calls.push('.bar') # specificity ties favor commands added later, like CSS
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
|
||||
expect(calls).toEqual ['.foo.bar', '.bar', '.foo']
|
||||
|
||||
it "does not bubble the event if the ::bubbles property is false on the dispatched event", ->
|
||||
calls = []
|
||||
|
||||
registry.listen '.grandchild', 'command', -> calls.push('grandchild')
|
||||
registry.listen '.child', 'command', -> calls.push('child')
|
||||
registry.listen '.parent', 'command', -> calls.push('parent')
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command', bubbles: false))
|
||||
expect(calls).toEqual ['grandchild']
|
||||
|
||||
it "invokes capture-phase listeners before bubble-phase listeners", ->
|
||||
calls = []
|
||||
|
||||
# Spot-check event details for both capture and bubbling phase
|
||||
registry.capture '.grandchild', 'command', (event) ->
|
||||
expect(this).toBe grandchild
|
||||
expect(event.type).toBe 'command'
|
||||
expect(event.eventPhase).toBe Event.CAPTURING_PHASE
|
||||
expect(event.target).toBe grandchild
|
||||
expect(event.currentTarget).toBe grandchild
|
||||
calls.push('grandchild-capture')
|
||||
|
||||
registry.listen '.grandchild', 'command', (event) ->
|
||||
expect(this).toBe grandchild
|
||||
expect(event.type).toBe 'command'
|
||||
expect(event.eventPhase).toBe Event.BUBBLING_PHASE
|
||||
expect(event.target).toBe grandchild
|
||||
expect(event.currentTarget).toBe grandchild
|
||||
calls.push('grandchild-bubble')
|
||||
|
||||
registry.capture child, 'command', -> calls.push('child-inline-capture')
|
||||
registry.listen child, 'command', -> calls.push('child-inline-bubble')
|
||||
registry.capture '.child', 'command', -> calls.push('child-capture')
|
||||
registry.capture '.parent', 'command', -> calls.push('parent-capture')
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
|
||||
expect(calls).toEqual ['parent-capture', 'child-inline-capture', 'child-capture', 'grandchild-capture', 'grandchild-bubble', 'child-inline-bubble']
|
||||
|
||||
it "stops bubbling through ancestors when .stopPropagation() is called on the event", ->
|
||||
calls = []
|
||||
|
||||
registry.add '.parent', 'command', -> calls.push('parent')
|
||||
registry.add '.child', 'command', -> calls.push('child-2')
|
||||
registry.add '.child', 'command', (event) -> calls.push('child-1'); event.stopPropagation()
|
||||
registry.listen '.parent', 'command', -> calls.push('parent')
|
||||
registry.listen '.child', 'command', -> calls.push('child-2')
|
||||
registry.listen '.child', 'command', (event) -> calls.push('child-1'); event.stopPropagation()
|
||||
|
||||
dispatchedEvent = new CustomEvent('command', bubbles: true)
|
||||
spyOn(dispatchedEvent, 'stopPropagation')
|
||||
grandchild.dispatchEvent(dispatchedEvent)
|
||||
expect(calls).toEqual ['child-1', 'child-2']
|
||||
expect(dispatchedEvent.stopPropagation).toHaveBeenCalled()
|
||||
|
||||
it "stops invoking callbacks when .stopImmediatePropagation() is called on the event", ->
|
||||
calls = []
|
||||
|
||||
registry.add '.parent', 'command', -> calls.push('parent')
|
||||
registry.add '.child', 'command', -> calls.push('child-2')
|
||||
registry.add '.child', 'command', (event) -> calls.push('child-1'); event.stopImmediatePropagation()
|
||||
registry.listen '.parent', 'command', -> calls.push('parent')
|
||||
registry.listen '.child', 'command', -> calls.push('child-2')
|
||||
registry.listen '.child', 'command', (event) -> calls.push('child-1'); event.stopImmediatePropagation()
|
||||
|
||||
dispatchedEvent = new CustomEvent('command', bubbles: true)
|
||||
spyOn(dispatchedEvent, 'stopImmediatePropagation')
|
||||
grandchild.dispatchEvent(dispatchedEvent)
|
||||
expect(calls).toEqual ['child-1']
|
||||
expect(dispatchedEvent.stopImmediatePropagation).toHaveBeenCalled()
|
||||
|
||||
it "forwards .preventDefault() calls from the synthetic event to the original", ->
|
||||
registry.add '.child', 'command', (event) -> event.preventDefault()
|
||||
registry.listen '.child', 'command', (event) -> event.preventDefault()
|
||||
|
||||
dispatchedEvent = new CustomEvent('command', bubbles: true)
|
||||
spyOn(dispatchedEvent, 'preventDefault')
|
||||
@@ -107,7 +147,7 @@ describe "CommandRegistry", ->
|
||||
expect(dispatchedEvent.preventDefault).toHaveBeenCalled()
|
||||
|
||||
it "forwards .abortKeyBinding() calls from the synthetic event to the original", ->
|
||||
registry.add '.child', 'command', (event) -> event.abortKeyBinding()
|
||||
registry.listen '.child', 'command', (event) -> event.abortKeyBinding()
|
||||
|
||||
dispatchedEvent = new CustomEvent('command', bubbles: true)
|
||||
dispatchedEvent.abortKeyBinding = jasmine.createSpy('abortKeyBinding')
|
||||
@@ -117,8 +157,8 @@ describe "CommandRegistry", ->
|
||||
it "allows listeners to be removed via a disposable returned by ::add", ->
|
||||
calls = []
|
||||
|
||||
disposable1 = registry.add '.parent', 'command', -> calls.push('parent')
|
||||
disposable2 = registry.add '.child', 'command', -> calls.push('child')
|
||||
disposable1 = registry.listen '.parent', 'command', -> calls.push('parent')
|
||||
disposable2 = registry.listen '.child', 'command', -> calls.push('child')
|
||||
|
||||
disposable1.dispose()
|
||||
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
|
||||
@@ -132,7 +172,7 @@ describe "CommandRegistry", ->
|
||||
it "allows multiple commands to be registered under one selector when called with an object", ->
|
||||
calls = []
|
||||
|
||||
disposable = registry.add '.child',
|
||||
disposable = registry.listen '.child',
|
||||
'command-1': -> calls.push('command-1')
|
||||
'command-2': -> calls.push('command-2')
|
||||
|
||||
@@ -149,10 +189,10 @@ describe "CommandRegistry", ->
|
||||
|
||||
describe "::findCommands({target})", ->
|
||||
it "returns commands that can be invoked on the target or its ancestors", ->
|
||||
registry.add '.parent', 'namespace:command-1', ->
|
||||
registry.add '.child', 'namespace:command-2', ->
|
||||
registry.add '.grandchild', 'namespace:command-3', ->
|
||||
registry.add '.grandchild.no-match', 'namespace:command-4', ->
|
||||
registry.listen '.parent', 'namespace:command-1', ->
|
||||
registry.listen '.child', 'namespace:command-2', ->
|
||||
registry.listen '.grandchild', 'namespace:command-3', ->
|
||||
registry.listen '.grandchild.no-match', 'namespace:command-4', ->
|
||||
|
||||
expect(registry.findCommands(target: grandchild)[0..2]).toEqual [
|
||||
{name: 'namespace:command-3', displayName: 'Namespace: Command 3'}
|
||||
@@ -163,7 +203,7 @@ describe "CommandRegistry", ->
|
||||
describe "::dispatch(target, commandName)", ->
|
||||
it "simulates invocation of the given command ", ->
|
||||
called = false
|
||||
registry.add '.grandchild', 'command', (event) ->
|
||||
registry.listen '.grandchild', 'command', (event) ->
|
||||
expect(this).toBe grandchild
|
||||
expect(event.type).toBe 'command'
|
||||
expect(event.eventPhase).toBe Event.BUBBLING_PHASE
|
||||
@@ -175,18 +215,42 @@ describe "CommandRegistry", ->
|
||||
expect(called).toBe true
|
||||
|
||||
it "returns a boolean indicating whether any listeners matched the command", ->
|
||||
registry.add '.grandchild', 'command', ->
|
||||
registry.listen '.grandchild', 'command', ->
|
||||
|
||||
expect(registry.dispatch(grandchild, 'command')).toBe true
|
||||
expect(registry.dispatch(grandchild, 'bogus')).toBe false
|
||||
expect(registry.dispatch(parent, 'command')).toBe false
|
||||
|
||||
it "does not perform bubbling for native event names that should not bubble", ->
|
||||
calls = []
|
||||
|
||||
registry.listen '.grandchild', 'focus', -> calls.push('grandchild')
|
||||
registry.listen '.child', 'focus', -> calls.push('child')
|
||||
registry.listen '.parent', 'focus', -> calls.push('parent')
|
||||
|
||||
registry.dispatch(grandchild, 'focus')
|
||||
expect(calls).toEqual ['grandchild']
|
||||
|
||||
it "allows an event object to be passed instead of an event name", ->
|
||||
called = false
|
||||
registry.listen '.grandchild', 'command', (event) ->
|
||||
expect(this).toBe grandchild
|
||||
expect(event.type).toBe 'command'
|
||||
expect(event.eventPhase).toBe Event.BUBBLING_PHASE
|
||||
expect(event.target).toBe grandchild
|
||||
expect(event.currentTarget).toBe grandchild
|
||||
expect(event.detail).toEqual {a: 1}
|
||||
called = true
|
||||
|
||||
registry.dispatch(grandchild, new CustomEvent('command', bubbles: true), {a: 1})
|
||||
expect(called).toBe true
|
||||
|
||||
describe "::getSnapshot and ::restoreSnapshot", ->
|
||||
it "removes all command handlers except for those in the snapshot", ->
|
||||
registry.add '.parent', 'namespace:command-1', ->
|
||||
registry.add '.child', 'namespace:command-2', ->
|
||||
registry.listen '.parent', 'namespace:command-1', ->
|
||||
registry.listen '.child', 'namespace:command-2', ->
|
||||
snapshot = registry.getSnapshot()
|
||||
registry.add '.grandchild', 'namespace:command-3', ->
|
||||
registry.listen '.grandchild', 'namespace:command-3', ->
|
||||
|
||||
expect(registry.findCommands(target: grandchild)[0..2]).toEqual [
|
||||
{name: 'namespace:command-3', displayName: 'Namespace: Command 3'}
|
||||
@@ -201,10 +265,39 @@ describe "CommandRegistry", ->
|
||||
{name: 'namespace:command-1', displayName: 'Namespace: Command 1'}
|
||||
]
|
||||
|
||||
registry.add '.grandchild', 'namespace:command-3', ->
|
||||
registry.listen '.grandchild', 'namespace:command-3', ->
|
||||
registry.restoreSnapshot(snapshot)
|
||||
|
||||
expect(registry.findCommands(target: grandchild)[0..1]).toEqual [
|
||||
{name: 'namespace:command-2', displayName: 'Namespace: Command 2'}
|
||||
{name: 'namespace:command-1', displayName: 'Namespace: Command 1'}
|
||||
]
|
||||
|
||||
describe "::addEventListener and ::removeEventListener overrides", ->
|
||||
it "mixes listeners registered via ::addEventListener with selector-based listeners", ->
|
||||
calls = []
|
||||
registry.listen '.grandchild', 'command', -> calls.push('grandchild')
|
||||
registry.listen '.child', 'command', -> calls.push('child')
|
||||
registry.listen '.parent', 'command', -> calls.push('parent')
|
||||
|
||||
bubbleListener = -> calls.push('child-inline-bubble')
|
||||
captureListener = -> calls.push('child-inline-capture')
|
||||
child.addEventListener('command', bubbleListener)
|
||||
child.addEventListener('command', captureListener, true)
|
||||
|
||||
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
|
||||
expect(calls).toEqual ['child-inline-capture', 'grandchild', 'child-inline-bubble', 'child', 'parent']
|
||||
|
||||
child.removeEventListener('command', bubbleListener)
|
||||
child.removeEventListener('command', captureListener, true)
|
||||
|
||||
calls = []
|
||||
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
|
||||
expect(calls).toEqual ['grandchild', 'child', 'parent']
|
||||
|
||||
it "invokes handlers on detached DOM nodes", ->
|
||||
detachedNode = document.createElement('div')
|
||||
called = false
|
||||
detachedNode.addEventListener 'command', -> called = true
|
||||
detachedNode.dispatchEvent(new CustomEvent('command', bubbles: true))
|
||||
expect(called).toBe true
|
||||
|
||||
@@ -6,7 +6,7 @@ module.exports =
|
||||
activate: ->
|
||||
@activateCallCount++
|
||||
|
||||
atom.commands.add 'atom-workspace', 'activation-command', =>
|
||||
atom.commands.listen 'atom-workspace', 'activation-command', =>
|
||||
@activationCommandCallCount++
|
||||
|
||||
atom.workspaceView.getActiveView()?.command 'activation-command', =>
|
||||
|
||||
@@ -118,7 +118,7 @@ describe "PackageManager", ->
|
||||
spyOn(Package.prototype, 'requireMainModule').andCallThrough()
|
||||
|
||||
workspaceCommandListener = jasmine.createSpy('workspaceCommandListener')
|
||||
atom.commands.add '.workspace', 'activation-command', workspaceCommandListener
|
||||
atom.commands.listen '.workspace', 'activation-command', workspaceCommandListener
|
||||
|
||||
promise = atom.packages.activatePackage('package-with-activation-commands')
|
||||
|
||||
@@ -138,7 +138,7 @@ describe "PackageManager", ->
|
||||
legacyCommandListener = jasmine.createSpy("legacyCommandListener")
|
||||
editorView.command 'activation-command', legacyCommandListener
|
||||
editorCommandListener = jasmine.createSpy("editorCommandListener")
|
||||
atom.commands.add 'atom-text-editor', 'activation-command', editorCommandListener
|
||||
atom.commands.listen 'atom-text-editor', 'activation-command', editorCommandListener
|
||||
editorView[0].dispatchEvent(new CustomEvent('activation-command', bubbles: true))
|
||||
expect(mainModule.activate.callCount).toBe 1
|
||||
expect(mainModule.legacyActivationCommandCallCount).toBe 1
|
||||
|
||||
@@ -351,11 +351,7 @@ $.fn.resultOfTrigger = (type) ->
|
||||
event.result
|
||||
|
||||
$.fn.enableKeymap = ->
|
||||
@on 'keydown', (e) ->
|
||||
originalEvent = e.originalEvent ? e
|
||||
Object.defineProperty(originalEvent, 'target', get: -> e.target) unless originalEvent.target?
|
||||
atom.keymaps.handleKeyboardEvent(originalEvent)
|
||||
not e.originalEvent.defaultPrevented
|
||||
@element.addEventListener 'keydown', (event) -> atom.keymaps.handleKeyboardEvent(event)
|
||||
|
||||
$.fn.attachToDom = ->
|
||||
@appendTo($('#jasmine-content')) unless @isOnDom()
|
||||
|
||||
@@ -14,14 +14,9 @@ describe "Window", ->
|
||||
loadSettings.initialPath = initialPath
|
||||
loadSettings
|
||||
atom.project.destroy()
|
||||
windowEventHandler = new WindowEventHandler()
|
||||
atom.deserializeEditorWindow()
|
||||
projectPath = atom.project.getPaths()[0]
|
||||
|
||||
afterEach ->
|
||||
windowEventHandler.unsubscribe()
|
||||
$(window).off 'beforeunload'
|
||||
|
||||
describe "when the window is loaded", ->
|
||||
it "doesn't have .is-blurred on the body tag", ->
|
||||
expect($("body")).not.toHaveClass("is-blurred")
|
||||
@@ -107,6 +102,8 @@ describe "Window", ->
|
||||
|
||||
describe ".removeEditorWindow()", ->
|
||||
it "unsubscribes from all buffers", ->
|
||||
spyOn(atom.windowEventHandler, 'unsubscribe') # don't unsubscribe from window events
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open("sample.js")
|
||||
|
||||
|
||||
@@ -209,6 +209,7 @@ class Atom extends Model
|
||||
@keymaps = new KeymapManager({configDirPath, resourcePath})
|
||||
@keymap = @keymaps # Deprecated
|
||||
@commands = new CommandRegistry
|
||||
@commands.patchDOMEventMethods()
|
||||
@packages = new PackageManager({devMode, configDirPath, resourcePath, safeMode})
|
||||
@styles = new StyleManager
|
||||
document.head.appendChild(new StylesElement)
|
||||
|
||||
+178
-42
@@ -1,15 +1,50 @@
|
||||
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
|
||||
{specificity} = require 'clear-cut'
|
||||
_ = require 'underscore-plus'
|
||||
Grim = require 'grim'
|
||||
{$} = require './space-pen-extensions'
|
||||
|
||||
SequenceCount = 0
|
||||
SpecificityCache = {}
|
||||
|
||||
# Public: Associates listener functions with commands in a
|
||||
# context-sensitive way using CSS selectors. You can access a global instance of
|
||||
# this class via `atom.commands`, and commands registered there will be
|
||||
# presented in the command palette.
|
||||
NativeEventBubbling =
|
||||
abort: false
|
||||
beforeinput: true
|
||||
blur: false
|
||||
click: true
|
||||
compositionstart: true
|
||||
compositionupdate: true
|
||||
compositionend: true
|
||||
dblclick: true
|
||||
error: false
|
||||
focus: false
|
||||
focusin: true
|
||||
focusout: true
|
||||
input: true
|
||||
keydown: true
|
||||
keyup: true
|
||||
load: false
|
||||
mousedown: true
|
||||
mouseenter: false
|
||||
mouseleave: false
|
||||
mousemove: true
|
||||
mouseout: true
|
||||
mouseover: true
|
||||
mouseup: true
|
||||
resize: false
|
||||
scroll: false
|
||||
select: true
|
||||
textInput: true
|
||||
unload: false
|
||||
wheel: true
|
||||
mousewheel: true
|
||||
|
||||
module.exports =
|
||||
|
||||
# Public: Associates listener functions with commands in a context-sensitive way
|
||||
# using CSS selectors. You can access a global instance of this class via
|
||||
# `atom.commands`, and commands registered there will be presented in the
|
||||
# command palette.
|
||||
#
|
||||
# The global command registry facilitates a style of event handling known as
|
||||
# *event delegation* that was popularized by jQuery. Atom commands are expressed
|
||||
@@ -33,7 +68,7 @@ SpecificityCache = {}
|
||||
# Here is a command that inserts the current date in an editor:
|
||||
#
|
||||
# ```coffee
|
||||
# atom.commands.add 'atom-text-editor',
|
||||
# atom.commands.listen 'atom-text-editor',
|
||||
# 'user:insert-date': (event) ->
|
||||
# editor = $(this).view().getModel()
|
||||
# # soon the above above line will be:
|
||||
@@ -42,8 +77,13 @@ SpecificityCache = {}
|
||||
# ```
|
||||
module.exports =
|
||||
class CommandRegistry
|
||||
patchedDOMEventMethods: false
|
||||
originalAddEventListener: Node::addEventListener
|
||||
originalRemoveEventListener: Node::removeEventListener
|
||||
|
||||
constructor: (@rootNode) ->
|
||||
@registeredCommands = {}
|
||||
@registeredCommandsByInlineNode = new WeakMap
|
||||
@selectorBasedListenersByCommandName = {}
|
||||
@inlineListenersByCommandName = {}
|
||||
@emitter = new Emitter
|
||||
@@ -52,6 +92,52 @@ class CommandRegistry
|
||||
for commandName of @registeredCommands
|
||||
window.removeEventListener(commandName, @handleCommandEvent, true)
|
||||
|
||||
patchDOMEventMethods: ->
|
||||
if @patchedDOMEventMethods
|
||||
throw new Error("Already patched DOM event methods for this registry.")
|
||||
else
|
||||
@patchedDOMEventMethods = true
|
||||
|
||||
registry = this
|
||||
disposablesByEventName = {}
|
||||
|
||||
@originalAddEventListener = Node::addEventListener
|
||||
@originalRemoveEventListener = Node::originalRemoveEventListener
|
||||
|
||||
Node::addEventListener = (eventName, callback, useCapture=false) ->
|
||||
return unless callback?
|
||||
|
||||
target = this
|
||||
disposable = registry.listen(target, eventName, callback, useCapture)
|
||||
|
||||
disposablesByEventName[eventName] ?= {}
|
||||
disposablesByUseCapture = disposablesByEventName[eventName]
|
||||
disposablesByUseCapture[useCapture] ?= new WeakMap
|
||||
disposablesByTarget = disposablesByUseCapture[useCapture]
|
||||
disposablesByTarget.set(target, new WeakMap) unless disposablesByTarget.has(target)
|
||||
disposablesByCallback = disposablesByTarget.get(target)
|
||||
disposablesByCallback.set(callback, new CompositeDisposable) unless disposablesByCallback.has(callback)
|
||||
disposablesByCallback.get(callback).add(disposable)
|
||||
return
|
||||
|
||||
Node::removeEventListener = (eventName, callback, useCapture=false) ->
|
||||
return unless callback?
|
||||
|
||||
target = this
|
||||
disposablesByEventName[eventName]?[useCapture]?.get(target)?.get(callback)?.dispose()
|
||||
return
|
||||
|
||||
restoreDOMEventMethods: ->
|
||||
Node::addEventListener = @originalAddEventListener
|
||||
Node::removeEventListener = @originalRemoveEventListener
|
||||
@patchedDOMEventMethods = false
|
||||
|
||||
addEventListener: (target, eventName, callback, useCapture) ->
|
||||
@originalAddEventListener.call(target, eventName, callback, useCapture)
|
||||
|
||||
removeEventListener: (target, eventName, callback, useCapture) ->
|
||||
@originalRemoveEventListener.call(target, eventName, callback, useCapture)
|
||||
|
||||
# Public: Add one or more command listeners associated with a selector.
|
||||
#
|
||||
# ## Arguments: Registering One Command
|
||||
@@ -77,23 +163,30 @@ class CommandRegistry
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to remove the
|
||||
# added command handler(s).
|
||||
add: (target, commandName, callback) ->
|
||||
listen: (target, commandName, callback, useCapture=false) ->
|
||||
if typeof commandName is 'object'
|
||||
commands = commandName
|
||||
disposable = new CompositeDisposable
|
||||
for commandName, callback of commands
|
||||
disposable.add @add(target, commandName, callback)
|
||||
disposable.add @listen(target, commandName, callback, useCapture)
|
||||
return disposable
|
||||
|
||||
if typeof target is 'string'
|
||||
@addSelectorBasedListener(target, commandName, callback)
|
||||
@addSelectorBasedListener(target, commandName, callback, useCapture)
|
||||
else
|
||||
@addInlineListener(target, commandName, callback)
|
||||
@addInlineListener(target, commandName, callback, useCapture)
|
||||
|
||||
addSelectorBasedListener: (selector, commandName, callback) ->
|
||||
add: (target, commandName, callback) ->
|
||||
Grim.deprecate("Use CommandRegistry::listen instead")
|
||||
@listen(target, commandName, callback)
|
||||
|
||||
capture: (target, commandName, callback) ->
|
||||
@listen(target, commandName, callback, true)
|
||||
|
||||
addSelectorBasedListener: (selector, commandName, callback, useCapture) ->
|
||||
@selectorBasedListenersByCommandName[commandName] ?= []
|
||||
listenersForCommand = @selectorBasedListenersByCommandName[commandName]
|
||||
listener = new SelectorBasedListener(selector, callback)
|
||||
listener = new SelectorBasedListener(selector, callback, useCapture)
|
||||
listenersForCommand.push(listener)
|
||||
|
||||
@commandRegistered(commandName)
|
||||
@@ -102,17 +195,17 @@ class CommandRegistry
|
||||
listenersForCommand.splice(listenersForCommand.indexOf(listener), 1)
|
||||
delete @selectorBasedListenersByCommandName[commandName] if listenersForCommand.length is 0
|
||||
|
||||
addInlineListener: (element, commandName, callback) ->
|
||||
addInlineListener: (element, commandName, callback, useCapture) ->
|
||||
@inlineListenersByCommandName[commandName] ?= new WeakMap
|
||||
|
||||
listenersForCommand = @inlineListenersByCommandName[commandName]
|
||||
unless listenersForElement = listenersForCommand.get(element)
|
||||
listenersForElement = []
|
||||
listenersForCommand.set(element, listenersForElement)
|
||||
listener = new InlineListener(callback)
|
||||
listener = new InlineListener(callback, useCapture)
|
||||
listenersForElement.push(listener)
|
||||
|
||||
@commandRegistered(commandName)
|
||||
@commandRegistered(commandName, element)
|
||||
|
||||
new Disposable ->
|
||||
listenersForElement.splice(listenersForElement.indexOf(listener), 1)
|
||||
@@ -160,11 +253,17 @@ class CommandRegistry
|
||||
# processed.
|
||||
#
|
||||
# * `target` The DOM node at which to start bubbling the command event.
|
||||
# * `commandName` {String} indicating the name of the command to dispatch.
|
||||
dispatch: (target, commandName, detail) ->
|
||||
event = new CustomEvent(commandName, {bubbles: true, detail})
|
||||
# * `event` {String} indicating the name of the command to dispatch or a
|
||||
# DOM event object.
|
||||
# * `detail` The detail to associate with the dispatched DOM event. If present
|
||||
# overrides the `detail` of the `event` if it is a DOM event object.
|
||||
dispatch: (target, event, detail) ->
|
||||
if typeof event is 'string'
|
||||
event = new CustomEvent(event, {bubbles: NativeEventBubbling[event] ? true})
|
||||
|
||||
eventWithTarget = Object.create event,
|
||||
target: value: target
|
||||
detail: value: detail ? event.detail
|
||||
preventDefault: value: ->
|
||||
stopPropagation: value: ->
|
||||
stopImmediatePropagation: value: ->
|
||||
@@ -185,56 +284,93 @@ class CommandRegistry
|
||||
@selectorBasedListenersByCommandName[commandName] = listeners.slice()
|
||||
|
||||
handleCommandEvent: (originalEvent) =>
|
||||
originalEvent.stopImmediatePropagation()
|
||||
|
||||
propagationStopped = false
|
||||
immediatePropagationStopped = false
|
||||
matched = false
|
||||
currentTarget = originalEvent.target
|
||||
invokedListener = false
|
||||
{target, type} = originalEvent
|
||||
currentTarget = null
|
||||
bubbles = NativeEventBubbling[type] ? originalEvent.bubbles
|
||||
|
||||
syntheticEvent = Object.create originalEvent,
|
||||
eventPhase: value: Event.BUBBLING_PHASE
|
||||
eventPhase: get: -> eventPhase
|
||||
currentTarget: get: -> currentTarget
|
||||
preventDefault: value: ->
|
||||
originalEvent.preventDefault()
|
||||
stopPropagation: value: ->
|
||||
originalEvent.stopPropagation()
|
||||
propagationStopped = true
|
||||
stopImmediatePropagation: value: ->
|
||||
originalEvent.stopImmediatePropagation()
|
||||
propagationStopped = true
|
||||
immediatePropagationStopped = true
|
||||
preventDefault: value: ->
|
||||
originalEvent.preventDefault()
|
||||
abortKeyBinding: value: ->
|
||||
originalEvent.abortKeyBinding?()
|
||||
|
||||
@emitter.emit 'will-dispatch', syntheticEvent
|
||||
|
||||
loop
|
||||
listeners = @inlineListenersByCommandName[originalEvent.type]?.get(currentTarget) ? []
|
||||
if currentTarget.webkitMatchesSelector?
|
||||
selectorBasedListeners =
|
||||
(@selectorBasedListenersByCommandName[originalEvent.type] ? [])
|
||||
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)
|
||||
.sort (a, b) -> a.compare(b)
|
||||
listeners = listeners.concat(selectorBasedListeners)
|
||||
path = @getBubblePath(target)
|
||||
|
||||
matched = true if listeners.length > 0
|
||||
|
||||
for listener in listeners
|
||||
# Capture phase: Invoke listeners registered via {::capture}, starting with
|
||||
# the window and moving downward towards the event target.
|
||||
eventPhase = Event.CAPTURING_PHASE
|
||||
for pathIndex in [(path.length - 1)..0]
|
||||
currentTarget = path[pathIndex]
|
||||
listeners = @listenersForNode(currentTarget, type)
|
||||
for listener in listeners when listener.useCapture
|
||||
break if immediatePropagationStopped
|
||||
invokedListener = true
|
||||
listener.callback.call(currentTarget, syntheticEvent)
|
||||
break if propagationStopped
|
||||
|
||||
return invokedListener if propagationStopped
|
||||
|
||||
# Bubble phase: Invoke listeners registered via {::listen}, starting with
|
||||
# the event target and moving upward towards the window. If the event's
|
||||
# `.bubbles` property is false, we abort after dispatching on the target.
|
||||
eventPhase = Event.BUBBLING_PHASE
|
||||
for currentTarget in path
|
||||
listeners = @listenersForNode(currentTarget, type)
|
||||
for listener in listeners when not listener.useCapture
|
||||
break if immediatePropagationStopped
|
||||
invokedListener = true
|
||||
listener.callback.call(currentTarget, syntheticEvent)
|
||||
|
||||
break if currentTarget is window
|
||||
break unless bubbles
|
||||
break if propagationStopped
|
||||
currentTarget = currentTarget.parentNode ? window
|
||||
|
||||
matched
|
||||
invokedListener
|
||||
|
||||
commandRegistered: (commandName) ->
|
||||
commandRegistered: (commandName, inlineNode) ->
|
||||
unless @registeredCommands[commandName]
|
||||
window.addEventListener(commandName, @handleCommandEvent, true)
|
||||
@addEventListener(window, commandName, @handleCommandEvent, true)
|
||||
@registeredCommands[commandName] = true
|
||||
|
||||
if inlineNode? and not @registeredCommandsByInlineNode.get(inlineNode)?[commandName]
|
||||
@addEventListener(inlineNode, commandName, @handleCommandEvent, true)
|
||||
@registeredCommandsByInlineNode.set(inlineNode, {}) unless @registeredCommandsByInlineNode.has(inlineNode)
|
||||
@registeredCommandsByInlineNode.get(inlineNode)[commandName] = true
|
||||
|
||||
getBubblePath: (target) ->
|
||||
path = []
|
||||
currentTarget = target
|
||||
loop
|
||||
path.push(currentTarget)
|
||||
break if currentTarget is window
|
||||
currentTarget = currentTarget.parentNode ? window
|
||||
path
|
||||
|
||||
listenersForNode: (node, eventType) ->
|
||||
listeners = @inlineListenersByCommandName[eventType]?.get(node) ? []
|
||||
if node.matches?
|
||||
selectorBasedListeners =
|
||||
(@selectorBasedListenersByCommandName[eventType] ? [])
|
||||
.filter (listener) -> node.matches(listener.selector)
|
||||
.sort (a, b) -> a.compare(b)
|
||||
listeners = listeners.concat(selectorBasedListeners)
|
||||
listeners
|
||||
|
||||
class SelectorBasedListener
|
||||
constructor: (@selector, @callback) ->
|
||||
constructor: (@selector, @callback, @useCapture) ->
|
||||
@specificity = (SpecificityCache[@selector] ?= specificity(@selector))
|
||||
@sequenceNumber = SequenceCount++
|
||||
|
||||
@@ -243,4 +379,4 @@ class SelectorBasedListener
|
||||
other.sequenceNumber - @sequenceNumber
|
||||
|
||||
class InlineListener
|
||||
constructor: (@callback) ->
|
||||
constructor: (@callback, @useCapture) ->
|
||||
|
||||
@@ -375,7 +375,7 @@ class Package
|
||||
do (selector, command) =>
|
||||
# Add dummy command so it appears in menu.
|
||||
# The real command will be registered on package activation
|
||||
@activationCommandSubscriptions.add atom.commands.add selector, command, ->
|
||||
@activationCommandSubscriptions.add atom.commands.listen selector, command, ->
|
||||
@activationCommandSubscriptions.add atom.commands.onWillDispatch (event) =>
|
||||
return unless event.type is command
|
||||
currentTarget = event.target
|
||||
|
||||
@@ -15,46 +15,53 @@ SpacePen.callRemoveHooks = (element) ->
|
||||
view.unsubscribe() for view in SpacePen.viewsForElement(element)
|
||||
SpacePenCallRemoveHooks(element)
|
||||
|
||||
NativeEventNames = new Set
|
||||
NativeEventNames.add(nativeEvent) for nativeEvent in ["blur", "focus", "focusin",
|
||||
"focusout", "load", "resize", "scroll", "unload", "click", "dblclick", "mousedown",
|
||||
"mouseup", "mousemove", "mouseover", "mouseout", "mouseenter", "mouseleave", "change",
|
||||
"select", "submit", "keydown", "keypress", "keyup", "error", "contextmenu", "textInput",
|
||||
"textinput"]
|
||||
|
||||
JQueryTrigger = jQuery.fn.trigger
|
||||
jQuery.fn.trigger = (eventName, data) ->
|
||||
if NativeEventNames.has(eventName) or typeof eventName is 'object'
|
||||
JQueryTrigger.call(this, eventName, data)
|
||||
jQuery.fn.trigger = (event, data) ->
|
||||
if typeof event is 'object'
|
||||
{type, target, originalEvent} = event
|
||||
if originalEvent?
|
||||
originalEvent.type ?= type
|
||||
event = originalEvent
|
||||
else
|
||||
type = event
|
||||
|
||||
specialTrigger = jQuery.event.special[event]?.trigger
|
||||
|
||||
# Don't deal with namespaces
|
||||
return JQueryTrigger.apply(this, arguments) unless type.indexOf('.') is -1
|
||||
|
||||
if target?
|
||||
atom.commands.dispatch(target, event, data)
|
||||
else
|
||||
for element in this
|
||||
atom.commands.dispatch(element, eventName, data)
|
||||
this
|
||||
continue if specialTrigger?.apply(element) is false
|
||||
atom.commands.dispatch(element, event, data)
|
||||
this
|
||||
|
||||
# Allow command registry integration with focusin and focusout events
|
||||
# Otherwise jQuery registers these event handlers in a special way for bubbling
|
||||
# compatibility
|
||||
delete jQuery.event.special.focusin
|
||||
delete jQuery.event.special.focusout
|
||||
|
||||
HandlersByOriginalHandler = new WeakMap
|
||||
CommandDisposablesByElement = new WeakMap
|
||||
|
||||
AddEventListener = (element, type, listener) ->
|
||||
if NativeEventNames.has(type)
|
||||
element.addEventListener(type, listener)
|
||||
else
|
||||
disposable = atom.commands.add(element, type, listener)
|
||||
disposable = atom.commands.listen(element, type, listener)
|
||||
|
||||
unless disposablesByType = CommandDisposablesByElement.get(element)
|
||||
disposablesByType = {}
|
||||
CommandDisposablesByElement.set(element, disposablesByType)
|
||||
unless disposablesByType = CommandDisposablesByElement.get(element)
|
||||
disposablesByType = {}
|
||||
CommandDisposablesByElement.set(element, disposablesByType)
|
||||
|
||||
unless disposablesByListener = disposablesByType[type]
|
||||
disposablesByListener = new WeakMap
|
||||
disposablesByType[type] = disposablesByListener
|
||||
unless disposablesByListener = disposablesByType[type]
|
||||
disposablesByListener = new WeakMap
|
||||
disposablesByType[type] = disposablesByListener
|
||||
|
||||
disposablesByListener.set(listener, disposable)
|
||||
disposablesByListener.set(listener, disposable)
|
||||
|
||||
RemoveEventListener = (element, type, listener) ->
|
||||
if NativeEventNames.has(type)
|
||||
element.removeEventListener(type, listener)
|
||||
else
|
||||
CommandDisposablesByElement.get(element)?[type]?.get(listener)?.dispose()
|
||||
CommandDisposablesByElement.get(element)?[type]?.get(listener)?.dispose()
|
||||
|
||||
JQueryEventAdd = jQuery.event.add
|
||||
jQuery.event.add = (elem, types, originalHandler, data, selector) ->
|
||||
|
||||
@@ -103,7 +103,7 @@ stopCommandEventPropagation = (commandListeners) ->
|
||||
commandListener.call(this, event)
|
||||
newCommandListeners
|
||||
|
||||
atom.commands.add 'atom-text-editor', stopCommandEventPropagation(
|
||||
atom.commands.listen 'atom-text-editor', stopCommandEventPropagation(
|
||||
'core:move-left': -> @getModel().moveLeft()
|
||||
'core:move-right': -> @getModel().moveRight()
|
||||
'core:select-left': -> @getModel().selectLeft()
|
||||
@@ -153,7 +153,7 @@ atom.commands.add 'atom-text-editor', stopCommandEventPropagation(
|
||||
'editor:lower-case': -> @getModel().lowerCase()
|
||||
)
|
||||
|
||||
atom.commands.add 'atom-text-editor:not(.mini)', stopCommandEventPropagation(
|
||||
atom.commands.listen 'atom-text-editor:not(.mini)', stopCommandEventPropagation(
|
||||
'core:move-up': -> @getModel().moveUp()
|
||||
'core:move-down': -> @getModel().moveDown()
|
||||
'core:move-to-top': -> @getModel().moveToTop()
|
||||
|
||||
@@ -65,7 +65,7 @@ class WindowEventHandler
|
||||
|
||||
@subscribeToCommand $(document), 'core:focus-previous', @focusPrevious
|
||||
|
||||
document.addEventListener 'keydown', @onKeydown
|
||||
atom.commands.listen window, 'keydown', @onKeydown
|
||||
|
||||
@subscribe $(document), 'drop', (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
@@ -114,7 +114,7 @@ class WorkspaceElement extends HTMLElement
|
||||
|
||||
focusPaneViewOnRight: -> @paneContainer.focusPaneViewOnRight()
|
||||
|
||||
atom.commands.add 'atom-workspace',
|
||||
atom.commands.listen 'atom-workspace',
|
||||
'window:increase-font-size': -> @getModel().increaseFontSize()
|
||||
'window:decrease-font-size': -> @getModel().decreaseFontSize()
|
||||
'window:reset-font-size': -> @getModel().resetFontSize()
|
||||
@@ -160,6 +160,6 @@ atom.commands.add 'atom-workspace',
|
||||
'core:save-as': -> @getModel().saveActivePaneItemAs()
|
||||
|
||||
if process.platform is 'darwin'
|
||||
atom.commands.add 'atom-workspace', 'window:install-shell-commands', -> @getModel().installShellCommands()
|
||||
atom.commands.listen 'atom-workspace', 'window:install-shell-commands', -> @getModel().installShellCommands()
|
||||
|
||||
module.exports = WorkspaceElement = document.registerElement 'atom-workspace', prototype: WorkspaceElement.prototype
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário