uhc-portal-components-react 0.2.19 → 0.2.21

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,27 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.21 - 2026-10-21
6
+
7
+ - Move the card preview, profile recovery, elapsed-operation status and
8
+ collapsible portal section into the shared package and remove their cloned
9
+ consumer implementations and duplicate elapsed-status tests.
10
+ - Keep package metadata and current usage guidance brand-neutral; consumer
11
+ names belong in host repositories rather than this reusable package.
12
+ - Centralize the platform-authenticator capability check and mandatory
13
+ passkey-before-wallet ordering so Personal and Professional hosts cannot
14
+ silently diverge on biometric enrollment policy.
15
+
16
+ ## 0.2.20 - 2026-10-21
17
+
18
+ - Add one accessible Personal subject workspace shared by UHC, PetChain and
19
+ SOSChain consumers, with five canonical screens and governed product-module
20
+ extensions.
21
+ - Add explicit human and animal clinical-summary profiles. The animal profile
22
+ reuses the same clinical screen while excluding Advance Directives for now.
23
+ - Keep the existing capability-driven professional subject workspace as the
24
+ shared UHC/VetChain composition; SOSChain layers emergency, Encounter and
25
+ communication capabilities on shared components rather than cloning them.
5
26
  - Add one neutral notification composer screen for subject-addressed care
6
27
  transitions and public-dataset alerts, with safe Markdown preview and host-
7
28
  uploaded image/PDF attachment references.
package/README.md CHANGED
@@ -1,23 +1,19 @@
1
- # UHC Portal Components React
1
+ # Shared Portal Components React
2
2
 
3
3
  Historical reusable UI and flow anchors are tracked in
4
4
  [`docs/LEGACY_FRONTEND_RECOVERY_INVENTORY.md`](docs/LEGACY_FRONTEND_RECOVERY_INVENTORY.md).
5
5
 
6
- Publishable, brand-neutral React components shared by:
7
-
8
- - UHC Personal
9
- - UHC Professional
10
- - VetChain Personal
11
- - VetChain Professional
6
+ Publishable, brand-neutral React components shared by independent Personal and
7
+ Professional portal deployments.
12
8
 
13
9
  The package owns visual shell behavior only. Authentication, protected-profile
14
10
  storage, PIN verification and actor authorization remain in the host portal
15
11
  through `ProfileRuntime`, `LoadedProfileWorkspace` and actor-scoped SDK facades.
16
12
 
17
13
  `AnimalIdentityOnboardingV2` and `AnimalIdentityDetailsV2` define one shared
18
- progressive create/read pattern. VetChain uses it for an animal and responsible
19
- party. UHC Professional must reuse the same contract for a newborn and
20
- responsible person, changing only governed role policy and copy: an attending
14
+ progressive create/read pattern. A host can use the same contract for an animal
15
+ and responsible party or a newborn and responsible person, changing only
16
+ governed role policy and copy: an attending
21
17
  physician, midwife, nurse or paramedic may prepare the identity and default
22
18
  permission proposal where the jurisdiction authorizes that occupation. The
23
19
  proposal remains pending until the responsible authority confirms it; showing
@@ -38,17 +34,17 @@ Brand differences are supplied through `PortalBrand`: product name,
38
34
  organization, official artwork and CSS variables. Artwork declares its
39
35
  light/dark-surface files and intrinsic dimensions; the shell preserves those
40
36
  proportions and never synthesizes a text monogram. Components do not import
41
- Firebase, Next.js or any GDC/UHC SDK runtime.
37
+ Firebase, Next.js or any product SDK runtime.
42
38
 
43
39
  ```tsx
44
40
  const brand = {
45
41
  family: 'professional',
46
- productName: 'UNID Professional',
47
- organizationName: 'Fundación UNID',
42
+ productName: 'Example Professional',
43
+ organizationName: 'Example Organization',
48
44
  homeHref: '/es',
49
45
  mark: {
50
- lightSurface: { src: '/logos/uhc-logo.svg', alt: 'UHC', width: 100, height: 100 },
51
- darkSurface: { src: '/logos/uhc-logo.svg', alt: 'UHC', width: 100, height: 100 },
46
+ lightSurface: { src: '/logos/product.svg', alt: 'Product', width: 100, height: 100 },
47
+ darkSurface: { src: '/logos/product.svg', alt: 'Product', width: 100, height: 100 },
52
48
  },
53
49
  } satisfies PortalBrand
54
50
  ```
@@ -74,9 +70,16 @@ The package deliberately exposes modules instead of a cloned application:
74
70
  authorization, fetching or persistence in the browser.
