uhc-portal-components-react 0.1.36 → 0.1.37
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/CHANGELOG.md +6 -0
- package/README.md +10 -0
- package/dist/ClinicalArtifactEditor.d.ts.map +1 -1
- package/dist/ClinicalArtifactEditor.js +18 -1
- package/dist/ClinicalSpeechInput.d.ts +58 -0
- package/dist/ClinicalSpeechInput.d.ts.map +1 -0
- package/dist/ClinicalSpeechInput.js +83 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/styles.css +8 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Add a portal-owned clinical speech-input provider. Schema-backed IPS text
|
|
6
|
+
fields now reflect the shared model state, append editable transcripts and
|
|
7
|
+
reuse the existing explicit terminology-candidate selection flow. A shared
|
|
8
|
+
external channel propagates runtime phase changes to editors that are
|
|
9
|
+
already open without importing or duplicating the model in those editors.
|
|
10
|
+
|
|
5
11
|
- Add a shared progressive animal, owner-or-caregiver and physical-evidence
|
|
6
12
|
onboarding form for veterinarian-assisted identity attestation.
|
|
7
13
|
- Add a capability-aware `SubjectIdentityCard` for provider-confirmed animal
|
package/README.md
CHANGED
|
@@ -86,6 +86,16 @@ The package deliberately exposes modules instead of a cloned application:
|
|
|
86
86
|
terminology candidate preserves source text and adds only the confirmed
|
|
87
87
|
`system|code` and display. Success requires a fresh provider readback Bundle;
|
|
88
88
|
local form state is never presented as a persisted write.
|
|
89
|
+
- `ClinicalSpeechInputProvider`: lets the owning portal publish one
|
|
90
|
+
speech-recognition capability and model phase to every nested clinical
|
|
91
|
+
editor. Compatible IPS text fields show an accessible microphone only when
|
|
92
|
+
configured; recognized text remains editable and enters the same debounced
|
|
93
|
+
terminology-selection flow. The shared component never downloads a model,
|
|
94
|
+
stores audio or confirms a clinical code. Use
|
|
95
|
+
`createClinicalSpeechInputChannel` with
|
|
96
|
+
`ClinicalSpeechInputChannelProvider` when the portal model must notify
|
|
97
|
+
already-mounted IPS sections as it moves through download, initialization,
|
|
98
|
+
readiness or failure.
|
|
89
99
|
- `InvitationAcceptancePanel`: controlled invitation-code UI for a related
|
|
90
100
|
individual member; the host refreshes the authorized-subject list after its
|
|
91
101
|
BFF accepts the invitation.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClinicalArtifactEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalArtifactEditor.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kDAAkD,CAAA;AAUlG,OAAO,EAML,KAAK,2BAA2B,EAEhC,KAAK,2BAA2B,EACjC,MAAM,kDAAkD,CAAA;AACzD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"ClinicalArtifactEditor.d.ts","sourceRoot":"","sources":["../src/ClinicalArtifactEditor.tsx"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
|
4
4
|
import { clinicalArtifactEditorClaimsFromFhirR4, clinicalArtifactFhirR4ToFlat, composeFhirDateTimeFromLocalParts, splitFhirDateTimeForInput, } from 'fhir-data-utils-ts';
|
|
5
5
|
import { buildMultichannelOperationBundle, MultichannelOperationMode, readMultichannelOperationFromBundle, selectMultichannelOperationClaimValues, } from 'sos-data-utils-ts/multichannel-operation-schemas';
|
|
6
6
|
import { ClinicalEditorLayer } from './ClinicalEditorLayer.js';
|
|
7
|
+
import { ClinicalSpeechInputButton } from './ClinicalSpeechInput.js';
|
|
7
8
|
/**
|
|
8
9
|
* Shared human/animal clinical editor. The host owns authorization, transport,
|
|
9
10
|
* persistence and terminology service access. A successful form submission is
|
|
@@ -140,7 +141,16 @@ export function ClinicalArtifactEditor({ request, schema, operationSchema, autho
|
|
|
140
141
|
};
|
|
141
142
|
if (isDateTimeSchemaNode(schemaNode))
|
|
142
143
|
return _jsx(ClinicalDateTimeClaimField, { label: label, required: requiredClaims.has(claim), value: value, timeZone: timeZone, labels: copy.dateTime ?? DEFAULT_DATE_TIME_LABELS, onChange: (nextValue) => setClaims((current) => ({ ...current, [claim]: nextValue })) }, claim);
|
|
143
|
-
|
|
144
|
+
const fieldClassName = `uhcp-clinical-editor-field${isMultiline(parameter) ? ' uhcp-clinical-editor-wide' : ''}`;
|
|
145
|
+
const appendTranscript = (transcript) => {
|
|
146
|
+
setClaims((current) => ({
|
|
147
|
+
...current,
|
|
148
|
+
[claim]: [current[claim]?.trim(), transcript].filter(Boolean).join(' '),
|
|
149
|
+
}));
|
|
150
|
+
if (terminologyEnabled)
|
|
151
|
+
setActiveTerminologyClaim(claim);
|
|
152
|
+
};
|
|
153
|
+
return _jsxs("div", { className: fieldClassName, children: [_jsxs("span", { className: "uhcp-clinical-editor-field-heading", children: [_jsxs("label", { htmlFor: inputId, children: [label, requiredClaims.has(claim) ? ' *' : ''] }), isSpeechCompatibleField(parameter, schemaNode) ? _jsx(ClinicalSpeechInputButton, { resourceType: request.resourceType, claim: claim, locale: locale, fieldLabel: label, onTranscript: appendTranscript }) : null] }), statusOptions(request.resourceType, parameter) ? _jsxs("select", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), onChange: update, children: [!requiredClaims.has(claim) ? _jsx("option", { value: "" }) : null, statusOptions(request.resourceType, parameter).map((option) => _jsx("option", { value: option, children: humanize(option) }, option))] }) : isMultiline(parameter) ? _jsx("textarea", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), rows: 3, onChange: update })
|
|
144
154
|
: _jsx("input", { id: inputId, "aria-label": label, value: value, required: requiredClaims.has(claim), type: fieldInputType(parameter, operationSchema?.properties.claims?.properties?.[claim]), onChange: update }), terminologyEnabled && activeTerminologyClaim === claim ? _jsxs("div", { className: "uhcp-terminology-results", children: [terminologyBusy ? _jsx("small", { role: "status", children: copy.terminologyLoading }) : null, terminologyError ? _jsx("small", { role: "alert", children: copy.terminologyUnavailable }) : null, candidates.length ? _jsx("ul", { children: candidates.map((candidate) => _jsx("li", { children: _jsxs("button", { type: "button", onClick: () => {
|
|
145
155
|
setClaims((current) => selectedCodingClaims(request.resourceType, claim, candidate, acceptedClaims, current));
|
|
146
156
|
setCandidates([]);
|
|
@@ -185,6 +195,13 @@ function composeTemporalClaim(parts, timeZone) {
|
|
|
185
195
|
function isDateTimeSchemaNode(schemaNode) {
|
|
186
196
|
return schemaNode?.format === 'date-time' || schemaNode?.format === 'fhir-date-time';
|
|
187
197
|
}
|
|
198
|
+
function isSpeechCompatibleField(parameter, schemaNode) {
|
|
199
|
+
if (statusOptions('', parameter))
|
|
200
|
+
return false;
|
|
201
|
+
if (schemaNode?.type === 'number' || schemaNode?.type === 'integer' || schemaNode?.type === 'boolean')
|
|
202
|
+
return false;
|
|
203
|
+
return fieldInputType(parameter, schemaNode) === 'text';
|
|
204
|
+
}
|
|
188
205
|
function isSaveOutcome(value) {
|
|
189
206
|
return typeof value === 'object' && value !== null && 'state' in value;
|
|
190
207
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export declare const ClinicalSpeechInputPhases: Readonly<{
|
|
3
|
+
readonly Idle: "idle";
|
|
4
|
+
readonly Checking: "checking";
|
|
5
|
+
readonly Downloading: "downloading";
|
|
6
|
+
readonly Initializing: "initializing";
|
|
7
|
+
readonly Ready: "ready";
|
|
8
|
+
readonly Error: "error";
|
|
9
|
+
readonly Unavailable: "unavailable";
|
|
10
|
+
}>;
|
|
11
|
+
export type ClinicalSpeechInputPhase = typeof ClinicalSpeechInputPhases[keyof typeof ClinicalSpeechInputPhases];
|
|
12
|
+
export type ClinicalSpeechInputRequest = Readonly<{
|
|
13
|
+
resourceType: string;
|
|
14
|
+
claim: string;
|
|
15
|
+
locale: string;
|
|
16
|
+
}>;
|
|
17
|
+
export type ClinicalSpeechInputCapability = Readonly<{
|
|
18
|
+
phase: ClinicalSpeechInputPhase;
|
|
19
|
+
transcribe: (input: ClinicalSpeechInputRequest) => Promise<string>;
|
|
20
|
+
labels: Readonly<{
|
|
21
|
+
start: string;
|
|
22
|
+
unavailable: string;
|
|
23
|
+
busy: string;
|
|
24
|
+
failed?: string;
|
|
25
|
+
}>;
|
|
26
|
+
}>;
|
|
27
|
+
export type ClinicalSpeechInputRuntime = Pick<ClinicalSpeechInputCapability, 'phase' | 'transcribe'>;
|
|
28
|
+
export type ClinicalSpeechInputChannel = Readonly<{
|
|
29
|
+
getSnapshot: () => ClinicalSpeechInputRuntime;
|
|
30
|
+
publish: (runtime: ClinicalSpeechInputRuntime) => void;
|
|
31
|
+
subscribe: (listener: () => void) => () => void;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Creates one small external store through which a portal-owned model runtime
|
|
35
|
+
* can announce download, initialization and readiness changes to every nested
|
|
36
|
+
* clinical editor without importing the model into the shared UI package.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createClinicalSpeechInputChannel(initialRuntime?: ClinicalSpeechInputRuntime): ClinicalSpeechInputChannel;
|
|
39
|
+
/**
|
|
40
|
+
* Supplies one portal-owned speech-recognition capability to nested clinical
|
|
41
|
+
* editors. The portal owns model download, microphone permission and fallback;
|
|
42
|
+
* shared editors receive only an editable transcript.
|
|
43
|
+
*/
|
|
44
|
+
export declare function ClinicalSpeechInputProvider({ value, children }: Readonly<{
|
|
45
|
+
value: ClinicalSpeechInputCapability;
|
|
46
|
+
children: ReactNode;
|
|
47
|
+
}>): import("react").JSX.Element;
|
|
48
|
+
export declare function ClinicalSpeechInputChannelProvider({ channel, labels, children, }: Readonly<{
|
|
49
|
+
channel: ClinicalSpeechInputChannel;
|
|
50
|
+
labels: ClinicalSpeechInputCapability['labels'];
|
|
51
|
+
children: ReactNode;
|
|
52
|
+
}>): import("react").JSX.Element;
|
|
53
|
+
export declare function useClinicalSpeechInput(): ClinicalSpeechInputCapability | undefined;
|
|
54
|
+
export declare function ClinicalSpeechInputButton({ resourceType, claim, locale, fieldLabel, onTranscript, }: Readonly<ClinicalSpeechInputRequest & {
|
|
55
|
+
fieldLabel: string;
|
|
56
|
+
onTranscript: (transcript: string) => void;
|
|
57
|
+
}>): import("react").JSX.Element | null;
|
|
58
|
+
//# sourceMappingURL=ClinicalSpeechInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClinicalSpeechInput.d.ts","sourceRoot":"","sources":["../src/ClinicalSpeechInput.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA6D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjG,eAAO,MAAM,yBAAyB;;;;;;;;EAQ3B,CAAA;AAEX,MAAM,MAAM,wBAAwB,GAAG,OAAO,yBAAyB,CAAC,MAAM,OAAO,yBAAyB,CAAC,CAAA;AAE/G,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAC,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,KAAK,EAAE,wBAAwB,CAAA;IAC/B,UAAU,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAClE,MAAM,EAAE,QAAQ,CAAC;QACf,KAAK,EAAE,MAAM,CAAA;QACb,WAAW,EAAE,MAAM,CAAA;QACnB,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAC,CAAA;CACH,CAAC,CAAA;AAEF,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,6BAA6B,EAAE,OAAO,GAAG,YAAY,CAAC,CAAA;AAEpG,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,WAAW,EAAE,MAAM,0BAA0B,CAAA;IAC7C,OAAO,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAA;IACtD,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAChD,CAAC,CAAA;AAOF;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,cAAc,GAAE,0BAA+C,GAC9D,0BAA0B,CAc5B;AAID;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC;IACxE,KAAK,EAAE,6BAA6B,CAAA;IACpC,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAC,+BAED;AAED,wBAAgB,kCAAkC,CAAC,EACjD,OAAO,EACP,MAAM,EACN,QAAQ,GACT,EAAE,QAAQ,CAAC;IACV,OAAO,EAAE,0BAA0B,CAAA;IACnC,MAAM,EAAE,6BAA6B,CAAC,QAAQ,CAAC,CAAA;IAC/C,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAC,+BAGD;AAED,wBAAgB,sBAAsB,IAAI,6BAA6B,GAAG,SAAS,CAElF;AAED,wBAAgB,yBAAyB,CAAC,EACxC,YAAY,EACZ,KAAK,EACL,MAAM,EACN,UAAU,EACV,YAAY,GACb,EAAE,QAAQ,CAAC,0BAA0B,GAAG;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;CAC3C,CAAC,sCA2CD"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useState, useSyncExternalStore } from 'react';
|
|
4
|
+
export const ClinicalSpeechInputPhases = Object.freeze({
|
|
5
|
+
Idle: 'idle',
|
|
6
|
+
Checking: 'checking',
|
|
7
|
+
Downloading: 'downloading',
|
|
8
|
+
Initializing: 'initializing',
|
|
9
|
+
Ready: 'ready',
|
|
10
|
+
Error: 'error',
|
|
11
|
+
Unavailable: 'unavailable',
|
|
12
|
+
});
|
|
13
|
+
const unavailableRuntime = Object.freeze({
|
|
14
|
+
phase: ClinicalSpeechInputPhases.Unavailable,
|
|
15
|
+
transcribe: async () => { throw new Error('clinical_speech_input_unavailable'); },
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Creates one small external store through which a portal-owned model runtime
|
|
19
|
+
* can announce download, initialization and readiness changes to every nested
|
|
20
|
+
* clinical editor without importing the model into the shared UI package.
|
|
21
|
+
*/
|
|
22
|
+
export function createClinicalSpeechInputChannel(initialRuntime = unavailableRuntime) {
|
|
23
|
+
let snapshot = Object.freeze(initialRuntime);
|
|
24
|
+
const listeners = new Set();
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
getSnapshot: () => snapshot,
|
|
27
|
+
publish(runtime) {
|
|
28
|
+
snapshot = Object.freeze(runtime);
|
|
29
|
+
listeners.forEach((listener) => listener());
|
|
30
|
+
},
|
|
31
|
+
subscribe(listener) {
|
|
32
|
+
listeners.add(listener);
|
|
33
|
+
return () => listeners.delete(listener);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const ClinicalSpeechInputContext = createContext(undefined);
|
|
38
|
+
/**
|
|
39
|
+
* Supplies one portal-owned speech-recognition capability to nested clinical
|
|
40
|
+
* editors. The portal owns model download, microphone permission and fallback;
|
|
41
|
+
* shared editors receive only an editable transcript.
|
|
42
|
+
*/
|
|
43
|
+
export function ClinicalSpeechInputProvider({ value, children }) {
|
|
44
|
+
return _jsx(ClinicalSpeechInputContext.Provider, { value: value, children: children });
|
|
45
|
+
}
|
|
46
|
+
export function ClinicalSpeechInputChannelProvider({ channel, labels, children, }) {
|
|
47
|
+
const runtime = useSyncExternalStore(channel.subscribe, channel.getSnapshot, channel.getSnapshot);
|
|
48
|
+
return _jsx(ClinicalSpeechInputProvider, { value: { ...runtime, labels }, children: children });
|
|
49
|
+
}
|
|
50
|
+
export function useClinicalSpeechInput() {
|
|
51
|
+
return useContext(ClinicalSpeechInputContext);
|
|
52
|
+
}
|
|
53
|
+
export function ClinicalSpeechInputButton({ resourceType, claim, locale, fieldLabel, onTranscript, }) {
|
|
54
|
+
const capability = useClinicalSpeechInput();
|
|
55
|
+
const [busy, setBusy] = useState(false);
|
|
56
|
+
const [failed, setFailed] = useState(false);
|
|
57
|
+
if (!capability)
|
|
58
|
+
return null;
|
|
59
|
+
const ready = capability.phase === ClinicalSpeechInputPhases.Ready;
|
|
60
|
+
const actionLabel = busy
|
|
61
|
+
? capability.labels.busy
|
|
62
|
+
: ready
|
|
63
|
+
? capability.labels.start
|
|
64
|
+
: capability.labels.unavailable;
|
|
65
|
+
const transcribe = async () => {
|
|
66
|
+
if (!ready || busy)
|
|
67
|
+
return;
|
|
68
|
+
setBusy(true);
|
|
69
|
+
setFailed(false);
|
|
70
|
+
try {
|
|
71
|
+
const transcript = (await capability.transcribe({ resourceType, claim, locale })).trim();
|
|
72
|
+
if (transcript)
|
|
73
|
+
onTranscript(transcript);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
setFailed(true);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
setBusy(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return _jsxs("span", { className: "uhcp-clinical-speech-control", children: [_jsx("button", { type: "button", className: "uhcp-clinical-speech-button", "aria-label": `${actionLabel}: ${fieldLabel}`, title: actionLabel, disabled: !ready || busy, "aria-pressed": busy, onClick: () => { void transcribe(); }, children: _jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", focusable: "false", children: _jsx("path", { d: "M12 14a3 3 0 0 0 3-3V5a3 3 0 1 0-6 0v6a3 3 0 0 0 3 3Zm-5-3a1 1 0 1 0-2 0 7 7 0 0 0 6 6.92V21H8a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2h-3v-3.08A7 7 0 0 0 19 11a1 1 0 1 0-2 0 5 5 0 0 1-10 0Z" }) }) }), failed ? _jsx("small", { role: "alert", children: capability.labels.failed ?? capability.labels.unavailable }) : null] });
|
|
83
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from './ClinicalDocumentEncounterExplorer.js';
|
|
|
12
12
|
export * from './AllClinicalSections.js';
|
|
13
13
|
export * from './ClinicalTimeline.js';
|
|
14
14
|
export * from './ClinicalArtifactEditor.js';
|
|
15
|
+
export * from './ClinicalSpeechInput.js';
|
|
15
16
|
export * from './ClinicalActionButton.js';
|
|
16
17
|
export * from './VitalSignArtifactEditor.js';
|
|
17
18
|
export * from './InvitationAcceptancePanel.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,wCAAwC,CAAA;AACtD,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,0CAA0C,CAAA;AACxD,cAAc,kCAAkC,CAAA;AAChD,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,oCAAoC,CAAA;AAClD,cAAc,sCAAsC,CAAA;AACpD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,wCAAwC,CAAA;AACtD,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AACrC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,0BAA0B,CAAA;AACxC,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wBAAwB,CAAA;AACtC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,0CAA0C,CAAA;AACxD,cAAc,kCAAkC,CAAA;AAChD,cAAc,0BAA0B,CAAA;AACxC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,oCAAoC,CAAA;AAClD,cAAc,sCAAsC,CAAA;AACpD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './ClinicalDocumentEncounterExplorer.js';
|
|
|
12
12
|
export * from './AllClinicalSections.js';
|
|
13
13
|
export * from './ClinicalTimeline.js';
|
|
14
14
|
export * from './ClinicalArtifactEditor.js';
|
|
15
|
+
export * from './ClinicalSpeechInput.js';
|
|
15
16
|
export * from './ClinicalActionButton.js';
|
|
16
17
|
export * from './VitalSignArtifactEditor.js';
|
|
17
18
|
export * from './InvitationAcceptancePanel.js';
|
package/dist/styles.css
CHANGED
|
@@ -177,8 +177,14 @@ html[data-theme='dark'] .uhcp-brand-artwork-dark { display: block !important; }
|
|
|
177
177
|
.uhcp-clinical-editor h2,.uhcp-clinical-editor p { margin:.2rem 0; }
|
|
178
178
|
.uhcp-clinical-editor>header>button { background:transparent; border:1px solid var(--uhcp-border); border-radius:50%; color:inherit; cursor:pointer; font-size:1.4rem; height:2.5rem; width:2.5rem; }
|
|
179
179
|
.uhcp-clinical-editor form { display:grid; gap:1rem; grid-template-columns:repeat(2,minmax(0,1fr)); margin-top:1.25rem; min-width:0; overflow:auto; }
|
|
180
|
-
.uhcp-clinical-editor label { color:var(--uhcp-text);
|
|
181
|
-
.uhcp-clinical-editor label,.uhcp-clinical-editor fieldset { min-width:0; }
|
|
180
|
+
.uhcp-clinical-editor label { color:var(--uhcp-text); font-weight:700; }
|
|
181
|
+
.uhcp-clinical-editor label,.uhcp-clinical-editor fieldset,.uhcp-clinical-editor-field { min-width:0; }
|
|
182
|
+
.uhcp-clinical-editor-field { display:grid; gap:.35rem; }
|
|
183
|
+
.uhcp-clinical-editor-field-heading { align-items:center; display:flex; gap:.5rem; justify-content:space-between; }
|
|
184
|
+
.uhcp-clinical-speech-control { align-items:center; display:flex; gap:.4rem; }
|
|
185
|
+
.uhcp-clinical-speech-button { align-items:center; background:transparent; border:1px solid var(--uhcp-border); border-radius:50%; color:inherit; cursor:pointer; display:inline-flex; height:2.25rem; justify-content:center; padding:.45rem; width:2.25rem; }
|
|
186
|
+
.uhcp-clinical-speech-button:disabled { cursor:not-allowed; opacity:.45; }
|
|
187
|
+
.uhcp-clinical-speech-button svg { fill:currentColor; height:1.15rem; width:1.15rem; }
|
|
182
188
|
.uhcp-clinical-editor input,.uhcp-clinical-editor select,.uhcp-clinical-editor textarea { background:var(--uhcp-panel); border:1px solid var(--uhcp-border); border-radius:.7rem; color:inherit; font:inherit; min-width:0; padding:.75rem; width:100%; }
|
|
183
189
|
.uhcp-clinical-editor-date-time { grid-column:1/-1; }
|
|
184
190
|
.uhcp-clinical-editor-wide,.uhcp-clinical-editor-error,.uhcp-clinical-editor footer { grid-column:1/-1; }
|
package/package.json
CHANGED