@jielga/react-popup-window 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +371 -249
- package/dist/copyStyles.d.ts +11 -0
- package/dist/copyStyles.d.ts.map +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +145 -72
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +26 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/usePopupWindow.d.ts.map +1 -1
- package/dist/whenStylesheetsLoad.d.ts +13 -0
- package/dist/whenStylesheetsLoad.d.ts.map +1 -0
- package/package.json +101 -100
- package/skills/getting-started/SKILL.md +229 -211
- package/skills/popup-content/SKILL.md +428 -227
package/dist/copyStyles.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copyStyles.d.ts","sourceRoot":"","sources":["../src/copyStyles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,UAAO,GAAG,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"copyStyles.d.ts","sourceRoot":"","sources":["../src/copyStyles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,UAAO,GAAG,MAAM,IAAI,CAEvF;AAKD,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB;;;OAGG;IACH,KAAK,EAAE,eAAe,EAAE,CAAA;CACzB;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,UAAO,GAAG,WAAW,CAiK1F"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react"),t=require("react-dom");function n(e,t,n=!0){
|
|
2
|
-
`)}catch{}return e.textContent??``},
|
|
3
|
-
`)),t})}catch{}};for(let t of e.querySelectorAll(`style, link[rel~="stylesheet" i]`))
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react"),t=require("react-dom");function n(e,t,n=!0){return i(e,t,n).stop}var r=[`rel`,`media`,`title`,`crossorigin`,`referrerpolicy`,`integrity`];function i(e,t,n=!0){let i=new Map,a=e=>{if(e.nodeType!==Node.ELEMENT_NODE)return!1;let t=e;return t.tagName===`STYLE`||t.tagName===`LINK`&&(t.getAttribute(`rel`)??``).toLowerCase().includes(`stylesheet`)},o=e=>{let t=e.sheet;if(t)try{return Array.from(t.cssRules).map(e=>e.cssText).join(`
|
|
2
|
+
`)}catch{}return e.textContent??``},s=e=>{if(i.has(e))return;let n;if(e.tagName===`LINK`){let i=t.createElement(`link`);for(let t of r){let n=e.getAttribute(t);n!==null&&i.setAttribute(t,n)}i.href=e.href,n=i}else{let r=t.createElement(`style`);r.textContent=o(e);let i=e.getAttribute(`media`);i&&r.setAttribute(`media`,i),n=r}t.head.appendChild(n),i.set(e,n)},c=e=>{let t=i.get(e);t&&(t.textContent=o(e))},l=e=>{for(let[t,n]of i)(e===t||e.nodeType===Node.ELEMENT_NODE&&e.contains(t))&&(n.remove(),i.delete(t))},u=[`class`,`style`],d=()=>{let n=[[e.documentElement,t.documentElement],[e.body,t.body]];for(let[e,t]of n)if(!(!e||!t)){for(let n of e.attributes)(u.includes(n.name)||n.name.startsWith(`data-`))&&t.setAttribute(n.name,n.value);for(let n of Array.from(t.attributes))(u.includes(n.name)||n.name.startsWith(`data-`))&&!e.hasAttribute(n.name)&&t.removeAttribute(n.name)}},f=()=>{let n=t.defaultView;if(!(!n?.CSSStyleSheet||!(`adoptedStyleSheets`in e)))try{t.adoptedStyleSheets=e.adoptedStyleSheets.map(e=>{let t=new n.CSSStyleSheet;return t.replaceSync(Array.from(e.cssRules).map(e=>e.cssText).join(`
|
|
3
|
+
`)),t})}catch{}};for(let t of e.querySelectorAll(`style, link[rel~="stylesheet" i]`))s(t);let p=Array.from(i.values()).filter(e=>e.tagName===`LINK`);if(d(),f(),!n||typeof MutationObserver>`u`)return{stop:()=>{},links:p};let m=new MutationObserver(e=>{for(let t of e){if(t.type===`characterData`){let e=t.target.parentElement;e?.tagName===`STYLE`&&c(e);continue}a(t.target)&&t.target.tagName===`STYLE`&&c(t.target);for(let e of t.addedNodes)if(a(e))s(e);else if(e.nodeType===Node.ELEMENT_NODE)for(let t of e.querySelectorAll(`style, link[rel~="stylesheet" i]`))s(t);for(let e of t.removedNodes)l(e)}});m.observe(e.head,{childList:!0,subtree:!0,characterData:!0});let h=new MutationObserver(()=>d());return h.observe(e.documentElement,{attributes:!0}),e.body&&h.observe(e.body,{attributes:!0}),{stop:()=>{m.disconnect(),h.disconnect()},links:p}}function a(e,t,n){let r=e.filter(e=>e.sheet===null&&o(e));if(r.length===0)return n(),()=>{};let i=r.length,a=()=>{i--,i===0&&c()},s=()=>{window.clearTimeout(l);for(let e of r)e.removeEventListener(`load`,a),e.removeEventListener(`error`,a)},c=()=>{s(),n()};for(let e of r)e.addEventListener(`load`,a),e.addEventListener(`error`,a);let l=window.setTimeout(c,t);return s}function o(e){if(/\balternate\b/i.test(e.rel))return!1;let t=e.ownerDocument.defaultView;return!e.media||!t?.matchMedia||t.matchMedia(e.media).matches}var s={popupWindow:null,container:null,blocked:!1},c=class{state=s;listeners=new Set;subscribe=e=>(this.listeners.add(e),()=>this.listeners.delete(e));getSnapshot=()=>this.state;getServerSnapshot=()=>s;setState(e){this.state={...this.state,...e};for(let e of this.listeners)e()}},l={popup:!0,width:640,height:480};function u(e){let t={...l,...e.features};return e.center!==!1&&t.left===void 0&&t.top===void 0&&typeof t.width==`number`&&typeof t.height==`number`&&(t.left=Math.max(0,Math.round(window.screenX+(window.outerWidth-t.width)/2)),t.top=Math.max(0,Math.round(window.screenY+(window.outerHeight-t.height)/2))),Object.entries(t).filter(([,e])=>e!==void 0&&e!==!1).map(([e,t])=>t===!0?`${e}=yes`:`${e}=${t}`).join(`,`)}var d=`about:blank`,f=20,p=3e3;function m(e){let t=e.document;return t.URL===d||t.readyState===`loading`?null:t}function h(n){function r({children:r}){let{container:i}=(0,e.useSyncExternalStore)(n.subscribe,n.getSnapshot,n.getServerSnapshot);return i?(0,t.createPortal)(r,i):null}return r.displayName=`PopupWindow.Popup`,r}function g(t={}){let[n]=(0,e.useState)(()=>new c),[r]=(0,e.useState)(()=>h(n)),o=(0,e.useRef)(t);o.current=t;let s=(0,e.useRef)(null),l=(0,e.useSyncExternalStore)(n.subscribe,n.getSnapshot,n.getServerSnapshot),g=(0,e.useCallback)(e=>{let{popupWindow:t}=n.getSnapshot();s.current?.(),s.current=null,t&&(!e&&!t.closed&&t.close(),n.setState({popupWindow:null,container:null}),o.current.onClose?.())},[n]),_=(0,e.useCallback)(()=>{if(typeof window>`u`)return null;let e=n.getSnapshot();if(e.popupWindow&&!e.popupWindow.closed)return e.popupWindow.focus(),e.popupWindow;let t=o.current,r=t.url??d,c=window.open(r,t.name??`_blank`,u(t));if(!c)return n.setState({blocked:!0}),t.onBlocked?.(),null;let l;try{l=c.document}catch{return c.close(),n.setState({blocked:!0}),t.onBlocked?.(),null}let h=()=>g(!0),_=()=>{setTimeout(()=>{c.closed&&h()},0)},v=window.setInterval(()=>{c.closed&&h()},250),y=()=>c.close();window.addEventListener(`pagehide`,y);let b,x=!1,S,C;s.current=()=>{window.clearInterval(v),window.clearInterval(b),window.removeEventListener(`pagehide`,y),x&&c.removeEventListener(`pagehide`,_),S?.(),C?.()};let w=e=>{x=!0,c.addEventListener(`pagehide`,_),e.title=t.title??document.title;let r=[];if(t.copyStyles!==!1){let t=i(document,e);S=t.stop,r=t.links}let o=e.createElement(`div`);o.setAttribute(`data-popup-window-root`,``),e.body.appendChild(o),C=a(r,p,()=>{n.setState({container:o}),t.onOpen?.(c)})};return n.setState({popupWindow:c,container:null,blocked:!1}),r===d?w(l):b=window.setInterval(()=>{if(c.closed){h();return}let e;try{e=m(c)}catch{g(!1),n.setState({blocked:!0}),t.onBlocked?.();return}e&&(window.clearInterval(b),w(e))},f),c},[n,g]),v=(0,e.useCallback)(()=>g(!1),[g]),y=(0,e.useCallback)(()=>{n.getSnapshot().popupWindow?g(!1):_()},[n,g,_]),b=(0,e.useCallback)(()=>{let{popupWindow:e}=n.getSnapshot();e&&!e.closed&&e.focus()},[n]);return(0,e.useEffect)(()=>()=>g(!1),[g]),{open:_,close:v,toggle:y,focus:b,isOpen:l.popupWindow!==null,isBlocked:l.blocked,popupWindow:l.popupWindow,Popup:r}}exports.copyStyles=n,exports.usePopupWindow=g;
|
|
4
4
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/copyStyles.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\r\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\r\n * in sync while the popup is open:\r\n *\r\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\r\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\r\n * possible, so rules injected via `insertRule` (CSS-in-JS \"speedy\" mode)\r\n * are included.\r\n * - Additions/removals/edits of style nodes in the source `<head>` are\r\n * observed (covers Vite HMR, lazily loaded chunk CSS, styled-components).\r\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\r\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\r\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\r\n *\r\n * Returns a function that stops observing.\r\n */\r\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\r\n const mirrors = new Map<Element, Element>()\r\n\r\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\r\n if (node.nodeType !== Node.ELEMENT_NODE) return false\r\n const el = node as Element\r\n return (\r\n el.tagName === 'STYLE' ||\r\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\r\n )\r\n }\r\n\r\n const serializeStyle = (el: HTMLStyleElement): string => {\r\n const sheet = el.sheet\r\n if (sheet) {\r\n try {\r\n return Array.from(sheet.cssRules)\r\n .map((rule) => rule.cssText)\r\n .join('\\n')\r\n } catch {\r\n // Inaccessible cssRules — fall back to the raw text below.\r\n }\r\n }\r\n return el.textContent ?? ''\r\n }\r\n\r\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\r\n if (mirrors.has(el)) return\r\n let clone: Element\r\n if (el.tagName === 'LINK') {\r\n const link = target.createElement('link')\r\n link.rel = 'stylesheet'\r\n // .href resolves to an absolute URL, so relative hrefs keep working\r\n // from the popup's `about:blank` document.\r\n link.href = (el as HTMLLinkElement).href\r\n const media = el.getAttribute('media')\r\n if (media) link.media = media\r\n clone = link\r\n } else {\r\n const style = target.createElement('style')\r\n style.textContent = serializeStyle(el as HTMLStyleElement)\r\n const media = el.getAttribute('media')\r\n if (media) style.setAttribute('media', media)\r\n clone = style\r\n }\r\n target.head.appendChild(clone)\r\n mirrors.set(el, clone)\r\n }\r\n\r\n const refresh = (el: HTMLStyleElement): void => {\r\n const clone = mirrors.get(el)\r\n if (clone) clone.textContent = serializeStyle(el)\r\n }\r\n\r\n const unmirrorSubtree = (removed: Node): void => {\r\n for (const [src, clone] of mirrors) {\r\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\r\n clone.remove()\r\n mirrors.delete(src)\r\n }\r\n }\r\n }\r\n\r\n const ROOT_ATTRS = ['class', 'style']\r\n const syncRootAttrs = (): void => {\r\n const pairs: Array<[Element | null, Element | null]> = [\r\n [source.documentElement, target.documentElement],\r\n [source.body, target.body],\r\n ]\r\n for (const [from, to] of pairs) {\r\n if (!from || !to) continue\r\n for (const attr of from.attributes) {\r\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\r\n to.setAttribute(attr.name, attr.value)\r\n }\r\n }\r\n for (const attr of Array.from(to.attributes)) {\r\n if (\r\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\r\n !from.hasAttribute(attr.name)\r\n ) {\r\n to.removeAttribute(attr.name)\r\n }\r\n }\r\n }\r\n }\r\n\r\n const syncAdoptedSheets = (): void => {\r\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\r\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\r\n try {\r\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\r\n const copy = new targetWindow.CSSStyleSheet!()\r\n copy.replaceSync(\r\n Array.from(sheet.cssRules)\r\n .map((rule) => rule.cssText)\r\n .join('\\n'),\r\n )\r\n return copy\r\n })\r\n } catch {\r\n // Best effort — older browsers or cross-origin rules.\r\n }\r\n }\r\n\r\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\r\n mirror(el as HTMLStyleElement | HTMLLinkElement)\r\n }\r\n syncRootAttrs()\r\n syncAdoptedSheets()\r\n\r\n if (!watch || typeof MutationObserver === 'undefined') {\r\n return () => {}\r\n }\r\n\r\n const headObserver = new MutationObserver((records) => {\r\n for (const record of records) {\r\n if (record.type === 'characterData') {\r\n const parent = record.target.parentElement\r\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\r\n continue\r\n }\r\n // childList\r\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\r\n // textContent assignment replaces the text node → childList on <style>\r\n refresh(record.target as HTMLStyleElement)\r\n }\r\n for (const added of record.addedNodes) {\r\n if (isStyleNode(added)) {\r\n mirror(added)\r\n } else if (added.nodeType === Node.ELEMENT_NODE) {\r\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\r\n mirror(el as HTMLStyleElement | HTMLLinkElement)\r\n }\r\n }\r\n }\r\n for (const removed of record.removedNodes) {\r\n unmirrorSubtree(removed)\r\n }\r\n }\r\n })\r\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\r\n\r\n const rootObserver = new MutationObserver(() => syncRootAttrs())\r\n rootObserver.observe(source.documentElement, { attributes: true })\r\n if (source.body) rootObserver.observe(source.body, { attributes: true })\r\n\r\n return () => {\r\n headObserver.disconnect()\r\n rootObserver.disconnect()\r\n }\r\n}\r\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\r\nimport type { FC } from 'react'\r\nimport { createPortal } from 'react-dom'\r\nimport { copyStyles } from './copyStyles'\r\nimport type {\r\n PopupProps,\r\n PopupWindowApi,\r\n PopupWindowFeatures,\r\n UsePopupWindowOptions,\r\n} from './types'\r\n\r\ninterface PopupState {\r\n popupWindow: Window | null\r\n container: HTMLElement | null\r\n blocked: boolean\r\n}\r\n\r\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\r\n\r\nclass PopupStore {\r\n state: PopupState = INITIAL_STATE\r\n private listeners = new Set<() => void>()\r\n\r\n subscribe = (listener: () => void): (() => void) => {\r\n this.listeners.add(listener)\r\n return () => this.listeners.delete(listener)\r\n }\r\n\r\n getSnapshot = (): PopupState => this.state\r\n\r\n getServerSnapshot = (): PopupState => INITIAL_STATE\r\n\r\n setState(partial: Partial<PopupState>): void {\r\n this.state = { ...this.state, ...partial }\r\n for (const listener of this.listeners) listener()\r\n }\r\n}\r\n\r\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\r\n\r\nfunction buildFeatures(options: UsePopupWindowOptions): string {\r\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\r\n if (\r\n options.center !== false &&\r\n merged.left === undefined &&\r\n merged.top === undefined &&\r\n typeof merged.width === 'number' &&\r\n typeof merged.height === 'number'\r\n ) {\r\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\r\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\r\n }\r\n return Object.entries(merged)\r\n .filter(([, value]) => value !== undefined && value !== false)\r\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\r\n .join(',')\r\n}\r\n\r\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\r\n function Popup({ children }: PopupProps) {\r\n const { container } = useSyncExternalStore(\r\n store.subscribe,\r\n store.getSnapshot,\r\n store.getServerSnapshot,\r\n )\r\n return container ? createPortal(children, container) : null\r\n }\r\n Popup.displayName = 'PopupWindow.Popup'\r\n return Popup\r\n}\r\n\r\n/**\r\n * Open part of your React tree in a separate browser window.\r\n *\r\n * The popup content is rendered with a portal into the popup's document, so\r\n * it stays part of your component tree: state, context and event handlers\r\n * all keep working across windows.\r\n *\r\n * ```tsx\r\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\r\n *\r\n * return (\r\n * <>\r\n * <button onClick={open}>Open panel</button>\r\n * <Popup>\r\n * <MyPanel />\r\n * </Popup>\r\n * </>\r\n * )\r\n * ```\r\n */\r\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\r\n const [store] = useState(() => new PopupStore())\r\n const [Popup] = useState(() => createPopupComponent(store))\r\n\r\n const optionsRef = useRef(options)\r\n optionsRef.current = options\r\n\r\n const cleanupRef = useRef<(() => void) | null>(null)\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\r\n\r\n // `userClosed` is true when the window was closed outside our control\r\n // (user hit the close button, opener unloaded) — then we must not touch it.\r\n const closePopup = useCallback(\r\n (userClosed: boolean) => {\r\n const { popupWindow } = store.getSnapshot()\r\n cleanupRef.current?.()\r\n cleanupRef.current = null\r\n if (!popupWindow) return\r\n if (!userClosed && !popupWindow.closed) popupWindow.close()\r\n store.setState({ popupWindow: null, container: null })\r\n optionsRef.current.onClose?.()\r\n },\r\n [store],\r\n )\r\n\r\n const open = useCallback((): Window | null => {\r\n if (typeof window === 'undefined') return null\r\n const current = store.getSnapshot()\r\n if (current.popupWindow && !current.popupWindow.closed) {\r\n current.popupWindow.focus()\r\n return current.popupWindow\r\n }\r\n\r\n const opts = optionsRef.current\r\n const popupWindow = window.open('about:blank', opts.name ?? '_blank', buildFeatures(opts))\r\n if (!popupWindow) {\r\n store.setState({ blocked: true })\r\n opts.onBlocked?.()\r\n return null\r\n }\r\n\r\n const doc = popupWindow.document\r\n doc.title = opts.title ?? document.title\r\n\r\n let stopStyleSync: (() => void) | undefined\r\n if (opts.copyStyles !== false) {\r\n stopStyleSync = copyStyles(document, doc)\r\n }\r\n\r\n const container = doc.createElement('div')\r\n container.setAttribute('data-popup-window-root', '')\r\n doc.body.appendChild(container)\r\n\r\n const handleExternalClose = () => closePopup(true)\r\n const onPopupPagehide = () => {\r\n // pagehide also fires on navigation; only treat it as a close when the\r\n // window really is gone a tick later.\r\n setTimeout(() => {\r\n if (popupWindow.closed) handleExternalClose()\r\n }, 0)\r\n }\r\n popupWindow.addEventListener('pagehide', onPopupPagehide)\r\n\r\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\r\n const closePoll = window.setInterval(() => {\r\n if (popupWindow.closed) handleExternalClose()\r\n }, 250)\r\n\r\n const onOpenerPagehide = () => popupWindow.close()\r\n window.addEventListener('pagehide', onOpenerPagehide)\r\n\r\n cleanupRef.current = () => {\r\n window.clearInterval(closePoll)\r\n window.removeEventListener('pagehide', onOpenerPagehide)\r\n popupWindow.removeEventListener('pagehide', onPopupPagehide)\r\n stopStyleSync?.()\r\n }\r\n\r\n store.setState({ popupWindow, container, blocked: false })\r\n opts.onOpen?.(popupWindow)\r\n return popupWindow\r\n }, [store, closePopup])\r\n\r\n const close = useCallback(() => closePopup(false), [closePopup])\r\n\r\n const toggle = useCallback(() => {\r\n if (store.getSnapshot().popupWindow) {\r\n closePopup(false)\r\n } else {\r\n open()\r\n }\r\n }, [store, closePopup, open])\r\n\r\n const focus = useCallback(() => {\r\n const { popupWindow } = store.getSnapshot()\r\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\r\n }, [store])\r\n\r\n // Close the popup when the owning component unmounts — its portal content\r\n // would unmount anyway, leaving an empty window behind.\r\n useEffect(() => {\r\n return () => closePopup(false)\r\n }, [closePopup])\r\n\r\n return {\r\n open,\r\n close,\r\n toggle,\r\n focus,\r\n isOpen: state.popupWindow !== null,\r\n isBlocked: state.blocked,\r\n popupWindow: state.popupWindow,\r\n Popup,\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/copyStyles.ts","../src/whenStylesheetsLoad.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\n * in sync while the popup is open:\n *\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\n * possible, so rules injected via `insertRule` (CSS-in-JS \"speedy\" mode)\n * are included.\n * - Additions/removals/edits of style nodes in the source `<head>` are\n * observed (covers Vite HMR, lazily loaded chunk CSS, styled-components).\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\n *\n * Returns a function that stops observing.\n */\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\n return mirrorStyles(source, target, watch).stop\n}\n\n/** Attributes copied from a source `<link>` onto its mirror. */\nconst LINK_ATTRS = ['rel', 'media', 'title', 'crossorigin', 'referrerpolicy', 'integrity']\n\nexport interface StyleMirror {\n /** Stops observing the source document. */\n stop: () => void\n /**\n * The `<link>` elements created in `target` by the initial copy. The target\n * fetches each of them asynchronously.\n */\n links: HTMLLinkElement[]\n}\n\n/** {@link copyStyles}, plus the `<link>` elements the initial copy created. */\nexport function mirrorStyles(source: Document, target: Document, watch = true): StyleMirror {\n const mirrors = new Map<Element, Element>()\n\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\n if (node.nodeType !== Node.ELEMENT_NODE) return false\n const el = node as Element\n return (\n el.tagName === 'STYLE' ||\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\n )\n }\n\n const serializeStyle = (el: HTMLStyleElement): string => {\n const sheet = el.sheet\n if (sheet) {\n try {\n return Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n } catch {\n // Inaccessible cssRules — fall back to the raw text below.\n }\n }\n return el.textContent ?? ''\n }\n\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\n if (mirrors.has(el)) return\n let clone: Element\n if (el.tagName === 'LINK') {\n const link = target.createElement('link')\n // `rel` is copied as is, so an alternate stylesheet stays disabled.\n // `crossorigin` and `referrerpolicy` make the popup send the same request\n // as the opener, so it can reuse the cached response.\n for (const name of LINK_ATTRS) {\n const value = el.getAttribute(name)\n if (value !== null) link.setAttribute(name, value)\n }\n // .href resolves to an absolute URL, so relative hrefs keep working\n // from the popup's `about:blank` document.\n link.href = (el as HTMLLinkElement).href\n clone = link\n } else {\n const style = target.createElement('style')\n style.textContent = serializeStyle(el as HTMLStyleElement)\n const media = el.getAttribute('media')\n if (media) style.setAttribute('media', media)\n clone = style\n }\n target.head.appendChild(clone)\n mirrors.set(el, clone)\n }\n\n const refresh = (el: HTMLStyleElement): void => {\n const clone = mirrors.get(el)\n if (clone) clone.textContent = serializeStyle(el)\n }\n\n const unmirrorSubtree = (removed: Node): void => {\n for (const [src, clone] of mirrors) {\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\n clone.remove()\n mirrors.delete(src)\n }\n }\n }\n\n const ROOT_ATTRS = ['class', 'style']\n const syncRootAttrs = (): void => {\n const pairs: Array<[Element | null, Element | null]> = [\n [source.documentElement, target.documentElement],\n [source.body, target.body],\n ]\n for (const [from, to] of pairs) {\n if (!from || !to) continue\n for (const attr of from.attributes) {\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\n to.setAttribute(attr.name, attr.value)\n }\n }\n for (const attr of Array.from(to.attributes)) {\n if (\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\n !from.hasAttribute(attr.name)\n ) {\n to.removeAttribute(attr.name)\n }\n }\n }\n }\n\n const syncAdoptedSheets = (): void => {\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\n try {\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\n const copy = new targetWindow.CSSStyleSheet!()\n copy.replaceSync(\n Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n'),\n )\n return copy\n })\n } catch {\n // Best effort — older browsers or cross-origin rules.\n }\n }\n\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n const links = Array.from(mirrors.values()).filter(\n (clone): clone is HTMLLinkElement => clone.tagName === 'LINK',\n )\n syncRootAttrs()\n syncAdoptedSheets()\n\n if (!watch || typeof MutationObserver === 'undefined') {\n return { stop: () => {}, links }\n }\n\n const headObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'characterData') {\n const parent = record.target.parentElement\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\n continue\n }\n // childList\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\n // textContent assignment replaces the text node → childList on <style>\n refresh(record.target as HTMLStyleElement)\n }\n for (const added of record.addedNodes) {\n if (isStyleNode(added)) {\n mirror(added)\n } else if (added.nodeType === Node.ELEMENT_NODE) {\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n }\n }\n for (const removed of record.removedNodes) {\n unmirrorSubtree(removed)\n }\n }\n })\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\n\n const rootObserver = new MutationObserver(() => syncRootAttrs())\n rootObserver.observe(source.documentElement, { attributes: true })\n if (source.body) rootObserver.observe(source.body, { attributes: true })\n\n return {\n stop: () => {\n headObserver.disconnect()\n rootObserver.disconnect()\n },\n links,\n }\n}\n","/**\n * Calls `callback` once every stylesheet in `links` that applies has loaded\n * or failed, or after `timeoutMs`, whichever comes first.\n *\n * When none of them is still loading, `callback` runs synchronously, before\n * this function returns. Links that do not apply are not waited for: an\n * alternate stylesheet, or a `media` query that does not match, such as\n * `print`. Browsers fetch those at the lowest priority.\n *\n * Returns a function that stops waiting without calling `callback`.\n */\nexport function whenStylesheetsLoad(\n links: readonly HTMLLinkElement[],\n timeoutMs: number,\n callback: () => void,\n): () => void {\n const pending = links.filter((link) => link.sheet === null && applies(link))\n if (pending.length === 0) {\n callback()\n return () => {}\n }\n\n let remaining = pending.length\n const settle = () => {\n remaining--\n if (remaining === 0) finish()\n }\n const stop = () => {\n window.clearTimeout(timeout)\n for (const link of pending) {\n link.removeEventListener('load', settle)\n link.removeEventListener('error', settle)\n }\n }\n const finish = () => {\n stop()\n callback()\n }\n\n for (const link of pending) {\n link.addEventListener('load', settle)\n // A broken stylesheet must not keep the popup blank.\n link.addEventListener('error', settle)\n }\n const timeout = window.setTimeout(finish, timeoutMs)\n return stop\n}\n\nfunction applies(link: HTMLLinkElement): boolean {\n if (/\\balternate\\b/i.test(link.rel)) return false\n const view = link.ownerDocument.defaultView\n return !link.media || !view?.matchMedia || view.matchMedia(link.media).matches\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { FC } from 'react'\nimport { createPortal } from 'react-dom'\nimport { mirrorStyles } from './copyStyles'\nimport type {\n PopupProps,\n PopupWindowApi,\n PopupWindowFeatures,\n UsePopupWindowOptions,\n} from './types'\nimport { whenStylesheetsLoad } from './whenStylesheetsLoad'\n\ninterface PopupState {\n popupWindow: Window | null\n container: HTMLElement | null\n blocked: boolean\n}\n\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\n\nclass PopupStore {\n state: PopupState = INITIAL_STATE\n private listeners = new Set<() => void>()\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getSnapshot = (): PopupState => this.state\n\n getServerSnapshot = (): PopupState => INITIAL_STATE\n\n setState(partial: Partial<PopupState>): void {\n this.state = { ...this.state, ...partial }\n for (const listener of this.listeners) listener()\n }\n}\n\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\n\nfunction buildFeatures(options: UsePopupWindowOptions): string {\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\n if (\n options.center !== false &&\n merged.left === undefined &&\n merged.top === undefined &&\n typeof merged.width === 'number' &&\n typeof merged.height === 'number'\n ) {\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\n }\n return Object.entries(merged)\n .filter(([, value]) => value !== undefined && value !== false)\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\n .join(',')\n}\n\nconst ABOUT_BLANK = 'about:blank'\n/** Interval for checking whether the page given by `url` has loaded. */\nconst LOAD_POLL_MS = 20\n/** Longest time `Popup` waits for copied stylesheets before rendering anyway. */\nconst STYLES_TIMEOUT_MS = 3000\n\n/**\n * The popup's document once the page given by `url` has replaced the initial\n * `about:blank` document and finished parsing, otherwise `null`. Throws when\n * the document is not scriptable.\n */\nfunction loadedDocument(popupWindow: Window): Document | null {\n const doc = popupWindow.document\n if (doc.URL === ABOUT_BLANK || doc.readyState === 'loading') return null\n return doc\n}\n\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\n function Popup({ children }: PopupProps) {\n const { container } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n )\n return container ? createPortal(children, container) : null\n }\n Popup.displayName = 'PopupWindow.Popup'\n return Popup\n}\n\n/**\n * Open part of your React tree in a separate browser window.\n *\n * The popup content is rendered with a portal into the popup's document, so\n * it stays part of your component tree: state, context and event handlers\n * all keep working across windows.\n *\n * ```tsx\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\n *\n * return (\n * <>\n * <button onClick={open}>Open panel</button>\n * <Popup>\n * <MyPanel />\n * </Popup>\n * </>\n * )\n * ```\n */\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\n const [store] = useState(() => new PopupStore())\n const [Popup] = useState(() => createPopupComponent(store))\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const cleanupRef = useRef<(() => void) | null>(null)\n\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\n\n // `userClosed` is true when the window was closed outside our control\n // (user hit the close button, opener unloaded) — then we must not touch it.\n const closePopup = useCallback(\n (userClosed: boolean) => {\n const { popupWindow } = store.getSnapshot()\n cleanupRef.current?.()\n cleanupRef.current = null\n if (!popupWindow) return\n if (!userClosed && !popupWindow.closed) popupWindow.close()\n store.setState({ popupWindow: null, container: null })\n optionsRef.current.onClose?.()\n },\n [store],\n )\n\n const open = useCallback((): Window | null => {\n if (typeof window === 'undefined') return null\n const current = store.getSnapshot()\n if (current.popupWindow && !current.popupWindow.closed) {\n current.popupWindow.focus()\n return current.popupWindow\n }\n\n const opts = optionsRef.current\n const url = opts.url ?? ABOUT_BLANK\n const popupWindow = window.open(url, opts.name ?? '_blank', buildFeatures(opts))\n if (!popupWindow) {\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n // Sandboxed embedders (VS Code's built-in browser, CodeSandbox/StackBlitz\n // previews, iframes sandboxed without popup-escape) open the popup with an\n // opaque origin, so reading its document throws a SecurityError. Treat\n // that as blocked instead of stranding a blank window the portal can\n // never reach.\n let initialDoc: Document\n try {\n initialDoc = popupWindow.document\n } catch {\n popupWindow.close()\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n const handleExternalClose = () => closePopup(true)\n const onPopupPagehide = () => {\n // pagehide also fires on navigation; only treat it as a close when the\n // window really is gone a tick later.\n setTimeout(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 0)\n }\n\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\n const closePoll = window.setInterval(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 250)\n\n const onOpenerPagehide = () => popupWindow.close()\n window.addEventListener('pagehide', onOpenerPagehide)\n\n let loadPoll: number | undefined\n let prepared = false\n let stopStyleSync: (() => void) | undefined\n let stopStylesWait: (() => void) | undefined\n\n cleanupRef.current = () => {\n window.clearInterval(closePoll)\n window.clearInterval(loadPoll)\n window.removeEventListener('pagehide', onOpenerPagehide)\n if (prepared) popupWindow.removeEventListener('pagehide', onPopupPagehide)\n stopStyleSync?.()\n stopStylesWait?.()\n }\n\n // Title, styles and the portal container go into the document that\n // stays: `about:blank` itself, or the page `url` loads in its place.\n const prepare = (doc: Document) => {\n prepared = true\n popupWindow.addEventListener('pagehide', onPopupPagehide)\n doc.title = opts.title ?? document.title\n let links: HTMLLinkElement[] = []\n if (opts.copyStyles !== false) {\n const styles = mirrorStyles(document, doc)\n stopStyleSync = styles.stop\n links = styles.links\n }\n const container = doc.createElement('div')\n container.setAttribute('data-popup-window-root', '')\n doc.body.appendChild(container)\n // The popup fetches copied <link> stylesheets asynchronously. Render\n // only once they have loaded, so the first paint is not unstyled.\n stopStylesWait = whenStylesheetsLoad(links, STYLES_TIMEOUT_MS, () => {\n store.setState({ container })\n opts.onOpen?.(popupWindow)\n })\n }\n\n store.setState({ popupWindow, container: null, blocked: false })\n\n if (url === ABOUT_BLANK) {\n prepare(initialDoc)\n } else {\n // The window starts on an initial about:blank document and then\n // navigates to `url`. Wait until that page has replaced it and parsed.\n loadPoll = window.setInterval(() => {\n if (popupWindow.closed) {\n handleExternalClose()\n return\n }\n let doc: Document | null\n try {\n doc = loadedDocument(popupWindow)\n } catch {\n // The page went somewhere the opener cannot script, such as a\n // cross-origin redirect: nothing can ever render there.\n closePopup(false)\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return\n }\n if (!doc) return\n window.clearInterval(loadPoll)\n prepare(doc)\n }, LOAD_POLL_MS)\n }\n\n return popupWindow\n }, [store, closePopup])\n\n const close = useCallback(() => closePopup(false), [closePopup])\n\n const toggle = useCallback(() => {\n if (store.getSnapshot().popupWindow) {\n closePopup(false)\n } else {\n open()\n }\n }, [store, closePopup, open])\n\n const focus = useCallback(() => {\n const { popupWindow } = store.getSnapshot()\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\n }, [store])\n\n // Close the popup when the owning component unmounts — its portal content\n // would unmount anyway, leaving an empty window behind.\n useEffect(() => {\n return () => closePopup(false)\n }, [closePopup])\n\n return {\n open,\n close,\n toggle,\n focus,\n isOpen: state.popupWindow !== null,\n isBlocked: state.blocked,\n popupWindow: state.popupWindow,\n Popup,\n }\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,17 @@ import { useCallback as e, useEffect as t, useRef as n, useState as r, useSyncEx
|
|
|
2
2
|
import { createPortal as a } from "react-dom";
|
|
3
3
|
//#region src/copyStyles.ts
|
|
4
4
|
function o(e, t, n = !0) {
|
|
5
|
+
return c(e, t, n).stop;
|
|
6
|
+
}
|
|
7
|
+
var s = [
|
|
8
|
+
"rel",
|
|
9
|
+
"media",
|
|
10
|
+
"title",
|
|
11
|
+
"crossorigin",
|
|
12
|
+
"referrerpolicy",
|
|
13
|
+
"integrity"
|
|
14
|
+
];
|
|
15
|
+
function c(e, t, n = !0) {
|
|
5
16
|
let r = /* @__PURE__ */ new Map(), i = (e) => {
|
|
6
17
|
if (e.nodeType !== Node.ELEMENT_NODE) return !1;
|
|
7
18
|
let t = e;
|
|
@@ -17,9 +28,11 @@ function o(e, t, n = !0) {
|
|
|
17
28
|
let n;
|
|
18
29
|
if (e.tagName === "LINK") {
|
|
19
30
|
let r = t.createElement("link");
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
for (let t of s) {
|
|
32
|
+
let n = e.getAttribute(t);
|
|
33
|
+
n !== null && r.setAttribute(t, n);
|
|
34
|
+
}
|
|
35
|
+
r.href = e.href, n = r;
|
|
23
36
|
} else {
|
|
24
37
|
let r = t.createElement("style");
|
|
25
38
|
r.textContent = a(e);
|
|
@@ -27,18 +40,18 @@ function o(e, t, n = !0) {
|
|
|
27
40
|
i && r.setAttribute("media", i), n = r;
|
|
28
41
|
}
|
|
29
42
|
t.head.appendChild(n), r.set(e, n);
|
|
30
|
-
},
|
|
43
|
+
}, c = (e) => {
|
|
31
44
|
let t = r.get(e);
|
|
32
45
|
t && (t.textContent = a(e));
|
|
33
|
-
},
|
|
46
|
+
}, l = (e) => {
|
|
34
47
|
for (let [t, n] of r) (e === t || e.nodeType === Node.ELEMENT_NODE && e.contains(t)) && (n.remove(), r.delete(t));
|
|
35
|
-
},
|
|
48
|
+
}, u = ["class", "style"], d = () => {
|
|
36
49
|
let n = [[e.documentElement, t.documentElement], [e.body, t.body]];
|
|
37
50
|
for (let [e, t] of n) if (!(!e || !t)) {
|
|
38
|
-
for (let n of e.attributes) (
|
|
39
|
-
for (let n of Array.from(t.attributes)) (
|
|
51
|
+
for (let n of e.attributes) (u.includes(n.name) || n.name.startsWith("data-")) && t.setAttribute(n.name, n.value);
|
|
52
|
+
for (let n of Array.from(t.attributes)) (u.includes(n.name) || n.name.startsWith("data-")) && !e.hasAttribute(n.name) && t.removeAttribute(n.name);
|
|
40
53
|
}
|
|
41
|
-
},
|
|
54
|
+
}, f = () => {
|
|
42
55
|
let n = t.defaultView;
|
|
43
56
|
if (!(!n?.CSSStyleSheet || !("adoptedStyleSheets" in e))) try {
|
|
44
57
|
t.adoptedStyleSheets = e.adoptedStyleSheets.map((e) => {
|
|
@@ -48,42 +61,71 @@ function o(e, t, n = !0) {
|
|
|
48
61
|
} catch {}
|
|
49
62
|
};
|
|
50
63
|
for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) o(t);
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
let p = Array.from(r.values()).filter((e) => e.tagName === "LINK");
|
|
65
|
+
if (d(), f(), !n || typeof MutationObserver > "u") return {
|
|
66
|
+
stop: () => {},
|
|
67
|
+
links: p
|
|
68
|
+
};
|
|
69
|
+
let m = new MutationObserver((e) => {
|
|
53
70
|
for (let t of e) {
|
|
54
71
|
if (t.type === "characterData") {
|
|
55
72
|
let e = t.target.parentElement;
|
|
56
|
-
e?.tagName === "STYLE" &&
|
|
73
|
+
e?.tagName === "STYLE" && c(e);
|
|
57
74
|
continue;
|
|
58
75
|
}
|
|
59
|
-
i(t.target) && t.target.tagName === "STYLE" &&
|
|
76
|
+
i(t.target) && t.target.tagName === "STYLE" && c(t.target);
|
|
60
77
|
for (let e of t.addedNodes) if (i(e)) o(e);
|
|
61
78
|
else if (e.nodeType === Node.ELEMENT_NODE) for (let t of e.querySelectorAll("style, link[rel~=\"stylesheet\" i]")) o(t);
|
|
62
|
-
for (let e of t.removedNodes)
|
|
79
|
+
for (let e of t.removedNodes) l(e);
|
|
63
80
|
}
|
|
64
81
|
});
|
|
65
|
-
|
|
82
|
+
m.observe(e.head, {
|
|
66
83
|
childList: !0,
|
|
67
84
|
subtree: !0,
|
|
68
85
|
characterData: !0
|
|
69
86
|
});
|
|
70
|
-
let
|
|
71
|
-
return
|
|
72
|
-
|
|
87
|
+
let h = new MutationObserver(() => d());
|
|
88
|
+
return h.observe(e.documentElement, { attributes: !0 }), e.body && h.observe(e.body, { attributes: !0 }), {
|
|
89
|
+
stop: () => {
|
|
90
|
+
m.disconnect(), h.disconnect();
|
|
91
|
+
},
|
|
92
|
+
links: p
|
|
73
93
|
};
|
|
74
94
|
}
|
|
75
95
|
//#endregion
|
|
96
|
+
//#region src/whenStylesheetsLoad.ts
|
|
97
|
+
function l(e, t, n) {
|
|
98
|
+
let r = e.filter((e) => e.sheet === null && u(e));
|
|
99
|
+
if (r.length === 0) return n(), () => {};
|
|
100
|
+
let i = r.length, a = () => {
|
|
101
|
+
i--, i === 0 && s();
|
|
102
|
+
}, o = () => {
|
|
103
|
+
window.clearTimeout(c);
|
|
104
|
+
for (let e of r) e.removeEventListener("load", a), e.removeEventListener("error", a);
|
|
105
|
+
}, s = () => {
|
|
106
|
+
o(), n();
|
|
107
|
+
};
|
|
108
|
+
for (let e of r) e.addEventListener("load", a), e.addEventListener("error", a);
|
|
109
|
+
let c = window.setTimeout(s, t);
|
|
110
|
+
return o;
|
|
111
|
+
}
|
|
112
|
+
function u(e) {
|
|
113
|
+
if (/\balternate\b/i.test(e.rel)) return !1;
|
|
114
|
+
let t = e.ownerDocument.defaultView;
|
|
115
|
+
return !e.media || !t?.matchMedia || t.matchMedia(e.media).matches;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
76
118
|
//#region src/usePopupWindow.ts
|
|
77
|
-
var
|
|
119
|
+
var d = {
|
|
78
120
|
popupWindow: null,
|
|
79
121
|
container: null,
|
|
80
122
|
blocked: !1
|
|
81
|
-
},
|
|
82
|
-
state =
|
|
123
|
+
}, f = class {
|
|
124
|
+
state = d;
|
|
83
125
|
listeners = /* @__PURE__ */ new Set();
|
|
84
126
|
subscribe = (e) => (this.listeners.add(e), () => this.listeners.delete(e));
|
|
85
127
|
getSnapshot = () => this.state;
|
|
86
|
-
getServerSnapshot = () =>
|
|
128
|
+
getServerSnapshot = () => d;
|
|
87
129
|
setState(e) {
|
|
88
130
|
this.state = {
|
|
89
131
|
...this.state,
|
|
@@ -91,84 +133,115 @@ var s = {
|
|
|
91
133
|
};
|
|
92
134
|
for (let e of this.listeners) e();
|
|
93
135
|
}
|
|
94
|
-
},
|
|
136
|
+
}, p = {
|
|
95
137
|
popup: !0,
|
|
96
138
|
width: 640,
|
|
97
139
|
height: 480
|
|
98
140
|
};
|
|
99
|
-
function
|
|
141
|
+
function m(e) {
|
|
100
142
|
let t = {
|
|
101
|
-
...
|
|
143
|
+
...p,
|
|
102
144
|
...e.features
|
|
103
145
|
};
|
|
104
146
|
return e.center !== !1 && t.left === void 0 && t.top === void 0 && typeof t.width == "number" && typeof t.height == "number" && (t.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - t.width) / 2)), t.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - t.height) / 2))), Object.entries(t).filter(([, e]) => e !== void 0 && e !== !1).map(([e, t]) => t === !0 ? `${e}=yes` : `${e}=${t}`).join(",");
|
|
105
147
|
}
|
|
106
|
-
|
|
148
|
+
var h = "about:blank", g = 20, _ = 3e3;
|
|
149
|
+
function v(e) {
|
|
150
|
+
let t = e.document;
|
|
151
|
+
return t.URL === h || t.readyState === "loading" ? null : t;
|
|
152
|
+
}
|
|
153
|
+
function y(e) {
|
|
107
154
|
function t({ children: t }) {
|
|
108
155
|
let { container: n } = i(e.subscribe, e.getSnapshot, e.getServerSnapshot);
|
|
109
156
|
return n ? a(t, n) : null;
|
|
110
157
|
}
|
|
111
158
|
return t.displayName = "PopupWindow.Popup", t;
|
|
112
159
|
}
|
|
113
|
-
function
|
|
114
|
-
let [
|
|
115
|
-
|
|
116
|
-
let
|
|
117
|
-
let { popupWindow: t } =
|
|
118
|
-
|
|
160
|
+
function b(a = {}) {
|
|
161
|
+
let [o] = r(() => new f()), [s] = r(() => y(o)), u = n(a);
|
|
162
|
+
u.current = a;
|
|
163
|
+
let d = n(null), p = i(o.subscribe, o.getSnapshot, o.getServerSnapshot), b = e((e) => {
|
|
164
|
+
let { popupWindow: t } = o.getSnapshot();
|
|
165
|
+
d.current?.(), d.current = null, t && (!e && !t.closed && t.close(), o.setState({
|
|
119
166
|
popupWindow: null,
|
|
120
167
|
container: null
|
|
121
|
-
}),
|
|
122
|
-
}, [
|
|
168
|
+
}), u.current.onClose?.());
|
|
169
|
+
}, [o]), x = e(() => {
|
|
123
170
|
if (typeof window > "u") return null;
|
|
124
|
-
let e =
|
|
171
|
+
let e = o.getSnapshot();
|
|
125
172
|
if (e.popupWindow && !e.popupWindow.closed) return e.popupWindow.focus(), e.popupWindow;
|
|
126
|
-
let t =
|
|
127
|
-
if (!
|
|
128
|
-
let r = n.document;
|
|
129
|
-
r.title = t.title ?? document.title;
|
|
173
|
+
let t = u.current, n = t.url ?? h, r = window.open(n, t.name ?? "_blank", m(t));
|
|
174
|
+
if (!r) return o.setState({ blocked: !0 }), t.onBlocked?.(), null;
|
|
130
175
|
let i;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
176
|
+
try {
|
|
177
|
+
i = r.document;
|
|
178
|
+
} catch {
|
|
179
|
+
return r.close(), o.setState({ blocked: !0 }), t.onBlocked?.(), null;
|
|
180
|
+
}
|
|
181
|
+
let a = () => b(!0), s = () => {
|
|
135
182
|
setTimeout(() => {
|
|
136
|
-
|
|
183
|
+
r.closed && a();
|
|
137
184
|
}, 0);
|
|
185
|
+
}, f = window.setInterval(() => {
|
|
186
|
+
r.closed && a();
|
|
187
|
+
}, 250), p = () => r.close();
|
|
188
|
+
window.addEventListener("pagehide", p);
|
|
189
|
+
let y, x = !1, S, C;
|
|
190
|
+
d.current = () => {
|
|
191
|
+
window.clearInterval(f), window.clearInterval(y), window.removeEventListener("pagehide", p), x && r.removeEventListener("pagehide", s), S?.(), C?.();
|
|
138
192
|
};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
n
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
193
|
+
let w = (e) => {
|
|
194
|
+
x = !0, r.addEventListener("pagehide", s), e.title = t.title ?? document.title;
|
|
195
|
+
let n = [];
|
|
196
|
+
if (t.copyStyles !== !1) {
|
|
197
|
+
let t = c(document, e);
|
|
198
|
+
S = t.stop, n = t.links;
|
|
199
|
+
}
|
|
200
|
+
let i = e.createElement("div");
|
|
201
|
+
i.setAttribute("data-popup-window-root", ""), e.body.appendChild(i), C = l(n, _, () => {
|
|
202
|
+
o.setState({ container: i }), t.onOpen?.(r);
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
return o.setState({
|
|
206
|
+
popupWindow: r,
|
|
207
|
+
container: null,
|
|
148
208
|
blocked: !1
|
|
149
|
-
}),
|
|
150
|
-
|
|
151
|
-
|
|
209
|
+
}), n === h ? w(i) : y = window.setInterval(() => {
|
|
210
|
+
if (r.closed) {
|
|
211
|
+
a();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
let e;
|
|
215
|
+
try {
|
|
216
|
+
e = v(r);
|
|
217
|
+
} catch {
|
|
218
|
+
b(!1), o.setState({ blocked: !0 }), t.onBlocked?.();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
e && (window.clearInterval(y), w(e));
|
|
222
|
+
}, g), r;
|
|
223
|
+
}, [o, b]), S = e(() => b(!1), [b]), C = e(() => {
|
|
224
|
+
o.getSnapshot().popupWindow ? b(!1) : x();
|
|
152
225
|
}, [
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
]),
|
|
157
|
-
let { popupWindow: e } =
|
|
226
|
+
o,
|
|
227
|
+
b,
|
|
228
|
+
x
|
|
229
|
+
]), w = e(() => {
|
|
230
|
+
let { popupWindow: e } = o.getSnapshot();
|
|
158
231
|
e && !e.closed && e.focus();
|
|
159
|
-
}, [
|
|
160
|
-
return t(() => () =>
|
|
161
|
-
open:
|
|
162
|
-
close:
|
|
163
|
-
toggle:
|
|
164
|
-
focus:
|
|
165
|
-
isOpen:
|
|
166
|
-
isBlocked:
|
|
167
|
-
popupWindow:
|
|
168
|
-
Popup:
|
|
232
|
+
}, [o]);
|
|
233
|
+
return t(() => () => b(!1), [b]), {
|
|
234
|
+
open: x,
|
|
235
|
+
close: S,
|
|
236
|
+
toggle: C,
|
|
237
|
+
focus: w,
|
|
238
|
+
isOpen: p.popupWindow !== null,
|
|
239
|
+
isBlocked: p.blocked,
|
|
240
|
+
popupWindow: p.popupWindow,
|
|
241
|
+
Popup: s
|
|
169
242
|
};
|
|
170
243
|
}
|
|
171
244
|
//#endregion
|
|
172
|
-
export { o as copyStyles,
|
|
245
|
+
export { o as copyStyles, b as usePopupWindow };
|
|
173
246
|
|
|
174
247
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/copyStyles.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\r\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\r\n * in sync while the popup is open:\r\n *\r\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\r\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\r\n * possible, so rules injected via `insertRule` (CSS-in-JS \"speedy\" mode)\r\n * are included.\r\n * - Additions/removals/edits of style nodes in the source `<head>` are\r\n * observed (covers Vite HMR, lazily loaded chunk CSS, styled-components).\r\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\r\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\r\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\r\n *\r\n * Returns a function that stops observing.\r\n */\r\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\r\n const mirrors = new Map<Element, Element>()\r\n\r\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\r\n if (node.nodeType !== Node.ELEMENT_NODE) return false\r\n const el = node as Element\r\n return (\r\n el.tagName === 'STYLE' ||\r\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\r\n )\r\n }\r\n\r\n const serializeStyle = (el: HTMLStyleElement): string => {\r\n const sheet = el.sheet\r\n if (sheet) {\r\n try {\r\n return Array.from(sheet.cssRules)\r\n .map((rule) => rule.cssText)\r\n .join('\\n')\r\n } catch {\r\n // Inaccessible cssRules — fall back to the raw text below.\r\n }\r\n }\r\n return el.textContent ?? ''\r\n }\r\n\r\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\r\n if (mirrors.has(el)) return\r\n let clone: Element\r\n if (el.tagName === 'LINK') {\r\n const link = target.createElement('link')\r\n link.rel = 'stylesheet'\r\n // .href resolves to an absolute URL, so relative hrefs keep working\r\n // from the popup's `about:blank` document.\r\n link.href = (el as HTMLLinkElement).href\r\n const media = el.getAttribute('media')\r\n if (media) link.media = media\r\n clone = link\r\n } else {\r\n const style = target.createElement('style')\r\n style.textContent = serializeStyle(el as HTMLStyleElement)\r\n const media = el.getAttribute('media')\r\n if (media) style.setAttribute('media', media)\r\n clone = style\r\n }\r\n target.head.appendChild(clone)\r\n mirrors.set(el, clone)\r\n }\r\n\r\n const refresh = (el: HTMLStyleElement): void => {\r\n const clone = mirrors.get(el)\r\n if (clone) clone.textContent = serializeStyle(el)\r\n }\r\n\r\n const unmirrorSubtree = (removed: Node): void => {\r\n for (const [src, clone] of mirrors) {\r\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\r\n clone.remove()\r\n mirrors.delete(src)\r\n }\r\n }\r\n }\r\n\r\n const ROOT_ATTRS = ['class', 'style']\r\n const syncRootAttrs = (): void => {\r\n const pairs: Array<[Element | null, Element | null]> = [\r\n [source.documentElement, target.documentElement],\r\n [source.body, target.body],\r\n ]\r\n for (const [from, to] of pairs) {\r\n if (!from || !to) continue\r\n for (const attr of from.attributes) {\r\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\r\n to.setAttribute(attr.name, attr.value)\r\n }\r\n }\r\n for (const attr of Array.from(to.attributes)) {\r\n if (\r\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\r\n !from.hasAttribute(attr.name)\r\n ) {\r\n to.removeAttribute(attr.name)\r\n }\r\n }\r\n }\r\n }\r\n\r\n const syncAdoptedSheets = (): void => {\r\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\r\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\r\n try {\r\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\r\n const copy = new targetWindow.CSSStyleSheet!()\r\n copy.replaceSync(\r\n Array.from(sheet.cssRules)\r\n .map((rule) => rule.cssText)\r\n .join('\\n'),\r\n )\r\n return copy\r\n })\r\n } catch {\r\n // Best effort — older browsers or cross-origin rules.\r\n }\r\n }\r\n\r\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\r\n mirror(el as HTMLStyleElement | HTMLLinkElement)\r\n }\r\n syncRootAttrs()\r\n syncAdoptedSheets()\r\n\r\n if (!watch || typeof MutationObserver === 'undefined') {\r\n return () => {}\r\n }\r\n\r\n const headObserver = new MutationObserver((records) => {\r\n for (const record of records) {\r\n if (record.type === 'characterData') {\r\n const parent = record.target.parentElement\r\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\r\n continue\r\n }\r\n // childList\r\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\r\n // textContent assignment replaces the text node → childList on <style>\r\n refresh(record.target as HTMLStyleElement)\r\n }\r\n for (const added of record.addedNodes) {\r\n if (isStyleNode(added)) {\r\n mirror(added)\r\n } else if (added.nodeType === Node.ELEMENT_NODE) {\r\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\r\n mirror(el as HTMLStyleElement | HTMLLinkElement)\r\n }\r\n }\r\n }\r\n for (const removed of record.removedNodes) {\r\n unmirrorSubtree(removed)\r\n }\r\n }\r\n })\r\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\r\n\r\n const rootObserver = new MutationObserver(() => syncRootAttrs())\r\n rootObserver.observe(source.documentElement, { attributes: true })\r\n if (source.body) rootObserver.observe(source.body, { attributes: true })\r\n\r\n return () => {\r\n headObserver.disconnect()\r\n rootObserver.disconnect()\r\n }\r\n}\r\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\r\nimport type { FC } from 'react'\r\nimport { createPortal } from 'react-dom'\r\nimport { copyStyles } from './copyStyles'\r\nimport type {\r\n PopupProps,\r\n PopupWindowApi,\r\n PopupWindowFeatures,\r\n UsePopupWindowOptions,\r\n} from './types'\r\n\r\ninterface PopupState {\r\n popupWindow: Window | null\r\n container: HTMLElement | null\r\n blocked: boolean\r\n}\r\n\r\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\r\n\r\nclass PopupStore {\r\n state: PopupState = INITIAL_STATE\r\n private listeners = new Set<() => void>()\r\n\r\n subscribe = (listener: () => void): (() => void) => {\r\n this.listeners.add(listener)\r\n return () => this.listeners.delete(listener)\r\n }\r\n\r\n getSnapshot = (): PopupState => this.state\r\n\r\n getServerSnapshot = (): PopupState => INITIAL_STATE\r\n\r\n setState(partial: Partial<PopupState>): void {\r\n this.state = { ...this.state, ...partial }\r\n for (const listener of this.listeners) listener()\r\n }\r\n}\r\n\r\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\r\n\r\nfunction buildFeatures(options: UsePopupWindowOptions): string {\r\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\r\n if (\r\n options.center !== false &&\r\n merged.left === undefined &&\r\n merged.top === undefined &&\r\n typeof merged.width === 'number' &&\r\n typeof merged.height === 'number'\r\n ) {\r\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\r\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\r\n }\r\n return Object.entries(merged)\r\n .filter(([, value]) => value !== undefined && value !== false)\r\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\r\n .join(',')\r\n}\r\n\r\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\r\n function Popup({ children }: PopupProps) {\r\n const { container } = useSyncExternalStore(\r\n store.subscribe,\r\n store.getSnapshot,\r\n store.getServerSnapshot,\r\n )\r\n return container ? createPortal(children, container) : null\r\n }\r\n Popup.displayName = 'PopupWindow.Popup'\r\n return Popup\r\n}\r\n\r\n/**\r\n * Open part of your React tree in a separate browser window.\r\n *\r\n * The popup content is rendered with a portal into the popup's document, so\r\n * it stays part of your component tree: state, context and event handlers\r\n * all keep working across windows.\r\n *\r\n * ```tsx\r\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\r\n *\r\n * return (\r\n * <>\r\n * <button onClick={open}>Open panel</button>\r\n * <Popup>\r\n * <MyPanel />\r\n * </Popup>\r\n * </>\r\n * )\r\n * ```\r\n */\r\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\r\n const [store] = useState(() => new PopupStore())\r\n const [Popup] = useState(() => createPopupComponent(store))\r\n\r\n const optionsRef = useRef(options)\r\n optionsRef.current = options\r\n\r\n const cleanupRef = useRef<(() => void) | null>(null)\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\r\n\r\n // `userClosed` is true when the window was closed outside our control\r\n // (user hit the close button, opener unloaded) — then we must not touch it.\r\n const closePopup = useCallback(\r\n (userClosed: boolean) => {\r\n const { popupWindow } = store.getSnapshot()\r\n cleanupRef.current?.()\r\n cleanupRef.current = null\r\n if (!popupWindow) return\r\n if (!userClosed && !popupWindow.closed) popupWindow.close()\r\n store.setState({ popupWindow: null, container: null })\r\n optionsRef.current.onClose?.()\r\n },\r\n [store],\r\n )\r\n\r\n const open = useCallback((): Window | null => {\r\n if (typeof window === 'undefined') return null\r\n const current = store.getSnapshot()\r\n if (current.popupWindow && !current.popupWindow.closed) {\r\n current.popupWindow.focus()\r\n return current.popupWindow\r\n }\r\n\r\n const opts = optionsRef.current\r\n const popupWindow = window.open('about:blank', opts.name ?? '_blank', buildFeatures(opts))\r\n if (!popupWindow) {\r\n store.setState({ blocked: true })\r\n opts.onBlocked?.()\r\n return null\r\n }\r\n\r\n const doc = popupWindow.document\r\n doc.title = opts.title ?? document.title\r\n\r\n let stopStyleSync: (() => void) | undefined\r\n if (opts.copyStyles !== false) {\r\n stopStyleSync = copyStyles(document, doc)\r\n }\r\n\r\n const container = doc.createElement('div')\r\n container.setAttribute('data-popup-window-root', '')\r\n doc.body.appendChild(container)\r\n\r\n const handleExternalClose = () => closePopup(true)\r\n const onPopupPagehide = () => {\r\n // pagehide also fires on navigation; only treat it as a close when the\r\n // window really is gone a tick later.\r\n setTimeout(() => {\r\n if (popupWindow.closed) handleExternalClose()\r\n }, 0)\r\n }\r\n popupWindow.addEventListener('pagehide', onPopupPagehide)\r\n\r\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\r\n const closePoll = window.setInterval(() => {\r\n if (popupWindow.closed) handleExternalClose()\r\n }, 250)\r\n\r\n const onOpenerPagehide = () => popupWindow.close()\r\n window.addEventListener('pagehide', onOpenerPagehide)\r\n\r\n cleanupRef.current = () => {\r\n window.clearInterval(closePoll)\r\n window.removeEventListener('pagehide', onOpenerPagehide)\r\n popupWindow.removeEventListener('pagehide', onPopupPagehide)\r\n stopStyleSync?.()\r\n }\r\n\r\n store.setState({ popupWindow, container, blocked: false })\r\n opts.onOpen?.(popupWindow)\r\n return popupWindow\r\n }, [store, closePopup])\r\n\r\n const close = useCallback(() => closePopup(false), [closePopup])\r\n\r\n const toggle = useCallback(() => {\r\n if (store.getSnapshot().popupWindow) {\r\n closePopup(false)\r\n } else {\r\n open()\r\n }\r\n }, [store, closePopup, open])\r\n\r\n const focus = useCallback(() => {\r\n const { popupWindow } = store.getSnapshot()\r\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\r\n }, [store])\r\n\r\n // Close the popup when the owning component unmounts — its portal content\r\n // would unmount anyway, leaving an empty window behind.\r\n useEffect(() => {\r\n return () => closePopup(false)\r\n }, [closePopup])\r\n\r\n return {\r\n open,\r\n close,\r\n toggle,\r\n focus,\r\n isOpen: state.popupWindow !== null,\r\n isBlocked: state.blocked,\r\n popupWindow: state.popupWindow,\r\n Popup,\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/copyStyles.ts","../src/whenStylesheetsLoad.ts","../src/usePopupWindow.ts"],"sourcesContent":["/**\n * Copy all stylesheets from `source` into `target` and (optionally) keep them\n * in sync while the popup is open:\n *\n * - `<link rel=\"stylesheet\">` and `<style>` elements are mirrored into the\n * target `<head>`. `<style>` contents are serialized from the CSSOM when\n * possible, so rules injected via `insertRule` (CSS-in-JS \"speedy\" mode)\n * are included.\n * - Additions/removals/edits of style nodes in the source `<head>` are\n * observed (covers Vite HMR, lazily loaded chunk CSS, styled-components).\n * - `class`/`style`/`data-*` attributes on `<html>` and `<body>` are mirrored\n * and kept in sync, so theme switching (e.g. a `dark` class) propagates.\n * - `document.adoptedStyleSheets` are re-constructed in the target document.\n *\n * Returns a function that stops observing.\n */\nexport function copyStyles(source: Document, target: Document, watch = true): () => void {\n return mirrorStyles(source, target, watch).stop\n}\n\n/** Attributes copied from a source `<link>` onto its mirror. */\nconst LINK_ATTRS = ['rel', 'media', 'title', 'crossorigin', 'referrerpolicy', 'integrity']\n\nexport interface StyleMirror {\n /** Stops observing the source document. */\n stop: () => void\n /**\n * The `<link>` elements created in `target` by the initial copy. The target\n * fetches each of them asynchronously.\n */\n links: HTMLLinkElement[]\n}\n\n/** {@link copyStyles}, plus the `<link>` elements the initial copy created. */\nexport function mirrorStyles(source: Document, target: Document, watch = true): StyleMirror {\n const mirrors = new Map<Element, Element>()\n\n const isStyleNode = (node: Node): node is HTMLStyleElement | HTMLLinkElement => {\n if (node.nodeType !== Node.ELEMENT_NODE) return false\n const el = node as Element\n return (\n el.tagName === 'STYLE' ||\n (el.tagName === 'LINK' && (el.getAttribute('rel') ?? '').toLowerCase().includes('stylesheet'))\n )\n }\n\n const serializeStyle = (el: HTMLStyleElement): string => {\n const sheet = el.sheet\n if (sheet) {\n try {\n return Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n')\n } catch {\n // Inaccessible cssRules — fall back to the raw text below.\n }\n }\n return el.textContent ?? ''\n }\n\n const mirror = (el: HTMLStyleElement | HTMLLinkElement): void => {\n if (mirrors.has(el)) return\n let clone: Element\n if (el.tagName === 'LINK') {\n const link = target.createElement('link')\n // `rel` is copied as is, so an alternate stylesheet stays disabled.\n // `crossorigin` and `referrerpolicy` make the popup send the same request\n // as the opener, so it can reuse the cached response.\n for (const name of LINK_ATTRS) {\n const value = el.getAttribute(name)\n if (value !== null) link.setAttribute(name, value)\n }\n // .href resolves to an absolute URL, so relative hrefs keep working\n // from the popup's `about:blank` document.\n link.href = (el as HTMLLinkElement).href\n clone = link\n } else {\n const style = target.createElement('style')\n style.textContent = serializeStyle(el as HTMLStyleElement)\n const media = el.getAttribute('media')\n if (media) style.setAttribute('media', media)\n clone = style\n }\n target.head.appendChild(clone)\n mirrors.set(el, clone)\n }\n\n const refresh = (el: HTMLStyleElement): void => {\n const clone = mirrors.get(el)\n if (clone) clone.textContent = serializeStyle(el)\n }\n\n const unmirrorSubtree = (removed: Node): void => {\n for (const [src, clone] of mirrors) {\n if (removed === src || (removed.nodeType === Node.ELEMENT_NODE && removed.contains(src))) {\n clone.remove()\n mirrors.delete(src)\n }\n }\n }\n\n const ROOT_ATTRS = ['class', 'style']\n const syncRootAttrs = (): void => {\n const pairs: Array<[Element | null, Element | null]> = [\n [source.documentElement, target.documentElement],\n [source.body, target.body],\n ]\n for (const [from, to] of pairs) {\n if (!from || !to) continue\n for (const attr of from.attributes) {\n if (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) {\n to.setAttribute(attr.name, attr.value)\n }\n }\n for (const attr of Array.from(to.attributes)) {\n if (\n (ROOT_ATTRS.includes(attr.name) || attr.name.startsWith('data-')) &&\n !from.hasAttribute(attr.name)\n ) {\n to.removeAttribute(attr.name)\n }\n }\n }\n }\n\n const syncAdoptedSheets = (): void => {\n const targetWindow = target.defaultView as (Window & { CSSStyleSheet?: typeof CSSStyleSheet }) | null\n if (!targetWindow?.CSSStyleSheet || !('adoptedStyleSheets' in source)) return\n try {\n target.adoptedStyleSheets = source.adoptedStyleSheets.map((sheet) => {\n const copy = new targetWindow.CSSStyleSheet!()\n copy.replaceSync(\n Array.from(sheet.cssRules)\n .map((rule) => rule.cssText)\n .join('\\n'),\n )\n return copy\n })\n } catch {\n // Best effort — older browsers or cross-origin rules.\n }\n }\n\n for (const el of source.querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n const links = Array.from(mirrors.values()).filter(\n (clone): clone is HTMLLinkElement => clone.tagName === 'LINK',\n )\n syncRootAttrs()\n syncAdoptedSheets()\n\n if (!watch || typeof MutationObserver === 'undefined') {\n return { stop: () => {}, links }\n }\n\n const headObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === 'characterData') {\n const parent = record.target.parentElement\n if (parent?.tagName === 'STYLE') refresh(parent as HTMLStyleElement)\n continue\n }\n // childList\n if (isStyleNode(record.target) && record.target.tagName === 'STYLE') {\n // textContent assignment replaces the text node → childList on <style>\n refresh(record.target as HTMLStyleElement)\n }\n for (const added of record.addedNodes) {\n if (isStyleNode(added)) {\n mirror(added)\n } else if (added.nodeType === Node.ELEMENT_NODE) {\n for (const el of (added as Element).querySelectorAll('style, link[rel~=\"stylesheet\" i]')) {\n mirror(el as HTMLStyleElement | HTMLLinkElement)\n }\n }\n }\n for (const removed of record.removedNodes) {\n unmirrorSubtree(removed)\n }\n }\n })\n headObserver.observe(source.head, { childList: true, subtree: true, characterData: true })\n\n const rootObserver = new MutationObserver(() => syncRootAttrs())\n rootObserver.observe(source.documentElement, { attributes: true })\n if (source.body) rootObserver.observe(source.body, { attributes: true })\n\n return {\n stop: () => {\n headObserver.disconnect()\n rootObserver.disconnect()\n },\n links,\n }\n}\n","/**\n * Calls `callback` once every stylesheet in `links` that applies has loaded\n * or failed, or after `timeoutMs`, whichever comes first.\n *\n * When none of them is still loading, `callback` runs synchronously, before\n * this function returns. Links that do not apply are not waited for: an\n * alternate stylesheet, or a `media` query that does not match, such as\n * `print`. Browsers fetch those at the lowest priority.\n *\n * Returns a function that stops waiting without calling `callback`.\n */\nexport function whenStylesheetsLoad(\n links: readonly HTMLLinkElement[],\n timeoutMs: number,\n callback: () => void,\n): () => void {\n const pending = links.filter((link) => link.sheet === null && applies(link))\n if (pending.length === 0) {\n callback()\n return () => {}\n }\n\n let remaining = pending.length\n const settle = () => {\n remaining--\n if (remaining === 0) finish()\n }\n const stop = () => {\n window.clearTimeout(timeout)\n for (const link of pending) {\n link.removeEventListener('load', settle)\n link.removeEventListener('error', settle)\n }\n }\n const finish = () => {\n stop()\n callback()\n }\n\n for (const link of pending) {\n link.addEventListener('load', settle)\n // A broken stylesheet must not keep the popup blank.\n link.addEventListener('error', settle)\n }\n const timeout = window.setTimeout(finish, timeoutMs)\n return stop\n}\n\nfunction applies(link: HTMLLinkElement): boolean {\n if (/\\balternate\\b/i.test(link.rel)) return false\n const view = link.ownerDocument.defaultView\n return !link.media || !view?.matchMedia || view.matchMedia(link.media).matches\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport type { FC } from 'react'\nimport { createPortal } from 'react-dom'\nimport { mirrorStyles } from './copyStyles'\nimport type {\n PopupProps,\n PopupWindowApi,\n PopupWindowFeatures,\n UsePopupWindowOptions,\n} from './types'\nimport { whenStylesheetsLoad } from './whenStylesheetsLoad'\n\ninterface PopupState {\n popupWindow: Window | null\n container: HTMLElement | null\n blocked: boolean\n}\n\nconst INITIAL_STATE: PopupState = { popupWindow: null, container: null, blocked: false }\n\nclass PopupStore {\n state: PopupState = INITIAL_STATE\n private listeners = new Set<() => void>()\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n getSnapshot = (): PopupState => this.state\n\n getServerSnapshot = (): PopupState => INITIAL_STATE\n\n setState(partial: Partial<PopupState>): void {\n this.state = { ...this.state, ...partial }\n for (const listener of this.listeners) listener()\n }\n}\n\nconst DEFAULT_FEATURES: PopupWindowFeatures = { popup: true, width: 640, height: 480 }\n\nfunction buildFeatures(options: UsePopupWindowOptions): string {\n const merged: PopupWindowFeatures = { ...DEFAULT_FEATURES, ...options.features }\n if (\n options.center !== false &&\n merged.left === undefined &&\n merged.top === undefined &&\n typeof merged.width === 'number' &&\n typeof merged.height === 'number'\n ) {\n merged.left = Math.max(0, Math.round(window.screenX + (window.outerWidth - merged.width) / 2))\n merged.top = Math.max(0, Math.round(window.screenY + (window.outerHeight - merged.height) / 2))\n }\n return Object.entries(merged)\n .filter(([, value]) => value !== undefined && value !== false)\n .map(([key, value]) => (value === true ? `${key}=yes` : `${key}=${value}`))\n .join(',')\n}\n\nconst ABOUT_BLANK = 'about:blank'\n/** Interval for checking whether the page given by `url` has loaded. */\nconst LOAD_POLL_MS = 20\n/** Longest time `Popup` waits for copied stylesheets before rendering anyway. */\nconst STYLES_TIMEOUT_MS = 3000\n\n/**\n * The popup's document once the page given by `url` has replaced the initial\n * `about:blank` document and finished parsing, otherwise `null`. Throws when\n * the document is not scriptable.\n */\nfunction loadedDocument(popupWindow: Window): Document | null {\n const doc = popupWindow.document\n if (doc.URL === ABOUT_BLANK || doc.readyState === 'loading') return null\n return doc\n}\n\nfunction createPopupComponent(store: PopupStore): FC<PopupProps> {\n function Popup({ children }: PopupProps) {\n const { container } = useSyncExternalStore(\n store.subscribe,\n store.getSnapshot,\n store.getServerSnapshot,\n )\n return container ? createPortal(children, container) : null\n }\n Popup.displayName = 'PopupWindow.Popup'\n return Popup\n}\n\n/**\n * Open part of your React tree in a separate browser window.\n *\n * The popup content is rendered with a portal into the popup's document, so\n * it stays part of your component tree: state, context and event handlers\n * all keep working across windows.\n *\n * ```tsx\n * const { open, close, isOpen, Popup } = usePopupWindow({ title: 'Panel' })\n *\n * return (\n * <>\n * <button onClick={open}>Open panel</button>\n * <Popup>\n * <MyPanel />\n * </Popup>\n * </>\n * )\n * ```\n */\nexport function usePopupWindow(options: UsePopupWindowOptions = {}): PopupWindowApi {\n const [store] = useState(() => new PopupStore())\n const [Popup] = useState(() => createPopupComponent(store))\n\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const cleanupRef = useRef<(() => void) | null>(null)\n\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)\n\n // `userClosed` is true when the window was closed outside our control\n // (user hit the close button, opener unloaded) — then we must not touch it.\n const closePopup = useCallback(\n (userClosed: boolean) => {\n const { popupWindow } = store.getSnapshot()\n cleanupRef.current?.()\n cleanupRef.current = null\n if (!popupWindow) return\n if (!userClosed && !popupWindow.closed) popupWindow.close()\n store.setState({ popupWindow: null, container: null })\n optionsRef.current.onClose?.()\n },\n [store],\n )\n\n const open = useCallback((): Window | null => {\n if (typeof window === 'undefined') return null\n const current = store.getSnapshot()\n if (current.popupWindow && !current.popupWindow.closed) {\n current.popupWindow.focus()\n return current.popupWindow\n }\n\n const opts = optionsRef.current\n const url = opts.url ?? ABOUT_BLANK\n const popupWindow = window.open(url, opts.name ?? '_blank', buildFeatures(opts))\n if (!popupWindow) {\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n // Sandboxed embedders (VS Code's built-in browser, CodeSandbox/StackBlitz\n // previews, iframes sandboxed without popup-escape) open the popup with an\n // opaque origin, so reading its document throws a SecurityError. Treat\n // that as blocked instead of stranding a blank window the portal can\n // never reach.\n let initialDoc: Document\n try {\n initialDoc = popupWindow.document\n } catch {\n popupWindow.close()\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return null\n }\n\n const handleExternalClose = () => closePopup(true)\n const onPopupPagehide = () => {\n // pagehide also fires on navigation; only treat it as a close when the\n // window really is gone a tick later.\n setTimeout(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 0)\n }\n\n // Belt and braces: some browsers don't fire pagehide reliably for popups.\n const closePoll = window.setInterval(() => {\n if (popupWindow.closed) handleExternalClose()\n }, 250)\n\n const onOpenerPagehide = () => popupWindow.close()\n window.addEventListener('pagehide', onOpenerPagehide)\n\n let loadPoll: number | undefined\n let prepared = false\n let stopStyleSync: (() => void) | undefined\n let stopStylesWait: (() => void) | undefined\n\n cleanupRef.current = () => {\n window.clearInterval(closePoll)\n window.clearInterval(loadPoll)\n window.removeEventListener('pagehide', onOpenerPagehide)\n if (prepared) popupWindow.removeEventListener('pagehide', onPopupPagehide)\n stopStyleSync?.()\n stopStylesWait?.()\n }\n\n // Title, styles and the portal container go into the document that\n // stays: `about:blank` itself, or the page `url` loads in its place.\n const prepare = (doc: Document) => {\n prepared = true\n popupWindow.addEventListener('pagehide', onPopupPagehide)\n doc.title = opts.title ?? document.title\n let links: HTMLLinkElement[] = []\n if (opts.copyStyles !== false) {\n const styles = mirrorStyles(document, doc)\n stopStyleSync = styles.stop\n links = styles.links\n }\n const container = doc.createElement('div')\n container.setAttribute('data-popup-window-root', '')\n doc.body.appendChild(container)\n // The popup fetches copied <link> stylesheets asynchronously. Render\n // only once they have loaded, so the first paint is not unstyled.\n stopStylesWait = whenStylesheetsLoad(links, STYLES_TIMEOUT_MS, () => {\n store.setState({ container })\n opts.onOpen?.(popupWindow)\n })\n }\n\n store.setState({ popupWindow, container: null, blocked: false })\n\n if (url === ABOUT_BLANK) {\n prepare(initialDoc)\n } else {\n // The window starts on an initial about:blank document and then\n // navigates to `url`. Wait until that page has replaced it and parsed.\n loadPoll = window.setInterval(() => {\n if (popupWindow.closed) {\n handleExternalClose()\n return\n }\n let doc: Document | null\n try {\n doc = loadedDocument(popupWindow)\n } catch {\n // The page went somewhere the opener cannot script, such as a\n // cross-origin redirect: nothing can ever render there.\n closePopup(false)\n store.setState({ blocked: true })\n opts.onBlocked?.()\n return\n }\n if (!doc) return\n window.clearInterval(loadPoll)\n prepare(doc)\n }, LOAD_POLL_MS)\n }\n\n return popupWindow\n }, [store, closePopup])\n\n const close = useCallback(() => closePopup(false), [closePopup])\n\n const toggle = useCallback(() => {\n if (store.getSnapshot().popupWindow) {\n closePopup(false)\n } else {\n open()\n }\n }, [store, closePopup, open])\n\n const focus = useCallback(() => {\n const { popupWindow } = store.getSnapshot()\n if (popupWindow && !popupWindow.closed) popupWindow.focus()\n }, [store])\n\n // Close the popup when the owning component unmounts — its portal content\n // would unmount anyway, leaving an empty window behind.\n useEffect(() => {\n return () => closePopup(false)\n }, [closePopup])\n\n return {\n open,\n close,\n toggle,\n focus,\n isOpen: state.popupWindow !== null,\n isBlocked: state.blocked,\n popupWindow: state.popupWindow,\n Popup,\n }\n}\n"],"mappings":"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"}
|