@jielga/react-popup-window 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,CAuJvF"}
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"}
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){let r=new Map,i=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`)},a=e=>{let t=e.sheet;if(t)try{return Array.from(t.cssRules).map(e=>e.cssText).join(`
2
- `)}catch{}return e.textContent??``},o=e=>{if(r.has(e))return;let n;if(e.tagName===`LINK`){let r=t.createElement(`link`);r.rel=`stylesheet`,r.href=e.href;let i=e.getAttribute(`media`);i&&(r.media=i),n=r}else{let r=t.createElement(`style`);r.textContent=a(e);let i=e.getAttribute(`media`);i&&r.setAttribute(`media`,i),n=r}t.head.appendChild(n),r.set(e,n)},s=e=>{let t=r.get(e);t&&(t.textContent=a(e))},c=e=>{for(let[t,n]of r)(e===t||e.nodeType===Node.ELEMENT_NODE&&e.contains(t))&&(n.remove(),r.delete(t))},l=[`class`,`style`],u=()=>{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)(l.includes(n.name)||n.name.startsWith(`data-`))&&t.setAttribute(n.name,n.value);for(let n of Array.from(t.attributes))(l.includes(n.name)||n.name.startsWith(`data-`))&&!e.hasAttribute(n.name)&&t.removeAttribute(n.name)}},d=()=>{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]`))o(t);if(u(),d(),!n||typeof MutationObserver>`u`)return()=>{};let f=new MutationObserver(e=>{for(let t of e){if(t.type===`characterData`){let e=t.target.parentElement;e?.tagName===`STYLE`&&s(e);continue}i(t.target)&&t.target.tagName===`STYLE`&&s(t.target);for(let e of t.addedNodes)if(i(e))o(e);else if(e.nodeType===Node.ELEMENT_NODE)for(let t of e.querySelectorAll(`style, link[rel~="stylesheet" i]`))o(t);for(let e of t.removedNodes)c(e)}});f.observe(e.head,{childList:!0,subtree:!0,characterData:!0});let p=new MutationObserver(()=>u());return p.observe(e.documentElement,{attributes:!0}),e.body&&p.observe(e.body,{attributes:!0}),()=>{f.disconnect(),p.disconnect()}}var r={popupWindow:null,container:null,blocked:!1},i=class{state=r;listeners=new Set;subscribe=e=>(this.listeners.add(e),()=>this.listeners.delete(e));getSnapshot=()=>this.state;getServerSnapshot=()=>r;setState(e){this.state={...this.state,...e};for(let e of this.listeners)e()}},a={popup:!0,width:640,height:480};function o(e){let t={...a,...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(`,`)}function s(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 c(t={}){let[r]=(0,e.useState)(()=>new i),[a]=(0,e.useState)(()=>s(r)),c=(0,e.useRef)(t);c.current=t;let l=(0,e.useRef)(null),u=(0,e.useSyncExternalStore)(r.subscribe,r.getSnapshot,r.getServerSnapshot),d=(0,e.useCallback)(e=>{let{popupWindow:t}=r.getSnapshot();l.current?.(),l.current=null,t&&(!e&&!t.closed&&t.close(),r.setState({popupWindow:null,container:null}),c.current.onClose?.())},[r]),f=(0,e.useCallback)(()=>{if(typeof window>`u`)return null;let e=r.getSnapshot();if(e.popupWindow&&!e.popupWindow.closed)return e.popupWindow.focus(),e.popupWindow;let t=c.current,i=window.open(`about:blank`,t.name??`_blank`,o(t));if(!i)return r.setState({blocked:!0}),t.onBlocked?.(),null;let a=i.document;a.title=t.title??document.title;let s;t.copyStyles!==!1&&(s=n(document,a));let u=a.createElement(`div`);u.setAttribute(`data-popup-window-root`,``),a.body.appendChild(u);let f=()=>d(!0),p=()=>{setTimeout(()=>{i.closed&&f()},0)};i.addEventListener(`pagehide`,p);let m=window.setInterval(()=>{i.closed&&f()},250),h=()=>i.close();return window.addEventListener(`pagehide`,h),l.current=()=>{window.clearInterval(m),window.removeEventListener(`pagehide`,h),i.removeEventListener(`pagehide`,p),s?.()},r.setState({popupWindow:i,container:u,blocked:!1}),t.onOpen?.(i),i},[r,d]),p=(0,e.useCallback)(()=>d(!1),[d]),m=(0,e.useCallback)(()=>{r.getSnapshot().popupWindow?d(!1):f()},[r,d,f]),h=(0,e.useCallback)(()=>{let{popupWindow:e}=r.getSnapshot();e&&!e.closed&&e.focus()},[r]);return(0,e.useEffect)(()=>()=>d(!1),[d]),{open:f,close:p,toggle:m,focus:h,isOpen:u.popupWindow!==null,isBlocked:u.blocked,popupWindow:u.popupWindow,Popup:a}}exports.copyStyles=n,exports.usePopupWindow=c;
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;
4
4
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/copyStyles.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\r\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\r\n * in sync while the popup is open:\r\n *\r\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\r\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\r\n * possible, so rules injected via `insertRule` (CSS-in-JS \"speedy\" mode)\r\n * are included.\r\n * - Additions/removals/edits of style nodes in the source `<head>` are\r\n * observed (covers Vite HMR, lazily loaded chunk CSS, styled-components).\r\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\r\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\r\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\r\n *\r\n * Returns a function that stops observing.\r\n */\r\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\r\n const mirrors = new Map<Element, Element>()\r\n\r\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\r\n if (node.nodeType !== Node.ELEMENT_NODE) return false\r\n const el = node as Element\r\n return (\r\n el.tagName === 'STYLE' ||\r\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\r\n )\r\n }\r\n\r\n const serializeStyle = (el: HTMLStyleElement): string => {\r\n const sheet = el.sheet\r\n if (sheet) {\r\n try {\r\n return Array.from(sheet.cssRules)\r\n .map((rule) => rule.cssText)\r\n .join('\\n')\r\n } catch {\r\n // Inaccessible cssRules — fall back to the raw text below.\r\n }\r\n }\r\n return el.textContent ?? ''\r\n }\r\n\r\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\r\n if (mirrors.has(el)) return\r\n let clone: Element\r\n if (el.tagName === 'LINK') {\r\n const link = target.createElement('link')\r\n link.rel = 'stylesheet'\r\n // .href resolves to an absolute URL, so relative hrefs keep working\r\n // from the popup's `about:blank` document.\r\n link.href = (el as HTMLLinkElement).href\r\n const media = el.getAttribute('media')\r\n if (media) link.media = media\r\n clone = link\r\n } else {\r\n const style = target.createElement('style')\r\n style.textContent = serializeStyle(el as HTMLStyleElement)\r\n const media = el.getAttribute('media')\r\n if (media) style.setAttribute('media', media)\r\n clone = style\r\n }\r\n target.head.appendChild(clone)\r\n mirrors.set(el, clone)\r\n }\r\n\r\n const refresh = (el: HTMLStyleElement): void => {\r\n const clone = mirrors.get(el)\r\n if (clone) clone.textContent = serializeStyle(el)\r\n }\r\n\r\n const unmirrorSubtree = (removed: Node): void => {\r\n for (const [src, clone] of mirrors) {\r\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\r\n clone.remove()\r\n mirrors.delete(src)\r\n }\r\n }\r\n }\r\n\r\n const ROOT_ATTRS = ['class', 'style']\r\n const syncRootAttrs = (): void => {\r\n const pairs: Array<[Element | null, Element | null]> = [\r\n [source.documentElement, target.documentElement],\r\n [source.body, target.body],\r\n ]\r\n for (const [from, to] of pairs) {\r\n if (!from || !to) continue\r\n for (const attr of from.attributes) {\r\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\r\n to.setAttribute(attr.name, attr.value)\r\n }\r\n }\r\n for (const attr of Array.from(to.attributes)) {\r\n if (\r\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\r\n !from.hasAttribute(attr.name)\r\n ) {\r\n to.removeAttribute(attr.name)\r\n }\r\n }\r\n }\r\n }\r\n\r\n const syncAdoptedSheets = (): void => {\r\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\r\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\r\n try {\r\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\r\n const copy = new targetWindow.CSSStyleSheet!()\r\n copy.replaceSync(\r\n Array.from(sheet.cssRules)\r\n .map((rule) => rule.cssText)\r\n .join('\\n'),\r\n )\r\n return copy\r\n })\r\n } catch {\r\n // Best effort — older browsers or cross-origin rules.\r\n }\r\n }\r\n\r\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\r\n mirror(el as HTMLStyleElement | HTMLLinkElement)\r\n }\r\n syncRootAttrs()\r\n syncAdoptedSheets()\r\n\r\n if (!watch || typeof MutationObserver === 'undefined') {\r\n return () => {}\r\n }\r\n\r\n const headObserver = new MutationObserver((records) => {\r\n for (const record of records) {\r\n if (record.type === 'characterData') {\r\n const parent = record.target.parentElement\r\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\r\n continue\r\n }\r\n // childList\r\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\r\n // textContent assignment replaces the text node → childList on <style>\r\n refresh(record.target as HTMLStyleElement)\r\n }\r\n for (const added of record.addedNodes) {\r\n if (isStyleNode(added)) {\r\n mirror(added)\r\n } else if (added.nodeType === Node.ELEMENT_NODE) {\r\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\r\n mirror(el as HTMLStyleElement | HTMLLinkElement)\r\n }\r\n }\r\n }\r\n for (const removed of record.removedNodes) {\r\n unmirrorSubtree(removed)\r\n }\r\n }\r\n })\r\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\r\n\r\n const rootObserver = new MutationObserver(() => syncRootAttrs())\r\n rootObserver.observe(source.documentElement, { attributes: true })\r\n if (source.body) rootObserver.observe(source.body, { attributes: true })\r\n\r\n return () => {\r\n headObserver.disconnect()\r\n rootObserver.disconnect()\r\n }\r\n}\r\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\r\nimport type { FC } from 'react'\r\nimport { createPortal } from 'react-dom'\r\nimport { copyStyles } from './copyStyles'\r\nimport type {\r\n PopupProps,\r\n PopupWindowApi,\r\n PopupWindowFeatures,\r\n UsePopupWindowOptions,\r\n} from './types'\r\n\r\ninterface PopupState {\r\n popupWindow: Window | null\r\n container: HTMLElement | null\r\n blocked: boolean\r\n}\r\n\r\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\r\n\r\nclass PopupStore {\r\n state: PopupState = INITIAL_STATE\r\n private listeners = new Set<() => void>()\r\n\r\n subscribe = (listener: () => void): (() => void) => {\r\n this.listeners.add(listener)\r\n return () => this.listeners.delete(listener)\r\n }\r\n\r\n getSnapshot = (): PopupState => this.state\r\n\r\n getServerSnapshot = (): PopupState => INITIAL_STATE\r\n\r\n setState(partial: Partial<PopupState>): void {\r\n this.state = { ...this.state, ...partial }\r\n for (const listener of this.listeners) listener()\r\n }\r\n}\r\n\r\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\r\n\r\nfunction buildFeatures(options: UsePopupWindowOptions): string {\r\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\r\n if (\r\n options.center !== false &&\r\n merged.left === undefined &&\r\n merged.top === undefined &&\r\n typeof merged.width === 'number' &&\r\n typeof merged.height === 'number'\r\n ) {\r\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\r\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\r\n }\r\n return Object.entries(merged)\r\n .filter(([, value]) => value !== undefined && value !== false)\r\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\r\n .join(',')\r\n}\r\n\r\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\r\n function Popup({ children }: PopupProps) {\r\n const { container } = useSyncExternalStore(\r\n store.subscribe,\r\n store.getSnapshot,\r\n store.getServerSnapshot,\r\n )\r\n return container ? createPortal(children, container) : null\r\n }\r\n Popup.displayName = 'PopupWindow.Popup'\r\n return Popup\r\n}\r\n\r\n/**\r\n * Open part of your React tree in a separate browser window.\r\n *\r\n * The popup content is rendered with a portal into the popup's document, so\r\n * it stays part of your component tree: state, context and event handlers\r\n * all keep working across windows.\r\n *\r\n * ```tsx\r\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\r\n *\r\n * return (\r\n * <>\r\n * <button onClick={open}>Open panel</button>\r\n * <Popup>\r\n * <MyPanel />\r\n * </Popup>\r\n * </>\r\n * )\r\n * ```\r\n */\r\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\r\n const [store] = useState(() => new PopupStore())\r\n const [Popup] = useState(() => createPopupComponent(store))\r\n\r\n const optionsRef = useRef(options)\r\n optionsRef.current = options\r\n\r\n const cleanupRef = useRef<(() => void) | null>(null)\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\r\n\r\n // `userClosed` is true when the window was closed outside our control\r\n // (user hit the close button, opener unloaded) — then we must not touch it.\r\n const closePopup = useCallback(\r\n (userClosed: boolean) => {\r\n const { popupWindow } = store.getSnapshot()\r\n cleanupRef.current?.()\r\n cleanupRef.current = null\r\n if (!popupWindow) return\r\n if (!userClosed && !popupWindow.closed) popupWindow.close()\r\n store.setState({ popupWindow: null, container: null })\r\n optionsRef.current.onClose?.()\r\n },\r\n [store],\r\n )\r\n\r\n const open = useCallback((): Window | null => {\r\n if (typeof window === 'undefined') return null\r\n const current = store.getSnapshot()\r\n if (current.popupWindow && !current.popupWindow.closed) {\r\n current.popupWindow.focus()\r\n return current.popupWindow\r\n }\r\n\r\n const opts = optionsRef.current\r\n const popupWindow = window.open('about:blank', opts.name ?? '_blank', buildFeatures(opts))\r\n if (!popupWindow) {\r\n store.setState({ blocked: true })\r\n opts.onBlocked?.()\r\n return null\r\n }\r\n\r\n const doc = popupWindow.document\r\n doc.title = opts.title ?? document.title\r\n\r\n let stopStyleSync: (() => void) | undefined\r\n if (opts.copyStyles !== false) {\r\n stopStyleSync = copyStyles(document, doc)\r\n }\r\n\r\n const container = doc.createElement('div')\r\n container.setAttribute('data-popup-window-root', '')\r\n doc.body.appendChild(container)\r\n\r\n const handleExternalClose = () => closePopup(true)\r\n const onPopupPagehide = () => {\r\n // pagehide also fires on navigation; only treat it as a close when the\r\n // window really is gone a tick later.\r\n setTimeout(() => {\r\n if (popupWindow.closed) handleExternalClose()\r\n }, 0)\r\n }\r\n popupWindow.addEventListener('pagehide', onPopupPagehide)\r\n\r\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\r\n const closePoll = window.setInterval(() => {\r\n if (popupWindow.closed) handleExternalClose()\r\n }, 250)\r\n\r\n const onOpenerPagehide = () => popupWindow.close()\r\n window.addEventListener('pagehide', onOpenerPagehide)\r\n\r\n cleanupRef.current = () => {\r\n window.clearInterval(closePoll)\r\n window.removeEventListener('pagehide', onOpenerPagehide)\r\n popupWindow.removeEventListener('pagehide', onPopupPagehide)\r\n stopStyleSync?.()\r\n }\r\n\r\n store.setState({ popupWindow, container, blocked: false })\r\n opts.onOpen?.(popupWindow)\r\n return popupWindow\r\n }, [store, closePopup])\r\n\r\n const close = useCallback(() => closePopup(false), [closePopup])\r\n\r\n const toggle = useCallback(() => {\r\n if (store.getSnapshot().popupWindow) {\r\n closePopup(false)\r\n } else {\r\n open()\r\n }\r\n }, [store, closePopup, open])\r\n\r\n const focus = useCallback(() => {\r\n const { popupWindow } = store.getSnapshot()\r\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\r\n }, [store])\r\n\r\n // Close the popup when the owning component unmounts — its portal content\r\n // would unmount anyway, leaving an empty window behind.\r\n useEffect(() => {\r\n return () => closePopup(false)\r\n }, [closePopup])\r\n\r\n return {\r\n open,\r\n close,\r\n toggle,\r\n focus,\r\n isOpen: state.popupWindow !== null,\r\n isBlocked: state.blocked,\r\n popupWindow: state.popupWindow,\r\n Popup,\r\n }\r\n}\r\n"],"mappings":"iHAgBA,SAAgB,EAAW,EAAkB,EAAkB,EAAQ,GAAkB,CACvF,IAAM,EAAU,IAAI,IAEd,EAAe,GAA2D,CAC9E,GAAI,EAAK,WAAa,KAAK,aAAc,MAAO,GAChD,IAAM,EAAK,EACX,OACE,EAAG,UAAY,SACd,EAAG,UAAY,SAAW,EAAG,aAAa,KAAK,GAAK,GAAA,CAAI,YAAY,CAAC,CAAC,SAAS,YAAY,CAEhG,EAEM,EAAkB,GAAiC,CACvD,IAAM,EAAQ,EAAG,MACjB,GAAI,EACF,GAAI,CACF,OAAO,MAAM,KAAK,EAAM,QAAQ,CAAC,CAC9B,IAAK,GAAS,EAAK,OAAO,CAAC,CAC3B,KAAK;CAAI,CACd,MAAQ,CAER,CAEF,OAAO,EAAG,aAAe,EAC3B,EAEM,EAAU,GAAiD,CAC/D,GAAI,EAAQ,IAAI,CAAE,EAAG,OACrB,IAAI,EACJ,GAAI,EAAG,UAAY,OAAQ,CACzB,IAAM,EAAO,EAAO,cAAc,MAAM,EACxC,EAAK,IAAM,aAGX,EAAK,KAAQ,EAAuB,KACpC,IAAM,EAAQ,EAAG,aAAa,OAAO,EACjC,IAAO,EAAK,MAAQ,GACxB,EAAQ,CACV,KAAO,CACL,IAAM,EAAQ,EAAO,cAAc,OAAO,EAC1C,EAAM,YAAc,EAAe,CAAsB,EACzD,IAAM,EAAQ,EAAG,aAAa,OAAO,EACjC,GAAO,EAAM,aAAa,QAAS,CAAK,EAC5C,EAAQ,CACV,CACA,EAAO,KAAK,YAAY,CAAK,EAC7B,EAAQ,IAAI,EAAI,CAAK,CACvB,EAEM,EAAW,GAA+B,CAC9C,IAAM,EAAQ,EAAQ,IAAI,CAAE,EACxB,IAAO,EAAM,YAAc,EAAe,CAAE,EAClD,EAEM,EAAmB,GAAwB,CAC/C,IAAK,GAAM,CAAC,EAAK,KAAU,GACrB,IAAY,GAAQ,EAAQ,WAAa,KAAK,cAAgB,EAAQ,SAAS,CAAG,KACpF,EAAM,OAAO,EACb,EAAQ,OAAO,CAAG,EAGxB,EAEM,EAAa,CAAC,QAAS,OAAO,EAC9B,MAA4B,CAChC,IAAM,EAAiD,CACrD,CAAC,EAAO,gBAAiB,EAAO,eAAe,EAC/C,CAAC,EAAO,KAAM,EAAO,IAAI,CAC3B,EACA,IAAK,GAAM,CAAC,EAAM,KAAO,EACnB,MAAC,GAAQ,CAAC,GACd,KAAK,IAAM,KAAQ,EAAK,YAClB,EAAW,SAAS,EAAK,IAAI,GAAK,EAAK,KAAK,WAAW,OAAO,IAChE,EAAG,aAAa,EAAK,KAAM,EAAK,KAAK,EAGzC,IAAK,IAAM,KAAQ,MAAM,KAAK,EAAG,UAAU,GAEtC,EAAW,SAAS,EAAK,IAAI,GAAK,EAAK,KAAK,WAAW,OAAO,IAC/D,CAAC,EAAK,aAAa,EAAK,IAAI,GAE5B,EAAG,gBAAgB,EAAK,IAAI,CARS,CAY7C,EAEM,MAAgC,CACpC,IAAM,EAAe,EAAO,YACxB,MAAC,GAAc,eAAiB,EAAE,uBAAwB,IAC9D,GAAI,CACF,EAAO,mBAAqB,EAAO,mBAAmB,IAAK,GAAU,CACnE,IAAM,EAAO,IAAI,EAAa,cAM9B,OALA,EAAK,YACH,MAAM,KAAK,EAAM,QAAQ,CAAC,CACvB,IAAK,GAAS,EAAK,OAAO,CAAC,CAC3B,KAAK;CAAI,CACd,EACO,CACT,CAAC,CACH,MAAQ,CAER,CACF,EAEA,IAAK,IAAM,KAAM,EAAO,iBAAiB,kCAAkC,EACzE,EAAO,CAAwC,EAKjD,GAHA,EAAc,EACd,EAAkB,EAEd,CAAC,GAAS,OAAO,iBAAqB,IACxC,UAAa,CAAC,EAGhB,IAAM,EAAe,IAAI,iBAAkB,GAAY,CACrD,IAAK,IAAM,KAAU,EAAS,CAC5B,GAAI,EAAO,OAAS,gBAAiB,CACnC,IAAM,EAAS,EAAO,OAAO,cACzB,GAAQ,UAAY,SAAS,EAAQ,CAA0B,EACnE,QACF,CAEI,EAAY,EAAO,MAAM,GAAK,EAAO,OAAO,UAAY,SAE1D,EAAQ,EAAO,MAA0B,EAE3C,IAAK,IAAM,KAAS,EAAO,WACzB,GAAI,EAAY,CAAK,EACnB,EAAO,CAAK,OACP,GAAI,EAAM,WAAa,KAAK,aACjC,IAAK,IAAM,KAAO,EAAkB,iBAAiB,kCAAkC,EACrF,EAAO,CAAwC,EAIrD,IAAK,IAAM,KAAW,EAAO,aAC3B,EAAgB,CAAO,CAE3B,CACF,CAAC,EACD,EAAa,QAAQ,EAAO,KAAM,CAAE,UAAW,GAAM,QAAS,GAAM,cAAe,EAAK,CAAC,EAEzF,IAAM,EAAe,IAAI,qBAAuB,EAAc,CAAC,EAI/D,OAHA,EAAa,QAAQ,EAAO,gBAAiB,CAAE,WAAY,EAAK,CAAC,EAC7D,EAAO,MAAM,EAAa,QAAQ,EAAO,KAAM,CAAE,WAAY,EAAK,CAAC,MAE1D,CACX,EAAa,WAAW,EACxB,EAAa,WAAW,CAC1B,CACF,CCtJA,IAAM,EAA4B,CAAE,YAAa,KAAM,UAAW,KAAM,QAAS,EAAM,EAEjF,EAAN,KAAiB,CACf,MAAoB,EACpB,UAAoB,IAAI,IAExB,UAAa,IACX,KAAK,UAAU,IAAI,CAAQ,MACd,KAAK,UAAU,OAAO,CAAQ,GAG7C,gBAAgC,KAAK,MAErC,sBAAsC,EAEtC,SAAS,EAAoC,CAC3C,KAAK,MAAQ,CAAE,GAAG,KAAK,MAAO,GAAG,CAAQ,EACzC,IAAK,IAAM,KAAY,KAAK,UAAW,EAAS,CAClD,CACF,EAEM,EAAwC,CAAE,MAAO,GAAM,MAAO,IAAK,OAAQ,GAAI,EAErF,SAAS,EAAc,EAAwC,CAC7D,IAAM,EAA8B,CAAE,GAAG,EAAkB,GAAG,EAAQ,QAAS,EAW/E,OATE,EAAQ,SAAW,IACnB,EAAO,OAAS,IAAA,IAChB,EAAO,MAAQ,IAAA,IACf,OAAO,EAAO,OAAU,UACxB,OAAO,EAAO,QAAW,WAEzB,EAAO,KAAO,KAAK,IAAI,EAAG,KAAK,MAAM,OAAO,SAAW,OAAO,WAAa,EAAO,OAAS,CAAC,CAAC,EAC7F,EAAO,IAAM,KAAK,IAAI,EAAG,KAAK,MAAM,OAAO,SAAW,OAAO,YAAc,EAAO,QAAU,CAAC,CAAC,GAEzF,OAAO,QAAQ,CAAM,CAAC,CAC1B,QAAQ,EAAG,KAAW,IAAU,IAAA,IAAa,IAAU,EAAK,CAAC,CAC7D,KAAK,CAAC,EAAK,KAAY,IAAU,GAAO,GAAG,EAAI,MAAQ,GAAG,EAAI,GAAG,GAAQ,CAAC,CAC1E,KAAK,GAAG,CACb,CAEA,SAAS,EAAqB,EAAmC,CAC/D,SAAS,EAAM,CAAE,YAAwB,CACvC,GAAM,CAAE,cAAA,EAAc,EAAA,qBAAA,CACpB,EAAM,UACN,EAAM,YACN,EAAM,iBACR,EACA,OAAO,GAAA,EAAY,EAAA,aAAA,CAAa,EAAU,CAAS,EAAI,IACzD,CAEA,MADA,GAAM,YAAc,oBACb,CACT,CAsBA,SAAgB,EAAe,EAAiC,CAAC,EAAmB,CAClF,GAAM,CAAC,IAAA,EAAS,EAAA,SAAA,KAAe,IAAI,CAAY,EACzC,CAAC,IAAA,EAAS,EAAA,SAAA,KAAe,EAAqB,CAAK,CAAC,EAEpD,GAAA,EAAa,EAAA,OAAA,CAAO,CAAO,EACjC,EAAW,QAAU,EAErB,IAAM,GAAA,EAAa,EAAA,OAAA,CAA4B,IAAI,EAE7C,GAAA,EAAQ,EAAA,qBAAA,CAAqB,EAAM,UAAW,EAAM,YAAa,EAAM,iBAAiB,EAIxF,GAAA,EAAa,EAAA,YAAA,CAChB,GAAwB,CACvB,GAAM,CAAE,eAAgB,EAAM,YAAY,EAC1C,EAAW,UAAU,EACrB,EAAW,QAAU,KAChB,IACD,CAAC,GAAc,CAAC,EAAY,QAAQ,EAAY,MAAM,EAC1D,EAAM,SAAS,CAAE,YAAa,KAAM,UAAW,IAAK,CAAC,EACrD,EAAW,QAAQ,UAAU,EAC/B,EACA,CAAC,CAAK,CACR,EAEM,GAAA,EAAO,EAAA,YAAA,KAAiC,CAC5C,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,IAAM,EAAU,EAAM,YAAY,EAClC,GAAI,EAAQ,aAAe,CAAC,EAAQ,YAAY,OAE9C,OADA,EAAQ,YAAY,MAAM,EACnB,EAAQ,YAGjB,IAAM,EAAO,EAAW,QAClB,EAAc,OAAO,KAAK,cAAe,EAAK,MAAQ,SAAU,EAAc,CAAI,CAAC,EACzF,GAAI,CAAC,EAGH,OAFA,EAAM,SAAS,CAAE,QAAS,EAAK,CAAC,EAChC,EAAK,YAAY,EACV,KAGT,IAAM,EAAM,EAAY,SACxB,EAAI,MAAQ,EAAK,OAAS,SAAS,MAEnC,IAAI,EACA,EAAK,aAAe,KACtB,EAAgB,EAAW,SAAU,CAAG,GAG1C,IAAM,EAAY,EAAI,cAAc,KAAK,EACzC,EAAU,aAAa,yBAA0B,EAAE,EACnD,EAAI,KAAK,YAAY,CAAS,EAE9B,IAAM,MAA4B,EAAW,EAAI,EAC3C,MAAwB,CAG5B,eAAiB,CACX,EAAY,QAAQ,EAAoB,CAC9C,EAAG,CAAC,CACN,EACA,EAAY,iBAAiB,WAAY,CAAe,EAGxD,IAAM,EAAY,OAAO,gBAAkB,CACrC,EAAY,QAAQ,EAAoB,CAC9C,EAAG,GAAG,EAEA,MAAyB,EAAY,MAAM,EAYjD,OAXA,OAAO,iBAAiB,WAAY,CAAgB,EAEpD,EAAW,YAAgB,CACzB,OAAO,cAAc,CAAS,EAC9B,OAAO,oBAAoB,WAAY,CAAgB,EACvD,EAAY,oBAAoB,WAAY,CAAe,EAC3D,IAAgB,CAClB,EAEA,EAAM,SAAS,CAAE,cAAa,YAAW,QAAS,EAAM,CAAC,EACzD,EAAK,SAAS,CAAW,EAClB,CACT,EAAG,CAAC,EAAO,CAAU,CAAC,EAEhB,GAAA,EAAQ,EAAA,YAAA,KAAkB,EAAW,EAAK,EAAG,CAAC,CAAU,CAAC,EAEzD,GAAA,EAAS,EAAA,YAAA,KAAkB,CAC3B,EAAM,YAAY,CAAC,CAAC,YACtB,EAAW,EAAK,EAEhB,EAAK,CAET,EAAG,CAAC,EAAO,EAAY,CAAI,CAAC,EAEtB,GAAA,EAAQ,EAAA,YAAA,KAAkB,CAC9B,GAAM,CAAE,eAAgB,EAAM,YAAY,EACtC,GAAe,CAAC,EAAY,QAAQ,EAAY,MAAM,CAC5D,EAAG,CAAC,CAAK,CAAC,EAQV,OAJA,EAAA,EAAA,UAAA,SACe,EAAW,EAAK,EAC5B,CAAC,CAAU,CAAC,EAER,CACL,OACA,QACA,SACA,QACA,OAAQ,EAAM,cAAgB,KAC9B,UAAW,EAAM,QACjB,YAAa,EAAM,YACnB,OACF,CACF"}
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":"iHAgBA,SAAgB,EAAW,EAAkB,EAAkB,EAAQ,GAAkB,CACvF,OAAO,EAAa,EAAQ,EAAQ,CAAK,CAAC,CAAC,IAC7C,CAGA,IAAM,EAAa,CAAC,MAAO,QAAS,QAAS,cAAe,iBAAkB,WAAW,EAazF,SAAgB,EAAa,EAAkB,EAAkB,EAAQ,GAAmB,CAC1F,IAAM,EAAU,IAAI,IAEd,EAAe,GAA2D,CAC9E,GAAI,EAAK,WAAa,KAAK,aAAc,MAAO,GAChD,IAAM,EAAK,EACX,OACE,EAAG,UAAY,SACd,EAAG,UAAY,SAAW,EAAG,aAAa,KAAK,GAAK,GAAA,CAAI,YAAY,CAAC,CAAC,SAAS,YAAY,CAEhG,EAEM,EAAkB,GAAiC,CACvD,IAAM,EAAQ,EAAG,MACjB,GAAI,EACF,GAAI,CACF,OAAO,MAAM,KAAK,EAAM,QAAQ,CAAC,CAC9B,IAAK,GAAS,EAAK,OAAO,CAAC,CAC3B,KAAK;CAAI,CACd,MAAQ,CAER,CAEF,OAAO,EAAG,aAAe,EAC3B,EAEM,EAAU,GAAiD,CAC/D,GAAI,EAAQ,IAAI,CAAE,EAAG,OACrB,IAAI,EACJ,GAAI,EAAG,UAAY,OAAQ,CACzB,IAAM,EAAO,EAAO,cAAc,MAAM,EAIxC,IAAK,IAAM,KAAQ,EAAY,CAC7B,IAAM,EAAQ,EAAG,aAAa,CAAI,EAC9B,IAAU,MAAM,EAAK,aAAa,EAAM,CAAK,CACnD,CAGA,EAAK,KAAQ,EAAuB,KACpC,EAAQ,CACV,KAAO,CACL,IAAM,EAAQ,EAAO,cAAc,OAAO,EAC1C,EAAM,YAAc,EAAe,CAAsB,EACzD,IAAM,EAAQ,EAAG,aAAa,OAAO,EACjC,GAAO,EAAM,aAAa,QAAS,CAAK,EAC5C,EAAQ,CACV,CACA,EAAO,KAAK,YAAY,CAAK,EAC7B,EAAQ,IAAI,EAAI,CAAK,CACvB,EAEM,EAAW,GAA+B,CAC9C,IAAM,EAAQ,EAAQ,IAAI,CAAE,EACxB,IAAO,EAAM,YAAc,EAAe,CAAE,EAClD,EAEM,EAAmB,GAAwB,CAC/C,IAAK,GAAM,CAAC,EAAK,KAAU,GACrB,IAAY,GAAQ,EAAQ,WAAa,KAAK,cAAgB,EAAQ,SAAS,CAAG,KACpF,EAAM,OAAO,EACb,EAAQ,OAAO,CAAG,EAGxB,EAEM,EAAa,CAAC,QAAS,OAAO,EAC9B,MAA4B,CAChC,IAAM,EAAiD,CACrD,CAAC,EAAO,gBAAiB,EAAO,eAAe,EAC/C,CAAC,EAAO,KAAM,EAAO,IAAI,CAC3B,EACA,IAAK,GAAM,CAAC,EAAM,KAAO,EACnB,MAAC,GAAQ,CAAC,GACd,KAAK,IAAM,KAAQ,EAAK,YAClB,EAAW,SAAS,EAAK,IAAI,GAAK,EAAK,KAAK,WAAW,OAAO,IAChE,EAAG,aAAa,EAAK,KAAM,EAAK,KAAK,EAGzC,IAAK,IAAM,KAAQ,MAAM,KAAK,EAAG,UAAU,GAEtC,EAAW,SAAS,EAAK,IAAI,GAAK,EAAK,KAAK,WAAW,OAAO,IAC/D,CAAC,EAAK,aAAa,EAAK,IAAI,GAE5B,EAAG,gBAAgB,EAAK,IAAI,CARS,CAY7C,EAEM,MAAgC,CACpC,IAAM,EAAe,EAAO,YACxB,MAAC,GAAc,eAAiB,EAAE,uBAAwB,IAC9D,GAAI,CACF,EAAO,mBAAqB,EAAO,mBAAmB,IAAK,GAAU,CACnE,IAAM,EAAO,IAAI,EAAa,cAM9B,OALA,EAAK,YACH,MAAM,KAAK,EAAM,QAAQ,CAAC,CACvB,IAAK,GAAS,EAAK,OAAO,CAAC,CAC3B,KAAK;CAAI,CACd,EACO,CACT,CAAC,CACH,MAAQ,CAER,CACF,EAEA,IAAK,IAAM,KAAM,EAAO,iBAAiB,kCAAkC,EACzE,EAAO,CAAwC,EAEjD,IAAM,EAAQ,MAAM,KAAK,EAAQ,OAAO,CAAC,CAAC,CAAC,OACxC,GAAoC,EAAM,UAAY,MACzD,EAIA,GAHA,EAAc,EACd,EAAkB,EAEd,CAAC,GAAS,OAAO,iBAAqB,IACxC,MAAO,CAAE,SAAY,CAAC,EAAG,OAAM,EAGjC,IAAM,EAAe,IAAI,iBAAkB,GAAY,CACrD,IAAK,IAAM,KAAU,EAAS,CAC5B,GAAI,EAAO,OAAS,gBAAiB,CACnC,IAAM,EAAS,EAAO,OAAO,cACzB,GAAQ,UAAY,SAAS,EAAQ,CAA0B,EACnE,QACF,CAEI,EAAY,EAAO,MAAM,GAAK,EAAO,OAAO,UAAY,SAE1D,EAAQ,EAAO,MAA0B,EAE3C,IAAK,IAAM,KAAS,EAAO,WACzB,GAAI,EAAY,CAAK,EACnB,EAAO,CAAK,OACP,GAAI,EAAM,WAAa,KAAK,aACjC,IAAK,IAAM,KAAO,EAAkB,iBAAiB,kCAAkC,EACrF,EAAO,CAAwC,EAIrD,IAAK,IAAM,KAAW,EAAO,aAC3B,EAAgB,CAAO,CAE3B,CACF,CAAC,EACD,EAAa,QAAQ,EAAO,KAAM,CAAE,UAAW,GAAM,QAAS,GAAM,cAAe,EAAK,CAAC,EAEzF,IAAM,EAAe,IAAI,qBAAuB,EAAc,CAAC,EAI/D,OAHA,EAAa,QAAQ,EAAO,gBAAiB,CAAE,WAAY,EAAK,CAAC,EAC7D,EAAO,MAAM,EAAa,QAAQ,EAAO,KAAM,CAAE,WAAY,EAAK,CAAC,EAEhE,CACL,SAAY,CACV,EAAa,WAAW,EACxB,EAAa,WAAW,CAC1B,EACA,OACF,CACF,CCxLA,SAAgB,EACd,EACA,EACA,EACY,CACZ,IAAM,EAAU,EAAM,OAAQ,GAAS,EAAK,QAAU,MAAQ,EAAQ,CAAI,CAAC,EAC3E,GAAI,EAAQ,SAAW,EAErB,OADA,EAAS,MACI,CAAC,EAGhB,IAAI,EAAY,EAAQ,OAClB,MAAe,CACnB,IACI,IAAc,GAAG,EAAO,CAC9B,EACM,MAAa,CACjB,OAAO,aAAa,CAAO,EAC3B,IAAK,IAAM,KAAQ,EACjB,EAAK,oBAAoB,OAAQ,CAAM,EACvC,EAAK,oBAAoB,QAAS,CAAM,CAE5C,EACM,MAAe,CACnB,EAAK,EACL,EAAS,CACX,EAEA,IAAK,IAAM,KAAQ,EACjB,EAAK,iBAAiB,OAAQ,CAAM,EAEpC,EAAK,iBAAiB,QAAS,CAAM,EAEvC,IAAM,EAAU,OAAO,WAAW,EAAQ,CAAS,EACnD,OAAO,CACT,CAEA,SAAS,EAAQ,EAAgC,CAC/C,GAAI,iBAAiB,KAAK,EAAK,GAAG,EAAG,MAAO,GAC5C,IAAM,EAAO,EAAK,cAAc,YAChC,MAAO,CAAC,EAAK,OAAS,CAAC,GAAM,YAAc,EAAK,WAAW,EAAK,KAAK,CAAC,CAAC,OACzE,CClCA,IAAM,EAA4B,CAAE,YAAa,KAAM,UAAW,KAAM,QAAS,EAAM,EAEjF,EAAN,KAAiB,CACf,MAAoB,EACpB,UAAoB,IAAI,IAExB,UAAa,IACX,KAAK,UAAU,IAAI,CAAQ,MACd,KAAK,UAAU,OAAO,CAAQ,GAG7C,gBAAgC,KAAK,MAErC,sBAAsC,EAEtC,SAAS,EAAoC,CAC3C,KAAK,MAAQ,CAAE,GAAG,KAAK,MAAO,GAAG,CAAQ,EACzC,IAAK,IAAM,KAAY,KAAK,UAAW,EAAS,CAClD,CACF,EAEM,EAAwC,CAAE,MAAO,GAAM,MAAO,IAAK,OAAQ,GAAI,EAErF,SAAS,EAAc,EAAwC,CAC7D,IAAM,EAA8B,CAAE,GAAG,EAAkB,GAAG,EAAQ,QAAS,EAW/E,OATE,EAAQ,SAAW,IACnB,EAAO,OAAS,IAAA,IAChB,EAAO,MAAQ,IAAA,IACf,OAAO,EAAO,OAAU,UACxB,OAAO,EAAO,QAAW,WAEzB,EAAO,KAAO,KAAK,IAAI,EAAG,KAAK,MAAM,OAAO,SAAW,OAAO,WAAa,EAAO,OAAS,CAAC,CAAC,EAC7F,EAAO,IAAM,KAAK,IAAI,EAAG,KAAK,MAAM,OAAO,SAAW,OAAO,YAAc,EAAO,QAAU,CAAC,CAAC,GAEzF,OAAO,QAAQ,CAAM,CAAC,CAC1B,QAAQ,EAAG,KAAW,IAAU,IAAA,IAAa,IAAU,EAAK,CAAC,CAC7D,KAAK,CAAC,EAAK,KAAY,IAAU,GAAO,GAAG,EAAI,MAAQ,GAAG,EAAI,GAAG,GAAQ,CAAC,CAC1E,KAAK,GAAG,CACb,CAEA,IAAM,EAAc,cAEd,EAAe,GAEf,EAAoB,IAO1B,SAAS,EAAe,EAAsC,CAC5D,IAAM,EAAM,EAAY,SAExB,OADI,EAAI,MAAQ,GAAe,EAAI,aAAe,UAAkB,KAC7D,CACT,CAEA,SAAS,EAAqB,EAAmC,CAC/D,SAAS,EAAM,CAAE,YAAwB,CACvC,GAAM,CAAE,cAAA,EAAc,EAAA,qBAAA,CACpB,EAAM,UACN,EAAM,YACN,EAAM,iBACR,EACA,OAAO,GAAA,EAAY,EAAA,aAAA,CAAa,EAAU,CAAS,EAAI,IACzD,CAEA,MADA,GAAM,YAAc,oBACb,CACT,CAsBA,SAAgB,EAAe,EAAiC,CAAC,EAAmB,CAClF,GAAM,CAAC,IAAA,EAAS,EAAA,SAAA,KAAe,IAAI,CAAY,EACzC,CAAC,IAAA,EAAS,EAAA,SAAA,KAAe,EAAqB,CAAK,CAAC,EAEpD,GAAA,EAAa,EAAA,OAAA,CAAO,CAAO,EACjC,EAAW,QAAU,EAErB,IAAM,GAAA,EAAa,EAAA,OAAA,CAA4B,IAAI,EAE7C,GAAA,EAAQ,EAAA,qBAAA,CAAqB,EAAM,UAAW,EAAM,YAAa,EAAM,iBAAiB,EAIxF,GAAA,EAAa,EAAA,YAAA,CAChB,GAAwB,CACvB,GAAM,CAAE,eAAgB,EAAM,YAAY,EAC1C,EAAW,UAAU,EACrB,EAAW,QAAU,KAChB,IACD,CAAC,GAAc,CAAC,EAAY,QAAQ,EAAY,MAAM,EAC1D,EAAM,SAAS,CAAE,YAAa,KAAM,UAAW,IAAK,CAAC,EACrD,EAAW,QAAQ,UAAU,EAC/B,EACA,CAAC,CAAK,CACR,EAEM,GAAA,EAAO,EAAA,YAAA,KAAiC,CAC5C,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,IAAM,EAAU,EAAM,YAAY,EAClC,GAAI,EAAQ,aAAe,CAAC,EAAQ,YAAY,OAE9C,OADA,EAAQ,YAAY,MAAM,EACnB,EAAQ,YAGjB,IAAM,EAAO,EAAW,QAClB,EAAM,EAAK,KAAO,EAClB,EAAc,OAAO,KAAK,EAAK,EAAK,MAAQ,SAAU,EAAc,CAAI,CAAC,EAC/E,GAAI,CAAC,EAGH,OAFA,EAAM,SAAS,CAAE,QAAS,EAAK,CAAC,EAChC,EAAK,YAAY,EACV,KAQT,IAAI,EACJ,GAAI,CACF,EAAa,EAAY,QAC3B,MAAQ,CAIN,OAHA,EAAY,MAAM,EAClB,EAAM,SAAS,CAAE,QAAS,EAAK,CAAC,EAChC,EAAK,YAAY,EACV,IACT,CAEA,IAAM,MAA4B,EAAW,EAAI,EAC3C,MAAwB,CAG5B,eAAiB,CACX,EAAY,QAAQ,EAAoB,CAC9C,EAAG,CAAC,CACN,EAGM,EAAY,OAAO,gBAAkB,CACrC,EAAY,QAAQ,EAAoB,CAC9C,EAAG,GAAG,EAEA,MAAyB,EAAY,MAAM,EACjD,OAAO,iBAAiB,WAAY,CAAgB,EAEpD,IAAI,EACA,EAAW,GACX,EACA,EAEJ,EAAW,YAAgB,CACzB,OAAO,cAAc,CAAS,EAC9B,OAAO,cAAc,CAAQ,EAC7B,OAAO,oBAAoB,WAAY,CAAgB,EACnD,GAAU,EAAY,oBAAoB,WAAY,CAAe,EACzE,IAAgB,EAChB,IAAiB,CACnB,EAIA,IAAM,EAAW,GAAkB,CACjC,EAAW,GACX,EAAY,iBAAiB,WAAY,CAAe,EACxD,EAAI,MAAQ,EAAK,OAAS,SAAS,MACnC,IAAI,EAA2B,CAAC,EAChC,GAAI,EAAK,aAAe,GAAO,CAC7B,IAAM,EAAS,EAAa,SAAU,CAAG,EACzC,EAAgB,EAAO,KACvB,EAAQ,EAAO,KACjB,CACA,IAAM,EAAY,EAAI,cAAc,KAAK,EACzC,EAAU,aAAa,yBAA0B,EAAE,EACnD,EAAI,KAAK,YAAY,CAAS,EAG9B,EAAiB,EAAoB,EAAO,MAAyB,CACnE,EAAM,SAAS,CAAE,WAAU,CAAC,EAC5B,EAAK,SAAS,CAAW,CAC3B,CAAC,CACH,EA+BA,OA7BA,EAAM,SAAS,CAAE,cAAa,UAAW,KAAM,QAAS,EAAM,CAAC,EAE3D,IAAQ,EACV,EAAQ,CAAU,EAIlB,EAAW,OAAO,gBAAkB,CAClC,GAAI,EAAY,OAAQ,CACtB,EAAoB,EACpB,MACF,CACA,IAAI,EACJ,GAAI,CACF,EAAM,EAAe,CAAW,CAClC,MAAQ,CAGN,EAAW,EAAK,EAChB,EAAM,SAAS,CAAE,QAAS,EAAK,CAAC,EAChC,EAAK,YAAY,EACjB,MACF,CACK,IACL,OAAO,cAAc,CAAQ,EAC7B,EAAQ,CAAG,EACb,EAAG,CAAY,EAGV,CACT,EAAG,CAAC,EAAO,CAAU,CAAC,EAEhB,GAAA,EAAQ,EAAA,YAAA,KAAkB,EAAW,EAAK,EAAG,CAAC,CAAU,CAAC,EAEzD,GAAA,EAAS,EAAA,YAAA,KAAkB,CAC3B,EAAM,YAAY,CAAC,CAAC,YACtB,EAAW,EAAK,EAEhB,EAAK,CAET,EAAG,CAAC,EAAO,EAAY,CAAI,CAAC,EAEtB,GAAA,EAAQ,EAAA,YAAA,KAAkB,CAC9B,GAAM,CAAE,eAAgB,EAAM,YAAY,EACtC,GAAe,CAAC,EAAY,QAAQ,EAAY,MAAM,CAC5D,EAAG,CAAC,CAAK,CAAC,EAQV,OAJA,EAAA,EAAA,UAAA,SACe,EAAW,EAAK,EAC5B,CAAC,CAAU,CAAC,EAER,CACL,OACA,QACA,SACA,QACA,OAAQ,EAAM,cAAgB,KAC9B,UAAW,EAAM,QACjB,YAAa,EAAM,YACnB,OACF,CACF"}
package/dist/index.js CHANGED
@@ -2,6 +2,17 @@ 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;
6
+ }
7
+ var s = [
8
+ "rel",
9
+ "media",
10
+ "title",
11
+ "crossorigin",
12
+ "referrerpolicy",
13
+ "integrity"
14
+ ];
15
+ function c(e, t, n = !0) {
5
16
  let r = /* @__PURE__ */ new Map(), i = (e) => {
6
17
  if (e.nodeType !== Node.ELEMENT_NODE) return !1;
7
18
  let t = e;
@@ -17,9 +28,11 @@ function o(e, t, n = !0) {
17
28
  let n;
18
29
  if (e.tagName === "LINK") {
19
30
  let r = t.createElement("link");
20
- r.rel = "stylesheet", r.href = e.href;
21
- let i = e.getAttribute("media");
22
- i && (r.media = i), n = r;
31
+ for (let t of s) {
32
+ let n = e.getAttribute(t);
33
+ n !== null && r.setAttribute(t, n);
34
+ }
35
+ r.href = e.href, n = r;
23
36
  } else {
24
37
  let r = t.createElement("style");
25
38
  r.textContent = a(e);
@@ -27,18 +40,18 @@ function o(e, t, n = !0) {
27
40
  i && r.setAttribute("media", i), n = r;
28
41
  }
29
42
  t.head.appendChild(n), r.set(e, n);
30
- }, s = (e) => {
43
+ }, c = (e) => {
31
44
  let t = r.get(e);
32
45
  t && (t.textContent = a(e));
33
- }, c = (e) => {
46
+ }, l = (e) => {
34
47
  for (let [t, n] of r) (e === t || e.nodeType === Node.ELEMENT_NODE && e.contains(t)) && (n.remove(), r.delete(t));
35
- }, l = ["class", "style"], u = () => {
48
+ }, u = ["class", "style"], d = () => {
36
49
  let n = [[e.documentElement, t.documentElement], [e.body, t.body]];
37
50
  for (let [e, t] of n) if (!(!e || !t)) {
38
- for (let n of e.attributes) (l.includes(n.name) || n.name.startsWith("data-")) && t.setAttribute(n.name, n.value);
39
- for (let n of Array.from(t.attributes)) (l.includes(n.name) || n.name.startsWith("data-")) && !e.hasAttribute(n.name) && t.removeAttribute(n.name);
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);
40
53
  }
41
- }, d = () => {
54
+ }, f = () => {
42
55
  let n = t.defaultView;
43
56
  if (!(!n?.CSSStyleSheet || !("adoptedStyleSheets" in e))) try {
44
57
  t.adoptedStyleSheets = e.adoptedStyleSheets.map((e) => {
@@ -48,42 +61,71 @@ function o(e, t, n = !0) {
48
61
  } catch {}
49
62
  };
50
63
  for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) o(t);
51
- if (u(), d(), !n || typeof MutationObserver > "u") return () => {};
52
- let f = new MutationObserver((e) => {
64
+ let p = Array.from(r.values()).filter((e) => e.tagName === "LINK");
65
+ if (d(), f(), !n || typeof MutationObserver > "u") return {
66
+ stop: () => {},
67
+ links: p
68
+ };
69
+ let m = new MutationObserver((e) => {
53
70
  for (let t of e) {
54
71
  if (t.type === "characterData") {
55
72
  let e = t.target.parentElement;
56
- e?.tagName === "STYLE" && s(e);
73
+ e?.tagName === "STYLE" && c(e);
57
74
  continue;
58
75
  }
59
- i(t.target) && t.target.tagName === "STYLE" && s(t.target);
76
+ i(t.target) && t.target.tagName === "STYLE" && c(t.target);
60
77
  for (let e of t.addedNodes) if (i(e)) o(e);
61
78
  else if (e.nodeType === Node.ELEMENT_NODE) for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) o(t);
62
- for (let e of t.removedNodes) c(e);
79
+ for (let e of t.removedNodes) l(e);
63
80
  }
64
81
  });
65
- f.observe(e.head, {
82
+ m.observe(e.head, {
66
83
  childList: !0,
67
84
  subtree: !0,
68
85
  characterData: !0
69
86
  });
70
- let p = new MutationObserver(() => u());
71
- return p.observe(e.documentElement, { attributes: !0 }), e.body && p.observe(e.body, { attributes: !0 }), () => {
72
- f.disconnect(), p.disconnect();
87
+ let h = new MutationObserver(() => d());
88
+ return h.observe(e.documentElement, { attributes: !0 }), e.body && h.observe(e.body, { attributes: !0 }), {
89
+ stop: () => {
90
+ m.disconnect(), h.disconnect();
91
+ },
92
+ links: p
73
93
  };
74
94
  }
75
95
  //#endregion
96
+ //#region src/whenStylesheetsLoad.ts
97
+ function l(e, t, n) {
98
+ let r = e.filter((e) => e.sheet === null && u(e));
99
+ if (r.length === 0) return n(), () => {};
100
+ let i = r.length, a = () => {
101
+ i--, i === 0 && s();
102
+ }, o = () => {
103
+ window.clearTimeout(c);
104
+ for (let e of r) e.removeEventListener("load", a), e.removeEventListener("error", a);
105
+ }, s = () => {
106
+ o(), n();
107
+ };
108
+ for (let e of r) e.addEventListener("load", a), e.addEventListener("error", a);
109
+ let c = window.setTimeout(s, t);
110
+ return o;
111
+ }
112
+ function u(e) {
113
+ if (/\balternate\b/i.test(e.rel)) return !1;
114
+ let t = e.ownerDocument.defaultView;
115
+ return !e.media || !t?.matchMedia || t.matchMedia(e.media).matches;
116
+ }
117
+ //#endregion
76
118
  //#region src/usePopupWindow.ts
77
- var s = {
119
+ var d = {
78
120
  popupWindow: null,
79
121
  container: null,
80
122
  blocked: !1
81
- }, c = class {
82
- state = s;
123
+ }, f = class {
124
+ state = d;
83
125
  listeners = /* @__PURE__ */ new Set();
84
126
  subscribe = (e) => (this.listeners.add(e), () => this.listeners.delete(e));
85
127
  getSnapshot = () => this.state;
86
- getServerSnapshot = () => s;
128
+ getServerSnapshot = () => d;
87
129
  setState(e) {
88
130
  this.state = {
89
131
  ...this.state,
@@ -91,84 +133,115 @@ var s = {
91
133
  };
92
134
  for (let e of this.listeners) e();
93
135
  }
94
- }, l = {
136
+ }, p = {
95
137
  popup: !0,
96
138
  width: 640,
97
139
  height: 480
98
140
  };
99
- function u(e) {
141
+ function m(e) {
100
142
  let t = {
101
- ...l,
143
+ ...p,
102
144
  ...e.features
103
145
  };
104
146
  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(",");
105
147
  }
106
- function d(e) {
148
+ var h = "about:blank", g = 20, _ = 3e3;
149
+ function v(e) {
150
+ let t = e.document;
151
+ return t.URL === h || t.readyState === "loading" ? null : t;
152
+ }
153
+ function y(e) {
107
154
  function t({ children: t }) {
108
155
  let { container: n } = i(e.subscribe, e.getSnapshot, e.getServerSnapshot);
109
156
  return n ? a(t, n) : null;
110
157
  }
111
158
  return t.displayName = "PopupWindow.Popup", t;
112
159
  }
113
- function f(a = {}) {
114
- let [s] = r(() => new c()), [l] = r(() => d(s)), f = n(a);
115
- f.current = a;
116
- let p = n(null), m = i(s.subscribe, s.getSnapshot, s.getServerSnapshot), h = e((e) => {
117
- let { popupWindow: t } = s.getSnapshot();
118
- p.current?.(), p.current = null, t && (!e && !t.closed && t.close(), s.setState({
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) => {
164
+ let { popupWindow: t } = o.getSnapshot();
165
+ d.current?.(), d.current = null, t && (!e && !t.closed && t.close(), o.setState({
119
166
  popupWindow: null,
120
167
  container: null
121
- }), f.current.onClose?.());
122
- }, [s]), g = e(() => {
168
+ }), u.current.onClose?.());
169
+ }, [o]), x = e(() => {
123
170
  if (typeof window > "u") return null;
124
- let e = s.getSnapshot();
171
+ let e = o.getSnapshot();
125
172
  if (e.popupWindow && !e.popupWindow.closed) return e.popupWindow.focus(), e.popupWindow;
126
- let t = f.current, n = window.open("about:blank", t.name ?? "_blank", u(t));
127
- if (!n) return s.setState({ blocked: !0 }), t.onBlocked?.(), null;
128
- let r = n.document;
129
- r.title = t.title ?? document.title;
173
+ let t = u.current, n = t.url ?? h, r = window.open(n, t.name ?? "_blank", m(t));
174
+ if (!r) return o.setState({ blocked: !0 }), t.onBlocked?.(), null;
130
175
  let i;
131
- t.copyStyles !== !1 && (i = o(document, r));
132
- let a = r.createElement("div");
133
- a.setAttribute("data-popup-window-root", ""), r.body.appendChild(a);
134
- let c = () => h(!0), l = () => {
176
+ try {
177
+ i = r.document;
178
+ } catch {
179
+ return r.close(), o.setState({ blocked: !0 }), t.onBlocked?.(), null;
180
+ }
181
+ let a = () => b(!0), s = () => {
135
182
  setTimeout(() => {
136
- n.closed && c();
183
+ r.closed && a();
137
184
  }, 0);
185
+ }, f = window.setInterval(() => {
186
+ 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?.();
138
192
  };
139
- n.addEventListener("pagehide", l);
140
- let d = window.setInterval(() => {
141
- n.closed && c();
142
- }, 250), m = () => n.close();
143
- return window.addEventListener("pagehide", m), p.current = () => {
144
- window.clearInterval(d), window.removeEventListener("pagehide", m), n.removeEventListener("pagehide", l), i?.();
145
- }, s.setState({
146
- popupWindow: n,
147
- container: a,
193
+ let w = (e) => {
194
+ x = !0, r.addEventListener("pagehide", s), e.title = t.title ?? document.title;
195
+ let n = [];
196
+ if (t.copyStyles !== !1) {
197
+ let t = c(document, e);
198
+ S = t.stop, n = t.links;
199
+ }
200
+ let i = e.createElement("div");
201
+ i.setAttribute("data-popup-window-root", ""), e.body.appendChild(i), C = l(n, _, () => {
202
+ o.setState({ container: i }), t.onOpen?.(r);
203
+ });
204
+ };
205
+ return o.setState({
206
+ popupWindow: r,
207
+ container: null,
148
208
  blocked: !1
149
- }), t.onOpen?.(n), n;
150
- }, [s, h]), _ = e(() => h(!1), [h]), v = e(() => {
151
- s.getSnapshot().popupWindow ? h(!1) : g();
209
+ }), n === h ? w(i) : y = window.setInterval(() => {
210
+ if (r.closed) {
211
+ a();
212
+ return;
213
+ }
214
+ let e;
215
+ try {
216
+ e = v(r);
217
+ } catch {
218
+ b(!1), o.setState({ blocked: !0 }), t.onBlocked?.();
219
+ return;
220
+ }
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();
152
225
  }, [
153
- s,
154
- h,
155
- g
156
- ]), y = e(() => {
157
- let { popupWindow: e } = s.getSnapshot();
226
+ o,
227
+ b,
228
+ x
229
+ ]), w = e(() => {
230
+ let { popupWindow: e } = o.getSnapshot();
158
231
  e && !e.closed && e.focus();
159
- }, [s]);
160
- return t(() => () => h(!1), [h]), {
161
- open: g,
162
- close: _,
163
- toggle: v,
164
- focus: y,
165
- isOpen: m.popupWindow !== null,
166
- isBlocked: m.blocked,
167
- popupWindow: m.popupWindow,
168
- Popup: l
232
+ }, [o]);
233
+ return t(() => () => b(!1), [b]), {
234
+ open: x,
235
+ close: S,
236
+ toggle: C,
237
+ focus: w,
238
+ isOpen: p.popupWindow !== null,
239
+ isBlocked: p.blocked,
240
+ popupWindow: p.popupWindow,
241
+ Popup: s
169
242
  };
170
243
  }
171
244
  //#endregion
172
- export { o as copyStyles, f as usePopupWindow };
245
+ export { o as copyStyles, b as usePopupWindow };
173
246
 
174
247
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/copyStyles.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\r\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\r\n * in sync while the popup is open:\r\n *\r\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\r\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\r\n * possible, so rules injected via `insertRule` (CSS-in-JS \"speedy\" mode)\r\n * are included.\r\n * - Additions/removals/edits of style nodes in the source `<head>` are\r\n * observed (covers Vite HMR, lazily loaded chunk CSS, styled-components).\r\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\r\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\r\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\r\n *\r\n * Returns a function that stops observing.\r\n */\r\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\r\n const mirrors = new Map<Element, Element>()\r\n\r\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\r\n if (node.nodeType !== Node.ELEMENT_NODE) return false\r\n const el = node as Element\r\n return (\r\n el.tagName === 'STYLE' ||\r\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\r\n )\r\n }\r\n\r\n const serializeStyle = (el: HTMLStyleElement): string => {\r\n const sheet = el.sheet\r\n if (sheet) {\r\n try {\r\n return Array.from(sheet.cssRules)\r\n .map((rule) => rule.cssText)\r\n .join('\\n')\r\n } catch {\r\n // Inaccessible cssRules — fall back to the raw text below.\r\n }\r\n }\r\n return el.textContent ?? ''\r\n }\r\n\r\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\r\n if (mirrors.has(el)) return\r\n let clone: Element\r\n if (el.tagName === 'LINK') {\r\n const link = target.createElement('link')\r\n link.rel = 'stylesheet'\r\n // .href resolves to an absolute URL, so relative hrefs keep working\r\n // from the popup's `about:blank` document.\r\n link.href = (el as HTMLLinkElement).href\r\n const media = el.getAttribute('media')\r\n if (media) link.media = media\r\n clone = link\r\n } else {\r\n const style = target.createElement('style')\r\n style.textContent = serializeStyle(el as HTMLStyleElement)\r\n const media = el.getAttribute('media')\r\n if (media) style.setAttribute('media', media)\r\n clone = style\r\n }\r\n target.head.appendChild(clone)\r\n mirrors.set(el, clone)\r\n }\r\n\r\n const refresh = (el: HTMLStyleElement): void => {\r\n const clone = mirrors.get(el)\r\n if (clone) clone.textContent = serializeStyle(el)\r\n }\r\n\r\n const unmirrorSubtree = (removed: Node): void => {\r\n for (const [src, clone] of mirrors) {\r\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\r\n clone.remove()\r\n mirrors.delete(src)\r\n }\r\n }\r\n }\r\n\r\n const ROOT_ATTRS = ['class', 'style']\r\n const syncRootAttrs = (): void => {\r\n const pairs: Array<[Element | null, Element | null]> = [\r\n [source.documentElement, target.documentElement],\r\n [source.body, target.body],\r\n ]\r\n for (const [from, to] of pairs) {\r\n if (!from || !to) continue\r\n for (const attr of from.attributes) {\r\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\r\n to.setAttribute(attr.name, attr.value)\r\n }\r\n }\r\n for (const attr of Array.from(to.attributes)) {\r\n if (\r\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\r\n !from.hasAttribute(attr.name)\r\n ) {\r\n to.removeAttribute(attr.name)\r\n }\r\n }\r\n }\r\n }\r\n\r\n const syncAdoptedSheets = (): void => {\r\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\r\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\r\n try {\r\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\r\n const copy = new targetWindow.CSSStyleSheet!()\r\n copy.replaceSync(\r\n Array.from(sheet.cssRules)\r\n .map((rule) => rule.cssText)\r\n .join('\\n'),\r\n )\r\n return copy\r\n })\r\n } catch {\r\n // Best effort — older browsers or cross-origin rules.\r\n }\r\n }\r\n\r\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\r\n mirror(el as HTMLStyleElement | HTMLLinkElement)\r\n }\r\n syncRootAttrs()\r\n syncAdoptedSheets()\r\n\r\n if (!watch || typeof MutationObserver === 'undefined') {\r\n return () => {}\r\n }\r\n\r\n const headObserver = new MutationObserver((records) => {\r\n for (const record of records) {\r\n if (record.type === 'characterData') {\r\n const parent = record.target.parentElement\r\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\r\n continue\r\n }\r\n // childList\r\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\r\n // textContent assignment replaces the text node → childList on <style>\r\n refresh(record.target as HTMLStyleElement)\r\n }\r\n for (const added of record.addedNodes) {\r\n if (isStyleNode(added)) {\r\n mirror(added)\r\n } else if (added.nodeType === Node.ELEMENT_NODE) {\r\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\r\n mirror(el as HTMLStyleElement | HTMLLinkElement)\r\n }\r\n }\r\n }\r\n for (const removed of record.removedNodes) {\r\n unmirrorSubtree(removed)\r\n }\r\n }\r\n })\r\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\r\n\r\n const rootObserver = new MutationObserver(() => syncRootAttrs())\r\n rootObserver.observe(source.documentElement, { attributes: true })\r\n if (source.body) rootObserver.observe(source.body, { attributes: true })\r\n\r\n return () => {\r\n headObserver.disconnect()\r\n rootObserver.disconnect()\r\n }\r\n}\r\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\r\nimport type { FC } from 'react'\r\nimport { createPortal } from 'react-dom'\r\nimport { copyStyles } from './copyStyles'\r\nimport type {\r\n PopupProps,\r\n PopupWindowApi,\r\n PopupWindowFeatures,\r\n UsePopupWindowOptions,\r\n} from './types'\r\n\r\ninterface PopupState {\r\n popupWindow: Window | null\r\n container: HTMLElement | null\r\n blocked: boolean\r\n}\r\n\r\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\r\n\r\nclass PopupStore {\r\n state: PopupState = INITIAL_STATE\r\n private listeners = new Set<() => void>()\r\n\r\n subscribe = (listener: () => void): (() => void) => {\r\n this.listeners.add(listener)\r\n return () => this.listeners.delete(listener)\r\n }\r\n\r\n getSnapshot = (): PopupState => this.state\r\n\r\n getServerSnapshot = (): PopupState => INITIAL_STATE\r\n\r\n setState(partial: Partial<PopupState>): void {\r\n this.state = { ...this.state, ...partial }\r\n for (const listener of this.listeners) listener()\r\n }\r\n}\r\n\r\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\r\n\r\nfunction buildFeatures(options: UsePopupWindowOptions): string {\r\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\r\n if (\r\n options.center !== false &&\r\n merged.left === undefined &&\r\n merged.top === undefined &&\r\n typeof merged.width === 'number' &&\r\n typeof merged.height === 'number'\r\n ) {\r\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\r\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\r\n }\r\n return Object.entries(merged)\r\n .filter(([, value]) => value !== undefined && value !== false)\r\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\r\n .join(',')\r\n}\r\n\r\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\r\n function Popup({ children }: PopupProps) {\r\n const { container } = useSyncExternalStore(\r\n store.subscribe,\r\n store.getSnapshot,\r\n store.getServerSnapshot,\r\n )\r\n return container ? createPortal(children, container) : null\r\n }\r\n Popup.displayName = 'PopupWindow.Popup'\r\n return Popup\r\n}\r\n\r\n/**\r\n * Open part of your React tree in a separate browser window.\r\n *\r\n * The popup content is rendered with a portal into the popup's document, so\r\n * it stays part of your component tree: state, context and event handlers\r\n * all keep working across windows.\r\n *\r\n * ```tsx\r\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\r\n *\r\n * return (\r\n * <>\r\n * <button onClick={open}>Open panel</button>\r\n * <Popup>\r\n * <MyPanel />\r\n * </Popup>\r\n * </>\r\n * )\r\n * ```\r\n */\r\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\r\n const [store] = useState(() => new PopupStore())\r\n const [Popup] = useState(() => createPopupComponent(store))\r\n\r\n const optionsRef = useRef(options)\r\n optionsRef.current = options\r\n\r\n const cleanupRef = useRef<(() => void) | null>(null)\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\r\n\r\n // `userClosed` is true when the window was closed outside our control\r\n // (user hit the close button, opener unloaded) — then we must not touch it.\r\n const closePopup = useCallback(\r\n (userClosed: boolean) => {\r\n const { popupWindow } = store.getSnapshot()\r\n cleanupRef.current?.()\r\n cleanupRef.current = null\r\n if (!popupWindow) return\r\n if (!userClosed && !popupWindow.closed) popupWindow.close()\r\n store.setState({ popupWindow: null, container: null })\r\n optionsRef.current.onClose?.()\r\n },\r\n [store],\r\n )\r\n\r\n const open = useCallback((): Window | null => {\r\n if (typeof window === 'undefined') return null\r\n const current = store.getSnapshot()\r\n if (current.popupWindow && !current.popupWindow.closed) {\r\n current.popupWindow.focus()\r\n return current.popupWindow\r\n }\r\n\r\n const opts = optionsRef.current\r\n const popupWindow = window.open('about:blank', opts.name ?? '_blank', buildFeatures(opts))\r\n if (!popupWindow) {\r\n store.setState({ blocked: true })\r\n opts.onBlocked?.()\r\n return null\r\n }\r\n\r\n const doc = popupWindow.document\r\n doc.title = opts.title ?? document.title\r\n\r\n let stopStyleSync: (() => void) | undefined\r\n if (opts.copyStyles !== false) {\r\n stopStyleSync = copyStyles(document, doc)\r\n }\r\n\r\n const container = doc.createElement('div')\r\n container.setAttribute('data-popup-window-root', '')\r\n doc.body.appendChild(container)\r\n\r\n const handleExternalClose = () => closePopup(true)\r\n const onPopupPagehide = () => {\r\n // pagehide also fires on navigation; only treat it as a close when the\r\n // window really is gone a tick later.\r\n setTimeout(() => {\r\n if (popupWindow.closed) handleExternalClose()\r\n }, 0)\r\n }\r\n popupWindow.addEventListener('pagehide', onPopupPagehide)\r\n\r\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\r\n const closePoll = window.setInterval(() => {\r\n if (popupWindow.closed) handleExternalClose()\r\n }, 250)\r\n\r\n const onOpenerPagehide = () => popupWindow.close()\r\n window.addEventListener('pagehide', onOpenerPagehide)\r\n\r\n cleanupRef.current = () => {\r\n window.clearInterval(closePoll)\r\n window.removeEventListener('pagehide', onOpenerPagehide)\r\n popupWindow.removeEventListener('pagehide', onPopupPagehide)\r\n stopStyleSync?.()\r\n }\r\n\r\n store.setState({ popupWindow, container, blocked: false })\r\n opts.onOpen?.(popupWindow)\r\n return popupWindow\r\n }, [store, closePopup])\r\n\r\n const close = useCallback(() => closePopup(false), [closePopup])\r\n\r\n const toggle = useCallback(() => {\r\n if (store.getSnapshot().popupWindow) {\r\n closePopup(false)\r\n } else {\r\n open()\r\n }\r\n }, [store, closePopup, open])\r\n\r\n const focus = useCallback(() => {\r\n const { popupWindow } = store.getSnapshot()\r\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\r\n }, [store])\r\n\r\n // Close the popup when the owning component unmounts — its portal content\r\n // would unmount anyway, leaving an empty window behind.\r\n useEffect(() => {\r\n return () => closePopup(false)\r\n }, [closePopup])\r\n\r\n return {\r\n open,\r\n close,\r\n toggle,\r\n focus,\r\n isOpen: state.popupWindow !== null,\r\n isBlocked: state.blocked,\r\n popupWindow: state.popupWindow,\r\n Popup,\r\n }\r\n}\r\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` (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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"}