@patientos/website-kit 0.1.0
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/LICENSE +201 -0
- package/README.md +30 -0
- package/dist/checkout-block-CrO7OxI9.d.ts +121 -0
- package/dist/chunk-G3U6EXJM.js +1240 -0
- package/dist/chunk-JTZ6GYA7.js +30 -0
- package/dist/chunk-LCHW6XF4.js +64 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-Q4URDZAK.js +36 -0
- package/dist/chunk-QDO3SJWR.js +72 -0
- package/dist/chunk-THMT43MV.js +61 -0
- package/dist/chunk-TOPPLOH2.js +12229 -0
- package/dist/chunk-ZOF22TJA.js +19 -0
- package/dist/consult-room.d.ts +35 -0
- package/dist/consult-room.js +31540 -0
- package/dist/contrast.d.ts +63 -0
- package/dist/contrast.js +15 -0
- package/dist/index.d.ts +189 -0
- package/dist/index.js +484 -0
- package/dist/islands-impl.d.ts +68 -0
- package/dist/islands-impl.js +23 -0
- package/dist/islands-registry.d.ts +17 -0
- package/dist/islands-registry.js +14 -0
- package/dist/islands.d.ts +57 -0
- package/dist/islands.js +15 -0
- package/dist/patient-surface-theme.d.ts +22 -0
- package/dist/patient-surface-theme.js +7 -0
- package/dist/portal-account-EhOtLV5u.d.ts +113 -0
- package/dist/portal-account.client-CTget1-3.d.ts +169 -0
- package/dist/portal-booking-client.d.ts +232 -0
- package/dist/portal-booking-client.js +51 -0
- package/dist/script-sources.d.ts +11 -0
- package/dist/script-sources.js +17 -0
- package/dist/show-when.d.ts +70 -0
- package/dist/show-when.js +9 -0
- package/dist/website-kit.css +3580 -0
- package/package.json +120 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/patient-surface-theme.ts
|
|
2
|
+
var HEX_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
3
|
+
var LENGTH_RE = /^-?(?:\d+\.?\d*|\.\d+)(?:px|rem|em|%|pt|vh|vw)?$/;
|
|
4
|
+
var FONT_RE = /^[A-Za-z0-9 _,'"-]{1,120}$/;
|
|
5
|
+
function patientSurfaceTheme(themeTokens) {
|
|
6
|
+
if (!themeTokens || typeof themeTokens !== "object") return {};
|
|
7
|
+
const out = {};
|
|
8
|
+
const primary = pick(themeTokens, ["primary", "brandColor"], HEX_RE);
|
|
9
|
+
if (primary) out.primary = primary;
|
|
10
|
+
const accent = pick(themeTokens, ["accent", "accentColor"], HEX_RE);
|
|
11
|
+
if (accent) out.accent = accent;
|
|
12
|
+
const radius = pick(themeTokens, ["radius", "borderRadius"], LENGTH_RE);
|
|
13
|
+
if (radius) out.radius = radius;
|
|
14
|
+
const fontFamily = pick(themeTokens, ["fontFamily", "font"], FONT_RE);
|
|
15
|
+
if (fontFamily) out.fontFamily = fontFamily;
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
function pick(tokens, keys, re) {
|
|
19
|
+
for (const k of keys) {
|
|
20
|
+
const v = tokens[k];
|
|
21
|
+
if (typeof v !== "string") continue;
|
|
22
|
+
const trimmed = v.trim();
|
|
23
|
+
if (re.test(trimmed)) return trimmed;
|
|
24
|
+
}
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
patientSurfaceTheme
|
|
30
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// src/contrast.ts
|
|
2
|
+
function parseHexColor(hex) {
|
|
3
|
+
const raw = hex.trim().replace(/^#/, "");
|
|
4
|
+
if (!/^[0-9a-fA-F]+$/.test(raw)) return null;
|
|
5
|
+
if (raw.length === 3) {
|
|
6
|
+
const [r, g, b] = [...raw].map((c) => parseInt(c + c, 16));
|
|
7
|
+
return { r, g, b };
|
|
8
|
+
}
|
|
9
|
+
if (raw.length === 6) {
|
|
10
|
+
return {
|
|
11
|
+
r: parseInt(raw.slice(0, 2), 16),
|
|
12
|
+
g: parseInt(raw.slice(2, 4), 16),
|
|
13
|
+
b: parseInt(raw.slice(4, 6), 16)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
function relativeLuminance(rgb) {
|
|
19
|
+
const lin = (channel) => {
|
|
20
|
+
const c = channel / 255;
|
|
21
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
22
|
+
};
|
|
23
|
+
return 0.2126 * lin(rgb.r) + 0.7152 * lin(rgb.g) + 0.0722 * lin(rgb.b);
|
|
24
|
+
}
|
|
25
|
+
function contrastRatio(a, b) {
|
|
26
|
+
const ca = parseHexColor(a);
|
|
27
|
+
const cb = parseHexColor(b);
|
|
28
|
+
if (!ca || !cb) return 1;
|
|
29
|
+
const la = relativeLuminance(ca);
|
|
30
|
+
const lb = relativeLuminance(cb);
|
|
31
|
+
const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
|
|
32
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
33
|
+
}
|
|
34
|
+
var WHITE = "#ffffff";
|
|
35
|
+
var INK = "#1b2733";
|
|
36
|
+
function deriveForeground(bg) {
|
|
37
|
+
if (!parseHexColor(bg)) return INK;
|
|
38
|
+
return contrastRatio(bg, WHITE) >= contrastRatio(bg, INK) ? WHITE : INK;
|
|
39
|
+
}
|
|
40
|
+
var AA_NORMAL = 4.5;
|
|
41
|
+
var KIT_PRIMARY = "#0f766e";
|
|
42
|
+
function toHex({ r, g, b }) {
|
|
43
|
+
const hex = (v) => Math.round(Math.min(255, Math.max(0, v))).toString(16).padStart(2, "0");
|
|
44
|
+
return `#${hex(r)}${hex(g)}${hex(b)}`;
|
|
45
|
+
}
|
|
46
|
+
function derivePrimaryPair(brand) {
|
|
47
|
+
const rgb = parseHexColor(brand);
|
|
48
|
+
if (!rgb) return { bg: KIT_PRIMARY, fg: WHITE };
|
|
49
|
+
const fg = deriveForeground(brand);
|
|
50
|
+
if (contrastRatio(brand, fg) >= AA_NORMAL) return { bg: brand, fg };
|
|
51
|
+
let cur = rgb;
|
|
52
|
+
for (let i = 0; i < 24 && contrastRatio(toHex(cur), WHITE) < AA_NORMAL; i++) {
|
|
53
|
+
cur = { r: cur.r * 0.96, g: cur.g * 0.96, b: cur.b * 0.96 };
|
|
54
|
+
}
|
|
55
|
+
return { bg: toHex(cur), fg: WHITE };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
parseHexColor,
|
|
60
|
+
relativeLuminance,
|
|
61
|
+
contrastRatio,
|
|
62
|
+
deriveForeground,
|
|
63
|
+
derivePrimaryPair
|
|
64
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BookingBlockClient,
|
|
3
|
+
CartClient,
|
|
4
|
+
CertificateFunnelClient,
|
|
5
|
+
CheckoutClient,
|
|
6
|
+
PortalAccountClient,
|
|
7
|
+
PortalPanelClient,
|
|
8
|
+
StoreClient
|
|
9
|
+
} from "./chunk-TOPPLOH2.js";
|
|
10
|
+
import {
|
|
11
|
+
mountIslandsWith
|
|
12
|
+
} from "./chunk-THMT43MV.js";
|
|
13
|
+
|
|
14
|
+
// src/islands-registry.tsx
|
|
15
|
+
var ISLANDS = {
|
|
16
|
+
booking: BookingBlockClient,
|
|
17
|
+
"certificate-funnel": CertificateFunnelClient,
|
|
18
|
+
"portal-panel": PortalPanelClient,
|
|
19
|
+
// PAT-783 — the FULL account surface (manage appointments, download documents, view
|
|
20
|
+
// orders, edit contact details) rendered INSIDE the clinic's own page. It reads and
|
|
21
|
+
// WRITES the patient's own record over the same session-authed `/portal/api/*` edge
|
|
22
|
+
// the teaser already uses; the reason it is permitted here is the reason one-origin
|
|
23
|
+
// exists — finishing the portal without bouncing the patient to another host.
|
|
24
|
+
"portal-account": PortalAccountClient,
|
|
25
|
+
store: StoreClient,
|
|
26
|
+
cart: CartClient,
|
|
27
|
+
checkout: CheckoutClient
|
|
28
|
+
};
|
|
29
|
+
function mountIslands(root, createRoot) {
|
|
30
|
+
return mountIslandsWith(ISLANDS, root, createRoot);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
ISLANDS,
|
|
35
|
+
mountIslands
|
|
36
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// src/show-when.ts
|
|
2
|
+
function isAnswered(v) {
|
|
3
|
+
return v !== void 0 && v !== null && v !== "";
|
|
4
|
+
}
|
|
5
|
+
function combineAnd(states) {
|
|
6
|
+
if (states.includes("not_satisfied")) return "not_satisfied";
|
|
7
|
+
if (states.includes("unknown")) return "unknown";
|
|
8
|
+
return "satisfied";
|
|
9
|
+
}
|
|
10
|
+
function combineOr(states) {
|
|
11
|
+
if (states.includes("satisfied")) return "satisfied";
|
|
12
|
+
if (states.includes("unknown")) return "unknown";
|
|
13
|
+
return "not_satisfied";
|
|
14
|
+
}
|
|
15
|
+
function negateTri(s) {
|
|
16
|
+
if (s === "satisfied") return "not_satisfied";
|
|
17
|
+
if (s === "not_satisfied") return "satisfied";
|
|
18
|
+
return "unknown";
|
|
19
|
+
}
|
|
20
|
+
function evaluateLeaf(leaf, answers) {
|
|
21
|
+
const key = leaf.questionKey;
|
|
22
|
+
if (typeof key !== "string") return "unknown";
|
|
23
|
+
const answer = answers[key];
|
|
24
|
+
if (leaf.op === "answered") return isAnswered(answer) ? "satisfied" : "not_satisfied";
|
|
25
|
+
if (!isAnswered(answer)) return "unknown";
|
|
26
|
+
const value = leaf.value;
|
|
27
|
+
switch (leaf.op) {
|
|
28
|
+
case "equals":
|
|
29
|
+
return answer === value ? "satisfied" : "not_satisfied";
|
|
30
|
+
case "notEquals":
|
|
31
|
+
return answer !== value ? "satisfied" : "not_satisfied";
|
|
32
|
+
case "in":
|
|
33
|
+
if (!Array.isArray(value)) return "unknown";
|
|
34
|
+
return value.includes(answer) ? "satisfied" : "not_satisfied";
|
|
35
|
+
case "gt":
|
|
36
|
+
case "gte":
|
|
37
|
+
case "lt":
|
|
38
|
+
case "lte": {
|
|
39
|
+
if (typeof answer !== "number" || typeof value !== "number") return "unknown";
|
|
40
|
+
const ok = leaf.op === "gt" ? answer > value : leaf.op === "gte" ? answer >= value : leaf.op === "lt" ? answer < value : answer <= value;
|
|
41
|
+
return ok ? "satisfied" : "not_satisfied";
|
|
42
|
+
}
|
|
43
|
+
case "before":
|
|
44
|
+
case "after": {
|
|
45
|
+
if (typeof answer !== "string" || typeof value !== "string") return "unknown";
|
|
46
|
+
const ok = leaf.op === "before" ? answer < value : answer > value;
|
|
47
|
+
return ok ? "satisfied" : "not_satisfied";
|
|
48
|
+
}
|
|
49
|
+
default:
|
|
50
|
+
return "unknown";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function evaluateTri(cond, answers) {
|
|
54
|
+
if (!cond || typeof cond !== "object") return "unknown";
|
|
55
|
+
const node = cond;
|
|
56
|
+
if (Array.isArray(node.and)) return combineAnd(node.and.map((c) => evaluateTri(c, answers)));
|
|
57
|
+
if (Array.isArray(node.or)) return combineOr(node.or.map((c) => evaluateTri(c, answers)));
|
|
58
|
+
if ("not" in node) return negateTri(evaluateTri(node.not, answers));
|
|
59
|
+
return evaluateLeaf(node, answers);
|
|
60
|
+
}
|
|
61
|
+
function evaluateShowWhen(cond, answers) {
|
|
62
|
+
if (cond == null) return true;
|
|
63
|
+
return evaluateTri(cond, answers) === "satisfied";
|
|
64
|
+
}
|
|
65
|
+
function visibleQuestions(ordered, answers) {
|
|
66
|
+
return ordered.filter((q) => evaluateShowWhen(q.showWhen ?? null, answers));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
evaluateShowWhen,
|
|
71
|
+
visibleQuestions
|
|
72
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/islands.tsx
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var ISLAND_ATTR = "data-pos-island";
|
|
5
|
+
var ISLAND_PROPS_ATTR = "data-pos-island-props";
|
|
6
|
+
var MOUNTED_ATTR = "data-pos-island-mounted";
|
|
7
|
+
function Island({
|
|
8
|
+
name,
|
|
9
|
+
props,
|
|
10
|
+
children,
|
|
11
|
+
className,
|
|
12
|
+
style
|
|
13
|
+
}) {
|
|
14
|
+
const serialised = JSON.stringify(props ?? {});
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
...{ [ISLAND_ATTR]: name, [ISLAND_PROPS_ATTR]: serialised },
|
|
19
|
+
className,
|
|
20
|
+
style,
|
|
21
|
+
children
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
function readIslandProps(el) {
|
|
26
|
+
const raw = el.getAttribute(ISLAND_PROPS_ATTR);
|
|
27
|
+
if (!raw) return {};
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
31
|
+
return parsed;
|
|
32
|
+
} catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function mountIslandsWith(registry, root, createRoot) {
|
|
37
|
+
const markers = root.querySelectorAll(`[${ISLAND_ATTR}]`);
|
|
38
|
+
let mounted = 0;
|
|
39
|
+
for (const el of Array.from(markers)) {
|
|
40
|
+
if (el.hasAttribute(MOUNTED_ATTR)) continue;
|
|
41
|
+
const name = el.getAttribute(ISLAND_ATTR);
|
|
42
|
+
if (!name) continue;
|
|
43
|
+
const Impl = registry[name];
|
|
44
|
+
if (!Impl) {
|
|
45
|
+
console.warn(`[patientos] unknown island "${name}" \u2014 skipped`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
el.setAttribute(MOUNTED_ATTR, "");
|
|
49
|
+
createRoot(el).render(React.createElement(Impl, readIslandProps(el)));
|
|
50
|
+
mounted += 1;
|
|
51
|
+
}
|
|
52
|
+
return mounted;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
ISLAND_ATTR,
|
|
57
|
+
ISLAND_PROPS_ATTR,
|
|
58
|
+
Island,
|
|
59
|
+
readIslandProps,
|
|
60
|
+
mountIslandsWith
|
|
61
|
+
};
|