@frame-kit/ui-ng 0.2.4 → 0.2.6

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": "@frame-kit/ui-ng",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Style-agnostic, token-driven Angular UI component library for FrameKit.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,6 +40,19 @@ interface TableConfig<T = unknown> {
40
40
  emptyMessage: string;
41
41
  trackBy: (index: number, row: T) => unknown;
42
42
  rowStyleFn: ((row: T, index: number) => Record<string, string> | null) | null;
43
+ /**
44
+ * Groups consecutive rows under a heading. Returns the group key for a row,
45
+ * or `null` to leave it ungrouped; a heading is emitted wherever the key
46
+ * changes from the previous row.
47
+ *
48
+ * Grouping reads the rows in the order they are already sorted — it does not
49
+ * reorder them. Sort by the same field you group on, or the same group will
50
+ * open more than once.
51
+ *
52
+ * Mutually exclusive with virtualization, which assumes every row is
53
+ * `rowHeight` tall; a heading is not. Setting this turns virtualization off.
54
+ */
55
+ groupBy: ((row: T) => string | null) | null;
43
56
  virtualization: boolean | 'auto';
44
57
  virtualizationThreshold: number;
45
58
  rowHeight: number;
@@ -245,6 +258,15 @@ declare class DataTableComponent<T = unknown> {
245
258
  readonly expandableRow: _angular_core.InputSignal<((row: T) => boolean) | null>;
246
259
  /** Accessible label for each row's expansion chevron. */
247
260
  readonly expansionLabel: _angular_core.InputSignal<string>;
261
+ /**
262
+ * Template rendered inside a group heading. Receives the group key and the
263
+ * first row of the group, so a heading can show more than the key it was
264
+ * grouped by. Omit it and the key is rendered as text.
265
+ */
266
+ readonly groupHeadingRenderer: _angular_core.InputSignal<TemplateRef<{
267
+ $implicit: string;
268
+ row: T;
269
+ }> | null>;
248
270
  /** Fires when the user clicks a sortable column header, emitting the new sort state. */
249
271
  readonly sortChange: _angular_core.OutputEmitterRef<SortState>;
250
272
  /** Fires when a filter changes, emitting the updated filter state. */
@@ -269,6 +291,11 @@ declare class DataTableComponent<T = unknown> {
269
291
  readonly useVirtualization: _angular_core.Signal<boolean>;
270
292
  readonly virtualRows: _angular_core.Signal<T[]>;
271
293
  readonly virtualStartIndex: _angular_core.Signal<number>;
294
+ /**
295
+ * How many cells a heading has to span — the visible columns plus whichever
296
+ * of the selection and expansion cells the table is currently rendering.
297
+ */
298
+ readonly groupHeadingSpan: _angular_core.Signal<number>;
272
299
  readonly isEmpty: _angular_core.Signal<boolean>;
273
300
  readonly classes: _angular_core.Signal<string>;
274
301
  get hostClass(): string;
@@ -282,6 +309,8 @@ declare class DataTableComponent<T = unknown> {
282
309
  */
283
310
  private readonly groupCoordinator;
284
311
  constructor();
312
+ /** The group heading to open before this row, if it starts a new group. */
313
+ groupHeadingFor(row: T, index: number): string | null;
285
314
  /** Resolves the display value for a cell given its column definition and row data. */
286
315
  getCellValue(column: ColumnDef<T>, row: T): unknown;
287
316
  /** Returns the trackBy function from the merged config for use in `*ngFor`. */
@@ -372,7 +401,7 @@ declare class DataTableComponent<T = unknown> {
372
401
  /** Returns the current sort direction for the given column id, or null if not sorted. */
373
402
  getSortDirection(columnId: string): 'asc' | 'desc' | null;
374
403
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DataTableComponent<any>, never>;
375
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DataTableComponent<any>, "fk-data-table", never, { "className": { "alias": "className"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "columns": { "alias": "columns"; "required": false; "isSignal": true; }; "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "sortState": { "alias": "sortState"; "required": false; "isSignal": true; }; "filterState": { "alias": "filterState"; "required": false; "isSignal": true; }; "pageState": { "alias": "pageState"; "required": false; "isSignal": true; }; "rowSelection": { "alias": "rowSelection"; "required": false; "isSignal": true; }; "selectedKeys": { "alias": "selectedKeys"; "required": false; "isSignal": true; }; "getRowId": { "alias": "getRowId"; "required": false; "isSignal": true; }; "selectionDisabled": { "alias": "selectionDisabled"; "required": false; "isSignal": true; }; "selectionColumnPosition": { "alias": "selectionColumnPosition"; "required": false; "isSignal": true; }; "selectionLabel": { "alias": "selectionLabel"; "required": false; "isSignal": true; }; "rowDetailTemplate": { "alias": "rowDetailTemplate"; "required": false; "isSignal": true; }; "expansionColumnPosition": { "alias": "expansionColumnPosition"; "required": false; "isSignal": true; }; "expandedKeys": { "alias": "expandedKeys"; "required": false; "isSignal": true; }; "expandableRow": { "alias": "expandableRow"; "required": false; "isSignal": true; }; "expansionLabel": { "alias": "expansionLabel"; "required": false; "isSignal": true; }; }, { "sortChange": "sortChange"; "filterChange": "filterChange"; "pageChange": "pageChange"; "rowClick": "rowClick"; "selectionChange": "selectionChange"; "expansionChange": "expansionChange"; "columnResize": "columnResize"; }, never, never, true, never>;
404
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DataTableComponent<any>, "fk-data-table", never, { "className": { "alias": "className"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "columns": { "alias": "columns"; "required": false; "isSignal": true; }; "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "sortState": { "alias": "sortState"; "required": false; "isSignal": true; }; "filterState": { "alias": "filterState"; "required": false; "isSignal": true; }; "pageState": { "alias": "pageState"; "required": false; "isSignal": true; }; "rowSelection": { "alias": "rowSelection"; "required": false; "isSignal": true; }; "selectedKeys": { "alias": "selectedKeys"; "required": false; "isSignal": true; }; "getRowId": { "alias": "getRowId"; "required": false; "isSignal": true; }; "selectionDisabled": { "alias": "selectionDisabled"; "required": false; "isSignal": true; }; "selectionColumnPosition": { "alias": "selectionColumnPosition"; "required": false; "isSignal": true; }; "selectionLabel": { "alias": "selectionLabel"; "required": false; "isSignal": true; }; "rowDetailTemplate": { "alias": "rowDetailTemplate"; "required": false; "isSignal": true; }; "expansionColumnPosition": { "alias": "expansionColumnPosition"; "required": false; "isSignal": true; }; "expandedKeys": { "alias": "expandedKeys"; "required": false; "isSignal": true; }; "expandableRow": { "alias": "expandableRow"; "required": false; "isSignal": true; }; "expansionLabel": { "alias": "expansionLabel"; "required": false; "isSignal": true; }; "groupHeadingRenderer": { "alias": "groupHeadingRenderer"; "required": false; "isSignal": true; }; }, { "sortChange": "sortChange"; "filterChange": "filterChange"; "pageChange": "pageChange"; "rowClick": "rowClick"; "selectionChange": "selectionChange"; "expansionChange": "expansionChange"; "columnResize": "columnResize"; }, never, never, true, never>;
376
405
  }
377
406
 
378
407
  /**
@@ -446,6 +475,14 @@ declare class DataTableStackComponent<T = unknown> {
446
475
  readonly config: _angular_core.InputSignal<TableConfig<T> | null>;
447
476
  /** TrackBy function used by the `@for` loop over rows. */
448
477
  readonly trackByFn: _angular_core.InputSignal<(index: number, row: T) => unknown>;
478
+ /**
479
+ * Template rendered inside a group heading. Mirrors the semantic-table path
480
+ * so a grouped table looks grouped at every viewport.
481
+ */
482
+ readonly groupHeadingRenderer: _angular_core.InputSignal<TemplateRef<{
483
+ $implicit: string;
484
+ row: T;
485
+ }> | null>;
449
486
  /** Mirrors the host table's `rowSelection` input to show checkboxes on each card. */
450
487
  readonly rowSelection: _angular_core.InputSignal<boolean>;
451
488
  /** Currently selected row keys forwarded from the host table engine. */
@@ -456,6 +493,8 @@ declare class DataTableStackComponent<T = unknown> {
456
493
  readonly selectionDisabled: _angular_core.InputSignal<SelectionDisabled<T>>;
457
494
  /** Accessible label for each card's selection checkbox. */
458
495
  readonly selectionLabel: _angular_core.InputSignal<string>;
496
+ /** The group heading to open before this card, if it starts a new group. */
497
+ groupHeadingFor(row: T, index: number): string | null;
459
498
  /** Fires when the user clicks a card row, emitting the row context. */
460
499
  readonly rowClick: _angular_core.OutputEmitterRef<TableRowContext<T>>;
461
500
  /** Fires when the user toggles a card's selection checkbox, bubbling up to the host. */
@@ -475,7 +514,7 @@ declare class DataTableStackComponent<T = unknown> {
475
514
  isRowSelectionDisabled(row: T): boolean;
476
515
  private getSelectionKey;
477
516
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DataTableStackComponent<any>, never>;
478
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DataTableStackComponent<any>, "fk-data-table-stack", never, { "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "columns": { "alias": "columns"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "trackByFn": { "alias": "trackByFn"; "required": false; "isSignal": true; }; "rowSelection": { "alias": "rowSelection"; "required": false; "isSignal": true; }; "selectedKeys": { "alias": "selectedKeys"; "required": false; "isSignal": true; }; "getRowId": { "alias": "getRowId"; "required": false; "isSignal": true; }; "selectionDisabled": { "alias": "selectionDisabled"; "required": false; "isSignal": true; }; "selectionLabel": { "alias": "selectionLabel"; "required": false; "isSignal": true; }; }, { "rowClick": "rowClick"; "toggleRow": "toggleRow"; }, never, never, true, never>;
517
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DataTableStackComponent<any>, "fk-data-table-stack", never, { "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "columns": { "alias": "columns"; "required": false; "isSignal": true; }; "config": { "alias": "config"; "required": false; "isSignal": true; }; "trackByFn": { "alias": "trackByFn"; "required": false; "isSignal": true; }; "groupHeadingRenderer": { "alias": "groupHeadingRenderer"; "required": false; "isSignal": true; }; "rowSelection": { "alias": "rowSelection"; "required": false; "isSignal": true; }; "selectedKeys": { "alias": "selectedKeys"; "required": false; "isSignal": true; }; "getRowId": { "alias": "getRowId"; "required": false; "isSignal": true; }; "selectionDisabled": { "alias": "selectionDisabled"; "required": false; "isSignal": true; }; "selectionLabel": { "alias": "selectionLabel"; "required": false; "isSignal": true; }; }, { "rowClick": "rowClick"; "toggleRow": "toggleRow"; }, never, never, true, never>;
479
518
  }
480
519
 
481
520
  export { ColumnResizeDirective, DEFAULT_TABLE_CONFIG, DataTableComponent, DataTableGroupComponent, DataTableGroupCoordinator, DataTableStackComponent, ResponsiveStrategyService, TableEngineService, VirtualizationEngineService };
@@ -8,24 +8,25 @@ A token-driven, responsive data table component for Angular with sorting, virtua
8
8
 
9
9
  ### Inputs
10
10
 
11
- | Input | Type | Default | Description |
12
- | ------------------------- | ---------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------- |
13
- | `columns` | `ColumnDef<T>[]` | `[]` | Column definitions describing headers, accessors, and behavior |
14
- | `rows` | `T[]` | `[]` | Data rows to render |
15
- | `config` | `Partial<TableConfig<T>>` | `{}` | Table configuration merged with defaults |
16
- | `loading` | `boolean` | `false` | Shows a loading overlay |
17
- | `sortState` | `SortState \| null` | `null` | Controlled sort state; `null` uses internal state |
18
- | `filterState` | `FilterState \| null` | `null` | Controlled filter state; `null` uses internal state |
19
- | `pageState` | `PageState \| null` | `null` | Controlled page state; `null` uses internal state |
20
- | `rowSelection` | `boolean` | `false` | Render a leading (or trailing) checkbox column with multi-row selection |
21
- | `selectedKeys` | `Set<unknown> \| null` | `null` | Controlled selection set; `null` uses internal state. Table never mutates the input — emits new Sets |
22
- | `getRowId` | `(row: T) => unknown` \| `null` | `null` | Required when `rowSelection` is `true`. Returns each row's stable identity key |
23
- | `selectionDisabled` | `boolean \| ((row: T) => boolean)` | `false` | Per-row opt-out. Disabled rows can't be selected and are skipped by the header "select all" |
24
- | `selectionColumnPosition` | `'start' \| 'end'` | `'start'` | Render the checkbox column at the leading or trailing edge |
25
- | `selectionLabel` | `string` | `'Select row'` | `aria-label` applied to each row checkbox |
26
- | `className` | `string` | `''` | Additional CSS classes for the host |
27
- | `id` | `string \| null` | `null` | Optional ID for the host element |
28
- | `ariaLabel` | `string \| null` | `null` | ARIA label for the table |
11
+ | Input | Type | Default | Description |
12
+ | ------------------------- | ---------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------- |
13
+ | `columns` | `ColumnDef<T>[]` | `[]` | Column definitions describing headers, accessors, and behavior |
14
+ | `rows` | `T[]` | `[]` | Data rows to render |
15
+ | `config` | `Partial<TableConfig<T>>` | `{}` | Table configuration merged with defaults |
16
+ | `loading` | `boolean` | `false` | Shows a loading overlay |
17
+ | `sortState` | `SortState \| null` | `null` | Controlled sort state; `null` uses internal state |
18
+ | `filterState` | `FilterState \| null` | `null` | Controlled filter state; `null` uses internal state |
19
+ | `pageState` | `PageState \| null` | `null` | Controlled page state; `null` uses internal state |
20
+ | `rowSelection` | `boolean` | `false` | Render a leading (or trailing) checkbox column with multi-row selection |
21
+ | `selectedKeys` | `Set<unknown> \| null` | `null` | Controlled selection set; `null` uses internal state. Table never mutates the input — emits new Sets |
22
+ | `getRowId` | `(row: T) => unknown` \| `null` | `null` | Required when `rowSelection` is `true`. Returns each row's stable identity key |
23
+ | `selectionDisabled` | `boolean \| ((row: T) => boolean)` | `false` | Per-row opt-out. Disabled rows can't be selected and are skipped by the header "select all" |
24
+ | `selectionColumnPosition` | `'start' \| 'end'` | `'start'` | Render the checkbox column at the leading or trailing edge |
25
+ | `selectionLabel` | `string` | `'Select row'` | `aria-label` applied to each row checkbox |
26
+ | `groupHeadingRenderer` | `TemplateRef<{ $implicit: string; row: T }> \| null` | `null` | Template for a group heading; omit to render the group key as text |
27
+ | `className` | `string` | `''` | Additional CSS classes for the host |
28
+ | `id` | `string \| null` | `null` | Optional ID for the host element |
29
+ | `ariaLabel` | `string \| null` | `null` | ARIA label for the table |
29
30
 
30
31
  ### Outputs
31
32
 
@@ -74,6 +75,7 @@ A token-driven, responsive data table component for Angular with sorting, virtua
74
75
  | `rowExpansion` | `boolean` | `false` | Enable row expansion |
75
76
  | `emptyMessage` | `string` | `'No data available'` | Message shown when no rows exist |
76
77
  | `trackBy` | `(index: number, row: T) => unknown` | `(index) => index` | Track-by function for row rendering |
78
+ | `groupBy` | `((row: T) => string \| null) \| null` | `null` | Group consecutive rows under a heading (see below) |
77
79
  | `virtualization` | `boolean \| 'auto'` | `'auto'` | Virtual scrolling mode |
78
80
  | `virtualizationThreshold` | `number` | `200` | Row count threshold for auto-activating virtualization |
79
81
  | `rowHeight` | `number` | `48` | Fixed row height in px (required for virtual scrolling) |
@@ -235,6 +237,39 @@ isLocked = (row: Order) => row.status === 'Closed';
235
237
  <fk-data-table [columns]="columns" [rows]="[]" [config]="{ emptyMessage: 'No records found' }" />
236
238
  ```
237
239
 
240
+ ### Row grouping
241
+
242
+ Rows sharing a group key render under a heading spanning the full table:
243
+
244
+ ```html
245
+ <fk-data-table [columns]="columns" [rows]="events" [config]="{ groupBy: byDay }" />
246
+ ```
247
+
248
+ ```ts
249
+ byDay = (row: Event) => this.formatDay(row.created_at);
250
+ ```
251
+
252
+ Return `null` from `groupBy` to leave a row ungrouped.
253
+
254
+ Grouping reads the rows in the order it is handed them — it does not reorder.
255
+ Sort by the same field you group on, or a group will open more than once.
256
+
257
+ For a heading richer than the key, pass a template:
258
+
259
+ ```html
260
+ <fk-data-table [columns]="columns" [rows]="events" [config]="{ groupBy: byDay }" [groupHeadingRenderer]="dayHeading" />
261
+
262
+ <ng-template #dayHeading let-key let-row="row"> {{ key }} <span class="muted">{{ row.created_at | date }}</span> </ng-template>
263
+ ```
264
+
265
+ Headings render on both the semantic-table and stack (small-viewport) paths, so
266
+ a grouped table stays grouped at every width.
267
+
268
+ > **Grouping and virtualization are mutually exclusive.** Virtual scrolling
269
+ > positions rows by multiplying an index by `rowHeight`, and a heading is not
270
+ > that tall. Setting `groupBy` turns virtualization off rather than misplacing
271
+ > every row after the first heading.
272
+
238
273
  ### Virtual scrolling
239
274
 
240
275
  Virtual scrolling activates automatically when row count exceeds `virtualizationThreshold` (default 200). Force it on or off:
@@ -341,6 +376,15 @@ Pinned columns stick to the left or right edge when scrolling horizontally.
341
376
  --fk-data-table-row-hover-bg
342
377
  --fk-data-table-viewport-height
343
378
 
379
+ // Group headings
380
+ --fk-data-table-group-heading-bg
381
+ --fk-data-table-group-heading-color
382
+ --fk-data-table-group-heading-font-size
383
+ --fk-data-table-group-heading-font-weight
384
+ --fk-data-table-group-heading-letter-spacing
385
+ --fk-data-table-group-heading-text-transform
386
+ --fk-data-table-group-heading-padding
387
+
344
388
  // Empty & Loading
345
389
  --fk-data-table-empty-padding
346
390
  --fk-data-table-empty-color