@truenas/ui-components 0.7.6 → 0.7.8

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": "@truenas/ui-components",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public"
@@ -54,8 +54,8 @@ declare function kebabTestSegment(part: string | number): string;
54
54
  * re-implemented at each call site.
55
55
  *
56
56
  * Note this is *base-first*: the suffix trails the base. Fixed chrome whose role
57
- * should lead (e.g. `tn-dialog-shell`'s `button-close-<base>`) prepends the role
58
- * manually instead of calling this.
57
+ * should lead (e.g. `tn-dialog-shell`'s `button-close-<base>` and
58
+ * `dialog-title-<base>`) prepends the role manually instead of calling this.
59
59
  *
60
60
  * Falsy segments are preserved here (not filtered) so `composeTestId` can apply
61
61
  * its own drop/scoping rules — `scopeTestId(undefined, 'edit')` yields
@@ -1840,6 +1840,16 @@ declare class TnBannerComponent {
1840
1840
  message: _angular_core.InputSignal<string | undefined>;
1841
1841
  type: _angular_core.InputSignal<TnBannerType>;
1842
1842
  bordered: _angular_core.InputSignal<boolean>;
1843
+ /**
1844
+ * Semantic base for the banner's test id, rendered onto the banner root as
1845
+ * `banner-<testId>` through {@link TnTestIdDirective} — so the value follows
1846
+ * the library's own naming and honours `TN_TEST_ATTR`.
1847
+ *
1848
+ * The root, rather than the host element, because that is what carries the
1849
+ * live-region role: the element a test asserts a warning on is the same one
1850
+ * assistive tech announces.
1851
+ */
1852
+ testId: _angular_core.InputSignal<TnTestIdValue>;
1843
1853
  constructor();
1844
1854
  /**
1845
1855
  * Register all MDI icons used by the banner component
@@ -1865,7 +1875,7 @@ declare class TnBannerComponent {
1865
1875
  */
1866
1876
  classes: _angular_core.Signal<string[]>;
1867
1877
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnBannerComponent, never>;
1868
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnBannerComponent, "tn-banner", never, { "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "message": { "alias": "message"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; }, {}, ["actionContent"], ["*", "[tnBannerAction]"], true, never>;
1878
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnBannerComponent, "tn-banner", never, { "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "message": { "alias": "message"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, {}, ["actionContent"], ["*", "[tnBannerAction]"], true, never>;
1869
1879
  }
1870
1880
 
1871
1881
  /**
@@ -9713,6 +9723,36 @@ declare class TnTableComponent<T = unknown> implements OnInit {
9713
9723
  * `rowKey = (row: Job) => row.id` on the host.
9714
9724
  */
9715
9725
  expansionKey: _angular_core.InputSignal<((row: T) => unknown) | undefined>;
9726
+ /**
9727
+ * Test id for each ROW, as a function of the row and its index.
9728
+ *
9729
+ * The table writes no id on a `<tr>` of its own, and a cell body is free to be bare
9730
+ * interpolation — `{{ row.username }}` renders no element the consumer can bind to. A list
9731
+ * built that way is unaddressable: an automated suite that has to open, select or delete one
9732
+ * specific row has nothing to select it by but position or cell text, both of which change
9733
+ * under the test. Set this and every row carries `row-<base>` on the `<tr>`, and on its card
9734
+ * in card mode, through the same {@link TnTestIdDirective} as every other id the library
9735
+ * emits — so it honours {@link TN_TEST_ATTR} and is kebab-cased by {@link composeTestId}.
9736
+ *
9737
+ * Pass the base only, not the `row-` prefix, and identify the ROW rather than its position,
9738
+ * the way {@link selectionKey} and {@link expansionKey} do — an index-derived id renames every
9739
+ * row below the one that was deleted. The index is passed for the case where the data genuinely
9740
+ * has nothing unique in it.
9741
+ *
9742
+ * Bind a stable member rather than writing the function in the template, since Angular's
9743
+ * expression grammar has no arrow functions:
9744
+ * `[rowTestId]="rowTestId"` for a `rowTestId = (row: User) => row.username` on the host.
9745
+ *
9746
+ * ```html
9747
+ * <tn-table [dataSource]="users" [rowTestId]="rowTestId" />
9748
+ * <!-- <tr data-testid="row-jane-doe"> -->
9749
+ * ```
9750
+ *
9751
+ * Cells are not covered by this: a suite that has to read one cell of a row still needs an id
9752
+ * on the element that cell renders. Tagging the row is what makes the row itself reachable —
9753
+ * which is the part a consumer cannot add from the outside.
9754
+ */
9755
+ rowTestId: _angular_core.InputSignal<((row: T, index: number) => TnTestIdValue) | undefined>;
9716
9756
  emptyMessage: _angular_core.InputSignal<string>;
9717
9757
  /**
9718
9758
  * Optional second line under `emptyMessage`, for empty states that carry both a headline and an
@@ -9988,6 +10028,16 @@ declare class TnTableComponent<T = unknown> implements OnInit {
9988
10028
  /** True when the layout should keep the table but pin edge columns and scroll. */
9989
10029
  isScrollMode: Signal<boolean>;
9990
10030
  data: Signal<T[]>;
10031
+ /**
10032
+ * The resolved base for each row, by index.
10033
+ *
10034
+ * Computed once per `data()` change rather than called per row per change-detection pass: a
10035
+ * base is usually an array, every call builds a new one, and a new reference restarts the
10036
+ * directive's effect — so binding the call directly would rewrite every row's attribute on
10037
+ * every pass. Recomputing here on the signal the rows are rendered from keeps the value
10038
+ * identical between passes, and correct after a reload hands back new row objects.
10039
+ */
10040
+ protected readonly rowTestIds: Signal<TnTestIdValue[]>;
9991
10041
  effectiveDisplayedColumns: Signal<string[]>;
9992
10042
  /**
9993
10043
  * Every column the table actually renders, including the trailing actions column, which
@@ -10286,7 +10336,7 @@ declare class TnTableComponent<T = unknown> implements OnInit {
10286
10336
  getCardLabel(column: string): string;
10287
10337
  getCellValue(row: T, column: string): unknown;
10288
10338
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<TnTableComponent<any>, never>;
10289
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnTableComponent<any>, "tn-table", never, { "dataSource": { "alias": "dataSource"; "required": false; "isSignal": true; }; "displayedColumns": { "alias": "displayedColumns"; "required": false; "isSignal": true; }; "trackBy": { "alias": "trackBy"; "required": false; "isSignal": true; }; "selectionKey": { "alias": "selectionKey"; "required": false; "isSignal": true; }; "expansionKey": { "alias": "expansionKey"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "emptyDescription": { "alias": "emptyDescription"; "required": false; "isSignal": true; }; "emptyIcon": { "alias": "emptyIcon"; "required": false; "isSignal": true; }; "scrollRegionAriaLabel": { "alias": "scrollRegionAriaLabel"; "required": false; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; "expandable": { "alias": "expandable"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "isRowExpandable": { "alias": "isRowExpandable"; "required": false; "isSignal": true; }; "activeRow": { "alias": "activeRow"; "required": false; "isSignal": true; }; "activeWhen": { "alias": "activeWhen"; "required": false; "isSignal": true; }; "activeBg": { "alias": "activeBg"; "required": false; "isSignal": true; }; "activeIndicator": { "alias": "activeIndicator"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "loadingMessage": { "alias": "loadingMessage"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "expandOnRowClick": { "alias": "expandOnRowClick"; "required": false; "isSignal": true; }; "singleExpand": { "alias": "singleExpand"; "required": false; "isSignal": true; }; "fixedLayout": { "alias": "fixedLayout"; "required": false; "isSignal": true; }; "minColumnWidth": { "alias": "minColumnWidth"; "required": false; "isSignal": true; }; "minWidth": { "alias": "minWidth"; "required": false; "isSignal": true; }; "mobileLayout": { "alias": "mobileLayout"; "required": false; "isSignal": true; }; "cardBreakpoint": { "alias": "cardBreakpoint"; "required": false; "isSignal": true; }; "cardPrimaryCount": { "alias": "cardPrimaryCount"; "required": false; "isSignal": true; }; "sortColumn": { "alias": "sortColumn"; "required": false; "isSignal": true; }; "sortDirection": { "alias": "sortDirection"; "required": false; "isSignal": true; }; }, { "sortChange": "sortChange"; "selectionChange": "selectionChange"; "rowClick": "rowClick"; "rowDoubleClick": "rowDoubleClick"; "sortColumn": "sortColumnChange"; "sortDirection": "sortDirectionChange"; }, ["columnDefs", "detailRowDef", "rowActionsDef"], never, true, [{ directive: typeof TnTestIdDirective; inputs: { "tnTestId": "testId"; }; outputs: {}; }]>;
10339
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<TnTableComponent<any>, "tn-table", never, { "dataSource": { "alias": "dataSource"; "required": false; "isSignal": true; }; "displayedColumns": { "alias": "displayedColumns"; "required": false; "isSignal": true; }; "trackBy": { "alias": "trackBy"; "required": false; "isSignal": true; }; "selectionKey": { "alias": "selectionKey"; "required": false; "isSignal": true; }; "expansionKey": { "alias": "expansionKey"; "required": false; "isSignal": true; }; "rowTestId": { "alias": "rowTestId"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "emptyDescription": { "alias": "emptyDescription"; "required": false; "isSignal": true; }; "emptyIcon": { "alias": "emptyIcon"; "required": false; "isSignal": true; }; "scrollRegionAriaLabel": { "alias": "scrollRegionAriaLabel"; "required": false; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; "expandable": { "alias": "expandable"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "isRowExpandable": { "alias": "isRowExpandable"; "required": false; "isSignal": true; }; "activeRow": { "alias": "activeRow"; "required": false; "isSignal": true; }; "activeWhen": { "alias": "activeWhen"; "required": false; "isSignal": true; }; "activeBg": { "alias": "activeBg"; "required": false; "isSignal": true; }; "activeIndicator": { "alias": "activeIndicator"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "loadingMessage": { "alias": "loadingMessage"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; "expandOnRowClick": { "alias": "expandOnRowClick"; "required": false; "isSignal": true; }; "singleExpand": { "alias": "singleExpand"; "required": false; "isSignal": true; }; "fixedLayout": { "alias": "fixedLayout"; "required": false; "isSignal": true; }; "minColumnWidth": { "alias": "minColumnWidth"; "required": false; "isSignal": true; }; "minWidth": { "alias": "minWidth"; "required": false; "isSignal": true; }; "mobileLayout": { "alias": "mobileLayout"; "required": false; "isSignal": true; }; "cardBreakpoint": { "alias": "cardBreakpoint"; "required": false; "isSignal": true; }; "cardPrimaryCount": { "alias": "cardPrimaryCount"; "required": false; "isSignal": true; }; "sortColumn": { "alias": "sortColumn"; "required": false; "isSignal": true; }; "sortDirection": { "alias": "sortDirection"; "required": false; "isSignal": true; }; }, { "sortChange": "sortChange"; "selectionChange": "selectionChange"; "rowClick": "rowClick"; "rowDoubleClick": "rowDoubleClick"; "sortColumn": "sortColumnChange"; "sortDirection": "sortDirectionChange"; }, ["columnDefs", "detailRowDef", "rowActionsDef"], never, true, [{ directive: typeof TnTestIdDirective; inputs: { "tnTestId": "testId"; }; outputs: {}; }]>;
10290
10340
  }
10291
10341
 
10292
10342
  /**
@@ -10491,6 +10541,17 @@ declare class TnTableHarness extends ComponentHarness {
10491
10541
  * @returns Promise resolving to true if the loading overlay is visible.
10492
10542
  */
10493
10543
  isLoading(): Promise<boolean>;
10544
+ /**
10545
+ * Gets the test id written on a data row by `[rowTestId]`.
10546
+ *
10547
+ * Layout-aware, like the rest of the row queries: reads the card in the card layout, which
10548
+ * carries the same id. Reads `data-testid` first and falls through to `data-test`, so it
10549
+ * answers the same either way the consumer configured `TN_TEST_ATTR`.
10550
+ *
10551
+ * @param rowIndex Zero-based index of the data row (or card).
10552
+ * @returns Promise resolving to the row's test id, or null when `[rowTestId]` is unset.
10553
+ */
10554
+ getRowTestId(rowIndex: number): Promise<string | null>;
10494
10555
  /**
10495
10556
  * Checks if a data row is currently marked active.
10496
10557
  *
@@ -14381,9 +14442,10 @@ declare class TnDialogShellComponent implements OnInit {
14381
14442
  /** Hide the actions footer. Same wrapper-case rationale as {@link hideContent}. */
14382
14443
  hideActions: _angular_core.InputSignal<boolean>;
14383
14444
  /**
14384
- * Optional semantic base that scopes the shell's chrome buttons. The close
14385
- * and fullscreen buttons emit `button-close` / `button-fullscreen` by default,
14386
- * or `button-close-<testId>` / `button-fullscreen-<testId>` when a base is
14445
+ * Optional semantic base that scopes the shell's chrome. The close and
14446
+ * fullscreen buttons emit `button-close` / `button-fullscreen` by default, and
14447
+ * the title heading `dialog-title`, or `button-close-<testId>` /
14448
+ * `button-fullscreen-<testId>` / `dialog-title-<testId>` when a base is
14387
14449
  * provided (useful when more than one dialog can be open).
14388
14450
  */
14389
14451
  testId: _angular_core.InputSignal<TnTestIdValue>;
@@ -14422,6 +14484,19 @@ declare class TnDialogShellComponent implements OnInit {
14422
14484
  protected closeTestId: Signal<(string | number | null | undefined)[]>;
14423
14485
  /** Role-first test-id segments for the fullscreen button: `button-fullscreen[-<base>]`. */
14424
14486
  protected fullscreenTestId: Signal<(string | number | null | undefined)[]>;
14487
+ /**
14488
+ * Role-first test-id segments for the title heading: `dialog-title[-<base>]`.
14489
+ *
14490
+ * The heading is fixed chrome like the two buttons, so it follows the same
14491
+ * role-first shape — and a test that knows a dialog's base knows its title's
14492
+ * id without being told separately, which is the point: dialogs raised
14493
+ * through one generic error path are otherwise indistinguishable by id, and
14494
+ * the only remaining handle is a match on their prose.
14495
+ *
14496
+ * There is no id when there is no title: the heading is not rendered at all
14497
+ * (see the template), so nothing is left behind to select.
14498
+ */
14499
+ protected titleTestId: Signal<(string | number | null | undefined)[]>;
14425
14500
  /** Stable id for the title heading, referenced by the dialog's aria-labelledby. */
14426
14501
  readonly titleId: string;
14427
14502
  isFullscreen: _angular_core.WritableSignal<boolean>;