@djvlc/runtime-host-react 1.1.3 → 1.1.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/dist/index.cjs CHANGED
@@ -1,982 +1 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- DJVProvider: () => DJVProvider,
24
- DJVRenderer: () => DJVRenderer,
25
- RuntimeContext: () => RuntimeContext,
26
- RuntimeProvider: () => RuntimeProvider,
27
- useAction: () => useAction,
28
- useAsync: () => useAsync,
29
- useComponentState: () => useComponentState,
30
- useDJVRuntime: () => useDJVRuntime,
31
- useData: () => useData,
32
- useDebouncedAction: () => useDebouncedAction,
33
- useHostApi: () => useHostApi,
34
- useLifecycle: () => useLifecycle,
35
- usePageInfo: () => usePageInfo,
36
- usePrevious: () => usePrevious,
37
- useQuery: () => useQuery,
38
- useReactRuntime: () => useReactRuntime,
39
- useRuntimeContext: () => useRuntimeContext,
40
- useRuntimeEvent: () => useRuntimeEvent,
41
- useRuntimeState: () => useRuntimeState,
42
- useRuntimeStateWritable: () => useRuntimeStateWritable,
43
- useWhen: () => useWhen
44
- });
45
- module.exports = __toCommonJS(index_exports);
46
-
47
- // src/react-runtime.ts
48
- var import_react = require("react");
49
- var import_runtime_core = require("@djvlc/runtime-core");
50
- var defaultMetrics = {
51
- initTime: 0,
52
- loadTime: 0,
53
- renderTime: 0,
54
- totalTime: 0,
55
- initTimestamp: null,
56
- readyTimestamp: null
57
- };
58
- function useReactRuntime(options) {
59
- const [runtime, setRuntime] = (0, import_react.useState)(null);
60
- const [loading, setLoading] = (0, import_react.useState)(true);
61
- const [phase, setPhase] = (0, import_react.useState)("idle");
62
- const [page, setPage] = (0, import_react.useState)(null);
63
- const [error, setError] = (0, import_react.useState)(null);
64
- const [hostApi, setHostApi] = (0, import_react.useState)(null);
65
- const [metrics, setMetrics] = (0, import_react.useState)(defaultMetrics);
66
- const runtimeRef = (0, import_react.useRef)(null);
67
- const timingRef = (0, import_react.useRef)({
68
- startTime: 0,
69
- initStartTime: 0,
70
- loadStartTime: 0,
71
- renderStartTime: 0
72
- });
73
- const isReady = (0, import_react.useMemo)(() => phase === "ready", [phase]);
74
- const hasError = (0, import_react.useMemo)(() => phase === "error" || error !== null, [phase, error]);
75
- const init = (0, import_react.useCallback)(async () => {
76
- const container = options.containerRef?.current;
77
- if (!container) {
78
- throw new Error("Container element not found");
79
- }
80
- timingRef.current.startTime = performance.now();
81
- timingRef.current.initStartTime = timingRef.current.startTime;
82
- if (options.enableMetrics) {
83
- setMetrics((prev) => ({
84
- ...prev,
85
- initTimestamp: Date.now()
86
- }));
87
- }
88
- const runtimeInstance = (0, import_runtime_core.createRuntime)({
89
- ...options,
90
- container,
91
- onError: (err) => {
92
- setError(err);
93
- options.onError?.(err);
94
- },
95
- onEvent: (event) => {
96
- options.onEvent?.(event);
97
- }
98
- });
99
- runtimeRef.current = runtimeInstance;
100
- setRuntime(runtimeInstance);
101
- runtimeInstance.onStateChange((state) => {
102
- setPhase(state.phase);
103
- setLoading(state.phase !== "ready" && state.phase !== "error");
104
- if (state.page) {
105
- setPage(state.page);
106
- }
107
- if (state.error) {
108
- setError(state.error);
109
- }
110
- });
111
- await runtimeInstance.init();
112
- setHostApi(runtimeInstance.getHostApi());
113
- if (options.enableMetrics) {
114
- setMetrics((prev) => ({
115
- ...prev,
116
- initTime: performance.now() - timingRef.current.initStartTime
117
- }));
118
- }
119
- }, [options]);
120
- const load = (0, import_react.useCallback)(async () => {
121
- if (!runtimeRef.current) {
122
- throw new Error("Runtime not initialized");
123
- }
124
- timingRef.current.loadStartTime = performance.now();
125
- const result = await runtimeRef.current.load();
126
- setPage(result);
127
- setHostApi(runtimeRef.current.getHostApi());
128
- if (options.enableMetrics) {
129
- setMetrics((prev) => ({
130
- ...prev,
131
- loadTime: performance.now() - timingRef.current.loadStartTime
132
- }));
133
- }
134
- return result;
135
- }, [options.enableMetrics]);
136
- const render = (0, import_react.useCallback)(async () => {
137
- if (!runtimeRef.current) {
138
- throw new Error("Runtime not initialized");
139
- }
140
- timingRef.current.renderStartTime = performance.now();
141
- await runtimeRef.current.render();
142
- setLoading(false);
143
- if (options.enableMetrics) {
144
- const now = performance.now();
145
- setMetrics((prev) => ({
146
- ...prev,
147
- renderTime: now - timingRef.current.renderStartTime,
148
- totalTime: now - timingRef.current.startTime,
149
- readyTimestamp: Date.now()
150
- }));
151
- }
152
- }, [options.enableMetrics]);
153
- const destroy = (0, import_react.useCallback)(() => {
154
- runtimeRef.current?.destroy();
155
- runtimeRef.current = null;
156
- setRuntime(null);
157
- setHostApi(null);
158
- setPage(null);
159
- setError(null);
160
- setPhase("idle");
161
- setLoading(true);
162
- setMetrics(defaultMetrics);
163
- }, []);
164
- const reload = (0, import_react.useCallback)(async () => {
165
- if (!runtimeRef.current) {
166
- throw new Error("Runtime not initialized");
167
- }
168
- setError(null);
169
- setLoading(true);
170
- await load();
171
- await render();
172
- }, [load, render]);
173
- const setVariable = (0, import_react.useCallback)((key, value) => {
174
- runtimeRef.current?.setVariable(key, value);
175
- }, []);
176
- const setVariables = (0, import_react.useCallback)((variables) => {
177
- if (!runtimeRef.current) return;
178
- Object.entries(variables).forEach(([key, value]) => {
179
- runtimeRef.current?.setVariable(key, value);
180
- });
181
- }, []);
182
- const getVariable = (0, import_react.useCallback)((key) => {
183
- return runtimeRef.current?.getState().variables[key];
184
- }, []);
185
- const refreshData = (0, import_react.useCallback)(async (queryId) => {
186
- await runtimeRef.current?.refreshData(queryId);
187
- }, []);
188
- const executeAction = (0, import_react.useCallback)(async (actionType, params) => {
189
- const api = hostApi;
190
- if (!api) {
191
- throw new Error("HostAPI not available");
192
- }
193
- const response = await api.executeAction(actionType, params || {});
194
- if (response.success) {
195
- return response.data;
196
- }
197
- throw new Error(response.errorMessage || "Action failed");
198
- }, [hostApi]);
199
- return {
200
- runtime,
201
- loading,
202
- phase,
203
- page,
204
- error,
205
- hostApi,
206
- isReady,
207
- hasError,
208
- metrics,
209
- init,
210
- load,
211
- render,
212
- destroy,
213
- reload,
214
- setVariable,
215
- setVariables,
216
- getVariable,
217
- refreshData,
218
- executeAction
219
- };
220
- }
221
-
222
- // src/context.tsx
223
- var import_react2 = require("react");
224
- var import_jsx_runtime = require("react/jsx-runtime");
225
- var defaultState = {
226
- phase: "idle",
227
- page: null,
228
- variables: {},
229
- queries: {},
230
- components: /* @__PURE__ */ new Map(),
231
- error: null,
232
- destroyed: false
233
- };
234
- var RuntimeContext = (0, import_react2.createContext)({
235
- runtime: null,
236
- state: defaultState,
237
- hostApi: null
238
- });
239
- function RuntimeProvider({
240
- runtime,
241
- state,
242
- hostApi,
243
- children
244
- }) {
245
- const value = {
246
- runtime,
247
- state,
248
- hostApi
249
- };
250
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RuntimeContext.Provider, { value, children });
251
- }
252
- function useRuntimeContext() {
253
- const context = (0, import_react2.useContext)(RuntimeContext);
254
- return context;
255
- }
256
-
257
- // src/components/DJVRenderer.tsx
258
- var import_react3 = require("react");
259
- var import_jsx_runtime2 = require("react/jsx-runtime");
260
- function DJVRenderer({
261
- pageUid,
262
- apiBaseUrl,
263
- cdnBaseUrl,
264
- channel = "prod",
265
- userId,
266
- deviceId,
267
- authToken,
268
- previewToken,
269
- debug = false,
270
- enableSRI = true,
271
- onLoad,
272
- onError,
273
- onReady,
274
- onPhaseChange,
275
- loadingComponent,
276
- errorComponent,
277
- emptyComponent,
278
- children,
279
- className,
280
- style,
281
- retryCount = 3,
282
- retryDelay = 1e3,
283
- timeout = 3e4
284
- }) {
285
- const containerRef = (0, import_react3.useRef)(null);
286
- const mountedRef = (0, import_react3.useRef)(true);
287
- const retryCountRef = (0, import_react3.useRef)(0);
288
- const [state, setState] = (0, import_react3.useState)({
289
- phase: "idle",
290
- page: null,
291
- variables: {},
292
- queries: {},
293
- components: /* @__PURE__ */ new Map(),
294
- error: null,
295
- destroyed: false
296
- });
297
- const options = {
298
- pageUid,
299
- apiBaseUrl,
300
- cdnBaseUrl,
301
- channel,
302
- userId,
303
- deviceId,
304
- authToken,
305
- previewToken,
306
- debug,
307
- enableSRI,
308
- containerRef,
309
- onError: (error2) => {
310
- onError?.(error2);
311
- }
312
- };
313
- const {
314
- runtime,
315
- loading,
316
- phase,
317
- page,
318
- error,
319
- hostApi,
320
- init,
321
- load,
322
- render,
323
- destroy
324
- } = useReactRuntime(options);
325
- (0, import_react3.useEffect)(() => {
326
- onPhaseChange?.(phase);
327
- }, [phase, onPhaseChange]);
328
- const initWithRetry = (0, import_react3.useCallback)(async () => {
329
- if (!containerRef.current || !mountedRef.current) return;
330
- const timeoutPromise = new Promise((_, reject) => {
331
- setTimeout(() => reject(new Error("\u52A0\u8F7D\u8D85\u65F6")), timeout);
332
- });
333
- try {
334
- await Promise.race([
335
- (async () => {
336
- await init();
337
- const pageData = await load();
338
- if (mountedRef.current) {
339
- onLoad?.(pageData);
340
- }
341
- await render();
342
- if (mountedRef.current) {
343
- onReady?.();
344
- retryCountRef.current = 0;
345
- if (runtime) {
346
- runtime.onStateChange((newState) => {
347
- if (mountedRef.current) {
348
- setState(newState);
349
- }
350
- });
351
- }
352
- }
353
- })(),
354
- timeoutPromise
355
- ]);
356
- } catch (err) {
357
- if (!mountedRef.current) return;
358
- if (retryCountRef.current < retryCount) {
359
- retryCountRef.current++;
360
- if (debug) {
361
- console.log(`[DJVRenderer] \u91CD\u8BD5 ${retryCountRef.current}/${retryCount}...`);
362
- }
363
- setTimeout(() => {
364
- if (mountedRef.current) {
365
- initWithRetry();
366
- }
367
- }, retryDelay);
368
- }
369
- }
370
- }, [init, load, render, runtime, onLoad, onReady, timeout, retryCount, retryDelay, debug]);
371
- const handleRetry = (0, import_react3.useCallback)(() => {
372
- retryCountRef.current = 0;
373
- initWithRetry();
374
- }, [initWithRetry]);
375
- (0, import_react3.useEffect)(() => {
376
- mountedRef.current = true;
377
- initWithRetry();
378
- return () => {
379
- mountedRef.current = false;
380
- destroy();
381
- };
382
- }, [pageUid]);
383
- const renderLoading = () => {
384
- if (!loading) return null;
385
- return loadingComponent || /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "djvlc-loading", children: [
386
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "djvlc-loading-spinner" }),
387
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u52A0\u8F7D\u4E2D..." }),
388
- retryCountRef.current > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "djvlc-loading-retry", children: [
389
- "\u91CD\u8BD5 ",
390
- retryCountRef.current,
391
- "/",
392
- retryCount
393
- ] })
394
- ] });
395
- };
396
- const renderError = () => {
397
- if (!error) return null;
398
- if (typeof errorComponent === "function") {
399
- return errorComponent(error, handleRetry);
400
- }
401
- return errorComponent || /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "djvlc-error", children: [
402
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "djvlc-error-icon", children: "\u26A0\uFE0F" }),
403
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "djvlc-error-message", children: [
404
- "\u52A0\u8F7D\u5931\u8D25\uFF1A",
405
- error.message
406
- ] }),
407
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
408
- "button",
409
- {
410
- className: "djvlc-error-retry-btn",
411
- onClick: handleRetry,
412
- type: "button",
413
- children: "\u91CD\u8BD5"
414
- }
415
- )
416
- ] });
417
- };
418
- const renderEmpty = () => {
419
- if (loading || error || page) return null;
420
- if (phase === "ready" && !page) {
421
- return emptyComponent || /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "djvlc-empty", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u6682\u65E0\u5185\u5BB9" }) });
422
- }
423
- return null;
424
- };
425
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RuntimeProvider, { runtime, state, hostApi, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
426
- "div",
427
- {
428
- ref: containerRef,
429
- className: `djvlc-renderer ${className || ""}`,
430
- style,
431
- "data-phase": phase,
432
- "data-page-uid": pageUid,
433
- children: [
434
- renderLoading(),
435
- renderError(),
436
- renderEmpty(),
437
- children
438
- ]
439
- }
440
- ) });
441
- }
442
-
443
- // src/components/DJVProvider.tsx
444
- var import_react4 = require("react");
445
- var import_jsx_runtime3 = require("react/jsx-runtime");
446
- var defaultState2 = {
447
- phase: "idle",
448
- page: null,
449
- variables: {},
450
- queries: {},
451
- components: /* @__PURE__ */ new Map(),
452
- error: null,
453
- destroyed: false
454
- };
455
- function DJVProvider({
456
- runtime,
457
- hostApi,
458
- children,
459
- className,
460
- debug = false,
461
- onStateChange,
462
- onPhaseChange,
463
- onError
464
- }) {
465
- const [state, setState] = (0, import_react4.useState)(
466
- runtime?.getState() || defaultState2
467
- );
468
- const prevPhaseRef = (0, import_react4.useRef)(state.phase);
469
- (0, import_react4.useEffect)(() => {
470
- if (!runtime) {
471
- setState(defaultState2);
472
- return;
473
- }
474
- setState(runtime.getState());
475
- const unsubscribe = runtime.onStateChange((newState) => {
476
- setState(newState);
477
- onStateChange?.(newState);
478
- if (newState.phase !== prevPhaseRef.current) {
479
- onPhaseChange?.(newState.phase);
480
- if (debug) {
481
- console.log(`[DJVProvider] Phase changed: ${prevPhaseRef.current} -> ${newState.phase}`);
482
- }
483
- prevPhaseRef.current = newState.phase;
484
- }
485
- if (newState.error) {
486
- onError?.(newState.error);
487
- }
488
- });
489
- return () => {
490
- unsubscribe();
491
- };
492
- }, [runtime, onStateChange, onPhaseChange, onError, debug]);
493
- (0, import_react4.useEffect)(() => {
494
- }, [hostApi]);
495
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RuntimeProvider, { runtime, state, hostApi, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
496
- "div",
497
- {
498
- className: ["djvlc-provider", className].filter(Boolean).join(" "),
499
- "data-phase": state.phase,
500
- children
501
- }
502
- ) });
503
- }
504
-
505
- // src/hooks/useDJVRuntime.ts
506
- var import_react5 = require("react");
507
- function useDJVRuntime() {
508
- const context = useRuntimeContext();
509
- const loading = (0, import_react5.useMemo)(() => {
510
- const phase = context.state.phase;
511
- return phase !== "ready" && phase !== "error";
512
- }, [context.state.phase]);
513
- const isReady = context.state.phase === "ready";
514
- const hasError = context.state.phase === "error" || context.state.error !== null;
515
- const reload = (0, import_react5.useCallback)(async () => {
516
- if (!context.runtime) {
517
- throw new Error("Runtime not available");
518
- }
519
- await context.runtime.load();
520
- await context.runtime.render();
521
- }, [context.runtime]);
522
- return {
523
- runtime: context.runtime,
524
- state: context.state,
525
- loading,
526
- phase: context.state.phase,
527
- error: context.state.error,
528
- page: context.state.page,
529
- isReady,
530
- hasError,
531
- reload
532
- };
533
- }
534
- function useHostApi() {
535
- const context = useRuntimeContext();
536
- if (!context.hostApi) {
537
- throw new Error("HostAPI not available. Make sure runtime is initialized.");
538
- }
539
- return context.hostApi;
540
- }
541
- function useRuntimeState(key) {
542
- const context = useRuntimeContext();
543
- return context.state.variables[key];
544
- }
545
- function useRuntimeStateWritable(key, defaultValue) {
546
- const context = useRuntimeContext();
547
- const value = context.state.variables[key] ?? defaultValue;
548
- const setValue = (0, import_react5.useCallback)(
549
- (newValue) => {
550
- context.runtime?.setVariable(key, newValue);
551
- },
552
- [context.runtime, key]
553
- );
554
- return [value, setValue];
555
- }
556
- function useQuery(queryId, options) {
557
- const context = useRuntimeContext();
558
- const [loading, setLoading] = (0, import_react5.useState)(false);
559
- const [error, setError] = (0, import_react5.useState)(null);
560
- const [lastUpdated, setLastUpdated] = (0, import_react5.useState)(null);
561
- const mountedRef = (0, import_react5.useRef)(true);
562
- const data = context.state.queries[queryId];
563
- const refetch = (0, import_react5.useCallback)(async () => {
564
- if (!mountedRef.current) return;
565
- setLoading(true);
566
- setError(null);
567
- try {
568
- await context.runtime?.refreshData(queryId);
569
- if (mountedRef.current) {
570
- setLastUpdated(Date.now());
571
- }
572
- } catch (e) {
573
- if (mountedRef.current) {
574
- setError(e);
575
- }
576
- } finally {
577
- if (mountedRef.current) {
578
- setLoading(false);
579
- }
580
- }
581
- }, [context.runtime, queryId]);
582
- (0, import_react5.useEffect)(() => {
583
- mountedRef.current = true;
584
- if (options?.refreshOnMount && context.runtime) {
585
- refetch();
586
- }
587
- return () => {
588
- mountedRef.current = false;
589
- };
590
- }, []);
591
- (0, import_react5.useEffect)(() => {
592
- if (!options?.refreshInterval || options.refreshInterval <= 0) return;
593
- const timer = setInterval(() => {
594
- refetch();
595
- }, options.refreshInterval);
596
- return () => clearInterval(timer);
597
- }, [options?.refreshInterval, refetch]);
598
- (0, import_react5.useEffect)(() => {
599
- if (!options?.refreshOnFocus) return;
600
- const handleFocus = () => {
601
- refetch();
602
- };
603
- window.addEventListener("focus", handleFocus);
604
- return () => window.removeEventListener("focus", handleFocus);
605
- }, [options?.refreshOnFocus, refetch]);
606
- return {
607
- data,
608
- loading,
609
- error,
610
- refetch,
611
- lastUpdated
612
- };
613
- }
614
- function useAction(actionType, options) {
615
- const context = useRuntimeContext();
616
- const [loading, setLoading] = (0, import_react5.useState)(false);
617
- const [result, setResult] = (0, import_react5.useState)();
618
- const [error, setError] = (0, import_react5.useState)(null);
619
- const [executionCount, setExecutionCount] = (0, import_react5.useState)(0);
620
- const mountedRef = (0, import_react5.useRef)(true);
621
- (0, import_react5.useEffect)(() => {
622
- mountedRef.current = true;
623
- return () => {
624
- mountedRef.current = false;
625
- };
626
- }, []);
627
- const reset = (0, import_react5.useCallback)(() => {
628
- setResult(void 0);
629
- setError(null);
630
- setExecutionCount(0);
631
- }, []);
632
- const executeWithRetry = (0, import_react5.useCallback)(
633
- async (params, retriesLeft) => {
634
- const hostApi = context.hostApi;
635
- if (!hostApi) {
636
- throw new Error("HostAPI not available");
637
- }
638
- try {
639
- const response = await hostApi.executeAction(
640
- actionType,
641
- params
642
- );
643
- if (response.success) {
644
- return response.data;
645
- } else {
646
- throw new Error(response.errorMessage || "Action failed");
647
- }
648
- } catch (e) {
649
- if (retriesLeft > 0) {
650
- await new Promise((resolve) => setTimeout(resolve, options?.retryDelay || 1e3));
651
- return executeWithRetry(params, retriesLeft - 1);
652
- }
653
- throw e;
654
- }
655
- },
656
- [context.hostApi, actionType, options?.retryDelay]
657
- );
658
- const execute = (0, import_react5.useCallback)(
659
- async (params) => {
660
- if (!mountedRef.current) return void 0;
661
- setLoading(true);
662
- setError(null);
663
- setExecutionCount((c) => c + 1);
664
- try {
665
- const data = await executeWithRetry(params, options?.retryCount || 0);
666
- if (mountedRef.current) {
667
- setResult(data);
668
- options?.onSuccess?.(data);
669
- }
670
- return data;
671
- } catch (e) {
672
- const err = e;
673
- if (mountedRef.current) {
674
- setError(err);
675
- options?.onError?.(err);
676
- }
677
- throw e;
678
- } finally {
679
- if (mountedRef.current) {
680
- setLoading(false);
681
- }
682
- }
683
- },
684
- [executeWithRetry, options]
685
- );
686
- return {
687
- execute,
688
- loading,
689
- result,
690
- error,
691
- reset,
692
- executionCount
693
- };
694
- }
695
- function useData(queryId, params, options) {
696
- const context = useRuntimeContext();
697
- const [data, setData] = (0, import_react5.useState)();
698
- const [loading, setLoading] = (0, import_react5.useState)(false);
699
- const [error, setError] = (0, import_react5.useState)(null);
700
- const [isFetched, setIsFetched] = (0, import_react5.useState)(false);
701
- const mountedRef = (0, import_react5.useRef)(true);
702
- const paramsRef = (0, import_react5.useRef)(params);
703
- (0, import_react5.useEffect)(() => {
704
- mountedRef.current = true;
705
- return () => {
706
- mountedRef.current = false;
707
- };
708
- }, []);
709
- const refetch = (0, import_react5.useCallback)(
710
- async (newParams) => {
711
- const hostApi = context.hostApi;
712
- if (!hostApi || !mountedRef.current) return;
713
- setLoading(true);
714
- setError(null);
715
- try {
716
- const data2 = await hostApi.requestData(
717
- queryId,
718
- newParams || paramsRef.current
719
- );
720
- if (!mountedRef.current) return;
721
- setData(data2);
722
- setIsFetched(true);
723
- options?.onSuccess?.(data2);
724
- } catch (e) {
725
- if (mountedRef.current) {
726
- const err = e;
727
- setError(err);
728
- options?.onError?.(err);
729
- }
730
- } finally {
731
- if (mountedRef.current) {
732
- setLoading(false);
733
- }
734
- }
735
- },
736
- [context.hostApi, queryId, options]
737
- );
738
- (0, import_react5.useEffect)(() => {
739
- const prevParams = paramsRef.current;
740
- paramsRef.current = params;
741
- if (options?.refreshOnParamsChange !== false && context.hostApi && isFetched && JSON.stringify(prevParams) !== JSON.stringify(params)) {
742
- refetch();
743
- }
744
- }, [params, context.hostApi, options?.refreshOnParamsChange, isFetched, refetch]);
745
- (0, import_react5.useEffect)(() => {
746
- if (options?.immediate !== false && context.hostApi) {
747
- refetch();
748
- }
749
- }, [context.hostApi]);
750
- (0, import_react5.useEffect)(() => {
751
- if (!options?.refreshInterval || options.refreshInterval <= 0) return;
752
- const timer = setInterval(() => {
753
- refetch();
754
- }, options.refreshInterval);
755
- return () => clearInterval(timer);
756
- }, [options?.refreshInterval, refetch]);
757
- return {
758
- data,
759
- loading,
760
- error,
761
- refetch,
762
- isFetched
763
- };
764
- }
765
- function useRuntimeEvent(eventType, handler) {
766
- const context = useRuntimeContext();
767
- (0, import_react5.useEffect)(() => {
768
- const unsubscribe = context.runtime?.on(eventType, (event) => {
769
- handler(event.data);
770
- });
771
- return () => {
772
- unsubscribe?.();
773
- };
774
- }, [context.runtime, eventType, handler]);
775
- }
776
- function usePageInfo() {
777
- const context = useRuntimeContext();
778
- const page = context.state.page;
779
- return (0, import_react5.useMemo)(() => ({
780
- /** 页面 UID */
781
- pageUid: page?.pageUid,
782
- /** 页面版本 ID */
783
- pageVersionId: page?.pageVersionId,
784
- /** Schema 版本 */
785
- schemaVersion: page?.pageJson?.schemaVersion,
786
- /** 页面标题 */
787
- title: page?.title,
788
- /** 页面配置 */
789
- config: page?.config,
790
- /** 页面是否已加载 */
791
- isLoaded: page !== null
792
- }), [page]);
793
- }
794
- function useComponentState(componentId) {
795
- const context = useRuntimeContext();
796
- const componentStatus = context.state.components.get(componentId);
797
- return (0, import_react5.useMemo)(() => ({
798
- /** 组件是否已加载 */
799
- isLoaded: componentStatus?.status === "loaded",
800
- /** 组件是否加载中 */
801
- isLoading: componentStatus?.status === "loading",
802
- /** 组件是否加载失败 */
803
- hasError: componentStatus?.status === "failed",
804
- /** 加载耗时 */
805
- loadTime: componentStatus?.loadTime,
806
- /** 组件信息 */
807
- info: componentStatus
808
- }), [componentStatus]);
809
- }
810
- function useLifecycle(options) {
811
- const context = useRuntimeContext();
812
- const [hasMounted, setHasMounted] = (0, import_react5.useState)(false);
813
- const [hasReady, setHasReady] = (0, import_react5.useState)(false);
814
- const prevPhaseRef = (0, import_react5.useRef)(context.state.phase);
815
- const mountedRef = (0, import_react5.useRef)(true);
816
- (0, import_react5.useEffect)(() => {
817
- mountedRef.current = true;
818
- return () => {
819
- mountedRef.current = false;
820
- };
821
- }, []);
822
- (0, import_react5.useEffect)(() => {
823
- const currentPhase = context.state.phase;
824
- if (currentPhase !== prevPhaseRef.current) {
825
- options?.onPhaseChange?.(currentPhase);
826
- prevPhaseRef.current = currentPhase;
827
- }
828
- if (!hasMounted && currentPhase !== "idle") {
829
- setHasMounted(true);
830
- Promise.resolve(options?.onMounted?.()).catch((error) => {
831
- options?.onError?.(error);
832
- });
833
- }
834
- if (!hasReady && currentPhase === "ready") {
835
- setHasReady(true);
836
- Promise.resolve(options?.onReady?.()).catch((error) => {
837
- options?.onError?.(error);
838
- });
839
- }
840
- if (currentPhase === "error" && context.state.error) {
841
- options?.onError?.(context.state.error);
842
- }
843
- }, [context.state.phase, context.state.error, hasMounted, hasReady, options]);
844
- return {
845
- /** 当前阶段 */
846
- phase: context.state.phase,
847
- /** 是否已 mounted */
848
- hasMounted,
849
- /** 是否已 ready */
850
- hasReady
851
- };
852
- }
853
- function useWhen(condition, callback, options) {
854
- const [executed, setExecuted] = (0, import_react5.useState)(false);
855
- const { once = true } = options || {};
856
- (0, import_react5.useEffect)(() => {
857
- if (condition && (!once || !executed)) {
858
- setExecuted(true);
859
- callback();
860
- }
861
- }, [condition, callback, once, executed]);
862
- return {
863
- /** 是否已执行 */
864
- executed
865
- };
866
- }
867
- function useDebouncedAction(actionType, delay = 300) {
868
- const { execute: rawExecute, loading, result, error } = useAction(actionType);
869
- const timeoutRef = (0, import_react5.useRef)(null);
870
- const mountedRef = (0, import_react5.useRef)(true);
871
- (0, import_react5.useEffect)(() => {
872
- mountedRef.current = true;
873
- return () => {
874
- mountedRef.current = false;
875
- if (timeoutRef.current) {
876
- clearTimeout(timeoutRef.current);
877
- }
878
- };
879
- }, []);
880
- const execute = (0, import_react5.useCallback)((params) => {
881
- if (timeoutRef.current) {
882
- clearTimeout(timeoutRef.current);
883
- }
884
- timeoutRef.current = setTimeout(() => {
885
- if (mountedRef.current) {
886
- rawExecute(params).catch(() => {
887
- });
888
- }
889
- }, delay);
890
- }, [rawExecute, delay]);
891
- const cancel = (0, import_react5.useCallback)(() => {
892
- if (timeoutRef.current) {
893
- clearTimeout(timeoutRef.current);
894
- timeoutRef.current = null;
895
- }
896
- }, []);
897
- return {
898
- execute,
899
- loading,
900
- result,
901
- error,
902
- cancel
903
- };
904
- }
905
- function useAsync(asyncFn, deps = [], options) {
906
- const [data, setData] = (0, import_react5.useState)();
907
- const [loading, setLoading] = (0, import_react5.useState)(false);
908
- const [error, setError] = (0, import_react5.useState)(null);
909
- const mountedRef = (0, import_react5.useRef)(true);
910
- (0, import_react5.useEffect)(() => {
911
- mountedRef.current = true;
912
- return () => {
913
- mountedRef.current = false;
914
- };
915
- }, []);
916
- const execute = (0, import_react5.useCallback)(async () => {
917
- if (!mountedRef.current) return void 0;
918
- setLoading(true);
919
- setError(null);
920
- try {
921
- const result = await asyncFn();
922
- if (mountedRef.current) {
923
- setData(result);
924
- options?.onSuccess?.(result);
925
- }
926
- return result;
927
- } catch (e) {
928
- const err = e;
929
- if (mountedRef.current) {
930
- setError(err);
931
- options?.onError?.(err);
932
- }
933
- return void 0;
934
- } finally {
935
- if (mountedRef.current) {
936
- setLoading(false);
937
- }
938
- }
939
- }, [asyncFn, ...deps]);
940
- (0, import_react5.useEffect)(() => {
941
- if (options?.immediate !== false) {
942
- execute();
943
- }
944
- }, []);
945
- return {
946
- data,
947
- loading,
948
- error,
949
- execute
950
- };
951
- }
952
- function usePrevious(value) {
953
- const ref = (0, import_react5.useRef)();
954
- (0, import_react5.useEffect)(() => {
955
- ref.current = value;
956
- }, [value]);
957
- return ref.current;
958
- }
959
- // Annotate the CommonJS export names for ESM import in node:
960
- 0 && (module.exports = {
961
- DJVProvider,
962
- DJVRenderer,
963
- RuntimeContext,
964
- RuntimeProvider,
965
- useAction,
966
- useAsync,
967
- useComponentState,
968
- useDJVRuntime,
969
- useData,
970
- useDebouncedAction,
971
- useHostApi,
972
- useLifecycle,
973
- usePageInfo,
974
- usePrevious,
975
- useQuery,
976
- useReactRuntime,
977
- useRuntimeContext,
978
- useRuntimeEvent,
979
- useRuntimeState,
980
- useRuntimeStateWritable,
981
- useWhen
982
- });
1
+ var e=require("react"),r=require("@djvlc/runtime-core"),n=require("react/jsx-runtime"),t={initTime:0,loadTime:0,renderTime:0,totalTime:0,initTimestamp:null,readyTimestamp:null};function o(n){const[o,a]=e.useState(null),[i,l]=e.useState(!0),[s,c]=e.useState("idle"),[u,d]=e.useState(null),[p,m]=e.useState(null),[h,f]=e.useState(null),[w,y]=e.useState(t),v=e.useRef(null),g=e.useRef({startTime:0,initStartTime:0,loadStartTime:0,renderStartTime:0}),x=e.useMemo(()=>"ready"===s,[s]),T=e.useMemo(()=>"error"===s||null!==p,[s,p]),b=e.useCallback(async()=>{const e=n.containerRef?.current;if(!e)throw new Error("Container element not found");g.current.startTime=performance.now(),g.current.initStartTime=g.current.startTime,n.enableMetrics&&y(e=>({...e,initTimestamp:Date.now()}));const t=r.createRuntime({...n,container:e,onError:e=>{m(e),n.onError?.(e)},onEvent:e=>{n.onEvent?.(e)}});v.current=t,a(t),t.onStateChange(e=>{c(e.phase),l("ready"!==e.phase&&"error"!==e.phase),e.page&&d(e.page),e.error&&m(e.error)}),await t.init(),f(t.getHostApi()),n.enableMetrics&&y(e=>({...e,initTime:performance.now()-g.current.initStartTime}))},[n]),E=e.useCallback(async()=>{if(!v.current)throw new Error("Runtime not initialized");g.current.loadStartTime=performance.now();const e=await v.current.load();return d(e),f(v.current.getHostApi()),n.enableMetrics&&y(e=>({...e,loadTime:performance.now()-g.current.loadStartTime})),e},[n.enableMetrics]),A=e.useCallback(async()=>{if(!v.current)throw new Error("Runtime not initialized");if(g.current.renderStartTime=performance.now(),await v.current.render(),l(!1),n.enableMetrics){const e=performance.now();y(r=>({...r,renderTime:e-g.current.renderStartTime,totalTime:e-g.current.startTime,readyTimestamp:Date.now()}))}},[n.enableMetrics]),I=e.useCallback(()=>{v.current?.destroy(),v.current=null,a(null),f(null),d(null),m(null),c("idle"),l(!0),y(t)},[]),N=e.useCallback(async()=>{if(!v.current)throw new Error("Runtime not initialized");m(null),l(!0),await E(),await A()},[E,A]),j=e.useCallback((e,r)=>{v.current?.setVariable(e,r)},[]),R=e.useCallback(e=>{v.current&&Object.entries(e).forEach(([e,r])=>{v.current?.setVariable(e,r)})},[]),C=e.useCallback(e=>v.current?.getState().variables[e],[]),P=e.useCallback(async e=>{await(v.current?.refreshData(e))},[]),S=e.useCallback(async(e,r)=>{const n=h;if(!n)throw new Error("HostAPI not available");const t=await n.executeAction(e,r||{});if(t.success)return t.data;throw new Error(t.errorMessage||"Action failed")},[h]);return{runtime:o,loading:i,phase:s,page:u,error:p,hostApi:h,isReady:x,hasError:T,metrics:w,init:b,load:E,render:A,destroy:I,reload:N,setVariable:j,setVariables:R,getVariable:C,refreshData:P,executeAction:S}}var a={phase:"idle",page:null,variables:{},queries:{},components:new Map,error:null,destroyed:!1},i=e.createContext({runtime:null,state:a,hostApi:null});function l({runtime:e,state:r,hostApi:t,children:o}){const a={runtime:e,state:r,hostApi:t};return n.jsx(i.Provider,{value:a,children:o})}function s(){return e.useContext(i)}var c={phase:"idle",page:null,variables:{},queries:{},components:new Map,error:null,destroyed:!1};function u(r,n){const t=s(),[o,a]=e.useState(!1),[i,l]=e.useState(),[c,u]=e.useState(null),[d,p]=e.useState(0),m=e.useRef(!0);e.useEffect(()=>(m.current=!0,()=>{m.current=!1}),[]);const h=e.useCallback(()=>{l(void 0),u(null),p(0)},[]),f=e.useCallback(async(e,o)=>{const a=t.hostApi;if(!a)throw new Error("HostAPI not available");try{const n=await a.executeAction(r,e);if(n.success)return n.data;throw new Error(n.errorMessage||"Action failed")}catch(r){if(o>0)return await new Promise(e=>setTimeout(e,n?.retryDelay||1e3)),f(e,o-1);throw r}},[t.hostApi,r,n?.retryDelay]);return{execute:e.useCallback(async e=>{if(m.current){a(!0),u(null),p(e=>e+1);try{const r=await f(e,n?.retryCount||0);return m.current&&(l(r),n?.onSuccess?.(r)),r}catch(e){const r=e;throw m.current&&(u(r),n?.onError?.(r)),e}finally{m.current&&a(!1)}}},[f,n]),loading:o,result:i,error:c,reset:h,executionCount:d}}exports.DJVProvider=function({runtime:r,hostApi:t,children:o,className:a,debug:i=!1,onStateChange:s,onPhaseChange:u,onError:d}){const[p,m]=e.useState(r?.getState()||c),h=e.useRef(p.phase);return e.useEffect(()=>{if(!r)return void m(c);m(r.getState());const e=r.onStateChange(e=>{m(e),s?.(e),e.phase!==h.current&&(u?.(e.phase),h.current=e.phase),e.error&&d?.(e.error)});return()=>{e()}},[r,s,u,d,i]),e.useEffect(()=>{},[t]),n.jsx(l,{runtime:r,state:p,hostApi:t,children:n.jsx("div",{className:["djvlc-provider",a].filter(Boolean).join(" "),"data-phase":p.phase,children:o})})},exports.DJVRenderer=function({pageUid:r,apiBaseUrl:t,cdnBaseUrl:a,channel:i="prod",userId:s,deviceId:c,authToken:u,previewToken:d,debug:p=!1,enableSRI:m=!0,onLoad:h,onError:f,onReady:w,onPhaseChange:y,loadingComponent:v,errorComponent:g,emptyComponent:x,children:T,className:b,style:E,retryCount:A=3,retryDelay:I=1e3,timeout:N=3e4}){const j=e.useRef(null),R=e.useRef(!0),C=e.useRef(0),[P,S]=e.useState({phase:"idle",page:null,variables:{},queries:{},components:new Map,error:null,destroyed:!1}),U={pageUid:r,apiBaseUrl:t,cdnBaseUrl:a,channel:i,userId:s,deviceId:c,authToken:u,previewToken:d,debug:p,enableSRI:m,containerRef:j,onError:e=>{f?.(e)}},{runtime:k,loading:q,phase:B,page:D,error:M,hostApi:V,init:z,load:L,render:H,destroy:O}=o(U);e.useEffect(()=>{y?.(B)},[B,y]);const J=e.useCallback(async()=>{if(!j.current||!R.current)return;const e=new Promise((e,r)=>{setTimeout(()=>r(new Error("加载超时")),N)});try{await Promise.race([(async()=>{await z();const e=await L();R.current&&h?.(e),await H(),R.current&&(w?.(),C.current=0,k&&k.onStateChange(e=>{R.current&&S(e)}))})(),e])}catch(e){if(!R.current)return;C.current<A&&(C.current++,setTimeout(()=>{R.current&&J()},I))}},[z,L,H,k,h,w,N,A,I,p]),F=e.useCallback(()=>{C.current=0,J()},[J]);return e.useEffect(()=>(R.current=!0,J(),()=>{R.current=!1,O()}),[r]),n.jsx(l,{runtime:k,state:P,hostApi:V,children:n.jsxs("div",{ref:j,className:`djvlc-renderer ${b||""}`,style:E,"data-phase":B,"data-page-uid":r,children:[q?v||n.jsxs("div",{className:"djvlc-loading",children:[n.jsx("div",{className:"djvlc-loading-spinner"}),n.jsx("span",{children:"加载中..."}),C.current>0&&n.jsxs("span",{className:"djvlc-loading-retry",children:["重试 ",C.current,"/",A]})]}):null,M?"function"==typeof g?g(M,F):g||n.jsxs("div",{className:"djvlc-error",children:[n.jsx("div",{className:"djvlc-error-icon",children:"⚠️"}),n.jsxs("span",{className:"djvlc-error-message",children:["加载失败:",M.message]}),n.jsx("button",{className:"djvlc-error-retry-btn",onClick:F,type:"button",children:"重试"})]}):null,q||M||D||"ready"!==B||D?null:x||n.jsx("div",{className:"djvlc-empty",children:n.jsx("span",{children:"暂无内容"})}),T]})})},exports.RuntimeContext=i,exports.RuntimeProvider=l,exports.useAction=u,exports.useAsync=function(r,n=[],t){const[o,a]=e.useState(),[i,l]=e.useState(!1),[s,c]=e.useState(null),u=e.useRef(!0);e.useEffect(()=>(u.current=!0,()=>{u.current=!1}),[]);const d=e.useCallback(async()=>{if(u.current){l(!0),c(null);try{const e=await r();return u.current&&(a(e),t?.onSuccess?.(e)),e}catch(e){const r=e;return void(u.current&&(c(r),t?.onError?.(r)))}finally{u.current&&l(!1)}}},[r,...n]);return e.useEffect(()=>{!1!==t?.immediate&&d()},[]),{data:o,loading:i,error:s,execute:d}},exports.useComponentState=function(r){const n=s().state.components.get(r);return e.useMemo(()=>({isLoaded:"loaded"===n?.status,isLoading:"loading"===n?.status,hasError:"failed"===n?.status,loadTime:n?.loadTime,info:n}),[n])},exports.useDJVRuntime=function(){const r=s(),n=e.useMemo(()=>{const e=r.state.phase;return"ready"!==e&&"error"!==e},[r.state.phase]),t="ready"===r.state.phase,o="error"===r.state.phase||null!==r.state.error,a=e.useCallback(async()=>{if(!r.runtime)throw new Error("Runtime not available");await r.runtime.load(),await r.runtime.render()},[r.runtime]);return{runtime:r.runtime,state:r.state,loading:n,phase:r.state.phase,error:r.state.error,page:r.state.page,isReady:t,hasError:o,reload:a}},exports.useData=function(r,n,t){const o=s(),[a,i]=e.useState(),[l,c]=e.useState(!1),[u,d]=e.useState(null),[p,m]=e.useState(!1),h=e.useRef(!0),f=e.useRef(n);e.useEffect(()=>(h.current=!0,()=>{h.current=!1}),[]);const w=e.useCallback(async e=>{const n=o.hostApi;if(n&&h.current){c(!0),d(null);try{const o=await n.requestData(r,e||f.current);if(!h.current)return;i(o),m(!0),t?.onSuccess?.(o)}catch(e){if(h.current){const r=e;d(r),t?.onError?.(r)}}finally{h.current&&c(!1)}}},[o.hostApi,r,t]);return e.useEffect(()=>{const e=f.current;f.current=n,!1!==t?.refreshOnParamsChange&&o.hostApi&&p&&JSON.stringify(e)!==JSON.stringify(n)&&w()},[n,o.hostApi,t?.refreshOnParamsChange,p,w]),e.useEffect(()=>{!1!==t?.immediate&&o.hostApi&&w()},[o.hostApi]),e.useEffect(()=>{if(!t?.refreshInterval||t.refreshInterval<=0)return;const e=setInterval(()=>{w()},t.refreshInterval);return()=>clearInterval(e)},[t?.refreshInterval,w]),{data:a,loading:l,error:u,refetch:w,isFetched:p}},exports.useDebouncedAction=function(r,n=300){const{execute:t,loading:o,result:a,error:i}=u(r),l=e.useRef(null),s=e.useRef(!0);return e.useEffect(()=>(s.current=!0,()=>{s.current=!1,l.current&&clearTimeout(l.current)}),[]),{execute:e.useCallback(e=>{l.current&&clearTimeout(l.current),l.current=setTimeout(()=>{s.current&&t(e).catch(()=>{})},n)},[t,n]),loading:o,result:a,error:i,cancel:e.useCallback(()=>{l.current&&(clearTimeout(l.current),l.current=null)},[])}},exports.useHostApi=function(){const e=s();if(!e.hostApi)throw new Error("HostAPI not available. Make sure runtime is initialized.");return e.hostApi},exports.useLifecycle=function(r){const n=s(),[t,o]=e.useState(!1),[a,i]=e.useState(!1),l=e.useRef(n.state.phase),c=e.useRef(!0);return e.useEffect(()=>(c.current=!0,()=>{c.current=!1}),[]),e.useEffect(()=>{const e=n.state.phase;e!==l.current&&(r?.onPhaseChange?.(e),l.current=e),t||"idle"===e||(o(!0),Promise.resolve(r?.onMounted?.()).catch(e=>{r?.onError?.(e)})),a||"ready"!==e||(i(!0),Promise.resolve(r?.onReady?.()).catch(e=>{r?.onError?.(e)})),"error"===e&&n.state.error&&r?.onError?.(n.state.error)},[n.state.phase,n.state.error,t,a,r]),{phase:n.state.phase,hasMounted:t,hasReady:a}},exports.usePageInfo=function(){const r=s().state.page;return e.useMemo(()=>({pageUid:r?.pageUid,pageVersionId:r?.pageVersionId,schemaVersion:r?.pageJson?.schemaVersion,title:r?.title,config:r?.config,isLoaded:null!==r}),[r])},exports.usePrevious=function(r){const n=e.useRef();return e.useEffect(()=>{n.current=r},[r]),n.current},exports.useQuery=function(r,n){const t=s(),[o,a]=e.useState(!1),[i,l]=e.useState(null),[c,u]=e.useState(null),d=e.useRef(!0),p=t.state.queries[r],m=e.useCallback(async()=>{if(d.current){a(!0),l(null);try{await(t.runtime?.refreshData(r)),d.current&&u(Date.now())}catch(e){d.current&&l(e)}finally{d.current&&a(!1)}}},[t.runtime,r]);return e.useEffect(()=>(d.current=!0,n?.refreshOnMount&&t.runtime&&m(),()=>{d.current=!1}),[]),e.useEffect(()=>{if(!n?.refreshInterval||n.refreshInterval<=0)return;const e=setInterval(()=>{m()},n.refreshInterval);return()=>clearInterval(e)},[n?.refreshInterval,m]),e.useEffect(()=>{if(!n?.refreshOnFocus)return;const e=()=>{m()};return window.addEventListener("focus",e),()=>window.removeEventListener("focus",e)},[n?.refreshOnFocus,m]),{data:p,loading:o,error:i,refetch:m,lastUpdated:c}},exports.useReactRuntime=o,exports.useRuntimeContext=s,exports.useRuntimeEvent=function(r,n){const t=s();e.useEffect(()=>{const e=t.runtime?.on(r,e=>{n(e.data)});return()=>{e?.()}},[t.runtime,r,n])},exports.useRuntimeState=function(e){return s().state.variables[e]},exports.useRuntimeStateWritable=function(r,n){const t=s();return[t.state.variables[r]??n,e.useCallback(e=>{t.runtime?.setVariable(r,e)},[t.runtime,r])]},exports.useWhen=function(r,n,t){const[o,a]=e.useState(!1),{once:i=!0}=t||{};return e.useEffect(()=>{!r||i&&o||(a(!0),n())},[r,n,i,o]),{executed:o}};