@forgeroute/ui 0.4.20 → 0.4.22

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.
@@ -480,6 +480,8 @@ export declare class PaginationComponent {
480
480
  readonly totalItems: import("@angular/core").InputSignal<number>;
481
481
  /** Standalone look (full border, full radius, small top margin) for use under separate cards rather than flush under a table. */
482
482
  readonly detached: import("@angular/core").InputSignal<boolean>;
483
+ /** Embedded look for a container that already has its own background and edge (a `ui-detail-card` footer, a panel): transparent, no border, no radius, no padding. Default false: unchanged. Wins over `detached`. */
484
+ readonly embedded: import("@angular/core").InputSignal<boolean>;
483
485
  readonly pageChange: import("@angular/core").OutputEmitterRef<number>;
484
486
  readonly totalPages: import("@angular/core").Signal<number>;
485
487
  readonly startItem: import("@angular/core").Signal<number>;
@@ -508,6 +510,11 @@ export declare class PaginationComponent {
508
510
  "required": false;
509
511
  "isSignal": true;
510
512
  };
513
+ "embedded": {
514
+ "alias": "embedded";
515
+ "required": false;
516
+ "isSignal": true;
517
+ };
511
518
  }, {
512
519
  "pageChange": "pageChange";
513
520
  }, never, never, true, never>;
@@ -653,12 +660,45 @@ export declare class BadgeComponent {
653
660
  };
654
661
  }, {}, never, never, true, never>;
655
662
  }
663
+ /**
664
+ * Round avatar: initials on a colour derived from them (default), or a photo when `imageUrl` is set. The photo falls back to the
665
+ * initials by itself when it fails to load (and tries again when `imageUrl` changes).
666
+ *
667
+ * <ui-avatar initials="JS" name="Jane Smith" [size]="72" [imageUrl]="photoUrl()" />
668
+ *
669
+ * Editable (a profile picture the user can change): `[editable]="true"` makes the avatar a keyboard button (role="button",
670
+ * tabindex 0, Enter / Space / click) that emits `editClick` — the host opens its file picker and uploads — with a small camera
671
+ * badge in the corner. `editLabel` is its name for assistive tech and tooltip (default "Change photo"). `[busy]="true"` while the
672
+ * upload runs: spinner in the badge, the photo is dimmed, `aria-busy` is set and clicks / keys do nothing.
673
+ *
674
+ * <ui-avatar initials="JS" name="Jane Smith" [size]="72" [imageUrl]="photoUrl()" [editable]="canEdit()" [busy]="uploading()" (editClick)="picker.open()" />
675
+ *
676
+ * Without `imageUrl` and `editable` nothing changes from the plain initials avatar.
677
+ */
656
678
  export declare class AvatarComponent {
657
679
  readonly initials: import("@angular/core").InputSignal<string>;
658
680
  readonly name: import("@angular/core").InputSignal<string>;
659
681
  readonly size: import("@angular/core").InputSignal<number>;
682
+ /** Photo URL. Empty / null = initials. A photo that fails to load falls back to the initials until the URL changes. */
683
+ readonly imageUrl: import("@angular/core").InputSignal<string | null | undefined>;
684
+ /** true = the avatar is a button that emits `editClick` (change the photo), with a camera badge. Default false: unchanged. */
685
+ readonly editable: import("@angular/core").InputSignal<boolean>;
686
+ /** Name of the editable avatar for assistive tech and the tooltip. */
687
+ readonly editLabel: import("@angular/core").InputSignal<string>;
688
+ /** The host is uploading: spinner badge, dimmed photo, aria-busy, no clicks. */
689
+ readonly busy: import("@angular/core").InputSignal<boolean>;
690
+ /** Fires on click / Enter / Space of an editable avatar (not while busy). */
691
+ readonly editClick: import("@angular/core").OutputEmitterRef<void>;
692
+ /** Fires when the photo could not be loaded (the initials are shown instead). */
693
+ readonly imageError: import("@angular/core").OutputEmitterRef<void>;
694
+ private readonly failedUrl;
695
+ protected readonly showImage: import("@angular/core").Signal<boolean>;
696
+ protected readonly badgeSize: import("@angular/core").Signal<number>;
660
697
  protected readonly bg: import("@angular/core").Signal<string>;
661
698
  protected readonly color: import("@angular/core").Signal<string>;
699
+ protected onImageError(): void;
700
+ protected onEdit(): void;
701
+ protected onKey(event: Event): void;
662
702
  static ɵfac: i0.ɵɵFactoryDeclaration<AvatarComponent, never>;
663
703
  static ɵcmp: i0.ɵɵComponentDeclaration<AvatarComponent, "ui-avatar", never, {
664
704
  "initials": {
@@ -676,7 +716,30 @@ export declare class AvatarComponent {
676
716
  "required": false;
677
717
  "isSignal": true;
678
718
  };
679
- }, {}, never, never, true, never>;
719
+ "imageUrl": {
720
+ "alias": "imageUrl";
721
+ "required": false;
722
+ "isSignal": true;
723
+ };
724
+ "editable": {
725
+ "alias": "editable";
726
+ "required": false;
727
+ "isSignal": true;
728
+ };
729
+ "editLabel": {
730
+ "alias": "editLabel";
731
+ "required": false;
732
+ "isSignal": true;
733
+ };
734
+ "busy": {
735
+ "alias": "busy";
736
+ "required": false;
737
+ "isSignal": true;
738
+ };
739
+ }, {
740
+ "editClick": "editClick";
741
+ "imageError": "imageError";
742
+ }, never, never, true, never>;
680
743
  }