75
71
  - `AllClinicalSections<Person|Animal>`: lets each portal compose the same
76
72
  authorized section views as a stack or tabs, nest collapsible subsections and
77
- exclude inapplicable areas. Family history, care-team members and emergency
73
+ exclude inapplicable areas. `ClinicalSummaryProfiles.Human` retains Advance
74
+ Directives; the current `ClinicalSummaryProfiles.Animal` excludes that
75
+ human-only section while reusing the same screen. Family history, care-team members and emergency
78
76
  contacts remain independent; the organizer never follows another subject's
79
77
  card or documents automatically.
78
+ - `SubjectWorkspace`: canonical Personal navigation for Profile, Health, Care,
79
+ Communications and Contacts/Permissions. Product profiles may append an
80
+ explicit capability screen, such as animal mobility or SOS emergency work,
81
+ but tab selection never creates authority and all content remains host-
82
+ authorized.
80
83
  - `ClinicalTimeline` and `ClinicalDateTimeFields`: render longitudinal entries
81
84
  with localized dates, inclusive date/time ranges, multi-select clinical
82
85
  facets and accessible latest-first navigation. Editors capture calendar date
@@ -122,9 +125,11 @@ The package deliberately exposes modules instead of a cloned application:
122
125
  returned identifier evidence never grant subject access.
123
126
  resolver plus section selector. It deliberately has no scanner role: the
124
127
  same component is reusable by an individual member or a professional.
125
- - `SoschainWorkspace`: subject-card picker and four-area workspace for Places,
126
- devices/vehicles and read-only emergency communications. The host supplies
127
- already-authorized cards and capabilities.
128
+ - `SoschainWorkspace`: subject-card picker and capability composition for the
129
+ overview, health summary, care journey, emergency data, communications,
130
+ places and products screens. The host supplies already-authorized cards,
131
+ content and capabilities; SOS-specific emergency modules do not fork the
132
+ common Personal or Professional components.
128
133
  - `EncounterTransferConsole`: visual patient-transfer workspace. The host
129
134
  resolves the authorized subject, contacts and write capability; the component
