layerpro 3.4.0 → 4.0.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.
package/README.md CHANGED
@@ -18,7 +18,11 @@
18
18
  ![OXlint](https://img.shields.io/badge/Oxlint-gray?logo=oxc)
19
19
  ![Playwright](https://img.shields.io/badge/Playwright-gray?logo=playwright)
20
20
 
21
- LayerPro replaces the browser's native `alert()`, `confirm()`, and `prompt()` with beautiful, fully customizable modals that work everywhere - no more blocking the main thread, no more default browser styling.
21
+ LayerPro is a browser window system: it generates **OS-style windows** - draggable, resizable, maximizable, dockable - that can host any content, including **React components**.
22
+
23
+ It also replaces the browser's native `alert()`, `confirm()`, and `prompt()` with beautiful, fully customizable modals that work everywhere - no more blocking the main thread, no more default browser styling.
24
+
25
+ LayerPro is **not only** a replacement for `alert`/`prompt`/`confirm`: the same window engine is used to build rich, interactive windows on top of your app.
22
26
 
23
27
  ## Table of Contents
24
28
 
@@ -28,15 +32,19 @@ LayerPro replaces the browser's native `alert()`, `confirm()`, and `prompt()` wi
28
32
  - [Usage](#usage)
29
33
  - [Basic Modals](#basic-modals)
30
34
  - [Custom Popups](#custom-popups)
35
+ - [OS-style Windows](#os-style-windows)
31
36
  - [React Components](#react-components)
32
37
  - [The `body` Parameter](#the-body-parameter)
33
38
  - [Options](#options)
34
39
  - [Global Overrides](#global-overrides)
35
40
  - [Methods](#methods)
36
41
  - [Styling & z-index](#styling--z-index)
42
+ - [Overriding the CSS](#overriding-the-css)
43
+ - [CSS custom properties](#1-css-custom-properties-recommended)
44
+ - [Target the generated classes](#2-target-the-generated-classes)
45
+ - [Cascade layers](#3-cascade-layers-full-control)
46
+ - [Dock](#dock)
37
47
  - [Security](#security)
38
- - [Browser Support](#browser-support)
39
- - [Contributing](#contributing)
40
48
  - [License](#license)
41
49
 
42
50
  ---
@@ -44,7 +52,9 @@ LayerPro replaces the browser's native `alert()`, `confirm()`, and `prompt()` wi
44
52
  ## Features
45
53
 
46
54
  - **Zero runtime dependencies** - no jQuery, pure vanilla DOM helpers; `react`/`react-dom` are peer dependencies, only loaded when React bodies are used, powered by [`reattach`](https://npmjs.com/package/reattach)
47
- - **Multiple modal types** - alert, confirm, prompt, message
55
+ - **OS-style window engine** - not just modals: every popup is a real window with a title bar, drag handle, resize handles, maximize/restore, minimize-to-dock, focus stacking, and a z-order you control
56
+ - **Hosts any content** - plain text, HTML strings, or **full React components** (with Context bridging and DOM auto-healing), rendered inside the window body
57
+ - **Multiple modal types** - alert, confirm, prompt, message - built on top of the same window engine, so they inherit the same styling, dock, and accessibility features
48
58
  - **Fully customizable** - appearance, behavior, and content
49
59
  - **Dockable windows** (taskbar-style) - click a dock icon to restore, focus, or minimize, just like an OS taskbar
50
60
  - **Draggable & resizable** - instant, with no transition lag while resizing
@@ -72,7 +82,7 @@ Import LayerPro once, in your app's entry point:
72
82
  import "layerpro";
73
83
  ```
74
84
 
75
- That's it - `alert()`, `confirm()`, `prompt()`, `message()`, and `popup()` are now available globally.
85
+ That's it - `layerpro.popup()`, `layerpro.alert()`, `layerpro.confirm()`, `layerpro.prompt()`, `layerpro.message()` and `layerpro.version()` are now available. Import `layerpro/global` instead if you also want the browser's own `alert`/`confirm`/`prompt` overridden (see [Global Overrides](#global-overrides-opt-in-in-40)).
76
86
 
77
87
  The `popup` alias is a shorthand for `layerpro.popup`:
78
88
 
@@ -89,19 +99,15 @@ popup.open({ id: 'myPopup', body: 'Hello World' });
89
99
  ### Basic Modals
90
100
 
91
101
  ```javascript
92
- alert("Hello World!");
102
+ layerpro.alert("Hello World!"); // Promise<void>
103
+ layerpro.message("Operation complete!"); // Promise<void>
93
104
 
94
- confirm("Are you sure?", () => {
95
- console.debug("User clicked YES");
96
- });
97
-
98
- prompt("What's your name?", (name) => {
99
- console.debug("User entered:", name);
100
- });
101
-
102
- message("Operation complete!");
105
+ const confirmed = await layerpro.confirm("Are you sure?"); // Promise<boolean>
106
+ const name = await layerpro.prompt("What's your name?"); // Promise<string>
103
107
  ```
104
108
 
109
+ > **Note:** with the default `import "layerpro"` entry the browser's own `alert`/`confirm`/`prompt` are **not** replaced. Use `import "layerpro/global"` to opt in to the global overrides, or call the `layerpro.*` methods directly as shown above.
110
+
105
111
  ### Custom Popups
106
112
 
107
113
  ```javascript
@@ -118,6 +124,48 @@ popup.open({
118
124
  });
119
125
  ```
120
126
 
127
+ ### OS-style Windows
128
+
129
+ A popup is not limited to a modal dialog. Turn on `movable`, `resizable`, `maximize`, `iconize` and `dockable` and it becomes a real OS-style window: draggable by its title bar, resizable from the edges, maximizable, minimizable into the dock, and stackable with the other windows on the page.
130
+
131
+ ```javascript
132
+ popup.open({
133
+ id: 'dashboard',
134
+ name: 'Dashboard',
135
+ icon: '&#128202;',
136
+ body: '<h2>Live metrics</h2><p>Anything you want inside.</p>',
137
+ width: 640,
138
+ height: 420,
139
+ movable: true,
140
+ resizable: true,
141
+ maximize: true,
142
+ iconize: true,
143
+ dockable: true,
144
+ close: true
145
+ });
146
+ ```
147
+
148
+ Combined with a React component, this becomes a real application window - a dashboard, a settings panel, a file browser - rendered inside the browser page with native-looking chrome:
149
+
150
+ ```jsx
151
+ popup.open({
152
+ id: 'settingsWindow',
153
+ name: 'Settings',
154
+ icon: '&#9881;',
155
+ body: <SettingsPanel />,
156
+ width: 720,
157
+ height: 480,
158
+ movable: true,
159
+ resizable: true,
160
+ maximize: true,
161
+ iconize: true,
162
+ dockable: true,
163
+ close: true
164
+ });
165
+ ```
166
+
167
+ > **Tip:** `modal: true` is what turns a window into a blocking dialog. Leave it off for windows the user should be able to move around while working on the page.
168
+
121
169
  ### React Components
122
170
 
123
171
  LayerPro's React adapter is powered by [`reattach`](https://npmjs.com/package/reattach), a zero-dependency library for mounting React components anywhere in the DOM. It handles root creation, automatic root reuse on duplicate-id reopen, **Context Bridging** across detached roots, and **auto-healing DOM** recovery - all without extra setup.
@@ -184,12 +232,13 @@ popup.open({
184
232
  maxWidth: number,
185
233
  maxHeight: number,
186
234
 
187
- // Remote content (same-origin only, enforced)
188
- source: string,
235
+ // Remote content (same-origin only, enforced). Rendered as raw HTML;
236
+ // sanitize server-side if the endpoint is not fully trusted.
237
+ source: string,
189
238
 
190
- // React adapter (via reattach)
191
- inheritContext: boolean,
192
- resilient: boolean,
239
+ // React adapter (via reattach)
240
+ inheritContext: boolean,
241
+ resilient: boolean,
193
242
 
194
243
  // Position (numbers are px; strings applied as-is, e.g. "10%")
195
244
  top: number | string,
@@ -210,8 +259,7 @@ popup.open({
210
259
  iconize: boolean, // allow minimize to dock
211
260
  dockable: boolean, // show an icon in the dock bar
212
261
  close: boolean, // show the close button
213
- raised: boolean, // alias: modal - full-screen overlay that blocks pointer events beneath
214
- modal: boolean, // alias of raised
262
+ modal: boolean, // full-screen overlay that blocks pointer events beneath
215
263
 
216
264
  // Buttons
217
265
  buttons: {
@@ -221,44 +269,60 @@ popup.open({
221
269
  }
222
270
  ```
223
271
 
224
- > **`raised` / `modal`** injects a full-viewport overlay (`popup_raised_<id>`) that intercepts every pointer event - the page underneath becomes unclickable until the popup closes. Both names are accepted.
272
+ > **`modal`** injects a full-viewport overlay (`popup_modal_<id>`) that intercepts every pointer event - the page underneath becomes unclickable until the popup closes. Use `modal: true` for alert/confirm/prompt/message pop ups.
225
273
 
226
274
  > **Duplicate id** - opening a popup whose `id` already exists brings that window to the front and refreshes its `body`/`buttons` instead of doing nothing.
227
275
 
228
- ## Global Overrides
276
+ ## Global Overrides (Opt-in in 4.0)
229
277
 
230
- Importing LayerPro replaces the native `alert`, `confirm`, and `prompt` globals with callback-based versions, and adds a `message` global:
278
+ In 4.0, `import "layerpro"` no longer overrides the native `alert`, `confirm`,
279
+ and `prompt` globals. The library exposes its own methods on `window.layerpro`
280
+ and provides Promise-based alternatives. To override the browser globals, use
281
+ the **explicit entrypoint**:
231
282
 
232
283
  ```javascript
284
+ // Default entry - library installed, globals NOT overridden
233
285
  import "layerpro";
234
286
 
235
- alert("Hello World!"); // LayerPro alert
236
- confirm("Sure?", yes => {...}); // LayerPro confirm (callback, not a boolean return!)
237
- prompt("Name?", name => {...}); // LayerPro prompt (callback, not a string return!)
238
- message("Done!"); // LayerPro message
287
+ layerpro.alert("Hello World!"); // Promise<void>
288
+ const confirmed = await layerpro.confirm("Sure?"); // Promise<boolean>
289
+ const name = await layerpro.prompt("Name?"); // Promise<string>
290
+ layerpro.message("Done!"); // Promise<void>
291
+ ```
292
+
293
+ ### Override browser globals (opt-in)
294
+
295
+ If you need `window.alert` / `window.confirm` / `window.prompt` / `window.message`
296
+ replaced with LayerPro's Promise-based versions, import the global entrypoint:
297
+
298
+ ```javascript
299
+ import "layerpro/global"; // installs Promise-based overrides on window
300
+
301
+ // These now return Promises instead of native values:
302
+ await alert("Hello World!"); // Promise<void>
303
+ const ok = await confirm("Sure?"); // Promise<boolean>
304
+ const val = await prompt("Name?"); // Promise<string>
239
305
  ```
240
306
 
241
- > **⚠️ Important:** the overridden `confirm()` no longer returns a `boolean`, and `prompt()` no longer returns a `string` - they are callback-based and return `undefined`. Any third-party code that does `if (confirm("..."))` will silently fall into the falsy branch. If your codebase depends on native synchronous return values, call **`layerpro.noGlobals()`** right after importing LayerPro to restore the originals:
242
- >
243
- > ```javascript
244
- > import "layerpro";
245
- > layerpro.noGlobals(); // restores native alert/confirm/prompt, drops `message`
246
- > ```
307
+ > **⚠️ Important:** overridden `confirm()` returns `Promise<boolean>`, not a
308
+ > synchronous `boolean`. Code that does `if (confirm("..."))` must be
309
+ > `await`-ed. Only use `layerpro/global` if you control all call sites.
247
310
 
248
311
  ## Methods
249
312
 
250
313
  | Method | Description |
251
314
  |--------|-------------|
252
- | `popup.open(props)` | Open a modal; returns the props. Re-opens a same-id window to front and refreshes its body. |
315
+ | `popup.open(props)` | Open a modal; returns `Promise<GenLayerProps>` - resolves when the popup closes. |
253
316
  | `popup.close(id)` | Close (and unmount) a modal. |
254
317
  | `popup.center(id, offset?)` | Center a modal in the viewport; optional pixel offset. |
255
318
  | `popup.maximize(props)` | Toggle maximize (pure CSS). |
256
319
  | `popup.iconize(id)` | Toggle minimize to the dock. |
257
320
  | `popup.index(id)` | Bring a modal to the front (select). |
258
- | `popup.zIndex()` | Get the next z-index counter value. |
259
321
  | `popup.movable(props)` | Enable dragging. |
260
322
  | `popup.resizable(props)` | Enable resizing. |
261
- | `layerpro.noGlobals()` | Restore native `alert`/`confirm`/`prompt`; remove `message`. |
323
+ | `layerpro.installGlobals()` | Override `window.alert/confirm/prompt/message` with LayerPro's Promise-based versions. |
324
+ | `layerpro.version()` | Returns the current LayerPro version string (read from `app.json`). |
325
+ | `layerpro.config(opts)` | Runtime config. Currently supports `{ nonce: string }` for CSP; sets `window.__layerproNonce` consumed by the injected `<style>`. Call before importing LayerPro for the nonce to be present at injection. |
262
326
 
263
327
  ## Styling & z-index
264
328
 
@@ -280,25 +344,196 @@ The title bar, window surface, internal area, and body all read from CSS custom
280
344
  | `--window-border-radius` | popup border radius |
281
345
  | `--internal-background` | body wrapper |
282
346
  | `--body-color` | body text color |
283
- | `--raised-background` | modal overlay |
347
+ | `--modal-background` | modal overlay |
284
348
  | `--popup-z-index` | base z-index for the stack |
285
349
 
286
- ## Security
350
+ Additional variables available for buttons, inputs, resize handles, and the dock:
287
351
 
288
- - **Same-origin fetch guard** - the `source` prop only accepts same-origin URLs; cross-origin requests are rejected before any network call is made, preventing SSRF and data exfiltration.
289
- - **HTML escaping** - all user-provided text content (button labels, body content, titles) is automatically escaped via `escapeHtml()` to prevent XSS.
290
- - **CSP-friendly** - no inline scripts or `eval()` are used. React components are rendered via `reattach.mount()` (backed by `createRoot()`).
291
- - **Opt-out of global overrides** - call `layerpro.noGlobals()` after import to restore native `alert`, `confirm`, `prompt`, and remove the `message` global if your codebase depends on synchronous return values.
352
+ | Variable | Applies to |
353
+ |----------|-----------|
354
+ | `--button-background-color` / `--button-color` | confirm/cancel buttons |
355
+ | `--input-background-color` / `--input-color` | prompt input field |
356
+ | `--inputButton-fontSize` | button/input font size |
357
+ | `--resize-background-color` / `--resize-shadow` | resize handles |
358
+ | `--dock-background-color` / `--dock-height` | taskbar strip |
359
+ | `--dock-sideLeft` / `--dock-sideCenter` / `--dock-sideRight` | dock alignment |
360
+ | `--dock-icon-width` | dock icon width |
361
+ | `--transition-time` | fade/hover transitions |
362
+ | `--border-radius` / `--corner-size` / `--corner-border-radius` | resize handle shape |
363
+ | `--hover-opacity` / `--hover-filter` | hover feedback |
364
+
365
+ ## Overriding the CSS
366
+
367
+ LayerPro ships its styles **injected at runtime** into a `<style>` tag (no separate CSS import needed). There are three ways to restyle it, from the most recommended to the most surgical.
368
+
369
+ > **No inline styles.** LayerPro never writes a plain inline style on the elements it renders. Every declaration lives in the stylesheet and is driven by a **class** (the state) or by a **CSS custom property** (the value). That means you can restyle any part of a window with a plain CSS rule - nothing needs `!important` to win over an inline style.
370
+
371
+ ### 1. CSS custom properties (recommended)
372
+
373
+ Every visual detail is driven by a CSS variable declared on `:root`. Override the variable and every popup updates - no selectors, no `!important`:
374
+
375
+ ```css
376
+ :root {
377
+ --bar-background: #1f2937;
378
+ --bar-selected-background: #2563eb;
379
+ --bar-color: #f9fafb;
380
+ --window-background: #111827;
381
+ --window-border-radius: 4px;
382
+ --internal-background: #0b1220;
383
+ --body-color: #e5e7eb;
384
+ --button-background-color: #2563eb;
385
+ --button-color: #ffffff;
386
+ }
387
+ ```
388
+
389
+ Variables can also be scoped per popup subtree, because each window is rendered inside `#popup`:
292
390
 
293
- ## Browser Support
391
+ ```css
392
+ #popup .popup_window_myDarkPopup {
393
+ --window-background: #000;
394
+ --bar-background: #000;
395
+ }
396
+ ```
397
+
398
+ ### 2. Target the generated classes
399
+
400
+ Every element LayerPro creates carries a **stable class alias** next to its id-suffixed class, so you can target windows, bars, bodies, and buttons without depending on generated ids:
401
+
402
+ | Element | Stable class | Id-suffixed class |
403
+ |---------|--------------|-------------------|
404
+ | Window surface | `.popup_window` | `.popup_window_<id>` |
405
+ | Title bar | `.popup_bar` | `.popup_bar_<id>` |
406
+ | Drag handle (title) | `.popup_drag` | `.popup_drag_<id>` |
407
+ | Icon slot | `.popup_icon` | `.popup_icon_<id>` |
408
+ | Option buttons | `.popup_option` | `.popup_option_<id>` |
409
+ | Internal area | `.popup_internal` | `.popup_internal_<id>` |
410
+ | Body | `.popup_body` | `.popup_body_<id>` |
411
+ | Modal overlay | `.popup_modal` | `.popup_modal_<id>` |
412
+ | Positioned wrapper | `.popup_overflow` | `.popup_overflow_<id>` |
413
+ | Confirm/cancel row | `.buttons` | - |
414
+ | Selected/focused window | `.zHigh`, `.popup_bar_selected` | - |
415
+
416
+ ```css
417
+ /* square windows, no rounded corners */
418
+ #popup .popup_window {
419
+ border-radius: 0;
420
+ }
421
+
422
+ /* restyle the confirm/cancel row */
423
+ #popup .buttons button {
424
+ border-radius: 6px;
425
+ background: #2563eb;
426
+ }
427
+
428
+ /* make the close button red on hover */
429
+ #popup .popup_option .close:hover {
430
+ background: #dc2626;
431
+ }
432
+
433
+ /* change the dock strip */
434
+ #popup_dock {
435
+ background: #111827;
436
+ }
437
+ ```
294
438
 
295
- Chrome 80+ • Firefox 75+ • Safari 13+ • Edge 80+
439
+ > **Note:** some internal rules use `!important` (for example the active-window border and the built-in icons). For those, use the cascade layer approach below instead of `!important`.
440
+
441
+ ### State classes
442
+
443
+ The visual **states** of a window are classes too, so you can restyle them like any other element:
444
+
445
+ | Class | Applied when | Effect |
446
+ |-------|--------------|--------|
447
+ | `.popup_maximized` | the window is maximized | fills the viewport above the dock |
448
+ | `.popup_iconized` | the window is minimized to the dock | collapsed on the taskbar |
449
+ | `.popup_dimmed` | another window has the focus | pushed to the back (opacity `.5`) |
450
+ | `.popup_content_hidden` | another window has the focus | body content not rendered |
451
+ | `.popup_content_visible` | the window has the focus | body content always rendered |
452
+ | `.lp_hidden` | the element is hidden | `display: none` |
453
+ | `.lp_fade_in` / `.lp_fade_out` | during a fade | opacity transition |
454
+ | `.lp_no_pointer` | while dragging/resizing | `pointer-events: none` |
455
+ | `.lp_no_touch` | on the drag/resize handles | `touch-action: none` |
456
+ | `.lp_no_transition` | while resizing | transitions disabled |
457
+
458
+ ```css
459
+ /* a maximized window with a different look */
460
+ #popup .popup_maximized {
461
+ inset: 0;
462
+ bottom: 0;
463
+ }
464
+
465
+ /* a dimmed window that stays readable */
466
+ #popup .popup_dimmed {
467
+ opacity: .8;
468
+ }
469
+ ```
470
+
471
+ The geometry of a window (position and size) is carried by the custom properties below, so it can be overridden too:
472
+
473
+ | Property | Value |
474
+ |----------|-------|
475
+ | `--lp-top` / `--lp-left` | window position |
476
+ | `--lp-right` / `--lp-bottom` | window position (alternative anchors) |
477
+ | `--lp-width` / `--lp-height` | window size |
478
+ | `--lp-min-width` / `--lp-min-height` | minimum size |
479
+ | `--lp-max-width` / `--lp-max-height` | maximum size |
480
+ | `--lp-z-index` | stacking order |
481
+
482
+ ```css
483
+ /* every window opens at a fixed position */
484
+ #popup .popup_overflow {
485
+ --lp-left: 50%;
486
+ --lp-top: 100px;
487
+ }
488
+ ```
296
489
 
297
- ## Contributing
490
+ ### 3. Cascade layers (full control)
298
491
 
299
- LayerPro's source is currently private, but contributions and discussion are welcome. Get in touch:
492
+ All LayerPro styles live inside the CSS cascade layer `layerpro`, so a host app can restyle the popups without `!important` or fragile selectors.
300
493
 
301
- - **Dario Passariello** - https://dario.passariello.ca/ - dariopassariello@gmail.com
494
+ ```css
495
+ /* host app: plain rules beat every layer */
496
+ #popup { border-radius: 4px; }
497
+ ```
498
+
499
+ Precedence rules:
500
+
501
+ - **Unlayered host rules always win** over anything inside `layerpro`.
502
+ - **Host layers must be declared before `layerpro`** to outrank it. Declare the
503
+ full order in the host stylesheet, otherwise the host layer is created after
504
+ LayerPro's and loses:
505
+
506
+ ```css
507
+ @layer app, layerpro;
508
+ ```
509
+
510
+ - **`!important` inverts layer order.** A host `!important` rule is *weaker*
511
+ than LayerPro's `!important` rules, because LayerPro's live inside a layer
512
+ and host `!important` rules are unlayered. Do not rely on `!important` to
513
+ override LayerPro - use plain rules or a host layer declared before
514
+ `layerpro`.
515
+
516
+ ## Dock
517
+
518
+ Popups that can be minimized share a dock, a taskbar-like strip pinned to the bottom of the viewport. The dock is mounted directly in `<body>` and it stays visible while the page scrolls.
519
+
520
+ While the dock is mounted LayerPro puts the `layerpro-dock` class on `<html>` and `<body>`:
521
+
522
+ ```css
523
+ html.layerpro-dock { height: 100%; overflow: hidden; }
524
+ body.layerpro-dock { position: absolute; top: 0; right: 0; bottom: var(--dock-height); left: 0; overflow-y: auto; }
525
+ ```
526
+
527
+ The body is lifted from the bottom, so the strip used by the dock stays free and the page content is never covered by it. The class is removed as soon as the last popup is closed, so the host page is left exactly as it was. No inline style is written on the host page.
528
+
529
+ > **Note:** while the dock is open the body is the scrolling element, so `window.scrollY` stays `0` and page level scroll listeners do not fire. Host the scrolling in an inner container if the page needs it.
530
+
531
+ ## Security
532
+
533
+ - **Same-origin fetch guard** - the `source` prop only accepts same-origin URLs; cross-origin requests are rejected before any network call is made, preventing SSRF and data exfiltration.
534
+ - **HTML escaping** - all user-provided text content (button labels, body content, titles) is automatically escaped via `escapeHtml()` to prevent XSS.
535
+ - **CSP-friendly** - no inline scripts or `eval()` are used. React components are rendered via `reattach.mount()` (backed by `createRoot()`). For strict CSP environments, the injected stylesheet auto-detects the nonce from `window.__layerproNonce`, existing `<style>`/`<script>` elements, or `document.body.dataset.nonce`. Alternatively, import the stylesheet as a static file via `import 'layerpro/style.css'` to avoid inline style injection entirely. Use `layerpro.config({ nonce })` for explicit nonce control.
536
+ - **No global side effects** - `import 'layerpro'` does NOT override `window.alert`/`confirm`/`prompt`. Use `import 'layerpro/global'` or `layerpro.installGlobals()` to opt in to the global overrides.
302
537
 
303
538
  ## License
304
539