snapsvg/src/eve.js

441 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Copyright (c) 2017 Adobe Systems Incorporated. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ┌────────────────────────────────────────────────────────────┐ \\
// │ Eve 0.5.4 - JavaScript Events Library │ \\
// ├────────────────────────────────────────────────────────────┤ \\
// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
// └────────────────────────────────────────────────────────────┘ \\
let version = '0.5.4',
has = 'hasOwnProperty',
separator = /[\.\/]/,
comaseparator = /\s*,\s*/,
wildcard = '*',
numsort = function (a, b) {
return a - b
},
current_event,
stop,
events = { n: {} },
firstDefined = function () {
for (let i = 0, ii = this.length; i < ii; i++) {
if (this[i] !== void 0) {
return this[i]
}
}
},
lastDefined = function () {
let i = this.length
while (--i) {
if (this[i] !== void 0) {
return this[i]
}
}
},
objtos = Object.prototype.toString,
Str = String,
isArray =
Array.isArray ||
function (ar) {
return ar instanceof Array || objtos.call(ar) == '[object Array]'
},
/*\
* eve
[ method ]
* Fires event with given `name`, given scope and other parameters.
- name (string) name of the *event*, dot (`.`) or slash (`/`) separated
- scope (object) context for the event handlers
- varargs (...) the rest of arguments will be sent to event handlers
= (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value.
\*/
eve = function (name, scope) {
let oldstop = stop,
args = Array.prototype.slice.call(arguments, 2),
listeners = eve.listeners(name),
z = 0,
l,
indexed = [],
queue = {},
out = [],
ce = current_event
out.firstDefined = firstDefined
out.lastDefined = lastDefined
current_event = name
stop = 0
for (let i = 0; i < listeners.length; i++)
if ('zIndex' in listeners[i]) {
indexed.push(listeners[i].zIndex)
if (listeners[i].zIndex < 0) {
queue[listeners[i].zIndex] = listeners[i]
}
}
indexed.sort(numsort)
while (indexed[z] < 0) {
l = queue[indexed[z++]]
out.push(l.apply(scope, args))
if (stop) {
stop = oldstop
return out
}
}
for (let i = 0; i < listeners.length; i++) {
l = listeners[i]
if ('zIndex' in l) {
if (l.zIndex == indexed[z]) {
out.push(l.apply(scope, args))
if (stop) {
break
}
do {
z++
l = queue[indexed[z]]
l && out.push(l.apply(scope, args))
if (stop) {
break
}
} while (l)
} else {
queue[l.zIndex] = l
}
} else {
out.push(l.apply(scope, args))
if (stop) {
break
}
}
}
stop = oldstop
current_event = ce
return out
}
// Undocumented. Debug only.
eve._events = events
/*\
* eve.listeners
[ method ]
* Internal method which gives you array of all event handlers that will be triggered by the given `name`.
- name (string) name of the event, dot (`.`) or slash (`/`) separated
= (array) array of event handlers
\*/
eve.listeners = function (name) {
let names = isArray(name) ? name : name.split(separator),
e = events,
item,
items,
k,
i,
ii,
j,
jj,
nes,
es = [e],
out = []
for (i = 0, ii = names.length; i < ii; i++) {
nes = []
for (j = 0, jj = es.length; j < jj; j++) {
e = es[j].n
items = [e[names[i]], e[wildcard]]
k = 2
while (k--) {
item = items[k]
if (item) {
nes.push(item)
out = out.concat(item.f || [])
}
}
}
es = nes
}
return out
}
/*\
* eve.separator
[ method ]
* If for some reasons you dont like default separators (`.` or `/`) you can specify yours
* here. Be aware that if you pass a string longer than one character it will be treated as
* a list of characters.
- separator (string) new separator. Empty string resets to default: `.` or `/`.
\*/
eve.separator = function (sep) {
if (sep) {
sep = Str(sep).replace(/(?=[\.\^\]\[\-])/g, '\\')
sep = '[' + sep + ']'
separator = new RegExp(sep)
} else {
separator = /[\.\/]/
}
}
/*\
* eve.on
[ method ]
**
* Binds given event handler with a given name. You can use wildcards “`*`” for the names:
| eve.on("*.under.*", f);
| eve("mouse.under.floor"); // triggers f
* Use @eve to trigger the listener.
**
- name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
- f (function) event handler function
**
- name (array) if you dont want to use separators, you can use array of strings
- f (function) event handler function
**
= (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment.
> Example:
| eve.on("mouse", eatIt)(2);
| eve.on("mouse", scream);
| eve.on("mouse", catchIt)(1);
* This will ensure that `catchIt` function will be called before `eatIt`.
*
* If you want to put your handler before non-indexed handlers, specify a negative value.
* Note: I assume most of the time you dont need to worry about z-index, but its nice to have this feature “just in case”.
\*/
eve.on = function (name, f) {
if (typeof f != 'function') {
return function () {}
}
let names = isArray(name)
? isArray(name[0])
? name
: [name]
: Str(name).split(comaseparator)
for (let i = 0, ii = names.length; i < ii; i++) {
;(function (name) {
let names = isArray(name) ? name : Str(name).split(separator),
e = events,
exist
for (let i = 0, ii = names.length; i < ii; i++) {
e = e.n
e =
(e.hasOwnProperty(names[i]) && e[names[i]]) ||
(e[names[i]] = { n: {} })
}
e.f = e.f || []
for (i = 0, ii = e.f.length; i < ii; i++)
if (e.f[i] == f) {
exist = true
break
}
!exist && e.f.push(f)
})(names[i])
}
return function (zIndex) {
if (+zIndex == +zIndex) {
f.zIndex = +zIndex
}
}
}
/*\
**
* Returns function that will fire given event with optional arguments.
* Arguments that will be passed to the result function will be also
* concated to the list of final arguments.
| el.onclick = eve.f("click", 1, 2);
| eve.on("click", function (a, b, c) {
| console.log(a, b, c); // 1, 2, [event object]
| });
- event (string) event name
- varargs (…) and any other arguments
= (function) possible event handler function
\*/
eve.f = function (event, ...attrs) {
return function (...args) {
eve.apply(null, [event, null].concat(attrs).concat(args))
}
}
/*\
**
* Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
\*/
eve.stop = function () {
stop = 1
}
/*\
* eve.nt
[ method ]
**
* Could be used inside event handler to figure out actual name of the event.
**
- subname (string) #optional subname of the event
**
= (string) name of the event, if `subname` is not specified
* or
= (boolean) `true`, if current events name contains `subname`
\*/
eve.nt = function (subname) {
let cur = isArray(current_event) ? current_event.join('.') : current_event
if (subname) {
return new RegExp('(?:\\.|\\/|^)' + subname + '(?:\\.|\\/|$)').test(cur)
}
return cur
}
/*\
* eve.nts
[ method ]
**
* Could be used inside event handler to figure out actual name of the event.
**
**
= (array) names of the event
\*/
eve.nts = function () {
return isArray(current_event) ? current_event : current_event.split(separator)
}
/*\
* eve.off
[ method ]
**
* Removes given function from the list of event listeners assigned to given name.
* If no arguments specified all the events will be cleared.
**
- name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
- f (function) event handler function
\*/
/*\
* eve.unbind
[ method ]
**
* See @eve.off
\*/
eve.off = eve.unbind = function (name, f) {
if (!name) {
eve._events = events = { n: {} }
return
}
let names = isArray(name)
? isArray(name[0])
? name
: [name]
: Str(name).split(comaseparator)
if (names.length > 1) {
for (let i = 0, ii = names.length; i < ii; i++) {
eve.off(names[i], f)
}
return
}
names = isArray(name) ? name : Str(name).split(separator)
let e,
key,
splice,
i,
ii,
j,
jj,
cur = [events],
inodes = []
for (i = 0, ii = names.length; i < ii; i++) {
for (j = 0; j < cur.length; j += splice.length - 2) {
splice = [j, 1]
e = cur[j].n
if (names[i] != wildcard) {
if (e[names[i]]) {
splice.push(e[names[i]])
inodes.unshift({
n: e,
name: names[i]
})
}
} else {
for (key in e)
if (e[has](key)) {
splice.push(e[key])
inodes.unshift({
n: e,
name: key
})
}
}
cur.splice.apply(cur, splice)
}
}
for (i = 0, ii = cur.length; i < ii; i++) {
e = cur[i]
while (e.n) {
if (f) {
if (e.f) {
for (j = 0, jj = e.f.length; j < jj; j++)
if (e.f[j] == f) {
e.f.splice(j, 1)
break
}
!e.f.length && delete e.f
}
for (key in e.n)
if (e.n[has](key) && e.n[key].f) {
let funcs = e.n[key].f
for (j = 0, jj = funcs.length; j < jj; j++)
if (funcs[j] == f) {
funcs.splice(j, 1)
break
}
!funcs.length && delete e.n[key].f
}
} else {
delete e.f
for (key in e.n)
if (e.n[has](key) && e.n[key].f) {
delete e.n[key].f
}
}
e = e.n
}
}
// prune inner nodes in path
prune: for (i = 0, ii = inodes.length; i < ii; i++) {
e = inodes[i]
for (key in e.n[e.name].f) {
// not empty (has listeners)
continue prune
}
for (key in e.n[e.name].n) {
// not empty (has children)
continue prune
}
// is empty
delete e.n[e.name]
}
}
/*\
* eve.once
[ method ]
**
* Binds given event handler with a given name to only run once then unbind itself.
| eve.once("login", f);
| eve("login"); // triggers f
| eve("login"); // no listeners
* Use @eve to trigger the listener.
**
- name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
- f (function) event handler function
**
= (function) same return function as @eve.on
\*/
eve.once = function (name, f) {
let f2 = function () {
eve.off(name, f2)
return f.apply(this, arguments)
}
return eve.on(name, f2)
}
export default eve
JavaScript 100%