@ukiahinsure/a2ui-react-adapter 0.1.18 → 0.1.19

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
@@ -220,6 +220,7 @@ var A2UIClientAdapter = class {
220
220
  #listeners = /* @__PURE__ */ new Set();
221
221
  #actionListeners = /* @__PURE__ */ new Set();
222
222
  #errorListeners = /* @__PURE__ */ new Set();
223
+ #fieldUpdateListeners = /* @__PURE__ */ new Set();
223
224
  #surfaceOrder = [];
224
225
  #structureBySurface = /* @__PURE__ */ new Map();
225
226
  #persistedListRowsByPath = /* @__PURE__ */ new Map();
@@ -420,6 +421,10 @@ var A2UIClientAdapter = class {
420
421
  this.#errorListeners.add(listener);
421
422
  return () => this.#errorListeners.delete(listener);
422
423
  };
424
+ subscribeFieldUpdates = (listener) => {
425
+ this.#fieldUpdateListeners.add(listener);
426
+ return () => this.#fieldUpdateListeners.delete(listener);
427
+ };
423
428
  /** Monotonic change counter for `useSyncExternalStore`. */
424
429
  getVersion = () => this.#version;
425
430
  dispose() {
@@ -436,6 +441,7 @@ var A2UIClientAdapter = class {
436
441
  this.#listeners.clear();
437
442
  this.#actionListeners.clear();
438
443
  this.#errorListeners.clear();
444
+ this.#fieldUpdateListeners.clear();
439
445
  }
440
446
  #applySurfaceEnvelope(envelope) {
441
447
  const validated = A2uiMessageListSchema.safeParse(envelope.messages);
@@ -502,6 +508,12 @@ var A2UIClientAdapter = class {
502
508
  );
503
509
  return false;
504
510
  }
511
+ const changedFields = envelope.kind === "snapshot" ? [] : changedFieldNames(
512
+ previous,
513
+ candidate,
514
+ envelope.surfaceId,
515
+ nextStructure.get(envelope.surfaceId)
516
+ );
505
517
  this.#processor = candidate;
506
518
  this.#surfaceOrder = [...candidate.model.surfacesMap.keys()];
507
519
  this.#structureBySurface = nextStructure;
@@ -518,8 +530,20 @@ var A2UIClientAdapter = class {
518
530
  );
519
531
  previous.model.dispose();
520
532
  this.#notify();
533
+ if (changedFields.length > 0) {
534
+ this.#emitFieldUpdate({
535
+ surfaceId: envelope.surfaceId,
536
+ fields: changedFields
537
+ });
538
+ }
521
539
  return true;
522
540
  }
541
+ #emitFieldUpdate(event) {
542
+ this.#options.onFieldUpdate?.(event);
543
+ for (const listener of this.#fieldUpdateListeners) {
544
+ listener(event);
545
+ }
546
+ }
523
547
  #createProcessor() {