130
135
  only reports the chosen source outcome (`on-hold` when returning or
@@ -185,12 +190,12 @@ token immediately; otherwise it records a `Communication` request for the
185
190
  individual controller to review. The GW remains authoritative for the exact
186
191
  intersection of requested and consented sections.
187
192
 
188
- UHC Personal and PetChain Personal must ultimately compose the same reusable
189
- personal flow modules. UHC Professional and VetChain Professional must do the
190
- same for professional flows. This package does **not yet expose those complete
193
+ Personal deployments must compose the same reusable Personal flow modules;
194
+ Professional deployments must do the same for Professional flows. This package
195
+ does **not yet expose every complete
191
196
  route modules**: login, versioned terms, profile protection, individual/member
192
197
  management, licences, clinical data, care and permissions still live in the
193
- UHC host application and must be extracted behind host-owned adapters. A brand
198
+ host application and must be extracted behind host-owned adapters. A brand
194
199
  adapter may change tokens, logo, product vocabulary, animal-specific forms and
195
200
  route destinations; it must not fork shared flow or authorization behavior.
196
201
  Independent deployments are expected: reuse means versioned complete screens,
@@ -10,9 +10,21 @@ export declare const ClinicalSectionNavigationIds: Readonly<{
10
10
  readonly ClinicalNotes: "clinical-notes";
11
11
  readonly CareTeamMembers: "care-team-members";
12
12
  readonly EmergencyContacts: "emergency-contacts";
13
+ readonly AdvanceDirectives: "advance-directives";
13
14
  }>;
14
15
  export type ClinicalSectionNavigationId = typeof ClinicalSectionNavigationIds[keyof typeof ClinicalSectionNavigationIds];
15
16
  export type ClinicalSubjectKind = 'person' | 'animal';
17
+ export declare const ClinicalSummaryProfiles: Readonly<{
18
+ readonly Human: Readonly<{
19
+ id: "human";
20
+ excludedSectionIds: readonly never[];
21
+ }>;
22
+ readonly Animal: Readonly<{
23
+ id: "animal";
24
+ excludedSectionIds: readonly "advance-directives"[];
25
+ }>;
26
+ }>;
27
+ export type ClinicalSummaryProfile = typeof ClinicalSummaryProfiles[keyof typeof ClinicalSummaryProfiles];
16
28
  export type AllClinicalSectionDefinition<SubjectKind extends ClinicalSubjectKind> = Readonly<{
17
29
  id: ClinicalSectionNavigationId | (string & {});
18
30
  title: string;
@@ -27,6 +39,7 @@ export type AllClinicalSectionsProps<SubjectKind extends ClinicalSubjectKind> =
27
39
  title: string;
28
40
  sections: readonly AllClinicalSectionDefinition<SubjectKind>[];
29
41
  excludedSectionIds?: readonly string[];
42
+ profile?: ClinicalSummaryProfile;
30
43
  presentation?: 'sections' | 'tabs';
31
44
  }>;
32
45
  /**
@@ -37,5 +50,5 @@ export type AllClinicalSectionsProps<SubjectKind extends ClinicalSubjectKind> =
37
50
  * contacts and care-team members remain independent domains; this component
38
51
  * never follows another subject's card or document links automatically.
39
52
  */
40
- export declare function AllClinicalSections<SubjectKind extends ClinicalSubjectKind>({ subjectKind, title, sections, excludedSectionIds, presentation, }: AllClinicalSectionsProps<SubjectKind>): import("react").JSX.Element;
53
+ export declare function AllClinicalSections<SubjectKind extends ClinicalSubjectKind>({ subjectKind, title, sections, excludedSectionIds, profile, presentation, }: AllClinicalSectionsProps<SubjectKind>): import("react").JSX.Element;
41
54
  //# sourceMappingURL=AllClinicalSections.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AllClinicalSections.d.ts","sourceRoot":"","sources":["../src/AllClinicalSections.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEhD,eAAO,MAAM,4BAA4B;;;;;;;;;;;EAW9B,CAAA;AAEX,MAAM,MAAM,2BAA2B,GAAG,OAAO,4BAA4B,CAAC,MAAM,OAAO,4BAA4B,CAAC,CAAA;AACxH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAErD,MAAM,MAAM,4BAA4B,CAAC,WAAW,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IAC3F,EAAE,EAAE,2BAA2B,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,SAAS,4BAA4B,CAAC,WAAW,CAAC,EAAE,CAAA;CAChE,CAAC,CAAA;AAEF,MAAM,MAAM,wBAAwB,CAAC,WAAW,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IACvF,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,SAAS,4BAA4B,CAAC,WAAW,CAAC,EAAE,CAAA;IAC9D,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,YAAY,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;CACnC,CAAC,CAAA;AAEF;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,SAAS,mBAAmB,EAAE,EAC3E,WAAW,EACX,KAAK,EACL,QAAQ,EACR,kBAAuB,EACvB,YAAyB,GAC1B,EAAE,wBAAwB,CAAC,WAAW,CAAC,+BAqCvC"}
1
+ {"version":3,"file":"AllClinicalSections.d.ts","sourceRoot":"","sources":["../src/AllClinicalSections.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEhD,eAAO,MAAM,4BAA4B;;;;;;;;;;;;EAY9B,CAAA;AAEX,MAAM,MAAM,2BAA2B,GAAG,OAAO,4BAA4B,CAAC,MAAM,OAAO,4BAA4B,CAAC,CAAA;AACxH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAErD,eAAO,MAAM,uBAAuB;;;;;;;;;EAGzB,CAAA;AAEX,MAAM,MAAM,sBAAsB,GAAG,OAAO,uBAAuB,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAA;AAEzG,MAAM,MAAM,4BAA4B,CAAC,WAAW,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IAC3F,EAAE,EAAE,2BAA2B,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,SAAS,4BAA4B,CAAC,WAAW,CAAC,EAAE,CAAA;CAChE,CAAC,CAAA;AAEF,MAAM,MAAM,wBAAwB,CAAC,WAAW,SAAS,mBAAmB,IAAI,QAAQ,CAAC;IACvF,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,SAAS,4BAA4B,CAAC,WAAW,CAAC,EAAE,CAAA;IAC9D,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,OAAO,CAAC,EAAE,sBAAsB,CAAA;IAChC,YAAY,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;CACnC,CAAC,CAAA;AAEF;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,SAAS,mBAAmB,EAAE,EAC3E,WAAW,EACX,KAAK,EACL,QAAQ,EACR,kBAAuB,EACvB,OAAO,EACP,YAAyB,GAC1B,EAAE,wBAAwB,CAAC,WAAW,CAAC,+BAsCvC"}
@@ -11,6 +11,11 @@ export const ClinicalSectionNavigationIds = Object.freeze({
11
11
  ClinicalNotes: 'clinical-notes',
12
12
  CareTeamMembers: 'care-team-members',
13
13
  EmergencyContacts: 'emergency-contacts',
14
+ AdvanceDirectives: 'advance-directives',
15
+ });
16
+ export const ClinicalSummaryProfiles = Object.freeze({
17
+ Human: Object.freeze({ id: 'human', excludedSectionIds: Object.freeze([]) }),
18
+ Animal: Object.freeze({ id: 'animal', excludedSectionIds: Object.freeze([ClinicalSectionNavigationIds.AdvanceDirectives]) }),
14
19
  });
15
20
  /**
16
21
  * Organizes host-provided clinical views without redefining their FHIR model.
@@ -20,11 +25,12 @@ export const ClinicalSectionNavigationIds = Object.freeze({
20
25
  * contacts and care-team members remain independent domains; this component
21
26
  * never follows another subject's card or document links automatically.
22
27
  */
23
- export function AllClinicalSections({ subjectKind, title, sections, excludedSectionIds = [], presentation = 'sections', }) {
24
- const visibleSections = organizeClinicalSections(sections.filter(({ id }) => !excludedSectionIds.includes(id)));
28
+ export function AllClinicalSections({ subjectKind, title, sections, excludedSectionIds = [], profile, presentation = 'sections', }) {
29
+ const effectiveExcludedSectionIds = [...new Set([...(profile?.excludedSectionIds ?? []), ...excludedSectionIds])];
30
+ const visibleSections = organizeClinicalSections(sections.filter(({ id }) => !effectiveExcludedSectionIds.includes(id)));
25
31
  const [selectedId, setSelectedId] = useState(visibleSections[0]?.id ?? '');
26
32
  const selectedSection = visibleSections.find(({ id }) => id === selectedId) ?? visibleSections[0];
27
- return (_jsxs("section", { className: "uhcp-all-clinical-sections", "data-subject-kind": subjectKind, children: [_jsx("h2", { children: title }), presentation === 'tabs' ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "uhcp-all-clinical-tabs", role: "tablist", "aria-label": title, children: visibleSections.map((section) => (_jsx("button", { type: "button", role: "tab", "aria-selected": section.id === selectedSection?.id, "aria-controls": `uhcp-all-section-${section.id}`, onClick: () => setSelectedId(section.id), children: section.title }, section.id))) }), selectedSection ? (_jsx("div", { id: `uhcp-all-section-${selectedSection.id}`, role: "tabpanel", children: _jsx(ClinicalSection, { definition: selectedSection, excludedSectionIds: excludedSectionIds }) })) : null] })) : (_jsx("div", { className: "uhcp-all-clinical-stack", children: visibleSections.map((section) => (_jsx(ClinicalSection, { definition: section, excludedSectionIds: excludedSectionIds }, section.id))) }))] }));
33
+ return (_jsxs("section", { className: "uhcp-all-clinical-sections", "data-subject-kind": subjectKind, children: [_jsx("h2", { children: title }), presentation === 'tabs' ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "uhcp-all-clinical-tabs", role: "tablist", "aria-label": title, children: visibleSections.map((section) => (_jsx("button", { type: "button", role: "tab", "aria-selected": section.id === selectedSection?.id, "aria-controls": `uhcp-all-section-${section.id}`, onClick: () => setSelectedId(section.id), children: section.title }, section.id))) }), selectedSection ? (_jsx("div", { id: `uhcp-all-section-${selectedSection.id}`, role: "tabpanel", children: _jsx(ClinicalSection, { definition: selectedSection, excludedSectionIds: effectiveExcludedSectionIds }) })) : null] })) : (_jsx("div", { className: "uhcp-all-clinical-stack", children: visibleSections.map((section) => (_jsx(ClinicalSection, { definition: section, excludedSectionIds: effectiveExcludedSectionIds }, section.id))) }))] }));
28
34
  }
