@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.cjs CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  require('./chunk-K2BY7YPJ.cjs');
4
4
  var chunkM42FTWY2_cjs = require('./chunk-M42FTWY2.cjs');
5
- var chunkSCYLVQWA_cjs = require('./chunk-SCYLVQWA.cjs');
5
+ var chunkX6HEWIYP_cjs = require('./chunk-X6HEWIYP.cjs');
6
6
  var chunkJCO62XMU_cjs = require('./chunk-JCO62XMU.cjs');
7
7
  require('./chunk-GLG6UE2H.cjs');
8
8
  var chunkSV6YGK4U_cjs = require('./chunk-SV6YGK4U.cjs');
9
9
  var chunkG7EQ3JPX_cjs = require('./chunk-G7EQ3JPX.cjs');
10
10
  var chunkAIJAFPGJ_cjs = require('./chunk-AIJAFPGJ.cjs');
11
11
  var chunkPHUTJKHL_cjs = require('./chunk-PHUTJKHL.cjs');
12
- var chunk2Z2GFDRS_cjs = require('./chunk-2Z2GFDRS.cjs');
13
- require('./chunk-FQUSSPXX.cjs');
12
+ var chunkZNYBLM4O_cjs = require('./chunk-ZNYBLM4O.cjs');
13
+ require('./chunk-LJ4CCM3V.cjs');
14
14
  var chunkFSLI5IKP_cjs = require('./chunk-FSLI5IKP.cjs');
15
15
  require('./chunk-YP5T6NZD.cjs');
16
16
  var chunkUPWIJC6I_cjs = require('./chunk-UPWIJC6I.cjs');
@@ -23,6 +23,7 @@ require('./chunk-I44YNYI6.cjs');
23
23
  var chunkZ5S5JEJD_cjs = require('./chunk-Z5S5JEJD.cjs');
24
24
  require('./chunk-GF2BE4FB.cjs');
25
25
  require('./chunk-CPDNX3LS.cjs');
26
+ require('./chunk-PHYQSCP6.cjs');
26
27
  require('./chunk-NNEFAUHV.cjs');
27
28
  var chunkFHY3K6PL_cjs = require('./chunk-FHY3K6PL.cjs');
28
29
  require('./chunk-FSGEDDF2.cjs');
@@ -46,6 +47,7 @@ var renderer = require('@esheet/renderer');
46
47
  var DOMPurify = require('dompurify');
47
48
  var jsxRuntime = require('react/jsx-runtime');
48
49
  var fields = require('@esheet/fields');
50
+ var yaml = require('yaml');
49
51
  var builder = require('@esheet/builder');
50
52
 
51
53
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -281,6 +283,31 @@ function EncounterMdyView({ source }) {
281
283
  }
282
284
  );
283
285
  }
