// Copyright (c) 2013 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. import eve from './eve.js' import { Snap, SnapElement, Paper, Fragment } from './svg.js' import mina from './mina.js' import { is } from './utils.js' let mmax = Math.max, mmin = Math.min // Set let Set = function (items) { this.items = [] this.bindings = {} this.length = 0 this.type = 'set' if (items) { for (let i = 0, ii = items.length; i < ii; i++) { if (items[i]) { this[this.items.length] = this.items[this.items.length] = items[i] this.length++ } } } }, setproto = Set.prototype /*\ * Set.push [ method ] ** * Adds each argument to the current set = (object) original element \*/ setproto.push = function () { let item, len for (let i = 0, ii = arguments.length; i < ii; i++) { item = arguments[i] if (item) { len = this.items.length this[len] = this.items[len] = item this.length++ } } return this } /*\ * Set.pop [ method ] ** * Removes last element and returns it = (object) element \*/ setproto.pop = function () { this.length && delete this[this.length--] return this.items.pop() } /*\ * Set.forEach [ method ] ** * Executes given function for each element in the set * * If the function returns `false`, the loop stops running. ** - callback (function) function to run - thisArg (object) context object for the callback = (object) Set object \*/ setproto.forEach = function (callback, thisArg) { for (let i = 0, ii = this.items.length; i < ii; i++) { if (callback.call(thisArg, this.items[i], i) === false) { return this } } return this } /*\ * Set.animate [ method ] ** * Animates each element in set in sync. * ** - attrs (object) key-value pairs of destination attributes - duration (number) duration of the animation in milliseconds - easing (function) #optional easing function from @mina or custom - callback (function) #optional callback function that executes when the animation ends * or - animation (array) array of animation parameter for each element in set in format `[attrs, duration, easing, callback]` > Usage | // animate all elements in set to radius 10 | set.animate({r: 10}, 500, mina.easein); | // or | // animate first element to radius 10, but second to radius 20 and in different time | set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]); = (SnapElement) the current element \*/ setproto.animate = function (attrs, ms, easing, callback) { if (typeof easing == 'function' && !easing.length) { callback = easing easing = mina.linear } if (attrs instanceof Snap._.Animation) { callback = attrs.callback easing = attrs.easing ms = easing.dur attrs = attrs.attr } let args = arguments if (is(attrs, 'array') && is(args[args.length - 1], 'array')) { let each = true } let begin, handler = function () { if (begin) { this.b = begin } else { begin = this.b } }, cb = 0, set = this, callbacker = callback && function () { if (++cb == set.length) { callback.call(this) } } return this.forEach(function (el, i) { eve.once('snap.animcreated.' + el.id, handler) if (each) { args[i] && el.animate.apply(el, args[i]) } else { el.animate(attrs, ms, easing, callbacker) } }) } /*\ * Set.remove [ method ] ** * Removes all children of the set. * = (object) Set object \*/ setproto.remove = function () { while (this.length) { this.pop().remove() } return this } /*\ * Set.bind [ method ] ** * Specifies how to handle a specific attribute when applied * to a set. * ** - attr (string) attribute name - callback (function) function to run * or - attr (string) attribute name - element (SnapElement) specific element in the set to apply the attribute to * or - attr (string) attribute name - element (SnapElement) specific element in the set to apply the attribute to - eattr (string) attribute on the element to bind the attribute to = (object) Set object \*/ setproto.bind = function (attr, a, b) { let data = {} if (typeof a == 'function') { this.bindings[attr] = a } else { let aname = b || attr this.bindings[attr] = function (v) { data[aname] = v a.attr(data) } } return this } /*\ * Set.attr [ method ] ** * Equivalent of @SnapElement.attr. = (object) Set object \*/ setproto.attr = function (value) { let unbound = {} for (let k in value) { if (this.bindings[k]) { this.bindings[k](value[k]) } else { unbound[k] = value[k] } } for (let i = 0, ii = this.items.length; i < ii; i++) { this.items[i].attr(unbound) } return this } /*\ * Set.clear [ method ] ** * Removes all elements from the set \*/ setproto.clear = function () { while (this.length) { this.pop() } } /*\ * Set.splice [ method ] ** * Removes range of elements from the set ** - index (number) position of the deletion - count (number) number of element to remove - insertion… (object) #optional elements to insert = (object) set elements that were deleted \*/ setproto.splice = function (index, count, insertion) { index = index < 0 ? mmax(this.length + index, 0) : index count = mmax(0, mmin(this.length - index, count)) let tail = [], todel = [], args = [], i for (i = 2; i < arguments.length; i++) { args.push(arguments[i]) } for (i = 0; i < count; i++) { todel.push(this[index + i]) } for (; i < this.length - index; i++) { tail.push(this[index + i]) } let arglen = args.length for (i = 0; i < arglen + tail.length; i++) { this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen] } i = this.items.length = this.length -= count - arglen while (this[i]) { delete this[i++] } return new Set(todel) } /*\ * Set.exclude [ method ] ** * Removes given element from the set ** - element (object) element to remove = (boolean) `true` if object was found and removed from the set \*/ setproto.exclude = function (el) { for (let i = 0, ii = this.length; i < ii; i++) if (this[i] == el) { this.splice(i, 1) return true } return false } /*\ * Set.insertAfter [ method ] ** * Inserts set elements after given element. ** - element (object) set will be inserted after this element = (object) Set object \*/ setproto.insertAfter = function (el) { let i = this.items.length while (i--) { this.items[i].insertAfter(el) } return this } /*\ * Set.getBBox [ method ] ** * Union of all bboxes of the set. See @SnapElement.getBBox. = (object) bounding box descriptor. See @SnapElement.getBBox. \*/ setproto.getBBox = function () { let x = [], y = [], x2 = [], y2 = [] for (let i = this.items.length; i--; ) if (!this.items[i].removed) { let box = this.items[i].getBBox() x.push(box.x) y.push(box.y) x2.push(box.x + box.width) y2.push(box.y + box.height) } x = mmin.apply(0, x) y = mmin.apply(0, y) x2 = mmax.apply(0, x2) y2 = mmax.apply(0, y2) return { x: x, y: y, x2: x2, y2: y2, width: x2 - x, height: y2 - y, cx: x + (x2 - x) / 2, cy: y + (y2 - y) / 2 } } /*\ * Set.insertAfter [ method ] ** * Creates a clone of the set. ** = (object) New Set object \*/ setproto.clone = function (s) { s = new Set() for (let i = 0, ii = this.items.length; i < ii; i++) { s.push(this.items[i].clone()) } return s } setproto.toString = function () { return 'Snap\u2018s set' } setproto.type = 'set' // export /*\ * Snap.Set [ property ] ** * Set constructor. \*/ Snap.Set = Set /*\ * Snap.set [ method ] ** * Creates a set and fills it with list of arguments. ** = (object) New Set object | let r = paper.rect(0, 0, 10, 10), | s1 = Snap.set(), // empty set | s2 = Snap.set(r, paper.circle(100, 100, 20)); // prefilled set \*/ Snap.set = function () { let set = new Set() if (arguments.length) { set.push.apply(set, Array.prototype.slice.call(arguments, 0)) } return set }