@mieweb/ui 0.11.0-dev.31 → 0.11.0-dev.33

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.
Files changed (63) hide show
  1. package/agent/README.md +7 -7
  2. package/agent/agents/mieweb-ui-design.agent.md +8 -3
  3. package/agent/agents/mieweb-ui-i18n.agent.md +91 -0
  4. package/agent/agents/mieweb-ui-styling.agent.md +1 -1
  5. package/agent/skills/mieweb-ui-catalog/SKILL.md +15 -10
  6. package/agent/skills/mieweb-ui-catalog/selection-guidance.md +14 -7
  7. package/agent/skills/mieweb-ui-i18n-checklist/SKILL.md +133 -0
  8. package/dist/{chunk-INMDLVUB.cjs → chunk-6UYMO3PF.cjs} +7 -2
  9. package/dist/chunk-6UYMO3PF.cjs.map +1 -0
  10. package/dist/{chunk-YG7BR76Y.js → chunk-GMNF4E7B.js} +3 -42
  11. package/dist/chunk-GMNF4E7B.js.map +1 -0
  12. package/dist/chunk-HTZ27DGZ.js +46 -0
  13. package/dist/chunk-HTZ27DGZ.js.map +1 -0
  14. package/dist/{chunk-JQAGNAY2.js → chunk-IBEQ54XW.js} +4 -4
  15. package/dist/{chunk-JQAGNAY2.js.map → chunk-IBEQ54XW.js.map} +1 -1
  16. package/dist/{chunk-SNAMNKFH.js → chunk-JEXEFWEM.js} +7 -2
  17. package/dist/chunk-JEXEFWEM.js.map +1 -0
  18. package/dist/{chunk-FQUSSPXX.cjs → chunk-LJ4CCM3V.cjs} +22 -4
  19. package/dist/chunk-LJ4CCM3V.cjs.map +1 -0
  20. package/dist/{chunk-KIVEYQGA.cjs → chunk-MSX2P5JE.cjs} +2 -48
  21. package/dist/chunk-MSX2P5JE.cjs.map +1 -0
  22. package/dist/{chunk-L52HKEUD.js → chunk-NAQMIFUF.js} +3 -3
  23. package/dist/{chunk-L52HKEUD.js.map → chunk-NAQMIFUF.js.map} +1 -1
  24. package/dist/chunk-PHYQSCP6.cjs +54 -0
  25. package/dist/chunk-PHYQSCP6.cjs.map +1 -0
  26. package/dist/{chunk-DG3CXR24.js → chunk-PYAK7XXD.js} +3 -3
  27. package/dist/{chunk-DG3CXR24.js.map → chunk-PYAK7XXD.js.map} +1 -1
  28. package/dist/{chunk-JN2CL7MK.js → chunk-WEGKY2LY.js} +22 -4
  29. package/dist/chunk-WEGKY2LY.js.map +1 -0
  30. package/dist/{chunk-SCYLVQWA.cjs → chunk-X6HEWIYP.cjs} +11 -11
  31. package/dist/{chunk-SCYLVQWA.cjs.map → chunk-X6HEWIYP.cjs.map} +1 -1
  32. package/dist/{chunk-2Z2GFDRS.cjs → chunk-ZNYBLM4O.cjs} +4 -4
  33. package/dist/{chunk-2Z2GFDRS.cjs.map → chunk-ZNYBLM4O.cjs.map} +1 -1
  34. package/dist/{chunk-BP4LXRC6.cjs → chunk-ZPPMEUGR.cjs} +7 -7
  35. package/dist/{chunk-BP4LXRC6.cjs.map → chunk-ZPPMEUGR.cjs.map} +1 -1
  36. package/dist/components/EmailEditor/index.cjs +14 -12
  37. package/dist/components/EmailEditor/index.js +5 -3
  38. package/dist/components/RichTextEditor/index.cjs +6 -4
  39. package/dist/components/RichTextEditor/index.js +3 -1
  40. package/dist/components/Textarea/index.cjs +7 -5
  41. package/dist/components/Textarea/index.js +4 -2
  42. package/dist/esheet.cjs +614 -117
  43. package/dist/esheet.cjs.map +1 -1
  44. package/dist/esheet.d.cts +209 -2
  45. package/dist/esheet.d.ts +209 -2
  46. package/dist/esheet.js +573 -95
  47. package/dist/esheet.js.map +1 -1
  48. package/dist/hooks/index.cjs +41 -40
  49. package/dist/hooks/index.js +2 -1
  50. package/dist/index.cjs +110 -109
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.js +11 -9
  53. package/dist/index.js.map +1 -1
  54. package/dist/styles.css +1 -1
  55. package/dist/tailwind-preset.cjs +4 -4
  56. package/dist/tailwind-preset.js +1 -1
  57. package/package.json +1 -1
  58. package/dist/chunk-FQUSSPXX.cjs.map +0 -1
  59. package/dist/chunk-INMDLVUB.cjs.map +0 -1
  60. package/dist/chunk-JN2CL7MK.js.map +0 -1
  61. package/dist/chunk-KIVEYQGA.cjs.map +0 -1
  62. package/dist/chunk-SNAMNKFH.js.map +0 -1
  63. package/dist/chunk-YG7BR76Y.js.map +0 -1
package/dist/esheet.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import './chunk-GADJASST.js';
2
2
  import { parseEncounterMdy, DEFAULT_ENCOUNTER_VISIT_DEFINITION, createEncounterFormDefinition, ENCOUNTER_DOCUMENT_FIELD_ID, encounterDocumentResponse, createEncounterSnapshot, updateEncounterDocumentBody, getEncounterSectionFieldId, getEncounterMdyFields, getEncounterSectionFieldIds, getEncounterDocumentBody, renderEncounterMdy, validateEncounterVitals, validateEncounterPlan, validateEncounterConditions, validAssessmentItem, isEncounterDate, validateEncounterAllergyValue, validateEncounterMedicationValue, validateEncounterAssessment, validateEncounterResponses, getEncounterFieldId, validateEncounterDefinition } from './chunk-QNJEDBIN.js';
3
3
  export { DEFAULT_ENCOUNTER_VISIT_DEFINITION, ENCOUNTER_DOCUMENT_FIELD_ID, EXAMPLE_ENCOUNTER_VISIT_RESPONSES, createEncounterFormDefinition, createEncounterMdy, createEncounterSnapshot, encounterDocumentResponse, getEncounterConcerns, getEncounterDocumentBody, getEncounterFieldId, getEncounterMdyFields, getEncounterSectionFieldId, getEncounterSectionFieldIds, parseEncounterAssessment, parseEncounterConditions, parseEncounterMdy, parseEncounterPlan, parseEncounterVitals, refreshEncounterMdyLinks, renderEncounterMdy, updateEncounterDocumentBody, validAssessmentItem, validateEncounterAllergyValue, validateEncounterAssessment, validateEncounterConditions, validateEncounterDefinition, validateEncounterMedicationValue, validateEncounterPlan, validateEncounterResponses, validateEncounterVitals } from './chunk-QNJEDBIN.js';
