├── .babelrc ├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── Progress.js ├── README.md ├── dist ├── app.12eb2afb.js ├── app.12eb2afb.map ├── app.b185db3b.js ├── app.b185db3b.map ├── fetch-progress.db655106.js ├── fetch-progress.db655106.map └── index.html ├── examples ├── ProgressItem.js ├── app.js └── index.html ├── index.d.ts ├── index.js ├── package-lock.json ├── package.json └── speedometer.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["transform-react-jsx", { "pragma": "h" }], 4 | "transform-class-properties" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [samundrak]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode/ 3 | .cache/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Samundra Khatri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Progress.js: -------------------------------------------------------------------------------- 1 | import speedometer from './speedometer'; 2 | 3 | export default class Progress { 4 | constructor(length, emitDelay = 1000) { 5 | this.length = parseInt(length, 10) || 0; 6 | this.transferred = 0; 7 | this.speed = 0; 8 | this.streamSpeed = speedometer(this.speed || 5000); 9 | this.initial = false; 10 | this.emitDelay = emitDelay; 11 | this.eventStart = 0; 12 | this.percentage = 0; 13 | } 14 | 15 | getRemainingBytes() { 16 | return parseInt(this.length, 10) - parseInt(this.transferred, 10); 17 | } 18 | 19 | getEta() { 20 | return this.length >= this.transferred 21 | ? this.getRemainingBytes() / this.speed * 1000000000 22 | : 0; 23 | } 24 | 25 | flow(chunk, onProgress) { 26 | const chunkLength = chunk.length; 27 | this.transferred += chunkLength; 28 | this.speed = this.streamSpeed(chunkLength); 29 | this.percentage = Math.round(this.transferred / this.length * 100); 30 | if (!this.initial) { 31 | this.eventStart = Date.now(); 32 | this.initial = true; 33 | } 34 | if ( 35 | this.length >= this.transferred || 36 | Date.now() - this.eventStart > this.emitDelay 37 | ) { 38 | this.eventStart = Date.now(); 39 | 40 | const progress = { 41 | total: this.length, 42 | transferred: this.transferred, 43 | speed: this.speed, 44 | eta: this.getEta(), 45 | }; 46 | if (this.length) { 47 | progress.remaining = this.getRemainingBytes(); 48 | progress.percentage = this.percentage; 49 | } 50 | onProgress(progress); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Fetch Progress](https://samundrak.github.io/fetch-progress) 2 | 3 | Progress of response for fetch API. 4 | Get progress report of your response called from fetch like percentage, speed, total, transferred, eta and remaining. 5 | 6 | ## Install 7 | 8 | `npm i fetch-progress` 9 | 10 | ## Usage 11 | 12 | import `fetchProgress` method to your project 13 | 14 | ```js 15 | import fetchProgress from 'fetch-progress 16 | ``` 17 | 18 | Now use `fetchProgress` method on your fetch calls, try to put this before using response. You can 19 | 20 | ```js 21 | fetch(this.props.src) 22 | .then( 23 | fetchProgress({ 24 | // implement onProgress method 25 | onProgress(progress) { 26 | console.log({ progress }); 27 | // A possible progress report you will get 28 | // { 29 | // total: 3333, 30 | // transferred: 3333, 31 | // speed: 3333, 32 | // eta: 33, 33 | // percentage: 33 34 | // remaining: 3333, 35 | // } 36 | }, 37 | }) 38 | ) 39 | ``` 40 | 41 | # Example 42 | 43 | ```js 44 | import fetchProgress from '../index'; 45 | 46 | const self = this; 47 | fetch(this.props.src) 48 | .then( 49 | fetchProgress({ 50 | onProgress(progress) { 51 | self.setState({ progress }); 52 | }, 53 | onError(err) { 54 | console.log(err); 55 | }, 56 | }) 57 | ) 58 | .then(r => r.blob()) 59 | .then(src => { 60 | this.src = URL.createObjectURL(src); 61 | this.setState({ 62 | loaded: true, 63 | }); 64 | }); 65 | ``` 66 | 67 | # Demo 68 | 69 | [Live Demo](https://samundrak.github.io/fetch-progress) 70 | 71 | Clone this repo and run `npm i` and `npm run dev` which will start a server or you can see `examples/` folder in this repo. 72 | -------------------------------------------------------------------------------- /dist/app.12eb2afb.js: -------------------------------------------------------------------------------- 1 | parcelRequire=function(e,r,n){var t="function"==typeof parcelRequire&&parcelRequire,i="function"==typeof require&&require;function u(n,o){if(!r[n]){if(!e[n]){var f="function"==typeof parcelRequire&&parcelRequire;if(!o&&f)return f(n,!0);if(t)return t(n,!0);if(i&&"string"==typeof n)return i(n);var c=new Error("Cannot find module '"+n+"'");throw c.code="MODULE_NOT_FOUND",c}a.resolve=function(r){return e[n][1][r]||r};var l=r[n]=new u.Module(n);e[n][0].call(l.exports,a,l,l.exports)}return r[n].exports;function a(e){return u(a.resolve(e))}}u.isParcelRequire=!0,u.Module=function(e){this.id=e,this.bundle=u,this.exports={}},u.modules=e,u.cache=r,u.parent=t;for(var o=0;o2;)n.push(arguments[s]);for(i&&null!=i.children&&(n.length||n.push(i.children),delete i.children);n.length;)if((a=n.pop())&&void 0!==a.pop)for(s=a.length;s--;)n.push(a[s]);else"boolean"==typeof a&&(a=null),(p="function"!=typeof r)&&(null==a?a="":"number"==typeof a?a=String(a):"string"!=typeof a&&(p=!1)),p&&l?c[c.length-1]+=a:c===o?c=[a]:c.push(a),l=p;var u=new e;return u.nodeName=r,u.children=c,u.attributes=null==i?void 0:i,u.key=null==i?void 0:i.key,void 0!==t.vnode&&t.vnode(u),u}function i(e,t){for(var n in t)e[n]=t[n];return e}var l="function"==typeof Promise?Promise.resolve().then.bind(Promise.resolve()):setTimeout;function a(e,t){return r(e.nodeName,i(i({},e.attributes),t),arguments.length>2?[].slice.call(arguments,2):e.children)}var p=/acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i,s=[];function c(e){!e._dirty&&(e._dirty=!0)&&1==s.push(e)&&(t.debounceRendering||l)(u)}function u(){var e,t=s;for(s=[];e=t.pop();)e._dirty&&A(e)}function f(e,t,n){return"string"==typeof t||"number"==typeof t?void 0!==e.splitText:"string"==typeof t.nodeName?!e._componentConstructor&&d(e,t.nodeName):n||e._componentConstructor===t.nodeName}function d(e,t){return e.normalizedNodeName===t||e.nodeName.toLowerCase()===t.toLowerCase()}function _(e){var t=i({},e.attributes);t.children=e.children;var n=e.nodeName.defaultProps;if(void 0!==n)for(var o in n)void 0===t[o]&&(t[o]=n[o]);return t}function v(e,t){var n=t?document.createElementNS("http://www.w3.org/2000/svg",e):document.createElement(e);return n.normalizedNodeName=e,n}function m(e){var t=e.parentNode;t&&t.removeChild(e)}function h(e,t,n,o,r){if("className"===t&&(t="class"),"key"===t);else if("ref"===t)n&&n(null),o&&o(e);else if("class"!==t||r)if("style"===t){if(o&&"string"!=typeof o&&"string"!=typeof n||(e.style.cssText=o||""),o&&"object"==typeof o){if("string"!=typeof n)for(var i in n)i in o||(e.style[i]="");for(var i in o)e.style[i]="number"==typeof o[i]&&!1===p.test(i)?o[i]+"px":o[i]}}else if("dangerouslySetInnerHTML"===t)o&&(e.innerHTML=o.__html||"");else if("o"==t[0]&&"n"==t[1]){var l=t!==(t=t.replace(/Capture$/,""));t=t.toLowerCase().substring(2),o?n||e.addEventListener(t,y,l):e.removeEventListener(t,y,l),(e._listeners||(e._listeners={}))[t]=o}else if("list"!==t&&"type"!==t&&!r&&t in e)b(e,t,null==o?"":o),null!=o&&!1!==o||e.removeAttribute(t);else{var a=r&&t!==(t=t.replace(/^xlink\:?/,""));null==o||!1===o?a?e.removeAttributeNS("http://www.w3.org/1999/xlink",t.toLowerCase()):e.removeAttribute(t):"function"!=typeof o&&(a?e.setAttributeNS("http://www.w3.org/1999/xlink",t.toLowerCase(),o):e.setAttribute(t,o))}else e.className=o||""}function b(e,t,n){try{e[t]=n}catch(e){}}function y(e){return this._listeners[e.type](t.event&&t.event(e)||e)}var x=[],C=0,g=!1,N=!1;function k(){for(var e;e=x.pop();)t.afterMount&&t.afterMount(e),e.componentDidMount&&e.componentDidMount()}function w(e,t,n,o,r,i){C++||(g=null!=r&&void 0!==r.ownerSVGElement,N=null!=e&&!("__preactattr_"in e));var l=S(e,t,n,o,i);return r&&l.parentNode!==r&&r.appendChild(l),--C||(N=!1,i||k()),l}function S(e,t,n,o,r){var i=e,l=g;if(null!=t&&"boolean"!=typeof t||(t=""),"string"==typeof t||"number"==typeof t)return e&&void 0!==e.splitText&&e.parentNode&&(!e._component||r)?e.nodeValue!=t&&(e.nodeValue=t):(i=document.createTextNode(t),e&&(e.parentNode&&e.parentNode.replaceChild(i,e),L(e,!0))),i.__preactattr_=!0,i;var a=t.nodeName;if("function"==typeof a)return D(e,t,n,o);if(g="svg"===a||"foreignObject"!==a&&g,a=String(a),(!e||!d(e,a))&&(i=v(a,g),e)){for(;e.firstChild;)i.appendChild(e.firstChild);e.parentNode&&e.parentNode.replaceChild(i,e),L(e,!0)}var p=i.firstChild,s=i.__preactattr_,c=t.children;if(null==s){s=i.__preactattr_={};for(var u=i.attributes,f=u.length;f--;)s[u[f].name]=u[f].value}return!N&&c&&1===c.length&&"string"==typeof c[0]&&null!=p&&void 0!==p.splitText&&null==p.nextSibling?p.nodeValue!=c[0]&&(p.nodeValue=c[0]):(c&&c.length||null!=p)&&U(i,c,n,o,N||null!=s.dangerouslySetInnerHTML),P(i,t.attributes,s),g=l,i}function U(e,t,n,o,r){var i,l,a,p,s,c=e.childNodes,u=[],d={},_=0,v=0,h=c.length,b=0,y=t?t.length:0;if(0!==h)for(var x=0;xu&&(l=u),a=e;l--;)o===u&&(o=0),f[o]=f[0===o?u-1:o-1],o++;n&&(f[o-1]+=n);var s=f[o-1],v=f.length1&&void 0!==arguments[1]?arguments[1]:1e3;r(this,t),this.length=parseInt(e,10)||0,this.transferred=0,this.speed=0,this.streamSpeed=(0,n.default)(this.speed||5e3),this.initial=!1,this.emitDelay=i,this.eventStart=0,this.percentage=0}return e(t,[{key:"getRemainingBytes",value:function(){return parseInt(this.length,10)-parseInt(this.transferred,10)}},{key:"getEta",value:function(){return this.length>=this.transferred?this.getRemainingBytes()/this.speed*1e9:0}},{key:"flow",value:function(e,t){var n=e.length;if(this.transferred+=n,this.speed=this.streamSpeed(n),this.percentage=Math.round(this.transferred/this.length*100),this.initial||(this.eventStart=Date.now(),this.initial=!0),this.length>=this.transferred||Date.now()-this.eventStart>this.emitDelay){this.eventStart=Date.now();var i={total:this.length,transferred:this.transferred,speed:this.speed,eta:this.getEta()};this.length&&(i.remaining=this.getRemainingBytes(),i.percentage=this.percentage),t(i)}}}]),t}();exports.default=s; 7 | },{"./speedometer":13}],10:[function(require,module,exports) { 8 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isFetchProgressSupported=r,exports.default=function(e){var t=e.defaultSize,o=void 0===t?0:t,u=e.emitDelay,d=void 0===u?10:u,i=e.onProgress,a=void 0===i?function(){return null}:i,s=e.onComplete,f=void 0===s?function(){return null}:s;return function(e){if(!r())return e;var t=e.body,u=e.headers,i=u.get("content-length")||o,s=new n.default(i,d),l=t.getReader(),c=new ReadableStream({start:function(e){!function n(){l.read().then(function(t){var r=t.done,o=t.value;if(r)return f({}),void e.close();o&&s.flow(o,a),e.enqueue(o),n()})}()}});return new Response(c,{headers:u})}};var e=require("./Progress"),n=t(e);function t(e){return e&&e.__esModule?e:{default:e}}function r(){return"undefined"!=typeof Response&&"undefined"!=typeof ReadableStream} 9 | },{"./Progress":12}],6:[function(require,module,exports) { 10 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=function(){function e(e,t){for(var r=0;rHello!`\n *\n * can be constructed using this function as:\n *\n * `h('div', { id: 'foo', name : 'bar' }, 'Hello!');`\n *\n * @param {string} nodeName\tAn element name. Ex: `div`, `a`, `span`, etc.\n * @param {Object} attributes\tAny attributes/props to set on the created element.\n * @param rest\t\t\tAdditional arguments are taken to be children to append. Can be infinitely nested Arrays.\n *\n * @public\n */\nfunction h(nodeName, attributes) {\n\tvar children = EMPTY_CHILDREN,\n\t lastSimple,\n\t child,\n\t simple,\n\t i;\n\tfor (i = arguments.length; i-- > 2;) {\n\t\tstack.push(arguments[i]);\n\t}\n\tif (attributes && attributes.children != null) {\n\t\tif (!stack.length) stack.push(attributes.children);\n\t\tdelete attributes.children;\n\t}\n\twhile (stack.length) {\n\t\tif ((child = stack.pop()) && child.pop !== undefined) {\n\t\t\tfor (i = child.length; i--;) {\n\t\t\t\tstack.push(child[i]);\n\t\t\t}\n\t\t} else {\n\t\t\tif (typeof child === 'boolean') child = null;\n\n\t\t\tif (simple = typeof nodeName !== 'function') {\n\t\t\t\tif (child == null) child = '';else if (typeof child === 'number') child = String(child);else if (typeof child !== 'string') simple = false;\n\t\t\t}\n\n\t\t\tif (simple && lastSimple) {\n\t\t\t\tchildren[children.length - 1] += child;\n\t\t\t} else if (children === EMPTY_CHILDREN) {\n\t\t\t\tchildren = [child];\n\t\t\t} else {\n\t\t\t\tchildren.push(child);\n\t\t\t}\n\n\t\t\tlastSimple = simple;\n\t\t}\n\t}\n\n\tvar p = new VNode();\n\tp.nodeName = nodeName;\n\tp.children = children;\n\tp.attributes = attributes == null ? undefined : attributes;\n\tp.key = attributes == null ? undefined : attributes.key;\n\n\t// if a \"vnode hook\" is defined, pass every created VNode to it\n\tif (options.vnode !== undefined) options.vnode(p);\n\n\treturn p;\n}\n\n/**\n * Copy all properties from `props` onto `obj`.\n * @param {Object} obj\t\tObject onto which properties should be copied.\n * @param {Object} props\tObject from which to copy properties.\n * @returns obj\n * @private\n */\nfunction extend(obj, props) {\n for (var i in props) {\n obj[i] = props[i];\n }return obj;\n}\n\n/**\n * Call a function asynchronously, as soon as possible. Makes\n * use of HTML Promise to schedule the callback if available,\n * otherwise falling back to `setTimeout` (mainly for IE<11).\n *\n * @param {Function} callback\n */\nvar defer = typeof Promise == 'function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;\n\n/**\n * Clones the given VNode, optionally adding attributes/props and replacing its children.\n * @param {VNode} vnode\t\tThe virutal DOM element to clone\n * @param {Object} props\tAttributes/props to add when cloning\n * @param {VNode} rest\t\tAny additional arguments will be used as replacement children.\n */\nfunction cloneElement(vnode, props) {\n return h(vnode.nodeName, extend(extend({}, vnode.attributes), props), arguments.length > 2 ? [].slice.call(arguments, 2) : vnode.children);\n}\n\n// DOM properties that should NOT have \"px\" added when numeric\nvar IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i;\n\n/** Managed queue of dirty components to be re-rendered */\n\nvar items = [];\n\nfunction enqueueRender(component) {\n\tif (!component._dirty && (component._dirty = true) && items.push(component) == 1) {\n\t\t(options.debounceRendering || defer)(rerender);\n\t}\n}\n\nfunction rerender() {\n\tvar p,\n\t list = items;\n\titems = [];\n\twhile (p = list.pop()) {\n\t\tif (p._dirty) renderComponent(p);\n\t}\n}\n\n/**\n * Check if two nodes are equivalent.\n *\n * @param {Node} node\t\t\tDOM Node to compare\n * @param {VNode} vnode\t\t\tVirtual DOM node to compare\n * @param {boolean} [hyrdating=false]\tIf true, ignores component constructors when comparing.\n * @private\n */\nfunction isSameNodeType(node, vnode, hydrating) {\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return node.splitText !== undefined;\n }\n if (typeof vnode.nodeName === 'string') {\n return !node._componentConstructor && isNamedNode(node, vnode.nodeName);\n }\n return hydrating || node._componentConstructor === vnode.nodeName;\n}\n\n/**\n * Check if an Element has a given nodeName, case-insensitively.\n *\n * @param {Element} node\tA DOM Element to inspect the name of.\n * @param {String} nodeName\tUnnormalized name to compare against.\n */\nfunction isNamedNode(node, nodeName) {\n return node.normalizedNodeName === nodeName || node.nodeName.toLowerCase() === nodeName.toLowerCase();\n}\n\n/**\n * Reconstruct Component-style `props` from a VNode.\n * Ensures default/fallback values from `defaultProps`:\n * Own-properties of `defaultProps` not present in `vnode.attributes` are added.\n *\n * @param {VNode} vnode\n * @returns {Object} props\n */\nfunction getNodeProps(vnode) {\n var props = extend({}, vnode.attributes);\n props.children = vnode.children;\n\n var defaultProps = vnode.nodeName.defaultProps;\n if (defaultProps !== undefined) {\n for (var i in defaultProps) {\n if (props[i] === undefined) {\n props[i] = defaultProps[i];\n }\n }\n }\n\n return props;\n}\n\n/** Create an element with the given nodeName.\n *\t@param {String} nodeName\n *\t@param {Boolean} [isSvg=false]\tIf `true`, creates an element within the SVG namespace.\n *\t@returns {Element} node\n */\nfunction createNode(nodeName, isSvg) {\n\tvar node = isSvg ? document.createElementNS('http://www.w3.org/2000/svg', nodeName) : document.createElement(nodeName);\n\tnode.normalizedNodeName = nodeName;\n\treturn node;\n}\n\n/** Remove a child node from its parent if attached.\n *\t@param {Element} node\t\tThe node to remove\n */\nfunction removeNode(node) {\n\tvar parentNode = node.parentNode;\n\tif (parentNode) parentNode.removeChild(node);\n}\n\n/** Set a named attribute on the given Node, with special behavior for some names and event handlers.\n *\tIf `value` is `null`, the attribute/handler will be removed.\n *\t@param {Element} node\tAn element to mutate\n *\t@param {string} name\tThe name/key to set, such as an event or attribute name\n *\t@param {any} old\tThe last value that was set for this name/node pair\n *\t@param {any} value\tAn attribute value, such as a function to be used as an event handler\n *\t@param {Boolean} isSvg\tAre we currently diffing inside an svg?\n *\t@private\n */\nfunction setAccessor(node, name, old, value, isSvg) {\n\tif (name === 'className') name = 'class';\n\n\tif (name === 'key') {\n\t\t// ignore\n\t} else if (name === 'ref') {\n\t\tif (old) old(null);\n\t\tif (value) value(node);\n\t} else if (name === 'class' && !isSvg) {\n\t\tnode.className = value || '';\n\t} else if (name === 'style') {\n\t\tif (!value || typeof value === 'string' || typeof old === 'string') {\n\t\t\tnode.style.cssText = value || '';\n\t\t}\n\t\tif (value && typeof value === 'object') {\n\t\t\tif (typeof old !== 'string') {\n\t\t\t\tfor (var i in old) {\n\t\t\t\t\tif (!(i in value)) node.style[i] = '';\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (var i in value) {\n\t\t\t\tnode.style[i] = typeof value[i] === 'number' && IS_NON_DIMENSIONAL.test(i) === false ? value[i] + 'px' : value[i];\n\t\t\t}\n\t\t}\n\t} else if (name === 'dangerouslySetInnerHTML') {\n\t\tif (value) node.innerHTML = value.__html || '';\n\t} else if (name[0] == 'o' && name[1] == 'n') {\n\t\tvar useCapture = name !== (name = name.replace(/Capture$/, ''));\n\t\tname = name.toLowerCase().substring(2);\n\t\tif (value) {\n\t\t\tif (!old) node.addEventListener(name, eventProxy, useCapture);\n\t\t} else {\n\t\t\tnode.removeEventListener(name, eventProxy, useCapture);\n\t\t}\n\t\t(node._listeners || (node._listeners = {}))[name] = value;\n\t} else if (name !== 'list' && name !== 'type' && !isSvg && name in node) {\n\t\tsetProperty(node, name, value == null ? '' : value);\n\t\tif (value == null || value === false) node.removeAttribute(name);\n\t} else {\n\t\tvar ns = isSvg && name !== (name = name.replace(/^xlink\\:?/, ''));\n\t\tif (value == null || value === false) {\n\t\t\tif (ns) node.removeAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase());else node.removeAttribute(name);\n\t\t} else if (typeof value !== 'function') {\n\t\t\tif (ns) node.setAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase(), value);else node.setAttribute(name, value);\n\t\t}\n\t}\n}\n\n/** Attempt to set a DOM property to the given value.\n *\tIE & FF throw for certain property-value combinations.\n */\nfunction setProperty(node, name, value) {\n\ttry {\n\t\tnode[name] = value;\n\t} catch (e) {}\n}\n\n/** Proxy an event to hooked event handlers\n *\t@private\n */\nfunction eventProxy(e) {\n\treturn this._listeners[e.type](options.event && options.event(e) || e);\n}\n\n/** Queue of components that have been mounted and are awaiting componentDidMount */\nvar mounts = [];\n\n/** Diff recursion count, used to track the end of the diff cycle. */\nvar diffLevel = 0;\n\n/** Global flag indicating if the diff is currently within an SVG */\nvar isSvgMode = false;\n\n/** Global flag indicating if the diff is performing hydration */\nvar hydrating = false;\n\n/** Invoke queued componentDidMount lifecycle methods */\nfunction flushMounts() {\n\tvar c;\n\twhile (c = mounts.pop()) {\n\t\tif (options.afterMount) options.afterMount(c);\n\t\tif (c.componentDidMount) c.componentDidMount();\n\t}\n}\n\n/** Apply differences in a given vnode (and it's deep children) to a real DOM Node.\n *\t@param {Element} [dom=null]\t\tA DOM node to mutate into the shape of the `vnode`\n *\t@param {VNode} vnode\t\t\tA VNode (with descendants forming a tree) representing the desired DOM structure\n *\t@returns {Element} dom\t\t\tThe created/mutated element\n *\t@private\n */\nfunction diff(dom, vnode, context, mountAll, parent, componentRoot) {\n\t// diffLevel having been 0 here indicates initial entry into the diff (not a subdiff)\n\tif (!diffLevel++) {\n\t\t// when first starting the diff, check if we're diffing an SVG or within an SVG\n\t\tisSvgMode = parent != null && parent.ownerSVGElement !== undefined;\n\n\t\t// hydration is indicated by the existing element to be diffed not having a prop cache\n\t\thydrating = dom != null && !('__preactattr_' in dom);\n\t}\n\n\tvar ret = idiff(dom, vnode, context, mountAll, componentRoot);\n\n\t// append the element if its a new parent\n\tif (parent && ret.parentNode !== parent) parent.appendChild(ret);\n\n\t// diffLevel being reduced to 0 means we're exiting the diff\n\tif (! --diffLevel) {\n\t\thydrating = false;\n\t\t// invoke queued componentDidMount lifecycle methods\n\t\tif (!componentRoot) flushMounts();\n\t}\n\n\treturn ret;\n}\n\n/** Internals of `diff()`, separated to allow bypassing diffLevel / mount flushing. */\nfunction idiff(dom, vnode, context, mountAll, componentRoot) {\n\tvar out = dom,\n\t prevSvgMode = isSvgMode;\n\n\t// empty values (null, undefined, booleans) render as empty Text nodes\n\tif (vnode == null || typeof vnode === 'boolean') vnode = '';\n\n\t// Fast case: Strings & Numbers create/update Text nodes.\n\tif (typeof vnode === 'string' || typeof vnode === 'number') {\n\n\t\t// update if it's already a Text node:\n\t\tif (dom && dom.splitText !== undefined && dom.parentNode && (!dom._component || componentRoot)) {\n\t\t\t/* istanbul ignore if */ /* Browser quirk that can't be covered: https://github.com/developit/preact/commit/fd4f21f5c45dfd75151bd27b4c217d8003aa5eb9 */\n\t\t\tif (dom.nodeValue != vnode) {\n\t\t\t\tdom.nodeValue = vnode;\n\t\t\t}\n\t\t} else {\n\t\t\t// it wasn't a Text node: replace it with one and recycle the old Element\n\t\t\tout = document.createTextNode(vnode);\n\t\t\tif (dom) {\n\t\t\t\tif (dom.parentNode) dom.parentNode.replaceChild(out, dom);\n\t\t\t\trecollectNodeTree(dom, true);\n\t\t\t}\n\t\t}\n\n\t\tout['__preactattr_'] = true;\n\n\t\treturn out;\n\t}\n\n\t// If the VNode represents a Component, perform a component diff:\n\tvar vnodeName = vnode.nodeName;\n\tif (typeof vnodeName === 'function') {\n\t\treturn buildComponentFromVNode(dom, vnode, context, mountAll);\n\t}\n\n\t// Tracks entering and exiting SVG namespace when descending through the tree.\n\tisSvgMode = vnodeName === 'svg' ? true : vnodeName === 'foreignObject' ? false : isSvgMode;\n\n\t// If there's no existing element or it's the wrong type, create a new one:\n\tvnodeName = String(vnodeName);\n\tif (!dom || !isNamedNode(dom, vnodeName)) {\n\t\tout = createNode(vnodeName, isSvgMode);\n\n\t\tif (dom) {\n\t\t\t// move children into the replacement node\n\t\t\twhile (dom.firstChild) {\n\t\t\t\tout.appendChild(dom.firstChild);\n\t\t\t} // if the previous Element was mounted into the DOM, replace it inline\n\t\t\tif (dom.parentNode) dom.parentNode.replaceChild(out, dom);\n\n\t\t\t// recycle the old element (skips non-Element node types)\n\t\t\trecollectNodeTree(dom, true);\n\t\t}\n\t}\n\n\tvar fc = out.firstChild,\n\t props = out['__preactattr_'],\n\t vchildren = vnode.children;\n\n\tif (props == null) {\n\t\tprops = out['__preactattr_'] = {};\n\t\tfor (var a = out.attributes, i = a.length; i--;) {\n\t\t\tprops[a[i].name] = a[i].value;\n\t\t}\n\t}\n\n\t// Optimization: fast-path for elements containing a single TextNode:\n\tif (!hydrating && vchildren && vchildren.length === 1 && typeof vchildren[0] === 'string' && fc != null && fc.splitText !== undefined && fc.nextSibling == null) {\n\t\tif (fc.nodeValue != vchildren[0]) {\n\t\t\tfc.nodeValue = vchildren[0];\n\t\t}\n\t}\n\t// otherwise, if there are existing or new children, diff them:\n\telse if (vchildren && vchildren.length || fc != null) {\n\t\t\tinnerDiffNode(out, vchildren, context, mountAll, hydrating || props.dangerouslySetInnerHTML != null);\n\t\t}\n\n\t// Apply attributes/props from VNode to the DOM Element:\n\tdiffAttributes(out, vnode.attributes, props);\n\n\t// restore previous SVG mode: (in case we're exiting an SVG namespace)\n\tisSvgMode = prevSvgMode;\n\n\treturn out;\n}\n\n/** Apply child and attribute changes between a VNode and a DOM Node to the DOM.\n *\t@param {Element} dom\t\t\tElement whose children should be compared & mutated\n *\t@param {Array} vchildren\t\tArray of VNodes to compare to `dom.childNodes`\n *\t@param {Object} context\t\t\tImplicitly descendant context object (from most recent `getChildContext()`)\n *\t@param {Boolean} mountAll\n *\t@param {Boolean} isHydrating\tIf `true`, consumes externally created elements similar to hydration\n */\nfunction innerDiffNode(dom, vchildren, context, mountAll, isHydrating) {\n\tvar originalChildren = dom.childNodes,\n\t children = [],\n\t keyed = {},\n\t keyedLen = 0,\n\t min = 0,\n\t len = originalChildren.length,\n\t childrenLen = 0,\n\t vlen = vchildren ? vchildren.length : 0,\n\t j,\n\t c,\n\t f,\n\t vchild,\n\t child;\n\n\t// Build up a map of keyed children and an Array of unkeyed children:\n\tif (len !== 0) {\n\t\tfor (var i = 0; i < len; i++) {\n\t\t\tvar _child = originalChildren[i],\n\t\t\t props = _child['__preactattr_'],\n\t\t\t key = vlen && props ? _child._component ? _child._component.__key : props.key : null;\n\t\t\tif (key != null) {\n\t\t\t\tkeyedLen++;\n\t\t\t\tkeyed[key] = _child;\n\t\t\t} else if (props || (_child.splitText !== undefined ? isHydrating ? _child.nodeValue.trim() : true : isHydrating)) {\n\t\t\t\tchildren[childrenLen++] = _child;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (vlen !== 0) {\n\t\tfor (var i = 0; i < vlen; i++) {\n\t\t\tvchild = vchildren[i];\n\t\t\tchild = null;\n\n\t\t\t// attempt to find a node based on key matching\n\t\t\tvar key = vchild.key;\n\t\t\tif (key != null) {\n\t\t\t\tif (keyedLen && keyed[key] !== undefined) {\n\t\t\t\t\tchild = keyed[key];\n\t\t\t\t\tkeyed[key] = undefined;\n\t\t\t\t\tkeyedLen--;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// attempt to pluck a node of the same type from the existing children\n\t\t\telse if (!child && min < childrenLen) {\n\t\t\t\t\tfor (j = min; j < childrenLen; j++) {\n\t\t\t\t\t\tif (children[j] !== undefined && isSameNodeType(c = children[j], vchild, isHydrating)) {\n\t\t\t\t\t\t\tchild = c;\n\t\t\t\t\t\t\tchildren[j] = undefined;\n\t\t\t\t\t\t\tif (j === childrenLen - 1) childrenLen--;\n\t\t\t\t\t\t\tif (j === min) min++;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// morph the matched/found/created DOM child to match vchild (deep)\n\t\t\tchild = idiff(child, vchild, context, mountAll);\n\n\t\t\tf = originalChildren[i];\n\t\t\tif (child && child !== dom && child !== f) {\n\t\t\t\tif (f == null) {\n\t\t\t\t\tdom.appendChild(child);\n\t\t\t\t} else if (child === f.nextSibling) {\n\t\t\t\t\tremoveNode(f);\n\t\t\t\t} else {\n\t\t\t\t\tdom.insertBefore(child, f);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// remove unused keyed children:\n\tif (keyedLen) {\n\t\tfor (var i in keyed) {\n\t\t\tif (keyed[i] !== undefined) recollectNodeTree(keyed[i], false);\n\t\t}\n\t}\n\n\t// remove orphaned unkeyed children:\n\twhile (min <= childrenLen) {\n\t\tif ((child = children[childrenLen--]) !== undefined) recollectNodeTree(child, false);\n\t}\n}\n\n/** Recursively recycle (or just unmount) a node and its descendants.\n *\t@param {Node} node\t\t\t\t\t\tDOM node to start unmount/removal from\n *\t@param {Boolean} [unmountOnly=false]\tIf `true`, only triggers unmount lifecycle, skips removal\n */\nfunction recollectNodeTree(node, unmountOnly) {\n\tvar component = node._component;\n\tif (component) {\n\t\t// if node is owned by a Component, unmount that component (ends up recursing back here)\n\t\tunmountComponent(component);\n\t} else {\n\t\t// If the node's VNode had a ref function, invoke it with null here.\n\t\t// (this is part of the React spec, and smart for unsetting references)\n\t\tif (node['__preactattr_'] != null && node['__preactattr_'].ref) node['__preactattr_'].ref(null);\n\n\t\tif (unmountOnly === false || node['__preactattr_'] == null) {\n\t\t\tremoveNode(node);\n\t\t}\n\n\t\tremoveChildren(node);\n\t}\n}\n\n/** Recollect/unmount all children.\n *\t- we use .lastChild here because it causes less reflow than .firstChild\n *\t- it's also cheaper than accessing the .childNodes Live NodeList\n */\nfunction removeChildren(node) {\n\tnode = node.lastChild;\n\twhile (node) {\n\t\tvar next = node.previousSibling;\n\t\trecollectNodeTree(node, true);\n\t\tnode = next;\n\t}\n}\n\n/** Apply differences in attributes from a VNode to the given DOM Element.\n *\t@param {Element} dom\t\tElement with attributes to diff `attrs` against\n *\t@param {Object} attrs\t\tThe desired end-state key-value attribute pairs\n *\t@param {Object} old\t\t\tCurrent/previous attributes (from previous VNode or element's prop cache)\n */\nfunction diffAttributes(dom, attrs, old) {\n\tvar name;\n\n\t// remove attributes no longer present on the vnode by setting them to undefined\n\tfor (name in old) {\n\t\tif (!(attrs && attrs[name] != null) && old[name] != null) {\n\t\t\tsetAccessor(dom, name, old[name], old[name] = undefined, isSvgMode);\n\t\t}\n\t}\n\n\t// add new & update changed attributes\n\tfor (name in attrs) {\n\t\tif (name !== 'children' && name !== 'innerHTML' && (!(name in old) || attrs[name] !== (name === 'value' || name === 'checked' ? dom[name] : old[name]))) {\n\t\t\tsetAccessor(dom, name, old[name], old[name] = attrs[name], isSvgMode);\n\t\t}\n\t}\n}\n\n/** Retains a pool of Components for re-use, keyed on component name.\n *\tNote: since component names are not unique or even necessarily available, these are primarily a form of sharding.\n *\t@private\n */\nvar components = {};\n\n/** Reclaim a component for later re-use by the recycler. */\nfunction collectComponent(component) {\n\tvar name = component.constructor.name;\n\t(components[name] || (components[name] = [])).push(component);\n}\n\n/** Create a component. Normalizes differences between PFC's and classful Components. */\nfunction createComponent(Ctor, props, context) {\n\tvar list = components[Ctor.name],\n\t inst;\n\n\tif (Ctor.prototype && Ctor.prototype.render) {\n\t\tinst = new Ctor(props, context);\n\t\tComponent.call(inst, props, context);\n\t} else {\n\t\tinst = new Component(props, context);\n\t\tinst.constructor = Ctor;\n\t\tinst.render = doRender;\n\t}\n\n\tif (list) {\n\t\tfor (var i = list.length; i--;) {\n\t\t\tif (list[i].constructor === Ctor) {\n\t\t\t\tinst.nextBase = list[i].nextBase;\n\t\t\t\tlist.splice(i, 1);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn inst;\n}\n\n/** The `.render()` method for a PFC backing instance. */\nfunction doRender(props, state, context) {\n\treturn this.constructor(props, context);\n}\n\n/** Set a component's `props` (generally derived from JSX attributes).\n *\t@param {Object} props\n *\t@param {Object} [opts]\n *\t@param {boolean} [opts.renderSync=false]\tIf `true` and {@link options.syncComponentUpdates} is `true`, triggers synchronous rendering.\n *\t@param {boolean} [opts.render=true]\t\t\tIf `false`, no render will be triggered.\n */\nfunction setComponentProps(component, props, opts, context, mountAll) {\n\tif (component._disable) return;\n\tcomponent._disable = true;\n\n\tif (component.__ref = props.ref) delete props.ref;\n\tif (component.__key = props.key) delete props.key;\n\n\tif (!component.base || mountAll) {\n\t\tif (component.componentWillMount) component.componentWillMount();\n\t} else if (component.componentWillReceiveProps) {\n\t\tcomponent.componentWillReceiveProps(props, context);\n\t}\n\n\tif (context && context !== component.context) {\n\t\tif (!component.prevContext) component.prevContext = component.context;\n\t\tcomponent.context = context;\n\t}\n\n\tif (!component.prevProps) component.prevProps = component.props;\n\tcomponent.props = props;\n\n\tcomponent._disable = false;\n\n\tif (opts !== 0) {\n\t\tif (opts === 1 || options.syncComponentUpdates !== false || !component.base) {\n\t\t\trenderComponent(component, 1, mountAll);\n\t\t} else {\n\t\t\tenqueueRender(component);\n\t\t}\n\t}\n\n\tif (component.__ref) component.__ref(component);\n}\n\n/** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account.\n *\t@param {Component} component\n *\t@param {Object} [opts]\n *\t@param {boolean} [opts.build=false]\t\tIf `true`, component will build and store a DOM node if not already associated with one.\n *\t@private\n */\nfunction renderComponent(component, opts, mountAll, isChild) {\n\tif (component._disable) return;\n\n\tvar props = component.props,\n\t state = component.state,\n\t context = component.context,\n\t previousProps = component.prevProps || props,\n\t previousState = component.prevState || state,\n\t previousContext = component.prevContext || context,\n\t isUpdate = component.base,\n\t nextBase = component.nextBase,\n\t initialBase = isUpdate || nextBase,\n\t initialChildComponent = component._component,\n\t skip = false,\n\t rendered,\n\t inst,\n\t cbase;\n\n\t// if updating\n\tif (isUpdate) {\n\t\tcomponent.props = previousProps;\n\t\tcomponent.state = previousState;\n\t\tcomponent.context = previousContext;\n\t\tif (opts !== 2 && component.shouldComponentUpdate && component.shouldComponentUpdate(props, state, context) === false) {\n\t\t\tskip = true;\n\t\t} else if (component.componentWillUpdate) {\n\t\t\tcomponent.componentWillUpdate(props, state, context);\n\t\t}\n\t\tcomponent.props = props;\n\t\tcomponent.state = state;\n\t\tcomponent.context = context;\n\t}\n\n\tcomponent.prevProps = component.prevState = component.prevContext = component.nextBase = null;\n\tcomponent._dirty = false;\n\n\tif (!skip) {\n\t\trendered = component.render(props, state, context);\n\n\t\t// context to pass to the child, can be updated via (grand-)parent component\n\t\tif (component.getChildContext) {\n\t\t\tcontext = extend(extend({}, context), component.getChildContext());\n\t\t}\n\n\t\tvar childComponent = rendered && rendered.nodeName,\n\t\t toUnmount,\n\t\t base;\n\n\t\tif (typeof childComponent === 'function') {\n\t\t\t// set up high order component link\n\n\t\t\tvar childProps = getNodeProps(rendered);\n\t\t\tinst = initialChildComponent;\n\n\t\t\tif (inst && inst.constructor === childComponent && childProps.key == inst.__key) {\n\t\t\t\tsetComponentProps(inst, childProps, 1, context, false);\n\t\t\t} else {\n\t\t\t\ttoUnmount = inst;\n\n\t\t\t\tcomponent._component = inst = createComponent(childComponent, childProps, context);\n\t\t\t\tinst.nextBase = inst.nextBase || nextBase;\n\t\t\t\tinst._parentComponent = component;\n\t\t\t\tsetComponentProps(inst, childProps, 0, context, false);\n\t\t\t\trenderComponent(inst, 1, mountAll, true);\n\t\t\t}\n\n\t\t\tbase = inst.base;\n\t\t} else {\n\t\t\tcbase = initialBase;\n\n\t\t\t// destroy high order component link\n\t\t\ttoUnmount = initialChildComponent;\n\t\t\tif (toUnmount) {\n\t\t\t\tcbase = component._component = null;\n\t\t\t}\n\n\t\t\tif (initialBase || opts === 1) {\n\t\t\t\tif (cbase) cbase._component = null;\n\t\t\t\tbase = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true);\n\t\t\t}\n\t\t}\n\n\t\tif (initialBase && base !== initialBase && inst !== initialChildComponent) {\n\t\t\tvar baseParent = initialBase.parentNode;\n\t\t\tif (baseParent && base !== baseParent) {\n\t\t\t\tbaseParent.replaceChild(base, initialBase);\n\n\t\t\t\tif (!toUnmount) {\n\t\t\t\t\tinitialBase._component = null;\n\t\t\t\t\trecollectNodeTree(initialBase, false);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (toUnmount) {\n\t\t\tunmountComponent(toUnmount);\n\t\t}\n\n\t\tcomponent.base = base;\n\t\tif (base && !isChild) {\n\t\t\tvar componentRef = component,\n\t\t\t t = component;\n\t\t\twhile (t = t._parentComponent) {\n\t\t\t\t(componentRef = t).base = base;\n\t\t\t}\n\t\t\tbase._component = componentRef;\n\t\t\tbase._componentConstructor = componentRef.constructor;\n\t\t}\n\t}\n\n\tif (!isUpdate || mountAll) {\n\t\tmounts.unshift(component);\n\t} else if (!skip) {\n\t\t// Ensure that pending componentDidMount() hooks of child components\n\t\t// are called before the componentDidUpdate() hook in the parent.\n\t\t// Note: disabled as it causes duplicate hooks, see https://github.com/developit/preact/issues/750\n\t\t// flushMounts();\n\n\t\tif (component.componentDidUpdate) {\n\t\t\tcomponent.componentDidUpdate(previousProps, previousState, previousContext);\n\t\t}\n\t\tif (options.afterUpdate) options.afterUpdate(component);\n\t}\n\n\tif (component._renderCallbacks != null) {\n\t\twhile (component._renderCallbacks.length) {\n\t\t\tcomponent._renderCallbacks.pop().call(component);\n\t\t}\n\t}\n\n\tif (!diffLevel && !isChild) flushMounts();\n}\n\n/** Apply the Component referenced by a VNode to the DOM.\n *\t@param {Element} dom\tThe DOM node to mutate\n *\t@param {VNode} vnode\tA Component-referencing VNode\n *\t@returns {Element} dom\tThe created/mutated element\n *\t@private\n */\nfunction buildComponentFromVNode(dom, vnode, context, mountAll) {\n\tvar c = dom && dom._component,\n\t originalComponent = c,\n\t oldDom = dom,\n\t isDirectOwner = c && dom._componentConstructor === vnode.nodeName,\n\t isOwner = isDirectOwner,\n\t props = getNodeProps(vnode);\n\twhile (c && !isOwner && (c = c._parentComponent)) {\n\t\tisOwner = c.constructor === vnode.nodeName;\n\t}\n\n\tif (c && isOwner && (!mountAll || c._component)) {\n\t\tsetComponentProps(c, props, 3, context, mountAll);\n\t\tdom = c.base;\n\t} else {\n\t\tif (originalComponent && !isDirectOwner) {\n\t\t\tunmountComponent(originalComponent);\n\t\t\tdom = oldDom = null;\n\t\t}\n\n\t\tc = createComponent(vnode.nodeName, props, context);\n\t\tif (dom && !c.nextBase) {\n\t\t\tc.nextBase = dom;\n\t\t\t// passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L229:\n\t\t\toldDom = null;\n\t\t}\n\t\tsetComponentProps(c, props, 1, context, mountAll);\n\t\tdom = c.base;\n\n\t\tif (oldDom && dom !== oldDom) {\n\t\t\toldDom._component = null;\n\t\t\trecollectNodeTree(oldDom, false);\n\t\t}\n\t}\n\n\treturn dom;\n}\n\n/** Remove a component from the DOM and recycle it.\n *\t@param {Component} component\tThe Component instance to unmount\n *\t@private\n */\nfunction unmountComponent(component) {\n\tif (options.beforeUnmount) options.beforeUnmount(component);\n\n\tvar base = component.base;\n\n\tcomponent._disable = true;\n\n\tif (component.componentWillUnmount) component.componentWillUnmount();\n\n\tcomponent.base = null;\n\n\t// recursively tear down & recollect high-order component children:\n\tvar inner = component._component;\n\tif (inner) {\n\t\tunmountComponent(inner);\n\t} else if (base) {\n\t\tif (base['__preactattr_'] && base['__preactattr_'].ref) base['__preactattr_'].ref(null);\n\n\t\tcomponent.nextBase = base;\n\n\t\tremoveNode(base);\n\t\tcollectComponent(component);\n\n\t\tremoveChildren(base);\n\t}\n\n\tif (component.__ref) component.__ref(null);\n}\n\n/** Base Component class.\n *\tProvides `setState()` and `forceUpdate()`, which trigger rendering.\n *\t@public\n *\n *\t@example\n *\tclass MyFoo extends Component {\n *\t\trender(props, state) {\n *\t\t\treturn
;\n *\t\t}\n *\t}\n */\nfunction Component(props, context) {\n\tthis._dirty = true;\n\n\t/** @public\n *\t@type {object}\n */\n\tthis.context = context;\n\n\t/** @public\n *\t@type {object}\n */\n\tthis.props = props;\n\n\t/** @public\n *\t@type {object}\n */\n\tthis.state = this.state || {};\n}\n\nextend(Component.prototype, {\n\n\t/** Returns a `boolean` indicating if the component should re-render when receiving the given `props` and `state`.\n *\t@param {object} nextProps\n *\t@param {object} nextState\n *\t@param {object} nextContext\n *\t@returns {Boolean} should the component re-render\n *\t@name shouldComponentUpdate\n *\t@function\n */\n\n\t/** Update component state by copying properties from `state` to `this.state`.\n *\t@param {object} state\t\tA hash of state properties to update with new values\n *\t@param {function} callback\tA function to be called once component state is updated\n */\n\tsetState: function setState(state, callback) {\n\t\tvar s = this.state;\n\t\tif (!this.prevState) this.prevState = extend({}, s);\n\t\textend(s, typeof state === 'function' ? state(s, this.props) : state);\n\t\tif (callback) (this._renderCallbacks = this._renderCallbacks || []).push(callback);\n\t\tenqueueRender(this);\n\t},\n\n\n\t/** Immediately perform a synchronous re-render of the component.\n *\t@param {function} callback\t\tA function to be called after component is re-rendered.\n *\t@private\n */\n\tforceUpdate: function forceUpdate(callback) {\n\t\tif (callback) (this._renderCallbacks = this._renderCallbacks || []).push(callback);\n\t\trenderComponent(this, 2);\n\t},\n\n\n\t/** Accepts `props` and `state`, and returns a new Virtual DOM tree to build.\n *\tVirtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx).\n *\t@param {object} props\t\tProps (eg: JSX attributes) received from parent element/component\n *\t@param {object} state\t\tThe component's current state\n *\t@param {object} context\t\tContext object (if a parent component has provided context)\n *\t@returns VNode\n */\n\trender: function render() {}\n});\n\n/** Render JSX into a `parent` Element.\n *\t@param {VNode} vnode\t\tA (JSX) VNode to render\n *\t@param {Element} parent\t\tDOM element to render into\n *\t@param {Element} [merge]\tAttempt to re-use an existing DOM tree rooted at `merge`\n *\t@public\n *\n *\t@example\n *\t// render a div into :\n *\trender(
hello!
, document.body);\n *\n *\t@example\n *\t// render a \"Thing\" component into #foo:\n *\tconst Thing = ({ name }) => { name };\n *\trender(, document.querySelector('#foo'));\n */\nfunction render(vnode, parent, merge) {\n return diff(merge, vnode, {}, false, parent, false);\n}\n\nvar preact = {\n\th: h,\n\tcreateElement: h,\n\tcloneElement: cloneElement,\n\tComponent: Component,\n\trender: render,\n\trerender: rerender,\n\toptions: options\n};\n\nexport { h, h as createElement, cloneElement, Component, render, rerender, options };\nexport default preact;\n//# sourceMappingURL=preact.esm.js.map\n","// Thanks! https://github.com/mafintosh/speedometer\nvar tick = 1;\nvar maxTick = 65535;\nvar resolution = 4;\nvar inc = function() {\n tick = (tick + 1) & maxTick;\n};\n\nvar timer = setInterval(inc, (1000 / resolution) | 0);\nif (timer.unref) timer.unref();\n\nexport default function speedometer(seconds) {\n var size = resolution * (seconds || 5);\n var buffer = [0];\n var pointer = 1;\n var last = (tick - 1) & maxTick;\n\n return function(delta) {\n var dist = (tick - last) & maxTick;\n if (dist > size) dist = size;\n last = tick;\n\n while (dist--) {\n if (pointer === size) pointer = 0;\n buffer[pointer] = buffer[pointer === 0 ? size - 1 : pointer - 1];\n pointer++;\n }\n\n if (delta) buffer[pointer - 1] += delta;\n\n var top = buffer[pointer - 1];\n var btm = buffer.length < size ? 0 : buffer[pointer === size ? 0 : pointer];\n\n return buffer.length < resolution\n ? top\n : (top - btm) * resolution / buffer.length;\n };\n}\n","import speedometer from './speedometer';\n\nexport default class Progress {\n constructor(length, emitDelay = 1000) {\n this.length = parseInt(length, 10) || 0;\n this.transferred = 0;\n this.speed = 0;\n this.streamSpeed = speedometer(this.speed || 5000);\n this.initial = false;\n this.emitDelay = emitDelay;\n this.eventStart = 0;\n this.percentage = 0;\n }\n\n getRemainingBytes() {\n return parseInt(this.length, 10) - parseInt(this.transferred, 10);\n }\n\n getEta() {\n return this.length >= this.transferred\n ? this.getRemainingBytes() / this.speed * 1000000000\n : 0;\n }\n\n flow(chunk, onProgress) {\n const chunkLength = chunk.length;\n this.transferred += chunkLength;\n this.speed = this.streamSpeed(chunkLength);\n this.percentage = Math.round(this.transferred / this.length * 100);\n if (!this.initial) {\n this.eventStart = Date.now();\n this.initial = true;\n }\n if (\n this.length >= this.transferred ||\n Date.now() - this.eventStart > this.emitDelay\n ) {\n this.eventStart = Date.now();\n\n const progress = {\n total: this.length,\n transferred: this.transferred,\n speed: this.speed,\n eta: this.getEta(),\n };\n if (this.length) {\n progress.remaining = this.getRemainingBytes();\n progress.percentage = this.percentage;\n }\n onProgress(progress);\n }\n }\n}\n","import Progress from './Progress';\n\nexport function isFetchProgressSupported() {\n return (\n typeof Response !== 'undefined' && typeof ReadableStream !== 'undefined'\n );\n}\nexport default function({\n defaultSize = 0,\n emitDelay = 10,\n onProgress = () => null,\n onComplete = () => null,\n}) {\n return function FetchProgress(response) {\n if (!isFetchProgressSupported()) {\n return response;\n }\n const { body, headers } = response;\n const contentLength = headers.get('content-length') || defaultSize;\n const progress = new Progress(contentLength, emitDelay);\n const reader = body.getReader();\n const stream = new ReadableStream({\n start(controller) {\n function push() {\n reader.read().then(({ done, value }) => {\n if (done) {\n onComplete({});\n controller.close();\n return;\n }\n if (value) {\n progress.flow(value, onProgress);\n }\n controller.enqueue(value);\n push();\n });\n }\n\n push();\n },\n });\n return new Response(stream, { headers });\n };\n}\n","import { h, Component } from 'preact';\nimport fetchProgress from '../index';\n\nclass ProgressItem extends Component {\n src = null;\n state = {\n loaded: false,\n progress: {\n percentage: 0,\n },\n };\n componentDidMount() {\n const self = this;\n fetch(this.props.src)\n .then(\n fetchProgress({\n onProgress(progress) {\n self.setState({ progress });\n },\n })\n )\n .then(r => r.blob())\n .then(src => {\n this.src = URL.createObjectURL(src);\n this.setState({\n loaded: true,\n });\n });\n }\n\n render() {\n return this.props.render({\n loaded: this.state.loaded,\n blob: this.src,\n progress: this.state.progress,\n });\n }\n}\nexport default ProgressItem;\n","import { render, h, Component } from 'preact';\nimport ProgressItem from './ProgressItem';\n\nclass App extends Component {\n state = {\n images: [\n 'https://images.unsplash.com/photo-1514832510016-108f38c20162?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=dbb79afb2cb593a13ea63e3f4b393f95&auto=format',\n 'https://images.unsplash.com/photo-1499514694201-9e89bcba16c5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=14fa0571fb8d326c611e5aa87a5b843f&auto=format',\n 'https://images.unsplash.com/photo-1495198551082-4499d2dd5312?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=fad723e4a048edea0afb17605e11a853&auto=format&',\n 'https://images.unsplash.com/photo-1496935127680-16e7e9e5eba3?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=a2ece95cddc5ac4a95a1e9b8fbbe6a61&auto=format',\n ],\n };\n render() {\n return (\n
\n
    \n {this.state.images.map(image => (\n
  • \n (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    TotalTransferredRemainingETASpeedPercentageImage
    {progress.total}{progress.transferred}{progress.remaining || 'N/A'}{progress.eta}{progress.speed}{progress.percentage || 'N/A'}\n {loaded ? (\n \n ) : (\n 'Loading'\n )}\n
    \n )}\n />\n
  • \n ))}\n
