@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-BcHSjUlD.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 A2UIFieldValue, r as A2UIPendingAction, s as A2UIRendererActionEvent, t as A2UIServerEnvelope, u as A2UISessionControllerOptions, v as A2UISessionSnapshot, w as A2UISessionStatus, x as A2UIStreamParticipant, y as A2UISurfaceStructure, z as A2UITabDescriptor, B as A2UITrustedServerParticipant, C as A2UI_CAPABILITIES_TOPIC, D as A2UI_CLIENT_TOPIC, E as A2UI_PROTOCOL_VERSION, F as A2UI_SERVER_TOPIC, G as AckEnvelope, H as AckFieldError, I as AdapterSurface, J as CLIENT_ACTIONS, K as CLIENT_ERROR_CODES, L as ClientAction, M as ClientErrorCode, N as ENVELOPE_VERSION, O as MAX_ENVELOPE_BYTES, P as ParticipantRole, S as SectionNavigation, Q as SnapshotEnvelope, R as SurfaceUpdateEnvelope } from './session-BcHSjUlD.js';
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 = (raw) => {
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
  }