@mieweb/ui 0.11.0-dev.32 → 0.11.0-dev.34

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 (108) hide show
  1. package/dist/{chunk-PJN4HM2X.cjs → chunk-3O3Q3DX7.cjs} +21 -21
  2. package/dist/{chunk-PJN4HM2X.cjs.map → chunk-3O3Q3DX7.cjs.map} +1 -1
  3. package/dist/{chunk-M42FTWY2.cjs → chunk-46RYJYN2.cjs} +6 -6
  4. package/dist/{chunk-M42FTWY2.cjs.map → chunk-46RYJYN2.cjs.map} +1 -1
  5. package/dist/{chunk-JMAZ2GKT.js → chunk-4GSFVD5V.js} +3 -3
  6. package/dist/{chunk-JMAZ2GKT.js.map → chunk-4GSFVD5V.js.map} +1 -1
  7. package/dist/{chunk-2R3ILG4Y.cjs → chunk-4M2KE4O5.cjs} +7 -7
  8. package/dist/{chunk-2R3ILG4Y.cjs.map → chunk-4M2KE4O5.cjs.map} +1 -1
  9. package/dist/{chunk-L52HKEUD.js → chunk-4SUEBKTY.js} +3 -3
  10. package/dist/{chunk-L52HKEUD.js.map → chunk-4SUEBKTY.js.map} +1 -1
  11. package/dist/{chunk-SCYLVQWA.cjs → chunk-6O6EYKOG.cjs} +130 -874
  12. package/dist/chunk-6O6EYKOG.cjs.map +1 -0
  13. package/dist/{chunk-INMDLVUB.cjs → chunk-6UYMO3PF.cjs} +7 -2
  14. package/dist/chunk-6UYMO3PF.cjs.map +1 -0
  15. package/dist/{chunk-2Z2GFDRS.cjs → chunk-DXNVQQYN.cjs} +4 -4
  16. package/dist/{chunk-2Z2GFDRS.cjs.map → chunk-DXNVQQYN.cjs.map} +1 -1
  17. package/dist/{chunk-JN2CL7MK.js → chunk-EWPNM6OO.js} +25 -5
  18. package/dist/chunk-EWPNM6OO.js.map +1 -0
  19. package/dist/{chunk-JQAGNAY2.js → chunk-EX7Q5545.js} +4 -4
  20. package/dist/{chunk-JQAGNAY2.js.map → chunk-EX7Q5545.js.map} +1 -1
  21. package/dist/{chunk-YG7BR76Y.js → chunk-GMNF4E7B.js} +3 -42
  22. package/dist/chunk-GMNF4E7B.js.map +1 -0
  23. package/dist/chunk-HTZ27DGZ.js +46 -0
  24. package/dist/chunk-HTZ27DGZ.js.map +1 -0
  25. package/dist/{chunk-SNAMNKFH.js → chunk-JEXEFWEM.js} +7 -2
  26. package/dist/chunk-JEXEFWEM.js.map +1 -0
  27. package/dist/{chunk-KIVEYQGA.cjs → chunk-MSX2P5JE.cjs} +2 -48
  28. package/dist/chunk-MSX2P5JE.cjs.map +1 -0
  29. package/dist/{chunk-VV2U36T6.cjs → chunk-MZR634HI.cjs} +9 -9
  30. package/dist/{chunk-VV2U36T6.cjs.map → chunk-MZR634HI.cjs.map} +1 -1
  31. package/dist/{chunk-JCO62XMU.cjs → chunk-NCPDUJKJ.cjs} +43 -26
  32. package/dist/chunk-NCPDUJKJ.cjs.map +1 -0
  33. package/dist/{chunk-DG3CXR24.js → chunk-NW6D6AFL.js} +80 -819
  34. package/dist/chunk-NW6D6AFL.js.map +1 -0
  35. package/dist/{chunk-BP4LXRC6.cjs → chunk-PGJLW2NO.cjs} +7 -7
  36. package/dist/{chunk-BP4LXRC6.cjs.map → chunk-PGJLW2NO.cjs.map} +1 -1
  37. package/dist/chunk-PHYQSCP6.cjs +54 -0
  38. package/dist/chunk-PHYQSCP6.cjs.map +1 -0
  39. package/dist/{chunk-ZFJJMDV3.js → chunk-RTADGBBN.js} +3 -3
  40. package/dist/{chunk-ZFJJMDV3.js.map → chunk-RTADGBBN.js.map} +1 -1
  41. package/dist/{chunk-FQUSSPXX.cjs → chunk-S2CJ57BX.cjs} +25 -5
  42. package/dist/chunk-S2CJ57BX.cjs.map +1 -0
  43. package/dist/{chunk-KSQQBFDQ.js → chunk-SPGP252Q.js} +43 -26
  44. package/dist/chunk-SPGP252Q.js.map +1 -0
  45. package/dist/{chunk-67SD6VPF.cjs → chunk-SVC7NPQ6.cjs} +6 -6
  46. package/dist/{chunk-67SD6VPF.cjs.map → chunk-SVC7NPQ6.cjs.map} +1 -1
  47. package/dist/{chunk-TWQSMUE6.js → chunk-VMEVLBI3.js} +46 -38
  48. package/dist/chunk-VMEVLBI3.js.map +1 -0
  49. package/dist/{chunk-QNJEDBIN.js → chunk-X5HLVACK.js} +4 -4
  50. package/dist/{chunk-QNJEDBIN.js.map → chunk-X5HLVACK.js.map} +1 -1
  51. package/dist/{chunk-LC3JL2HB.cjs → chunk-XEHD2HA5.cjs} +46 -38
  52. package/dist/chunk-XEHD2HA5.cjs.map +1 -0
  53. package/dist/{chunk-R3MJKY63.js → chunk-YGSPIDE4.js} +3 -3
  54. package/dist/{chunk-R3MJKY63.js.map → chunk-YGSPIDE4.js.map} +1 -1
  55. package/dist/{chunk-7LKTIZLR.js → chunk-YR45POH2.js} +3 -3
  56. package/dist/{chunk-7LKTIZLR.js.map → chunk-YR45POH2.js.map} +1 -1
  57. package/dist/components/AlertDialog/index.cjs +6 -6
  58. package/dist/components/AlertDialog/index.js +2 -2
  59. package/dist/components/EmailEditor/index.cjs +14 -12
  60. package/dist/components/EmailEditor/index.js +5 -3
  61. package/dist/components/KeyboardShortcutsOverlay/index.cjs +4 -4
  62. package/dist/components/KeyboardShortcutsOverlay/index.js +2 -2
  63. package/dist/components/MediaEditor/index.cjs +7 -7
  64. package/dist/components/MediaEditor/index.js +2 -2
  65. package/dist/components/MediaFeed/index.cjs +3 -3
  66. package/dist/components/MediaFeed/index.js +2 -2
  67. package/dist/components/Modal/index.cjs +9 -9
  68. package/dist/components/Modal/index.js +1 -1
  69. package/dist/components/RichTextEditor/index.cjs +6 -4
  70. package/dist/components/RichTextEditor/index.js +3 -1
  71. package/dist/components/SuperChat/index.cjs +3 -3
  72. package/dist/components/SuperChat/index.js +2 -2
  73. package/dist/components/Textarea/index.cjs +7 -5
  74. package/dist/components/Textarea/index.js +4 -2
  75. package/dist/esheet.cjs +1119 -1393
  76. package/dist/esheet.cjs.map +1 -1
  77. package/dist/esheet.d.cts +211 -4
  78. package/dist/esheet.d.ts +211 -4
  79. package/dist/esheet.js +946 -1239
  80. package/dist/esheet.js.map +1 -1
  81. package/dist/hooks/index.cjs +41 -40
  82. package/dist/hooks/index.js +2 -1
  83. package/dist/index.cjs +1058 -285
  84. package/dist/index.cjs.map +1 -1
  85. package/dist/index.d.cts +6 -2
  86. package/dist/index.d.ts +6 -2
  87. package/dist/index.js +809 -20
  88. package/dist/index.js.map +1 -1
  89. package/dist/{model-NPW2JMHZ.cjs → model-EFXX5JZN.cjs} +18 -18
  90. package/dist/{model-NPW2JMHZ.cjs.map → model-EFXX5JZN.cjs.map} +1 -1
  91. package/dist/{model-SG5H3TCJ.js → model-TAARSYDY.js} +4 -4
  92. package/dist/{model-SG5H3TCJ.js.map → model-TAARSYDY.js.map} +1 -1
  93. package/dist/styles.css +1 -1
  94. package/dist/tailwind-preset.cjs +4 -4
  95. package/dist/tailwind-preset.js +1 -1
  96. package/package.json +1 -1
  97. package/dist/chunk-DG3CXR24.js.map +0 -1
  98. package/dist/chunk-FQUSSPXX.cjs.map +0 -1
  99. package/dist/chunk-INMDLVUB.cjs.map +0 -1
  100. package/dist/chunk-JCO62XMU.cjs.map +0 -1
  101. package/dist/chunk-JN2CL7MK.js.map +0 -1
  102. package/dist/chunk-KIVEYQGA.cjs.map +0 -1
  103. package/dist/chunk-KSQQBFDQ.js.map +0 -1
  104. package/dist/chunk-LC3JL2HB.cjs.map +0 -1
  105. package/dist/chunk-SCYLVQWA.cjs.map +0 -1
  106. package/dist/chunk-SNAMNKFH.js.map +0 -1
  107. package/dist/chunk-TWQSMUE6.js.map +0 -1
  108. package/dist/chunk-YG7BR76Y.js.map +0 -1
package/dist/esheet.js CHANGED
@@ -1,32 +1,33 @@
1
1
  import './chunk-GADJASST.js';
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
- 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';
5
- import { ProblemList, currentAssertion } from './chunk-KSQQBFDQ.js';
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-X5HLVACK.js';
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-X5HLVACK.js';
4
+ import { useCodeLookupConfig, CodeLookupContext, Assessment, ConditionEditor, AllergyManager, MedicationReconciliation, normalizeConditionCodingSystem } from './chunk-NW6D6AFL.js';
5
+ import { ProblemList, currentAssertion } from './chunk-SPGP252Q.js';
6
6
  import './chunk-YFWDS6MX.js';
7
7
  import { ButtonGroup } from './chunk-BBLEGF5L.js';
8
- import { RadioGroup, Radio } from './chunk-EPSB4WTL.js';
9
- import { Label } from './chunk-I3WGZPI6.js';
8
+ import './chunk-EPSB4WTL.js';
9
+ import './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 } from './chunk-4SUEBKTY.js';
12
+ import './chunk-EWPNM6OO.js';
13
13
  import { MessagesProvider, useMessages } from './chunk-5YOT4DVH.js';
14
14
  import './chunk-OQWZYKJM.js';
15
15
  import { Select } from './chunk-VCI2Y7ST.js';
16
16
  import './chunk-AMYFRY7K.js';
17
17
  import './chunk-OYUIEJPN.js';
18
18
  import './chunk-QYSUYB3S.js';
19
- import { Checkbox } from './chunk-753Z3NXL.js';
20
- import './chunk-TWQSMUE6.js';
19
+ import './chunk-753Z3NXL.js';
20
+ import './chunk-VMEVLBI3.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';
28
29
  import './chunk-WSNMQQ4P.js';
29
- import { dateToDisplayFormat, displayFormatToDateString } from './chunk-RC2YMOMS.js';
30
+ import './chunk-RC2YMOMS.js';
30
31
  import './chunk-ERLPVXOV.js';
31
32
  import './chunk-VSQF22GL.js';
32
33
  import './chunk-BKSPDTX4.js';
@@ -40,12 +41,13 @@ import './chunk-F4N67NAF.js';
40
41
  import { cn } from './chunk-TQONQSJO.js';
41
42
  import './chunk-PX6F3LHL.js';
42
43
  import * as React11 from 'react';
43
- import { Save, X, Trash2, Plus } from 'lucide-react';
44
+ import { Save, Trash2, Plus } from 'lucide-react';
44
45
  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" },
@@ -631,103 +658,6 @@ function EncounterVitalsField({
631
658
  }
632
659
  );
633
660
  }
634
- function FreeClinicalEntry({
635
- placeholder,
636
- translate,
637
- onFreeText
638
- }) {
639
- const [text, setText] = React11.useState("");
640
- return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap gap-2", children: [
641
- /* @__PURE__ */ jsx(
642
- Input,
643
- {
644
- "aria-label": translate("Clinical entry"),
645
- placeholder,
646
- value: text,
647
- onChange: (event) => setText(event.target.value)
648
- }
649
- ),
650
- /* @__PURE__ */ jsx(
651
- Button,
652
- {
653
- type: "button",
654
- disabled: !text.trim(),
655
- onClick: () => {
656
- onFreeText?.(text.trim());
657
- setText("");
658
- },
659
- children: translate("Add")
660
- }
661
- )
662
- ] });
663
- }
664
- function codedOrderSearch(config, requireCoding = true, translate = (message) => message) {
665
- const CodedOrderSearch = ({
666
- domains,
667
- preferDomains,
668
- preferCodetypes,
669
- billableOnly,
670
- placeholder,
671
- onPick,
672
- onFreeText
673
- }) => {
674
- if (!config)
675
- return /* @__PURE__ */ jsx(
676
- FreeClinicalEntry,
677
- {
678
- placeholder,
679
- translate,
680
- onFreeText
681
- }
682
- );
683
- const Lookup = config.component;
684
- const allowedDomains = domains;
685
- return /* @__PURE__ */ jsx(
686
- Lookup,
687
- {
688
- indexUrl: config.indexUrl,
689
- locale: config.locale,
690
- domains: allowedDomains,
691
- preferDomains,
692
- preferCodetypes,
693
- billableOnly,
694
- bare: true,
695
- clearOnSelect: true,
696
- placeholder,
697
- onFreeText: requireCoding ? void 0 : onFreeText,
698
- onSelect: (result) => {
699
- if (!result.fullid?.trim() || !result.codetype?.trim() || !result.fullcode?.trim() || !result.label?.trim() || allowedDomains !== void 0 && !allowedDomains.includes(result.domain))
700
- return;
701
- onPick({
702
- fullid: result.fullid,
703
- label: result.label,
704
- codetype: result.codetype,
705
- fullcode: result.fullcode
706
- });
707
- }
708
- }
709
- );
710
- };
711
- return CodedOrderSearch;
712
- }
713
- var clinicalAssessmentClasses = "rounded-none border-0 bg-transparent shadow-none [&_.border-dashed]:rounded-none [&_.border-dashed]:border-0 [&_.border-dashed]:bg-transparent [&_.border-dashed]:p-0 [&_.min-w-64]:min-w-0 [&_[data-slot=assessment-problem]]:rounded-none [&_[data-slot=assessment-problem]]:border-0 [&_[data-slot=assessment-problem]]:px-0 [&_[data-slot=assessment-problem]>ul]:ms-0 [&_[data-slot=assessment-problem]>ul]:border-0 [&_[data-slot=assessment-problem]>ul]:ps-4 [&_button]:min-h-11 [&_button]:min-w-11 [&_input]:min-h-11 [&_select]:min-h-11 [&>[data-slot=card-content]]:p-0 [&>[data-slot=card-content]>section]:border-0 [&>[data-slot=card-content]>section]:bg-transparent [&>[data-slot=card-content]>section]:px-0";
714
- var clinicalEntryCardClasses = "bg-muted/40 min-w-0 space-y-3 rounded-lg p-3";
715
- function ClinicalAddButton({
716
- label,
717
- onClick
718
- }) {
719
- return /* @__PURE__ */ jsx(
720
- Button,
721
- {
722
- type: "button",
723
- variant: "ghost",
724
- leftIcon: /* @__PURE__ */ jsx(Plus, { "aria-hidden": "true" }),
725
- className: "text-primary-800 hover:bg-primary-50 hover:text-primary-900 active:bg-primary-100 dark:text-primary-400 dark:hover:bg-primary-950 dark:hover:text-primary-200 min-h-11",
726
- onClick,
727
- children: label
728
- }
729
- );
730
- }
731
661
  function InvalidClinicalAnswer({
732
662
  label,
733
663
  answer
@@ -881,9 +811,6 @@ function matchingConditionCode(concern, picked) {
881
811
  )
882
812
  );
883
813
  }
