react-dialogger 1.1.146 → 1.2.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 +123 -0
- package/bridge/DialogContextHost.d.ts +43 -0
- package/bridge/DialogContextHost.js +90 -0
- package/bridge/DialogContextProvide.d.ts +28 -0
- package/bridge/DialogContextProvide.js +81 -0
- package/bridge/channel.d.ts +51 -0
- package/bridge/channel.js +64 -0
- package/bridge/index.d.ts +13 -0
- package/bridge/index.js +19 -0
- package/index.d.ts +2 -0
- package/index.js +7 -1
- package/models/Dialog.js +16 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,6 +91,129 @@ Access them using dialog.deps.*`
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
# 🌉 Context Bridge (since v1.2.0)
|
|
95
|
+
|
|
96
|
+
> Opt-in mechanism that lets dialog bodies consume React `Context` values from
|
|
97
|
+
> the host application's main tree — without manual `inject/deps` plumbing for
|
|
98
|
+
> every Context.
|
|
99
|
+
|
|
100
|
+
#### Why it exists
|
|
101
|
+
|
|
102
|
+
react-dialogger mounts each dialog into its **own** React root via `createRoot(...)`.
|
|
103
|
+
This is by design (a dialog's lifetime is independent from the parent component
|
|
104
|
+
that opened it), but the side effect is that `useContext(...)` inside a dialog
|
|
105
|
+
body returns the `createContext` default (typically `null`) — Providers from the
|
|
106
|
+
host tree do not reach across roots.
|
|
107
|
+
|
|
108
|
+
The `inject` / `deps` API already solves this for explicit values. The Context
|
|
109
|
+
Bridge complements it for the cases where:
|
|
110
|
+
|
|
111
|
+
- you want to use existing hook-based code unchanged (`useContext(AppContext)`,
|
|
112
|
+
custom hooks like `useTheme`, `useAuth`, etc.) inside dialog bodies; or
|
|
113
|
+
- you have many Contexts to forward and listing them in every `inject({...})`
|
|
114
|
+
call is noisy.
|
|
115
|
+
|
|
116
|
+
The bridge is **opt-in and orthogonal to inject/deps** — existing dialogs that
|
|
117
|
+
do not opt in see zero behavior change.
|
|
118
|
+
|
|
119
|
+
#### Setup — one component, one place
|
|
120
|
+
|
|
121
|
+
Mount `<DialogContextHost contexts={[...]}/>` once in your app's main tree,
|
|
122
|
+
**beneath** every Provider you want to bridge:
|
|
123
|
+
|
|
124
|
+
```jsx
|
|
125
|
+
import { DialogContextHost } from "react-dialogger";
|
|
126
|
+
import { AppContext } from "./contexts/AppContext";
|
|
127
|
+
import { ThemeContext } from "./contexts/ThemeContext";
|
|
128
|
+
|
|
129
|
+
function App() {
|
|
130
|
+
return (
|
|
131
|
+
<AppContextProvider>
|
|
132
|
+
<ThemeProvider>
|
|
133
|
+
{/* anywhere below the Providers you want to bridge */}
|
|
134
|
+
<DialogContextHost contexts={[AppContext, ThemeContext]} />
|
|
135
|
+
|
|
136
|
+
<Router>{/* ...your app... */}</Router>
|
|
137
|
+
</ThemeProvider>
|
|
138
|
+
</AppContextProvider>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
That is the entire setup. Every dialog opened from anywhere in the app from
|
|
144
|
+
this point on will see the bridged Context values inside its body — no
|
|
145
|
+
`setBody` wrapping, no `inject` calls for these Contexts.
|
|
146
|
+
|
|
147
|
+
#### Dialog body — unchanged
|
|
148
|
+
|
|
149
|
+
```jsx
|
|
150
|
+
const MyDialogBody = ({ dialog }) => {
|
|
151
|
+
// Works exactly as if the body were rendered inside the host tree.
|
|
152
|
+
const app = React.useContext(AppContext);
|
|
153
|
+
const theme = React.useContext(ThemeContext);
|
|
154
|
+
|
|
155
|
+
return <div>Hello {app.user.name}</div>;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
new Dialog(null, { /* opts */ })
|
|
159
|
+
.setHeader(() => "Greeting")
|
|
160
|
+
.setBody(d => <MyDialogBody dialog={d} />)
|
|
161
|
+
.show();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### Rules and gotchas
|
|
165
|
+
|
|
166
|
+
1. **Place `<DialogContextHost/>` below every Provider you list.** `useContext(X)`
|
|
167
|
+
returns the createContext default if no `X.Provider` ancestor exists — so the
|
|
168
|
+
bridge would capture (and propagate) `null`. The fix is structural: position
|
|
169
|
+
the Host inside the relevant Providers.
|
|
170
|
+
|
|
171
|
+
2. **If a bridged Context's Provider is only mounted in some pages, the bridge
|
|
172
|
+
delivers `null` from pages where it is unmounted.** Two ways to handle this:
|
|
173
|
+
- Mount the Provider higher up (e.g. at app root) so it is always present.
|
|
174
|
+
- Or wrap the dialog body locally with that Provider: the nearest Provider
|
|
175
|
+
wins, so a body-local Provider overrides the bridge's `null`.
|
|
176
|
+
```jsx
|
|
177
|
+
.setBody(d => (
|
|
178
|
+
<ExpenseProvider> {/* fresh, only for this dialog */}
|
|
179
|
+
<MyExpenseBody dialog={d} />
|
|
180
|
+
</ExpenseProvider>
|
|
181
|
+
))
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
3. **The bridge is transparent when not used.** Omit `<DialogContextHost/>` and
|
|
185
|
+
the internal wrapper detects the empty snapshot and renders children
|
|
186
|
+
unchanged. Existing pre-v1.2 projects upgrade with no code changes.
|
|
187
|
+
|
|
188
|
+
4. **`inject` / `deps` still works.** Use it for primitive values, callbacks, or
|
|
189
|
+
anything you do not want as a full React Context. The bridge does not
|
|
190
|
+
replace it.
|
|
191
|
+
|
|
192
|
+
#### Architecture (for the curious)
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
HOST REACT ROOT DIALOG ROOT (per Dialog.show())
|
|
196
|
+
+-----------------------+ +------------------------------+
|
|
197
|
+
| <AppContextProvider> | (internal) ContextProvide |
|
|
198
|
+
| <ThemeProvider> | +- AppContext.Provider |
|
|
199
|
+
| <DialogContextHost | | +- ThemeContext.Pr. |
|
|
200
|
+
| contexts={[X,Y]}/>|-- channel ->| | | +- DialogBase |
|
|
201
|
+
| <Router> | | | +- UserBody |
|
|
202
|
+
| ... | | | useContext(...)
|
|
203
|
+
+-----------------------+ +------------------------------+
|
|
204
|
+
|
|
|
205
|
+
v
|
|
206
|
+
globalDialogBridge (module singleton — pub/sub)
|
|
207
|
+
Host writes here, every open dialog reads from here
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`DialogContextHost` mounts one capture slot per registered Context, each calling
|
|
211
|
+
`useContext` exactly once and writing the value into the singleton channel.
|
|
212
|
+
Internally, every dialog body is wrapped with a Provider component that reads the
|
|
213
|
+
channel via `useSyncExternalStore` and re-supplies each Context as a `<Ctx.Provider>`
|
|
214
|
+
above the body. React's "nearest Provider wins" rule then makes hook-based code
|
|
215
|
+
in the body work transparently.
|
|
216
|
+
|
|
94
217
|
|
|
95
218
|
# Why react-dialogger?
|
|
96
219
|
> react-dialogger is built for React applications that need dynamic, runtime-managed dialogs rather than static JSX-based modals.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DialogContextHost (PUBLIC)
|
|
3
|
+
* --------------------------
|
|
4
|
+
* Mounted ONCE in the host application's main React tree to enable the Context
|
|
5
|
+
* Bridge. Reads the current value of every Context listed in the `contexts` prop
|
|
6
|
+
* and publishes them to the internal pub/sub channel — opt-in dialog bodies
|
|
7
|
+
* automatically consume these values.
|
|
8
|
+
*
|
|
9
|
+
* The host component must be placed BELOW the Providers of every Context it
|
|
10
|
+
* intends to bridge — `useContext(X)` returns the createContext default
|
|
11
|
+
* (typically null) if no `X.Provider` ancestor exists.
|
|
12
|
+
*
|
|
13
|
+
* <AppProvider>
|
|
14
|
+
* <ThemeProvider>
|
|
15
|
+
* <DialogContextHost contexts={[AppContext, ThemeContext]} />
|
|
16
|
+
* <Router>...</Router>
|
|
17
|
+
* </ThemeProvider>
|
|
18
|
+
* </AppProvider>
|
|
19
|
+
*
|
|
20
|
+
* Renders nothing visible. Each bridged Context gets its own `CaptureSlot`
|
|
21
|
+
* subcomponent — this is intentional, so React's hook order stays stable even
|
|
22
|
+
* when the `contexts` prop changes shape (each slot calls `useContext` exactly
|
|
23
|
+
* once on its own).
|
|
24
|
+
*/
|
|
25
|
+
import * as React from "react";
|
|
26
|
+
import { BridgedContext } from "./channel";
|
|
27
|
+
export interface DialogContextHostProps {
|
|
28
|
+
/**
|
|
29
|
+
* Contexts to bridge into every dialog opened by this package.
|
|
30
|
+
*
|
|
31
|
+
* Each Context's Provider must be an ancestor of this `DialogContextHost`
|
|
32
|
+
* element; otherwise the captured value will be the createContext default
|
|
33
|
+
* (typically null), which is harmless but means the bridge effectively does
|
|
34
|
+
* nothing for that Context.
|
|
35
|
+
*/
|
|
36
|
+
contexts: ReadonlyArray<BridgedContext>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Mount this once in the host application's main React tree, beneath every
|
|
40
|
+
* Provider whose Context you want to bridge to dialog bodies.
|
|
41
|
+
*/
|
|
42
|
+
export declare const DialogContextHost: React.FC<DialogContextHostProps>;
|
|
43
|
+
export default DialogContextHost;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DialogContextHost = void 0;
|
|
37
|
+
var jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
|
+
/**
|
|
39
|
+
* DialogContextHost (PUBLIC)
|
|
40
|
+
* --------------------------
|
|
41
|
+
* Mounted ONCE in the host application's main React tree to enable the Context
|
|
42
|
+
* Bridge. Reads the current value of every Context listed in the `contexts` prop
|
|
43
|
+
* and publishes them to the internal pub/sub channel — opt-in dialog bodies
|
|
44
|
+
* automatically consume these values.
|
|
45
|
+
*
|
|
46
|
+
* The host component must be placed BELOW the Providers of every Context it
|
|
47
|
+
* intends to bridge — `useContext(X)` returns the createContext default
|
|
48
|
+
* (typically null) if no `X.Provider` ancestor exists.
|
|
49
|
+
*
|
|
50
|
+
* <AppProvider>
|
|
51
|
+
* <ThemeProvider>
|
|
52
|
+
* <DialogContextHost contexts={[AppContext, ThemeContext]} />
|
|
53
|
+
* <Router>...</Router>
|
|
54
|
+
* </ThemeProvider>
|
|
55
|
+
* </AppProvider>
|
|
56
|
+
*
|
|
57
|
+
* Renders nothing visible. Each bridged Context gets its own `CaptureSlot`
|
|
58
|
+
* subcomponent — this is intentional, so React's hook order stays stable even
|
|
59
|
+
* when the `contexts` prop changes shape (each slot calls `useContext` exactly
|
|
60
|
+
* once on its own).
|
|
61
|
+
*/
|
|
62
|
+
var React = __importStar(require("react"));
|
|
63
|
+
var channel_1 = require("./channel");
|
|
64
|
+
/**
|
|
65
|
+
* Internal: a single capture point for ONE Context. Calls `useContext(ctx)` and
|
|
66
|
+
* writes the value into the channel whenever it changes.
|
|
67
|
+
*
|
|
68
|
+
* We copy-then-mutate-then-publish so concurrent slots do not clobber each
|
|
69
|
+
* other's entries in the shared snapshot.
|
|
70
|
+
*/
|
|
71
|
+
var CaptureSlot = function (_a) {
|
|
72
|
+
var ctx = _a.ctx, channel = _a.channel;
|
|
73
|
+
var value = React.useContext(ctx);
|
|
74
|
+
React.useEffect(function () {
|
|
75
|
+
var next = new Map(channel.getSnapshot());
|
|
76
|
+
next.set(ctx, value);
|
|
77
|
+
channel.setSnapshot(next);
|
|
78
|
+
}, [ctx, value, channel]);
|
|
79
|
+
return null;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Mount this once in the host application's main React tree, beneath every
|
|
83
|
+
* Provider whose Context you want to bridge to dialog bodies.
|
|
84
|
+
*/
|
|
85
|
+
var DialogContextHost = function (_a) {
|
|
86
|
+
var contexts = _a.contexts;
|
|
87
|
+
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: contexts.map(function (Ctx, i) { return ((0, jsx_runtime_1.jsx)(CaptureSlot, { ctx: Ctx, channel: channel_1.globalDialogBridge }, i)); }) }));
|
|
88
|
+
};
|
|
89
|
+
exports.DialogContextHost = DialogContextHost;
|
|
90
|
+
exports.default = exports.DialogContextHost;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DialogContextProvide (INTERNAL)
|
|
3
|
+
* -------------------------------
|
|
4
|
+
* NOT part of the public API. The Dialog model wraps every body with this
|
|
5
|
+
* component automatically — host applications must not import or use it.
|
|
6
|
+
*
|
|
7
|
+
* Subscribes to `globalDialogBridge`, reads the current snapshot, and re-supplies
|
|
8
|
+
* every captured Context as a Provider above `children`. React's nearest-Provider
|
|
9
|
+
* rule means the dialog body sees these values as if it lived inside the host
|
|
10
|
+
* tree.
|
|
11
|
+
*
|
|
12
|
+
* If the channel snapshot is empty (no `<DialogContextHost/>` mounted, or empty
|
|
13
|
+
* `contexts` prop), this component is a transparent pass-through — zero
|
|
14
|
+
* Providers wrapped, zero behavior change. This is the backwards-compatibility
|
|
15
|
+
* guarantee for existing users who do not opt into the bridge.
|
|
16
|
+
*
|
|
17
|
+
* Override semantics: dialog body authors may wrap their own component in an
|
|
18
|
+
* additional `<X.Provider>` to override the bridged value for that Context. The
|
|
19
|
+
* nearest Provider wins. This is the recommended pattern when the bridged value
|
|
20
|
+
* is null (because the source Provider is not mounted in the host tree at the
|
|
21
|
+
* moment the dialog opens).
|
|
22
|
+
*/
|
|
23
|
+
import * as React from "react";
|
|
24
|
+
interface DialogContextProvideProps {
|
|
25
|
+
children: React.ReactNode;
|
|
26
|
+
}
|
|
27
|
+
export declare const DialogContextProvide: React.FC<DialogContextProvideProps>;
|
|
28
|
+
export default DialogContextProvide;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DialogContextProvide = void 0;
|
|
37
|
+
var jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
|
+
/**
|
|
39
|
+
* DialogContextProvide (INTERNAL)
|
|
40
|
+
* -------------------------------
|
|
41
|
+
* NOT part of the public API. The Dialog model wraps every body with this
|
|
42
|
+
* component automatically — host applications must not import or use it.
|
|
43
|
+
*
|
|
44
|
+
* Subscribes to `globalDialogBridge`, reads the current snapshot, and re-supplies
|
|
45
|
+
* every captured Context as a Provider above `children`. React's nearest-Provider
|
|
46
|
+
* rule means the dialog body sees these values as if it lived inside the host
|
|
47
|
+
* tree.
|
|
48
|
+
*
|
|
49
|
+
* If the channel snapshot is empty (no `<DialogContextHost/>` mounted, or empty
|
|
50
|
+
* `contexts` prop), this component is a transparent pass-through — zero
|
|
51
|
+
* Providers wrapped, zero behavior change. This is the backwards-compatibility
|
|
52
|
+
* guarantee for existing users who do not opt into the bridge.
|
|
53
|
+
*
|
|
54
|
+
* Override semantics: dialog body authors may wrap their own component in an
|
|
55
|
+
* additional `<X.Provider>` to override the bridged value for that Context. The
|
|
56
|
+
* nearest Provider wins. This is the recommended pattern when the bridged value
|
|
57
|
+
* is null (because the source Provider is not mounted in the host tree at the
|
|
58
|
+
* moment the dialog opens).
|
|
59
|
+
*/
|
|
60
|
+
var React = __importStar(require("react"));
|
|
61
|
+
var channel_1 = require("./channel");
|
|
62
|
+
var DialogContextProvide = function (_a) {
|
|
63
|
+
var children = _a.children;
|
|
64
|
+
var subscribe = React.useCallback(function (cb) { return channel_1.globalDialogBridge.subscribe(cb); }, []);
|
|
65
|
+
var getSnapshot = React.useCallback(function () { return channel_1.globalDialogBridge.getSnapshot(); }, []);
|
|
66
|
+
// useSyncExternalStore is React 18+. The package's peerDependencies already
|
|
67
|
+
// pin `react: ^18.2.0`, so this is safe.
|
|
68
|
+
var snapshot = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
69
|
+
// Map iteration order follows insertion order; the FIRST inserted Context
|
|
70
|
+
// becomes the OUTERMOST Provider, the LAST inserted is nearest to children.
|
|
71
|
+
// For host-body overrides (a body wrapping its own <X.Provider>), the body's
|
|
72
|
+
// wrap is even closer to the consumer than this layer, so it wins regardless
|
|
73
|
+
// of which slot inserted X here.
|
|
74
|
+
var node = children;
|
|
75
|
+
snapshot.forEach(function (value, Ctx) {
|
|
76
|
+
node = (0, jsx_runtime_1.jsx)(Ctx.Provider, { value: value, children: node });
|
|
77
|
+
});
|
|
78
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: node });
|
|
79
|
+
};
|
|
80
|
+
exports.DialogContextProvide = DialogContextProvide;
|
|
81
|
+
exports.default = exports.DialogContextProvide;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DialogBridgeChannel
|
|
3
|
+
* -------------------
|
|
4
|
+
* Internal pub/sub channel used by the Context Bridge.
|
|
5
|
+
*
|
|
6
|
+
* react-dialogger mounts each dialog into its OWN React root via `createRoot(...)`,
|
|
7
|
+
* which makes it impossible for the dialog body to consume Context values from the
|
|
8
|
+
* host application's tree using `useContext(...)`.
|
|
9
|
+
*
|
|
10
|
+
* The Context Bridge solves this by:
|
|
11
|
+
* 1. Letting the host app declare a list of Contexts to bridge by mounting
|
|
12
|
+
* `<DialogContextHost contexts={[...]}/>` once in the main tree.
|
|
13
|
+
* 2. Capturing the value of each registered Context in the main tree and
|
|
14
|
+
* pushing it into this singleton channel.
|
|
15
|
+
* 3. Internally wrapping every dialog body with `<DialogContextProvide/>` which
|
|
16
|
+
* reads the channel and re-supplies each Context as a Provider in the dialog
|
|
17
|
+
* tree.
|
|
18
|
+
*
|
|
19
|
+
* Public API exposed via the package entry point: only `DialogContextHost`.
|
|
20
|
+
* This channel and `DialogContextProvide` are internal — host applications should
|
|
21
|
+
* never reach into them directly.
|
|
22
|
+
*/
|
|
23
|
+
import * as React from "react";
|
|
24
|
+
/** A React Context whose value can be anything. The bridge is value-agnostic. */
|
|
25
|
+
export type BridgedContext = React.Context<any>;
|
|
26
|
+
/** A snapshot of all bridged Contexts' current values. */
|
|
27
|
+
export type ContextSnapshot = Map<BridgedContext, any>;
|
|
28
|
+
/**
|
|
29
|
+
* Pub/sub channel between the host capture component and per-dialog provide.
|
|
30
|
+
*
|
|
31
|
+
* One module-level singleton (`globalDialogBridge`) is shared across the entire
|
|
32
|
+
* application — host capture writes, every open dialog subscribes. This is safe
|
|
33
|
+
* because there is only ever one main React tree per application; if you have
|
|
34
|
+
* multiple isolated apps on a single page, they should each ship their own copy
|
|
35
|
+
* of this module via independent bundles.
|
|
36
|
+
*/
|
|
37
|
+
export declare class DialogBridgeChannel {
|
|
38
|
+
private snapshot;
|
|
39
|
+
private listeners;
|
|
40
|
+
/** Replace the current snapshot and notify every subscriber. */
|
|
41
|
+
setSnapshot(next: ContextSnapshot): void;
|
|
42
|
+
/** Read the current snapshot. Safe to call from any tree. */
|
|
43
|
+
getSnapshot(): ContextSnapshot;
|
|
44
|
+
/** Subscribe to snapshot changes. Returns an unsubscribe function. */
|
|
45
|
+
subscribe(listener: () => void): () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The single bridge channel. Capture (in host tree) writes; Provide (in every
|
|
49
|
+
* dialog tree) reads. Never re-instantiate — import this exact reference.
|
|
50
|
+
*/
|
|
51
|
+
export declare const globalDialogBridge: DialogBridgeChannel;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DialogBridgeChannel
|
|
4
|
+
* -------------------
|
|
5
|
+
* Internal pub/sub channel used by the Context Bridge.
|
|
6
|
+
*
|
|
7
|
+
* react-dialogger mounts each dialog into its OWN React root via `createRoot(...)`,
|
|
8
|
+
* which makes it impossible for the dialog body to consume Context values from the
|
|
9
|
+
* host application's tree using `useContext(...)`.
|
|
10
|
+
*
|
|
11
|
+
* The Context Bridge solves this by:
|
|
12
|
+
* 1. Letting the host app declare a list of Contexts to bridge by mounting
|
|
13
|
+
* `<DialogContextHost contexts={[...]}/>` once in the main tree.
|
|
14
|
+
* 2. Capturing the value of each registered Context in the main tree and
|
|
15
|
+
* pushing it into this singleton channel.
|
|
16
|
+
* 3. Internally wrapping every dialog body with `<DialogContextProvide/>` which
|
|
17
|
+
* reads the channel and re-supplies each Context as a Provider in the dialog
|
|
18
|
+
* tree.
|
|
19
|
+
*
|
|
20
|
+
* Public API exposed via the package entry point: only `DialogContextHost`.
|
|
21
|
+
* This channel and `DialogContextProvide` are internal — host applications should
|
|
22
|
+
* never reach into them directly.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.globalDialogBridge = exports.DialogBridgeChannel = void 0;
|
|
26
|
+
/**
|
|
27
|
+
* Pub/sub channel between the host capture component and per-dialog provide.
|
|
28
|
+
*
|
|
29
|
+
* One module-level singleton (`globalDialogBridge`) is shared across the entire
|
|
30
|
+
* application — host capture writes, every open dialog subscribes. This is safe
|
|
31
|
+
* because there is only ever one main React tree per application; if you have
|
|
32
|
+
* multiple isolated apps on a single page, they should each ship their own copy
|
|
33
|
+
* of this module via independent bundles.
|
|
34
|
+
*/
|
|
35
|
+
var DialogBridgeChannel = /** @class */ (function () {
|
|
36
|
+
function DialogBridgeChannel() {
|
|
37
|
+
this.snapshot = new Map();
|
|
38
|
+
this.listeners = new Set();
|
|
39
|
+
}
|
|
40
|
+
/** Replace the current snapshot and notify every subscriber. */
|
|
41
|
+
DialogBridgeChannel.prototype.setSnapshot = function (next) {
|
|
42
|
+
this.snapshot = next;
|
|
43
|
+
this.listeners.forEach(function (l) { return l(); });
|
|
44
|
+
};
|
|
45
|
+
/** Read the current snapshot. Safe to call from any tree. */
|
|
46
|
+
DialogBridgeChannel.prototype.getSnapshot = function () {
|
|
47
|
+
return this.snapshot;
|
|
48
|
+
};
|
|
49
|
+
/** Subscribe to snapshot changes. Returns an unsubscribe function. */
|
|
50
|
+
DialogBridgeChannel.prototype.subscribe = function (listener) {
|
|
51
|
+
var _this = this;
|
|
52
|
+
this.listeners.add(listener);
|
|
53
|
+
return function () {
|
|
54
|
+
_this.listeners.delete(listener);
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
return DialogBridgeChannel;
|
|
58
|
+
}());
|
|
59
|
+
exports.DialogBridgeChannel = DialogBridgeChannel;
|
|
60
|
+
/**
|
|
61
|
+
* The single bridge channel. Capture (in host tree) writes; Provide (in every
|
|
62
|
+
* dialog tree) reads. Never re-instantiate — import this exact reference.
|
|
63
|
+
*/
|
|
64
|
+
exports.globalDialogBridge = new DialogBridgeChannel();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge module — internal barrel.
|
|
3
|
+
*
|
|
4
|
+
* Only `DialogContextHost` is re-exported from the package root (`src/index.tsx`).
|
|
5
|
+
* `DialogContextProvide`, `globalDialogBridge`, and the channel class are
|
|
6
|
+
* intentionally internal — they are used by the Dialog model to wrap each
|
|
7
|
+
* dialog body, but host applications should never import them directly.
|
|
8
|
+
*/
|
|
9
|
+
export { DialogContextHost } from "./DialogContextHost";
|
|
10
|
+
export type { DialogContextHostProps } from "./DialogContextHost";
|
|
11
|
+
export { DialogContextProvide } from "./DialogContextProvide";
|
|
12
|
+
export type { BridgedContext, ContextSnapshot } from "./channel";
|
|
13
|
+
export { DialogBridgeChannel, globalDialogBridge } from "./channel";
|
package/bridge/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Bridge module — internal barrel.
|
|
4
|
+
*
|
|
5
|
+
* Only `DialogContextHost` is re-exported from the package root (`src/index.tsx`).
|
|
6
|
+
* `DialogContextProvide`, `globalDialogBridge`, and the channel class are
|
|
7
|
+
* intentionally internal — they are used by the Dialog model to wrap each
|
|
8
|
+
* dialog body, but host applications should never import them directly.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.globalDialogBridge = exports.DialogBridgeChannel = exports.DialogContextProvide = exports.DialogContextHost = void 0;
|
|
12
|
+
var DialogContextHost_1 = require("./DialogContextHost");
|
|
13
|
+
Object.defineProperty(exports, "DialogContextHost", { enumerable: true, get: function () { return DialogContextHost_1.DialogContextHost; } });
|
|
14
|
+
// Internal — consumed by `models/Dialog.tsx` only.
|
|
15
|
+
var DialogContextProvide_1 = require("./DialogContextProvide");
|
|
16
|
+
Object.defineProperty(exports, "DialogContextProvide", { enumerable: true, get: function () { return DialogContextProvide_1.DialogContextProvide; } });
|
|
17
|
+
var channel_1 = require("./channel");
|
|
18
|
+
Object.defineProperty(exports, "DialogBridgeChannel", { enumerable: true, get: function () { return channel_1.DialogBridgeChannel; } });
|
|
19
|
+
Object.defineProperty(exports, "globalDialogBridge", { enumerable: true, get: function () { return channel_1.globalDialogBridge; } });
|
package/index.d.ts
CHANGED
|
@@ -7,3 +7,5 @@ export { DialogProcessing, DialogCloseAction, DialogFullscreenAction, DialogActi
|
|
|
7
7
|
export { DialogAction, ActionProgress, baseDialogOptions } from "./models";
|
|
8
8
|
export { InitDialogMemoizeBounds } from "./helpers";
|
|
9
9
|
export { IDialogApiDef, ActionApiDef } from './types';
|
|
10
|
+
export { DialogContextHost } from "./bridge";
|
|
11
|
+
export type { DialogContextHostProps } from "./bridge";
|
package/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.InitDialogMemoizeBounds = exports.baseDialogOptions = exports.ActionProgress = exports.DialogAction = exports.DialogInfoAction = exports.DialogActionsWrapper = exports.DialogFullscreenAction = exports.DialogCloseAction = exports.DialogProcessing = exports.default = void 0;
|
|
6
|
+
exports.DialogContextHost = exports.InitDialogMemoizeBounds = exports.baseDialogOptions = exports.ActionProgress = exports.DialogAction = exports.DialogInfoAction = exports.DialogActionsWrapper = exports.DialogFullscreenAction = exports.DialogCloseAction = exports.DialogProcessing = exports.default = void 0;
|
|
7
7
|
require("./styles/dialog.css");
|
|
8
8
|
require("./styles/dialog.action.css");
|
|
9
9
|
require("./styles/circular-progress.css");
|
|
@@ -24,3 +24,9 @@ Object.defineProperty(exports, "baseDialogOptions", { enumerable: true, get: fun
|
|
|
24
24
|
// Hooks
|
|
25
25
|
var helpers_1 = require("./helpers");
|
|
26
26
|
Object.defineProperty(exports, "InitDialogMemoizeBounds", { enumerable: true, get: function () { return helpers_1.InitDialogMemoizeBounds; } });
|
|
27
|
+
// Context Bridge — opt-in mechanism that lets dialog bodies consume Contexts
|
|
28
|
+
// from the host application's main React tree without manual inject/deps.
|
|
29
|
+
// Mount <DialogContextHost contexts={[...]}/> once in the main tree; nothing
|
|
30
|
+
// else is needed at dialog call sites. See ./bridge/* and README for details.
|
|
31
|
+
var bridge_1 = require("./bridge");
|
|
32
|
+
Object.defineProperty(exports, "DialogContextHost", { enumerable: true, get: function () { return bridge_1.DialogContextHost; } });
|
package/models/Dialog.js
CHANGED
|
@@ -51,6 +51,10 @@ var React = __importStar(require("react"));
|
|
|
51
51
|
var DialogBase_1 = __importDefault(require("../components/DialogBase"));
|
|
52
52
|
var client_1 = require("react-dom/client");
|
|
53
53
|
var appLogger_1 = require("../helpers/appLogger");
|
|
54
|
+
// Internal Context Bridge wrapper. Wraps every dialog body so that Contexts
|
|
55
|
+
// registered via <DialogContextHost/> in the host tree flow into the dialog
|
|
56
|
+
// tree. No behavior change when no host is mounted (empty snapshot → no-op).
|
|
57
|
+
var bridge_1 = require("../bridge");
|
|
54
58
|
var Dialog = /** @class */ (function () {
|
|
55
59
|
// private _initialValues :Dialogify<V>;
|
|
56
60
|
function Dialog(dialogRef, options, _initialValues, _inject) {
|
|
@@ -240,10 +244,20 @@ var Dialog = /** @class */ (function () {
|
|
|
240
244
|
// >, props ) );
|
|
241
245
|
// root.render( React.createElement( DialogBase as React.ComponentType<BaseDialogProps<Values, I>>, props ) );
|
|
242
246
|
var element = React.createElement(DialogBase_1.default, props);
|
|
247
|
+
// Internal Context Bridge: every dialog body is wrapped so that Contexts
|
|
248
|
+
// registered by the host application via <DialogContextHost/> flow into
|
|
249
|
+
// the dialog's React root. If no host is mounted (or its contexts list
|
|
250
|
+
// is empty), the snapshot is empty and DialogContextProvide passes
|
|
251
|
+
// children through untouched — zero behavior change for existing users
|
|
252
|
+
// who do not opt into the bridge.
|
|
253
|
+
var bridged = React.createElement(bridge_1.DialogContextProvide, null, element);
|
|
243
254
|
// Wrapper varsa sar, yoksa direkt render
|
|
255
|
+
// Sıralama: globalWrapper (kullanıcı tanımlı) en dışta, ardından bridge,
|
|
256
|
+
// en içte gerçek dialog ağacı. Bu sayede globalWrapper'ın eklediği
|
|
257
|
+
// Provider'ları bridge override edebilir (en yakın Provider kuralı).
|
|
244
258
|
root.render(Dialog._globalWrapper
|
|
245
|
-
? React.createElement(Dialog._globalWrapper, null,
|
|
246
|
-
:
|
|
259
|
+
? React.createElement(Dialog._globalWrapper, null, bridged)
|
|
260
|
+
: bridged);
|
|
247
261
|
};
|
|
248
262
|
return Dialog;
|
|
249
263
|
}());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-dialogger",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "This package is a continuation of the react-araci package. Due to an error, react-araci was removed, and it has been decided to continue under the new package name react-dialogger",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Sueleyman Topaloglu",
|