@ukiahinsure/a2ui-react-adapter 0.1.15 → 0.1.17

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
@@ -19,48 +19,153 @@ import {
19
19
  } from "@a2ui/web_core/v0_9";
20
20
 
21
21
  // src/DateTimeInput.tsx
22
- import { useId, useLayoutEffect, useRef } from "react";
22
+ import { useId, useLayoutEffect, useRef, useState } from "react";
23
23
  import { createComponentImplementation } from "@a2ui/react/v0_9";
24
24
  import { DateTimeInputApi } from "@a2ui/web_core/v0_9/basic_catalog";
25
25
  import { jsx, jsxs } from "react/jsx-runtime";
26
- var DateTimeInput = createComponentImplementation(
27
- DateTimeInputApi,
28
- ({ props }) => {
29
- const id = useId();
30
- const inputRef = useRef(null);
31
- const initialValue = useRef(props.value || "");
32
- const type = props.enableDate && !props.enableTime ? "date" : !props.enableDate && props.enableTime ? "time" : "datetime-local";
33
- useLayoutEffect(() => {
34
- const input = inputRef.current;
35
- if (input && input.value !== (props.value || "")) {
36
- input.value = props.value || "";
37
- }
38
- }, [props.value]);
39
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "var(--a2ui-spacing-xs, 0.25rem)" }, children: [
40
- props.label && /* @__PURE__ */ jsx("label", { htmlFor: id, style: { fontSize: "var(--a2ui-label-font-size, var(--a2ui-font-size-s))", fontWeight: "bold" }, children: props.label }),
26
+ var inputStyle = {
27
+ backgroundColor: "var(--a2ui-datetimeinput-background, var(--a2ui-color-input, #fff))",
28
+ color: "var(--a2ui-datetimeinput-color, var(--a2ui-color-on-input, #333))",
29
+ border: "var(--a2ui-datetimeinput-border, var(--a2ui-border))",
30
+ borderRadius: "var(--a2ui-datetimeinput-border-radius, var(--a2ui-border-radius))",
31
+ padding: "var(--a2ui-datetimeinput-padding, var(--a2ui-spacing-s))",
32
+ boxSizing: "border-box"
33
+ };
34
+ var labelStyle = { fontSize: "var(--a2ui-label-font-size, var(--a2ui-font-size-s))", fontWeight: "bold" };
35
+ function parseDateDraft(raw) {
36
+ const value = raw.trim();
37
+ const iso = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
38
+ const us = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/.exec(value);
39
+ if (!iso && !us) return null;
40
+ const year = Number(iso ? iso[1] : us[3]);
41
+ const month = Number(iso ? iso[2] : us[1]);
42
+ const day = Number(iso ? iso[3] : us[2]);
43
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
44
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
45
+ if (year < 1 || month < 1 || month > 12 || day < 1 || day > days[month - 1]) return null;
46
+ return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
47
+ }
48
+ function displayDate(value) {
49
+ const iso = parseDateDraft(value);
50
+ return iso ? `${iso.slice(5, 7)}/${iso.slice(8, 10)}/${iso.slice(0, 4)}` : value;
51
+ }
52
+ function DateOnlyInput(props) {
53
+ const id = useId();
54
+ const [draft, setDraft] = useState(() => displayDate(props.value || ""));
55
+ const [pickerOpen, setPickerOpen] = useState(false);
56
+ const published = useRef(props.value || "");
57
+ const inputRef = useRef(null);
58
+ const pickerRef = useRef(null);
59
+ const min = typeof props.min === "string" ? props.min : void 0;
60
+ const max = typeof props.max === "string" ? props.max : void 0;
61
+ const parsed = parseDateDraft(draft);
62
+ useLayoutEffect(() => {
63
+ const incoming = props.value || "";
64
+ if (incoming !== published.current) {
65
+ published.current = incoming;
66
+ setDraft(displayDate(incoming));
67
+ }
68
+ }, [props.value]);
69
+ useLayoutEffect(() => {
70
+ const message = draft.trim() && !parsed ? "Enter a valid date in MM/DD/YYYY format." : parsed && min && parsed < min ? `Enter a date on or after ${displayDate(min)}.` : parsed && max && parsed > max ? `Enter a date on or before ${displayDate(max)}.` : "";
71
+ inputRef.current?.setCustomValidity(message);
72
+ }, [draft, parsed, min, max]);
73
+ const update = (raw) => {
74
+ setDraft(raw);
75
+ const value = parseDateDraft(raw) || "";
76
+ published.current = value;
77
+ props.setValue(value);
78
+ };
79
+ return /* @__PURE__ */ jsxs("div", { "data-a2ui-date-editor": "true", style: { display: "flex", flexDirection: "column", gap: "var(--a2ui-spacing-xs, 0.25rem)" }, children: [
80
+ props.label && /* @__PURE__ */ jsx("label", { htmlFor: id, style: labelStyle, children: props.label }),
81
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.25rem", position: "relative" }, children: [
41
82
  /* @__PURE__ */ jsx(
42
83
  "input",
43
84
  {
44
85
  ref: inputRef,
45
86
  id,
46
- type,
47
- "data-a2ui-native-date-draft": "true",
48
- defaultValue: initialValue.current,
49
- onChange: (event) => props.setValue(event.currentTarget.value),
50
- min: typeof props.min === "string" ? props.min : void 0,
51
- max: typeof props.max === "string" ? props.max : void 0,
52
- style: {
53
- backgroundColor: "var(--a2ui-datetimeinput-background, var(--a2ui-color-input, #fff))",
54
- color: "var(--a2ui-datetimeinput-color, var(--a2ui-color-on-input, #333))",
55
- border: "var(--a2ui-datetimeinput-border, var(--a2ui-border))",
56
- borderRadius: "var(--a2ui-datetimeinput-border-radius, var(--a2ui-border-radius))",
57
- padding: "var(--a2ui-datetimeinput-padding, var(--a2ui-spacing-s))",
58
- boxSizing: "border-box"
59
- }
87
+ type: "text",
88
+ inputMode: "numeric",
89
+ autoComplete: "off",
90
+ "data-a2ui-date-text-draft": "true",
91
+ placeholder: "MM/DD/YYYY",
92
+ value: draft,
93
+ onChange: (event) => update(event.currentTarget.value),
94
+ onBlur: () => {
95
+ if (parsed) setDraft(displayDate(parsed));
96
+ },
97
+ style: { ...inputStyle, minWidth: 0, flex: 1 }
98
+ }
99
+ ),
100
+ /* @__PURE__ */ jsx(
101
+ "button",
102
+ {
103
+ type: "button",
104
+ "aria-label": `Choose ${props.label || "date"} from calendar`,
105
+ style: inputStyle,
106
+ onClick: () => {
107
+ const picker = pickerRef.current;
108
+ if (picker && typeof picker.showPicker === "function") {
109
+ try {
110
+ picker.showPicker();
111
+ return;
112
+ } catch {
113
+ }
114
+ }
115
+ setPickerOpen(true);
116
+ },
117
+ children: "\u25A6"
118
+ }
119
+ ),
120
+ /* @__PURE__ */ jsx(
121
+ "input",
122
+ {
123
+ ref: pickerRef,
124
+ type: "date",
125
+ "aria-label": `${props.label || "Date"} calendar`,
126
+ tabIndex: pickerOpen ? 0 : -1,
127
+ "aria-hidden": !pickerOpen,
128
+ "data-a2ui-calendar-input": "true",
129
+ value: parsed || "",
130
+ min,
131
+ max,
132
+ onChange: (event) => {
133
+ update(displayDate(event.currentTarget.value));
134
+ setPickerOpen(false);
135
+ },
136
+ style: pickerOpen ? inputStyle : { position: "absolute", opacity: 0, pointerEvents: "none", width: 1, height: 1, bottom: 0, right: 0 }
60
137
  }
61
138
  )
62
- ] });
63
- }
139
+ ] })
140
+ ] });
141
+ }
142
+ function NativeTimeInput(props) {
143
+ const id = useId();
144
+ const inputRef = useRef(null);
145
+ const initialValue = useRef(props.value || "");
146
+ useLayoutEffect(() => {
147
+ if (inputRef.current && inputRef.current.value !== (props.value || "")) inputRef.current.value = props.value || "";
148
+ }, [props.value]);
149
+ return /* @__PURE__ */ jsxs("div", { children: [
150
+ props.label && /* @__PURE__ */ jsx("label", { htmlFor: id, style: labelStyle, children: props.label }),
151
+ /* @__PURE__ */ jsx(
152
+ "input",
153
+ {
154
+ ref: inputRef,
155
+ id,
156
+ type: !props.enableDate && props.enableTime ? "time" : "datetime-local",
157
+ defaultValue: initialValue.current,
158
+ onChange: (event) => props.setValue(event.currentTarget.value),
159
+ min: typeof props.min === "string" ? props.min : void 0,
160
+ max: typeof props.max === "string" ? props.max : void 0,
161
+ style: inputStyle
162
+ }
163
+ )
164
+ ] });
165
+ }
166
+ var DateTimeInput = createComponentImplementation(
167
+ DateTimeInputApi,
168
+ ({ props }) => props.enableDate && !props.enableTime ? /* @__PURE__ */ jsx(DateOnlyInput, { ...props }) : /* @__PURE__ */ jsx(NativeTimeInput, { ...props })
64
169
  );
