@jielga/react-popup-window 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/copyStyles.d.ts +3 -0
- package/dist/copyStyles.d.ts.map +1 -1
- package/dist/index.cjs +3 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +69 -47
- package/dist/index.js.map +1 -1
- package/dist/usePopupWindow.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/getting-started/SKILL.md +3 -2
- package/skills/popup-content/SKILL.md +5 -3
package/README.md
CHANGED
|
@@ -111,12 +111,15 @@ const { open, Popup } = usePopupWindow({ title: 'Panel', url: '/popup.html' })
|
|
|
111
111
|
|
|
112
112
|
`isOpen` turns `true` as soon as the window opens; `Popup` renders once the page has loaded.
|
|
113
113
|
Important: do not point `url` at a route of the app, as that starts a second copy of the app inside the popup.
|
|
114
|
+
Start the page with `<!doctype html>`: without it, the popup lays out in quirks mode.
|
|
114
115
|
If the page redirects to another origin, the popup closes and `onBlocked` is called.
|
|
115
116
|
|
|
116
117
|
## How it works
|
|
117
118
|
|
|
118
119
|
The hook opens a same-origin window, `about:blank` by default or the page
|
|
119
|
-
set with `url`, and creates a container element in its body. The
|
|
120
|
+
set with `url`, and creates a container element in its body. The
|
|
121
|
+
`about:blank` document is written with an HTML5 doctype first, so the popup
|
|
122
|
+
lays out in standards mode, like the opener. The `Popup` component renders its children with
|
|
120
123
|
`createPortal` into that container. A portal changes where DOM output is
|
|
121
124
|
placed, not where the components sit in the tree, so popup content
|
|
122
125
|
participates in the calling tree's state, context, and event system. React
|
|
@@ -200,7 +203,9 @@ The popup loads each copied `<link>` stylesheet again.
|
|
|
200
203
|
`Popup` renders once they have loaded or failed, so the first frame shown is styled.
|
|
201
204
|
Alternate stylesheets and stylesheets whose `media` does not match are not waited for.
|
|
202
205
|
If a stylesheet takes longer than 3 seconds, `Popup` renders without it.
|
|
203
|
-
|
|
206
|
+
A `<link>` added to the opener while the popup is open, such as lazily loaded chunk CSS, is loaded by the popup again.
|
|
207
|
+
Until that copy has loaded, the popup applies the rules the opener already loaded, so content rendered after the opener's load is styled.
|
|
208
|
+
Note that a cross-origin stylesheet served without CORS cannot be read that way, and its content is unstyled until the popup has loaded it.
|
|
204
209
|
Edits inside an existing rule, rules inserted into a nested rule such as `@media`, and changes to `document.adoptedStyleSheets` after the popup opened are not mirrored.
|
|
205
210
|
|
|
206
211
|
Set `copyStyles: false` to disable.
|
package/dist/copyStyles.d.ts
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
* - Additions/removals/edits of style nodes in the source `<head>` are
|
|
13
13
|
* observed (covers Vite HMR, lazily loaded chunk CSS, CSS-in-JS in
|
|
14
14
|
* development builds).
|
|
15
|
+
* - A `<link>` added while watching is loaded by the target again. Until
|
|
16
|
+
* that copy has loaded, the rules the source already loaded are applied
|
|
17
|
+
* to the target through a constructed stylesheet.
|
|
15
18
|
* - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored
|
|
16
19
|
* and kept in sync, so theme switching (e.g. a `dark` class) propagates.
|
|
17
20
|
* - `document.adoptedStyleSheets` are re-constructed in the target document.
|
package/dist/copyStyles.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copyStyles.d.ts","sourceRoot":"","sources":["../src/copyStyles.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"copyStyles.d.ts","sourceRoot":"","sources":["../src/copyStyles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,UAAO,GAAG,MAAM,IAAI,CAEvF;AA6CD,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB;;;OAGG;IACH,KAAK,EAAE,eAAe,EAAE,CAAA;CACzB;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,UAAO,GAAG,WAAW,CAiS1F"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react"),t=require("react-dom");function n(e,t,n=!0){return o(e,t,n).stop}var r=new WeakMap;function i(e,t){let n=r.get(e);if(!n){let t=new Set;n=t,r.set(e,t);let i=Object.getPrototypeOf(e);e.insertRule=function(e,n){let r=i.insertRule.call(this,e,n);for(let n of[...t])n({type:`insert`,rule:e,index:r});return r},e.deleteRule=function(e){i.deleteRule.call(this,e);for(let n of[...t])n({type:`delete`,index:e})}}let i=n;return i.add(t),()=>{if(i.delete(t),i.size>0)return;r.delete(e);let n=e;delete n.insertRule,delete n.deleteRule}}var a=[`rel`,`media`,`title`,`crossorigin`,`referrerpolicy`,`integrity`];function o(e,t,n=!0){let r=new Map,o=n&&typeof MutationObserver<`u`,s=e=>{if(e.nodeType!==Node.ELEMENT_NODE)return!1;let t=e;return t.tagName===`STYLE`||t.tagName===`LINK`&&(t.getAttribute(`rel`)??``).toLowerCase().includes(`stylesheet`)},c=e=>{let t=e.sheet;if(t)try{return Array.from(t.cssRules).map(e=>e.cssText).join(`
|
|
2
|
-
`)}catch{}return e.textContent??``},l=new Map,u=(e,t)=>{let n=e.sheet,r=l.get(e);if(r&&r.sheet===n){r.count=n.cssRules.length;return}if(r?.stop(),l.delete(e),!o||!n)return;let a={sheet:n,count:n.cssRules.length,stop:i(n,r=>{let i=t.sheet;try{if(i&&(r.type===`insert`?i.insertRule(r.rule,r.index):i.deleteRule(r.index),i.cssRules.length===n.cssRules.length)){a.count=n.cssRules.length;return}}catch{}
|
|
3
|
-
`)
|
|
2
|
+
`)}catch{}return e.textContent??``},l=new Map,u=(e,t)=>{let n=e.sheet,r=l.get(e);if(r&&r.sheet===n){r.count=n.cssRules.length;return}if(r?.stop(),l.delete(e),!o||!n)return;let a={sheet:n,count:n.cssRules.length,stop:i(n,r=>{let i=t.sheet;try{if(i&&(r.type===`insert`?i.insertRule(r.rule,r.index):i.deleteRule(r.index),i.cssRules.length===n.cssRules.length)){a.count=n.cssRules.length;return}}catch{}g(e)})};l.set(e,a)},d=e=>{l.get(e)?.stop(),l.delete(e)},f=new Map,p=(e,n)=>{let r=t.defaultView;if(!r||typeof r.CSSStyleSheet!=`function`||!(`adoptedStyleSheets`in t)||/\balternate\b/i.test(e.rel))return;let i=null,a=()=>{if(!(n.sheet!==null||!e.sheet))try{let n=Array.from(e.sheet.cssRules).map(e=>e.cssText).join(`
|
|
3
|
+
`),a=new r.CSSStyleSheet({baseURL:e.href,media:e.media||void 0});a.replaceSync(n),t.adoptedStyleSheets=[...t.adoptedStyleSheets,a],i=a}catch{}},o=()=>{e.removeEventListener(`load`,a),n.removeEventListener(`load`,o),n.removeEventListener(`error`,o),f.delete(e);let r=i;r&&(t.adoptedStyleSheets=t.adoptedStyleSheets.filter(e=>e!==r)),i=null};n.addEventListener(`load`,o),n.addEventListener(`error`,o),f.set(e,o),e.sheet?a():e.addEventListener(`load`,a)},m=!0,h=e=>{if(r.has(e))return;let n;if(e.tagName===`LINK`){let r=t.createElement(`link`);for(let t of a){let n=e.getAttribute(t);n!==null&&r.setAttribute(t,n)}r.href=e.href,n=r}else{let r=t.createElement(`style`);r.textContent=c(e);let i=e.getAttribute(`media`);i&&r.setAttribute(`media`,i),n=r}t.head.appendChild(n),r.set(e,n),e.tagName===`STYLE`?u(e,n):!m&&o&&p(e,n)},g=e=>{let t=r.get(e);t&&(t.textContent=c(e),u(e,t))},_=e=>{for(let[t,n]of r)(e===t||e.nodeType===Node.ELEMENT_NODE&&e.contains(t))&&(n.remove(),r.delete(t),d(t),f.get(t)?.())},v=[`class`,`style`],y=()=>{let n=[[e.documentElement,t.documentElement],[e.body,t.body]];for(let[e,t]of n)if(!(!e||!t)){for(let n of e.attributes)(v.includes(n.name)||n.name.startsWith(`data-`))&&t.setAttribute(n.name,n.value);for(let n of Array.from(t.attributes))(v.includes(n.name)||n.name.startsWith(`data-`))&&!e.hasAttribute(n.name)&&t.removeAttribute(n.name)}},b=()=>{let n=t.defaultView;if(!(!n?.CSSStyleSheet||!(`adoptedStyleSheets`in e)))try{t.adoptedStyleSheets=e.adoptedStyleSheets.map(e=>{let t=new n.CSSStyleSheet;return t.replaceSync(Array.from(e.cssRules).map(e=>e.cssText).join(`
|
|
4
|
+
`)),t})}catch{}};for(let t of e.querySelectorAll(`style, link[rel~="stylesheet" i]`))h(t);m=!1;let x=Array.from(r.values()).filter(e=>e.tagName===`LINK`);if(y(),b(),!o)return{stop:()=>{},links:x};let S=new MutationObserver(e=>{for(let t of e){if(t.type===`characterData`){let e=t.target.parentElement;e?.tagName===`STYLE`&&g(e);continue}s(t.target)&&t.target.tagName===`STYLE`&&g(t.target);for(let e of t.addedNodes)if(s(e))h(e);else if(e.nodeType===Node.ELEMENT_NODE)for(let t of e.querySelectorAll(`style, link[rel~="stylesheet" i]`))h(t);for(let e of t.removedNodes)_(e)}});S.observe(e.head,{childList:!0,subtree:!0,characterData:!0});let C=new MutationObserver(()=>y());C.observe(e.documentElement,{attributes:!0}),e.body&&C.observe(e.body,{attributes:!0});let w=t.defaultView,T,E=()=>{for(let[e,t]of l)e.sheet===t.sheet&&t.sheet.cssRules.length!==t.count&&g(e);T=w?.requestAnimationFrame(E)};return w&&typeof w.requestAnimationFrame==`function`&&(T=w.requestAnimationFrame(E)),{stop:()=>{S.disconnect(),C.disconnect(),T!==void 0&&w?.cancelAnimationFrame(T);for(let e of l.values())e.stop();for(let e of[...f.values()])e();l.clear()},links:x}}function s(e,t,n){let r=e.filter(e=>e.sheet===null&&c(e));if(r.length===0)return n(),()=>{};let i=r.length,a=()=>{i--,i===0&&s()},o=()=>{window.clearTimeout(l);for(let e of r)e.removeEventListener(`load`,a),e.removeEventListener(`error`,a)},s=()=>{o(),n()};for(let e of r)e.addEventListener(`load`,a),e.addEventListener(`error`,a);let l=window.setTimeout(s,t);return o}function c(e){if(/\balternate\b/i.test(e.rel))return!1;let t=e.ownerDocument.defaultView;return!e.media||!t?.matchMedia||t.matchMedia(e.media).matches}var l={popupWindow:null,container:null,blocked:!1},u=class{state=l;listeners=new Set;subscribe=e=>(this.listeners.add(e),()=>this.listeners.delete(e));getSnapshot=()=>this.state;getServerSnapshot=()=>l;setState(e){this.state={...this.state,...e};for(let e of this.listeners)e()}},d={popup:!0,width:640,height:480};function f(e){let t={...d,...e.features};return e.center!==!1&&t.left===void 0&&t.top===void 0&&typeof t.width==`number`&&typeof t.height==`number`&&(t.left=Math.max(0,Math.round(window.screenX+(window.outerWidth-t.width)/2)),t.top=Math.max(0,Math.round(window.screenY+(window.outerHeight-t.height)/2))),Object.entries(t).filter(([,e])=>e!==void 0&&e!==!1).map(([e,t])=>t===!0?`${e}=yes`:`${e}=${t}`).join(`,`)}var p=`about:blank`,m=20,h=3e3;function g(e){e.open(),e.write(`<!DOCTYPE html><html><head></head><body></body></html>`),e.close()}function _(e){let t=e.document;return t.URL===p||t.readyState===`loading`?null:t}function v(n){function r({children:r}){let{container:i}=(0,e.useSyncExternalStore)(n.subscribe,n.getSnapshot,n.getServerSnapshot);return i?(0,t.createPortal)(r,i):null}return r.displayName=`PopupWindow.Popup`,r}function y(t={}){let[n]=(0,e.useState)(()=>new u),[r]=(0,e.useState)(()=>v(n)),i=(0,e.useRef)(t);i.current=t;let a=(0,e.useRef)(null),c=(0,e.useSyncExternalStore)(n.subscribe,n.getSnapshot,n.getServerSnapshot),l=(0,e.useCallback)(e=>{let{popupWindow:t}=n.getSnapshot();a.current?.(),a.current=null,t&&(!e&&!t.closed&&t.close(),n.setState({popupWindow:null,container:null}),i.current.onClose?.())},[n]),d=(0,e.useCallback)(()=>{if(typeof window>`u`)return null;let e=n.getSnapshot();if(e.popupWindow&&!e.popupWindow.closed)return e.popupWindow.focus(),e.popupWindow;let t=i.current,r=t.url??p,c=window.open(r,t.name??`_blank`,f(t));if(!c)return n.setState({blocked:!0}),t.onBlocked?.(),null;let u;try{u=c.document}catch{return c.close(),n.setState({blocked:!0}),t.onBlocked?.(),null}let d=()=>l(!0),v=()=>{setTimeout(()=>{c.closed&&d()},0)},y=window.setInterval(()=>{c.closed&&d()},250),b=()=>c.close();window.addEventListener(`pagehide`,b);let x,S=!1,C,w;a.current=()=>{window.clearInterval(y),window.clearInterval(x),window.removeEventListener(`pagehide`,b),S&&c.removeEventListener(`pagehide`,v),C?.(),w?.()};let T=e=>{S=!0,c.addEventListener(`pagehide`,v),e.title=t.title??document.title;let r=[];if(t.copyStyles!==!1){let t=o(document,e);C=t.stop,r=t.links}let i=e.createElement(`div`);i.setAttribute(`data-popup-window-root`,``),e.body.appendChild(i),w=s(r,h,()=>{n.setState({container:i}),t.onOpen?.(c)})};return n.setState({popupWindow:c,container:null,blocked:!1}),r===p?(g(u),T(u)):x=window.setInterval(()=>{if(c.closed){d();return}let e;try{e=_(c)}catch{l(!1),n.setState({blocked:!0}),t.onBlocked?.();return}e&&(window.clearInterval(x),T(e))},m),c},[n,l]),y=(0,e.useCallback)(()=>l(!1),[l]),b=(0,e.useCallback)(()=>{n.getSnapshot().popupWindow?l(!1):d()},[n,l,d]),x=(0,e.useCallback)(()=>{let{popupWindow:e}=n.getSnapshot();e&&!e.closed&&e.focus()},[n]);return(0,e.useEffect)(()=>()=>l(!1),[l]),{open:d,close:y,toggle:b,focus:x,isOpen:c.popupWindow!==null,isBlocked:c.blocked,popupWindow:c.popupWindow,Popup:r}}exports.copyStyles=n,exports.usePopupWindow=y;
|
|
4
5
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/copyStyles.ts","../src/whenStylesheetsLoad.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\n * in sync while the popup is open:\n *\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\n * possible, so rules injected via `insertRule` are included.\n * - Rules added to or removed from a `<style>` sheet later with\n * `insertRule`/`deleteRule` are applied to its mirror as they happen. This\n * is how CSS-in-JS libraries (Emotion, styled-components) add rules in\n * production builds, and it changes no DOM.\n * - Additions/removals/edits of style nodes in the source `<head>` are\n * observed (covers Vite HMR, lazily loaded chunk CSS, CSS-in-JS in\n * development builds).\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\n *\n * Returns a function that stops observing.\n */\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\n return mirrorStyles(source, target, watch).stop\n}\n\ntype RuleChange = { type: 'insert'; rule: string; index: number } | { type: 'delete'; index: number }\ntype RuleListener = (change: RuleChange) => void\n\nconst ruleListeners = new WeakMap<CSSStyleSheet, Set<RuleListener>>()\n\n/**\n * Calls `listener` after each `insertRule`/`deleteRule` call on `sheet`.\n * Wraps the two methods on this sheet object only, not on the prototype, and\n * restores them once the sheet's last listener is removed. Returns a function\n * that removes the listener.\n */\nfunction onRuleChange(sheet: CSSStyleSheet, listener: RuleListener): () => void {\n let listeners = ruleListeners.get(sheet)\n if (!listeners) {\n const all = new Set<RuleListener>()\n listeners = all\n ruleListeners.set(sheet, all)\n const proto = Object.getPrototypeOf(sheet) as CSSStyleSheet\n sheet.insertRule = function (this: CSSStyleSheet, rule: string, index?: number): number {\n const inserted = proto.insertRule.call(this, rule, index)\n for (const notify of [...all]) notify({ type: 'insert', rule, index: inserted })\n return inserted\n }\n sheet.deleteRule = function (this: CSSStyleSheet, index: number): void {\n proto.deleteRule.call(this, index)\n for (const notify of [...all]) notify({ type: 'delete', index })\n }\n }\n const own = listeners\n own.add(listener)\n return () => {\n own.delete(listener)\n if (own.size > 0) return\n ruleListeners.delete(sheet)\n const wrapped = sheet as { insertRule?: unknown; deleteRule?: unknown }\n delete wrapped.insertRule\n delete wrapped.deleteRule\n }\n}\n\n/** Attributes copied from a source `<link>` onto its mirror. */\nconst LINK_ATTRS = ['rel', 'media', 'title', 'crossorigin', 'referrerpolicy', 'integrity']\n\nexport interface StyleMirror {\n /** Stops observing the source document. */\n stop: () => void\n /**\n * The `<link>` elements created in `target` by the initial copy. The target\n * fetches each of them asynchronously.\n */\n links: HTMLLinkElement[]\n}\n\n/** {@link copyStyles}, plus the `<link>` elements the initial copy created. */\nexport function mirrorStyles(source: Document, target: Document, watch = true): StyleMirror {\n const mirrors = new Map<Element, Element>()\n const observing = watch && typeof MutationObserver !== 'undefined'\n\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\n if (node.nodeType !== Node.ELEMENT_NODE) return false\n const el = node as Element\n return (\n el.tagName === 'STYLE' ||\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\n )\n }\n\n const serializeStyle = (el: HTMLStyleElement): string => {\n const sheet = el.sheet\n if (sheet) {\n try {\n return Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n } catch {\n // Inaccessible cssRules — fall back to the raw text below.\n }\n }\n return el.textContent ?? ''\n }\n\n interface RuleSync {\n sheet: CSSStyleSheet\n /** Rule count of `sheet` that the mirror reflects. */\n count: number\n stop: () => void\n }\n const ruleSyncs = new Map<HTMLStyleElement, RuleSync>()\n\n // CSS-in-JS libraries in production builds add rules with insertRule, which\n // changes no DOM. Apply each change to the mirror as it happens, so content\n // that renders in the same task, such as the popup's first render, is\n // already styled.\n const syncRules = (el: HTMLStyleElement, clone: HTMLStyleElement): void => {\n const sheet = el.sheet\n const current = ruleSyncs.get(el)\n if (current && current.sheet === sheet) {\n current.count = sheet.cssRules.length\n return\n }\n current?.stop()\n ruleSyncs.delete(el)\n if (!observing || !sheet) return\n const sync: RuleSync = {\n sheet,\n count: sheet.cssRules.length,\n stop: onRuleChange(sheet, (change) => {\n const copy = clone.sheet\n try {\n if (copy) {\n if (change.type === 'insert') copy.insertRule(change.rule, change.index)\n else copy.deleteRule(change.index)\n if (copy.cssRules.length === sheet.cssRules.length) {\n sync.count = sheet.cssRules.length\n return\n }\n }\n } catch {\n // Out of step with the source - copy the whole sheet again below.\n }\n refresh(el)\n }),\n }\n ruleSyncs.set(el, sync)\n }\n\n const unsyncRules = (el: Element): void => {\n ruleSyncs.get(el as HTMLStyleElement)?.stop()\n ruleSyncs.delete(el as HTMLStyleElement)\n }\n\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\n if (mirrors.has(el)) return\n let clone: Element\n if (el.tagName === 'LINK') {\n const link = target.createElement('link')\n // `rel` is copied as is, so an alternate stylesheet stays disabled.\n // `crossorigin` and `referrerpolicy` make the popup send the same request\n // as the opener, so it can reuse the cached response.\n for (const name of LINK_ATTRS) {\n const value = el.getAttribute(name)\n if (value !== null) link.setAttribute(name, value)\n }\n // .href resolves to an absolute URL, so relative hrefs keep working\n // from the popup's `about:blank` document.\n link.href = (el as HTMLLinkElement).href\n clone = link\n } else {\n const style = target.createElement('style')\n style.textContent = serializeStyle(el as HTMLStyleElement)\n const media = el.getAttribute('media')\n if (media) style.setAttribute('media', media)\n clone = style\n }\n target.head.appendChild(clone)\n mirrors.set(el, clone)\n if (el.tagName === 'STYLE') syncRules(el as HTMLStyleElement, clone as HTMLStyleElement)\n }\n\n const refresh = (el: HTMLStyleElement): void => {\n const clone = mirrors.get(el)\n if (!clone) return\n clone.textContent = serializeStyle(el)\n // New text gives the source a new sheet object.\n syncRules(el, clone as HTMLStyleElement)\n }\n\n const unmirrorSubtree = (removed: Node): void => {\n for (const [src, clone] of mirrors) {\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\n clone.remove()\n mirrors.delete(src)\n unsyncRules(src)\n }\n }\n }\n\n const ROOT_ATTRS = ['class', 'style']\n const syncRootAttrs = (): void => {\n const pairs: Array<[Element | null, Element | null]> = [\n [source.documentElement, target.documentElement],\n [source.body, target.body],\n ]\n for (const [from, to] of pairs) {\n if (!from || !to) continue\n for (const attr of from.attributes) {\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\n to.setAttribute(attr.name, attr.value)\n }\n }\n for (const attr of Array.from(to.attributes)) {\n if (\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\n !from.hasAttribute(attr.name)\n ) {\n to.removeAttribute(attr.name)\n }\n }\n }\n }\n\n const syncAdoptedSheets = (): void => {\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\n try {\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\n const copy = new targetWindow.CSSStyleSheet!()\n copy.replaceSync(\n Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n'),\n )\n return copy\n })\n } catch {\n // Best effort — older browsers or cross-origin rules.\n }\n }\n\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n const links = Array.from(mirrors.values()).filter(\n (clone): clone is HTMLLinkElement => clone.tagName === 'LINK',\n )\n syncRootAttrs()\n syncAdoptedSheets()\n\n if (!observing) {\n return { stop: () => {}, links }\n }\n\n const headObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'characterData') {\n const parent = record.target.parentElement\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\n continue\n }\n // childList\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\n // textContent assignment replaces the text node → childList on <style>\n refresh(record.target as HTMLStyleElement)\n }\n for (const added of record.addedNodes) {\n if (isStyleNode(added)) {\n mirror(added)\n } else if (added.nodeType === Node.ELEMENT_NODE) {\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n }\n }\n for (const removed of record.removedNodes) {\n unmirrorSubtree(removed)\n }\n }\n })\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\n\n const rootObserver = new MutationObserver(() => syncRootAttrs())\n rootObserver.observe(source.documentElement, { attributes: true })\n if (source.body) rootObserver.observe(source.body, { attributes: true })\n\n // Fallback for rule changes that bypass the sheet's own methods, such as\n // CSSStyleSheet.prototype.insertRule.call(sheet, ...): once per frame of the\n // target window, copy again any sheet whose rule count changed.\n const view = target.defaultView\n let frame: number | undefined\n const checkRuleCounts = (): void => {\n for (const [el, sync] of ruleSyncs) {\n if (el.sheet === sync.sheet && sync.sheet.cssRules.length !== sync.count) refresh(el)\n }\n frame = view?.requestAnimationFrame(checkRuleCounts)\n }\n if (view && typeof view.requestAnimationFrame === 'function') {\n frame = view.requestAnimationFrame(checkRuleCounts)\n }\n\n return {\n stop: () => {\n headObserver.disconnect()\n rootObserver.disconnect()\n if (frame !== undefined) view?.cancelAnimationFrame(frame)\n for (const sync of ruleSyncs.values()) sync.stop()\n ruleSyncs.clear()\n },\n links,\n }\n}\n","/**\n * Calls `callback` once every stylesheet in `links` that applies has loaded\n * or failed, or after `timeoutMs`, whichever comes first.\n *\n * When none of them is still loading, `callback` runs synchronously, before\n * this function returns. Links that do not apply are not waited for: an\n * alternate stylesheet, or a `media` query that does not match, such as\n * `print`. Browsers fetch those at the lowest priority.\n *\n * Returns a function that stops waiting without calling `callback`.\n */\nexport function whenStylesheetsLoad(\n links: readonly HTMLLinkElement[],\n timeoutMs: number,\n callback: () => void,\n): () => void {\n const pending = links.filter((link) => link.sheet === null && applies(link))\n if (pending.length === 0) {\n callback()\n return () => {}\n }\n\n let remaining = pending.length\n const settle = () => {\n remaining--\n if (remaining === 0) finish()\n }\n const stop = () => {\n window.clearTimeout(timeout)\n for (const link of pending) {\n link.removeEventListener('load', settle)\n link.removeEventListener('error', settle)\n }\n }\n const finish = () => {\n stop()\n callback()\n }\n\n for (const link of pending) {\n link.addEventListener('load', settle)\n // A broken stylesheet must not keep the popup blank.\n link.addEventListener('error', settle)\n }\n const timeout = window.setTimeout(finish, timeoutMs)\n return stop\n}\n\nfunction applies(link: HTMLLinkElement): boolean {\n if (/\\balternate\\b/i.test(link.rel)) return false\n const view = link.ownerDocument.defaultView\n return !link.media || !view?.matchMedia || view.matchMedia(link.media).matches\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { FC } from 'react'\nimport { createPortal } from 'react-dom'\nimport { mirrorStyles } from './copyStyles'\nimport type {\n PopupProps,\n PopupWindowApi,\n PopupWindowFeatures,\n UsePopupWindowOptions,\n} from './types'\nimport { whenStylesheetsLoad } from './whenStylesheetsLoad'\n\ninterface PopupState {\n popupWindow: Window | null\n container: HTMLElement | null\n blocked: boolean\n}\n\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\n\nclass PopupStore {\n state: PopupState = INITIAL_STATE\n private listeners = new Set<() => void>()\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getSnapshot = (): PopupState => this.state\n\n getServerSnapshot = (): PopupState => INITIAL_STATE\n\n setState(partial: Partial<PopupState>): void {\n this.state = { ...this.state, ...partial }\n for (const listener of this.listeners) listener()\n }\n}\n\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\n\nfunction buildFeatures(options: UsePopupWindowOptions): string {\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\n if (\n options.center !== false &&\n merged.left === undefined &&\n merged.top === undefined &&\n typeof merged.width === 'number' &&\n typeof merged.height === 'number'\n ) {\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\n }\n return Object.entries(merged)\n .filter(([, value]) => value !== undefined && value !== false)\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\n .join(',')\n}\n\nconst ABOUT_BLANK = 'about:blank'\n/** Interval for checking whether the page given by `url` has loaded. */\nconst LOAD_POLL_MS = 20\n/** Longest time `Popup` waits for copied stylesheets before rendering anyway. */\nconst STYLES_TIMEOUT_MS = 3000\n\n/**\n * The popup's document once the page given by `url` has replaced the initial\n * `about:blank` document and finished parsing, otherwise `null`. Throws when\n * the document is not scriptable.\n */\nfunction loadedDocument(popupWindow: Window): Document | null {\n const doc = popupWindow.document\n if (doc.URL === ABOUT_BLANK || doc.readyState === 'loading') return null\n return doc\n}\n\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\n function Popup({ children }: PopupProps) {\n const { container } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n )\n return container ? createPortal(children, container) : null\n }\n Popup.displayName = 'PopupWindow.Popup'\n return Popup\n}\n\n/**\n * Open part of your React tree in a separate browser window.\n *\n * The popup content is rendered with a portal into the popup's document, so\n * it stays part of your component tree: state, context and event handlers\n * all keep working across windows.\n *\n * ```tsx\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\n *\n * return (\n * <>\n * <button onClick={open}>Open panel</button>\n * <Popup>\n * <MyPanel />\n * </Popup>\n * </>\n * )\n * ```\n */\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\n const [store] = useState(() => new PopupStore())\n const [Popup] = useState(() => createPopupComponent(store))\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const cleanupRef = useRef<(() => void) | null>(null)\n\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\n\n // `userClosed` is true when the window was closed outside our control\n // (user hit the close button, opener unloaded) — then we must not touch it.\n const closePopup = useCallback(\n (userClosed: boolean) => {\n const { popupWindow } = store.getSnapshot()\n cleanupRef.current?.()\n cleanupRef.current = null\n if (!popupWindow) return\n if (!userClosed && !popupWindow.closed) popupWindow.close()\n store.setState({ popupWindow: null, container: null })\n optionsRef.current.onClose?.()\n },\n [store],\n )\n\n const open = useCallback((): Window | null => {\n if (typeof window === 'undefined') return null\n const current = store.getSnapshot()\n if (current.popupWindow && !current.popupWindow.closed) {\n current.popupWindow.focus()\n return current.popupWindow\n }\n\n const opts = optionsRef.current\n const url = opts.url ?? ABOUT_BLANK\n const popupWindow = window.open(url, opts.name ?? '_blank', buildFeatures(opts))\n if (!popupWindow) {\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n // Sandboxed embedders (VS Code's built-in browser, CodeSandbox/StackBlitz\n // previews, iframes sandboxed without popup-escape) open the popup with an\n // opaque origin, so reading its document throws a SecurityError. Treat\n // that as blocked instead of stranding a blank window the portal can\n // never reach.\n let initialDoc: Document\n try {\n initialDoc = popupWindow.document\n } catch {\n popupWindow.close()\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n const handleExternalClose = () => closePopup(true)\n const onPopupPagehide = () => {\n // pagehide also fires on navigation; only treat it as a close when the\n // window really is gone a tick later.\n setTimeout(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 0)\n }\n\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\n const closePoll = window.setInterval(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 250)\n\n const onOpenerPagehide = () => popupWindow.close()\n window.addEventListener('pagehide', onOpenerPagehide)\n\n let loadPoll: number | undefined\n let prepared = false\n let stopStyleSync: (() => void) | undefined\n let stopStylesWait: (() => void) | undefined\n\n cleanupRef.current = () => {\n window.clearInterval(closePoll)\n window.clearInterval(loadPoll)\n window.removeEventListener('pagehide', onOpenerPagehide)\n if (prepared) popupWindow.removeEventListener('pagehide', onPopupPagehide)\n stopStyleSync?.()\n stopStylesWait?.()\n }\n\n // Title, styles and the portal container go into the document that\n // stays: `about:blank` itself, or the page `url` loads in its place.\n const prepare = (doc: Document) => {\n prepared = true\n popupWindow.addEventListener('pagehide', onPopupPagehide)\n doc.title = opts.title ?? document.title\n let links: HTMLLinkElement[] = []\n if (opts.copyStyles !== false) {\n const styles = mirrorStyles(document, doc)\n stopStyleSync = styles.stop\n links = styles.links\n }\n const container = doc.createElement('div')\n container.setAttribute('data-popup-window-root', '')\n doc.body.appendChild(container)\n // The popup fetches copied <link> stylesheets asynchronously. Render\n // only once they have loaded, so the first paint is not unstyled.\n stopStylesWait = whenStylesheetsLoad(links, STYLES_TIMEOUT_MS, () => {\n store.setState({ container })\n opts.onOpen?.(popupWindow)\n })\n }\n\n store.setState({ popupWindow, container: null, blocked: false })\n\n if (url === ABOUT_BLANK) {\n prepare(initialDoc)\n } else {\n // The window starts on an initial about:blank document and then\n // navigates to `url`. Wait until that page has replaced it and parsed.\n loadPoll = window.setInterval(() => {\n if (popupWindow.closed) {\n handleExternalClose()\n return\n }\n let doc: Document | null\n try {\n doc = loadedDocument(popupWindow)\n } catch {\n // The page went somewhere the opener cannot script, such as a\n // cross-origin redirect: nothing can ever render there.\n closePopup(false)\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return\n }\n if (!doc) return\n window.clearInterval(loadPoll)\n prepare(doc)\n }, LOAD_POLL_MS)\n }\n\n return popupWindow\n }, [store, closePopup])\n\n const close = useCallback(() => closePopup(false), [closePopup])\n\n const toggle = useCallback(() => {\n if (store.getSnapshot().popupWindow) {\n closePopup(false)\n } else {\n open()\n }\n }, [store, closePopup, open])\n\n const focus = useCallback(() => {\n const { popupWindow } = store.getSnapshot()\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\n }, [store])\n\n // Close the popup when the owning component unmounts — its portal content\n // would unmount anyway, leaving an empty window behind.\n useEffect(() => {\n return () => closePopup(false)\n }, [closePopup])\n\n return {\n open,\n close,\n toggle,\n focus,\n isOpen: state.popupWindow !== null,\n isBlocked: state.blocked,\n popupWindow: state.popupWindow,\n Popup,\n }\n}\n"],"mappings":"iHAoBA,SAAgB,EAAW,EAAkB,EAAkB,EAAQ,GAAkB,CACvF,OAAO,EAAa,EAAQ,EAAQ,CAAK,CAAC,CAAC,IAC7C,CAKA,IAAM,EAAgB,IAAI,QAQ1B,SAAS,EAAa,EAAsB,EAAoC,CAC9E,IAAI,EAAY,EAAc,IAAI,CAAK,EACvC,GAAI,CAAC,EAAW,CACd,IAAM,EAAM,IAAI,IAChB,EAAY,EACZ,EAAc,IAAI,EAAO,CAAG,EAC5B,IAAM,EAAQ,OAAO,eAAe,CAAK,EACzC,EAAM,WAAa,SAA+B,EAAc,EAAwB,CACtF,IAAM,EAAW,EAAM,WAAW,KAAK,KAAM,EAAM,CAAK,EACxD,IAAK,IAAM,IAAU,CAAC,GAAG,CAAG,EAAG,EAAO,CAAE,KAAM,SAAU,OAAM,MAAO,CAAS,CAAC,EAC/E,OAAO,CACT,EACA,EAAM,WAAa,SAA+B,EAAqB,CACrE,EAAM,WAAW,KAAK,KAAM,CAAK,EACjC,IAAK,IAAM,IAAU,CAAC,GAAG,CAAG,EAAG,EAAO,CAAE,KAAM,SAAU,OAAM,CAAC,CACjE,CACF,CACA,IAAM,EAAM,EAEZ,OADA,EAAI,IAAI,CAAQ,MACH,CAEX,GADA,EAAI,OAAO,CAAQ,EACf,EAAI,KAAO,EAAG,OAClB,EAAc,OAAO,CAAK,EAC1B,IAAM,EAAU,EAChB,OAAO,EAAQ,WACf,OAAO,EAAQ,UACjB,CACF,CAGA,IAAM,EAAa,CAAC,MAAO,QAAS,QAAS,cAAe,iBAAkB,WAAW,EAazF,SAAgB,EAAa,EAAkB,EAAkB,EAAQ,GAAmB,CAC1F,IAAM,EAAU,IAAI,IACd,EAAY,GAAS,OAAO,iBAAqB,IAEjD,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,EAQM,EAAY,IAAI,IAMhB,GAAa,EAAsB,IAAkC,CACzE,IAAM,EAAQ,EAAG,MACX,EAAU,EAAU,IAAI,CAAE,EAChC,GAAI,GAAW,EAAQ,QAAU,EAAO,CACtC,EAAQ,MAAQ,EAAM,SAAS,OAC/B,MACF,CAGA,GAFA,GAAS,KAAK,EACd,EAAU,OAAO,CAAE,EACf,CAAC,GAAa,CAAC,EAAO,OAC1B,IAAM,EAAiB,CACrB,QACA,MAAO,EAAM,SAAS,OACtB,KAAM,EAAa,EAAQ,GAAW,CACpC,IAAM,EAAO,EAAM,MACnB,GAAI,CACF,GAAI,IACE,EAAO,OAAS,SAAU,EAAK,WAAW,EAAO,KAAM,EAAO,KAAK,EAClE,EAAK,WAAW,EAAO,KAAK,EAC7B,EAAK,SAAS,SAAW,EAAM,SAAS,QAAQ,CAClD,EAAK,MAAQ,EAAM,SAAS,OAC5B,MACF,CAEJ,MAAQ,CAER,CACA,EAAQ,CAAE,CACZ,CAAC,CACH,EACA,EAAU,IAAI,EAAI,CAAI,CACxB,EAEM,EAAe,GAAsB,CACzC,EAAU,IAAI,CAAsB,CAAC,EAAE,KAAK,EAC5C,EAAU,OAAO,CAAsB,CACzC,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,EACjB,EAAG,UAAY,SAAS,EAAU,EAAwB,CAAyB,CACzF,EAEM,EAAW,GAA+B,CAC9C,IAAM,EAAQ,EAAQ,IAAI,CAAE,EACvB,IACL,EAAM,YAAc,EAAe,CAAE,EAErC,EAAU,EAAI,CAAyB,EACzC,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,EAClB,EAAY,CAAG,EAGrB,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,EACH,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,EAC/D,EAAa,QAAQ,EAAO,gBAAiB,CAAE,WAAY,EAAK,CAAC,EAC7D,EAAO,MAAM,EAAa,QAAQ,EAAO,KAAM,CAAE,WAAY,EAAK,CAAC,EAKvE,IAAM,EAAO,EAAO,YAChB,EACE,MAA8B,CAClC,IAAK,GAAM,CAAC,EAAI,KAAS,EACnB,EAAG,QAAU,EAAK,OAAS,EAAK,MAAM,SAAS,SAAW,EAAK,OAAO,EAAQ,CAAE,EAEtF,EAAQ,GAAM,sBAAsB,CAAe,CACrD,EAKA,OAJI,GAAQ,OAAO,EAAK,uBAA0B,aAChD,EAAQ,EAAK,sBAAsB,CAAe,GAG7C,CACL,SAAY,CACV,EAAa,WAAW,EACxB,EAAa,WAAW,EACpB,IAAU,IAAA,IAAW,GAAM,qBAAqB,CAAK,EACzD,IAAK,IAAM,KAAQ,EAAU,OAAO,EAAG,EAAK,KAAK,EACjD,EAAU,MAAM,CAClB,EACA,OACF,CACF,CC9SA,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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/copyStyles.ts","../src/whenStylesheetsLoad.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\n * in sync while the popup is open:\n *\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\n * possible, so rules injected via `insertRule` are included.\n * - Rules added to or removed from a `<style>` sheet later with\n * `insertRule`/`deleteRule` are applied to its mirror as they happen. This\n * is how CSS-in-JS libraries (Emotion, styled-components) add rules in\n * production builds, and it changes no DOM.\n * - Additions/removals/edits of style nodes in the source `<head>` are\n * observed (covers Vite HMR, lazily loaded chunk CSS, CSS-in-JS in\n * development builds).\n * - A `<link>` added while watching is loaded by the target again. Until\n * that copy has loaded, the rules the source already loaded are applied\n * to the target through a constructed stylesheet.\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\n *\n * Returns a function that stops observing.\n */\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\n return mirrorStyles(source, target, watch).stop\n}\n\ntype RuleChange = { type: 'insert'; rule: string; index: number } | { type: 'delete'; index: number }\ntype RuleListener = (change: RuleChange) => void\n\nconst ruleListeners = new WeakMap<CSSStyleSheet, Set<RuleListener>>()\n\n/**\n * Calls `listener` after each `insertRule`/`deleteRule` call on `sheet`.\n * Wraps the two methods on this sheet object only, not on the prototype, and\n * restores them once the sheet's last listener is removed. Returns a function\n * that removes the listener.\n */\nfunction onRuleChange(sheet: CSSStyleSheet, listener: RuleListener): () => void {\n let listeners = ruleListeners.get(sheet)\n if (!listeners) {\n const all = new Set<RuleListener>()\n listeners = all\n ruleListeners.set(sheet, all)\n const proto = Object.getPrototypeOf(sheet) as CSSStyleSheet\n sheet.insertRule = function (this: CSSStyleSheet, rule: string, index?: number): number {\n const inserted = proto.insertRule.call(this, rule, index)\n for (const notify of [...all]) notify({ type: 'insert', rule, index: inserted })\n return inserted\n }\n sheet.deleteRule = function (this: CSSStyleSheet, index: number): void {\n proto.deleteRule.call(this, index)\n for (const notify of [...all]) notify({ type: 'delete', index })\n }\n }\n const own = listeners\n own.add(listener)\n return () => {\n own.delete(listener)\n if (own.size > 0) return\n ruleListeners.delete(sheet)\n const wrapped = sheet as { insertRule?: unknown; deleteRule?: unknown }\n delete wrapped.insertRule\n delete wrapped.deleteRule\n }\n}\n\n/** Attributes copied from a source `<link>` onto its mirror. */\nconst LINK_ATTRS = ['rel', 'media', 'title', 'crossorigin', 'referrerpolicy', 'integrity']\n\nexport interface StyleMirror {\n /** Stops observing the source document. */\n stop: () => void\n /**\n * The `<link>` elements created in `target` by the initial copy. The target\n * fetches each of them asynchronously.\n */\n links: HTMLLinkElement[]\n}\n\n/** {@link copyStyles}, plus the `<link>` elements the initial copy created. */\nexport function mirrorStyles(source: Document, target: Document, watch = true): StyleMirror {\n const mirrors = new Map<Element, Element>()\n const observing = watch && typeof MutationObserver !== 'undefined'\n\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\n if (node.nodeType !== Node.ELEMENT_NODE) return false\n const el = node as Element\n return (\n el.tagName === 'STYLE' ||\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\n )\n }\n\n const serializeStyle = (el: HTMLStyleElement): string => {\n const sheet = el.sheet\n if (sheet) {\n try {\n return Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n } catch {\n // Inaccessible cssRules — fall back to the raw text below.\n }\n }\n return el.textContent ?? ''\n }\n\n interface RuleSync {\n sheet: CSSStyleSheet\n /** Rule count of `sheet` that the mirror reflects. */\n count: number\n stop: () => void\n }\n const ruleSyncs = new Map<HTMLStyleElement, RuleSync>()\n\n // CSS-in-JS libraries in production builds add rules with insertRule, which\n // changes no DOM. Apply each change to the mirror as it happens, so content\n // that renders in the same task, such as the popup's first render, is\n // already styled.\n const syncRules = (el: HTMLStyleElement, clone: HTMLStyleElement): void => {\n const sheet = el.sheet\n const current = ruleSyncs.get(el)\n if (current && current.sheet === sheet) {\n current.count = sheet.cssRules.length\n return\n }\n current?.stop()\n ruleSyncs.delete(el)\n if (!observing || !sheet) return\n const sync: RuleSync = {\n sheet,\n count: sheet.cssRules.length,\n stop: onRuleChange(sheet, (change) => {\n const copy = clone.sheet\n try {\n if (copy) {\n if (change.type === 'insert') copy.insertRule(change.rule, change.index)\n else copy.deleteRule(change.index)\n if (copy.cssRules.length === sheet.cssRules.length) {\n sync.count = sheet.cssRules.length\n return\n }\n }\n } catch {\n // Out of step with the source - copy the whole sheet again below.\n }\n refresh(el)\n }),\n }\n ruleSyncs.set(el, sync)\n }\n\n const unsyncRules = (el: Element): void => {\n ruleSyncs.get(el as HTMLStyleElement)?.stop()\n ruleSyncs.delete(el as HTMLStyleElement)\n }\n\n // A <link> added while watching, such as lazily loaded chunk CSS: the\n // target loads its copy separately, and content that waits only for the\n // source's copy, as bundlers do, can render before the target's has\n // loaded. Until it has, apply the rules the source already parsed through a\n // constructed sheet. `baseURL` keeps relative url()s resolving against the\n // stylesheet. Adopted sheets come last in the cascade, which can change\n // precedence for that short time. Unreadable cross-origin sheets are left\n // to load normally.\n const unbridgeLinks = new Map<Element, () => void>()\n const bridgeLink = (el: HTMLLinkElement, clone: HTMLLinkElement): void => {\n const view = target.defaultView as (Window & typeof globalThis) | null\n if (!view || typeof view.CSSStyleSheet !== 'function' || !('adoptedStyleSheets' in target)) {\n return\n }\n if (/\\balternate\\b/i.test(el.rel)) return\n let bridge: CSSStyleSheet | null = null\n const add = (): void => {\n if (clone.sheet !== null || !el.sheet) return\n try {\n const text = Array.from(el.sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n const sheet = new view.CSSStyleSheet({ baseURL: el.href, media: el.media || undefined })\n sheet.replaceSync(text)\n target.adoptedStyleSheets = [...target.adoptedStyleSheets, sheet]\n bridge = sheet\n } catch {\n // Cross-origin rules or no constructable stylesheets - wait for the copy.\n }\n }\n const remove = (): void => {\n el.removeEventListener('load', add)\n clone.removeEventListener('load', remove)\n clone.removeEventListener('error', remove)\n unbridgeLinks.delete(el)\n const sheet = bridge\n if (sheet) target.adoptedStyleSheets = target.adoptedStyleSheets.filter((s) => s !== sheet)\n bridge = null\n }\n clone.addEventListener('load', remove)\n clone.addEventListener('error', remove)\n unbridgeLinks.set(el, remove)\n if (el.sheet) add()\n else el.addEventListener('load', add)\n }\n\n // True during the initial copy. `usePopupWindow` waits for those links\n // itself before rendering.\n let copying = true\n\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\n if (mirrors.has(el)) return\n let clone: Element\n if (el.tagName === 'LINK') {\n const link = target.createElement('link')\n // `rel` is copied as is, so an alternate stylesheet stays disabled.\n // `crossorigin` and `referrerpolicy` make the popup send the same request\n // as the opener, so it can reuse the cached response.\n for (const name of LINK_ATTRS) {\n const value = el.getAttribute(name)\n if (value !== null) link.setAttribute(name, value)\n }\n // .href resolves to an absolute URL, so relative hrefs keep working\n // from the popup's `about:blank` document.\n link.href = (el as HTMLLinkElement).href\n clone = link\n } else {\n const style = target.createElement('style')\n style.textContent = serializeStyle(el as HTMLStyleElement)\n const media = el.getAttribute('media')\n if (media) style.setAttribute('media', media)\n clone = style\n }\n target.head.appendChild(clone)\n mirrors.set(el, clone)\n if (el.tagName === 'STYLE') syncRules(el as HTMLStyleElement, clone as HTMLStyleElement)\n else if (!copying && observing) bridgeLink(el as HTMLLinkElement, clone as HTMLLinkElement)\n }\n\n const refresh = (el: HTMLStyleElement): void => {\n const clone = mirrors.get(el)\n if (!clone) return\n clone.textContent = serializeStyle(el)\n // New text gives the source a new sheet object.\n syncRules(el, clone as HTMLStyleElement)\n }\n\n const unmirrorSubtree = (removed: Node): void => {\n for (const [src, clone] of mirrors) {\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\n clone.remove()\n mirrors.delete(src)\n unsyncRules(src)\n unbridgeLinks.get(src)?.()\n }\n }\n }\n\n const ROOT_ATTRS = ['class', 'style']\n const syncRootAttrs = (): void => {\n const pairs: Array<[Element | null, Element | null]> = [\n [source.documentElement, target.documentElement],\n [source.body, target.body],\n ]\n for (const [from, to] of pairs) {\n if (!from || !to) continue\n for (const attr of from.attributes) {\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\n to.setAttribute(attr.name, attr.value)\n }\n }\n for (const attr of Array.from(to.attributes)) {\n if (\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\n !from.hasAttribute(attr.name)\n ) {\n to.removeAttribute(attr.name)\n }\n }\n }\n }\n\n const syncAdoptedSheets = (): void => {\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\n try {\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\n const copy = new targetWindow.CSSStyleSheet!()\n copy.replaceSync(\n Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n'),\n )\n return copy\n })\n } catch {\n // Best effort — older browsers or cross-origin rules.\n }\n }\n\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n copying = false\n const links = Array.from(mirrors.values()).filter(\n (clone): clone is HTMLLinkElement => clone.tagName === 'LINK',\n )\n syncRootAttrs()\n syncAdoptedSheets()\n\n if (!observing) {\n return { stop: () => {}, links }\n }\n\n const headObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'characterData') {\n const parent = record.target.parentElement\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\n continue\n }\n // childList\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\n // textContent assignment replaces the text node → childList on <style>\n refresh(record.target as HTMLStyleElement)\n }\n for (const added of record.addedNodes) {\n if (isStyleNode(added)) {\n mirror(added)\n } else if (added.nodeType === Node.ELEMENT_NODE) {\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n }\n }\n for (const removed of record.removedNodes) {\n unmirrorSubtree(removed)\n }\n }\n })\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\n\n const rootObserver = new MutationObserver(() => syncRootAttrs())\n rootObserver.observe(source.documentElement, { attributes: true })\n if (source.body) rootObserver.observe(source.body, { attributes: true })\n\n // Fallback for rule changes that bypass the sheet's own methods, such as\n // CSSStyleSheet.prototype.insertRule.call(sheet, ...): once per frame of the\n // target window, copy again any sheet whose rule count changed.\n const view = target.defaultView\n let frame: number | undefined\n const checkRuleCounts = (): void => {\n for (const [el, sync] of ruleSyncs) {\n if (el.sheet === sync.sheet && sync.sheet.cssRules.length !== sync.count) refresh(el)\n }\n frame = view?.requestAnimationFrame(checkRuleCounts)\n }\n if (view && typeof view.requestAnimationFrame === 'function') {\n frame = view.requestAnimationFrame(checkRuleCounts)\n }\n\n return {\n stop: () => {\n headObserver.disconnect()\n rootObserver.disconnect()\n if (frame !== undefined) view?.cancelAnimationFrame(frame)\n for (const sync of ruleSyncs.values()) sync.stop()\n for (const remove of [...unbridgeLinks.values()]) remove()\n ruleSyncs.clear()\n },\n links,\n }\n}\n","/**\n * Calls `callback` once every stylesheet in `links` that applies has loaded\n * or failed, or after `timeoutMs`, whichever comes first.\n *\n * When none of them is still loading, `callback` runs synchronously, before\n * this function returns. Links that do not apply are not waited for: an\n * alternate stylesheet, or a `media` query that does not match, such as\n * `print`. Browsers fetch those at the lowest priority.\n *\n * Returns a function that stops waiting without calling `callback`.\n */\nexport function whenStylesheetsLoad(\n links: readonly HTMLLinkElement[],\n timeoutMs: number,\n callback: () => void,\n): () => void {\n const pending = links.filter((link) => link.sheet === null && applies(link))\n if (pending.length === 0) {\n callback()\n return () => {}\n }\n\n let remaining = pending.length\n const settle = () => {\n remaining--\n if (remaining === 0) finish()\n }\n const stop = () => {\n window.clearTimeout(timeout)\n for (const link of pending) {\n link.removeEventListener('load', settle)\n link.removeEventListener('error', settle)\n }\n }\n const finish = () => {\n stop()\n callback()\n }\n\n for (const link of pending) {\n link.addEventListener('load', settle)\n // A broken stylesheet must not keep the popup blank.\n link.addEventListener('error', settle)\n }\n const timeout = window.setTimeout(finish, timeoutMs)\n return stop\n}\n\nfunction applies(link: HTMLLinkElement): boolean {\n if (/\\balternate\\b/i.test(link.rel)) return false\n const view = link.ownerDocument.defaultView\n return !link.media || !view?.matchMedia || view.matchMedia(link.media).matches\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { FC } from 'react'\nimport { createPortal } from 'react-dom'\nimport { mirrorStyles } from './copyStyles'\nimport type {\n PopupProps,\n PopupWindowApi,\n PopupWindowFeatures,\n UsePopupWindowOptions,\n} from './types'\nimport { whenStylesheetsLoad } from './whenStylesheetsLoad'\n\ninterface PopupState {\n popupWindow: Window | null\n container: HTMLElement | null\n blocked: boolean\n}\n\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\n\nclass PopupStore {\n state: PopupState = INITIAL_STATE\n private listeners = new Set<() => void>()\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getSnapshot = (): PopupState => this.state\n\n getServerSnapshot = (): PopupState => INITIAL_STATE\n\n setState(partial: Partial<PopupState>): void {\n this.state = { ...this.state, ...partial }\n for (const listener of this.listeners) listener()\n }\n}\n\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\n\nfunction buildFeatures(options: UsePopupWindowOptions): string {\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\n if (\n options.center !== false &&\n merged.left === undefined &&\n merged.top === undefined &&\n typeof merged.width === 'number' &&\n typeof merged.height === 'number'\n ) {\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\n }\n return Object.entries(merged)\n .filter(([, value]) => value !== undefined && value !== false)\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\n .join(',')\n}\n\nconst ABOUT_BLANK = 'about:blank'\n/** Interval for checking whether the page given by `url` has loaded. */\nconst LOAD_POLL_MS = 20\n/** Longest time `Popup` waits for copied stylesheets before rendering anyway. */\nconst STYLES_TIMEOUT_MS = 3000\n\n/**\n * Gives the initial `about:blank` document an HTML5 doctype, so the popup\n * lays out in standards mode like the opener instead of in quirks mode. Only\n * the parser can set the mode, so the document is written again. In Chromium\n * its `URL` then reports the opener's URL; a reload still loads `about:blank`.\n */\nfunction writeStandardsDocument(doc: Document): void {\n doc.open()\n doc.write('<!DOCTYPE html><html><head></head><body></body></html>')\n doc.close()\n}\n\n/**\n * The popup's document once the page given by `url` has replaced the initial\n * `about:blank` document and finished parsing, otherwise `null`. Throws when\n * the document is not scriptable.\n */\nfunction loadedDocument(popupWindow: Window): Document | null {\n const doc = popupWindow.document\n if (doc.URL === ABOUT_BLANK || doc.readyState === 'loading') return null\n return doc\n}\n\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\n function Popup({ children }: PopupProps) {\n const { container } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n )\n return container ? createPortal(children, container) : null\n }\n Popup.displayName = 'PopupWindow.Popup'\n return Popup\n}\n\n/**\n * Open part of your React tree in a separate browser window.\n *\n * The popup content is rendered with a portal into the popup's document, so\n * it stays part of your component tree: state, context and event handlers\n * all keep working across windows.\n *\n * ```tsx\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\n *\n * return (\n * <>\n * <button onClick={open}>Open panel</button>\n * <Popup>\n * <MyPanel />\n * </Popup>\n * </>\n * )\n * ```\n */\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\n const [store] = useState(() => new PopupStore())\n const [Popup] = useState(() => createPopupComponent(store))\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const cleanupRef = useRef<(() => void) | null>(null)\n\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\n\n // `userClosed` is true when the window was closed outside our control\n // (user hit the close button, opener unloaded) — then we must not touch it.\n const closePopup = useCallback(\n (userClosed: boolean) => {\n const { popupWindow } = store.getSnapshot()\n cleanupRef.current?.()\n cleanupRef.current = null\n if (!popupWindow) return\n if (!userClosed && !popupWindow.closed) popupWindow.close()\n store.setState({ popupWindow: null, container: null })\n optionsRef.current.onClose?.()\n },\n [store],\n )\n\n const open = useCallback((): Window | null => {\n if (typeof window === 'undefined') return null\n const current = store.getSnapshot()\n if (current.popupWindow && !current.popupWindow.closed) {\n current.popupWindow.focus()\n return current.popupWindow\n }\n\n const opts = optionsRef.current\n const url = opts.url ?? ABOUT_BLANK\n const popupWindow = window.open(url, opts.name ?? '_blank', buildFeatures(opts))\n if (!popupWindow) {\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n // Sandboxed embedders (VS Code's built-in browser, CodeSandbox/StackBlitz\n // previews, iframes sandboxed without popup-escape) open the popup with an\n // opaque origin, so reading its document throws a SecurityError. Treat\n // that as blocked instead of stranding a blank window the portal can\n // never reach.\n let initialDoc: Document\n try {\n initialDoc = popupWindow.document\n } catch {\n popupWindow.close()\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n const handleExternalClose = () => closePopup(true)\n const onPopupPagehide = () => {\n // pagehide also fires on navigation; only treat it as a close when the\n // window really is gone a tick later.\n setTimeout(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 0)\n }\n\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\n const closePoll = window.setInterval(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 250)\n\n const onOpenerPagehide = () => popupWindow.close()\n window.addEventListener('pagehide', onOpenerPagehide)\n\n let loadPoll: number | undefined\n let prepared = false\n let stopStyleSync: (() => void) | undefined\n let stopStylesWait: (() => void) | undefined\n\n cleanupRef.current = () => {\n window.clearInterval(closePoll)\n window.clearInterval(loadPoll)\n window.removeEventListener('pagehide', onOpenerPagehide)\n if (prepared) popupWindow.removeEventListener('pagehide', onPopupPagehide)\n stopStyleSync?.()\n stopStylesWait?.()\n }\n\n // Title, styles and the portal container go into the document that\n // stays: `about:blank` itself, or the page `url` loads in its place.\n const prepare = (doc: Document) => {\n prepared = true\n popupWindow.addEventListener('pagehide', onPopupPagehide)\n doc.title = opts.title ?? document.title\n let links: HTMLLinkElement[] = []\n if (opts.copyStyles !== false) {\n const styles = mirrorStyles(document, doc)\n stopStyleSync = styles.stop\n links = styles.links\n }\n const container = doc.createElement('div')\n container.setAttribute('data-popup-window-root', '')\n doc.body.appendChild(container)\n // The popup fetches copied <link> stylesheets asynchronously. Render\n // only once they have loaded, so the first paint is not unstyled.\n stopStylesWait = whenStylesheetsLoad(links, STYLES_TIMEOUT_MS, () => {\n store.setState({ container })\n opts.onOpen?.(popupWindow)\n })\n }\n\n store.setState({ popupWindow, container: null, blocked: false })\n\n if (url === ABOUT_BLANK) {\n // Before prepare(): writing the document removes event listeners on the\n // popup window.\n writeStandardsDocument(initialDoc)\n prepare(initialDoc)\n } else {\n // The window starts on an initial about:blank document and then\n // navigates to `url`. Wait until that page has replaced it and parsed.\n loadPoll = window.setInterval(() => {\n if (popupWindow.closed) {\n handleExternalClose()\n return\n }\n let doc: Document | null\n try {\n doc = loadedDocument(popupWindow)\n } catch {\n // The page went somewhere the opener cannot script, such as a\n // cross-origin redirect: nothing can ever render there.\n closePopup(false)\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return\n }\n if (!doc) return\n window.clearInterval(loadPoll)\n prepare(doc)\n }, LOAD_POLL_MS)\n }\n\n return popupWindow\n }, [store, closePopup])\n\n const close = useCallback(() => closePopup(false), [closePopup])\n\n const toggle = useCallback(() => {\n if (store.getSnapshot().popupWindow) {\n closePopup(false)\n } else {\n open()\n }\n }, [store, closePopup, open])\n\n const focus = useCallback(() => {\n const { popupWindow } = store.getSnapshot()\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\n }, [store])\n\n // Close the popup when the owning component unmounts — its portal content\n // would unmount anyway, leaving an empty window behind.\n useEffect(() => {\n return () => closePopup(false)\n }, [closePopup])\n\n return {\n open,\n close,\n toggle,\n focus,\n isOpen: state.popupWindow !== null,\n isBlocked: state.blocked,\n popupWindow: state.popupWindow,\n Popup,\n }\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -72,13 +72,30 @@ function u(e, t, n = !0) {
|
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
} catch {}
|
|
75
|
-
|
|
75
|
+
g(e);
|
|
76
76
|
})
|
|
77
77
|
};
|
|
78
78
|
s.set(e, a);
|
|
79
79
|
}, d = (e) => {
|
|
80
80
|
s.get(e)?.stop(), s.delete(e);
|
|
81
|
-
}, f = (e) => {
|
|
81
|
+
}, f = /* @__PURE__ */ new Map(), p = (e, n) => {
|
|
82
|
+
let r = t.defaultView;
|
|
83
|
+
if (!r || typeof r.CSSStyleSheet != "function" || !("adoptedStyleSheets" in t) || /\balternate\b/i.test(e.rel)) return;
|
|
84
|
+
let i = null, a = () => {
|
|
85
|
+
if (!(n.sheet !== null || !e.sheet)) try {
|
|
86
|
+
let n = Array.from(e.sheet.cssRules).map((e) => e.cssText).join("\n"), a = new r.CSSStyleSheet({
|
|
87
|
+
baseURL: e.href,
|
|
88
|
+
media: e.media || void 0
|
|
89
|
+
});
|
|
90
|
+
a.replaceSync(n), t.adoptedStyleSheets = [...t.adoptedStyleSheets, a], i = a;
|
|
91
|
+
} catch {}
|
|
92
|
+
}, o = () => {
|
|
93
|
+
e.removeEventListener("load", a), n.removeEventListener("load", o), n.removeEventListener("error", o), f.delete(e);
|
|
94
|
+
let r = i;
|
|
95
|
+
r && (t.adoptedStyleSheets = t.adoptedStyleSheets.filter((e) => e !== r)), i = null;
|
|
96
|
+
};
|
|
97
|
+
n.addEventListener("load", o), n.addEventListener("error", o), f.set(e, o), e.sheet ? a() : e.addEventListener("load", a);
|
|
98
|
+
}, m = !0, h = (e) => {
|
|
82
99
|
if (r.has(e)) return;
|
|
83
100
|
let n;
|
|
84
101
|
if (e.tagName === "LINK") {
|
|
@@ -94,19 +111,19 @@ function u(e, t, n = !0) {
|
|
|
94
111
|
let i = e.getAttribute("media");
|
|
95
112
|
i && r.setAttribute("media", i), n = r;
|
|
96
113
|
}
|
|
97
|
-
t.head.appendChild(n), r.set(e, n), e.tagName === "STYLE"
|
|
98
|
-
},
|
|
114
|
+
t.head.appendChild(n), r.set(e, n), e.tagName === "STYLE" ? u(e, n) : !m && i && p(e, n);
|
|
115
|
+
}, g = (e) => {
|
|
99
116
|
let t = r.get(e);
|
|
100
117
|
t && (t.textContent = o(e), u(e, t));
|
|
101
|
-
},
|
|
102
|
-
for (let [t, n] of r) (e === t || e.nodeType === Node.ELEMENT_NODE && e.contains(t)) && (n.remove(), r.delete(t), d(t));
|
|
103
|
-
},
|
|
118
|
+
}, _ = (e) => {
|
|
119
|
+
for (let [t, n] of r) (e === t || e.nodeType === Node.ELEMENT_NODE && e.contains(t)) && (n.remove(), r.delete(t), d(t), f.get(t)?.());
|
|
120
|
+
}, v = ["class", "style"], y = () => {
|
|
104
121
|
let n = [[e.documentElement, t.documentElement], [e.body, t.body]];
|
|
105
122
|
for (let [e, t] of n) if (!(!e || !t)) {
|
|
106
|
-
for (let n of e.attributes) (
|
|
107
|
-
for (let n of Array.from(t.attributes)) (
|
|
123
|
+
for (let n of e.attributes) (v.includes(n.name) || n.name.startsWith("data-")) && t.setAttribute(n.name, n.value);
|
|
124
|
+
for (let n of Array.from(t.attributes)) (v.includes(n.name) || n.name.startsWith("data-")) && !e.hasAttribute(n.name) && t.removeAttribute(n.name);
|
|
108
125
|
}
|
|
109
|
-
},
|
|
126
|
+
}, b = () => {
|
|
110
127
|
let n = t.defaultView;
|
|
111
128
|
if (!(!n?.CSSStyleSheet || !("adoptedStyleSheets" in e))) try {
|
|
112
129
|
t.adoptedStyleSheets = e.adoptedStyleSheets.map((e) => {
|
|
@@ -115,43 +132,45 @@ function u(e, t, n = !0) {
|
|
|
115
132
|
});
|
|
116
133
|
} catch {}
|
|
117
134
|
};
|
|
118
|
-
for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]"))
|
|
119
|
-
|
|
120
|
-
|
|
135
|
+
for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) h(t);
|
|
136
|
+
m = !1;
|
|
137
|
+
let x = Array.from(r.values()).filter((e) => e.tagName === "LINK");
|
|
138
|
+
if (y(), b(), !i) return {
|
|
121
139
|
stop: () => {},
|
|
122
|
-
links:
|
|
140
|
+
links: x
|
|
123
141
|
};
|
|
124
|
-
let
|
|
142
|
+
let S = new MutationObserver((e) => {
|
|
125
143
|
for (let t of e) {
|
|
126
144
|
if (t.type === "characterData") {
|
|
127
145
|
let e = t.target.parentElement;
|
|
128
|
-
e?.tagName === "STYLE" &&
|
|
146
|
+
e?.tagName === "STYLE" && g(e);
|
|
129
147
|
continue;
|
|
130
148
|
}
|
|
131
|
-
a(t.target) && t.target.tagName === "STYLE" &&
|
|
132
|
-
for (let e of t.addedNodes) if (a(e))
|
|
133
|
-
else if (e.nodeType === Node.ELEMENT_NODE) for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]"))
|
|
134
|
-
for (let e of t.removedNodes)
|
|
149
|
+
a(t.target) && t.target.tagName === "STYLE" && g(t.target);
|
|
150
|
+
for (let e of t.addedNodes) if (a(e)) h(e);
|
|
151
|
+
else if (e.nodeType === Node.ELEMENT_NODE) for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) h(t);
|
|
152
|
+
for (let e of t.removedNodes) _(e);
|
|
135
153
|
}
|
|
136
154
|
});
|
|
137
|
-
|
|
155
|
+
S.observe(e.head, {
|
|
138
156
|
childList: !0,
|
|
139
157
|
subtree: !0,
|
|
140
158
|
characterData: !0
|
|
141
159
|
});
|
|
142
|
-
let
|
|
143
|
-
|
|
144
|
-
let
|
|
145
|
-
for (let [e, t] of s) e.sheet === t.sheet && t.sheet.cssRules.length !== t.count &&
|
|
146
|
-
|
|
160
|
+
let C = new MutationObserver(() => y());
|
|
161
|
+
C.observe(e.documentElement, { attributes: !0 }), e.body && C.observe(e.body, { attributes: !0 });
|
|
162
|
+
let w = t.defaultView, T, E = () => {
|
|
163
|
+
for (let [e, t] of s) e.sheet === t.sheet && t.sheet.cssRules.length !== t.count && g(e);
|
|
164
|
+
T = w?.requestAnimationFrame(E);
|
|
147
165
|
};
|
|
148
|
-
return
|
|
166
|
+
return w && typeof w.requestAnimationFrame == "function" && (T = w.requestAnimationFrame(E)), {
|
|
149
167
|
stop: () => {
|
|
150
|
-
|
|
168
|
+
S.disconnect(), C.disconnect(), T !== void 0 && w?.cancelAnimationFrame(T);
|
|
151
169
|
for (let e of s.values()) e.stop();
|
|
170
|
+
for (let e of [...f.values()]) e();
|
|
152
171
|
s.clear();
|
|
153
172
|
},
|
|
154
|
-
links:
|
|
173
|
+
links: x
|
|
155
174
|
};
|
|
156
175
|
}
|
|
157
176
|
//#endregion
|
|
@@ -209,18 +228,21 @@ function g(e) {
|
|
|
209
228
|
}
|
|
210
229
|
var _ = "about:blank", v = 20, y = 3e3;
|
|
211
230
|
function b(e) {
|
|
231
|
+
e.open(), e.write("<!DOCTYPE html><html><head></head><body></body></html>"), e.close();
|
|
232
|
+
}
|
|
233
|
+
function x(e) {
|
|
212
234
|
let t = e.document;
|
|
213
235
|
return t.URL === _ || t.readyState === "loading" ? null : t;
|
|
214
236
|
}
|
|
215
|
-
function
|
|
237
|
+
function S(e) {
|
|
216
238
|
function t({ children: t }) {
|
|
217
239
|
let { container: n } = i(e.subscribe, e.getSnapshot, e.getServerSnapshot);
|
|
218
240
|
return n ? a(t, n) : null;
|
|
219
241
|
}
|
|
220
242
|
return t.displayName = "PopupWindow.Popup", t;
|
|
221
243
|
}
|
|
222
|
-
function
|
|
223
|
-
let [o] = r(() => new m()), [s] = r(() =>
|
|
244
|
+
function C(a = {}) {
|
|
245
|
+
let [o] = r(() => new m()), [s] = r(() => S(o)), c = n(a);
|
|
224
246
|
c.current = a;
|
|
225
247
|
let l = n(null), f = i(o.subscribe, o.getSnapshot, o.getServerSnapshot), p = e((e) => {
|
|
226
248
|
let { popupWindow: t } = o.getSnapshot();
|
|
@@ -248,19 +270,19 @@ function S(a = {}) {
|
|
|
248
270
|
r.closed && a();
|
|
249
271
|
}, 250), m = () => r.close();
|
|
250
272
|
window.addEventListener("pagehide", m);
|
|
251
|
-
let h,
|
|
273
|
+
let h, S = !1, C, w;
|
|
252
274
|
l.current = () => {
|
|
253
|
-
window.clearInterval(f), window.clearInterval(h), window.removeEventListener("pagehide", m),
|
|
275
|
+
window.clearInterval(f), window.clearInterval(h), window.removeEventListener("pagehide", m), S && r.removeEventListener("pagehide", s), C?.(), w?.();
|
|
254
276
|
};
|
|
255
|
-
let
|
|
256
|
-
|
|
277
|
+
let T = (e) => {
|
|
278
|
+
S = !0, r.addEventListener("pagehide", s), e.title = t.title ?? document.title;
|
|
257
279
|
let n = [];
|
|
258
280
|
if (t.copyStyles !== !1) {
|
|
259
281
|
let t = u(document, e);
|
|
260
|
-
|
|
282
|
+
C = t.stop, n = t.links;
|
|
261
283
|
}
|
|
262
284
|
let i = e.createElement("div");
|
|
263
|
-
i.setAttribute("data-popup-window-root", ""), e.body.appendChild(i),
|
|
285
|
+
i.setAttribute("data-popup-window-root", ""), e.body.appendChild(i), w = d(n, y, () => {
|
|
264
286
|
o.setState({ container: i }), t.onOpen?.(r);
|
|
265
287
|
});
|
|
266
288
|
};
|
|
@@ -268,35 +290,35 @@ function S(a = {}) {
|
|
|
268
290
|
popupWindow: r,
|
|
269
291
|
container: null,
|
|
270
292
|
blocked: !1
|
|
271
|
-
}), n === _ ?
|
|
293
|
+
}), n === _ ? (b(i), T(i)) : h = window.setInterval(() => {
|
|
272
294
|
if (r.closed) {
|
|
273
295
|
a();
|
|
274
296
|
return;
|
|
275
297
|
}
|
|
276
298
|
let e;
|
|
277
299
|
try {
|
|
278
|
-
e =
|
|
300
|
+
e = x(r);
|
|
279
301
|
} catch {
|
|
280
302
|
p(!1), o.setState({ blocked: !0 }), t.onBlocked?.();
|
|
281
303
|
return;
|
|
282
304
|
}
|
|
283
|
-
e && (window.clearInterval(h),
|
|
305
|
+
e && (window.clearInterval(h), T(e));
|
|
284
306
|
}, v), r;
|
|
285
|
-
}, [o, p]),
|
|
307
|
+
}, [o, p]), C = e(() => p(!1), [p]), w = e(() => {
|
|
286
308
|
o.getSnapshot().popupWindow ? p(!1) : h();
|
|
287
309
|
}, [
|
|
288
310
|
o,
|
|
289
311
|
p,
|
|
290
312
|
h
|
|
291
|
-
]),
|
|
313
|
+
]), T = e(() => {
|
|
292
314
|
let { popupWindow: e } = o.getSnapshot();
|
|
293
315
|
e && !e.closed && e.focus();
|
|
294
316
|
}, [o]);
|
|
295
317
|
return t(() => () => p(!1), [p]), {
|
|
296
318
|
open: h,
|
|
297
|
-
close:
|
|
298
|
-
toggle:
|
|
299
|
-
focus:
|
|
319
|
+
close: C,
|
|
320
|
+
toggle: w,
|
|
321
|
+
focus: T,
|
|
300
322
|
isOpen: f.popupWindow !== null,
|
|
301
323
|
isBlocked: f.blocked,
|
|
302
324
|
popupWindow: f.popupWindow,
|
|
@@ -304,6 +326,6 @@ function S(a = {}) {
|
|
|
304
326
|
};
|
|
305
327
|
}
|
|
306
328
|
//#endregion
|
|
307
|
-
export { o as copyStyles,
|
|
329
|
+
export { o as copyStyles, C as usePopupWindow };
|
|
308
330
|
|
|
309
331
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/copyStyles.ts","../src/whenStylesheetsLoad.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\n * in sync while the popup is open:\n *\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\n * possible, so rules injected via `insertRule` are included.\n * - Rules added to or removed from a `<style>` sheet later with\n * `insertRule`/`deleteRule` are applied to its mirror as they happen. This\n * is how CSS-in-JS libraries (Emotion, styled-components) add rules in\n * production builds, and it changes no DOM.\n * - Additions/removals/edits of style nodes in the source `<head>` are\n * observed (covers Vite HMR, lazily loaded chunk CSS, CSS-in-JS in\n * development builds).\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\n *\n * Returns a function that stops observing.\n */\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\n return mirrorStyles(source, target, watch).stop\n}\n\ntype RuleChange = { type: 'insert'; rule: string; index: number } | { type: 'delete'; index: number }\ntype RuleListener = (change: RuleChange) => void\n\nconst ruleListeners = new WeakMap<CSSStyleSheet, Set<RuleListener>>()\n\n/**\n * Calls `listener` after each `insertRule`/`deleteRule` call on `sheet`.\n * Wraps the two methods on this sheet object only, not on the prototype, and\n * restores them once the sheet's last listener is removed. Returns a function\n * that removes the listener.\n */\nfunction onRuleChange(sheet: CSSStyleSheet, listener: RuleListener): () => void {\n let listeners = ruleListeners.get(sheet)\n if (!listeners) {\n const all = new Set<RuleListener>()\n listeners = all\n ruleListeners.set(sheet, all)\n const proto = Object.getPrototypeOf(sheet) as CSSStyleSheet\n sheet.insertRule = function (this: CSSStyleSheet, rule: string, index?: number): number {\n const inserted = proto.insertRule.call(this, rule, index)\n for (const notify of [...all]) notify({ type: 'insert', rule, index: inserted })\n return inserted\n }\n sheet.deleteRule = function (this: CSSStyleSheet, index: number): void {\n proto.deleteRule.call(this, index)\n for (const notify of [...all]) notify({ type: 'delete', index })\n }\n }\n const own = listeners\n own.add(listener)\n return () => {\n own.delete(listener)\n if (own.size > 0) return\n ruleListeners.delete(sheet)\n const wrapped = sheet as { insertRule?: unknown; deleteRule?: unknown }\n delete wrapped.insertRule\n delete wrapped.deleteRule\n }\n}\n\n/** Attributes copied from a source `<link>` onto its mirror. */\nconst LINK_ATTRS = ['rel', 'media', 'title', 'crossorigin', 'referrerpolicy', 'integrity']\n\nexport interface StyleMirror {\n /** Stops observing the source document. */\n stop: () => void\n /**\n * The `<link>` elements created in `target` by the initial copy. The target\n * fetches each of them asynchronously.\n */\n links: HTMLLinkElement[]\n}\n\n/** {@link copyStyles}, plus the `<link>` elements the initial copy created. */\nexport function mirrorStyles(source: Document, target: Document, watch = true): StyleMirror {\n const mirrors = new Map<Element, Element>()\n const observing = watch && typeof MutationObserver !== 'undefined'\n\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\n if (node.nodeType !== Node.ELEMENT_NODE) return false\n const el = node as Element\n return (\n el.tagName === 'STYLE' ||\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\n )\n }\n\n const serializeStyle = (el: HTMLStyleElement): string => {\n const sheet = el.sheet\n if (sheet) {\n try {\n return Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n } catch {\n // Inaccessible cssRules — fall back to the raw text below.\n }\n }\n return el.textContent ?? ''\n }\n\n interface RuleSync {\n sheet: CSSStyleSheet\n /** Rule count of `sheet` that the mirror reflects. */\n count: number\n stop: () => void\n }\n const ruleSyncs = new Map<HTMLStyleElement, RuleSync>()\n\n // CSS-in-JS libraries in production builds add rules with insertRule, which\n // changes no DOM. Apply each change to the mirror as it happens, so content\n // that renders in the same task, such as the popup's first render, is\n // already styled.\n const syncRules = (el: HTMLStyleElement, clone: HTMLStyleElement): void => {\n const sheet = el.sheet\n const current = ruleSyncs.get(el)\n if (current && current.sheet === sheet) {\n current.count = sheet.cssRules.length\n return\n }\n current?.stop()\n ruleSyncs.delete(el)\n if (!observing || !sheet) return\n const sync: RuleSync = {\n sheet,\n count: sheet.cssRules.length,\n stop: onRuleChange(sheet, (change) => {\n const copy = clone.sheet\n try {\n if (copy) {\n if (change.type === 'insert') copy.insertRule(change.rule, change.index)\n else copy.deleteRule(change.index)\n if (copy.cssRules.length === sheet.cssRules.length) {\n sync.count = sheet.cssRules.length\n return\n }\n }\n } catch {\n // Out of step with the source - copy the whole sheet again below.\n }\n refresh(el)\n }),\n }\n ruleSyncs.set(el, sync)\n }\n\n const unsyncRules = (el: Element): void => {\n ruleSyncs.get(el as HTMLStyleElement)?.stop()\n ruleSyncs.delete(el as HTMLStyleElement)\n }\n\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\n if (mirrors.has(el)) return\n let clone: Element\n if (el.tagName === 'LINK') {\n const link = target.createElement('link')\n // `rel` is copied as is, so an alternate stylesheet stays disabled.\n // `crossorigin` and `referrerpolicy` make the popup send the same request\n // as the opener, so it can reuse the cached response.\n for (const name of LINK_ATTRS) {\n const value = el.getAttribute(name)\n if (value !== null) link.setAttribute(name, value)\n }\n // .href resolves to an absolute URL, so relative hrefs keep working\n // from the popup's `about:blank` document.\n link.href = (el as HTMLLinkElement).href\n clone = link\n } else {\n const style = target.createElement('style')\n style.textContent = serializeStyle(el as HTMLStyleElement)\n const media = el.getAttribute('media')\n if (media) style.setAttribute('media', media)\n clone = style\n }\n target.head.appendChild(clone)\n mirrors.set(el, clone)\n if (el.tagName === 'STYLE') syncRules(el as HTMLStyleElement, clone as HTMLStyleElement)\n }\n\n const refresh = (el: HTMLStyleElement): void => {\n const clone = mirrors.get(el)\n if (!clone) return\n clone.textContent = serializeStyle(el)\n // New text gives the source a new sheet object.\n syncRules(el, clone as HTMLStyleElement)\n }\n\n const unmirrorSubtree = (removed: Node): void => {\n for (const [src, clone] of mirrors) {\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\n clone.remove()\n mirrors.delete(src)\n unsyncRules(src)\n }\n }\n }\n\n const ROOT_ATTRS = ['class', 'style']\n const syncRootAttrs = (): void => {\n const pairs: Array<[Element | null, Element | null]> = [\n [source.documentElement, target.documentElement],\n [source.body, target.body],\n ]\n for (const [from, to] of pairs) {\n if (!from || !to) continue\n for (const attr of from.attributes) {\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\n to.setAttribute(attr.name, attr.value)\n }\n }\n for (const attr of Array.from(to.attributes)) {\n if (\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\n !from.hasAttribute(attr.name)\n ) {\n to.removeAttribute(attr.name)\n }\n }\n }\n }\n\n const syncAdoptedSheets = (): void => {\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\n try {\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\n const copy = new targetWindow.CSSStyleSheet!()\n copy.replaceSync(\n Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n'),\n )\n return copy\n })\n } catch {\n // Best effort — older browsers or cross-origin rules.\n }\n }\n\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n const links = Array.from(mirrors.values()).filter(\n (clone): clone is HTMLLinkElement => clone.tagName === 'LINK',\n )\n syncRootAttrs()\n syncAdoptedSheets()\n\n if (!observing) {\n return { stop: () => {}, links }\n }\n\n const headObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'characterData') {\n const parent = record.target.parentElement\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\n continue\n }\n // childList\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\n // textContent assignment replaces the text node → childList on <style>\n refresh(record.target as HTMLStyleElement)\n }\n for (const added of record.addedNodes) {\n if (isStyleNode(added)) {\n mirror(added)\n } else if (added.nodeType === Node.ELEMENT_NODE) {\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n }\n }\n for (const removed of record.removedNodes) {\n unmirrorSubtree(removed)\n }\n }\n })\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\n\n const rootObserver = new MutationObserver(() => syncRootAttrs())\n rootObserver.observe(source.documentElement, { attributes: true })\n if (source.body) rootObserver.observe(source.body, { attributes: true })\n\n // Fallback for rule changes that bypass the sheet's own methods, such as\n // CSSStyleSheet.prototype.insertRule.call(sheet, ...): once per frame of the\n // target window, copy again any sheet whose rule count changed.\n const view = target.defaultView\n let frame: number | undefined\n const checkRuleCounts = (): void => {\n for (const [el, sync] of ruleSyncs) {\n if (el.sheet === sync.sheet && sync.sheet.cssRules.length !== sync.count) refresh(el)\n }\n frame = view?.requestAnimationFrame(checkRuleCounts)\n }\n if (view && typeof view.requestAnimationFrame === 'function') {\n frame = view.requestAnimationFrame(checkRuleCounts)\n }\n\n return {\n stop: () => {\n headObserver.disconnect()\n rootObserver.disconnect()\n if (frame !== undefined) view?.cancelAnimationFrame(frame)\n for (const sync of ruleSyncs.values()) sync.stop()\n ruleSyncs.clear()\n },\n links,\n }\n}\n","/**\n * Calls `callback` once every stylesheet in `links` that applies has loaded\n * or failed, or after `timeoutMs`, whichever comes first.\n *\n * When none of them is still loading, `callback` runs synchronously, before\n * this function returns. Links that do not apply are not waited for: an\n * alternate stylesheet, or a `media` query that does not match, such as\n * `print`. Browsers fetch those at the lowest priority.\n *\n * Returns a function that stops waiting without calling `callback`.\n */\nexport function whenStylesheetsLoad(\n links: readonly HTMLLinkElement[],\n timeoutMs: number,\n callback: () => void,\n): () => void {\n const pending = links.filter((link) => link.sheet === null && applies(link))\n if (pending.length === 0) {\n callback()\n return () => {}\n }\n\n let remaining = pending.length\n const settle = () => {\n remaining--\n if (remaining === 0) finish()\n }\n const stop = () => {\n window.clearTimeout(timeout)\n for (const link of pending) {\n link.removeEventListener('load', settle)\n link.removeEventListener('error', settle)\n }\n }\n const finish = () => {\n stop()\n callback()\n }\n\n for (const link of pending) {\n link.addEventListener('load', settle)\n // A broken stylesheet must not keep the popup blank.\n link.addEventListener('error', settle)\n }\n const timeout = window.setTimeout(finish, timeoutMs)\n return stop\n}\n\nfunction applies(link: HTMLLinkElement): boolean {\n if (/\\balternate\\b/i.test(link.rel)) return false\n const view = link.ownerDocument.defaultView\n return !link.media || !view?.matchMedia || view.matchMedia(link.media).matches\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { FC } from 'react'\nimport { createPortal } from 'react-dom'\nimport { mirrorStyles } from './copyStyles'\nimport type {\n PopupProps,\n PopupWindowApi,\n PopupWindowFeatures,\n UsePopupWindowOptions,\n} from './types'\nimport { whenStylesheetsLoad } from './whenStylesheetsLoad'\n\ninterface PopupState {\n popupWindow: Window | null\n container: HTMLElement | null\n blocked: boolean\n}\n\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\n\nclass PopupStore {\n state: PopupState = INITIAL_STATE\n private listeners = new Set<() => void>()\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getSnapshot = (): PopupState => this.state\n\n getServerSnapshot = (): PopupState => INITIAL_STATE\n\n setState(partial: Partial<PopupState>): void {\n this.state = { ...this.state, ...partial }\n for (const listener of this.listeners) listener()\n }\n}\n\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\n\nfunction buildFeatures(options: UsePopupWindowOptions): string {\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\n if (\n options.center !== false &&\n merged.left === undefined &&\n merged.top === undefined &&\n typeof merged.width === 'number' &&\n typeof merged.height === 'number'\n ) {\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\n }\n return Object.entries(merged)\n .filter(([, value]) => value !== undefined && value !== false)\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\n .join(',')\n}\n\nconst ABOUT_BLANK = 'about:blank'\n/** Interval for checking whether the page given by `url` has loaded. */\nconst LOAD_POLL_MS = 20\n/** Longest time `Popup` waits for copied stylesheets before rendering anyway. */\nconst STYLES_TIMEOUT_MS = 3000\n\n/**\n * The popup's document once the page given by `url` has replaced the initial\n * `about:blank` document and finished parsing, otherwise `null`. Throws when\n * the document is not scriptable.\n */\nfunction loadedDocument(popupWindow: Window): Document | null {\n const doc = popupWindow.document\n if (doc.URL === ABOUT_BLANK || doc.readyState === 'loading') return null\n return doc\n}\n\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\n function Popup({ children }: PopupProps) {\n const { container } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n )\n return container ? createPortal(children, container) : null\n }\n Popup.displayName = 'PopupWindow.Popup'\n return Popup\n}\n\n/**\n * Open part of your React tree in a separate browser window.\n *\n * The popup content is rendered with a portal into the popup's document, so\n * it stays part of your component tree: state, context and event handlers\n * all keep working across windows.\n *\n * ```tsx\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\n *\n * return (\n * <>\n * <button onClick={open}>Open panel</button>\n * <Popup>\n * <MyPanel />\n * </Popup>\n * </>\n * )\n * ```\n */\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\n const [store] = useState(() => new PopupStore())\n const [Popup] = useState(() => createPopupComponent(store))\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const cleanupRef = useRef<(() => void) | null>(null)\n\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\n\n // `userClosed` is true when the window was closed outside our control\n // (user hit the close button, opener unloaded) — then we must not touch it.\n const closePopup = useCallback(\n (userClosed: boolean) => {\n const { popupWindow } = store.getSnapshot()\n cleanupRef.current?.()\n cleanupRef.current = null\n if (!popupWindow) return\n if (!userClosed && !popupWindow.closed) popupWindow.close()\n store.setState({ popupWindow: null, container: null })\n optionsRef.current.onClose?.()\n },\n [store],\n )\n\n const open = useCallback((): Window | null => {\n if (typeof window === 'undefined') return null\n const current = store.getSnapshot()\n if (current.popupWindow && !current.popupWindow.closed) {\n current.popupWindow.focus()\n return current.popupWindow\n }\n\n const opts = optionsRef.current\n const url = opts.url ?? ABOUT_BLANK\n const popupWindow = window.open(url, opts.name ?? '_blank', buildFeatures(opts))\n if (!popupWindow) {\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n // Sandboxed embedders (VS Code's built-in browser, CodeSandbox/StackBlitz\n // previews, iframes sandboxed without popup-escape) open the popup with an\n // opaque origin, so reading its document throws a SecurityError. Treat\n // that as blocked instead of stranding a blank window the portal can\n // never reach.\n let initialDoc: Document\n try {\n initialDoc = popupWindow.document\n } catch {\n popupWindow.close()\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n const handleExternalClose = () => closePopup(true)\n const onPopupPagehide = () => {\n // pagehide also fires on navigation; only treat it as a close when the\n // window really is gone a tick later.\n setTimeout(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 0)\n }\n\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\n const closePoll = window.setInterval(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 250)\n\n const onOpenerPagehide = () => popupWindow.close()\n window.addEventListener('pagehide', onOpenerPagehide)\n\n let loadPoll: number | undefined\n let prepared = false\n let stopStyleSync: (() => void) | undefined\n let stopStylesWait: (() => void) | undefined\n\n cleanupRef.current = () => {\n window.clearInterval(closePoll)\n window.clearInterval(loadPoll)\n window.removeEventListener('pagehide', onOpenerPagehide)\n if (prepared) popupWindow.removeEventListener('pagehide', onPopupPagehide)\n stopStyleSync?.()\n stopStylesWait?.()\n }\n\n // Title, styles and the portal container go into the document that\n // stays: `about:blank` itself, or the page `url` loads in its place.\n const prepare = (doc: Document) => {\n prepared = true\n popupWindow.addEventListener('pagehide', onPopupPagehide)\n doc.title = opts.title ?? document.title\n let links: HTMLLinkElement[] = []\n if (opts.copyStyles !== false) {\n const styles = mirrorStyles(document, doc)\n stopStyleSync = styles.stop\n links = styles.links\n }\n const container = doc.createElement('div')\n container.setAttribute('data-popup-window-root', '')\n doc.body.appendChild(container)\n // The popup fetches copied <link> stylesheets asynchronously. Render\n // only once they have loaded, so the first paint is not unstyled.\n stopStylesWait = whenStylesheetsLoad(links, STYLES_TIMEOUT_MS, () => {\n store.setState({ container })\n opts.onOpen?.(popupWindow)\n })\n }\n\n store.setState({ popupWindow, container: null, blocked: false })\n\n if (url === ABOUT_BLANK) {\n prepare(initialDoc)\n } else {\n // The window starts on an initial about:blank document and then\n // navigates to `url`. Wait until that page has replaced it and parsed.\n loadPoll = window.setInterval(() => {\n if (popupWindow.closed) {\n handleExternalClose()\n return\n }\n let doc: Document | null\n try {\n doc = loadedDocument(popupWindow)\n } catch {\n // The page went somewhere the opener cannot script, such as a\n // cross-origin redirect: nothing can ever render there.\n closePopup(false)\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return\n }\n if (!doc) return\n window.clearInterval(loadPoll)\n prepare(doc)\n }, LOAD_POLL_MS)\n }\n\n return popupWindow\n }, [store, closePopup])\n\n const close = useCallback(() => closePopup(false), [closePopup])\n\n const toggle = useCallback(() => {\n if (store.getSnapshot().popupWindow) {\n closePopup(false)\n } else {\n open()\n }\n }, [store, closePopup, open])\n\n const focus = useCallback(() => {\n const { popupWindow } = store.getSnapshot()\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\n }, [store])\n\n // Close the popup when the owning component unmounts — its portal content\n // would unmount anyway, leaving an empty window behind.\n useEffect(() => {\n return () => closePopup(false)\n }, [closePopup])\n\n return {\n open,\n close,\n toggle,\n focus,\n isOpen: state.popupWindow !== null,\n isBlocked: state.blocked,\n popupWindow: state.popupWindow,\n Popup,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/copyStyles.ts","../src/whenStylesheetsLoad.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\n * in sync while the popup is open:\n *\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\n * possible, so rules injected via `insertRule` are included.\n * - Rules added to or removed from a `<style>` sheet later with\n * `insertRule`/`deleteRule` are applied to its mirror as they happen. This\n * is how CSS-in-JS libraries (Emotion, styled-components) add rules in\n * production builds, and it changes no DOM.\n * - Additions/removals/edits of style nodes in the source `<head>` are\n * observed (covers Vite HMR, lazily loaded chunk CSS, CSS-in-JS in\n * development builds).\n * - A `<link>` added while watching is loaded by the target again. Until\n * that copy has loaded, the rules the source already loaded are applied\n * to the target through a constructed stylesheet.\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\n *\n * Returns a function that stops observing.\n */\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\n return mirrorStyles(source, target, watch).stop\n}\n\ntype RuleChange = { type: 'insert'; rule: string; index: number } | { type: 'delete'; index: number }\ntype RuleListener = (change: RuleChange) => void\n\nconst ruleListeners = new WeakMap<CSSStyleSheet, Set<RuleListener>>()\n\n/**\n * Calls `listener` after each `insertRule`/`deleteRule` call on `sheet`.\n * Wraps the two methods on this sheet object only, not on the prototype, and\n * restores them once the sheet's last listener is removed. Returns a function\n * that removes the listener.\n */\nfunction onRuleChange(sheet: CSSStyleSheet, listener: RuleListener): () => void {\n let listeners = ruleListeners.get(sheet)\n if (!listeners) {\n const all = new Set<RuleListener>()\n listeners = all\n ruleListeners.set(sheet, all)\n const proto = Object.getPrototypeOf(sheet) as CSSStyleSheet\n sheet.insertRule = function (this: CSSStyleSheet, rule: string, index?: number): number {\n const inserted = proto.insertRule.call(this, rule, index)\n for (const notify of [...all]) notify({ type: 'insert', rule, index: inserted })\n return inserted\n }\n sheet.deleteRule = function (this: CSSStyleSheet, index: number): void {\n proto.deleteRule.call(this, index)\n for (const notify of [...all]) notify({ type: 'delete', index })\n }\n }\n const own = listeners\n own.add(listener)\n return () => {\n own.delete(listener)\n if (own.size > 0) return\n ruleListeners.delete(sheet)\n const wrapped = sheet as { insertRule?: unknown; deleteRule?: unknown }\n delete wrapped.insertRule\n delete wrapped.deleteRule\n }\n}\n\n/** Attributes copied from a source `<link>` onto its mirror. */\nconst LINK_ATTRS = ['rel', 'media', 'title', 'crossorigin', 'referrerpolicy', 'integrity']\n\nexport interface StyleMirror {\n /** Stops observing the source document. */\n stop: () => void\n /**\n * The `<link>` elements created in `target` by the initial copy. The target\n * fetches each of them asynchronously.\n */\n links: HTMLLinkElement[]\n}\n\n/** {@link copyStyles}, plus the `<link>` elements the initial copy created. */\nexport function mirrorStyles(source: Document, target: Document, watch = true): StyleMirror {\n const mirrors = new Map<Element, Element>()\n const observing = watch && typeof MutationObserver !== 'undefined'\n\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\n if (node.nodeType !== Node.ELEMENT_NODE) return false\n const el = node as Element\n return (\n el.tagName === 'STYLE' ||\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\n )\n }\n\n const serializeStyle = (el: HTMLStyleElement): string => {\n const sheet = el.sheet\n if (sheet) {\n try {\n return Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n } catch {\n // Inaccessible cssRules — fall back to the raw text below.\n }\n }\n return el.textContent ?? ''\n }\n\n interface RuleSync {\n sheet: CSSStyleSheet\n /** Rule count of `sheet` that the mirror reflects. */\n count: number\n stop: () => void\n }\n const ruleSyncs = new Map<HTMLStyleElement, RuleSync>()\n\n // CSS-in-JS libraries in production builds add rules with insertRule, which\n // changes no DOM. Apply each change to the mirror as it happens, so content\n // that renders in the same task, such as the popup's first render, is\n // already styled.\n const syncRules = (el: HTMLStyleElement, clone: HTMLStyleElement): void => {\n const sheet = el.sheet\n const current = ruleSyncs.get(el)\n if (current && current.sheet === sheet) {\n current.count = sheet.cssRules.length\n return\n }\n current?.stop()\n ruleSyncs.delete(el)\n if (!observing || !sheet) return\n const sync: RuleSync = {\n sheet,\n count: sheet.cssRules.length,\n stop: onRuleChange(sheet, (change) => {\n const copy = clone.sheet\n try {\n if (copy) {\n if (change.type === 'insert') copy.insertRule(change.rule, change.index)\n else copy.deleteRule(change.index)\n if (copy.cssRules.length === sheet.cssRules.length) {\n sync.count = sheet.cssRules.length\n return\n }\n }\n } catch {\n // Out of step with the source - copy the whole sheet again below.\n }\n refresh(el)\n }),\n }\n ruleSyncs.set(el, sync)\n }\n\n const unsyncRules = (el: Element): void => {\n ruleSyncs.get(el as HTMLStyleElement)?.stop()\n ruleSyncs.delete(el as HTMLStyleElement)\n }\n\n // A <link> added while watching, such as lazily loaded chunk CSS: the\n // target loads its copy separately, and content that waits only for the\n // source's copy, as bundlers do, can render before the target's has\n // loaded. Until it has, apply the rules the source already parsed through a\n // constructed sheet. `baseURL` keeps relative url()s resolving against the\n // stylesheet. Adopted sheets come last in the cascade, which can change\n // precedence for that short time. Unreadable cross-origin sheets are left\n // to load normally.\n const unbridgeLinks = new Map<Element, () => void>()\n const bridgeLink = (el: HTMLLinkElement, clone: HTMLLinkElement): void => {\n const view = target.defaultView as (Window & typeof globalThis) | null\n if (!view || typeof view.CSSStyleSheet !== 'function' || !('adoptedStyleSheets' in target)) {\n return\n }\n if (/\\balternate\\b/i.test(el.rel)) return\n let bridge: CSSStyleSheet | null = null\n const add = (): void => {\n if (clone.sheet !== null || !el.sheet) return\n try {\n const text = Array.from(el.sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n const sheet = new view.CSSStyleSheet({ baseURL: el.href, media: el.media || undefined })\n sheet.replaceSync(text)\n target.adoptedStyleSheets = [...target.adoptedStyleSheets, sheet]\n bridge = sheet\n } catch {\n // Cross-origin rules or no constructable stylesheets - wait for the copy.\n }\n }\n const remove = (): void => {\n el.removeEventListener('load', add)\n clone.removeEventListener('load', remove)\n clone.removeEventListener('error', remove)\n unbridgeLinks.delete(el)\n const sheet = bridge\n if (sheet) target.adoptedStyleSheets = target.adoptedStyleSheets.filter((s) => s !== sheet)\n bridge = null\n }\n clone.addEventListener('load', remove)\n clone.addEventListener('error', remove)\n unbridgeLinks.set(el, remove)\n if (el.sheet) add()\n else el.addEventListener('load', add)\n }\n\n // True during the initial copy. `usePopupWindow` waits for those links\n // itself before rendering.\n let copying = true\n\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\n if (mirrors.has(el)) return\n let clone: Element\n if (el.tagName === 'LINK') {\n const link = target.createElement('link')\n // `rel` is copied as is, so an alternate stylesheet stays disabled.\n // `crossorigin` and `referrerpolicy` make the popup send the same request\n // as the opener, so it can reuse the cached response.\n for (const name of LINK_ATTRS) {\n const value = el.getAttribute(name)\n if (value !== null) link.setAttribute(name, value)\n }\n // .href resolves to an absolute URL, so relative hrefs keep working\n // from the popup's `about:blank` document.\n link.href = (el as HTMLLinkElement).href\n clone = link\n } else {\n const style = target.createElement('style')\n style.textContent = serializeStyle(el as HTMLStyleElement)\n const media = el.getAttribute('media')\n if (media) style.setAttribute('media', media)\n clone = style\n }\n target.head.appendChild(clone)\n mirrors.set(el, clone)\n if (el.tagName === 'STYLE') syncRules(el as HTMLStyleElement, clone as HTMLStyleElement)\n else if (!copying && observing) bridgeLink(el as HTMLLinkElement, clone as HTMLLinkElement)\n }\n\n const refresh = (el: HTMLStyleElement): void => {\n const clone = mirrors.get(el)\n if (!clone) return\n clone.textContent = serializeStyle(el)\n // New text gives the source a new sheet object.\n syncRules(el, clone as HTMLStyleElement)\n }\n\n const unmirrorSubtree = (removed: Node): void => {\n for (const [src, clone] of mirrors) {\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\n clone.remove()\n mirrors.delete(src)\n unsyncRules(src)\n unbridgeLinks.get(src)?.()\n }\n }\n }\n\n const ROOT_ATTRS = ['class', 'style']\n const syncRootAttrs = (): void => {\n const pairs: Array<[Element | null, Element | null]> = [\n [source.documentElement, target.documentElement],\n [source.body, target.body],\n ]\n for (const [from, to] of pairs) {\n if (!from || !to) continue\n for (const attr of from.attributes) {\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\n to.setAttribute(attr.name, attr.value)\n }\n }\n for (const attr of Array.from(to.attributes)) {\n if (\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\n !from.hasAttribute(attr.name)\n ) {\n to.removeAttribute(attr.name)\n }\n }\n }\n }\n\n const syncAdoptedSheets = (): void => {\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\n try {\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\n const copy = new targetWindow.CSSStyleSheet!()\n copy.replaceSync(\n Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n'),\n )\n return copy\n })\n } catch {\n // Best effort — older browsers or cross-origin rules.\n }\n }\n\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n copying = false\n const links = Array.from(mirrors.values()).filter(\n (clone): clone is HTMLLinkElement => clone.tagName === 'LINK',\n )\n syncRootAttrs()\n syncAdoptedSheets()\n\n if (!observing) {\n return { stop: () => {}, links }\n }\n\n const headObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'characterData') {\n const parent = record.target.parentElement\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\n continue\n }\n // childList\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\n // textContent assignment replaces the text node → childList on <style>\n refresh(record.target as HTMLStyleElement)\n }\n for (const added of record.addedNodes) {\n if (isStyleNode(added)) {\n mirror(added)\n } else if (added.nodeType === Node.ELEMENT_NODE) {\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n }\n }\n for (const removed of record.removedNodes) {\n unmirrorSubtree(removed)\n }\n }\n })\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\n\n const rootObserver = new MutationObserver(() => syncRootAttrs())\n rootObserver.observe(source.documentElement, { attributes: true })\n if (source.body) rootObserver.observe(source.body, { attributes: true })\n\n // Fallback for rule changes that bypass the sheet's own methods, such as\n // CSSStyleSheet.prototype.insertRule.call(sheet, ...): once per frame of the\n // target window, copy again any sheet whose rule count changed.\n const view = target.defaultView\n let frame: number | undefined\n const checkRuleCounts = (): void => {\n for (const [el, sync] of ruleSyncs) {\n if (el.sheet === sync.sheet && sync.sheet.cssRules.length !== sync.count) refresh(el)\n }\n frame = view?.requestAnimationFrame(checkRuleCounts)\n }\n if (view && typeof view.requestAnimationFrame === 'function') {\n frame = view.requestAnimationFrame(checkRuleCounts)\n }\n\n return {\n stop: () => {\n headObserver.disconnect()\n rootObserver.disconnect()\n if (frame !== undefined) view?.cancelAnimationFrame(frame)\n for (const sync of ruleSyncs.values()) sync.stop()\n for (const remove of [...unbridgeLinks.values()]) remove()\n ruleSyncs.clear()\n },\n links,\n }\n}\n","/**\n * Calls `callback` once every stylesheet in `links` that applies has loaded\n * or failed, or after `timeoutMs`, whichever comes first.\n *\n * When none of them is still loading, `callback` runs synchronously, before\n * this function returns. Links that do not apply are not waited for: an\n * alternate stylesheet, or a `media` query that does not match, such as\n * `print`. Browsers fetch those at the lowest priority.\n *\n * Returns a function that stops waiting without calling `callback`.\n */\nexport function whenStylesheetsLoad(\n links: readonly HTMLLinkElement[],\n timeoutMs: number,\n callback: () => void,\n): () => void {\n const pending = links.filter((link) => link.sheet === null && applies(link))\n if (pending.length === 0) {\n callback()\n return () => {}\n }\n\n let remaining = pending.length\n const settle = () => {\n remaining--\n if (remaining === 0) finish()\n }\n const stop = () => {\n window.clearTimeout(timeout)\n for (const link of pending) {\n link.removeEventListener('load', settle)\n link.removeEventListener('error', settle)\n }\n }\n const finish = () => {\n stop()\n callback()\n }\n\n for (const link of pending) {\n link.addEventListener('load', settle)\n // A broken stylesheet must not keep the popup blank.\n link.addEventListener('error', settle)\n }\n const timeout = window.setTimeout(finish, timeoutMs)\n return stop\n}\n\nfunction applies(link: HTMLLinkElement): boolean {\n if (/\\balternate\\b/i.test(link.rel)) return false\n const view = link.ownerDocument.defaultView\n return !link.media || !view?.matchMedia || view.matchMedia(link.media).matches\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { FC } from 'react'\nimport { createPortal } from 'react-dom'\nimport { mirrorStyles } from './copyStyles'\nimport type {\n PopupProps,\n PopupWindowApi,\n PopupWindowFeatures,\n UsePopupWindowOptions,\n} from './types'\nimport { whenStylesheetsLoad } from './whenStylesheetsLoad'\n\ninterface PopupState {\n popupWindow: Window | null\n container: HTMLElement | null\n blocked: boolean\n}\n\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\n\nclass PopupStore {\n state: PopupState = INITIAL_STATE\n private listeners = new Set<() => void>()\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getSnapshot = (): PopupState => this.state\n\n getServerSnapshot = (): PopupState => INITIAL_STATE\n\n setState(partial: Partial<PopupState>): void {\n this.state = { ...this.state, ...partial }\n for (const listener of this.listeners) listener()\n }\n}\n\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\n\nfunction buildFeatures(options: UsePopupWindowOptions): string {\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\n if (\n options.center !== false &&\n merged.left === undefined &&\n merged.top === undefined &&\n typeof merged.width === 'number' &&\n typeof merged.height === 'number'\n ) {\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\n }\n return Object.entries(merged)\n .filter(([, value]) => value !== undefined && value !== false)\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\n .join(',')\n}\n\nconst ABOUT_BLANK = 'about:blank'\n/** Interval for checking whether the page given by `url` has loaded. */\nconst LOAD_POLL_MS = 20\n/** Longest time `Popup` waits for copied stylesheets before rendering anyway. */\nconst STYLES_TIMEOUT_MS = 3000\n\n/**\n * Gives the initial `about:blank` document an HTML5 doctype, so the popup\n * lays out in standards mode like the opener instead of in quirks mode. Only\n * the parser can set the mode, so the document is written again. In Chromium\n * its `URL` then reports the opener's URL; a reload still loads `about:blank`.\n */\nfunction writeStandardsDocument(doc: Document): void {\n doc.open()\n doc.write('<!DOCTYPE html><html><head></head><body></body></html>')\n doc.close()\n}\n\n/**\n * The popup's document once the page given by `url` has replaced the initial\n * `about:blank` document and finished parsing, otherwise `null`. Throws when\n * the document is not scriptable.\n */\nfunction loadedDocument(popupWindow: Window): Document | null {\n const doc = popupWindow.document\n if (doc.URL === ABOUT_BLANK || doc.readyState === 'loading') return null\n return doc\n}\n\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\n function Popup({ children }: PopupProps) {\n const { container } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n )\n return container ? createPortal(children, container) : null\n }\n Popup.displayName = 'PopupWindow.Popup'\n return Popup\n}\n\n/**\n * Open part of your React tree in a separate browser window.\n *\n * The popup content is rendered with a portal into the popup's document, so\n * it stays part of your component tree: state, context and event handlers\n * all keep working across windows.\n *\n * ```tsx\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\n *\n * return (\n * <>\n * <button onClick={open}>Open panel</button>\n * <Popup>\n * <MyPanel />\n * </Popup>\n * </>\n * )\n * ```\n */\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\n const [store] = useState(() => new PopupStore())\n const [Popup] = useState(() => createPopupComponent(store))\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const cleanupRef = useRef<(() => void) | null>(null)\n\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\n\n // `userClosed` is true when the window was closed outside our control\n // (user hit the close button, opener unloaded) — then we must not touch it.\n const closePopup = useCallback(\n (userClosed: boolean) => {\n const { popupWindow } = store.getSnapshot()\n cleanupRef.current?.()\n cleanupRef.current = null\n if (!popupWindow) return\n if (!userClosed && !popupWindow.closed) popupWindow.close()\n store.setState({ popupWindow: null, container: null })\n optionsRef.current.onClose?.()\n },\n [store],\n )\n\n const open = useCallback((): Window | null => {\n if (typeof window === 'undefined') return null\n const current = store.getSnapshot()\n if (current.popupWindow && !current.popupWindow.closed) {\n current.popupWindow.focus()\n return current.popupWindow\n }\n\n const opts = optionsRef.current\n const url = opts.url ?? ABOUT_BLANK\n const popupWindow = window.open(url, opts.name ?? '_blank', buildFeatures(opts))\n if (!popupWindow) {\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n // Sandboxed embedders (VS Code's built-in browser, CodeSandbox/StackBlitz\n // previews, iframes sandboxed without popup-escape) open the popup with an\n // opaque origin, so reading its document throws a SecurityError. Treat\n // that as blocked instead of stranding a blank window the portal can\n // never reach.\n let initialDoc: Document\n try {\n initialDoc = popupWindow.document\n } catch {\n popupWindow.close()\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n const handleExternalClose = () => closePopup(true)\n const onPopupPagehide = () => {\n // pagehide also fires on navigation; only treat it as a close when the\n // window really is gone a tick later.\n setTimeout(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 0)\n }\n\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\n const closePoll = window.setInterval(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 250)\n\n const onOpenerPagehide = () => popupWindow.close()\n window.addEventListener('pagehide', onOpenerPagehide)\n\n let loadPoll: number | undefined\n let prepared = false\n let stopStyleSync: (() => void) | undefined\n let stopStylesWait: (() => void) | undefined\n\n cleanupRef.current = () => {\n window.clearInterval(closePoll)\n window.clearInterval(loadPoll)\n window.removeEventListener('pagehide', onOpenerPagehide)\n if (prepared) popupWindow.removeEventListener('pagehide', onPopupPagehide)\n stopStyleSync?.()\n stopStylesWait?.()\n }\n\n // Title, styles and the portal container go into the document that\n // stays: `about:blank` itself, or the page `url` loads in its place.\n const prepare = (doc: Document) => {\n prepared = true\n popupWindow.addEventListener('pagehide', onPopupPagehide)\n doc.title = opts.title ?? document.title\n let links: HTMLLinkElement[] = []\n if (opts.copyStyles !== false) {\n const styles = mirrorStyles(document, doc)\n stopStyleSync = styles.stop\n links = styles.links\n }\n const container = doc.createElement('div')\n container.setAttribute('data-popup-window-root', '')\n doc.body.appendChild(container)\n // The popup fetches copied <link> stylesheets asynchronously. Render\n // only once they have loaded, so the first paint is not unstyled.\n stopStylesWait = whenStylesheetsLoad(links, STYLES_TIMEOUT_MS, () => {\n store.setState({ container })\n opts.onOpen?.(popupWindow)\n })\n }\n\n store.setState({ popupWindow, container: null, blocked: false })\n\n if (url === ABOUT_BLANK) {\n // Before prepare(): writing the document removes event listeners on the\n // popup window.\n writeStandardsDocument(initialDoc)\n prepare(initialDoc)\n } else {\n // The window starts on an initial about:blank document and then\n // navigates to `url`. Wait until that page has replaced it and parsed.\n loadPoll = window.setInterval(() => {\n if (popupWindow.closed) {\n handleExternalClose()\n return\n }\n let doc: Document | null\n try {\n doc = loadedDocument(popupWindow)\n } catch {\n // The page went somewhere the opener cannot script, such as a\n // cross-origin redirect: nothing can ever render there.\n closePopup(false)\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return\n }\n if (!doc) return\n window.clearInterval(loadPoll)\n prepare(doc)\n }, LOAD_POLL_MS)\n }\n\n return popupWindow\n }, [store, closePopup])\n\n const close = useCallback(() => closePopup(false), [closePopup])\n\n const toggle = useCallback(() => {\n if (store.getSnapshot().popupWindow) {\n closePopup(false)\n } else {\n open()\n }\n }, [store, closePopup, open])\n\n const focus = useCallback(() => {\n const { popupWindow } = store.getSnapshot()\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\n }, [store])\n\n // Close the popup when the owning component unmounts — its portal content\n // would unmount anyway, leaving an empty window behind.\n useEffect(() => {\n return () => closePopup(false)\n }, [closePopup])\n\n return {\n open,\n close,\n toggle,\n focus,\n isOpen: state.popupWindow !== null,\n isBlocked: state.blocked,\n popupWindow: state.popupWindow,\n Popup,\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePopupWindow.d.ts","sourceRoot":"","sources":["../src/usePopupWindow.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,cAAc,EAEd,qBAAqB,EACtB,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"usePopupWindow.d.ts","sourceRoot":"","sources":["../src/usePopupWindow.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,cAAc,EAEd,qBAAqB,EACtB,MAAM,SAAS,CAAA;AA4FhB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CAkLlF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jielga/react-popup-window",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "React hook to open part of your UI in a separate browser popup window — live portal rendering, style syncing and cross-window messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,7 +11,7 @@ description: >
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: core
|
|
13
13
|
library: '@jielga/react-popup-window'
|
|
14
|
-
library_version: '0.
|
|
14
|
+
library_version: '0.3.0'
|
|
15
15
|
sources:
|
|
16
16
|
- 'Jielga/react-popup-window:README.md'
|
|
17
17
|
- 'Jielga/react-popup-window:src/usePopupWindow.ts'
|
|
@@ -103,7 +103,8 @@ const { open, Popup } = usePopupWindow({ title: 'Panel', url: '/popup.html' })
|
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
`isOpen` turns `true` as soon as the window opens; `Popup` renders once the
|
|
106
|
-
page has loaded.
|
|
106
|
+
page has loaded. Start the page with `<!doctype html>`, or the popup lays out
|
|
107
|
+
in quirks mode. Important: do not point `url` at a route of the app, as that
|
|
107
108
|
starts a second copy of the app inside the popup. If the page redirects to
|
|
108
109
|
another origin, the popup closes and `onBlocked` is called.
|
|
109
110
|
|
|
@@ -12,7 +12,7 @@ description: >
|
|
|
12
12
|
metadata:
|
|
13
13
|
type: sub-skill
|
|
14
14
|
library: '@jielga/react-popup-window'
|
|
15
|
-
library_version: '0.
|
|
15
|
+
library_version: '0.3.0'
|
|
16
16
|
requires:
|
|
17
17
|
- '@jielga/react-popup-window/getting-started'
|
|
18
18
|
sources:
|
|
@@ -50,8 +50,10 @@ styling into it and keeps the mirror current:
|
|
|
50
50
|
The popup loads each copied `<link>` stylesheet again. `Popup` renders once
|
|
51
51
|
they have loaded or failed (at most 3 seconds), so the first frame shown is
|
|
52
52
|
styled. Alternate stylesheets and non-matching `media` are not waited for.
|
|
53
|
-
A `<link>` added to the opener while the popup is open
|
|
54
|
-
|
|
53
|
+
A `<link>` added to the opener while the popup is open, such as lazily loaded
|
|
54
|
+
chunk CSS, is loaded by the popup again; until then the popup applies the
|
|
55
|
+
rules the opener already loaded. A cross-origin stylesheet without CORS
|
|
56
|
+
cannot be read that way and is unstyled until the popup has loaded it.
|
|
55
57
|
|
|
56
58
|
`copyStyles: false` disables all of it. The mechanism is also exported
|
|
57
59
|
standalone as `copyStyles(source, target, watch?)`, returning a `stop`
|