@forgeroute/ui 0.4.70 → 0.4.71

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.70",
3
+ "version": "0.4.71",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=22.0.0",
6
6
  "@angular/core": ">=22.0.0",
@@ -7,8 +7,11 @@
7
7
  to { opacity: 1; }
8
8
  }
9
9
 
10
- @keyframes rowFadeUp {
11
- from { opacity: 0; transform: translateY(6px); }
10
+ // Entrance of the table header and rows: slides up only, it never fades. The header holds the tabs, search and buttons and the rows hold the
11
+ // cell text, so while they fade in (opacity 0 to 1) an accessibility audit run right after render measures half-transparent text and reports a
12
+ // colour-contrast failure that is gone 300ms later. Moving without changing opacity keeps every pixel of text at full contrast at all times.
13
+ @keyframes slideUp {
14
+ from { transform: translateY(6px); }
12
15
  }
13
16
 
14
17
  @keyframes dotPulse {
@@ -25,13 +28,13 @@
25
28
  // Applied automatically by ui-data-table (rows and cards).
26
29
  // The stagger is capped at 8 steps (240ms) so a 50-row page never starts its last row ~1.5s late.
27
30
  .alive-row {
28
- animation: rowFadeUp .3s ease backwards;
31
+ animation: slideUp .3s ease backwards;
29
32
  animation-delay: calc(min(var(--i, 0), 8) * 30ms);
30
33
  }
31
34
 
32
35
  // ── Page/toolbar header ──
33
36
  .alive-header {
34
- animation: fadeUp .3s ease both;
37
+ animation: slideUp .3s ease both;
35
38
  }
36
39
 
37
40
  // ── Detail page cards ──
@@ -1801,6 +1801,10 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1801
1801
  /** State for the built-in single-row delete confirmation modal. */
1802
1802
  protected readonly _rowDeleteTarget: import("@angular/core").WritableSignal<T | null>;
1803
1803
  protected readonly _rowDeleting: import("@angular/core").WritableSignal<boolean>;
1804
+ /** Ids the built-in delete confirm dialog is described by (aria-describedby): its message and the "what will happen" list. */
1805
+ private readonly _confirmUid;
1806
+ protected readonly _confirmMsgId: string;
1807
+ protected readonly _confirmDetailsId: string;
1804
1808
  protected readonly _rowDeleteConfig: import("@angular/core").WritableSignal<{
1805
1809
  title: string;
1806
1810
  message: string;
@@ -2261,6 +2265,12 @@ export declare class ModalComponent implements OnInit, OnDestroy {
2261
2265
  readonly open: import("@angular/core").InputSignal<boolean>;
2262
2266
  readonly title: import("@angular/core").InputSignal<string>;
2263
2267
  readonly description: import("@angular/core").InputSignal<string>;
2268
+ /**
2269
+ * Id(s) of the element(s) inside the dialog that describe it (space-separated, as `aria-describedby` takes them), e.g. the confirm message
2270
+ * paragraph. Screen readers read them after the title when the dialog opens. When it is not set and `description` is, the dialog is described
2271
+ * by that description line; with neither, no `aria-describedby` is rendered (the body of a form dialog is not a description).
2272
+ */
2273
+ readonly describedBy: import("@angular/core").InputSignal<string | null>;
2264
2274
  readonly maxWidth: import("@angular/core").InputSignal<string>;
2265
2275
  readonly bodyOverflow: import("@angular/core").InputSignal<"hidden" | "auto" | "visible">;
2266
2276
  /** `data-testid` of the dialog element itself (the host `<ui-modal>` is always in the page, the dialog only while open). None when empty. */
@@ -2295,6 +2305,8 @@ export declare class ModalComponent implements OnInit, OnDestroy {
2295
2305
  readonly returnFocusTo: import("@angular/core").InputSignal<HTMLElement | null>;
2296
2306
  protected readonly kiosk: import("@angular/core").Signal<boolean>;
2297
2307
  protected readonly titleId: string;
2308
+ protected readonly descId: string;
2309
+ protected readonly describedById: import("@angular/core").Signal<string | null>;
2298
2310
  private readonly el;
2299
2311
  private readonly ren;
2300
2312
  private readonly doc;
@@ -2332,6 +2344,11 @@ export declare class ModalComponent implements OnInit, OnDestroy {
2332
2344
  "required": false;
2333
2345
  "isSignal": true;
2334
2346
  };
2347
+ "describedBy": {
2348
+ "alias": "describedBy";
2349
+ "required": false;
2350
+ "isSignal": true;
2351
+ };
2335
2352
  "maxWidth": {
2336
2353
  "alias": "maxWidth";
2337
2354
  "required": false;
@@ -4943,6 +4960,11 @@ export declare class ConfirmService {
4943
4960
  export declare class ConfirmOutletComponent implements OnInit, OnDestroy {
4944
4961
  protected readonly svc: ConfirmService;
4945
4962
  protected readonly _isPrimary: import("@angular/core").Signal<boolean>;
4963
+ /** Unique per outlet: the dialog is described by the message and, when there is one, the "what will happen" list (aria-describedby). */
4964
+ private readonly _uid;
4965
+ protected readonly msgId: string;
4966
+ protected readonly detailsId: string;
4967
+ protected readonly _describedBy: import("@angular/core").Signal<string>;
4946
4968
  ngOnInit(): void;
4947
4969
  ngOnDestroy(): void;
4948
4970
  static ɵfac: i0.ɵɵFactoryDeclaration<ConfirmOutletComponent, never>;
@@ -13693,6 +13715,30 @@ export interface ConversationMessage {
13693
13715
  sender?: string | null;
13694
13716
  /** A link-style button under the text, e.g. "View transaction"; clicking it emits `(messageAction)` with this message. */
13695
13717
  action?: ConversationMessageAction | null;
13718
+ /** A record the message points at (a job, an estimate…), shown as a chip under the text; a chip with a `url` is a link. */
13719
+ link?: ConversationLink | null;
13720
+ }
13721
+ /** A record linked to a message (see `ConversationMessage.link`, `[(link)]` and `ConversationSendEvent.link`). */
13722
+ export interface ConversationLink {
13723
+ /** What kind of record it is (`JOB`, `ESTIMATE`…): the host's own code, handed back unchanged. */
13724
+ type: string;
13725
+ /** The record's id. */
13726
+ id: string;
13727
+ /** What the chip says, e.g. "Johnson Roof — Alice". */
13728
+ label: string;
13729
+ /** Where the chip goes when it is clicked; none = a plain chip. Only used on messages, not in the composer. */
13730
+ url?: string | null;
13731
+ }
13732
+ /** One section of the "Link a record" picker: the records of one kind. */
13733
+ export interface ConversationLinkGroup {
13734
+ /** The kind, copied to `ConversationLink.type` of every pick from this section. */
13735
+ type: string;
13736
+ /** The section heading, e.g. "Jobs". */
13737
+ label: string;
13738
+ items: ReadonlyArray<{
13739
+ id: string;
13740
+ label: string;
13741
+ }>;
13696
13742
  }
13697
13743
  /** The button a message can carry (see `ConversationMessage.action`). */
13698
13744
  export interface ConversationMessageAction {
@@ -13710,6 +13756,8 @@ export interface ConversationSendEvent {
13710
13756
  text: string;
13711
13757
  /** Only with `[richText]="true"`: the formatted message as sanitised HTML (bold, italic, underline, links, lists). */
13712
13758
  html?: string;
13759
+ /** Only when a record is linked (`[(link)]`): the chosen record. The key is absent otherwise. */
13760
+ link?: ConversationLink;
13713
13761
  }
13714
13762
  /** A message with the date separator that goes above it ('' when it shares the day of the previous message). */
13715
13763
  export interface ConversationMessageRow {
@@ -13921,6 +13969,24 @@ export declare class ConversationsComponent {
13921
13969
  /** Files accepted by the attach picker (type and size already checked against `acceptedFiles` / `maxFileSize`). */
13922
13970
  readonly filesSelected: import("@angular/core").OutputEmitterRef<File[]>;
13923
13971
  readonly attachmentRemove: import("@angular/core").OutputEmitterRef<void>;
13972
+ /**
13973
+ * Link a record to a message (opt-in): `null` (default) = no link button, no picker, nothing changes. A list (even an empty one) turns on a "Link a record"
13974
+ * button in the composer that opens a searchable picker of these sections; the pick becomes `[(link)]`, shows as a removable chip above the composer, and
13975
+ * goes out with `(send)` as `link`. Load the sections when `(linkPickerOpened)` fires and show `[linkLoading]` meanwhile.
13976
+ */
13977
+ readonly linkGroups: import("@angular/core").InputSignal<readonly ConversationLinkGroup[] | null>;
13978
+ readonly linkLoading: import("@angular/core").InputSignal<boolean>;
13979
+ /** The record linked to the message being written. Cleared by the host after a send, like `[(draft)]`; the chip's × clears it too. */
13980
+ readonly link: import("@angular/core").ModelSignal<ConversationLink | null>;
13981
+ readonly linkLabel: import("@angular/core").InputSignal<string>;
13982
+ readonly linkPickerTitle: import("@angular/core").InputSignal<string>;
13983
+ readonly linkSearchPlaceholder: import("@angular/core").InputSignal<string>;
13984
+ readonly linkSearchLabel: import("@angular/core").InputSignal<string>;
13985
+ readonly linkEmptyText: import("@angular/core").InputSignal<string>;
13986
+ readonly linkLoadingText: import("@angular/core").InputSignal<string>;
13987
+ readonly removeLinkLabel: import("@angular/core").InputSignal<string>;
13988
+ /** The "Link a record" button opened the picker: load the sections now. */
13989
+ readonly linkPickerOpened: import("@angular/core").OutputEmitterRef<void>;
13924
13990
  /** An image in a bubble was clicked: the host opens it (usually in a new tab). */
13925
13991
  readonly mediaOpen: import("@angular/core").OutputEmitterRef<string>;
13926
13992
  /** The link-style button of a message (`ConversationMessage.action`) was clicked. */
@@ -13949,8 +14015,12 @@ export declare class ConversationsComponent {
13949
14015
  protected readonly emptyText: import("@angular/core").Signal<string>;
13950
14016
  protected readonly messageRows: import("@angular/core").Signal<import("@forgeroute/ui").ConversationMessageRow[]>;
13951
14017
  protected readonly contextVisible: import("@angular/core").Signal<boolean>;
13952
- /** Something to send (text or a file), the composer is open and no request is running. */
14018
+ /** Something to send (text, a file or a linked record), the composer is open and no request is running. */
13953
14019
  readonly canSend: import("@angular/core").Signal<boolean>;
14020
+ protected readonly pickerOpen: import("@angular/core").WritableSignal<boolean>;
14021
+ protected readonly linkQuery: import("@angular/core").WritableSignal<string>;
14022
+ /** The picker's sections narrowed by the search (case-insensitive, on the record label); a section with no match is left out. */
14023
+ protected readonly visibleLinkGroups: import("@angular/core").Signal<ConversationLinkGroup[]>;
13954
14024
  /** The composer's formatted content. `draft` is always its plain text, so a host that reads or sets `[(draft)]` never sees markup. */
13955
14025
  protected readonly editorHtml: import("@angular/core").WritableSignal<string>;
13956
14026
  protected readonly richHide: RichTextOption[];
@@ -13969,6 +14039,12 @@ export declare class ConversationsComponent {
13969
14039
  protected onComposeKeydown(event: KeyboardEvent): void;
13970
14040
  protected onEditorChange(html: string): void;
13971
14041
  protected sendMessage(): void;
14042
+ protected openLinkPicker(): void;
14043
+ protected closeLinkPicker(): void;
14044
+ protected pickLink(group: ConversationLinkGroup, item: {
14045
+ id: string;
14046
+ label: string;
14047
+ }): void;
13972
14048
  private clock;
13973
14049
  private singleColumn;
13974
14050
  private scrollToBottom;
@@ -14399,6 +14475,56 @@ export declare class ConversationsComponent {
14399
14475
  "required": false;
14400
14476
  "isSignal": true;
14401
14477
  };
14478
+ "linkGroups": {
14479
+ "alias": "linkGroups";
14480
+ "required": false;
14481
+ "isSignal": true;
14482
+ };
14483
+ "linkLoading": {
14484
+ "alias": "linkLoading";
14485
+ "required": false;
14486
+ "isSignal": true;
14487
+ };
14488
+ "link": {
14489
+ "alias": "link";
14490
+ "required": false;
14491
+ "isSignal": true;
14492
+ };
14493
+ "linkLabel": {
14494
+ "alias": "linkLabel";
14495
+ "required": false;
14496
+ "isSignal": true;
14497
+ };
14498
+ "linkPickerTitle": {
14499
+ "alias": "linkPickerTitle";
14500
+ "required": false;
14501
+ "isSignal": true;
14502
+ };
14503
+ "linkSearchPlaceholder": {
14504
+ "alias": "linkSearchPlaceholder";
14505
+ "required": false;
14506
+ "isSignal": true;
14507
+ };
14508
+ "linkSearchLabel": {
14509
+ "alias": "linkSearchLabel";
14510
+ "required": false;
14511
+ "isSignal": true;
14512
+ };
14513
+ "linkEmptyText": {
14514
+ "alias": "linkEmptyText";
14515
+ "required": false;
14516
+ "isSignal": true;
14517
+ };
14518
+ "linkLoadingText": {
14519
+ "alias": "linkLoadingText";
14520
+ "required": false;
14521
+ "isSignal": true;
14522
+ };
14523
+ "removeLinkLabel": {
14524
+ "alias": "removeLinkLabel";
14525
+ "required": false;
14526
+ "isSignal": true;
14527
+ };
14402
14528
  }, {
14403
14529
  "searchText": "searchTextChange";
14404
14530
  "contextOpen": "contextOpenChange";
@@ -14416,6 +14542,8 @@ export declare class ConversationsComponent {
14416
14542
  "send": "send";
14417
14543
  "filesSelected": "filesSelected";
14418
14544
  "attachmentRemove": "attachmentRemove";
14545
+ "link": "linkChange";
14546
+ "linkPickerOpened": "linkPickerOpened";
14419
14547
  "mediaOpen": "mediaOpen";
14420
14548
  "messageAction": "messageAction";
14421
14549
  "loadEarlier": "loadEarlier";