layerpro 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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,20 @@ 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)
38
+ - [Targeting a window](#targeting-a-window)
33
39
  - [Options](#options)
34
40
  - [Global Overrides](#global-overrides)
35
41
  - [Methods](#methods)
36
42
  - [Styling & z-index](#styling--z-index)
43
+ - [Overriding the CSS](#overriding-the-css)
44
+ - [CSS custom properties](#1-css-custom-properties-recommended)
45
+ - [Target the generated classes](#2-target-the-generated-classes)
46
+ - [Cascade layers](#3-cascade-layers-full-control)
47
+ - [Dock](#dock)
37
48
  - [Security](#security)
38
- - [Browser Support](#browser-support)
39
- - [Contributing](#contributing)
40
49
  - [License](#license)
41
50
 
42
51
  ---
@@ -44,7 +53,10 @@ LayerPro replaces the browser's native `alert()`, `confirm()`, and `prompt()` wi
44
53
  ## Features
45
54
 
46
55
  - **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
56
+ - **Native `<dialog>` windows** - every popup is a real `<dialog>` element, so the platform owns the window semantics (open state, implicit dialog role) while LayerPro keeps the geometry, the drag and the stacking order
57
+ - **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
58
+ - **Hosts any content** - plain text, HTML strings, or **full React components** (with Context bridging and DOM auto-healing), rendered inside the window body
59
+ - **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
60
  - **Fully customizable** - appearance, behavior, and content
49
61
  - **Dockable windows** (taskbar-style) - click a dock icon to restore, focus, or minimize, just like an OS taskbar
50
62
  - **Draggable & resizable** - instant, with no transition lag while resizing
@@ -72,7 +84,7 @@ Import LayerPro once, in your app's entry point:
72
84
  import "layerpro";
73
85
  ```
74
86
 
75
- That's it - `alert()`, `confirm()`, `prompt()`, `message()`, and `popup()` are now available globally.
87
+ 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
88
 
77
89
  The `popup` alias is a shorthand for `layerpro.popup`:
78
90
 
@@ -89,19 +101,15 @@ popup.open({ id: 'myPopup', body: 'Hello World' });
89
101
  ### Basic Modals
90
102
 
91
103
  ```javascript
92
- alert("Hello World!");
93
-
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
- });
104
+ layerpro.alert("Hello World!"); // Promise<void>
105
+ layerpro.message("Operation complete!"); // Promise<void>
101
106
 
102
- message("Operation complete!");
107
+ const confirmed = await layerpro.confirm("Are you sure?"); // Promise<boolean>
108
+ const name = await layerpro.prompt("What's your name?"); // Promise<string>
103
109
  ```
104
110
 
111
+ > **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.
112
+
105
113
  ### Custom Popups
106
114
 
107
115
  ```javascript
@@ -118,6 +126,48 @@ popup.open({
118
126
  });
119
127
  ```
120
128
 
129
+ ### OS-style Windows
130
+
131
+ 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.
132
+
133
+ ```javascript
134
+ popup.open({
135
+ id: 'dashboard',
136
+ name: 'Dashboard',
137
+ icon: '&#128202;',
138
+ body: '<h2>Live metrics</h2><p>Anything you want inside.</p>',
139
+ width: 640,
140
+ height: 420,
141
+ movable: true,
142
+ resizable: true,
143
+ maximize: true,
144
+ iconize: true,
145
+ dockable: true,
146
+ close: true
147
+ });
148
+ ```
149
+
150
+ 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:
151
+
152
+ ```jsx
153
+ popup.open({
154
+ id: 'settingsWindow',
155
+ name: 'Settings',
156
+ icon: '&#9881;',
157
+ body: <SettingsPanel />,
158
+ width: 720,
159
+ height: 480,
160
+ movable: true,
161
+ resizable: true,
162
+ maximize: true,
163
+ iconize: true,
164
+ dockable: true,
165
+ close: true
166
+ });
167
+ ```
168
+
169
+ > **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.
170
+
121
171
  ### React Components
122
172
 
123
173
  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.
@@ -167,6 +217,33 @@ popup.open({
167
217
 
168
218
  > **Note**: in React, always use elements (`<Component />`), not function calls (`Component()`).
169
219
 
220
+ ### Targeting a window
221
+
222
+ A layer is drawn in the document of the page that loaded LayerPro. The
223
+ `target` option names the window a layer belongs to, so a window opened with
224
+ `native: true` can host its own layers instead of seeing them land on the
225
+ opener:
226
+
227
+ ```javascript
228
+ // Open a real browser window
229
+ await layerpro.popup.open({ id: 'desk', name: 'Desktop', native: true })
230
+
231
+ // Open a layer inside that window
232
+ await layerpro.popup.open({ id: 'detail', body: 'Inside the desk', target: 'desk' })
233
+
234
+ // The same thing, through the handle bound to that window
235
+ const desk = layerpro.window('desk')
236
+ await desk.popup.open({ id: 'detail2', body: 'Also inside' })
237
+
238
+ // The modals accept a target too
239
+ await layerpro.confirm('Save the changes?', { target: 'desk' })
240
+ ```
241
+
242
+ A target is the id of a native window, a `Window` object, or a
243
+ `{ win, doc }` scope. A target that cannot be reached (unknown id, closed
244
+ window, another origin) produces a warning and the layer is drawn in the
245
+ current document.
246
+
170
247
  ## Options
171
248
 
172
249
  ```typescript
@@ -218,12 +295,17 @@ popup.open({
218
295
  confirm?: { text: string; cb?: () => void },
219
296
  cancel?: { text: string; cb?: () => void }
220
297
  }
298
+
299
+ // The window the layer is drawn in: the id of a native window, a Window
300
+ // object, or a { win, doc } scope. Without it the layer is drawn in the
301
+ // document of the page that loaded LayerPro.
302
+ target?: string | number | Window | LayerScope
221
303
  }
222
304
  ```
223
305
 
224
306
  > **`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
307
 
226
- > **Duplicate id** - opening a popup whose `id` already exists brings that window to the front and refreshes its `body`/`buttons` instead of doing nothing.
308
+ > **Duplicate id** - opening a popup whose `id` already exists brings that window to the front and refreshes its `body`/`buttons` instead of doing nothing. The refresh reuses the existing button row: the row is emptied and refilled, never stacked.
227
309
 
228
310
  ## Global Overrides (Opt-in in 4.0)
229
311
 
@@ -273,6 +355,8 @@ const val = await prompt("Name?"); // Promise<string>
273
355
  | `popup.movable(props)` | Enable dragging. |
274
356
  | `popup.resizable(props)` | Enable resizing. |
275
357
  | `layerpro.installGlobals()` | Override `window.alert/confirm/prompt/message` with LayerPro's Promise-based versions. |
358
+ | `layerpro.window(target)` | A handle bound to one window: `win`, `doc` and a `popup` API whose layers are drawn in that window. |
359
+ | `layerpro.version()` | Returns the current LayerPro version string (read from `app.json`). |
276
360
  | `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. |
277
361
 
278
362
  ## Styling & z-index
@@ -298,6 +382,196 @@ The title bar, window surface, internal area, and body all read from CSS custom
298
382
  | `--modal-background` | modal overlay |
299
383
  | `--popup-z-index` | base z-index for the stack |
300
384
 
385
+ Additional variables available for buttons, inputs, resize handles, and the dock:
386
+
387
+ | Variable | Applies to |
388
+ |----------|-----------|
389
+ | `--button-background-color` / `--button-color` | confirm/cancel buttons |
390
+ | `--input-background-color` / `--input-color` | prompt input field |
391
+ | `--inputButton-fontSize` | button/input font size |
392
+ | `--resize-background-color` / `--resize-shadow` | resize handles |
393
+ | `--dock-background-color` / `--dock-height` | taskbar strip |
394
+ | `--dock-sideLeft` / `--dock-sideCenter` / `--dock-sideRight` | dock alignment |
395
+ | `--dock-icon-width` | dock icon width |
396
+ | `--transition-time` | fade/hover transitions |
397
+ | `--border-radius` / `--corner-size` / `--corner-border-radius` | resize handle shape |
398
+ | `--hover-opacity` / `--hover-filter` | hover feedback |
399
+
400
+ ## Overriding the CSS
401
+
402
+ 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.
403
+
404
+ > **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.
405
+
406
+ ### 1. CSS custom properties (recommended)
407
+
408
+ Every visual detail is driven by a CSS variable declared on `:root`. Override the variable and every popup updates - no selectors, no `!important`:
409
+
410
+ ```css
411
+ :root {
412
+ --bar-background: #1f2937;
413
+ --bar-selected-background: #2563eb;
414
+ --bar-color: #f9fafb;
415
+ --window-background: #111827;
416
+ --window-border-radius: 4px;
417
+ --internal-background: #0b1220;
418
+ --body-color: #e5e7eb;
419
+ --button-background-color: #2563eb;
420
+ --button-color: #ffffff;
421
+ }
422
+ ```
423
+
424
+ Variables can also be scoped per popup subtree, because each window is rendered inside `#popup`:
425
+
426
+ ```css
427
+ #popup .popup_window_myDarkPopup {
428
+ --window-background: #000;
429
+ --bar-background: #000;
430
+ }
431
+ ```
432
+
433
+ ### 2. Target the generated classes
434
+
435
+ 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:
436
+
437
+ | Element | Stable class | Id-suffixed class |
438
+ |---------|--------------|-------------------|
439
+ | Window surface (`<dialog>`) | `.popup_window` | `.popup_window_<id>` |
440
+ | Title bar | `.popup_bar` | `.popup_bar_<id>` |
441
+ | Drag handle (title) | `.popup_drag` | `.popup_drag_<id>` |
442
+ | Icon slot | `.popup_icon` | `.popup_icon_<id>` |
443
+ | Option buttons | `.popup_option` | `.popup_option_<id>` |
444
+ | Internal area | `.popup_internal` | `.popup_internal_<id>` |
445
+ | Body | `.popup_body` | `.popup_body_<id>` |
446
+ | Modal overlay | `.popup_modal` | `.popup_modal_<id>` |
447
+ | Positioned wrapper | `.popup_overflow` | `.popup_overflow_<id>` |
448
+ | Confirm/cancel row | `.buttons` | - |
449
+ | Selected/focused window | `.zHigh`, `.popup_bar_selected` | - |
450
+
451
+ The confirm/cancel row built from the `buttons` option lives in the **internal
452
+ area** (`.popup_internal`), next to the body, and not inside the body itself:
453
+ a body renderer (React) owns the body container and empties it when it
454
+ commits, so a row kept inside the body would be wiped by the first render.
455
+ The built-in `alert`/`confirm`/`prompt`/`message` modals carry their own row
456
+ inside their body markup, as part of the modal content. Target the row with
457
+ `.buttons` (or `.lp_confirm` / `.lp_cancel` for the single buttons), never
458
+ with `.popup_body .buttons`.
459
+
460
+ ```css
461
+ /* square windows, no rounded corners */
462
+ #popup .popup_window {
463
+ border-radius: 0;
464
+ }
465
+
466
+ /* restyle the confirm/cancel row */
467
+ #popup .buttons button {
468
+ border-radius: 6px;
469
+ background: #2563eb;
470
+ }
471
+
472
+ /* make the close button red on hover */
473
+ #popup .popup_option .close:hover {
474
+ background: #dc2626;
475
+ }
476
+
477
+ /* change the dock strip */
478
+ #popup_dock {
479
+ background: #111827;
480
+ }
481
+ ```
482
+
483
+ > **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`.
484
+
485
+ ### State classes
486
+
487
+ The visual **states** of a window are classes too, so you can restyle them like any other element:
488
+
489
+ | Class | Applied when | Effect |
490
+ |-------|--------------|--------|
491
+ | `.popup_maximized` | the window is maximized | fills the viewport above the dock |
492
+ | `.popup_iconized` | the window is minimized to the dock | collapsed on the taskbar |
493
+ | `.popup_dimmed` | another window has the focus | pushed to the back (opacity `.5`) |
494
+ | `.popup_content_hidden` | another window has the focus | body content not rendered |
495
+ | `.popup_content_visible` | the window has the focus | body content always rendered |
496
+ | `.lp_hidden` | the element is hidden | `display: none` |
497
+ | `.lp_fade_in` / `.lp_fade_out` | during a fade | opacity transition |
498
+ | `.lp_no_pointer` | while dragging/resizing | `pointer-events: none` |
499
+ | `.lp_no_touch` | on the drag/resize handles | `touch-action: none` |
500
+ | `.lp_no_transition` | while resizing | transitions disabled |
501
+
502
+ ```css
503
+ /* a maximized window with a different look */
504
+ #popup .popup_maximized {
505
+ inset: 0;
506
+ bottom: 0;
507
+ }
508
+
509
+ /* a dimmed window that stays readable */
510
+ #popup .popup_dimmed {
511
+ opacity: .8;
512
+ }
513
+ ```
514
+
515
+ The geometry of a window (position and size) is carried by the custom properties below, so it can be overridden too:
516
+
517
+ | Property | Value |
518
+ |----------|-------|
519
+ | `--lp-top` / `--lp-left` | window position |
520
+ | `--lp-right` / `--lp-bottom` | window position (alternative anchors) |
521
+ | `--lp-width` / `--lp-height` | window size |
522
+ | `--lp-min-width` / `--lp-min-height` | minimum size |
523
+ | `--lp-max-width` / `--lp-max-height` | maximum size |
524
+ | `--lp-z-index` | stacking order |
525
+
526
+ ```css
527
+ /* every window opens at a fixed position */
528
+ #popup .popup_overflow {
529
+ --lp-left: 50%;
530
+ --lp-top: 100px;
531
+ }
532
+ ```
533
+
534
+ ### 3. Cascade layers (full control)
535
+
536
+ All LayerPro styles live inside the CSS cascade layer `layerpro`, so a host app can restyle the popups without `!important` or fragile selectors.
537
+
538
+ ```css
539
+ /* host app: plain rules beat every layer */
540
+ #popup { border-radius: 4px; }
541
+ ```
542
+
543
+ Precedence rules:
544
+
545
+ - **Unlayered host rules always win** over anything inside `layerpro`.
546
+ - **Host layers must be declared before `layerpro`** to outrank it. Declare the
547
+ full order in the host stylesheet, otherwise the host layer is created after
548
+ LayerPro's and loses:
549
+
550
+ ```css
551
+ @layer app, layerpro;
552
+ ```
553
+
554
+ - **`!important` inverts layer order.** A host `!important` rule is *weaker*
555
+ than LayerPro's `!important` rules, because LayerPro's live inside a layer
556
+ and host `!important` rules are unlayered. Do not rely on `!important` to
557
+ override LayerPro - use plain rules or a host layer declared before
558
+ `layerpro`.
559
+
560
+ ## Dock
561
+
562
+ 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.
563
+
564
+ While the dock is mounted LayerPro puts the `layerpro-dock` class on `<html>` and `<body>`:
565
+
566
+ ```css
567
+ html.layerpro-dock { height: 100%; overflow: hidden; }
568
+ body.layerpro-dock { position: absolute; top: 0; right: 0; bottom: var(--dock-height); left: 0; overflow-y: auto; }
569
+ ```
570
+
571
+ 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.
572
+
573
+ > **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.
574
+
301
575
  ## Security
302
576
 
303
577
  - **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.