@qontinui/ui-bridge-native 0.1.6 → 0.2.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/dist/control/{action-executor.d.ts → index.d.mts} +14 -6
- package/dist/control/index.d.ts +95 -5
- package/dist/control/index.js +74 -11
- package/dist/control/index.js.map +1 -1
- package/dist/control/index.mjs +74 -11
- package/dist/control/index.mjs.map +1 -1
- package/dist/core/index.d.mts +46 -0
- package/dist/core/index.d.ts +43 -6
- package/dist/core/index.js +69 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +69 -1
- package/dist/core/index.mjs.map +1 -1
- package/dist/debug/{inspector.d.ts → index.d.mts} +6 -3
- package/dist/debug/index.d.ts +44 -4
- package/dist/debug/index.js.map +1 -1
- package/dist/debug/index.mjs.map +1 -1
- package/dist/design/{design-inspector-native.d.ts → index.d.mts} +11 -7
- package/dist/design/index.d.ts +60 -5
- package/dist/{design/design-types.d.ts → design-types-BHr6Q1FX.d.mts} +11 -10
- package/dist/design-types-BHr6Q1FX.d.ts +205 -0
- package/dist/http-server-DCdR8WlL.d.mts +622 -0
- package/dist/http-server-Dj3grQW4.d.ts +622 -0
- package/dist/index.d.mts +89 -0
- package/dist/index.d.ts +86 -28
- package/dist/index.js +391 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +386 -13
- package/dist/index.mjs.map +1 -1
- package/dist/react/{useAutoRegister.d.ts → index.d.mts} +15 -7
- package/dist/react/index.d.ts +69 -8
- package/dist/react/index.js +388 -12
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +386 -13
- package/dist/react/index.mjs.map +1 -1
- package/dist/registry-Cahd8Gn2.d.mts +230 -0
- package/dist/{core/registry.d.ts → registry-z-3WJsxp.d.ts} +28 -10
- package/dist/server/index.d.mts +119 -0
- package/dist/server/index.d.ts +117 -13
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/types-DAfCeWXc.d.mts +197 -0
- package/dist/{core/types.d.ts → types-Oz13YLZq.d.mts} +124 -30
- package/dist/types-Oz13YLZq.d.ts +477 -0
- package/dist/{control/types.d.ts → types-ajakyp-l.d.ts} +36 -15
- package/dist/useUIBridgeToast-D32KXyL1.d.ts +720 -0
- package/dist/useUIBridgeToast-GmT1TzUe.d.mts +720 -0
- package/package.json +1 -1
- package/dist/control/action-executor.d.ts.map +0 -1
- package/dist/control/index.d.ts.map +0 -1
- package/dist/control/types.d.ts.map +0 -1
- package/dist/core/element-identifier.d.ts +0 -41
- package/dist/core/element-identifier.d.ts.map +0 -1
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/registry.d.ts.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/debug/index.d.ts.map +0 -1
- package/dist/debug/inspector.d.ts.map +0 -1
- package/dist/design/design-inspector-native.d.ts.map +0 -1
- package/dist/design/design-types.d.ts.map +0 -1
- package/dist/design/index.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/react/UIBridgeNativeProvider.d.ts +0 -147
- package/dist/react/UIBridgeNativeProvider.d.ts.map +0 -1
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/useAutoRegister.d.ts.map +0 -1
- package/dist/react/useUIBridge.d.ts +0 -103
- package/dist/react/useUIBridge.d.ts.map +0 -1
- package/dist/react/useUIComponent.d.ts +0 -112
- package/dist/react/useUIComponent.d.ts.map +0 -1
- package/dist/react/useUIElement.d.ts +0 -162
- package/dist/react/useUIElement.d.ts.map +0 -1
- package/dist/server/design-handlers.d.ts +0 -47
- package/dist/server/design-handlers.d.ts.map +0 -1
- package/dist/server/handlers.d.ts +0 -20
- package/dist/server/handlers.d.ts.map +0 -1
- package/dist/server/http-server.d.ts +0 -247
- package/dist/server/http-server.d.ts.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/types.d.ts +0 -200
- package/dist/server/types.d.ts.map +0 -1
- package/dist/server/ws-connection.d.ts +0 -48
- package/dist/server/ws-connection.d.ts.map +0 -1
- package/dist/server/ws-event-bridge.d.ts +0 -113
- package/dist/server/ws-event-bridge.d.ts.map +0 -1
- package/dist/server/ws-protocol.d.ts +0 -48
- package/dist/server/ws-protocol.d.ts.map +0 -1
- package/dist/server/ws-types.d.ts +0 -65
- package/dist/server/ws-types.d.ts.map +0 -1
- package/dist/transport/CloudRelayClient.d.ts +0 -51
- package/dist/transport/CloudRelayClient.d.ts.map +0 -1
- package/dist/transport/DeviceAnnouncer.d.ts +0 -75
- package/dist/transport/DeviceAnnouncer.d.ts.map +0 -1
- package/dist/transport/index.d.ts +0 -3
- package/dist/transport/index.d.ts.map +0 -1
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
export { b as ComponentActionDef, c as ComponentActionRequest, d as ComponentActionResponse, h as ToastRecorderInput, U as UIBridgeNativeContextValue, i as UIBridgeNativeProvider, j as UIBridgeNativeProviderProps, k as UIBridgeProps, n as UseUIBridgeModalOptions, o as UseUIBridgeReturn, p as UseUIBridgeToastOptions, q as UseUIComponentOptions, r as UseUIComponentReturn, s as UseUIElementOptions, t as UseUIElementReturn, u as UseUIElementWithPropsReturn, v as useToastRecorder, w as useUIBridge, x as useUIBridgeModal, y as useUIBridgeNative, z as useUIBridgeNativeOptional, B as useUIBridgeNativeRequired, E as useUIBridgeRequired, F as useUIBridgeToast, G as useUIComponent, H as useUIComponentAction, I as useUIElement, J as useUIElementWithProps } from '../useUIBridgeToast-GmT1TzUe.mjs';
|
|
2
|
+
import { j as NativeElementRef } from '../types-Oz13YLZq.mjs';
|
|
3
|
+
import { b as RegisterElementOptions } from '../registry-Cahd8Gn2.mjs';
|
|
4
|
+
import 'react/jsx-runtime';
|
|
5
|
+
import 'react';
|
|
6
|
+
import '../types-DAfCeWXc.mjs';
|
|
7
|
+
import '../http-server-DCdR8WlL.mjs';
|
|
8
|
+
import '../design-types-BHr6Q1FX.mjs';
|
|
9
|
+
|
|
1
10
|
/**
|
|
2
11
|
* useAutoRegister Hook for React Native
|
|
3
12
|
*
|
|
@@ -9,12 +18,11 @@
|
|
|
9
18
|
* 1. Use this hook at the app root to enable the registry
|
|
10
19
|
* 2. Use useUIElement on interactive components (or wrap with Pressable/TouchableOpacity)
|
|
11
20
|
*/
|
|
12
|
-
|
|
13
|
-
import type { RegisterElementOptions } from '../core/registry';
|
|
21
|
+
|
|
14
22
|
/**
|
|
15
23
|
* Options for auto-registration in React Native
|
|
16
24
|
*/
|
|
17
|
-
|
|
25
|
+
interface NativeAutoRegisterOptions {
|
|
18
26
|
/** Enable auto-registration (default: true in dev mode) */
|
|
19
27
|
enabled?: boolean;
|
|
20
28
|
/** Callback when element is registered */
|
|
@@ -25,7 +33,7 @@ export interface NativeAutoRegisterOptions {
|
|
|
25
33
|
/**
|
|
26
34
|
* Handle returned by useAutoRegister for programmatic registration
|
|
27
35
|
*/
|
|
28
|
-
|
|
36
|
+
interface NativeAutoRegisterHandle {
|
|
29
37
|
/** Register an element programmatically */
|
|
30
38
|
register: (id: string, ref: React.RefObject<NativeElementRef>, options?: RegisterElementOptions) => void;
|
|
31
39
|
/** Unregister an element programmatically */
|
|
@@ -59,6 +67,6 @@ export interface NativeAutoRegisterHandle {
|
|
|
59
67
|
* }
|
|
60
68
|
* ```
|
|
61
69
|
*/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
declare function useAutoRegister(options?: NativeAutoRegisterOptions): NativeAutoRegisterHandle;
|
|
71
|
+
|
|
72
|
+
export { type NativeAutoRegisterHandle, type NativeAutoRegisterOptions, useAutoRegister };
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,11 +1,72 @@
|
|
|
1
|
+
export { b as ComponentActionDef, c as ComponentActionRequest, d as ComponentActionResponse, h as ToastRecorderInput, U as UIBridgeNativeContextValue, i as UIBridgeNativeProvider, j as UIBridgeNativeProviderProps, k as UIBridgeProps, n as UseUIBridgeModalOptions, o as UseUIBridgeReturn, p as UseUIBridgeToastOptions, q as UseUIComponentOptions, r as UseUIComponentReturn, s as UseUIElementOptions, t as UseUIElementReturn, u as UseUIElementWithPropsReturn, v as useToastRecorder, w as useUIBridge, x as useUIBridgeModal, y as useUIBridgeNative, z as useUIBridgeNativeOptional, B as useUIBridgeNativeRequired, E as useUIBridgeRequired, F as useUIBridgeToast, G as useUIComponent, H as useUIComponentAction, I as useUIElement, J as useUIElementWithProps } from '../useUIBridgeToast-D32KXyL1.js';
|
|
2
|
+
import { j as NativeElementRef } from '../types-Oz13YLZq.js';
|
|
3
|
+
import { b as RegisterElementOptions } from '../registry-z-3WJsxp.js';
|
|
4
|
+
import 'react/jsx-runtime';
|
|
5
|
+
import 'react';
|
|
6
|
+
import '../types-ajakyp-l.js';
|
|
7
|
+
import '../http-server-Dj3grQW4.js';
|
|
8
|
+
import '../design-types-BHr6Q1FX.js';
|
|
9
|
+
|
|
1
10
|
/**
|
|
2
|
-
*
|
|
11
|
+
* useAutoRegister Hook for React Native
|
|
3
12
|
*
|
|
4
|
-
*
|
|
13
|
+
* Enables automatic registration of interactive React Native components.
|
|
14
|
+
* Unlike the web version, this doesn't use MutationObserver (no DOM).
|
|
15
|
+
* Instead, it provides a simple API for components to register themselves.
|
|
16
|
+
*
|
|
17
|
+
* For comprehensive coverage in React Native, components should:
|
|
18
|
+
* 1. Use this hook at the app root to enable the registry
|
|
19
|
+
* 2. Use useUIElement on interactive components (or wrap with Pressable/TouchableOpacity)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Options for auto-registration in React Native
|
|
24
|
+
*/
|
|
25
|
+
interface NativeAutoRegisterOptions {
|
|
26
|
+
/** Enable auto-registration (default: true in dev mode) */
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
/** Callback when element is registered */
|
|
29
|
+
onRegister?: (id: string) => void;
|
|
30
|
+
/** Callback when element is unregistered */
|
|
31
|
+
onUnregister?: (id: string) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Handle returned by useAutoRegister for programmatic registration
|
|
35
|
+
*/
|
|
36
|
+
interface NativeAutoRegisterHandle {
|
|
37
|
+
/** Register an element programmatically */
|
|
38
|
+
register: (id: string, ref: React.RefObject<NativeElementRef>, options?: RegisterElementOptions) => void;
|
|
39
|
+
/** Unregister an element programmatically */
|
|
40
|
+
unregister: (id: string) => void;
|
|
41
|
+
/** Check if auto-registration is enabled */
|
|
42
|
+
isEnabled: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Hook for enabling automatic element registration in React Native.
|
|
46
|
+
*
|
|
47
|
+
* Since React Native doesn't have a DOM to observe, this hook provides
|
|
48
|
+
* a handle for programmatic registration. For most use cases, you should
|
|
49
|
+
* use useUIElement directly on interactive components.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* function App() {
|
|
54
|
+
* const autoRegister = useAutoRegister({ enabled: __DEV__ });
|
|
55
|
+
*
|
|
56
|
+
* // Programmatic registration (rare use case)
|
|
57
|
+
* useEffect(() => {
|
|
58
|
+
* autoRegister.register('custom-element', {
|
|
59
|
+
* type: 'button',
|
|
60
|
+
* label: 'Custom Button',
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* return () => autoRegister.unregister('custom-element');
|
|
64
|
+
* }, []);
|
|
65
|
+
*
|
|
66
|
+
* return <YourApp />;
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
5
69
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export
|
|
9
|
-
export * from './useUIBridge';
|
|
10
|
-
export * from './useAutoRegister';
|
|
11
|
-
//# sourceMappingURL=index.d.ts.map
|
|
70
|
+
declare function useAutoRegister(options?: NativeAutoRegisterOptions): NativeAutoRegisterHandle;
|
|
71
|
+
|
|
72
|
+
export { type NativeAutoRegisterHandle, type NativeAutoRegisterOptions, useAutoRegister };
|
package/dist/react/index.js
CHANGED
|
@@ -55,9 +55,34 @@ var NativeUIBridgeRegistry = class {
|
|
|
55
55
|
this.components = /* @__PURE__ */ new Map();
|
|
56
56
|
this.workflows = /* @__PURE__ */ new Map();
|
|
57
57
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
58
|
+
this.enrichers = {};
|
|
59
|
+
this.snapshotExtras = /* @__PURE__ */ new Map();
|
|
58
60
|
this.config = config;
|
|
59
61
|
}
|
|
60
62
|
// ============================================================================
|
|
63
|
+
// Snapshot Enricher Slots
|
|
64
|
+
// ============================================================================
|
|
65
|
+
/**
|
|
66
|
+
* Register/replace canonical enrichers (modal/toast/undo). HMR-safe — calling
|
|
67
|
+
* with a partial set merges into existing slots instead of clobbering them.
|
|
68
|
+
*/
|
|
69
|
+
setEnrichers(e) {
|
|
70
|
+
this.enrichers = { ...this.enrichers, ...e };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Register a custom snapshot enricher. The returned object will be
|
|
74
|
+
* `Object.assign`ed onto the snapshot, so use unique top-level keys to avoid
|
|
75
|
+
* clobbering canonical fields. Returns a disposer.
|
|
76
|
+
*/
|
|
77
|
+
registerSnapshotEnricher(name, fn) {
|
|
78
|
+
this.snapshotExtras.set(name, fn);
|
|
79
|
+
return () => this.unregisterSnapshotEnricher(name);
|
|
80
|
+
}
|
|
81
|
+
/** Remove a custom snapshot enricher by name */
|
|
82
|
+
unregisterSnapshotEnricher(name) {
|
|
83
|
+
this.snapshotExtras.delete(name);
|
|
84
|
+
}
|
|
85
|
+
// ============================================================================
|
|
61
86
|
// Element Management
|
|
62
87
|
// ============================================================================
|
|
63
88
|
/**
|
|
@@ -420,7 +445,7 @@ var NativeUIBridgeRegistry = class {
|
|
|
420
445
|
const currentRoute = routeInfo.currentRoute;
|
|
421
446
|
elements = elements.filter((e) => e.registrationRoute === currentRoute);
|
|
422
447
|
}
|
|
423
|
-
|
|
448
|
+
const snapshot = {
|
|
424
449
|
timestamp: Date.now(),
|
|
425
450
|
elements: elements.map((e) => {
|
|
426
451
|
const handlers = extractHandlerNames(e.props);
|
|
@@ -452,6 +477,49 @@ var NativeUIBridgeRegistry = class {
|
|
|
452
477
|
currentRoute: routeInfo?.currentRoute ?? null,
|
|
453
478
|
segments: routeInfo?.segments
|
|
454
479
|
};
|
|
480
|
+
if (this.enrichers.modalDetector) {
|
|
481
|
+
try {
|
|
482
|
+
snapshot.modalStack = this.enrichers.modalDetector.getSnapshotModalContext();
|
|
483
|
+
} catch (error3) {
|
|
484
|
+
if (this.config.verbose) {
|
|
485
|
+
console.warn(`[ui-bridge-native] modalDetector enricher threw:`, error3);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (this.enrichers.toastCapture) {
|
|
490
|
+
try {
|
|
491
|
+
snapshot.toasts = this.enrichers.toastCapture.getSnapshotToastContext();
|
|
492
|
+
} catch (error3) {
|
|
493
|
+
if (this.config.verbose) {
|
|
494
|
+
console.warn(`[ui-bridge-native] toastCapture enricher threw:`, error3);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (this.enrichers.undoTracker) {
|
|
499
|
+
try {
|
|
500
|
+
snapshot.undoRedo = this.enrichers.undoTracker.getSnapshotUndoContext();
|
|
501
|
+
} catch (error3) {
|
|
502
|
+
if (this.config.verbose) {
|
|
503
|
+
console.warn(`[ui-bridge-native] undoTracker enricher threw:`, error3);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (this.snapshotExtras.size > 0) {
|
|
508
|
+
const ctx = { elements, currentRoute: routeInfo?.currentRoute ?? null };
|
|
509
|
+
for (const [name, fn] of this.snapshotExtras) {
|
|
510
|
+
try {
|
|
511
|
+
const extra = fn(ctx);
|
|
512
|
+
if (extra && typeof extra === "object") {
|
|
513
|
+
Object.assign(snapshot, extra);
|
|
514
|
+
}
|
|
515
|
+
} catch (error3) {
|
|
516
|
+
if (this.config.verbose) {
|
|
517
|
+
console.warn(`[ui-bridge-native] snapshot enricher "${name}" threw:`, error3);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return snapshot;
|
|
455
523
|
}
|
|
456
524
|
/**
|
|
457
525
|
* Get registry statistics
|
|
@@ -545,6 +613,23 @@ function sleep(ms) {
|
|
|
545
613
|
var DefaultNativeActionExecutor = class {
|
|
546
614
|
constructor(registry) {
|
|
547
615
|
this.registry = registry;
|
|
616
|
+
this.actionListeners = /* @__PURE__ */ new Set();
|
|
617
|
+
}
|
|
618
|
+
onActionExecuted(listener) {
|
|
619
|
+
this.actionListeners.add(listener);
|
|
620
|
+
return () => {
|
|
621
|
+
this.actionListeners.delete(listener);
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
/** Notify listeners out-of-band; isolate each invocation so one bad listener can't poison the rest. */
|
|
625
|
+
emitActionEvent(event) {
|
|
626
|
+
for (const listener of this.actionListeners) {
|
|
627
|
+
try {
|
|
628
|
+
listener(event);
|
|
629
|
+
} catch (error3) {
|
|
630
|
+
console.warn(`[ui-bridge-native] Action listener error:`, error3);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
548
633
|
}
|
|
549
634
|
/**
|
|
550
635
|
* Execute an action on an element
|
|
@@ -558,11 +643,23 @@ var DefaultNativeActionExecutor = class {
|
|
|
558
643
|
registered = findElementByIdentifier(elementId) ?? void 0;
|
|
559
644
|
}
|
|
560
645
|
if (!registered) {
|
|
646
|
+
const failTime = Date.now();
|
|
647
|
+
const errorMsg = `Element not found: ${elementId}`;
|
|
648
|
+
this.emitActionEvent({
|
|
649
|
+
elementId,
|
|
650
|
+
action: request.action,
|
|
651
|
+
params: request.params,
|
|
652
|
+
success: false,
|
|
653
|
+
error: errorMsg,
|
|
654
|
+
timestamp: failTime,
|
|
655
|
+
requestId: request.requestId,
|
|
656
|
+
durationMs: failTime - startTime
|
|
657
|
+
});
|
|
561
658
|
return {
|
|
562
659
|
success: false,
|
|
563
|
-
error:
|
|
564
|
-
durationMs:
|
|
565
|
-
timestamp:
|
|
660
|
+
error: errorMsg,
|
|
661
|
+
durationMs: failTime - startTime,
|
|
662
|
+
timestamp: failTime,
|
|
566
663
|
requestId: request.requestId
|
|
567
664
|
};
|
|
568
665
|
}
|
|
@@ -570,33 +667,67 @@ var DefaultNativeActionExecutor = class {
|
|
|
570
667
|
const waitResult = await this.waitForElementInternal(registered.id, request.waitOptions);
|
|
571
668
|
waitDurationMs = waitResult.waitedMs;
|
|
572
669
|
if (!waitResult.met) {
|
|
670
|
+
const failTime = Date.now();
|
|
671
|
+
const errorMsg = waitResult.error || "Wait condition not met";
|
|
672
|
+
this.emitActionEvent({
|
|
673
|
+
elementId,
|
|
674
|
+
action: request.action,
|
|
675
|
+
params: request.params,
|
|
676
|
+
success: false,
|
|
677
|
+
error: errorMsg,
|
|
678
|
+
timestamp: failTime,
|
|
679
|
+
requestId: request.requestId,
|
|
680
|
+
durationMs: failTime - startTime
|
|
681
|
+
});
|
|
573
682
|
return {
|
|
574
683
|
success: false,
|
|
575
|
-
error:
|
|
576
|
-
durationMs:
|
|
577
|
-
timestamp:
|
|
684
|
+
error: errorMsg,
|
|
685
|
+
durationMs: failTime - startTime,
|
|
686
|
+
timestamp: failTime,
|
|
578
687
|
requestId: request.requestId,
|
|
579
688
|
waitDurationMs
|
|
580
689
|
};
|
|
581
690
|
}
|
|
582
691
|
}
|
|
583
692
|
const result = await this.performAction(registered, request.action, request.params);
|
|
693
|
+
const successTime = Date.now();
|
|
694
|
+
this.emitActionEvent({
|
|
695
|
+
elementId,
|
|
696
|
+
action: request.action,
|
|
697
|
+
params: request.params,
|
|
698
|
+
success: true,
|
|
699
|
+
timestamp: successTime,
|
|
700
|
+
requestId: request.requestId,
|
|
701
|
+
durationMs: successTime - startTime
|
|
702
|
+
});
|
|
584
703
|
return {
|
|
585
704
|
success: true,
|
|
586
705
|
elementState: registered.getState(),
|
|
587
706
|
result,
|
|
588
|
-
durationMs:
|
|
589
|
-
timestamp:
|
|
707
|
+
durationMs: successTime - startTime,
|
|
708
|
+
timestamp: successTime,
|
|
590
709
|
requestId: request.requestId,
|
|
591
710
|
waitDurationMs
|
|
592
711
|
};
|
|
593
712
|
} catch (error3) {
|
|
713
|
+
const failTime = Date.now();
|
|
714
|
+
const errorMsg = error3 instanceof Error ? error3.message : String(error3);
|
|
715
|
+
this.emitActionEvent({
|
|
716
|
+
elementId,
|
|
717
|
+
action: request.action,
|
|
718
|
+
params: request.params,
|
|
719
|
+
success: false,
|
|
720
|
+
error: errorMsg,
|
|
721
|
+
timestamp: failTime,
|
|
722
|
+
requestId: request.requestId,
|
|
723
|
+
durationMs: failTime - startTime
|
|
724
|
+
});
|
|
594
725
|
return {
|
|
595
726
|
success: false,
|
|
596
|
-
error:
|
|
727
|
+
error: errorMsg,
|
|
597
728
|
stack: error3 instanceof Error ? error3.stack : void 0,
|
|
598
|
-
durationMs:
|
|
599
|
-
timestamp:
|
|
729
|
+
durationMs: failTime - startTime,
|
|
730
|
+
timestamp: failTime,
|
|
600
731
|
requestId: request.requestId,
|
|
601
732
|
waitDurationMs
|
|
602
733
|
};
|
|
@@ -3514,6 +3645,186 @@ function parseQueryParams(path) {
|
|
|
3514
3645
|
}
|
|
3515
3646
|
return result;
|
|
3516
3647
|
}
|
|
3648
|
+
|
|
3649
|
+
// src/modal/modal-detector.ts
|
|
3650
|
+
var ModalDetector = class {
|
|
3651
|
+
constructor() {
|
|
3652
|
+
/** Map preserves O(1) idempotent replace by id; ordering comes from detectedAt. */
|
|
3653
|
+
this.modals = /* @__PURE__ */ new Map();
|
|
3654
|
+
}
|
|
3655
|
+
pushModal(modal) {
|
|
3656
|
+
const existing = this.modals.get(modal.id);
|
|
3657
|
+
const detectedAt = existing ? existing.detectedAt : Date.now();
|
|
3658
|
+
const next = {
|
|
3659
|
+
id: modal.id,
|
|
3660
|
+
title: modal.title,
|
|
3661
|
+
type: modal.type ?? "modal",
|
|
3662
|
+
blocking: modal.blocking ?? true,
|
|
3663
|
+
dismissible: modal.dismissible ?? true,
|
|
3664
|
+
detectedAt
|
|
3665
|
+
};
|
|
3666
|
+
this.modals.set(modal.id, next);
|
|
3667
|
+
}
|
|
3668
|
+
dismissModal(id) {
|
|
3669
|
+
this.modals.delete(id);
|
|
3670
|
+
}
|
|
3671
|
+
getSnapshotModalContext() {
|
|
3672
|
+
const active = this.getActive();
|
|
3673
|
+
const ctx = {
|
|
3674
|
+
modals: active,
|
|
3675
|
+
hasBlockingModal: active.some((m) => m.blocking),
|
|
3676
|
+
count: active.length
|
|
3677
|
+
};
|
|
3678
|
+
if (active.length > 0) {
|
|
3679
|
+
ctx.topModal = active[active.length - 1];
|
|
3680
|
+
}
|
|
3681
|
+
return ctx;
|
|
3682
|
+
}
|
|
3683
|
+
getActive() {
|
|
3684
|
+
return Array.from(this.modals.values()).sort((a, b) => a.detectedAt - b.detectedAt);
|
|
3685
|
+
}
|
|
3686
|
+
clear() {
|
|
3687
|
+
this.modals.clear();
|
|
3688
|
+
}
|
|
3689
|
+
};
|
|
3690
|
+
|
|
3691
|
+
// src/toast/toast-capture.ts
|
|
3692
|
+
var DEFAULT_MAX_RECENT = 50;
|
|
3693
|
+
var ToastCapture = class {
|
|
3694
|
+
constructor(config = {}) {
|
|
3695
|
+
this.active = /* @__PURE__ */ new Map();
|
|
3696
|
+
this.recent = [];
|
|
3697
|
+
this.totalCaptured = 0;
|
|
3698
|
+
this.maxRecent = config.maxRecent ?? DEFAULT_MAX_RECENT;
|
|
3699
|
+
}
|
|
3700
|
+
recordToast(toast) {
|
|
3701
|
+
if (this.active.has(toast.id)) {
|
|
3702
|
+
return;
|
|
3703
|
+
}
|
|
3704
|
+
const appearedAt = toast.appearedAt ?? Date.now();
|
|
3705
|
+
const entry = {
|
|
3706
|
+
id: toast.id,
|
|
3707
|
+
message: toast.message,
|
|
3708
|
+
level: toast.level ?? "info",
|
|
3709
|
+
appearedAt,
|
|
3710
|
+
visible: true,
|
|
3711
|
+
durationMs: 0
|
|
3712
|
+
};
|
|
3713
|
+
this.active.set(toast.id, entry);
|
|
3714
|
+
this.totalCaptured += 1;
|
|
3715
|
+
}
|
|
3716
|
+
dismissToast(id) {
|
|
3717
|
+
const entry = this.active.get(id);
|
|
3718
|
+
if (!entry) {
|
|
3719
|
+
return;
|
|
3720
|
+
}
|
|
3721
|
+
this.active.delete(id);
|
|
3722
|
+
const dismissedAt = Date.now();
|
|
3723
|
+
const dismissed = {
|
|
3724
|
+
...entry,
|
|
3725
|
+
dismissedAt,
|
|
3726
|
+
visible: false,
|
|
3727
|
+
durationMs: dismissedAt - entry.appearedAt
|
|
3728
|
+
};
|
|
3729
|
+
this.recent.push(dismissed);
|
|
3730
|
+
if (this.recent.length > this.maxRecent) {
|
|
3731
|
+
this.recent.shift();
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
getSnapshotToastContext() {
|
|
3735
|
+
const now = Date.now();
|
|
3736
|
+
const active = Array.from(this.active.values()).map((entry) => ({
|
|
3737
|
+
...entry,
|
|
3738
|
+
durationMs: now - entry.appearedAt
|
|
3739
|
+
}));
|
|
3740
|
+
return {
|
|
3741
|
+
active,
|
|
3742
|
+
recent: [...this.recent],
|
|
3743
|
+
totalCaptured: this.totalCaptured
|
|
3744
|
+
};
|
|
3745
|
+
}
|
|
3746
|
+
clear() {
|
|
3747
|
+
this.active.clear();
|
|
3748
|
+
this.recent = [];
|
|
3749
|
+
this.totalCaptured = 0;
|
|
3750
|
+
}
|
|
3751
|
+
};
|
|
3752
|
+
|
|
3753
|
+
// src/undo/undo-tracker.ts
|
|
3754
|
+
var DEFAULT_MAX_HISTORY = 50;
|
|
3755
|
+
function buildSummary(ctx) {
|
|
3756
|
+
if (!ctx.canUndo && !ctx.canRedo) {
|
|
3757
|
+
return "no undo available";
|
|
3758
|
+
}
|
|
3759
|
+
const parts = [];
|
|
3760
|
+
if (ctx.canUndo) {
|
|
3761
|
+
parts.push(`undo: '${ctx.undoDescription ?? "available"}'`);
|
|
3762
|
+
}
|
|
3763
|
+
if (ctx.canRedo) {
|
|
3764
|
+
parts.push(`redo: '${ctx.redoDescription ?? "available"}'`);
|
|
3765
|
+
}
|
|
3766
|
+
return parts.join(" / ");
|
|
3767
|
+
}
|
|
3768
|
+
var UndoTracker = class {
|
|
3769
|
+
constructor(config = {}) {
|
|
3770
|
+
this.actions = [];
|
|
3771
|
+
this.declaredState = null;
|
|
3772
|
+
this.maxHistory = config.maxHistory ?? DEFAULT_MAX_HISTORY;
|
|
3773
|
+
}
|
|
3774
|
+
recordAction(record) {
|
|
3775
|
+
this.actions.push(record);
|
|
3776
|
+
if (this.actions.length > this.maxHistory) {
|
|
3777
|
+
this.actions = this.actions.slice(-this.maxHistory);
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
setDeclaredState(state) {
|
|
3781
|
+
this.declaredState = state;
|
|
3782
|
+
}
|
|
3783
|
+
getActionHistory() {
|
|
3784
|
+
return this.actions.slice();
|
|
3785
|
+
}
|
|
3786
|
+
getSnapshotUndoContext() {
|
|
3787
|
+
const declared = this.declaredState;
|
|
3788
|
+
if (declared) {
|
|
3789
|
+
const summary2 = buildSummary({
|
|
3790
|
+
canUndo: declared.canUndo,
|
|
3791
|
+
canRedo: declared.canRedo,
|
|
3792
|
+
undoDescription: declared.undoDescription,
|
|
3793
|
+
redoDescription: declared.redoDescription
|
|
3794
|
+
});
|
|
3795
|
+
return {
|
|
3796
|
+
canUndo: declared.canUndo,
|
|
3797
|
+
canRedo: declared.canRedo,
|
|
3798
|
+
undoDescription: declared.undoDescription,
|
|
3799
|
+
redoDescription: declared.redoDescription,
|
|
3800
|
+
undoDepth: declared.undoDepth,
|
|
3801
|
+
redoDepth: declared.redoDepth,
|
|
3802
|
+
summary: summary2
|
|
3803
|
+
};
|
|
3804
|
+
}
|
|
3805
|
+
const canUndo = this.actions.some((a) => a.reversible !== false);
|
|
3806
|
+
const canRedo = false;
|
|
3807
|
+
const lastReversible = [...this.actions].reverse().find((a) => a.reversible !== false);
|
|
3808
|
+
const undoDescription = lastReversible?.description;
|
|
3809
|
+
const summary = buildSummary({ canUndo, canRedo, undoDescription });
|
|
3810
|
+
return {
|
|
3811
|
+
canUndo,
|
|
3812
|
+
canRedo,
|
|
3813
|
+
undoDescription,
|
|
3814
|
+
summary
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
executeUndo() {
|
|
3818
|
+
return { ok: false, reason: "Not implemented in native" };
|
|
3819
|
+
}
|
|
3820
|
+
executeRedo() {
|
|
3821
|
+
return { ok: false, reason: "Not implemented in native" };
|
|
3822
|
+
}
|
|
3823
|
+
clear() {
|
|
3824
|
+
this.actions = [];
|
|
3825
|
+
this.declaredState = null;
|
|
3826
|
+
}
|
|
3827
|
+
};
|
|
3517
3828
|
var UIBridgeNativeContext = react.createContext(null);
|
|
3518
3829
|
function UIBridgeNativeProvider({
|
|
3519
3830
|
children,
|
|
@@ -3530,6 +3841,9 @@ function UIBridgeNativeProvider({
|
|
|
3530
3841
|
}) {
|
|
3531
3842
|
const registryRef = react.useRef(null);
|
|
3532
3843
|
const executorRef = react.useRef(null);
|
|
3844
|
+
const modalDetectorRef = react.useRef(null);
|
|
3845
|
+
const toastCaptureRef = react.useRef(null);
|
|
3846
|
+
const undoTrackerRef = react.useRef(null);
|
|
3533
3847
|
const [serverRunning, setServerRunning] = react.useState(false);
|
|
3534
3848
|
if (!registryRef.current) {
|
|
3535
3849
|
registryRef.current = new NativeUIBridgeRegistry({
|
|
@@ -3543,6 +3857,12 @@ function UIBridgeNativeProvider({
|
|
|
3543
3857
|
executorRef.current = createNativeActionExecutor(registry);
|
|
3544
3858
|
}
|
|
3545
3859
|
const executor = executorRef.current;
|
|
3860
|
+
if (!modalDetectorRef.current) modalDetectorRef.current = new ModalDetector();
|
|
3861
|
+
if (!toastCaptureRef.current) toastCaptureRef.current = new ToastCapture();
|
|
3862
|
+
if (!undoTrackerRef.current) undoTrackerRef.current = new UndoTracker();
|
|
3863
|
+
const modalDetector = modalDetectorRef.current;
|
|
3864
|
+
const toastCapture = toastCaptureRef.current;
|
|
3865
|
+
const undoTracker = undoTrackerRef.current;
|
|
3546
3866
|
const serverRef = react.useRef(null);
|
|
3547
3867
|
const eventBridgeRef = react.useRef(null);
|
|
3548
3868
|
const announcerRef = react.useRef(null);
|
|
@@ -3731,6 +4051,22 @@ function UIBridgeNativeProvider({
|
|
|
3731
4051
|
lastRoute = current;
|
|
3732
4052
|
});
|
|
3733
4053
|
}, [routeProvider, registry]);
|
|
4054
|
+
react.useEffect(() => {
|
|
4055
|
+
registry.setEnrichers({ modalDetector, toastCapture, undoTracker });
|
|
4056
|
+
}, [registry, modalDetector, toastCapture, undoTracker]);
|
|
4057
|
+
react.useEffect(() => {
|
|
4058
|
+
const unsubscribe = executor.onActionExecuted((event) => {
|
|
4059
|
+
if (!event.success) return;
|
|
4060
|
+
undoTracker.recordAction({
|
|
4061
|
+
id: event.requestId ?? `action-${event.timestamp}`,
|
|
4062
|
+
type: event.action,
|
|
4063
|
+
targetId: event.elementId,
|
|
4064
|
+
timestamp: event.timestamp,
|
|
4065
|
+
reversible: true
|
|
4066
|
+
});
|
|
4067
|
+
});
|
|
4068
|
+
return unsubscribe;
|
|
4069
|
+
}, [executor, undoTracker]);
|
|
3734
4070
|
const on = react.useCallback(
|
|
3735
4071
|
(type, listener) => registry.on(type, listener),
|
|
3736
4072
|
[registry]
|
|
@@ -3745,6 +4081,9 @@ function UIBridgeNativeProvider({
|
|
|
3745
4081
|
config,
|
|
3746
4082
|
registry,
|
|
3747
4083
|
executor,
|
|
4084
|
+
modalDetector,
|
|
4085
|
+
toastCapture,
|
|
4086
|
+
undoTracker,
|
|
3748
4087
|
getElements,
|
|
3749
4088
|
getComponents,
|
|
3750
4089
|
createSnapshot,
|
|
@@ -3761,6 +4100,9 @@ function UIBridgeNativeProvider({
|
|
|
3761
4100
|
config,
|
|
3762
4101
|
registry,
|
|
3763
4102
|
executor,
|
|
4103
|
+
modalDetector,
|
|
4104
|
+
toastCapture,
|
|
4105
|
+
undoTracker,
|
|
3764
4106
|
getElements,
|
|
3765
4107
|
getComponents,
|
|
3766
4108
|
createSnapshot,
|
|
@@ -4278,14 +4620,48 @@ function useAutoRegister(options = {}) {
|
|
|
4278
4620
|
isEnabled: enabled && !!bridge?.registry
|
|
4279
4621
|
};
|
|
4280
4622
|
}
|
|
4623
|
+
function useUIBridgeModal(opts) {
|
|
4624
|
+
const { modalDetector } = useUIBridgeNative();
|
|
4625
|
+
const { id, title, type, blocking, dismissible } = opts;
|
|
4626
|
+
react.useEffect(() => {
|
|
4627
|
+
modalDetector.pushModal({ id, title, type, blocking, dismissible });
|
|
4628
|
+
return () => {
|
|
4629
|
+
modalDetector.dismissModal(id);
|
|
4630
|
+
};
|
|
4631
|
+
}, [modalDetector, id, title, type, blocking, dismissible]);
|
|
4632
|
+
}
|
|
4633
|
+
function useUIBridgeToast(opts) {
|
|
4634
|
+
const { toastCapture } = useUIBridgeNative();
|
|
4635
|
+
const { id, message, level } = opts;
|
|
4636
|
+
react.useEffect(() => {
|
|
4637
|
+
toastCapture.recordToast({ id, message, level });
|
|
4638
|
+
return () => {
|
|
4639
|
+
toastCapture.dismissToast(id);
|
|
4640
|
+
};
|
|
4641
|
+
}, [toastCapture, id, message, level]);
|
|
4642
|
+
}
|
|
4643
|
+
function useToastRecorder() {
|
|
4644
|
+
const { toastCapture } = useUIBridgeNative();
|
|
4645
|
+
const recordToast = react.useCallback(
|
|
4646
|
+
(toast) => {
|
|
4647
|
+
toastCapture.recordToast(toast);
|
|
4648
|
+
},
|
|
4649
|
+
[toastCapture]
|
|
4650
|
+
);
|
|
4651
|
+
const dismissToast = react.useCallback((id) => toastCapture.dismissToast(id), [toastCapture]);
|
|
4652
|
+
return { recordToast, dismissToast };
|
|
4653
|
+
}
|
|
4281
4654
|
|
|
4282
4655
|
exports.UIBridgeNativeProvider = UIBridgeNativeProvider;
|
|
4283
4656
|
exports.useAutoRegister = useAutoRegister;
|
|
4657
|
+
exports.useToastRecorder = useToastRecorder;
|
|
4284
4658
|
exports.useUIBridge = useUIBridge;
|
|
4659
|
+
exports.useUIBridgeModal = useUIBridgeModal;
|
|
4285
4660
|
exports.useUIBridgeNative = useUIBridgeNative;
|
|
4286
4661
|
exports.useUIBridgeNativeOptional = useUIBridgeNativeOptional;
|
|
4287
4662
|
exports.useUIBridgeNativeRequired = useUIBridgeNativeRequired;
|
|
4288
4663
|
exports.useUIBridgeRequired = useUIBridgeRequired;
|
|
4664
|
+
exports.useUIBridgeToast = useUIBridgeToast;
|
|
4289
4665
|
exports.useUIComponent = useUIComponent;
|
|
4290
4666
|
exports.useUIComponentAction = useUIComponentAction;
|
|
4291
4667
|
exports.useUIElement = useUIElement;
|