@jielga/react-popup-window 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -111,12 +111,15 @@ const { open, Popup } = usePopupWindow({ title: 'Panel', url: '/popup.html' })
111
111
 
112
112
  `isOpen` turns `true` as soon as the window opens; `Popup` renders once the page has loaded.
113
113
  Important: do not point `url` at a route of the app, as that starts a second copy of the app inside the popup.
114
+ Start the page with `<!doctype html>`: without it, the popup lays out in quirks mode.
114
115
  If the page redirects to another origin, the popup closes and `onBlocked` is called.
115
116
 
116
117
  ## How it works
117
118
 
118
119
  The hook opens a same-origin window, `about:blank` by default or the page
119
- set with `url`, and creates a container element in its body. The `Popup` component renders its children with
120
+ set with `url`, and creates a container element in its body. The
121
+ `about:blank` document is written with an HTML5 doctype first, so the popup
122
+ lays out in standards mode, like the opener. The `Popup` component renders its children with
120
123
  `createPortal` into that container. A portal changes where DOM output is
121
124
  placed, not where the components sit in the tree, so popup content
122
125
  participates in the calling tree's state, context, and event system. React
@@ -185,8 +188,13 @@ document and kept current:
185
188
  - `<style>` and `<link rel="stylesheet">` elements. `<style>` contents are
186
189
  serialized from the CSSOM, so rules injected with `insertRule` are
187
190
  included.
191
+ - Rules added to or removed from a `<style>` sheet with `insertRule` and
192
+ `deleteRule`, applied to the popup as they happen. CSS-in-JS libraries
193
+ such as Emotion and styled-components add rules this way in production
194
+ builds.
188
195
  - Additions, removals, and text edits of style nodes in `<head>`. This
189
- covers Vite HMR, lazily loaded chunk CSS, and CSS-in-JS libraries.
196
+ covers Vite HMR, lazily loaded chunk CSS, and CSS-in-JS libraries in
197
+ development builds.
190
198
  - `class`, `style`, and `data-*` attributes on `<html>` and `<body>`.
191
199
  Theme systems keyed on root attributes propagate to the popup.
192
200
  - `document.adoptedStyleSheets`.
@@ -195,7 +203,10 @@ The popup loads each copied `<link>` stylesheet again.
195
203
  `Popup` renders once they have loaded or failed, so the first frame shown is styled.
196
204
  Alternate stylesheets and stylesheets whose `media` does not match are not waited for.
197
205
  If a stylesheet takes longer than 3 seconds, `Popup` renders without it.
198
- Note that a `<link>` added to the opener while the popup is open loads in the popup after the content is shown.
206
+ A `<link>` added to the opener while the popup is open, such as lazily loaded chunk CSS, is loaded by the popup again.
207
+ Until that copy has loaded, the popup applies the rules the opener already loaded, so content rendered after the opener's load is styled.
208
+ Note that a cross-origin stylesheet served without CORS cannot be read that way, and its content is unstyled until the popup has loaded it.
209
+ Edits inside an existing rule, rules inserted into a nested rule such as `@media`, and changes to `document.adoptedStyleSheets` after the popup opened are not mirrored.
199
210
 
200
211
  Set `copyStyles: false` to disable.
201
212
 
@@ -4,10 +4,17 @@
4
4
  *
5
5
  * - `<link rel="stylesheet">` and `<style>` elements are mirrored into the
6
6
  * target `<head>`. `<style>` contents are serialized from the CSSOM when
7
- * possible, so rules injected via `insertRule` (CSS-in-JS "speedy" mode)
8
- * are included.
7
+ * possible, so rules injected via `insertRule` are included.
8
+ * - Rules added to or removed from a `<style>` sheet later with
9
+ * `insertRule`/`deleteRule` are applied to its mirror as they happen. This
10
+ * is how CSS-in-JS libraries (Emotion, styled-components) add rules in
11
+ * production builds, and it changes no DOM.
9
12
  * - Additions/removals/edits of style nodes in the source `<head>` are
10
- * observed (covers Vite HMR, lazily loaded chunk CSS, styled-components).
13
+ * observed (covers Vite HMR, lazily loaded chunk CSS, CSS-in-JS in
14
+ * development builds).
15
+ * - A `<link>` added while watching is loaded by the target again. Until
16
+ * that copy has loaded, the rules the source already loaded are applied
17
+ * to the target through a constructed stylesheet.
11
18
  * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored
12
19
  * and kept in sync, so theme switching (e.g. a `dark` class) propagates.
13
20
  * - `document.adoptedStyleSheets` are re-constructed in the target document.
