@polymorfa/react 0.1.0-dev.20260922094209

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.
@@ -0,0 +1,47 @@
1
+ import { type ReactNode } from "react";
2
+ import { PolymorfaClient, type PolymorfaClientSnapshot, type PolymorfaPermission } from "@polymorfa/browser";
3
+ import { type Appearance, type AppearanceInput, type Locale } from "@polymorfa/ui";
4
+ export interface PolymorfaReactConfiguration {
5
+ readonly appearance: Appearance;
6
+ readonly locale: Locale;
7
+ }
8
+ export interface PolymorfaProviderProps {
9
+ readonly appearance?: AppearanceInput;
10
+ readonly locale?: Locale;
11
+ /**
12
+ * Your handler's token route, for example `/api/polymorfa/token`. The
13
+ * provider fetches a client token from it, refreshes it before expiry and
14
+ * shares it with every component inside.
15
+ */
16
+ readonly tokenEndpoint?: string;
17
+ /** A client you created yourself; takes precedence over `tokenEndpoint`. */
18
+ readonly client?: PolymorfaClient;
19
+ readonly children?: ReactNode;
20
+ }
21
+ export declare function PolymorfaProvider({ appearance, locale, tokenEndpoint, client, children, }: PolymorfaProviderProps): import("react").JSX.Element;
22
+ export declare function usePolymorfa(): PolymorfaReactConfiguration;
23
+ /**
24
+ * The provider's client: token refresh, `can()`, and the browser Messaging
25
+ * client. Throws outside `<PolymorfaProvider tokenEndpoint>`.
26
+ */
27
+ export declare function usePolymorfaClient(): PolymorfaClient;
28
+ /** The provider's client, or `undefined` without one. */
29
+ export declare function useOptionalPolymorfaClient(): PolymorfaClient | undefined;
30
+ export interface PolymorfaPermissions {
31
+ /** `unknown` without a provider client: controls render and the server decides. */
32
+ readonly status: PolymorfaClientSnapshot["status"] | "unknown";
33
+ readonly snapshot: PolymorfaClientSnapshot | undefined;
34
+ /** Whether the grant allows `permission`. True without a provider client. */
35
+ can(permission: PolymorfaPermission): boolean;
36
+ }
37
+ /**
38
+ * What the current token allows, for rendering only. The server enforces
39
+ * every permission again; this never widens access.
40
+ */
41
+ export declare function usePermissions(): PolymorfaPermissions;
42
+ /**
43
+ * Whether a control may render. Once a token has been minted without
44
+ * `permission`, logs one development warning naming the component.
45
+ */
46
+ export declare function usePermission(component: string, control: string, permission: PolymorfaPermission): boolean;
47
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EACL,eAAe,EAEf,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AAGvB,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAMD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,CAAC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;CAC/B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,UAAU,EACV,MAAM,EACN,aAAa,EACb,MAAM,EACN,QAAQ,GACT,EAAE,sBAAsB,+BAsBxB;AAgBD,wBAAgB,YAAY,IAAI,2BAA2B,CAQ1D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAOpD;AAED,yDAAyD;AACzD,wBAAgB,0BAA0B,IAAI,eAAe,GAAG,SAAS,CAExE;AAKD,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC;IAC/D,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,GAAG,SAAS,CAAC;IACvD,6EAA6E;IAC7E,GAAG,CAAC,UAAU,EAAE,mBAAmB,GAAG,OAAO,CAAC;CAC/C;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,oBAAoB,CAkBrD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,mBAAmB,GAC9B,OAAO,CAST"}
@@ -0,0 +1,82 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect, useMemo, useSyncExternalStore, } from "react";
3
+ import { PolymorfaClient, warnMissingPermission, } from "@polymorfa/browser";
4
+ import { createLocale, defineAppearance, } from "@polymorfa/ui";
5
+ import { safeSubscribe, useOwnedController } from "./hooks.js";
6
+ const Context = createContext(undefined);
7
+ const ClientContext = createContext(undefined);
8
+ export function PolymorfaProvider({ appearance, locale, tokenEndpoint, client, children, }) {
9
+ const value = useMemo(() => ({
10
+ appearance: defineAppearance(appearance),
11
+ locale: locale ?? createLocale("en"),
12
+ }), [appearance, locale]);
13
+ const owned = useOwnedClient(client === undefined ? tokenEndpoint : undefined);
14
+ const resolved = client ?? owned;
15
+ useEffect(() => {
16
+ resolved?.start();
17
+ }, [resolved]);
18
+ return (_jsx(Context.Provider, { value: value, children: _jsx(ClientContext.Provider, { value: resolved, children: children }) }));
19
+ }
20
+ /** One owned client per endpoint, safe under StrictMode. */
21
+ function useOwnedClient(tokenEndpoint) {
22
+ return useOwnedController(undefined, tokenEndpoint === undefined
23
+ ? undefined
24
+ : () => new PolymorfaClient({ tokenEndpoint }));
25
+ }
26
+ // One shared default keeps memoized components stable without a provider.
27
+ let defaultConfiguration;
28
+ export function usePolymorfa() {
29
+ const value = useContext(Context);
30
+ if (value !== undefined)
31
+ return value;
32
+ defaultConfiguration ??= {
33
+ appearance: defineAppearance(),
34
+ locale: createLocale("en"),
35
+ };
36
+ return defaultConfiguration;
37
+ }
38
+ /**
39
+ * The provider's client: token refresh, `can()`, and the browser Messaging
40
+ * client. Throws outside `<PolymorfaProvider tokenEndpoint>`.
41
+ */
42
+ export function usePolymorfaClient() {
43
+ const client = useContext(ClientContext);
44
+ if (client === undefined)
45
+ throw new Error('usePolymorfaClient() needs <PolymorfaProvider tokenEndpoint="/api/polymorfa/token">.');
46
+ return client;
47
+ }
48
+ /** The provider's client, or `undefined` without one. */
49
+ export function useOptionalPolymorfaClient() {
50
+ return useContext(ClientContext);
51
+ }
52
+ const NO_SUBSCRIPTION = () => () => undefined;
53
+ const NO_SNAPSHOT = () => undefined;
54
+ /**
55
+ * What the current token allows, for rendering only. The server enforces
56
+ * every permission again; this never widens access.
57
+ */
58
+ export function usePermissions() {
59
+ const client = useContext(ClientContext);
60
+ const snapshot = useSyncExternalStore(client === undefined ? NO_SUBSCRIPTION : safeSubscribe(client), client?.getSnapshot ?? NO_SNAPSHOT, client?.getSnapshot ?? NO_SNAPSHOT);
61
+ return useMemo(() => ({
62
+ status: snapshot?.status ?? "unknown",
63
+ snapshot,
64
+ can: (permission) => snapshot === undefined
65
+ ? true
66
+ : snapshot.grant?.allow.includes(permission) === true,
67
+ }), [snapshot]);
68
+ }
69
+ /**
70
+ * Whether a control may render. Once a token has been minted without
71
+ * `permission`, logs one development warning naming the component.
72
+ */
73
+ export function usePermission(component, control, permission) {
74
+ const permissions = usePermissions();
75
+ const allowed = permissions.can(permission);
76
+ const minted = permissions.snapshot?.grant !== undefined;
77
+ useEffect(() => {
78
+ if (minted && !allowed)
79
+ warnMissingPermission(component, control, permission);
80
+ }, [minted, allowed, component, control, permission]);
81
+ return allowed;
82
+ }
@@ -0,0 +1,100 @@
1
+ import { InboxController, type ConnectWhatsAppOptions, type ConnectWhatsAppResult, type InboxContact, type InboxConversation, type InboxDataSource, type TemplateDraft } from "@polymorfa/browser";
2
+ import { type SlotClassNames } from "@polymorfa/ui";
3
+ import { type ReactNode } from "react";
4
+ import { type ComposeBoxProps, type Slots } from "./components.js";
5
+ /** Initials avatar, or the image when `src` is set. */
6
+ export declare function Avatar({ id, name, src, size, slots, }: {
7
+ readonly id: string;
8
+ readonly name: string;
9
+ readonly src?: string;
10
+ readonly size?: "s" | "l";
11
+ readonly slots: Slots;
12
+ }): import("react").JSX.Element;
13
+ export interface ConversationListProps {
14
+ readonly controller: InboxController;
15
+ readonly onSelect?: (conversation: InboxConversation) => void;
16
+ /** Heading above the list. Defaults to the locale's `inbox.title`. */
17
+ readonly title?: ReactNode;
18
+ /** Show the search field. Defaults to `true`. */
19
+ readonly search?: boolean;
20
+ readonly className?: string;
21
+ readonly classNames?: SlotClassNames;
22
+ }
23
+ /** The inbox's conversation list, usable on its own. */
24
+ export declare function ConversationList({ controller, onSelect, title, search, className, classNames, }: ConversationListProps): import("react").JSX.Element;
25
+ export interface ContactPanelProps {
26
+ readonly conversation: InboxConversation;
27
+ /** Loads the contact. Defaults to the inbox source's `contact()`. */
28
+ readonly load?: (conversation: InboxConversation, signal: AbortSignal) => Promise<InboxContact | undefined>;
29
+ readonly onClose?: () => void;
30
+ /** Extra content under the details, such as your CRM fields. */
31
+ readonly children?: ReactNode;
32
+ /** Actions under the name, for example a `<CallButton/>`. */
33
+ readonly actions?: ReactNode;
34
+ readonly className?: string;
35
+ readonly classNames?: SlotClassNames;
36
+ }
37
+ /** Contact details for a conversation. */
38
+ export declare function ContactPanel({ conversation, load, onClose, children, actions, className, classNames, }: ContactPanelProps): import("react").JSX.Element;
39
+ export interface InboxProps {
40
+ /**
41
+ * Where conversations come from. Defaults to your handler's history and
42
+ * events routes, through the provider's client.
43
+ */
44
+ readonly source?: InboxDataSource;
45
+ /** Supply your own controller to drive the inbox from outside. */
46
+ readonly controller?: InboxController;
47
+ /** Open this conversation first. */
48
+ readonly defaultConversationId?: string;
49
+ readonly onSelect?: (conversation: InboxConversation | undefined) => void;
50
+ /** Show the contact panel toggle. Defaults to `true`. */
51
+ readonly contactPanel?: boolean;
52
+ /** Extra content inside the contact panel. */
53
+ readonly renderContact?: (conversation: InboxConversation) => ReactNode;
54
+ /**
55
+ * Actions in the thread header. Defaults to a `<CallButton/>` when the
56
+ * grant allows `voip_place` and the conversation has a phone number.
57
+ */
58
+ readonly renderActions?: (conversation: InboxConversation) => ReactNode;
59
+ readonly title?: ReactNode;
60
+ readonly composerProps?: Omit<ComposeBoxProps, "controller" | "createController" | "conversation">;
61
+ readonly className?: string;
62
+ readonly classNames?: SlotClassNames;
63
+ }
64
+ /**
65
+ * A complete inbox: conversation list, chat and contact panel. Controls the
66
+ * token does not allow are hidden, with one development warning each.
67
+ */
68
+ export declare function Inbox({ source, controller, defaultConversationId, onSelect, contactPanel, renderContact, renderActions, title, composerProps, className, classNames, }: InboxProps): import("react").JSX.Element;
69
+ export interface ConnectWhatsAppButtonProps extends Pick<ConnectWhatsAppOptions, "target" | "open"> {
70
+ readonly children?: ReactNode;
71
+ readonly onConnect?: (result: ConnectWhatsAppResult) => void;
72
+ readonly onError?: (error: unknown) => void;
73
+ readonly className?: string;
74
+ readonly classNames?: SlotClassNames;
75
+ }
76
+ /**
77
+ * Creates a QuickLink through your handler and opens Polymorfa's hosted
78
+ * pairing page. The pairing UI is never embedded.
79
+ */
80
+ export declare function ConnectWhatsAppButton({ target, open, children, onConnect, onError, className, classNames, }: ConnectWhatsAppButtonProps): import("react").JSX.Element | null;
81
+ export interface SessionStatusProps {
82
+ /** Show a Retry button after a failure. Defaults to `true`. */
83
+ readonly retry?: boolean;
84
+ readonly className?: string;
85
+ readonly classNames?: SlotClassNames;
86
+ }
87
+ /** The provider's connection state as a badge, with a retry prompt. */
88
+ export declare function SessionStatus({ retry, className, classNames, }: SessionStatusProps): import("react").JSX.Element;
89
+ export interface TemplateManagerProps {
90
+ /** Starting draft for "New template". */
91
+ readonly newDraft?: TemplateDraft;
92
+ readonly className?: string;
93
+ readonly classNames?: SlotClassNames;
94
+ }
95
+ /**
96
+ * Lists your project's templates and edits one with `<TemplateBuilder/>`,
97
+ * through your handler's `templates` route.
98
+ */
99
+ export declare function TemplateManager({ newDraft, className, classNames, }: TemplateManagerProps): import("react").JSX.Element | null;
100
+ //# sourceMappingURL=dropin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dropin.d.ts","sourceRoot":"","sources":["../src/dropin.tsx"],"names":[],"mappings":"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"}
package/dist/dropin.js ADDED
@@ -0,0 +1,298 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { InboxController, MessageComposerController, TemplateBuilderController, connectWhatsApp, createConversationComposerActions, createHandlerInboxSource, createSameOriginTemplateBuilderTransport, } from "@polymorfa/browser";
3
+ import { formatDayLabel, formatMessageTime, } from "@polymorfa/ui";
4
+ import { useCallback, useEffect, useId, useMemo, useRef, useState, } from "react";
5
+ import { ComposeBox, Icon, MessageList, TemplateBuilder, text, useShell, useSlots, } from "./components.js";
6
+ import { useOptionalPolymorfaClient, usePermission, usePermissions, usePolymorfa, usePolymorfaClient, } from "./context.js";
7
+ import { useController, useOwnedController } from "./hooks.js";
8
+ import { CallButton } from "./call-button.js";
9
+ // ── Shared pieces ─────────────────────────────────────────────────────
10
+ function initials(name) {
11
+ const letters = name
12
+ .replace(/[^\p{L}\p{N}\s]/gu, " ")
13
+ .trim()
14
+ .split(/\s+/)
15
+ .filter(Boolean)
16
+ .map((word) => Array.from(word)[0] ?? "");
17
+ const first = letters[0] ?? "";
18
+ const last = letters.length > 1 ? (letters[letters.length - 1] ?? "") : "";
19
+ return (first + last).toUpperCase() || "#";
20
+ }
21
+ function hue(id) {
22
+ let hash = 0;
23
+ for (const character of id)
24
+ hash = Math.imul(hash ^ character.charCodeAt(0), 2654435761);
25
+ return Math.round(Math.abs(hash % 360) * 137.508) % 360;
26
+ }
27
+ function displayName(conversation) {
28
+ return conversation.name ?? conversation.phoneNumber ?? conversation.id;
29
+ }
30
+ /** Initials avatar, or the image when `src` is set. */
31
+ export function Avatar({ id, name, src, size, slots, }) {
32
+ const slot = slots("avatar", size === undefined ? "pmfa-avatar" : `pmfa-avatar pmfa-avatar-${size}`);
33
+ const [failed, setFailed] = useState(false);
34
+ return (_jsx("span", { ...slot, style: {
35
+ ...slot.style,
36
+ "--pmfa-avatar-hue": String(hue(id)),
37
+ }, "aria-hidden": "true", children: src !== undefined && !failed ? (_jsx("img", { src: src, alt: "", onError: () => setFailed(true) })) : (initials(name)) }));
38
+ }
39
+ function conversationTime(configuration, time, now) {
40
+ const today = new Date(now).toDateString() === new Date(time).toDateString();
41
+ if (today)
42
+ return formatMessageTime(time, configuration.locale.code) ?? "";
43
+ return formatDayLabel(time, configuration.locale.code, {
44
+ today: text(configuration, "chat.today"),
45
+ yesterday: text(configuration, "chat.yesterday"),
46
+ }, now);
47
+ }
48
+ /** The inbox's conversation list, usable on its own. */
49
+ export function ConversationList({ controller, onSelect, title, search = true, className, classNames, }) {
50
+ const snapshot = useController(controller);
51
+ const configuration = usePolymorfa();
52
+ const slots = useSlots(classNames);
53
+ const list = slots("conversationList", "pmfa-inbox-list");
54
+ const [query, setQuery] = useState("");
55
+ const headingId = useId();
56
+ const normalized = query.trim().toLowerCase();
57
+ const visible = useMemo(() => normalized === ""
58
+ ? snapshot.conversations
59
+ : snapshot.conversations.filter((conversation) => [
60
+ conversation.name,
61
+ conversation.phoneNumber,
62
+ conversation.lastMessage?.text,
63
+ ].some((value) => value?.toLowerCase().includes(normalized))), [snapshot.conversations, normalized]);
64
+ const now = Date.now();
65
+ return (_jsxs("section", { ...list, className: [list.className, className].filter(Boolean).join(" "), "aria-labelledby": headingId, "data-pmfa": "conversation-list", children: [_jsxs("div", { className: "pmfa-inbox-head", children: [_jsx("h2", { className: "pmfa-inbox-title", id: headingId, children: title ?? text(configuration, "inbox.title") }), search && (_jsxs("div", { ...slots("conversationSearch", "pmfa-search"), children: [_jsx(Icon, { name: "search" }), _jsx("input", { ...slots("input", "pmfa-input"), type: "search", value: query, placeholder: text(configuration, "inbox.search"), "aria-label": text(configuration, "inbox.search"), onChange: (event) => setQuery(event.currentTarget.value) })] }))] }), snapshot.status === "loading" || snapshot.status === "idle" ? (_jsxs("div", { className: "pmfa-skeleton", "aria-busy": "true", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {}), _jsx("span", { className: "pmfa-sr", children: text(configuration, "status.loading") })] })) : snapshot.status === "error" ? (_jsxs("div", { className: "pmfa-inbox-state", role: "alert", children: [_jsx("p", { className: "pmfa-error", children: text(configuration, "inbox.loadError") }), _jsx("button", { type: "button", ...slots("button", "pmfa-btn"), onClick: () => void controller.load(), children: text(configuration, "common.retry") })] })) : visible.length === 0 ? (_jsxs("div", { ...slots("empty", "pmfa-inbox-state"), children: [_jsx(Icon, { name: normalized === "" ? "inbox" : "search" }), text(configuration, normalized === "" ? "inbox.empty" : "inbox.noMatches")] })) : (_jsxs("ul", { className: "pmfa-convs", children: [visible.map((conversation) => {
66
+ const name = displayName(conversation);
67
+ const selected = conversation.id === snapshot.selectedId;
68
+ const last = conversation.lastMessage;
69
+ const unread = conversation.unreadCount;
70
+ return (_jsx("li", { children: _jsxs("button", { type: "button", ...slots("conversationItem", "pmfa-conv"), "aria-current": selected ? "true" : undefined, ...(unread > 0 ? { "data-unread": "" } : {}), onClick: () => {
71
+ controller.select(conversation.id);
72
+ onSelect?.(conversation);
73
+ }, children: [_jsx(Avatar, { id: conversation.id, name: name, ...(conversation.avatarUrl === undefined
74
+ ? {}
75
+ : { src: conversation.avatarUrl }), slots: slots }), _jsx("span", { className: "pmfa-conv-name", children: name }), _jsx("span", { className: "pmfa-conv-time", children: conversation.lastActivity > 0
76
+ ? conversationTime(configuration, conversation.lastActivity, now)
77
+ : "" }), _jsx("span", { className: "pmfa-conv-preview", children: last === undefined
78
+ ? (conversation.phoneNumber ?? "")
79
+ : `${last.direction === "outbound" ? text(configuration, "inbox.you") : ""}${last.text}` }), unread > 0 && (_jsxs("span", { ...slots("unreadBadge", "pmfa-badge"), children: [_jsx("span", { "aria-hidden": "true", children: unread > 99 ? "99+" : unread }), _jsx("span", { className: "pmfa-sr", children: text(configuration, "inbox.unread", {
80
+ count: String(unread),
81
+ }) })] }))] }) }, conversation.id));
82
+ }), snapshot.hasMore && normalized === "" && (_jsx("li", { className: "pmfa-loadmore", style: { alignItems: "center", padding: 8 }, children: _jsx("button", { type: "button", ...slots("loadMore", "pmfa-btn"), disabled: snapshot.status === "loading_more", onClick: () => void controller.loadMore(), children: text(configuration, "inbox.loadMore") }) }))] }))] }));
83
+ }
84
+ /** Contact details for a conversation. */
85
+ export function ContactPanel({ conversation, load, onClose, children, actions, className, classNames, }) {
86
+ const configuration = usePolymorfa();
87
+ const slots = useSlots(classNames);
88
+ const panel = slots("contactPanel", "pmfa-contact");
89
+ const [contact, setContact] = useState();
90
+ const headingId = useId();
91
+ useEffect(() => {
92
+ setContact(undefined);
93
+ if (load === undefined)
94
+ return;
95
+ const abort = new AbortController();
96
+ load(conversation, abort.signal).then((value) => {
97
+ if (!abort.signal.aborted)
98
+ setContact(value);
99
+ }, () => undefined);
100
+ return () => abort.abort();
101
+ }, [conversation.id, load]);
102
+ const name = contact?.name ?? displayName(conversation);
103
+ const phone = contact?.phoneNumber ?? conversation.phoneNumber;
104
+ const rows = [];
105
+ if (phone !== undefined)
106
+ rows.push({ label: text(configuration, "inbox.phone"), value: phone });
107
+ if (contact?.email !== undefined)
108
+ rows.push({
109
+ label: text(configuration, "inbox.email"),
110
+ value: contact.email,
111
+ });
112
+ if (contact?.about !== undefined)
113
+ rows.push({
114
+ label: text(configuration, "inbox.about"),
115
+ value: contact.about,
116
+ });
117
+ for (const field of contact?.fields ?? [])
118
+ rows.push(field);
119
+ const avatar = contact?.avatarUrl ?? conversation.avatarUrl;
120
+ return (_jsxs("aside", { ...panel, className: [panel.className, className].filter(Boolean).join(" "), "aria-labelledby": headingId, "data-pmfa": "contact-panel", children: [_jsxs("div", { className: "pmfa-contact-head", children: [_jsx("h3", { id: headingId, children: text(configuration, "inbox.contact") }), onClose !== undefined && (_jsx("button", { type: "button", ...slots("button", "pmfa-btn pmfa-btn-ghost pmfa-btn-icon"), "aria-label": text(configuration, "inbox.hideContact"), onClick: onClose, children: _jsx(Icon, { name: "close" }) }))] }), _jsxs("div", { className: "pmfa-contact-card", children: [_jsx(Avatar, { id: conversation.id, name: name, ...(avatar === undefined ? {} : { src: avatar }), size: "l", slots: slots }), _jsx("p", { className: "pmfa-contact-name", children: name }), phone !== undefined && phone !== name && (_jsx("span", { className: "pmfa-contact-phone", children: phone })), actions !== undefined && (_jsx("div", { className: "pmfa-contact-actions", children: actions }))] }), rows.length > 0 && (_jsx("dl", { className: "pmfa-contact-fields", children: rows.map((row, index) => (_jsxs("div", { children: [_jsx("dt", { children: row.label }), _jsx("dd", { children: row.value })] }, `${row.label}-${index}`))) })), children] }));
121
+ }
122
+ // ── Thread ────────────────────────────────────────────────────────────
123
+ const noUpload = async () => {
124
+ throw new Error("Attachments need an upload adapter.");
125
+ };
126
+ function Thread({ inbox, conversation, slots, onBack, contactOpen, onToggleContact, showContactToggle, actions, composerProps, }) {
127
+ const configuration = usePolymorfa();
128
+ const controller = inbox.conversation(conversation);
129
+ const canSend = usePermission("Inbox", "composer", "send_message");
130
+ const ownedComposer = useOwnedController(undefined, canSend
131
+ ? () => new MessageComposerController(createConversationComposerActions(controller, noUpload))
132
+ : undefined);
133
+ // A revoked permission hides the composer even if one was created.
134
+ const composer = canSend ? ownedComposer : undefined;
135
+ const threadRef = useRef(null);
136
+ const reply = useCallback((message) => {
137
+ composer?.setReplyTo(message.id);
138
+ threadRef.current?.querySelector("textarea")?.focus();
139
+ }, [composer]);
140
+ const name = displayName(conversation);
141
+ const sub = conversation.phoneNumber !== undefined && conversation.phoneNumber !== name
142
+ ? conversation.phoneNumber
143
+ : undefined;
144
+ return (_jsxs("section", { ...slots("thread", "pmfa-thread"), ref: threadRef, "aria-label": name, "data-pmfa": "thread", children: [_jsxs("header", { ...slots("threadHeader", "pmfa-thread-head"), children: [_jsx("button", { type: "button", ...slots("backButton", "pmfa-btn pmfa-btn-ghost pmfa-btn-icon pmfa-back"), "aria-label": text(configuration, "inbox.back"), onClick: onBack, children: _jsx(Icon, { name: "back" }) }), _jsx(Avatar, { id: conversation.id, name: name, ...(conversation.avatarUrl === undefined
145
+ ? {}
146
+ : { src: conversation.avatarUrl }), size: "s", slots: slots }), _jsxs("div", { className: "pmfa-thread-who", children: [_jsx("span", { className: "pmfa-thread-name", children: name }), sub !== undefined && _jsx("span", { className: "pmfa-thread-sub", children: sub })] }), _jsxs("div", { className: "pmfa-thread-actions", children: [actions, showContactToggle && onToggleContact !== undefined && (_jsx("button", { type: "button", ...slots("button", "pmfa-btn pmfa-btn-ghost pmfa-btn-icon"), "aria-label": text(configuration, contactOpen ? "inbox.hideContact" : "inbox.showContact"), title: text(configuration, "inbox.contact"), "aria-expanded": contactOpen, onClick: onToggleContact, children: _jsx(Icon, { name: "info" }) }))] })] }), _jsx(MessageList, { controller: controller, ...(composer === undefined ? {} : { onReply: reply }) }), _jsx("div", { className: "pmfa-thread-foot", children: composer !== undefined ? (_jsx(ComposeBox, { attachments: false, voiceNotes: false, ...composerProps, controller: composer, conversation: controller })) : (_jsx("p", { className: "pmfa-readonly", children: text(configuration, "inbox.readOnly") })) })] }));
147
+ }
148
+ /**
149
+ * A complete inbox: conversation list, chat and contact panel. Controls the
150
+ * token does not allow are hidden, with one development warning each.
151
+ */
152
+ export function Inbox({ source, controller, defaultConversationId, onSelect, contactPanel = true, renderContact, renderActions, title, composerProps, className, classNames, }) {
153
+ const client = useOptionalPolymorfaClient();
154
+ const permissions = usePermissions();
155
+ if (controller === undefined && source === undefined && client === undefined)
156
+ throw new Error('<Inbox/> needs <PolymorfaProvider tokenEndpoint="/api/polymorfa/token"> or a source.');
157
+ const inbox = useOwnedController(controller, () => {
158
+ const created = new InboxController(source ?? createHandlerInboxSource(client));
159
+ if (defaultConversationId !== undefined)
160
+ created.select(defaultConversationId);
161
+ return created;
162
+ });
163
+ const canRead = usePermission("Inbox", "conversations", "read_messages");
164
+ const ready = permissions.status === "unknown" ||
165
+ permissions.snapshot?.grant !== undefined;
166
+ useEffect(() => {
167
+ if (!ready || !canRead)
168
+ return;
169
+ const status = inbox.getSnapshot().status;
170
+ if (status === "idle")
171
+ void inbox.load();
172
+ }, [inbox, ready, canRead]);
173
+ const snapshot = useController(inbox);
174
+ const configuration = usePolymorfa();
175
+ const slots = useSlots(classNames);
176
+ const root = useShell(slots, "inbox", "pmfa-inbox", className);
177
+ const contactAllowed = usePermission("Inbox", "contact panel", "read_contact");
178
+ const canContact = contactPanel && contactAllowed;
179
+ const canCall = permissions.can("voip_place");
180
+ const [contactOpen, setContactOpen] = useState(false);
181
+ const selected = snapshot.conversations.find((conversation) => conversation.id === snapshot.selectedId);
182
+ const latestSelect = useRef(onSelect);
183
+ latestSelect.current = onSelect;
184
+ const loadContact = useMemo(() => inbox.source.contact === undefined
185
+ ? undefined
186
+ : (conversation, signal) => inbox.source.contact(conversation, signal), [inbox]);
187
+ const back = useCallback(() => {
188
+ inbox.select(undefined);
189
+ setContactOpen(false);
190
+ latestSelect.current?.(undefined);
191
+ }, [inbox]);
192
+ if (!canRead && permissions.snapshot?.grant !== undefined)
193
+ return (_jsx("div", { ...root, "data-pmfa": "inbox", "data-view": "list", children: _jsxs("div", { className: "pmfa-inbox-state", style: { gridColumn: "1 / -1" }, children: [_jsx(Icon, { name: "inbox" }), text(configuration, "inbox.empty")] }) }));
194
+ const actions = selected === undefined ? null : renderActions !== undefined ? (renderActions(selected)) : canCall && selected.phoneNumber !== undefined ? (_jsx(CallButton, { to: selected.phoneNumber, variant: "icon" })) : null;
195
+ const showPanel = canContact && contactOpen && selected !== undefined;
196
+ return (_jsxs("div", { ...root, "data-pmfa": "inbox", "data-view": selected === undefined ? "list" : "thread", ...(showPanel ? { "data-contact-open": "" } : {}), children: [_jsx(ConversationList, { controller: inbox, ...(title === undefined ? {} : { title }), ...(classNames === undefined ? {} : { classNames }), onSelect: (conversation) => latestSelect.current?.(conversation) }), selected === undefined ? (_jsx("section", { ...slots("thread", "pmfa-thread"), "data-pmfa": "thread", children: _jsxs("div", { className: "pmfa-inbox-state", children: [_jsx(Icon, { name: "inbox" }), text(configuration, "inbox.select")] }) })) : (_jsx(Thread, { inbox: inbox, conversation: selected, slots: slots, onBack: back, contactOpen: showPanel, onToggleContact: canContact ? () => setContactOpen((open) => !open) : undefined, showContactToggle: canContact, actions: actions, composerProps: composerProps }, selected.id)), showPanel && (_jsx(ContactPanel, { conversation: selected, ...(loadContact === undefined ? {} : { load: loadContact }), onClose: () => setContactOpen(false), ...(classNames === undefined ? {} : { classNames }), children: renderContact?.(selected) }))] }));
197
+ }
198
+ /**
199
+ * Creates a QuickLink through your handler and opens Polymorfa's hosted
200
+ * pairing page. The pairing UI is never embedded.
201
+ */
202
+ export function ConnectWhatsAppButton({ target = "redirect", open, children, onConnect, onError, className, classNames, }) {
203
+ const client = usePolymorfaClient();
204
+ const configuration = usePolymorfa();
205
+ const slots = useSlots(classNames);
206
+ const root = useShell(slots, "connectButton", "pmfa-btn pmfa-btn-primary pmfa-connect", className);
207
+ const allowed = usePermission("ConnectWhatsAppButton", "button", "connect_whatsapp");
208
+ const [busy, setBusy] = useState(false);
209
+ const [failed, setFailed] = useState(false);
210
+ const errorId = useId();
211
+ if (!allowed)
212
+ return null;
213
+ return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", ...root, "data-pmfa": "connect-whatsapp", "aria-busy": busy ? "true" : undefined, "aria-describedby": failed ? errorId : undefined, disabled: busy, onClick: () => {
214
+ setBusy(true);
215
+ setFailed(false);
216
+ connectWhatsApp(client, {
217
+ target,
218
+ ...(open === undefined ? {} : { open }),
219
+ }).then((result) => {
220
+ setBusy(false);
221
+ onConnect?.(result);
222
+ }, (error) => {
223
+ setBusy(false);
224
+ setFailed(true);
225
+ onError?.(error);
226
+ });
227
+ }, children: [_jsx(Icon, { name: "link" }), busy
228
+ ? text(configuration, "connect.opening")
229
+ : (children ?? text(configuration, "connect.button"))] }), failed && (_jsx("span", { id: errorId, role: "alert", ...slots("error", "pmfa pmfa-error"), style: { display: "block", marginTop: 6 }, children: text(configuration, "connect.failed") }))] }));
230
+ }
231
+ /** The provider's connection state as a badge, with a retry prompt. */
232
+ export function SessionStatus({ retry = true, className, classNames, }) {
233
+ const client = usePolymorfaClient();
234
+ const { snapshot } = usePermissions();
235
+ const configuration = usePolymorfa();
236
+ const slots = useSlots(classNames);
237
+ const root = useShell(slots, "sessionStatus", "pmfa-session", className);
238
+ const status = snapshot?.status ?? "idle";
239
+ const label = text(configuration, `status.${status}`);
240
+ const failed = status === "error" || status === "unauthenticated" || status === "retrying";
241
+ return (_jsxs("span", { ...root, role: "status", "data-pmfa": "session-status", "data-status": status, "aria-label": text(configuration, "status.label", { status: label }), children: [_jsx("span", { className: "pmfa-session-dot", "aria-hidden": "true" }), _jsx("span", { "aria-hidden": "true", children: label }), retry && failed && (_jsx("button", { type: "button", ...slots("button", "pmfa-btn pmfa-btn-ghost"), onClick: () => void client.refresh().catch(() => undefined), children: text(configuration, "common.retry") }))] }));
242
+ }
243
+ const DEFAULT_DRAFT = {
244
+ name: "new_template",
245
+ definition: {
246
+ version: 1,
247
+ kind: "standard",
248
+ category: "UTILITY",
249
+ language: "en_US",
250
+ body: "Hello {{name}}",
251
+ variables: [{ name: "name", type: "text", example: "Ada" }],
252
+ },
253
+ };
254
+ /**
255
+ * Lists your project's templates and edits one with `<TemplateBuilder/>`,
256
+ * through your handler's `templates` route.
257
+ */
258
+ export function TemplateManager({ newDraft = DEFAULT_DRAFT, className, classNames, }) {
259
+ const client = usePolymorfaClient();
260
+ const configuration = usePolymorfa();
261
+ const slots = useSlots(classNames);
262
+ const root = useShell(slots, "templateManager", "pmfa-tm", className);
263
+ const allowed = usePermission("TemplateManager", "templates", "manage_templates");
264
+ const transport = useMemo(() => createSameOriginTemplateBuilderTransport({
265
+ path: client.url("templates"),
266
+ fetch: client.fetch,
267
+ }), [client]);
268
+ const [templates, setTemplates] = useState();
269
+ const [failed, setFailed] = useState(false);
270
+ const [editing, setEditing] = useState();
271
+ const reload = useCallback(() => {
272
+ const abort = new AbortController();
273
+ setFailed(false);
274
+ transport.list(abort.signal).then(setTemplates, () => setFailed(true));
275
+ return () => abort.abort();
276
+ }, [transport]);
277
+ useEffect(() => {
278
+ if (allowed && editing === undefined)
279
+ return reload();
280
+ return undefined;
281
+ }, [allowed, editing, reload]);
282
+ if (!allowed)
283
+ return null;
284
+ if (editing !== undefined)
285
+ return (_jsxs("div", { ...root, "data-pmfa": "template-manager", children: [_jsx("div", { className: "pmfa-tm-head", children: _jsxs("button", { type: "button", ...slots("backButton", "pmfa-btn pmfa-btn-ghost"), onClick: () => setEditing(undefined), children: [_jsx(Icon, { name: "back" }), text(configuration, "templates.back")] }) }), _jsx(TemplateEditor, { transport: transport, ...(editing.id === undefined ? {} : { templateId: editing.id }), draft: newDraft, ...(classNames === undefined ? {} : { classNames }) }, editing.key)] }));
286
+ return (_jsxs("div", { ...root, "data-pmfa": "template-manager", children: [_jsxs("div", { className: "pmfa-tm-head", children: [_jsx("h2", { children: text(configuration, "templates.list") }), _jsxs("button", { type: "button", ...slots("primaryButton", "pmfa-btn pmfa-btn-primary"), onClick: () => setEditing({ key: Date.now() }), children: [_jsx(Icon, { name: "plus" }), text(configuration, "templates.new")] })] }), failed ? (_jsxs("div", { className: "pmfa-inbox-state", role: "alert", children: [_jsx("p", { className: "pmfa-error", children: text(configuration, "templates.loadError") }), _jsx("button", { type: "button", ...slots("button", "pmfa-btn"), onClick: reload, children: text(configuration, "common.retry") })] })) : templates === undefined ? (_jsxs("div", { className: "pmfa-skeleton", "aria-busy": "true", children: [_jsx("span", {}), _jsx("span", {})] })) : templates.length === 0 ? (_jsx("div", { ...slots("empty", "pmfa-inbox-state"), children: text(configuration, "templates.empty") })) : (_jsx("ul", { ...slots("templateList", "pmfa-tm-list"), children: templates.map((template) => (_jsx("li", { children: _jsxs("button", { type: "button", ...slots("templateItem", "pmfa-tm-item"), onClick: () => setEditing({ id: template.id, key: Date.now() }), children: [_jsx("span", { className: "pmfa-tm-name", children: template.name }), _jsx("span", { className: "pmfa-tm-body", children: template.definition.body }), _jsxs("span", { className: "pmfa-tm-meta", children: [_jsx("span", { className: "pmfa-tag", "data-status": template.status, children: template.status }), _jsx("span", { className: "pmfa-tag", children: template.category }), _jsx("span", { className: "pmfa-tag", children: template.language })] })] }) }, template.id))) }))] }));
287
+ }
288
+ function TemplateEditor({ transport, templateId, draft, classNames, }) {
289
+ const controller = useOwnedController(undefined, () => {
290
+ const created = new TemplateBuilderController(transport);
291
+ if (templateId === undefined)
292
+ created.create(draft);
293
+ else
294
+ void created.load(templateId);
295
+ return created;
296
+ });
297
+ return (_jsx(TemplateBuilder, { controller: controller, ...(classNames === undefined ? {} : { classNames }) }));
298
+ }
@@ -0,0 +1,30 @@
1
+ export interface ReactController<TSnapshot> {
2
+ getSnapshot(): TSnapshot;
3
+ subscribe(listener: () => void): () => void;
4
+ dispose(): void;
5
+ }
6
+ type Subscribable = {
7
+ subscribe(listener: () => void): () => void;
8
+ };
9
+ /**
10
+ * `controller.subscribe`, except that a disposed controller yields a no-op
11
+ * subscription. Under StrictMode an owned controller can be disposed and
12
+ * replaced while a child is still subscribing to it; the child re-renders
13
+ * with the replacement right after.
14
+ * @internal
15
+ */
16
+ export declare function safeSubscribe(controller: Subscribable): (listener: () => void) => () => void;
17
+ export declare function useController<TSnapshot>(controller: ReactController<TSnapshot>): TSnapshot;
18
+ /**
19
+ * The passed `controller`, or one made by `create` that this component owns
20
+ * and disposes. Safe under StrictMode and `<Activity>`: when an effect
21
+ * cleanup disposes the owned controller and the effect runs again, a fresh
22
+ * controller replaces it and the component re-renders with it.
23
+ * @internal
24
+ */
25
+ export declare function useOwnedController<T extends {
26
+ dispose(): void;
27
+ }>(controller: T | undefined, create: (() => T) | undefined): T | undefined;
28
+ export declare function useResolvedController<TSnapshot, TController extends ReactController<TSnapshot>>(controller: TController | undefined, createController: (() => TController) | undefined): TController;
29
+ export {};
30
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe,CAAC,SAAS;IACxC,WAAW,IAAI,SAAS,CAAC;IACzB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,KAAK,YAAY,GAAG;IAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;CAAE,CAAC;AAMpE;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,YAAY,GACvB,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAetC;AAED,wBAAgB,aAAa,CAAC,SAAS,EACrC,UAAU,EAAE,eAAe,CAAC,SAAS,CAAC,GACrC,SAAS,CAMX;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,OAAO,IAAI,IAAI,CAAA;CAAE,EAC9D,UAAU,EAAE,CAAC,GAAG,SAAS,EACzB,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,GAC5B,CAAC,GAAG,SAAS,CAwBf;AAED,wBAAgB,qBAAqB,CACnC,SAAS,EACT,WAAW,SAAS,eAAe,CAAC,SAAS,CAAC,EAE9C,UAAU,EAAE,WAAW,GAAG,SAAS,EACnC,gBAAgB,EAAE,CAAC,MAAM,WAAW,CAAC,GAAG,SAAS,GAChD,WAAW,CAMb"}