884
- function clinicalRequiresCoding(field) {
885
- return field.definition._sourceData?.encounterRequireCoding ?? true;
886
- }
887
814
  function clinicalAnswerResponse(response, answer) {
888
815
  const next = { ...response, answer, _ai: false };
889
816
  delete next.selected;
@@ -897,37 +824,6 @@ function useEncounterAssertionDate(field) {
897
824
  }, [hostDate]);
898
825
  return { date, setDate, valid: isEncounterDate(date) };
899
826
  }
900
- function EncounterAssertionDateInput({
901
- date,
902
- setDate
903
- }) {
904
- const m = useMessages();
905
- const id = React11.useId();
906
- const [display, setDisplay] = React11.useState(() => dateToDisplayFormat(date));
907
- React11.useEffect(() => {
908
- setDisplay(
909
- (current) => displayFormatToDateString(current) === date ? current : dateToDisplayFormat(date)
910
- );
911
- }, [date]);
912
- return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [
913
- /* @__PURE__ */ jsx("label", { htmlFor: id, className: "text-sm font-medium", children: m("Assertion date") }),
914
- /* @__PURE__ */ jsx(
915
- DateInput,
916
- {
917
- id,
918
- value: display,
919
- onChange: (value) => {
920
- setDisplay(value);
921
- setDate(displayFormatToDateString(value));
922
- },
923
- showCalendar: true,
924
- width: "fixed",
925
- className: "min-h-11"
926
- }
927
- ),
928
- !isEncounterDate(date) && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: m("Enter the encounter date before adding or changing a diagnosis.") })
929
- ] });
930
- }
931
827
  function readAssessment(answer, externalConcerns) {
932
828
  if (!answer) return { value: { concerns: [], items: [], orders: [] } };
933
829
  try {
@@ -940,7 +836,7 @@ function readAssessment(answer, externalConcerns) {
940
836
  } catch {
941
837
  return {
942
838
  value: { concerns: [], items: [], orders: [] },
943
- error: "The saved assessment answer could not be read. The original answer is retained below; correct it through the visit data before editing."
839
+ error: "unreadable"
944
840
  };
945
841
  }
946
842
  }
@@ -961,11 +857,9 @@ function EncounterAssessmentField({
961
857
  onResponse
962
858
  }) {
963
859
  const m = useMessages();
964
- const requireCoding = clinicalRequiresCoding(field);
965
860
  const assertionDate = useEncounterAssertionDate(field);
966
861
  const currentDate = React11.useRef(assertionDate);
967
862
  currentDate.current = assertionDate;
968
- const codeLookup = useCodeLookupConfig();
969
863
  const state = useClinicalFormState(form);
970
864
  const externalConcerns = React11.useMemo(
971
865
  () => getExternalEncounterConditions(state),
@@ -990,10 +884,6 @@ function EncounterAssessmentField({
990
884
  currentValue.current = value;
991
885
  const canEdit = React11.useRef(!readOnly);
992
886
  canEdit.current = !readOnly;
993
- const renderSearch = React11.useMemo(
994
- () => codeLookup || !requireCoding ? codedOrderSearch(codeLookup, requireCoding, m) : void 0,
995
- [codeLookup, requireCoding, m]
996
- );
997
887
  const definition = field.definition;
998
888
  const commit = (update) => {
999
889
  if (!canEdit.current || form.getState().readOnly || form.getState().disposed)
@@ -1008,124 +898,92 @@ function EncounterAssessmentField({
1008
898
  );
1009
899
  };
1010
900
  if (error) {
1011
- return /* @__PURE__ */ jsx(
1012
- "section",
901
+ return /* @__PURE__ */ jsx(InvalidClinicalAnswer, { label: "assessment", answer: response?.answer });
902
+ }
903
+ return /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
904
+ /* @__PURE__ */ jsx(
905
+ Assessment,
1013
906
  {
907
+ concerns,
908
+ items: value.items,
909
+ orders: value.orders,
1014
910
  "aria-label": definition.question ?? m("Assessment and plan"),
1015
- className: "space-y-3",
1016
- children: /* @__PURE__ */ jsxs(
1017
- "div",
1018
- {
1019
- role: "alert",
1020
- className: "border-destructive text-destructive rounded-lg border p-3",
1021
- children: [
1022
- /* @__PURE__ */ jsx("p", { children: m(error) }),
1023
- /* @__PURE__ */ jsx(
1024
- "pre",
911
+ readOnly,
912
+ addFormLabel: "Add assessment or order",
913
+ onAddAssessment: !assertionDate.valid ? void 0 : (pick) => {
914
+ if (!currentDate.current.valid) return;
915
+ const existingConcern = pick.code ? concerns.find(
916
+ (concern) => matchingConditionCode(concern, pick.code)
917
+ ) : void 0;
918
+ const existingAssertion = existingConcern && currentAssertion(existingConcern);
919
+ if (existingConcern && existingAssertion) {
920
+ commit((next) => ({
921
+ ...next,
922
+ items: [
923
+ ...next.items.filter(
924
+ (item) => item.concernId !== existingConcern.concernId
925
+ ),
1025
926
  {
1026
- tabIndex: 0,
1027
- "aria-label": m("Original assessment answer"),
1028
- className: "mt-2 max-h-48 overflow-auto text-xs break-all whitespace-pre-wrap",
1029
- children: response?.answer
927
+ concernId: existingConcern.concernId,
928
+ assertionId: existingAssertion.id
1030
929
  }
1031
- )
1032
- ]
930
+ ]
931
+ }));
932
+ return;
1033
933
  }
1034
- )
1035
- }
1036
- );
1037
- }
1038
- return /* @__PURE__ */ jsx("section", { className: "space-y-2", children: /* @__PURE__ */ jsx(
1039
- Assessment,
1040
- {
1041
- concerns,
1042
- items: value.items,
1043
- orders: value.orders,
1044
- title: null,
1045
- addFormStart: !readOnly && /* @__PURE__ */ jsx(EncounterAssertionDateInput, { ...assertionDate }),
1046
- "aria-label": definition.question ?? m("Assessment and plan"),
1047
- className: "rounded-none border-0 bg-transparent shadow-none [&_.border-dashed]:rounded-none [&_.border-dashed]:border-0 [&_.border-dashed]:bg-transparent [&_.border-dashed]:p-0 [&_.min-w-64]:min-w-0 [&_[data-slot=assessment-problem]]:rounded-none [&_[data-slot=assessment-problem]]:border-0 [&_[data-slot=assessment-problem]]:px-0 [&_[data-slot=assessment-problem]>ul]:ms-0 [&_[data-slot=assessment-problem]>ul]:border-0 [&_[data-slot=assessment-problem]>ul]:ps-4 [&_button]:min-h-11 [&_button]:min-w-11 [&_input]:min-h-11 [&_select]:min-h-11 [&>[data-slot=card-content]]:p-0 [&>[data-slot=card-content]>section]:border-0 [&>[data-slot=card-content]>section]:bg-transparent [&>[data-slot=card-content]>section]:px-0",
1048
- readOnly,
1049
- defaultAddMode: "problem",
1050
- addFormLabel: "Add assessment or order",
1051
- renderOrderSearch: renderSearch,
1052
- onAddAssessment: (codeLookup || !requireCoding) && assertionDate.valid ? (pick) => {
1053
- if (!currentDate.current.valid || requireCoding && !pick.code)
1054
- return;
1055
- const existingConcern = pick.code ? concerns.find(
1056
- (concern) => matchingConditionCode(concern, pick.code)
1057
- ) : void 0;
1058
- const existingAssertion = existingConcern && currentAssertion(existingConcern);
1059
- if (existingConcern && existingAssertion) {
934
+ const concernId = globalThis.crypto.randomUUID();
935
+ const assertionId = globalThis.crypto.randomUUID();
1060
936
  commit((next) => ({
1061
937
  ...next,
1062
- items: [
1063
- ...next.items.filter(
1064
- (item) => item.concernId !== existingConcern.concernId
1065
- ),
938
+ concerns: [
939
+ ...next.concerns,
1066
940
  {
1067
- concernId: existingConcern.concernId,
1068
- assertionId: existingAssertion.id
941
+ concernId,
942
+ clinicalStatus: "active",
943
+ source: "manuallyAdded",
944
+ assertions: [
945
+ {
946
+ id: assertionId,
947
+ date: currentDate.current.date,
948
+ text: pick.label,
949
+ verificationStatus: "unconfirmed",
950
+ coding: pick.code ? [
951
+ {
952
+ system: pick.code.codetype,
953
+ code: pick.code.fullcode,
954
+ display: pick.label
955
+ }
956
+ ] : void 0
957
+ }
958
+ ]
1069
959
  }
1070
- ]
960
+ ],
961
+ items: [...next.items, { concernId, assertionId }]
1071
962
  }));
1072
- return;
1073
- }
1074
- const concernId = globalThis.crypto.randomUUID();
1075
- const assertionId = globalThis.crypto.randomUUID();
1076
- commit((next) => ({
1077
- ...next,
1078
- concerns: [
1079
- ...next.concerns,
1080
- {
1081
- concernId,
1082
- clinicalStatus: "active",
1083
- source: "manuallyAdded",
1084
- assertions: [
1085
- {
1086
- id: assertionId,
1087
- date: currentDate.current.date,
1088
- text: pick.label,
1089
- verificationStatus: "unconfirmed",
1090
- coding: pick.code ? [
1091
- {
1092
- system: pick.code.codetype,
1093
- code: pick.code.fullcode,
1094
- display: pick.label
1095
- }
1096
- ] : void 0
1097
- }
1098
- ]
1099
- }
1100
- ],
1101
- items: [...next.items, { concernId, assertionId }]
1102
- }));
1103
- } : void 0,
1104
- onRemoveAssessment: (item) => {
1105
- const wasLocal = currentValue.current.concerns.some(
1106
- (concern) => concern.concernId === item.concernId
1107
- );
1108
- commit((next) => ({
1109
- ...next,
1110
- // Removing the row also drops its local concern record — nothing
1111
- // else can reference it once the orders below are unlinked, and an
1112
- // orphaned uncoded concern would block Save with no visible repair.
1113
- concerns: next.concerns.filter(
1114
- (concern) => concern.concernId !== item.concernId
1115
- ),
1116
- items: next.items.filter(
1117
- (candidate) => candidate.concernId !== item.concernId
1118
- ),
1119
- orders: next.orders.map(
1120
- (order) => order.concernId === item.concernId ? { ...order, concernId: void 0 } : order
1121
- )
1122
- }));
1123
- if (wasLocal && canEdit.current)
1124
- repairEncounterConcernReferences(form.getState(), item.concernId);
1125
- },
1126
- onAddOrder: codeLookup || !requireCoding ? (item, order) => {
1127
- if (requireCoding && !order.code) return;
1128
- commit((next) => ({
963
+ },
964
+ onRemoveAssessment: (item) => {
965
+ const wasLocal = currentValue.current.concerns.some(
966
+ (concern) => concern.concernId === item.concernId
967
+ );
968
+ commit((next) => ({
969
+ ...next,
970
+ // Removing the row also drops its local concern record — nothing
971
+ // else can reference it once the orders below are unlinked, and an
972
+ // orphaned uncoded concern would block Save with no visible repair.
973
+ concerns: next.concerns.filter(
974
+ (concern) => concern.concernId !== item.concernId
975
+ ),
976
+ items: next.items.filter(
977
+ (candidate) => candidate.concernId !== item.concernId
978
+ ),
979
+ orders: next.orders.map(
980
+ (order) => order.concernId === item.concernId ? { ...order, concernId: void 0 } : order
981
+ )
982
+ }));
983
+ if (wasLocal && canEdit.current)
984
+ repairEncounterConcernReferences(form.getState(), item.concernId);
985
+ },
986
+ onAddOrder: (item, order) => commit((next) => ({
1129
987
  ...next,
1130
988
  orders: [
1131
989
  ...next.orders,
@@ -1135,59 +993,64 @@ function EncounterAssessmentField({
1135
993
  concernId: item?.concernId
1136
994
  }
1137
995
  ]
1138
- }));
1139
- } : void 0,
1140
- onEditOrder: (order, changes) => commit((next) => ({
1141
- ...next,
1142
- orders: next.orders.map(
1143
- (candidate) => candidate.orderId === order.orderId ? { ...candidate, ...changes } : candidate
1144
- )
1145
- })),
1146
- onRemoveOrder: (order) => commit((next) => ({
1147
- ...next,
1148
- orders: next.orders.filter(
1149
- (candidate) => candidate.orderId !== order.orderId
1150
- )
1151
- })),
1152
- onLinkOrder: (order, concernId) => commit((next) => ({
1153
- ...next,
1154
- orders: next.orders.map(
1155
- (candidate) => candidate.orderId === order.orderId ? { ...candidate, concernId } : candidate
1156
- )
1157
- })),
1158
- onReorderItems: (ids) => commit((next) => ({
1159
- ...next,
1160
- items: reorder(next.items, ids, (item) => item.concernId)
1161
- })),
1162
- onReorderOrders: (ids) => commit((next) => ({
1163
- ...next,
1164
- orders: reorder(next.orders, ids, (order) => order.orderId)
1165
- }))
1166
- }
1167
- ) });
996
+ })),
997
+ onEditOrder: (order, changes) => commit((next) => ({
998
+ ...next,
999
+ orders: next.orders.map(
1000
+ (candidate) => candidate.orderId === order.orderId ? { ...candidate, ...changes } : candidate
1001
+ )
1002
+ })),
1003
+ onRemoveOrder: (order) => commit((next) => ({
1004
+ ...next,
1005
+ orders: next.orders.filter(
1006
+ (candidate) => candidate.orderId !== order.orderId
1007
+ )
1008
+ })),
1009
+ onLinkOrder: (order, concernId) => commit((next) => ({
1010
+ ...next,
1011
+ orders: next.orders.map(
1012
+ (candidate) => candidate.orderId === order.orderId ? { ...candidate, concernId } : candidate
1013
+ )
1014
+ })),
1015
+ onReorderItems: (ids) => commit((next) => ({
1016
+ ...next,
1017
+ items: reorder(next.items, ids, (item) => item.concernId)
1018
+ })),
1019
+ onReorderOrders: (ids) => commit((next) => ({
1020
+ ...next,
1021
+ orders: reorder(next.orders, ids, (order) => order.orderId)
1022
+ }))
1023
+ }
1024
+ ),
1025
+ !readOnly && !assertionDate.valid && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: m(
1026
+ "No encounter date \u2014 new diagnoses are disabled; orders can still be added."
1027
+ ) })
1028
+ ] });
1168
1029
  }
