@forgeroute/ui 0.4.21 → 0.4.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeroute/ui",
3
- "version": "0.4.21",
3
+ "version": "0.4.23",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=22.0.0",
6
6
  "@angular/core": ">=22.0.0",
@@ -47,7 +47,7 @@ export interface ToolbarAction {
47
47
  /** Hide this action entirely */
48
48
  hidden?: boolean;
49
49
  /** Documentation of intent: marks a mutating action ("Add …", bulk mutations). It does not change behavior - the table hides
50
- * EVERY page-defined toolbar action for any `permission` other than 'full', with or without this flag. */
50
+ * EVERY page-defined toolbar action for any `permission` other than 'full', unless it is marked `allowReadOnly`. */
51
51
  requiresFull?: boolean;
52
52
  /**
53
53
  * Keep this action when the table is empty and unfiltered. By default only `variant: 'primary'` buttons survive that
@@ -56,6 +56,13 @@ export interface ToolbarAction {
56
56
  * menu back (with its usual items).
57
57
  */
58
58
  keepWhenEmpty?: boolean;
59
+ /**
60
+ * Keep this action for a read-only table (`permission` other than 'full'). By default a read-only table drops every
61
+ * page-defined toolbar action and keeps only Export; set this on a PURE READ - e.g. a "Board View" / "List View" switch
62
+ * that changes what is shown but writes nothing. Never set it on an action that creates, changes or deletes a record.
63
+ * On an empty, unfiltered table only primary buttons survive: add `keepWhenEmpty` as well to keep a view switch there.
64
+ */
65
+ allowReadOnly?: boolean;
59
66
  /**
60
67
  * Standalone toggle buttons (e.g. "Assigned to Me"): true / false renders `aria-pressed` so screen readers announce
61
68
  * the on / off state. Leave unset for ordinary buttons (no attribute).
@@ -660,12 +667,45 @@ export declare class BadgeComponent {
660
667
  };
661
668
  }, {}, never, never, true, never>;
662
669
  }
670
+ /**
671
+ * Round avatar: initials on a colour derived from them (default), or a photo when `imageUrl` is set. The photo falls back to the
672
+ * initials by itself when it fails to load (and tries again when `imageUrl` changes).
673
+ *
674
+ * <ui-avatar initials="JS" name="Jane Smith" [size]="72" [imageUrl]="photoUrl()" />
675
+ *
676
+ * Editable (a profile picture the user can change): `[editable]="true"` makes the avatar a keyboard button (role="button",
677
+ * tabindex 0, Enter / Space / click) that emits `editClick` — the host opens its file picker and uploads — with a small camera
678
+ * badge in the corner. `editLabel` is its name for assistive tech and tooltip (default "Change photo"). `[busy]="true"` while the
679
+ * upload runs: spinner in the badge, the photo is dimmed, `aria-busy` is set and clicks / keys do nothing.
680
+ *
681
+ * <ui-avatar initials="JS" name="Jane Smith" [size]="72" [imageUrl]="photoUrl()" [editable]="canEdit()" [busy]="uploading()" (editClick)="picker.open()" />
682
+ *
683
+ * Without `imageUrl` and `editable` nothing changes from the plain initials avatar.
684
+ */
663
685
  export declare class AvatarComponent {
664
686
  readonly initials: import("@angular/core").InputSignal<string>;
665
687
  readonly name: import("@angular/core").InputSignal<string>;
666
688
  readonly size: import("@angular/core").InputSignal<number>;
689
+ /** Photo URL. Empty / null = initials. A photo that fails to load falls back to the initials until the URL changes. */
690
+ readonly imageUrl: import("@angular/core").InputSignal<string | null | undefined>;
691
+ /** true = the avatar is a button that emits `editClick` (change the photo), with a camera badge. Default false: unchanged. */
692
+ readonly editable: import("@angular/core").InputSignal<boolean>;
693
+ /** Name of the editable avatar for assistive tech and the tooltip. */
694
+ readonly editLabel: import("@angular/core").InputSignal<string>;
695
+ /** The host is uploading: spinner badge, dimmed photo, aria-busy, no clicks. */
696
+ readonly busy: import("@angular/core").InputSignal<boolean>;
697
+ /** Fires on click / Enter / Space of an editable avatar (not while busy). */
698
+ readonly editClick: import("@angular/core").OutputEmitterRef<void>;
699
+ /** Fires when the photo could not be loaded (the initials are shown instead). */
700
+ readonly imageError: import("@angular/core").OutputEmitterRef<void>;
701
+ private readonly failedUrl;
702
+ protected readonly showImage: import("@angular/core").Signal<boolean>;
703
+ protected readonly badgeSize: import("@angular/core").Signal<number>;
667
704
  protected readonly bg: import("@angular/core").Signal<string>;
668
705
  protected readonly color: import("@angular/core").Signal<string>;
706
+ protected onImageError(): void;
707
+ protected onEdit(): void;
708
+ protected onKey(event: Event): void;
669
709
  static ɵfac: i0.ɵɵFactoryDeclaration<AvatarComponent, never>;
670
710
  static ɵcmp: i0.ɵɵComponentDeclaration<AvatarComponent, "ui-avatar", never, {
671
711
  "initials": {
@@ -683,7 +723,30 @@ export declare class AvatarComponent {
683
723
  "required": false;
684
724
  "isSignal": true;
685
725
  };
686
- }, {}, never, never, true, never>;
726
+ "imageUrl": {
727
+ "alias": "imageUrl";
728
+ "required": false;
729
+ "isSignal": true;
730
+ };
731
+ "editable": {
732
+ "alias": "editable";
733
+ "required": false;
734
+ "isSignal": true;
735
+ };
736
+ "editLabel": {
737
+ "alias": "editLabel";
738
+ "required": false;
739
+ "isSignal": true;
740
+ };
741
+ "busy": {
742
+ "alias": "busy";
743
+ "required": false;
744
+ "isSignal": true;
745
+ };
746
+ }, {
747
+ "editClick": "editClick";
748
+ "imageError": "imageError";
749
+ }, never, never, true, never>;
687
750
  }
688
751
  export interface MenuItem {
689
752
  type: string;
@@ -780,7 +843,7 @@ export declare class TableToolbarComponent {
780
843
  readonly activeViewId: import("@angular/core").InputSignal<string | null>;
781
844
  readonly showSavedViews: import("@angular/core").InputSignal<boolean>;
782
845
  /**
783
- * Read-only (permission isn't 'full'): only Export stays — no page buttons, no Import, no Columns, no
846
+ * Read-only (permission isn't 'full'): only Export and pure reads marked `allowReadOnly` stay — no other page buttons, no Import, no Columns, no
784
847
  * Save Current View, no deleting views. More Actions offers only the user's existing Saved Views and is
785
848
  * hidden when they have none.
786
849
  */
@@ -800,7 +863,7 @@ export declare class TableToolbarComponent {
800
863
  protected _saveViewName: string;
801
864
  /** Draft column visibility — only applied to the table when the user clicks Apply. */
802
865
  private readonly _pendingCols;
803
- /** Buttons this user may see — read-only users only get Export (page actions and Import are hidden). */
866
+ /** Buttons this user may see — read-only users only get Export and pure reads marked `allowReadOnly` (page actions and Import are hidden). */
804
867
  private readonly _allowedButtons;
805
868
  protected readonly standaloneButtons: import("@angular/core").Signal<ToolbarAction[]>;
806
869
  protected readonly importButton: import("@angular/core").Signal<ToolbarAction | null>;
@@ -1286,7 +1349,7 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1286
1349
  /**
1287
1350
  * Page permission. Anything other than 'full' makes the table read-only: no Import,
1288
1351
  * no bulk update/delete and no checkbox column (even when `selectable` is set), no page-defined
1289
- * toolbar buttons at all (only Export; More Actions offers only their existing Saved Views), and the
1352
+ * toolbar buttons except Export and pure reads marked `allowReadOnly` (More Actions offers only their existing Saved Views), and the
1290
1353
  * row menu offers only View (`type: 'view'`).
1291
1354
  * Rows with nothing to show get no "⋮" trigger; the Actions column is dropped when no row has View.
1292
1355
  */
@@ -4215,6 +4278,11 @@ export declare class ConfirmOutletComponent implements OnInit, OnDestroy {
4215
4278
  static ɵfac: i0.ɵɵFactoryDeclaration<ConfirmOutletComponent, never>;
4216
4279
  static ɵcmp: i0.ɵɵComponentDeclaration<ConfirmOutletComponent, "ui-confirm-outlet", never, {}, {}, never, never, true, never>;
4217
4280
  }
4281
+ /**
4282
+ * Centered empty state: icon in a round tile, optional bold title, message and a projected action.
4283
+ * `iconType`: search, table, chart, users, person, message, email, task, house, money, star, video, shield, pipeline (funnel),
4284
+ * property (building), clipboard (clipboard with check and lines), document (default; any unknown value also draws it).
4285
+ */
4218
4286
  export declare class EmptyStateComponent {
4219
4287
  readonly message: import("@angular/core").InputSignal<string>;
4220
4288
  readonly title: import("@angular/core").InputSignal<string>;
@@ -6276,6 +6344,11 @@ export interface NoteItem {
6276
6344
  createdAt: string;
6277
6345
  updatedAt: string;
6278
6346
  }
6347
+ /**
6348
+ * Longest note body, in characters, counted AFTER trimming. Matches the backend `NoteController` cap (10000 after trim; the server trims first,
6349
+ * 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).".
6350
+ */
6351
+ export declare const NOTES_MAX_LENGTH = 10000;
6279
6352
  /**
6280
6353
  * 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),
6281
6354
  * 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:
@@ -6290,6 +6363,8 @@ export interface NoteItem {
6290
6363
  *
6291
6364
  * `readOnly` lists the notes only (no box, no edit / delete). `totalChange` emits the total after every load. Failures show a
6292
6365
  * library toast with the server's message. Put it in a `ui-detail-card`: the card's body padding is the only outer inset.
6366
+ * A note may hold at most `NOTES_MAX_LENGTH` (10000) characters after trimming; longer text (typed or pasted) shows the field's
6367
+ * "Too long" message at once and Add Note / Save stay disabled, so no request is sent.
6293
6368
  * Pagination is the library `ui-pagination` (shown only above one page). Cmd/Ctrl+Enter in the add box adds the note.
6294
6369
  */
6295
6370
  export declare class NotesComponent {
@@ -6305,6 +6380,8 @@ export declare class NotesComponent {
6305
6380
  private readonly destroyRef;
6306
6381
  private readonly apiBase;
6307
6382
  readonly pageSize = 5;
6383
+ /** The character limit of a note (see `NOTES_MAX_LENGTH`). */
6384
+ readonly maxLength = 10000;
6308
6385
  readonly notes: import("@angular/core").WritableSignal<NoteItem[]>;
6309
6386
  readonly loading: import("@angular/core").WritableSignal<boolean>;
6310
6387
  readonly saving: import("@angular/core").WritableSignal<boolean>;
@@ -6317,6 +6394,10 @@ export declare class NotesComponent {
6317
6394
  editBody: string;
6318
6395
  /** Up to two initials of the author's name for the avatar ("Ana Lopez" -> "AL"); "Y" for the current user's own unnamed notes. */
6319
6396
  protected initials(name: string): string;
6397
+ /** True when the trimmed text is longer than the limit. */
6398
+ protected tooLong(text: string): boolean;
6399
+ /** True when the trimmed text is not empty and within the limit. */
6400
+ protected validBody(text: string): boolean;
6320
6401
  private get listUrl();
6321
6402
  constructor();
6322
6403
  loadPage(p: number): void;
@@ -6821,6 +6902,11 @@ export declare class PanelInfoListComponent {
6821
6902
  * Default false = the label and value as before.
6822
6903
  */
6823
6904
  readonly labelColon: import("@angular/core").InputSignal<boolean>;
6905
+ /**
6906
+ * 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
6907
+ * 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.
6908
+ */
6909
+ readonly fill: import("@angular/core").InputSignal<boolean>;
6824
6910
  readonly displaySections: import("@angular/core").Signal<PanelInfoSection[]>;
6825
6911
  /**
6826
6912
  * The safe href of an item, or null: `href` first, else `mailto:`/`tel:` built from `email`/`phone`. Only mailto, tel, http and https
@@ -6848,6 +6934,11 @@ export declare class PanelInfoListComponent {
6848
6934
  "required": false;
6849
6935
  "isSignal": true;
6850
6936
  };
6937
+ "fill": {
6938
+ "alias": "fill";
6939
+ "required": false;
6940
+ "isSignal": true;
6941
+ };
6851
6942
  }, {}, never, never, true, never>;
6852
6943
  }
6853
6944
  /**
@@ -7238,7 +7329,7 @@ export declare class AlertComponent {
7238
7329
  /** The SVG path (24×24 stroke icon) for an icon key, or undefined when the key is unknown. */
7239
7330
  export declare function iconPath(name: string): string | undefined;
7240
7331
  /** Keys with an icon of their own (the data-table action keys are accepted too — see `builtInActionIcon`). */
7241
- 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';
7332
+ 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' | 'clipboard';
7242
7333
  /**
7243
7334
  * A clickable icon. The host passes a key (`name="delete"`) and the library draws that icon; clicking it emits
7244
7335
  * `iconClick`, and the host does the actual work (delete the row, open a panel, …).
@@ -7363,6 +7454,8 @@ export interface TimelineItem {
7363
7454
  * { id: 'b', title: 'Letter sent', description: 'Mailed to the owner.', tone: 'success', icon: 'email' },
7364
7455
  * ]" />
7365
7456
  *
7457
+ * `groupByDay` adds a heading per calendar day (see the inputs `groupByDay`, `timeZone`, `now`, `dayHeadingLevel`).
7458
+ *
7366
7459
  * Semantics: `<ol>` / `<li>`; the line and dots are decorative (aria-hidden). `time` is the visible text; `dateTime` goes into
7367
7460
  * `<time datetime>`. With no items, `emptyMessage` (if set) is shown as a hint, otherwise nothing is rendered.
7368
7461
  */
@@ -7376,6 +7469,26 @@ export declare class TimelineComponent {
7376
7469
  readonly maxHeight: import("@angular/core").InputSignal<string>;
7377
7470
  /** `below` (default): the time is under the title / description. `end`: the time sits right-aligned on the title row. */
7378
7471
  readonly timePosition: import("@angular/core").InputSignal<"end" | "below">;
7472
+ /**
7473
+ * Group the items under a heading per calendar day of `dateTime` ("Today", "Yesterday", "Oct 3", "Oct 3, 2025"). Default false = no headings,
7474
+ * 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.
7475
+ * An item without a parseable ISO `dateTime` joins the group it follows (a heading-less group at the top when first).
7476
+ * 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>`.
7477
+ */
7478
+ readonly groupByDay: import("@angular/core").InputSignal<boolean>;
7479
+ /** 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. */
7480
+ readonly timeZone: import("@angular/core").InputSignal<string>;
7481
+ /** 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. */
7482
+ readonly now: import("@angular/core").InputSignal<string | Date | null>;
7483
+ /** Heading level 1-6 of the day headings (role="heading" + aria-level); anything else becomes 3. */
7484
+ readonly dayHeadingLevel: import("@angular/core").InputSignal<number>;
7485
+ protected readonly headingLevel: import("@angular/core").Signal<number>;
7486
+ /** The items split into day groups (only used when `groupByDay` is on). */
7487
+ protected readonly groups: import("@angular/core").Signal<{
7488
+ key: string;
7489
+ label: string;
7490
+ items: TimelineItem[];
7491
+ }[]>;
7379
7492
  /** Solid dot colour of an item's `dotVariant`, or null (then `tone` colours it). */
7380
7493
  dotColor(item: TimelineItem): string | null;
7381
7494
  static ɵfac: i0.ɵɵFactoryDeclaration<TimelineComponent, never>;
@@ -7405,6 +7518,26 @@ export declare class TimelineComponent {
7405
7518
  "required": false;
7406
7519
  "isSignal": true;
7407
7520
  };
7521
+ "groupByDay": {
7522
+ "alias": "groupByDay";
7523
+ "required": false;
7524
+ "isSignal": true;
7525
+ };
7526
+ "timeZone": {
7527
+ "alias": "timeZone";
7528
+ "required": false;
7529
+ "isSignal": true;
7530
+ };
7531
+ "now": {
7532
+ "alias": "now";
7533
+ "required": false;
7534
+ "isSignal": true;
7535
+ };
7536
+ "dayHeadingLevel": {
7537
+ "alias": "dayHeadingLevel";
7538
+ "required": false;
7539
+ "isSignal": true;
7540
+ };
7408
7541
  }, {}, never, never, true, never>;
7409
7542
  }
7410
7543
  /**
@@ -9052,12 +9185,24 @@ export interface BoardCardContext<T = unknown> {
9052
9185
  $implicit: T;
9053
9186
  }>;
9054
9187
  }
9055
- /** Marks the template that renders the INSIDE of a card (the board draws the card frame, menu and drag handling). */
9188
+ /**
9189
+ * Marks the template that renders the INSIDE of a card (the board draws the card frame, menu and drag handling).
9190
+ * Bind the board's columns to type the card item: `<ng-template [uiBoardCard]="columns()" let-lead>` gives `lead` the
9191
+ * columns' item type. The bare `<ng-template uiBoardCard let-item>` still works; `item` is then `unknown`.
9192
+ */
9056
9193
  export declare class BoardCardDirective<T = unknown> {
9194
+ /** Only used to infer the item type in the template; the value is never read. */
9195
+ readonly typeFrom: import("@angular/core").InputSignal<"" | BoardColumn<T>[]>;
9057
9196
  readonly template: TemplateRef<BoardCardContext<T>>;
9058
9197
  static ngTemplateContextGuard<T>(_dir: BoardCardDirective<T>, _ctx: unknown): _ctx is BoardCardContext<T>;
9059
9198
  static ɵfac: i0.ɵɵFactoryDeclaration<BoardCardDirective<any>, never>;
9060
- static ɵdir: i0.ɵɵDirectiveDeclaration<BoardCardDirective<any>, "ng-template[uiBoardCard]", never, {}, {}, never, never, true, never>;
9199
+ static ɵdir: i0.ɵɵDirectiveDeclaration<BoardCardDirective<any>, "ng-template[uiBoardCard]", never, {
9200
+ "typeFrom": {
9201
+ "alias": "uiBoardCard";
9202
+ "required": false;
9203
+ "isSignal": true;
9204
+ };
9205
+ }, {}, never, never, true, never>;
9061
9206
  }
9062
9207
  /**
9063
9208
  * Kanban board: columns of cards that can be dragged between columns. Data and saving stay with the host.
@@ -9065,7 +9210,7 @@ export declare class BoardCardDirective<T = unknown> {
9065
9210
  * <ui-board [columns]="columns()" [itemKey]="key" [itemLabel]="label" [cardActions]="actions" [readOnly]="perm() !== 'full'"
9066
9211
  * [loading]="loading()" [error]="error()" (retry)="load()"
9067
9212
  * (cardClick)="open($event)" (cardAction)="onAction($event)" (itemMove)="onMove($event)">
9068
- * <ng-template uiBoardCard let-deal let-menu="menu">
9213
+ * <ng-template [uiBoardCard]="columns()" let-deal let-menu="menu">
9069
9214
  * <div class="ub-card__top"><span class="ub-card__title">{{ deal.title }}</span>
9070
9215
  * <ng-container [ngTemplateOutlet]="menu" [ngTemplateOutletContext]="{ $implicit: deal }" /></div>
9071
9216
  * <div class="ub-card__contact">{{ deal.contactName }}</div>
@@ -9079,12 +9224,18 @@ export declare class BoardCardDirective<T = unknown> {
9079
9224
  export declare class BoardComponent<T = unknown> {
9080
9225
  /** The columns in display order, each with its items. A new value replaces the board's content (and any on-screen move). */
9081
9226
  readonly columns: import("@angular/core").InputSignal<BoardColumn<T>[]>;
9082
- /** Stable unique id of a card (used to track cards between renders). */
9227
+ /** Stable unique id of a card (used to track cards between renders, to find its menu and to move it). Must be unique across the whole board. */
9083
9228
  readonly itemKey: import("@angular/core").InputSignal<(item: T) => string>;
9084
9229
  /** Spoken name of a card: title, who, status, value... The card's visible content is the `uiBoardCard` template. */
9085
9230
  readonly itemLabel: import("@angular/core").InputSignal<(item: T) => string>;
9086
9231
  /** Menu on every card (the "⋮" button). Empty = no menu. Handle the pick in `(cardAction)`. */
9087
9232
  readonly cardActions: import("@angular/core").InputSignal<RowAction[]>;
9233
+ /**
9234
+ * Adds "Move to <column>" to every card's menu (one item per column the card may be moved to), so a card can be moved
9235
+ * without dragging - by touch, with a screen reader or with the mouse alone. Only while cards can be moved (not read-only,
9236
+ * not `moveDisabled`) and only to columns that accept the card (`canMove`, `dropDisabled`). Set false to leave the menu to `cardActions`.
9237
+ */
9238
+ readonly moveMenu: import("@angular/core").InputSignal<boolean>;
9088
9239
  readonly loading: import("@angular/core").InputSignal<boolean>;
9089
9240
  /** Error text: replaces the board with the message and a Retry button. */
9090
9241
  readonly error: import("@angular/core").InputSignal<string | null>;
@@ -9101,21 +9252,42 @@ export declare class BoardComponent<T = unknown> {
9101
9252
  readonly itemNoun: import("@angular/core").InputSignal<string>;
9102
9253
  readonly itemNounPlural: import("@angular/core").InputSignal<string>;
9103
9254
  readonly retryLabel: import("@angular/core").InputSignal<string>;
9255
+ /**
9256
+ * Cards drawn per column at first, and added by each "Show more" click (0 = draw every card). The board loads every item
9257
+ * at once, so drawing only a page of each column keeps the first paint fast however many items there are. Column counts
9258
+ * and summaries still cover every item; a card moved into a column is drawn at its top, so it is always on screen.
9259
+ */
9260
+ readonly pageSize: import("@angular/core").InputSignal<number>;
9104
9261
  readonly cardClick: import("@angular/core").OutputEmitterRef<T>;
9105
9262
  readonly cardAction: import("@angular/core").OutputEmitterRef<BoardCardActionEvent<T>>;
9106
9263
  readonly itemMove: import("@angular/core").OutputEmitterRef<BoardMoveEvent<T>>;
9107
9264
  readonly retry: import("@angular/core").OutputEmitterRef<void>;
9108
- protected readonly card: import("@angular/core").Signal<BoardCardDirective<any> | undefined>;
9265
+ protected readonly card: import("@angular/core").Signal<BoardCardDirective<T> | undefined>;
9109
9266
  /** The columns as shown: the host's value, plus any move not yet replaced by new data. */
9110
9267
  protected readonly cols: import("@angular/core").WritableSignal<BoardColumn<T>[]>;
9268
+ /** 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`). */
9269
+ private readonly drawn;
9270
+ /** The card the user last moved and where it went: it stays drawn even if the host re-sends it far down the column. */
9271
+ private readonly movedTo;
9272
+ protected readonly shownCols: import("@angular/core").Signal<{
9273
+ col: BoardColumn<T>;
9274
+ shown: T[];
9275
+ hidden: number;
9276
+ next: number;
9277
+ }[]>;
9278
+ /** How many cards of `col` are drawn: the pages shown so far, extended so the card the user moved is always on screen. */
9279
+ private limitOf;
9280
+ protected showMore(key: string): void;
9281
+ /** Id of the keyboard-move hint that describes the board region. */
9282
+ protected readonly hintId: string;
9111
9283
  protected readonly overKey: import("@angular/core").WritableSignal<string | null>;
9112
9284
  protected readonly draggingKey: import("@angular/core").WritableSignal<string | null>;
9113
9285
  protected readonly announcement: import("@angular/core").WritableSignal<string>;
9114
9286
  private readonly host;
9287
+ constructor();
9115
9288
  private drag;
9116
9289
  protected readonly skeletonColumns: import("@angular/core").Signal<number[]>;
9117
9290
  protected readonly canDrag: import("@angular/core").Signal<boolean>;
9118
- protected readonly canMoveAny: import("@angular/core").Signal<boolean>;
9119
9291
  protected dot(color?: string): string;
9120
9292
  protected onDragStart(event: DragEvent, item: T, col: BoardColumn<T>): void;
9121
9293
  protected onDragEnd(): void;
@@ -9123,6 +9295,11 @@ export declare class BoardComponent<T = unknown> {
9123
9295
  protected onDragLeave(event: DragEvent, col: BoardColumn<T>): void;
9124
9296
  protected onDrop(event: DragEvent, col: BoardColumn<T>): void;
9125
9297
  protected onCardKey(event: KeyboardEvent, item: T, col: BoardColumn<T>): void;
9298
+ /** The menu of each card by item key: the host's `cardActions`, then "Move to <column>" for each column that accepts the card (one shared array per column when the host has no `canMove` rule). */
9299
+ protected readonly menuActions: import("@angular/core").Signal<Map<string, RowAction[]>>;
9300
+ protected onMenuAction(type: string, item: T): void;
9301
+ /** The card is re-created in its new column: put the focus back on it once it is drawn. */
9302
+ private focusCard;
9126
9303
  private accepts;
9127
9304
  private move;
9128
9305
  static ɵfac: i0.ɵɵFactoryDeclaration<BoardComponent<any>, never>;
@@ -9147,6 +9324,11 @@ export declare class BoardComponent<T = unknown> {
9147
9324
  "required": false;
9148
9325
  "isSignal": true;
9149
9326
  };
9327
+ "moveMenu": {
9328
+ "alias": "moveMenu";
9329
+ "required": false;
9330
+ "isSignal": true;
9331
+ };
9150
9332
  "loading": {
9151
9333
  "alias": "loading";
9152
9334
  "required": false;
@@ -9202,6 +9384,11 @@ export declare class BoardComponent<T = unknown> {
9202
9384
  "required": false;
9203
9385
  "isSignal": true;
9204
9386
  };
9387
+ "pageSize": {
9388
+ "alias": "pageSize";
9389
+ "required": false;
9390
+ "isSignal": true;
9391
+ };
9205
9392
  }, {
9206
9393
  "cardClick": "cardClick";
9207
9394
  "cardAction": "cardAction";
@@ -9809,6 +9996,144 @@ export declare class StackComponent {
9809
9996
  };
9810
9997
  }, {}, never, ["*"], true, never>;
9811
9998
  }
9999
+ export type SidebarPosition = 'start' | 'end';
10000
+ /** Viewport width at and below which the layout becomes one column: sm = 640px, md = 900px, lg = 1200px, never = always two columns. */
10001
+ export type SidebarCollapse = 'sm' | 'md' | 'lg' | 'never';
10002
+ /**
10003
+ * Detail-page layout: a MAIN column plus a SIDE column of fixed width.
10004
+ *
10005
+ * Replaces the page-level grid / `position: sticky` CSS each detail page used to carry. Project the two columns with
10006
+ * the `layout-main` and `layout-side` attributes:
10007
+ *
10008
+ * <ui-sidebar-layout sideWidth="340px" gap="md" collapseAt="md">
10009
+ * <div layout-main> ...tabs, tables, timeline... </div>
10010
+ * <aside layout-side aria-label="Contact summary"> ...profile card, quick actions... </aside>
10011
+ * </ui-sidebar-layout>
10012
+ *
10013
+ * - `sideWidth`: any CSS length (default `320px`). The main column takes the rest (`minmax(0, 1fr)` track and
10014
+ * `min-width: 0`, so a wide table cannot stretch the grid).
10015
+ * - `sidePosition`: `end` (default, side on the right) | `start` (side on the left). The DOM order is always main then
10016
+ * side; only the visual position changes.
10017
+ * - `gap`: layout gap token (none | xs | sm | md | lg | xl) or a CSS length. Default `md` (16px). It is the space between
10018
+ * the columns and, when collapsed, between the stacked columns.
10019
+ * - `stickySide` (default true): at wide widths the side column is `position: sticky` at `stickyTop` px (default 16) with
10020
+ * `max-height: 100vh - (stickyTop + stickyOffset)` (default offset 32px; `100vh` first, `100dvh` after it for browsers
10021
+ * that know it) and `overflow-y: auto`, so only the main column scrolls the page; a side column taller than the viewport
10022
+ * scrolls inside itself. `false` = a plain static column.
10023
+ * - `collapseAt`: sm (640px) | md (900px, default) | lg (1200px) | never. At and below that viewport width there is ONE
10024
+ * column and the side column is static (never sticky, no max-height). `sideFirstWhenCollapsed` (default true) puts the
10025
+ * side column first there; `false` puts the main column first.
10026
+ * - `sideFirstInDom` (default false): by default the side column is only moved to the top on small screens with CSS
10027
+ * `order`, so Tab / reading order stays main then side. With `sideFirstInDom` AND `sideFirstWhenCollapsed`, the side
10028
+ * wrapper is rendered BEFORE the main wrapper in the DOM while the viewport is at or below the `collapseAt` width
10029
+ * (tracked with `matchMedia` for the same query), so Tab order equals the visual order. At wide widths the DOM is main
10030
+ * then side. The projected nodes are moved, never recreated (same element instances, no state lost). No `matchMedia`
10031
+ * (server rendering) = not collapsed; CSS `order` still gives the right visual order until the browser takes over.
10032
+ * The `.sl` root element carries `data-dom-order="main-first|side-first"`.
10033
+ * - `sideBleed` (CSS length, default `4px`; `0px` = none): the sticky side column scrolls inside itself
10034
+ * (`overflow-y: auto`), which would clip focus rings and shadows at its edges. It gets padding and a negative margin of the
10035
+ * bleed size (the full `sideBleed` on the outer and block edges, `min(sideBleed, gap)` toward main), so the content keeps exactly its `sideWidth` and `gap` (the layout does not
10036
+ * move) while a 2px outline with a 2px offset still fits. `top` is reduced by the bleed so the content sticks at
10037
+ * `stickyTop`, and `max-height` is `100vh - (stickyTop + stickyOffset)` for the CONTENT (the bleed is added back).
10038
+ * The bleed toward main is clamped to the gap (`min(sideBleed, gap)`), so the transparent padding never overlaps or
10039
+ * intercepts clicks on main; the outer edge keeps the full bleed. Needs CSS `min()` (all current browsers).
10040
+ * Collapsed and non-sticky columns have no bleed. NOTE: the outer edge of the side box overhangs the layout by the bleed
10041
+ * (4px by default, on the right for `end`, on the left for `start`), so a flush clipping or scrolling ancestor can
10042
+ * show 4px of horizontal overflow: use `sideBleed="0px"` there.
10043
+ * - `sideWidth`, `sideBleed` and `gap` must be non-negative lengths; a negative value falls back to the default. A `calc(...)` or
10044
+ * `var(...)` value is passed through as written and is not checked: make sure it cannot compute to a negative length.
10045
+ * - An empty side slot (no `layout-side` element) hides the side column and the main column takes the full width.
10046
+ *
10047
+ * Plain media queries only (no `:has()`, no container queries) so it behaves the same in Chrome, Edge, Firefox and Safari.
10048
+ * The column gap is a margin on the side column (not a grid `column-gap`), so an empty side column leaves no stray gap.
10049
+ */
10050
+ export declare class SidebarLayoutComponent {
10051
+ /** Width of the side column: any CSS length (default `320px`). */
10052
+ readonly sideWidth: import("@angular/core").InputSignal<string>;
10053
+ readonly sidePosition: import("@angular/core").InputSignal<SidebarPosition>;
10054
+ /** Space between the columns (and between them when collapsed): a layout gap token or a CSS length (default `md` = 16px). */
10055
+ readonly gap: import("@angular/core").InputSignal<string>;
10056
+ /** Keep the side column pinned while the main column scrolls (wide widths only). */
10057
+ readonly stickySide: import("@angular/core").InputSignal<boolean>;
10058
+ /** Distance in px from the top of the viewport at which the side column's content sticks (default 16). */
10059
+ readonly stickyTop: import("@angular/core").InputSignal<number>;
10060
+ /** Total px of non-viewport space above and below the side column, used for its max-height (default 32). */
10061
+ readonly stickyOffset: import("@angular/core").InputSignal<number>;
10062
+ readonly collapseAt: import("@angular/core").InputSignal<SidebarCollapse>;
10063
+ /** When collapsed to one column: side column first (default) or main column first. */
10064
+ readonly sideFirstWhenCollapsed: import("@angular/core").InputSignal<boolean>;
10065
+ /** Also put the side wrapper first in the DOM (not only visually) while collapsed, so Tab order equals visual order. Default false. */
10066
+ readonly sideFirstInDom: import("@angular/core").InputSignal<boolean>;
10067
+ /** Room (CSS length) inside the sticky side's scroll box so focus rings at its edge are not clipped. Default `4px`; `0px` = none. */
10068
+ readonly sideBleed: import("@angular/core").InputSignal<string>;
10069
+ /** True while the viewport is at or below the collapseAt width; only tracked when sideFirstInDom is set. */
10070
+ private readonly collapsed;
10071
+ protected readonly position: import("@angular/core").Signal<SidebarPosition>;
10072
+ private readonly collapseKey;
10073
+ /** The side wrapper is rendered before the main wrapper. */
10074
+ protected readonly domSideFirst: import("@angular/core").Signal<boolean>;
10075
+ protected readonly domOrder: import("@angular/core").Signal<"side-first" | "main-first">;
10076
+ protected readonly sideCss: import("@angular/core").Signal<string>;
10077
+ protected readonly bleedCss: import("@angular/core").Signal<string>;
10078
+ protected readonly gapCss: import("@angular/core").Signal<string>;
10079
+ protected readonly topCss: import("@angular/core").Signal<string>;
10080
+ protected readonly offCss: import("@angular/core").Signal<string>;
10081
+ protected readonly rootClass: import("@angular/core").Signal<string>;
10082
+ constructor();
10083
+ static ɵfac: i0.ɵɵFactoryDeclaration<SidebarLayoutComponent, never>;
10084
+ static ɵcmp: i0.ɵɵComponentDeclaration<SidebarLayoutComponent, "ui-sidebar-layout", never, {
10085
+ "sideWidth": {
10086
+ "alias": "sideWidth";
10087
+ "required": false;
10088
+ "isSignal": true;
10089
+ };
10090
+ "sidePosition": {
10091
+ "alias": "sidePosition";
10092
+ "required": false;
10093
+ "isSignal": true;
10094
+ };
10095
+ "gap": {
10096
+ "alias": "gap";
10097
+ "required": false;
10098
+ "isSignal": true;
10099
+ };
10100
+ "stickySide": {
10101
+ "alias": "stickySide";
10102
+ "required": false;
10103
+ "isSignal": true;
10104
+ };
10105
+ "stickyTop": {
10106
+ "alias": "stickyTop";
10107
+ "required": false;
10108
+ "isSignal": true;
10109
+ };
10110
+ "stickyOffset": {
10111
+ "alias": "stickyOffset";
10112
+ "required": false;
10113
+ "isSignal": true;
10114
+ };
10115
+ "collapseAt": {
10116
+ "alias": "collapseAt";
10117
+ "required": false;
10118
+ "isSignal": true;
10119
+ };
10120
+ "sideFirstWhenCollapsed": {
10121
+ "alias": "sideFirstWhenCollapsed";
10122
+ "required": false;
10123
+ "isSignal": true;
10124
+ };
10125
+ "sideFirstInDom": {
10126
+ "alias": "sideFirstInDom";
10127
+ "required": false;
10128
+ "isSignal": true;
10129
+ };
10130
+ "sideBleed": {
10131
+ "alias": "sideBleed";
10132
+ "required": false;
10133
+ "isSignal": true;
10134
+ };
10135
+ }, {}, never, ["[layout-main]", "[layout-side]"], true, never>;
10136
+ }
9812
10137
  export type ActionBarAlign = 'start' | 'center' | 'end' | 'between';
9813
10138
  /**
9814
10139
  * Bar for page-level actions (Save / Cancel) that stays at the bottom of the scrolling area.