@forgeroute/ui 0.4.70 → 0.4.72

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeroute/ui",
3
- "version": "0.4.70",
3
+ "version": "0.4.72",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=22.0.0",
6
6
  "@angular/core": ">=22.0.0",
@@ -7,8 +7,11 @@
7
7
  to { opacity: 1; }
8
8
  }
9
9
 
10
- @keyframes rowFadeUp {
11
- from { opacity: 0; transform: translateY(6px); }
10
+ // Entrance of the table header and rows: slides up only, it never fades. The header holds the tabs, search and buttons and the rows hold the
11
+ // cell text, so while they fade in (opacity 0 to 1) an accessibility audit run right after render measures half-transparent text and reports a
12
+ // colour-contrast failure that is gone 300ms later. Moving without changing opacity keeps every pixel of text at full contrast at all times.
13
+ @keyframes slideUp {
14
+ from { transform: translateY(6px); }
12
15
  }
13
16
 
14
17
  @keyframes dotPulse {
@@ -25,13 +28,13 @@
25
28
  // Applied automatically by ui-data-table (rows and cards).
26
29
  // The stagger is capped at 8 steps (240ms) so a 50-row page never starts its last row ~1.5s late.
27
30
  .alive-row {
28
- animation: rowFadeUp .3s ease backwards;
31
+ animation: slideUp .3s ease backwards;
29
32
  animation-delay: calc(min(var(--i, 0), 8) * 30ms);
30
33
  }
31
34
 
32
35
  // ── Page/toolbar header ──
33
36
  .alive-header {
34
- animation: fadeUp .3s ease both;
37
+ animation: slideUp .3s ease both;
35
38
  }
36
39
 
37
40
  // ── Detail page cards ──
@@ -929,6 +929,8 @@ declare function sendDocuSignRequest(http: HttpClient, apiBase: string, body: Do
929
929
  * A failure in step 2 leaves the created request as a draft on the server; the error is surfaced like any send error.
930
930
  */
931
931
  declare function sendDocumentRequest(http: HttpClient, apiBase: string, body: CreateDocumentRequestBody, templateId?: string): Observable<unknown>;
932
+ /** Shown when a bulk send is attempted without the host's send function (a bulk send has no default endpoint). */
933
+ export declare const BULK_NO_SEND_MESSAGE = "Sending to several recipients is not set up for this page, so nothing was sent.";
932
934
  /** What the email form sends — the default `POST <baseUrl>/email-messages` body, or the host's `sendFn` argument. */
933
935
  interface SendEmailPayload {
934
936
  contactName: string;
@@ -936,6 +938,12 @@ interface SendEmailPayload {
936
938
  subject: string;
937
939
  body: string;
938
940
  contactId?: string;
941
+ /**
942
+ * Bulk send only (`ui-send-message-panel` `[recipients]`): the eligible recipients (those with an email address) of ONE send; then `contactName`,
943
+ * `contactEmail` are '' and `contactId` is undefined. The host function sends to every one of them, filling the personal tags
944
+ * ({{first_name}} …, left as written in `subject` / `body`) per recipient. Absent for a single contact.
945
+ */
946
+ recipients?: MessageContact[];
939
947
  scheduledAt?: string;
940
948
  attachments?: {
941
949
  filename: string;
@@ -973,6 +981,14 @@ declare class SendEmailComponent implements OnInit {
973
981
  /** The contact's phone — fills `{{phone}}` (a blank phone makes `{{phone}}` a prompted field). */
974
982
  readonly contactPhone: import("@angular/core").InputSignal<string>;
975
983
  readonly contactId: import("@angular/core").InputSignal<string>;
984
+ /**
985
+ * Bulk mode (set by `ui-send-message-panel` `[recipients]`): the ELIGIBLE recipients of one send. Non-null = bulk: no single address is needed or shown,
986
+ * the personal tags ({{first_name}} …) stay unresolved and do not block Send, `sendFn` is required, and it is called ONCE with `recipients`.
987
+ */
988
+ readonly bulkRecipients: import("@angular/core").InputSignal<MessageContact[] | null>;
989
+ protected readonly bulk: import("@angular/core").Signal<boolean>;
990
+ /** Tag keys that stay in the text as written (the host fills them per recipient in a bulk send). */
991
+ protected get deferredKeys(): readonly string[];
976
992
  /** Prefills Subject in "Create Message" mode (e.g. "Estimate #1042 — Kitchen Remodel"). */
977
993
  readonly defaultSubject: import("@angular/core").InputSignal<string>;
978
994
  /** Prefills Message in "Create Message" mode. */
@@ -1036,6 +1052,13 @@ declare class SendEmailComponent implements OnInit {
1036
1052
  /** false hides the Video section (and its picker, `GET /video-messages`). Default true. */
1037
1053
  readonly showVideo: import("@angular/core").InputSignal<boolean>;
1038
1054
  readonly sent: import("@angular/core").OutputEmitterRef<void>;
1055
+ /** Bulk mode: the value the host's `sendFn` returned and the recipients it was called with, emitted just before `sent`. */
1056
+ readonly bulkResult: import("@angular/core").OutputEmitterRef<{
1057
+ result: unknown;
1058
+ recipients: MessageContact[];
1059
+ }>;
1060
+ /** Bulk mode: the recipients of a send that just started (before the request), `null` when that send failed. Lets the panel pin the send and lock its channel toggle. */
1061
+ readonly bulkSending: import("@angular/core").OutputEmitterRef<MessageContact[] | null>;
1039
1062
  readonly emailAdded: import("@angular/core").OutputEmitterRef<string>;
1040
1063
  readonly validChange: import("@angular/core").OutputEmitterRef<boolean>;
1041
1064
  /** A document send was refused because the document / envelope changed meanwhile (404 / 409): the host refreshes its list. */
@@ -1231,6 +1254,11 @@ declare class SendEmailComponent implements OnInit {
1231
1254
  "required": false;
1232
1255
  "isSignal": true;
1233
1256
  };
1257
+ "bulkRecipients": {
1258
+ "alias": "bulkRecipients";
1259
+ "required": false;
1260
+ "isSignal": true;
1261
+ };
1234
1262
  "defaultSubject": {
1235
1263
  "alias": "defaultSubject";
1236
1264
  "required": false;
@@ -1363,6 +1391,8 @@ declare class SendEmailComponent implements OnInit {
1363
1391
  };
1364
1392
  }, {
1365
1393
  "sent": "sent";
1394
+ "bulkResult": "bulkResult";
1395
+ "bulkSending": "bulkSending";
1366
1396
  "emailAdded": "emailAdded";
1367
1397
  "validChange": "validChange";
1368
1398
  "rejected": "rejected";
@@ -1501,6 +1531,12 @@ interface SendTextPayload {
1501
1531
  contactPhone: string;
1502
1532
  body: string;
1503
1533
  contactId?: string;
1534
+ /**
1535
+ * Bulk send only (`ui-send-message-panel` `[recipients]`): the eligible recipients (phone on file, opted in) of ONE send; then `contactName`,
1536
+ * `contactPhone` are '' and `contactId` is undefined. The host function texts every one of them, filling the personal tags ({{first_name}} …,
1537
+ * left as written in `body`) per recipient. Absent for a single contact.
1538
+ */
1539
+ recipients?: MessageContact[];
1504
1540
  scheduledAt?: string;
1505
1541
  mediaUrl?: string;
1506
1542
  mediaType?: string;
@@ -1524,6 +1560,14 @@ declare class SendTextComponent implements OnInit {
1524
1560
  /** The contact's email — fills `{{email}}` (a blank email makes `{{email}}` a prompted field). */
1525
1561
  readonly contactEmail: import("@angular/core").InputSignal<string>;
1526
1562
  readonly contactId: import("@angular/core").InputSignal<string>;
1563
+ /**
1564
+ * Bulk mode (set by `ui-send-message-panel` `[recipients]`): the ELIGIBLE recipients of one send. Non-null = bulk: no single number is needed or shown,
1565
+ * the personal tags ({{first_name}} …) stay unresolved and do not block Send, `sendFn` is required, and it is called ONCE with `recipients`.
1566
+ */
1567
+ readonly bulkRecipients: import("@angular/core").InputSignal<MessageContact[] | null>;
1568
+ protected readonly bulk: import("@angular/core").Signal<boolean>;
1569
+ /** Tag keys that stay in the text as written (the host fills them per recipient in a bulk send). */
1570
+ protected get deferredKeys(): readonly string[];
1527
1571
  /** Prefills Message in "Create Message" mode. */
1528
1572
  readonly defaultBody: import("@angular/core").InputSignal<string>;
1529
1573
  /** Extra merge variables (e.g. `estimate_number`) used in templates and the preview. */
@@ -1573,6 +1617,13 @@ declare class SendTextComponent implements OnInit {
1573
1617
  */
1574
1618
  readonly videoDefaults: import("@angular/core").InputSignal<VideoDefaults | null>;
1575
1619
  readonly sent: import("@angular/core").OutputEmitterRef<void>;
1620
+ /** Bulk mode: the value the host's `sendFn` returned and the recipients it was called with, emitted just before `sent`. */
1621
+ readonly bulkResult: import("@angular/core").OutputEmitterRef<{
1622
+ result: unknown;
1623
+ recipients: MessageContact[];
1624
+ }>;
1625
+ /** Bulk mode: the recipients of a send that just started (before the request), `null` when that send failed. Lets the panel pin the send and lock its channel toggle. */
1626
+ readonly bulkSending: import("@angular/core").OutputEmitterRef<MessageContact[] | null>;
1576
1627
  readonly phoneAdded: import("@angular/core").OutputEmitterRef<string>;
1577
1628
  readonly validChange: import("@angular/core").OutputEmitterRef<boolean>;
1578
1629
  /** A document send was refused because the document / envelope changed meanwhile (404 / 409): the host refreshes its list. */
@@ -1715,6 +1766,11 @@ declare class SendTextComponent implements OnInit {
1715
1766
  "required": false;
1716
1767
  "isSignal": true;
1717
1768
  };
1769
+ "bulkRecipients": {
1770
+ "alias": "bulkRecipients";
1771
+ "required": false;
1772
+ "isSignal": true;
1773
+ };
1718
1774
  "defaultBody": {
1719
1775
  "alias": "defaultBody";
1720
1776
  "required": false;
@@ -1807,6 +1863,8 @@ declare class SendTextComponent implements OnInit {
1807
1863
  };
1808
1864
  }, {
1809
1865
  "sent": "sent";
1866
+ "bulkResult": "bulkResult";
1867
+ "bulkSending": "bulkSending";
1810
1868
  "phoneAdded": "phoneAdded";
1811
1869
  "validChange": "validChange";
1812
1870
  "rejected": "rejected";
@@ -1825,10 +1883,43 @@ export interface MessageContact {
1825
1883
  phone?: string | null;
1826
1884
  smsOptedIn?: boolean | null;
1827
1885
  }
1886
+ /** What `sentBulk` emits after a bulk send (`[recipients]`). */
1887
+ export interface BulkSentEvent {
1888
+ channel: 'email' | 'text';
1889
+ /** The recipients the host function was called with (the ones that can receive this channel). */
1890
+ recipients: MessageContact[];
1891
+ /** The selected recipients that were left out (no address / not opted in / text not enabled). */
1892
+ skipped: MessageContact[];
1893
+ /** Whatever the host's `sendEmail` / `sendText` observable emitted. */
1894
+ result: unknown;
1895
+ }
1896
+ /** The hint under the bulk summary: personal tags are not resolved by the panel. */
1897
+ export declare const BULK_MERGE_TAG_HINT = "Personal details such as {{first_name}} are filled in for each recipient when sent.";
1828
1898
  export declare class SendMessagePanelComponent {
1829
1899
  readonly open: import("@angular/core").InputSignal<boolean>;
1830
1900
  readonly contact: import("@angular/core").InputSignal<MessageContact | null>;
1831
1901
  readonly loading: import("@angular/core").InputSignal<boolean>;
1902
+ /**
1903
+ * BULK mode: the selected recipients of ONE send. Non-null with at least one entry (one is still bulk) = the panel opens for all of them:
1904
+ * `contact` is ignored, no contact picker / "add an email" form / per-contact notices, the summary shows "To: N <recipientNoun>" and how many
1905
+ * can receive each channel (email = has an email address; text = has a phone, `smsOptedIn === true` and `textEnabled`). Recipients that can't
1906
+ * receive the active channel are skipped and listed. Sending needs the host's `sendEmail` / `sendText` (no default endpoint): called ONCE with
1907
+ * the usual payload plus `recipients` (only the eligible ones; `contactName` / `contactEmail` / `contactPhone` are ''). Personal tags such as
1908
+ * `{{first_name}}` are NOT resolved by the panel (preview shows them as written, Send is not blocked): the host / server fills them per
1909
+ * recipient. The Documents section, the job picker and a single-recipient `documentEnvelope` are off in bulk mode. Outputs: `sent` then `sentBulk`.
1910
+ * Duplicate `id`s are dropped (first wins). `recipientMode="in-app"` is ignored in bulk mode. null (default) or an empty list = the single-contact panel exactly as before.
1911
+ */
1912
+ readonly recipients: import("@angular/core").InputSignal<MessageContact[] | null>;
1913
+ /**
1914
+ * Bulk mode: the channel to start on when the panel opens (a host with separate "Email" and "Text" bulk buttons passes the clicked one). Used only if
1915
+ * somebody can receive that channel; otherwise the usual default applies (email if anybody has an email, else text). null (default) = the usual default.
1916
+ * `textEnabled` must already be known when the panel opens for `'text'` to take effect (bind `[recipients]` after async settings loaded, or re-open).
1917
+ */
1918
+ readonly initialChannel: import("@angular/core").InputSignal<"email" | "text" | null>;
1919
+ /** Bulk mode: the plural noun in "To: 12 residents" (default `recipients`). */
1920
+ readonly recipientNoun: import("@angular/core").InputSignal<string>;
1921
+ /** Bulk mode: the singular noun used when exactly one is selected; empty = the plural without a trailing "s". */
1922
+ readonly recipientNounSingular: import("@angular/core").InputSignal<string>;
1832
1923
  /** Pass true when Twilio / SMS is enabled in the consuming app's settings. */
1833
1924
  readonly textEnabled: import("@angular/core").InputSignal<boolean>;
1834
1925
  /** Panel header. */
@@ -1880,7 +1971,10 @@ export declare class SendMessagePanelComponent {
1880
1971
  readonly showTemplates: import("@angular/core").InputSignal<boolean>;
1881
1972
  /** false hides the Video section (picker, `GET /video-messages`) in the email and text forms. Default true. */
1882
1973
  readonly showVideo: import("@angular/core").InputSignal<boolean>;
1974
+ /** In-app mode is for one contact: it is ignored in bulk mode. */
1883
1975
  protected readonly inApp: import("@angular/core").Signal<boolean>;
1976
+ protected readonly BULK_NO_SEND_MESSAGE = "Sending to several recipients is not set up for this page, so nothing was sent.";
1977
+ protected readonly BULK_MERGE_TAG_HINT = "Personal details such as {{first_name}} are filled in for each recipient when sent.";
1884
1978
  /** false hides Attach Files / Attach Image and "Schedule for later" in both forms (a host `sendEmail` / `sendText` that can't deliver them). */
1885
1979
  readonly attachmentsAndScheduling: import("@angular/core").InputSignal<boolean>;
1886
1980
  /** Purposes the app's backend supports for documents (default all three); `['ESIGN']` hides the purpose select. */
@@ -1949,6 +2043,43 @@ export declare class SendMessagePanelComponent {
1949
2043
  readonly videoDefaults: import("@angular/core").InputSignal<VideoDefaults | null>;
1950
2044
  /** The Documents section is shown only when the host turned it on AND the user may send documents. */
1951
2045
  protected readonly documentsOn: import("@angular/core").Signal<boolean>;
2046
+ /** `recipients` without duplicates (same `id`: the first wins); every count, payload and `sentBulk` uses this list. */
2047
+ readonly bulkList: import("@angular/core").Signal<MessageContact[]>;
2048
+ /** True when `recipients` holds at least one entry (the single-contact panel otherwise). */
2049
+ readonly bulkMode: import("@angular/core").Signal<boolean>;
2050
+ /** Recipients that can get the email: have a non-blank email address (the format is not checked). */
2051
+ readonly emailRecipients: import("@angular/core").Signal<MessageContact[]>;
2052
+ /** Recipients that can get the text: text enabled, a phone on file and `smsOptedIn === true` (unknown consent is NOT texted, like the single-contact panel). */
2053
+ readonly textRecipients: import("@angular/core").Signal<MessageContact[]>;
2054
+ private readonly activeRecipients;
2055
+ /** The selected recipients the active channel leaves out. */
2056
+ readonly skippedRecipients: import("@angular/core").Signal<MessageContact[]>;
2057
+ readonly bulkNoneEligible: import("@angular/core").Signal<boolean>;
2058
+ /** A bulk send has no default endpoint: the host's function for the active channel is required. */
2059
+ readonly bulkMissingSend: import("@angular/core").Signal<boolean>;
2060
+ readonly bulkBlocked: import("@angular/core").Signal<boolean>;
2061
+ protected readonly hasTarget: import("@angular/core").Signal<boolean>;
2062
+ /** True from the moment a bulk send starts until it succeeds or fails: the channel is locked so the form is not destroyed mid-request. */
2063
+ readonly sending: import("@angular/core").WritableSignal<boolean>;
2064
+ private submitSnapshot;
2065
+ protected readonly recipientWord: import("@angular/core").Signal<string>;
2066
+ private skippedReason;
2067
+ private skippedNames;
2068
+ readonly skippedText: import("@angular/core").Signal<string>;
2069
+ readonly noneEligibleText: import("@angular/core").Signal<string>;
2070
+ /** The one live-region sentence (the alerts are quiet): how many are selected, who can get the active channel and who is skipped. */
2071
+ readonly bulkAnnouncement: import("@angular/core").Signal<string>;
2072
+ /** The eligible recipients handed to the form of a channel (null outside bulk mode). */
2073
+ bulkRecipientsFor(ch: 'email' | 'text'): MessageContact[] | null;
2074
+ /** A bulk send started (the form's recipients) or failed (`null`): pins who was selected / sent to at that moment and locks the channel. */
2075
+ onBulkSending(sent: MessageContact[] | null): void;
2076
+ /** Forgets a bulk send in flight (no `sentBulk` follows): unlocks the channel and drops the snapshot and any stale result. */
2077
+ private resetBulkSend;
2078
+ onBulkResult(e: {
2079
+ result: unknown;
2080
+ recipients: MessageContact[];
2081
+ }): void;
2082
+ private lastBulkResult;
1952
2083
  /**
1953
2084
  * Shows an optional "Job" search under the contact (off by default): the sender may link the message to one of the
1954
2085
  * company's jobs (found with the host's `jobSearch` function). The pick is reported through `jobSelected`; it is cleared whenever the
@@ -1969,6 +2100,8 @@ export declare class SendMessagePanelComponent {
1969
2100
  } | null>;
1970
2101
  readonly closed: import("@angular/core").OutputEmitterRef<void>;
1971
2102
  readonly sent: import("@angular/core").OutputEmitterRef<void>;
2103
+ /** Bulk mode: emitted right after `sent` with what was sent, who was skipped and the host function's result. */
2104
+ readonly sentBulk: import("@angular/core").OutputEmitterRef<BulkSentEvent>;
1972
2105
  readonly emailAdded: import("@angular/core").OutputEmitterRef<string>;
1973
2106
  /** A document / envelope send was refused because it changed meanwhile (404 / 409): the host refreshes its list. */
1974
2107
  readonly rejected: import("@angular/core").OutputEmitterRef<void>;
@@ -2027,6 +2160,26 @@ export declare class SendMessagePanelComponent {
2027
2160
  "required": false;
2028
2161
  "isSignal": true;
2029
2162
  };
2163
+ "recipients": {
2164
+ "alias": "recipients";
2165
+ "required": false;
2166
+ "isSignal": true;
2167
+ };
2168
+ "initialChannel": {
2169
+ "alias": "initialChannel";
2170
+ "required": false;
2171
+ "isSignal": true;
2172
+ };
2173
+ "recipientNoun": {
2174
+ "alias": "recipientNoun";
2175
+ "required": false;
2176
+ "isSignal": true;
2177
+ };
2178
+ "recipientNounSingular": {
2179
+ "alias": "recipientNounSingular";
2180
+ "required": false;
2181
+ "isSignal": true;
2182
+ };
2030
2183
  "textEnabled": {
2031
2184
  "alias": "textEnabled";
2032
2185
  "required": false;
@@ -2241,12 +2394,49 @@ export declare class SendMessagePanelComponent {
2241
2394
  "jobSelected": "jobSelected";
2242
2395
  "closed": "closed";
2243
2396
  "sent": "sent";
2397
+ "sentBulk": "sentBulk";
2244
2398
  "emailAdded": "emailAdded";
2245
2399
  "rejected": "rejected";
2246
2400
  "mapFieldsClick": "mapFieldsClick";
2247
2401
  "phoneAdded": "phoneAdded";
2248
2402
  }, never, never, true, never>;
2249
2403
  }
2404
+ /**
2405
+ * Merge-tag helpers shared by the email and text forms, so the Preview pane, the "View Send Preview" modal and the
2406
+ * sent payload all come from the same values.
2407
+ *
2408
+ * Rules:
2409
+ * - A tag is `{{key}}` (inner spaces allowed: `{{ key }}`); `key` is letters, digits, `_` and `.`.
2410
+ * - A value counts only when it is non-blank: a blank contact field, market value or `context` entry never
2411
+ * resolves a tag — the form asks for it instead.
2412
+ * - Anything still shaped like `{{…}}` after merging blocks sending (the server answers such a body with a 400).
2413
+ */
2414
+ /** Every distinct tag key in `text`, in order of first appearance. */
2415
+ declare function mergeTagKeys(text: string): string[];
2416
+ /** Replaces each tag that has a non-blank value in `values`; other tags are left as written. */
2417
+ declare function applyMergeTags(text: string, values: Record<string, string>): string;
2418
+ /**
2419
+ * Tags that describe ONE recipient. In a bulk send (several recipients) the form cannot fill them: they stay in the text as written, are
2420
+ * never prompted for and do not block Send; the host / server fills them per recipient.
2421
+ */
2422
+ export declare const PERSONAL_TAG_KEYS: readonly string[];
2423
+ /**
2424
+ * True while `text` still contains anything shaped like `{{…}}`. Tags whose key is in `ignoreKeys` (a bulk send's per-recipient tags) do not count.
2425
+ */
2426
+ declare function hasLeftoverTags(text: string, ignoreKeys?: readonly string[]): boolean;
2427
+ /** Copies only the non-blank entries of each source, later sources winning. */
2428
+ declare function nonBlankValues(...sources: Record<string, unknown>[]): Record<string, string>;
2429
+ /** The current date variables (`month`, `year`, `date`, `today`). */
2430
+ declare function dateVars(now?: Date): Record<string, string>;
2431
+ /**
2432
+ * Market variables from the `/service-areas` page: the primary area (or the first one). Only values the server
2433
+ * actually returned are kept — with no areas (or a blank value) the tag stays a prompted field.
2434
+ */
2435
+ declare function marketVarsFrom(resp: {
2436
+ content?: any[];
2437
+ } | null | undefined): Record<string, string>;
2438
+ /** Turns a key like `job_name` into the missing-field label "Job Name". */
2439
+ declare function formatVarLabel(key: string): string;
2250
2440
  /**
2251
2441
  * ui-document-signers — the signer section of a document send: one required contact per customer signer role of the chosen document
2252
2442
  * (Signer 1 is the recipient, shown read-only) in signing order, and the one read-only countersign line.
@@ -1,6 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
- import { DestroyRef, InjectionToken, Provider, WritableSignal } from "@angular/core";
2
+ import { DestroyRef, InjectionToken, Provider, TemplateRef, WritableSignal } from "@angular/core";
3
3
  import { Observable } from "rxjs";
4
+ import { HostResult, PaymentAllocation, PaymentMethodBodyContext, PaymentMethodPlugin, PaymentPluginMethod } from "@forgeroute/ui";
4
5
  /**
5
6
  * The small part of Stripe.js the library calls, written as plain structural types so the library has NO compile-time or run-time
6
7
  * dependency on `@stripe/stripe-js` outside of the lazy loader (`stripe-loader.ts`). The real `Stripe` object satisfies these; so does a hand-written fake.
@@ -1410,4 +1411,162 @@ export declare class StripeConnectComponent {
1410
1411
  "loading": "loadingChange";
1411
1412
  }, never, never, true, never>;
1412
1413
  }
1413
- export type { HandleReturnOptions, MicrodepositStartedBy, MicrodepositStepsContext, MicrodepositType, MicrodepositVerifyData, MicrodepositVerifyEvent, ProvideStripePaymentOptions, StripeA11yMessages, StripeAppearance, StripeBillingDetails, StripeConfirmResult, StripeConfirmedEvent, StripeConnectAccount, StripeConnectFlowOptions, StripeConnectOutcome, StripeConnectState, StripeConnectStatus, StripeConnection, StripeCreateIntentFn, StripeDeferredIntent, StripeElementChange, StripeElementsLike, StripeErrorDisplay, StripeErrorLike, StripeErrorMessageOptions, StripeFailure, StripeFailureStage, StripeIntentKind, StripeIntentLike, StripeLike, StripeLikeWithBankSetup, StripeLoadOptions, StripeLoaderFn, StripeMicrodepositNextAction, StripeMicrodepositResult, StripeOutcome, StripePaymentDefaults, StripePaymentElementLike, StripeReadyEvent, StripeReturnParams, StripeReturnResult, StripeVerification };
1414
+ /** What the host's `createIntent` receives: the entered payment and how it is applied to charges. */
1415
+ interface StripeIntentContext extends PaymentAllocation {
1416
+ /** The amount the payer owes (before processing fees), in major units (12.5 = $12.50). */
1417
+ amount: number;
1418
+ /** The method value that was selected (`'CARD'` / `'ACH'`). */
1419
+ paymentMethod: string;
1420
+ /** Stripe's name of it: `'card'` or `'us_bank_account'`. */
1421
+ paymentMethodType: 'card' | 'us_bank_account';
1422
+ receivedDate: string;
1423
+ notes: string | null;
1424
+ }
1425
+ /** What the host's `createIntent` answers. Fees are optional; when given they are shown as rows under the form. */
1426
+ interface StripeIntentInfo {
1427
+ clientSecret: string;
1428
+ paymentIntentId: string;
1429
+ stripeFee?: number;
1430
+ platformFee?: number;
1431
+ totalAmount?: number;
1432
+ /** Connect account of a direct charge (passed back to `finalize` / `cancel`). `[stripeAccount]` of the plugin must already be this account: Stripe.js is loaded for it before the form shows. */
1433
+ stripeAccountId?: string | null;
1434
+ /** Shown after "Processing fee" / "Platform fee", e.g. `'2.9% + $0.30'`. */
1435
+ stripeFeeLabel?: string;
1436
+ platformFeeLabel?: string;
1437
+ }
1438
+ /** The fees of a payment, shown before the payer pays (the host's `estimateFees`) or after the intent exists. */
1439
+ interface StripeFees {
1440
+ stripeFee?: number;
1441
+ platformFee?: number;
1442
+ /** What the payer is charged (amount + fees). The Payment Element is created with this amount, so it MUST equal the intent's amount. */
1443
+ totalAmount?: number;
1444
+ stripeFeeLabel?: string;
1445
+ platformFeeLabel?: string;
1446
+ }
1447
+ interface StripeFeeContext {
1448
+ amount: number;
1449
+ paymentMethod: string;
1450
+ paymentMethodType: 'card' | 'us_bank_account';
1451
+ }
1452
+ export declare const STRIPE_PAYMENT_METHODS: readonly [{
1453
+ readonly value: "CARD";
1454
+ readonly label: "Card";
1455
+ }, {
1456
+ readonly value: "ACH";
1457
+ readonly label: "ACH / Bank Transfer";
1458
+ }];
1459
+ declare const stripeMethodType: (method: string) => "card" | "us_bank_account";
1460
+ export declare const STRIPE_METHOD_TEXTS: {
1461
+ readonly amountNeeded: "Enter an amount to continue.";
1462
+ readonly notCompleted: "Payment was not completed. Please try again or choose another method.";
1463
+ readonly achTitle: "ACH Payment Submitted";
1464
+ readonly achMessage: (amount: string) => string;
1465
+ readonly verificationTitle: "Bank Verification Needed";
1466
+ readonly verificationMessage: (amount: string) => string;
1467
+ readonly receiptNote: "A receipt is sent once the payment has been fully processed and settled.";
1468
+ };
1469
+ /**
1470
+ * Adds LIVE Card and ACH (Stripe) to `ui-payment-side-panel`. Put it inside the panel only for customers who use Stripe: its presence replaces the
1471
+ * record-only Card / ACH (last 4 digits) with the Payment Element; remove it and the panel is record-only again.
1472
+ *
1473
+ * <ui-payment-side-panel ...>
1474
+ * @if (stripeEnabled()) {
1475
+ * <ui-payment-stripe-method [publishableKey]="key" [createIntent]="createIntent" [finalize]="finalize" [cancel]="cancel" />
1476
+ * }
1477
+ * </ui-payment-side-panel>
1478
+ *
1479
+ * The host owns the HTTP: `createIntent(ctx)` (POST create-payment-intent) answers `{ clientSecret, paymentIntentId, fees... }`, `finalize(info)` (POST
1480
+ * process-payment) runs after Stripe took the payment, `cancel(info)` cancels an abandoned intent. Outcomes arrive as the panel's `outcome` events with
1481
+ * kind `'plugin'` and codes `paid`, `processing`, `verification`, `finalize-failed` and `failed`.
1482
+ */
1483
+ export declare class StripePaymentMethodComponent extends PaymentMethodPlugin {
1484
+ protected readonly self: this;
1485
+ /** Creates the PaymentIntent on the host's backend (called when the payer presses Pay). */
1486
+ readonly createIntent: import("@angular/core").InputSignal<((ctx: StripeIntentContext) => HostResult<StripeIntentInfo>) | null>;
1487
+ /** Records the payment after Stripe accepted it (succeeded, processing or needs bank verification). A failure here is reported, the payment still counts as taken. */
1488
+ readonly finalize: import("@angular/core").InputSignal<((info: StripeIntentInfo) => HostResult<unknown>) | null>;
1489
+ /** Cancels an intent that was created but not paid (panel closed, another method, a retry). */
1490
+ readonly cancel: import("@angular/core").InputSignal<((info: StripeIntentInfo) => HostResult<unknown>) | null>;
1491
+ /** Optional fee preview before paying; its `totalAmount` becomes the amount of the Payment Element (it must equal the intent's amount). */
1492
+ readonly estimateFees: import("@angular/core").InputSignal<((ctx: StripeFeeContext) => HostResult<StripeFees>) | null>;
1493
+ readonly publishableKey: import("@angular/core").InputSignal<string | null>;
1494
+ /** Connect account for direct charges; Stripe.js is loaded for it. */
1495
+ readonly stripeAccount: import("@angular/core").InputSignal<string | null>;
1496
+ readonly billingDetails: import("@angular/core").InputSignal<StripeBillingDetails | null>;
1497
+ readonly appearance: import("@angular/core").InputSignal<Record<string, unknown> | null>;
1498
+ /** The methods this plugin takes over (default: Card and ACH / Bank Transfer). Pass `[{ value: 'CARD', label: 'Card' }]` for cards only. */
1499
+ readonly methodList: import("@angular/core").InputSignal<readonly PaymentPluginMethod[]>;
1500
+ /** How long a fee estimate may take before Pay is released and "Try again" is offered (ms). */
1501
+ readonly estimateTimeoutMs: import("@angular/core").InputSignal<number>;
1502
+ /** The payer covers fees, so the intent amount is payment + fees: a rejected or empty estimate keeps Pay off (with "Try again") instead of falling back to the base amount, which Stripe would reject as a mismatch. */
1503
+ readonly feesRequired: import("@angular/core").InputSignal<boolean>;
1504
+ readonly receiptNote: import("@angular/core").InputSignal<string>;
1505
+ private readonly body;
1506
+ readonly methods: import("@angular/core").InputSignal<readonly PaymentPluginMethod[]>;
1507
+ readonly template: import("@angular/core").Signal<TemplateRef<PaymentMethodBodyContext> | undefined>;
1508
+ static ɵfac: i0.ɵɵFactoryDeclaration<StripePaymentMethodComponent, never>;
1509
+ static ɵcmp: i0.ɵɵComponentDeclaration<StripePaymentMethodComponent, "ui-payment-stripe-method", never, {
1510
+ "createIntent": {
1511
+ "alias": "createIntent";
1512
+ "required": false;
1513
+ "isSignal": true;
1514
+ };
1515
+ "finalize": {
1516
+ "alias": "finalize";
1517
+ "required": false;
1518
+ "isSignal": true;
1519
+ };
1520
+ "cancel": {
1521
+ "alias": "cancel";
1522
+ "required": false;
1523
+ "isSignal": true;
1524
+ };
1525
+ "estimateFees": {
1526
+ "alias": "estimateFees";
1527
+ "required": false;
1528
+ "isSignal": true;
1529
+ };
1530
+ "publishableKey": {
1531
+ "alias": "publishableKey";
1532
+ "required": false;
1533
+ "isSignal": true;
1534
+ };
1535
+ "stripeAccount": {
1536
+ "alias": "stripeAccount";
1537
+ "required": false;
1538
+ "isSignal": true;
1539
+ };
1540
+ "billingDetails": {
1541
+ "alias": "billingDetails";
1542
+ "required": false;
1543
+ "isSignal": true;
1544
+ };
1545
+ "appearance": {
1546
+ "alias": "appearance";
1547
+ "required": false;
1548
+ "isSignal": true;
1549
+ };
1550
+ "methodList": {
1551
+ "alias": "methodList";
1552
+ "required": false;
1553
+ "isSignal": true;
1554
+ };
1555
+ "estimateTimeoutMs": {
1556
+ "alias": "estimateTimeoutMs";
1557
+ "required": false;
1558
+ "isSignal": true;
1559
+ };
1560
+ "feesRequired": {
1561
+ "alias": "feesRequired";
1562
+ "required": false;
1563
+ "isSignal": true;
1564
+ };
1565
+ "receiptNote": {
1566
+ "alias": "receiptNote";
1567
+ "required": false;
1568
+ "isSignal": true;
1569
+ };
1570
+ }, {}, never, never, true, never>;
1571
+ }
1572
+ export type { HandleReturnOptions, MicrodepositStartedBy, MicrodepositStepsContext, MicrodepositType, MicrodepositVerifyData, MicrodepositVerifyEvent, ProvideStripePaymentOptions, StripeA11yMessages, StripeAppearance, StripeBillingDetails, StripeConfirmResult, StripeConfirmedEvent, StripeConnectAccount, StripeConnectFlowOptions, StripeConnectOutcome, StripeConnectState, StripeConnectStatus, StripeConnection, StripeCreateIntentFn, StripeDeferredIntent, StripeElementChange, StripeElementsLike, StripeErrorDisplay, StripeErrorLike, StripeErrorMessageOptions, StripeFailure, StripeFailureStage, StripeFeeContext, StripeFees, StripeIntentContext, StripeIntentInfo, StripeIntentKind, StripeIntentLike, StripeLike, StripeLikeWithBankSetup, StripeLoadOptions, StripeLoaderFn, StripeMicrodepositNextAction, StripeMicrodepositResult, StripeOutcome, StripePaymentDefaults, StripePaymentElementLike, StripeReadyEvent, StripeReturnParams, StripeReturnResult, StripeVerification };