@jielga/react-popup-window 0.2.0 → 0.2.1

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
@@ -185,8 +185,13 @@ document and kept current:
185
185
  - `<style>` and `<link rel="stylesheet">` elements. `<style>` contents are
186
186
  serialized from the CSSOM, so rules injected with `insertRule` are
187
187
  included.
188
+ - Rules added to or removed from a `<style>` sheet with `insertRule` and
189
+ `deleteRule`, applied to the popup as they happen. CSS-in-JS libraries
190
+ such as Emotion and styled-components add rules this way in production
191
+ builds.
188
192
  - Additions, removals, and text edits of style nodes in `<head>`. This
189
- covers Vite HMR, lazily loaded chunk CSS, and CSS-in-JS libraries.
193
+ covers Vite HMR, lazily loaded chunk CSS, and CSS-in-JS libraries in
194
+ development builds.
190
195
  - `class`, `style`, and `data-*` attributes on `<html>` and `<body>`.
191
196
  Theme systems keyed on root attributes propagate to the popup.
192
197
  - `document.adoptedStyleSheets`.
@@ -196,6 +201,7 @@ The popup loads each copied `<link>` stylesheet again.
196
201
  Alternate stylesheets and stylesheets whose `media` does not match are not waited for.
197
202
  If a stylesheet takes longer than 3 seconds, `Popup` renders without it.
198
203
  Note that a `<link>` added to the opener while the popup is open loads in the popup after the content is shown.
204
+ 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
205
 
200
206
  Set `copyStyles: false` to disable.
201
207
 
@@ -4,10 +4,14 @@
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).
11
15
  * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored
12
16
  * and kept in sync, so theme switching (e.g. a `dark` class) propagates.
