@forgeroute/ui 0.4.26 → 0.4.28

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.
@@ -2491,7 +2491,7 @@ export declare class AudioPlayerComponent {
2491
2491
  /** Accessible name of the player. */
2492
2492
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
2493
2493
  /** How much to load before play: 'metadata' (default), 'auto' or 'none'. */
2494
- readonly preload: import("@angular/core").InputSignal<"none" | "auto" | "metadata">;
2494
+ readonly preload: import("@angular/core").InputSignal<"metadata" | "none" | "auto">;
2495
2495
  /** Fires when the browser can't load or decode the source. */
2496
2496
  readonly playbackError: import("@angular/core").OutputEmitterRef<void>;
2497
2497
  protected readonly failed: import("@angular/core").WritableSignal<boolean>;
@@ -3766,7 +3766,7 @@ export declare class ButtonComponent {
3766
3766
  * `ghost-start`: a ghost button for list / section toggles - a full-width row with its content aligned to the start, transparent
3767
3767
  * background, hover tint and the normal focus ring. Pair it with `ariaExpanded` / `ariaPressed`; the label may contain a heading element.
3768
3768
  */
3769
- readonly variant: import("@angular/core").InputSignal<"primary" | "secondary" | "danger" | "warning" | "link" | "ghost" | "ghost-start" | "tab" | "icon" | "icon-danger">;
3769
+ readonly variant: import("@angular/core").InputSignal<"link" | "primary" | "secondary" | "danger" | "warning" | "ghost" | "ghost-start" | "tab" | "icon" | "icon-danger">;
3770
3770
  /** Renders an anchor to this route instead of a button (use with `variant="link"` for an inline text link). `['/settings']` or `'/settings'`. While `disabled` or `loading` it renders a non-clickable `<span role="link" aria-disabled>` with no href (like `href`). */
3771
3771
  readonly routerLink: import("@angular/core").InputSignal<string | readonly unknown[] | null>;
3772
3772
  /** Query params of the `routerLink` (e.g. `{ section: 'deal-defaults' }`). */
@@ -3787,7 +3787,7 @@ export declare class ButtonComponent {
3787
3787
  */
3788
3788
  readonly href: import("@angular/core").InputSignal<string | null>;
3789
3789
  /** Where to open the `href` (`_blank` = a new tab). */
3790
- readonly target: import("@angular/core").InputSignal<"_self" | "_blank" | "_parent" | "_top" | null>;
3790
+ readonly target: import("@angular/core").InputSignal<"_blank" | "_self" | "_parent" | "_top" | null>;
3791
3791
  /** `rel` of the `href` link. A `_blank` target always gets `noopener noreferrer` added. */
3792
3792
  readonly rel: import("@angular/core").InputSignal<string | null>;
3793
3793
  /** The href when it is allowed (http, https, mailto, tel, sms or a relative URL), else null. */
@@ -3812,7 +3812,7 @@ export declare class ButtonComponent {
3812
3812
  readonly color: import("@angular/core").InputSignal<string | undefined>;
3813
3813
  readonly active: import("@angular/core").InputSignal<boolean>;
3814
3814
  readonly size: import("@angular/core").InputSignal<"sm" | "md" | "lg" | "xs">;
3815
- readonly type: import("@angular/core").InputSignal<"reset" | "submit" | "button">;
3815
+ readonly type: import("@angular/core").InputSignal<"button" | "reset" | "submit">;
3816
3816
  readonly disabled: import("@angular/core").InputSignal<boolean>;
3817
3817
  readonly loading: import("@angular/core").InputSignal<boolean>;
3818
3818
  readonly fullWidth: import("@angular/core").InputSignal<boolean>;
@@ -4427,7 +4427,7 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
4427
4427
  readonly passwordRequirements: import("@angular/core").InputSignal<boolean>;
4428
4428
  /** Rules for `passwordRequirements`; default = the library's five rules (8+ characters, upper, lower, digit, special). */
4429
4429
  readonly passwordRules: import("@angular/core").InputSignal<readonly PasswordRule[] | undefined>;
4430
- readonly type: import("@angular/core").InputSignal<"number" | "text" | "email" | "tel" | "password" | "url">;
4430
+ readonly type: import("@angular/core").InputSignal<"number" | "text" | "password" | "email" | "tel" | "url">;
4431
4431
  readonly maxlength: import("@angular/core").InputSignal<number | undefined>;
4432
4432
  readonly required: import("@angular/core").InputSignal<boolean>;
4433
4433
  readonly autocomplete: import("@angular/core").InputSignal<string>;
@@ -4436,7 +4436,7 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
4436
4436
  * `search`, `text`, `none`). Empty (default) = the browser's default for the field, except `type="tel"`, which keeps `tel`.
4437
4437
  * It only picks the keyboard; it does not restrict what can be typed or pasted.
4438
4438
  */
4439
- readonly inputmode: import("@angular/core").InputSignal<"" | "none" | "text" | "search" | "email" | "tel" | "url" | "decimal" | "numeric">;
4439
+ readonly inputmode: import("@angular/core").InputSignal<"" | "text" | "email" | "search" | "none" | "tel" | "url" | "decimal" | "numeric">;
4440
4440
  readonly readonly: import("@angular/core").InputSignal<boolean>;
4441
4441
  readonly hint: import("@angular/core").InputSignal<string>;
4442
4442
  readonly error: import("@angular/core").InputSignal<string>;
@@ -4500,7 +4500,7 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
4500
4500
  protected readonly _stepAttr: import("@angular/core").Signal<string | null>;
4501
4501
  protected readonly _revealable: import("@angular/core").Signal<boolean>;
4502
4502
  protected readonly _shown: import("@angular/core").WritableSignal<boolean>;
4503
- protected readonly _inputType: import("@angular/core").Signal<"number" | "text" | "email" | "tel" | "password" | "url">;
4503
+ protected readonly _inputType: import("@angular/core").Signal<"number" | "text" | "password" | "email" | "tel" | "url">;
4504
4504
  protected readonly _eyePath: string;
4505
4505
  protected readonly _eyeOffPath: string;
4506
4506
  protected readonly _showRequirements: import("@angular/core").Signal<boolean>;
@@ -5848,7 +5848,7 @@ export declare class DropdownComponent {
5848
5848
  readonly loader: import("@angular/core").InputSignal<(() => Promise<DropdownItem[]>) | null>;
5849
5849
  readonly active: import("@angular/core").InputSignal<boolean>;
5850
5850
  readonly activeValue: import("@angular/core").InputSignal<string>;
5851
- readonly variant: import("@angular/core").InputSignal<"badge" | "button">;
5851
+ readonly variant: import("@angular/core").InputSignal<"button" | "badge">;
5852
5852
  readonly menuWidth: import("@angular/core").InputSignal<string>;
5853
5853
  /**
5854
5854
  * Accessible name. Set it when the trigger has no meaningful visible text. Applied as `aria-label` on the
@@ -6046,7 +6046,7 @@ export declare class FormRowComponent {
6046
6046
  * child simply takes the full width. Keep a numeric span within the row's `cols` (use `all` for the whole row).
6047
6047
  */
6048
6048
  export declare class FormColSpanDirective {
6049
- readonly uiColSpan: import("@angular/core").InputSignal<"" | "2" | "3" | 2 | 3 | "all">;
6049
+ readonly uiColSpan: import("@angular/core").InputSignal<"" | "all" | 2 | "2" | 3 | "3">;
6050
6050
  protected readonly _class: import("@angular/core").Signal<string>;
6051
6051
  static ɵfac: i0.ɵɵFactoryDeclaration<FormColSpanDirective, never>;
6052
6052
  static ɵdir: i0.ɵɵDirectiveDeclaration<FormColSpanDirective, "[uiColSpan]", never, {
@@ -7076,7 +7076,7 @@ export declare class ChipGroupComponent {
7076
7076
  readonly spaceAbove: import("@angular/core").InputSignal<"none" | "sm" | "md">;
7077
7077
  /** Space below the chip group. */
7078
7078
  readonly spaceBelow: import("@angular/core").InputSignal<"none" | "sm" | "md">;
7079
- protected readonly _gap: import("@angular/core").Signal<"6px" | "12px" | "8px">;
7079
+ protected readonly _gap: import("@angular/core").Signal<"8px" | "6px" | "12px">;
7080
7080
  protected readonly _spaceAbove: import("@angular/core").Signal<"0" | "6px" | "12px">;
7081
7081
  protected readonly _spaceBelow: import("@angular/core").Signal<"0" | "6px" | "12px">;
7082
7082
  static ɵfac: i0.ɵɵFactoryDeclaration<ChipGroupComponent, never>;
@@ -10970,4 +10970,868 @@ export declare class ImageGalleryDialogComponent {
10970
10970
  "index": "indexChange";
10971
10971
  }, never, never, true, never>;
10972
10972
  }
10973
+ /** The apps / modules that can offer integrations ('marketing' = the shared marketing module, which has its own Integrations page). */
10974
+ export type IntegrationApp = 'real-estate' | 'contractor' | 'cremo' | 'mfh' | 'marketing';
10975
+ /** One integration. `apps` says which apps offer it: `'all'` (Twilio, ...) or the list of apps it belongs to. Live ones can be connected; coming-soon ones are display-only. */
10976
+ export interface IntegrationDef {
10977
+ key: string;
10978
+ name: string;
10979
+ description: string;
10980
+ /** `'all'` = every app; otherwise only the listed apps offer it. */
10981
+ apps: 'all' | readonly IntegrationApp[];
10982
+ status: 'live' | 'coming-soon';
10983
+ iconBg: string;
10984
+ iconColor: string;
10985
+ connectLabel: string;
10986
+ connectDesc: string;
10987
+ setupSteps?: {
10988
+ title: string;
10989
+ desc: string;
10990
+ }[];
10991
+ howItWorks?: {
10992
+ title: string;
10993
+ desc: string;
10994
+ }[];
10995
+ group?: string;
10996
+ pricingNote?: string;
10997
+ /** IDX providers only: where the provider's own documentation lives. */
10998
+ supportUrl?: string;
10999
+ /** IDX providers only: the lead fields the provider sends. */
11000
+ fieldFormats?: string;
11001
+ /** IDX providers only: how the provider delivers leads. */
11002
+ webhookNote?: string;
11003
+ }
11004
+ /** Every integration any app offers: IDX providers first, then the rest in catalog order. Use `integrationsForApp()` to get one app's. */
11005
+ export declare const INTEGRATION_CATALOG: readonly IntegrationDef[];
11006
+ /** A text field the connect form shows while an integration is not connected. */
11007
+ export interface IntegrationFieldDef {
11008
+ id: string;
11009
+ label: string;
11010
+ type: 'text' | 'password';
11011
+ placeholder: string;
11012
+ hint?: string;
11013
+ }
11014
+ /** A checkbox of an integration's import options (shown once connected). */
11015
+ export interface IntegrationOptionDef {
11016
+ id: string;
11017
+ label: string;
11018
+ hint?: string;
11019
+ }
11020
+ export declare const INTEGRATION_FIELD_DEFS: Readonly<Record<string, readonly IntegrationFieldDef[]>>;
11021
+ export declare const INTEGRATION_OPTION_DEFS: Readonly<Record<string, readonly IntegrationOptionDef[]>>;
11022
+ /** Behavioural signals the IDX tracking webhook records (tier = how many lead-score points it is worth). */
11023
+ export declare const IDX_BEHAVIOR_SIGNALS: readonly {
11024
+ label: string;
11025
+ tier: 'high' | 'med' | 'low';
11026
+ }[];
11027
+ /** Ordered section labels used by `IntegrationDef.group`. */
11028
+ export declare const INTEGRATION_GROUP_ORDER: readonly string[];
11029
+ /** The integrations one app offers (those marked `'all'` plus those that list the app), in catalog order. */
11030
+ export declare function integrationsForApp(app: IntegrationApp): IntegrationDef[];
11031
+ /**
11032
+ * The integrations that belong to ONE app / module only, i.e. whose `apps` list names it (integrations offered to 'all' apps are not included).
11033
+ * The marketing module's own Integrations page uses `integrationsOnlyFor('marketing')`.
11034
+ */
11035
+ export declare function integrationsOnlyFor(app: IntegrationApp): IntegrationDef[];
11036
+ /** Plain text plus the snippets to emphasise, for `<ui-text [text] [strong]>`. */
11037
+ export interface IntegrationPlainText {
11038
+ text: string;
11039
+ strong: string[];
11040
+ }
11041
+ /** Splits `<strong>x</strong>` markup out of catalog copy: `"a <strong>b</strong> c"` -> `{ text: 'a b c', strong: ['b'] }`. Any other tag is dropped. */
11042
+ export declare function integrationPlainText(html: string): IntegrationPlainText;
11043
+ /**
11044
+ * How a connect button for an integration works.
11045
+ * - `oauth`: GET `authorizeEndpoint` answers `{ url }` and the browser goes there (QuickBooks, Slack, Gmail, Outlook, Google / Outlook calendar).
11046
+ * - `area-code`: asks for a US area code and POSTs `{ areaCode }` to `connectEndpoint` (Twilio buys a number that way); the status is reloaded after.
11047
+ */
11048
+ export type SetupIntegrationConnect = {
11049
+ type: 'oauth';
11050
+ authorizeEndpoint: string;
11051
+ } | {
11052
+ type: 'area-code';
11053
+ connectEndpoint: string;
11054
+ areaCodeLabel?: string;
11055
+ };
11056
+ /** One integration row in an `integrations` step. The three common ones (QuickBooks, Twilio, Slack) and email / calendar come ready made: see `SETUP_INTEGRATIONS`. */
11057
+ export interface SetupIntegration {
11058
+ key: string;
11059
+ title: string;
11060
+ /** Shown while the integration is not connected. */
11061
+ description: string;
11062
+ /** Library `ui-list-item` icon name (default 'link'). */
11063
+ icon?: string;
11064
+ /** GET answering `{ connected: boolean, ... }` (relative to the API base URL of `FORGEROUTE_CONFIG`). */
11065
+ statusEndpoint: string;
11066
+ /** When one status endpoint answers several providers (`{ gmail: {...}, outlook: {...} }`), the property that belongs to this one. */
11067
+ statusKey?: string;
11068
+ /** The "Connected — …" detail built from the status payload (default: just "Connected"). */
11069
+ connectedDetail?: (status: Record<string, unknown>) => string;
11070
+ connect: SetupIntegrationConnect;
11071
+ }
11072
+ /** Texts of the built-in controls; every one has an English default and every one can be replaced by the app. */
11073
+ export interface SetupWizardTexts {
11074
+ brand: string;
11075
+ stepperLabel: string;
11076
+ progress: (done: number, total: number) => string;
11077
+ back: string;
11078
+ skip: string;
11079
+ saveAndContinue: string;
11080
+ continue: string;
11081
+ exit: string;
11082
+ connect: string;
11083
+ reconnect: string;
11084
+ connected: string;
11085
+ importMore: string;
11086
+ whatGetsImported: string;
11087
+ doneTitle: string;
11088
+ doneDescription: string;
11089
+ doneHint: string;
11090
+ finish: string;
11091
+ summaryDone: string;
11092
+ summarySkipped: string;
11093
+ summaryPending: string;
11094
+ areaCode: string;
11095
+ connectIntegrationFailed: (title: string) => string;
11096
+ }
11097
+ /** An import step: the library import window (CSV / pasted data / competitor sources) with the step's own words around it. */
11098
+ export interface SetupImportStep {
11099
+ config: ImportExportConfig;
11100
+ sources?: ImportSource[];
11101
+ /** Short "what gets imported" tags shown above the button. */
11102
+ tags?: string[];
11103
+ /** Text on the open button, e.g. "Import Contacts". */
11104
+ buttonLabel: string;
11105
+ /** Hint above the button. */
11106
+ hint?: string;
11107
+ /** Result alert title, built from the number imported (default `${n} ${config.entityNamePlural.toLowerCase()} imported successfully.`). */
11108
+ resultTitle?: (imported: number) => string;
11109
+ /** Result alert body. */
11110
+ resultText?: string;
11111
+ }
11112
+ /**
11113
+ * One step of the wizard. `steps` is the list in walking order: the app builds it (and so decides the order, the wording and which steps exist for
11114
+ * each kind of user). A `done` summary step is always added at the end by the wizard itself.
11115
+ */
11116
+ export interface SetupStep {
11117
+ /** Unique within the list. */
11118
+ key: string;
11119
+ label: string;
11120
+ /** The paragraph under the heading. */
11121
+ description?: string;
11122
+ /** `custom` shows the app's own `<ng-template uiSetupStep="key">`; `import` and `integrations` are built in. */
11123
+ kind: 'custom' | 'import' | 'integrations';
11124
+ /** Shows "Skip for now" (default true). The first step of a wizard usually sets this to false. */
11125
+ skippable?: boolean;
11126
+ /** `custom`: runs on "Save & Continue". Resolve to continue; throw / error to stay on the step (the message is shown in an alert). */
11127
+ save?: () => Observable<unknown> | Promise<unknown> | void;
11128
+ /** `custom`: false disables "Save & Continue". */
11129
+ canContinue?: () => boolean;
11130
+ /** `custom`: text of the continue button (default "Save & Continue"). */
11131
+ saveLabel?: string;
11132
+ import?: SetupImportStep;
11133
+ integrations?: SetupIntegration[];
11134
+ /** `integrations`: runs when the step is left with Continue (e.g. to save sender settings). */
11135
+ onContinue?: () => Observable<unknown> | Promise<unknown> | void;
11136
+ /** Called when the step is shown (load data for a custom step). */
11137
+ onEnter?: () => void;
11138
+ }
11139
+ /**
11140
+ * The integrations every app can offer in a setup step. The endpoints follow the convention the apps' backends already use (`GET <x>/status`,
11141
+ * `GET <x>/authorize` answering `{ url }`); an app whose routes differ spreads a preset and overrides what differs:
11142
+ * `{ ...SETUP_INTEGRATIONS['quickbooks'], statusEndpoint: '/accounting/status' }`.
11143
+ */
11144
+ export declare const SETUP_INTEGRATIONS: Readonly<Record<string, SetupIntegration>>;
11145
+ /** The presets for the given keys, in that order (unknown keys throw, so a typo is found at once). */
11146
+ export declare function setupIntegrations(...keys: (keyof typeof SETUP_INTEGRATIONS | string)[]): SetupIntegration[];
11147
+ /**
11148
+ * Marks the content of a `custom` step of `ui-setup-wizard`:
11149
+ *
11150
+ * <ui-setup-wizard [steps]="steps()">
11151
+ * <ng-template uiSetupStep="profile"> …the app's own form… </ng-template>
11152
+ * </ui-setup-wizard>
11153
+ */
11154
+ export declare class SetupWizardStepDirective {
11155
+ /** The `key` of the step this template belongs to. */
11156
+ readonly key: import("@angular/core").InputSignal<string>;
11157
+ readonly template: TemplateRef<unknown>;
11158
+ static ɵfac: i0.ɵɵFactoryDeclaration<SetupWizardStepDirective, never>;
11159
+ static ɵdir: i0.ɵɵDirectiveDeclaration<SetupWizardStepDirective, "ng-template[uiSetupStep]", never, {
11160
+ "key": {
11161
+ "alias": "uiSetupStep";
11162
+ "required": true;
11163
+ "isSignal": true;
11164
+ };
11165
+ }, {}, never, never, true, never>;
11166
+ }
11167
+ /** The key of the summary step the wizard appends to the app's steps. */
11168
+ export declare const SETUP_DONE_KEY = "done";
11169
+ /**
11170
+ * `ui-setup-wizard` — the first-run setup wizard every app plugs into. A left stepper with progress, one step at a time on the right, Back / Skip /
11171
+ * Save & Continue, and a final summary. The app gives it the list of steps (in the order that user type walks them) and the wording; the library
11172
+ * does the rest:
11173
+ *
11174
+ * - `kind: 'import'` steps open the library import window (CSV, pasted data, competitor sources) and show the result;
11175
+ * - `kind: 'integrations'` steps list integrations with Connect / Reconnect — QuickBooks, Twilio, Slack, email and calendar come ready made
11176
+ * (`SETUP_INTEGRATIONS` / `setupIntegrations(...)`); an app can add its own;
11177
+ * - `kind: 'custom'` steps show the app's own content (`<ng-template uiSetupStep="key">`) with the step's `save` / `canContinue` hooks.
11178
+ *
11179
+ * <ui-setup-wizard [steps]="steps()" [texts]="{ brand: 'My App Setup' }" (finished)="goToDashboard()" (exited)="goToDashboard()">
11180
+ * <ng-template uiSetupStep="profile"> … </ng-template>
11181
+ * </ui-setup-wizard>
11182
+ *
11183
+ * Changing `steps` (for example when the user type becomes known) re-orders the stepper; progress of a step that is still in the list is kept.
11184
+ */
11185
+ export declare class SetupWizardComponent {
11186
+ private readonly http;
11187
+ private readonly apiBase;
11188
+ private readonly destroyRef;
11189
+ /** The steps in walking order. A `done` summary step is appended automatically. */
11190
+ readonly steps: import("@angular/core").InputSignal<SetupStep[]>;
11191
+ /** Replaces any of the built-in texts. */
11192
+ readonly texts: import("@angular/core").InputSignal<Partial<SetupWizardTexts>>;
11193
+ /** The last step's button ("Go to Dashboard"). */
11194
+ readonly finished: import("@angular/core").OutputEmitterRef<void>;
11195
+ /** "Exit Setup". */
11196
+ readonly exited: import("@angular/core").OutputEmitterRef<void>;
11197
+ /** The step that was just shown (its key). */
11198
+ readonly stepChange: import("@angular/core").OutputEmitterRef<string>;
11199
+ protected readonly stepTemplates: import("@angular/core").Signal<readonly SetupWizardStepDirective[]>;
11200
+ protected readonly importers: import("@angular/core").Signal<readonly ImportExportComponent[]>;
11201
+ protected readonly t: import("@angular/core").Signal<SetupWizardTexts>;
11202
+ protected readonly currentKey: import("@angular/core").WritableSignal<string>;
11203
+ private readonly completed;
11204
+ private readonly skipped;
11205
+ private readonly imports;
11206
+ private readonly statuses;
11207
+ protected readonly busy: import("@angular/core").WritableSignal<boolean>;
11208
+ protected readonly error: import("@angular/core").WritableSignal<string>;
11209
+ protected readonly areaCodeOpen: import("@angular/core").WritableSignal<string>;
11210
+ protected readonly areaCode: import("@angular/core").WritableSignal<string>;
11211
+ /** A US area code: exactly three digits. */
11212
+ protected readonly validAreaCode: import("@angular/core").Signal<boolean>;
11213
+ /** The app's steps plus the summary step. */
11214
+ protected readonly allSteps: import("@angular/core").Signal<SetupStep[]>;
11215
+ protected readonly index: import("@angular/core").Signal<number>;
11216
+ protected readonly current: import("@angular/core").Signal<SetupStep>;
11217
+ protected readonly isDone: import("@angular/core").Signal<boolean>;
11218
+ protected readonly completedCount: import("@angular/core").Signal<number>;
11219
+ protected readonly stepperSteps: import("@angular/core").Signal<StepperStep[]>;
11220
+ protected readonly summarySections: import("@angular/core").Signal<PanelInfoSection[]>;
11221
+ constructor();
11222
+ protected templateFor(key: string): import("@angular/core").TemplateRef<unknown> | null;
11223
+ protected goTo(key: string): void;
11224
+ private enter;
11225
+ private advanceFrom;
11226
+ protected back(): void;
11227
+ protected skip(): void;
11228
+ private markCompleted;
11229
+ /** "Save & Continue" of a custom or integrations step: runs the step's hook, then moves on. */
11230
+ protected saveAndContinue(): Promise<void>;
11231
+ private run;
11232
+ /** The message to show for a failure: the server's own `{ message }` (or a thrown error's message); never the HTTP client's technical text. */
11233
+ private messageOf;
11234
+ protected openImport(key: string): void;
11235
+ protected onImported(key: string): void;
11236
+ protected importResult(key: string): number | null;
11237
+ protected clearImport(key: string): void;
11238
+ protected importTitle(key: string): string;
11239
+ protected continueFromImport(): void;
11240
+ private loadStatuses;
11241
+ private setStatuses;
11242
+ protected isConnected(i: SetupIntegration): boolean;
11243
+ protected detailFor(i: SetupIntegration): string;
11244
+ protected connect(i: SetupIntegration): void;
11245
+ protected areaCodeLabelFor(i: SetupIntegration): string;
11246
+ /** Sends the browser to the provider's authorization page. */
11247
+ protected redirect(url: string): void;
11248
+ protected submitAreaCode(i: SetupIntegration): void;
11249
+ static ɵfac: i0.ɵɵFactoryDeclaration<SetupWizardComponent, never>;
11250
+ static ɵcmp: i0.ɵɵComponentDeclaration<SetupWizardComponent, "ui-setup-wizard", never, {
11251
+ "steps": {
11252
+ "alias": "steps";
11253
+ "required": true;
11254
+ "isSignal": true;
11255
+ };
11256
+ "texts": {
11257
+ "alias": "texts";
11258
+ "required": false;
11259
+ "isSignal": true;
11260
+ };
11261
+ }, {
11262
+ "finished": "finished";
11263
+ "exited": "exited";
11264
+ "stepChange": "stepChange";
11265
+ }, ["stepTemplates"], ["[setup-wizard-logo]"], true, never>;
11266
+ }
11267
+ /** One automated trigger a template can be assigned to (the host decides which exist). */
11268
+ export interface EmailTemplateAssignmentOption {
11269
+ key: string;
11270
+ name: string;
11271
+ description: string;
11272
+ }
11273
+ /** The channels a template is assigned to for one trigger. */
11274
+ export interface EmailTemplateAssignmentChannels {
11275
+ key: string;
11276
+ email: boolean;
11277
+ text: boolean;
11278
+ }
11279
+ /** A template as the editor shows it. `id` is null/absent for a template that is not stored yet (new, or a copy of another). */
11280
+ export interface EmailTemplateValue {
11281
+ id?: string | null;
11282
+ name: string;
11283
+ subject: string;
11284
+ category: string;
11285
+ useAllYear: boolean;
11286
+ /** `YYYY-MM-DD` or ''. */
11287
+ activeFrom: string;
11288
+ /** `YYYY-MM-DD` or ''. */
11289
+ activeTo: string;
11290
+ /** The stored message HTML (the editor reads and writes `<div data-block="true">…</div>` blocks). */
11291
+ body: string;
11292
+ videoId?: string | null;
11293
+ videoTitle?: string | null;
11294
+ /** The channels per trigger; a trigger that is missing is not assigned. */
11295
+ assignments?: EmailTemplateAssignmentChannels[];
11296
+ }
11297
+ /** What `(save)` emits. Dates are the raw `YYYY-MM-DD` values (null while "all year" is on); the host maps them to its API. */
11298
+ export interface EmailTemplateSavePayload {
11299
+ name: string;
11300
+ subject: string;
11301
+ body: string;
11302
+ category: string;
11303
+ useAllYear: boolean;
11304
+ activeFrom: string | null;
11305
+ activeTo: string | null;
11306
+ videoId: string | null;
11307
+ /** Only the triggers with at least one channel on. */
11308
+ assignments: EmailTemplateAssignmentChannels[];
11309
+ }
11310
+ export interface EmailTemplateBlock {
11311
+ key: string;
11312
+ label: string;
11313
+ /** The HTML a click / drop inserts as a new block. */
11314
+ html: string;
11315
+ }
11316
+ export interface EmailTemplateBlockGroup {
11317
+ name: string;
11318
+ blocks: EmailTemplateBlock[];
11319
+ }
11320
+ export interface EmailTemplateVariable {
11321
+ /** Inserted as `{{key}}`. */
11322
+ key: string;
11323
+ label: string;
11324
+ example: string;
11325
+ }
11326
+ export interface EmailTemplateVariableGroup {
11327
+ name: string;
11328
+ variables: EmailTemplateVariable[];
11329
+ }
11330
+ /** A video from the host's library that can be added to the message. */
11331
+ export interface EmailTemplateVideo {
11332
+ id: string;
11333
+ title: string;
11334
+ shareLink: string;
11335
+ thumbnailUrl?: string | null;
11336
+ durationSeconds?: number | null;
11337
+ category?: string | null;
11338
+ }
11339
+ /** The video options the host starts with (e.g. the company's saved defaults); the user can change them in the Video tab. */
11340
+ export interface EmailTemplateVideoDefaults {
11341
+ autoplay: boolean;
11342
+ caption: string;
11343
+ ctaType: string;
11344
+ ctaUrl: string;
11345
+ ctaLabel: string;
11346
+ hideReactions: boolean;
11347
+ }
11348
+ export interface EmailTemplateVideoOptions extends EmailTemplateVideoDefaults {}
11349
+ /** Builds the link a video block points at; the default is the video's own share link. */
11350
+ export type EmailTemplateVideoLinkFn = (video: EmailTemplateVideo, options: EmailTemplateVideoOptions) => string;
11351
+ interface EmailBlock {
11352
+ id: string;
11353
+ html: string;
11354
+ spacing?: string;
11355
+ }
11356
+ interface AssignmentView extends EmailTemplateAssignmentOption {
11357
+ email: boolean;
11358
+ text: boolean;
11359
+ }
11360
+ type EditorTab = 'edit' | 'preview' | 'mobile';
11361
+ type RightTab = 'blocks' | 'variables' | 'video';
11362
+ /**
11363
+ * `ui-email-template-editor` - the full-page, block-based email template editor (details, assignments, block canvas with drag and drop,
11364
+ * rich-text toolbar, preview / mobile preview, content blocks, merge variables, video library, test send and AI draft).
11365
+ *
11366
+ * The component owns the editing behaviour and holds no HTTP: the host loads the template (`[template]`), supplies what differs per app
11367
+ * (`categories`, `assignmentOptions`, `variableGroups`, `blockGroups`, `videos`, `videoDefaults`, `maxBodyLength`, permissions) and reacts to
11368
+ * `(save)`, `(testSend)`, `(aiGenerate)`, `(videosRequested)`, `(back)` and `(useAsMine)`. After a request the host calls back:
11369
+ * `markSaved(id)` (a create succeeded), `closeTestSend()` and `addGeneratedHtml(html)`.
11370
+ *
11371
+ * Project an element with the `uiSubjectAction` attribute to put a host control (e.g. an AI copy button) at the end of the Subject label line.
11372
+ *
11373
+ * Message limit: `maxBodyLength` is measured on the serialised HTML. An untouched stored body that is already longer than the limit is
11374
+ * accepted unchanged (legacy rows), exactly as the server does.
11375
+ */
11376
+ export declare class EmailTemplateEditorComponent {
11377
+ private readonly host;
11378
+ private readonly injector;
11379
+ private readonly toast;
11380
+ /** The template to edit. `null` = a blank new template. An object without `id` is a new template that starts from those values (e.g. a copy). */
11381
+ readonly template: import("@angular/core").InputSignal<EmailTemplateValue | null>;
11382
+ readonly categories: import("@angular/core").InputSignal<SelectOption<string>[]>;
11383
+ readonly categoryPlaceholder: import("@angular/core").InputSignal<string>;
11384
+ readonly assignmentOptions: import("@angular/core").InputSignal<EmailTemplateAssignmentOption[]>;
11385
+ /** The assignment shown first (e.g. the trigger the user came from). */
11386
+ readonly featuredAssignmentKey: import("@angular/core").InputSignal<string | null>;
11387
+ readonly assignmentsDescription: import("@angular/core").InputSignal<string>;
11388
+ readonly textEnabled: import("@angular/core").InputSignal<boolean>;
11389
+ readonly variableGroups: import("@angular/core").InputSignal<EmailTemplateVariableGroup[]>;
11390
+ readonly blockGroups: import("@angular/core").InputSignal<EmailTemplateBlockGroup[]>;
11391
+ readonly aiExamples: import("@angular/core").InputSignal<string[]>;
11392
+ readonly namePlaceholder: import("@angular/core").InputSignal<string>;
11393
+ readonly subjectPlaceholder: import("@angular/core").InputSignal<string>;
11394
+ readonly backLabel: import("@angular/core").InputSignal<string>;
11395
+ readonly createLabel: import("@angular/core").InputSignal<string>;
11396
+ readonly editLabel: import("@angular/core").InputSignal<string>;
11397
+ readonly systemNote: import("@angular/core").InputSignal<string>;
11398
+ readonly readOnlyNote: import("@angular/core").InputSignal<string>;
11399
+ /** Prefix of the ids of the field errors the editor renders (`<prefix>-body-error`). */
11400
+ readonly idPrefix: import("@angular/core").InputSignal<string>;
11401
+ /** The longest message (serialised HTML) that can be saved. */
11402
+ readonly maxBodyLength: import("@angular/core").InputSignal<number>;
11403
+ readonly maxTextLength: import("@angular/core").InputSignal<number>;
11404
+ /** The member may change templates (false = read-only: everything is shown, nothing can be edited). */
11405
+ readonly fullAccess: import("@angular/core").InputSignal<boolean>;
11406
+ /** A built-in template: shown read-only; a member with full access can copy it ((useAsMine)). */
11407
+ readonly systemTemplate: import("@angular/core").InputSignal<boolean>;
11408
+ readonly aiEnabled: import("@angular/core").InputSignal<boolean>;
11409
+ readonly testSendEnabled: import("@angular/core").InputSignal<boolean>;
11410
+ readonly videosEnabled: import("@angular/core").InputSignal<boolean>;
11411
+ readonly saving: import("@angular/core").InputSignal<boolean>;
11412
+ readonly testSending: import("@angular/core").InputSignal<boolean>;
11413
+ readonly aiGenerating: import("@angular/core").InputSignal<boolean>;
11414
+ readonly videos: import("@angular/core").InputSignal<EmailTemplateVideo[]>;
11415
+ readonly videosLoading: import("@angular/core").InputSignal<boolean>;
11416
+ readonly videoDefaults: import("@angular/core").InputSignal<EmailTemplateVideoDefaults>;
11417
+ readonly videoLink: import("@angular/core").InputSignal<EmailTemplateVideoLinkFn>;
11418
+ readonly noVideosHint: import("@angular/core").InputSignal<string>;
11419
+ readonly back: import("@angular/core").OutputEmitterRef<void>;
11420
+ readonly save: import("@angular/core").OutputEmitterRef<EmailTemplateSavePayload>;
11421
+ readonly testSend: import("@angular/core").OutputEmitterRef<string>;
11422
+ readonly aiGenerate: import("@angular/core").OutputEmitterRef<string>;
11423
+ /** The Video tab was opened (the host loads the library once). */
11424
+ readonly videosRequested: import("@angular/core").OutputEmitterRef<void>;
11425
+ readonly useAsMine: import("@angular/core").OutputEmitterRef<void>;
11426
+ readonly editingId: import("@angular/core").WritableSignal<string | null>;
11427
+ readonly isNew: import("@angular/core").Signal<boolean>;
11428
+ readonly name: import("@angular/core").WritableSignal<string>;
11429
+ readonly subject: import("@angular/core").WritableSignal<string>;
11430
+ readonly category: import("@angular/core").WritableSignal<string>;
11431
+ readonly useAllYear: import("@angular/core").WritableSignal<boolean>;
11432
+ readonly activeFrom: import("@angular/core").WritableSignal<string>;
11433
+ readonly activeTo: import("@angular/core").WritableSignal<string>;
11434
+ readonly videoId: import("@angular/core").WritableSignal<string | null>;
11435
+ readonly videoTitle: import("@angular/core").WritableSignal<string | null>;
11436
+ /** The channels the template has per trigger; the rows shown are `assignmentOptions` with these applied. */
11437
+ private readonly assignmentChannels;
11438
+ private readonly assignmentState;
11439
+ readonly blocks: import("@angular/core").WritableSignal<EmailBlock[]>;
11440
+ readonly selectedBlockId: import("@angular/core").WritableSignal<string | null>;
11441
+ private initializedBlockIds;
11442
+ private savedRange;
11443
+ private readonly blockEditors;
11444
+ private readonly colorInputRef;
11445
+ /** The stored body of the template being edited; null for a new one. */
11446
+ private readonly loadedBody;
11447
+ /** True once the user really edited the message (typing, blocks, formatting, links...). */
11448
+ private readonly bodyEdited;
11449
+ private savedSnapshot;
11450
+ private dragFromPalette;
11451
+ readonly dragFromCanvas: import("@angular/core").WritableSignal<number | null>;
11452
+ readonly dragOverIndex: import("@angular/core").WritableSignal<number | null>;
11453
+ readonly currentColor: import("@angular/core").WritableSignal<string>;
11454
+ readonly blockSpacing: import("@angular/core").WritableSignal<string>;
11455
+ readonly headingKey: import("@angular/core").WritableSignal<number>;
11456
+ readonly sizeKey: import("@angular/core").WritableSignal<number>;
11457
+ readonly headingOptions: SelectOption<string>[];
11458
+ readonly fontSizeOptions: SelectOption<string>[];
11459
+ readonly spacingOptions: SelectOption<string>[];
11460
+ readonly ctaTypeOptions: SelectOption<string>[];
11461
+ readonly playbackOptions: {
11462
+ value: string;
11463
+ label: string;
11464
+ }[];
11465
+ readonly showLinkDialog: import("@angular/core").WritableSignal<boolean>;
11466
+ readonly linkUrl: import("@angular/core").WritableSignal<string>;
11467
+ readonly linkText: import("@angular/core").WritableSignal<string>;
11468
+ /** True when the link is made from selected text (the text field is then not needed). */
11469
+ readonly linkTextFromSelection: import("@angular/core").WritableSignal<boolean>;
11470
+ readonly showImageDialog: import("@angular/core").WritableSignal<boolean>;
11471
+ readonly imageUrl: import("@angular/core").WritableSignal<string>;
11472
+ readonly imageAlt: import("@angular/core").WritableSignal<string>;
11473
+ readonly showTestSendDialog: import("@angular/core").WritableSignal<boolean>;
11474
+ readonly testSendEmail: import("@angular/core").WritableSignal<string>;
11475
+ readonly showAiPanel: import("@angular/core").WritableSignal<boolean>;
11476
+ readonly aiPrompt: import("@angular/core").WritableSignal<string>;
11477
+ readonly showAssignmentConfig: import("@angular/core").WritableSignal<boolean>;
11478
+ readonly configTarget: import("@angular/core").WritableSignal<AssignmentView | null>;
11479
+ readonly configEmail: import("@angular/core").WritableSignal<boolean>;
11480
+ readonly configText: import("@angular/core").WritableSignal<boolean>;
11481
+ readonly activeEditorTab: import("@angular/core").WritableSignal<EditorTab>;
11482
+ readonly activeRightTab: import("@angular/core").WritableSignal<RightTab>;
11483
+ readonly blockSearch: import("@angular/core").WritableSignal<string>;
11484
+ readonly assignmentSearch: import("@angular/core").WritableSignal<string>;
11485
+ /** Groups the user opened or closed; the first variable group starts open. */
11486
+ private readonly toggledGroups;
11487
+ readonly videoAutoplay: import("@angular/core").WritableSignal<boolean>;
11488
+ readonly videoCaption: import("@angular/core").WritableSignal<string>;
11489
+ readonly videoCtaType: import("@angular/core").WritableSignal<string>;
11490
+ readonly videoCtaUrl: import("@angular/core").WritableSignal<string>;
11491
+ readonly videoCtaLabel: import("@angular/core").WritableSignal<string>;
11492
+ readonly videoHideReactions: import("@angular/core").WritableSignal<boolean>;
11493
+ readonly canWrite: import("@angular/core").Signal<boolean>;
11494
+ /** A system template can be copied into a new template of the company's own (full access). */
11495
+ readonly canCopySystem: import("@angular/core").Signal<boolean>;
11496
+ readonly filteredAssignments: import("@angular/core").Signal<AssignmentView[]>;
11497
+ readonly filteredBlockGroups: import("@angular/core").Signal<EmailTemplateBlockGroup[]>;
11498
+ private readonly serialized;
11499
+ readonly previewHtml: import("@angular/core").Signal<string>;
11500
+ /** Characters in the body as it is saved. */
11501
+ readonly bodyLength: import("@angular/core").Signal<number>;
11502
+ readonly bodyHasContent: import("@angular/core").Signal<boolean>;
11503
+ /** The body as the server stores it and as the editor serialises it right after loading: while they match the message has not been touched. */
11504
+ readonly bodyUnchanged: import("@angular/core").Signal<boolean>;
11505
+ readonly nameError: import("@angular/core").Signal<string>;
11506
+ readonly subjectError: import("@angular/core").Signal<string>;
11507
+ readonly bodyError: import("@angular/core").Signal<string>;
11508
+ /** "Active From / To" only apply when the template is not "all year"; when both are given To may not be before From. */
11509
+ readonly datesError: import("@angular/core").Signal<"" | "Active To must be on or after Active From.">;
11510
+ readonly canSave: import("@angular/core").Signal<boolean>;
11511
+ readonly saveDisabled: import("@angular/core").Signal<boolean>;
11512
+ private normalizedLink;
11513
+ /** Why Insert is disabled: nothing for an empty field, a message for an address that is not a link. */
11514
+ readonly linkUrlError: import("@angular/core").Signal<"" | "Enter a valid email address." | "Enter a valid web address, like https://example.com.">;
11515
+ readonly canInsertLink: import("@angular/core").Signal<boolean>;
11516
+ readonly imageUrlError: import("@angular/core").Signal<"" | "Enter a valid image address, like https://example.com/image.jpg.">;
11517
+ readonly canInsertImage: import("@angular/core").Signal<boolean>;
11518
+ readonly testEmailError: import("@angular/core").Signal<"" | "Enter a valid email address.">;
11519
+ readonly canSendTest: import("@angular/core").Signal<boolean>;
11520
+ /** The test email is the SAVED template: true when the editor holds changes that were not saved yet. */
11521
+ readonly hasUnsavedChanges: import("@angular/core").Signal<boolean>;
11522
+ readonly canGenerate: import("@angular/core").Signal<boolean>;
11523
+ constructor();
11524
+ /** A create succeeded: the template now has an id (Test Send becomes available) and what is on screen is the saved version. */
11525
+ markSaved(id?: string | null): void;
11526
+ /** The test email was sent: close the dialog. */
11527
+ closeTestSend(): void;
11528
+ /** The AI draft arrived: add it as a new block and close the AI panel. */
11529
+ addGeneratedHtml(html: string): void;
11530
+ private applyTemplate;
11531
+ onSave(): void;
11532
+ private snapshot;
11533
+ private serialize;
11534
+ private htmlToBlocks;
11535
+ private stripBlockWrappers;
11536
+ private genId;
11537
+ private editorFor;
11538
+ /** Copies each block's HTML from its DOM element (before saving / preview). */
11539
+ syncActiveBlockHtml(): void;
11540
+ private syncBlock;
11541
+ setEditorTab(tab: EditorTab): void;
11542
+ addBlock(blockKey?: string): void;
11543
+ private addBlockHtml;
11544
+ deleteBlock(index: number): void;
11545
+ duplicateBlock(index: number): void;
11546
+ moveBlock(index: number, direction: -1 | 1): void;
11547
+ onBlockInput(event: Event, block: EmailBlock): void;
11548
+ /**
11549
+ * Typing and pasting stop at the body limit: input that would push the saved HTML past it is refused before it reaches the block
11550
+ * (the disabled Save and the inline message cover everything else). Deleting is always allowed.
11551
+ */
11552
+ onBlockBeforeInput(event: InputEvent): void;
11553
+ onBlockFocus(block: EmailBlock): void;
11554
+ onPaletteDragStart(event: DragEvent, block: EmailTemplateBlock): void;
11555
+ /**
11556
+ * Starts reordering a block. Only the block's drag HANDLE is draggable (not the whole block): a draggable ancestor of a contenteditable makes
11557
+ * Firefox and Safari hijack mouse text selection inside it. `wrapper` is the block, shown as the drag image instead of the small handle.
11558
+ */
11559
+ onBlockDragStart(event: DragEvent, index: number, wrapper?: HTMLElement): void;
11560
+ onBlockDragEnd(): void;
11561
+ /** The canvas as a whole accepts only a block drag; a text drag inside a block keeps the browser's own behaviour. */
11562
+ onCanvasDragOver(event: DragEvent): void;
11563
+ onDropZoneDragOver(event: DragEvent, index: number): void;
11564
+ onDropZoneLeave(): void;
11565
+ onDropZoneDrop(event: DragEvent, insertBefore: number): void;
11566
+ /**
11567
+ * Mousedown on the toolbar keeps the selection in the editor (a button click must not blur it). A `<select>` / `<input>` must be left alone:
11568
+ * preventing the default of its mousedown stops the dropdown from opening. Those controls take focus, so they save the selection first
11569
+ * (`(mousedown)="saveSelection()"`) and the command restores it.
11570
+ */
11571
+ preventFocusLoss(event: MouseEvent): void;
11572
+ /** Remembers the selection before a control takes focus. Only a selection inside an editor block is kept; anything else clears an older one. */
11573
+ saveSelection(): void;
11574
+ /**
11575
+ * Puts the remembered selection back, once. A saved range is used only when focus really left the editor (a dialog, a select, the colour
11576
+ * picker): when the live selection is already inside a block (a toolbar button keeps it there) that live selection wins, and a range left over
11577
+ * from an earlier interaction is dropped instead of formatting the wrong block.
11578
+ */
11579
+ private restoreSelection;
11580
+ /** The HTML of the block being edited, or null when its editor is not on screen. */
11581
+ private activeBlockHtml;
11582
+ /**
11583
+ * Runs one editing command and marks the body edited only when it can have changed it: it ran over a real (non-collapsed) selection, or the
11584
+ * block's HTML is different afterwards. A click on Bold with only a caret in an untouched legacy body is not an edit. When the block cannot be
11585
+ * read the command counts as an edit, so a real edit is never lost; typing always raises `(input)`, which marks it too.
11586
+ */
11587
+ private runEditCommand;
11588
+ execCommand(cmd: string, value?: string): void;
11589
+ onHeading(value: string): void;
11590
+ onFontSize(value: string): void;
11591
+ applyFontSize(size: string): void;
11592
+ applyTextColor(color: string): void;
11593
+ openColorPicker(): void;
11594
+ applySpacing(value: string): void;
11595
+ openLinkDialog(): void;
11596
+ insertLink(): void;
11597
+ openImageDialog(): void;
11598
+ insertImageFromUrl(): void;
11599
+ openTestSendDialog(): void;
11600
+ onTestSend(): void;
11601
+ openAiPanel(): void;
11602
+ onAiGenerate(): void;
11603
+ openVideoTab(): void;
11604
+ insertVideo(video: EmailTemplateVideo): void;
11605
+ formatDuration(seconds: number | null | undefined): string;
11606
+ categoryLabel(cat: string | null | undefined): string;
11607
+ openAssignmentConfig(item: AssignmentView): void;
11608
+ saveAssignmentConfig(): void;
11609
+ isOpen(id: string): boolean;
11610
+ toggleGroup(id: string): void;
11611
+ insertBlock(block: EmailTemplateBlock): void;
11612
+ insertVariable(variable: EmailTemplateVariable): void;
11613
+ private blockHtml;
11614
+ static ɵfac: i0.ɵɵFactoryDeclaration<EmailTemplateEditorComponent, never>;
11615
+ static ɵcmp: i0.ɵɵComponentDeclaration<EmailTemplateEditorComponent, "ui-email-template-editor", never, {
11616
+ "template": {
11617
+ "alias": "template";
11618
+ "required": false;
11619
+ "isSignal": true;
11620
+ };
11621
+ "categories": {
11622
+ "alias": "categories";
11623
+ "required": false;
11624
+ "isSignal": true;
11625
+ };
11626
+ "categoryPlaceholder": {
11627
+ "alias": "categoryPlaceholder";
11628
+ "required": false;
11629
+ "isSignal": true;
11630
+ };
11631
+ "assignmentOptions": {
11632
+ "alias": "assignmentOptions";
11633
+ "required": false;
11634
+ "isSignal": true;
11635
+ };
11636
+ "featuredAssignmentKey": {
11637
+ "alias": "featuredAssignmentKey";
11638
+ "required": false;
11639
+ "isSignal": true;
11640
+ };
11641
+ "assignmentsDescription": {
11642
+ "alias": "assignmentsDescription";
11643
+ "required": false;
11644
+ "isSignal": true;
11645
+ };
11646
+ "textEnabled": {
11647
+ "alias": "textEnabled";
11648
+ "required": false;
11649
+ "isSignal": true;
11650
+ };
11651
+ "variableGroups": {
11652
+ "alias": "variableGroups";
11653
+ "required": false;
11654
+ "isSignal": true;
11655
+ };
11656
+ "blockGroups": {
11657
+ "alias": "blockGroups";
11658
+ "required": false;
11659
+ "isSignal": true;
11660
+ };
11661
+ "aiExamples": {
11662
+ "alias": "aiExamples";
11663
+ "required": false;
11664
+ "isSignal": true;
11665
+ };
11666
+ "namePlaceholder": {
11667
+ "alias": "namePlaceholder";
11668
+ "required": false;
11669
+ "isSignal": true;
11670
+ };
11671
+ "subjectPlaceholder": {
11672
+ "alias": "subjectPlaceholder";
11673
+ "required": false;
11674
+ "isSignal": true;
11675
+ };
11676
+ "backLabel": {
11677
+ "alias": "backLabel";
11678
+ "required": false;
11679
+ "isSignal": true;
11680
+ };
11681
+ "createLabel": {
11682
+ "alias": "createLabel";
11683
+ "required": false;
11684
+ "isSignal": true;
11685
+ };
11686
+ "editLabel": {
11687
+ "alias": "editLabel";
11688
+ "required": false;
11689
+ "isSignal": true;
11690
+ };
11691
+ "systemNote": {
11692
+ "alias": "systemNote";
11693
+ "required": false;
11694
+ "isSignal": true;
11695
+ };
11696
+ "readOnlyNote": {
11697
+ "alias": "readOnlyNote";
11698
+ "required": false;
11699
+ "isSignal": true;
11700
+ };
11701
+ "idPrefix": {
11702
+ "alias": "idPrefix";
11703
+ "required": false;
11704
+ "isSignal": true;
11705
+ };
11706
+ "maxBodyLength": {
11707
+ "alias": "maxBodyLength";
11708
+ "required": false;
11709
+ "isSignal": true;
11710
+ };
11711
+ "maxTextLength": {
11712
+ "alias": "maxTextLength";
11713
+ "required": false;
11714
+ "isSignal": true;
11715
+ };
11716
+ "fullAccess": {
11717
+ "alias": "fullAccess";
11718
+ "required": false;
11719
+ "isSignal": true;
11720
+ };
11721
+ "systemTemplate": {
11722
+ "alias": "systemTemplate";
11723
+ "required": false;
11724
+ "isSignal": true;
11725
+ };
11726
+ "aiEnabled": {
11727
+ "alias": "aiEnabled";
11728
+ "required": false;
11729
+ "isSignal": true;
11730
+ };
11731
+ "testSendEnabled": {
11732
+ "alias": "testSendEnabled";
11733
+ "required": false;
11734
+ "isSignal": true;
11735
+ };
11736
+ "videosEnabled": {
11737
+ "alias": "videosEnabled";
11738
+ "required": false;
11739
+ "isSignal": true;
11740
+ };
11741
+ "saving": {
11742
+ "alias": "saving";
11743
+ "required": false;
11744
+ "isSignal": true;
11745
+ };
11746
+ "testSending": {
11747
+ "alias": "testSending";
11748
+ "required": false;
11749
+ "isSignal": true;
11750
+ };
11751
+ "aiGenerating": {
11752
+ "alias": "aiGenerating";
11753
+ "required": false;
11754
+ "isSignal": true;
11755
+ };
11756
+ "videos": {
11757
+ "alias": "videos";
11758
+ "required": false;
11759
+ "isSignal": true;
11760
+ };
11761
+ "videosLoading": {
11762
+ "alias": "videosLoading";
11763
+ "required": false;
11764
+ "isSignal": true;
11765
+ };
11766
+ "videoDefaults": {
11767
+ "alias": "videoDefaults";
11768
+ "required": false;
11769
+ "isSignal": true;
11770
+ };
11771
+ "videoLink": {
11772
+ "alias": "videoLink";
11773
+ "required": false;
11774
+ "isSignal": true;
11775
+ };
11776
+ "noVideosHint": {
11777
+ "alias": "noVideosHint";
11778
+ "required": false;
11779
+ "isSignal": true;
11780
+ };
11781
+ }, {
11782
+ "back": "back";
11783
+ "save": "save";
11784
+ "testSend": "testSend";
11785
+ "aiGenerate": "aiGenerate";
11786
+ "videosRequested": "videosRequested";
11787
+ "useAsMine": "useAsMine";
11788
+ }, never, ["[uiSubjectAction]"], true, never>;
11789
+ }
11790
+ /** The text a new template's first block starts with: it is a prompt, not content, so a template still holding only this has no message. */
11791
+ export declare const EMAIL_TEMPLATE_START_TEXT = "Start writing your email...";
11792
+ /** Longest message (serialised HTML) the editor accepts by default; hosts pass their own server limit through `maxBodyLength`. */
11793
+ export declare const EMAIL_TEMPLATE_MAX_BODY = 9000;
11794
+ /** Longest name / subject accepted by default. */
11795
+ export declare const EMAIL_TEMPLATE_MAX_TEXT = 255;
11796
+ /** Longest single email address (server rule). */
11797
+ export declare const MAX_EMAIL_ADDRESS_LENGTH = 254;
11798
+ /** True for a plausible single email address of at most 254 characters. */
11799
+ export declare const isValidEmailAddress: (address: string) => boolean;
11800
+ /** 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`. */
11801
+ export declare function isWebUrl(raw: string): boolean;
11802
+ /** Escapes text for HTML text and double-quoted attribute values. */
11803
+ export declare function escapeHtml(value: string): string;
11804
+ /** 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. */
11805
+ export declare function blockHasContent(html: string): boolean;
11806
+ /**
11807
+ * The drag payload of a block (a palette item or a block being reordered). A custom type, never `text/plain`: a text drop onto a block's
11808
+ * contenteditable would insert the payload into the message in every engine. Firefox only starts a drag when some data is set (a custom
11809
+ * type counts); Safari lets a drop zone read `types` during dragover but not `getData`.
11810
+ */
11811
+ export declare const EMAIL_BLOCK_DRAG_TYPE = "application/x-tpl-block";
11812
+ /** True when the drag carries a block payload (`types` is an array in current engines, a DOMStringList in very old ones). */
11813
+ export declare function isBlockDrag(event: {
11814
+ dataTransfer?: {
11815
+ types?: ArrayLike<string> | readonly string[];
11816
+ } | null;
11817
+ }): boolean;
11818
+ /** True when a node sits inside one of the editor's blocks (the only place a saved selection may point). */
11819
+ export declare function insideEditorBlock(node: Node | null | undefined): boolean;
11820
+ /** `m:ss` for a duration in seconds; '' when there is none. */
11821
+ export declare function formatVideoDuration(seconds: number | null | undefined): string;
11822
+ /** `follow-up` → `Follow Up`. */
11823
+ export declare function formatCategoryLabel(cat: string | null | undefined): string;
11824
+ /** The email block a video becomes: a thumbnail with a play overlay, linked to `link`, with the title and a caption. Every user value is escaped. */
11825
+ export declare function buildVideoBlockHtml(video: EmailTemplateVideo, link: string, caption: string, origin: string): string;
11826
+ /** The content blocks every host gets unless it passes its own `blockGroups`. */
11827
+ export declare const DEFAULT_EMAIL_TEMPLATE_BLOCK_GROUPS: EmailTemplateBlockGroup[];
11828
+ /** Generic merge variables; a host passes its own `variableGroups` for the data its sender can fill in. */
11829
+ export declare const DEFAULT_EMAIL_TEMPLATE_VARIABLE_GROUPS: {
11830
+ name: string;
11831
+ variables: {
11832
+ key: string;
11833
+ label: string;
11834
+ example: string;
11835
+ }[];
11836
+ }[];
10973
11837
  export type { CalendarDay, CalendarRange, CalendarViewMode };