1169
1030
 
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";
1031
+ // src/esheet-fields/narrativeFindings.ts
1032
+ var NARRATIVE_ATTRIBUTES = {
1033
+ html: "narrativeHtml",
1034
+ text: "narrativeText",
1035
+ findings: "narrativeFindings",
1036
+ findingsText: "narrativeFindingsText"
1037
+ };
1175
1038
  function escapedTextHtml(text) {
1176
1039
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\n/g, "<br>");
1177
1040
  }
1178
1041
  function hasNarrativeTagsOnly(html) {
1179
1042
  return !html.replace(/<\/?(?:p|div|strong|b|em|i|u|ul|ol|li)\s*>|<br\s*\/?>/gi, "").includes("<");
1180
1043
  }
1181
- function appendedRichAttributes(response, answer, finding) {
1044
+ function appendedRichAttributes(response, answer, finding, attributes) {
1182
1045
  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))
1046
+ const html = response?.attributes?.[attributes.html];
1047
+ if (answer === previous || response?.attributes?.[attributes.text] !== previous || typeof html !== "string" || !hasNarrativeTagsOnly(html))
1185
1048
  return {};
1186
1049
  const aligned = previous.endsWith("\n") ? html.replace(/<br\s*\/?>((?:<\/(?:p|div|ul|ol|li)\s*>|\s)*)$/i, "$1") : html;
1187
1050
  const next = `${previous.trim() ? aligned : ""}<p>${escapedTextHtml(finding.text)}${finding.text.endsWith("\n") ? "<br>" : ""}</p>`;
1188
- return { [HTML_ATTRIBUTE]: next, [TEXT_ATTRIBUTE]: answer };
1051
+ return { [attributes.html]: next, [attributes.text]: answer };
1189
1052
  }
