@qontinui/ui-bridge-native 0.1.5 → 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/index.d.mts +9 -5
- package/dist/control/index.d.ts +9 -5
- package/dist/control/index.js +75 -12
- package/dist/control/index.js.map +1 -1
- package/dist/control/index.mjs +75 -12
- package/dist/control/index.mjs.map +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.d.ts +3 -3
- 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/index.js.map +1 -1
- package/dist/debug/index.mjs.map +1 -1
- package/dist/design/index.d.mts +1 -1
- package/dist/design/index.d.ts +1 -1
- package/dist/{http-server-D7uVANBU.d.mts → http-server-DCdR8WlL.d.mts} +3 -3
- package/dist/{http-server-B7KudcYJ.d.ts → http-server-Dj3grQW4.d.ts} +3 -3
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +498 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +493 -17
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +5 -5
- package/dist/react/index.d.ts +5 -5
- package/dist/react/index.js +504 -16
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +502 -17
- package/dist/react/index.mjs.map +1 -1
- package/dist/{registry-DhbGbl9c.d.mts → registry-Cahd8Gn2.d.mts} +16 -1
- package/dist/{registry-B8A_EoxS.d.ts → registry-z-3WJsxp.d.ts} +16 -1
- package/dist/server/index.d.mts +5 -5
- package/dist/server/index.d.ts +5 -5
- package/dist/server/index.js +102 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +102 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/{types-Bcj7ji97.d.mts → types-DAfCeWXc.d.mts} +22 -2
- package/dist/{types-BPNuYxDx.d.mts → types-Oz13YLZq.d.mts} +95 -1
- package/dist/{types-BPNuYxDx.d.ts → types-Oz13YLZq.d.ts} +95 -1
- package/dist/{types-BCYWwyVy.d.ts → types-ajakyp-l.d.ts} +22 -2
- package/dist/{useUIBridge-CDZbWxII.d.ts → useUIBridgeToast-D32KXyL1.d.ts} +154 -5
- package/dist/{useUIBridge-DfIEq9EQ.d.mts → useUIBridgeToast-GmT1TzUe.d.mts} +154 -5
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -53,9 +53,34 @@ var NativeUIBridgeRegistry = class {
|
|
|
53
53
|
this.components = /* @__PURE__ */ new Map();
|
|
54
54
|
this.workflows = /* @__PURE__ */ new Map();
|
|
55
55
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
56
|
+
this.enrichers = {};
|
|
57
|
+
this.snapshotExtras = /* @__PURE__ */ new Map();
|
|
56
58
|
this.config = config;
|
|
57
59
|
}
|
|
58
60
|
// ============================================================================
|
|
61
|
+
// Snapshot Enricher Slots
|
|
62
|
+
// ============================================================================
|
|
63
|
+
/**
|
|
64
|
+
* Register/replace canonical enrichers (modal/toast/undo). HMR-safe — calling
|
|
65
|
+
* with a partial set merges into existing slots instead of clobbering them.
|
|
66
|
+
*/
|
|
67
|
+
setEnrichers(e) {
|
|
68
|
+
this.enrichers = { ...this.enrichers, ...e };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Register a custom snapshot enricher. The returned object will be
|
|
72
|
+
* `Object.assign`ed onto the snapshot, so use unique top-level keys to avoid
|
|
73
|
+
* clobbering canonical fields. Returns a disposer.
|
|
74
|
+
*/
|
|
75
|
+
registerSnapshotEnricher(name, fn) {
|
|
76
|
+
this.snapshotExtras.set(name, fn);
|
|
77
|
+
return () => this.unregisterSnapshotEnricher(name);
|
|
78
|
+
}
|
|
79
|
+
/** Remove a custom snapshot enricher by name */
|
|
80
|
+
unregisterSnapshotEnricher(name) {
|
|
81
|
+
this.snapshotExtras.delete(name);
|
|
82
|
+
}
|
|
83
|
+
// ============================================================================
|
|
59
84
|
// Element Management
|
|
60
85
|
// ============================================================================
|
|
61
86
|
/**
|
|
@@ -418,7 +443,7 @@ var NativeUIBridgeRegistry = class {
|
|
|
418
443
|
const currentRoute = routeInfo.currentRoute;
|
|
419
444
|
elements = elements.filter((e) => e.registrationRoute === currentRoute);
|
|
420
445
|
}
|
|
421
|
-
|
|
446
|
+
const snapshot = {
|
|
422
447
|
timestamp: Date.now(),
|
|
423
448
|
elements: elements.map((e) => {
|
|
424
449
|
const handlers = extractHandlerNames(e.props);
|
|
@@ -450,6 +475,49 @@ var NativeUIBridgeRegistry = class {
|
|
|
450
475
|
currentRoute: routeInfo?.currentRoute ?? null,
|
|
451
476
|
segments: routeInfo?.segments
|
|
452
477
|
};
|
|
478
|
+
if (this.enrichers.modalDetector) {
|
|
479
|
+
try {
|
|
480
|
+
snapshot.modalStack = this.enrichers.modalDetector.getSnapshotModalContext();
|
|
481
|
+
} catch (error3) {
|
|
482
|
+
if (this.config.verbose) {
|
|
483
|
+
console.warn(`[ui-bridge-native] modalDetector enricher threw:`, error3);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (this.enrichers.toastCapture) {
|
|
488
|
+
try {
|
|
489
|
+
snapshot.toasts = this.enrichers.toastCapture.getSnapshotToastContext();
|
|
490
|
+
} catch (error3) {
|
|
491
|
+
if (this.config.verbose) {
|
|
492
|
+
console.warn(`[ui-bridge-native] toastCapture enricher threw:`, error3);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (this.enrichers.undoTracker) {
|
|
497
|
+
try {
|
|
498
|
+
snapshot.undoRedo = this.enrichers.undoTracker.getSnapshotUndoContext();
|
|
499
|
+
} catch (error3) {
|
|
500
|
+
if (this.config.verbose) {
|
|
501
|
+
console.warn(`[ui-bridge-native] undoTracker enricher threw:`, error3);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (this.snapshotExtras.size > 0) {
|
|
506
|
+
const ctx = { elements, currentRoute: routeInfo?.currentRoute ?? null };
|
|
507
|
+
for (const [name, fn] of this.snapshotExtras) {
|
|
508
|
+
try {
|
|
509
|
+
const extra = fn(ctx);
|
|
510
|
+
if (extra && typeof extra === "object") {
|
|
511
|
+
Object.assign(snapshot, extra);
|
|
512
|
+
}
|
|
513
|
+
} catch (error3) {
|
|
514
|
+
if (this.config.verbose) {
|
|
515
|
+
console.warn(`[ui-bridge-native] snapshot enricher "${name}" threw:`, error3);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return snapshot;
|
|
453
521
|
}
|
|
454
522
|
/**
|
|
455
523
|
* Get registry statistics
|
|
@@ -595,6 +663,23 @@ function sleep(ms) {
|
|
|
595
663
|
var DefaultNativeActionExecutor = class {
|
|
596
664
|
constructor(registry) {
|
|
597
665
|
this.registry = registry;
|
|
666
|
+
this.actionListeners = /* @__PURE__ */ new Set();
|
|
667
|
+
}
|
|
668
|
+
onActionExecuted(listener) {
|
|
669
|
+
this.actionListeners.add(listener);
|
|
670
|
+
return () => {
|
|
671
|
+
this.actionListeners.delete(listener);
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
/** Notify listeners out-of-band; isolate each invocation so one bad listener can't poison the rest. */
|
|
675
|
+
emitActionEvent(event) {
|
|
676
|
+
for (const listener of this.actionListeners) {
|
|
677
|
+
try {
|
|
678
|
+
listener(event);
|
|
679
|
+
} catch (error3) {
|
|
680
|
+
console.warn(`[ui-bridge-native] Action listener error:`, error3);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
598
683
|
}
|
|
599
684
|
/**
|
|
600
685
|
* Execute an action on an element
|
|
@@ -608,11 +693,23 @@ var DefaultNativeActionExecutor = class {
|
|
|
608
693
|
registered = findElementByIdentifier(elementId) ?? void 0;
|
|
609
694
|
}
|
|
610
695
|
if (!registered) {
|
|
696
|
+
const failTime = Date.now();
|
|
697
|
+
const errorMsg = `Element not found: ${elementId}`;
|
|
698
|
+
this.emitActionEvent({
|
|
699
|
+
elementId,
|
|
700
|
+
action: request.action,
|
|
701
|
+
params: request.params,
|
|
702
|
+
success: false,
|
|
703
|
+
error: errorMsg,
|
|
704
|
+
timestamp: failTime,
|
|
705
|
+
requestId: request.requestId,
|
|
706
|
+
durationMs: failTime - startTime
|
|
707
|
+
});
|
|
611
708
|
return {
|
|
612
709
|
success: false,
|
|
613
|
-
error:
|
|
614
|
-
durationMs:
|
|
615
|
-
timestamp:
|
|
710
|
+
error: errorMsg,
|
|
711
|
+
durationMs: failTime - startTime,
|
|
712
|
+
timestamp: failTime,
|
|
616
713
|
requestId: request.requestId
|
|
617
714
|
};
|
|
618
715
|
}
|
|
@@ -620,33 +717,67 @@ var DefaultNativeActionExecutor = class {
|
|
|
620
717
|
const waitResult = await this.waitForElementInternal(registered.id, request.waitOptions);
|
|
621
718
|
waitDurationMs = waitResult.waitedMs;
|
|
622
719
|
if (!waitResult.met) {
|
|
720
|
+
const failTime = Date.now();
|
|
721
|
+
const errorMsg = waitResult.error || "Wait condition not met";
|
|
722
|
+
this.emitActionEvent({
|
|
723
|
+
elementId,
|
|
724
|
+
action: request.action,
|
|
725
|
+
params: request.params,
|
|
726
|
+
success: false,
|
|
727
|
+
error: errorMsg,
|
|
728
|
+
timestamp: failTime,
|
|
729
|
+
requestId: request.requestId,
|
|
730
|
+
durationMs: failTime - startTime
|
|
731
|
+
});
|
|
623
732
|
return {
|
|
624
733
|
success: false,
|
|
625
|
-
error:
|
|
626
|
-
durationMs:
|
|
627
|
-
timestamp:
|
|
734
|
+
error: errorMsg,
|
|
735
|
+
durationMs: failTime - startTime,
|
|
736
|
+
timestamp: failTime,
|
|
628
737
|
requestId: request.requestId,
|
|
629
738
|
waitDurationMs
|
|
630
739
|
};
|
|
631
740
|
}
|
|
632
741
|
}
|
|
633
742
|
const result = await this.performAction(registered, request.action, request.params);
|
|
743
|
+
const successTime = Date.now();
|
|
744
|
+
this.emitActionEvent({
|
|
745
|
+
elementId,
|
|
746
|
+
action: request.action,
|
|
747
|
+
params: request.params,
|
|
748
|
+
success: true,
|
|
749
|
+
timestamp: successTime,
|
|
750
|
+
requestId: request.requestId,
|
|
751
|
+
durationMs: successTime - startTime
|
|
752
|
+
});
|
|
634
753
|
return {
|
|
635
754
|
success: true,
|
|
636
755
|
elementState: registered.getState(),
|
|
637
756
|
result,
|
|
638
|
-
durationMs:
|
|
639
|
-
timestamp:
|
|
757
|
+
durationMs: successTime - startTime,
|
|
758
|
+
timestamp: successTime,
|
|
640
759
|
requestId: request.requestId,
|
|
641
760
|
waitDurationMs
|
|
642
761
|
};
|
|
643
762
|
} catch (error3) {
|
|
763
|
+
const failTime = Date.now();
|
|
764
|
+
const errorMsg = error3 instanceof Error ? error3.message : String(error3);
|
|
765
|
+
this.emitActionEvent({
|
|
766
|
+
elementId,
|
|
767
|
+
action: request.action,
|
|
768
|
+
params: request.params,
|
|
769
|
+
success: false,
|
|
770
|
+
error: errorMsg,
|
|
771
|
+
timestamp: failTime,
|
|
772
|
+
requestId: request.requestId,
|
|
773
|
+
durationMs: failTime - startTime
|
|
774
|
+
});
|
|
644
775
|
return {
|
|
645
776
|
success: false,
|
|
646
|
-
error:
|
|
777
|
+
error: errorMsg,
|
|
647
778
|
stack: error3 instanceof Error ? error3.stack : void 0,
|
|
648
|
-
durationMs:
|
|
649
|
-
timestamp:
|
|
779
|
+
durationMs: failTime - startTime,
|
|
780
|
+
timestamp: failTime,
|
|
650
781
|
requestId: request.requestId,
|
|
651
782
|
waitDurationMs
|
|
652
783
|
};
|
|
@@ -739,7 +870,7 @@ var DefaultNativeActionExecutor = class {
|
|
|
739
870
|
if (!params?.text) {
|
|
740
871
|
throw new Error("Type action requires text parameter");
|
|
741
872
|
}
|
|
742
|
-
if (params.clearFirst) {
|
|
873
|
+
if (params.clearFirst || params.clear) {
|
|
743
874
|
await this.performClear(element, props);
|
|
744
875
|
}
|
|
745
876
|
if (params.delay && params.delay > 0) {
|
|
@@ -1972,6 +2103,10 @@ var WS_ROUTES = [
|
|
|
1972
2103
|
{ pattern: "health", handler: "health", httpMethods: ["GET"] },
|
|
1973
2104
|
// Elements
|
|
1974
2105
|
{ pattern: "control/elements", handler: "getElements", httpMethods: ["GET"] },
|
|
2106
|
+
// Flat alias — matches the runner UI Bridge's `/ui-bridge/elements` path so
|
|
2107
|
+
// agents can use the same URL across runner + mobile without remembering
|
|
2108
|
+
// the `control/` prefix. Delegates to the same handler.
|
|
2109
|
+
{ pattern: "elements", handler: "getElements", httpMethods: ["GET"] },
|
|
1975
2110
|
{ pattern: "control/element/:id", handler: "getElement", httpMethods: ["GET"] },
|
|
1976
2111
|
{ pattern: "control/element/:id/state", handler: "getElementState", httpMethods: ["GET"] },
|
|
1977
2112
|
{ pattern: "control/element/:id/action", handler: "executeAction", httpMethods: ["POST"] },
|
|
@@ -2022,6 +2157,87 @@ var WS_ROUTES = [
|
|
|
2022
2157
|
// Meta / Introspection
|
|
2023
2158
|
{ pattern: "meta/methods", handler: "getMethods", httpMethods: ["GET"] }
|
|
2024
2159
|
];
|
|
2160
|
+
var HANDLER_DESCRIPTIONS = {
|
|
2161
|
+
health: "Liveness probe; returns app metadata + element count",
|
|
2162
|
+
getElements: "Flat list of registered UI elements",
|
|
2163
|
+
getElement: "Single element by id",
|
|
2164
|
+
getElementState: "Dynamic state (value, focused, checked) for one element",
|
|
2165
|
+
executeAction: 'Run an action on an element (see "actions" below for names)',
|
|
2166
|
+
getComponents: "Higher-level components with action schemas",
|
|
2167
|
+
getComponent: "Component detail + per-action paramSchema",
|
|
2168
|
+
executeComponentAction: "Invoke a component action directly (preferred over button clicks)",
|
|
2169
|
+
find: "AI-powered element search by natural-language query",
|
|
2170
|
+
getSnapshot: "Full snapshot: elements + route + state. Supports ?visibleOnly=true and ?currentRouteOnly=true",
|
|
2171
|
+
getWorkflows: "List registered workflows",
|
|
2172
|
+
runWorkflow: "Trigger a workflow by id",
|
|
2173
|
+
pageRefresh: "Reload the current route",
|
|
2174
|
+
pageNavigate: "Imperative router.push(url) \u2014 preferred over tapping tab buttons",
|
|
2175
|
+
pageReplace: "router.replace(url)",
|
|
2176
|
+
pageGoBack: "router.back()",
|
|
2177
|
+
pageGoForward: "router.forward()",
|
|
2178
|
+
getScreenshot: "PNG screenshot of the current screen",
|
|
2179
|
+
getElementStyles: "Computed styles for an element (design review)",
|
|
2180
|
+
getElementStateStyles: "Computed styles per interaction state (hover/focus/disabled)",
|
|
2181
|
+
getDesignSnapshot: "Visual + style snapshot for design review",
|
|
2182
|
+
getResponsiveSnapshots: "Screenshots at multiple viewport widths",
|
|
2183
|
+
runDesignAudit: "Accessibility + visual audit",
|
|
2184
|
+
loadStyleGuide: "Load a design system style guide",
|
|
2185
|
+
getStyleGuide: "Fetch the currently loaded style guide",
|
|
2186
|
+
clearStyleGuide: "Unload the style guide",
|
|
2187
|
+
evaluateQuality: "Score the current UI against a quality context",
|
|
2188
|
+
getQualityContexts: "List available quality evaluation contexts",
|
|
2189
|
+
saveBaseline: "Save a quality baseline snapshot",
|
|
2190
|
+
diffBaseline: "Diff current UI against a saved baseline",
|
|
2191
|
+
getMethods: "List every handler name (legacy introspection)"
|
|
2192
|
+
};
|
|
2193
|
+
var ACTION_REFERENCE = [
|
|
2194
|
+
{ name: "press", aliases: ["click"], params: "PressActionParams (optional position)" },
|
|
2195
|
+
{ name: "longPress", params: "PressActionParams (optional position)" },
|
|
2196
|
+
{ name: "doubleTap", params: "none" },
|
|
2197
|
+
{
|
|
2198
|
+
name: "type",
|
|
2199
|
+
params: "TypeActionParams { text, delay?, clear? | clearFirst? } \u2014 `clear:true` replaces content atomically"
|
|
2200
|
+
},
|
|
2201
|
+
{ name: "clear", params: "none \u2014 empties the input" },
|
|
2202
|
+
{ name: "focus", params: "none" },
|
|
2203
|
+
{ name: "blur", params: "none" },
|
|
2204
|
+
{ name: "scroll", params: "ScrollActionParams { offset? {x,y}, to? }" },
|
|
2205
|
+
{ name: "swipe", params: "SwipeActionParams { direction, distance }" },
|
|
2206
|
+
{ name: "toggle", params: "none \u2014 flips boolean controls" }
|
|
2207
|
+
];
|
|
2208
|
+
function buildRoutesPayload() {
|
|
2209
|
+
const entries = [];
|
|
2210
|
+
for (const r of WS_ROUTES) {
|
|
2211
|
+
if (!r.httpMethods) continue;
|
|
2212
|
+
for (const m of r.httpMethods) {
|
|
2213
|
+
entries.push({ method: m, path: `/ui-bridge/${r.pattern}` });
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
entries.push({ method: "GET", path: "/ui-bridge/_help" });
|
|
2217
|
+
entries.push({ method: "GET", path: "/ui-bridge/_routes" });
|
|
2218
|
+
entries.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
|
|
2219
|
+
return { routes: entries, count: entries.length };
|
|
2220
|
+
}
|
|
2221
|
+
function buildHelpPayload() {
|
|
2222
|
+
return {
|
|
2223
|
+
version: 1,
|
|
2224
|
+
description: "UI Bridge Native HTTP surface. Prefix every path with /ui-bridge/.",
|
|
2225
|
+
routes: WS_ROUTES.map((r) => ({
|
|
2226
|
+
pattern: r.pattern,
|
|
2227
|
+
httpMethods: r.httpMethods ?? [],
|
|
2228
|
+
wsOnly: !r.httpMethods,
|
|
2229
|
+
handler: r.handler,
|
|
2230
|
+
description: HANDLER_DESCRIPTIONS[r.handler] ?? ""
|
|
2231
|
+
})),
|
|
2232
|
+
actions: ACTION_REFERENCE,
|
|
2233
|
+
tips: [
|
|
2234
|
+
"Start with `GET /ui-bridge/control/snapshot?visibleOnly=true` to see what the user actually sees.",
|
|
2235
|
+
"Prefer `control/component/:id/action/:actionId` over button presses \u2014 typed paramSchema, no button-id guessing.",
|
|
2236
|
+
"Use `pageNavigate` for tab switches instead of pressing tab buttons; gesture state often swallows clicks.",
|
|
2237
|
+
"The `type` action supports `clear:true` to replace existing text atomically in one call."
|
|
2238
|
+
]
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2025
2241
|
var NativeUIBridgeServer = class {
|
|
2026
2242
|
constructor(registry, executor, config = {}) {
|
|
2027
2243
|
this.registry = registry;
|
|
@@ -2038,7 +2254,9 @@ var NativeUIBridgeServer = class {
|
|
|
2038
2254
|
cors: true,
|
|
2039
2255
|
...config
|
|
2040
2256
|
};
|
|
2041
|
-
this.handlers = createServerHandlers(registry, executor
|
|
2257
|
+
this.handlers = createServerHandlers(registry, executor, {
|
|
2258
|
+
appInfo: this.config.appInfo
|
|
2259
|
+
});
|
|
2042
2260
|
this.handlers.getMethods = async () => {
|
|
2043
2261
|
const methods = WS_ROUTES.map((r) => ({
|
|
2044
2262
|
method: r.pattern,
|
|
@@ -2842,6 +3060,20 @@ var NativeUIBridgeServer = class {
|
|
|
2842
3060
|
};
|
|
2843
3061
|
}
|
|
2844
3062
|
const stripped = path.slice(PREFIX.length);
|
|
3063
|
+
if (method === "GET" && stripped === "_help") {
|
|
3064
|
+
return {
|
|
3065
|
+
success: true,
|
|
3066
|
+
data: buildHelpPayload(),
|
|
3067
|
+
timestamp: Date.now()
|
|
3068
|
+
};
|
|
3069
|
+
}
|
|
3070
|
+
if (method === "GET" && stripped === "_routes") {
|
|
3071
|
+
return {
|
|
3072
|
+
success: true,
|
|
3073
|
+
data: buildRoutesPayload(),
|
|
3074
|
+
timestamp: Date.now()
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
2845
3077
|
let patternMatchedButWrongVerb = false;
|
|
2846
3078
|
for (const route of WS_ROUTES) {
|
|
2847
3079
|
const pathParams = parsePath(route.pattern, stripped);
|
|
@@ -3463,6 +3695,186 @@ function parseQueryParams(path) {
|
|
|
3463
3695
|
}
|
|
3464
3696
|
return result;
|
|
3465
3697
|
}
|
|
3698
|
+
|
|
3699
|
+
// src/modal/modal-detector.ts
|
|
3700
|
+
var ModalDetector = class {
|
|
3701
|
+
constructor() {
|
|
3702
|
+
/** Map preserves O(1) idempotent replace by id; ordering comes from detectedAt. */
|
|
3703
|
+
this.modals = /* @__PURE__ */ new Map();
|
|
3704
|
+
}
|
|
3705
|
+
pushModal(modal) {
|
|
3706
|
+
const existing = this.modals.get(modal.id);
|
|
3707
|
+
const detectedAt = existing ? existing.detectedAt : Date.now();
|
|
3708
|
+
const next = {
|
|
3709
|
+
id: modal.id,
|
|
3710
|
+
title: modal.title,
|
|
3711
|
+
type: modal.type ?? "modal",
|
|
3712
|
+
blocking: modal.blocking ?? true,
|
|
3713
|
+
dismissible: modal.dismissible ?? true,
|
|
3714
|
+
detectedAt
|
|
3715
|
+
};
|
|
3716
|
+
this.modals.set(modal.id, next);
|
|
3717
|
+
}
|
|
3718
|
+
dismissModal(id) {
|
|
3719
|
+
this.modals.delete(id);
|
|
3720
|
+
}
|
|
3721
|
+
getSnapshotModalContext() {
|
|
3722
|
+
const active = this.getActive();
|
|
3723
|
+
const ctx = {
|
|
3724
|
+
modals: active,
|
|
3725
|
+
hasBlockingModal: active.some((m) => m.blocking),
|
|
3726
|
+
count: active.length
|
|
3727
|
+
};
|
|
3728
|
+
if (active.length > 0) {
|
|
3729
|
+
ctx.topModal = active[active.length - 1];
|
|
3730
|
+
}
|
|
3731
|
+
return ctx;
|
|
3732
|
+
}
|
|
3733
|
+
getActive() {
|
|
3734
|
+
return Array.from(this.modals.values()).sort((a, b) => a.detectedAt - b.detectedAt);
|
|
3735
|
+
}
|
|
3736
|
+
clear() {
|
|
3737
|
+
this.modals.clear();
|
|
3738
|
+
}
|
|
3739
|
+
};
|
|
3740
|
+
|
|
3741
|
+
// src/toast/toast-capture.ts
|
|
3742
|
+
var DEFAULT_MAX_RECENT = 50;
|
|
3743
|
+
var ToastCapture = class {
|
|
3744
|
+
constructor(config = {}) {
|
|
3745
|
+
this.active = /* @__PURE__ */ new Map();
|
|
3746
|
+
this.recent = [];
|
|
3747
|
+
this.totalCaptured = 0;
|
|
3748
|
+
this.maxRecent = config.maxRecent ?? DEFAULT_MAX_RECENT;
|
|
3749
|
+
}
|
|
3750
|
+
recordToast(toast) {
|
|
3751
|
+
if (this.active.has(toast.id)) {
|
|
3752
|
+
return;
|
|
3753
|
+
}
|
|
3754
|
+
const appearedAt = toast.appearedAt ?? Date.now();
|
|
3755
|
+
const entry = {
|
|
3756
|
+
id: toast.id,
|
|
3757
|
+
message: toast.message,
|
|
3758
|
+
level: toast.level ?? "info",
|
|
3759
|
+
appearedAt,
|
|
3760
|
+
visible: true,
|
|
3761
|
+
durationMs: 0
|
|
3762
|
+
};
|
|
3763
|
+
this.active.set(toast.id, entry);
|
|
3764
|
+
this.totalCaptured += 1;
|
|
3765
|
+
}
|
|
3766
|
+
dismissToast(id) {
|
|
3767
|
+
const entry = this.active.get(id);
|
|
3768
|
+
if (!entry) {
|
|
3769
|
+
return;
|
|
3770
|
+
}
|
|
3771
|
+
this.active.delete(id);
|
|
3772
|
+
const dismissedAt = Date.now();
|
|
3773
|
+
const dismissed = {
|
|
3774
|
+
...entry,
|
|
3775
|
+
dismissedAt,
|
|
3776
|
+
visible: false,
|
|
3777
|
+
durationMs: dismissedAt - entry.appearedAt
|
|
3778
|
+
};
|
|
3779
|
+
this.recent.push(dismissed);
|
|
3780
|
+
if (this.recent.length > this.maxRecent) {
|
|
3781
|
+
this.recent.shift();
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
getSnapshotToastContext() {
|
|
3785
|
+
const now = Date.now();
|
|
3786
|
+
const active = Array.from(this.active.values()).map((entry) => ({
|
|
3787
|
+
...entry,
|
|
3788
|
+
durationMs: now - entry.appearedAt
|
|
3789
|
+
}));
|
|
3790
|
+
return {
|
|
3791
|
+
active,
|
|
3792
|
+
recent: [...this.recent],
|
|
3793
|
+
totalCaptured: this.totalCaptured
|
|
3794
|
+
};
|
|
3795
|
+
}
|
|
3796
|
+
clear() {
|
|
3797
|
+
this.active.clear();
|
|
3798
|
+
this.recent = [];
|
|
3799
|
+
this.totalCaptured = 0;
|
|
3800
|
+
}
|
|
3801
|
+
};
|
|
3802
|
+
|
|
3803
|
+
// src/undo/undo-tracker.ts
|
|
3804
|
+
var DEFAULT_MAX_HISTORY = 50;
|
|
3805
|
+
function buildSummary(ctx) {
|
|
3806
|
+
if (!ctx.canUndo && !ctx.canRedo) {
|
|
3807
|
+
return "no undo available";
|
|
3808
|
+
}
|
|
3809
|
+
const parts = [];
|
|
3810
|
+
if (ctx.canUndo) {
|
|
3811
|
+
parts.push(`undo: '${ctx.undoDescription ?? "available"}'`);
|
|
3812
|
+
}
|
|
3813
|
+
if (ctx.canRedo) {
|
|
3814
|
+
parts.push(`redo: '${ctx.redoDescription ?? "available"}'`);
|
|
3815
|
+
}
|
|
3816
|
+
return parts.join(" / ");
|
|
3817
|
+
}
|
|
3818
|
+
var UndoTracker = class {
|
|
3819
|
+
constructor(config = {}) {
|
|
3820
|
+
this.actions = [];
|
|
3821
|
+
this.declaredState = null;
|
|
3822
|
+
this.maxHistory = config.maxHistory ?? DEFAULT_MAX_HISTORY;
|
|
3823
|
+
}
|
|
3824
|
+
recordAction(record) {
|
|
3825
|
+
this.actions.push(record);
|
|
3826
|
+
if (this.actions.length > this.maxHistory) {
|
|
3827
|
+
this.actions = this.actions.slice(-this.maxHistory);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
setDeclaredState(state) {
|
|
3831
|
+
this.declaredState = state;
|
|
3832
|
+
}
|
|
3833
|
+
getActionHistory() {
|
|
3834
|
+
return this.actions.slice();
|
|
3835
|
+
}
|
|
3836
|
+
getSnapshotUndoContext() {
|
|
3837
|
+
const declared = this.declaredState;
|
|
3838
|
+
if (declared) {
|
|
3839
|
+
const summary2 = buildSummary({
|
|
3840
|
+
canUndo: declared.canUndo,
|
|
3841
|
+
canRedo: declared.canRedo,
|
|
3842
|
+
undoDescription: declared.undoDescription,
|
|
3843
|
+
redoDescription: declared.redoDescription
|
|
3844
|
+
});
|
|
3845
|
+
return {
|
|
3846
|
+
canUndo: declared.canUndo,
|
|
3847
|
+
canRedo: declared.canRedo,
|
|
3848
|
+
undoDescription: declared.undoDescription,
|
|
3849
|
+
redoDescription: declared.redoDescription,
|
|
3850
|
+
undoDepth: declared.undoDepth,
|
|
3851
|
+
redoDepth: declared.redoDepth,
|
|
3852
|
+
summary: summary2
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
const canUndo = this.actions.some((a) => a.reversible !== false);
|
|
3856
|
+
const canRedo = false;
|
|
3857
|
+
const lastReversible = [...this.actions].reverse().find((a) => a.reversible !== false);
|
|
3858
|
+
const undoDescription = lastReversible?.description;
|
|
3859
|
+
const summary = buildSummary({ canUndo, canRedo, undoDescription });
|
|
3860
|
+
return {
|
|
3861
|
+
canUndo,
|
|
3862
|
+
canRedo,
|
|
3863
|
+
undoDescription,
|
|
3864
|
+
summary
|
|
3865
|
+
};
|
|
3866
|
+
}
|
|
3867
|
+
executeUndo() {
|
|
3868
|
+
return { ok: false, reason: "Not implemented in native" };
|
|
3869
|
+
}
|
|
3870
|
+
executeRedo() {
|
|
3871
|
+
return { ok: false, reason: "Not implemented in native" };
|
|
3872
|
+
}
|
|
3873
|
+
clear() {
|
|
3874
|
+
this.actions = [];
|
|
3875
|
+
this.declaredState = null;
|
|
3876
|
+
}
|
|
3877
|
+
};
|
|
3466
3878
|
var UIBridgeNativeContext = createContext(null);
|
|
3467
3879
|
function UIBridgeNativeProvider({
|
|
3468
3880
|
children,
|
|
@@ -3479,6 +3891,9 @@ function UIBridgeNativeProvider({
|
|
|
3479
3891
|
}) {
|
|
3480
3892
|
const registryRef = useRef(null);
|
|
3481
3893
|
const executorRef = useRef(null);
|
|
3894
|
+
const modalDetectorRef = useRef(null);
|
|
3895
|
+
const toastCaptureRef = useRef(null);
|
|
3896
|
+
const undoTrackerRef = useRef(null);
|
|
3482
3897
|
const [serverRunning, setServerRunning] = useState(false);
|
|
3483
3898
|
if (!registryRef.current) {
|
|
3484
3899
|
registryRef.current = new NativeUIBridgeRegistry({
|
|
@@ -3492,6 +3907,12 @@ function UIBridgeNativeProvider({
|
|
|
3492
3907
|
executorRef.current = createNativeActionExecutor(registry);
|
|
3493
3908
|
}
|
|
3494
3909
|
const executor = executorRef.current;
|
|
3910
|
+
if (!modalDetectorRef.current) modalDetectorRef.current = new ModalDetector();
|
|
3911
|
+
if (!toastCaptureRef.current) toastCaptureRef.current = new ToastCapture();
|
|
3912
|
+
if (!undoTrackerRef.current) undoTrackerRef.current = new UndoTracker();
|
|
3913
|
+
const modalDetector = modalDetectorRef.current;
|
|
3914
|
+
const toastCapture = toastCaptureRef.current;
|
|
3915
|
+
const undoTracker = undoTrackerRef.current;
|
|
3495
3916
|
const serverRef = useRef(null);
|
|
3496
3917
|
const eventBridgeRef = useRef(null);
|
|
3497
3918
|
const announcerRef = useRef(null);
|
|
@@ -3506,7 +3927,8 @@ function UIBridgeNativeProvider({
|
|
|
3506
3927
|
}
|
|
3507
3928
|
const server = createNativeServer(registry, executor, {
|
|
3508
3929
|
serverPort: config.serverPort || 8087,
|
|
3509
|
-
cors: true
|
|
3930
|
+
cors: true,
|
|
3931
|
+
appInfo: config.appInfo
|
|
3510
3932
|
});
|
|
3511
3933
|
server.setAdapter(serverAdapter);
|
|
3512
3934
|
if (navigationProvider) {
|
|
@@ -3593,7 +4015,8 @@ function UIBridgeNativeProvider({
|
|
|
3593
4015
|
try {
|
|
3594
4016
|
const bareServer = createNativeServer(registry, executor, {
|
|
3595
4017
|
serverPort: config.serverPort || 8087,
|
|
3596
|
-
cors: true
|
|
4018
|
+
cors: true,
|
|
4019
|
+
appInfo: config.appInfo
|
|
3597
4020
|
});
|
|
3598
4021
|
if (navigationProvider) bareServer.setNavigationProvider(navigationProvider);
|
|
3599
4022
|
if (screenshotProvider) bareServer.setScreenshotProvider(screenshotProvider);
|
|
@@ -3678,6 +4101,22 @@ function UIBridgeNativeProvider({
|
|
|
3678
4101
|
lastRoute = current;
|
|
3679
4102
|
});
|
|
3680
4103
|
}, [routeProvider, registry]);
|
|
4104
|
+
useEffect(() => {
|
|
4105
|
+
registry.setEnrichers({ modalDetector, toastCapture, undoTracker });
|
|
4106
|
+
}, [registry, modalDetector, toastCapture, undoTracker]);
|
|
4107
|
+
useEffect(() => {
|
|
4108
|
+
const unsubscribe = executor.onActionExecuted((event) => {
|
|
4109
|
+
if (!event.success) return;
|
|
4110
|
+
undoTracker.recordAction({
|
|
4111
|
+
id: event.requestId ?? `action-${event.timestamp}`,
|
|
4112
|
+
type: event.action,
|
|
4113
|
+
targetId: event.elementId,
|
|
4114
|
+
timestamp: event.timestamp,
|
|
4115
|
+
reversible: true
|
|
4116
|
+
});
|
|
4117
|
+
});
|
|
4118
|
+
return unsubscribe;
|
|
4119
|
+
}, [executor, undoTracker]);
|
|
3681
4120
|
const on = useCallback(
|
|
3682
4121
|
(type, listener) => registry.on(type, listener),
|
|
3683
4122
|
[registry]
|
|
@@ -3692,6 +4131,9 @@ function UIBridgeNativeProvider({
|
|
|
3692
4131
|
config,
|
|
3693
4132
|
registry,
|
|
3694
4133
|
executor,
|
|
4134
|
+
modalDetector,
|
|
4135
|
+
toastCapture,
|
|
4136
|
+
undoTracker,
|
|
3695
4137
|
getElements,
|
|
3696
4138
|
getComponents,
|
|
3697
4139
|
createSnapshot,
|
|
@@ -3708,6 +4150,9 @@ function UIBridgeNativeProvider({
|
|
|
3708
4150
|
config,
|
|
3709
4151
|
registry,
|
|
3710
4152
|
executor,
|
|
4153
|
+
modalDetector,
|
|
4154
|
+
toastCapture,
|
|
4155
|
+
undoTracker,
|
|
3711
4156
|
getElements,
|
|
3712
4157
|
getComponents,
|
|
3713
4158
|
createSnapshot,
|
|
@@ -4188,6 +4633,37 @@ function useUIBridgeRequired() {
|
|
|
4188
4633
|
}
|
|
4189
4634
|
return result;
|
|
4190
4635
|
}
|
|
4636
|
+
function useUIBridgeModal(opts) {
|
|
4637
|
+
const { modalDetector } = useUIBridgeNative();
|
|
4638
|
+
const { id, title, type, blocking, dismissible } = opts;
|
|
4639
|
+
useEffect(() => {
|
|
4640
|
+
modalDetector.pushModal({ id, title, type, blocking, dismissible });
|
|
4641
|
+
return () => {
|
|
4642
|
+
modalDetector.dismissModal(id);
|
|
4643
|
+
};
|
|
4644
|
+
}, [modalDetector, id, title, type, blocking, dismissible]);
|
|
4645
|
+
}
|
|
4646
|
+
function useUIBridgeToast(opts) {
|
|
4647
|
+
const { toastCapture } = useUIBridgeNative();
|
|
4648
|
+
const { id, message, level } = opts;
|
|
4649
|
+
useEffect(() => {
|
|
4650
|
+
toastCapture.recordToast({ id, message, level });
|
|
4651
|
+
return () => {
|
|
4652
|
+
toastCapture.dismissToast(id);
|
|
4653
|
+
};
|
|
4654
|
+
}, [toastCapture, id, message, level]);
|
|
4655
|
+
}
|
|
4656
|
+
function useToastRecorder() {
|
|
4657
|
+
const { toastCapture } = useUIBridgeNative();
|
|
4658
|
+
const recordToast = useCallback(
|
|
4659
|
+
(toast) => {
|
|
4660
|
+
toastCapture.recordToast(toast);
|
|
4661
|
+
},
|
|
4662
|
+
[toastCapture]
|
|
4663
|
+
);
|
|
4664
|
+
const dismissToast = useCallback((id) => toastCapture.dismissToast(id), [toastCapture]);
|
|
4665
|
+
return { recordToast, dismissToast };
|
|
4666
|
+
}
|
|
4191
4667
|
|
|
4192
4668
|
// src/server/types.ts
|
|
4193
4669
|
var UI_BRIDGE_NATIVE_ROUTES = {
|
|
@@ -5089,6 +5565,6 @@ var styles = StyleSheet.create({
|
|
|
5089
5565
|
}
|
|
5090
5566
|
});
|
|
5091
5567
|
|
|
5092
|
-
export { CloudRelayClient, DefaultNativeActionExecutor, DeviceAnnouncer, NativeUIBridgeRegistry, NativeUIBridgeServer, UIBridgeInspector, UIBridgeNativeProvider, UI_BRIDGE_NATIVE_ROUTES, WebSocketConnection, WebSocketEventBridge, buildTreePath, buildUpgradeResponse, captureNativeResponsiveSnapshot, captureNativeStateVariations, computeAcceptKey, createDesignHandlers, createNativeActionExecutor, createNativeElementIdentifier, createNativeServer, createServerHandlers, extractHandlerNames, findAllByPattern, findElementByIdentifier, getGlobalRegistry, getNativeElementDesignData, mapNativeStyleToExtended, matchesIdentifier, parseTreePath, resetGlobalRegistry, setGlobalRegistry, useUIBridge, useUIBridgeNative, useUIBridgeNativeOptional, useUIBridgeNativeRequired, useUIBridgeRequired, useUIComponent, useUIComponentAction, useUIElement, useUIElementWithProps };
|
|
5568
|
+
export { CloudRelayClient, DefaultNativeActionExecutor, DeviceAnnouncer, ModalDetector, NativeUIBridgeRegistry, NativeUIBridgeServer, ToastCapture, UIBridgeInspector, UIBridgeNativeProvider, UI_BRIDGE_NATIVE_ROUTES, UndoTracker, WebSocketConnection, WebSocketEventBridge, buildTreePath, buildUpgradeResponse, captureNativeResponsiveSnapshot, captureNativeStateVariations, computeAcceptKey, createDesignHandlers, createNativeActionExecutor, createNativeElementIdentifier, createNativeServer, createServerHandlers, extractHandlerNames, findAllByPattern, findElementByIdentifier, getGlobalRegistry, getNativeElementDesignData, mapNativeStyleToExtended, matchesIdentifier, parseTreePath, resetGlobalRegistry, setGlobalRegistry, useToastRecorder, useUIBridge, useUIBridgeModal, useUIBridgeNative, useUIBridgeNativeOptional, useUIBridgeNativeRequired, useUIBridgeRequired, useUIBridgeToast, useUIComponent, useUIComponentAction, useUIElement, useUIElementWithProps };
|
|
5093
5569
|
//# sourceMappingURL=index.mjs.map
|
|
5094
5570
|
//# sourceMappingURL=index.mjs.map
|