@jielga/react-popup-window 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,211 +1,211 @@
1
- ---
2
- name: getting-started
3
- description: >
4
- Set up @jielga/react-popup-window: the usePopupWindow hook, the Popup portal
5
- component, window controls (open, close, toggle, focus), reactive state
6
- (isOpen, isBlocked, popupWindow), and hook options (title, name, features,
7
- center, copyStyles, onOpen, onClose, onBlocked). Load when opening part of a
8
- React tree in a separate browser window, building an "open in new window" or
9
- detachable panel, or when a popup is blocked, closes unexpectedly, or loses
10
- state.
11
- metadata:
12
- type: core
13
- library: '@jielga/react-popup-window'
14
- library_version: '0.1.0'
15
- sources:
16
- - 'Jielga/react-popup-window:README.md'
17
- - 'Jielga/react-popup-window:src/usePopupWindow.ts'
18
- - 'Jielga/react-popup-window:src/types.ts'
19
- ---
20
-
21
- # react-popup-window — Getting started
22
-
23
- `usePopupWindow` opens part of a React tree in a separate browser window.
24
- Content is rendered with a portal into the popup's `about:blank` document,
25
- so it remains part of the calling component tree: state, context, and event
26
- handlers work across windows without bridging. The popup document runs no
27
- JavaScript of its own; the opener's React instance renders into it.
28
-
29
- ## Setup
30
-
31
- ```tsx
32
- import { usePopupWindow } from '@jielga/react-popup-window'
33
-
34
- function Dashboard() {
35
- const { open, close, isOpen, Popup } = usePopupWindow({
36
- title: 'Detached panel',
37
- features: { width: 640, height: 480 },
38
- })
39
-
40
- return (
41
- <>
42
- <button onClick={open}>Open in new window</button>
43
- <Popup>
44
- <MyPanel />
45
- </Popup>
46
- </>
47
- )
48
- }
49
- ```
50
-
51
- `Popup` renders its children into the popup while open and nothing
52
- otherwise. It has a stable identity; destructuring it from the hook result
53
- is safe.
54
-
55
- ## Core patterns
56
-
57
- ### Detach a section: hide it inline while popped out
58
-
59
- ```tsx
60
- const { open, close, focus, isOpen, Popup } = usePopupWindow({ title: 'People' })
61
-
62
- const table = <DataTable />
63
-
64
- return (
65
- <>
66
- {isOpen ? (
67
- <div>
68
- <button onClick={focus}>Focus window</button>
69
- <button onClick={close}>Bring back</button>
70
- </div>
71
- ) : (
72
- <>
73
- <button onClick={open}>Open in new window</button>
74
- {table}
75
- </>
76
- )}
77
- <Popup>{table}</Popup>
78
- </>
79
- )
80
- ```
81
-
82
- The element is created once and mounted in exactly one of the two homes at
83
- a time. `isOpen` also flips when the user closes the window by hand, so the
84
- inline branch restores itself.
85
-
86
- ### Handle blocked popups
87
-
88
- ```tsx
89
- const { open, isBlocked } = usePopupWindow({ onBlocked: () => notifyUser() })
90
- // open() returns Window | null; null means blocked (or non-browser environment)
91
- ```
92
-
93
- ### Options
94
-
95
- ```ts
96
- interface UsePopupWindowOptions {
97
- title?: string // popup document title; defaults to the opener's title
98
- name?: string // window.open target name; same name reuses the window
99
- features?: PopupWindowFeatures // merged over { popup: true, width: 640, height: 480 }
100
- center?: boolean // center over the opener window; default true
101
- copyStyles?: boolean // mirror and live-sync stylesheets; default true
102
- onOpen?: (popupWindow: Window) => void
103
- onClose?: () => void // close(), user close, or opener unload
104
- onBlocked?: () => void
105
- }
106
- ```
107
-
108
- Return value: `{ open, close, toggle, focus, isOpen, isBlocked, popupWindow,
109
- Popup }`. `popupWindow` is the raw `Window` while open, otherwise `null`.
110
-
111
- Lifecycle: the popup closes automatically when the component that owns the
112
- hook unmounts and when the opener window unloads. Closing the window by
113
- hand is detected (`pagehide` plus a `closed` poll); `isOpen` flips and the
114
- portal unmounts.
115
-
116
- ## Common mistakes
117
-
118
- ### HIGH Calling open() outside a user gesture
119
-
120
- Wrong:
121
-
122
- ```tsx
123
- useEffect(() => {
124
- open() // blocked by the popup blocker on mount
125
- }, [open])
126
- ```
127
-
128
- Correct:
129
-
130
- ```tsx
131
- <button onClick={open}>Open panel</button>
132
- ```
133
-
134
- Browsers only allow `window.open` in response to a user gesture; outside
135
- one, `open()` returns `null` and sets `isBlocked` without throwing.
136
-
137
- Source: README.md, src/usePopupWindow.ts (open)
138
-
139
- ### HIGH Expecting component-local state to survive detaching
140
-
141
- Wrong:
142
-
143
- ```tsx
144
- function Panel() {
145
- const [sort, setSort] = useState('name') // resets when the panel moves windows
146
- return <SortedList sort={sort} onSort={setSort} />
147
- }
148
- ```
149
-
150
- Correct:
151
-
152
- ```tsx
153
- function Owner() {
154
- const [sort, setSort] = useState('name') // owner stays mounted in the main window
155
- const { open, isOpen, Popup } = usePopupWindow()
156
- const panel = <SortedList sort={sort} onSort={setSort} />
157
- return <>{isOpen ? null : panel}<Popup>{panel}</Popup></>
158
- }
159
- ```
160
-
161
- Popup content unmounts and remounts when it moves between windows, so state
162
- held inside it is discarded. State held by the component that owns the hook
163
- persists, because that component never moves.
164
-
165
- Source: README.md (Remounting)
166
-
167
- ### MEDIUM Messaging the opener from a portal event handler
168
-
169
- Wrong:
170
-
171
- ```tsx
172
- // inside <Popup> content
173
- <button onClick={() => popupWindow.opener.postMessage(data, '*')}>Send</button>
174
- ```
175
-
176
- Correct:
177
-
178
- ```tsx
179
- // inside <Popup> content — same tree, same realm; call the handler directly
180
- <button onClick={() => onData(data)}>Send</button>
181
- ```
182
-
183
- Portal content executes in the main window's JavaScript realm, so the
184
- message is posted by the main window to itself and `event.source` is the
185
- main window. `postMessage` is only meaningful for scripts hosted in the
186
- popup document itself; for portal content, props, state, and context are
187
- the communication channel.
188
-
189
- Source: README.md (Communicating with popup content)
190
-
191
- ### MEDIUM Keeping the popup open across owner unmount or navigation
192
-
193
- Wrong:
194
-
195
- ```tsx
196
- // route A mounts the hook; navigating to route B is expected to keep the popup
197
- <Route path="/a" element={<PanelWithPopup />} />
198
- ```
199
-
200
- Correct:
201
-
202
- ```tsx
203
- // mount the hook in a component that stays mounted across the interaction,
204
- // e.g. a layout component above the route switch
205
- ```
206
-
207
- The popup is closed deliberately when the owning component unmounts: its
208
- portal content would unmount anyway, leaving an empty window. Place the
209
- hook at a level that lives as long as the popup should.
210
-
211
- Source: src/usePopupWindow.ts (unmount effect)
1
+ ---
2
+ name: getting-started
3
+ description: >
4
+ Set up @jielga/react-popup-window: the usePopupWindow hook, the Popup portal
5
+ component, window controls (open, close, toggle, focus), reactive state
6
+ (isOpen, isBlocked, popupWindow), and hook options (title, name, features,
7
+ center, copyStyles, onOpen, onClose, onBlocked). Load when opening part of a
8
+ React tree in a separate browser window, building an "open in new window" or
9
+ detachable panel, or when a popup is blocked, closes unexpectedly, or loses
10
+ state.
11
+ metadata:
12
+ type: core
13
+ library: '@jielga/react-popup-window'
14
+ library_version: '0.1.1'
15
+ sources:
16
+ - 'Jielga/react-popup-window:README.md'
17
+ - 'Jielga/react-popup-window:src/usePopupWindow.ts'
18
+ - 'Jielga/react-popup-window:src/types.ts'
19
+ ---
20
+
21
+ # react-popup-window — Getting started
22
+
23
+ `usePopupWindow` opens part of a React tree in a separate browser window.
24
+ Content is rendered with a portal into the popup's `about:blank` document,
25
+ so it remains part of the calling component tree: state, context, and event
26
+ handlers work across windows without bridging. The popup document runs no
27
+ JavaScript of its own; the opener's React instance renders into it.
28
+
29
+ ## Setup
30
+
31
+ ```tsx
32
+ import { usePopupWindow } from '@jielga/react-popup-window'
33
+
34
+ function Dashboard() {
35
+ const { open, close, isOpen, Popup } = usePopupWindow({
36
+ title: 'Detached panel',
37
+ features: { width: 640, height: 480 },
38
+ })
39
+
40
+ return (
41
+ <>
42
+ <button onClick={open}>Open in new window</button>
43
+ <Popup>
44
+ <MyPanel />
45
+ </Popup>
46
+ </>
47
+ )
48
+ }
49
+ ```
50
+
51
+ `Popup` renders its children into the popup while open and nothing
52
+ otherwise. It has a stable identity; destructuring it from the hook result
53
+ is safe.
54
+
55
+ ## Core patterns
56
+
57
+ ### Detach a section: hide it inline while popped out
58
+
59
+ ```tsx
60
+ const { open, close, focus, isOpen, Popup } = usePopupWindow({ title: 'People' })
61
+
62
+ const table = <DataTable />
63
+
64
+ return (
65
+ <>
66
+ {isOpen ? (
67
+ <div>
68
+ <button onClick={focus}>Focus window</button>
69
+ <button onClick={close}>Bring back</button>
70
+ </div>
71
+ ) : (
72
+ <>
73
+ <button onClick={open}>Open in new window</button>
74
+ {table}
75
+ </>
76
+ )}
77
+ <Popup>{table}</Popup>
78
+ </>
79
+ )
80
+ ```
81
+
82
+ The element is created once and mounted in exactly one of the two homes at
83
+ a time. `isOpen` also flips when the user closes the window by hand, so the
84
+ inline branch restores itself.
85
+
86
+ ### Handle blocked popups
87
+
88
+ ```tsx
89
+ const { open, isBlocked } = usePopupWindow({ onBlocked: () => notifyUser() })
90
+ // open() returns Window | null; null means blocked (or non-browser environment)
91
+ ```
92
+
93
+ ### Options
94
+
95
+ ```ts
96
+ interface UsePopupWindowOptions {
97
+ title?: string // popup document title; defaults to the opener's title
98
+ name?: string // window.open target name; same name reuses the window
99
+ features?: PopupWindowFeatures // merged over { popup: true, width: 640, height: 480 }
100
+ center?: boolean // center over the opener window; default true
101
+ copyStyles?: boolean // mirror and live-sync stylesheets; default true
102
+ onOpen?: (popupWindow: Window) => void
103
+ onClose?: () => void // close(), user close, or opener unload
104
+ onBlocked?: () => void
105
+ }
106
+ ```
107
+
108
+ Return value: `{ open, close, toggle, focus, isOpen, isBlocked, popupWindow,
109
+ Popup }`. `popupWindow` is the raw `Window` while open, otherwise `null`.
110
+
111
+ Lifecycle: the popup closes automatically when the component that owns the
112
+ hook unmounts and when the opener window unloads. Closing the window by
113
+ hand is detected (`pagehide` plus a `closed` poll); `isOpen` flips and the
114
+ portal unmounts.
115
+
116
+ ## Common mistakes
117
+
118
+ ### HIGH Calling open() outside a user gesture
119
+
120
+ Wrong:
121
+
122
+ ```tsx
123
+ useEffect(() => {
124
+ open() // blocked by the popup blocker on mount
125
+ }, [open])
126
+ ```
127
+
128
+ Correct:
129
+
130
+ ```tsx
131
+ <button onClick={open}>Open panel</button>
132
+ ```
133
+
134
+ Browsers only allow `window.open` in response to a user gesture; outside
135
+ one, `open()` returns `null` and sets `isBlocked` without throwing.
136
+
137
+ Source: README.md, src/usePopupWindow.ts (open)
138
+
139
+ ### HIGH Expecting component-local state to survive detaching
140
+
141
+ Wrong:
142
+
143
+ ```tsx
144
+ function Panel() {
145
+ const [sort, setSort] = useState('name') // resets when the panel moves windows
146
+ return <SortedList sort={sort} onSort={setSort} />
147
+ }
148
+ ```
149
+
150
+ Correct:
151
+
152
+ ```tsx
153
+ function Owner() {
154
+ const [sort, setSort] = useState('name') // owner stays mounted in the main window
155
+ const { open, isOpen, Popup } = usePopupWindow()
156
+ const panel = <SortedList sort={sort} onSort={setSort} />
157
+ return <>{isOpen ? null : panel}<Popup>{panel}</Popup></>
158
+ }
159
+ ```
160
+
161
+ Popup content unmounts and remounts when it moves between windows, so state
162
+ held inside it is discarded. State held by the component that owns the hook
163
+ persists, because that component never moves.
164
+
165
+ Source: README.md (Limitations)
166
+
167
+ ### MEDIUM Messaging the opener from a portal event handler
168
+
169
+ Wrong:
170
+
171
+ ```tsx
172
+ // inside <Popup> content
173
+ <button onClick={() => popupWindow.opener.postMessage(data, '*')}>Send</button>
174
+ ```
175
+
176
+ Correct:
177
+
178
+ ```tsx
179
+ // inside <Popup> content — same tree, same realm; call the handler directly
180
+ <button onClick={() => onData(data)}>Send</button>
181
+ ```
182
+
183
+ Portal content executes in the main window's JavaScript realm, so the
184
+ message is posted by the main window to itself and `event.source` is the
185
+ main window. `postMessage` is only meaningful for scripts hosted in the
186
+ popup document itself; for portal content, props, state, and context are
187
+ the communication channel.
188
+
189
+ Source: README.md (Communication)
190
+
191
+ ### MEDIUM Keeping the popup open across owner unmount or navigation
192
+
193
+ Wrong:
194
+
195
+ ```tsx
196
+ // route A mounts the hook; navigating to route B is expected to keep the popup
197
+ <Route path="/a" element={<PanelWithPopup />} />
198
+ ```
199
+
200
+ Correct:
201
+
202
+ ```tsx
203
+ // mount the hook in a component that stays mounted across the interaction,
204
+ // e.g. a layout component above the route switch
205
+ ```
206
+
207
+ The popup is closed deliberately when the owning component unmounts: its
208
+ portal content would unmount anyway, leaving an empty window. Place the
209
+ hook at a level that lives as long as the popup should.
210
+
211
+ Source: src/usePopupWindow.ts (unmount effect)