\n
\n );\n }\n}\n\nrender(, document.getElementById('root'));\n"]} -------------------------------------------------------------------------------- /dist/app.b185db3b.js: -------------------------------------------------------------------------------- 1 | // modules are defined as an array 2 | // [ module function, map of requires ] 3 | // 4 | // map of requires is short require name -> numeric require 5 | // 6 | // anything defined in a previous bundle is accessed via the 7 | // orig method which is the require for previous bundles 8 | 9 | // eslint-disable-next-line no-global-assign 10 | parcelRequire = (function (modules, cache, entry) { 11 | // Save the require from previous bundle to this closure if any 12 | var previousRequire = typeof parcelRequire === 'function' && parcelRequire; 13 | var nodeRequire = typeof require === 'function' && require; 14 | 15 | function newRequire(name, jumped) { 16 | if (!cache[name]) { 17 | if (!modules[name]) { 18 | // if we cannot find the module within our internal map or 19 | // cache jump to the current global require ie. the last bundle 20 | // that was added to the page. 21 | var currentRequire = typeof parcelRequire === 'function' && parcelRequire; 22 | if (!jumped && currentRequire) { 23 | return currentRequire(name, true); 24 | } 25 | 26 | // If there are other bundles on this page the require from the 27 | // previous one is saved to 'previousRequire'. Repeat this as 28 | // many times as there are bundles until the module is found or 29 | // we exhaust the require chain. 30 | if (previousRequire) { 31 | return previousRequire(name, true); 32 | } 33 | 34 | // Try the node require function if it exists. 35 | if (nodeRequire && typeof name === 'string') { 36 | return nodeRequire(name); 37 | } 38 | 39 | var err = new Error('Cannot find module \'' + name + '\''); 40 | err.code = 'MODULE_NOT_FOUND'; 41 | throw err; 42 | } 43 | 44 | localRequire.resolve = resolve; 45 | 46 | var module = cache[name] = new newRequire.Module(name); 47 | 48 | modules[name][0].call(module.exports, localRequire, module, module.exports); 49 | } 50 | 51 | return cache[name].exports; 52 | 53 | function localRequire(x){ 54 | return newRequire(localRequire.resolve(x)); 55 | } 56 | 57 | function resolve(x){ 58 | return modules[name][1][x] || x; 59 | } 60 | } 61 | 62 | function Module(moduleName) { 63 | this.id = moduleName; 64 | this.bundle = newRequire; 65 | this.exports = {}; 66 | } 67 | 68 | newRequire.isParcelRequire = true; 69 | newRequire.Module = Module; 70 | newRequire.modules = modules; 71 | newRequire.cache = cache; 72 | newRequire.parent = previousRequire; 73 | 74 | for (var i = 0; i < entry.length; i++) { 75 | newRequire(entry[i]); 76 | } 77 | 78 | // Override the current require with this new one 79 | return newRequire; 80 | })({7:[function(require,module,exports) { 81 | 'use strict'; 82 | 83 | Object.defineProperty(exports, "__esModule", { 84 | value: true 85 | }); 86 | /** Virtual DOM Node */ 87 | function VNode() {} 88 | 89 | /** Global options 90 | * @public 91 | * @namespace options {Object} 92 | */ 93 | var options = { 94 | 95 | /** If `true`, `prop` changes trigger synchronous component updates. 96 | * @name syncComponentUpdates 97 | * @type Boolean 98 | * @default true 99 | */ 100 | //syncComponentUpdates: true, 101 | 102 | /** Processes all created VNodes. 103 | * @param {VNode} vnode A newly-created VNode to normalize/process 104 | */ 105 | //vnode(vnode) { } 106 | 107 | /** Hook invoked after a component is mounted. */ 108 | // afterMount(component) { } 109 | 110 | /** Hook invoked after the DOM is updated with a component's latest render. */ 111 | // afterUpdate(component) { } 112 | 113 | /** Hook invoked immediately before a component is unmounted. */ 114 | // beforeUnmount(component) { } 115 | }; 116 | 117 | var stack = []; 118 | 119 | var EMPTY_CHILDREN = []; 120 | 121 | /** 122 | * JSX/hyperscript reviver. 123 | * @see http://jasonformat.com/wtf-is-jsx 124 | * Benchmarks: https://esbench.com/bench/57ee8f8e330ab09900a1a1a0 125 | * 126 | * Note: this is exported as both `h()` and `createElement()` for compatibility reasons. 127 | * 128 | * Creates a VNode (virtual DOM element). A tree of VNodes can be used as a lightweight representation 129 | * of the structure of a DOM tree. This structure can be realized by recursively comparing it against 130 | * the current _actual_ DOM structure, and applying only the differences. 131 | * 132 | * `h()`/`createElement()` accepts an element name, a list of attributes/props, 133 | * and optionally children to append to the element. 134 | * 135 | * @example The following DOM tree 136 | * 137 | * `
Hello!
` 138 | * 139 | * can be constructed using this function as: 140 | * 141 | * `h('div', { id: 'foo', name : 'bar' }, 'Hello!');` 142 | * 143 | * @param {string} nodeName An element name. Ex: `div`, `a`, `span`, etc. 144 | * @param {Object} attributes Any attributes/props to set on the created element. 145 | * @param rest Additional arguments are taken to be children to append. Can be infinitely nested Arrays. 146 | * 147 | * @public 148 | */ 149 | function h(nodeName, attributes) { 150 | var children = EMPTY_CHILDREN, 151 | lastSimple, 152 | child, 153 | simple, 154 | i; 155 | for (i = arguments.length; i-- > 2;) { 156 | stack.push(arguments[i]); 157 | } 158 | if (attributes && attributes.children != null) { 159 | if (!stack.length) stack.push(attributes.children); 160 | delete attributes.children; 161 | } 162 | while (stack.length) { 163 | if ((child = stack.pop()) && child.pop !== undefined) { 164 | for (i = child.length; i--;) { 165 | stack.push(child[i]); 166 | } 167 | } else { 168 | if (typeof child === 'boolean') child = null; 169 | 170 | if (simple = typeof nodeName !== 'function') { 171 | if (child == null) child = '';else if (typeof child === 'number') child = String(child);else if (typeof child !== 'string') simple = false; 172 | } 173 | 174 | if (simple && lastSimple) { 175 | children[children.length - 1] += child; 176 | } else if (children === EMPTY_CHILDREN) { 177 | children = [child]; 178 | } else { 179 | children.push(child); 180 | } 181 | 182 | lastSimple = simple; 183 | } 184 | } 185 | 186 | var p = new VNode(); 187 | p.nodeName = nodeName; 188 | p.children = children; 189 | p.attributes = attributes == null ? undefined : attributes; 190 | p.key = attributes == null ? undefined : attributes.key; 191 | 192 | // if a "vnode hook" is defined, pass every created VNode to it 193 | if (options.vnode !== undefined) options.vnode(p); 194 | 195 | return p; 196 | } 197 | 198 | /** 199 | * Copy all properties from `props` onto `obj`. 200 | * @param {Object} obj Object onto which properties should be copied. 201 | * @param {Object} props Object from which to copy properties. 202 | * @returns obj 203 | * @private 204 | */ 205 | function extend(obj, props) { 206 | for (var i in props) { 207 | obj[i] = props[i]; 208 | }return obj; 209 | } 210 | 211 | /** 212 | * Call a function asynchronously, as soon as possible. Makes 213 | * use of HTML Promise to schedule the callback if available, 214 | * otherwise falling back to `setTimeout` (mainly for IE<11). 215 | * 216 | * @param {Function} callback 217 | */ 218 | var defer = typeof Promise == 'function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout; 219 | 220 | /** 221 | * Clones the given VNode, optionally adding attributes/props and replacing its children. 222 | * @param {VNode} vnode The virutal DOM element to clone 223 | * @param {Object} props Attributes/props to add when cloning 224 | * @param {VNode} rest Any additional arguments will be used as replacement children. 225 | */ 226 | function cloneElement(vnode, props) { 227 | return h(vnode.nodeName, extend(extend({}, vnode.attributes), props), arguments.length > 2 ? [].slice.call(arguments, 2) : vnode.children); 228 | } 229 | 230 | // DOM properties that should NOT have "px" added when numeric 231 | var IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i; 232 | 233 | /** Managed queue of dirty components to be re-rendered */ 234 | 235 | var items = []; 236 | 237 | function enqueueRender(component) { 238 | if (!component._dirty && (component._dirty = true) && items.push(component) == 1) { 239 | (options.debounceRendering || defer)(rerender); 240 | } 241 | } 242 | 243 | function rerender() { 244 | var p, 245 | list = items; 246 | items = []; 247 | while (p = list.pop()) { 248 | if (p._dirty) renderComponent(p); 249 | } 250 | } 251 | 252 | /** 253 | * Check if two nodes are equivalent. 254 | * 255 | * @param {Node} node DOM Node to compare 256 | * @param {VNode} vnode Virtual DOM node to compare 257 | * @param {boolean} [hyrdating=false] If true, ignores component constructors when comparing. 258 | * @private 259 | */ 260 | function isSameNodeType(node, vnode, hydrating) { 261 | if (typeof vnode === 'string' || typeof vnode === 'number') { 262 | return node.splitText !== undefined; 263 | } 264 | if (typeof vnode.nodeName === 'string') { 265 | return !node._componentConstructor && isNamedNode(node, vnode.nodeName); 266 | } 267 | return hydrating || node._componentConstructor === vnode.nodeName; 268 | } 269 | 270 | /** 271 | * Check if an Element has a given nodeName, case-insensitively. 272 | * 273 | * @param {Element} node A DOM Element to inspect the name of. 274 | * @param {String} nodeName Unnormalized name to compare against. 275 | */ 276 | function isNamedNode(node, nodeName) { 277 | return node.normalizedNodeName === nodeName || node.nodeName.toLowerCase() === nodeName.toLowerCase(); 278 | } 279 | 280 | /** 281 | * Reconstruct Component-style `props` from a VNode. 282 | * Ensures default/fallback values from `defaultProps`: 283 | * Own-properties of `defaultProps` not present in `vnode.attributes` are added. 284 | * 285 | * @param {VNode} vnode 286 | * @returns {Object} props 287 | */ 288 | function getNodeProps(vnode) { 289 | var props = extend({}, vnode.attributes); 290 | props.children = vnode.children; 291 | 292 | var defaultProps = vnode.nodeName.defaultProps; 293 | if (defaultProps !== undefined) { 294 | for (var i in defaultProps) { 295 | if (props[i] === undefined) { 296 | props[i] = defaultProps[i]; 297 | } 298 | } 299 | } 300 | 301 | return props; 302 | } 303 | 304 | /** Create an element with the given nodeName. 305 | * @param {String} nodeName 306 | * @param {Boolean} [isSvg=false] If `true`, creates an element within the SVG namespace. 307 | * @returns {Element} node 308 | */ 309 | function createNode(nodeName, isSvg) { 310 | var node = isSvg ? document.createElementNS('http://www.w3.org/2000/svg', nodeName) : document.createElement(nodeName); 311 | node.normalizedNodeName = nodeName; 312 | return node; 313 | } 314 | 315 | /** Remove a child node from its parent if attached. 316 | * @param {Element} node The node to remove 317 | */ 318 | function removeNode(node) { 319 | var parentNode = node.parentNode; 320 | if (parentNode) parentNode.removeChild(node); 321 | } 322 | 323 | /** Set a named attribute on the given Node, with special behavior for some names and event handlers. 324 | * If `value` is `null`, the attribute/handler will be removed. 325 | * @param {Element} node An element to mutate 326 | * @param {string} name The name/key to set, such as an event or attribute name 327 | * @param {any} old The last value that was set for this name/node pair 328 | * @param {any} value An attribute value, such as a function to be used as an event handler 329 | * @param {Boolean} isSvg Are we currently diffing inside an svg? 330 | * @private 331 | */ 332 | function setAccessor(node, name, old, value, isSvg) { 333 | if (name === 'className') name = 'class'; 334 | 335 | if (name === 'key') { 336 | // ignore 337 | } else if (name === 'ref') { 338 | if (old) old(null); 339 | if (value) value(node); 340 | } else if (name === 'class' && !isSvg) { 341 | node.className = value || ''; 342 | } else if (name === 'style') { 343 | if (!value || typeof value === 'string' || typeof old === 'string') { 344 | node.style.cssText = value || ''; 345 | } 346 | if (value && typeof value === 'object') { 347 | if (typeof old !== 'string') { 348 | for (var i in old) { 349 | if (!(i in value)) node.style[i] = ''; 350 | } 351 | } 352 | for (var i in value) { 353 | node.style[i] = typeof value[i] === 'number' && IS_NON_DIMENSIONAL.test(i) === false ? value[i] + 'px' : value[i]; 354 | } 355 | } 356 | } else if (name === 'dangerouslySetInnerHTML') { 357 | if (value) node.innerHTML = value.__html || ''; 358 | } else if (name[0] == 'o' && name[1] == 'n') { 359 | var useCapture = name !== (name = name.replace(/Capture$/, '')); 360 | name = name.toLowerCase().substring(2); 361 | if (value) { 362 | if (!old) node.addEventListener(name, eventProxy, useCapture); 363 | } else { 364 | node.removeEventListener(name, eventProxy, useCapture); 365 | } 366 | (node._listeners || (node._listeners = {}))[name] = value; 367 | } else if (name !== 'list' && name !== 'type' && !isSvg && name in node) { 368 | setProperty(node, name, value == null ? '' : value); 369 | if (value == null || value === false) node.removeAttribute(name); 370 | } else { 371 | var ns = isSvg && name !== (name = name.replace(/^xlink\:?/, '')); 372 | if (value == null || value === false) { 373 | if (ns) node.removeAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase());else node.removeAttribute(name); 374 | } else if (typeof value !== 'function') { 375 | if (ns) node.setAttributeNS('http://www.w3.org/1999/xlink', name.toLowerCase(), value);else node.setAttribute(name, value); 376 | } 377 | } 378 | } 379 | 380 | /** Attempt to set a DOM property to the given value. 381 | * IE & FF throw for certain property-value combinations. 382 | */ 383 | function setProperty(node, name, value) { 384 | try { 385 | node[name] = value; 386 | } catch (e) {} 387 | } 388 | 389 | /** Proxy an event to hooked event handlers 390 | * @private 391 | */ 392 | function eventProxy(e) { 393 | return this._listeners[e.type](options.event && options.event(e) || e); 394 | } 395 | 396 | /** Queue of components that have been mounted and are awaiting componentDidMount */ 397 | var mounts = []; 398 | 399 | /** Diff recursion count, used to track the end of the diff cycle. */ 400 | var diffLevel = 0; 401 | 402 | /** Global flag indicating if the diff is currently within an SVG */ 403 | var isSvgMode = false; 404 | 405 | /** Global flag indicating if the diff is performing hydration */ 406 | var hydrating = false; 407 | 408 | /** Invoke queued componentDidMount lifecycle methods */ 409 | function flushMounts() { 410 | var c; 411 | while (c = mounts.pop()) { 412 | if (options.afterMount) options.afterMount(c); 413 | if (c.componentDidMount) c.componentDidMount(); 414 | } 415 | } 416 | 417 | /** Apply differences in a given vnode (and it's deep children) to a real DOM Node. 418 | * @param {Element} [dom=null] A DOM node to mutate into the shape of the `vnode` 419 | * @param {VNode} vnode A VNode (with descendants forming a tree) representing the desired DOM structure 420 | * @returns {Element} dom The created/mutated element 421 | * @private 422 | */ 423 | function diff(dom, vnode, context, mountAll, parent, componentRoot) { 424 | // diffLevel having been 0 here indicates initial entry into the diff (not a subdiff) 425 | if (!diffLevel++) { 426 | // when first starting the diff, check if we're diffing an SVG or within an SVG 427 | isSvgMode = parent != null && parent.ownerSVGElement !== undefined; 428 | 429 | // hydration is indicated by the existing element to be diffed not having a prop cache 430 | hydrating = dom != null && !('__preactattr_' in dom); 431 | } 432 | 433 | var ret = idiff(dom, vnode, context, mountAll, componentRoot); 434 | 435 | // append the element if its a new parent 436 | if (parent && ret.parentNode !== parent) parent.appendChild(ret); 437 | 438 | // diffLevel being reduced to 0 means we're exiting the diff 439 | if (! --diffLevel) { 440 | hydrating = false; 441 | // invoke queued componentDidMount lifecycle methods 442 | if (!componentRoot) flushMounts(); 443 | } 444 | 445 | return ret; 446 | } 447 | 448 | /** Internals of `diff()`, separated to allow bypassing diffLevel / mount flushing. */ 449 | function idiff(dom, vnode, context, mountAll, componentRoot) { 450 | var out = dom, 451 | prevSvgMode = isSvgMode; 452 | 453 | // empty values (null, undefined, booleans) render as empty Text nodes 454 | if (vnode == null || typeof vnode === 'boolean') vnode = ''; 455 | 456 | // Fast case: Strings & Numbers create/update Text nodes. 457 | if (typeof vnode === 'string' || typeof vnode === 'number') { 458 | 459 | // update if it's already a Text node: 460 | if (dom && dom.splitText !== undefined && dom.parentNode && (!dom._component || componentRoot)) { 461 | /* istanbul ignore if */ /* Browser quirk that can't be covered: https://github.com/developit/preact/commit/fd4f21f5c45dfd75151bd27b4c217d8003aa5eb9 */ 462 | if (dom.nodeValue != vnode) { 463 | dom.nodeValue = vnode; 464 | } 465 | } else { 466 | // it wasn't a Text node: replace it with one and recycle the old Element 467 | out = document.createTextNode(vnode); 468 | if (dom) { 469 | if (dom.parentNode) dom.parentNode.replaceChild(out, dom); 470 | recollectNodeTree(dom, true); 471 | } 472 | } 473 | 474 | out['__preactattr_'] = true; 475 | 476 | return out; 477 | } 478 | 479 | // If the VNode represents a Component, perform a component diff: 480 | var vnodeName = vnode.nodeName; 481 | if (typeof vnodeName === 'function') { 482 | return buildComponentFromVNode(dom, vnode, context, mountAll); 483 | } 484 | 485 | // Tracks entering and exiting SVG namespace when descending through the tree. 486 | isSvgMode = vnodeName === 'svg' ? true : vnodeName === 'foreignObject' ? false : isSvgMode; 487 | 488 | // If there's no existing element or it's the wrong type, create a new one: 489 | vnodeName = String(vnodeName); 490 | if (!dom || !isNamedNode(dom, vnodeName)) { 491 | out = createNode(vnodeName, isSvgMode); 492 | 493 | if (dom) { 494 | // move children into the replacement node 495 | while (dom.firstChild) { 496 | out.appendChild(dom.firstChild); 497 | } // if the previous Element was mounted into the DOM, replace it inline 498 | if (dom.parentNode) dom.parentNode.replaceChild(out, dom); 499 | 500 | // recycle the old element (skips non-Element node types) 501 | recollectNodeTree(dom, true); 502 | } 503 | } 504 | 505 | var fc = out.firstChild, 506 | props = out['__preactattr_'], 507 | vchildren = vnode.children; 508 | 509 | if (props == null) { 510 | props = out['__preactattr_'] = {}; 511 | for (var a = out.attributes, i = a.length; i--;) { 512 | props[a[i].name] = a[i].value; 513 | } 514 | } 515 | 516 | // Optimization: fast-path for elements containing a single TextNode: 517 | if (!hydrating && vchildren && vchildren.length === 1 && typeof vchildren[0] === 'string' && fc != null && fc.splitText !== undefined && fc.nextSibling == null) { 518 | if (fc.nodeValue != vchildren[0]) { 519 | fc.nodeValue = vchildren[0]; 520 | } 521 | } 522 | // otherwise, if there are existing or new children, diff them: 523 | else if (vchildren && vchildren.length || fc != null) { 524 | innerDiffNode(out, vchildren, context, mountAll, hydrating || props.dangerouslySetInnerHTML != null); 525 | } 526 | 527 | // Apply attributes/props from VNode to the DOM Element: 528 | diffAttributes(out, vnode.attributes, props); 529 | 530 | // restore previous SVG mode: (in case we're exiting an SVG namespace) 531 | isSvgMode = prevSvgMode; 532 | 533 | return out; 534 | } 535 | 536 | /** Apply child and attribute changes between a VNode and a DOM Node to the DOM. 537 | * @param {Element} dom Element whose children should be compared & mutated 538 | * @param {Array} vchildren Array of VNodes to compare to `dom.childNodes` 539 | * @param {Object} context Implicitly descendant context object (from most recent `getChildContext()`) 540 | * @param {Boolean} mountAll 541 | * @param {Boolean} isHydrating If `true`, consumes externally created elements similar to hydration 542 | */ 543 | function innerDiffNode(dom, vchildren, context, mountAll, isHydrating) { 544 | var originalChildren = dom.childNodes, 545 | children = [], 546 | keyed = {}, 547 | keyedLen = 0, 548 | min = 0, 549 | len = originalChildren.length, 550 | childrenLen = 0, 551 | vlen = vchildren ? vchildren.length : 0, 552 | j, 553 | c, 554 | f, 555 | vchild, 556 | child; 557 | 558 | // Build up a map of keyed children and an Array of unkeyed children: 559 | if (len !== 0) { 560 | for (var i = 0; i < len; i++) { 561 | var _child = originalChildren[i], 562 | props = _child['__preactattr_'], 563 | key = vlen && props ? _child._component ? _child._component.__key : props.key : null; 564 | if (key != null) { 565 | keyedLen++; 566 | keyed[key] = _child; 567 | } else if (props || (_child.splitText !== undefined ? isHydrating ? _child.nodeValue.trim() : true : isHydrating)) { 568 | children[childrenLen++] = _child; 569 | } 570 | } 571 | } 572 | 573 | if (vlen !== 0) { 574 | for (var i = 0; i < vlen; i++) { 575 | vchild = vchildren[i]; 576 | child = null; 577 | 578 | // attempt to find a node based on key matching 579 | var key = vchild.key; 580 | if (key != null) { 581 | if (keyedLen && keyed[key] !== undefined) { 582 | child = keyed[key]; 583 | keyed[key] = undefined; 584 | keyedLen--; 585 | } 586 | } 587 | // attempt to pluck a node of the same type from the existing children 588 | else if (!child && min < childrenLen) { 589 | for (j = min; j < childrenLen; j++) { 590 | if (children[j] !== undefined && isSameNodeType(c = children[j], vchild, isHydrating)) { 591 | child = c; 592 | children[j] = undefined; 593 | if (j === childrenLen - 1) childrenLen--; 594 | if (j === min) min++; 595 | break; 596 | } 597 | } 598 | } 599 | 600 | // morph the matched/found/created DOM child to match vchild (deep) 601 | child = idiff(child, vchild, context, mountAll); 602 | 603 | f = originalChildren[i]; 604 | if (child && child !== dom && child !== f) { 605 | if (f == null) { 606 | dom.appendChild(child); 607 | } else if (child === f.nextSibling) { 608 | removeNode(f); 609 | } else { 610 | dom.insertBefore(child, f); 611 | } 612 | } 613 | } 614 | } 615 | 616 | // remove unused keyed children: 617 | if (keyedLen) { 618 | for (var i in keyed) { 619 | if (keyed[i] !== undefined) recollectNodeTree(keyed[i], false); 620 | } 621 | } 622 | 623 | // remove orphaned unkeyed children: 624 | while (min <= childrenLen) { 625 | if ((child = children[childrenLen--]) !== undefined) recollectNodeTree(child, false); 626 | } 627 | } 628 | 629 | /** Recursively recycle (or just unmount) a node and its descendants. 630 | * @param {Node} node DOM node to start unmount/removal from 631 | * @param {Boolean} [unmountOnly=false] If `true`, only triggers unmount lifecycle, skips removal 632 | */ 633 | function recollectNodeTree(node, unmountOnly) { 634 | var component = node._component; 635 | if (component) { 636 | // if node is owned by a Component, unmount that component (ends up recursing back here) 637 | unmountComponent(component); 638 | } else { 639 | // If the node's VNode had a ref function, invoke it with null here. 640 | // (this is part of the React spec, and smart for unsetting references) 641 | if (node['__preactattr_'] != null && node['__preactattr_'].ref) node['__preactattr_'].ref(null); 642 | 643 | if (unmountOnly === false || node['__preactattr_'] == null) { 644 | removeNode(node); 645 | } 646 | 647 | removeChildren(node); 648 | } 649 | } 650 | 651 | /** Recollect/unmount all children. 652 | * - we use .lastChild here because it causes less reflow than .firstChild 653 | * - it's also cheaper than accessing the .childNodes Live NodeList 654 | */ 655 | function removeChildren(node) { 656 | node = node.lastChild; 657 | while (node) { 658 | var next = node.previousSibling; 659 | recollectNodeTree(node, true); 660 | node = next; 661 | } 662 | } 663 | 664 | /** Apply differences in attributes from a VNode to the given DOM Element. 665 | * @param {Element} dom Element with attributes to diff `attrs` against 666 | * @param {Object} attrs The desired end-state key-value attribute pairs 667 | * @param {Object} old Current/previous attributes (from previous VNode or element's prop cache) 668 | */ 669 | function diffAttributes(dom, attrs, old) { 670 | var name; 671 | 672 | // remove attributes no longer present on the vnode by setting them to undefined 673 | for (name in old) { 674 | if (!(attrs && attrs[name] != null) && old[name] != null) { 675 | setAccessor(dom, name, old[name], old[name] = undefined, isSvgMode); 676 | } 677 | } 678 | 679 | // add new & update changed attributes 680 | for (name in attrs) { 681 | if (name !== 'children' && name !== 'innerHTML' && (!(name in old) || attrs[name] !== (name === 'value' || name === 'checked' ? dom[name] : old[name]))) { 682 | setAccessor(dom, name, old[name], old[name] = attrs[name], isSvgMode); 683 | } 684 | } 685 | } 686 | 687 | /** Retains a pool of Components for re-use, keyed on component name. 688 | * Note: since component names are not unique or even necessarily available, these are primarily a form of sharding. 689 | * @private 690 | */ 691 | var components = {}; 692 | 693 | /** Reclaim a component for later re-use by the recycler. */ 694 | function collectComponent(component) { 695 | var name = component.constructor.name; 696 | (components[name] || (components[name] = [])).push(component); 697 | } 698 | 699 | /** Create a component. Normalizes differences between PFC's and classful Components. */ 700 | function createComponent(Ctor, props, context) { 701 | var list = components[Ctor.name], 702 | inst; 703 | 704 | if (Ctor.prototype && Ctor.prototype.render) { 705 | inst = new Ctor(props, context); 706 | Component.call(inst, props, context); 707 | } else { 708 | inst = new Component(props, context); 709 | inst.constructor = Ctor; 710 | inst.render = doRender; 711 | } 712 | 713 | if (list) { 714 | for (var i = list.length; i--;) { 715 | if (list[i].constructor === Ctor) { 716 | inst.nextBase = list[i].nextBase; 717 | list.splice(i, 1); 718 | break; 719 | } 720 | } 721 | } 722 | return inst; 723 | } 724 | 725 | /** The `.render()` method for a PFC backing instance. */ 726 | function doRender(props, state, context) { 727 | return this.constructor(props, context); 728 | } 729 | 730 | /** Set a component's `props` (generally derived from JSX attributes). 731 | * @param {Object} props 732 | * @param {Object} [opts] 733 | * @param {boolean} [opts.renderSync=false] If `true` and {@link options.syncComponentUpdates} is `true`, triggers synchronous rendering. 734 | * @param {boolean} [opts.render=true] If `false`, no render will be triggered. 735 | */ 736 | function setComponentProps(component, props, opts, context, mountAll) { 737 | if (component._disable) return; 738 | component._disable = true; 739 | 740 | if (component.__ref = props.ref) delete props.ref; 741 | if (component.__key = props.key) delete props.key; 742 | 743 | if (!component.base || mountAll) { 744 | if (component.componentWillMount) component.componentWillMount(); 745 | } else if (component.componentWillReceiveProps) { 746 | component.componentWillReceiveProps(props, context); 747 | } 748 | 749 | if (context && context !== component.context) { 750 | if (!component.prevContext) component.prevContext = component.context; 751 | component.context = context; 752 | } 753 | 754 | if (!component.prevProps) component.prevProps = component.props; 755 | component.props = props; 756 | 757 | component._disable = false; 758 | 759 | if (opts !== 0) { 760 | if (opts === 1 || options.syncComponentUpdates !== false || !component.base) { 761 | renderComponent(component, 1, mountAll); 762 | } else { 763 | enqueueRender(component); 764 | } 765 | } 766 | 767 | if (component.__ref) component.__ref(component); 768 | } 769 | 770 | /** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account. 771 | * @param {Component} component 772 | * @param {Object} [opts] 773 | * @param {boolean} [opts.build=false] If `true`, component will build and store a DOM node if not already associated with one. 774 | * @private 775 | */ 776 | function renderComponent(component, opts, mountAll, isChild) { 777 | if (component._disable) return; 778 | 779 | var props = component.props, 780 | state = component.state, 781 | context = component.context, 782 | previousProps = component.prevProps || props, 783 | previousState = component.prevState || state, 784 | previousContext = component.prevContext || context, 785 | isUpdate = component.base, 786 | nextBase = component.nextBase, 787 | initialBase = isUpdate || nextBase, 788 | initialChildComponent = component._component, 789 | skip = false, 790 | rendered, 791 | inst, 792 | cbase; 793 | 794 | // if updating 795 | if (isUpdate) { 796 | component.props = previousProps; 797 | component.state = previousState; 798 | component.context = previousContext; 799 | if (opts !== 2 && component.shouldComponentUpdate && component.shouldComponentUpdate(props, state, context) === false) { 800 | skip = true; 801 | } else if (component.componentWillUpdate) { 802 | component.componentWillUpdate(props, state, context); 803 | } 804 | component.props = props; 805 | component.state = state; 806 | component.context = context; 807 | } 808 | 809 | component.prevProps = component.prevState = component.prevContext = component.nextBase = null; 810 | component._dirty = false; 811 | 812 | if (!skip) { 813 | rendered = component.render(props, state, context); 814 | 815 | // context to pass to the child, can be updated via (grand-)parent component 816 | if (component.getChildContext) { 817 | context = extend(extend({}, context), component.getChildContext()); 818 | } 819 | 820 | var childComponent = rendered && rendered.nodeName, 821 | toUnmount, 822 | base; 823 | 824 | if (typeof childComponent === 'function') { 825 | // set up high order component link 826 | 827 | var childProps = getNodeProps(rendered); 828 | inst = initialChildComponent; 829 | 830 | if (inst && inst.constructor === childComponent && childProps.key == inst.__key) { 831 | setComponentProps(inst, childProps, 1, context, false); 832 | } else { 833 | toUnmount = inst; 834 | 835 | component._component = inst = createComponent(childComponent, childProps, context); 836 | inst.nextBase = inst.nextBase || nextBase; 837 | inst._parentComponent = component; 838 | setComponentProps(inst, childProps, 0, context, false); 839 | renderComponent(inst, 1, mountAll, true); 840 | } 841 | 842 | base = inst.base; 843 | } else { 844 | cbase = initialBase; 845 | 846 | // destroy high order component link 847 | toUnmount = initialChildComponent; 848 | if (toUnmount) { 849 | cbase = component._component = null; 850 | } 851 | 852 | if (initialBase || opts === 1) { 853 | if (cbase) cbase._component = null; 854 | base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true); 855 | } 856 | } 857 | 858 | if (initialBase && base !== initialBase && inst !== initialChildComponent) { 859 | var baseParent = initialBase.parentNode; 860 | if (baseParent && base !== baseParent) { 861 | baseParent.replaceChild(base, initialBase); 862 | 863 | if (!toUnmount) { 864 | initialBase._component = null; 865 | recollectNodeTree(initialBase, false); 866 | } 867 | } 868 | } 869 | 870 | if (toUnmount) { 871 | unmountComponent(toUnmount); 872 | } 873 | 874 | component.base = base; 875 | if (base && !isChild) { 876 | var componentRef = component, 877 | t = component; 878 | while (t = t._parentComponent) { 879 | (componentRef = t).base = base; 880 | } 881 | base._component = componentRef; 882 | base._componentConstructor = componentRef.constructor; 883 | } 884 | } 885 | 886 | if (!isUpdate || mountAll) { 887 | mounts.unshift(component); 888 | } else if (!skip) { 889 | // Ensure that pending componentDidMount() hooks of child components 890 | // are called before the componentDidUpdate() hook in the parent. 891 | // Note: disabled as it causes duplicate hooks, see https://github.com/developit/preact/issues/750 892 | // flushMounts(); 893 | 894 | if (component.componentDidUpdate) { 895 | component.componentDidUpdate(previousProps, previousState, previousContext); 896 | } 897 | if (options.afterUpdate) options.afterUpdate(component); 898 | } 899 | 900 | if (component._renderCallbacks != null) { 901 | while (component._renderCallbacks.length) { 902 | component._renderCallbacks.pop().call(component); 903 | } 904 | } 905 | 906 | if (!diffLevel && !isChild) flushMounts(); 907 | } 908 | 909 | /** Apply the Component referenced by a VNode to the DOM. 910 | * @param {Element} dom The DOM node to mutate 911 | * @param {VNode} vnode A Component-referencing VNode 912 | * @returns {Element} dom The created/mutated element 913 | * @private 914 | */ 915 | function buildComponentFromVNode(dom, vnode, context, mountAll) { 916 | var c = dom && dom._component, 917 | originalComponent = c, 918 | oldDom = dom, 919 | isDirectOwner = c && dom._componentConstructor === vnode.nodeName, 920 | isOwner = isDirectOwner, 921 | props = getNodeProps(vnode); 922 | while (c && !isOwner && (c = c._parentComponent)) { 923 | isOwner = c.constructor === vnode.nodeName; 924 | } 925 | 926 | if (c && isOwner && (!mountAll || c._component)) { 927 | setComponentProps(c, props, 3, context, mountAll); 928 | dom = c.base; 929 | } else { 930 | if (originalComponent && !isDirectOwner) { 931 | unmountComponent(originalComponent); 932 | dom = oldDom = null; 933 | } 934 | 935 | c = createComponent(vnode.nodeName, props, context); 936 | if (dom && !c.nextBase) { 937 | c.nextBase = dom; 938 | // passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L229: 939 | oldDom = null; 940 | } 941 | setComponentProps(c, props, 1, context, mountAll); 942 | dom = c.base; 943 | 944 | if (oldDom && dom !== oldDom) { 945 | oldDom._component = null; 946 | recollectNodeTree(oldDom, false); 947 | } 948 | } 949 | 950 | return dom; 951 | } 952 | 953 | /** Remove a component from the DOM and recycle it. 954 | * @param {Component} component The Component instance to unmount 955 | * @private 956 | */ 957 | function unmountComponent(component) { 958 | if (options.beforeUnmount) options.beforeUnmount(component); 959 | 960 | var base = component.base; 961 | 962 | component._disable = true; 963 | 964 | if (component.componentWillUnmount) component.componentWillUnmount(); 965 | 966 | component.base = null; 967 | 968 | // recursively tear down & recollect high-order component children: 969 | var inner = component._component; 970 | if (inner) { 971 | unmountComponent(inner); 972 | } else if (base) { 973 | if (base['__preactattr_'] && base['__preactattr_'].ref) base['__preactattr_'].ref(null); 974 | 975 | component.nextBase = base; 976 | 977 | removeNode(base); 978 | collectComponent(component); 979 | 980 | removeChildren(base); 981 | } 982 | 983 | if (component.__ref) component.__ref(null); 984 | } 985 | 986 | /** Base Component class. 987 | * Provides `setState()` and `forceUpdate()`, which trigger rendering. 988 | * @public 989 | * 990 | * @example 991 | * class MyFoo extends Component { 992 | * render(props, state) { 993 | * return
; 994 | * } 995 | * } 996 | */ 997 | function Component(props, context) { 998 | this._dirty = true; 999 | 1000 | /** @public 1001 | * @type {object} 1002 | */ 1003 | this.context = context; 1004 | 1005 | /** @public 1006 | * @type {object} 1007 | */ 1008 | this.props = props; 1009 | 1010 | /** @public 1011 | * @type {object} 1012 | */ 1013 | this.state = this.state || {}; 1014 | } 1015 | 1016 | extend(Component.prototype, { 1017 | 1018 | /** Returns a `boolean` indicating if the component should re-render when receiving the given `props` and `state`. 1019 | * @param {object} nextProps 1020 | * @param {object} nextState 1021 | * @param {object} nextContext 1022 | * @returns {Boolean} should the component re-render 1023 | * @name shouldComponentUpdate 1024 | * @function 1025 | */ 1026 | 1027 | /** Update component state by copying properties from `state` to `this.state`. 1028 | * @param {object} state A hash of state properties to update with new values 1029 | * @param {function} callback A function to be called once component state is updated 1030 | */ 1031 | setState: function setState(state, callback) { 1032 | var s = this.state; 1033 | if (!this.prevState) this.prevState = extend({}, s); 1034 | extend(s, typeof state === 'function' ? state(s, this.props) : state); 1035 | if (callback) (this._renderCallbacks = this._renderCallbacks || []).push(callback); 1036 | enqueueRender(this); 1037 | }, 1038 | 1039 | /** Immediately perform a synchronous re-render of the component. 1040 | * @param {function} callback A function to be called after component is re-rendered. 1041 | * @private 1042 | */ 1043 | forceUpdate: function forceUpdate(callback) { 1044 | if (callback) (this._renderCallbacks = this._renderCallbacks || []).push(callback); 1045 | renderComponent(this, 2); 1046 | }, 1047 | 1048 | /** Accepts `props` and `state`, and returns a new Virtual DOM tree to build. 1049 | * Virtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx). 1050 | * @param {object} props Props (eg: JSX attributes) received from parent element/component 1051 | * @param {object} state The component's current state 1052 | * @param {object} context Context object (if a parent component has provided context) 1053 | * @returns VNode 1054 | */ 1055 | render: function render() {} 1056 | }); 1057 | 1058 | /** Render JSX into a `parent` Element. 1059 | * @param {VNode} vnode A (JSX) VNode to render 1060 | * @param {Element} parent DOM element to render into 1061 | * @param {Element} [merge] Attempt to re-use an existing DOM tree rooted at `merge` 1062 | * @public 1063 | * 1064 | * @example 1065 | * // render a div into : 1066 | * render(
hello!
, document.body); 1067 | * 1068 | * @example 1069 | * // render a "Thing" component into #foo: 1070 | * const Thing = ({ name }) => { name }; 1071 | * render(, document.querySelector('#foo')); 1072 | */ 1073 | function render(vnode, parent, merge) { 1074 | return diff(merge, vnode, {}, false, parent, false); 1075 | } 1076 | 1077 | var preact = { 1078 | h: h, 1079 | createElement: h, 1080 | cloneElement: cloneElement, 1081 | Component: Component, 1082 | render: render, 1083 | rerender: rerender, 1084 | options: options 1085 | }; 1086 | 1087 | exports.h = h; 1088 | exports.createElement = h; 1089 | exports.cloneElement = cloneElement; 1090 | exports.Component = Component; 1091 | exports.render = render; 1092 | exports.rerender = rerender; 1093 | exports.options = options; 1094 | exports.default = preact; 1095 | //# sourceMappingURL=preact.esm.js.map 1096 | },{}],14:[function(require,module,exports) { 1097 | "use strict"; 1098 | 1099 | Object.defineProperty(exports, "__esModule", { 1100 | value: true 1101 | }); 1102 | exports.default = speedometer; 1103 | // Thanks! https://github.com/mafintosh/speedometer 1104 | var tick = 1; 1105 | var maxTick = 65535; 1106 | var resolution = 4; 1107 | var inc = function inc() { 1108 | tick = tick + 1 & maxTick; 1109 | }; 1110 | 1111 | var timer = setInterval(inc, 1000 / resolution | 0); 1112 | if (timer.unref) timer.unref(); 1113 | 1114 | function speedometer(seconds) { 1115 | var size = resolution * (seconds || 5); 1116 | var buffer = [0]; 1117 | var pointer = 1; 1118 | var last = tick - 1 & maxTick; 1119 | 1120 | return function (delta) { 1121 | var dist = tick - last & maxTick; 1122 | if (dist > size) dist = size; 1123 | last = tick; 1124 | 1125 | while (dist--) { 1126 | if (pointer === size) pointer = 0; 1127 | buffer[pointer] = buffer[pointer === 0 ? size - 1 : pointer - 1]; 1128 | pointer++; 1129 | } 1130 | 1131 | if (delta) buffer[pointer - 1] += delta; 1132 | 1133 | var top = buffer[pointer - 1]; 1134 | var btm = buffer.length < size ? 0 : buffer[pointer === size ? 0 : pointer]; 1135 | 1136 | return buffer.length < resolution ? top : (top - btm) * resolution / buffer.length; 1137 | }; 1138 | } 1139 | },{}],12:[function(require,module,exports) { 1140 | 'use strict'; 1141 | 1142 | Object.defineProperty(exports, "__esModule", { 1143 | value: true 1144 | }); 1145 | 1146 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 1147 | 1148 | var _speedometer = require('./speedometer'); 1149 | 1150 | var _speedometer2 = _interopRequireDefault(_speedometer); 1151 | 1152 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 1153 | 1154 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 1155 | 1156 | var Progress = function () { 1157 | function Progress(length) { 1158 | var emitDelay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1000; 1159 | 1160 | _classCallCheck(this, Progress); 1161 | 1162 | this.length = parseInt(length, 10) || 0; 1163 | this.transferred = 0; 1164 | this.speed = 0; 1165 | this.streamSpeed = (0, _speedometer2.default)(this.speed || 5000); 1166 | this.initial = false; 1167 | this.emitDelay = emitDelay; 1168 | this.eventStart = 0; 1169 | this.percentage = 0; 1170 | } 1171 | 1172 | _createClass(Progress, [{ 1173 | key: 'getRemainingBytes', 1174 | value: function getRemainingBytes() { 1175 | return parseInt(this.length, 10) - parseInt(this.transferred, 10); 1176 | } 1177 | }, { 1178 | key: 'getEta', 1179 | value: function getEta() { 1180 | return this.length >= this.transferred ? this.getRemainingBytes() / this.speed * 1000000000 : 0; 1181 | } 1182 | }, { 1183 | key: 'flow', 1184 | value: function flow(chunk, onProgress) { 1185 | var chunkLength = chunk.length; 1186 | this.transferred += chunkLength; 1187 | this.speed = this.streamSpeed(chunkLength); 1188 | this.percentage = Math.round(this.transferred / this.length * 100); 1189 | if (!this.initial) { 1190 | this.eventStart = Date.now(); 1191 | this.initial = true; 1192 | } 1193 | if (this.length >= this.transferred || Date.now() - this.eventStart > this.emitDelay) { 1194 | this.eventStart = Date.now(); 1195 | 1196 | var progress = { 1197 | total: this.length, 1198 | transferred: this.transferred, 1199 | speed: this.speed, 1200 | eta: this.getEta() 1201 | }; 1202 | if (this.length) { 1203 | progress.remaining = this.getRemainingBytes(); 1204 | progress.percentage = this.percentage; 1205 | } 1206 | onProgress(progress); 1207 | } 1208 | } 1209 | }]); 1210 | 1211 | return Progress; 1212 | }(); 1213 | 1214 | exports.default = Progress; 1215 | },{"./speedometer":14}],10:[function(require,module,exports) { 1216 | 'use strict'; 1217 | 1218 | Object.defineProperty(exports, "__esModule", { 1219 | value: true 1220 | }); 1221 | exports.isFetchProgressSupported = isFetchProgressSupported; 1222 | 1223 | exports.default = function (_ref) { 1224 | var _ref$defaultSize = _ref.defaultSize, 1225 | defaultSize = _ref$defaultSize === undefined ? 0 : _ref$defaultSize, 1226 | _ref$emitDelay = _ref.emitDelay, 1227 | emitDelay = _ref$emitDelay === undefined ? 10 : _ref$emitDelay, 1228 | _ref$onProgress = _ref.onProgress, 1229 | onProgress = _ref$onProgress === undefined ? function () { 1230 | return null; 1231 | } : _ref$onProgress, 1232 | _ref$onComplete = _ref.onComplete, 1233 | onComplete = _ref$onComplete === undefined ? function () { 1234 | return null; 1235 | } : _ref$onComplete, 1236 | _ref$onError = _ref.onError, 1237 | onError = _ref$onError === undefined ? function () { 1238 | return null; 1239 | } : _ref$onError; 1240 | 1241 | return function FetchProgress(response) { 1242 | if (!isFetchProgressSupported()) { 1243 | return response; 1244 | } 1245 | var body = response.body, 1246 | headers = response.headers; 1247 | 1248 | var contentLength = headers.get('content-length') || defaultSize; 1249 | var progress = new _Progress2.default(contentLength, emitDelay); 1250 | var reader = body.getReader(); 1251 | var stream = new ReadableStream({ 1252 | start: function start(controller) { 1253 | function push() { 1254 | reader.read().then(function (_ref2) { 1255 | var done = _ref2.done, 1256 | value = _ref2.value; 1257 | 1258 | if (done) { 1259 | onComplete({}); 1260 | controller.close(); 1261 | return; 1262 | } 1263 | if (value) { 1264 | progress.flow(value, onProgress); 1265 | } 1266 | controller.enqueue(value); 1267 | push(); 1268 | }).catch(function (err) { 1269 | onError(err); 1270 | }); 1271 | } 1272 | 1273 | push(); 1274 | } 1275 | }); 1276 | return new Response(stream, { headers: headers }); 1277 | }; 1278 | }; 1279 | 1280 | var _Progress = require('./Progress'); 1281 | 1282 | var _Progress2 = _interopRequireDefault(_Progress); 1283 | 1284 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 1285 | 1286 | function isFetchProgressSupported() { 1287 | return typeof Response !== 'undefined' && typeof ReadableStream !== 'undefined'; 1288 | } 1289 | },{"./Progress":12}],6:[function(require,module,exports) { 1290 | 'use strict'; 1291 | 1292 | Object.defineProperty(exports, "__esModule", { 1293 | value: true 1294 | }); 1295 | 1296 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 1297 | 1298 | var _preact = require('preact'); 1299 | 1300 | var _index = require('../index'); 1301 | 1302 | var _index2 = _interopRequireDefault(_index); 1303 | 1304 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 1305 | 1306 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 1307 | 1308 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 1309 | 1310 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 1311 | 1312 | var ProgressItem = function (_Component) { 1313 | _inherits(ProgressItem, _Component); 1314 | 1315 | function ProgressItem() { 1316 | var _ref; 1317 | 1318 | var _temp, _this, _ret; 1319 | 1320 | _classCallCheck(this, ProgressItem); 1321 | 1322 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 1323 | args[_key] = arguments[_key]; 1324 | } 1325 | 1326 | return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = ProgressItem.__proto__ || Object.getPrototypeOf(ProgressItem)).call.apply(_ref, [this].concat(args))), _this), _this.src = null, _this.state = { 1327 | loaded: false, 1328 | progress: { 1329 | percentage: 0 1330 | } 1331 | }, _temp), _possibleConstructorReturn(_this, _ret); 1332 | } 1333 | 1334 | _createClass(ProgressItem, [{ 1335 | key: 'componentDidMount', 1336 | value: function componentDidMount() { 1337 | var _this2 = this; 1338 | 1339 | var self = this; 1340 | fetch(this.props.src).then((0, _index2.default)({ 1341 | onProgress: function onProgress(progress) { 1342 | self.setState({ progress: progress }); 1343 | }, 1344 | onError: function onError(err) { 1345 | console.log(err); 1346 | } 1347 | })).then(function (r) { 1348 | return r.blob(); 1349 | }).then(function (src) { 1350 | _this2.src = URL.createObjectURL(src); 1351 | _this2.setState({ 1352 | loaded: true 1353 | }); 1354 | }); 1355 | } 1356 | }, { 1357 | key: 'render', 1358 | value: function render() { 1359 | return this.props.render({ 1360 | loaded: this.state.loaded, 1361 | blob: this.src, 1362 | progress: this.state.progress 1363 | }); 1364 | } 1365 | }]); 1366 | 1367 | return ProgressItem; 1368 | }(_preact.Component); 1369 | 1370 | exports.default = ProgressItem; 1371 | },{"preact":7,"../index":10}],4:[function(require,module,exports) { 1372 | 'use strict'; 1373 | 1374 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 1375 | 1376 | var _preact = require('preact'); 1377 | 1378 | var _ProgressItem = require('./ProgressItem'); 1379 | 1380 | var _ProgressItem2 = _interopRequireDefault(_ProgressItem); 1381 | 1382 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 1383 | 1384 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 1385 | 1386 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 1387 | 1388 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 1389 | 1390 | var App = function (_Component) { 1391 | _inherits(App, _Component); 1392 | 1393 | function App() { 1394 | var _ref; 1395 | 1396 | var _temp, _this, _ret; 1397 | 1398 | _classCallCheck(this, App); 1399 | 1400 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 1401 | args[_key] = arguments[_key]; 1402 | } 1403 | 1404 | return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = App.__proto__ || Object.getPrototypeOf(App)).call.apply(_ref, [this].concat(args))), _this), _this.state = { 1405 | images: ['https://images.unsplash.com/photo-1514832510016-108f38c20162?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=dbb79afb2cb593a13ea63e3f4b393f95&auto=format', 'https://images.unsplash.com/photo-1499514694201-9e89bcba16c5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=14fa0571fb8d326c611e5aa87a5b843f&auto=format', 'https://images.unsplash.com/photo-1495198551082-4499d2dd5312?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=fad723e4a048edea0afb17605e11a853&auto=format&', 'https://images.unsplash.com/photo-1496935127680-16e7e9e5eba3?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=a2ece95cddc5ac4a95a1e9b8fbbe6a61&auto=format'] 1406 | }, _temp), _possibleConstructorReturn(_this, _ret); 1407 | } 1408 | 1409 | _createClass(App, [{ 1410 | key: 'render', 1411 | value: function render() { 1412 | return (0, _preact.h)( 1413 | 'div', 1414 | null, 1415 | (0, _preact.h)( 1416 | 'ul', 1417 | null, 1418 | this.state.images.map(function (image) { 1419 | return (0, _preact.h)( 1420 | 'li', 1421 | null, 1422 | (0, _preact.h)(_ProgressItem2.default, { 1423 | src: image, 1424 | width: '200', 1425 | height: '200', 1426 | render: function render(_ref2) { 1427 | var blob = _ref2.blob, 1428 | progress = _ref2.progress, 1429 | loaded = _ref2.loaded; 1430 | return (0, _preact.h)( 1431 | 'table', 1432 | { border: '1' }, 1433 | (0, _preact.h)( 1434 | 'tr', 1435 | null, 1436 | (0, _preact.h)( 1437 | 'th', 1438 | null, 1439 | 'Total' 1440 | ), 1441 | (0, _preact.h)( 1442 | 'th', 1443 | null, 1444 | 'Transferred' 1445 | ), 1446 | (0, _preact.h)( 1447 | 'th', 1448 | null, 1449 | 'Remaining' 1450 | ), 1451 | (0, _preact.h)( 1452 | 'th', 1453 | null, 1454 | 'ETA' 1455 | ), 1456 | (0, _preact.h)( 1457 | 'th', 1458 | null, 1459 | 'Speed' 1460 | ), 1461 | (0, _preact.h)( 1462 | 'th', 1463 | null, 1464 | 'Percentage' 1465 | ), 1466 | (0, _preact.h)( 1467 | 'th', 1468 | null, 1469 | 'Image' 1470 | ) 1471 | ), 1472 | (0, _preact.h)( 1473 | 'tr', 1474 | null, 1475 | (0, _preact.h)( 1476 | 'td', 1477 | null, 1478 | progress.total 1479 | ), 1480 | (0, _preact.h)( 1481 | 'td', 1482 | null, 1483 | progress.transferred 1484 | ), 1485 | (0, _preact.h)( 1486 | 'td', 1487 | null, 1488 | progress.remaining || 'N/A' 1489 | ), 1490 | (0, _preact.h)( 1491 | 'td', 1492 | null, 1493 | progress.eta 1494 | ), 1495 | (0, _preact.h)( 1496 | 'td', 1497 | null, 1498 | progress.speed 1499 | ), 1500 | (0, _preact.h)( 1501 | 'td', 1502 | null, 1503 | progress.percentage || 'N/A' 1504 | ), 1505 | (0, _preact.h)( 1506 | 'td', 1507 | null, 1508 | loaded ? (0, _preact.h)('img', { src: blob, width: '100', height: '100' }) : 'Loading' 1509 | ) 1510 | ) 1511 | ); 1512 | } 1513 | }) 1514 | ); 1515 | }) 1516 | ) 1517 | ); 1518 | } 1519 | }]); 1520 | 1521 | return App; 1522 | }(_preact.Component); 1523 | 1524 | (0, _preact.render)((0, _preact.h)(App, null), document.getElementById('root')); 1525 | },{"preact":7,"./ProgressItem":6}],16:[function(require,module,exports) { 1526 | 1527 | var OVERLAY_ID = '__parcel__error__overlay__'; 1528 | 1529 | var global = (1, eval)('this'); 1530 | var OldModule = module.bundle.Module; 1531 | 1532 | function Module(moduleName) { 1533 | OldModule.call(this, moduleName); 1534 | this.hot = { 1535 | data: module.bundle.hotData, 1536 | _acceptCallbacks: [], 1537 | _disposeCallbacks: [], 1538 | accept: function (fn) { 1539 | this._acceptCallbacks.push(fn || function () {}); 1540 | }, 1541 | dispose: function (fn) { 1542 | this._disposeCallbacks.push(fn); 1543 | } 1544 | }; 1545 | 1546 | module.bundle.hotData = null; 1547 | } 1548 | 1549 | module.bundle.Module = Module; 1550 | 1551 | var parent = module.bundle.parent; 1552 | if ((!parent || !parent.isParcelRequire) && typeof WebSocket !== 'undefined') { 1553 | var hostname = '' || location.hostname; 1554 | var protocol = location.protocol === 'https:' ? 'wss' : 'ws'; 1555 | var ws = new WebSocket(protocol + '://' + hostname + ':' + '32864' + '/'); 1556 | ws.onmessage = function (event) { 1557 | var data = JSON.parse(event.data); 1558 | 1559 | if (data.type === 'update') { 1560 | data.assets.forEach(function (asset) { 1561 | hmrApply(global.parcelRequire, asset); 1562 | }); 1563 | 1564 | data.assets.forEach(function (asset) { 1565 | if (!asset.isNew) { 1566 | hmrAccept(global.parcelRequire, asset.id); 1567 | } 1568 | }); 1569 | } 1570 | 1571 | if (data.type === 'reload') { 1572 | ws.close(); 1573 | ws.onclose = function () { 1574 | location.reload(); 1575 | }; 1576 | } 1577 | 1578 | if (data.type === 'error-resolved') { 1579 | console.log('[parcel] ✨ Error resolved'); 1580 | 1581 | removeErrorOverlay(); 1582 | } 1583 | 1584 | if (data.type === 'error') { 1585 | console.error('[parcel] 🚨 ' + data.error.message + '\n' + data.error.stack); 1586 | 1587 | removeErrorOverlay(); 1588 | 1589 | var overlay = createErrorOverlay(data); 1590 | document.body.appendChild(overlay); 1591 | } 1592 | }; 1593 | } 1594 | 1595 | function removeErrorOverlay() { 1596 | var overlay = document.getElementById(OVERLAY_ID); 1597 | if (overlay) { 1598 | overlay.remove(); 1599 | } 1600 | } 1601 | 1602 | function createErrorOverlay(data) { 1603 | var overlay = document.createElement('div'); 1604 | overlay.id = OVERLAY_ID; 1605 | 1606 | // html encode message and stack trace 1607 | var message = document.createElement('div'); 1608 | var stackTrace = document.createElement('pre'); 1609 | message.innerText = data.error.message; 1610 | stackTrace.innerText = data.error.stack; 1611 | 1612 | overlay.innerHTML = '
' + 'ERROR' + '🚨' + '
' + message.innerHTML + '
' + '
' + stackTrace.innerHTML + '
' + '
'; 1613 | 1614 | return overlay; 1615 | } 1616 | 1617 | function getParents(bundle, id) { 1618 | var modules = bundle.modules; 1619 | if (!modules) { 1620 | return []; 1621 | } 1622 | 1623 | var parents = []; 1624 | var k, d, dep; 1625 | 1626 | for (k in modules) { 1627 | for (d in modules[k][1]) { 1628 | dep = modules[k][1][d]; 1629 | if (dep === id || Array.isArray(dep) && dep[dep.length - 1] === id) { 1630 | parents.push(+k); 1631 | } 1632 | } 1633 | } 1634 | 1635 | if (bundle.parent) { 1636 | parents = parents.concat(getParents(bundle.parent, id)); 1637 | } 1638 | 1639 | return parents; 1640 | } 1641 | 1642 | function hmrApply(bundle, asset) { 1643 | var modules = bundle.modules; 1644 | if (!modules) { 1645 | return; 1646 | } 1647 | 1648 | if (modules[asset.id] || !bundle.parent) { 1649 | var fn = new Function('require', 'module', 'exports', asset.generated.js); 1650 | asset.isNew = !modules[asset.id]; 1651 | modules[asset.id] = [fn, asset.deps]; 1652 | } else if (bundle.parent) { 1653 | hmrApply(bundle.parent, asset); 1654 | } 1655 | } 1656 | 1657 | function hmrAccept(bundle, id) { 1658 | var modules = bundle.modules; 1659 | if (!modules) { 1660 | return; 1661 | } 1662 | 1663 | if (!modules[id] && bundle.parent) { 1664 | return hmrAccept(bundle.parent, id); 1665 | } 1666 | 1667 | var cached = bundle.cache[id]; 1668 | bundle.hotData = {}; 1669 | if (cached) { 1670 | cached.hot.data = bundle.hotData; 1671 | } 1672 | 1673 | if (cached && cached.hot && cached.hot._disposeCallbacks.length) { 1674 | cached.hot._disposeCallbacks.forEach(function (cb) { 1675 | cb(bundle.hotData); 1676 | }); 1677 | } 1678 | 1679 | delete bundle.cache[id]; 1680 | bundle(id); 1681 | 1682 | cached = bundle.cache[id]; 1683 | if (cached && cached.hot && cached.hot._acceptCallbacks.length) { 1684 | cached.hot._acceptCallbacks.forEach(function (cb) { 1685 | cb(); 1686 | }); 1687 | return true; 1688 | } 1689 | 1690 | return getParents(global.parcelRequire, id).some(function (id) { 1691 | return hmrAccept(global.parcelRequire, id); 1692 | }); 1693 | } 1694 | },{}]},{},[16,4]) 1695 | //# sourceMappingURL=/app.b185db3b.map -------------------------------------------------------------------------------- /dist/fetch-progress.db655106.js: -------------------------------------------------------------------------------- 1 | parcelRequire=function(e,r,n){var t="function"==typeof parcelRequire&&parcelRequire,i="function"==typeof require&&require;function u(n,o){if(!r[n]){if(!e[n]){var f="function"==typeof parcelRequire&&parcelRequire;if(!o&&f)return f(n,!0);if(t)return t(n,!0);if(i&&"string"==typeof n)return i(n);var c=new Error("Cannot find module '"+n+"'");throw c.code="MODULE_NOT_FOUND",c}a.resolve=function(r){return e[n][1][r]||r};var l=r[n]=new u.Module(n);e[n][0].call(l.exports,a,l,l.exports)}return r[n].exports;function a(e){return u(a.resolve(e))}}u.isParcelRequire=!0,u.Module=function(e){this.id=e,this.bundle=u,this.exports={}},u.modules=e,u.cache=r,u.parent=t;for(var o=0;ou&&(l=u),a=e;l--;)o===u&&(o=0),f[o]=f[0===o?u-1:o-1],o++;n&&(f[o-1]+=n);var s=f[o-1],v=f.length1&&void 0!==arguments[1]?arguments[1]:1e3;r(this,t),this.length=parseInt(e,10)||0,this.transferred=0,this.speed=0,this.streamSpeed=(0,n.default)(this.speed||5e3),this.initial=!1,this.emitDelay=i,this.eventStart=0,this.percentage=0}return e(t,[{key:"getRemainingBytes",value:function(){return parseInt(this.length,10)-parseInt(this.transferred,10)}},{key:"getEta",value:function(){return this.length>=this.transferred?this.getRemainingBytes()/this.speed*1e9:0}},{key:"flow",value:function(e,t){var n=e.length;if(this.transferred+=n,this.speed=this.streamSpeed(n),this.percentage=Math.round(this.transferred/this.length*100),this.initial||(this.eventStart=Date.now(),this.initial=!0),this.length>=this.transferred||Date.now()-this.eventStart>this.emitDelay){this.eventStart=Date.now();var i={total:this.length,transferred:this.transferred,speed:this.speed,eta:this.getEta()};this.length&&(i.remaining=this.getRemainingBytes(),i.percentage=this.percentage),t(i)}}}]),t}();exports.default=s; 5 | },{"./speedometer":3}],1:[function(require,module,exports) { 6 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isFetchProgressSupported=r,exports.default=function(e){var t=e.defaultSize,o=void 0===t?0:t,u=e.emitDelay,d=void 0===u?10:u,i=e.onProgress,a=void 0===i?function(){return null}:i,s=e.onComplete,f=void 0===s?function(){return null}:s;return function(e){if(!r())return e;var t=e.body,u=e.headers,i=u.get("content-length")||o,s=new n.default(i,d),l=t.getReader(),c=new ReadableStream({start:function(e){!function n(){l.read().then(function(t){var r=t.done,o=t.value;if(r)return f({}),void e.close();o&&s.flow(o,a),e.enqueue(o),n()})}()}});return new Response(c,{headers:u})}};var e=require("./Progress"),n=t(e);function t(e){return e&&e.__esModule?e:{default:e}}function r(){return"undefined"!=typeof Response&&"undefined"!=typeof ReadableStream} 7 | },{"./Progress":2}]},{},[1]) 8 | //# sourceMappingURL=/fetch-progress.db655106.map -------------------------------------------------------------------------------- /dist/fetch-progress.db655106.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["fetch-progress/speedometer.js","fetch-progress/Progress.js","fetch-progress/index.js"],"names":["speedometer","tick","maxTick","resolution","inc","timer","setInterval","unref","seconds","size","buffer","pointer","last","delta","dist","top","btm","length","Progress","emitDelay","parseInt","transferred","speed","streamSpeed","initial","eventStart","percentage","getRemainingBytes","chunk","onProgress","chunkLength","Math","round","Date","now","progress","total","eta","getEta","remaining","isFetchProgressSupported","defaultSize","onComplete","FetchProgress","response","body","headers","contentLength","get","reader","getReader","stream","ReadableStream","start","controller","push","read","then","done","value","close","flow","enqueue","Response"],"mappings":";;;;;;kBAWwBA;AAXxB;AACA,IAAIC,OAAO,CAAX;AACA,IAAIC,UAAU,KAAd;AACA,IAAIC,IEDYqC,SFCC,CAAjB,iBEDgBA;ADFhB,ADIA,IAAIpC,MAAM,SAANA,GAAM,GAAW;AACnBH,SAAQA,OAAO,CAAR,CEEM,EFFOC,OAApB,OEOC;AAAA,AFNF,CAFD,6BEIEuC,WAIC;AAAA,MAJDA,WAIC,oCAJa,CAIb;AAAA,AFJH,IAAIpC,QAAQC,YAAYF,GAAZ,CECVe,CFD4B,OAAOhB,CEIlC,SFJ0B,GAAsB,CAAvC,CAAZ;AEIG,AFHH,IAAIE,EEAFc,IFAQZ,KEGP,AFHH,EAAiBF,MAAME,KAAN,qBEAH,EAGX;AAAA,6BAFDsB,UAEC;AAAA,AFDY,MEDbA,GFCsB7B,OECrB,IFDY,CAAqBQ,OAArB,EAA8B,qBED9B;AAAA,AFEb,ICVmBU,EDUfT,KEFe,EFERN,EEFE,YFEYK,WAAW,CAAzB,CAAX;ACTA,ACOa,AFGb,GEDC,GFCGE,SAAS,CAAC,CAAD,CAAb,ECVYO,MAAZ,EAAsC;AAAA,ACSrC,AFED,MAAIN,ECXgBQ,QDWN,CCXwB,ADWtC,YEHAuB,UACC,iDDT+B,IAAM;ACSrC,AFGD,MEJAA,AFII9B,OAAQX,GEHX,IFGkB,CAAR,GAAaC,OAAxB,oBEJa;ADRyB,ACQzB,WAAM,IAAN;AAAA,AFMb,GELC,MFKM,UAASW,KAAT,EAAgB;ACbrB,ADcA,QAAIC,CCdCG,MAAL,ADcYhB,GCdEmB,IDcKR,IAAR,CCdYK,EDcIf,ICdb,EAAiB,CDc/B,CCdc,KAAwB,CAAtC;AACA,ACQF,AFME,QAAIY,CCdCO,ACQA,MFMMZ,GENGkC,CFMd,CCdA,CDciB7B,ECdE,CAAnB,IDcwBL,GENnB,CAAuBmC,AFMX,QENZ,EAAiC;ADPtC,ACQA,AFMAhC,QENI,CDRCU,ACQAkB,EFMEvC,GCdP,CDcA,ECda,CAAb,iBCQA,EAAiC;ADPjC,ACQE,SDRGsB,ICQIqB,ODRT,CCQE,EDRiB,2BAAY,KAAKtB,KAAL,IAAc,IAA1B,CAAnB;AACA,ACQC,AFMD,SCdKE,EDcEV,KCdP,CDcA,ECde,ADcA,KCdf;AACA,ACIsC,AFUpC,QENM+B,CDRH1B,CDcCR,EEVgC,GAIZiC,GDR1B,GAAiBzB,CDcCV,CEVoB,CAI9BoC,EFMN,EEVoC,AFUdlC,ECdxB,QDckC,CAAV;ACbxB,ACGsC,AFWpCD,QEPYoC,CDPTrB,IDcId,EEX6B,GAIZiC,CDP1B,CDcE,ECdgB,CAAlB,CDcoBlC,EEXkB,CAIxBoC,IFOanC,GEXW,SFWC,CAAZ,GAAgBF,OAAO,CAAvB,GAA2BE,UAAU,CAA5C,CAAlB;ACbF,ADcEA,SCdGe,UAAL,GAAkB,CAAlB;AACD,ACMC,AFQC,QERKqB,gBAAgBD,QAAQE,GAAR,CAAY,gBAAZ,KAAiCP,WAAvD;AACA,QAAMN,WAAW,uBAAaY,aAAb,EAA4B5B,SAA5B,CAAjB;AACA,AFQA,QERM8B,AFQFpC,KAAJ,EAAWH,EERImC,KAAKK,AFQFvC,SERH,CFQa,CER5B,AFQW,KAAuBE,KAAvB;AEPX,QAAMsC,SAAS,IAAIC,cAAJ,CAAmB;AAChCC,AFQF,QAAItC,GET8B,GFSxBL,OAAOC,OERT2C,GFQmB,CAAjB,CAAV,KETkC,EDPhB,ACQE;ADPpB,ACQI,AFQJ,QAAItC,KChBGI,CDgBGV,GERG6C,IAAT,AFQatC,CChBD,ECQI,GDRCA,ADgBX,GAAgBR,GChBnB,CDgBG,CChBmB,EAAtB,ADgB0B,CAAvB,GChByBW,ADgBEV,OAAOC,EChBA,KAAKU,KDgBOZ,IAAZ,EChBT,CDgB4B,CChBD,ADgBlB,EChBT,CAAnC,ADgBmEE,OAA9B,CAArC;ACfD,ACQOsC,iBAAOO,IAAP,GAAcC,IAAd,CAAmB,iBAAqB;AAAA,AFS9C,WAAO/C,KETqBgD,EFSdzC,EETgC,IFSvC,GAAgBd,EETKuD,IAAkB,IFSvC,GACH3C,GADG,GAEH,CAACA,MAAMC,GAAP,IAAcb,UAAd,GAA2BO,OAAOO,MAFtC;AET8C,AFY/C,GAnBD,aEOoC0C,KAAY,SAAZA,KAAY;AFajD,6BCnBU;AACP,ACMQ,aDND,GCMKD,EDNAzC,ECMJ,EAAU,EDNX,IAAe,KAAKI,WAApB,GACH,KAAKM,iBAAL,KAA2B,KAAKL,KAAhC,GAAwC,UADrC,GAEH,CAFJ;AAGD,ACIWoB,yBAAW,EAAX;AACAY,yBAAWM,KAAX;AACA;AACD,yBDLNhC,OAAOC,YAAY;AACtB,ACKQ,UDLFC,MCKM6B,KAAJ,EAAW,CDLC/B,MAAMX,MAA1B;AACA,ACKUkB,WDLLd,WAAL,CCKmBwC,GDLC/B,CCKV,CAAc6B,KAAd,EAAqB9B,EDL/B,QCKU;ADJV,ACKS,WDLJP,KAAL,GAAa,KAAKC,WAAL,CAAiBO,WAAjB,CAAb;AACA,ACKQwB,WDLH5B,UAAL,ECKmBoC,CDLD/B,KAAKC,CCKf,CAAmB2B,GDLT,CAAW,CCKrB,IDL0BtC,WAAL,GAAmB,KAAKJ,MAAxB,GAAiC,GAA5C,CAAlB;AACA,ACKQsC,UDLJ,CAAC,KAAK/B,OAAV,EAAmB;AACjB,ACKK,WAXD,EDMCC,UAAL,GAAkBQ,KAAKC,GAAL,EAAlB;AACA,ACKG,aDLEV,OAAL,GAAe,IAAf;AACD;AACD,ACKI+B,UDJF,KAAKtC,MAAL,IAAe,KAAKI,WAApB,IACAY,KAAKC,GAAL,KAAa,KAAKT,UAAlB,GAA+B,KAAKN,SAFtC,EAGE;AACA,ACEC,aDFIM,UAAL,GAAkBQ,KAAKC,GAAL,EAAlB;AChBgC,KAAnB,CAAf;ADkBE,ACEF,WAAO,CDFCC,GCEG4B,QDFQ,ACEZ,CAAaZ,MAAb,EAAqB,EAAEL,gBAAF,EAArB,CAAP;ADDIV,ACEL,GA7BD,cD2Ba,KAAKnB,MADG;AAEfI,ACEP,uBDFoB,KAAKA,WAFH;AAGfC,iBAAO,KAAKA,KAHG;AAIfe,AC3CR,eD2Ca,KAAKC,MAAL;AAJU,SAAjB;AAMA,YAAI,KAAKrB,MAAT,EAAiB;AACfkB,mBAASI,SAAT,GAAqB,KAAKZ,iBAAL,EAArB;AACAQ,mBAAST,UAAT,GAAsB,KAAKA,UAA3B;AACD;AACDG,AC/CC,SAASW,UD+CCL,QAAX,MC/CC,GAAoC;ADgDtC,AC/CH,SACE,OAAO4B,QAAP,KAAoB,WAApB,IAAmC,OAAOX,cAAP,KAA0B,WAD/D;ADgDC,AC7CF;;;;;;kBDJoBlC","file":"fetch-progress.db655106.map","sourcesContent":["// Thanks! https://github.com/mafintosh/speedometer\nvar tick = 1;\nvar maxTick = 65535;\nvar resolution = 4;\nvar inc = function() {\n tick = (tick + 1) & maxTick;\n};\n\nvar timer = setInterval(inc, (1000 / resolution) | 0);\nif (timer.unref) timer.unref();\n\nexport default function speedometer(seconds) {\n var size = resolution * (seconds || 5);\n var buffer = [0];\n var pointer = 1;\n var last = (tick - 1) & maxTick;\n\n return function(delta) {\n var dist = (tick - last) & maxTick;\n if (dist > size) dist = size;\n last = tick;\n\n while (dist--) {\n if (pointer === size) pointer = 0;\n buffer[pointer] = buffer[pointer === 0 ? size - 1 : pointer - 1];\n pointer++;\n }\n\n if (delta) buffer[pointer - 1] += delta;\n\n var top = buffer[pointer - 1];\n var btm = buffer.length < size ? 0 : buffer[pointer === size ? 0 : pointer];\n\n return buffer.length < resolution\n ? top\n : (top - btm) * resolution / buffer.length;\n };\n}\n","import speedometer from './speedometer';\n\nexport default class Progress {\n constructor(length, emitDelay = 1000) {\n this.length = parseInt(length, 10) || 0;\n this.transferred = 0;\n this.speed = 0;\n this.streamSpeed = speedometer(this.speed || 5000);\n this.initial = false;\n this.emitDelay = emitDelay;\n this.eventStart = 0;\n this.percentage = 0;\n }\n\n getRemainingBytes() {\n return parseInt(this.length, 10) - parseInt(this.transferred, 10);\n }\n\n getEta() {\n return this.length >= this.transferred\n ? this.getRemainingBytes() / this.speed * 1000000000\n : 0;\n }\n\n flow(chunk, onProgress) {\n const chunkLength = chunk.length;\n this.transferred += chunkLength;\n this.speed = this.streamSpeed(chunkLength);\n this.percentage = Math.round(this.transferred / this.length * 100);\n if (!this.initial) {\n this.eventStart = Date.now();\n this.initial = true;\n }\n if (\n this.length >= this.transferred ||\n Date.now() - this.eventStart > this.emitDelay\n ) {\n this.eventStart = Date.now();\n\n const progress = {\n total: this.length,\n transferred: this.transferred,\n speed: this.speed,\n eta: this.getEta(),\n };\n if (this.length) {\n progress.remaining = this.getRemainingBytes();\n progress.percentage = this.percentage;\n }\n onProgress(progress);\n }\n }\n}\n","import Progress from './Progress';\n\nexport function isFetchProgressSupported() {\n return (\n typeof Response !== 'undefined' && typeof ReadableStream !== 'undefined'\n );\n}\nexport default function({\n defaultSize = 0,\n emitDelay = 10,\n onProgress = () => null,\n onComplete = () => null,\n}) {\n return function FetchProgress(response) {\n if (!isFetchProgressSupported()) {\n return response;\n }\n const { body, headers } = response;\n const contentLength = headers.get('content-length') || defaultSize;\n const progress = new Progress(contentLength, emitDelay);\n const reader = body.getReader();\n const stream = new ReadableStream({\n start(controller) {\n function push() {\n reader.read().then(({ done, value }) => {\n if (done) {\n onComplete({});\n controller.close();\n return;\n }\n if (value) {\n progress.flow(value, onProgress);\n }\n controller.enqueue(value);\n push();\n });\n }\n\n push();\n },\n });\n return new Response(stream, { headers });\n };\n}\n"]} -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple demo 5 | 6 | 7 | 8 |

This is demo page, image is pulled from unspalsh.com,

9 |

If server dont expose Content-Length header then progress report like Remaining, Percentage and total won't be seen 10 |

11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/ProgressItem.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import fetchProgress from '../index'; 3 | 4 | class ProgressItem extends Component { 5 | src = null; 6 | state = { 7 | loaded: false, 8 | progress: { 9 | percentage: 0, 10 | }, 11 | }; 12 | componentDidMount() { 13 | const self = this; 14 | fetch(this.props.src) 15 | .then( 16 | fetchProgress({ 17 | onProgress(progress) { 18 | self.setState({ progress }); 19 | }, 20 | onError(err) { 21 | console.log(err); 22 | }, 23 | }) 24 | ) 25 | .then((r) => r.blob()) 26 | .then((src) => { 27 | this.src = URL.createObjectURL(src); 28 | this.setState({ 29 | loaded: true, 30 | }); 31 | }); 32 | } 33 | 34 | render() { 35 | return this.props.render({ 36 | loaded: this.state.loaded, 37 | blob: this.src, 38 | progress: this.state.progress, 39 | }); 40 | } 41 | } 42 | export default ProgressItem; 43 | -------------------------------------------------------------------------------- /examples/app.js: -------------------------------------------------------------------------------- 1 | import { render, h, Component } from 'preact'; 2 | import ProgressItem from './ProgressItem'; 3 | 4 | class App extends Component { 5 | state = { 6 | images: [ 7 | 'https://images.unsplash.com/photo-1514832510016-108f38c20162?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=dbb79afb2cb593a13ea63e3f4b393f95&auto=format', 8 | 'https://images.unsplash.com/photo-1499514694201-9e89bcba16c5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=14fa0571fb8d326c611e5aa87a5b843f&auto=format', 9 | 'https://images.unsplash.com/photo-1495198551082-4499d2dd5312?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=fad723e4a048edea0afb17605e11a853&auto=format&', 10 | 'https://images.unsplash.com/photo-1496935127680-16e7e9e5eba3?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=a2ece95cddc5ac4a95a1e9b8fbbe6a61&auto=format', 11 | ], 12 | }; 13 | render() { 14 | return ( 15 |
16 |
    17 | {this.state.images.map(image => ( 18 |
  • 19 | ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 48 | 49 |
    TotalTransferredRemainingETASpeedPercentageImage
    {progress.total}{progress.transferred}{progress.remaining || 'N/A'}{progress.eta}{progress.speed}{progress.percentage || 'N/A'} 42 | {loaded ? ( 43 | 44 | ) : ( 45 | 'Loading' 46 | )} 47 |
    50 | )} 51 | /> 52 |
  • 53 | ))} 54 |
55 |
56 | ); 57 | } 58 | } 59 | 60 | render(, document.getElementById('root')); 61 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Simple demo 5 | 6 | 7 | 8 |

This is demo page, image is pulled from unspalsh.com,

9 |

If server dont expose Content-Length header then progress report like Remaining, Percentage and total won't be seen 10 |

11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export = fetchProgress; 4 | 5 | declare function fetchProgress(initOptions?: fetchProgress.FetchProgressInitOptions): fetchProgress.FetchProgressInterceptor; 6 | 7 | declare namespace fetchProgress { 8 | export function isFetchProgressSupported(): boolean; 9 | export type FetchProgressInterceptor = (response: Response) => Promise; 10 | 11 | export interface FetchProgressData { 12 | total: number; 13 | transferred: number; 14 | speed: number; 15 | eta: number; 16 | } 17 | 18 | export interface FetchProgressInitOptions { 19 | defaultSize?: number; 20 | emitDelay?: number; 21 | onProgress?: (progress: FetchProgressData) => void; 22 | onComplete?: () => void; 23 | onError?: (error: Error) => void; 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Progress from './Progress'; 2 | 3 | export function isFetchProgressSupported() { 4 | return ( 5 | typeof Response !== 'undefined' && typeof ReadableStream !== 'undefined' 6 | ); 7 | } 8 | export default function({ 9 | defaultSize = 0, 10 | emitDelay = 10, 11 | onProgress = () => null, 12 | onComplete = () => null, 13 | onError = () => null, 14 | }) { 15 | return function FetchProgress(response) { 16 | if (!isFetchProgressSupported()) { 17 | return response; 18 | } 19 | const { body, headers, status } = response; 20 | const contentLength = headers.get('content-length') || defaultSize; 21 | const progress = new Progress(contentLength, emitDelay); 22 | const reader = body.getReader(); 23 | const stream = new ReadableStream({ 24 | start(controller) { 25 | function push() { 26 | reader 27 | .read() 28 | .then(({ done, value }) => { 29 | if (done) { 30 | onComplete({}); 31 | controller.close(); 32 | return; 33 | } 34 | if (value) { 35 | progress.flow( 36 | value, 37 | onProgress 38 | ); 39 | } 40 | controller.enqueue(value); 41 | push(); 42 | }) 43 | .catch((err) => { 44 | onError(err); 45 | }); 46 | } 47 | 48 | push(); 49 | }, 50 | }); 51 | return new Response(stream, { headers, status }); 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fetch-progress", 3 | "version": "1.2.0", 4 | "description": "Progress of response for fetch API", 5 | "main": "index.js", 6 | "typings": "index.d.ts", 7 | "scripts": { 8 | "dev": "parcel examples/index.html", 9 | "build:demo": "parcel build examples/index.html --public-url ./", 10 | "deploy": "gh-pages -d dist" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "homepage": "https://github.com/samundrak/fetch-progress", 16 | "devDependencies": { 17 | "babel-plugin-transform-class-properties": "^6.24.1", 18 | "gh-pages": "^1.1.0", 19 | "parcel-bundler": "^1.7.0", 20 | "preact": "^8.2.7" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /speedometer.js: -------------------------------------------------------------------------------- 1 | // Thanks! https://github.com/mafintosh/speedometer 2 | var tick = 1; 3 | var maxTick = 65535; 4 | var resolution = 4; 5 | var inc = function() { 6 | tick = (tick + 1) & maxTick; 7 | }; 8 | 9 | var timer = setInterval(inc, (1000 / resolution) | 0); 10 | if (timer.unref) timer.unref(); 11 | 12 | export default function speedometer(seconds) { 13 | var size = resolution * (seconds || 5); 14 | var buffer = [0]; 15 | var pointer = 1; 16 | var last = (tick - 1) & maxTick; 17 | 18 | return function(delta) { 19 | var dist = (tick - last) & maxTick; 20 | if (dist > size) dist = size; 21 | last = tick; 22 | 23 | while (dist--) { 24 | if (pointer === size) pointer = 0; 25 | buffer[pointer] = buffer[pointer === 0 ? size - 1 : pointer - 1]; 26 | pointer++; 27 | } 28 | 29 | if (delta) buffer[pointer - 1] += delta; 30 | 31 | var top = buffer[pointer - 1]; 32 | var btm = buffer.length < size ? 0 : buffer[pointer === size ? 0 : pointer]; 33 | 34 | return buffer.length < resolution 35 | ? top 36 | : (top - btm) * resolution / buffer.length; 37 | }; 38 | } 39 | --------------------------------------------------------------------------------