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 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;AAGxE,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACjD,CAAC,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACjD,2EAA2E;IAC3E,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9E,gFAAgF;IAChF,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAClD,CAAC,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,YAAY,EAAE,MAAM,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACzB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACxC,yFAAyF;IACzF,eAAe,CAAC,EAAE,2BAA2B,CAAA;IAC7C,8DAA8D;IAC9D,aAAa,CAAC,EAAE,6BAA6B,CAAA;CAC9C,CAAC,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IACnD,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;IACzB,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;CAC3B,CAAC,CAAA;AAEF,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IACtD,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,gBAAgB,EAAE,OAAO,GAAG,WAAW,CAAA;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAC,CAAA;AAEF,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAC,CAAA;AAEF,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;CACd,CAAC,CAAA;AAEF,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,kBAAkB,EAAE,MAAM,CAAA;IAC1B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,eAAe,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,2BAA2B,CAAA;CACvC,CAAC,CAAA;AAEF,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAC9C;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,0BAA0B,CAAA;CAAE,GAC1D;IAAE,KAAK,EAAE,SAAS,GAAG,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CACzC,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,OAAO,EAAE,6BAA6B,CAAA;IACtC,MAAM,EAAE,sBAAsB,CAAA;IAC9B,oFAAoF;IACpF,eAAe,CAAC,EAAE,2BAA2B,CAAA;IAC7C,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,gCAAgC,CAAA;IACnD,IAAI,EAAE,0BAA0B,CAAA;IAChC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACrC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,OAAO,CAAC,SAAS,4BAA4B,EAAE,CAAC,CAAA;IAC1G,YAAY,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,OAAO,CAAC,0BAA0B,GAAG,2BAA2B,GAAG,SAAS,CAAC,CAAA;IAC7H,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,UAAU,EAAE,CAAC,MAAM,EAAE,0BAA0B,KAAK,IAAI,CAAA;CACzD,CAAC,CAAA;AAEF;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,OAAO,EACP,MAAM,EACN,eAAe,EACf,gBAAgB,EAChB,IAAI,EACJ,MAAa,EACb,QAA2D,EAC3D,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,QAAQ,EACR,UAAU,GACX,EAAE,2BAA2B,+BA8K7B"}
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
- return _jsxs("label", { className: isMultiline(parameter) ? 'uhcp-clinical-editor-wide' : undefined, htmlFor: inputId, children: [_jsxs("span", { children: [label, requiredClaims.has(claim) ? ' *' : ''] }), 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
+ 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';
@@ -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); display:grid; font-weight:700; gap:.35rem; }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uhc-portal-components-react",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Brand-neutral React portal shell, account preferences and protected-profile UI for UHC and VetChain portals.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",