4
- import { useCodeLookupConfig, CodeLookupContext, Assessment, ConditionEditor, ALLERGY_TYPE_LABELS, lookupToMedicationFields, doseFormToQuantityUnit, DateRangePicker, parseSig, normalizeConditionCodingSystem, MedicationReconciliation, AllergyManager } from './chunk-DG3CXR24.js';
4
+ import { useCodeLookupConfig, CodeLookupContext, Assessment, ConditionEditor, ALLERGY_TYPE_LABELS, lookupToMedicationFields, doseFormToQuantityUnit, DateRangePicker, parseSig, normalizeConditionCodingSystem, MedicationReconciliation, AllergyManager } from './chunk-PYAK7XXD.js';
5
5
  import { ProblemList, currentAssertion } from './chunk-KSQQBFDQ.js';
6
6
  import './chunk-YFWDS6MX.js';
7
7
  import { ButtonGroup } from './chunk-BBLEGF5L.js';
8
8
  import { RadioGroup, Radio } from './chunk-EPSB4WTL.js';
9
9
  import { Label } from './chunk-I3WGZPI6.js';
10
10
  import { DateInput } from './chunk-I6Y2KLTZ.js';
11
- import { TextAreaRich, Textarea } from './chunk-L52HKEUD.js';
12
- import './chunk-JN2CL7MK.js';
11
+ import { TextAreaRich, Textarea } from './chunk-NAQMIFUF.js';
12
+ import './chunk-WEGKY2LY.js';
13
13
  import { MessagesProvider, useMessages } from './chunk-5YOT4DVH.js';
14
14
  import './chunk-OQWZYKJM.js';
15
15
  import { Select } from './chunk-VCI2Y7ST.js';
@@ -19,9 +19,10 @@ import './chunk-QYSUYB3S.js';
19
19
  import { Checkbox } from './chunk-753Z3NXL.js';
20
20
  import './chunk-TWQSMUE6.js';
21
21
  import './chunk-6NZXWKUE.js';
22
- import { RequiredMark, Input } from './chunk-3IMM2PM4.js';
22
+ import { Input, RequiredMark } from './chunk-3IMM2PM4.js';
23
23
  import './chunk-YUMOM3KE.js';
24
24
  import './chunk-HNXLLDPN.js';
25
+ import './chunk-HTZ27DGZ.js';
25
26
  import './chunk-4SMSH4OY.js';
26
27
  import { useEscapeKey } from './chunk-T4ME7QCT.js';
27
28
  import './chunk-62ZOA6PX.js';
@@ -45,7 +46,8 @@ import { EsheetRenderer } from '@esheet/renderer';
45
46
  export { EsheetRenderer } from '@esheet/renderer';
46
47
  import DOMPurify from 'dompurify';
47
48
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
48
- import { getFieldComponent, SectionField, registerFieldComponents, registerCustomFieldTypes } from '@esheet/fields';
49
+ import { getFieldComponent, registerCustomFieldTypes, SectionField, registerFieldComponents } from '@esheet/fields';
50
+ import { stringify } from 'yaml';
49
51
  export { EsheetBuilder } from '@esheet/builder';
50
52
 
51
53
  var HEADINGS = "h1,h2,h3,h4,h5,h6";
@@ -258,6 +260,31 @@ function EncounterMdyView({ source }) {
258
260
  }
259
261
  );
260
262
  }
263
+ var EsheetSectionContext = React11.createContext(false);
264
+ function EsheetSectionField(props) {
265
+ const inDocument = React11.useContext(EsheetSectionContext);
266
+ const { field, isPreview, isRequired, isSoftRequired, nestedChildren } = props;
267
+ const def = field.definition;
268
+ if (!inDocument || !isPreview) return /* @__PURE__ */ jsx(SectionField, { ...props });
269
+ const title = def.title || "Section";
270
+ return /* @__PURE__ */ jsxs("div", { className: "section-field-preview grid gap-1.5", children: [
271
+ /* @__PURE__ */ jsx("h3", { className: "text-foreground m-0 flex items-center gap-2 text-sm font-medium", children: /* @__PURE__ */ jsxs("span", { className: "min-w-0 break-words", children: [
272
+ title,
273
+ (isRequired || isSoftRequired) && /* @__PURE__ */ jsx(
274
+ RequiredMark,
275
+ {
276
+ variant: isSoftRequired && !isRequired ? "warning" : void 0
277
+ }
278
+ )
279
+ ] }) }),
280
+ nestedChildren
281
+ ] });
282
+ }
283
+ function registerEsheetSectionFieldType() {
284
+ const section = getFieldComponent("section");
285
+ if (section === void 0 || section === SectionField)
286
+ registerFieldComponents({ section: EsheetSectionField });
287
+ }
261
288
  var measurements = [
262
289
  { key: "pulse", label: "Pulse", unit: "bpm" },
263
290
  { key: "respiratoryRate", label: "Respiratory rate", unit: "/min" },
@@ -1167,27 +1194,29 @@ function EncounterAssessmentField({
1167
1194
  ) });
1168
1195
  }
1169
1196
 
1170
- // src/components/EncounterVisit/narrativeFindings.ts
1171
- var FINDINGS_ATTRIBUTE = "encounterNarrativeFindings";
1172
- var FINDINGS_TEXT_ATTRIBUTE = "encounterNarrativeFindingsText";
1173
- var HTML_ATTRIBUTE = "encounterNarrativeHtml";
1174
- var TEXT_ATTRIBUTE = "encounterNarrativeText";
1197
+ // src/esheet-fields/narrativeFindings.ts
1198
+ var NARRATIVE_ATTRIBUTES = {
1199
+ html: "narrativeHtml",
1200
+ text: "narrativeText",
1201
+ findings: "narrativeFindings",
1202
+ findingsText: "narrativeFindingsText"
1203
+ };
1175
1204
  function escapedTextHtml(text) {
1176
1205
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\n/g, "<br>");
1177
1206
  }
1178
1207
  function hasNarrativeTagsOnly(html) {
1179
1208
  return !html.replace(/<\/?(?:p|div|strong|b|em|i|u|ul|ol|li)\s*>|<br\s*\/?>/gi, "").includes("<");
1180
1209
  }
