react-dockable-desktop 2.1.0 → 3.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
@@ -1,28 +1,33 @@
1
1
  # React Dockable Desktop
2
2
 
3
- [![npm version](https://img.shields.io/badge/npm-v2.0.0-blue.svg)](https://www.npmjs.com/package/react-dockable-desktop)
3
+ [![npm version](https://img.shields.io/badge/npm-v3.1.0-blue.svg)](https://www.npmjs.com/package/react-dockable-desktop)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-first-3178c6.svg)](https://www.typescriptlang.org/)
5
+ [![Touch Ready](https://img.shields.io/badge/touch-iPad%20%7C%20Android-success.svg)](#touch--mobile)
4
6
  [![license](https://img.shields.io/badge/license-MIT-green.svg)](#license)
5
7
  [![Demo](https://img.shields.io/badge/demo-live-brightgreen.svg)](https://felipecarrillo100.github.io/react-dockable-desktop/demo/)
6
8
  [![Docs](https://img.shields.io/badge/docs-site-blue.svg)](https://felipecarrillo100.github.io/react-dockable-desktop/)
7
9
 
8
- A premium React window manager and dockable layout engine. Fluid split-docking grids, resizable floating windows, dynamic taskbars, and tabbed panels with **zero-unmount DOM persistence** and **built-in i18n/RTL support**.
10
+ A premium dockable layout engine for React. Build desktop-class applications with fluid split-docking grids, tabbed panels, resizable floating windows, and **zero-unmount DOM preservation** — so WebGL contexts, live maps, and stateful editors never lose their state when moved or re-tabbed.
9
11
 
10
- **[Full Documentation](https://felipecarrillo100.github.io/react-dockable-desktop/)**  | 
11
12
  **[Live Demo](https://felipecarrillo100.github.io/react-dockable-desktop/demo/)**  | 
13
+ **[Full Documentation](https://felipecarrillo100.github.io/react-dockable-desktop/)**  | 
12
14
  **[API Reference](https://felipecarrillo100.github.io/react-dockable-desktop/api/)**
13
15
 
14
16
  ---
15
17
 
16
- ## Key Features
18
+ ## Features
17
19
 
18
- - **Dockable Splits & Tab Grid** — drag panels to split screens or group them into tabbed containers
19
- - **Workspace Edge Docking** — drag to outer edges to dock as full-width/height columns or rows
20
- - **Floating Windows** — pop panels into resizable floating windows with maximize and minimize
21
- - **Zero-Unmount DOM Persistence** — WebGL, maps, terminals, and stateful forms keep their DOM node and state when moved
22
- - **i18n & RTL** — full Right-to-Left layout support with automatic `dir="rtl"` detection
23
- - **Inter-Panel Pub/Sub** — decoupled lightweight messaging between active panels
24
- - **Imperative API** — `WorkspaceClient` lets you open, close, focus, and serialize panels from anywhere outside React
25
- - **Layout Serialization** — save/restore the full workspace layout as a JSON string
20
+ - **Split-Docking Grid** — drag panels to split any zone into rows/columns or group into tabbed containers
21
+ - **Workspace Edge Docking** — drag to the outer edges to dock a panel as a full-width or full-height strip
22
+ - **Floating Windows** — pop panels into freely resizable floating windows; 8-direction resize handles (N/NE/E/SE/S/SW/W/NW), maximize, minimize
23
+ - **Touch & Mobile Ready** — full iPad and Android support: long-press to drag tabs, touch resize, 44px coarse-pointer targets throughout
24
+ - **Zero-Unmount DOM Persistence** — panel DOM nodes are moved, never destroyed; WebGL, maps, terminals, and forms retain full state
25
+ - **i18n & RTL** — full Right-to-Left layout support; `dir="rtl"` flips every control, tab order, and drop zone automatically
26
+ - **Inter-Panel Pub/Sub** — lightweight typed event bus for decoupled panel-to-panel communication
27
+ - **Imperative API** — `WorkspaceClient` opens, closes, focuses, and serializes panels from anywhere — inside or outside React
28
+ - **Layout Serialization** — save and restore the full workspace as a JSON string; survives page reloads
29
+ - **7 Built-in Skins** — VSCode, macOS, Chrome, Slate, Nord, Obsidian, Tokyo — all fully themeable via CSS variables
30
+ - **TypeScript-first** — complete type definitions included; no separate `@types/` package needed
26
31
 
27
32
  ---
28
33
 
@@ -32,28 +37,33 @@ A premium React window manager and dockable layout engine. Fluid split-docking g
32
37
  npm install react-dockable-desktop replace-react-contexify
33
38
  ```
34
39
 
35
- Import styles in your entry file:
40
+ Import styles in your app entry file. **Order matters** — the contexify sheet must come first:
36
41
 
37
42
  ```ts
38
43
  import 'replace-react-contexify/styles.css';
39
44
  import 'react-dockable-desktop/styles.css';
40
45
  ```
41
46
 
47
+ **Requirements:** React ≥ 16.8 · Node ≥ 18
48
+
42
49
  ---
43
50
 
44
51
  ## Quick Start
45
52
 
46
53
  ### 1. Create a WorkspaceClient
47
54
 
55
+ Define your panel catalog and create a `WorkspaceClient` **outside React**, at module scope. It acts as the bridge between your imperative code and the React tree.
56
+
48
57
  ```ts
58
+ // workspace.ts
49
59
  import { WorkspaceClient } from 'react-dockable-desktop';
50
- import MapPanel from './panels/MapPanel';
60
+ import MapPanel from './panels/MapPanel';
51
61
  import EditorPanel from './panels/EditorPanel';
52
62
 
53
- export const client = new WorkspaceClient({
63
+ export const workspace = new WorkspaceClient({
54
64
  panels: {
55
65
  map: { component: MapPanel, defaultOptions: { title: 'Map View' } },
56
- editor: { component: EditorPanel, defaultOptions: { title: 'Editor' } },
66
+ editor: { component: EditorPanel, defaultOptions: { title: 'Editor' } },
57
67
  },
58
68
  initialState: localStorage.getItem('workspace-layout'),
59
69
  });
@@ -61,97 +71,287 @@ export const client = new WorkspaceClient({
61
71
 
62
72
  ### 2. Mount the Provider
63
73
 
74
+ `DockableDesktopProvider` is the single composite provider — it wraps everything the library needs:
75
+
64
76
  ```tsx
65
- import { WindowManagerProvider, WindowManager, PanelProvider, ModalStackRenderer, SidePanelRenderer } from 'react-dockable-desktop';
66
- import { client } from './workspaceClient';
77
+ // App.tsx
78
+ import { DockableDesktopProvider, WindowManager, ModalStackRenderer } from 'react-dockable-desktop';
79
+ import { workspace } from './workspace';
67
80
 
68
- function App() {
81
+ export default function App() {
69
82
  return (
70
- <WindowManagerProvider client={client}>
71
- <PanelProvider>
72
- <div style={{ width: '100vw', height: '100vh' }}>
73
- <WindowManager />
74
- </div>
75
- <ModalStackRenderer />
76
- <SidePanelRenderer />
77
- </PanelProvider>
78
- </WindowManagerProvider>
83
+ <DockableDesktopProvider client={workspace}>
84
+ <div style={{ width: '100vw', height: '100vh' }}>
85
+ <WindowManager />
86
+ </div>
87
+ <ModalStackRenderer />
88
+ </DockableDesktopProvider>
79
89
  );
80
90
  }
81
91
  ```
82
92
 
83
- ### 3. Open Panels Imperatively
93
+ > **Important:** the `WindowManager` container must have an explicit height. A `height: 100%` that resolves to zero will produce a development warning.
94
+
95
+ ### 3. Open Panels
84
96
 
85
97
  ```ts
86
- // From anywhere outside React:
87
- client.openPanel('map-1', 'map', { title: 'Satellite View' });
88
- client.focusPanel('map-1');
89
- client.saveLayout();
90
-
91
- // Query state:
92
- client.isOpen('map-1'); // boolean
93
- client.getOpenPanelIds(); // string[]
98
+ // From anywhere — inside or outside React:
99
+ workspace.openPanel('map-1', 'map');
100
+ workspace.openPanel('ed-1', 'editor', { title: 'config.json', initialTarget: 'floating' });
101
+ workspace.focusPanel('map-1');
102
+
103
+ // Layout persistence:
104
+ localStorage.setItem('workspace-layout', workspace.saveLayout());
105
+
106
+ // Query state without a hook:
107
+ workspace.isOpen('map-1'); // boolean
108
+ workspace.getOpenPanelIds(); // string[]
109
+ ```
110
+
111
+ Panels can now be dragged, split, tabbed, floated, and minimized out of the box.
112
+
113
+ ---
114
+
115
+ ## Writing a Panel Component
116
+
117
+ A panel is any React component. Use built-in hooks to integrate with the layout:
118
+
119
+ ```tsx
120
+ import { useFormContainer, usePanelId } from 'react-dockable-desktop';
121
+
122
+ export default function EditorPanel() {
123
+ const panelId = usePanelId(); // this panel's instance ID — no prop needed
124
+ const { setDirty, setTitle } = useFormContainer();
125
+
126
+ const handleChange = (value: string) => {
127
+ setDirty(true); // blocks close until user confirms discard
128
+ setTitle('config.json *'); // updates the tab title live
129
+ };
130
+
131
+ return <textarea onChange={e => handleChange(e.target.value)} />;
132
+ }
94
133
  ```
95
134
 
96
135
  ---
97
136
 
98
137
  ## Hooks
99
138
 
100
- Use these inside any component within the provider tree:
139
+ Call these inside any component within the `DockableDesktopProvider` tree:
101
140
 
102
- | Hook | Returns | Description |
141
+ | Hook | Returns | Use For |
103
142
  | :--- | :--- | :--- |
104
- | `useWindowManagerActions()` | `WindowActions` | Open, close, focus, dock, float, minimize, maximize, serialize panels |
105
- | `useWindowManagerState()` | `WindowState` | Reactive access to grid layout, floating windows, minimized panels |
143
+ | `useWindowManagerActions()` | `WindowActions` | Open, close, float, dock, minimize, maximize, serialize panels |
144
+ | `useWindowManagerState(selector?)` | `WindowState` or selected slice | Read layout, floating windows, active panel ID |
145
+ | `usePanelActions()` | `PanelActions` | Open modal overlays and left/right side drawers |
146
+ | `usePanelContext()` | `{ publish, subscribe }` | Inter-panel typed event bus |
147
+ | `useFormContainer()` | `FormContainerContract` | Dirty state, close guards, dynamic panel title/icon |
148
+ | `usePanelId()` | `string` | The panel's own instance ID — no prop drilling needed |
106
149
  | `useRegistry()` | `PanelRegistryClass` | The scoped panel registry for the current provider |
107
- | `usePanelContext()` | `{ publish, subscribe }` | Inter-panel pub/sub event bus |
108
- | `useFormContainer()` | `FormContainerContract` | Dirty-state tracking, dynamic title overrides, close guards |
109
- | `useFormatMessage()` | `(msg) => string` | Translation formatter matching the provider's i18n config |
150
+ | `useFormatMessage()` | `MessageFormatter` | i18n formatter matching the current provider's locale |
151
+
152
+ **State selectors** prevent unnecessary re-renders:
153
+
154
+ ```ts
155
+ // Only re-renders when activePanelId changes — not on every layout mutation:
156
+ const activeId = useWindowManagerState(s => s.activePanelId);
157
+ const panelCount = useWindowManagerState(s => Object.keys(s.panels).length);
158
+ ```
110
159
 
111
160
  ---
112
161
 
113
- ## v2.0.0 — Breaking Changes
162
+ ## WorkspaceClient Reference
114
163
 
115
- | Removed | Replacement |
116
- | :--- | :--- |
117
- | `bringToFront(id)` | `focusPanel(id)` — works for both floating and docked panels |
118
- | `setActivePanel(id)` on `WindowActions` | `focusPanel(id)` |
164
+ ```ts
165
+ const workspace = new WorkspaceClient({ panels, initialState?, formatMessage?, dir? });
166
+
167
+ // Panel lifecycle
168
+ workspace.openPanel(id, component, options?) // options: title, initialTarget, stickyRight, stickyBottom
169
+ workspace.closePanel(id)
170
+ workspace.focusPanel(id) // raises floating / selects tab for docked
171
+ workspace.floatPanel(id, rect?) // detach to a floating window
172
+ workspace.dockPanel(id) // return floating to the grid
173
+ workspace.minimizePanel(id)
174
+ workspace.restorePanel(id)
175
+ workspace.maximizePanel(id)
176
+
177
+ // Synchronous state queries (no hook needed)
178
+ workspace.isOpen(id) // → boolean
179
+ workspace.getOpenPanelIds() // → string[]
180
+
181
+ // Layout persistence
182
+ workspace.saveLayout() // → JSON string
183
+ workspace.loadLayout(json) // → boolean (true = success)
184
+
185
+ // Event bus
186
+ workspace.publish(event, data)
187
+ workspace.subscribe(event, callback) // → unsubscribe()
188
+ workspace.onPanelOpen(cb)
189
+ workspace.onPanelClose(cb)
190
+ workspace.onPanelMinimize(cb)
191
+ workspace.onPanelRestore(cb)
192
+
193
+ // Direction
194
+ workspace.setDirection('ltr' | 'rtl')
195
+ ```
119
196
 
120
- **New in v2.0.0:**
121
- - `focusPanel(id)` — unified "show this panel" method
122
- - `isOpen(id): boolean` — synchronous panel state query
123
- - `getOpenPanelIds(): string[]` — list all open panel IDs
124
- - Pending-call queue: imperative calls before the provider mounts are automatically buffered and replayed
125
- - DEV-mode warning if `client=` prop is missing on the provider
126
- - DEV-mode warning if `replace-react-contexify` CSS is not detected
197
+ ---
198
+
199
+ ## Layout Persistence
127
200
 
128
- See the [Migration Guide](https://felipecarrillo100.github.io/react-dockable-desktop/guide/migration) for the full list of changes.
201
+ ```ts
202
+ // Save on unload (or on any meaningful user action):
203
+ window.addEventListener('beforeunload', () => {
204
+ localStorage.setItem('workspace-layout', workspace.saveLayout());
205
+ });
206
+
207
+ // Restore by passing the saved string to the constructor:
208
+ new WorkspaceClient({
209
+ panels: { ... },
210
+ initialState: localStorage.getItem('workspace-layout'),
211
+ });
212
+ ```
129
213
 
130
214
  ---
131
215
 
132
- ## Demo Environments
216
+ ## Side Panels & Modals
133
217
 
134
- ```bash
135
- npm run dev # Leaflet + Monaco open-source demo
136
- npm run dev:ria # LuciadRIA 3D Earth demo (requires license)
218
+ Add `SidePanelRenderer` and `ModalStackRenderer` to your app root. Placement matters — `SidePanelRenderer` must be **inside** the workspace container so drawers position correctly; `ModalStackRenderer` goes **outside** as a full-screen overlay:
219
+
220
+ ```tsx
221
+ // App.tsx
222
+ import { SidePanelRenderer, ModalStackRenderer } from 'react-dockable-desktop';
223
+
224
+ function App() {
225
+ return (
226
+ <DockableDesktopProvider client={workspace}>
227
+ <div style={{ width: '100vw', height: '100vh', overflow: 'hidden', position: 'relative' }}>
228
+ <WindowManager />
229
+ <SidePanelRenderer /> {/* inside — drawers position relative to this container */}
230
+ </div>
231
+ <ModalStackRenderer /> {/* outside — full-screen overlay */}
232
+ </DockableDesktopProvider>
233
+ );
234
+ }
235
+
236
+ // From any panel component:
237
+ const { openModal, openRightPanel } = usePanelActions();
238
+
239
+ openModal(MyForm, { itemId: 42 }, { title: 'Edit Item', size: 'medium' });
240
+ openRightPanel(PropertiesPanel, { nodeId }, { title: 'Properties', width: 320 });
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Touch & Mobile
246
+
247
+ Touch support is built in for v3.1.0+. No extra setup required:
248
+
249
+ - **Tab drag** — long-press (300ms) on any tab to start dragging; haptic feedback on supported devices
250
+ - **Floating window drag** — long-press the titlebar, then drag
251
+ - **Resize** — drag any of the 8 resize handles; minimum 44px touch targets throughout
252
+ - **Split resizer** — drag the 1px divider line; the hit area extends into the safe direction to avoid accidental tab activation
253
+ - **Tab bar scroll** — swipe horizontally in the tab strip to scroll when there are many tabs
254
+
255
+ ---
256
+
257
+ ## i18n & RTL
258
+
259
+ ```tsx
260
+ <DockableDesktopProvider
261
+ dir="rtl"
262
+ formatMessage={(msg) => intl.formatMessage({ id: msg.id, defaultMessage: msg.defaultMessage })}
263
+ predefinedMessages={customMessages}
264
+ >
265
+ ```
266
+
267
+ `dir` can be `'ltr'` (default) or `'rtl'`. All layout, split directions, tab ordering, drop zones, and context menus flip automatically. You can also switch at runtime:
268
+
269
+ ```ts
270
+ workspace.setDirection('rtl');
271
+ ```
272
+
273
+ ---
274
+
275
+ ## Skins
276
+
277
+ ```tsx
278
+ <WindowManager skin="vscode" /> // default
279
+ <WindowManager skin="macos" />
280
+ <WindowManager skin="nord" />
281
+ <WindowManager skin="tokyo" />
137
282
  ```
138
283
 
284
+ **Built-in skins:** `vscode` · `macos` · `chrome` · `slate` · `nord` · `obsidian` · `tokyo`
285
+
286
+ Create your own skin by overriding CSS custom properties under a `[data-workspace-skin="myskin"]` selector. See the [Theming Guide](https://felipecarrillo100.github.io/react-dockable-desktop/guide/theming) for the full variable reference.
287
+
288
+ ---
289
+
290
+ ## What's New
291
+
292
+ ### v3.1.0
293
+ - **Full touch & iPad/Android support** — Pointer Events API migration; long-press (300ms) activates tab drag; all drag/resize surfaces work with finger and Apple Pencil
294
+ - **8-direction resize handles** — floating windows now have N, NE, E, SE, S, SW, W, NW resize handles
295
+ - **Smart resizer hit areas** — horizontal resizer extends only upward into the safe content zone, preventing accidental activation when clicking tabs directly below
296
+ - **Cursor bug fix** — cursor orientation during active resize drag now matches the visual handle correctly
297
+
298
+ ### v3.0.0
299
+ - **`DockableDesktopProvider`** — single composite provider replaces the manual `WindowManagerProvider + PanelProvider` nesting
300
+ - **RTL support** — `dir` prop on provider; `setDirection()` on client; full mirroring of all controls
301
+ - **State selectors** — `useWindowManagerState(s => s.activePanelId)` for surgical re-renders
302
+ - **Dynamic ModalStack** — clean overlay system with dirty-state close guards
303
+ - **`usePanelId()` hook** — zero-prop-drilling panel identity
304
+ - **Typed event bus** — `WorkspaceClient<AppEvents>` for type-safe inter-panel messaging
305
+
306
+ ### v2.0.0 — Breaking Changes
307
+
308
+ | Removed | Replacement |
309
+ | :--- | :--- |
310
+ | `bringToFront(id)` | `focusPanel(id)` — works for both floating and docked panels |
311
+ | `setActivePanel(id)` | `focusPanel(id)` |
312
+
313
+ Full details in the [Migration Guide](https://felipecarrillo100.github.io/react-dockable-desktop/guide/migration).
314
+
139
315
  ---
140
316
 
141
317
  ## Documentation
142
318
 
143
- Full narrative guides, API reference, and the interactive demo are published at:
319
+ Complete guides, API reference, and interactive demo at:
320
+
321
+ **[https://felipecarrillo100.github.io/react-dockable-desktop/](https://felipecarrillo100.github.io/react-dockable-desktop/)**
144
322
 
145
- **https://felipecarrillo100.github.io/react-dockable-desktop/**
323
+ | Guide | Description |
324
+ | :--- | :--- |
325
+ | [Installation](https://felipecarrillo100.github.io/react-dockable-desktop/guide/installation) | Requirements, CSS import order, module formats |
326
+ | [Quick Start](https://felipecarrillo100.github.io/react-dockable-desktop/guide/quick-start) | Minimal working app with layout persistence |
327
+ | [WorkspaceClient](https://felipecarrillo100.github.io/react-dockable-desktop/guide/workspace-client) | Full imperative API, multiple providers, i18n config |
328
+ | [Panel Registry](https://felipecarrillo100.github.io/react-dockable-desktop/guide/panel-registry) | `defaultOptions`, scoped vs global registry |
329
+ | [Layout System](https://felipecarrillo100.github.io/react-dockable-desktop/guide/layout) | Opening, floating, minimizing, serializing layouts |
330
+ | [Panel Lifecycle & Forms](https://felipecarrillo100.github.io/react-dockable-desktop/guide/forms-and-panels) | Dirty state, close guards, `useFormContainer` |
331
+ | [Modals & Side Panels](https://felipecarrillo100.github.io/react-dockable-desktop/guide/modals-and-drawers) | Modal stack, drawers, `Sidebar` component |
332
+ | [Event Bus](https://felipecarrillo100.github.io/react-dockable-desktop/guide/event-bus) | Typed pub/sub, built-in lifecycle events |
333
+ | [Theming](https://felipecarrillo100.github.io/react-dockable-desktop/guide/theming) | CSS variables, custom skins, dark/light modes |
334
+ | [Advanced Topics](https://felipecarrillo100.github.io/react-dockable-desktop/guide/advanced) | RTL, multiple workspaces, custom header actions |
335
+ | [Best Practices](https://felipecarrillo100.github.io/react-dockable-desktop/guide/best-practices) | Patterns for production-ready implementations |
336
+ | [Migration Guide](https://felipecarrillo100.github.io/react-dockable-desktop/guide/migration) | Upgrade from v1 → v2 → v3 |
337
+ | [API Reference](https://felipecarrillo100.github.io/react-dockable-desktop/api/) | Full type-level reference for all exports |
338
+
339
+ ---
146
340
 
147
- - [Getting Started](https://felipecarrillo100.github.io/react-dockable-desktop/guide/)
148
- - [WorkspaceClient Guide](https://felipecarrillo100.github.io/react-dockable-desktop/guide/workspace-client)
149
- - [Layout System](https://felipecarrillo100.github.io/react-dockable-desktop/guide/layout)
150
- - [API Reference](https://felipecarrillo100.github.io/react-dockable-desktop/api/)
151
- - [Migration v1 → v2](https://felipecarrillo100.github.io/react-dockable-desktop/guide/migration)
341
+ ## Development
342
+
343
+ ```bash
344
+ git clone https://github.com/felipecarrillo100/react-dockable-desktop.git
345
+ cd react-dockable-desktop
346
+ npm install
347
+ npm run dev # Leaflet + Monaco open-source demo
348
+ npm run dev:ria # LuciadRIA 3D Earth demo (requires license)
349
+ npm test # vitest unit suite
350
+ npm run build # build dist/
351
+ ```
152
352
 
153
353
  ---
154
354
 
155
355
  ## License
156
356
 
157
- MIT. Free to use, adapt, and build upon.
357
+ MIT free to use, adapt, and build upon.