@ukiahinsure/a2ui-react-adapter 0.1.3 → 0.1.5

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,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;
@@ -72,6 +73,10 @@ var A2UIClientAdapter = class {
72
73
  const values = {};
73
74
  for (const field of fields) {
74
75
  const value = surface.dataModel.get(fieldPointer(field));
76
+ if (Array.isArray(value) && value.length === 1 && isFieldValue(value[0])) {
77
+ values[field] = value[0];
78
+ continue;
79
+ }
75
80
  if (!isFieldValue(value)) {
76
81
  return null;
77
82
  }
@@ -88,8 +93,9 @@ var A2UIClientAdapter = class {
88
93
  let changed = false;
89
94
  for (const [field, value] of Object.entries(values)) {
90
95
  const path = fieldPointer(field);
91
- if (!Object.is(surface.dataModel.get(path), value)) {
92
- surface.dataModel.set(path, value);
96
+ const next = this.#dataModelValueForField(surfaceId, field, value);
97
+ if (!fieldValueEquivalent(surface.dataModel.get(path), next)) {
98
+ surface.dataModel.set(path, next);
93
99
  changed = true;
94
100
  }
95
101
  }
@@ -98,6 +104,22 @@ var A2UIClientAdapter = class {
98
104
  }
99
105
  return true;
100
106
  }
107
+ #dataModelValueForField(surfaceId, field, value) {
108
+ const descriptor = this.#fieldDescriptor(surfaceId, field);
109
+ if (descriptor?.control === "choice" && typeof value === "string" && value.length > 0) {
110
+ return [value];
111
+ }
112
+ return value;
113
+ }
114
+ #fieldDescriptor(surfaceId, field) {
115
+ const structure = this.#structureBySurface.get(surfaceId);
116
+ if (structure === void 0) {
117
+ return void 0;
118
+ }
119
+ return [...structure.fieldsByComponent.values()].find(
120
+ (descriptor) => descriptor.field === field
121
+ );
122
+ }
101
123
  /**
102
124
  * Apply one validated server envelope. Returns false for duplicates and
103
125
  * out-of-order deliveries (the per-session `seq` guard) and for envelopes
@@ -134,6 +156,7 @@ var A2UIClientAdapter = class {
134
156
  this.#processor = replacement;
135
157
  this.#surfaceOrder = [];
136
158
  this.#structureBySurface.clear();
159
+ this.#persistedListRowsByPath.clear();
137
160
  previous.model.dispose();
138
161
  this.#notify();
139
162
  }
@@ -187,12 +210,28 @@ var A2UIClientAdapter = class {
187
210
  );
188
211
  return false;
189
212
  }
213
+ const rawMessages = envelope.kind === "snapshot" ? validated.data : mergePersistedListRowsIntoMessages(
214
+ validated.data,
215
+ envelope.surfaceId,
216
+ this.#persistedListRowsByPath
217
+ );
218
+ const nextStructure = updateSurfaceStructures(
219
+ rawMessages,
220
+ envelope.kind === "snapshot" ? /* @__PURE__ */ new Map() : cloneSurfaceStructures(this.#structureBySurface)
221
+ );
222
+ const messages = normalizeChoiceValuesInMessages(
223
+ rawMessages,
224
+ envelope.surfaceId,
225
+ nextStructure.get(envelope.surfaceId)
226
+ );
190
227
  const candidate = this.#createProcessor();
191
228
  try {
192
229
  if (envelope.kind !== "snapshot") {
193
230
  candidate.processMessages(this.#snapshotMessages());
194
231
  }
195
- candidate.processMessages(validated.data);
232
+ candidate.processMessages(
233
+ messages
234
+ );
196
235
  } catch (error) {
197
236
  candidate.model.dispose();
198
237
  this.reportClientError(
@@ -203,10 +242,6 @@ var A2UIClientAdapter = class {
203
242
  return false;
204
243
  }
205
244
  const previous = this.#processor;
206
- const nextStructure = updateSurfaceStructures(
207
- validated.data,
208
- envelope.kind === "snapshot" ? /* @__PURE__ */ new Map() : cloneSurfaceStructures(this.#structureBySurface)
209
- );
210
245
  pruneSurfaceStructures(nextStructure, candidate);
211
246
  if (!fieldInteractionBindingsTrusted(
212
247
  envelope.fieldInteractions ?? [],
@@ -223,6 +258,13 @@ var A2UIClientAdapter = class {
223
258
  this.#processor = candidate;
224
259
  this.#surfaceOrder = [...candidate.model.surfacesMap.keys()];
225
260
  this.#structureBySurface = nextStructure;
261
+ if (envelope.kind === "snapshot") {
262
+ this.#persistedListRowsByPath.clear();
263
+ }
264
+ rememberPersistedListRows(
265
+ this.#persistedListRowsByPath,
266
+ candidate.model.getSurface(envelope.surfaceId)?.dataModel.get("/")
267
+ );
226
268
  previous.model.dispose();
227
269
  this.#notify();
228
270
  return true;
@@ -403,24 +445,95 @@ function fieldDescriptor(component) {
403
445
  ChoicePicker: "choice"
404
446
  };
405
447
  const control = typeof component.component === "string" ? controls[component.component] : void 0;
448
+ const componentId = isNonEmptyString(component.id) ? component.id : void 0;
406
449
  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)) {
450
+ const field = isRecord(value) && typeof value.path === "string" ? fieldFromPointer(value.path) ?? listFieldFromComponentId(componentId) : listFieldFromComponentId(componentId);
451
+ if (control === void 0 || field === void 0 || componentId === void 0) {
409
452
  return void 0;
410
453
  }
411
454
  return {
412
- componentId: component.id,
455
+ componentId,
413
456
  field,
414
457
  label: typeof component.label === "string" && component.label.length > 0 ? component.label : field,
415
458
  control,
416
- required: hasRequiredCheck(component.checks)
459
+ required: hasRequiredCheck(component.checks),
460
+ ...typeof component.validationRegexp === "string" ? { validationRegexp: component.validationRegexp } : {},
461
+ ...typeof component.max === "string" ? { max: component.max } : {}
417
462
  };
418
463
  }
464
+ function normalizeChoiceValuesInMessages(messages, surfaceId, structure) {
465
+ if (structure === void 0) {
466
+ return [...messages];
467
+ }
468
+ const choiceFields = new Set(
469
+ [...structure.fieldsByComponent.values()].filter((field) => field.control === "choice").map((field) => field.field)
470
+ );
471
+ if (choiceFields.size === 0) {
472
+ return [...messages];
473
+ }
474
+ return messages.map((message) => {
475
+ if (!isRecord(message) || !isRecord(message.updateDataModel)) {
476
+ return message;
477
+ }
478
+ const update = message.updateDataModel;
479
+ if (update.surfaceId !== surfaceId) {
480
+ return message;
481
+ }
482
+ const path = typeof update.path === "string" ? update.path : "/";
483
+ if (path === "/" && isRecord(update.value)) {
484
+ const value = cloneJsonRecord(update.value);
485
+ for (const field2 of choiceFields) {
486
+ if (Object.hasOwn(value, field2)) {
487
+ value[field2] = choiceDataModelValue(value[field2]);
488
+ }
489
+ }
490
+ return {
491
+ ...message,
492
+ updateDataModel: { ...update, value }
493
+ };
494
+ }
495
+ const field = fieldFromPointer(path);
496
+ if (field !== void 0 && choiceFields.has(field)) {
497
+ return {
498
+ ...message,
499
+ updateDataModel: {
500
+ ...update,
501
+ value: choiceDataModelValue(update.value)
502
+ }
503
+ };
504
+ }
505
+ return message;
506
+ });
507
+ }
508
+ function choiceDataModelValue(value) {
509
+ if (Array.isArray(value)) {
510
+ return value;
511
+ }
512
+ if (typeof value === "string") {
513
+ return value.length === 0 ? [] : [value];
514
+ }
515
+ return value;
516
+ }
419
517
  function hasRequiredCheck(value) {
420
518
  return Array.isArray(value) && value.some(
421
519
  (check) => isRecord(check) && isRecord(check.condition) && check.condition.call === "required"
422
520
  );
423
521
  }
522
+ function listFieldFromComponentId(componentId) {
523
+ if (componentId === void 0) {
524
+ return void 0;
525
+ }
526
+ const markers = [":list-new:field:", ":list-row:field:"];
527
+ for (const marker of markers) {
528
+ const markerIndex = componentId.indexOf(marker);
529
+ if (markerIndex < 0) {
530
+ continue;
531
+ }
532
+ const field = componentId.slice(markerIndex + marker.length);
533
+ return field.length > 0 ? field : void 0;
534
+ }
535
+ return void 0;
536
+ }
424
537
  function fieldFromPointer(path) {
425
538
  if (!path.startsWith("/") || path.length < 2 || path.slice(1).includes("/")) {
426
539
  return void 0;
@@ -430,9 +543,150 @@ function fieldFromPointer(path) {
430
543
  function fieldPointer(field) {
431
544
  return `/${field.replaceAll("~", "~0").replaceAll("/", "~1")}`;
432
545
  }
546
+ function mergePersistedListRowsIntoMessages(messages, surfaceId, persistedRowsByPath) {
547
+ if (persistedRowsByPath.size === 0) {
548
+ return [...messages];
549
+ }
550
+ let mergedRoot = false;
551
+ const mergedMessages = messages.map((message) => {
552
+ if (!isRootDataModelUpdate(message, surfaceId)) {
553
+ return message;
554
+ }
555
+ mergedRoot = true;
556
+ return {
557
+ ...message,
558
+ updateDataModel: {
559
+ ...message.updateDataModel,
560
+ value: mergePersistedListRowsIntoRoot(
561
+ message.updateDataModel.value,
562
+ persistedRowsByPath
563
+ )
564
+ }
565
+ };
566
+ });
567
+ if (mergedRoot) {
568
+ return mergedMessages;
569
+ }
570
+ return [
571
+ ...mergedMessages,
572
+ {
573
+ version: "v0.9",
574
+ updateDataModel: {
575
+ surfaceId,
576
+ path: "/",
577
+ value: mergePersistedListRowsIntoRoot({}, persistedRowsByPath)
578
+ }
579
+ }
580
+ ];
581
+ }
582
+ function mergePersistedListRowsIntoRoot(root, persistedRowsByPath) {
583
+ const merged = cloneJsonRecord(root);
584
+ const activeDraftListPaths = activeListDraftPaths(merged);
585
+ for (const [listPath, rows] of persistedRowsByPath) {
586
+ const dottedRows = merged[listPath];
587
+ const nestedRows = getNestedValue(merged, listPath);
588
+ const incomingRows = Array.isArray(dottedRows) ? dottedRows : Array.isArray(nestedRows) ? nestedRows : void 0;
589
+ const shouldPreserveCachedRows = rows.length > 0 && (incomingRows === void 0 || incomingRows.length === 0) && !activeDraftListPaths.has(listPath);
590
+ const effectiveRows = shouldPreserveCachedRows ? rows : incomingRows ?? rows;
591
+ if (!Array.isArray(dottedRows) || shouldPreserveCachedRows) {
592
+ merged[listPath] = cloneJsonArray(effectiveRows);
593
+ }
594
+ if (!Array.isArray(nestedRows) || shouldPreserveCachedRows) {
595
+ setNestedValue(merged, listPath, cloneJsonArray(effectiveRows));
596
+ }
597
+ }
598
+ return merged;
599
+ }
600
+ function activeListDraftPaths(root) {
601
+ const drafts = root.__listDrafts;
602
+ if (!isRecord(drafts)) {
603
+ return /* @__PURE__ */ new Set();
604
+ }
605
+ return new Set(
606
+ Object.keys(drafts).filter((listPath) => listPath.trim().length > 0)
607
+ );
608
+ }
609
+ function rememberPersistedListRows(persistedRowsByPath, root) {
610
+ if (!isRecord(root)) {
611
+ return;
612
+ }
613
+ for (const [listPath, rows] of listRowsFromRoot(root)) {
614
+ persistedRowsByPath.set(listPath, cloneJsonArray(rows));
615
+ }
616
+ }
617
+ function listRowsFromRoot(root) {
618
+ const rowsByPath = /* @__PURE__ */ new Map();
619
+ for (const [key, value] of Object.entries(root)) {
620
+ if (key.includes(".") && Array.isArray(value)) {
621
+ rowsByPath.set(key, cloneJsonArray(value));
622
+ }
623
+ }
624
+ collectNestedListRows(root, [], rowsByPath);
625
+ return rowsByPath;
626
+ }
627
+ function collectNestedListRows(value, path, rowsByPath) {
628
+ if (Array.isArray(value)) {
629
+ if (path.length > 1) {
630
+ rowsByPath.set(path.join("."), cloneJsonArray(value));
631
+ }
632
+ return;
633
+ }
634
+ if (!isRecord(value)) {
635
+ return;
636
+ }
637
+ for (const [key, child] of Object.entries(value)) {
638
+ if (key === "__listDrafts") {
639
+ continue;
640
+ }
641
+ collectNestedListRows(child, [...path, key], rowsByPath);
642
+ }
643
+ }
644
+ function isRootDataModelUpdate(message, surfaceId) {
645
+ return isRecord(message) && isRecord(message.updateDataModel) && message.updateDataModel.surfaceId === surfaceId && (message.updateDataModel.path === void 0 || message.updateDataModel.path === "/") && isRecord(message.updateDataModel.value);
646
+ }
647
+ function getNestedValue(root, listPath) {
648
+ let current = root;
649
+ for (const segment of listPath.split(".")) {
650
+ if (!isRecord(current)) {
651
+ return void 0;
652
+ }
653
+ current = current[segment];
654
+ }
655
+ return current;
656
+ }
657
+ function setNestedValue(root, listPath, value) {
658
+ const segments = listPath.split(".");
659
+ let current = root;
660
+ for (const segment of segments.slice(0, -1)) {
661
+ const next = current[segment];
662
+ if (isRecord(next)) {
663
+ current = next;
664
+ continue;
665
+ }
666
+ const created = {};
667
+ current[segment] = created;
668
+ current = created;
669
+ }
670
+ const leaf = segments.at(-1);
671
+ if (leaf !== void 0) {
672
+ current[leaf] = value;
673
+ }
674
+ }
675
+ function cloneJsonRecord(value) {
676
+ return JSON.parse(JSON.stringify(value));
677
+ }
678
+ function cloneJsonArray(value) {
679
+ return JSON.parse(JSON.stringify(value));
680
+ }
433
681
  function isFieldValue(value) {
434
682
  return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
435
683
  }
684
+ function fieldValueEquivalent(left, right) {
685
+ if (Array.isArray(left) && Array.isArray(right)) {
686
+ return left.length === right.length && left.every((value, index) => Object.is(value, right[index]));
687
+ }
688
+ return Object.is(left, right);
689
+ }
436
690
  function tabDescriptors(component) {
437
691
  if (component.component !== "Tabs" || !isNonEmptyString(component.id) || !Array.isArray(component.tabs)) {
438
692
  return [];
@@ -542,6 +796,7 @@ function A2UISurfaceHost({
542
796
  resolvedSurfaceMetadata?.fieldInteractions ?? []
543
797
  ),
544
798
  annotateActionControls(root),
799
+ blockInvalidActionControls(root),
545
800
  setPendingGroupsDisabled(
546
801
  root,
547
802
  new Set(editing.pendingGroupIds)
@@ -910,7 +1165,11 @@ function annotateFields(root, fields, invalidFields, validationSummaryId) {
910
1165
  field: control.dataset.a2uiField,
911
1166
  required: control.getAttribute("aria-required"),
912
1167
  invalid: control.getAttribute("aria-invalid"),
913
- describedBy: control.getAttribute("aria-describedby")
1168
+ describedBy: control.getAttribute("aria-describedby"),
1169
+ pattern: control.getAttribute("pattern"),
1170
+ max: control.getAttribute("max"),
1171
+ inputMode: control.getAttribute("inputmode"),
1172
+ maxLength: control.getAttribute("maxlength")
914
1173
  });
915
1174
  }
916
1175
  control.dataset.a2uiField = field.field;
@@ -930,6 +1189,11 @@ function annotateFields(root, fields, invalidFields, validationSummaryId) {
930
1189
  );
931
1190
  }
932
1191
  }
1192
+ applyControlValidationAttributes(control, field);
1193
+ const cleanup = attachControlValidationBehavior(control, field);
1194
+ if (cleanup !== void 0) {
1195
+ matchCleanups.push(cleanup);
1196
+ }
933
1197
  }
934
1198
  }
935
1199
  return () => {
@@ -942,12 +1206,108 @@ function annotateFields(root, fields, invalidFields, validationSummaryId) {
942
1206
  restoreAttribute(control, "aria-required", original.required);
943
1207
  restoreAttribute(control, "aria-invalid", original.invalid);
944
1208
  restoreAttribute(control, "aria-describedby", original.describedBy);
1209
+ restoreAttribute(control, "pattern", original.pattern);
1210
+ restoreAttribute(control, "max", original.max);
1211
+ restoreAttribute(control, "inputmode", original.inputMode);
1212
+ restoreAttribute(control, "maxlength", original.maxLength);
945
1213
  }
946
1214
  for (const cleanup of matchCleanups.reverse()) {
947
1215
  cleanup();
948
1216
  }
949
1217
  };
950
1218
  }
1219
+ function applyControlValidationAttributes(control, field) {
1220
+ if (field.validationRegexp !== void 0) {
1221
+ control.setAttribute("pattern", field.validationRegexp);
1222
+ }
1223
+ if (field.max !== void 0) {
1224
+ control.setAttribute("max", field.max);
1225
+ }
1226
+ if (isPhoneValidationRegexp(field.validationRegexp)) {
1227
+ control.setAttribute("inputmode", "numeric");
1228
+ control.setAttribute("maxlength", "10");
1229
+ } else if (isZipValidationRegexp(field.validationRegexp)) {
1230
+ control.setAttribute("inputmode", "numeric");
1231
+ control.setAttribute("maxlength", "5");
1232
+ } else if (isIntegerValidationRegexp(field.validationRegexp)) {
1233
+ control.setAttribute("inputmode", "numeric");
1234
+ control.setAttribute("maxlength", "2");
1235
+ }
1236
+ }
1237
+ function attachControlValidationBehavior(control, field) {
1238
+ const maxLength = numericMaxLengthForField(field);
1239
+ if (maxLength === void 0 || !isTextInput(control)) {
1240
+ return void 0;
1241
+ }
1242
+ const sanitize = () => {
1243
+ const next = control.value.replace(/\D/g, "").slice(0, maxLength);
1244
+ if (control.value !== next) {
1245
+ control.value = next;
1246
+ control.dispatchEvent(new Event("input", { bubbles: true }));
1247
+ }
1248
+ };
1249
+ control.addEventListener("input", sanitize);
1250
+ control.addEventListener("change", sanitize);
1251
+ sanitize();
1252
+ return () => {
1253
+ control.removeEventListener("input", sanitize);
1254
+ control.removeEventListener("change", sanitize);
1255
+ };
1256
+ }
1257
+ function numericMaxLengthForField(field) {
1258
+ if (isPhoneValidationRegexp(field.validationRegexp)) {
1259
+ return 10;
1260
+ }
1261
+ if (isZipValidationRegexp(field.validationRegexp)) {
1262
+ return 5;
1263
+ }
1264
+ if (isIntegerValidationRegexp(field.validationRegexp)) {
1265
+ return 2;
1266
+ }
1267
+ return void 0;
1268
+ }
1269
+ function isPhoneValidationRegexp(pattern) {
1270
+ return pattern === "^[2-9]\\d{2}[2-9]\\d{6}$" || pattern?.startsWith("^(?:") === true && pattern.endsWith(")[2-9]\\d{6}$");
1271
+ }
1272
+ function isZipValidationRegexp(pattern) {
1273
+ return pattern === "^(?!00000$)\\d{5}$" || pattern === "^(?:00(?:50[1-9]|5[1-9][0-9]|[6-9][0-9]{2})|0[1-9][0-9]{3}|[1-8][0-9]{4}|9[0-8][0-9]{3}|99[0-8][0-9]{2}|999[0-4][0-9]|99950)$";
1274
+ }
1275
+ function isIntegerValidationRegexp(pattern) {
1276
+ return pattern === "^\\d{1,2}$";
1277
+ }
1278
+ function isTextInput(element) {
1279
+ return element instanceof HTMLInputElement && element.type !== "radio";
1280
+ }
1281
+ function blockInvalidActionControls(root) {
1282
+ const blockIfInvalid = (event) => {
1283
+ const target = event.target;
1284
+ if (!(target instanceof HTMLElement)) {
1285
+ return;
1286
+ }
1287
+ const action = target.closest("[data-a2ui-action='true']");
1288
+ if (action === null || !root.contains(action)) {
1289
+ return;
1290
+ }
1291
+ const invalid2 = firstInvalidInput(root);
1292
+ if (invalid2 === void 0) {
1293
+ return;
1294
+ }
1295
+ event.preventDefault();
1296
+ event.stopImmediatePropagation();
1297
+ invalid2.setAttribute("aria-invalid", "true");
1298
+ invalid2.reportValidity();
1299
+ invalid2.focus();
1300
+ };
1301
+ root.addEventListener("click", blockIfInvalid, true);
1302
+ root.addEventListener("keydown", blockIfInvalid, true);
1303
+ return () => {
1304
+ root.removeEventListener("click", blockIfInvalid, true);
1305
+ root.removeEventListener("keydown", blockIfInvalid, true);
1306
+ };
1307
+ }
1308
+ function firstInvalidInput(root) {
1309
+ return [...root.querySelectorAll("input[data-a2ui-field]")].filter((input) => input.type !== "radio" && !isControlDisabled(input)).find((input) => !input.checkValidity());
1310
+ }
951
1311
  function findFieldControls(root, field) {
952
1312
  const labels = [...root.querySelectorAll("label")];
953
1313
  const label = labels.find(
@@ -1622,8 +1982,26 @@ function decodeRendererAction(event) {
1622
1982
  return entries.length === 0 ? { action } : null;
1623
1983
  }
1624
1984
  if (action === "flow.submit") {
1985
+ const listPath = event.context.listPath;
1986
+ if (typeof listPath === "string" && listPath.length > 0) {
1987
+ const fieldEntries2 = entries.filter(([key]) => key !== "listPath");
1988
+ const fields3 = scalarFields(fieldEntries2);
1989
+ if (fields3 === null) {
1990
+ return null;
1991
+ }
1992
+ return {
1993
+ action,
1994
+ payload: {
1995
+ listPath,
1996
+ ...Object.keys(fields3).length > 0 ? { fields: fields3 } : {}
1997
+ }
1998
+ };
1999
+ }
1625
2000
  const fields2 = scalarFields(entries);
1626
- return fields2 === null || Object.keys(fields2).length === 0 ? null : { action, payload: { fields: fields2 } };
2001
+ if (fields2 === null) {
2002
+ return null;
2003
+ }
2004
+ return Object.keys(fields2).length === 0 ? { action, payload: {} } : { action, payload: { fields: fields2 } };
1627
2005
  }
1628
2006
  if (action === "flow.list.add" || action === "flow.list.update" || action === "flow.list.delete") {
1629
2007
  const listPath = event.context.listPath;