13
17
  * - `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;;;;;;;;;;;;;;;;;;;GAmBG;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,CA2O1F"}
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
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{}p(e)})};l.set(e,a)},d=e=>{l.get(e)?.stop(),l.delete(e)},f=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)},p=e=>{let t=r.get(e);t&&(t.textContent=c(e),u(e,t))},m=e=>{for(let[t,n]of r)(e===t||e.nodeType===Node.ELEMENT_NODE&&e.contains(t))&&(n.remove(),r.delete(t),d(t))},h=[`class`,`style`],g=()=>{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)(h.includes(n.name)||n.name.startsWith(`data-`))&&t.setAttribute(n.name,n.value);for(let n of Array.from(t.attributes))(h.includes(n.name)||n.name.startsWith(`data-`))&&!e.hasAttribute(n.name)&&t.removeAttribute(n.name)}},_=()=>{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]`))f(t);let v=Array.from(r.values()).filter(e=>e.tagName===`LINK`);if(g(),_(),!o)return{stop:()=>{},links:v};let y=new MutationObserver(e=>{for(let t of e){if(t.type===`characterData`){let e=t.target.parentElement;e?.tagName===`STYLE`&&p(e);continue}s(t.target)&&t.target.tagName===`STYLE`&&p(t.target);for(let e of t.addedNodes)if(s(e))f(e);else if(e.nodeType===Node.ELEMENT_NODE)for(let t of e.querySelectorAll(`style, link[rel~="stylesheet" i]`))f(t);for(let e of t.removedNodes)m(e)}});y.observe(e.head,{childList:!0,subtree:!0,characterData:!0});let b=new MutationObserver(()=>g());b.observe(e.documentElement,{attributes:!0}),e.body&&b.observe(e.body,{attributes:!0});let x=t.defaultView,S,C=()=>{for(let[e,t]of l)e.sheet===t.sheet&&t.sheet.cssRules.length!==t.count&&p(e);S=x?.requestAnimationFrame(C)};return x&&typeof x.requestAnimationFrame==`function`&&(S=x.requestAnimationFrame(C)),{stop:()=>{y.disconnect(),b.disconnect(),S!==void 0&&x?.cancelAnimationFrame(S);for(let e of l.values())e.stop();l.clear()},links:v}}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){let t=e.document;return t.URL===p||t.readyState===`loading`?null:t}function _(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 v(t={}){let[n]=(0,e.useState)(()=>new u),[r]=(0,e.useState)(()=>_(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),_=()=>{setTimeout(()=>{c.closed&&d()},0)},v=window.setInterval(()=>{c.closed&&d()},250),y=()=>c.close();window.addEventListener(`pagehide`,y);let b,x=!1,S,C;a.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=o(document,e);S=t.stop,r=t.links}let i=e.createElement(`div`);i.setAttribute(`data-popup-window-root`,``),e.body.appendChild(i),C=s(r,h,()=>{n.setState({container:i}),t.onOpen?.(c)})};return n.setState({popupWindow:c,container:null,blocked:!1}),r===p?w(u):b=window.setInterval(()=>{if(c.closed){d();return}let e;try{e=g(c)}catch{l(!1),n.setState({blocked:!0}),t.onBlocked?.();return}e&&(window.clearInterval(b),w(e))},m),c},[n,l]),v=(0,e.useCallback)(()=>l(!1),[l]),y=(0,e.useCallback)(()=>{n.getSnapshot().popupWindow?l(!1):d()},[n,l,d]),b=(0,e.useCallback)(()=>{let{popupWindow:e}=n.getSnapshot();e&&!e.closed&&e.focus()},[n]);return(0,e.useEffect)(()=>()=>l(!1),[l]),{open:d,close:v,toggle:y,focus:b,isOpen:c.popupWindow!==null,isBlocked:c.blocked,popupWindow:c.popupWindow,Popup:r}}exports.copyStyles=n,exports.usePopupWindow=v;
4
4
  //# 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 * - `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 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 }\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 }\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 (!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 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 * 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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"}
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,70 @@ 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
+ p(e);
76
+ })
77
+ };
78
+ s.set(e, a);
79
+ }, d = (e) => {
80
+ s.get(e)?.stop(), s.delete(e);
81
+ }, f = (e) => {
27
82
  if (r.has(e)) return;
28
83
  let n;
29
84
  if (e.tagName === "LINK") {
30
85
  let r = t.createElement("link");
31
- for (let t of s) {
86
+ for (let t of l) {
32
87
  let n = e.getAttribute(t);
33
88
  n !== null && r.setAttribute(t, n);
34
89
  }
35
90
  r.href = e.href, n = r;
36
91
  } else {
37
92
  let r = t.createElement("style");
38
- r.textContent = a(e);
93
+ r.textContent = o(e);
39
94
  let i = e.getAttribute("media");
40
95
  i && r.setAttribute("media", i), n = r;
41
96
  }
42
- t.head.appendChild(n), r.set(e, n);
43
- }, c = (e) => {
97
+ t.head.appendChild(n), r.set(e, n), e.tagName === "STYLE" && u(e, n);
98
+ }, p = (e) => {
44
99
  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 = () => {
100
+ t && (t.textContent = o(e), u(e, t));
101
+ }, m = (e) => {
102
+ for (let [t, n] of r) (e === t || e.nodeType === Node.ELEMENT_NODE && e.contains(t)) && (n.remove(), r.delete(t), d(t));
103
+ }, h = ["class", "style"], g = () => {
49
104
  let n = [[e.documentElement, t.documentElement], [e.body, t.body]];
50
105
  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);
106
+ for (let n of e.attributes) (h.includes(n.name) || n.name.startsWith("data-")) && t.setAttribute(n.name, n.value);
107
+ for (let n of Array.from(t.attributes)) (h.includes(n.name) || n.name.startsWith("data-")) && !e.hasAttribute(n.name) && t.removeAttribute(n.name);
53
108
  }
54
- }, f = () => {
109
+ }, _ = () => {
55
110
  let n = t.defaultView;
56
111
  if (!(!n?.CSSStyleSheet || !("adoptedStyleSheets" in e))) try {
57
112
  t.adoptedStyleSheets = e.adoptedStyleSheets.map((e) => {
@@ -60,42 +115,49 @@ function c(e, t, n = !0) {
60
115
  });
61
116
  } catch {}
62
117
  };
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 {
118
+ for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) f(t);
119
+ let v = Array.from(r.values()).filter((e) => e.tagName === "LINK");
120
+ if (g(), _(), !i) return {
66
121
  stop: () => {},
67
- links: p
122
+ links: v
68
123
  };
69
- let m = new MutationObserver((e) => {
124
+ let y = new MutationObserver((e) => {
70
125
  for (let t of e) {
71
126
  if (t.type === "characterData") {
72
127
  let e = t.target.parentElement;
73
- e?.tagName === "STYLE" && c(e);
128
+ e?.tagName === "STYLE" && p(e);
74
129
  continue;
75
130
  }
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);
131
+ a(t.target) && t.target.tagName === "STYLE" && p(t.target);
132
+ for (let e of t.addedNodes) if (a(e)) f(e);
133
+ else if (e.nodeType === Node.ELEMENT_NODE) for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) f(t);
134
+ for (let e of t.removedNodes) m(e);
80
135
  }
81
136
  });
82
- m.observe(e.head, {
137
+ y.observe(e.head, {
83
138
  childList: !0,
84
139
  subtree: !0,
85
140
  characterData: !0
86
141
  });
87
- let h = new MutationObserver(() => d());
88
- return h.observe(e.documentElement, { attributes: !0 }), e.body && h.observe(e.body, { attributes: !0 }), {
142
+ let b = new MutationObserver(() => g());
143
+ b.observe(e.documentElement, { attributes: !0 }), e.body && b.observe(e.body, { attributes: !0 });
144
+ let x = t.defaultView, S, C = () => {
145
+ for (let [e, t] of s) e.sheet === t.sheet && t.sheet.cssRules.length !== t.count && p(e);
146
+ S = x?.requestAnimationFrame(C);
147
+ };
148
+ return x && typeof x.requestAnimationFrame == "function" && (S = x.requestAnimationFrame(C)), {
89
149
  stop: () => {
90
- m.disconnect(), h.disconnect();
150
+ y.disconnect(), b.disconnect(), S !== void 0 && x?.cancelAnimationFrame(S);
151
+ for (let e of s.values()) e.stop();
152
+ s.clear();
91
153
  },
92
- links: p
154
+ links: v
93
155
  };
94
156
  }
95
157
  //#endregion
96
158
  //#region src/whenStylesheetsLoad.ts
97
- function l(e, t, n) {
98
- let r = e.filter((e) => e.sheet === null && u(e));
159
+ function d(e, t, n) {
160
+ let r = e.filter((e) => e.sheet === null && f(e));
99
161
  if (r.length === 0) return n(), () => {};
100
162
  let i = r.length, a = () => {
101
163
  i--, i === 0 && s();
@@ -109,23 +171,23 @@ function l(e, t, n) {
109
171
  let c = window.setTimeout(s, t);
110
172
  return o;
111
173
  }
112
- function u(e) {
174
+ function f(e) {
113
175
  if (/\balternate\b/i.test(e.rel)) return !1;
114
176
  let t = e.ownerDocument.defaultView;
115
177
  return !e.media || !t?.matchMedia || t.matchMedia(e.media).matches;
116
178
  }
117
179
  //#endregion
118
180
  //#region src/usePopupWindow.ts
119
- var d = {
181
+ var p = {
120
182
  popupWindow: null,
121
183
  container: null,
122
184
  blocked: !1
123
- }, f = class {
124
- state = d;
185
+ }, m = class {
186
+ state = p;
125
187
  listeners = /* @__PURE__ */ new Set();
126
188
  subscribe = (e) => (this.listeners.add(e), () => this.listeners.delete(e));
127
189
  getSnapshot = () => this.state;
128
- getServerSnapshot = () => d;
190
+ getServerSnapshot = () => p;
129
191
  setState(e) {
130
192
  this.state = {
131
193
  ...this.state,
@@ -133,44 +195,44 @@ var d = {
133
195
  };
134
196
  for (let e of this.listeners) e();
135
197
  }
136
- }, p = {
198
+ }, h = {
137
199
  popup: !0,
138
200
  width: 640,
139
201
  height: 480
140
202
  };
141
- function m(e) {
203
+ function g(e) {
142
204
  let t = {
143
- ...p,
205
+ ...h,
144
206
  ...e.features
145
207
  };
146
208
  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
209
  }
148
- var h = "about:blank", g = 20, _ = 3e3;
149
- function v(e) {
210
+ var _ = "about:blank", v = 20, y = 3e3;
211
+ function b(e) {
150
212
  let t = e.document;
151
- return t.URL === h || t.readyState === "loading" ? null : t;
213
+ return t.URL === _ || t.readyState === "loading" ? null : t;
152
214
  }
153
- function y(e) {
215
+ function x(e) {
154
216
  function t({ children: t }) {
155
217
  let { container: n } = i(e.subscribe, e.getSnapshot, e.getServerSnapshot);
156
218
  return n ? a(t, n) : null;
157
219
  }
158
220
  return t.displayName = "PopupWindow.Popup", t;
159
221
  }
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) => {
222
+ function S(a = {}) {
223
+ let [o] = r(() => new m()), [s] = r(() => x(o)), c = n(a);
224
+ c.current = a;
225
+ let l = n(null), f = i(o.subscribe, o.getSnapshot, o.getServerSnapshot), p = e((e) => {
164
226
  let { popupWindow: t } = o.getSnapshot();
165
- d.current?.(), d.current = null, t && (!e && !t.closed && t.close(), o.setState({
227
+ l.current?.(), l.current = null, t && (!e && !t.closed && t.close(), o.setState({
166
228
  popupWindow: null,
167
229
  container: null
168
- }), u.current.onClose?.());
169
- }, [o]), x = e(() => {
230
+ }), c.current.onClose?.());
231
+ }, [o]), h = e(() => {
170
232
  if (typeof window > "u") return null;
171
233
  let e = o.getSnapshot();
172
234
  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));
235
+ let t = c.current, n = t.url ?? _, r = window.open(n, t.name ?? "_blank", g(t));
174
236
  if (!r) return o.setState({ blocked: !0 }), t.onBlocked?.(), null;
175
237
  let i;
176
238
  try {
@@ -178,27 +240,27 @@ function b(a = {}) {
178
240
  } catch {
179
241
  return r.close(), o.setState({ blocked: !0 }), t.onBlocked?.(), null;
180
242
  }
181
- let a = () => b(!0), s = () => {
243
+ let a = () => p(!0), s = () => {
182
244
  setTimeout(() => {
183
245
  r.closed && a();
184
246
  }, 0);
185
247
  }, f = window.setInterval(() => {
186
248
  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?.();
249
+ }, 250), m = () => r.close();
250
+ window.addEventListener("pagehide", m);
251
+ let h, x = !1, S, C;
252
+ l.current = () => {
253
+ window.clearInterval(f), window.clearInterval(h), window.removeEventListener("pagehide", m), x && r.removeEventListener("pagehide", s), S?.(), C?.();
192
254
  };
193
255
  let w = (e) => {
194
256
  x = !0, r.addEventListener("pagehide", s), e.title = t.title ?? document.title;
195
257
  let n = [];
196
258
  if (t.copyStyles !== !1) {
197
- let t = c(document, e);
259
+ let t = u(document, e);
198
260
  S = t.stop, n = t.links;
199
261
  }
200
262
  let i = e.createElement("div");
201
- i.setAttribute("data-popup-window-root", ""), e.body.appendChild(i), C = l(n, _, () => {
263
+ i.setAttribute("data-popup-window-root", ""), e.body.appendChild(i), C = d(n, y, () => {
202
264
  o.setState({ container: i }), t.onOpen?.(r);
203
265
  });
204
266
  };
@@ -206,42 +268,42 @@ function b(a = {}) {
206
268
  popupWindow: r,
207
269
  container: null,
208
270
  blocked: !1
209
- }), n === h ? w(i) : y = window.setInterval(() => {
271
+ }), n === _ ? w(i) : h = window.setInterval(() => {
210
272
  if (r.closed) {
211
273
  a();
212
274
  return;
213
275
  }
214
276
  let e;
215
277
  try {
216
- e = v(r);
278
+ e = b(r);
217
279
  } catch {
218
- b(!1), o.setState({ blocked: !0 }), t.onBlocked?.();
280
+ p(!1), o.setState({ blocked: !0 }), t.onBlocked?.();
219
281
  return;
220
282
  }
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();
283
+ e && (window.clearInterval(h), w(e));
284
+ }, v), r;
285
+ }, [o, p]), S = e(() => p(!1), [p]), C = e(() => {
286
+ o.getSnapshot().popupWindow ? p(!1) : h();
225
287
  }, [
226
288
  o,
227
- b,
228
- x
289
+ p,
290
+ h
229
291
  ]), w = e(() => {
230
292
  let { popupWindow: e } = o.getSnapshot();
231
293
  e && !e.closed && e.focus();
232
294
  }, [o]);
233
- return t(() => () => b(!1), [b]), {
234
- open: x,
295
+ return t(() => () => p(!1), [p]), {
296
+ open: h,
235
297
  close: S,
236
298
  toggle: C,
237
299
  focus: w,
238
- isOpen: p.popupWindow !== null,
239
- isBlocked: p.blocked,
240
- popupWindow: p.popupWindow,
300
+ isOpen: f.popupWindow !== null,
301
+ isBlocked: f.blocked,
302
+ popupWindow: f.popupWindow,
241
303
  Popup: s
242
304
  };
243
305
  }
244
306
  //#endregion
245
- export { o as copyStyles, b as usePopupWindow };
307
+ export { o as copyStyles, S as usePopupWindow };
246
308
 
247
309
  //# 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 * - `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 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 }\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 }\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 (!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 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 * 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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"}
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.2.1",
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.2.1'
15
15
  sources:
16
16
  - 'Jielga/react-popup-window:README.md'
17
17
  - 'Jielga/react-popup-window:src/usePopupWindow.ts'
@@ -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.2.1'
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.
@@ -401,28 +406,28 @@ message — require listeners on the `popupWindow` object.
401
406
 
402
407
  Source: README.md (Communication)
403
408
 
404
- ### MEDIUM Expecting CSSOM-only rule changes to sync after open
409
+ ### MEDIUM Expecting in-place CSSOM edits to sync after open
405
410
 
406
411
  Wrong:
407
412
 
408
413
  ```tsx
409
414
  // after the popup is open
410
- someStyleSheet.insertRule('.late { color: red }') // no DOM mutation; not observed
415
+ rule.style.setProperty('color', 'red') // edits an existing rule; not mirrored
416
+ mediaRule.insertRule('.late { color: red }') // nested rule; not mirrored
411
417
  ```
412
418
 
413
419
  Correct:
414
420
 
415
421
  ```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)
422
+ // add a rule to the <style> sheet itself; insertRule on the sheet is mirrored
423
+ const sheet = styleElement.sheet!
424
+ sheet.insertRule('.late { color: red }', sheet.cssRules.length)
420
425
  ```
421
426
 
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.
427
+ Style synchronization copies each sheet when the popup opens, applies
428
+ `insertRule`/`deleteRule` calls on a `<style>` sheet as they happen, and
429
+ copies a sheet again when its node changes. Edits inside an existing rule,
430
+ rules inserted into a nested rule such as `@media`, and changes to
431
+ `document.adoptedStyleSheets` after the popup opened are not mirrored.
427
432
 
428
433
  Source: src/copyStyles.ts