@ukiahinsure/a2ui-react-adapter 0.1.6 → 0.1.8

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.js CHANGED
@@ -29,6 +29,7 @@ var A2UIClientAdapter = class {
29
29
  #surfaceOrder = [];
30
30
  #structureBySurface = /* @__PURE__ */ new Map();
31
31
  #persistedListRowsByPath = /* @__PURE__ */ new Map();
32
+ #deletedListItemIdsByPath = /* @__PURE__ */ new Map();
32
33
  #version = 0;
33
34
  #lastSeq = 0;
34
35
  #disposed = false;
@@ -157,6 +158,7 @@ var A2UIClientAdapter = class {
157
158
  this.#surfaceOrder = [];
158
159
  this.#structureBySurface.clear();
159
160
  this.#persistedListRowsByPath.clear();
161
+ this.#deletedListItemIdsByPath.clear();
160
162
  previous.model.dispose();
161
163
  this.#notify();
162
164
  }
@@ -213,14 +215,23 @@ var A2UIClientAdapter = class {
213
215
  const rawMessages = envelope.kind === "snapshot" ? validated.data : mergePersistedListRowsIntoMessages(
214
216
  validated.data,
215
217
  envelope.surfaceId,
216
- this.#persistedListRowsByPath
218
+ this.#persistedListRowsByPath,
219
+ this.#deletedListItemIdsByPath
217
220
  );
218
- const nextStructure = updateSurfaceStructures(
221
+ const listAddFreeMessages = removeListAddActionMessages(
219
222
  rawMessages,
223
+ envelope.surfaceId
224
+ );
225
+ const actionCompleteMessages = ensureOptionalCoverageSkipActionMessages(
226
+ listAddFreeMessages,
227
+ envelope.surfaceId
228
+ );
229
+ const nextStructure = updateSurfaceStructures(
230
+ actionCompleteMessages,
220
231
  envelope.kind === "snapshot" ? /* @__PURE__ */ new Map() : cloneSurfaceStructures(this.#structureBySurface)
221
232
  );
222
233
  const messages = normalizeChoiceValuesInMessages(
223
- rawMessages,
234
+ actionCompleteMessages,
224
235
  envelope.surfaceId,
225
236
  nextStructure.get(envelope.surfaceId)
226
237
  );
@@ -260,9 +271,11 @@ var A2UIClientAdapter = class {
260
271
  this.#structureBySurface = nextStructure;
261
272
  if (envelope.kind === "snapshot") {
262
273
  this.#persistedListRowsByPath.clear();
274
+ this.#deletedListItemIdsByPath.clear();
263
275
  }
264
276
  rememberPersistedListRows(
265
277
  this.#persistedListRowsByPath,
278
+ this.#deletedListItemIdsByPath,
266
279
  candidate.model.getSurface(envelope.surfaceId)?.dataModel.get("/")
267
280
  );
268
281
  previous.model.dispose();
@@ -290,11 +303,63 @@ var A2UIClientAdapter = class {
290
303
  sourceComponentId: action.sourceComponentId,
291
304
  context: { ...action.context }
292
305
  };
306
+ const submittedProviderRow = submittedProviderRowFromEvent(event);
307
+ if (submittedProviderRow !== void 0) {
308
+ this.#rememberSubmittedProviderRow(
309
+ event.surfaceId,
310
+ submittedProviderRow,
311
+ event.context
312
+ );
313
+ }
314
+ if (event.actionName === "flow.list.delete") {
315
+ this.#removeDeletedListRow(event.surfaceId, event.context);
316
+ }
293
317
  this.#options.onAction?.(event);
294
318
  for (const listener of this.#actionListeners) {
295
319
  listener(event);
296
320
  }
297
321
  }
322
+ #rememberSubmittedProviderRow(surfaceId, row, context) {
323
+ const rows = upsertListRow(
324
+ this.#persistedListRowsByPath.get("needs.providers") ?? [],
325
+ row
326
+ );
327
+ this.#persistedListRowsByPath.set("needs.providers", rows);
328
+ const surface = this.#processor.model.getSurface(surfaceId);
329
+ if (surface === void 0) {
330
+ return;
331
+ }
332
+ surface.dataModel.set("/needs.providers", cloneJsonArray(rows));
333
+ surface.dataModel.set("/needs/providers", cloneJsonArray(rows));
334
+ clearSubmittedProviderDraft(surface.dataModel, context);
335
+ removeSubmittedProviderDraftControls(
336
+ surface.componentsModel.entries,
337
+ eventSourceFieldPaths(context),
338
+ context
339
+ );
340
+ this.#notify();
341
+ }
342
+ #removeDeletedListRow(surfaceId, context) {
343
+ const listPath = context.listPath;
344
+ const itemId = context.itemId;
345
+ if (typeof listPath !== "string" || listPath.length === 0 || typeof itemId !== "string" || itemId.length === 0) {
346
+ return;
347
+ }
348
+ const cachedRows = this.#persistedListRowsByPath.get(listPath) ?? [];
349
+ const nextRows = removeListRowByItemId(cachedRows, itemId);
350
+ this.#persistedListRowsByPath.set(listPath, nextRows);
351
+ rememberDeletedListItemId(this.#deletedListItemIdsByPath, listPath, itemId);
352
+ const surface = this.#processor.model.getSurface(surfaceId);
353
+ if (surface === void 0) {
354
+ return;
355
+ }
356
+ surface.dataModel.set(`/${listPath}`, cloneJsonArray(nextRows));
357
+ surface.dataModel.set(
358
+ `/${listPath.replaceAll(".", "/")}`,
359
+ cloneJsonArray(nextRows)
360
+ );
361
+ this.#notify();
362
+ }
298
363
  #snapshotMessages() {
299
364
  const messages = [];
300
365
  for (const surface of this.#processor.model.surfacesMap.values()) {
@@ -364,6 +429,254 @@ function cloneSurfaceStructures(source) {
364
429
  ])
365
430
  );
366
431
  }