681
744
  export interface MenuItem {
682
745
  type: string;
@@ -959,6 +1022,12 @@ export interface SelectOption<V extends string | number = string> {
959
1022
  * Options without it are unchanged.
960
1023
  */
961
1024
  badge?: string;
1025
+ /**
1026
+ * Optional: list the choice but make it unselectable (native `<option disabled>`). Use it for an entry that must stay visible because it is the
1027
+ * CURRENT value (e.g. an owner who is no longer active) but must not be picked. A disabled option that equals the current value is still shown as
1028
+ * selected and still counts as a valid value; the user just cannot choose it again after picking another option. Options without it are unchanged.
1029
+ */
1030
+ disabled?: boolean;
962
1031
  }
963
1032
  export declare class SelectComponent implements ControlValueAccessor, Validator, UiFieldValidity {
964
1033
  readonly label: import("@angular/core").InputSignal<string>;
@@ -1244,7 +1313,13 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1244
1313
  * tabs are all off, the header row is not rendered at all.
1245
1314
  */
1246
1315
  readonly showQuickTabs: import("@angular/core").InputSignal<boolean>;
1247
- /** False when the header row would be empty (no search box, no toolbar, no quick-filter tabs). */
1316
+ /**
1317
+ * Optional heading shown at the start of the header row, on the same row as the search box / tabs on the left and the
1318
+ * Export / primary buttons on the right (rendered as an `<h3>`, also while the table is loading and when it is empty).
1319
+ * Empty (default) renders nothing. Use it when the table is a section of a page and its title belongs with its toolbar.
1320
+ */
1321
+ readonly tableTitle: import("@angular/core").InputSignal<string>;
1322
+ /** False when the header row would be empty (no title, no search box, no toolbar, no quick-filter tabs). */
1248
1323
  protected readonly _showHeader: Signal<boolean>;
1249
1324
  /** Optional endpoint that returns Record<string,number> counts per quick-filter value. */
1250
1325
  readonly countsEndpoint: import("@angular/core").InputSignal<string>;
@@ -1777,6 +1852,11 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1777
1852
  "required": false;
1778
1853
  "isSignal": true;
1779
1854
  };
1855
+ "tableTitle": {
1856
+ "alias": "tableTitle";
1857
+ "required": false;
1858
+ "isSignal": true;
1859
+ };
1780
1860
  "countsEndpoint": {
1781
1861
  "alias": "countsEndpoint";
1782
1862
  "required": false;
@@ -4191,6 +4271,11 @@ export declare class ConfirmOutletComponent implements OnInit, OnDestroy {
4191
4271
  static ɵfac: i0.ɵɵFactoryDeclaration<ConfirmOutletComponent, never>;
4192
4272
  static ɵcmp: i0.ɵɵComponentDeclaration<ConfirmOutletComponent, "ui-confirm-outlet", never, {}, {}, never, never, true, never>;
4193
4273
  }
4274
+ /**
4275
+ * Centered empty state: icon in a round tile, optional bold title, message and a projected action.
4276
+ * `iconType`: search, table, chart, users, person, message, email, task, house, money, star, video, shield, pipeline (funnel),
4277
+ * property (building), clipboard (clipboard with check and lines), document (default; any unknown value also draws it).
4278
+ */
4194
4279
  export declare class EmptyStateComponent {
4195
4280
  readonly message: import("@angular/core").InputSignal<string>;
4196
4281
  readonly title: import("@angular/core").InputSignal<string>;
@@ -6116,6 +6201,226 @@ export declare class ListItemComponent {
6116
6201
  "select": "select";
6117
6202
  }, never, ["[list-item-action], [li-action]"], true, never>;
6118
6203
  }
6204
+ /** Colour of the priority dot (`high` red, `medium` amber, `low` grey, `blue`, `green`); `none` = no dot. */
6205
+ export type CardListIndicator = 'high' | 'medium' | 'low' | 'blue' | 'green' | 'none';
6206
+ /** Icon of the round marker on the left. */
6207
+ export type CardListIcon = 'call' | 'email' | 'sms' | 'task' | 'doc' | 'money' | 'deal' | 'survey' | 'property' | 'search' | 'heart' | 'calc' | 'repeat';
6208
+ /**
6209
+ * Record row for the cards of a detail page (tasks, documents, deals, expenses, matches, surveys...). The layout is fixed;
6210
+ * pass inputs only:
6211
+ *
6212
+ * [dot | round icon] [title] [type badge] [status badge] [Edit] [View]
6213
+ * [description] [inline link]
6214
+ * [calendar icon + date]
6215
+ *
6216
+ * Each row is a bordered rounded box, 8px apart; put the rows straight in a `ui-detail-card` body (or in a `ui-list` template).
6217
+ *
6218
+ * <ui-card-list-item title="Call John" indicator="high" type="Meeting" status="Pending" statusVariant="gray"
6219
+ * date="Due Aug 21, 2026" description="Follow up about the offer"
6220
+ * [editButton]="true" (editClick)="edit(t)" [viewButton]="true" (viewClick)="view(t)" />
6221
+ *
6222
+ * Links: the library has no router, so a link is content you project and the app owns (`routerLink` etc.):
6223
+ * - `<a card-list-action class="cli-chip cli-chip--view" routerLink="/x">View</a>` at the row end (blue chip, like the View button);
6224
+ * - `<a card-list-inline class="cli-inline-link" routerLink="/x">View ...</a>` at the end of the description line.
6225
+ * Other projected content (default slot) shows after the chips at the row end; `[card-stats]` and `[card-detail]` add a footer row
6226
+ * under the row (use the `.cli-stat`, `.cli-stat-label`, `.cli-stat-value`, `.cli-stat-divider` classes inside `[card-stats]`).
6227
+ *
6228
+ * Accessibility: the Edit / View buttons are real buttons; give them a longer name with `editAriaLabel` / `viewAriaLabel`
6229
+ * ("Edit task Call John") when the visible word alone is ambiguous in a list. The priority dot (`high`/`medium`/`low`) has a tooltip and an
6230
+ * accessible name (`High priority`, ...), is focusable so the tooltip works by keyboard; `blue`/`green` dots are decorative. The icon circle is decorative.
6231
+ */
6232
+ export declare class CardListItemComponent {
6233
+ readonly title: import("@angular/core").InputSignal<string>;
6234
+ readonly description: import("@angular/core").InputSignal<string | undefined>;
6235
+ /** Date text with a small calendar icon, e.g. "Due Aug 21, 2026". Pre-formatted by the page. */
6236
+ readonly date: import("@angular/core").InputSignal<string | undefined>;
6237
+ /** Badge after the title; `typeVariant` colours it (default `gray`). */
6238
+ readonly type: import("@angular/core").InputSignal<string | undefined>;
6239
+ readonly typeVariant: import("@angular/core").InputSignal<BadgeVariant>;
6240
+ /** Second badge; shown only together with `statusVariant`. */
6241
+ readonly status: import("@angular/core").InputSignal<string | undefined>;
6242
+ readonly statusVariant: import("@angular/core").InputSignal<BadgeVariant | undefined>;
6243
+ /** Priority dot on the left (ignored when `icon` is set). */
6244
+ readonly indicator: import("@angular/core").InputSignal<CardListIndicator>;
6245
+ /** Round icon on the left (wins over `indicator`). */
6246
+ readonly icon: import("@angular/core").InputSignal<CardListIcon | undefined>;
6247
+ /** Built-in "Edit" chip button -> `editClick`. */
6248
+ readonly editButton: import("@angular/core").InputSignal<boolean>;
6249
+ /** Built-in "View" chip button -> `viewClick`. */
6250
+ readonly viewButton: import("@angular/core").InputSignal<boolean>;
6251
+ /** Accessible names for the buttons when the visible word is not enough ("Edit task Call John"). */
6252
+ readonly editAriaLabel: import("@angular/core").InputSignal<string>;
6253
+ readonly viewAriaLabel: import("@angular/core").InputSignal<string>;
6254
+ readonly editClick: import("@angular/core").OutputEmitterRef<void>;
6255
+ readonly viewClick: import("@angular/core").OutputEmitterRef<void>;
6256
+ protected readonly calendarPath = "M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z M16 2v4 M8 2v4 M3 10h18";
6257
+ protected readonly editPath = "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7 M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z";
6258
+ protected readonly iconPath: import("@angular/core").Signal<string | null>;
6259
+ protected readonly indicatorLabel: import("@angular/core").Signal<string>;
6260
+ static ɵfac: i0.ɵɵFactoryDeclaration<CardListItemComponent, never>;
6261
+ static ɵcmp: i0.ɵɵComponentDeclaration<CardListItemComponent, "ui-card-list-item", never, {
6262
+ "title": {
6263
+ "alias": "title";
6264
+ "required": false;
6265
+ "isSignal": true;
6266
+ };
6267
+ "description": {
6268
+ "alias": "description";
6269
+ "required": false;
6270
+ "isSignal": true;
6271
+ };
6272
+ "date": {
6273
+ "alias": "date";
6274
+ "required": false;
6275
+ "isSignal": true;
6276
+ };
6277
+ "type": {
6278
+ "alias": "type";
6279
+ "required": false;
6280
+ "isSignal": true;
6281
+ };
6282
+ "typeVariant": {
6283
+ "alias": "typeVariant";
6284
+ "required": false;
6285
+ "isSignal": true;
6286
+ };
6287
+ "status": {
6288
+ "alias": "status";
6289
+ "required": false;
6290
+ "isSignal": true;
6291
+ };
6292
+ "statusVariant": {
6293
+ "alias": "statusVariant";
6294
+ "required": false;
6295
+ "isSignal": true;
6296
+ };
6297
+ "indicator": {
6298
+ "alias": "indicator";
6299
+ "required": false;
6300
+ "isSignal": true;
6301
+ };
6302
+ "icon": {
6303
+ "alias": "icon";
6304
+ "required": false;
6305
+ "isSignal": true;
6306
+ };
6307
+ "editButton": {
6308
+ "alias": "editButton";
6309
+ "required": false;
6310
+ "isSignal": true;
6311
+ };
6312
+ "viewButton": {
6313
+ "alias": "viewButton";
6314
+ "required": false;
6315
+ "isSignal": true;
6316
+ };
6317
+ "editAriaLabel": {
6318
+ "alias": "editAriaLabel";
6319
+ "required": false;
6320
+ "isSignal": true;
6321
+ };
6322
+ "viewAriaLabel": {
6323
+ "alias": "viewAriaLabel";
6324
+ "required": false;
6325
+ "isSignal": true;
6326
+ };
6327
+ }, {
6328
+ "editClick": "editClick";
6329
+ "viewClick": "viewClick";
6330
+ }, never, ["[card-list-inline]", "[card-list-action]", "*", "[card-stats]", "[card-detail]"], true, never>;
6331
+ }
6332
+ /** A note as the notes API returns it. */
6333
+ export interface NoteItem {
6334
+ id: string;
6335
+ body: string;
6336
+ createdByName: string;
6337
+ createdAt: string;
6338
+ updatedAt: string;
6339
+ }
6340
+ /**
6341
+ * Longest note body, in characters, counted AFTER trimming. Matches the backend `NoteController` cap (10000 after trim; the server trims first,
6342
+ * so leading / trailing spaces never count). The add box and the edit box block Add Note / Save above it and show "Too long (max 10000 characters).".
6343
+ */
6344
+ export declare const NOTES_MAX_LENGTH = 10000;
6345
+ /**
6346
+ * Notes for one record: a soft "Add a note" panel, then one clean card per note (author avatar, name, date, edit / delete icons, the text),
6347
+ * inline edit and a delete confirmation. Notes are listed 5 per page in the server's order. It talks to the notes API itself, so a page only names the record:
6348
+ *
6349
+ * <ui-notes resourceType="contact" [resourceId]="contactId" [readOnly]="!canEdit()" (totalChange)="notesTotal.set($event)" />
6350
+ *
6351
+ * API (under `FORGEROUTE_CONFIG.baseUrl`, default `/api`):
6352
+ * - `GET /notes/{resourceType}/{resourceId}?page=<0-based>&size=5` -> `{ content: NoteItem[], totalElements, totalPages, page, size }`
6353
+ * - `POST /notes/{resourceType}/{resourceId}` `{ body }` -> the new note
6354
+ * - `PUT /notes/{noteId}` `{ body }` -> the updated note
6355
+ * - `DELETE /notes/{noteId}`
6356
+ *
6357
+ * `readOnly` lists the notes only (no box, no edit / delete). `totalChange` emits the total after every load. Failures show a
6358
+ * library toast with the server's message. Put it in a `ui-detail-card`: the card's body padding is the only outer inset.
6359
+ * A note may hold at most `NOTES_MAX_LENGTH` (10000) characters after trimming; longer text (typed or pasted) shows the field's
6360
+ * "Too long" message at once and Add Note / Save stay disabled, so no request is sent.
6361
+ * Pagination is the library `ui-pagination` (shown only above one page). Cmd/Ctrl+Enter in the add box adds the note.
6362
+ */
6363
+ export declare class NotesComponent {
6364
+ /** What the notes belong to, as the API names it: `contact`, `transaction`, `property`, `task`, ... */
6365
+ readonly resourceType: import("@angular/core").InputSignal<string>;
6366
+ readonly resourceId: import("@angular/core").InputSignal<string>;
6367
+ /** True: list only, no add box, edit or delete. */
6368
+ readonly readOnly: import("@angular/core").InputSignal<boolean>;
6369
+ /** Total number of notes after every load / add / delete. */
6370
+ readonly totalChange: import("@angular/core").OutputEmitterRef<number>;
6371
+ private readonly http;
6372
+ private readonly toast;
6373
+ private readonly destroyRef;
6374
+ private readonly apiBase;
6375
+ readonly pageSize = 5;
6376
+ /** The character limit of a note (see `NOTES_MAX_LENGTH`). */
6377
+ readonly maxLength = 10000;
6378
+ readonly notes: import("@angular/core").WritableSignal<NoteItem[]>;
6379
+ readonly loading: import("@angular/core").WritableSignal<boolean>;
6380
+ readonly saving: import("@angular/core").WritableSignal<boolean>;
6381
+ /** 0-based page index, as the server numbers it. */
6382
+ readonly page: import("@angular/core").WritableSignal<number>;
6383
+ readonly totalElements: import("@angular/core").WritableSignal<number>;
6384
+ readonly editingId: import("@angular/core").WritableSignal<string | null>;
6385
+ readonly deletingId: import("@angular/core").WritableSignal<string | null>;
6386
+ newBody: string;
6387
+ editBody: string;
6388
+ /** Up to two initials of the author's name for the avatar ("Ana Lopez" -> "AL"); "Y" for the current user's own unnamed notes. */
6389
+ protected initials(name: string): string;
6390
+ /** True when the trimmed text is longer than the limit. */
6391
+ protected tooLong(text: string): boolean;
6392
+ /** True when the trimmed text is not empty and within the limit. */
6393
+ protected validBody(text: string): boolean;
6394
+ private get listUrl();
6395
+ constructor();
6396
+ loadPage(p: number): void;
6397
+ addNote(): void;
6398
+ startEdit(note: NoteItem): void;
6399
+ cancelEdit(): void;
6400
+ saveEdit(noteId: string): void;
6401
+ confirmDelete(id: string): void;
6402
+ deleteNote(): void;
6403
+ static ɵfac: i0.ɵɵFactoryDeclaration<NotesComponent, never>;
6404
+ static ɵcmp: i0.ɵɵComponentDeclaration<NotesComponent, "ui-notes", never, {
6405
+ "resourceType": {
6406
+ "alias": "resourceType";
6407
+ "required": false;
6408
+ "isSignal": true;
6409
+ };
6410
+ "resourceId": {
6411
+ "alias": "resourceId";
6412
+ "required": false;
6413
+ "isSignal": true;
6414
+ };
6415
+ "readOnly": {
6416
+ "alias": "readOnly";
6417
+ "required": false;
6418
+ "isSignal": true;
6419
+ };
6420
+ }, {
6421
+ "totalChange": "totalChange";
6422
+ }, never, never, true, never>;
6423
+ }
6119
6424
  /**
6120
6425
  * Adds one app-wide toast outlet (a `<ui-toast>` appended to `<body>`) when the app starts, so toasts show on pages that have
6121
6426
  * no `<ui-data-table>` and no `<ui-toast />` of their own.
@@ -6550,7 +6855,7 @@ export interface PanelInfoItem {
6550
6855
  phone?: string;
6551
6856
  /** Show the value with an "open" icon instead of blue link styling. */
6552
6857
  linkIcon?: boolean;
6553
- /** Renders a small edit (pencil) icon button after the value. */
6858
+ /** Renders a small edit (pencil) `ui-icon` button after the value; `editTooltip` is its tooltip and accessible name (default "Edit"). */
6554
6859
  editAction?: () => void;
6555
6860
  editTooltip?: string;
6556
6861
  muted?: boolean;
@@ -6590,6 +6895,11 @@ export declare class PanelInfoListComponent {
6590
6895
  * Default false = the label and value as before.
6591
6896
  */
6592
6897
  readonly labelColon: import("@angular/core").InputSignal<boolean>;
6898
+ /**
6899
+ * The list stretches to the height of its container (a grid cell or flex item with a definite height), so lists side by side in
6900
+ * one grid row end at the same height; with one section, that section's box fills the height. Default false = as tall as its content.
6901
+ */
6902
+ readonly fill: import("@angular/core").InputSignal<boolean>;
6593
6903
  readonly displaySections: import("@angular/core").Signal<PanelInfoSection[]>;
6594
6904
  /**
6595
6905
  * The safe href of an item, or null: `href` first, else `mailto:`/`tel:` built from `email`/`phone`. Only mailto, tel, http and https
@@ -6617,6 +6927,11 @@ export declare class PanelInfoListComponent {
6617
6927
  "required": false;
6618
6928
  "isSignal": true;
6619
6929
  };
6930
+ "fill": {
6931
+ "alias": "fill";
6932
+ "required": false;
6933
+ "isSignal": true;
6934
+ };
6620
6935
  }, {}, never, never, true, never>;
6621
6936
  }
6622
6937
  /**
@@ -7007,7 +7322,7 @@ export declare class AlertComponent {
7007
7322
  /** The SVG path (24×24 stroke icon) for an icon key, or undefined when the key is unknown. */
7008
7323
  export declare function iconPath(name: string): string | undefined;
7009
7324
  /** Keys with an icon of their own (the data-table action keys are accepted too — see `builtInActionIcon`). */
7010
- export type IconName = 'add' | 'close' | 'check' | 'upload' | 'image' | 'chevron-up' | 'chevron-down' | 'arrow-left' | 'bell' | 'print' | 'lock' | 'shield' | 'arrow-up' | 'arrow-down' | 'arrow-right' | 'document' | 'grip' | 'card' | 'bank' | 'view' | 'view-off' | 'edit' | 'delete' | 'download' | 'duplicate' | 'archive' | 'restore' | 'share' | 'email' | 'message' | 'call' | 'refund' | 'chevron-right';
7325
+ export type IconName = 'add' | 'close' | 'check' | 'upload' | 'image' | 'chevron-up' | 'chevron-down' | 'arrow-left' | 'bell' | 'print' | 'lock' | 'shield' | 'arrow-up' | 'arrow-down' | 'arrow-right' | 'document' | 'grip' | 'card' | 'bank' | 'view' | 'view-off' | 'edit' | 'delete' | 'download' | 'duplicate' | 'archive' | 'restore' | 'share' | 'email' | 'message' | 'call' | 'refund' | 'chevron-right' | 'link' | 'save' | 'chart';
7011
7326
  /**
7012
7327
  * A clickable icon. The host passes a key (`name="delete"`) and the library draws that icon; clicking it emits
7013
7328
  * `iconClick`, and the host does the actual work (delete the row, open a panel, …).
@@ -7132,6 +7447,8 @@ export interface TimelineItem {
7132
7447
  * { id: 'b', title: 'Letter sent', description: 'Mailed to the owner.', tone: 'success', icon: 'email' },
7133
7448
  * ]" />
7134
7449
  *
7450
+ * `groupByDay` adds a heading per calendar day (see the inputs `groupByDay`, `timeZone`, `now`, `dayHeadingLevel`).
7451
+ *
7135
7452
  * Semantics: `<ol>` / `<li>`; the line and dots are decorative (aria-hidden). `time` is the visible text; `dateTime` goes into
7136
7453
  * `<time datetime>`. With no items, `emptyMessage` (if set) is shown as a hint, otherwise nothing is rendered.
7137
7454
  */
@@ -7145,6 +7462,26 @@ export declare class TimelineComponent {
7145
7462
  readonly maxHeight: import("@angular/core").InputSignal<string>;
7146
7463
  /** `below` (default): the time is under the title / description. `end`: the time sits right-aligned on the title row. */
7147
7464
  readonly timePosition: import("@angular/core").InputSignal<"end" | "below">;
7465
+ /**
7466
+ * Group the items under a heading per calendar day of `dateTime` ("Today", "Yesterday", "Oct 3", "Oct 3, 2025"). Default false = no headings,
7467
+ * DOM unchanged. The given order is kept: consecutive items of one day share a heading, the same day appearing again later starts a new group.
7468
+ * An item without a parseable ISO `dateTime` joins the group it follows (a heading-less group at the top when first).
7469
+ * Grouped markup: `<ol class="tl"><li class="tl-day-group"><div class="tl-day" role="heading" aria-level>…</div><ol class="tl-sub">…items…</ol></li></ol>`.
7470
+ */
7471
+ readonly groupByDay: import("@angular/core").InputSignal<boolean>;
7472
+ /** IANA time zone (e.g. `America/Chicago`) for the day grouping and the labels. A `dateTime` with a trailing Z / offset = an instant shown in this zone; a date or a wall-clock time without offset is taken as already being in that day. Empty (default) = the browser's local zone; an unknown zone also falls back to it. */
7473
+ readonly timeZone: import("@angular/core").InputSignal<string>;
7474
+ /** The moment "Today" / "Yesterday" are measured from. `null` (default) = the real current time; a Date or ISO string pins it (tests, replays). The real time is read when the groups are computed, not on a timer: a long-lived page should pass `now` (or re-bind `items`) to refresh Today / Yesterday after midnight. */
7475
+ readonly now: import("@angular/core").InputSignal<string | Date | null>;
7476
+ /** Heading level 1-6 of the day headings (role="heading" + aria-level); anything else becomes 3. */
7477
+ readonly dayHeadingLevel: import("@angular/core").InputSignal<number>;
7478
+ protected readonly headingLevel: import("@angular/core").Signal<number>;
7479
+ /** The items split into day groups (only used when `groupByDay` is on). */
7480
+ protected readonly groups: import("@angular/core").Signal<{
7481
+ key: string;
7482
+ label: string;
7483
+ items: TimelineItem[];
7484
+ }[]>;
7148
7485
  /** Solid dot colour of an item's `dotVariant`, or null (then `tone` colours it). */
7149
7486
  dotColor(item: TimelineItem): string | null;
7150
7487
  static ɵfac: i0.ɵɵFactoryDeclaration<TimelineComponent, never>;
@@ -7174,6 +7511,26 @@ export declare class TimelineComponent {
7174
7511
  "required": false;
7175
7512
  "isSignal": true;
7176
7513
  };
7514
+ "groupByDay": {
7515
+ "alias": "groupByDay";
7516
+ "required": false;
7517
+ "isSignal": true;
7518
+ };
7519
+ "timeZone": {
7520
+ "alias": "timeZone";
7521
+ "required": false;
7522
+ "isSignal": true;
7523
+ };
7524
+ "now": {
7525
+ "alias": "now";
7526
+ "required": false;
7527
+ "isSignal": true;
7528
+ };
7529
+ "dayHeadingLevel": {
7530
+ "alias": "dayHeadingLevel";
7531
+ "required": false;
7532
+ "isSignal": true;
7533
+ };
7177
7534
  }, {}, never, never, true, never>;
7178
7535
  }
7179
7536
  /**
@@ -8535,6 +8892,8 @@ export declare class PdfHotspotComponent {
8535
8892
  * - `background`: the card's background colour (any CSS colour; default white).
8536
8893
  * - `headerBackground`: the background colour of the header bar only (any CSS colour; default none, the card's own background shows). The body keeps the card's `background`, and a header-less card (`showHeader` false) has no bar to colour. A collapsible header keeps this colour on hover. Pair it with `textColor` when the colour is dark.
8537
8894
  * - `textColor`: the colour of the title, subtitle, chevron and body text (any CSS colour; default: the normal slate colours). Use it for light text on a dark `background`. Text you colour yourself inside the body keeps its own colour.
8895
+ * - `<ng-container card-footer>` / `<div card-footer>`: a footer bar under the body (a pager, totals, a link). Hidden while empty or collapsed, 10px 18px padding,
8896
+ * a light top line; `footerBackground` colours it (pair it with `headerBackground` to frame the card). In a `fill` card it is pinned to the bottom edge.
8538
8897
  * - The body has built-in padding (16px 18px, matching the header's 18px side padding); there is no padding input.
8539
8898
  * - `collapsible` (default true): a chevron toggle and a clickable header; `[(expanded)]` is two-way.
8540
8899
  * Without a header, or with `collapsible` false, the body is always open.
@@ -8577,6 +8936,8 @@ export declare class DetailCardComponent {
8577
8936
  readonly background: import("@angular/core").InputSignal<string>;
8578
8937
  /** Background colour of the header bar only, as any CSS colour (`#eaf3ff`, `var(--surface-2)` …). Empty = the header has no colour of its own (the card background shows). The body is not affected. */
8579
8938
  readonly headerBackground: import("@angular/core").InputSignal<string>;
8939
+ /** Background colour of the footer bar (the `[card-footer]` slot) as any CSS colour. Empty = no colour of its own. */
8940
+ readonly footerBackground: import("@angular/core").InputSignal<string>;
8580
8941
  /** Text colour as any CSS colour (`#fff`, `var(--on-dark)` …). Empty = the default colours. Colours the title, subtitle, chevron and body text. */
8581
8942
  readonly textColor: import("@angular/core").InputSignal<string>;
8582
8943
  /** false = no header at all (no title, edit button, toggle or divider): the card is only the body, always open. */
@@ -8656,6 +9017,11 @@ export declare class DetailCardComponent {
8656
9017
  "required": false;
8657
9018
  "isSignal": true;
8658
9019
  };
9020
+ "footerBackground": {
9021
+ "alias": "footerBackground";
9022
+ "required": false;
9023
+ "isSignal": true;
9024
+ };
8659
9025
  "textColor": {
8660
9026
  "alias": "textColor";
8661
9027
  "required": false;
@@ -8730,7 +9096,7 @@ export declare class DetailCardComponent {
8730
9096
  "expanded": "expandedChange";
8731
9097
  "editClick": "editClick";
8732
9098
  "selected": "selectedChange";
8733
- }, never, ["[card-header-leading]", "[card-header]", "[card-header-action]", "*"], true, never>;
9099
+ }, never, ["[card-header-leading]", "[card-header]", "[card-header-action]", "*", "[card-footer]"], true, never>;
8734
9100
  }
8735
9101
  export type DividerSpace = 'none' | 'sm' | 'md' | 'lg';
8736
9102
  /**
@@ -8812,12 +9178,24 @@ export interface BoardCardContext<T = unknown> {
8812
9178
  $implicit: T;
8813
9179
  }>;
8814
9180
  }
8815
- /** Marks the template that renders the INSIDE of a card (the board draws the card frame, menu and drag handling). */
9181
+ /**
9182
+ * Marks the template that renders the INSIDE of a card (the board draws the card frame, menu and drag handling).
9183
+ * Bind the board's columns to type the card item: `<ng-template [uiBoardCard]="columns()" let-lead>` gives `lead` the
9184
+ * columns' item type. The bare `<ng-template uiBoardCard let-item>` still works; `item` is then `unknown`.
9185
+ */
8816
9186
  export declare class BoardCardDirective<T = unknown> {
9187
+ /** Only used to infer the item type in the template; the value is never read. */
9188
+ readonly typeFrom: import("@angular/core").InputSignal<"" | BoardColumn<T>[]>;
8817
9189
  readonly template: TemplateRef<BoardCardContext<T>>;
8818
9190
  static ngTemplateContextGuard<T>(_dir: BoardCardDirective<T>, _ctx: unknown): _ctx is BoardCardContext<T>;
8819
9191
  static ɵfac: i0.ɵɵFactoryDeclaration<BoardCardDirective<any>, never>;
8820
- static ɵdir: i0.ɵɵDirectiveDeclaration<BoardCardDirective<any>, "ng-template[uiBoardCard]", never, {}, {}, never, never, true, never>;
9192
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BoardCardDirective<any>, "ng-template[uiBoardCard]", never, {
9193
+ "typeFrom": {
9194
+ "alias": "uiBoardCard";
9195
+ "required": false;
9196
+ "isSignal": true;
9197
+ };
9198
+ }, {}, never, never, true, never>;
8821
9199
  }
8822
9200
  /**
8823
9201
  * Kanban board: columns of cards that can be dragged between columns. Data and saving stay with the host.
@@ -8825,7 +9203,7 @@ export declare class BoardCardDirective<T = unknown> {
8825
9203
  * <ui-board [columns]="columns()" [itemKey]="key" [itemLabel]="label" [cardActions]="actions" [readOnly]="perm() !== 'full'"
8826
9204
  * [loading]="loading()" [error]="error()" (retry)="load()"
8827
9205
  * (cardClick)="open($event)" (cardAction)="onAction($event)" (itemMove)="onMove($event)">
8828
- * <ng-template uiBoardCard let-deal let-menu="menu">
9206
+ * <ng-template [uiBoardCard]="columns()" let-deal let-menu="menu">
8829
9207
  * <div class="ub-card__top"><span class="ub-card__title">{{ deal.title }}</span>
8830
9208
  * <ng-container [ngTemplateOutlet]="menu" [ngTemplateOutletContext]="{ $implicit: deal }" /></div>
8831
9209
  * <div class="ub-card__contact">{{ deal.contactName }}</div>
@@ -8861,21 +9239,42 @@ export declare class BoardComponent<T = unknown> {
8861
9239
  readonly itemNoun: import("@angular/core").InputSignal<string>;
8862
9240
  readonly itemNounPlural: import("@angular/core").InputSignal<string>;
8863
9241
  readonly retryLabel: import("@angular/core").InputSignal<string>;
9242
+ /**
9243
+ * Cards drawn per column at first, and added by each "Show more" click (0 = draw every card). The board loads every item
9244
+ * at once, so drawing only a page of each column keeps the first paint fast however many items there are. Column counts
9245
+ * and summaries still cover every item; a card moved into a column is drawn at its top, so it is always on screen.
9246
+ */
9247
+ readonly pageSize: import("@angular/core").InputSignal<number>;
8864
9248
  readonly cardClick: import("@angular/core").OutputEmitterRef<T>;
8865
9249
  readonly cardAction: import("@angular/core").OutputEmitterRef<BoardCardActionEvent<T>>;
8866
9250
  readonly itemMove: import("@angular/core").OutputEmitterRef<BoardMoveEvent<T>>;
8867
9251
  readonly retry: import("@angular/core").OutputEmitterRef<void>;
8868
- protected readonly card: import("@angular/core").Signal<BoardCardDirective<any> | undefined>;
9252
+ protected readonly card: import("@angular/core").Signal<BoardCardDirective<T> | undefined>;
8869
9253
  /** The columns as shown: the host's value, plus any move not yet replaced by new data. */
8870
9254
  protected readonly cols: import("@angular/core").WritableSignal<BoardColumn<T>[]>;
9255
+ /** Cards drawn per column. Kept when the host re-sends columns (a saved move, a search), so the board never jumps back; back to the first page on a reload (`loading`). */
9256
+ private readonly drawn;
9257
+ /** The card the user last moved and where it went: it stays drawn even if the host re-sends it far down the column. */
9258
+ private readonly movedTo;
9259
+ protected readonly shownCols: import("@angular/core").Signal<{
9260
+ col: BoardColumn<T>;
9261
+ shown: T[];
9262
+ hidden: number;
9263
+ next: number;
9264
+ }[]>;
9265
+ /** How many cards of `col` are drawn: the pages shown so far, extended so the card the user moved is always on screen. */
9266
+ private limitOf;
9267
+ protected showMore(key: string): void;
9268
+ /** Id of the keyboard-move hint that describes the board region. */
9269
+ protected readonly hintId: string;
8871
9270
  protected readonly overKey: import("@angular/core").WritableSignal<string | null>;
8872
9271
  protected readonly draggingKey: import("@angular/core").WritableSignal<string | null>;
8873
9272
  protected readonly announcement: import("@angular/core").WritableSignal<string>;
8874
9273
  private readonly host;
9274
+ constructor();
8875
9275
  private drag;
8876
9276
  protected readonly skeletonColumns: import("@angular/core").Signal<number[]>;
8877
9277
  protected readonly canDrag: import("@angular/core").Signal<boolean>;
8878
- protected readonly canMoveAny: import("@angular/core").Signal<boolean>;
8879
9278
  protected dot(color?: string): string;
8880
9279
  protected onDragStart(event: DragEvent, item: T, col: BoardColumn<T>): void;
8881
9280
  protected onDragEnd(): void;
@@ -8962,6 +9361,11 @@ export declare class BoardComponent<T = unknown> {
8962
9361
  "required": false;
8963
9362
  "isSignal": true;
8964
9363
  };
9364
+ "pageSize": {
9365
+ "alias": "pageSize";
9366
+ "required": false;
9367
+ "isSignal": true;
9368
+ };
8965
9369
  }, {
8966
9370
  "cardClick": "cardClick";
8967
9371
  "cardAction": "cardAction";
@@ -9569,6 +9973,103 @@ export declare class StackComponent {
9569
9973
  };
9570
9974
  }, {}, never, ["*"], true, never>;
