@forgeroute/ui 0.3.11 → 0.3.13

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from "@angular/core";
2
- import { ChangeDetectorRef, InjectionToken, OnDestroy, OnInit, Signal, WritableSignal } from "@angular/core";
2
+ import { ChangeDetectorRef, EnvironmentProviders, InjectionToken, OnDestroy, OnInit, Signal, TemplateRef, WritableSignal } from "@angular/core";
3
3
  import { AbstractControl, ControlValueAccessor, FormControl, ValidationErrors, Validator } from "@angular/forms";
4
4
  import { Observable, Subject } from "rxjs";
5
5
  import { HttpClient, HttpInterceptorFn } from "@angular/common/http";
@@ -145,7 +145,7 @@ export interface ColumnDef {
145
145
  overdueThreshold?: number;
146
146
  /**
147
147
  * Maps a row to a progress bar. Return null to render a plain '—' (e.g. nothing to track).
148
- * `label` is the text beside the bar (e.g. "3/5"); `tooltip` is the hover text. Leave `variant` out and the bar colors itself by completion (red / amber / blue / green).
148
+ * `label` is the text beside the bar (e.g. "3/5"); `tooltip` is the hover text. `ariaLabel` is the bar's accessible name (e.g. the row's name + " cap progress"); leave it out and the bar is named by its `label` text — which, with only "80%" to go on, tells a screen-reader user nothing about WHICH row it belongs to, so set it whenever several rows show a bar. Leave `variant` out and the bar colors itself by completion (red / amber / blue / green).
149
149
  */
150
150
  progressFn?: (item: any) => {
151
151
  value: number;
@@ -153,6 +153,7 @@ export interface ColumnDef {
153
153
  label?: string;
154
154
  variant?: 'blue' | 'green' | 'amber' | 'red' | 'slate';
155
155
  tooltip?: string;
156
+ ariaLabel?: string;
156
157
  } | null;
157
158
  /** Key in item holding the tags array. Defaults to col.key.
158
159
  * Expected shape: { name: string; variant?: BadgeVariant }[] */
@@ -237,6 +238,15 @@ export interface SavedView {
237
238
  */
238
239
  export type BulkUpdateOption<T = any> = string | {
239
240
  field: string;
241
+ /**
242
+ * Field changes (`{ field, label }`): the bulk bar button text instead of "Change <column label>", e.g. `{ field: 'state', label: 'Archive / Restore' }`.
243
+ * It is also the inline select's accessible name, the default confirm's title and confirm button, and the default confirm message reads
244
+ * "<label>: <option> for N items?" (instead of "Change <column> to <option> for N items?"). Host `confirm` copy still wins; its
245
+ * `ctx.columnLabel` stays the column's label. Trimmed; blank = ignored. Ignored for the reserved `delete` key. Omit it and nothing changes.
246
+ *
247
+ * { field: 'state', label: 'Archive / Restore' }
248
+ */
249
+ label?: string;
240
250
  exclude?: string[];
241
251
  allowedFrom?: Record<string, string[]>;
242
252
  /**
@@ -312,6 +322,8 @@ export interface BulkActionDef {
312
322
  type: string;
313
323
  /** Button label shown in the bulk bar. */
314
324
  label: string;
325
+ /** True when `label` came from the entry's custom `label` (the default confirm message then reads "<label>: <option> for N items?"). Only set then. */
326
+ customLabel?: boolean;
315
327
  /** Button style. Default: 'secondary'. Use 'danger' for destructive actions. */
316
328
  variant?: 'secondary' | 'danger';
317
329
  /** Values left out of this field's bulk select (from a `{ field, exclude }` bulkUpdateOptions entry). */
@@ -868,8 +880,12 @@ export declare function isoKey(p: {
868
880
  export declare function parseTime(value: string | null | undefined): number | null;
869
881
  /** "9:00 AM" / "12:30 PM" from an `HH:mm` value (returned unchanged when it cannot be parsed; '' for null/undefined/non-strings). */
870
882
  export declare function formatTime(value: string | null | undefined): string;
871
- export interface SelectOption {
872
- value: string;
883
+ /**
884
+ * One choice. `V` defaults to `string`, so existing `SelectOption[]` code is unchanged; `ui-select` itself accepts
885
+ * `SelectOption<string | number>[]` (numeric values for `valueType="number"`).
886
+ */
887
+ export interface SelectOption<V extends string | number = string> {
888
+ value: V;
873
889
  label: string;
874
890
  }
875
891
  export declare class SelectComponent implements ControlValueAccessor, Validator, UiFieldValidity {
@@ -877,7 +893,9 @@ export declare class SelectComponent implements ControlValueAccessor, Validator,
877
893
  /** Accessible name for a select with no visible label (e.g. an inline row or table-cell select). */
878
894
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
879
895
  readonly placeholder: import("@angular/core").InputSignal<string>;
880
- readonly options: import("@angular/core").InputSignal<SelectOption[]>;
896
+ readonly options: import("@angular/core").InputSignal<SelectOption<string | number>[]>;
897
+ /** What the control value is: `'string'` (default, numeric option values become strings) or `'number'` (`number | null`). */
898
+ readonly valueType: import("@angular/core").InputSignal<"string" | "number">;
881
899
  readonly required: import("@angular/core").InputSignal<boolean>;
882
900
  readonly hint: import("@angular/core").InputSignal<string>;
883
901
  readonly error: import("@angular/core").InputSignal<string>;
@@ -888,6 +906,8 @@ export declare class SelectComponent implements ControlValueAccessor, Validator,
888
906
  private readonly _cvaDisabled;
889
907
  protected readonly disabled: import("@angular/core").Signal<boolean>;
890
908
  protected readonly _value: import("@angular/core").WritableSignal<string>;
909
+ /** Index of the first option matching the written / chosen value, or -1 (so only ONE option is selected even with duplicate keys). */
910
+ protected readonly _selectedIndex: import("@angular/core").Signal<number>;
891
911
  private readonly _reveal;
892
912
  private readonly _issue;
893
913
  /** True while the value breaks a built-in rule or the host passed an `[error]`. */
@@ -905,12 +925,19 @@ export declare class SelectComponent implements ControlValueAccessor, Validator,
905
925
  registerOnValidatorChange(fn: () => void): void;
906
926
  protected readonly _selectId: string;
907
927
  private _onChange;
928
+ private _warned;
929
+ /** The comparison form of a key: in number mode a numeric text ('21.0', ' 21 ') becomes the canonical number text ('21'). */
930
+ private _norm;
931
+ protected _key(opt: SelectOption<string | number>): string;
932
+ private static _toKey;
908
933
  private _onTouched;
909
- writeValue(val: string): void;
910
- registerOnChange(fn: (v: string) => void): void;
934
+ writeValue(val: string | number | null | undefined): void;
935
+ registerOnChange(fn: (v: string | number | null) => void): void;
911
936
  registerOnTouched(fn: () => void): void;
912
937
  setDisabledState(d: boolean): void;
913
938
  protected onChange(event: Event): void;
939
+ /** The control value for the chosen native option value. */
940
+ private _emitted;
914
941
  protected onBlur(): void;
915
942
  private _check;
916
943
  static ɵfac: i0.ɵɵFactoryDeclaration<SelectComponent, never>;
@@ -935,6 +962,11 @@ export declare class SelectComponent implements ControlValueAccessor, Validator,
935
962
  "required": false;
936
963
  "isSignal": true;
937
964
  };
965
+ "valueType": {
966
+ "alias": "valueType";
967
+ "required": false;
968
+ "isSignal": true;
969
+ };
938
970
  "required": {
939
971
  "alias": "required";
940
972
  "required": false;
@@ -1290,7 +1322,7 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1290
1322
  /**
1291
1323
  * Derives internal action definitions from the `bulkUpdateOptions` string array.
1292
1324
  * 'delete' → built-in danger action with table-managed confirm dialog.
1293
- * Everything else → matched to a column: label = "Change {col.label}", options from column config.
1325
+ * Everything else → matched to a column: label = the entry's `label` when given, else "Change {col.label}"; options from column config.
1294
1326
  */
1295
1327
  protected readonly _bulkDefs: Signal<BulkActionDef[]>;
1296
1328
  /** Options for a field-based bulk action: the field's options minus the entry's `exclude` values. */
@@ -1788,14 +1820,33 @@ export declare class ModalComponent implements OnInit, OnDestroy {
1788
1820
  }, never, ["*", "[modal-footer-left]", "[modal-footer-right]"], true, never>;
1789
1821
  }
1790
1822
  export type ToastType = 'success' | 'error' | 'info';
1791
- export declare class ToastService {
1823
+ export declare class ToastService implements OnDestroy {
1792
1824
  readonly message: import("@angular/core").WritableSignal<string | null>;
1793
1825
  readonly type: import("@angular/core").WritableSignal<ToastType>;
1826
+ /**
1827
+ * Opt-in: every `<ui-toast>` shows a close button ("Dismiss notification"). Default `false`: toasts look exactly as before.
1828
+ * Set once per app with `provideForgerouteToast({ dismissible: true })`, or per outlet with `<ui-toast [dismissible]="true" />`
1829
+ * (the input wins). Escape, pause on hover / focus and `dismiss()` work either way.
1830
+ */
1831
+ readonly dismissible: import("@angular/core").WritableSignal<boolean>;
1794
1832
  private _timer;
1833
+ private _deadline;
1834
+ private _remaining;
1835
+ private _paused;
1795
1836
  show(msg: string, duration?: number): void;
1796
1837
  /** Neutral (blue) toast for a result that is neither a success nor a failure, e.g. "No changes to save.". */
1797
1838
  info(msg: string, duration?: number): void;
1798
1839
  error(msg: string, duration?: number): void;
1840
+ /** Removes the current toast now (close button / Escape). Safe when nothing is showing. */
1841
+ dismiss(): void;
1842
+ /** Holds the countdown while the pointer or keyboard focus is on the toast (WCAG 2.2.1). No-op when nothing is counting down. */
1843
+ pause(): void;
1844
+ /** Continues after `pause()`; the toast gets at least 1000ms more so it can be read. No-op when not paused. */
1845
+ resume(): void;
1846
+ /** The root injector is destroyed (app teardown, TestBed reset): no timer may outlive it. */
1847
+ ngOnDestroy(): void;
1848
+ private _clearTimer;
1849
+ private _start;
1799
1850
  private _display;
1800
1851
  static ɵfac: i0.ɵɵFactoryDeclaration<ToastService, never>;
1801
1852
  static ɵprov: i0.ɵɵInjectableDeclaration<any>;
@@ -1806,6 +1857,13 @@ export declare class ToastComponent {
1806
1857
  private readonly _id;
1807
1858
  /** True only for the earliest live instance, so exactly one toast (and one live region) renders. */
1808
1859
  protected readonly isHost: import("@angular/core").Signal<boolean>;
1860
+ /**
1861
+ * Opt-in close button. `null` (default) = follow `ToastService.dismissible` (itself `false` unless the app opted in with
1862
+ * `provideForgerouteToast({ dismissible: true })`); `true` / `false` forces it for this outlet. Without it the toast has no
1863
+ * control and looks exactly as it always did.
1864
+ */
1865
+ readonly dismissible: import("@angular/core").InputSignal<boolean | null>;
1866
+ protected readonly showClose: import("@angular/core").Signal<boolean>;
1809
1867
  /**
1810
1868
  * Message that was already on screen when this instance took over from a destroyed host. While it is
1811
1869
  * unchanged the toast renders without the entry animation and without live-region roles, so the
@@ -1814,9 +1872,24 @@ export declare class ToastComponent {
1814
1872
  private readonly _adoptedMessage;
1815
1873
  protected readonly adopted: import("@angular/core").Signal<boolean>;
1816
1874
  private _wasWaiting;
1875
+ private _hover;
1876
+ private _focus;
1877
+ /** Escape on a focused toast dismisses only the toast: a panel or dialog behind it that also listens for Escape stays open. */
1878
+ protected onEscape(e: Event): void;
1879
+ protected setHover(on: boolean): void;
1880
+ protected setFocus(on: boolean): void;
1881
+ protected onFocusOut(e: FocusEvent): void;
1882
+ /** The countdown holds while the pointer or focus is on the toast and continues when both have left. */
1883
+ private _syncPause;
1817
1884
  constructor();
1818
1885
  static ɵfac: i0.ɵɵFactoryDeclaration<ToastComponent, never>;
1819
- static ɵcmp: i0.ɵɵComponentDeclaration<ToastComponent, "ui-toast", never, {}, {}, never, never, true, never>;
1886
+ static ɵcmp: i0.ɵɵComponentDeclaration<ToastComponent, "ui-toast", never, {
1887
+ "dismissible": {
1888
+ "alias": "dismissible";
1889
+ "required": false;
1890
+ "isSignal": true;
1891
+ };
1892
+ }, {}, never, never, true, never>;
1820
1893
  }
1821
1894
  export type ProgressBarVariant = 'blue' | 'green' | 'amber' | 'red' | 'slate';
1822
1895
  /**
@@ -1826,7 +1899,10 @@ export type ProgressBarVariant = 'blue' | 'green' | 'amber' | 'red' | 'slate';
1826
1899
  *
1827
1900
  * The fill color is built in and follows completion, in four bands: red up to 25%, amber up to 50%, blue up to 75%,
1828
1901
  * green above 75% (full = green). Nothing needs to be passed. `variant` is an optional override for a bar whose
1829
- * color means something else (for example an overdue status) — when it is set the color stays fixed.
1902
+ * color means something else (for example an overdue status, or a category-share bar) — when it is set the color stays fixed
1903
+ * at every value. For a share-of-largest bar pass `[value]="count" [max]="largestCount" variant="blue"`.
1904
+ * The width is set inline exactly as before (`style="width: N%;"` and nothing else, so existing consumers and e2e keep working); the transition is off for
1905
+ * `prefers-reduced-motion` and the bar stays visible in forced-colors mode. Invalid numbers (NaN, negative max) draw an empty bar.
1830
1906
  */
1831
1907
  export declare class ProgressBarComponent {
1832
1908
  /** Completed amount. */
@@ -1840,6 +1916,12 @@ export declare class ProgressBarComponent {
1840
1916
  /** Hover / focus tooltip (library `uiTooltip`); `\n` starts a new line. */
1841
1917
  readonly tooltip: import("@angular/core").InputSignal<string>;
1842
1918
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
1919
+ /** Id of an element that names the bar (wins over `ariaLabel` / `label`). */
1920
+ readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
1921
+ /** Human text for `aria-valuetext`, e.g. "12 documents, 40% of the largest category". Empty = not set. */
1922
+ readonly valueText: import("@angular/core").InputSignal<string>;
1923
+ /** `max` made safe: NaN / negative / infinite become 0 (empty bar, no division by zero). */
1924
+ protected readonly safeMax: import("@angular/core").Signal<number>;
1843
1925
  protected readonly clampedValue: import("@angular/core").Signal<number>;
1844
1926
  /** Fill width in %: the rounded share, but never less than MIN_VISIBLE_PERCENT while the value is above 0 (1 of 300 would round to 0% and look empty). */
1845
1927
  protected readonly percent: import("@angular/core").Signal<number>;
@@ -1877,6 +1959,117 @@ export declare class ProgressBarComponent {
1877
1959
  "required": false;
1878
1960
  "isSignal": true;
1879
1961
  };
1962
+ "ariaLabelledby": {
1963
+ "alias": "ariaLabelledby";
1964
+ "required": false;
1965
+ "isSignal": true;
1966
+ };
1967
+ "valueText": {
1968
+ "alias": "valueText";
1969
+ "required": false;
1970
+ "isSignal": true;
1971
+ };
1972
+ }, {}, never, never, true, never>;
1973
+ }
1974
+ export type ProgressRingVariant = 'blue' | 'green' | 'amber' | 'red' | 'slate' | 'sky';
1975
+ /**
1976
+ * Circular progress ring with the rounded percentage (or a custom `label`) in the middle.
1977
+ * Pass `value` and `max` (default 100); the arc is clamped to 0–100% and starts at 12 o'clock. A value of 0 draws no arc; invalid numbers
1978
+ * (NaN, a negative or infinite max) draw an empty ring.
1979
+ *
1980
+ * The arc color follows completion exactly like `ui-progress-bar` (red up to 25%, amber up to 50%, blue up to 75%, green above 75%);
1981
+ * `variant` fixes it for a ring whose color means something else (e.g. one ring per metric). The ring is a `role="progressbar"`
1982
+ * (name it with `ariaLabel` or `ariaLabelledby`; `valueText` adds a human reading such as "$200K of $300K"). The arc animates unless
1983
+ * `prefers-reduced-motion` is set and stays visible in forced-colors mode.
1984
+ *
1985
+ * <ui-progress-ring [value]="67" ariaLabel="Gross income progress" variant="green" />
1986
+ * <ui-progress-ring [value]="done" [max]="total" [size]="72" [strokeWidth]="6" [label]="done + '/' + total" ariaLabel="Checklist" />
1987
+ */
1988
+ export declare class ProgressRingComponent {
1989
+ /** Completed amount. */
1990
+ readonly value: import("@angular/core").InputSignal<number>;
1991
+ /** Total amount (default 100, so `value` is a percentage); 0 renders an empty ring. */
1992
+ readonly max: import("@angular/core").InputSignal<number>;
1993
+ /** Diameter in px. */
1994
+ readonly size: import("@angular/core").InputSignal<number>;
1995
+ /** Ring thickness in px. */
1996
+ readonly strokeWidth: import("@angular/core").InputSignal<number>;
1997
+ /** Optional fixed arc color. Leave it out and the color follows completion (red / amber / blue / green). */
1998
+ readonly variant: import("@angular/core").InputSignal<ProgressRingVariant | null | undefined>;
1999
+ /** Text in the middle instead of the rounded percentage, e.g. "3/5". */
2000
+ readonly label: import("@angular/core").InputSignal<string>;
2001
+ /** `false` draws the ring without any text in the middle. */
2002
+ readonly showValue: import("@angular/core").InputSignal<boolean>;
2003
+ readonly ariaLabel: import("@angular/core").InputSignal<string>;
2004
+ /** Id of an element that names the ring (wins over `ariaLabel`). */
2005
+ readonly ariaLabelledby: import("@angular/core").InputSignal<string>;
2006
+ /** Human text for `aria-valuetext`, e.g. "$200K of $300K". Empty = not set. */
2007
+ readonly valueText: import("@angular/core").InputSignal<string>;
2008
+ /** `max` made safe: NaN / negative / infinite become 0 (empty ring, no division by zero). */
2009
+ protected readonly safeMax: import("@angular/core").Signal<number>;
2010
+ protected readonly clampedValue: import("@angular/core").Signal<number>;
2011
+ /** Exact completion in % (0–100). */
2012
+ private readonly exact;
2013
+ /** Rounded completion in %, shown in the middle. */
2014
+ protected readonly percent: import("@angular/core").Signal<number>;
2015
+ protected readonly center: import("@angular/core").Signal<number>;
2016
+ protected readonly radius: import("@angular/core").Signal<number>;
2017
+ protected readonly circumference: import("@angular/core").Signal<number>;
2018
+ protected readonly dashOffset: import("@angular/core").Signal<number>;
2019
+ /** The override when given, else the completion band: up to 25% red, up to 50% amber, up to 75% blue, above 75% green. */
2020
+ protected readonly effectiveVariant: import("@angular/core").Signal<ProgressRingVariant>;
2021
+ static ɵfac: i0.ɵɵFactoryDeclaration<ProgressRingComponent, never>;
2022
+ static ɵcmp: i0.ɵɵComponentDeclaration<ProgressRingComponent, "ui-progress-ring", never, {
2023
+ "value": {
2024
+ "alias": "value";
2025
+ "required": false;
2026
+ "isSignal": true;
2027
+ };
2028
+ "max": {
2029
+ "alias": "max";
2030
+ "required": false;
2031
+ "isSignal": true;
2032
+ };
2033
+ "size": {
2034
+ "alias": "size";
2035
+ "required": false;
2036
+ "isSignal": true;
2037
+ };
2038
+ "strokeWidth": {
2039
+ "alias": "strokeWidth";
2040
+ "required": false;
2041
+ "isSignal": true;
2042
+ };
2043
+ "variant": {
2044
+ "alias": "variant";
2045
+ "required": false;
2046
+ "isSignal": true;
2047
+ };
2048
+ "label": {
2049
+ "alias": "label";
2050
+ "required": false;
2051
+ "isSignal": true;
2052
+ };
2053
+ "showValue": {
2054
+ "alias": "showValue";
2055
+ "required": false;
2056
+ "isSignal": true;
2057
+ };
2058
+ "ariaLabel": {
2059
+ "alias": "ariaLabel";
2060
+ "required": false;
2061
+ "isSignal": true;
2062
+ };
2063
+ "ariaLabelledby": {
2064
+ "alias": "ariaLabelledby";
2065
+ "required": false;
2066
+ "isSignal": true;
2067
+ };
2068
+ "valueText": {
2069
+ "alias": "valueText";
2070
+ "required": false;
2071
+ "isSignal": true;
2072
+ };
1880
2073
  }, {}, never, never, true, never>;
1881
2074
  }
1882
2075
  /**
@@ -2223,6 +2416,7 @@ export declare class ToggleComponent implements ControlValueAccessor {
2223
2416
  private readonly _cvaDisabled;
2224
2417
  protected readonly disabled: import("@angular/core").Signal<boolean>;
2225
2418
  private readonly _id;
2419
+ protected readonly _inputId: string;
2226
2420
  protected readonly _labelId: string;
2227
2421
  protected readonly _hintId: string;
2228
2422
  private _onChange;
@@ -2264,6 +2458,136 @@ export declare class ToggleComponent implements ControlValueAccessor {
2264
2458
  "checked": "checkedChange";
2265
2459
  }, never, never, true, never>;
2266
2460
  }
2461
+ /** One step of a `ui-stepper`. `key` must be unique within the list. */
2462
+ export interface StepperStep {
2463
+ key: string;
2464
+ label: string;
2465
+ /** Small grey text under the label. */
2466
+ description?: string;
2467
+ /** Shows "(optional)" after the label and announces "optional". */
2468
+ optional?: boolean;
2469
+ /** Never selectable and announced "disabled". Does not change the step's state (complete / current / upcoming). */
2470
+ disabled?: boolean;
2471
+ /** `true` or a message marks the step as failed ("!" indicator, red). A message string is shown under the label and announced. */
2472
+ error?: boolean | string;
2473
+ }
2474
+ export type StepperOrientation = 'horizontal' | 'vertical';
2475
+ /**
2476
+ * Step indicator for wizards, onboarding flows and progress timelines. The state of every step comes from the order of
2477
+ * `steps` and the key in `current`: steps before it are complete, it is current, steps after it are upcoming.
2478
+ *
2479
+ * <!-- Wizard header: completed steps are buttons, so the user can go back (never skip ahead) -->
2480
+ * <ui-stepper [steps]="steps" [clickable]="true" [(current)]="step" />
2481
+ *
2482
+ * <!-- Read-only progress indicator (steps are plain text; the host moves `current`) -->
2483
+ * <ui-stepper ariaLabel="Porting steps" size="sm" [steps]="steps" [current]="step()" />
2484
+ *
2485
+ * <!-- Vertical timeline with descriptions; free navigation to any enabled step -->
2486
+ * <ui-stepper orientation="vertical" [steps]="steps" [clickable]="true" [linear]="false" [(current)]="step" />
2487
+ *
2488
+ * <!-- Error state: `error: true` or a message on a step -->
2489
+ * steps = [{ key: 'info', label: 'Info' }, { key: 'pay', label: 'Payment', error: 'Card was declined' }, ...]
2490
+ *
2491
+ * `current` is two-way (`[(current)]`). An empty or unknown key means there is no current step: every step is upcoming
2492
+ * (and with `linear`, none is selectable). `error` changes the indicator, not the state or `aria-current`.
2493
+ *
2494
+ * Interaction (`clickable`): a selectable step is a native `<button>` (Tab / Enter / Space, visible focus ring). With
2495
+ * `linear` (default) only complete steps and the current step are selectable, so the user can go back but not skip
2496
+ * ahead; with `[linear]="false"` every non-disabled step is. Activating a selectable step other than the current one
2497
+ * sets `current` (so `[(current)]` follows) and emits `stepClick` with its key; activating the current step, or an
2498
+ * unselectable one, does nothing. Without `clickable` there are no buttons or tab stops at all.
2499
+ *
2500
+ * Accessibility: an ordered list named by `ariaLabel`; the current step has `aria-current="step"`. Each step's name is
2501
+ * "<label>, step N of M, completed | current step | not started" (+ ", optional", ", error: <message>", ", disabled"),
2502
+ * built from visually hidden text so it is announced in the list even when the step is not a button. Indicators and
2503
+ * connectors are decorative. A description is referenced with `aria-describedby`.
2504
+ *
2505
+ * Layout: `orientation` horizontal (row with connector lines that fill as steps complete) or vertical (column, connector
2506
+ * on the left). Long labels wrap. At <= 480px a horizontal stepper with `labelsOnSmallScreens="current"` (default) shows
2507
+ * only the current step's label (the others stay in the accessible name); `"all"` keeps them. Touch devices and phones
2508
+ * get >= 44px tall step buttons. The connector transition honours `prefers-reduced-motion`.
2509
+ */
2510
+ export declare class StepperComponent {
2511
+ /** The steps in order. Keys must be unique. */
2512
+ readonly steps: import("@angular/core").InputSignal<StepperStep[]>;
2513
+ /** Key of the current step. Two-way: `[(current)]`. Empty / unknown = no current step (all upcoming). */
2514
+ readonly current: import("@angular/core").ModelSignal<string>;
2515
+ readonly orientation: import("@angular/core").InputSignal<StepperOrientation>;
2516
+ /** `true`: selectable steps are buttons. `false` (default): purely indicative. */
2517
+ readonly clickable: import("@angular/core").InputSignal<boolean>;
2518
+ /** With `clickable`: `true` (default) = only complete steps and the current step are selectable; `false` = any enabled step. */
2519
+ readonly linear: import("@angular/core").InputSignal<boolean>;
2520
+ readonly size: import("@angular/core").InputSignal<"sm" | "md">;
2521
+ /** Accessible name of the list. */
2522
+ readonly ariaLabel: import("@angular/core").InputSignal<string>;
2523
+ /** Horizontal, <= 480px: show only the current step's label (`'current'`, default) or every label (`'all'`). */
2524
+ readonly labelsOnSmallScreens: import("@angular/core").InputSignal<"all" | "current">;
2525
+ /** Emits the key of an activated selectable step that was not already current (after `current` was updated). */
2526
+ readonly stepClick: import("@angular/core").OutputEmitterRef<string>;
2527
+ private readonly _id;
2528
+ protected readonly rows: import("@angular/core").Signal<{
2529
+ step: StepperStep;
2530
+ index: number;
2531
+ state: "complete" | "current" | "upcoming";
2532
+ error: boolean;
2533
+ errorMessage: string;
2534
+ selectable: boolean;
2535
+ srText: string;
2536
+ last: boolean;
2537
+ descId: string;
2538
+ }[]>;
2539
+ protected activate(r: {
2540
+ step: StepperStep;
2541
+ selectable: boolean;
2542
+ state: string;
2543
+ }): void;
2544
+ static ɵfac: i0.ɵɵFactoryDeclaration<StepperComponent, never>;
2545
+ static ɵcmp: i0.ɵɵComponentDeclaration<StepperComponent, "ui-stepper", never, {
2546
+ "steps": {
2547
+ "alias": "steps";
2548
+ "required": true;
2549
+ "isSignal": true;
2550
+ };
2551
+ "current": {
2552
+ "alias": "current";
2553
+ "required": false;
2554
+ "isSignal": true;
2555
+ };
2556
+ "orientation": {
2557
+ "alias": "orientation";
2558
+ "required": false;
2559
+ "isSignal": true;
2560
+ };
2561
+ "clickable": {
2562
+ "alias": "clickable";
2563
+ "required": false;
2564
+ "isSignal": true;
2565
+ };
2566
+ "linear": {
2567
+ "alias": "linear";
2568
+ "required": false;
2569
+ "isSignal": true;
2570
+ };
2571
+ "size": {
2572
+ "alias": "size";
2573
+ "required": false;
2574
+ "isSignal": true;
2575
+ };
2576
+ "ariaLabel": {
2577
+ "alias": "ariaLabel";
2578
+ "required": false;
2579
+ "isSignal": true;
2580
+ };
2581
+ "labelsOnSmallScreens": {
2582
+ "alias": "labelsOnSmallScreens";
2583
+ "required": false;
2584
+ "isSignal": true;
2585
+ };
2586
+ }, {
2587
+ "current": "currentChange";
2588
+ "stepClick": "stepClick";
2589
+ }, never, never, true, never>;
2590
+ }
2267
2591
  /** One weekday of the schedule. `open` / `close` are `HH:mm`; `open2` / `close2` are the optional second shift (split shift). */
2268
2592
  export interface BusinessHoursDay {
2269
2593
  /** 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun' */
@@ -2310,6 +2634,8 @@ export declare function businessTimeOptions(step?: BusinessHoursStep, hour24?: b
2310
2634
  * the opening time ("Monday: closing time must be after opening time."); with a second shift it must start after the first ends and end after it starts.
2311
2635
  * Overnight ranges are not supported (a single `open < close` window per shift), the same as call routing.
2312
2636
  *
2637
+ * Test ids: every row is `bh-day-<day>` (mon … sun); inside it `bh-enabled-<day>` (the open/closed checkbox), `bh-open-<day>`, `bh-close-<day>`, and with a second shift `bh-open2-<day>`, `bh-close2-<day>`; the buttons keep `bh-add-shift`, `bh-remove-shift` (scope them with the row) and `bh-copy-weekdays`.
2638
+ *
2313
2639
  * Accessibility: a `role="group"` named by `label`; every select has its own name ("Monday opens", "Monday closes", "Monday second shift opens" …);
2314
2640
  * errors are announced (`role="alert"`) and linked with `aria-describedby`.
2315
2641
  */
@@ -2458,7 +2784,7 @@ export declare class RadioGroupComponent implements ControlValueAccessor, Valida
2458
2784
  readonly error: import("@angular/core").InputSignal<string>;
2459
2785
  /** Reveal the built-in error messages now (e.g. after a failed Save click), without waiting for blur. */
2460
2786
  readonly showErrors: import("@angular/core").InputSignal<boolean>;
2461
- readonly orientation: import("@angular/core").InputSignal<"vertical" | "horizontal">;
2787
+ readonly orientation: import("@angular/core").InputSignal<"horizontal" | "vertical">;
2462
2788
  /** Native `name` shared by the radios; a unique one is generated when omitted. */
2463
2789
  readonly name: import("@angular/core").InputSignal<string>;
2464
2790
  protected readonly _disabledInput: import("@angular/core").InputSignal<boolean>;
@@ -2739,6 +3065,20 @@ export declare class ButtonComponent {
2739
3065
  readonly routerLink: import("@angular/core").InputSignal<string | readonly unknown[] | null>;
2740
3066
  /** Query params of the `routerLink` (e.g. `{ section: 'deal-defaults' }`). */
2741
3067
  readonly queryParams: import("@angular/core").InputSignal<Record<string, unknown> | null>;
3068
+ /**
3069
+ * Renders a plain `<a href>` instead of a button: for `mailto:`, `tel:`, `sms:`, `https://…` and relative URLs (use with
3070
+ * `variant="link"` for an inline text link). The browser handles the click, so middle-click, copy-link and open-in-new-tab work and
3071
+ * the host needs no click handler. Other schemes (`javascript:`, `data:`, …) are never rendered as a link: the component stays a
3072
+ * button. `routerLink` wins when both are set. While `disabled` or `loading` the link has no `href` (it only looks disabled).
3073
+ */
3074
+ readonly href: import("@angular/core").InputSignal<string | null>;
3075
+ /** Where to open the `href` (`_blank` = a new tab). */
3076
+ readonly target: import("@angular/core").InputSignal<"_blank" | "_self" | "_parent" | "_top" | null>;
3077
+ /** `rel` of the `href` link. A `_blank` target always gets `noopener noreferrer` added. */
3078
+ readonly rel: import("@angular/core").InputSignal<string | null>;
3079
+ /** The href when it is allowed (http, https, mailto, tel, sms or a relative URL), else null. */
3080
+ protected readonly _safeHref: import("@angular/core").Signal<string | null>;
3081
+ protected readonly _rel: import("@angular/core").Signal<string | null>;
2742
3082
  readonly color: import("@angular/core").InputSignal<string | undefined>;
2743
3083
  readonly active: import("@angular/core").InputSignal<boolean>;
2744
3084
  readonly size: import("@angular/core").InputSignal<"sm" | "md" | "xs" | "lg">;
@@ -2770,6 +3110,21 @@ export declare class ButtonComponent {
2770
3110
  "required": false;
2771
3111
  "isSignal": true;
2772
3112
  };
3113
+ "href": {
3114
+ "alias": "href";
3115
+ "required": false;
3116
+ "isSignal": true;
3117
+ };
3118
+ "target": {
3119
+ "alias": "target";
3120
+ "required": false;
3121
+ "isSignal": true;
3122
+ };
3123
+ "rel": {
3124
+ "alias": "rel";
3125
+ "required": false;
3126
+ "isSignal": true;
3127
+ };
2773
3128
  "color": {
2774
3129
  "alias": "color";
2775
3130
  "required": false;
@@ -3622,6 +3977,8 @@ export interface SignerDocLike {
3622
3977
  id: string;
3623
3978
  title: string;
3624
3979
  requiresCountersign?: boolean;
3980
+ /** The row says whether the countersign box is placed on the document. `false` (with `requiresCountersign`) offers the "Place your countersignature box" link; `undefined` = unknown = no link. */
3981
+ hasCountersignPlacement?: boolean;
3625
3982
  signerRoles?: SignerRole[];
3626
3983
  hasMapping?: boolean;
3627
3984
  hasDataFields?: boolean;
@@ -3643,6 +4000,14 @@ export interface DocumentSignerInfo {
3643
4000
  multiSigner: boolean;
3644
4001
  /** A document that is sent for signing requires the sender's countersignature. */
3645
4002
  countersign: boolean;
4003
+ /**
4004
+ * Only present (and non-empty) when a signable chosen document requires the countersignature and its row says the countersign box is NOT
4005
+ * placed (`hasCountersignPlacement === false`): the documents the "Place your countersignature box" link is offered for.
4006
+ */
4007
+ countersignUnplaced?: {
4008
+ id: string;
4009
+ title: string;
4010
+ }[];
3646
4011
  /** Several documents are chosen and one of them has several signers (it still goes to the one recipient). */
3647
4012
  multiDocumentNote: boolean;
3648
4013
  /** Under the document chip: what the mapping covers ('' when unknown). */
@@ -3686,6 +4051,8 @@ export declare function forwardContext(ctx: SendContext | null | undefined, type
3686
4051
  export declare function mergeFieldText(f: MergeCheckEmptyField): string;
3687
4052
  /** The warning lines of a merge-check answer: one line for one document, `<title>: …` per document for several. Nothing when nothing is empty. */
3688
4053
  export declare function mergeWarningLines(res: MergeCheckResult | null | undefined, docTitles: Record<string, string>): string[];
4054
+ /** The accessible name of the "Place your countersignature box" link of a document. */
4055
+ export declare function countersignPlaceLabel(title: string): string;
3689
4056
  /** What the sender wants done with the chosen documents. Same values as the backend (`ESIGN` | `ATTACH` | `BOTH`). */
3690
4057
  export type DocumentSendPurpose = 'ESIGN' | 'ATTACH' | 'BOTH';
3691
4058
  /** How the request reaches the recipient — derived from the channel: the Email tab sends `EMAIL`, the Text tab `TEXT`. */
@@ -3701,6 +4068,8 @@ export interface DocumentOption {
3701
4068
  hasFile: boolean;
3702
4069
  /** Only present (and true) when the row says the document needs the sender's countersignature (`requiresCountersign` on the `GET /documents` row): then a DocuSign send offers "Require my countersignature after client signs". */
3703
4070
  requiresCountersign?: boolean;
4071
+ /** Only with `documentSigners` and `requiresCountersign`: the row says whether the countersign box is placed. `false` shows the "Place your countersignature box" link after the countersign line (`mapFieldsClick`); absent = unknown = no link. */
4072
+ hasCountersignPlacement?: boolean;
3704
4073
  /** Only with `documentSigners`: the document's customer roles from the `GET /documents` row (`signerRoles`). `[]` = no signature fields (informational). */
3705
4074
  signerRoles?: SignerRole[];
3706
4075
  /** The row says the document has saved placements of any kind (drives the "Fields placed for N signers" line and the merge check). */
@@ -3717,6 +4086,7 @@ export interface DocumentSeed {
3717
4086
  fileName?: string | null;
3718
4087
  hasFile?: boolean;
3719
4088
  requiresCountersign?: boolean;
4089
+ hasCountersignPlacement?: boolean;
3720
4090
  signerRoles?: SignerRole[];
3721
4091
  hasMapping?: boolean;
3722
4092
  hasDataFields?: boolean;
@@ -3906,7 +4276,7 @@ export declare class DocumentSendOptionsComponent implements UiFieldValidity {
3906
4276
  readonly envelope: import("@angular/core").InputSignal<DocumentEnvelopeTarget | null>;
3907
4277
  /** The document list is fixed: an existing envelope's. */
3908
4278
  readonly locked: import("@angular/core").Signal<boolean>;
3909
- protected readonly purposeOptions: import("@angular/core").Signal<SelectOption[]>;
4279
+ protected readonly purposeOptions: import("@angular/core").Signal<SelectOption<string>[]>;
3910
4280
  readonly search$: Subject<string>;
3911
4281
  readonly options: import("@angular/core").WritableSignal<ComboboxOption<DocumentOption>[]>;
3912
4282
  readonly loading: import("@angular/core").WritableSignal<boolean>;
@@ -4344,6 +4714,11 @@ declare class SendEmailComponent implements OnInit {
4344
4714
  readonly validChange: import("@angular/core").OutputEmitterRef<boolean>;
4345
4715
  /** A document send was refused because the document / envelope changed meanwhile (404 / 409): the host refreshes its list. */
4346
4716
  readonly rejected: import("@angular/core").OutputEmitterRef<void>;
4717
+ /** The "Place your countersignature box" link under the countersign line was clicked (document row `hasCountersignPlacement: false`). */
4718
+ readonly mapFieldsClick: import("@angular/core").OutputEmitterRef<{
4719
+ id: string;
4720
+ title: string;
4721
+ }>;
4347
4722
  readonly EMAIL_DISABLED_MESSAGE = "Email sending is disabled, so this can't be sent. Enable it in Communication Settings.";
4348
4723
  mode: 'compose' | 'template';
4349
4724
  subject: string;
@@ -4650,6 +5025,7 @@ declare class SendEmailComponent implements OnInit {
4650
5025
  "emailAdded": "emailAdded";
4651
5026
  "validChange": "validChange";
4652
5027
  "rejected": "rejected";
5028
+ "mapFieldsClick": "mapFieldsClick";
4653
5029
  }, never, never, true, never>;
4654
5030
  }
4655
5031
  /** What the text form sends — the default `POST <baseUrl>/text-messages` body, or the host's `sendFn` argument. */
@@ -5093,6 +5469,15 @@ export declare class SendMessagePanelComponent {
5093
5469
  readonly emailAdded: import("@angular/core").OutputEmitterRef<string>;
5094
5470
  /** A document / envelope send was refused because it changed meanwhile (404 / 409): the host refreshes its list. */
5095
5471
  readonly rejected: import("@angular/core").OutputEmitterRef<void>;
5472
+ /**
5473
+ * With `documentSigners` on: the user clicked "Place your countersignature box" under the countersign line of a document that requires a
5474
+ * countersignature but whose row says `hasCountersignPlacement: false` (docs/send-documents-contract.md, Addendum 2026-10-19 (b)).
5475
+ * Payload = that document `{ id, title }`; the host navigates to Map Data Fields. Never emitted when the flag is true / absent.
5476
+ */
5477
+ readonly mapFieldsClick: import("@angular/core").OutputEmitterRef<{
5478
+ id: string;
5479
+ title: string;
5480
+ }>;
5096
5481
  readonly phoneAdded: import("@angular/core").OutputEmitterRef<string>;
5097
5482
  readonly sendEmailRef: import("@angular/core").Signal<SendEmailComponent | undefined>;
5098
5483
  readonly sendTextRef: import("@angular/core").Signal<SendTextComponent | undefined>;
@@ -5314,6 +5699,7 @@ export declare class SendMessagePanelComponent {
5314
5699
  "sent": "sent";
5315
5700
  "emailAdded": "emailAdded";
5316
5701
  "rejected": "rejected";
5702
+ "mapFieldsClick": "mapFieldsClick";
5317
5703
  "phoneAdded": "phoneAdded";
5318
5704
  }, never, never, true, never>;
5319
5705
  }
@@ -5340,14 +5726,27 @@ export declare class DocumentSignersComponent implements UiFieldValidity {
5340
5726
  /** The complete signer list (recipient first) or null while a role has no contact. Emitted on every change. */
5341
5727
  readonly signersChange: import("@angular/core").OutputEmitterRef<SendSigner[] | null>;
5342
5728
  readonly invalidChange: import("@angular/core").OutputEmitterRef<boolean>;
5729
+ /** The "Place your countersignature box" link was clicked: the document whose countersign box is not placed (the host opens Map Data Fields). */
5730
+ readonly mapFieldsClick: import("@angular/core").OutputEmitterRef<{
5731
+ id: string;
5732
+ title: string;
5733
+ }>;
5343
5734
  readonly picks: import("@angular/core").WritableSignal<Partial<Record<SignerRole, ContactLookup | null>>>;
5344
5735
  protected readonly rowsShown: import("@angular/core").Signal<boolean>;
5345
5736
  protected readonly line: import("@angular/core").Signal<string>;
5737
+ /** Documents the "place your box" link is offered for: native send only (DocuSign places its own fields) and only while the countersign line shows. */
5738
+ protected readonly placeDocs: import("@angular/core").Signal<{
5739
+ id: string;
5740
+ title: string;
5741
+ }[]>;
5346
5742
  readonly problems: import("@angular/core").Signal<Partial<Record<SignerRole, string>>>;
5347
5743
  readonly signers: import("@angular/core").Signal<SendSigner[] | null>;
5348
5744
  readonly errorMessage: import("@angular/core").Signal<string>;
5349
5745
  readonly invalid: import("@angular/core").Signal<boolean>;
5350
5746
  constructor();
5747
+ protected placeLabel(title: string): string;
5748
+ /** Visible text: with several documents the title is part of it (label-in-name: it contains the start of the accessible name). */
5749
+ protected placeText(title: string): string;
5351
5750
  protected label(role: SignerRole): string;
5352
5751
  /** Under a picker: the problems of a chosen contact (no email / same person twice); the empty-row message is the picker's own (`visibleErrorMessage`). */
5353
5752
  protected visibleProblem(role: SignerRole): string;
@@ -5382,6 +5781,7 @@ export declare class DocumentSignersComponent implements UiFieldValidity {
5382
5781
  }, {
5383
5782
  "signersChange": "signersChange";
5384
5783
  "invalidChange": "invalidChange";
5784
+ "mapFieldsClick": "mapFieldsClick";
5385
5785
  }, never, never, true, never>;
5386
5786
  }
5387
5787
  /** Quiet time after the last change before the check is requested. */
@@ -5604,10 +6004,16 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
5604
6004
  /** Accessible name for a field with no visible label (e.g. an inline cell or title input). */
5605
6005
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
5606
6006
  readonly placeholder: import("@angular/core").InputSignal<string>;
5607
- readonly type: import("@angular/core").InputSignal<"number" | "text" | "email" | "password" | "tel" | "url">;
6007
+ readonly type: import("@angular/core").InputSignal<"number" | "text" | "email" | "tel" | "password" | "url">;
5608
6008
  readonly maxlength: import("@angular/core").InputSignal<number | undefined>;
5609
6009
  readonly required: import("@angular/core").InputSignal<boolean>;
5610
6010
  readonly autocomplete: import("@angular/core").InputSignal<string>;
6011
+ /**
6012
+ * Which on-screen keyboard a phone shows (`numeric` = digits only, e.g. ZIP / area code; `decimal`, `tel`, `email`, `url`,
6013
+ * `search`, `text`, `none`). Empty (default) = the browser's default for the field, except `type="tel"`, which keeps `tel`.
6014
+ * It only picks the keyboard; it does not restrict what can be typed or pasted.
6015
+ */
6016
+ readonly inputmode: import("@angular/core").InputSignal<"" | "none" | "text" | "search" | "email" | "tel" | "url" | "decimal" | "numeric">;
5611
6017
  readonly readonly: import("@angular/core").InputSignal<boolean>;
5612
6018
  readonly hint: import("@angular/core").InputSignal<string>;
5613
6019
  readonly error: import("@angular/core").InputSignal<string>;
@@ -5619,6 +6025,11 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
5619
6025
  readonly max: import("@angular/core").InputSignal<number | undefined>;
5620
6026
  /** `type="number"` only: most digits allowed after the decimal point. */
5621
6027
  readonly maxDecimals: import("@angular/core").InputSignal<number | undefined>;
6028
+ /**
6029
+ * What the control value is. `'string'` (default) = the typed text. `'number'` = `number | null`; requires `type="number"`
6030
+ * (otherwise a dev-mode warning is logged once and string behaviour is used).
6031
+ */
6032
+ readonly valueType: import("@angular/core").InputSignal<"string" | "number">;
5622
6033
  /** Reveal the built-in error messages now (e.g. after a failed Save click), without waiting for blur. */
5623
6034
  readonly showErrors: import("@angular/core").InputSignal<boolean>;
5624
6035
  readonly focused: import("@angular/core").OutputEmitterRef<void>;
@@ -5630,6 +6041,9 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
5630
6041
  protected readonly disabled: import("@angular/core").Signal<boolean>;
5631
6042
  protected readonly _value: import("@angular/core").WritableSignal<string>;
5632
6043
  private readonly _reveal;
6044
+ /** True when the control value is a number: `valueType="number"` on a `type="number"` field. */
6045
+ private readonly _numeric;
6046
+ private _warned;
5633
6047
  private readonly _formatting;
5634
6048
  private readonly _issue;
5635
6049
  /** True while the value breaks a built-in rule or the host passed an `[error]`. */
@@ -5645,13 +6059,15 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
5645
6059
  constructor();
5646
6060
  /** Re-run the form control's validation when a rule input (required, maxlength, min, options, ...) changes. */
5647
6061
  ngOnChanges(): void;
5648
- writeValue(val: string | number | null): void;
6062
+ writeValue(val: string | number | null | undefined): void;
5649
6063
  validate(control: AbstractControl): ValidationErrors | null;
5650
6064
  registerOnValidatorChange(fn: () => void): void;
5651
- registerOnChange(fn: (v: string) => void): void;
6065
+ registerOnChange(fn: (v: string | number | null) => void): void;
5652
6066
  registerOnTouched(fn: () => void): void;
5653
6067
  setDisabledState(d: boolean): void;
5654
6068
  protected onInput(event: Event): void;
6069
+ /** Typed text -> number, or null when blank / not a finite number. */
6070
+ private static _parse;
5655
6071
  protected onFocus(): void;
5656
6072
  protected onBlur(): void;
5657
6073
  /** First broken built-in rule for `value`, or null. */
@@ -5696,6 +6112,11 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
5696
6112
  "required": false;
5697
6113
  "isSignal": true;
5698
6114
  };
6115
+ "inputmode": {
6116
+ "alias": "inputmode";
6117
+ "required": false;
6118
+ "isSignal": true;
6119
+ };
5699
6120
  "readonly": {
5700
6121
  "alias": "readonly";
5701
6122
  "required": false;
@@ -5731,6 +6152,11 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
5731
6152
  "required": false;
5732
6153
  "isSignal": true;
5733
6154
  };
6155
+ "valueType": {
6156
+ "alias": "valueType";
6157
+ "required": false;
6158
+ "isSignal": true;
6159
+ };
5734
6160
  "showErrors": {
5735
6161
  "alias": "showErrors";
5736
6162
  "required": false;
@@ -5757,6 +6183,12 @@ export declare class TextareaInputComponent implements ControlValueAccessor, Val
5757
6183
  readonly required: import("@angular/core").InputSignal<boolean>;
5758
6184
  readonly readonly: import("@angular/core").InputSignal<boolean>;
5759
6185
  readonly hint: import("@angular/core").InputSignal<string>;
6186
+ /**
6187
+ * Opt-in. `false` (default) = unchanged: a labelled field's hint renders INSIDE the `<label>` ("Label (hint)" is the accessible name,
6188
+ * no aria-describedby for it). `true` = the hint is a sibling of the label (`.ta-head` row, id `<textarea id>-hint`), the accessible
6189
+ * name is the label only and the hint is the description (`aria-describedby`, then the error). See docs/textarea-input.md.
6190
+ */
6191
+ readonly hintAsDescription: import("@angular/core").InputSignal<boolean>;
5760
6192
  readonly error: import("@angular/core").InputSignal<string>;
5761
6193
  /** Reveal the built-in error messages now (e.g. after a failed Save click), without waiting for blur. */
5762
6194
  readonly showErrors: import("@angular/core").InputSignal<boolean>;
@@ -5766,6 +6198,11 @@ export declare class TextareaInputComponent implements ControlValueAccessor, Val
5766
6198
  protected readonly _value: import("@angular/core").WritableSignal<string>;
5767
6199
  /** The hint is shown inline after the label; with no label there is nothing to hang it on, so it renders under the field. */
5768
6200
  protected readonly _unlabelledHint: import("@angular/core").Signal<boolean>;
6201
+ /**
6202
+ * aria-describedby: the hint (inline after the label, or under the field when there is no label; the under-field
6203
+ * hint is replaced by the error) followed by the error. The label is the accessible NAME; the hint is the DESCRIPTION.
6204
+ */
6205
+ protected readonly _describedBy: import("@angular/core").Signal<string | null>;
5769
6206
  private readonly _reveal;
5770
6207
  private readonly _issue;
5771
6208
  /** True while the value breaks a built-in rule or the host passed an `[error]`. */
@@ -5833,6 +6270,11 @@ export declare class TextareaInputComponent implements ControlValueAccessor, Val
5833
6270
  "required": false;
5834
6271
  "isSignal": true;
5835
6272
  };
6273
+ "hintAsDescription": {
6274
+ "alias": "hintAsDescription";
6275
+ "required": false;
6276
+ "isSignal": true;
6277
+ };
5836
6278
  "error": {
5837
6279
  "alias": "error";
5838
6280
  "required": false;
@@ -5853,7 +6295,8 @@ export declare class TextareaInputComponent implements ControlValueAccessor, Val
5853
6295
  }, never, never, true, never>;
5854
6296
  }
5855
6297
  /** Why a chosen file was refused. */
5856
- export type FileRejectReason = 'type' | 'size';
6298
+ /** 'type' = not an accepted type, 'size' = over `maxSize`, 'empty' = a 0-byte file (`rejectEmpty`), 'small' = under `minSize`. */
6299
+ export type FileRejectReason = 'type' | 'size' | 'empty' | 'small';
5857
6300
  /** A refused file with the message the field shows (also emitted by `(fileRejected)`). */
5858
6301
  export interface FileRejection {
5859
6302
  file: File;
@@ -5876,7 +6319,7 @@ export declare function acceptLabel(accept: string): string;
5876
6319
  * Built in (all opt-in, nothing changes unless an input is set):
5877
6320
  * - `clearable`: a Remove button next to the chosen file; `(fileRemoved)` fires after it clears the selection.
5878
6321
  * - `accept` is enforced on picked AND dropped files (`enforceAccept`, on by default), `maxSize` (bytes) refuses bigger
5879
- * files. A refused file is never added to the value: the field shows the message (`typeMessage` / `sizeMessage`
6322
+ * files, `rejectEmpty` refuses a 0-byte file (`emptyMessage`) and `minSize` (bytes) refuses smaller ones (`minSizeMessage`). A refused file is never added to the value: the field shows the message (`typeMessage` / `sizeMessage`
5880
6323
  * override the wording, also in picker-only mode), and `(fileRejected)` emits `{ file, reason, message }`.
5881
6324
  * - `required` is a real rule: Angular `Validator` + `UiFieldValidity` (`invalid`, `errorMessage`, `invalidChange`,
5882
6325
  * `showErrors`), like the other inputs. The message shows after the field was used or `[showErrors]="true"`.
@@ -5913,6 +6356,14 @@ export declare class FileInputComponent implements ControlValueAccessor, Validat
5913
6356
  readonly typeMessage: import("@angular/core").InputSignal<string>;
5914
6357
  /** Replaces the built-in "The file must be 5 MB or smaller." message. */
5915
6358
  readonly sizeMessage: import("@angular/core").InputSignal<string>;
6359
+ /** Refuse a file with no content (0 bytes). Off by default. */
6360
+ readonly rejectEmpty: import("@angular/core").InputSignal<boolean>;
6361
+ /** Replaces the built-in "The file is empty." message. */
6362
+ readonly emptyMessage: import("@angular/core").InputSignal<string>;
6363
+ /** Smallest allowed file in bytes; a smaller (non-empty) file is refused. null = no minimum. */
6364
+ readonly minSize: import("@angular/core").InputSignal<number | null>;
6365
+ /** Replaces the built-in "The file must be at least 10 KB." message. */
6366
+ readonly minSizeMessage: import("@angular/core").InputSignal<string>;
5916
6367
  /** The field accepts files dropped on it. */
5917
6368
  readonly dropzone: import("@angular/core").InputSignal<boolean>;
5918
6369
  /** Show the file size after the name. */
@@ -6066,6 +6517,26 @@ export declare class FileInputComponent implements ControlValueAccessor, Validat
6066
6517
  "required": false;
6067
6518
  "isSignal": true;
6068
6519
  };
6520
+ "rejectEmpty": {
6521
+ "alias": "rejectEmpty";
6522
+ "required": false;
6523
+ "isSignal": true;
6524
+ };
6525
+ "emptyMessage": {
6526
+ "alias": "emptyMessage";
6527
+ "required": false;
6528
+ "isSignal": true;
6529
+ };
6530
+ "minSize": {
6531
+ "alias": "minSize";
6532
+ "required": false;
6533
+ "isSignal": true;
6534
+ };
6535
+ "minSizeMessage": {
6536
+ "alias": "minSizeMessage";
6537
+ "required": false;
6538
+ "isSignal": true;
6539
+ };
6069
6540
  "dropzone": {
6070
6541
  "alias": "dropzone";
6071
6542
  "required": false;
@@ -6372,7 +6843,7 @@ export declare class TimeInputComponent implements ControlValueAccessor, Validat
6372
6843
  export declare class MultiSelectComponent implements ControlValueAccessor {
6373
6844
  readonly label: import("@angular/core").InputSignal<string>;
6374
6845
  readonly placeholder: import("@angular/core").InputSignal<string>;
6375
- readonly options: import("@angular/core").InputSignal<SelectOption[]>;
6846
+ readonly options: import("@angular/core").InputSignal<SelectOption<string>[]>;
6376
6847
  readonly searchable: import("@angular/core").InputSignal<boolean>;
6377
6848
  readonly searchPlaceholder: import("@angular/core").InputSignal<string>;
6378
6849
  readonly required: import("@angular/core").InputSignal<boolean>;
@@ -6388,8 +6859,8 @@ export declare class MultiSelectComponent implements ControlValueAccessor {
6388
6859
  protected readonly _open: import("@angular/core").WritableSignal<boolean>;
6389
6860
  protected readonly _query: import("@angular/core").WritableSignal<string>;
6390
6861
  protected readonly _selectedSet: import("@angular/core").Signal<Set<string>>;
6391
- protected readonly _selectedOptions: import("@angular/core").Signal<SelectOption[]>;
6392
- protected readonly _filteredOptions: import("@angular/core").Signal<SelectOption[]>;
6862
+ protected readonly _selectedOptions: import("@angular/core").Signal<SelectOption<string>[]>;
6863
+ protected readonly _filteredOptions: import("@angular/core").Signal<SelectOption<string>[]>;
6393
6864
  private _onChange;
6394
6865
  private _onTouched;
6395
6866
  writeValue(val: string[] | null): void;
@@ -6607,7 +7078,8 @@ export declare class FormRowComponent {
6607
7078
  /**
6608
7079
  * Fixed-layout list item with an optional leading radio/checkbox, a title + badge,
6609
7080
  * and a secondary detail row rendered from a string array. All layout is handled
6610
- * internally — pass inputs only, no ng-content.
7081
+ * internally — pass inputs only; the one optional projection is the row-end action slot
7082
+ * (`list-item-action`, alias `li-action`, see below).
6611
7083
  *
6612
7084
  * Usage:
6613
7085
  * <ui-list-item
@@ -6621,6 +7093,16 @@ export declare class FormRowComponent {
6621
7093
  * badgeVariant="green"
6622
7094
  * [details]="[c.email, c.phone, 'Added ' + (c.createdAt | date:'MMM d, y')]"
6623
7095
  * (select)="onSelect(c.id)" />
7096
+ *
7097
+ * Read-only row with actions (2026-10-19): `[interactive]="false"` renders a plain row (no label, no leading
7098
+ * input, no pointer cursor, no hover tint, nothing focusable) and `[alwaysShowBadge]="true"` shows the badge
7099
+ * without needing `checked`. Elements carrying `list-item-action` (alias `li-action`) are projected at the
7100
+ * row end, vertically centred, after the text; they sit outside the row's label so clicking them never emits
7101
+ * `select`. Absent = no extra DOM. Example:
7102
+ * <ui-list-item [interactive]="false" [bordered]="true" [title]="d.name" [details]="[d.fileName]"
7103
+ * badge="Archived" badgeVariant="gray" [alwaysShowBadge]="true">
7104
+ * <ui-button list-item-action variant="icon" ariaLabel="Remove" (click)="remove(d)">…</ui-button>
7105
+ * </ui-list-item>
6624
7106
  */
6625
7107
  export declare class ListItemComponent {
6626
7108
  /** Show a radio button, checkbox, or nothing on the left. */
@@ -6635,10 +7117,24 @@ export declare class ListItemComponent {
6635
7117
  readonly highlighted: import("@angular/core").InputSignal<boolean>;
6636
7118
  /** Wraps the item in a bordered card (border + border-radius). */
6637
7119
  readonly bordered: import("@angular/core").InputSignal<boolean>;
7120
+ /**
7121
+ * `true` (default) = today's behaviour: clickable label, optional radio/checkbox, hover tint, pointer cursor.
7122
+ * `false` = plain row: no label, no leading input (`leading` is ignored), no hover tint, no pointer, `select` never
7123
+ * fires. Title, details, badge, `bordered` and `highlighted` look the same. Only projected actions stay focusable.
7124
+ */
7125
+ readonly interactive: import("@angular/core").InputSignal<boolean>;
7126
+ /** Show the badge even when the row is not `checked` (for non-interactive rows with a status badge). Default false. */
7127
+ readonly alwaysShowBadge: import("@angular/core").InputSignal<boolean>;
7128
+ /**
7129
+ * Break long unbroken title / detail text (file names, e-mails) inside the text column instead of letting it overflow; use it
7130
+ * on a row with `list-item-action` projections so the actions stay on screen at 320px. Default `false` = today's rendering.
7131
+ * Rows with `[interactive]="false"` always wrap (new in 2026-10-19, nothing to preserve).
7132
+ */
7133
+ readonly wrapText: import("@angular/core").InputSignal<boolean>;
6638
7134
  get _borderedClass(): boolean;
6639
7135
  /** Primary label. */
6640
7136
  readonly title: import("@angular/core").InputSignal<string>;
6641
- /** Badge shown next to the title — only visible when the item is checked. */
7137
+ /** Badge shown next to the title — only visible when the item is checked, unless `alwaysShowBadge` is set. */
6642
7138
  readonly badge: import("@angular/core").InputSignal<string | null>;
6643
7139
  readonly badgeVariant: import("@angular/core").InputSignal<BadgeVariant>;
6644
7140
  /**
@@ -6682,6 +7178,21 @@ export declare class ListItemComponent {
6682
7178
  "required": false;
6683
7179
  "isSignal": true;
6684
7180
  };
7181
+ "interactive": {
7182
+ "alias": "interactive";
7183
+ "required": false;
7184
+ "isSignal": true;
7185
+ };
7186
+ "alwaysShowBadge": {
7187
+ "alias": "alwaysShowBadge";
7188
+ "required": false;
7189
+ "isSignal": true;
7190
+ };
7191
+ "wrapText": {
7192
+ "alias": "wrapText";
7193
+ "required": false;
7194
+ "isSignal": true;
7195
+ };
6685
7196
  "title": {
6686
7197
  "alias": "title";
6687
7198
  "required": false;
@@ -6704,44 +7215,264 @@ export declare class ListItemComponent {
6704
7215
  };
6705
7216
  }, {
6706
7217
  "select": "select";
6707
- }, never, never, true, never>;
7218
+ }, never, ["[list-item-action], [li-action]"], true, never>;
6708
7219
  }
6709
7220
  /**
6710
- * QR code for a URL (or any text), with a "Download QR Code" button (PNG). Projected content
6711
- * renders next to the download button (e.g. an "Open Kiosk Mode" action).
7221
+ * Adds one app-wide toast outlet (a `<ui-toast>` appended to `<body>`) when the app starts, so toasts show on pages that have
7222
+ * no `<ui-data-table>` and no `<ui-toast />` of their own.
6712
7223
  *
6713
- * Self-contained: uses the built-in encoder in ./qr-encoder.ts — no third-party QR package.
7224
+ * providers: [provideForgerouteToast()]
7225
+ * providers: [provideForgerouteToast({ dismissible: true })] // every toast also gets a close button (default: none)
6714
7226
  *
6715
- * ```html
6716
- * <ui-qr-code [url]="publicLink()" filename="open-house-qr-code">
6717
- * <ui-button variant="secondary" size="sm" (click)="openKiosk()">Open Kiosk Mode</ui-button>
6718
- * </ui-qr-code>
6719
- * ```
7227
+ * It is created first, so it is the registry's host and any other `<ui-toast>` (a table's own, a shell's) stays silent: each
7228
+ * message renders and is announced once. Nothing happens on the server. The outlet is destroyed with the app.
6720
7229
  */
6721
- export declare class QrCodeComponent {
6722
- /** URL (or text) encoded into the QR code. Nothing is rendered while empty. */
6723
- readonly url: import("@angular/core").InputSignal<string>;
6724
- /** Displayed width/height in px. The PNG is rendered at 2x for sharp printing. */
6725
- readonly size: import("@angular/core").InputSignal<number>;
6726
- /** Download file name, without the .png extension. */
6727
- readonly filename: import("@angular/core").InputSignal<string>;
6728
- readonly alt: import("@angular/core").InputSignal<string>;
6729
- /** iOS Safari doesn't support programmatic download of data: or blob: URLs. */
6730
- protected readonly isIOS: boolean;
6731
- protected readonly qr: import("@angular/core").Signal<{
6732
- dataUrl: string;
6733
- error: boolean;
6734
- }>;
6735
- download(): void;
6736
- static ɵfac: i0.ɵɵFactoryDeclaration<QrCodeComponent, never>;
6737
- static ɵcmp: i0.ɵɵComponentDeclaration<QrCodeComponent, "ui-qr-code", never, {
6738
- "url": {
6739
- "alias": "url";
6740
- "required": false;
6741
- "isSignal": true;
6742
- };
6743
- "size": {
6744
- "alias": "size";
7230
+ export declare function provideForgerouteToast(options?: {
7231
+ dismissible?: boolean;
7232
+ }): EnvironmentProviders;
7233
+ /** Payload of `(reorder)`. `order[newIndex] = oldIndex`; apply it with `reorderItems(items, order)`. */
7234
+ export interface ListReorderEvent {
7235
+ /** Index the moved row had before the move. */
7236
+ from: number;
7237
+ /** Index the moved row has after the move. */
7238
+ to: number;
7239
+ /** The new index order of the whole list: `order[newIndex] = oldIndex`. */
7240
+ order: number[];
7241
+ }
7242
+ /** Context handed to the row template: `<ng-template let-item let-i="index" let-n="count">`. */
7243
+ export interface ListRowContext<T = unknown> {
7244
+ $implicit: T;
7245
+ index: number;
7246
+ count: number;
7247
+ }
7248
+ /** Returns a new array in the order given by a `ListReorderEvent.order` (the input array is not changed). */
7249
+ export declare function reorderItems<T>(items: readonly T[], order: readonly number[]): T[];
7250
+ interface Row {
7251
+ item: unknown;
7252
+ key: unknown;
7253
+ index: number;
7254
+ label: string;
7255
+ upLabel: string;
7256
+ downLabel: string;
7257
+ first: boolean;
7258
+ last: boolean;
7259
+ ctx: ListRowContext;
7260
+ }
7261
+ /**
7262
+ * Semantic list container. Renders a real `<ul>` (default) or `<ol>` (`[ordered]="true"`) whose children are real `<li>`
7263
+ * elements, one per entry of `items`. The row template (the single `<ng-template>` child) renders what is inside each
7264
+ * `<li>`, normally a `<ui-list-item>`. Screen readers announce "list, N items" and "item i of N" natively.
7265
+ *
7266
+ * Optional reordering (`[reorderable]="true"`, off by default): Move up / Move down icon buttons at the row end,
7267
+ * Alt+ArrowUp / Alt+ArrowDown from any focused element in the row, a polite live region and a `(reorder)` output.
7268
+ * The host owns the data: the list never changes `items`; apply the event (`reorderItems`) and pass the new array.
7269
+ *
7270
+ * <ui-list ariaLabel="Documents" [items]="docs()" [reorderable]="true" (reorder)="docs.set(reorderItems(docs(), $event.order))">
7271
+ * <ng-template let-doc let-i="index">
7272
+ * <ui-list-item [interactive]="false" [bordered]="true" [title]="doc.title" [details]="[doc.fileName]" />
7273
+ * </ng-template>
7274
+ * </ui-list>
7275
+ */
7276
+ export declare class ListComponent<T = unknown> {
7277
+ private readonly host;
7278
+ private readonly injector;
7279
+ /** The entries to render, one `<li>` each. The host owns this array; the list never changes it. */
7280
+ readonly items: import("@angular/core").InputSignal<readonly T[]>;
7281
+ /** `false` (default) renders a `<ul>`; `true` renders an `<ol>` with native numbering. */
7282
+ readonly ordered: import("@angular/core").InputSignal<boolean>;
7283
+ /** Shows Move up / Move down controls and enables Alt+ArrowUp / Alt+ArrowDown. Default `false`: no controls, no live region. */
7284
+ readonly reorderable: import("@angular/core").InputSignal<boolean>;
7285
+ /** Disables all move controls and the keyboard shortcut (the rows stay visible). */
7286
+ readonly disabled: import("@angular/core").InputSignal<boolean>;
7287
+ /** Accessible name of the list (`aria-label`). */
7288
+ readonly ariaLabel: import("@angular/core").InputSignal<string | undefined>;
7289
+ /** Id of the element naming the list (`aria-labelledby`). */
7290
+ readonly ariaLabelledby: import("@angular/core").InputSignal<string | undefined>;
7291
+ /**
7292
+ * Row label used in "Move <label> up" and the live announcement. Default: the string itself, else the item's
7293
+ * `title`, `label` or `name`, else "Item N".
7294
+ */
7295
+ readonly itemLabelFn: import("@angular/core").InputSignal<(item: T, index: number) => string>;
7296
+ /** Stable identity per row (default: the item object itself) so reordering moves DOM instead of recreating it. */
7297
+ readonly trackBy: import("@angular/core").InputSignal<(item: T, index: number) => unknown>;
7298
+ /** Fires when the user moves a row. The host applies it and passes the new `items`. */
7299
+ readonly reorder: import("@angular/core").OutputEmitterRef<ListReorderEvent>;
7300
+ protected readonly rowTemplate: import("@angular/core").Signal<TemplateRef<any> | undefined>;
7301
+ protected readonly announcement: import("@angular/core").WritableSignal<string>;
7302
+ protected readonly movedKey: import("@angular/core").WritableSignal<unknown>;
7303
+ private pendingFocus;
7304
+ private announceSeq;
7305
+ /** Stable disambiguation tokens for repeated keys (duplicates in a primitive list), reused across recomputes. */
7306
+ private dupTokens;
7307
+ protected readonly rows: import("@angular/core").Signal<Row[]>;
7308
+ private canReorder;
7309
+ /** The `<li>` (direct child of this list) that contains `el`, or null. */
7310
+ private rowIndexOf;
7311
+ protected onKeydown(e: KeyboardEvent): void;
7312
+ protected onClick(e: Event): void;
7313
+ protected onFocusOut(e: FocusEvent): void;
7314
+ /** Emits the reorder, announces it and restores focus to the moved row's control once the host has re-rendered. */
7315
+ private move;
7316
+ /** Identical text is not re-read by screen readers, so a repeat of the current message is cleared first and written after the next render. */
7317
+ private announce;
7318
+ private restoreFocus;
7319
+ static ɵfac: i0.ɵɵFactoryDeclaration<ListComponent<any>, never>;
7320
+ static ɵcmp: i0.ɵɵComponentDeclaration<ListComponent<any>, "ui-list", never, {
7321
+ "items": {
7322
+ "alias": "items";
7323
+ "required": false;
7324
+ "isSignal": true;
7325
+ };
7326
+ "ordered": {
7327
+ "alias": "ordered";
7328
+ "required": false;
7329
+ "isSignal": true;
7330
+ };
7331
+ "reorderable": {
7332
+ "alias": "reorderable";
7333
+ "required": false;
7334
+ "isSignal": true;
7335
+ };
7336
+ "disabled": {
7337
+ "alias": "disabled";
7338
+ "required": false;
7339
+ "isSignal": true;
7340
+ };
7341
+ "ariaLabel": {
7342
+ "alias": "ariaLabel";
7343
+ "required": false;
7344
+ "isSignal": true;
7345
+ };
7346
+ "ariaLabelledby": {
7347
+ "alias": "ariaLabelledby";
7348
+ "required": false;
7349
+ "isSignal": true;
7350
+ };
7351
+ "itemLabelFn": {
7352
+ "alias": "itemLabelFn";
7353
+ "required": false;
7354
+ "isSignal": true;
7355
+ };
7356
+ "trackBy": {
7357
+ "alias": "trackBy";
7358
+ "required": false;
7359
+ "isSignal": true;
7360
+ };
7361
+ }, {
7362
+ "reorder": "reorder";
7363
+ }, ["rowTemplate"], ["[list-empty]"], true, never>;
7364
+ }
7365
+ /**
7366
+ * One labelled bar row: label | bar | count (and an optional share text under the count).
7367
+ * Use it for category / share charts: `value` is the row's amount, `max` the largest amount (the bar is value / max),
7368
+ * `variant` a fixed colour (default blue, never completion based).
7369
+ *
7370
+ * The row has no list role of its own: put `role="list"` on the host container and `role="listitem"` on each `ui-bar-row`
7371
+ * (or wrap them in `ui-list`). The label names the inner progressbar (`aria-labelledby`) and `valueText`
7372
+ * (default: the count plus the share) is its `aria-valuetext`.
7373
+ * Layout: `minmax(0, 12rem) 1fr auto`; at 480px and below the label sits above the bar with the count at the end of the label line.
7374
+ */
7375
+ export declare class BarRowComponent {
7376
+ /** Row label (wraps; long unbroken text breaks instead of overflowing). */
7377
+ readonly label: import("@angular/core").InputSignal<string>;
7378
+ /** This row's amount. */
7379
+ readonly value: import("@angular/core").InputSignal<number>;
7380
+ /** The amount that fills the bar (e.g. the largest category's count). 0 / NaN / negative draws an empty bar. */
7381
+ readonly max: import("@angular/core").InputSignal<number>;
7382
+ /** Fixed bar colour. Default blue; never follows completion. */
7383
+ readonly variant: import("@angular/core").InputSignal<ProgressBarVariant>;
7384
+ /** Count text override, e.g. "$1.2M". Empty = `value` formatted with `locale`. */
7385
+ readonly count: import("@angular/core").InputSignal<string>;
7386
+ /** Secondary text under the count, e.g. "40%". Empty = not shown. */
7387
+ readonly share: import("@angular/core").InputSignal<string>;
7388
+ /** Screen-reader text for the bar, e.g. "12 documents, 40% of the largest category". Empty = count plus share. */
7389
+ readonly valueText: import("@angular/core").InputSignal<string>;
7390
+ /** BCP 47 locale for the formatted count. Explicit, never the browser's implicit one. */
7391
+ readonly locale: import("@angular/core").InputSignal<string>;
7392
+ /** Unique id tying the label to the progressbar. */
7393
+ protected readonly labelId: string;
7394
+ protected readonly countText: import("@angular/core").Signal<string>;
7395
+ protected readonly effectiveValueText: import("@angular/core").Signal<string>;
7396
+ static ɵfac: i0.ɵɵFactoryDeclaration<BarRowComponent, never>;
7397
+ static ɵcmp: i0.ɵɵComponentDeclaration<BarRowComponent, "ui-bar-row", never, {
7398
+ "label": {
7399
+ "alias": "label";
7400
+ "required": false;
7401
+ "isSignal": true;
7402
+ };
7403
+ "value": {
7404
+ "alias": "value";
7405
+ "required": false;
7406
+ "isSignal": true;
7407
+ };
7408
+ "max": {
7409
+ "alias": "max";
7410
+ "required": false;
7411
+ "isSignal": true;
7412
+ };
7413
+ "variant": {
7414
+ "alias": "variant";
7415
+ "required": false;
7416
+ "isSignal": true;
7417
+ };
7418
+ "count": {
7419
+ "alias": "count";
7420
+ "required": false;
7421
+ "isSignal": true;
7422
+ };
7423
+ "share": {
7424
+ "alias": "share";
7425
+ "required": false;
7426
+ "isSignal": true;
7427
+ };
7428
+ "valueText": {
7429
+ "alias": "valueText";
7430
+ "required": false;
7431
+ "isSignal": true;
7432
+ };
7433
+ "locale": {
7434
+ "alias": "locale";
7435
+ "required": false;
7436
+ "isSignal": true;
7437
+ };
7438
+ }, {}, never, never, true, never>;
7439
+ }
7440
+ /**
7441
+ * QR code for a URL (or any text), with a "Download QR Code" button (PNG). Projected content
7442
+ * renders next to the download button (e.g. an "Open Kiosk Mode" action).
7443
+ *
7444
+ * Self-contained: uses the built-in encoder in ./qr-encoder.ts — no third-party QR package.
7445
+ *
7446
+ * ```html
7447
+ * <ui-qr-code [url]="publicLink()" filename="open-house-qr-code">
7448
+ * <ui-button variant="secondary" size="sm" (click)="openKiosk()">Open Kiosk Mode</ui-button>
7449
+ * </ui-qr-code>
7450
+ * ```
7451
+ */
7452
+ export declare class QrCodeComponent {
7453
+ /** URL (or text) encoded into the QR code. Nothing is rendered while empty. */
7454
+ readonly url: import("@angular/core").InputSignal<string>;
7455
+ /** Displayed width/height in px. The PNG is rendered at 2x for sharp printing. */
7456
+ readonly size: import("@angular/core").InputSignal<number>;
7457
+ /** Download file name, without the .png extension. */
7458
+ readonly filename: import("@angular/core").InputSignal<string>;
7459
+ readonly alt: import("@angular/core").InputSignal<string>;
7460
+ /** iOS Safari doesn't support programmatic download of data: or blob: URLs. */
7461
+ protected readonly isIOS: boolean;
7462
+ protected readonly qr: import("@angular/core").Signal<{
7463
+ dataUrl: string;
7464
+ error: boolean;
7465
+ }>;
7466
+ download(): void;
7467
+ static ɵfac: i0.ɵɵFactoryDeclaration<QrCodeComponent, never>;
7468
+ static ɵcmp: i0.ɵɵComponentDeclaration<QrCodeComponent, "ui-qr-code", never, {
7469
+ "url": {
7470
+ "alias": "url";
7471
+ "required": false;
7472
+ "isSignal": true;
7473
+ };
7474
+ "size": {
7475
+ "alias": "size";
6745
7476
  "required": false;
6746
7477
  "isSignal": true;
6747
7478
  };
@@ -6994,6 +7725,10 @@ export declare const errorContextInterceptor: HttpInterceptorFn;
6994
7725
  *
6995
7726
  * The fallback names what the user was doing: pass `action` explicitly ('send', 'export', …), or
6996
7727
  * let it come from the request method recorded by `errorContextInterceptor`. With neither it says "loading".
7728
+ *
7729
+ * `fallbackContext` is a bare noun phrase ("contact", "your invoices"); an empty or whitespace-only one becomes "data".
7730
+ * The 404 text starts with "The <context>", so one leading article ("the", "a", "an") on the context is dropped
7731
+ * there: "the contact" and "a contact" both read "The contact you're looking for could not be found.".
6997
7732
  */
6998
7733
  export declare function getErrorMessage(error: unknown, fallbackContext?: string, action?: ErrorAction): string;
6999
7734
  export interface UsStateOption {
@@ -7189,6 +7924,9 @@ export type IconName = 'add' | 'close' | 'check' | 'upload' | 'image' | 'chevron
7189
7924
  * `tooltip` is the text shown on hover / keyboard focus (any length) and, unless `ariaLabel` is set, the name
7190
7925
  * assistive tech reads — always set it on a clickable icon. An unknown `name` draws nothing.
7191
7926
  *
7927
+ * `[loading]="true"` replaces the picture with a spinner and disables the button while the request the click started is running
7928
+ * (the same busy state `ui-button` has) — e.g. `<ui-icon name="delete" [loading]="deleting()" … />`.
7929
+ *
7192
7930
  * `[interactive]="false"` draws just the picture (no button, no click) for an icon that sits next to text or inside another
7193
7931
  * button — e.g. the arrow in a "Back" button, a bell beside a reminder. It is hidden from assistive tech unless a `tooltip` or `ariaLabel` is set.
7194
7932
  *
@@ -7205,6 +7943,8 @@ export declare class IconComponent {
7205
7943
  readonly size: import("@angular/core").InputSignal<number>;
7206
7944
  readonly variant: import("@angular/core").InputSignal<"danger" | "default">;
7207
7945
  readonly disabled: import("@angular/core").InputSignal<boolean>;
7946
+ /** The host is running the request this icon started: a spinner replaces the picture and the button is disabled (no click) until it is false again. */
7947
+ readonly loading: import("@angular/core").InputSignal<boolean>;
7208
7948
  /** false = just the picture (no button, no click) — for an icon beside text or inside another button. */
7209
7949
  readonly interactive: import("@angular/core").InputSignal<boolean>;
7210
7950
  /** Fires on click (not while disabled); the host performs the action. */
@@ -7244,6 +7984,11 @@ export declare class IconComponent {
7244
7984
  "required": false;
7245
7985
  "isSignal": true;
7246
7986
  };
7987
+ "loading": {
7988
+ "alias": "loading";
7989
+ "required": false;
7990
+ "isSignal": true;
7991
+ };
7247
7992
  "interactive": {
7248
7993
  "alias": "interactive";
7249
7994
  "required": false;
@@ -7253,6 +7998,143 @@ export declare class IconComponent {
7253
7998
  "iconClick": "iconClick";
7254
7999
  }, never, never, true, never>;
7255
8000
  }
8001
+ /** What an item template receives (`let-item let-i="index"`). */
8002
+ export interface SortableItemContext<T> {
8003
+ $implicit: T;
8004
+ index: number;
8005
+ first: boolean;
8006
+ last: boolean;
8007
+ /** True while this item is being dragged. */
8008
+ dragging: boolean;
8009
+ }
8010
+ /** Marks the `<ng-template>` that renders one item: `<ng-template uiSortableItem let-item let-i="index"> … </ng-template>`. */
8011
+ export declare class SortableItemDirective<T = unknown> {
8012
+ readonly template: TemplateRef<SortableItemContext<T>>;
8013
+ static ngTemplateContextGuard<T>(_dir: SortableItemDirective<T>, _ctx: unknown): _ctx is SortableItemContext<T>;
8014
+ static ɵfac: i0.ɵɵFactoryDeclaration<SortableItemDirective<any>, never>;
8015
+ static ɵdir: i0.ɵɵDirectiveDeclaration<SortableItemDirective<any>, "ng-template[uiSortableItem]", never, {}, {}, never, never, true, never>;
8016
+ }
8017
+ /** Emitted after the order changed (a drop, a keyboard move or a move button). */
8018
+ export interface SortableOrderChange<T> {
8019
+ item: T;
8020
+ from: number;
8021
+ to: number;
8022
+ /** The new full order. */
8023
+ items: T[];
8024
+ }
8025
+ /**
8026
+ * A list (or wrapping grid) of items that can optionally be reordered. Reordering is OFF by default: with `sortable` false it only
8027
+ * renders the items (nothing is draggable or focusable and no controls show), so it is safe to use as a plain list.
8028
+ *
8029
+ * <ui-sortable-list [(items)]="images" [sortable]="canReorder" layout="grid" ariaLabel="Product images"
8030
+ * [itemLabel]="imageLabel" (orderChange)="saveOrder($event)">
8031
+ * <ng-template uiSortableItem let-url let-i="index"> <ui-image-preview [src]="url" /> </ng-template>
8032
+ * </ui-sortable-list>
8033
+ *
8034
+ * With `sortable` on, an item can be moved by
8035
+ * - dragging it onto another item (mouse and any browser with native drag and drop; Firefox / Safari quirks are handled),
8036
+ * - the keyboard: focus an item and press Shift + Arrow keys (Shift + Home / End = first / last),
8037
+ * - the optional move buttons (`moveControls`), the touch- and pointer-friendly way (and the one that needs no gesture).
8038
+ * Every change updates `items` (two-way `[(items)]`) and emits `orderChange`; the host saves it and can set `items` back if the
8039
+ * save fails. A polite live region announces each move ("Image 3 moved to position 1 of 4").
8040
+ */
8041
+ export declare class SortableListComponent<T = unknown> {
8042
+ /** The items in their current order (two-way: `[(items)]`). */
8043
+ readonly items: import("@angular/core").ModelSignal<T[]>;
8044
+ /** Reordering on/off. Default OFF: the list only renders its items. */
8045
+ readonly sortable: import("@angular/core").InputSignal<boolean>;
8046
+ /** 'list' = one column; 'grid' = wrapping columns of at least `gridMin` px. */
8047
+ readonly layout: import("@angular/core").InputSignal<"list" | "grid">;
8048
+ readonly gridMin: import("@angular/core").InputSignal<number>;
8049
+ /** Accessible name of the list. */
8050
+ readonly ariaLabel: import("@angular/core").InputSignal<string>;
8051
+ /** Name of an item for assistive tech and tooltips ("Image 3"). */
8052
+ readonly itemLabel: import("@angular/core").InputSignal<(item: T, index: number) => string>;
8053
+ /** Show Move earlier / Move later buttons on each item (needs `sortable`) — the touch-friendly way to reorder. */
8054
+ readonly moveControls: import("@angular/core").InputSignal<boolean>;
8055
+ readonly moveEarlierLabel: import("@angular/core").InputSignal<string>;
8056
+ readonly moveLaterLabel: import("@angular/core").InputSignal<string>;
8057
+ /** Screen-reader instruction for a focused sortable item. */
8058
+ readonly helpText: import("@angular/core").InputSignal<string>;
8059
+ /** Announcement after a move; receives the item label, the new position (1-based) and the list size. */
8060
+ readonly moveAnnouncement: import("@angular/core").InputSignal<(label: string, position: number, total: number) => string>;
8061
+ /** The order changed. */
8062
+ readonly orderChange: import("@angular/core").OutputEmitterRef<SortableOrderChange<T>>;
8063
+ protected readonly itemTemplate: import("@angular/core").Signal<SortableItemDirective<any>>;
8064
+ protected readonly _helpId: string;
8065
+ protected readonly _dragIndex: import("@angular/core").WritableSignal<number | null>;
8066
+ protected readonly _overIndex: import("@angular/core").WritableSignal<number | null>;
8067
+ protected readonly _announcement: import("@angular/core").WritableSignal<string>;
8068
+ protected readonly _count: import("@angular/core").Signal<number>;
8069
+ /** Moves the item at `from` to position `to` (clamped). No-op when nothing changes or sorting is off. */
8070
+ move(from: number, to: number): void;
8071
+ protected onDragStart(event: DragEvent, index: number): void;
8072
+ protected onDragOver(event: DragEvent, index: number): void;
8073
+ protected onDrop(event: DragEvent, index: number): void;
8074
+ protected onDragEnd(): void;
8075
+ protected onKeydown(event: KeyboardEvent, index: number): void;
8076
+ static ɵfac: i0.ɵɵFactoryDeclaration<SortableListComponent<any>, never>;
8077
+ static ɵcmp: i0.ɵɵComponentDeclaration<SortableListComponent<any>, "ui-sortable-list", never, {
8078
+ "items": {
8079
+ "alias": "items";
8080
+ "required": false;
8081
+ "isSignal": true;
8082
+ };
8083
+ "sortable": {
8084
+ "alias": "sortable";
8085
+ "required": false;
8086
+ "isSignal": true;
8087
+ };
8088
+ "layout": {
8089
+ "alias": "layout";
8090
+ "required": false;
8091
+ "isSignal": true;
8092
+ };
8093
+ "gridMin": {
8094
+ "alias": "gridMin";
8095
+ "required": false;
8096
+ "isSignal": true;
8097
+ };
8098
+ "ariaLabel": {
8099
+ "alias": "ariaLabel";
8100
+ "required": false;
8101
+ "isSignal": true;
8102
+ };
8103
+ "itemLabel": {
8104
+ "alias": "itemLabel";
8105
+ "required": false;
8106
+ "isSignal": true;
8107
+ };
8108
+ "moveControls": {
8109
+ "alias": "moveControls";
8110
+ "required": false;
8111
+ "isSignal": true;
8112
+ };
8113
+ "moveEarlierLabel": {
8114
+ "alias": "moveEarlierLabel";
8115
+ "required": false;
8116
+ "isSignal": true;
8117
+ };
8118
+ "moveLaterLabel": {
8119
+ "alias": "moveLaterLabel";
8120
+ "required": false;
8121
+ "isSignal": true;
8122
+ };
8123
+ "helpText": {
8124
+ "alias": "helpText";
8125
+ "required": false;
8126
+ "isSignal": true;
8127
+ };
8128
+ "moveAnnouncement": {
8129
+ "alias": "moveAnnouncement";
8130
+ "required": false;
8131
+ "isSignal": true;
8132
+ };
8133
+ }, {
8134
+ "items": "itemsChange";
8135
+ "orderChange": "orderChange";
8136
+ }, ["itemTemplate"], never, true, never>;
8137
+ }
7256
8138
  export type ImagePreviewShape = 'circle' | 'rounded';
7257
8139
  export type ImagePreviewSize = 'sm' | 'md' | 'lg';
7258
8140
  export type ImagePreviewPlaceholder = 'person' | 'image';
@@ -7376,6 +8258,9 @@ export type KpiCardValueColor = 'default' | 'green' | 'blue';
7376
8258
  * role="button", focusable (tabindex 0, visible focus ring), and Enter / Space fire the host's (click). Give it an
7377
8259
  * accessible name with [attr.aria-label] when the label alone isn't enough (e.g. "Net Commission: $12.5K — show details").
7378
8260
  *
8261
+ * Optional ring: `[progress]="67"` (+ `progressMax`, `progressVariant`, `progressSize`) draws a ui-progress-ring beside the label; extra lines
8262
+ * (a pace badge, a projection…) go in an element with the `kpi-footer` attribute.
8263
+ *
7379
8264
  * <ui-kpi-card label="Deals Closed" [value]="'' + deals" subtitle="Year to Date" icon="check-circle" color="blue" />
7380
8265
  * <ui-kpi-card label="Net Commission" [value]="net" icon="dollar" color="green" [highlight]="true"
7381
8266
  * [clickable]="true" (click)="openDetails()" />
@@ -7392,6 +8277,20 @@ export declare class KpiCardComponent {
7392
8277
  readonly highlight: import("@angular/core").InputSignal<boolean>;
7393
8278
  readonly clickable: import("@angular/core").InputSignal<boolean>;
7394
8279
  readonly valueColor: import("@angular/core").InputSignal<KpiCardValueColor>;
8280
+ /** Completed amount shown as a `ui-progress-ring` beside the label. `null` (default) = no ring. */
8281
+ readonly progress: import("@angular/core").InputSignal<number | null>;
8282
+ /** Total for `progress` (default 100, so `progress` is a percentage). */
8283
+ readonly progressMax: import("@angular/core").InputSignal<number>;
8284
+ /** Ring diameter in px. */
8285
+ readonly progressSize: import("@angular/core").InputSignal<number>;
8286
+ readonly progressStrokeWidth: import("@angular/core").InputSignal<number>;
8287
+ /** Fixed ring color; leave it out and the color follows completion (red / amber / blue / green). */
8288
+ readonly progressVariant: import("@angular/core").InputSignal<ProgressRingVariant | null | undefined>;
8289
+ /** Accessible name of the ring; default "<label> progress". */
8290
+ readonly progressAriaLabel: import("@angular/core").InputSignal<string>;
8291
+ /** Human reading of the ring for assistive tech, e.g. "$200K of $300K". */
8292
+ readonly progressValueText: import("@angular/core").InputSignal<string>;
8293
+ protected readonly hasProgress: import("@angular/core").Signal<boolean>;
7395
8294
  /** Enter / Space on a clickable card act like a click (Space would otherwise scroll the page). */
7396
8295
  protected onActivateKey(event: Event): void;
7397
8296
  protected readonly iconSvg: import("@angular/core").Signal<SafeHtml | null>;
@@ -7437,7 +8336,42 @@ export declare class KpiCardComponent {
7437
8336
  "required": false;
7438
8337
  "isSignal": true;
7439
8338
  };
7440
- }, {}, never, never, true, never>;
8339
+ "progress": {
8340
+ "alias": "progress";
8341
+ "required": false;
8342
+ "isSignal": true;
8343
+ };
8344
+ "progressMax": {
8345
+ "alias": "progressMax";
8346
+ "required": false;
8347
+ "isSignal": true;
8348
+ };
8349
+ "progressSize": {
8350
+ "alias": "progressSize";
8351
+ "required": false;
8352
+ "isSignal": true;
8353
+ };
8354
+ "progressStrokeWidth": {
8355
+ "alias": "progressStrokeWidth";
8356
+ "required": false;
8357
+ "isSignal": true;
8358
+ };
8359
+ "progressVariant": {
8360
+ "alias": "progressVariant";
8361
+ "required": false;
8362
+ "isSignal": true;
8363
+ };
8364
+ "progressAriaLabel": {
8365
+ "alias": "progressAriaLabel";
8366
+ "required": false;
8367
+ "isSignal": true;
8368
+ };
8369
+ "progressValueText": {
8370
+ "alias": "progressValueText";
8371
+ "required": false;
8372
+ "isSignal": true;
8373
+ };
8374
+ }, {}, never, ["[kpi-footer]"], true, never>;
7441
8375
  }
7442
8376
  export type DatePreset = 'today' | '7days' | '30days' | 'month' | 'quarter' | 'ytd' | 'all' | 'custom';
7443
8377
  export interface DateRange {
@@ -8244,4 +9178,331 @@ export declare class ColorInputComponent implements ControlValueAccessor, Valida
8244
9178
  "invalidChange": "invalidChange";
8245
9179
  }, never, never, true, never>;
8246
9180
  }
9181
+ /**
9182
+ * A heading for a block of a page or form: an optional icon in a rounded tile, a bold title, a grey description
9183
+ * and optional buttons on the right. Use it above a group of settings or fields, alone or inside a `ui-detail-card`.
9184
+ *
9185
+ * <ui-section-header title="Notifications" />
9186
+ * <ui-section-header title="Email" description="Send and receive email from your own address." icon="email"
9187
+ * background="#eaf3ff" />
9188
+ * <ui-section-header title="Team" [bordered]="true">
9189
+ * <ui-button section-header-actions size="xs" variant="secondary">Add member</ui-button>
9190
+ * </ui-section-header>
9191
+ * <ui-section-header title="Billing" background="#0f172a" textColor="#fff" iconColor="#93c5fd" iconBackground="#1e293b">
9192
+ * <svg section-header-icon width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>
9193
+ * </ui-section-header>
9194
+ *
9195
+ * Inputs:
9196
+ * - `title` (required): the heading text.
9197
+ * - `description`: a grey line under the title. Empty = none. The heading is described by it (aria-describedby).
9198
+ * - `level` (1-6, default 2): the heading level for assistive tech (role="heading" + aria-level). Anything else becomes 2.
9199
+ * - `icon`: the name of a library icon, drawn in a rounded tile on the left (decorative, hidden from assistive tech).
9200
+ * Supported names (the same keys `<ui-icon>` accepts): add, close, check, upload, image, chevron-up, chevron-down, arrow-left,
9201
+ * bell, print, view, edit, delete, call, message, email, task, opportunity, assign-owner, assign-agent, view-contact, confirm,
9202
+ * decline, sphere, remove-sphere, portal-invite, archive, duplicate, download, export, share, approve, reject, activate,
9203
+ * deactivate, pause, cancel, restore, send-message, create-job, view-job, create-invoice, create-estimate, add-note, invite-portal.
9204
+ * An unknown name draws no tile and raises no error.
9205
+ * - `background`: any CSS colour for the whole header (default none/transparent). Setting it rounds the corners (10px).
9206
+ * - `textColor`: any CSS colour for the title (default slate #0f172a). The description then uses the same colour at 80% opacity.
9207
+ * Without it the description is #64748b, or #475569 when a `background` is set (keeps it readable on tinted backgrounds).
9208
+ * - `iconColor`: colour of the icon (default blue #2563eb).
9209
+ * - `iconBackground`: colour of the tile behind the icon (default white when a `background` is set, otherwise light grey #f1f5f9).
9210
+ * - `bordered`: 1px #e2e8f0 border and 10px radius (default false).
9211
+ * - `size`: 'md' (default; 1rem title, 28px tile) or 'sm' (0.875rem title, 24px tile).
9212
+ * Padding is built in (14px 18px; 12px 14px at 480px and narrower) - there is no padding input.
9213
+ *
9214
+ * Content slots:
9215
+ * - `[section-header-icon]`: your own icon (an `<svg>`, an `<img>` ...) shown in the tile instead of the `icon` input.
9216
+ * - `[section-header-actions]`: buttons or links on the right; they drop under the text when there is no room.
9217
+ *
9218
+ * Theming by CSS: the colours are the custom properties `--sh-bg`, `--sh-text`, `--sh-icon`, `--sh-icon-bg`; set them on
9219
+ * `ui-section-header` (or an ancestor) instead of using the inputs. An unset input adds no inline style at all.
9220
+ *
9221
+ * Accessibility: colours you choose are your responsibility - keep text at 4.5:1 against the background. The defaults
9222
+ * (#0f172a on white or #eaf3ff, #475569 description on tinted backgrounds) meet WCAG AA.
9223
+ */
9224
+ export declare class SectionHeaderComponent {
9225
+ /** The heading text. */
9226
+ readonly title: import("@angular/core").InputSignal<string>;
9227
+ /** Grey line under the title. Empty = none. */
9228
+ readonly description: import("@angular/core").InputSignal<string>;
9229
+ /** Name of a library icon (see the list above). Empty or unknown = no icon. */
9230
+ readonly icon: import("@angular/core").InputSignal<string>;
9231
+ /** Heading level 1-6 for assistive tech; anything else is treated as 2. */
9232
+ readonly level: import("@angular/core").InputSignal<number>;
9233
+ /** Header background as any CSS colour. Empty = none. */
9234
+ readonly background: import("@angular/core").InputSignal<string>;
9235
+ /** Title colour as any CSS colour. Empty = slate. */
9236
+ readonly textColor: import("@angular/core").InputSignal<string>;
9237
+ /** Icon colour as any CSS colour. Empty = blue. */
9238
+ readonly iconColor: import("@angular/core").InputSignal<string>;
9239
+ /** Icon tile colour as any CSS colour. Empty = white on a `background`, otherwise light grey. */
9240
+ readonly iconBackground: import("@angular/core").InputSignal<string>;
9241
+ /** 1px border and 10px corners. */
9242
+ readonly bordered: import("@angular/core").InputSignal<boolean>;
9243
+ /** 'md' (default) or 'sm'. */
9244
+ readonly size: import("@angular/core").InputSignal<"sm" | "md">;
9245
+ protected readonly titleId: string;
9246
+ protected readonly descriptionId: string;
9247
+ protected readonly headingLevel: import("@angular/core").Signal<number>;
9248
+ protected readonly path: import("@angular/core").Signal<string | null>;
9249
+ static ɵfac: i0.ɵɵFactoryDeclaration<SectionHeaderComponent, never>;
9250
+ static ɵcmp: i0.ɵɵComponentDeclaration<SectionHeaderComponent, "ui-section-header", never, {
9251
+ "title": {
9252
+ "alias": "title";
9253
+ "required": true;
9254
+ "isSignal": true;
9255
+ };
9256
+ "description": {
9257
+ "alias": "description";
9258
+ "required": false;
9259
+ "isSignal": true;
9260
+ };
9261
+ "icon": {
9262
+ "alias": "icon";
9263
+ "required": false;
9264
+ "isSignal": true;
9265
+ };
9266
+ "level": {
9267
+ "alias": "level";
9268
+ "required": false;
9269
+ "isSignal": true;
9270
+ };
9271
+ "background": {
9272
+ "alias": "background";
9273
+ "required": false;
9274
+ "isSignal": true;
9275
+ };
9276
+ "textColor": {
9277
+ "alias": "textColor";
9278
+ "required": false;
9279
+ "isSignal": true;
9280
+ };
9281
+ "iconColor": {
9282
+ "alias": "iconColor";
9283
+ "required": false;
9284
+ "isSignal": true;
9285
+ };
9286
+ "iconBackground": {
9287
+ "alias": "iconBackground";
9288
+ "required": false;
9289
+ "isSignal": true;
9290
+ };
9291
+ "bordered": {
9292
+ "alias": "bordered";
9293
+ "required": false;
9294
+ "isSignal": true;
9295
+ };
9296
+ "size": {
9297
+ "alias": "size";
9298
+ "required": false;
9299
+ "isSignal": true;
9300
+ };
9301
+ }, {}, never, ["[section-header-icon]", "[section-header-actions]"], true, never>;
9302
+ }
9303
+ /**
9304
+ * Shared spacing scale of the layout components (`ui-columns`, `ui-stack`, `ui-action-bar`):
9305
+ * none 0 · xs 4px · sm 8px · md 16px · lg 24px · xl 32px
9306
+ * Every `gap` input also accepts any CSS length string for a custom value, e.g. `gap="20px"` or `gap="1.25rem"`.
9307
+ */
9308
+ export type LayoutGap = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
9309
+ export declare const LAYOUT_GAP_PX: Readonly<Record<LayoutGap, number>>;
9310
+ /**
9311
+ * Resolves a gap token or a custom CSS length to a CSS length. A token maps to its px value; a string that looks like a
9312
+ * CSS length (`20px`, `1.5rem`, `0`, `calc(1rem + 2px)`, `var(--x)`) passes through trimmed; anything else
9313
+ * ('' / null / undefined / an unknown word) falls back to `fallback`.
9314
+ */
9315
+ export declare function resolveLayoutGap(gap: LayoutGap | string | null | undefined, fallback: LayoutGap): string;
9316
+ export type ColumnsCount = 1 | 2 | 3 | 4;
9317
+ /** Width below which the columns stack into one: sm = 480px, md = 768px, lg = 1024px, never = always side by side. */
9318
+ export type ColumnsCollapse = 'sm' | 'md' | 'lg' | 'never';
9319
+ export type ColumnsAlign = 'start' | 'center' | 'stretch';
9320
+ /**
9321
+ * Equal-width columns for page content (the bigger sibling of `ui-form-row`).
9322
+ *
9323
+ * `ui-form-row` is for 1-3 form fields: a fixed 12px gap, and it stacks at 480px. `ui-columns` lays out whole blocks
9324
+ * (cards, sections, lists): 1-4 columns, a gap from the shared scale (default 24px), and it stacks at a width you choose
9325
+ * (default 768px, tablet portrait). Every direct child is one cell. Cells use `minmax(0, 1fr)` tracks, so a long
9326
+ * unbreakable word or a wide table never stretches the grid.
9327
+ *
9328
+ * - `count`: 1 | 2 | 3 | 4 equal columns (default 2). Other values are clamped to 1..4 (0 and negatives give 1, 5+ gives 4,
9329
+ * a non-number gives 2).
9330
+ * - `gap`: none | xs | sm | md | lg | xl (0/4/8/16/24/32px) or any CSS length such as `"20px"` (default `lg`).
9331
+ * - `collapseAt`: sm (480px) | md (768px) | lg (1024px) | never. Below that viewport width the columns become one.
9332
+ * Plain media queries are used (not container queries) because they behave the same in every supported browser.
9333
+ * - `align`: start | center | stretch (default start) - how cells of different heights line up.
9334
+ *
9335
+ * <ui-columns>
9336
+ * <ui-form-section label="Email"> ... </ui-form-section>
9337
+ * <ui-form-section label="Text"> ... </ui-form-section>
9338
+ * </ui-columns>
9339
+ * <ui-columns [count]="3" gap="md" collapseAt="lg" align="stretch"> ... </ui-columns>
9340
+ */
9341
+ export declare class ColumnsComponent {
9342
+ readonly count: import("@angular/core").InputSignal<ColumnsCount>;
9343
+ readonly gap: import("@angular/core").InputSignal<string>;
9344
+ readonly collapseAt: import("@angular/core").InputSignal<ColumnsCollapse>;
9345
+ readonly align: import("@angular/core").InputSignal<ColumnsAlign>;
9346
+ protected readonly cols: import("@angular/core").Signal<ColumnsCount>;
9347
+ protected readonly gapCss: import("@angular/core").Signal<string>;
9348
+ protected readonly collapseClass: import("@angular/core").Signal<string>;
9349
+ static ɵfac: i0.ɵɵFactoryDeclaration<ColumnsComponent, never>;
9350
+ static ɵcmp: i0.ɵɵComponentDeclaration<ColumnsComponent, "ui-columns", never, {
9351
+ "count": {
9352
+ "alias": "count";
9353
+ "required": false;
9354
+ "isSignal": true;
9355
+ };
9356
+ "gap": {
9357
+ "alias": "gap";
9358
+ "required": false;
9359
+ "isSignal": true;
9360
+ };
9361
+ "collapseAt": {
9362
+ "alias": "collapseAt";
9363
+ "required": false;
9364
+ "isSignal": true;
9365
+ };
9366
+ "align": {
9367
+ "alias": "align";
9368
+ "required": false;
9369
+ "isSignal": true;
9370
+ };
9371
+ }, {}, never, ["*"], true, never>;
9372
+ }
9373
+ export type StackDirection = 'vertical' | 'horizontal';
9374
+ export type StackAlign = 'start' | 'center' | 'end' | 'stretch';
9375
+ export type StackJustify = 'start' | 'center' | 'end' | 'between';
9376
+ /**
9377
+ * Stacks its children with ONE consistent gap (vertical by default, or in a row).
9378
+ *
9379
+ * The stack owns the spacing: it resets the margins of its direct children (with `!important`, so it also beats
9380
+ * rules such as the `margin-bottom: 16px` of `ui-detail-card`)
9381
+ * and uses flex `gap` instead. The distance between any two neighbours is exactly `gap`, whatever they are.
9382
+ * Do not put margins on children of a stack; wrap them in a nested `ui-stack` to group. A `ui-divider` keeps its space
9383
+ * inside itself, so give it `spaceAbove="none" spaceBelow="none"` in a stack.
9384
+ *
9385
+ * - `gap`: none | xs | sm | md | lg | xl (0/4/8/16/24/32px) or any CSS length (`gap="20px"`). Default `md`.
9386
+ * - `direction`: vertical (default) | horizontal.
9387
+ * - `align`: start | center | end | stretch (cross axis, default stretch).
9388
+ * - `justify`: start | center | end | between (main axis, default start).
9389
+ * - `wrap`: let a horizontal stack wrap onto more lines (default false).
9390
+ *
9391
+ * <ui-stack gap="20px">
9392
+ * <ui-detail-card title="EMAIL"> ... </ui-detail-card>
9393
+ * <ui-detail-card title="TEXT"> ... </ui-detail-card>
9394
+ * </ui-stack>
9395
+ * <ui-stack direction="horizontal" gap="sm" justify="between" [wrap]="true"> ...buttons... </ui-stack>
9396
+ */
9397
+ export declare class StackComponent {
9398
+ readonly gap: import("@angular/core").InputSignal<string>;
9399
+ readonly direction: import("@angular/core").InputSignal<StackDirection>;
9400
+ readonly align: import("@angular/core").InputSignal<StackAlign>;
9401
+ readonly justify: import("@angular/core").InputSignal<StackJustify>;
9402
+ readonly wrap: import("@angular/core").InputSignal<boolean>;
9403
+ protected readonly gapCss: import("@angular/core").Signal<string>;
9404
+ protected readonly alignCss: import("@angular/core").Signal<string>;
9405
+ protected readonly justifyCss: import("@angular/core").Signal<string>;
9406
+ static ɵfac: i0.ɵɵFactoryDeclaration<StackComponent, never>;
9407
+ static ɵcmp: i0.ɵɵComponentDeclaration<StackComponent, "ui-stack", never, {
9408
+ "gap": {
9409
+ "alias": "gap";
9410
+ "required": false;
9411
+ "isSignal": true;
9412
+ };
9413
+ "direction": {
9414
+ "alias": "direction";
9415
+ "required": false;
9416
+ "isSignal": true;
9417
+ };
9418
+ "align": {
9419
+ "alias": "align";
9420
+ "required": false;
9421
+ "isSignal": true;
9422
+ };
9423
+ "justify": {
9424
+ "alias": "justify";
9425
+ "required": false;
9426
+ "isSignal": true;
9427
+ };
9428
+ "wrap": {
9429
+ "alias": "wrap";
9430
+ "required": false;
9431
+ "isSignal": true;
9432
+ };
9433
+ }, {}, never, ["*"], true, never>;
9434
+ }
9435
+ export type ActionBarAlign = 'start' | 'center' | 'end' | 'between';
9436
+ /**
9437
+ * Bar for page-level actions (Save / Cancel) that stays at the bottom of the scrolling area.
9438
+ *
9439
+ * - `sticky` (default true): sticks to the bottom edge of the scroll container while the page content scrolls under it.
9440
+ * - `align`: start | center | end | between (default end).
9441
+ * - `fade` (default true): a gradient from the bar colour to transparent behind the bar so scrolled content fades out.
9442
+ * - `bordered` (default false): a 1px top border and a solid background instead of the fade; side padding 16px.
9443
+ * - `background`: the bar colour (any CSS colour; default white). Sets the CSS variable `--ab-bg`, which you can
9444
+ * also set from outside.
9445
+ * - `gap`: none | xs | sm | md | lg | xl or any CSS length (default `sm` = 8px).
9446
+ * - `ariaLabel`: when set, the bar is `role="group"` with this label; otherwise it has no role. (It is not a `toolbar`: that role promises
9447
+ * arrow-key navigation between the controls, which a plain Save / Cancel group does not implement.)
9448
+ *
9449
+ * Padding is 12px top and bottom (the bottom grows to the iOS safe-area inset), z-index 10 so it stays under the side
9450
+ * panel (90+) and modals (200+). Below 480px the actions wrap onto more lines; `ui-button` keeps 44px touch targets.
9451
+ * Sticky needs the bar to be a direct part of the scrolling content, with the scroll container as an ancestor.
9452
+ *
9453
+ * <ui-stack gap="20px"> ...cards... </ui-stack>
9454
+ * <ui-action-bar ariaLabel="Page actions">
9455
+ * <ui-button variant="secondary" (click)="cancel()">Cancel</ui-button>
9456
+ * <ui-button [loading]="saving()" (click)="save()">Save</ui-button>
9457
+ * </ui-action-bar>
9458
+ */
9459
+ export declare class ActionBarComponent {
9460
+ readonly sticky: import("@angular/core").InputSignal<boolean>;
9461
+ readonly align: import("@angular/core").InputSignal<ActionBarAlign>;
9462
+ readonly fade: import("@angular/core").InputSignal<boolean>;
9463
+ readonly bordered: import("@angular/core").InputSignal<boolean>;
9464
+ readonly background: import("@angular/core").InputSignal<string>;
9465
+ readonly gap: import("@angular/core").InputSignal<string>;
9466
+ readonly ariaLabel: import("@angular/core").InputSignal<string>;
9467
+ protected readonly justify: import("@angular/core").Signal<string>;
9468
+ protected readonly gapCss: import("@angular/core").Signal<string>;
9469
+ static ɵfac: i0.ɵɵFactoryDeclaration<ActionBarComponent, never>;
9470
+ static ɵcmp: i0.ɵɵComponentDeclaration<ActionBarComponent, "ui-action-bar", never, {
9471
+ "sticky": {
9472
+ "alias": "sticky";
9473
+ "required": false;
9474
+ "isSignal": true;
9475
+ };
9476
+ "align": {
9477
+ "alias": "align";
9478
+ "required": false;
9479
+ "isSignal": true;
9480
+ };
9481
+ "fade": {
9482
+ "alias": "fade";
9483
+ "required": false;
9484
+ "isSignal": true;
9485
+ };
9486
+ "bordered": {
9487
+ "alias": "bordered";
9488
+ "required": false;
9489
+ "isSignal": true;
9490
+ };
9491
+ "background": {
9492
+ "alias": "background";
9493
+ "required": false;
9494
+ "isSignal": true;
9495
+ };
9496
+ "gap": {
9497
+ "alias": "gap";
9498
+ "required": false;
9499
+ "isSignal": true;
9500
+ };
9501
+ "ariaLabel": {
9502
+ "alias": "ariaLabel";
9503
+ "required": false;
9504
+ "isSignal": true;
9505
+ };
9506
+ }, {}, never, ["*"], true, never>;
9507
+ }
8247
9508
  export type { CreateDocumentRequestBody, DocuSignSendBody, DocumentSentInfo, DocumentSentMessage, DocumentsPayloadExtras, ExistingEnvelopeSendBody, SendDocumentsPayload, SendEmailPayload, SendTextPayload, VideoDefaults, VideoLinkOptions };