432
+ function ensureOptionalCoverageSkipActionMessages(messages, surfaceId) {
433
+ for (let index = 0; index < messages.length; index += 1) {
434
+ const message = messages[index];
435
+ if (!isRecord(message) || !isRecord(message.updateComponents)) {
436
+ continue;
437
+ }
438
+ const update = message.updateComponents;
439
+ if (update.surfaceId !== surfaceId || !Array.isArray(update.components)) {
440
+ continue;
441
+ }
442
+ const submit = update.components.find(
443
+ (component) => isRecord(component) && isNonEmptyString(component.id) && hasActionName(component, "flow.submit")
444
+ );
445
+ if (!isRecord(submit) || !isNonEmptyString(submit.id)) {
446
+ return messages;
447
+ }
448
+ const optionalCoverageField = optionalCoverageFieldName(
449
+ update.components,
450
+ submit.id
451
+ );
452
+ if (optionalCoverageField === void 0) {
453
+ continue;
454
+ }
455
+ const patchedSubmit = submitActionWithDefaultUnknown(
456
+ submit,
457
+ optionalCoverageField
458
+ );
459
+ const hasSkip = update.components.some(
460
+ (component) => hasActionName(component, "flow.skip")
461
+ );
462
+ if (hasSkip && patchedSubmit === submit) {
463
+ return messages;
464
+ }
465
+ if (hasSkip) {
466
+ const nextMessages2 = [...messages];
467
+ nextMessages2[index] = {
468
+ ...message,
469
+ updateComponents: {
470
+ ...update,
471
+ components: update.components.map(
472
+ (component) => component === submit ? patchedSubmit : component
473
+ )
474
+ }
475
+ };
476
+ return nextMessages2;
477
+ }
478
+ const parent = update.components.find(
479
+ (component) => isRecord(component) && Array.isArray(component.children) && component.children.includes(submit.id)
480
+ );
481
+ if (!isRecord(parent) || !Array.isArray(parent.children)) {
482
+ return messages;
483
+ }
484
+ const labelId = `${submit.id}:optional-skip-label`;
485
+ const buttonId = `${submit.id}:optional-skip`;
486
+ if (update.components.some(
487
+ (component) => isRecord(component) && (component.id === labelId || component.id === buttonId)
488
+ )) {
489
+ return messages;
490
+ }
491
+ const nextParent = {
492
+ ...parent,
493
+ children: parent.children.flatMap(
494
+ (child) => child === submit.id ? [buttonId, child] : [child]
495
+ )
496
+ };
497
+ const nextComponents = update.components.map(
498
+ (component) => component === parent ? nextParent : component === submit ? patchedSubmit : component
499
+ );
500
+ nextComponents.push(
501
+ {
502
+ id: labelId,
503
+ component: "Text",
504
+ text: "Skip"
505
+ },
506
+ {
507
+ id: buttonId,
508
+ component: "Button",
509
+ child: labelId,
510
+ action: {
511
+ event: {
512
+ name: "flow.submit",
513
+ context: { [optionalCoverageField]: "unknown" }
514
+ }
515
+ }
516
+ }
517
+ );
518
+ const nextMessages = [...messages];
519
+ nextMessages[index] = {
520
+ ...message,
521
+ updateComponents: {
522
+ ...update,
523
+ components: nextComponents
524
+ }
525
+ };
526
+ return nextMessages;
527
+ }
528
+ return messages;
529
+ }
530
+ function removeListAddActionMessages(messages, surfaceId) {
531
+ for (let index = 0; index < messages.length; index += 1) {
532
+ const message = messages[index];
533
+ if (!isRecord(message) || !isRecord(message.updateComponents)) {
534
+ continue;
535
+ }
536
+ const update = message.updateComponents;
537
+ if (update.surfaceId !== surfaceId || !Array.isArray(update.components)) {
538
+ continue;
539
+ }
540
+ const addIds = listAddComponentIds(update.components);
541
+ if (addIds.size === 0) {
542
+ continue;
543
+ }
544
+ const nextMessages = [...messages];
545
+ nextMessages[index] = {
546
+ ...message,
547
+ updateComponents: {
548
+ ...update,
549
+ components: update.components.filter(
550
+ (component) => !(isRecord(component) && isNonEmptyString(component.id) && addIds.has(component.id))
551
+ ).map(
552
+ (component) => removeComponentChildren(component, addIds)
553
+ )
554
+ }
555
+ };
556
+ return nextMessages;
557
+ }
558
+ return messages;
559
+ }
560
+ function listAddComponentIds(components) {
561
+ const ids = /* @__PURE__ */ new Set();
562
+ for (const component of components) {
563
+ if (!isRecord(component) || !isNonEmptyString(component.id) || !hasActionName(component, "flow.list.add")) {
564
+ continue;
565
+ }
566
+ ids.add(component.id);
567
+ const child = component.child;
568
+ if (typeof child === "string" && child.length > 0) {
569
+ ids.add(child);
570
+ }
571
+ }
572
+ return ids;
573
+ }
574
+ function removeComponentChildren(component, removedIds) {
575
+ if (!isRecord(component) || !Array.isArray(component.children)) {
576
+ return component;
577
+ }
578
+ const children = component.children.filter(
579
+ (child) => typeof child !== "string" || !removedIds.has(child)
580
+ );
581
+ return children.length === component.children.length ? component : { ...component, children };
582
+ }
583
+ function submitActionWithDefaultUnknown(submit, field) {
584
+ if (!isRecord(submit.action) || !isRecord(submit.action.event)) {
585
+ return submit;
586
+ }
587
+ const event = submit.action.event;
588
+ const context = isRecord(event.context) ? event.context : {};
589
+ if (Object.hasOwn(context, field)) {
590
+ return submit;
591
+ }
592
+ return {
593
+ ...submit,
594
+ action: {
595
+ ...submit.action,
596
+ event: {
597
+ ...event,
598
+ context: {
599
+ ...context,
600
+ [field]: "unknown"
601
+ }
602
+ }
603
+ }
604
+ };
605
+ }
606
+ function optionalCoverageFieldName(components, submitId) {
607
+ const statePrefix = componentStatePrefix(submitId);
608
+ if (statePrefix !== void 0) {
609
+ const stateField = optionalCoverageFieldFromFieldBindings(
610
+ components.filter(
611
+ (component) => isRecord(component) && isNonEmptyString(component.id) && component.id.startsWith(`${statePrefix}:`)
612
+ )
613
+ );
614
+ if (stateField !== void 0) {
615
+ return stateField;
616
+ }
617
+ }
618
+ const parent = components.find(
619
+ (component) => isRecord(component) && Array.isArray(component.children) && component.children.includes(submitId)
620
+ );
621
+ if (isRecord(parent) && Array.isArray(parent.children)) {
622
+ const childIds = new Set(parent.children.filter(isNonEmptyString));
623
+ const childField = optionalCoverageFieldFromFieldBindings(
624
+ components.filter(
625
+ (component) => isRecord(component) && isNonEmptyString(component.id) && childIds.has(component.id)
626
+ )
627
+ );
628
+ if (childField !== void 0) {
629
+ return childField;
630
+ }
631
+ }
632
+ return void 0;
633
+ }
634
+ function componentStatePrefix(componentId) {
635
+ const marker = ":action:";
636
+ const markerIndex = componentId.indexOf(marker);
637
+ if (markerIndex > 0) {
638
+ return componentId.slice(0, markerIndex);
639
+ }
640
+ const fallbackIndex = componentId.lastIndexOf(":");
641
+ return fallbackIndex > 0 ? componentId.slice(0, fallbackIndex) : void 0;
642
+ }
643
+ function optionalCoverageFieldFromFieldBindings(components) {
644
+ const normalized = components.flatMap((component) => componentFieldSearchText(component)).map(normalizeSearchText).join(" ");
645
+ if (normalized.includes("vatricarebenefits") || normalized.includes("coveragevatricarebenefits") || normalized.includes("tricareorchampvabenefits") || normalized.includes("vachampvabenefits")) {
646
+ return "va_tricare_benefits";
647
+ }
648
+ if (normalized.includes("employerunioncoverage") || normalized.includes("coverageemployerunioncoverage")) {
649
+ return "employer_union_coverage";
650
+ }
651
+ return void 0;
652
+ }
653
+ function componentFieldSearchText(component) {
654
+ if (!isRecord(component)) {
655
+ return [];
656
+ }
657
+ const values = [];
658
+ for (const key of ["label", "field"]) {
659
+ const value2 = component[key];
660
+ if (typeof value2 === "string") {
661
+ values.push(value2);
662
+ }
663
+ }
664
+ const value = component.value;
665
+ if (isRecord(value) && typeof value.path === "string") {
666
+ values.push(value.path);
667
+ }
668
+ return values;
669
+ }
670
+ function normalizeSearchText(value) {
671
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
672
+ }
673
+ function hasActionName(component, actionName) {
674
+ if (!isRecord(component) || !isRecord(component.action)) {
675
+ return false;
676
+ }
677
+ const event = component.action.event;
678
+ return isRecord(event) && event.name === actionName;
679
+ }
367
680
  function updateSurfaceStructures(messages, structures) {
368
681
  for (const message of messages) {
369
682
  if (!isRecord(message)) {
@@ -543,8 +856,8 @@ function fieldFromPointer(path) {
543
856
  function fieldPointer(field) {
544
857
  return `/${field.replaceAll("~", "~0").replaceAll("/", "~1")}`;
545
858
  }
546
- function mergePersistedListRowsIntoMessages(messages, surfaceId, persistedRowsByPath) {
547
- if (persistedRowsByPath.size === 0) {
859
+ function mergePersistedListRowsIntoMessages(messages, surfaceId, persistedRowsByPath, deletedItemIdsByPath) {
860
+ if (persistedRowsByPath.size === 0 && deletedItemIdsByPath.size === 0) {
548
861
  return [...messages];
549
862
  }
550
863
  let mergedRoot = false;
@@ -559,7 +872,8 @@ function mergePersistedListRowsIntoMessages(messages, surfaceId, persistedRowsBy
559
872
  ...message.updateDataModel,
560
873
  value: mergePersistedListRowsIntoRoot(
561
874
  message.updateDataModel.value,
562
- persistedRowsByPath
875
+ persistedRowsByPath,
876
+ deletedItemIdsByPath
563
877
  )
564
878
  }
565
879
  };
@@ -574,46 +888,454 @@ function mergePersistedListRowsIntoMessages(messages, surfaceId, persistedRowsBy
574
888
  updateDataModel: {
575
889
  surfaceId,
576
890
  path: "/",
577
- value: mergePersistedListRowsIntoRoot({}, persistedRowsByPath)
891
+ value: mergePersistedListRowsIntoRoot(
892
+ {},
893
+ persistedRowsByPath,
894
+ deletedItemIdsByPath
895
+ )
578
896
  }
579
897
  }
580
898
  ];
581
899
  }
582
- function mergePersistedListRowsIntoRoot(root, persistedRowsByPath) {
900
+ function mergePersistedListRowsIntoRoot(root, persistedRowsByPath, deletedItemIdsByPath) {
583
901
  const merged = cloneJsonRecord(root);
584
- const activeDraftListPaths = activeListDraftPaths(merged);
585
- for (const [listPath, rows] of persistedRowsByPath) {
902
+ for (const listPath of /* @__PURE__ */ new Set([
903
+ ...persistedRowsByPath.keys(),
904
+ ...deletedItemIdsByPath.keys()
905
+ ])) {
906
+ const rows = persistedRowsByPath.get(listPath) ?? [];
586
907
  const dottedRows = merged[listPath];
587
908
  const nestedRows = getNestedValue(merged, listPath);
588
909
  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);
910
+ const shouldPreserveCachedRows = rows.length > 0 && (incomingRows === void 0 || incomingRows.length === 0 && (!hasListDraft(merged, listPath) || hasBlankListDraft(merged, listPath)));
590
911
  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
- }
912
+ const visibleRows = filterDeletedListRows(
913
+ effectiveRows,
914
+ deletedItemIdsByPath.get(listPath)
915
+ );
916
+ const normalizedRows = normalizeListRows(listPath, visibleRows, merged);
917
+ merged[listPath] = cloneJsonArray(normalizedRows);
918
+ setNestedValue(merged, listPath, cloneJsonArray(normalizedRows));
597
919
  }
598
920
  return merged;
599
921
  }
600
- function activeListDraftPaths(root) {
922
+ function hasBlankListDraft(root, listPath) {
601
923
  const drafts = root.__listDrafts;
602
924
  if (!isRecord(drafts)) {
603
- return /* @__PURE__ */ new Set();
925
+ return false;
604
926
  }
605
- return new Set(
606
- Object.keys(drafts).filter((listPath) => listPath.trim().length > 0)
607
- );
927
+ const draft = drafts[listPath];
928
+ if (!isRecord(draft)) {
929
+ return false;
930
+ }
931
+ return Object.values(draft).every(isBlankDraftValue);
932
+ }
933
+ function hasListDraft(root, listPath) {
934
+ const drafts = root.__listDrafts;
935
+ return isRecord(drafts) && isRecord(drafts[listPath]);
936
+ }
937
+ function isBlankDraftValue(value) {
938
+ if (value === null || value === void 0) {
939
+ return true;
940
+ }
941
+ if (typeof value === "string") {
942
+ return value.trim().length === 0;
943
+ }
944
+ if (Array.isArray(value)) {
945
+ return value.length === 0 || value.every(isBlankDraftValue);
946
+ }
947
+ return false;
608
948
  }
609
- function rememberPersistedListRows(persistedRowsByPath, root) {
949
+ function rememberPersistedListRows(persistedRowsByPath, deletedItemIdsByPath, root) {
610
950
  if (!isRecord(root)) {
611
951
  return;
612
952
  }
613
953
  for (const [listPath, rows] of listRowsFromRoot(root)) {
614
- persistedRowsByPath.set(listPath, cloneJsonArray(rows));
954
+ const visibleRows = filterDeletedListRows(
955
+ rows,
956
+ deletedItemIdsByPath.get(listPath)
957
+ );
958
+ persistedRowsByPath.set(
959
+ listPath,
960
+ normalizeListRows(listPath, visibleRows, root)
961
+ );
615
962
  }
616
963
  }
964
+ function normalizeListRows(listPath, rows, root) {
965
+ const clonedRows = cloneJsonArray(rows);
966
+ if (listPath !== "needs.providers") {
967
+ return clonedRows;
968
+ }
969
+ const rootProvider = root === void 0 ? void 0 : providerRowFromAliases(root);
970
+ if (clonedRows.length === 0 && rootProvider !== void 0) {
971
+ return [normalizeProviderRow(rootProvider)];
972
+ }
973
+ return clonedRows.map(
974
+ (row) => isRecord(row) ? normalizeProviderRow(row, rootProvider) : row
975
+ );
976
+ }
977
+ function normalizeProviderRow(row, fallback) {
978
+ const normalized = cloneJsonRecord(row);
979
+ const name = firstProviderName(normalized) ?? (fallback === void 0 ? void 0 : firstProviderName(fallback));
980
+ const location = firstProviderLocation(normalized) ?? (fallback === void 0 ? void 0 : firstProviderLocation(fallback));
981
+ if (normalized.primary_care_provider === void 0 && name !== void 0) {
982
+ normalized.primary_care_provider = name;
983
+ }
984
+ if (normalized.primary_care_provider_location === void 0 && location !== void 0) {
985
+ normalized.primary_care_provider_location = location;
986
+ }
987
+ if (name !== void 0 && location !== void 0 && typeof normalized.__displayText === "string" && !normalized.__displayText.includes(location)) {
988
+ normalized.__displayText = `${normalized.__displayText} | Provider Location: ${location}`;
989
+ } else if (normalized.__displayText === void 0 && (name !== void 0 || location !== void 0)) {
990
+ normalized.__displayText = [
991
+ name === void 0 ? void 0 : `Provider Name: ${name}`,
992
+ location === void 0 ? void 0 : `Provider Location: ${location}`
993
+ ].filter((part) => part !== void 0).join(" | ");
994
+ }
995
+ return normalized;
996
+ }
997
+ function providerRowFromAliases(source) {
998
+ const name = firstProviderName(source);
999
+ const location = firstProviderLocation(source);
1000
+ if (name === void 0 && location === void 0) {
1001
+ return void 0;
1002
+ }
1003
+ return {
1004
+ itemId: "voice-primary-care-provider",
1005
+ ...name === void 0 ? {} : { primary_care_provider: name },
1006
+ ...location === void 0 ? {} : { primary_care_provider_location: location }
1007
+ };
1008
+ }
1009
+ function submittedProviderRowFromEvent(event) {
1010
+ if (event.actionName !== "flow.submit") {
1011
+ return void 0;
1012
+ }
1013
+ const listPath = event.context.listPath;
1014
+ if (listPath !== "needs.providers" && !hasProviderSubmitFields(event.context)) {
1015
+ return void 0;
1016
+ }
1017
+ const name = firstProviderName(event.context);
1018
+ const location = firstProviderLocation(event.context);
1019
+ if (name === void 0 && location === void 0) {
1020
+ return void 0;
1021
+ }
1022
+ return {
1023
+ itemId: "submitted-primary-care-provider",
1024
+ ...name === void 0 ? {} : { primary_care_provider: name },
1025
+ ...location === void 0 ? {} : { primary_care_provider_location: location },
1026
+ __displayText: [
1027
+ name === void 0 ? void 0 : `Provider Name: ${name}`,
1028
+ location === void 0 ? void 0 : `Provider Location: ${location}`
1029
+ ].filter((part) => part !== void 0).join(" | ")
1030
+ };
1031
+ }
1032
+ function clearSubmittedProviderDraft(dataModel, context) {
1033
+ for (const key of Object.keys(context)) {
1034
+ if (key === "listPath") {
1035
+ continue;
1036
+ }
1037
+ if (firstTextField(context, [key]) === void 0 || !isProviderSubmitField(key)) {
1038
+ continue;
1039
+ }
1040
+ dataModel.set(`/__listDrafts/needs.providers/${key}`, "");
1041
+ }
1042
+ }
1043
+ function removeSubmittedProviderDraftControls(entries, fieldPaths, context) {
1044
+ const components = [...entries];
1045
+ const controlIds = /* @__PURE__ */ new Set();
1046
+ for (const [id, component] of components) {
1047
+ if (component.type === "Button" && isProviderSubmitButton(component, context)) {
1048
+ controlIds.add(id);
1049
+ const child = component.properties.child;
1050
+ if (typeof child === "string") {
1051
+ controlIds.add(child);
1052
+ }
1053
+ continue;
1054
+ }
1055
+ if (component.type !== "TextField") {
1056
+ continue;
1057
+ }
1058
+ const value = component.properties.value;
1059
+ const label = component.properties.label;
1060
+ if (isRecord(value) && typeof value.path === "string" && (fieldPaths.has(value.path) || isProviderDraftFieldPath(value.path)) || typeof label === "string" && isProviderSubmitLabel(label)) {
1061
+ controlIds.add(id);
1062
+ }
1063
+ }
1064
+ let changed = true;
1065
+ while (changed) {
1066
+ changed = false;
1067
+ for (const [id, component] of components) {
1068
+ if (controlIds.has(id) || component.type === "Column") {
1069
+ continue;
1070
+ }
1071
+ const children = component.properties.children;
1072
+ if (Array.isArray(children) && children.length > 0 && children.every(
1073
+ (child) => typeof child === "string" && controlIds.has(child)
1074
+ )) {
1075
+ controlIds.add(id);
1076
+ changed = true;
1077
+ }
1078
+ }
1079
+ }
1080
+ for (const [, component] of components) {
1081
+ const children = component.properties.children;
1082
+ if (!Array.isArray(children)) {
1083
+ continue;
1084
+ }
1085
+ const nextChildren = children.filter(
1086
+ (child) => typeof child !== "string" || !controlIds.has(child)
1087
+ );
1088
+ if (nextChildren.length === children.length) {
1089
+ continue;
1090
+ }
1091
+ component.properties = {
1092
+ ...component.properties,
1093
+ children: nextChildren
1094
+ };
1095
+ }
1096
+ }
1097
+ function isProviderSubmitButton(component, context) {
1098
+ const action = component.properties.action;
1099
+ if (!isRecord(action) || !isRecord(action.event)) {
1100
+ return false;
1101
+ }
1102
+ const event = action.event;
1103
+ return event.name === "flow.submit" && sameProviderSubmitContext(event.context, context);
1104
+ }
1105
+ function sameProviderSubmitContext(candidate, context) {
1106
+ if (!isRecord(candidate) || candidate.listPath !== "needs.providers" && !hasProviderSubmitFields(candidate)) {
1107
+ return false;
1108
+ }
1109
+ for (const key of Object.keys(context)) {
1110
+ if (key === "listPath" || !isProviderSubmitField(key)) {
1111
+ continue;
1112
+ }
1113
+ if (!(key in candidate)) {
1114
+ return false;
1115
+ }
1116
+ }
1117
+ return true;
1118
+ }
1119
+ function hasProviderSubmitFields(context) {
1120
+ return Object.keys(context).some(
1121
+ (key) => key !== "listPath" && isProviderSubmitField(key)
1122
+ );
1123
+ }
1124
+ function isProviderSubmitField(key) {
1125
+ return (/* @__PURE__ */ new Set([
1126
+ "primary_care_provider",
1127
+ "primaryCareProvider",
1128
+ "primary_provider",
1129
+ "primaryProvider",
1130
+ "primary_care_doctor",
1131
+ "primaryCareDoctor",
1132
+ "pcp",
1133
+ "providerName",
1134
+ "provider_name",
1135
+ "displayName",
1136
+ "display_name",
1137
+ "doctorName",
1138
+ "doctorname",
1139
+ "doctor_name",
1140
+ "name",
1141
+ "primary_care_provider_location",
1142
+ "primaryCareProviderLocation",
1143
+ "provider_location",
1144
+ "providerLocation",
1145
+ "location_details",
1146
+ "locationDetails",
1147
+ "office_location",
1148
+ "officeLocation",
1149
+ "practice_location",
1150
+ "practiceLocation",
1151
+ "location",
1152
+ "address"
1153
+ ])).has(key);
1154
+ }
1155
+ function eventSourceFieldPaths(context) {
1156
+ const paths = /* @__PURE__ */ new Set();
1157
+ for (const [key, value] of Object.entries(context)) {
1158
+ if (key === "listPath" || !isProviderSubmitField(key) || !isRecord(value)) {
1159
+ continue;
1160
+ }
1161
+ const path = value.path;
1162
+ if (typeof path === "string") {
1163
+ paths.add(path);
1164
+ }
1165
+ }
1166
+ return paths;
1167
+ }
1168
+ function isProviderDraftFieldPath(path) {
1169
+ const prefix = "/__listDrafts/needs.providers/";
1170
+ const field = path.startsWith(prefix) ? path.slice(prefix.length) : lastPathSegment(path);
1171
+ return field !== void 0 && isProviderSubmitField(field);
1172
+ }
1173
+ function lastPathSegment(path) {
1174
+ const normalized = path.trim().replaceAll("\\", "/");
1175
+ const index = normalized.lastIndexOf("/");
1176
+ const segment = index >= 0 ? normalized.slice(index + 1) : normalized;
1177
+ return segment.length > 0 ? segment : void 0;
1178
+ }
1179
+ function isProviderSubmitLabel(label) {
1180
+ switch (normalizeFieldKey(label)) {
1181
+ case "provider_name":
1182
+ case "primary_care_provider":
1183
+ case "primary_care_provider_name":
1184
+ case "provider_location":
1185
+ case "primary_care_provider_location":
1186
+ case "location_details":
1187
+ return true;
1188
+ default:
1189
+ return false;
1190
+ }
1191
+ }
1192
+ function normalizeFieldKey(key) {
1193
+ return key.trim().toLowerCase().replaceAll(/[^a-z0-9]+/g, "_").replaceAll(/^_+|_+$/g, "");
1194
+ }
1195
+ function upsertListRow(rows, row) {
1196
+ const itemId = row.itemId;
1197
+ if (typeof itemId !== "string" || itemId.length === 0) {
1198
+ return [...rows, row];
1199
+ }
1200
+ const next = [...rows];
1201
+ const existing = next.findIndex(
1202
+ (candidate) => isRecord(candidate) && candidate.itemId === itemId
1203
+ );
1204
+ if (existing >= 0) {
1205
+ next[existing] = row;
1206
+ return next;
1207
+ }
1208
+ return [...next, row];
1209
+ }
1210
+ function removeListRowByItemId(rows, itemId) {
1211
+ return rows.filter(
1212
+ (row) => !isRecord(row) || row.itemId !== itemId
1213
+ );
1214
+ }
1215
+ function rememberDeletedListItemId(deletedItemIdsByPath, listPath, itemId) {
1216
+ const deleted = deletedItemIdsByPath.get(listPath) ?? /* @__PURE__ */ new Set();
1217
+ deleted.add(itemId);
1218
+ deletedItemIdsByPath.set(listPath, deleted);
1219
+ }
1220
+ function filterDeletedListRows(rows, deletedItemIds) {
1221
+ const clonedRows = cloneJsonArray(rows);
1222
+ if (deletedItemIds === void 0 || deletedItemIds.size === 0) {
1223
+ return clonedRows;
1224
+ }
1225
+ return clonedRows.filter(
1226
+ (row) => !isRecord(row) || !deletedItemIds.has(stringRowItemId(row))
1227
+ );
1228
+ }
1229
+ function stringRowItemId(row) {
1230
+ const itemId = row.itemId;
1231
+ return typeof itemId === "string" ? itemId : "";
1232
+ }
1233
+ var PROVIDER_NAME_FIELDS = /* @__PURE__ */ new Set([
1234
+ "primary_care_provider",
1235
+ "primarycareprovider",
1236
+ "primary_care_provider_name",
1237
+ "primarycareprovidername",
1238
+ "primary_provider",
1239
+ "primaryprovider",
1240
+ "primary_provider_name",
1241
+ "primaryprovidername",
1242
+ "primary_care_doctor",
1243
+ "primarycaredoctor",
1244
+ "primary_care_doctor_name",
1245
+ "primarycaredoctorname",
1246
+ "pcp",
1247
+ "pcp_name",
1248
+ "pcpname",
1249
+ "provider",
1250
+ "provider_name",
1251
+ "providername",
1252
+ "display_name",
1253
+ "displayname",
1254
+ "doctor",
1255
+ "doctor_name",
1256
+ "doctorname",
1257
+ "name"
1258
+ ]);
1259
+ var PROVIDER_LOCATION_FIELDS = /* @__PURE__ */ new Set([
1260
+ "primary_care_provider_location",
1261
+ "primarycareproviderlocation",
1262
+ "primary_provider_location",
1263
+ "primaryproviderlocation",
1264
+ "primary_care_location",
1265
+ "primarycarelocation",
1266
+ "primary_care_doctor_location",
1267
+ "primarycaredoctorlocation",
1268
+ "pcp_location",
1269
+ "pcplocation",
1270
+ "provider_location",
1271
+ "providerlocation",
1272
+ "provider_location_details",
1273
+ "providerlocationdetails",
1274
+ "doctor_location",
1275
+ "doctorlocation",
1276
+ "location",
1277
+ "location_details",
1278
+ "locationdetails",
1279
+ "office_location",
1280
+ "officelocation",
1281
+ "practice_location",
1282
+ "practicelocation",
1283
+ "provider_address",
1284
+ "provideraddress",
1285
+ "doctor_address",
1286
+ "doctoraddress",
1287
+ "address",
1288
+ "city_state",
1289
+ "citystate"
1290
+ ]);
1291
+ function firstProviderName(source) {
1292
+ return firstTextByNormalizedField(source, PROVIDER_NAME_FIELDS);
1293
+ }
1294
+ function firstProviderLocation(source) {
1295
+ return firstTextByNormalizedField(source, PROVIDER_LOCATION_FIELDS);
1296
+ }
1297
+ function firstTextByNormalizedField(source, fields) {
1298
+ for (const [key, value] of Object.entries(source)) {
1299
+ if (!fields.has(normalizeFieldKey(key))) {
1300
+ continue;
1301
+ }
1302
+ const text = scalarText(value);
1303
+ if (text !== void 0) {
1304
+ return text;
1305
+ }
1306
+ }
1307
+ return void 0;
1308
+ }
1309
+ function scalarText(value) {
1310
+ if (typeof value === "string") {
1311
+ const text = value.trim();
1312
+ return text.length > 0 ? text : void 0;
1313
+ }
1314
+ if (!isRecord(value)) {
1315
+ return void 0;
1316
+ }
1317
+ return firstTextField(value, [
1318
+ "value",
1319
+ "displayValue",
1320
+ "display_value",
1321
+ "text",
1322
+ "answer",
1323
+ "label"
1324
+ ]);
1325
+ }
1326
+ function firstTextField(source, keys) {
1327
+ for (const key of keys) {
1328
+ const value = source[key];
1329
+ if (typeof value !== "string") {
1330
+ continue;
1331
+ }
1332
+ const text = value.trim();
1333
+ if (text.length > 0) {
1334
+ return text;
1335
+ }
1336
+ }
1337
+ return void 0;
1338
+ }
617
1339
  function listRowsFromRoot(root) {
618
1340
  const rowsByPath = /* @__PURE__ */ new Map();
619
1341
  for (const [key, value] of Object.entries(root)) {
@@ -796,6 +1518,7 @@ function A2UISurfaceHost({
796
1518
  resolvedSurfaceMetadata?.fieldInteractions ?? []
797
1519
  ),
798
1520
  annotateActionControls(root),
1521
+ suppressSkipForMandatoryFields(root, structure.fields),
799
1522
  blockInvalidActionControls(root),
800
1523
  setPendingGroupsDisabled(
801
1524
  root,
@@ -1113,7 +1836,9 @@ function annotateActionControls(root) {
1113
1836
  );
1114
1837
  const groups = /* @__PURE__ */ new Set();
1115
1838
  for (const action of actions) {
1839
+ const label = action.textContent?.trim().toLowerCase() ?? "";
1116
1840
  action.dataset.a2uiAction = "true";
1841
+ action.dataset.a2uiActionLabel = label;
1117
1842
  if (action.parentElement !== null) {
1118
1843
  action.parentElement.dataset.a2uiActionGroup = "true";
1119
1844
  groups.add(action.parentElement);
@@ -1122,12 +1847,40 @@ function annotateActionControls(root) {
1122
1847
  return () => {
1123
1848
  for (const action of actions) {
1124
1849
  delete action.dataset.a2uiAction;
1850
+ delete action.dataset.a2uiActionLabel;
1125
1851
  }
1126
1852
  for (const group of groups) {
1127
1853
  delete group.dataset.a2uiActionGroup;
1128
1854
  }
1129
1855
  };
1130
1856
  }
1857
+ function suppressSkipForMandatoryFields(root, fields) {
1858
+ const requiredFields = fields.filter((field) => field.required);
1859
+ if (requiredFields.length === 0 || requiredFields.every((field) => isOptionalCoverageField(field.field))) {
1860
+ return () => void 0;
1861
+ }
1862
+ const skipButtons = [
1863
+ ...root.querySelectorAll("[data-a2ui-action='true']")
1864
+ ].filter((button) => button.dataset.a2uiActionLabel === "skip");
1865
+ const originals = skipButtons.map((button) => ({
1866
+ button,
1867
+ hidden: button.hidden,
1868
+ ariaHidden: button.getAttribute("aria-hidden")
1869
+ }));
1870
+ for (const button of skipButtons) {
1871
+ button.hidden = true;
1872
+ button.setAttribute("aria-hidden", "true");
1873
+ }
1874
+ return () => {
1875
+ for (const original of originals) {
1876
+ original.button.hidden = original.hidden;
1877
+ restoreAttribute(original.button, "aria-hidden", original.ariaHidden);
1878
+ }
1879
+ };
1880
+ }
1881
+ function isOptionalCoverageField(field) {
1882
+ return field === "employer_union_coverage" || field === "va_tricare_benefits" || field === "coverage.employerUnionCoverage" || field === "coverage.vaTricareBenefits";
1883
+ }
1131
1884
  var EMPTY_STRUCTURE = { fields: [], tabs: [] };
1132
1885
  var SurfaceErrorBoundary = class extends Component {
1133
1886
  state = { failedSurfaceId: void 0 };
@@ -1236,8 +1989,22 @@ function applyControlValidationAttributes(control, field) {
1236
1989
  }
1237
1990
  function attachControlValidationBehavior(control, field) {
1238
1991
  const maxLength = numericMaxLengthForField(field);
1992
+ const customValidation = customValidationForField(field);
1239
1993
  if (maxLength === void 0 || !isTextInput(control)) {
1240
- return void 0;
1994
+ if (customValidation === void 0 || !isTextInput(control)) {
1995
+ return void 0;
1996
+ }
1997
+ const validate = () => {
1998
+ applyCustomValidationMessage(control, customValidation);
1999
+ };
2000
+ control.addEventListener("input", validate);
2001
+ control.addEventListener("change", validate);
2002
+ validate();
2003
+ return () => {
2004
+ control.setCustomValidity("");
2005
+ control.removeEventListener("input", validate);
2006
+ control.removeEventListener("change", validate);
2007
+ };
1241
2008
  }
1242
2009
  const sanitize = () => {
1243
2010
  const next = control.value.replace(/\D/g, "").slice(0, maxLength);
@@ -1245,15 +2012,50 @@ function attachControlValidationBehavior(control, field) {
1245
2012
  control.value = next;
1246
2013
  control.dispatchEvent(new Event("input", { bubbles: true }));
1247
2014
  }
2015
+ if (customValidation !== void 0) {
2016
+ applyCustomValidationMessage(control, customValidation);
2017
+ }
1248
2018
  };
1249
2019
  control.addEventListener("input", sanitize);
1250
2020
  control.addEventListener("change", sanitize);
1251
2021
  sanitize();
1252
2022
  return () => {
2023
+ control.setCustomValidity("");
1253
2024
  control.removeEventListener("input", sanitize);
1254
2025
  control.removeEventListener("change", sanitize);
1255
2026
  };
1256
2027
  }
2028
+ function customValidationForField(field) {
2029
+ if (isZipValidationRegexp(field.validationRegexp)) {
2030
+ return {
2031
+ message: "Enter a valid 5-digit ZIP code.",
2032
+ pattern: regexpFromPattern(field.validationRegexp)
2033
+ };
2034
+ }
2035
+ if (isPhoneValidationRegexp(field.validationRegexp)) {
2036
+ return {
2037
+ message: "Enter a valid 10-digit US phone number.",
2038
+ pattern: regexpFromPattern(field.validationRegexp)
2039
+ };
2040
+ }
2041
+ return void 0;
2042
+ }
2043
+ function applyCustomValidationMessage(control, validation) {
2044
+ control.setCustomValidity("");
2045
+ if (control.value.length > 0 && (validation.pattern?.test(control.value) === false || !control.checkValidity())) {
2046
+ control.setCustomValidity(validation.message);
2047
+ }
2048
+ }
2049
+ function regexpFromPattern(pattern) {
2050
+ if (pattern === void 0) {
2051
+ return void 0;
2052
+ }
2053
+ try {
2054
+ return new RegExp(pattern);
2055
+ } catch {
2056
+ return void 0;
2057
+ }
2058
+ }
1257
2059
  function numericMaxLengthForField(field) {
1258
2060
  if (isPhoneValidationRegexp(field.validationRegexp)) {
1259
2061
  return 10;
@@ -1288,6 +2090,9 @@ function blockInvalidActionControls(root) {
1288
2090
  if (action === null || !root.contains(action)) {
1289
2091
  return;
1290
2092
  }
2093
+ if (action.dataset.a2uiActionLabel === "skip") {
2094
+ return;
2095
+ }
1291
2096
  const invalid2 = firstInvalidInput(root);
1292
2097
  if (invalid2 === void 0) {
1293
2098
  return;
@@ -1985,7 +2790,7 @@ function decodeRendererAction(event) {
1985
2790
  const listPath = event.context.listPath;
1986
2791
  if (typeof listPath === "string" && listPath.length > 0) {
1987
2792
  const fieldEntries2 = entries.filter(([key]) => key !== "listPath");
1988
- const fields3 = scalarFields(fieldEntries2);
2793
+ const fields3 = scalarFields(fieldEntries2, listPath);
1989
2794
  if (fields3 === null) {
1990
2795
  return null;
1991
2796
  }
@@ -2015,7 +2820,8 @@ function decodeRendererAction(event) {
2015
2820
  const fieldEntries2 = entries.filter(
2016
2821
  ([key]) => key !== "listPath" && key !== "itemId"
2017
2822
  );
2018
- const fields2 = scalarFields(fieldEntries2);
2823
+ const fields2 = scalarFields(fieldEntries2, listPath);
2824
+ const targetStateId2 = listActionTargetStateId(event.sourceComponentId);
2019
2825
  if (action !== "flow.list.delete" && (fields2 === null || Object.keys(fields2).length === 0)) {
2020
2826
  return null;
2021
2827
  }
@@ -2023,6 +2829,7 @@ function decodeRendererAction(event) {
2023
2829
  action,
2024
2830
  payload: {
2025
2831
  listPath,
2832
+ ...targetStateId2 === void 0 ? {} : { targetStateId: targetStateId2 },
2026
2833
  ...typeof itemId === "string" ? { itemId } : {},
2027
2834
  ...fields2 !== null && Object.keys(fields2).length > 0 ? { fields: fields2 } : {}
2028
2835
  }
@@ -2045,17 +2852,77 @@ function decodeRendererAction(event) {
2045
2852
  }
2046
2853
  };
2047
2854
  }
2048
- function scalarFields(entries) {
2855
+ function listActionTargetStateId(sourceComponentId) {
2856
+ const markerIndex = sourceComponentId.indexOf(":list");
2857
+ if (markerIndex <= 0) {
2858
+ return void 0;
2859
+ }
2860
+ const stateId = sourceComponentId.slice(0, markerIndex);
2861
+ return stateId.length > 0 ? stateId : void 0;
2862
+ }
2863
+ function scalarFields(entries, listPath) {
2049
2864
  const fields = {};
2050
2865
  for (const [key, value] of entries) {
2051
2866
  const normalized = normalizeFieldValue(value);
2052
2867
  if (normalized === void 0) {
2053
2868
  return null;
2054
2869
  }
2055
- fields[key] = normalized;
2870
+ fields[canonicalListFieldKey(listPath, key)] = normalized;
2056
2871
  }
2057
2872
  return fields;
2058
2873
  }
2874
+ function canonicalListFieldKey(listPath, key) {
2875
+ if (listPath !== "needs.providers") {
2876
+ return key;
2877
+ }
2878
+ switch (normalizeFieldKey2(key)) {
2879
+ case "primary_care_provider":
2880
+ case "primarycareprovider":
2881
+ case "primarycare":
2882
+ case "primary_care":
2883
+ case "primary_care_doctor":
2884
+ case "primary_doctor":
2885
+ case "primarydoctor":
2886
+ case "primary_provider":
2887
+ case "primaryprovider":
2888
+ case "pcp":
2889
+ case "provider_name":
2890
+ case "providername":
2891
+ case "display_name":
2892
+ case "displayname":
2893
+ return "primary_care_provider";
2894
+ case "primary_care_provider_location":
2895
+ case "primarycareproviderlocation":
2896
+ case "primarycarelocation":
2897
+ case "primary_care_location":
2898
+ case "provider_location":
2899
+ case "providerlocation":
2900
+ case "doctor_location":
2901
+ case "doctorlocation":
2902
+ case "doctor_address":
2903
+ case "doctoraddress":
2904
+ case "location_details":
2905
+ case "locationdetails":
2906
+ case "provider_address":
2907
+ case "provideraddress":
2908
+ case "office_location":
2909
+ case "officelocation":
2910
+ case "practice_location":
2911
+ case "practicelocation":
2912
+ case "city":
2913
+ case "city_state":
2914
+ case "cityandstate":
2915
+ case "city_and_state":
2916
+ case "location":
2917
+ case "address":
2918
+ return "primary_care_provider_location";
2919
+ default:
2920
+ return key;
2921
+ }
2922
+ }
2923
+ function normalizeFieldKey2(key) {
2924
+ return key.trim().toLowerCase().replaceAll(/[^a-z0-9]+/g, "_").replaceAll(/^_+|_+$/g, "");
2925
+ }
2059
2926
  function isSameLogicalAction(envelope, candidate) {
2060
2927
  return envelope.action === candidate.action && JSON.stringify(envelope.payload) === JSON.stringify(candidate.payload);
2061
2928
  }