UNPKG

filepond

Version:

FilePond, Where files go to stretch their bits.

1,902 lines (1,611 loc) 274 kB
/*! * FilePond 4.3.7 * Licensed under MIT, https://opensource.org/licenses/MIT/ * Please visit https://pqina.nl/filepond/ for details. */ /* eslint-disable */ (function(global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : ((global = global || self), factory((global.FilePond = {}))); })(this, function(exports) { 'use strict'; var isNode = function isNode(value) { return value instanceof HTMLElement; }; var createStore = function createStore(initialState) { var queries = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; var actions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; // internal state var state = Object.assign({}, initialState); // contains all actions for next frame, is clear when actions are requested var actionQueue = []; var dispatchQueue = []; // returns a duplicate of the current state var getState = function getState() { return Object.assign({}, state); }; // returns a duplicate of the actions array and clears the actions array var processActionQueue = function processActionQueue() { // create copy of actions queue var queue = [].concat(actionQueue); // clear actions queue (we don't want no double actions) actionQueue.length = 0; return queue; }; // processes actions that might block the main UI thread var processDispatchQueue = function processDispatchQueue() { // create copy of actions queue var queue = [].concat(dispatchQueue); // clear actions queue (we don't want no double actions) dispatchQueue.length = 0; // now dispatch these actions queue.forEach(function(_ref) { var type = _ref.type, data = _ref.data; dispatch(type, data); }); }; // adds a new action, calls its handler and var dispatch = function dispatch(type, data, isBlocking) { // is blocking action if (isBlocking) { dispatchQueue.push({ type: type, data: data }); return; } // if this action has a handler, handle the action if (actionHandlers[type]) { actionHandlers[type](data); } // now add action actionQueue.push({ type: type, data: data }); }; var query = function query(str) { var _queryHandles; for ( var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++ ) { args[_key - 1] = arguments[_key]; } return queryHandles[str] ? (_queryHandles = queryHandles)[str].apply(_queryHandles, args) : null; }; var api = { getState: getState, processActionQueue: processActionQueue, processDispatchQueue: processDispatchQueue, dispatch: dispatch, query: query }; var queryHandles = {}; queries.forEach(function(query) { queryHandles = Object.assign({}, query(state), queryHandles); }); var actionHandlers = {}; actions.forEach(function(action) { actionHandlers = Object.assign( {}, action(dispatch, query, state), actionHandlers ); }); return api; }; var defineProperty = function defineProperty(obj, property, definition) { if (typeof definition === 'function') { obj[property] = definition; return; } Object.defineProperty(obj, property, Object.assign({}, definition)); }; var forin = function forin(obj, cb) { for (var key in obj) { if (!obj.hasOwnProperty(key)) { continue; } cb(key, obj[key]); } }; var createObject = function createObject(definition) { var obj = {}; forin(definition, function(property) { defineProperty(obj, property, definition[property]); }); return obj; }; var attr = function attr(node, name) { var value = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; if (value === null) { return node.getAttribute(name) || node.hasAttribute(name); } node.setAttribute(name, value); }; var ns = 'http://www.w3.org/2000/svg'; var svgElements = ['svg', 'path']; // only svg elements used var isSVGElement = function isSVGElement(tag) { return svgElements.includes(tag); }; var createElement = function createElement(tag, className) { var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; if (typeof className === 'object') { attributes = className; className = null; } var element = isSVGElement(tag) ? document.createElementNS(ns, tag) : document.createElement(tag); if (className) { if (isSVGElement(tag)) { attr(element, 'class', className); } else { element.className = className; } } forin(attributes, function(name, value) { attr(element, name, value); }); return element; }; var appendChild = function appendChild(parent) { return function(child, index) { if (typeof index !== 'undefined' && parent.children[index]) { parent.insertBefore(child, parent.children[index]); } else { parent.appendChild(child); } }; }; var appendChildView = function appendChildView(parent, childViews) { return function(view, index) { if (typeof index !== 'undefined') { childViews.splice(index, 0, view); } else { childViews.push(view); } return view; }; }; var removeChildView = function removeChildView(parent, childViews) { return function(view) { // remove from child views childViews.splice(childViews.indexOf(view), 1); // remove the element if (view.element.parentNode) { parent.removeChild(view.element); } return view; }; }; var getViewRect = function getViewRect( elementRect, childViews, offset, scale ) { var left = offset[0] || elementRect.left; var top = offset[1] || elementRect.top; var right = left + elementRect.width; var bottom = top + elementRect.height * (scale[1] || 1); var rect = { // the rectangle of the element itself element: Object.assign({}, elementRect), // the rectangle of the element expanded to contain its children, does not include any margins inner: { left: elementRect.left, top: elementRect.top, right: elementRect.right, bottom: elementRect.bottom }, // the rectangle of the element expanded to contain its children including own margin and child margins // margins will be added after we've recalculated the size outer: { left: left, top: top, right: right, bottom: bottom } }; // expand rect to fit all child rectangles childViews .filter(function(childView) { return !childView.isRectIgnored(); }) .map(function(childView) { return childView.rect; }) .forEach(function(childViewRect) { expandRect(rect.inner, Object.assign({}, childViewRect.inner)); expandRect(rect.outer, Object.assign({}, childViewRect.outer)); }); // calculate inner width and height calculateRectSize(rect.inner); // append additional margin (top and left margins are included in top and left automatically) rect.outer.bottom += rect.element.marginBottom; rect.outer.right += rect.element.marginRight; // calculate outer width and height calculateRectSize(rect.outer); return rect; }; var expandRect = function expandRect(parent, child) { // adjust for parent offset child.top += parent.top; child.right += parent.left; child.bottom += parent.top; child.left += parent.left; if (child.bottom > parent.bottom) { parent.bottom = child.bottom; } if (child.right > parent.right) { parent.right = child.right; } }; var calculateRectSize = function calculateRectSize(rect) { rect.width = rect.right - rect.left; rect.height = rect.bottom - rect.top; }; var isNumber = function isNumber(value) { return typeof value === 'number'; }; /** * Determines if position is at destination * @param position * @param destination * @param velocity * @param errorMargin * @returns {boolean} */ var thereYet = function thereYet(position, destination, velocity) { var errorMargin = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0.001; return ( Math.abs(position - destination) < errorMargin && Math.abs(velocity) < errorMargin ); }; /** * Spring animation */ var spring = // default options function spring() // method definition { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$stiffness = _ref.stiffness, stiffness = _ref$stiffness === void 0 ? 0.5 : _ref$stiffness, _ref$damping = _ref.damping, damping = _ref$damping === void 0 ? 0.75 : _ref$damping, _ref$mass = _ref.mass, mass = _ref$mass === void 0 ? 10 : _ref$mass; var target = null; var position = null; var velocity = 0; var resting = false; // updates spring state var interpolate = function interpolate() { // in rest, don't animate if (resting) { return; } // need at least a target or position to do springy things if (!(isNumber(target) && isNumber(position))) { resting = true; velocity = 0; return; } // calculate spring force var f = -(position - target) * stiffness; // update velocity by adding force based on mass velocity += f / mass; // update position by adding velocity position += velocity; // slow down based on amount of damping velocity *= damping; // we've arrived if we're near target and our velocity is near zero if (thereYet(position, target, velocity)) { position = target; velocity = 0; resting = true; // we done api.onupdate(position); api.oncomplete(position); } else { // progress update api.onupdate(position); } }; /** * Set new target value * @param value */ var setTarget = function setTarget(value) { // if currently has no position, set target and position to this value if (isNumber(value) && !isNumber(position)) { position = value; } // next target value will not be animated to if (target === null) { target = value; position = value; } // let start moving to target target = value; // already at target if (position === target || typeof target === 'undefined') { // now resting as target is current position, stop moving resting = true; velocity = 0; // done! api.onupdate(position); api.oncomplete(position); return; } resting = false; }; // need 'api' to call onupdate callback var api = createObject({ interpolate: interpolate, target: { set: setTarget, get: function get() { return target; } }, resting: { get: function get() { return resting; } }, onupdate: function onupdate(value) {}, oncomplete: function oncomplete(value) {} }); return api; }; var easeLinear = function easeLinear(t) { return t; }; var easeInOutQuad = function easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; }; var tween = // default values function tween() // method definition { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$duration = _ref.duration, duration = _ref$duration === void 0 ? 500 : _ref$duration, _ref$easing = _ref.easing, easing = _ref$easing === void 0 ? easeInOutQuad : _ref$easing, _ref$delay = _ref.delay, delay = _ref$delay === void 0 ? 0 : _ref$delay; var start = null; var t; var p; var resting = true; var reverse = false; var target = null; var interpolate = function interpolate(ts) { if (resting || target === null) { return; } if (start === null) { start = ts; } if (ts - start < delay) { return; } t = ts - start - delay; if (t < duration) { p = t / duration; api.onupdate((t >= 0 ? easing(reverse ? 1 - p : p) : 0) * target); } else { t = 1; p = reverse ? 0 : 1; api.onupdate(p * target); api.oncomplete(p * target); resting = true; } }; // need 'api' to call onupdate callback var api = createObject({ interpolate: interpolate, target: { get: function get() { return reverse ? 0 : target; }, set: function set(value) { // is initial value if (target === null) { target = value; api.onupdate(value); api.oncomplete(value); return; } // want to tween to a smaller value and have a current value if (value < target) { target = 1; reverse = true; } else { // not tweening to a smaller value reverse = false; target = value; } // let's go! resting = false; start = null; } }, resting: { get: function get() { return resting; } }, onupdate: function onupdate(value) {}, oncomplete: function oncomplete(value) {} }); return api; }; var animator = { spring: spring, tween: tween }; /* { type: 'spring', stiffness: .5, damping: .75, mass: 10 }; { translation: { type: 'spring', ... }, ... } { translation: { x: { type: 'spring', ... } } } */ var createAnimator = function createAnimator(definition, category, property) { // default is single definition // we check if transform is set, if so, we check if property is set var def = definition[category] && typeof definition[category][property] === 'object' ? definition[category][property] : definition[category] || definition; var type = typeof def === 'string' ? def : def.type; var props = typeof def === 'object' ? Object.assign({}, def) : {}; return animator[type] ? animator[type](props) : null; }; var addGetSet = function addGetSet(keys, obj, props) { var overwrite = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; obj = Array.isArray(obj) ? obj : [obj]; obj.forEach(function(o) { keys.forEach(function(key) { var name = key; var getter = function getter() { return props[key]; }; var setter = function setter(value) { return (props[key] = value); }; if (typeof key === 'object') { name = key.key; getter = key.getter || getter; setter = key.setter || setter; } if (o[name] && !overwrite) { return; } o[name] = { get: getter, set: setter }; }); }); }; var isDefined = function isDefined(value) { return value != null; }; // add to state, // add getters and setters to internal and external api (if not set) // setup animators var animations = function animations(_ref) { var mixinConfig = _ref.mixinConfig, viewProps = _ref.viewProps, viewInternalAPI = _ref.viewInternalAPI, viewExternalAPI = _ref.viewExternalAPI, viewState = _ref.viewState; // initial properties var initialProps = Object.assign({}, viewProps); // list of all active animations var animations = []; // setup animators forin(mixinConfig, function(property, animation) { var animator = createAnimator(animation); if (!animator) { return; } // when the animator updates, update the view state value animator.onupdate = function(value) { viewProps[property] = value; }; // set animator target animator.target = initialProps[property]; // when value is set, set the animator target value var prop = { key: property, setter: function setter(value) { // if already at target, we done! if (animator.target === value) { return; } animator.target = value; }, getter: function getter() { return viewProps[property]; } }; // add getters and setters addGetSet([prop], [viewInternalAPI, viewExternalAPI], viewProps, true); // add it to the list for easy updating from the _write method animations.push(animator); }); // expose internal write api return { write: function write(ts) { var resting = true; animations.forEach(function(animation) { if (!animation.resting) { resting = false; } animation.interpolate(ts); }); return resting; }, destroy: function destroy() {} }; }; var addEvent = function addEvent(element) { return function(type, fn) { element.addEventListener(type, fn); }; }; var removeEvent = function removeEvent(element) { return function(type, fn) { element.removeEventListener(type, fn); }; }; // mixin var listeners = function listeners(_ref) { var mixinConfig = _ref.mixinConfig, viewProps = _ref.viewProps, viewInternalAPI = _ref.viewInternalAPI, viewExternalAPI = _ref.viewExternalAPI, viewState = _ref.viewState, view = _ref.view; var events = []; var add = addEvent(view.element); var remove = removeEvent(view.element); viewExternalAPI.on = function(type, fn) { events.push({ type: type, fn: fn }); add(type, fn); }; viewExternalAPI.off = function(type, fn) { events.splice( events.findIndex(function(event) { return event.type === type && event.fn === fn; }), 1 ); remove(type, fn); }; return { write: function write() { // not busy return true; }, destroy: function destroy() { events.forEach(function(event) { remove(event.type, event.fn); }); } }; }; // add to external api and link to props var apis = function apis(_ref) { var mixinConfig = _ref.mixinConfig, viewProps = _ref.viewProps, viewExternalAPI = _ref.viewExternalAPI; addGetSet(mixinConfig, viewExternalAPI, viewProps); }; // add to state, // add getters and setters to internal and external api (if not set) // set initial state based on props in viewProps // apply as transforms each frame var defaults = { opacity: 1, scaleX: 1, scaleY: 1, translateX: 0, translateY: 0, rotateX: 0, rotateY: 0, rotateZ: 0, originX: 0, originY: 0 }; var styles = function styles(_ref) { var mixinConfig = _ref.mixinConfig, viewProps = _ref.viewProps, viewInternalAPI = _ref.viewInternalAPI, viewExternalAPI = _ref.viewExternalAPI, view = _ref.view; // initial props var initialProps = Object.assign({}, viewProps); // current props var currentProps = {}; // we will add those properties to the external API and link them to the viewState addGetSet(mixinConfig, [viewInternalAPI, viewExternalAPI], viewProps); // override rect on internal and external rect getter so it takes in account transforms var getOffset = function getOffset() { return [viewProps['translateX'] || 0, viewProps['translateY'] || 0]; }; var getScale = function getScale() { return [viewProps['scaleX'] || 0, viewProps['scaleY'] || 0]; }; var getRect = function getRect() { return view.rect ? getViewRect(view.rect, view.childViews, getOffset(), getScale()) : null; }; viewInternalAPI.rect = { get: getRect }; viewExternalAPI.rect = { get: getRect }; // apply view props mixinConfig.forEach(function(key) { viewProps[key] = typeof initialProps[key] === 'undefined' ? defaults[key] : initialProps[key]; }); // expose api return { write: function write() { // see if props have changed if (!propsHaveChanged(currentProps, viewProps)) { return; } // moves element to correct position on screen applyStyles(view.element, viewProps); // store new transforms Object.assign(currentProps, Object.assign({}, viewProps)); // no longer busy return true; }, destroy: function destroy() {} }; }; var propsHaveChanged = function propsHaveChanged(currentProps, newProps) { // different amount of keys if (Object.keys(currentProps).length !== Object.keys(newProps).length) { return true; } // lets analyze the individual props for (var prop in newProps) { if (newProps[prop] !== currentProps[prop]) { return true; } } return false; }; var applyStyles = function applyStyles(element, _ref2) { var opacity = _ref2.opacity, perspective = _ref2.perspective, translateX = _ref2.translateX, translateY = _ref2.translateY, scaleX = _ref2.scaleX, scaleY = _ref2.scaleY, rotateX = _ref2.rotateX, rotateY = _ref2.rotateY, rotateZ = _ref2.rotateZ, originX = _ref2.originX, originY = _ref2.originY, width = _ref2.width, height = _ref2.height; var transforms = ''; var styles = ''; // handle transform origin if (isDefined(originX) || isDefined(originY)) { styles += 'transform-origin: ' + (originX || 0) + 'px ' + (originY || 0) + 'px;'; } // transform order is relevant // 0. perspective if (isDefined(perspective)) { transforms += 'perspective(' + perspective + 'px) '; } // 1. translate if (isDefined(translateX) || isDefined(translateY)) { transforms += 'translate3d(' + (translateX || 0) + 'px, ' + (translateY || 0) + 'px, 0) '; } // 2. scale if (isDefined(scaleX) || isDefined(scaleY)) { transforms += 'scale3d(' + (isDefined(scaleX) ? scaleX : 1) + ', ' + (isDefined(scaleY) ? scaleY : 1) + ', 1) '; } // 3. rotate if (isDefined(rotateZ)) { transforms += 'rotateZ(' + rotateZ + 'rad) '; } if (isDefined(rotateX)) { transforms += 'rotateX(' + rotateX + 'rad) '; } if (isDefined(rotateY)) { transforms += 'rotateY(' + rotateY + 'rad) '; } // add transforms if (transforms.length) { styles += 'transform:' + transforms + ';'; } // add opacity if (isDefined(opacity)) { styles += 'opacity:' + opacity + ';'; // if we reach zero, we make the element inaccessible if (opacity === 0) { styles += 'visibility:hidden;'; } // if we're below 100% opacity this element can't be clicked if (opacity < 1) { styles += 'pointer-events:none;'; } } // add height if (isDefined(height)) { styles += 'height:' + height + 'px;'; } // add width if (isDefined(width)) { styles += 'width:' + width + 'px;'; } // apply styles var elementCurrentStyle = element.elementCurrentStyle || ''; // if new styles does not match current styles, lets update! if ( styles.length !== elementCurrentStyle.length || styles !== elementCurrentStyle ) { element.setAttribute('style', styles); // store current styles so we can compare them to new styles later on // _not_ getting the style attribute is faster element.elementCurrentStyle = styles; } }; var Mixins = { styles: styles, listeners: listeners, animations: animations, apis: apis }; var updateRect = function updateRect() { var rect = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var element = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var style = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; if (!element.layoutCalculated) { rect.paddingTop = parseInt(style.paddingTop, 10) || 0; rect.marginTop = parseInt(style.marginTop, 10) || 0; rect.marginRight = parseInt(style.marginRight, 10) || 0; rect.marginBottom = parseInt(style.marginBottom, 10) || 0; rect.marginLeft = parseInt(style.marginLeft, 10) || 0; element.layoutCalculated = true; } rect.left = element.offsetLeft || 0; rect.top = element.offsetTop || 0; rect.width = element.offsetWidth || 0; rect.height = element.offsetHeight || 0; rect.right = rect.left + rect.width; rect.bottom = rect.top + rect.height; rect.scrollTop = element.scrollTop; rect.hidden = element.offsetParent === null; return rect; }; var createView = // default view definition function createView() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$tag = _ref.tag, tag = _ref$tag === void 0 ? 'div' : _ref$tag, _ref$name = _ref.name, name = _ref$name === void 0 ? null : _ref$name, _ref$attributes = _ref.attributes, attributes = _ref$attributes === void 0 ? {} : _ref$attributes, _ref$read = _ref.read, read = _ref$read === void 0 ? function() {} : _ref$read, _ref$write = _ref.write, write = _ref$write === void 0 ? function() {} : _ref$write, _ref$create = _ref.create, create = _ref$create === void 0 ? function() {} : _ref$create, _ref$destroy = _ref.destroy, destroy = _ref$destroy === void 0 ? function() {} : _ref$destroy, _ref$filterFrameActio = _ref.filterFrameActionsForChild, filterFrameActionsForChild = _ref$filterFrameActio === void 0 ? function(child, actions) { return actions; } : _ref$filterFrameActio, _ref$didCreateView = _ref.didCreateView, didCreateView = _ref$didCreateView === void 0 ? function() {} : _ref$didCreateView, _ref$didWriteView = _ref.didWriteView, didWriteView = _ref$didWriteView === void 0 ? function() {} : _ref$didWriteView, _ref$ignoreRect = _ref.ignoreRect, ignoreRect = _ref$ignoreRect === void 0 ? false : _ref$ignoreRect, _ref$ignoreRectUpdate = _ref.ignoreRectUpdate, ignoreRectUpdate = _ref$ignoreRectUpdate === void 0 ? false : _ref$ignoreRectUpdate, _ref$mixins = _ref.mixins, mixins = _ref$mixins === void 0 ? [] : _ref$mixins; return function( // each view requires reference to store store ) { var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; // root element should not be changed var element = createElement(tag, 'filepond--' + name, attributes); // style reference should also not be changed var style = window.getComputedStyle(element, null); // element rectangle var rect = updateRect(); var frameRect = null; // rest state var isResting = false; // pretty self explanatory var childViews = []; // loaded mixins var activeMixins = []; // references to created children var ref = {}; // state used for each instance var state = {}; // list of writers that will be called to update this view var writers = [ write // default writer ]; var readers = [ read // default reader ]; var destroyers = [ destroy // default destroy ]; // core view methods var getElement = function getElement() { return element; }; var getChildViews = function getChildViews() { return childViews.concat(); }; var getReference = function getReference() { return ref; }; var createChildView = function createChildView(store) { return function(view, props) { return view(store, props); }; }; var getRect = function getRect() { if (frameRect) { return frameRect; } frameRect = getViewRect(rect, childViews, [0, 0], [1, 1]); return frameRect; }; var getStyle = function getStyle() { return style; }; /** * Read data from DOM * @private */ var _read = function _read() { frameRect = null; // read child views childViews.forEach(function(child) { return child._read(); }); var shouldUpdate = !(ignoreRectUpdate && rect.width && rect.height); if (shouldUpdate) { updateRect(rect, element, style); } // readers var api = { root: internalAPI, props: props, rect: rect }; readers.forEach(function(reader) { return reader(api); }); }; /** * Write data to DOM * @private */ var _write = function _write(ts, frameActions, shouldOptimize) { // if no actions, we assume that the view is resting var resting = frameActions.length === 0; // writers writers.forEach(function(writer) { var writerResting = writer({ props: props, root: internalAPI, actions: frameActions, timestamp: ts, shouldOptimize: shouldOptimize }); if (writerResting === false) { resting = false; } }); // run mixins activeMixins.forEach(function(mixin) { // if one of the mixins is still busy after write operation, we are not resting var mixinResting = mixin.write(ts); if (mixinResting === false) { resting = false; } }); // updates child views that are currently attached to the DOM childViews .filter(function(child) { return !!child.element.parentNode; }) .forEach(function(child) { // if a child view is not resting, we are not resting var childResting = child._write( ts, filterFrameActionsForChild(child, frameActions), shouldOptimize ); if (!childResting) { resting = false; } }); // append new elements to DOM and update those childViews //.filter(child => !child.element.parentNode) .forEach(function(child, index) { // skip if (child.element.parentNode) { return; } // append to DOM internalAPI.appendChild(child.element, index); // call read (need to know the size of these elements) child._read(); // re-call write child._write( ts, filterFrameActionsForChild(child, frameActions), shouldOptimize ); // we just added somthing to the dom, no rest resting = false; }); // update resting state isResting = resting; didWriteView({ props: props, root: internalAPI, actions: frameActions, timestamp: ts }); // let parent know if we are resting return resting; }; var _destroy = function _destroy() { activeMixins.forEach(function(mixin) { return mixin.destroy(); }); destroyers.forEach(function(destroyer) { destroyer({ root: internalAPI, props: props }); }); childViews.forEach(function(child) { return child._destroy(); }); }; // sharedAPI var sharedAPIDefinition = { element: { get: getElement }, style: { get: getStyle }, childViews: { get: getChildViews } }; // private API definition var internalAPIDefinition = Object.assign({}, sharedAPIDefinition, { rect: { get: getRect }, // access to custom children references ref: { get: getReference }, // dom modifiers is: function is(needle) { return name === needle; }, appendChild: appendChild(element), createChildView: createChildView(store), linkView: function linkView(view) { childViews.push(view); return view; }, unlinkView: function unlinkView(view) { childViews.splice(childViews.indexOf(view), 1); }, appendChildView: appendChildView(element, childViews), removeChildView: removeChildView(element, childViews), registerWriter: function registerWriter(writer) { return writers.push(writer); }, registerReader: function registerReader(reader) { return readers.push(reader); }, registerDestroyer: function registerDestroyer(destroyer) { return destroyers.push(destroyer); }, invalidateLayout: function invalidateLayout() { return (element.layoutCalculated = false); }, // access to data store dispatch: store.dispatch, query: store.query }); // public view API methods var externalAPIDefinition = { element: { get: getElement }, childViews: { get: getChildViews }, rect: { get: getRect }, resting: { get: function get() { return isResting; } }, isRectIgnored: function isRectIgnored() { return ignoreRect; }, _read: _read, _write: _write, _destroy: _destroy }; // mixin API methods var mixinAPIDefinition = Object.assign({}, sharedAPIDefinition, { rect: { get: function get() { return rect; } } }); // add mixin functionality Object.keys(mixins) .sort(function(a, b) { // move styles to the back of the mixin list (so adjustments of other mixins are applied to the props correctly) if (a === 'styles') { return 1; } else if (b === 'styles') { return -1; } return 0; }) .forEach(function(key) { var mixinAPI = Mixins[key]({ mixinConfig: mixins[key], viewProps: props, viewState: state, viewInternalAPI: internalAPIDefinition, viewExternalAPI: externalAPIDefinition, view: createObject(mixinAPIDefinition) }); if (mixinAPI) { activeMixins.push(mixinAPI); } }); // construct private api var internalAPI = createObject(internalAPIDefinition); // create the view create({ root: internalAPI, props: props }); // append created child views to root node var childCount = element.children.length; // need to know the current child count so appending happens in correct order childViews.forEach(function(child, index) { internalAPI.appendChild(child.element, childCount + index); }); // call did create didCreateView(internalAPI); // expose public api return createObject(externalAPIDefinition, props); }; }; var createPainter = function createPainter(read, write) { var fps = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 60; var name = '__framePainter'; // set global painter if (window[name]) { window[name].readers.push(read); window[name].writers.push(write); return; } window[name] = { readers: [read], writers: [write] }; var painter = window[name]; var interval = 1000 / fps; var last = null; var frame = null; var tick = function tick(ts) { // queue next tick frame = window.requestAnimationFrame(tick); // limit fps if (!last) { last = ts; } var delta = ts - last; if (delta <= interval) { // skip frame return; } // align next frame last = ts - (delta % interval); // update view painter.readers.forEach(function(read) { return read(); }); painter.writers.forEach(function(write) { return write(ts); }); }; tick(performance.now()); return { pause: function pause() { window.cancelAnimationFrame(frame); } }; }; var createRoute = function createRoute(routes, fn) { return function(_ref) { var root = _ref.root, props = _ref.props, _ref$actions = _ref.actions, actions = _ref$actions === void 0 ? [] : _ref$actions, timestamp = _ref.timestamp, shouldOptimize = _ref.shouldOptimize; actions .filter(function(action) { return routes[action.type]; }) .forEach(function(action) { return routes[action.type]({ root: root, props: props, action: action.data, timestamp: timestamp, shouldOptimize: shouldOptimize }); }); if (fn) { fn({ root: root, props: props, actions: actions, timestamp: timestamp, shouldOptimize: shouldOptimize }); } }; }; var insertBefore = function insertBefore(newNode, referenceNode) { return referenceNode.parentNode.insertBefore(newNode, referenceNode); }; var insertAfter = function insertAfter(newNode, referenceNode) { return referenceNode.parentNode.insertBefore( newNode, referenceNode.nextSibling ); }; var isArray = function isArray(value) { return Array.isArray(value); }; var isEmpty = function isEmpty(value) { return value == null; }; var trim = function trim(str) { return str.trim(); }; var toString = function toString(value) { return '' + value; }; var toArray = function toArray(value) { var splitter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ','; if (isEmpty(value)) { return []; } if (isArray(value)) { return value; } return toString(value) .split(splitter) .map(trim) .filter(function(str) { return str.length; }); }; var isBoolean = function isBoolean(value) { return typeof value === 'boolean'; }; var toBoolean = function toBoolean(value) { return isBoolean(value) ? value : value === 'true'; }; var isString = function isString(value) { return typeof value === 'string'; }; var toNumber = function toNumber(value) { return isNumber(value) ? value : isString(value) ? toString(value).replace(/[a-z]+/gi, '') : 0; }; var toInt = function toInt(value) { return parseInt(toNumber(value), 10); }; var toFloat = function toFloat(value) { return parseFloat(toNumber(value)); }; var isInt = function isInt(value) { return isNumber(value) && isFinite(value) && Math.floor(value) === value; }; var toBytes = function toBytes(value) { // is in bytes if (isInt(value)) { return value; } // is natural file size var naturalFileSize = toString(value).trim(); // if is value in megabytes if (/MB$/i.test(naturalFileSize)) { naturalFileSize = naturalFileSize.replace(/MB$i/, '').trim(); return toInt(naturalFileSize) * 1000 * 1000; } // if is value in kilobytes if (/KB/i.test(naturalFileSize)) { naturalFileSize = naturalFileSize.replace(/KB$i/, '').trim(); return toInt(naturalFileSize) * 1000; } return toInt(naturalFileSize); }; var isFunction = function isFunction(value) { return typeof value === 'function'; }; var toFunctionReference = function toFunctionReference(string) { var ref = self; var levels = string.split('.'); var level = null; while ((level = levels.shift())) { ref = ref[level]; if (!ref) { return null; } } return ref; }; var methods = { process: 'POST', revert: 'DELETE', fetch: 'GET', restore: 'GET', load: 'GET' }; var createServerAPI = function createServerAPI(outline) { var api = {}; api.url = isString(outline) ? outline : outline.url || ''; api.timeout = outline.timeout ? parseInt(outline.timeout, 10) : 0; forin(methods, function(key) { api[key] = createAction(key, outline[key], methods[key], api.timeout); }); // special treatment for remove api.remove = outline.remove || null; return api; }; var createAction = function createAction(name, outline, method, timeout) { // is explicitely set to null so disable if (outline === null) { return null; } // if is custom function, done! Dev handles everything. if (typeof outline === 'function') { return outline; } // build action object var action = { url: method === 'GET' ? '?' + name + '=' : '', method: method, headers: {}, withCredentials: false, timeout: timeout, onload: null, ondata: null, onerror: null }; // is a single url if (isString(outline)) { action.url = outline; return action; } // overwrite Object.assign(action, outline); // see if should reformat headers; if (isString(action.headers)) { var parts = action.headers.split(/:(.+)/); action.headers = { header: parts[0], value: parts[1] }; } // if is bool withCredentials action.withCredentials = toBoolean(action.withCredentials); return action; }; var toServerAPI = function toServerAPI(value) { return createServerAPI(value); }; var isNull = function isNull(value) { return value === null; }; var isObject = function isObject(value) { return typeof value === 'object' && value !== null; }; var isAPI = function isAPI(value) { return ( isObject(value) && isString(value.url) && isObject(value.process) && isObject(value.revert) && isObject(value.restore) && isObject(value.fetch) ); }; var getType = function getType(value) { if (isArray(value)) { return 'array'; } if (isNull(value)) { return 'null'; } if (isInt(value)) { return 'int'; } if (/^[0-9]+ ?(?:GB|MB|KB)$/gi.test(value)) { return 'bytes'; } if (isAPI(value)) { return 'api'; } return typeof value; }; var replaceSingleQuotes = function replaceSingleQuotes(str) { return str .replace(/{\s*'/g, '{"') .replace(/'\s*}/g, '"}') .replace(/'\s*:/g, '":') .replace(/:\s*'/g, ':"') .replace(/,\s*'/g, ',"') .replace(/'\s*,/g, '",'); }; var conversionTable = { array: toArray, boolean: toBoolean, int: function int(value) { return getType(value) === 'bytes' ? toBytes(value) : toInt(value); }, float: toFloat, bytes: toBytes, string: function string(value) { return isFunction(value) ? value : toString(value); }, serverapi: toServerAPI, object: function object(value) { try { return JSON.parse(replaceSingleQuotes(value)); } catch (e) { return null; } }, function: function _function(value) { return toFunctionReference(value); } }; var convertTo = function convertTo(value, type) { return conversionTable[type](value); }; var getValueByType = function getValueByType( newValue, defaultValue, valueType ) { // can always assign default value if (newValue === defaultValue) { return newValue; } // get the type of the new value var newValueType = getType(newValue); // is valid type? if (newValueType !== valueType) { // is string input, let's attempt to convert var convertedValue = convertTo(newValue, valueType); // what is the type now newValueType = getType(convertedValue); // no valid conversions found if (convertedValue === null) { throw 'Trying to assign value with incorrect type to "' + option + '", allowed type: "' + valueType + '"'; } else { newValue = convertedValue; } } // assign new value return newValue; }; var createOption = function createOption(defaultValue, valueType) { var currentValue = defaultValue; return { enumerable: true, get: function get() { return currentValue; }, set: function set(newValue) { currentValue = getValueByType(newValue, defaultValue, valueType); } }; }; var createOptions = function createOptions(options) { var obj = {}; forin(options, function(prop) { var optionDefinition = options[prop]; obj[prop] = createOption(optionDefinition[0], optionDefinition[1]); }); return createObject(obj); }; var createInitialState = function createInitialState(options) { return { // model items: [], // timeout used for calling update items listUpdateTimeout: null, // queue of items waiting to be processed processingQueue: [], // options options: createOptions(options) }; }; var fromCamels = function fromCamels(string) { var separator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '-'; return string .split(/(?=[A-Z])/) .map(function(part) { return part.toLowerCase(); }) .join(separator); }; var createOptionAPI = function createOptionAPI(store, options) { var obj = {}; forin(options, function(key) { obj[key] = { get: function get() { return store.getState().options[key]; }, set: function set(value) { store.dispatch('SET_' + fromCamels(key, '_').toUpperCase(), { value: value }); } }; }); return obj; }; var createOptionActions = function createOptionActions(options) { return function(dispatch, query, state) { var obj = {}; forin(options, function(key) { var name = fromCamels(key, '_').toUpperCase(); obj['SET_' + name] = function(action) { try { state.options[key] = action.value; } catch (e) {} // nope, failed // we successfully set the value of this option dispatch('DID_SET_' + name, { value: state.options[key] }); }; }); return obj; }; }; var createOptionQueries = function createOptionQueries(options) { return function(state) { var obj = {}; forin(options, function(key) { obj['GET_' + fromCamels(key, '_').toUpperCase()] = function(action) { return state.options[key]; }; }); return obj; }; }; var InteractionMethod = { API: 1, DROP: 2, BROWSE: 3, PASTE: 4, NONE: 5 }; var getUniqueId = function getUniqueId() { return Math.random() .toString(36) .substr(2, 9); }; var arrayRemove = function arrayRemove(arr, index) { return arr.splice(index, 1); }; var on = function on() { var listeners = []; var off = function off(event, cb) { arrayRemove( listeners, listeners.findIndex(function(listener) { return listener.event ===