@forgeroute/ui 0.4.38 → 0.4.40

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.38",
3
+ "version": "0.4.40",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=22.0.0",
6
6
  "@angular/core": ">=22.0.0",
@@ -115,7 +115,7 @@ export declare class TemplatePickerComponent {
115
115
  private readonly host;
116
116
  private readonly search$;
117
117
  /** Filter results to a specific channel: 'email', 'text', or omit for all. */
118
- readonly channel: import("@angular/core").InputSignal<"" | "text" | "email">;
118
+ readonly channel: import("@angular/core").InputSignal<"" | "email" | "text">;
119
119
  readonly selectedId: import("@angular/core").InputSignal<string | null>;
120
120
  readonly label: import("@angular/core").InputSignal<string>;
121
121
  /** Shows the red required asterisk (same as the library inputs) — e.g. when a template must be picked to send. */
@@ -543,7 +543,7 @@ export declare class DocumentSendOptionsComponent implements UiFieldValidity {
543
543
  /** API root from FORGEROUTE_CONFIG (e.g. a cross-origin prod API); falls back to same-origin `/api`. */
544
544
  private readonly apiBase;
545
545
  /** `email` → purpose select + attachment allowed; `text` → link only (purpose fixed to e-signature). */
546
- readonly channel: import("@angular/core").InputSignal<"text" | "email">;
546
+ readonly channel: import("@angular/core").InputSignal<"email" | "text">;
547
547
  readonly label: import("@angular/core").InputSignal<string>;
548
548
  /** When true, at least one document must be chosen. The message forms leave it false: documents are optional there. */
549
549
  readonly required: import("@angular/core").InputSignal<boolean>;
@@ -608,7 +608,7 @@ export declare class DocumentSendOptionsComponent implements UiFieldValidity {
608
608
  /** Roles of documents whose row had no `signerRoles` and that the host resolves (`signers().resolveRoles`). */
609
609
  readonly roleStates: import("@angular/core").WritableSignal<Record<string, RoleState>>;
610
610
  /** The envelope's documents: `loading` / `error` while the panel reads them (`loadDocuments`), `ready` once known. */
611
- readonly envelopeStatus: import("@angular/core").WritableSignal<"error" | "loading" | "ready" | "idle">;
611
+ readonly envelopeStatus: import("@angular/core").WritableSignal<"idle" | "loading" | "ready" | "error">;
612
612
  private envelopeSub;
613
613
  private readonly roleSubs;
614
614
  /** The sender opened the collapsed "Signing options" line. */
@@ -1813,7 +1813,7 @@ export declare class SendMessagePanelComponent {
1813
1813
  readonly activeContact: import("@angular/core").WritableSignal<MessageContact | null>;
1814
1814
  /** What the sender should know about the picked contact before sending (why a channel is missing, what to add). */
1815
1815
  readonly contactNotices: import("@angular/core").Signal<string[]>;
1816
- readonly channel: import("@angular/core").WritableSignal<"text" | "email">;
1816
+ readonly channel: import("@angular/core").WritableSignal<"email" | "text">;
1817
1817
  readonly valid: import("@angular/core").WritableSignal<boolean>;
1818
1818
  /** Bumped on every fresh open / new contact — re-creates the email and text forms so no draft carries over. */
1819
1819
  readonly formEpoch: import("@angular/core").WritableSignal<number>;
@@ -1141,4 +1141,114 @@ export declare class MicrodepositVerifyComponent {
1141
1141
  "verifyFailed": "verifyFailed";
1142
1142
  }, never, never, true, never>;
1143
1143
  }
1144
+ /**
1145
+ * "Connect your Stripe account": the one card every app shows where a business connects the Stripe account it collects payments into (Stripe Connect). It reads
1146
+ * the account's state from the app's backend, and Connect asks the backend for the address of Stripe's onboarding page and sends the browser there: the user
1147
+ * finishes setting up the account on Stripe and Stripe sends them back to the address the backend chose. An unfinished onboarding simply shows Connect again
1148
+ * (the backend resumes the same account); a finished one shows Connected with Reconnect.
1149
+ *
1150
+ * <ui-stripe-connect [connectParams]="{ returnTo: 'setup' }" (statusChange)="onConnected($event)" />
1151
+ *
1152
+ * The backend follows the convention of the setup wizard's `stripe` preset: `GET /integrations/stripe/status` answers `{ connected }` and
1153
+ * `GET /integrations/stripe/connect` answers `{ url }` (`authUrl` / `onboardingUrl` are read too). Both addresses and the texts can be replaced. The calls go
1154
+ * through `FORGEROUTE_CONFIG.baseUrl` and the app's HTTP interceptors (its sign-in token).
1155
+ */
1156
+ export declare class StripeConnectComponent {
1157
+ private readonly http;
1158
+ private readonly destroyRef;
1159
+ private readonly apiBase;
1160
+ /** GET: answers `{ connected }`. Relative to `FORGEROUTE_CONFIG.baseUrl`. */
1161
+ readonly statusEndpoint: import("@angular/core").InputSignal<string>;
1162
+ /** GET: answers the address of Stripe's onboarding page (`{ url }`). Relative to `FORGEROUTE_CONFIG.baseUrl`. */
1163
+ readonly connectEndpoint: import("@angular/core").InputSignal<string>;
1164
+ /** Query parameters sent with the connect request (for example `{ returnTo: 'setup' }`: where Stripe sends the user back to). */
1165
+ readonly connectParams: import("@angular/core").InputSignal<Record<string, string> | null>;
1166
+ readonly title: import("@angular/core").InputSignal<string>;
1167
+ readonly description: import("@angular/core").InputSignal<string>;
1168
+ readonly connectedText: import("@angular/core").InputSignal<string>;
1169
+ readonly connectLabel: import("@angular/core").InputSignal<string>;
1170
+ readonly reconnectLabel: import("@angular/core").InputSignal<string>;
1171
+ /** What the user sees when the status or the address could not be loaded and the server gave no message. */
1172
+ readonly loadFailedText: import("@angular/core").InputSignal<string>;
1173
+ readonly connectFailedText: import("@angular/core").InputSignal<string>;
1174
+ readonly disabled: import("@angular/core").InputSignal<boolean>;
1175
+ /** The account's state changed (it was read, or read again): true = connected. */
1176
+ readonly statusChange: import("@angular/core").OutputEmitterRef<boolean>;
1177
+ protected readonly connected: import("@angular/core").WritableSignal<boolean>;
1178
+ protected readonly statusKnown: import("@angular/core").WritableSignal<boolean>;
1179
+ protected readonly connecting: import("@angular/core").WritableSignal<boolean>;
1180
+ protected readonly error: import("@angular/core").WritableSignal<string>;
1181
+ protected readonly busy: import("@angular/core").Signal<boolean>;
1182
+ private started;
1183
+ constructor();
1184
+ /** Reads the account's state again (after the user came back from Stripe, for example). */
1185
+ refresh(): void;
1186
+ /** Asks the backend for Stripe's onboarding address and sends the browser there. */
1187
+ protected connect(): void;
1188
+ /** Sends the browser to Stripe. */
1189
+ protected redirect(url: string): void;
1190
+ /** Only an http(s) address is ever opened (never `javascript:` or `data:` from a response). */
1191
+ private isWebAddress;
1192
+ private messageOf;
1193
+ static ɵfac: i0.ɵɵFactoryDeclaration<StripeConnectComponent, never>;
1194
+ static ɵcmp: i0.ɵɵComponentDeclaration<StripeConnectComponent, "ui-stripe-connect", never, {
1195
+ "statusEndpoint": {
1196
+ "alias": "statusEndpoint";
1197
+ "required": false;
1198
+ "isSignal": true;
1199
+ };
1200
+ "connectEndpoint": {
1201
+ "alias": "connectEndpoint";
1202
+ "required": false;
1203
+ "isSignal": true;
1204
+ };
1205
+ "connectParams": {
1206
+ "alias": "connectParams";
1207
+ "required": false;
1208
+ "isSignal": true;
1209
+ };
1210
+ "title": {
1211
+ "alias": "title";
1212
+ "required": false;
1213
+ "isSignal": true;
1214
+ };
1215
+ "description": {
1216
+ "alias": "description";
1217
+ "required": false;
1218
+ "isSignal": true;
1219
+ };
1220
+ "connectedText": {
1221
+ "alias": "connectedText";
1222
+ "required": false;
1223
+ "isSignal": true;
1224
+ };
1225
+ "connectLabel": {
1226
+ "alias": "connectLabel";
1227
+ "required": false;
1228
+ "isSignal": true;
1229
+ };
1230
+ "reconnectLabel": {
1231
+ "alias": "reconnectLabel";
1232
+ "required": false;
1233
+ "isSignal": true;
1234
+ };
1235
+ "loadFailedText": {
1236
+ "alias": "loadFailedText";
1237
+ "required": false;
1238
+ "isSignal": true;
1239
+ };
1240
+ "connectFailedText": {
1241
+ "alias": "connectFailedText";
1242
+ "required": false;
1243
+ "isSignal": true;
1244
+ };
1245
+ "disabled": {
1246
+ "alias": "disabled";
1247
+ "required": false;
1248
+ "isSignal": true;
1249
+ };
1250
+ }, {
1251
+ "statusChange": "statusChange";
1252
+ }, never, never, true, never>;
1253
+ }
1144
1254
  export type { HandleReturnOptions, MicrodepositStartedBy, MicrodepositStepsContext, MicrodepositType, MicrodepositVerifyData, MicrodepositVerifyEvent, ProvideStripePaymentOptions, StripeA11yMessages, StripeAppearance, StripeBillingDetails, StripeConfirmResult, StripeConfirmedEvent, 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 };
@@ -298,6 +298,8 @@ export type BulkUpdateOption<T = any> = string | {
298
298
  * It is also the inline select's accessible name, the default confirm's title and confirm button, and the default confirm message reads
299
299
  * "<label>: <option> for N items?" (instead of "Change <column> to <option> for N items?"). Host `confirm` copy still wins; its
300
300
  * `ctx.columnLabel` stays the column's label. Trimmed; blank = ignored. Ignored for the reserved `delete` key. Omit it and nothing changes.
301
+ * Direct actions (`action: true`): the bar button text, default confirm title and confirm button (required in practice; trimmed,
302
+ * blank = the key split on `_`, `-` and camelCase with only the first word capitalised: 'resend' -> 'Resend', 'send_reminder' / 'sendReminder' -> 'Send reminder'); `ctx.columnLabel` is this label.
301
303
  *
302
304
  * { field: 'state', label: 'Archive / Restore' }
303
305
  */
@@ -330,8 +332,32 @@ export type BulkUpdateOption<T = any> = string | {
330
332
  * { field: 'status', allowedFrom: { SENT: ['DRAFT'] }, canChange: (row, target) => target !== 'SENT' || canSend(row) }
331
333
  */
332
334
  canChange?: (row: T, target: string) => boolean;
335
+ /**
336
+ * Direct bulk action (`{ field: 'resend', action: true, label: 'Resend' }`): a bulk-bar button that is neither a field change nor
337
+ * Delete. `field` is the action key (no column needed), the button type is `action-<field>` and there is no inline select.
338
+ * Clicking it opens the library confirm, then POSTs `<endpoint>/bulk/<field>` with exactly `{ ids }` (the eligible rows). The response is
339
+ * handled like a field change (`updated`, `skipped`, `notAllowed`, readable `message`). Ignored for the reserved `delete` key.
340
+ * Read-only users get no bulk bar, so nothing changes for them. Omit it and nothing changes.
341
+ * `confirm` on an action takes only the function form; a map (target value -> function) is ignored and the default copy is used.
342
+ *
343
+ * { field: 'resend', action: true, label: 'Resend', eligible: (row: Request) => row.status === 'PENDING' }
344
+ */
345
+ action?: boolean;
346
+ /** Direct action only: button style, default 'secondary'. 'danger' also makes the default confirm button red. */
347
+ variant?: 'secondary' | 'danger';
348
+ /**
349
+ * Direct action only: true when the server will accept the action for this row (keep it identical to the backend rule). Rows it
350
+ * refuses are skipped (counted, NEVER sent; "N selected rows don't qualify for <label lower-cased> and will be skipped."). When no selected loaded row passes,
351
+ * the button is disabled with a visible reason ("None of the selected rows qualify for <label lower-cased>."). Omit it and every selected row is sent. A predicate that throws counts as "row not eligible" (never breaks rendering).
352
+ */
353
+ eligible?: (row: T) => boolean;
354
+ /**
355
+ * Direct action only: the past-tense word in the result toast. "N <doneLabel>, M skipped." when anything was skipped / not allowed,
356
+ * else "N <itemLabel>(s) <doneLabel>.". Default 'updated'.
357
+ */
358
+ doneLabel?: string;
333
359
  };
334
- /** What a bulk confirm function receives. For bulk Delete: `targetValue`/`targetLabel` are '', `columnLabel` is 'Delete', skipped counts are 0. */
360
+ /** What a bulk confirm function receives. For a direct bulk action: `columnLabel` is the action label, `targetValue`/`targetLabel` are '', `rows`/`skippedRows` the eligible / refused selected rows. For bulk Delete: `targetValue`/`targetLabel` are '', `columnLabel` is 'Delete', skipped counts are 0. */
335
361
  export interface BulkConfirmContext<T = any> {
336
362
  /** Rows that WILL be sent (eligible). For Delete: the selected count. */
337
363
  count: number;
@@ -391,6 +417,12 @@ export interface BulkActionDef {
391
417
  deletable?: (row: any) => boolean;
392
418
  /** Field changes: per-row check that the server will accept the move to a target (from `{ field, canChange }`). */
393
419
  canChange?: (row: any, target: string) => boolean;
420
+ /** True for a direct bulk action (from `{ field, action: true, label }`); `type` is `action-<key>` and `field` is not set (no inline select). */
421
+ action?: boolean;
422
+ /** Direct action: which rows the server will accept (from `{ field, action: true, eligible }`). */
423
+ eligible?: (row: any) => boolean;
424
+ /** Direct action: past-tense word in the result toast (from `{ doneLabel }`), default 'updated'. */
425
+ doneLabel?: string;
394
426
  /**
395
427
  * The item field this action targets (e.g. 'status', 'type').
396
428
  * When set the table shows an inline select populated from the column's option sources.
@@ -1210,6 +1242,10 @@ export declare class ImportExportComponent {
1210
1242
  private readonly apiBase;
1211
1243
  private readonly toast;
1212
1244
  private readonly cdr;
1245
+ private readonly destroyRef;
1246
+ /** Seconds since the import request started (shown while it runs). */
1247
+ importElapsed: number;
1248
+ private importTimer;
1213
1249
  constructor();
1214
1250
  exporting: boolean;
1215
1251
  exportAll(): void;
@@ -1237,6 +1273,7 @@ export declare class ImportExportComponent {
1237
1273
  closeImport(): void;
1238
1274
  finishImport(): void;
1239
1275
  onFileChange(event: Event): void;
1276
+ private readCsvFile;
1240
1277
  private autoMap;
1241
1278
  get canSubmit(): boolean;
1242
1279
  get unmappedRequiredFields(): string[];
@@ -1247,6 +1284,7 @@ export declare class ImportExportComponent {
1247
1284
  goToPreview(): void;
1248
1285
  get mappedFields(): ImportFieldDef[];
1249
1286
  get previewRows(): Record<string, string>[];
1287
+ private stopImportTimer;
1250
1288
  submitImport(): void;
1251
1289
  static ɵfac: i0.ɵɵFactoryDeclaration<ImportExportComponent, never>;
1252
1290
  static ɵcmp: i0.ɵɵComponentDeclaration<ImportExportComponent, "ui-import-export", never, {
@@ -1386,6 +1424,10 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1386
1424
  * `[{ field: 'status', exclude: ['EXPIRED'] }, 'delete']` (see `BulkUpdateOption`).
1387
1425
  * Use `{ field, allowedFrom: { TARGET: [sourceValues] } }` for a status workflow: only targets some selected row can
1388
1426
  * legally move to are offered, and rows that can't move are skipped (never sent).
1427
+ * Use `{ field: 'resend', action: true, label: 'Resend', eligible?, confirm?, variant?, doneLabel? }` for a direct bulk action: a bar button
1428
+ * that confirms and POSTs `<endpoint>/bulk/<field>` with `{ ids }` of the eligible rows (no select, no column needed).
1429
+ * A blank `label` falls back to the key split on `_`, `-` and camelCase with only the first word capitalised ('send_reminder' -> 'Send reminder');
1430
+ * two actions with the same key collapse to the first; an `eligible` that throws counts as "row not eligible".
1389
1431
  */
1390
1432
  readonly bulkUpdateOptions: import("@angular/core").InputSignal<BulkUpdateOption<T>[]>;
1391
1433
  readonly rowClick: import("@angular/core").OutputEmitterRef<T>;
@@ -1576,9 +1618,14 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1576
1618
  /**
1577
1619
  * Derives internal action definitions from the `bulkUpdateOptions` string array.
1578
1620
  * 'delete' → built-in danger action with table-managed confirm dialog.
1621
+ * `{ field, action: true, label }` → direct action (type `action-<key>`, no column needed, no select).
1579
1622
  * Everything else → matched to a column: label = the entry's `label` when given, else "Change {col.label}"; options from column config.
1580
1623
  */
1581
1624
  protected readonly _bulkDefs: Signal<BulkActionDef[]>;
1625
+ /** Fallback label of a direct action without one: splits the key on `_`, `-` and camelCase, capitalises the first word only ('send_reminder' / 'sendReminder' -> 'Send reminder'). */
1626
+ private _actionKeyLabel;
1627
+ /** Runs a direct action's `eligible` predicate; a predicate that throws counts as "row not eligible". */
1628
+ private _isActionEligible;
1582
1629
  /** Options for a field-based bulk action: the field's options minus the entry's `exclude` values. */
1583
1630
  private _bulkOptionsFor;
1584
1631
  /** True when a row whose field currently holds `current` may be moved to `target` by this action. */
@@ -1632,12 +1679,20 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1632
1679
  * skipped-rows sentence, appended on a new paragraph unless the copy sets `omitSkippedNote`.
1633
1680
  */
1634
1681
  private _hostConfirm;
1635
- /** Why a bulk action can't be used right now ('' = it can). Only Delete with `deletable` can be blocked. */
1682
+ /** Why a bulk action can't be used right now ('' = it can). Only Delete with `deletable` and a direct action with `eligible` can be blocked. */
1636
1683
  protected _bulkBlockedReason(def: BulkActionDef): string;
1637
1684
  /** The distinct reasons of the currently blocked bulk actions, shown as visible text next to the disabled buttons. */
1638
1685
  protected _bulkBlockedHints(): string[];
1639
1686
  /** Selected rows (loaded page) split by the delete option's `deletable` predicate; without it every selected row is deletable. */
1640
1687
  private _deleteSplit;
1688
+ /** Selected rows (loaded page) split by a direct action's `eligible` predicate; without it every selected row is eligible. */
1689
+ private _actionSplit;
1690
+ /** Type of the direct bulk action whose request is in flight (null = none). */
1691
+ protected readonly _runningAction: import("@angular/core").WritableSignal<string | null>;
1692
+ /** True while a direct action's confirm dialog is open (guards against a second click). */
1693
+ private _confirmingAction;
1694
+ /** Direct bulk action: confirm, then POST `<endpoint>/bulk/<key>` with `{ ids }` of the eligible rows. */
1695
+ private _runDirectAction;
1641
1696
  /** Called when the user clicks a bulk action button in the selection bar. */
1642
1697
  protected onBulkActionClick(def: BulkActionDef): void;
1643
1698
  /** True while the field-change confirmation dialog is open (guards against a second Apply click). */
@@ -1686,6 +1741,11 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1686
1741
  */
1687
1742
  private _fetchCounts;
1688
1743
  ngOnInit(): void;
1744
+ /**
1745
+ * Retry from the error state. A failed table shows no search box or quick tabs (there is nothing to search), so a search term that was
1746
+ * active when the request failed could not be cleared any more: the retry therefore asks for the unsearched list on page 1.
1747
+ */
1748
+ protected _retryAfterError(): void;
1689
1749
  protected onSearchChange(val: string): void;
1690
1750
  /** Phone sort control: "Default" (value '') + one entry per sortable visible column and direction ("<key>:asc|desc"). */
1691
1751
  protected readonly _phoneSortOptions: Signal<{
@@ -6025,7 +6085,11 @@ export interface DropdownItem {
6025
6085
  count?: number;
6026
6086
  /** Greyed-out, not selectable and skipped by keyboard navigation. */
6027
6087
  disabled?: boolean;
6088
+ /** Optional 14px icon before the label: an SVG path (the `d` value, like a toolbar action's `icon`). Decorative (hidden from assistive tech). */
6089
+ icon?: string;
6028
6090
  }
6091
+ /** Which edge of the trigger the menu lines up with: `auto` (default) opens from the trigger's left edge and switches to its right edge when the menu would run off the right of the screen. */
6092
+ export type DropdownAlign = 'auto' | 'start' | 'end';
6029
6093
  export declare class DropdownComponent {
6030
6094
  readonly label: import("@angular/core").InputSignal<string>;
6031
6095
  readonly items: import("@angular/core").InputSignal<DropdownItem[]>;
@@ -6042,12 +6106,19 @@ export declare class DropdownComponent {
6042
6106
  */
6043
6107
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
6044
6108
  readonly itemClick: import("@angular/core").OutputEmitterRef<DropdownItem>;
6109
+ /** Which edge of the trigger the menu lines up with (default `auto`: from the left edge, switching to the right edge when it would run off the screen). */
6110
+ readonly align: import("@angular/core").InputSignal<DropdownAlign>;
6045
6111
  protected readonly open: import("@angular/core").WritableSignal<boolean>;
6046
6112
  protected readonly loading: import("@angular/core").WritableSignal<boolean>;
6113
+ /** Placement measured when the menu opens: right-aligned, above the trigger, and the height left on that side (the menu scrolls inside it). */
6114
+ protected readonly alignEnd: import("@angular/core").WritableSignal<boolean>;
6115
+ protected readonly openUp: import("@angular/core").WritableSignal<boolean>;
6116
+ protected readonly menuMaxHeight: import("@angular/core").WritableSignal<number | null>;
6047
6117
  private readonly _loaded;
6048
6118
  private readonly _id;
6049
6119
  private readonly _host;
6050
6120
  private readonly _cdr;
6121
+ private readonly _injector;
6051
6122
  private _pendingFocus;
6052
6123
  private _typeahead;
6053
6124
  private _typeaheadTimer;
@@ -6077,6 +6148,12 @@ export declare class DropdownComponent {
6077
6148
  protected onMenuKeydown(event: KeyboardEvent): void;
6078
6149
  protected onEscape(event: Event): void;
6079
6150
  private _setOpen;
6151
+ /**
6152
+ * Places the open menu from its measured size and the trigger's screen position: it keeps 16px clear of the screen edges, lines up with the trigger's
6153
+ * right edge instead of the left when that is the side with room (`align` = auto), opens above the trigger when there is more room there, and is capped to
6154
+ * the room left on that side (so it scrolls inside only when it truly does not fit). No layout APIs (server rendering) = the default placement.
6155
+ */
6156
+ private _place;
6080
6157
  private _openAndFocus;
6081
6158
  /** Renders the menu synchronously, then focuses the pending first / last enabled item. */
6082
6159
  private _focusPending;
@@ -6125,6 +6202,11 @@ export declare class DropdownComponent {
6125
6202
  "required": false;
6126
6203
  "isSignal": true;
6127
6204
  };
6205
+ "align": {
6206
+ "alias": "align";
6207
+ "required": false;
6208
+ "isSignal": true;
6209
+ };
6128
6210
  }, {
6129
6211
  "itemClick": "itemClick";
6130
6212
  }, never, never, true, never>;
@@ -7169,8 +7251,8 @@ export declare class PanelInfoListComponent {
7169
7251
  * readers announce it as pressed / not pressed. A dismissible chip is a removable tag, not a toggle, so it carries no
7170
7252
  * `aria-pressed` (the ✕ button is named "Remove <label>").
7171
7253
  *
7172
- * Dismissible chips can be removed from the keyboard: Tab to the ✕ (focusable, named "Remove <label>") and press Enter or
7173
- * Space, or press Delete / Backspace while the chip itself is focused.
7254
+ * A dismissible chip is a wrapper of two sibling native buttons (the label and the ✕), never a button inside a button (axe nested-interactive).
7255
+ * Keyboard removal: Tab to the ✕ (named "Remove <label>") and press Enter or Space, or press Delete / Backspace on either button.
7174
7256
  *
7175
7257
  * Dismissible (shows an ✕ button — parent removes the chip on dismiss):
7176
7258
  * <ui-chip label="Austin" size="sm" [dismissible]="true" (dismiss)="removeCity('Austin')" />
@@ -7210,10 +7292,7 @@ export declare class ChipComponent implements ControlValueAccessor {
7210
7292
  protected _onTouched: () => void;
7211
7293
  protected onClick(): void;
7212
7294
  protected onDismiss(event: Event): void;
7213
- /**
7214
- * Keyboard removal: Enter / Space on the focused ✕, or Delete / Backspace on the chip itself (dismissible chips only).
7215
- * Handled here so the key never also toggles the chip.
7216
- */
7295
+ /** Delete / Backspace on either button of a dismissible chip removes it (the ✕ itself is a native button: Enter / Space click it). */
7217
7296
  protected onDismissKey(event: Event): void;
7218
7297
  writeValue(v: boolean): void;
7219
7298
  registerOnChange(fn: (v: boolean) => void): void;
@@ -9201,6 +9280,8 @@ export declare class DetailCardComponent {
9201
9280
  readonly editAriaLabel: import("@angular/core").InputSignal<string>;
9202
9281
  /** true = the card fills the height of its container (a grid cell / flex item) so sibling cards line up; the body grows. Spacing between cells comes from the gap: the filled card's margin-bottom is 0. Default false: unchanged. */
9203
9282
  readonly fill: import("@angular/core").InputSignal<boolean>;
9283
+ /** Max height of the card body (any CSS length, e.g. `360px`): a longer body scrolls inside the card so the card never gets crazy long. Empty = no limit (or the `--dc-body-max-height` custom property, which a page can set on the card or an ancestor). */
9284
+ readonly maxBodyHeight: import("@angular/core").InputSignal<string>;
9204
9285
  /** true = a click on the card (outside interactive controls and text selections) toggles `selected`. The card must contain its own checkbox for keyboard / screen-reader users. Default false: no handler, cursor or outline. */
9205
9286
  readonly selectable: import("@angular/core").InputSignal<boolean>;
9206
9287
  /** Two-way: whether the card is selected (only shown / toggled when `selectable`). */
@@ -9315,6 +9396,11 @@ export declare class DetailCardComponent {
9315
9396
  "required": false;
9316
9397
  "isSignal": true;
9317
9398
  };
9399
+ "maxBodyHeight": {
9400
+ "alias": "maxBodyHeight";
9401
+ "required": false;
9402
+ "isSignal": true;
9403
+ };
9318
9404
  "selectable": {
9319
9405
  "alias": "selectable";
9320
9406
  "required": false;
@@ -11227,7 +11313,7 @@ export interface IntegrationPlainText {
11227
11313
  export declare function integrationPlainText(html: string): IntegrationPlainText;
11228
11314
  /**
11229
11315
  * How a connect button for an integration works.
11230
- * - `oauth`: GET `authorizeEndpoint` answers `{ url }` and the browser goes there (QuickBooks, Slack, Gmail, Outlook, Google / Outlook calendar).
11316
+ * - `oauth`: GET `authorizeEndpoint` answers `{ url }` (or `{ authUrl }`) and the browser goes there (QuickBooks, Slack, Gmail, Outlook, Google / Outlook calendar, Stripe, Square).
11231
11317
  * - `area-code`: asks for a US area code and POSTs `{ areaCode }` to `connectEndpoint` (Twilio buys a number that way); the status is reloaded after.
11232
11318
  */
11233
11319
  export type SetupIntegrationConnect = {
@@ -11331,7 +11417,7 @@ export interface SetupStep {
11331
11417
  skippable?: boolean;
11332
11418
  /** `custom`: runs on "Save & Continue". Resolve to continue; throw / error to stay on the step (the message is shown in an alert). */
11333
11419
  save?: () => Observable<unknown> | Promise<unknown> | void;
11334
- /** `custom`: false disables "Save & Continue". */
11420
+ /** `custom` and `integrations`: false disables "Save & Continue" (read on every render, so it can follow signals). */
11335
11421
  canContinue?: () => boolean;
11336
11422
  /** `custom`: text of the continue button (default "Save & Continue"). */
11337
11423
  saveLabel?: string;
@@ -11370,7 +11456,9 @@ export interface SetupWizardApi {
11370
11456
  markCompleted(...keys: string[]): void;
11371
11457
  }
11372
11458
  /**
11373
- * The integrations every app can offer in a setup step. The endpoints follow the convention the apps' backends already use (`GET <x>/status`,
11459
+ * The integrations every app can offer in a setup step. Payment processing (`stripe`, `square`) belongs in almost every app's wizard:
11460
+ * `setupIntegrations('stripe', 'square')` is the ready-made "connect payments" step (an app that has one processor passes just that one; the
11461
+ * backend answers 409 when the other is already connected, and the step shows it). The endpoints follow the convention the apps' backends already use (`GET <x>/status`,
11374
11462
  * `GET <x>/authorize` answering `{ url }`); an app whose routes differ spreads a preset and overrides what differs:
11375
11463
  * `{ ...SETUP_INTEGRATIONS['quickbooks'], statusEndpoint: '/accounting/status' }`.
11376
11464
  */
@@ -11524,6 +11612,11 @@ export declare class SetupWizardComponent {
11524
11612
  protected back(): void;
11525
11613
  protected skip(): void;
11526
11614
  /** Marks the given steps done (ignoring keys that are not steps of this wizard). */
11615
+ /**
11616
+ * Marks steps as done from outside the step templates (unknown keys are ignored), e.g. when the app finds that a step's data already exists after a
11617
+ * page reload, so the user is not asked to import it a second time. Same effect as `wizard.markCompleted(...)` inside a step template.
11618
+ */
11619
+ completeSteps(...keys: string[]): void;
11527
11620
  private markStepsCompleted;
11528
11621
  private markCompleted;
11529
11622
  /** "Save & Continue" of a custom or integrations step: runs the step's hook, then moves on. */
@@ -11546,6 +11639,7 @@ export declare class SetupWizardComponent {
11546
11639
  protected detailFor(i: SetupIntegration): string;
11547
11640
  protected connect(i: SetupIntegration): void;
11548
11641
  private startConnect;
11642
+ private isWebAddress;
11549
11643
  /** SVG path of a library icon (the buttons' leading / trailing glyph). */
11550
11644
  protected iconD(name: string): string;
11551
11645
  protected areaCodeLabelFor(i: SetupIntegration): string;
@@ -12073,6 +12167,11 @@ export declare const MAX_EMAIL_ADDRESS_LENGTH = 254;
12073
12167
  export declare const isValidEmailAddress: (address: string) => boolean;
12074
12168
  /** An http(s) address with a real host: no spaces or quotes (the address is written into an HTML attribute) and a dot or `localhost`. */
12075
12169
  export declare function isWebUrl(raw: string): boolean;
12170
+ /**
12171
+ * How many characters typed text takes once it is saved, i.e. as `innerHTML` writes a TEXT node: `&`, `<` and `>` become entities and a
12172
+ * non-breaking space (what a browser types for a run of spaces) becomes `&nbsp;`; quotes are not escaped in text, so each counts as one.
12173
+ */
12174
+ export declare function serializedTextLength(text: string): number;
12076
12175
  /** Escapes text for HTML text and double-quoted attribute values. */
12077
12176
  export declare function escapeHtml(value: string): string;
12078
12177
  /** True when a block's HTML shows something to a reader: an image or other media, or text once tags, non-breaking spaces and whitespace are gone. */