9571
9975
  }
9976
+ export type SidebarPosition = 'start' | 'end';
9977
+ /** Viewport width at and below which the layout becomes one column: sm = 640px, md = 900px, lg = 1200px, never = always two columns. */
9978
+ export type SidebarCollapse = 'sm' | 'md' | 'lg' | 'never';
9979
+ /**
9980
+ * Detail-page layout: a MAIN column plus a SIDE column of fixed width.
9981
+ *
9982
+ * Replaces the page-level grid / `position: sticky` CSS each detail page used to carry. Project the two columns with
9983
+ * the `layout-main` and `layout-side` attributes:
9984
+ *
9985
+ * <ui-sidebar-layout sideWidth="340px" gap="md" collapseAt="md">
9986
+ * <div layout-main> ...tabs, tables, timeline... </div>
9987
+ * <aside layout-side aria-label="Contact summary"> ...profile card, quick actions... </aside>
9988
+ * </ui-sidebar-layout>
9989
+ *
9990
+ * - `sideWidth`: any CSS length (default `320px`). The main column takes the rest (`minmax(0, 1fr)` track and
9991
+ * `min-width: 0`, so a wide table cannot stretch the grid).
9992
+ * - `sidePosition`: `end` (default, side on the right) | `start` (side on the left). The DOM order is always main then
9993
+ * side; only the visual position changes.
9994
+ * - `gap`: layout gap token (none | xs | sm | md | lg | xl) or a CSS length. Default `md` (16px). It is the space between
9995
+ * the columns and, when collapsed, between the stacked columns.
9996
+ * - `stickySide` (default true): at wide widths the side column is `position: sticky` at `stickyTop` px (default 16) with
9997
+ * `max-height: 100vh - (stickyTop + stickyOffset)` (default offset 32px; `100vh` first, `100dvh` after it for browsers
9998
+ * that know it) and `overflow-y: auto`, so only the main column scrolls the page; a side column taller than the viewport
9999
+ * scrolls inside itself. `false` = a plain static column.
10000
+ * - `collapseAt`: sm (640px) | md (900px, default) | lg (1200px) | never. At and below that viewport width there is ONE
10001
+ * column and the side column is static (never sticky, no max-height). `sideFirstWhenCollapsed` (default true) puts the
10002
+ * side column first there; `false` puts the main column first.
10003
+ * - An empty side slot (no `layout-side` element) hides the side column and the main column takes the full width.
10004
+ *
10005
+ * Plain media queries only (no `:has()`, no container queries) so it behaves the same in Chrome, Edge, Firefox and Safari.
10006
+ * The column gap is a margin on the side column (not a grid `column-gap`), so an empty side column leaves no stray gap.
10007
+ */
10008
+ export declare class SidebarLayoutComponent {
10009
+ /** Width of the side column: any CSS length (default `320px`). */
10010
+ readonly sideWidth: import("@angular/core").InputSignal<string>;
10011
+ readonly sidePosition: import("@angular/core").InputSignal<SidebarPosition>;
10012
+ /** Space between the columns (and between them when collapsed): a layout gap token or a CSS length (default `md` = 16px). */
10013
+ readonly gap: import("@angular/core").InputSignal<string>;
10014
+ /** Keep the side column pinned while the main column scrolls (wide widths only). */
10015
+ readonly stickySide: import("@angular/core").InputSignal<boolean>;
10016
+ /** Distance in px from the top of the viewport at which the side column sticks (default 16). */
10017
+ readonly stickyTop: import("@angular/core").InputSignal<number>;
10018
+ /** Total px of non-viewport space above and below the side column, used for its max-height (default 32). */
10019
+ readonly stickyOffset: import("@angular/core").InputSignal<number>;
10020
+ readonly collapseAt: import("@angular/core").InputSignal<SidebarCollapse>;
10021
+ /** When collapsed to one column: side column first (default) or main column first. */
10022
+ readonly sideFirstWhenCollapsed: import("@angular/core").InputSignal<boolean>;
10023
+ protected readonly position: import("@angular/core").Signal<SidebarPosition>;
10024
+ protected readonly sideCss: import("@angular/core").Signal<string>;
10025
+ protected readonly gapCss: import("@angular/core").Signal<string>;
10026
+ protected readonly topCss: import("@angular/core").Signal<string>;
10027
+ protected readonly offCss: import("@angular/core").Signal<string>;
10028
+ protected readonly rootClass: import("@angular/core").Signal<string>;
10029
+ static ɵfac: i0.ɵɵFactoryDeclaration<SidebarLayoutComponent, never>;
10030
+ static ɵcmp: i0.ɵɵComponentDeclaration<SidebarLayoutComponent, "ui-sidebar-layout", never, {
10031
+ "sideWidth": {
10032
+ "alias": "sideWidth";
10033
+ "required": false;
10034
+ "isSignal": true;
10035
+ };
10036
+ "sidePosition": {
10037
+ "alias": "sidePosition";
10038
+ "required": false;
10039
+ "isSignal": true;
10040
+ };
10041
+ "gap": {
10042
+ "alias": "gap";
10043
+ "required": false;
10044
+ "isSignal": true;
10045
+ };
10046
+ "stickySide": {
10047
+ "alias": "stickySide";
10048
+ "required": false;
10049
+ "isSignal": true;
10050
+ };
10051
+ "stickyTop": {
10052
+ "alias": "stickyTop";
10053
+ "required": false;
10054
+ "isSignal": true;
10055
+ };
10056
+ "stickyOffset": {
10057
+ "alias": "stickyOffset";
10058
+ "required": false;
10059
+ "isSignal": true;
10060
+ };
10061
+ "collapseAt": {
10062
+ "alias": "collapseAt";
10063
+ "required": false;
10064
+ "isSignal": true;
10065
+ };
10066
+ "sideFirstWhenCollapsed": {
10067
+ "alias": "sideFirstWhenCollapsed";
10068
+ "required": false;
10069
+ "isSignal": true;
10070
+ };
10071
+ }, {}, never, ["[layout-main]", "[layout-side]"], true, never>;
10072
+ }
9572
10073
  export type ActionBarAlign = 'start' | 'center' | 'end' | 'between';
9573
10074
  /**
9574
10075
  * Bar for page-level actions (Save / Cancel) that stays at the bottom of the scrolling area.