react-native-nswindow 0.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 +150 -0
- package/macos/RNNSWindow.h +142 -0
- package/macos/RNNSWindow.mm +570 -0
- package/macos/RNNSWindowHelper.h +80 -0
- package/macos/RNNSWindowHelper.mm +683 -0
- package/macos/RNNSWindowLoader.mm +19 -0
- package/package.json +53 -0
- package/react-native-nswindow.podspec +18 -0
- package/spec/NativeNSWindow.ts +148 -0
- package/src/index.ts +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# react-native-nswindow
|
|
2
|
+
|
|
3
|
+
Multi-window support for React Native macOS. Create, modify, close, and observe native `NSWindow` instances from JavaScript.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- React Native macOS >= 0.81
|
|
8
|
+
- macOS deployment target >= 14.0
|
|
9
|
+
- New Architecture (TurboModules) enabled
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install react-native-nswindow
|
|
15
|
+
cd macos && pod install
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import NSWindowModule from 'react-native-nswindow';
|
|
22
|
+
import { AppRegistry } from 'react-native';
|
|
23
|
+
|
|
24
|
+
// Register a component to render in the new window
|
|
25
|
+
function MyWindow() {
|
|
26
|
+
return <Text>Hello from a new window!</Text>;
|
|
27
|
+
}
|
|
28
|
+
AppRegistry.registerComponent('MyWindow', () => MyWindow);
|
|
29
|
+
|
|
30
|
+
// Create a window
|
|
31
|
+
const windowId = await NSWindowModule.addWindow({
|
|
32
|
+
componentName: 'MyWindow',
|
|
33
|
+
windowName: 'my-window',
|
|
34
|
+
initialProps: {},
|
|
35
|
+
title: 'My Window',
|
|
36
|
+
width: 600,
|
|
37
|
+
height: 400,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Modify it
|
|
41
|
+
await NSWindowModule.modifyWindow(windowId, {
|
|
42
|
+
title: 'Updated Title',
|
|
43
|
+
backgroundColor: '#1e1e1e',
|
|
44
|
+
vibrancy: 'sidebar',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Listen for events
|
|
48
|
+
const sub = NSWindowModule.onWindowClose((id) => {
|
|
49
|
+
console.log('Window closed:', id);
|
|
50
|
+
});
|
|
51
|
+
sub.remove(); // cleanup
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API
|
|
55
|
+
|
|
56
|
+
### Methods
|
|
57
|
+
|
|
58
|
+
| Method | Description |
|
|
59
|
+
|--------|-------------|
|
|
60
|
+
| `addWindow(props: WindowProps)` | Create a new window. Returns its ID. |
|
|
61
|
+
| `closeWindow(windowId)` | Close a window. |
|
|
62
|
+
| `modifyWindow(windowId, props)` | Modify window properties. |
|
|
63
|
+
| `listWindows()` | List all tracked window IDs. |
|
|
64
|
+
| `getWindowState(windowId)` | Get position, size, and state flags. |
|
|
65
|
+
| `focusWindow(windowId)` | Make window key and bring to front. |
|
|
66
|
+
| `hideWindow(windowId)` | Hide (order out) a window. |
|
|
67
|
+
| `showWindow(windowId)` | Show (order front) a window. |
|
|
68
|
+
| `minimizeWindow(windowId)` | Minimize to dock. |
|
|
69
|
+
| `deminimizeWindow(windowId)` | Restore from dock. |
|
|
70
|
+
| `setFullScreen(windowId, bool)` | Enter/exit full screen. |
|
|
71
|
+
| `bringToFront(windowId)` | Order front. |
|
|
72
|
+
| `sendToBack(windowId)` | Order back. |
|
|
73
|
+
| `getScreenInfo()` | Get screen layout info (all screens, total bounds, main screen). |
|
|
74
|
+
|
|
75
|
+
### Window Props
|
|
76
|
+
|
|
77
|
+
All properties are optional except `componentName`, `windowName`, and `initialProps` (on `addWindow`).
|
|
78
|
+
|
|
79
|
+
| Prop | Type | Default | Description |
|
|
80
|
+
|------|------|---------|-------------|
|
|
81
|
+
| `x`, `y` | number | 100 | Window origin |
|
|
82
|
+
| `width`, `height` | number | 400×300 | Window size |
|
|
83
|
+
| `minWidth`, `minHeight` | number | — | Minimum size constraints |
|
|
84
|
+
| `maxWidth`, `maxHeight` | number | — | Maximum size constraints |
|
|
85
|
+
| `center` | boolean | false | Center on screen |
|
|
86
|
+
| `title` | string | windowName | Title bar text |
|
|
87
|
+
| `titleBarStyle` | string | 'default' | 'default' \| 'hidden' \| 'hiddenInset' \| 'transparent' |
|
|
88
|
+
| `vibrancy` | string | 'none' | 'none' \| 'sidebar' \| 'menu' \| 'popover' \| 'fullScreenUI' \| 'underWindowBackground' \| 'hudWindow' |
|
|
89
|
+
| `backgroundColor` | string | — | Any React Native color string (e.g. '#ff0000', 'rgba(0,0,0,0.5)', 'red') |
|
|
90
|
+
| `transparent` | boolean | false | Set window non-opaque (required for translucent backgrounds) |
|
|
91
|
+
| `hasShadow` | boolean | true | Window shadow |
|
|
92
|
+
| `resizable` | boolean | true | Allow resize |
|
|
93
|
+
| `movable` | boolean | true | Allow drag |
|
|
94
|
+
| `minimizable` | boolean | true | Show minimize button |
|
|
95
|
+
| `closable` | boolean | true | Show close button |
|
|
96
|
+
| `zoomable` | boolean | true | Enable zoom button |
|
|
97
|
+
| `alwaysOnTop` | boolean | false | Float above other windows |
|
|
98
|
+
| `level` | string | 'normal' | 'normal' \| 'floating' \| 'modalPanel' \| 'mainMenu' \| 'statusBar' \| 'screenSaver' |
|
|
99
|
+
| `show` | boolean | true | Show immediately |
|
|
100
|
+
| `focusOnCreate` | boolean | true | Make key on show |
|
|
101
|
+
| `autoSaveFrame` | string | — | Persist frame position across launches |
|
|
102
|
+
| `stopShouldClose` | boolean | false | Intercept close (window stays open) |
|
|
103
|
+
|
|
104
|
+
### Events
|
|
105
|
+
|
|
106
|
+
| Event | Payload | Description |
|
|
107
|
+
|-------|---------|-------------|
|
|
108
|
+
| `onWindowClose` | windowId | Window was closed |
|
|
109
|
+
| `onWindowWillClose` | windowId | Close was attempted (fires even if blocked) |
|
|
110
|
+
| `onWindowFocus` | windowId | Became key window |
|
|
111
|
+
| `onWindowBlur` | windowId | Resigned key window |
|
|
112
|
+
| `onWindowMove` | `{ windowId, x, y }` | Window moved |
|
|
113
|
+
| `onWindowResize` | `{ windowId, width, height }` | Window resized |
|
|
114
|
+
| `onWindowMinimize` | windowId | Minimized to dock |
|
|
115
|
+
| `onWindowDeminimize` | windowId | Restored from dock |
|
|
116
|
+
| `onWindowEnterFullScreen` | windowId | Entered full screen |
|
|
117
|
+
| `onWindowExitFullScreen` | windowId | Exited full screen |
|
|
118
|
+
| `onWindowOcclusionStateChange` | `{ windowId, isVisible }` | Window occlusion state changed (visible/occluded) |
|
|
119
|
+
| `onWindowBackingPropertiesChange` | windowId | Backing properties changed (e.g. moved between Retina/non-Retina displays) |
|
|
120
|
+
| `onScreenInfoChange` | — | Screen parameters changed (display added/removed/resized) |
|
|
121
|
+
|
|
122
|
+
Events use the New Architecture `EventEmitter` pattern. Each event method takes a callback and returns a subscription with a `.remove()` method:
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { useEffect } from 'react';
|
|
126
|
+
import NSWindowModule from 'react-native-nswindow';
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const subs = [
|
|
130
|
+
NSWindowModule.onWindowClose((windowId) => {
|
|
131
|
+
console.log('closed:', windowId);
|
|
132
|
+
}),
|
|
133
|
+
NSWindowModule.onWindowMove(({ windowId, x, y }) => {
|
|
134
|
+
console.log('moved:', windowId, x, y);
|
|
135
|
+
}),
|
|
136
|
+
NSWindowModule.onWindowResize(({ windowId, width, height }) => {
|
|
137
|
+
console.log('resized:', windowId, width, height);
|
|
138
|
+
}),
|
|
139
|
+
];
|
|
140
|
+
return () => subs.forEach((s) => s.remove());
|
|
141
|
+
}, []);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## How It Works
|
|
145
|
+
|
|
146
|
+
This is a C++ TurboModule (CxxTurboModule). An ObjC singleton (`RNNSWindowHelper`) manages the window dictionary, acts as `NSWindowDelegate`, and observes `NSNotificationCenter` for all window events. Window operations dispatch to the main thread; promises resolve via the JS invoker.
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "RNNSWindowSpecJSI.h"
|
|
4
|
+
#include <ReactCommon/TurboModuleUtils.h>
|
|
5
|
+
|
|
6
|
+
namespace facebook::react {
|
|
7
|
+
|
|
8
|
+
// Concrete types from codegen structs
|
|
9
|
+
using WindowProps = NativeNSWindowWindowProps<
|
|
10
|
+
std::optional<double>, std::optional<double>, std::optional<double>,
|
|
11
|
+
std::optional<double>, std::optional<double>, std::optional<double>,
|
|
12
|
+
std::optional<double>, std::optional<double>, std::optional<bool>,
|
|
13
|
+
std::optional<std::string>, std::optional<std::string>,
|
|
14
|
+
std::optional<std::string>, std::optional<std::string>, std::optional<bool>,
|
|
15
|
+
std::optional<bool>, std::optional<bool>, std::optional<bool>,
|
|
16
|
+
std::optional<bool>, std::optional<bool>, std::optional<bool>,
|
|
17
|
+
std::optional<bool>, std::optional<std::string>, std::optional<bool>,
|
|
18
|
+
std::optional<bool>, std::optional<std::string>, std::optional<bool>,
|
|
19
|
+
std::string, std::string, jsi::Object>;
|
|
20
|
+
|
|
21
|
+
using ModifyProps = NativeNSWindowModifyableWindowProps<
|
|
22
|
+
std::optional<double>, std::optional<double>, std::optional<double>,
|
|
23
|
+
std::optional<double>, std::optional<double>, std::optional<double>,
|
|
24
|
+
std::optional<double>, std::optional<double>, std::optional<bool>,
|
|
25
|
+
std::optional<std::string>, std::optional<std::string>,
|
|
26
|
+
std::optional<std::string>, std::optional<std::string>, std::optional<bool>,
|
|
27
|
+
std::optional<bool>, std::optional<bool>, std::optional<bool>,
|
|
28
|
+
std::optional<bool>, std::optional<bool>, std::optional<bool>,
|
|
29
|
+
std::optional<bool>, std::optional<std::string>, std::optional<bool>,
|
|
30
|
+
std::optional<bool>, std::optional<std::string>, std::optional<bool>>;
|
|
31
|
+
|
|
32
|
+
// Concrete types for events
|
|
33
|
+
using WindowMovePayload =
|
|
34
|
+
NativeNSWindowWindowMovePayload<std::string, double, double>;
|
|
35
|
+
using WindowResizePayload =
|
|
36
|
+
NativeNSWindowWindowResizePayload<std::string, double, double>;
|
|
37
|
+
using WindowOcclusionStatePayload =
|
|
38
|
+
NativeNSWindowWindowOcclusionStatePayload<std::string, bool>;
|
|
39
|
+
using WindowRect = NativeNSWindowRect<double, double, double, double>;
|
|
40
|
+
using WindowStateResult =
|
|
41
|
+
NativeNSWindowWindowState<std::string, std::string, double, double, double,
|
|
42
|
+
double, bool, bool, bool, bool, bool, double,
|
|
43
|
+
std::optional<WindowRect>>;
|
|
44
|
+
|
|
45
|
+
} // namespace facebook::react
|
|
46
|
+
|
|
47
|
+
// bridging support for event payload structs
|
|
48
|
+
namespace facebook::react {
|
|
49
|
+
|
|
50
|
+
template <> struct Bridging<WindowMovePayload> {
|
|
51
|
+
static WindowMovePayload
|
|
52
|
+
fromJs(jsi::Runtime &rt, const jsi::Object &value,
|
|
53
|
+
const std::shared_ptr<CallInvoker> &jsInvoker) {
|
|
54
|
+
return NativeNSWindowWindowMovePayloadBridging<WindowMovePayload>::fromJs(
|
|
55
|
+
rt, value, jsInvoker);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static jsi::Object toJs(jsi::Runtime &rt, const WindowMovePayload &value,
|
|
59
|
+
const std::shared_ptr<CallInvoker> &jsInvoker) {
|
|
60
|
+
return NativeNSWindowWindowMovePayloadBridging<WindowMovePayload>::toJs(
|
|
61
|
+
rt, value, jsInvoker);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
template <> struct Bridging<WindowResizePayload> {
|
|
66
|
+
static WindowResizePayload
|
|
67
|
+
fromJs(jsi::Runtime &rt, const jsi::Object &value,
|
|
68
|
+
const std::shared_ptr<CallInvoker> &jsInvoker) {
|
|
69
|
+
return NativeNSWindowWindowResizePayloadBridging<
|
|
70
|
+
WindowResizePayload>::fromJs(rt, value, jsInvoker);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static jsi::Object toJs(jsi::Runtime &rt, const WindowResizePayload &value,
|
|
74
|
+
const std::shared_ptr<CallInvoker> &jsInvoker) {
|
|
75
|
+
return NativeNSWindowWindowResizePayloadBridging<WindowResizePayload>::toJs(
|
|
76
|
+
rt, value, jsInvoker);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
template <> struct Bridging<WindowOcclusionStatePayload> {
|
|
81
|
+
static WindowOcclusionStatePayload
|
|
82
|
+
fromJs(jsi::Runtime &rt, const jsi::Object &value,
|
|
83
|
+
const std::shared_ptr<CallInvoker> &jsInvoker) {
|
|
84
|
+
return NativeNSWindowWindowOcclusionStatePayloadBridging<
|
|
85
|
+
WindowOcclusionStatePayload>::fromJs(rt, value, jsInvoker);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static jsi::Object toJs(jsi::Runtime &rt,
|
|
89
|
+
const WindowOcclusionStatePayload &value,
|
|
90
|
+
const std::shared_ptr<CallInvoker> &jsInvoker) {
|
|
91
|
+
return NativeNSWindowWindowOcclusionStatePayloadBridging<
|
|
92
|
+
WindowOcclusionStatePayload>::toJs(rt, value, jsInvoker);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
} // namespace facebook::react
|
|
97
|
+
|
|
98
|
+
namespace facebook::react {
|
|
99
|
+
|
|
100
|
+
class RNNSWindow : public NativeNSWindowCxxSpec<RNNSWindow> {
|
|
101
|
+
public:
|
|
102
|
+
RNNSWindow(std::shared_ptr<CallInvoker> jsInvoker);
|
|
103
|
+
~RNNSWindow();
|
|
104
|
+
|
|
105
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowClose;
|
|
106
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowWillClose;
|
|
107
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowMove;
|
|
108
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowResize;
|
|
109
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowFocus;
|
|
110
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowBlur;
|
|
111
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowMinimize;
|
|
112
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowDeminimize;
|
|
113
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowEnterFullScreen;
|
|
114
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowExitFullScreen;
|
|
115
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowOcclusionStateChange;
|
|
116
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnWindowBackingPropertiesChange;
|
|
117
|
+
using NativeNSWindowCxxSpec<RNNSWindow>::emitOnScreenInfoChange;
|
|
118
|
+
|
|
119
|
+
jsi::Value addWindow(jsi::Runtime &rt, jsi::Object props);
|
|
120
|
+
jsi::Value closeWindow(jsi::Runtime &rt, jsi::String windowId);
|
|
121
|
+
jsi::Value modifyWindow(jsi::Runtime &rt, jsi::String windowId,
|
|
122
|
+
jsi::Object props);
|
|
123
|
+
|
|
124
|
+
jsi::Value listWindows(jsi::Runtime &rt);
|
|
125
|
+
jsi::Value getWindowState(jsi::Runtime &rt, jsi::String windowId);
|
|
126
|
+
|
|
127
|
+
jsi::Value focusWindow(jsi::Runtime &rt, jsi::String windowId);
|
|
128
|
+
jsi::Value hideWindow(jsi::Runtime &rt, jsi::String windowId);
|
|
129
|
+
jsi::Value showWindow(jsi::Runtime &rt, jsi::String windowId);
|
|
130
|
+
|
|
131
|
+
jsi::Value minimizeWindow(jsi::Runtime &rt, jsi::String windowId);
|
|
132
|
+
jsi::Value deminimizeWindow(jsi::Runtime &rt, jsi::String windowId);
|
|
133
|
+
jsi::Value setFullScreen(jsi::Runtime &rt, jsi::String windowId,
|
|
134
|
+
bool fullscreen);
|
|
135
|
+
|
|
136
|
+
jsi::Value bringToFront(jsi::Runtime &rt, jsi::String windowId);
|
|
137
|
+
jsi::Value sendToBack(jsi::Runtime &rt, jsi::String windowId);
|
|
138
|
+
|
|
139
|
+
jsi::Value getScreenInfo(jsi::Runtime &rt);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
} // namespace facebook::react
|