@alepha/react 0.11.3 → 0.11.4
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 +23 -4
- package/dist/index.browser.js +322 -31
- package/dist/index.browser.js.map +1 -1
- package/dist/{index.d.ts → index.d.mts} +374 -65
- package/dist/index.d.mts.map +1 -0
- package/dist/{index.js → index.mjs} +389 -90
- package/dist/index.mjs.map +1 -0
- package/package.json +13 -12
- package/src/components/NestedView.tsx +4 -4
- package/src/descriptors/$page.ts +21 -25
- package/src/hooks/useAction.ts +467 -0
- package/src/hooks/useActive.ts +1 -7
- package/src/hooks/useEvents.ts +51 -0
- package/src/index.browser.ts +4 -0
- package/src/index.shared.ts +2 -1
- package/src/index.ts +73 -1
- package/src/providers/ReactBrowserRouterProvider.ts +14 -0
- package/src/providers/ReactPageProvider.ts +34 -1
- package/src/providers/ReactServerProvider.ts +48 -68
- package/src/services/ReactPageServerService.ts +43 -0
- package/src/services/ReactPageService.ts +24 -0
- package/src/services/ReactRouter.ts +21 -0
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/src/hooks/useRouterEvents.ts +0 -66
package/README.md
CHANGED
|
@@ -147,6 +147,29 @@ Hook to get a virtual client for the specified scope.
|
|
|
147
147
|
|
|
148
148
|
It's the React-hook version of `$client()`, from `AlephaServerLinks` module.
|
|
149
149
|
|
|
150
|
+
#### useEvents()
|
|
151
|
+
|
|
152
|
+
Allow subscribing to multiple Alepha events. See {@link Hooks} for available events.
|
|
153
|
+
|
|
154
|
+
useEvents is fully typed to ensure correct event callback signatures.
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
useEvents(
|
|
158
|
+
{
|
|
159
|
+
"react:transition:begin": (ev) => {
|
|
160
|
+
console.log("Transition began to:", ev.to);
|
|
161
|
+
},
|
|
162
|
+
"react:transition:error": {
|
|
163
|
+
priority: "first",
|
|
164
|
+
callback: (ev) => {
|
|
165
|
+
console.error("Transition error:", ev.error);
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
[],
|
|
170
|
+
);
|
|
171
|
+
```
|
|
172
|
+
|
|
150
173
|
#### useInject()
|
|
151
174
|
|
|
152
175
|
Hook to inject a service instance.
|
|
@@ -170,10 +193,6 @@ class App {
|
|
|
170
193
|
const router = useRouter<App>();
|
|
171
194
|
router.go("home"); // typesafe
|
|
172
195
|
|
|
173
|
-
#### useRouterEvents()
|
|
174
|
-
|
|
175
|
-
Subscribe to various router events.
|
|
176
|
-
|
|
177
196
|
#### useStore()
|
|
178
197
|
|
|
179
198
|
Hook to access and mutate the Alepha state.
|
package/dist/index.browser.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { $env, $hook, $inject, $module, Alepha, AlephaError, Descriptor, KIND, createDescriptor, t } from "@alepha/core";
|
|
2
|
+
import { AlephaDateTime, DateTimeProvider } from "@alepha/datetime";
|
|
2
3
|
import { AlephaServer, HttpClient } from "@alepha/server";
|
|
3
4
|
import { AlephaServerLinks, LinkProvider } from "@alepha/server-links";
|
|
4
|
-
import { DateTimeProvider } from "@alepha/datetime";
|
|
5
5
|
import { $logger } from "@alepha/logger";
|
|
6
6
|
import { RouterProvider } from "@alepha/router";
|
|
7
|
-
import React, { StrictMode, createContext, createElement, memo, use, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import React, { StrictMode, createContext, createElement, memo, use, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
10
10
|
|
|
11
|
+
//#region src/services/ReactPageService.ts
|
|
12
|
+
var ReactPageService = class {
|
|
13
|
+
fetch(pathname, options = {}) {
|
|
14
|
+
throw new AlephaError("Fetch is not available for this environment.");
|
|
15
|
+
}
|
|
16
|
+
render(name, options = {}) {
|
|
17
|
+
throw new AlephaError("Render is not available for this environment.");
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
11
22
|
//#region src/descriptors/$page.ts
|
|
12
23
|
/**
|
|
13
24
|
* Main descriptor for defining a React route in the application.
|
|
@@ -101,6 +112,7 @@ const $page = (options) => {
|
|
|
101
112
|
return createDescriptor(PageDescriptor, options);
|
|
102
113
|
};
|
|
103
114
|
var PageDescriptor = class extends Descriptor {
|
|
115
|
+
reactPageService = $inject(ReactPageService);
|
|
104
116
|
onInit() {
|
|
105
117
|
if (this.options.static) this.options.cache ??= { store: {
|
|
106
118
|
provider: "memory",
|
|
@@ -111,14 +123,16 @@ var PageDescriptor = class extends Descriptor {
|
|
|
111
123
|
return this.options.name ?? this.config.propertyKey;
|
|
112
124
|
}
|
|
113
125
|
/**
|
|
114
|
-
* For testing or build purposes
|
|
126
|
+
* For testing or build purposes.
|
|
127
|
+
*
|
|
128
|
+
* This will render the page (HTML layout included or not) and return the HTML + context.
|
|
115
129
|
* Only valid for server-side rendering, it will throw an error if called on the client-side.
|
|
116
130
|
*/
|
|
117
131
|
async render(options) {
|
|
118
|
-
|
|
132
|
+
return this.reactPageService.render(this.name, options);
|
|
119
133
|
}
|
|
120
134
|
async fetch(options) {
|
|
121
|
-
|
|
135
|
+
return this.reactPageService.fetch(this.options.path || "", options);
|
|
122
136
|
}
|
|
123
137
|
match(url) {
|
|
124
138
|
return false;
|
|
@@ -365,29 +379,38 @@ const useAlepha = () => {
|
|
|
365
379
|
};
|
|
366
380
|
|
|
367
381
|
//#endregion
|
|
368
|
-
//#region src/hooks/
|
|
382
|
+
//#region src/hooks/useEvents.ts
|
|
369
383
|
/**
|
|
370
|
-
*
|
|
384
|
+
* Allow subscribing to multiple Alepha events. See {@link Hooks} for available events.
|
|
385
|
+
*
|
|
386
|
+
* useEvents is fully typed to ensure correct event callback signatures.
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```tsx
|
|
390
|
+
* useEvents(
|
|
391
|
+
* {
|
|
392
|
+
* "react:transition:begin": (ev) => {
|
|
393
|
+
* console.log("Transition began to:", ev.to);
|
|
394
|
+
* },
|
|
395
|
+
* "react:transition:error": {
|
|
396
|
+
* priority: "first",
|
|
397
|
+
* callback: (ev) => {
|
|
398
|
+
* console.error("Transition error:", ev.error);
|
|
399
|
+
* },
|
|
400
|
+
* },
|
|
401
|
+
* },
|
|
402
|
+
* [],
|
|
403
|
+
* );
|
|
404
|
+
* ```
|
|
371
405
|
*/
|
|
372
|
-
const
|
|
406
|
+
const useEvents = (opts, deps) => {
|
|
373
407
|
const alepha = useAlepha();
|
|
374
408
|
useEffect(() => {
|
|
375
409
|
if (!alepha.isBrowser()) return;
|
|
376
|
-
const cb = (callback) => {
|
|
377
|
-
if (typeof callback === "function") return { callback };
|
|
378
|
-
return callback;
|
|
379
|
-
};
|
|
380
410
|
const subs = [];
|
|
381
|
-
const
|
|
382
|
-
const onEnd = opts.onEnd;
|
|
383
|
-
const onError = opts.onError;
|
|
384
|
-
const onSuccess = opts.onSuccess;
|
|
385
|
-
if (onBegin) subs.push(alepha.events.on("react:transition:begin", cb(onBegin)));
|
|
386
|
-
if (onEnd) subs.push(alepha.events.on("react:transition:end", cb(onEnd)));
|
|
387
|
-
if (onError) subs.push(alepha.events.on("react:transition:error", cb(onError)));
|
|
388
|
-
if (onSuccess) subs.push(alepha.events.on("react:transition:success", cb(onSuccess)));
|
|
411
|
+
for (const [name, hook] of Object.entries(opts)) subs.push(alepha.events.on(name, hook));
|
|
389
412
|
return () => {
|
|
390
|
-
for (const
|
|
413
|
+
for (const clear of subs) clear();
|
|
391
414
|
};
|
|
392
415
|
}, deps);
|
|
393
416
|
};
|
|
@@ -483,8 +506,8 @@ const NestedView = (props) => {
|
|
|
483
506
|
const [animation, setAnimation] = useState("");
|
|
484
507
|
const animationExitDuration = useRef(0);
|
|
485
508
|
const animationExitNow = useRef(0);
|
|
486
|
-
|
|
487
|
-
|
|
509
|
+
useEvents({
|
|
510
|
+
"react:transition:begin": async ({ previous, state: state$1 }) => {
|
|
488
511
|
const layer = previous.layers[index];
|
|
489
512
|
if (`${state$1.url.pathname}/`.startsWith(`${layer?.path}/`)) return;
|
|
490
513
|
const animationExit = parseAnimation(layer.route?.animation, state$1, "exit");
|
|
@@ -499,7 +522,7 @@ const NestedView = (props) => {
|
|
|
499
522
|
setAnimation("");
|
|
500
523
|
}
|
|
501
524
|
},
|
|
502
|
-
|
|
525
|
+
"react:transition:end": async ({ state: state$1 }) => {
|
|
503
526
|
const layer = state$1.layers[index];
|
|
504
527
|
if (animationExitNow.current) {
|
|
505
528
|
const duration = animationExitDuration.current;
|
|
@@ -586,9 +609,34 @@ var ReactPageProvider = class {
|
|
|
586
609
|
getPages() {
|
|
587
610
|
return this.pages;
|
|
588
611
|
}
|
|
612
|
+
getConcretePages() {
|
|
613
|
+
const pages = [];
|
|
614
|
+
for (const page of this.pages) {
|
|
615
|
+
if (page.children && page.children.length > 0) continue;
|
|
616
|
+
const fullPath = this.pathname(page.name);
|
|
617
|
+
if (fullPath.includes(":") || fullPath.includes("*")) {
|
|
618
|
+
if (typeof page.static === "object") {
|
|
619
|
+
const entries = page.static.entries;
|
|
620
|
+
if (entries && entries.length > 0) for (const entry of entries) {
|
|
621
|
+
const params = entry.params;
|
|
622
|
+
const path = this.compile(page.path ?? "", params);
|
|
623
|
+
if (!path.includes(":") && !path.includes("*")) pages.push({
|
|
624
|
+
...page,
|
|
625
|
+
name: params[Object.keys(params)[0]],
|
|
626
|
+
path,
|
|
627
|
+
...entry
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
pages.push(page);
|
|
634
|
+
}
|
|
635
|
+
return pages;
|
|
636
|
+
}
|
|
589
637
|
page(name) {
|
|
590
638
|
for (const page of this.pages) if (page.name === name) return page;
|
|
591
|
-
throw new
|
|
639
|
+
throw new AlephaError(`Page '${name}' not found`);
|
|
592
640
|
}
|
|
593
641
|
pathname(name, options = {}) {
|
|
594
642
|
const page = this.page(name);
|
|
@@ -901,6 +949,7 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
|
901
949
|
onError: () => null,
|
|
902
950
|
meta
|
|
903
951
|
};
|
|
952
|
+
await this.alepha.events.emit("react:action:begin", { type: "transition" });
|
|
904
953
|
await this.alepha.events.emit("react:transition:begin", {
|
|
905
954
|
previous: this.alepha.state.get("react.router.state"),
|
|
906
955
|
state
|
|
@@ -921,6 +970,7 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
|
921
970
|
index: 0,
|
|
922
971
|
path: "/"
|
|
923
972
|
});
|
|
973
|
+
await this.alepha.events.emit("react:action:success", { type: "transition" });
|
|
924
974
|
await this.alepha.events.emit("react:transition:success", { state });
|
|
925
975
|
} catch (e) {
|
|
926
976
|
this.log.error("Transition has failed", e);
|
|
@@ -930,6 +980,10 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
|
930
980
|
index: 0,
|
|
931
981
|
path: "/"
|
|
932
982
|
}];
|
|
983
|
+
await this.alepha.events.emit("react:action:error", {
|
|
984
|
+
type: "transition",
|
|
985
|
+
error: e
|
|
986
|
+
});
|
|
933
987
|
await this.alepha.events.emit("react:transition:error", {
|
|
934
988
|
error: e,
|
|
935
989
|
state
|
|
@@ -940,6 +994,7 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
|
|
|
940
994
|
if (state.layers[i]?.name !== layer.name) this.pageApi.page(layer.name)?.onLeave?.();
|
|
941
995
|
}
|
|
942
996
|
this.alepha.state.set("react.router.state", state);
|
|
997
|
+
await this.alepha.events.emit("react:action:end", { type: "transition" });
|
|
943
998
|
await this.alepha.events.emit("react:transition:end", { state });
|
|
944
999
|
}
|
|
945
1000
|
root(state) {
|
|
@@ -1135,9 +1190,18 @@ var ReactRouter = class {
|
|
|
1135
1190
|
get pages() {
|
|
1136
1191
|
return this.pageApi.getPages();
|
|
1137
1192
|
}
|
|
1193
|
+
get concretePages() {
|
|
1194
|
+
return this.pageApi.getConcretePages();
|
|
1195
|
+
}
|
|
1138
1196
|
get browser() {
|
|
1139
1197
|
if (this.alepha.isBrowser()) return this.alepha.inject(ReactBrowserProvider);
|
|
1140
1198
|
}
|
|
1199
|
+
isActive(href, options = {}) {
|
|
1200
|
+
const current = this.state.url.pathname;
|
|
1201
|
+
let isActive = current === href || current === `${href}/` || `${current}/` === href;
|
|
1202
|
+
if (options.startWith && !isActive) isActive = current.startsWith(href);
|
|
1203
|
+
return isActive;
|
|
1204
|
+
}
|
|
1141
1205
|
path(name, config = {}) {
|
|
1142
1206
|
return this.pageApi.pathname(name, {
|
|
1143
1207
|
params: {
|
|
@@ -1271,19 +1335,245 @@ const Link = (props) => {
|
|
|
1271
1335
|
};
|
|
1272
1336
|
var Link_default = Link;
|
|
1273
1337
|
|
|
1338
|
+
//#endregion
|
|
1339
|
+
//#region src/hooks/useAction.ts
|
|
1340
|
+
/**
|
|
1341
|
+
* Hook for handling async actions with automatic error handling and event emission.
|
|
1342
|
+
*
|
|
1343
|
+
* By default, prevents concurrent executions - if an action is running and you call it again,
|
|
1344
|
+
* the second call will be ignored. Use `debounce` option to delay execution instead.
|
|
1345
|
+
*
|
|
1346
|
+
* Emits lifecycle events:
|
|
1347
|
+
* - `react:action:begin` - When action starts
|
|
1348
|
+
* - `react:action:success` - When action completes successfully
|
|
1349
|
+
* - `react:action:error` - When action throws an error
|
|
1350
|
+
* - `react:action:end` - Always emitted at the end
|
|
1351
|
+
*
|
|
1352
|
+
* @example Basic usage
|
|
1353
|
+
* ```tsx
|
|
1354
|
+
* const action = useAction({
|
|
1355
|
+
* handler: async (data) => {
|
|
1356
|
+
* await api.save(data);
|
|
1357
|
+
* }
|
|
1358
|
+
* }, []);
|
|
1359
|
+
*
|
|
1360
|
+
* <button onClick={() => action.run(data)} disabled={action.loading}>
|
|
1361
|
+
* Save
|
|
1362
|
+
* </button>
|
|
1363
|
+
* ```
|
|
1364
|
+
*
|
|
1365
|
+
* @example With debounce (search input)
|
|
1366
|
+
* ```tsx
|
|
1367
|
+
* const search = useAction({
|
|
1368
|
+
* handler: async (query: string) => {
|
|
1369
|
+
* await api.search(query);
|
|
1370
|
+
* },
|
|
1371
|
+
* debounce: 300 // Wait 300ms after last call
|
|
1372
|
+
* }, []);
|
|
1373
|
+
*
|
|
1374
|
+
* <input onChange={(e) => search.run(e.target.value)} />
|
|
1375
|
+
* ```
|
|
1376
|
+
*
|
|
1377
|
+
* @example Run on component mount
|
|
1378
|
+
* ```tsx
|
|
1379
|
+
* const fetchData = useAction({
|
|
1380
|
+
* handler: async () => {
|
|
1381
|
+
* const data = await api.getData();
|
|
1382
|
+
* return data;
|
|
1383
|
+
* },
|
|
1384
|
+
* runOnInit: true // Runs once when component mounts
|
|
1385
|
+
* }, []);
|
|
1386
|
+
* ```
|
|
1387
|
+
*
|
|
1388
|
+
* @example Run periodically (polling)
|
|
1389
|
+
* ```tsx
|
|
1390
|
+
* const pollStatus = useAction({
|
|
1391
|
+
* handler: async () => {
|
|
1392
|
+
* const status = await api.getStatus();
|
|
1393
|
+
* return status;
|
|
1394
|
+
* },
|
|
1395
|
+
* runEvery: 5000 // Run every 5 seconds
|
|
1396
|
+
* }, []);
|
|
1397
|
+
*
|
|
1398
|
+
* // Or with duration tuple
|
|
1399
|
+
* const pollStatus = useAction({
|
|
1400
|
+
* handler: async () => {
|
|
1401
|
+
* const status = await api.getStatus();
|
|
1402
|
+
* return status;
|
|
1403
|
+
* },
|
|
1404
|
+
* runEvery: [30, 'seconds'] // Run every 30 seconds
|
|
1405
|
+
* }, []);
|
|
1406
|
+
* ```
|
|
1407
|
+
*
|
|
1408
|
+
* @example With AbortController
|
|
1409
|
+
* ```tsx
|
|
1410
|
+
* const fetch = useAction({
|
|
1411
|
+
* handler: async (url, { signal }) => {
|
|
1412
|
+
* const response = await fetch(url, { signal });
|
|
1413
|
+
* return response.json();
|
|
1414
|
+
* }
|
|
1415
|
+
* }, []);
|
|
1416
|
+
* // Automatically cancelled on unmount or when new request starts
|
|
1417
|
+
* ```
|
|
1418
|
+
*
|
|
1419
|
+
* @example With error handling
|
|
1420
|
+
* ```tsx
|
|
1421
|
+
* const deleteAction = useAction({
|
|
1422
|
+
* handler: async (id: string) => {
|
|
1423
|
+
* await api.delete(id);
|
|
1424
|
+
* },
|
|
1425
|
+
* onError: (error) => {
|
|
1426
|
+
* if (error.code === 'NOT_FOUND') {
|
|
1427
|
+
* // Custom error handling
|
|
1428
|
+
* }
|
|
1429
|
+
* }
|
|
1430
|
+
* }, []);
|
|
1431
|
+
*
|
|
1432
|
+
* {deleteAction.error && <div>Error: {deleteAction.error.message}</div>}
|
|
1433
|
+
* ```
|
|
1434
|
+
*
|
|
1435
|
+
* @example Global error handling
|
|
1436
|
+
* ```tsx
|
|
1437
|
+
* // In your root app setup
|
|
1438
|
+
* alepha.events.on("react:action:error", ({ error }) => {
|
|
1439
|
+
* toast.danger(error.message);
|
|
1440
|
+
* Sentry.captureException(error);
|
|
1441
|
+
* });
|
|
1442
|
+
* ```
|
|
1443
|
+
*/
|
|
1444
|
+
function useAction(options, deps) {
|
|
1445
|
+
const alepha = useAlepha();
|
|
1446
|
+
const dateTimeProvider = useInject(DateTimeProvider);
|
|
1447
|
+
const [loading, setLoading] = useState(false);
|
|
1448
|
+
const [error, setError] = useState();
|
|
1449
|
+
const isExecutingRef = useRef(false);
|
|
1450
|
+
const debounceTimerRef = useRef(void 0);
|
|
1451
|
+
const abortControllerRef = useRef(void 0);
|
|
1452
|
+
const isMountedRef = useRef(true);
|
|
1453
|
+
const intervalRef = useRef(void 0);
|
|
1454
|
+
useEffect(() => {
|
|
1455
|
+
return () => {
|
|
1456
|
+
isMountedRef.current = false;
|
|
1457
|
+
if (debounceTimerRef.current) {
|
|
1458
|
+
dateTimeProvider.clearTimeout(debounceTimerRef.current);
|
|
1459
|
+
debounceTimerRef.current = void 0;
|
|
1460
|
+
}
|
|
1461
|
+
if (intervalRef.current) {
|
|
1462
|
+
dateTimeProvider.clearInterval(intervalRef.current);
|
|
1463
|
+
intervalRef.current = void 0;
|
|
1464
|
+
}
|
|
1465
|
+
if (abortControllerRef.current) {
|
|
1466
|
+
abortControllerRef.current.abort();
|
|
1467
|
+
abortControllerRef.current = void 0;
|
|
1468
|
+
}
|
|
1469
|
+
};
|
|
1470
|
+
}, []);
|
|
1471
|
+
const executeAction = useCallback(async (...args) => {
|
|
1472
|
+
if (isExecutingRef.current) return;
|
|
1473
|
+
if (abortControllerRef.current) abortControllerRef.current.abort();
|
|
1474
|
+
const abortController = new AbortController();
|
|
1475
|
+
abortControllerRef.current = abortController;
|
|
1476
|
+
isExecutingRef.current = true;
|
|
1477
|
+
setLoading(true);
|
|
1478
|
+
setError(void 0);
|
|
1479
|
+
await alepha.events.emit("react:action:begin", {
|
|
1480
|
+
type: "custom",
|
|
1481
|
+
id: options.id
|
|
1482
|
+
});
|
|
1483
|
+
try {
|
|
1484
|
+
const result = await options.handler(...args, { signal: abortController.signal });
|
|
1485
|
+
if (!isMountedRef.current || abortController.signal.aborted) return;
|
|
1486
|
+
await alepha.events.emit("react:action:success", {
|
|
1487
|
+
type: "custom",
|
|
1488
|
+
id: options.id
|
|
1489
|
+
});
|
|
1490
|
+
if (options.onSuccess) await options.onSuccess(result);
|
|
1491
|
+
return result;
|
|
1492
|
+
} catch (err) {
|
|
1493
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
1494
|
+
if (!isMountedRef.current) return;
|
|
1495
|
+
const error$1 = err;
|
|
1496
|
+
setError(error$1);
|
|
1497
|
+
await alepha.events.emit("react:action:error", {
|
|
1498
|
+
type: "custom",
|
|
1499
|
+
id: options.id,
|
|
1500
|
+
error: error$1
|
|
1501
|
+
});
|
|
1502
|
+
if (options.onError) await options.onError(error$1);
|
|
1503
|
+
else throw error$1;
|
|
1504
|
+
} finally {
|
|
1505
|
+
isExecutingRef.current = false;
|
|
1506
|
+
setLoading(false);
|
|
1507
|
+
await alepha.events.emit("react:action:end", {
|
|
1508
|
+
type: "custom",
|
|
1509
|
+
id: options.id
|
|
1510
|
+
});
|
|
1511
|
+
if (abortControllerRef.current === abortController) abortControllerRef.current = void 0;
|
|
1512
|
+
}
|
|
1513
|
+
}, [
|
|
1514
|
+
...deps,
|
|
1515
|
+
options.id,
|
|
1516
|
+
options.onError,
|
|
1517
|
+
options.onSuccess
|
|
1518
|
+
]);
|
|
1519
|
+
const handler = useCallback(async (...args) => {
|
|
1520
|
+
if (options.debounce) {
|
|
1521
|
+
if (debounceTimerRef.current) dateTimeProvider.clearTimeout(debounceTimerRef.current);
|
|
1522
|
+
return new Promise((resolve) => {
|
|
1523
|
+
debounceTimerRef.current = dateTimeProvider.createTimeout(async () => {
|
|
1524
|
+
resolve(await executeAction(...args));
|
|
1525
|
+
}, options.debounce ?? 0);
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
return executeAction(...args);
|
|
1529
|
+
}, [executeAction, options.debounce]);
|
|
1530
|
+
const cancel = useCallback(() => {
|
|
1531
|
+
if (debounceTimerRef.current) {
|
|
1532
|
+
dateTimeProvider.clearTimeout(debounceTimerRef.current);
|
|
1533
|
+
debounceTimerRef.current = void 0;
|
|
1534
|
+
}
|
|
1535
|
+
if (abortControllerRef.current) {
|
|
1536
|
+
abortControllerRef.current.abort();
|
|
1537
|
+
abortControllerRef.current = void 0;
|
|
1538
|
+
}
|
|
1539
|
+
if (isMountedRef.current) {
|
|
1540
|
+
isExecutingRef.current = false;
|
|
1541
|
+
setLoading(false);
|
|
1542
|
+
}
|
|
1543
|
+
}, []);
|
|
1544
|
+
useEffect(() => {
|
|
1545
|
+
if (options.runOnInit) handler(...[]);
|
|
1546
|
+
}, []);
|
|
1547
|
+
useEffect(() => {
|
|
1548
|
+
if (!options.runEvery) return;
|
|
1549
|
+
intervalRef.current = dateTimeProvider.createInterval(() => handler(...[]), options.runEvery, true);
|
|
1550
|
+
return () => {
|
|
1551
|
+
if (intervalRef.current) {
|
|
1552
|
+
dateTimeProvider.clearInterval(intervalRef.current);
|
|
1553
|
+
intervalRef.current = void 0;
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
}, [handler, options.runEvery]);
|
|
1557
|
+
return {
|
|
1558
|
+
run: handler,
|
|
1559
|
+
loading,
|
|
1560
|
+
error,
|
|
1561
|
+
cancel
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1274
1565
|
//#endregion
|
|
1275
1566
|
//#region src/hooks/useActive.ts
|
|
1276
1567
|
const useActive = (args) => {
|
|
1277
1568
|
const router = useRouter();
|
|
1278
1569
|
const [isPending, setPending] = useState(false);
|
|
1279
|
-
|
|
1570
|
+
useRouterState().url.pathname;
|
|
1280
1571
|
const options = typeof args === "string" ? { href: args } : {
|
|
1281
1572
|
...args,
|
|
1282
1573
|
href: args.href
|
|
1283
1574
|
};
|
|
1284
1575
|
const href = options.href;
|
|
1285
|
-
|
|
1286
|
-
if (options.startWith && !isActive) isActive = current.startsWith(href);
|
|
1576
|
+
const isActive = router.isActive(href, options);
|
|
1287
1577
|
return {
|
|
1288
1578
|
isPending,
|
|
1289
1579
|
isActive,
|
|
@@ -1395,11 +1685,12 @@ const AlephaReact = $module({
|
|
|
1395
1685
|
ReactBrowserRouterProvider,
|
|
1396
1686
|
ReactBrowserProvider,
|
|
1397
1687
|
ReactRouter,
|
|
1398
|
-
ReactBrowserRendererProvider
|
|
1688
|
+
ReactBrowserRendererProvider,
|
|
1689
|
+
ReactPageService
|
|
1399
1690
|
],
|
|
1400
|
-
register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactBrowserRendererProvider).with(ReactRouter)
|
|
1691
|
+
register: (alepha) => alepha.with(AlephaDateTime).with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactBrowserRendererProvider).with(ReactRouter)
|
|
1401
1692
|
});
|
|
1402
1693
|
|
|
1403
1694
|
//#endregion
|
|
1404
|
-
export { $page, AlephaContext, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, ErrorViewer_default as ErrorViewer, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, ReactBrowserProvider, ReactBrowserRouterProvider, ReactPageProvider, ReactRouter, Redirection, RouterLayerContext, isPageRoute, ssrSchemaLoading, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter,
|
|
1695
|
+
export { $page, AlephaContext, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, ErrorViewer_default as ErrorViewer, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, ReactBrowserProvider, ReactBrowserRouterProvider, ReactPageProvider, ReactRouter, Redirection, RouterLayerContext, isPageRoute, ssrSchemaLoading, useAction, useActive, useAlepha, useClient, useEvents, useInject, useQueryParams, useRouter, useRouterState, useSchema, useStore };
|
|
1405
1696
|
//# sourceMappingURL=index.browser.js.map
|