65
170
 
66
171
  // src/adapter.ts
@@ -1068,7 +1173,7 @@ function mergePersistedListRowsIntoRoot(root, persistedRowsByPath, deletedItemId
1068
1173
  optimisticEffectiveRows,
1069
1174
  deletedItemIdsByPath.get(listPath)
1070
1175
  );
1071
- const normalizedRows = normalizeListRows(listPath, visibleRows, merged);
1176
+ const normalizedRows = normalizeListRows(listPath, visibleRows);
1072
1177
  merged[listPath] = cloneJsonArray(normalizedRows);
1073
1178
  setNestedValue(merged, listPath, cloneJsonArray(normalizedRows));
1074
1179
  }
@@ -1122,21 +1227,17 @@ function rememberPersistedListRows(persistedRowsByPath, deletedItemIdsByPath, ro
1122
1227
  );
1123
1228
  persistedRowsByPath.set(
1124
1229
  listPath,
1125
- normalizeListRows(listPath, visibleRows, root)
1230
+ normalizeListRows(listPath, visibleRows)
1126
1231
  );
1127
1232
  }
1128
1233
  }
1129
- function normalizeListRows(listPath, rows, root) {
1234
+ function normalizeListRows(listPath, rows) {
1130
1235
  const clonedRows = cloneJsonArray(rows);
1131
1236
  if (listPath !== "needs.providers") {
1132
1237
  return clonedRows;
1133
1238
  }
1134
- const rootProvider = root === void 0 ? void 0 : providerRowFromAliases(root);
1135
- if (clonedRows.length === 0 && rootProvider !== void 0) {
1136
- return [normalizeProviderRow(rootProvider)];
1137
- }
1138
1239
  return clonedRows.map(
1139
- (row) => isRecord(row) ? normalizeProviderRow(row, rootProvider) : row
1240
+ (row) => isRecord(row) ? normalizeProviderRow(row) : row
1140
1241
  );
1141
1242
  }
1142
1243
  function normalizeProviderRow(row, fallback) {
@@ -1159,24 +1260,12 @@ function normalizeProviderRow(row, fallback) {
1159
1260
  }
1160
1261
  return normalized;
1161
1262
  }
1162
- function providerRowFromAliases(source) {
1163
- const name = firstProviderName(source);
1164
- const location = firstProviderLocation(source);
1165
- if (name === void 0 && location === void 0) {
1166
- return void 0;
1167
- }
1168
- return {
1169
- itemId: "voice-primary-care-provider",
1170
- ...name === void 0 ? {} : { primary_care_provider: name },
1171
- ...location === void 0 ? {} : { primary_care_provider_location: location }
1172
- };
1173
- }
1174
1263
  function submittedProviderRowFromEvent(event) {
1175
1264
  if (event.actionName !== "flow.submit") {
1176
1265
  return void 0;
1177
1266
  }
1178
1267
  const listPath = event.context.listPath;
1179
- if (listPath !== "needs.providers" && !hasProviderSubmitFields(event.context)) {
1268
+ if (listPath !== "needs.providers") {
1180
1269
  return void 0;
1181
1270
  }
1182
1271
  const name = firstProviderName(event.context);
@@ -1703,7 +1792,7 @@ function A2UISurfaceHost({
1703
1792
  resolvedSurfaceMetadata?.fieldInteractions ?? []
1704
1793
  ),
1705
1794
  annotateActionControls(root),
1706
- suppressSkipForMandatoryFields(root, structure.fields),
1795
+ suppressSkipForMandatoryFields(root, structure.fields, resolvedSurfaceMetadata?.fieldInteractions ?? []),
1707
1796
  blockInvalidActionControls(root),
1708
1797
  setPendingGroupsDisabled(
1709
1798
  root,
@@ -1859,14 +1948,18 @@ function bindFieldInteractionGroups(root, groups, editing, controller) {
1859
1948
  const field = control.dataset.a2uiField;
1860
1949
  const group = field === void 0 ? void 0 : groupByField.get(field);
1861
1950
  if (group !== void 0) {
1862
- originalGroupAttributes.set(control, {
1863
- groupId: control.dataset.a2uiGroupId,
1864
- groupMode: control.dataset.a2uiGroupMode,
1865
- editState: control.dataset.a2uiEditState
1866
- });
1867
- control.dataset.a2uiGroupId = group.groupId;
1868
- control.dataset.a2uiGroupMode = group.mode;
1869
- control.dataset.a2uiEditState = groupEditState(group.groupId, editing);
1951
+ const dateEditor = control.closest("[data-a2ui-date-editor]");
1952
+ const targets = dateEditor ? [control, ...dateEditor.querySelectorAll("button, input[data-a2ui-calendar-input]")] : [control];
1953
+ for (const target of targets) {
1954
+ originalGroupAttributes.set(target, {
1955
+ groupId: target.dataset.a2uiGroupId,
1956
+ groupMode: target.dataset.a2uiGroupMode,
1957
+ editState: target.dataset.a2uiEditState
1958
+ });
1959
+ target.dataset.a2uiGroupId = group.groupId;
1960
+ target.dataset.a2uiGroupMode = group.mode;
1961
+ target.dataset.a2uiEditState = groupEditState(group.groupId, editing);
1962
+ }
1870
1963
  }
1871
1964
  }
1872
1965
  if (controller === void 0) {
@@ -1899,7 +1992,8 @@ function bindFieldInteractionGroups(root, groups, editing, controller) {
1899
1992
  }
1900
1993
  queueMicrotask(() => {
1901
1994
  if (interactionGroupId(root, root.ownerDocument.activeElement) !== groupId) {
1902
- controller.commitFieldGroup(groupId);
1995
+ const invalidDate = [...root.querySelectorAll("input[data-a2ui-date-text-draft]")].some((input) => input.dataset.a2uiGroupId === groupId && !input.checkValidity());
1996
+ if (!invalidDate) controller.commitFieldGroup(groupId);
1903
1997
  }
1904
1998
  });
1905
1999
  };
@@ -2039,8 +2133,11 @@ function annotateActionControls(root) {
2039
2133
  }
2040
2134
  };
2041
2135
  }
2042
- function suppressSkipForMandatoryFields(root, fields) {
2043
- const requiredFields = fields.filter((field) => field.required);
2136
+ function suppressSkipForMandatoryFields(root, fields, groups) {
2137
+ const currentFields = new Set(groups.filter((group) => group.mode === "current").flatMap((group) => group.fields.map((binding) => binding.field)));
2138
+ const requiredFields = fields.filter(
2139
+ (field) => field.required && (groups.length === 0 || currentFields.has(field.field)) && !field.componentId.includes(":list-new:field:") && !field.componentId.includes(":list-row:field:")
2140
+ );
2044
2141
  if (requiredFields.length === 0 || requiredFields.every((field) => isOptionalCoverageField(field.field))) {
2045
2142
  return () => void 0;
2046
2143
  }