@@ -1 +1 @@
1
- {"version":3,"file":"copyStyles.d.ts","sourceRoot":"","sources":["../src/copyStyles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,UAAO,GAAG,MAAM,IAAI,CAEvF;AAKD,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB;;;OAGG;IACH,KAAK,EAAE,eAAe,EAAE,CAAA;CACzB;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,UAAO,GAAG,WAAW,CAiK1F"}
1
+ {"version":3,"file":"copyStyles.d.ts","sourceRoot":"","sources":["../src/copyStyles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,UAAO,GAAG,MAAM,IAAI,CAEvF;AA6CD,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB;;;OAGG;IACH,KAAK,EAAE,eAAe,EAAE,CAAA;CACzB;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,UAAO,GAAG,WAAW,CAiS1F"}
package/dist/index.cjs CHANGED
@@ -1,4 +1,5 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react"),t=require("react-dom");function n(e,t,n=!0){return i(e,t,n).stop}var r=[`rel`,`media`,`title`,`crossorigin`,`referrerpolicy`,`integrity`];function i(e,t,n=!0){let i=new Map,a=e=>{if(e.nodeType!==Node.ELEMENT_NODE)return!1;let t=e;return t.tagName===`STYLE`||t.tagName===`LINK`&&(t.getAttribute(`rel`)??``).toLowerCase().includes(`stylesheet`)},o=e=>{let t=e.sheet;if(t)try{return Array.from(t.cssRules).map(e=>e.cssText).join(`
2
- `)}catch{}return e.textContent??``},s=e=>{if(i.has(e))return;let n;if(e.tagName===`LINK`){let i=t.createElement(`link`);for(let t of r){let n=e.getAttribute(t);n!==null&&i.setAttribute(t,n)}i.href=e.href,n=i}else{let r=t.createElement(`style`);r.textContent=o(e);let i=e.getAttribute(`media`);i&&r.setAttribute(`media`,i),n=r}t.head.appendChild(n),i.set(e,n)},c=e=>{let t=i.get(e);t&&(t.textContent=o(e))},l=e=>{for(let[t,n]of i)(e===t||e.nodeType===Node.ELEMENT_NODE&&e.contains(t))&&(n.remove(),i.delete(t))},u=[`class`,`style`],d=()=>{let n=[[e.documentElement,t.documentElement],[e.body,t.body]];for(let[e,t]of n)if(!(!e||!t)){for(let n of e.attributes)(u.includes(n.name)||n.name.startsWith(`data-`))&&t.setAttribute(n.name,n.value);for(let n of Array.from(t.attributes))(u.includes(n.name)||n.name.startsWith(`data-`))&&!e.hasAttribute(n.name)&&t.removeAttribute(n.name)}},f=()=>{let n=t.defaultView;if(!(!n?.CSSStyleSheet||!(`adoptedStyleSheets`in e)))try{t.adoptedStyleSheets=e.adoptedStyleSheets.map(e=>{let t=new n.CSSStyleSheet;return t.replaceSync(Array.from(e.cssRules).map(e=>e.cssText).join(`
3
- `)),t})}catch{}};for(let t of e.querySelectorAll(`style, link[rel~="stylesheet" i]`))s(t);let p=Array.from(i.values()).filter(e=>e.tagName===`LINK`);if(d(),f(),!n||typeof MutationObserver>`u`)return{stop:()=>{},links:p};let m=new MutationObserver(e=>{for(let t of e){if(t.type===`characterData`){let e=t.target.parentElement;e?.tagName===`STYLE`&&c(e);continue}a(t.target)&&t.target.tagName===`STYLE`&&c(t.target);for(let e of t.addedNodes)if(a(e))s(e);else if(e.nodeType===Node.ELEMENT_NODE)for(let t of e.querySelectorAll(`style, link[rel~="stylesheet" i]`))s(t);for(let e of t.removedNodes)l(e)}});m.observe(e.head,{childList:!0,subtree:!0,characterData:!0});let h=new MutationObserver(()=>d());return h.observe(e.documentElement,{attributes:!0}),e.body&&h.observe(e.body,{attributes:!0}),{stop:()=>{m.disconnect(),h.disconnect()},links:p}}function a(e,t,n){let r=e.filter(e=>e.sheet===null&&o(e));if(r.length===0)return n(),()=>{};let i=r.length,a=()=>{i--,i===0&&c()},s=()=>{window.clearTimeout(l);for(let e of r)e.removeEventListener(`load`,a),e.removeEventListener(`error`,a)},c=()=>{s(),n()};for(let e of r)e.addEventListener(`load`,a),e.addEventListener(`error`,a);let l=window.setTimeout(c,t);return s}function o(e){if(/\balternate\b/i.test(e.rel))return!1;let t=e.ownerDocument.defaultView;return!e.media||!t?.matchMedia||t.matchMedia(e.media).matches}var s={popupWindow:null,container:null,blocked:!1},c=class{state=s;listeners=new Set;subscribe=e=>(this.listeners.add(e),()=>this.listeners.delete(e));getSnapshot=()=>this.state;getServerSnapshot=()=>s;setState(e){this.state={...this.state,...e};for(let e of this.listeners)e()}},l={popup:!0,width:640,height:480};function u(e){let t={...l,...e.features};return e.center!==!1&&t.left===void 0&&t.top===void 0&&typeof t.width==`number`&&typeof t.height==`number`&&(t.left=Math.max(0,Math.round(window.screenX+(window.outerWidth-t.width)/2)),t.top=Math.max(0,Math.round(window.screenY+(window.outerHeight-t.height)/2))),Object.entries(t).filter(([,e])=>e!==void 0&&e!==!1).map(([e,t])=>t===!0?`${e}=yes`:`${e}=${t}`).join(`,`)}var d=`about:blank`,f=20,p=3e3;function m(e){let t=e.document;return t.URL===d||t.readyState===`loading`?null:t}function h(n){function r({children:r}){let{container:i}=(0,e.useSyncExternalStore)(n.subscribe,n.getSnapshot,n.getServerSnapshot);return i?(0,t.createPortal)(r,i):null}return r.displayName=`PopupWindow.Popup`,r}function g(t={}){let[n]=(0,e.useState)(()=>new c),[r]=(0,e.useState)(()=>h(n)),o=(0,e.useRef)(t);o.current=t;let s=(0,e.useRef)(null),l=(0,e.useSyncExternalStore)(n.subscribe,n.getSnapshot,n.getServerSnapshot),g=(0,e.useCallback)(e=>{let{popupWindow:t}=n.getSnapshot();s.current?.(),s.current=null,t&&(!e&&!t.closed&&t.close(),n.setState({popupWindow:null,container:null}),o.current.onClose?.())},[n]),_=(0,e.useCallback)(()=>{if(typeof window>`u`)return null;let e=n.getSnapshot();if(e.popupWindow&&!e.popupWindow.closed)return e.popupWindow.focus(),e.popupWindow;let t=o.current,r=t.url??d,c=window.open(r,t.name??`_blank`,u(t));if(!c)return n.setState({blocked:!0}),t.onBlocked?.(),null;let l;try{l=c.document}catch{return c.close(),n.setState({blocked:!0}),t.onBlocked?.(),null}let h=()=>g(!0),_=()=>{setTimeout(()=>{c.closed&&h()},0)},v=window.setInterval(()=>{c.closed&&h()},250),y=()=>c.close();window.addEventListener(`pagehide`,y);let b,x=!1,S,C;s.current=()=>{window.clearInterval(v),window.clearInterval(b),window.removeEventListener(`pagehide`,y),x&&c.removeEventListener(`pagehide`,_),S?.(),C?.()};let w=e=>{x=!0,c.addEventListener(`pagehide`,_),e.title=t.title??document.title;let r=[];if(t.copyStyles!==!1){let t=i(document,e);S=t.stop,r=t.links}let o=e.createElement(`div`);o.setAttribute(`data-popup-window-root`,``),e.body.appendChild(o),C=a(r,p,()=>{n.setState({container:o}),t.onOpen?.(c)})};return n.setState({popupWindow:c,container:null,blocked:!1}),r===d?w(l):b=window.setInterval(()=>{if(c.closed){h();return}let e;try{e=m(c)}catch{g(!1),n.setState({blocked:!0}),t.onBlocked?.();return}e&&(window.clearInterval(b),w(e))},f),c},[n,g]),v=(0,e.useCallback)(()=>g(!1),[g]),y=(0,e.useCallback)(()=>{n.getSnapshot().popupWindow?g(!1):_()},[n,g,_]),b=(0,e.useCallback)(()=>{let{popupWindow:e}=n.getSnapshot();e&&!e.closed&&e.focus()},[n]);return(0,e.useEffect)(()=>()=>g(!1),[g]),{open:_,close:v,toggle:y,focus:b,isOpen:l.popupWindow!==null,isBlocked:l.blocked,popupWindow:l.popupWindow,Popup:r}}exports.copyStyles=n,exports.usePopupWindow=g;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react"),t=require("react-dom");function n(e,t,n=!0){return o(e,t,n).stop}var r=new WeakMap;function i(e,t){let n=r.get(e);if(!n){let t=new Set;n=t,r.set(e,t);let i=Object.getPrototypeOf(e);e.insertRule=function(e,n){let r=i.insertRule.call(this,e,n);for(let n of[...t])n({type:`insert`,rule:e,index:r});return r},e.deleteRule=function(e){i.deleteRule.call(this,e);for(let n of[...t])n({type:`delete`,index:e})}}let i=n;return i.add(t),()=>{if(i.delete(t),i.size>0)return;r.delete(e);let n=e;delete n.insertRule,delete n.deleteRule}}var a=[`rel`,`media`,`title`,`crossorigin`,`referrerpolicy`,`integrity`];function o(e,t,n=!0){let r=new Map,o=n&&typeof MutationObserver<`u`,s=e=>{if(e.nodeType!==Node.ELEMENT_NODE)return!1;let t=e;return t.tagName===`STYLE`||t.tagName===`LINK`&&(t.getAttribute(`rel`)??``).toLowerCase().includes(`stylesheet`)},c=e=>{let t=e.sheet;if(t)try{return Array.from(t.cssRules).map(e=>e.cssText).join(`
2
+ `)}catch{}return e.textContent??``},l=new Map,u=(e,t)=>{let n=e.sheet,r=l.get(e);if(r&&r.sheet===n){r.count=n.cssRules.length;return}if(r?.stop(),l.delete(e),!o||!n)return;let a={sheet:n,count:n.cssRules.length,stop:i(n,r=>{let i=t.sheet;try{if(i&&(r.type===`insert`?i.insertRule(r.rule,r.index):i.deleteRule(r.index),i.cssRules.length===n.cssRules.length)){a.count=n.cssRules.length;return}}catch{}g(e)})};l.set(e,a)},d=e=>{l.get(e)?.stop(),l.delete(e)},f=new Map,p=(e,n)=>{let r=t.defaultView;if(!r||typeof r.CSSStyleSheet!=`function`||!(`adoptedStyleSheets`in t)||/\balternate\b/i.test(e.rel))return;let i=null,a=()=>{if(!(n.sheet!==null||!e.sheet))try{let n=Array.from(e.sheet.cssRules).map(e=>e.cssText).join(`
3
+ `),a=new r.CSSStyleSheet({baseURL:e.href,media:e.media||void 0});a.replaceSync(n),t.adoptedStyleSheets=[...t.adoptedStyleSheets,a],i=a}catch{}},o=()=>{e.removeEventListener(`load`,a),n.removeEventListener(`load`,o),n.removeEventListener(`error`,o),f.delete(e);let r=i;r&&(t.adoptedStyleSheets=t.adoptedStyleSheets.filter(e=>e!==r)),i=null};n.addEventListener(`load`,o),n.addEventListener(`error`,o),f.set(e,o),e.sheet?a():e.addEventListener(`load`,a)},m=!0,h=e=>{if(r.has(e))return;let n;if(e.tagName===`LINK`){let r=t.createElement(`link`);for(let t of a){let n=e.getAttribute(t);n!==null&&r.setAttribute(t,n)}r.href=e.href,n=r}else{let r=t.createElement(`style`);r.textContent=c(e);let i=e.getAttribute(`media`);i&&r.setAttribute(`media`,i),n=r}t.head.appendChild(n),r.set(e,n),e.tagName===`STYLE`?u(e,n):!m&&o&&p(e,n)},g=e=>{let t=r.get(e);t&&(t.textContent=c(e),u(e,t))},_=e=>{for(let[t,n]of r)(e===t||e.nodeType===Node.ELEMENT_NODE&&e.contains(t))&&(n.remove(),r.delete(t),d(t),f.get(t)?.())},v=[`class`,`style`],y=()=>{let n=[[e.documentElement,t.documentElement],[e.body,t.body]];for(let[e,t]of n)if(!(!e||!t)){for(let n of e.attributes)(v.includes(n.name)||n.name.startsWith(`data-`))&&t.setAttribute(n.name,n.value);for(let n of Array.from(t.attributes))(v.includes(n.name)||n.name.startsWith(`data-`))&&!e.hasAttribute(n.name)&&t.removeAttribute(n.name)}},b=()=>{let n=t.defaultView;if(!(!n?.CSSStyleSheet||!(`adoptedStyleSheets`in e)))try{t.adoptedStyleSheets=e.adoptedStyleSheets.map(e=>{let t=new n.CSSStyleSheet;return t.replaceSync(Array.from(e.cssRules).map(e=>e.cssText).join(`
4
+ `)),t})}catch{}};for(let t of e.querySelectorAll(`style, link[rel~="stylesheet" i]`))h(t);m=!1;let x=Array.from(r.values()).filter(e=>e.tagName===`LINK`);if(y(),b(),!o)return{stop:()=>{},links:x};let S=new MutationObserver(e=>{for(let t of e){if(t.type===`characterData`){let e=t.target.parentElement;e?.tagName===`STYLE`&&g(e);continue}s(t.target)&&t.target.tagName===`STYLE`&&g(t.target);for(let e of t.addedNodes)if(s(e))h(e);else if(e.nodeType===Node.ELEMENT_NODE)for(let t of e.querySelectorAll(`style, link[rel~="stylesheet" i]`))h(t);for(let e of t.removedNodes)_(e)}});S.observe(e.head,{childList:!0,subtree:!0,characterData:!0});let C=new MutationObserver(()=>y());C.observe(e.documentElement,{attributes:!0}),e.body&&C.observe(e.body,{attributes:!0});let w=t.defaultView,T,E=()=>{for(let[e,t]of l)e.sheet===t.sheet&&t.sheet.cssRules.length!==t.count&&g(e);T=w?.requestAnimationFrame(E)};return w&&typeof w.requestAnimationFrame==`function`&&(T=w.requestAnimationFrame(E)),{stop:()=>{S.disconnect(),C.disconnect(),T!==void 0&&w?.cancelAnimationFrame(T);for(let e of l.values())e.stop();for(let e of[...f.values()])e();l.clear()},links:x}}function s(e,t,n){let r=e.filter(e=>e.sheet===null&&c(e));if(r.length===0)return n(),()=>{};let i=r.length,a=()=>{i--,i===0&&s()},o=()=>{window.clearTimeout(l);for(let e of r)e.removeEventListener(`load`,a),e.removeEventListener(`error`,a)},s=()=>{o(),n()};for(let e of r)e.addEventListener(`load`,a),e.addEventListener(`error`,a);let l=window.setTimeout(s,t);return o}function c(e){if(/\balternate\b/i.test(e.rel))return!1;let t=e.ownerDocument.defaultView;return!e.media||!t?.matchMedia||t.matchMedia(e.media).matches}var l={popupWindow:null,container:null,blocked:!1},u=class{state=l;listeners=new Set;subscribe=e=>(this.listeners.add(e),()=>this.listeners.delete(e));getSnapshot=()=>this.state;getServerSnapshot=()=>l;setState(e){this.state={...this.state,...e};for(let e of this.listeners)e()}},d={popup:!0,width:640,height:480};function f(e){let t={...d,...e.features};return e.center!==!1&&t.left===void 0&&t.top===void 0&&typeof t.width==`number`&&typeof t.height==`number`&&(t.left=Math.max(0,Math.round(window.screenX+(window.outerWidth-t.width)/2)),t.top=Math.max(0,Math.round(window.screenY+(window.outerHeight-t.height)/2))),Object.entries(t).filter(([,e])=>e!==void 0&&e!==!1).map(([e,t])=>t===!0?`${e}=yes`:`${e}=${t}`).join(`,`)}var p=`about:blank`,m=20,h=3e3;function g(e){e.open(),e.write(`<!DOCTYPE html><html><head></head><body></body></html>`),e.close()}function _(e){let t=e.document;return t.URL===p||t.readyState===`loading`?null:t}function v(n){function r({children:r}){let{container:i}=(0,e.useSyncExternalStore)(n.subscribe,n.getSnapshot,n.getServerSnapshot);return i?(0,t.createPortal)(r,i):null}return r.displayName=`PopupWindow.Popup`,r}function y(t={}){let[n]=(0,e.useState)(()=>new u),[r]=(0,e.useState)(()=>v(n)),i=(0,e.useRef)(t);i.current=t;let a=(0,e.useRef)(null),c=(0,e.useSyncExternalStore)(n.subscribe,n.getSnapshot,n.getServerSnapshot),l=(0,e.useCallback)(e=>{let{popupWindow:t}=n.getSnapshot();a.current?.(),a.current=null,t&&(!e&&!t.closed&&t.close(),n.setState({popupWindow:null,container:null}),i.current.onClose?.())},[n]),d=(0,e.useCallback)(()=>{if(typeof window>`u`)return null;let e=n.getSnapshot();if(e.popupWindow&&!e.popupWindow.closed)return e.popupWindow.focus(),e.popupWindow;let t=i.current,r=t.url??p,c=window.open(r,t.name??`_blank`,f(t));if(!c)return n.setState({blocked:!0}),t.onBlocked?.(),null;let u;try{u=c.document}catch{return c.close(),n.setState({blocked:!0}),t.onBlocked?.(),null}let d=()=>l(!0),v=()=>{setTimeout(()=>{c.closed&&d()},0)},y=window.setInterval(()=>{c.closed&&d()},250),b=()=>c.close();window.addEventListener(`pagehide`,b);let x,S=!1,C,w;a.current=()=>{window.clearInterval(y),window.clearInterval(x),window.removeEventListener(`pagehide`,b),S&&c.removeEventListener(`pagehide`,v),C?.(),w?.()};let T=e=>{S=!0,c.addEventListener(`pagehide`,v),e.title=t.title??document.title;let r=[];if(t.copyStyles!==!1){let t=o(document,e);C=t.stop,r=t.links}let i=e.createElement(`div`);i.setAttribute(`data-popup-window-root`,``),e.body.appendChild(i),w=s(r,h,()=>{n.setState({container:i}),t.onOpen?.(c)})};return n.setState({popupWindow:c,container:null,blocked:!1}),r===p?(g(u),T(u)):x=window.setInterval(()=>{if(c.closed){d();return}let e;try{e=_(c)}catch{l(!1),n.setState({blocked:!0}),t.onBlocked?.();return}e&&(window.clearInterval(x),T(e))},m),c},[n,l]),y=(0,e.useCallback)(()=>l(!1),[l]),b=(0,e.useCallback)(()=>{n.getSnapshot().popupWindow?l(!1):d()},[n,l,d]),x=(0,e.useCallback)(()=>{let{popupWindow:e}=n.getSnapshot();e&&!e.closed&&e.focus()},[n]);return(0,e.useEffect)(()=>()=>l(!1),[l]),{open:d,close:y,toggle:b,focus:x,isOpen:c.popupWindow!==null,isBlocked:c.blocked,popupWindow:c.popupWindow,Popup:r}}exports.copyStyles=n,exports.usePopupWindow=y;
4
5
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/copyStyles.ts","../src/whenStylesheetsLoad.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\n * in sync while the popup is open:\n *\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\n * possible, so rules injected via `insertRule` (CSS-in-JS \"speedy\" mode)\n * are included.\n * - Additions/removals/edits of style nodes in the source `<head>` are\n * observed (covers Vite HMR, lazily loaded chunk CSS, styled-components).\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\n *\n * Returns a function that stops observing.\n */\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\n return mirrorStyles(source, target, watch).stop\n}\n\n/** Attributes copied from a source `<link>` onto its mirror. */\nconst LINK_ATTRS = ['rel', 'media', 'title', 'crossorigin', 'referrerpolicy', 'integrity']\n\nexport interface StyleMirror {\n /** Stops observing the source document. */\n stop: () => void\n /**\n * The `<link>` elements created in `target` by the initial copy. The target\n * fetches each of them asynchronously.\n */\n links: HTMLLinkElement[]\n}\n\n/** {@link copyStyles}, plus the `<link>` elements the initial copy created. */\nexport function mirrorStyles(source: Document, target: Document, watch = true): StyleMirror {\n const mirrors = new Map<Element, Element>()\n\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\n if (node.nodeType !== Node.ELEMENT_NODE) return false\n const el = node as Element\n return (\n el.tagName === 'STYLE' ||\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\n )\n }\n\n const serializeStyle = (el: HTMLStyleElement): string => {\n const sheet = el.sheet\n if (sheet) {\n try {\n return Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n } catch {\n // Inaccessible cssRules — fall back to the raw text below.\n }\n }\n return el.textContent ?? ''\n }\n\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\n if (mirrors.has(el)) return\n let clone: Element\n if (el.tagName === 'LINK') {\n const link = target.createElement('link')\n // `rel` is copied as is, so an alternate stylesheet stays disabled.\n // `crossorigin` and `referrerpolicy` make the popup send the same request\n // as the opener, so it can reuse the cached response.\n for (const name of LINK_ATTRS) {\n const value = el.getAttribute(name)\n if (value !== null) link.setAttribute(name, value)\n }\n // .href resolves to an absolute URL, so relative hrefs keep working\n // from the popup's `about:blank` document.\n link.href = (el as HTMLLinkElement).href\n clone = link\n } else {\n const style = target.createElement('style')\n style.textContent = serializeStyle(el as HTMLStyleElement)\n const media = el.getAttribute('media')\n if (media) style.setAttribute('media', media)\n clone = style\n }\n target.head.appendChild(clone)\n mirrors.set(el, clone)\n }\n\n const refresh = (el: HTMLStyleElement): void => {\n const clone = mirrors.get(el)\n if (clone) clone.textContent = serializeStyle(el)\n }\n\n const unmirrorSubtree = (removed: Node): void => {\n for (const [src, clone] of mirrors) {\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\n clone.remove()\n mirrors.delete(src)\n }\n }\n }\n\n const ROOT_ATTRS = ['class', 'style']\n const syncRootAttrs = (): void => {\n const pairs: Array<[Element | null, Element | null]> = [\n [source.documentElement, target.documentElement],\n [source.body, target.body],\n ]\n for (const [from, to] of pairs) {\n if (!from || !to) continue\n for (const attr of from.attributes) {\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\n to.setAttribute(attr.name, attr.value)\n }\n }\n for (const attr of Array.from(to.attributes)) {\n if (\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\n !from.hasAttribute(attr.name)\n ) {\n to.removeAttribute(attr.name)\n }\n }\n }\n }\n\n const syncAdoptedSheets = (): void => {\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\n try {\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\n const copy = new targetWindow.CSSStyleSheet!()\n copy.replaceSync(\n Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n'),\n )\n return copy\n })\n } catch {\n // Best effort — older browsers or cross-origin rules.\n }\n }\n\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n const links = Array.from(mirrors.values()).filter(\n (clone): clone is HTMLLinkElement => clone.tagName === 'LINK',\n )\n syncRootAttrs()\n syncAdoptedSheets()\n\n if (!watch || typeof MutationObserver === 'undefined') {\n return { stop: () => {}, links }\n }\n\n const headObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'characterData') {\n const parent = record.target.parentElement\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\n continue\n }\n // childList\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\n // textContent assignment replaces the text node → childList on <style>\n refresh(record.target as HTMLStyleElement)\n }\n for (const added of record.addedNodes) {\n if (isStyleNode(added)) {\n mirror(added)\n } else if (added.nodeType === Node.ELEMENT_NODE) {\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n }\n }\n for (const removed of record.removedNodes) {\n unmirrorSubtree(removed)\n }\n }\n })\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\n\n const rootObserver = new MutationObserver(() => syncRootAttrs())\n rootObserver.observe(source.documentElement, { attributes: true })\n if (source.body) rootObserver.observe(source.body, { attributes: true })\n\n return {\n stop: () => {\n headObserver.disconnect()\n rootObserver.disconnect()\n },\n links,\n }\n}\n","/**\n * Calls `callback` once every stylesheet in `links` that applies has loaded\n * or failed, or after `timeoutMs`, whichever comes first.\n *\n * When none of them is still loading, `callback` runs synchronously, before\n * this function returns. Links that do not apply are not waited for: an\n * alternate stylesheet, or a `media` query that does not match, such as\n * `print`. Browsers fetch those at the lowest priority.\n *\n * Returns a function that stops waiting without calling `callback`.\n */\nexport function whenStylesheetsLoad(\n links: readonly HTMLLinkElement[],\n timeoutMs: number,\n callback: () => void,\n): () => void {\n const pending = links.filter((link) => link.sheet === null && applies(link))\n if (pending.length === 0) {\n callback()\n return () => {}\n }\n\n let remaining = pending.length\n const settle = () => {\n remaining--\n if (remaining === 0) finish()\n }\n const stop = () => {\n window.clearTimeout(timeout)\n for (const link of pending) {\n link.removeEventListener('load', settle)\n link.removeEventListener('error', settle)\n }\n }\n const finish = () => {\n stop()\n callback()\n }\n\n for (const link of pending) {\n link.addEventListener('load', settle)\n // A broken stylesheet must not keep the popup blank.\n link.addEventListener('error', settle)\n }\n const timeout = window.setTimeout(finish, timeoutMs)\n return stop\n}\n\nfunction applies(link: HTMLLinkElement): boolean {\n if (/\\balternate\\b/i.test(link.rel)) return false\n const view = link.ownerDocument.defaultView\n return !link.media || !view?.matchMedia || view.matchMedia(link.media).matches\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { FC } from 'react'\nimport { createPortal } from 'react-dom'\nimport { mirrorStyles } from './copyStyles'\nimport type {\n PopupProps,\n PopupWindowApi,\n PopupWindowFeatures,\n UsePopupWindowOptions,\n} from './types'\nimport { whenStylesheetsLoad } from './whenStylesheetsLoad'\n\ninterface PopupState {\n popupWindow: Window | null\n container: HTMLElement | null\n blocked: boolean\n}\n\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\n\nclass PopupStore {\n state: PopupState = INITIAL_STATE\n private listeners = new Set<() => void>()\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getSnapshot = (): PopupState => this.state\n\n getServerSnapshot = (): PopupState => INITIAL_STATE\n\n setState(partial: Partial<PopupState>): void {\n this.state = { ...this.state, ...partial }\n for (const listener of this.listeners) listener()\n }\n}\n\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\n\nfunction buildFeatures(options: UsePopupWindowOptions): string {\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\n if (\n options.center !== false &&\n merged.left === undefined &&\n merged.top === undefined &&\n typeof merged.width === 'number' &&\n typeof merged.height === 'number'\n ) {\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\n }\n return Object.entries(merged)\n .filter(([, value]) => value !== undefined && value !== false)\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\n .join(',')\n}\n\nconst ABOUT_BLANK = 'about:blank'\n/** Interval for checking whether the page given by `url` has loaded. */\nconst LOAD_POLL_MS = 20\n/** Longest time `Popup` waits for copied stylesheets before rendering anyway. */\nconst STYLES_TIMEOUT_MS = 3000\n\n/**\n * The popup's document once the page given by `url` has replaced the initial\n * `about:blank` document and finished parsing, otherwise `null`. Throws when\n * the document is not scriptable.\n */\nfunction loadedDocument(popupWindow: Window): Document | null {\n const doc = popupWindow.document\n if (doc.URL === ABOUT_BLANK || doc.readyState === 'loading') return null\n return doc\n}\n\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\n function Popup({ children }: PopupProps) {\n const { container } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n )\n return container ? createPortal(children, container) : null\n }\n Popup.displayName = 'PopupWindow.Popup'\n return Popup\n}\n\n/**\n * Open part of your React tree in a separate browser window.\n *\n * The popup content is rendered with a portal into the popup's document, so\n * it stays part of your component tree: state, context and event handlers\n * all keep working across windows.\n *\n * ```tsx\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\n *\n * return (\n * <>\n * <button onClick={open}>Open panel</button>\n * <Popup>\n * <MyPanel />\n * </Popup>\n * </>\n * )\n * ```\n */\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\n const [store] = useState(() => new PopupStore())\n const [Popup] = useState(() => createPopupComponent(store))\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const cleanupRef = useRef<(() => void) | null>(null)\n\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\n\n // `userClosed` is true when the window was closed outside our control\n // (user hit the close button, opener unloaded) — then we must not touch it.\n const closePopup = useCallback(\n (userClosed: boolean) => {\n const { popupWindow } = store.getSnapshot()\n cleanupRef.current?.()\n cleanupRef.current = null\n if (!popupWindow) return\n if (!userClosed && !popupWindow.closed) popupWindow.close()\n store.setState({ popupWindow: null, container: null })\n optionsRef.current.onClose?.()\n },\n [store],\n )\n\n const open = useCallback((): Window | null => {\n if (typeof window === 'undefined') return null\n const current = store.getSnapshot()\n if (current.popupWindow && !current.popupWindow.closed) {\n current.popupWindow.focus()\n return current.popupWindow\n }\n\n const opts = optionsRef.current\n const url = opts.url ?? ABOUT_BLANK\n const popupWindow = window.open(url, opts.name ?? '_blank', buildFeatures(opts))\n if (!popupWindow) {\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n // Sandboxed embedders (VS Code's built-in browser, CodeSandbox/StackBlitz\n // previews, iframes sandboxed without popup-escape) open the popup with an\n // opaque origin, so reading its document throws a SecurityError. Treat\n // that as blocked instead of stranding a blank window the portal can\n // never reach.\n let initialDoc: Document\n try {\n initialDoc = popupWindow.document\n } catch {\n popupWindow.close()\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n const handleExternalClose = () => closePopup(true)\n const onPopupPagehide = () => {\n // pagehide also fires on navigation; only treat it as a close when the\n // window really is gone a tick later.\n setTimeout(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 0)\n }\n\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\n const closePoll = window.setInterval(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 250)\n\n const onOpenerPagehide = () => popupWindow.close()\n window.addEventListener('pagehide', onOpenerPagehide)\n\n let loadPoll: number | undefined\n let prepared = false\n let stopStyleSync: (() => void) | undefined\n let stopStylesWait: (() => void) | undefined\n\n cleanupRef.current = () => {\n window.clearInterval(closePoll)\n window.clearInterval(loadPoll)\n window.removeEventListener('pagehide', onOpenerPagehide)\n if (prepared) popupWindow.removeEventListener('pagehide', onPopupPagehide)\n stopStyleSync?.()\n stopStylesWait?.()\n }\n\n // Title, styles and the portal container go into the document that\n // stays: `about:blank` itself, or the page `url` loads in its place.\n const prepare = (doc: Document) => {\n prepared = true\n popupWindow.addEventListener('pagehide', onPopupPagehide)\n doc.title = opts.title ?? document.title\n let links: HTMLLinkElement[] = []\n if (opts.copyStyles !== false) {\n const styles = mirrorStyles(document, doc)\n stopStyleSync = styles.stop\n links = styles.links\n }\n const container = doc.createElement('div')\n container.setAttribute('data-popup-window-root', '')\n doc.body.appendChild(container)\n // The popup fetches copied <link> stylesheets asynchronously. Render\n // only once they have loaded, so the first paint is not unstyled.\n stopStylesWait = whenStylesheetsLoad(links, STYLES_TIMEOUT_MS, () => {\n store.setState({ container })\n opts.onOpen?.(popupWindow)\n })\n }\n\n store.setState({ popupWindow, container: null, blocked: false })\n\n if (url === ABOUT_BLANK) {\n prepare(initialDoc)\n } else {\n // The window starts on an initial about:blank document and then\n // navigates to `url`. Wait until that page has replaced it and parsed.\n loadPoll = window.setInterval(() => {\n if (popupWindow.closed) {\n handleExternalClose()\n return\n }\n let doc: Document | null\n try {\n doc = loadedDocument(popupWindow)\n } catch {\n // The page went somewhere the opener cannot script, such as a\n // cross-origin redirect: nothing can ever render there.\n closePopup(false)\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return\n }\n if (!doc) return\n window.clearInterval(loadPoll)\n prepare(doc)\n }, LOAD_POLL_MS)\n }\n\n return popupWindow\n }, [store, closePopup])\n\n const close = useCallback(() => closePopup(false), [closePopup])\n\n const toggle = useCallback(() => {\n if (store.getSnapshot().popupWindow) {\n closePopup(false)\n } else {\n open()\n }\n }, [store, closePopup, open])\n\n const focus = useCallback(() => {\n const { popupWindow } = store.getSnapshot()\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\n }, [store])\n\n // Close the popup when the owning component unmounts — its portal content\n // would unmount anyway, leaving an empty window behind.\n useEffect(() => {\n return () => closePopup(false)\n }, [closePopup])\n\n return {\n open,\n close,\n toggle,\n focus,\n isOpen: state.popupWindow !== null,\n isBlocked: state.blocked,\n popupWindow: state.popupWindow,\n Popup,\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/copyStyles.ts","../src/whenStylesheetsLoad.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\n * in sync while the popup is open:\n *\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\n * possible, so rules injected via `insertRule` are included.\n * - Rules added to or removed from a `<style>` sheet later with\n * `insertRule`/`deleteRule` are applied to its mirror as they happen. This\n * is how CSS-in-JS libraries (Emotion, styled-components) add rules in\n * production builds, and it changes no DOM.\n * - Additions/removals/edits of style nodes in the source `<head>` are\n * observed (covers Vite HMR, lazily loaded chunk CSS, CSS-in-JS in\n * development builds).\n * - A `<link>` added while watching is loaded by the target again. Until\n * that copy has loaded, the rules the source already loaded are applied\n * to the target through a constructed stylesheet.\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\n *\n * Returns a function that stops observing.\n */\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\n return mirrorStyles(source, target, watch).stop\n}\n\ntype RuleChange = { type: 'insert'; rule: string; index: number } | { type: 'delete'; index: number }\ntype RuleListener = (change: RuleChange) => void\n\nconst ruleListeners = new WeakMap<CSSStyleSheet, Set<RuleListener>>()\n\n/**\n * Calls `listener` after each `insertRule`/`deleteRule` call on `sheet`.\n * Wraps the two methods on this sheet object only, not on the prototype, and\n * restores them once the sheet's last listener is removed. Returns a function\n * that removes the listener.\n */\nfunction onRuleChange(sheet: CSSStyleSheet, listener: RuleListener): () => void {\n let listeners = ruleListeners.get(sheet)\n if (!listeners) {\n const all = new Set<RuleListener>()\n listeners = all\n ruleListeners.set(sheet, all)\n const proto = Object.getPrototypeOf(sheet) as CSSStyleSheet\n sheet.insertRule = function (this: CSSStyleSheet, rule: string, index?: number): number {\n const inserted = proto.insertRule.call(this, rule, index)\n for (const notify of [...all]) notify({ type: 'insert', rule, index: inserted })\n return inserted\n }\n sheet.deleteRule = function (this: CSSStyleSheet, index: number): void {\n proto.deleteRule.call(this, index)\n for (const notify of [...all]) notify({ type: 'delete', index })\n }\n }\n const own = listeners\n own.add(listener)\n return () => {\n own.delete(listener)\n if (own.size > 0) return\n ruleListeners.delete(sheet)\n const wrapped = sheet as { insertRule?: unknown; deleteRule?: unknown }\n delete wrapped.insertRule\n delete wrapped.deleteRule\n }\n}\n\n/** Attributes copied from a source `<link>` onto its mirror. */\nconst LINK_ATTRS = ['rel', 'media', 'title', 'crossorigin', 'referrerpolicy', 'integrity']\n\nexport interface StyleMirror {\n /** Stops observing the source document. */\n stop: () => void\n /**\n * The `<link>` elements created in `target` by the initial copy. The target\n * fetches each of them asynchronously.\n */\n links: HTMLLinkElement[]\n}\n\n/** {@link copyStyles}, plus the `<link>` elements the initial copy created. */\nexport function mirrorStyles(source: Document, target: Document, watch = true): StyleMirror {\n const mirrors = new Map<Element, Element>()\n const observing = watch && typeof MutationObserver !== 'undefined'\n\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\n if (node.nodeType !== Node.ELEMENT_NODE) return false\n const el = node as Element\n return (\n el.tagName === 'STYLE' ||\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\n )\n }\n\n const serializeStyle = (el: HTMLStyleElement): string => {\n const sheet = el.sheet\n if (sheet) {\n try {\n return Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n } catch {\n // Inaccessible cssRules — fall back to the raw text below.\n }\n }\n return el.textContent ?? ''\n }\n\n interface RuleSync {\n sheet: CSSStyleSheet\n /** Rule count of `sheet` that the mirror reflects. */\n count: number\n stop: () => void\n }\n const ruleSyncs = new Map<HTMLStyleElement, RuleSync>()\n\n // CSS-in-JS libraries in production builds add rules with insertRule, which\n // changes no DOM. Apply each change to the mirror as it happens, so content\n // that renders in the same task, such as the popup's first render, is\n // already styled.\n const syncRules = (el: HTMLStyleElement, clone: HTMLStyleElement): void => {\n const sheet = el.sheet\n const current = ruleSyncs.get(el)\n if (current && current.sheet === sheet) {\n current.count = sheet.cssRules.length\n return\n }\n current?.stop()\n ruleSyncs.delete(el)\n if (!observing || !sheet) return\n const sync: RuleSync = {\n sheet,\n count: sheet.cssRules.length,\n stop: onRuleChange(sheet, (change) => {\n const copy = clone.sheet\n try {\n if (copy) {\n if (change.type === 'insert') copy.insertRule(change.rule, change.index)\n else copy.deleteRule(change.index)\n if (copy.cssRules.length === sheet.cssRules.length) {\n sync.count = sheet.cssRules.length\n return\n }\n }\n } catch {\n // Out of step with the source - copy the whole sheet again below.\n }\n refresh(el)\n }),\n }\n ruleSyncs.set(el, sync)\n }\n\n const unsyncRules = (el: Element): void => {\n ruleSyncs.get(el as HTMLStyleElement)?.stop()\n ruleSyncs.delete(el as HTMLStyleElement)\n }\n\n // A <link> added while watching, such as lazily loaded chunk CSS: the\n // target loads its copy separately, and content that waits only for the\n // source's copy, as bundlers do, can render before the target's has\n // loaded. Until it has, apply the rules the source already parsed through a\n // constructed sheet. `baseURL` keeps relative url()s resolving against the\n // stylesheet. Adopted sheets come last in the cascade, which can change\n // precedence for that short time. Unreadable cross-origin sheets are left\n // to load normally.\n const unbridgeLinks = new Map<Element, () => void>()\n const bridgeLink = (el: HTMLLinkElement, clone: HTMLLinkElement): void => {\n const view = target.defaultView as (Window & typeof globalThis) | null\n if (!view || typeof view.CSSStyleSheet !== 'function' || !('adoptedStyleSheets' in target)) {\n return\n }\n if (/\\balternate\\b/i.test(el.rel)) return\n let bridge: CSSStyleSheet | null = null\n const add = (): void => {\n if (clone.sheet !== null || !el.sheet) return\n try {\n const text = Array.from(el.sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n const sheet = new view.CSSStyleSheet({ baseURL: el.href, media: el.media || undefined })\n sheet.replaceSync(text)\n target.adoptedStyleSheets = [...target.adoptedStyleSheets, sheet]\n bridge = sheet\n } catch {\n // Cross-origin rules or no constructable stylesheets - wait for the copy.\n }\n }\n const remove = (): void => {\n el.removeEventListener('load', add)\n clone.removeEventListener('load', remove)\n clone.removeEventListener('error', remove)\n unbridgeLinks.delete(el)\n const sheet = bridge\n if (sheet) target.adoptedStyleSheets = target.adoptedStyleSheets.filter((s) => s !== sheet)\n bridge = null\n }\n clone.addEventListener('load', remove)\n clone.addEventListener('error', remove)\n unbridgeLinks.set(el, remove)\n if (el.sheet) add()\n else el.addEventListener('load', add)\n }\n\n // True during the initial copy. `usePopupWindow` waits for those links\n // itself before rendering.\n let copying = true\n\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\n if (mirrors.has(el)) return\n let clone: Element\n if (el.tagName === 'LINK') {\n const link = target.createElement('link')\n // `rel` is copied as is, so an alternate stylesheet stays disabled.\n // `crossorigin` and `referrerpolicy` make the popup send the same request\n // as the opener, so it can reuse the cached response.\n for (const name of LINK_ATTRS) {\n const value = el.getAttribute(name)\n if (value !== null) link.setAttribute(name, value)\n }\n // .href resolves to an absolute URL, so relative hrefs keep working\n // from the popup's `about:blank` document.\n link.href = (el as HTMLLinkElement).href\n clone = link\n } else {\n const style = target.createElement('style')\n style.textContent = serializeStyle(el as HTMLStyleElement)\n const media = el.getAttribute('media')\n if (media) style.setAttribute('media', media)\n clone = style\n }\n target.head.appendChild(clone)\n mirrors.set(el, clone)\n if (el.tagName === 'STYLE') syncRules(el as HTMLStyleElement, clone as HTMLStyleElement)\n else if (!copying && observing) bridgeLink(el as HTMLLinkElement, clone as HTMLLinkElement)\n }\n\n const refresh = (el: HTMLStyleElement): void => {\n const clone = mirrors.get(el)\n if (!clone) return\n clone.textContent = serializeStyle(el)\n // New text gives the source a new sheet object.\n syncRules(el, clone as HTMLStyleElement)\n }\n\n const unmirrorSubtree = (removed: Node): void => {\n for (const [src, clone] of mirrors) {\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\n clone.remove()\n mirrors.delete(src)\n unsyncRules(src)\n unbridgeLinks.get(src)?.()\n }\n }\n }\n\n const ROOT_ATTRS = ['class', 'style']\n const syncRootAttrs = (): void => {\n const pairs: Array<[Element | null, Element | null]> = [\n [source.documentElement, target.documentElement],\n [source.body, target.body],\n ]\n for (const [from, to] of pairs) {\n if (!from || !to) continue\n for (const attr of from.attributes) {\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\n to.setAttribute(attr.name, attr.value)\n }\n }\n for (const attr of Array.from(to.attributes)) {\n if (\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\n !from.hasAttribute(attr.name)\n ) {\n to.removeAttribute(attr.name)\n }\n }\n }\n }\n\n const syncAdoptedSheets = (): void => {\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\n try {\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\n const copy = new targetWindow.CSSStyleSheet!()\n copy.replaceSync(\n Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n'),\n )\n return copy\n })\n } catch {\n // Best effort — older browsers or cross-origin rules.\n }\n }\n\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n copying = false\n const links = Array.from(mirrors.values()).filter(\n (clone): clone is HTMLLinkElement => clone.tagName === 'LINK',\n )\n syncRootAttrs()\n syncAdoptedSheets()\n\n if (!observing) {\n return { stop: () => {}, links }\n }\n\n const headObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'characterData') {\n const parent = record.target.parentElement\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\n continue\n }\n // childList\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\n // textContent assignment replaces the text node → childList on <style>\n refresh(record.target as HTMLStyleElement)\n }\n for (const added of record.addedNodes) {\n if (isStyleNode(added)) {\n mirror(added)\n } else if (added.nodeType === Node.ELEMENT_NODE) {\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n }\n }\n for (const removed of record.removedNodes) {\n unmirrorSubtree(removed)\n }\n }\n })\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\n\n const rootObserver = new MutationObserver(() => syncRootAttrs())\n rootObserver.observe(source.documentElement, { attributes: true })\n if (source.body) rootObserver.observe(source.body, { attributes: true })\n\n // Fallback for rule changes that bypass the sheet's own methods, such as\n // CSSStyleSheet.prototype.insertRule.call(sheet, ...): once per frame of the\n // target window, copy again any sheet whose rule count changed.\n const view = target.defaultView\n let frame: number | undefined\n const checkRuleCounts = (): void => {\n for (const [el, sync] of ruleSyncs) {\n if (el.sheet === sync.sheet && sync.sheet.cssRules.length !== sync.count) refresh(el)\n }\n frame = view?.requestAnimationFrame(checkRuleCounts)\n }\n if (view && typeof view.requestAnimationFrame === 'function') {\n frame = view.requestAnimationFrame(checkRuleCounts)\n }\n\n return {\n stop: () => {\n headObserver.disconnect()\n rootObserver.disconnect()\n if (frame !== undefined) view?.cancelAnimationFrame(frame)\n for (const sync of ruleSyncs.values()) sync.stop()\n for (const remove of [...unbridgeLinks.values()]) remove()\n ruleSyncs.clear()\n },\n links,\n }\n}\n","/**\n * Calls `callback` once every stylesheet in `links` that applies has loaded\n * or failed, or after `timeoutMs`, whichever comes first.\n *\n * When none of them is still loading, `callback` runs synchronously, before\n * this function returns. Links that do not apply are not waited for: an\n * alternate stylesheet, or a `media` query that does not match, such as\n * `print`. Browsers fetch those at the lowest priority.\n *\n * Returns a function that stops waiting without calling `callback`.\n */\nexport function whenStylesheetsLoad(\n links: readonly HTMLLinkElement[],\n timeoutMs: number,\n callback: () => void,\n): () => void {\n const pending = links.filter((link) => link.sheet === null && applies(link))\n if (pending.length === 0) {\n callback()\n return () => {}\n }\n\n let remaining = pending.length\n const settle = () => {\n remaining--\n if (remaining === 0) finish()\n }\n const stop = () => {\n window.clearTimeout(timeout)\n for (const link of pending) {\n link.removeEventListener('load', settle)\n link.removeEventListener('error', settle)\n }\n }\n const finish = () => {\n stop()\n callback()\n }\n\n for (const link of pending) {\n link.addEventListener('load', settle)\n // A broken stylesheet must not keep the popup blank.\n link.addEventListener('error', settle)\n }\n const timeout = window.setTimeout(finish, timeoutMs)\n return stop\n}\n\nfunction applies(link: HTMLLinkElement): boolean {\n if (/\\balternate\\b/i.test(link.rel)) return false\n const view = link.ownerDocument.defaultView\n return !link.media || !view?.matchMedia || view.matchMedia(link.media).matches\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { FC } from 'react'\nimport { createPortal } from 'react-dom'\nimport { mirrorStyles } from './copyStyles'\nimport type {\n PopupProps,\n PopupWindowApi,\n PopupWindowFeatures,\n UsePopupWindowOptions,\n} from './types'\nimport { whenStylesheetsLoad } from './whenStylesheetsLoad'\n\ninterface PopupState {\n popupWindow: Window | null\n container: HTMLElement | null\n blocked: boolean\n}\n\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\n\nclass PopupStore {\n state: PopupState = INITIAL_STATE\n private listeners = new Set<() => void>()\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getSnapshot = (): PopupState => this.state\n\n getServerSnapshot = (): PopupState => INITIAL_STATE\n\n setState(partial: Partial<PopupState>): void {\n this.state = { ...this.state, ...partial }\n for (const listener of this.listeners) listener()\n }\n}\n\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\n\nfunction buildFeatures(options: UsePopupWindowOptions): string {\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\n if (\n options.center !== false &&\n merged.left === undefined &&\n merged.top === undefined &&\n typeof merged.width === 'number' &&\n typeof merged.height === 'number'\n ) {\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\n }\n return Object.entries(merged)\n .filter(([, value]) => value !== undefined && value !== false)\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\n .join(',')\n}\n\nconst ABOUT_BLANK = 'about:blank'\n/** Interval for checking whether the page given by `url` has loaded. */\nconst LOAD_POLL_MS = 20\n/** Longest time `Popup` waits for copied stylesheets before rendering anyway. */\nconst STYLES_TIMEOUT_MS = 3000\n\n/**\n * Gives the initial `about:blank` document an HTML5 doctype, so the popup\n * lays out in standards mode like the opener instead of in quirks mode. Only\n * the parser can set the mode, so the document is written again. In Chromium\n * its `URL` then reports the opener's URL; a reload still loads `about:blank`.\n */\nfunction writeStandardsDocument(doc: Document): void {\n doc.open()\n doc.write('<!DOCTYPE html><html><head></head><body></body></html>')\n doc.close()\n}\n\n/**\n * The popup's document once the page given by `url` has replaced the initial\n * `about:blank` document and finished parsing, otherwise `null`. Throws when\n * the document is not scriptable.\n */\nfunction loadedDocument(popupWindow: Window): Document | null {\n const doc = popupWindow.document\n if (doc.URL === ABOUT_BLANK || doc.readyState === 'loading') return null\n return doc\n}\n\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\n function Popup({ children }: PopupProps) {\n const { container } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n )\n return container ? createPortal(children, container) : null\n }\n Popup.displayName = 'PopupWindow.Popup'\n return Popup\n}\n\n/**\n * Open part of your React tree in a separate browser window.\n *\n * The popup content is rendered with a portal into the popup's document, so\n * it stays part of your component tree: state, context and event handlers\n * all keep working across windows.\n *\n * ```tsx\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\n *\n * return (\n * <>\n * <button onClick={open}>Open panel</button>\n * <Popup>\n * <MyPanel />\n * </Popup>\n * </>\n * )\n * ```\n */\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\n const [store] = useState(() => new PopupStore())\n const [Popup] = useState(() => createPopupComponent(store))\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const cleanupRef = useRef<(() => void) | null>(null)\n\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\n\n // `userClosed` is true when the window was closed outside our control\n // (user hit the close button, opener unloaded) — then we must not touch it.\n const closePopup = useCallback(\n (userClosed: boolean) => {\n const { popupWindow } = store.getSnapshot()\n cleanupRef.current?.()\n cleanupRef.current = null\n if (!popupWindow) return\n if (!userClosed && !popupWindow.closed) popupWindow.close()\n store.setState({ popupWindow: null, container: null })\n optionsRef.current.onClose?.()\n },\n [store],\n )\n\n const open = useCallback((): Window | null => {\n if (typeof window === 'undefined') return null\n const current = store.getSnapshot()\n if (current.popupWindow && !current.popupWindow.closed) {\n current.popupWindow.focus()\n return current.popupWindow\n }\n\n const opts = optionsRef.current\n const url = opts.url ?? ABOUT_BLANK\n const popupWindow = window.open(url, opts.name ?? '_blank', buildFeatures(opts))\n if (!popupWindow) {\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n // Sandboxed embedders (VS Code's built-in browser, CodeSandbox/StackBlitz\n // previews, iframes sandboxed without popup-escape) open the popup with an\n // opaque origin, so reading its document throws a SecurityError. Treat\n // that as blocked instead of stranding a blank window the portal can\n // never reach.\n let initialDoc: Document\n try {\n initialDoc = popupWindow.document\n } catch {\n popupWindow.close()\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n const handleExternalClose = () => closePopup(true)\n const onPopupPagehide = () => {\n // pagehide also fires on navigation; only treat it as a close when the\n // window really is gone a tick later.\n setTimeout(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 0)\n }\n\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\n const closePoll = window.setInterval(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 250)\n\n const onOpenerPagehide = () => popupWindow.close()\n window.addEventListener('pagehide', onOpenerPagehide)\n\n let loadPoll: number | undefined\n let prepared = false\n let stopStyleSync: (() => void) | undefined\n let stopStylesWait: (() => void) | undefined\n\n cleanupRef.current = () => {\n window.clearInterval(closePoll)\n window.clearInterval(loadPoll)\n window.removeEventListener('pagehide', onOpenerPagehide)\n if (prepared) popupWindow.removeEventListener('pagehide', onPopupPagehide)\n stopStyleSync?.()\n stopStylesWait?.()\n }\n\n // Title, styles and the portal container go into the document that\n // stays: `about:blank` itself, or the page `url` loads in its place.\n const prepare = (doc: Document) => {\n prepared = true\n popupWindow.addEventListener('pagehide', onPopupPagehide)\n doc.title = opts.title ?? document.title\n let links: HTMLLinkElement[] = []\n if (opts.copyStyles !== false) {\n const styles = mirrorStyles(document, doc)\n stopStyleSync = styles.stop\n links = styles.links\n }\n const container = doc.createElement('div')\n container.setAttribute('data-popup-window-root', '')\n doc.body.appendChild(container)\n // The popup fetches copied <link> stylesheets asynchronously. Render\n // only once they have loaded, so the first paint is not unstyled.\n stopStylesWait = whenStylesheetsLoad(links, STYLES_TIMEOUT_MS, () => {\n store.setState({ container })\n opts.onOpen?.(popupWindow)\n })\n }\n\n store.setState({ popupWindow, container: null, blocked: false })\n\n if (url === ABOUT_BLANK) {\n // Before prepare(): writing the document removes event listeners on the\n // popup window.\n writeStandardsDocument(initialDoc)\n prepare(initialDoc)\n } else {\n // The window starts on an initial about:blank document and then\n // navigates to `url`. Wait until that page has replaced it and parsed.\n loadPoll = window.setInterval(() => {\n if (popupWindow.closed) {\n handleExternalClose()\n return\n }\n let doc: Document | null\n try {\n doc = loadedDocument(popupWindow)\n } catch {\n // The page went somewhere the opener cannot script, such as a\n // cross-origin redirect: nothing can ever render there.\n closePopup(false)\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return\n }\n if (!doc) return\n window.clearInterval(loadPoll)\n prepare(doc)\n }, LOAD_POLL_MS)\n }\n\n return popupWindow\n }, [store, closePopup])\n\n const close = useCallback(() => closePopup(false), [closePopup])\n\n const toggle = useCallback(() => {\n if (store.getSnapshot().popupWindow) {\n closePopup(false)\n } else {\n open()\n }\n }, [store, closePopup, open])\n\n const focus = useCallback(() => {\n const { popupWindow } = store.getSnapshot()\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\n }, [store])\n\n // Close the popup when the owning component unmounts — its portal content\n // would unmount anyway, leaving an empty window behind.\n useEffect(() => {\n return () => closePopup(false)\n }, [closePopup])\n\n return {\n open,\n close,\n toggle,\n focus,\n isOpen: state.popupWindow !== null,\n isBlocked: state.blocked,\n popupWindow: state.popupWindow,\n Popup,\n }\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,9 +2,40 @@ import { useCallback as e, useEffect as t, useRef as n, useState as r, useSyncEx
2
2
  import { createPortal as a } from "react-dom";
3
3
  //#region src/copyStyles.ts
4
4
  function o(e, t, n = !0) {
5
- return c(e, t, n).stop;
5
+ return u(e, t, n).stop;
6
6
  }
7
- var s = [
7
+ var s = /* @__PURE__ */ new WeakMap();
8
+ function c(e, t) {
9
+ let n = s.get(e);
10
+ if (!n) {
11
+ let t = /* @__PURE__ */ new Set();
12
+ n = t, s.set(e, t);
13
+ let r = Object.getPrototypeOf(e);
14
+ e.insertRule = function(e, n) {
15
+ let i = r.insertRule.call(this, e, n);
16
+ for (let n of [...t]) n({
17
+ type: "insert",
18
+ rule: e,
19
+ index: i
20
+ });
21
+ return i;
22
+ }, e.deleteRule = function(e) {
23
+ r.deleteRule.call(this, e);
24
+ for (let n of [...t]) n({
25
+ type: "delete",
26
+ index: e
27
+ });
28
+ };
29
+ }
30
+ let r = n;
31
+ return r.add(t), () => {
32
+ if (r.delete(t), r.size > 0) return;
33
+ s.delete(e);
34
+ let n = e;
35
+ delete n.insertRule, delete n.deleteRule;
36
+ };
37
+ }
38
+ var l = [
8
39
  "rel",
9
40
  "media",
10
41
  "title",
@@ -12,46 +43,87 @@ var s = [
12
43
  "referrerpolicy",
13
44
  "integrity"
14
45
  ];
15
- function c(e, t, n = !0) {
16
- let r = /* @__PURE__ */ new Map(), i = (e) => {
46
+ function u(e, t, n = !0) {
47
+ let r = /* @__PURE__ */ new Map(), i = n && typeof MutationObserver < "u", a = (e) => {
17
48
  if (e.nodeType !== Node.ELEMENT_NODE) return !1;
18
49
  let t = e;
19
50
  return t.tagName === "STYLE" || t.tagName === "LINK" && (t.getAttribute("rel") ?? "").toLowerCase().includes("stylesheet");
20
- }, a = (e) => {
51
+ }, o = (e) => {
21
52
  let t = e.sheet;
22
53
  if (t) try {
23
54
  return Array.from(t.cssRules).map((e) => e.cssText).join("\n");
24
55
  } catch {}
25
56
  return e.textContent ?? "";
26
- }, o = (e) => {
57
+ }, s = /* @__PURE__ */ new Map(), u = (e, t) => {
58
+ let n = e.sheet, r = s.get(e);
59
+ if (r && r.sheet === n) {
60
+ r.count = n.cssRules.length;
61
+ return;
62
+ }
63
+ if (r?.stop(), s.delete(e), !i || !n) return;
64
+ let a = {
65
+ sheet: n,
66
+ count: n.cssRules.length,
67
+ stop: c(n, (r) => {
68
+ let i = t.sheet;
69
+ try {
70
+ if (i && (r.type === "insert" ? i.insertRule(r.rule, r.index) : i.deleteRule(r.index), i.cssRules.length === n.cssRules.length)) {
71
+ a.count = n.cssRules.length;
72
+ return;
73
+ }
74
+ } catch {}
75
+ g(e);
76
+ })
77
+ };
78
+ s.set(e, a);
79
+ }, d = (e) => {
80
+ s.get(e)?.stop(), s.delete(e);
81
+ }, f = /* @__PURE__ */ new Map(), p = (e, n) => {
82
+ let r = t.defaultView;
83
+ if (!r || typeof r.CSSStyleSheet != "function" || !("adoptedStyleSheets" in t) || /\balternate\b/i.test(e.rel)) return;
84
+ let i = null, a = () => {
85
+ if (!(n.sheet !== null || !e.sheet)) try {
86
+ let n = Array.from(e.sheet.cssRules).map((e) => e.cssText).join("\n"), a = new r.CSSStyleSheet({
87
+ baseURL: e.href,
88
+ media: e.media || void 0
89
+ });
90
+ a.replaceSync(n), t.adoptedStyleSheets = [...t.adoptedStyleSheets, a], i = a;
91
+ } catch {}
92
+ }, o = () => {
93
+ e.removeEventListener("load", a), n.removeEventListener("load", o), n.removeEventListener("error", o), f.delete(e);
94
+ let r = i;
95
+ r && (t.adoptedStyleSheets = t.adoptedStyleSheets.filter((e) => e !== r)), i = null;
96
+ };
97
+ n.addEventListener("load", o), n.addEventListener("error", o), f.set(e, o), e.sheet ? a() : e.addEventListener("load", a);
98
+ }, m = !0, h = (e) => {
27
99
  if (r.has(e)) return;
28
100
  let n;
29
101
  if (e.tagName === "LINK") {
30
102
  let r = t.createElement("link");
31
- for (let t of s) {
103
+ for (let t of l) {
32
104
  let n = e.getAttribute(t);
33
105
  n !== null && r.setAttribute(t, n);
34
106
  }
35
107
  r.href = e.href, n = r;
36
108
  } else {
37
109
  let r = t.createElement("style");
38
- r.textContent = a(e);
110
+ r.textContent = o(e);
39
111
  let i = e.getAttribute("media");
40
112
  i && r.setAttribute("media", i), n = r;
41
113
  }
42
- t.head.appendChild(n), r.set(e, n);
43
- }, c = (e) => {
114
+ t.head.appendChild(n), r.set(e, n), e.tagName === "STYLE" ? u(e, n) : !m && i && p(e, n);
115
+ }, g = (e) => {
44
116
  let t = r.get(e);
45
- t && (t.textContent = a(e));
46
- }, l = (e) => {
47
- for (let [t, n] of r) (e === t || e.nodeType === Node.ELEMENT_NODE && e.contains(t)) && (n.remove(), r.delete(t));
48
- }, u = ["class", "style"], d = () => {
117
+ t && (t.textContent = o(e), u(e, t));
118
+ }, _ = (e) => {
119
+ for (let [t, n] of r) (e === t || e.nodeType === Node.ELEMENT_NODE && e.contains(t)) && (n.remove(), r.delete(t), d(t), f.get(t)?.());
120
+ }, v = ["class", "style"], y = () => {
49
121
  let n = [[e.documentElement, t.documentElement], [e.body, t.body]];
50
122
  for (let [e, t] of n) if (!(!e || !t)) {
51
- for (let n of e.attributes) (u.includes(n.name) || n.name.startsWith("data-")) && t.setAttribute(n.name, n.value);
52
- for (let n of Array.from(t.attributes)) (u.includes(n.name) || n.name.startsWith("data-")) && !e.hasAttribute(n.name) && t.removeAttribute(n.name);
123
+ for (let n of e.attributes) (v.includes(n.name) || n.name.startsWith("data-")) && t.setAttribute(n.name, n.value);
124
+ for (let n of Array.from(t.attributes)) (v.includes(n.name) || n.name.startsWith("data-")) && !e.hasAttribute(n.name) && t.removeAttribute(n.name);
53
125
  }
54
- }, f = () => {
126
+ }, b = () => {
55
127
  let n = t.defaultView;
56
128
  if (!(!n?.CSSStyleSheet || !("adoptedStyleSheets" in e))) try {
57
129
  t.adoptedStyleSheets = e.adoptedStyleSheets.map((e) => {
@@ -60,42 +132,51 @@ function c(e, t, n = !0) {
60
132
  });
61
133
  } catch {}
62
134
  };
63
- for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) o(t);
64
- let p = Array.from(r.values()).filter((e) => e.tagName === "LINK");
65
- if (d(), f(), !n || typeof MutationObserver > "u") return {
135
+ for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) h(t);
136
+ m = !1;
137
+ let x = Array.from(r.values()).filter((e) => e.tagName === "LINK");
138
+ if (y(), b(), !i) return {
66
139
  stop: () => {},
67
- links: p
140
+ links: x
68
141
  };
69
- let m = new MutationObserver((e) => {
142
+ let S = new MutationObserver((e) => {
70
143
  for (let t of e) {
71
144
  if (t.type === "characterData") {
72
145
  let e = t.target.parentElement;
73
- e?.tagName === "STYLE" && c(e);
146
+ e?.tagName === "STYLE" && g(e);
74
147
  continue;
75
148
  }
76
- i(t.target) && t.target.tagName === "STYLE" && c(t.target);
77
- for (let e of t.addedNodes) if (i(e)) o(e);
78
- else if (e.nodeType === Node.ELEMENT_NODE) for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) o(t);
79
- for (let e of t.removedNodes) l(e);
149
+ a(t.target) && t.target.tagName === "STYLE" && g(t.target);
150
+ for (let e of t.addedNodes) if (a(e)) h(e);
151
+ else if (e.nodeType === Node.ELEMENT_NODE) for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) h(t);
152
+ for (let e of t.removedNodes) _(e);
80
153
  }
81
154
  });
82
- m.observe(e.head, {
155
+ S.observe(e.head, {
83
156
  childList: !0,
84
157
  subtree: !0,
85
158
  characterData: !0
86
159
  });
87
- let h = new MutationObserver(() => d());
88
- return h.observe(e.documentElement, { attributes: !0 }), e.body && h.observe(e.body, { attributes: !0 }), {
160
+ let C = new MutationObserver(() => y());
161
+ C.observe(e.documentElement, { attributes: !0 }), e.body && C.observe(e.body, { attributes: !0 });
162
+ let w = t.defaultView, T, E = () => {
163
+ for (let [e, t] of s) e.sheet === t.sheet && t.sheet.cssRules.length !== t.count && g(e);
164
+ T = w?.requestAnimationFrame(E);
165
+ };
166
+ return w && typeof w.requestAnimationFrame == "function" && (T = w.requestAnimationFrame(E)), {
89
167
  stop: () => {
90
- m.disconnect(), h.disconnect();
168
+ S.disconnect(), C.disconnect(), T !== void 0 && w?.cancelAnimationFrame(T);
169
+ for (let e of s.values()) e.stop();
170
+ for (let e of [...f.values()]) e();
171
+ s.clear();
91
172
  },
92
- links: p
173
+ links: x
93
174
  };
94
175
  }
95
176
  //#endregion
96
177
  //#region src/whenStylesheetsLoad.ts
97
- function l(e, t, n) {
98
- let r = e.filter((e) => e.sheet === null && u(e));
178
+ function d(e, t, n) {
179
+ let r = e.filter((e) => e.sheet === null && f(e));
99
180
  if (r.length === 0) return n(), () => {};
100
181
  let i = r.length, a = () => {
101
182
  i--, i === 0 && s();
@@ -109,23 +190,23 @@ function l(e, t, n) {
109
190
  let c = window.setTimeout(s, t);
110
191
  return o;
111
192
  }
112
- function u(e) {
193
+ function f(e) {
113
194
  if (/\balternate\b/i.test(e.rel)) return !1;
114
195
  let t = e.ownerDocument.defaultView;
115
196
  return !e.media || !t?.matchMedia || t.matchMedia(e.media).matches;
116
197
  }
117
198
  //#endregion
118
199
  //#region src/usePopupWindow.ts
119
- var d = {
200
+ var p = {
120
201
  popupWindow: null,
121
202
  container: null,
122
203
  blocked: !1
123
- }, f = class {
124
- state = d;
204
+ }, m = class {
205
+ state = p;
125
206
  listeners = /* @__PURE__ */ new Set();
126
207
  subscribe = (e) => (this.listeners.add(e), () => this.listeners.delete(e));
127
208
  getSnapshot = () => this.state;
128
- getServerSnapshot = () => d;
209
+ getServerSnapshot = () => p;
129
210
  setState(e) {
130
211
  this.state = {
131
212
  ...this.state,
@@ -133,44 +214,47 @@ var d = {
133
214
  };
134
215
  for (let e of this.listeners) e();
135
216
  }
136
- }, p = {
217
+ }, h = {
137
218
  popup: !0,
138
219
  width: 640,
139
220
  height: 480
140
221
  };
141
- function m(e) {
222
+ function g(e) {
142
223
  let t = {
143
- ...p,
224
+ ...h,
144
225
  ...e.features
145
226
  };
146
227
  return e.center !== !1 && t.left === void 0 && t.top === void 0 && typeof t.width == "number" && typeof t.height == "number" && (t.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - t.width) / 2)), t.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - t.height) / 2))), Object.entries(t).filter(([, e]) => e !== void 0 && e !== !1).map(([e, t]) => t === !0 ? `${e}=yes` : `${e}=${t}`).join(",");
147
228
  }
148
- var h = "about:blank", g = 20, _ = 3e3;
149
- function v(e) {
229
+ var _ = "about:blank", v = 20, y = 3e3;
230
+ function b(e) {
231
+ e.open(), e.write("<!DOCTYPE html><html><head></head><body></body></html>"), e.close();
232
+ }
233
+ function x(e) {
150
234
  let t = e.document;
151
- return t.URL === h || t.readyState === "loading" ? null : t;
235
+ return t.URL === _ || t.readyState === "loading" ? null : t;
152
236
  }
153
- function y(e) {
237
+ function S(e) {
154
238
  function t({ children: t }) {
155
239
  let { container: n } = i(e.subscribe, e.getSnapshot, e.getServerSnapshot);
156
240
  return n ? a(t, n) : null;
157
241
  }
158
242
  return t.displayName = "PopupWindow.Popup", t;
159
243
  }
160
- function b(a = {}) {
161
- let [o] = r(() => new f()), [s] = r(() => y(o)), u = n(a);
162
- u.current = a;
163
- let d = n(null), p = i(o.subscribe, o.getSnapshot, o.getServerSnapshot), b = e((e) => {
244
+ function C(a = {}) {
245
+ let [o] = r(() => new m()), [s] = r(() => S(o)), c = n(a);
246
+ c.current = a;
247
+ let l = n(null), f = i(o.subscribe, o.getSnapshot, o.getServerSnapshot), p = e((e) => {
164
248
  let { popupWindow: t } = o.getSnapshot();
165
- d.current?.(), d.current = null, t && (!e && !t.closed && t.close(), o.setState({
249
+ l.current?.(), l.current = null, t && (!e && !t.closed && t.close(), o.setState({
166
250
  popupWindow: null,
167
251
  container: null
168
- }), u.current.onClose?.());
169
- }, [o]), x = e(() => {
252
+ }), c.current.onClose?.());
253
+ }, [o]), h = e(() => {
170
254
  if (typeof window > "u") return null;
171
255
  let e = o.getSnapshot();
172
256
  if (e.popupWindow && !e.popupWindow.closed) return e.popupWindow.focus(), e.popupWindow;
173
- let t = u.current, n = t.url ?? h, r = window.open(n, t.name ?? "_blank", m(t));
257
+ let t = c.current, n = t.url ?? _, r = window.open(n, t.name ?? "_blank", g(t));
174
258
  if (!r) return o.setState({ blocked: !0 }), t.onBlocked?.(), null;
175
259
  let i;
176
260
  try {
@@ -178,27 +262,27 @@ function b(a = {}) {
178
262
  } catch {
179
263
  return r.close(), o.setState({ blocked: !0 }), t.onBlocked?.(), null;
180
264
  }
181
- let a = () => b(!0), s = () => {
265
+ let a = () => p(!0), s = () => {
182
266
  setTimeout(() => {
183
267
  r.closed && a();
184
268
  }, 0);
185
269
  }, f = window.setInterval(() => {
186
270
  r.closed && a();
187
- }, 250), p = () => r.close();
188
- window.addEventListener("pagehide", p);
189
- let y, x = !1, S, C;
190
- d.current = () => {
191
- window.clearInterval(f), window.clearInterval(y), window.removeEventListener("pagehide", p), x && r.removeEventListener("pagehide", s), S?.(), C?.();
271
+ }, 250), m = () => r.close();
272
+ window.addEventListener("pagehide", m);
273
+ let h, S = !1, C, w;
274
+ l.current = () => {
275
+ window.clearInterval(f), window.clearInterval(h), window.removeEventListener("pagehide", m), S && r.removeEventListener("pagehide", s), C?.(), w?.();
192
276
  };
193
- let w = (e) => {
194
- x = !0, r.addEventListener("pagehide", s), e.title = t.title ?? document.title;
277
+ let T = (e) => {
278
+ S = !0, r.addEventListener("pagehide", s), e.title = t.title ?? document.title;
195
279
  let n = [];
196
280
  if (t.copyStyles !== !1) {
197
- let t = c(document, e);
198
- S = t.stop, n = t.links;
281
+ let t = u(document, e);
282
+ C = t.stop, n = t.links;
199
283
  }
200
284
  let i = e.createElement("div");
201
- i.setAttribute("data-popup-window-root", ""), e.body.appendChild(i), C = l(n, _, () => {
285
+ i.setAttribute("data-popup-window-root", ""), e.body.appendChild(i), w = d(n, y, () => {
202
286
  o.setState({ container: i }), t.onOpen?.(r);
203
287
  });
204
288
  };
@@ -206,42 +290,42 @@ function b(a = {}) {
206
290
  popupWindow: r,
207
291
  container: null,
208
292
  blocked: !1
209
- }), n === h ? w(i) : y = window.setInterval(() => {
293
+ }), n === _ ? (b(i), T(i)) : h = window.setInterval(() => {
210
294
  if (r.closed) {
211
295
  a();
212
296
  return;
213
297
  }
214
298
  let e;
215
299
  try {
216
- e = v(r);
300
+ e = x(r);
217
301
  } catch {
218
- b(!1), o.setState({ blocked: !0 }), t.onBlocked?.();
302
+ p(!1), o.setState({ blocked: !0 }), t.onBlocked?.();
219
303
  return;
220
304
  }
221
- e && (window.clearInterval(y), w(e));
222
- }, g), r;
223
- }, [o, b]), S = e(() => b(!1), [b]), C = e(() => {
224
- o.getSnapshot().popupWindow ? b(!1) : x();
305
+ e && (window.clearInterval(h), T(e));
306
+ }, v), r;
307
+ }, [o, p]), C = e(() => p(!1), [p]), w = e(() => {
308
+ o.getSnapshot().popupWindow ? p(!1) : h();
225
309
  }, [
226
310
  o,
227
- b,
228
- x
229
- ]), w = e(() => {
311
+ p,
312
+ h
313
+ ]), T = e(() => {
230
314
  let { popupWindow: e } = o.getSnapshot();
231
315
  e && !e.closed && e.focus();
232
316
  }, [o]);
233
- return t(() => () => b(!1), [b]), {
234
- open: x,
235
- close: S,
236
- toggle: C,
237
- focus: w,
238
- isOpen: p.popupWindow !== null,
239
- isBlocked: p.blocked,
240
- popupWindow: p.popupWindow,
317
+ return t(() => () => p(!1), [p]), {
318
+ open: h,
319
+ close: C,
320
+ toggle: w,
321
+ focus: T,
322
+ isOpen: f.popupWindow !== null,
323
+ isBlocked: f.blocked,
324
+ popupWindow: f.popupWindow,
241
325
  Popup: s
242
326
  };
243
327
  }
244
328
  //#endregion
245
- export { o as copyStyles, b as usePopupWindow };
329
+ export { o as copyStyles, C as usePopupWindow };
246
330
 
247
331
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/copyStyles.ts","../src/whenStylesheetsLoad.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\n * in sync while the popup is open:\n *\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\n * possible, so rules injected via `insertRule` (CSS-in-JS \"speedy\" mode)\n * are included.\n * - Additions/removals/edits of style nodes in the source `<head>` are\n * observed (covers Vite HMR, lazily loaded chunk CSS, styled-components).\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\n *\n * Returns a function that stops observing.\n */\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\n return mirrorStyles(source, target, watch).stop\n}\n\n/** Attributes copied from a source `<link>` onto its mirror. */\nconst LINK_ATTRS = ['rel', 'media', 'title', 'crossorigin', 'referrerpolicy', 'integrity']\n\nexport interface StyleMirror {\n /** Stops observing the source document. */\n stop: () => void\n /**\n * The `<link>` elements created in `target` by the initial copy. The target\n * fetches each of them asynchronously.\n */\n links: HTMLLinkElement[]\n}\n\n/** {@link copyStyles}, plus the `<link>` elements the initial copy created. */\nexport function mirrorStyles(source: Document, target: Document, watch = true): StyleMirror {\n const mirrors = new Map<Element, Element>()\n\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\n if (node.nodeType !== Node.ELEMENT_NODE) return false\n const el = node as Element\n return (\n el.tagName === 'STYLE' ||\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\n )\n }\n\n const serializeStyle = (el: HTMLStyleElement): string => {\n const sheet = el.sheet\n if (sheet) {\n try {\n return Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n } catch {\n // Inaccessible cssRules — fall back to the raw text below.\n }\n }\n return el.textContent ?? ''\n }\n\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\n if (mirrors.has(el)) return\n let clone: Element\n if (el.tagName === 'LINK') {\n const link = target.createElement('link')\n // `rel` is copied as is, so an alternate stylesheet stays disabled.\n // `crossorigin` and `referrerpolicy` make the popup send the same request\n // as the opener, so it can reuse the cached response.\n for (const name of LINK_ATTRS) {\n const value = el.getAttribute(name)\n if (value !== null) link.setAttribute(name, value)\n }\n // .href resolves to an absolute URL, so relative hrefs keep working\n // from the popup's `about:blank` document.\n link.href = (el as HTMLLinkElement).href\n clone = link\n } else {\n const style = target.createElement('style')\n style.textContent = serializeStyle(el as HTMLStyleElement)\n const media = el.getAttribute('media')\n if (media) style.setAttribute('media', media)\n clone = style\n }\n target.head.appendChild(clone)\n mirrors.set(el, clone)\n }\n\n const refresh = (el: HTMLStyleElement): void => {\n const clone = mirrors.get(el)\n if (clone) clone.textContent = serializeStyle(el)\n }\n\n const unmirrorSubtree = (removed: Node): void => {\n for (const [src, clone] of mirrors) {\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\n clone.remove()\n mirrors.delete(src)\n }\n }\n }\n\n const ROOT_ATTRS = ['class', 'style']\n const syncRootAttrs = (): void => {\n const pairs: Array<[Element | null, Element | null]> = [\n [source.documentElement, target.documentElement],\n [source.body, target.body],\n ]\n for (const [from, to] of pairs) {\n if (!from || !to) continue\n for (const attr of from.attributes) {\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\n to.setAttribute(attr.name, attr.value)\n }\n }\n for (const attr of Array.from(to.attributes)) {\n if (\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\n !from.hasAttribute(attr.name)\n ) {\n to.removeAttribute(attr.name)\n }\n }\n }\n }\n\n const syncAdoptedSheets = (): void => {\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\n try {\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\n const copy = new targetWindow.CSSStyleSheet!()\n copy.replaceSync(\n Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n'),\n )\n return copy\n })\n } catch {\n // Best effort — older browsers or cross-origin rules.\n }\n }\n\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n const links = Array.from(mirrors.values()).filter(\n (clone): clone is HTMLLinkElement => clone.tagName === 'LINK',\n )\n syncRootAttrs()\n syncAdoptedSheets()\n\n if (!watch || typeof MutationObserver === 'undefined') {\n return { stop: () => {}, links }\n }\n\n const headObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'characterData') {\n const parent = record.target.parentElement\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\n continue\n }\n // childList\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\n // textContent assignment replaces the text node → childList on <style>\n refresh(record.target as HTMLStyleElement)\n }\n for (const added of record.addedNodes) {\n if (isStyleNode(added)) {\n mirror(added)\n } else if (added.nodeType === Node.ELEMENT_NODE) {\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n }\n }\n for (const removed of record.removedNodes) {\n unmirrorSubtree(removed)\n }\n }\n })\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\n\n const rootObserver = new MutationObserver(() => syncRootAttrs())\n rootObserver.observe(source.documentElement, { attributes: true })\n if (source.body) rootObserver.observe(source.body, { attributes: true })\n\n return {\n stop: () => {\n headObserver.disconnect()\n rootObserver.disconnect()\n },\n links,\n }\n}\n","/**\n * Calls `callback` once every stylesheet in `links` that applies has loaded\n * or failed, or after `timeoutMs`, whichever comes first.\n *\n * When none of them is still loading, `callback` runs synchronously, before\n * this function returns. Links that do not apply are not waited for: an\n * alternate stylesheet, or a `media` query that does not match, such as\n * `print`. Browsers fetch those at the lowest priority.\n *\n * Returns a function that stops waiting without calling `callback`.\n */\nexport function whenStylesheetsLoad(\n links: readonly HTMLLinkElement[],\n timeoutMs: number,\n callback: () => void,\n): () => void {\n const pending = links.filter((link) => link.sheet === null && applies(link))\n if (pending.length === 0) {\n callback()\n return () => {}\n }\n\n let remaining = pending.length\n const settle = () => {\n remaining--\n if (remaining === 0) finish()\n }\n const stop = () => {\n window.clearTimeout(timeout)\n for (const link of pending) {\n link.removeEventListener('load', settle)\n link.removeEventListener('error', settle)\n }\n }\n const finish = () => {\n stop()\n callback()\n }\n\n for (const link of pending) {\n link.addEventListener('load', settle)\n // A broken stylesheet must not keep the popup blank.\n link.addEventListener('error', settle)\n }\n const timeout = window.setTimeout(finish, timeoutMs)\n return stop\n}\n\nfunction applies(link: HTMLLinkElement): boolean {\n if (/\\balternate\\b/i.test(link.rel)) return false\n const view = link.ownerDocument.defaultView\n return !link.media || !view?.matchMedia || view.matchMedia(link.media).matches\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { FC } from 'react'\nimport { createPortal } from 'react-dom'\nimport { mirrorStyles } from './copyStyles'\nimport type {\n PopupProps,\n PopupWindowApi,\n PopupWindowFeatures,\n UsePopupWindowOptions,\n} from './types'\nimport { whenStylesheetsLoad } from './whenStylesheetsLoad'\n\ninterface PopupState {\n popupWindow: Window | null\n container: HTMLElement | null\n blocked: boolean\n}\n\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\n\nclass PopupStore {\n state: PopupState = INITIAL_STATE\n private listeners = new Set<() => void>()\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getSnapshot = (): PopupState => this.state\n\n getServerSnapshot = (): PopupState => INITIAL_STATE\n\n setState(partial: Partial<PopupState>): void {\n this.state = { ...this.state, ...partial }\n for (const listener of this.listeners) listener()\n }\n}\n\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\n\nfunction buildFeatures(options: UsePopupWindowOptions): string {\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\n if (\n options.center !== false &&\n merged.left === undefined &&\n merged.top === undefined &&\n typeof merged.width === 'number' &&\n typeof merged.height === 'number'\n ) {\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\n }\n return Object.entries(merged)\n .filter(([, value]) => value !== undefined && value !== false)\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\n .join(',')\n}\n\nconst ABOUT_BLANK = 'about:blank'\n/** Interval for checking whether the page given by `url` has loaded. */\nconst LOAD_POLL_MS = 20\n/** Longest time `Popup` waits for copied stylesheets before rendering anyway. */\nconst STYLES_TIMEOUT_MS = 3000\n\n/**\n * The popup's document once the page given by `url` has replaced the initial\n * `about:blank` document and finished parsing, otherwise `null`. Throws when\n * the document is not scriptable.\n */\nfunction loadedDocument(popupWindow: Window): Document | null {\n const doc = popupWindow.document\n if (doc.URL === ABOUT_BLANK || doc.readyState === 'loading') return null\n return doc\n}\n\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\n function Popup({ children }: PopupProps) {\n const { container } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n )\n return container ? createPortal(children, container) : null\n }\n Popup.displayName = 'PopupWindow.Popup'\n return Popup\n}\n\n/**\n * Open part of your React tree in a separate browser window.\n *\n * The popup content is rendered with a portal into the popup's document, so\n * it stays part of your component tree: state, context and event handlers\n * all keep working across windows.\n *\n * ```tsx\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\n *\n * return (\n * <>\n * <button onClick={open}>Open panel</button>\n * <Popup>\n * <MyPanel />\n * </Popup>\n * </>\n * )\n * ```\n */\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\n const [store] = useState(() => new PopupStore())\n const [Popup] = useState(() => createPopupComponent(store))\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const cleanupRef = useRef<(() => void) | null>(null)\n\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\n\n // `userClosed` is true when the window was closed outside our control\n // (user hit the close button, opener unloaded) — then we must not touch it.\n const closePopup = useCallback(\n (userClosed: boolean) => {\n const { popupWindow } = store.getSnapshot()\n cleanupRef.current?.()\n cleanupRef.current = null\n if (!popupWindow) return\n if (!userClosed && !popupWindow.closed) popupWindow.close()\n store.setState({ popupWindow: null, container: null })\n optionsRef.current.onClose?.()\n },\n [store],\n )\n\n const open = useCallback((): Window | null => {\n if (typeof window === 'undefined') return null\n const current = store.getSnapshot()\n if (current.popupWindow && !current.popupWindow.closed) {\n current.popupWindow.focus()\n return current.popupWindow\n }\n\n const opts = optionsRef.current\n const url = opts.url ?? ABOUT_BLANK\n const popupWindow = window.open(url, opts.name ?? '_blank', buildFeatures(opts))\n if (!popupWindow) {\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n // Sandboxed embedders (VS Code's built-in browser, CodeSandbox/StackBlitz\n // previews, iframes sandboxed without popup-escape) open the popup with an\n // opaque origin, so reading its document throws a SecurityError. Treat\n // that as blocked instead of stranding a blank window the portal can\n // never reach.\n let initialDoc: Document\n try {\n initialDoc = popupWindow.document\n } catch {\n popupWindow.close()\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n const handleExternalClose = () => closePopup(true)\n const onPopupPagehide = () => {\n // pagehide also fires on navigation; only treat it as a close when the\n // window really is gone a tick later.\n setTimeout(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 0)\n }\n\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\n const closePoll = window.setInterval(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 250)\n\n const onOpenerPagehide = () => popupWindow.close()\n window.addEventListener('pagehide', onOpenerPagehide)\n\n let loadPoll: number | undefined\n let prepared = false\n let stopStyleSync: (() => void) | undefined\n let stopStylesWait: (() => void) | undefined\n\n cleanupRef.current = () => {\n window.clearInterval(closePoll)\n window.clearInterval(loadPoll)\n window.removeEventListener('pagehide', onOpenerPagehide)\n if (prepared) popupWindow.removeEventListener('pagehide', onPopupPagehide)\n stopStyleSync?.()\n stopStylesWait?.()\n }\n\n // Title, styles and the portal container go into the document that\n // stays: `about:blank` itself, or the page `url` loads in its place.\n const prepare = (doc: Document) => {\n prepared = true\n popupWindow.addEventListener('pagehide', onPopupPagehide)\n doc.title = opts.title ?? document.title\n let links: HTMLLinkElement[] = []\n if (opts.copyStyles !== false) {\n const styles = mirrorStyles(document, doc)\n stopStyleSync = styles.stop\n links = styles.links\n }\n const container = doc.createElement('div')\n container.setAttribute('data-popup-window-root', '')\n doc.body.appendChild(container)\n // The popup fetches copied <link> stylesheets asynchronously. Render\n // only once they have loaded, so the first paint is not unstyled.\n stopStylesWait = whenStylesheetsLoad(links, STYLES_TIMEOUT_MS, () => {\n store.setState({ container })\n opts.onOpen?.(popupWindow)\n })\n }\n\n store.setState({ popupWindow, container: null, blocked: false })\n\n if (url === ABOUT_BLANK) {\n prepare(initialDoc)\n } else {\n // The window starts on an initial about:blank document and then\n // navigates to `url`. Wait until that page has replaced it and parsed.\n loadPoll = window.setInterval(() => {\n if (popupWindow.closed) {\n handleExternalClose()\n return\n }\n let doc: Document | null\n try {\n doc = loadedDocument(popupWindow)\n } catch {\n // The page went somewhere the opener cannot script, such as a\n // cross-origin redirect: nothing can ever render there.\n closePopup(false)\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return\n }\n if (!doc) return\n window.clearInterval(loadPoll)\n prepare(doc)\n }, LOAD_POLL_MS)\n }\n\n return popupWindow\n }, [store, closePopup])\n\n const close = useCallback(() => closePopup(false), [closePopup])\n\n const toggle = useCallback(() => {\n if (store.getSnapshot().popupWindow) {\n closePopup(false)\n } else {\n open()\n }\n }, [store, closePopup, open])\n\n const focus = useCallback(() => {\n const { popupWindow } = store.getSnapshot()\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\n }, [store])\n\n // Close the popup when the owning component unmounts — its portal content\n // would unmount anyway, leaving an empty window behind.\n useEffect(() => {\n return () => closePopup(false)\n }, [closePopup])\n\n return {\n open,\n close,\n toggle,\n focus,\n isOpen: state.popupWindow !== null,\n isBlocked: state.blocked,\n popupWindow: state.popupWindow,\n Popup,\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/copyStyles.ts","../src/whenStylesheetsLoad.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\n * in sync while the popup is open:\n *\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\n * possible, so rules injected via `insertRule` are included.\n * - Rules added to or removed from a `<style>` sheet later with\n * `insertRule`/`deleteRule` are applied to its mirror as they happen. This\n * is how CSS-in-JS libraries (Emotion, styled-components) add rules in\n * production builds, and it changes no DOM.\n * - Additions/removals/edits of style nodes in the source `<head>` are\n * observed (covers Vite HMR, lazily loaded chunk CSS, CSS-in-JS in\n * development builds).\n * - A `<link>` added while watching is loaded by the target again. Until\n * that copy has loaded, the rules the source already loaded are applied\n * to the target through a constructed stylesheet.\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\n *\n * Returns a function that stops observing.\n */\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\n return mirrorStyles(source, target, watch).stop\n}\n\ntype RuleChange = { type: 'insert'; rule: string; index: number } | { type: 'delete'; index: number }\ntype RuleListener = (change: RuleChange) => void\n\nconst ruleListeners = new WeakMap<CSSStyleSheet, Set<RuleListener>>()\n\n/**\n * Calls `listener` after each `insertRule`/`deleteRule` call on `sheet`.\n * Wraps the two methods on this sheet object only, not on the prototype, and\n * restores them once the sheet's last listener is removed. Returns a function\n * that removes the listener.\n */\nfunction onRuleChange(sheet: CSSStyleSheet, listener: RuleListener): () => void {\n let listeners = ruleListeners.get(sheet)\n if (!listeners) {\n const all = new Set<RuleListener>()\n listeners = all\n ruleListeners.set(sheet, all)\n const proto = Object.getPrototypeOf(sheet) as CSSStyleSheet\n sheet.insertRule = function (this: CSSStyleSheet, rule: string, index?: number): number {\n const inserted = proto.insertRule.call(this, rule, index)\n for (const notify of [...all]) notify({ type: 'insert', rule, index: inserted })\n return inserted\n }\n sheet.deleteRule = function (this: CSSStyleSheet, index: number): void {\n proto.deleteRule.call(this, index)\n for (const notify of [...all]) notify({ type: 'delete', index })\n }\n }\n const own = listeners\n own.add(listener)\n return () => {\n own.delete(listener)\n if (own.size > 0) return\n ruleListeners.delete(sheet)\n const wrapped = sheet as { insertRule?: unknown; deleteRule?: unknown }\n delete wrapped.insertRule\n delete wrapped.deleteRule\n }\n}\n\n/** Attributes copied from a source `<link>` onto its mirror. */\nconst LINK_ATTRS = ['rel', 'media', 'title', 'crossorigin', 'referrerpolicy', 'integrity']\n\nexport interface StyleMirror {\n /** Stops observing the source document. */\n stop: () => void\n /**\n * The `<link>` elements created in `target` by the initial copy. The target\n * fetches each of them asynchronously.\n */\n links: HTMLLinkElement[]\n}\n\n/** {@link copyStyles}, plus the `<link>` elements the initial copy created. */\nexport function mirrorStyles(source: Document, target: Document, watch = true): StyleMirror {\n const mirrors = new Map<Element, Element>()\n const observing = watch && typeof MutationObserver !== 'undefined'\n\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\n if (node.nodeType !== Node.ELEMENT_NODE) return false\n const el = node as Element\n return (\n el.tagName === 'STYLE' ||\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\n )\n }\n\n const serializeStyle = (el: HTMLStyleElement): string => {\n const sheet = el.sheet\n if (sheet) {\n try {\n return Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n } catch {\n // Inaccessible cssRules — fall back to the raw text below.\n }\n }\n return el.textContent ?? ''\n }\n\n interface RuleSync {\n sheet: CSSStyleSheet\n /** Rule count of `sheet` that the mirror reflects. */\n count: number\n stop: () => void\n }\n const ruleSyncs = new Map<HTMLStyleElement, RuleSync>()\n\n // CSS-in-JS libraries in production builds add rules with insertRule, which\n // changes no DOM. Apply each change to the mirror as it happens, so content\n // that renders in the same task, such as the popup's first render, is\n // already styled.\n const syncRules = (el: HTMLStyleElement, clone: HTMLStyleElement): void => {\n const sheet = el.sheet\n const current = ruleSyncs.get(el)\n if (current && current.sheet === sheet) {\n current.count = sheet.cssRules.length\n return\n }\n current?.stop()\n ruleSyncs.delete(el)\n if (!observing || !sheet) return\n const sync: RuleSync = {\n sheet,\n count: sheet.cssRules.length,\n stop: onRuleChange(sheet, (change) => {\n const copy = clone.sheet\n try {\n if (copy) {\n if (change.type === 'insert') copy.insertRule(change.rule, change.index)\n else copy.deleteRule(change.index)\n if (copy.cssRules.length === sheet.cssRules.length) {\n sync.count = sheet.cssRules.length\n return\n }\n }\n } catch {\n // Out of step with the source - copy the whole sheet again below.\n }\n refresh(el)\n }),\n }\n ruleSyncs.set(el, sync)\n }\n\n const unsyncRules = (el: Element): void => {\n ruleSyncs.get(el as HTMLStyleElement)?.stop()\n ruleSyncs.delete(el as HTMLStyleElement)\n }\n\n // A <link> added while watching, such as lazily loaded chunk CSS: the\n // target loads its copy separately, and content that waits only for the\n // source's copy, as bundlers do, can render before the target's has\n // loaded. Until it has, apply the rules the source already parsed through a\n // constructed sheet. `baseURL` keeps relative url()s resolving against the\n // stylesheet. Adopted sheets come last in the cascade, which can change\n // precedence for that short time. Unreadable cross-origin sheets are left\n // to load normally.\n const unbridgeLinks = new Map<Element, () => void>()\n const bridgeLink = (el: HTMLLinkElement, clone: HTMLLinkElement): void => {\n const view = target.defaultView as (Window & typeof globalThis) | null\n if (!view || typeof view.CSSStyleSheet !== 'function' || !('adoptedStyleSheets' in target)) {\n return\n }\n if (/\\balternate\\b/i.test(el.rel)) return\n let bridge: CSSStyleSheet | null = null\n const add = (): void => {\n if (clone.sheet !== null || !el.sheet) return\n try {\n const text = Array.from(el.sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n const sheet = new view.CSSStyleSheet({ baseURL: el.href, media: el.media || undefined })\n sheet.replaceSync(text)\n target.adoptedStyleSheets = [...target.adoptedStyleSheets, sheet]\n bridge = sheet\n } catch {\n // Cross-origin rules or no constructable stylesheets - wait for the copy.\n }\n }\n const remove = (): void => {\n el.removeEventListener('load', add)\n clone.removeEventListener('load', remove)\n clone.removeEventListener('error', remove)\n unbridgeLinks.delete(el)\n const sheet = bridge\n if (sheet) target.adoptedStyleSheets = target.adoptedStyleSheets.filter((s) => s !== sheet)\n bridge = null\n }\n clone.addEventListener('load', remove)\n clone.addEventListener('error', remove)\n unbridgeLinks.set(el, remove)\n if (el.sheet) add()\n else el.addEventListener('load', add)\n }\n\n // True during the initial copy. `usePopupWindow` waits for those links\n // itself before rendering.\n let copying = true\n\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\n if (mirrors.has(el)) return\n let clone: Element\n if (el.tagName === 'LINK') {\n const link = target.createElement('link')\n // `rel` is copied as is, so an alternate stylesheet stays disabled.\n // `crossorigin` and `referrerpolicy` make the popup send the same request\n // as the opener, so it can reuse the cached response.\n for (const name of LINK_ATTRS) {\n const value = el.getAttribute(name)\n if (value !== null) link.setAttribute(name, value)\n }\n // .href resolves to an absolute URL, so relative hrefs keep working\n // from the popup's `about:blank` document.\n link.href = (el as HTMLLinkElement).href\n clone = link\n } else {\n const style = target.createElement('style')\n style.textContent = serializeStyle(el as HTMLStyleElement)\n const media = el.getAttribute('media')\n if (media) style.setAttribute('media', media)\n clone = style\n }\n target.head.appendChild(clone)\n mirrors.set(el, clone)\n if (el.tagName === 'STYLE') syncRules(el as HTMLStyleElement, clone as HTMLStyleElement)\n else if (!copying && observing) bridgeLink(el as HTMLLinkElement, clone as HTMLLinkElement)\n }\n\n const refresh = (el: HTMLStyleElement): void => {\n const clone = mirrors.get(el)\n if (!clone) return\n clone.textContent = serializeStyle(el)\n // New text gives the source a new sheet object.\n syncRules(el, clone as HTMLStyleElement)\n }\n\n const unmirrorSubtree = (removed: Node): void => {\n for (const [src, clone] of mirrors) {\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\n clone.remove()\n mirrors.delete(src)\n unsyncRules(src)\n unbridgeLinks.get(src)?.()\n }\n }\n }\n\n const ROOT_ATTRS = ['class', 'style']\n const syncRootAttrs = (): void => {\n const pairs: Array<[Element | null, Element | null]> = [\n [source.documentElement, target.documentElement],\n [source.body, target.body],\n ]\n for (const [from, to] of pairs) {\n if (!from || !to) continue\n for (const attr of from.attributes) {\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\n to.setAttribute(attr.name, attr.value)\n }\n }\n for (const attr of Array.from(to.attributes)) {\n if (\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\n !from.hasAttribute(attr.name)\n ) {\n to.removeAttribute(attr.name)\n }\n }\n }\n }\n\n const syncAdoptedSheets = (): void => {\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\n try {\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\n const copy = new targetWindow.CSSStyleSheet!()\n copy.replaceSync(\n Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n'),\n )\n return copy\n })\n } catch {\n // Best effort — older browsers or cross-origin rules.\n }\n }\n\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n copying = false\n const links = Array.from(mirrors.values()).filter(\n (clone): clone is HTMLLinkElement => clone.tagName === 'LINK',\n )\n syncRootAttrs()\n syncAdoptedSheets()\n\n if (!observing) {\n return { stop: () => {}, links }\n }\n\n const headObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'characterData') {\n const parent = record.target.parentElement\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\n continue\n }\n // childList\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\n // textContent assignment replaces the text node → childList on <style>\n refresh(record.target as HTMLStyleElement)\n }\n for (const added of record.addedNodes) {\n if (isStyleNode(added)) {\n mirror(added)\n } else if (added.nodeType === Node.ELEMENT_NODE) {\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n }\n }\n for (const removed of record.removedNodes) {\n unmirrorSubtree(removed)\n }\n }\n })\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\n\n const rootObserver = new MutationObserver(() => syncRootAttrs())\n rootObserver.observe(source.documentElement, { attributes: true })\n if (source.body) rootObserver.observe(source.body, { attributes: true })\n\n // Fallback for rule changes that bypass the sheet's own methods, such as\n // CSSStyleSheet.prototype.insertRule.call(sheet, ...): once per frame of the\n // target window, copy again any sheet whose rule count changed.\n const view = target.defaultView\n let frame: number | undefined\n const checkRuleCounts = (): void => {\n for (const [el, sync] of ruleSyncs) {\n if (el.sheet === sync.sheet && sync.sheet.cssRules.length !== sync.count) refresh(el)\n }\n frame = view?.requestAnimationFrame(checkRuleCounts)\n }\n if (view && typeof view.requestAnimationFrame === 'function') {\n frame = view.requestAnimationFrame(checkRuleCounts)\n }\n\n return {\n stop: () => {\n headObserver.disconnect()\n rootObserver.disconnect()\n if (frame !== undefined) view?.cancelAnimationFrame(frame)\n for (const sync of ruleSyncs.values()) sync.stop()\n for (const remove of [...unbridgeLinks.values()]) remove()\n ruleSyncs.clear()\n },\n links,\n }\n}\n","/**\n * Calls `callback` once every stylesheet in `links` that applies has loaded\n * or failed, or after `timeoutMs`, whichever comes first.\n *\n * When none of them is still loading, `callback` runs synchronously, before\n * this function returns. Links that do not apply are not waited for: an\n * alternate stylesheet, or a `media` query that does not match, such as\n * `print`. Browsers fetch those at the lowest priority.\n *\n * Returns a function that stops waiting without calling `callback`.\n */\nexport function whenStylesheetsLoad(\n links: readonly HTMLLinkElement[],\n timeoutMs: number,\n callback: () => void,\n): () => void {\n const pending = links.filter((link) => link.sheet === null && applies(link))\n if (pending.length === 0) {\n callback()\n return () => {}\n }\n\n let remaining = pending.length\n const settle = () => {\n remaining--\n if (remaining === 0) finish()\n }\n const stop = () => {\n window.clearTimeout(timeout)\n for (const link of pending) {\n link.removeEventListener('load', settle)\n link.removeEventListener('error', settle)\n }\n }\n const finish = () => {\n stop()\n callback()\n }\n\n for (const link of pending) {\n link.addEventListener('load', settle)\n // A broken stylesheet must not keep the popup blank.\n link.addEventListener('error', settle)\n }\n const timeout = window.setTimeout(finish, timeoutMs)\n return stop\n}\n\nfunction applies(link: HTMLLinkElement): boolean {\n if (/\\balternate\\b/i.test(link.rel)) return false\n const view = link.ownerDocument.defaultView\n return !link.media || !view?.matchMedia || view.matchMedia(link.media).matches\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { FC } from 'react'\nimport { createPortal } from 'react-dom'\nimport { mirrorStyles } from './copyStyles'\nimport type {\n PopupProps,\n PopupWindowApi,\n PopupWindowFeatures,\n UsePopupWindowOptions,\n} from './types'\nimport { whenStylesheetsLoad } from './whenStylesheetsLoad'\n\ninterface PopupState {\n popupWindow: Window | null\n container: HTMLElement | null\n blocked: boolean\n}\n\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\n\nclass PopupStore {\n state: PopupState = INITIAL_STATE\n private listeners = new Set<() => void>()\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getSnapshot = (): PopupState => this.state\n\n getServerSnapshot = (): PopupState => INITIAL_STATE\n\n setState(partial: Partial<PopupState>): void {\n this.state = { ...this.state, ...partial }\n for (const listener of this.listeners) listener()\n }\n}\n\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\n\nfunction buildFeatures(options: UsePopupWindowOptions): string {\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\n if (\n options.center !== false &&\n merged.left === undefined &&\n merged.top === undefined &&\n typeof merged.width === 'number' &&\n typeof merged.height === 'number'\n ) {\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\n }\n return Object.entries(merged)\n .filter(([, value]) => value !== undefined && value !== false)\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\n .join(',')\n}\n\nconst ABOUT_BLANK = 'about:blank'\n/** Interval for checking whether the page given by `url` has loaded. */\nconst LOAD_POLL_MS = 20\n/** Longest time `Popup` waits for copied stylesheets before rendering anyway. */\nconst STYLES_TIMEOUT_MS = 3000\n\n/**\n * Gives the initial `about:blank` document an HTML5 doctype, so the popup\n * lays out in standards mode like the opener instead of in quirks mode. Only\n * the parser can set the mode, so the document is written again. In Chromium\n * its `URL` then reports the opener's URL; a reload still loads `about:blank`.\n */\nfunction writeStandardsDocument(doc: Document): void {\n doc.open()\n doc.write('<!DOCTYPE html><html><head></head><body></body></html>')\n doc.close()\n}\n\n/**\n * The popup's document once the page given by `url` has replaced the initial\n * `about:blank` document and finished parsing, otherwise `null`. Throws when\n * the document is not scriptable.\n */\nfunction loadedDocument(popupWindow: Window): Document | null {\n const doc = popupWindow.document\n if (doc.URL === ABOUT_BLANK || doc.readyState === 'loading') return null\n return doc\n}\n\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\n function Popup({ children }: PopupProps) {\n const { container } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n )\n return container ? createPortal(children, container) : null\n }\n Popup.displayName = 'PopupWindow.Popup'\n return Popup\n}\n\n/**\n * Open part of your React tree in a separate browser window.\n *\n * The popup content is rendered with a portal into the popup's document, so\n * it stays part of your component tree: state, context and event handlers\n * all keep working across windows.\n *\n * ```tsx\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\n *\n * return (\n * <>\n * <button onClick={open}>Open panel</button>\n * <Popup>\n * <MyPanel />\n * </Popup>\n * </>\n * )\n * ```\n */\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\n const [store] = useState(() => new PopupStore())\n const [Popup] = useState(() => createPopupComponent(store))\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const cleanupRef = useRef<(() => void) | null>(null)\n\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\n\n // `userClosed` is true when the window was closed outside our control\n // (user hit the close button, opener unloaded) — then we must not touch it.\n const closePopup = useCallback(\n (userClosed: boolean) => {\n const { popupWindow } = store.getSnapshot()\n cleanupRef.current?.()\n cleanupRef.current = null\n if (!popupWindow) return\n if (!userClosed && !popupWindow.closed) popupWindow.close()\n store.setState({ popupWindow: null, container: null })\n optionsRef.current.onClose?.()\n },\n [store],\n )\n\n const open = useCallback((): Window | null => {\n if (typeof window === 'undefined') return null\n const current = store.getSnapshot()\n if (current.popupWindow && !current.popupWindow.closed) {\n current.popupWindow.focus()\n return current.popupWindow\n }\n\n const opts = optionsRef.current\n const url = opts.url ?? ABOUT_BLANK\n const popupWindow = window.open(url, opts.name ?? '_blank', buildFeatures(opts))\n if (!popupWindow) {\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n // Sandboxed embedders (VS Code's built-in browser, CodeSandbox/StackBlitz\n // previews, iframes sandboxed without popup-escape) open the popup with an\n // opaque origin, so reading its document throws a SecurityError. Treat\n // that as blocked instead of stranding a blank window the portal can\n // never reach.\n let initialDoc: Document\n try {\n initialDoc = popupWindow.document\n } catch {\n popupWindow.close()\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n const handleExternalClose = () => closePopup(true)\n const onPopupPagehide = () => {\n // pagehide also fires on navigation; only treat it as a close when the\n // window really is gone a tick later.\n setTimeout(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 0)\n }\n\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\n const closePoll = window.setInterval(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 250)\n\n const onOpenerPagehide = () => popupWindow.close()\n window.addEventListener('pagehide', onOpenerPagehide)\n\n let loadPoll: number | undefined\n let prepared = false\n let stopStyleSync: (() => void) | undefined\n let stopStylesWait: (() => void) | undefined\n\n cleanupRef.current = () => {\n window.clearInterval(closePoll)\n window.clearInterval(loadPoll)\n window.removeEventListener('pagehide', onOpenerPagehide)\n if (prepared) popupWindow.removeEventListener('pagehide', onPopupPagehide)\n stopStyleSync?.()\n stopStylesWait?.()\n }\n\n // Title, styles and the portal container go into the document that\n // stays: `about:blank` itself, or the page `url` loads in its place.\n const prepare = (doc: Document) => {\n prepared = true\n popupWindow.addEventListener('pagehide', onPopupPagehide)\n doc.title = opts.title ?? document.title\n let links: HTMLLinkElement[] = []\n if (opts.copyStyles !== false) {\n const styles = mirrorStyles(document, doc)\n stopStyleSync = styles.stop\n links = styles.links\n }\n const container = doc.createElement('div')\n container.setAttribute('data-popup-window-root', '')\n doc.body.appendChild(container)\n // The popup fetches copied <link> stylesheets asynchronously. Render\n // only once they have loaded, so the first paint is not unstyled.\n stopStylesWait = whenStylesheetsLoad(links, STYLES_TIMEOUT_MS, () => {\n store.setState({ container })\n opts.onOpen?.(popupWindow)\n })\n }\n\n store.setState({ popupWindow, container: null, blocked: false })\n\n if (url === ABOUT_BLANK) {\n // Before prepare(): writing the document removes event listeners on the\n // popup window.\n writeStandardsDocument(initialDoc)\n prepare(initialDoc)\n } else {\n // The window starts on an initial about:blank document and then\n // navigates to `url`. Wait until that page has replaced it and parsed.\n loadPoll = window.setInterval(() => {\n if (popupWindow.closed) {\n handleExternalClose()\n return\n }\n let doc: Document | null\n try {\n doc = loadedDocument(popupWindow)\n } catch {\n // The page went somewhere the opener cannot script, such as a\n // cross-origin redirect: nothing can ever render there.\n closePopup(false)\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return\n }\n if (!doc) return\n window.clearInterval(loadPoll)\n prepare(doc)\n }, LOAD_POLL_MS)\n }\n\n return popupWindow\n }, [store, closePopup])\n\n const close = useCallback(() => closePopup(false), [closePopup])\n\n const toggle = useCallback(() => {\n if (store.getSnapshot().popupWindow) {\n closePopup(false)\n } else {\n open()\n }\n }, [store, closePopup, open])\n\n const focus = useCallback(() => {\n const { popupWindow } = store.getSnapshot()\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\n }, [store])\n\n // Close the popup when the owning component unmounts — its portal content\n // would unmount anyway, leaving an empty window behind.\n useEffect(() => {\n return () => closePopup(false)\n }, [closePopup])\n\n return {\n open,\n close,\n toggle,\n focus,\n isOpen: state.popupWindow !== null,\n isBlocked: state.blocked,\n popupWindow: state.popupWindow,\n Popup,\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"usePopupWindow.d.ts","sourceRoot":"","sources":["../src/usePopupWindow.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,cAAc,EAEd,qBAAqB,EACtB,MAAM,SAAS,CAAA;AAgFhB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CA+KlF"}
1
+ {"version":3,"file":"usePopupWindow.d.ts","sourceRoot":"","sources":["../src/usePopupWindow.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,cAAc,EAEd,qBAAqB,EACtB,MAAM,SAAS,CAAA;AA4FhB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CAkLlF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jielga/react-popup-window",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "React hook to open part of your UI in a separate browser popup window — live portal rendering, style syncing and cross-window messaging.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -11,7 +11,7 @@ description: >
11
11
  metadata:
12
12
  type: core
13
13
  library: '@jielga/react-popup-window'
14
- library_version: '0.2.0'
14
+ library_version: '0.3.0'
15
15
  sources:
16
16
  - 'Jielga/react-popup-window:README.md'
17
17
  - 'Jielga/react-popup-window:src/usePopupWindow.ts'
@@ -103,7 +103,8 @@ const { open, Popup } = usePopupWindow({ title: 'Panel', url: '/popup.html' })
103
103
  ```
104
104
 
105
105
  `isOpen` turns `true` as soon as the window opens; `Popup` renders once the
106
- page has loaded. Important: do not point `url` at a route of the app, as that
106
+ page has loaded. Start the page with `<!doctype html>`, or the popup lays out
107
+ in quirks mode. Important: do not point `url` at a route of the app, as that
107
108
  starts a second copy of the app inside the popup. If the page redirects to
108
109
  another origin, the popup closes and `onBlocked` is called.
109
110
 
@@ -12,7 +12,7 @@ description: >
12
12
  metadata:
13
13
  type: sub-skill
14
14
  library: '@jielga/react-popup-window'
15
- library_version: '0.2.0'
15
+ library_version: '0.3.0'
16
16
  requires:
17
17
  - '@jielga/react-popup-window/getting-started'
18
18
  sources:
@@ -32,7 +32,12 @@ styling into it and keeps the mirror current:
32
32
 
33
33
  - `<style>` and `<link rel="stylesheet">` elements are copied into the
34
34
  popup `<head>`. `<style>` contents are serialized from the CSSOM, so
35
- rules injected with `insertRule` (CSS-in-JS) are included at copy time.
35
+ rules injected with `insertRule` are included.
36
+ - Rules added to or removed from a `<style>` sheet later with `insertRule`
37
+ and `deleteRule` are applied to the popup's copy as they happen. Emotion
38
+ (including MUI and `@mantine/emotion`) and styled-components add rules
39
+ this way in production builds, often while popup content renders for the
40
+ first time.
36
41
  - Additions, removals, and edits of style nodes in the opener's `<head>`
37
42
  are observed and re-mirrored. This covers Vite HMR and lazily loaded
38
43
  chunk CSS.
@@ -45,8 +50,10 @@ styling into it and keeps the mirror current:
45
50
  The popup loads each copied `<link>` stylesheet again. `Popup` renders once
46
51
  they have loaded or failed (at most 3 seconds), so the first frame shown is
47
52
  styled. Alternate stylesheets and non-matching `media` are not waited for.
48
- A `<link>` added to the opener while the popup is open loads in the popup
49
- after the content is shown.
53
+ A `<link>` added to the opener while the popup is open, such as lazily loaded
54
+ chunk CSS, is loaded by the popup again; until then the popup applies the
55
+ rules the opener already loaded. A cross-origin stylesheet without CORS
56
+ cannot be read that way and is unstyled until the popup has loaded it.
50
57
 
51
58
  `copyStyles: false` disables all of it. The mechanism is also exported
52
59
  standalone as `copyStyles(source, target, watch?)`, returning a `stop`
@@ -401,28 +408,28 @@ message — require listeners on the `popupWindow` object.
401
408
 
402
409
  Source: README.md (Communication)
403
410
 
404
- ### MEDIUM Expecting CSSOM-only rule changes to sync after open
411
+ ### MEDIUM Expecting in-place CSSOM edits to sync after open
405
412
 
406
413
  Wrong:
407
414
 
408
415
  ```tsx
409
416
  // after the popup is open
410
- someStyleSheet.insertRule('.late { color: red }') // no DOM mutation; not observed
417
+ rule.style.setProperty('color', 'red') // edits an existing rule; not mirrored
418
+ mediaRule.insertRule('.late { color: red }') // nested rule; not mirrored
411
419
  ```
412
420
 
413
421
  Correct:
414
422
 
415
423
  ```tsx
416
- // inject a new <style> element instead; node additions are observed
417
- const el = document.createElement('style')
418
- el.textContent = '.late { color: red }'
419
- document.head.appendChild(el)
424
+ // add a rule to the <style> sheet itself; insertRule on the sheet is mirrored
425
+ const sheet = styleElement.sheet!
426
+ sheet.insertRule('.late { color: red }', sheet.cssRules.length)
420
427
  ```
421
428
 
422
- Style synchronization serializes each sheet when it is copied and re-reads
423
- it when its DOM node changes. A rule inserted directly into an existing
424
- sheet's CSSOM after the popup opened produces no mutation and is not
425
- re-mirrored. Most CSS-in-JS libraries create or update style elements and
426
- are unaffected.
429
+ Style synchronization copies each sheet when the popup opens, applies
430
+ `insertRule`/`deleteRule` calls on a `<style>` sheet as they happen, and
431
+ copies a sheet again when its node changes. Edits inside an existing rule,
432
+ rules inserted into a nested rule such as `@media`, and changes to
433
+ `document.adoptedStyleSheets` after the popup opened are not mirrored.
427
434
 
428
435
  Source: src/copyStyles.ts