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