1190
- function validEncounterNarrativeFindings(value) {
1053
+ function validNarrativeFindings(value) {
1191
1054
  if (!Array.isArray(value)) return [];
1192
1055
  const ids = /* @__PURE__ */ new Set();
1193
1056
  return value.flatMap((finding) => {
@@ -1212,7 +1075,7 @@ function validEncounterNarrativeFindings(value) {
1212
1075
  ];
1213
1076
  });
1214
1077
  }
1215
- function hasEncounterNarrativeFindingText(answer, text) {
1078
+ function hasNarrativeFindingText(answer, text) {
1216
1079
  const normalize = (value) => value.replace(/\r\n?/g, "\n").trim();
1217
1080
  return `
1218
1081
  ${normalize(answer)}
@@ -1220,46 +1083,39 @@ ${normalize(answer)}
1220
1083
  ${normalize(text)}
1221
1084
  `);
1222
1085
  }
1223
- function getEncounterNarrativeFindings(response) {
1086
+ function getNarrativeFindings(response, attributes = NARRATIVE_ATTRIBUTES) {
1224
1087
  const answer = response?.answer ?? "";
1225
- if (response?.attributes?.[FINDINGS_TEXT_ATTRIBUTE] !== answer) return [];
1226
- const selected = response.attributes[FINDINGS_ATTRIBUTE];
1088
+ if (response?.attributes?.[attributes.findingsText] !== answer) return [];
1089
+ const selected = response.attributes[attributes.findings];
1227
1090
  if (typeof selected !== "string") return [];
1228
1091
  try {
1229
- return validEncounterNarrativeFindings(JSON.parse(selected)).filter(
1230
- (finding) => hasEncounterNarrativeFindingText(answer, finding.text)
1092
+ return validNarrativeFindings(JSON.parse(selected)).filter(
1093
+ (finding) => hasNarrativeFindingText(answer, finding.text)
1231
1094
  );
1232
1095
  } catch {
1233
1096
  return [];
1234
1097
  }
1235
1098
  }
1236
- function applyEncounterNarrativeFinding(response, finding) {
1237
- const validated = validEncounterNarrativeFindings([finding])[0];
1099
+ function applyNarrativeFinding(response, finding, attributes = NARRATIVE_ATTRIBUTES) {
1100
+ const validated = validNarrativeFindings([finding])[0];
1238
1101
  if (!validated) return response ?? {};
1239
- const selected = getEncounterNarrativeFindings(response);
1102
+ const selected = getNarrativeFindings(response, attributes);
1240
1103
  if (selected.some((item) => item.id === validated.id)) return response ?? {};
1241
1104
  const previousAnswer = response?.answer ?? "";
1242
- const answer = hasEncounterNarrativeFindingText(
1243
- previousAnswer,
1244
- validated.text
1245
- ) ? previousAnswer : `${previousAnswer.trim() ? `${previousAnswer}${previousAnswer.endsWith("\n") ? "" : "\n"}` : ""}${validated.text}`;
1105
+ const answer = hasNarrativeFindingText(previousAnswer, validated.text) ? previousAnswer : `${previousAnswer.trim() ? `${previousAnswer}${previousAnswer.endsWith("\n") ? "" : "\n"}` : ""}${validated.text}`;
1246
1106
  return {
1247
1107
  ...response,
1248
1108
  answer,
1249
1109
  _ai: false,
1250
1110
  attributes: {
1251
1111
  ...response?.attributes,
1252
- ...appendedRichAttributes(response, answer, validated),
1253
- [FINDINGS_ATTRIBUTE]: JSON.stringify([...selected, validated]),
1254
- [FINDINGS_TEXT_ATTRIBUTE]: answer
1112
+ ...appendedRichAttributes(response, answer, validated, attributes),
1113
+ [attributes.findings]: JSON.stringify([...selected, validated]),
1114
+ [attributes.findingsText]: answer
1255
1115
  }
1256
1116
  };
1257
1117
  }
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) {
1118
+ function sanitizeNarrativeHtml(html) {
1263
1119
  return DOMPurify.sanitize(html, {
1264
1120
  ALLOWED_TAGS: [
1265
1121
  "p",
@@ -1286,9 +1142,9 @@ function sanitizeEncounterNarrativeHtml(html) {
1286
1142
  function textToHtml(text) {
1287
1143
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\n/g, "<br>");
1288
1144
  }
1289
- function encounterNarrativeText(html) {
1145
+ function narrativeText(html) {
1290
1146
  const documentBody = document.createElement("div");
1291
- documentBody.innerHTML = sanitizeEncounterNarrativeHtml(html);
1147
+ documentBody.innerHTML = sanitizeNarrativeHtml(html);
1292
1148
  let text = "";
1293
1149
  const lineBreak = () => {
1294
1150
  if (text && !text.endsWith("\n")) text += "\n";
@@ -1326,12 +1182,12 @@ function encounterNarrativeText(html) {
1326
1182
  documentBody.childNodes.forEach(visit);
1327
1183
  return text.replace(/\u00a0/g, " ").replace(/\n$/, "");
1328
1184
  }
1329
- function responseHtml(response) {
1185
+ function responseHtml(response, attributes) {
1330
1186
  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;
1187
+ const responseAttributes = response?.attributes;
1188
+ if (typeof window !== "undefined" && responseAttributes?.[attributes.text] === answer && typeof responseAttributes[attributes.html] === "string") {
1189
+ const html = sanitizeNarrativeHtml(responseAttributes[attributes.html]);
1190
+ if (narrativeText(html) === answer) return html;
1335
1191
  }
1336
1192
  return textToHtml(answer);
1337
1193
  }
@@ -1348,7 +1204,7 @@ function findingShortcuts(findings) {
1348
1204
  }
1349
1205
  return shortcuts;
1350
1206
  }
1351
- function EncounterNarrativeField({
1207
+ function EsheetNarrativeField({
1352
1208
  field,
1353
1209
  response,
1354
1210
  isPreview,
@@ -1356,7 +1212,11 @@ function EncounterNarrativeField({
1356
1212
  isReadOnly,
1357
1213
  isRequired,
1358
1214
  isSoftRequired,
1359
- onResponse
1215
+ onResponse,
1216
+ attributes = NARRATIVE_ATTRIBUTES,
1217
+ findingsKey = "narrativeFindings",
1218
+ hideLabelKey = "narrativeHideLabel",
1219
+ slot = "esheet-narrative"
1360
1220
  }) {
1361
1221
  const m = useMessages();
1362
1222
  const editorRef = React11.useRef(null);
@@ -1366,9 +1226,12 @@ function EncounterNarrativeField({
1366
1226
  const definition = field.definition;
1367
1227
  const question = definition.question || m("Narrative");
1368
1228
  const readOnly = !isPreview || !isEnabled || isReadOnly;
1369
- const html = React11.useMemo(() => responseHtml(response), [response]);
1370
- const findings = validEncounterNarrativeFindings(
1371
- definition._sourceData?.encounterFindings
1229
+ const html = React11.useMemo(
1230
+ () => responseHtml(response, attributes),
1231
+ [response, attributes]
1232
+ );
1233
+ const findings = validNarrativeFindings(
1234
+ definition._sourceData?.[findingsKey]
1372
1235
  );
1373
1236
  const shortcuts = findingShortcuts(findings);
1374
1237
  const macros = findings.length ? [
@@ -1384,18 +1247,23 @@ function EncounterNarrativeField({
1384
1247
  }
1385
1248
  ] : void 0;
1386
1249
  const publish = (value, previous, selected) => {
1387
- const sanitized = sanitizeEncounterNarrativeHtml(value);
1388
- const answer = encounterNarrativeText(sanitized);
1389
- const attributes = {
1250
+ const sanitized = sanitizeNarrativeHtml(value);
1251
+ const answer = narrativeText(sanitized);
1252
+ const nextAttributes = {
1390
1253
  ...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
1254
+ [attributes.html]: sanitized,
1255
+ [attributes.text]: answer,
1256
+ ...selected.length || previous?.attributes?.[attributes.findings] !== void 0 ? {
1257
+ [attributes.findings]: JSON.stringify(selected),
1258
+ [attributes.findingsText]: answer
1396
1259
  } : {}
1397
1260
  };
1398
- const next = { ...previous, answer, _ai: false, attributes };
1261
+ const next = {
1262
+ ...previous,
1263
+ answer,
1264
+ _ai: false,
1265
+ attributes: nextAttributes
1266
+ };
1399
1267
  delete next.selected;
1400
1268
  responseRef.current = next;
1401
1269
  onResponse(next);
@@ -1409,12 +1277,12 @@ function EncounterNarrativeField({
1409
1277
  String(isRequired || isSoftRequired)
1410
1278
  );
1411
1279
  }, [isRequired, isSoftRequired, labelId, readOnly]);
1412
- return /* @__PURE__ */ jsxs("div", { "data-slot": "encounter-narrative", className: "min-w-0", children: [
1280
+ return /* @__PURE__ */ jsxs("div", { "data-slot": slot, className: "min-w-0", children: [
1413
1281
  /* @__PURE__ */ jsxs(
1414
1282
  "div",
1415
1283
  {
1416
1284
  id: labelId,
1417
- className: definition._sourceData?.encounterHideLabel ? "sr-only" : "text-muted-foreground text-sm font-medium",
1285
+ className: definition._sourceData?.[hideLabelKey] ? "sr-only" : "text-muted-foreground text-sm font-medium",
1418
1286
  children: [
1419
1287
  question,
1420
1288
  definition.unit && ` (${definition.unit})`,
@@ -1429,14 +1297,14 @@ function EncounterNarrativeField({
1429
1297
  value: html,
1430
1298
  onChange: (value) => {
1431
1299
  if (readOnly) return;
1432
- const sanitized = sanitizeEncounterNarrativeHtml(value);
1433
- const answer = encounterNarrativeText(sanitized);
1300
+ const sanitized = sanitizeNarrativeHtml(value);
1301
+ const answer = narrativeText(sanitized);
1434
1302
  const previous = responseRef.current;
1435
1303
  publish(
1436
1304
  sanitized,
1437
1305
  previous,
1438
- getEncounterNarrativeFindings(previous).filter(
1439
- (finding) => hasEncounterNarrativeFindingText(answer, finding.text)
1306
+ getNarrativeFindings(previous, attributes).filter(
1307
+ (finding) => hasNarrativeFindingText(answer, finding.text)
1440
1308
  )
1441
1309
  );
1442
1310
  },
@@ -1444,14 +1312,11 @@ function EncounterNarrativeField({
1444
1312
  const finding = findings.find((item) => item.id === macro.id);
1445
1313
  if (!finding || readOnly) return true;
1446
1314
  const previous = responseRef.current;
1447
- const next = applyEncounterNarrativeFinding(previous, finding);
1315
+ const next = applyNarrativeFinding(previous, finding, attributes);
1448
1316
  if (next === previous) return true;
1449
1317
  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));
1318
+ const nextHtml = hasNarrativeFindingText(answer, finding.text) ? responseHtml(previous, attributes) : `${answer.trim() ? responseHtml(previous, attributes) : ""}<p>${textToHtml(finding.text)}</p>`;
1319
+ publish(nextHtml, previous, getNarrativeFindings(next, attributes));
1455
1320
  return true;
1456
1321
  },
1457
1322
  "aria-label": question,
@@ -1489,12 +1354,53 @@ function EncounterNarrativeField({
1489
1354
  showToolbar: m("Show toolbar"),
1490
1355
  hideToolbar: m("Hide toolbar")
1491
1356
  },
1492
- sanitizeHtml: sanitizeEncounterNarrativeHtml,
1357
+ sanitizeHtml: sanitizeNarrativeHtml,
1493
1358
  className: "mt-1"
1494
1359
  }
1495
1360
  )
1496
1361
  ] });
1497
1362
  }
1363
+ function registerEsheetNarrativeFieldType() {
1364
+ if (getFieldComponent("esheetNarrative")) return;
1365
+ registerCustomFieldTypes({
1366
+ esheetNarrative: {
1367
+ label: "Narrative",
1368
+ category: "rich",
1369
+ answerType: "text",
1370
+ hasOptions: false,
1371
+ hasMatrix: false,
1372
+ defaultProps: { question: "Narrative" },
1373
+ component: EsheetNarrativeField
1374
+ }
1375
+ });
1376
+ }
1377
+
1378
+ // src/components/EncounterVisit/narrativeFindings.ts
1379
+ var ENCOUNTER_NARRATIVE_ATTRIBUTES = {
1380
+ html: "encounterNarrativeHtml",
1381
+ text: "encounterNarrativeText",
1382
+ findings: "encounterNarrativeFindings",
1383
+ findingsText: "encounterNarrativeFindingsText"
1384
+ };
1385
+ function applyEncounterNarrativeFinding(response, finding) {
1386
+ return applyNarrativeFinding(
1387
+ response,
1388
+ finding,
1389
+ ENCOUNTER_NARRATIVE_ATTRIBUTES
1390
+ );
1391
+ }
1392
+ function EncounterNarrativeField(props) {
1393
+ return /* @__PURE__ */ jsx(
1394
+ EsheetNarrativeField,
1395
+ {
1396
+ ...props,
1397
+ attributes: ENCOUNTER_NARRATIVE_ATTRIBUTES,
1398
+ findingsKey: "encounterFindings",
1399
+ hideLabelKey: "encounterHideLabel",
1400
+ slot: "encounter-narrative"
1401
+ }
1402
+ );
1403
+ }
1498
1404
  function readValue(answer) {
1499
1405
  if (!answer) return { value: { medications: [] } };
1500
1406
  try {
@@ -1508,250 +1414,6 @@ function readValue(answer) {
1508
1414
  };
1509
1415
  }
1510
1416
  }
1511
- function parseAnswerDate(value) {
1512
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value ?? "");
1513
- return match ? new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])) : null;
1514
- }
1515
- function toAnswerDate(date) {
1516
- if (!date) return void 0;
1517
- const pad = (part) => String(part).padStart(2, "0");
1518
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
1519
- }
1520
- function MedicationCard({
1521
- index,
1522
- medication,
1523
- readOnly,
1524
- requireCoding,
1525
- codeLookup,
1526
- onPatch,
1527
- onRemove
1528
- }) {
1529
- const m = useMessages();
1530
- const id = React11.useId();
1531
- const Lookup = codeLookup?.component;
1532
- const text = (value) => value.trim() && value;
1533
- return /* @__PURE__ */ jsxs("fieldset", { className: clinicalEntryCardClasses, children: [
1534
- /* @__PURE__ */ jsx("legend", { className: "sr-only", children: m("Medication {number}", { number: index }) }),
1535
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1536
- /* @__PURE__ */ jsx("h4", { className: "font-semibold", children: m("Medication {number}", { number: index }) }),
1537
- !readOnly && /* @__PURE__ */ jsx(
1538
- Button,
1539
- {
1540
- type: "button",
1541
- variant: "ghost",
1542
- size: "icon",
1543
- className: "shrink-0",
1544
- "aria-label": m("Remove medication {number}", { number: index }),
1545
- onClick: onRemove,
1546
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4", "aria-hidden": "true" })
1547
- }
1548
- )
1549
- ] }),
1550
- !readOnly && Lookup && codeLookup ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1551
- /* @__PURE__ */ jsx(
1552
- Lookup,
1553
- {
1554
- indexUrl: codeLookup.indexUrl,
1555
- locale: codeLookup.locale,
1556
- domains: ["med"],
1557
- bare: true,
1558
- clearOnSelect: false,
1559
- initialQuery: medication.name || void 0,
1560
- initialSearch: false,
1561
- "aria-label": m("Medication"),
1562
- placeholder: m("Search RxNorm / FDB \u2014 e.g. lisinopril"),
1563
- onSelect: (pick) => {
1564
- if (requireCoding && (!pick.codetype.trim() || !pick.fullcode.trim()))
1565
- return;
1566
- onPatch(lookupToMedicationFields(pick));
1567
- },
1568
- onFreeText: requireCoding ? void 0 : (name) => {
1569
- if (name.trim()) onPatch({ name, code: void 0 });
1570
- }
1571
- }
1572
- ),
1573
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: medication.code ? m("Coded: {system} {code}", {
1574
- system: medication.code.system,
1575
- code: medication.code.code
1576
- }) : medication.name ? m('Uncoded free text: "{name}" \u2014 pick a result to code it', {
1577
- name: medication.name
1578
- }) : requireCoding ? m("Pick a catalog result to code the medication") : m(
1579
- "Pick a result to code the medication, or press Enter for free text"
1580
- ) })
1581
- ] }) : /* @__PURE__ */ jsx(
1582
- Input,
1583
- {
1584
- label: m("Medication"),
1585
- className: "min-h-11",
1586
- value: medication.name,
1587
- readOnly: readOnly || requireCoding,
1588
- onChange: (event) => {
1589
- if (event.target.value.trim())
1590
- onPatch({ name: event.target.value, code: void 0 });
1591
- }
1592
- }
1593
- ),
1594
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [
1595
- /* @__PURE__ */ jsx(
1596
- Input,
1597
- {
1598
- label: m("Strength"),
1599
- className: "min-h-11",
1600
- value: medication.strength ?? "",
1601
- readOnly,
1602
- onChange: (event) => onPatch({ strength: text(event.target.value) || void 0 })
1603
- }
1604
- ),
1605
- /* @__PURE__ */ jsx(
1606
- Input,
1607
- {
1608
- label: m("Dose form"),
1609
- className: "min-h-11",
1610
- value: medication.doseForm ?? "",
1611
- readOnly,
1612
- onChange: (event) => (
1613
- // Quantity unit follows the dose form (tablet → tablet,
1614
- // solution → milliliter, …)
1615
- onPatch({
1616
- doseForm: event.target.value || void 0,
1617
- quantityUnit: doseFormToQuantityUnit(event.target.value)
1618
- })
1619
- )
1620
- }
1621
- ),
1622
- /* @__PURE__ */ jsx(
1623
- Input,
1624
- {
1625
- label: medication.quantityUnit ? `${m("Quantity")} (${medication.quantityUnit}s)` : m("Quantity"),
1626
- className: "min-h-11",
1627
- inputMode: "decimal",
1628
- value: medication.quantity ?? "",
1629
- readOnly,
1630
- onChange: (event) => onPatch({ quantity: text(event.target.value) || void 0 })
1631
- }
1632
- )
1633
- ] }),
1634
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [
1635
- /* @__PURE__ */ jsx(
1636
- Input,
1637
- {
1638
- label: m("Days supply"),
1639
- className: "min-h-11",
1640
- inputMode: "numeric",
1641
- value: medication.daysSupply ?? "",
1642
- readOnly,
1643
- onChange: (event) => onPatch({ daysSupply: text(event.target.value) || void 0 })
1644
- }
1645
- ),
1646
- /* @__PURE__ */ jsx(
1647
- Input,
1648
- {
1649
- label: m("Refills"),
1650
- className: "min-h-11",
1651
- inputMode: "numeric",
1652
- value: medication.refills ?? "",
1653
- readOnly,
1654
- onChange: (event) => onPatch({ refills: text(event.target.value) || void 0 })
1655
- }
1656
- ),
1657
- readOnly ? /* @__PURE__ */ jsx(
1658
- Input,
1659
- {
1660
- label: m("Start \u2013 end date"),
1661
- className: "min-h-11",
1662
- value: [medication.startDate, medication.endDate].filter(Boolean).join(" \u2013 "),
1663
- readOnly: true
1664
- }
1665
- ) : /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1.5", children: [
1666
- /* @__PURE__ */ jsx("span", { className: "text-foreground block text-sm font-medium", children: m("Start \u2013 end date") }),
1667
- /* @__PURE__ */ jsx(
1668
- DateRangePicker,
1669
- {
1670
- className: "w-full [&_[data-slot=date-range-trigger]]:min-h-11 [&_[data-slot=date-range-trigger]]:w-full",
1671
- variant: "responsive",
1672
- showPresets: false,
1673
- placeholder: m("Start \u2013 end date"),
1674
- value: {
1675
- start: parseAnswerDate(medication.startDate),
1676
- end: parseAnswerDate(medication.endDate)
1677
- },
1678
- onChange: (range) => onPatch({
1679
- startDate: toAnswerDate(range.start),
1680
- endDate: toAnswerDate(range.end)
1681
- })
1682
- }
1683
- )
1684
- ] })
1685
- ] }),
1686
- /* @__PURE__ */ jsxs(
1687
- RadioGroup,
1688
- {
1689
- name: `${id}-substitution`,
1690
- label: m("Substitution"),
1691
- value: medication.substitution ?? "0",
1692
- onValueChange: (value) => {
1693
- if (!readOnly) onPatch({ substitution: value });
1694
- },
1695
- orientation: "horizontal",
1696
- size: "sm",
1697
- disabled: readOnly,
1698
- children: [
1699
- /* @__PURE__ */ jsx(Radio, { value: "0", label: m("Substitution permitted") }),
1700
- /* @__PURE__ */ jsx(Radio, { value: "1", label: m("Dispense as written (DAW)") })
1701
- ]
1702
- }
1703
- ),
1704
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1705
- /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-sig`, children: m("Sig (patient directions)") }),
1706
- /* @__PURE__ */ jsx(
1707
- Textarea,
1708
- {
1709
- id: `${id}-sig`,
1710
- rows: 2,
1711
- value: medication.sig ?? "",
1712
- readOnly,
1713
- onChange: (event) => {
1714
- const sig = event.target.value;
1715
- const { route, frequency, prn } = parseSig(sig);
1716
- onPatch({ sig, route, frequency, prn: prn || void 0 });
1717
- }
1718
- }
1719
- ),
1720
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", "aria-live": "polite", children: medication.route || medication.frequency || medication.prn ? /* @__PURE__ */ jsxs(Fragment, { children: [
1721
- m("Derived:"),
1722
- " ",
1723
- [
1724
- medication.route && m("route {route}", { route: medication.route }),
1725
- medication.frequency && m(medication.frequency.toLowerCase()),
1726
- medication.prn && m("PRN (as needed)")
1727
- ].filter(Boolean).join(" \xB7 ")
1728
- ] }) : m("Route, frequency, and PRN are derived from the directions") })
1729
- ] }),
1730
- /* @__PURE__ */ jsx(
1731
- Input,
1732
- {
1733
- label: m("Indication"),
1734
- className: "min-h-11",
1735
- value: medication.indication ?? "",
1736
- readOnly,
1737
- onChange: (event) => onPatch({ indication: text(event.target.value) || void 0 })
1738
- }
1739
- ),
1740
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1741
- /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-pharmacy-notes`, children: m("Pharmacy notes") }),
1742
- /* @__PURE__ */ jsx(
1743
- Textarea,
1744
- {
1745
- id: `${id}-pharmacy-notes`,
1746
- rows: 2,
1747
- value: medication.pharmacyNotes ?? "",
1748
- readOnly,
1749
- onChange: (event) => onPatch({ pharmacyNotes: event.target.value || void 0 })
1750
- }
1751
- )
1752
- ] })
1753
- ] });
1754
- }
1755
1417
  function EncounterMedicationField({
1756
1418
  field,
1757
1419
  form,
@@ -1765,24 +1427,16 @@ function EncounterMedicationField({
1765
1427
  () => readValue(response?.answer),
1766
1428
  [response?.answer]
1767
1429
  );
1768
- const m = useMessages();
1769
- const requireCoding = clinicalRequiresCoding(field);
1770
- const codeLookup = useCodeLookupConfig();
1771
1430
  const state = useClinicalFormState(form);
1772
- const [draft, setDraft] = React11.useState(null);
1773
1431
  const readOnly = !(isPreview && isEnabled) || Boolean(isReadOnly) || state.readOnly || state.disposed || Boolean(error);
1774
1432
  const canEdit = React11.useRef(!readOnly);
1775
1433
  canEdit.current = !readOnly;
1776
1434
  const currentValue = React11.useRef(value);
1777
1435
  currentValue.current = value;
1778
- const definition = field.definition;
1779
- const commit = (update) => {
1436
+ const commit = (medications) => {
1780
1437
  if (!canEdit.current || form.getState().readOnly || form.getState().disposed)
1781
1438
  return;
1782
- const next = {
1783
- ...currentValue.current,
1784
- medications: update(currentValue.current)
1785
- };
1439
+ const next = { ...currentValue.current, medications };
1786
1440
  currentValue.current = next;
1787
1441
  onResponse(
1788
1442
  clinicalAnswerResponse(
@@ -1791,77 +1445,15 @@ function EncounterMedicationField({
1791
1445
  )
1792
1446
  );
1793
1447
  };
1794
- const patchMedication = (medication, patch) => {
1795
- if (draft && medication.id === draft.id) {
1796
- const next = { ...draft, ...patch };
1797
- if (next.name.trim()) {
1798
- setDraft(null);
1799
- commit((current) => [...current.medications, next]);
1800
- } else setDraft(next);
1801
- } else {
1802
- commit(
1803
- (current) => current.medications.map(
1804
- (candidate) => candidate.id === medication.id ? { ...candidate, ...patch } : candidate
1805
- )
1806
- );
1807
- }
1808
- };
1809
- const cards = !readOnly && draft ? [...value.medications, draft] : value.medications;
1448
+ if (error)
1449
+ return /* @__PURE__ */ jsx(InvalidClinicalAnswer, { label: "medication", answer: response?.answer });
1810
1450
  return /* @__PURE__ */ jsx(
1811
- "section",
1451
+ MedicationReconciliation,
1812
1452
  {
1813
- "aria-label": definition.question ?? m("Presenting medications"),
1814
- className: "min-w-0 space-y-3",
1815
- children: error ? /* @__PURE__ */ jsxs("div", { role: "alert", className: "text-destructive space-y-2 text-sm", children: [
1816
- /* @__PURE__ */ jsx("p", { children: m(error) }),
1817
- /* @__PURE__ */ jsx(
1818
- "pre",
1819
- {
1820
- tabIndex: 0,
1821
- "aria-label": m("Original medication answer"),
1822
- className: "max-h-48 overflow-auto text-xs break-all whitespace-pre-wrap",
1823
- children: response?.answer
1824
- }
1825
- )
1826
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1827
- !readOnly && requireCoding && !codeLookup && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: m(
1828
- "Connect a medication terminology catalog to add coded medications."
1829
- ) }),
1830
- requireCoding && value.medications.some((medication) => !medication.code) && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: m(
1831
- "Imported medications without a code are retained. Pick a catalog result to code them."
1832
- ) }),
1833
- readOnly && value.medications.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: m("No medications recorded.") }),
1834
- cards.map((medication, index) => /* @__PURE__ */ jsx(
1835
- MedicationCard,
1836
- {
1837
- index: index + 1,
1838
- medication,
1839
- readOnly,
1840
- requireCoding,
1841
- codeLookup,
1842
- onPatch: (patch) => patchMedication(medication, patch),
1843
- onRemove: () => {
1844
- if (draft && medication.id === draft.id) setDraft(null);
1845
- else
1846
- commit(
1847
- (current) => current.medications.filter(({ id }) => id !== medication.id)
1848
- );
1849
- }
1850
- },
1851
- medication.id
1852
- )),
1853
- !readOnly && !draft && (codeLookup || !requireCoding) && /* @__PURE__ */ jsx(
1854
- ClinicalAddButton,
1855
- {
1856
- label: cards.length > 0 ? m("Add another medication") : m("Add medication"),
1857
- onClick: () => setDraft({
1858
- id: globalThis.crypto.randomUUID(),
1859
- name: "",
1860
- status: "unreconciled"
1861
- })
1862
- }
1863
- )
1864
- ] })
1453
+ medications: value.medications,
1454
+ onChange: commit,
1455
+ inlineAddSearch: true,
1456
+ readOnly
1865
1457
  }
1866
1458
  );
1867
1459
  }
@@ -1878,201 +1470,6 @@ function readValue2(answer) {
1878
1470
  };
1879
1471
  }
1880
1472
  }
