@ukiahinsure/a2ui-react-adapter 0.1.2 → 0.1.4

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.
@@ -12,52 +12,52 @@
12
12
  margin: var(--_a2ui-text-margin, 0);
13
13
  box-sizing: border-box;
14
14
  }
15
- .a2uiText :global(p),
16
- .a2uiText :global(h1),
17
- .a2uiText :global(h2),
18
- .a2uiText :global(h3),
19
- .a2uiText :global(h4),
20
- .a2uiText :global(h5),
21
- .a2uiText :global(h6),
22
- .a2uiText :global(ol),
23
- .a2uiText :global(ul),
24
- .a2uiText :global(li),
25
- .a2uiText :global(blockquote),
26
- .a2uiText :global(pre) {
15
+ .a2uiText p,
16
+ .a2uiText h1,
17
+ .a2uiText h2,
18
+ .a2uiText h3,
19
+ .a2uiText h4,
20
+ .a2uiText h5,
21
+ .a2uiText h6,
22
+ .a2uiText ol,
23
+ .a2uiText ul,
24
+ .a2uiText li,
25
+ .a2uiText blockquote,
26
+ .a2uiText pre {
27
27
  margin: var(--_a2ui-text-margin, 0);
28
28
  }
29
- .a2uiText :global(h1),
30
- .a2uiText :global(h2),
31
- .a2uiText :global(h3),
32
- .a2uiText :global(h4),
33
- .a2uiText :global(h5) {
29
+ .a2uiText h1,
30
+ .a2uiText h2,
31
+ .a2uiText h3,
32
+ .a2uiText h4,
33
+ .a2uiText h5 {
34
34
  font-family: var(--a2ui-font-family-title, inherit);
35
35
  line-height: var(--a2ui-line-height-headings, 1.2);
36
36
  }
37
- .a2uiText :global(h1) {
37
+ .a2uiText h1 {
38
38
  font-size: var(--a2ui-font-size-2xl);
39
39
  }
40
- .a2uiText :global(h2) {
40
+ .a2uiText h2 {
41
41
  font-size: var(--a2ui-font-size-xl);
42
42
  }
43
- .a2uiText :global(h3) {
43
+ .a2uiText h3 {
44
44
  font-size: var(--a2ui-font-size-l);
45
45
  }
46
- .a2uiText :global(p),
47
- .a2uiText :global(h4) {
46
+ .a2uiText p,
47
+ .a2uiText h4 {
48
48
  font-size: var(--a2ui-font-size-m);
49
49
  }
50
- .a2uiText :global(h5) {
50
+ .a2uiText h5 {
51
51
  font-size: var(--a2ui-font-size-s);
52
52
  }
53
- .a2uiText :global(p),
54
- .a2uiText :global(ol),
55
- .a2uiText :global(ul),
56
- .a2uiText :global(li),
57
- .a2uiText :global(blockquote) {
53
+ .a2uiText p,
54
+ .a2uiText ol,
55
+ .a2uiText ul,
56
+ .a2uiText li,
57
+ .a2uiText blockquote {
58
58
  line-height: var(--a2ui-line-height-body, 1.5);
59
59
  }
60
- .a2uiText :global(a) {
60
+ .a2uiText a {
61
61
  color: var(--a2ui-text-a-color, inherit);
62
62
  font-weight: var(--a2ui-text-a-font-weight, inherit);
63
63
  }
@@ -202,4 +202,3 @@
202
202
  .optionText {
203
203
  font-size: var(--a2ui-font-size-m, 1rem);
204
204
  }
205
- /*# sourceMappingURL=index.css.map */
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as A2UIClientAdapter, a as A2UISurfaceMetadata, b as A2UISessionController, c as A2UIRoomLike } from './session-B_2-bK2s.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-B_2-bK2s.js';
1
+ import { A as A2UIClientAdapter, a as A2UISurfaceMetadata, b as A2UISessionController, c as A2UIRoomLike } from './session-Ckvifx8K.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-Ckvifx8K.js';
3
3
  import { ReactNode } from 'react';
4
4
 
5
5
  /** Accessible host around the official A2UI v0.9.1 React surface. */
package/dist/index.js CHANGED
@@ -28,6 +28,7 @@ var A2UIClientAdapter = class {
28
28
  #errorListeners = /* @__PURE__ */ new Set();
29
29
  #surfaceOrder = [];
30
30
  #structureBySurface = /* @__PURE__ */ new Map();
31
+ #persistedListRowsByPath = /* @__PURE__ */ new Map();
31
32
  #version = 0;
32
33
  #lastSeq = 0;
33
34
  #disposed = false;
@@ -134,6 +135,7 @@ var A2UIClientAdapter = class {
134
135
  this.#processor = replacement;
135
136
  this.#surfaceOrder = [];
136
137
  this.#structureBySurface.clear();
138
+ this.#persistedListRowsByPath.clear();
137
139
  previous.model.dispose();
138
140
  this.#notify();
139
141
  }
@@ -187,12 +189,19 @@ var A2UIClientAdapter = class {
187
189
  );
188
190
  return false;
189
191
  }
192
+ const messages = envelope.kind === "snapshot" ? validated.data : mergePersistedListRowsIntoMessages(
193
+ validated.data,
194
+ envelope.surfaceId,
195
+ this.#persistedListRowsByPath
196
+ );
190
197
  const candidate = this.#createProcessor();
191
198
  try {
192
199
  if (envelope.kind !== "snapshot") {
193
200
  candidate.processMessages(this.#snapshotMessages());
194
201
  }
195
- candidate.processMessages(validated.data);
202
+ candidate.processMessages(
203
+ messages
204
+ );
196
205
  } catch (error) {
197
206
  candidate.model.dispose();
198
207
  this.reportClientError(
@@ -204,7 +213,7 @@ var A2UIClientAdapter = class {
204
213
  }
205
214
  const previous = this.#processor;
206
215
  const nextStructure = updateSurfaceStructures(
207
- validated.data,
216
+ messages,
208
217
  envelope.kind === "snapshot" ? /* @__PURE__ */ new Map() : cloneSurfaceStructures(this.#structureBySurface)
209
218
  );
210
219
  pruneSurfaceStructures(nextStructure, candidate);
@@ -223,6 +232,13 @@ var A2UIClientAdapter = class {
223
232
  this.#processor = candidate;
224
233
  this.#surfaceOrder = [...candidate.model.surfacesMap.keys()];
225
234
  this.#structureBySurface = nextStructure;
235
+ if (envelope.kind === "snapshot") {
236
+ this.#persistedListRowsByPath.clear();
237
+ }
238
+ rememberPersistedListRows(
239
+ this.#persistedListRowsByPath,
240
+ candidate.model.getSurface(envelope.surfaceId)?.dataModel.get("/")
241
+ );
226
242
  previous.model.dispose();
227
243
  this.#notify();
228
244
  return true;
@@ -403,17 +419,20 @@ function fieldDescriptor(component) {
403
419
  ChoicePicker: "choice"
404
420
  };
405
421
  const control = typeof component.component === "string" ? controls[component.component] : void 0;
422
+ const componentId = isNonEmptyString(component.id) ? component.id : void 0;
406
423
  const value = component.value;
407
- const field = isRecord(value) && typeof value.path === "string" ? fieldFromPointer(value.path) : void 0;
408
- if (control === void 0 || field === void 0 || !isNonEmptyString(component.id)) {
424
+ const field = isRecord(value) && typeof value.path === "string" ? fieldFromPointer(value.path) ?? listFieldFromComponentId(componentId) : listFieldFromComponentId(componentId);
425
+ if (control === void 0 || field === void 0 || componentId === void 0) {
409
426
  return void 0;
410
427
  }
411
428
  return {
412
- componentId: component.id,
429
+ componentId,
413
430
  field,
414
431
  label: typeof component.label === "string" && component.label.length > 0 ? component.label : field,
415
432
  control,
416
- required: hasRequiredCheck(component.checks)
433
+ required: hasRequiredCheck(component.checks),
434
+ ...typeof component.validationRegexp === "string" ? { validationRegexp: component.validationRegexp } : {},
435
+ ...typeof component.max === "string" ? { max: component.max } : {}
417
436
  };
418
437
  }
419
438
  function hasRequiredCheck(value) {
@@ -421,6 +440,21 @@ function hasRequiredCheck(value) {
421
440
  (check) => isRecord(check) && isRecord(check.condition) && check.condition.call === "required"
422
441
  );
423
442
  }
443
+ function listFieldFromComponentId(componentId) {
444
+ if (componentId === void 0) {
445
+ return void 0;
446
+ }
447
+ const markers = [":list-new:field:", ":list-row:field:"];
448
+ for (const marker of markers) {
449
+ const markerIndex = componentId.indexOf(marker);
450
+ if (markerIndex < 0) {
451
+ continue;
452
+ }
453
+ const field = componentId.slice(markerIndex + marker.length);
454
+ return field.length > 0 ? field : void 0;
455
+ }
456
+ return void 0;
457
+ }
424
458
  function fieldFromPointer(path) {
425
459
  if (!path.startsWith("/") || path.length < 2 || path.slice(1).includes("/")) {
426
460
  return void 0;
@@ -430,6 +464,141 @@ function fieldFromPointer(path) {
430
464
  function fieldPointer(field) {
431
465
  return `/${field.replaceAll("~", "~0").replaceAll("/", "~1")}`;
432
466
  }
467
+ function mergePersistedListRowsIntoMessages(messages, surfaceId, persistedRowsByPath) {
468
+ if (persistedRowsByPath.size === 0) {
469
+ return [...messages];
470
+ }
471
+ let mergedRoot = false;
472
+ const mergedMessages = messages.map((message) => {
473
+ if (!isRootDataModelUpdate(message, surfaceId)) {
474
+ return message;
475
+ }
476
+ mergedRoot = true;
477
+ return {
478
+ ...message,
479
+ updateDataModel: {
480
+ ...message.updateDataModel,
481
+ value: mergePersistedListRowsIntoRoot(
482
+ message.updateDataModel.value,
483
+ persistedRowsByPath
484
+ )
485
+ }
486
+ };
487
+ });
488
+ if (mergedRoot) {
489
+ return mergedMessages;
490
+ }
491
+ return [
492
+ ...mergedMessages,
493
+ {
494
+ version: "v0.9",
495
+ updateDataModel: {
496
+ surfaceId,
497
+ path: "/",
498
+ value: mergePersistedListRowsIntoRoot({}, persistedRowsByPath)
499
+ }
500
+ }
501
+ ];
502
+ }
503
+ function mergePersistedListRowsIntoRoot(root, persistedRowsByPath) {
504
+ const merged = cloneJsonRecord(root);
505
+ const activeDraftListPaths = activeListDraftPaths(merged);
506
+ for (const [listPath, rows] of persistedRowsByPath) {
507
+ const dottedRows = merged[listPath];
508
+ const nestedRows = getNestedValue(merged, listPath);
509
+ const incomingRows = Array.isArray(dottedRows) ? dottedRows : Array.isArray(nestedRows) ? nestedRows : void 0;
510
+ const shouldPreserveCachedRows = rows.length > 0 && (incomingRows === void 0 || incomingRows.length === 0) && !activeDraftListPaths.has(listPath);
511
+ const effectiveRows = shouldPreserveCachedRows ? rows : incomingRows ?? rows;
512
+ if (!Array.isArray(dottedRows) || shouldPreserveCachedRows) {
513
+ merged[listPath] = cloneJsonArray(effectiveRows);
514
+ }
515
+ if (!Array.isArray(nestedRows) || shouldPreserveCachedRows) {
516
+ setNestedValue(merged, listPath, cloneJsonArray(effectiveRows));
517
+ }
518
+ }
519
+ return merged;
520
+ }
521
+ function activeListDraftPaths(root) {
522
+ const drafts = root.__listDrafts;
523
+ if (!isRecord(drafts)) {
524
+ return /* @__PURE__ */ new Set();
525
+ }
526
+ return new Set(
527
+ Object.keys(drafts).filter((listPath) => listPath.trim().length > 0)
528
+ );
529
+ }
530
+ function rememberPersistedListRows(persistedRowsByPath, root) {
531
+ if (!isRecord(root)) {
532
+ return;
533
+ }
534
+ for (const [listPath, rows] of listRowsFromRoot(root)) {
535
+ persistedRowsByPath.set(listPath, cloneJsonArray(rows));
536
+ }
537
+ }
538
+ function listRowsFromRoot(root) {
539
+ const rowsByPath = /* @__PURE__ */ new Map();
540
+ for (const [key, value] of Object.entries(root)) {
541
+ if (key.includes(".") && Array.isArray(value)) {
542
+ rowsByPath.set(key, cloneJsonArray(value));
543
+ }
544
+ }
545
+ collectNestedListRows(root, [], rowsByPath);
546
+ return rowsByPath;
547
+ }
548
+ function collectNestedListRows(value, path, rowsByPath) {
549
+ if (Array.isArray(value)) {
550
+ if (path.length > 1) {
551
+ rowsByPath.set(path.join("."), cloneJsonArray(value));
552
+ }
553
+ return;
554
+ }
555
+ if (!isRecord(value)) {
556
+ return;
557
+ }
558
+ for (const [key, child] of Object.entries(value)) {
559
+ if (key === "__listDrafts") {
560
+ continue;
561
+ }
562
+ collectNestedListRows(child, [...path, key], rowsByPath);
563
+ }
564
+ }
565
+ function isRootDataModelUpdate(message, surfaceId) {
566
+ return isRecord(message) && isRecord(message.updateDataModel) && message.updateDataModel.surfaceId === surfaceId && (message.updateDataModel.path === void 0 || message.updateDataModel.path === "/") && isRecord(message.updateDataModel.value);
567
+ }
568
+ function getNestedValue(root, listPath) {
569
+ let current = root;
570
+ for (const segment of listPath.split(".")) {
571
+ if (!isRecord(current)) {
572
+ return void 0;
573
+ }
574
+ current = current[segment];
575
+ }
576
+ return current;
577
+ }
578
+ function setNestedValue(root, listPath, value) {
579
+ const segments = listPath.split(".");
580
+ let current = root;
581
+ for (const segment of segments.slice(0, -1)) {
582
+ const next = current[segment];
583
+ if (isRecord(next)) {
584
+ current = next;
585
+ continue;
586
+ }
587
+ const created = {};
588
+ current[segment] = created;
589
+ current = created;
590
+ }
591
+ const leaf = segments.at(-1);
592
+ if (leaf !== void 0) {
593
+ current[leaf] = value;
594
+ }
595
+ }
596
+ function cloneJsonRecord(value) {
597
+ return JSON.parse(JSON.stringify(value));
598
+ }
599
+ function cloneJsonArray(value) {
600
+ return JSON.parse(JSON.stringify(value));
601
+ }
433
602
  function isFieldValue(value) {
434
603
  return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
435
604
  }
@@ -910,7 +1079,11 @@ function annotateFields(root, fields, invalidFields, validationSummaryId) {
910
1079
  field: control.dataset.a2uiField,
911
1080
  required: control.getAttribute("aria-required"),
912
1081
  invalid: control.getAttribute("aria-invalid"),
913
- describedBy: control.getAttribute("aria-describedby")
1082
+ describedBy: control.getAttribute("aria-describedby"),
1083
+ pattern: control.getAttribute("pattern"),
1084
+ max: control.getAttribute("max"),
1085
+ inputMode: control.getAttribute("inputmode"),
1086
+ maxLength: control.getAttribute("maxlength")
914
1087
  });
915
1088
  }
916
1089
  control.dataset.a2uiField = field.field;
@@ -930,6 +1103,7 @@ function annotateFields(root, fields, invalidFields, validationSummaryId) {
930
1103
  );
931
1104
  }
932
1105
  }
1106
+ applyControlValidationAttributes(control, field);
933
1107
  }
934
1108
  }
935
1109
  return () => {
@@ -942,12 +1116,31 @@ function annotateFields(root, fields, invalidFields, validationSummaryId) {
942
1116
  restoreAttribute(control, "aria-required", original.required);
943
1117
  restoreAttribute(control, "aria-invalid", original.invalid);
944
1118
  restoreAttribute(control, "aria-describedby", original.describedBy);
1119
+ restoreAttribute(control, "pattern", original.pattern);
1120
+ restoreAttribute(control, "max", original.max);
1121
+ restoreAttribute(control, "inputmode", original.inputMode);
1122
+ restoreAttribute(control, "maxlength", original.maxLength);
945
1123
  }
946
1124
  for (const cleanup of matchCleanups.reverse()) {
947
1125
  cleanup();
948
1126
  }
949
1127
  };
950
1128
  }
1129
+ function applyControlValidationAttributes(control, field) {
1130
+ if (field.validationRegexp !== void 0) {
1131
+ control.setAttribute("pattern", field.validationRegexp);
1132
+ }
1133
+ if (field.max !== void 0) {
1134
+ control.setAttribute("max", field.max);
1135
+ }
1136
+ if (field.validationRegexp === "^[2-9]\\d{2}[2-9]\\d{6}$") {
1137
+ control.setAttribute("inputmode", "numeric");
1138
+ control.setAttribute("maxlength", "10");
1139
+ } else if (field.validationRegexp === "^(?!00000$)\\d{5}$") {
1140
+ control.setAttribute("inputmode", "numeric");
1141
+ control.setAttribute("maxlength", "5");
1142
+ }
1143
+ }
951
1144
  function findFieldControls(root, field) {
952
1145
  const labels = [...root.querySelectorAll("label")];
953
1146
  const label = labels.find(
@@ -1622,8 +1815,26 @@ function decodeRendererAction(event) {
1622
1815
  return entries.length === 0 ? { action } : null;
1623
1816
  }
1624
1817
  if (action === "flow.submit") {
1818
+ const listPath = event.context.listPath;
1819
+ if (typeof listPath === "string" && listPath.length > 0) {
1820
+ const fieldEntries2 = entries.filter(([key]) => key !== "listPath");
1821
+ const fields3 = scalarFields(fieldEntries2);
1822
+ if (fields3 === null) {
1823
+ return null;
1824
+ }
1825
+ return {
1826
+ action,
1827
+ payload: {
1828
+ listPath,
1829
+ ...Object.keys(fields3).length > 0 ? { fields: fields3 } : {}
1830
+ }
1831
+ };
1832
+ }
1625
1833
  const fields2 = scalarFields(entries);
1626
- return fields2 === null || Object.keys(fields2).length === 0 ? null : { action, payload: { fields: fields2 } };
1834
+ if (fields2 === null) {
1835
+ return null;
1836
+ }
1837
+ return Object.keys(fields2).length === 0 ? { action, payload: {} } : { action, payload: { fields: fields2 } };
1627
1838
  }
1628
1839
  if (action === "flow.list.add" || action === "flow.list.update" || action === "flow.list.delete") {
1629
1840
  const listPath = event.context.listPath;