@stone-js/use-react 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/dist/browser.js +157 -153
- package/dist/index.d.ts +262 -267
- package/dist/index.js +265 -261
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -1,11 +1,63 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InitializationError, isEmpty, isNotEmpty, isMetaClassModule, isMetaFactoryModule, isObjectLikeModule, isFunction, isFunctionModule, mergeBlueprints, stoneBlueprint, classDecoratorLegacyWrapper, setMetadata, methodDecoratorLegacyWrapper, addMetadata, LIFECYCLE_HOOK_KEY, hasMetadata, getMetadata, isMatchedAdapter, Logger, addBlueprint } from '@stone-js/core';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { renderToString } from 'react-dom/server';
|
|
4
|
-
import { createContext, StrictMode, useState, useEffect
|
|
5
|
-
import {
|
|
4
|
+
import { createContext, StrictMode, useContext, useMemo, useState, useEffect } from 'react';
|
|
5
|
+
import { hydrateRoot, createRoot } from 'react-dom/client';
|
|
6
6
|
import { Config } from '@stone-js/config';
|
|
7
7
|
import { GET, NODE_CONSOLE_PLATFORM, Router, RouteEvent } from '@stone-js/router';
|
|
8
|
-
import { OutgoingHttpResponse,
|
|
8
|
+
import { RedirectResponse, OutgoingHttpResponse, MetaCompressionMiddleware, MetaStaticFileMiddleware } from '@stone-js/http-core';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Custom error for react operations.
|
|
12
|
+
*/
|
|
13
|
+
class UseReactError extends InitializationError {
|
|
14
|
+
constructor(message, options) {
|
|
15
|
+
super(message, options);
|
|
16
|
+
this.name = 'UseReactError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A useReact event handler for processing incoming events
|
|
22
|
+
* For single event handler.
|
|
23
|
+
*
|
|
24
|
+
* Multiple event handlers will be processed by the router.
|
|
25
|
+
*
|
|
26
|
+
* @template IncomingEventType - The type representing the incoming event.
|
|
27
|
+
* @template OutgoingResponseType - The type representing the outgoing response.
|
|
28
|
+
*/
|
|
29
|
+
class UseReactEventHandler {
|
|
30
|
+
blueprint;
|
|
31
|
+
/**
|
|
32
|
+
* Constructs a `UseReactEventHandler` instance.
|
|
33
|
+
*
|
|
34
|
+
* @param options - The UseReactEventHandler options including blueprint.
|
|
35
|
+
*/
|
|
36
|
+
constructor({ blueprint }) {
|
|
37
|
+
this.blueprint = blueprint;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Handle an incoming event.
|
|
41
|
+
*
|
|
42
|
+
* @returns The outgoing response.
|
|
43
|
+
*/
|
|
44
|
+
handle() {
|
|
45
|
+
return this.getComponentEventHandler();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the component event handler.
|
|
49
|
+
*
|
|
50
|
+
* @returns The component event handler.
|
|
51
|
+
* @throws {UseReactError} If the component event handler is missing.
|
|
52
|
+
*/
|
|
53
|
+
getComponentEventHandler() {
|
|
54
|
+
const handler = this.blueprint.get('stone.useReact.componentEventHandler');
|
|
55
|
+
if (isEmpty(handler)) {
|
|
56
|
+
throw new UseReactError('The component event handler is missing.');
|
|
57
|
+
}
|
|
58
|
+
return handler;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
9
61
|
|
|
10
62
|
/**
|
|
11
63
|
* Stone DOM Attribute.
|
|
@@ -269,16 +321,6 @@ const StoneError = () => {
|
|
|
269
321
|
return (jsxs(Fragment, { children: [jsx("h1", { children: "An error occured" }), jsx("p", { children: "Sorry, something went wrong." })] }));
|
|
270
322
|
};
|
|
271
323
|
|
|
272
|
-
/**
|
|
273
|
-
* Custom error for react operations.
|
|
274
|
-
*/
|
|
275
|
-
class UseReactError extends InitializationError {
|
|
276
|
-
constructor(message, options) {
|
|
277
|
-
super(message, options);
|
|
278
|
-
this.name = 'UseReactError';
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
324
|
/**
|
|
283
325
|
* Build the React application for the current route.
|
|
284
326
|
* Or for the main handler if the route is not defined.
|
|
@@ -710,48 +752,6 @@ class ReactRuntime {
|
|
|
710
752
|
*/
|
|
711
753
|
const MetaReactRuntime = { module: ReactRuntime, isClass: true, alias: 'reactRuntime', singleton: true };
|
|
712
754
|
|
|
713
|
-
/**
|
|
714
|
-
* A useReact event handler for processing incoming events
|
|
715
|
-
* For single event handler.
|
|
716
|
-
*
|
|
717
|
-
* Multiple event handlers will be processed by the router.
|
|
718
|
-
*
|
|
719
|
-
* @template IncomingEventType - The type representing the incoming event.
|
|
720
|
-
* @template OutgoingResponseType - The type representing the outgoing response.
|
|
721
|
-
*/
|
|
722
|
-
class UseReactEventHandler {
|
|
723
|
-
blueprint;
|
|
724
|
-
/**
|
|
725
|
-
* Constructs a `UseReactEventHandler` instance.
|
|
726
|
-
*
|
|
727
|
-
* @param options - The UseReactEventHandler options including blueprint.
|
|
728
|
-
*/
|
|
729
|
-
constructor({ blueprint }) {
|
|
730
|
-
this.blueprint = blueprint;
|
|
731
|
-
}
|
|
732
|
-
/**
|
|
733
|
-
* Handle an incoming event.
|
|
734
|
-
*
|
|
735
|
-
* @returns The outgoing response.
|
|
736
|
-
*/
|
|
737
|
-
handle() {
|
|
738
|
-
return this.getComponentEventHandler();
|
|
739
|
-
}
|
|
740
|
-
/**
|
|
741
|
-
* Get the component event handler.
|
|
742
|
-
*
|
|
743
|
-
* @returns The component event handler.
|
|
744
|
-
* @throws {UseReactError} If the component event handler is missing.
|
|
745
|
-
*/
|
|
746
|
-
getComponentEventHandler() {
|
|
747
|
-
const handler = this.blueprint.get('stone.useReact.componentEventHandler');
|
|
748
|
-
if (isEmpty(handler)) {
|
|
749
|
-
throw new UseReactError('The component event handler is missing.');
|
|
750
|
-
}
|
|
751
|
-
return handler;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
755
|
/**
|
|
756
756
|
* Class representing an UseReactUseReactKernelErrorHandler.
|
|
757
757
|
*
|
|
@@ -1098,30 +1098,6 @@ const REACT_ADAPTER_ERROR_PAGE_KEY = Symbol.for('ReactAdapterErrorPage');
|
|
|
1098
1098
|
*/
|
|
1099
1099
|
const STONE_REACT_APP_KEY = Symbol.for('StoneReactApp');
|
|
1100
1100
|
|
|
1101
|
-
/**
|
|
1102
|
-
* A class decorator for defining a class as a React Handler layout.
|
|
1103
|
-
*
|
|
1104
|
-
* @param options - Configuration options for the layout definition.
|
|
1105
|
-
* @returns A method decorator to be applied to a class method.
|
|
1106
|
-
*
|
|
1107
|
-
* @example
|
|
1108
|
-
* ```typescript
|
|
1109
|
-
* import { AdapterErrorPage } from '@stone-js/use-react';
|
|
1110
|
-
*
|
|
1111
|
-
* @AdapterErrorPage({ error: 'UserNotFoundError' })
|
|
1112
|
-
* class UserAdapterErrorPage {
|
|
1113
|
-
* render({ error }) {
|
|
1114
|
-
* return <h1>User name: {error.message}</h1>;
|
|
1115
|
-
* }
|
|
1116
|
-
* }
|
|
1117
|
-
* ```
|
|
1118
|
-
*/
|
|
1119
|
-
const AdapterErrorPage = (options) => {
|
|
1120
|
-
return classDecoratorLegacyWrapper((_target, context) => {
|
|
1121
|
-
setMetadata(context, REACT_ADAPTER_ERROR_PAGE_KEY, { ...options, isClass: true });
|
|
1122
|
-
});
|
|
1123
|
-
};
|
|
1124
|
-
|
|
1125
1101
|
/**
|
|
1126
1102
|
* A class decorator for defining a class as a React Handler layout.
|
|
1127
1103
|
*
|
|
@@ -1147,37 +1123,24 @@ const ErrorPage = (options) => {
|
|
|
1147
1123
|
};
|
|
1148
1124
|
|
|
1149
1125
|
/**
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1152
|
-
*
|
|
1153
|
-
* @param options - Configuration options for the route definition, excluding the `methods` property.
|
|
1154
|
-
* @returns A method decorator to be applied to a class method.
|
|
1126
|
+
* Hook decorator to mark a method as a lifecycle hook
|
|
1127
|
+
* And automatically add it to the global lifecycle hook registry.
|
|
1155
1128
|
*
|
|
1156
1129
|
* @example
|
|
1157
1130
|
* ```typescript
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1162
|
-
* handle({ event }): Record<string, string> {
|
|
1163
|
-
* return { name: 'Jane Doe' };
|
|
1164
|
-
* }
|
|
1165
|
-
*
|
|
1166
|
-
* render({ data }) {
|
|
1167
|
-
* return <h1>User name: {data.name}</h1>;
|
|
1168
|
-
* }
|
|
1131
|
+
* class MyClass {
|
|
1132
|
+
* // ...
|
|
1133
|
+
* @Hook('onPreparingPage')
|
|
1134
|
+
* onPreparingPage () {}
|
|
1169
1135
|
* }
|
|
1170
1136
|
* ```
|
|
1137
|
+
*
|
|
1138
|
+
* @param name - The name of the lifecycle hook.
|
|
1139
|
+
* @returns A class decorator function that sets the metadata using the provided options.
|
|
1171
1140
|
*/
|
|
1172
|
-
const
|
|
1173
|
-
return
|
|
1174
|
-
|
|
1175
|
-
...options,
|
|
1176
|
-
path,
|
|
1177
|
-
method: GET,
|
|
1178
|
-
methods: [],
|
|
1179
|
-
handler: { isClass: true, isComponent: true, layout: options.layout, module: target }
|
|
1180
|
-
});
|
|
1141
|
+
const Hook = (name) => {
|
|
1142
|
+
return methodDecoratorLegacyWrapper((_target, context) => {
|
|
1143
|
+
addMetadata(context, LIFECYCLE_HOOK_KEY, { name, method: context.name });
|
|
1181
1144
|
});
|
|
1182
1145
|
};
|
|
1183
1146
|
|
|
@@ -1206,24 +1169,26 @@ const PageLayout = (options) => {
|
|
|
1206
1169
|
};
|
|
1207
1170
|
|
|
1208
1171
|
/**
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1172
|
+
* A class decorator for defining a class as a React Handler layout.
|
|
1173
|
+
*
|
|
1174
|
+
* @param options - Configuration options for the layout definition.
|
|
1175
|
+
* @returns A method decorator to be applied to a class method.
|
|
1211
1176
|
*
|
|
1212
1177
|
* @example
|
|
1213
1178
|
* ```typescript
|
|
1214
|
-
*
|
|
1215
|
-
*
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1179
|
+
* import { AdapterErrorPage } from '@stone-js/use-react';
|
|
1180
|
+
*
|
|
1181
|
+
* @AdapterErrorPage({ error: 'UserNotFoundError' })
|
|
1182
|
+
* class UserAdapterErrorPage {
|
|
1183
|
+
* render({ error }) {
|
|
1184
|
+
* return <h1>User name: {error.message}</h1>;
|
|
1185
|
+
* }
|
|
1218
1186
|
* }
|
|
1219
1187
|
* ```
|
|
1220
|
-
*
|
|
1221
|
-
* @param name - The name of the lifecycle hook.
|
|
1222
|
-
* @returns A class decorator function that sets the metadata using the provided options.
|
|
1223
1188
|
*/
|
|
1224
|
-
const
|
|
1225
|
-
return
|
|
1226
|
-
|
|
1189
|
+
const AdapterErrorPage = (options) => {
|
|
1190
|
+
return classDecoratorLegacyWrapper((_target, context) => {
|
|
1191
|
+
setMetadata(context, REACT_ADAPTER_ERROR_PAGE_KEY, { ...options, isClass: true });
|
|
1227
1192
|
});
|
|
1228
1193
|
};
|
|
1229
1194
|
|
|
@@ -1256,6 +1221,41 @@ const PageStatus = (statusCode = 200, headers = {}) => {
|
|
|
1256
1221
|
});
|
|
1257
1222
|
};
|
|
1258
1223
|
|
|
1224
|
+
/**
|
|
1225
|
+
* A class decorator for defining a class as a React Page route action.
|
|
1226
|
+
* Uses the `Match` decorator internally to register the route with the HTTP `GET` method.
|
|
1227
|
+
*
|
|
1228
|
+
* @param options - Configuration options for the route definition, excluding the `methods` property.
|
|
1229
|
+
* @returns A method decorator to be applied to a class method.
|
|
1230
|
+
*
|
|
1231
|
+
* @example
|
|
1232
|
+
* ```typescript
|
|
1233
|
+
* import { Page } from '@stone-js/use-react';
|
|
1234
|
+
*
|
|
1235
|
+
* @Page('/user-profile')
|
|
1236
|
+
* class UserPage {
|
|
1237
|
+
* handle({ event }): Record<string, string> {
|
|
1238
|
+
* return { name: 'Jane Doe' };
|
|
1239
|
+
* }
|
|
1240
|
+
*
|
|
1241
|
+
* render({ data }) {
|
|
1242
|
+
* return <h1>User name: {data.name}</h1>;
|
|
1243
|
+
* }
|
|
1244
|
+
* }
|
|
1245
|
+
* ```
|
|
1246
|
+
*/
|
|
1247
|
+
const Page = (path, options = {}) => {
|
|
1248
|
+
return classDecoratorLegacyWrapper((target, context) => {
|
|
1249
|
+
setMetadata(context, REACT_PAGE_KEY, {
|
|
1250
|
+
...options,
|
|
1251
|
+
path,
|
|
1252
|
+
method: GET,
|
|
1253
|
+
methods: [],
|
|
1254
|
+
handler: { isClass: true, isComponent: true, layout: options.layout, module: target }
|
|
1255
|
+
});
|
|
1256
|
+
});
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
1259
|
/**
|
|
1260
1260
|
* Decorator to create a snapshot of the current data.
|
|
1261
1261
|
*
|
|
@@ -1285,6 +1285,111 @@ const Snapshot = (name) => {
|
|
|
1285
1285
|
});
|
|
1286
1286
|
};
|
|
1287
1287
|
|
|
1288
|
+
/**
|
|
1289
|
+
* Sets the error handler for the React adapter and registers error pages.
|
|
1290
|
+
*
|
|
1291
|
+
* @param errorHandler - The error handler to set for the React adapter.
|
|
1292
|
+
* @param context - The blueprint context containing modules and blueprint.
|
|
1293
|
+
* @returns The updated blueprint context with the error handler and error pages set.
|
|
1294
|
+
*/
|
|
1295
|
+
function setUseReactAdapterErrorHandler(errorHandler, context) {
|
|
1296
|
+
context
|
|
1297
|
+
.blueprint
|
|
1298
|
+
.set('stone.adapter.errorHandlers.default', { module: errorHandler, isClass: true });
|
|
1299
|
+
context
|
|
1300
|
+
.modules
|
|
1301
|
+
.filter(module => hasMetadata(module, REACT_ADAPTER_ERROR_PAGE_KEY))
|
|
1302
|
+
.forEach(module => {
|
|
1303
|
+
const { error, layout, adapterAlias, platform } = getMetadata(module, REACT_ADAPTER_ERROR_PAGE_KEY, { error: 'default' });
|
|
1304
|
+
if (isMatchedAdapter(context.blueprint, platform, adapterAlias)) {
|
|
1305
|
+
Array(error).flat().forEach(name => {
|
|
1306
|
+
context
|
|
1307
|
+
.blueprint
|
|
1308
|
+
.set(`stone.useReact.adapterErrorPages.${name}`, { isClass: true, layout, module });
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
// Process both eager and lazy loaded error pages
|
|
1313
|
+
Object
|
|
1314
|
+
.keys(context.blueprint.get('stone.useReact.adapterErrorPages', {}))
|
|
1315
|
+
.forEach((name) => {
|
|
1316
|
+
context
|
|
1317
|
+
.blueprint
|
|
1318
|
+
.set(`stone.adapter.errorHandlers.${name}`, { module: errorHandler, isClass: true });
|
|
1319
|
+
});
|
|
1320
|
+
return context;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* Create an UseReact response.
|
|
1325
|
+
*
|
|
1326
|
+
* @param options - The options for creating the response.
|
|
1327
|
+
* @returns The React response.
|
|
1328
|
+
*/
|
|
1329
|
+
const reactResponse = (options) => {
|
|
1330
|
+
if (isNotEmpty(options) &&
|
|
1331
|
+
(isNotEmpty(options.url) ||
|
|
1332
|
+
(isNotEmpty(options.content) && isNotEmpty(options.content.redirect)))) {
|
|
1333
|
+
return reactRedirectResponse(options);
|
|
1334
|
+
}
|
|
1335
|
+
return OutgoingHttpResponse.create(options);
|
|
1336
|
+
};
|
|
1337
|
+
/**
|
|
1338
|
+
* Create an UseReact redirect response.
|
|
1339
|
+
*
|
|
1340
|
+
* @param options - The options for creating the response.
|
|
1341
|
+
* @returns The React redirect response.
|
|
1342
|
+
*/
|
|
1343
|
+
const reactRedirectResponse = (options) => {
|
|
1344
|
+
return RedirectResponse.create({ statusCode: 302, ...options });
|
|
1345
|
+
};
|
|
1346
|
+
|
|
1347
|
+
/**
|
|
1348
|
+
* Class representing an UseReactServerErrorHandler.
|
|
1349
|
+
*
|
|
1350
|
+
* Adapter level error handler for React applications.
|
|
1351
|
+
*/
|
|
1352
|
+
class UseReactServerErrorHandler {
|
|
1353
|
+
logger;
|
|
1354
|
+
blueprint;
|
|
1355
|
+
/**
|
|
1356
|
+
* Create an UseReactServerErrorHandler.
|
|
1357
|
+
*
|
|
1358
|
+
* @param options - UseReactServerErrorHandler options.
|
|
1359
|
+
*/
|
|
1360
|
+
constructor({ blueprint }) {
|
|
1361
|
+
this.blueprint = blueprint;
|
|
1362
|
+
this.logger = Logger.getInstance();
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Handle an error.
|
|
1366
|
+
*
|
|
1367
|
+
* @param error - The error to handle.
|
|
1368
|
+
* @param context - The context of the adapter.
|
|
1369
|
+
* @returns The raw response.
|
|
1370
|
+
*/
|
|
1371
|
+
async handle(error, context) {
|
|
1372
|
+
this.logger.error(error.message, { error });
|
|
1373
|
+
return context
|
|
1374
|
+
.rawResponseBuilder
|
|
1375
|
+
.add('statusCode', error.statusCode ?? 500)
|
|
1376
|
+
.add('headers', new Headers({ 'Content-Type': 'text/html' }))
|
|
1377
|
+
.add('body', await this.getErrorBody(error, context));
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Get the error body.
|
|
1381
|
+
*
|
|
1382
|
+
* @param error - The error to handle.
|
|
1383
|
+
* @returns The error body.
|
|
1384
|
+
*/
|
|
1385
|
+
async getErrorBody(error, context) {
|
|
1386
|
+
const statusCode = error.statusCode ?? 500;
|
|
1387
|
+
const template = htmlTemplate(this.blueprint);
|
|
1388
|
+
const ClientApp = await buildAdapterErrorComponent(this.blueprint, context, statusCode, error);
|
|
1389
|
+
return template.replace('<!--app-html-->', renderToString(ClientApp));
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1288
1393
|
/**
|
|
1289
1394
|
* Blueprint middleware to dynamically set lifecycle hooks for react.
|
|
1290
1395
|
*
|
|
@@ -1416,111 +1521,6 @@ async function SetUseReactEventHandlerMiddleware(context, next) {
|
|
|
1416
1521
|
return blueprint;
|
|
1417
1522
|
}
|
|
1418
1523
|
|
|
1419
|
-
/**
|
|
1420
|
-
* Sets the error handler for the React adapter and registers error pages.
|
|
1421
|
-
*
|
|
1422
|
-
* @param errorHandler - The error handler to set for the React adapter.
|
|
1423
|
-
* @param context - The blueprint context containing modules and blueprint.
|
|
1424
|
-
* @returns The updated blueprint context with the error handler and error pages set.
|
|
1425
|
-
*/
|
|
1426
|
-
function setUseReactAdapterErrorHandler(errorHandler, context) {
|
|
1427
|
-
context
|
|
1428
|
-
.blueprint
|
|
1429
|
-
.set('stone.adapter.errorHandlers.default', { module: errorHandler, isClass: true });
|
|
1430
|
-
context
|
|
1431
|
-
.modules
|
|
1432
|
-
.filter(module => hasMetadata(module, REACT_ADAPTER_ERROR_PAGE_KEY))
|
|
1433
|
-
.forEach(module => {
|
|
1434
|
-
const { error, layout, adapterAlias, platform } = getMetadata(module, REACT_ADAPTER_ERROR_PAGE_KEY, { error: 'default' });
|
|
1435
|
-
if (isMatchedAdapter(context.blueprint, platform, adapterAlias)) {
|
|
1436
|
-
Array(error).flat().forEach(name => {
|
|
1437
|
-
context
|
|
1438
|
-
.blueprint
|
|
1439
|
-
.set(`stone.useReact.adapterErrorPages.${name}`, { isClass: true, layout, module });
|
|
1440
|
-
});
|
|
1441
|
-
}
|
|
1442
|
-
});
|
|
1443
|
-
// Process both eager and lazy loaded error pages
|
|
1444
|
-
Object
|
|
1445
|
-
.keys(context.blueprint.get('stone.useReact.adapterErrorPages', {}))
|
|
1446
|
-
.forEach((name) => {
|
|
1447
|
-
context
|
|
1448
|
-
.blueprint
|
|
1449
|
-
.set(`stone.adapter.errorHandlers.${name}`, { module: errorHandler, isClass: true });
|
|
1450
|
-
});
|
|
1451
|
-
return context;
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
/**
|
|
1455
|
-
* Create an UseReact response.
|
|
1456
|
-
*
|
|
1457
|
-
* @param options - The options for creating the response.
|
|
1458
|
-
* @returns The React response.
|
|
1459
|
-
*/
|
|
1460
|
-
const reactResponse = (options) => {
|
|
1461
|
-
if (isNotEmpty(options) &&
|
|
1462
|
-
(isNotEmpty(options.url) ||
|
|
1463
|
-
(isNotEmpty(options.content) && isNotEmpty(options.content.redirect)))) {
|
|
1464
|
-
return reactRedirectResponse(options);
|
|
1465
|
-
}
|
|
1466
|
-
return OutgoingHttpResponse.create(options);
|
|
1467
|
-
};
|
|
1468
|
-
/**
|
|
1469
|
-
* Create an UseReact redirect response.
|
|
1470
|
-
*
|
|
1471
|
-
* @param options - The options for creating the response.
|
|
1472
|
-
* @returns The React redirect response.
|
|
1473
|
-
*/
|
|
1474
|
-
const reactRedirectResponse = (options) => {
|
|
1475
|
-
return RedirectResponse.create({ statusCode: 302, ...options });
|
|
1476
|
-
};
|
|
1477
|
-
|
|
1478
|
-
/**
|
|
1479
|
-
* Class representing an UseReactServerErrorHandler.
|
|
1480
|
-
*
|
|
1481
|
-
* Adapter level error handler for React applications.
|
|
1482
|
-
*/
|
|
1483
|
-
class UseReactServerErrorHandler {
|
|
1484
|
-
logger;
|
|
1485
|
-
blueprint;
|
|
1486
|
-
/**
|
|
1487
|
-
* Create an UseReactServerErrorHandler.
|
|
1488
|
-
*
|
|
1489
|
-
* @param options - UseReactServerErrorHandler options.
|
|
1490
|
-
*/
|
|
1491
|
-
constructor({ blueprint }) {
|
|
1492
|
-
this.blueprint = blueprint;
|
|
1493
|
-
this.logger = Logger.getInstance();
|
|
1494
|
-
}
|
|
1495
|
-
/**
|
|
1496
|
-
* Handle an error.
|
|
1497
|
-
*
|
|
1498
|
-
* @param error - The error to handle.
|
|
1499
|
-
* @param context - The context of the adapter.
|
|
1500
|
-
* @returns The raw response.
|
|
1501
|
-
*/
|
|
1502
|
-
async handle(error, context) {
|
|
1503
|
-
this.logger.error(error.message, { error });
|
|
1504
|
-
return context
|
|
1505
|
-
.rawResponseBuilder
|
|
1506
|
-
.add('statusCode', error.statusCode ?? 500)
|
|
1507
|
-
.add('headers', new Headers({ 'Content-Type': 'text/html' }))
|
|
1508
|
-
.add('body', await this.getErrorBody(error, context));
|
|
1509
|
-
}
|
|
1510
|
-
/**
|
|
1511
|
-
* Get the error body.
|
|
1512
|
-
*
|
|
1513
|
-
* @param error - The error to handle.
|
|
1514
|
-
* @returns The error body.
|
|
1515
|
-
*/
|
|
1516
|
-
async getErrorBody(error, context) {
|
|
1517
|
-
const statusCode = error.statusCode ?? 500;
|
|
1518
|
-
const template = htmlTemplate(this.blueprint);
|
|
1519
|
-
const ClientApp = await buildAdapterErrorComponent(this.blueprint, context, statusCode, error);
|
|
1520
|
-
return template.replace('<!--app-html-->', renderToString(ClientApp));
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
1524
|
/**
|
|
1525
1525
|
* Blueprint middleware to process and register adapter error page definitions from modules.
|
|
1526
1526
|
*
|
|
@@ -1624,6 +1624,47 @@ const StoneClient = ({ children }) => {
|
|
|
1624
1624
|
return isClient() ? jsx(Fragment, { children: children }) : jsx(Fragment, {});
|
|
1625
1625
|
};
|
|
1626
1626
|
|
|
1627
|
+
/**
|
|
1628
|
+
* Internal link component using Stone.js router.
|
|
1629
|
+
*/
|
|
1630
|
+
const StoneLink = ({ to, href, noRel, external, children, ariaCurrentValue = 'page', selectedClass = 'selected', ...rest }) => {
|
|
1631
|
+
const isExternal = external === true;
|
|
1632
|
+
const shouldHandleNav = !isExternal && isNotEmpty(to);
|
|
1633
|
+
const router = useContext(StoneContext).container.resolve(Router);
|
|
1634
|
+
const path = useMemo(() => {
|
|
1635
|
+
return isObjectLikeModule(to) ? router.generate(to) : to ?? href ?? '#';
|
|
1636
|
+
}, [to, href, router]);
|
|
1637
|
+
const [currentRoute, setCurrentRoute] = useState(router.getCurrentRoute());
|
|
1638
|
+
const selectedClassName = currentRoute?.path === path ? selectedClass : undefined;
|
|
1639
|
+
const elemClassName = [rest.className, selectedClassName].filter(Boolean).join(' ').trim();
|
|
1640
|
+
const handleClick = (event) => {
|
|
1641
|
+
rest.onClick?.(event);
|
|
1642
|
+
if (event.defaultPrevented || isExternal)
|
|
1643
|
+
return;
|
|
1644
|
+
event.preventDefault();
|
|
1645
|
+
isNotEmpty(to) && router.navigate(to);
|
|
1646
|
+
};
|
|
1647
|
+
if (isEmpty(to) && isEmpty(href)) {
|
|
1648
|
+
Logger.warn('StoneLink: missing "to" or "href"');
|
|
1649
|
+
}
|
|
1650
|
+
useEffect(() => {
|
|
1651
|
+
const routerEventHandler = (event) => {
|
|
1652
|
+
setCurrentRoute(event.get('route'));
|
|
1653
|
+
};
|
|
1654
|
+
router.on(RouteEvent.ROUTED, routerEventHandler);
|
|
1655
|
+
return () => {
|
|
1656
|
+
router.off(RouteEvent.ROUTED, routerEventHandler);
|
|
1657
|
+
};
|
|
1658
|
+
}, [router]);
|
|
1659
|
+
return (
|
|
1660
|
+
// eslint-disable-next-line react/jsx-no-target-blank
|
|
1661
|
+
jsx("a", { ...rest, href: path, className: elemClassName, target: isExternal ? '_blank' : rest.target, "aria-current": isNotEmpty(selectedClassName) ? ariaCurrentValue : undefined, rel: noRel === true
|
|
1662
|
+
? undefined
|
|
1663
|
+
: isExternal
|
|
1664
|
+
? 'noopener noreferrer'
|
|
1665
|
+
: rest.rel, onClick: shouldHandleNav ? handleClick : rest.onClick, children: children }));
|
|
1666
|
+
};
|
|
1667
|
+
|
|
1627
1668
|
/**
|
|
1628
1669
|
* A dynamic rendering component that updates its content based on a global event.
|
|
1629
1670
|
*
|
|
@@ -1636,7 +1677,7 @@ const StoneClient = ({ children }) => {
|
|
|
1636
1677
|
* @param options - The options to create the Stone Outlet.
|
|
1637
1678
|
* @returns The Stone Outlet component.
|
|
1638
1679
|
*/
|
|
1639
|
-
const StoneOutlet = ({ children }) => {
|
|
1680
|
+
const StoneOutlet = ({ children, ...rest }) => {
|
|
1640
1681
|
const [currentView, setCurrentView] = useState(children);
|
|
1641
1682
|
useEffect(() => {
|
|
1642
1683
|
const eventName = STONE_PAGE_EVENT_OUTLET;
|
|
@@ -1648,44 +1689,7 @@ const StoneOutlet = ({ children }) => {
|
|
|
1648
1689
|
window.addEventListener(eventName, handleEvent);
|
|
1649
1690
|
return () => window.removeEventListener(eventName, handleEvent);
|
|
1650
1691
|
}, []);
|
|
1651
|
-
return jsx("div", { "data-stone-outlet": 'true', children: currentView });
|
|
1652
|
-
};
|
|
1653
|
-
|
|
1654
|
-
/**
|
|
1655
|
-
* Internal link component using Stone.js router.
|
|
1656
|
-
*/
|
|
1657
|
-
const InternalLink = ({ to, href, noRel, children, className, defaultNav, selectedClass = 'selected', ariaCurrentValue = 'page', rel = 'noopener noreferrer' }) => {
|
|
1658
|
-
const router = useContext(StoneContext).container.resolve(Router);
|
|
1659
|
-
const path = isObjectLikeModule(to) ? router.generate(to) : to ?? href;
|
|
1660
|
-
const [currentRoute, setCurrentRoute] = useState(router.getCurrentRoute());
|
|
1661
|
-
const selectedClassName = currentRoute?.path === path ? selectedClass : undefined;
|
|
1662
|
-
const elemClassName = [className, selectedClassName].filter(Boolean).join(' ').trim();
|
|
1663
|
-
const handleClick = (event) => {
|
|
1664
|
-
event.preventDefault();
|
|
1665
|
-
router.navigate(to ?? '');
|
|
1666
|
-
};
|
|
1667
|
-
useEffect(() => {
|
|
1668
|
-
const routerEventHandler = (event) => {
|
|
1669
|
-
setCurrentRoute(event.get('route'));
|
|
1670
|
-
};
|
|
1671
|
-
router.on(RouteEvent.ROUTED, routerEventHandler);
|
|
1672
|
-
return () => {
|
|
1673
|
-
router.off(RouteEvent.ROUTED, routerEventHandler);
|
|
1674
|
-
};
|
|
1675
|
-
}, [router]);
|
|
1676
|
-
return defaultNav === true
|
|
1677
|
-
? (jsx("a", { href: path, className: elemClassName, "aria-current": ariaCurrentValue, rel: noRel !== undefined ? undefined : rel, children: children }))
|
|
1678
|
-
: (jsx("button", { onClick: handleClick, className: elemClassName, "aria-current": ariaCurrentValue, rel: noRel !== undefined ? undefined : rel, children: children }));
|
|
1679
|
-
};
|
|
1680
|
-
/**
|
|
1681
|
-
* External link component rendering a regular <a> tag.
|
|
1682
|
-
*/
|
|
1683
|
-
const ExternalLink = ({ to, href, noRel, target, children, className, ariaCurrentValue = 'page', rel = 'noopener noreferrer' }) => (jsx("a", { target: target, className: className, "aria-current": ariaCurrentValue, rel: noRel !== undefined ? undefined : rel, href: typeof to === 'string' ? to : href, children: children }));
|
|
1684
|
-
/**
|
|
1685
|
-
* Main StoneLink component delegating to internal or external versions.
|
|
1686
|
-
*/
|
|
1687
|
-
const StoneLink = (props) => {
|
|
1688
|
-
return props.external === true ? jsx(ExternalLink, { ...props }) : jsx(InternalLink, { ...props });
|
|
1692
|
+
return jsx("div", { ...rest, "data-stone-outlet": 'true', children: currentView });
|
|
1689
1693
|
};
|
|
1690
1694
|
|
|
1691
1695
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stone-js/use-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "React integration for Stone.js, universal rendering, SSR hydration, layouts, pages, snapshots, head management, and more.",
|
|
5
5
|
"author": "Mr. Stone <evensstone@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
"@stone-js/browser-adapter": "^0.1.0",
|
|
61
61
|
"@stone-js/browser-core": "^0.1.1",
|
|
62
62
|
"@stone-js/config": "^0.1.0",
|
|
63
|
-
"@stone-js/core": "^0.1.
|
|
63
|
+
"@stone-js/core": "^0.1.4",
|
|
64
64
|
"@stone-js/http-core": "^0.1.2",
|
|
65
|
-
"@stone-js/router": "^0.
|
|
65
|
+
"@stone-js/router": "^0.2.0",
|
|
66
66
|
"react": "^19.0.0",
|
|
67
67
|
"react-dom": "^19.0.0"
|
|
68
68
|
},
|
|
@@ -72,25 +72,25 @@
|
|
|
72
72
|
"@rollup/plugin-commonjs": "^28.0.5",
|
|
73
73
|
"@rollup/plugin-multi-entry": "^6.0.1",
|
|
74
74
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
75
|
-
"@rollup/plugin-typescript": "^12.1.
|
|
75
|
+
"@rollup/plugin-typescript": "^12.1.4",
|
|
76
76
|
"@testing-library/react": "^16.3.0",
|
|
77
|
-
"@types/node": "^24.0.
|
|
77
|
+
"@types/node": "^24.0.7",
|
|
78
78
|
"@types/react": "^19.0.7",
|
|
79
79
|
"@types/react-dom": "^19.0.3",
|
|
80
|
-
"@vitest/coverage-v8": "^3.2.
|
|
80
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
81
81
|
"husky": "^9.1.7",
|
|
82
82
|
"jsdom": "^26.1.0",
|
|
83
83
|
"rimraf": "^6.0.1",
|
|
84
|
-
"rollup": "^4.
|
|
84
|
+
"rollup": "^4.44.1",
|
|
85
85
|
"rollup-plugin-delete": "^3.0.1",
|
|
86
86
|
"rollup-plugin-dts": "^6.2.1",
|
|
87
|
-
"rollup-plugin-node-externals": "^8.0.
|
|
87
|
+
"rollup-plugin-node-externals": "^8.0.1",
|
|
88
88
|
"ts-standard": "^12.0.2",
|
|
89
89
|
"tslib": "^2.8.1",
|
|
90
|
-
"typedoc": "^0.28.
|
|
91
|
-
"typedoc-plugin-markdown": "^4.
|
|
90
|
+
"typedoc": "^0.28.6",
|
|
91
|
+
"typedoc-plugin-markdown": "^4.7.0",
|
|
92
92
|
"typescript": "^5.6.3",
|
|
93
|
-
"vitest": "^3.2.
|
|
93
|
+
"vitest": "^3.2.4"
|
|
94
94
|
},
|
|
95
95
|
"ts-standard": {
|
|
96
96
|
"globals": [
|