1881
- function AllergyCard({
1882
- index,
1883
- allergy,
1884
- readOnly,
1885
- requireCoding,
1886
- codeLookup,
1887
- onPatch,
1888
- onRemove
1889
- }) {
1890
- const m = useMessages();
1891
- const id = React11.useId();
1892
- const Lookup = codeLookup?.component;
1893
- const isDrug = (allergy.type ?? "drug") === "drug";
1894
- const text = (value) => value.trim() && value;
1895
- return /* @__PURE__ */ jsxs("fieldset", { className: clinicalEntryCardClasses, children: [
1896
- /* @__PURE__ */ jsx("legend", { className: "sr-only", children: m("Allergy {number}", { number: index }) }),
1897
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1898
- /* @__PURE__ */ jsx("h4", { className: "font-semibold", children: m("Allergy {number}", { number: index }) }),
1899
- !readOnly && /* @__PURE__ */ jsx(
1900
- Button,
1901
- {
1902
- type: "button",
1903
- variant: "ghost",
1904
- size: "icon",
1905
- className: "shrink-0",
1906
- "aria-label": m("Remove allergy {number}", { number: index }),
1907
- onClick: onRemove,
1908
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4", "aria-hidden": "true" })
1909
- }
1910
- )
1911
- ] }),
1912
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [
1913
- readOnly ? /* @__PURE__ */ jsx(
1914
- Input,
1915
- {
1916
- label: m("Type"),
1917
- className: "min-h-11",
1918
- value: m(ALLERGY_TYPE_LABELS[allergy.type ?? "drug"]),
1919
- readOnly: true
1920
- }
1921
- ) : /* @__PURE__ */ jsx(
1922
- Select,
1923
- {
1924
- label: m("Type"),
1925
- className: "[&_[data-slot=select-trigger]]:min-h-11",
1926
- options: Object.keys(ALLERGY_TYPE_LABELS).map(
1927
- (type) => ({ value: type, label: m(ALLERGY_TYPE_LABELS[type]) })
1928
- ),
1929
- value: allergy.type ?? "drug",
1930
- onValueChange: (next) => {
1931
- const type = next;
1932
- onPatch({
1933
- type,
1934
- ...type !== (allergy.type ?? "drug") && { code: void 0 }
1935
- });
1936
- }
1937
- }
1938
- ),
1939
- /* @__PURE__ */ jsx("div", { className: "min-w-0 sm:col-span-2", children: !readOnly && Lookup && codeLookup && (isDrug || requireCoding) ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1940
- /* @__PURE__ */ jsx("span", { className: "text-foreground block text-sm font-medium", children: m("Allergen") }),
1941
- /* @__PURE__ */ jsx(
1942
- Lookup,
1943
- {
1944
- indexUrl: codeLookup.indexUrl,
1945
- locale: codeLookup.locale,
1946
- domains: isDrug ? ["med"] : ["condition"],
1947
- searchCodetypes: isDrug ? void 0 : ["SNOMED US"],
1948
- bare: true,
1949
- clearOnSelect: false,
1950
- initialQuery: allergy.allergen || void 0,
1951
- initialSearch: false,
1952
- "aria-label": m("Allergen"),
1953
- placeholder: isDrug ? m("Search drug allergens \u2014 e.g. penicillin") : m("Search SNOMED allergen substances"),
1954
- onSelect: (pick) => {
1955
- if (requireCoding && (!pick.codetype.trim() || !pick.fullcode.trim()))
1956
- return;
1957
- onPatch({
1958
- allergen: pick.label,
1959
- code: {
1960
- system: pick.codetype,
1961
- code: pick.fullcode,
1962
- display: pick.label
1963
- }
1964
- });
1965
- },
1966
- onFreeText: requireCoding ? void 0 : (allergen) => {
1967
- if (allergen.trim())
1968
- onPatch({ allergen, code: void 0 });
1969
- }
1970
- }
1971
- ),
1972
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: allergy.code ? m("Coded: {system} {code}", {
1973
- system: allergy.code.system,
1974
- code: allergy.code.code
1975
- }) : allergy.allergen ? m(
1976
- 'Uncoded free text: "{name}" \u2014 pick a result to code it',
1977
- { name: allergy.allergen }
1978
- ) : requireCoding ? m("Pick a catalog result to code the allergen") : m(
1979
- "Pick a result to code the allergen, or press Enter for free text"
1980
- ) })
1981
- ] }) : /* @__PURE__ */ jsx(
1982
- Input,
1983
- {
1984
- label: m("Allergen"),
1985
- className: "min-h-11",
1986
- value: allergy.allergen,
1987
- readOnly: readOnly || requireCoding,
1988
- onChange: (event) => {
1989
- if (event.target.value.trim())
1990
- onPatch({ allergen: event.target.value, code: void 0 });
1991
- }
1992
- }
1993
- ) })
1994
- ] }),
1995
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [
1996
- /* @__PURE__ */ jsx(
1997
- Input,
1998
- {
1999
- label: m("Reaction"),
2000
- className: "min-h-11",
2001
- value: allergy.reaction ?? "",
2002
- readOnly,
2003
- onChange: (event) => onPatch({ reaction: text(event.target.value) || void 0 })
2004
- }
2005
- ),
2006
- /* @__PURE__ */ jsx(
2007
- DateInput,
2008
- {
2009
- label: m("Onset date"),
2010
- className: "min-h-11",
2011
- value: allergy.onsetDate ?? "",
2012
- onChange: (next) => {
2013
- if (!readOnly) onPatch({ onsetDate: next || void 0 });
2014
- }
2015
- }
2016
- )
2017
- ] }),
2018
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: [
2019
- /* @__PURE__ */ jsxs(
2020
- RadioGroup,
2021
- {
2022
- name: `${id}-kind`,
2023
- label: m("Mechanism"),
2024
- description: m(
2025
- "A true immune-mediated allergy, or a non-immune intolerance (e.g. GI upset)"
2026
- ),
2027
- value: allergy.kind ?? "allergy",
2028
- onValueChange: (next) => {
2029
- if (!readOnly) onPatch({ kind: next });
2030
- },
2031
- orientation: "horizontal",
2032
- size: "sm",
2033
- disabled: readOnly,
2034
- children: [
2035
- /* @__PURE__ */ jsx(Radio, { value: "allergy", label: m("Allergy") }),
2036
- /* @__PURE__ */ jsx(Radio, { value: "intolerance", label: m("Intolerance") })
2037
- ]
2038
- }
2039
- ),
2040
- /* @__PURE__ */ jsxs(
2041
- RadioGroup,
2042
- {
2043
- name: `${id}-severity`,
2044
- label: m("Severity"),
2045
- value: allergy.severity ?? "",
2046
- onValueChange: (next) => {
2047
- if (!readOnly)
2048
- onPatch({ severity: next || void 0 });
2049
- },
2050
- orientation: "horizontal",
2051
- size: "sm",
2052
- disabled: readOnly,
2053
- children: [
2054
- /* @__PURE__ */ jsx(Radio, { value: "mild", label: m("Mild") }),
2055
- /* @__PURE__ */ jsx(Radio, { value: "moderate", label: m("Moderate") }),
2056
- /* @__PURE__ */ jsx(Radio, { value: "severe", label: m("Severe") })
2057
- ]
2058
- }
2059
- )
2060
- ] }),
2061
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2062
- /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-note`, children: m("Note") }),
2063
- /* @__PURE__ */ jsx(
2064
- Textarea,
2065
- {
2066
- id: `${id}-note`,
2067
- rows: 2,
2068
- value: allergy.note ?? "",
2069
- readOnly,
2070
- onChange: (event) => onPatch({ note: event.target.value || void 0 })
2071
- }
2072
- )
2073
- ] })
2074
- ] });
2075
- }
2076
1473
  function EncounterAllergyField({
2077
1474
  field,
2078
1475
  form,
@@ -2086,17 +1483,12 @@ function EncounterAllergyField({
2086
1483
  () => readValue2(response?.answer),
2087
1484
  [response?.answer]
2088
1485
  );
2089
- const m = useMessages();
2090
- const requireCoding = clinicalRequiresCoding(field);
2091
- const codeLookup = useCodeLookupConfig();
2092
1486
  const state = useClinicalFormState(form);
2093
- const [draft, setDraft] = React11.useState(null);
2094
1487
  const readOnly = !(isPreview && isEnabled) || Boolean(isReadOnly) || state.readOnly || state.disposed || Boolean(error);
2095
1488
  const canEdit = React11.useRef(!readOnly);
2096
1489
  canEdit.current = !readOnly;
2097
1490
  const currentValue = React11.useRef(value);
2098
1491
  currentValue.current = value;
2099
- const definition = field.definition;
2100
1492
  const commitValue = (update) => {
2101
1493
  if (!canEdit.current || form.getState().readOnly || form.getState().disposed)
2102
1494
  return;
@@ -2109,97 +1501,25 @@ function EncounterAllergyField({
2109
1501
  )
2110
1502
  );
2111
1503
  };
2112
- const commit = (update) => commitValue((current) => {
2113
- const allergies = update(current);
2114
- return {
2115
- ...current,
2116
- allergies,
2117
- // Any recorded allergy clears the no-known-allergies flag.
2118
- noKnownAllergies: allergies.length > 0 ? void 0 : current.noKnownAllergies
2119
- };
2120
- });
2121
- const patchAllergy = (allergy, patch) => {
2122
- if (draft && allergy.id === draft.id) {
2123
- const next = { ...draft, ...patch };
2124
- if (next.allergen.trim()) {
2125
- setDraft(null);
2126
- commit((current) => [...current.allergies, next]);
2127
- } else setDraft(next);
2128
- } else {
2129
- commit(
2130
- (current) => current.allergies.map(
2131
- (candidate) => candidate.id === allergy.id ? { ...candidate, ...patch } : candidate
2132
- )
2133
- );
2134
- }
2135
- };
2136
- const cards = !readOnly && draft ? [...value.allergies, draft] : value.allergies;
1504
+ if (error)
1505
+ return /* @__PURE__ */ jsx(InvalidClinicalAnswer, { label: "allergy", answer: response?.answer });
2137
1506
  return /* @__PURE__ */ jsx(
2138
- "section",
1507
+ AllergyManager,
2139
1508
  {
2140
- "aria-label": definition.question ?? m("Allergies and intolerances"),
2141
- className: "min-w-0 space-y-3",
2142
- children: error ? /* @__PURE__ */ jsxs("div", { role: "alert", className: "text-destructive space-y-2 text-sm", children: [
2143
- /* @__PURE__ */ jsx("p", { children: m(error) }),
2144
- /* @__PURE__ */ jsx(
2145
- "pre",
2146
- {
2147
- tabIndex: 0,
2148
- "aria-label": m("Original allergy answer"),
2149
- className: "max-h-48 overflow-auto text-xs break-all whitespace-pre-wrap",
2150
- children: response?.answer
2151
- }
2152
- )
2153
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2154
- !readOnly && requireCoding && !codeLookup && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: m(
2155
- "Connect an allergen terminology catalog to add coded allergies."
2156
- ) }),
2157
- requireCoding && value.allergies.some((allergy) => !allergy.code) && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: m(
2158
- "Imported allergies without a code are retained. Pick a catalog result to code them."
2159
- ) }),
2160
- readOnly && value.allergies.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: value.noKnownAllergies ? m("No known allergies (NKA).") : m("No allergies recorded.") }),
2161
- cards.map((allergy, index) => /* @__PURE__ */ jsx(
2162
- AllergyCard,
2163
- {
2164
- index: index + 1,
2165
- allergy,
2166
- readOnly,
2167
- requireCoding,
2168
- codeLookup,
2169
- onPatch: (patch) => patchAllergy(allergy, patch),
2170
- onRemove: () => {
2171
- if (draft && allergy.id === draft.id) setDraft(null);
2172
- else
2173
- commit(
2174
- (current) => current.allergies.filter(({ id }) => id !== allergy.id)
2175
- );
2176
- }
2177
- },
2178
- allergy.id
2179
- )),
2180
- !readOnly && value.allergies.length === 0 && !draft && /* @__PURE__ */ jsx(
2181
- Checkbox,
2182
- {
2183
- label: m("No known allergies (NKA)"),
2184
- checked: value.noKnownAllergies ?? false,
2185
- onChange: (event) => commitValue((current) => ({
2186
- ...current,
2187
- noKnownAllergies: event.target.checked
2188
- }))
2189
- }
2190
- ),
2191
- !readOnly && !draft && (codeLookup || !requireCoding) && /* @__PURE__ */ jsx(
2192
- ClinicalAddButton,
2193
- {
2194
- label: cards.length > 0 ? m("Add another allergy") : m("Add allergy"),
2195
- onClick: () => setDraft({
2196
- id: globalThis.crypto.randomUUID(),
2197
- allergen: "",
2198
- type: "drug"
2199
- })
2200
- }
2201
- )
2202
- ] })
1509
+ allergies: value.allergies,
1510
+ onChange: (allergies) => commitValue((current) => ({
1511
+ ...current,
1512
+ allergies,
1513
+ // Any recorded allergy clears the no-known-allergies flag.
1514
+ noKnownAllergies: allergies.length > 0 ? void 0 : current.noKnownAllergies
1515
+ })),
1516
+ noKnownAllergies: value.noKnownAllergies ?? false,
1517
+ onNoKnownAllergiesChange: (noKnownAllergies) => commitValue((current) => ({
1518
+ ...current,
1519
+ noKnownAllergies: current.allergies.length > 0 ? void 0 : noKnownAllergies
1520
+ })),
1521
+ inlineAddSearch: true,
1522
+ readOnly
2203
1523
  }
2204
1524
  );
2205
1525
  }
@@ -2228,14 +1548,11 @@ function EncounterConditionsField({
2228
1548
  [response?.answer]
2229
1549
  );
2230
1550
  const m = useMessages();
2231
- const requireCoding = clinicalRequiresCoding(field);
1551
+ const codeLookup = useCodeLookupConfig();
2232
1552
  const assertionDate = useEncounterAssertionDate(field);
2233
1553
  const currentDate = React11.useRef(assertionDate);
2234
1554
  currentDate.current = assertionDate;
2235
- const [adding, setAdding] = React11.useState(false);
2236
- const [manualEditing, setManualEditing] = React11.useState(false);
2237
1555
  const state = useClinicalFormState(form);
2238
- const codeLookup = useCodeLookupConfig();
2239
1556
  const readOnly = !(isPreview && isEnabled) || Boolean(isReadOnly) || state.readOnly || state.disposed || !value;
2240
1557
  const currentValue = React11.useRef(value);
2241
1558
  const currentResponse = React11.useRef(response);
@@ -2243,211 +1560,210 @@ function EncounterConditionsField({
2243
1560
  const canEdit = React11.useRef(!readOnly);
2244
1561
  currentValue.current = value;
2245
1562
  canEdit.current = !readOnly;
2246
- const [editing, setEditing] = React11.useState(
2247
- null
2248
- );
2249
- const definition = field.definition;
2250
- const label = definition.question ?? m("Conditions");
1563
+ const [editor, setEditor] = React11.useState(null);
2251
1564
  const commit = (update) => {
2252
1565
  if (!canEdit.current || form.getState().readOnly || form.getState().disposed || !currentValue.current)
2253
1566
  return;
1567
+ const next = update(currentValue.current);
1568
+ currentValue.current = next;
2254
1569
  onResponse(
2255
1570
  clinicalAnswerResponse(
2256
1571
  form.getState().responses[field.definition.id] ?? currentResponse.current,
2257
- JSON.stringify(update(currentValue.current))
1572
+ JSON.stringify(next)
2258
1573
  )
2259
1574
  );
2260
1575
  };
2261
- const recordAssertion = (draft, pick) => {
2262
- if (!currentDate.current.valid || requireCoding && !draft.coding?.length)
2263
- return;
1576
+ const saveAssertion = (concernId, draft) => {
1577
+ if (!currentDate.current.valid) return;
1578
+ const assertion = {
1579
+ ...draft,
1580
+ id: globalThis.crypto.randomUUID(),
1581
+ date: currentDate.current.date
1582
+ };
2264
1583
  commit((next) => {
2265
- const assertion = {
2266
- ...draft,
2267
- id: globalThis.crypto.randomUUID(),
2268
- date: currentDate.current.date
2269
- };
2270
- if (editing)
1584
+ if (!concernId)
2271
1585
  return {
2272
1586
  ...next,
2273
- concerns: next.concerns.map(
2274
- (concern) => concern.concernId === editing.concernId ? {
2275
- ...concern,
2276
- assertions: [
2277
- ...concern.assertions,
2278
- {
2279
- ...assertion,
2280
- changeType: draft.changeType ?? "refinement",
2281
- supersedes: currentAssertion(concern)?.id
2282
- }
2283
- ]
2284
- } : concern
2285
- )
1587
+ concerns: [
1588
+ ...next.concerns,
1589
+ {
1590
+ concernId: globalThis.crypto.randomUUID(),
1591
+ clinicalStatus: "active",
1592
+ source: "manuallyAdded",
1593
+ assertions: [assertion]
1594
+ }
1595
+ ]
2286
1596
  };
2287
- if (pick && next.concerns.some((concern) => matchingConditionCode(concern, pick)))
2288
- return next;
2289
1597
  return {
2290
1598
  ...next,
2291
- concerns: [
2292
- ...next.concerns,
2293
- {
2294
- concernId: globalThis.crypto.randomUUID(),
2295
- clinicalStatus: "active",
2296
- source: "manuallyAdded",
2297
- assertions: [assertion]
2298
- }
2299
- ]
1599
+ concerns: next.concerns.map((concern) => {
1600
+ if (concern.concernId !== concernId) return concern;
1601
+ const prior = currentAssertion(concern);
1602
+ const assertions = draft.changeType === "revision" && prior ? concern.assertions.map(
1603
+ (candidate) => candidate.id === prior.id ? { ...candidate, verificationStatus: "refuted" } : candidate
1604
+ ) : concern.assertions;
1605
+ return {
1606
+ ...concern,
1607
+ assertions: [
1608
+ ...assertions,
1609
+ { ...assertion, supersedes: prior?.id }
1610
+ ]
1611
+ };
1612
+ })
2300
1613
  };
2301
1614
  });
2302
- if (canEdit.current) {
2303
- setEditing(null);
2304
- setManualEditing(false);
2305
- }
2306
1615
  };
2307
1616
  if (!value)
2308
- return /* @__PURE__ */ jsx(InvalidClinicalAnswer, { label, answer: response?.answer });
2309
- const editingConcern = editing ? value.concerns.find(({ concernId }) => concernId === editing.concernId) : void 0;
2310
- const Lookup = codeLookup?.component;
2311
- const problemList = /* @__PURE__ */ jsx(
2312
- ProblemList,
1617
+ return /* @__PURE__ */ jsx(InvalidClinicalAnswer, { label: "condition", answer: response?.answer });
1618
+ const editorConcern = editor?.concernId ? value.concerns.find(({ concernId }) => concernId === editor.concernId) : void 0;
1619
+ const canAssert = assertionDate.valid;
1620
+ const addSearch = codeLookup && !readOnly && canAssert ? /* @__PURE__ */ jsx(
1621
+ codeLookup.component,
2313
1622
  {
2314
- concerns: value.concerns,
2315
- title: null,
2316
- readOnly,
2317
- actions: [
2318
- ...assertionDate.valid && (codeLookup || !requireCoding) ? ["refine"] : [],
2319
- "remove"
2320
- ],
2321
- inlineActions: true,
2322
- className: "rounded-none border-0 bg-transparent shadow-none [&_[data-slot=clinical-list-heading]]:hidden [&_button]:min-h-11 [&_button]:min-w-11 [&>[data-slot=card-content]]:space-y-2 [&>[data-slot=card-content]]:p-0",
2323
- onAction: (concern, action) => {
2324
- if (readOnly) return;
2325
- if (action === "remove") {
2326
- setEditing(
2327
- (current) => current?.concernId === concern.concernId ? null : current
2328
- );
2329
- commit((next) => ({
2330
- ...next,
2331
- concerns: next.concerns.filter(
2332
- (candidate) => candidate.concernId !== concern.concernId
2333
- )
2334
- }));
2335
- if (canEdit.current)
2336
- repairEncounterConcernReferences(
2337
- form.getState(),
2338
- concern.concernId
2339
- );
2340
- } else if (action === "refine") {
2341
- setEditing({ concernId: concern.concernId });
2342
- if (!codeLookup) setManualEditing(true);
2343
- }
1623
+ indexUrl: codeLookup.indexUrl,
1624
+ locale: codeLookup.locale,
1625
+ domains: ["condition"],
1626
+ preferCodetypes: ["ICD10"],
1627
+ bare: true,
1628
+ clearOnSelect: true,
1629
+ placeholder: m("Search conditions to add"),
1630
+ onSelect: (result) => {
1631
+ const coded = result.codetype.trim() && result.fullcode.trim();
1632
+ if (coded && (currentValue.current ?? value).concerns.some(
1633
+ (concern) => matchingConditionCode(concern, result)
1634
+ ))
1635
+ return;
1636
+ saveAssertion(void 0, {
1637
+ text: result.label,
1638
+ verificationStatus: "unconfirmed",
1639
+ ...coded ? {
1640
+ coding: [
1641
+ {
1642
+ system: result.codetype,
1643
+ code: result.fullcode,
1644
+ display: result.label,
1645
+ primary: true
1646
+ }
1647
+ ]
1648
+ } : {}
1649
+ });
2344
1650
  },
2345
- onReorder: (ids) => commit((next) => ({
2346
- ...next,
2347
- concerns: reorderClinicalEntries(
2348
- next.concerns,
2349
- ids,
2350
- ({ concernId }) => concernId
2351
- )
2352
- }))
1651
+ onFreeText: (text) => saveAssertion(void 0, {
1652
+ text,
1653
+ verificationStatus: "unconfirmed"
1654
+ })
2353
1655
  }
2354
- );
2355
- return /* @__PURE__ */ jsxs("section", { "aria-label": label, className: "min-w-0 space-y-2", children: [
2356
- !readOnly && (adding || editingConcern || value.concerns.length > 0) && /* @__PURE__ */ jsxs("div", { className: clinicalEntryCardClasses, children: [
2357
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
2358
- /* @__PURE__ */ jsx("h4", { className: "min-w-0 font-semibold break-words", children: editingConcern ? /* @__PURE__ */ jsxs(Fragment, { children: [
2359
- m("Refine"),
2360
- " ",
2361
- currentAssertion(editingConcern)?.text
2362
- ] }) : m("Add condition") }),
2363
- (editingConcern || value.concerns.length === 0) && /* @__PURE__ */ jsx(
2364
- Button,
2365
- {
2366
- type: "button",
2367
- variant: "ghost",
2368
- size: "icon",
2369
- className: "shrink-0",
2370
- "aria-label": m("Cancel"),
2371
- onClick: () => {
2372
- setEditing(null);
2373
- setAdding(false);
2374
- setManualEditing(false);
2375
- },
2376
- children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", "aria-hidden": "true" })
2377
- }
2378
- )
2379
- ] }),
2380
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-end", children: [
2381
- /* @__PURE__ */ jsx(EncounterAssertionDateInput, { ...assertionDate }),
2382
- assertionDate.valid && Lookup && codeLookup && /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 sm:basis-56", children: /* @__PURE__ */ jsx(
2383
- Lookup,
2384
- {
2385
- indexUrl: codeLookup.indexUrl,
2386
- locale: codeLookup.locale,
2387
- domains: ["condition"],
2388
- preferCodetypes: ["ICD10"],
2389
- bare: true,
2390
- clearOnSelect: true,
2391
- initialQuery: editingConcern ? currentAssertion(editingConcern)?.text : "",
2392
- "aria-label": editingConcern ? m("Recode condition") : m("Add condition"),
2393
- placeholder: editingConcern ? m("Search replacement diagnosis\u2026") : m("Search conditions\u2026"),
2394
- onSelect: (pick) => {
2395
- if (!pick.fullid?.trim() || !pick.codetype?.trim() || !pick.fullcode?.trim() || !pick.label?.trim() || pick.domain !== "condition")
2396
- return;
2397
- recordAssertion(
2398
- {
2399
- text: pick.label,
2400
- verificationStatus: "unconfirmed",
2401
- coding: [
2402
- {
2403
- system: pick.codetype,
2404
- code: pick.fullcode,
2405
- display: pick.label,
2406
- primary: true
2407
- }
2408
- ]
2409
- },
2410
- pick
1656
+ ) : void 0;
1657
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1658
+ /* @__PURE__ */ jsx(
1659
+ ProblemList,
1660
+ {
1661
+ concerns: value.concerns,
1662
+ readOnly,
1663
+ actions: canAssert ? ["observe", "refine", "revise", "relate", "resolve", "remove"] : ["relate", "resolve", "remove"],
1664
+ onAction: (concern, action) => {
1665
+ if (action === "remove") {
1666
+ setEditor(null);
1667
+ commit((next) => ({
1668
+ ...next,
1669
+ concerns: next.concerns.filter(
1670
+ (candidate) => candidate.concernId !== concern.concernId
1671
+ )
1672
+ }));
1673
+ if (canEdit.current)
1674
+ repairEncounterConcernReferences(
1675
+ form.getState(),
1676
+ concern.concernId
2411
1677
  );
2412
- }
2413
- },
2414
- editing ? editing.concernId : "add"
2415
- ) })
2416
- ] }),
2417
- assertionDate.valid && !requireCoding && /* @__PURE__ */ jsxs(Fragment, { children: [
2418
- /* @__PURE__ */ jsx(
2419
- Button,
2420
- {
2421
- type: "button",
2422
- onClick: () => {
2423
- setManualEditing(true);
2424
- },
2425
- children: m("Other\u2026")
2426
- }
2427
- ),
2428
- /* @__PURE__ */ jsx(
2429
- ConditionEditor,
2430
- {
2431
- mode: editing ? "refine" : "add",
2432
- open: manualEditing,
2433
- onOpenChange: setManualEditing,
2434
- concern: editingConcern,
2435
- renderCodeSearch: false,
2436
- onSave: (draft) => recordAssertion(draft)
1678
+ } else if (action === "resolve") {
1679
+ commit((next) => ({
1680
+ ...next,
1681
+ concerns: next.concerns.map(
1682
+ (candidate) => candidate.concernId === concern.concernId ? { ...candidate, clinicalStatus: "resolved" } : candidate
1683
+ )
1684
+ }));
1685
+ } else if (action === "observe" || action === "refine" || action === "revise" || action === "relate") {
1686
+ setEditor({ mode: action, concernId: concern.concernId });
2437
1687
  }
2438
- )
2439
- ] }),
2440
- value.concerns.length > 0 && problemList
2441
- ] }),
2442
- !readOnly && !adding && !editingConcern && value.concerns.length === 0 && (codeLookup || !requireCoding) && /* @__PURE__ */ jsx(
2443
- ClinicalAddButton,
1688
+ },
1689
+ addSearch,
1690
+ onAddProblem: addSearch || !canAssert ? void 0 : (text) => saveAssertion(void 0, {
1691
+ text,
1692
+ verificationStatus: "unconfirmed"
1693
+ }),
1694
+ onAdd: canAssert ? () => setEditor({ mode: "add" }) : void 0,
1695
+ onReorder: (ids) => commit((next) => ({
1696
+ ...next,
1697
+ concerns: reorderClinicalEntries(
1698
+ next.concerns,
1699
+ ids,
1700
+ ({ concernId }) => concernId
1701
+ )
1702
+ }))
1703
+ }
1704
+ ),
1705
+ /* @__PURE__ */ jsx(
1706
+ ConditionEditor,
2444
1707
  {
2445
- label: m("Add condition"),
2446
- onClick: () => setAdding(true)
1708
+ mode: editor?.mode ?? "add",
1709
+ open: editor !== null,
1710
+ onOpenChange: (open) => {
1711
+ if (!open) setEditor(null);
1712
+ },
1713
+ concern: editor?.mode === "add" ? void 0 : editorConcern,
1714
+ relatableConcerns: value.concerns.filter(
1715
+ ({ concernId }) => concernId !== editor?.concernId
1716
+ ),
1717
+ onSave: (draft) => {
1718
+ saveAssertion(
1719
+ editor?.mode === "add" ? void 0 : editor?.concernId,
1720
+ draft
1721
+ );
1722
+ setEditor(null);
1723
+ },
1724
+ onRelate: (relationship) => {
1725
+ const target = editor?.concernId;
1726
+ if (target)
1727
+ commit((next) => ({
1728
+ ...next,
1729
+ concerns: next.concerns.map(
1730
+ (concern) => concern.concernId === target ? {
1731
+ ...concern,
1732
+ relationships: [
1733
+ ...concern.relationships ?? [],
1734
+ relationship
1735
+ ]
1736
+ } : concern
1737
+ )
1738
+ }));
1739
+ setEditor(null);
1740
+ },
1741
+ onAddObservation: (text) => {
1742
+ const target = editor?.concernId;
1743
+ if (target && currentDate.current.valid)
1744
+ commit((next) => ({
1745
+ ...next,
1746
+ concerns: next.concerns.map(
1747
+ (concern) => concern.concernId === target ? {
1748
+ ...concern,
1749
+ observations: [
1750
+ ...concern.observations ?? [],
1751
+ {
1752
+ id: globalThis.crypto.randomUUID(),
1753
+ date: currentDate.current.date,
1754
+ text
1755
+ }
1756
+ ]
1757
+ } : concern
1758
+ )
1759
+ }));
1760
+ setEditor(null);
1761
+ }
2447
1762
  }
2448
1763
  ),
2449
- !readOnly && requireCoding && !codeLookup && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: m("Coded condition search is unavailable.") }),
2450
- readOnly && problemList
1764
+ !readOnly && !canAssert && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-2 text-xs", children: m(
1765
+ "No encounter date \u2014 conditions cannot be added or revised until the visit provides one."
1766
+ ) })
2451
1767
  ] });
2452
1768
  }
2453
1769
  function readPlan(answer) {
@@ -2516,9 +1832,7 @@ function EncounterPlanField({
2516
1832
  [response?.answer]
2517
1833
  );
2518
1834
  const m = useMessages();
2519
- const requireCoding = clinicalRequiresCoding(field);
2520
1835
  const state = useClinicalFormState(form);
2521
- const codeLookup = useCodeLookupConfig();
2522
1836
  const readOnly = !(isPreview && isEnabled) || Boolean(isReadOnly) || state.readOnly || state.disposed || !value;
2523
1837
  const currentValue = React11.useRef(value);
2524
1838
  const currentResponse = React11.useRef(response);
@@ -2544,98 +1858,63 @@ function EncounterPlanField({
2544
1858
  )
2545
1859
  );
2546
1860
  };
2547
- const renderSearch = React11.useMemo(
2548
- () => codeLookup || !requireCoding ? codedOrderSearch(codeLookup, requireCoding, m) : void 0,
2549
- [codeLookup, requireCoding, m]
2550
- );
2551
1861
  if (!value)
2552
1862
  return /* @__PURE__ */ jsx(InvalidClinicalAnswer, { label, answer: response?.answer });
2553
- return /* @__PURE__ */ jsxs("section", { "aria-label": label, className: "min-w-0 space-y-2", children: [
2554
- /* @__PURE__ */ jsx(
2555
- Assessment,
2556
- {
2557
- concerns,
2558
- items,
2559
- orders: value.orders,
2560
- title: null,
2561
- className: clinicalAssessmentClasses,
2562
- readOnly,
2563
- rowActions: codeLookup || !requireCoding ? ["add-order"] : [],
2564
- addModes: ["order"],
2565
- addFormLabel: "Add plan order",
2566
- defaultAddMode: "order",
2567
- renderOrderSearch: renderSearch,
2568
- onAddOrder: codeLookup || !requireCoding ? (item, order) => {
2569
- if (requireCoding && !order.code) return;
2570
- commit((next) => ({
2571
- ...next,
2572
- orders: [
2573
- ...next.orders,
2574
- {
2575
- ...order,
2576
- orderId: globalThis.crypto.randomUUID(),
2577
- concernId: item?.concernId
2578
- }
2579
- ]
2580
- }));
2581
- } : void 0,
2582
- onEditOrder: (order, changes) => commit((next) => ({
2583
- ...next,
2584
- orders: next.orders.map(
2585
- (candidate) => candidate.orderId === order.orderId ? { ...candidate, ...changes } : candidate
2586
- )
2587
- })),
2588
- onRemoveOrder: (order) => commit((next) => ({
2589
- ...next,
2590
- orders: next.orders.filter(
2591
- (candidate) => candidate.orderId !== order.orderId
2592
- )
2593
- })),
2594
- onLinkOrder: (order, concernId) => commit((next) => ({
2595
- ...next,
2596
- orders: next.orders.map(
2597
- (candidate) => candidate.orderId === order.orderId ? { ...candidate, concernId } : candidate
2598
- )
2599
- })),
2600
- onReorderOrders: (ids) => commit((next) => ({
2601
- ...next,
2602
- orders: reorderClinicalEntries(
2603
- next.orders,
2604
- ids,
2605
- ({ orderId }) => orderId
2606
- )
2607
- }))
2608
- }
2609
- ),
2610
- !readOnly && requireCoding && !codeLookup && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: m("Coded order search is unavailable.") })
2611
- ] });
2612
- }
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
- ] });
1863
+ return /* @__PURE__ */ jsx("section", { "aria-label": label, className: "min-w-0 space-y-2", children: /* @__PURE__ */ jsx(
1864
+ Assessment,
1865
+ {
1866
+ concerns,
1867
+ items,
1868
+ orders: value.orders,
1869
+ title: label,
1870
+ readOnly,
1871
+ rowActions: ["add-order"],
1872
+ addModes: ["order"],
1873
+ addFormLabel: "Add plan order",
1874
+ onAddOrder: (item, order) => commit((next) => ({
1875
+ ...next,
1876
+ orders: [
1877
+ ...next.orders,
1878
+ {
1879
+ ...order,
1880
+ orderId: globalThis.crypto.randomUUID(),
1881
+ concernId: item?.concernId
1882
+ }
1883
+ ]
1884
+ })),
1885
+ onEditOrder: (order, changes) => commit((next) => ({
1886
+ ...next,
1887
+ orders: next.orders.map(
1888
+ (candidate) => candidate.orderId === order.orderId ? { ...candidate, ...changes } : candidate
1889
+ )
1890
+ })),
1891
+ onRemoveOrder: (order) => commit((next) => ({
1892
+ ...next,
1893
+ orders: next.orders.filter(
1894
+ (candidate) => candidate.orderId !== order.orderId
1895
+ )
1896
+ })),
1897
+ onLinkOrder: (order, concernId) => commit((next) => ({
1898
+ ...next,
1899
+ orders: next.orders.map(
1900
+ (candidate) => candidate.orderId === order.orderId ? { ...candidate, concernId } : candidate
1901
+ )
1902
+ })),
1903
+ onReorderOrders: (ids) => commit((next) => ({
1904
+ ...next,
1905
+ orders: reorderClinicalEntries(
1906
+ next.orders,
1907
+ ids,
1908
+ ({ orderId }) => orderId
1909
+ )
1910
+ }))
1911
+ }
1912
+ ) });
2632
1913
  }
2633
1914
 
2634
1915
  // src/components/EncounterVisit/fields.ts
2635
1916
  function registerEncounterFieldTypes() {
2636
- const section = getFieldComponent("section");
2637
- if (section === void 0 || section === SectionField)
2638
- registerFieldComponents({ section: EncounterSectionField });
1917
+ registerEsheetSectionFieldType();
2639
1918
  const clinicalFields = {
2640
1919
  encounterMedications: {
2641
1920
  label: "Encounter medications",
@@ -4056,7 +3335,7 @@ var EncounterVisitSession = React11.forwardRef(function EncounterVisitSession2({
4056
3335
  }
4057
3336
  )
4058
3337
  ] }),
4059
- /* @__PURE__ */ jsx(EncounterSectionContext.Provider, { value: true, children: /* @__PURE__ */ jsx(
3338
+ /* @__PURE__ */ jsx(EsheetSectionContext.Provider, { value: true, children: /* @__PURE__ */ jsx(
4060
3339
  EsheetRenderer,
4061
3340
  {
4062
3341
  ref: renderer,
@@ -4114,6 +3393,434 @@ var EncounterVisitSession = React11.forwardRef(function EncounterVisitSession2({
4114
3393
  }
4115
3394
  );
4116
3395
  });
3396
+ function getRichTextEsheetFieldId(sectionId) {
3397
+ return `richtext:${encodeURIComponent(sectionId)}:value`;
3398
+ }
3399
+ function getRichTextEsheetSectionFieldId(sectionId) {
3400
+ return `richtext:${encodeURIComponent(sectionId)}:section`;
3401
+ }
3402
+ function nonempty(value) {
3403
+ return typeof value === "string" && value.trim().length > 0;
3404
+ }
3405
+ function invalid(message) {
3406
+ throw new Error(`Invalid RichTextEsheet definition: ${message}`);
3407
+ }
3408
+ function validateRichTextEsheetDefinition(definition) {
3409
+ if (!definition || typeof definition !== "object")
3410
+ invalid("expected an object");
3411
+ if (!nonempty(definition.id) || !nonempty(definition.title))
3412
+ invalid("id and title must be nonempty strings");
3413
+ if (!Array.isArray(definition.sections) || definition.sections.length === 0)
3414
+ invalid("at least one section is required");
3415
+ const sectionIds = /* @__PURE__ */ new Set();
3416
+ for (const section of definition.sections) {
3417
+ if (!section || typeof section !== "object")
3418
+ invalid("expected a section object");
3419
+ if (!nonempty(section.id) || !nonempty(section.title))
3420
+ invalid("each section requires an id and title");
3421
+ if (sectionIds.has(section.id))
3422
+ invalid(`duplicate section id ${section.id}`);
3423
+ sectionIds.add(section.id);
3424
+ if (section.description !== void 0 && typeof section.description !== "string")
3425
+ invalid(`invalid description in ${section.id}`);
3426
+ if (section.required !== void 0 && typeof section.required !== "boolean")
3427
+ invalid(`required must be boolean in ${section.id}`);
3428
+ if (section.fieldType !== void 0 && !nonempty(section.fieldType))
3429
+ invalid(`fieldType must be a nonempty string in ${section.id}`);
3430
+ if (section.findings === void 0) continue;
3431
+ if (!Array.isArray(section.findings))
3432
+ invalid(`findings must be an array in ${section.id}`);
3433
+ const findingIds = /* @__PURE__ */ new Set();
3434
+ for (const finding of section.findings) {
3435
+ if (!finding || typeof finding !== "object" || !nonempty(finding.id) || !nonempty(finding.label) || !nonempty(finding.text))
3436
+ invalid(`findings require id, label and text in ${section.id}`);
3437
+ if (findingIds.has(finding.id))
3438
+ invalid(`duplicate finding id ${finding.id} in ${section.id}`);
3439
+ findingIds.add(finding.id);
3440
+ }
3441
+ }
3442
+ }
3443
+ function partyFindings(parties) {
3444
+ return (parties ?? []).map((party) => ({
3445
+ // Content-derived identity: editing or reordering a party yields a new
3446
+ // macro id, so the updated text stays insertable after an earlier
3447
+ // version was applied (selection dedupe is by id).
3448
+ id: `party:${encodeURIComponent(party.role)}:${encodeURIComponent(
3449
+ party.name
3450
+ )}:${encodeURIComponent(party.detail ?? "")}`,
3451
+ label: `${party.role}: ${party.name}`,
3452
+ text: party.detail ? `${party.name} (${party.detail})` : party.name
3453
+ }));
3454
+ }
3455
+ function createRichTextEsheetFormDefinition(definition, parties) {
3456
+ validateRichTextEsheetDefinition(definition);
3457
+ const partyMacros = partyFindings(parties);
3458
+ return {
3459
+ id: definition.id,
3460
+ title: definition.title,
3461
+ pages: [
3462
+ {
3463
+ id: `${definition.id}:document`,
3464
+ title: definition.title,
3465
+ fields: definition.sections.map((section) => {
3466
+ const fields = [];
3467
+ if (section.description) {
3468
+ fields.push({
3469
+ id: `${getRichTextEsheetSectionFieldId(section.id)}:description`,
3470
+ fieldType: "display",
3471
+ content: section.description,
3472
+ width: "full"
3473
+ });
3474
+ }
3475
+ const fieldType = section.fieldType ?? "esheetNarrative";
3476
+ fields.push({
3477
+ id: getRichTextEsheetFieldId(section.id),
3478
+ fieldType,
3479
+ question: section.title,
3480
+ required: section.required,
3481
+ width: "full",
3482
+ // Keyed off the resolved type: an explicit 'esheetNarrative'
3483
+ // override keeps its findings, macros and hidden label.
3484
+ _sourceData: fieldType === "esheetNarrative" ? {
3485
+ narrativeHideLabel: true,
3486
+ narrativeFindings: [
3487
+ ...section.findings ?? [],
3488
+ ...partyMacros
3489
+ ]
3490
+ } : void 0
3491
+ });
3492
+ return {
3493
+ id: getRichTextEsheetSectionFieldId(section.id),
3494
+ fieldType: "section",
3495
+ title: section.title,
3496
+ sectionCollapse: "expanded",
3497
+ width: "full",
3498
+ fields
3499
+ };
3500
+ })
3501
+ }
3502
+ ]
3503
+ };
3504
+ }
3505
+ function record(value) {
3506
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3507
+ }
3508
+ function hasValue(value) {
3509
+ if (typeof value === "string") return value.trim().length > 0;
3510
+ if (typeof value === "number" || typeof value === "boolean") return true;
3511
+ if (Array.isArray(value)) return value.some(hasValue);
3512
+ if (record(value)) return Object.values(value).some(hasValue);
3513
+ return false;
3514
+ }
3515
+ function valueText(value) {
3516
+ if (typeof value === "string") return value.trim();
3517
+ if (typeof value === "number" || typeof value === "boolean")
3518
+ return String(value);
3519
+ if (Array.isArray(value))
3520
+ return value.map(valueText).filter(Boolean).join(", ");
3521
+ return "";
3522
+ }
3523
+ function selectedText(selected) {
3524
+ if (Array.isArray(selected))
3525
+ return selected.map(selectedText).filter(Boolean).join(", ");
3526
+ if (!record(selected)) return "";
3527
+ if ("value" in selected) {
3528
+ if (!hasValue(selected.value)) return "";
3529
+ const text = valueText(selected.value);
3530
+ const label = typeof selected.label === "string" ? selected.label.trim() : "";
3531
+ return label && label !== text ? `${label}: ${text}` : text;
3532
+ }
3533
+ return Object.values(selected).map(selectedText).filter(Boolean).join(", ");
3534
+ }
3535
+ function sectionText(responses, sectionId) {
3536
+ const response = responses[getRichTextEsheetFieldId(sectionId)];
3537
+ return valueText(response?.answer) || selectedText(response?.selected);
3538
+ }
3539
+ function markdownText(value) {
3540
+ return value.replace(/&/g, "&amp;").replace(/[\\[\]`*_~<>]/g, "\\$&");
3541
+ }
3542
+ function markdownHeading(value) {
3543
+ return markdownText(value).replace(/\r?\n/g, " \xB7 ");
3544
+ }
3545
+ function markdownBody(value) {
3546
+ return value.split(/\r?\n/).map(
3547
+ (line) => markdownText(line.trim()).replace(/^([#+-])/, "\\$1").replace(/^(\d+)([.)])/, "$1\\$2")
3548
+ ).filter(Boolean).join("\n\n");
3549
+ }
3550
+ function createRichTextEsheetSnapshot(definition, responses, parties) {
3551
+ validateRichTextEsheetDefinition(definition);
3552
+ const documentedSections = definition.sections.filter((section) => sectionText(responses, section.id).length > 0).map((section) => section.id);
3553
+ const errors = definition.sections.filter(
3554
+ (section) => section.required && !documentedSections.includes(section.id)
3555
+ ).map((section) => ({
3556
+ sectionId: section.id,
3557
+ message: `${section.title} is required.`
3558
+ }));
3559
+ const frontMatter = {
3560
+ richTextEsheet: { id: definition.id, title: definition.title },
3561
+ ...parties?.length ? { parties } : {}
3562
+ };
3563
+ const body = definition.sections.filter((section) => documentedSections.includes(section.id)).map(
3564
+ (section) => `## ${markdownHeading(section.title)}
3565
+
3566
+ ${markdownBody(
3567
+ sectionText(responses, section.id)
3568
+ )}`
3569
+ ).join("\n\n");
3570
+ const mdy = `---
3571
+ ${stringify(frontMatter)}---
3572
+
3573
+ # ${markdownHeading(
3574
+ definition.title
3575
+ )}
3576
+ ${body ? `
3577
+ ${body}
3578
+ ` : ""}`;
3579
+ return { definition, responses, documentedSections, errors, mdy };
3580
+ }
3581
+ var RichTextEsheet = React11.forwardRef(function RichTextEsheet2({ messages, ...props }, ref) {
3582
+ return /* @__PURE__ */ jsx(MessagesProvider, { messages, children: /* @__PURE__ */ jsx(RichTextEsheetSession, { ...props, ref }, props.definition.id) });
3583
+ });
3584
+ function ensureFields2() {
3585
+ registerEsheetSectionFieldType();
3586
+ registerEsheetNarrativeFieldType();
3587
+ }
3588
+ var esheetDefaultsSettled2 = false;
3589
+ var RichTextEsheetSession = React11.forwardRef(function RichTextEsheetSession2({
3590
+ definition,
3591
+ parties,
3592
+ initialResponses,
3593
+ onChange,
3594
+ onSubmit,
3595
+ readOnly = false,
3596
+ className,
3597
+ ...props
3598
+ }, ref) {
3599
+ ensureFields2();
3600
+ const [fieldsSettled, setFieldsSettled] = React11.useState(
3601
+ () => esheetDefaultsSettled2
3602
+ );
3603
+ React11.useLayoutEffect(() => {
3604
+ if (!esheetDefaultsSettled2) {
3605
+ esheetDefaultsSettled2 = true;
3606
+ ensureFields2();
3607
+ }
3608
+ setFieldsSettled(true);
3609
+ }, []);
3610
+ const m = useMessages();
3611
+ const configJson = JSON.stringify({ definition, parties });
3612
+ const stable = React11.useMemo(
3613
+ () => JSON.parse(configJson),
3614
+ [configJson]
3615
+ );
3616
+ const form = React11.useMemo(
3617
+ () => createRichTextEsheetFormDefinition(stable.definition, stable.parties),
3618
+ [stable]
3619
+ );
3620
+ const [initialSeed] = React11.useState(() => initialResponses ?? {});
3621
+ const [snapshot, setSnapshot] = React11.useState(
3622
+ () => createRichTextEsheetSnapshot(stable.definition, initialSeed, stable.parties)
3623
+ );
3624
+ const [showErrors, setShowErrors] = React11.useState(false);
3625
+ const [saving, setSaving] = React11.useState(false);
3626
+ const submissionPending = React11.useRef(false);
3627
+ const [saveMessage, setSaveMessage] = React11.useState("");
3628
+ const renderer = React11.useRef(null);
3629
+ const root = React11.useRef(null);
3630
+ const unsubscribe = React11.useRef(null);
3631
+ const responses = React11.useRef(initialSeed);
3632
+ const latest = React11.useRef({ stable, onChange, onSubmit, readOnly, m });
3633
+ latest.current = { stable, onChange, onSubmit, readOnly, m };
3634
+ const rendererInput = React11.useMemo(
3635
+ () => ({ definition: form, responses: responses.current }),
3636
+ [form]
3637
+ );
3638
+ const currentSnapshot = React11.useCallback(() => {
3639
+ const { stable: stable2 } = latest.current;
3640
+ return createRichTextEsheetSnapshot(
3641
+ stable2.definition,
3642
+ renderer.current?.getRawResponse() ?? responses.current,
3643
+ stable2.parties
3644
+ );
3645
+ }, []);
3646
+ const focusSection = React11.useCallback((sectionId) => {
3647
+ const fieldId = getRichTextEsheetSectionFieldId(sectionId);
3648
+ const section = Array.from(
3649
+ root.current?.querySelectorAll("[data-field-id]") ?? []
3650
+ ).find((element) => element.dataset.fieldId === fieldId);
3651
+ if (!section) return;
3652
+ section.tabIndex = -1;
3653
+ section.focus({ preventScroll: true });
3654
+ section.scrollIntoView?.({ block: "start", behavior: "auto" });
3655
+ }, []);
3656
+ React11.useImperativeHandle(
3657
+ ref,
3658
+ () => ({ getSnapshot: currentSnapshot, focusSection }),
3659
+ [currentSnapshot, focusSection]
3660
+ );
3661
+ const handleReady = React11.useCallback(() => {
3662
+ const store = renderer.current?.getFormStore();
3663
+ if (!store) return;
3664
+ unsubscribe.current?.();
3665
+ const publish = () => {
3666
+ responses.current = store.getState().responses;
3667
+ const next = currentSnapshot();
3668
+ setSnapshot(next);
3669
+ latest.current.onChange?.(next);
3670
+ };
3671
+ unsubscribe.current = store.subscribe((state, previous) => {
3672
+ if (state.responses !== previous.responses) {
3673
+ setSaveMessage("");
3674
+ publish();
3675
+ }
3676
+ });
3677
+ publish();
3678
+ }, [currentSnapshot]);
3679
+ React11.useLayoutEffect(() => {
3680
+ unsubscribe.current?.();
3681
+ unsubscribe.current = null;
3682
+ }, [rendererInput]);
3683
+ React11.useEffect(() => () => unsubscribe.current?.(), []);
3684
+ const submit = async () => {
3685
+ const context = latest.current;
3686
+ if (!context.onSubmit || context.readOnly || saving || submissionPending.current)
3687
+ return;
3688
+ submissionPending.current = true;
3689
+ try {
3690
+ const next = currentSnapshot();
3691
+ setShowErrors(true);
3692
+ if (next.errors.length > 0) {
3693
+ setSnapshot(next);
3694
+ setSaveMessage(m("Resolve the highlighted sections before saving."));
3695
+ focusSection(next.errors[0].sectionId);
3696
+ return;
3697
+ }
3698
+ renderer.current?.getFormStore().getState().setReadOnly(true);
3699
+ setSaving(true);
3700
+ setSaveMessage("");
3701
+ try {
3702
+ await context.onSubmit(next);
3703
+ setSaveMessage(latest.current.m("Document saved."));
3704
+ } catch (error) {
3705
+ setSaveMessage(
3706
+ error instanceof Error ? error.message : latest.current.m(
3707
+ "Unable to save this document. Your draft is still here."
3708
+ )
3709
+ );
3710
+ } finally {
3711
+ renderer.current?.getFormStore().getState().setReadOnly(latest.current.readOnly);
3712
+ setSaving(false);
3713
+ }
3714
+ } finally {
3715
+ submissionPending.current = false;
3716
+ }
3717
+ };
3718
+ return /* @__PURE__ */ jsxs(
3719
+ "div",
3720
+ {
3721
+ ...props,
3722
+ ref: root,
3723
+ "data-slot": "richtext-esheet",
3724
+ className: cn(
3725
+ "richtext-esheet bg-background text-foreground min-w-0",
3726
+ className
3727
+ ),
3728
+ children: [
3729
+ /* @__PURE__ */ jsxs("header", { className: "flex flex-wrap items-start justify-between gap-2 px-2 pt-3 pb-2 sm:px-3", children: [
3730
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
3731
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: m(stable.definition.title) }),
3732
+ 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: [
3733
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: m(party.role) }),
3734
+ /* @__PURE__ */ jsxs("dd", { dir: "auto", className: "break-words", children: [
3735
+ party.name,
3736
+ party.detail && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
3737
+ " ",
3738
+ "(",
3739
+ party.detail,
3740
+ ")"
3741
+ ] })
3742
+ ] })
3743
+ ] }, index)) })
3744
+ ] }),
3745
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: /* @__PURE__ */ jsx("bdi", { children: m("{count} of {total} sections documented", {
3746
+ count: snapshot.documentedSections.length,
3747
+ total: stable.definition.sections.length
3748
+ }) }) })
3749
+ ] }),
3750
+ /* @__PURE__ */ jsxs(
3751
+ "div",
3752
+ {
3753
+ "data-slot": "richtext-esheet-document",
3754
+ className: "min-w-0 px-2 pt-2 pb-4 sm:px-3",
3755
+ children: [
3756
+ showErrors && snapshot.errors.length > 0 && /* @__PURE__ */ jsxs(
3757
+ "div",
3758
+ {
3759
+ role: "alert",
3760
+ className: "border-destructive/40 bg-destructive/5 mb-4 rounded-lg border p-3 text-sm",
3761
+ children: [
3762
+ /* @__PURE__ */ jsx("p", { className: "font-semibold", children: m("Sections need attention") }),
3763
+ /* @__PURE__ */ jsx("ul", { className: "mt-2 space-y-1", children: snapshot.errors.map((issue) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
3764
+ "button",
3765
+ {
3766
+ type: "button",
3767
+ className: "text-destructive min-h-11 text-start underline underline-offset-2",
3768
+ onClick: () => focusSection(issue.sectionId),
3769
+ children: m("{section} is required.", {
3770
+ section: stable.definition.sections.find(
3771
+ (section) => section.id === issue.sectionId
3772
+ )?.title ?? issue.sectionId
3773
+ })
3774
+ }
3775
+ ) }, issue.sectionId)) })
3776
+ ]
3777
+ }
3778
+ ),
3779
+ /* @__PURE__ */ jsx(EsheetSectionContext.Provider, { value: true, children: /* @__PURE__ */ jsx(
3780
+ EsheetRenderer,
3781
+ {
3782
+ ref: renderer,
3783
+ formDataInput: rendererInput.definition,
3784
+ initialResponses: rendererInput.responses,
3785
+ onReady: handleReady,
3786
+ strict: true,
3787
+ readOnly: readOnly || saving,
3788
+ fitToContainer: true,
3789
+ topNavigation: false,
3790
+ bottomNavigation: false,
3791
+ className: "richtext-esheet-renderer"
3792
+ },
3793
+ fieldsSettled ? "settled" : "priming"
3794
+ ) })
3795
+ ]
3796
+ }
3797
+ ),
3798
+ /* @__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: [
3799
+ /* @__PURE__ */ jsx(
3800
+ "p",
3801
+ {
3802
+ role: "status",
3803
+ "aria-live": "polite",
3804
+ className: "text-muted-foreground min-w-0 text-sm",
3805
+ children: saveMessage || (readOnly ? m("Read-only document") : "")
3806
+ }
3807
+ ),
3808
+ onSubmit && /* @__PURE__ */ jsx(
3809
+ Button,
3810
+ {
3811
+ type: "button",
3812
+ className: "min-h-11",
3813
+ disabled: readOnly || saving,
3814
+ leftIcon: /* @__PURE__ */ jsx(Save, { size: 16, "aria-hidden": "true" }),
3815
+ onClick: () => void submit(),
3816
+ children: m(saving ? "Saving\u2026" : "Save document")
3817
+ }
3818
+ )
3819
+ ] })
3820
+ ]
3821
+ }
3822
+ );
3823
+ });
4117
3824
  function parseValue(answer) {
4118
3825
  if (!answer) return { medications: [] };
4119
3826
  try {
@@ -4252,6 +3959,6 @@ function registerMieEsheetFields(options) {
4252
3959
  registerAllergyListFieldType({ codeLookup: options?.codeLookup });
4253
3960
  }
4254
3961
 
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 };
3962
+ 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
3963
  //# sourceMappingURL=esheet.js.map
4257
3964
  //# sourceMappingURL=esheet.js.map