@alepha/react 0.13.6 → 0.13.8
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/dist/auth/index.browser.js +5 -5
- package/dist/auth/index.browser.js.map +1 -1
- package/dist/auth/index.d.ts +105 -103
- package/dist/auth/index.js +5 -5
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.browser.js +407 -142
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +144 -116
- package/dist/core/index.js +409 -145
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +24 -2
- package/dist/core/index.native.js.map +1 -1
- package/dist/form/index.d.ts +14 -6
- package/dist/form/index.js +32 -12
- package/dist/form/index.js.map +1 -1
- package/dist/head/index.d.ts +18 -18
- package/dist/head/index.js +5 -1
- package/dist/head/index.js.map +1 -1
- package/dist/i18n/index.d.ts +25 -25
- package/dist/i18n/index.js +4 -3
- package/dist/i18n/index.js.map +1 -1
- package/dist/websocket/index.d.ts +1 -1
- package/package.json +22 -23
- package/src/auth/hooks/useAuth.ts +1 -0
- package/src/auth/services/ReactAuth.ts +6 -4
- package/src/core/components/ErrorViewer.tsx +378 -130
- package/src/core/components/NestedView.tsx +16 -11
- package/src/core/contexts/AlephaProvider.tsx +41 -0
- package/src/core/contexts/RouterLayerContext.ts +2 -0
- package/src/core/hooks/useAction.ts +4 -1
- package/src/core/index.shared.ts +1 -0
- package/src/core/primitives/$page.ts +15 -2
- package/src/core/providers/ReactPageProvider.ts +6 -7
- package/src/core/providers/ReactServerProvider.ts +2 -6
- package/src/form/services/FormModel.ts +81 -26
- package/src/head/index.ts +2 -1
- package/src/i18n/providers/I18nProvider.ts +4 -2
|
@@ -150,10 +150,10 @@ export interface PagePrimitiveOptions<
|
|
|
150
150
|
* Load data before rendering the page.
|
|
151
151
|
*
|
|
152
152
|
* This function receives
|
|
153
|
-
* - the request context
|
|
153
|
+
* - the request context (params, query, etc.)
|
|
154
154
|
* - the parent props (if page has a parent)
|
|
155
155
|
*
|
|
156
|
-
* In SSR, the returned data will be serialized and sent to the client, then reused during the client-side hydration.
|
|
156
|
+
* > In SSR, the returned data will be serialized and sent to the client, then reused during the client-side hydration.
|
|
157
157
|
*
|
|
158
158
|
* Resolve can be stopped by throwing an error, which will be handled by the `errorHandler` function.
|
|
159
159
|
* It's common to throw a `NotFoundError` to display a 404 page.
|
|
@@ -162,6 +162,13 @@ export interface PagePrimitiveOptions<
|
|
|
162
162
|
*/
|
|
163
163
|
resolve?: (context: PageResolve<TConfig, TPropsParent>) => Async<TProps>;
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Default props to pass to the component when rendering the page.
|
|
167
|
+
*
|
|
168
|
+
* Resolved props from the `resolve` function will override these default props.
|
|
169
|
+
*/
|
|
170
|
+
props?: () => Partial<TProps>;
|
|
171
|
+
|
|
165
172
|
/**
|
|
166
173
|
* The component to render when the page is loaded.
|
|
167
174
|
*
|
|
@@ -189,6 +196,12 @@ export interface PagePrimitiveOptions<
|
|
|
189
196
|
*/
|
|
190
197
|
parent?: PagePrimitive<PageConfigSchema, TPropsParent, any>;
|
|
191
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Function to determine if the page can be accessed.
|
|
201
|
+
*
|
|
202
|
+
* If it returns false, the page will not be accessible and a 403 Forbidden error will be returned.
|
|
203
|
+
* This function can be used to implement permission-based access control.
|
|
204
|
+
*/
|
|
192
205
|
can?: () => boolean;
|
|
193
206
|
|
|
194
207
|
/**
|
|
@@ -314,7 +314,11 @@ export class ReactPageProvider {
|
|
|
314
314
|
if (!it.error) {
|
|
315
315
|
try {
|
|
316
316
|
const element = await this.createElement(it.route, {
|
|
317
|
+
// default props attached to page
|
|
318
|
+
...(it.route.props ? it.route.props() : {}),
|
|
319
|
+
// resolved props
|
|
317
320
|
...props,
|
|
321
|
+
// context props (from previous layers)
|
|
318
322
|
...context,
|
|
319
323
|
});
|
|
320
324
|
|
|
@@ -380,12 +384,6 @@ export class ReactPageProvider {
|
|
|
380
384
|
return { state };
|
|
381
385
|
}
|
|
382
386
|
|
|
383
|
-
protected createRedirectionLayer(redirect: string): CreateLayersResult {
|
|
384
|
-
return {
|
|
385
|
-
redirect,
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
|
|
389
387
|
protected getErrorHandler(route: PageRoute): ErrorHandler | undefined {
|
|
390
388
|
if (route.errorHandler) return route.errorHandler;
|
|
391
389
|
let parent = route.parent;
|
|
@@ -431,7 +429,7 @@ export class ReactPageProvider {
|
|
|
431
429
|
): string {
|
|
432
430
|
const found = this.pages.find((it) => it.name === page.options.name);
|
|
433
431
|
if (!found) {
|
|
434
|
-
throw new
|
|
432
|
+
throw new AlephaError(`Page ${page.options.name} not found`);
|
|
435
433
|
}
|
|
436
434
|
|
|
437
435
|
let url = found.path ?? "";
|
|
@@ -475,6 +473,7 @@ export class ReactPageProvider {
|
|
|
475
473
|
value: {
|
|
476
474
|
index,
|
|
477
475
|
path,
|
|
476
|
+
onError: this.getErrorHandler(page) ?? ((error) => this.renderError(error)),
|
|
478
477
|
},
|
|
479
478
|
},
|
|
480
479
|
element,
|
|
@@ -39,17 +39,13 @@ import {
|
|
|
39
39
|
const envSchema = t.object({
|
|
40
40
|
REACT_SSR_ENABLED: t.optional(t.boolean()),
|
|
41
41
|
REACT_ROOT_ID: t.text({ default: "root" }), // TODO: move to ReactPageProvider.options?
|
|
42
|
-
REACT_SERVER_TEMPLATE: t.optional(
|
|
43
|
-
t.text({
|
|
44
|
-
size: "rich",
|
|
45
|
-
}),
|
|
46
|
-
),
|
|
47
42
|
});
|
|
48
43
|
|
|
49
44
|
declare module "alepha" {
|
|
50
45
|
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
51
46
|
interface State {
|
|
52
47
|
"alepha.react.server.ssr"?: boolean;
|
|
48
|
+
"alepha.react.server.template"?: string;
|
|
53
49
|
}
|
|
54
50
|
}
|
|
55
51
|
|
|
@@ -171,7 +167,7 @@ export class ReactServerProvider {
|
|
|
171
167
|
|
|
172
168
|
public get template() {
|
|
173
169
|
return (
|
|
174
|
-
this.alepha.
|
|
170
|
+
this.alepha.store.get("alepha.react.server.template") ??
|
|
175
171
|
"<!DOCTYPE html><html lang='en'><head></head><body></body></html>"
|
|
176
172
|
);
|
|
177
173
|
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
Alepha,
|
|
4
|
-
type Static,
|
|
5
|
-
type TObject,
|
|
6
|
-
type TSchema,
|
|
7
|
-
t,
|
|
8
|
-
} from "alepha";
|
|
1
|
+
import type { TArray } from "alepha";
|
|
2
|
+
import { $inject, Alepha, type Static, t, type TObject, type TSchema, } from "alepha";
|
|
9
3
|
import { $logger } from "alepha/logger";
|
|
10
4
|
import type { ChangeEvent, InputHTMLAttributes } from "react";
|
|
11
5
|
|
|
@@ -221,17 +215,21 @@ export class FormModel<T extends TObject> {
|
|
|
221
215
|
if (!options.schema || !t.schema.isObject(schema)) {
|
|
222
216
|
return {};
|
|
223
217
|
}
|
|
218
|
+
|
|
224
219
|
if (prop in schema.properties) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
220
|
+
|
|
221
|
+
// // it's a nested object, create another proxy
|
|
222
|
+
// if (t.schema.isObject(schema.properties[prop])) {
|
|
223
|
+
// return this.createProxyFromSchema(
|
|
224
|
+
// options,
|
|
225
|
+
// schema.properties[prop],
|
|
226
|
+
// {
|
|
227
|
+
// parent: parent ? `${parent}.${prop}` : prop,
|
|
228
|
+
// store: context.store,
|
|
229
|
+
// },
|
|
230
|
+
// );
|
|
231
|
+
// }
|
|
232
|
+
|
|
235
233
|
return this.createInputFromSchema<T>(
|
|
236
234
|
prop as keyof Static<T> & string,
|
|
237
235
|
options,
|
|
@@ -253,7 +251,7 @@ export class FormModel<T extends TObject> {
|
|
|
253
251
|
parent: string;
|
|
254
252
|
store: Record<string, any>;
|
|
255
253
|
},
|
|
256
|
-
):
|
|
254
|
+
): BaseInputField {
|
|
257
255
|
const parent = context.parent || "";
|
|
258
256
|
const field = schema.properties?.[name];
|
|
259
257
|
if (!field) {
|
|
@@ -268,7 +266,6 @@ export class FormModel<T extends TObject> {
|
|
|
268
266
|
}
|
|
269
267
|
|
|
270
268
|
const isRequired = schema.required?.includes(name) ?? false;
|
|
271
|
-
|
|
272
269
|
const key = parent ? `${parent}.${name}` : name;
|
|
273
270
|
const path = `/${key.replaceAll(".", "/")}`;
|
|
274
271
|
|
|
@@ -278,7 +275,7 @@ export class FormModel<T extends TObject> {
|
|
|
278
275
|
|
|
279
276
|
if (context.store[key] === typedValue) {
|
|
280
277
|
// no change, do not update
|
|
281
|
-
// return;
|
|
278
|
+
// return; <- disabled for now, as some inputs may need to sync even if value is same
|
|
282
279
|
}
|
|
283
280
|
|
|
284
281
|
context.store[key] = typedValue;
|
|
@@ -291,6 +288,8 @@ export class FormModel<T extends TObject> {
|
|
|
291
288
|
id: this.id,
|
|
292
289
|
path: path,
|
|
293
290
|
value: typedValue,
|
|
291
|
+
}, {
|
|
292
|
+
catch: true
|
|
294
293
|
});
|
|
295
294
|
|
|
296
295
|
if (sync) {
|
|
@@ -299,6 +298,7 @@ export class FormModel<T extends TObject> {
|
|
|
299
298
|
);
|
|
300
299
|
if (inputElement instanceof HTMLInputElement) {
|
|
301
300
|
if (t.schema.isBoolean(field)) {
|
|
301
|
+
inputElement.value = value;
|
|
302
302
|
inputElement.checked = Boolean(value);
|
|
303
303
|
} else {
|
|
304
304
|
inputElement.value = value;
|
|
@@ -324,7 +324,15 @@ export class FormModel<T extends TObject> {
|
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
if (t.schema.isBoolean(field)) {
|
|
327
|
-
|
|
327
|
+
if (event.target.value === "true") {
|
|
328
|
+
set(true, false);
|
|
329
|
+
} else if (event.target.value === "false") {
|
|
330
|
+
set(false, false);
|
|
331
|
+
} else if (event.target.value === "") {
|
|
332
|
+
set(undefined, false);
|
|
333
|
+
} else {
|
|
334
|
+
set(event.target.checked, false);
|
|
335
|
+
}
|
|
328
336
|
} else {
|
|
329
337
|
set(event.target.value, false);
|
|
330
338
|
}
|
|
@@ -391,6 +399,39 @@ export class FormModel<T extends TObject> {
|
|
|
391
399
|
Object.assign(attr, customAttr);
|
|
392
400
|
}
|
|
393
401
|
|
|
402
|
+
// if type = object, add items: { [key: string]: InputField }
|
|
403
|
+
if (t.schema.isObject(field)) {
|
|
404
|
+
return {
|
|
405
|
+
path,
|
|
406
|
+
props: attr,
|
|
407
|
+
schema: field,
|
|
408
|
+
set,
|
|
409
|
+
form: this,
|
|
410
|
+
required,
|
|
411
|
+
items: this.createProxyFromSchema(
|
|
412
|
+
options,
|
|
413
|
+
field,
|
|
414
|
+
{
|
|
415
|
+
parent: key,
|
|
416
|
+
store: context.store,
|
|
417
|
+
},
|
|
418
|
+
)
|
|
419
|
+
} as ObjectInputField<any>;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// if type = array, add items: InputField[]
|
|
423
|
+
if (t.schema.isArray(field)) {
|
|
424
|
+
return {
|
|
425
|
+
path,
|
|
426
|
+
props: attr,
|
|
427
|
+
schema: field,
|
|
428
|
+
set,
|
|
429
|
+
form: this,
|
|
430
|
+
required,
|
|
431
|
+
items: [], // <- will be populated dynamically in the UI
|
|
432
|
+
} as ArrayInputField<any>;
|
|
433
|
+
}
|
|
434
|
+
|
|
394
435
|
return {
|
|
395
436
|
path,
|
|
396
437
|
props: attr,
|
|
@@ -466,9 +507,7 @@ export class FormModel<T extends TObject> {
|
|
|
466
507
|
}
|
|
467
508
|
|
|
468
509
|
export type SchemaToInput<T extends TObject> = {
|
|
469
|
-
[K in keyof T["properties"]]: T["properties"][K]
|
|
470
|
-
? SchemaToInput<T["properties"][K]>
|
|
471
|
-
: InputField;
|
|
510
|
+
[K in keyof T["properties"]]: InputField<T["properties"][K]>;
|
|
472
511
|
};
|
|
473
512
|
|
|
474
513
|
export interface FormEventLike {
|
|
@@ -476,13 +515,29 @@ export interface FormEventLike {
|
|
|
476
515
|
stopPropagation?: () => void;
|
|
477
516
|
}
|
|
478
517
|
|
|
479
|
-
export
|
|
518
|
+
export type InputField<T extends TSchema> =
|
|
519
|
+
T extends TObject
|
|
520
|
+
? ObjectInputField<T>
|
|
521
|
+
: T extends TArray<infer U>
|
|
522
|
+
? ArrayInputField<U>
|
|
523
|
+
: BaseInputField;
|
|
524
|
+
|
|
525
|
+
export interface BaseInputField {
|
|
480
526
|
path: string;
|
|
481
527
|
required: boolean;
|
|
482
528
|
props: InputHTMLAttributesLike;
|
|
483
529
|
schema: TSchema;
|
|
484
530
|
set: (value: any) => void;
|
|
485
531
|
form: FormModel<any>;
|
|
532
|
+
items?: any;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export interface ObjectInputField<T extends TObject> extends BaseInputField {
|
|
536
|
+
items: SchemaToInput<T>;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export interface ArrayInputField<T extends TSchema> extends BaseInputField {
|
|
540
|
+
items: Array<InputField<T>>
|
|
486
541
|
}
|
|
487
542
|
|
|
488
543
|
export type InputHTMLAttributesLike = Pick<
|
package/src/head/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { $module } from "alepha";
|
|
|
8
8
|
import { $head } from "./primitives/$head.ts";
|
|
9
9
|
import type { Head } from "./interfaces/Head.ts";
|
|
10
10
|
import { ServerHeadProvider } from "./providers/ServerHeadProvider.ts";
|
|
11
|
+
import { HeadProvider } from "./providers/HeadProvider.ts";
|
|
11
12
|
|
|
12
13
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
13
14
|
|
|
@@ -43,5 +44,5 @@ declare module "@alepha/react" {
|
|
|
43
44
|
export const AlephaReactHead = $module({
|
|
44
45
|
name: "alepha.react.head",
|
|
45
46
|
primitives: [$head],
|
|
46
|
-
services: [AlephaReact, ServerHeadProvider],
|
|
47
|
+
services: [AlephaReact, ServerHeadProvider, HeadProvider],
|
|
47
48
|
});
|
|
@@ -15,6 +15,7 @@ export class I18nProvider<
|
|
|
15
15
|
protected cookie = $cookie({
|
|
16
16
|
name: "lang",
|
|
17
17
|
schema: t.text(),
|
|
18
|
+
ttl: [1, "year"]
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
public readonly registry: Array<{
|
|
@@ -170,7 +171,7 @@ export class I18nProvider<
|
|
|
170
171
|
options: I18nLocalizeOptions = {},
|
|
171
172
|
) => {
|
|
172
173
|
// Handle numbers
|
|
173
|
-
if (typeof value === "number") {
|
|
174
|
+
if (typeof value === "number" && !options.date) {
|
|
174
175
|
return new Intl.NumberFormat(this.lang, options.number).format(value);
|
|
175
176
|
}
|
|
176
177
|
|
|
@@ -178,7 +179,8 @@ export class I18nProvider<
|
|
|
178
179
|
if (
|
|
179
180
|
value instanceof Date ||
|
|
180
181
|
this.dateTimeProvider.isDateTime(value) ||
|
|
181
|
-
(typeof value === "string" && options.date)
|
|
182
|
+
(typeof value === "string" && options.date) ||
|
|
183
|
+
(typeof value === "number" && options.date)
|
|
182
184
|
) {
|
|
183
185
|
// convert to DateTime with locale applied
|
|
184
186
|
let dt = this.dateTimeProvider.of(value);
|