524
548
  return new MessageProcessor(
525
549
  [editableDateCatalog],
@@ -1709,6 +1733,25 @@ function cloneJsonArray(value) {
1709
1733
  function isFieldValue(value) {
1710
1734
  return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
1711
1735
  }
1736
+ function changedFieldNames(previous, candidate, surfaceId, structure) {
1737
+ const before = previous.model.getSurface(surfaceId);
1738
+ const after = candidate.model.getSurface(surfaceId);
1739
+ if (before === void 0 || after === void 0 || structure === void 0) {
1740
+ return [];
1741
+ }
1742
+ const fields = /* @__PURE__ */ new Set();
1743
+ for (const { field } of structure.fieldsByComponent.values()) {
1744
+ const path = fieldPointer(field);
1745
+ const next = after.dataModel.get(path);
1746
+ if (hasFieldValue(next) && !fieldValueEquivalent(before.dataModel.get(path), next)) {
1747
+ fields.add(field);
1748
+ }
1749
+ }
1750
+ return [...fields];
1751
+ }
1752
+ function hasFieldValue(value) {
1753
+ return Array.isArray(value) ? value.length > 0 : value !== void 0 && value !== null && value !== "";
1754
+ }
1712
1755
  function fieldValueEquivalent(left, right) {
1713
1756
  if (Array.isArray(left) && Array.isArray(right)) {
1714
1757
  return left.length === right.length && left.every((value, index) => Object.is(value, right[index]));
@@ -1747,6 +1790,7 @@ function classifyProcessingError(error) {
1747
1790
  // src/SurfaceHost.tsx
1748
1791
  import {
1749
1792
  Component,
1793
+ useEffect,
1750
1794
  useId as useId3,
1751
1795
  useLayoutEffect as useLayoutEffect2,
1752
1796
  useRef as useRef2,
@@ -1766,7 +1810,8 @@ function A2UISurfaceHost({
1766
1810
  invalidFields = [],
1767
1811
  validationSummaryId,
1768
1812
  noSurfaceFallback,
1769
- errorFallback
1813
+ errorFallback,
1814
+ onAgentUpdate
1770
1815
  }) {
1771
1816
  const version = useSyncExternalStore(
1772
1817
  adapter.subscribe,
@@ -1785,12 +1830,26 @@ function A2UISurfaceHost({
1785
1830
  const lastFocusedState = useRef2(void 0);
1786
1831
  const lastViewedSection = useRef2(void 0);
1787
1832
  const lastValidationKey = useRef2("");
1833
+ const pendingFieldUpdates = useRef2(/* @__PURE__ */ new Map());
1834
+ const lastCurrentGroupId = useRef2(void 0);
1835
+ const onAgentUpdateRef = useRef2(onAgentUpdate);
1836
+ onAgentUpdateRef.current = onAgentUpdate;
1788
1837
  const resolvedSurfaceMetadata = surfaceMetadata ?? controllerSnapshot?.surface ?? null;
1789
1838
  const editing = controllerSnapshot?.editing ?? editingForSurface(resolvedSurfaceMetadata);
1790
1839
  const surface = surfaceId === void 0 ? adapter.currentSurface : adapter.getSurface(surfaceId);
1791
1840
  const structure = surface === void 0 ? EMPTY_STRUCTURE : adapter.getSurfaceStructure(surface.id);
1792
1841
  const focusKey = surface === void 0 ? void 0 : `${surface.id}:${resolvedSurfaceMetadata?.activeStateId ?? "surface"}`;
1793
1842
  const frameId = hostId ?? `a2ui-surface-${generatedId}`;
1843
+ useEffect(
1844
+ () => adapter.subscribeFieldUpdates((event) => {
1845
+ const pending = pendingFieldUpdates.current.get(event.surfaceId) ?? /* @__PURE__ */ new Set();
1846
+ for (const field of event.fields) {
1847
+ pending.add(field);
1848
+ }
1849
+ pendingFieldUpdates.current.set(event.surfaceId, pending);
1850
+ }),
1851
+ [adapter]
1852
+ );
1794
1853
  useLayoutEffect2(() => {
1795
1854
  const root = rootRef.current;
1796
1855
  if (root === null || surface === void 0) {
@@ -1833,6 +1892,42 @@ function A2UISurfaceHost({
1833
1892
  ),
1834
1893
  setInteractionDisabled(root, disabled || busy)
1835
1894
  ];
1895
+ reportAgentUpdates();
1896
+ };
1897
+ const reportAgentUpdates = () => {
1898
+ const changedFields = [
1899
+ ...pendingFieldUpdates.current.get(surface.id) ?? []
1900
+ ];
1901
+ pendingFieldUpdates.current.clear();
1902
+ const currentGroup = resolvedSurfaceMetadata?.fieldInteractions.find(
1903
+ (group) => group.mode === "current"
1904
+ );
1905
+ const previousGroupId = lastCurrentGroupId.current;
1906
+ if (currentGroup !== void 0) {
1907
+ lastCurrentGroupId.current = currentGroup.groupId;
1908
+ }
1909
+ const report = onAgentUpdateRef.current;
1910
+ if (report === void 0) {
1911
+ return;
1912
+ }
1913
+ if (changedFields.length > 0) {
1914
+ report({
1915
+ reason: "field-value",
1916
+ surfaceId: surface.id,
1917
+ fields: changedFields,
1918
+ element: agentUpdateElement(root, changedFields, structure.fields)
1919
+ });
1920
+ }
1921
+ if (currentGroup !== void 0 && previousGroupId !== void 0 && currentGroup.groupId !== previousGroupId) {
1922
+ const fields = currentGroup.fields.map((binding) => binding.field);
1923
+ report({
1924
+ reason: "current-group",
1925
+ surfaceId: surface.id,
1926
+ fields,
1927
+ groupId: currentGroup.groupId,
1928
+ element: agentUpdateElement(root, fields, structure.fields)
1929
+ });
1930
+ }
1836
1931
  };
1837
1932
  const scheduleControlRefresh = () => {
1838
1933
  queueMicrotask(refreshControls);
@@ -1957,6 +2052,33 @@ function focusProgressTarget(root, metadata, invalidFields) {
1957
2052
  });
1958
2053
  }
1959
2054
  }
2055
+ function agentUpdateElement(root, fields, descriptors) {
2056
+ const controls = [
2057
+ ...root.querySelectorAll("[data-a2ui-field]")
2058
+ ];
2059
+ for (const field of fields) {
2060
+ const control = controls.find(
2061
+ (candidate) => candidate.dataset.a2uiField === field
2062
+ );
2063
+ if (control !== void 0) {
2064
+ return control.closest("[data-a2ui-date-editor]") ?? control.closest('[role="radiogroup"]') ?? control;
2065
+ }
2066
+ const descriptor = descriptors.find(
2067
+ (candidate) => candidate.field === field
2068
+ );
2069
+ const labels = /* @__PURE__ */ new Set([displayFieldLabel(field)]);
2070
+ if (descriptor !== void 0) {
2071
+ labels.add(descriptor.label);
2072
+ }
2073
+ const label = [
2074
+ ...root.querySelectorAll("label, strong, legend")
2075
+ ].find((candidate) => labels.has(candidate.textContent?.trim() ?? ""));
2076
+ if (label?.parentElement != null && root.contains(label.parentElement)) {
2077
+ return label.parentElement;
2078
+ }
2079
+ }
2080
+ return void 0;
2081
+ }
1960
2082
  function isControlDisabled(control) {
1961
2083
  return control.getAttribute("aria-disabled") === "true" || "disabled" in control && control.disabled === true;
1962
2084
  }