ui-soxo-bootstrap-core 2.4.26 โ 2.5.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/core/components/external-window/DEVELOPER_GUIDE.md +705 -0
- package/core/components/external-window/external-window.js +225 -0
- package/core/components/external-window/external-window.test.js +80 -0
- package/core/components/index.js +4 -1
- package/core/components/landing-api/landing-api.js +18 -18
- package/core/lib/Store.js +20 -18
- package/core/lib/components/index.js +4 -1
- package/core/lib/elements/basic/rangepicker/rangepicker.js +118 -29
- package/core/lib/elements/basic/switch/switch.js +34 -24
- package/core/models/dashboard/dashboard.js +14 -0
- package/core/modules/index.js +2 -0
- package/core/modules/steps/action-buttons.js +88 -0
- package/core/modules/steps/steps.js +332 -0
- package/core/modules/steps/steps.scss +158 -0
- package/core/modules/steps/timeline.js +54 -0
- package/jest.config.js +8 -0
- package/jest.setup.js +1 -0
- package/package.json +9 -4
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
# ExternalWindow Component
|
|
2
|
+
|
|
3
|
+
A React component that renders content in a separate browser window with full style inheritance and keyboard shortcuts support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ๐ช Opens content in a new browser window
|
|
8
|
+
- ๐จ Automatically copies parent window styles (Ant Design, SCSS, etc.)
|
|
9
|
+
- โจ๏ธ Customizable keyboard shortcuts
|
|
10
|
+
- ๐ Flexible positioning (manual or auto-center)
|
|
11
|
+
- ๐ React Portal-based rendering
|
|
12
|
+
- โก No white flash - instant background color matching
|
|
13
|
+
- ๐งน Automatic cleanup on unmount
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
import { useState, useCallback } from 'react';
|
|
19
|
+
import { ExternalWindow } from './ExternalWindow';
|
|
20
|
+
import { Button } from 'antd';
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
const [showWindow, setShowWindow] = useState(false);
|
|
24
|
+
|
|
25
|
+
// Use useCallback to prevent re-renders
|
|
26
|
+
const handleClose = useCallback(() => {
|
|
27
|
+
setShowWindow(false);
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
<Button onClick={() => setShowWindow(true)}>Open New Window</Button>
|
|
33
|
+
|
|
34
|
+
{showWindow && (
|
|
35
|
+
<ExternalWindow title="My External Window" onClose={handleClose}>
|
|
36
|
+
<div style={{ padding: 20 }}>
|
|
37
|
+
<h1>Hello from external window!</h1>
|
|
38
|
+
<p>All your styles are automatically copied here.</p>
|
|
39
|
+
</div>
|
|
40
|
+
</ExternalWindow>
|
|
41
|
+
)}
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Props Reference
|
|
48
|
+
|
|
49
|
+
### Props
|
|
50
|
+
|
|
51
|
+
| Prop | Type | Default | Description |
|
|
52
|
+
| -------------- | --------------- | -------------------------------------------- | ----------------------------------------------------------------- |
|
|
53
|
+
| `children` | `ReactNode` | **required** | Content to render in the new window |
|
|
54
|
+
| `onClose` | `Function` | **required** | Callback when window closes (wrap in `useCallback`) |
|
|
55
|
+
| `title` | `string` | `'New Window'` | Window title (shown in browser tab) |
|
|
56
|
+
| `width` | `number` | `600` | Window width in pixels |
|
|
57
|
+
| `height` | `number` | `400` | Window height in pixels |
|
|
58
|
+
| `left` | `number` | `200` | Window X position (overridden by `centerScreen`) |
|
|
59
|
+
| `top` | `number` | `200` | Window Y position (overridden by `centerScreen`) |
|
|
60
|
+
| `copyStyles` | `boolean` | `true` | Whether to copy parent window styles |
|
|
61
|
+
| `centerScreen` | `string\|false` | `false` | Center window: `'horizontal'`, `'vertical'`, `'both'`, or `false` |
|
|
62
|
+
| `shortcuts` | `Object` | `{ close: 'Escape', focus: 'Ctrl+Shift+F' }` | Keyboard shortcuts configuration |
|
|
63
|
+
| `onMinimize` | `Function` | `undefined` | Callback when window is minimized |
|
|
64
|
+
| `onMaximize` | `Function` | `undefined` | Callback when window is maximized |
|
|
65
|
+
|
|
66
|
+
## Usage Examples
|
|
67
|
+
|
|
68
|
+
### Centered Window
|
|
69
|
+
|
|
70
|
+
```jsx
|
|
71
|
+
import { useState, useCallback } from 'react';
|
|
72
|
+
|
|
73
|
+
function App() {
|
|
74
|
+
const [showWindow, setShowWindow] = useState(false);
|
|
75
|
+
|
|
76
|
+
const handleClose = useCallback(() => {
|
|
77
|
+
setShowWindow(false);
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<>
|
|
82
|
+
<Button onClick={() => setShowWindow(true)}>Open Centered</Button>
|
|
83
|
+
|
|
84
|
+
{showWindow && (
|
|
85
|
+
<ExternalWindow title="Centered Window" width={800} height={600} centerScreen="both" onClose={handleClose}>
|
|
86
|
+
<YourContent />
|
|
87
|
+
</ExternalWindow>
|
|
88
|
+
)}
|
|
89
|
+
</>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Custom Positioning
|
|
95
|
+
|
|
96
|
+
```jsx
|
|
97
|
+
const handleClose = useCallback(() => setShowWindow(false), []);
|
|
98
|
+
|
|
99
|
+
<ExternalWindow title="Custom Position" width={1000} height={700} left={100} top={50} onClose={handleClose}>
|
|
100
|
+
<YourContent />
|
|
101
|
+
</ExternalWindow>;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### With Custom Keyboard Shortcuts
|
|
105
|
+
|
|
106
|
+
```jsx
|
|
107
|
+
const handleClose = useCallback(() => setShowWindow(false), []);
|
|
108
|
+
const handleMinimize = useCallback(() => console.log('Minimized'), []);
|
|
109
|
+
const handleMaximize = useCallback(() => console.log('Maximized'), []);
|
|
110
|
+
|
|
111
|
+
<ExternalWindow
|
|
112
|
+
title="With Shortcuts"
|
|
113
|
+
shortcuts={{
|
|
114
|
+
close: 'Escape', // Close window
|
|
115
|
+
focus: 'Ctrl+Shift+F', // Focus window
|
|
116
|
+
minimize: 'Ctrl+M', // Minimize window
|
|
117
|
+
maximize: 'Ctrl+Shift+M', // Maximize window
|
|
118
|
+
}}
|
|
119
|
+
onClose={handleClose}
|
|
120
|
+
onMinimize={handleMinimize}
|
|
121
|
+
onMaximize={handleMaximize}
|
|
122
|
+
>
|
|
123
|
+
<YourContent />
|
|
124
|
+
</ExternalWindow>;
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Without Style Copying
|
|
128
|
+
|
|
129
|
+
```jsx
|
|
130
|
+
const handleClose = useCallback(() => setShowWindow(false), []);
|
|
131
|
+
|
|
132
|
+
<ExternalWindow title="No Styles" copyStyles={false} onClose={handleClose}>
|
|
133
|
+
{/* You'll need to add inline styles or external CSS links */}
|
|
134
|
+
<div style={{ padding: 20, fontFamily: 'Arial' }}>
|
|
135
|
+
<h1>Plain content</h1>
|
|
136
|
+
</div>
|
|
137
|
+
</ExternalWindow>;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Managing Multiple Windows
|
|
141
|
+
|
|
142
|
+
```jsx
|
|
143
|
+
function MultiWindowApp() {
|
|
144
|
+
const [windows, setWindows] = useState([]);
|
|
145
|
+
|
|
146
|
+
const openWindow = useCallback((config) => {
|
|
147
|
+
const id = Date.now();
|
|
148
|
+
setWindows((prev) => [...prev, { id, ...config }]);
|
|
149
|
+
}, []);
|
|
150
|
+
|
|
151
|
+
const closeWindow = useCallback((id) => {
|
|
152
|
+
setWindows((prev) => prev.filter((w) => w.id !== id));
|
|
153
|
+
}, []);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<>
|
|
157
|
+
<Button
|
|
158
|
+
onClick={() =>
|
|
159
|
+
openWindow({
|
|
160
|
+
title: 'Window ' + (windows.length + 1),
|
|
161
|
+
content: <MyComponent />,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
>
|
|
165
|
+
Add Window
|
|
166
|
+
</Button>
|
|
167
|
+
|
|
168
|
+
{windows.map(({ id, title, content }) => (
|
|
169
|
+
<ExternalWindow key={id} title={title} onClose={() => closeWindow(id)}>
|
|
170
|
+
{content}
|
|
171
|
+
</ExternalWindow>
|
|
172
|
+
))}
|
|
173
|
+
</>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Full-Featured Dashboard Example
|
|
179
|
+
|
|
180
|
+
```jsx
|
|
181
|
+
import { useState, useCallback } from 'react';
|
|
182
|
+
import { ExternalWindow } from './ExternalWindow';
|
|
183
|
+
import { Button, Card, Space } from 'antd';
|
|
184
|
+
|
|
185
|
+
function Dashboard() {
|
|
186
|
+
const [windows, setWindows] = useState({
|
|
187
|
+
chart: false,
|
|
188
|
+
report: false,
|
|
189
|
+
settings: false,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const openWindow = useCallback((name) => {
|
|
193
|
+
setWindows((prev) => ({ ...prev, [name]: true }));
|
|
194
|
+
}, []);
|
|
195
|
+
|
|
196
|
+
const closeWindow = useCallback((name) => {
|
|
197
|
+
setWindows((prev) => ({ ...prev, [name]: false }));
|
|
198
|
+
}, []);
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div style={{ padding: 24 }}>
|
|
202
|
+
<Space>
|
|
203
|
+
<Button onClick={() => openWindow('chart')}>Open Chart</Button>
|
|
204
|
+
<Button onClick={() => openWindow('report')}>Open Report</Button>
|
|
205
|
+
<Button onClick={() => openWindow('settings')}>Open Settings</Button>
|
|
206
|
+
</Space>
|
|
207
|
+
|
|
208
|
+
{windows.chart && (
|
|
209
|
+
<ExternalWindow title="Analytics Chart" width={1200} height={800} centerScreen="both" onClose={() => closeWindow('chart')}>
|
|
210
|
+
<Card title="Sales Analytics" style={{ margin: 20 }}>
|
|
211
|
+
{/* Your chart component */}
|
|
212
|
+
</Card>
|
|
213
|
+
</ExternalWindow>
|
|
214
|
+
)}
|
|
215
|
+
|
|
216
|
+
{windows.report && (
|
|
217
|
+
<ExternalWindow title="Monthly Report" width={900} height={700} left={50} top={50} onClose={() => closeWindow('report')}>
|
|
218
|
+
<div style={{ padding: 20 }}>{/* Your report component */}</div>
|
|
219
|
+
</ExternalWindow>
|
|
220
|
+
)}
|
|
221
|
+
|
|
222
|
+
{windows.settings && (
|
|
223
|
+
<ExternalWindow
|
|
224
|
+
title="Settings"
|
|
225
|
+
width={600}
|
|
226
|
+
height={500}
|
|
227
|
+
centerScreen="horizontal"
|
|
228
|
+
shortcuts={{
|
|
229
|
+
close: 'Escape',
|
|
230
|
+
focus: 'Ctrl+S',
|
|
231
|
+
}}
|
|
232
|
+
onClose={() => closeWindow('settings')}
|
|
233
|
+
>
|
|
234
|
+
<div style={{ padding: 20 }}>{/* Your settings component */}</div>
|
|
235
|
+
</ExternalWindow>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### With Shared State
|
|
243
|
+
|
|
244
|
+
```jsx
|
|
245
|
+
function StatefulWindow() {
|
|
246
|
+
const [showWindow, setShowWindow] = useState(false);
|
|
247
|
+
const [data, setData] = useState([]);
|
|
248
|
+
|
|
249
|
+
const handleClose = useCallback(() => {
|
|
250
|
+
setShowWindow(false);
|
|
251
|
+
}, []);
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<>
|
|
255
|
+
<Button onClick={() => setShowWindow(true)}>Open Data Viewer</Button>
|
|
256
|
+
|
|
257
|
+
{showWindow && (
|
|
258
|
+
<ExternalWindow title="Data Viewer" onClose={handleClose}>
|
|
259
|
+
{/* State is shared between parent and child window */}
|
|
260
|
+
<DataTable data={data} onUpdate={setData} />
|
|
261
|
+
</ExternalWindow>
|
|
262
|
+
)}
|
|
263
|
+
</>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Keyboard Shortcuts
|
|
269
|
+
|
|
270
|
+
### Default Shortcuts
|
|
271
|
+
|
|
272
|
+
- **Escape**: Close window
|
|
273
|
+
- **Ctrl+Shift+F**: Focus window
|
|
274
|
+
|
|
275
|
+
### Customizing Shortcuts
|
|
276
|
+
|
|
277
|
+
Define custom shortcuts using the `shortcuts` prop. Shortcuts support modifiers:
|
|
278
|
+
|
|
279
|
+
- `Ctrl` / `Control`
|
|
280
|
+
- `Shift`
|
|
281
|
+
- `Alt`
|
|
282
|
+
- `Meta` (Command on Mac)
|
|
283
|
+
|
|
284
|
+
**Format Examples:**
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
shortcuts={{
|
|
288
|
+
close: 'Escape',
|
|
289
|
+
focus: 'Ctrl+Shift+F',
|
|
290
|
+
minimize: 'Ctrl+M',
|
|
291
|
+
maximize: 'Ctrl+Shift+M',
|
|
292
|
+
save: 'Ctrl+S',
|
|
293
|
+
custom: 'Alt+K'
|
|
294
|
+
}}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## How It Works
|
|
298
|
+
|
|
299
|
+
### Style Inheritance
|
|
300
|
+
|
|
301
|
+
When `copyStyles={true}` (default):
|
|
302
|
+
|
|
303
|
+
1. **Instant Background**: Critical inline styles are applied immediately, inheriting the parent window's background color and text color
|
|
304
|
+
2. **No White Flash**: The window opens with proper styling from the start
|
|
305
|
+
3. **Async Loading**: Full stylesheets are copied asynchronously for smooth rendering
|
|
306
|
+
4. **Complete Coverage**: All parent window styles are copied:
|
|
307
|
+
- Ant Design themes
|
|
308
|
+
- SCSS/CSS modules
|
|
309
|
+
- Custom fonts
|
|
310
|
+
- External stylesheets
|
|
311
|
+
- Inline styles
|
|
312
|
+
|
|
313
|
+
### Window Lifecycle
|
|
314
|
+
|
|
315
|
+
1. Window opens with specified dimensions and position
|
|
316
|
+
2. Critical styles applied instantly (background, colors, layout)
|
|
317
|
+
3. Full styles copied from parent window
|
|
318
|
+
4. Content rendered via React Portal
|
|
319
|
+
5. Event listeners attached (keyboard shortcuts, window close)
|
|
320
|
+
6. Automatic cleanup on component unmount
|
|
321
|
+
|
|
322
|
+
### Performance Optimizations
|
|
323
|
+
|
|
324
|
+
- **Single Window Creation**: Window is created once and reused throughout component lifecycle
|
|
325
|
+
- **Async Style Loading**: Styles are copied using `requestAnimationFrame` to avoid blocking
|
|
326
|
+
- **Stable References**: Uses `useCallback` and `useMemo` to prevent unnecessary re-renders
|
|
327
|
+
- **Efficient Cleanup**: Proper event listener removal and window closure
|
|
328
|
+
|
|
329
|
+
## Best Practices
|
|
330
|
+
|
|
331
|
+
### โ
Do's
|
|
332
|
+
|
|
333
|
+
```jsx
|
|
334
|
+
// Use useCallback for all callbacks
|
|
335
|
+
const handleClose = useCallback(() => setShowWindow(false), []);
|
|
336
|
+
const handleMinimize = useCallback(() => console.log('Min'), []);
|
|
337
|
+
|
|
338
|
+
// Center important windows
|
|
339
|
+
<ExternalWindow centerScreen="both" ... />
|
|
340
|
+
|
|
341
|
+
// Provide meaningful titles
|
|
342
|
+
<ExternalWindow title="Sales Report - Q4 2024" ... />
|
|
343
|
+
|
|
344
|
+
// Use appropriate dimensions
|
|
345
|
+
<ExternalWindow width={1200} height={800} ... />
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### โ Don'ts
|
|
349
|
+
|
|
350
|
+
```jsx
|
|
351
|
+
// Don't use inline functions - causes re-renders
|
|
352
|
+
<ExternalWindow onClose={() => setShowWindow(false)} ... />
|
|
353
|
+
|
|
354
|
+
// Don't forget to handle window state
|
|
355
|
+
{showWindow && <ExternalWindow ... />}
|
|
356
|
+
|
|
357
|
+
// Don't use tiny dimensions
|
|
358
|
+
<ExternalWindow width={100} height={100} ... />
|
|
359
|
+
|
|
360
|
+
// Don't disable style copying without reason
|
|
361
|
+
<ExternalWindow copyStyles={false} ... />
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Advanced Patterns
|
|
365
|
+
|
|
366
|
+
### Window with Tabs
|
|
367
|
+
|
|
368
|
+
```jsx
|
|
369
|
+
function TabbedWindow() {
|
|
370
|
+
const [showWindow, setShowWindow] = useState(false);
|
|
371
|
+
const [activeTab, setActiveTab] = useState('1');
|
|
372
|
+
|
|
373
|
+
const handleClose = useCallback(() => setShowWindow(false), []);
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<>
|
|
377
|
+
<Button onClick={() => setShowWindow(true)}>Open</Button>
|
|
378
|
+
|
|
379
|
+
{showWindow && (
|
|
380
|
+
<ExternalWindow title="Multi-Tab Window" width={1000} height={700} centerScreen="both" onClose={handleClose}>
|
|
381
|
+
<Tabs activeKey={activeTab} onChange={setActiveTab}>
|
|
382
|
+
<TabPane tab="Dashboard" key="1">
|
|
383
|
+
<Dashboard />
|
|
384
|
+
</TabPane>
|
|
385
|
+
<TabPane tab="Settings" key="2">
|
|
386
|
+
<Settings />
|
|
387
|
+
</TabPane>
|
|
388
|
+
<TabPane tab="Reports" key="3">
|
|
389
|
+
<Reports />
|
|
390
|
+
</TabPane>
|
|
391
|
+
</Tabs>
|
|
392
|
+
</ExternalWindow>
|
|
393
|
+
)}
|
|
394
|
+
</>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Window with Form Submission
|
|
400
|
+
|
|
401
|
+
```jsx
|
|
402
|
+
function FormWindow() {
|
|
403
|
+
const [showWindow, setShowWindow] = useState(false);
|
|
404
|
+
|
|
405
|
+
const handleClose = useCallback(() => setShowWindow(false), []);
|
|
406
|
+
|
|
407
|
+
const handleSubmit = useCallback((values) => {
|
|
408
|
+
console.log('Form submitted:', values);
|
|
409
|
+
setShowWindow(false);
|
|
410
|
+
}, []);
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
<>
|
|
414
|
+
<Button onClick={() => setShowWindow(true)}>Open Form</Button>
|
|
415
|
+
|
|
416
|
+
{showWindow && (
|
|
417
|
+
<ExternalWindow title="User Form" width={600} height={500} centerScreen="both" onClose={handleClose}>
|
|
418
|
+
<Form onFinish={handleSubmit} style={{ padding: 20 }}>
|
|
419
|
+
<Form.Item name="name" label="Name">
|
|
420
|
+
<Input />
|
|
421
|
+
</Form.Item>
|
|
422
|
+
<Form.Item>
|
|
423
|
+
<Button type="primary" htmlType="submit">
|
|
424
|
+
Submit
|
|
425
|
+
</Button>
|
|
426
|
+
</Form.Item>
|
|
427
|
+
</Form>
|
|
428
|
+
</ExternalWindow>
|
|
429
|
+
)}
|
|
430
|
+
</>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Browser Compatibility
|
|
436
|
+
|
|
437
|
+
| Browser | Support | Notes |
|
|
438
|
+
| ------------- | ---------- | -------------------------------- |
|
|
439
|
+
| Chrome/Edge | โ
Full | Recommended |
|
|
440
|
+
| Firefox | โ
Full | All features work |
|
|
441
|
+
| Safari | โ
Full | All features work |
|
|
442
|
+
| Mobile Safari | โ ๏ธ Limited | Popup windows not well supported |
|
|
443
|
+
| Mobile Chrome | โ ๏ธ Limited | Popup windows not well supported |
|
|
444
|
+
|
|
445
|
+
**Note**: Mobile browsers have limited support for popup windows due to platform restrictions.
|
|
446
|
+
|
|
447
|
+
## Troubleshooting
|
|
448
|
+
|
|
449
|
+
### Issue: Window flickering or re-rendering constantly
|
|
450
|
+
|
|
451
|
+
**Symptoms:**
|
|
452
|
+
|
|
453
|
+
- Window content flickers
|
|
454
|
+
- Continuous re-renders
|
|
455
|
+
- Poor performance
|
|
456
|
+
|
|
457
|
+
**Cause:** The `onClose` callback is not memoized, creating a new function reference on every render.
|
|
458
|
+
|
|
459
|
+
**Solution:**
|
|
460
|
+
|
|
461
|
+
```jsx
|
|
462
|
+
// โ Wrong - creates new function every render
|
|
463
|
+
<ExternalWindow onClose={() => setShowWindow(false)}>
|
|
464
|
+
|
|
465
|
+
// โ
Correct - stable function reference
|
|
466
|
+
const handleClose = useCallback(() => setShowWindow(false), []);
|
|
467
|
+
<ExternalWindow onClose={handleClose}>
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
### Issue: Window not opening / "Please allow popups" message
|
|
473
|
+
|
|
474
|
+
**Symptoms:**
|
|
475
|
+
|
|
476
|
+
- Error message appears
|
|
477
|
+
- Window doesn't open
|
|
478
|
+
- `window.open()` returns `null`
|
|
479
|
+
|
|
480
|
+
**Cause:** Browser popup blocker is active.
|
|
481
|
+
|
|
482
|
+
**Solution:**
|
|
483
|
+
|
|
484
|
+
1. Click the popup blocker icon in the browser's address bar
|
|
485
|
+
2. Allow popups for your site
|
|
486
|
+
3. Refresh the page and try again
|
|
487
|
+
|
|
488
|
+
**Prevention:** Always trigger `ExternalWindow` from a user action (click, keyboard event), not automatically on page load.
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
### Issue: Styles not appearing correctly
|
|
493
|
+
|
|
494
|
+
**Symptoms:**
|
|
495
|
+
|
|
496
|
+
- Missing Ant Design styles
|
|
497
|
+
- SCSS not applied
|
|
498
|
+
- Different appearance from parent window
|
|
499
|
+
|
|
500
|
+
**Cause:**
|
|
501
|
+
|
|
502
|
+
- `copyStyles={false}` is set
|
|
503
|
+
- Cross-origin stylesheet restrictions (CORS)
|
|
504
|
+
- Styles loaded after component initialization
|
|
505
|
+
|
|
506
|
+
**Solution:**
|
|
507
|
+
|
|
508
|
+
```jsx
|
|
509
|
+
// Ensure copyStyles is true (default)
|
|
510
|
+
<ExternalWindow copyStyles={true} ... />
|
|
511
|
+
|
|
512
|
+
// Check browser console for CORS warnings
|
|
513
|
+
// Allow time for async style loading
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
### Issue: White flash when opening window
|
|
519
|
+
|
|
520
|
+
**Status:** โ
**Fixed in current version**
|
|
521
|
+
|
|
522
|
+
The component now includes critical inline styles that apply immediately, inheriting the parent window's background color. Full styles load asynchronously without blocking.
|
|
523
|
+
|
|
524
|
+
If you still see a white flash:
|
|
525
|
+
|
|
526
|
+
- Ensure you're using the latest version of the component
|
|
527
|
+
- Check that `copyStyles={true}` (default)
|
|
528
|
+
- Verify your parent window has a defined background color
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
### Issue: Unexpected body spacing or margins
|
|
533
|
+
|
|
534
|
+
**Symptoms:**
|
|
535
|
+
|
|
536
|
+
- Unwanted whitespace around content
|
|
537
|
+
- Incorrect layout
|
|
538
|
+
- Content not filling window
|
|
539
|
+
|
|
540
|
+
**Cause:** Browser default styles or inherited styles.
|
|
541
|
+
|
|
542
|
+
**Solution:** The component applies aggressive CSS resets:
|
|
543
|
+
|
|
544
|
+
```css
|
|
545
|
+
html,
|
|
546
|
+
body {
|
|
547
|
+
margin: 0 !important;
|
|
548
|
+
padding: 0 !important;
|
|
549
|
+
width: 100%;
|
|
550
|
+
height: 100%;
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
If you need custom body styles, apply them to your content wrapper:
|
|
555
|
+
|
|
556
|
+
```jsx
|
|
557
|
+
<ExternalWindow ...>
|
|
558
|
+
<div style={{ padding: 20, background: '#f0f0f0' }}>
|
|
559
|
+
Your content here
|
|
560
|
+
</div>
|
|
561
|
+
</ExternalWindow>
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
### Issue: Keyboard shortcuts not working
|
|
567
|
+
|
|
568
|
+
**Symptoms:**
|
|
569
|
+
|
|
570
|
+
- Pressing shortcut keys doesn't trigger actions
|
|
571
|
+
- Only works in parent window
|
|
572
|
+
|
|
573
|
+
**Cause:**
|
|
574
|
+
|
|
575
|
+
- Window doesn't have focus
|
|
576
|
+
- Shortcut conflicts with browser defaults
|
|
577
|
+
- Incorrect shortcut format
|
|
578
|
+
|
|
579
|
+
**Solution:**
|
|
580
|
+
|
|
581
|
+
```jsx
|
|
582
|
+
// Correct format
|
|
583
|
+
shortcuts={{
|
|
584
|
+
close: 'Escape',
|
|
585
|
+
focus: 'Ctrl+Shift+F', // Not 'Control+Shift+F'
|
|
586
|
+
save: 'Ctrl+S'
|
|
587
|
+
}}
|
|
588
|
+
|
|
589
|
+
// Use focus shortcut to bring window to front
|
|
590
|
+
// Avoid browser shortcuts (Ctrl+T, Ctrl+W, etc.)
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
### Issue: Multiple windows interfere with each other
|
|
596
|
+
|
|
597
|
+
**Symptoms:**
|
|
598
|
+
|
|
599
|
+
- Opening one window closes another
|
|
600
|
+
- State conflicts between windows
|
|
601
|
+
|
|
602
|
+
**Cause:** Poor state management or shared state keys.
|
|
603
|
+
|
|
604
|
+
**Solution:**
|
|
605
|
+
|
|
606
|
+
```jsx
|
|
607
|
+
// Use unique keys and separate state
|
|
608
|
+
const [windows, setWindows] = useState({});
|
|
609
|
+
|
|
610
|
+
const openWindow = (id) => {
|
|
611
|
+
setWindows((prev) => ({ ...prev, [id]: true }));
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const closeWindow = (id) => {
|
|
615
|
+
setWindows((prev) => {
|
|
616
|
+
const updated = { ...prev };
|
|
617
|
+
delete updated[id];
|
|
618
|
+
return updated;
|
|
619
|
+
});
|
|
620
|
+
};
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
### Issue: Content not updating in external window
|
|
626
|
+
|
|
627
|
+
**Symptoms:**
|
|
628
|
+
|
|
629
|
+
- Changes in parent don't reflect in child window
|
|
630
|
+
- Stale data displayed
|
|
631
|
+
|
|
632
|
+
**Cause:** React Portal works correctly, but state might not be lifting properly.
|
|
633
|
+
|
|
634
|
+
**Solution:** React Portal automatically syncs state. Ensure your state is at the correct level:
|
|
635
|
+
|
|
636
|
+
```jsx
|
|
637
|
+
// State should be in parent component
|
|
638
|
+
const [data, setData] = useState([]);
|
|
639
|
+
|
|
640
|
+
<ExternalWindow ...>
|
|
641
|
+
<DataDisplay data={data} onChange={setData} />
|
|
642
|
+
</ExternalWindow>
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
### Issue: Window closes unexpectedly
|
|
648
|
+
|
|
649
|
+
**Symptoms:**
|
|
650
|
+
|
|
651
|
+
- Window closes on its own
|
|
652
|
+
- Window closes when clicking parent
|
|
653
|
+
|
|
654
|
+
**Cause:**
|
|
655
|
+
|
|
656
|
+
- User closed the window (triggers `onClose`)
|
|
657
|
+
- Parent component unmounted
|
|
658
|
+
- State management issue
|
|
659
|
+
|
|
660
|
+
**Solution:** This is expected behavior. The `onClose` callback is triggered when:
|
|
661
|
+
|
|
662
|
+
- User clicks the window's close button
|
|
663
|
+
- User presses the close keyboard shortcut
|
|
664
|
+
- Component unmounts
|
|
665
|
+
- Parent explicitly calls the close handler
|
|
666
|
+
|
|
667
|
+
---
|
|
668
|
+
|
|
669
|
+
### Issue: Cross-origin stylesheet warnings in console
|
|
670
|
+
|
|
671
|
+
**Symptoms:**
|
|
672
|
+
|
|
673
|
+
- Console warnings: "Could not copy stylesheet"
|
|
674
|
+
- Some styles missing
|
|
675
|
+
|
|
676
|
+
**Cause:** CORS restrictions prevent accessing external stylesheet rules.
|
|
677
|
+
|
|
678
|
+
**Solution:** This is expected and usually not a problem:
|
|
679
|
+
|
|
680
|
+
- External stylesheets (CDNs) are copied by `<link>` reference
|
|
681
|
+
- Only inline style rules require CORS access
|
|
682
|
+
- Most styles will still work correctly
|
|
683
|
+
|
|
684
|
+
To debug:
|
|
685
|
+
|
|
686
|
+
```javascript
|
|
687
|
+
// The component logs warnings for debugging
|
|
688
|
+
console.warn('Could not copy stylesheet:', e);
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
---
|
|
692
|
+
|
|
693
|
+
## Getting Help
|
|
694
|
+
|
|
695
|
+
If you encounter issues not covered here:
|
|
696
|
+
|
|
697
|
+
1. Check the browser console for errors
|
|
698
|
+
2. Verify all props are correctly typed
|
|
699
|
+
3. Ensure `useCallback` is used for callbacks
|
|
700
|
+
4. Test in a different browser
|
|
701
|
+
5. Check for popup blocker settings
|
|
702
|
+
|
|
703
|
+
## License
|
|
704
|
+
|
|
705
|
+
MIT
|