react-dockable-desktop 1.2.0 β†’ 1.3.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
@@ -1,365 +1,385 @@
1
- # React Dockable Desktop
2
-
3
- [![npm version](https://img.shields.io/badge/npm-v1.0.0-blue.svg)](#)
4
- [![license](https://img.shields.io/badge/license-MIT-green.svg)](#)
5
- [![Demo](https://img.shields.io/badge/demo-live-brightgreen.svg)](https://felipecarrillo100.github.io/react-dockable-desktop/)
6
-
7
- A beautiful, premium, state-of-the-art React window manager and dockable layout engine. It features fluid split-docking grids, resizable floating windows, dynamic taskbars, and tabbed panels with **zero-unmount DOM persistence** and **built-in internationalization (i18n) support**.
8
-
9
- [**Live Interactive Demo πŸš€**](https://felipecarrillo100.github.io/react-dockable-desktop/)
10
-
11
- ---
12
-
13
- ## 🌟 Key Features
14
-
15
- * **πŸ“¦ Dockable Splits & Tab Grid**: Drag-and-drop panels to split screens or group them together into tabbed containers.
16
- * **πŸ—ΊοΈ Workspace Edge Docking**: Drag a tab or floating window to the outer edges of the screen (left, right, top, bottom) to instantly dock it as a full-width or full-height column/row.
17
- * **πŸͺ Floating Windows**: Seamlessly pop panels out into floating windows with smooth drag-and-drop movement, custom resizing, maximize, and minimize behaviors.
18
- * **🎨 Custom Global Style Classes**: Inject custom, global styles at the provider level (`modalClass`, `modalBodyClass`, `sidePanelClass`, `sidePanelBodyClass`, `windowClass`, and `windowBodyClass`) to style widgets uniformly.
19
- * **πŸ₯ž Aspect-Ratio Modal Engine**: Modals automatically wrap and shrink-to-fit content snugly, but cap height and scroll gracefully at visual aspect-ratio thresholds (4:3 for small/medium, 16:10 for large layouts).
20
- * **πŸ”¬ Zero-Unmount DOM Persistence**: Heavy widgets (like Leaflet Maps, WebGL viewports, terminal consoles, or stateful forms) maintain their DOM nodes and state. The engine moves existing DOM structures into a hidden document fragment host rather than unmounting them.
21
- * **🌍 i18n & Full RTL Support**: Native translation pipeline matching React Intl formats and **Right-to-Left (RTL) language layouts** (Arabic, Hebrew, Persian, etc.). Detection is completely automatic based on surrounding `dir="rtl"` elements, with explicit provider control options.
22
- * **πŸ› οΈ Custom Header Actions**: Inject custom React components directly into tabbed headers and floating window titlebars dynamically using the `renderHeaderActions` registry property.
23
- * **πŸ” Initial Letter Hover Miniatures**: Miniature preview popovers automatically display the uppercase first character of the panel title inside a theme-responsive grayish layout fallback for panels with disabled live previews.
24
- * **πŸ’Ž Glassmorphic & Modern Styling**: Sleek dark mode aesthetics, interactive micro-animations, and fluid transitions.
25
- * **πŸ”Œ Inter-Panel Pub/Sub Event Bus**: A robust messaging system for lightweight, decoupled communication between active panels.
26
-
27
- ---
28
-
29
- ## πŸš€ Installation
30
-
31
- ```bash
32
- npm install react-dockable-desktop replace-react-contexify
33
- ```
34
-
35
- Ensure the styling for both the layout engine and context menu is imported in your main entry file (e.g., `index.js` or `main.tsx`):
36
-
37
- ```typescript
38
- import 'replace-react-contexify/styles.css';
39
- import 'react-dockable-desktop/styles.css';
40
- ```
41
-
42
- ---
43
-
44
- ## πŸ’» Running the Demo Environments
45
-
46
- The project includes two built-in demo setups to explore and test the window manager layout features:
47
-
48
- ### 1. Leaflet & Monaco Open Source Demo (Default)
49
- A clean, lightweight dashboard demo suitable for public deployment. It showcases Leaflet 2D/3D map integration (with dynamic CARTO Light/Dark tile styles mapping to dashboard theme switches) and Monaco Editor panels.
50
- ```bash
51
- npm run dev
52
- ```
53
-
54
- ### 2. LuciadRIA Earth 3D Demo (Isolated)
55
- An isolated sandbox dashboard demonstrating premium 3D Earth visualizations in the `EPSG:4978` reference reference frame using LuciadRIA. *Note: Requires a valid LuciadRIA developer license locally to run.*
56
- ```bash
57
- npm run dev:ria
58
- ```
59
-
60
- ---
61
-
62
- ## πŸ› οΈ Getting Started
63
-
64
- ### 1. Define and Register Custom Panels
65
-
66
- Register your components with `PanelRegistry`. This exposes them to the window manager layout engine for spawning and custom configuration.
67
-
68
- ```typescript
69
- import React from 'react';
70
- import { PanelRegistry } from 'react-dockable-desktop';
71
-
72
- // Example Panel Component
73
- const MapView: React.FC = () => {
74
- return (
75
- <div style={{ width: '100%', height: '100%', padding: '1rem', color: '#fff' }}>
76
- <h3>Interactive Map</h3>
77
- <p>This DOM is preserved across tabs, floats, and minimizations!</p>
78
- </div>
79
- );
80
- };
81
-
82
- // Register
83
- PanelRegistry.register('mainMap', MapView, {
84
- title: { id: 'app.mapTitle', defaultMessage: 'Satellite Map View' },
85
- canClose: false, // Permanent panel
86
- canMinimize: true,
87
- canDrag: true,
88
- favoritePosition: { x: 100, y: 120, width: 600, height: 400 }
89
- });
90
- ```
91
-
92
- ### 2. Set Up the WindowManager Context Provider
93
-
94
- Wrap your workspace inside `WindowManagerProvider` and render `WindowManager`. If you need modals or side panels, also add `ModalStackRenderer` and/or `SidePanelRenderer` inside the same provider tree.
95
-
96
- ```typescript
97
- import React from 'react';
98
- import {
99
- WindowManagerProvider,
100
- WindowManager,
101
- PanelProvider,
102
- ModalStackRenderer,
103
- SidePanelRenderer,
104
- } from 'react-dockable-desktop';
105
-
106
- function App() {
107
- return (
108
- <WindowManagerProvider>
109
- <PanelProvider>
110
- <div style={{ width: '100vw', height: '100vh', display: 'flex', flexDirection: 'column' }}>
111
- <WindowManager />
112
- </div>
113
- <ModalStackRenderer />
114
- <SidePanelRenderer />
115
- </PanelProvider>
116
- </WindowManagerProvider>
117
- );
118
- }
119
-
120
- export default App;
121
- ```
122
-
123
- ---
124
-
125
- ## 🌍 Internationalization (i18n)
126
-
127
- `react-dockable-desktop` is built with dynamic translation in mind. Titles and context menus can accept either raw `string` values or a structured descriptor object resembling `ContextMenuPredefinedMessage`.
128
-
129
- ### Message Descriptor Format
130
-
131
- ```typescript
132
- interface ContextMenuPredefinedMessage {
133
- id: string;
134
- defaultMessage?: string;
135
- values?: Record<string, string | number>;
136
- }
137
- ```
138
-
139
- ### Integrating custom formatters (e.g., `react-intl`)
140
-
141
- To route messages through your application's translation engine, pass a `formatMessage` callback to the `WindowManagerProvider`.
142
-
143
- ```typescript
144
- import React from 'react';
145
- import { useIntl } from 'react-intl';
146
- import { WindowManagerProvider, Desktop } from 'react-dockable-desktop';
147
-
148
- function App() {
149
- const intl = useIntl();
150
-
151
- // Map descriptor payload directly to react-intl formatter
152
- const handleFormatMessage = (msg: { id: string; defaultMessage?: string; values?: any }) => {
153
- return intl.formatMessage({ id: msg.id, defaultMessage: msg.defaultMessage }, msg.values);
154
- };
155
-
156
- return (
157
- <WindowManagerProvider formatMessage={handleFormatMessage}>
158
- <div style={{ width: '100vw', height: '100vh' }}>
159
- <Desktop />
160
- </div>
161
- </WindowManagerProvider>
162
- );
163
- }
164
- ```
165
-
166
- *If no `formatMessage` function is provided, the engine defaults to a fallback formatting template parser resolving placeholders like `Hello {user}` using values.*
167
-
168
- ### Right-to-Left (RTL) Layout Support
169
-
170
- The library features built-in, theme-resilient Right-to-Left (RTL) rendering. It automatically mirrors tab stacking flows, close icon alignments, custom header buttons, side drawers (which slide in from the opposite edge), titlebars, and taskbar icons.
171
-
172
- #### 1. Automatic Detection (Zero Configuration)
173
- Simply apply the HTML `dir="rtl"` attribute to the container enclosing your layout, or globally on the `<html>`/`<body>` nodes. The workspace will observe it using a `MutationObserver` and mirror dynamically:
174
- ```html
175
- <div dir="rtl">
176
- <Desktop />
177
- </div>
178
- ```
179
-
180
- #### 2. Explicit Programmatic Control
181
- To force a specific direction regardless of the surrounding HTML structure, pass the `dir` prop directly to the provider:
182
- ```typescript
183
- <WindowManagerProvider dir="rtl">
184
- <Desktop />
185
- </WindowManagerProvider>
186
- ```
187
-
188
- ---
189
-
190
- ## πŸŽ›οΈ Programmatic Spawning and Layout API
191
-
192
- Consume actions from the layout context anywhere inside or outside your panel components using the provided hooks.
193
-
194
- ```typescript
195
- import { useWindowManagerActions, useWindowManagerState } from 'react-dockable-desktop';
196
-
197
- const SidebarControls = () => {
198
- const { openPanel, closePanel, saveLayout, loadLayout } = useWindowManagerActions();
199
- const state = useWindowManagerState();
200
-
201
- const handleOpenConsole = () => {
202
- openPanel('debug-console', 'terminal', {
203
- title: { id: 'app.console', defaultMessage: 'System Console Log' },
204
- initialTarget: 'floating' // Options: 'docked' | 'floating' | 'tabbed'
205
- });
206
- };
207
-
208
- return (
209
- <div>
210
- <button onClick={handleOpenConsole}>Spawn Console</button>
211
- <button onClick={() => alert(saveLayout())}>Backup Layout</button>
212
- </div>
213
- );
214
- };
215
- ```
216
-
217
- ### Hook Reference
218
-
219
- | Hook Name | Return Type | Description |
220
- | :--- | :--- | :--- |
221
- | `useWindowManagerState()` | `WindowState` | Access grid layout trees, list of active floating windows, minimized windows list, and general dragging statuses. |
222
- | `useWindowManagerActions()` | `WindowActions` | Spawns, minimizes, restores, docks, floats, maximizes, or closes panels. Also handles split sizing, custom locations, and layout serialization (`saveLayout` / `loadLayout`). |
223
- | `useFormatMessage()` | `(msg: ContextMenuPredefinedMessage) => string` | Returns the translation message formatter hook matching the provider preset configuration. |
224
- | `usePanelContext()` | `{ publish, subscribe }` | Dynamic decoupled event bus helper for active panels. |
225
- | `useStyleClasses()` | `StyleClasses` | Returns the custom CSS class overrides configured on the provider (`windowClass`, `windowBodyClass`, etc.). |
226
-
227
- ---
228
-
229
- ## πŸ›‘οΈ Form Container Context (Close Interception & Dirty States)
230
-
231
- `react-dockable-desktop` provides a context-driven panel container contract to support dirty form tracking, dynamic title overrides, and close action guards. Child elements can access this container context using the `useFormContainer()` hook.
232
-
233
- ### Context Hook Functions
234
-
235
- | Function / Property | Type | Description |
236
- | :--- | :--- | :--- |
237
- | `setDirty(dirty)` | `(dirty: boolean) => void` | Marks the container as dirty. An asterisk `*` will be appended to the panel title (in tabs, minimized taskbars, and floating headers). Attempting to close the panel will trigger a confirmation warning modal. |
238
- | `onCloseRequested(handler)` | `(handler: () => boolean \| Promise<boolean>) => () => void` | Registers an interception handler. When the panel is closed, this function is triggered. If it returns `false`, the closing is cancelled. Returns an unregister cleanup function. |
239
- | `setTitle(title)` | `(title: string \| ContextMenuPredefinedMessage) => void` | Overrides the tab / window title dynamically from the child element. |
240
- | `requestClose(options)` | `(options?: { force?: boolean }) => void` | Request the parent panel container to close programmatically. If `force: true` is passed, it closes immediately, bypassing any dirty checks or guards. |
241
- | `instanceId` | `string` | The unique ID of the panel. |
242
-
243
- ### Implementation Example
244
-
245
- ```typescript
246
- import React, { useState, useEffect } from 'react';
247
- import { useFormContainer } from 'react-dockable-desktop';
248
-
249
- const EditFormPanel: React.FC = () => {
250
- const container = useFormContainer();
251
- const [text, setText] = useState('');
252
-
253
- // 1. Mark dirty when typing
254
- const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
255
- setText(e.target.value);
256
- container.setDirty(true); // Appends '*' to tab header and prompts warning on close
257
- };
258
-
259
- const handleSave = () => {
260
- container.setDirty(false); // Resets dirty state
261
- alert('Saved successfully!');
262
- };
263
-
264
- // 2. Intercept and block closing based on conditions
265
- useEffect(() => {
266
- const unregister = container.onCloseRequested(() => {
267
- if (text.includes('BLOCK')) {
268
- alert('Cannot close while the word "BLOCK" is present!');
269
- return false; // Blocks closure
270
- }
271
- return true; // Allows closure
272
- });
273
- return unregister;
274
- }, [container, text]);
275
-
276
- return (
277
- <div style={{ padding: '1rem', color: '#fff' }}>
278
- <h5>Dynamic Form Editor</h5>
279
- <textarea value={text} onChange={handleChange} />
280
- <button onClick={handleSave}>Save</button>
281
- <button onClick={() => container.requestClose()}>Cancel & Close</button>
282
- </div>
283
- );
284
- };
285
- ```
286
-
287
- ---
288
-
289
- ## πŸ”Œ Inter-Panel Event Communication Bus
290
-
291
- Avoid complex state management boilerplate. Active panels can broadcast lightweight messages across the workspace seamlessly:
292
-
293
- ```typescript
294
- import React, { useEffect } from 'react';
295
- import { usePanelContext } from 'react-dockable-desktop';
296
-
297
- // Subscriber Panel (e.g. Console Log Viewer)
298
- const ConsoleView: React.FC = () => {
299
- const { subscribe } = usePanelContext();
300
-
301
- useEffect(() => {
302
- const unsubscribe = subscribe('CONSOLE_LOG', (payload) => {
303
- console.log('Received log message: ', payload.message);
304
- });
305
- return () => unsubscribe();
306
- }, [subscribe]);
307
-
308
- return <div>Console Viewer</div>;
309
- };
310
-
311
- // Publisher Panel (e.g. Map View)
312
- const MapView: React.FC = () => {
313
- const { publish } = usePanelContext();
314
-
315
- const handleInteract = () => {
316
- publish('CONSOLE_LOG', { message: 'User zoomed map viewport.' });
317
- };
318
-
319
- return <button onClick={handleInteract}>Click Map</button>;
320
- };
321
- ```
322
-
323
- ---
324
-
325
- ## 🎨 Layout Presets & Configuration Options
326
-
327
- You can customized defaults, positioning attributes, and sizes using the registry builder options:
328
-
329
- ```typescript
330
- PanelRegistry.register('unique-panel-key', PanelComponent, {
331
- title: 'Default Title String', // fallback
332
- canMinimize: true,
333
- canDrag: true,
334
- canClose: true,
335
- initialTarget: 'docked', // or 'floating'
336
- favoritePosition: {
337
- x: 400,
338
- y: 200,
339
- width: 500,
340
- height: 350
341
- }
342
- });
343
- ```
344
-
345
- To customize CSS layout attributes, you can override variables in your stylesheet:
346
- ```css
347
- :root {
348
- --accent-color: #00f0ff;
349
- --bg-dark-color: #12131a;
350
- --glass-bg: rgba(18, 19, 26, 0.65);
351
- --border-color: rgba(255, 255, 255, 0.08);
352
- }
353
- ```
354
-
355
- ---
356
-
357
- ## πŸ“ Architecture
358
-
359
- For a deep-dive into the layout tree model, DOM persistence strategy, overlay system, RTL detection, and build pipeline, see [ARCHITECTURE.md](ARCHITECTURE.md).
360
-
361
- ---
362
-
363
- ## πŸ“„ License
364
-
365
- MIT. Free to use, adapt, and build upon.
1
+ # React Dockable Desktop
2
+
3
+ [![npm version](https://img.shields.io/badge/npm-v1.2.0-blue.svg)](#)
4
+ [![license](https://img.shields.io/badge/license-MIT-green.svg)](#)
5
+ [![Demo](https://img.shields.io/badge/demo-live-brightgreen.svg)](https://felipecarrillo100.github.io/react-dockable-desktop/)
6
+
7
+ A beautiful, premium, state-of-the-art React window manager and dockable layout engine. It features fluid split-docking grids, resizable floating windows, dynamic taskbars, and tabbed panels with **zero-unmount DOM persistence** and **built-in internationalization (i18n) support**.
8
+
9
+ [**Live Interactive Demo πŸš€**](https://felipecarrillo100.github.io/react-dockable-desktop/)
10
+
11
+ ---
12
+
13
+ ## 🌟 Key Features
14
+
15
+ * **πŸ“¦ Dockable Splits & Tab Grid**: Drag-and-drop panels to split screens or group them together into tabbed containers.
16
+ * **πŸ—ΊοΈ Workspace Edge Docking**: Drag a tab or floating window to the outer edges of the screen (left, right, top, bottom) to instantly dock it as a full-width or full-height column/row.
17
+ * **πŸͺ Floating Windows**: Seamlessly pop panels out into floating windows with smooth drag-and-drop movement, custom resizing, maximize, and minimize behaviors.
18
+ * **🎨 Custom Global Style Classes**: Inject custom, global styles at the provider level (`modalClass`, `modalBodyClass`, `sidePanelClass`, `sidePanelBodyClass`, `windowClass`, and `windowBodyClass`) to style widgets uniformly.
19
+ * **πŸ₯ž Aspect-Ratio Modal Engine**: Modals automatically wrap and shrink-to-fit content snugly, but cap height and scroll gracefully at visual aspect-ratio thresholds (4:3 for small/medium, 16:10 for large layouts).
20
+ * **πŸ”¬ Zero-Unmount DOM Persistence**: Heavy widgets (like Leaflet Maps, WebGL viewports, terminal consoles, or stateful forms) maintain their DOM nodes and state. The engine moves existing DOM structures into a hidden document fragment host rather than unmounting them.
21
+ * **🌍 i18n & Full RTL Support**: Native translation pipeline matching React Intl formats and **Right-to-Left (RTL) language layouts** (Arabic, Hebrew, Persian, etc.). Detection is completely automatic based on surrounding `dir="rtl"` elements, with explicit provider control options.
22
+ * **πŸ› οΈ Custom Header Actions**: Inject custom React components directly into tabbed headers and floating window titlebars dynamically using the `renderHeaderActions` registry property.
23
+ * **πŸ” Initial Letter Hover Miniatures**: Miniature preview popovers automatically display the uppercase first character of the panel title inside a theme-responsive grayish layout fallback for panels with disabled live previews.
24
+ * **πŸ’Ž Glassmorphic & Modern Styling**: Sleek dark mode aesthetics, interactive micro-animations, and fluid transitions.
25
+ * **πŸ”Œ Inter-Panel Pub/Sub Event Bus**: A robust messaging system for lightweight, decoupled communication between active panels.
26
+
27
+ ---
28
+
29
+ ## πŸš€ Installation
30
+
31
+ ```bash
32
+ npm install react-dockable-desktop replace-react-contexify
33
+ ```
34
+
35
+ Ensure the styling for both the layout engine and context menu is imported in your main entry file (e.g., `index.js` or `main.tsx`):
36
+
37
+ ```typescript
38
+ import 'replace-react-contexify/styles.css';
39
+ import 'react-dockable-desktop/styles.css';
40
+ ```
41
+
42
+ ---
43
+
44
+ ## πŸ’» Running the Demo Environments
45
+
46
+ The project includes two built-in demo setups to explore and test the window manager layout features:
47
+
48
+ ### 1. Leaflet & Monaco Open Source Demo (Default)
49
+ A clean, lightweight dashboard demo suitable for public deployment. It showcases Leaflet 2D/3D map integration (with dynamic CARTO Light/Dark tile styles mapping to dashboard theme switches) and Monaco Editor panels.
50
+ ```bash
51
+ npm run dev
52
+ ```
53
+
54
+ ### 2. LuciadRIA Earth 3D Demo (Isolated)
55
+ An isolated sandbox dashboard demonstrating premium 3D Earth visualizations in the `EPSG:4978` reference reference frame using LuciadRIA. *Note: Requires a valid LuciadRIA developer license locally to run.*
56
+ ```bash
57
+ npm run dev:ria
58
+ ```
59
+
60
+ ---
61
+
62
+ ## πŸ› οΈ Getting Started
63
+
64
+ ### 1. Create a WorkspaceClient
65
+
66
+ ```typescript
67
+ import { WorkspaceClient } from 'react-dockable-desktop';
68
+ import MapView from './panels/MapView';
69
+ import EditorPanel from './panels/EditorPanel';
70
+
71
+ export const client = new WorkspaceClient({
72
+ panels: {
73
+ mainMap: { component: MapView, defaultOptions: { title: 'Satellite Map View', canClose: false, canMinimize: true, canDrag: true } },
74
+ editor: { component: EditorPanel, defaultOptions: { title: 'Editor', canMinimize: true } },
75
+ },
76
+ // Restore last layout, or null for an empty canvas
77
+ initialState: localStorage.getItem('workspace-layout'),
78
+ });
79
+ ```
80
+
81
+ > **No module-level side effects.** Panels are registered inside `WorkspaceClient`, not via a global `PanelRegistry.register` call. The client can be exported and used imperatively from anywhere: `client.openPanel(...)`, `client.saveLayout()`.
82
+
83
+ ### 2. Set Up the Provider
84
+
85
+ ```typescript
86
+ import React from 'react';
87
+ import {
88
+ WindowManagerProvider,
89
+ WindowManager,
90
+ PanelProvider,
91
+ ModalStackRenderer,
92
+ SidePanelRenderer,
93
+ } from 'react-dockable-desktop';
94
+ import { client } from './workspaceClient';
95
+
96
+ function App() {
97
+ return (
98
+ <WindowManagerProvider client={client}>
99
+ <PanelProvider>
100
+ <div style={{ width: '100vw', height: '100vh', display: 'flex', flexDirection: 'column' }}>
101
+ <WindowManager />
102
+ </div>
103
+ <ModalStackRenderer />
104
+ <SidePanelRenderer />
105
+ </PanelProvider>
106
+ </WindowManagerProvider>
107
+ );
108
+ }
109
+
110
+ export default App;
111
+ ```
112
+
113
+ > **Backward compatibility:** The `client` prop is optional. The `PanelRegistry` global singleton and the old provider props (`formatMessage`, `predefinedMessages`, `dir`) still work as before.
114
+
115
+ ---
116
+
117
+ ## 🌍 Internationalization (i18n)
118
+
119
+ `react-dockable-desktop` is built with dynamic translation in mind. Titles and context menus can accept either raw `string` values or a structured descriptor object resembling `ContextMenuPredefinedMessage`.
120
+
121
+ ### Message Descriptor Format
122
+
123
+ ```typescript
124
+ interface ContextMenuPredefinedMessage {
125
+ id: string;
126
+ defaultMessage?: string;
127
+ values?: Record<string, string | number>;
128
+ }
129
+ ```
130
+
131
+ ### Integrating custom formatters (e.g., `react-intl`)
132
+
133
+ To route messages through your application's translation engine, pass a `formatMessage` callback to `WorkspaceClient`:
134
+
135
+ ```typescript
136
+ const client = new WorkspaceClient({
137
+ panels: { ... },
138
+ formatMessage: (msg) => intl.formatMessage({ id: msg.id, defaultMessage: msg.defaultMessage }, msg.values),
139
+ });
140
+
141
+ function App() {
142
+ return (
143
+ <WindowManagerProvider client={client}>
144
+ ...
145
+ </WindowManagerProvider>
146
+ );
147
+ }
148
+ ```
149
+
150
+ > **Backward compatibility:** Passing `formatMessage` directly as a prop to `WindowManagerProvider` is still supported for existing code.
151
+
152
+ *If no `formatMessage` function is provided, the engine defaults to a fallback formatting template parser resolving placeholders like `Hello {user}` using values.*
153
+
154
+ ### Right-to-Left (RTL) Layout Support
155
+
156
+ The library features built-in, theme-resilient Right-to-Left (RTL) rendering. It automatically mirrors tab stacking flows, close icon alignments, custom header buttons, side drawers (which slide in from the opposite edge), titlebars, and taskbar icons.
157
+
158
+ #### 1. Automatic Detection (Zero Configuration)
159
+ Simply apply the HTML `dir="rtl"` attribute to the container enclosing your layout, or globally on the `<html>`/`<body>` nodes. The workspace will observe it using a `MutationObserver` and mirror dynamically:
160
+ ```html
161
+ <div dir="rtl">
162
+ <Desktop />
163
+ </div>
164
+ ```
165
+
166
+ #### 2. Explicit Programmatic Control
167
+ To force a specific direction regardless of the surrounding HTML structure, pass the `dir` option to `WorkspaceClient`:
168
+ ```typescript
169
+ const client = new WorkspaceClient({ panels: { ... }, dir: 'rtl' });
170
+ <WindowManagerProvider client={client}>
171
+ <Desktop />
172
+ </WindowManagerProvider>
173
+ ```
174
+
175
+ ---
176
+
177
+ ## πŸŽ›οΈ Programmatic Spawning and Layout API
178
+
179
+ Consume actions from the layout context anywhere inside or outside your panel components using the provided hooks.
180
+
181
+ ```typescript
182
+ import { useWindowManagerActions, useWindowManagerState } from 'react-dockable-desktop';
183
+
184
+ const SidebarControls = () => {
185
+ const { openPanel, closePanel, saveLayout, loadLayout } = useWindowManagerActions();
186
+ const state = useWindowManagerState();
187
+
188
+ const handleOpenConsole = () => {
189
+ openPanel('debug-console', 'terminal', {
190
+ title: { id: 'app.console', defaultMessage: 'System Console Log' },
191
+ initialTarget: 'floating' // Options: 'docked' | 'floating' | 'tabbed'
192
+ });
193
+ };
194
+
195
+ return (
196
+ <div>
197
+ <button onClick={handleOpenConsole}>Spawn Console</button>
198
+ <button onClick={() => alert(saveLayout())}>Backup Layout</button>
199
+ </div>
200
+ );
201
+ };
202
+ ```
203
+
204
+ ### Hook Reference
205
+
206
+ | Hook Name | Return Type | Description |
207
+ | :--- | :--- | :--- |
208
+ | `useWindowManagerState()` | `WindowState` | Access grid layout trees, list of active floating windows, minimized windows list, and general dragging statuses. |
209
+ | `useWindowManagerActions()` | `WindowActions` | Spawns, minimizes, restores, docks, floats, maximizes, or closes panels. Also handles split sizing, custom locations, and layout serialization (`saveLayout` / `loadLayout`). |
210
+ | `useFormatMessage()` | `(msg: ContextMenuPredefinedMessage) => string` | Returns the translation message formatter hook matching the provider preset configuration. |
211
+ | `usePanelContext()` | `{ publish, subscribe }` | Dynamic decoupled event bus helper for active panels. |
212
+ | `useStyleClasses()` | `StyleClasses` | Returns the custom CSS class overrides configured on the provider (`windowClass`, `windowBodyClass`, etc.). |
213
+ | `useRegistry()` | `PanelRegistryClass` | Returns the scoped panel registry for the current provider. |
214
+
215
+ ---
216
+
217
+ ## New Exports (v1.2.0)
218
+
219
+ The following additional exports are available in v1.2.0:
220
+
221
+ - `WorkspaceClient` β€” class for creating a scoped workspace configuration outside React.
222
+ - `WorkspaceClientConfig` β€” TypeScript interface for the `WorkspaceClient` constructor options.
223
+ - `PanelDefinition` β€” TypeScript interface describing a single panel entry in the `panels` map.
224
+ - `PanelRegistryClass` β€” the class backing both the scoped (per-client) and global registries; useful for typing or extending the registry.
225
+
226
+ ---
227
+
228
+ ## πŸ›‘οΈ Form Container Context (Close Interception & Dirty States)
229
+
230
+ `react-dockable-desktop` provides a context-driven panel container contract to support dirty form tracking, dynamic title overrides, and close action guards. Child elements can access this container context using the `useFormContainer()` hook.
231
+
232
+ ### Context Hook Functions
233
+
234
+ | Function / Property | Type | Description |
235
+ | :--- | :--- | :--- |
236
+ | `setDirty(dirty)` | `(dirty: boolean) => void` | Marks the container as dirty. An asterisk `*` will be appended to the panel title (in tabs, minimized taskbars, and floating headers). Attempting to close the panel will trigger a confirmation warning modal. |
237
+ | `onCloseRequested(handler)` | `(handler: () => boolean \| Promise<boolean>) => () => void` | Registers an interception handler. When the panel is closed, this function is triggered. If it returns `false`, the closing is cancelled. Returns an unregister cleanup function. |
238
+ | `setTitle(title)` | `(title: string \| ContextMenuPredefinedMessage) => void` | Overrides the tab / window title dynamically from the child element. |
239
+ | `requestClose(options)` | `(options?: { force?: boolean }) => void` | Request the parent panel container to close programmatically. If `force: true` is passed, it closes immediately, bypassing any dirty checks or guards. |
240
+ | `instanceId` | `string` | The unique ID of the panel. |
241
+
242
+ ### Implementation Example
243
+
244
+ ```typescript
245
+ import React, { useState, useEffect } from 'react';
246
+ import { useFormContainer } from 'react-dockable-desktop';
247
+
248
+ const EditFormPanel: React.FC = () => {
249
+ const container = useFormContainer();
250
+ const [text, setText] = useState('');
251
+
252
+ // 1. Mark dirty when typing
253
+ const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
254
+ setText(e.target.value);
255
+ container.setDirty(true); // Appends '*' to tab header and prompts warning on close
256
+ };
257
+
258
+ const handleSave = () => {
259
+ container.setDirty(false); // Resets dirty state
260
+ alert('Saved successfully!');
261
+ };
262
+
263
+ // 2. Intercept and block closing based on conditions
264
+ useEffect(() => {
265
+ const unregister = container.onCloseRequested(() => {
266
+ if (text.includes('BLOCK')) {
267
+ alert('Cannot close while the word "BLOCK" is present!');
268
+ return false; // Blocks closure
269
+ }
270
+ return true; // Allows closure
271
+ });
272
+ return unregister;
273
+ }, [container, text]);
274
+
275
+ return (
276
+ <div style={{ padding: '1rem', color: '#fff' }}>
277
+ <h5>Dynamic Form Editor</h5>
278
+ <textarea value={text} onChange={handleChange} />
279
+ <button onClick={handleSave}>Save</button>
280
+ <button onClick={() => container.requestClose()}>Cancel & Close</button>
281
+ </div>
282
+ );
283
+ };
284
+ ```
285
+
286
+ ---
287
+
288
+ ## πŸ”Œ Inter-Panel Event Communication Bus
289
+
290
+ Avoid complex state management boilerplate. Active panels can broadcast lightweight messages across the workspace seamlessly:
291
+
292
+ ```typescript
293
+ import React, { useEffect } from 'react';
294
+ import { usePanelContext } from 'react-dockable-desktop';
295
+
296
+ // Subscriber Panel (e.g. Console Log Viewer)
297
+ const ConsoleView: React.FC = () => {
298
+ const { subscribe } = usePanelContext();
299
+
300
+ useEffect(() => {
301
+ const unsubscribe = subscribe('CONSOLE_LOG', (payload) => {
302
+ console.log('Received log message: ', payload.message);
303
+ });
304
+ return () => unsubscribe();
305
+ }, [subscribe]);
306
+
307
+ return <div>Console Viewer</div>;
308
+ };
309
+
310
+ // Publisher Panel (e.g. Map View)
311
+ const MapView: React.FC = () => {
312
+ const { publish } = usePanelContext();
313
+
314
+ const handleInteract = () => {
315
+ publish('CONSOLE_LOG', { message: 'User zoomed map viewport.' });
316
+ };
317
+
318
+ return <button onClick={handleInteract}>Click Map</button>;
319
+ };
320
+ ```
321
+
322
+ ---
323
+
324
+ ## 🎨 Layout Presets & Configuration Options
325
+
326
+ You can customize defaults, positioning attributes, and sizes using the `WorkspaceClient` panels configuration:
327
+
328
+ ```typescript
329
+ const client = new WorkspaceClient({
330
+ panels: {
331
+ 'unique-panel-key': {
332
+ component: PanelComponent,
333
+ defaultOptions: {
334
+ title: 'Default Title String', // fallback
335
+ canMinimize: true,
336
+ canDrag: true,
337
+ canClose: true,
338
+ initialTarget: 'docked', // or 'floating'
339
+ favoritePosition: {
340
+ x: 400,
341
+ y: 200,
342
+ width: 500,
343
+ height: 350,
344
+ },
345
+ },
346
+ },
347
+ },
348
+ });
349
+ ```
350
+
351
+ > **Legacy / backward-compatible approach:** You can still use the global `PanelRegistry.register('unique-panel-key', PanelComponent, options)` at module scope. This singleton is fully supported for existing codebases.
352
+
353
+ To customize CSS layout attributes, you can override variables in your stylesheet:
354
+ ```css
355
+ :root {
356
+ --accent-color: #00f0ff;
357
+ --bg-dark-color: #12131a;
358
+ --glass-bg: rgba(18, 19, 26, 0.65);
359
+ --border-color: rgba(255, 255, 255, 0.08);
360
+ }
361
+ ```
362
+
363
+ ---
364
+
365
+ ## 🎨 Theming
366
+
367
+ The library's theming attribute is `data-color-scheme`. When switching themes in your app, set this attribute on the workspace root (or a wrapping element):
368
+
369
+ ```typescript
370
+ document.documentElement.setAttribute('data-color-scheme', 'dark'); // or 'light'
371
+ ```
372
+
373
+ If you are also using Bootstrap, you may set `data-bs-theme` alongside it for Bootstrap component compatibility.
374
+
375
+ ---
376
+
377
+ ## πŸ“ Architecture
378
+
379
+ For a deep-dive into the layout tree model, DOM persistence strategy, overlay system, `WorkspaceClient` and scoped registries, empty canvas default behavior, RTL detection, and build pipeline, see [ARCHITECTURE.md](ARCHITECTURE.md).
380
+
381
+ ---
382
+
383
+ ## πŸ“„ License
384
+
385
+ MIT. Free to use, adapt, and build upon.