@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-
|
|
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-
|
|
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
|
-
|
|
92
|
-
|
|
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(
|
|
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) :
|
|
408
|
-
if (control === void 0 || field === void 0 ||
|
|
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
|
|
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
|
-
|
|
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;
|