1181
- function appendedRichAttributes(response, answer, finding) {
1210
+ function appendedRichAttributes(response, answer, finding, attributes) {
1182
1211
  const previous = response?.answer ?? "";
1183
- const html = response?.attributes?.[HTML_ATTRIBUTE];
1184
- if (answer === previous || response?.attributes?.[TEXT_ATTRIBUTE] !== previous || typeof html !== "string" || !hasNarrativeTagsOnly(html))
1212
+ const html = response?.attributes?.[attributes.html];
1213
+ if (answer === previous || response?.attributes?.[attributes.text] !== previous || typeof html !== "string" || !hasNarrativeTagsOnly(html))
1185
1214
  return {};
1186
1215
  const aligned = previous.endsWith("\n") ? html.replace(/<br\s*\/?>((?:<\/(?:p|div|ul|ol|li)\s*>|\s)*)$/i, "$1") : html;
1187
1216
  const next = `${previous.trim() ? aligned : ""}<p>${escapedTextHtml(finding.text)}${finding.text.endsWith("\n") ? "<br>" : ""}</p>`;
1188
- return { [HTML_ATTRIBUTE]: next, [TEXT_ATTRIBUTE]: answer };
1217
+ return { [attributes.html]: next, [attributes.text]: answer };
1189
1218
  }
1190
- function validEncounterNarrativeFindings(value) {
1219
+ function validNarrativeFindings(value) {
1191
1220
  if (!Array.isArray(value)) return [];
1192
1221
  const ids = /* @__PURE__ */ new Set();
1193
1222
  return value.flatMap((finding) => {
@@ -1212,7 +1241,7 @@ function validEncounterNarrativeFindings(value) {
1212
1241
  ];
1213
1242
  });
1214
1243
  }
1215
- function hasEncounterNarrativeFindingText(answer, text) {
1244
+ function hasNarrativeFindingText(answer, text) {
1216
1245
  const normalize = (value) => value.replace(/\r\n?/g, "\n").trim();
1217
1246
  return `
1218
1247
  ${normalize(answer)}
@@ -1220,46 +1249,39 @@ ${normalize(answer)}
1220
1249
  ${normalize(text)}
1221
1250
  `);
1222
1251
  }
1223
- function getEncounterNarrativeFindings(response) {
1252
+ function getNarrativeFindings(response, attributes = NARRATIVE_ATTRIBUTES) {
1224
1253
  const answer = response?.answer ?? "";
1225
- if (response?.attributes?.[FINDINGS_TEXT_ATTRIBUTE] !== answer) return [];
1226
- const selected = response.attributes[FINDINGS_ATTRIBUTE];
1254
+ if (response?.attributes?.[attributes.findingsText] !== answer) return [];
1255
+ const selected = response.attributes[attributes.findings];
1227
1256
  if (typeof selected !== "string") return [];
1228
1257
  try {
1229
- return validEncounterNarrativeFindings(JSON.parse(selected)).filter(
1230
- (finding) => hasEncounterNarrativeFindingText(answer, finding.text)
1258
+ return validNarrativeFindings(JSON.parse(selected)).filter(
1259
+ (finding) => hasNarrativeFindingText(answer, finding.text)
1231
1260
  );
1232
1261
  } catch {
1233
1262
  return [];
1234
1263
  }
1235
1264
  }
1236
- function applyEncounterNarrativeFinding(response, finding) {
1237
- const validated = validEncounterNarrativeFindings([finding])[0];
1265
+ function applyNarrativeFinding(response, finding, attributes = NARRATIVE_ATTRIBUTES) {
1266
+ const validated = validNarrativeFindings([finding])[0];
1238
1267
  if (!validated) return response ?? {};
1239
- const selected = getEncounterNarrativeFindings(response);
1268
+ const selected = getNarrativeFindings(response, attributes);
1240
1269
  if (selected.some((item) => item.id === validated.id)) return response ?? {};
1241
1270
  const previousAnswer = response?.answer ?? "";
1242
- const answer = hasEncounterNarrativeFindingText(
1243
- previousAnswer,
1244
- validated.text
1245
- ) ? previousAnswer : `${previousAnswer.trim() ? `${previousAnswer}${previousAnswer.endsWith("\n") ? "" : "\n"}` : ""}${validated.text}`;
1271
+ const answer = hasNarrativeFindingText(previousAnswer, validated.text) ? previousAnswer : `${previousAnswer.trim() ? `${previousAnswer}${previousAnswer.endsWith("\n") ? "" : "\n"}` : ""}${validated.text}`;
1246
1272
  return {
1247
1273
  ...response,
1248
1274
  answer,
1249
1275
  _ai: false,
1250
1276
  attributes: {
1251
1277
  ...response?.attributes,
1252
- ...appendedRichAttributes(response, answer, validated),
1253
- [FINDINGS_ATTRIBUTE]: JSON.stringify([...selected, validated]),
1254
- [FINDINGS_TEXT_ATTRIBUTE]: answer
1278
+ ...appendedRichAttributes(response, answer, validated, attributes),
1279
+ [attributes.findings]: JSON.stringify([...selected, validated]),
1280
+ [attributes.findingsText]: answer
1255
1281
  }
1256
1282
  };
1257
1283
  }
1258
- var HTML_ATTRIBUTE2 = "encounterNarrativeHtml";
1259
- var TEXT_ATTRIBUTE2 = "encounterNarrativeText";
1260
- var FINDINGS_ATTRIBUTE2 = "encounterNarrativeFindings";
1261
- var FINDINGS_TEXT_ATTRIBUTE2 = "encounterNarrativeFindingsText";
1262
- function sanitizeEncounterNarrativeHtml(html) {
1284
+ function sanitizeNarrativeHtml(html) {
1263
1285
  return DOMPurify.sanitize(html, {
1264
1286
  ALLOWED_TAGS: [
1265
1287
  "p",
@@ -1286,9 +1308,9 @@ function sanitizeEncounterNarrativeHtml(html) {
1286
1308
  function textToHtml(text) {
1287
1309
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\n/g, "<br>");
1288
1310
  }
1289
- function encounterNarrativeText(html) {
1311
+ function narrativeText(html) {
1290
1312
  const documentBody = document.createElement("div");
1291
- documentBody.innerHTML = sanitizeEncounterNarrativeHtml(html);
1313
+ documentBody.innerHTML = sanitizeNarrativeHtml(html);
1292
1314
  let text = "";
1293
1315
  const lineBreak = () => {
1294
1316
  if (text && !text.endsWith("\n")) text += "\n";
@@ -1326,12 +1348,12 @@ function encounterNarrativeText(html) {
1326
1348
  documentBody.childNodes.forEach(visit);
1327
1349
  return text.replace(/\u00a0/g, " ").replace(/\n$/, "");
1328
1350
  }
1329
- function responseHtml(response) {
1351
+ function responseHtml(response, attributes) {
1330
1352
  const answer = response?.answer ?? "";
1331
- const attributes = response?.attributes;
1332
- if (typeof window !== "undefined" && attributes?.[TEXT_ATTRIBUTE2] === answer && typeof attributes[HTML_ATTRIBUTE2] === "string") {
1333
- const html = sanitizeEncounterNarrativeHtml(attributes[HTML_ATTRIBUTE2]);
1334
- if (encounterNarrativeText(html) === answer) return html;
1353
+ const responseAttributes = response?.attributes;
1354
+ if (typeof window !== "undefined" && responseAttributes?.[attributes.text] === answer && typeof responseAttributes[attributes.html] === "string") {
1355
+ const html = sanitizeNarrativeHtml(responseAttributes[attributes.html]);
1356
+ if (narrativeText(html) === answer) return html;
1335
1357
  }
1336
1358
  return textToHtml(answer);
1337
1359
  }
@@ -1348,7 +1370,7 @@ function findingShortcuts(findings) {
1348
1370
  }
1349
1371
  return shortcuts;
1350
1372
  }
1351
- function EncounterNarrativeField({
1373
+ function EsheetNarrativeField({
1352
1374
  field,
1353
1375
  response,
1354
1376
  isPreview,
@@ -1356,7 +1378,11 @@ function EncounterNarrativeField({
1356
1378
  isReadOnly,
1357
1379
  isRequired,
1358
1380
  isSoftRequired,
1359
- onResponse
1381
+ onResponse,
1382
+ attributes = NARRATIVE_ATTRIBUTES,
1383
+ findingsKey = "narrativeFindings",
1384
+ hideLabelKey = "narrativeHideLabel",
1385
+ slot = "esheet-narrative"
1360
1386
  }) {
1361
1387
  const m = useMessages();
1362
1388
  const editorRef = React11.useRef(null);
@@ -1366,9 +1392,12 @@ function EncounterNarrativeField({
1366
1392
  const definition = field.definition;
1367
1393
  const question = definition.question || m("Narrative");
1368
1394
  const readOnly = !isPreview || !isEnabled || isReadOnly;
1369
- const html = React11.useMemo(() => responseHtml(response), [response]);
1370
- const findings = validEncounterNarrativeFindings(
1371
- definition._sourceData?.encounterFindings
1395
+ const html = React11.useMemo(
1396
+ () => responseHtml(response, attributes),
1397
+ [response, attributes]
1398
+ );
1399
+ const findings = validNarrativeFindings(
1400
+ definition._sourceData?.[findingsKey]
1372
1401
  );
1373
1402
  const shortcuts = findingShortcuts(findings);
1374
1403
  const macros = findings.length ? [
@@ -1384,18 +1413,23 @@ function EncounterNarrativeField({
1384
1413
  }
1385
1414
  ] : void 0;
1386
1415
  const publish = (value, previous, selected) => {
1387
- const sanitized = sanitizeEncounterNarrativeHtml(value);
1388
- const answer = encounterNarrativeText(sanitized);
1389
- const attributes = {
1416
+ const sanitized = sanitizeNarrativeHtml(value);
1417
+ const answer = narrativeText(sanitized);
1418
+ const nextAttributes = {
1390
1419
  ...previous?.attributes,
1391
- [HTML_ATTRIBUTE2]: sanitized,
1392
- [TEXT_ATTRIBUTE2]: answer,
1393
- ...selected.length || previous?.attributes?.[FINDINGS_ATTRIBUTE2] !== void 0 ? {
1394
- [FINDINGS_ATTRIBUTE2]: JSON.stringify(selected),
1395
- [FINDINGS_TEXT_ATTRIBUTE2]: answer
1420
+ [attributes.html]: sanitized,
1421
+ [attributes.text]: answer,
1422
+ ...selected.length || previous?.attributes?.[attributes.findings] !== void 0 ? {
1423
+ [attributes.findings]: JSON.stringify(selected),
1424
+ [attributes.findingsText]: answer
1396
1425
  } : {}
1397
1426
  };
1398
- const next = { ...previous, answer, _ai: false, attributes };
1427
+ const next = {
1428
+ ...previous,
1429
+ answer,
1430
+ _ai: false,
1431
+ attributes: nextAttributes
1432
+ };
1399
1433
  delete next.selected;
1400
1434
  responseRef.current = next;
1401
1435
  onResponse(next);
@@ -1409,12 +1443,12 @@ function EncounterNarrativeField({
1409
1443
  String(isRequired || isSoftRequired)
1410
1444
  );
1411
1445
  }, [isRequired, isSoftRequired, labelId, readOnly]);
1412
- return /* @__PURE__ */ jsxs("div", { "data-slot": "encounter-narrative", className: "min-w-0", children: [
1446
+ return /* @__PURE__ */ jsxs("div", { "data-slot": slot, className: "min-w-0", children: [
1413
1447
  /* @__PURE__ */ jsxs(
1414
1448
  "div",
1415
1449
  {
1416
1450
  id: labelId,
1417
- className: definition._sourceData?.encounterHideLabel ? "sr-only" : "text-muted-foreground text-sm font-medium",
1451
+ className: definition._sourceData?.[hideLabelKey] ? "sr-only" : "text-muted-foreground text-sm font-medium",
1418
1452
  children: [
1419
1453
  question,
1420
1454
  definition.unit && ` (${definition.unit})`,
@@ -1429,14 +1463,14 @@ function EncounterNarrativeField({
1429
1463
  value: html,
1430
1464
  onChange: (value) => {
1431
1465
  if (readOnly) return;
1432
- const sanitized = sanitizeEncounterNarrativeHtml(value);
1433
- const answer = encounterNarrativeText(sanitized);
1466
+ const sanitized = sanitizeNarrativeHtml(value);
1467
+ const answer = narrativeText(sanitized);
1434
1468
  const previous = responseRef.current;
1435
1469
  publish(
1436
1470
  sanitized,
1437
1471
  previous,
1438
- getEncounterNarrativeFindings(previous).filter(
1439
- (finding) => hasEncounterNarrativeFindingText(answer, finding.text)
1472
+ getNarrativeFindings(previous, attributes).filter(
1473
+ (finding) => hasNarrativeFindingText(answer, finding.text)
1440
1474
  )
1441
1475
  );
1442
1476
  },
@@ -1444,14 +1478,11 @@ function EncounterNarrativeField({
1444
1478
  const finding = findings.find((item) => item.id === macro.id);
1445
1479
  if (!finding || readOnly) return true;
1446
1480
  const previous = responseRef.current;
1447
- const next = applyEncounterNarrativeFinding(previous, finding);
1481
+ const next = applyNarrativeFinding(previous, finding, attributes);
1448
1482
  if (next === previous) return true;
1449
1483
  const answer = previous?.answer ?? "";
1450
- const nextHtml = hasEncounterNarrativeFindingText(
1451
- answer,
1452
- finding.text
1453
- ) ? responseHtml(previous) : `${answer.trim() ? responseHtml(previous) : ""}<p>${textToHtml(finding.text)}</p>`;
1454
- publish(nextHtml, previous, getEncounterNarrativeFindings(next));
1484
+ const nextHtml = hasNarrativeFindingText(answer, finding.text) ? responseHtml(previous, attributes) : `${answer.trim() ? responseHtml(previous, attributes) : ""}<p>${textToHtml(finding.text)}</p>`;
1485
+ publish(nextHtml, previous, getNarrativeFindings(next, attributes));
1455
1486
  return true;
1456
1487
  },
1457
1488
  "aria-label": question,
@@ -1489,12 +1520,53 @@ function EncounterNarrativeField({
1489
1520
  showToolbar: m("Show toolbar"),
1490
1521
  hideToolbar: m("Hide toolbar")
1491
1522
  },
1492
- sanitizeHtml: sanitizeEncounterNarrativeHtml,
1523
+ sanitizeHtml: sanitizeNarrativeHtml,
1493
1524
  className: "mt-1"
1494
1525
  }
1495
1526
  )
1496
1527
  ] });
1497
1528
  }
1529
+ function registerEsheetNarrativeFieldType() {
1530
+ if (getFieldComponent("esheetNarrative")) return;
1531
+ registerCustomFieldTypes({
1532
+ esheetNarrative: {
1533
+ label: "Narrative",
1534
+ category: "rich",
1535
+ answerType: "text",
1536
+ hasOptions: false,
1537
+ hasMatrix: false,
1538
+ defaultProps: { question: "Narrative" },
1539
+ component: EsheetNarrativeField
1540
+ }
1541
+ });
1542
+ }
1543
+
1544
+ // src/components/EncounterVisit/narrativeFindings.ts
1545
+ var ENCOUNTER_NARRATIVE_ATTRIBUTES = {
1546
+ html: "encounterNarrativeHtml",
1547
+ text: "encounterNarrativeText",
1548
+ findings: "encounterNarrativeFindings",
1549
+ findingsText: "encounterNarrativeFindingsText"
1550
+ };
1551
+ function applyEncounterNarrativeFinding(response, finding) {
1552
+ return applyNarrativeFinding(
1553
+ response,
1554
+ finding,
1555
+ ENCOUNTER_NARRATIVE_ATTRIBUTES
1556
+ );
1557
+ }
1558
+ function EncounterNarrativeField(props) {
1559
+ return /* @__PURE__ */ jsx(
1560
+ EsheetNarrativeField,
1561
+ {
1562
+ ...props,
1563
+ attributes: ENCOUNTER_NARRATIVE_ATTRIBUTES,
1564
+ findingsKey: "encounterFindings",
1565
+ hideLabelKey: "encounterHideLabel",
1566
+ slot: "encounter-narrative"
1567
+ }
1568
+ );
1569
+ }
1498
1570
  function readValue(answer) {
1499
1571
  if (!answer) return { value: { medications: [] } };
1500
1572
  try {
@@ -2610,32 +2682,10 @@ function EncounterPlanField({
2610
2682
  !readOnly && requireCoding && !codeLookup && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: m("Coded order search is unavailable.") })
2611
2683
  ] });
2612
2684
  }
2613
- var EncounterSectionContext = React11.createContext(false);
2614
- function EncounterSectionField(props) {
2615
- const inEncounter = React11.useContext(EncounterSectionContext);
2616
- const { field, isPreview, isRequired, isSoftRequired, nestedChildren } = props;
2617
- const def = field.definition;
2618
- if (!inEncounter || !isPreview) return /* @__PURE__ */ jsx(SectionField, { ...props });
2619
- const title = def.title || "Section";
2620
- return /* @__PURE__ */ jsxs("div", { className: "section-field-preview grid gap-1.5", children: [
2621
- /* @__PURE__ */ jsx("h3", { className: "text-foreground m-0 flex items-center gap-2 text-sm font-medium", children: /* @__PURE__ */ jsxs("span", { className: "min-w-0 break-words", children: [
2622
- title,
2623
- (isRequired || isSoftRequired) && /* @__PURE__ */ jsx(
2624
- RequiredMark,
2625
- {
2626
- variant: isSoftRequired && !isRequired ? "warning" : void 0
2627
- }
2628
- )
2629
- ] }) }),
2630
- nestedChildren
2631
- ] });
2632
- }
2633
2685
 
2634
2686
  // src/components/EncounterVisit/fields.ts
2635
2687
  function registerEncounterFieldTypes() {
2636
- const section = getFieldComponent("section");
2637
- if (section === void 0 || section === SectionField)
2638
- registerFieldComponents({ section: EncounterSectionField });
2688
+ registerEsheetSectionFieldType();
2639
2689
  const clinicalFields = {
2640
2690
  encounterMedications: {
2641
2691
  label: "Encounter medications",
@@ -4056,7 +4106,7 @@ var EncounterVisitSession = React11.forwardRef(function EncounterVisitSession2({
4056
4106
  }
4057
4107
  )
4058
4108
  ] }),
4059
- /* @__PURE__ */ jsx(EncounterSectionContext.Provider, { value: true, children: /* @__PURE__ */ jsx(
4109
+ /* @__PURE__ */ jsx(EsheetSectionContext.Provider, { value: true, children: /* @__PURE__ */ jsx(
4060
4110
  EsheetRenderer,
4061
4111
  {
4062
4112
  ref: renderer,
@@ -4114,6 +4164,434 @@ var EncounterVisitSession = React11.forwardRef(function EncounterVisitSession2({
4114
4164
  }
4115
4165
  );
4116
4166
  });
4167
+ function getRichTextEsheetFieldId(sectionId) {
4168
+ return `richtext:${encodeURIComponent(sectionId)}:value`;
4169
+ }
4170
+ function getRichTextEsheetSectionFieldId(sectionId) {
4171
+ return `richtext:${encodeURIComponent(sectionId)}:section`;
4172
+ }
4173
+ function nonempty(value) {
4174
+ return typeof value === "string" && value.trim().length > 0;
4175
+ }
4176
+ function invalid(message) {
4177
+ throw new Error(`Invalid RichTextEsheet definition: ${message}`);
4178
+ }
4179
+ function validateRichTextEsheetDefinition(definition) {
4180
+ if (!definition || typeof definition !== "object")
4181
+ invalid("expected an object");
4182
+ if (!nonempty(definition.id) || !nonempty(definition.title))
4183
+ invalid("id and title must be nonempty strings");
4184
+ if (!Array.isArray(definition.sections) || definition.sections.length === 0)
4185
+ invalid("at least one section is required");
4186
+ const sectionIds = /* @__PURE__ */ new Set();
4187
+ for (const section of definition.sections) {
4188
+ if (!section || typeof section !== "object")
4189
+ invalid("expected a section object");
4190
+ if (!nonempty(section.id) || !nonempty(section.title))
4191
+ invalid("each section requires an id and title");
4192
+ if (sectionIds.has(section.id))
4193
+ invalid(`duplicate section id ${section.id}`);
4194
+ sectionIds.add(section.id);
4195
+ if (section.description !== void 0 && typeof section.description !== "string")
4196
+ invalid(`invalid description in ${section.id}`);
4197
+ if (section.required !== void 0 && typeof section.required !== "boolean")
4198
+ invalid(`required must be boolean in ${section.id}`);
4199
+ if (section.fieldType !== void 0 && !nonempty(section.fieldType))
4200
+ invalid(`fieldType must be a nonempty string in ${section.id}`);
4201
+ if (section.findings === void 0) continue;
4202
+ if (!Array.isArray(section.findings))
4203
+ invalid(`findings must be an array in ${section.id}`);
4204
+ const findingIds = /* @__PURE__ */ new Set();
4205
+ for (const finding of section.findings) {
4206
+ if (!finding || typeof finding !== "object" || !nonempty(finding.id) || !nonempty(finding.label) || !nonempty(finding.text))
4207
+ invalid(`findings require id, label and text in ${section.id}`);
4208
+ if (findingIds.has(finding.id))
4209
+ invalid(`duplicate finding id ${finding.id} in ${section.id}`);
4210
+ findingIds.add(finding.id);
4211
+ }
4212
+ }
4213
+ }
4214
+ function partyFindings(parties) {
4215
+ return (parties ?? []).map((party) => ({
4216
+ // Content-derived identity: editing or reordering a party yields a new
4217
+ // macro id, so the updated text stays insertable after an earlier
4218
+ // version was applied (selection dedupe is by id).
4219
+ id: `party:${encodeURIComponent(party.role)}:${encodeURIComponent(
4220
+ party.name
4221
+ )}:${encodeURIComponent(party.detail ?? "")}`,
4222
+ label: `${party.role}: ${party.name}`,
4223
+ text: party.detail ? `${party.name} (${party.detail})` : party.name
4224
+ }));
4225
+ }
4226
+ function createRichTextEsheetFormDefinition(definition, parties) {
4227
+ validateRichTextEsheetDefinition(definition);
4228
+ const partyMacros = partyFindings(parties);
4229
+ return {
4230
+ id: definition.id,
4231
+ title: definition.title,
4232
+ pages: [
4233
+ {
4234
+ id: `${definition.id}:document`,
4235
+ title: definition.title,
4236
+ fields: definition.sections.map((section) => {
4237
+ const fields = [];
4238
+ if (section.description) {
4239
+ fields.push({
4240
+ id: `${getRichTextEsheetSectionFieldId(section.id)}:description`,
4241
+ fieldType: "display",
4242
+ content: section.description,
4243
+ width: "full"
4244
+ });
4245
+ }
4246
+ const fieldType = section.fieldType ?? "esheetNarrative";
4247
+ fields.push({
4248
+ id: getRichTextEsheetFieldId(section.id),
4249
+ fieldType,
4250
+ question: section.title,
4251
+ required: section.required,
4252
+ width: "full",
4253
+ // Keyed off the resolved type: an explicit 'esheetNarrative'
4254
+ // override keeps its findings, macros and hidden label.
4255
+ _sourceData: fieldType === "esheetNarrative" ? {
4256
+ narrativeHideLabel: true,
4257
+ narrativeFindings: [
4258
+ ...section.findings ?? [],
4259
+ ...partyMacros
4260
+ ]
4261
+ } : void 0
4262
+ });
4263
+ return {
4264
+ id: getRichTextEsheetSectionFieldId(section.id),
4265
+ fieldType: "section",
4266
+ title: section.title,
4267
+ sectionCollapse: "expanded",
4268
+ width: "full",
4269
+ fields
4270
+ };
4271
+ })
4272
+ }
4273
+ ]
4274
+ };
4275
+ }
4276
+ function record(value) {
4277
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4278
+ }
4279
+ function hasValue(value) {
4280
+ if (typeof value === "string") return value.trim().length > 0;
4281
+ if (typeof value === "number" || typeof value === "boolean") return true;
4282
+ if (Array.isArray(value)) return value.some(hasValue);
4283
+ if (record(value)) return Object.values(value).some(hasValue);
4284
+ return false;
4285
+ }
4286
+ function valueText(value) {
4287
+ if (typeof value === "string") return value.trim();
4288
+ if (typeof value === "number" || typeof value === "boolean")
4289
+ return String(value);
4290
+ if (Array.isArray(value))
4291
+ return value.map(valueText).filter(Boolean).join(", ");
4292
+ return "";
4293
+ }
4294
+ function selectedText(selected) {
4295
+ if (Array.isArray(selected))
4296
+ return selected.map(selectedText).filter(Boolean).join(", ");
4297
+ if (!record(selected)) return "";
4298
+ if ("value" in selected) {
4299
+ if (!hasValue(selected.value)) return "";
4300
+ const text = valueText(selected.value);
4301
+ const label = typeof selected.label === "string" ? selected.label.trim() : "";
4302
+ return label && label !== text ? `${label}: ${text}` : text;
4303
+ }
4304
+ return Object.values(selected).map(selectedText).filter(Boolean).join(", ");
4305
+ }
4306
+ function sectionText(responses, sectionId) {
4307
+ const response = responses[getRichTextEsheetFieldId(sectionId)];
4308
+ return valueText(response?.answer) || selectedText(response?.selected);
4309
+ }
4310
+ function markdownText(value) {
4311
+ return value.replace(/&/g, "&amp;").replace(/[\\[\]`*_~<>]/g, "\\$&");
4312
+ }
4313
+ function markdownHeading(value) {
4314
+ return markdownText(value).replace(/\r?\n/g, " \xB7 ");
4315
+ }
4316
+ function markdownBody(value) {
4317
+ return value.split(/\r?\n/).map(
4318
+ (line) => markdownText(line.trim()).replace(/^([#+-])/, "\\$1").replace(/^(\d+)([.)])/, "$1\\$2")
4319
+ ).filter(Boolean).join("\n\n");
4320
+ }
4321
+ function createRichTextEsheetSnapshot(definition, responses, parties) {
4322
+ validateRichTextEsheetDefinition(definition);
4323
+ const documentedSections = definition.sections.filter((section) => sectionText(responses, section.id).length > 0).map((section) => section.id);
4324
+ const errors = definition.sections.filter(
4325
+ (section) => section.required && !documentedSections.includes(section.id)
4326
+ ).map((section) => ({
4327
+ sectionId: section.id,
4328
+ message: `${section.title} is required.`
4329
+ }));
4330
+ const frontMatter = {
4331
+ richTextEsheet: { id: definition.id, title: definition.title },
4332
+ ...parties?.length ? { parties } : {}
4333
+ };
4334
+ const body = definition.sections.filter((section) => documentedSections.includes(section.id)).map(
4335
+ (section) => `## ${markdownHeading(section.title)}
4336
+
4337
+ ${markdownBody(
4338
+ sectionText(responses, section.id)
4339
+ )}`
4340
+ ).join("\n\n");
4341
+ const mdy = `---
4342
+ ${stringify(frontMatter)}---
4343
+
4344
+ # ${markdownHeading(
4345
+ definition.title
4346
+ )}
4347
+ ${body ? `
4348
+ ${body}
4349
+ ` : ""}`;
4350
+ return { definition, responses, documentedSections, errors, mdy };
4351
+ }
4352
+ var RichTextEsheet = React11.forwardRef(function RichTextEsheet2({ messages, ...props }, ref) {
4353
+ return /* @__PURE__ */ jsx(MessagesProvider, { messages, children: /* @__PURE__ */ jsx(RichTextEsheetSession, { ...props, ref }, props.definition.id) });
4354
+ });
4355
+ function ensureFields2() {
4356
+ registerEsheetSectionFieldType();
4357
+ registerEsheetNarrativeFieldType();
4358
+ }
4359
+ var esheetDefaultsSettled2 = false;
4360
+ var RichTextEsheetSession = React11.forwardRef(function RichTextEsheetSession2({
4361
+ definition,
4362
+ parties,
4363
+ initialResponses,
4364
+ onChange,
4365
+ onSubmit,
4366
+ readOnly = false,
4367
+ className,
4368
+ ...props
4369
+ }, ref) {
4370
+ ensureFields2();
4371
+ const [fieldsSettled, setFieldsSettled] = React11.useState(
4372
+ () => esheetDefaultsSettled2
4373
+ );
4374
+ React11.useLayoutEffect(() => {
4375
+ if (!esheetDefaultsSettled2) {
4376
+ esheetDefaultsSettled2 = true;
4377
+ ensureFields2();
4378
+ }
4379
+ setFieldsSettled(true);
4380
+ }, []);
4381
+ const m = useMessages();
4382
+ const configJson = JSON.stringify({ definition, parties });
4383
+ const stable = React11.useMemo(
4384
+ () => JSON.parse(configJson),
4385
+ [configJson]
4386
+ );
4387
+ const form = React11.useMemo(
4388
+ () => createRichTextEsheetFormDefinition(stable.definition, stable.parties),
4389
+ [stable]
4390
+ );
4391
+ const [initialSeed] = React11.useState(() => initialResponses ?? {});
4392
+ const [snapshot, setSnapshot] = React11.useState(
4393
+ () => createRichTextEsheetSnapshot(stable.definition, initialSeed, stable.parties)
4394
+ );
4395
+ const [showErrors, setShowErrors] = React11.useState(false);
4396
+ const [saving, setSaving] = React11.useState(false);
4397
+ const submissionPending = React11.useRef(false);
4398
+ const [saveMessage, setSaveMessage] = React11.useState("");
4399
+ const renderer = React11.useRef(null);
4400
+ const root = React11.useRef(null);
4401
+ const unsubscribe = React11.useRef(null);
4402
+ const responses = React11.useRef(initialSeed);
4403
+ const latest = React11.useRef({ stable, onChange, onSubmit, readOnly, m });
4404
+ latest.current = { stable, onChange, onSubmit, readOnly, m };
4405
+ const rendererInput = React11.useMemo(
4406
+ () => ({ definition: form, responses: responses.current }),
4407
+ [form]
4408
+ );
4409
+ const currentSnapshot = React11.useCallback(() => {
4410
+ const { stable: stable2 } = latest.current;
4411
+ return createRichTextEsheetSnapshot(
4412
+ stable2.definition,
4413
+ renderer.current?.getRawResponse() ?? responses.current,
4414
+ stable2.parties
4415
+ );
4416
+ }, []);
4417
+ const focusSection = React11.useCallback((sectionId) => {
4418
+ const fieldId = getRichTextEsheetSectionFieldId(sectionId);
4419
+ const section = Array.from(
4420
+ root.current?.querySelectorAll("[data-field-id]") ?? []
4421
+ ).find((element) => element.dataset.fieldId === fieldId);
4422
+ if (!section) return;
4423
+ section.tabIndex = -1;
4424
+ section.focus({ preventScroll: true });
4425
+ section.scrollIntoView?.({ block: "start", behavior: "auto" });
4426
+ }, []);
4427
+ React11.useImperativeHandle(
4428
+ ref,
4429
+ () => ({ getSnapshot: currentSnapshot, focusSection }),
4430
+ [currentSnapshot, focusSection]
4431
+ );
4432
+ const handleReady = React11.useCallback(() => {
4433
+ const store = renderer.current?.getFormStore();
4434
+ if (!store) return;
4435
+ unsubscribe.current?.();
4436
+ const publish = () => {
4437
+ responses.current = store.getState().responses;
4438
+ const next = currentSnapshot();
4439
+ setSnapshot(next);
4440
+ latest.current.onChange?.(next);
4441
+ };
4442
+ unsubscribe.current = store.subscribe((state, previous) => {
4443
+ if (state.responses !== previous.responses) {
4444
+ setSaveMessage("");
4445
+ publish();
4446
+ }
4447
+ });
4448
+ publish();
4449
+ }, [currentSnapshot]);
4450
+ React11.useLayoutEffect(() => {
4451
+ unsubscribe.current?.();
4452
+ unsubscribe.current = null;
4453
+ }, [rendererInput]);
4454
+ React11.useEffect(() => () => unsubscribe.current?.(), []);
4455
+ const submit = async () => {
4456
+ const context = latest.current;
4457
+ if (!context.onSubmit || context.readOnly || saving || submissionPending.current)
4458
+ return;
4459
+ submissionPending.current = true;
4460
+ try {
4461
+ const next = currentSnapshot();
4462
+ setShowErrors(true);
4463
+ if (next.errors.length > 0) {
4464
+ setSnapshot(next);
4465
+ setSaveMessage(m("Resolve the highlighted sections before saving."));
4466
+ focusSection(next.errors[0].sectionId);
4467
+ return;
4468
+ }
4469
+ renderer.current?.getFormStore().getState().setReadOnly(true);
4470
+ setSaving(true);
4471
+ setSaveMessage("");
4472
+ try {
4473
+ await context.onSubmit(next);
4474
+ setSaveMessage(latest.current.m("Document saved."));
4475
+ } catch (error) {
4476
+ setSaveMessage(
4477
+ error instanceof Error ? error.message : latest.current.m(
4478
+ "Unable to save this document. Your draft is still here."
4479
+ )
4480
+ );
4481
+ } finally {
4482
+ renderer.current?.getFormStore().getState().setReadOnly(latest.current.readOnly);
4483
+ setSaving(false);
4484
+ }
4485
+ } finally {
4486
+ submissionPending.current = false;
4487
+ }
4488
+ };
4489
+ return /* @__PURE__ */ jsxs(
4490
+ "div",
4491
+ {
4492
+ ...props,
4493
+ ref: root,
4494
+ "data-slot": "richtext-esheet",
4495
+ className: cn(
4496
+ "richtext-esheet bg-background text-foreground min-w-0",
4497
+ className
4498
+ ),
4499
+ children: [
4500
+ /* @__PURE__ */ jsxs("header", { className: "flex flex-wrap items-start justify-between gap-2 px-2 pt-3 pb-2 sm:px-3", children: [
4501
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
4502
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: m(stable.definition.title) }),
4503
+ stable.parties && stable.parties.length > 0 && /* @__PURE__ */ jsx("dl", { className: "mt-1 space-y-0.5 text-sm", children: stable.parties.map((party, index) => /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-x-1.5", children: [
4504
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: m(party.role) }),
4505
+ /* @__PURE__ */ jsxs("dd", { dir: "auto", className: "break-words", children: [
4506
+ party.name,
4507
+ party.detail && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
4508
+ " ",
4509
+ "(",
4510
+ party.detail,
4511
+ ")"
4512
+ ] })
4513
+ ] })
4514
+ ] }, index)) })
4515
+ ] }),
4516
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: /* @__PURE__ */ jsx("bdi", { children: m("{count} of {total} sections documented", {
4517
+ count: snapshot.documentedSections.length,
4518
+ total: stable.definition.sections.length
4519
+ }) }) })
4520
+ ] }),
4521
+ /* @__PURE__ */ jsxs(
4522
+ "div",
4523
+ {
4524
+ "data-slot": "richtext-esheet-document",
4525
+ className: "min-w-0 px-2 pt-2 pb-4 sm:px-3",
4526
+ children: [
4527
+ showErrors && snapshot.errors.length > 0 && /* @__PURE__ */ jsxs(
4528
+ "div",
4529
+ {
4530
+ role: "alert",
4531
+ className: "border-destructive/40 bg-destructive/5 mb-4 rounded-lg border p-3 text-sm",
4532
+ children: [
4533
+ /* @__PURE__ */ jsx("p", { className: "font-semibold", children: m("Sections need attention") }),
4534
+ /* @__PURE__ */ jsx("ul", { className: "mt-2 space-y-1", children: snapshot.errors.map((issue) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
4535
+ "button",
4536
+ {
4537
+ type: "button",
4538
+ className: "text-destructive min-h-11 text-start underline underline-offset-2",
4539
+ onClick: () => focusSection(issue.sectionId),
4540
+ children: m("{section} is required.", {
4541
+ section: stable.definition.sections.find(
4542
+ (section) => section.id === issue.sectionId
4543
+ )?.title ?? issue.sectionId
4544
+ })
4545
+ }
4546
+ ) }, issue.sectionId)) })
4547
+ ]
4548
+ }
4549
+ ),
4550
+ /* @__PURE__ */ jsx(EsheetSectionContext.Provider, { value: true, children: /* @__PURE__ */ jsx(
4551
+ EsheetRenderer,
4552
+ {
4553
+ ref: renderer,
4554
+ formDataInput: rendererInput.definition,
4555
+ initialResponses: rendererInput.responses,
4556
+ onReady: handleReady,
4557
+ strict: true,
4558
+ readOnly: readOnly || saving,
4559
+ fitToContainer: true,
4560
+ topNavigation: false,
4561
+ bottomNavigation: false,
4562
+ className: "richtext-esheet-renderer"
4563
+ },
4564
+ fieldsSettled ? "settled" : "priming"
4565
+ ) })
4566
+ ]
4567
+ }
4568
+ ),
4569
+ /* @__PURE__ */ jsxs("footer", { className: "bg-background sticky bottom-0 z-10 flex flex-wrap items-center justify-between gap-2 px-2 pt-2 pb-[max(0.5rem,env(safe-area-inset-bottom))] sm:px-3", children: [
4570
+ /* @__PURE__ */ jsx(
4571
+ "p",
4572
+ {
4573
+ role: "status",
4574
+ "aria-live": "polite",
4575
+ className: "text-muted-foreground min-w-0 text-sm",
4576
+ children: saveMessage || (readOnly ? m("Read-only document") : "")
4577
+ }
4578
+ ),
4579
+ onSubmit && /* @__PURE__ */ jsx(
4580
+ Button,
4581
+ {
4582
+ type: "button",
4583
+ className: "min-h-11",
4584
+ disabled: readOnly || saving,
4585
+ leftIcon: /* @__PURE__ */ jsx(Save, { size: 16, "aria-hidden": "true" }),
4586
+ onClick: () => void submit(),
4587
+ children: m(saving ? "Saving\u2026" : "Save document")
4588
+ }
4589
+ )
4590
+ ] })
4591
+ ]
4592
+ }
4593
+ );
4594
+ });
4117
4595
  function parseValue(answer) {
4118
4596
  if (!answer) return { medications: [] };
4119
4597
  try {
@@ -4252,6 +4730,6 @@ function registerMieEsheetFields(options) {
4252
4730
  registerAllergyListFieldType({ codeLookup: options?.codeLookup });
4253
4731
  }
4254
4732
 
4255
- export { AllergyListField, ENCOUNTER_VISIT_SYSTEM_PROMPT, ENCOUNTER_VISIT_TOOL_DEFINITIONS, EncounterAllergyField, EncounterAssessmentField, EncounterConditionsField, EncounterMedicationField, EncounterNarrativeField, EncounterPlanField, EncounterVisit, EncounterVitalsField, MedicationListField, createEncounterVisitTools, executeEncounterVisitToolCall, registerAllergyListFieldType, registerEncounterFieldTypes, registerMedicationListFieldType, registerMieEsheetFields };
4733
+ export { AllergyListField, ENCOUNTER_VISIT_SYSTEM_PROMPT, ENCOUNTER_VISIT_TOOL_DEFINITIONS, EncounterAllergyField, EncounterAssessmentField, EncounterConditionsField, EncounterMedicationField, EncounterNarrativeField, EncounterPlanField, EncounterVisit, EncounterVitalsField, EsheetNarrativeField, EsheetSectionContext, EsheetSectionField, MedicationListField, NARRATIVE_ATTRIBUTES, RichTextEsheet, applyNarrativeFinding, createEncounterVisitTools, createRichTextEsheetFormDefinition, createRichTextEsheetSnapshot, executeEncounterVisitToolCall, getNarrativeFindings, getRichTextEsheetFieldId, getRichTextEsheetSectionFieldId, hasNarrativeFindingText, narrativeText, partyFindings, registerAllergyListFieldType, registerEncounterFieldTypes, registerEsheetNarrativeFieldType, registerEsheetSectionFieldType, registerMedicationListFieldType, registerMieEsheetFields, sanitizeNarrativeHtml, validNarrativeFindings, validateRichTextEsheetDefinition };
4256
4734
  //# sourceMappingURL=esheet.js.map
4257
4735
  //# sourceMappingURL=esheet.js.map