29
35
  function organizeClinicalSections(sections) {
30
36
  const ipsChildrenIds = new Set([
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ /** Keeps the canonical physical-card aspect ratio inside a responsive preview shell. */
3
+ export declare function CardPreview({ children }: Readonly<{
4
+ children: ReactNode;
5
+ }>): import("react").JSX.Element;
6
+ //# sourceMappingURL=CardPreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CardPreview.d.ts","sourceRoot":"","sources":["../src/CardPreview.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,CAAC,+BAI1E"}
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /** Keeps the canonical physical-card aspect ratio inside a responsive preview shell. */
3
+ export function CardPreview({ children }) {
4
+ return _jsx("div", { className: "uhc-card-preview-shell", "data-testid": "card-preview", children: _jsx("div", { className: "uhc-card-preview", children: children }) });
5
+ }
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from 'react';
2
+ /** Keeps one portal phase mounted while allowing its content to be collapsed. */
3
+ export declare function CollapsiblePortalSection({ title, description, children, className, initiallyOpen, }: Readonly<{
4
+ title: string;
5
+ description?: string;
6
+ children: ReactNode;
7
+ className?: string;
8
+ initiallyOpen?: boolean;
9
+ }>): import("react").JSX.Element;
10
+ //# sourceMappingURL=CollapsiblePortalSection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollapsiblePortalSection.d.ts","sourceRoot":"","sources":["../src/CollapsiblePortalSection.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAA;AAErE,iFAAiF;AACjF,wBAAgB,wBAAwB,CAAC,EACvC,KAAK,EACL,WAAW,EACX,QAAQ,EACR,SAAc,EACd,aAAoB,GACrB,EAAE,QAAQ,CAAC;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB,CAAC,+BAaD"}
@@ -0,0 +1,11 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ /** Keeps one portal phase mounted while allowing its content to be collapsed. */
5
+ export function CollapsiblePortalSection({ title, description, children, className = '', initiallyOpen = true, }) {
6
+ const [open, setOpen] = useState(initiallyOpen);
7
+ function retainDisclosureState(event) {
8
+ setOpen(event.currentTarget.open);
9
+ }
10
+ return _jsxs("details", { className: `research-import-disclosure ${className}`.trim(), open: open, onToggle: retainDisclosureState, children: [_jsxs("summary", { children: [_jsx("strong", { children: title }), description ? _jsx("span", { children: description }) : null] }), _jsx("div", { className: "research-import-disclosure-content", children: children })] });
11
+ }
@@ -0,0 +1,9 @@
1
+ /** Renders one accessible status and elapsed counter for a long portal operation. */
2
+ export declare function ElapsedOperationStatus({ busy, status, fallback, tone, testId }: Readonly<{
3
+ busy: boolean;
4
+ status: string;
5
+ fallback: string;
6
+ tone?: 'info' | 'warning' | 'success' | 'error';
7
+ testId?: string;
8
+ }>): import("react").JSX.Element | null;
9
+ //# sourceMappingURL=ElapsedOperationStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ElapsedOperationStatus.d.ts","sourceRoot":"","sources":["../src/ElapsedOperationStatus.tsx"],"names":[],"mappings":"AAIA,qFAAqF;AACrF,wBAAgB,sBAAsB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAgB,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC;IACpG,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAC,sCAiBD"}
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+ import { jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from 'react';
4
+ /** Renders one accessible status and elapsed counter for a long portal operation. */
5
+ export function ElapsedOperationStatus({ busy, status, fallback, tone = 'warning', testId }) {
6
+ const [elapsedSeconds, setElapsedSeconds] = useState(0);
7
+ useEffect(() => {
8
+ if (!busy) {
9
+ setElapsedSeconds(0);
10
+ return;
11
+ }
12
+ const startedAt = Date.now();
13
+ setElapsedSeconds(0);
14
+ const timer = window.setInterval(() => setElapsedSeconds(Math.floor((Date.now() - startedAt) / 1000)), 1000);
15
+ return () => window.clearInterval(timer);
16
+ }, [busy]);
17
+ const text = status || (busy ? fallback : '');
18
+ if (!text)
19
+ return null;
20
+ return _jsxs("p", { role: tone === 'error' ? 'alert' : 'status', "data-testid": testId, className: `notice notice-${tone}`, children: [text, busy ? ` (${elapsedSeconds}s)` : ''] });
21
+ }
@@ -0,0 +1,29 @@
1
+ export type ProfileRecoveryMessages = Readonly<{
2
+ open: string;
3
+ title: string;
4
+ explanation: string;
5
+ recoveryCode: string;
6
+ recoveryCodeHelp: string;
7
+ newPin: string;
8
+ confirmPin: string;
9
+ submit: string;
10
+ cancel: string;
11
+ mismatch: string;
12
+ completed: string;
13
+ replacementCode: string;
14
+ replacementCodeHelp: string;
15
+ notificationQueued: string;
16
+ }>;
17
+ /** Collects recovery proof and a replacement PIN without receiving wallet keys. */
18
+ export declare function ProfileRecoveryPanel({ profileId, messages, disabled, onRecover, onClose }: Readonly<{
19
+ profileId: string;
20
+ messages: ProfileRecoveryMessages;
21
+ disabled?: boolean;
22
+ onRecover: (input: {
23
+ profileId: string;
24
+ recoveryCode: string;
25
+ newPin: string;
26
+ }) => Promise<string>;
27
+ onClose: () => void;
28
+ }>): import("react").JSX.Element;
29
+ //# sourceMappingURL=ProfileRecoveryPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProfileRecoveryPanel.d.ts","sourceRoot":"","sources":["../src/ProfileRecoveryPanel.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,gBAAgB,EAAE,MAAM,CAAA;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,MAAM,CAAA;IACvB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,kBAAkB,EAAE,MAAM,CAAA;CAC3B,CAAC,CAAA;AAEF,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAgB,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,QAAQ,CAAC;IAC3G,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,uBAAuB,CAAA;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAClG,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAC,+BAwCD"}
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ /** Collects recovery proof and a replacement PIN without receiving wallet keys. */
5
+ export function ProfileRecoveryPanel({ profileId, messages, disabled = false, onRecover, onClose }) {
6
+ const [busy, setBusy] = useState(false);
7
+ const [error, setError] = useState('');
8
+ const [replacementCode, setReplacementCode] = useState('');
9
+ async function submit(event) {
10
+ event.preventDefault();
11
+ setError('');
12
+ const form = new FormData(event.currentTarget);
13
+ const newPin = String(form.get('newPin') || '');
14
+ if (newPin !== String(form.get('confirmPin') || '')) {
15
+ setError(messages.mismatch);
16
+ return;
17
+ }
18
+ setBusy(true);
19
+ try {
20
+ setReplacementCode(await onRecover({ profileId, recoveryCode: String(form.get('recoveryCode') || ''), newPin }));
21
+ }
22
+ catch (reason) {
23
+ setError(reason instanceof Error ? reason.message : 'profile_recovery_failed');
24
+ }
25
+ finally {
26
+ setBusy(false);
27
+ }
28
+ }
29
+ if (replacementCode)
30
+ return _jsxs("section", { className: "panel profile-recovery-result", role: "status", children: [_jsx("strong", { children: messages.completed }), _jsxs("label", { children: [messages.replacementCode, _jsx("textarea", { readOnly: true, rows: 3, value: replacementCode, "aria-label": messages.replacementCode })] }), _jsx("p", { children: messages.replacementCodeHelp }), _jsx("p", { children: messages.notificationQueued }), _jsx("button", { type: "button", "data-portal-action": "close-profile-recovery", onClick: onClose, children: messages.cancel })] });
31
+ return _jsxs("form", { className: "panel form profile-recovery-form", onSubmit: submit, children: [_jsxs("div", { children: [_jsx("h3", { children: messages.title }), _jsx("p", { className: "muted", children: messages.explanation })] }), _jsxs("label", { children: [messages.recoveryCode, _jsx("textarea", { name: "recoveryCode", rows: 3, required: true, autoComplete: "off", "aria-describedby": "profile-recovery-help" })] }), _jsx("p", { id: "profile-recovery-help", className: "technical-note", children: messages.recoveryCodeHelp }), _jsxs("label", { children: [messages.newPin, _jsx("input", { name: "newPin", type: "password", minLength: 6, required: true, autoComplete: "new-password" })] }), _jsxs("label", { children: [messages.confirmPin, _jsx("input", { name: "confirmPin", type: "password", minLength: 6, required: true, autoComplete: "new-password" })] }), error ? _jsx("p", { role: "alert", children: error }) : null, _jsxs("div", { className: "inline-actions", children: [_jsx("button", { "data-portal-action": "submit-profile-recovery", disabled: busy, children: messages.submit }), _jsx("button", { type: "button", "data-portal-action": "cancel-profile-recovery", className: "secondary", disabled: busy || disabled, onClick: onClose, children: messages.cancel })] })] });
32
+ }
@@ -6,6 +6,7 @@ export type SoschainCardSummary = Readonly<{
6
6
  identifier: string;
7
7
  displayName: string;
8
8
  kindLabel: string;
9
+ subjectKind?: 'person' | 'animal';
9
10
  selected?: boolean;
10
11
  openHref: string;
11
12
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"SoschainWorkspace.d.ts","sourceRoot":"","sources":["../src/SoschainWorkspace.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SoschainWorkspace.d.ts","sourceRoot":"","sources":["../src/SoschainWorkspace.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { type ReactNode } from 'react';
2
+ export declare const PERSONAL_SUBJECT_WORKSPACE_AREAS: readonly ["profile", "health", "care", "communications", "access"];
3
+ export type PersonalSubjectWorkspaceAreaId = typeof PERSONAL_SUBJECT_WORKSPACE_AREAS[number];
4
+ export type SubjectWorkspaceArea = Readonly<{
5
+ id: string;
6
+ label: string;
7
+ content: ReactNode;
8
+ icon?: ReactNode;
9
+ disabled?: boolean;
10
+ }>;
11
+ export type SubjectWorkspaceProps = Readonly<{
12
+ ariaLabel: string;
13
+ areas: readonly SubjectWorkspaceArea[];
14
+ activeArea?: string;
15
+ initialArea?: string;
16
+ onAreaChange?: (area: string) => void;
17
+ className?: string;
18
+ tabListClassName?: string;
19
+ tabClassName?: string;
20
+ panelClassName?: string;
21
+ }>;
22
+ /**
23
+ * Shared subject-screen shell for Personal portals.
24
+ *
25
+ * Hosts provide already-authorized content and product-specific additions.
26
+ * Selecting a tab changes presentation only and never grants data access.
27
+ */
28
+ export declare function SubjectWorkspace({ ariaLabel, areas, activeArea, initialArea, onAreaChange, className, tabListClassName, tabClassName, panelClassName, }: SubjectWorkspaceProps): import("react").JSX.Element;
29
+ //# sourceMappingURL=SubjectWorkspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubjectWorkspace.d.ts","sourceRoot":"","sources":["../src/SubjectWorkspace.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAkD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtF,eAAO,MAAM,gCAAgC,oEAMnC,CAAA;AAEV,MAAM,MAAM,8BAA8B,GAAG,OAAO,gCAAgC,CAAC,MAAM,CAAC,CAAA;AAC5F,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,SAAS,CAAA;IAClB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAC,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAA;IACtC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAC,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,KAAK,EACL,UAAU,EACV,WAAW,EACX,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,EAAE,qBAAqB,+BAyDvB"}
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useId, useState } from 'react';
4
+ export const PERSONAL_SUBJECT_WORKSPACE_AREAS = [
5
+ 'profile',
6
+ 'health',
7
+ 'care',
8
+ 'communications',
9
+ 'access',
10
+ ];
11
+ /**
12
+ * Shared subject-screen shell for Personal portals.
13
+ *
14
+ * Hosts provide already-authorized content and product-specific additions.
15
+ * Selecting a tab changes presentation only and never grants data access.
16
+ */
17
+ export function SubjectWorkspace({ ariaLabel, areas, activeArea, initialArea, onAreaChange, className, tabListClassName, tabClassName, panelClassName, }) {
18
+ const firstEnabledArea = areas.find(({ disabled }) => !disabled)?.id ?? '';
19
+ const [uncontrolledArea, setUncontrolledArea] = useState(initialArea ?? firstEnabledArea);
20
+ const requestedArea = activeArea ?? uncontrolledArea;
21
+ const selectedArea = areas.some(({ id, disabled }) => id === requestedArea && !disabled)
22
+ ? requestedArea
23
+ : firstEnabledArea;
24
+ const instanceId = useId().replaceAll(':', '');
25
+ useEffect(() => {
26
+ if (activeArea === undefined && uncontrolledArea !== selectedArea)
27
+ setUncontrolledArea(selectedArea);
28
+ }, [activeArea, selectedArea, uncontrolledArea]);
29
+ function select(area) {
30
+ if (area.disabled)
31
+ return;
32
+ if (activeArea === undefined)
33
+ setUncontrolledArea(area.id);
34
+ onAreaChange?.(area.id);
35
+ }
36
+ function moveWithKeyboard(event, area) {
37
+ const enabledAreas = areas.filter(({ disabled }) => !disabled);
38
+ const currentIndex = enabledAreas.findIndex(({ id }) => id === area.id);
39
+ let nextIndex;
40
+ if (event.key === 'ArrowRight' || event.key === 'ArrowDown')
41
+ nextIndex = (currentIndex + 1) % enabledAreas.length;
42
+ if (event.key === 'ArrowLeft' || event.key === 'ArrowUp')
43
+ nextIndex = (currentIndex - 1 + enabledAreas.length) % enabledAreas.length;
44
+ if (event.key === 'Home')
45
+ nextIndex = 0;
46
+ if (event.key === 'End')
47
+ nextIndex = enabledAreas.length - 1;
48
+ if (nextIndex === undefined || !enabledAreas[nextIndex])
49
+ return;
50
+ event.preventDefault();
51
+ const nextArea = enabledAreas[nextIndex];
52
+ select(nextArea);
53
+ document.getElementById(`${instanceId}-tab-${nextArea.id}`)?.focus();
54
+ }
55
+ return _jsxs("section", { className: className ?? 'uhcp-subject-workspace', children: [_jsx("div", { className: tabListClassName ?? 'uhcp-subject-workspace-tabs', role: "tablist", "aria-label": ariaLabel, children: areas.map((area) => _jsxs("button", { id: `${instanceId}-tab-${area.id}`, type: "button", role: "tab", "aria-selected": selectedArea === area.id, "aria-controls": `${instanceId}-panel-${area.id}`, disabled: area.disabled, className: tabClassName, onClick: () => select(area), onKeyDown: (event) => moveWithKeyboard(event, area), children: [area.icon, area.label] }, area.id)) }), areas.map((area) => selectedArea === area.id ? _jsx("div", { id: `${instanceId}-panel-${area.id}`, role: "tabpanel", "aria-labelledby": `${instanceId}-tab-${area.id}`, className: panelClassName, children: area.content }, area.id) : null)] });
56
+ }
package/dist/index.d.ts CHANGED
@@ -49,4 +49,10 @@ export * from './NotificationComposer.js';
49
49
  export * from './notification-bff-client.js';
50
50
  export * from './CommunicationsWorkspace.js';
51
51
  export * from './SelectedSubjectWorkspace.js';
52
+ export * from './SubjectWorkspace.js';
53
+ export * from './CardPreview.js';
54
+ export * from './CollapsiblePortalSection.js';
55
+ export * from './ElapsedOperationStatus.js';
56
+ export * from './ProfileRecoveryPanel.js';
57
+ export * from './passkey-policy.js';
52
58
  //# sourceMappingURL=index.d.ts.map
@@ -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,2BAA2B,CAAA;AACzC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,8BAA8B,CAAA;AAC5C,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,mCAAmC,CAAA;AACjD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,qCAAqC,CAAA;AACnD,cAAc,mCAAmC,CAAA;AACjD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,0BAA0B,CAAA;AACxC,cAAc,iCAAiC,CAAA;AAC/C,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,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uCAAuC,CAAA;AACrD,cAAc,uBAAuB,CAAA;AACrC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,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,2BAA2B,CAAA;AACzC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,8BAA8B,CAAA;AAC5C,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,mCAAmC,CAAA;AACjD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,qCAAqC,CAAA;AACnD,cAAc,mCAAmC,CAAA;AACjD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,0BAA0B,CAAA;AACxC,cAAc,iCAAiC,CAAA;AAC/C,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,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uCAAuC,CAAA;AACrD,cAAc,uBAAuB,CAAA;AACrC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA"}
package/dist/index.js CHANGED
@@ -49,3 +49,9 @@ export * from './NotificationComposer.js';
49
49
  export * from './notification-bff-client.js';
50
50
  export * from './CommunicationsWorkspace.js';
51
51
  export * from './SelectedSubjectWorkspace.js';
52
+ export * from './SubjectWorkspace.js';
53
+ export * from './CardPreview.js';
54
+ export * from './CollapsiblePortalSection.js';
55
+ export * from './ElapsedOperationStatus.js';
56
+ export * from './ProfileRecoveryPanel.js';
57
+ export * from './passkey-policy.js';
@@ -0,0 +1,9 @@
1
+ /** Reports only a positive browser claim for a user-verifying platform authenticator. */
2
+ export declare function isUserVerifyingPlatformPasskeyAvailable(): Promise<boolean>;
3
+ /** Requires WebAuthn enrollment to finish before issuing a usable wallet session. */
4
+ export declare function openWalletAfterRequiredPasskey<T>(input: Readonly<{
5
+ passkeyRequired: boolean;
6
+ enroll(): Promise<void>;
7
+ open(): Promise<T>;
8
+ }>): Promise<T>;
9
+ //# sourceMappingURL=passkey-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passkey-policy.d.ts","sourceRoot":"","sources":["../src/passkey-policy.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,wBAAsB,uCAAuC,IAAI,OAAO,CAAC,OAAO,CAAC,CAQhF;AAED,qFAAqF;AACrF,wBAAsB,8BAA8B,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC;IACtE,eAAe,EAAE,OAAO,CAAA;IACxB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACvB,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,CAAA;CACnB,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAGd"}
@@ -0,0 +1,18 @@
1
+ /** Reports only a positive browser claim for a user-verifying platform authenticator. */
2
+ export async function isUserVerifyingPlatformPasskeyAvailable() {
3
+ if (typeof PublicKeyCredential === 'undefined'
4
+ || typeof PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable !== 'function')
5
+ return false;
6
+ try {
7
+ return await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ /** Requires WebAuthn enrollment to finish before issuing a usable wallet session. */
14
+ export async function openWalletAfterRequiredPasskey(input) {
15
+ if (input.passkeyRequired)
16
+ await input.enroll();
17
+ return input.open();
18
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "uhc-portal-components-react",
3
- "version": "0.2.19",
4
- "description": "Brand-neutral React portal shell, account preferences and protected-profile UI for UHC and VetChain portals.",
3
+ "version": "0.2.21",
4
+ "description": "Brand-neutral React portal screens, account preferences and protected-profile UI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "files": [