@matrix-widget-toolkit/react 1.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 ADDED
@@ -0,0 +1,39 @@
1
+ # `@matrix-widget-toolkit/react`
2
+
3
+ This is package that provides a Widget API intergration for React apps.
4
+
5
+ ## Usage
6
+
7
+ Install it with:
8
+
9
+ ```bash
10
+ yarn add @matrix-widget-toolkit/react
11
+ ```
12
+
13
+ ### Providing the Widget API to React components
14
+
15
+ While this package contains a `<WidgetApiProvider>` you probably don't want to use this package most of the time.
16
+ Prefer using [`@matrix-widget-toolkit/mui`](../mui/) or [`@matrix-widget-toolkit/semantic-ui`](../semantic-ui/) which internally use this package to share functionality.
17
+
18
+ ### Acessing the Widget API
19
+
20
+ Once the Widget API is provided to React components, use the `useWidgetApi` hook to access it:
21
+
22
+ ```typescript
23
+ import { useWidgetApi } from '@matrix-widget-toolkit/react';
24
+
25
+ const widgetApi = useWidgetApi();
26
+ ```
27
+
28
+ ### Mocking the Widget API
29
+
30
+ Most of the time you will use `<MuiWidgetApiProvider>` to initialize and provide the `WidgetApi` to your react components.
31
+ However, if you want to mock it in tests, you can use `<WidgetApiMockProvider>` to provide a mocked version:
32
+
33
+ ```tsx
34
+ import { WidgetApiMockProvider } from '@matrix-widget-toolkit/react';
35
+
36
+ <WidgetApiMockProvider value={widgetApi}>
37
+ /* Your child components */
38
+ </WidgetApiMockProvider>;
39
+ ```
@@ -0,0 +1,236 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var reactUse = require('react-use');
5
+ var react = require('react');
6
+ var api = require('@matrix-widget-toolkit/api');
7
+ var reactErrorBoundary = require('react-error-boundary');
8
+
9
+ /*
10
+ * Copyright 2022 Nordeck IT + Consulting GmbH
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ var WidgetApiContext = react.createContext(undefined);
25
+ /**
26
+ * Hook for accessing the widget API.
27
+ *
28
+ * @remarks Can only be called inside a `WidgetApiProvider`
29
+ * (or WidgetApiMockProvider in tests).
30
+ *
31
+ * @returns A fully initialized widget API.
32
+ */
33
+ var useWidgetApi = function () {
34
+ var context = react.useContext(WidgetApiContext);
35
+ if (context === undefined) {
36
+ throw new Error('useWidgetApi must be used within a WidgetApiProvider (or WidgetApiMockProvider in tests)');
37
+ }
38
+ return context;
39
+ };
40
+ /**
41
+ * Provides a custom instance of the `WidgetApi` to the context.
42
+ *
43
+ * @remarks Should only be used in tests.
44
+ */
45
+ var WidgetApiMockProvider = WidgetApiContext.Provider;
46
+
47
+ var __assign$1 = (undefined && undefined.__assign) || function () {
48
+ __assign$1 = Object.assign || function(t) {
49
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
50
+ s = arguments[i];
51
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
52
+ t[p] = s[p];
53
+ }
54
+ return t;
55
+ };
56
+ return __assign$1.apply(this, arguments);
57
+ };
58
+ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
59
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
60
+ return new (P || (P = Promise))(function (resolve, reject) {
61
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
62
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
63
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
64
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
65
+ });
66
+ };
67
+ var __generator = (undefined && undefined.__generator) || function (thisArg, body) {
68
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
69
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
70
+ function verb(n) { return function (v) { return step([n, v]); }; }
71
+ function step(op) {
72
+ if (f) throw new TypeError("Generator is already executing.");
73
+ while (_) try {
74
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
75
+ if (y = 0, t) op = [op[0] & 2, t.value];
76
+ switch (op[0]) {
77
+ case 0: case 1: t = op; break;
78
+ case 4: _.label++; return { value: op[1], done: false };
79
+ case 5: _.label++; y = op[1]; op = [0]; continue;
80
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
81
+ default:
82
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
83
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
84
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
85
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
86
+ if (t[2]) _.ops.pop();
87
+ _.trys.pop(); continue;
88
+ }
89
+ op = body.call(thisArg, _);
90
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
91
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
92
+ }
93
+ };
94
+ /**
95
+ * Provides the `WidgetApi` in the React context once it's fully
96
+ * initialized without errors.
97
+ * Use {@link useWidgetApi} to access it.
98
+ * @param param0 - {@link WidgetApiProviderProps}
99
+ */
100
+ function WidgetApiProvider(_a) {
101
+ var _this = this;
102
+ var children = _a.children, widgetApiPromise = _a.widgetApiPromise, widgetRegistration = _a.widgetRegistration, LoadingViewComponent = _a.loadingViewComponent, MobileClientErrorComponent = _a.mobileClientErrorComponent, OutsideClientErrorComponent = _a.outsideClientErrorComponent, ChildErrorComponent = _a.childErrorComponent, MissingCapabilitiesComponent = _a.missingCapabilitiesComponent, MissingParametersErrorComponent = _a.missingParametersErrorComponent;
103
+ var isOpenedByClient = react.useMemo(function () { return api.extractWidgetParameters(); }, []).isOpenedByClient;
104
+ var _b = react.useState(), hasInitalCapabilitiesGranted = _b[0], setInitialCapabilitiesGranted = _b[1];
105
+ var _c = reactUse.useAsyncFn(function (widgetApi) { return __awaiter(_this, void 0, void 0, function () {
106
+ return __generator(this, function (_a) {
107
+ switch (_a.label) {
108
+ case 0:
109
+ _a.trys.push([0, 2, , 3]);
110
+ return [4 /*yield*/, widgetApi.rerequestInitialCapabilities()];
111
+ case 1:
112
+ _a.sent();
113
+ setInitialCapabilitiesGranted(true);
114
+ return [3 /*break*/, 3];
115
+ case 2:
116
+ _a.sent();
117
+ setInitialCapabilitiesGranted(false);
118
+ return [3 /*break*/, 3];
119
+ case 3: return [2 /*return*/];
120
+ }
121
+ });
122
+ }); }, []), rerequestInitialCapabilities = _c[1];
123
+ var _d = reactUse.useAsync(function () { return __awaiter(_this, void 0, void 0, function () {
124
+ var widgetApi;
125
+ return __generator(this, function (_a) {
126
+ switch (_a.label) {
127
+ case 0: return [4 /*yield*/, widgetApiPromise];
128
+ case 1:
129
+ widgetApi = _a.sent();
130
+ setInitialCapabilitiesGranted(widgetApi.hasInitialCapabilities());
131
+ return [2 /*return*/, widgetApi];
132
+ }
133
+ });
134
+ }); }, [widgetApiPromise]), widgetApi = _d.value, error = _d.error, loading = _d.loading;
135
+ if (loading) {
136
+ return jsxRuntime.jsx(LoadingViewComponent, {});
137
+ }
138
+ if (error || !widgetApi) {
139
+ if (isOpenedByClient) {
140
+ return jsxRuntime.jsx(MobileClientErrorComponent, {});
141
+ }
142
+ else {
143
+ return jsxRuntime.jsx(OutsideClientErrorComponent, {});
144
+ }
145
+ }
146
+ if (!hasInitalCapabilitiesGranted) {
147
+ return (jsxRuntime.jsx(MissingCapabilitiesComponent, { onRetry: function () { return rerequestInitialCapabilities(widgetApi); } }));
148
+ }
149
+ var hasParameters = api.hasRequiredWidgetParameters(widgetApi);
150
+ return (jsxRuntime.jsx(WidgetApiContext.Provider, __assign$1({ value: widgetApi }, { children: hasParameters ? (jsxRuntime.jsx(reactErrorBoundary.ErrorBoundary, __assign$1({ FallbackComponent: ChildErrorComponent }, { children: children }))) : (jsxRuntime.jsx(MissingParametersErrorComponent, { widgetRegistration: widgetRegistration })) })));
151
+ }
152
+
153
+ /**
154
+ * A guard that ask the user for capabilities and only shows the `children`
155
+ * if all capabilities were accepted.
156
+ * If capabilities are denined, a message and a button to retry is displayed
157
+ * instead.
158
+ * @param param0 - {@link CapabilitiesGuardProps}
159
+ */
160
+ function CapabilitiesGuard(_a) {
161
+ var capabilities = _a.capabilities, children = _a.children, MissingCapabilitiesComponent = _a.missingCapabilitiesComponent, LoadingComponent = _a.loadingComponent;
162
+ var widgetApi = useWidgetApi();
163
+ var _b = reactUse.useAsyncRetry(function () { return widgetApi.requestCapabilities(capabilities); }, [widgetApi, capabilities]), loading = _b.loading, error = _b.error, requestCapabilities = _b.retry;
164
+ if (loading) {
165
+ return jsxRuntime.jsx(LoadingComponent, {});
166
+ }
167
+ if (error) {
168
+ return jsxRuntime.jsx(MissingCapabilitiesComponent, { onRetry: requestCapabilities });
169
+ }
170
+ return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children });
171
+ }
172
+
173
+ var __assign = (undefined && undefined.__assign) || function () {
174
+ __assign = Object.assign || function(t) {
175
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
176
+ s = arguments[i];
177
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
178
+ t[p] = s[p];
179
+ }
180
+ return t;
181
+ };
182
+ return __assign.apply(this, arguments);
183
+ };
184
+ var ThemeSelectionContext = react.createContext(undefined);
185
+ /**
186
+ * Hook for accessing the current theme selection.
187
+ * @returns The current theme selection.
188
+ */
189
+ var useThemeSelection = function () {
190
+ var context = react.useContext(ThemeSelectionContext);
191
+ if (context === undefined) {
192
+ throw new Error('useThemeSelection must be used within a ThemeSelectionProvider');
193
+ }
194
+ return context;
195
+ };
196
+ /**
197
+ * Provides the current theme selection to child components.
198
+ * Use the {@link useThemeSelection} hook to access it.
199
+ * @param param0 - {@link ThemeSelectionProviderProps}
200
+ */
201
+ function ThemeSelectionProvider(_a) {
202
+ var children = _a.children;
203
+ var _b = react.useState(function () {
204
+ var widgetId = '';
205
+ try {
206
+ (widgetId = api.extractWidgetApiParameters().widgetId);
207
+ }
208
+ catch (e) {
209
+ // ignore
210
+ }
211
+ var isModal = api.parseWidgetId(widgetId).isModal;
212
+ var theme = api.extractWidgetParameters().theme;
213
+ if (theme) {
214
+ return { theme: theme, isModal: isModal };
215
+ }
216
+ var prefersColorSchemeDark = window.matchMedia &&
217
+ window.matchMedia('(prefers-color-scheme: dark)').matches;
218
+ return { theme: prefersColorSchemeDark ? 'dark' : 'light', isModal: isModal };
219
+ }), _c = _b[0], theme = _c.theme, isModal = _c.isModal, setState = _b[1];
220
+ var setTheme = react.useCallback(function (theme) {
221
+ setState(function (old) { return (__assign(__assign({}, old), { theme: theme })); });
222
+ }, []);
223
+ var context = react.useMemo(function () { return ({
224
+ theme: theme,
225
+ isModal: isModal,
226
+ setTheme: setTheme,
227
+ }); }, [isModal, setTheme, theme]);
228
+ return (jsxRuntime.jsx(ThemeSelectionContext.Provider, __assign({ value: context }, { children: children })));
229
+ }
230
+
231
+ exports.CapabilitiesGuard = CapabilitiesGuard;
232
+ exports.ThemeSelectionProvider = ThemeSelectionProvider;
233
+ exports.WidgetApiMockProvider = WidgetApiMockProvider;
234
+ exports.WidgetApiProvider = WidgetApiProvider;
235
+ exports.useThemeSelection = useThemeSelection;
236
+ exports.useWidgetApi = useWidgetApi;
@@ -0,0 +1,229 @@
1
+ import { jsx, Fragment } from 'react/jsx-runtime';
2
+ import { useAsyncFn, useAsync, useAsyncRetry } from 'react-use';
3
+ import { createContext, useContext, useMemo, useState, useCallback } from 'react';
4
+ import { extractWidgetParameters, hasRequiredWidgetParameters, extractWidgetApiParameters, parseWidgetId } from '@matrix-widget-toolkit/api';
5
+ import { ErrorBoundary } from 'react-error-boundary';
6
+
7
+ /*
8
+ * Copyright 2022 Nordeck IT + Consulting GmbH
9
+ *
10
+ * Licensed under the Apache License, Version 2.0 (the "License");
11
+ * you may not use this file except in compliance with the License.
12
+ * You may obtain a copy of the License at
13
+ *
14
+ * http://www.apache.org/licenses/LICENSE-2.0
15
+ *
16
+ * Unless required by applicable law or agreed to in writing, software
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ * See the License for the specific language governing permissions and
20
+ * limitations under the License.
21
+ */
22
+ var WidgetApiContext = createContext(undefined);
23
+ /**
24
+ * Hook for accessing the widget API.
25
+ *
26
+ * @remarks Can only be called inside a `WidgetApiProvider`
27
+ * (or WidgetApiMockProvider in tests).
28
+ *
29
+ * @returns A fully initialized widget API.
30
+ */
31
+ var useWidgetApi = function () {
32
+ var context = useContext(WidgetApiContext);
33
+ if (context === undefined) {
34
+ throw new Error('useWidgetApi must be used within a WidgetApiProvider (or WidgetApiMockProvider in tests)');
35
+ }
36
+ return context;
37
+ };
38
+ /**
39
+ * Provides a custom instance of the `WidgetApi` to the context.
40
+ *
41
+ * @remarks Should only be used in tests.
42
+ */
43
+ var WidgetApiMockProvider = WidgetApiContext.Provider;
44
+
45
+ var __assign$1 = (undefined && undefined.__assign) || function () {
46
+ __assign$1 = Object.assign || function(t) {
47
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
48
+ s = arguments[i];
49
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
50
+ t[p] = s[p];
51
+ }
52
+ return t;
53
+ };
54
+ return __assign$1.apply(this, arguments);
55
+ };
56
+ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
57
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
58
+ return new (P || (P = Promise))(function (resolve, reject) {
59
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
60
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
61
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
62
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
63
+ });
64
+ };
65
+ var __generator = (undefined && undefined.__generator) || function (thisArg, body) {
66
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
67
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
68
+ function verb(n) { return function (v) { return step([n, v]); }; }
69
+ function step(op) {
70
+ if (f) throw new TypeError("Generator is already executing.");
71
+ while (_) try {
72
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
73
+ if (y = 0, t) op = [op[0] & 2, t.value];
74
+ switch (op[0]) {
75
+ case 0: case 1: t = op; break;
76
+ case 4: _.label++; return { value: op[1], done: false };
77
+ case 5: _.label++; y = op[1]; op = [0]; continue;
78
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
79
+ default:
80
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
81
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
82
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
83
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
84
+ if (t[2]) _.ops.pop();
85
+ _.trys.pop(); continue;
86
+ }
87
+ op = body.call(thisArg, _);
88
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
89
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
90
+ }
91
+ };
92
+ /**
93
+ * Provides the `WidgetApi` in the React context once it's fully
94
+ * initialized without errors.
95
+ * Use {@link useWidgetApi} to access it.
96
+ * @param param0 - {@link WidgetApiProviderProps}
97
+ */
98
+ function WidgetApiProvider(_a) {
99
+ var _this = this;
100
+ var children = _a.children, widgetApiPromise = _a.widgetApiPromise, widgetRegistration = _a.widgetRegistration, LoadingViewComponent = _a.loadingViewComponent, MobileClientErrorComponent = _a.mobileClientErrorComponent, OutsideClientErrorComponent = _a.outsideClientErrorComponent, ChildErrorComponent = _a.childErrorComponent, MissingCapabilitiesComponent = _a.missingCapabilitiesComponent, MissingParametersErrorComponent = _a.missingParametersErrorComponent;
101
+ var isOpenedByClient = useMemo(function () { return extractWidgetParameters(); }, []).isOpenedByClient;
102
+ var _b = useState(), hasInitalCapabilitiesGranted = _b[0], setInitialCapabilitiesGranted = _b[1];
103
+ var _c = useAsyncFn(function (widgetApi) { return __awaiter(_this, void 0, void 0, function () {
104
+ return __generator(this, function (_a) {
105
+ switch (_a.label) {
106
+ case 0:
107
+ _a.trys.push([0, 2, , 3]);
108
+ return [4 /*yield*/, widgetApi.rerequestInitialCapabilities()];
109
+ case 1:
110
+ _a.sent();
111
+ setInitialCapabilitiesGranted(true);
112
+ return [3 /*break*/, 3];
113
+ case 2:
114
+ _a.sent();
115
+ setInitialCapabilitiesGranted(false);
116
+ return [3 /*break*/, 3];
117
+ case 3: return [2 /*return*/];
118
+ }
119
+ });
120
+ }); }, []), rerequestInitialCapabilities = _c[1];
121
+ var _d = useAsync(function () { return __awaiter(_this, void 0, void 0, function () {
122
+ var widgetApi;
123
+ return __generator(this, function (_a) {
124
+ switch (_a.label) {
125
+ case 0: return [4 /*yield*/, widgetApiPromise];
126
+ case 1:
127
+ widgetApi = _a.sent();
128
+ setInitialCapabilitiesGranted(widgetApi.hasInitialCapabilities());
129
+ return [2 /*return*/, widgetApi];
130
+ }
131
+ });
132
+ }); }, [widgetApiPromise]), widgetApi = _d.value, error = _d.error, loading = _d.loading;
133
+ if (loading) {
134
+ return jsx(LoadingViewComponent, {});
135
+ }
136
+ if (error || !widgetApi) {
137
+ if (isOpenedByClient) {
138
+ return jsx(MobileClientErrorComponent, {});
139
+ }
140
+ else {
141
+ return jsx(OutsideClientErrorComponent, {});
142
+ }
143
+ }
144
+ if (!hasInitalCapabilitiesGranted) {
145
+ return (jsx(MissingCapabilitiesComponent, { onRetry: function () { return rerequestInitialCapabilities(widgetApi); } }));
146
+ }
147
+ var hasParameters = hasRequiredWidgetParameters(widgetApi);
148
+ return (jsx(WidgetApiContext.Provider, __assign$1({ value: widgetApi }, { children: hasParameters ? (jsx(ErrorBoundary, __assign$1({ FallbackComponent: ChildErrorComponent }, { children: children }))) : (jsx(MissingParametersErrorComponent, { widgetRegistration: widgetRegistration })) })));
149
+ }
150
+
151
+ /**
152
+ * A guard that ask the user for capabilities and only shows the `children`
153
+ * if all capabilities were accepted.
154
+ * If capabilities are denined, a message and a button to retry is displayed
155
+ * instead.
156
+ * @param param0 - {@link CapabilitiesGuardProps}
157
+ */
158
+ function CapabilitiesGuard(_a) {
159
+ var capabilities = _a.capabilities, children = _a.children, MissingCapabilitiesComponent = _a.missingCapabilitiesComponent, LoadingComponent = _a.loadingComponent;
160
+ var widgetApi = useWidgetApi();
161
+ var _b = useAsyncRetry(function () { return widgetApi.requestCapabilities(capabilities); }, [widgetApi, capabilities]), loading = _b.loading, error = _b.error, requestCapabilities = _b.retry;
162
+ if (loading) {
163
+ return jsx(LoadingComponent, {});
164
+ }
165
+ if (error) {
166
+ return jsx(MissingCapabilitiesComponent, { onRetry: requestCapabilities });
167
+ }
168
+ return jsx(Fragment, { children: children });
169
+ }
170
+
171
+ var __assign = (undefined && undefined.__assign) || function () {
172
+ __assign = Object.assign || function(t) {
173
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
174
+ s = arguments[i];
175
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
176
+ t[p] = s[p];
177
+ }
178
+ return t;
179
+ };
180
+ return __assign.apply(this, arguments);
181
+ };
182
+ var ThemeSelectionContext = createContext(undefined);
183
+ /**
184
+ * Hook for accessing the current theme selection.
185
+ * @returns The current theme selection.
186
+ */
187
+ var useThemeSelection = function () {
188
+ var context = useContext(ThemeSelectionContext);
189
+ if (context === undefined) {
190
+ throw new Error('useThemeSelection must be used within a ThemeSelectionProvider');
191
+ }
192
+ return context;
193
+ };
194
+ /**
195
+ * Provides the current theme selection to child components.
196
+ * Use the {@link useThemeSelection} hook to access it.
197
+ * @param param0 - {@link ThemeSelectionProviderProps}
198
+ */
199
+ function ThemeSelectionProvider(_a) {
200
+ var children = _a.children;
201
+ var _b = useState(function () {
202
+ var widgetId = '';
203
+ try {
204
+ (widgetId = extractWidgetApiParameters().widgetId);
205
+ }
206
+ catch (e) {
207
+ // ignore
208
+ }
209
+ var isModal = parseWidgetId(widgetId).isModal;
210
+ var theme = extractWidgetParameters().theme;
211
+ if (theme) {
212
+ return { theme: theme, isModal: isModal };
213
+ }
214
+ var prefersColorSchemeDark = window.matchMedia &&
215
+ window.matchMedia('(prefers-color-scheme: dark)').matches;
216
+ return { theme: prefersColorSchemeDark ? 'dark' : 'light', isModal: isModal };
217
+ }), _c = _b[0], theme = _c.theme, isModal = _c.isModal, setState = _b[1];
218
+ var setTheme = useCallback(function (theme) {
219
+ setState(function (old) { return (__assign(__assign({}, old), { theme: theme })); });
220
+ }, []);
221
+ var context = useMemo(function () { return ({
222
+ theme: theme,
223
+ isModal: isModal,
224
+ setTheme: setTheme,
225
+ }); }, [isModal, setTheme, theme]);
226
+ return (jsx(ThemeSelectionContext.Provider, __assign({ value: context }, { children: children })));
227
+ }
228
+
229
+ export { CapabilitiesGuard, ThemeSelectionProvider, WidgetApiMockProvider, WidgetApiProvider, useThemeSelection, useWidgetApi };
@@ -0,0 +1,167 @@
1
+ /**
2
+ * @packageDocumentation This is package that provides a Widget API
3
+ * intergration for React apps.
4
+ */
5
+
6
+ /// <reference types="react" />
7
+
8
+ import { Capability } from 'matrix-widget-api';
9
+ import { ComponentType } from 'react';
10
+ import { DispatchWithoutAction } from 'react';
11
+ import { FallbackProps } from 'react-error-boundary';
12
+ import { PropsWithChildren } from 'react';
13
+ import { Provider } from 'react';
14
+ import { ReactElement } from 'react';
15
+ import { WidgetApi } from '@matrix-widget-toolkit/api';
16
+ import { WidgetEventCapability } from 'matrix-widget-api';
17
+ import { WidgetRegistration } from '@matrix-widget-toolkit/api';
18
+
19
+ /**
20
+ * A guard that ask the user for capabilities and only shows the `children`
21
+ * if all capabilities were accepted.
22
+ * If capabilities are denined, a message and a button to retry is displayed
23
+ * instead.
24
+ * @param param0 - {@link CapabilitiesGuardProps}
25
+ */
26
+ export declare function CapabilitiesGuard({ capabilities, children, missingCapabilitiesComponent: MissingCapabilitiesComponent, loadingComponent: LoadingComponent, }: CapabilitiesGuardProps): ReactElement;
27
+
28
+ /**
29
+ * Props for the {@link CapabilitiesGuard} component.
30
+ */
31
+ export declare type CapabilitiesGuardProps = PropsWithChildren<{
32
+ /**
33
+ * Required capabilities to display the `children`.
34
+ */
35
+ capabilities: Array<WidgetEventCapability | Capability>;
36
+ /**
37
+ * Component to display if the required capabilities are missing. The
38
+ * `onRetry` callback can be used to re-request them from the user.
39
+ */
40
+ missingCapabilitiesComponent: ComponentType<{
41
+ onRetry: DispatchWithoutAction;
42
+ }>;
43
+ /**
44
+ * Component to display while the capabilities are evaluated or requested from
45
+ * the user.
46
+ */
47
+ loadingComponent: ComponentType;
48
+ }>;
49
+
50
+ /**
51
+ * Themes with different color schemes, either `light` or `dark`.
52
+ */
53
+ export declare type Theme = 'light' | 'dark' | string;
54
+
55
+ /**
56
+ * Return value of the {@link useThemeSelection} hook.
57
+ */
58
+ export declare type ThemeSelectionContextType = {
59
+ /**
60
+ * The current color scheme.
61
+ */
62
+ theme: Theme;
63
+ /**
64
+ * Whether the widget is displayed in a modal.
65
+ *
66
+ * @remarks Modals have different background colors which the theme needs to
67
+ * take into account.
68
+ */
69
+ isModal: boolean;
70
+ /**
71
+ * Select the current color scheme.
72
+ *
73
+ * @param theme - The new color scheme.
74
+ */
75
+ setTheme: (theme: Theme) => void;
76
+ };
77
+
78
+ /**
79
+ * Provides the current theme selection to child components.
80
+ * Use the {@link useThemeSelection} hook to access it.
81
+ * @param param0 - {@link ThemeSelectionProviderProps}
82
+ */
83
+ export declare function ThemeSelectionProvider({ children, }: ThemeSelectionProviderProps): ReactElement;
84
+
85
+ /**
86
+ * Props for the {@link ThemeSelectionProvider} component.
87
+ */
88
+ export declare type ThemeSelectionProviderProps = PropsWithChildren<{}>;
89
+
90
+ /**
91
+ * Hook for accessing the current theme selection.
92
+ * @returns The current theme selection.
93
+ */
94
+ export declare const useThemeSelection: () => ThemeSelectionContextType;
95
+
96
+ /**
97
+ * Hook for accessing the widget API.
98
+ *
99
+ * @remarks Can only be called inside a `WidgetApiProvider`
100
+ * (or WidgetApiMockProvider in tests).
101
+ *
102
+ * @returns A fully initialized widget API.
103
+ */
104
+ export declare const useWidgetApi: () => WidgetApi;
105
+
106
+ /**
107
+ * Provides a custom instance of the `WidgetApi` to the context.
108
+ *
109
+ * @remarks Should only be used in tests.
110
+ */
111
+ export declare const WidgetApiMockProvider: Provider<WidgetApi | undefined>;
112
+
113
+ /**
114
+ * Provides the `WidgetApi` in the React context once it's fully
115
+ * initialized without errors.
116
+ * Use {@link useWidgetApi} to access it.
117
+ * @param param0 - {@link WidgetApiProviderProps}
118
+ */
119
+ export declare function WidgetApiProvider({ children, widgetApiPromise, widgetRegistration, loadingViewComponent: LoadingViewComponent, mobileClientErrorComponent: MobileClientErrorComponent, outsideClientErrorComponent: OutsideClientErrorComponent, childErrorComponent: ChildErrorComponent, missingCapabilitiesComponent: MissingCapabilitiesComponent, missingParametersErrorComponent: MissingParametersErrorComponent, }: WidgetApiProviderProps): ReactElement;
120
+
121
+ /**
122
+ * Props for the {@link (WidgetApiProvider:function)} component.
123
+ */
124
+ export declare type WidgetApiProviderProps = PropsWithChildren<{
125
+ /**
126
+ * Configuration to set during Widget registration.
127
+ */
128
+ widgetRegistration?: WidgetRegistration;
129
+ /**
130
+ * Result from a call to `WidgetApiImpl.create`.
131
+ */
132
+ widgetApiPromise: Promise<WidgetApi>;
133
+ /**
134
+ * Component to display while the widget API communication is established or
135
+ * while capabilities are evaluated or requested from the user.
136
+ */
137
+ loadingViewComponent: ComponentType;
138
+ /**
139
+ * Component to display if the widget is opened in an unsupported mobile
140
+ * client.
141
+ */
142
+ mobileClientErrorComponent: ComponentType;
143
+ /**
144
+ * Component to display if the widget is opened outside a Matrix client.
145
+ */
146
+ outsideClientErrorComponent: ComponentType;
147
+ /**
148
+ * Component to display when a child component fails to render.
149
+ */
150
+ childErrorComponent: ComponentType<FallbackProps>;
151
+ /**
152
+ * Component to display if the required capabilities are missing. The
153
+ * `onRetry` callback can be used to re-request them from the user.
154
+ */
155
+ missingCapabilitiesComponent: ComponentType<{
156
+ onRetry: DispatchWithoutAction;
157
+ }>;
158
+ /**
159
+ * Component to display when the widget is not properly configured in the
160
+ * room. Takes the expected `widgetRegistration` as a parameter.
161
+ */
162
+ missingParametersErrorComponent: ComponentType<{
163
+ widgetRegistration?: WidgetRegistration;
164
+ }>;
165
+ }>;
166
+
167
+ export { }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@matrix-widget-toolkit/react",
3
+ "version": "1.0.1",
4
+ "description": "A simplified layer on top of @matrix-widget-toolkit/api to use it in a React based widget.",
5
+ "author": "Nordeck IT + Consulting GmbH",
6
+ "license": "Apache-2.0",
7
+ "source": "./src/index.ts",
8
+ "module": "./build/esm/index.js",
9
+ "types": "./build/index.d.ts",
10
+ "devDependencies": {
11
+ "@craco/craco": "^6.4.5",
12
+ "@testing-library/jest-dom": "^5.16.5",
13
+ "@testing-library/react": "^12.1.5",
14
+ "@testing-library/react-hooks": "^8.0.0",
15
+ "@testing-library/user-event": "^14.4.3",
16
+ "@types/jest": "^27.5.2",
17
+ "@types/node": "^16.11.64",
18
+ "@types/react": "^17.0.45",
19
+ "react-scripts": "5.0.1",
20
+ "typescript": "^4.8.4"
21
+ },
22
+ "scripts": {
23
+ "build": "tsc && rollup --config ../../rollup.config.mjs",
24
+ "tsc": "tsc",
25
+ "lint": "eslint .",
26
+ "test": "jest --watch",
27
+ "depcheck": "depcheck --ignores=@types/jest,@types/node --ignore-dirs=lib,build",
28
+ "prepack": "node ../../scripts/prepack.js",
29
+ "postpack": "node ../../scripts/postpack.js",
30
+ "translate": "echo \"Nothing to translate\"",
31
+ "check-api-report": "api-extractor run --verbose",
32
+ "generate-api-report": "tsc && api-extractor run --verbose --local"
33
+ },
34
+ "dependencies": {
35
+ "@matrix-widget-toolkit/api": "^1.0.1",
36
+ "matrix-widget-api": "^1.1.1",
37
+ "react": "^17.0.2",
38
+ "react-error-boundary": "^3.1.4",
39
+ "react-use": "^17.3.2"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/nordeck/matrix-widget-toolkit.git",
44
+ "directory": "packages/react"
45
+ },
46
+ "files": [
47
+ "build"
48
+ ],
49
+ "keywords": [
50
+ "matrix",
51
+ "widget",
52
+ "matrix-widget-api"
53
+ ],
54
+ "main": "./build/cjs/index.js"
55
+ }