286
+ var EsheetSectionContext = React11__namespace.createContext(false);
287
+ function EsheetSectionField(props) {
288
+ const inDocument = React11__namespace.useContext(EsheetSectionContext);
289
+ const { field, isPreview, isRequired, isSoftRequired, nestedChildren } = props;
290
+ const def = field.definition;
291
+ if (!inDocument || !isPreview) return /* @__PURE__ */ jsxRuntime.jsx(fields.SectionField, { ...props });
292
+ const title = def.title || "Section";
293
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "section-field-preview grid gap-1.5", children: [
294
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-foreground m-0 flex items-center gap-2 text-sm font-medium", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 break-words", children: [
295
+ title,
296
+ (isRequired || isSoftRequired) && /* @__PURE__ */ jsxRuntime.jsx(
297
+ chunkZ5S5JEJD_cjs.RequiredMark,
298
+ {
299
+ variant: isSoftRequired && !isRequired ? "warning" : void 0
300
+ }
301
+ )
302
+ ] }) }),
303
+ nestedChildren
304
+ ] });
305
+ }
306
+ function registerEsheetSectionFieldType() {
307
+ const section = fields.getFieldComponent("section");
308
+ if (section === void 0 || section === fields.SectionField)
309
+ fields.registerFieldComponents({ section: EsheetSectionField });
310
+ }
284
311
  var measurements = [
285
312
  { key: "pulse", label: "Pulse", unit: "bpm" },
286
313
  { key: "respiratoryRate", label: "Respiratory rate", unit: "/min" },
@@ -895,7 +922,7 @@ function conditionSystemIdentity(system) {
895
922
  const trimmed = system.trim();
896
923
  if (trimmed === "http://hl7.org/fhir/sid/icd-10-cm") return "ICD-10-CM";
897
924
  if (trimmed === "http://snomed.info/sct") return "SNOMED";
898
- return chunkSCYLVQWA_cjs.normalizeConditionCodingSystem(trimmed);
925
+ return chunkX6HEWIYP_cjs.normalizeConditionCodingSystem(trimmed);
899
926
  }
900
927
  function matchingConditionCode(concern, picked) {
901
928
  return Boolean(
@@ -988,7 +1015,7 @@ function EncounterAssessmentField({
988
1015
  const assertionDate = useEncounterAssertionDate(field);
989
1016
  const currentDate = React11__namespace.useRef(assertionDate);
990
1017
  currentDate.current = assertionDate;
991
- const codeLookup = chunkSCYLVQWA_cjs.useCodeLookupConfig();
1018
+ const codeLookup = chunkX6HEWIYP_cjs.useCodeLookupConfig();
992
1019
  const state = useClinicalFormState(form);
993
1020
  const externalConcerns = React11__namespace.useMemo(
994
1021
  () => getExternalEncounterConditions(state),
@@ -1059,7 +1086,7 @@ function EncounterAssessmentField({
1059
1086
  );
1060
1087
  }
1061
1088
  return /* @__PURE__ */ jsxRuntime.jsx("section", { className: "space-y-2", children: /* @__PURE__ */ jsxRuntime.jsx(
1062
- chunkSCYLVQWA_cjs.Assessment,
1089
+ chunkX6HEWIYP_cjs.Assessment,
1063
1090
  {
1064
1091
  concerns,
1065
1092
  items: value.items,
@@ -1190,27 +1217,29 @@ function EncounterAssessmentField({
1190
1217
  ) });
1191
1218
  }
1192
1219
 
1193
- // src/components/EncounterVisit/narrativeFindings.ts
1194
- var FINDINGS_ATTRIBUTE = "encounterNarrativeFindings";
1195
- var FINDINGS_TEXT_ATTRIBUTE = "encounterNarrativeFindingsText";
1196
- var HTML_ATTRIBUTE = "encounterNarrativeHtml";
1197
- var TEXT_ATTRIBUTE = "encounterNarrativeText";
1220
+ // src/esheet-fields/narrativeFindings.ts
1221
+ var NARRATIVE_ATTRIBUTES = {
1222
+ html: "narrativeHtml",
1223
+ text: "narrativeText",
1224
+ findings: "narrativeFindings",
1225
+ findingsText: "narrativeFindingsText"
1226
+ };
1198
1227
  function escapedTextHtml(text) {
1199
1228
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\n/g, "<br>");
1200
1229
  }
1201
1230
  function hasNarrativeTagsOnly(html) {
1202
1231
  return !html.replace(/<\/?(?:p|div|strong|b|em|i|u|ul|ol|li)\s*>|<br\s*\/?>/gi, "").includes("<");
1203
1232
  }
1204
- function appendedRichAttributes(response, answer, finding) {
1233
+ function appendedRichAttributes(response, answer, finding, attributes) {
1205
1234
  const previous = response?.answer ?? "";
1206
- const html = response?.attributes?.[HTML_ATTRIBUTE];
1207
- if (answer === previous || response?.attributes?.[TEXT_ATTRIBUTE] !== previous || typeof html !== "string" || !hasNarrativeTagsOnly(html))
1235
+ const html = response?.attributes?.[attributes.html];
1236
+ if (answer === previous || response?.attributes?.[attributes.text] !== previous || typeof html !== "string" || !hasNarrativeTagsOnly(html))
1208
1237
  return {};
1209
1238
  const aligned = previous.endsWith("\n") ? html.replace(/<br\s*\/?>((?:<\/(?:p|div|ul|ol|li)\s*>|\s)*)$/i, "$1") : html;
1210
1239
  const next = `${previous.trim() ? aligned : ""}<p>${escapedTextHtml(finding.text)}${finding.text.endsWith("\n") ? "<br>" : ""}</p>`;
1211
- return { [HTML_ATTRIBUTE]: next, [TEXT_ATTRIBUTE]: answer };
1240
+ return { [attributes.html]: next, [attributes.text]: answer };
1212
1241
  }
1213
- function validEncounterNarrativeFindings(value) {
1242
+ function validNarrativeFindings(value) {
1214
1243
  if (!Array.isArray(value)) return [];
1215
1244
  const ids = /* @__PURE__ */ new Set();
1216
1245
  return value.flatMap((finding) => {
@@ -1235,7 +1264,7 @@ function validEncounterNarrativeFindings(value) {
1235
1264
  ];
1236
1265
  });
1237
1266
  }
1238
- function hasEncounterNarrativeFindingText(answer, text) {
1267
+ function hasNarrativeFindingText(answer, text) {
1239
1268
  const normalize = (value) => value.replace(/\r\n?/g, "\n").trim();
1240
1269
  return `
1241
1270
  ${normalize(answer)}
@@ -1243,46 +1272,39 @@ ${normalize(answer)}
1243
1272
  ${normalize(text)}
1244
1273
  `);
1245
1274
  }
1246
- function getEncounterNarrativeFindings(response) {
1275
+ function getNarrativeFindings(response, attributes = NARRATIVE_ATTRIBUTES) {
1247
1276
  const answer = response?.answer ?? "";
1248
- if (response?.attributes?.[FINDINGS_TEXT_ATTRIBUTE] !== answer) return [];
1249
- const selected = response.attributes[FINDINGS_ATTRIBUTE];
1277
+ if (response?.attributes?.[attributes.findingsText] !== answer) return [];
1278
+ const selected = response.attributes[attributes.findings];
1250
1279
  if (typeof selected !== "string") return [];
1251
1280
  try {
1252
- return validEncounterNarrativeFindings(JSON.parse(selected)).filter(
1253
- (finding) => hasEncounterNarrativeFindingText(answer, finding.text)
1281
+ return validNarrativeFindings(JSON.parse(selected)).filter(
1282
+ (finding) => hasNarrativeFindingText(answer, finding.text)
1254
1283
  );
1255
1284
  } catch {
1256
1285
  return [];
1257
1286
  }
1258
1287
  }
1259
- function applyEncounterNarrativeFinding(response, finding) {
1260
- const validated = validEncounterNarrativeFindings([finding])[0];
1288
+ function applyNarrativeFinding(response, finding, attributes = NARRATIVE_ATTRIBUTES) {
1289
+ const validated = validNarrativeFindings([finding])[0];
1261
1290
  if (!validated) return response ?? {};
1262
- const selected = getEncounterNarrativeFindings(response);
1291
+ const selected = getNarrativeFindings(response, attributes);
1263
1292
  if (selected.some((item) => item.id === validated.id)) return response ?? {};
1264
1293
  const previousAnswer = response?.answer ?? "";
1265
- const answer = hasEncounterNarrativeFindingText(
1266
- previousAnswer,
1267
- validated.text
1268
- ) ? previousAnswer : `${previousAnswer.trim() ? `${previousAnswer}${previousAnswer.endsWith("\n") ? "" : "\n"}` : ""}${validated.text}`;
1294
+ const answer = hasNarrativeFindingText(previousAnswer, validated.text) ? previousAnswer : `${previousAnswer.trim() ? `${previousAnswer}${previousAnswer.endsWith("\n") ? "" : "\n"}` : ""}${validated.text}`;
1269
1295
  return {
1270
1296
  ...response,
1271
1297
  answer,
1272
1298
  _ai: false,
1273
1299
  attributes: {
1274
1300
  ...response?.attributes,
1275
- ...appendedRichAttributes(response, answer, validated),
1276
- [FINDINGS_ATTRIBUTE]: JSON.stringify([...selected, validated]),
1277
- [FINDINGS_TEXT_ATTRIBUTE]: answer
1301
+ ...appendedRichAttributes(response, answer, validated, attributes),
1302
+ [attributes.findings]: JSON.stringify([...selected, validated]),
1303
+ [attributes.findingsText]: answer
1278
1304
  }
1279
1305
  };
1280
1306
  }
1281
- var HTML_ATTRIBUTE2 = "encounterNarrativeHtml";
1282
- var TEXT_ATTRIBUTE2 = "encounterNarrativeText";
1283
- var FINDINGS_ATTRIBUTE2 = "encounterNarrativeFindings";
1284
- var FINDINGS_TEXT_ATTRIBUTE2 = "encounterNarrativeFindingsText";
1285
- function sanitizeEncounterNarrativeHtml(html) {
1307
+ function sanitizeNarrativeHtml(html) {
1286
1308
  return DOMPurify__default.default.sanitize(html, {
1287
1309
  ALLOWED_TAGS: [
1288
1310
  "p",
@@ -1309,9 +1331,9 @@ function sanitizeEncounterNarrativeHtml(html) {
1309
1331
  function textToHtml(text) {
1310
1332
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\n/g, "<br>");
1311
1333
  }
1312
- function encounterNarrativeText(html) {
1334
+ function narrativeText(html) {
1313
1335
  const documentBody = document.createElement("div");
1314
- documentBody.innerHTML = sanitizeEncounterNarrativeHtml(html);
1336
+ documentBody.innerHTML = sanitizeNarrativeHtml(html);
1315
1337
  let text = "";
1316
1338
  const lineBreak = () => {
1317
1339
  if (text && !text.endsWith("\n")) text += "\n";
@@ -1349,12 +1371,12 @@ function encounterNarrativeText(html) {
1349
1371
  documentBody.childNodes.forEach(visit);
1350
1372
  return text.replace(/\u00a0/g, " ").replace(/\n$/, "");
1351
1373
  }
1352
- function responseHtml(response) {
1374
+ function responseHtml(response, attributes) {
1353
1375
  const answer = response?.answer ?? "";
1354
- const attributes = response?.attributes;
1355
- if (typeof window !== "undefined" && attributes?.[TEXT_ATTRIBUTE2] === answer && typeof attributes[HTML_ATTRIBUTE2] === "string") {
1356
- const html = sanitizeEncounterNarrativeHtml(attributes[HTML_ATTRIBUTE2]);
1357
- if (encounterNarrativeText(html) === answer) return html;
1376
+ const responseAttributes = response?.attributes;
1377
+ if (typeof window !== "undefined" && responseAttributes?.[attributes.text] === answer && typeof responseAttributes[attributes.html] === "string") {
1378
+ const html = sanitizeNarrativeHtml(responseAttributes[attributes.html]);
1379
+ if (narrativeText(html) === answer) return html;
1358
1380
  }
1359
1381
  return textToHtml(answer);
1360
1382
  }
@@ -1371,7 +1393,7 @@ function findingShortcuts(findings) {
1371
1393
  }
1372
1394
  return shortcuts;
1373
1395
  }
1374
- function EncounterNarrativeField({
1396
+ function EsheetNarrativeField({
1375
1397
  field,
1376
1398
  response,
1377
1399
  isPreview,
@@ -1379,7 +1401,11 @@ function EncounterNarrativeField({
1379
1401
  isReadOnly,
1380
1402
  isRequired,
1381
1403
  isSoftRequired,
1382
- onResponse
1404
+ onResponse,
1405
+ attributes = NARRATIVE_ATTRIBUTES,
1406
+ findingsKey = "narrativeFindings",
1407
+ hideLabelKey = "narrativeHideLabel",
1408
+ slot = "esheet-narrative"
1383
1409
  }) {
1384
1410
  const m = chunkFSLI5IKP_cjs.useMessages();
1385
1411
  const editorRef = React11__namespace.useRef(null);
@@ -1389,9 +1415,12 @@ function EncounterNarrativeField({
1389
1415
  const definition = field.definition;
1390
1416
  const question = definition.question || m("Narrative");
1391
1417
  const readOnly = !isPreview || !isEnabled || isReadOnly;
1392
- const html = React11__namespace.useMemo(() => responseHtml(response), [response]);
1393
- const findings = validEncounterNarrativeFindings(
1394
- definition._sourceData?.encounterFindings
1418
+ const html = React11__namespace.useMemo(
1419
+ () => responseHtml(response, attributes),
1420
+ [response, attributes]
1421
+ );
1422
+ const findings = validNarrativeFindings(
1423
+ definition._sourceData?.[findingsKey]
1395
1424
  );
1396
1425
  const shortcuts = findingShortcuts(findings);
1397
1426
  const macros = findings.length ? [
@@ -1407,18 +1436,23 @@ function EncounterNarrativeField({
1407
1436
  }
1408
1437
  ] : void 0;
1409
1438
  const publish = (value, previous, selected) => {
1410
- const sanitized = sanitizeEncounterNarrativeHtml(value);
1411
- const answer = encounterNarrativeText(sanitized);
1412
- const attributes = {
1439
+ const sanitized = sanitizeNarrativeHtml(value);
1440
+ const answer = narrativeText(sanitized);
1441
+ const nextAttributes = {
1413
1442
  ...previous?.attributes,
1414
- [HTML_ATTRIBUTE2]: sanitized,
1415
- [TEXT_ATTRIBUTE2]: answer,
1416
- ...selected.length || previous?.attributes?.[FINDINGS_ATTRIBUTE2] !== void 0 ? {
1417
- [FINDINGS_ATTRIBUTE2]: JSON.stringify(selected),
1418
- [FINDINGS_TEXT_ATTRIBUTE2]: answer
1443
+ [attributes.html]: sanitized,
1444
+ [attributes.text]: answer,
1445
+ ...selected.length || previous?.attributes?.[attributes.findings] !== void 0 ? {
1446
+ [attributes.findings]: JSON.stringify(selected),
1447
+ [attributes.findingsText]: answer
1419
1448
  } : {}
1420
1449
  };
1421
- const next = { ...previous, answer, _ai: false, attributes };
1450
+ const next = {
1451
+ ...previous,
1452
+ answer,
1453
+ _ai: false,
1454
+ attributes: nextAttributes
1455
+ };
1422
1456
  delete next.selected;
1423
1457
  responseRef.current = next;
1424
1458
  onResponse(next);
@@ -1432,12 +1466,12 @@ function EncounterNarrativeField({
1432
1466
  String(isRequired || isSoftRequired)
1433
1467
  );
1434
1468
  }, [isRequired, isSoftRequired, labelId, readOnly]);
1435
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": "encounter-narrative", className: "min-w-0", children: [
1469
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-slot": slot, className: "min-w-0", children: [
1436
1470
  /* @__PURE__ */ jsxRuntime.jsxs(
1437
1471
  "div",
1438
1472
  {
1439
1473
  id: labelId,
1440
- className: definition._sourceData?.encounterHideLabel ? "sr-only" : "text-muted-foreground text-sm font-medium",
1474
+ className: definition._sourceData?.[hideLabelKey] ? "sr-only" : "text-muted-foreground text-sm font-medium",
1441
1475
  children: [
1442
1476
  question,
1443
1477
  definition.unit && ` (${definition.unit})`,
@@ -1446,20 +1480,20 @@ function EncounterNarrativeField({
1446
1480
  }
1447
1481
  ),
1448
1482
  /* @__PURE__ */ jsxRuntime.jsx(
1449
- chunk2Z2GFDRS_cjs.TextAreaRich,
1483
+ chunkZNYBLM4O_cjs.TextAreaRich,
1450
1484
  {
1451
1485
  ref: editorRef,
1452
1486
  value: html,
1453
1487
  onChange: (value) => {
1454
1488
  if (readOnly) return;
1455
- const sanitized = sanitizeEncounterNarrativeHtml(value);
1456
- const answer = encounterNarrativeText(sanitized);
1489
+ const sanitized = sanitizeNarrativeHtml(value);
1490
+ const answer = narrativeText(sanitized);
1457
1491
  const previous = responseRef.current;
1458
1492
  publish(
1459
1493
  sanitized,
1460
1494
  previous,
1461
- getEncounterNarrativeFindings(previous).filter(
1462
- (finding) => hasEncounterNarrativeFindingText(answer, finding.text)
1495
+ getNarrativeFindings(previous, attributes).filter(
1496
+ (finding) => hasNarrativeFindingText(answer, finding.text)
1463
1497
  )
1464
1498
  );
1465
1499
  },
@@ -1467,14 +1501,11 @@ function EncounterNarrativeField({
1467
1501
  const finding = findings.find((item) => item.id === macro.id);
1468
1502
  if (!finding || readOnly) return true;
1469
1503
  const previous = responseRef.current;
1470
- const next = applyEncounterNarrativeFinding(previous, finding);
1504
+ const next = applyNarrativeFinding(previous, finding, attributes);
1471
1505
  if (next === previous) return true;
1472
1506
  const answer = previous?.answer ?? "";
1473
- const nextHtml = hasEncounterNarrativeFindingText(
1474
- answer,
1475
- finding.text
1476
- ) ? responseHtml(previous) : `${answer.trim() ? responseHtml(previous) : ""}<p>${textToHtml(finding.text)}</p>`;
1477
- publish(nextHtml, previous, getEncounterNarrativeFindings(next));
1507
+ const nextHtml = hasNarrativeFindingText(answer, finding.text) ? responseHtml(previous, attributes) : `${answer.trim() ? responseHtml(previous, attributes) : ""}<p>${textToHtml(finding.text)}</p>`;
1508
+ publish(nextHtml, previous, getNarrativeFindings(next, attributes));
1478
1509
  return true;
1479
1510
  },
1480
1511
  "aria-label": question,
@@ -1512,12 +1543,53 @@ function EncounterNarrativeField({
1512
1543
  showToolbar: m("Show toolbar"),
1513
1544
  hideToolbar: m("Hide toolbar")
1514
1545
  },
1515
- sanitizeHtml: sanitizeEncounterNarrativeHtml,
1546
+ sanitizeHtml: sanitizeNarrativeHtml,
1516
1547
  className: "mt-1"
1517
1548
  }
1518
1549
  )
1519
1550
  ] });
1520
1551
  }
1552
+ function registerEsheetNarrativeFieldType() {
1553
+ if (fields.getFieldComponent("esheetNarrative")) return;
1554
+ fields.registerCustomFieldTypes({
1555
+ esheetNarrative: {
1556
+ label: "Narrative",
1557
+ category: "rich",
1558
+ answerType: "text",
1559
+ hasOptions: false,
1560
+ hasMatrix: false,
1561
+ defaultProps: { question: "Narrative" },
1562
+ component: EsheetNarrativeField
1563
+ }
1564
+ });
1565
+ }
1566
+
1567
+ // src/components/EncounterVisit/narrativeFindings.ts
1568
+ var ENCOUNTER_NARRATIVE_ATTRIBUTES = {
1569
+ html: "encounterNarrativeHtml",
1570
+ text: "encounterNarrativeText",
1571
+ findings: "encounterNarrativeFindings",
1572
+ findingsText: "encounterNarrativeFindingsText"
1573
+ };
1574
+ function applyEncounterNarrativeFinding(response, finding) {
1575
+ return applyNarrativeFinding(
1576
+ response,
1577
+ finding,
1578
+ ENCOUNTER_NARRATIVE_ATTRIBUTES
1579
+ );
1580
+ }
1581
+ function EncounterNarrativeField(props) {
1582
+ return /* @__PURE__ */ jsxRuntime.jsx(
1583
+ EsheetNarrativeField,
1584
+ {
1585
+ ...props,
1586
+ attributes: ENCOUNTER_NARRATIVE_ATTRIBUTES,
1587
+ findingsKey: "encounterFindings",
1588
+ hideLabelKey: "encounterHideLabel",
1589
+ slot: "encounter-narrative"
1590
+ }
1591
+ );
1592
+ }
1521
1593
  function readValue(answer) {
1522
1594
  if (!answer) return { value: { medications: [] } };
1523
1595
  try {
@@ -1586,7 +1658,7 @@ function MedicationCard({
1586
1658
  onSelect: (pick) => {
1587
1659
  if (requireCoding && (!pick.codetype.trim() || !pick.fullcode.trim()))
1588
1660
  return;
1589
- onPatch(chunkSCYLVQWA_cjs.lookupToMedicationFields(pick));
1661
+ onPatch(chunkX6HEWIYP_cjs.lookupToMedicationFields(pick));
1590
1662
  },
1591
1663
  onFreeText: requireCoding ? void 0 : (name) => {
1592
1664
  if (name.trim()) onPatch({ name, code: void 0 });
@@ -1637,7 +1709,7 @@ function MedicationCard({
1637
1709
  // solution → milliliter, …)
1638
1710
  onPatch({
1639
1711
  doseForm: event.target.value || void 0,
1640
- quantityUnit: chunkSCYLVQWA_cjs.doseFormToQuantityUnit(event.target.value)
1712
+ quantityUnit: chunkX6HEWIYP_cjs.doseFormToQuantityUnit(event.target.value)
1641
1713
  })
1642
1714
  )
1643
1715
  }
@@ -1688,7 +1760,7 @@ function MedicationCard({
1688
1760
  ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 space-y-1.5", children: [
1689
1761
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground block text-sm font-medium", children: m("Start \u2013 end date") }),
1690
1762
  /* @__PURE__ */ jsxRuntime.jsx(
1691
- chunkSCYLVQWA_cjs.DateRangePicker,
1763
+ chunkX6HEWIYP_cjs.DateRangePicker,
1692
1764
  {
1693
1765
  className: "w-full [&_[data-slot=date-range-trigger]]:min-h-11 [&_[data-slot=date-range-trigger]]:w-full",
1694
1766
  variant: "responsive",
@@ -1727,7 +1799,7 @@ function MedicationCard({
1727
1799
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
1728
1800
  /* @__PURE__ */ jsxRuntime.jsx(chunkAIJAFPGJ_cjs.Label, { htmlFor: `${id}-sig`, children: m("Sig (patient directions)") }),
1729
1801
  /* @__PURE__ */ jsxRuntime.jsx(
1730
- chunk2Z2GFDRS_cjs.Textarea,
1802
+ chunkZNYBLM4O_cjs.Textarea,
1731
1803
  {
1732
1804
  id: `${id}-sig`,
1733
1805
  rows: 2,
@@ -1735,7 +1807,7 @@ function MedicationCard({
1735
1807
  readOnly,
1736
1808
  onChange: (event) => {
1737
1809
  const sig = event.target.value;
1738
- const { route, frequency, prn } = chunkSCYLVQWA_cjs.parseSig(sig);
1810
+ const { route, frequency, prn } = chunkX6HEWIYP_cjs.parseSig(sig);
1739
1811
  onPatch({ sig, route, frequency, prn: prn || void 0 });
1740
1812
  }
1741
1813
  }
@@ -1763,7 +1835,7 @@ function MedicationCard({
1763
1835
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
1764
1836
  /* @__PURE__ */ jsxRuntime.jsx(chunkAIJAFPGJ_cjs.Label, { htmlFor: `${id}-pharmacy-notes`, children: m("Pharmacy notes") }),
1765
1837
  /* @__PURE__ */ jsxRuntime.jsx(
1766
- chunk2Z2GFDRS_cjs.Textarea,
1838
+ chunkZNYBLM4O_cjs.Textarea,
1767
1839
  {
1768
1840
  id: `${id}-pharmacy-notes`,
1769
1841
  rows: 2,
@@ -1790,7 +1862,7 @@ function EncounterMedicationField({
1790
1862
  );
1791
1863
  const m = chunkFSLI5IKP_cjs.useMessages();
1792
1864
  const requireCoding = clinicalRequiresCoding(field);
1793
- const codeLookup = chunkSCYLVQWA_cjs.useCodeLookupConfig();
1865
+ const codeLookup = chunkX6HEWIYP_cjs.useCodeLookupConfig();
1794
1866
  const state = useClinicalFormState(form);
1795
1867
  const [draft, setDraft] = React11__namespace.useState(null);
1796
1868
  const readOnly = !(isPreview && isEnabled) || Boolean(isReadOnly) || state.readOnly || state.disposed || Boolean(error);
@@ -1938,7 +2010,7 @@ function AllergyCard({
1938
2010
  {
1939
2011
  label: m("Type"),
1940
2012
  className: "min-h-11",
1941
- value: m(chunkSCYLVQWA_cjs.ALLERGY_TYPE_LABELS[allergy.type ?? "drug"]),
2013
+ value: m(chunkX6HEWIYP_cjs.ALLERGY_TYPE_LABELS[allergy.type ?? "drug"]),
1942
2014
  readOnly: true
1943
2015
  }
1944
2016
  ) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -1946,8 +2018,8 @@ function AllergyCard({
1946
2018
  {
1947
2019
  label: m("Type"),
1948
2020
  className: "[&_[data-slot=select-trigger]]:min-h-11",
1949
- options: Object.keys(chunkSCYLVQWA_cjs.ALLERGY_TYPE_LABELS).map(
1950
- (type) => ({ value: type, label: m(chunkSCYLVQWA_cjs.ALLERGY_TYPE_LABELS[type]) })
2021
+ options: Object.keys(chunkX6HEWIYP_cjs.ALLERGY_TYPE_LABELS).map(
2022
+ (type) => ({ value: type, label: m(chunkX6HEWIYP_cjs.ALLERGY_TYPE_LABELS[type]) })
1951
2023
  ),
1952
2024
  value: allergy.type ?? "drug",
1953
2025
  onValueChange: (next) => {
@@ -2084,7 +2156,7 @@ function AllergyCard({
2084
2156
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
2085
2157
  /* @__PURE__ */ jsxRuntime.jsx(chunkAIJAFPGJ_cjs.Label, { htmlFor: `${id}-note`, children: m("Note") }),
2086
2158
  /* @__PURE__ */ jsxRuntime.jsx(
2087
- chunk2Z2GFDRS_cjs.Textarea,
2159
+ chunkZNYBLM4O_cjs.Textarea,
2088
2160
  {
2089
2161
  id: `${id}-note`,
2090
2162
  rows: 2,
@@ -2111,7 +2183,7 @@ function EncounterAllergyField({
2111
2183
  );
2112
2184
  const m = chunkFSLI5IKP_cjs.useMessages();
2113
2185
  const requireCoding = clinicalRequiresCoding(field);
2114
- const codeLookup = chunkSCYLVQWA_cjs.useCodeLookupConfig();
2186
+ const codeLookup = chunkX6HEWIYP_cjs.useCodeLookupConfig();
2115
2187
  const state = useClinicalFormState(form);
2116
2188
  const [draft, setDraft] = React11__namespace.useState(null);
2117
2189
  const readOnly = !(isPreview && isEnabled) || Boolean(isReadOnly) || state.readOnly || state.disposed || Boolean(error);
@@ -2258,7 +2330,7 @@ function EncounterConditionsField({
2258
2330
  const [adding, setAdding] = React11__namespace.useState(false);
2259
2331
  const [manualEditing, setManualEditing] = React11__namespace.useState(false);
2260
2332
  const state = useClinicalFormState(form);
2261
- const codeLookup = chunkSCYLVQWA_cjs.useCodeLookupConfig();
2333
+ const codeLookup = chunkX6HEWIYP_cjs.useCodeLookupConfig();
2262
2334
  const readOnly = !(isPreview && isEnabled) || Boolean(isReadOnly) || state.readOnly || state.disposed || !value;
2263
2335
  const currentValue = React11__namespace.useRef(value);
2264
2336
  const currentResponse = React11__namespace.useRef(response);
@@ -2449,7 +2521,7 @@ function EncounterConditionsField({
2449
2521
  }
2450
2522
  ),
2451
2523
  /* @__PURE__ */ jsxRuntime.jsx(
2452
- chunkSCYLVQWA_cjs.ConditionEditor,
2524
+ chunkX6HEWIYP_cjs.ConditionEditor,
2453
2525
  {
2454
2526
  mode: editing ? "refine" : "add",
2455
2527
  open: manualEditing,
@@ -2541,7 +2613,7 @@ function EncounterPlanField({
2541
2613
  const m = chunkFSLI5IKP_cjs.useMessages();
2542
2614
  const requireCoding = clinicalRequiresCoding(field);
2543
2615
  const state = useClinicalFormState(form);
2544
- const codeLookup = chunkSCYLVQWA_cjs.useCodeLookupConfig();
2616
+ const codeLookup = chunkX6HEWIYP_cjs.useCodeLookupConfig();
2545
2617
  const readOnly = !(isPreview && isEnabled) || Boolean(isReadOnly) || state.readOnly || state.disposed || !value;
2546
2618
  const currentValue = React11__namespace.useRef(value);
2547
2619
  const currentResponse = React11__namespace.useRef(response);
@@ -2575,7 +2647,7 @@ function EncounterPlanField({
2575
2647
  return /* @__PURE__ */ jsxRuntime.jsx(InvalidClinicalAnswer, { label, answer: response?.answer });
2576
2648
  return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": label, className: "min-w-0 space-y-2", children: [
2577
2649
  /* @__PURE__ */ jsxRuntime.jsx(
2578
- chunkSCYLVQWA_cjs.Assessment,
2650
+ chunkX6HEWIYP_cjs.Assessment,
2579
2651
  {
2580
2652
  concerns,
2581
2653
  items,
@@ -2633,32 +2705,10 @@ function EncounterPlanField({
2633
2705
  !readOnly && requireCoding && !codeLookup && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm", children: m("Coded order search is unavailable.") })
2634
2706
  ] });
2635
2707
  }
2636
- var EncounterSectionContext = React11__namespace.createContext(false);
2637
- function EncounterSectionField(props) {
2638
- const inEncounter = React11__namespace.useContext(EncounterSectionContext);
2639
- const { field, isPreview, isRequired, isSoftRequired, nestedChildren } = props;
2640
- const def = field.definition;
2641
- if (!inEncounter || !isPreview) return /* @__PURE__ */ jsxRuntime.jsx(fields.SectionField, { ...props });
2642
- const title = def.title || "Section";
2643
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "section-field-preview grid gap-1.5", children: [
2644
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-foreground m-0 flex items-center gap-2 text-sm font-medium", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 break-words", children: [
2645
- title,
2646
- (isRequired || isSoftRequired) && /* @__PURE__ */ jsxRuntime.jsx(
2647
- chunkZ5S5JEJD_cjs.RequiredMark,
2648
- {
2649
- variant: isSoftRequired && !isRequired ? "warning" : void 0
2650
- }
2651
- )
2652
- ] }) }),
2653
- nestedChildren
2654
- ] });
2655
- }
2656
2708
 
2657
2709
  // src/components/EncounterVisit/fields.ts
2658
2710
  function registerEncounterFieldTypes() {
2659
- const section = fields.getFieldComponent("section");
2660
- if (section === void 0 || section === fields.SectionField)
2661
- fields.registerFieldComponents({ section: EncounterSectionField });
2711
+ registerEsheetSectionFieldType();
2662
2712
  const clinicalFields = {
2663
2713
  encounterMedications: {
2664
2714
  label: "Encounter medications",
@@ -3430,7 +3480,7 @@ function ensureFields() {
3430
3480
  }
3431
3481
  var esheetDefaultsSettled = false;
3432
3482
  var EncounterVisit = React11__namespace.forwardRef(function EncounterVisit2({ definition, initialMdy, codeLookup, messages, ...props }, ref) {
3433
- const inheritedCodeLookup = chunkSCYLVQWA_cjs.useCodeLookupConfig();
3483
+ const inheritedCodeLookup = chunkX6HEWIYP_cjs.useCodeLookupConfig();
3434
3484
  const parsed = React11__namespace.useMemo(
3435
3485
  () => initialMdy === void 0 ? null : chunkM42FTWY2_cjs.parseEncounterMdy(initialMdy),
3436
3486
  [initialMdy]
@@ -3497,7 +3547,7 @@ var EncounterVisit = React11__namespace.forwardRef(function EncounterVisit2({ de
3497
3547
  },
3498
3548
  visitDefinition.id
3499
3549
  );
3500
- return /* @__PURE__ */ jsxRuntime.jsx(chunkSCYLVQWA_cjs.CodeLookupContext.Provider, { value: codeLookup ?? inheritedCodeLookup, children: /* @__PURE__ */ jsxRuntime.jsx(chunkFSLI5IKP_cjs.MessagesProvider, { messages, children: session }) });
3550
+ return /* @__PURE__ */ jsxRuntime.jsx(chunkX6HEWIYP_cjs.CodeLookupContext.Provider, { value: codeLookup ?? inheritedCodeLookup, children: /* @__PURE__ */ jsxRuntime.jsx(chunkFSLI5IKP_cjs.MessagesProvider, { messages, children: session }) });
3501
3551
  });
3502
3552
  var EncounterVisitSession = React11__namespace.forwardRef(function EncounterVisitSession2({
3503
3553
  definition,
@@ -4079,7 +4129,7 @@ var EncounterVisitSession = React11__namespace.forwardRef(function EncounterVisi
4079
4129
  }
4080
4130
  )
4081
4131
  ] }),
4082
- /* @__PURE__ */ jsxRuntime.jsx(EncounterSectionContext.Provider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4132
+ /* @__PURE__ */ jsxRuntime.jsx(EsheetSectionContext.Provider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4083
4133
  renderer.EsheetRenderer,
4084
4134
  {
4085
4135
  ref: renderer$1,
@@ -4137,6 +4187,434 @@ var EncounterVisitSession = React11__namespace.forwardRef(function EncounterVisi
4137
4187
  }
4138
4188
  );
4139
4189
  });
4190
+ function getRichTextEsheetFieldId(sectionId) {
4191
+ return `richtext:${encodeURIComponent(sectionId)}:value`;
4192
+ }
4193
+ function getRichTextEsheetSectionFieldId(sectionId) {
4194
+ return `richtext:${encodeURIComponent(sectionId)}:section`;
4195
+ }
4196
+ function nonempty(value) {
4197
+ return typeof value === "string" && value.trim().length > 0;
4198
+ }
4199
+ function invalid(message) {
4200
+ throw new Error(`Invalid RichTextEsheet definition: ${message}`);
4201
+ }
4202
+ function validateRichTextEsheetDefinition(definition) {
4203
+ if (!definition || typeof definition !== "object")
4204
+ invalid("expected an object");
4205
+ if (!nonempty(definition.id) || !nonempty(definition.title))
4206
+ invalid("id and title must be nonempty strings");
4207
+ if (!Array.isArray(definition.sections) || definition.sections.length === 0)
4208
+ invalid("at least one section is required");
4209
+ const sectionIds = /* @__PURE__ */ new Set();
4210
+ for (const section of definition.sections) {
4211
+ if (!section || typeof section !== "object")
4212
+ invalid("expected a section object");
4213
+ if (!nonempty(section.id) || !nonempty(section.title))
4214
+ invalid("each section requires an id and title");
4215
+ if (sectionIds.has(section.id))
4216
+ invalid(`duplicate section id ${section.id}`);
4217
+ sectionIds.add(section.id);
4218
+ if (section.description !== void 0 && typeof section.description !== "string")
4219
+ invalid(`invalid description in ${section.id}`);
4220
+ if (section.required !== void 0 && typeof section.required !== "boolean")
4221
+ invalid(`required must be boolean in ${section.id}`);
4222
+ if (section.fieldType !== void 0 && !nonempty(section.fieldType))
4223
+ invalid(`fieldType must be a nonempty string in ${section.id}`);
4224
+ if (section.findings === void 0) continue;
4225
+ if (!Array.isArray(section.findings))
4226
+ invalid(`findings must be an array in ${section.id}`);
4227
+ const findingIds = /* @__PURE__ */ new Set();
4228
+ for (const finding of section.findings) {
4229
+ if (!finding || typeof finding !== "object" || !nonempty(finding.id) || !nonempty(finding.label) || !nonempty(finding.text))
4230
+ invalid(`findings require id, label and text in ${section.id}`);
4231
+ if (findingIds.has(finding.id))
4232
+ invalid(`duplicate finding id ${finding.id} in ${section.id}`);
4233
+ findingIds.add(finding.id);
4234
+ }
4235
+ }
4236
+ }
4237
+ function partyFindings(parties) {
4238
+ return (parties ?? []).map((party) => ({
4239
+ // Content-derived identity: editing or reordering a party yields a new
4240
+ // macro id, so the updated text stays insertable after an earlier
4241
+ // version was applied (selection dedupe is by id).
4242
+ id: `party:${encodeURIComponent(party.role)}:${encodeURIComponent(
4243
+ party.name
4244
+ )}:${encodeURIComponent(party.detail ?? "")}`,
4245
+ label: `${party.role}: ${party.name}`,
4246
+ text: party.detail ? `${party.name} (${party.detail})` : party.name
4247
+ }));
4248
+ }
4249
+ function createRichTextEsheetFormDefinition(definition, parties) {
4250
+ validateRichTextEsheetDefinition(definition);
4251
+ const partyMacros = partyFindings(parties);
4252
+ return {
4253
+ id: definition.id,
4254
+ title: definition.title,
4255
+ pages: [
4256
+ {
4257
+ id: `${definition.id}:document`,
4258
+ title: definition.title,
4259
+ fields: definition.sections.map((section) => {
4260
+ const fields = [];
4261
+ if (section.description) {
4262
+ fields.push({
4263
+ id: `${getRichTextEsheetSectionFieldId(section.id)}:description`,
4264
+ fieldType: "display",
4265
+ content: section.description,
4266
+ width: "full"
4267
+ });
4268
+ }
4269
+ const fieldType = section.fieldType ?? "esheetNarrative";
4270
+ fields.push({
4271
+ id: getRichTextEsheetFieldId(section.id),
4272
+ fieldType,
4273
+ question: section.title,
4274
+ required: section.required,
4275
+ width: "full",
4276
+ // Keyed off the resolved type: an explicit 'esheetNarrative'
4277
+ // override keeps its findings, macros and hidden label.
4278
+ _sourceData: fieldType === "esheetNarrative" ? {
4279
+ narrativeHideLabel: true,
4280
+ narrativeFindings: [
4281
+ ...section.findings ?? [],
4282
+ ...partyMacros
4283
+ ]
4284
+ } : void 0
4285
+ });
4286
+ return {
4287
+ id: getRichTextEsheetSectionFieldId(section.id),
4288
+ fieldType: "section",
4289
+ title: section.title,
4290
+ sectionCollapse: "expanded",
4291
+ width: "full",
4292
+ fields
4293
+ };
4294
+ })
4295
+ }
4296
+ ]
4297
+ };
4298
+ }
4299
+ function record(value) {
4300
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4301
+ }
4302
+ function hasValue(value) {
4303
+ if (typeof value === "string") return value.trim().length > 0;
4304
+ if (typeof value === "number" || typeof value === "boolean") return true;
4305
+ if (Array.isArray(value)) return value.some(hasValue);
4306
+ if (record(value)) return Object.values(value).some(hasValue);
4307
+ return false;
4308
+ }
4309
+ function valueText(value) {
4310
+ if (typeof value === "string") return value.trim();
4311
+ if (typeof value === "number" || typeof value === "boolean")
4312
+ return String(value);
4313
+ if (Array.isArray(value))
4314
+ return value.map(valueText).filter(Boolean).join(", ");
4315
+ return "";
4316
+ }
4317
+ function selectedText(selected) {
4318
+ if (Array.isArray(selected))
4319
+ return selected.map(selectedText).filter(Boolean).join(", ");
4320
+ if (!record(selected)) return "";
4321
+ if ("value" in selected) {
4322
+ if (!hasValue(selected.value)) return "";
4323
+ const text = valueText(selected.value);
4324
+ const label = typeof selected.label === "string" ? selected.label.trim() : "";
4325
+ return label && label !== text ? `${label}: ${text}` : text;
4326
+ }
4327
+ return Object.values(selected).map(selectedText).filter(Boolean).join(", ");
4328
+ }
4329
+ function sectionText(responses, sectionId) {
4330
+ const response = responses[getRichTextEsheetFieldId(sectionId)];
4331
+ return valueText(response?.answer) || selectedText(response?.selected);
4332
+ }
4333
+ function markdownText(value) {
4334
+ return value.replace(/&/g, "&amp;").replace(/[\\[\]`*_~<>]/g, "\\$&");
4335
+ }
4336
+ function markdownHeading(value) {
4337
+ return markdownText(value).replace(/\r?\n/g, " \xB7 ");
4338
+ }
4339
+ function markdownBody(value) {
4340
+ return value.split(/\r?\n/).map(
4341
+ (line) => markdownText(line.trim()).replace(/^([#+-])/, "\\$1").replace(/^(\d+)([.)])/, "$1\\$2")
4342
+ ).filter(Boolean).join("\n\n");
4343
+ }
4344
+ function createRichTextEsheetSnapshot(definition, responses, parties) {
4345
+ validateRichTextEsheetDefinition(definition);
4346
+ const documentedSections = definition.sections.filter((section) => sectionText(responses, section.id).length > 0).map((section) => section.id);
4347
+ const errors = definition.sections.filter(
4348
+ (section) => section.required && !documentedSections.includes(section.id)
4349
+ ).map((section) => ({
4350
+ sectionId: section.id,
4351
+ message: `${section.title} is required.`
4352
+ }));
4353
+ const frontMatter = {
4354
+ richTextEsheet: { id: definition.id, title: definition.title },
4355
+ ...parties?.length ? { parties } : {}
4356
+ };
4357
+ const body = definition.sections.filter((section) => documentedSections.includes(section.id)).map(
4358
+ (section) => `## ${markdownHeading(section.title)}
4359
+
4360
+ ${markdownBody(
4361
+ sectionText(responses, section.id)
4362
+ )}`
4363
+ ).join("\n\n");
4364
+ const mdy = `---
4365
+ ${yaml.stringify(frontMatter)}---
4366
+
4367
+ # ${markdownHeading(
4368
+ definition.title
4369
+ )}
4370
+ ${body ? `
4371
+ ${body}
4372
+ ` : ""}`;
4373
+ return { definition, responses, documentedSections, errors, mdy };
4374
+ }
4375
+ var RichTextEsheet = React11__namespace.forwardRef(function RichTextEsheet2({ messages, ...props }, ref) {
4376
+ return /* @__PURE__ */ jsxRuntime.jsx(chunkFSLI5IKP_cjs.MessagesProvider, { messages, children: /* @__PURE__ */ jsxRuntime.jsx(RichTextEsheetSession, { ...props, ref }, props.definition.id) });
4377
+ });
4378
+ function ensureFields2() {
4379
+ registerEsheetSectionFieldType();
4380
+ registerEsheetNarrativeFieldType();
4381
+ }
4382
+ var esheetDefaultsSettled2 = false;
4383
+ var RichTextEsheetSession = React11__namespace.forwardRef(function RichTextEsheetSession2({
4384
+ definition,
4385
+ parties,
4386
+ initialResponses,
4387
+ onChange,
4388
+ onSubmit,
4389
+ readOnly = false,
4390
+ className,
4391
+ ...props
4392
+ }, ref) {
4393
+ ensureFields2();
4394
+ const [fieldsSettled, setFieldsSettled] = React11__namespace.useState(
4395
+ () => esheetDefaultsSettled2
4396
+ );
4397
+ React11__namespace.useLayoutEffect(() => {
4398
+ if (!esheetDefaultsSettled2) {
4399
+ esheetDefaultsSettled2 = true;
4400
+ ensureFields2();
4401
+ }
4402
+ setFieldsSettled(true);
4403
+ }, []);
4404
+ const m = chunkFSLI5IKP_cjs.useMessages();
4405
+ const configJson = JSON.stringify({ definition, parties });
4406
+ const stable = React11__namespace.useMemo(
4407
+ () => JSON.parse(configJson),
4408
+ [configJson]
4409
+ );
4410
+ const form = React11__namespace.useMemo(
4411
+ () => createRichTextEsheetFormDefinition(stable.definition, stable.parties),
4412
+ [stable]
4413
+ );
4414
+ const [initialSeed] = React11__namespace.useState(() => initialResponses ?? {});
4415
+ const [snapshot, setSnapshot] = React11__namespace.useState(
4416
+ () => createRichTextEsheetSnapshot(stable.definition, initialSeed, stable.parties)
4417
+ );
4418
+ const [showErrors, setShowErrors] = React11__namespace.useState(false);
4419
+ const [saving, setSaving] = React11__namespace.useState(false);
4420
+ const submissionPending = React11__namespace.useRef(false);
4421
+ const [saveMessage, setSaveMessage] = React11__namespace.useState("");
4422
+ const renderer$1 = React11__namespace.useRef(null);
4423
+ const root = React11__namespace.useRef(null);
4424
+ const unsubscribe = React11__namespace.useRef(null);
4425
+ const responses = React11__namespace.useRef(initialSeed);
4426
+ const latest = React11__namespace.useRef({ stable, onChange, onSubmit, readOnly, m });
4427
+ latest.current = { stable, onChange, onSubmit, readOnly, m };
4428
+ const rendererInput = React11__namespace.useMemo(
4429
+ () => ({ definition: form, responses: responses.current }),
4430
+ [form]
4431
+ );
4432
+ const currentSnapshot = React11__namespace.useCallback(() => {
4433
+ const { stable: stable2 } = latest.current;
4434
+ return createRichTextEsheetSnapshot(
4435
+ stable2.definition,
4436
+ renderer$1.current?.getRawResponse() ?? responses.current,
4437
+ stable2.parties
4438
+ );
4439
+ }, []);
4440
+ const focusSection = React11__namespace.useCallback((sectionId) => {
4441
+ const fieldId = getRichTextEsheetSectionFieldId(sectionId);
4442
+ const section = Array.from(
4443
+ root.current?.querySelectorAll("[data-field-id]") ?? []
4444
+ ).find((element) => element.dataset.fieldId === fieldId);
4445
+ if (!section) return;
4446
+ section.tabIndex = -1;
4447
+ section.focus({ preventScroll: true });
4448
+ section.scrollIntoView?.({ block: "start", behavior: "auto" });
4449
+ }, []);
4450
+ React11__namespace.useImperativeHandle(
4451
+ ref,
4452
+ () => ({ getSnapshot: currentSnapshot, focusSection }),
4453
+ [currentSnapshot, focusSection]
4454
+ );
4455
+ const handleReady = React11__namespace.useCallback(() => {
4456
+ const store = renderer$1.current?.getFormStore();
4457
+ if (!store) return;
4458
+ unsubscribe.current?.();
4459
+ const publish = () => {
4460
+ responses.current = store.getState().responses;
4461
+ const next = currentSnapshot();
4462
+ setSnapshot(next);
4463
+ latest.current.onChange?.(next);
4464
+ };
4465
+ unsubscribe.current = store.subscribe((state, previous) => {
4466
+ if (state.responses !== previous.responses) {
4467
+ setSaveMessage("");
4468
+ publish();
4469
+ }
4470
+ });
4471
+ publish();
4472
+ }, [currentSnapshot]);
4473
+ React11__namespace.useLayoutEffect(() => {
4474
+ unsubscribe.current?.();
4475
+ unsubscribe.current = null;
4476
+ }, [rendererInput]);
4477
+ React11__namespace.useEffect(() => () => unsubscribe.current?.(), []);
4478
+ const submit = async () => {
4479
+ const context = latest.current;
4480
+ if (!context.onSubmit || context.readOnly || saving || submissionPending.current)
4481
+ return;
4482
+ submissionPending.current = true;
4483
+ try {
4484
+ const next = currentSnapshot();
4485
+ setShowErrors(true);
4486
+ if (next.errors.length > 0) {
4487
+ setSnapshot(next);
4488
+ setSaveMessage(m("Resolve the highlighted sections before saving."));
4489
+ focusSection(next.errors[0].sectionId);
4490
+ return;
4491
+ }
4492
+ renderer$1.current?.getFormStore().getState().setReadOnly(true);
4493
+ setSaving(true);
4494
+ setSaveMessage("");
4495
+ try {
4496
+ await context.onSubmit(next);
4497
+ setSaveMessage(latest.current.m("Document saved."));
4498
+ } catch (error) {
4499
+ setSaveMessage(
4500
+ error instanceof Error ? error.message : latest.current.m(
4501
+ "Unable to save this document. Your draft is still here."
4502
+ )
4503
+ );
4504
+ } finally {
4505
+ renderer$1.current?.getFormStore().getState().setReadOnly(latest.current.readOnly);
4506
+ setSaving(false);
4507
+ }
4508
+ } finally {
4509
+ submissionPending.current = false;
4510
+ }
4511
+ };
4512
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4513
+ "div",
4514
+ {
4515
+ ...props,
4516
+ ref: root,
4517
+ "data-slot": "richtext-esheet",
4518
+ className: chunkS5RVSXDV_cjs.cn(
4519
+ "richtext-esheet bg-background text-foreground min-w-0",
4520
+ className
4521
+ ),
4522
+ children: [
4523
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-wrap items-start justify-between gap-2 px-2 pt-3 pb-2 sm:px-3", children: [
4524
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
4525
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold", children: m(stable.definition.title) }),
4526
+ stable.parties && stable.parties.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "mt-1 space-y-0.5 text-sm", children: stable.parties.map((party, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-x-1.5", children: [
4527
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: m(party.role) }),
4528
+ /* @__PURE__ */ jsxRuntime.jsxs("dd", { dir: "auto", className: "break-words", children: [
4529
+ party.name,
4530
+ party.detail && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground", children: [
4531
+ " ",
4532
+ "(",
4533
+ party.detail,
4534
+ ")"
4535
+ ] })
4536
+ ] })
4537
+ ] }, index)) })
4538
+ ] }),
4539
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-xs", children: /* @__PURE__ */ jsxRuntime.jsx("bdi", { children: m("{count} of {total} sections documented", {
4540
+ count: snapshot.documentedSections.length,
4541
+ total: stable.definition.sections.length
4542
+ }) }) })
4543
+ ] }),
4544
+ /* @__PURE__ */ jsxRuntime.jsxs(
4545
+ "div",
4546
+ {
4547
+ "data-slot": "richtext-esheet-document",
4548
+ className: "min-w-0 px-2 pt-2 pb-4 sm:px-3",
4549
+ children: [
4550
+ showErrors && snapshot.errors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
4551
+ "div",
4552
+ {
4553
+ role: "alert",
4554
+ className: "border-destructive/40 bg-destructive/5 mb-4 rounded-lg border p-3 text-sm",
4555
+ children: [
4556
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-semibold", children: m("Sections need attention") }),
4557
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-2 space-y-1", children: snapshot.errors.map((issue) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
4558
+ "button",
4559
+ {
4560
+ type: "button",
4561
+ className: "text-destructive min-h-11 text-start underline underline-offset-2",
4562
+ onClick: () => focusSection(issue.sectionId),
4563
+ children: m("{section} is required.", {
4564
+ section: stable.definition.sections.find(
4565
+ (section) => section.id === issue.sectionId
4566
+ )?.title ?? issue.sectionId
4567
+ })
4568
+ }
4569
+ ) }, issue.sectionId)) })
4570
+ ]
4571
+ }
4572
+ ),
4573
+ /* @__PURE__ */ jsxRuntime.jsx(EsheetSectionContext.Provider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4574
+ renderer.EsheetRenderer,
4575
+ {
4576
+ ref: renderer$1,
4577
+ formDataInput: rendererInput.definition,
4578
+ initialResponses: rendererInput.responses,
4579
+ onReady: handleReady,
4580
+ strict: true,
4581
+ readOnly: readOnly || saving,
4582
+ fitToContainer: true,
4583
+ topNavigation: false,
4584
+ bottomNavigation: false,
4585
+ className: "richtext-esheet-renderer"
4586
+ },
4587
+ fieldsSettled ? "settled" : "priming"
4588
+ ) })
4589
+ ]
4590
+ }
4591
+ ),
4592
+ /* @__PURE__ */ jsxRuntime.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: [
4593
+ /* @__PURE__ */ jsxRuntime.jsx(
4594
+ "p",
4595
+ {
4596
+ role: "status",
4597
+ "aria-live": "polite",
4598
+ className: "text-muted-foreground min-w-0 text-sm",
4599
+ children: saveMessage || (readOnly ? m("Read-only document") : "")
4600
+ }
4601
+ ),
4602
+ onSubmit && /* @__PURE__ */ jsxRuntime.jsx(
4603
+ chunkFSARTLUE_cjs.Button,
4604
+ {
4605
+ type: "button",
4606
+ className: "min-h-11",
4607
+ disabled: readOnly || saving,
4608
+ leftIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { size: 16, "aria-hidden": "true" }),
4609
+ onClick: () => void submit(),
4610
+ children: m(saving ? "Saving\u2026" : "Save document")
4611
+ }
4612
+ )
4613
+ ] })
4614
+ ]
4615
+ }
4616
+ );
4617
+ });
4140
4618
  function parseValue(answer) {
4141
4619
  if (!answer) return { medications: [] };
4142
4620
  try {
@@ -4167,7 +4645,7 @@ function MedicationListField({
4167
4645
  return definition.medications ?? [];
4168
4646
  }, [response?.answer, definition.medications]);
4169
4647
  return /* @__PURE__ */ jsxRuntime.jsx(
4170
- chunkSCYLVQWA_cjs.MedicationReconciliation,
4648
+ chunkX6HEWIYP_cjs.MedicationReconciliation,
4171
4649
  {
4172
4650
  medications,
4173
4651
  onChange: (next) => onResponse({ answer: JSON.stringify({ medications: next }) }),
@@ -4225,7 +4703,7 @@ function AllergyListField({
4225
4703
  codeLookup
4226
4704
  }) {
4227
4705
  const definition = field.definition;
4228
- const ambientCodeLookup = chunkSCYLVQWA_cjs.useCodeLookupConfig();
4706
+ const ambientCodeLookup = chunkX6HEWIYP_cjs.useCodeLookupConfig();
4229
4707
  const effectiveCodeLookup = codeLookup === false ? void 0 : codeLookup ?? ambientCodeLookup ?? void 0;
4230
4708
  const value = React11__namespace.useMemo(() => {
4231
4709
  if (response?.answer) return parseValue2(response.answer);
@@ -4235,7 +4713,7 @@ function AllergyListField({
4235
4713
  onResponse({ answer: JSON.stringify(next) });
4236
4714
  };
4237
4715
  return /* @__PURE__ */ jsxRuntime.jsx(
4238
- chunkSCYLVQWA_cjs.AllergyManager,
4716
+ chunkX6HEWIYP_cjs.AllergyManager,
4239
4717
  {
4240
4718
  allergies: value.allergies,
4241
4719
  onChange: (allergies) => commit({
@@ -4414,12 +4892,31 @@ exports.EncounterNarrativeField = EncounterNarrativeField;
4414
4892
  exports.EncounterPlanField = EncounterPlanField;
4415
4893
  exports.EncounterVisit = EncounterVisit;
4416
4894
  exports.EncounterVitalsField = EncounterVitalsField;
4895
+ exports.EsheetNarrativeField = EsheetNarrativeField;
4896
+ exports.EsheetSectionContext = EsheetSectionContext;
4897
+ exports.EsheetSectionField = EsheetSectionField;
4417
4898
  exports.MedicationListField = MedicationListField;
4899
+ exports.NARRATIVE_ATTRIBUTES = NARRATIVE_ATTRIBUTES;
4900
+ exports.RichTextEsheet = RichTextEsheet;
4901
+ exports.applyNarrativeFinding = applyNarrativeFinding;
4418
4902
  exports.createEncounterVisitTools = createEncounterVisitTools;
4903
+ exports.createRichTextEsheetFormDefinition = createRichTextEsheetFormDefinition;
4904
+ exports.createRichTextEsheetSnapshot = createRichTextEsheetSnapshot;
4419
4905
  exports.executeEncounterVisitToolCall = executeEncounterVisitToolCall;
4906
+ exports.getNarrativeFindings = getNarrativeFindings;
4907
+ exports.getRichTextEsheetFieldId = getRichTextEsheetFieldId;
4908
+ exports.getRichTextEsheetSectionFieldId = getRichTextEsheetSectionFieldId;
4909
+ exports.hasNarrativeFindingText = hasNarrativeFindingText;
4910
+ exports.narrativeText = narrativeText;
4911
+ exports.partyFindings = partyFindings;
4420
4912
  exports.registerAllergyListFieldType = registerAllergyListFieldType;
4421
4913
  exports.registerEncounterFieldTypes = registerEncounterFieldTypes;
4914
+ exports.registerEsheetNarrativeFieldType = registerEsheetNarrativeFieldType;
4915
+ exports.registerEsheetSectionFieldType = registerEsheetSectionFieldType;
4422
4916
  exports.registerMedicationListFieldType = registerMedicationListFieldType;
4423
4917
  exports.registerMieEsheetFields = registerMieEsheetFields;
4918
+ exports.sanitizeNarrativeHtml = sanitizeNarrativeHtml;
4919
+ exports.validNarrativeFindings = validNarrativeFindings;
4920
+ exports.validateRichTextEsheetDefinition = validateRichTextEsheetDefinition;
4424
4921
  //# sourceMappingURL=esheet.cjs.map
4425
4922
  //# sourceMappingURL=esheet.cjs.map