UNPKG

filepond

Version:

FilePond, Where files go to stretch their bits.

1,481 lines (1,250 loc) 439 kB
/*! * FilePond 4.32.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 (should never block if document is hidden) if (isBlocking && !document.hidden) { 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 IS_BROWSER = (function() { return typeof window !== 'undefined' && typeof window.document !== 'undefined'; })(); var isBrowser = function isBrowser() { return IS_BROWSER; }; var testElement = isBrowser() ? createElement('svg') : {}; var getChildCount = 'children' in testElement ? function(el) { return el.children.length; } : function(el) { return el.childNodes.length; }; 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(ts, skipToEndState) { // 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) || skipToEndState) { 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, skipToEndState) { if (resting || target === null) return; if (start === null) { start = ts; } if (ts - start < delay) return; t = ts - start - delay; if (t >= duration || skipToEndState) { t = 1; p = reverse ? 0 : 1; api.onupdate(p * target); api.oncomplete(p * target); resting = true; } else { p = t / duration; api.onupdate((t >= 0 ? easing(reverse ? 1 - p : p) : 0) * target); } }; // 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, }; }); }); }; // 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; // 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 skipToEndState = document.hidden; var resting = true; animations.forEach(function(animation) { if (!animation.resting) resting = false; animation.interpolate(ts, skipToEndState); }); 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); }; var isDefined = function isDefined(value) { return value != null; }; // 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.style.cssText = styles; // store current styles so we can compare them to new styles later on // _not_ getting the style value 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 = getChildCount(element); // 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); }; }; 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 id = null; var requestTick = null; var cancelTick = null; var setTimerType = function setTimerType() { if (document.hidden) { requestTick = function requestTick() { return window.setTimeout(function() { return tick(performance.now()); }, interval); }; cancelTick = function cancelTick() { return window.clearTimeout(id); }; } else { requestTick = function requestTick() { return window.requestAnimationFrame(tick); }; cancelTick = function cancelTick() { return window.cancelAnimationFrame(id); }; } }; document.addEventListener('visibilitychange', function() { if (cancelTick) cancelTick(); setTimerType(); tick(performance.now()); }); var tick = function tick(ts) { // queue next tick id = requestTick(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); }); }; setTimerType(); tick(performance.now()); return { pause: function pause() { cancelTick(id); }, }; }; 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) { v