filepond
Version:
FilePond, Where files go to stretch their bits.
1,902 lines (1,611 loc) • 274 kB
JavaScript
/*!
* 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 ===