Commit d0dbfc73 authored by 吴志俊's avatar 吴志俊

fix: modal内toggle失效

parent de072d17
...@@ -9,14 +9,43 @@ ...@@ -9,14 +9,43 @@
- ModalRouter: 始终只显示一个modal - ModalRouter: 始终只显示一个modal
- ModalRouter.Multi: 展示多个modal - ModalRouter.Multi: 展示多个modal
#### 内置toggle方法 ## Example
```javascript
import xx from './xx.jsx'
const map={x:xx}
<ModalRouter(.Multi) map={map} contextMode>
<div/>
<YourComponent/>
</ModalRouter(.Multi)>
//YourComponent
props.(...toggle)
const FuncTest = ({ children, ...rest }) => {
const { openModal, closeModal, closeAllModal } = useContext(AllModals)
//try to fire a timer to destroy this FC, make it looks like a Toast
useEffect(() => {
openModal('x')
return () => closeModal('x')
}, [])
return <span>FC Test</span>
}
```
## 内置toggle方法
- openModal(key,data) - openModal(key,data)
>map内key对应的组件会接收到modalData属性,如果data是个普通对象会自动展开 >map内key对应的组件会接收到modalData属性,如果data是个普通对象会自动展开
- closeModal(key?) - closeModal(key?)
>如果key不提供或者未匹配,默认pop
- closeAllModal() - closeAllModal()
## 配置属性 ### 配置属性
### map: { key: modal } ### map: { key: modal }
>key: 用于标识一个modal组件 >key: 用于标识一个modal组件
...@@ -28,10 +57,17 @@ ...@@ -28,10 +57,17 @@
> >
>默认ModalRouter所有儿子组件的props会接收到所有toogle方法 >默认ModalRouter所有儿子组件的props会接收到所有toogle方法
### container: ### container: function/class or object of React.ReactElement
>可传入自定义容器组件,记得处理props.children,以及接收并使用内置toggle方法 >可传入自定义容器组件,记得处理props.children,以及接收并使用内置toggle方法
>
>考虑配合内置导出对象 ScrollLocker,作用如下
### locker: HTMLElement
>暂且支持一个,用于解决scroll chain现象,阻止非Modal元素滚动
>
>默认为document.body
__默认container css:__ 默认 __container css:__
```css ```css
.HOC_modal { .HOC_modal {
position: fixed; position: fixed;
......
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t(require("react"));else if("function"==typeof define&&define.amd)define(["react"],t);else{var n="object"==typeof exports?t(require("react")):t(e.React);for(var r in n)("object"==typeof exports?exports:e)[r]=n[r]}}(self,(function(e){return(()=>{"use strict";var t={261:(e,t,n)=>{n.r(t),n.d(t,{AllModals:()=>a,default:()=>g});var r=n(888),o=n.n(r);const a=o().createContext(),l=e=>(e.preventDefault&&e.preventDefault(),e.returnValue=!1,e.stopPropagation&&e.stopPropagation(),!1),c=(e,t)=>function(){e(...arguments),t()},i=(e,t)=>o().Children.map(e,((e,n)=>o().isValidElement(e)&&"string"!=typeof e.type?o().cloneElement(e,{...t}):e)),s=()=>{const[,e]=(0,r.useReducer)((e=>[]),null);return e},u=(e=window,t,n,o={})=>{let{current:a}=(0,r.useRef)();a=n,(0,r.useEffect)((()=>{if(!e||!e.addEventListener)return;const n=e=>a(e);return e.addEventListener(t,n,{capture:!!o.capture,once:!!o.once,passive:!!o.passice}),()=>e.removeEventListener(t,n,{capture:!!o.capture})}),[e,t,o.capture,o.once,o.passice])},d=(e,t)=>{const{current:n}=(0,r.useRef)([]),{current:a}=(0,r.useRef)([]),l=(0,r.useCallback)(((t,r)=>{if(!e.hasOwnProperty(t))throw new ReferenceError("ModalRouter: In the map you provided, key didn't matched");a.push(t),n.push(r)}),[e]),c=(0,r.useCallback)(((e,t)=>l(e,t)),[e]),i=(0,r.useCallback)(t?e=>{let t=a.findIndex(((t,n)=>t===e));-1!==t?(a.splice(t,1),n.splice(t,1)):(a.pop(),n.pop())}:()=>u(),[t]),s=(0,r.useCallback)((e=>{i(e)}),[]),u=(0,r.useCallback)((()=>{a.length=0,n.length=0}),[]),d=(0,r.useCallback)(t?()=>a.map(((t,r)=>f(e[t],n[r],t+r))):()=>a.length?[f(e[a.slice(-1)[0]],n.slice(-1)[0],a.slice(-1)[0]+0)]:null,[t,e]);function f(e,t,n){let r={modalData:t,openModal:c,closeModal:s,closeAllModal:u};return"object"!=typeof t||t[Symbol.iterator]||(r={...r,...t}),o().isValidElement(e)?o().cloneElement(e,{...r,key:n}):o().createElement(e,{...r,key:n})}return[a,n,{getModal:d,openModal:c,closeModal:s,closeAllModal:u}]};var f=n(379),p=n.n(f),m=n(528),v={insert:"head",singleton:!1};p()(m.Z,v);m.Z.locals;const h=({container:e,hidden:t,children:n,...a})=>{const c=(0,r.useRef)();if(u(c.current,"mousewheel",l),u(c.current,"touchmove",l),!t){if("function"==typeof e)return e.prototype.isReactComponent?o().createElement(e,a,n):o().cloneElement(e({children:n,funcs:a}));if(o().isValidElement(e))return o().cloneElement(e,a,n)}return o().createElement("div",{className:"HOC_modal",ref:c,style:{display:t?"none":"flex"}},n)},b=({contextMode:e,Provider:t,children:n,...a})=>e?o().createElement(t,{value:a},n):o().createElement(r.Fragment,null,n);const y=function({children:e,map:t,contextMode:n,container:l,...u}){const f=s(),[p,m,{getModal:v,openModal:y,closeModal:g,closeAllModal:M}]=d(t,!1),x={openModal:(0,r.useCallback)(c(y,f),[]),closeModal:(0,r.useCallback)(c(g,f),[]),closeAllModal:(0,r.useCallback)(c(M,f),[])},E=i(e,x);return o().createElement(r.Fragment,null,o().createElement(b,{contextMode:n,Provider:a.Provider,...x},E),o().createElement(h,{container:l,hidden:!p.length,...x},v()))};y.Multi=function({children:e,map:t,contextMode:n,container:l,...u}){const f=s(),[p,m,{getModal:v,openModal:y,closeModal:g,closeAllModal:M}]=d(t,!0),x={openModal:(0,r.useCallback)(c(y,f),[]),closeModal:(0,r.useCallback)(c(g,f),[]),closeAllModal:(0,r.useCallback)(c(M,f),[])},E=i(e,x);return o().createElement(r.Fragment,null,o().createElement(b,{contextMode:n,Provider:a.Provider,...x},E),o().createElement(h,{container:l,hidden:!p.length,...x},v()))};const g=y},528:(e,t,n)=>{n.d(t,{Z:()=>a});var r=n(645),o=n.n(r)()((function(e){return e[1]}));o.push([e.id,".HOC_modal {\r\n position: fixed;\r\n width: 100%;\r\n height: 100%;\r\n top: 0;\r\n left: 0;\r\n background-color: rgba(0, 0, 0, 0.7);\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n z-index: 2;\r\n}\r\n",""]);const a=o},645:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=e(t);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,r){"string"==typeof e&&(e=[[null,e,""]]);var o={};if(r)for(var a=0;a<this.length;a++){var l=this[a][0];null!=l&&(o[l]=!0)}for(var c=0;c<e.length;c++){var i=[].concat(e[c]);r&&o[i[0]]||(n&&(i[2]?i[2]="".concat(n," and ").concat(i[2]):i[2]=n),t.push(i))}},t}},379:(e,t,n)=>{var r,o=function(){return void 0===r&&(r=Boolean(window&&document&&document.all&&!window.atob)),r},a=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),l=[];function c(e){for(var t=-1,n=0;n<l.length;n++)if(l[n].identifier===e){t=n;break}return t}function i(e,t){for(var n={},r=[],o=0;o<e.length;o++){var a=e[o],i=t.base?a[0]+t.base:a[0],s=n[i]||0,u="".concat(i," ").concat(s);n[i]=s+1;var d=c(u),f={css:a[1],media:a[2],sourceMap:a[3]};-1!==d?(l[d].references++,l[d].updater(f)):l.push({identifier:u,updater:h(f,t),references:1}),r.push(u)}return r}function s(e){var t=document.createElement("style"),r=e.attributes||{};if(void 0===r.nonce){var o=n.nc;o&&(r.nonce=o)}if(Object.keys(r).forEach((function(e){t.setAttribute(e,r[e])})),"function"==typeof e.insert)e.insert(t);else{var l=a(e.insert||"head");if(!l)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");l.appendChild(t)}return t}var u,d=(u=[],function(e,t){return u[e]=t,u.filter(Boolean).join("\n")});function f(e,t,n,r){var o=n?"":r.media?"@media ".concat(r.media," {").concat(r.css,"}"):r.css;if(e.styleSheet)e.styleSheet.cssText=d(t,o);else{var a=document.createTextNode(o),l=e.childNodes;l[t]&&e.removeChild(l[t]),l.length?e.insertBefore(a,l[t]):e.appendChild(a)}}function p(e,t,n){var r=n.css,o=n.media,a=n.sourceMap;if(o?e.setAttribute("media",o):e.removeAttribute("media"),a&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(a))))," */")),e.styleSheet)e.styleSheet.cssText=r;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(r))}}var m=null,v=0;function h(e,t){var n,r,o;if(t.singleton){var a=v++;n=m||(m=s(t)),r=f.bind(null,n,a,!1),o=f.bind(null,n,a,!0)}else n=s(t),r=p.bind(null,n,t),o=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)};return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else o()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=o());var n=i(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var r=0;r<n.length;r++){var o=c(n[r]);l[o].references--}for(var a=i(e,t),s=0;s<n.length;s++){var u=c(n[s]);0===l[u].references&&(l[u].updater(),l.splice(u,1))}n=a}}}},888:t=>{t.exports=e}},n={};function r(e){if(n[e])return n[e].exports;var o=n[e]={id:e,exports:{}};return t[e](o,o.exports,r),o.exports}return r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r(261)})()})); !function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t(require("react"));else if("function"==typeof define&&define.amd)define(["react"],t);else{var n="object"==typeof exports?t(require("react")):t(e.React);for(var o in n)("object"==typeof exports?exports:e)[o]=n[o]}}(self,(function(e){return(()=>{"use strict";var t={539:(e,t,n)=>{n.r(t),n.d(t,{AllModals:()=>l,ScrollLocker:()=>s,default:()=>g});var o=n(888),r=n.n(o);const l=r().createContext(),c=(e,t)=>r().Children.map(e,((e,n)=>r().isValidElement(e)&&"string"!=typeof e.type?r().cloneElement(e,{...t}):e));let i;const a=new WeakMap,s=(e=document.body)=>{if("object"!=typeof e||!e?.baseURI||!document.body.contains(e))return console.warn("ScrollLocker: get invalid Node or dom doesn't exit in document.body, locker can't work as expected");let t,n=Math.random().toString(16).slice(2,10);return a.has(e)?t=a.get(e):(t=new d(e),a.set(e,t)),{lock:()=>{t.push(n)},unlock:()=>{t.pop(n)},unlockAll:()=>{t.clear(n)}}};class d{constructor(e){this.element=e,this.previousStyle={overflow:e.style.overflow,width:e.style.width},this.ed=!1,this.countSum=0,this.locker=[]}push(e){let t=!1;for(const n of this.locker)n.r===e&&(n.c++,this.count++,t=!0);t||(this.locker.push({r:e,c:1}),this.count++)}pop(e){for(const t of this.locker)t.r===e&&t.c>=1&&(t.c--,this.count--)}clear(e){for(const t of this.locker)t.r===e&&t.c>=1&&(this.count-=t.c,t.c=0)}get count(){return this.countSum}set count(e){e<=0?(e=0,this.locked=!1):this.locked=!0,this.countSum=e}get locked(){return this.ed}set locked(e){let t=(e=>{if(e||!i){let e=document.createElement("div");e.style.height="110%";let t=document.createElement("div");t.style.visibity="hidden",t.style.width="100px",t.style.height="100px",t.style.overflow="hidden",t.appendChild(e),document.body.appendChild(t);const n=e.offsetWidth;t.style.overflow="scroll";let o=e.offsetWidth;n===o&&(o=t.clientWidth),i=n-o,document.body.removeChild(t)}return i})();t&&(this.element.style.width=e?`calc(100% - ${t}px)`:this.previousStyle.width),this.element.style.overflow=e?"hidden":this.previousStyle.overflow,this.ed=e}}const u=(e,t,n)=>{const l=(()=>{const[,e]=(0,o.useReducer)((e=>[]),null);return e})(),{current:c}=(0,o.useRef)([]),{current:i}=(0,o.useRef)([]),{current:a}=(0,o.useRef)(s(n)),d=(0,o.useCallback)(((t,n)=>{if(!e.hasOwnProperty(t))throw new ReferenceError("ModalRouter: In the map you provided, key didn't matched");i.push(t),c.push(n)}),[e]),u=(0,o.useCallback)(((e,t)=>{d(e,t),l(),a.lock()}),[e]),f=(0,o.useCallback)(t?e=>{let t=i.findIndex(((t,n)=>t===e));-1!==t?(i.splice(t,1),c.splice(t,1)):(console.warn("ModalRouter: In the stack you pushed, key didn't matched. Maybe you closeModal with the wrong name, this may causes bug."),i.pop(),c.pop())}:()=>p(),[t]),h=(0,o.useCallback)((e=>{f(e),l(),a.unlock()}),[]),p=(0,o.useCallback)((()=>{i.length=0,c.length=0,l(),a.unlockAll()}),[]),m=(0,o.useCallback)(t?()=>i.map(((t,n)=>v(e[t],c[n],t+n))):()=>i.length?[v(e[i.slice(-1)[0]],c.slice(-1)[0],i.slice(-1)[0]+0)]:null,[t,e]);function v(e,t,n){let o={modalData:t,openModal:u,closeModal:h,closeAllModal:p};return"object"!=typeof t||t[Symbol.iterator]||(o={...o,...t}),r().isValidElement(e)?r().cloneElement(e,{...o,key:n}):r().createElement(e,{...o,key:n})}return[i,c,{getModal:m,openModal:u,closeModal:h,closeAllModal:p}]};var f=n(379),h=n.n(f),p=n(528),m={insert:"head",singleton:!1};h()(p.Z,m);p.Z.locals;const v=({container:e,hidden:t,children:n,...l})=>{const c=(0,o.useRef)();if(!t){if("function"==typeof e)return e.prototype.isReactComponent?r().createElement(e,l,n):r().cloneElement(e({children:n,funcs:l}));if(r().isValidElement(e))return r().cloneElement(e,l,n)}return r().createElement("div",{className:"HOC_modal",ref:c,style:{display:t?"none":"flex"}},n)},y=({contextMode:e,Provider:t,children:n,...l})=>e?r().createElement(t,{value:l},n):r().createElement(o.Fragment,null,n);const b=function({children:e,map:t,contextMode:n,container:i,locker:a,...s}){const[d,f,{getModal:h,...p}]=u(t,!1,a),m=c(e,p);return r().createElement(o.Fragment,null,r().createElement(y,{contextMode:n,Provider:l.Provider,...p},m),r().createElement(v,{container:i,hidden:!d.length,...p},h()))};b.Multi=function({children:e,map:t,contextMode:n,container:i,locker:a,...s}){const[d,f,{getModal:h,...p}]=u(t,!0,a),m=c(e,p);return r().createElement(o.Fragment,null,r().createElement(y,{contextMode:n,Provider:l.Provider,...p},m),r().createElement(v,{container:i,hidden:!d.length,...p},h()))};const g=b},528:(e,t,n)=>{n.d(t,{Z:()=>l});var o=n(645),r=n.n(o)()((function(e){return e[1]}));r.push([e.id,".HOC_modal {\r\n position: fixed;\r\n width: 100%;\r\n height: 100%;\r\n top: 0;\r\n left: 0;\r\n background-color: rgba(0, 0, 0, 0.7);\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n z-index: 2;\r\n}\r\n",""]);const l=r},645:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=e(t);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,o){"string"==typeof e&&(e=[[null,e,""]]);var r={};if(o)for(var l=0;l<this.length;l++){var c=this[l][0];null!=c&&(r[c]=!0)}for(var i=0;i<e.length;i++){var a=[].concat(e[i]);o&&r[a[0]]||(n&&(a[2]?a[2]="".concat(n," and ").concat(a[2]):a[2]=n),t.push(a))}},t}},379:(e,t,n)=>{var o,r=function(){return void 0===o&&(o=Boolean(window&&document&&document.all&&!window.atob)),o},l=function(){var e={};return function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(e){n=null}e[t]=n}return e[t]}}(),c=[];function i(e){for(var t=-1,n=0;n<c.length;n++)if(c[n].identifier===e){t=n;break}return t}function a(e,t){for(var n={},o=[],r=0;r<e.length;r++){var l=e[r],a=t.base?l[0]+t.base:l[0],s=n[a]||0,d="".concat(a," ").concat(s);n[a]=s+1;var u=i(d),f={css:l[1],media:l[2],sourceMap:l[3]};-1!==u?(c[u].references++,c[u].updater(f)):c.push({identifier:d,updater:v(f,t),references:1}),o.push(d)}return o}function s(e){var t=document.createElement("style"),o=e.attributes||{};if(void 0===o.nonce){var r=n.nc;r&&(o.nonce=r)}if(Object.keys(o).forEach((function(e){t.setAttribute(e,o[e])})),"function"==typeof e.insert)e.insert(t);else{var c=l(e.insert||"head");if(!c)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");c.appendChild(t)}return t}var d,u=(d=[],function(e,t){return d[e]=t,d.filter(Boolean).join("\n")});function f(e,t,n,o){var r=n?"":o.media?"@media ".concat(o.media," {").concat(o.css,"}"):o.css;if(e.styleSheet)e.styleSheet.cssText=u(t,r);else{var l=document.createTextNode(r),c=e.childNodes;c[t]&&e.removeChild(c[t]),c.length?e.insertBefore(l,c[t]):e.appendChild(l)}}function h(e,t,n){var o=n.css,r=n.media,l=n.sourceMap;if(r?e.setAttribute("media",r):e.removeAttribute("media"),l&&"undefined"!=typeof btoa&&(o+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(l))))," */")),e.styleSheet)e.styleSheet.cssText=o;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(o))}}var p=null,m=0;function v(e,t){var n,o,r;if(t.singleton){var l=m++;n=p||(p=s(t)),o=f.bind(null,n,l,!1),r=f.bind(null,n,l,!0)}else n=s(t),o=h.bind(null,n,t),r=function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)};return o(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;o(e=t)}else r()}}e.exports=function(e,t){(t=t||{}).singleton||"boolean"==typeof t.singleton||(t.singleton=r());var n=a(e=e||[],t);return function(e){if(e=e||[],"[object Array]"===Object.prototype.toString.call(e)){for(var o=0;o<n.length;o++){var r=i(n[o]);c[r].references--}for(var l=a(e,t),s=0;s<n.length;s++){var d=i(n[s]);0===c[d].references&&(c[d].updater(),c.splice(d,1))}n=l}}}},888:t=>{t.exports=e}},n={};function o(e){if(n[e])return n[e].exports;var r=n[e]={id:e,exports:{}};return t[e](r,r.exports,o),r.exports}return o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o(539)})()}));
\ No newline at end of file \ No newline at end of file
{ {
"name": "modal-router", "name": "modal-router",
"version": "0.0.4", "version": "0.0.7",
"description": "a simple way to toggle modals", "description": "a simple way to toggle modals",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"build": "webpack --config webpack.prod.config.js", "build": "webpack --config webpack.prod.config.js",
"dev": "webpack --config webpack.dev.config.js" "dev": "webpack serve --config webpack.dev.config.js"
}, },
"keywords": [ "keywords": [
"react", "react",
...@@ -23,12 +23,14 @@ ...@@ -23,12 +23,14 @@
"@babel/preset-react": "^7.12.10", "@babel/preset-react": "^7.12.10",
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"css-loader": "^5.0.1", "css-loader": "^5.0.1",
"html-webpack-plugin": "^4.5.1",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"style-loader": "^2.0.0", "style-loader": "^2.0.0",
"terser-webpack-plugin": "^5.1.1", "terser-webpack-plugin": "^5.1.1",
"webpack": "^5.15.0", "webpack": "^5.15.0",
"webpack-cli": "^4.3.1" "webpack-cli": "^4.3.1",
"webpack-dev-server": "^3.11.2"
}, },
"dependencies": {}, "dependencies": {},
"peerDependencies": { "peerDependencies": {
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
\ No newline at end of file
...@@ -6,8 +6,6 @@ import './index.css' ...@@ -6,8 +6,6 @@ import './index.css'
// issue: container 优化 不重新渲染 // issue: container 优化 不重新渲染
export const ModalCan = ({ container, hidden, children, ...funcs }) => { export const ModalCan = ({ container, hidden, children, ...funcs }) => {
const ref = useRef() const ref = useRef()
useListener(ref.current, 'mousewheel', NoPopAndDef)
useListener(ref.current, 'touchmove', NoPopAndDef)
// issue: 优先mount?HOC_modal // issue: 优先mount?HOC_modal
// if (hidden) { // if (hidden) {
......
...@@ -6,15 +6,8 @@ import { useUpdate, useModals } from "./hooks.jsx"; ...@@ -6,15 +6,8 @@ import { useUpdate, useModals } from "./hooks.jsx";
// todo: mutile type // todo: mutile type
export default function Multi ({ children, map, contextMode, container, ...rest }) { export default function Multi ({ children, map, contextMode, container, locker, ...rest }) {
const update = useUpdate() const [modals, modalData, { getModal, ...funcs }] = useModals(map, true, locker)
const [modals, modalData, { getModal, openModal: open, closeModal: close, closeAllModal: closeAll }] = useModals(map, true)
const openModal = useCallback(enhance(open, update), [])
const closeModal = useCallback(enhance(close, update), [])
const closeAllModal = useCallback(enhance(closeAll, update), [])
const funcs = { openModal, closeModal, closeAllModal }
const getChildren = enhanceChildren(children, funcs) const getChildren = enhanceChildren(children, funcs)
return ( return (
......
import { getScrollBarSize } from "./getScrollBarSize.jsx";
// issue: 多locker同时存在时,由于dom元素始终只有一个
// 对于同一个元素的所有操作都得考虑任意顺序的执行方法,结果的正确性
const keys = new WeakMap()
/**
* 用于锁定某滚动对象,全局公用一个存储池,由于dom元素即锁定对象唯一,在完全解锁前皆不可滚动
* @param element locker的dom元素,即锁定滚动的对象
*/
export const ScrollLocker = (element = document.body) => {
// check: valid Node
if (typeof element !== 'object' || !element?.baseURI || !document.body.contains(element)) {
return console.warn("ScrollLocker: get invalid Node or dom doesn't exit in document.body, locker can't work as expected")
}
let random = Math.random().toString(16).slice(2, 10)
let k
if (keys.has(element)) {
k = keys.get(element)
} else {
k = new key(element)
keys.set(element, k)
}
// 每个新locker的标识是random,统一存储在keys上
return {
lock: () => {
k.push(random)
},
unlock: () => {
k.pop(random)
},
unlockAll: () => {
k.clear(random)
}
}
}
/**
* 记录各元素锁定滚动状态
* element: 被锁定元素
* previousStyle: 记录样式
* locked/ed: 是否锁定
* count/countSum: 多个locker使用同一个key,锁定同一个dom,用于确定锁定状态,0时解锁
* locker:[{r,c}] r为生成的随机标识用于区分多个locker,c为单一locker计数
* push: 某locker在key上增次
* pop: 某locker在key上减次
* clear: 某locker在key上清零
*/
class key {
constructor(element) {
this.element = element
this.previousStyle = {
overflow: element.style.overflow,
width: element.style.width
}
this.ed = false
this.countSum = 0
// {r:标识,c:数目}
this.locker = []
}
push (r) {
let find = false
for (const lock of this.locker) {
if (lock.r === r) {
lock.c++
this.count++
find = true
}
}
if (!find) {
this.locker.push({ r, c: 1 })
this.count++
}
}
pop (r) {
for (const lock of this.locker) {
if (lock.r === r) {
if (lock.c >= 1) {
lock.c--
this.count--
}
}
}
}
clear (r) {
for (const lock of this.locker) {
if (lock.r === r) {
if (lock.c >= 1) {
this.count -= lock.c
lock.c = 0
}
}
}
}
// count->countSum->locked?
get count () { return this.countSum }
set count (n) {
if (n <= 0) {
n = 0
this.locked = false
} else this.locked = true
this.countSum = n
}
// locked?->style change
get locked () { return this.ed }
set locked (b) {
// issue:确定locker对象处于滚动状态
// if ((this.element === document.body && window.innerWidth > document.documentElement.clientWidth)
// || this.element.scrollHeight > this.element.clientHeight) {
let w = getScrollBarSize()
if (w) {
this.element.style.width = b ? `calc(100% - ${w}px)` : this.previousStyle.width
}
// }
this.element.style.overflow = b ? 'hidden' : this.previousStyle.overflow
this.ed = b
}
}
\ No newline at end of file
...@@ -6,15 +6,8 @@ import { useUpdate, useModals } from "./hooks.jsx"; ...@@ -6,15 +6,8 @@ import { useUpdate, useModals } from "./hooks.jsx";
// todos:lifetime\callback // todos:lifetime\callback
export default function Single ({ children, map, contextMode, container, ...rest }) { export default function Single ({ children, map, contextMode, container, locker, ...rest }) {
const update = useUpdate() const [modals, modalData, { getModal, ...funcs }] = useModals(map, false, locker)
const [modals, modalData, { getModal, openModal: open, closeModal: close, closeAllModal: closeAll }] = useModals(map, false)
const openModal = useCallback(enhance(open, update), [])
const closeModal = useCallback(enhance(close, update), [])
const closeAllModal = useCallback(enhance(closeAll, update), [])
const funcs = { openModal, closeModal, closeAllModal }
const getChildren = enhanceChildren(children, funcs) const getChildren = enhanceChildren(children, funcs)
return ( return (
......
...@@ -2,19 +2,23 @@ import React from "react"; ...@@ -2,19 +2,23 @@ import React from "react";
const name = 'ModalRouter: ' const name = 'ModalRouter: '
export const NotMatched = `${name}In the map you provided, key didn't matched` export const NotMatched = `${name}In the map you provided, key didn't matched`
export const NotMatchedButPop = `${name}In the stack you pushed, key didn't matched. Maybe you closeModal with the wrong name, this may causes bug.`
export const InvalidElement_i = i => `${name}The Index:${i} Child isn't valid React Element` export const InvalidElement_i = i => `${name}The Index:${i} Child isn't valid React Element`
export const InvalidElement = `${name}get InvalidElement from prop/child you given` export const InvalidElement = `${name}get InvalidElement from prop/child you given`
export const NoPopAndDef = (e) => { export const NoPopAndDef = (e) => {
e.preventDefault && e.preventDefault() e.preventDefault && e.preventDefault()
e.returnValue = false
e.stopPropagation && e.stopPropagation() e.stopPropagation && e.stopPropagation()
return false; e.returnValue = false
return false
} }
export const enhance = (mainFunc, sub) => { export const enhance = (mainFunc, ...subs) => {
return function () { return function () {
mainFunc(...arguments) mainFunc(...arguments)
sub() for (const sub of subs)
sub()
} }
} }
......
let stored
export const getScrollBarSize = (refresh) => {
if (refresh || !stored) {
let inner = document.createElement('div')
inner.style.height = '110%';
let outer = document.createElement('div')
outer.style.visibity = 'hidden'
outer.style.width = '100px'
outer.style.height = '100px'
outer.style.overflow = 'hidden'
outer.appendChild(inner)
document.body.appendChild(outer)
const hidden = inner.offsetWidth
outer.style.overflow = 'scroll'
let scroll = inner.offsetWidth
if (hidden === scroll) {
scroll = outer.clientWidth
}
stored = hidden - scroll
document.body.removeChild(outer)
}
return stored
}
\ No newline at end of file
import React, { Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react" import React, { Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react"
import { NotMatched } from "./constant.jsx"; import { NotMatched, NotMatchedButPop } from "./constant.jsx";
// 目的:打开modal,背景内容不滚动,modal本身和内部元素可滚动
// bug?关闭一个弹窗的同时打开另一个,页面会怎么样?
// 源自https://github.com/ant-design/ant-design/issues/19340
import { ScrollLocker } from "./ScrollLocker.jsx";
export const useUpdate = () => { export const useUpdate = () => {
const [, forceUpdate] = useReducer(o => [], null) const [, forceUpdate] = useReducer(o => [], null)
return forceUpdate return forceUpdate
} }
export const useListener = (element = window, event, handler, options = {}) => { // delete: element=window
// issue: ali的ahooks是默认window对象,
// 但是这里用到一个禁止滚动的func,对滚动的root不友好,删了
// 原因是ModalCan首次渲染时element先拿到undefined,会默认挂方法到window上,
// ahooks的没试过,不知道
export const useListener = (element, event, handler, options = {}) => {
let { current } = useRef() let { current } = useRef()
current = handler current = handler
useEffect(() => { useEffect(() => {
...@@ -22,9 +32,13 @@ export const useListener = (element = window, event, handler, options = {}) => { ...@@ -22,9 +32,13 @@ export const useListener = (element = window, event, handler, options = {}) => {
} }
// issue: necessery to useCallback? // issue: necessery to useCallback?
export const useModals = (map, multi) => { export const useModals = (map, multi, locker_) => {
// console.log('modals', Date.now());
const update = useUpdate()
const { current: modalData } = useRef([]) const { current: modalData } = useRef([])
const { current: modals } = useRef([]) const { current: modals } = useRef([])
// issue:始终锁定document.body
const { current: locker } = useRef(ScrollLocker(locker_))
// multi? always push or ? // multi? always push or ?
const open = useCallback((str, data) => { const open = useCallback((str, data) => {
...@@ -33,10 +47,15 @@ export const useModals = (map, multi) => { ...@@ -33,10 +47,15 @@ export const useModals = (map, multi) => {
modalData.push(data) modalData.push(data)
} else throw new ReferenceError(NotMatched) } else throw new ReferenceError(NotMatched)
}, [map]) }, [map])
const openModal = useCallback((str, data) => open(str, data), [map]) const openModal = useCallback((str, data) => {
open(str, data)
update()
locker.lock()
}, [map])
// multi? always splice or ? // multi? always splice or ?
// single: always close all? // single: always close all?
// bug!!!!!
// issue: same key/double open cause unmatch // issue: same key/double open cause unmatch
const close = useCallback(multi ? (str) => { const close = useCallback(multi ? (str) => {
let index = modals.findIndex((v, i) => v === str) let index = modals.findIndex((v, i) => v === str)
...@@ -44,18 +63,23 @@ export const useModals = (map, multi) => { ...@@ -44,18 +63,23 @@ export const useModals = (map, multi) => {
modals.splice(index, 1) modals.splice(index, 1)
modalData.splice(index, 1) modalData.splice(index, 1)
} else { } else {
console.warn(NotMatchedButPop)
modals.pop() modals.pop()
modalData.pop() modalData.pop()
} }
} : () => closeAllModal(), [multi]) } : () => closeAllModal(), [multi])
const closeModal = useCallback((str) => { const closeModal = useCallback((str) => {
close(str) close(str)
update()
locker.unlock()
}, []) }, [])
// issue: should this func always provided to developer // issue: should this func always provided to developer?
// especially in singleMode, remind them to close after open // especially in singleMode, remind them to close after open
const closeAllModal = useCallback(() => { const closeAllModal = useCallback(() => {
modals.length = 0 modals.length = 0
modalData.length = 0 modalData.length = 0
update()
locker.unlockAll()
}, []) }, [])
// 返回的是表达式还是函数? // 返回的是表达式还是函数?
......
...@@ -4,4 +4,8 @@ export { AllModals } from "./context.jsx"; ...@@ -4,4 +4,8 @@ export { AllModals } from "./context.jsx";
const ModalRouter = Single const ModalRouter = Single
ModalRouter.Multi = Multi ModalRouter.Multi = Multi
export default ModalRouter export default ModalRouter
\ No newline at end of file
// 返回工具
export { ScrollLocker } from './ScrollLocker.jsx'
// todo 返回数据队列,modals等,可能有用
\ No newline at end of file
import React from "react"
export function Control ({ openModal, closeModal, closeAllModal, who }) {
// console.log('control:', arguments)
const random = () => {
Math.random() > 0.5 ? openModal(who) : closeModal(who)
}
return <button className="button" onClick={random}>random{who}</button>
}
export function Open ({ openModal, who }) {
// console.log('open:', arguments)
return <button className="button" onClick={() => openModal(who)}>open{who}</button>
}
export function Close ({ closeModal, who }) {
// console.log('close:', arguments)
return <button className="button" onClick={() => closeModal(who)}>close{who}</button>
}
export const CloseAll = ({ closeAllModal }) => {
return <button className="button" onClick={() => { closeAllModal() }}>closeAll</button>
}
\ No newline at end of file
* {
margin: 0;
padding: 0;
}
body {
background-color: gray;
border: white 3px solid;
}
.simpleHead {
text-align: center;
color: black;
}
.simpleEle {
position: absolute;
bottom: 0;
left: 0;
right: 0;
margin: 0 auto;
text-align: center;
color: red;
}
.modal {
border: greenyellow 3px solid;
border-radius: 25px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
color: red;
}
.small {
width: 35%;
height: 35%;
text-align: center;
vertical-align: middle;
background-color: black;
}
.big {
width: 75%;
height: 75%;
text-align: center;
vertical-align: middle;
background-color: black;
}
.giant {
width: 75%;
height: 200vh;
text-align: center;
vertical-align: middle;
background-color: black;
overflow: scroll;
}
.button {
width: 25%;
height: 50px;
margin: 3%;
border: black 3px solid;
border-radius: 5px;
color: blue;
}
import React, { Fragment, useContext, useEffect } from "react"
import ReactDOM from "react-dom"
import ModalRouter, { AllModals } from "../index.js";
import "./index.css";
import { SmallModal, BigModal, GiantModal, GiantContent } from "./modal.jsx";
import { Control, Open, Close } from "./control.jsx";
const map = {
small: SmallModal,
big: BigModal,
giant: <GiantModal><GiantContent /></GiantModal>,
}
const App = () => {
// console.log('app rendering...', Date.now());
return <Fragment>
<div className="simpleHead">simpleHead</div>
<ModalRouter.Multi map={map} contextMode>
<Open who='giant' />
<Open who='small' />
<Open who='big' />
<GiantModal />
<div className="simpleEle">
simpleEle
</div>
<div style={{ color: 'red', textAlign: 'center', width: '100%' }}>
<FuncTest />
</div>
</ModalRouter.Multi>
</Fragment>
}
const FuncTest = ({ children, ...rest }) => {
const { openModal, closeModal, closeAllModal } = useContext(AllModals)
useEffect(() => {
openModal('small')
return () => closeModal('small')
}, [])
return <span>FC Test</span>
}
ReactDOM.render(<App />, document.getElementById('root'))
\ No newline at end of file
import React from "react"
import { Open, Close, CloseAll } from "./control.jsx";
export const SmallModal = ({ children, openModal, closeModal, closeAllModal, ...rest }) => {
return <div className="modal small">
SmallModal{children}
<Close who='small' closeModal={closeModal} />
</div>
}
export const BigModal = ({ children, openModal, closeModal, closeAllModal, ...rest }) => {
return <div className="modal big">
BigModal{children}
<Open who='small' openModal={openModal} />
<Close who='big' closeModal={closeModal} />
</div>
}
export const GiantModal = ({ children, openModal, closeModal, closeAllModal, ...rest }) => {
return (
<div style={{ overflow: 'scroll' }}>
<div className="modal giant">
GiantModal
<Open who='small' openModal={openModal} />
<Close who='small' closeModal={closeModal} />
<Open who='big' openModal={openModal} />
<Close who='big' closeModal={closeModal} />
<Open who='giant' openModal={openModal} />
<Close who='giant' closeModal={closeModal} />
<CloseAll closeAllModal={closeAllModal} />
{children}
</div>
// </div>
)
}
export const GiantContent = ({ }) => {
return <div className="giant">
giantContent
</div>
}
\ No newline at end of file
const path = require('path') const path = require('path')
const TerserPlugin = require('terser-webpack-plugin') const html = require('html-webpack-plugin')
// test: npm link
// https://zhuanlan.zhihu.com/p/270649464
module.exports = { module.exports = {
mode: 'development', mode: 'development',
devtool: 'cheap-module-source-map',
entry: { entry: {
index: './src/index.js', index: './src/test/index.jsx',
}, },
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: '[name].js', filename: '[name].js',
libraryTarget: 'umd', libraryTarget: 'umd',
}, },
// bug devServer: {
// https://caijialinxx.github.io/2019/11/20/fix-react-error-321-by-webpack-externals/ contentBase: path.join(__dirname + 'dist'),
externals: { hot: true,
react: { open: true,
commonjs: 'react',
commonjs2: 'react',
amd: 'react',
root: 'React',
},
'react-dom': {
commonjs: 'react-dom',
commonjs2: 'react-dom',
amd: 'react-dom',
root: 'ReactDOM',
},
}, },
module: { module: {
rules: [ rules: [
...@@ -51,6 +38,13 @@ module.exports = { ...@@ -51,6 +38,13 @@ module.exports = {
}, },
] ]
}, },
plugins: [
new html({
template: path.resolve(__dirname, './public/index.html'),
favicon: './public/favicon.ico',
hash: true,
})
],
optimization: { optimization: {
minimize: false, minimize: false,
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment