@ukiahinsure/a2ui-react-adapter 0.1.18 → 0.1.20
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.d.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
import { A as A2UIClientAdapter, a as A2UISurfaceMetadata, b as A2UISessionController, c as A2UIRoomLike } from './session-
|
|
2
|
-
export { d as A2UIActionDeliveryStatus, e as A2UIActionResult, f as A2UIActionState, g as A2UICapabilityEnvelope, h as A2UIClientActionEnvelope, i as A2UIClientAdapterOptions, j as A2UIClientErrorEnvelope, k as A2UIClientErrorEvent, l as A2UIEditingSnapshot, m as A2UIFieldControl, n as A2UIFieldDescriptor, o as A2UIFieldInteractionField, p as A2UIFieldInteractionGroup, q as
|
|
1
|
+
import { A as A2UIClientAdapter, a as A2UISurfaceMetadata, b as A2UISessionController, c as A2UIRoomLike } from './session-BnvcVx-k.js';
|
|
2
|
+
export { d as A2UIActionDeliveryStatus, e as A2UIActionResult, f as A2UIActionState, g as A2UICapabilityEnvelope, h as A2UIClientActionEnvelope, i as A2UIClientAdapterOptions, j as A2UIClientErrorEnvelope, k as A2UIClientErrorEvent, l as A2UIEditingSnapshot, m as A2UIFieldControl, n as A2UIFieldDescriptor, o as A2UIFieldInteractionField, p as A2UIFieldInteractionGroup, q as A2UIFieldUpdateEvent, r as A2UIFieldValue, s as A2UIPendingAction, t as A2UIRendererActionEvent, u as A2UIServerEnvelope, v as A2UISessionControllerOptions, w as A2UISessionSnapshot, x as A2UISessionStatus, y as A2UIStreamParticipant, z as A2UISurfaceStructure, B as A2UITabDescriptor, C as A2UITrustedServerParticipant, D as A2UI_CAPABILITIES_TOPIC, E as A2UI_CLIENT_TOPIC, F as A2UI_PROTOCOL_VERSION, G as A2UI_SERVER_TOPIC, H as AckEnvelope, I as AckFieldError, J as AdapterSurface, K as CLIENT_ACTIONS, L as CLIENT_ERROR_CODES, M as ClientAction, N as ClientErrorCode, O as ENVELOPE_VERSION, P as MAX_ENVELOPE_BYTES, Q as ParticipantRole, S as SectionNavigation, R as SnapshotEnvelope, T as SurfaceUpdateEnvelope } from './session-BnvcVx-k.js';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
|
|
5
5
|
/** Accessible host around the official A2UI v0.9.1 React surface. */
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* The agent changed what the form shows: it filled or corrected field values
|
|
9
|
+
* (`field-value`), or moved the conversation to a new current field group
|
|
10
|
+
* (`current-group`). `element` is the rendered container for the first
|
|
11
|
+
* affected field (the whole choice group or date editor, not one option), so
|
|
12
|
+
* hosts can scroll to it for any control type.
|
|
13
|
+
*/
|
|
14
|
+
interface A2UIAgentUpdateEvent {
|
|
15
|
+
reason: "field-value" | "current-group";
|
|
16
|
+
surfaceId: string;
|
|
17
|
+
fields: readonly string[];
|
|
18
|
+
groupId?: string;
|
|
19
|
+
element?: HTMLElement;
|
|
20
|
+
}
|
|
7
21
|
interface A2UISurfaceHostProps {
|
|
8
22
|
adapter: A2UIClientAdapter;
|
|
9
23
|
/** Render a specific surface; defaults to the adapter's current surface. */
|
|
@@ -25,8 +39,10 @@ interface A2UISurfaceHostProps {
|
|
|
25
39
|
noSurfaceFallback?: ReactNode;
|
|
26
40
|
/** Shown when the surface fails to render. */
|
|
27
41
|
errorFallback?: ReactNode;
|
|
42
|
+
/** Called after the DOM reflects an agent-driven field or group change. */
|
|
43
|
+
onAgentUpdate?: (event: A2UIAgentUpdateEvent) => void;
|
|
28
44
|
}
|
|
29
|
-
declare function A2UISurfaceHost({ adapter, surfaceId, surfaceMetadata, sessionController, hostId, ariaLabel, disabled, busy, invalidFields, validationSummaryId, noSurfaceFallback, errorFallback, }: A2UISurfaceHostProps): ReactNode;
|
|
45
|
+
declare function A2UISurfaceHost({ adapter, surfaceId, surfaceMetadata, sessionController, hostId, ariaLabel, disabled, busy, invalidFields, validationSummaryId, noSurfaceFallback, errorFallback, onAgentUpdate, }: A2UISurfaceHostProps): ReactNode;
|
|
30
46
|
|
|
31
47
|
/** Explicit room-ownership helper backed by backend-issued runtime material. */
|
|
32
48
|
|
|
@@ -48,4 +64,4 @@ interface A2UIJoinedRoom {
|
|
|
48
64
|
*/
|
|
49
65
|
declare function joinA2UIRoom(tokenProvider: A2UITokenProvider): Promise<A2UIJoinedRoom>;
|
|
50
66
|
|
|
51
|
-
export { A2UIClientAdapter, type A2UIJoinedRoom, A2UIRoomLike, type A2UIRuntimeMaterial, A2UISessionController, A2UISurfaceHost, type A2UISurfaceHostProps, A2UISurfaceMetadata, type A2UITokenProvider, joinA2UIRoom };
|
|
67
|
+
export { type A2UIAgentUpdateEvent, A2UIClientAdapter, type A2UIJoinedRoom, A2UIRoomLike, type A2UIRuntimeMaterial, A2UISessionController, A2UISurfaceHost, type A2UISurfaceHostProps, A2UISurfaceMetadata, type A2UITokenProvider, joinA2UIRoom };
|
package/dist/index.js
CHANGED
|
@@ -78,6 +78,14 @@ function parseDateDraft(raw) {
|
|
|
78
78
|
if (year < 1 || month < 1 || month > 12 || day < 1 || day > days[month - 1]) return null;
|
|
79
79
|
return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
80
80
|
}
|
|
81
|
+
function formatDateDigits(raw) {
|
|
82
|
+
if (!/^(\d*|\d{2}\/\d*|\d{2}\/\d{2}\/\d*)$/.test(raw)) return raw;
|
|
83
|
+
const digits = raw.replace(/\D/g, "").slice(0, 8);
|
|
84
|
+
const typedSlash = raw.endsWith("/") && (digits.length === 2 || digits.length === 4) ? "/" : "";
|
|
85
|
+
if (digits.length <= 2) return digits + typedSlash;
|
|
86
|
+
if (digits.length <= 4) return `${digits.slice(0, 2)}/${digits.slice(2)}${typedSlash}`;
|
|
87
|
+
return `${digits.slice(0, 2)}/${digits.slice(2, 4)}/${digits.slice(4)}`;
|
|
88
|
+
}
|
|
81
89
|
function displayDate(value) {
|
|
82
90
|
const iso = parseDateDraft(value);
|
|
83
91
|
return iso ? `${iso.slice(5, 7)}/${iso.slice(8, 10)}/${iso.slice(0, 4)}` : value;
|
|
@@ -103,7 +111,8 @@ function DateOnlyInput(props) {
|
|
|
103
111
|
const message = draft.trim() && !parsed ? "Enter a valid date in MM/DD/YYYY format." : parsed && min && parsed < min ? `Enter a date on or after ${displayDate(min)}.` : parsed && max && parsed > max ? `Enter a date on or before ${displayDate(max)}.` : "";
|
|
104
112
|
inputRef.current?.setCustomValidity(message);
|
|
105
113
|
}, [draft, parsed, min, max]);
|
|
106
|
-
const update = (
|
|
114
|
+
const update = (typed) => {
|
|
115
|
+
const raw = formatDateDigits(typed);
|
|
107
116
|
setDraft(raw);
|
|
108
117
|
const value = parseDateDraft(raw) || "";
|
|
109
118
|
published.current = value;
|
|
@@ -220,6 +229,7 @@ var A2UIClientAdapter = class {
|
|
|
220
229
|
#listeners = /* @__PURE__ */ new Set();
|
|
221
230
|
#actionListeners = /* @__PURE__ */ new Set();
|
|
222
231
|
#errorListeners = /* @__PURE__ */ new Set();
|
|
232
|
+
#fieldUpdateListeners = /* @__PURE__ */ new Set();
|
|
223
233
|
#surfaceOrder = [];
|
|
224
234
|
#structureBySurface = /* @__PURE__ */ new Map();
|
|
225
235
|
#persistedListRowsByPath = /* @__PURE__ */ new Map();
|
|
@@ -420,6 +430,10 @@ var A2UIClientAdapter = class {
|
|
|
420
430
|
this.#errorListeners.add(listener);
|
|
421
431
|
return () => this.#errorListeners.delete(listener);
|
|
422
432
|
};
|
|
433
|
+
subscribeFieldUpdates = (listener) => {
|
|
434
|
+
this.#fieldUpdateListeners.add(listener);
|
|
435
|
+
return () => this.#fieldUpdateListeners.delete(listener);
|
|
436
|
+
};
|
|
423
437
|
/** Monotonic change counter for `useSyncExternalStore`. */
|
|
424
438
|
getVersion = () => this.#version;
|
|
425
439
|
dispose() {
|
|
@@ -436,6 +450,7 @@ var A2UIClientAdapter = class {
|
|
|
436
450
|
this.#listeners.clear();
|
|
437
451
|
this.#actionListeners.clear();
|
|
438
452
|
this.#errorListeners.clear();
|
|
453
|
+
this.#fieldUpdateListeners.clear();
|
|
439
454
|
}
|
|
440
455
|
#applySurfaceEnvelope(envelope) {
|
|
441
456
|
const validated = A2uiMessageListSchema.safeParse(envelope.messages);
|
|
@@ -502,6 +517,12 @@ var A2UIClientAdapter = class {
|
|
|
502
517
|
);
|
|
503
518
|
return false;
|
|
504
519
|
}
|
|
520
|
+
const changedFields = envelope.kind === "snapshot" ? [] : changedFieldNames(
|
|
521
|
+
previous,
|
|
522
|
+
candidate,
|
|
523
|
+
envelope.surfaceId,
|
|
524
|
+
nextStructure.get(envelope.surfaceId)
|
|
525
|
+
);
|
|
505
526
|
this.#processor = candidate;
|
|
506
527
|
this.#surfaceOrder = [...candidate.model.surfacesMap.keys()];
|
|
507
528
|
this.#structureBySurface = nextStructure;
|
|
@@ -518,8 +539,20 @@ var A2UIClientAdapter = class {
|
|
|
518
539
|
);
|
|
519
540
|
previous.model.dispose();
|
|
520
541
|
this.#notify();
|
|
542
|
+
if (changedFields.length > 0) {
|
|
543
|
+
this.#emitFieldUpdate({
|
|
544
|
+
surfaceId: envelope.surfaceId,
|
|
545
|
+
fields: changedFields
|
|
546
|
+
});
|
|
547
|
+
}
|
|
521
548
|
return true;
|
|
522
549
|
}
|
|
550
|
+
#emitFieldUpdate(event) {
|
|
551
|
+
this.#options.onFieldUpdate?.(event);
|
|
552
|
+
for (const listener of this.#fieldUpdateListeners) {
|
|
553
|
+
listener(event);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
523
556
|
#createProcessor() {
|
|
524
557
|
return new MessageProcessor(
|
|
525
558
|
[editableDateCatalog],
|
|
@@ -1709,6 +1742,25 @@ function cloneJsonArray(value) {
|
|
|
1709
1742
|
function isFieldValue(value) {
|
|
1710
1743
|
return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
|
|
1711
1744
|
}
|
|
1745
|
+
function changedFieldNames(previous, candidate, surfaceId, structure) {
|
|
1746
|
+
const before = previous.model.getSurface(surfaceId);
|
|
1747
|
+
const after = candidate.model.getSurface(surfaceId);
|
|
1748
|
+
if (before === void 0 || after === void 0 || structure === void 0) {
|
|
1749
|
+
return [];
|
|
1750
|
+
}
|
|
1751
|
+
const fields = /* @__PURE__ */ new Set();
|
|
1752
|
+
for (const { field } of structure.fieldsByComponent.values()) {
|
|
1753
|
+
const path = fieldPointer(field);
|
|
1754
|
+
const next = after.dataModel.get(path);
|
|
1755
|
+
if (hasFieldValue(next) && !fieldValueEquivalent(before.dataModel.get(path), next)) {
|
|
1756
|
+
fields.add(field);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return [...fields];
|
|
1760
|
+
}
|
|
1761
|
+
function hasFieldValue(value) {
|
|
1762
|
+
return Array.isArray(value) ? value.length > 0 : value !== void 0 && value !== null && value !== "";
|
|
1763
|
+
}
|
|
1712
1764
|
function fieldValueEquivalent(left, right) {
|
|
1713
1765
|
if (Array.isArray(left) && Array.isArray(right)) {
|
|
1714
1766
|
return left.length === right.length && left.every((value, index) => Object.is(value, right[index]));
|
|
@@ -1747,6 +1799,7 @@ function classifyProcessingError(error) {
|
|
|
1747
1799
|
// src/SurfaceHost.tsx
|
|
1748
1800
|
import {
|
|
1749
1801
|
Component,
|
|
1802
|
+
useEffect,
|
|
1750
1803
|
useId as useId3,
|
|
1751
1804
|
useLayoutEffect as useLayoutEffect2,
|
|
1752
1805
|
useRef as useRef2,
|
|
@@ -1766,7 +1819,8 @@ function A2UISurfaceHost({
|
|
|
1766
1819
|
invalidFields = [],
|
|
1767
1820
|
validationSummaryId,
|
|
1768
1821
|
noSurfaceFallback,
|
|
1769
|
-
errorFallback
|
|
1822
|
+
errorFallback,
|
|
1823
|
+
onAgentUpdate
|
|
1770
1824
|
}) {
|
|
1771
1825
|
const version = useSyncExternalStore(
|
|
1772
1826
|
adapter.subscribe,
|
|
@@ -1785,12 +1839,26 @@ function A2UISurfaceHost({
|
|
|
1785
1839
|
const lastFocusedState = useRef2(void 0);
|
|
1786
1840
|
const lastViewedSection = useRef2(void 0);
|
|
1787
1841
|
const lastValidationKey = useRef2("");
|
|
1842
|
+
const pendingFieldUpdates = useRef2(/* @__PURE__ */ new Map());
|
|
1843
|
+
const lastQuestionKey = useRef2(void 0);
|
|
1844
|
+
const onAgentUpdateRef = useRef2(onAgentUpdate);
|
|
1845
|
+
onAgentUpdateRef.current = onAgentUpdate;
|
|
1788
1846
|
const resolvedSurfaceMetadata = surfaceMetadata ?? controllerSnapshot?.surface ?? null;
|
|
1789
1847
|
const editing = controllerSnapshot?.editing ?? editingForSurface(resolvedSurfaceMetadata);
|
|
1790
1848
|
const surface = surfaceId === void 0 ? adapter.currentSurface : adapter.getSurface(surfaceId);
|
|
1791
1849
|
const structure = surface === void 0 ? EMPTY_STRUCTURE : adapter.getSurfaceStructure(surface.id);
|
|
1792
1850
|
const focusKey = surface === void 0 ? void 0 : `${surface.id}:${resolvedSurfaceMetadata?.activeStateId ?? "surface"}`;
|
|
1793
1851
|
const frameId = hostId ?? `a2ui-surface-${generatedId}`;
|
|
1852
|
+
useEffect(
|
|
1853
|
+
() => adapter.subscribeFieldUpdates((event) => {
|
|
1854
|
+
const pending = pendingFieldUpdates.current.get(event.surfaceId) ?? /* @__PURE__ */ new Set();
|
|
1855
|
+
for (const field of event.fields) {
|
|
1856
|
+
pending.add(field);
|
|
1857
|
+
}
|
|
1858
|
+
pendingFieldUpdates.current.set(event.surfaceId, pending);
|
|
1859
|
+
}),
|
|
1860
|
+
[adapter]
|
|
1861
|
+
);
|
|
1794
1862
|
useLayoutEffect2(() => {
|
|
1795
1863
|
const root = rootRef.current;
|
|
1796
1864
|
if (root === null || surface === void 0) {
|
|
@@ -1833,6 +1901,44 @@ function A2UISurfaceHost({
|
|
|
1833
1901
|
),
|
|
1834
1902
|
setInteractionDisabled(root, disabled || busy)
|
|
1835
1903
|
];
|
|
1904
|
+
reportAgentUpdates();
|
|
1905
|
+
};
|
|
1906
|
+
const reportAgentUpdates = () => {
|
|
1907
|
+
const changedFields = [
|
|
1908
|
+
...pendingFieldUpdates.current.get(surface.id) ?? []
|
|
1909
|
+
];
|
|
1910
|
+
pendingFieldUpdates.current.clear();
|
|
1911
|
+
const currentGroup = resolvedSurfaceMetadata?.fieldInteractions.find(
|
|
1912
|
+
(group) => group.mode === "current"
|
|
1913
|
+
);
|
|
1914
|
+
const activeStateId = resolvedSurfaceMetadata?.activeStateId;
|
|
1915
|
+
const questionKey = currentGroup?.groupId ?? (activeStateId == null ? void 0 : `state:${activeStateId}`);
|
|
1916
|
+
const previousQuestionKey = lastQuestionKey.current;
|
|
1917
|
+
if (questionKey !== void 0) {
|
|
1918
|
+
lastQuestionKey.current = questionKey;
|
|
1919
|
+
}
|
|
1920
|
+
const report = onAgentUpdateRef.current;
|
|
1921
|
+
if (report === void 0) {
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
if (changedFields.length > 0) {
|
|
1925
|
+
report({
|
|
1926
|
+
reason: "field-value",
|
|
1927
|
+
surfaceId: surface.id,
|
|
1928
|
+
fields: changedFields,
|
|
1929
|
+
element: agentUpdateElement(root, changedFields, structure.fields)
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
if (questionKey !== void 0 && previousQuestionKey !== void 0 && questionKey !== previousQuestionKey) {
|
|
1933
|
+
const fields = currentGroup?.fields.map((binding) => binding.field) ?? [];
|
|
1934
|
+
report({
|
|
1935
|
+
reason: "current-group",
|
|
1936
|
+
surfaceId: surface.id,
|
|
1937
|
+
fields,
|
|
1938
|
+
...currentGroup === void 0 ? {} : { groupId: currentGroup.groupId },
|
|
1939
|
+
element: agentUpdateElement(root, fields, structure.fields) ?? currentQuestionElement(root)
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1836
1942
|
};
|
|
1837
1943
|
const scheduleControlRefresh = () => {
|
|
1838
1944
|
queueMicrotask(refreshControls);
|
|
@@ -1957,6 +2063,51 @@ function focusProgressTarget(root, metadata, invalidFields) {
|
|
|
1957
2063
|
});
|
|
1958
2064
|
}
|
|
1959
2065
|
}
|
|
2066
|
+
function agentUpdateElement(root, fields, descriptors) {
|
|
2067
|
+
const controls = [
|
|
2068
|
+
...root.querySelectorAll("[data-a2ui-field]")
|
|
2069
|
+
];
|
|
2070
|
+
for (const field of fields) {
|
|
2071
|
+
const control = controls.find(
|
|
2072
|
+
(candidate) => candidate.dataset.a2uiField === field
|
|
2073
|
+
);
|
|
2074
|
+
if (control !== void 0) {
|
|
2075
|
+
return control.closest("[data-a2ui-date-editor]") ?? control.closest('[role="radiogroup"]') ?? control;
|
|
2076
|
+
}
|
|
2077
|
+
const descriptor = descriptors.find(
|
|
2078
|
+
(candidate) => candidate.field === field
|
|
2079
|
+
);
|
|
2080
|
+
const labels = /* @__PURE__ */ new Set([displayFieldLabel(field)]);
|
|
2081
|
+
if (descriptor !== void 0) {
|
|
2082
|
+
labels.add(descriptor.label);
|
|
2083
|
+
}
|
|
2084
|
+
const label = [...root.querySelectorAll("*")].find(
|
|
2085
|
+
(candidate) => candidate.childElementCount === 0 && labels.has(candidate.textContent?.trim() ?? "")
|
|
2086
|
+
);
|
|
2087
|
+
if (label?.parentElement != null && root.contains(label.parentElement)) {
|
|
2088
|
+
return label.parentElement;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
return void 0;
|
|
2092
|
+
}
|
|
2093
|
+
function currentQuestionElement(root) {
|
|
2094
|
+
const action = [
|
|
2095
|
+
...root.querySelectorAll("[data-a2ui-action='true']")
|
|
2096
|
+
].find(
|
|
2097
|
+
(candidate) => candidate.dataset.a2uiActionLabel !== "skip" && candidate.closest("[data-a2ui-loop-row]") === null
|
|
2098
|
+
);
|
|
2099
|
+
if (action === void 0) {
|
|
2100
|
+
return void 0;
|
|
2101
|
+
}
|
|
2102
|
+
let container = action.parentElement;
|
|
2103
|
+
if (container !== null && textOf(container) === [...container.querySelectorAll("button")].map(textOf).join("")) {
|
|
2104
|
+
container = container.parentElement;
|
|
2105
|
+
}
|
|
2106
|
+
return container !== null && container !== root && root.contains(container) ? container : action;
|
|
2107
|
+
}
|
|
2108
|
+
function textOf(element) {
|
|
2109
|
+
return (element.textContent ?? "").replace(/\s+/g, "");
|
|
2110
|
+
}
|
|
1960
2111
|
function isControlDisabled(control) {
|
|
1961
2112
|
return control.getAttribute("aria-disabled") === "true" || "disabled" in control && control.disabled === true;
|
|
1962
2113
|
}
|