a(c,t)))break e;e[r]=c,e[s]=t,r=s}}}return n}function a(e,n){var t=e.sortIndex-n.sortIndex;return 0!==t?t:e.id-n.id}if("object"===typeof performance&&"function"===typeof performance.now){var o=performance;n.unstable_now=function(){return o.now()}}else{var u=Date,i=u.now();n.unstable_now=function(){return u.now()-i}}var s=[],c=[],f=1,d=null,p=3,h=!1,m=!1,v=!1,g="function"===typeof setTimeout?setTimeout:null,y="function"===typeof clearTimeout?clearTimeout:null,b="undefined"!==typeof setImmediate?setImmediate:null;function w(e){for(var n=r(c);null!==n;){if(null===n.callback)l(c);else{if(!(n.startTime<=e))break;l(c),n.sortIndex=n.expirationTime,t(s,n)}n=r(c)}}function k(e){if(v=!1,w(e),!m)if(null!==r(s))m=!0,D(S);else{var n=r(c);null!==n&&R(k,n.startTime-e)}}function S(e,t){m=!1,v&&(v=!1,y(C),C=-1),h=!0;var a=p;try{for(w(t),d=r(s);null!==d&&(!(d.expirationTime>t)||e&&!z());){var o=d.callback;if("function"===typeof o){d.callback=null,p=d.priorityLevel;var u=o(d.expirationTime<=t);t=n.unstable_now(),"function"===typeof u?d.callback=u:d===r(s)&&l(s),w(t)}else l(s);d=r(s)}if(null!==d)var i=!0;else{var f=r(c);null!==f&&R(k,f.startTime-t),i=!1}return i}finally{d=null,p=a,h=!1}}"undefined"!==typeof navigator&&void 0!==navigator.scheduling&&void 0!==navigator.scheduling.isInputPending&&navigator.scheduling.isInputPending.bind(navigator.scheduling);var x,E=!1,_=null,C=-1,P=5,N=-1;function z(){return!(n.unstable_now()-Ne||125o?(e.sortIndex=a,t(c,e),null===r(s)&&e===r(c)&&(v?(y(C),C=-1):v=!0,R(k,a-o))):(e.sortIndex=u,t(s,e),m||h||(m=!0,D(S))),e},n.unstable_shouldYield=z,n.unstable_wrapCallback=function(e){var n=p;return function(){var t=p;p=n;try{return e.apply(this,arguments)}finally{p=t}}}},296:function(e,n,t){e.exports=t(813)}},n={};function t(r){var l=n[r];if(void 0!==l)return l.exports;var a=n[r]={exports:{}};return e[r](a,a.exports,t),a.exports}!function(){var e,n=t(791),r=t(250);function l(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=new Array(n);t0;throw new Error("unsupported direction")}function s(e,n){return!!i(e,n)||null!=e.parentElement&&s(e.parentElement,n)}function c(e,n){void 0===n&&(n={});var t=n.insertAt;if(e&&"undefined"!==typeof document){var r=document.head||document.getElementsByTagName("head")[0],l=document.createElement("style");l.type="text/css","top"===t&&r.firstChild?r.insertBefore(l,r.firstChild):r.appendChild(l),l.styleSheet?l.styleSheet.cssText=e:l.appendChild(document.createTextNode(e))}}!function(e){e[e.UP=-1]="UP",e[e.DOWN=1]="DOWN"}(e||(e={}));c(".lds-ellipsis {\n display: inline-block;\n position: relative;\n width: 64px;\n height: 64px; }\n\n.lds-ellipsis div {\n position: absolute;\n top: 27px;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #363636;\n animation-timing-function: cubic-bezier(0, 1, 1, 0); }\n\n.lds-ellipsis div:nth-child(1) {\n left: 6px;\n animation: lds-ellipsis1 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(2) {\n left: 6px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(3) {\n left: 26px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(4) {\n left: 45px;\n animation: lds-ellipsis3 0.6s infinite; }\n\n@keyframes lds-ellipsis1 {\n 0% {\n transform: scale(0); }\n 100% {\n transform: scale(1); } }\n\n@keyframes lds-ellipsis3 {\n 0% {\n transform: scale(1); }\n 100% {\n transform: scale(0); } }\n\n@keyframes lds-ellipsis2 {\n 0% {\n transform: translate(0, 0); }\n 100% {\n transform: translate(19px, 0); } }\n");var f=function(){return n.createElement("div",{className:"lds-ellipsis"},n.createElement("div",null),n.createElement("div",null),n.createElement("div",null),n.createElement("div",null))},d=function(){return n.createElement("div",null,n.createElement("p",null,"\u21a7\xa0\xa0pull to refresh\xa0\xa0\u21a7"))};c(".ptr,\n.ptr__children {\n height: 100%;\n width: 100%;\n overflow: hidden;\n -webkit-overflow-scrolling: touch;\n position: relative; }\n\n.ptr.ptr--fetch-more-treshold-breached .ptr__fetch-more {\n display: block; }\n\n.ptr__fetch-more {\n display: none; }\n\n/**\n * Pull down transition \n */\n.ptr__children,\n.ptr__pull-down {\n transition: transform 0.2s cubic-bezier(0, 0, 0.31, 1); }\n\n.ptr__pull-down {\n position: absolute;\n overflow: hidden;\n left: 0;\n right: 0;\n top: 0;\n visibility: hidden; }\n .ptr__pull-down > div {\n display: none; }\n\n.ptr--dragging {\n /**\n * Hide PullMore content is treshold breached\n */\n /**\n * Otherwize, display content\n */ }\n .ptr--dragging.ptr--pull-down-treshold-breached .ptr__pull-down--pull-more {\n display: none; }\n .ptr--dragging .ptr__pull-down--pull-more {\n display: block; }\n\n.ptr--pull-down-treshold-breached {\n /**\n * Force opacity to 1 is pull down trashold breached\n */\n /**\n * And display loader\n */ }\n .ptr--pull-down-treshold-breached .ptr__pull-down {\n opacity: 1 !important; }\n .ptr--pull-down-treshold-breached .ptr__pull-down--loading {\n display: block; }\n\n.ptr__loader {\n margin: 0 auto;\n text-align: center; }\n");var p=function(t){var r=t.isPullable,l=void 0===r||r,a=t.canFetchMore,o=void 0!==a&&a,u=t.onRefresh,i=t.onFetchMore,c=t.refreshingContent,p=void 0===c?n.createElement(f,null):c,h=t.pullingContent,m=void 0===h?n.createElement(d,null):h,v=t.children,g=t.pullDownThreshold,y=void 0===g?67:g,b=t.fetchMoreThreshold,w=void 0===b?100:b,k=t.maxPullDownDistance,S=void 0===k?95:k,x=t.resistance,E=void 0===x?1:x,_=t.backgroundColor,C=t.className,P=void 0===C?"":C,N=(0,n.useRef)(null),z=(0,n.useRef)(null),T=(0,n.useRef)(null),L=(0,n.useRef)(null),M=!1,D=!1,R=!1,F=0,O=0;(0,n.useEffect)((function(){if(l&&z&&z.current){var e=z.current;return e.addEventListener("touchstart",U,{passive:!0}),e.addEventListener("mousedown",U),e.addEventListener("touchmove",A,{passive:!1}),e.addEventListener("mousemove",A),window.addEventListener("scroll",V),e.addEventListener("touchend",$),e.addEventListener("mouseup",$),document.body.addEventListener("mouseleave",$),function(){e.removeEventListener("touchstart",U),e.removeEventListener("mousedown",U),e.removeEventListener("touchmove",A),e.removeEventListener("mousemove",A),window.removeEventListener("scroll",V),e.removeEventListener("touchend",$),e.removeEventListener("mouseup",$),document.body.removeEventListener("mouseleave",$)}}}),[v,l,u,y,S,o,w]),(0,n.useEffect)((function(){var e;(null===(e=N)||void 0===e?void 0:e.current)&&(N.current.classList.contains("ptr--fetch-more-treshold-breached")||o&&I()=y&&(R=!0,M=!0,N.current.classList.remove("ptr--dragging"),N.current.classList.add("ptr--pull-down-treshold-breached")),n>=S||(T.current.style.opacity=(n/65).toString(),z.current.style.overflow="visible",z.current.style.transform="translate(0px, "+n+"px)",T.current.style.visibility="visible")}},V=function(e){D||o&&I()0;throw new Error("unsupported direction")}function d(e,n){void 0===n&&(n={});var t=n.insertAt;if(e&&"undefined"!==typeof document){var r=document.head||document.getElementsByTagName("head")[0],l=document.createElement("style");l.type="text/css","top"===t&&r.firstChild?r.insertBefore(l,r.firstChild):r.appendChild(l),l.styleSheet?l.styleSheet.cssText=e:l.appendChild(document.createTextNode(e))}}!function(e){e[e.UP=-1]="UP",e[e.DOWN=1]="DOWN"}(r||(r={}));d(".lds-ellipsis {\n display: inline-block;\n position: relative;\n width: 64px;\n height: 64px; }\n\n.lds-ellipsis div {\n position: absolute;\n top: 27px;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #363636;\n animation-timing-function: cubic-bezier(0, 1, 1, 0); }\n\n.lds-ellipsis div:nth-child(1) {\n left: 6px;\n animation: lds-ellipsis1 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(2) {\n left: 6px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(3) {\n left: 26px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(4) {\n left: 45px;\n animation: lds-ellipsis3 0.6s infinite; }\n\n@keyframes lds-ellipsis1 {\n 0% {\n transform: scale(0); }\n 100% {\n transform: scale(1); } }\n\n@keyframes lds-ellipsis3 {\n 0% {\n transform: scale(1); }\n 100% {\n transform: scale(0); } }\n\n@keyframes lds-ellipsis2 {\n 0% {\n transform: translate(0, 0); }\n 100% {\n transform: translate(19px, 0); } }\n");var p=function(){return i.a.createElement("div",{className:"lds-ellipsis"},i.a.createElement("div",null),i.a.createElement("div",null),i.a.createElement("div",null),i.a.createElement("div",null))},m=function(){return i.a.createElement("div",null,i.a.createElement("p",null,"\u21a7\xa0\xa0Pull To Refresh\xa0\xa0\u21a7"))};d(".ptr,\n.ptr__children {\n height: 100%;\n overflow: hidden;\n -webkit-overflow-scrolling: touch;\n position: relative;\n z-index: 1; }\n\n.ptr__children,\n.ptr__pull-down {\n transition: transform 0.2s cubic-bezier(0, 0, 0.31, 1); }\n\n.ptr__pull-down {\n position: absolute;\n overflow: hidden;\n left: 0;\n right: 0;\n top: 0;\n visibility: 'hidden';\n text-align: center; }\n .ptr__pull-down > div {\n margin: 0 auto; }\n .ptr__pull-down > .ptr__pull-down--loading {\n display: none; }\n .ptr__pull-down > .ptr__pull-down--pull-more {\n display: none; }\n\n.ptr--dragging.ptr--treshold-breached .ptr__pull-down--pull-more {\n display: none; }\n\n.ptr--dragging .ptr__pull-down--pull-more {\n display: block; }\n\n.ptr--treshold-breached .ptr__pull-down {\n opacity: 1 !important; }\n\n.ptr--treshold-breached .ptr__pull-down--loading {\n display: block; }\n");var v=function(e){var n=e.refreshingContent,t=void 0===n?i.a.createElement(p,null):n,o=e.pullingContent,s=void 0===o?i.a.createElement(m,null):o,a=e.pullDownThreshold,c=void 0===a?67:a,d=e.maxPullDownDistance,v=void 0===d?95:d,f=e.onRefresh,h=e.backgroundColor,g=e.isPullable,E=void 0===g||g,y=e.children,b=e.className,w=void 0===b?"":b,_=Object(l.useRef)(null),x=Object(l.useRef)(null),L=Object(l.useRef)(null),k=!1,N=!1,O=0,T=0;Object(l.useEffect)((function(){if(E&&x&&x.current)return x.current.addEventListener("touchstart",j,{passive:!0}),x.current.addEventListener("mousedown",j),x.current.addEventListener("touchmove",P,{passive:!1}),x.current.addEventListener("mousemove",P),x.current.addEventListener("touchend",R),x.current.addEventListener("mouseup",R),document.body.addEventListener("mouseleave",R),function(){E&&x&&x.current&&(x.current.removeEventListener("touchstart",j),x.current.removeEventListener("mousedown",j),x.current.removeEventListener("touchmove",P),x.current.removeEventListener("mousemove",P),x.current.removeEventListener("touchend",R),x.current.removeEventListener("mouseup",R),document.body.removeEventListener("mouseleave",R))}}),[E]),Object(l.useEffect)((function(){C()}),[y]);var C=function(){requestAnimationFrame((function(){x.current.style.overflowX="hidden",x.current.style.overflowY="auto",x.current.style.transform="translate(0px, 0px)",L.current.style.opacity="0",_.current.classList.remove("ptr--treshold-breached"),_.current.classList.remove("ptr--dragging")}))},j=function(e){N=!1,e instanceof MouseEvent&&(O=e.pageY),e instanceof TouchEvent&&(O=e.touches[0].pageY),T=O,"touchstart"===e.type&&function e(n,t){return!!u(n,t)||null!=n.parentElement&&e(n.parentElement,t)}(e.target,r.UP)||x.current.getBoundingClientRect().top<0||(N=!0)},P=function(e){e.preventDefault(),N&&(T=e instanceof TouchEvent?e.touches[0].pageY:e.pageY,_.current.classList.add("ptr--dragging"),T=c&&(N=!0,k=!0,_.current.classList.remove("ptr--dragging"),_.current.classList.add("ptr--treshold-breached")),T-O>v||(L.current.style.opacity=((T-O)/65).toString(),x.current.style.overflow="visible",x.current.style.transform="translate(0px, "+(T-O)+"px)",L.current.style.visibility="visible")))},R=function(){if(N=!1,O=0,T=0,!k)return L.current.style.visibility="hidden",void C();x.current.style.overflow="visible",x.current.style.transform="translate(0px, "+c+"px)",k=!1,f()};return i.a.createElement("div",{className:"ptr "+w,style:{backgroundColor:h},ref:_},i.a.createElement("div",{className:"ptr__pull-down",ref:L},i.a.createElement("div",{className:"ptr__pull-down--loading"},t),i.a.createElement("div",{className:"ptr__pull-down--pull-more"},s)),i.a.createElement("div",{className:"ptr__children",ref:x},y))},f=function(){var e=["foo","bar","baz","foo"],n=Object(l.useState)(e),t=Object(c.a)(n,2),r=t[0],o=t[1];return i.a.createElement("div",{className:"App"},i.a.createElement(v,{onRefresh:function(){setTimeout((function(){o([].concat(Object(a.a)(r),e))}),1500)}},i.a.createElement(i.a.Fragment,null,i.a.createElement("header",{className:"App-header"},"Pull To Refresh"),i.a.createElement("div",{className:"App-container"},i.a.createElement("ul",null,r.map((function(e,n){return i.a.createElement("li",{key:n},e)})))))))};Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));s.a.render(i.a.createElement(f,null),document.getElementById("root")),"serviceWorker"in navigator&&navigator.serviceWorker.ready.then((function(e){e.unregister()}))}],[[5,1,2]]]);
//# sourceMappingURL=main.9bf6d823.chunk.js.map
================================================
FILE: docs/static/js/main.b28d2740.chunk.js
================================================
(this["webpackJsonpreact-simple-pull-to-refresh-playground"]=this["webpackJsonpreact-simple-pull-to-refresh-playground"]||[]).push([[0],[,,,,,function(e,t,n){e.exports=n(13)},,,,,function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){"use strict";n.r(t);var r,l=n(0),a=n.n(l),o=n(3),c=n.n(o),i=(n(10),n(4)),s=n(1);n(11);function u(e,t){if(!function(e){var t=getComputedStyle(e).overflowY;return e===document.scrollingElement&&"visible"===t||("scroll"===t||"auto"===t)}(e))return!1;if(t===r.DOWN)return e.scrollTop+e.clientHeight0;throw new Error("unsupported direction")}function d(e,t){void 0===t&&(t={});var n=t.insertAt;if(e&&"undefined"!==typeof document){var r=document.head||document.getElementsByTagName("head")[0],l=document.createElement("style");l.type="text/css","top"===n&&r.firstChild?r.insertBefore(l,r.firstChild):r.appendChild(l),l.styleSheet?l.styleSheet.cssText=e:l.appendChild(document.createTextNode(e))}}!function(e){e[e.UP=-1]="UP",e[e.DOWN=1]="DOWN"}(r||(r={}));d(".lds-ellipsis {\n display: inline-block;\n position: relative;\n width: 64px;\n height: 64px; }\n\n.lds-ellipsis div {\n position: absolute;\n top: 27px;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #363636;\n animation-timing-function: cubic-bezier(0, 1, 1, 0); }\n\n.lds-ellipsis div:nth-child(1) {\n left: 6px;\n animation: lds-ellipsis1 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(2) {\n left: 6px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(3) {\n left: 26px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(4) {\n left: 45px;\n animation: lds-ellipsis3 0.6s infinite; }\n\n@keyframes lds-ellipsis1 {\n 0% {\n transform: scale(0); }\n 100% {\n transform: scale(1); } }\n\n@keyframes lds-ellipsis3 {\n 0% {\n transform: scale(1); }\n 100% {\n transform: scale(0); } }\n\n@keyframes lds-ellipsis2 {\n 0% {\n transform: translate(0, 0); }\n 100% {\n transform: translate(19px, 0); } }\n");var m=function(){return a.a.createElement("div",{className:"lds-ellipsis"},a.a.createElement("div",null),a.a.createElement("div",null),a.a.createElement("div",null),a.a.createElement("div",null))},p=function(){return a.a.createElement("div",null,a.a.createElement("p",null,"\u21a7\xa0\xa0pull to refresh\xa0\xa0\u21a7"))};d(".ptr,\n.ptr__children {\n height: 100%;\n width: 100%;\n overflow: hidden;\n -webkit-overflow-scrolling: touch;\n position: relative; }\n\n.ptr.ptr--fetch-more-treshold-breached .ptr__fetch-more {\n display: block; }\n\n.ptr__fetch-more {\n display: none; }\n\n/**\n * Pull down transition \n */\n.ptr__children,\n.ptr__pull-down {\n transition: transform 0.2s cubic-bezier(0, 0, 0.31, 1); }\n\n.ptr__pull-down {\n position: absolute;\n overflow: hidden;\n left: 0;\n right: 0;\n top: 0;\n visibility: hidden; }\n .ptr__pull-down > div {\n display: none; }\n\n.ptr--dragging {\n /**\n * Hide PullMore content is treshold breached\n */\n /**\n * Otherwize, display content\n */ }\n .ptr--dragging.ptr--pull-down-treshold-breached .ptr__pull-down--pull-more {\n display: none; }\n .ptr--dragging .ptr__pull-down--pull-more {\n display: block; }\n\n.ptr--pull-down-treshold-breached {\n /**\n * Force opacity to 1 is pull down trashold breached\n */\n /**\n * And display loader\n */ }\n .ptr--pull-down-treshold-breached .ptr__pull-down {\n opacity: 1 !important; }\n .ptr--pull-down-treshold-breached .ptr__pull-down--loading {\n display: block; }\n\n.ptr__loader {\n margin: 0 auto;\n text-align: center; }\n");var h=function(e){var t=e.isPullable,n=void 0===t||t,o=e.canFetchMore,c=void 0!==o&&o,i=e.onRefresh,s=e.onFetchMore,d=e.refreshingContent,h=void 0===d?a.a.createElement(m,null):d,f=e.pullingContent,v=void 0===f?a.a.createElement(p,null):f,E=e.children,b=e.pullDownThreshold,w=void 0===b?67:b,g=e.fetchMoreThreshold,y=void 0===g?100:g,_=e.maxPullDownDistance,x=void 0===_?95:_,D=e.resistance,T=void 0===D?1:D,N=e.backgroundColor,P=e.className,L=void 0===P?"":P,M=Object(l.useRef)(null),O=Object(l.useRef)(null),k=Object(l.useRef)(null),j=Object(l.useRef)(null),C=!1,F=!1,R=!1,S=0,A=0;Object(l.useEffect)((function(){if(n&&O&&O.current)return O.current.addEventListener("touchstart",B,{passive:!0}),O.current.addEventListener("mousedown",B),O.current.addEventListener("touchmove",H,{passive:!1}),O.current.addEventListener("mousemove",H),window.addEventListener("scroll",W),O.current.addEventListener("touchend",U),O.current.addEventListener("mouseup",U),document.body.addEventListener("mouseleave",U),function(){n&&O&&O.current&&(O.current.removeEventListener("touchstart",B),O.current.removeEventListener("mousedown",B),O.current.removeEventListener("touchmove",H),O.current.removeEventListener("mousemove",H),window.removeEventListener("scroll",W),O.current.removeEventListener("touchend",U),O.current.removeEventListener("mouseup",U),document.body.removeEventListener("mouseleave",U))}}),[E,n,i,w,x,c,y]),Object(l.useEffect)((function(){var e;(null===(e=M)||void 0===e?void 0:e.current)&&(M.current.classList.contains("ptr--fetch-more-treshold-breached")||c&&z()=w&&(R=!0,C=!0,M.current.classList.remove("ptr--dragging"),M.current.classList.add("ptr--pull-down-treshold-breached")),t>=x||(k.current.style.opacity=(t/65).toString(),O.current.style.overflow="visible",O.current.style.transform="translate(0px, "+t+"px)",k.current.style.visibility="visible")}},W=function(e){F||c&&z()0;throw new Error("unsupported direction")}function d(e,t){void 0===t&&(t={});var n=t.insertAt;if(e&&"undefined"!==typeof document){var r=document.head||document.getElementsByTagName("head")[0],l=document.createElement("style");l.type="text/css","top"===n&&r.firstChild?r.insertBefore(l,r.firstChild):r.appendChild(l),l.styleSheet?l.styleSheet.cssText=e:l.appendChild(document.createTextNode(e))}}!function(e){e[e.UP=-1]="UP",e[e.DOWN=1]="DOWN"}(r||(r={}));d(".lds-ellipsis {\n display: inline-block;\n position: relative;\n width: 64px;\n height: 64px; }\n\n.lds-ellipsis div {\n position: absolute;\n top: 27px;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #363636;\n animation-timing-function: cubic-bezier(0, 1, 1, 0); }\n\n.lds-ellipsis div:nth-child(1) {\n left: 6px;\n animation: lds-ellipsis1 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(2) {\n left: 6px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(3) {\n left: 26px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(4) {\n left: 45px;\n animation: lds-ellipsis3 0.6s infinite; }\n\n@keyframes lds-ellipsis1 {\n 0% {\n transform: scale(0); }\n 100% {\n transform: scale(1); } }\n\n@keyframes lds-ellipsis3 {\n 0% {\n transform: scale(1); }\n 100% {\n transform: scale(0); } }\n\n@keyframes lds-ellipsis2 {\n 0% {\n transform: translate(0, 0); }\n 100% {\n transform: translate(19px, 0); } }\n");var m=function(){return a.a.createElement("div",{className:"lds-ellipsis"},a.a.createElement("div",null),a.a.createElement("div",null),a.a.createElement("div",null),a.a.createElement("div",null))},p=function(){return a.a.createElement("div",null,a.a.createElement("p",null,"\u21a7\xa0\xa0pull to refresh\xa0\xa0\u21a7"))};d(".ptr,\n.ptr__children {\n height: 100%;\n width: 100%;\n overflow: hidden;\n -webkit-overflow-scrolling: touch;\n position: relative;\n z-index: 1; }\n\n.ptr.ptr--fetch-more-treshold-breached .ptr__fetch-more {\n display: block; }\n\n.ptr__fetch-more {\n display: none; }\n\n/**\n * Pull down transition \n */\n.ptr__children,\n.ptr__pull-down {\n transition: transform 0.2s cubic-bezier(0, 0, 0.31, 1); }\n\n.ptr__pull-down {\n position: absolute;\n overflow: hidden;\n left: 0;\n right: 0;\n top: 0;\n visibility: hidden; }\n .ptr__pull-down > div {\n display: none; }\n\n.ptr--dragging {\n /**\n * Hide PullMore content is treshold breached\n */\n /**\n * Otherwize, display content\n */ }\n .ptr--dragging.ptr--pull-down-treshold-breached .ptr__pull-down--pull-more {\n display: none; }\n .ptr--dragging .ptr__pull-down--pull-more {\n display: block; }\n\n.ptr--pull-down-treshold-breached {\n /**\n * Force opacity to 1 is pull down trashold breached\n */\n /**\n * And display loader\n */ }\n .ptr--pull-down-treshold-breached .ptr__pull-down {\n opacity: 1 !important; }\n .ptr--pull-down-treshold-breached .ptr__pull-down--loading {\n display: block; }\n\n.ptr__loader {\n margin: 0 auto;\n text-align: center; }\n");var h=function(e){var t=e.isPullable,n=void 0===t||t,o=e.canFetchMore,c=void 0!==o&&o,i=e.onRefresh,s=e.onFetchMore,d=e.refreshingContent,h=void 0===d?a.a.createElement(m,null):d,f=e.pullingContent,v=void 0===f?a.a.createElement(p,null):f,b=e.children,E=e.pullDownThreshold,g=void 0===E?67:E,w=e.fetchMoreThreshold,y=void 0===w?100:w,_=e.maxPullDownDistance,x=void 0===_?95:_,D=e.backgroundColor,T=e.className,N=void 0===T?"":T,P=Object(l.useRef)(null),L=Object(l.useRef)(null),M=Object(l.useRef)(null),O=Object(l.useRef)(null),k=!1,j=!1,F=!1,C=0,R=0;Object(l.useEffect)((function(){if(n&&L&&L.current)return L.current.addEventListener("touchstart",z,{passive:!0}),L.current.addEventListener("mousedown",z),L.current.addEventListener("touchmove",Y,{passive:!1}),L.current.addEventListener("mousemove",Y),window.addEventListener("scroll",B),L.current.addEventListener("touchend",H),L.current.addEventListener("mouseup",H),document.body.addEventListener("mouseleave",H),function(){n&&L&&L.current&&(L.current.removeEventListener("touchstart",z),L.current.removeEventListener("mousedown",z),L.current.removeEventListener("touchmove",Y),L.current.removeEventListener("mousemove",Y),window.removeEventListener("scroll",B),L.current.removeEventListener("touchend",H),L.current.removeEventListener("mouseup",H),document.body.removeEventListener("mouseleave",H))}}),[b,n,i,g,x,c,y]),Object(l.useEffect)((function(){var e;(null===(e=P)||void 0===e?void 0:e.current)&&(P.current.classList.contains("ptr--fetch-more-treshold-breached")||c&&S()=g&&(F=!0,k=!0,P.current.classList.remove("ptr--dragging"),P.current.classList.add("ptr--pull-down-treshold-breached")),R-C>x||(M.current.style.opacity=((R-C)/65).toString(),L.current.style.overflow="visible",L.current.style.transform="translate(0px, "+(R-C)+"px)",M.current.style.visibility="visible")))},B=function(e){j||c&&S()0;throw new Error("unsupported direction")}function d(e,t){void 0===t&&(t={});var n=t.insertAt;if(e&&"undefined"!==typeof document){var r=document.head||document.getElementsByTagName("head")[0],l=document.createElement("style");l.type="text/css","top"===n&&r.firstChild?r.insertBefore(l,r.firstChild):r.appendChild(l),l.styleSheet?l.styleSheet.cssText=e:l.appendChild(document.createTextNode(e))}}!function(e){e[e.UP=-1]="UP",e[e.DOWN=1]="DOWN"}(r||(r={}));d(".lds-ellipsis {\n display: inline-block;\n position: relative;\n width: 64px;\n height: 64px; }\n\n.lds-ellipsis div {\n position: absolute;\n top: 27px;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #363636;\n animation-timing-function: cubic-bezier(0, 1, 1, 0); }\n\n.lds-ellipsis div:nth-child(1) {\n left: 6px;\n animation: lds-ellipsis1 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(2) {\n left: 6px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(3) {\n left: 26px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(4) {\n left: 45px;\n animation: lds-ellipsis3 0.6s infinite; }\n\n@keyframes lds-ellipsis1 {\n 0% {\n transform: scale(0); }\n 100% {\n transform: scale(1); } }\n\n@keyframes lds-ellipsis3 {\n 0% {\n transform: scale(1); }\n 100% {\n transform: scale(0); } }\n\n@keyframes lds-ellipsis2 {\n 0% {\n transform: translate(0, 0); }\n 100% {\n transform: translate(19px, 0); } }\n");var m=function(){return a.a.createElement("div",{className:"lds-ellipsis"},a.a.createElement("div",null),a.a.createElement("div",null),a.a.createElement("div",null),a.a.createElement("div",null))},p=function(){return a.a.createElement("div",null,a.a.createElement("p",null,"\u21a7\xa0\xa0pull to refresh\xa0\xa0\u21a7"))};d(".ptr,\n.ptr__children {\n height: 100%;\n width: 100%;\n overflow: hidden;\n -webkit-overflow-scrolling: touch;\n position: relative; }\n\n.ptr.ptr--fetch-more-treshold-breached .ptr__fetch-more {\n display: block; }\n\n.ptr__fetch-more {\n display: none; }\n\n/**\n * Pull down transition \n */\n.ptr__children,\n.ptr__pull-down {\n transition: transform 0.2s cubic-bezier(0, 0, 0.31, 1); }\n\n.ptr__pull-down {\n position: absolute;\n overflow: hidden;\n left: 0;\n right: 0;\n top: 0;\n visibility: hidden; }\n .ptr__pull-down > div {\n display: none; }\n\n.ptr--dragging {\n /**\n * Hide PullMore content is treshold breached\n */\n /**\n * Otherwize, display content\n */ }\n .ptr--dragging.ptr--pull-down-treshold-breached .ptr__pull-down--pull-more {\n display: none; }\n .ptr--dragging .ptr__pull-down--pull-more {\n display: block; }\n\n.ptr--pull-down-treshold-breached {\n /**\n * Force opacity to 1 is pull down trashold breached\n */\n /**\n * And display loader\n */ }\n .ptr--pull-down-treshold-breached .ptr__pull-down {\n opacity: 1 !important; }\n .ptr--pull-down-treshold-breached .ptr__pull-down--loading {\n display: block; }\n\n.ptr__loader {\n margin: 0 auto;\n text-align: center; }\n");var h=function(e){var t=e.isPullable,n=void 0===t||t,o=e.canFetchMore,c=void 0!==o&&o,i=e.onRefresh,s=e.onFetchMore,d=e.refreshingContent,h=void 0===d?a.a.createElement(m,null):d,f=e.pullingContent,v=void 0===f?a.a.createElement(p,null):f,b=e.children,E=e.pullDownThreshold,g=void 0===E?67:E,w=e.fetchMoreThreshold,y=void 0===w?100:w,_=e.maxPullDownDistance,D=void 0===_?95:_,x=e.resistance,T=void 0===x?1:x,N=e.backgroundColor,P=e.className,L=void 0===P?"":P,M=Object(l.useRef)(null),O=Object(l.useRef)(null),j=Object(l.useRef)(null),k=Object(l.useRef)(null),C=!1,F=!1,R=!1,S=0,A=0;Object(l.useEffect)((function(){if(n&&O&&O.current){var e=O.current;return e.addEventListener("touchstart",B,{passive:!0}),e.addEventListener("mousedown",B),e.addEventListener("touchmove",H,{passive:!1}),e.addEventListener("mousemove",H),window.addEventListener("scroll",W),e.addEventListener("touchend",U),e.addEventListener("mouseup",U),document.body.addEventListener("mouseleave",U),function(){e.removeEventListener("touchstart",B),e.removeEventListener("mousedown",B),e.removeEventListener("touchmove",H),e.removeEventListener("mousemove",H),window.removeEventListener("scroll",W),e.removeEventListener("touchend",U),e.removeEventListener("mouseup",U),document.body.removeEventListener("mouseleave",U)}}}),[b,n,i,g,D,c,y]),Object(l.useEffect)((function(){var e;(null===(e=M)||void 0===e?void 0:e.current)&&(M.current.classList.contains("ptr--fetch-more-treshold-breached")||c&&z()=g&&(R=!0,C=!0,M.current.classList.remove("ptr--dragging"),M.current.classList.add("ptr--pull-down-treshold-breached")),t>=D||(j.current.style.opacity=(t/65).toString(),O.current.style.overflow="visible",O.current.style.transform="translate(0px, "+t+"px)",j.current.style.visibility="visible")}},W=function(e){F||c&&z()0;throw new Error("unsupported direction")}function d(e,n){void 0===n&&(n={});var t=n.insertAt;if(e&&"undefined"!==typeof document){var r=document.head||document.getElementsByTagName("head")[0],l=document.createElement("style");l.type="text/css","top"===t&&r.firstChild?r.insertBefore(l,r.firstChild):r.appendChild(l),l.styleSheet?l.styleSheet.cssText=e:l.appendChild(document.createTextNode(e))}}!function(e){e[e.UP=-1]="UP",e[e.DOWN=1]="DOWN"}(r||(r={}));d(".lds-ellipsis {\n display: inline-block;\n position: relative;\n width: 64px;\n height: 64px; }\n\n.lds-ellipsis div {\n position: absolute;\n top: 27px;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: #363636;\n animation-timing-function: cubic-bezier(0, 1, 1, 0); }\n\n.lds-ellipsis div:nth-child(1) {\n left: 6px;\n animation: lds-ellipsis1 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(2) {\n left: 6px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(3) {\n left: 26px;\n animation: lds-ellipsis2 0.6s infinite; }\n\n.lds-ellipsis div:nth-child(4) {\n left: 45px;\n animation: lds-ellipsis3 0.6s infinite; }\n\n@keyframes lds-ellipsis1 {\n 0% {\n transform: scale(0); }\n 100% {\n transform: scale(1); } }\n\n@keyframes lds-ellipsis3 {\n 0% {\n transform: scale(1); }\n 100% {\n transform: scale(0); } }\n\n@keyframes lds-ellipsis2 {\n 0% {\n transform: translate(0, 0); }\n 100% {\n transform: translate(19px, 0); } }\n");var p=function(){return i.a.createElement("div",{className:"lds-ellipsis"},i.a.createElement("div",null),i.a.createElement("div",null),i.a.createElement("div",null),i.a.createElement("div",null))},m=function(){return i.a.createElement("div",null,i.a.createElement("p",null,"\u21a7\xa0\xa0pull to refresh\xa0\xa0\u21a7"))};d(".ptr,\n.ptr__children {\n height: 100%;\n overflow: hidden;\n -webkit-overflow-scrolling: touch;\n position: relative;\n z-index: 1; }\n\n.ptr__children,\n.ptr__pull-down {\n transition: transform 0.2s cubic-bezier(0, 0, 0.31, 1); }\n\n.ptr__pull-down {\n position: absolute;\n overflow: hidden;\n left: 0;\n right: 0;\n top: 0;\n visibility: hidden;\n text-align: center; }\n .ptr__pull-down > div {\n margin: 0 auto; }\n .ptr__pull-down > .ptr__pull-down--loading {\n display: none;\n text-align: center;\n margin: 0 auto; }\n .ptr__pull-down > .ptr__pull-down--pull-more {\n display: none;\n text-align: center;\n margin: 0 auto; }\n\n.ptr--dragging.ptr--treshold-breached .ptr__pull-down--pull-more {\n display: none; }\n\n.ptr--dragging .ptr__pull-down--pull-more {\n display: block; }\n\n.ptr--treshold-breached .ptr__pull-down {\n opacity: 1 !important; }\n\n.ptr--treshold-breached .ptr__pull-down--loading {\n display: block; }\n");var v=function(e){var n=e.refreshingContent,t=void 0===n?i.a.createElement(p,null):n,o=e.pullingContent,a=void 0===o?i.a.createElement(m,null):o,s=e.pullDownThreshold,c=void 0===s?67:s,d=e.maxPullDownDistance,v=void 0===d?95:d,f=e.onRefresh,h=e.backgroundColor,g=e.isPullable,E=void 0===g||g,y=e.children,b=e.className,w=void 0===b?"":b,_=Object(l.useRef)(null),x=Object(l.useRef)(null),L=Object(l.useRef)(null),k=!1,N=!1,O=0,C=0;Object(l.useEffect)((function(){if(E&&x&&x.current)return x.current.addEventListener("touchstart",j,{passive:!0}),x.current.addEventListener("mousedown",j),x.current.addEventListener("touchmove",D,{passive:!1}),x.current.addEventListener("mousemove",D),x.current.addEventListener("touchend",P),x.current.addEventListener("mouseup",P),document.body.addEventListener("mouseleave",P),function(){E&&x&&x.current&&(x.current.removeEventListener("touchstart",j),x.current.removeEventListener("mousedown",j),x.current.removeEventListener("touchmove",D),x.current.removeEventListener("mousemove",D),x.current.removeEventListener("touchend",P),x.current.removeEventListener("mouseup",P),document.body.removeEventListener("mouseleave",P))}}),[E]),Object(l.useEffect)((function(){T()}),[y]);var T=function(){requestAnimationFrame((function(){x.current&&(x.current.style.overflowX="hidden",x.current.style.overflowY="auto",x.current.style.transform="translate(0px, 0px)"),L.current&&(L.current.style.opacity="0"),_.current&&(_.current.classList.remove("ptr--treshold-breached"),_.current.classList.remove("ptr--dragging"))}))},j=function(e){N=!1,e instanceof MouseEvent&&(O=e.pageY),e instanceof TouchEvent&&(O=e.touches[0].pageY),C=O,"touchstart"===e.type&&function e(n,t){return!!u(n,t)||null!=n.parentElement&&e(n.parentElement,t)}(e.target,r.UP)||x.current.getBoundingClientRect().top<0||(N=!0)},D=function(e){e.preventDefault(),N&&(C=e instanceof TouchEvent?e.touches[0].pageY:e.pageY,_.current.classList.add("ptr--dragging"),C=c&&(N=!0,k=!0,_.current.classList.remove("ptr--dragging"),_.current.classList.add("ptr--treshold-breached")),C-O>v||(L.current.style.opacity=((C-O)/65).toString(),x.current.style.overflow="visible",x.current.style.transform="translate(0px, "+(C-O)+"px)",L.current.style.visibility="visible")))},P=function(){if(N=!1,O=0,C=0,!k)return L.current.style.visibility="hidden",void T();x.current.style.overflow="visible",x.current.style.transform="translate(0px, "+c+"px)",k=!1,f()};return i.a.createElement("div",{className:"ptr "+w,style:{backgroundColor:h},ref:_},i.a.createElement("div",{className:"ptr__pull-down",ref:L},i.a.createElement("div",{className:"ptr__pull-down--loading"},t),i.a.createElement("div",{className:"ptr__pull-down--pull-more"},a)),i.a.createElement("div",{className:"ptr__children",ref:x},y))},f=function(){var e=["foo","bar","baz","foo"],n=Object(l.useState)(e),t=Object(c.a)(n,2),r=t[0],o=t[1];return i.a.createElement("div",{className:"App"},i.a.createElement(v,{onRefresh:function(){setTimeout((function(){o([].concat(Object(s.a)(r),e))}),1500)}},i.a.createElement(i.a.Fragment,null,i.a.createElement("header",{className:"App-header"},"Pull To Refresh"),i.a.createElement("div",{className:"App-container"},i.a.createElement("ul",null,r.map((function(e,n){return i.a.createElement("li",{key:n},e)})))))))};Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));a.a.render(i.a.createElement(f,null),document.getElementById("root")),"serviceWorker"in navigator&&navigator.serviceWorker.ready.then((function(e){e.unregister()}))}],[[5,1,2]]]);
//# sourceMappingURL=main.f34627b8.chunk.js.map
================================================
FILE: docs/static/js/runtime-main.dd9dbc43.js
================================================
!function(e){function r(r){for(var n,u,p=r[0],a=r[1],f=r[2],c=0,s=[];c
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
================================================
FILE: playground/package.json
================================================
{
"name": "react-simple-pull-to-refresh-playground",
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/jest": "24.0.19",
"@types/node": "^18.6.4",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/styled-components": "^5.1.34",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-scripts": "5.0.1",
"styled-components": "^6.1.13",
"typescript": "^5.3.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"homepage": "https://thmsgbrt.github.io/react-simple-pull-to-refresh"
}
================================================
FILE: playground/public/index.html
================================================
React App
================================================
FILE: playground/public/manifest.json
================================================
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
================================================
FILE: playground/public/robots.txt
================================================
# https://www.robotstxt.org/robotstxt.html
User-agent: *
================================================
FILE: playground/src/App.css
================================================
.App-header {
text-align: center;
background-color: #282c34;
height: 15vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-ptr {
height: 100%;
}
.App-commands {
background: linear-gradient(35deg, #64b6ac, #3c6b7c 100%);
}
================================================
FILE: playground/src/App.tsx
================================================
import React, { useState } from 'react';
import './App.css';
import PullToRefresh from './pull-to-refresh';
import Commands from './commands/commands';
const DEFAULT_VALUES = {
isPullable: true,
canFetchMore: false,
fetchMoreThreshold: 100,
pullDownThreshold: 67,
maxPullDownDistance: 95,
resistance: 1,
};
const App: React.FC = () => {
// prettier-ignore
const FAKE_LIST = ['foo','bar','baz','foo','foo','bar','baz','foo','foo','bar','baz','foo','foo','bar','baz'];
const [list, setList] = useState(FAKE_LIST);
const [isPullable, setIsPullable] = useState(DEFAULT_VALUES.isPullable);
const [canFetchMore, setCanFetchMore] = useState(DEFAULT_VALUES.canFetchMore);
const [fetchMoreThreshold, setFetchMoreThreshold] = useState(DEFAULT_VALUES.fetchMoreThreshold);
const [pullDownThreshold, setPullDownThreshold] = useState(DEFAULT_VALUES.pullDownThreshold);
const [maxPullDownDistance, setMaxPullDownDistance] = useState(DEFAULT_VALUES.maxPullDownDistance);
const [resistance, setResistance] = useState(DEFAULT_VALUES.resistance);
const handleReset = (): void => {
setIsPullable(DEFAULT_VALUES.isPullable);
setCanFetchMore(DEFAULT_VALUES.canFetchMore);
setFetchMoreThreshold(DEFAULT_VALUES.fetchMoreThreshold);
setPullDownThreshold(DEFAULT_VALUES.pullDownThreshold);
setMaxPullDownDistance(DEFAULT_VALUES.maxPullDownDistance);
setResistance(DEFAULT_VALUES.resistance);
};
const getNewData = (): Promise => {
return new Promise(res => {
setTimeout(() => {
res(setList([...list, ...FAKE_LIST]));
}, 1500);
});
};
return (
setCanFetchMore(!canFetchMore)}
isPullable={isPullable}
setIsPullable={() => setIsPullable(!isPullable)}
fetchMoreThreshold={fetchMoreThreshold}
setFetchMoreThreshold={(n: number) => setFetchMoreThreshold(n)}
pullDownThreshold={pullDownThreshold}
setPullDownThreshold={(n: number) => setPullDownThreshold(n)}
maxPullDownDistance={maxPullDownDistance}
setMaxPullDownDistance={(n: number) => setMaxPullDownDistance(n)}
resistance={resistance}
setResistance={(n: number) => setResistance(n)}
onReset={handleReset}
/>
<>
{list.map((item: string, index: number) => (
-
{index + 1} - {item}
))}
>
);
};
export default App;
================================================
FILE: playground/src/commands/commands.css
================================================
.commands {
display: flex;
overflow-x: auto;
padding: 15px 20px;
}
.command__group {
display: flex;
align-items: center;
margin: 10px;
font-size: 1rem;
}
input[type='number'] {
background: white;
border-radius: 3px;
line-height: 15px;
background-position: 0 15px;
background-repeat: no-repeat;
background-size: 15px 15px;
border-width: 2px;
border-style: solid;
border-color: transparent;
color: salmon;
font-weight: bold;
padding: 5px;
margin-right: 1rem;
width: 35px;
}
input[type='checkbox'] {
height: 20px;
width: 20px;
position: relative;
margin-right: 1rem;
}
input[type='checkbox']:after {
content: '\00D7';
display: block;
background: white;
background-image: url('https://cdn0.iconfinder.com/data/icons/slim-square-icons-basics/100/basics-21-32.png');
pointer-events: none;
position: absolute;
top: 0;
left: 0;
height: 15px;
width: 15px;
color: transparent;
transition: 0.25s all ease-in-out;
border-radius: 3px;
line-height: 15px;
background-position: 0 15px;
background-repeat: no-repeat;
background-size: 15px 15px;
border-width: 2px;
border-style: solid;
border-color: transparent;
}
input[type='checkbox']:checked:after {
background-color: salmon;
background-position: 0 0;
}
input[type='checkbox']:hover:after {
border-color: salmon;
}
button {
background: transparent;
border: 1px solid white;
border-radius: 3px;
padding: 10px;
color: white;
}
================================================
FILE: playground/src/commands/commands.tsx
================================================
import React from 'react';
import './commands.css';
interface Props {
canFetchMore: boolean;
isPullable: boolean;
fetchMoreThreshold: number;
pullDownThreshold: number;
maxPullDownDistance: number;
resistance: number;
setCanFetchMore: Function;
setIsPullable: Function;
setFetchMoreThreshold: Function;
setPullDownThreshold: Function;
setMaxPullDownDistance: Function;
setResistance: Function;
onReset: Function;
}
const Commands = ({
canFetchMore,
isPullable,
setCanFetchMore,
setIsPullable,
setFetchMoreThreshold,
fetchMoreThreshold,
setPullDownThreshold,
pullDownThreshold,
setMaxPullDownDistance,
maxPullDownDistance,
resistance,
setResistance,
onReset,
}: Props) => {
return (
);
};
export default Commands;
================================================
FILE: playground/src/index.css
================================================
body {
margin: 0;
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
h1,
h2 {
margin: 0.3rem;
}
h1 {
font-size: 2rem;
}
h2 {
font-size: 1rem;
}
================================================
FILE: playground/src/index.tsx
================================================
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
const container = document.getElementById('root');
const root = createRoot(container!);
root.render();
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
================================================
FILE: playground/src/pull-to-refresh/components/pull-to-refresh.d.ts
================================================
import React from 'react';
import '../styles/main.scss';
interface PullToRefreshProps {
isPullable?: boolean;
canFetchMore?: boolean;
onRefresh: () => Promise;
onFetchMore?: () => Promise;
refreshingContent?: JSX.Element | string;
pullingContent?: JSX.Element | string;
children: JSX.Element;
pullDownThreshold?: number;
fetchMoreThreshold?: number;
maxPullDownDistance?: number;
resistance?: number;
backgroundColor?: string;
className?: string;
}
declare const PullToRefresh: React.FC;
export default PullToRefresh;
================================================
FILE: playground/src/pull-to-refresh/components/pulling-content.d.ts
================================================
///
declare const PullingContent: () => JSX.Element;
export default PullingContent;
================================================
FILE: playground/src/pull-to-refresh/components/refreshing-content.d.ts
================================================
///
import '../styles/refreshing-content.scss';
declare const RefreshingContent: () => JSX.Element;
export default RefreshingContent;
================================================
FILE: playground/src/pull-to-refresh/index.d.ts
================================================
import PullToRefresh from './components/pull-to-refresh';
export default PullToRefresh;
================================================
FILE: playground/src/pull-to-refresh/index.js
================================================
/* eslint-disable */
import React, { useRef, useEffect } from 'react';
var DIRECTION;
(function (DIRECTION) {
DIRECTION[DIRECTION["UP"] = -1] = "UP";
DIRECTION[DIRECTION["DOWN"] = 1] = "DOWN";
})(DIRECTION || (DIRECTION = {}));
function isOverflowScrollable(element) {
var overflowType = getComputedStyle(element).overflowY;
if (element === document.scrollingElement && overflowType === 'visible') {
return true;
}
if (overflowType !== 'scroll' && overflowType !== 'auto') {
return false;
}
return true;
}
function isScrollable(element, direction) {
if (!isOverflowScrollable(element)) {
return false;
}
if (direction === DIRECTION.DOWN) {
var bottomScroll = element.scrollTop + element.clientHeight;
return bottomScroll < element.scrollHeight;
}
if (direction === DIRECTION.UP) {
return element.scrollTop > 0;
}
throw new Error('unsupported direction');
}
/**
* Returns whether a given element or any of its ancestors (up to rootElement) is scrollable in a given direction.
*/
function isTreeScrollable(element, direction) {
if (isScrollable(element, direction)) {
return true;
}
if (element.parentElement == null) {
return false;
}
return isTreeScrollable(element.parentElement, direction);
}
function styleInject(css, ref) {
if ( ref === void 0 ) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css = ".lds-ellipsis {\n display: inline-block;\n position: relative;\n width: 64px;\n height: 64px;\n}\n\n.lds-ellipsis div {\n position: absolute;\n top: 27px;\n width: 11px;\n height: 11px;\n border-radius: 50%;\n background: rgb(54, 54, 54);\n animation-timing-function: cubic-bezier(0, 1, 1, 0);\n}\n\n.lds-ellipsis div:nth-child(1) {\n left: 6px;\n animation: lds-ellipsis1 0.6s infinite;\n}\n\n.lds-ellipsis div:nth-child(2) {\n left: 6px;\n animation: lds-ellipsis2 0.6s infinite;\n}\n\n.lds-ellipsis div:nth-child(3) {\n left: 26px;\n animation: lds-ellipsis2 0.6s infinite;\n}\n\n.lds-ellipsis div:nth-child(4) {\n left: 45px;\n animation: lds-ellipsis3 0.6s infinite;\n}\n\n@keyframes lds-ellipsis1 {\n 0% {\n transform: scale(0);\n }\n 100% {\n transform: scale(1);\n }\n}\n@keyframes lds-ellipsis3 {\n 0% {\n transform: scale(1);\n }\n 100% {\n transform: scale(0);\n }\n}\n@keyframes lds-ellipsis2 {\n 0% {\n transform: translate(0, 0);\n }\n 100% {\n transform: translate(19px, 0);\n }\n}";
styleInject(css);
// Source: https://loading.io/css/
var RefreshingContent = function () {
return (React.createElement("div", { className: "lds-ellipsis" },
React.createElement("div", null),
React.createElement("div", null),
React.createElement("div", null),
React.createElement("div", null)));
};
var PullingContent = function () {
return (React.createElement("div", null,
React.createElement("p", null, "\u21A7\u00A0\u00A0pull to refresh\u00A0\u00A0\u21A7")));
};
var css$1 = ".ptr,\n.ptr__children {\n height: 100%;\n width: 100%;\n overflow: hidden;\n -webkit-overflow-scrolling: touch;\n position: relative;\n}\n\n.ptr.ptr--fetch-more-treshold-breached .ptr__fetch-more {\n display: block;\n}\n\n.ptr__fetch-more {\n display: none;\n}\n\n/**\n * Pull down transition \n */\n.ptr__children,\n.ptr__pull-down {\n transition: transform 0.2s cubic-bezier(0, 0, 0.31, 1);\n}\n\n.ptr__pull-down {\n position: absolute;\n overflow: hidden;\n left: 0;\n right: 0;\n top: 0;\n visibility: hidden;\n}\n.ptr__pull-down > div {\n display: none;\n}\n\n.ptr--dragging {\n /**\n * Hide PullMore content is treshold breached\n */\n}\n.ptr--dragging.ptr--pull-down-treshold-breached .ptr__pull-down--pull-more {\n display: none;\n}\n.ptr--dragging {\n /**\n * Otherwize, display content\n */\n}\n.ptr--dragging .ptr__pull-down--pull-more {\n display: block;\n}\n\n.ptr--pull-down-treshold-breached {\n /**\n * Force opacity to 1 is pull down trashold breached\n */\n}\n.ptr--pull-down-treshold-breached .ptr__pull-down {\n opacity: 1 !important;\n}\n.ptr--pull-down-treshold-breached {\n /**\n * And display loader\n */\n}\n.ptr--pull-down-treshold-breached .ptr__pull-down--loading {\n display: block;\n}\n\n.ptr__loader {\n margin: 0 auto;\n text-align: center;\n}";
styleInject(css$1);
var PullToRefresh = function (_a) {
var _b = _a.isPullable, isPullable = _b === void 0 ? true : _b, _c = _a.canFetchMore, canFetchMore = _c === void 0 ? false : _c, onRefresh = _a.onRefresh, onFetchMore = _a.onFetchMore, _d = _a.refreshingContent, refreshingContent = _d === void 0 ? React.createElement(RefreshingContent, null) : _d, _e = _a.pullingContent, pullingContent = _e === void 0 ? React.createElement(PullingContent, null) : _e, children = _a.children, _f = _a.pullDownThreshold, pullDownThreshold = _f === void 0 ? 67 : _f, _g = _a.fetchMoreThreshold, fetchMoreThreshold = _g === void 0 ? 100 : _g, _h = _a.maxPullDownDistance, maxPullDownDistance = _h === void 0 ? 95 : _h, // max distance to scroll to trigger refresh
_j = _a.resistance, // max distance to scroll to trigger refresh
resistance = _j === void 0 ? 1 : _j, backgroundColor = _a.backgroundColor, _k = _a.className, className = _k === void 0 ? '' : _k;
var containerRef = useRef(null);
var childrenRef = useRef(null);
var pullDownRef = useRef(null);
var fetchMoreRef = useRef(null);
var pullToRefreshThresholdBreached = false;
var fetchMoreTresholdBreached = false; // if true, fetchMore loader is displayed
var isDragging = false;
var startY = 0;
var currentY = 0;
useEffect(function () {
if (!isPullable || !childrenRef || !childrenRef.current)
return;
var childrenEl = childrenRef.current;
childrenEl.addEventListener('touchstart', onTouchStart, { passive: true });
childrenEl.addEventListener('mousedown', onTouchStart);
childrenEl.addEventListener('touchmove', onTouchMove, { passive: false });
childrenEl.addEventListener('mousemove', onTouchMove);
window.addEventListener('scroll', onScroll);
childrenEl.addEventListener('touchend', onEnd);
childrenEl.addEventListener('mouseup', onEnd);
document.body.addEventListener('mouseleave', onEnd);
return function () {
childrenEl.removeEventListener('touchstart', onTouchStart);
childrenEl.removeEventListener('mousedown', onTouchStart);
childrenEl.removeEventListener('touchmove', onTouchMove);
childrenEl.removeEventListener('mousemove', onTouchMove);
window.removeEventListener('scroll', onScroll);
childrenEl.removeEventListener('touchend', onEnd);
childrenEl.removeEventListener('mouseup', onEnd);
document.body.removeEventListener('mouseleave', onEnd);
};
}, [
children,
isPullable,
onRefresh,
pullDownThreshold,
maxPullDownDistance,
canFetchMore,
fetchMoreThreshold,
]);
/**
* Check onMount / canFetchMore becomes true
* if fetchMoreThreshold is already breached
*/
useEffect(function () {
var _a;
/**
* Check if it is already in fetching more state
*/
if (!((_a = containerRef) === null || _a === void 0 ? void 0 : _a.current))
return;
var isAlreadyFetchingMore = containerRef.current.classList.contains('ptr--fetch-more-treshold-breached');
if (isAlreadyFetchingMore)
return;
/**
* Proceed
*/
if (canFetchMore && getScrollToBottomValue() < fetchMoreThreshold && onFetchMore) {
containerRef.current.classList.add('ptr--fetch-more-treshold-breached');
fetchMoreTresholdBreached = true;
onFetchMore().then(initContainer).catch(initContainer);
}
}, [canFetchMore, children]);
/**
* Returns distance to bottom of the container
*/
var getScrollToBottomValue = function () {
if (!childrenRef || !childrenRef.current)
return -1;
var scrollTop = window.scrollY; // is the pixels hidden in top due to the scroll. With no scroll its value is 0.
var scrollHeight = childrenRef.current.scrollHeight; // is the pixels of the whole container
return scrollHeight - scrollTop - window.innerHeight;
};
var initContainer = function () {
requestAnimationFrame(function () {
/**
* Reset Styles
*/
if (childrenRef.current) {
childrenRef.current.style.overflowX = 'hidden';
childrenRef.current.style.overflowY = 'auto';
childrenRef.current.style.transform = "unset";
}
if (pullDownRef.current) {
pullDownRef.current.style.opacity = '0';
}
if (containerRef.current) {
containerRef.current.classList.remove('ptr--pull-down-treshold-breached');
containerRef.current.classList.remove('ptr--dragging');
containerRef.current.classList.remove('ptr--fetch-more-treshold-breached');
}
if (pullToRefreshThresholdBreached)
pullToRefreshThresholdBreached = false;
if (fetchMoreTresholdBreached)
fetchMoreTresholdBreached = false;
});
};
var onTouchStart = function (e) {
isDragging = false;
if (e instanceof MouseEvent) {
startY = e.pageY;
}
if (window.TouchEvent && e instanceof TouchEvent) {
startY = e.touches[0].pageY;
}
currentY = startY;
// Check if element can be scrolled
if (e.type === 'touchstart' && isTreeScrollable(e.target, DIRECTION.UP)) {
return;
}
// Top non visible so cancel
if (childrenRef.current.getBoundingClientRect().top < 0) {
return;
}
isDragging = true;
};
var onTouchMove = function (e) {
if (!isDragging) {
return;
}
if (window.TouchEvent && e instanceof TouchEvent) {
currentY = e.touches[0].pageY;
}
else {
currentY = e.pageY;
}
containerRef.current.classList.add('ptr--dragging');
if (currentY < startY) {
isDragging = false;
return;
}
if (e.cancelable) {
e.preventDefault();
}
var yDistanceMoved = Math.min((currentY - startY) / resistance, maxPullDownDistance);
// Limit to trigger refresh has been breached
if (yDistanceMoved >= pullDownThreshold) {
isDragging = true;
pullToRefreshThresholdBreached = true;
containerRef.current.classList.remove('ptr--dragging');
containerRef.current.classList.add('ptr--pull-down-treshold-breached');
}
// maxPullDownDistance breached, stop the animation
if (yDistanceMoved >= maxPullDownDistance) {
return;
}
pullDownRef.current.style.opacity = ((yDistanceMoved) / 65).toString();
childrenRef.current.style.overflow = 'visible';
childrenRef.current.style.transform = "translate(0px, " + yDistanceMoved + "px)";
pullDownRef.current.style.visibility = 'visible';
};
var onScroll = function (e) {
/**
* Check if component has already called onFetchMore
*/
if (fetchMoreTresholdBreached)
return;
/**
* Check if user breached fetchMoreThreshold
*/
if (canFetchMore && getScrollToBottomValue() < fetchMoreThreshold && onFetchMore) {
fetchMoreTresholdBreached = true;
containerRef.current.classList.add('ptr--fetch-more-treshold-breached');
onFetchMore().then(initContainer).catch(initContainer);
}
};
var onEnd = function () {
isDragging = false;
startY = 0;
currentY = 0;
// Container has not been dragged enough, put it back to it's initial state
if (!pullToRefreshThresholdBreached) {
if (pullDownRef.current)
pullDownRef.current.style.visibility = 'hidden';
initContainer();
return;
}
if (childrenRef.current) {
childrenRef.current.style.overflow = 'visible';
childrenRef.current.style.transform = "translate(0px, " + pullDownThreshold + "px)";
}
onRefresh().then(initContainer).catch(initContainer);
};
return (React.createElement("div", { className: "ptr " + className, style: { backgroundColor: backgroundColor }, ref: containerRef },
React.createElement("div", { className: "ptr__pull-down", ref: pullDownRef },
React.createElement("div", { className: "ptr__loader ptr__pull-down--loading" }, refreshingContent),
React.createElement("div", { className: "ptr__pull-down--pull-more" }, pullingContent)),
React.createElement("div", { className: "ptr__children", ref: childrenRef },
children,
React.createElement("div", { className: "ptr__fetch-more", ref: fetchMoreRef },
React.createElement("div", { className: "ptr__loader ptr__fetch-more--loading" }, refreshingContent)))));
};
export default PullToRefresh;
================================================
FILE: playground/src/pull-to-refresh/isScrollable.d.ts
================================================
import { DIRECTION } from './types';
/**
* Returns whether a given element or any of its ancestors (up to rootElement) is scrollable in a given direction.
*/
export declare function isTreeScrollable(element: HTMLElement, direction: DIRECTION): boolean;
================================================
FILE: playground/src/pull-to-refresh/types.d.ts
================================================
export declare enum DIRECTION {
UP = -1,
DOWN = 1
}
================================================
FILE: playground/src/react-app-env.d.ts
================================================
///
================================================
FILE: playground/src/serviceWorker.ts
================================================
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
================================================
FILE: playground/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
================================================
FILE: rollup.config.js
================================================
import typescript from 'rollup-plugin-typescript2';
import del from 'rollup-plugin-delete';
import postcss from 'rollup-plugin-postcss';
import pkg from './package.json';
export default [
{
input: 'src/index.ts',
output: [
{
file: 'playground/src/pull-to-refresh/index.js',
format: 'esm',
banner: '/* eslint-disable */',
},
{ file: pkg.main, format: 'cjs' },
{ file: pkg.module, format: 'esm' },
],
plugins: [
del({ targets: ['build/*', 'playground/src/pull-to-refresh'] }),
typescript(),
postcss({
plugins: [],
}),
],
external: Object.keys(pkg.peerDependencies || {}),
},
];
================================================
FILE: src/components/pull-to-refresh.tsx
================================================
import React, { useRef, useEffect } from 'react';
import { isTreeScrollable } from '../isScrollable';
import RefreshingContent from './refreshing-content';
import PullingContent from './pulling-content';
import { DIRECTION } from '../types';
import '../styles/main.scss';
interface PullToRefreshProps {
isPullable?: boolean;
canFetchMore?: boolean;
onRefresh: () => Promise;
onFetchMore?: () => Promise;
refreshingContent?: JSX.Element | string;
pullingContent?: JSX.Element | string;
children: JSX.Element;
pullDownThreshold?: number;
fetchMoreThreshold?: number;
maxPullDownDistance?: number;
resistance?: number;
backgroundColor?: string;
className?: string;
}
const PullToRefresh: React.FC = ({
isPullable = true,
canFetchMore = false,
onRefresh,
onFetchMore,
refreshingContent = ,
pullingContent = ,
children,
pullDownThreshold = 67,
fetchMoreThreshold = 100,
maxPullDownDistance = 95, // max distance to scroll to trigger refresh
resistance = 1,
backgroundColor,
className = '',
}) => {
const containerRef = useRef(null);
const childrenRef = useRef(null);
const pullDownRef = useRef(null);
const fetchMoreRef = useRef(null);
let pullToRefreshThresholdBreached: boolean = false;
let fetchMoreTresholdBreached: boolean = false; // if true, fetchMore loader is displayed
let isDragging: boolean = false;
let startY: number = 0;
let currentY: number = 0;
useEffect(() => {
if (!isPullable || !childrenRef || !childrenRef.current) return;
const childrenEl = childrenRef.current;
childrenEl.addEventListener('touchstart', onTouchStart, { passive: true });
childrenEl.addEventListener('mousedown', onTouchStart);
childrenEl.addEventListener('touchmove', onTouchMove, { passive: false });
childrenEl.addEventListener('mousemove', onTouchMove);
window.addEventListener('scroll', onScroll);
childrenEl.addEventListener('touchend', onEnd);
childrenEl.addEventListener('mouseup', onEnd);
document.body.addEventListener('mouseleave', onEnd);
return () => {
childrenEl.removeEventListener('touchstart', onTouchStart);
childrenEl.removeEventListener('mousedown', onTouchStart);
childrenEl.removeEventListener('touchmove', onTouchMove);
childrenEl.removeEventListener('mousemove', onTouchMove);
window.removeEventListener('scroll', onScroll);
childrenEl.removeEventListener('touchend', onEnd);
childrenEl.removeEventListener('mouseup', onEnd);
document.body.removeEventListener('mouseleave', onEnd);
};
}, [
children,
isPullable,
onRefresh,
pullDownThreshold,
maxPullDownDistance,
canFetchMore,
fetchMoreThreshold,
]);
/**
* Check onMount / canFetchMore becomes true
* if fetchMoreThreshold is already breached
*/
useEffect(() => {
/**
* Check if it is already in fetching more state
*/
if (!containerRef?.current) return;
const isAlreadyFetchingMore = containerRef.current.classList.contains(
'ptr--fetch-more-treshold-breached'
);
if (isAlreadyFetchingMore) return;
/**
* Proceed
*/
if (canFetchMore && getScrollToBottomValue() < fetchMoreThreshold && onFetchMore) {
containerRef.current.classList.add('ptr--fetch-more-treshold-breached');
fetchMoreTresholdBreached = true;
onFetchMore().then(initContainer).catch(initContainer);
}
}, [canFetchMore, children]);
/**
* Returns distance to bottom of the container
*/
const getScrollToBottomValue = (): number => {
if (!childrenRef || !childrenRef.current) return -1;
const scrollTop = window.scrollY; // is the pixels hidden in top due to the scroll. With no scroll its value is 0.
const scrollHeight = childrenRef.current.scrollHeight; // is the pixels of the whole container
return scrollHeight - scrollTop - window.innerHeight;
};
const initContainer = (): void => {
requestAnimationFrame(() => {
/**
* Reset Styles
*/
if (childrenRef.current) {
childrenRef.current.style.overflowX = 'hidden';
childrenRef.current.style.overflowY = 'auto';
childrenRef.current.style.transform = `unset`;
}
if (pullDownRef.current) {
pullDownRef.current.style.opacity = '0';
}
if (containerRef.current) {
containerRef.current.classList.remove('ptr--pull-down-treshold-breached');
containerRef.current.classList.remove('ptr--dragging');
containerRef.current.classList.remove('ptr--fetch-more-treshold-breached');
}
if (pullToRefreshThresholdBreached) pullToRefreshThresholdBreached = false;
if (fetchMoreTresholdBreached) fetchMoreTresholdBreached = false;
});
};
const onTouchStart = (e: MouseEvent | TouchEvent): void => {
isDragging = false;
if (e instanceof MouseEvent) {
startY = e.pageY;
}
if (window.TouchEvent && e instanceof TouchEvent) {
startY = e.touches[0].pageY;
}
currentY = startY;
// Check if element can be scrolled
if (e.type === 'touchstart' && isTreeScrollable(e.target as HTMLElement, DIRECTION.UP)) {
return;
}
// Top non visible so cancel
if (childrenRef.current!.getBoundingClientRect().top < 0) {
return;
}
isDragging = true;
};
const onTouchMove = (e: MouseEvent | TouchEvent): void => {
if (!isDragging) {
return;
}
if (window.TouchEvent && e instanceof TouchEvent) {
currentY = e.touches[0].pageY;
} else {
currentY = (e as MouseEvent).pageY;
}
containerRef.current!.classList.add('ptr--dragging');
if (currentY < startY) {
isDragging = false;
return;
}
if (e.cancelable) {
e.preventDefault();
}
const yDistanceMoved = Math.min((currentY - startY) / resistance, maxPullDownDistance);
// Limit to trigger refresh has been breached
if (yDistanceMoved >= pullDownThreshold) {
isDragging = true;
pullToRefreshThresholdBreached = true;
containerRef.current!.classList.remove('ptr--dragging');
containerRef.current!.classList.add('ptr--pull-down-treshold-breached');
}
// maxPullDownDistance breached, stop the animation
if (yDistanceMoved >= maxPullDownDistance) {
return;
}
pullDownRef.current!.style.opacity = ((yDistanceMoved) / 65).toString();
childrenRef.current!.style.overflow = 'visible';
childrenRef.current!.style.transform = `translate(0px, ${yDistanceMoved}px)`;
pullDownRef.current!.style.visibility = 'visible';
};
const onScroll = (e: Event): void => {
/**
* Check if component has already called onFetchMore
*/
if (fetchMoreTresholdBreached) return;
/**
* Check if user breached fetchMoreThreshold
*/
if (canFetchMore && getScrollToBottomValue() < fetchMoreThreshold && onFetchMore) {
fetchMoreTresholdBreached = true;
containerRef.current!.classList.add('ptr--fetch-more-treshold-breached');
onFetchMore().then(initContainer).catch(initContainer);
}
};
const onEnd = (): void => {
isDragging = false;
startY = 0;
currentY = 0;
// Container has not been dragged enough, put it back to it's initial state
if (!pullToRefreshThresholdBreached) {
if (pullDownRef.current) pullDownRef.current.style.visibility = 'hidden';
initContainer();
return;
}
if (childrenRef.current) {
childrenRef.current.style.overflow = 'visible';
childrenRef.current.style.transform = `translate(0px, ${pullDownThreshold}px)`;
}
onRefresh().then(initContainer).catch(initContainer);
};
return (
{refreshingContent}
{pullingContent}
);
};
export default PullToRefresh;
================================================
FILE: src/components/pulling-content.tsx
================================================
import React from 'react';
const PullingContent = () => {
return (
);
};
export default PullingContent;
================================================
FILE: src/components/refreshing-content.tsx
================================================
import React from 'react';
import '../styles/refreshing-content.scss';
// Source: https://loading.io/css/
const RefreshingContent = () => {
return (
);
};
export default RefreshingContent;
================================================
FILE: src/index.ts
================================================
import PullToRefresh from './components/pull-to-refresh';
export default PullToRefresh;
================================================
FILE: src/isScrollable.ts
================================================
import { DIRECTION } from './types';
function isOverflowScrollable(element: HTMLElement): boolean {
const overflowType: string = getComputedStyle(element).overflowY;
if (element === document.scrollingElement && overflowType === 'visible') {
return true;
}
if (overflowType !== 'scroll' && overflowType !== 'auto') {
return false;
}
return true;
}
function isScrollable(element: HTMLElement, direction: DIRECTION): boolean {
if (!isOverflowScrollable(element)) {
return false;
}
if (direction === DIRECTION.DOWN) {
const bottomScroll = element.scrollTop + element.clientHeight;
return bottomScroll < element.scrollHeight;
}
if (direction === DIRECTION.UP) {
return element.scrollTop > 0;
}
throw new Error('unsupported direction');
}
/**
* Returns whether a given element or any of its ancestors (up to rootElement) is scrollable in a given direction.
*/
export function isTreeScrollable(element: HTMLElement, direction: DIRECTION): boolean {
if (isScrollable(element, direction)) {
return true;
}
if (element.parentElement == null) {
return false;
}
return isTreeScrollable(element.parentElement, direction);
}
================================================
FILE: src/styles/main.scss
================================================
.ptr,
.ptr__children {
height: 100%;
width: 100%;
overflow: hidden;
-webkit-overflow-scrolling: touch;
position: relative;
}
.ptr {
&.ptr--fetch-more-treshold-breached {
.ptr__fetch-more {
display: block;
}
}
}
.ptr__fetch-more {
display: none;
}
/**
* Pull down transition
*/
.ptr__children,
.ptr__pull-down {
transition: transform 0.2s cubic-bezier(0, 0, 0.31, 1);
}
.ptr__pull-down {
position: absolute;
overflow: hidden;
left: 0;
right: 0;
top: 0;
visibility: hidden;
> div {
display: none;
}
}
.ptr--dragging {
/**
* Hide PullMore content is treshold breached
*/
&.ptr--pull-down-treshold-breached {
.ptr__pull-down--pull-more {
display: none;
}
}
/**
* Otherwize, display content
*/
.ptr__pull-down--pull-more {
display: block;
}
}
.ptr--pull-down-treshold-breached {
/**
* Force opacity to 1 is pull down trashold breached
*/
.ptr__pull-down {
opacity: 1 !important;
}
/**
* And display loader
*/
.ptr__pull-down--loading {
display: block;
}
}
.ptr__loader {
margin: 0 auto;
text-align: center;
}
================================================
FILE: src/styles/refreshing-content.scss
================================================
.lds-ellipsis {
display: inline-block;
position: relative;
width: 64px;
height: 64px;
}
.lds-ellipsis div {
position: absolute;
top: 27px;
width: 11px;
height: 11px;
border-radius: 50%;
background: rgb(54, 54, 54);
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
.lds-ellipsis div:nth-child(1) {
left: 6px;
animation: lds-ellipsis1 0.6s infinite;
}
.lds-ellipsis div:nth-child(2) {
left: 6px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(3) {
left: 26px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(4) {
left: 45px;
animation: lds-ellipsis3 0.6s infinite;
}
@keyframes lds-ellipsis1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes lds-ellipsis3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes lds-ellipsis2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(19px, 0);
}
}
================================================
FILE: src/types.ts
================================================
export enum DIRECTION {
UP = -0b01,
DOWN = 0b01,
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"module": "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
"declaration": true /* Generates corresponding '.d.ts' file. */,
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./build" /* Redirect output structure to the directory. */,
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
},
"include": ["src/**/*", "src/.styled.ts"]
}